/* ** farchive.cpp ** Implements an archiver for DObject serialization. ** **--------------------------------------------------------------------------- ** Copyright 1998-2009 Randy Heit ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** 3. The name of the author may not be used to endorse or promote products ** derived from this software without specific prior written permission. ** ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. **--------------------------------------------------------------------------- ** ** The structure of the archive file generated is influenced heavily by the ** description of the MFC archive format published somewhere in the MSDN ** library. ** ** Two major shortcomings of the format I use are that there is no version ** control and no support for storing the non-default portions of objects. ** The latter would allow for easier extension of objects in future ** releases even without a versioning system. */ #include #include #include #include #include "doomtype.h" #include "farchive.h" #include "m_swap.h" #include "m_crc32.h" #include "cmdlib.h" #include "i_system.h" #include "c_cvars.h" #include "c_dispatch.h" #include "d_player.h" #include "m_misc.h" #include "dobject.h" // These are special tokens found in the data stream of an archive. // Whenever a new object is encountered, it gets created using new and // is then asked to serialize itself before processing of the previous // object continues. This can result in some very deep recursion if // you aren't careful about how you organize your data. #define NEW_OBJ ((BYTE)1) // Data for a new object follows #define NEW_CLS_OBJ ((BYTE)2) // Data for a new class and object follows #define OLD_OBJ ((BYTE)3) // Reference to an old object follows #define NULL_OBJ ((BYTE)4) // Load as NULL #define M1_OBJ ((BYTE)44) // Load as (DObject*)-1 #define NEW_PLYR_OBJ ((BYTE)5) // Data for a new player follows #define NEW_PLYR_CLS_OBJ ((BYTE)6) // Data for a new class and player follows #define NEW_NAME ((BYTE)27) // A new name follows #define OLD_NAME ((BYTE)28) // Reference to an old name follows #define NIL_NAME ((BYTE)33) // Load as NULL #define NEW_SPRITE ((BYTE)11) // A new sprite name follows #define OLD_SPRITE ((BYTE)12) // Reference to an old sprite name follows #ifdef __BIG_ENDIAN__ static inline WORD SWAP_WORD(WORD x) { return x; } static inline DWORD SWAP_DWORD(DWORD x) { return x; } static inline QWORD SWAP_QWORD(QWORD x) { return x; } static inline void SWAP_FLOAT(float x) { } static inline void SWAP_DOUBLE(double &dst, double src) { dst = src; } #else #ifdef _MSC_VER static inline WORD SWAP_WORD(WORD x) { return _byteswap_ushort(x); } static inline DWORD SWAP_DWORD(DWORD x) { return _byteswap_ulong(x); } static inline QWORD SWAP_QWORD(QWORD x) { return _byteswap_uint64(x); } static inline void SWAP_DOUBLE(double &dst, double &src) { union twiddle { QWORD q; double d; } tdst, tsrc; tsrc.d = src; tdst.q = _byteswap_uint64(tsrc.q); dst = tdst.d; } #else static inline WORD SWAP_WORD(WORD x) { return (((x)<<8) | ((x)>>8)); } static inline DWORD SWAP_DWORD(DWORD x) { return x = (((x)>>24) | (((x)>>8)&0xff00) | (((x)<<8)&0xff0000) | ((x)<<24)); } static inline QWORD SWAP_QWORD(QWORD x) { union { QWORD q; DWORD d[2]; } t, u; t.q = x; u.d[0] = SWAP_DWORD(t.d[1]); u.d[1] = SWAP_DWORD(t.d[0]); return u.q; } static inline void SWAP_DOUBLE(double &dst, double &src) { union twiddle { double f; DWORD d[2]; } tdst, tsrc; DWORD t; tsrc.f = src; t = tsrc.d[0]; tdst.d[0] = SWAP_DWORD(tsrc.d[1]); tdst.d[1] = SWAP_DWORD(t); dst = tdst.f; } #endif static inline void SWAP_FLOAT(float &x) { union twiddle { DWORD i; float f; } t; t.f = x; t.i = SWAP_DWORD(t.i); x = t.f; } #endif // Output buffer size for compression; need some extra space. // I assume the description in zlib.h is accurate. #define OUT_LEN(a) ((a) + (a) / 1000 + 12) void FCompressedFile::BeEmpty () { m_Pos = 0; m_BufferSize = 0; m_MaxBufferSize = 0; m_Buffer = NULL; m_File = NULL; m_NoCompress = false; m_Mode = ENotOpen; } static const char LZOSig[4] = { 'F', 'L', 'Z', 'O' }; static const char ZSig[4] = { 'F', 'L', 'Z', 'L' }; FCompressedFile::FCompressedFile () { BeEmpty (); } FCompressedFile::FCompressedFile (const char *name, EOpenMode mode, bool dontCompress) { BeEmpty (); Open (name, mode); m_NoCompress = dontCompress; } FCompressedFile::FCompressedFile (FILE *file, EOpenMode mode, bool dontCompress, bool postopen) { BeEmpty (); m_Mode = mode; m_File = file; m_NoCompress = dontCompress; if (postopen) { PostOpen (); } } FCompressedFile::~FCompressedFile () { Close (); } bool FCompressedFile::Open (const char *name, EOpenMode mode) { Close (); if (name == NULL) return false; m_Mode = mode; m_File = fopen (name, mode == EReading ? "rb" : "wb"); PostOpen (); return !!m_File; } void FCompressedFile::PostOpen () { if (m_File && m_Mode == EReading) { char sig[4]; fread (sig, 4, 1, m_File); if (sig[0] != ZSig[0] || sig[1] != ZSig[1] || sig[2] != ZSig[2] || sig[3] != ZSig[3]) { fclose (m_File); m_File = NULL; if (sig[0] == LZOSig[0] && sig[1] == LZOSig[1] && sig[2] == LZOSig[2] && sig[3] == LZOSig[3]) { Printf ("Compressed files from older ZDooms are not supported.\n"); } return; } else { DWORD sizes[2]; fread (sizes, sizeof(DWORD), 2, m_File); sizes[0] = SWAP_DWORD (sizes[0]); sizes[1] = SWAP_DWORD (sizes[1]); unsigned int len = sizes[0] == 0 ? sizes[1] : sizes[0]; m_Buffer = (BYTE *)M_Malloc (len+8); fread (m_Buffer+8, len, 1, m_File); sizes[0] = SWAP_DWORD (sizes[0]); sizes[1] = SWAP_DWORD (sizes[1]); ((DWORD *)m_Buffer)[0] = sizes[0]; ((DWORD *)m_Buffer)[1] = sizes[1]; Explode (); } } } void FCompressedFile::Close () { if (m_File) { if (m_Mode == EWriting) { Implode (); fwrite (ZSig, 4, 1, m_File); fwrite (m_Buffer, m_BufferSize + 8, 1, m_File); } fclose (m_File); m_File = NULL; } if (m_Buffer) { M_Free (m_Buffer); m_Buffer = NULL; } BeEmpty (); } void FCompressedFile::Flush () { } FFile::EOpenMode FCompressedFile::Mode () const { return m_Mode; } bool FCompressedFile::IsOpen () const { return !!m_File; } FFile &FCompressedFile::Write (const void *mem, unsigned int len) { if (m_Mode == EWriting) { if (m_Pos + len > m_MaxBufferSize) { do { m_MaxBufferSize = m_MaxBufferSize ? m_MaxBufferSize * 2 : 16384; } while (m_Pos + len > m_MaxBufferSize); m_Buffer = (BYTE *)M_Realloc (m_Buffer, m_MaxBufferSize); } if (len == 1) m_Buffer[m_Pos] = *(BYTE *)mem; else memcpy (m_Buffer + m_Pos, mem, len); m_Pos += len; if (m_Pos > m_BufferSize) m_BufferSize = m_Pos; } else { I_Error ("Tried to write to reading cfile"); } return *this; } FFile &FCompressedFile::Read (void *mem, unsigned int len) { if (m_Mode == EReading) { if (m_Pos + len > m_BufferSize) { I_Error ("Attempt to read past end of cfile"); } if (len == 1) *(BYTE *)mem = m_Buffer[m_Pos]; else memcpy (mem, m_Buffer + m_Pos, len); m_Pos += len; } else { I_Error ("Tried to read from writing cfile"); } return *this; } unsigned int FCompressedFile::Tell () const { return m_Pos; } FFile &FCompressedFile::Seek (int pos, ESeekPos ofs) { if (ofs == ESeekRelative) pos += m_Pos; else if (ofs == ESeekEnd) pos = m_BufferSize - pos; if (pos < 0) m_Pos = 0; else if ((unsigned)pos > m_BufferSize) m_Pos = m_BufferSize; else m_Pos = pos; return *this; } CVAR (Bool, nofilecompression, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) void FCompressedFile::Implode () { uLong outlen; uLong len = m_BufferSize; Byte *compressed = NULL; BYTE *oldbuf = m_Buffer; int r; if (!nofilecompression && !m_NoCompress) { outlen = OUT_LEN(len); do { compressed = new Bytef[outlen]; r = compress (compressed, &outlen, m_Buffer, len); if (r == Z_BUF_ERROR) { delete[] compressed; outlen += 1024; } } while (r == Z_BUF_ERROR); // If the data could not be compressed, store it as-is. if (r != Z_OK || outlen >= len) { DPrintf ("cfile could not be compressed\n"); outlen = 0; } else { DPrintf ("cfile shrank from %lu to %lu bytes\n", len, outlen); } } else { outlen = 0; } m_MaxBufferSize = m_BufferSize = ((outlen == 0) ? len : outlen); m_Buffer = (BYTE *)M_Malloc (m_BufferSize + 8); m_Pos = 0; DWORD *lens = (DWORD *)(m_Buffer); lens[0] = BigLong((unsigned int)outlen); lens[1] = BigLong((unsigned int)len); if (outlen == 0) memcpy (m_Buffer + 8, oldbuf, len); else memcpy (m_Buffer + 8, compressed, outlen); if (compressed) delete[] compressed; M_Free (oldbuf); } void FCompressedFile::Explode () { uLong expandsize, cprlen; unsigned char *expand; if (m_Buffer) { unsigned int *ints = (unsigned int *)(m_Buffer); cprlen = BigLong(ints[0]); expandsize = BigLong(ints[1]); expand = (unsigned char *)M_Malloc (expandsize); if (cprlen) { int r; uLong newlen; newlen = expandsize; r = uncompress (expand, &newlen, m_Buffer + 8, cprlen); if (r != Z_OK || newlen != expandsize) { M_Free (expand); I_Error ("Could not decompress buffer: %s", M_ZLibError(r).GetChars()); } } else { memcpy (expand, m_Buffer + 8, expandsize); } if (FreeOnExplode ()) M_Free (m_Buffer); m_Buffer = expand; m_BufferSize = expandsize; } } FCompressedMemFile::FCompressedMemFile () { m_SourceFromMem = false; m_ImplodedBuffer = NULL; } /* FCompressedMemFile::FCompressedMemFile (const char *name, EOpenMode mode) : FCompressedFile (name, mode) { m_SourceFromMem = false; m_ImplodedBuffer = NULL; } */ FCompressedMemFile::~FCompressedMemFile () { if (m_ImplodedBuffer != NULL) { M_Free (m_ImplodedBuffer); } } bool FCompressedMemFile::Open (const char *name, EOpenMode mode) { if (mode == EWriting) { if (name) { I_Error ("FCompressedMemFile cannot write to disk"); } else { return Open (); } } else { bool res = FCompressedFile::Open (name, EReading); if (res) { fclose (m_File); m_File = NULL; } return res; } return false; } bool FCompressedMemFile::Open (void *memblock) { Close (); m_Mode = EReading; m_Buffer = (BYTE *)memblock; m_SourceFromMem = true; Explode (); m_SourceFromMem = false; return !!m_Buffer; } bool FCompressedMemFile::Open () { Close (); m_Mode = EWriting; m_BufferSize = 0; m_MaxBufferSize = 16384; m_Buffer = (unsigned char *)M_Malloc (16384); m_Pos = 0; return true; } bool FCompressedMemFile::Reopen () { if (m_Buffer == NULL && m_ImplodedBuffer) { m_Mode = EReading; m_Buffer = m_ImplodedBuffer; m_SourceFromMem = true; try { Explode (); } catch(...) { // If we just leave things as they are, m_Buffer and m_ImplodedBuffer // both point to the same memory block and both will try to free it. m_Buffer = NULL; m_SourceFromMem = false; throw; } m_SourceFromMem = false; return true; } return false; } void FCompressedMemFile::Close () { if (m_Mode == EWriting) { Implode (); m_ImplodedBuffer = m_Buffer; m_Buffer = NULL; } } void FCompressedMemFile::Serialize (FArchive &arc) { if (arc.IsStoring ()) { if (m_ImplodedBuffer == NULL) { I_Error ("FCompressedMemFile must be compressed before storing"); } arc.Write (ZSig, 4); DWORD sizes[2]; sizes[0] = SWAP_DWORD (((DWORD *)m_ImplodedBuffer)[0]); sizes[1] = SWAP_DWORD (((DWORD *)m_ImplodedBuffer)[1]); arc.Write (m_ImplodedBuffer, (sizes[0] ? sizes[0] : sizes[1])+8); } else { Close (); m_Mode = EReading; char sig[4]; DWORD sizes[2] = { 0, 0 }; arc.Read (sig, 4); if (sig[0] != ZSig[0] || sig[1] != ZSig[1] || sig[2] != ZSig[2] || sig[3] != ZSig[3]) I_Error ("Expected to extract a compressed file"); arc << sizes[0] << sizes[1]; DWORD len = sizes[0] == 0 ? sizes[1] : sizes[0]; m_Buffer = (BYTE *)M_Malloc (len+8); ((DWORD *)m_Buffer)[0] = SWAP_DWORD(sizes[0]); ((DWORD *)m_Buffer)[1] = SWAP_DWORD(sizes[1]); arc.Read (m_Buffer+8, len); m_ImplodedBuffer = m_Buffer; m_Buffer = NULL; m_Mode = EWriting; } } bool FCompressedMemFile::IsOpen () const { return !!m_Buffer; } void FCompressedMemFile::GetSizes(unsigned int &compressed, unsigned int &uncompressed) const { if (m_ImplodedBuffer != NULL) { compressed = BigLong(*(unsigned int *)m_ImplodedBuffer); uncompressed = BigLong(*(unsigned int *)(m_ImplodedBuffer + 4)); } else { compressed = 0; uncompressed = m_BufferSize; } } FPNGChunkFile::FPNGChunkFile (FILE *file, DWORD id) : FCompressedFile (file, EWriting, true, false), m_ChunkID (id) { } FPNGChunkFile::FPNGChunkFile (FILE *file, DWORD id, size_t chunklen) : FCompressedFile (file, EReading, true, false), m_ChunkID (id) { m_Buffer = (BYTE *)M_Malloc (chunklen); m_BufferSize = (unsigned int)chunklen; fread (m_Buffer, chunklen, 1, m_File); // Skip the CRC for now. Maybe later it will be used. fseek (m_File, 4, SEEK_CUR); } // Unlike FCompressedFile::Close, m_File is left open void FPNGChunkFile::Close () { DWORD data[2]; DWORD crc; if (m_File) { if (m_Mode == EWriting) { crc = CalcCRC32 ((BYTE *)&m_ChunkID, 4); crc = AddCRC32 (crc, (BYTE *)m_Buffer, m_BufferSize); data[0] = BigLong(m_BufferSize); data[1] = m_ChunkID; fwrite (data, 8, 1, m_File); fwrite (m_Buffer, m_BufferSize, 1, m_File); crc = SWAP_DWORD (crc); fwrite (&crc, 4, 1, m_File); } m_File = NULL; } FCompressedFile::Close (); } FPNGChunkArchive::FPNGChunkArchive (FILE *file, DWORD id) : FArchive (), Chunk (file, id) { AttachToFile (Chunk); } FPNGChunkArchive::FPNGChunkArchive (FILE *file, DWORD id, size_t len) : FArchive (), Chunk (file, id, len) { AttachToFile (Chunk); } FPNGChunkArchive::~FPNGChunkArchive () { // Close before FArchive's destructor, because Chunk will be // destroyed before the FArchive is destroyed. Close (); } //============================================ // // FArchive // //============================================ FArchive::FArchive () { } FArchive::FArchive (FFile &file) { AttachToFile (file); } void FArchive::AttachToFile (FFile &file) { m_HubTravel = false; m_File = &file; if (file.Mode() == FFile::EReading) { m_Loading = true; m_Storing = false; } else { m_Loading = false; m_Storing = true; } m_Persistent = file.IsPersistent(); ClassToArchive.Clear(); ArchiveToClass.Clear(); ObjectToArchive.Clear(); ArchiveToObject.Clear(); memset(m_NameHash, 0xFF, sizeof(m_NameHash)); m_Names.Clear(); m_NameStorage.Clear(); m_NumSprites = 0; m_SpriteMap = new int[sprites.Size()]; for (size_t s = 0; s < sprites.Size(); ++s) { m_SpriteMap[s] = -1; } } FArchive::~FArchive () { Close (); if (m_SpriteMap) delete[] m_SpriteMap; } void FArchive::Write (const void *mem, unsigned int len) { m_File->Write (mem, len); } void FArchive::Read (void *mem, unsigned int len) { m_File->Read (mem, len); } void FArchive::Close () { if (m_File) { m_File->Close (); m_File = NULL; DPrintf ("Processed %u objects\n", ArchiveToObject.Size()); } } void FArchive::WriteByte(BYTE val) { m_File->Write(&val, 1); } void FArchive::WriteInt16(WORD val) { WORD out = SWAP_WORD(val); m_File->Write(&out, 2); } void FArchive::WriteInt32(DWORD val) { int out = SWAP_DWORD(val); m_File->Write(&out, 4); } void FArchive::WriteInt64(QWORD val) { long long out = SWAP_QWORD(val); m_File->Write(&out, 8); } void FArchive::WriteCount (DWORD count) { BYTE out; do { out = count & 0x7f; if (count >= 0x80) out |= 0x80; Write (&out, sizeof(BYTE)); count >>= 7; } while (count); } DWORD FArchive::ReadCount () { BYTE in; DWORD count = 0; int ofs = 0; do { Read (&in, sizeof(BYTE)); count |= (in & 0x7f) << ofs; ofs += 7; } while (in & 0x80); return count; } void FArchive::WriteName (const char *name) { BYTE id; if (name == NULL) { id = NIL_NAME; Write (&id, 1); } else { DWORD index = FindName (name); if (index != NameMap::NO_INDEX) { id = OLD_NAME; Write (&id, 1); WriteCount (index); } else { AddName (name); id = NEW_NAME; Write (&id, 1); WriteString (name); } } } const char *FArchive::ReadName () { BYTE id; operator<< (id); if (id == NIL_NAME) { return NULL; } else if (id == OLD_NAME) { DWORD index = ReadCount (); if (index >= m_Names.Size()) { I_Error ("Name %u has not been read yet\n", index); } return &m_NameStorage[m_Names[index].StringStart]; } else if (id == NEW_NAME) { DWORD index; DWORD size = ReadCount (); char *str; index = (DWORD)m_NameStorage.Reserve (size); str = &m_NameStorage[index]; Read (str, size-1); str[size-1] = 0; AddName (index); return str; } else { I_Error ("Expected a name but got something else\n"); return NULL; } } void FArchive::WriteString (const char *str) { if (str == NULL) { WriteCount (0); } else { DWORD size = (DWORD)(strlen (str) + 1); WriteCount (size); Write (str, size - 1); } } void FArchive::WriteString(const FString &str) { // The count includes the '\0' terminator, but we don't // actually write it out. WriteCount(DWORD(str.Len() + 1)); Write(str, DWORD(str.Len())); } FArchive &FArchive::operator<< (char *&str) { if (m_Storing) { WriteString (str); } else { DWORD size = ReadCount (); char *str2; if (size == 0) { str2 = NULL; } else { str2 = new char[size]; size--; Read (str2, size); str2[size] = 0; ReplaceString ((char **)&str, str2); } if (str) { delete[] str; } str = str2; } return *this; } FArchive &FArchive::operator<< (FString &str) { if (m_Storing) { WriteString (str); } else { DWORD size = ReadCount(); if (size == 0) { str = ""; } else { char *str2 = (char *)alloca(size*sizeof(char)); size--; Read (str2, size); str = FString(str2, size); } } return *this; } FArchive &FArchive::operator<< (BYTE &c) { if (m_Storing) Write (&c, sizeof(BYTE)); else Read (&c, sizeof(BYTE)); return *this; } FArchive &FArchive::operator<< (WORD &w) { if (m_Storing) { WORD temp = SWAP_WORD(w); Write (&temp, sizeof(WORD)); } else { Read (&w, sizeof(WORD)); w = SWAP_WORD(w); } return *this; } FArchive &FArchive::operator<< (DWORD &w) { if (m_Storing) { DWORD temp = SWAP_DWORD(w); Write (&temp, sizeof(DWORD)); } else { Read (&w, sizeof(DWORD)); w = SWAP_DWORD(w); } return *this; } FArchive &FArchive::operator<< (QWORD &w) { if (m_Storing) { QWORD temp = SWAP_QWORD(w); Write (&temp, sizeof(QWORD)); } else { Read (&w, sizeof(QWORD)); w = SWAP_QWORD(w); } return *this; } FArchive &FArchive::operator<< (float &w) { if (m_Storing) { float temp = w; SWAP_FLOAT(temp); Write (&temp, sizeof(float)); } else { Read (&w, sizeof(float)); SWAP_FLOAT(w); } return *this; } FArchive &FArchive::operator<< (double &w) { if (m_Storing) { double temp; SWAP_DOUBLE(temp,w); Write (&temp, sizeof(double)); } else { Read (&w, sizeof(double)); SWAP_DOUBLE(w,w); } return *this; } FArchive &FArchive::operator<< (FName &n) { // In an archive, a "name" is a string that might be stored multiple times, // so it is only stored once. It is still treated as a normal string. In the // rest of the game, a name is a unique identifier for a number. if (m_Storing) { WriteName (n.GetChars()); } else { n = FName(ReadName()); } return *this; } FArchive &FArchive::SerializePointer (void *ptrbase, BYTE **ptr, DWORD elemSize) { DWORD w; if (m_Storing) { if (*(void **)ptr) { w = DWORD(((size_t)*ptr - (size_t)ptrbase) / elemSize); } else { w = ~0u; } WriteCount (w); } else { w = ReadCount (); if (w != ~0u) { *(void **)ptr = (BYTE *)ptrbase + w * elemSize; } else { *(void **)ptr = NULL; } } return *this; } FArchive &FArchive::SerializeObject (DObject *&object, PClass *type) { if (!m_ThinkersAllowed && type->IsDescendantOf(RUNTIME_CLASS(DThinker))) { assert(true); I_Error("Tried to serialize a thinker before P_SerializeThinkers"); } if (!type->IsDescendantOf(RUNTIME_CLASS(PClass))) { // a regular object if (IsStoring()) { return WriteObject(object); } else { return ReadObject(object, type); } } else { // a class object if (IsStoring()) { UserWriteClass((PClass *)object); } else { UserReadClass(object); } return *this; } } FArchive &FArchive::WriteObject (DObject *obj) { player_t *player; BYTE id[2]; if (obj == NULL) { id[0] = NULL_OBJ; Write (id, 1); } else if (obj == (DObject*)~0) { id[0] = M1_OBJ; Write (id, 1); } else if (obj->ObjectFlags & OF_EuthanizeMe) { // Objects that want to die are not saved to the archive, but // we leave the pointers to them alone. id[0] = NULL_OBJ; Write (id, 1); } else { PClass *type = obj->GetClass(); DWORD *classarcid; if (type == RUNTIME_CLASS(DObject)) { //I_Error ("Tried to save an instance of DObject.\n" // "This should not happen.\n"); id[0] = NULL_OBJ; Write (id, 1); } else if (NULL == (classarcid = ClassToArchive.CheckKey(type))) { // No instances of this class have been written out yet. // Write out the class, then write out the object. If this // is an actor controlled by a player, make note of that // so that it can be overridden when moving around in a hub. if (obj->IsKindOf (RUNTIME_CLASS (AActor)) && (player = static_cast(obj)->player) && player->mo == obj) { id[0] = NEW_PLYR_CLS_OBJ; id[1] = (BYTE)(player - players); Write (id, 2); } else { id[0] = NEW_CLS_OBJ; Write (id, 1); } WriteClass (type); // Printf ("Make class %s (%u)\n", type->Name, m_File->Tell()); MapObject (obj); obj->SerializeUserVars (*this); obj->Serialize (*this); obj->CheckIfSerialized (); } else { // An instance of this class has already been saved. If // this object has already been written, save a reference // to the saved object. Otherwise, save a reference to the // class, then save the object. Again, if this is a player- // controlled actor, remember that. DWORD *objarcid = ObjectToArchive.CheckKey(obj); if (objarcid == NULL) { if (obj->IsKindOf (RUNTIME_CLASS (AActor)) && (player = static_cast(obj)->player) && player->mo == obj) { id[0] = NEW_PLYR_OBJ; id[1] = (BYTE)(player - players); Write (id, 2); } else { id[0] = NEW_OBJ; Write (id, 1); } WriteCount (*classarcid); // Printf ("Reuse class %s (%u)\n", type->Name, m_File->Tell()); MapObject (obj); obj->SerializeUserVars (*this); obj->Serialize (*this); obj->CheckIfSerialized (); } else { id[0] = OLD_OBJ; Write (id, 1); WriteCount (*objarcid); } } } return *this; } FArchive &FArchive::ReadObject (DObject* &obj, PClass *wanttype) { BYTE objHead; const PClass *type; BYTE playerNum; DWORD index; operator<< (objHead); switch (objHead) { case NULL_OBJ: obj = NULL; break; case M1_OBJ: obj = (DObject *)~0; break; case OLD_OBJ: index = ReadCount(); if (index >= ArchiveToObject.Size()) { I_Error ("Object reference too high (%u; max is %u)\n", index, ArchiveToObject.Size()); } obj = ArchiveToObject[index]; break; case NEW_PLYR_CLS_OBJ: operator<< (playerNum); if (m_HubTravel) { // If travelling inside a hub, use the existing player actor type = ReadClass (wanttype); // Printf ("New player class: %s (%u)\n", type->Name, m_File->Tell()); obj = players[playerNum].mo; // But also create a new one so that we can get past the one // stored in the archive. AActor *tempobj = static_cast(type->CreateNew ()); MapObject (obj != NULL ? obj : tempobj); tempobj->SerializeUserVars (*this); tempobj->Serialize (*this); tempobj->CheckIfSerialized (); // If this player is not present anymore, keep the new body // around just so that the load will succeed. if (obj != NULL) { // When the temporary player's inventory items were loaded, // they became owned by the real player. Undo that now. for (AInventory *item = tempobj->Inventory; item != NULL; item = item->Inventory) { item->Owner = tempobj; } tempobj->Destroy (); } else { obj = tempobj; players[playerNum].mo = static_cast(obj); } break; } /* fallthrough when not travelling to a previous level */ case NEW_CLS_OBJ: type = ReadClass (wanttype); // Printf ("New class: %s (%u)\n", type->Name, m_File->Tell()); obj = type->CreateNew (); MapObject (obj); obj->SerializeUserVars (*this); obj->Serialize (*this); obj->CheckIfSerialized (); break; case NEW_PLYR_OBJ: operator<< (playerNum); if (m_HubTravel) { type = ReadStoredClass (wanttype); // Printf ("Use player class: %s (%u)\n", type->Name, m_File->Tell()); obj = players[playerNum].mo; AActor *tempobj = static_cast(type->CreateNew ()); MapObject (obj != NULL ? obj : tempobj); tempobj->SerializeUserVars (*this); tempobj->Serialize (*this); tempobj->CheckIfSerialized (); if (obj != NULL) { for (AInventory *item = tempobj->Inventory; item != NULL; item = item->Inventory) { item->Owner = tempobj; } tempobj->Destroy (); } else { obj = tempobj; players[playerNum].mo = static_cast(obj); } break; } /* fallthrough when not travelling to a previous level */ case NEW_OBJ: type = ReadStoredClass (wanttype); // Printf ("Use class: %s (%u)\n", type->Name, m_File->Tell()); obj = type->CreateNew (); MapObject (obj); obj->SerializeUserVars (*this); obj->Serialize (*this); obj->CheckIfSerialized (); break; default: I_Error ("Unknown object code (%d) in archive\n", objHead); } return *this; } void FArchive::WriteSprite (int spritenum) { BYTE id; if ((unsigned)spritenum >= (unsigned)sprites.Size()) { spritenum = 0; } if (m_SpriteMap[spritenum] < 0) { m_SpriteMap[spritenum] = (int)(m_NumSprites++); id = NEW_SPRITE; Write (&id, 1); Write (sprites[spritenum].name, 4); // Write the current sprite number as a hint, because // these will only change between different versions. WriteCount (spritenum); } else { id = OLD_SPRITE; Write (&id, 1); WriteCount (m_SpriteMap[spritenum]); } } int FArchive::ReadSprite () { BYTE id; Read (&id, 1); if (id == OLD_SPRITE) { DWORD index = ReadCount (); if (index >= m_NumSprites) { I_Error ("Sprite %u has not been read yet\n", index); } return m_SpriteMap[index]; } else if (id == NEW_SPRITE) { DWORD name; DWORD hint; Read (&name, 4); hint = ReadCount (); if (hint >= NumStdSprites || sprites[hint].dwName != name) { for (hint = NumStdSprites; hint-- != 0; ) { if (sprites[hint].dwName == name) { break; } } if (hint >= sprites.Size()) { // Don't know this sprite, so just use the first one hint = 0; } } m_SpriteMap[m_NumSprites++] = hint; return hint; } else { I_Error ("Expected a sprite but got something else\n"); return 0; } } DWORD FArchive::AddName (const char *name) { DWORD index; unsigned int hash = MakeKey (name) % EObjectHashSize; index = FindName (name, hash); if (index == NameMap::NO_INDEX) { DWORD namelen = (DWORD)(strlen (name) + 1); DWORD strpos = (DWORD)m_NameStorage.Reserve (namelen); NameMap mapper = { strpos, (DWORD)m_NameHash[hash] }; memcpy (&m_NameStorage[strpos], name, namelen); m_NameHash[hash] = index = (DWORD)m_Names.Push (mapper); } return index; } DWORD FArchive::AddName (unsigned int start) { DWORD hash = MakeKey (&m_NameStorage[start]) % EObjectHashSize; NameMap mapper = { (DWORD)start, (DWORD)m_NameHash[hash] }; return (DWORD)(m_NameHash[hash] = m_Names.Push (mapper)); } DWORD FArchive::FindName (const char *name) const { return FindName (name, MakeKey (name) % EObjectHashSize); } DWORD FArchive::FindName (const char *name, unsigned int bucket) const { unsigned int map = m_NameHash[bucket]; while (map != NameMap::NO_INDEX) { const NameMap *mapping = &m_Names[map]; if (strcmp (name, &m_NameStorage[mapping->StringStart]) == 0) { return (DWORD)map; } map = mapping->HashNext; } return (DWORD)map; } DWORD FArchive::WriteClass (PClass *info) { if (ClassToArchive.CheckKey(info) != NULL) { I_Error ("Attempt to write '%s' twice.\n", info->TypeName.GetChars()); } DWORD index = ArchiveToClass.Push(info); ClassToArchive[info] = index; WriteString (info->TypeName.GetChars()); return index; } PClass *FArchive::ReadClass () { struct String { String() { val = NULL; } ~String() { if (val) delete[] val; } char *val; } typeName; operator<< (typeName.val); FName zaname(typeName.val, true); if (zaname != NAME_None) { PClass *type = PClass::FindClass(zaname); if (type != NULL) { ClassToArchive[type] = ArchiveToClass.Push(type); return type; } } I_Error ("Unknown class '%s'\n", typeName.val); return NULL; } PClass *FArchive::ReadClass (const PClass *wanttype) { PClass *type = ReadClass (); if (!type->IsDescendantOf (wanttype)) { I_Error ("Expected to extract an object of type '%s'.\n" "Found one of type '%s' instead.\n", wanttype->TypeName.GetChars(), type->TypeName.GetChars()); } return type; } PClass *FArchive::ReadStoredClass (const PClass *wanttype) { DWORD index = ReadCount (); PClass *type = ArchiveToClass[index]; if (!type->IsDescendantOf (wanttype)) { I_Error ("Expected to extract an object of type '%s'.\n" "Found one of type '%s' instead.\n", wanttype->TypeName.GetChars(), type->TypeName.GetChars()); } return type; } DWORD FArchive::MapObject (DObject *obj) { DWORD i = ArchiveToObject.Push(obj); ObjectToArchive[obj] = i; return i; } void FArchive::UserWriteClass (PClass *type) { BYTE id; if (type == NULL) { id = 2; Write (&id, 1); } else { DWORD *arcid; if (NULL == (arcid = ClassToArchive.CheckKey(type))) { id = 1; Write (&id, 1); WriteClass (type); } else { id = 0; Write (&id, 1); WriteCount (*arcid); } } } void FArchive::UserReadClass (PClass *&type) { BYTE newclass; Read (&newclass, 1); switch (newclass) { case 0: type = ReadStoredClass (RUNTIME_CLASS(DObject)); break; case 1: type = ReadClass (); break; case 2: type = NULL; break; default: I_Error ("Unknown class type %d in archive.\n", newclass); break; } } FArchive &operator<< (FArchive &arc, sector_t *&sec) { return arc.SerializePointer (sectors, (BYTE **)&sec, sizeof(*sectors)); } FArchive &operator<< (FArchive &arc, const sector_t *&sec) { return arc.SerializePointer (sectors, (BYTE **)&sec, sizeof(*sectors)); } FArchive &operator<< (FArchive &arc, line_t *&line) { return arc.SerializePointer (lines, (BYTE **)&line, sizeof(*lines)); } FArchive &operator<< (FArchive &arc, vertex_t *&vert) { return arc.SerializePointer (vertexes, (BYTE **)&vert, sizeof(*vertexes)); } FArchive &operator<< (FArchive &arc, side_t *&side) { return arc.SerializePointer (sides, (BYTE **)&side, sizeof(*sides)); } FArchive &operator<<(FArchive &arc, DAngle &ang) { arc << ang.Degrees; return arc; } FArchive &operator<<(FArchive &arc, DVector3 &vec) { arc << vec.X << vec.Y << vec.Z; return arc; } FArchive &operator<<(FArchive &arc, DVector2 &vec) { arc << vec.X << vec.Y; return arc; }