First Demo Walkthrough
Let's go over the skeleton code generated by gvasm. It's okay not to understand everything.
Comments
//
// Game v0
//The first thing to notice is comments are just like C/C++/JavaScript/etc:
Use
//for end of line comments. Everything after//will be ignored.Use
/* */for block comments. Everything between/*and*/will be ignored.
Standard Library
// include standard library for useful constants
.stdlibJust as the comment says, gvasm includes a very minimal standard library.
The standard library contains useful constants, like REG_WAITCNT. These constants are just numbers. For example, REG_WAITCNT contains the value 0x04000204.
The standard library does not include any code, so it will not increase the size of your ROM file.
GBA Header
// GBA header
.begin header
.arm
b main
.logo
.title "GAME"
.str "CUNE77"
.i16 150, 0, 0, 0, 0
.i8 0 // version
.crc
.i16 0
b header // ensure ROM isn't interpreted as multi-boot
.str "SRAM_Vnnn" // tell emulators to reserve 32K of SRAM
.align 4
.endThe first 192 bytes of a .gba file is the GBA header. The meaning behind these 192 bytes was decided by Nintendo when they created the GBA. You can read a detailed description of the header in the reference section.
You don't really need to memorize the format of the GBA header -- the purpose of using gvasm init is specifically so you can get started making a game quickly.
However, there are still some things useful to notice here.
.begin / .end
The header is wrapped in a .begin / .end block. This is a way to organize code in gvasm.
.begin TheNameOfTheBlock
// ...
.end.arm
The ARM instruction set is actually two instructions sets, named ARM and Thumb.
Use .arm to select the ARM instruction set, and .thumb to select the Thumb instruction set.
b main
Execution begins at the top. And since the header is not code, the first instruction needs to jump over the rest of the header. This is called a branch, and the instruction is b <label>.
.logo
The Nintendo logo is part of the header. The .logo instruction inserts the necessary bytes.
.title "GAME"
The game title is embedded in the header, and displayed on mGBA in the title bar.
.str "CUNE77"
The .str instruction will output a string to the ROM. The CUNE77 code sets a few parameters you can look up and customize if you want.
.i16 / .i8
The .i16 and .i8 instructions will output either 16-bit numbers or 8-bit numbers to the final ROM.
.crc
The header has a checksum that must be calculated. The assembler will do that automatically.
The .i16 0 is actually the end of the official header, but gvasm will add a few more things afterwards:
b header
Some games are considered multi-boot, and emulators don't know whether a ROM is normal, or multi-boot. They typically perform a series of tests and guess. One common test is to see if the instruction after the header jumps backwards or forwards.
By jumping back to the start of the header, the emulator won't incorrectly flag this ROM as multi-boot.
.str "SRAM_Vnnn"
One challenging aspect of GBA games is saving data.
Different carts had different hardware, and different methods of saving data. Unfortunately, there is no part of the header which indicates what kind of save hardware exists on the cart.
However, Nintendo provided common save routines in their SDK, which most developers used. Thankfully, these save routines had strings in them that indicate the type of save hardware.
So, emulators typically scan the ROM for these strings, in order to guess which save hardware to emulate.
SRAM is the easiest to use, so by default, gvasm will place the SRAM hint string near the header to help emulators.
.align 4
Lastly, this tells gvasm to output 0x00 until the file size is aligned to 4 bytes.
Alignment is very important in assembly. Instructions will need to be aligned to 2 or 4 bytes depending on the mode (Thumb or ARM), and memory access needs to be aligned as well.
Main Code
Finally, we get to the demo code:
.begin main
.arm
// set cartridge wait state for faster access
ldr r0, =REG_WAITCNT
ldr r1, =0x4317
strh r1, [r0]
// Your game here!
// For example, this will set the display to blueish green:
// set REG_DISPCNT to 0
ldr r0, =REG_DISPCNT
ldr r1, =0
strh r1, [r0]
// set color 0 to blueish green
ldr r0, =0x05000000
ldr r1, =rgb(0, 31, 15)
strh r1, [r0]
// infinite loop
loop:
b loop
.pool
.endThis seems like a lot at first, but you'll see that it's really quite simple when you know how to look at it.
.begin / .end
Once again, this block of code is wrapped in .begin and .end:
.begin main
...
.endThe block is named "main". This doesn't matter, as long as the header references the same name (b main).
.arm
Once again, we are setting the instruction set to ARM mode.
Storing a value in memory
Next, we'll see three groups of code, which all have the same basic structure:
ldr r0, =ADDRESS
ldr r1, =VALUE
strh r1, [r0]The first thing to understand is that, unlike high level languages, assembly only has a limited number of variables. These are called registers.
For the GBA, the CPU has 37 registers, but usually only 16 or 8 are accessible at any given time.
Typically, your code will load data into registers from memory, perform some operation using the registers, then store the result back into memory.
You might be wondering: how can I ever accomplish anything useful with only 8 variables?
Well, very carefully.
You have 32K of internal memory, and 256K of external memory. So these memory locations can be used to offload data while you work on something else.
But at any given moment, you will only have a handful of registers to do real work.
Suppose you have 10 sprites on the screen, and each object has an X, Y location. That alone is 20 pieces of data.
What you will do is put that data somewhere in memory. Then, when you need to update the position of a single sprite, you will read the X, Y position into two registers, update the values, then write the registers back to memory.
This might seem very tedious, and in some sense, it is! But believe it or not, if you stick with it, you will get used to it.
The tedious nature eventually goes away, because just like you probably never think too hard about creating a class with a higher level language, you will also not think too hard about loading and storing data in memory with assembly.
Now let's get back to this code:
ldr r0, =ADDRESS
ldr r1, =VALUE
strh r1, [r0]This is a very common sequence of code.
First, we load the memory address we want to write data to into a register (r0).
Then we load the value we want to write to that address into another register (r1).
Then we store the value at that memory location.
If we were to convert these three statements to C, it would look like:
int r0, r1;
r0 = ADDRESS; // ldr r0, =ADDRESS
r1 = VALUE; // ldr r1, =VALUE
*((int16_t *)r0) = r1; // strh r1, [r0]Or, if we wanted to make it more compact, we could do:
*((int16_t *)ADDRESS) = VALUE;So, now you can see what our demo does:
// *REG_WAITCNT = 0x4317;
ldr r0, =REG_WAITCNT
ldr r1, =0x4317
strh r1, [r0]
// *REG_DISPCNT = 0;
ldr r0, =REG_DISPCNT
ldr r1, =0
strh r1, [r0]
// *((int16_t *)0x05000000) = rgb(0, 31, 15);
ldr r0, =0x05000000
ldr r1, =rgb(0, 31, 15)
strh r1, [r0]It's still not clear why we want to do this, but at least you understand what the code is doing.
In order to understand why we do this, we need to understand the GBA hardware.
Input/Output Registers
Confusingly, not only do we have CPU registers (r0, r1, etc), but we also have I/O registers (REG_WAITCNT, REG_DISPCNT, etc).
Even though they're both called "registers", they are very different.
CPU registers are like variables. You can store values in them, and do work on them directly.
I/O registers are like an API. Think of it as the GBA API. This is how you interact with the GBA to accomplish things like putting sprites on the screen, or playing sound.
There are a lot of I/O registers, and understanding how they work is essential for writing a game.
REG_WAITCNT
The first register we write to is the REG_WAITCNT register. And we write a very specific value, 0x4317. Why?
This sets the wait count when the CPU accesses the ROM.
By default, the CPU will wait a little bit before it reads data from the ROM. This can really slow things down, because every time the CPU needs something from the ROM (very often), it adds a slight delay.
Setting to 0x4317 will remove this extra delay, so that the CPU can access the ROM quickly. Typically, you set this value, and never change it again.
REG_DISPCNT
The REG_DISPCNT register has a lot of different options. This will set your display mode, and enable or disable different effects and features.
For the purpose of the first demo, we want to use Mode 0, and disable everything else, so that conveniently becomes the value 0.
0x05000000
0x05000000Lastly, we write a value at the memory location 0x05000000.
TODO: more
Last updated