
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…

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…

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

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…

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.
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!
-
I mean, in addition to the countless others that are still in there to this day, obviously. ↩︎
-
In my brain’s defense,
0xff80 + 0x7e
is totally equal to0xfffe
, 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. ↩︎ -
Well, not missing as such, but to the emulator it looked like its value was always
0xff
. ↩︎