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