Skip to content

Commit 0735396

Browse files
committed
updated readme
1 parent 396bb4b commit 0735396

4 files changed

Lines changed: 397 additions & 36 deletions

File tree

README.md

Lines changed: 128 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,18 @@ zig build nes-megablast
3636
zig build nes-gg-demo
3737
zig build nes-mappers
3838
zig build nes-cnrom-hello
39+
zig build nes-cnrom-sprites
3940
zig build nes-unrom-hello
41+
zig build nes-unrom-color-cycle
4042
zig build nes-mmc1-hello
43+
zig build nes-mmc1-sprites
4144
zig build nes-mmc3-hello
45+
zig build nes-mmc3-pads
4246
zig build nes-gtrom-hello
47+
zig build nes-gtrom-color-cycle
4348
zig build nes-unrom-512-hello
49+
zig build nes-action53-hello
50+
zig build fds-hello
4451

4552
# Commodore 64
4653
zig build c64-hello
@@ -50,6 +57,7 @@ zig build c64-plasma
5057
# Commander X16
5158
zig build cx16-hello
5259
zig build cx16-k-console-test
60+
zig build vic20-hello
5361

5462
# Atari Lynx
5563
zig build lynx-hello
@@ -76,7 +84,6 @@ zig build snes-zig-logo
7684
zig build snes-pi-test
7785
zig build snes-pi-fastrom
7886
zig build snes-hirom-hello
79-
zig build snes-megablast
8087
zig build snes-pads
8188

8289
# mos-sim (6502 simulator)
@@ -118,11 +125,18 @@ Output files land in `zig-out/bin/`.
118125
| `nes-pads` — controller input with two 16×16 metasprites | ![](.github/mmc3-pads.gif) |
119126
| `nes-mappers` — CNROM 4-bank CHR demo, press Start to cycle banks | ![](.github/mappers.gif) |
120127
| `nes-cnrom-hello` — CNROM banked CHR ROM | |
128+
| `nes-cnrom-sprites` — CNROM sprites example | |
121129
| `nes-unrom-hello` — UNROM banked PRG ROM | |
130+
| `nes-unrom-color-cycle` — UNROM colour-cycle across PRG banks | |
122131
| `nes-mmc1-hello` — MMC1 mapper | |
132+
| `nes-mmc1-sprites` — MMC1 sprites with CHR RAM upload | |
123133
| `nes-mmc3-hello` — MMC3 mapper | |
134+
| `nes-mmc3-pads` — MMC3 controller + collision example | ![](.github/mmc3-pads.gif) |
124135
| `nes-gtrom-hello` — GTROM mapper | |
136+
| `nes-gtrom-color-cycle` — GTROM colour-cycle with LED | |
125137
| `nes-unrom-512-hello` — UNROM-512 mapper (mapper 30) | |
138+
| `nes-action53-hello` — Action53 multicart (mapper 28) | |
139+
| `fds-hello` — Famicom Disk System backdrop hello | |
126140

127141
### SNES
128142

@@ -134,7 +148,6 @@ Output files land in `zig-out/bin/`.
134148
| `snes-pi-test`~900 digits of π via Spigot algorithm, BG1 text (port of pi_snes by Sirmacho) | |
135149
| `snes-pi-fastrom` — same π demo built as FastROM (map mode $30, MEMSEL=1) | |
136150
| `snes-hirom-hello` — HiROM backdrop hello (map mode $21) | |
137-
| `snes-megablast` — full-game port [no sound] of NES Megablast (CH13), Mode 1 BG+OBJ | |
138151
| `snes-pads` — joypad demo: LEFT/RIGHT cycles backdrop color, A+B resets; exercises `buttonMask`, `held`, `pressed` | |
139152

