# CLAUDE.md Guidance for agents working in this repository. Read `README.md` (what the project is, build, run, layout), `docs/modules.md` (the game's modules and address convention) and `tools/README.md` (RE tools, Ghidra project) first; this file adds how we work and what we learned. ## What this is `sacred2K.dll` is injected into **Sacred 2 Gold** with Community Patch 2.65.1 (`system\sacred2.exe` plus `orcSystem`, `s2core`, `s2logic`, `s2logicai`, `s2render` DLLs; 2009, 32-bit, MSVC 8 + STLport) by the launcher `sacred2K.exe`. It hooks and patches the running game: functions are rewritten and mods change behaviour on top of the original. The game stays playable at every step; the user plays it to test. Sister project: sacredK (Sacred Gold, `../sacred_k`), whose framework and working rules this one follows. - Target files: the user's local installation (see `README.md`, Supported build). The game folder is given to the launcher once (`--game-dir`) and kept in `build\\sacred2K.ini`, `[paths] GameDir`. Never hard-code a local path in code, docs or configs. - Game texts are German (`locale\de_DE`); keep the game's names when talking about features. - The user's monitor is 3440x1440. ## 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, in player terms. 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 anything that takes over the desktop** (starting the game, autotests). - **Commit only what the user confirmed**, or when asked. End commit messages with the attribution line given by the harness. ## Principles - **Measure before imitating.** Log the original's runtime behaviour instead of guessing from decompiles. Temporary diagnostics: log lines start with `diag`, code is marked `TEMPORARY`, both 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** into shared headers or tables. - **Name as you go.** Exported functions are named already (decorated names; prefer hooking them with `RE_HOOK_EXPORT`). Name what we identify ourselves in `tools/names.txt` (`module 0xADDR Name`). No bare hex in logic: named `re::Address` constants with a comment saying where they come from. - **Data-driven over id lists.** Prefer the game's own data tables and scripts over hard-coded ids. - Document each mod in `docs/.md`: the request (with date), what was measured, addresses, how the mod works. `docs/todo.md` for deferred items. ## Build and run ```powershell .\build.ps1 # x86-debug -> build\x86-debug\ build\x86-debug\sacred2K.exe # start the game with the DLL ``` New source files must be added to `CMakeLists.txt`. Output to check: `build\x86-debug\sacred2K.log` (written anew each start: module check with running bases, hooks, the game's `Orc::Log` messages as `[game:]`). Crashes: `sacred2K_crash.txt` / `.dmp` next to the DLL (module+offset; the offset is from the running base, add the preferred base for Ghidra). ## Code map - `src/re/` — framework: `module.h` (`re::Module`, `re::Address{module, va}`, `GameFunction`, build check by SHA-256), `RE_HOOK(address, fn)` / `RE_HOOK_EXPORT(module, "?decorated@@", fn)` / `RE_HOOK_API` (Detours; define `fn_original`, null until installed), `RE_STARTUP(name, fn)`, patch helpers in `patch.h` (all take `re::Address` and verify the original bytes first), config, log, crash handler. - `src/game//` — the game's structures and functions: `orc/string.h` (`Orc::String`), `orc/log.cpp` (logger hook), `render/dx9base.h` (`cDX9Base`, `sInitParams`). - `src/mods/` — one file per mod; the top comment explains the request, the measured behaviour and the approach. `borderless.cpp`: always borderless windowed at monitor size (`docs/borderless.md`). ## Reverse-engineering workflow - Find by name first: `node tools/exports.mjs ""`; most classes of the DLLs are exported (`?method@Class@@...`). - Decompile: `.\tools\decompile.ps1 0xADDR|symbol -ReadOnly`. - Disassemble: `node tools/disasm.mjs 0xVA [n]`, or `objdump -d -M intel --start-address=.. --stop-address=.. `. - **Verify calling conventions before hooking or calling:** decorated names tell them (`QAE`/`UAE` = `__thiscall`, `QAA`/`UAA` = `__cdecl` with `this` on the stack, `SA` = static `__cdecl`, `SG` = static `__stdcall`); still check `ret N`. Use `__fastcall(self, edx, ...)` for `__thiscall`. - Absolute addresses inside code were relocated with their module: compare and patch them as `re::Address`, never as raw Ghidra VAs.