docs: add FFI style guidelines to CLAUDE.md
Browse filesCo-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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`).
|