• 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.

Help Thread: ASM & Disassembly

Status
Not open for further replies.
417
Posts
9
Years
  • Age 33
  • Seen Nov 20, 2016
Code:
start:
    push {r0-r5}
    mov r5, #0x3C 
    mov r4, #0x86        
    lsl r4, r4, #0x4    
    add r4, r4, #0x7    
    bl flag_check        
    cmp r0, #0x0        
    beq the_end        
    add r5, r5, #0x5    
    add r4, r4, #0x1
    bl flag_check        
    cmp r0, #0x0        
    beq the_end        
    add r5, r5, #0x5
    add r4, r4, #0x1
    bl flag_check
    cmp r0, #0x0
    beq the_end
    add r5, r5, #0x5
    add r4, r4, #0x1
    bl flag_check
    cmp r0, #0x0
    beq the_end
    add r5, r5, #0x5
    add r4, r4, #0x1
    bl flag_check
    cmp r0, #0x0
    beq the_end
    add r5, r5, #0x5
    add r4, r4, #0x1
    bl flag_check
    cmp r0, #0x0
    beq the_end
    add r5, r5, #0x5
    add r4, r4, #0x1
    bl flag_check
    cmp r0, #0x0
    beq the_end
    add r5, r5, #0x5
    add r4, r4, #0x1
    bl flag_check
    cmp r0, #0x0
    beq the_end
    add r5, r5, #0x5
    add r4, r4, #0x1
    b the_end    
    
    
the_end:
    ldrb r0, = 0x02024400 
    strb r5, [r0]        
    pop {r0-r5}

flag_check:
    mov r0, r4
    ldr r1, = 0x809D790 +1
    bx r1
I had to take out the LR or it wouldn't assemble but it still just freezes the game.
What do you mean "wouldn't assemble?" What specifically happened, because you shouldn't ever not be able to push {lr}, and that also is (at least one of) the reason(s) it freezes - you took out the return.

Aside from that, a few things. When you use callasm, you don't need to push and pop {r0-r3}. They're scratch registers and assumed to be garbage by the function that calls your asm function. Another thing is I'd handle your flag checks with a loop.

loop:
mov r0, r4 @i'd move this up here; having it down there is a little weird imo
bl flag_check
cmp r0, #0x0
beq the_end
add r4, #0x1
add r5, #0x5
@edit: I misread your initial routine. You need to check against the final flag. Better would be initial flag + loop counter and compare against the loop counter.
b loop

You could save a bit more by changing the setup of the loop, but meh.
 
Last edited:

C me

Creator of Pokemon League Of Legends
681
Posts
10
Years
  • Age 27
  • Seen Apr 9, 2021
lr is the last called location, and pc is the offset of the next opcode to be read. You were trying to pop lr rather than pc, which is why it wouldn't assemble. Add , lr to your push, and , pc to your pop and then it should work.

It still doesn't work. In VBA's memory viewer, nothing gets written to 02024400 so I guess it's something before that.
 
417
Posts
9
Years
  • Age 33
  • Seen Nov 20, 2016
It still doesn't work. In VBA's memory viewer, nothing gets written to 02024400 so I guess it's something before that.
ldr r0, =0x02024400

Surprised it even assembled the ldrb without an error, or at least a warning.
 

C me

Creator of Pokemon League Of Legends
681
Posts
10
Years
  • Age 27
  • Seen Apr 9, 2021
ldr r0, =0x02024400

Surprised it even assembled the ldrb without an error, or at least a warning.

Didn't make a difference to the bin file, the assembler probably corrected it itself. strb to str does change but it still doesn't work so it's not that.
 

miksy91

