Files
2026-05-09 22:10:40 -07:00

854 lines
14 KiB
C++

/*
===========================================================================
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