User Tools

Site Tools


zero_mission:intermediate_guides:yohannasm

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
zero_mission:intermediate_guides:yohannasm [2024/03/23 18:31] felixwrightzero_mission:intermediate_guides:yohannasm [2024/03/23 19:39] (current) felixwright
Line 2: Line 2:
  
 imported to wiki by felixwright imported to wiki by felixwright
 +
 +Quick Links:
 +
 +
 +[[ https://metroidconstruction.com/resource.php?id=90                 | Metroid Advance Game Editor (MAGE) ]]\\
 +[[ general:guides:hxd_guide                                           | HxD Hex Editing Guide              ]]\\
 +[[ https://code.visualstudio.com/                                     | Visual Studio Code                 ]]\\ 
 +[[ https://marketplace.visualstudio.com/items?itemName=ajarmar.armips | ARMIPS Extension for VS Code       ]]\\
 +[[ https://problemkaputt.de/no$gba.zip                                | No$GBA Debugger                    ]]\\
 +[[ https://github.com/Kingcom/armips/releases                         | ARMIPS Releases (GitHub)           ]]\\
 +[[ https://labk.org/maps/                                             | GBA Metroid Data Maps              ]]\\ 
  
 <WRAP center 75% leftalign> <WRAP center 75% leftalign>
Line 16: Line 27:
  
 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. 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. 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.
 \\  \\ 
Line 26: Line 39:
  
 First things first, we’ll need to setup an environment to test and run our code, for that we will need six things : First things first, we’ll need to setup an environment to test and run our code, for that we will need six things :
-    * An IDE, I’ll use VS Code with the ARMIPS extension, feel free to use whatever you feel the most comfortable with. +    * An IDE, I’ll use [[ https://code.visualstudio.com/VS Code ]] with the [[ https://marketplace.visualstudio.com/items?itemName=ajarmar.armips | ARMIPS Extension]] feel free to use whatever you feel the most comfortable with. 
-    * ARMIPS, this will handle assembling our ASM code to binary code and inserting it into the ROM, it’s available here+ 
-    * A Hex Editor, this will be good for finding freespace and it’s a useful thing to have around. +    * [[ https://github.com/Kingcom/armips/releases ARMIPS, ]] this will handle assembling our ASM code to binary code and inserting it into the ROM. 
-    * A debugger, aka an emulator, I highly recommend using no$gba because it’s a very good debugger, grab it here.+ 
 +    * [[ general:guides:hxd_guide | A Hex Editor, ]] this will be good for finding freespace and it’s a useful thing to have around. 
 + 
 +    * [[ https://problemkaputt.de/no$gba.zip | 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.     * A vanilla USA ROM of Metroid Zero Mission.
-    * MAGE (Metroid Advance Game Editor), since we’ll use Metroid Zero Mission, it’s going to be useful to have a way to edit the game, spawn wherever we want… Grab it here.+ 
 +    [[https://metroidconstruction.com/resource.php?id=90 | MAGE (Metroid Advance Game Editor), ]] since we’ll use Metroid Zero Mission, it’s going to be useful to have a way to edit the game, spawn wherever we want…etc. 
 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. 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.
 \\  \\ 
Line 42: Line 61:
  
 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. 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. 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.
  
Line 72: Line 92:
  
 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. 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 : We only have 16 registers that interests us, from r0 to r15, I like to divide them in 4 categories :
-    * 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]. +^        Name        ^                  Description                                                                                        ^ 
-    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. +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. | 
-    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.+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. 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 : Now let’s talk about those special registers :
-    * 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. +^        Name        ^                  Description                                                                                        ^ 
-    * r15|pc This is the program counter, it holds the ROM address of the current instruction executed.+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. 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.
  
Line 89: Line 118:
  
 I’ve been talking about the stack multiple times now, but what is it ? 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 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 : 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. +    * 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.\\   
-    * 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]…+ 
 +    * 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. 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.
  
Line 101: Line 136:
  
 Banks refer to how data is structured, this can be split like this : Banks refer to how data is structured, this can be split like this :
-    0000000-0003FFF BIOS, System ROM + 
-    2000000-203FFFF WRAM or Work RAM (Fast RAM) +^        Name     ^                     Description                   ^ 
-    3000000-3007FFF IRAM or Internal RAM (Normal RAM) +0000000-0003FFF BIOS, System ROM                                  | 
-    4000000-40003FE IO, Input Output (hardware registers) +2000000-203FFFF WRAM or Work RAM (Fast RAM)                       | 
-    5000000-50003FF PAL, Palette Memory +3000000-3007FFF IRAM or Internal RAM (Normal RAM)                 | 
-    6000000-6017FFF VRAM, or Video RAM +4000000-40003FE IO, Input Output (hardware registers)             | 
-    7000000-70003FF OAM or Object Attribute Memory (data for sprites) +5000000-50003FF PAL, Palette Memory                               | 
-    8000000-8FFFFFF ROM, data and code+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. We will mainly work with IRAM and ROM, but it’s good to know how it works overall.
  
Line 114: Line 153:
  
  
-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 : +In order to do conditions, we will use the flags of the CPU to determine where to jump and cmp instructions to compare value.  
-    The Zero flagZ = 1 if equal and 0 if not equal + 
-    The Carry flagC = 1 if unsigned higher or same and 0 if unsigned lower +Here are all the flags : 
-    The Negative flagN = 1 if signed negative and 0 if signed positive or zero + 
-    The oVerflow flagV = 1 if signed overflow and 0 if signed no overflow+^ Flag  ^      Name                                   Description                        ^ 
 +|     | The Zero flag     | Z = 1 if equal and 0 if not equal                            | 
 +|     | The Carry flag    C = 1 if unsigned higher or same and 0 if unsigned lower     | 
 +|     | The Negative flag N = 1 if signed negative and 0 if signed positive or zero    | 
 +|     | 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. 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). 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).
 \\  \\ 
Line 144: Line 189:
  
 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. 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. 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.
  
Line 149: Line 195:
  
  
-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 :+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'' :
  
 <code arm> <code arm>
Line 162: Line 208:
  
 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. 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.+ 
 +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 ==== ==== Hijack ====
Line 174: Line 221:
 </code> </code>
  
-.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… +''.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.+ 
 +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 ==== ==== Actual Code ====
Line 188: Line 236:
  
 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. 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). 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 :+To start, we load the IRAM address to r0 by using ''ldr'' (LoaD Register), the syntax is like this :
  
 <code arm> <code arm>
Line 195: Line 244:
 </code> </code>
  
-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 :+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 :
  
 <code arm> <code arm>
Line 201: Line 250:
 </code> </code>
  
-Here we are loading into r1, the halfword contained in r0 at offset 0. In C this would translate to :+Here we are loading into ''r1'', the halfword contained in ''r0'' at offset ''0''. In C this would translate to :
  
-<code arm>+<code c>
 r0 = 0x300006e; r0 = 0x300006e;
 ushort r1 = *(ushort*)r0[0]; ushort r1 = *(ushort*)r0[0];
 </code> </code>
  
-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.+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 : Let’s decrement our value now :
  
Line 216: Line 266:
  
 This is fairly simple, sub just subtracts the given register with the given value so this is the same as doing r1 -= 3. 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 store :+ 
 +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) :
  
 <code arm> <code arm>
Line 223: Line 274:
  
 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. 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 ! 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. 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 : If your code looks like this :
  
Line 235: Line 289:
  
 You’ve successfully done it, if not try to understand what you did wrong and why. 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 : 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 :
  
Line 243: Line 298:
  
 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. 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). 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.+ 
 +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 ==== ==== Assembling & Testing ====
Line 250: Line 307:
  
 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 :  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. +''armips.exe Test.asm'' 
-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 :+ 
 +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 :
  
 <code arm test.asm> <code arm test.asm>
Line 288: Line 346:
  
 If it still doesn’t work, come ask for help in the MAGConst discord server, here’s an invite link if needed. 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.+ 
 +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 ! Congratulations, you just created your first ASM patch !
 \\  \\ 
Line 299: Line 359:
  
 Now that you know the basics on how to write an ASM patch, let’s try something more difficult. 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. 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. This time we’re going to make something much more complicated, an ice bomb.
 +
 For that we will need to : For that we will need to :
-    * Hijack the bomb exploding on sprite subroutine +    * Hijack the bomb exploding on sprite subroutine\\ 
-    * Check the weaknesses of the sprite + 
-    * If it can be freezed, we freeze it +    * Check the weaknesses of the sprite\\ 
-    * Prevent conflicts with vanilla code+ 
 +    * If it can be freezed, we freeze it\\ 
 + 
 +    * Prevent conflicts with vanilla code\\
  
 ==== Setup ==== ==== Setup ====
Line 311: Line 377:
  
 Let’s begin by creating the file, I’ll name it “TestIceBomb.asm” and setting up ARMIPS directories. 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. 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. 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 : In the end, you should have something like this :
  
Line 335: Line 404:
  
 We’ll start by doing a super small edit, changing the particle effect of a bomb exploding. 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. 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. 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 We want to change that value to have a more “icy” effect, so let’s replace it with 0x28
  
Line 350: Line 422:
  
 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. 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 : 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 :
  
Line 373: Line 446:
  
 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. 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. 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. 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 : 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 :
  
Line 385: Line 460:
  
 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. 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. 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. 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). 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).
  
Line 395: Line 473:
  
 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. 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. 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.
  
Line 403: Line 482:
  
 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. 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 : 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 :
  
Line 416: Line 496:
  
 Put the CallR2 label right after CallR1 one to organize the code properly. Put the CallR2 label right after CallR1 one to organize the code properly.
 +
 For now the entire code looks like this : For now the entire code looks like this :
  
Line 448: Line 529:
  
 I also added a .pool because of the ldr of the functions. 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. 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. 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. 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. 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.
  
Line 460: Line 545:
  
 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. 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 Now let’s just call Set Particle Effect with r3
  
Line 476: Line 562:
  
 Now we need to check if the sprite is vulnerable to bombs, so we’re going to do the same as before. 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 :  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 : 
  
Line 546: Line 633:
  
 Our patch is almost complete, all that’s left to do is to handle 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. 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 :  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 : 
  
Line 574: Line 663:
  
 Sorry I lied earlier, this wasn’t the last step, but this one really is. 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. 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 : To prevent that, we’ll change when the vanilla function returns, cutting a part of its code :
  
zero_mission/intermediate_guides/yohannasm.1711218711.txt.gz · Last modified: 2024/03/23 18:31 by felixwright