/* =========================================================================== Copyright (C) 2015 the OpenMoHAA team This file is part of OpenMoHAA source code. OpenMoHAA source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. OpenMoHAA source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenMoHAA source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // archive.cpp: OpenMoHAA Archiver #include "glb_local.h" #include "archive.h" #include "level.h" #include #ifdef GAME_DLL # include "../fgame/entity.h" #endif enum { ARC_NULL, ARC_Vector, ARC_Vec2, ARC_Vec3, ARC_Vec4, ARC_Integer, ARC_Unsigned, ARC_Byte, ARC_Char, ARC_Short, ARC_UnsignedShort, ARC_Float, ARC_Double, ARC_Boolean, ARC_String, ARC_Raw, ARC_Object, ARC_ObjectPointer, ARC_SafePointer, ARC_EventPointer, ARC_Quat, ARC_Entity, ARC_Bool, ARC_Position, ARC_Size, ARC_NUMTYPES }; static const char *typenames[] = { "NULL", "vector", "vec2", "vec3", "vec4", "int", "unsigned", "byte", "char", "short", "unsigned short", "float", "double", "qboolean", "string", "raw data", "object", "objectpointer", "safepointer", "eventpointer", "quaternion", "entity", "bool", "position", "size"}; #define ArchiveHeader (LittleLong(*(int *)"MHAA")) #define ArchiveVersion 14 // This must be changed any time the format changes! #define ArchiveInfo "OPENMOHAA Archive Version 14" // This must be changed any time the format changes! ArchiveFile::ArchiveFile() { length = 0; buffer = 0; pos = 0; bufferlength = 0; writing = 0; opened = 0; } ArchiveFile::~ArchiveFile() { Close(); } void ArchiveFile::Close() { if (writing) { gi.FS_WriteFile(filename.c_str(), buffer, length); } if (buffer) { gi.Free((void *)buffer); buffer = NULL; } writing = false; filename = ""; length = 0; pos = 0; } const char *ArchiveFile::Filename(void) { return filename.c_str(); } qboolean ArchiveFile::Compress() { byte *tempbuf; size_t out_len; size_t tempbuf_len; tempbuf_len = (length >> 6) + length + 27; tempbuf = (byte *)gi.Malloc(tempbuf_len); // Set the signature tempbuf[0] = 'C'; tempbuf[1] = 'S'; tempbuf[2] = 'V'; tempbuf[3] = 'G'; *(size_t *)(tempbuf + 4) = length; // Compress the data if (g_lz77.Compress(buffer, length, tempbuf + 8, &out_len)) { gi.Error(ERR_DROP, "Compression of SaveGame Failed!\n"); return false; } gi.Free(buffer); buffer = tempbuf; length = out_len + 8; bufferlength = tempbuf_len; return true; } size_t ArchiveFile::Length(void) { return length; } size_t ArchiveFile::Pos(void) { return pos - buffer; } size_t ArchiveFile::Tell(void) { return pos - buffer; } qboolean ArchiveFile::Seek(size_t newpos) { if (!buffer) { return false; } if (newpos > length) { return false; } pos = buffer + newpos; return true; } qboolean ArchiveFile::OpenRead(const char *name) { byte *tempbuf; assert(name); assert(!buffer); Close(); if (!name) { return false; } length = gi.FS_ReadFile(name, (void **)&tempbuf, qtrue); if (length == (size_t)(-1) || length == 0) { return false; } // create our own space buffer = (byte *)gi.Malloc(length); bufferlength = length; // copy the file over to our space memcpy(buffer, tempbuf, length); // free the file gi.FS_FreeFile(tempbuf); // set the file name filename = name; pos = buffer; writing = false; opened = true; char FileHeader[4]; Read(FileHeader, sizeof(FileHeader)); if (FileHeader[0] != 'C' || FileHeader[1] != 'S' || FileHeader[2] != 'V' || FileHeader[3] != 'G') { pos = buffer; } else { uint32_t new_len; size_t iCSVGLength; new_len = 0; Read(&new_len, sizeof(uint32_t)); new_len = LittleLong(new_len); tempbuf = (byte *)gi.Malloc(new_len); if (g_lz77.Decompress(pos, length - 8, tempbuf, &iCSVGLength) || iCSVGLength != new_len) { gi.Error(ERR_DROP, "Decompression of save game failed\n"); return false; } gi.Free(buffer); buffer = tempbuf; length = iCSVGLength; bufferlength = length; pos = buffer; } return true; } qboolean ArchiveFile::OpenWrite(const char *name) { this->length = 0; // 4 MiB buffer this->bufferlength = 4 * 1024 * 1024; this->buffer = (byte *)gi.Malloc(bufferlength); this->filename = name; this->pos = buffer; this->writing = true; this->opened = true; return true; } qboolean ArchiveFile::Read(void *dest, size_t size) { if (!size) { return false; } if ((pos + size) > (buffer + length)) { return false; } memcpy(dest, pos, size); pos += size; return true; } qboolean ArchiveFile::Write(const void *source, size_t size) { if ((pos + size) > (buffer + bufferlength)) { byte *oldbuf; do { bufferlength *= 2; } while ((pos + size) > (buffer + bufferlength)); oldbuf = buffer; // reallocate a bigger buffer buffer = (byte *)gi.Malloc(bufferlength); memcpy(buffer, oldbuf, length); // free the old buffer gi.Free(oldbuf); // set the position with the new buffer pos = buffer + (pos - oldbuf); } memcpy(pos, source, size); pos += size; if (length < (pos - buffer)) { length = (pos - buffer); } return true; } Archiver::Archiver() { archivemode = ARCHIVE_WRITE; fileerror = false; harderror = true; Reset(); silent = false; assert((sizeof(typenames) / sizeof(typenames[0])) == ARC_NUMTYPES); } Archiver::~Archiver() { if (archivemode != ARCHIVE_NONE) { Close(); } } void Archiver::FileError(const char *fmt, ...) { va_list argptr; char text[1024]; va_start(argptr, fmt); Q_vsnprintf(text, sizeof(text), fmt, argptr); va_end(argptr); fileerror = true; Close(); if (archivemode == ARCHIVE_READ) { if (harderror) { gi.Error(ERR_DROP, "Error while loading %s : %s\n", filename.c_str(), text); } else if (!silent) { gi.Printf("Error while loading %s : %s\n", filename.c_str(), text); } } else if (archivemode == ARCHIVE_WRITE) { if (harderror) { gi.Error(ERR_DROP, "Error while writing to %s : %s\n", filename.c_str(), text); } else if (!silent) { gi.Printf("Error while writing to %s : %s\n", filename.c_str(), text); } } else { if (harderror) { gi.Error(ERR_DROP, "Error while neither reading nor writing: %s\n", text); } else { gi.Printf("Error while neither reading nor writing: %s\n", text); } } } void Archiver::Close(void) { if (archivemode == ARCHIVE_NONE) { // nothing to process return; } if (archivemode == ARCHIVE_WRITE) { int numobjects; size_t pos; // write out the number of classpointers pos = archivefile.Tell(); archivefile.Seek(numclassespos); numobjects = classpointerList.NumObjects(); ArchiveInteger(&numobjects); // compress the file archivefile.Seek(pos); archivefile.Compress(); } archivefile.Close(); if (archivemode == ARCHIVE_READ) { int i, num; pointer_fixup_t *fixup; num = fixupList.NumObjects(); for (i = 1; i <= num; i++) { fixup = fixupList.ObjectAt(i); if (fixup->type == pointer_fixup_ptr) { LightClass **fixupptr; fixupptr = (LightClass **)fixup->ptr; *fixupptr = classpointerList.ObjectAt(fixup->index); } else if (fixup->type == pointer_fixup_normal) { Class **fixupptr; fixupptr = (Class **)fixup->ptr; *fixupptr = static_cast(classpointerList.ObjectAt(fixup->index)); } else if (fixup->type == pointer_fixup_safe) { SafePtrBase *fixupptr; fixupptr = (SafePtrBase *)fixup->ptr; fixupptr->InitSafePtr(static_cast(classpointerList.ObjectAt(fixup->index))); } delete fixup; } fixupList.FreeObjectList(); classpointerList.FreeObjectList(); } archivemode = ARCHIVE_NONE; } /**************************************************************************************** File Read/Write functions *****************************************************************************************/ qboolean Archiver::Read(const char *name, qboolean harderror) { unsigned header; unsigned version; str info; int num; int i; Class *null; this->harderror = harderror; this->fileerror = false; this->archivemode = ARCHIVE_READ; this->filename = name; if (!archivefile.OpenRead(filename.c_str())) { if (harderror) { FileError("Couldn't open file."); } return false; } ArchiveUnsigned(&header); if (header != ArchiveHeader) { archivefile.Close(); FileError("Not a valid MOHAA archive."); return false; } ArchiveUnsigned(&version); if (version > ArchiveVersion) { archivefile.Close(); FileError("Archive is from version %u. Check http://www.x-null.net for an update.", version); return false; } if (version < ArchiveVersion) { archivefile.Close(); FileError("Archive is out of date."); return false; } ArchiveString(&info); gi.DPrintf("%s\n", info.c_str()); // setup out class pointers ArchiveInteger(&num); classpointerList.Resize(num); null = NULL; for (i = 1; i <= num; i++) { classpointerList.AddObject(null); } return true; } qboolean Archiver::Create(const char *name, qboolean harderror) { unsigned header; unsigned version; str info; int numZero = 0; this->harderror = harderror; this->fileerror = false; this->archivemode = ARCHIVE_WRITE; this->filename = name; if (!archivefile.OpenWrite(filename.c_str())) { FileError("Couldn't open file."); return false; } header = ArchiveHeader; ArchiveUnsigned(&header); version = ArchiveVersion; ArchiveUnsigned(&version); info = ArchiveInfo; ArchiveString(&info); numclassespos = archivefile.Tell(); ArchiveInteger(&numZero); Reset(); return true; } /**************************************************************************************** File Archive functions *****************************************************************************************/ //#define ARCHIVE_USE_TYPES 1 template void ArchiveSwapValue(v* value) { LittleSwap(value, sizeof(v)); } template void ArchiveSwapValue(v* value, size_t size) { for (size_t i = 0; i < size; i++) { LittleSwap(&value[i], sizeof(value[i])); } } template<> void ArchiveSwapValue(Vector* value) { for (int i = 0; i < 3; i++) { (*value)[i] = LittleFloat((*value)[i]); } } template<> void ArchiveSwapValue(Quat* value) { for (int i = 0; i < 4; i++) { (*value)[i] = LittleFloat((*value)[i]); } } #define ARCHIVE(func, type) \ void Archiver::Archive##func(type *v) \ { \ if (archivemode == ARCHIVE_WRITE) { \ type nv = *v; \ ArchiveSwapValue(&nv); \ ArchiveData(ARC_##func, &nv, sizeof(type)); \ } else { \ ArchiveData(ARC_##func, v, sizeof(type)); \ ArchiveSwapValue(v); \ } \ } ARCHIVE(Vector, Vector); ARCHIVE(Integer, int); ARCHIVE(Unsigned, unsigned); ARCHIVE(Byte, byte); ARCHIVE(Char, char); ARCHIVE(Short, short); ARCHIVE(UnsignedShort, unsigned short); ARCHIVE(Float, float); ARCHIVE(Double, double); ARCHIVE(Boolean, qboolean); ARCHIVE(Quat, Quat); ARCHIVE(Bool, bool); ARCHIVE(Position, int); ARCHIVE(Size, long); void Archiver::ArchiveSvsTime(int *time) { #ifdef GAME_DLL if (archivemode == ARCHIVE_READ) { ArchiveInteger(time); gi.AddSvsTimeFixup(time); } else { *time -= level.svsTime; ArchiveInteger(time); *time += level.svsTime; } #endif } void Archiver::ArchiveVec2(vec2_t vec) { if (archivemode == ARCHIVE_WRITE) { vec2_t nv = { vec[0], vec[1] }; ArchiveSwapValue(nv, 2); ArchiveData(ARC_Vec2, nv, sizeof(vec2_t)); } else { ArchiveData(ARC_Vec2, vec, sizeof(vec2_t)); ArchiveSwapValue(vec, 2); } } void Archiver::ArchiveVec3(vec3_t vec) { if (archivemode == ARCHIVE_WRITE) { vec3_t nv = { vec[0], vec[1], vec[2]}; ArchiveSwapValue(nv, 3); ArchiveData(ARC_Vec3, nv, sizeof(vec3_t)); } else { ArchiveData(ARC_Vec3, vec, sizeof(vec3_t)); ArchiveSwapValue(vec, 3); } } void Archiver::ArchiveVec4(vec4_t vec) { if (archivemode == ARCHIVE_WRITE) { vec4_t nv = { vec[0], vec[1], vec[2], vec[3] }; ArchiveSwapValue(nv, 4); ArchiveData(ARC_Vec4, nv, sizeof(vec4_t)); } else { ArchiveData(ARC_Vec4, vec, sizeof(vec4_t)); ArchiveSwapValue(vec, 4); } } void Archiver::ArchiveObjectPointer(LightClass **ptr) { int index = 0; if (archivemode == ARCHIVE_READ) { pointer_fixup_t *fixup; ArchiveData(ARC_ObjectPointer, &index, sizeof(index)); index = LittleLong(index); // // see if the variable was NULL // if (index == ARCHIVE_NULL_POINTER) { *ptr = NULL; } else { // init the pointer with NULL until we can fix it *ptr = NULL; fixup = new pointer_fixup_t; fixup->ptr = (void **)ptr; fixup->index = index; fixup->type = pointer_fixup_ptr; fixupList.AddObject(fixup); } } else { if (*ptr) { index = classpointerList.AddUniqueObject(*ptr); } else { index = ARCHIVE_NULL_POINTER; } index = LittleLong(index); ArchiveData(ARC_ObjectPointer, &index, sizeof(index)); } } void Archiver::ArchiveObjectPointer(Class **ptr) { int index = 0; if (archivemode == ARCHIVE_READ) { pointer_fixup_t *fixup; ArchiveData(ARC_ObjectPointer, &index, sizeof(index)); index = LittleLong(index); // // see if the variable was NULL // if (index == ARCHIVE_NULL_POINTER) { *ptr = NULL; } else { // init the pointer with NULL until we can fix it *ptr = NULL; fixup = new pointer_fixup_t; fixup->ptr = (void **)ptr; fixup->index = index; fixup->type = pointer_fixup_normal; fixupList.AddObject(fixup); } } else { if (*ptr) { index = classpointerList.AddUniqueObject(*ptr); } else { index = ARCHIVE_NULL_POINTER; } index = LittleLong(index); ArchiveData(ARC_ObjectPointer, &index, sizeof(index)); } } void Archiver::ArchiveSafePointer(SafePtrBase *ptr) { int index = 0; if (archivemode == ARCHIVE_READ) { pointer_fixup_t *fixup; ArchiveData(ARC_SafePointer, &index, sizeof(index)); index = LittleLong(index); // // see if the variable was NULL // if (index == ARCHIVE_NULL_POINTER) { ptr->InitSafePtr(NULL); } else { // init the pointer with NULL until we can fix it ptr->InitSafePtr(NULL); // Add new fixup fixup = new pointer_fixup_t; fixup->ptr = (void **)ptr; fixup->index = index; fixup->type = pointer_fixup_safe; fixupList.AddObject(fixup); } } else { if (ptr->Pointer()) { Class *obj; obj = ptr->Pointer(); index = classpointerList.AddUniqueObject(obj); } else { index = ARCHIVE_NULL_POINTER; } index = LittleLong(index); ArchiveData(ARC_SafePointer, &index, sizeof(index)); } } void Archiver::ArchiveEventPointer(Event **ev) { int index; #ifdef ARCHIVE_USE_TYPES CheckType(ARC_EventPointer); #endif if (archivemode == ARCHIVE_READ) { #ifndef NDEBUG CheckRead(); #endif ArchiveInteger(&index); if (!fileerror) { if (index == ARCHIVE_POINTER_VALID) { *ev = new Event; (*ev)->Archive(*this); } else { (*ev) = NULL; } } } else { #ifndef NDEBUG CheckWrite(); #endif if (*ev) { index = ARCHIVE_POINTER_VALID; } else { index = ARCHIVE_NULL_POINTER; } ArchiveInteger(&index); if (*ev) { (*ev)->Archive(*this); } } } void Archiver::ArchiveRaw(void *data, size_t size) { ArchiveData(ARC_Raw, data, size); } void Archiver::ArchiveString(str *string) { #ifdef ARCHIVE_USE_TYPES CheckType(ARC_String); #endif if (archivemode == ARCHIVE_READ) { fileSize_t s; char *data; #ifndef NDEBUG CheckRead(); #endif if (!fileerror) { s = ReadSize(); if (!fileerror) { data = new char[s + 1]; if (data) { if (s) { archivefile.Read(data, s); } data[s] = 0; *string = data; delete[] data; } } } } else { #ifndef NDEBUG CheckWrite(); #endif WriteSize(string->length()); archivefile.Write(string->c_str(), string->length()); } } Class *Archiver::ReadObject(void) { ClassDef *cls; Class *obj; str classname; size_t objstart; size_t endpos; int index; size_t size; qboolean isent; int type; CheckRead(); type = ReadType(); if ((type != ARC_Object) && (type != ARC_Entity)) { FileError("Expecting %s or %s", typenames[ARC_Object], typenames[ARC_Entity]); } size = ReadSize(); ArchiveString(&classname); cls = getClass(classname.c_str()); if (!cls) { FileError("Invalid class %s.", classname.c_str()); } #if defined(GAME_DLL) isent = checkInheritance(&Entity::ClassInfo, cls); if (type == ARC_Entity) { if (!isent) { FileError("Non-Entity class object '%s' saved as an Entity based object.", classname.c_str()); } ArchiveInteger(&level.spawn_entnum); // // make sure to setup spawnflags properly // ArchiveInteger(&level.spawnflags); } else if (isent) { FileError("Entity class object '%s' saved as non-Entity based object.", classname.c_str()); } #else isent = false; #endif ArchiveInteger(&index); objstart = archivefile.Pos(); obj = (Class *)cls->newInstance(); if (!obj) { FileError("Failed to on new instance of class %s.", classname.c_str()); } else { obj->Archive(*this); } if (!fileerror) { endpos = archivefile.Pos(); if ((endpos - objstart) > size) { FileError("Object read past end of object's data"); } else if ((endpos - objstart) < size) { FileError("Object didn't read entire data from file"); } } // // register this pointer with our list // classpointerList.AddObjectAt(index, obj); return obj; } void Archiver::ArchiveObject(Class *obj) { str classname; int index; fileSize_t size; qboolean isent; if (archivemode == ARCHIVE_READ) { ClassDef *cls; size_t objstart; size_t endpos; int type; CheckRead(); type = ReadType(); if ((type != ARC_Object) && (type != ARC_Entity)) { FileError("Expecting %s or %s", typenames[ARC_Object], typenames[ARC_Entity]); } size = ReadSize(); ArchiveString(&classname); cls = getClass(classname.c_str()); if (!cls) { FileError("Invalid class %s.", classname.c_str()); } if (obj->classinfo() != cls) { FileError( "Archive has a '%s' object, but was expecting a '%s' object.", classname.c_str(), obj->getClassname() ); } #if defined(GAME_DLL) isent = obj->isSubclassOf(Entity); if (type == ARC_Entity) { int entnum; if (!isent) { FileError("Non-Entity class object '%s' saved as an Entity based object.", classname.c_str()); } ArchiveInteger(&entnum); ((Entity *)obj)->entnum = entnum; // // make sure to setup spawnflags properly // ArchiveInteger(&level.spawnflags); } else if (isent) { FileError("Entity class object '%s' saved as non-Entity based object.", classname.c_str()); } #else isent = false; #endif ArchiveInteger(&index); objstart = archivefile.Pos(); obj->Archive(*this); if (!fileerror) { endpos = archivefile.Pos(); if ((endpos - objstart) > size) { FileError("Object read past end of object's data"); } else if ((endpos - objstart) < size) { FileError("Object didn't read entire data from file"); } } // // register this pointer with our list // classpointerList.AddObjectAt(index, obj); } else { long sizepos; long objstart = 0; long endpos; assert(obj); if (!obj) { FileError("NULL object in WriteObject"); } #if defined(GAME_DLL) isent = obj->isSubclassOf(Entity); #else isent = false; #endif CheckWrite(); if (isent) { WriteType(ARC_Entity); } else { WriteType(ARC_Object); } sizepos = archivefile.Tell(); size = 0; WriteSize(size); classname = obj->getClassname(); ArchiveString(&classname); #if defined(GAME_DLL) if (isent) { // Write out the entity number ArchiveInteger(&((Entity *)obj)->entnum); // // make sure to setup spawnflags properly // ArchiveInteger(&((Entity *)obj)->spawnflags); } #endif // write out pointer index for this class pointer index = classpointerList.AddUniqueObject(obj); ArchiveInteger(&index); if (!fileerror) { objstart = archivefile.Tell(); obj->Archive(*this); } if (!fileerror) { endpos = archivefile.Tell(); size = endpos - objstart; archivefile.Seek(sizepos); WriteSize(size); if (!fileerror) { archivefile.Seek(archivefile.Length()); } } } } void Archiver::ArchiveObject(SafePtrBase* obj) { ArchiveSafePointer(obj); } void Archiver::ArchiveObjectPosition(LightClass *obj) { int index = 0; if (archivemode == ARCHIVE_READ) { ArchivePosition(&index); classpointerList.AddObjectAt(index, (Class *)obj); } else { index = classpointerList.AddUniqueObject((Class *)obj); ArchivePosition(&index); } } qboolean Archiver::ObjectPositionExists(void *obj) { return classpointerList.IndexOfObject((Class *)obj) != 0; } void Archiver::CheckRead(void) { assert(archivemode == ARCHIVE_READ); if (!fileerror && (archivemode != ARCHIVE_READ)) { FileError("File read during a write operation."); } } void Archiver::CheckWrite(void) { assert(archivemode == ARCHIVE_WRITE); if (!fileerror && (archivemode != ARCHIVE_WRITE)) { FileError("File write during a read operation."); } } qboolean Archiver::Read(str& name, qboolean harderror) { return Read(name.c_str(), harderror); } qboolean Archiver::Create(str& name, qboolean harderror) { return Create(name.c_str(), harderror); } qboolean Archiver::Loading(void) { return (archivemode == ARCHIVE_READ) ? qtrue : qfalse; } qboolean Archiver::Saving(void) { return (archivemode == ARCHIVE_WRITE) ? qtrue : qfalse; } qboolean Archiver::NoErrors(void) { return fileerror ? qfalse : qtrue; } size_t Archiver::Counter() const { return m_iNumBytesIO; } void Archiver::Reset() { m_iNumBytesIO = 0; } fileSize_t Archiver::ReadSize(void) { fileSize_t s; s = 0; if (!fileerror) { archivefile.Read(&s, sizeof(s)); LittleSwap(&s, sizeof(s)); } return s; } void Archiver::CheckSize(int type, fileSize_t size) { fileSize_t s; if (!fileerror) { s = ReadSize(); if (size != s) { FileError("Invalid data size of %d on %s.", s, typenames[type]); } } } void Archiver::WriteSize(fileSize_t size) { LittleSwap(&size, sizeof(size)); archivefile.Write(&size, sizeof(fileSize_t)); } int Archiver::ReadType(void) { int t; if (!fileerror) { archivefile.Read(&t, sizeof(t)); t = LittleLong(t); return t; } return ARC_NULL; } void Archiver::WriteType(int type) { archivefile.Write(&type, sizeof(type)); } void Archiver::CheckType(int type) { int t; assert((type >= 0) && (type < ARC_NUMTYPES)); if (archivemode == ARCHIVE_READ) { if (!fileerror) { t = ReadType(); if (t != type) { if (t < ARC_NUMTYPES) { FileError("Expecting %s, Should be %s", typenames[type], typenames[t]); assert(0); } else { FileError("Expecting %s, Should be %i (Unknown type)", typenames[type], t); } } } } else { int nt = LittleLong(type); archivefile.Write(&nt, sizeof(nt)); } } void Archiver::ArchiveData(int type, void *data, size_t size) { #ifdef ARCHIVE_USE_TYPES CheckType(type); #endif if (archivemode == ARCHIVE_READ) { #ifndef NDEBUG CheckRead(); #endif if (!fileerror && size) { m_iNumBytesIO += size; archivefile.Read(data, size); } } else { #ifndef NDEBUG CheckWrite(); #endif if (!fileerror && size) { m_iNumBytesIO += size; archivefile.Write(data, size); } } } void Archiver::ArchiveConfigString(int cs) { str s; if (archivemode == ARCHIVE_READ) { ArchiveString(&s); gi.setConfigstring(cs, s.c_str()); } else { s = gi.getConfigstring(cs); ArchiveString(&s); } } void Archiver::SetSilent(bool bSilent) { silent = bSilent; }