140153
### Other platforms
@@ -162,10 +175,15 @@ Output files land in `zig-out/bin/`.
162175
| `nes-gg-demo` | NES NROM | mos6502 | `.nes` |
163176
| `nes-mappers` | NES CNROM (4-bank) | mos6502 | `.nes` |
164177
| `nes-cnrom-hello` | NES CNROM | mos6502 | `.nes` |
178+
| `nes-cnrom-sprites` | NES CNROM sprites | mos6502 | `.nes` |
165179
| `nes-unrom-hello` | NES UNROM | mos6502 | `.nes` |
180+
| `nes-unrom-color-cycle` | NES UNROM colour-cycle | mos6502 | `.nes` |
166181
| `nes-mmc1-hello` | NES MMC1 | mos6502 | `.nes` |
182+
| `nes-mmc1-sprites` | NES MMC1 sprites (CHR RAM) | mos6502 | `.nes` |
167183
| `nes-mmc3-hello` | NES MMC3 | mos6502 | `.nes` |
184+
| `nes-mmc3-pads` | NES MMC3 controller + collision | mos6502 | `.nes` |
168185
| `nes-gtrom-hello` | NES GTROM | mos6502 | `.nes` |
186+
| `nes-gtrom-color-cycle` | NES GTROM colour-cycle + LED | mos6502 | `.nes` |
169187
| `nes-unrom-512-hello` | NES UNROM-512 | mos6502 | `.nes` |
170188
| `nes-action53-hello` | NES Action53 (mapper 28) | mos6502 | `.nes` |
171189
| `fds-hello` | Famicom Disk System | mos6502 | `.fds` |
@@ -188,7 +206,6 @@ Output files land in `zig-out/bin/`.
188206
| `snes-pi-test` | SNES LoROM | mosw65816 | `.sfc` |
189207
| `snes-pi-fastrom` | SNES FastROM | mosw65816 | `.sfc` |
190208
| `snes-hirom-hello` | SNES HiROM | mosw65816 | `.sfc` |
191-
| `snes-megablast` | SNES LoROM | mosw65816 | `.sfc` |
192209
| `snes-pads` | SNES LoROM | mosw65816 | `.sfc` |
193210
| `sim-hello` | mos-sim (6502 simulator) | mos6502 | binary |
194211
| `mega65-hello`, `mega65-plasma` | MEGA65 | mos45gs02 | `.prg` |
@@ -219,21 +236,127 @@ fib(20) = 6765 ( 4 cycles)
219236
sieve<127>: 31 primes (6905 cycles)
220237
```
221238

239+
## Host tools
240+
241+
Built automatically alongside the examples (`zig build --summary all`).
242+
243+
### `bininfo` — binary inspector
244+
245+
Identifies and inspects any MOS-platform output binary.
246+
247+
```sh
248+
zig-out/bin/bininfo <file> [files…] [flags]
249+
```
250+
251+
| Flag | Short | Description |
252+
|------|-------|-------------|
253+
| `--sections` | `-S` | List ELF sections |
254+
| `--symbols` | `-n` | List ELF symbols |
255+
| `--dwarf` | `-d` | Dump DWARF section inventory |
256+
| `--xxd` | `-x` | Hex+ASCII dump (xxd style) |
257+
| `--xxd-limit N` | | Cap xxd output at N bytes |
258+
| `--disasm` | `-D` | 6502 disassembly of file payload |
259+
260+
Flags may appear before or after filenames.
261+
262+
Detected formats: iNES 1.0/2.0 (`.nes`), SNES SFC/SMC (`.sfc`/`.smc` — SMC 512-byte copier header auto-stripped), FDS raw (`.fds`), GEOS CVT (`.cvt`), CBM PRG (`.prg`), Atari 2600 (`.a26`), Atari 8-bit cart (`.rom`), Atari XEX (`.xex`), Lynx BLL (`.bll`), PC Engine (`.pce`), Neo6502 (`.neo`), Apple IIe ProDOS (`.sys`), mos-sim binary, ELF. HiROM vs LoROM is auto-detected from map-mode byte.
263+
264+
```sh
265+
# Inspect a built NES ROM
266+
bininfo zig-out/bin/hello1.nes --sections
267+
268+
# Dump first 64 bytes of an FDS binary
269+
bininfo zig-out/bin/fds-hello.fds -x --xxd-limit 64
270+
271+
# Disassemble a PRG (auto-skips 2-byte load-address header)
272+
bininfo zig-out/bin/c64-hello.prg -D
273+
```
274+
275+
### `romtool` — NES / SNES ROM analyser
276+
277+
```sh
278+
zig-out/bin/romtool <subcommand> [options] <file>
279+
```
280+
281+
| Subcommand | Description |
282+
|------------|-------------|
283+
| `disasm` | 6502 / 65816 disassembly |
284+
| `unpack` | Extract PRG/CHR banks + `header.txt` |
285+
| `pack nes` | Assemble PRG+CHR banks back into an iNES ROM |
286+
| `pack snes` | Assemble bank binaries into an SFC ROM with correct checksum |
287+
288+
**`disasm` options:**
289+
290+
| Flag | Description |
291+
|------|-------------|
292+
| `--bank N` | NES: PRG bank N (16 KB, 0-based); SNES: 32 KB bank N (0-based) |
293+
| `--base 0xNNNN` | Override load address displayed |
294+
| `--offset N` | Skip N bytes before disassembling (address adjusts automatically) |
295+
| `--length N` | Disassemble at most N bytes |
296+
| `--m8` / `--x8` | SNES: start with M/X flags set (8-bit accumulator/index) |
297+
298+
```sh
299+
# Disassemble NES PRG bank 1 from offset $1234
300+
romtool disasm game.nes --bank 1 --base 0xC000 --offset 0x1234 --length 128
301+
302+
# Disassemble SNES RESET vector (HiROM, bank 1, offset $7F98)
303+
romtool disasm game.smc --bank 1 --base 0x8000 --offset 0x7F98 --m8 --x8 --length 40
304+
305+
# Unpack all banks to a directory
306+
romtool unpack game.nes /path/to/game-banks/
307+
308+
# Reassemble NES ROM from PRG + CHR binaries
309+
romtool pack nes -o repacked.nes prg-bank-00.bin chr-bank-00.bin
310+
311+
# Assemble SNES ROM from 32KB bank binaries (checksum auto-computed)
312+
romtool pack snes -o out.sfc --map lorom bank-00.bin [bank-01.bin ...]
313+
314+
# Same with SMC copier header and custom title
315+
romtool pack snes -o out.smc --map hirom --title "MY GAME" --smc bank-00.bin
316+
```
317+
318+
### `chr2svg` / `svg2chr` — NES CHR tile converter
319+
320+
```sh
321+
# CHR ROM → SVG (view/edit tiles in any SVG editor)
322+
chr2svg zig-out/bin/chr-bank-00.bin tiles.svg --scale 3 --cols 16
323+
324+
# SVG → CHR ROM (after editing)
325+
svg2chr tiles.svg chr-bank-00.bin
326+
```
327+
328+
### `elf2mlb` — Mesen label file generator
329+
330+
Converts a MOS ELF debug binary to a Mesen `.mlb` label file for source-level symbol display in the Mesen emulator debugger.
331+
332+
```sh
333+
elf2mlb zig-out/bin/hello1.nes.elf hello1.mlb
334+
```
335+
336+
The `gen-labels` build step runs this automatically for all NES examples.
337+
222338
## Platform notes
223339

224340
- **NES mappers** — CNROM 4-bank CHR demo; press Start to cycle through 4 CHR banks with distinct palettes. ROM: 32 KB PRG + 32 KB CHR ROM (4×8 KB banks).
225341
- **NES CNROM hello** — uses translated `mapper.h` via `b.addTranslateC`; calls `set_chr_bank(0)` to initialise the CNROM CHR bank. ROM: 32 KB PRG + 8 KB CHR ROM.
342+
- **NES CNROM sprites** — CNROM OAM sprite rendering with banked CHR. ROM: 32 KB PRG + 8 KB CHR ROM.
226343
- **NES UNROM hello** — uses translated `mapper.h`; calls `set_prg_bank(0)` to initialise the UNROM PRG bank. ROM: 256 KB PRG + 8 KB CHR RAM.
344+
- **NES UNROM colour-cycle** — cycles backdrop colour across multiple UNROM PRG banks. ROM: 256 KB PRG + CHR RAM.
227345
- **NES MMC1 hello** — uses translated `mapper.h`; calls `set_prg_bank(0)` and `set_mirroring(MIRROR_VERTICAL)` to initialise MMC1 registers. ROM: 256 KB PRG + 8 KB CHR RAM.
228-
- **NES MMC3 hello** — uses translated `mapper.h`; calls `set_prg_bank(0)` to initialise MMC3 PRG bank. ROM: 512 KB PRG + 256 KB CHR ROM.
346+
- **NES MMC1 sprites** — MMC1 OAM sprites with 8 KB CHR uploaded to CHR RAM via `vram_write`; calls `set_mmc1_ctrl(0x0E)` first to set 8 KB CHR mode.
347+
- **NES MMC3 hello** — uses translated `mapper.h`; calls `set_prg_bank(0)` to initialise MMC3 PRG bank. ROM: 512 KB PRG + CHR RAM.
348+
- **NES MMC3 pads** — MMC3 controller input + sprite-background collision; uploads CHR tiles to CHR RAM.
229349
- **NES GTROM hello** — uses translated `mapper.h`; GTROM (Codemasters) flash mapper. ROM: 512 KB PRG flash.
350+
- **NES GTROM colour-cycle** — GTROM PRG bank cycling with LED toggle via `$5000` write.
230351
- **NES UNROM-512 hello** — uses translated `mapper.h`; calls `set_prg_bank(0)` and `set_chr_bank(0)` to initialise mapper 30 registers. ROM: 512 KB PRG + 32 KB CHR RAM.
231-
- **NES Action53 hello** — uses translated `mapper.h`; mapper 28 (Action53 multicart). ROM: 128 KB PRG + 8 KB CHR RAM.
352+
- **NES Action53 hello** — uses translated `mapper.h`; mapper 28 (Action53 multicart). ROM: 64 KB PRG + 8 KB CHR RAM.
232353
- **FDS hello** — Famicom Disk System; uses raw PPU register writes via `nes/hardware.zig` (no neslib — FDS has no CHR ROM, raw PPU only). Writes dark-green backdrop (`$1A`) to palette `$3F00`.
233354
- **VIC-20 hello** — uses CBM KERNAL `cbm_k_chrout` to print "HELLO VIC20!", then cycles VIC chip background/border colour register (`$900F`). Targets 24K memory expansion, loads at `$1201`.
234355
- **C64 hello** — uses translated `c64.h` (VIC-II typed struct) via `b.addTranslateC`; cycles VIC-II border colour register.
235356
- **CX16 hello** — uses CBM KERNAL `cbm_k_chrout` to print "HELLO X16!", then cycles the border colour register.
236357
- **Lynx hello** — uses translated `_mikey.h` (MIKEY typed struct) via `b.addTranslateC`; animates all 32 palette entries.
358+
- **SNES FastROM** — same π demo as `snes-pi-test` built with map mode `$30` (FastROM) and MEMSEL=1; ROM mirrored at `$80:8000`, runs at full 3.58 MHz.
359+
- **SNES HiROM** — 64 KB bank layout with header at `$FFC0`; data bank register forced to `$00` via `lda #$00; pha; plb` in crt0 (safe for both LoROM and HiROM).
237360
- **Atari 8-bit DOS hello** — uses `std.c.printf` via CIO-backed libc (E: screen editor device).
238361
- **Atari 8-bit cart hello** — uses translated `_gtia.h` (GTIA write struct) via `b.addTranslateC`; cycles COLBK background colour, synced to ANTIC VCOUNT.
239362
- **sim-hello** — uses translated `sim-io.h` (typed MMIO struct) via `b.addTranslateC`; benchmarks fib(10), fib(20), and sieve of Eratosthenes for primes < 128.

