Initial sacred_re scaffold with borderless fullscreen mod

- Launcher (sacred_re.exe) starts sacred.exe with sacred_re.dll injected
  via Detours; the DLL verifies the Sacred Gold 2.28 build before hooking.
- Hook framework: RE_HOOK for game addresses, RE_HOOK_API for Windows
  APIs, per-hook enable/disable in sacred_re.ini, sacred_re.log.
- Game code: Log (0x66f1c0) hooked, CmdLineHasFlag (0x813140) rewritten.
- Borderless mod: forces windowed mode, covers the monitor, scales the
  1024x768 image aspect-correct, remaps mouse input, keeps running in the
  background with keyboard input hidden while unfocused.
- CMake/Ninja/MSVC x86 build, VS Code setup, disasm tool, function map.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
khallaandClaude Opus 5.5 committed 2026-10-13 23:55:50 +02:00
commit d9f5c85430
31 files changed
+1184

No files matched your search

+102
View File
@@ -0,0 +1,102 @@
// sacred_re.exe: starts sacred.exe with sacred_re.dll injected.
//
// sacred_re.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
// launcher's own directory. Remaining arguments are passed to the game
// unchanged, e.g. `sacred_re.exe log` enables the game's DEBUG.LOG.
#include <string>
#include <windows.h>
#include <detours.h>
#include <shellapi.h>
namespace {
std::wstring ExeDir() {
wchar_t path[MAX_PATH];
DWORD length = GetModuleFileNameW(nullptr, path, MAX_PATH);
std::wstring dir(path, length);
return dir.substr(0, dir.find_last_of(L"\\/") + 1);
}
bool FileExists(const std::wstring& path) {
DWORD attributes = GetFileAttributesW(path.c_str());
return attributes != INVALID_FILE_ATTRIBUTES && !(attributes & FILE_ATTRIBUTE_DIRECTORY);
}
std::wstring WithTrailingSlash(std::wstring dir) {
if (!dir.empty() && dir.back() != L'\\' && dir.back() != L'/') {
dir += L'\\';
}
return dir;
}
int Fail(const std::wstring& message) {
MessageBoxW(nullptr, message.c_str(), L"sacred_re", MB_OK | MB_ICONERROR);
return 1;
}
} // namespace
int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) {
const std::wstring launcherDir = ExeDir();
const std::wstring ini = launcherDir + L"sacred_re.ini";
int argc = 0;
wchar_t** argv = CommandLineToArgvW(GetCommandLineW(), &argc);
std::wstring gameDir;
std::wstring gameArgs;
for (int i = 1; i < argc; ++i) {
std::wstring arg = argv[i];
if (arg == L"--game-dir" && i + 1 < argc) {
gameDir = WithTrailingSlash(argv[++i]);
WritePrivateProfileStringW(L"paths", L"GameDir", gameDir.c_str(), ini.c_str());
} else {
gameArgs += L' ';
gameArgs += arg;
}
}
LocalFree(argv);
if (gameDir.empty()) {
wchar_t configured[MAX_PATH] = {};
GetPrivateProfileStringW(L"paths", L"GameDir", L"", configured, MAX_PATH, ini.c_str());
gameDir = configured[0] ? WithTrailingSlash(configured) : launcherDir;
}
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>\"");
}
const std::wstring dllPath = launcherDir + L"sacred_re.dll";
if (!FileExists(dllPath)) {
return Fail(L"sacred_re.dll not found next to the launcher:\n" + dllPath);
}
// Detours takes the DLL path as ANSI.
char dllPathA[MAX_PATH];
if (!WideCharToMultiByte(CP_ACP, 0, dllPath.c_str(), -1, dllPathA, MAX_PATH, nullptr, nullptr)) {
return Fail(L"DLL path is not representable in the ANSI code page:\n" + dllPath);
}
std::wstring commandLine = L"\"" + gameExe + L"\"" + gameArgs;
STARTUPINFOW startup = {sizeof(startup)};
PROCESS_INFORMATION process = {};
if (!DetourCreateProcessWithDllExW(gameExe.c_str(), commandLine.data(), nullptr, nullptr, FALSE,
CREATE_DEFAULT_ERROR_MODE, nullptr, gameDir.c_str(), &startup, &process,
dllPathA, nullptr)) {
return Fail(L"Failed to start sacred.exe (error " + std::to_wstring(GetLastError()) + L").");
}
WaitForSingleObject(process.hProcess, INFINITE);
DWORD exitCode = 0;
GetExitCodeProcess(process.hProcess, &exitCode);
CloseHandle(process.hThread);
CloseHandle(process.hProcess);
return static_cast<int>(exitCode);
}