Skip to content

Add Kazzo NES cartridge dumper driver#31

Draft
pathawks wants to merge 12 commits into
mainfrom
kazzo-nes-pr
Draft

Add Kazzo NES cartridge dumper driver#31
pathawks wants to merge 12 commits into
mainfrom
kazzo-nes-pr

Conversation

@pathawks

Copy link
Copy Markdown
Owner

Draft. The driver was hardware-validated earlier (NROM + MMC3 over WebUSB), but this rebased-onto-main form — which reconciles the M2-idle-gating with the BATMAP/413 work that landed since — has not been re-run on hardware. Holding as draft until a validation pass.

What

Adds a Kazzo NES/Famicom cartridge dumper driver (src/lib/drivers/kazzo/), reusing the shared systems/nes/mappers/ layer. Kazzo is naruko's AVR dumper (circa 2010); AVR-based INL Retro boards can be reflashed with Kazzo firmware and driven via this protocol as an alternative to the INL dictionary protocol.

The 5-commit series:

  1. Add Kazzo NES cartridge dumper driver — WebUSB device wrapper, transport, opcodes, NES bus, mirroring detect, mapper-support gating, plus THIRD-PARTY-LICENSES attribution. ~870 lines of tests.
  2. Disambiguate INL Retro and Kazzo on their shared USB ID — both firmwares use V-USB's 16c0:05dc; the connect flow picks the driver by the iProduct string at open time.
  3. Kazzo: batch MMC1 serial loads; enable mappers 268 and 470.
  4. INL: gate mappers 268/470 on the firmware's M2 idle level — feature-detects M2 idle (one PINPORT CTL_RD after NES init): stock (M2-low) keeps the SMD172-family CPLD mappers greyed out and pre-flight-rejected; an M2-idle-high build enables them.
  5. Kazzo: gate mappers 268/470 on the firmware's M2 idle era — same gating, Kazzo-side, via a firmware-era probe.

Notes for review

  • 413/M2 reconciliation (the rebase seam). Commit 4 restructures inl/unsupported-mappers.ts from a static UNSUPPORTED_MAPPERS map into M2_IDLE_GATED_MAPPERS + unsupportedMappersFor(m2IdleHigh). Since BATMAP/413 landed on main in the interim, 413 is threaded in as a separate always-unsupported entry (gated on the unreleased NESCPU_SPI413 memtype, not M2 idle) so that an M2-idle-high firmware does not lift it. The INL driver tests assert this explicitly.
  • License attribution states the protocol was reimplemented from documented interface facts (unagi_kazzo usbrequest.txt, anago kazzo_request.h/reader_kazzo.c), with no GPL-2.0-only firmware/host source incorporated.
  • README device-table row intentionally left out pending the hardware-validation pass.

tsc + eslint clean; 410 tests pass.

pathawks added 5 commits June 13, 2026 16:53
Kazzo is naruko's AVR + V-USB NES dumper (anago host); AVR-based INL
Retro boards can be reflashed with its firmware. It shares the INL's
16c0:05dc VID/PID, disambiguated by the "kazzo" product string.

The driver reuses the shared, device-agnostic NES mapper catalog through
a thin NesBus adapter: per-byte 6502/PPU read/write requests over vendor
control transfers, 256-byte page reads, and the XOR-0xA5 write masking
the firmware expects. Mappers 268/470 are pre-flight-rejected (same
CPLD-refusal family as on the INL). Reads only: every flash/firmware
program or erase request is refused at the device layer.

Protocol constants are reimplemented from the documented USB protocol,
not ported from the GPL-2.0-only firmware.

Not yet hardware-validated: the dump path is exercised only by fakes.
Both devices enumerate as 16c0:05dc; matching on VID/PID alone let one
physical device claim both connection entries, and page-load auto-reconnect
would run the INL driver against kazzo firmware (dictionary error 0xff).
DeviceDef gains an optional usbProduct substring: a def that declares one
requires it in the USB product string, and a def without one acts as the
catch-all only when no sibling claims the device.
writeSerialRegister sends all five serial bits in one CPU_WRITE_6502
transfer (the firmware writes each payload byte to the register address),
matching the reference host's MMC1 routine - more robust than five
separate USB writes for the stateful shift-register load.

Both CPLD reissue mappers are hardware-validated byte-perfect on this
driver against references: the inherited unsupported inference from the
INL classification does not transfer to this firmware's bus behavior.
The SMD172-family CPLD boards require M2 to idle high between bus
operations; sustained M2-low reads as console-off and register writes are
reverted (reads unaffected). Stock INL firmware idles M2 low, so these
mappers cannot dump on it - but an M2-idle-high firmware build can,
hardware-verified byte-perfect on a 2 MiB mapper-268 cart with an MMC3
regression pass on the same build.

