User Tools

Site Tools


zero_mission:intermediate_guides:yohannasm

by yohann

imported to wiki by felixwright

Quick Links:

Metroid Advance Game Editor (MAGE)
HxD Hex Editing Guide
Visual Studio Code
ARMIPS Extension for VS Code
No$GBA Debugger
ARMIPS Releases (GitHub)
GBA Metroid Data Maps


THUMB Assembly Tutorial



Introduction

This document aims to teach how THUMB works, how to set up an environment to test your code and ultimately how to read/write THUMB code.

It is assumed you have some required knowledge about programming (variable types, hexadecimal, pointers and code fundamentals), if you are not familiar with these concepts I highly suggest you learn them before starting as they are extremely important to properly understand assembly.


Setup

First things first, we’ll need to setup an environment to test and run our code, for that we will need six things :

  • ARMIPS, this will handle assembling our ASM code to binary code and inserting it into the ROM.
  • A Hex Editor, this will be good for finding freespace and it’s a useful thing to have around.
  • A Debugger, aka an emulator, I highly recommend using no$gba because it’s a very good debugger.
  • A vanilla USA ROM of Metroid Zero Mission.

I recommend you make a dedicated folder to all those thing and then in it have no$gba, MAGE, and a folder containing armips and the ROM.


First Look At Code

Here’s the simplest patch I ever wrote in ASM, this probably looks a little scary but don’t worry, everything will soon make sense.

