paavohtl Claude Opus 4.6 commited on
Commit
b036aa9
·
1 Parent(s): c09eb2f

docs: add FFI style guidelines to CLAUDE.md

Browse files

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

Files changed (1) hide show
  1. CLAUDE.md +12 -0
CLAUDE.md CHANGED
@@ -132,3 +132,15 @@ A Ghidra MCP bridge is configured in `.mcp.json`. When using Ghidra tools:
132
  - Unknown struct fields as `_unknown_XX` padding arrays
133
  - Fixed-point: `Fixed(i32)` newtype, 16.16 format (0x10000 = 1.0)
134
  - Naked asm uses `naked_asm!` (Rust 1.79+ syntax), not `asm!`
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  - Unknown struct fields as `_unknown_XX` padding arrays
133
  - Fixed-point: `Fixed(i32)` newtype, 16.16 format (0x10000 = 1.0)
134
  - Naked asm uses `naked_asm!` (Rust 1.79+ syntax), not `asm!`
135
+
136
+ ## FFI Style
137
+
138
+ Add type safety incrementally where it's beneficial — this is a reverse engineering project, not a greenfield codebase. Perfect types aren't always possible, but small improvements compound.
139
+
140
+ - **Wrapper structs over raw values**: Create `#[repr(C)]` structs for known memory layouts. Access fields by name, not pointer arithmetic. Even partially-known structs (with `_unknown_XX` padding) are better than raw offsets.
141
+ - **Handle newtypes for opaque pointers**: When a pointer's target layout is unknown, wrap it in a newtype (e.g., `WavPlayerHandle(u32)`, `CWndHandle(u32)`) with methods that encapsulate the unsafe calls. This keeps inline asm and raw pointer work out of hook logic.
142
+ - **Typed pointers over integers**: Prefer `*mut DDGame` over `u32` for pointer parameters. Use `*const c_char` for C string pointers, not `*const u8`.
143
+ - **Constants over magic numbers**: Name addresses (`va::FESFX_WAV_PLAYER`), sizes (`MAX_PATH`), and offsets. Magic numbers in code should be rare and commented.
144
+ - **Wrap inline asm in safe-to-call functions**: Isolate `asm!` / `naked_asm!` blocks in small dedicated functions (e.g., `get_team_config_name()`, `wav_player_stop_raw()`). Hook functions should read like normal Rust, calling into asm wrappers only when needed.
145
+ - **ESI/EDI are LLVM-reserved on x86**: Cannot use `in("esi")` or `in("edi")` in `core::arch::asm!`. Use `#[unsafe(naked)]` functions with `naked_asm!` when these registers are needed.
146
+ - **`heapless::CString<N>`** for stack-allocated null-terminated path buffers (auto nul terminator, `as_ptr()` returns `*const c_char`).