Files
sacred_k/CLAUDE.md
T
khallaandClaude Opus 5.5 70673dc301 Rename project to sacredK
sacred_re.dll/.exe/.ini/.log/_crash files are now sacredK.*; inventory saves
with the old "sacred_re.inventory" format id still load.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 22:07:14 +02:00

147 lines
7.9 KiB
Markdown

# 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
`sacredK.dll` is injected into the original **Sacred Gold 2.28**
`sacred.exe` (2006, 32-bit, MSVC 6) by the launcher `sacredK.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\sacredK.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\sacredK.exe log # start the game with the DLL
```
New source files must be added to `CMakeLists.txt`. Output to check:
`build\x86-debug\sacredK.log` (written anew each start; our lines plus the
game's own log). Crashes: `sacredK_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.