Skip to content

maxpoletaev/dendy

Repository files navigation

Kivi Logo

NES/Famicom emulator with network multiplayer written in Go


Dendy is a NES/Famicom emulator written in Go and named after the post-soviet Famicom clone, that I used to have back in my childhood. It’s nothing serious, so do not expect it to beat any of the existing emulators in terms of performance or accuracy. Yet, it is capable of running most of the games I tried, so it’s not completely useless, and it comes with a nice network multiplayer feature.

Screenshots

Download

You can download the latest pre-built binaries for Windows, macOS, and Linux from the releases page: https://github.com/maxpoletaev/dendy/releases (not tested).

Alternatively, if you have Go installed, you can build it from source:

$ go install github.com/maxpoletaev/dendy/cmd/dendy@latest

For this, you may need to install a C compiler (gcc or clang) and additional dependencies required by raylib. See https://github.com/gen2brain/raylib-go#requirements for more details.

Play

There is no GUI, so you will have to run the emulator from the command line. Just point it to the ROM file you want to play (only .nes, .zip won’t work for now):

$ dendy romfile.nes

There’s a bunch of command line flags that you can know about by running dendy -help. Here are some of the most useful ones:

  • -scale=<n> - Scale the window by n times (default: 2)
  • -nospritelimit - Disable original sprite per scanline limit (eliminates flickering)
  • -listen and -connect - For network multiplayer (see below)
  • -nosave - Do not load and save the game state on exit

Controls

Controller

Player 1 controller is emulated using the keyboard. The default mapping is as follows. Multiplayer on a single keyboard is not supported.

                   ┆┆
┌───────────────────────────────────────┐
│                                       │
│    [W]                                │
│ [A]   [D]                             │
│    [S]                       [J] [K]  │
│           [Enter] [RShift]            │
│                                       │
└───────────────────────────────────────┘

Zapper (Light Gun)

Zapper is emulated using the mouse and can be used in games like Duck Hunt. Just point the mouse cursor at the right position on the screen and click to shoot.

Hotkeys

  • CTRL+R or ⌘+R - Reset the game
  • CTRL+Q or ⌘+Q - Quit the emulator
  • CTRL+X or ⌘+X - Resync the emulators (netplay)
  • F12 - Take a screenshot
  • M - Mute/unmute

Network Multiplayer

To utilize the multiplayer feature, you need to start the emulator with the -listen=<host>:<port> argument on the host machine and the -connect=<host>:<port> argument on the client machine. Once the connection is established, the game will start for both sides. The host machine will be the first player. The players must ensure they are running the same ROM file and the same version of the emulator.

$ dendy -listen=0.0.0.0:1234 roms/game.nes       # Player 1
$ dendy -connect=192.168.1.4:1234 roms/game.nes  # Player 2

When players are behind NATs

There is also a way to connect two players behind NATs without having to set up port forwarding, with a little help from an external relay server. You can use the -createroom flag to create a room on the public server, and the -joinroom=<id> to join it. Two clients will exchange their public IP addresses and port numbers through the relay server and establish a peer-to-peer UDP connection using a technique called "hole punching". The relay server will not be involved in the actual gameplay, so it should not affect the latency. This method won’t work if both players are behind symmetric NATs (luckily, most home residential NATs are not symmetric).

$ dendy -createroom roms/game.nes            # Player 1
$ dendy -joinroom=XXX-XXX-XXX roms/game.nes  # Player 2

Behind the scenes

The multiplayer part works by using something called rollback networking. This is a fancy way of making sure that when you play a game with someone else over the internet, it feels smooth and quick, just like playing side by side.

Here’s how it works: the game runs full speed for both players, and every move you make is sent over the internet to the other player. The cool part is, the local player should not notice any delay when they press buttons because the game guesses what the other player is going to do before their moves actually get to you. If the guess is wrong, the game quickly fixes it by going back a tiny bit to when both players agreed on what was happening, then moves forward again using the correct moves.

In addition to predicting and correcting player actions, it has a mechanism to ensure frame rate synchronization between players, especially when there are drifts in clock speed causing one player’s game running slower than the other’s. The emulator can detect that one of the players is falling behind too much and adjust the frame rate accordingly or even pause the game for one player for a few frames to let the other player catch up.

Within a reasonable latency, all of this should happen in a fraction of a second, without the players noticing anything weird. When tested, ping of up to 150ms felt pretty playable.

Tested Games

Game Status Issues
Bad Apple Playable
Balloon Fight Playable
Batman Playable
Battle City Playable
Battletoads Playable
Battletoads & Double Dragon Not Playable Freezes
Bomberman Playable
Captain America Playable
Castlevania Playable
Chip 'n Dale Playable
Chip 'n Dale 2 Playable
Contra Force Playable
Contra Playable
Darkwing Duck Playable
Donkey Kong Playable
Double Dragon 2 Playable
Double Dragon 3 Playable
Duck Hunt Playable
Duck Tales Playable
Ice Climber Playable
Jackal Not Playable Black screen
Kirby's Adventure Playable
Legend of Zelda Playable
Mario Bros. Playable
Megaman Playable
Megaman 4 Playable
Metal Gear Playable
Mighty Final Fight Playable
Ninja Cat Playable
Prince of Persia Playable Incorrect sprite/background priority
Super Contra Playable
Super Mario Bros. 3 Crash
Super Mario Bros. Playable
Teenage Mutant Ninja Turtles Playable
Teenage Mutant Ninja Turtles 2 Playable
Teenage Mutant Ninja Turtles 3 Playable
Teenage Mutant Ninja Turtles: Tournament Fighters Not Playable Graphical artifacts
Tiny Toon Adventures Playable

Status

CPU

  • Official opcodes
  • Unofficial opcodes
  • Runtime disassembly
  • Cycle-accurate emulation
  • Accurate clock speed
  • Interrupts

Graphics

  • Background rendering
  • Sprite rendering
  • 8×16 sprites
  • Palettes
  • Scrolling
  • Color emphasis

Input/Output

  • Graphics output
  • Controllers
  • Zapper

Sound

The sound chip emulation is still work in progress and is not very reliable yet. It may occasionally produce some pops and crackles, but it should be good enough for most games.

  • Square channels
  • Triangle channel
  • Noise channel
  • Length counter
  • Envelope
  • Sweep
  • DMC

Mappers

The goal is to support top 7 mappers covering the majority of games. The percentage indicates the number of games that use the mapper according to nescartdb.com.

  • MMC1 (Mapper 1) - 28%
  • MMC3 (Mapper 4) - 24%
  • UxROM (Mapper 2) - 11%
  • NROM (Mapper 0) - 10%
  • CNROM (Mapper 3) - 6%
  • AxROM (Mapper 7) - 3%
  • MMC5 (Mapper 5) - 1%

Resources

Although NES emulation is a pretty well-covered topic, It is still a very interesting and challenging project to work on. Here are some of the resources that I found particularly useful while writing this emulator. Big thanks to everyone who made them!

Documentation

Videos

  • The NES Emulator from Scratch series covers most of the topics from the CPU to the sound, but I found the two videos about the PPU to be the most useful for understanding the obscure details of the NES rendering pipeline: [1], [2].

Code

During bad times, it’s always nice to look at other people’s code to see how they solved the same problems. Here are some of the emulators written by other people that I often referred to when I was stuck: