Files
sacred2_k/README.md
T
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

4.3 KiB

sacred2K

An incremental reimplementation of Sacred 2 Gold (Sacred 2: Fallen Angel + Ice & Blood, 2009) with Community Patch 2.65.1. Sister project of sacredK (Sacred Gold).

sacred2K.dll is injected into the original game at startup and replaces game functions with rewritten C++ one by one. Everything not yet rewritten keeps running as original machine code, so the game stays playable at every step and each rewrite can be tested in-game immediately.

No game assets or original code are included; you need your own installed copy of Sacred 2 Gold.

Supported build

Sacred 2 Gold (Steam, app 225640) with Community Patch 2.65.1. The game is split into an executable and several DLLs; hook addresses are specific to these files:

File (in system\) Size SHA-256
sacred2.exe 2,825,728 37b6d077ce9ff7c1b539e9e17db5351cb3a22c02827b36cc327b1f5fc0900090
orcSystem.dll 935,560 43b34c8c5a825f6f485f8cf8122054faff4075a33f44554901d8de167c7389b3
s2core.dll 1,406,088 2b9b8e05746f7a6f439746c3e26919a983ec794bae6bbcfe25f54cf0d05c13ec
s2logic.dll 3,754,120 9370aa81d3b1ca10f603a0758d2520bf42c20f8f5605966aab046fd236d5ed1b
s2logicai.dll 1,918,600 26b41eaa9e306ba9e9b51a1d33d011d6df7934e14bf21c289876516b393ebe6e
s2render.dll 3,711,624 09bd4975a2b01816f9a20db4a9365ee944611a78612e72a6abfae125c2723d75

The Community Patch kept the original PE timestamps, so the DLL hashes every module at startup and leaves any other build unpatched.

Building

Requirements: Visual Studio 2022 Build Tools ("Desktop development with C++", MSVC x86 + Windows SDK), CMake ≥ 3.24, Ninja, git (Detours is fetched at configure time).

.\build.ps1               # x86-debug   -> build\x86-debug\
.\build.ps1 x86-release   # x86-release -> build\x86-release\

Everything is 32-bit x86: the code runs inside the 32-bit game process.

Running

Tell the launcher the Sacred 2 Gold folder (the one containing system\) once:

build\x86-debug\sacred2K.exe --game-dir "<Sacred 2 Gold folder>"   # first run; remembered in sacred2K.ini
build\x86-debug\sacred2K.exe                                       # later runs; extra args go to the game

The folder is stored as [paths] GameDir in sacred2K.ini next to the launcher (one per build preset, not tracked by git). The game is started from its system folder, as Steam does.

sacred2K.log (next to the DLL) records the module check, what was hooked, and every message the game passes to its logger (Orc::Log).

Hooks can be switched off individually in sacred2K.ini:

[hooks]
LogMessage=0

Layout

src/re/         hook framework, module addresses, startup, logging, config, crash reports
src/launcher/   sacred2K.exe — starts sacred2.exe with sacred2K.dll injected
src/game/       rewritten game code and original-layout structs, by module/subsystem
src/mods/       behaviour changes on top of the original
docs/           reverse-engineering notes; function-map.md tracks progress
tools/          disassembler, export index, RTTI, Ghidra scripts

Addresses

The game's DLLs all prefer image base 0x10000000 and are relocated when loaded, so an address is always a module plus a VA at the module's preferred base, exactly as Ghidra shows it (docs/modules.md):

constexpr re::Address kSomething{re::Module::S2Logic, 0x10123450};

Exported functions (the DLLs export most of their classes) are hooked by decorated name instead:

namespace orc {
void __fastcall LogMessage(Log* self, void* edx, const String& message, int level);
RE_HOOK_EXPORT(re::Module::OrcSystem, "?logMessage@Log@Orc@@UAEXABVString@2@W4LogMessageLevel@2@@Z", LogMessage);
void __fastcall LogMessage(Log* self, void* edx, const String& message, int level) { ... }
}

The signature and calling convention must match the original exactly. Add the file to CMakeLists.txt and a row to docs/function-map.md.

Portability rules (for an eventual 64-bit build)

The game is 32-bit and must stay so while we hook into it, but new code should not make that permanent:

  • use fixed-width types (uint32_t, not long/DWORD) for anything with a fixed layout;
  • never store pointers in, or cast pointers to, 32-bit integers;
  • keep file and network formats behind explicit serialization code instead of fwrite-ing structs.