pokemon rom researcher

Seen July 2nd, 2016
Posted August 31st, 2015
104 posts
10.9 Years
After studying battle scripts (here), I've come across the code that kept coming around to bug me, the capture rate function. Turns out, the reason we couldn't find it on the item function was because the ball function is, in fact, a simple generic "load item" function with a "search if there's room for more pokemon" function attached. The battle script function was the one that did all the work.
But this function alone was not enough. The game has two checks preventing an item to be used in battle if not real. The first one was the one who loaded the script. An extensive search for the script assumed address linked to a table, and that table linked to a function that kept all allowed items to run scripts for. The function is too big (and largely irrelevant), so only part of it will be posted:
ROM:08016418                 PUSH    {R4-R7,LR}
ROM:0801641A                 MOV     R7, R10
ROM:0801641C                 MOV     R6, R9
ROM:0801641E                 MOV     R5, R8
ROM:08016420                 PUSH    {R5-R7}
ROM:08016422                 LDR     R4, =0x2023D6B
ROM:08016424                 LDR     R2, =0x2023D6C
ROM:08016426                 LDR     R1, =0x2023BDE
ROM:08016428                 LDR     R0, =0x2023BE2
ROM:0801642A                 LDRB    R0, [R0]
ROM:0801642C                 ADD     R0, R0, R1
ROM:0801642E                 LDRB    R0, [R0]
ROM:08016430                 STRB    R0, [R2]
ROM:08016432                 STRB    R0, [R4]
ROM:08016434                 LDR     R0, =0x2022974
ROM:08016436                 MOV     R1, #0
ROM:08016438                 STRH    R1, [R0]
ROM:0801643A                 LDR     R0, =0x2022976
ROM:0801643C                 STRH    R1, [R0]
ROM:0801643E                 LDRB    R0, [R4]
ROM:08016440                 BL      sub_801CFE4
ROM:08016444                 LDR     R5, =0x2023D68
ROM:08016446                 LDR     R2, =0x20233C4
ROM:08016448                 LDRB    R1, [R4]
ROM:0801644A                 LSL     R1, R1, #9
ROM:0801644C                 ADD     R0, R2, #1
ROM:0801644E                 ADD     R0, R1, R0
ROM:08016450                 LDRB    R3, [R0]
ROM:08016452                 ADD     R2, #2
ROM:08016454                 ADD     R1, R1, R2
ROM:08016456                 LDRB    R0, [R1]
ROM:08016458                 LSL     R0, R0, #8
ROM:0801645A                 ORR     R3, R0
ROM:0801645C                 STRH    R3, [R5]
ROM:0801645E                 ADD     R1, R3, #0
ROM:08016460                 CMP     R1, #0xC     ;number of pokeball types
ROM:08016462                 BHI     loc_801649C     ;larger means other item
ROM:08016464                 LDR     R2, =0x2023D74
ROM:08016466                 LDR     R1, =PokeBall_Toss
ROM:08016468                 LDRH    R0, [R5]
ROM:0801646A                 LSL     R0, R0, #2
ROM:0801646C                 ADD     R0, R0, R1
ROM:0801646E                 LDR     R0, [R0]
ROM:08016470                 STR     R0, [R2]
ROM:08016472                 B       loc_80164FC
ROM:08016472 ; ---------------------------------------------------------------------------
ROM:08016474 dword_8016474   DCD 0x2023D6B           ; DATA XREF: ROM:08016422r
ROM:08016478 dword_8016478   DCD 0x2023D6C           ; DATA XREF: ROM:08016424r
ROM:0801647C dword_801647C   DCD 0x2023BDE           ; DATA XREF: ROM:08016426r
ROM:08016480 dword_8016480   DCD 0x2023BE2           ; DATA XREF: ROM:08016428r
ROM:08016484 dword_8016484   DCD 0x2022974           ; DATA XREF: ROM:08016434r
ROM:08016488 dword_8016488   DCD 0x2022976           ; DATA XREF: ROM:0801643Ar
ROM:0801648C dword_801648C   DCD 0x2023D68           ; DATA XREF: ROM:08016444r
ROM:08016490 dword_8016490   DCD 0x20233C4           ; DATA XREF: ROM:08016446r
ROM:08016494 dword_8016494   DCD 0x2023D74           ; DATA XREF: ROM:08016464r
ROM:08016498 off_8016498     DCD PokeBall_Toss       ; DATA XREF: ROM:08016466r
ROM:0801649C ; ---------------------------------------------------------------------------
ROM:0801649C loc_801649C                             ; CODE XREF: ROM:08016462j
ROM:0801649C                 ADD     R0, R3, #0
ROM:0801649E                 SUB     R0, #0x50
ROM:080164A0                 LSL     R0, R0, #0x10
ROM:080164A2                 LSR     R0, R0, #0x10
ROM:080164A4                 CMP     R0, #1 ;0x50 and 0x51 are the pokedoll and fluffy tail items
ROM:080164A6                 BHI     loc_80164C4
ROM:080164A8                 LDR     R0, =0x2023D74
ROM:080164AA                 LDR     R1, =off_81D99FC
ROM:080164AC                 LDR     R1, [R1]
ROM:080164AE                 STR     R1, [R0]
ROM:080164B0                 LDR     R1, =0x2023BE3
ROM:080164B2                 MOV     R10, R1
ROM:080164B4                 B       loc_801671E
ROM:080164B4 ; ---------------------------------------------------------------------------
ROM:080164B6                 DCB    0
ROM:080164B7                 DCB    0
ROM:080164B8 dword_80164B8   DCD 0x2023D74           ; DATA XREF: ROM:080164A8r
ROM:080164BC off_80164BC     DCD off_81D99FC         ; DATA XREF: ROM:080164AAr
ROM:080164C0 dword_80164C0   DCD 0x2023BE3           ; DATA XREF: ROM:080164B0r
ROM:080164C4 ; ---------------------------------------------------------------------------
ROM:080164C4 loc_80164C4                             ; CODE XREF: ROM:080164A6j
ROM:080164C4                 MOVL    R0, 0x15E  ;check for the pokeflute
ROM:080164C8                 CMP     R1, R0
ROM:080164CA                 BNE     loc_80164E8
ROM:080164CC                 LDR     R0, =0x2023D74
ROM:080164CE                 LDR     R1, =off_81D99FC
ROM:080164D0                 LDR     R1, [R1,#4]
ROM:080164D2                 STR     R1, [R0]
ROM:080164D4                 LDR     R2, =0x2023BE3
ROM:080164D6                 MOV     R10, R2
ROM:080164D8                 B       loc_801671E
ROM:080164D8 ; ---------------------------------------------------------------------------
ROM:080164DA                 DCB    0
ROM:080164DB                 DCB    0
ROM:080164DC dword_80164DC   DCD 0x2023D74           ; DATA XREF: ROM:080164CCr
ROM:080164E0 off_80164E0     DCD off_81D99FC         ; DATA XREF: ROM:080164CEr
ROM:080164E4 dword_80164E4   DCD 0x2023BE3           ; DATA XREF: ROM:080164D4r
This code is still quite big, so focus on the item-indicating lines. Directly below them there are some lines of code that load a script onto the BattleScript Pointer (0x2023D74). But those scripts are limited for the balls, pointing to a table of 12 positions. What is their content? This is it:
ROM:081D99B0 PokeBall_Toss   DCD unk_81D9A14         
ROM:081D99B4                 DCD unk_81D9A14
ROM:081D99B8                 DCD unk_81D9A14
ROM:081D99BC                 DCD unk_81D9A14
ROM:081D99C0                 DCD unk_81D9A14
ROM:081D99C4                 DCD unk_81D9A3C
ROM:081D99C8                 DCD unk_81D9A14
ROM:081D99CC                 DCD unk_81D9A14
ROM:081D99D0                 DCD unk_81D9A14
ROM:081D99D4                 DCD unk_81D9A14
ROM:081D99D8                 DCD unk_81D9A14
ROM:081D99DC                 DCD unk_81D9A14
ROM:081D99E0                 DCD unk_81D9A14
As you can see, apart from one, they are the same script. The different one is the Safari Ball.
Now, imagine you want a new type of ball. Repointing the table would work if it wasn't for a simple detail: Potions (0xd) are handy items, but adding one entry would replace their behaviour for this one. Unless you're interested in a potion that captures pokemon and does not heal them, this wouldn't be the best solution. Instead, let's repoint the whole relevant part of this function and work from it. Here's my take:
.align 2
/*This function is meant to replace the one at 08016460, where the choice
of which item is delegated to. 
08016460 to 08016463 will be replaced by a null op (0s), 08016464 stays as-is and 08016466 will be replaced by a bx r2 (10 47). At 0x08016494, place pointer to this*/
/*r1,r3 are the item, r5 is the item temp storage addr*/
/*this code will simulate the interesting part of the code, not using redundant functions.*/
Item_get:  cmp r1, #0x5
               beq Is_safari_ball
               cmp r1, #0xc
               bls Is_ball
               /*add new balls here, example given with item 0x3d*/
               cmp r1, #0x3d
               beq Is_ball
               cmp r1, #0x50
               beq dolly
               cmp r1, #0x51
               beq dolly
               mov r0, #0xaf
               lsl r0, r0, #0x1
               cmp r1, r0
               beq flute
               ldr r0, return_addr
               bx r0
.hword 0x0000
Is_safari_ball:   ldr r1, safari_script
                     b script_ender
Is_ball:           ldr r1, ball_script
                     b script_ender
dolly:              ldr r1, doll_script
                     b script_ender
flute:  ldr r1, flute_script
script_ender: ldr r0, script_ptr
                  str r1, [r0]
                  ldr r1, be3_addr
                  mov r10, r1
                  ldr r0, end_addr
                  bx r0
safari_script: .word 0x081D9A3c
ball_script:  .word 0x081D9A14
doll_script: .word 0x081D9B7C
flute_script: .word 0x081D9B86
script_ptr: .word 0x02023d74
be3_addr: .word 0x02023be3
return_addr:  .word 0x080164e9
end_addr: .word 0x0801671f
This code example shows a previously added 0x3d item as a ball. To add it yourselves, simply copy-paste the pokeball item on this new ball, changing the index to 0x3d.
Now, if you try to use it as-is, you'll have a funny scene where the player throws <insert ball name here> and it always fails to capture the target. At least it's a working ball, right? Also, note the graphics shown are those of the normal pokeball. The game defaults to that when the graphics to the corresponding ball do not exist. I will not address this problem yet.
So, it would be nice if our ball could capture something, right? Well, that's the area of another function, the one mentioned at the beggining of this post, the EF battle command, Pokemon catching function. Located at 0x0802D434, it's even bigger than the one before. It starts out by calculating lots of stuff, but let's focus only on the part that matters:
loc_802D4DC                             ; CODE XREF: ROM:0802D4BCj
ROM:0802D4DC                 LDR     R0, =0x2023D68 ;item address
ROM:0802D4DE                 LDRH    R0, [R0]
ROM:0802D4E0                 CMP     R0, #5 ;is safari ball
ROM:0802D4E2                 BNE     loc_802D508
ROM:0802D4E4                 LDR     R0, =0x2023FE8 ;special safari ball case
ROM:0802D4E6                 LDR     R0, [R0]
ROM:0802D4E8                 ADD     R0, #0x7C
ROM:0802D4EA                 LDRB    R0, [R0]
ROM:0802D4EC                 LSL     R1, R0, #2
ROM:0802D4EE                 ADD     R1, R1, R0
ROM:0802D4F0                 LSL     R0, R1, #8
ROM:0802D4F2                 SUB     R0, R0, R1
ROM:0802D4F4                 MOV     R1, #0x64
ROM:0802D4F6                 BL      sub_81E4018
ROM:0802D4FA                 LSL     R0, R0, #0x18
ROM:0802D4FC                 LSR     R5, R0, #0x18
ROM:0802D4FE                 B       loc_802D520
ROM:0802D4FE ; ---------------------------------------------------------------------------
ROM:0802D500 dword_802D500   DCD 0x2023D68           ; DATA XREF: ROM:loc_802D4DCr
ROM:0802D504 dword_802D504   DCD 0x2023FE8           ; DATA XREF: ROM:0802D4E4r
ROM:0802D508 ; ---------------------------------------------------------------------------
ROM:0802D508 loc_802D508                             ; CODE XREF: ROM:0802D4E2j
ROM:0802D508                 LDR     R3, =Base_Stats
ROM:0802D50A                 LDR     R2, =0x2023BE4
ROM:0802D50C                 LDRB    R1, [R6]
ROM:0802D50E                 MOV     R0, #0x58
ROM:0802D510                 MUL     R0, R1
ROM:0802D512                 ADD     R0, R0, R2
ROM:0802D514                 LDRH    R1, [R0]
ROM:0802D516                 LSL     R0, R1, #3
ROM:0802D518                 SUB     R0, R0, R1
ROM:0802D51A                 LSL     R0, R0, #2
ROM:0802D51C                 ADD     R0, R0, R3
ROM:0802D51E                 LDRB    R5, [R0,#8]
ROM:0802D520 loc_802D520                             ; CODE XREF: ROM:0802D4FEj
ROM:0802D520                 LDR     R2, =0x2023D68
ROM:0802D522                 LDRH    R0, [R2]
ROM:0802D524                 CMP     R0, #5
ROM:0802D526                 BHI     loc_802D52A
ROM:0802D528                 B       loc_802D620 ;is regular ball
ROM:0802D52A ; ---------------------------------------------------------------------------
ROM:0802D52A loc_802D52A                             ; CODE XREF: ROM:0802D526j
ROM:0802D52A                 SUB     R0, #6 ;is special ball
ROM:0802D52C                 CMP     R0, #6
ROM:0802D52E                 BLS     loc_802D532
ROM:0802D530                 B       loc_802D62A
ROM:0802D532 ; ---------------------------------------------------------------------------
ROM:0802D532 loc_802D532                             ; CODE XREF: ROM:0802D52Ej
ROM:0802D532                 LSL     R0, R0, #2
ROM:0802D534                 LDR     R1, =off_802D54C
ROM:0802D536                 ADD     R0, R0, R1
ROM:0802D538                 LDR     R0, [R0]
ROM:0802D53A                 MOV     PC, R0
ROM:0802D53A ; ---------------------------------------------------------------------------
ROM:0802D53C off_802D53C     DCD Base_Stats          ; DATA XREF: ROM:loc_802D508r
ROM:0802D540 dword_802D540   DCD 0x2023BE4           ; DATA XREF: ROM:0802D50Ar
ROM:0802D544 dword_802D544   DCD 0x2023D68           ; DATA XREF: ROM:loc_802D520r
ROM:0802D548 off_802D548     DCD off_802D54C         ; DATA XREF: ROM:0802D534r
ROM:0802D54C off_802D54C     DCD loc_802D568         ; DATA XREF: ROM:off_802D548o
ROM:0802D550                 DCD loc_802D598
ROM:0802D554                 DCD loc_802D5AA
ROM:0802D558                 DCD loc_802D5D8
ROM:0802D55C                 DCD loc_802D608
ROM:0802D560                 DCD loc_802D5CA
ROM:0802D564                 DCD loc_802D5CA
The way the capture is calculated for individual balls is not relevant here, so check them out if you want to know how they did it. First let's take a look at this code. Once again, balls are chosen for being below 0xc. the last piece of code perform a jump based on the ball chosen. If we replace that function with one of our own, we can jump to our own functions with our own balls of choice. But that is irrelevant if we know nothing of how to calculate the catch rate. Well, to learn that the easy way, let's check the simple pokeball codes (master ball not included):
loc_802D620                             ; CODE XREF: ROM:0802D528j
ROM:0802D620                 LDR     R1, =unk_8250892
ROM:0802D622                 LDRH    R0, [R2]
ROM:0802D624                 SUB     R0, #2
ROM:0802D626                 ADD     R0, R0, R1
ROM:0802D628                 LDRB    R4, [R0]
ROM:08250892 unk_8250892     DCB 0x14                ; DATA XREF: ROM:off_802D694o
ROM:08250893                 DCB  0xF
ROM:08250894                 DCB  0xA
This piece of code is the answer to our problems. It basically subtracts two from the item number (so ultra ball is 0x0), and goes to a small table to get the catch rate value, that is stored in R4. Basically, in Fire Red, pokeballs have a catch multiplier of 10, great balls a mutiplier of 15 and ultra balls have a catch rate of 20, meaning that 5 is the true base value, even though no balls use it. Now, we know how to change the catch rate of a pokeball: simply place a different value in r4 before going to the actual catch chance calculation.
With all of the above in mind, I created a small example for a replacement function that uses the old functions for all old pokeballs, but uses our own for the ones we add.
.align 2
/*this code is to be inserted at 0802d52c, implementing a simple bx r1(08 47), and deleting 0802d52a (00 00)
This piece of code will replace the old check for balls larger than 5 (6 to c) and determine
a new catch rate for other balls, for any new balls we wish to add.*/
new_code:    cmp r0, #0xc
                   bgt not_normal_ball /*Our balls go here*/
                   sub r0, #0x6
                   lsl r0, r0, #0x2
                   ldr r1, old_ball_checks
                   add r0, r0, r1
                   ldr r0, [r0]
                   mov pc, r0
old_ball_checks: .word 0x0802d54c
not_normal_ball:  cmp r0, #0x3d
                        beq check_this_ball
                        mov r4, #0x0
                        ldr r0, return_me
                        bx r0
/*our simple ball function, ball rate depends on a value we set in the RAM address 0x0203fe00*/
check_this_ball: ldr r0, a_RAM_addr
                       ldrb r4, [r0]
                       ldr r0, return_me
                       bx r0
.hword 0x0000
a_RAM_addr: .word 0x0203fe00 
return_me: .word 0x0802d62b
With this, we are now able to build any type of ball we want, as long as we know how to check for the condition.
I know this might be a bit confusing, but I encourage those who know to compile it and see that it works. With this, ball hacking is possible. Last entry here will be the "how to change the throw ball graphics and the in-game status screen ball graphics".
Here are the links for my work

Currently working on:
Battle Script Documentation
Another large project