Files
sacred_k/tools/find-imm.mjs
T
khallaandClaude Opus 5.5 b2dc1338d2 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>
2026-10-14 00:19:52 +02:00

74 lines
2.6 KiB
JavaScript

// 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();