Game Boy emulator

On-Hold
See Source

Back in 2018 I stumbled upon a blog post by Imran Nazar describing how to create a Game Boy emulator that runs in the browser. While reading about Imran's implementation, I was coming up with alternate implementation strategies. I was also looking for a fun project to teach myself TypeScript. I was so eager that I began writing code within the same week.

In the rest of this article I'll describe what I intend to build (it's not yet finished), which portion I have built, then some reflections.

What I intend to build

Tetris for the Game Boy running in the mGBA emulator

There are loads of resources online that describe what an emulator is and how the Game Boy works. Instead of rehashing that info, I'll summarize the intended user experience (UX) and high level design.

UX

I started this project at a time in my life when I was thoroughly exploring the capabilities of the browser and much of the tooling surrounding SPA development, so naturally I wanted provide the full emulation experience in the browser: loading a ROM image (ROM), video, audio, controller, save states, fast forward, rewind, networking, and more.

Design

After reading a few documents about how the Game Boy works and several more about how to build an emulator, I felt confident enough to start breaking down the problem of "emulation" into more manageable chunks.

SPA

With my UX goals in mind, it was clear that to start I'd need to get a SPA running in the browser. Here's an illustration of the generic process:

graph TB
    %% Entities
        User
        subgraph Client
            Browser
        end
        subgraph Server
            WebServer[Web Server]
            Distributables
        end
        Developer
        subgraph Development Machine
            SourceCode[Source Code]
        end
    %% Relationships
        User -- opens website in --> Browser
        Browser -- requests website from --> WebServer
        WebServer -- loads website from --> Distributables
        Developer -- develops --> SourceCode
        SourceCode -- compiles to --> Distributables

As with many SPAs, the interesting stuff happens inside the browser, so let's go one layer deeper.

Browser / JavaScript

My SPA would request a ROM from the user, then load an emulator and an acompanying user interface (UI). The UI would act as an input/output (I/O) bridge between the emulator and browser.

graph TB
    %% Entities
        User
        subgraph Disk
            ROM
        end
        subgraph Browser
            WebPage[Web Page]
            CartData[Cartridge Data]
            Emulator
            UI[User Interface]
        end
    %% Relationships
        User -- navigates to --> WebPage
        User -- selects --> ROM
        ROM -- reads into --> CartData
        WebPage -- creates --> Emulator
        WebPage -- creates --> UI
        CartData -- passes into --> Emulator
        UI -- ticks --> Emulator
        UI -- renders data from --> Emulator
        User -- views & interacts with --> UI

Emulator

With this design I could encapsulate all Game Boy hardware emulation inside the emulator class. The primary components of the emulator include the central processing unit (CPU), the memory map, the input handler, and the picture processing unit (PPU). Here's a diagram illustrating the relationships between all of these components:

graph TB
    %% Entities
        CartData[Cartridge Data]
        UI[User Interface]
        subgraph Emulator
            InputHandler[Input Handler]
            MemoryMap[Memory Map]
            CPU
            PPU
        end
    %% Relationships
        CPU -- reads from & writes to --> MemoryMap
        MemoryMap -- maps from --> InputHandler
        MemoryMap -- maps from --> CartData
        InputHandler -- reads controller state from --> UI
        PPU -- reads from --> MemoryMap
        PPU -- renders to --> UI

At the time of writing my understanding of the Game Boy internals is still quite limited, so I imagine more components will fit into this diagram in the future; for example an audio controller, an interrupt controller, and a sprite mapper.

CPU

The CPU's primary job would be to execute instructions from the instruction set architecture (ISA), which often involves either reading from/writing to memory or manipulating CPU registers.

Memory Map

The memory map's primary job would be to take requests to read or write data at a variety of addresses and map those operations to either raw byte/word storage via typed arrays or a virtual address range represented by another emulator component. For example:

  • Writing to video RAM would redirect to a sprite mapper, which would be used by the PPU and UI to draw the bitmap.
  • Reading from the controller I/O registers would redirect to the input handler to ask for the controller's state.
  • Reading from the screen state registers would redirect to the PPU to ask for the screen's state.

I chose to use typed arrays because they accurately represent the binary data that a real Game Boy would have stored in a region of raw memory.

Here's a not-so-comprehensive illustration of the memory map:

graph TB
    %% Entities
        CPU
        CartData[Cartridge Data]
        PPU
        SpriteMapper[Sprite Mapper]
        subgraph Memory Map
            ROMBanks[ROM Banks]
            VRAM[Video RAM]
            WRAM[Work RAM]
            WRAMArr[Typed Array]
        end
    %% Relationships
        CPU -- reads from & writes to --> ROMBanks
        ROMBanks -- redirect to --> CartData
        CPU -- writes to --> VRAM
        VRAM -- redirects to --> SpriteMapper
        PPU -- renders sprites from --> SpriteMapper
        CPU -- reads from & writes to --> WRAM
        WRAM -- redirects to --> WRAMArr

Input Handler

The input handler would act as a bridge between the UI and the memory map. The UI would get input events from the browser to update the state of the input handler. Then the CPU would request input state from the memory map, which would redirect its requests to the input handler.

graph TB
    %% Entities
        CPU
        subgraph Memory Map
            IORegisters[I/O Registers]
        end
        InputHandler[Input Handler]
        Browser
        UI[User Interface]
    %% Relationships
        Browser -- dispatches input events to --> UI
        UI -- updates input state of --> InputHandler
        CPU -- reads from --> IORegisters
        IORegisters -- redirect to --> InputHandler