Dark Energy is back in action! ;)
1,480
Posts
15
Years
Didn't make a difference to the bin file, the assembler probably corrected it itself. strb to str does change but it still doesn't work so it's not that.
According to this page (http://patater.com/gbaguy/oldgba/day22.htm), ldrb and strb are special instructions for loading and writing data to SRAM, and they probably refuse to run if the given address (to load or write to) isn't in SRAM space. If your compiler did recognize this and handled the situation correctly, then there is no bug here though.
 

pokedude9

Creator (with Diego) of AME and ASE
31
Posts
7
Years
  • Age 26
  • Seen Jan 1, 2017
Having a BL call in your routine? Try it like this:

Code:
main:
    push {r0-r5, lr}
    [...] your code

the_end:
   pop {r0-r5}
   pop {pc}
Or you simply put "bx lr" (without pushing/popping lr/pc) at the end of your function, because in fact it HAS to end somewhere. The code, as written currently, will never return to the script-handler

EDIT: bx lr won't work in your routine because you are overwriting the link register through your BL call! (branch & link)
 
417
Posts
9
Years
  • Age 33
  • Seen Nov 20, 2016
Didn't make a difference to the bin file, the assembler probably corrected it itself. strb to str does change but it still doesn't work so it's not that.
Outside of the lr and pc issue (which I assume you fixed), I can't see where the problem is by scanning it. Debugger time :D Sometimes I miss obvious things when I'm just reading a routine. First, set a breakpoint on your routine to make sure that callasm is actually properly calling it. Then follow it instruction by instruction to make sure it is doing what you want it to. If you're unsure of whether or not some instructions are working probably, screenshot what's going on with the debugger (both the instructions and registers).
 

C me

Creator of Pokemon League Of Legends
681
Posts
10
Years
  • Age 27
  • Seen Apr 9, 2021
Outside of the lr and pc issue (which I assume you fixed), I can't see where the problem is by scanning it. Debugger time :D Sometimes I miss obvious things when I'm just reading a routine. First, set a breakpoint on your routine to make sure that callasm is actually properly calling it. Then follow it instruction by instruction to make sure it is doing what you want it to. If you're unsure of whether or not some instructions are working probably, screenshot what's going on with the debugger (both the instructions and registers).

Great idea to suggest debugging. I first looked at both routines and they were the same. Then I tried making multiple breakpoints and none of them broke. So I thought ok, something's wrong here. Went back to BSP to see if the pointer looked fine, it did. Then I thought about looking at the script in a hex editor so I serached for the callasm pointer.

It found nothing.

I wondered why it looked fine in BSP but apparently no such pointer exists. I went to the script and reealised the custom callasm command doesn't automatically add 08 to the end of pointers and you have to have 08 in front of it. So my pointer was XX XX XX 00.

I spent a whole day not actually testing a routine because it wasn't being pointed to in the first place.

But it works now (I've only tested 0 badges and 8 badges but there's no reason the rest shouldn't work) so thanks to everyone for their input.

___________________________________________________________________________________________________________________________

No point double posting so here's the next thing I've been working on.

I've modified the prevent hail damage routine for stuff like Magic Guard by also adding in a check to see who started the hail so that allies can't be damaged by your own teams weather.
I've come up with this:

Code:
 SameTeamcheck:    
    push {r3-r4}
    ldr r3, =0x02024064  @loads the active_side byte into r3.
    ldr r4, =0x0203e330  @loads the byte from a free memory space which was set to the active_side byte in the battlescript with copyarray.
    cmp r3, r4           @I think this works, the assembler doesn't mind. If it doesn't I guess I could sub them and compare to 0.
    beq NoDamage
    pop {r3-r4}
It could just be an error with the ram byte for active_side. I don't think it does what I think it does.
Whatever ram locations I choose, it always does damage (unless of course I change beq to bne then it never does damage).
 
Last edited:
325
Posts
10
Years
No point double posting so here's the next thing I've been working on.

I've modified the prevent hail damage routine for stuff like Magic Guard by also adding in a check to see who started the hail so that allies can't be damaged by your own teams weather.
I've come up with this:

Code:
 SameTeamcheck:    
    push {r3-r4}
    ldr r3, =0x02024064  @loads the active_side byte into r3.
    ldr r4, =0x0203e330  @loads the byte from a free memory space which was set to the active_side byte in the battlescript with copyarray.
    cmp r3, r4           @I think this works, the assembler doesn't mind. If it doesn't I guess I could sub them and compare to 0.
    beq NoDamage
    pop {r3-r4}
It could just be an error with the ram byte for active_side. I don't think it does what I think it does.
Whatever ram locations I choose, it always does damage (unless of course I change beq to bne then it never does damage).
By using ldr r3, [offset], you're only loading the offset into that register. In order to get the byte at that offset, you need to do this;
Code:
.thumb

main:
    ldr rX, [offset]
    ldrb rX, [rX]
As you can see, your current implementation will never set the correct flags to go to NoDamage.
 

C me

Creator of Pokemon League Of Legends
681
Posts
10
Years
  • Age 27
  • Seen Apr 9, 2021
I decided to scrap the active_side ram and just use banks instead. There's probably a better way to do this but it works so I'm happy.

Spoiler:

The current user byte gets copied to 0x0203E330 during the move execution to check from which side the move was used. It goes after the ability checks of course.

I used the same code for the prevent sandstorm damage routine but it doesn't work the same way for some reason.

Nevermind, I got it working, I think it was a register problem.
 
Last edited:

C me

Creator of Pokemon League Of Legends
681
Posts
10
Years
  • Age 27
  • Seen Apr 9, 2021
Just a quick question, how do you heal Pokemon with asm? I've got this modified sturdy routine which works except after not fainting I want the Pokemon to be healed.

I could do this with a pointer to a battle script and then do it that way or do it like water/volt absorb.
I don't know how it's done in the absorbs so I've tried something like this:

Code:
    ldrh r1, [r0, #0xC]  @ This was earlier in the routine for the sturdy check, loads max hp.
    ldrh r0, [r0, #0x8]   @ Loads current hp
    ldr r1, =0x082E7541 @ calls the divider to divide max hp by 2.
    add r0, r1 @ adds maxhp/2 to current hp
The game kind of just ignores it so nothing special happens.
 

pokedude9

Creator (with Diego) of AME and ASE
31
Posts
7
Years
  • Age 26
  • Seen Jan 1, 2017
Lol, you didn't actually call the routine, you added the current HP to 0x082E7541 which yields to undefined behaviour. You have to do something like this:

Code:
main:
    ldrh r0, [r0, #0xC]       
    ldrh r4, [r0, #0x8]       @ SWI 0x6 overwrites 0-3
    mov r1, #0x2              @ Denominator
    swi 0x6                   @ R0 = R0/2
    add r0, r4                @ R0 = R0 + Current HP

swi 0x6 is the supervisor function for division for the GBA.
 
417
Posts
9
Years
  • Age 33
  • Seen Nov 20, 2016
Just a quick question, how do you heal Pokemon with asm? I've got this modified sturdy routine which works except after not fainting I want the Pokemon to be healed.

I could do this with a pointer to a battle script and then do it that way or do it like water/volt absorb.
I don't know how it's done in the absorbs so I've tried something like this:

Code:
    ldrh r1, [r0, #0xC]  @ This was earlier in the routine for the sturdy check, loads max hp.
    ldrh r0, [r0, #0x8]   @ Loads current hp
    ldr r1, =0x082E7541 @ calls the divider to divide max hp by 2.
    add r0, r1 @ adds maxhp/2 to current hp
The game kind of just ignores it so nothing special happens.

Lol, you didn't actually call the routine, you added the current HP to 0x082E7541 which yields to undefined behaviour. You have to do something like this:

Code:
main:
    ldrh r0, [r0, #0xC]       
    ldrh r4, [r0, #0x8]       @ SWI 0x6 overwrites 0-3
    mov r1, #0x2              @ Denominator
    swi 0x6                   @ R0 = R0/2
    add r0, r4                @ R0 = R0 + Current HP

swi 0x6 is the supervisor function for division for the GBA.
You can't ldrh r4, [r0, #0x8] there because r0 already has the max hp, not a pointer.

Anyways don't use division to begin with here.

Just lsr the max hp by 1 and that's the same thing as div 2 except it's faster and shorter.
 

C me

Creator of Pokemon League Of Legends
681
Posts
10
Years
  • Age 27
  • Seen Apr 9, 2021
So double battles have their own check in their scripts to see if you have at least 2 unfainted Pokemon. If you set a battle to a double battle in a trainer editor but keep the script as a normal trainer script you can start a double battle as normal except when you don't have 2 or more healthy Pokemon.
In the latter case, the game loads a question mark as your partner, a fainted version of your second Pokemon or a clone of your first Pokemon.

Starting these battles can sometimes result in some negative effects for your party.

So I decided to make a little routine during the trainerbattle routine which does a quick check to see if you have 2 or more unfainted Pokemon and if you don't, it only loads a single battle. This is just a quicker way to implement double battles than repointing the scripts which would take a lot longer.

I managed to find where the battle type was set and realised it was always set as a normal trainer battle but then 1 was added (1 being the bit for double battles) if the battle was a double. This is done by the strb at 0x389D0. Noping it results in all double battles becoming single including properly scripted ones but not battle frontier ones.

So I decided to make a hook before the strb to a routine which checks to see if you have 2 or more unfainted Pokemon (well it actually checks to see if you have only one unfainted Pokemon but it produces the same result). If you only have one unfainted Pokemon it skips the strb, preventing 1 from being added to the battle type and resulting in a single battle.

I read up on hooking but after everything it doesn't work. Setting a breakpoint on my routine doesn't result in any breaks meaning the hook is wrong.
I've done it like this:
Code:
ldr rX, =(Location to hook to) 
bx rX
But I'm unsure of what registers to use since r0 to r6 seem to already be used in the routine. Likewise I'm unsure of which registers would be safe in my routine and which ones to use for the return. I'm only using 2 for the routine itself.

I've included the bytes I had to overwrite for the hook in my routine as well.
 
325
Posts
10
Years
So double battles have their own check in their scripts to see if you have at least 2 unfainted Pokemon. If you set a battle to a double battle in a trainer editor but keep the script as a normal trainer script you can start a double battle as normal except when you don't have 2 or more healthy Pokemon.
In the latter case, the game loads a question mark as your partner, a fainted version of your second Pokemon or a clone of your first Pokemon.

Starting these battles can sometimes result in some negative effects for your party.

So I decided to make a little routine during the trainerbattle routine which does a quick check to see if you have 2 or more unfainted Pokemon and if you don't, it only loads a single battle. This is just a quicker way to implement double battles than repointing the scripts which would take a lot longer.

I managed to find where the battle type was set and realised it was always set as a normal trainer battle but then 1 was added (1 being the bit for double battles) if the battle was a double. This is done by the strb at 0x389D0. Noping it results in all double battles becoming single including properly scripted ones but not battle frontier ones.

So I decided to make a hook before the strb to a routine which checks to see if you have 2 or more unfainted Pokemon (well it actually checks to see if you have only one unfainted Pokemon but it produces the same result). If you only have one unfainted Pokemon it skips the strb, preventing 1 from being added to the battle type and resulting in a single battle.

I read up on hooking but after everything it doesn't work. Setting a breakpoint on my routine doesn't result in any breaks meaning the hook is wrong.
I've done it like this:
Code:
ldr rX, =(Location to hook to) 
bx rX
But I'm unsure of what registers to use since r0 to r6 seem to already be used in the routine. Likewise I'm unsure of which registers would be safe in my routine and which ones to use for the return. I'm only using 2 for the routine itself.

I've included the bytes I had to overwrite for the hook in my routine as well.
What's the offset you're hooking in? It might be in a conditional branch, you should see if that offset is read in both normal battles and double battles before hooking there.
Also, your hook should be 4 aligned if you didn't already know that.
If you're hooking right before the strb, I'd suggest hooking at 0x389C8. If you hook at 0x389CC or 0x389D0, then your pointer will overwrite another branch. Also, by looking at that routine, hook with r3. r3 gets discarded a couple of opcodes later, while r0, r1, and r2 are used in that branch.
 
130
Posts
8
Years
  • Age 28
  • Seen Jun 14, 2023
I'm messing around with PokeRed with Gen 2 aspects, and I'm looking at how pokeballs work and I would like to fix the code to make Ultra Balls (and subsequently Safari Balls) work better than Great Balls. I've found the area I want to change but I have no knowledge of ASM but looking at the commands have kind of given me an idea of how they work.

Code:
    ld a,[wcf91]     - This I think put's the value at wcf91 to a. Which would be the type of Pokeball.
    cp a,GREAT_BALL     - This would compare the value of a to the value of a Great Ball.
    ld a,12     - This would put 12 to a. As if you were using any other type of Ball.
    jr nz,.skip1     - This (I'm guessing) would get the value of the last compare, and if not = would jump to .skip1
    ld a,8     - This would put 8 to a. As if you were using a Great Ball.
As I don't know assembly, I could possibly be totally wrong.

My first thought would be to put the code like this.
Code:
    ld a,[wcf91]
    cp a,GREAT_BALL
    ld a,12
    jr nz,.skip1
    cp a,ULTRA_BALL
    ld a,8
    jr nz,.skip1
    ld a,4     -Putting a value of 4 to a. Being better than a Great Ball
But then I realized that the value of a would no longer be [wcf91] as ld a,12 changed the value. So then I thought.
Code:
    ld a,[wcf91]
    cp a,GREAT_BALL
    ld a,12
    jr nz,.skip1
    ld a,[wcf91]     -Reloading the value of [wcf91] back to a to compare it again to the type of ball.
    cp a,ULTRA_BALL
    ld a,8
    jr nz,.skip1
    ld a,4
Which at first seemed wrong to me. But looking over it might seem like it word work. But I was thinking that this would be better. [EDIT] It would be wrong because it would never check correctly for an Ultra Ball.
Code:
    ld a,[wcf91]
    cp a,GREAT_BALL
    ld a,8
    jr (Value that would mean =),.skip1
    ld a,[wcf91]
    cp a,ULTRA_BALL
    ld a,4
    jr (Value that would mean =),.skip1
    ld a,12
But I don't know what function could be put after jr to mean =. Unless, jr z means =. I saw a bit of code later on that dealt with another ball factor.
Code:
    ld a,[wcf91]
    ld b,255
    cp a,POKE_BALL
    jr z,.skip4     -as if the last cp was =, then it would jump to .skip4
    ld b,200
    cp a,GREAT_BALL
    jr z,.skip4
    ld b,150
    cp a,ULTRA_BALL
    jr z,.skip4
So yeah. Any help would be nice.
 
Last edited:

C me

Creator of Pokemon League Of Legends
681
Posts
10
Years
  • Age 27
  • Seen Apr 9, 2021
What's the offset you're hooking in? It might be in a conditional branch, you should see if that offset is read in both normal battles and double battles before hooking there.
Also, your hook should be 4 aligned if you didn't already know that.
If you're hooking right before the strb, I'd suggest hooking at 0x389C8. If you hook at 0x389CC or 0x389D0, then your pointer will overwrite another branch. Also, by looking at that routine, hook with r3. r3 gets discarded a couple of opcodes later, while r0, r1, and r2 are used in that branch.

Yeah I was actually using 0x389C8 with r3 too. The issue wasn't even the hook, it was the return. I viewed the registers in vba's dissassembler with the automatic update on and reaslised my routine was actually taking place so the hook was fine.

The problem was that I wrote 08389D0/08389D2 instead of 080389D0/080389D2 and then it still didn't work so I looked at other routines and realised they didn't return to an even offset so I +1d the 2 returns and it works fine.

I've only tested some cases but there's no reason it shouldn't work perfectly regardless of the number of Pokemon and how many fainted Pokemon you have.

Thanks for confirming the hook was correct.
 

miksy91

Dark Energy is back in action! ;)
1,480
Posts
15
Years
Try looking for a file that explains exactly what each asm instruction does. I have one in my pc so if you for some reason can't find one, I could also share that one.

Anyway, I didn't clearly read all you had written here but what "cp a, x" does is to compare the value in register a with any 8-bit value, or value stored in another register (b, c, d, e, h, l, (hl)). The result of this operation is stored in register f. f register is a 8-bit register just like every other register out there. That one is special so that it is used to store information about all kinds of comparisons, number over-/underflows and such. So what happens is that when for example "cp a, ULTRA BALL" is run (where ULTRA BALL is just notation for the byte code of Ultra Ball which happens to be 2) is that if a has value 2 in it, zero flag/bit (z) is set (inside register f). "jr z addr" is an instruction that is practically used to "jump" to address 'addr' (except for the fact that this instruction is so called relative jump code which can only be made to jump to addresses close the the instruction itself; practically in "address - 0x80 < address < address + 0x80" area where 'address' is the rom address where that instruction is located. That works here nicely because you're not jumping far ahead. If you had to do that though, you could use "jp z addr" which is the "real" jump code and 'addr' here is the pointer to the address jumped to.

Apart from zero flag (z) (which is practically (although not always) being set everytime the result of some operation is 0), another handly flag is carry flag (c) which is being set if the result of operation over- or underflows. *By the way the reason why cp a, x sets the zero flag if "a == x" is because the instruction is basically "substract x from a" instruction. And the result of that is of course 0 only if a == x. The result of that operation is thrown away though, and not stored in register a (unlike what would happen if you used "sub a, x" instruction instead).
 
130
Posts
8
Years
  • Age 28
  • Seen Jun 14, 2023
Ok... You kind of made me both less and more confused at the same time. I assumed jr z was something like "jump if equal to" but seeing as it means "jump if zero" makes more sense.

So, as I want it to be 0 if the values are the same, would my code example
ld a,[wcf91] cp a,GREAT_BALL ld a,8 jr z,.skip1 ld a,[wcf91] cp a,ULTRA_BALL ld a,4 jr z,.skip1 ld a,12be what I want to use to make it work properly?
P.S. The .skip1 is right after "ld a,12", so that thing about being near in bytes I don't need to worry about.
 
Status
Not open for further replies.
Back
Top