Skip to content

RustyNES v3.7.0 — "Spindle" (the Famicom Disk System on the MiSTer core, spectators through the hosted relay, desync forensics and live ROM patching)

Latest

Choose a tag to compare

@github-actions github-actions released this 11 Nov 13:58
9c62143

RustyNES v3.7.0 — "Spindle"

A minor release on v3.6.2 "Latchkey". The MiSTer core gains the Famicom Disk System. Netplay gains spectators who join a match through the hosted relay, and an opt-in report of where a desync began. The debugger edits the ROM while a game runs and records every edit as a BPS patch. Both mobile apps gain the desktop's equalizer, code database and correction switch. Netplay peers on v3.4.x to v3.6.x are refused (protocol 10), with a reason naming their version. The emulation epoch stays 10: v3.6.2 movies replay unchanged, and v3.6.x save states load.

Netplay

  • Spectators through the hosted relay (T-NETPLAY-EDGE-SPECTATE).
    • How it works. A host publishes its match's confirmed inputs to a relay at the network edge, one Cloudflare Durable Object per match. Up to 200 viewers can watch each match. Every viewer runs the game locally from that stream, so a viewer can join from any point (the relay keeps the log from frame 0) and can seek, rewind and pause.
    • What the relay carries: confirmed inputs only, never frames or states. It enforces per-address and daily limits, and the operator has a kill switch.
    • On the desktop: the netplay panel publishes a hosted match under a relay match id, and watches one by that id. It has no seek or rewind buttons yet, though the client supports both. The mobile apps have no spectating UI yet.
    • Who can watch: anyone who has the match id. Viewers present no key, so leave the match id empty when you host: the desktop then picks a random one that cannot be guessed and shows it in the status bar for you to pass on. A short id you type yourself works too, and the panel warns that it can be guessed.
    • Availability. Live since 2026-11-11, deployed from this release's merge 9c62143c. Against the live endpoint: a viewer that joined after 300 confirmed frames matched the players for all 300 (edge_live, 1 passed); every relayed message was an input or a sync, never a frame or a state; and with the kill switch off, viewers were refused (HTTP 503) until it was switched back on. If the relay is unreachable or switched off, publishing or watching says so, and the match itself is unaffected.
  • Desync forensics, opt-in on each peer (T-NETPLAY-DESYNC-FORENSICS).
    • What you get. When two peers drift apart, the netplay panel names the frame, the part of the console (CPU, bus, mapper, PPU or APU), the first byte that differs (with its CPU address for RAM), both peers' values, and how many bytes differ.
    • How. Opted-in peers exchange a digest of each save-state section for every confirmed frame, then only the sections that differ. That finds a divergence the gameplay checksum cannot see, such as a RAM byte that has not reached the picture yet. A peer that has not opted in sends no state.
    • Turn it on in the netplay panel or with [netplay] desync_forensics.
  • Protocol 10 (handshake "RN10"). The two features above add messages an older peer cannot read, so a v3.4.x to v3.6.x peer is refused at the handshake rather than connecting and failing later. Both players need v3.7.0.

ROM hacking

  • Live ROM editing. The debugger's Memory panel gains PRG-ROM and CHR-ROM views, by offset in the ROM file, whose bytes can be edited while the game runs; the change shows on the next frame.
    • Recorded as a patch. Every edit is recorded, and Save patch... writes them as a BPS patch of the clean ROM. Enabled raw RAM cheats and memory freezes go into a .pins.toml sidecar that the patch names by file name and SHA-256. The BPS writer follows the format's public specification.
    • Refused when it would break something: in RetroAchievements hardcore, while a movie records or plays, while TAStudio is open, and during netplay, each with the reason shown.
    • Recording, playback and netplay wait for a clean ROM. A movie, a TAStudio session and a netplay session all name the clean ROM while the console would run the edited one, so none can start while an edit exists: recording or playing a movie, opening TAStudio, and hosting, joining or watching a match. Save the patch and load the patched ROM, or press Reset edits to put the clean bytes back. RAM pins alone do not block any of them.
    • Supported boards: 204 mapper numbers for PRG and 175 for CHR. Boards whose "ROM" is RAM or a flash save are not: CHR-RAM boards, GTROM, flashable UNROM 512 and the FFE copiers. Nor are FDS disks and NSF files.
    • Save states carry no ROM, so a state saved on a patched session runs the clean game when loaded on the clean ROM.
  • rustynes patch-regress <ROM> <PATCH> <MOVIE> [--pins] replays a reference movie on the clean and the patched ROM. It reports the first frame where they differ and why: a RAM address with both values, else a pixel, else the cycle count. A patch recorded with RAM pins is refused without its sidecar.
  • See docs/live-patch-recorder.md.

