romdevtools 0.16.0 → 0.21.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.
Files changed (110) hide show
  1. package/AGENTS.md +60 -12
  2. package/CHANGELOG.md +258 -0
  3. package/examples/README.md +2 -0
  4. package/examples/atari2600/templates/platformer.asm +460 -0
  5. package/examples/atari2600/templates/racing.asm +463 -0
  6. package/examples/atari2600/templates/shmup.asm +386 -0
  7. package/examples/atari2600/templates/sports.asm +362 -0
  8. package/examples/atari7800/templates/default.c +49 -5
  9. package/examples/atari7800/templates/platformer.c +43 -4
  10. package/examples/atari7800/templates/puzzle.c +39 -4
  11. package/examples/atari7800/templates/racing.c +39 -4
  12. package/examples/atari7800/templates/shmup.c +40 -2
  13. package/examples/atari7800/templates/sports.c +36 -5
  14. package/examples/c64/templates/platformer.c +19 -5
  15. package/examples/c64/templates/puzzle.c +32 -2
  16. package/examples/c64/templates/shmup.c +28 -2
  17. package/examples/c64/templates/sports.c +30 -2
  18. package/examples/gb/templates/default.c +110 -16
  19. package/examples/gb/templates/platformer.c +25 -4
  20. package/examples/gb/templates/puzzle.c +32 -2
  21. package/examples/gb/templates/racing.c +72 -8
  22. package/examples/gb/templates/shmup.c +38 -1
  23. package/examples/gb/templates/sports.c +48 -1
  24. package/examples/gba/templates/gba_hello.c +29 -11
  25. package/examples/gba/templates/puzzle.c +15 -3
  26. package/examples/gba/templates/racing.c +65 -3
  27. package/examples/gba/templates/shmup.c +41 -4
  28. package/examples/gba/templates/sports.c +36 -2
  29. package/examples/gba/templates/tonc_hello.c +41 -5
  30. package/examples/gbc/templates/default.c +103 -26
  31. package/examples/gbc/templates/platformer.c +25 -4
  32. package/examples/gbc/templates/puzzle.c +32 -2
  33. package/examples/gbc/templates/racing.c +85 -19
  34. package/examples/gbc/templates/shmup.c +34 -1
  35. package/examples/gbc/templates/sports.c +45 -1
  36. package/examples/genesis/templates/puzzle.c +37 -3
  37. package/examples/genesis/templates/racing.c +44 -11
  38. package/examples/genesis/templates/sgdk_hello.c +34 -1
  39. package/examples/genesis/templates/shmup.c +31 -1
  40. package/examples/gg/templates/default.c +56 -18
  41. package/examples/gg/templates/platformer.c +18 -12
  42. package/examples/gg/templates/puzzle.c +38 -7
  43. package/examples/gg/templates/racing.c +51 -5
  44. package/examples/gg/templates/shmup.c +47 -3
  45. package/examples/gg/templates/sports.c +46 -3
  46. package/examples/lynx/templates/default.c +39 -8
  47. package/examples/lynx/templates/puzzle.c +28 -1
  48. package/examples/lynx/templates/racing.c +34 -7
  49. package/examples/lynx/templates/shmup.c +42 -3
  50. package/examples/lynx/templates/sports.c +29 -2
  51. package/examples/msx/platformer/main.c +213 -0
  52. package/examples/msx/puzzle/main.c +250 -0
  53. package/examples/msx/racing/main.c +249 -0
  54. package/examples/msx/shmup/main.c +288 -0
  55. package/examples/msx/sports/main.c +182 -0
  56. package/examples/nes/templates/default.c +67 -19
  57. package/examples/nes/templates/platformer.c +65 -6
  58. package/examples/nes/templates/puzzle.c +67 -6
  59. package/examples/nes/templates/racing.c +45 -13
  60. package/examples/nes/templates/shmup.c +51 -2
  61. package/examples/nes/templates/sports.c +51 -6
  62. package/examples/pce/platformer/main.c +283 -0
  63. package/examples/pce/puzzle/main.c +304 -0
  64. package/examples/pce/racing/main.c +304 -0
  65. package/examples/pce/shmup/main.c +346 -0
  66. package/examples/pce/sports/main.c +254 -0
  67. package/examples/sms/main.c +35 -6
  68. package/examples/sms/templates/puzzle.c +34 -5
  69. package/examples/sms/templates/racing.c +39 -2
  70. package/examples/sms/templates/shmup.c +41 -2
  71. package/examples/sms/templates/sports.c +43 -2
  72. package/examples/snes/templates/default.c +50 -28
  73. package/examples/snes/templates/platformer-data.asm +22 -0
  74. package/examples/snes/templates/platformer.c +16 -1
  75. package/examples/snes/templates/puzzle-data.asm +22 -0
  76. package/examples/snes/templates/puzzle.c +17 -1
  77. package/examples/snes/templates/racing-data.asm +22 -0
  78. package/examples/snes/templates/racing.c +17 -1
  79. package/examples/snes/templates/shmup-data.asm +22 -0
  80. package/examples/snes/templates/shmup.c +20 -1
  81. package/examples/snes/templates/sports-data.asm +22 -0
  82. package/examples/snes/templates/sports.c +16 -1
  83. package/package.json +1 -1
  84. package/src/cores/wasm/vice_x64_libretro.js +1 -1
  85. package/src/cores/wasm/vice_x64_libretro.wasm +0 -0
  86. package/src/host/LibretroHost.js +122 -1
  87. package/src/host/callbacks.js +9 -1
  88. package/src/host/types.js +15 -8
  89. package/src/http/tool-registry.js +26 -1
  90. package/src/mcp/tools/cart-parts.js +75 -3
  91. package/src/mcp/tools/disasm-rebuild.js +507 -0
  92. package/src/mcp/tools/disasm.js +95 -6
  93. package/src/mcp/tools/frame.js +168 -3
  94. package/src/mcp/tools/lifecycle.js +4 -2
  95. package/src/mcp/tools/project.js +54 -9
  96. package/src/mcp/tools/state.js +201 -14
  97. package/src/mcp/tools/toolchain.js +76 -3
  98. package/src/mcp/tools/watch-memory.js +125 -14
  99. package/src/platforms/c64/MENTAL_MODEL.md +45 -1
  100. package/src/platforms/c64/d64.js +281 -0
  101. package/src/platforms/gb/MENTAL_MODEL.md +10 -0
  102. package/src/platforms/msx/MENTAL_MODEL.md +10 -6
  103. package/src/platforms/nes/MENTAL_MODEL.md +63 -2
  104. package/src/platforms/pce/MENTAL_MODEL.md +9 -4
  105. package/src/platforms/pce/lib/c/pce_video.c +1 -1
  106. package/src/rom-id/identifier.js +15 -0
  107. package/src/toolchains/cc65/ines.js +145 -0
  108. package/src/toolchains/cc65/presets/nes/chr-rom.cfg +83 -0
  109. package/src/toolchains/cc65/presets/nes/chr-rom.crt0.s +153 -0
  110. package/src/toolchains/common/reassemble.js +10 -2
