paavohtl Claude Opus 4.6 (1M context) commited on
Commit
d45f977
Β·
1 Parent(s): c090ef4

docs: update CLAUDE.md for typed vtable infrastructure

Browse files

Document new patterns from this session:
- Generic CTask<V: Vtable> and CGameTask<V> for typed vtable pointers
- Task trait with ddgame(), as_task_ptr(), broadcast_message()
- read_volatile requirement in broadcast_message (LLVM caching bug)
- Vtable method hooking via vtable_replace! and replacements/task/
- bind macro dotted field path support (base.vtable)
- Decompiler calling convention warning
- CTaskMissile constructor exception (thiscall, not stdcall)
- New key files: task/base.rs, team_arena.rs, ddgame_constructor.rs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Files changed (1) hide show
  1. CLAUDE.md +15 -4
CLAUDE.md CHANGED
@@ -150,9 +150,11 @@ All addresses in `address.rs` are Ghidra VAs. Always use `rb()` to convert to ru
150
 
151
  ## Calling Convention Rules
152
 
153
- These are critical β€” wrong conventions cause stack corruption and crashes:
154
 
155
- - **Constructors are `__stdcall`**, NOT `__thiscall`. `this` is passed on stack, not ECX.
 
 
156
  - **VTable methods are `__thiscall`**: ECX = this, remaining params on stack.
157
  - **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.
158
  - **MSVC `__usercall`**: Some functions pass implicit params in registers (e.g., FrontendChangeScreen uses ESI for dialog pointer). These need `#[unsafe(naked)]` trampolines.
@@ -166,6 +168,8 @@ Hooks use the `minhook` crate. Two patterns:
166
 
167
  For `__usercall` functions, use a naked trampoline to capture register params before calling the Rust impl.
168
 
 
 
169
  ## Desync Debugging
170
 
171
  Replay desyncs (checksum mismatches) can be caused by any code difference β€” constructor side effects, hooked function behaviour, missing state, wrong calling conventions, etc. Key methodology:
@@ -196,7 +200,11 @@ Currently dormant β€” no hooks wired up. Activate by adding calls in `game_sessi
196
  - `crates/openwa-core/src/wa_call.rs` β€” Helpers for calling WA functions (thiscall, stdcall wrappers)
197
  - `crates/openwa-core/src/rebase.rs` β€” ASLR delta computation
198
  - `crates/openwa-core/src/wa/` β€” Typed WA function wrappers (MFC, frontend, registry, DDGame)
 
 
 
199
  - `crates/openwa-wormkit/src/replacements/` β€” Function replacements (one file per subsystem)
 
200
  - `docs/re-notes/` β€” Reverse engineering documentation (task hierarchy, memory map, frontend screens)
201
  - `docs/plans/` β€” Design docs and implementation plans
202
 
@@ -312,8 +320,11 @@ vtable_replace!(DSSoundVtable, va::DS_SOUND_VTABLE, {
312
  - Unknown struct fields as `_unknown_XX` padding arrays
313
  - Fixed-point: `Fixed(i32)` newtype, 16.16 format (0x10000 = 1.0)
314
  - Naked asm uses `naked_asm!` (Rust 1.79+ syntax), not `asm!`
315
- - **Typed vtable structs via `#[vtable(...)]`**: Use the attribute macro to define vtable structs with `fn(this: *mut T, ...)` shorthand and `#[slot(N)]` for sparse layouts. The macro handles `unsafe extern "thiscall"`, gap-filling, registry metadata, and generates `bind_!` calling wrappers. See the `#[vtable(...)]` section above.
316
- - **`bind_XxxVtable!`**: Generated by `#[vtable(...)]`, creates `&mut self` method wrappers on the class struct. Callers write `(*obj).method(args)`.
 
 
 
317
  - **`vcall!` macro**: Still available for raw-pointer vtable dispatch without bind wrappers. Expands to `((*(*obj).vtable).method)(obj, args...)`.
318
 
319
  ## FFI Style
 
150
 
151
  ## Calling Convention Rules
152
 
153
+ These are critical β€” wrong conventions cause stack corruption and crashes.
154
 
155
+ **The Ghidra decompiler is UNTRUSTWORTHY for calling conventions.** It frequently misidentifies stdcall/thiscall/usercall. Always verify via disassembly: check the `RET imm16` instruction AND the caller's register setup at the call site.
156
+
157
+ - **Constructors are usually `__stdcall`** β€” `this` passed on stack, not ECX. **Exception**: CTaskMissile constructor (0x507D10) is `__thiscall` (ECX=this, 3 stack params, RET 0xC). Always verify by checking the call site's register setup.
158
  - **VTable methods are `__thiscall`**: ECX = this, remaining params on stack.
