docs: update CLAUDE.md for #[vtable(...)] macro and vtable_replace!
Browse filesAdd 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>
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
|
| 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!`
|
| 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
|
| 251 |
-
- **`
|
| 252 |
-
- **
|
| 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 |
|