RSS About
An edited screenshot of Metroid II showing Samus facing the screen next to a Metroid egg that should have hatched but has not.

Funny bugs: the missing byte

For literal years, there was a bug in my emulator1 that was just sneaky enough to kinda break a few games.

Like, the coin count in Super Mario Land would constantly increase, which was sort of funny and didn’t prevent you from playing the game…

An animated GIF of Super Mario Land showing Mario standing motionless while the coin count at the top of the screen keeps increasing, adding an extra life every time it wraps up after 99.
Built-in (accidental) infinite lives cheat code.

Or the items wouldn’t blink in Metroid II, which looked weird if you already knew the game (I did) and was otherwise pretty harmless…

An animated GIF of Metroid II showing Samus standing motionless while a health item is showing with no animation, where it should normally be blinking.
It’s not as obvious but that health pellet should be blinking. It can still be taken!

Or the first metroid in Metroid II just wouldn’t hatch, which was annoying and soft-locked you there and then…

An animated GIF of Metroid II showing Samus reaching and then moving around the first Metroid egg that is normally scripted to hatch when she gets close to it. Here, it does not, and the animation ends on a still of Samus facing the screen.
That Metroid egg should hatch when you come close, and the rest of the map only unlocks when you beat it, so that was not ideal.

So, you know… little things like that.

It will get fixed. Eventually.

I didn’t really know how to tackle the problem at first — in fact, I had no idea those two bugs were even related, back then. I also vaguely hoped it would somehow get fixed as I implemented and debugged more features, but it didn’t.

I finally thought it might be time to see if I wasn’t missing some obvious hardware register or something. So I traced memory accesses to addresses that were not handled by the emulator.

Some of it was confusing because it showed accesses to “prohibited” addresses in GameBoy memory. There was one, however, that looked weird…

Logs of memory accesses showing unmapped addresses that the emulator’s MMU did not have a handler for. The address 0xfffe is highlighted.
Wait, that last address can’t possibly be unmapped, it’s the bottom of our stack!

Now, I don’t know the GameBoy’s memory map by heart but I am positive that this 0xfffe address should be handled somewhere in the emulator, it looks like it should be in High RAM, and I definitely had a dedicated Addressable object for that.

But I did remember that there was a quirk and that High RAM did not quite go all the way to 0xffff because that specific address is used for interrupts.

Does that look like an off-by-one error to you? It sure looked like an off-by-one error to me…

diff --git a/gameboy/gameboy.go b/gameboy/gameboy.go
index 9f16f36..fa7625d 100644
--- a/gameboy/gameboy.go
+++ b/gameboy/gameboy.go
@@ -173,7 +173,7 @@ func New(config *options.Options) *GameBoy {
     }

     wram := memory.NewRAM(0xc000, 0x2000)
-    hram := memory.NewRAM(0xff80, 0x7e)
+    hram := memory.NewRAM(0xff80, 0x7f)
     g.JPad = joypad.New() // TODO: interrupts
     mmu := memory.NewMMU([]memory.Addressable{
         boot,

The diff output for the fix that corrected the emulator’s High RAM size. Which was off by one because numbers are hard2.

And now Super Mario Land works as intended. Metroid II as well — I suspect that last High RAM byte was used as a frame counter for animating sprites and that, in turn, must have been used to trigger some events.

An animated GIF of Super Mario Land playing as you would expect. The coin count only increases when Mario actually obtains them.

An animated GIF of Metroid II showing Samus reaching the first Metroid egg. It hatches and she can then destroy the Metroid, which unlocks the next part of the map.

In retrospect, I am stunned that so many games were entirely playable while a whole byte of High RAM was missing3.

I’m also surprised — once again, somewhat pleasantly — that it wasn’t more complicated to fix than that.

Numbers be hard, I swear. Zero even more so!


  1. I mean, in addition to the countless others that are still in there to this day, obviously. ↩︎

  2. In my brain’s defense, 0xff80 + 0x7e is totally equal to 0xfffe, the last available address in High RAM. Except here, 0x7e represents the memory array’s size, and with zero-based arrays, you only get items in the [0, size[ range, where size is not included. Bummer. ↩︎

  3. Well, not missing as such, but to the emulator it looked like its value was always 0xff↩︎