diff --git a/src/Modules/Engine.cpp b/src/Modules/Engine.cpp index 2a38e91d..71ab8960 100644 --- a/src/Modules/Engine.cpp +++ b/src/Modules/Engine.cpp @@ -97,6 +97,40 @@ REDECL(Engine::ParseSmoothingInfo_Mid); REDECL(Engine::ParseSmoothingInfo_Mid_Trampoline); #endif +static void (__rescall *ForceFullUpdate)(void *thisptr, const char *reason); +#ifdef _WIN32 +void __fastcall ForceFullUpdate_Detour(void *thisptr, void *unused, const char *reason); +#else +void ForceFullUpdate_Detour(void *thisptr, const char *reason); +#endif +static Hook ForceFullUpdate_Hook(&ForceFullUpdate_Detour); + +bool g_suppressNextFullUpdate = false; +bool g_fullUpdateSuppressed = false; + +Variable sar_prevent_demo_lagspike("sar_prevent_demo_lagspike", "0", "Prevent the lag spike caused by the full game update when demo recording starts.\n", FCVAR_CHEAT); +DECL_CVAR_CALLBACK(sar_prevent_demo_lagspike) { + if (sar_prevent_demo_lagspike.GetBool() && !sv_cheats.GetBool()) { + console->Print("sar_prevent_demo_lagspike requires sv_cheats 1.\n"); + sar_prevent_demo_lagspike.SetValue(0); + } +} + +#ifdef _WIN32 +void __fastcall ForceFullUpdate_Detour(void *thisptr, void *unused, const char *reason) +#else +void ForceFullUpdate_Detour(void *thisptr, const char *reason) +#endif +{ + if (g_suppressNextFullUpdate && sar_prevent_demo_lagspike.GetBool()) { + g_fullUpdateSuppressed = true; + return; + } + ForceFullUpdate_Hook.Disable(); + ForceFullUpdate(thisptr, reason); + ForceFullUpdate_Hook.Enable(); +} + void Engine::ExecuteCommand(const char *cmd, bool immediately) { this->SendToCommandBuffer(cmd, 0); if (immediately) { @@ -1018,6 +1052,9 @@ bool Engine::Init() { Memory::Deref(HostState_OnClientConnected + Offsets::hoststate, &hoststate); } + ForceFullUpdate = (decltype(ForceFullUpdate))Memory::Scan(this->Name(), Offsets::ForceFullUpdate); + if (ForceFullUpdate) ForceFullUpdate_Hook.SetFunc(ForceFullUpdate); + if (this->engineTrace = Interface::Create(this->Name(), "EngineTraceServer004")) { this->TraceRay = this->engineTrace->Original<_TraceRay>(Offsets::TraceRay); this->PointOutsideWorld = this->engineTrace->Original<_PointOutsideWorld>(Offsets::TraceRay + 14); @@ -1145,6 +1182,7 @@ bool Engine::Init() { Command::Hook("changelevel", Engine::changelevel_command_callback_hook, Engine::changelevel_command_callback); Command::Hook("changelevel2", Engine::changelevel2_command_callback_hook, Engine::changelevel2_command_callback); CVAR_HOOK_AND_CALLBACK(ss_force_primary_fullscreen); + sar_prevent_demo_lagspike.AddCallBack(&sar_prevent_demo_lagspike_callback); host_timescale = Variable("host_timescale"); host_framerate = Variable("host_framerate"); diff --git a/src/Modules/EngineDemoRecorder.cpp b/src/Modules/EngineDemoRecorder.cpp index 249f64cd..66b42923 100644 --- a/src/Modules/EngineDemoRecorder.cpp +++ b/src/Modules/EngineDemoRecorder.cpp @@ -24,6 +24,10 @@ #include #include +extern bool g_suppressNextFullUpdate; +extern bool g_fullUpdateSuppressed; +extern Variable sar_prevent_demo_lagspike; + REDECL(EngineDemoRecorder::SetSignonState); REDECL(EngineDemoRecorder::StartRecording); REDECL(EngineDemoRecorder::StopRecording); @@ -232,9 +236,27 @@ DETOUR(EngineDemoRecorder::StartRecording, const char *filename, bool continuous engine->demorecorder->autorecordStartNum = *engine->demorecorder->m_nDemoNumber + 1; } + if (sar_prevent_demo_lagspike.GetBool()) { + // Pre-set to 0 so the engine's internal SetValue(0) causes no ConVar change and fires no ForceFullUpdate. + engine->ExecuteCommand("cl_localnetworkbackdoor 0", true); + g_suppressNextFullUpdate = true; + } auto result = EngineDemoRecorder::StartRecording(thisptr, filename, continuously); + g_suppressNextFullUpdate = false; - needToRecordInitialVals = true; + if (g_fullUpdateSuppressed) { + g_fullUpdateSuppressed = false; + needToRecordInitialVals = true; + // ForceFullUpdate was suppressed, so SetSignonState(SIGNONSTATE_FULL) won't fire + // naturally. Replicate it through the hooked vtable to initialize the engine's + // packet-recording path and SAR's tracking simultaneously. + *engine->demorecorder->m_bRecording = true; + using SetSignonStateFn = int (__rescall *)(void *, int); + auto fn = engine->demorecorder->s_ClientDemoRecorder->Current(Offsets::SetSignonState); + fn(thisptr, SIGNONSTATE_FULL); + } else { + needToRecordInitialVals = true; + } return result; } @@ -271,6 +293,12 @@ DETOUR(EngineDemoRecorder::StopRecording) { name += std::string("_") + std::to_string(engine->demorecorder->autorecordStartNum); } engine->demoplayer->replayName = name; + + // Keep cl_localnetworkbackdoor at 0 so the next recording starts without lag. + // The engine's StopRecording restores it to 1; we undo that here. + if (sar_prevent_demo_lagspike.GetBool()) { + engine->ExecuteCommand("cl_localnetworkbackdoor 0", true); + } } return result; diff --git a/src/Offsets/Portal 2 9568.hpp b/src/Offsets/Portal 2 9568.hpp index 1b4227e7..43941f0b 100644 --- a/src/Offsets/Portal 2 9568.hpp +++ b/src/Offsets/Portal 2 9568.hpp @@ -449,6 +449,7 @@ SIGSCAN_DEFAULT(ResetToneMapping, "55 8B EC 83 EC 14 F3 0F 10 45 ?", "55 89 E5 5 // Engine SIGSCAN_DEFAULT(ParseSmoothingInfoSig, "55 8B EC 0F 57 C0 81 EC ? ? ? ? B9 ? ? ? ? 8D 85 ? ? ? ? EB", ""); // "cl_demosmootherpanel.cpp" xref -> CDemoSmootherPanel::ParseSmoothingInfo OFFSET_DEFAULT(ParseSmoothingInfoOff, 178, -1) +SIGSCAN_DEFAULT(ForceFullUpdate, "55 8B EC 56 8B F1 83 BE CC 00 00 00 FF 74 21 E8 ? ? ? ? 8B 45 08", "") // "cl_fullupdate" string xref -> ConCommand callback -> CClientState::ForceFullUpdate SIGSCAN_DEFAULT(Host_AccumulateTime, "55 8B EC 51 F3 0F 10 05 ? ? ? ? F3 0F 58 45 08 8B 0D ? ? ? ? F3 0F 11 05 ? ? ? ? 8B 01 8B 50 20 53 B3 01 FF D2", "83 EC 1C 8B 15 ? ? ? ? F3 0F 10 05 ? ? ? ? F3 0F 58 44 24 20 F3 0F 11 05 ? ? ? ? 8B 02 8B 40 24 3D ? ? ? ? 0F 85 41 03 00 00") // "-tools" -> function with 2 references -> Host_AccumulateTime SIGSCAN_DEFAULT(_Host_RunFrame_Render, "A1 ? ? ? ? 85 C0 75 1B 8B 0D ? ? ? ? 8B 01 8B 50 40 68 ? ? ? ? FF D2 A3 ? ? ? ? 85 C0 74 0D 6A 02 6A F6 50 E8 ? ? ? ? 83 C4 0C",