/* =========================================================================== IceTech GPL Source Code Copyright (C) 2026 Justin Marshall =========================================================================== */ #include "precompiled.h" #pragma hdrstop idCVar idDemoFile::com_logDemos( "com_logDemos", "0", CVAR_SYSTEM | CVAR_BOOL, "Write demo.log with debug information in it" ); idCVar idDemoFile::com_compressDemos( "com_compressDemos", "1", CVAR_SYSTEM | CVAR_INTEGER | CVAR_ARCHIVE, "Compression scheme for demo files\n" "0: None (Fast, large files)\n" "1: LZW (Fast to compress, Fast to decompress, medium/small files)\n" "2: LZSS (Slow to compress, Fast to decompress, small files)\n" "3: Huffman (Fast to compress, Slow to decompress, medium files)\n" "See also: The 'CompressDemo' command" ); idCVar idDemoFile::com_preloadDemos( "com_preloadDemos", "0", CVAR_SYSTEM | CVAR_BOOL | CVAR_ARCHIVE, "Load the whole demo in to RAM before running it" ); #ifdef QUAKE4 #define DEMO_MAGIC "Quake4 RDEMO" #else #define DEMO_MAGIC GAME_NAME " RDEMO" #endif /* ================ idDemoFile::idDemoFile ================ */ idDemoFile::idDemoFile() { f = NULL; fLog = NULL; log = false; fileImage = NULL; compressor = NULL; writing = false; } /* ================ idDemoFile::~idDemoFile ================ */ idDemoFile::~idDemoFile() { Close(); } /* ================ idDemoFile::AllocCompressor ================ */ idCompressor* idDemoFile::AllocCompressor(int type) { switch (type) { case 0: return idCompressor::AllocNoCompression(); default: case 1: return idCompressor::AllocLZW(); case 2: return idCompressor::AllocLZSS(); case 3: return idCompressor::AllocHuffman(); } } /* ================ idDemoFile::OpenForReading ================ */ bool idDemoFile::OpenForReading(const char* fileName) { static const int magicLen = sizeof(DEMO_MAGIC); char magicBuffer[magicLen]; int compression; int fileLength; Close(); f = fileSystem->OpenFileRead(fileName); if (!f) { return false; } fileLength = f->Length(); if (com_preloadDemos.GetBool()) { fileImage = (byte*)Mem_Alloc(fileLength); f->Read(fileImage, fileLength); fileSystem->CloseFile(f); f = new idFile_Memory( va("preloaded(%s)", fileName), (const char*)fileImage, fileLength ); } if (com_logDemos.GetBool()) { fLog = fileSystem->OpenFileWrite("demoread.log"); } writing = false; f->Read(magicBuffer, magicLen); if (memcmp(magicBuffer, DEMO_MAGIC, magicLen) == 0) { f->ReadInt(compression); } else { // Backwards compatibility: old Doom 3 demos had no magic/compression header. compression = 0; f->Rewind(); } compressor = AllocCompressor(compression); compressor->Init(f, false, 8); return true; } /* ================ idDemoFile::OpenForWriting ================ */ bool idDemoFile::OpenForWriting(const char* fileName) { Close(); f = fileSystem->OpenFileWrite(fileName); if (f == NULL) { return false; } if (com_logDemos.GetBool()) { fLog = fileSystem->OpenFileWrite("demowrite.log"); } writing = true; f->Write(DEMO_MAGIC, sizeof(DEMO_MAGIC)); f->WriteInt(com_compressDemos.GetInteger()); f->Flush(); compressor = AllocCompressor(com_compressDemos.GetInteger()); compressor->Init(f, true, 8); return true; } /* ================ idDemoFile::Close ================ */ void idDemoFile::Close() { if (writing && compressor) { compressor->FinishCompress(); } if (f) { fileSystem->CloseFile(f); f = NULL; } if (fLog) { fileSystem->CloseFile(fLog); fLog = NULL; } if (fileImage) { Mem_Free(fileImage); fileImage = NULL; } if (compressor) { delete compressor; compressor = NULL; } demoStrings.DeleteContents(true); writing = false; } /* ================ idDemoFile::SetLog ================ */ void idDemoFile::SetLog(bool b, const char* p) { log = b; if (p) { logStr = p; } } /* ================ idDemoFile::Log ================ */ void idDemoFile::Log(const char* p) { if (fLog && p && *p) { fLog->Write(p, strlen(p)); } } /* ================ idDemoFile::ReadHashString ================ */ const char* idDemoFile::ReadHashString() { int index; if (log && fLog) { const char* text = va("%s > Reading hash string\n", logStr.c_str()); fLog->Write(text, strlen(text)); } ReadInt(index); if (index == -1) { idStr* str = new idStr; idStr data; ReadString(data); *str = data; demoStrings.Append(str); return str->c_str(); } if (index < -1 || index >= demoStrings.Num()) { Close(); common->Error("demo hash index out of range"); } return demoStrings[index]->c_str(); } /* ================ idDemoFile::WriteHashString ================ */ void idDemoFile::WriteHashString(const char* str) { if (log && fLog) { const char* text = va("%s > Writing hash string\n", logStr.c_str()); fLog->Write(text, strlen(text)); } for (int i = 0; i < demoStrings.Num(); i++) { if (!strcmp(demoStrings[i]->c_str(), str)) { WriteInt(i); return; } } idStr* copy = new idStr(str); demoStrings.Append(copy); WriteInt(-1); WriteString(str); } /* ================ idDemoFile::ReadDict ================ */ void idDemoFile::ReadDict(idDict& dict) { int c; idStr key; idStr val; dict.Clear(); ReadInt(c); for (int i = 0; i < c; i++) { key = ReadHashString(); val = ReadHashString(); dict.Set(key, val); } } /* ================ idDemoFile::WriteDict ================ */ void idDemoFile::WriteDict(const idDict& dict) { const int c = dict.GetNumKeyVals(); WriteInt(c); for (int i = 0; i < c; i++) { WriteHashString(dict.GetKeyVal(i)->GetKey()); WriteHashString(dict.GetKeyVal(i)->GetValue()); } } /* ================ idDemoFile::Read ================ */ int idDemoFile::Read(void* buffer, int len) { if (!compressor) { return 0; } const int read = compressor->Read(buffer, len); #ifdef QUAKE4 // Quake 4 behavior from RDEMO path: clear the first byte on EOF/read failure. if (read == 0 && len >= 1) { *(byte*)buffer = 0; } #else if (read == 0 && len >= 4) { *(demoSystem_t*)buffer = DS_FINISHED; } #endif return read; } /* ================ idDemoFile::Write ================ */ int idDemoFile::Write(const void* buffer, int len) { if (!compressor) { return 0; } return compressor->Write(buffer, len); } #ifdef QUAKE4 /* ================ idDemoFile::Length ================ */ int idDemoFile::Length(void) { return f ? f->Length() : 0; } /* ================ idDemoFile::Timestamp ================ */ unsigned int idDemoFile::Timestamp(void) { return f ? f->Timestamp() : 0; } /* ================ idDemoFile::Tell ================ */ int idDemoFile::Tell(void) { return f ? f->Tell() : 0; } /* ================ idDemoFile::ForceFlush ================ */ void idDemoFile::ForceFlush(void) { if (compressor && writing) { compressor->FinishCompress(); } if (f) { f->ForceFlush(); } } /* ================ idDemoFile::Flush ================ */ void idDemoFile::Flush(void) { if (f) { f->Flush(); } } /* ================ idDemoFile::Seek ================ */ int idDemoFile::Seek(long offset, fsOrigin_t origin) { // Seeking inside compressed demo streams is not safe through the compressor. // This is here to satisfy the Quake 4 idFile interface. return f ? f->Seek(offset, origin) : -1; } /* ================ idDemoFile::Rewind ================ */ void idDemoFile::Rewind(void) { if (f) { f->Rewind(); } } /* ================ idDemoFile::Printf ================ */ int idDemoFile::Printf(const char* fmt, ...) { va_list argptr; int ret; va_start(argptr, fmt); ret = VPrintf(fmt, argptr); va_end(argptr); return ret; } /* ================ idDemoFile::VPrintf ================ */ int idDemoFile::VPrintf(const char* fmt, va_list arg) { char buffer[4096]; idStr::vsnPrintf(buffer, sizeof(buffer), fmt, arg); return Write(buffer, strlen(buffer)); } /* ================ idDemoFile::WriteFloatString ================ */ int idDemoFile::WriteFloatString(const char* fmt, ...) { va_list argptr; char buffer[4096]; va_start(argptr, fmt); idStr::vsnPrintf(buffer, sizeof(buffer), fmt, argptr); va_end(argptr); return Write(buffer, strlen(buffer)); } /* ================ idDemoFile::ReadInt ================ */ int idDemoFile::ReadInt(int& value) { return Read(&value, sizeof(value)); } /* ================ idDemoFile::ReadUnsignedInt ================ */ int idDemoFile::ReadUnsignedInt(unsigned int& value) { return Read(&value, sizeof(value)); } /* ================ idDemoFile::ReadShort ================ */ int idDemoFile::ReadShort(short& value) { return Read(&value, sizeof(value)); } /* ================ idDemoFile::ReadUnsignedShort ================ */ int idDemoFile::ReadUnsignedShort(unsigned short& value) { return Read(&value, sizeof(value)); } /* ================ idDemoFile::ReadChar ================ */ int idDemoFile::ReadChar(char& value) { return Read(&value, sizeof(value)); } /* ================ idDemoFile::ReadUnsignedChar ================ */ int idDemoFile::ReadUnsignedChar(unsigned char& value) { return Read(&value, sizeof(value)); } /* ================ idDemoFile::ReadFloat ================ */ int idDemoFile::ReadFloat(float& value) { return Read(&value, sizeof(value)); } /* ================ idDemoFile::ReadBool ================ */ int idDemoFile::ReadBool(bool& value) { return Read(&value, sizeof(value)); } /* ================ idDemoFile::ReadString ================ */ /* ================ idDemoFile::ReadString ================ */ int idDemoFile::ReadString(idStr& string) { int len; int bytesRead; char* buffer; bytesRead = ReadInt(len); if (len <= 0) { string.Clear(); return bytesRead; } buffer = (char*)_alloca(len + 1); bytesRead += Read(buffer, len); buffer[len] = '\0'; string = buffer; return bytesRead; } /* ================ idDemoFile::ReadVec2 ================ */ int idDemoFile::ReadVec2(idVec2& vec) { return Read(&vec, sizeof(vec)); } /* ================ idDemoFile::ReadVec3 ================ */ int idDemoFile::ReadVec3(idVec3& vec) { return Read(&vec, sizeof(vec)); } /* ================ idDemoFile::ReadVec4 ================ */ int idDemoFile::ReadVec4(idVec4& vec) { return Read(&vec, sizeof(vec)); } /* ================ idDemoFile::ReadVec5 ================ */ int idDemoFile::ReadVec5(idVec5& vec) { return Read(&vec, sizeof(vec)); } /* ================ idDemoFile::ReadVec6 ================ */ int idDemoFile::ReadVec6(idVec6& vec) { return Read(&vec, sizeof(vec)); } /* ================ idDemoFile::ReadMat3 ================ */ int idDemoFile::ReadMat3(idMat3& mat) { return Read(&mat, sizeof(mat)); } /* ================ idDemoFile::ReadBounds ================ */ int idDemoFile::ReadBounds(idBounds& bounds) { return Read(&bounds, sizeof(bounds)); } /* ================ idDemoFile::WriteInt ================ */ int idDemoFile::WriteInt(const int value) { return Write(&value, sizeof(value)); } /* ================ idDemoFile::WriteUnsignedInt ================ */ int idDemoFile::WriteUnsignedInt(const unsigned int value) { return Write(&value, sizeof(value)); } /* ================ idDemoFile::WriteShort ================ */ int idDemoFile::WriteShort(const short value) { return Write(&value, sizeof(value)); } /* ================ idDemoFile::WriteUnsignedShort ================ */ int idDemoFile::WriteUnsignedShort(unsigned short value) { return Write(&value, sizeof(value)); } /* ================ idDemoFile::WriteChar ================ */ int idDemoFile::WriteChar(const char value) { return Write(&value, sizeof(value)); } /* ================ idDemoFile::WriteUnsignedChar ================ */ int idDemoFile::WriteUnsignedChar(const unsigned char value) { return Write(&value, sizeof(value)); } /* ================ idDemoFile::WriteFloat ================ */ int idDemoFile::WriteFloat(const float value) { return Write(&value, sizeof(value)); } /* ================ idDemoFile::WriteBool ================ */ int idDemoFile::WriteBool(const bool value) { return Write(&value, sizeof(value)); } /* ================ idDemoFile::WriteString ================ */ int idDemoFile::WriteString(const char* string) { int len; int bytesWritten; if (!string) { len = 0; bytesWritten = WriteInt(len); return bytesWritten; } len = strlen(string); bytesWritten = WriteInt(len); bytesWritten += Write(string, len); return bytesWritten; } /* ================ idDemoFile::WriteVec2 ================ */ int idDemoFile::WriteVec2(const idVec2& vec) { return Write(&vec, sizeof(vec)); } /* ================ idDemoFile::WriteVec3 ================ */ int idDemoFile::WriteVec3(const idVec3& vec) { return Write(&vec, sizeof(vec)); } /* ================ idDemoFile::WriteVec4 ================ */ int idDemoFile::WriteVec4(const idVec4& vec) { return Write(&vec, sizeof(vec)); } /* ================ idDemoFile::WriteVec5 ================ */ int idDemoFile::WriteVec5(const idVec5& vec) { return Write(&vec, sizeof(vec)); } /* ================ idDemoFile::WriteVec6 ================ */ int idDemoFile::WriteVec6(const idVec6& vec) { return Write(&vec, sizeof(vec)); } /* ================ idDemoFile::WriteMat3 ================ */ int idDemoFile::WriteMat3(const idMat3& mat) { return Write(&mat, sizeof(mat)); } /* ================ idDemoFile::WriteBounds ================ */ int idDemoFile::WriteBounds(const idBounds& bounds) { return Write(&bounds, sizeof(bounds)); } /* ================ idDemoFile::WriteNumericStructArray ================ */ void idDemoFile::WriteNumericStructArray( int numStructs, int structSize[], int numValues, byte* data, const char* format ) { if (f) { f->WriteNumericStructArray(numStructs, structSize, numValues, data, format); } } /* ================ idDemoFile::WriteSyncId ================ */ void idDemoFile::WriteSyncId(void) { if (f) { f->WriteSyncId(); } } /* ================ idDemoFile::ReadSyncId ================ */ void idDemoFile::ReadSyncId(const char* syncId, const char* debugLabel) { if (f) { f->ReadSyncId(syncId, debugLabel); } } #endif // QUAKE4