@@ -0,0 +1,83 @@
1
+ # NES CHR-ROM linker config preset for romdev (cc65-C with FIXED tile art).
2
+ #
3
+ # Use this when you write NES homebrew in C AND ship your pattern tables as a
4
+ # fixed 8KB CHR-ROM blob (vs chr-ram-runtime, which has the CPU upload tiles to
5
+ # CHR-RAM at runtime). The cartridge is NROM-256: 32KB PRG + 8KB CHR-ROM.
6
+ #
7
+ # DIFFERENCE FROM chr-ram-runtime.cfg:
8
+ # - Adds a ROM2 memory area (8KB CHR-ROM bank) + a CHARS segment. Put your
9
+ # tile data in `.segment "CHARS"` (e.g. `.incbin "tiles.chr"`); it lands in
10
+ # the CHR-ROM bank the PPU reads pattern tables from directly.
11
+ # - The companion crt0 (chr-rom.crt0.s) emits an iNES header with byte 5 = 1
12
+ # (one 8KB CHR-ROM bank) and does NOT clear CHR-RAM (there is none).
13
+ #
14
+ # To use this preset:
15
+ # 1. linkerConfig: "chr-rom" on build({output:'rom'}) (pulls in chr-rom.crt0.s)
16
+ # 2. Supply your tile art: a source with `.segment "CHARS"` + `.incbin
17
+ # "tiles.chr"`, plus the blob via binaryIncludePaths:{ "tiles.chr": path }.
18
+ # The CHARS segment must total exactly 8192 bytes (fill is on, so a short
19
+ # blob is padded; an over-long one errors).
20
+ # 3. For a DIFFERENT cart config (NROM-128, >8KB CHR, a mapper, vertical
21
+ # mirroring), don't use this preset — use the parametric `inesHeader`
22
+ # build option instead (it generates the header + .cfg for any NROM shape).
23
+ #
24
+ # This is the cc65-C-with-segments shape. To rebuild a COMMERCIAL game from its
25
+ # disassembly (one flat CODE blob with embedded vectors), use `inesHeader`.
26
+
27
+ SYMBOLS {
28
+ __STACKSIZE__: type = weak, value = $0300;
29
+ }
30
+ MEMORY {
31
+ ZP: file = "", start = $0002, size = $001A, type = rw, define = yes;
32
+
33
+ # iNES Cartridge Header — emitted by chr-rom.crt0.s (HEADER segment).
34
+ HEADER: file = %O, start = $0000, size = $0010, fill = yes;
35
+
36
+ # PRG-ROM: 2 × 16K = 32K (NROM-256).
37
+ ROM0: file = %O, start = $8000, size = $7FFA, fill = yes, define = yes;
38
+
39
+ # Hardware vectors at end of PRG.
40
+ ROMV: file = %O, start = $FFFA, size = $0006, fill = yes;
41
+
42
+ # CHR-ROM: one 8K bank. The PPU reads pattern tables straight from here —
43
+ # this is the whole point of the CHR-ROM preset.
44
+ ROM2: file = %O, start = $0000, size = $2000, fill = yes, define = yes;
45
+
46
+ SRAM: file = "", start = $0500, size = __STACKSIZE__, define = yes;
47
+
48
+ # BSS / DATA live in real RAM ($0300-$04FF, 512 bytes). NROM (mapper 0)
49
+ # with no battery has $6000-$7FFF UNMAPPED — don't put BSS there.
50
+ RAM: file = "", start = $0300, size = $0200, define = yes;
51
+
52
+ # Shadow OAM at $0200 (page 2). NMI handler DMAs it to PPU sprite RAM.
53
+ OAM: file = "", start = $0200, size = $0100;
54
+ }
55
+ SEGMENTS {
56
+ ZEROPAGE: load = ZP, type = zp;
57
+ HEADER: load = HEADER, type = ro;
58
+ STARTUP: load = ROM0, type = ro, define = yes;
59
+ LOWCODE: load = ROM0, type = ro, optional = yes;
60
+ ONCE: load = ROM0, type = ro, optional = yes;
61
+ CODE: load = ROM0, type = ro, define = yes;
62
+ RODATA: load = ROM0, type = ro, define = yes;
63
+ DATA: load = ROM0, run = RAM, type = rw, define = yes;
64
+ VECTORS: load = ROMV, type = rw;
65
+ CHARS: load = ROM2, type = ro;
66
+ BSS: load = RAM, type = bss, define = yes;
67
+ OAM: load = OAM, type = bss, optional = yes;
68
+ }
69
+ FEATURES {
70
+ CONDES: type = constructor,
71
+ label = __CONSTRUCTOR_TABLE__,
72
+ count = __CONSTRUCTOR_COUNT__,
73
+ segment = ONCE;
74
+ CONDES: type = destructor,
75
+ label = __DESTRUCTOR_TABLE__,
76
+ count = __DESTRUCTOR_COUNT__,
77
+ segment = RODATA;
78
+ CONDES: type = interruptor,
79
+ label = __INTERRUPTOR_TABLE__,
80
+ count = __INTERRUPTOR_COUNT__,
81
+ segment = RODATA,
82
+ import = __CALLIRQ__;
83
+ }
@@ -0,0 +1,153 @@
1
+ ; crt0 for NES CHR-ROM mode + nes_runtime NMI handler.
2
+ ;
3
+ ; Companion to the linkerConfig:"chr-rom" preset. Identical to
4
+ ; chr-ram-runtime.crt0.s EXCEPT:
5
+ ; - the iNES header sets byte 5 = 1 (one 8KB CHR-ROM bank), and
6
+ ; - there is NO CHR-RAM clear loop — pattern tables come from the CHR-ROM
7
+ ; bank (the CHARS segment / ROM2 area) the PPU reads directly.
8
+ ;
9
+ ; The NMI handler is the canonical sprite-engine sequence (OAM DMA + VRAM-queue
10
+ ; flush + scroll + PPUCTRL), same as the CHR-RAM runtime.
11
+ ;
12
+ ; Loaded silently by the linkerConfig:"chr-rom" preset.
13
+
14
+ .export _exit
15
+ .export __STARTUP__ : absolute = 1
16
+ .export start
17
+ .export nmi
18
+ .export irq
19
+ .export _shadow_oam
20
+
21
+ .import initlib, donelib, callmain
22
+ .import _main, zerobss, copydata
23
+ .import __RAM_START__, __RAM_SIZE__
24
+ .import __SRAM_START__, __SRAM_SIZE__
25
+ .import _vram_queue_flush
26
+ .import _scroll_x, _scroll_y, _ppuctrl_value, _nmi_counter
27
+ .importzp c_sp
28
+
29
+ ; ------------------------------------------------------------------------
30
+ ; 16-byte iNES header — CHR-ROM (byte 5 = 1 → one 8KB CHR-ROM bank).
31
+
32
+ .segment "HEADER"
33
+ .byte $4e, $45, $53, $1a ; "NES" + EOF
34
+ .byte 2 ; PRG-ROM banks (16K each) → 32K
35
+ .byte 1 ; CHR-ROM banks (8K each) → 8K CHR-ROM
36
+ .byte %00000001 ; flags6 — vertical mirroring
37
+ .byte %00000000 ; flags7 — mapper hi nybble
38
+ .byte 0, 0, 0, 0, 0, 0, 0, 0
39
+
40
+ ; ------------------------------------------------------------------------
41
+ .segment "STARTUP"
42
+
43
+ start:
44
+ sei
45
+ cld
46
+ ldx #$ff
47
+ txs
48
+
49
+ ; Disable everything that could fire during init.
50
+ lda #0
51
+ sta $2000 ; disable NMI
52
+ sta $2001 ; disable rendering
53
+ sta $4010 ; disable DMC IRQ
54
+ sta $4015 ; disable APU channels
55
+ bit $2002 ; clear vblank flag
56
+
57
+ ; Wait two VBlanks before touching the PPU (standard NES init).
58
+ @vbl1: bit $2002
59
+ bpl @vbl1
60
+ @vbl2: bit $2002
61
+ bpl @vbl2
62
+
63
+ ; Initialise shadow_oam to Y=$FF (off-screen) before anything else.
64
+ ldx #0
65
+ lda #$ff
66
+ @oam: sta _shadow_oam,x
67
+ inx
68
+ bne @oam
69
+
70
+ ; NO CHR-RAM clear — pattern tables live in the CHR-ROM bank (CHARS).
71
+ ; Just point PPUADDR at the palette ($3F00) ready for the caller.
72
+ bit $2002 ; reset PPUADDR latch
73
+ lda #$3F
74
+ sta $2006
75
+ lda #$00
76
+ sta $2006
77
+
78
+ ; Clear BSS + copy DATA (cc65 conventions).
79
+ jsr zerobss
80
+ jsr copydata
81
+
82
+ ; Set up cc65's C parameter stack pointer.
83
+ lda #<(__SRAM_START__ + __SRAM_SIZE__)
84
+ ldx #>(__SRAM_START__ + __SRAM_SIZE__)
85
+ sta c_sp
86
+ stx c_sp+1
87
+
88
+ jsr initlib
89
+ jsr callmain
90
+
91
+ _exit: jsr donelib
92
+ jmp start
93
+
94
+ ; ------------------------------------------------------------------------
95
+ ; NMI handler — runs every vblank when ppuctrl bit 7 is set.
96
+
97
+ .segment "STARTUP"
98
+
99
+ nmi:
100
+ pha
101
+ txa
102
+ pha
103
+ tya
104
+ pha
105
+
106
+ ; OAM DMA: copy 256 bytes from $0200 to PPU OAM. Takes 513 cycles.
107
+ lda #$00
108
+ sta $2003 ; PPU OAMADDR = 0
109
+ lda #$02 ; high byte of $0200
110
+ sta $4014 ; PPU OAMDMA — kicks off the copy
111
+
112
+ ; Flush the VRAM queue (nametable/palette writes game code stashed).
113
+ jsr _vram_queue_flush
114
+
115
+ ; Reset PPUADDR to $2000 so the PPU doesn't sample random VRAM as BG.
116
+ bit $2002
117
+ lda #$20
118
+ sta $2006
119
+ lda #$00
120
+ sta $2006
121
+
122
+ ; Set scroll (two PPUSCROLL writes: x then y).
123
+ lda _scroll_x
124
+ sta $2005
125
+ lda _scroll_y
126
+ sta $2005
127
+
128
+ ; Re-enable NMI + base nametable + pattern-table bits via cached PPUCTRL.
129
+ lda _ppuctrl_value
130
+ sta $2000
131
+
132
+ ; Tick the frame counter so ppu_wait_nmi can return.
133
+ inc _nmi_counter
134
+
135
+ pla
136
+ tay
137
+ pla
138
+ tax
139
+ pla
140
+ rti
141
+
142
+ irq: rti
143
+
144
+ ; ------------------------------------------------------------------------
145
+ ; Shadow OAM at $0200 — the NMI handler DMAs this to the PPU each frame.
146
+ .segment "OAM"
147
+ _shadow_oam: .res 256
148
+
149
+ ; ------------------------------------------------------------------------
150
+ .segment "VECTORS"
151
+ .word nmi ; $FFFA
152
+ .word start ; $FFFC
153
+ .word irq ; $FFFE
@@ -67,7 +67,7 @@ function firstDiff(a, b) {
67
67
  // m68k (genesis) → objdump → m68k-elf-as/ld/objcopy
68
68
  // arm (gba) → objdump → arm-none-eabi-as/ld/objcopy
69
69
 
70
- const CPU_FAMILY = {
70
+ export const CPU_FAMILY = {
71
71
  nes: "6502", c64: "6502", atari2600: "6502", atari7800: "6502", lynx: "6502",
72
72
  snes: "65816",
73
73
  sms: "z80", gg: "z80", msx: "z80",
@@ -254,7 +254,15 @@ async function reassembleGnuNative(disasm, startAddress, original, tools, family
254
254
  if (!pinned) { const n = codeIdx.find((i) => !forced.has(i)); if (n == null) break; forced.add(n); }
255
255
  }
256
256
  // Floor: clean all-`.byte` (proven byte-exact, no labels to perturb layout).
257
- const rows = [".section .text", ".global _start", "_start:"];
257
+ // Mirror build()'s `.org` for the no-link (z80/gbz80) path: without it,
258
+ // objcopy emits the section from file offset 0, and assemble()'s
259
+ // `bin.slice(startAddress, …)` then returns bytes that are `startAddress`-short
260
+ // (empty for a $4000/$8000-based region) — so any non-zero-org region (MSX
261
+ // $4010, GB bank1 $4000, …) silently fails the floor. The linked path sets the
262
+ // origin via the link script, so it must NOT carry a redundant `.org`.
263
+ const rows = [".section .text", ".global _start"];
264
+ if (tools.noLink) rows.push(`.org 0x${startAddress.toString(16)}`);
265
+ rows.push("_start:");
258
266
  for (let i = 0; i < original.length; i += 16) rows.push("\t.byte " + Array.from(original.slice(i, i + 16)).map((b) => "0x" + b.toString(16).padStart(2, "0")).join(","));
259
267
  const r = await assemble(rows.join("\n") + "\n");
260
268
  const ok = r.ok && r.bytes.length === original.length && firstDiff(original, r.bytes) < 0;