The driver now feature-detects the connected firmware: one PINPORT CTL_RD
of the M2 pin right after NES init (present in stock firmware too). Low or
probe error keeps 268/470 pre-flight-rejected and greyed out; high enables
them. M2_IDLE_GATED_MAPPERS is the single source of gated ids.
The CPLD reissue boards need M2 idling high; only some Kazzo firmware
does. The driver classifies the connected build once per session from its
FIRMWARE_VERSION fingerprint: pre-2010-01-25 version strings (kazzo16
0.1.0-0.1.2) and the self-identifying m2-idle-high branch (kazzo16
0.1.3+m2) idle high and enable the mappers; 0.1.3 and later, unknown
strings, blank (all-0xFF) version sections, short reads, and transfer
errors all fail safe to gated, with reason strings naming the capable
builds. A blank version section is deliberately not treated as an
identity even though the historical clipped distribution it matches is
capable in practice.

The version read is the benign FIRMWARE_VERSION request only; the device
layer hard-rejects the adjacent self-flash opcodes in both transfer
directions.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds first-class support for the Kazzo NES/Famicom cartridge dumper (and INL Retro boards reflashed with Kazzo firmware) by introducing a new WebUSB driver that reuses the existing shared NES mapper catalog, and by updating connection logic to disambiguate Kazzo vs INL on their shared V-USB VID/PID.

Changes:

  • Added a new Kazzo driver stack (KazzoDevice + KazzoTransport + KazzoNesBus + KazzoDriver) with firmware-era classification to gate M2-idle-sensitive mappers (268/470) and comprehensive unit tests.
  • Refactored INL unsupported-mapper handling into M2-idle gating + “always unsupported” (BATMAP/413) and added an initialize-time M2 idle probe.
  • Updated WebUSB device matching to disambiguate shared VID/PID devices by iProduct substring, and updated NES mapper documentation comments and third-party license attributions.

Reviewed changes

