JPAN
pokemon rom researcher
- 104
- Posts
- 16
- Years
- Seen Jul 2, 2016
mod edit: just for your information, ingame routines that do exactly this already exist within the code
NOTE: all the code posted here was made with the US fire red version in mind. If you are interested in using this code in any other verison, it should still work as long as you change the values for the variable adress and party adress with those from your respective version. To find them, see the end of this post.
I have made an algorithm to decrypt the encripted data and place it somewhere in the RAM where we can look at and modify it acording to our needs. First, a brief explanation on how it works.
The GBA pokemon data is composed of 80 bytes (or 100, for party pokemon). On those 80 bytes, we can divide it in two:
Applying that key to the data, one word at the time will decrypt it, and passing it on decrypted information will encrypt it.
On that second block, the information is compacted in four sub-blocks that store pieces of information of the same nature. Those sub-blocks change position depending on the Personality value. The remainder of Personality\24 indicates it position.
To make it harder to modify, there is also a checksum for this second block on the first block, and if the second block, adding all data as 16bit words is not equal to that checksum, the pokemon becomes a BadEgg.
This algorithm works by having a party pokemon number stored at 0x8004 (where the special 9f stores it)
So, what this algorithm does:
Edit: the portions in bold on the code is the part you need to change in order for it to work. Goes for all valid posts.
But this code is pretty much useless if you don't know how to access its information. I will post further ahead some code examples on how to change and read this information.
This code has a second part to it, that must be called upon to save changes on the selected pokemon. It's pretty much the reverse, but much shorter because it doesn't need to know what was changed or the position where it must put the data. It just corrects the checksum and copies the information after encrypting it.
With these two algoritms, many new commands manipulating and checking pokemon caracteristics are possible. I hope this code will be helpful on your rom-hacking.
Edit:
For easy insertion, The two main codes are now available in a small hex file, at this post.
https://www.pokecommunity.com/posts/4914212/
Now, some asm examples on how to use this new found information.
Identifying a pokemon species
I know there is a combination of commands to identify a pokemon, but it only works on pokemon that are not eggs. This code allows you to identify a pokemon egg as well. Only one problem. You must manually set the variable 0x8004 to the egg's position as it cannot be selected by special 0x9f. I would recommend setting it to 0x0, and asking the player to put the egg in the first party slot.
Or in ROM-ready version
Increasing a pokemon happiness by any value
If you wish to make some ways for a pokemon to gain happiness, or to make a pokemon love his trainer after an event, this code will allow it.
b50f push {r0-r3, lr}
4808 ldr r0, 0x0203f408 ;growth structure address
6800 ldr r0, [r0]
4908 ldr r1, 0x020370c2 ;var 0x8005 contains value to increase happiness by
880b ldrh r3, [r1]
7a42 ldrb r2, [r0+0x9] ;current happiness
189a add r2, r3, r2
2aff cmp r2, 0xff ; sees if the sum overflows the max happiness level
dc01 bgt correct
7262 strb r2, [r0 + 0x9]
bd0f pop {r0-r3, pc}
23ff correct: mov r3, 0xff
1ad3 sub r3, r2, r3 ;calculates difference between original and max happiness
22ff mov r2, 0xff
7242 strb r2, [r0+ 0x9]
800b strh r3, [r1] ;places actual given value back in variable, for checking purposes.
bd0f pop {r0-r3, pc}
xxxx garbage
pointers
ROM-ready version
See a Pokemon's pokeball
If you wish to have a place to release pokemon but keep their ball, or for a script that
bars entrance to any trainer who doesn't keep all pokemons in a special kind of ball, we have this code
b503 push {r0-r1, lr}
4803 ldr r0, 0x0203f414 ;Miscellaneous data
6800 ldr r0, [r0]
4903 ldr r1, 0x020370c2 ;var 0x8005, where the ball is stored afterards
78c0 ldrb r0, [r0 + 0x3] ; Because ball storage is peculiar, one must get rid of all
08c0 lsl r0, r0, 0x3 ;remainig information inside it
8008 strh r2, [r1]
bd03 pop {r0-r1, pc}
pointers
ROM ready version
And that's it for now. I shall post more examples later on. For now, you may post requests on this thread.
For more information on the data stored, I recommend the place where I learned about it, bulbapedia (search there for Pokémon data substructures in the GBA).
To find the addresses for your version:
To find the variables 0x8000:
NOTE: all the code posted here was made with the US fire red version in mind. If you are interested in using this code in any other verison, it should still work as long as you change the values for the variable adress and party adress with those from your respective version. To find them, see the end of this post.
I have made an algorithm to decrypt the encripted data and place it somewhere in the RAM where we can look at and modify it acording to our needs. First, a brief explanation on how it works.
The GBA pokemon data is composed of 80 bytes (or 100, for party pokemon). On those 80 bytes, we can divide it in two:
- The first 32 bytes of unencrypted information, like the nickname, original trainer name and IDs and a 32 bit word usually called Personality/Pokemon ID, that determines if it's shiny, gender, nature, amongst other things;
- The remaining 48 bytes, encrypted information that contains almost everything important about our pokemon, like species, IV's, EV's, contest and regular status, happiness, experience, level...
Applying that key to the data, one word at the time will decrypt it, and passing it on decrypted information will encrypt it.
On that second block, the information is compacted in four sub-blocks that store pieces of information of the same nature. Those sub-blocks change position depending on the Personality value. The remainder of Personality\24 indicates it position.
To make it harder to modify, there is also a checksum for this second block on the first block, and if the second block, adding all data as 16bit words is not equal to that checksum, the pokemon becomes a BadEgg.
This algorithm works by having a party pokemon number stored at 0x8004 (where the special 9f stores it)
So, what this algorithm does:
- Finds the encryption key and stores it at the first memory position (first)
- Stores the checksum at another memory position (first + 0x6)
- decrypts and copies all the data from the selected pokemon (first+ 0x18)
- trough a large algorytm of compares, stores all four sub-blocks starting positions (growth = first +0x8; attack = first + 0xc; effort = first +0x10; miscelaneous info = first + 0x14)
Edit: the portions in bold on the code is the part you need to change in order for it to work. Goes for all valid posts.
Spoiler:
.align 2
.thumb
Start: PUSH {R0-R7,LR}
LDR R5, var_8004
LDRH R5, [R5, #0x0]
LDR R4, party_addr
MOV R6, #0x64
LDR R7, New_poke_addr
MUL R5, R6
ADD R4, R4, R5
B decrypt
.hword 0x0000
var_8004: .word 0x20370C0
party_addr: .word 0x2024284
New_poke_addr: .word 0x203F400
decrypt: LDR R0, [R4, #0x0]
PUSH {R0}
LDR R1, [R4,#0x4]
EOR R1, R0
STR R1, [R7, #0x0]
LDRH R1, [R4, #0x1C]
STRH R1, [R7, #0x6]
MOV R2, #0x20
ADD R0, R2, R4
MOV R2, #0x18
ADD R1, R2, R7
MOV R2, #0xC
LDR R5, [R7, #0x0]
decrypt_loop: LDR R3, [R0, #0x0]
EOR R3, R5
STR R3, [R1, #0x0]
ADD R1, #0x4
ADD R0, #0x4
SUB R2, #0x1
CMP R2, #0x0
BNE decrypt_loop
POP {R0}
MOV R1, #0x18
MOV R2, #0xC0
LSL R2, R2, #0x18
CMP R2, R0
BHI lower_than_c
SUB R0, R0, R2
lower_than_c: LSR R2, R2, #0x1
CMP R2, R0
BCS lower_than_6
SUB R0, R0, R2
LSL R0, R0, #0x0
lower_than_6: SWI #0x6
ADD R0, R1, #0x0
MOV R1, #6
SWI #0x6
g_in_first: CMP R0, #0x0
BNE g_in_second
MOV R2, #0x18
B g_final
g_in_second: CMP r1, #0x1
BGT g_in_third
MOV R2, #0x24
B g_final
g_in_third: MOV R2, #1
AND R2, R1
CMP R2, #1
BEQ g_in_fourth
MOV R2, #0x30
B g_final
g_in_fourth: MOV R2, #0x3C
g_final: ADD R2, R7, R2
STR R2, [R7, #0x8]
m_in_first: CMP R0, #0x3
BNE m_in_second
MOV R2, #0x18
B m_final
m_in_second: CMP R1, #0x4
BLT m_in_third
MOV R2, #0x24
B m_final
m_in_third: MOV R2, #0x1
AND R2, R1
CMP R2, #0x1
BNE m_in_fourth
MOV R2, #0x30
B m_final
m_in_fourth: MOV R2, #0x3C
m_final: ADD R2, R7, R2
STR R2, [R7,#0x14]
a_first: CMP R0, #0x1
BGT a_second
CMP R0, #0x0
BEQ a_lesser_second
MOV R2, #0x18
B a_final
a_second: MOV R3, #0x2
BEQ a_greater_second
CMP R1, #0x3
BEQ a_greater_second
CMP R1, #0x0
BEQ a_greater_third
CMP R1, #0x5
BEQ a_greater_third
MOV R2, #0x3C
B a_final
a_greater_second: MOV R2, #0x24
B a_final
a_greater_third: MOV R2, #0x30
B a_final
a_lesser_second: CMP R1, #0x1
BGT a_lesser_third
MOV R2, #0x24
B a_final
a_lesser_third: MOV R2, #0x1
AND R2, R1
CMP R2, #0x2
BNE a_lesser_fourth
MOV R2, #0x30
B a_final
a_lesser_fourth: MOV R2, #0x3C
a_final: ADD R2, R7, R2
STR R2, [R7,#0xC]
e_first: CMP R0, #0x2
BLT e_second
CMP R0, #0x2
BGT e_greater_second
MOV R2, #0x18
B e_final
e_second: CMP R1, #0x2
BEQ e_lesser_second
CMP R1, #0x3
BEQ e_lesser_second
CMP R1, #0x0
BEQ e_lesser_third
CMP R1, #0x5
BEQ e_lesser_third
MOV R2, #0x3C
B e_final
e_lesser_second: MOV R2, #0x24
B e_final
e_lesser_third: MOV R2, #0x30
B e_final
e_greater_second: CMP R1, #0x5
BLT e_greater_third
MOV R2, #0x24
B e_final
e_greater_third: MOV R2, #1
AND R2, R1
CMP R2, #0
BEQ e_greater_fourth
MOV R2, #0x30
B e_final
e_greater_fourth: MOV R2, #0x3C
e_final: ADD R2, R7, R2
STR R2, [R7,#0x10]
POP {R0-R7,PC}
.thumb
Start: PUSH {R0-R7,LR}
LDR R5, var_8004
LDRH R5, [R5, #0x0]
LDR R4, party_addr
MOV R6, #0x64
LDR R7, New_poke_addr
MUL R5, R6
ADD R4, R4, R5
B decrypt
.hword 0x0000
var_8004: .word 0x20370C0
party_addr: .word 0x2024284
New_poke_addr: .word 0x203F400
decrypt: LDR R0, [R4, #0x0]
PUSH {R0}
LDR R1, [R4,#0x4]
EOR R1, R0
STR R1, [R7, #0x0]
LDRH R1, [R4, #0x1C]
STRH R1, [R7, #0x6]
MOV R2, #0x20
ADD R0, R2, R4
MOV R2, #0x18
ADD R1, R2, R7
MOV R2, #0xC
LDR R5, [R7, #0x0]
decrypt_loop: LDR R3, [R0, #0x0]
EOR R3, R5
STR R3, [R1, #0x0]
ADD R1, #0x4
ADD R0, #0x4
SUB R2, #0x1
CMP R2, #0x0
BNE decrypt_loop
POP {R0}
MOV R1, #0x18
MOV R2, #0xC0
LSL R2, R2, #0x18
CMP R2, R0
BHI lower_than_c
SUB R0, R0, R2
lower_than_c: LSR R2, R2, #0x1
CMP R2, R0
BCS lower_than_6
SUB R0, R0, R2
LSL R0, R0, #0x0
lower_than_6: SWI #0x6
ADD R0, R1, #0x0
MOV R1, #6
SWI #0x6
g_in_first: CMP R0, #0x0
BNE g_in_second
MOV R2, #0x18
B g_final
g_in_second: CMP r1, #0x1
BGT g_in_third
MOV R2, #0x24
B g_final
g_in_third: MOV R2, #1
AND R2, R1
CMP R2, #1
BEQ g_in_fourth
MOV R2, #0x30
B g_final
g_in_fourth: MOV R2, #0x3C
g_final: ADD R2, R7, R2
STR R2, [R7, #0x8]
m_in_first: CMP R0, #0x3
BNE m_in_second
MOV R2, #0x18
B m_final
m_in_second: CMP R1, #0x4
BLT m_in_third
MOV R2, #0x24
B m_final
m_in_third: MOV R2, #0x1
AND R2, R1
CMP R2, #0x1
BNE m_in_fourth
MOV R2, #0x30
B m_final
m_in_fourth: MOV R2, #0x3C
m_final: ADD R2, R7, R2
STR R2, [R7,#0x14]
a_first: CMP R0, #0x1
BGT a_second
CMP R0, #0x0
BEQ a_lesser_second
MOV R2, #0x18
B a_final
a_second: MOV R3, #0x2
BEQ a_greater_second
CMP R1, #0x3
BEQ a_greater_second
CMP R1, #0x0
BEQ a_greater_third
CMP R1, #0x5
BEQ a_greater_third
MOV R2, #0x3C
B a_final
a_greater_second: MOV R2, #0x24
B a_final
a_greater_third: MOV R2, #0x30
B a_final
a_lesser_second: CMP R1, #0x1
BGT a_lesser_third
MOV R2, #0x24
B a_final
a_lesser_third: MOV R2, #0x1
AND R2, R1
CMP R2, #0x2
BNE a_lesser_fourth
MOV R2, #0x30
B a_final
a_lesser_fourth: MOV R2, #0x3C
a_final: ADD R2, R7, R2
STR R2, [R7,#0xC]
e_first: CMP R0, #0x2
BLT e_second
CMP R0, #0x2
BGT e_greater_second
MOV R2, #0x18
B e_final
e_second: CMP R1, #0x2
BEQ e_lesser_second
CMP R1, #0x3
BEQ e_lesser_second
CMP R1, #0x0
BEQ e_lesser_third
CMP R1, #0x5
BEQ e_lesser_third
MOV R2, #0x3C
B e_final
e_lesser_second: MOV R2, #0x24
B e_final
e_lesser_third: MOV R2, #0x30
B e_final
e_greater_second: CMP R1, #0x5
BLT e_greater_third
MOV R2, #0x24
B e_final
e_greater_third: MOV R2, #1
AND R2, R1
CMP R2, #0
BEQ e_greater_fourth
MOV R2, #0x30
B e_final
e_greater_fourth: MOV R2, #0x3C
e_final: ADD R2, R7, R2
STR R2, [R7,#0x10]
POP {R0-R7,PC}
But this code is pretty much useless if you don't know how to access its information. I will post further ahead some code examples on how to change and read this information.
This code has a second part to it, that must be called upon to save changes on the selected pokemon. It's pretty much the reverse, but much shorter because it doesn't need to know what was changed or the position where it must put the data. It just corrects the checksum and copies the information after encrypting it.
Spoiler:
.align 2
.thumb
Start: push {r0-r7, lr}
ldr r5, var_8004
ldrh r5, [r5, #0x0]
ldr r4, Party_addr
mov r6, #0x64
ldr r7, new_store_addr
mul r5, r6
add r4, r4, r5
b calc_checksum
.hword 0x0000
var_8004: .word 0x020370c0
Party_addr: .word 0x02024284
new_store_addr: .word 0x0203f400
calc_checksum: mov r2, #0x1c
add r0, r4, r2
mov r2, #0x18
add r1, r7, r2
mov r5, #0x0
checksum_add: ldrh r3, [r1, #0x0]
add r5, r3, r5
add r1, #0x2
sub r2, #0x1
cmp r2, #0x0
bhi checksum_add
lsl r5, r5, #0x10
lsr r5, r5, #0x10
strh r5, [r0, #0x0]
sub r1, #0x30
add r0, #0x4
mov r2, #0xc
ldr r5, [r7, #0x0]
store_loop: ldr r3, [r1, #0x0]
eor r3, r5
str r3, [r0, #0x0]
add r0, #0x4
add r1, #0x4
sub r2, #0x1
cmp r2, #0x0
bhi store_loop
pop {r0-r7, pc}
.thumb
Start: push {r0-r7, lr}
ldr r5, var_8004
ldrh r5, [r5, #0x0]
ldr r4, Party_addr
mov r6, #0x64
ldr r7, new_store_addr
mul r5, r6
add r4, r4, r5
b calc_checksum
.hword 0x0000
var_8004: .word 0x020370c0
Party_addr: .word 0x02024284
new_store_addr: .word 0x0203f400
calc_checksum: mov r2, #0x1c
add r0, r4, r2
mov r2, #0x18
add r1, r7, r2
mov r5, #0x0
checksum_add: ldrh r3, [r1, #0x0]
add r5, r3, r5
add r1, #0x2
sub r2, #0x1
cmp r2, #0x0
bhi checksum_add
lsl r5, r5, #0x10
lsr r5, r5, #0x10
strh r5, [r0, #0x0]
sub r1, #0x30
add r0, #0x4
mov r2, #0xc
ldr r5, [r7, #0x0]
store_loop: ldr r3, [r1, #0x0]
eor r3, r5
str r3, [r0, #0x0]
add r0, #0x4
add r1, #0x4
sub r2, #0x1
cmp r2, #0x0
bhi store_loop
pop {r0-r7, pc}
With these two algoritms, many new commands manipulating and checking pokemon caracteristics are possible. I hope this code will be helpful on your rom-hacking.
Edit:
For easy insertion, The two main codes are now available in a small hex file, at this post.
https://www.pokecommunity.com/posts/4914212/
Now, some asm examples on how to use this new found information.
Identifying a pokemon species
I know there is a combination of commands to identify a pokemon, but it only works on pokemon that are not eggs. This code allows you to identify a pokemon egg as well. Only one problem. You must manually set the variable 0x8004 to the egg's position as it cannot be selected by special 0x9f. I would recommend setting it to 0x0, and asking the player to put the egg in the first party slot.
Spoiler:
b503 push {r0-r1, lr}
4803 ldr r0, 0x0203f408 ;growth structure address
6800 ldr r0, [r0]
4903 ldr r1, 0x020370c2 ;var to store pokemon species (0x8005)
8800 ldrh r0, [r0]
8008 sdrh r0, [r1]
bd03 pop {r0-r1, pc}
0000 garbage
pointers
4803 ldr r0, 0x0203f408 ;growth structure address
6800 ldr r0, [r0]
4903 ldr r1, 0x020370c2 ;var to store pokemon species (0x8005)
8800 ldrh r0, [r0]
8008 sdrh r0, [r1]
bd03 pop {r0-r1, pc}
0000 garbage
pointers
Or in ROM-ready version
Spoiler:
03 b5 03 48 00 68 03 49 00 88 08 80 03 db 00 00
08 f4 03 02 c2 70 03 02
08 f4 03 02 c2 70 03 02
Increasing a pokemon happiness by any value
If you wish to make some ways for a pokemon to gain happiness, or to make a pokemon love his trainer after an event, this code will allow it.
Spoiler:
b50f push {r0-r3, lr}
4808 ldr r0, 0x0203f408 ;growth structure address
6800 ldr r0, [r0]
4908 ldr r1, 0x020370c2 ;var 0x8005 contains value to increase happiness by
880b ldrh r3, [r1]
7a42 ldrb r2, [r0+0x9] ;current happiness
189a add r2, r3, r2
2aff cmp r2, 0xff ; sees if the sum overflows the max happiness level
dc01 bgt correct
7262 strb r2, [r0 + 0x9]
bd0f pop {r0-r3, pc}
23ff correct: mov r3, 0xff
1ad3 sub r3, r2, r3 ;calculates difference between original and max happiness
22ff mov r2, 0xff
7242 strb r2, [r0+ 0x9]
800b strh r3, [r1] ;places actual given value back in variable, for checking purposes.
bd0f pop {r0-r3, pc}
xxxx garbage
pointers
ROM-ready version
Spoiler:
0f b5 08 48 00 68 08 49 0b 88 42 7a 9a 18 ff 2a
01 dc 62 72 0f bd ff 23 d3 1a ff 22 42 72 0b 80
0f bd 00 00 08 f4 03 02 c2 70 03 02
01 dc 62 72 0f bd ff 23 d3 1a ff 22 42 72 0b 80
0f bd 00 00 08 f4 03 02 c2 70 03 02
See a Pokemon's pokeball
If you wish to have a place to release pokemon but keep their ball, or for a script that
bars entrance to any trainer who doesn't keep all pokemons in a special kind of ball, we have this code
Spoiler:
b503 push {r0-r1, lr}
4803 ldr r0, 0x0203f414 ;Miscellaneous data
6800 ldr r0, [r0]
4903 ldr r1, 0x020370c2 ;var 0x8005, where the ball is stored afterards
78c0 ldrb r0, [r0 + 0x3] ; Because ball storage is peculiar, one must get rid of all
08c0 lsl r0, r0, 0x3 ;remainig information inside it
8008 strh r2, [r1]
bd03 pop {r0-r1, pc}
pointers
ROM ready version
Spoiler:
03 b5 03 48 00 68 03 49 c0 78 c0 08 08 80 03 bd
14 f4 03 02 c2 70 03 02
14 f4 03 02 c2 70 03 02
And that's it for now. I shall post more examples later on. For now, you may post requests on this thread.
For more information on the data stored, I recommend the place where I learned about it, bulbapedia (search there for Pokémon data substructures in the GBA).
To find the addresses for your version:
Spoiler:
More than likely, someone else already found the party data for you. A search on google should land you results. If not, the manual method to find them is
1. open up your rom of choice in VBA (or other emulator with cheat-search function)
2. play up to a pokecenter and have at least two pokemon. deposit all pokemon you may carry except for the one you cannot.
3. search for 32 bit value 00000000.
4. withdraw one pokemon, close the box and search with the "different than" option for 0000000
5.deposit pokemon, close box and repeat steps 3-5 until you have a set of sequential non-zero values when the second pokemon is in your party.
The first code from the sequence should be less 4 than the next, and so on, for about 20 entries. Don't worry if there is one adress missing between them (a gap of +8)
Some pokemon have a null entrance, by sheer luck.
That first code is the first value (PID) for the second pokemon in the party. Subrtract 0x64 to that address and you got the first party address.
1. open up your rom of choice in VBA (or other emulator with cheat-search function)
2. play up to a pokecenter and have at least two pokemon. deposit all pokemon you may carry except for the one you cannot.
3. search for 32 bit value 00000000.
4. withdraw one pokemon, close the box and search with the "different than" option for 0000000
5.deposit pokemon, close box and repeat steps 3-5 until you have a set of sequential non-zero values when the second pokemon is in your party.
The first code from the sequence should be less 4 than the next, and so on, for about 20 entries. Don't worry if there is one adress missing between them (a gap of +8)
Some pokemon have a null entrance, by sheer luck.
That first code is the first value (PID) for the second pokemon in the party. Subrtract 0x64 to that address and you got the first party address.
To find the variables 0x8000:
Spoiler:
These you must find by a similar method to the one above.
Make two scripts - one that sets the variable 0x8000 to any value you want and one that sets it to 0x0 (I will use 0x10d as an example)
Insert the scripts somewhere you can activate them quickly (if in a new game, two signposts in pallet town is a good choice)
1. Open up the game and activate one of the scripts (know which.) For instance, start with the 0x0 script
2. Search for that value (0x0000) in 16bit mode
3. activate the other script.
4. search for the other value (0x10d) in the same mode.
If you picked a odd enough number, you should get only one result. if not, repeat until there is only one.
The address you get is the address of 0x8000. add the last digit times two to the adress and you got the adress for that variable (to find 0x8004, add 0x8 to the address)
Make two scripts - one that sets the variable 0x8000 to any value you want and one that sets it to 0x0 (I will use 0x10d as an example)
Insert the scripts somewhere you can activate them quickly (if in a new game, two signposts in pallet town is a good choice)
1. Open up the game and activate one of the scripts (know which.) For instance, start with the 0x0 script
2. Search for that value (0x0000) in 16bit mode
3. activate the other script.
4. search for the other value (0x10d) in the same mode.
If you picked a odd enough number, you should get only one result. if not, repeat until there is only one.
The address you get is the address of 0x8000. add the last digit times two to the adress and you got the adress for that variable (to find 0x8004, add 0x8 to the address)
Last edited by a moderator: