Add Kazzo NES cartridge dumper driver#31
Draft
pathawks wants to merge 12 commits into
Draft
Conversation
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.
There was a problem hiding this comment.
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.
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds a Kazzo NES/Famicom cartridge dumper driver (
src/lib/drivers/kazzo/), reusing the sharedsystems/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:
THIRD-PARTY-LICENSESattribution. ~870 lines of tests.16c0:05dc; the connect flow picks the driver by theiProductstring at open time.PINPORT CTL_RDafter NES init): stock (M2-low) keeps the SMD172-family CPLD mappers greyed out and pre-flight-rejected; an M2-idle-high build enables them.Notes for review
inl/unsupported-mappers.tsfrom a staticUNSUPPORTED_MAPPERSmap intoM2_IDLE_GATED_MAPPERS+unsupportedMappersFor(m2IdleHigh). Since BATMAP/413 landed onmainin the interim, 413 is threaded in as a separate always-unsupported entry (gated on the unreleasedNESCPU_SPI413memtype, not M2 idle) so that an M2-idle-high firmware does not lift it. The INL driver tests assert this explicitly.usbrequest.txt, anagokazzo_request.h/reader_kazzo.c), with no GPL-2.0-only firmware/host source incorporated.tsc + eslint clean; 410 tests pass.