A NES emulator written in Rust compiled to WebAssemly for usage on the web.
The Nintendo Entertainment System (NES) is an 8-bit third-generation home video game console produced by Nintendo. Nintendo first released it in Japan as the Family Computer, commonly known as the Famicom, in 1983. The NES, a remodelled version, was released internationally in the following years.
- β CPU: all official opcodes # Central Processing Unit (Ricoh 2A03)
- β PPU: Pixel Processing Unit
- π§ APU: Audio Processing Unit: Pulse,
triangle,noise,DMC. - β Input: Controller input
- β
Mappers:
NROM
,MMC1
,UxROM
,003
,CNROM
,AxROM
,GxROM
. - β Save states: game saves via cartridge RAM
The emulator synchronizes to video with the requestAnimationFrame function, which usually matches the refresh rate of the display.
- CPU runs at 500Hz
- Timers run at 60Hz
At every repaint, enough emulator cycles are run to simulate that the duration for one frame has passed. Given an ideal refresh rate of 60FPS, that is 1/60s.
The emulator currently lacks in the following areas:
- Open bus behaviour is missing
- Precise PPU timing
- Some sprites are not displayed correctly
Before creating the emulator, you need to call the init
function which will correctly instantiate and setup the WebAssembly module.
import init, { Emulator } from '@kabukki/wasm-nes';
init().then(() => {
const emulator = new Emulator();
document.getElementById('input').addEventListener('change', async (e) => {
const buffer = await e.target.files[0]?.arrayBuffer();
emulator.load(new Uint8Array(buffer));
emulator.start();
});
}).catch(console.error);
This hook provides various information regarding emulation status.
logs
emulator logs (nestest compliant) produced through Rust's log facade.emulator
emulator stateperformance
measures of browser frame performance
Emulation accuracy is tested thanks to test ROMs taken from https://wiki.nesdev.com/w/index.php/Emulator_tests (available here), and inspired from http://tasvideos.org/EmulatorResources/NESAccuracyTests.html. Here is the summary of results, you can find details below.
Component | Passed | Total | % |
---|---|---|---|
CPU | 21 | 30 | 70% |
PPU | 10 | 41 | 24% |
APU | 3 | 18 | 17% |
Mappers | - | - | - |
Total | 34 | 89 | 38% |
Test | Status |
---|---|
branch_timing_tests/branch_basics |
β Passed |
branch_timing_tests/backward_branch |
β Passed |
branch_timing_tests/forward_branch |
β Passed |
cpu_dummy_reads |
β Failed |
cpu_dummy_writes/cpu_dummy_writes_oam |
β Failed |
cpu_dummy_writes/cpu_dummy_writes_ppumem |
β Failed |
cpu_exec_space/test_cpu_exec_space_apu |
β Failed |
cpu_exec_space/test_cpu_exec_space_ppuio |
β Failed |
cpu_interrupts_v2 |
β Failed |
cpu_reset/ram_after_reset |
β Passed |
cpu_reset/registers |
β Passed |
cpu_timing_test6 |
β Passed |
instr_misc |
β Failed |
instr_test_v5/basics |
β Passed |
instr_test_v5/implied |
β Passed |
instr_test_v5/immediate |
|
instr_test_v5/zero_page |
|
instr_test_v5/zp_xy |
|
instr_test_v5/absolute |
|
instr_test_v5/abs_xy |
|
instr_test_v5/ind_x |
|
instr_test_v5/ind_y |
|
instr_test_v5/branches |
β Passed |
instr_test_v5/stack |
β Passed |
instr_test_v5/jmp_jsr |
β Passed |
instr_test_v5/rts |
β Passed |
instr_test_v5/rti |
β Passed |
instr_test_v5/brk |
β Failed |
instr_test_v5/special |
β Failed |
nestest |
Test | Status |
---|---|
blargg_ppu_tests_2005.09.15b/palette_ram |
β Passed |
blargg_ppu_tests_2005.09.15b/sprite_ram |
β Passed |
blargg_ppu_tests_2005.09.15b/vbl_clear_time |
β Failed |
blargg_ppu_tests_2005.09.15b/vram_access |
β Failed |
nmi_sync/demo_ntsc |
β Failed |
oam_read |
β Passed |
oam_stress |
β Failed |
oamtest3 |
β Failed |
ppu_open_bus |
β Decay not implemented |
ppu_read_buffer |
β Failed |
ppu_sprite_hit/basics |
β Passed |
ppu_sprite_hit/alignment |
β Failed |
ppu_sprite_hit/corners |
β Failed |
ppu_sprite_hit/flip |
β Failed |
ppu_sprite_hit/left_clip |
β Failed |
ppu_sprite_hit/right_edge |
β Failed |
ppu_sprite_hit/screen_bottom |
β Failed |
ppu_sprite_hit/double_height |
β Passed |
ppu_sprite_hit/timing |
β Failed |
ppu_sprite_hit/timing_order |
β Failed |
ppu_sprite_overflow/basics |
β Failed |
ppu_sprite_overflow/details |
β Passed |
ppu_sprite_overflow/timing |
β Failed |
ppu_sprite_overflow/obscure |
β Failed |
ppu_sprite_overflow/emulator |
β Failed |
ppu_vbl_nmi/vbl_basics |
β Passed |
ppu_vbl_nmi/vbl_set_time |
β Failed |
ppu_vbl_nmi/vbl_clear_time |
β Passed |
ppu_vbl_nmi/nmi_control |
β Failed |
ppu_vbl_nmi/nmi_timing |
β Failed |
ppu_vbl_nmi/suppression |
β Failed |
ppu_vbl_nmi/nmi_on_timing |
β Failed |
ppu_vbl_nmi/nmi_off_timing |
β Failed |
ppu_vbl_nmi/even_odd_frames |
β Passed |
ppu_vbl_nmi/even_odd_timing |
β Failed |
sprdma_and_dmc_dma |
- |
sprite_overflow_tests/basics |
β Failed |
sprite_overflow_tests/details |
β Passed |
sprite_overflow_tests/timing |
β Failed |
sprite_overflow_tests/obscure |
β Failed |
sprite_overflow_tests/emulator |
β Failed |
Test | Status |
---|---|
apu_mixer/dmc |
β Failed |
apu_mixer/noise |
β Failed |
apu_mixer/square |
β Failed |
apu_mixer/triangle |
β Failed |
apu_reset/4015_cleared |
β Failed |
apu_reset/4017_timing |
β Failed |
apu_reset/4017_written |
β Passed |
apu_reset/irq_flag_cleared |
β Passed |
apu_reset/len_ctrs_enabled |
β Failed |
apu_reset/works_immediately |
β Failed |
apu_test/len_ctr |
β Failed |
apu_test/len_table |
β Failed |
apu_test/irq_flag |
β Passed |
apu_test/jitter |
β Failed |
apu_test/len_timing |
β Failed |
apu_test/irq_flag_timing |
β Failed |
apu_test/dmc_basics |
β Failed |
apu_test/dmc_rates |
β Failed |
... | ... |
Test | Status |
---|---|
Holy Mapperel |
- |
... | ... |
The emulator is written in Rust and compiled into a WebAssembly module through wasm-pack and uses wasm-bindgen to ease interoperability with the JavaScript environment. A custom JavaScript file wraps the produced package for convenience when consuming it in JavaScript.
.rs ---[wasm-pack]---> .wasm <--> JS wrapper <--- JS
The emitted JS wrapper is distributed as an ES Module.
You'll need a 6502 assembler & linker such as cc65.
cl65 roms/test.s -C roms/test.cfg -o roms/test.bin
- http://wiki.nesdev.com
- http://nesdev.com/NESDoc.pdf
- https://en.wikipedia.org/wiki/MOS_Technology_6502
- https://www.copetti.org/writings/consoles/nes
- https://www.masswerk.at/6502/6502_instruction_set.html (contains errors for instruction timing)
- http://www.obelisk.me.uk/6502/reference.html
- https://www.nesdev.com/6502_cpu.txt
- https://github.com/gianlucag/mos6502
- https://github.com/GarettCooper/gc_nes_emulator
- https://skilldrick.github.io/easy6502/
- https://famicom.party/book
- https://bugzmanov.github.io/nes_ebook
- http://emudev.de/
- https://austinmorlan.com/posts/nes_rendering_overview/
- https://wiki.nesdev.com/w/index.php/The_frame_and_NMIs
- http://wiki.nesdev.com/w/index.php/Emulator_tests
- https://github.com/christopherpow/nes-test-roms
- https://github.com/koute/pinky/tree/master/mos6502/roms
- https://github.com/koute/pinky/tree/master/nes-testsuite/roms
- https://github.com/Klaus2m5/6502_65C02_functional_tests
- https://www.qmtpro.com/~nes/
- https://github.com/bbbradsmith/nes-audio-tests
- http://bootgod.dyndns.org:7777/search.php?ines=1&group=groupid