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,59 @@
|
||||
// Disassemble sacred.exe at a virtual address, independent of Ghidra's
|
||||
// function boundaries (useful for code only reached via function pointers).
|
||||
//
|
||||
// node tools/disasm.mjs <exe> <va> [count=60]
|
||||
//
|
||||
// Stops after `count` instructions or at the first RET/INT3 run, whichever
|
||||
// comes later than 8 instructions.
|
||||
|
||||
import { readFileSync } from "node:fs";
|
||||
import { Decoder, DecoderOptions, Formatter, FormatterSyntax, Mnemonic } from "iced-x86";
|
||||
|
||||
const [exePath, vaText, countText = "60"] = process.argv.slice(2);
|
||||
if (!exePath || !vaText) {
|
||||
console.error("usage: node disasm.mjs <exe> <va> [count]");
|
||||
process.exit(1);
|
||||
}
|
||||
const va = Number.parseInt(vaText, 16);
|
||||
const count = Number.parseInt(countText, 10);
|
||||
|
||||
const file = readFileSync(exePath);
|
||||
const peOffset = file.readUInt32LE(0x3c);
|
||||
const sectionCount = file.readUInt16LE(peOffset + 6);
|
||||
const optionalSize = file.readUInt16LE(peOffset + 20);
|
||||
const imageBase = file.readUInt32LE(peOffset + 24 + 28);
|
||||
const sectionTable = peOffset + 24 + optionalSize;
|
||||
|
||||
function fileOffset(address) {
|
||||
const rva = address - imageBase;
|
||||
for (let i = 0; i < sectionCount; i++) {
|
||||
const s = sectionTable + 40 * i;
|
||||
const virtualSize = file.readUInt32LE(s + 8);
|
||||
const virtualAddress = file.readUInt32LE(s + 12);
|
||||
const rawOffset = file.readUInt32LE(s + 20);
|
||||
if (rva >= virtualAddress && rva < virtualAddress + virtualSize) {
|
||||
return rawOffset + rva - virtualAddress;
|
||||
}
|
||||
}
|
||||
throw new Error(`address 0x${address.toString(16)} is not in a section`);
|
||||
}
|
||||
|
||||
const start = fileOffset(va);
|
||||
const bytes = file.subarray(start, start + count * 15);
|
||||
const decoder = new Decoder(32, bytes, DecoderOptions.None);
|
||||
decoder.ip = BigInt(va);
|
||||
const formatter = new Formatter(FormatterSyntax.Intel);
|
||||
formatter.firstOperandCharIndex = 8;
|
||||
|
||||
for (let n = 0; n < count && decoder.canDecode; n++) {
|
||||
const instruction = decoder.decode();
|
||||
const address = Number(instruction.ip).toString(16).padStart(8, "0");
|
||||
const offset = Number(instruction.ip) - va;
|
||||
const raw = [...bytes.subarray(offset, offset + instruction.length)]
|
||||
.map((b) => b.toString(16).padStart(2, "0"))
|
||||
.join(" ");
|
||||
console.log(`${address} ${raw.padEnd(30)} ${formatter.format(instruction)}`);
|
||||
instruction.free();
|
||||
}
|
||||
decoder.free();
|
||||
formatter.free();
|
||||
Reference in new issue
Block a user