A work-in-progress NES emulator with a real-time rewind feature that correctly reverses sound.
Some other cool features are planned :). Still lacks a GUI and persistent (on-disk) save states.
See this video for a demonstration of rewinding. The channel has some other videos as well.
SDL2 is used for the final output and is the only dependency. You currently need a *nix system.
The only *nix/POSIX dependencies are the timing functions in src/timing.cpp, which should be trivial to port. A quick-and-dirty experimental port to Windows has already been done by miker00lz, but contributions are welcome. One GCC extension (case ranges) is used currently.
Commands for building on Ubuntu:
$ apt-get install libsdl2-dev
$ make CONF=release
Parallel builds (e.g., make CONF=release -j8
) are supported too.
See the Makefile for other options. The built-in movie recording support has sadly bitrotted due to libav changes.
$ ./nes <ROM file>
Controls are currently hardcoded (in src/input.cpp and src/sdl_backend.cpp) as follows:
D-pad | Arrow keys |
A | X |
B | Z |
Start | Return |
Select | Right shift |
Rewind | R (hold down) |
Save state | S |
Load state | L |
(Soft) reset | F5 |
The save state is in-memory and not saved to disk yet.
Uses a low-level renderer that simulates the rendering pipeline in the real PPU (NES graphics processor), following the model in this timing diagram that I put together with help from the NesDev community. (It won't make much sense without some prior knowledge of how graphics work on the NES. :)
Most prediction and catch-up (two popular emulator optimization techniques) is omitted in favor of straightforward and robust code. This makes many effects that require special handling in some other emulators work automagically. The emulator currently manages about 6x emulation speed on a single core on my old 2600K Core i7 CPU.
The current state is appended to a ring buffer once per frame. During rewinding, states are loaded in the reverse order from the buffer. Individual frames still run "forwards" during rewinding, but audio is added in reverse from the end of the audio buffer instead of from the beginning. Getting things to line up properly at frame boundaries requires some care.
A thirty-minute rewind buffer uses around 1.3 GB of memory for most games. There's no attempt to compress states yet, so a lot of memory is wasted. The length of the rewind buffer can be set by changing rewind_seconds in src/save_states.cpp and rebuilding.
iNES mappers (support circuitry inside cartridges) supported so far: 0, 1, 2, 3, 4, 5 (including ExGrafix, split screen, and PRG RAM swapping), 7, 9, 10, 11, 13, 28, 71, 232. This covers the majority of ROMs.
Supports tricky-to-emulate games like Mig-29 Soviet Fighter, Bee 52, Uchuu Keibitai SDF, Just Breed, and Battletoads.
Supports both PAL and NTSC. NTSC ROMs are recommended due to 10 extra FPS and PAL conversions often being half-assed. PAL roms can usually be recognized from having "(E)" in their name. (This is often the only way for the emulator to detect them without using a database, as very few ROMs specify the TV system in the header.)
For functions and variables with external linkage, the documentation appears at the declaration in the header. For stuff with internal linkage, the documentation is in the source file. The headers start with a short blurb.
The source is mostly C-like C++, but still strives for modularization, implementation hiding, and clean interfaces. Internal linkage is used for "private" data. Classes might be used for general-purpose objects with multiple instances, but there aren't any of those yet. I try to reduce clutter and boilerplate code.
All .cpp files include headers according to this scheme:
#include "common.h"
<#includes for local headers>
<#includes for system headers>
common.h includes general utility functions and types as well as common system headers. Nesalizer still builds very quickly, so over-inclusion of system headers isn't a huge deal.
The above setup allows most headers to assume that common.h has been included, which simplifies headers and often makes include guards redundant.
If you spot stuff that can be improved (or sucks), tell me (or contribute :). I appreciate reports for small nits too.
A set of test ROMs listed in test.cpp can be run automatically with
$ make TEST=1
$ ./nes
This requires https://github.com/christopherpow/nes-test-roms to first be cloned into a directory called tests. All tests listed in test.cpp are expected to pass.
-
Shay Green (blargg) for the blip_buf waveform synthesis library and lots of test ROMs.
-
Quietust for the Visual 2A03 and Visual 2C02 simulators.
-
beannaich, cpow, Disch, kevtris, Movax21, Lidnariq, loopy, thefox, Tepples, and lots of other people on the NesDev forums and wiki and in #nesdev for help, docs, tests, and discussion.
moc.liamg[ta]rezilaflu in reverse. I'm currently looking for a job, so feel free to e-mail regarding that too. :)
A tutorial I wrote (with help from Lidnariq and Quietust) on reading circuits in Visual 6502 and other simulators based on the same framework can be found here. Some obscure behaviors were reverse-engineered from studying circuits and running simulations.
(Not sure why I took these with linear filtering enabled. It's a bit ugly in retrospect.)