• Our software update is now concluded. You will need to reset your password to log in. In order to do this, you will have to click "Log in" in the top right corner and then "Forgot your password?".
  • Welcome to PokéCommunity! Register now and join one of the best fan communities on the 'net to talk Pokémon and more! We are not affiliated with The Pokémon Company or Nintendo.

Development: A Way To Check Pokemon Levels

pokemonforever27

IS RIDING LUGIA...
55
Posts
15
Years
If you play long enough and make it to the TRAINER TOWER in many POKEMON games, one of the things that's unique about the tower is that the trainers' POKEMON in there all match the highest level of your POKEMON. That's the only place in the game where that occurs.

After hours of digging through commands, tutorials, FIRERED TRAINER TOWER scripts, and tools, I finally figured it out! This comes in four parts:

PART A - JPAN/pokemonforever27's LevelCheck ASM routine
When Darthatron first suggested I look into JPAN's hacked FIRERED engine, I thought "I'm stumped now, I'll look into it." So I took a look, and that solved part of the problem. A way to do this was implemented, but that still didn't solve the burning question in my head: What is THE way? So I dug more. Eventually, JPAN posted an ASM routine that would store the highest level in LASTRESULT. I then took to learning ASM, and finally got it and the CheckLevel routine learned. This is also when I realized how GAMEFREAK did it.

PART B - GAMEFREAK's Method
GAMEFREAK's method is rather long and drawn out. It consists of checking party pointers in variables and comparing them until all possible combinations have been exhausted. The process is so long, in fact, that trying to read through it all is next to impossible. It takes them over 400 lines of code to make the check! Why do it this way? Well, because they only had to code it once (in the TRAINER TOWER). Since it was only needed once, why go through the extra trouble to make the function? The battles are custom as well, in a similar fashion. There are actually 4 hidden person events that setup the custom battles. I've included images in the attachment so that you can take a look at the script and person events for yourself.

PART C - Mysterious msgbox
The one thing that puzzled me though, even after figuring out their method, is a textbox. You can find it's code in the file "MSGBOX.txt" in the attachment. Confused? I'd be surprised if you weren't. I didn't dig into every command in this one like I did the LevelCheck because that wasn't what I was researching. However, somehow, these commands, when called in the form "msgbox 0x2021D18 MSG_KEEPOPEN" repeat the last string the player read. If you talk to the girl in PALLET TOWN she'll say something about raising strong POKEMON, then execute this script, it will say the same thing. If you talk with the man about technology and the PC, the script will say the same thing. Even go so far as to say "Previously on your quest..."

PART D - End result
The best method for implementing this is to learn ASM, and write a simple routine to checklevels there. GAMEFREAK's method would take way too long to decipher fully, and the time could be better spent learning something that could be far more powerfull to know. I still have some work to do on implementation, but then I'll include a patch for FIRERED in the attachment. For now, use JPAN's engine.

SPECIAL THANKS
Nintendo/GAMEFREAK - POKEMON FIRERED VERSION
Forgotten and the VBA team - VisualBoyAdvance Emulator
HackMew - XSE; ASM Tutorials
LU-HO Poke - Advance Map
PokeCommunity - Hosting the ROM hacking scene
JPAN - Helping me learn ASM; ASM Tutorials; getting me started; FireRed Hacked Engine
interdpth - GetLevel Function; Simple ASM routine; LevelCheck routine in POKEMON EMERALD
Darthatron - Getting me started; Fixing the memory address for LASTRESULT

