| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146 |
- // vprof_timings.txt — Source Scramble memory patches that make Source Engine
- // VProf captures record real per-frame timing data.
- //
- // Target: Team Fortress 2 build 11068238 (Linux x86 and x86_64),
- // addons/sourcemod/extensions/sourcescramble.ext.
- //
- // Background
- // ----------
- // The VProf recorder writes the *current*-frame counters:
- // CVProfRecorder::Record_WriteTimings_R() reads pIn->m_nCurFrameCalls
- // and pIn->m_CurFrameTime
- // but engine/vprof_engine.cpp calls VProfRecord_Snapshot() after
- // CVProfile::MarkFrame() has already rotated those counters into the
- // *previous*-frame fields (m_nPrevFrameCalls / m_PrevFrameTime) and cleared
- // the current ones. The result is that every .vprof capture contains a
- // correct call tree with all-zero times and call counts.
- //
- // CVProfExport::CalculateBudgetGroupTimes_Recursive() additionally calls
- // CVProfNode::ClearPrevTime() before the recorder runs, which would wipe the
- // previous-frame data even if the recorder read it.
- //
- // This patch set:
- // 1. redirects the three current-frame field reads in
- // Record_WriteTimings_R() to the matching previous-frame fields, and
- // 2. forces VProfRecord_IsPlayingBack() to return true so that
- // CalculateBudgetGroupTimes_Recursive() skips ClearPrevTime().
- //
- // The recorder still runs after the export, but the previous-frame data now
- // survives until it does. The recorded frame carries the tick of the frame
- // it was snapshotted in (a harmless one-frame label offset; the timing values
- // themselves are correct).
- //
- // The signatures are resolved from the unstripped engine binary's ELF
- // .symtab via SourceMod's `@symbol` support (TF2 reports hidden symbols, so
- // SourceMod uses MemoryUtils::ResolveSymbol rather than dlsym). This keeps
- // them independent of ASLR / load-base relocation. The byte offsets are
- // relative to the function entry and are build-specific; regenerate them
- // whenever the engine is updated.
- "Games"
- {
- "tf"
- {
- "Signatures"
- {
- // CVProfRecorder::Record_WriteTimings_R(CVProfNode const*)
- "CVProfRecorder::Record_WriteTimings_R()"
- {
- "library" "engine"
- "linux" "@_ZN14CVProfRecorder21Record_WriteTimings_REPK10CVProfNode"
- "linux64" "@_ZN14CVProfRecorder21Record_WriteTimings_REPK10CVProfNode"
- }
- // bool VProfRecord_IsPlayingBack()
- "VProfRecord_IsPlayingBack()"
- {
- "library" "engine"
- "linux" "@_Z25VProfRecord_IsPlayingBackv"
- "linux64" "@_Z25VProfRecord_IsPlayingBackv"
- }
- }
- "MemPatches"
- {
- // pIn->m_nCurFrameCalls (0x38) -> pIn->m_nPrevFrameCalls (0x44).
- // 32-bit: mov eax,[esi+0x38] at +0x1B, displacement at +0x1D.
- // 64-bit: mov eax,[rsi+0x4C] at +0x1C, displacement at +0x1E.
- "CVProfRecorder::Record_WriteTimings_R()::UsePrevCalls"
- {
- "signature" "CVProfRecorder::Record_WriteTimings_R()"
- "linux"
- {
- "offset" "1Dh"
- "verify" "\x38"
- "patch" "\x44"
- }
- "linux64"
- {
- "offset" "1Eh"
- "verify" "\x4C"
- "patch" "\x58"
- }
- }
- // pIn->m_CurFrameTime -> pIn->m_PrevFrameTime.
- //
- // 32-bit CCycleCount is an unaligned int64 and the compiler reads
- // its high dword for the 64x32 multiplication:
- // imul ecx,[esi+0x40],1000000 (high, 0x40 -> 0x4C, +0x53)
- // mul [esi+0x3C] (low, 0x3C -> 0x48, +0x5F)
- // 64-bit is a single 64-bit load:
- // imul rax,[rbx+0x50],1000000 (0x50 -> 0x60, +0x66)
- "CVProfRecorder::Record_WriteTimings_R()::UsePrevTimeHigh"
- {
- "signature" "CVProfRecorder::Record_WriteTimings_R()"
- "linux"
- {
- "offset" "53h"
- "verify" "\x40"
- "patch" "\x4C"
- }
- "linux64"
- {
- "offset" "66h"
- "verify" "\x50"
- "patch" "\x60"
- }
- }
- // 32-bit only: low dword of m_CurFrameTime.
- "CVProfRecorder::Record_WriteTimings_R()::UsePrevTimeLow"
- {
- "signature" "CVProfRecorder::Record_WriteTimings_R()"
- "linux"
- {
- "offset" "5Fh"
- "verify" "\x3C"
- "patch" "\x48"
- }
- }
- // Force VProfRecord_IsPlayingBack() to return true so the export
- // path does not clear the previous-frame data before the recorder
- // reads it:
- // cmp dword [...], 2 ; sete al ; ret
- // becomes
- // cmp dword [...], 2 ; mov al,1 ; nop ; ret
- "VProfRecord_IsPlayingBack()::SkipClearPrev"
- {
- "signature" "VProfRecord_IsPlayingBack()"
- "linux"
- {
- "offset" "7h"
- "verify" "\x0F\x94\xC0"
- "patch" "\xB0\x01\x90"
- }
- "linux64"
- {
- "offset" "7h"
- "verify" "\x0F\x94\xC0"
- "patch" "\xB0\x01\x90"
- }
- }
- }
- }
- }
|