Map the world view sizing; add RE tooling
- docs/widescreen.md: render-thread frame, cWorldView0 layout and methods, view size = zoom * 1024.0/768.0 float constants and all their users, ortho projection extents, open questions (activity radius, buffers). - tools: find-imm (immediate/regex instruction search), rtti (vtables via MSVC RTTI), decompile.ps1 + Ghidra scripts for a persistent project; ApplyRtti recovers 410 vtables / ~1200 functions Ghidra misses. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
1549464af6
commit
b2dc1338d2
8 files changed
+493
No files matched your search
@@ -0,0 +1,73 @@
|
||||
// List every instruction in sacred.exe's .text that uses one of the given
|
||||
// immediate values (decimal or 0x-hex), e.g. to find hardcoded resolutions.
|
||||
//
|
||||
// node tools/find-imm.mjs <exe> 1024 768 [...]
|
||||
// node tools/find-imm.mjs <exe> /96AB0h\]/ (regex on instruction text)
|
||||
//
|
||||
// Output: one line per hit, "address instruction".
|
||||
|
||||
import { readFileSync } from "node:fs";
|
||||
import { Decoder, DecoderOptions, Formatter, FormatterSyntax, OpKind } from "iced-x86";
|
||||
|
||||
const [exePath, ...valueTexts] = process.argv.slice(2);
|
||||
if (!exePath || valueTexts.length === 0) {
|
||||
console.error("usage: node find-imm.mjs <exe> <value> [value...]");
|
||||
process.exit(1);
|
||||
}
|
||||
const textPattern = valueTexts.length === 1 && /^\/.*\/$/.test(valueTexts[0])
|
||||
? new RegExp(valueTexts[0].slice(1, -1), "i")
|
||||
: null;
|
||||
const wanted = textPattern ? new Set() : new Set(valueTexts.map((t) => BigInt(Number(t))));
|
||||
|
||||
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;
|
||||
|
||||
let text = null;
|
||||
for (let i = 0; i < sectionCount; i++) {
|
||||
const s = sectionTable + 40 * i;
|
||||
const name = file.toString("latin1", s, s + 8).replace(/\0+$/, "");
|
||||
if (name === ".text") {
|
||||
text = {
|
||||
virtualAddress: file.readUInt32LE(s + 12),
|
||||
rawSize: file.readUInt32LE(s + 16),
|
||||
rawOffset: file.readUInt32LE(s + 20),
|
||||
};
|
||||
}
|
||||
}
|
||||
if (!text) throw new Error("no .text section");
|
||||
|
||||
const bytes = file.subarray(text.rawOffset, text.rawOffset + text.rawSize);
|
||||
const decoder = new Decoder(32, bytes, DecoderOptions.None);
|
||||
decoder.ip = BigInt(imageBase + text.virtualAddress);
|
||||
const formatter = new Formatter(FormatterSyntax.Intel);
|
||||
|
||||
const immediateKinds = new Set([
|
||||
OpKind.Immediate8, OpKind.Immediate16, OpKind.Immediate32,
|
||||
OpKind.Immediate8to16, OpKind.Immediate8to32,
|
||||
]);
|
||||
|
||||
// Linear sweep: data embedded in .text can produce a few bogus hits.
|
||||
while (decoder.canDecode) {
|
||||
const instruction = decoder.decode();
|
||||
if (textPattern) {
|
||||
const text = formatter.format(instruction);
|
||||
if (textPattern.test(text)) {
|
||||
console.log(`${Number(instruction.ip).toString(16).padStart(8, "0")} ${text}`);
|
||||
}
|
||||
instruction.free();
|
||||
continue;
|
||||
}
|
||||
for (let op = 0; op < instruction.opCount; op++) {
|
||||
if (immediateKinds.has(instruction.opKind(op)) && wanted.has(instruction.immediate(op) & 0xffffffffn)) {
|
||||
console.log(`${Number(instruction.ip).toString(16).padStart(8, "0")} ${formatter.format(instruction)}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
instruction.free();
|
||||
}
|
||||
decoder.free();
|
||||
formatter.free();
|
||||
Reference in new issue
Block a user