Mobile

  • Both apps:

    • the desktop's 20-band graphic equalizer (a flat setting leaves the audio bit-identical);
    • its Game Genie code database, about 10,800 codes for some 520 games. On Android this is a new Game Genie sheet, since the app had no cheat screen; on iOS it is a "Code database" section of Cheats;
    • a switch for the per-game picture corrections (ADR 0051), as on the desktop and in RetroArch.

    The bridge compiles the desktop's own code for the equalizer and the database, so a fix reaches all three.

  • iOS:

    • box art from libretro-thumbnails, fetched only when you ask;
    • a home-screen widget that reopens the last game;
    • the game on an external display (iOS 16 to 26), with the phone as the controller. On iOS 27 the display mirrors the phone.
  • Android:

    • when RetroAchievements hardcore stops a game from resuming, the status bar now says so. Before, the game restarted with no word why;
    • with the GPU renderer, the picture is no longer converted for the CPU every frame, which saves work on each frame;
    • save-state slots get live thumbnails;
    • the library tile menu opens from a TV remote;
    • a folder import is bounded at eight levels (it had no bound at all).
  • Not yet verified on a device:

    • The new Swift compiles: the iOS workflow's Simulator build on the release branch built the app and the widget extension, unsigned. No device has run it.
    • The widget needs an App ID and App Group registered before a signed build.
    • The Android frameskip saving is unmeasured until a device runs it.
    • Zero-copy frames were planned here, but wait for UniFFI 0.33, which is unreleased.

Fixed

  • A Vs. DualSystem cabinet's save state now round-trips exactly under CPU overclock. At x2, a restored state drifted from the uninterrupted run in 64 bytes, the OAM-decay ages. The decay is off by default, so play was unaffected, but anything comparing state checksums, such as RetroArch netplay, saw a desync. The state now carries the decay clock's phase in two bits that were always zero, so v3.6.x states still load.

