havoc-remaster/docs/PATCH_VERSIONS.md
pyr0ball 763509e4bc feat: set up Linux Ghidra pipeline, find and patch FPS-coupling and pause-hang bugs
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.
2026-07-03 22:32:28 -07:00

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.md section 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).