romdevtools 0.30.0 → 0.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +12 -3
- package/CHANGELOG.md +70 -13
- package/README.md +2 -1
- package/examples/gb/templates/tile_engine.c +1 -1
- package/examples/gbc/templates/tile_engine.c +1 -1
- package/examples/genesis/templates/two_plane_parallax.c +4 -4
- package/examples/nes/templates/tile_engine.c +1 -1
- package/package.json +3 -1
- package/src/analysis/analyze.js +263 -0
- package/src/analysis/decompile.js +108 -0
- package/src/analysis/decompiler/sleigh/6502.cspec +34 -0
- package/src/analysis/decompiler/sleigh/6502.ldefs +33 -0
- package/src/analysis/decompiler/sleigh/6502.pspec +16 -0
- package/src/analysis/decompiler/sleigh/6502.sla +3414 -0
- package/src/analysis/decompiler/sleigh/65816-snes.pspec +249 -0
- package/src/analysis/decompiler/sleigh/65816.cspec +17 -0
- package/src/analysis/decompiler/sleigh/65816.ldefs +15 -0
- package/src/analysis/decompiler/sleigh/65816.sla +23670 -0
- package/src/analysis/decompiler/sleigh/65c02.sla +4683 -0
- package/src/analysis/decompiler/sleigh/68000.cspec +67 -0
- package/src/analysis/decompiler/sleigh/68000.ldefs +68 -0
- package/src/analysis/decompiler/sleigh/68000.pspec +9 -0
- package/src/analysis/decompiler/sleigh/68000_register.cspec +118 -0
- package/src/analysis/decompiler/sleigh/68040.sla +81693 -0
- package/src/analysis/decompiler/sleigh/ARM.ldefs +377 -0
- package/src/analysis/decompiler/sleigh/ARM4t_le.sla +53758 -0
- package/src/analysis/decompiler/sleigh/ARM_v45.cspec +209 -0
- package/src/analysis/decompiler/sleigh/ARM_v45.pspec +40 -0
- package/src/analysis/decompiler/sleigh/ARMt_v45.pspec +43 -0
- package/src/analysis/decompiler/sleigh/HuC6280.cspec +67 -0
- package/src/analysis/decompiler/sleigh/HuC6280.ldefs +18 -0
- package/src/analysis/decompiler/sleigh/HuC6280.pspec +150 -0
- package/src/analysis/decompiler/sleigh/HuC6280.sla +7524 -0
- package/src/analysis/decompiler/sleigh/sm83.cspec +70 -0
- package/src/analysis/decompiler/sleigh/sm83.ldefs +29 -0
- package/src/analysis/decompiler/sleigh/sm83.pspec +19 -0
- package/src/analysis/decompiler/sleigh/sm83.sla +7695 -0
- package/src/analysis/decompiler/sleigh/z80.cspec +122 -0
- package/src/analysis/decompiler/sleigh/z80.ldefs +57 -0
- package/src/analysis/decompiler/sleigh/z80.pspec +43 -0
- package/src/analysis/decompiler/sleigh/z80.sla +20518 -0
- package/src/analysis/decompiler/wasm/decompile.js +2 -0
- package/src/analysis/decompiler/wasm/decompile.wasm +0 -0
- package/src/analysis/rizin.js +129 -0
- package/src/analysis/wasm/rizin.js +6032 -0
- package/src/analysis/wasm/rizin.wasm +0 -0
- package/src/http/routes.js +1 -1
- package/src/http/skill-doc.js +1 -1
- package/src/mcp/tools/cart-parts.js +5 -2
- package/src/mcp/tools/disasm.js +32 -5
- package/src/mcp/tools/font-map.js +3 -3
- package/src/mcp/tools/index.js +2 -2
- package/src/mcp/tools/project.js +1 -1
- package/src/mcp/tools/reinject.js +1 -1
- package/src/mcp/tools/symbols.js +10 -4
- package/src/mcp/tools/trace-vram-source.js +1 -1
- package/src/mcp/tools/watch-memory.js +1 -1
- package/src/platforms/_guides/ROMHACKING_PLAYBOOK.md +48 -3
- package/src/platforms/atari2600/MENTAL_MODEL.md +6 -0
- package/src/platforms/atari7800/MENTAL_MODEL.md +6 -0
- package/src/platforms/c64/MENTAL_MODEL.md +6 -0
- package/src/platforms/gb/MENTAL_MODEL.md +6 -0
- package/src/platforms/gb/lib/c/README.md +1 -1
- package/src/platforms/gba/MENTAL_MODEL.md +7 -1
- package/src/platforms/gbc/MENTAL_MODEL.md +6 -0
- package/src/platforms/gbc/lib/c/README.md +1 -1
- package/src/platforms/genesis/MENTAL_MODEL.md +8 -2
- package/src/platforms/genesis/TROUBLESHOOTING.md +2 -2
- package/src/platforms/genesis/lib/wram.s +1 -1
- package/src/platforms/gg/MENTAL_MODEL.md +6 -0
- package/src/platforms/lynx/MENTAL_MODEL.md +6 -0
- package/src/platforms/msx/MENTAL_MODEL.md +6 -0
- package/src/platforms/nes/MENTAL_MODEL.md +6 -0
- package/src/platforms/pce/MENTAL_MODEL.md +6 -0
- package/src/platforms/sms/MENTAL_MODEL.md +6 -0
- package/src/platforms/snes/MENTAL_MODEL.md +10 -4
- package/src/toolchains/_worker/wasm-worker.js +5 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// decompile.js — drive the Ghidra decompiler WASM (romdev-analysis-decompiler)
|
|
2
|
+
// to turn a function into C pseudocode. Runs the decompiler's REPL one-shot
|
|
3
|
+
// through the isolated worker pool: mount the SLEIGH home + the ROM image,
|
|
4
|
+
// feed `load file <langid> <rom>; map function <addr>; decompile; print C`.
|
|
5
|
+
//
|
|
6
|
+
// The ROM is loaded as a RAW binary at VMA 0 — so the address passed must be a
|
|
7
|
+
// FILE OFFSET into the image we hand it, not a banked CPU address. Callers that
|
|
8
|
+
// have a CPU address use the disasm mappers to slice the right bank first; for
|
|
9
|
+
// the common flat/first-bank case the file offset equals the CPU address minus
|
|
10
|
+
// the platform's load base (handled in analyze.js).
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import fs from "node:fs";
|
|
14
|
+
import { runIsolated } from "../toolchains/_worker/run.js";
|
|
15
|
+
|
|
16
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
|
|
18
|
+
/** Resolve the decompiler package (or the gitignored src staging fallback). */
|
|
19
|
+
function decompilerPaths() {
|
|
20
|
+
let base;
|
|
21
|
+
try {
|
|
22
|
+
base = path.dirname(fileURLToPath(import.meta.resolve("romdev-analysis-decompiler")));
|
|
23
|
+
} catch { base = null; }
|
|
24
|
+
const candidates = [
|
|
25
|
+
base && { js: path.join(base, "wasm", "decompile.js"), sleigh: path.join(base, "sleigh") },
|
|
26
|
+
{ js: path.join(__dirname, "decompiler", "wasm", "decompile.js"), sleigh: path.join(__dirname, "decompiler", "sleigh") },
|
|
27
|
+
].filter(Boolean);
|
|
28
|
+
for (const c of candidates) if (fs.existsSync(c.js)) return c;
|
|
29
|
+
throw new Error(
|
|
30
|
+
"decompiler not found: install romdev-analysis-decompiler or run scripts/build-decompiler.sh"
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** romdev platform → Ghidra SLEIGH language id. null = not decompilable yet. */
|
|
35
|
+
export const SLEIGH_LANGID = {
|
|
36
|
+
nes: "6502:LE:16:default",
|
|
37
|
+
atari2600: "6502:LE:16:default",
|
|
38
|
+
atari7800: "6502:LE:16:default",
|
|
39
|
+
c64: "6502:LE:16:default",
|
|
40
|
+
lynx: "65C02:LE:16:default",
|
|
41
|
+
sms: "z80:LE:16:default",
|
|
42
|
+
gg: "z80:LE:16:default",
|
|
43
|
+
msx: "z80:LE:16:default",
|
|
44
|
+
gb: "SM83:LE:16:default",
|
|
45
|
+
gbc: "SM83:LE:16:default",
|
|
46
|
+
gba: "ARM:LE:32:v4t",
|
|
47
|
+
genesis: "68000:BE:32:default",
|
|
48
|
+
snes: "65816:LE:24:snes",
|
|
49
|
+
pce: "HuC6280:LE:16:default",
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Decompile the function at `fileOffset` in `romBytes` for `platform`.
|
|
54
|
+
* @returns {{platform, langid, address, code, warnings:string[], raw:string}}
|
|
55
|
+
*/
|
|
56
|
+
export async function decompileFunction({ platform, romBytes, fileOffset, name = "fn_target" }) {
|
|
57
|
+
const langid = SLEIGH_LANGID[platform];
|
|
58
|
+
if (!langid) throw new Error(`decompile: no SLEIGH language for platform '${platform}'`);
|
|
59
|
+
const { js, sleigh } = decompilerPaths();
|
|
60
|
+
const addr = "0x" + (fileOffset >>> 0).toString(16);
|
|
61
|
+
|
|
62
|
+
// REPL script. RawBinary loads at vma 0; `print C` emits the pseudocode.
|
|
63
|
+
const script = [
|
|
64
|
+
`load file ${langid} /work/rom.bin`,
|
|
65
|
+
`map function ${addr} ${name}`,
|
|
66
|
+
`decompile ${name}`,
|
|
67
|
+
`print C`,
|
|
68
|
+
`quit`,
|
|
69
|
+
"",
|
|
70
|
+
].join("\n");
|
|
71
|
+
|
|
72
|
+
const res = await runIsolated({
|
|
73
|
+
gluePath: js,
|
|
74
|
+
argv: [],
|
|
75
|
+
env: { SLEIGHHOME: "/sleigh" },
|
|
76
|
+
stdinText: script,
|
|
77
|
+
hostDirMounts: [{ hostDir: sleigh, vfsDir: "/sleigh" }],
|
|
78
|
+
inputFiles: [{
|
|
79
|
+
vfsPath: "/work/rom.bin",
|
|
80
|
+
encoding: "base64",
|
|
81
|
+
data: Buffer.from(romBytes).toString("base64"),
|
|
82
|
+
}],
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const raw = res.log ?? "";
|
|
86
|
+
// The decompiler echoes each prompt; the C body follows `print C`.
|
|
87
|
+
const code = extractC(raw);
|
|
88
|
+
const warnings = [...raw.matchAll(/\/\* WARNING: (.+?) \*\//g)].map((m) => m[1]);
|
|
89
|
+
if (!code) {
|
|
90
|
+
throw new Error(`decompile produced no C output (exit=${res.exitCode}): ${raw.slice(-400)}`);
|
|
91
|
+
}
|
|
92
|
+
return { platform, langid, address: fileOffset, addressHex: addr, code, warnings, raw };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Pull the C function text out of the REPL transcript (between `print C` and
|
|
96
|
+
* the next `[decomp]>` prompt). */
|
|
97
|
+
function extractC(transcript) {
|
|
98
|
+
const lines = transcript.split("\n");
|
|
99
|
+
const start = lines.findIndex((l) => /\[decomp\]>\s*print C\b/.test(l));
|
|
100
|
+
if (start === -1) return null;
|
|
101
|
+
const body = [];
|
|
102
|
+
for (let i = start + 1; i < lines.length; i++) {
|
|
103
|
+
if (/^\[decomp\]>/.test(lines[i])) break;
|
|
104
|
+
body.push(lines[i]);
|
|
105
|
+
}
|
|
106
|
+
const text = body.join("\n").trim();
|
|
107
|
+
return text.length ? text : null;
|
|
108
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
|
|
3
|
+
<compiler_spec>
|
|
4
|
+
<global>
|
|
5
|
+
<range space="RAM"/>
|
|
6
|
+
</global>
|
|
7
|
+
<stackpointer register="SP" space="RAM" growth="negative"/>
|
|
8
|
+
<returnaddress>
|
|
9
|
+
<varnode space="stack" offset="1" size="2"/>
|
|
10
|
+
</returnaddress>
|
|
11
|
+
<default_proto>
|
|
12
|
+
<prototype name="__stdcall" extrapop="2" stackshift="2" strategy="register">
|
|
13
|
+
<input>
|
|
14
|
+
<pentry minsize="1" maxsize="1">
|
|
15
|
+
<register name="A"/>
|
|
16
|
+
</pentry>
|
|
17
|
+
<pentry minsize="1" maxsize="1">
|
|
18
|
+
<register name="X"/>
|
|
19
|
+
</pentry>
|
|
20
|
+
<pentry minsize="1" maxsize="1">
|
|
21
|
+
<register name="Y"/>
|
|
22
|
+
</pentry>
|
|
23
|
+
</input>
|
|
24
|
+
<output>
|
|
25
|
+
<pentry minsize="1" maxsize="1">
|
|
26
|
+
<register name="A"/>
|
|
27
|
+
</pentry>
|
|
28
|
+
</output>
|
|
29
|
+
<unaffected>
|
|
30
|
+
<register name="SP"/>
|
|
31
|
+
</unaffected>
|
|
32
|
+
</prototype>
|
|
33
|
+
</default_proto>
|
|
34
|
+
</compiler_spec>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
|
|
3
|
+
<language_definitions>
|
|
4
|
+
|
|
5
|
+
<language processor="6502"
|
|
6
|
+
endian="little"
|
|
7
|
+
size="16"
|
|
8
|
+
variant="default"
|
|
9
|
+
version="1.0"
|
|
10
|
+
slafile="6502.sla"
|
|
11
|
+
processorspec="6502.pspec"
|
|
12
|
+
manualindexfile="../manuals/6502.idx"
|
|
13
|
+
id="6502:LE:16:default">
|
|
14
|
+
<description>6502 Microcontroller Family</description>
|
|
15
|
+
<compiler name="default" spec="6502.cspec" id="default"/>
|
|
16
|
+
<external_name tool="IDA-PRO" name="m6502"/>
|
|
17
|
+
</language>
|
|
18
|
+
|
|
19
|
+
<language processor="65C02"
|
|
20
|
+
endian="little"
|
|
21
|
+
size="16"
|
|
22
|
+
variant="default"
|
|
23
|
+
version="1.0"
|
|
24
|
+
slafile="65c02.sla"
|
|
25
|
+
processorspec="6502.pspec"
|
|
26
|
+
manualindexfile="../manuals/65c02.idx"
|
|
27
|
+
id="65C02:LE:16:default">
|
|
28
|
+
<description>65C02 Microcontroller Family</description>
|
|
29
|
+
<compiler name="default" spec="6502.cspec" id="default"/>
|
|
30
|
+
<external_name tool="IDA-PRO" name="m65c02"/>
|
|
31
|
+
</language>
|
|
32
|
+
|
|
33
|
+
</language_definitions>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
|
|
3
|
+
<processor_spec>
|
|
4
|
+
<programcounter register="PC"/>
|
|
5
|
+
|
|
6
|
+
<default_symbols>
|
|
7
|
+
<symbol name="NMI" address="FFFA" entry="true" type="code_ptr"/>
|
|
8
|
+
<symbol name="RES" address="FFFC" entry="true" type="code_ptr"/>
|
|
9
|
+
<symbol name="IRQ" address="FFFE" entry="true" type="code_ptr"/>
|
|
10
|
+
</default_symbols>
|
|
11
|
+
|
|
12
|
+
<default_memory_blocks>
|
|
13
|
+
<memory_block name="ZERO_PAGE" start_address="0x0000" length="0x0100" initialized="false"/>
|
|
14
|
+
<memory_block name="STACK" start_address="0x0100" length="0x0100" initialized="false"/>
|
|
15
|
+
</default_memory_blocks>
|
|
16
|
+
</processor_spec>
|