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>
4.9 KiB
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 inbuild\<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 markedTEMPORARY, 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 intools/names.txt(module 0xADDR Name). No bare hex in logic: namedre::Addressconstants 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.mdfor deferred items.
Build and run
.\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; definefn_original, null until installed),RE_STARTUP(name, fn), patch helpers inpatch.h(all takere::Addressand 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], orobjdump -d -M intel --start-address=.. --stop-address=.. <module>. - Verify calling conventions before hooking or calling: decorated names
tell them (
QAE/UAE=__thiscall,QAA/UAA=__cdeclwiththison the stack,SA= static__cdecl,SG= static__stdcall); still checkret 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.