[][src]Crate tetanes

Summary

photo credit for background: Zsolt Palatinus on unsplash

TetaNES is an emulator for the Nintendo Entertainment System (NES) released in 1983, written using Rust, SDL2 and WASM.

It started as a personal curiosity that turned into a passion project. It is still a work-in-progress, but I hope to transform it into a fully-featured NES emulator that can play most games as accurately as possible. It is my hope to see a Rust emulator rise in popularity and compete with the more popular C and C++ versions.

TetaNES is also meant to showcase how clean and readable low-level Rust programs can be in addition to them having the type and memory-safety guarantees that Rust is known for. Many useful features of Rust are leveraged in this project including traits, trait objects, generics, matching, and iterators.

Try it out in your browser!

Screenshots

     

Mappers

Support for the following mappers is currently implemented or in development:

#NameExample Games# of Games1% of Games1
000NROMBomberman, Donkey Kong, Super Mario Bros.~24710.14%
001SxROM/MMC1Metroid, Legend of Zelda, Tetris~68027.91%
002UxROMCastlevania, Contra, Mega Man~26911.04%
003CNROMArkanoid, Paperboy, Pipe Dream~1556.36%
004TxROM/MMC3Kirby's Adventure, Super Mario Bros. 2/3~59924.59%
005ExROM/MMC5Castlevania 3, Laser Invasion~240.99%
007AxROMBattletoads, Marble Madness~753.08%
009PxROM/MMC2Punch Out!!1<0.01%
~205084.11%
  1. Source

Dependencies

Installation

This should run on most platforms that supports Rust and SDL2, howeer, it's only being developed and tested on macOS at this time. So far, I've tested on macOS High Sierra, Mojave, Windows 7, Windows 10, Linux (Fedora and Ubuntu), and Raspberry Pi 4 (though performance lacking). When 1.0.0 is released, I'll make binaries available for all major platforms. Until then, follow the below instructions to build for your platform.

  • Install Rust (follow the link)

    • If Rust is already installed. Ensure it's up-to-date by running:

      $ rustup update
      
  • Install SDL2 development libraries (follow the link)

    • Linux and macOS should be straightforward

    • Windows makes this a bit more complicated. Be sure to follow the above link instructions carefully. For the simple case of using rustup, all of the files in lib\ from the Visual C++ 32/64-bit development zip should go in:

      C:\Users\{Your Username}\.rustup\toolchains\{current toolchain}\lib\rustlib\{current toolchain}\lib
      

      Where {current toolchain} will likely have x86_64-pc-windows in its name. then a copy of lib\SDl2.dll needs to go in:

      %USERPROFILE%\.cargo\bin
      

      Next to the tetanes.exe binary.

  • Download & install TetaNES. Stable releases can be found on the Releases tab at the top of the page. To build directly from a release tag, follow these steps:

$ git clone https://raw.githubusercontent.com/lukexor/tetanes.git
$ cd tetanes/
$ git checkout v0.6.0
$ cargo install --path ./

This will install the v0.6.0 tagged release of the TetaNES binary to your cargo bin directory located at either $HOME/.cargo/bin/ on a Unix-like platform or %USERPROFILE%\.cargo\bin on Windows. Replace the release tag with the one you want to install. The latest is recommended. You can see which release tags are available by clicking the Releases tab at the top of this page or by running the following command from the checked out git repository:

$ git tag -l

Usage

For each platform, the first cd command may not be needed depending on the contents of your $PATH environment variable. filename should be replaced by the path to your game ROM ending in nes. At present, only the iNES format is fully supported, but NES 2.0 support is coming.

Windows

$ cd %USERPROFILE%\.cargo\bin
$ tetanes.exe {filename}

macOS/Linux

$ cd $HOME/.cargo/bin/
$ tetanes {filename}

Additional Options

USAGE:
    tetanes [FLAGS] [OPTIONS] [--] [path]

FLAGS:
    -c, --clear-savestate    Removes existing savestates for current save-slot
        --concurrent-dpad    Enables the ability to simulate concurrent L+R and U+D on the D-Pad.
    -d, --debug              Start with the CPU debugger enabled and emulation paused at first CPU instruction.
    -f, --fullscreen         Start fullscreen.
    -h, --help               Prints help information
    -r, --record             Record gameplay to a file for later action replay.
        --rewind             Enable savestate rewinding
        --savestates-off     Disable savestates
        --sound-off          Disable sound.
    -V, --version            Prints version information
        --vsync-off          Disable vsync.

OPTIONS:
    -g, --genie-codes <genie-codes>...    List of Game Genie Codes (space separated).
    -p, --replay <replay>                 Replay a saved action replay file.
        --savestate-slot <save-slot>      Set savestate slot #. [default: 1]  [possible values: 1, 2, 3, 4]
    -s, --scale <scale>                   Window scale [default: 3]
        --speed <speed>                   Increase/Decrease emulation speed. [default: 1.0]

ARGS:
    <path>    The NES ROM to load or a directory containing `.nes` ROM files. [default: current directory]

Controls

