Ported from sacredK with module-relative addresses, hooks by exported name and a SHA-256 check of the six game modules (Community Patch 2.65.1). Borderless windowed at monitor size replaces exclusive fullscreen, whose focus loss during loading made the game quit and crash on exit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
89 lines
3.3 KiB
C++
89 lines
3.3 KiB
C++
#pragma once
|
|
// Verified in-place patches of game code. Every function checks that the
|
|
// bytes it expects are present before writing, logs and refuses otherwise.
|
|
//
|
|
// Locations are module addresses (re::Address, as in Ghidra). Absolute
|
|
// addresses inside the code (operands, call targets) were relocated with
|
|
// their module, so they are given as re::Address too; plain immediates and
|
|
// struct offsets stay uint32_t.
|
|
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
|
|
#include "re/module.h"
|
|
|
|
namespace re {
|
|
|
|
// Writes to a running address (e.g. one already resolved).
|
|
bool WriteCode(uintptr_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(Address instruction, Address 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(Address instruction, uint32_t from, uint32_t to);
|
|
|
|
// Replace the byte `from` at `instruction + offset` (an imm8 or disp8) by `to`.
|
|
bool PatchInstructionByte(Address instruction, size_t offset, uint8_t from, uint8_t to);
|
|
|
|
// Replace the code bytes `from` at `address` by `to` (same length), e.g. a
|
|
// whole short instruction. The bytes must not contain relocated addresses.
|
|
bool ReplaceCode(Address address, const void* from, const void* to, size_t size);
|
|
|
|
// The `call rel32` at `instruction` (E8) calls `from`; make it call `to`.
|
|
bool RedirectCall(Address instruction, Address from, const void* to);
|
|
|
|
// The near conditional jump at `instruction` (0F 8x rel32) goes to `from`;
|
|
// make it go to `to`.
|
|
bool RedirectJcc(Address instruction, Address from, const void* to);
|
|
|
|
// A code replacement, for tables.
|
|
struct CodeReplacement {
|
|
Address address;
|
|
uint8_t from[8];
|
|
uint8_t to[8];
|
|
uint8_t size;
|
|
};
|
|
int ReplaceAll(const CodeReplacement* replacements, size_t count);
|
|
template <size_t N>
|
|
int ReplaceAll(const CodeReplacement (&replacements)[N]) {
|
|
return ReplaceAll(replacements, N);
|
|
}
|
|
|
|
// One byte operand of an instruction, for tables of byte patches.
|
|
struct BytePatch {
|
|
Address instruction;
|
|
uint8_t offset; // of the byte within the instruction
|
|
uint8_t from;
|
|
};
|
|
|
|
// Apply the same change to a table of instructions; return the number that
|
|
// failed (each failure is logged).
|
|
int PatchAll(const BytePatch* patches, size_t count, uint8_t to);
|
|
int PatchAll(const Address* instructions, size_t count, uint32_t from, uint32_t to);
|
|
template <size_t N>
|
|
int PatchAll(const BytePatch (&patches)[N], uint8_t to) {
|
|
return PatchAll(patches, N, to);
|
|
}
|
|
template <size_t N>
|
|
int PatchAll(const Address (&instructions)[N], uint32_t from, uint32_t to) {
|
|
return PatchAll(instructions, N, from, to);
|
|
}
|
|
|
|
// `push imm32` (68 xx xx xx xx) at `instruction`: replace `expected` by `value`.
|
|
bool PatchPushImm32(Address 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)
|