Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 2 additions & 12 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,7 @@ Shared tooling can be modified to improve the tools themselves — just not for

## Delegation Rule

**Never run static analysis tools directly.** Delegate to a `static-analyzer` subagent. Only exceptions — run these inline:
- `sigdb.py identify` / `fingerprint` (single-function ID, <5s)
- `context.py assemble` / `postprocess` (context gathering, <5s; use `--no-dataflow` on large functions)
- `dataflow.py --constants` / `--slice` (single-function analysis, <5s)
- `readmem.py` (single typed read from PE, <5s)
- `asi_patcher.py build` (build step, not analysis)
- `pyghidra_backend.py status` (project existence check, <1s)
**Never run static analysis tools directly.** Delegate to a `static-analyzer` subagent. The only inline exceptions are the fast (<5s) commands in the "Run Directly" section of `.claude/rules/tool-dispatch.md` (auto-loaded below).

If you're about to run a second retools command in the same turn, you should have delegated.

Expand Down Expand Up @@ -90,11 +84,7 @@ Each file reads as if it was always designed this way. Comments guide the next d

## DX9 FFP Porting

When working on any of the following — invoke the **`dx9-ffp-port` skill** immediately before starting:
- Editing `renderer.cpp`, `ffp_state.cpp`, `remix-comp-proxy.ini`, or draw routing logic
- Porting a game for RTX Remix / fixed-function pipeline
- Diagnosing VS constant registers, vertex declarations, matrix mapping, skinning
- Building, deploying, or iterating on a remix-comp-proxy patch (`build.bat`, `diagnostics.log`, ImGui F4)
Invoke the **`dx9-ffp-port` skill** before editing `renderer.cpp`, `ffp_state.cpp`, `remix-comp-proxy.ini`, or draw routing; porting a game for RTX Remix; diagnosing VS constants, vertex declarations, matrix mapping, or skinning; or building/deploying a remix-comp-proxy patch.

---

Expand Down
2 changes: 1 addition & 1 deletion .claude/agents/static-analyzer.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ You are a reverse engineering analyst specializing in static analysis of PE bina

## Setup

On first invocation, read the full tool catalog at `.claude/rules/tool-catalog.md` in the working directory. It contains exact syntax, flags, and caveats for every tool.
On first invocation, read the full tool catalog at `.claude/references/tool-catalog.md` in the working directory. It contains exact syntax, flags, and caveats for every tool.

## Pre-flight Checks

Expand Down
27 changes: 3 additions & 24 deletions .claude/references/tool-catalog.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ These are fast (<5s) and allowed inline:
- "What constant flows into this register?" → `python -m retools.dataflow $B $VA --constants`
- "Trace where this value comes from" → `python -m retools.dataflow $B $VA --slice TARGET_VA:REG`
- "Build an ASI patch DLL" → `python -m retools.asi_patcher build spec.json`
- "Does a Ghidra project exist for this binary?" → `python retools/pyghidra_backend.py status $B --project $P`

### Delegate to `static-analyzer` subagent

Expand Down Expand Up @@ -58,6 +59,7 @@ Everything else. Tell the subagent WHAT you need, not HOW to run it — it has t
- "What are the actual register values?" → `livetools trace --read` or `bp` + `regs`
- "How many draw calls happen?" → `livetools dipcnt`
- "Who writes to this memory address?" → `livetools memwatch`
- "Send keys/clicks to the game window?" → `livetools gamectl`

### DX analysis scripts (main agent, fast first-pass)

Expand Down Expand Up @@ -159,30 +161,7 @@ These are fast first-pass scanners — they surface candidate addresses. Follow

## Dynamic Analysis (`livetools/`) -- Frida-based, attaches to running process

```
python -m livetools attach <process> # attach to running process by name or PID
python -m livetools attach "C:/Games/game.exe" --spawn # launch + instrument before init code runs
python -m livetools detach # end session
python -m livetools status # check connection
```

