mirror of
https://github.com/jmarshall23/DoomRTX.git
synced 2026-08-12 08:11:10 +02:00
854 lines
14 KiB
C++
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
|