UI

Early development of the debugger required a lot of debugging, so I intended to start with an ugly UI that primarily provides quick access to debugging information and controls.

I'm most comfortable with React and I enjoy the simplicity it provides, so all UI state and rendering will be handled inside React. However, the emulator was designed to operate independent of any rendering frameworks (outside of the canvas API for rendering the game's scanlines to the screen), so I decided to have the UI manage a ticker that manages the scheduling of emulator ticks. Then the UI can pause, step, and resume the emulator at the click of a button.

graph TB
    %% Entities
        Emulator
        subgraph UI
            RootCmp[Root Component]
            CanvasPanel[Canvas Panel]
            MemoryPanel[Memory Panel]
            CPUPanel[CPU Panel]
            ActionsPanel[Actions Panel]
            Ticker
        end
    %% Relationships
        RootCmp -- renders --> CanvasPanel
        RootCmp -- renders --> MemoryPanel
        RootCmp -- renders --> CPUPanel
        RootCmp -- renders --> ActionsPanel
        RootCmp -- creates --> Ticker
        Ticker -- ticks --> Emulator
        Emulator -- renders to --> CanvasPanel
        MemoryPanel -- reads memory from --> Emulator
        CPUPanel -- reads registers from --> Emulator
        ActionsPanel -- steps & resumes --> Ticker
        Ticker -- reads breakpoints from --> MemoryPanel

What I have built

My emulator is maybe better described as a "Game Boy CPU Emulator" due to its lack of features, though my goal to build a playable emulator remains so I'm still going to call it a Game Boy emulator. I mention this because my emulator can load a ROM, accurately execute CPU instructions from the ROM, and implements enough of the memory map to support some simpler games.

A user interface showing two buttons; the first showing a game cartridge can be chosen and the second indicating that you can instead re-load the last game played.

Once a ROM is loaded, my UI renders multiple panels:

  1. A canvas.

    The PPU will use this canvas for final video output; currently unused.

  2. Raw memory view.

    • Allows viewing of hex values at every address in memory.
    • Highlights the point in memory referenced by the program counter register (PC) in the CPU.
    • Allows breakpoints to be set at any address.
  3. CPU register view.

  4. Debug controls.

    • Step: moves the CPU forward one instruction by executing the instruction at the current PC address.
    • Resume: moves the CPU forward in real-time indefinitely, or until a breakpoint is reached.

A user interface showing four panels; the first is a blank canvas, the second is a memory viewer, the third is a CPU register viewer, and the fourth is a pair of debugger controls for stepping or resuming.

Reflection

I'm writing about this project nearly seven years after starting this project; a lot of time to reflect on a project.

Challenges

This project started as a good way to learn TypeScript. The early days were spent reading the TypeScript docs and struggling with the language. To this day, my favorite way to learn about a language, library, or framework is to read the official docs or source code (assuming it's well documented and has well-made source code). Although I've never looked at the TypeScript source code, the official docs are very well formed and could hardly be more useful.

On top of struggling with the language, the problem I was solving, Game Boy emulation, was new and complex. There's a ton of great documentation to aid in Game Boy emulation scattered around the internet; most of the noteworthy resources can be found in the Awesome Game Boy Development GitHub repository. Being new to emulation and Game Boy hardware, I spent most of my reading time in the Pan Docs, it's like THE unofficial Game Boy development reference book.

I don't recommend taking on two or more difficult new endeavors in tandem, but it seemed to have worked out well enough because it didn't take long for me to learn a lot about TypeScript, Game Boy hardware, emulation, typed arrays, complex React state management, and so much more.

Future plans

Rendering graphics from memory would be a huge milestone, so that's my next big goal. This goal can be broken into smaller goals, for example creating another UI debug panel for inspecting raw graphical tiles, sprites, and backgrounds. Creating those things will help me build rendering tools that will be useful for ultimately rendering scanlines on the main "screen canvas" panel.

One challenge that I continue to face is the fact that the memory viewer is hard to read. With a little exposure, it's easy enough to read the instructions in assembly form, but as hex values I have to look up the hex in an opcode table (until I've committed the 501-instruction table to memory). I thought about adding the ability to disassemble the instruction in the memory view where the PC register points, but it would require a refactor of the CPU class' instruction mapper. I feel like the CPU instructions have been tested thoroughly enough that I'm much less interested in knowing which instructions are being executed, so this has taken a low priority.

Other than implementing the standard emulation features (save states, fast forward, rewind, networking, etc.) and more missing Game Boy features (e.g. audio, ROM banks), I haven't thought far beyond these two changes. I'd rather try to play some games and see what kinds of problems demand solving.

Personal growth

Before starting this project I was frustrated by the fact that I would start many side projects and finish none of them. Going into this project I was committed to "finishing" it. Seven years later and I still wouldn't consider it complete, but I'm happy that I made significant progress and still intend to work on it. I still regularly start side projects, but I'll often try to have more modest goals such as "learn about X" or "complete project with significantly reduced scope". It's hard to say whether I've learned or produced more than I would have otherwise, but at least my mind is no longer cluttered with unfinished projects.

Lessons learned

  • The Game Boy is an awesome little machine.
  • It's hard to go back to untyped JavaScript.
  • Understanding how things work is still one of my favorite pastimes.
  • The web is capable of creating quite rich user experiences.

Conclusion

Building a Game Boy emulator was (and hopefully continues to be) a great project. Perhaps when learning another new language I'll build another emulator. Feel free to play with my emulator or fork it (link at the top).