A post I read last week convinced me that I needed to start messing around with ROM hacking some of my favorite old school games. My platform of choice was the Nintendo GameBoy, a console I've played around with previously, and the first title I attempted was Seiken Densetsu -- released as "Final Fantasy Adventure" in the States. The memory addresses and values below are the same for both the US and Japanese versions of the game. I took the same approach as that of the referenced post, inspecting addresses and changing values with debugging tools. I'll look at some of the values in RAM I was able to hack, then wrap up by giving a short explanation on how to actually patch the game binary to make our modifications permanent.
So, on to the obvious first target:
Health, Wealth, and Power
The player data is stored in a block between 0xD6C5 and 0XD7C4. HP is a 16-bit unsigned integer at 0xD7B2. As such, it has a maximum value of 2^16-1. Mana is stored likewise, at address 0xD7B6. Gil is another 16-bit uint at 0xD7BE. Setting these values to max will easily be sufficient to last an entire play-through of the game.
That's a pretty boring hack, so we'll move on to player stats. These are single byte values, also stored in the block mentioned above, as follows:
My Level 99 character:
With that out of the way, let's move on to spells and equips. Equipment and other items are all stored as single-byte values, with items, magic, and equip sections each having their own block of memory. Let's take a look at each type:
This game has only 8 spells available, as follows:
Spells learned by the player are stored in the range 0xD6D5 - 0xD6DC. Each of the values above can be placed in this address range to obtain every spell like so:
Extra equipment is stored in the range 0xD6DD - 0xD6E8, one byte per item. Let's take a look at each type:
There are 15 weapons in total, as well as a null value for no weapon. They are:
The currently equipped weapon can be modified by writing one of these values to 0xD6E9. Strangely, writing values between 0x11 - 0x2D inclusive results in the player casting magic animations when you try to attack, though they don't actually have any effect.
The currently equipped helm can be changed by writing one of these values to 0xD6EA.
The currently equipped armor can be changed by writing one of these values to 0xD6EC.
The currently equipped shield can be changed by writing one of these values to 0xD6EE.
An example of maxed gear:
In total, there are 34 items -- not including gear and magic books. The inventory slots are single byte values stored in the address range 0xD6C5 – 0xD6D4. This address block has a 1:1 mapping to the block at 0xD69B – 0xD6AA, which stores the quantity of the item in each slot. The items are as follows:
You can fill the inventory with any number and combination of these items:
While looking for the values above, I ran into a number of other values I thought were interesting. Here are a few of the better ones:
Changing the value at 0xC241 from 0x01 to 0x02 doubles your movement speed. It can be changed higher, but this results in a lot of glitching.
The value at 0xC242, if set to 0x00, enables a no-clip/free-roam mode where no collision checking is performed on the player. You take no damage and can walk over any terrain. This is great for exploring different areas of the map:
You can warp around the map by changing the player's xpos at 0xC245 and ypos at 0xC244. This can allow you to skip around obstacles or through walls. Not terribly useful in light of the no-clip cheat above.
Patching the ROM
Now that we have the memory addresses of some values we'd like to change, we can go ahead and create a patch for the ROM so the modifications don't need to be applied manually through a debugger each time we play. This way, the game can be played on real hardware or shared, and our changes will persist. The basic idea here is to find the bits of executable code in the ROM file itself that affect the RAM values above. We start by setting write-mode breakpoints on the addresses of these RAM values. When the breakpoints are triggered, we can inspect the instructions being run and modify them to suit our needs. My favorite change we've made so far is the movement speed buff, so let's patch that into the game. We know the movement speed multiplier is stored at 0xC241, so we'll set a write-mode breakpoint there:
Now, restart the game and watch for it to trigger:
This bit is what we're interested in:
At this point, register A holds the value 0x01, setting the movement multiplier to default. We can replace these two lines with something like:
This will increment register A, load the value of register A into the address pointed to by register HL, and finally increment HL. Here's what the change looks like:
If we reset and start a new game, we can see that it worked. However...as soon as we take damage, the breakpoint is triggered again by this line:
We'll replace this with an instruction to simply decrement register HL (since we don't know what it might be used for after the subroutine call returns):
Now, the movement modifier is retained after the player takes damage. Perfect!
Finally, let's save the changes we made to the ROM and use it to make a patch in Lunar IPS:
This patch file is simply a list of differences between the original and the modified versions of the ROM. It allows us to share our modifications without redistributing a copyrighted binary. It also allows mixing and matching different modifications without the need for multiple different ROM files.