GBAFE Assembly for Dummies, by Dummies

Reference

Definitions

The following things may already be known to you if you’ve read the Ultimate Tutorial or done any romhacking in the past.

Basic Definitions

Bit: The most basic unit of information used in computing. Can only have 1 of 2 values, 0 or 1. Binary numbers are followed with a “b”, and will be split up into groups of 4 digits to make it easier to read.
Byte: 8 bits. The smallest addressable unit of memory in most computer architectures (ie, the smallest value that you can manipulate directly)
Short (or halfword): 16 bits, or 2 bytes.
Word: 32 bits, or 4 bytes. Note, however, that in other systems, a word can be 16 bits, and a double word, or dword, is the 32 bit version. For the purposes of this guide, however, a word is 32 bits.
Nibble (or nybble): 4 bits. There are 2 nybbles in a byte (get it?).
Bit field/Bitfield: A cluster of bits (usually a byte, short, or word) where each bit is a flag. An example is character and class abilities (see your nightmare modules), which is a 32-bit bitfield, each corresponding to a particular ability. The first (zero’th) bit is “Mounted” and has value 2^0 = 1. The second bit is "Canto (Move Again), and has value 2^1 = 2. To denote that a unit has both the Mounted and Canto abilities, we sum up the bits: 1 + 2 = 3. If you then want to add Dance, which is the 4th bit (2^4 = 0x10), then the new value of the bitfield is 0x1 + 0x2 + 0x10 = 0x13.
Binary: A system of representing numbers using only 2 symbols: 0 and 1. Also called base 2 notation. Binary numbers will have a ‘b’ at the end, such as 10010101b.
Hexadecimal: Base 16 notation, where we represent numbers using 0-9 and A-F. Denoted with a preceding 0x or $, or a trailing ‘h’. Example: 0x9A, $4, 3445h.
Little endian: A method of arranging bytes such that the least important byte is first (reading from the left). Example: the number 0x12345678, in little endian, would be represented by breaking it up into bytes ($12 $34 $56 $78) and reversing their order ($78 $56 $34 $12).
Pointer: An object whose value is an address of another piece of information. Example: FE8’s item table begins at 0x809B10. The value 0x8809B10 is “pointing” to the item table, and is thus a pointer. See memory map for an explanation of that extra 8 in the beginning.
Dereference: To dereference a pointer means going the place where the pointer is, uh, pointing to, and retrieving the data there. How much data depends on what kind of command is doing the dereferencing (usually either a byte, short, or word).
Offset: In our case, this is basically analogous to an address; ie, the location in memory where something is written.
ROM: Read-Only Memory. As the name suggests, this is memory that can only be read by a program, not written to. May or may not be capitalized, so don’t be alarmed if I use it without ALL CAPS. A .gba file is a ROM, so the program running it (your emulator) can only read from it, not write to it.
RAM: Random Access Memory. This is memory that can be both read and written to. A character’s stats, for instance, will be stored in ram, since they can change as the unit levels up.
Alignment: Sometimes, an address has to be 2- or 4-aligned. This means the address must be divisible by 2 or 4, which can be determined by looking at the last digit of the address.

Decimal/Binary/Hexadecimal Conversion

If you’re not used to hexadecimal (or hex, as it’s colloquially called), it can be confusing to use. It’s important that you learn how to manipulate it, because unless specified otherwise, all numbers used in this guide are in base 16. (Decimal numbers will have a ‘d’ at the end, like 99d). Why do we bother using it? As you may know, computers work with binary: strings of 0s and 1s. The human brain, however, cannot easily parse binary. If you ask me what 11010101011110101011010101 was in decimal, it would take me a minute or so to answer. Decimal, or base 10, doesn’t easily convert to base 2, because 10 is not a power of 2. 16, however, is. In fact, it’s a power of a power of 2 (2^2^2). This means that hex is very easy to convert to binary, and vice versa. Hex also has enough unique symbols (16 of them) that our brains can parse them relatively easily. So hexadecimal is a compromise between usability for the machine and readability for the human.
When you actually need to convert from decimal to hex, it’s easiest to use the calculator that comes with most computers. However, it can be useful to know how to convert from binary to hex quickly by hand. Let’s use the number I wrote earlier: 11010101011110101011010101.
First, break the number up into groups of 4, starting from the right. If the last group has less than 4 digits, add 0s to the left until it has 4.
0011 0101 0101 1110 1010 1101 0101
Next, find the hex representation of each group of 4, which will a number from 0x0-0xF:
3 5 5 E A D 5
Finally, concatenate the string:
0x355EAD5
Voila!
Use the inverse to convert from hex to binary.

Signed vs Unsigned Numbers

A byte can take a value from 0x00 to 0xFF, which is 0 to 255d. But say you want to have negative numbers. How would you do that? In decimal, we use the “-” sign to denote that a number is negative, but that’s not an option here because we don’t have room for another symbol in our alphabet. Instead, we use the topmost bit (furthest to the left) as a sign. If it is set (ie, 1), the number is negative; if not, the number is positive.
If your byte is between 0x00 and 0x7F (0111 1111b), then there’s no difference between signed and unsigned, since the top bit isn’t set. The decimal values range from 0 to 127.
Between 0x80 and 0xFF, however, the top bit is set, and a signed byte would have a negative value from -128 to -1.
To sum up:
Unsigned: 0 - 255
Signed: -128 - 127

About assembly