| Command | Purpose |
|---------|---------|
| `trace $VA` | Non-blocking: log N hits with register/memory reads |
| `steptrace $VA` | Instruction-level trace (Stalker) with call depth control |
| `collect $VA [$VA2...]` | Multi-address hit counting over duration |
| `bp add/del/list $VA` | Breakpoints (stops target) |
| `watch` | Wait for breakpoint hit |
| `regs` / `stack` / `bt` | Inspect registers, stack, backtrace at break |
| `mem read $VA $SIZE` | Read live process memory (supports --as float32) |
| `mem write $VA $HEX` | Write live process memory |
| `disasm [$VA]` | Disassemble from live process |
| `scan $PATTERN` | Search process memory for byte pattern |
| `modules` | List loaded modules with base addresses |
| `dipcnt on/off/read` | D3D9 DrawIndexedPrimitive call counter |
| `dipcnt callers [N]` | Sample N DIP calls and histogram return addresses |
| `memwatch start/stop/read` | Memory write watchpoint with backtrace |
| `analyze $FILE` | Offline analysis of collected .jsonl trace data |
Main-agent only (requires a live process; static-analyzer subagents must not use these). Canonical command reference with syntax, read-spec format, and recipes: the `/dynamic-analysis` skill (`.claude/skills/dynamic-analysis/SKILL.md`). Covers attach/spawn, breakpoints, trace/steptrace/collect, mem read/write/alloc, scan, disasm, modules, dipcnt, memwatch, vishook, gamectl, and offline `analyze`.

**NOTE**: Some processes require their window to be focused for traces to capture data.

Expand Down
33 changes: 10 additions & 23 deletions .claude/rules/tool-dispatch.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Run all tools from repo root via `python -m <module>`. **ALWAYS pass `--types pa
- `python -m retools.dataflow $B $VA --constants` — forward constant propagation
- `python -m retools.dataflow $B $VA --slice TARGET_VA:REG` — backward register slice
- `python -m retools.asi_patcher build spec.json` — build ASI patch DLL
- `python retools/pyghidra_backend.py status $B --project $P` — Ghidra project existence check

## Delegate to `static-analyzer`

Expand All @@ -29,36 +30,22 @@ Everything else in `retools`. Tell it WHAT you need, not HOW. D3D9-specific ques

## Live tools (main agent, attached process)

Full syntax and recipes: the `/dynamic-analysis` skill (canonical livetools reference).

- `livetools attach <name_or_pid>` — attach to running process
- `livetools attach <path> --spawn` — launch exe suspended, instrument, resume (catches init code)
- `livetools trace` / `collect` — hit logging, register reads
- `livetools trace` / `steptrace` / `collect` — hit logging, register reads, instruction traces
- `livetools bp` / `watch` / `regs` / `stack` / `bt` — breakpoints + inspection
- `livetools mem read/write` / `scan` — memory ops
- `livetools dipcnt` / `memwatch` — D3D9 counters, write watchpoints
- `livetools mem read/write/alloc` / `scan` — memory ops
- `livetools dipcnt` / `memwatch` — D3D9 draw counters, write watchpoints
- `livetools vishook` — selective visibility override via code cave
- `livetools gamectl` — send keys/clicks to game window (no focus steal)
- `livetools modules` — loaded module list
- `livetools analyze <jsonl>` — offline trace aggregation

## DX analysis scripts (main agent, fast first-pass)

Under `rtx_remix_tools/dx/scripts/`. Use BEFORE retools for D3D9 questions. Run as `python rtx_remix_tools/dx/scripts/<script> <args>`.

- `find_d3d_calls.py $B` — D3D9/D3DX imports + call sites
- `find_vs_constants.py $B` — SetVertexShaderConstantF sites with register/count
- `find_ps_constants.py $B` — SetPixelShaderConstantF/I/B sites with register/count
- `find_device_calls.py $B` — device vtable call patterns
- `find_vtable_calls.py $B` — D3DX CTAB + D3D9 vtable calls
- `find_render_states.py $B` — SetRenderState arguments with enum decoding
- `find_texture_ops.py $B` — texture pipeline: stages, TSS ops, sampler states
- `find_transforms.py $B` — SetTransform types (World, View, Projection, Texture)
- `find_surface_formats.py $B` — CreateTexture/RT/DS format extraction
- `find_stateblocks.py $B` — state block creation/recording/apply patterns
- `decode_fvf.py $B` — FVF bitfield decode from SetFVF calls
- `decode_vtx_decls.py $B --scan` — vertex declaration formats
- `find_shader_bytecode.py $B` — embedded shader bytecode extraction
- `classify_draws.py $B` — draw call classification (FFP/shader/hybrid)
- `find_matrix_registers.py $B` — identify View/Proj/World matrix registers (CTAB + frequency)
- `find_skinning.py $B` — consolidated skinning analysis (decls, bone palettes, blend states, INI suggestion)
- `find_blend_states.py $B` — D3DRS_VERTEXBLEND / INDEXEDVERTEXBLENDENABLE + WORLDMATRIX transforms
- `scan_d3d_region.py $B 0xSTART 0xEND` — D3D calls in code region
Targeted D3D9 scanners under `rtx_remix_tools/dx/scripts/` — use BEFORE retools for D3D9 questions (imports, VS/PS constants, render states, texture pipeline, transforms, FVF/vertex decls, draw classification, matrix registers, skinning, shader bytecode). Run as `python rtx_remix_tools/dx/scripts/<script> $B`. Full script table with examples: `.claude/references/tool-catalog.md`.

## dx9tracer

Expand Down
20 changes: 20 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"permissions": {
"allow": [
"Bash(git checkout:*)",
"Bash(git branch:*)",
"Bash(git rebase:*)",
"Bash(git add:*)",
"Bash(python verify_install.py:*)",
"Bash(python -m retools.sigdb:*)",
"Bash(python -m retools.context:*)",
"Bash(python -m retools.dataflow:*)",
"Bash(python -m retools.readmem:*)",
"Bash(python -m retools.asi_patcher:*)",
"Bash(python retools/pyghidra_backend.py status:*)",
"Bash(python -m livetools:*)",
"Bash(python -m graphics.directx.dx9.tracer:*)",
"Bash(python rtx_remix_tools/dx/scripts/:*)"
]
}
}
10 changes: 0 additions & 10 deletions .claude/settings.local.json

This file was deleted.

19 changes: 16 additions & 3 deletions .claude/skills/dx9-ffp-port/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: dx9-ffp-port
description: DX9 FFP Proxy -- Game Porting. TRIGGER when: user mentions porting a game for RTX Remix, working on renderer.cpp / ffp_state / remix-comp-proxy.ini / draw routing / VS constants / vertex declarations / matrix mapping / skinning, building or deploying a remix-comp-proxy patch, or diagnosing rendering issues in a patched game (white geometry, missing objects, wrong transforms, ImGui F4, diagnostics.log). Covers full workflow: static analysis, VS constant discovery, draw routing, INI config, build/deploy, pitfall diagnosis.
description: Use when porting a game for RTX Remix or to the fixed-function pipeline, working on renderer.cpp / ffp_state / remix-comp-proxy.ini / draw routing / VS constants / vertex declarations / matrix mapping / skinning, building or deploying a remix-comp-proxy patch, or diagnosing rendering issues in a patched game (white geometry, missing objects, wrong transforms, ImGui F4, diagnostics.log).
---

# DX9 FFP Proxy -- Game Porting
Expand Down Expand Up @@ -37,7 +37,7 @@ Each game folder under `patches/<GameName>/` is a self-contained remix-comp-prox
- Rigid 3D (has NORMAL) -> NULLs shaders, applies FFP transforms
4. Routes `DrawPrimitive` via `renderer::on_draw_primitive`: world-space (has decl, no POSITIONT, not skinned) -> FFP; otherwise pass-through
5. Applies captured matrices via `ffp_state::apply_transforms` -> `SetTransform`
6. Sets up texture stages and lighting for FFP rendering
6. Sets up texture stages and lighting for FFP rendering (stages 1-7 disabled to prevent stale auxiliary textures reaching Remix)
7. Loads the real d3d9 chain (RTX Remix `d3d9_remix.dll` or system d3d9) via d3d9_proxy

## Codebase File Map
Expand All @@ -60,7 +60,7 @@ Each game folder under `patches/<GameName>/` is a self-contained remix-comp-prox
| `src/comp/modules/imgui.cpp` | ImGui debug overlay (F4) with FFP tab |
| `src/comp/game/game.cpp` | Per-game address init (patterns, hooks) |
| `src/comp/game/game.hpp` | Per-game variables and function typedefs |
| `remix-comp-proxy.ini` | Runtime config: albedo stage, skinning toggle, diagnostics, DLL chain |
| `remix-comp-proxy.ini` (in `assets/`) | Runtime config: albedo stage, skinning toggle, diagnostics, DLL chain |
| `build.bat` | Build script: outputs d3d9.dll proxy. `build.bat [release\|debug] [--name Name]` |

**`rtx_remix_tools/dx/remix-comp-proxy/` is the TEMPLATE.** Each game gets a full copy under `patches/<GameName>/` — the entire folder is self-contained and can be distributed as a standalone repo. Edit `src/comp/` directly in the game's copy.
Expand Down Expand Up @@ -305,7 +305,20 @@ AND !ffp.cur_decl_has_pos_t() AND !ffp.cur_decl_is_skinned()?
- **Everything is white/black**: Albedo texture is on stage 1+, not stage 0. Set `AlbedoStage` in `remix-comp-proxy.ini`, or trace `SetTexture` calls to find the correct stage.
- **Some objects render, others don't**: Check whether missing geometry has NORMAL in its vertex decl. Check `ffp.view_proj_valid()` is true at draw time. DrawPrimitive routes on decl presence + no POSITIONT + not skinned.
- **Skinned meshes invisible**: Set `[Skinning] Enabled=1` in `remix-comp-proxy.ini`. Check log for skinning errors. Verify `bone_start_reg` and `num_bones` are non-zero in the log.
- **Bones mixed up between NPCs**: Stale WORLDMATRIX slots from a previous object. The game may need a game-specific reset hook at a per-object boundary -- see Skinning Stability below.
- **Game crashes on startup**: Set `[Remix] Enabled=0` in `remix-comp-proxy.ini` to test without Remix. Check `WINDOW_CLASS_NAME` in `comp/main.cpp`.
- **Geometry at origin / piled up**: World matrix register mapping wrong. Re-examine VS constant writes via `livetools trace` or DX9 tracer `--const-provenance`.
- **World geometry shifts after skinned draws**: `WORLDMATRIX(0)` clobbered by bone[0]. The proxy tracks `world_dirty_` for re-application. If still broken, check for bone register overlap with world matrix range in `ffp_state.hpp`.
- **ImGui overlay not appearing**: Press F4. Check that `WINDOW_CLASS_NAME` is correct and the window was found (console output). Check for DirectInput hook conflicts.

### Skinning Stability: Finding Game-Specific Hook Points

The proxy's generic heuristics handle most games. If bones still leak between objects, the game needs a hook at a per-object boundary function -- one that's called once per skinned object, before its bones are uploaded.

**Finding the per-object function:**

1. **Capture** 2+ frames with the D3D9 tracer while multiple skinned NPCs are on screen
2. **Hotpaths**: `--hotpaths --resolve-addrs <game.exe>` -- look at callers of bone-range `SetVertexShaderConstantF` writes
3. **Caller histogram**: `--callers SetVertexShaderConstantF` -- the function that appears N times per frame (N = number of skinned objects) is the per-object boundary
4. **Live confirm**: `livetools trace <candidate_addr> --count 50` -- with 3 NPCs, expect ~3 hits/frame
5. **Static context**: `callgraph.py --up` + `decompiler.py` on the caller -- confirm it loops over objects
Loading
Loading