NES emulator, written in Go

Fergulator

Build Status

This is an NES emulator, written in Go. It's fairly new and very much a work in progress, so not all games run yet and not all features are implemented. Details are below.

alt text

To build on Linux

Requires Go 1.1

From your GOPATH:

    $ sudo apt-get install libsdl1.2-dev libsdl-gfx1.2-dev libsdl-image1.2-dev libglew1.6-dev libxrandr-dev
    $ go get github.com/scottferg/Fergulator

To build on OSX

You'll need to install XQuartz in order to run on OSX.

Requires Go 1.1

From your GOPATH:

    $ brew install sdl --with-x11-driver
    $ brew install sdl_gfx sdl_image glew
    $ go get github.com/scottferg/Fergulator

Run the emulator

    $ Fergulator path/to/game.nes

Controls

    A - Z
    B - X
    Start - Enter
    Select - Right Shift
    Up/Down/Left/Right - Arrows

    Save State - S
    Load State - L

    Reset - R

    1:1 aspect ratio - 1
    2:1 aspect ratio - 2
    3:1 aspect ratio - 3
    4:1 aspect ratio - 4

    Emulate overscan - O
    Toggle audio - I

    Toggle pause - P
    Frame Advance - \
    Reload Debug JS - D
    Trigger Mode Event - M

Supported Mappers

  • NROM
  • UNROM
  • CNROM
  • MMC1
  • MMC2
  • MMC3
  • MMC5
  • ANROM

Tested games that run well or are playable

List is in the wiki

