← cd ../

~/labs/workshops/gba-firered/06-encontrando-a-funcao

Finding and cleaning up the functionClose the HP function in Ghidra and make the decompile readable: real before and after.

In chapter 05, dynamic analysis gave us an address: 0x0801F8AC, the instruction that writes HP. We know the exact place. Now comes the static work of turning that loose address into a whole function, with meaningful names.

This chapter has one detail worth saying up front. In a real class, Ghidra analysis is often done partially on purpose, so students do not wait for the machine to process the whole ROM. That leaves many things unfinished, and it is exactly the most common real-world scenario: you land in a block that Ghidra has not understood yet. The steps below are all manual, the way the student will do them by hand. At the end of the chapter there is a note about automating everything with a script, but learn it by hand first.

Step 1. The address is in no man’s land

Open Ghidra and go to the address found dynamically:

Navigation > Go To...
0801F8AC

In a partial analysis, three symptoms appear here:

- no function is defined at this point;
- the Functions panel shows nothing that contains this address;
- the surrounding bytes may still be raw data, not instructions.

A quick way to confirm this: ask Ghidra for the function containing the address. It will return the nearest defined function behind it, which has nothing to do with HP. In the class project, it lands in something like FUN_08017248, a small unrelated function. That proves the HP block does not belong to any closed function.

Step 2. The trap of creating a function on a fragment

The temptation is to place the cursor at 0x0801F8AC and press F to create a function right there. Do not expect a good result. That address is the middle of a function, not the beginning. If you create a function in the middle, Ghidra tries to decompile from there, without the start and without context, and the result is garbage:

void FUN_0801f8a0(void)
{
  /* WARNING: Bad instruction - Truncating control flow here */
  halt_baddata();
}

The lesson is direct: a fragment is not a function. To get a useful decompile, you need two things: the real function start and the whole body disassembled in the right mode.

Step 3. Find the function start

There are three ways to find where the function starts. Use whichever feels most natural. All three lead to the same place.

Path A. Go up looking for the prologue

Almost every Thumb function begins by saving registers on the stack. The classic prologue is:

push {r4, r5, r6, r7, lr}

In bytes, that push is f0 b5. So you can scroll the Listing upward from the HP block, looking for the first push {..., lr} that appears. In the class project, it is at:

0x0801F684:  push {r4, r5, r6, r7, lr}

That is the start of the function.

Path B. Follow the backward branch

Dynamic analysis landed at 0x0801F8A0. Looking just above the code, there is a branch that sends execution there:

0801F850:  b  0x0801F8A0

In other words, the HP block is reached by an internal branch from higher up in the same function. Following those branches backward takes you up to the beginning. This is control-flow reasoning, and it teaches the most because it shows how the function parts connect.

Path C. The battle command table

FireRed runs battle as a list of commands. There is a table where each slot points to a command function. The function we are looking at is one of those commands, the one that updates HP. By finding the table and the command index, you reach the function address without searching for the prologue by hand. This path is more advanced, but it is the cleanest once you already know the game’s structure.

Step 4. The literal pool problem

If you scroll the Listing between the start and the HP block, you will see strange lines in the middle of the code, such as:

subs r5, #0xd0
lsls r2, r0, #8