The ARM, the thumb, and the ugly

The processor that the GBA works with, the ARM7tdmi, uses an instruction set called, appropriately enough, ARM. ARM stands for Advanced RISC Machine, and RISC stands for Reduced Instruction Set Computing. As the name suggests, we have a smaller set of instructions that we can actually perform, but this is offset by being able to perform those instructions faster. The processor is a 32-bit one, and the ARM instructions (also called opcodes) are each 32 bits (4 bytes, or 1 word) long. There is also a subset of ARM, called THUMB (doesn’t stand for anything, it’s just a cute name), which uses 16-bit (2 bytes, or a short) instructions. As you might imagine, that limits the number of opcodes we can work with. Why bother? Because 16-bit opcodes execute even faster than 32-bit ones, and speed is the name of the game here. In addition, the ROM port (the connection between the memory containing the game’s code and the part of the processor that executes said code) is only 16 bits . We could run ARM assembly from the rom (and in fact, Fire Emblem does so sometimes), but it would be slower because the opcode has to be loaded in two separate pieces and put back together before it can be executed. Therefore, the majority of the game’s code is written in thumb assembly, which is actually a good thing, since it means it’s easy enough to memorize all the opcodes you can use. The hard part about assembly hacking isn’t so much learning the language, but rather applying it efficiently to whatever you’re trying to do.

That’s not to say that ARM code is too slow to use, ever. The chip does feature a section of memory with a 32-bit bus called IWRAM, or Internal Work RAM (usually abbreviated to IRAM). When you boot up the game, functions that are to be executed in ARM mode are copied from the ROM to IRAM, where they will stay until the game is turned off. Things such as sprite manipulation, text decompression, and pathfinding algorithms all reside in IRAM. You will probably never need to mess with these unless you’re attempting something extremely ambitious, and if you’re in a position to do so, then you probably don’t need this guide.

Ingredients

Alright, so we have our instruction set. How do we use it?
Glad you asked. The GBA has 16 registers (technically, it has more, but we don’t really care about the rest), labeled r0 to r15. Each register contains a 32-bit value, which you manipulate using the opcodes.
Oftentimes, it is necessary to save a value in a register so that you can use it later. Fortunately, we have a stack that we can copy the contents of a register to in order to save said contents. For more detail, see the stack explanation in the no$gba overview section.
Not all registers are equal. r0 - r7 are called “low” registers, and r8-r15 are “high” registers. In THUMB mode, we’re limited to what we can do with the high registers. In addition, r13-r15 are special registers that are reserved for a specific purpose.

Conventions

There are a number of conventions, or rules, that ought to be followed to make your (and everyone else’s) life easier when working with assembly. Don’t worry if you don’t quite understand these yet; I’ll explain things in more detail in other sections.

  • r0-r3, and r12, are called scratch registers. This means that the values in the them aren’t important and can be overwritten (most of the time). It also means that they’re not expected to be saved during a function call.
  • By contrast, r4-r11 are preserved registers. If you want to use them, you must save their values by copying to the stack. Failure to do so will almost certainly result in Very Bad Things occurring.
  • To pass parameters to a function, use r0-r3, starting with r0. If there are more than 4 parameters, the remaining arguments are expected to be passed in via the stack.
  • When a function is complete, if it returns a value, that value is expected to be in r0. Functions don’t return more than 1 value at a time.
  • After returning from a function, always assume that the scratch registers have been cleared, even if they haven’t actually been touched.
Memory blocks

The GBA’s memory is divided into several “blocks”, indicated by the first byte of the address (for instance, 0x8000000 is a rom address). Each block is reserved for a specific task.

  • 00 - BIOS: Basic Input/Output System. Called during start-up and software interrupts. Basically in charge of getting everything up and running.
  • 02 - EWRAM/WRAM/ram: External Work Random Access Memory. This is where most of the mutable information is stored, like a character’s stats, for instance.
  • 03 - IWRAM/IRAM: Internal Work Random Access Memory. As previously mentioned, this is where the majority of ARM code is stored and executed due to its 32-bit bus. Also used to store some data, like event ids, but not nearly as much as the 02 block.
  • 04 - I/O: In/out. This is basically the interface between the hardware and the software. Button presses, sound, etc, are located here.
  • 05 - PAL RAM: Palette ram. Stores palettes. That’s basically it.
  • 06 - VRAM: Video ram. Stores graphics data.
  • 07 - OAM: Object Attribute Memory. Controls sprites.
  • 08 - ROM: Read-only memory. In a physical GBA, this would be the game cartridge; in an emulator, it’s the .gba file.
  • 0E - Cart RAM. This is where memory is actually saved (when you save after completing a chapter, or suspend in the middle of one, data gets copied here).

In this guide, you will be editing ROM (08) the most, with an occasional foray into wram and iram (02 and 03). The rest will probably only be touched on briefly, if at all; they’re a bit beyond the scope here.

Documentation

Before starting a project, it is worth looking at the documentation that already exists and checking a) whether what you’re trying to do has already been done, and b) whether there’s information that can help you solve the problem. There’s no sense in reinventing the wheel, after all (actually, you can try and solve an already-solved problem yourself and then compare to the finished version if you want the practice, but I digress).
In the Unified Dropbox, each person with a folder usually has some doc somewhere in it. The most complete is Stan’s doc. For the purposes of this guide, I will referring to my own doc, found here (mostly the Teq Doq).

15 Likes