Comments
  • No available video device

    No available video device

    i've already passed go test, and when i load a nes:

    ./Fergulator ~/Downloads/super_mario.nes
    -----------------
    ROM:
      PRG-ROM banks: 2
      CHR-ROM banks: 1
      Mirroring: Vertical
      Mapper: 0x0 -> NROM
    2013/04/17 01:23:14 No available video device
    

    btw, my OS: Mac OS X 10.8.3, brew doctor also passed.

  • Running a ROM panics

    Running a ROM panics

    Here's the command and the panic. When I run it, I can see a window popup on my screen for at most a few milliseconds. Then it disappears.

    [andrew@Ocelot Fergulator] Fergulator test_roms/nesstress.nes 
    PRG-ROM Count: 2
    CHR-ROM Count: 1
    Vertical mirroring
    Writing bank 1 to 0xC000, base value: 0x0
    Mapper: 0x0
    unexpected fault address 0x7f32fc6c0000
    throw: fault
    [signal 0xb code=0x2 addr=0x7f32fc6c0000 pc=0x42d67f]
    
    goroutine 6 [running]:
    main.(*Video).Render(0x59ae58, 0x0)
            /home/andrew/go/src/github.com/scottferg/Fergulator/video.go:58 +0x37b
    created by main.main
            /home/andrew/go/src/github.com/scottferg/Fergulator/machine.go:176 +0x46e
    
    goroutine 1 [runnable]:
    main.(*Ppu).raster(0x5aa910, 0x40ea02)
            /home/andrew/go/src/github.com/scottferg/Fergulator/ppu.go:190 +0x17d
    main.(*Ppu).Step(0x5aa910, 0x3)
            /home/andrew/go/src/github.com/scottferg/Fergulator/ppu.go:214 +0xfd
    main.main()
            /home/andrew/go/src/github.com/scottferg/Fergulator/machine.go:182 +0x4c9
    
    goroutine 2 [syscall]:
    created by runtime.main
            /opt/go/src/pkg/runtime/proc.c:220
    
    goroutine 3 [runnable]:
    github.com/0xe2-0x9a-0x9b/Go-SDL/sdl.pollEvents()
            /home/andrew/go/src/github.com/0xe2-0x9a-0x9b/Go-SDL/sdl/event.go:52 +0xbc
    created by github.com/0xe2-0x9a-0x9b/Go-SDL/sdl.init·1
            /home/andrew/go/src/github.com/0xe2-0x9a-0x9b/Go-SDL/sdl/event.go:64 +0x1f
    
    goroutine 4 [timer goroutine (idle)]:
    created by addtimer
            /opt/go/src/pkg/runtime/ztime_linux_amd64.c:70
    
    goroutine 5 [chan receive]:
    main.JoypadListen()
            /home/andrew/go/src/github.com/scottferg/Fergulator/controller.go:77 +0x46
    created by main.main
            /home/andrew/go/src/github.com/scottferg/Fergulator/machine.go:175 +0x454
    

    I get the same error with any other ROM it seems. (I first tried Super Mario.)

    I am on the latest commit from master to my knowledge: 06de8e525bfb4e26d8d09a0a3ccfd7d9b0bd54d1

    I'm on Archlinux: Linux Ocelot 3.5.4-1-ARCH #1 SMP PREEMPT Sat Sep 15 08:12:04 CEST 2012 x86_64 GNU/Linux.

    Here are my versions of the dependencies from the README:

    [andrew@Ocelot Fergulator] y -Qi sdl sdl_gfx sdl_image | egrep '^(Name|Version)'
    Name           : sdl
    Version        : 1.2.15-3
    Name           : sdl_gfx
    Version        : 2.0.24-1
    Name           : sdl_image
    Version        : 1.2.12-2
    

    Go-SDL:

    [andrew@Ocelot Go-SDL] git show
    commit f4b2c0ad232e3955f9f9325f95e513a47bf399d6
    Merge: d4ded26 c6f47f2
    Author: ⚛ <[email protected]>
    Date:   Wed Sep 19 00:05:41 2012 -0700
    
        Merge pull request #12 from davecheney/master
    
        linux/arm support
    

    Oh, and I'm on Go tip:

    [andrew@Ocelot go-hg] go version
    go version devel +97b5f757b30e Fri Oct 05 23:51:40 2012 +0800
    

    Let me know if you want to me to try it on 1.0.3.

  • Build failure on Ubuntu

    Build failure on Ubuntu

    Attempting to build Fergulator on fresh Ubuntu system (with dependencies installed) fails with the following output.

    $ go get github.com/scottferg/Fergulator
    # github.com/go-gl/gl
    In file included from attriblocation.go:7:0:
    gl.h:5:25: error: enumerator value for '__cgo_enum__5' is not an integer constant
     #define GLEW_GET_FUN(x) (*x)
                             ^
    /usr/include/GL/glew.h:1944:26: note: in expansion of macro 'GLEW_GET_FUN'
     #define glVertexAttrib1f GLEW_GET_FUN(__glewVertexAttrib1f)
                              ^
    gl.h:5:25: error: enumerator value for '__cgo_enum__6' is not an integer constant
     #define GLEW_GET_FUN(x) (*x)
                             ^
    /usr/include/GL/glew.h:1945:27: note: in expansion of macro 'GLEW_GET_FUN'
     #define glVertexAttrib1fv GLEW_GET_FUN(__glewVertexAttrib1fv)
                               ^
    gl.h:5:25: error: enumerator value for '__cgo_enum__7' is not an integer constant
     #define GLEW_GET_FUN(x) (*x)
                             ^
    /usr/include/GL/glew.h:1957:27: note: in expansion of macro 'GLEW_GET_FUN'
     #define glVertexAttrib3fv GLEW_GET_FUN(__glewVertexAttrib3fv)
                               ^
    gl.h:5:25: error: enumerator value for '__cgo_enum__8' is not an integer constant
     #define GLEW_GET_FUN(x) (*x)
                             ^
    /usr/include/GL/glew.h:1970:26: note: in expansion of macro 'GLEW_GET_FUN'
     #define glVertexAttrib4f GLEW_GET_FUN(__glewVertexAttrib4f)
                              ^
    gl.h:5:25: error: enumerator value for '__cgo_enum__9' is not an integer constant
     #define GLEW_GET_FUN(x) (*x)
                             ^
    /usr/include/GL/glew.h:2369:31: note: in expansion of macro 'GLEW_GET_FUN'
     #define glVertexAttribDivisor GLEW_GET_FUN(__glewVertexAttribDivisor)
                                   ^
    gl.h:5:25: error: enumerator value for '__cgo_enum__10' is not an integer constant
     #define GLEW_GET_FUN(x) (*x)
                             ^
    /usr/include/GL/glew.h:1895:36: note: in expansion of macro 'GLEW_GET_FUN'
     #define glDisableVertexAttribArray GLEW_GET_FUN(__glewDisableVertexAttribArray)
                                        ^
    gl.h:5:25: error: enumerator value for '__cgo_enum__11' is not an integer constant
     #define GLEW_GET_FUN(x) (*x)
                             ^
    /usr/include/GL/glew.h:1950:26: note: in expansion of macro 'GLEW_GET_FUN'
     #define glVertexAttrib2f GLEW_GET_FUN(__glewVertexAttrib2f)
                              ^
    gl.h:5:25: error: enumerator value for '__cgo_enum__12' is not an integer constant
     #define GLEW_GET_FUN(x) (*x)
                             ^
    /usr/include/GL/glew.h:1951:27: note: in expansion of macro 'GLEW_GET_FUN'
     #define glVertexAttrib2fv GLEW_GET_FUN(__glewVertexAttrib2fv)
                               ^
    gl.h:5:25: error: enumerator value for '__cgo_enum__13' is not an integer constant
     #define GLEW_GET_FUN(x) (*x)
                             ^
    /usr/include/GL/glew.h:1971:27: note: in expansion of macro 'GLEW_GET_FUN'
     #define glVertexAttrib4fv GLEW_GET_FUN(__glewVertexAttrib4fv)
                               ^
    gl.h:5:25: error: enumerator value for '__cgo_enum__14' is not an integer constant
     #define GLEW_GET_FUN(x) (*x)
                             ^
    /usr/include/GL/glew.h:1978:31: note: in expansion of macro 'GLEW_GET_FUN'
     #define glVertexAttribPointer GLEW_GET_FUN(__glewVertexAttribPointer)
                                   ^
    gl.h:5:25: error: enumerator value for '__cgo_enum__15' is not an integer constant
     #define GLEW_GET_FUN(x) (*x)
                             ^
    /usr/include/GL/glew.h:1956:26: note: in expansion of macro 'GLEW_GET_FUN'
     #define glVertexAttrib3f GLEW_GET_FUN(__glewVertexAttrib3f)
                              ^
    gl.h:5:25: error: enumerator value for '__cgo_enum__16' is not an integer constant
     #define GLEW_GET_FUN(x) (*x)
                             ^
    /usr/include/GL/glew.h:1897:35: note: in expansion of macro 'GLEW_GET_FUN'
     #define glEnableVertexAttribArray GLEW_GET_FUN(__glewEnableVertexAttribArray)
                                       ^
    attriblocation.go:44:2: error: initializer element is not constant
     }
      ^
    attriblocation.go:44:2: error: (near initialization for '__cgodebug_data[5]')
    attriblocation.go:45:2: error: initializer element is not constant
    
      ^
    attriblocation.go:45:2: error: (near initialization for '__cgodebug_data[6]')
    attriblocation.go:46:2: error: initializer element is not constant
     func (indx AttribLocation) AttribPointer(size uint, typ GLenum, normalized bool, stride int, pointer interface{}) {
      ^
    attriblocation.go:46:2: error: (near initialization for '__cgodebug_data[7]')
    attriblocation.go:47:2: error: initializer element is not constant
      C.glVertexAttribPointer(C.GLuint(indx), C.GLint(size), C.GLenum(typ),
      ^
    attriblocation.go:47:2: error: (near initialization for '__cgodebug_data[8]')
    attriblocation.go:48:2: error: initializer element is not constant
       glBool(normalized), C.GLsizei(stride), ptr(pointer))
      ^
    attriblocation.go:48:2: error: (near initialization for '__cgodebug_data[9]')
    attriblocation.go:49:2: error: initializer element is not constant
     }
      ^
    attriblocation.go:49:2: error: (near initialization for '__cgodebug_data[10]')
    attriblocation.go:50:2: error: initializer element is not constant
    
      ^
    attriblocation.go:50:2: error: (near initialization for '__cgodebug_data[11]')
    attriblocation.go:51:2: error: initializer element is not constant
     func (indx AttribLocation) EnableArray() {
      ^
    attriblocation.go:51:2: error: (near initialization for '__cgodebug_data[12]')
    attriblocation.go:52:2: error: initializer element is not constant
      C.glEnableVertexAttribArray(C.GLuint(indx))
      ^
    attriblocation.go:52:2: error: (near initialization for '__cgodebug_data[13]')
    attriblocation.go:53:2: error: initializer element is not constant
     }
      ^
    attriblocation.go:53:2: error: (near initialization for '__cgodebug_data[14]')
    attriblocation.go:54:2: error: initializer element is not constant
    
      ^
    attriblocation.go:54:2: error: (near initialization for '__cgodebug_data[15]')
    attriblocation.go:55:2: error: initializer element is not constant
     func (indx AttribLocation) DisableArray() {
      ^
    attriblocation.go:55:2: error: (near initialization for '__cgodebug_data[16]')
    
    $ go version
    go version go1.1.2 linux/amd64
    
  • License?

    License?

    I didn't see a COPYING file in the root directory. Have you decided on a license for this emulator? GPLv3? Apache 2.0? other?

    FSF approved licenses: https://gnu.org/licenses/license-list.html#SoftwareLicenses OSI approved licenses: http://opensource.org/licenses/alphabetical

  • Debugging features

    Debugging features

    Totally understand if you don't want to merge this, it's a potentially large departure from your vision.

    Demo of usage at http://youtu.be/r2NE0SkzWXA in which I automatically filter down SMB RAM to interesting values.

  • Update OSX install instructions.

    Update OSX install instructions.

    It wasn't specified what this file was supposed to be edited to. And it had already been used to install, so this would be too late to do it. And it works without it.

  • RomInfo struct

    RomInfo struct

    Maybe not what you were thinking, but temporary solution to getting saveState and batteryState file paths into nes w/o relying on environment specifics

  • Upgrade CodeSee workflow to version 2

    Upgrade CodeSee workflow to version 2

    CodeSee is a code visibility platform.

    This change updates the CodeSee workflow file to the latest version for security, maintenance, and support improvements (see changelog below).

    That workflow file:

    • runs CodeSee's code analysis on every PR push and merge
    • uploads that analysis to CodeSee.
    • It does not transmit your code.

    The code analysis is used to generate maps and insights about this codebase.

    CodeSee workflow changelog:

    • Improved security: Updates permission to be read-only.
    • Improved future maintenance: Replaces the body of the workflow with a single github action: codesee-action. This makes it significantly easier for CodeSee to introduce future improvements and fixes without requiring another PR like this.
    • Improved Python support: The action now properly supports Python 3.11, and will continue to support new Python versions as they are released.
  • RAM (0x00 - 0x7FF) is not mirrored (through 0x800 - 0x1FFF)

    RAM (0x00 - 0x7FF) is not mirrored (through 0x800 - 0x1FFF)

    It doesn't look like RAM access is mirrored per this diagram: http://en.wikibooks.org/wiki/NES_Programming/Memory_Map

    I believe a simple fix would be to bitwise & the address with 0x7FFF which should be equivalent to modulus 0x800.

    func (m Memory) Read(a uint16) (Word, error) {
        switch {
        case a >= 0x2008 && a < 0x4000:
            offset := a % 0x8
            return ppu.RegRead(int(0x2000 + offset))
        case a <= 0x2007 && a >= 0x2000:
            return ppu.RegRead(int(a))
        case a == 0x4016:
            return Pads[0].Read(), nil
        case a == 0x4017:
            return Pads[1].Read(), nil
        case a&0xF000 == 0x4000:
            return apu.RegRead(int(a))
        case a >= 0x8000 && a <= 0xFFFF:
            return rom.Read(int(a)), nil
        case a >= 0x5100 && a <= 0x6000:
            if _, ok := rom.(*Mmc5); ok {
                // MMC5 register handling
                return rom.Read(int(a)), nil
            }
        }
    
        return m[a & 0x7FFF], nil // <--- Mirroring handled here
    }
    
  • Race condition in startup causing panic

    Race condition in startup causing panic

    This only happens sometimes. Have noticed on many different ROMs, so I don't think it is ROM specific.

    On OSX Yosemite.

    > ./Fergulator test_roms/nestest.nes
    2015/02/08 12:15:10 nestest .nestest.state
    -----------------
    ROM:
      PRG-ROM banks: 1 (1 real)
      CHR-ROM banks: 2 (1 real)
      Mirroring: Horizontal
      Mapper: 0x0 -> NROM
    fatal error: unexpected signal during runtime execution
    [signal 0xb code=0x1 addr=0x3b0 pc=0x7fff92b59c30]
    
    runtime stack:
    runtime.gothrow(0x43986f0, 0x2a)
        /usr/local/Cellar/go/1.4.1/libexec/src/runtime/panic.go:503 +0x8e
    runtime.sigpanic()
        /usr/local/Cellar/go/1.4.1/libexec/src/runtime/sigpanic_unix.go:14 +0x5e
    
    goroutine 1 [syscall, locked to thread]:
    runtime.cgocall_errno(0x40042a0, 0xc2080e7cf8, 0xc200000000)
        /usr/local/Cellar/go/1.4.1/libexec/src/runtime/cgocall.go:130 +0xf5 fp=0xc2080e7cd8 sp=0xc2080e7cb0
    github.com/go-gl/gl._Cfunc_glewInit(0x0)
        github.com/go-gl/gl/_obj/_cgo_gotypes.go:4399 +0x4b fp=0xc2080e7cf8 sp=0xc2080e7cd8
    github.com/go-gl/gl.Init(0xc20800c700)
        /Users/xavier/Code/go/src/github.com/go-gl/gl/gl.go:1232 +0x28 fp=0xc2080e7d08 sp=0xc2080e7cf8
    main.(*Video).initGL(0x45c6660)
        /Users/xavier/Code/go/src/github.com/scottferg/Fergulator/video.go:86 +0x27 fp=0xc2080e7da8 sp=0xc2080e7d08
    main.(*Video).Init(0x45c6660, 0xc20809b3e0, 0x7fff5fbff79f, 0x7)
        /Users/xavier/Code/go/src/github.com/scottferg/Fergulator/video.go:76 +0x392 fp=0xc2080e7e40 sp=0xc2080e7da8
    main.main()
        /Users/xavier/Code/go/src/github.com/scottferg/Fergulator/main.go:77 +0xaa1 fp=0xc2080e7f98 sp=0xc2080e7e40
    runtime.main()
        /usr/local/Cellar/go/1.4.1/libexec/src/runtime/proc.go:63 +0xf3 fp=0xc2080e7fe0 sp=0xc2080e7f98
    runtime.goexit()
        /usr/local/Cellar/go/1.4.1/libexec/src/runtime/asm_amd64.s:2232 +0x1 fp=0xc2080e7fe8 sp=0xc2080e7fe0
    
    goroutine 5 [chan send]:
    github.com/scottferg/Go-SDL/sdl.pollEvents()
        /Users/xavier/Code/go/src/github.com/scottferg/Go-SDL/sdl/event.go:37 +0x1dc
    created by github.com/scottferg/Go-SDL/sdl.init·1
        /Users/xavier/Code/go/src/github.com/scottferg/Go-SDL/sdl/event.go:64 +0x25
    
    goroutine 17 [syscall, locked to thread]:
    runtime.goexit()
        /usr/local/Cellar/go/1.4.1/libexec/src/runtime/asm_amd64.s:2232 +0x1
    
  • Invalid opcode for some ROMs

    Invalid opcode for some ROMs

    First of all thanks for this brilliant emulator.

    Some NES ROMs doesn't work with the Fergulator due to "Invalid opcode" error. I was downloading a lot of my favorite games from Internet and most of them work like a charm. Though some don't work while they are declared to be NES ROMs.

    Micro Machines ROM doesn't work: ----------------- ROM: PRG-ROM banks: 16 CHR-ROM banks: 0 Mirroring: Vertical Mapper: 0x2 -> UNROM ----------------- 2013/01/11 14:34:41 Invalid opcode at 0xBFA8: 0x7

    Can be downloaded from: http://coolrom.com/roms/nes/798/Micro_Machines.php http://romhustler.net/rom/nes/micro-machines-1

    May be they are really for SNES, I don't know, please comment.

Nintendo Entertainment System (NES) and Famicom emulator written in Go
Nintendo Entertainment System (NES) and Famicom emulator written in Go

go6502 Nintendo Entertainment System (NES) and Famicom emulator written in Go Dependencies go get -u github.com/go-gl/gl go get -u github.com/go-gl/gl

Apr 25, 2022
Gones - An attempt to program a NES emulator

Gones - An attempt to program a NES emulator The following resources were used for the project: The great work on wiki.nesdev.org R650X and R651X Data

Jan 10, 2022
A ZX Spectrum Emulator written in Go
A ZX Spectrum Emulator written in Go

GoSpeccy - An evolving ZX Spectrum 48k Emulator GoSpeccy is a free ZX Spectrum (Speccy for friends) emulator written in Go. Quick start Installing and

Nov 28, 2022
Mettaur is GBA emulator written in golang.
Mettaur is GBA emulator written in golang.

Mettaur Mettaur is GBA emulator written in golang. Warning: This emulator is WIP, so many ROMs don't work correctly now. Run Please download latest bi

Dec 25, 2022
A GameBoy emulator written in Go

gogoboy A GameBoy emulator written in Go About this project This project is a proof of concept of building emulators with test driven development. I'v

Aug 20, 2022
Magia is GBA emulator written in golang.
Magia is GBA emulator written in golang.

magia is GBA emulator written in golang.

Dec 25, 2022
A toy GameBoy Color emulator written in golang.
A toy GameBoy Color emulator written in golang.

?? worldwide 日本語のドキュメントはこちら GameBoyColor emulator written in golang. This emulator can play a lot of ROMs work without problems and has many features.

Jan 1, 2023
A Chip8 emulator written in Go

A Chip8 Emulator in Go chip8.go is a simple Chip8 emulator, compliant with the technical standard laid out in the Cowgod's Manual. Graphics and sound

Jun 6, 2022
CHIP-8 Emulator written in Go
CHIP-8 Emulator written in Go

dP oo 88 .d8888b. 88d888b. dP 88d888b. 88d888b. dP dP 88' `"" 88' `88 88 88' `88 88' `88 88 88 88. ... 88 88 88 88. .88

Nov 26, 2021
A simple CHIP-8 emulator written in Go

GoCHIP This is a simple CHIP-8 emulator written in Go. Check out this blogpost to learn more about CHIP-8 emulation: https://tobiasvl.github.io/blog/w

Jan 10, 2022
A CHIP-8 emulator written in Go

chip8 A CHIP-8 emulator written in Go. Usage Usage of chip8: -scaleFactor int Display scale factor (default 10) rom Path to ROM (m

May 25, 2022
🕹️ A basic gameboy emulator with terminal "Cloud Gaming" support
🕹️ A basic gameboy emulator with terminal

Gameboy.Live ??️ Gameboy.Live is a Gameboy emulator written in go for learning purposes. You can simply play Gameboy games on your desktop: Or, "Cloud

Jan 1, 2023
Gopher2600 is an Atari 2600/VCS Emulator.
Gopher2600 is an Atari 2600/VCS Emulator.

Gopher2600 Gopher2600 is an emulator for the Atari 2600 written in the Go language. The accuracy of the emulation is very high and the 6507, TIA and R

Dec 8, 2022
A chip-8 emulator built with Go

Introduction Chip-8 is an interpretted language designed to create programs/games on the 8bit systems like the COSMAC VIP and Telmac 1800. Chyp8 is an

Oct 3, 2021
Golang RISC-V emulator that can play DOOM
Golang RISC-V emulator that can play DOOM

RISC-V Emulator A toy Golang RISC-V emulator that can play DOOM For now it uses smunaut bootloader and riscv_doom from the ICE40 project. But since th

Dec 12, 2022
'mouseable' is keyboard-based mouse emulator for Windows.

Motivation Mouseable is intended to replace a mouse or trackpad. This program was inspired by Ultimate Hacking Keyboard Demo, Windows built-in functio

Dec 12, 2022
Sensor Emulator

Sensor Emulator This is a simple sensor emulator software implemented for IoT experiments. It emulates sensors sending data to an MQTT broker. Feature

Nov 28, 2021
VMAGI - Write an performant emulator in 24 hours!
VMAGI - Write an performant emulator in 24 hours!

VMAGI Welcome! VMAGI is a small emulator/interpreter my friend Matthew and I challenged each other to build in 24 hours. This includes both the implem

Nov 27, 2022
Chip-8 - A simple chip-8 emulator for golang
Chip-8 - A simple chip-8 emulator for golang

?? What the fuck is this A simple chip-8 emulator. Chip-8 is a simple, interpret

Aug 4, 2022