Goals:
1. Figure out how GAMEFREAK checks the levels of the player's POKEMON. - DONE
2. Work out the bugs of the script and implement it. - 90% DONE
3. Write in a simple command (e.x. "checkpokemonlevel") to call the function. - DONE (JPAN's hacked engine)

Any suggestions are both appreciated and encouraged.
 
Last edited:

pokemonforever27

IS RIDING LUGIA...
55
Posts
15
Years
Thank you, Darthatron.

That still leaves figuring out Nintendo's method. I'd at least like to know how they did it. There is a million different variables and comparisons in those scripts. After looking at JPAN's method, the levels are probably in there, but every time I try to dig through them, I end up getting lost! :disappoin

If it isn't in there, then it's probably hidden somewhere else that I haven't thought of to look.
 

interdpth

I've seen things, man.
275
Posts
19
Years
  • Seen Jun 8, 2021
In FireRed there is this GetLevel function at 803E7C4

I'm not sure how it decides which level to get but it returns the level in r0. :)
 
115
Posts
15
Years
  • Age 27
  • Seen Apr 27, 2023
It is an interesting question you pose—"how?" It is so strange that they somehow can slip these things into the game that are so complicated to figure out or determine its location.
 

interdpth

I've seen things, man.
275
Posts
19
Years
  • Seen Jun 8, 2021
I think it'd be
LDR r0,=PokemonVal(Not sure if it'd be an index number or a pointer to a struct)
BL 0x803E7C4
r0 would be the level!
 

pokemonforever27

IS RIDING LUGIA...
55
Posts
15
Years
I think it'd be
LDR r0,=PokemonVal(Not sure if it'd be an index number or a pointer to a struct)
BL 0x803E7C4
r0 would be the level!

Oh boy, here we go! I have no knowledge of assembler at all. You're probably right. This is probably a stupid question, but how then, would you implement this in, say, XSE, for example? The ultimate goal is to make an ips patch that would add a simple command to use it.

Thanks for all your help, guys!
 

ZodiacDaGreat

Working on a Mobile System
429
Posts
17
Years
Then you'd have to make a routine or command or special or whatever that returns the level to a variable, so it can be tempered with by scripts.
 

JPAN

pokemon rom researcher
104
Posts
15
Years
  • Seen Jul 2, 2016
Oh boy, here we go! I have no knowledge of assembler at all.
Well, I'll try to explain a simple sample code in compilable ASM and, hopefully, teach those who don't know ASM something, even if little. For all Experienced ASM hackers out there, better not look at this code, as it is over commented:
Code:
.align 2
.thumb
/*Get your pokemon highest level*/
/*this code can be implemented in any version if you change the pointers that are
indicated*/
start: push {r1-r4, lr} /*this is the start of the code. it saves the stuff the*/
                              /*command that came before this one used*/
         ldr r4, Party_pointer /*gets the location for the pokemon party from the data*/
         mov r0, #0x0   /*this one puts 0 in R0 so we can use it as our highest*/
                              /*level. Unless you have no pokemon, your pokemon will*/
                              /*have a level higher than 0, so it works fine*/
        mov r3, #0x6    /*this puts 6 in r3, as 6 is the max number of pokemon*/
                             /*you can have. this will be a counter to check how many*/
                             /*pokemon are left*/
        add r4, #0x54  /*adds to the party pointer the level location. this will*/
                            /*allow us to read from where the level is stored*/
                            /*read bulbapedia's "Pokémon data structure in Generation III"*/
                            /*for more info*/
loop: ldrb r1, [r4]     /*ldrb loads a byte from the pointer inside the R on the*/
                            /*right and puts it inside the one on the left*/
                            /*the lable on the left will let us make a loop. It can*/
                            /*have any name you want.*/
       cmp r0, r1       /*compares r0 to r1.*/
       bgt next         /*if r0 is Greater than r1, than we have a bigger level*/
                           /*if this one is the highest, we will keep it in r0*/
      add r0, r1, #0x0    /*this will store in r0 the contents of R1 plus 0, which*/
                               /*is R1. this is used to pass bytes from one register to*/
                               /*the other*/
                              /*be it the highest or not, we want to perform this. So,*/
                             /*we now want to know of all the pokemon you have. we*/
                              /*checked one so far. so, let's check the others*/
next: add r4, #0x64  /*that is the size of the party pokemon. so adding 0x64*/
                             /*we'll have the next pokemon*/
       sub r3, #0x1    /*subtracting one to the counter, as we now have one less*/
                            /*pokemon to check*/
       cmp r3, #0x0   /*now we check the counter to see if*/
       bgt loop          /*if it's not the last, then we need to check the rest.*/
                           /*makes it return to the Loop lable, and check all again*/
                           /*if it was the last pokemon, and the code is correct,*/
                           /*then it's time to leave. leaves R0 with the biggest level*/
 
                          /*to allow this to run properly with the callasm command*/
                          /*I'll also allow this to return to 0x800d (LASTRESULT)*/
 ldr r2, Var_800d   /*as with the party one, this one loads a pointer, but*/
                         /*this time is the variable 0x800d location*/
 strh r0, [r2]         /*this stores in the variable the contents of r0, that is*/
                          /*the highest level*/
 pop {r1-r4, pc}    /*this one ends the execution of the code, it's like the*/
                         /*return command on XSE. restores the old values so the*/
                         /*program running before can continue normally*/
                         /*notice r0 is not saved or restored. with specials, the*/
                         /*contents of r0 are returned to the variable you want*/
/*here follows data that the program needs to load to work porperly. The pointers can*/
/*be changed so that this program can be used in any other version, like ruby*/
Party_pointer: .word 0x02024284  /*this is the party pokemon location in FR(u)*/
Var_800d: .word 0x020370d2       /*and this is variable 0x800d, in FR(u)*/

how then, would you implement this in, say, XSE, for example?
After compiling this routine(and making it compatible with your version first), just put it at one of the empty special slots in the game (in r/s, you'ld have to make one, but all others have them) and run it there. Or, if you want, you can also run it with the callasm command.
And this is how we check the party highest level. For the second one, it takes a lot more work.
PS: sorry about the poor visibility, but this board doesn't allow tabs.
 

pokemonforever27

IS RIDING LUGIA...
55
Posts
15
Years
I've got the ASM routine compiled and put into the ROM. I checked all the memory values in VBA and the routine appears to be working. I tried implementing it with the callasm function, but can't seem to get it to work. That leaves the other option.

After compiling this routine(and making it compatible with your version first), just put it at one of the empty special slots in the game (in r/s, you'ld have to make one, but all others have them) and run it there

How do I insert something into an empty special?

PS, once I get this working in the ROM, I'll make a big post with all the information I've discovered.
 

interdpth

I've seen things, man.
275
Posts
19
Years
  • Seen Jun 8, 2021
I was looking at the battle tower script in Emerald, it looked like special EA and EB have stuff to do with the get level stuff, that's all I found out since I don't hack Emerald. D;
 

Darthatron

巨大なトロール。
1,152
Posts
18
Years
I've got the ASM routine compiled and put into the ROM. I checked all the memory values in VBA and the routine appears to be working. I tried implementing it with the callasm function, but can't seem to get it to work. That leaves the other option.

He had the wrong offset for LASTRESULT, change the line...
Code:
Var_800d: .word 0x020370d2       /*and this is variable 0x800d, in FR(u)*/
to...
Code:
Var_800d: .word 0x020370d0       /*and this is variable 0x800d, in FR(u)*/

Also, make sure you add 1 to the offset when using callasm.
 

HackMew

Mewtwo Strikes Back
1,314
Posts
17
Years
  • Seen Oct 26, 2011
I've got a couple of routines for you. They are designed to get the maximum and minum party level respectively. Both are tested and working, and can be executed through callasm.

Code:
.text
.align 2
.thumb
.thumb_func
.global GetMaxPartyLevel

main:
	push {r0-r3, lr}
	ldr r1, .PARTY_LVL
	ldrb r0, [r1]
	mov r3, #0x5

loop:
	add r1, #0x64
	ldrb r2, [r1]
	cmp r2, r0
	ble next
	add r0, r2, #0x0

next:
	sub r3, #0x1
	cmp r3, #0x0
	bne loop

return:
	ldr r1, .VAR
	strh r0, [r1]
	pop {r0-r3, pc}

.align 2
.PARTY_LVL:
	.word 0x02024284 + 0x54
.VAR:
	.word 0x020270B6 + (0x800D * 2)

Code:
.text
.align 2
.thumb
.thumb_func
.global GetMinPartyLevel

main:
	push {r0-r3, lr}
	ldr r1, .PARTY_LVL
	ldrb r0, [r1]
	mov r3, #0x5

loop:
	add r1, #0x64
	ldrb r2, [r1]
	cmp r2, #0x0
	beq return
	cmp r2, r0
	bge next
	add r0, r2, #0x0

next:
	sub r3, #0x1
	cmp r3, #0x0
	bne loop

return:
	ldr r1, .VAR
	strh r0, [r1]
	pop {r0-r3, pc}

.align 2
.PARTY_LVL:
	.word 0x02024284 + 0x54
.VAR:
	.word 0x020270B6 + (0x800D * 2)

As you can see, the routines are pretty short and simple so I did not comment them on purpose.
Either way, here's a brief description of what happens:

  1. Get the Pokémon party level address into r1.
  2. Load the first value into r0, and assume it's the maximum (or minimum) value yet.
  3. Loop 5 times, and update the maximum (or mininum) value if needed.
  4. Get the LASTRESULT var address into r1 and store the maximum (or minimum) level.
The minimum routine, however, is slighty different because a zero-level is not an acceptable value so there's an extra check to take that into account (otherwhise as long as your team is not full, the minimum level would always be zero, which is no good obviously). That also means the routine will loop up to 5 times rather than always 5 as it happens in the maximum routine.
 

pokemonforever27

IS RIDING LUGIA...
55
Posts
15
Years
Thank you, HackMew.

As soon as I get a demo released for the hack I'm currently working on, I will update the first post again with some more indepth stuff. I've learned a lot from troubleshooting my hacks.
 
Back
Top