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:
commit
d9f5c85430
31 files changed
+1184
No files matched your search
@@ -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);
|
||||
}
|
||||
Reference in new issue
Block a user