add support for split demo files (UNLOZE)

This commit is contained in:
BotoX 2022-09-12 22:20:16 +02:00
parent 3b723a64e0
commit d4a0d51bfc
9 changed files with 121 additions and 51 deletions

View File

@ -8,32 +8,49 @@
int main(const int argc, const char* argv[])
{
if (argc != 2)
if (argc < 2)
{
fprintf(stderr, "Usage: %s <in>.dem\n", argv[0]);
fprintf(stderr, "Usage: %s <in>.dem [in2.dem] ...\n", argv[0]);
return -1;
}
std::filesystem::path inputFile(argv[1]);
FILE* inputFp = fopen(inputFile.c_str(), "rb");
if (!inputFp)
DemoReader::Init();
SourceGameContext *context = nullptr;
bool error = false;
for (int i = 1; i < argc; i++)
{
fprintf(stderr, "Error: Could not open input file\n");
return -1;
std::filesystem::path inputFile(argv[i]);
FILE* inputFp = fopen(inputFile.c_str(), "rb");
if (!inputFp)
{
fprintf(stderr, "Error: Could not open input file\n");
return -1;
}
static std::filesystem::path outputDir = inputFile.filename().replace_extension();
static std::filesystem::path outputDirVoice = outputDir.string() + "/voice";
if (i == 1)
{
std::filesystem::create_directory(outputDir);
std::filesystem::create_directory(outputDirVoice);
context = new SourceGameContext(outputDir, outputDirVoice);
if(!context->init())
return -1;
}
bool dirty = DemoReader::ProcessDem(inputFp, context);
if(dirty)
error = true;
fclose(inputFp);
}
std::filesystem::path outputDir = inputFile.filename().replace_extension();
std::filesystem::path outputDirVoice = outputDir.string() + "/voice";
std::filesystem::create_directory(outputDir);
std::filesystem::create_directory(outputDirVoice);
context->End();
delete context;
SourceGameContext context = SourceGameContext(outputDir, outputDirVoice);
if (!context.init())
return -1;
bool error = !DemoReader::ProcessDem(inputFp, &context);
fclose(inputFp);
DemoReader::DeInit();
return error;
}

View File

@ -25,21 +25,31 @@ Logic::~Logic()
void Logic::Start()
{
data["demoheader"] = json({
{"demofilestamp", context->header.demofilestamp},
{"demoprotocol", context->header.demoprotocol},
{"networkprotocol", context->header.networkprotocol},
{"servername", context->header.servername},
{"clientname", context->header.clientname},
{"mapname", context->header.mapname},
{"gamedirectory", context->header.gamedirectory},
{"playback_time", context->header.playback_time},
{"playback_ticks", context->header.playback_ticks},
{"playback_frames", context->header.playback_frames},
{"signonlength", context->header.signonlength}
});
tickBase = curTick;
if(curTick)
{
data["demoheader"]["playback_time"] = data["demoheader"]["playback_time"].get<float>() + context->header.playback_time;
data["demoheader"]["playback_ticks"] = data["demoheader"]["playback_ticks"].get<int32_t>() + context->header.playback_ticks;
data["demoheader"]["playback_frames"] = data["demoheader"]["playback_frames"].get<int32_t>() + context->header.playback_frames;
}
else
{
data["demoheader"] = json({
{"demofilestamp", context->header.demofilestamp},
{"demoprotocol", context->header.demoprotocol},
{"networkprotocol", context->header.networkprotocol},
{"servername", context->header.servername},
{"clientname", context->header.clientname},
{"mapname", context->header.mapname},
{"gamedirectory", context->header.gamedirectory},
{"playback_time", context->header.playback_time},
{"playback_ticks", context->header.playback_ticks},
{"playback_frames", context->header.playback_frames},
{"signonlength", context->header.signonlength}
});
}
// std::cout << data.dump(2, ' ', false, json::error_handler_t::replace) << "\n";
// std::cout << data["demoheader"].dump(2, ' ', false, json::error_handler_t::replace) << "\n";
}
void Logic::Finish(bool dirty)
@ -59,9 +69,12 @@ void Logic::Finish(bool dirty)
{
data["demoheader"]["playback_ticks"] = curTick;
data["demoheader"]["playback_time"] = curTick * context->fTickInterval;
data["demoheader"]["playback_frames"] = context->curFrame;
data["demoheader"]["playback_frames"] = data["demoheader"]["playback_frames"].get<int32_t>() - context->header.playback_frames + context->curFrame;
}
}
void Logic::End()
{
data["voice"]["total_time"] = voiceTotalTime;
data["voice"]["active_time"] = voiceActiveTime;
@ -339,4 +352,4 @@ void Logic::OnRoundEnd(const char *message, int reason, int winner)
};
data["events"] += round_end;
}
}

