romdevtools 0.21.0 → 0.22.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 +15 -4
- package/CHANGELOG.md +58 -0
- package/examples/atari7800/templates/hello_sprite.c +48 -4
- package/examples/atari7800/templates/music_demo.c +47 -2
- package/examples/c64/templates/tile_engine.c +77 -27
- package/examples/gb/templates/hello_sprite.c +15 -6
- package/examples/gb/templates/music_demo.c +36 -0
- package/examples/gb/templates/platformer.c +3 -2
- package/examples/gb/templates/puzzle.c +3 -2
- package/examples/gb/templates/racing.c +3 -2
- package/examples/gb/templates/shmup.c +3 -2
- package/examples/gb/templates/sports.c +3 -2
- package/examples/gb/templates/tile_engine.c +3 -2
- package/examples/gba/templates/maxmod_demo.c +36 -2
- package/examples/gba/templates/platformer.c +3 -1
- package/examples/gba/templates/tonc_hello_sprite.c +35 -1
- package/examples/gbc/templates/hello_sprite.c +12 -3
- package/examples/gbc/templates/music_demo.c +56 -12
- package/examples/gbc/templates/platformer.c +3 -2
- package/examples/gbc/templates/puzzle.c +3 -2
- package/examples/gbc/templates/racing.c +3 -2
- package/examples/gbc/templates/shmup.c +3 -2
- package/examples/gbc/templates/sports.c +3 -2
- package/examples/gbc/templates/tile_engine.c +3 -2
- package/examples/genesis/main.s +53 -1
- package/examples/genesis/templates/hello_sprite.c +25 -3
- package/examples/genesis/templates/shmup_2p.c +31 -0
- package/examples/genesis/templates/xgm2_demo.c +20 -0
- package/examples/gg/templates/hello_sprite.c +25 -2
- package/examples/gg/templates/music_demo.c +24 -2
- package/examples/gg/templates/racing.c +7 -4
- package/examples/gg/templates/sports.c +11 -13
- package/examples/gg/templates/tile_engine.c +12 -6
- package/examples/lynx/templates/hello_sprite.c +15 -1
- package/examples/lynx/templates/music_demo.c +13 -1
- package/examples/nes/templates/hello_sprite.c +35 -0
- package/examples/nes/templates/music_demo.c +40 -0
- package/examples/pce/catch_game/main.c +22 -3
- package/examples/pce/music_sfx/main.c +28 -1
- package/examples/pce/sprite_move/main.c +7 -2
- package/examples/sms/templates/hello_sprite.c +29 -3
- package/examples/sms/templates/music_demo.c +18 -4
- package/examples/sms/templates/shmup_2p.c +24 -1
- package/examples/sms/templates/sports.c +4 -2
- package/examples/snes/main.asm +108 -17
- package/examples/snes/templates/c-hello-data.asm +23 -0
- package/examples/snes/templates/c-hello.c +18 -1
- package/examples/snes/templates/hello_sprite-data.asm +23 -0
- package/examples/snes/templates/hello_sprite.c +17 -1
- package/examples/snes/templates/music_demo-data.asm +23 -0
- package/examples/snes/templates/music_demo.c +22 -4
- package/examples/snes/templates/platformer.c +4 -1
- package/examples/snes/templates/puzzle.c +4 -1
- package/package.json +1 -1
- package/src/cheats/gamegenie.js +0 -1
- package/src/cli/smoke.js +1 -3
- package/src/host/LibretroHost.js +69 -15
- package/src/host/chafa-render.js +2 -0
- package/src/host/dsp-state.js +2 -2
- package/src/host/gpgx-state.js +4 -0
- package/src/http/routes.js +1 -1
- package/src/mcp/server.js +1 -1
- package/src/mcp/state.js +36 -0
- package/src/mcp/tools/address-to-symbol.js +0 -1
- package/src/mcp/tools/art-loaders.js +1 -1
- package/src/mcp/tools/cart-parts.js +0 -1
- package/src/mcp/tools/classify-region.js +1 -1
- package/src/mcp/tools/diff-roms.js +1 -1
- package/src/mcp/tools/disasm-rebuild.js +1 -1
- package/src/mcp/tools/disasm.js +2 -3
- package/src/mcp/tools/find-references.js +1 -2
- package/src/mcp/tools/font-map.js +1 -1
- package/src/mcp/tools/index.js +0 -49
- package/src/mcp/tools/input-layout.js +0 -1
- package/src/mcp/tools/input.js +33 -3
- package/src/mcp/tools/lifecycle.js +14 -2
- package/src/mcp/tools/lospec.js +0 -19
- package/src/mcp/tools/platform-docs.js +1 -1
- package/src/mcp/tools/platform-tools.js +4 -4
- package/src/mcp/tools/project.js +0 -2
- package/src/mcp/tools/reinject.js +0 -1
- package/src/mcp/tools/rom-id.js +2 -2
- package/src/mcp/tools/snippets.js +2 -2
- package/src/mcp/tools/sprite-pipeline.js +1 -2
- package/src/mcp/tools/tile-inspect.js +1 -1
- package/src/mcp/tools/toolchain.js +29 -9
- package/src/mcp/tools/watch-memory.js +13 -3
- package/src/platforms/_guides/ROMHACKING_PLAYBOOK.md +34 -0
- package/src/platforms/atari2600/TROUBLESHOOTING.md +6 -0
- package/src/platforms/atari7800/TROUBLESHOOTING.md +6 -0
- package/src/platforms/c64/TROUBLESHOOTING.md +6 -0
- package/src/platforms/c64/d64.js +0 -1
- package/src/platforms/c64/sid.js +0 -2
- package/src/platforms/common/metasprite-adapters.js +1 -1
- package/src/platforms/common/metasprite-codegen.js +3 -3
- package/src/platforms/common/registers.js +5 -3
- package/src/platforms/gb/TROUBLESHOOTING.md +6 -0
- package/src/platforms/gb/lib/c/gb_runtime.c +4 -4
- package/src/platforms/gba/TROUBLESHOOTING.md +6 -0
- package/src/platforms/gbc/TROUBLESHOOTING.md +6 -0
- package/src/platforms/gbc/lib/c/gb_runtime.c +4 -4
- package/src/platforms/genesis/TROUBLESHOOTING.md +6 -0
- package/src/platforms/gg/TROUBLESHOOTING.md +6 -0
- package/src/platforms/lynx/TROUBLESHOOTING.md +6 -0
- package/src/platforms/msx/TROUBLESHOOTING.md +6 -0
- package/src/platforms/nes/TROUBLESHOOTING.md +6 -0
- package/src/platforms/nes/image-to-tilemap.js +3 -0
- package/src/platforms/nes/lib/asm/famitone2.s +5 -1
- package/src/platforms/pce/TROUBLESHOOTING.md +6 -0
- package/src/platforms/sms/TROUBLESHOOTING.md +6 -0
- package/src/platforms/snes/TROUBLESHOOTING.md +6 -0
- package/src/platforms/snes/brr.js +0 -2
- package/src/playtest/playtest.js +0 -7
- package/src/toolchains/asar/asar.js +0 -9
- package/src/toolchains/assemble-snippet.js +30 -12
- package/src/toolchains/cc65/presets/nes/chr-ram-runtime.cfg +14 -1
- package/src/toolchains/common/reassemble.js +0 -1
- package/src/toolchains/common/sdk-cache.js +1 -1
- package/src/toolchains/genesis-c/genesis-c.js +5 -3
- package/src/toolchains/index.js +27 -3
- package/src/toolchains/parse-errors.js +78 -1
- package/src/toolchains/sdcc/preflight-lint.js +5 -1
- package/src/toolchains/sdcc/sdcc.js +1 -1
- package/src/toolchains/sjasm/sjasm.js +1 -1
- package/src/toolchains/snes-c/snes-c.js +2 -2
- package/src/toolchains/vasm68k/vasm68k.js +2 -4
- package/src/toolchains/wladx/wladx.js +1 -1
package/examples/snes/main.asm
CHANGED
|
@@ -2,32 +2,49 @@
|
|
|
2
2
|
;
|
|
3
3
|
; What this does:
|
|
4
4
|
; 1. Standard SNES reset (sei, clc xce → native mode, stack at $1FFF).
|
|
5
|
-
; 2. Uploads a
|
|
6
|
-
;
|
|
7
|
-
;
|
|
5
|
+
; 2. Uploads a 4-colour palette + two 2bpp tiles to VRAM via DMA.
|
|
6
|
+
; 3. Fills a 32x32 BG1 tilemap with a checkerboard of those two tiles
|
|
7
|
+
; so the whole screen shows a real tiled pattern — NOT a flat one-
|
|
8
|
+
; colour backdrop (which reads as "blank" to a human / the verifier).
|
|
9
|
+
; 4. Points BG1 at the tile + map bases, enables BG1, turns the screen
|
|
10
|
+
; on at full brightness, then parks forever.
|
|
8
11
|
;
|
|
9
|
-
; SUFFICIENT FOR: confirming your toolchain works
|
|
10
|
-
;
|
|
11
|
-
;
|
|
12
|
+
; SUFFICIENT FOR: confirming your toolchain works AND showing your user a
|
|
13
|
+
; real rendered screen in playtest. To go further (sprites, scrolling,
|
|
14
|
+
; sound, a vblank handler) see src/platforms/snes/lib/* snippets:
|
|
12
15
|
;
|
|
13
16
|
; listStarterSnippets({platform:"snes"})
|
|
14
17
|
;
|
|
15
18
|
; The key snippets are:
|
|
16
19
|
; - lorom_header.asm: full SNES header (this scaffold's is minimal)
|
|
17
20
|
; - cgram_upload.asm: palette upload boilerplate
|
|
18
|
-
; - vram_dma_upload.asm: fast tile/map uploads
|
|
21
|
+
; - vram_dma_upload.asm: fast tile/map uploads (used below)
|
|
19
22
|
; - oam_upload.asm: sprite table writes
|
|
20
23
|
; - nmi_safe.asm: vblank handler skeleton
|
|
21
24
|
;
|
|
22
25
|
; BUILD: complete LoROM image, no extra options needed.
|
|
23
|
-
; build({ output: "
|
|
26
|
+
; build({ output: "run", platform: "snes", source: /* this file */ });
|
|
24
27
|
|
|
25
28
|
lorom
|
|
26
29
|
|
|
30
|
+
; ── PPU / DMA registers ────────────────────────────────────────────────
|
|
27
31
|
INIDISP = $2100 ; screen brightness / forced blank
|
|
32
|
+
BGMODE = $2105 ; BG mode + tile-size
|
|
33
|
+
BG1SC = $2107 ; BG1 tilemap base + size
|
|
34
|
+
BG12NBA = $210B ; BG1/BG2 character (tile) base
|
|
35
|
+
TM = $212C ; main-screen layer enable
|
|
28
36
|
NMITIMEN = $4200
|
|
29
37
|
CGADD = $2121
|
|
30
38
|
CGDATA = $2122
|
|
39
|
+
VMAIN = $2115 ; VRAM address increment mode
|
|
40
|
+
VMADDL = $2116 ; VRAM word address (16-bit)
|
|
41
|
+
VMDATAL = $2118 ; VRAM data port (low)
|
|
42
|
+
DMAP0 = $4300 ; DMA0 control
|
|
43
|
+
BBAD0 = $4301 ; DMA0 B-bus address
|
|
44
|
+
A1T0L = $4302 ; DMA0 source address (16-bit)
|
|
45
|
+
A1B0 = $4304 ; DMA0 source bank
|
|
46
|
+
DAS0L = $4305 ; DMA0 byte count (16-bit)
|
|
47
|
+
MDMAEN = $420B ; DMA enable
|
|
31
48
|
|
|
32
49
|
; -----------------------------------------------------------------------
|
|
33
50
|
org $008000
|
|
@@ -40,29 +57,103 @@ START:
|
|
|
40
57
|
txs ; stack at $1FFF
|
|
41
58
|
sep #$20 ; A = 8-bit
|
|
42
59
|
|
|
43
|
-
; Blank screen during init.
|
|
60
|
+
; Blank screen during init; disable NMI/HDMA.
|
|
44
61
|
lda #$80
|
|
45
62
|
sta INIDISP
|
|
46
|
-
|
|
47
|
-
; Disable interrupts.
|
|
48
63
|
stz NMITIMEN
|
|
49
64
|
|
|
50
|
-
;
|
|
51
|
-
;
|
|
65
|
+
; ── Palette → CGRAM ───────────────────────────────────────────
|
|
66
|
+
; 4 colours (2bpp): 0 = blue backdrop, 1 = white, 2 = green,
|
|
67
|
+
; 3 = magenta. BGR-555, little-endian (low byte then high byte).
|
|
52
68
|
stz CGADD
|
|
53
69
|
lda #$00
|
|
54
|
-
sta CGDATA
|
|
70
|
+
sta CGDATA ; colour 0 low ($7C00 = blue)
|
|
71
|
+
lda #$7C
|
|
72
|
+
sta CGDATA ; colour 0 high
|
|
73
|
+
lda #$FF
|
|
74
|
+
sta CGDATA ; colour 1 low ($7FFF = white)
|
|
75
|
+
lda #$7F
|
|
76
|
+
sta CGDATA ; colour 1 high
|
|
77
|
+
lda #$E0
|
|
78
|
+
sta CGDATA ; colour 2 low ($03E0 = green)
|
|
79
|
+
lda #$03
|
|
80
|
+
sta CGDATA ; colour 2 high
|
|
81
|
+
lda #$1F
|
|
82
|
+
sta CGDATA ; colour 3 low ($7C1F = magenta)
|
|
55
83
|
lda #$7C
|
|
56
|
-
sta CGDATA
|
|
84
|
+
sta CGDATA ; colour 3 high
|
|
85
|
+
|
|
86
|
+
; ── Tile CHR → VRAM word $0000 (DMA channel 0) ────────────────
|
|
87
|
+
; Two 8x8 2bpp tiles = 32 bytes. VMAIN $80 = +1 word after the
|
|
88
|
+
; high-byte write; B-bus $18 = VMDATAL (auto-alternates to $2119).
|
|
89
|
+
ldx #$0000
|
|
90
|
+
stx VMADDL
|
|
91
|
+
lda #$80
|
|
92
|
+
sta VMAIN
|
|
93
|
+
lda #$01
|
|
94
|
+
sta DMAP0 ; DMA mode 1 (2 regs, word transfers)
|
|
95
|
+
lda #$18
|
|
96
|
+
sta BBAD0 ; → $2118 / $2119
|
|
97
|
+
ldx #TILES
|
|
98
|
+
stx A1T0L
|
|
99
|
+
lda #TILES>>16
|
|
100
|
+
sta A1B0
|
|
101
|
+
ldx #(TILES_END-TILES)
|
|
102
|
+
stx DAS0L ; byte count
|
|
103
|
+
lda #$01
|
|
104
|
+
sta MDMAEN ; fire channel 0
|
|
105
|
+
|
|
106
|
+
; ── Fill BG1 tilemap → VRAM word $0400 (byte $0800) ───────────
|
|
107
|
+
; A 32x32 map = 1024 entries. Each entry is a word: low byte =
|
|
108
|
+
; tile index, high byte = attributes (0). We write a checkerboard
|
|
109
|
+
; of tile 0 / tile 1 directly through the data port (no source
|
|
110
|
+
; buffer needed). VMAIN already = +1 word per write.
|
|
111
|
+
ldx #$0400
|
|
112
|
+
stx VMADDL
|
|
113
|
+
rep #$20 ; A = 16-bit for word writes
|
|
114
|
+
ldy #$0000 ; entry counter (0..1023)
|
|
115
|
+
.maploop:
|
|
116
|
+
tya
|
|
117
|
+
and #$0001 ; checker by column parity
|
|
118
|
+
sta VMDATAL ; entry = tile 0 or tile 1
|
|
119
|
+
iny
|
|
120
|
+
cpy #1024
|
|
121
|
+
bne .maploop
|
|
122
|
+
sep #$20 ; back to 8-bit A
|
|
57
123
|
|
|
58
|
-
;
|
|
59
|
-
|
|
124
|
+
; ── BG1 base registers ────────────────────────────────────────
|
|
125
|
+
stz BGMODE ; mode 0, 8x8 tiles
|
|
126
|
+
; BG1SC: bits 2-7 = tilemap base in $0400-word units, bits 0-1 =
|
|
127
|
+
; size (00 = 32x32). Map is at word $0400 → base = 1 → ($01<<2)=$04.
|
|
128
|
+
lda #$04
|
|
129
|
+
sta BG1SC
|
|
130
|
+
stz BG12NBA ; BG1 char base = word $0000 (our tiles)
|
|
131
|
+
lda #$01
|
|
132
|
+
sta TM ; enable BG1 on the main screen
|
|
133
|
+
|
|
134
|
+
; ── Screen ON at full brightness ──────────────────────────────
|
|
60
135
|
lda #$0F
|
|
61
136
|
sta INIDISP
|
|
62
137
|
|
|
63
138
|
LOOP:
|
|
64
139
|
bra LOOP
|
|
65
140
|
|
|
141
|
+
; -----------------------------------------------------------------------
|
|
142
|
+
; Tile CHR: two 8x8 2bpp tiles (16 bytes each). 2bpp = 2 bitplanes
|
|
143
|
+
; interleaved per row: byte0=row0 plane0, byte1=row0 plane1, ...
|
|
144
|
+
;
|
|
145
|
+
; Tile 0 — solid colour 1 (plane0 all set, plane1 clear → colour 1).
|
|
146
|
+
; Tile 1 — checker of colour 2 / colour 3 (both planes patterned) so the
|
|
147
|
+
; map's alternating tiles give a busy, multi-colour screen.
|
|
148
|
+
TILES:
|
|
149
|
+
; tile 0 (solid: every pixel colour 1)
|
|
150
|
+
db $FF, $00, $FF, $00, $FF, $00, $FF, $00
|
|
151
|
+
db $FF, $00, $FF, $00, $FF, $00, $FF, $00
|
|
152
|
+
; tile 1 (checkerboard: colour 2 / colour 3 alternating per pixel)
|
|
153
|
+
db $AA, $55, $55, $AA, $AA, $55, $55, $AA
|
|
154
|
+
db $AA, $55, $55, $AA, $AA, $55, $55, $AA
|
|
155
|
+
TILES_END:
|
|
156
|
+
|
|
66
157
|
; -----------------------------------------------------------------------
|
|
67
158
|
; Emulation-mode reset vector (used at boot, before `xce` flips to native).
|
|
68
159
|
; asar's `lorom` directive handles header + rest of vector table padding.
|
|
@@ -320,4 +320,27 @@ palfont:
|
|
|
320
320
|
.db $00, $00, $00, $00, $00, $00, $00, $00
|
|
321
321
|
.db $00, $00
|
|
322
322
|
|
|
323
|
+
|
|
324
|
+
; ── Background wallpaper (one 8x8 4bpp tile, 4 solid colour quadrants) ──
|
|
325
|
+
; Tiled across BG1 it paints the whole screen in four muted colours so the
|
|
326
|
+
; backdrop never reads as flat/blank. Quadrant->colour: TL=1, TR=2, BL=3,
|
|
327
|
+
; BR=4. 4bpp plane order: bytes 0-15 = rows 0-7 plane0/plane1 pairs, bytes
|
|
328
|
+
; 16-31 = rows 0-7 plane2/plane3 pairs.
|
|
329
|
+
tilbg:
|
|
330
|
+
.db $F0, $0F, $F0, $0F, $F0, $0F, $F0, $0F ; rows 0-3: p0=left p1=right
|
|
331
|
+
.db $F0, $F0, $F0, $F0, $F0, $F0, $F0, $F0 ; rows 4-7: p0+p1 = left
|
|
332
|
+
.db $00, $00, $00, $00, $00, $00, $00, $00 ; rows 0-3: p2/p3 = 0
|
|
333
|
+
.db $0F, $00, $0F, $00, $0F, $00, $0F, $00 ; rows 4-7: p2 = right
|
|
334
|
+
|
|
335
|
+
palbg:
|
|
336
|
+
; 16-colour BG palette; only 1-4 used (the four wallpaper quadrant tones).
|
|
337
|
+
.db $00, $00 ; 0 unused (BG fully opaque)
|
|
338
|
+
.db $C4, $30 ; 1 dark blue
|
|
339
|
+
.db $42, $29 ; 2 dark teal
|
|
340
|
+
.db $88, $30 ; 3 dark purple
|
|
341
|
+
.db $C6, $24 ; 4 dark slate
|
|
342
|
+
.db $00, $00, $00, $00, $00, $00, $00, $00
|
|
343
|
+
.db $00, $00, $00, $00, $00, $00, $00, $00
|
|
344
|
+
.db $00, $00, $00, $00, $00, $00
|
|
345
|
+
|
|
323
346
|
.ends
|
|
@@ -27,13 +27,20 @@
|
|
|
27
27
|
#include <snes.h>
|
|
28
28
|
|
|
29
29
|
extern char tilfont, palfont;
|
|
30
|
+
extern char tilbg, palbg; /* wallpaper tile + palette (data.asm) */
|
|
30
31
|
|
|
31
32
|
/* consoleVblank() copies the dirty text tilemap to VRAM during VBlank.
|
|
32
33
|
* It has no public prototype in console.h, so declare it here. Call it
|
|
33
34
|
* once per frame (after WaitForVBlank) or via nmiSet(consoleVblank). */
|
|
34
35
|
extern void consoleVblank(void);
|
|
35
36
|
|
|
37
|
+
/* BG1 wallpaper map: a full 32x32 screen of the 4-colour tile so the
|
|
38
|
+
* screen never reads as a flat/blank backdrop. Filled at runtime. */
|
|
39
|
+
static u16 bg_map[32 * 32];
|
|
40
|
+
|
|
36
41
|
int main(void) {
|
|
42
|
+
u16 i;
|
|
43
|
+
|
|
37
44
|
/* ── 1. PVSnesLib text-mode setup ─────────────────────────────
|
|
38
45
|
* Map + tile-data + palette-offset addresses are conventions —
|
|
39
46
|
* any free VRAM region will work, these match PVSnesLib's
|
|
@@ -54,7 +61,17 @@ int main(void) {
|
|
|
54
61
|
setMode(BG_MODE1, 0);
|
|
55
62
|
bgSetGfxPtr(0, 0x3000);
|
|
56
63
|
bgSetMapPtr(0, 0x6800, SC_32x32);
|
|
57
|
-
|
|
64
|
+
|
|
65
|
+
/* BG1 = full-screen wallpaper so the screen never reads as blank.
|
|
66
|
+
* Tiles -> VRAM $2000, map -> VRAM $4000 (clear of the console gfx
|
|
67
|
+
* $3000 / map $6800). Map entries use palette block 1 (0x0400) so the
|
|
68
|
+
* wallpaper palette doesn't disturb the console font palette in block 0
|
|
69
|
+
* (HUD text stays legible). */
|
|
70
|
+
bgInitTileSet(1, (u8 *)&tilbg, (u8 *)&palbg, 1,
|
|
71
|
+
32, 32, BG_16COLORS, 0x2000);
|
|
72
|
+
for (i = 0; i < 32 * 32; i++) bg_map[i] = 0x0400;
|
|
73
|
+
bgInitMapSet(1, (u8 *)bg_map, sizeof(bg_map), SC_32x32, 0x4000);
|
|
74
|
+
bgSetEnable(1);
|
|
58
75
|
bgSetDisable(2);
|
|
59
76
|
|
|
60
77
|
/* ── 3. Draw text ─────────────────────────────────────────────
|
|
@@ -340,4 +340,27 @@ palsprite:
|
|
|
340
340
|
.db $00, $00, $00, $00, $00, $00, $00, $00
|
|
341
341
|
.db $00, $00, $00, $00, $00, $00, $00, $00
|
|
342
342
|
|
|
343
|
+
|
|
344
|
+
; ── Background wallpaper (one 8x8 4bpp tile, 4 solid colour quadrants) ──
|
|
345
|
+
; Tiled across BG1 it paints the whole screen in four muted colours so the
|
|
346
|
+
; backdrop never reads as flat/blank. Quadrant->colour: TL=1, TR=2, BL=3,
|
|
347
|
+
; BR=4. 4bpp plane order: bytes 0-15 = rows 0-7 plane0/plane1 pairs, bytes
|
|
348
|
+
; 16-31 = rows 0-7 plane2/plane3 pairs.
|
|
349
|
+
tilbg:
|
|
350
|
+
.db $F0, $0F, $F0, $0F, $F0, $0F, $F0, $0F ; rows 0-3: p0=left p1=right
|
|
351
|
+
.db $F0, $F0, $F0, $F0, $F0, $F0, $F0, $F0 ; rows 4-7: p0+p1 = left
|
|
352
|
+
.db $00, $00, $00, $00, $00, $00, $00, $00 ; rows 0-3: p2/p3 = 0
|
|
353
|
+
.db $0F, $00, $0F, $00, $0F, $00, $0F, $00 ; rows 4-7: p2 = right
|
|
354
|
+
|
|
355
|
+
palbg:
|
|
356
|
+
; 16-colour BG palette; only 1-4 used (the four wallpaper quadrant tones).
|
|
357
|
+
.db $00, $00 ; 0 unused (BG fully opaque)
|
|
358
|
+
.db $C4, $30 ; 1 dark blue
|
|
359
|
+
.db $42, $29 ; 2 dark teal
|
|
360
|
+
.db $88, $30 ; 3 dark purple
|
|
361
|
+
.db $C6, $24 ; 4 dark slate
|
|
362
|
+
.db $00, $00, $00, $00, $00, $00, $00, $00
|
|
363
|
+
.db $00, $00, $00, $00, $00, $00, $00, $00
|
|
364
|
+
.db $00, $00, $00, $00, $00, $00
|
|
365
|
+
|
|
343
366
|
.ends
|
|
@@ -24,14 +24,20 @@
|
|
|
24
24
|
|
|
25
25
|
extern char tilfont, palfont;
|
|
26
26
|
extern char tilsprite, palsprite;
|
|
27
|
+
extern char tilbg, palbg; /* wallpaper tile + palette (data.asm) */
|
|
27
28
|
|
|
28
29
|
/* consoleVblank() copies the dirty text tilemap to VRAM during VBlank.
|
|
29
30
|
* No public prototype in console.h, so declare it; call once per frame. */
|
|
30
31
|
extern void consoleVblank(void);
|
|
31
32
|
|
|
33
|
+
/* BG1 wallpaper map: a full 32x32 screen of the 4-colour tile so the
|
|
34
|
+
* screen never reads as a flat/blank backdrop. Filled at runtime. */
|
|
35
|
+
static u16 bg_map[32 * 32];
|
|
36
|
+
|
|
32
37
|
int main(void) {
|
|
33
38
|
u16 x = 120, y = 100;
|
|
34
39
|
u16 prev = 0;
|
|
40
|
+
u16 i;
|
|
35
41
|
|
|
36
42
|
/* Text setup — same layout as c-hello. */
|
|
37
43
|
consoleSetTextMapPtr(0x6800);
|
|
@@ -44,7 +50,17 @@ int main(void) {
|
|
|
44
50
|
* registers — point BG0 at the same font ($3000) + map ($6800). */
|
|
45
51
|
bgSetGfxPtr(0, 0x3000);
|
|
46
52
|
bgSetMapPtr(0, 0x6800, SC_32x32);
|
|
47
|
-
|
|
53
|
+
|
|
54
|
+
/* BG1 = full-screen wallpaper so the screen never reads as blank.
|
|
55
|
+
* Tiles -> VRAM $2000, map -> VRAM $4000 (clear of sprites $0000 and
|
|
56
|
+
* the console gfx $3000 / map $6800). Map entries use palette block 1
|
|
57
|
+
* (0x0400) so the wallpaper palette doesn't disturb the console font
|
|
58
|
+
* palette in block 0 (status text stays legible). */
|
|
59
|
+
bgInitTileSet(1, (u8 *)&tilbg, (u8 *)&palbg, 1,
|
|
60
|
+
32, 32, BG_16COLORS, 0x2000);
|
|
61
|
+
for (i = 0; i < 32 * 32; i++) bg_map[i] = 0x0400;
|
|
62
|
+
bgInitMapSet(1, (u8 *)bg_map, sizeof(bg_map), SC_32x32, 0x4000);
|
|
63
|
+
bgSetEnable(1);
|
|
48
64
|
bgSetDisable(2);
|
|
49
65
|
|
|
50
66
|
/* OAM init — sprite tiles at VRAM $0000, 8x8 default. */
|
|
@@ -315,4 +315,27 @@ palfont:
|
|
|
315
315
|
.db $00, $00, $00, $00, $00, $00, $00, $00
|
|
316
316
|
.db $00, $00
|
|
317
317
|
|
|
318
|
+
|
|
319
|
+
; ── Background wallpaper (one 8x8 4bpp tile, 4 solid colour quadrants) ──
|
|
320
|
+
; Tiled across BG1 it paints the whole screen in four muted colours so the
|
|
321
|
+
; backdrop never reads as flat/blank. Quadrant->colour: TL=1, TR=2, BL=3,
|
|
322
|
+
; BR=4. 4bpp plane order: bytes 0-15 = rows 0-7 plane0/plane1 pairs, bytes
|
|
323
|
+
; 16-31 = rows 0-7 plane2/plane3 pairs.
|
|
324
|
+
tilbg:
|
|
325
|
+
.db $F0, $0F, $F0, $0F, $F0, $0F, $F0, $0F ; rows 0-3: p0=left p1=right
|
|
326
|
+
.db $F0, $F0, $F0, $F0, $F0, $F0, $F0, $F0 ; rows 4-7: p0+p1 = left
|
|
327
|
+
.db $00, $00, $00, $00, $00, $00, $00, $00 ; rows 0-3: p2/p3 = 0
|
|
328
|
+
.db $0F, $00, $0F, $00, $0F, $00, $0F, $00 ; rows 4-7: p2 = right
|
|
329
|
+
|
|
330
|
+
palbg:
|
|
331
|
+
; 16-colour BG palette; only 1-4 used (the four wallpaper quadrant tones).
|
|
332
|
+
.db $00, $00 ; 0 unused (BG fully opaque)
|
|
333
|
+
.db $C4, $30 ; 1 dark blue
|
|
334
|
+
.db $42, $29 ; 2 dark teal
|
|
335
|
+
.db $88, $30 ; 3 dark purple
|
|
336
|
+
.db $C6, $24 ; 4 dark slate
|
|
337
|
+
.db $00, $00, $00, $00, $00, $00, $00, $00
|
|
338
|
+
.db $00, $00, $00, $00, $00, $00, $00, $00
|
|
339
|
+
.db $00, $00, $00, $00, $00, $00
|
|
340
|
+
|
|
318
341
|
.ends
|
|
@@ -26,16 +26,22 @@
|
|
|
26
26
|
#include "snes_sfx.c"
|
|
27
27
|
|
|
28
28
|
extern char tilfont, palfont;
|
|
29
|
+
extern char tilbg, palbg; /* wallpaper tile + palette (data.asm) */
|
|
29
30
|
|
|
30
31
|
/* consoleVblank() copies the dirty text tilemap to VRAM during VBlank.
|
|
31
32
|
* No public prototype in console.h, so declare it; call once per frame. */
|
|
32
33
|
extern void consoleVblank(void);
|
|
33
34
|
|
|
35
|
+
/* BG1 wallpaper map: a full 32x32 screen of the 4-colour tile so the
|
|
36
|
+
* screen never reads as a flat/blank backdrop. Filled at runtime. */
|
|
37
|
+
static u16 bg_map[32 * 32];
|
|
38
|
+
|
|
34
39
|
int main(void) {
|
|
35
40
|
u16 pad;
|
|
36
41
|
u16 prev = 0;
|
|
37
42
|
u16 frame = 0;
|
|
38
43
|
u8 music_running;
|
|
44
|
+
u16 i;
|
|
39
45
|
|
|
40
46
|
/* ── Text-mode setup (PVSnesLib convention) ─────────────────── */
|
|
41
47
|
consoleSetTextMapPtr(0x6800);
|
|
@@ -47,11 +53,18 @@ int main(void) {
|
|
|
47
53
|
* registers — point BG0 at the same font ($3000) + map ($6800). */
|
|
48
54
|
bgSetGfxPtr(0, 0x3000);
|
|
49
55
|
bgSetMapPtr(0, 0x6800, SC_32x32);
|
|
50
|
-
bgSetDisable(1);
|
|
51
|
-
bgSetDisable(2);
|
|
52
56
|
|
|
53
|
-
/*
|
|
54
|
-
|
|
57
|
+
/* BG1 = full-screen wallpaper so the screen never reads as blank.
|
|
58
|
+
* Tiles -> VRAM $2000, map -> VRAM $4000 (clear of the console gfx
|
|
59
|
+
* $3000 / map $6800). Map entries use palette block 1 (0x0400) so the
|
|
60
|
+
* wallpaper palette doesn't disturb the console font palette in block 0
|
|
61
|
+
* (HUD text stays legible). */
|
|
62
|
+
bgInitTileSet(1, (u8 *)&tilbg, (u8 *)&palbg, 1,
|
|
63
|
+
32, 32, BG_16COLORS, 0x2000);
|
|
64
|
+
for (i = 0; i < 32 * 32; i++) bg_map[i] = 0x0400;
|
|
65
|
+
bgInitMapSet(1, (u8 *)bg_map, sizeof(bg_map), SC_32x32, 0x4000);
|
|
66
|
+
bgSetEnable(1);
|
|
67
|
+
bgSetDisable(2);
|
|
55
68
|
|
|
56
69
|
consoleDrawText( 8, 6, "SNES MUSIC DEMO");
|
|
57
70
|
consoleDrawText( 3, 11, "B = SHOOT SFX");
|
|
@@ -60,6 +73,11 @@ int main(void) {
|
|
|
60
73
|
|
|
61
74
|
setScreenOn();
|
|
62
75
|
|
|
76
|
+
/* Upload SPC driver + sample bank + song table to ARAM. sfx_init() must
|
|
77
|
+
* run AFTER setScreenOn() (snes_sfx.h:63) — if the SPC stalls before the
|
|
78
|
+
* screen is on you get a black/forced-blank screen forever. */
|
|
79
|
+
sfx_init();
|
|
80
|
+
|
|
63
81
|
/* Auto-start music. */
|
|
64
82
|
sfx_music_play();
|
|
65
83
|
music_running = 1;
|
|
@@ -107,10 +107,13 @@ int main(void) {
|
|
|
107
107
|
consoleDrawText( 9, 12, "|64");
|
|
108
108
|
consoleDrawText(17, 12, "|128");
|
|
109
109
|
consoleDrawText(25, 12, "|192");
|
|
110
|
-
sfx_init();
|
|
111
110
|
|
|
112
111
|
oamSet(0, 32, 100, 3, 0, 0, 0, 0);
|
|
112
|
+
/* Screen ON first, THEN sound. sfx_init() must run AFTER setScreenOn()
|
|
113
|
+
* (snes_sfx.h:63) — if the SPC stalls before the screen is on you get a
|
|
114
|
+
* black/forced-blank screen forever. */
|
|
113
115
|
setScreenOn();
|
|
116
|
+
sfx_init();
|
|
114
117
|
|
|
115
118
|
while (1) {
|
|
116
119
|
pad = padsCurrent(0);
|
|
@@ -176,10 +176,13 @@ int main(void) {
|
|
|
176
176
|
|
|
177
177
|
consoleDrawText(14, 2, "SCORE");
|
|
178
178
|
consoleDrawText(2, 26, "LR MOVE A ROT START DROP");
|
|
179
|
-
sfx_init();
|
|
180
179
|
draw_grid();
|
|
181
180
|
|
|
181
|
+
/* Screen ON first, THEN sound. sfx_init() must run AFTER setScreenOn()
|
|
182
|
+
* (snes_sfx.h:63) — if the SPC stalls before the screen is on you get a
|
|
183
|
+
* black/forced-blank screen forever. */
|
|
182
184
|
setScreenOn();
|
|
185
|
+
sfx_init();
|
|
183
186
|
|
|
184
187
|
while (1) {
|
|
185
188
|
pad = padsCurrent(0);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "romdevtools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
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",
|
package/src/cheats/gamegenie.js
CHANGED
|
@@ -446,7 +446,6 @@ export function decodeWithDevice(code, platform) {
|
|
|
446
446
|
|
|
447
447
|
/** Format a raw ADDR:VAL[:COMPARE] code from decoded parts (hex, no 0x). */
|
|
448
448
|
export function encodeRaw({ address, value, compare }) {
|
|
449
|
-
const h = (n, w) => (n & ((1 << (4 * w)) - 1) >>> 0).toString(16).toUpperCase().padStart(w, "0");
|
|
450
449
|
const addrHex = (address >>> 0).toString(16).toUpperCase();
|
|
451
450
|
const valHex = (value & 0xFF).toString(16).toUpperCase().padStart(2, "0");
|
|
452
451
|
return compare != null
|
package/src/cli/smoke.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// temp file and loaded into the matching libretro core. Platform is
|
|
15
15
|
// inferred from the file extension if not given.
|
|
16
16
|
|
|
17
|
-
import { writeFile, mkdtemp
|
|
17
|
+
import { writeFile, mkdtemp } from "node:fs/promises";
|
|
18
18
|
import { existsSync, statSync } from "node:fs";
|
|
19
19
|
import { tmpdir } from "node:os";
|
|
20
20
|
import path from "node:path";
|
|
@@ -108,12 +108,10 @@ async function playCommand(romPath, opts) {
|
|
|
108
108
|
|
|
109
109
|
// Extract zip if needed
|
|
110
110
|
let actualPath = romPath;
|
|
111
|
-
let extracted = false;
|
|
112
111
|
if (path.extname(romPath).toLowerCase() === ".zip") {
|
|
113
112
|
console.error(`unzipping ${path.basename(romPath)}...`);
|
|
114
113
|
const { tempPath, originalName } = await extractFirstRomFromZip(romPath);
|
|
115
114
|
actualPath = tempPath;
|
|
116
|
-
extracted = true;
|
|
117
115
|
console.error(` → ${originalName} (${statSync(tempPath).size} bytes)`);
|
|
118
116
|
}
|
|
119
117
|
|
package/src/host/LibretroHost.js
CHANGED
|
@@ -301,7 +301,20 @@ export class LibretroHost {
|
|
|
301
301
|
mod._free(infoPtr);
|
|
302
302
|
|
|
303
303
|
if (!ok) {
|
|
304
|
-
|
|
304
|
+
// This is the failure path for EVERY bad/wrong-platform/corrupt/
|
|
305
|
+
// unsupported-mapper image — the most common loadMedia failure. The core
|
|
306
|
+
// returns a bare false, so name the likely causes + the exact checks
|
|
307
|
+
// rather than leaving the agent with "failed".
|
|
308
|
+
throw new Error(
|
|
309
|
+
`The '${platform}' core REFUSED this ${mediaKind || "media"} ` +
|
|
310
|
+
`(${data.length} bytes${ext ? `, ${ext}` : ""}, path ${mediaPath}). ` +
|
|
311
|
+
`retro_load_game returned false — the bytes reached the core but it would not accept them. ` +
|
|
312
|
+
`Common causes: (1) wrong platform for this file (a GB ROM loaded as 'nes', etc.) — ` +
|
|
313
|
+
`confirm the platform matches the file; (2) a corrupt or TRUNCATED image — re-check the byte length; ` +
|
|
314
|
+
`(3) an unsupported mapper/board or a missing/!bad header. ` +
|
|
315
|
+
`Inspect the file with cart({op:'identify'}) to see what platform/mapper it really is, ` +
|
|
316
|
+
`then load with the matching platform.`,
|
|
317
|
+
);
|
|
305
318
|
}
|
|
306
319
|
|
|
307
320
|
this.status.platform = platform;
|
|
@@ -436,7 +449,7 @@ export class LibretroHost {
|
|
|
436
449
|
*/
|
|
437
450
|
stepFrames(n) {
|
|
438
451
|
const mod = this._needMod();
|
|
439
|
-
|
|
452
|
+
this._needMedia();
|
|
440
453
|
if (this.status.paused) return 0;
|
|
441
454
|
for (let i = 0; i < n; i++) {
|
|
442
455
|
mod._retro_run();
|
|
@@ -455,7 +468,7 @@ export class LibretroHost {
|
|
|
455
468
|
* Returns the frame count after. */
|
|
456
469
|
renderOneFrame() {
|
|
457
470
|
const mod = this._needMod();
|
|
458
|
-
|
|
471
|
+
this._needMedia();
|
|
459
472
|
mod._retro_run();
|
|
460
473
|
this.status.frameCount++;
|
|
461
474
|
if (this.state.lastFrame) {
|
|
@@ -594,7 +607,7 @@ export class LibretroHost {
|
|
|
594
607
|
/** @param {string} name */
|
|
595
608
|
loadState(name) {
|
|
596
609
|
const snapshot = this.namedStates.get(name);
|
|
597
|
-
if (!snapshot) throw new Error(
|
|
610
|
+
if (!snapshot) throw new Error(this._noStateError(name));
|
|
598
611
|
return this.unserializeState(snapshot); // returns # cheats cleared
|
|
599
612
|
}
|
|
600
613
|
|
|
@@ -641,10 +654,21 @@ export class LibretroHost {
|
|
|
641
654
|
* disturbing the live host). Throws if the slot doesn't exist. */
|
|
642
655
|
getStateBlob(name) {
|
|
643
656
|
const blob = this.namedStates.get(name);
|
|
644
|
-
if (!blob) throw new Error(
|
|
657
|
+
if (!blob) throw new Error(this._noStateError(name));
|
|
645
658
|
return blob;
|
|
646
659
|
}
|
|
647
660
|
|
|
661
|
+
/** Build a "no save state named X" error that lists the slots that DO exist
|
|
662
|
+
* (or says there are none) and names the op to create one. */
|
|
663
|
+
_noStateError(name) {
|
|
664
|
+
const names = [...this.namedStates.keys()];
|
|
665
|
+
return names.length
|
|
666
|
+
? `No save state named '${name}'. Existing in-memory slots: ${names.map((n) => `'${n}'`).join(", ")}. ` +
|
|
667
|
+
`(List them with state({op:'list'}); create one with state({op:'save', name}).)`
|
|
668
|
+
: `No save state named '${name}' — this session has NO in-memory save slots yet. ` +
|
|
669
|
+
`Create one with state({op:'save', name:'${name}'}) first (or load from disk with state({op:'load', path})).`;
|
|
670
|
+
}
|
|
671
|
+
|
|
648
672
|
/**
|
|
649
673
|
* @param {import("./types.js").MemoryRegion} region
|
|
650
674
|
* @param {number} offset
|
|
@@ -667,7 +691,7 @@ export class LibretroHost {
|
|
|
667
691
|
readMemory(region, offset, length) {
|
|
668
692
|
const mod = this._needMod();
|
|
669
693
|
const id = MemoryRegionToRetro[region];
|
|
670
|
-
if (id === undefined) throw new Error(
|
|
694
|
+
if (id === undefined) throw new Error(this._unknownRegionError(region));
|
|
671
695
|
const ptr = mod._retro_get_memory_data(id);
|
|
672
696
|
const size = mod._retro_get_memory_size(id);
|
|
673
697
|
if (!ptr || !size) throw new Error(this._emptyRegionError(region));
|
|
@@ -685,7 +709,7 @@ export class LibretroHost {
|
|
|
685
709
|
writeMemory(region, offset, bytes) {
|
|
686
710
|
const mod = this._needMod();
|
|
687
711
|
const id = MemoryRegionToRetro[region];
|
|
688
|
-
if (id === undefined) throw new Error(
|
|
712
|
+
if (id === undefined) throw new Error(this._unknownRegionError(region));
|
|
689
713
|
const ptr = mod._retro_get_memory_data(id);
|
|
690
714
|
const size = mod._retro_get_memory_size(id);
|
|
691
715
|
if (!ptr || !size) throw new Error(this._emptyRegionError(region));
|
|
@@ -1058,7 +1082,7 @@ export class LibretroHost {
|
|
|
1058
1082
|
|
|
1059
1083
|
runUntilPC(address, maxFrames = 600) {
|
|
1060
1084
|
this._needMod();
|
|
1061
|
-
|
|
1085
|
+
this._needMedia();
|
|
1062
1086
|
if (!this.pcBreakSupported()) {
|
|
1063
1087
|
throw new Error("PC breakpoint not supported by this core (Genesis today; other cores as patched).");
|
|
1064
1088
|
}
|
|
@@ -1090,7 +1114,7 @@ export class LibretroHost {
|
|
|
1090
1114
|
*/
|
|
1091
1115
|
runUntilRead(address, maxFrames = 600) {
|
|
1092
1116
|
this._needMod();
|
|
1093
|
-
|
|
1117
|
+
this._needMedia();
|
|
1094
1118
|
if (!this.readWatchSupported()) {
|
|
1095
1119
|
throw new Error("read watchpoint not supported by this core (Genesis today; other cores as patched).");
|
|
1096
1120
|
}
|
|
@@ -1122,7 +1146,7 @@ export class LibretroHost {
|
|
|
1122
1146
|
*/
|
|
1123
1147
|
stepInstruction() {
|
|
1124
1148
|
this._needMod();
|
|
1125
|
-
|
|
1149
|
+
this._needMedia();
|
|
1126
1150
|
if (!this.pcBreakSupported()) {
|
|
1127
1151
|
throw new Error("single-step not supported by this core (Genesis today; other cores as patched).");
|
|
1128
1152
|
}
|
|
@@ -1190,8 +1214,8 @@ export class LibretroHost {
|
|
|
1190
1214
|
* @param {(host:LibretroHost)=>any} [a.capture] read result from core RAM BEFORE restore
|
|
1191
1215
|
*/
|
|
1192
1216
|
callSubroutine(a) {
|
|
1193
|
-
|
|
1194
|
-
|
|
1217
|
+
this._needMod();
|
|
1218
|
+
this._needMedia();
|
|
1195
1219
|
if (!this.setRegSupported()) {
|
|
1196
1220
|
throw new Error("cpu({op:'call'}) not supported by this core (rebuild with romdev_setreg/romdev_getreg).");
|
|
1197
1221
|
}
|
|
@@ -1427,7 +1451,7 @@ export class LibretroHost {
|
|
|
1427
1451
|
*/
|
|
1428
1452
|
watchRange(lo, hi, mode, frames) {
|
|
1429
1453
|
const mod = this._needMod();
|
|
1430
|
-
|
|
1454
|
+
this._needMedia();
|
|
1431
1455
|
if (!this.rangeWatchSupported()) throw new Error("range watch not supported by this core.");
|
|
1432
1456
|
const m = mode === "read" ? 1 : mode === "write" ? 2 : 3;
|
|
1433
1457
|
mod._romdev_range_set(lo >>> 0, hi >>> 0, m, 1);
|
|
@@ -1460,7 +1484,7 @@ export class LibretroHost {
|
|
|
1460
1484
|
*/
|
|
1461
1485
|
logPCRange(lo, hi, frames) {
|
|
1462
1486
|
const mod = this._needMod();
|
|
1463
|
-
|
|
1487
|
+
this._needMedia();
|
|
1464
1488
|
if (!this.rangeWatchSupported()) throw new Error("coverage trace not supported by this core.");
|
|
1465
1489
|
mod._romdev_cov_set(lo >>> 0, hi >>> 0, 1);
|
|
1466
1490
|
this._runFramesExclusive(() => false, frames);
|
|
@@ -1500,7 +1524,7 @@ export class LibretroHost {
|
|
|
1500
1524
|
*/
|
|
1501
1525
|
watchDma(frames) {
|
|
1502
1526
|
const mod = this._needMod();
|
|
1503
|
-
|
|
1527
|
+
this._needMedia();
|
|
1504
1528
|
if (!this.dmaWatchSupported()) throw new Error("VDP-DMA watch not supported by this core (Genesis only).");
|
|
1505
1529
|
mod._romdev_dmawatch_set(1);
|
|
1506
1530
|
this._runFramesExclusive(() => false, frames);
|
|
@@ -1538,6 +1562,21 @@ export class LibretroHost {
|
|
|
1538
1562
|
return this.mod;
|
|
1539
1563
|
}
|
|
1540
1564
|
|
|
1565
|
+
// Guard for every op that needs a loaded game. The bare "no media loaded"
|
|
1566
|
+
// left the agent guessing; this names the fix (loadMedia) and the gotcha
|
|
1567
|
+
// (emulator state is in-memory, so a reconnect/restart drops it). The richer
|
|
1568
|
+
// session-aware recovery (echoing the exact prior loadMedia call) lives at the
|
|
1569
|
+
// tool layer in state.js getHost(); this is the host-level twin.
|
|
1570
|
+
_needMedia() {
|
|
1571
|
+
if (!this.status.loaded) {
|
|
1572
|
+
throw new Error(
|
|
1573
|
+
"No media loaded — call loadMedia({platform, path}) before this op. " +
|
|
1574
|
+
"(If you DID load and hit this after a reconnect/restart, the host's in-memory " +
|
|
1575
|
+
"state didn't survive — re-run loadMedia with your ROM to pick back up.)",
|
|
1576
|
+
);
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1541
1580
|
/**
|
|
1542
1581
|
* Build a friendly error message when a memory region is empty (the
|
|
1543
1582
|
* core didn't expose it). Includes per-platform suggestions when we
|
|
@@ -1548,6 +1587,21 @@ export class LibretroHost {
|
|
|
1548
1587
|
* "my VRAM writes are being optimized away" spiral — when in fact
|
|
1549
1588
|
* gambatte exposes VRAM as `gb_vram`, not the generic id.
|
|
1550
1589
|
*/
|
|
1590
|
+
_unknownRegionError(region) {
|
|
1591
|
+
// A bad region name should never leave the agent guessing — list the valid
|
|
1592
|
+
// ones (the single source of truth, MemoryRegionToRetro) so it can pick the
|
|
1593
|
+
// right one. The cross-platform names (system_ram / video_ram / save_ram)
|
|
1594
|
+
// exist everywhere; the rest are platform-specific.
|
|
1595
|
+
const valid = Object.keys(MemoryRegionToRetro).sort();
|
|
1596
|
+
const common = valid.filter((r) => ["system_ram", "video_ram", "save_ram", "rom"].includes(r));
|
|
1597
|
+
return (
|
|
1598
|
+
`Unknown memory region '${region}'. ` +
|
|
1599
|
+
`Common (most platforms): ${common.join(", ")}. ` +
|
|
1600
|
+
`All registered region names: ${valid.join(", ")}. ` +
|
|
1601
|
+
`(Region availability is per platform — some names only resolve on the platform that has that hardware.)`
|
|
1602
|
+
);
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1551
1605
|
_emptyRegionError(region) {
|
|
1552
1606
|
const plat = this.status && this.status.platform;
|
|
1553
1607
|
// SRAM gets an honest, specific answer: empty save_ram almost always means
|