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:
1 parent
462ebf8c0b
commit
70673dc301
34 files changed
+95
-93
No files matched your search
+1
-1
@@ -3,6 +3,6 @@
|
||||
/.idea/
|
||||
/cmake-build-*/
|
||||
*.rea-snapshot
|
||||
sacred_re.ini
|
||||
sacredK.ini
|
||||
/ghidra/
|
||||
/screenshots/
|
||||
Vendored
+3
-3
@@ -3,10 +3,10 @@
|
||||
"configurations": [
|
||||
{
|
||||
// Builds, then starts the game through the launcher (no debugger in the game).
|
||||
"name": "Run Sacred (sacred_re)",
|
||||
"name": "Run Sacred (sacredK)",
|
||||
"type": "cppvsdbg",
|
||||
"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"],
|
||||
"cwd": "${workspaceFolder}/build/x86-debug",
|
||||
"preLaunchTask": "CMake: build",
|
||||
@@ -14,7 +14,7 @@
|
||||
},
|
||||
{
|
||||
// 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.
|
||||
"name": "Attach to sacred.exe",
|
||||
"type": "cppvsdbg",
|
||||
|
||||
@@ -6,8 +6,8 @@ project, autotest) first; this file adds how we work and what we learned.
|
||||
|
||||
## What this is
|
||||
|
||||
`sacred_re.dll` is injected into the original **Sacred Gold 2.28**
|
||||
`sacred.exe` (2006, 32-bit, MSVC 6) by the launcher `sacred_re.exe`. It
|
||||
`sacredK.dll` is injected into the original **Sacred Gold 2.28**
|
||||
`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
|
||||
the work so far is **mods** — behaviour changes on top of the original
|
||||
(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.
|
||||
|
||||
- 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.
|
||||
- Game texts are German (Fertigkeiten, Attribute, Blitzschlag, ...); keep the
|
||||
game's names when talking about features.
|
||||
@@ -65,12 +65,12 @@ at every step; the user plays it to test.
|
||||
|
||||
```powershell
|
||||
.\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:
|
||||
`build\x86-debug\sacred_re.log` (written anew each start; our lines plus the
|
||||
game's own log). Crashes: `sacred_re_crash.txt` / `.dmp` next to the DLL
|
||||
`build\x86-debug\sacredK.log` (written anew each start; our lines plus the
|
||||
game's own log). Crashes: `sacredK_crash.txt` / `.dmp` next to the DLL
|
||||
(exception, module+offset, ebp chain); Windows also records the faulting
|
||||
offset in the Application event log (`Get-WinEvent`, provider "Application
|
||||
Error"). The debug build's run-time checks turn calling-convention mistakes
|
||||
|
||||
+16
-16
@@ -1,12 +1,12 @@
|
||||
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.
|
||||
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()
|
||||
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()
|
||||
|
||||
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/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 -------------
|
||||
add_library(sacred_re SHARED
|
||||
# --- sacredK.dll: injected into sacred.exe, installs the hooks -------------
|
||||
add_library(sacredK SHARED
|
||||
src/re/dllmain.cpp
|
||||
src/re/hook.cpp
|
||||
src/re/log.cpp
|
||||
src/re/config.cpp
|
||||
src/re/patch.cpp
|
||||
src/re/crash.cpp
|
||||
src/re/sacred_re.def
|
||||
src/re/sacredK.def
|
||||
src/game/engine/log.cpp
|
||||
src/game/engine/cmdline.cpp
|
||||
src/game/engine/events.cpp
|
||||
@@ -70,13 +70,13 @@ add_library(sacred_re SHARED
|
||||
src/mods/charexport.cpp
|
||||
src/mods/options.cpp
|
||||
)
|
||||
target_include_directories(sacred_re PRIVATE src)
|
||||
target_compile_options(sacred_re PRIVATE ${SACRED_RE_CXX_FLAGS})
|
||||
target_link_libraries(sacred_re PRIVATE detours nlohmann_json::nlohmann_json dbghelp)
|
||||
target_include_directories(sacredK PRIVATE src)
|
||||
target_compile_options(sacredK PRIVATE ${SACREDK_CXX_FLAGS})
|
||||
target_link_libraries(sacredK PRIVATE detours nlohmann_json::nlohmann_json dbghelp)
|
||||
|
||||
# --- sacred_re.exe: launches sacred.exe with sacred_re.dll injected ----------
|
||||
add_executable(sacred_re_launcher WIN32 src/launcher/main.cpp)
|
||||
set_target_properties(sacred_re_launcher PROPERTIES OUTPUT_NAME sacred_re)
|
||||
target_compile_options(sacred_re_launcher PRIVATE ${SACRED_RE_CXX_FLAGS})
|
||||
target_link_libraries(sacred_re_launcher PRIVATE detours)
|
||||
add_dependencies(sacred_re_launcher sacred_re)
|
||||
# --- sacredK.exe: launches sacred.exe with sacredK.dll injected ----------
|
||||
add_executable(sacredK_launcher WIN32 src/launcher/main.cpp)
|
||||
set_target_properties(sacredK_launcher PROPERTIES OUTPUT_NAME sacredK)
|
||||
target_compile_options(sacredK_launcher PRIVATE ${SACREDK_CXX_FLAGS})
|
||||
target_link_libraries(sacredK_launcher PRIVATE detours)
|
||||
add_dependencies(sacredK_launcher sacredK)
|
||||
@@ -1,8 +1,8 @@
|
||||
# sacred_re
|
||||
# sacredK
|
||||
|
||||
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
|
||||
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
|
||||
@@ -42,14 +42,14 @@ the CMake presets.
|
||||
## Running
|
||||
|
||||
```powershell
|
||||
build\x86-debug\sacred_re.exe --game-dir "D:\Gaming\Sacred Gold" # first run; remembered in sacred_re.ini
|
||||
build\x86-debug\sacred_re.exe log # later runs; extra args go to the game
|
||||
build\x86-debug\sacredK.exe --game-dir "D:\Gaming\Sacred Gold" # first run; remembered in sacredK.ini
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
```ini
|
||||
@@ -61,7 +61,7 @@ CmdLineHasFlag=0
|
||||
|
||||
```
|
||||
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/mods/ behaviour changes on top of the original (e.g. borderless.cpp)
|
||||
docs/ reverse-engineering notes; function-map.md tracks progress
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@
|
||||
Request (2026-10-18): the settings screen gets three tabs: *Allgemein*,
|
||||
*Beute* (loot filter; first iteration: the game's loot options moved there
|
||||
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`.
|
||||
|
||||
## 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
|
||||
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
|
||||
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.
|
||||
- Movement keys (W/A/S/D) are bindings now (`MoveUp/Left/Down/Right`);
|
||||
`mods/wasd.cpp` polls the bound keys.
|
||||
|
||||
@@ -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
|
||||
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.
|
||||
- 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
|
||||
`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
|
||||
|
||||
+2
-2
@@ -93,7 +93,7 @@ mouse/keyboard like `tools/autotest-load.ps1`:
|
||||
2. play scripted input through the existing hooks (keys, casting arts, weapon
|
||||
set switches, save + reload);
|
||||
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, ...);
|
||||
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
|
||||
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) |
|
||||
| A | pick up items nearby (creature event 0x113) | Q (`[keys] PickUp`, 2026-10-17; was V) |
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
// optional callback (0x017e5ee4) and to either OutputDebugString or the
|
||||
// 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.
|
||||
|
||||
#include "game/engine/log.h"
|
||||
|
||||
@@ -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
|
||||
// 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
|
||||
// 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>
|
||||
|
||||
@@ -35,7 +35,7 @@ std::wstring WithTrailingSlash(std::wstring dir) {
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ int Fail(const std::wstring& message) {
|
||||
|
||||
int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) {
|
||||
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;
|
||||
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";
|
||||
if (!FileExists(gameExe)) {
|
||||
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)) {
|
||||
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.
|
||||
char dllPathA[MAX_PATH];
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
// Mouse positions are mapped back from that rectangle to game pixels, both in
|
||||
// GetMouseClientPos (polled by the game) and in mouse window messages.
|
||||
//
|
||||
// sacred_re.ini:
|
||||
// sacredK.ini:
|
||||
// [borderless]
|
||||
// Enabled=1 ; 0 = original behaviour
|
||||
// IntegerScale=0 ; 1 = scale by whole multiples only (sharper, larger bars)
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ namespace game {
|
||||
// cParticleSystem_burningBone::Render (vf04 0x774e10, vtable 0x895b84):
|
||||
// crashed reading the device it was given, which was null (2026-10-16, Esc
|
||||
// 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.
|
||||
// (device, a 4x4 matrix by value; ret 0x44).
|
||||
struct Matrix4 {
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@
|
||||
// inside both.
|
||||
// The world never sees these offsets: they are only active inside the hooks.
|
||||
//
|
||||
// sacred_re.ini:
|
||||
// sacredK.ini:
|
||||
// [hud]
|
||||
// Enabled=1
|
||||
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@
|
||||
// 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
|
||||
// (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):
|
||||
// [keys]
|
||||
// SkillSlot0=0 ... SkillSlot6=6 ; cast combat-art slot (0 = right mouse button art)
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ const wchar_t* BindingGroup(int index);
|
||||
int BindingKey(int index); // virtual key, 0 = none
|
||||
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);
|
||||
|
||||
// Whether `vk` can be bound, and its name as shown ("Leertaste", "F1", ...).
|
||||
|
||||
@@ -193,7 +193,7 @@ void PutArtSlot(const json& variants, uint8_t* slot) {
|
||||
json ToJson(const std::vector<uint8_t>& block, const uint8_t* shadow) {
|
||||
Reader r(block);
|
||||
json j;
|
||||
j["format"] = "sacred_re.inventory";
|
||||
j["format"] = "sacredK.inventory";
|
||||
j["version"] = kVersion;
|
||||
j["player"] = 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.
|
||||
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");
|
||||
}
|
||||
Writer w;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Options window in three tabs: Allgemein, Beute, Tastenbelegung (request of
|
||||
// 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
|
||||
// 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
|
||||
// controls in 0x7184e0) is laid out for 1024x768: six frames in two columns
|
||||
|
||||
@@ -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 temp = path + ".tmp";
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
// 1024x768 layout, and set the variables from the render size.
|
||||
// See docs/widescreen.md.
|
||||
//
|
||||
// sacred_re.ini:
|
||||
// sacredK.ini:
|
||||
// [widescreen]
|
||||
// Enabled=1
|
||||
// Height=768 ; world pixels visible vertically (768 = original look);
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
#pragma once
|
||||
// sacred_re.ini next to sacred_re.dll / sacred_re.exe.
|
||||
// sacredK.ini next to sacredK.dll / sacredK.exe.
|
||||
|
||||
#include <string>
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace re {
|
||||
|
||||
void SetConfigPath(const std::wstring& path);
|
||||
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();
|
||||
int ConfigInt(const char* section, const char* key, int fallback);
|
||||
std::string ConfigString(const char* section, const char* key, const char* fallback);
|
||||
|
||||
+7
-7
@@ -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)
|
||||
// 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
|
||||
// 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
|
||||
// window is in front; once per hang. The game freezing on F2 while walking
|
||||
// (2026-10-18) left no report.
|
||||
@@ -52,7 +52,7 @@ bool IsCode(uintptr_t address) {
|
||||
}
|
||||
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
@@ -100,7 +100,7 @@ void WriteReport(EXCEPTION_POINTERS* info) {
|
||||
}
|
||||
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);
|
||||
if (dump != INVALID_HANDLE_VALUE) {
|
||||
MINIDUMP_EXCEPTION_INFORMATION exception = {GetCurrentThreadId(), info, FALSE};
|
||||
@@ -120,7 +120,7 @@ bool GameInFront() {
|
||||
}
|
||||
|
||||
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);
|
||||
if (dump != INVALID_HANDLE_VALUE) {
|
||||
MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), dump,
|
||||
@@ -142,7 +142,7 @@ DWORD WINAPI Watchdog(void*) {
|
||||
}
|
||||
if (!reported && GameInFront()) {
|
||||
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();
|
||||
Log("hang: dump written");
|
||||
}
|
||||
|
||||
+2
-2
@@ -4,8 +4,8 @@
|
||||
|
||||
namespace re {
|
||||
|
||||
// Writes sacred_re_crash.txt / .dmp into `dir` when the game crashes, and
|
||||
// sacred_re_hang.dmp when the engine stops ticking (see EngineHeartbeat).
|
||||
// Writes sacredK_crash.txt / .dmp into `dir` when the game crashes, and
|
||||
// sacredK_hang.dmp when the engine stops ticking (see EngineHeartbeat).
|
||||
void InstallCrashHandler(const std::wstring& dir);
|
||||
|
||||
// The engine ticked (running), or the game ended (not running).
|
||||
|
||||
+6
-6
@@ -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
|
||||
// instruction of game code.
|
||||
|
||||
@@ -40,17 +40,17 @@ bool IsSupportedGameBuild() {
|
||||
|
||||
void Startup(HMODULE module) {
|
||||
std::wstring dir = ModuleDir(module);
|
||||
re::OpenLog((dir + L"sacred_re.log").c_str());
|
||||
re::SetConfigPath(dir + L"sacred_re.ini");
|
||||
re::Log("sacred_re loaded");
|
||||
re::OpenLog((dir + L"sacredK.log").c_str());
|
||||
re::SetConfigPath(dir + L"sacredK.ini");
|
||||
re::Log("sacredK loaded");
|
||||
re::InstallCrashHandler(dir);
|
||||
|
||||
if (!IsSupportedGameBuild()) {
|
||||
re::Log("unsupported sacred.exe build, no hooks installed");
|
||||
MessageBoxW(nullptr,
|
||||
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"sacred_re", MB_OK | MB_ICONWARNING);
|
||||
L"sacredK will not patch it; the game runs unmodified.",
|
||||
L"sacredK", MB_OK | MB_ICONWARNING);
|
||||
return;
|
||||
}
|
||||
if (re::ConfigInt("debug", "WaitForDebugger", 0)) {
|
||||
|
||||
+2
-2
@@ -13,7 +13,7 @@
|
||||
//
|
||||
// The signature, including the calling convention, must match the original
|
||||
// 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]
|
||||
// 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).
|
||||
// `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) \
|
||||
static decltype(&replacement) replacement##_original = &api; \
|
||||
static ::re::HookRegistration replacement##_registration( \
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
#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 {
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
LIBRARY sacred_re
|
||||
LIBRARY sacredK
|
||||
EXPORTS
|
||||
; Detours requires ordinal 1 on any DLL injected via DetourCreateProcessWithDll.
|
||||
DetourFinishHelperProcess @1 NONAME
|
||||
+1
-1
@@ -30,7 +30,7 @@ $env:GHIDRA_HEADLESS_MAXMEM = "8G"
|
||||
`autotest-load.ps1` starts the game through the launcher, clicks
|
||||
"Spiel laden" -> "Laden" -> "Ok" (first save game), takes screenshots in-game
|
||||
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
|
||||
.\tools\autotest-load.ps1 -Name ws_off -Ini "[widescreen]`r`nEnabled=0"
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
# the list, 1 = the first, selected by default), take
|
||||
# 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"
|
||||
#
|
||||
# -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
|
||||
# monitor; adjust for other monitors. Don't touch mouse/keyboard while it runs.
|
||||
param(
|
||||
@@ -21,7 +21,7 @@ $ErrorActionPreference = "Stop"
|
||||
. (Join-Path $PSScriptRoot "game-input.ps1")
|
||||
|
||||
$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 { "" }
|
||||
if ($Ini) { Set-Content $iniPath ($iniBackup + "`r`n" + $Ini + "`r`n") -Encoding ASCII }
|
||||
|
||||
|
||||
@@ -31,9 +31,9 @@ function Focus-Sacred($Game) {
|
||||
[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.
|
||||
function Wait-Log([string]$Pattern, [int]$Count = 1, [int]$TimeoutSeconds = 60, [int]$AfterMs = 500, $Game = $null) {
|
||||
$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 }
|
||||
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
|
||||
@@ -54,7 +54,7 @@ function Wait-Log([string]$Pattern, [int]$Count = 1, [int]$TimeoutSeconds = 60,
|
||||
function Start-Sacred([string]$Arguments = "log") {
|
||||
if (Get-Process sacred -ErrorAction SilentlyContinue) { throw "sacred.exe is already running" }
|
||||
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
|
||||
for ($i = 0; $i -lt 80 -and -not $game; $i++) {
|
||||
Start-Sleep -Milliseconds 250
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
//
|
||||
// Ghidra's own RecoverClassesFromRTTIScript finds nothing in this binary.
|
||||
// Run once, headless, without -readOnly (see tools/README.md).
|
||||
// @category sacred_re
|
||||
// @category sacredK
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
//
|
||||
// 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.
|
||||
// @category sacred_re
|
||||
// @category sacredK
|
||||
|
||||
import java.io.File;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
// into a class namespace; "#" starts a comment. Functions are created where
|
||||
// none starts at the address yet. Names starting with "g_" label data
|
||||
// (globals, tables) instead of functions.
|
||||
// @category sacred_re
|
||||
// @category sacredK
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
|
||||
Generated
+2
-2
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "sacred-re-tools",
|
||||
"name": "sacredk-tools",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "sacred-re-tools",
|
||||
"name": "sacredk-tools",
|
||||
"dependencies": {
|
||||
"iced-x86": "1.21.0"
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "sacred-re-tools",
|
||||
"name": "sacredk-tools",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
Reference in new issue
Block a user