Copilot reviewed 24 out of 24 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
THIRD-PARTY-LICENSES Adds attribution/clarification for Kazzo/anago protocol reimplementation sources.
src/lib/systems/nes/mappers/inx007t.ts Updates mapper 470 notes to reflect Kazzo validation and INL M2-idle gating.
src/lib/systems/nes/mappers/index.ts Updates mapper catalog comments for 268/470 INL M2-idle gating notes.
src/lib/systems/nes/mappers/coolboy.ts Updates CoolBoy hardware caveat text to focus on M2-idle-high requirement.
src/lib/drivers/kazzo/unsupported-mappers.ts Introduces Kazzo-side M2-idle-gated mapper list + session map builder.
src/lib/drivers/kazzo/kazzo-transport.ts Adds Transport adapter for Kazzo control-transfer protocol.
src/lib/drivers/kazzo/kazzo-opcodes.ts Defines Kazzo USB protocol constants/opcodes and safety guard set.
src/lib/drivers/kazzo/kazzo-nes-bus.ts Implements NesBus adapter mapping mapper primitives to Kazzo requests (incl. MMC1 batching).
src/lib/drivers/kazzo/kazzo-nes-bus.test.ts Unit tests for KazzoNesBus call-shaping and abort/progress behavior.
src/lib/drivers/kazzo/kazzo-driver.ts Implements Kazzo DeviceDriver: init, firmware classification, mapper gating, ROM/save dumping.
src/lib/drivers/kazzo/kazzo-driver.test.ts Driver-level tests exercising mapper catalog integration, gating, detect, abort, save read path.
src/lib/drivers/kazzo/kazzo-device.ts Adds WebUSB wrapper providing chunked CPU/PPU reads, masked writes, version/mirroring probes.
src/lib/drivers/kazzo/kazzo-device.test.ts Unit tests for KazzoDevice control-transfer behavior, chunking, masking, guards, caching.
src/lib/drivers/kazzo/firmware-m2.ts Adds Kazzo firmware fingerprint classifier to decide M2-idle-high gating.
src/lib/drivers/kazzo/firmware-m2.test.ts Tests for firmware classifier fail-safe behavior and known fingerprints.
src/lib/drivers/kazzo/detect-mirroring.ts Adds Kazzo mirroring probe helper (vertical vs horizontal).
src/lib/drivers/inl/unsupported-mappers.ts Refactors unsupported mappers into M2-idle gated vs always-unsupported (413), adds helper.
src/lib/drivers/inl/inl-opcodes.ts Adds PINPORT.M2 operand constant and updates comments referencing new gate map.
src/lib/drivers/inl/inl-driver.ts Probes M2 idle level during initialize and applies unsupported mapper set dynamically.
src/lib/drivers/inl/inl-driver.test.ts Extends tests to cover M2-idle-high probe behavior and gating semantics.
src/lib/core/devices.ts Adds usbProduct discriminator and a Kazzo device definition entry.
src/lib/core/connection-registry.ts Registers Kazzo transport/driver in the centralized connection registry.
src/hooks/use-connection.ts Implements webusbMatches to disambiguate shared VID/PID by iProduct substring; uses it in probing/authorized lookup.
src/hooks/use-connection.test.ts Adds unit tests ensuring Kazzo vs INL matching behavior for shared VID/PID.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/lib/drivers/kazzo/kazzo-device.ts
Comment thread src/lib/drivers/kazzo/firmware-m2.test.ts Outdated
pathawks added 7 commits June 14, 2026 06:55
Brings in INL's flash-program opcode guard (NES_FLASH_WRITE_OPCODES with
8-bit opcode normalization). Resolved the inl-opcodes.ts conflict on the
MMC3_PRG_FLASH_WR comment by unioning both threads: main's flash-program
guard framing plus the branch's M2-burst tail-poll-spin caveat and the
M2_IDLE_GATED_MAPPERS cross-reference.
A physical unplug skips transport.disconnect() (the hook sees connected
=== false once handleDisconnect nulls the device), so the device-initiated
path must tear down the same state the explicit disconnect() does. Both
device wrappers now drop the global USB "disconnect" listener in
handleDisconnect (and Kazzo also clears its firmware-version caches so a
stale classification can't outlive the device).

Also correct the firmware-m2 test header: the all-0xFF clipped build is
gated (capable in practice, but a blank version section is not an
identity), not a fingerprint that opens the gate — matching the
implementation and the first test case.
Rename FLASH_WRITE_REQUESTS to REFUSED_REQUESTS and add the two flash-probe
opcodes (FLASH_DEVICE drives JEDEC command cycles onto the cart bus;
FLASH_STATUS is its companion poll) — a pure read-only dumper issues none of
these. The guard test now iterates the live set and pins its exact
membership, so coverage can't silently drift behind it.

readChunked rejects a region past the bus address ceiling (64 KiB CPU /
8 KiB PPU) instead of letting the 16-bit wValue alias it to low addresses
and reassemble a wrong-but-plausible dump. controlIn throws on a short read
rather than fabricating empty data — a failed VRAM probe was being reported
as 'horizontal' instead of surfacing the error. The echo() doc no longer
claims it is a connectivity handshake (it is an unused opcode primitive).
readSave resolved the mapper with a bare getNesMapper and never consulted
the unsupported-mapper set, so the M2-idle / always-unsupported gate that
readROM enforces was skipped for save dumps. Extract resolveMapper (a pure
pre-flight check, no device I/O) and call it from both paths so a gated
mapper is rejected up front on either dump, matching the Kazzo driver.

InlNesBus.writeCpu and writeSerialRegister now check the abort signal, so an
abort lands promptly mid-write-run instead of only at the next read —
matching KazzoNesBus's per-operation abort contract.
The interactive chooser can only filter by VID/PID, and the Kazzo and INL
Retro share 16c0:05dc, so a user can pick the sibling unit and run one
driver against the other's firmware (an opaque 0xff on the first opcode).
After the chooser resolves, verify the opened device's product string
matches the selected def (via the extracted matchesUsbProduct) and fail
fast with an actionable message instead.

A device-initiated disconnect can also fire mid-initialize, before any
driver is published: handleDisconnect now bails when there's no published
driver (no phantom 'Disconnected' / premature per-session clear), and
connectDevice won't publish a driver over a transport that closed during
initialize().
The mapper-268 dump test asserted only output length; it now also checks the
walk drove the outer registers ($5000 bank-select writes) and read each bank
under consensus (>=2 reads of the same window), not a single trusting read.

Add a mid-dump abort test proving the signal threads driver -> KazzoNesBus ->
device (the CHR read never issues after the PRG read aborts), and exercise
the two readSave branches the default-path test skipped: dumpSave (MMC3) and
enableSram (MMC1).
The 268/470 comments described only the INL runtime M2-idle probe. Both
drivers gate these mappers on M2-low firmware — INL by feature-detecting the
pin level at init, Kazzo by fingerprinting the firmware version — so the
comments now point at M2_IDLE_GATED_MAPPERS in each driver's
unsupported-mappers.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated no new comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants