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:
commit
8a7bdfd99f
44 files changed
+2505
No files matched your search
@@ -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();
|
||||
Reference in new issue
Block a user