diff --git a/src/Checksum.cpp b/src/Checksum.cpp index 178e11a7..821237a1 100644 --- a/src/Checksum.cpp +++ b/src/Checksum.cpp @@ -27,6 +27,8 @@ #define READ_LE32(arr, i) \ (((uint32_t)arr[i + 0] << 0) | ((uint32_t)arr[i + 1] << 8) | ((uint32_t)arr[i + 2] << 16) | ((uint32_t)arr[i + 3] << 24)) +static constexpr uint8_t SAR_MSG_VSCRIPT_RUNTIME_CHECKSUM = 0x14; + // clang-format off static const uint32_t crcTable[256] = { 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, @@ -507,6 +509,40 @@ static void addVpkInternalChecksum(const VpkInternalData &vpk) { engine->demorecorder->RecordData(data.data(), data.size()); } +static size_t BoundedCStringLen(const char *str, size_t maxLen) { + if (!str) return 0; + size_t len = 0; + while (len < maxLen && str[len]) ++len; + return len; +} + +void RecordRuntimeVscriptChecksum(const char *scriptName, const char *scriptData) { + if (!scriptName || !*scriptName || !scriptData || !*scriptData) return; + + // Avoid an unbounded C-string search in case file data is not null-terminated. + constexpr size_t MAX_SCRIPT_SIZE = 8 * 1024 * 1024; + size_t scriptLen = BoundedCStringLen(scriptData, MAX_SCRIPT_SIZE); + if (scriptLen == 0) return; + + uint32_t sum = 0; + if (scriptLen < MAX_SCRIPT_SIZE) { + sum = crc32(scriptData, scriptLen); + } + + if (engine->demorecorder->isRecordingDemo && engine->demorecorder->GetTick() >= 0) { + size_t nameLen = strlen(scriptName); + size_t bufLen = nameLen + 6; + auto *buf = new uint8_t[bufLen]; + buf[0] = SAR_MSG_VSCRIPT_RUNTIME_CHECKSUM; + *reinterpret_cast(buf + 1) = sum; + strcpy(reinterpret_cast(buf + 5), scriptName); + engine->demorecorder->RecordData(buf, bufLen); + delete[] buf; + } else { + engine->demorecorder->queuedVScriptChecksums.emplace_back(scriptName, sum); + } +} + void AddDemoVpkChecksums() { if (g_vpkThread.joinable()) g_vpkThread.join(); diff --git a/src/Checksum.hpp b/src/Checksum.hpp index c5d9113d..40dc27b4 100644 --- a/src/Checksum.hpp +++ b/src/Checksum.hpp @@ -6,3 +6,4 @@ bool AddDemoChecksum(const char *filename); void AddDemoFileChecksums(); void AddDemoVpkChecksums(); +void RecordRuntimeVscriptChecksum(const char *scriptName, const char *scriptData); diff --git a/src/Modules.hpp b/src/Modules.hpp index 6e7696a4..28ff626c 100644 --- a/src/Modules.hpp +++ b/src/Modules.hpp @@ -12,3 +12,4 @@ #include "Modules/Tier1.hpp" #include "Modules/VGui.hpp" #include "Modules/VPhysics.hpp" +#include "Modules/VScript.hpp" diff --git a/src/Modules/EngineDemoPlayer.cpp b/src/Modules/EngineDemoPlayer.cpp index 86987ede..5da1f24f 100644 --- a/src/Modules/EngineDemoPlayer.cpp +++ b/src/Modules/EngineDemoPlayer.cpp @@ -174,6 +174,7 @@ std::string EngineDemoPlayer::GetLevelName() { // 0x11: VPK internal checksums // 0x12: incomplete speedrun summary // 0x13: speedrun identifier +// 0x14: runtime vscript checksum void EngineDemoPlayer::CustomDemoData(char *data, size_t length) { if (data[0] == 0x03 || data[0] == 0x04) { // Entity input data std::optional slot; diff --git a/src/Modules/EngineDemoRecorder.cpp b/src/Modules/EngineDemoRecorder.cpp index 249f64cd..8f3714fe 100644 --- a/src/Modules/EngineDemoRecorder.cpp +++ b/src/Modules/EngineDemoRecorder.cpp @@ -103,7 +103,33 @@ static void RecordQueuedCommands() { engine->demorecorder->queuedCommands.clear(); } +static void RecordQueuedVScriptChecksums() { + if (!engine->demorecorder->isRecordingDemo) return; + if (!engine->demorecorder->customDataReady) return; + if (engine->demorecorder->GetTick() < 0) return; + + for (auto &queuedChecksum : engine->demorecorder->queuedVScriptChecksums) { + size_t nameLen = queuedChecksum.first.size(); + size_t bufLen = nameLen + 6; + auto *buf = new uint8_t[bufLen]; + buf[0] = 0x14; + *reinterpret_cast(buf + 1) = queuedChecksum.second; + strcpy(reinterpret_cast(buf + 5), queuedChecksum.first.c_str()); + engine->demorecorder->RecordData(buf, bufLen); + delete[] buf; + } + engine->demorecorder->queuedVScriptChecksums.clear(); +} + +ON_EVENT(PRE_TICK) { + if (!engine->demorecorder->queuedVScriptChecksums.empty()) { + RecordQueuedVScriptChecksums(); + } +} + ON_EVENT(SESSION_END) { + engine->demorecorder->queuedVScriptChecksums.clear(); + if (*engine->demorecorder->m_bRecording && sar_autorecord.GetInt() == -1) { engine->demorecorder->Stop(); } @@ -172,6 +198,7 @@ DETOUR(EngineDemoRecorder::SetSignonState, int state) { RecordTimestamp(); SpeedrunTimer::WriteIdToDemo(); // Write speedrun ID to every demo segment RecordQueuedCommands(); + RecordQueuedVScriptChecksums(); SpeedrunTimer::RecordIncompleteSummary(); engine->ExecuteCommand("echo \"SAR " SAR_VERSION " (Built " SAR_BUILT ")\"", true); AddDemoFileChecksums(); diff --git a/src/Modules/EngineDemoRecorder.hpp b/src/Modules/EngineDemoRecorder.hpp index bc82b09c..80007056 100644 --- a/src/Modules/EngineDemoRecorder.hpp +++ b/src/Modules/EngineDemoRecorder.hpp @@ -5,6 +5,8 @@ #include "Utils.hpp" #include +#include +#include // Ticks before demo autostop #define DEMO_AUTOSTOP_DELAY 15 @@ -28,6 +30,7 @@ class EngineDemoRecorder : public Module { int autorecordStartNum = 1; std::vector queuedCommands = {}; + std::vector> queuedVScriptChecksums = {}; char coopRadialMenuLastPos[8]; diff --git a/src/Modules/VScript.cpp b/src/Modules/VScript.cpp new file mode 100644 index 00000000..7aaf017c --- /dev/null +++ b/src/Modules/VScript.cpp @@ -0,0 +1,37 @@ +#include "VScript.hpp" + +#include "Checksum.hpp" +#include "Game.hpp" +#include "Hook.hpp" +#include "Offsets.hpp" +#include "SAR.hpp" + +REDECL(VScript::CompileScript); + +extern Hook g_VScriptCompileScriptHook; +DETOUR_T(int, VScript::CompileScript, const char *scriptData, const char *scriptName) { + if (scriptName && *scriptName) { + RecordRuntimeVscriptChecksum(scriptName, scriptData); + } + + g_VScriptCompileScriptHook.Disable(); + auto ret = VScript::CompileScript(thisptr, scriptData, scriptName); + g_VScriptCompileScriptHook.Enable(); + return ret; +} +Hook g_VScriptCompileScriptHook(&VScript::CompileScript_Hook); + +bool VScript::Init() { + VScript::CompileScript = (VScript::_CompileScript)Memory::Scan(this->Name(), Offsets::VScript_CompileScript); + if (!VScript::CompileScript) { + return false; + } + + g_VScriptCompileScriptHook.SetFunc(VScript::CompileScript); + return this->hasLoaded = true; +} + +void VScript::Shutdown() { +} + +VScript *vscript; diff --git a/src/Modules/VScript.hpp b/src/Modules/VScript.hpp new file mode 100644 index 00000000..bddd5d65 --- /dev/null +++ b/src/Modules/VScript.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "Module.hpp" +#include "Utils.hpp" + +class VScript : public Module { +public: + // CVScriptGameSystem::CompileScript + DECL_DETOUR_T(int, CompileScript, const char *scriptData, const char *scriptName); + + bool Init() override; + void Shutdown() override; + const char *Name() override { return MODULE("vscript"); } +}; + +extern VScript *vscript; diff --git a/src/Offsets/Portal 2 5723.hpp b/src/Offsets/Portal 2 5723.hpp index bbab9f47..f48f4381 100644 --- a/src/Offsets/Portal 2 5723.hpp +++ b/src/Offsets/Portal 2 5723.hpp @@ -30,3 +30,16 @@ OFFSET_LINUX(DrawPortalSpBranchOff, 0x15) SIGSCAN_LINUX(DrawPortalGhost, "55 89 E5 57 56 53 83 EC 5C A1 ? ? ? ? 8B 40") SIGSCAN_LINUX(DrawPortalGhostSpBranch, "0F 84 ? ? ? ? FF 90 ? ? ? ? 80 BB ? ? ? ? 01") SIGSCAN_LINUX(GetChapterProgress, "55 89 E5 57 56 53 83 EC 2C 8B 7D 08 E8 ? ? ? ? 8B 10 C7") +SIGSCAN_LINUX(DispatchParticleEffect,"") +SIGSCAN_LINUX(PrecacheParticleSystem, "") +SIGSCAN_LINUX(GetCurrentTonemappingSystem, "") +SIGSCAN_LINUX(ResetToneMapping, "") +SIGSCAN_LINUX(LoadingProgress__SetupControlStatesInstruction, "") + +//Server +SIGSCAN_LINUX(FloorReportalBranch,"") +SIGSCAN_LINUX(CPortal_Player__PollForUseEntity_CheckMP, "") + +//VScript +SIGSCAN_LINUX(VScript_CompileScript, "55 89 E5 83 EC 38 89 5D F4 8B 5D 0C 89 7D FC 31 FF 89 75 F8 8B 75 10 85 DB 0F 84 ? ? ? ? 80 3B 00 0F 84 ? ? ? ? 85 F6 B8 ? ? ? ? 89") + diff --git a/src/Offsets/Portal 2 8151.hpp b/src/Offsets/Portal 2 8151.hpp index f79461e8..caa4b8bb 100644 --- a/src/Offsets/Portal 2 8151.hpp +++ b/src/Offsets/Portal 2 8151.hpp @@ -68,6 +68,11 @@ SIGSCAN_LINUX(AddShadowToReceiver, "55 89 E5 57 56 53 83 EC ? 8B 45 ? 8B 4D ? 8B SIGSCAN_LINUX(UTIL_Portal_Color, "55 89 E5 56 53 83 EC 10 8B 75 ? 8B 5D ? 85 F6 0F 84") SIGSCAN_LINUX(UTIL_Portal_Color_Particles, "55 89 E5 53 83 EC 14 A1 ? ? ? ? 8B 5D ? 8B 10 89 04 24 FF 92 ? ? ? ? 84 C0 75 ? 83 7D ? 01") SIGSCAN_LINUX(GetChapterProgress, "55 89 E5 57 56 53 83 EC 2C 8B 5D 08 E8 ? ? ? ? 8B 10") +SIGSCAN_LINUX(DispatchParticleEffect, "") +SIGSCAN_LINUX(PrecacheParticleSystem, "") +SIGSCAN_LINUX(GetCurrentTonemappingSystem, "") +SIGSCAN_LINUX(ResetToneMapping, "") +SIGSCAN_LINUX(LoadingProgress__SetupControlStatesInstruction, "") // Engine SIGSCAN_LINUX(Host_AccumulateTime, "55 89 E5 83 EC 28 F3 0F 10 05 ? ? ? ? A1 ? ? ? ? F3 0F 58 45 08 F3 0F 11 05 ? ? ? ? 8B 10 89 04 24 FF 52 24") @@ -98,5 +103,11 @@ SIGSCAN_LINUX(UTIL_GetCommandClientIndex, "A1 ? ? ? ? 55 89 E5 5D 83 C0 01 C3") SIGSCAN_LINUX(CheckStuck_FloatTime, "E8 ? ? ? ? 8B 43 04 DD 9D ? ? ? ? F2 0F 10 B5 ? ? ? ? 8B 50 24 66 0F 14 F6 66 0F 5A CE 85 D2") SIGSCAN_DEFAULT(aircontrol_fling_speedSig, "0F 2F 25 ? ? ? ? 0F 28 F0", "0F 2E 05 ? ? ? ? 0F 86 ? ? ? ? 0F 2E 25") +SIGSCAN_DEFAULT(Portal2PromoFlagsSig, "", "") +SIGSCAN_LINUX(FloorReportalBranch, "") +SIGSCAN_LINUX(CPortal_Player__PollForUseEntity_CheckMP, "") + +//VScript +SIGSCAN_LINUX(VScript_CompileScript, "55 89 E5 57 56 53 83 EC 2C 8B 5D 0C 8B 75 08 8B 45 10 85 DB 0F 84 ? ? ? ? 80 3B 00 0F 84 ? ? ? ? 85 C0 BF ? ? ? ? 89 1C 24 0F 45 F8") // clang-format on diff --git a/src/Offsets/Portal 2 9568.hpp b/src/Offsets/Portal 2 9568.hpp index 820e0826..9317e8c1 100644 --- a/src/Offsets/Portal 2 9568.hpp +++ b/src/Offsets/Portal 2 9568.hpp @@ -545,6 +545,10 @@ OFFSET_DEFAULT(Portal2PromoFlagsOff, 2, 1) OFFSET_EMPTY(DestroyEnvironment) OFFSET_EMPTY(GetActiveEnvironmentByIndex) +// VScript +SIGSCAN_DEFAULT(VScript_CompileScript, "55 8B EC 83 EC 0C 56 57 8B 7D 08 8B F1 85 FF 0F 84 ? ? ? ? 80 3F 00 0F 84 ? ? ? ? 53 8B", + "57 56 53 8B 5C 24 14 8B 7C 24 10 8B 74 24 18 85 DB 74 ? 80 3B 00 74 ? 85 F6 B8 ? ? ? ? 0F 44 F0 83 EC 0C 53 E8 ? ? ? ? C7 04 24 01 00") // "unnamed" xref -> function with two references -> CScriptVM::CompileScript + // Steam API SIGSCAN_DEFAULT(interfaceMgrSig, "89 0D ? ? ? ? 85 C9 0F", "") diff --git a/src/SAR.cpp b/src/SAR.cpp index d2b61123..fd2d7526 100644 --- a/src/SAR.cpp +++ b/src/SAR.cpp @@ -101,6 +101,7 @@ bool SAR::Load(CreateInterfaceFn interfaceFactory, CreateInterfaceFn gameServerF this->modules->AddModule(&matchmaking); this->modules->AddModule(&steam); this->modules->AddModule(&vphysics); + this->modules->AddModule(&vscript); this->modules->InitAll(); SarInitHandler::RunAll();