# 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). ```powershell .\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: ```powershell build\x86-debug\sacred2K.exe --game-dir "" # 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`: ```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`): ```cpp constexpr re::Address kSomething{re::Module::S2Logic, 0x10123450}; ``` Exported functions (the DLLs export most of their classes) are hooked by decorated name instead: ```cpp 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.