Even if you never saw ASM, you can slightly guess what some of those instructions do, like add and sub for example. However you may not be familiar with those r0 and r1 and the other instructions.

    push r14
    ldr r0,=#0x300006e
    ldrh r1,[r0,#0x0]
    sub r1,#0x3
    strh r1,[r0,#0x0]
    ldr r0,=#0x30000E4
    ldrh r1,[r0,#0x0]
    add r1,#0x3
    strh r1,[r0,#0x0]
    pop r0
    bx r0
    .pool




The Basics

Registers

A register is essentially a little memory block in the CPU used to store data, in our case the GBA CPU has 37 registers of 32 bit in size each. I won’t explain in detail what all of them do here, you can check the technical documentation of no$gba or gbatek is you want, I’ll only explain what we need to know to write ASM.

We only have 16 registers that interests us, from r0 to r15, I like to divide them in 4 categories :

Name Description
Volatile/parameter registers [r0-r3] Those 4 registers are used to store parameters for functions and they also are considered overwritten when returning from a function, in other words they are sort of like local variables.
Work registers [r4-r7] Those 4 registers don’t have any particular function, so they can be used in any way you want, however they are saved even when returning from a function, meaning that you can use them without fear of losing them unlike [r0-r3].
Storage registers [r8-r12] Those are similar to the work registers (saved and don’t have any particular function), however due to how instructions are assembled to machine code, most instructions are incompatible with them since the number of the register concerned by the instructions is written on 3 bit meaning it can’t go higher than 7.
Special registers [r13-r15] Those registers hold special values and also have an alternative name, r13 or sp is the stack pointer, r14 or lr is the link register and r15 or pc is the program counter. I’ll explain what those are later.

If you couldn’t guess already, ASM doesn’t have any concept of variables and data type, we just put values on registers or the stack and do stuff with them.

Now let’s talk about those special registers :

Name Description
r13/sp This is the stack pointer, it contains a pointer to the top of the stack, I will explain what the stack is in the next chapter but for now, all you need to know is that the stack is a special block of memory that works in a LIFO (Last In First Out) way where we can essentially push and pop data from it.
r14/lr This is the link register, when calling a function, r15 will be saved to lr, which will allow the called function to know where it was called so that it can go back to that value when the function ended, basically acting as a return.
r15/pc This is the program counter, it holds the ROM address of the current instruction executed.

If all of that sounded complicated to you, just try to see registers as global variables. When writing code I’ll often re-explain what this and that do so if you understand better when you see a concrete example don’t worry.

Stack

I’ve been talking about the stack multiple times now, but what is it ?

The stack is a chunk of RAM that, as explained earlier, works in a LIFO manner (Last In First Out). LIFO specifically refers to the push and pop instructions, when pushing an element to the stack, you will put it on top of every other data that was previously pushed, and when poping a value from the stack, you will retrieve the last value that was pushed, aka Last In First Out.

The stack has 3 main purposes for us :

  • Preserving work and storage registers : When using those registers in a function, it’s important to push them at the beginning and to pop them at the end, this will essentially preserve the value that was in them and then restore it to prevent conflicts, this is the “saving” I was referring to earlier.
  • Preserving the return value : This is similar to the previous one but kind of a particular case, as mentioned earlier, r14 will contain the value of r15 when doing a function call, this means that it will contain the return value, it’s very important to keep it safe if we start to do nested call, since otherwise it’ll be overwritten and the game will basically jump nowhere and everywhere and crash.
  • Function parameters : The parameter registers [r0-r3] are used to store parameters for functions, however some functions have more than 4 parameters, in that case the rest of them will be stored in the stack, this is notified as sp[0], sp[4]…

The stack may seem complicated for now, but most of the time we will just push and pop what we need and that’s it so it’s pretty simple.

Banks

Banks refer to how data is structured, this can be split like this :

Name Description
0000000-0003FFF BIOS, System ROM
2000000-203FFFF WRAM or Work RAM (Fast RAM)
3000000-3007FFF IRAM or Internal RAM (Normal RAM)
4000000-40003FE IO, Input Output (hardware registers)
5000000-50003FF PAL, Palette Memory
6000000-6017FFF VRAM, or Video RAM
7000000-70003FF OAM or Object Attribute Memory (data for sprites)
8000000-8FFFFFF ROM, data and code

We will mainly work with IRAM and ROM, but it’s good to know how it works overall.

Flags and Conditions

In order to do conditions, we will use the flags of the CPU to determine where to jump and cmp instructions to compare value.

Here are all the flags :

Flag Name Description
Z The Zero flag Z = 1 if equal and 0 if not equal
C The Carry flag C = 1 if unsigned higher or same and 0 if unsigned lower
N The Negative flag N = 1 if signed negative and 0 if signed positive or zero
V The oVerflow flag V = 1 if signed overflow and 0 if signed no overflow

We can also combine the flags together to do some other checks, for example C = 1 and Z = 1 means unsigned lower or same.

Strictly speaking, we will never really work directly with those flags, but we will use jump instructions that will execute only if certain flags are set, for example beq (Branch EQual) or bne (Branch Not Equal).


The First Program

Now that you know all the basics, you are ready to create your first program in ASM, create a new file with the ASM extension (I will name it Test.asm) in the folder with the ROM and armips and open the file in your IDE.

ARMIPS Directives

We will start by giving ARMIPS some information about our code :

.gba
.open "ZM_U.gba","TestASM.gba",0x8000000
 
.close

First we tell it we are working with a GBA ROM, then we precise the input ROM (ZM_U.gba), the output ROM (TestASM.gba) and where we are starting (0x8000000 to start in ROM). We also close everything at the end.

Now we should decide what we’re going to do, to stay simple we’re going to re-create the code I used as an example earlier, it hijacks the AI of an unused enemy and makes it so the room effect goes up automatically.

Labels

To make things easier, we will define labels that will hold constants, this is especially useful for IRAM and ROM addresses, here we’ll need 2 IRAM addresses, 2 ROM addresses and one constant, the Effect Y Position Offset (0x300006e), the BG0Position (0x30000e4), the address to our freespace (0x8760d50), the Sprite AI Pointers (0x875E8C0) and our constant which is going to be the Sprite ID of the sprite we will hijack, in our case it’s 0x6c :

.definelabel SpriteID,0x6c
 
.definelabel SpriteAIPointers,0x875E8C0
.definelabel FreeSpace,0x8760d50
 
.definelabel EffectYPositionOffset,0x300006e
.definelabel BG0YPosition,0x30000E4

Sprite AI Pointers is an array of pointers to all the main AI of the sprites, this is where we’ll put a pointer to our freespace.

Freespace is unused data in the ROM where we can safely put our code, open the ROM in HxD and go to to our freespace address (without the 8 at the beginning because HxD doesn’t work with banks), you will see a lot of FF, this is what freespace usually looks like, repeated data.

Hijack

We will start by doing our hijack, for that we will do simple maths :

.org SpriteAIPointers + SpriteID * 4
    .dw FreeSpace+1

.org tells ARMIPS to go insert code at the location provided after, in our case it’s going to be SpriteAIPointers + SpriteID * 4 ; we start at SpriteAIPointers and then we add our SpriteID multiplied by 4 to offset the position to the pointer to the AI of our sprite. The multiplication by 4 is needed because a pointer is 32 bits long or 4 bytes long, so the first pointer is at offset 0, then the second at offset 4, then 8…

The .dw tells ARMIPS to generate a pointer to the address provided, in our case the freespace + 1. You may be wondering why we added 1, adding one to a function start offset tells the CPU to switch to THUMB mode, since our code will be written in THUMB we need to switch modes otherwise it’ll go back to ARM mode and the game will crash.

Actual Code

Now we have everything ready to start writing code, start by adding a .org to our freespace and by pushing r14 to the stack

.org FreeSpace
    push r14

In our case pushing r14 isn’t necessary since we don’t call any other function before returning, but I’m doing it because it needs to be a reflex for you when writing code.

Next, we’re going to decrement the Effect Y Position Offset (the origin is at the top-left of the screen, since we want it to go up we are decrementing it). To start, we load the IRAM address to r0 by using ldr (LoaD Register), the syntax is like this :

ldr r0,=EffectYPositionOffset

Now r0 contains the value we put in our label, so 0x300006e, we then need to actually load the value that the address points to, for that we use ldrh (LoaD Register with Halfword). Halfword means the data we are loading is 16 bit sized or 2 bytes, if we wanted to load an 8 bit/1 byte value we would use ldrb and if we wanted to load a 32 bit/4 bytes value we would just use ldr. Syntax goes like this :

ldrh r1,[r0,#0x0]

Here we are loading into r1, the halfword contained in r0 at offset 0. In C this would translate to :

r0 = 0x300006e;
ushort r1 = *(ushort*)r0[0];

In other words, we are just loading the 2 bytes value that r0 points to in r1, so now r1 contains the actual IRAM value of the Effect Y Position Offset.

Let’s decrement our value now :

sub r1,#0x3

This is fairly simple, sub just subtracts the given register with the given value so this is the same as doing r1 -= 3.

All we need to do is store our new value to IRAM now, so instead of doing a load, we are going to do a strh (Store Register Halfword) :

strh r1,[r0,#0x0]

We are storing the halfword value of r1 to r0 at offset 0. As you can see it’s very similar to the ldrh we did earlier.

And that’s it, we did EffectYPositionOffset -= 3; in only 4 lines of code !

Now let’s do the same thing but with the BG0 Y Position, the code is exactly the same except for 2 small differences, instead of subtracting we are going to add and instead of loading Effect Y Position Offset, we are going to load BG0 Y Position, try to do it yourself.

If your code looks like this :

ldr r0,=BG0YPosition
ldrh r1,[r0,#0x0]
add r1,#0x3
strh r1,[r0,#0x0]

You’ve successfully done it, if not try to understand what you did wrong and why.

The only thing we need to do now is to return, for that we’re going to pop the value we previously pushed in the stack and jump to that :

pop r0
bx r0

Here I’m popping the last value of the stack (remember we previously pushed r14 in it) and putting it in r0, then I’m branching to it with the bx instruction.

Last very important thing to do is to add .pool at the very end of our code (right after the bx r0).

We need a .pool because of the way instructions are assembled, in THUMB each instruction (except bl) is 16 bits long, but IRAM addresses are 32 bits long, so how can our ldr works ? Well instead of directly loading the address, we will create a pointer to that said address right after the code, and instead load the value of that pointer in our ldr, meaning that now the address loaded can fit in the 16 bites instruction. All this process is handled by the .pool directive, it’ll automatically create a pointer and replace our ldr.

Assembling & Testing

Now that our code is ready, we need to assemble it using ARMIPS, open the command line tool in the folder where everything is and simply type :

armips.exe Test.asm

Press enter and if everything works, no message should appear but you should have a new ROM named TestASM.gba in your folder. If it didn’t work, try and debug it yourself, you very likely did an error in the code or didn’t renamed your vanilla ROM to ZM_U.gba. In case you need it, here’s the entire source code :

test.asm
.gba
.open "ZM_U.gba","TestASM.gba",0x8000000
 
.definelabel SpriteID,0x6c
 
.definelabel SpriteAIPointers,0x875E8C0
.definelabel FreeSpace,0x8760d50
 
.definelabel EffectYPositionOffset,0x300006e
.definelabel BG0YPosition,0x30000E4
 
.org SpriteAIPointers + SpriteID * 4
    .dw FreeSpace+1
 
.org FreeSpace
    push r14
    ldr r0,=EffectYPositionOffset
    ldrh r1,[r0,#0x0]
    sub r1,#0x3
    strh r1,[r0,#0x0]
    ldr r0,=BG0YPosition
    ldrh r1,[r0,#0x0]
    add r1,#0x3
    strh r1,[r0,#0x0]
    pop r0
    bx r0
    .pool
    
.close

If it still doesn’t work, come ask for help in the MAGConst discord server, here’s an invite link if needed.

Now let’s test our code, open the new ROM (TestASM.gba) in MAGE and navigate to the fifth room in Kraid. Open the Spriteset Editor (Editor→Spriteset Editor), remove all the sprites using the “Remove slot” button, add a sprite using the “Add slot” button and choose the sprite number 6c in the combo box, it should have graphics that spell “Gekitai machine”. Click on apply to save your changes and close the Spriteset Editor. Enter Object editing mode by clicking on the red ball in the tool bar on the left, and remove all the sprites of the room by right clicking on the green squares and selecting “Remove sprite”, now add a new sprite (position doesn’t matter). Right click and select “Test Room Here”, if everything worked correctly, you should see the lava going up automatically.

Congratulations, you just created your first ASM patch !


The Second Program (Ice Bomb)

Now that you know the basics on how to write an ASM patch, let’s try something more difficult.

First of all, I highly recommend you have the data maps of Zero Mission open (here) as well as my function map (here), they will be very useful.

This time we’re going to make something much more complicated, an ice bomb.

For that we will need to :

  • Hijack the bomb exploding on sprite subroutine
  • Check the weaknesses of the sprite
  • If it can be freezed, we freeze it
  • Prevent conflicts with vanilla code

Setup

Let’s begin by creating the file, I’ll name it “TestIceBomb.asm” and setting up ARMIPS directories.

Then we need some labels, first the freespace, I’ll be using the same as last time so 0x8760d50. This time we won’t be needing any IRAM addresses, only ROM addresses for some functions.

Go to the code data map and search for “Get Sprite Weakness”, “Set Particle Effect”, “Projectile Hit Solid Sprite”, “Projectile Deal Damage” and “BX r0”. Go to the function map and search for “FreezeSprite”. Setup labels for all these functions, don’t forget to offset it to ROM.

In the end, you should have something like this :

.gba
.open "ZM_U.gba","TestIceBomb.gba",0x8000000
 
.definelabel FreeSpace,0x8760d50
 
.definelabel GetSpriteWeakness,0x8050370
.definelabel FreezeSprite,0x80506fc
.definelabel SetParticleEffect,0x80540EC
.definelabel ProjectileHitSolidSprite,0x80504cc
.definelabel ProjectileDealDamage,0x8050424
.definelabel BXR0,0x808abf8
 
.close

Breakpoints & Small Edit

We’ll start by doing a super small edit, changing the particle effect of a bomb exploding.

To do that, we need to know where this is done, we know that it’s a particle effect so we need to know when Set Particle Effect is called. This means we’ll have to set a breakpoint on the function, grab the start offset of the function (same as the definelabel) and start a test room with MAGE. Click on the code window of no$gba and press ctrl+B, paste the offset here and hit enter, now try to lay a bomb, the breakpoint should hit and no$gba will set the code window to be on focus. Now check the value in r14, press ctrl+G and enter that value, this will bring you to where the function was called, scroll up a little and you should see something that looks like “movs r2,13h”, that’s the instruction we are interested in since the value in r2 is the effect number, copy the ROM offset of the instruction (0x80521d8) and you can close no$gba and go back to your IDE.

Let’s put a .org with the address we got previously and change that mov instruction, the way mov works is pretty simple, it just sets the register with the value, in other words mov r0,#0x2 ⇔ r0 = 2.

We want to change that value to have a more “icy” effect, so let’s replace it with 0x28

.org 0x80521d8
    mov r2,#0x28

0x28 is the particle effect for freezing a sprite, if you want to test the patch to see what the effect looks like you can do it right now.

Hijack

Now let’s do the real code, we’re starting with the hijack, it’s going to be different than last time since we want to inject code in the middle of a function. The best optimal spot for us is at 0x8050b40, it’s right in the middle of the Bomb Hit Sprite function, after the checks to see if the sprite is immune or solid to projectiles, but before the weakness checks which is what we want to change.

Regarding the hijack, we’ll do it by loading the value of our freespace to r0 and then call BXR0, BXR0 is a function that takes a pointer to a function as a parameter and calls that function, so let’s use it :

.org 0x8050b40
    ldr r0,=FreeSpace+1
    bl BXR0
    .pool

Let’s not forget to add 1 to switch to THUMB mode, bl means Branch Link, it saves r15 to r14 and jumps to the given function, it’s basically a function call.

Freezing the Sprite

Let’s dive into the main code now, first we’ll push r14 and then we’ll copy the vanilla code where we made our hijack, so :

.org FreeSpace
    push r14
    add r0,r4,#0x0

The add instruction is this context is the equivalent to r0 = r4, it moves the value in r4 to r0, r4 previously contained a Sprite Data Pointer that needs to be moved to r0 so it can be used as a parameter for the function we’re going to call next, Get Sprite Weakness.

You would think we can just do bl GetSpriteWeakness, but if you try to compile that it won’t work. This is similar to the problem with .pool, GetSpriteWeakness is too far from where we are now because bl doesn’t jump to a specific location, it jumps by a number of bytes. And we can’t use BXR0 like earlier either since it’s too far as well.

So to actually call the function, we’re just going to move the offset in r15, it’s as simple as that. However, for the function to be able to return we need to use a bl to do that, so :

ldr r1,=GetSpriteWeakness
bl @@CallR1
@@CallR1:
mov r15,r1

So here, we load our function in r1 because r0 holds our parameter, we bl to a label, and that label moves r1 to r15, when GetSpriteWeakness will return, it’ll jump back to right after the bl where the rest of our code will be.

Get Sprite Weakness returns the weakness of the sprite we passed in r0, so it has a return value, those values will always be stored in r0.

Now we need to check for the weaknesses of the sprite, weaknesses are flags, you can see them in the ROM data map, in the sprite stats.

First we’ll check if it can be frozen (flag 0x40) and then we’ll check if it’s vulnerable to beam and bombs (flag 0x2).

mov r1,#0x40
and r1,r0

First we move 0x40 to r1, and then we do a bitwise and (&) on r1 and r0, now if the sprite can be frozen, r1 will hold 1, and if it can’t, r1 will hold 0.

From here we can just check the value and adapt to the result, in other words we need an if statement. To do that we need to compare the value in r1 with 0 so we’ll use the cmp instruction.

cmp r1,#0x0
beq @@WeaknessCheck

The cmp instruction will set the Z flag, and then we can use the beq instruction (Branch EQual) to jump only if r1 was equal to 0. We’ll keep the WeaknessCheck label for later and assume that r1 will always be 1 for now.

We need to call Freeze Sprite now, it takes 2 parameters, a Sprite Data Pointer in r0 and a Freeze Timer in r1, for r0 we’ll do like earlier by getting it from r4 since it’s still here, and for r1 we’ll use 0xF0 (same value as vanilla game). Then after that we’ll call the function in the same way we did but with r2 instead of r1. This all produces the following code :

add r0,r4,#0x0
mov r1,#0xF0
ldr r2,=FreezeSprite
bl @@CallR2
...
@@CallR2:
mov r15,r2

Put the CallR2 label right after CallR1 one to organize the code properly.

For now the entire code looks like this :

.org 0x8050b40
    ldr r0,=FreeSpace+1
    bl BXR0
    .pool
 
.org FreeSpace
    push r14
    add r0,r4,#0x0
    ldr r1,=GetSpriteWeakness
    bl @@CallR1
    mov r1,#0x40
    and r1,r0
    cmp r1,#0x0
    beq @@WeaknessCheck
    add r0,r4,#0x0
    mov r1,#0xF0 ; Freeze timer (EDITABLE)
    ldr r2,=FreezeSprite
    bl @@CallR2
@@WeaknessCheck:
 
 
@@CallR2:
    mov r15,r2
@@CallR1:
    mov r15,r1
    .pool

I also added a .pool because of the ldr of the functions.

Last step for freezing the sprite, adding a particle effect to make it look good.

We’re going to do this right after the CallR2 but before the WeaknessCheck label.

Set Particle Effect takes 3 parameters, r0 has Y Position, r1 has X Position and r2 has the effect number.

For the position, let’s use the position of the sprite, our Sprite Data Pointer is still in r0 so let’s load the X and Y position. Go to the RAM data map and search for “Current Sprite”, as you can see Y and X positions are halfwords, respectively stored at offset 2 and 4, so we’re gonna need some ldrh.

ldrh r0,[r4,#0x2]
ldrh r1,[r4,#0x4]
mov r2,#0x28

We load into r0 the Y position by offsetting with 2, and we load into r1 the X position by offsetting with 4. We also move into r2 0x28, it’s the same effect we used earlier.

Now let’s just call Set Particle Effect with r3

ldr r3,=SetParticleEffect
bl @@CallR3
...
@@CallR3:
    mov r15,r3

Now our sprite is getting freezed if a bomb is hitting it and the can be frozen weakness flag is set.

Damaging the Sprite

Now we need to check if the sprite is vulnerable to bombs, so we’re going to do the same as before.

But we lost our Sprite Weakness because it was in r0, and calling Get Sprite Weakness again would be inefficient, so instead let’s go back a little bit and save our sprite weakness to r5 :

add r0,r4,#0x0
ldr r1,=GetSpriteWeakness
bl @@CallR1
add r5,r0,#0x0

Now let’s go back to where we were and put back r5 into r0

@@WeaknessCheck:
add r0,r5,#0x0

Let’s do a bitwise and (&) with 2 and compare :

mov r1,#0x2
and r1,r0
cmp r1,#0x0
beq @@HitSolid

We’ll need to get our Sprite Data Pointer back for the next function calls, so let’s do that right before the cmp with :

add r0,r4,#0x0

Let’s assume our sprite is weak for now, we’ll need to call Projectile Deal Damage and set the parameters. r0 already contains our Sprite Data Pointer, and we need to set a damage value in r1, vanilla damage for a bomb is 0x8 so let’s use that, and then call our function with r2 :

mov r1,#0x8
ldr r2,=ProjectileDealDamage
bl @@CallR2

Now let’s handle if it’s not vulnerable, since it only has one parameter (Sprite Data Pointer in r0 and it’s already set) we just need to call it

ldr r1,=ProjectileHitSolidSprite
bl @@CallR1

This should look like this now :

add r0,r5,#0x0
mov r1,#0x2
and r1,r0
add r0,r4,#0x0
cmp r1,#0x0
beq @@HitSolid
mov r1,#0x8 ; Damage (EDITABLE)
ldr r2,=ProjectileDealDamage
bl @@CallR2
@@HitSolid:
ldr r1,=ProjectileHitSolidSprite
bl @@CallR1

The Return

Our patch is almost complete, all that’s left to do is to handle the return.

The return is just stack stuff, we pop what we pushed at the start and then bx.

Regarding the push at the start, we also need to push r4 and r5 since we used them in our code and they are work registers, so replace our initial push with :

push r4, r5, r14

And then at the end pop everything and return :

@@Return:
pop r4, r5
pop r0
bx r0

We also need to jump to the return label after call Projectile Deal Damage so that Projectile Hit Solid Sprite isn’t called, to do that just use a single branch instruction :

ldr r2,=ProjectileDealDamage
bl @@CallR2
b @@Return

Conflicts

Sorry I lied earlier, this wasn’t the last step, but this one really is.

When we’re going to return from our code, we’ll end up in the middle of the vanilla function, which will cause conflicts.

To prevent that, we’ll change when the vanilla function returns, cutting a part of its code :

.org 0x8050b4c
    pop r4
    pop r0
    bx r0

This is the same as the return code of the vanilla function, it’s slightly after our return because we need to preserve some instructions otherwise weird stuff will happen.

Assembling & Testing

Second patch done ! Assemble it and test it, you don’t need to do anything in MAGE this time other than launching a test room, if something went wrong at any point, try to debug it yourself before checking the source code (set a breakpoint at the beginning of the freespace and step through the instructions with F7).

testIceBomb.asm
.gba
.open "ZM_U.gba","TestIceBomb.gba",0x8000000
 
.definelabel FreeSpace,0x8760d50
 
.definelabel GetSpriteWeakness,0x8050370
.definelabel FreezeSprite,0x80506fc
.definelabel SetParticleEffect,0x80540EC
.definelabel ProjectileHitSolidSprite,0x80504cc
.definelabel ProjectileDealDamage,0x8050424
.definelabel BXR0,0x808abf8
 
.org 0x80521d8
    mov r2,#0x28 ; Change bomb exploding particle effect
 
.org 0x8050b4c ; Make the return of the vanilla function earlier 
    pop r4
    pop r0
    bx r0
    
.org 0x8050b40
    ldr r0,=FreeSpace+1 ; Hijack
    bl BXR0 ; BXR0 calls the function in r0
    .pool
 
.org FreeSpace
    push r4, r5, r14 ; Push r4 and r5 because we use them and r14 for the return
    add r0,r4,#0x0 ; Put the sprite data pointer from vanilla code to r0
    ldr r1,=GetSpriteWeakness
    bl @@CallR1 ; Call Get Sprite Weakness
    add r5,r0,#0x0 ; Save the weakness in r5 for later use
    mov r1,#0x40 ; Can be frozen flag
    and r1,r0 ; & operation with the weakness 
    cmp r1,#0x0 ; Check if can be frozen
    beq @@WeaknessCheck ; Branch if equal -> Can't be frozen
    add r0,r4,#0x0 ; Get sprite data pointer back (again)
    mov r1,#0xF0 ; Freeze timer
    ldr r2,=FreezeSprite
    bl @@CallR2 ; Call Freeze Sprite
    ldrh r0,[r4,#0x2] ; Get Y Position of the sprite
    ldrh r1,[r4,#0x4] ; Get X Position of the sprite
    mov r2,#0x28 ; Freezing sprite particle effect
    ldr r3,=SetParticleEffect
    bl @@CallR3 ; Call Set Particle Effect
@@WeaknessCheck:
    add r0,r5,#0x0 ; Get the weakness back
    mov r1,#0x2 ; Vulnerable to beam/bombs flag
    and r1,r0 ; & operation with the weakness 
    add r0,r4,#0x0 ; Get sprite data pointer back (again(again))
    cmp r1,#0x0 ; Check if vulnerable
    beq @@HitSolid
    mov r1,#0x8 ; Damage
    ldr r2,=ProjectileDealDamage
    bl @@CallR2 ; Call Projectile Deal Damage
    b @@Return ; Branch to return
@@HitSolid:
    ldr r1,=ProjectileHitSolidSprite
    bl @@CallR1 ; Call Projectile Hit Solid Sprite
@@Return:
    pop r4, r5 ; Pop r4 and r5 because we pushed them earlier
    pop r0 ; Pop the r14 value into r0
    bx r0 ; Return
 
@@CallR3:
    mov r15,r3
@@CallR2:
    mov r15,r2
@@CallR1:
    mov r15,r1
    .pool
 
.close

zero_mission/intermediate_guides/yohannasm.txt · Last modified: 2024/03/23 19:39 by felixwright