romdevtools 0.40.0 → 0.40.1

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/CHANGELOG.md CHANGED
@@ -4,6 +4,25 @@ All notable changes to `romdevtools`. Dates are release dates.
4
4
  (Published as `romdev-mcp` through 0.11.0; renamed to `romdevtools` in 0.13.0 —
5
5
  the `romdev-mcp` bin is kept as an alias.)
6
6
 
7
+ ## 0.40.1 — 2026-06-11
8
+
9
+ ### Fixed — Genesis `disasm({target:'decompile'})` was shifted +0x200
10
+
11
+ A Genesis decompile at a caller-supplied address silently returned the function
12
+ 0x200 bytes too low (the wrong one, or an empty `{ return; }` / `halt_baddata()`),
13
+ with no warning. `cfg` / `xrefs` / `functions` on the same address were correct —
14
+ only `decompile` was off.
15
+
16
+ Root cause: Rizin's Mega Drive loader splits a flat `.bin` into vtable / header /
17
+ text segments and reports a non-zero address delta (`0x200`) on the code segment;
18
+ the decompiler's address mapping honored that delta, but the raw image handed to
19
+ Ghidra loads flat at offset 0, so the two disagreed by exactly 0x200. Fix: flat-
20
+ cartridge platforms (Genesis, SMS, Game Gear, MSX, Game Boy / GBC) now force
21
+ file-offset == CPU-address and ignore Rizin's segment delta. The 6502-family
22
+ platforms were unaffected (they use a separate base-address path). Regression
23
+ test added. (No change to the `romdev-analysis` / `romdev-analysis-decompiler`
24
+ packages — the fix is entirely in the address-mapping JS.)
25
+
7
26
  ## 0.40.0 — 2026-06-11
8
27
 
9
28
  ### Reverse-engineering analysis engine — control-flow graphs, deep xrefs, function detection, and a decompiler
package/README.md CHANGED
@@ -6,7 +6,15 @@ The entry point for **romdev** — vibe-code real retro games. Build, run, inspe
6
6
  npx romdevtools
7
7
  ```
8
8
 
9
- That's it — one command starts the local romdev **tool server** (no global install, no host compiler/emulator). Point any coding agent at it three ways:
9
+ **What you get:**
10
+
11
+ - **Build** — bundled per-platform toolchains (cc65, SDCC, RGBDS, asar, vasm, SGDK, PVSnesLib, libtonc, …) as WASM. Write source, compile, get a real ROM.
12
+ - **Run + see + drive** — load the ROM into an emulated console (libretro cores as WASM), step frames, screenshot, script controller input.
13
+ - **Inspect + romhack** — read CPU/video/save RAM, watch memory, write-breakpoints, the Cheat-Engine value-search loop, a bundled cheat database, mapper-aware disassembly, and a byte-exact rebuildable-project disassembler.
14
+ - **Reverse-engineering analysis engine (all 14 platforms)** — control-flow graphs, deep cross-references, auto-detected functions, a one-shot structural map, and a Ghidra **decompiler** (C-like pseudocode): `disasm({target:'cfg'|'xrefs'|'functions'|'decompile'})` and `symbols({op:'analyze'})`. Understand *how* a routine works before you touch it — no $3,000 IDA license, no install.
15
+ - **Convert assets** — PNG → platform tiles/tilemaps, quantize-to-palette, audio importers (BRR for SNES, XGM2 PCM for Genesis).
16
+
17
+ Point any coding agent at it three ways:
10
18
 
11
19
  - **Plain HTTP** — `POST http://127.0.0.1:7331/tool/{name}`; browse/try every tool at `/documentation`.
