Widescreen experiment: native render size via dxwrapper, classic menus

- mods/widescreen: redirect the world view's reads of the 1024.0/768.0
  constants and the 3D ortho extents to our variables; render size =
  monitor; relocate the world view's tile draw list (1750 entries, no
  bounds check) into an enlarged object (16384 entries).
- mods/dxwrapper: route DirectDraw through dxwrapper (Dd7to9, D3D9) since
  Windows' D3D7 cannot render to targets larger than 2048; fix the game's
  device info GUID pointer that dxwrapper's enumeration leaves invalid.
  Pinned release fetched by CMake (hash-checked), installed next to the DLL.
- Main menu (UI thread) and loading screen render in a 1024x768 viewport
  and are presented scaled ("classic" layout); in-game uses the full size.
- re/patch: verified code patches (operand redirect, push immediates,
  displacements) and startup hooks; build.ps1/CMake download fixes.

In-game view untested yet; HUD still at the top-left.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
khallaandClaude Opus 5.5 committed 2026-10-14 00:47:03 +02:00
1 parent c8dc689bc6
commit 795fe8f087
19 files changed
+860 -28

No files matched your search

+4
View File
@@ -23,6 +23,10 @@ const std::wstring& ConfigPath() {
return g_path;
}
std::wstring ModuleDirectory() {
return g_path.substr(0, g_path.find_last_of(L"\\/") + 1);
}
int ConfigInt(const char* section, const char* key, int fallback) {
if (g_path.empty()) {
return fallback;
+2
View File
@@ -7,6 +7,8 @@ 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.
std::wstring ModuleDirectory();
int ConfigInt(const char* section, const char* key, int fallback);
} // namespace re
+2
View File
@@ -10,6 +10,7 @@
#include "re/config.h"
#include "re/hook.h"
#include "re/log.h"
#include "re/patch.h"
namespace {
@@ -58,6 +59,7 @@ void Startup(HMODULE module) {
re::Log(IsDebuggerPresent() ? "debugger attached" : "no debugger, continuing");
}
re::InstallHooks();
re::RunStartupFunctions();
}
} // namespace
+81
View File
@@ -0,0 +1,81 @@
#include "re/patch.h"
#include <cstring>
#include <vector>
#include <windows.h>
#include "re/log.h"
namespace re {
namespace {
struct Startup {
const char* name;
void (*function)();
};
std::vector<Startup>& Startups() {
static std::vector<Startup> startups;
return startups;
}
// Longest x86 instruction is 15 bytes; an absolute operand starts within it.
constexpr size_t kOperandSearch = 11;
} // namespace
bool WriteCode(uint32_t address, const void* bytes, size_t size) {
void* target = reinterpret_cast<void*>(static_cast<uintptr_t>(address));
DWORD oldProtect;
if (!VirtualProtect(target, size, PAGE_EXECUTE_READWRITE, &oldProtect)) {
Log("patch 0x%08x: VirtualProtect failed (%lu)", address, GetLastError());
return false;
}
std::memcpy(target, bytes, size);
VirtualProtect(target, size, oldProtect, &oldProtect);
FlushInstructionCache(GetCurrentProcess(), target, size);
return true;
}
bool PatchInstructionDword(uint32_t instruction, uint32_t from, uint32_t to) {
const auto* code = reinterpret_cast<const uint8_t*>(static_cast<uintptr_t>(instruction));
for (size_t offset = 1; offset + 4 <= kOperandSearch; ++offset) {
uint32_t operand;
std::memcpy(&operand, code + offset, 4);
if (operand == from) {
return WriteCode(instruction + static_cast<uint32_t>(offset), &to, 4);
}
}
Log("patch 0x%08x: operand 0x%08x not found, skipped", instruction, from);
return false;
}
bool RedirectOperand(uint32_t instruction, uint32_t from, const void* to) {
return PatchInstructionDword(instruction, from, static_cast<uint32_t>(reinterpret_cast<uintptr_t>(to)));
}
bool PatchPushImm32(uint32_t instruction, uint32_t expected, uint32_t value) {
const auto* code = reinterpret_cast<const uint8_t*>(static_cast<uintptr_t>(instruction));
uint32_t current;
std::memcpy(&current, code + 1, 4);
if (code[0] != 0x68 || current != expected) {
Log("patch 0x%08x: expected push 0x%08x, found %02x %08x, skipped", instruction, expected, code[0],
current);
return false;
}
return WriteCode(instruction + 1, &value, 4);
}
StartupRegistration::StartupRegistration(const char* name, void (*function)()) {
Startups().push_back({name, function});
}
void RunStartupFunctions() {
for (const Startup& startup : Startups()) {
Log("startup: %s", startup.name);
startup.function();
}
}
} // namespace re
+33
View File
@@ -0,0 +1,33 @@
#pragma once
// Verified in-place patches of sacred.exe code. Every function checks that the
// bytes it expects are present before writing, logs and refuses otherwise.
#include <cstddef>
#include <cstdint>
namespace re {
bool WriteCode(uint32_t address, const void* bytes, size_t size);
// The instruction at `instruction` has the absolute memory operand `from`
// (e.g. `fmul st, dword ptr [from]`); make it read `to` instead.
bool RedirectOperand(uint32_t instruction, uint32_t from, const void* to);
// Replace a 32-bit displacement/immediate `from` inside the instruction at
// `instruction` by `to` (e.g. a struct field offset in `[ebp+ecx*8+3FF2Ch]`).
bool PatchInstructionDword(uint32_t instruction, uint32_t from, uint32_t to);
// `push imm32` (68 xx xx xx xx) at `instruction`: replace `expected` by `value`.
bool PatchPushImm32(uint32_t instruction, uint32_t expected, uint32_t value);
// Code that must run once at startup, after the hooks are installed.
class StartupRegistration {
public:
StartupRegistration(const char* name, void (*function)());
};
void RunStartupFunctions();
} // namespace re
#define RE_STARTUP(name, function) \
static ::re::StartupRegistration name##_startup(#name, &function)