1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2 
3 /*
4  * creg - Code compoment registration system
5  * Classes for serialization of registrated class instances
6  */
7 
8 #define LOG_SECTION_CREG_SERIALIZER "CregSerializer"
9 
10 #include "creg_cond.h"
11 #include "Serializer.h"
12 
13 #include "System/Log/ILog.h"
14 #include "System/Platform/byteorder.h"
15 #include "System/Exceptions.h"
16 
17 #include <fstream>
18 #include <assert.h>
19 #include <stdexcept>
20 #include <map>
21 #include <vector>
22 #include <string>
23 #include <string.h>
24 
25 using namespace creg;
26 using std::string;
27 using std::map;
28 using std::vector;
29 
30 LOG_REGISTER_SECTION_GLOBAL(LOG_SECTION_CREG_SERIALIZER)
31 
32 //
33 #define CREG_PACKAGE_FILE_ID "CRPK"
34 
35 // File format structures
36 struct PackageHeader
37 {
38 	char magic[4];
39 	int objDataOffset;
40 	int objTableOffset;
41 	int numObjects;
42 	int objClassRefOffset; // a class ref is: zero-term class string + checksum DWORD
43 	int numObjClassRefs;
44 	unsigned int metadataChecksum;
45 
SwapBytesPackageHeader46 	void SwapBytes()
47 	{
48 		swabDWordInPlace(objDataOffset);
49 		swabDWordInPlace(objTableOffset);
50 		swabDWordInPlace(objClassRefOffset);
51 		swabDWordInPlace(numObjClassRefs);
52 		swabDWordInPlace(numObjects);
53 		swabDWordInPlace(metadataChecksum);
54 	}
PackageHeaderPackageHeader55 	PackageHeader():
56 		objDataOffset(0),
57 		objTableOffset(0),
58 		numObjects(0),
59 		objClassRefOffset(0),
60 		numObjClassRefs(0),
61 		metadataChecksum(0)
62 	{
63 		magic[0] = 0;
64 		magic[1] = 0;
65 		magic[2] = 0;
66 		magic[3] = 0;
67 	}
68 };
69 
70 
71 
ReadZStr(std::istream & file)72 static std::string ReadZStr(std::istream& file)
73 {
74 	char cstr[1024];
75 	file.getline(cstr, sizeof(cstr), 0);
76 	return std::string(cstr);
77 }
78 
WriteZStr(std::ostream & file,const std::string & str)79 static void WriteZStr(std::ostream& file, const std::string& str)
80 {
81 	assert(str.length() < 1024); // check func above!
82 	file.write(str.c_str(), str.length() + 1);
83 }
84 
WriteVarSizeUInt(std::ostream * stream,unsigned int val)85 void WriteVarSizeUInt(std::ostream* stream, unsigned int val)
86 {
87 	if (val < 0x80) {
88 		unsigned char a = val;
89 		stream->write((char*)&a, sizeof(char));
90 	} else if (val < 0x4000) {
91 		unsigned char a = (val & 0x7F) | 0x80;
92 		unsigned char b = val >> 7;
93 		stream->write((char*)&a, sizeof(char));
94 		stream->write((char*)&b, sizeof(char));
95 	} else if (val < 0x40000000) {
96 		unsigned char a = (val & 0x7F) | 0x80;
97 		unsigned char b = ((val >> 7) & 0x7F) | 0x80;
98 		unsigned short c = swabWord(val >> 14);
99 		stream->write((char*)&a, sizeof(char));
100 		stream->write((char*)&b, sizeof(char));
101 		stream->write((char*)&c, sizeof(short));
102 	} else throw "Cannot save varible-size int";
103 }
104 
ReadVarSizeUInt(std::istream * stream,unsigned int * buf)105 void ReadVarSizeUInt(std::istream* stream, unsigned int* buf)
106 {
107 	unsigned char a;
108 	stream->read((char*)&a, sizeof(char));
109 	if (a & 0x80) {
110 		unsigned char b;
111 		stream->read((char*)&b, sizeof(char));
112 		if (b & 0x80) {
113 			unsigned short c;
114 			stream->read((char*)&c, sizeof(short));
115 			*buf = (a & 0x7F) | ((b & 0x7F) << 7) | (c << 14);
116 		} else {
117 			*buf = (a & 0x7F) | ((b & 0x7F) << 7);
118 		}
119 	} else {
120 		*buf = a & 0x7F;
121 	}
122 }
123 
124 //-------------------------------------------------------------------------
125 // Base output serializer
126 //-------------------------------------------------------------------------
COutputStreamSerializer()127 COutputStreamSerializer::COutputStreamSerializer()
128 {
129 	stream = NULL;
130 }
131 
IsWriting()132 bool COutputStreamSerializer::IsWriting()
133 {
134 	return true;
135 }
136 
FindObjectRef(void * inst,creg::Class * objClass,bool isEmbedded)137 COutputStreamSerializer::ObjectRef* COutputStreamSerializer::FindObjectRef(void* inst, creg::Class* objClass, bool isEmbedded)
138 {
139 	std::vector<ObjectRef*>* refs = &(ptrToId[inst]);
140 	for (std::vector<ObjectRef*>::iterator i = refs->begin(); i != refs->end(); ++i) {
141 		if ((*i)->isThisObject(inst, objClass, isEmbedded))
142 			return *i;
143 	}
144 	return NULL;
145 }
146 
SerializeObject(Class * c,void * ptr,ObjectRef * objr)147 void COutputStreamSerializer::SerializeObject(Class* c, void* ptr, ObjectRef* objr)
148 {
149 	if (c->base)
150 		SerializeObject(c->base, ptr, objr);
151 
152 	ObjectMemberGroup omg;
153 	omg.membersClass = c;
154 
155 	for (uint a = 0; a < c->members.size(); a++)
156 	{
157 		creg::Class::Member* m = c->members[a];
158 		if (m->flags & CM_NoSerialize)
159 			continue;
160 
161 		ObjectMember om;
162 		om.member = m;
163 		om.memberId = a;
164 		void* memberAddr = ((char*)ptr) + m->offset;
165 		unsigned mstart = stream->tellp();
166 		LOG_SL(LOG_SECTION_CREG_SERIALIZER, L_DEBUG, "Serialized %s::%s type:%s", c->name.c_str(), m->name, m->type->GetName().c_str());
167 		m->type->Serialize(this, memberAddr);
168 		unsigned mend = stream->tellp();
169 		om.size = mend - mstart;
170 		omg.members.push_back(om);
171 		omg.size += om.size;
172 		LOG_SL(LOG_SECTION_CREG_SERIALIZER, L_DEBUG, "Serialized %s::%s type:%s size:%d", c->name.c_str(), m->name, m->type->GetName().c_str(), om.size);
173 	}
174 
175 
176 	if (c->serializeProc) {
177 		ObjectMember om;
178 		om.member = NULL;
179 		om.memberId = -1;
180 		unsigned mstart = stream->tellp();
181 		_DummyStruct *obj = (_DummyStruct*)ptr;
182 		(obj->*(c->serializeProc))(*this);
183 		unsigned mend = stream->tellp();
184 		om.size = mend - mstart;
185 		omg.members.push_back(om);
186 		omg.size += om.size;
187 	}
188 
189 	objr->memberGroups.push_back(omg);
190 }
191 
SerializeObjectInstance(void * inst,creg::Class * objClass)192 void COutputStreamSerializer::SerializeObjectInstance(void* inst, creg::Class* objClass)
193 {
194 	// register the object, and mark it as embedded if a pointer was already referencing it
195 	ObjectRef* obj = FindObjectRef(inst, objClass, true);
196 	if (!obj) {
197 		objects.push_back(ObjectRef(inst, objects.size(), true, objClass));
198 		obj = &objects.back();
199 		ptrToId[inst].push_back(obj);
200 	} else if (obj->isEmbedded) {
201 		throw "Reserialization of embedded object (" + objClass->name + ")";
202 	} else {
203 		std::vector<ObjectRef*>::iterator it = std::find(pendingObjects.begin(), pendingObjects.end(), obj);
204 		if (it == pendingObjects.end()) {
205 			throw "Object pointer was serialized (" + objClass->name + ")";
206 		} else {
207 			pendingObjects.erase(it);
208 		}
209 	}
210 	obj->class_ = objClass;
211 	obj->isEmbedded = true;
212 
213 	// write an object ID
214 	WriteVarSizeUInt(stream, obj->id);
215 
216 	// write the object
217 	SerializeObject(objClass, inst, obj);
218 }
219 
SerializeObjectPtr(void ** ptr,creg::Class * objClass)220 void COutputStreamSerializer::SerializeObjectPtr(void** ptr, creg::Class* objClass)
221 {
222 	if (*ptr) {
223 		// valid pointer, write a one and the object ID
224 		int id;
225 		ObjectRef* obj = FindObjectRef(*ptr, objClass, false);
226 		if (!obj) {
227 			objects.push_back(ObjectRef(*ptr, objects.size(), false, objClass));
228 			obj = &objects.back();
229 			ptrToId[*ptr].push_back(obj);
230 			pendingObjects.push_back(obj);
231 		}
232 		id = obj->id;
233 
234 		WriteVarSizeUInt(stream, id);
235 	} else {
236 		// null pointer, write a zero
237 		WriteVarSizeUInt(stream, 0);
238 	}
239 }
240 
Serialize(void * data,int byteSize)241 void COutputStreamSerializer::Serialize(void* data, int byteSize)
242 {
243 	stream->write((char*)data, byteSize);
244 }
245 
SerializeInt(void * data,int byteSize)246 void COutputStreamSerializer::SerializeInt(void* data, int byteSize)
247 {
248 	//FIXME transform to template?
249 	char buf[8];
250 	switch (byteSize) {
251 		case 1: {
252 			*(char*)buf = *(char*) data;
253 			break;
254 		}
255 		case 2: {
256 			*(short*)buf = swabWord(*(short*) data);
257 			break;
258 		}
259 		case 4: {
260 			*(int*)buf = swabDWord(*(int*) data);
261 			break;
262 		}
263 		case 8: {
264 			*(boost::int64_t*)buf = swab64(*(boost::int64_t*) data);
265 			break;
266 		}
267 		default: {
268 			throw "Unknown int type";
269 		}
270 	}
271 	stream->write((char*)data, byteSize); //TODO write buf?
272 }
273 
274 
275 struct COutputStreamSerializer::ClassRef
276 {
277 	int index;
278 	creg::Class* class_;
279 };
280 
SavePackage(std::ostream * s,void * rootObj,Class * rootObjClass)281 void COutputStreamSerializer::SavePackage(std::ostream* s, void* rootObj, Class* rootObjClass)
282 {
283 	PackageHeader ph;
284 
285 	stream = s;
286 	unsigned startOffset = stream->tellp();
287 	stream->write((char*)&ph, sizeof(PackageHeader));
288 	stream->seekp(startOffset + sizeof(PackageHeader));
289 	ph.objDataOffset = (int)stream->tellp();
290 
291 	// Insert dummy object with id 0
292 	objects.push_back(ObjectRef(0, 0, true, 0));
293 	ObjectRef* obj = &objects.back();
294 	obj->classIndex = 0;
295 
296 	// Insert the first object that will provide references to everything
297 	objects.push_back(ObjectRef(rootObj, objects.size(), false, rootObjClass));
298 	obj = &objects.back();
299 	ptrToId[rootObj].push_back(obj);
300 	pendingObjects.push_back(obj);
301 
302 	std::map<creg::Class*, int> classSizes;
303 	// Save until all the referenced objects have been stored
304 	while (!pendingObjects.empty())
305 	{
306 		const std::vector<ObjectRef*> po = pendingObjects;
307 		pendingObjects.clear();
308 
309 		for (std::vector<ObjectRef*>::const_iterator i = po.begin(); i != po.end(); ++i)
310 		{
311 			ObjectRef* obj = *i;
312 			const unsigned objstart = stream->tellp();
313 			SerializeObject(obj->class_, obj->ptr, obj);
314 			const unsigned objend = stream->tellp();
315 			const int sz = objend - objstart;
316 			classSizes[obj->class_] += sz;
317 			LOG_SL(LOG_SECTION_CREG_SERIALIZER, L_DEBUG, "Serialized %s size:%i", obj->class_->name.c_str(), sz);
318 		}
319 	}
320 
321 	// Collect a set of all used classes
322 	std::map<creg::Class*, ClassRef> classMap;
323 	std::vector<ClassRef*> classRefs;
324 	std::map<int, int> classObjects;
325 	for (std::list<ObjectRef>::iterator i = objects.begin(); i != objects.end(); ++i) {
326 		if (i->ptr == NULL) continue;
327 
328 		creg::Class* c = i->class_;
329 		while (c) {
330 			std::map<creg::Class*, ClassRef>::iterator cr = classMap.find(c);
331 			if (cr == classMap.end()) {
332 				ClassRef* pRef = &classMap[c];
333 				pRef->index = classRefs.size();
334 				pRef->class_ = c;
335 				classRefs.push_back(pRef);
336 			}
337 			c = c->base;
338 		}
339 
340 		std::map<creg::Class*, ClassRef>::iterator cr = classMap.find(i->class_);
341 		i->classIndex = cr->second.index;
342 		classObjects[i->classIndex]++;
343 	}
344 
345 /*
346 	if (LOG_IS_ENABLED(L_DEBUG)) {
347 		for (std::map<int, int>::iterator i = classObjects.begin(); i != classObjects.end(); i++) {
348 			LOG_L(L_DEBUG, "%20s %10u %10u",
349 					classRefs[i->first]->class_->name.c_str(),
350 					i->second,
351 					classSizes[classRefs[i->first]->class_]);
352 		}
353 	}
354 */
355 
356 	// Write the class references & calc their checksum
357 	ph.numObjClassRefs = classRefs.size();
358 	ph.objClassRefOffset = (int)stream->tellp();
359 	for (uint a = 0; a < classRefs.size(); a++) {
360 		creg::Class* c =  classRefs[a]->class_;
361 		WriteZStr(*stream, c->name);
362 	};
363 
364 	// Write object info
365 	ph.objTableOffset = (int)stream->tellp();
366 	ph.numObjects = objects.size();
367 	for (std::list<ObjectRef>::iterator i = objects.begin(); i != objects.end(); ++i) {
368 		int classRefIndex = i->classIndex;
369 		char isEmbedded = i->isEmbedded ? 1 : 0;
370 		WriteVarSizeUInt(stream, classRefIndex);
371 		stream->write((char*)&isEmbedded, sizeof(char));
372 
373 		char mgcnt = i->memberGroups.size();
374 		WriteVarSizeUInt(stream, mgcnt);
375 
376 		std::vector<COutputStreamSerializer::ObjectMemberGroup>::iterator j;
377 		for (j = i->memberGroups.begin(); j != i->memberGroups.end(); ++j) {
378 			std::map<creg::Class*, ClassRef>::iterator cr = classMap.find(j->membersClass);
379 			if (cr == classMap.end()) throw "Cannot find member class ref";
380 			int cid = cr->second.index;
381 			WriteVarSizeUInt(stream, cid);
382 
383 			unsigned int mcnt = j->members.size();
384 			WriteVarSizeUInt(stream, mcnt);
385 
386 			bool hasSerializerMember = false;
387 			char groupFlags = 0;
388 			if (!j->members.empty() && (j->members.back().memberId == -1)) {
389 				groupFlags |= 0x01;
390 				hasSerializerMember = true;
391 			}
392 			stream->write((char*)&groupFlags, sizeof(char));
393 
394 			int midx = 0;
395 			std::vector<COutputStreamSerializer::ObjectMember>::iterator k;
396 			for (k = j->members.begin(); k != j->members.end(); ++k, ++midx) {
397 				if ((k->memberId != midx) && (!hasSerializerMember || k != (j->members.end() - 1))) {
398 					throw "Invalid member id";
399 				}
400 				WriteVarSizeUInt(stream, k->size);
401 			}
402 		}
403 	}
404 
405 	// Calculate a checksum for metadata verification
406 	ph.metadataChecksum = 0;
407 	for (uint a = 0; a < classRefs.size(); a++)
408 	{
409 		Class* c = classRefs[a]->class_;
410 		c->CalculateChecksum(ph.metadataChecksum);
411 	}
412 
413 	int endOffset = stream->tellp();
414 	stream->seekp(startOffset);
415 	memcpy(ph.magic, CREG_PACKAGE_FILE_ID, 4);
416 	ph.SwapBytes();
417 	stream->write((const char*)&ph, sizeof(PackageHeader));
418 
419 	LOG_SL(LOG_SECTION_CREG_SERIALIZER, L_DEBUG,
420 			"Checksum: %X\nNumber of objects saved: %i\nNumber of classes involved: %i",
421 			ph.metadataChecksum, int(objects.size()), int(classRefs.size()));
422 
423 	stream->seekp(endOffset);
424 	ptrToId.clear();
425 	pendingObjects.clear();
426 	objects.clear();
427 }
428 
429 //-------------------------------------------------------------------------
430 // CInputStreamSerializer
431 //-------------------------------------------------------------------------
432 
CInputStreamSerializer()433 CInputStreamSerializer::CInputStreamSerializer()
434 	: stream(NULL)
435 {
436 }
437 
~CInputStreamSerializer()438 CInputStreamSerializer::~CInputStreamSerializer()
439 {
440 	for (std::vector<StoredObject>::iterator it = objects.begin(); it != objects.end(); ++it) {
441 		if (it->obj) {
442 			ClassBinder* binder = classRefs[it->classRef]->binder;
443 			binder->class_->DeleteInstance(it->obj);
444 		}
445 	}
446 }
447 
IsWriting()448 bool CInputStreamSerializer::IsWriting()
449 {
450 	return false;
451 }
452 
SerializeObject(Class * c,void * ptr)453 void CInputStreamSerializer::SerializeObject(Class* c, void* ptr)
454 {
455 	if (c->base)
456 		SerializeObject(c->base, ptr);
457 
458 	for (uint a = 0; a < c->members.size(); a++)
459 	{
460 		creg::Class::Member* m = c->members [a];
461 		if (m->flags & CM_NoSerialize)
462 			continue;
463 
464 		const unsigned oldPos = stream->tellg();
465 		void* memberAddr = ((char*)ptr) + m->offset;
466 		m->type->Serialize(this, memberAddr);
467 		LOG_SL(LOG_SECTION_CREG_SERIALIZER, L_DEBUG, "Deserialized %s::%s type:%s size:%u", c->name.c_str(), m->name, m->type->GetName().c_str(), unsigned(stream->tellg()) - oldPos);
468 	}
469 
470 	if (c->serializeProc) {
471 		_DummyStruct* obj = (_DummyStruct*)ptr;
472 		(obj->*(c->serializeProc))(*this);
473 	}
474 }
475 
Serialize(void * data,int byteSize)476 void CInputStreamSerializer::Serialize(void* data, int byteSize)
477 {
478 	stream->read((char*)data, byteSize);
479 }
480 
SerializeInt(void * data,int byteSize)481 void CInputStreamSerializer::SerializeInt(void* data, int byteSize)
482 {
483 	//FIXME transform to template?
484 	stream->read((char*)data, byteSize);
485 	switch (byteSize) {
486 		case 1: {
487 			*(char*)data = *(char*) data;
488 			break;
489 		}
490 		case 2: {
491 			swabWordInPlace(*(boost::int64_t*)data);
492 			break;
493 		}
494 		case 4: {
495 			swabDWordInPlace(*(int*)data);
496 			break;
497 		}
498 		case 8: {
499 			swab64InPlace(*(boost::int64_t*)data);
500 			break;
501 		}
502 		default: {
503 			throw "Unknown int type";
504 		}
505 	}
506 }
507 
SerializeObjectPtr(void ** ptr,creg::Class * cls)508 void CInputStreamSerializer::SerializeObjectPtr(void** ptr, creg::Class* cls)
509 {
510 	unsigned int id;
511 	ReadVarSizeUInt(stream, &id);
512 	if (id) {
513 		StoredObject& o = objects [id];
514 		if (o.obj) *ptr = o.obj;
515 		else {
516 			// The object is not yet available, so it needs fixing afterwards
517 			*ptr = (void*) 1;
518 			UnfixedPtr ufp;
519 			ufp.objID = id;
520 			ufp.ptrAddr = ptr;
521 			unfixedPointers.push_back(ufp);
522 		}
523 	} else
524 		*ptr = NULL;
525 }
526 
527 // Serialize an instance of an object embedded into another object
SerializeObjectInstance(void * inst,creg::Class * cls)528 void CInputStreamSerializer::SerializeObjectInstance(void* inst, creg::Class* cls)
529 {
530 	unsigned int id;
531 	ReadVarSizeUInt(stream, &id);
532 
533 	if (id == 0)
534 		return; // this is old save game and it has not this object - skip it
535 
536 	StoredObject& o = objects[id];
537 	assert(!o.obj);
538 	assert(o.isEmbedded);
539 
540 	o.obj = inst;
541 	SerializeObject(cls, inst);
542 }
543 
AddPostLoadCallback(void (* cb)(void *),void * ud)544 void CInputStreamSerializer::AddPostLoadCallback(void (*cb)(void*), void* ud)
545 {
546 	PostLoadCallback plcb;
547 
548 	plcb.cb = cb;
549 	plcb.userdata = ud;
550 
551 	callbacks.push_back(plcb);
552 }
553 
LoadPackage(std::istream * s,void * & root,creg::Class * & rootCls)554 void CInputStreamSerializer::LoadPackage(std::istream* s, void*& root, creg::Class*& rootCls)
555 {
556 	PackageHeader ph;
557 
558 	stream = s;
559 	s->read((char*)&ph, sizeof(PackageHeader));
560 
561 	if (memcmp(ph.magic, CREG_PACKAGE_FILE_ID, 4))
562 		throw std::runtime_error("Incorrect object package file ID");
563 
564 	// Load references
565 	classRefs.resize(ph.numObjClassRefs);
566 	s->seekg(ph.objClassRefOffset);
567 	for (int a = 0; a < ph.numObjClassRefs; a++)
568 	{
569 		const std::string className = ReadZStr(*s);
570 		creg::Class* class_ = System::GetClass(className);
571 		if (!class_)
572 			throw std::runtime_error("Package file contains reference to unknown class " + className);
573 		classRefs[a] = class_;
574 	}
575 
576 	// Calculate metadata checksum and compare with stored checksum
577 	unsigned int checksum = 0;
578 	for (uint a = 0; a < classRefs.size(); a++)
579 		classRefs[a]->CalculateChecksum(checksum);
580 	LOG_SL(LOG_SECTION_CREG_SERIALIZER, L_DEBUG, "Checksum: %X (savegame: %X)\n", checksum, ph.metadataChecksum);
581 	if (checksum != ph.metadataChecksum)
582 		throw std::runtime_error("Metadata checksum error: Package file was saved with a different version");
583 
584 	// Create all non-embedded objects
585 	s->seekg(ph.objTableOffset);
586 	objects.resize(ph.numObjects);
587 	for (int a = 0; a < ph.numObjects; a++)
588 	{
589 		unsigned int classRefIndex;
590 		char isEmbedded;
591 		unsigned int mgcnt;
592 		ReadVarSizeUInt(stream, &classRefIndex);
593 		stream->read((char*)&isEmbedded, sizeof(char));
594 		ReadVarSizeUInt(stream, &mgcnt);
595 
596 		for (unsigned int b = 0; b < mgcnt; b++) {
597 			unsigned int cid, mcnt;
598 			char groupFlags;
599 			ReadVarSizeUInt(stream, &cid);
600 			ReadVarSizeUInt(stream, &mcnt);
601 			stream->read((char*)&groupFlags, sizeof(char));
602 			for (unsigned int c = 0; c < mcnt; c++) {
603 				unsigned int size;
604 				ReadVarSizeUInt(stream, &size);
605 			}
606 		}
607 
608 		objects[a].obj = NULL;
609 		if (!isEmbedded) {
610 			// Allocate and construct
611 			ClassBinder* binder = classRefs[classRefIndex]->binder;
612 			void* inst = binder->class_->CreateInstance();
613 			objects[a].obj = inst;
614 		}
615 		objects[a].isEmbedded = !!isEmbedded;
616 		objects[a].classRef = classRefIndex;
617 	}
618 
619 	int endOffset = s->tellg();
620 
621 	// Read the object data using serialization
622 	s->seekg(ph.objDataOffset);
623 	for (uint a = 0; a < objects.size(); a++)
624 	{
625 		if (!objects[a].isEmbedded) {
626 			creg::Class* cls = classRefs[objects[a].classRef];
627 			SerializeObject(cls, objects[a].obj);
628 			LOG_SL(LOG_SECTION_CREG_SERIALIZER, L_DEBUG, "Deserialized %s size:%i", cls->name.c_str(), cls->size);
629 		}
630 	}
631 
632 	// Fix pointers to embedded objects
633 	for (uint a = 0; a < unfixedPointers.size(); a++) {
634 		void* p = objects[unfixedPointers[a].objID].obj;
635 		*unfixedPointers[a].ptrAddr = p;
636 	}
637 
638 	// Run all registered post load callbacks
639 	for (uint a = 0; a < callbacks.size(); a++) {
640 		callbacks[a].cb(callbacks[a].userdata);
641 	}
642 
643 	// Run post load functions on `all` objects (exclude root object)
644 	for (uint a = 1; a < objects.size(); a++) {
645 		StoredObject& o = objects[a];
646 		_DummyStruct* ds = (_DummyStruct*)o.obj;
647 		creg::Class* oc = classRefs[objects[a].classRef];
648 		creg::Class* c = oc;
649 		while (c) {
650 			if (c->postLoadProc) {
651 				LOG_SL(LOG_SECTION_CREG_SERIALIZER, L_DEBUG, "Run PostLoad of %s::%s", oc->name.c_str(), c->name.c_str());
652 				(ds->*c->postLoadProc)();
653 			}
654 			c = c->base;
655 		}
656 	}
657 
658 	// The first object is the root object
659 	root = objects[1].obj;
660 	rootCls = classRefs[objects[1].classRef];
661 
662 	LOG_SL(LOG_SECTION_CREG_SERIALIZER, L_DEBUG,
663 			"SaveGame loaded.\nNumber of objects loaded: %i\nNumber of classes involved: %i\n",
664 			int(objects.size()), int(classRefs.size()));
665 
666 	s->seekg(endOffset);
667 	unfixedPointers.clear();
668 	objects.clear();
669 }
670 
~ISerializer()671 ISerializer::~ISerializer() {
672 }
673