159
  - **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.
160
  - **MSVC `__usercall`**: Some functions pass implicit params in registers (e.g., FrontendChangeScreen uses ESI for dialog pointer). These need `#[unsafe(naked)]` trampolines.
 
168
 
169
  For `__usercall` functions, use a naked trampoline to capture register params before calling the Rust impl.
170
 
171
+ 3. **Vtable method replacement**: Use `vtable_replace!` to patch vtable slots at runtime. Write the replacement as `unsafe extern "thiscall" fn`. For base-class call-through, either save the original via `[ORIG_STATIC]` syntax, or call `broadcast_message()` for CTask::HandleMessage. See `replacements/task/cloud.rs`.
172
+
173
  ## Desync Debugging
174
 
175
  Replay desyncs (checksum mismatches) can be caused by any code difference β€” constructor side effects, hooked function behaviour, missing state, wrong calling conventions, etc. Key methodology:
 
200
  - `crates/openwa-core/src/wa_call.rs` β€” Helpers for calling WA functions (thiscall, stdcall wrappers)
201
  - `crates/openwa-core/src/rebase.rs` β€” ASLR delta computation
202
  - `crates/openwa-core/src/wa/` β€” Typed WA function wrappers (MFC, frontend, registry, DDGame)
203
+ - `crates/openwa-core/src/task/base.rs` β€” CTask<V>, CTaskVtable, Task trait, SharedDataTable, broadcast_message
204
+ - `crates/openwa-core/src/engine/team_arena.rs` β€” TeamArenaState, WormEntry, TeamHeader, TeamArenaRef
205
+ - `crates/openwa-core/src/engine/ddgame_constructor.rs` β€” DDGame constructor (create_ddgame, init_graphics_and_resources)
206
  - `crates/openwa-wormkit/src/replacements/` β€” Function replacements (one file per subsystem)
207
+ - `crates/openwa-wormkit/src/replacements/task/` β€” Vtable method replacements (cloud, filter, ...)
208
  - `docs/re-notes/` β€” Reverse engineering documentation (task hierarchy, memory map, frontend screens)
209
  - `docs/plans/` β€” Design docs and implementation plans
210
 
 
320
  - Unknown struct fields as `_unknown_XX` padding arrays
321
  - Fixed-point: `Fixed(i32)` newtype, 16.16 format (0x10000 = 1.0)
322
  - Naked asm uses `naked_asm!` (Rust 1.79+ syntax), not `asm!`
323
+ - **Generic CTask<V> for typed vtables**: `CTask<V: Vtable = *const c_void>` and `CGameTask<V: Vtable = *const c_void>` take a vtable pointer type parameter. Subclasses specify their typed vtable: `CTaskTeam { base: CTask<*const CTaskTeamVTable> }`. The `Vtable` marker trait is auto-implemented by the `#[vtable]` macro. `FieldRegistry` derive supports generics by substituting type params with their defaults for `offset_of`/`size_of`.
324
+ - **`Task` trait**: Provides `task()`, `ddgame()`, `as_task_ptr()`, `as_task_ptr_mut()`, and `broadcast_message()` on all CTask subclasses. Eliminates `.base.base` chains. Implemented for all task types in `task/mod.rs`.
325
+ - **`broadcast_message()`**: Pure Rust port of CTask::HandleMessage (0x562F30). Iterates the sparse children array and calls each child's vtable[2]. Uses `read_volatile` for `children_watermark` and `children_data` β€” **required** because LLVM caches these reads across virtual dispatch calls even through `*mut`.
326
+ - **Typed vtable structs via `#[vtable(...)]`**: Use the attribute macro to define vtable structs with `fn(this: *mut T, ...)` shorthand and `#[slot(N)]` for sparse layouts. The macro handles `unsafe extern "thiscall"`, gap-filling, registry metadata, generates `bind_!` calling wrappers, and implements the `Vtable` marker trait. See the `#[vtable(...)]` section above.
327
+ - **`bind_XxxVtable!`**: Generated by `#[vtable(...)]`, creates `&mut self` method wrappers on the class struct. Supports dotted field paths for inherited vtables: `bind_CTaskTeamVTable!(CTaskTeam, base.vtable)`.
328
  - **`vcall!` macro**: Still available for raw-pointer vtable dispatch without bind wrappers. Expands to `((*(*obj).vtable).method)(obj, args...)`.
329
 
330
  ## FFI Style