An NES emulator written in Rust
Official release artifacts are attached to each GitHub Release:
| Artifact | Platform |
|---|---|
nerust-vX.Y.Z-linux-x86_64.tar.gz |
Linux x86_64 |
nerust-vX.Y.Z-linux-aarch64.tar.gz |
Linux aarch64 |
nerust-vX.Y.Z-macos-aarch64.app.zip |
macOS aarch64 |
nerust-vX.Y.Z-android-arm64-v8a.apk |
Android arm64-v8a |
Each tarball contains nerust_tao (the Tao frontend binary), README.md,
and LICENSE. The Android artifact is a signed APK. Each artifact has a
matching .sha256 sidecar. The macOS bundle is ad-hoc signed and not
notarized.
The official desktop frontend is Tao (nerust_tao). The Android frontend
ships as an arm64-v8a APK. The GTK4 frontend (nerust_gtk) is maintained for
build-health but is not a release artifact.
Releases are prepared through the long-lived release branch.
- Create the
releasebranch once before the first release candidate. - Run the Prepare release candidate workflow from
master. - The workflow refreshes the
release-candidatebranch, updates the release version inCargo.toml, ensures the matchingCHANGELOG.mdsection exists, and opens or updates a PR intorelease. - The PR to
releasepublishes versioned workflow artifacts so the exact binaries can be reviewed before release. The release metadata logic lives inpackaging/release/release_flow.sh. - Merging that PR into
releasecreates thevX.Y.Ztag and GitHub Release. - The automation then opens a follow-up PR from
releaseback tomasterso the released version and changelog stay in sync. - Merge that sync PR before preparing the next release candidate.
The Release artifacts workflow also validates artifact creation for PRs into
master when release automation, workflow, packaging, or artifact-input files
change. Those runs build the same artifacts with a validation-only tag suffix
and never publish assets or open sync PRs.
The automation reads the major version from [workspace.package].version in
Cargo.toml. If there is already a v<major>.* tag, it bumps:
- patch for workflow, packaging, docs, lockfile, and dependency-only
Cargo.tomlupdates - minor for everything else
If there is no existing tag for the current major, the declared
[workspace.package].version becomes the release version. The workflows use
the built-in GITHUB_TOKEN for branch, release, and PR updates. Because GitHub
marks automation-created pull request workflows as approval-required when they
come from GITHUB_TOKEN, release-candidate PR checks may need an explicit
Approve workflows to run action in the PR UI.
Android signing uses ANDROID_CERTIFICATE and ANDROID_PRIVATE_KEY. If the
private key is encrypted, also set ANDROID_PRIVATE_KEY_PASSWORD.
- The default workspace developer path (
cargo build,cargo test) now coversnerust_core,nerust_persistence, andnerust_console. - Their in-workspace dependencies still build transitively, but GUI frontends, backend-specific crates, and ROM tooling are now validated with explicit package commands.
cargo test -p nerust_core persistence_tests --lib
cargo test -p nerust_console --lib
cargo test -p nerust_persistence --libRun support-crate unit tests explicitly when touching cartridge parsing, filters, buffers, or timing:
cargo test -p nerust_cartridge_data --lib
cargo test -p nerust_screen_buffer --lib
cargo test -p nerust_screen_filter --lib
cargo test -p nerust_timer --libRun ROM tooling and generated regression tests explicitly when touching manifest,
tooling, or ROM-test behavior. The manifest lives at rom_test/rom_tests.yaml.
cargo test -p nerust_rom_test --releaseRun frontend and backend validation explicitly when touching OpenGL or UI code:
cargo test -p nerust_screen_opengl --lib
cargo test -p nerust_gui_runtime --lib
cargo build -p nerust_android
cargo build -p nerust_gtk --release
cargo build -p nerust_tao --releaseBuild the Android APK with the Gradle packaging project:
packaging/android/package.shThis requires Java 17, the Android SDK/NDK, and cargo-ndk on the host.
If ANDROID_CERTIFICATE and ANDROID_PRIVATE_KEY are exported, the packaging
script generates a temporary JKS keystore automatically before Gradle signs the
release APK.
ROM import on Android uses the system document picker (ACTION_OPEN_DOCUMENT)
and persists the user-selected URI grant, so no broad storage permission is
declared in the manifest.
Rust and Kotlin Android logs share the Nerust tag. When the app closes
immediately on a device, clear logcat, start a filtered capture, then launch the
app:
adb logcat -c
adb logcat -v threadtime -s NerustKeep the capture running until the app closes so JNI_OnLoad, android_main,
lifecycle, popup attach, renderer resume, and panic breadcrumbs are included in
the output.
The Tao frontend is the official release target with wgpu-based rendering.
- Cargo + Rust
- Linux: GTK3 development headers (
libgtk-3-dev), OpenAL (libopenal-dev) - macOS: no additional system packages required
cargo build -p nerust_tao --releasetarget/release/nerust_tao [Rom File Path]Launch without arguments and use File → Open to load a ROM.
Note: GTK4 is maintained for build-health but is not an official release artifact. Use the Tao frontend for distribution.
- Cargo + Rust
- GTK 4.0 or greater (
libgtk-4-dev), OpenAL (libopenal-dev)
cargo build -p nerust_gtk --releasetarget/release/nerust_gtkROM regression cases are defined in rom_test/rom_tests.yaml, with
NESdev-style categories and short descriptions for each case.
# Run all ROM regression cases (requires ROM assets under roms/)
cargo test -p nerust_rom_test --release
# Validate configured ROM cases with an HTML report in target/rom-tests/validate/
cargo run -p nerust_rom_test --bin rom_tool -- validate
# Capture actual hashes/screenshots for a specific case
cargo run -p nerust_rom_test --bin rom_tool -- capture --case cpu.nestest
# Benchmark perf-enabled ROM cases
cargo run -p nerust_rom_test --bin perf --release -- --case cpu.nestestnerust_coreownsPERSISTENCE_SCHEMA_VERSION,MachineStatePayload,MapperSavePayload, and the nestedRomIdentity/CoreOptionschecks used during import.nerust_consoleownsCONSOLE_STATE_SCHEMA_VERSION,ConsoleStatePayload,ControllerStatePayload, and thepaused/frame_counter/source_framewrapper fields around opaque core state bytes.nerust_persistenceownsSTATE_ARCHIVE_SCHEMA_VERSION,StateArchiveMetadata, archive entry names, slot filtering, and thumbnail presence/blob handling;state.binremains opaque console state.- Nested payloads without their own version are covered by the
nearest owning outer schema version. For example, changing
controller representation bumps
CONSOLE_STATE_SCHEMA_VERSION, while changingRomIdentityorCoreOptionscomparison semantics bumps the owning core/archive schema and corresponding reject/filter tests. - Field addition, removal, type changes, or meaning changes that affect accepted bytes are schema changes. Bump the owning version constant before refactoring those fields.
- After merge to
master, payloads produced by the shipped schema versions must not break silently. Either keep them loadable with explicit compatibility tests, or intentionally reject them behind a version bump with explicit reject tests.
Schema change workflow:
- Identify the owning layer (
core,console, orpersistence). - Decide whether the change alters accepted bytes, target comparison, or archive interpretation.
- Bump the owning schema version when compatibility changes.
- Update the representative fixtures plus compatibility/reject tests for that layer.
- Confirm how previously shipped
masterpayloads are handled before starting the refactor.
- NRom (Mapper 0)
- MMC1 SxRom (Mapper 1)
- UxRom (Mapper 2)
- CnRom (Mapper 3, Mapper 185)
- MMC3 / MMC6 (Mapper 4)
- MMC5 (Mapper 5)
- AxRom (Mapper 7)
- BnRom (Mapper 34)
- NINA-001 (Mapper 34)
- TxSROM (Mapper 118)
- Load & Save
- Android support
- Other Mappers
- Network multiplay