build.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ pub fn build(b: *std.Build) void {
212212
});
213213
b.installArtifact(svg2chr);
214214

215-
// Host tool: NES / SNES / PRG pack · unpack · disassemble.
215+
// Host tool: NES / SNES pack · unpack · disassemble.
216216
const romtool = b.addExecutable(.{
217217
.name = "romtool",
218218
.root_module = b.createModule(.{

tools/bininfo.zig

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -558,21 +558,44 @@ fn checkNeo(out: anytype, path: []const u8, data: []const u8) bool {
558558
return true;
559559
}
560560

561-
fn checkSfc(out: anytype, path: []const u8, data: []const u8) bool {
561+
const SnesHeader = struct {
562+
hdr_off: usize,
563+
vec_reset: usize,
564+
vec_nmi: usize,
565+
};
566+
567+
fn snesDetectHeader(data: []const u8) SnesHeader {
568+
const valid_modes = [_]u8{ 0x20, 0x21, 0x23, 0x25, 0x30, 0x31 };
569+
// HiROM header at $FFC0; LoROM at $7FC0. Try HiROM first when map_mode matches.
570+
const candidates = [_]SnesHeader{
571+
.{ .hdr_off = 0xFFC0, .vec_reset = 0xFFFC, .vec_nmi = 0xFFEA },
572+
.{ .hdr_off = 0x7FC0, .vec_reset = 0x7FFC, .vec_nmi = 0x7FEA },
573+
};
574+
for (candidates) |c| {
575+
if (data.len < c.hdr_off + 32) continue;
576+
const m = data[c.hdr_off + 0x15];
577+
for (valid_modes) |v| if (m == v) return c;
578+
}
579+
return candidates[1]; // default LoROM
580+
}
581+
582+
fn checkSfc(out: anytype, path: []const u8, raw: []const u8) bool {
583+
// Strip 512-byte SMC copier header if present (size % 1024 == 512).
584+
const data = if (raw.len % 1024 == 512) raw[512..] else raw;
562585
const len = data.len;
563586
if (len < 32768 or (len % 32768) != 0) {
564-
out.print("{s}: [SFC] ERROR: unexpected size {d}B (expected multiple of 32KB)\n", .{ path, len }) catch {};
587+
out.print("{s}: [SFC] ERROR: unexpected size {d}B (expected multiple of 32KB)\n", .{ path, raw.len }) catch {};
565588
return false;
566589
}
567-
// LoROM internal header sits at $7FC0 within the first 32KB bank.
568-
const hdr: usize = 0x7FC0;
569-
const map_mode = data[hdr + 0x15]; // $7FD5: $20=LoROM/Slow, $30=LoROM/Fast, $21=HiROM, $31=HiROM/Fast
570-
const rom_sz_byte = data[hdr + 0x17]; // $7FD7: 1KB << n
571-
const chksum = readU16Le(data, hdr + 0x1E); // $7FDE
572-
const chksum_comp = readU16Le(data, hdr + 0x1C); // $7FDC
573-
const chk_ok = (chksum +% chksum_comp) == 0xFFFF;
574-
575-
// Title: 21 ASCII bytes at $7FC0, space-padded — trim trailing spaces.
590+
const h = snesDetectHeader(data);
591+
const hdr = h.hdr_off;
592+
const map_mode = data[hdr + 0x15];
593+
const rom_sz_byte = data[hdr + 0x17];
594+
const chksum = readU16Le(data, hdr + 0x1E);
595+
const chksum_comp = readU16Le(data, hdr + 0x1C);
596+
const chk_ok = (chksum ^ chksum_comp) == 0xFFFF;
597+
598+
// Title: 21 ASCII bytes at hdr, space-padded — trim trailing spaces.
576599
var title: [21]u8 = data[hdr..][0..21].*;
577600
var tlen: usize = 21;
578601
while (tlen > 0 and title[tlen - 1] == ' ') tlen -= 1;
@@ -587,10 +610,8 @@ fn checkSfc(out: anytype, path: []const u8, data: []const u8) bool {
587610
else => "unknown",
588611
};
589612

590-
// Emulation-mode RESET vector: CPU $FFFC = file offset $7FFC (LoROM).
591-
const reset = readU16Le(data, 0x7FFC);
592-
// Native-mode NMI vector: CPU $FFEA = file offset $7FEA (LoROM).
593-
const nmi_native = readU16Le(data, 0x7FEA);
613+
const reset = readU16Le(data, h.vec_reset);
614+
const nmi_native = readU16Le(data, h.vec_nmi);
594615
const rom_kb: u32 = if (rom_sz_byte < 14) @as(u32, 1) << @truncate(rom_sz_byte) else 0;
595616

596617
out.print(

0 commit comments

Comments
 (0)