← cd ../

~/labs/workshops/gba-firered/07-primeiro-patch

The first patchWrite, assemble, and install a hook that makes the enemy faint in one hit.

Now we put everything together. You already know how to read ARM and Thumb, found the instruction that applies damage, and understood the in-battle Pokemon struct. This chapter uses that to change the game’s behavior for real.

The patch goal:

The opponent's Pokemon faints from any hit.
Your Pokemon does not lose HP.

The attack point

In chapter 05, the watchpoint brought us to this Thumb block, inside the routine that updates HP in battle:

0801f8aa  ldrh r0, [r2, #0x28]   r0 = current HP
0801f8ac  ldr  r1, [r7]          r1 = damage
0801f8ae  cmp  r0, r1
0801f8b0  ble  0x0801f8d0        if HP <= damage, go to fainting
0801f8b2  subs r0, r0, r1        HP = HP - damage
0801f8b4  strh r0, [r2, #0x28]   writes the new HP

The naive idea would be to replace ldr r1, [r7] with something that puts huge damage in r1. But that has two problems. First: this same routine runs for both your Pokemon and the opponent, so huge damage would kill both. Second: a Thumb instruction has only 2 bytes, and there is no room there for logic that decides who is who.

The solution is a hook.

What a hook is

A hook replaces some original code instructions with a jump to new code, written by you, in a free area. That new code does what it needs to do and then returns to the original flow. The classic name for this is a trampoline.

original code at 0x0801F8AC
    jumps to our code in a new area
    our code decides: is the target player or opponent?
    sets damage accordingly
    returns to the original flow

To get a new area, we increase the ROM size. The original ROM is 16 MiB, from 0x08000000 to 0x08ffffff. The project build expands it to 32 MiB and places the new code at the first free address:

original ROM   0x08000000 to 0x08ffffff   (16 MiB)
new area       starting at 0x09000000      (where our code lives)

The rule for who is who

Pokemon in battle are numbered. The number, called battler, says which side they are on:

0 = player, left position
1 = opponent, left position
2 = player, right position
3 = opponent, right position

Notice the pattern: even number means player, odd number means opponent. To know the side, just inspect bit 0 of the number:

bit 0 = 0  ->  even  ->  player
bit 0 = 1  ->  odd   ->  opponent

The patch logic, in pseudo C, is:

battler = *gActiveBattler;

if (battler is odd)      // opponent
    damage = hp;         // damage equals HP, so it faints
else                     // player
    damage = 0;          // no damage

if (hp > damage)
    hp = hp - damage;
else
    hp = 0;

Writing the Assembly

The hook code is in patches/enemy_one_hit.s, commented line by line. Its core is this Thumb code:

enemy_one_hit:
    ldrb r1, [r5]        @ r1 = battler index
    lsls r1, r1, #31     @ move bit 0 into the sign bit
    bpl  player_target   @ if result is not negative, it is even (player)

    @ opponent target: damage = hp
    movs r1, r0
    b    compare_hp

player_target:
    @ player target: damage = 0
    movs r1, #0

compare_hp:
    cmp  r0, r1
    bgt  survives
    ldr  r1, =0x0801F8D1   @ fainting path, return to original
    bx   r1

survives:
    subs r0, r0, r1
    ldr  r3, =0x0801F8B5   @ return to the original strh
    bx   r3

It is worth stopping on two tricks, because they show real Thumb details.

The first is lsls r1, r1, #31. Basic Thumb does not have an instruction to test a bit with an immediate value. So the code shifts the number 31 positions left, which moves bit 0 into the sign position. If bit 0 was 1, the result becomes negative, and bpl, meaning branch if plus, does not branch. It is a compact way to ask “is this number odd?”.

The second is the return addresses ending in 1:

0x0801F8D1   is the real fainting path at 0x0801F8D0, with the Thumb bit set
0x0801F8B5   is the original strh at 0x0801F8B4, with the Thumb bit set

That +1 is the Thumb bit explained in chapter 02. It keeps the CPU in Thumb mode after the return jump. Without it, the CPU would try to execute ARM and crash.

Assembling the Assembly

Assembly text does not run. It must become machine bytes. The ARM toolchain assembler does that:

sudo apt install -y gcc-arm-none-eabi

Assembly has two steps. The first turns the .s file into an object. The second extracts only the code section bytes into a raw binary:

arm-none-eabi-as -mcpu=arm7tdmi -mthumb -o build/enemy_one_hit.o patches/enemy_one_hit.s
arm-none-eabi-objcopy -O binary -j .text build/enemy_one_hit.o build/enemy_one_hit.bin

-mthumb is important: it assembles in Thumb, which is the mode used by that game block.

The jump that replaces the original

In place of the original instructions at 0x0801F8AC, we write a long jump. In Thumb it looks like this:

ldr r1, [pc, #0]     @ loads the destination address stored just below
bx  r1               @ jumps there
.word 0x09000001     @ destination: 0x09000000 with the Thumb bit set

In bytes, this is eight bytes. It occupies exactly the space of the four original instructions (ldr, cmp, ble, subs), which are therefore reproduced inside our code.

Installing with the project build

You do not need to perform every step by hand. The script tools/build_enemy_one_hit.py automates everything and also checks that nothing moved:

python3 tools/build_enemy_one_hit.py

It does, in order:

1. Checks the SHA1 of the original ROM. If it is not the right ROM, it stops.
2. Checks that the bytes at 0x0801F8AC are still the originals
   (39 68 88 42 0e dd 40 1a). This prevents patching the wrong place.
3. Assembles patches/enemy_one_hit.s.
4. Expands the ROM from 16 to 32 MiB.
5. Writes the new code at 0x09000000.
6. Writes the long jump at 0x0801F8AC.
7. Saves rom/PokemonFireRed_enemy_one_hit.gba.

The output shows the summary:

New code: 0x09000000 (... bytes)
Hook: 0x0801f8ac -> 0x09000000
Expanded ROM: 32 MiB
Output: rom/PokemonFireRed_enemy_one_hit.gba

The original-byte check in step 2 is a lesson worth repeating for any patch: before writing over code, confirm the target is really what you think it is.

From address to file offset

When you edit the ROM on disk, the file offset matters, not the memory address. The conversion is the one from chapter 01:

file offset = address - 0x08000000

So the hook at 0x0801F8AC is at file offset 0x001F8AC, and the new code at 0x09000000 is at offset 0x01000000. The script does this conversion by itself, but it is good to know where it comes from.

Testing

Open the patched ROM in mGBA:

rom/PokemonFireRed_enemy_one_hit.gba

Load the save, enter battle, and attack. The opponent should faint from the first hit, and your Pokemon should not lose HP even when hit.

How to restore normal player damage

The Assembly file already documents this. To make your Pokemon receive normal damage again, change this line in the player_target label:

movs r1, #0          @ zero damage

to:

ldr  r1, [r7]        @ original damage

And assemble again. This is the kind of small adjustment that shows the class that the patch is just code, and code can be edited.

What to take from this chapter, and from the workshop

- A hook replaces original instructions with a jump to your code.
- Expanding the ROM gives free space for new code (starting at 0x09000000).
- The patch decides the target from battler bit 0: even is player, odd is opponent.
- Thumb return addresses end in 1 (the Thumb bit).
- Always check original bytes before writing over them.
- file offset = address - 0x08000000.

The full path you followed:

GBA architecture
  -> reading ARM and Thumb
  -> importing and mapping in Ghidra (static)
  -> finding HP in RAM with mGBA and GDB (dynamic)
  -> writing, assembling, and installing a patch

That is the game reverse engineering cycle. The target changes, the tools change a little, but the method is always this.