View File

@ -18,6 +18,7 @@ struct Logic
void Start();
void Finish(bool dirty);
void End();
struct
{
@ -41,6 +42,7 @@ struct Logic
void OnRoundStart(int timelimit);
void OnRoundEnd(const char *message, int reason, int winner);
int32_t tickBase = 0;
int32_t curTick = 0;
float voiceTotalTime = 0.0f;
float voiceActiveTime = 0.0f;

View File

@ -52,6 +52,9 @@ bool SourceGameContext::init()
}
voiceWriter = new VoiceDataWriter(this, outputDirVoice.c_str());
if(!voiceWriter->init())
return false;
logic = new Logic(this);
return true;
}
@ -64,8 +67,20 @@ void SourceGameContext::Start()
void SourceGameContext::Finish(bool dirty)
{
voiceWriter->Finish();
logic->Finish(dirty);
voiceWriter->Finish();
for(int i = 0; i < MAX_PLAYERS; i++)
players[i].connected = false;
curTick = -1;
curFrame = -1;
}
void SourceGameContext::End()
{
logic->End();
voiceWriter->End();
}
void SourceGameContext::StartCommandPacket(const CommandPacket& packet)
@ -76,7 +91,7 @@ void SourceGameContext::StartCommandPacket(const CommandPacket& packet)
return;
curTick = packet.tick;
logic->curTick = curTick;
logic->curTick = logic->tickBase + curTick;
voiceWriter->StartCommandPacket(packet);
}

View File

@ -75,6 +75,7 @@ struct SourceGameContext
void Start();
void Finish(bool dirty);
void End();
void StartCommandPacket(const CommandPacket& packet);
void EndCommandPacket(const PacketTrailingBits& trailingBits);

View File

