paavohtl Claude Opus 4.6 (1M context) commited on
Commit
77aec3a
Β·
1 Parent(s): d32514c

feat: add /type-struct skill for struct identification and typing

Browse files

Formalizes the process of turning raw pointer arithmetic into typed
Rust structs: identify β†’ map fields β†’ create struct β†’ update types β†’
replace pointer math β†’ propagate β†’ verify.

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

Files changed (1) hide show
  1. .claude/skills/type-struct/SKILL.md +82 -0
.claude/skills/type-struct/SKILL.md ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ name: type-struct
3
+ description: Identify a class/struct from raw pointer usage, create a typed Rust struct, and propagate type information to eliminate pointer arithmetic. Works with Ghidra for reverse engineering context.
4
+ ---
5
+
6
+ # Type a Struct
7
+
8
+ Turn raw pointer arithmetic (`base.add(0x1234)`, `wb(0x878095 + idx * 0xD7B, ...)`) into typed Rust struct field access.
9
+
10
+ ## Process
11
+
12
+ ### 1. Identify the struct
13
+
14
+ Find a pattern: repeated stride arithmetic, consistent base+offset reads/writes, or a Ghidra-visible vtable.
15
+
16
+ - Grep for the stride/offset pattern across the codebase to find all access sites
17
+ - Note the allocation size (from `wa_malloc`, `wa_malloc_zeroed`, or CRT malloc calls)
18
+ - Check Ghidra for vtable assignments, constructor calls, and inheritance (xrefs to the constructor show all callers and usage contexts)
19
+ - Determine: is this a new struct, or does it belong in an existing one (e.g., fields in DDGame, GameInfo)?
20
+
21
+ ### 2. Map the field layout
22
+
23
+ For each offset accessed in the code, record:
24
+
25
+ | Offset | Size | Type | Name | How used |
26
+ |--------|------|------|------|----------|
27
+
28
+ Compute offsets relative to the struct base. Verify with:
29
+ - Allocation size = total struct size
30
+ - `RET imm16` on the constructor confirms param count
31
+ - Ghidra decompilation of the constructor shows field initialization order
32
+ - Cross-reference with thirdparty RE sources (wkJellyWorm, WormKit) if available
33
+
34
+ ### 3. Create the Rust struct
35
+
36
+ - Place in the appropriate module (`engine/`, `frontend/`, `task/`, `audio/`, etc.)
37
+ - Use `#[repr(C)]` for ABI compatibility
38
+ - Add `const _: () = assert!(core::mem::size_of::<T>() == SIZE);` to verify total size
39
+ - Name known fields descriptively; use `_unknown_XXXX: [u8; N]` for gaps
40
+ - If the struct has a vtable, create a companion `FooVtable` struct with typed function pointers for known slots and `usize` for unknown slots
41
+ - Register the module in the parent `mod.rs` and re-export if appropriate
42
+
43
+ ### 4. Update variable and argument types
44
+
45
+ - Replace `u32` / `*mut u8` parameters with `*mut NewStruct` in function signatures
46
+ - This is ABI-compatible on i686 (all 32-bit pointers) so no calling convention changes needed
47
+ - Update both Rust functions AND FFI `extern` function pointer types (stdcall, thiscall, cdecl)
48
+ - For naked asm bridges, parameter types can be changed without affecting the assembly
49
+
50
+ ### 5. Replace pointer arithmetic with field access
51
+
52
+ - `*(base.add(0x1234) as *mut u32) = val` β†’ `(*ptr).field_name = val`
53
+ - `wb(CONST + idx * STRIDE, val)` β†’ `array[idx].field = val`
54
+ - For vtable calls: `*(vtable as *const u32).add(N)` β†’ `(*(*ptr).vtable).method`
55
+ - Use `vcall!` macro for one-liner vtable dispatch where appropriate
56
+
57
+ ### 6. Propagate types
58
+
59
+ - Update callers to pass typed pointers instead of `u32`
60
+ - Remove now-unnecessary `_OFF` address constants from `address.rs` (offsets are now struct fields)
61
+ - Rename address constants if the struct name changed (e.g., `MAP_CLASS_*` β†’ `MAP_VIEW_*`)
62
+ - Update Ghidra labels to match the new names
63
+
64
+ ### 7. Verify
65
+
66
+ - `cargo build --release -p openwa-wormkit` β€” must compile clean, zero warnings
67
+ - Run `/replay-test` (headful + headless) to verify runtime correctness
68
+ - Check that `sizeof` assertion passes (struct size matches allocation size)
69
+
70
+ ## Naming conventions
71
+
72
+ - Struct names: `PascalCase`, matching WA class names where known (e.g., `DDGame`, `CTaskWorm`, `MapView`)
73
+ - Unknown fields: `_unknown_XXXX` where XXXX is the hex offset
74
+ - Vtable structs: `FooVtable` companion to `Foo`
75
+ - Address constants: `STRUCT_NAME_FUNCTION` (e.g., `MAP_VIEW_CONSTRUCTOR`)
76
+ - Module placement: `engine/` for core game objects, `frontend/` for UI/CWnd-derived, `task/` for CTask hierarchy, `audio/` for sound, `display/` for graphics
77
+
78
+ ## When NOT to create a struct
79
+
80
+ - If offsets use inconsistent strides (e.g., player data with mixed 0x78/0x3C/0x1E strides) β€” these are interleaved arrays, not a single struct
81
+ - If the "struct" is actually a function's stack frame (local variables)
82
+ - If only 1-2 fields are known and the struct is huge β€” use raw offsets with named constants until more fields are discovered