Commit graph

15 commits

Author SHA1 Message Date
a77db1edcb fix: derive debug-agent client timeout from continue_execution's requested wait
DebugAgentClient.call() hardcoded a 60s socket timeout that also bounded
recv(), so any continue_execution call asking for a longer wait (e.g.
waiting for a user to alt-tab) would time out on the client side while
the debug agent was still legitimately waiting. call() now accepts an
optional socket_timeout override, and the continue_execution tool passes
timeout + 30 so the client socket outlives the agent's own timeout + 10s
of headroom.

Also adds a round-trip test for encode_message/decode_message (the wire
contract between the two independently-built sides of this protocol),
and corrects the WatchpointSlots docstring, which overstated what
Wow64SetThreadContext does -- the agent actually arms the hardware
watchpoints via a native x64 SetThreadContext call, with the Wow64
variant used only for 32-bit register readback.

Claude-Session: https://claude.ai/code/session_01E4YCRPfAE3328FcYPyKu9T
2026-07-04 14:38:54 -07:00
6311ecb69e chore: untrack debug screenshot artifact, ignore future MCP client config
tools/debug_screenshot_gameplay.png was a one-off capture from testing the
runtime-observation MCP's screenshot tool, not something that belongs in
version control. Also ignore .mcp.json (local MCP server registration) and
future tools/debug_screenshot*.png captures.
2026-07-04 14:27:12 -07:00
7d39ccddf0 fix: harden debug agent for reliable WOW64 watchpoints and multi-run stability
Key fixes applied during Task 6 end-to-end MCP integration testing:

- CONTEXT_ALL (CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS) for Wow64Get/SetThreadContext
  so DR0-DR7 are read and written alongside general-purpose registers
- Native x64 SetThreadContext path in _apply_watchpoints_to_thread: Wow64SetThreadContext
  updates the WOW64 context save area but does not propagate to hardware DR registers;
  the native path is required for hardware watchpoints to actually fire on WOW64 threads
- _REGISTER_NAMES extended to include Dr0-Dr3, Dr6, Dr7 so get_registers returns them
- launch() now terminates and drains the previously attached process before starting a
  new one, preventing stale debug events from a hung game from corrupting a new session
- continue_execution filters events by process ID so late-arriving exit events from a
  previously terminated game do not trigger a false "exited" status for the new process
- AV exception routing restored: EXCEPTION_ACCESS_VIOLATION stops and reports; callers
  skip boot-time AVs in a loop rather than silently continuing them in the agent
- launch_with_breakpoints atomic op in agent server and MCP server
- _DebugWorker dedicated thread: Windows debug API requires all WaitForDebugEvent,
  ContinueDebugEvent, and CreateProcessA calls to originate from the same OS thread;
  ThreadingTCPServer violated this -- replaced with single worker thread + queue
- Gameplay screenshot captured at watchpoint stop (272 KB real frame)

Acceptance checklist results:
- Item 1 PASS: bp@0x40C8E0, eip/eax confirmed
- Item 2 PASS: conditional breakpoint false-skip and true-fire verified
- Item 3 PASS: watchpoint@slot0 fired at 0x40E998 (MOV [DAT_0047c488], EAX in
  player_cooldown_timers_tick -- the DAT write is in the caller, not inside
  compute_frame_delta_ticks_60hz_min1_clamp as the spec assumed)
