Inspired by the recent Twitch Plays Pokémon phenomenon, I thought it would be interesting to delve into some old school ROM hacking with the first generation Pokémon games. In particular, Pokémon Red and Blue are already relatively well documented by the ROM hacking community and have several infamous bugs, technical analysis of which reveal useful information about the game internals. Exploiting this existing knowledge means we can cheat a little and not get bogged down in the drudgery of analysing a ROM from scratch.
We will require:
- Gameboy Color emulator and debugger
- Hex editor
- Pokémon ROM
On Windows, bgb appears to be one of the best choices of emulator as it features a built in debugger. This includes disassembly, register, and memory views (shown below). The hex editor I’m using is WinHex. As for the game ROM, it goes without saying that you should own a physical copy of the game before you download a ROM.
For this guide I am using Pokémon Blue, however I believe it should also work with Red.
Our goal will be to change one of the starting Pokémon, Bulbasaur, into something a bit more interesting: Mew (yes, Mew is built into the Generation 1 games). This should prove to be non-trivial but easy enough to do in an afternoon. In addition, choosing your character’s first Pokémon occurs after 30 seconds of gameplay meaning we can test the hack without needing to play for hours beforehand.
Setup Load up the emulator. Let’s take a snapshot of the game at the point just before we choose a Pokémon so we don’t have to navigate the dialogue every time. For readers unfamiliar with the game, the left-hand screenshot below shows the desired game state. Visible are 3 Pokemon the player must choose from on a desk: Charmander, Squirtle, and Bulbasaur (left to right). We are attempting to change the rightmost from Bulbasaur to another Pokémon.
Note that when the player selects a Pokémon, some information about it is displayed in the form of a Pokédex entry, shown in the right-hand screenshot below:
Before we begin, it is necessary to have a rudimentary understanding of the console’s internals. The Gameboy Color’s CPU is an 8-bit modified Zilog Z80 with a 16-bit address bus meaning it can access 65,536 byte addresses. To overcome this 64KB limitation (especially considering the Pokemon ROM is 1MB, for example), memory bank switching is used. The first 16KB of this address space (actually slightly less; from address
0x0100-0x3FFF) is mapped to the first 16KB of the cartridge ROM. This area is referred to as ROM bank 0. The next 16KB of address space (from
0x4000-0x7FFF) can be mapped to any other 16KB bank within the ROM.
It is important that we are able to convert between ROM addresses and internal addresses. ROM bank X extends from ROM address
X * 0x4000 to (X + 1) * 0x4000 - 1. As mentioned before, this is then mapped to internal addresses
0x4000-0x7FFF. Therefore, to find the ROM address corresponding to a particular bank and internal address, we use the following conversion formula:
ROM address = (internal address - 0x4000) + bank * 0x4000
Internal address = ROM address % 0x4000 + 0x4000 bank = ROM address / 0x4000
Internally, Pokémon are uniquely identified by index numbers which are completely distinct from the Pokédex numbers that readers may be familiar with. For example, while Bulbasaur has a Pokédex number of 1, its index number is
0x99. From this article (which is a recommended read in itself), we know that there exists a lookup table at address
0x41024 in the ROM that maps index numbers to corresponding Pokédex entry numbers. For example, the byte at ROM address
0x41024 + 0x99 = 0x410BC has a value of 1 and therefore represents the mapping for Bulbasaur.
We begin by considering how a reasonable implementation of the game might work. Intuitively, we might expect that somewhere in the ROM are 3 bytes, one for each Pokémon on the desk, containing that Pokémon’s index number. We would then expect that once a Pokémon is selected by the player, the aforementioned lookup table will be consulted to find the Pokédex index for that index number in order to display the corresponding Pokédex entry (e.g. for Bulbasaur as shown in the screenshot above). If we can detect when this value is read in memory, perhaps we can find the location of where the original index is stored.
Luckily, bgb supports memory access breakpoints. Recall that the byte at ROM address
0x410BC contains the Pokédex number for Bulbasaur. Using our conversion formula we find this should be mapped to internal address
0x410BC % 0x4000 + 0x4000 = 0x50BC. This is where we set our breakpoint, triggered on a memory read:
We then resume our game and select Bulbasaur. After a few seconds, the debugger breaks. In the memory view, we check to confirm that the correct bank (
0x410BC / 0x4000 = 0x10) is selected. It is – success! The bgb debugger at this point is shown below:
Now we take a look at the disassembly view. The debugger is paused on the instruction that triggered the memory access breakpoint (highlighted in blue). This instruction reads the byte at address held in register
HL and writes it to register
A. We can verify that the
HL register contains the address
0x50BC using the register view at the top right.
We can see this looks like a function that begins at address
push bc push hl ld a, (D11E) dec a ld hl, 5024 ld b, 00 ld c, a add hl, bc ld a, (hl) ld (D11E), a pop hl pop bc ret
In line 3, a byte is read from address
0xD11E and added to the constant
0x5024—which looks suspiciously like the base address of the index number lookup table—to result in the address
0x50BC. Aha! Lets follow that clue to address
0xD11E and see what we find there. Indeed we find that the byte at that address contains the value
0x99 (highlighted grey in the memory view):
0xD11E is in an area of internal RAM, therefore it is not present in the ROM and must have been written by the game program. So where does it get written from originally? Let’s add an on write access breakpoint to find out. After reloading our saved state and selecting Bulbasaur again, we break this time at address
0x5136 (ROM bank 7). The disassembly at that location looks like:
Aha, that instruction at address
0x512F looks exactly like what we want – the index number appears to be a hardcoded operand! Lets open up the ROM in our hex editor and modify the corresponding byte. Using our conversion formula,
ROM address = (0x512F – 0x4000)+ 7 * 0x4000 = 0x1D12F. As the instruction is 2 bytes, the operand is actually at address
0x1D12F + 1. We change it to
0x15 – the index number of Mew:
Now we save the file and restart the game. After a few warnings about invalid checksums, we have success!
Here follows a non-exhaustive list of things to try:
- Change the other 2 starting Pokémon.
- Change the Prof. Oak dialogue (notice that he still refers to Bulbasaur, so this text must be located somewhere else).
- Change the Pokémon starting levels.
- Give the starting Pokémon more HP/Attack Power/moves (hint: read).
- Change the Pokémon’s graphics.