Do not panic. This is not code. These are constants that the function stores in the middle of instructions, called literal pools. Remember chapter 02: ARM cannot fit a full 32-bit address inside the instruction, so it stores that address nearby and loads it with ldr rX, [pc, #...]. Those stored values, when read as instructions, turn into this kind of garbage. When Ghidra analyzes the function correctly, it marks those regions as data and stops trying to read them as code.

Step 5. Disassemble and close the function by hand

Now the fix. Everything happens in CodeBrowser, without a script.

1. Select from the function start to a little after the end.
   In the class project: from 0x0801F684 to around 0x0801FA70.

2. Right click > Clear Code Bytes.
   This clears any wrong instruction or function already in the range,
   including a fragment you may have created by mistake.

3. With the range still selected, right click > Set Register Values...
   Set TMode = 1. This tells Ghidra the block is Thumb.

4. Go to the start, at 0x0801F684, and press D to disassemble.
   Ghidra follows the flow and builds the instructions, skipping literal pools.

5. With the cursor at 0x0801F684, press F to create the function.

Done. Now there is a function from 0x0801F684 to around 0x0801FA69, and it contains the HP block at 0x0801F8AC.

Step 6. The raw decompile

Open the Decompile window with the function selected. The result starts raw:

undefined4 FUN_0801f684(void)
{
  ushort uVar1;
  byte bVar4;
  int iVar8;
  uint *puVar14;
  byte *pbVar16;
  undefined4 in_lr;

  if (*DAT_0801f6ac != 0) {
    return in_lr;
  }
  /* ... many lines ... */
  iVar8 = (uint)*pbVar15 * 0x58 + DAT_0801f8c8;
  uVar1 = *(ushort *)(iVar8 + 0x28);
  if ((int)uVar13 < (int)(uint)uVar1) {
    *(ushort *)(iVar8 + 0x28) = uVar1 - (short)uVar13;
  }
  else {
    *(undefined2 *)((uint)*pbVar15 * 0x58 + iVar10 + 0x28) = 0;
  }
}

This works, but it is hard to read. Everything has machine names: FUN_, DAT_, uVar1, pbVar15. Even so, you can recognize what dynamic analysis already showed. * 0x58 is the size of each Pokemon slot. + 0x28 is the HP offset. *(ushort *)(... + 0x28) is current HP. Static and dynamic analysis are agreeing on the same point.

Step 7. Give it names and shape by hand

Now the part that transforms the decompile. The idea is to teach Ghidra three things: what the Pokemon struct is, where the global data lives, and what the function is called.

Create the BattlePokemon struct

Chapter 05’s dynamic analysis already gave the map: each in-battle Pokemon takes 0x58 bytes, HP is at 0x28, and max HP comes right after. Create the struct by hand:

1. Open the Data Type Manager window.
2. Right click the program file > New > Structure.
3. Name it BattlePokemon.
4. Set the total size to 0x58.
5. Define the fields you already know, by offset:
      0x00  species   u16
      0x28  hp        u16
      0x2A  level     u8
      0x2C  maxHP     u16
   The other offsets can stay undefined for now.

Name and type the globals

In the raw decompile, DAT_0801f8c8 is the address that stores the base of the Pokemon list. Back in chapter 05 we saw that this value is 0x02023BE4. Do this on top of DAT_0801f8c8 in the Listing or Decompile:

1. Press L to rename the symbol to gBattleMons.
2. Press T (or Ctrl+L) to change the type to BattlePokemon *.

Repeat the idea for the damage pointer, which stores 0x02023D54:

Rename it to gBattleMoveDamage, type int *.

And for the battler index, rename it to gActiveBattler or gBattlerTarget depending on the case, with type u8 *.

Rename the function

Finally, click the function name and press L. According to the public symbols from pret/pokefirered, this function is the command that updates HP in battle:

Cmd_datahpupdate

Step 8. The cleaned decompile

Reopen Decompile. The same code as before now becomes:

undefined4 Cmd_datahpupdate(void)
{
  ushort uVar1;
  BattlePokemon *pBVar2;
  int iVar15;
  byte *pbVar16;

  /* ... */

  pBVar2 = gBattleMons;
  iVar15 = *piVar12;                 // damage
  if (iVar15 < 0) {                  // negative damage means healing
    gBattleMons[*gActiveBattler].hp = gBattleMons[*gActiveBattler].hp - (short)iVar15;
    if (pBVar2[bVar5].maxHP < pBVar2[bVar5].hp) {
      pBVar2[bVar5].hp = pBVar2[bVar5].maxHP;   // do not exceed max HP
    }
  }
  else {
    uVar1 = gBattleMons[*pbVar16].hp;
    if (iVar15 < (int)(uint)uVar1) {
      gBattleMons[*pbVar16].hp = uVar1 - (short)iVar15;   // HP -= damage
      *gBattleMoveDamage = iVar15;
    }
    else {
      pBVar2[*pbVar16].hp = 0;                            // HP goes to zero, Pokemon faints
    }
  }
}

Compare both versions. Where it used to say *(ushort *)(iVar8 + 0x28), it now says gBattleMons[*pbVar16].hp. Same machine code, read like a person. And notice that the damage path is exactly what we will alter in the next chapter:

if damage is lower than HP, subtract damage from HP;
otherwise, zero HP and the Pokemon faints.

Step 9. Check with external sources

A good reverse engineering practice is to cross-check your finding with public material. The pret/pokefirered project is a source reconstruction of the game. Searching for Cmd_datahpupdate there, you find the original C function, with the same HP logic, the same healing and damage handling, and the same global names. That confirms your static and dynamic work reached the right place.

https://github.com/pret/pokefirered

Other methods worth showing in the workshop

The HP case used dynamic search plus watchpoint plus static reading. It is worth showing students that other paths exist, and that combining them is the strong approach:

Follow xrefs       after closing the function, ask Ghidra who calls it.
                   This leads to the battle command table and interpreter.

Pointer table      find a function table and rename everything at once, using
                   pret names as a guide.

Compare with save  change a value in the game, save, and compare dumps to isolate
                   where that data lives, as in chapter 05.

Strings            search game text in the ROM and follow who uses it to find
                   menus, dialogue, and screens.

Public cross-check always compare with pret/pokefirered to validate names and structs.

The general rule: static analysis shows logic, dynamic analysis shows live data, and public sources confirm names. When all three agree, you can trust the finding.

Note about automation

Everything done by hand here can also be done by script. Ghidra has a Python executor that gives access to the same API: you can mark Thumb, disassemble, create the function, create the struct, rename, and type everything in one run. In a class, the manual path teaches the concepts. The scripted path comes later, when you need to repeat the process across many functions or many game versions. Learn it by hand first, then automate when the work becomes repetition.

What to take from this chapter

- A loose address from dynamic analysis is almost never the start of a function.
- Creating a function in the middle produces garbage. Find the real start first.
- The start can be found three ways: push prologue, backward branch, command table.
- Partial analysis leaves code undisassembled. Mark Thumb, press D and F by hand.
- Creating the struct and naming globals turns the decompile into readable code.
- Always check with pret/pokefirered.