- Item 4 PASS: screenshot PNG captured (gameplay frame 272 KB)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TN4Ytn3gdWRonNmHpisWQv
2026-07-04 14:05:24 -07:00
6baefe8e8a feat: add screenshot capture and local JSON-over-TCP server to debug agent
Adds DebugAgent.screenshot() using Pillow/ImageGrab -- locates the game window
via FindWindowA, grabs its rect, returns PNG bytes. Adds tools/havoc_debug_agent_server.py:
a 127.0.0.1-only ThreadingTCPServer that serializes all ops behind a single lock and
dispatches NDJSON requests to DebugAgent. Covers all debug-agent ops (launch,
breakpoints, watchpoints, continue_execution, read/write_memory, get_registers,
screenshot, get_status). Acceptance test confirmed: get_status returns
{"ok": true, "result": {"status": "not_launched"}} from a fresh server. Adds
tools/requirements-windows.txt (pillow).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TN4Ytn3gdWRonNmHpisWQv
2026-07-04 09:30:07 -07:00
12ec33b41a feat: add hardware watchpoints (DR0-DR3/DR7) and re-armable breakpoints to debug agent
Implements set_watchpoint/remove_watchpoint/list_watchpoints via Wow64SetThreadContext
on DR0-DR3/DR7. Skips 64-bit WOW64 helper threads (Wow64GetThreadContext returns
ERROR_ACCESS_DENIED on those -- found against the real process). Propagates active
watchpoints to new threads as they spawn. Re-armable breakpoints: on breakpoint hit,
restores original byte and sets EFlags trap flag; the resulting single-step re-plants
the 0xCC, making breakpoints persistent across multiple hits. EXCEPTION_SINGLE_STEP
handler distinguishes watchpoint trips (Dr6 bits 0-3 set) from re-arm single-steps
(Dr6 clear). Smoke test confirmed: re-armed breakpoint fires on second continue;
watchpoint on 0x0047C48C set on slot 0 -- full watchpoint trip validation requires
gameplay state==0x47 (deferred to Task 6 end-to-end run).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TN4Ytn3gdWRonNmHpisWQv
2026-07-04 09:26:37 -07:00
9360d1ea7a feat: add Win32 debug agent with breakpoint/register/memory support
Implements DebugAgent class (Task 2) with launch, set/remove breakpoint,
get_registers, read/write_memory, continue_execution, and get_status.
Handles WOW64 WX86 exception codes (0x4000001F/0x4000001E), keeps debuggee
suspended via _pending_event until caller resumes, and tracks stopped thread
for correct register reads. Smoke-tested against live HAVOC_NOCD.EXE: stopped
at breakpoint@0x40C8E0 with eip=0x40c8e0 confirmed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TN4Ytn3gdWRonNmHpisWQv
2026-07-04 09:17:36 -07:00
cf283c9bba fix: close timing-attack and empty-secret auth bypass in MCP server
check_bearer_token now uses hmac.compare_digest for constant-time comparison
and rejects a falsy expected token outright, with a startup guard that fails
loudly if HAVOC_MCP_TOKEN is set but empty. Adds Starlette TestClient coverage
of the _BearerAuthMiddleware auth-enforcement path (no header, wrong token,
correct token), and cleans up dead code found during review: unused token
param on build_mcp_server, a per-request import, and an unused json import.
2026-07-04 07:22:55 -07:00
0388488c74 feat: wire MCP tool definitions and bearer-token auth into server
Adds requirements.txt (mcp==1.28.1, pytest==9.1.1) since no Python
dependency-management convention existed in this repo yet.
2026-07-04 07:13:36 -07:00
b2887753e9 feat: add bearer token auth check for MCP server 2026-07-04 07:08:21 -07:00
d2b85fc276 feat: add DebugAgentClient for MCP-to-debug-agent translation 2026-07-04 07:07:36 -07:00
3256f4b607 feat: add watchpoint slot/DR7 bookkeeping to MCP protocol module 2026-07-04 06:58:46 -07:00
4a218cdb05 feat: add condition evaluator for runtime-observation MCP protocol 2026-07-04 06:57:59 -07:00
198977ed2b feat: crack GRAFIX0N.DAT texture format + extractor
WORLDS/GRAFIX0N.DAT is a big-endian (Mac-origin) tileset: an 8-byte header
(file size at 0x08, entry count at 0x0c), 16-byte entry records (BE offset +
id), and per-graphic blocks with a BE width/height/frames/id header followed by
w*h*frames 8-bit palette indices. Confirmed against the in-game loader at
0x444db0 (16-byte record stride, BE->LE dword swap).

GRAFIX00.DAT decodes to 29 graphics: explosion (id 901, 8 frames), smoke
(id 911, 8 frames), and 27 single-frame 64x64 structure/wall textures
(ids 8001-8028). tools/grafix_extract.py renders them to PNG with any
768-byte RGB palette (the XPAL palettes from INTRFACE.FF work).

- tools/grafix_extract.py: parser + PNG renderer
- docs/FORMATS.md: GRAFIX0N.DAT spec
- README: status update

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TN4Ytn3gdWRonNmHpisWQv
2026-07-03 09:36:41 -07:00
27174a1c79 feat: NOCD patch boots Havoc on modern Windows (defeats CD copy protection)
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
2026-07-03 09:29:59 -07:00
2287f3be12 Initial commit: Havoc preservation toolkit + format docs
- ff_extract.py: .FF (FlashFile) archive extractor with music song
  assembly (WAVE+PLST), ogg/mp3 conversion, and FL Studio pack export
  (samples + per-song MIDI + mapping).
- docs/FORMATS.md: reverse-engineered file formats (.FF, WAVE/PLST music,
  world data + binary notes).
- Original game data is gitignored (copyright Reality Bytes, 1995).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 23:33:30 -07:00