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:
Code:
ItemScript_Activator
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
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
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:
Code:
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:
Code:
.align 2
.thumb
/*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:
Code:
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
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
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
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
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):
Code:
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.
Code:
.align 2
.thumb
/*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".