Ghidra decompilation environment stood up on Linux (headless, no sudo): JDK 21 + Ghidra 12.1.2 in ~/tools, reusing the existing analyzed project at tools/ghidra_project/ from the prior Windows session instead of starting fresh. FPS-coupling investigation: traced the main game loop's per-frame path and found the real root cause - compute_frame_delta_ticks_60hz_min1_clamp (0x4092D0) computes correct delta-time from timeGetTime(), but clamps the minimum return value to 1 tick even when zero ticks truly elapsed, causing simulation speed to scale with framerate above 60fps. First patch attempt (NOP the clamp) crashed on level load because the shared delta value has 203 read sites across 40+ functions and only 2 were checked for zero-safety; reverted and archived as a documented lesson. The corrected fix (busy-wait instead of lying) is designed but not yet built. Pause-loop hang fixed: the pause command handler blocks the Windows message queue in a tight GetAsyncKeyState polling loop (found while locating the cheat code handler), which trips modern Windows' "Not Responding" hang detection if left paused too long, permanently disconnecting input. Patched (HAVOC_NOCD_PAUSEFIX_v1.EXE) to pump one message per loop iteration via the game's own existing message-drain function; confirmed fixed by play-testing, including the related "alt-tab while paused resets to menu" symptom. A separate "alt-tab while not paused" reset symptom remains open. New tracking docs: docs/FUNCTIONS.md (function-by-function RE progress), docs/PATCH_VERSIONS.md (binary patch version log), docs/LEVEL_NOTES.md (level/AI design quirks for the Remaster variant). Extended docs/PATCHES.md and docs/METHODOLOGY.md with full discovery narratives including dead ends.
3 KiB
3 KiB
Patched Binary Version Log
Tracks every patched HAVOC_NOCD*.EXE variant we've built, working or not. All binaries are
gitignored (copyrighted game code) -- this file is the durable record of what was tried.
Non-working / superseded binaries live in archive/. The current best-known-working one for
each patch line stays at the repo root so tooling (Ghidra project, scripts) doesn't need
updating every iteration.
| File | Based on | Patch (see docs/PATCHES.md) |
Status | Notes |
|---|---|---|---|---|
HAVOC_NOCD.EXE |
HAVOC.EXE |
Copy-protection + NOCD patch set (full table in docs/PATCHES.md) |
Working, in active use | The baseline. Boots and plays correctly; FPS-coupling bug still present (mitigated via dgVoodoo config, not a binary patch) |
archive/HAVOC_NOCD_FPSFIX_v1.EXE |
HAVOC_NOCD.EXE |
NOP the 9-byte min-1-tick clamp at FO 0x0871d |
Broken -- archived | Crashes on level load. Root cause: only 2 of 203 read sites of the shared delta value (DAT_0047c488) were checked for zero-safety before patching; one of the ~38 unaudited consumer functions almost certainly divides by it. See docs/PATCHES.md "FPS-coupling" section for the full writeup |
HAVOC_NOCD_FPSFIX_v2.EXE (not yet built) |
HAVOC_NOCD.EXE |
Same 9-byte region, corrected: turn the clamp into a busy-wait spin instead of a lie (see docs/PATCHES.md) |
(design only, deprioritized) | Preserves "return value is never 0" for all 203 read sites while still fixing the actual bug. Paused in favor of the pause-loop hang fix and the "maximized = 2x normal speed" lead, which turned out to be a separate mechanism -- see docs/FUNCTIONS.md |
HAVOC_NOCD_PAUSEFIX_v1.EXE |
HAVOC_NOCD.EXE |
Redirect the pause-loop's back-edge through a small code cave that pumps one Windows message per iteration (FO 0x1fbcf + cave at FO 0x66fdf, see docs/PATCHES.md) |
Working, confirmed by play-test | Fixes the "greys out / Not Responding, TAB stops working" hang when paused too long, and the "alt-tab while paused resets to menu" symptom (same root cause). Does NOT fix a separate symptom: alt-tab while not paused still resets to menu -- that's a different, still-open bug, see docs/FUNCTIONS.md. Isolated fix, doesn't touch the FPS-coupling code path |
Conventions
- Version suffix
_v2,_v3, ... increments per patch line (e.g. all FPS-coupling fix attempts), not globally across every patch ever made to the binary. - A version only earns a plain (non-archived) spot at the repo root once it's confirmed
working by play-testing. Anything superseded or confirmed broken moves to
archive/immediately -- don't leave dead/broken binaries at the root where they could get mixed up with the current candidate. - Every entry here should link back to the relevant
docs/PATCHES.mdsection for the actual byte-level detail; this file is the index/status tracker, not the technical writeup. - Update this table in the same commit/session as any patch build or status change (working -> broken, experimental -> confirmed, etc).