Shiny Quagsire
I'm Still Alive, Elsewhere
- 697
- Posts
- 15
- Years
- Age 27
- Hoenn Safari Zone
- Seen Aug 8, 2020
GBA ASM Programming
From scratch and on Pokemon Games
Introduction
If you've ever had to learn about computers, most people say: "The computer uses 0's and 1's," and "Programmers write applications all in 0's an 1's." That's only partially true. Machine code usually is written in C/C++, which is then compiled to Assembly Code, and then 1's and 0's.
What is Assembly Language
ASM is a low-level language that (When compiled) processors use to interpret instructions. Here's how Wikipedia defines it:
Assembly language is a low-level programming language for computers, microprocessors, microcontrollers, and other integrated circuits. It implements a symbolic representation of the binary machine codes and other constants needed to program a given CPU architecture. This representation is usually defined by the hardware manufacturer, and is based on mnemonics that symbolize processing steps (instructions), processor registers, memory locations, and other language features. An assembly language is thus specific to a certain physical (or virtual) computer architecture.
The processor and ASM language the GBA uses is ARM 7, (which is a lot easier compared to Z80, the GBC processor, in my opinion). When you write ASM, All you can do is Add, Subtract, Multiply, load data, and store data. There is no magical "Change the name" command.
You may have noticed there is no divide. The problem is that there are no decimals in ARM 7 ASM, and division is generally harder on the processor to calculate. There are BIOS functions to divide, but there are some tricks to doing simple dividing that I'll explain later.
What We'll need:
- VisualBoyAdvance
- A compiler - To put our ASM into Hex
- VBA-SDL-H - For testing, or debugging, our ASM to see how it works or to find problems.
- A Brain XD
Let Me Try! Let Me Try!
I think the best way of learning is to let you see what you can do with this knowledge. So, first, download my ASM Pack, which includes:
- A compiler
- HackMew's Nifty Batch File - Thanks HackMew!
- Some ASM Code
With that downloaded and extracted, go into the command prompt. After all, the assembler is command prompt based.
If you don't know how to use the command prompt, just google 'Command Prompt Tutorials.' You'll find something ;)
When you open it, you'll see somthing similar to this:
Microsoft Windows XP [Version 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.
C:\Documents and Settings\XXXXXX>
Now, navigate to the directory you placed it in. For me, it's in a folder on my desktop called ASM. Try to keep folder names short, and simple.
Microsoft Windows XP [Version 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.
C:\Documents and Settings\Maxamillion>cd Desktop\ASM\tutorial
Now we need to assemble it, so type in this:
Code:
[b]thumb example1.asm example1.bin[/b]
Here's our output:
Microsoft Windows XP [Version 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.
C:\Documents and Settings\Thomas>cd Desktop\ASM\tutorial
C:\Documents and Settings\Thomas\Desktop\ASM\tutorial>thumb example1.asm example1.bin
Assembled successfully.
Press any key to continue . . .
Obviously press any key, and we're done!
What a Piece of Junk!
OK, so we assembled our routine, but how do we use it?! First, open up our example1.bin we assembled into a hex editor. You should get something like this:
03 B5 02 48 80 88 02 49 08 80 03 BD 84 42 02 02 D0 70 03 02
Now, open your Pokemon ROM into a hex editor, and copy it to a free-space address ending with 0, 4, 8, or C. You may ask, "Why not 5? or 7?" Because these numbers aren't multiples of four, and it uses bit 0, a bit used to tell the game "Do you want to boot into Thumb or ARM?" So, once you have it copied, you'll need to set bit 0 to 1, or, simply put, add 1 to your offset. So if I placed it at 0x720000, I'd add 1, making it 0x720001.
With this inserted, how do we use it through a script? (Please note this is an ASM tutorial, and no questions regarding scripting will be answered, unless it's a problem with booting the ASM in the script.) The answer is simple. We use the magical callasm command. So, here's an example script to use, since you don't know how the ASM works yet.
#dynamic 0x800000
#org @start
lock
faceplayer
callasm 0xsomeoffset
buffernumber 0x0 lastresult
msgbox @msg 0x2
release
end
#org @msg
= Wow, your POKEMON is at level [buffer1]!
Once that's inserted, go ahead and go to VisualBoyAdvance and open your script. Here's what your result should look like:
![[PokeCommunity.com] GBA ASM Programming [PokeCommunity.com] GBA ASM Programming](https://i53.tinypic.com/ddj5za.png)
But was that it's level? Let's check!
![[PokeCommunity.com] GBA ASM Programming [PokeCommunity.com] GBA ASM Programming](https://i52.tinypic.com/v7uqf9.png)
There it is! But how'd the ASM return it? That will be explained in the next section.
Looking? Found someone you have, eh?
~Yoda
Alright, so we've had some fun, but we've never seen what's going on in the ASM, or the script. Now let's see how it works by looking at Pokemon data, then explain each command one by one in the ASM routine.
Pokemon Data
The Pokemon data can be explained in detail here. You can check it out to see where some things are. The stats and such don't start untill about byte 80.
The byte we are retrieving, is the level, 1 byte with a maximum of 256. Let's see how we load those bytes by opening the ASM file according to your game. They'll all be slightly different, but they all have the same commands at a glance.
.align 2
This aligns the ASM by a multiple of 2, since each instruction is 2 bytes long.
.thumb
This tells the assembler that we are using the thumb instruction set. If we were using ARM, we'd use .ARM
main:
This is label. Labels are ways to call parts of a routine without calculations or repeating things over and over. Labels are text (Without spaces), and end with a colon. This label is called "main"
push {r0-r1,lr}
This pushes registers r0, r1, and the link register into the stack.
Registers are like variables that store temporary values and information. Registers are 32 bits long, making the highest value possible FFFFFFFF.
The stack is a bit hard to explain. Think of 15 stacks of china plates. Each of them has a different design on them. When you push a register into the stack, it's like adding a new plate as the one on the top to the stack. Here's a visual representation from Wikipedia:
![[PokeCommunity.com] GBA ASM Programming [PokeCommunity.com] GBA ASM Programming](https://upload.wikimedia.org/wikipedia/commons/thumb/2/29/Data_stack.svg/391px-Data_stack.svg.png)
The Link Register tells the processor where to return from the parent routine that called this one.
ldr r0, pokemon_data
This loads a 32 bits at the label 'pokemon_data' into r0. These bits, or bytes contain the address to our party data.
ldrh r0, [r0]
Loads a 'half-word,' or 16 bits, (or 2 bytes) from the address at r0.
ldr r1, var
This loads the data at the label var, which is the location of LASTRESULT in the WRAM. (Work RAM)
strh r0, [r1]
This stores the half-word in r0 at the address in r1. But r0 has a byte, not a half-word! It stores a half-word, not because r0 has a half word, but variables are only 1 half-word long.
pop {r0-r1,pc}
This 'pops,' or removes the registers r0, r1, and pc from the top of the stack, leaving the original registers again. You may ask "Why not pop {r0-r1, lr}? Well, pc is our current position. When we pop this, It continues onto where it was before. The link register will reset by itself. Here's the image again to help explain:
![[PokeCommunity.com] GBA ASM Programming [PokeCommunity.com] GBA ASM Programming](https://upload.wikimedia.org/wikipedia/commons/thumb/2/29/Data_stack.svg/391px-Data_stack.svg.png)
.align 2
We need to make sure our variables are multiples of two, so the routine can access them.
pokemon_data:
Another label, giving our Pokemon data.
.word 0x02024284 + 0x54
Writes the word 0x02024284 + 0x54 at this spot.
var:
A label for our variable.
.word 0x020270B6 + (0x800D * 2)
The offset of the first variable plus 0x800D, the lastresult variable, x 2, since variables are 2 bytes long.
Returning Values at the Bit Level
Alright, we've checked code, we've disected code, now lets go even deeper into pokemon values!
What New Commands We'll need:
lsl rX, #0xV - This shifts the bits in rX left byte 0xV positions.
lsr rX, #0xV - This does the same as above, but shifts bits right.
ldrb rX, [rY, #0xV] - Loads a byte from the address at rY + 0xV into rX. The 0xV is optional though.
There are also other methods that you can use, and it's usually the same with all ldr commands.
ldrb rX, label - Loads a byte from a label.
What we'll be doing
I decided to make a tutorial with something to the bit level. After all, bits are hard to manipulate at first, but it's key to disecting some things.
Status Conditions
Status conditions are based on bits. Here's a quote from bulbapedia:
Now, let's see how we can get, say, the sleeping bit perhaps. Let's write some code!Bulbapedia said:Status ailment
The Pokémon's status ailments are stored as follows:
- 0-2: Sleep bits. Indicates turns of sleep, so 111b = 7 turns, 101b = 5 turns, etc.
- 3: Poison
- 4: Burned
- 5: Frozen
- 6: Paralysis
- 7: Bad poison
Down and Dirty!
Alright, lets get to work. First, create a brand new txt file, and rename it Status_[gameprefix].asm the game prefix should be like my example1_fr, or example1_rs. Try to keep names without spaces, and short and sweet.
Now, add this to the top of your file:
Code:
.align 2
.thumb
main:
push {r0-r1,lr}
ldr r0, pokemon_data
Code:
ldrb r0, [r0]
Code:
lsl r0, #0x5
lsr r0, #0x5
10101010
turns it into this:
01000000
Then this:
010
Which is the sleep value!
And now we write it to the variable 0x800D
Code:
ldr r1, var
strh r0, [r1]
pop {r0-r1,pc}
For example, I'd add this:
Code:
.align 2
pokemon_data:
.word 0x02024284 + 0x50
var:
.word 0x020270B6 + (0x800D * 2)
Code:
#dynamic 0x800000
#org @start
lock
faceplayer
callasm 0xyouroffset
buffernumber 0x0 lastresult
msgbox @msg 0x2
release
end
#org @msg
= That POKEMON sure is sleepy.\nIt's going to sleep [buffer1] turns!
![[PokeCommunity.com] GBA ASM Programming [PokeCommunity.com] GBA ASM Programming](https://i56.tinypic.com/282wro.png)
Of course, my pokemon wasn't asleep, but it returned the value. Feel free to debug it if you want!
The Challenge of the Day!
Can you return the value of a burn? Perhaps poison? Return your ASM with an image!Value Changing, Comparing, and Arithmetic Commands
In this tutorial, we're going to cover commands used to change value, compare, and do arithmetic functions, which are some of the most essential commands (In my opinion) to ARM7 ASM.
New Commands used in this Section:
mov rX, #0xY Moves the value in rX to 0xY.
add rX, rY, rZ Adds the contents of rY and rZ and places it on rX.
add rX, #0xY Adds 0xY to rX.
sub rX, rY, #0xZ Subtracts 0xZ from rY and places it on rX. It generally has the same syntax as adding.
mul rX, rY, rZ Multiplies rY and rZ and places the result on rX. rZ cannot be replaced with a hex number like #0x5.
cmp rX, #0xY Compares the value in rX to 0xY and stores the results to the flags. 0xY can also be a register.
teq rX, #0xY This is sort of a limited cmp command, that tests if rX is equal to 0xY.
b{condition} label Compares the flags depending on the condition, and if the result meets the condition, the processor goes to label.
Using Comparing Commands
Comparing commands work by subtracting 0xY from rX, and placing the results in the flags. It doesn't really return any result to a register. Here's a quote from ARM's website:
So, that's how it works. Unfortunatly, the flags are protected from access in thumb mode, but there is a command that can do it for us. It's the command "b".ARM.com said:These instructions compare the value in a register with Operand2. They update the condition flags on the result, but do not place the result in any register.
The CMP instruction subtracts the value of Operand2 from the value in Rn. This is the same as a SUBS instruction, except that the result is discarded.
Spoiler:
How 'b' works is it compares the flags to a condition. If it is met, it goes to another label. The conditions are as follows:
Code:
EQ Equal
NE Not equal
CS/HS Higher or same (unsigned >= )
CC/LO Lower (unsigned < )
MI Negative
PL Positive or zero
VS Overflow
VC No overflow
HI Higher (unsigned <= )
LS Lower or same (unsigned <= )
GE Signed >=
LT Signed <
GT Signed >
LE Signed <=
AL Always (usually omitted, same as just "b" by itself.)
Code:
rX <= rY
A Quick Example
For this, we won't have an active code sample that does something, simple because this is more for learning the commands. Instead, lets have some examples:
Code:
.align 2
.thumb
main:
push {r0-r1}
mov r0, #0x5 @Moves r0 to 5
sub r0, #0x3 @Subtracts 3 from r0
mov r1, #0x3 @Move r1 to 3
mul r0, r1, r0 @multiplies r0 and r1, and places the result on r0
cmp r0, #0x6 @Compares r0 to 6, which it should be
beq end @If equal goes to the label 'end'
end:
pop {r0-r1}
And that pretty much covers everything we need!
Last edited: