Add CLAUDE.md: project guide for agents
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
16c2844400
commit
b2e886c7f5
1 file changed
+146
@@ -0,0 +1,146 @@
|
||||
# CLAUDE.md
|
||||
|
||||
Guidance for agents working in this repository. Read `README.md` (what the
|
||||
project is, build, run, layout) and `tools/README.md` (RE tools, Ghidra
|
||||
project, autotest) first; this file adds how we work and what we learned.
|
||||
|
||||
## What this is
|
||||
|
||||
`sacred_re.dll` is injected into the original **Sacred Gold 2.28**
|
||||
`sacred.exe` (2006, 32-bit, MSVC 6) by the launcher `sacred_re.exe`. It
|
||||
hooks and patches the running game: some functions are rewritten, most of
|
||||
the work so far is **mods** — behaviour changes on top of the original
|
||||
(widescreen, HUD layout, 7 weapon sets / art slots, key bindings, WASD,
|
||||
auto-attack, skill reworks, respec, map fixes, ...). The game stays playable
|
||||
at every step; the user plays it to test.
|
||||
|
||||
- Target binary: the original `sacred.exe` in `D:\Gaming\Sacred Gold\`
|
||||
(path in `build\x86-debug\sacred_re.ini`, `[paths] GameDir`). Ignore the
|
||||
`SacredReborn*` files in that folder.
|
||||
- Game texts are German (Fertigkeiten, Attribute, Blitzschlag, ...); keep the
|
||||
game's names when talking about features.
|
||||
- The user's monitor is 3440x1440; the game renders about 1835x768 scaled up.
|
||||
Widescreen goal: same on-screen size as the original, view extended
|
||||
horizontally only. Only 16:9 and wider are supported (no 4:3).
|
||||
|
||||
## How the user works (important)
|
||||
|
||||
- **The user tests in-game** and reports back ("1: works, 2: ..."). After a
|
||||
change, say exactly what to test, numbered. Don't claim something works
|
||||
until the user (or a test you ran) confirmed it.
|
||||
- **Their game start builds the DLL.** A link error `LNK1168` (DLL locked)
|
||||
means the game is running; that's expected, not a failure to fix.
|
||||
- **Ask before running the autotest** — it takes over mouse, keyboard and
|
||||
screen. Keep tests fast (log markers, not sleeps). Save row 2 (`-Save 2`)
|
||||
is the battlemage, the usual test character.
|
||||
- **Commit only what the user confirmed**, or when asked. End commit
|
||||
messages with the attribution line given by the harness.
|
||||
- Feature requests often arrive mid-task as change-request lists; note them,
|
||||
finish the piece in hand, then take them in order.
|
||||
|
||||
## Principles
|
||||
|
||||
- **Measure before imitating.** Log the original's runtime behaviour (orders,
|
||||
fields, distances, draw calls) instead of guessing from decompiles. Many
|
||||
wrong turns in this project came from trusting a decompile; most fixes came
|
||||
from a one-off log. Temporary diagnostics: log lines start with `diag`,
|
||||
code is marked `TEMPORARY`, and both are removed before committing.
|
||||
- **No vanilla preservation.** Once a mod works, delete the old path, toggles
|
||||
and fallbacks. Keep only migrations (e.g. old save formats).
|
||||
- **Refactor duplicates while writing.** Repeated operations go into shared
|
||||
headers (`src/game/engine/*.h`, `src/game/ui/*.h`) or tables
|
||||
(`re::PatchAll`, `re::ReplaceAll`), not copies.
|
||||
- **Name as you go.** Functions, globals (`g_`), fields and constants get
|
||||
readable names when identified; add function names to `tools/names.txt`
|
||||
(applied to Ghidra with `tools/rename.ps1`). No bare hex in logic: named
|
||||
constants with a comment saying where they come from.
|
||||
- **Data-driven over id lists.** Prefer the game's own data (art definition
|
||||
tables, weapon class, launch type, the game's own behaviour) over per-art
|
||||
id constants. Hard-coded art ids don't scale.
|
||||
- Document each mod in `docs/<topic>.md`: the request (with date), what was
|
||||
measured, addresses, how the mod works. Keep `docs/todo.md` for noted but
|
||||
deferred requests.
|
||||
|
||||
## Build and run
|
||||
|
||||
```powershell
|
||||
.\build.ps1 # x86-debug -> build\x86-debug\
|
||||
build\x86-debug\sacred_re.exe log # start the game with the DLL
|
||||
```
|
||||
|
||||
New source files must be added to `CMakeLists.txt`. Output to check:
|
||||
`build\x86-debug\sacred_re.log` (written anew each start; our lines plus the
|
||||
game's own log). Crashes: `sacred_re_crash.txt` / `.dmp` next to the DLL
|
||||
(exception, module+offset, ebp chain); Windows also records the faulting
|
||||
offset in the Application event log (`Get-WinEvent`, provider "Application
|
||||
Error"). The debug build's run-time checks turn calling-convention mistakes
|
||||
into a breakpoint exception (0x80000003, "ESP was not properly saved").
|
||||
|
||||
## Code map
|
||||
|
||||
- `src/re/` — framework: `RE_HOOK(address, fn)` (Detours; defines
|
||||
`fn_original`), `RE_HOOK_API`, `RE_STARTUP(name, fn)` (runs after hooks are
|
||||
installed), patch helpers in `patch.h` (`PatchInstructionDword`,
|
||||
`PatchAll`, `RedirectCall`, `RedirectJcc`, `RedirectOperand`,
|
||||
`ReplaceCode`/`ReplaceAll`; all verify the original bytes first), config,
|
||||
log, crash handler.
|
||||
- `src/game/` — the game's structures and functions by subsystem, as inline
|
||||
wrappers with the address and what is known:
|
||||
`engine/orders.h` (creature orders, `OnCreatureOrder`, creature fields and
|
||||
actions, `IsHostileTo`, `DistanceTo`, `ContactDistance`, `WeaponClass`,
|
||||
`NearestHostile`), `engine/arts.h` (art definitions, cooldowns, launch
|
||||
types), `engine/objects.h` (object manager, nearby creatures, player hero),
|
||||
`engine/fx.h` (FX objects and events), `engine/events.h` (engine tick, save
|
||||
loaded), `ui/` (UI rects, fonts, events), `addresses.h` (shared tables).
|
||||
- `src/mods/` — one file per mod; the top comment explains the request, the
|
||||
measured behaviour and the approach. `hud.cpp` is the HUD layer: 1024-wide
|
||||
windows are drawn shifted (`Anchor::Center/Right`, or `Screen` for windows
|
||||
laid out across the full width), mouse events and the cursor shifted to
|
||||
match; other mods use `hud::RenderCentred` / `HandleCentred` /
|
||||
`DrawOffsetX` instead of hooking the same function again.
|
||||
- `docs/` — notes per topic; `function-map.md` lists known functions.
|
||||
|
||||
## Reverse-engineering workflow
|
||||
|
||||
- Decompile: `.\tools\decompile.ps1 0xADDR [...] -Out ghidra\x.c -ReadOnly`
|
||||
(persistent Ghidra project in `ghidra/`; large functions can time out —
|
||||
fall back to disassembly).
|
||||
- Disassemble: `objdump -d --no-show-raw-insn --start-address=.. --stop-address=..`
|
||||
on `sacred.exe` (Git Bash has mingw objdump), or `tools/disasm.mjs`.
|
||||
A full `.text` dump grepped for immediates/calls is often the fastest way to
|
||||
find all users of a constant or function; `tools/find-imm.mjs` does the same.
|
||||
- File offset ↔ VA: `.text` VA = offset + 0x400000; `.rdata` 0x88e000 ↔
|
||||
0x48e000; `.data` 0x8e5000 ↔ 0x4e5000. Vtables via RTTI
|
||||
(`tools/rtti.mjs`, `Class::vfNN` names in Ghidra).
|
||||
- **Verify calling conventions before hooking or calling:** check the
|
||||
function's `ret N` (callee-popped bytes) and every argument, including
|
||||
structs passed by value. A wrong count corrupts the caller's stack. Use
|
||||
`__fastcall(self, edx, ...)` as the stand-in for `__thiscall`.
|
||||
- Register-passing call sites are handled with naked thunks
|
||||
(`__declspec(naked)`, push the registers, call C++, `ret N`) installed with
|
||||
`RedirectCall`/`RedirectJcc`.
|
||||
|
||||
## Pitfalls learned
|
||||
|
||||
- Two `RE_HOOK`s on the same address don't stack; extend the existing hook
|
||||
(or expose a helper from the mod that owns it).
|
||||
- UI windows are laid out for 1024x768 and render even while hidden; check a
|
||||
control's visible flag (+0x10 bit 0) before drawing extras.
|
||||
- The hero's attributes/skills (cCreatureArts, hero +0x3a8) are mirrored
|
||||
xor-ed into a protected page (0x182cddc); change them only through the
|
||||
game's own functions (they refresh the copy), never by writing fields.
|
||||
- `GetKeyState` is per-thread message state: Alt reads as held after Alt+Tab
|
||||
until pressed again in the game. Don't gate key handling on it.
|
||||
- Hard-coded 1024/768, 512/384, 0x3ff/0x2ff constants appear throughout the
|
||||
UI code; widening a window means finding all of them (see `docs/hud.md`,
|
||||
`docs/widescreen.md`). Shared drawing routines (e.g. the map's zoomed view)
|
||||
may serve windows with different offsets.
|
||||
- The game's log messages (`[game] ...` in our log) are useful landmarks,
|
||||
e.g. `cEngine End and exit constructor` marks a finished save-game load.
|
||||
|
||||
## Memory
|
||||
|
||||
Project memory lives in
|
||||
`C:\Users\torha\.claude\projects\D--Projekte-sacred-decompile\memory\`
|
||||
(`project-status.md`: what is done and confirmed, what is open). Update it
|
||||
after commits and before long pauses.
|
||||
Reference in new issue
Block a user