ButtonKeyboardController
AZA
BXB
A (Turbo)AX
B (Turbo)SY
StartEnterStart
SelectRight ShiftBack
Up, Down, Left, RightArrow KeysLeft Stick/D-Pad

There are also some emulator actions:

ActionKeyboardController
PauseEscapeGuide Button
Help Menu*F1
Configuration Menu*Ctrl-C
Open ROM*Ctrl-O
QuitCtrl-Q
ResetCtrl-R
Power CycleCtrl-P
Increase Speed 25%Ctrl-=Right Shoulder
Decrease Speed 25%Ctrl--Left Shoulder
Fast-Forward 2x (while held)Space
Set Save State Slot #Ctrl-(1-4)
Save StateCtrl-S
Load StateCtrl-L
Rewind 5 SecondsR
Stop Action Replay RecordingShift-V
Toggle Music/SoundCtrl-M
Toggle CPU DebuggerCtrl-D
Toggle FullscreenCtrl-Return
Toggle VsyncCtrl-V
Toggle NTSC FilterCtrl-N
Toggle PPU ViewerShift-P
Toggle Nametable ViewerShift-N
Take ScreenshotF10

While the CPU Debugger is open (these can also be held down):

ActionKeyboard
Step a single CPU instructionC
Step over a CPU instructionO
Step out of a CPU instructionCtrl-O
Step a single scanlineS
Step an entire frameF
Toggle Live CPU Debug UpdatingD

*: Not yet Implemented

Note on Controls

Ctrl-(1-4) may have conflicts in macOS with switching Desktops 1-4. You can disable this in the keyboard settings. I may consider changing them to something else or making macOS use the Option key in place of Ctrl, but I'm not bothering with OS-specific bindings just yet.

Directories & Screenshots

Battery-backed game data and save states are stored in $HOME/.tetanes. Screenshots are saved to the directory where TetaNES was launched from. This may change in a future release.

Powerup State

The original NES hardware had semi-random contents located in RAM upon powerup and several games made use of this to seed their Random Number Generators (RNGs). By default, the binaries and build steps for TetaNES emulate randomized powerup RAM state. This shows up in several games such as Final Fantasy, River City Ransom, and Impossible Mission II, amongst others. Not emulating this would make these games seem deterministic when they weren't intended to be.

If you would like TetaNES to provide fully deterministic emulated powerup state, you'll need to build the binary from source using the following command:

$ cargo build --release --features no-randomize-ram

Building/Testing

To build the project, ensure the dependencies are installed as outlined in the Installation section and then run cargo build or cargo build --release (if you want better framerates).

Unit and integration tests can be run with cargo test --features no-randomize-ram. RAM randomization is enabled by default since this more accurately reflects the original NES, but many tests fail under this condition so it needs to be disabled. There are also several test roms that can be run to test various capabilities of the emulator. They are all located in the tests/ directory.

Run them in a similar way you would run a game. e.g.

$ cargo run --release --features no-randomize-ram tests/cpu/nestest.nes

Debugging

There are built-in debugging tools that allow you to monitor game state and step through CPU instructions manually. See the Controls section for more on keybindings.

The Default debugger screen provides CPU information such as the statis of the CPU register flags, Program Counter, Stack, PPU information, and the previous/upcoming CPU instructions.

The Nametable Viewer displays the current Nametables in PPU memory and allows you to scroll up/down to change the scanline at which the nametable is read. Some games swap out nametables mid-frame.

The PPU Viewer shows the current sprite and palettes loaded. You can also scroll up/down in a similar manner to the Nametable Viewer. Super Mario Bros 3 for example swaps out sprites mid-frame to render animations.

  

Logging can be set by setting the RUST_LOG environment variable and setting it to one of trace, debug, info, warn or error.

Troubleshooting

If you get an error running a ROM that's using the supported Mapper list above, it could be a corrupted or incompatible ROM format. If you're unsure which games use which mappers, see here. Trying other versions of the same game from different sources sometimes resolves the issue.

If you get some sort of nasty error when trying to start a game you've played before, try passing the --clear-savestate option to ensure it's not an incompatible savestate file causing the issue. When 1.0 releases, I'll be much more careful about backwards breaking changes with regards to savestate files, but for now it's highly volatile and due to the nature of how I serialize data, I can only catch certain sorts of data inconsistencies.

If you encounter any shortcuts not working, ensure your operating system does not have a binding for it that is overriding it. macOs specifically has many things bound to Ctrl-*. There are plans to allow keybind customization, but it's not finished yet.

If an an issue is not already created, please use the github issue tracker to create it. A good guideline for what to include is:

  • The game experiencing the issue (e.g. Super Mario Bros 3)
  • Operating system and version (e.g. Windows 7, macOS Mojave 10.14.6, etc)
  • What you were doing when the error happened
  • A description of the error and what happeneed
  • Any console output
  • Any related errors or logs

When using the browser version (not yet available), also include:

  • Web browser and version (e.g. Chrome 77.0.3865)

Known Issues

See the github issue tracker.

Roadmap

