paavohtl Claude Opus 4.6 (1M context) commited on
Commit
55094ab
Β·
1 Parent(s): 279a5d8

docs: update CLAUDE.md for #[vtable(...)] macro and vtable_replace!

Browse files

Add documentation for the vtable attribute macro, bind_! companion
macros, name-based vtable_replace!, and updated design conventions.
Remove outdated vtable guidance that predates the macro system.

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

Files changed (1) hide show
  1. CLAUDE.md +39 -5
CLAUDE.md CHANGED
@@ -115,7 +115,7 @@ Key env vars:
115
  ## Crate Architecture
116
 
117
  - **`openwa-core`** β€” Types, addresses, parsers, ASLR rebasing, and typed WA function wrappers. The source of truth for all reverse-engineered type layouts and known addresses. Contains `registry` (structured address database + field registries), `rebase` (ASLR delta), `wa_call` (calling convention helpers), and `wa/` (typed handle wrappers like `DDGameWrapperHandle`, `CWndHandle`).
118
- - **`openwa-derive`** β€” Proc macro crate. Provides `#[derive(FieldRegistry)]` for auto-generating field maps from `#[repr(C)]` structs using `offset_of!()`.
119
  - **`openwa-wormkit`** β€” Unified WormKit cdylib that replaces WA functions with Rust and optionally validates types against live memory. Logs to `OpenWA.log` (hooks) and `OpenWA_validation.log` (validation). Uses MinHook for inline hooking. Validation is enabled via `OPENWA_VALIDATE=1` env var.
120
  - **`openwa-harness`** β€” Offline test harness that loads WA.exe into process memory via `LoadLibraryExA(DONT_RESOLVE_DLL_REFERENCES)` for testing without running the game.
121
  - **`openwa-test-runner`** β€” Headless replay test runner (`openwa-test` binary). Discovers replay tests, runs them concurrently via WA.exe's `/getlog` mode, compares output logs. See "Replay Testing" section.
@@ -176,7 +176,7 @@ Currently dormant β€” no hooks wired up. Activate by adding calls in `game_sessi
176
 
177
  - `crates/openwa-core/src/address.rs` β€” Known WA.exe addresses (segment boundaries + re-exports from home modules)
178
  - `crates/openwa-core/src/registry.rs` β€” Structured address registry, field registries, live object tracker, query API
179
- - `crates/openwa-core/src/macros.rs` β€” `define_addresses!` macro and `vcall!` macro
180
  - `crates/openwa-core/src/mem.rs` β€” Pointer classification and `identify_pointer()` for debug tools
181
  - `crates/openwa-core/src/wa_call.rs` β€” Helpers for calling WA functions (thiscall, stdcall wrappers)
182
  - `crates/openwa-core/src/rebase.rs` β€” ASLR delta computation
@@ -241,15 +241,49 @@ pub struct CTask { ... }
241
  - `field_at_inherited("CTaskWorm", offset)` β€” inheritance-aware field lookup (walks CTaskWorm β†’ CGameTask β†’ CTask)
242
  - `identify_pointer(value, delta)` β†’ `PointerIdentity` β€” full pointer identification (static addresses, live objects, vtable-based object detection)
243
  - `register_live_object()` / `identify_live_pointer()` β€” track heap objects for field-level pointer resolution
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
 
245
  ## Design Conventions
246
 
247
  - Unknown struct fields as `_unknown_XX` padding arrays
248
  - Fixed-point: `Fixed(i32)` newtype, 16.16 format (0x10000 = 1.0)
249
  - Naked asm uses `naked_asm!` (Rust 1.79+ syntax), not `asm!`
250
- - **Typed vtable structs**: Define `#[repr(C)]` vtable structs with typed function pointers for known slots and `usize` for unknown slots (see `PaletteVtable`, `SoundEmitterVTable`). Set the class struct's vtable field to `*const FooVtable` instead of `*mut u8`.
251
- - **`vcall!` macro**: Use `vcall!(obj, method, args...)` for one-liner vtable dispatch. Expands to `((*(*obj).vtable).method)(obj, args...)`.
252
- - **Virtual method wrappers**: Add `impl` methods on the class struct that wrap `vcall!`. Callers write `(*obj).method(args)` β€” idiomatic, type-safe, and hides the vtable indirection.
253
 
254
  ## FFI Style
255
 
 
115
  ## Crate Architecture
116
 
117
  - **`openwa-core`** β€” Types, addresses, parsers, ASLR rebasing, and typed WA function wrappers. The source of truth for all reverse-engineered type layouts and known addresses. Contains `registry` (structured address database + field registries), `rebase` (ASLR delta), `wa_call` (calling convention helpers), and `wa/` (typed handle wrappers like `DDGameWrapperHandle`, `CWndHandle`).
