Rename project to sacredK

sacred_re.dll/.exe/.ini/.log/_crash files are now sacredK.*; inventory saves
with the old "sacred_re.inventory" format id still load.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
khallaandClaude Opus 5.5 committed 2026-10-19 22:07:14 +02:00
1 parent 462ebf8c0b
commit 70673dc301
34 files changed
+95 -93

No files matched your search

+1 -1
View File
@@ -3,6 +3,6 @@
/.idea/ /.idea/
/cmake-build-*/ /cmake-build-*/
*.rea-snapshot *.rea-snapshot
sacred_re.ini sacredK.ini
/ghidra/ /ghidra/
/screenshots/ /screenshots/
+3 -3
View File
@@ -3,10 +3,10 @@
"configurations": [ "configurations": [
{ {
// Builds, then starts the game through the launcher (no debugger in the game). // Builds, then starts the game through the launcher (no debugger in the game).
"name": "Run Sacred (sacred_re)", "name": "Run Sacred (sacredK)",
"type": "cppvsdbg", "type": "cppvsdbg",
"request": "launch", "request": "launch",
"program": "${workspaceFolder}/build/x86-debug/sacred_re.exe", "program": "${workspaceFolder}/build/x86-debug/sacredK.exe",
"args": ["--game-dir", "D:\\Gaming\\Sacred Gold", "log"], "args": ["--game-dir", "D:\\Gaming\\Sacred Gold", "log"],
"cwd": "${workspaceFolder}/build/x86-debug", "cwd": "${workspaceFolder}/build/x86-debug",
"preLaunchTask": "CMake: build", "preLaunchTask": "CMake: build",
@@ -14,7 +14,7 @@
}, },
{ {
// Debug our DLL inside the game: set [debug] WaitForDebugger=1 in // Debug our DLL inside the game: set [debug] WaitForDebugger=1 in
// build/<preset>/sacred_re.ini, start "Run Sacred", then run this and // build/<preset>/sacredK.ini, start "Run Sacred", then run this and
// pick sacred.exe. The DLL waits (up to 60 s) before installing hooks. // pick sacred.exe. The DLL waits (up to 60 s) before installing hooks.
"name": "Attach to sacred.exe", "name": "Attach to sacred.exe",
"type": "cppvsdbg", "type": "cppvsdbg",
+6 -6
View File
@@ -6,8 +6,8 @@ project, autotest) first; this file adds how we work and what we learned.
## What this is ## What this is
`sacred_re.dll` is injected into the original **Sacred Gold 2.28** `sacredK.dll` is injected into the original **Sacred Gold 2.28**
`sacred.exe` (2006, 32-bit, MSVC 6) by the launcher `sacred_re.exe`. It `sacred.exe` (2006, 32-bit, MSVC 6) by the launcher `sacredK.exe`. It
hooks and patches the running game: some functions are rewritten, most of hooks and patches the running game: some functions are rewritten, most of
the work so far is **mods** — behaviour changes on top of the original the work so far is **mods** — behaviour changes on top of the original
(widescreen, HUD layout, 7 weapon sets / art slots, key bindings, WASD, (widescreen, HUD layout, 7 weapon sets / art slots, key bindings, WASD,
@@ -15,7 +15,7 @@ auto-attack, skill reworks, respec, map fixes, ...). The game stays playable
at every step; the user plays it to test. at every step; the user plays it to test.
- Target binary: the original `sacred.exe` in `D:\Gaming\Sacred Gold\` - Target binary: the original `sacred.exe` in `D:\Gaming\Sacred Gold\`
(path in `build\x86-debug\sacred_re.ini`, `[paths] GameDir`). Ignore the (path in `build\x86-debug\sacredK.ini`, `[paths] GameDir`). Ignore the
`SacredReborn*` files in that folder. `SacredReborn*` files in that folder.
- Game texts are German (Fertigkeiten, Attribute, Blitzschlag, ...); keep the - Game texts are German (Fertigkeiten, Attribute, Blitzschlag, ...); keep the
game's names when talking about features. game's names when talking about features.
@@ -65,12 +65,12 @@ at every step; the user plays it to test.
```powershell ```powershell
.\build.ps1 # x86-debug -> build\x86-debug\ .\build.ps1 # x86-debug -> build\x86-debug\
build\x86-debug\sacred_re.exe log # start the game with the DLL build\x86-debug\sacredK.exe log # start the game with the DLL
``` ```
New source files must be added to `CMakeLists.txt`. Output to check: New source files must be added to `CMakeLists.txt`. Output to check:
`build\x86-debug\sacred_re.log` (written anew each start; our lines plus the `build\x86-debug\sacredK.log` (written anew each start; our lines plus the
game's own log). Crashes: `sacred_re_crash.txt` / `.dmp` next to the DLL game's own log). Crashes: `sacredK_crash.txt` / `.dmp` next to the DLL
(exception, module+offset, ebp chain); Windows also records the faulting (exception, module+offset, ebp chain); Windows also records the faulting
offset in the Application event log (`Get-WinEvent`, provider "Application offset in the Application event log (`Get-WinEvent`, provider "Application
Error"). The debug build's run-time checks turn calling-convention mistakes Error"). The debug build's run-time checks turn calling-convention mistakes
+16 -16
View File
@@ -1,12 +1,12 @@
cmake_minimum_required(VERSION 3.24) cmake_minimum_required(VERSION 3.24)
project(sacred_re LANGUAGES C CXX) project(sacredK LANGUAGES C CXX)
# sacred.exe is a 32-bit process; everything we inject into it must be 32-bit too. # sacred.exe is a 32-bit process; everything we inject into it must be 32-bit too.
if(NOT CMAKE_SIZEOF_VOID_P EQUAL 4) if(NOT CMAKE_SIZEOF_VOID_P EQUAL 4)
message(FATAL_ERROR "sacred_re must be built for 32-bit x86 (use the 'x86-*' presets / an x86 MSVC environment).") message(FATAL_ERROR "sacredK must be built for 32-bit x86 (use the 'x86-*' presets / an x86 MSVC environment).")
endif() endif()
if(NOT MSVC) if(NOT MSVC)
message(FATAL_ERROR "sacred_re requires MSVC (the game's C++ ABI is MSVC's).") message(FATAL_ERROR "sacredK requires MSVC (the game's C++ ABI is MSVC's).")
endif() endif()
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
@@ -19,17 +19,17 @@ add_compile_definitions(WIN32_LEAN_AND_MEAN NOMINMAX _CRT_SECURE_NO_WARNINGS)
include(cmake/Detours.cmake) include(cmake/Detours.cmake)
include(cmake/Json.cmake) include(cmake/Json.cmake)
set(SACRED_RE_CXX_FLAGS /W4 /permissive- /utf-8) set(SACREDK_CXX_FLAGS /W4 /permissive- /utf-8)
# --- sacred_re.dll: injected into sacred.exe, installs the hooks ------------- # --- sacredK.dll: injected into sacred.exe, installs the hooks -------------
add_library(sacred_re SHARED add_library(sacredK SHARED
src/re/dllmain.cpp src/re/dllmain.cpp
src/re/hook.cpp src/re/hook.cpp
src/re/log.cpp src/re/log.cpp
src/re/config.cpp src/re/config.cpp
src/re/patch.cpp src/re/patch.cpp
src/re/crash.cpp src/re/crash.cpp
src/re/sacred_re.def src/re/sacredK.def
src/game/engine/log.cpp src/game/engine/log.cpp
src/game/engine/cmdline.cpp src/game/engine/cmdline.cpp
src/game/engine/events.cpp src/game/engine/events.cpp
@@ -70,13 +70,13 @@ add_library(sacred_re SHARED
src/mods/charexport.cpp src/mods/charexport.cpp
src/mods/options.cpp src/mods/options.cpp
) )
target_include_directories(sacred_re PRIVATE src) target_include_directories(sacredK PRIVATE src)
target_compile_options(sacred_re PRIVATE ${SACRED_RE_CXX_FLAGS}) target_compile_options(sacredK PRIVATE ${SACREDK_CXX_FLAGS})
target_link_libraries(sacred_re PRIVATE detours nlohmann_json::nlohmann_json dbghelp) target_link_libraries(sacredK PRIVATE detours nlohmann_json::nlohmann_json dbghelp)
# --- sacred_re.exe: launches sacred.exe with sacred_re.dll injected ---------- # --- sacredK.exe: launches sacred.exe with sacredK.dll injected ----------
add_executable(sacred_re_launcher WIN32 src/launcher/main.cpp) add_executable(sacredK_launcher WIN32 src/launcher/main.cpp)
set_target_properties(sacred_re_launcher PROPERTIES OUTPUT_NAME sacred_re) set_target_properties(sacredK_launcher PROPERTIES OUTPUT_NAME sacredK)
target_compile_options(sacred_re_launcher PRIVATE ${SACRED_RE_CXX_FLAGS}) target_compile_options(sacredK_launcher PRIVATE ${SACREDK_CXX_FLAGS})
target_link_libraries(sacred_re_launcher PRIVATE detours) target_link_libraries(sacredK_launcher PRIVATE detours)
add_dependencies(sacred_re_launcher sacred_re) add_dependencies(sacredK_launcher sacredK)
+7 -7
View File
@@ -1,8 +1,8 @@
# sacred_re # sacredK
An incremental reimplementation of **Sacred Gold 2.28** (`sacred.exe`, 2006). An incremental reimplementation of **Sacred Gold 2.28** (`sacred.exe`, 2006).
`sacred_re.dll` is injected into the original game at startup and replaces game `sacredK.dll` is injected into the original game at startup and replaces game
functions with rewritten C++ one by one. Everything not yet rewritten keeps 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 running as original machine code, so the game stays playable at every step and
each rewrite can be tested in-game immediately. Once enough is rewritten, the each rewrite can be tested in-game immediately. Once enough is rewritten, the
@@ -42,14 +42,14 @@ the CMake presets.
## Running ## Running
```powershell ```powershell
build\x86-debug\sacred_re.exe --game-dir "D:\Gaming\Sacred Gold" # first run; remembered in sacred_re.ini build\x86-debug\sacredK.exe --game-dir "D:\Gaming\Sacred Gold" # first run; remembered in sacredK.ini
build\x86-debug\sacred_re.exe log # later runs; extra args go to the game build\x86-debug\sacredK.exe log # later runs; extra args go to the game
``` ```
`sacred_re.log` (next to the DLL) records what was hooked plus every message the `sacredK.log` (next to the DLL) records what was hooked plus every message the
game sends to its internal logger, even when the game's own logging is off. game sends to its internal logger, even when the game's own logging is off.
Hooks can be switched off individually in `sacred_re.ini` (next to the DLL) Hooks can be switched off individually in `sacredK.ini` (next to the DLL)
to compare a rewrite against the original: to compare a rewrite against the original:
```ini ```ini
@@ -61,7 +61,7 @@ CmdLineHasFlag=0
``` ```
src/re/ hook framework, injection-side startup, logging, config src/re/ hook framework, injection-side startup, logging, config
src/launcher/ sacred_re.exe — starts sacred.exe with the DLL injected src/launcher/ sacredK.exe — starts sacred.exe with the DLL injected
src/game/ rewritten game code and original-layout structs, by subsystem src/game/ rewritten game code and original-layout structs, by subsystem
src/mods/ behaviour changes on top of the original (e.g. borderless.cpp) src/mods/ behaviour changes on top of the original (e.g. borderless.cpp)
docs/ reverse-engineering notes; function-map.md tracks progress docs/ reverse-engineering notes; function-map.md tracks progress
+2 -2
View File
@@ -3,7 +3,7 @@
Request (2026-10-18): the settings screen gets three tabs: *Allgemein*, Request (2026-10-18): the settings screen gets three tabs: *Allgemein*,
*Beute* (loot filter; first iteration: the game's loot options moved there *Beute* (loot filter; first iteration: the game's loot options moved there
from the common page) and *Tastenbelegung* (key bindings, so far only from the common page) and *Tastenbelegung* (key bindings, so far only
settable in `sacred_re.ini`). Code: `src/mods/options.cpp`; bindings in settable in `sacredK.ini`). Code: `src/mods/options.cpp`; bindings in
`src/mods/input.cpp`. `src/mods/input.cpp`.
## The window (cUI_Options) ## The window (cUI_Options)
@@ -42,7 +42,7 @@ settable in `sacred_re.ini`). Code: `src/mods/options.cpp`; bindings in
a box captures the next key press (`input::SetKeyCapture`, swallowed before a box captures the next key press (`input::SetKeyCapture`, swallowed before
the game sees it): Esc cancels, Backspace unbinds, a bindable key is taken the game sees it): Esc cancels, Backspace unbinds, a bindable key is taken
(and removed from any other action). The page works on a copy loaded when (and removed from any other action). The page works on a copy loaded when
the window opens; OK (`0x717460`) saves it to `[keys]` in `sacred_re.ini`, the window opens; OK (`0x717460`) saves it to `[keys]` in `sacredK.ini`,
Cancel drops it. Cancel drops it.
- Movement keys (W/A/S/D) are bindings now (`MoveUp/Left/Down/Right`); - Movement keys (W/A/S/D) are bindings now (`MoveUp/Left/Down/Right`);
`mods/wasd.cpp` polls the bound keys. `mods/wasd.cpp` polls the bound keys.
+1 -1
View File
@@ -55,7 +55,7 @@ was empty after loading another character (in-game saves don't call saveHero).
the chest drew at the wrong spot after some loads. Both tabs' grids are put the chest drew at the wrong spot after some loads. Both tabs' grids are put
back to the constructor's values every tick and on each tab switch. back to the constructor's values every tick and on each tab switch.
- `MoveItem` from/to 0x11 while the shared tab is open moves from/to it. - `MoveItem` from/to 0x11 while the shared tab is open moves from/to it.
- File: `{"format": "sacred_re.shared_chest", "version": 1, "items": [{cell, - File: `{"format": "sacredK.shared_chest", "version": 1, "items": [{cell,
w, h, type, data (hex of vf1c), count}]}`; written after each successful w, h, type, data (hex of vf1c), count}]}`; written after each successful
`saveHero` and after each save-game write (in-game saves don't call saveHero; `saveHero` and after each save-game write (in-game saves don't call saveHero;
via a temp file), read on the first engine tick with a hero after a session via a temp file), read on the first engine tick with a hero after a session
+2 -2
View File
@@ -93,7 +93,7 @@ mouse/keyboard like `tools/autotest-load.ps1`:
2. play scripted input through the existing hooks (keys, casting arts, weapon 2. play scripted input through the existing hooks (keys, casting arts, weapon
set switches, save + reload); set switches, save + reload);
3. take back-buffer screenshots at checkpoints and log checks to 3. take back-buffer screenshots at checkpoints and log checks to
`sacred_re.log` (aura effect present after reload, slot 6-7 inventories `sacredK.log` (aura effect present after reload, slot 6-7 inventories
survive a save, ...); survive a save, ...);
4. exit the game. 4. exit the game.
@@ -116,7 +116,7 @@ W, A and S were game hotkeys. With WASD movement (`src/mods/wasd.cpp`) their
actions moved to other keys for now; choose final bindings (and update the actions moved to other keys for now; choose final bindings (and update the
in-game texts/tooltips that name the old keys): in-game texts/tooltips that name the old keys):
| Key | Action | Temporary key (sacred_re.ini) | | Key | Action | Temporary key (sacredK.ini) |
|---|---|---| |---|---|---|
| W | potion kind 3 (`cEngine::UseActionSlot`, like Q/E/R/B/Space) | game potion keys switched off; `[keys] Potion1-5` (docs/options.md) | | W | potion kind 3 (`cEngine::UseActionSlot`, like Q/E/R/B/Space) | game potion keys switched off; `[keys] Potion1-5` (docs/options.md) |
| A | pick up items nearby (creature event 0x113) | Q (`[keys] PickUp`, 2026-10-17; was V) | | A | pick up items nearby (creature event 0x113) | Q (`[keys] PickUp`, 2026-10-17; was V) |
+1 -1
View File
@@ -7,7 +7,7 @@
// optional callback (0x017e5ee4) and to either OutputDebugString or the // optional callback (0x017e5ee4) and to either OutputDebugString or the
// in-game console object (0x017e5edc). // in-game console object (0x017e5edc).
// //
// Status: hooked, not rewritten. Every message is mirrored into sacred_re.log // Status: hooked, not rewritten. Every message is mirrored into sacredK.log
// whether or not the game's own logging is enabled, then forwarded. // whether or not the game's own logging is enabled, then forwarded.
#include "game/engine/log.h" #include "game/engine/log.h"
+9 -9
View File
@@ -1,11 +1,11 @@
// sacred_re.exe: starts sacred.exe with sacred_re.dll injected. // sacredK.exe: starts sacred.exe with sacredK.dll injected.
// //
// sacred_re.exe [--game-dir <dir>] [game arguments...] // sacredK.exe [--game-dir <dir>] [game arguments...]
// //
// The game directory comes from --game-dir (which is then remembered in // The game directory comes from --game-dir (which is then remembered in
// sacred_re.ini), else from [paths] GameDir in sacred_re.ini, else the // sacredK.ini), else from [paths] GameDir in sacredK.ini, else the
// launcher's own directory. Remaining arguments are passed to the game // launcher's own directory. Remaining arguments are passed to the game
// unchanged, e.g. `sacred_re.exe log` enables the game's DEBUG.LOG. // unchanged, e.g. `sacredK.exe log` enables the game's DEBUG.LOG.
#include <string> #include <string>
@@ -35,7 +35,7 @@ std::wstring WithTrailingSlash(std::wstring dir) {
} }
int Fail(const std::wstring& message) { int Fail(const std::wstring& message) {
MessageBoxW(nullptr, message.c_str(), L"sacred_re", MB_OK | MB_ICONERROR); MessageBoxW(nullptr, message.c_str(), L"sacredK", MB_OK | MB_ICONERROR);
return 1; return 1;
} }
@@ -43,7 +43,7 @@ int Fail(const std::wstring& message) {
int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) { int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) {
const std::wstring launcherDir = ExeDir(); const std::wstring launcherDir = ExeDir();
const std::wstring ini = launcherDir + L"sacred_re.ini"; const std::wstring ini = launcherDir + L"sacredK.ini";
int argc = 0; int argc = 0;
wchar_t** argv = CommandLineToArgvW(GetCommandLineW(), &argc); wchar_t** argv = CommandLineToArgvW(GetCommandLineW(), &argc);
@@ -71,12 +71,12 @@ int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) {
const std::wstring gameExe = gameDir + L"sacred.exe"; const std::wstring gameExe = gameDir + L"sacred.exe";
if (!FileExists(gameExe)) { if (!FileExists(gameExe)) {
return Fail(L"sacred.exe not found in\n" + gameDir + return Fail(L"sacred.exe not found in\n" + gameDir +
L"\n\nRun once with: sacred_re.exe --game-dir \"<Sacred Gold folder>\""); L"\n\nRun once with: sacredK.exe --game-dir \"<Sacred Gold folder>\"");
} }
const std::wstring dllPath = launcherDir + L"sacred_re.dll"; const std::wstring dllPath = launcherDir + L"sacredK.dll";
if (!FileExists(dllPath)) { if (!FileExists(dllPath)) {
return Fail(L"sacred_re.dll not found next to the launcher:\n" + dllPath); return Fail(L"sacredK.dll not found next to the launcher:\n" + dllPath);
} }
// Detours takes the DLL path as ANSI. // Detours takes the DLL path as ANSI.
char dllPathA[MAX_PATH]; char dllPathA[MAX_PATH];
+1 -1
View File
@@ -9,7 +9,7 @@
// Mouse positions are mapped back from that rectangle to game pixels, both in // Mouse positions are mapped back from that rectangle to game pixels, both in
// GetMouseClientPos (polled by the game) and in mouse window messages. // GetMouseClientPos (polled by the game) and in mouse window messages.
// //
// sacred_re.ini: // sacredK.ini:
// [borderless] // [borderless]
// Enabled=1 ; 0 = original behaviour // Enabled=1 ; 0 = original behaviour
// IntegerScale=0 ; 1 = scale by whole multiples only (sharper, larger bars) // IntegerScale=0 ; 1 = scale by whole multiples only (sharper, larger bars)
+1 -1
View File
@@ -11,7 +11,7 @@ namespace game {
// cParticleSystem_burningBone::Render (vf04 0x774e10, vtable 0x895b84): // cParticleSystem_burningBone::Render (vf04 0x774e10, vtable 0x895b84):
// crashed reading the device it was given, which was null (2026-10-16, Esc // crashed reading the device it was given, which was null (2026-10-16, Esc
// pressed while walking; Windows reported sacred.exe+0x374e39). Who renders // pressed while walking; Windows reported sacred.exe+0x374e39). Who renders
// it without a device is not known yet (sacred_re_crash.txt will tell). With // it without a device is not known yet (sacredK_crash.txt will tell). With
// no device there is nothing to draw. // no device there is nothing to draw.
// (device, a 4x4 matrix by value; ret 0x44). // (device, a 4x4 matrix by value; ret 0x44).
struct Matrix4 { struct Matrix4 {
+1 -1
View File
@@ -16,7 +16,7 @@
// inside both. // inside both.
// The world never sees these offsets: they are only active inside the hooks. // The world never sees these offsets: they are only active inside the hooks.
// //
// sacred_re.ini: // sacredK.ini:
// [hud] // [hud]
// Enabled=1 // Enabled=1
+1 -1
View File
@@ -16,7 +16,7 @@
// off. Alt+key arrives as WM_SYSKEYDOWN and is not handled here anyway.) // off. Alt+key arrives as WM_SYSKEYDOWN and is not handled here anyway.)
// //
// The bindings are set on the options window's key bindings tab // The bindings are set on the options window's key bindings tab
// (mods/options.cpp) and kept in sacred_re.ini (key names: kKeyNames below, // (mods/options.cpp) and kept in sacredK.ini (key names: kKeyNames below,
// None): // None):
// [keys] // [keys]
// SkillSlot0=0 ... SkillSlot6=6 ; cast combat-art slot (0 = right mouse button art) // SkillSlot0=0 ... SkillSlot6=6 ; cast combat-art slot (0 = right mouse button art)
+1 -1
View File
@@ -33,7 +33,7 @@ const wchar_t* BindingGroup(int index);
int BindingKey(int index); // virtual key, 0 = none int BindingKey(int index); // virtual key, 0 = none
int DefaultBindingKey(int index); int DefaultBindingKey(int index);
// Sets all bindings (`keys[BindingCount()]`) and writes them to sacred_re.ini. // Sets all bindings (`keys[BindingCount()]`) and writes them to sacredK.ini.
void SetBindingKeys(const int* keys); void SetBindingKeys(const int* keys);
// Whether `vk` can be bound, and its name as shown ("Leertaste", "F1", ...). // Whether `vk` can be bound, and its name as shown ("Leertaste", "F1", ...).
+4 -2
View File
@@ -193,7 +193,7 @@ void PutArtSlot(const json& variants, uint8_t* slot) {
json ToJson(const std::vector<uint8_t>& block, const uint8_t* shadow) { json ToJson(const std::vector<uint8_t>& block, const uint8_t* shadow) {
Reader r(block); Reader r(block);
json j; json j;
j["format"] = "sacred_re.inventory"; j["format"] = "sacredK.inventory";
j["version"] = kVersion; j["version"] = kVersion;
j["player"] = r.U16(); j["player"] = r.U16();
j["flags"] = r.U16(); j["flags"] = r.U16();
@@ -262,7 +262,9 @@ json ToJson(const std::vector<uint8_t>& block, const uint8_t* shadow) {
// The original block for slots 1-5; slots 6-7 are applied afterwards. // The original block for slots 1-5; slots 6-7 are applied afterwards.
std::vector<uint8_t> ToBlock(const json& j) { std::vector<uint8_t> ToBlock(const json& j) {
if (j.at("format") != "sacred_re.inventory" || j.at("version").get<int>() > kVersion) { // "sacred_re.inventory": saves from before the project was renamed to sacredK.
const auto format = j.at("format").get<std::string>();
if ((format != "sacredK.inventory" && format != "sacred_re.inventory") || j.at("version").get<int>() > kVersion) {
throw std::runtime_error("unknown inventory format / version"); throw std::runtime_error("unknown inventory format / version");
} }
Writer w; Writer w;
+1 -1
View File
@@ -1,7 +1,7 @@
// Options window in three tabs: Allgemein, Beute, Tastenbelegung (request of // Options window in three tabs: Allgemein, Beute, Tastenbelegung (request of
// 2026-10-18). Beute holds the game's auto-pickup settings (first iteration: // 2026-10-18). Beute holds the game's auto-pickup settings (first iteration:
// moved there from the common page); Tastenbelegung sets the key bindings of // moved there from the common page); Tastenbelegung sets the key bindings of
// mods/input.cpp, which had only been settable in sacred_re.ini. // mods/input.cpp, which had only been settable in sacredK.ini.
// //
// The window (cUI_Options, vtable 0x89505c, constructor 0x716570 building its // The window (cUI_Options, vtable 0x89505c, constructor 0x716570 building its
// controls in 0x7184e0) is laid out for 1024x768: six frames in two columns // controls in 0x7184e0) is laid out for 1024x768: six frames in two columns
+1 -1
View File
@@ -334,7 +334,7 @@ void Save() {
} }
} }
} }
const json j = {{"format", "sacred_re.shared_chest"}, {"version", kFileVersion}, {"items", items}}; const json j = {{"format", "sacredK.shared_chest"}, {"version", kFileVersion}, {"items", items}};
const std::string path = FilePath(); const std::string path = FilePath();
const std::string temp = path + ".tmp"; const std::string temp = path + ".tmp";
{ {
+1 -1
View File
@@ -8,7 +8,7 @@
// 1024x768 layout, and set the variables from the render size. // 1024x768 layout, and set the variables from the render size.
// See docs/widescreen.md. // See docs/widescreen.md.
// //
// sacred_re.ini: // sacredK.ini:
// [widescreen] // [widescreen]
// Enabled=1 // Enabled=1
// Height=768 ; world pixels visible vertically (768 = original look); // Height=768 ; world pixels visible vertically (768 = original look);
+2 -2
View File
@@ -1,5 +1,5 @@
#pragma once #pragma once
// sacred_re.ini next to sacred_re.dll / sacred_re.exe. // sacredK.ini next to sacredK.dll / sacredK.exe.
#include <string> #include <string>
@@ -7,7 +7,7 @@ namespace re {
void SetConfigPath(const std::wstring& path); void SetConfigPath(const std::wstring& path);
const std::wstring& ConfigPath(); const std::wstring& ConfigPath();
// Folder of sacred_re.dll (with trailing separator), derived from the config path. // Folder of sacredK.dll (with trailing separator), derived from the config path.
std::wstring ModuleDirectory(); std::wstring ModuleDirectory();
int ConfigInt(const char* section, const char* key, int fallback); int ConfigInt(const char* section, const char* key, int fallback);
std::string ConfigString(const char* section, const char* key, const char* fallback); std::string ConfigString(const char* section, const char* key, const char* fallback);
+7 -7
View File
@@ -1,10 +1,10 @@
// Crash report: on an unhandled exception, writes sacred_re_crash.txt (code, // Crash report: on an unhandled exception, writes sacredK_crash.txt (code,
// address as module + offset, registers, return addresses found on the stack) // address as module + offset, registers, return addresses found on the stack)
// and sacred_re_crash.dmp next to the DLL. The game may install its own // and sacredK_crash.dmp next to the DLL. The game may install its own
// filter later; SetUnhandledExceptionFilter is hooked so ours stays first and // filter later; SetUnhandledExceptionFilter is hooked so ours stays first and
// passes the crash on to the game's afterwards. // passes the crash on to the game's afterwards.
// //
// Hangs: a watchdog thread writes sacred_re_hang.dmp (all threads) when the // Hangs: a watchdog thread writes sacredK_hang.dmp (all threads) when the
// engine tick (re::EngineHeartbeat) has stopped for kHangMs while the game // engine tick (re::EngineHeartbeat) has stopped for kHangMs while the game
// window is in front; once per hang. The game freezing on F2 while walking // window is in front; once per hang. The game freezing on F2 while walking
// (2026-10-18) left no report. // (2026-10-18) left no report.
@@ -52,7 +52,7 @@ bool IsCode(uintptr_t address) {
} }
void WriteReport(EXCEPTION_POINTERS* info) { void WriteReport(EXCEPTION_POINTERS* info) {
FILE* file = _wfopen((g_dir + L"sacred_re_crash.txt").c_str(), L"w"); FILE* file = _wfopen((g_dir + L"sacredK_crash.txt").c_str(), L"w");
if (!file) { if (!file) {
return; return;
} }
@@ -100,7 +100,7 @@ void WriteReport(EXCEPTION_POINTERS* info) {
} }
fclose(file); fclose(file);
HANDLE dump = CreateFileW((g_dir + L"sacred_re_crash.dmp").c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, HANDLE dump = CreateFileW((g_dir + L"sacredK_crash.dmp").c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, nullptr); FILE_ATTRIBUTE_NORMAL, nullptr);
if (dump != INVALID_HANDLE_VALUE) { if (dump != INVALID_HANDLE_VALUE) {
MINIDUMP_EXCEPTION_INFORMATION exception = {GetCurrentThreadId(), info, FALSE}; MINIDUMP_EXCEPTION_INFORMATION exception = {GetCurrentThreadId(), info, FALSE};
@@ -120,7 +120,7 @@ bool GameInFront() {
} }
void WriteHangDump() { void WriteHangDump() {
HANDLE dump = CreateFileW((g_dir + L"sacred_re_hang.dmp").c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, HANDLE dump = CreateFileW((g_dir + L"sacredK_hang.dmp").c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, nullptr); FILE_ATTRIBUTE_NORMAL, nullptr);
if (dump != INVALID_HANDLE_VALUE) { if (dump != INVALID_HANDLE_VALUE) {
MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), dump, MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), dump,
@@ -142,7 +142,7 @@ DWORD WINAPI Watchdog(void*) {
} }
if (!reported && GameInFront()) { if (!reported && GameInFront()) {
reported = true; reported = true;
Log("hang: no engine tick for %lu ms, writing sacred_re_hang.dmp", GetTickCount() - beat); Log("hang: no engine tick for %lu ms, writing sacredK_hang.dmp", GetTickCount() - beat);
WriteHangDump(); WriteHangDump();
Log("hang: dump written"); Log("hang: dump written");
} }
+2 -2
View File
@@ -4,8 +4,8 @@
namespace re { namespace re {
// Writes sacred_re_crash.txt / .dmp into `dir` when the game crashes, and // Writes sacredK_crash.txt / .dmp into `dir` when the game crashes, and
// sacred_re_hang.dmp when the engine stops ticking (see EngineHeartbeat). // sacredK_hang.dmp when the engine stops ticking (see EngineHeartbeat).
void InstallCrashHandler(const std::wstring& dir); void InstallCrashHandler(const std::wstring& dir);
// The engine ticked (running), or the game ended (not running). // The engine ticked (running), or the game ended (not running).
+6 -6
View File
@@ -1,4 +1,4 @@
// sacred_re.dll entry point. Loaded into sacred.exe by the launcher before the // sacredK.dll entry point. Loaded into sacred.exe by the launcher before the
// game's own entry point runs, so every hook is in place from the first // game's own entry point runs, so every hook is in place from the first
// instruction of game code. // instruction of game code.
@@ -40,17 +40,17 @@ bool IsSupportedGameBuild() {
void Startup(HMODULE module) { void Startup(HMODULE module) {
std::wstring dir = ModuleDir(module); std::wstring dir = ModuleDir(module);
re::OpenLog((dir + L"sacred_re.log").c_str()); re::OpenLog((dir + L"sacredK.log").c_str());
re::SetConfigPath(dir + L"sacred_re.ini"); re::SetConfigPath(dir + L"sacredK.ini");
re::Log("sacred_re loaded"); re::Log("sacredK loaded");
re::InstallCrashHandler(dir); re::InstallCrashHandler(dir);
if (!IsSupportedGameBuild()) { if (!IsSupportedGameBuild()) {
re::Log("unsupported sacred.exe build, no hooks installed"); re::Log("unsupported sacred.exe build, no hooks installed");
MessageBoxW(nullptr, MessageBoxW(nullptr,
L"This sacred.exe is not the supported Sacred Gold 2.28 build.\n" L"This sacred.exe is not the supported Sacred Gold 2.28 build.\n"
L"sacred_re will not patch it; the game runs unmodified.", L"sacredK will not patch it; the game runs unmodified.",
L"sacred_re", MB_OK | MB_ICONWARNING); L"sacredK", MB_OK | MB_ICONWARNING);
return; return;
} }
if (re::ConfigInt("debug", "WaitForDebugger", 0)) { if (re::ConfigInt("debug", "WaitForDebugger", 0)) {
+2 -2
View File
@@ -13,7 +13,7 @@
// //
// The signature, including the calling convention, must match the original // The signature, including the calling convention, must match the original
// exactly (`__cdecl`, `__stdcall`, `__thiscall` / `__fastcall` stand-in, ...). // exactly (`__cdecl`, `__stdcall`, `__thiscall` / `__fastcall` stand-in, ...).
// Hooks can be disabled individually at runtime in sacred_re.ini: // Hooks can be disabled individually at runtime in sacredK.ini:
// //
// [hooks] // [hooks]
// Log=0 // Log=0
@@ -40,7 +40,7 @@ bool AttachRuntimeHook(const char* name, void** original, void* replacement);
// Hooks a Windows API function for the whole process (game and DLLs alike). // Hooks a Windows API function for the whole process (game and DLLs alike).
// `replacement` must have the API's exact signature; defines // `replacement` must have the API's exact signature; defines
// `replacement##_original`. Disabled in sacred_re.ini under the API's name. // `replacement##_original`. Disabled in sacredK.ini under the API's name.
#define RE_HOOK_API(api, replacement) \ #define RE_HOOK_API(api, replacement) \
static decltype(&replacement) replacement##_original = &api; \ static decltype(&replacement) replacement##_original = &api; \
static ::re::HookRegistration replacement##_registration( \ static ::re::HookRegistration replacement##_registration( \
+1 -1
View File
@@ -1,5 +1,5 @@
#pragma once #pragma once
// sacred_re's own log: sacred_re.log next to sacred_re.dll, plus OutputDebugString. // sacredK's own log: sacredK.log next to sacredK.dll, plus OutputDebugString.
namespace re { namespace re {
+1 -1
View File
@@ -1,4 +1,4 @@
LIBRARY sacred_re LIBRARY sacredK
EXPORTS EXPORTS
; Detours requires ordinal 1 on any DLL injected via DetourCreateProcessWithDll. ; Detours requires ordinal 1 on any DLL injected via DetourCreateProcessWithDll.
DetourFinishHelperProcess @1 NONAME DetourFinishHelperProcess @1 NONAME
+1 -1
View File
@@ -30,7 +30,7 @@ $env:GHIDRA_HEADLESS_MAXMEM = "8G"
`autotest-load.ps1` starts the game through the launcher, clicks `autotest-load.ps1` starts the game through the launcher, clicks
"Spiel laden" -> "Laden" -> "Ok" (first save game), takes screenshots in-game "Spiel laden" -> "Laden" -> "Ok" (first save game), takes screenshots in-game
and with the ESC menu open, and stops the game. `-Ini` appends temporary and with the ESC menu open, and stops the game. `-Ini` appends temporary
settings to `build\x86-debug\sacred_re.ini` (restored afterwards), e.g. settings to `build\x86-debug\sacredK.ini` (restored afterwards), e.g.
```powershell ```powershell
.\tools\autotest-load.ps1 -Name ws_off -Ini "[widescreen]`r`nEnabled=0" .\tools\autotest-load.ps1 -Name ws_off -Ini "[widescreen]`r`nEnabled=0"
+3 -3
View File
@@ -2,10 +2,10 @@
# the list, 1 = the first, selected by default), take # the list, 1 = the first, selected by default), take
# screenshots in-game and with the ESC menu open, then stop the game. # screenshots in-game and with the ESC menu open, then stop the game.
# #
# .\tools\autotest-load.ps1 -Name ws_on # current sacred_re.ini # .\tools\autotest-load.ps1 -Name ws_on # current sacredK.ini
# .\tools\autotest-load.ps1 -Name ws_off -Ini "[widescreen]`r`nEnabled=0" # .\tools\autotest-load.ps1 -Name ws_off -Ini "[widescreen]`r`nEnabled=0"
# #
# -Ini temporarily appends settings to build\x86-debug\sacred_re.ini. # -Ini temporarily appends settings to build\x86-debug\sacredK.ini.
# Click positions are for the classic (menu) layout scaled onto a 3440x1440 # Click positions are for the classic (menu) layout scaled onto a 3440x1440
# monitor; adjust for other monitors. Don't touch mouse/keyboard while it runs. # monitor; adjust for other monitors. Don't touch mouse/keyboard while it runs.
param( param(
@@ -21,7 +21,7 @@ $ErrorActionPreference = "Stop"
. (Join-Path $PSScriptRoot "game-input.ps1") . (Join-Path $PSScriptRoot "game-input.ps1")
$root = Split-Path $PSScriptRoot $root = Split-Path $PSScriptRoot
$iniPath = Join-Path $root "build\x86-debug\sacred_re.ini" $iniPath = Join-Path $root "build\x86-debug\sacredK.ini"
$iniBackup = if (Test-Path $iniPath) { Get-Content $iniPath -Raw } else { "" } $iniBackup = if (Test-Path $iniPath) { Get-Content $iniPath -Raw } else { "" }
if ($Ini) { Set-Content $iniPath ($iniBackup + "`r`n" + $Ini + "`r`n") -Encoding ASCII } if ($Ini) { Set-Content $iniPath ($iniBackup + "`r`n" + $Ini + "`r`n") -Encoding ASCII }
+4 -4
View File
@@ -31,9 +31,9 @@ function Focus-Sacred($Game) {
[SacredRe.Input]::SetForegroundWindow($Game.MainWindowHandle) [SacredRe.Input]::SetForegroundWindow($Game.MainWindowHandle)
} }
$script:LogPath = Join-Path $script:Root "build\x86-debug\sacred_re.log" $script:LogPath = Join-Path $script:Root "build\x86-debug\sacredK.log"
# Waits until sacred_re.log (written anew at every start) has at least $Count # Waits until sacredK.log (written anew at every start) has at least $Count
# lines containing $Pattern, focusing the game meanwhile; then waits $AfterMs. # lines containing $Pattern, focusing the game meanwhile; then waits $AfterMs.
function Wait-Log([string]$Pattern, [int]$Count = 1, [int]$TimeoutSeconds = 60, [int]$AfterMs = 500, $Game = $null) { function Wait-Log([string]$Pattern, [int]$Count = 1, [int]$TimeoutSeconds = 60, [int]$AfterMs = 500, $Game = $null) {
$deadline = (Get-Date).AddSeconds($TimeoutSeconds) $deadline = (Get-Date).AddSeconds($TimeoutSeconds)
@@ -46,7 +46,7 @@ function Wait-Log([string]$Pattern, [int]$Count = 1, [int]$TimeoutSeconds = 60,
if ($found -ge $Count) { Start-Sleep -Milliseconds $AfterMs; return } if ($found -ge $Count) { Start-Sleep -Milliseconds $AfterMs; return }
Start-Sleep -Milliseconds 200 Start-Sleep -Milliseconds 200
} }
throw "timeout waiting for '$Pattern' in sacred_re.log" throw "timeout waiting for '$Pattern' in sacredK.log"
} }
# Starts the game and returns once its main menu is up (the UI manager # Starts the game and returns once its main menu is up (the UI manager
@@ -54,7 +54,7 @@ function Wait-Log([string]$Pattern, [int]$Count = 1, [int]$TimeoutSeconds = 60,
function Start-Sacred([string]$Arguments = "log") { function Start-Sacred([string]$Arguments = "log") {
if (Get-Process sacred -ErrorAction SilentlyContinue) { throw "sacred.exe is already running" } if (Get-Process sacred -ErrorAction SilentlyContinue) { throw "sacred.exe is already running" }
if (Test-Path $script:LogPath) { Remove-Item $script:LogPath -ErrorAction SilentlyContinue } if (Test-Path $script:LogPath) { Remove-Item $script:LogPath -ErrorAction SilentlyContinue }
$null = Start-Process (Join-Path $script:Root "build\x86-debug\sacred_re.exe") -ArgumentList $Arguments $null = Start-Process (Join-Path $script:Root "build\x86-debug\sacredK.exe") -ArgumentList $Arguments
$game = $null $game = $null
for ($i = 0; $i -lt 80 -and -not $game; $i++) { for ($i = 0; $i -lt 80 -and -not $game; $i++) {
Start-Sleep -Milliseconds 250 Start-Sleep -Milliseconds 250
+1 -1
View File
@@ -6,7 +6,7 @@
// //
// Ghidra's own RecoverClassesFromRTTIScript finds nothing in this binary. // Ghidra's own RecoverClassesFromRTTIScript finds nothing in this binary.
// Run once, headless, without -readOnly (see tools/README.md). // Run once, headless, without -readOnly (see tools/README.md).
// @category sacred_re // @category sacredK
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
+1 -1
View File
@@ -6,7 +6,7 @@
// //
// Addresses without a function (e.g. methods only reachable via a vtable) get // Addresses without a function (e.g. methods only reachable via a vtable) get
// one created first; run without -readOnly to keep those in the project. // one created first; run without -readOnly to keep those in the project.
// @category sacred_re // @category sacredK
import java.io.File; import java.io.File;
import java.io.PrintWriter; import java.io.PrintWriter;
+1 -1
View File
@@ -9,7 +9,7 @@
// into a class namespace; "#" starts a comment. Functions are created where // into a class namespace; "#" starts a comment. Functions are created where
// none starts at the address yet. Names starting with "g_" label data // none starts at the address yet. Names starting with "g_" label data
// (globals, tables) instead of functions. // (globals, tables) instead of functions.
// @category sacred_re // @category sacredK
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
+2 -2
View File
@@ -1,10 +1,10 @@
{ {
"name": "sacred-re-tools", "name": "sacredk-tools",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "sacred-re-tools", "name": "sacredk-tools",
"dependencies": { "dependencies": {
"iced-x86": "1.21.0" "iced-x86": "1.21.0"
} }
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"name": "sacred-re-tools", "name": "sacredk-tools",
"private": true, "private": true,
"type": "module", "type": "module",
"dependencies": { "dependencies": {