The following is a checklist of features and their progress:

  • [x] Console
    • [x] NTSC
    • [ ] PAL
    • [ ] Dendy
    • [ ] Headless mode
  • [x] Central Processing Unit (CPU)
    • [x] Official Instructions
    • [x] Unofficial Instructions (Some still incorrect)
    • [x] Interrupts
  • [x] Picture Processing Unit (PPU)
    • [x] VRAM
    • [x] Background
    • [x] Sprites
    • [x] NTSC TV Artifact Effects
    • [x] Emphasize RGB/Grayscale
  • [x] Audio Processing Unit (APU)
    • [x] Pulse Channels
    • [x] Triangle Channels
    • [x] Noise Channels
    • [x] Delta Mulation Channel (DMC)
  • [x] Inputs
    • [x] Keyboard
    • [x] Standard Controller
    • [x] Turbo
    • [ ] Zapper (Light Gun)
  • [x] Memory
  • [x] Cartridge
    • [x] Battery-backed Save RAM
    • [x] iNES Format
    • [x] NES 2.0 Format (Can read headers, but many features still unsupported)
    • [x] Mappers
      • [x] NROM (Mapper 0)
      • [x] SxROM/MMC1 (Mapper 1)
      • [x] UxROM (Mapper 2)
      • [x] CNROM (Mapper 3)
      • [x] TxROM/MMC3 (Mapper 4)
      • [x] ExROM/MMC5 (Mapper 5) (Split screen and sound is unfinished)
      • [x] AxROM (Mapper 7)
      • [x] PxROM/MMC2 (Mapper 9)
  • [x] User Interface (UI)
    • [x] PixEngine (Custom graphics library for handling video and audio)
    • [x] UI Notification messages
    • [x] SDL2
    • [ ] WebAssembly (WASM) - Run TetaNES in the browser! (in progress!)
    • [x] Window
    • [ ] Menus
      • [ ] Help Menu
      • [ ] Open/Run ROM with file browser
      • [ ] Configuration options
      • [ ] Custom Keybinds
      • [ ] Recent Game Selection
    • [x] Pause
    • [x] Toggle Fullscreen
    • [x] Reset
    • [x] Power Cycle
    • [x] Increase/Decrease Speed/Fast-forward
    • [x] Instant Rewind (5 seconds)
    • [ ] Visual Rewind (Holding R will time-travel backward)
    • [x] Save/Load State
    • [x] Take Screenshots
    • [x] Toggle Action Recording
    • [ ] Sound Recording (Save those memorable tunes!)
    • [x] Toggle Sound
    • [x] Toggle Debugger
    • [x] Game Genie
    • [ ] WideNES
    • [ ] 4-Player support
    • [ ] Network Multi-player
    • [ ] Toggle individual sound channels
    • [ ] Self Updater
  • [x] Testing/Debugging/Documentation
    • [x] CPU Debugger (Displays CPU status, registers, and disassembly)
      • [X] Step Into/Out/Over
      • [ ] Breakpoints
    • [ ] Memory Hex Debugger
    • [x] PPU Viewer (Displays PPU sprite patterns and color palettes)
    • [x] Nametable Viewer (Displays all four PPU backgrounds)
      • [X] Scanline Hit Configuration (For debugging IRQ Nametable changes)
      • [ ] Scroll lines (Automatically adjusts the scanline, showing live nametable changes)
    • [x] Unit/Integration tests (run with cargo test)
      • [x] CPU integration testing (with nestest)
      • [ ] Other tests (Missing a lot here)
    • [x] Test ROMs (most pass, many still do not)
      • [ ] Automated rom tests (in progress now that action recording is finished)
    • [ ] Rust Docs
    • [ ] Logging
      • [x] Console
      • [ ] File

Documentation

In addition to the wealth of information in the docs/ directory, I also referenced these websites extensively during development:

License

TetaNES is licensed under the GPLv3 license. See the LICENSE.md file in the root for a copy.

Contact

For issue reporting, please use the github issue tracker. You can contact me directly here.

Contributing

While this is primarily a personal project, I welcome any contributions or suggestions. Feel free to submit a pull request if you want to help out!

Credits

Implementation was inspiried by several amazing NES projects, without which I would not have been able to understand or digest all the information on the NES wiki.

I also couldn't have gotten this far without the amazing people over on the NES Dev Forums:

Also, a huge shout out to OneLoneCoder for his NES and olcPixelGameEngine series as those helped a ton in some recent refactorings.

Modules

apu

Audio Processing Unit

bus
cartridge

Handles reading NES Cartridge headers and ROMs

common

Utils and Traits shared among modules

cpu

A 6502 Central Processing Unit

filter

Contains the Filter Trait for both High Pass and Low Pass filters

input

NES Controller Inputs

mapper

NES Memory Mappers for Cartridges

memory

Memory types for dealing with bytes

nes

User Interface representing the the NES Control Deck

ppu

Picture Processing Unit

serialization

Serialization/Deserialization for internal game state

Macros

hashmap
map_nes_err
nes_err

Structs

NesErr

Type Definitions

NesResult