118
+ - **`openwa-derive`** β€” Proc macro crate. Provides `#[derive(FieldRegistry)]` for struct field maps and `#[vtable(...)]` for typed vtable definitions with introspection, calling wrappers, and replacement support.
119
  - **`openwa-wormkit`** β€” Unified WormKit cdylib that replaces WA functions with Rust and optionally validates types against live memory. Logs to `OpenWA.log` (hooks) and `OpenWA_validation.log` (validation). Uses MinHook for inline hooking. Validation is enabled via `OPENWA_VALIDATE=1` env var.
120
  - **`openwa-harness`** β€” Offline test harness that loads WA.exe into process memory via `LoadLibraryExA(DONT_RESOLVE_DLL_REFERENCES)` for testing without running the game.
121
  - **`openwa-test-runner`** β€” Headless replay test runner (`openwa-test` binary). Discovers replay tests, runs them concurrently via WA.exe's `/getlog` mode, compares output logs. See "Replay Testing" section.
 
176
 
177
  - `crates/openwa-core/src/address.rs` β€” Known WA.exe addresses (segment boundaries + re-exports from home modules)
178
  - `crates/openwa-core/src/registry.rs` β€” Structured address registry, field registries, live object tracker, query API
179
+ - `crates/openwa-core/src/macros.rs` β€” `define_addresses!`, `vcall!`, and `vtable_replace!` macros
180
  - `crates/openwa-core/src/mem.rs` β€” Pointer classification and `identify_pointer()` for debug tools
181
  - `crates/openwa-core/src/wa_call.rs` β€” Helpers for calling WA functions (thiscall, stdcall wrappers)
182
  - `crates/openwa-core/src/rebase.rs` β€” ASLR delta computation
 
241
  - `field_at_inherited("CTaskWorm", offset)` β€” inheritance-aware field lookup (walks CTaskWorm β†’ CGameTask β†’ CTask)
242
  - `identify_pointer(value, delta)` β†’ `PointerIdentity` β€” full pointer identification (static addresses, live objects, vtable-based object detection)
243
  - `register_live_object()` / `identify_live_pointer()` β€” track heap objects for field-level pointer resolution
244
+ - `vtable_info_for("PaletteVtable")` β€” vtable slot metadata (name, index, doc)
245
+
246
+ ### `#[vtable(...)]` attribute macro
247
+
248
+ Defines typed vtable structs from sparse slot definitions. The macro generates the full `#[repr(C)]` struct with `usize` gap-fillers, registry metadata, a companion `bind_!` macro, and optional address constants.
249
+
250
+ ```rust
251
+ #[openwa_core::vtable(size = 38, va = 0x0066_A218, class = "DDDisplay")]
252
+ pub struct DDDisplayVtable {
253
+ /// set layer color
254
+ #[slot(4)]
255
+ pub set_layer_color: fn(this: *mut DDDisplay, layer: i32, color: i32),
256
+ /// set active layer, returns layer context ptr
257
+ #[slot(5)]
258
+ pub set_active_layer: fn(this: *mut DDDisplay, layer: i32) -> *mut u8,
259
+ }
260
+
261
+ // Generate calling wrappers on the class struct
262
+ bind_DDDisplayVtable!(DDDisplay, vtable);
263
+ ```
264
+
265
+ Key features:
266
+ - **`#[slot(N)]`** for sparse vtables β€” gaps auto-filled with `usize`. Optional when all slots are declared sequentially.
267
+ - **`fn(...)` shorthand** β€” auto-normalized to `unsafe extern "thiscall" fn(...)`.
268
+ - **Named parameters** β€” `fn(this: *mut T, mode: u32)` flows through to generated wrappers as `fn set_mode(&mut self, mode: u32)`. The `this` param becomes `&mut self` (or `&self` for `*const`).
269
+ - **`bind_XxxVtable!`** β€” companion macro generates method wrappers on the class struct.
270
+ - **`vtable_replace!`** β€” type-safe vtable slot patching for `install()` functions. Accepts method names (resolved via `offset_of!`) or slot indices:
271
+
272
+ ```rust
273
+ vtable_replace!(DSSoundVtable, va::DS_SOUND_VTABLE, {
274
+ play_sound [originals::PLAY] => my_play_sound, // save original + replace
275
+ load_wav => my_load_wav, // pure replace
276
+ })?;
277
+ ```
278
 
279
  ## Design Conventions
280
 
281
  - Unknown struct fields as `_unknown_XX` padding arrays
282
  - Fixed-point: `Fixed(i32)` newtype, 16.16 format (0x10000 = 1.0)
283
  - Naked asm uses `naked_asm!` (Rust 1.79+ syntax), not `asm!`
284
+ - **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.
285
+ - **`bind_XxxVtable!`**: Generated by `#[vtable(...)]`, creates `&mut self` method wrappers on the class struct. Callers write `(*obj).method(args)`.
286
+ - **`vcall!` macro**: Still available for raw-pointer vtable dispatch without bind wrappers. Expands to `((*(*obj).vtable).method)(obj, args...)`.
287
 
288
  ## FFI Style
289