diff --git a/docs/cvars.md b/docs/cvars.md index beeca5d8..63c9e43d 100644 --- a/docs/cvars.md +++ b/docs/cvars.md @@ -173,6 +173,7 @@ |sar_demo_clean_start|0|Attempts to minimize visual interpolation of some elements (like post-processing or lighting) when demo playback begins.| |sar_demo_clean_start_tonemap|0|Overrides initial tonemap scalar value used in auto-exposure.
Setting it to 0 will attempt to skip over to target value for several ticks.| |sar_demo_clean_start_tonemap_sample|cmd|sar_demo_clean_start_tonemap_sample [tick] - samples tonemap scale from current demo at given tick and stores it in "sar_demo_clean_start_tonemap" variable. If no tick is given, sampling will happen when `__END__` is seen in demo playback.| +|sar_demo_modelcache_clear_protected_flags|0|Fix demo model-cache growth by clearing stale CModelLoader protected flags on demo stop.| |sar_demo_overwrite_bak|0|Rename demos to (name)_bak if they would be overwritten by recording| |sar_demo_portal_interp_fix|1|Fix eye interpolation through portals in demo playback.| |sar_demo_remove_broken|1|Whether to remove broken frames from demo playback| diff --git a/src/Features/Demo/ModelCacheTools.cpp b/src/Features/Demo/ModelCacheTools.cpp new file mode 100644 index 00000000..2b90a45d --- /dev/null +++ b/src/Features/Demo/ModelCacheTools.cpp @@ -0,0 +1,84 @@ +#include "Event.hpp" +#include "Modules/Console.hpp" +#include "Modules/Server.hpp" +#include "Offsets.hpp" +#include "Utils.hpp" +#include "Utils/Memory.hpp" +#include "Variable.hpp" + +namespace { +constexpr unsigned int MODEL_FLAG_PROTECTED_MASK = 0x7E; +constexpr unsigned int MODEL_FLAG_SKIP_PROTECTED_CLEAR = 0x40; +constexpr unsigned int MAX_REASONABLE_MODEL_COUNT = 16384; + +DECL_CVAR_CALLBACK(sar_demo_modelcache_clear_protected_flags); + +Variable sar_demo_modelcache_clear_protected_flags( + "sar_demo_modelcache_clear_protected_flags", + "0", + "Fix demo model-cache growth by clearing stale CModelLoader protected flags on demo stop. Requires sv_cheats 1 to enable.\n", + FCVAR_NONE, + sar_demo_modelcache_clear_protected_flags_callback); + +DECL_CVAR_CALLBACK(sar_demo_modelcache_clear_protected_flags) { + if (sar_demo_modelcache_clear_protected_flags.GetBool() && flOldValue == 0.0f && !sv_cheats.GetBool()) { + console->Print("sar_demo_modelcache_clear_protected_flags requires sv_cheats 1.\n"); + sar_demo_modelcache_clear_protected_flags.SetValue(pOldValue ? pOldValue : "0"); + } +} + +void *GetModelLoader() { + static uintptr_t global = 0; + if (!global) { + auto site = Memory::Scan(MODULE("engine"), Offsets::CModelLoaderModelPrecache); + if (!site) return nullptr; + + global = Memory::Deref(site + Offsets::CModelLoaderModelPrecacheGlobal); + } + + return Memory::Deref(global); +} + +void ClearProtectedModelFlags() { + auto modelLoader = reinterpret_cast(GetModelLoader()); + if (!modelLoader) { + static bool warned = false; + if (!warned) { + warned = true; + console->Warning("SAR: Failed to find CModelLoader for repeated-demo model-cache cleanup.\n"); + } + return; + } + + auto entries = *reinterpret_cast(modelLoader + Offsets::CModelLoaderEntryArray); + auto count = *reinterpret_cast(modelLoader + Offsets::CModelLoaderEntryCount); + if (!entries || count > MAX_REASONABLE_MODEL_COUNT) { + static bool warned = false; + if (!warned) { + warned = true; + console->Warning( + "SAR: Invalid CModelLoader state (loader=%p entries=%p count=%u).\n", + reinterpret_cast(modelLoader), + reinterpret_cast(entries), + count); + } + return; + } + + for (unsigned int i = 0; i < count; ++i) { + auto model = *reinterpret_cast(entries + i * Offsets::CModelLoaderEntryStride + Offsets::CModelLoaderEntryModel); + if (!model) continue; + + auto flags = reinterpret_cast(model + Offsets::CModelLoaderModelFlags); + if ((*flags & MODEL_FLAG_SKIP_PROTECTED_CLEAR) != 0) continue; + + *flags &= ~MODEL_FLAG_PROTECTED_MASK; + } +} +} // namespace + +ON_EVENT(DEMO_STOP) { + if (sar_demo_modelcache_clear_protected_flags.GetBool()) { + ClearProtectedModelFlags(); + } +} diff --git a/src/Offsets/Portal 2 5723.hpp b/src/Offsets/Portal 2 5723.hpp index bbab9f47..746730b3 100644 --- a/src/Offsets/Portal 2 5723.hpp +++ b/src/Offsets/Portal 2 5723.hpp @@ -30,3 +30,12 @@ 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, "") \ No newline at end of file diff --git a/src/Offsets/Portal 2 8151.hpp b/src/Offsets/Portal 2 8151.hpp index f79461e8..796e189e 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") @@ -82,6 +87,15 @@ SIGSCAN_LINUX(InsertCommand, "55 89 E5 57 56 53 83 EC 1C 8B 75 ? 8B 5D ? 81 FE F // EngineDemoPlayer SIGSCAN_LINUX(InterpolateDemoCommand, "55 31 C9 89 E5 57 56 53 83 EC 3C 89 4D F0 8B 45 08 8B 4D 14 8B 80 B0 05 00 00 89 45 B8 8B 45 14 83 C0 04 89 45 D0") +// CModelLoader +SIGSCAN_LINUX(CModelLoaderModelPrecache, "A1 ? ? ? ? 8B 8D ? ? ? ? 8B 10 C7 44 24 08 04 00 00 00 89 4C 24 04 89 04 24 FF 52 1C") +OFFSET_LINUX(CModelLoaderModelPrecacheGlobal, 1) +OFFSET_LINUX(CModelLoaderEntryArray, 0x8) +OFFSET_LINUX(CModelLoaderEntryCount, 0x16) +OFFSET_LINUX(CModelLoaderEntryStride, 0x10) +OFFSET_LINUX(CModelLoaderEntryModel, 0xC) +OFFSET_LINUX(CModelLoaderModelFlags, 0x108) + // MaterialSystem SIGSCAN_LINUX(KeyValues_SetString, "55 89 E5 53 83 EC ? 8B 45 ? C7 44 24 ? ? ? ? ? 8B 5D ? 89 44 24 ? 8B 45 ? 89 04 24 E8 ? ? ? ? 85 C0 74 ? 89 5D") @@ -98,5 +112,8 @@ 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, "") // clang-format on diff --git a/src/Offsets/Portal 2 9568.hpp b/src/Offsets/Portal 2 9568.hpp index 820e0826..be38ef68 100644 --- a/src/Offsets/Portal 2 9568.hpp +++ b/src/Offsets/Portal 2 9568.hpp @@ -481,7 +481,21 @@ OFFSET_DEFAULT(StartupDemoFile_HeaderName, 212, 184) // EngineDemoPlayer SIGSCAN_DEFAULT(InterpolateDemoCommand, "55 8B EC 83 EC 10 56 8B F1 8B 4D 10 57 8B BE B4 05 00 00 83 C1 04 89 75 F4 89 7D F0 E8 ? ? ? ? 8B 4D 14 83 C1 04", - "55 57 56 53 83 EC 10 8B 44 24 24 8B 5C 24 2C 8B 88 B0 05 00 00 8B 44 24 30 8D 70 04 8D 90 9C 00 00 00 89 F0 F3 0F 10 40 04") + "55 57 56 53 83 EC 10 8B 44 24 24 8B 5C 24 2C 8B 88 B0 05 00 00 8B 44 24 30 8D 70 04 8D 90 9C 00 00 00 89 F0 F3 0F 10 40 04") + + +// CModelLoader +// win: "modelprecache" xref -> client string-table update callback -> CModelLoader vtable +0x1C call with flag 4; global immediate is g_pModelLoader +// linux: "CClientState::ConsistencyCheck" xref -> model consistency type 3 block -> CModelLoader vtable +0x1C call with flag 4; global immediate is g_pModelLoader +SIGSCAN_DEFAULT(CModelLoaderModelPrecache, + "8B 0D ? ? ? ? 8B 11 6A 04 50 8B 42 1C FF D0 50 EB 02 6A 00", + "A1 ? ? ? ? 83 EC 04 8B 10 6A 04 FF B5 ? ? ? ? 50 FF 52 1C 89 85 ? ? ? ? 83 C4 10 85 C0") +OFFSET_DEFAULT(CModelLoaderModelPrecacheGlobal, 2, 1) +OFFSET_DEFAULT(CModelLoaderEntryArray, 0x8, 0x8) // "CModelLoader::FindModel: NULL name" xref -> successful lookup path reads [this+8] + index*0x10 + 0xC +OFFSET_DEFAULT(CModelLoaderEntryCount, 0x16, 0x16) // same CModelLoader::FindModel tree/list state; active count is this+0x16 +OFFSET_DEFAULT(CModelLoaderEntryStride, 0x10, 0x10) // same CModelLoader::FindModel lookup path; entry nodes are 0x10 bytes +OFFSET_DEFAULT(CModelLoaderEntryModel, 0xC, 0xC) // same CModelLoader::FindModel lookup path; entry+0xC is model_t * +OFFSET_DEFAULT(CModelLoaderModelFlags, 0x108, 0x108) // CModelLoader vtable +0x1C target ORs caller flags into model_t+0x108 // Matchmaking