@ -10,6 +10,9 @@
#include "sourcesdk/bitbuf.h"
#include <cstdint>
static NetHandlers::NetDataStructArray s_netDataStructs;
static DemHandlers::DemDataStructArray s_demDataStructs;
PacketTrailingBits ParsePacket(uint8_t* packet, size_t length,
SourceGameContext& context,
const NetHandlers::NetDataStructArray& netDataStructs)
@ -38,13 +41,20 @@ PacketTrailingBits ParsePacket(uint8_t* packet, size_t length,
return trailingBits;
}
void DemoReader::Init()
{
NetHandlers::CreateNetMsgStructs(s_netDataStructs);
DemHandlers::CreateDemMsgStructs(s_demDataStructs);
}
void DemoReader::DeInit()
{
DemHandlers::DestroyDemMsgStructs(s_demDataStructs);
NetHandlers::DestroyNetMsgStructs(s_netDataStructs);
}
bool DemoReader::ProcessDem(std::FILE* inputFp, SourceGameContext* context)
{
NetHandlers::NetDataStructArray netDataStructs;
DemHandlers::DemDataStructArray demDataStructs;
NetHandlers::CreateNetMsgStructs(netDataStructs);
DemHandlers::CreateDemMsgStructs(demDataStructs);
DemoFileReader reader(inputFp);
{
reader.ReadDemoHeader(context->header);
@ -59,7 +69,7 @@ bool DemoReader::ProcessDem(std::FILE* inputFp, SourceGameContext* context)
do
{
reader.ReadCmdHeader(packet.cmd, packet.tick);
packet.data = demDataStructs[packet.cmd];
packet.data = s_demDataStructs[packet.cmd];
DemHandlers::DemMsg_FileRead(packet.cmd, reader, packet.data);
PacketTrailingBits trailingBits = PacketTrailingBits();
@ -68,7 +78,7 @@ bool DemoReader::ProcessDem(std::FILE* inputFp, SourceGameContext* context)
if (packet.cmd == dem_packet || packet.cmd == dem_signon)
{
Array<uint8_t> buffer = reader.ReadRawData(NET_MAX_PAYLOAD);
trailingBits = ParsePacket(buffer.begin(), buffer.length(), *context, netDataStructs);
trailingBits = ParsePacket(buffer.begin(), buffer.length(), *context, s_netDataStructs);
}
else if (packet.cmd == dem_stringtables)
@ -100,8 +110,5 @@ bool DemoReader::ProcessDem(std::FILE* inputFp, SourceGameContext* context)
context->Finish(dirty);
DemHandlers::DestroyDemMsgStructs(demDataStructs);
NetHandlers::DestroyNetMsgStructs(netDataStructs);
return !dirty;
return dirty;
}

View File

@ -5,5 +5,7 @@
namespace DemoReader
{
void Init();
void DeInit();
bool ProcessDem(std::FILE* inputFp, SourceGameContext* context);
}

View File

@ -138,16 +138,26 @@ VoiceDataWriter::VoiceDataWriter(SourceGameContext* context, const char* outputP
{
}
void VoiceDataWriter::Start()
bool VoiceDataWriter::init()
{
int error = CELT_OK;
const CeltConfig& config = sCeltConfigs[sQuality];
m_celtMode = celt_mode_create(config.sampleRate, config.frameSizeSamples, &error);
assert(error == CELT_OK);
assert(m_celtMode);
return error == CELT_OK;
}
void VoiceDataWriter::Start()
{
m_tickBase = m_curTick;
}
void VoiceDataWriter::Finish()
{
}
void VoiceDataWriter::End()
{
if(m_isSilenced)
{
@ -175,7 +185,7 @@ void VoiceDataWriter::Finish()
void VoiceDataWriter::StartCommandPacket(const CommandPacket& packet)
{
m_lastTick = m_curTick;
m_curTick = packet.tick;
m_curTick = m_tickBase + packet.tick;
}
void VoiceDataWriter::EndCommandPacket(const PacketTrailingBits& trailingBits)
@ -184,8 +194,8 @@ void VoiceDataWriter::EndCommandPacket(const PacketTrailingBits& trailingBits)
if (m_curTick <= tickMargin)
return;
// Skip silence if noone talks for at least 3 seconds
if((m_curTick - m_lastVoiceTick) / context->fTickRate > 3.0)
// Skip silence if noone talks for at least 1.5 seconds
if((m_curTick - m_lastVoiceTick) / context->fTickRate > 1.5)
{
if(!m_isSilenced)
{

View File

@ -54,9 +54,11 @@ class VoiceDataWriter
{
public:
VoiceDataWriter(SourceGameContext *context, const char* outputPath);
bool init();
void Start();
void Finish();
void End();
void StartCommandPacket(const CommandPacket& packet);
void EndCommandPacket(const PacketTrailingBits& trailingBits);
@ -95,6 +97,7 @@ private:
eCodec m_Codec = CODEC_NONE;
public:
int32_t m_tickBase = 0;
bool m_isSilenced = false;
std::vector<std::pair<int32_t, int32_t>> m_silence;
};