Builds and tools

  • cargo full-native, cargo full-v3 and cargo full-baseline build the full-featured desktop binary for this CPU, for any x86-64-v3 PC, and for the baseline the releases ship.
    • Measured, and no faster. scripts/perf/cpu_target_ab.sh timed all three under the project's evidence rule (two runs, a balanced order). Neither is faster than the baseline on the maintainer's i9-10850K: v3 was 0.5 to 1.6% slower on five of six readings, and native was mixed in sign. The release binaries stay on the baseline. The table is in docs/performance.md.
  • The ROM-library scripts (scripts/coverage/, scripts/mapper-promotion/) read their library root from RUSTYNES_ROM_LIBRARY (default ~/roms/nes).
  • No tracked file names a developer's home directory any more, and a new audit fails on the next one.
  • The MCP server moves to rmcp 3.9.0 for RUSTSEC-2026-0462 (an unbounded response body in rmcp's HTTP clients). The server compiles neither client, so it was never exposed, but the dependency audits flagged the version.

The MiSTer core

  • The Famicom Disk System, on the off-die build (Phase F4b, mapper 20). The Lite (on-die) build carries no FDS.
    • Written from documentation: the NESdev wiki and Brad Taylor's FDS reference, then pinned by comparing outputs with this emulator's. Its FDS source was not read.
    • No Nintendo BIOS is needed or included. Every test boots a synthetic BIOS that is itself the test program.
    • Loading: load a .fds disk image and the FDS BIOS from the OSD (two new loaders, on their own indices), and pick the disk side there.
    • Saving: disk writes persist through the MiSTer's storage (a <game>.sav beside the image), written back when the OSD opens. The disk LED lights while the drive transfers.
    • Audio: the FDS wavetable channel with its modulator.
  • Two save-engine races fixed.
    • A ROM download on a request's edge. The engine dropped a save or load request issued on the clock edge a ROM download began, with no completion. Downloads now abandon only a request already under way, on their own clock. The FDS image and BIOS loaders count as downloads.
    • An abandoned request kept moving the chain. A request abandoned while it waited on the DDR3 mover shifted one bit of the chain it was told to drop. It now stops at once.
    • Each race has its own stimulus, and each stimulus catches the old wiring.
  • The NMI on a page-crossing branch. A new stimulus, mapper5nmivbl547, finds the two cores taking an NMI one instruction apart when it arrives during a taken branch that crosses a page. The NESdev page can be read either way, so neither core is changed. The stimulus is recorded as an expected failure, so a change on either side will show.
  • Where the NESdev pages and this emulator's FDS differ. The MiSTer work measured ten such places, including open bus at $402x, the stored CRC, and when audio gain changes take effect. The MiSTer core follows the pages, and the tests avoid all ten. Each will be checked against test ROMs or hardware evidence in v3.8.0 (T-FDS-WIKI-DIVERGENCE). Until then, this emulator's FDS is unchanged.
  • Save-state layout fingerprints: C76E97A8 off the die (5,648 chain bits; the FDS chain is new) and BEE10697 on the die (4,098, unchanged).
  • Co-simulation ladders: 355 passed, 0 failed, 4 expected failures on the die and 421 / 0 / 12 off it (USE_SDRAM=1), each one run with nothing skipped, on the FDS branch's RTL and again on the release RTL with the co-simulation pin at this release's merge (the same counts both times), with golden provenance at 292 of 292 stems. The new expected failures, both off the die, are fdsenvelope542 (the FDS audio rows above) and mapper5nmivbl547 (the NMI at a page-crossing branch). Every expected failure is graded both ways: it fails the suite if it starts passing.
  • New bitstreams, attached below: compiled from the MiSTer core's release RTL, with its co-simulation pin at this release's merge 9c62143c, at fitter seed 15 and build date 2026-11-12. Each file is byte-identical across two clean compiles.
    • Replaced 2026-11-12. The first pair, attached 2026-11-11 (off-die md5 509fe2d9…, on-die 14b942a0…), was withdrawn after review of the MiSTer core's release found defects in its FDS. Most slow volume and modulation envelopes never stepped, so FDS music sounded wrong. A 1 MiB cartridge loaded after the BIOS overwrote the BIOS. The drive did not fix a read's block at its first transfer. And the disk-save path could miss a disk being mounted, or hang after an aborted save. Those defects were in the off-die file only, since the on-die file has no FDS. Both files were rebuilt because the fixes changed RTL both builds share, and seeds 1-16 were swept again: both builds close only at 13 and 15, and 15 has the most margin. If you downloaded a file on 2026-11-11, download it again.

    • RustyNES_MiSTer-v3.7.0.rbf (off-die, with the FDS): md5 e14319ac558826ee804bd21a744879e9, setup +0.278 / hold +0.114 ns over all four corners, 74% of the FPGA's logic.

    • RustyNES_MiSTer-v3.7.0-lite.rbf (on-die): md5 10d22d28e1183a00664de865a4a78c59, setup +0.235 / hold +0.109 ns, 70% of the logic.

    • Known issues (off-die file only; to be fixed in v3.7.1, not yet released). These were found after these files were built.

      • A disk write that runs past the end of a side lands in the next side's bytes.
      • A console reset before you open the OSD loses an unsaved disk write: open the OSD after a game saves, before resetting.
      • (Withdrawn 2026-11-12: "downloading the BIOS while a disk save is running can hang the save" was listed here in error. Simulation shows the save resumes and finishes after the download.)
      • Added 2026-11-13: three further drive edge cases raised by review (T-MISTER-FDS-DRIVE-EDGES) are real. A write that follows a read paused inside a block drops part of the block. A write right after the disk motor restarts can start before the drive is ready. Loading or writing back the .sav can hang while a game has the motor stopped. (These replace the line that said the edge cases were not yet verified.)

      Licensed software is not known to trigger the first.

    • Known issue (both files, and every earlier MiSTer release; to be fixed in v3.7.1, not yet released). Added 2026-11-12. A console reset (the OSD's Reset) before you next open the OSD loses a cartridge's unsaved battery save: the save is still in memory, but the reset clears the core's note that it changed, so opening the OSD writes nothing. Open the OSD after a game saves, before resetting.

  • Release candidate, not hardware-verified: no hardware has run any bitstream.

Verification

  • The full suite with test ROMs and the local commercial corpus: 4,129 passed, 0 failed, 20 ignored, in 206 binaries. No golden, snapshot or commercial-ROM frame moved, so the emulation epoch stays 10.
  • Every new gate was mutation-checked: a mutant of each fails it.
  • Lint and builds: clippy is clean for the workspace, every frontend feature set and both WebAssembly builds. The no_std core build and the documentation build pass.

Install

  • Download the pre-built binaries for Linux, macOS and Windows below.
  • The MiSTer bitstreams, a release candidate and not hardware-verified, are attached below (see "The MiSTer core"). Copy RustyNES_MiSTer-v3.7.0.rbf (off-die, needs the SDRAM add-on; it has the FDS) or RustyNES_MiSTer-v3.7.0-lite.rbf (on-die) to the MiSTer's _Console folder.
  • The WebAssembly build is live at doublegate.github.io/RustyNES.
  • The RetroArch core is in RetroArch's Online Updater on the platforms the libretro buildbot publishes to.
  • Licensed under GPL-3.0-or-later.