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:
1 parent
c8dc689bc6
commit
795fe8f087
19 files changed
+860
-28
No files matched your search
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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(¤t, 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
|
||||
@@ -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)
|
||||
Reference in new issue
Block a user