Hacking Gameboy ROMs: Seiken Densetsu

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.

/images/maxhpmpgil.PNG

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:

Address Stat
0xD7BA Player Level
0xD7C1 Stamina
0xD7C2 Power
0xD7C3 Wisdom
0xD7C4 Will

My Level 99 character:

/images/maxstats.PNG

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:

Magic

This game has only 8 spells available, as follows:

Value Spell
0x01 Cure
0x02 Heal
0x03 Mute
0x04 Sleep
0x05 Fire
0x06 Ice
0x07 Lit
0x08 Nuke

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:

/images/maxmagic.PNG

Equipment

Extra equipment is stored in the range 0xD6DD - 0xD6E8, one byte per item. Let's take a look at each type:

Weapons

There are 15 weapons in total, as well as a null value for no weapon. They are:

Value Weapon
0x00 None
0x01 Broadsword
0x02 Battleaxe
0x03 Sickle
0x04 Chain
0x05 Silver Sword
0x06 Wind
0x07 Were
0x08 Star
0x09 Blood
0x0A Dragon Sword
0x0B Flame Chain
0x0C Ice Sword
0x0D Zeus Axe
0x0E Rusty Sword
0x0F Thunder Spear
0x10 XCalibr

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.

Helms

Value Helm
0x27 Bronze
0x28 Iron
0x29 Silver
0x2A Gold
0x2B Opal
0x2C Samurai

The currently equipped helm can be changed by writing one of these values to 0xD6EA.

Armor

Value Armor
0x11 Bronze
0x12 Iron
0x13 Silver
0x14 Gold
0x15 Flame
0x16 Ice
0x17 Dragon
0x18 Samurai
0x19 Opal

The currently equipped armor can be changed by writing one of these values to 0xD6EC.

Shields

Value Shield
0x1C Bronze
0x1D Iron
0x1E Silver
0x1F Gold
0x20 Flame
0x21 Dragon
0x22 Aegis
0x23 Opal
0x24 Ice

The currently equipped shield can be changed by writing one of these values to 0xD6EE.

An example of maxed gear:

/images/maxgear.PNG

Items

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:

Value Item
0x81 Cure
0x82 X-Cure
0x83 Ether
0x84 X-Ether
0x85 Elixir
0x86 Pure
0x87 Eyedrop
0x88 Soft
0x89 Moogle
0x8A Unicorn
0x8B Silence
0x8C Pillow
0x8F Flame
0x90 Blaze
0x91 Blizr
0x92 Frost
0x93 LitBlt
0x94 Thunder
0x95 Candy
0x97 Key
0x98 Bone Key
0x99 Bronze Key
0x9F Mirror
0xA2 Amanda's Tear
0xA5 Oil
0xAA Crystal
0xAC Nectar
0xAD Stamina
0xAE Wisdom
0xAF Will
0xB2 Gold
0xB3 Fang
0xB6 Mattock
0xB7 Ruby
0xB8 Opal

You can fill the inventory with any number and combination of these items:

/images/items.PNG

Misc

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:

Movement Speed

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.

Noclip mode

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:

/images/noclip1.PNG/images/noclip2.PNG

Player Position

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:

/images/write_bp.PNG

Now, restart the game and watch for it to trigger:

/images/breakpoint_hit.PNG

This bit is what we're interested in:

ld (hl),a
inc hl

At this point, register A holds the value 0x01, setting the movement multiplier to default. We can replace these two lines with something like:

inc a
ldi (hl),a

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:

/images/modified_code.PNG

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:

/images/second_trigger.PNG
ldd (hl),a

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):

dec hl

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:

/images/patch.PNG

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.