12
20
  - **Agent Skill** — `GET /skills/romdev/SKILL.md` (the [Agent Skills](https://agentskills.io) standard; save it to your skills dir as `skills/romdev/SKILL.md`; ~100 tokens until invoked).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "romdevtools",
3
- "version": "0.40.0",
3
+ "version": "0.40.1",
4
4
  "description": "Tool server giving coding agents full control of homebrew ROM development AND reverse-engineering/romhacking across 14 retro platforms (NES, SNES, GB, Genesis, Atari, C64, PC Engine, MSX, ...) via WASM toolchains + emulator cores. Use over plain HTTP, as an Agent Skill, or as an MCP server.",
5
5
  "type": "module",
6
6
  "main": "src/mcp/server.js",
@@ -178,7 +178,20 @@ export async function analyzeStructure(romPath, platformOverride) {
178
178
  * entry carries {from (vaddr base), delta (vaddr-paddr), to}. Returns
179
179
  * { paddr, vbase } where paddr = vaddr-delta is the raw-file offset and vbase
180
180
  * (= delta) is the address byte 0 of the file maps to on the CPU bus. */
181
- async function vaMapping(romBytes, arch, bits, vaddr) {
181
+ /** Platforms whose cartridge maps 1:1 to the CPU bus (file offset == CPU
182
+ * address, base 0). For these we DISTRUST Rizin's IO-map delta: some of Rizin's
183
+ * loaders (notably the Mega Drive loader) split the image into vtable/header/
184
+ * text SEGMENTS and report a non-zero delta on the code segment (e.g. 0x200 for
185
+ * Genesis), but the raw file we hand the decompiler loads flat at VMA 0 — so the
186
+ * vaddr IS the file offset and any delta is a lie for our purposes. Forcing
187
+ * identity here fixes the "+0x200 shifted decompile" bug (a code vaddr would
188
+ * otherwise resolve to vaddr-0x200, the WRONG function). */
189
+ export const FLAT_CPU_MAP = new Set(["genesis", "sms", "gg", "msx", "gb", "gbc"]);
190
+
191
+ export async function vaMapping(romBytes, arch, bits, vaddr, platform) {
192
+ // Flat-cartridge platforms: file offset == CPU address. Ignore Rizin's
193
+ // segment deltas entirely.
194
+ if (FLAT_CPU_MAP.has(platform)) return { paddr: vaddr, vbase: 0 };
182
195
  let maps;
183
196
  try {
184
197
  maps = await runRizinJson({ romBytes, arch, bits, commands: "omlj" });
@@ -208,7 +221,7 @@ export async function analyzeDecompile(romPath, address, platformOverride) {
208
221
  // decompiler's job via SLEIGH) — its flat image bases at 0 either way.
209
222
  const arch = RIZIN_ARCH[platform] ?? "6502";
210
223
  const bits = { arm: 32, m68k: 32, snes: 16 }[arch];
211
- const { paddr, vbase } = await vaMapping(romBytes, arch, bits, address);
224
+ const { paddr, vbase } = await vaMapping(romBytes, arch, bits, address, platform);
212
225
  if (paddr < 0 || paddr >= romBytes.length) {
213
226
  throw new Error(
214
227
  `decompile: address ${hx(address)} maps to file offset ${paddr}, outside the ` +
@@ -19,6 +19,7 @@ import { toolJsonSchema } from "./tool-registry.js";
19
19
  export const mcpPreamble = [
20
20
  "romdev: homebrew retro game development + reverse-engineering for coding agents.",
21
21
  "All ~32 tools register at session init — call any by name directly, no loading step. Each is a domain VERB with an operation axis: memory({op}), build({output}), breakpoint({on}), cpu({op}), sprites({op}), tiles({op}), disasm({target}), romPatch({op}), …",
22
+ "RE engine (all 14 platforms): disasm({target:'functions'}) auto-detects functions, disasm({target:'cfg'}) graphs control flow, disasm({target:'xrefs'}) finds cross-references, disasm({target:'decompile'}) emits Ghidra C pseudocode, symbols({op:'analyze'}) maps a ROM's structure in one call.",
22
23
  "catalog({op:'categories'}) maps the tools by purpose (a guide, not a gate); catalog({op:'status'}) is a session re-orient.",
23
24
  ].join("\n");
24
25
 
@@ -27,6 +28,7 @@ export const mcpPreamble = [
27
28
  */
28
29
  export const skillPreamble = [
29
30
  "romdev gives you homebrew retro game development + reverse-engineering for ~14 platforms (NES, SNES, Game Boy, Genesis, GBA, Atari, C64, and more) — build, run, screenshot, inspect, patch, disassemble, convert assets, drive emulators.",
31
+ "It also ships a full RE analysis engine (Rizin + Ghidra, all 14 platforms): control-flow graphs, cross-references, auto-detected functions, a one-shot structural map, and a C-pseudocode decompiler — `disasm({target:'cfg'|'xrefs'|'functions'|'decompile'})` and `symbols({op:'analyze'})`.",
30
32
  "",
31
33
  "## Prerequisite: romdev runs LOCALLY (same machine as you)",
32
34
  "romdev bundles every compiler + emulator as WASM and runs them in-process — that engine lives in the romdev SERVER, started once with `npx romdevtools` (listens on http://localhost:7331; no other install, no host gcc/emulator needed). If a call gets connection-refused, it isn't running — start it.",