paavohtl Claude Opus 4.6 commited on
Commit
8636b4b
Β·
1 Parent(s): b10ab27

docs: add CLAUDE.md with project guidance for Claude Code

Browse files

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Files changed (1) hide show
  1. CLAUDE.md +110 -0
CLAUDE.md ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project
6
+
7
+ OpenWA is an incremental Rust reimplementation of Worms Armageddon 3.8.1 (Steam). The strategy is "parasite" β€” WormKit DLLs injected into the running game that progressively replace functions, starting from self-contained "leaf" functions and working inward.
8
+
9
+ **Target:** WA.exe is a 32-bit x86 Windows PE binary built with MSVC 2005 + MFC. All Rust code targets `i686-pc-windows-msvc`.
10
+
11
+ ## Build & Test
12
+
13
+ ```bash
14
+ # Build everything (default target is i686-pc-windows-msvc via .cargo/config.toml)
15
+ cargo build --release
16
+
17
+ # Build a specific crate
18
+ cargo build -p openwa-wormkit --release
19
+ cargo build -p openwa-validator --release
20
+
21
+ # Run tests (openwa-types is pure Rust, works on any host)
22
+ cargo test -p openwa-types
23
+
24
+ # Run harness tests (must be 32-bit, needs WA.exe on disk)
25
+ cargo test --target i686-pc-windows-msvc -p openwa-harness
26
+
27
+ # Single test
28
+ cargo test -p openwa-types -- scheme_parse::parse_beginner_v2
29
+ ```
30
+
31
+ ## Deploy to Game
32
+
33
+ Game directory: `I:\games\SteamLibrary\steamapps\common\Worms Armageddon`
34
+
35
+ Copy release DLLs from `target/i686-pc-windows-msvc/release/` to the game directory:
36
+ - `openwa_wormkit.dll` β†’ rename to `wkOpenWA.dll`
37
+ - `openwa_validator.dll` β†’ rename to `wkOpenWAValidator.dll`
38
+
39
+ Quick deploy:
40
+ ```bash
41
+ cp target/i686-pc-windows-msvc/release/openwa_wormkit.dll "I:/games/SteamLibrary/steamapps/common/Worms Armageddon/wkOpenWA.dll"
42
+ cp target/i686-pc-windows-msvc/release/openwa_validator.dll "I:/games/SteamLibrary/steamapps/common/Worms Armageddon/wkOpenWAValidator.dll"
43
+ ```
44
+
45
+ WormKit auto-loads any `wk*.dll` from the game directory. Logs appear in the game directory as `OpenWA.log` and `OpenWA_validation.log`.
46
+
47
+ ## Crate Architecture
48
+
49
+ - **`openwa-types`** β€” Enums, structs, addresses, parsers (no_std compatible). The source of truth for all reverse-engineered type layouts and known addresses (`address.rs`). No game dependency.
50
+ - **`openwa-validator`** β€” WormKit cdylib that validates openwa-types against live WA.exe memory. Logs to `OpenWA_validation.log`. Read-only β€” never modifies game state.
51
+ - **`openwa-wormkit`** β€” WormKit cdylib that replaces WA functions with Rust. Logs to `OpenWA.log`. Uses MinHook for inline hooking. This is where reimplemented game logic lives.
52
+ - **`openwa-harness`** β€” Offline test harness that loads WA.exe into process memory via `LoadLibraryExA(DONT_RESOLVE_DLL_REFERENCES)` for testing without running the game.
53
+
54
+ ## ASLR Rebasing
55
+
56
+ WA.exe has ASLR enabled. Ghidra shows addresses at image base 0x400000, but runtime base varies. Both DLLs compute a delta at startup:
57
+
58
+ ```rust
59
+ let base = GetModuleHandleA(NULL) as u32;
60
+ let delta = base.wrapping_sub(0x400000);
61
+ // rb(ghidra_addr) = ghidra_addr + delta
62
+ ```
63
+
64
+ All addresses in `address.rs` are Ghidra VAs. Always use `rb()` to convert to runtime addresses.
65
+
66
+ ## Calling Convention Rules
67
+
68
+ These are critical β€” wrong conventions cause stack corruption and crashes:
69
+
70
+ - **Constructors are `__stdcall`**, NOT `__thiscall`. `this` is passed on stack, not ECX.
71
+ - **VTable methods are `__thiscall`**: ECX = this, remaining params on stack.
72
+ - **Always check `RET imm16`** in disassembly to verify stack parameter count. The immediate value = bytes of params cleaned by callee (stdcall/thiscall). `RET 0x10` = 16 bytes = 4 params.
73
+ - **MSVC `__usercall`**: Some functions pass implicit params in registers (e.g., FrontendChangeScreen uses ESI for dialog pointer). These need `#[unsafe(naked)]` trampolines.
74
+
75
+ ## Hooking Patterns
76
+
77
+ Hooks use the `minhook` crate. Two patterns:
78
+
79
+ 1. **Passthrough hook** (logging only): Call original via trampoline, log result. See `replacements/scheme.rs`.
80
+ 2. **Full replacement**: Reimplement the function in Rust, call WA functions via `wa_call` helpers. See `replacements/frontend.rs`.
81
+
82
+ For `__usercall` functions, use a naked trampoline to capture register params before calling the Rust impl.
83
+
84
+ ## Key Files
85
+
86
+ - `crates/openwa-types/src/address.rs` β€” All known WA.exe addresses with comments
87
+ - `crates/openwa-wormkit/src/replacements/` β€” Function replacements (one file per subsystem)
88
+ - `crates/openwa-wormkit/src/wa_call.rs` β€” Helpers for calling WA functions (thiscall, stdcall wrappers)
89
+ - `crates/openwa-wormkit/src/rebase.rs` β€” ASLR delta computation
90
+ - `docs/re-notes/` β€” Reverse engineering documentation (task hierarchy, memory map, frontend screens)
91
+ - `docs/plans/` β€” Design docs and implementation plans
92
+
93
+ ## Ghidra MCP
94
+
95
+ A Ghidra MCP bridge is configured in `.mcp.json`. When using Ghidra tools:
96
+ - **Prefer batch tools** (`batch_create_labels`, `batch_rename_function_components`) β€” single-item tools have address parsing bugs.
97
+ - WA.exe is loaded at image base 0x400000 in Ghidra.
98
+
99
+ ## Third-Party RE Sources
100
+
101
+ - `C:\koodia\worms-re\thirdparty\wkJellyWorm\` β€” Extensive RE data: task hierarchy, vtables, enums, weapon structs
102
+ - `C:\koodia\worms-re\thirdparty\WormKit\` β€” Modding framework, game state structures
103
+
104
+ ## Design Conventions
105
+
106
+ - `Ptr32 = u32` for pointer fields (compiles on 64-bit host, correct sizes on 32-bit target)
107
+ - `#[repr(u32)]` enums with `TryFrom<u32>` for safe conversion from game memory
108
+ - Unknown struct fields as `_unknown_XX` padding arrays
109
+ - Fixed-point: `Fixed(i32)` newtype, 16.16 format (0x10000 = 1.0)
110
+ - Naked asm uses `naked_asm!` (Rust 1.79+ syntax), not `asm!`