Diagnosed and fixed the crash that killed HAVOC_NOCD.EXE ~1s after launch
(dgVoodoo window appears black, then the process exits). It was not a clean
exit: an ACCESS_VIOLATION (null-pointer read at VA 0x444E6A) during init.
Root cause is a copy-protection check in the WORLDS\ data-file loader
(0x42a480). It opens each file (GRAFIX/LAND/MAP/STUF) from both a local
"WORLDS\<name>" path and a CD-drive path (HAVOC.INI [SETUP] DRIVE=D:\), and
returns a valid stream only when the local open fails and the CD-drive open
succeeds. With files present locally and no D: drive it returned NULL, and the
GRAFIX loader (0x444db0) dereferenced that NULL without checking.
Fix: 14-byte patch at FO 0x29a2b rewrites the return decision to hand back the
successfully-opened local object. The game now boots to the title screen
("HAVOC(tm) by Reality Bytes"), responsive, main loop running.
- docs/PATCHES.md: full patch table (28 patches) + crash write-up
- tools/: RE + patching scripts (r2pipe disasm, minidump parser, ctypes
debugger, PE/IAT/import analysis)
- run_probe.bat / run_and_log.bat: reliable native launch for repro
- .gitignore: exclude CD images, Ghidra install/project, dgVoodoo, *.ini
Diagnosed via WER minidumps (%LOCALAPPDATA%\CrashDumps) parsed in pure Python
(no cdb/windbg available).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TN4Ytn3gdWRonNmHpisWQv
36 lines
1 KiB
Python
36 lines
1 KiB
Python
import r2pipe, sys, io
|
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
|
|
|
r2 = r2pipe.open("HAVOC_NOCD.EXE", ["-e", "anal.strings=false"])
|
|
r2.cmd("aaa") # analyze all
|
|
|
|
def disasm(addr, n=20):
|
|
return r2.cmd("pd %d @ 0x%x" % (n, addr))
|
|
|
|
def xrefs_to(addr):
|
|
return r2.cmd("axt @ 0x%x" % addr)
|
|
|
|
def xrefs_from(addr):
|
|
return r2.cmd("axf @ 0x%x" % addr)
|
|
|
|
print("=== Entry point function ===")
|
|
ep_info = r2.cmd("ie")
|
|
print(ep_info)
|
|
|
|
print("=== 0x464DCA (first call from entry, CRT init?) ===")
|
|
print(disasm(0x464DCA, 40))
|
|
|
|
print("=== Search for writes to [0x480964] (heap handle init) ===")
|
|
# Search for MOV [0x480964], something
|
|
hits = r2.cmd("/ \\xa3\\x64\\x09\\x48\\x00") # MOV [0x480964],EAX
|
|
print("MOV [0x480964],EAX hits:", hits)
|
|
hits2 = r2.cmd("/ \\x89\\x05\\x64\\x09\\x48\\x00") # MOV [0x480964],r32
|
|
print("MOV [0x480964],r32 hits:", hits2)
|
|
|
|
print("=== 0x465BD3 (global ctor runner?) ===")
|
|
print(disasm(0x465BD3, 40))
|
|
|
|
print("=== 0x465BC8 ===")
|
|
print(disasm(0x465BC8, 20))
|
|
|
|
r2.quit()
|