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>
97 lines
4.9 KiB
Markdown
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.
|