Files
khallaandClaude Opus 5.5 8a7bdfd99f Framework for Sacred 2 Gold, game log mirror, borderless fullscreen
Ported from sacredK with module-relative addresses, hooks by exported
name and a SHA-256 check of the six game modules (Community Patch
2.65.1). Borderless windowed at monitor size replaces exclusive
fullscreen, whose focus loss during loading made the game quit and
crash on exit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 01:39:44 +01:00

97 lines
4.9 KiB
Markdown

# 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\<preset>\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/<topic>.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:<level>]`).
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<Fn>`, 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/<module or subsystem>/` — 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 <module> "<regex>"`; most
classes of the DLLs are exported (`?method@Class@@...`).
- Decompile: `.\tools\decompile.ps1 <module> 0xADDR|symbol -ReadOnly`.
- Disassemble: `node tools/disasm.mjs <module> 0xVA [n]`, or
`objdump -d -M intel --start-address=.. --stop-address=.. <module>`.
- **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.