diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..2151147 --- /dev/null +++ b/CLAUDE.md @@ -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/.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.