Framework for Sacred 2 Gold, game log mirror, borderless fullscreen

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>
This commit is contained in:
khallaandClaude Opus 5.5 committed 2026-10-31 01:39:44 +01:00
commit 8a7bdfd99f
44 files changed
+2505

No files matched your search

+60
View File
@@ -0,0 +1,60 @@
// Disassemble a game module (sacred2.exe, s2logic.dll, ...) at a virtual
// address (as in Ghidra: the module's preferred image base), independent of
// Ghidra's function boundaries (useful for code only reached via pointers).
//
// node tools/disasm.mjs <module> <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();