I spent a lot of time as a kid playing (generally, pretty terrible) games on my Game Boy. Having never written code for anything other than 'regular' general purpose computers before, I've been wondering recently: how easy is it to write a Game Boy (Advance) game?
A quick and easy to use drag and drop retro game creator for your favourite handheld video game system. Available on Windows, Mac and Linux. Download on Itch.io. 3DS Arcade Custom / Edited DS / DSi Game Boy / GBC Game Boy Advance GameCube Genesis / 32X / SCD Master System Mobile Neo Geo / NGCD NES Nintendo 64 Nintendo Switch PC / Computer PlayStation PlayStation 2 PlayStation 3 PlayStation 4 PlayStation Vita PSP SNES Wii Wii U Xbox 360 Other Systems. Gaming Templates. Browse MotionDen's collection of professionally-made gaming intro templates you can customize to promote your YouTube channel, brand or business.
For those unfamiliar, the Game Boy Advance (GBA) was a popular handheld games console produced by Nintendo (pictured below). This thing is kitted out with a 240x160 (3:2) 15-bit colour LCD display, along with six face buttons and a directional pad for input.
On the inside, the GBA's CPU contains a 32-bit ARM7tdmi ARMv4T RISC core (operating at 16.78 MHz). Along with regular 32-bit ARM instructions, this chip can execute 16-bit Thumb instructions. For those unfamiliar, the Thumb instruction set is a 16-bit encoding for some of the most common 32-bit ARM instructions, and can be used to increase code density.
In terms of memory, the device has 130 KB of embedded memory within the CPU (96 KB of which is used for VRAM, 32 KB of which is for general usage, and 2 KB of which are used elsewhere), and 256 KB of RAM external to the CPU. The system also has 16 KB of System ROM, which is used to store the BIOS. There are also some additional details in all this regarding backwards compatibility of the Game Boy Advance with the Game Boy Colour, but we're not going to discuss them here.
Along with all this internal memory, the GBA is typically loaded with some form of game cartridge. These typically consist of some ROM (to store instructions, read-only data, etc.), and some form of mutable storage (typically SRAM, Flash Memory, or EEPROM). As the Game Pak ROM is connected via a 16-bit wide bus, it makes sense to use 16-bit Thumb instructions rather than 32-bit ARM instructions most of the time in game code.
All of the memory sections we've discussed, along with I/O hardware registers (to control graphics, sound, DMA, etc.), are mapped into memory, giving a memory layout something like the following:
Booklet cd creator. A free online Pokemon Maker and GTS. Create Pokemon and send them to your game. Pokemon Bank compatible. Gaming Templates. Browse MotionDen's collection of professionally-made gaming intro templates you can customize to promote your YouTube channel, brand or business.
Gameboy Advance Games
0x00000000 - 0x00003FFF
- 16 KB System ROM (executable, but not readable)0x02000000 - 0x02030000
- 256 KB EWRAM (general purpose RAM external to the CPU)0x03000000 - 0x03007FFF
- 32 KB IWRAM (general purpose RAM internal to the CPU)0x04000000 - 0x040003FF
- I/O Registers0x05000000 - 0x050003FF
- 1 KB Colour Palette RAM0x06000000 - 0x06017FFF
- 96 KB VRAM (Video RAM)0x07000000 - 0x070003FF
- 1 KB OAM RAM (Object Attribute Memory — discussed later)0x08000000 - 0x????????
- Game Pak ROM (0 to 32 MB)0x0E000000 - 0x????????
- Game Pak RAM
These sections have varying bus widths and read/write widths (e.g. you can't write individual bytes into VRAM!), and some sections are mirrored in memory at multiple points. There is also some extra complexity to this in reality, but this is the main structure that we'll need to build a basic GBA game.
With this knowledge of the memory structure of the device, the plan to make a 'Hello, World!' GBA ROM is as follows: write some Thumb code for our Game Pak ROM which sets display parameters in I/O registers as appropriate for some particular display mode, and then write some graphical data into VRAM that we want to display. With some of the theory about the device out of the way, let's actually try and build something.
Setting Up A Development Environment
To begin executing our plan to build a GBA ROM, we need to know a bit about the ROM format. Without going too far into the details, GBA ROMs begin with a standard header. This should start with a four byte ARM instruction to branch to the start address of our program, followed by some magic bytes representing the Nintendo logo.
Additionally, in this header there is some data about the game (its title, etc.), and a 'check' value for this data. We will need to ensure that the header is perfectly correct if we want our ROM to execute properly (particularly if we're aiming to execute on an actual device rather than an emulator).
Thankfully, most of the details of ROM creation can be handled by a good toolchain. I use the devkitARM toolchain myself (one of the devkitPro toolchains, based on the GCC toolchain), which makes the process extremely easy. Essentially, once the toolchain is set up, we can turn some C code into a GBA ROM in four steps:
- Cross-compile our C code to Thumb instructions for the GBA's CPU, creating a Thumb object file with our ROM code.
- Link our object file into an executable using a specific 'specs' file to control the behaviour of the linking. Typically the specs file includes a link script (to specify segment locations [most mutable data will get stored in IWRAM, 'const' data in ROM, etc.] and alignments etc. for correct compilation for the GBA), and some other object files (usually, a standard ROM header, startup routines, program initialization and termination code, etc.).
- Strip our executable file of information we don't need (executable header, symbol and relocation information, etc.), to get a near-complete ROM file.
- Run a utility on the ROM file from the previous step to fix its header (ensure that the Nintendo logo data in the header is correct, set any 'check' values as appropriate, etc.)
With the version of the toolchain I have on my machine running macOS, I can run the following commands (providing I have /opt/devkitpro/devkitARM/bin
in my PATH environment variable) to compile a C file into a GBA ROM (as we described above):
arm-none-eabi-gcc -c main.c -mthumb-interwork -mthumb -O2 -o main.o
arm-none-eabi-gcc main.o -mthumb-interwork -mthumb -specs=gba.specs -o main.elf
arm-none-eabi-objcopy -v -O binary main.elf main.gba
gbafix main.gba
There are also some additional flags you might want to pass in for the first step (the compilation) — I'd recommend -fno-strict-aliasing
, for example, as we'll be dealing with raw memory and pointers a lot and don't really want C's strict aliasing rule to bite us. You might also find it beneficial to write a Makefile or shell script with these commands to make ROM compilation easier — these details seem a little unnecessary to include in this post though.
With a process for ROM compilation from C established, let's try and test it out. To throw a bone to any readers starting to drift off from all the theory, let's look at the code for our 'Hello, World' GBA ROM first, and then discuss it afterwards.
The code above is relatively simple, and should result in a horizontal set of three pixels being drawn in the middle of the GBA screen — one red, one green, and one blue:
Now, time to explain the code. Firstly, we write some display parameters to the memory mapped I/O registers — in particular, the first 16 bits of this memory is a display control register (often called DISPCNT). The first three bits of this register indicate the video mode, and the 11th bit indicates whether background #2 (BG2) is enabled or not — thus, by writing the values we do, we set the video mode to mode 3, and enable BG2.
Why do we need to do this? Well, first off the video mode. It turns out that video mode 3 is a mode in which we can write bitmap data into VRAM, and BG2 will display this bitmap (hence, why we also want to enable BG2). You might also be wondering why I've chosen to use video mode 3 rather than another video mode — the reason for this is that video modes 0 to 2 are much more difficult to explain (though we'll be using them later).
As I mentioned earlier, the LCD on the GBA can display 15-bit colours. Therefore, we can express GBA colours using a 15-bit long colour format — for data alignment reasons though, the GBA uses a 16-bit colour format. Specifically, the format is as follows: ?BBBBBGGGGGRRRRR
. So that's an unused bit, followed by five bits of blue, five bits of green, and then five bits of red.
Using this format, and with knowledge of how video mode 3 treats VRAM as a 240x160 bitmap, our 'Hello, World' ROM simply writes some colour values at specific pixel offsets (e.g. as we're assuming that unsigned short
is 16-bits in size, vram[80*240 + 120]
skips 80 horizontal lines of 240 pixels, and then accesses the middle pixel on that horizontal line). Note, by the way, that all the memory accesses for interfacing with hardware in the code occur through volatile
pointers — this prevents the compiler optimising out what it might think are useless memory operations.
Writing a pong-esque game
With the basics out of the way, let's try and build something a little more interesting. We're still going to hack the solution together rather than building a bunch of project infrastructure and helper functions, but we'll make use of some more advanced features of the GBA's graphics rendering. In particular, we'll depart from drawing using the bitmap video modes.
While drawing in the GBA's bitmap video modes (modes 3, 4, and 5) is very easy, for many games it's not really practical. Our 240x160 bitmap itself takes up the majority of VRAM just to fill the screen once, and pushing around so many pixels every frame can be computationally expensive too (we might not be able to afford this we're aiming to render our game at a reasonable framerate). Thus, we have video modes 0, 1, and 2.
There is a fair amount of complexity wrapped up in these modes, so we're only going to attempt to run through the most important pieces. Instead of operating on individual pixels, the GBA's first three video modes operate on tiles. A tile is an 8x8 bitmap. These exist in 4 and 8 bits per pixel (bpp) variants, but here we'll be using the 4bpp type — thus, the tiles we'll be using have a size of 32 bytes (8 * 8 * 4 = 256 bits).
If you're wondering how we're supposed to fit 15-bit colour values for each pixel in 4 (or 8) bits, we don't. Instead of referring directly to colours, the pixel values in tiles refer to colours within a particular colour palette. We can define colour palettes by writing colour values into the colour palette memory we mentioned earlier (0x05000000
). This can store 512 sets of 16 bits (hence, 512 colours), which essentially means that we can store 2 palettes of 256 colours, or 32 palettes of 16 colours.
In the case of our 4bpp 8x8 tile bitmaps, we'll treat the colour palette memory as 32 palettes of 16 colours. This way, we can use our four bits for each pixel to specify the colour index (within some palette of 16 colours) for this pixel. When using tile-based video modes, tiles are sectioned in VRAM into 'tile blocks' or 'charblocks'. Each tile block is 16 KB in size, so we can fit 512 4bpp tiles in a tile block, and 6 tile blocks in VRAM.
The theoretical set of 6 tile blocks in VRAM are split into two groups — the first four (0–3) can be used for backgrounds, and the last two (4 and 5) can be used for sprites. Similarly, the 32 palettes of 16 colours in palette memory are split into 16 palettes for backgrounds, and 16 palettes for sprites. Since we're not going to deal with backgrounds in our game, we're only interested in tile blocks 4 and 5 in VRAM (i.e. those starting at addresses 0x6010000
and 0x6014000
), and colour palette block 1 (address 0x5000200
). So, say that we've loaded some tiles into tile block 4 — what can we do with this? Well, the whole point of us dealing with tiles in this case is to create sprites which use them.
A sprite, in Computer Graphics, is a 2D image that fits within a larger scene. It turns out that the GBA has hardware that can render 'objects' (i.e. sprites) for you, and these objects get rendered such that the object can move around without leaving a trail of modified pixels. Providing that objects are enabled (bit 13 in the display control I/O register is set), an object can be created from a particular set of tiles by writing the object's attributes into the GBA's Object Attribute Memory (OAM). In this case, as we're looking to make a 'pong'-esque game, we'll probably want at least two sprites: a paddle, and a ball. Any particular 'object' has three sets of 16-bit attributes:
- Attribute 0: includes, among other things, the y coordinate of the object, the shape of the object, and the colour mode of the object's tiles (4bpp or 8bpp).
- Attribute 1: includes, among other things, the x coordinate of the object, and the size of the object.
- Attribute 2: includes, among other things, the base tile index of the object, and the colour palette the object should use (when in 4bpp mode).
The specifics of these values can be viewed elsewhere, but essentially, the y coordinates are the lowest 8 bits of attribute 0, the x coordinates are the lowest 9 bits of attribute 1, and the colour mode defaults to 4bpp (i.e. zero = 4bpp).
The 'shape' and 'size' bits of an object define its form, and different combinations of these four bits result in different final shapes (entities more complex than this system are made up of multiple smaller objects). If an object should be larger than one tile in size, it will use different tiles for it's appearance depending on the mapping mode that is set (the 7th bit of the display control I/O register). It's easiest for us to use the 1D mapping mode, so if an object is bigger than one tile, it will fill itself using the tiles that follow its 'base tile' in memory.
With sprites explained, we're almost ready to start building. We want to use video mode 0 for this program, in which BG0–BG3 operate in 'regular' mode (we can't perform affine transformations on them). Now we just need to feed the input from the GBA's directional pad into some primitive physics code, put that all inside some sort of game loop, and we have ourselves a game!
The last pieces of this puzzle are both in I/O registers. The input state of the device can simply be read from the KEYINPUT I/O register (0x04000130
), and we can use the particulars of how this is laid out to create masks on this state to determine whether particular keys have been pressed. As for the game loop.. unfortunately, this requires one last piece of theory.
A typical game loop consists of a draw period, and an update period — in this case, we can't just choose when these occur ourselves though. If we decide to change what we want to display when the Game Boy is half way through drawing an object, we might get screen tearing (as half of the object was drawn with one set of data, and the other half with another). As a result, we need some way to synchronise our drawing and updating with the GBA's display refresh cycle.
arm-none-eabi-gcc -c main.c -mthumb-interwork -mthumb -O2 -o main.o
arm-none-eabi-gcc main.o -mthumb-interwork -mthumb -specs=gba.specs -o main.elf
arm-none-eabi-objcopy -v -O binary main.elf main.gba
gbafix main.gba
There are also some additional flags you might want to pass in for the first step (the compilation) — I'd recommend -fno-strict-aliasing
, for example, as we'll be dealing with raw memory and pointers a lot and don't really want C's strict aliasing rule to bite us. You might also find it beneficial to write a Makefile or shell script with these commands to make ROM compilation easier — these details seem a little unnecessary to include in this post though.
With a process for ROM compilation from C established, let's try and test it out. To throw a bone to any readers starting to drift off from all the theory, let's look at the code for our 'Hello, World' GBA ROM first, and then discuss it afterwards.
The code above is relatively simple, and should result in a horizontal set of three pixels being drawn in the middle of the GBA screen — one red, one green, and one blue:
Now, time to explain the code. Firstly, we write some display parameters to the memory mapped I/O registers — in particular, the first 16 bits of this memory is a display control register (often called DISPCNT). The first three bits of this register indicate the video mode, and the 11th bit indicates whether background #2 (BG2) is enabled or not — thus, by writing the values we do, we set the video mode to mode 3, and enable BG2.
Why do we need to do this? Well, first off the video mode. It turns out that video mode 3 is a mode in which we can write bitmap data into VRAM, and BG2 will display this bitmap (hence, why we also want to enable BG2). You might also be wondering why I've chosen to use video mode 3 rather than another video mode — the reason for this is that video modes 0 to 2 are much more difficult to explain (though we'll be using them later).
As I mentioned earlier, the LCD on the GBA can display 15-bit colours. Therefore, we can express GBA colours using a 15-bit long colour format — for data alignment reasons though, the GBA uses a 16-bit colour format. Specifically, the format is as follows: ?BBBBBGGGGGRRRRR
. So that's an unused bit, followed by five bits of blue, five bits of green, and then five bits of red.
Using this format, and with knowledge of how video mode 3 treats VRAM as a 240x160 bitmap, our 'Hello, World' ROM simply writes some colour values at specific pixel offsets (e.g. as we're assuming that unsigned short
is 16-bits in size, vram[80*240 + 120]
skips 80 horizontal lines of 240 pixels, and then accesses the middle pixel on that horizontal line). Note, by the way, that all the memory accesses for interfacing with hardware in the code occur through volatile
pointers — this prevents the compiler optimising out what it might think are useless memory operations.
Writing a pong-esque game
With the basics out of the way, let's try and build something a little more interesting. We're still going to hack the solution together rather than building a bunch of project infrastructure and helper functions, but we'll make use of some more advanced features of the GBA's graphics rendering. In particular, we'll depart from drawing using the bitmap video modes.
While drawing in the GBA's bitmap video modes (modes 3, 4, and 5) is very easy, for many games it's not really practical. Our 240x160 bitmap itself takes up the majority of VRAM just to fill the screen once, and pushing around so many pixels every frame can be computationally expensive too (we might not be able to afford this we're aiming to render our game at a reasonable framerate). Thus, we have video modes 0, 1, and 2.
There is a fair amount of complexity wrapped up in these modes, so we're only going to attempt to run through the most important pieces. Instead of operating on individual pixels, the GBA's first three video modes operate on tiles. A tile is an 8x8 bitmap. These exist in 4 and 8 bits per pixel (bpp) variants, but here we'll be using the 4bpp type — thus, the tiles we'll be using have a size of 32 bytes (8 * 8 * 4 = 256 bits).
If you're wondering how we're supposed to fit 15-bit colour values for each pixel in 4 (or 8) bits, we don't. Instead of referring directly to colours, the pixel values in tiles refer to colours within a particular colour palette. We can define colour palettes by writing colour values into the colour palette memory we mentioned earlier (0x05000000
). This can store 512 sets of 16 bits (hence, 512 colours), which essentially means that we can store 2 palettes of 256 colours, or 32 palettes of 16 colours.
In the case of our 4bpp 8x8 tile bitmaps, we'll treat the colour palette memory as 32 palettes of 16 colours. This way, we can use our four bits for each pixel to specify the colour index (within some palette of 16 colours) for this pixel. When using tile-based video modes, tiles are sectioned in VRAM into 'tile blocks' or 'charblocks'. Each tile block is 16 KB in size, so we can fit 512 4bpp tiles in a tile block, and 6 tile blocks in VRAM.
The theoretical set of 6 tile blocks in VRAM are split into two groups — the first four (0–3) can be used for backgrounds, and the last two (4 and 5) can be used for sprites. Similarly, the 32 palettes of 16 colours in palette memory are split into 16 palettes for backgrounds, and 16 palettes for sprites. Since we're not going to deal with backgrounds in our game, we're only interested in tile blocks 4 and 5 in VRAM (i.e. those starting at addresses 0x6010000
and 0x6014000
), and colour palette block 1 (address 0x5000200
). So, say that we've loaded some tiles into tile block 4 — what can we do with this? Well, the whole point of us dealing with tiles in this case is to create sprites which use them.
A sprite, in Computer Graphics, is a 2D image that fits within a larger scene. It turns out that the GBA has hardware that can render 'objects' (i.e. sprites) for you, and these objects get rendered such that the object can move around without leaving a trail of modified pixels. Providing that objects are enabled (bit 13 in the display control I/O register is set), an object can be created from a particular set of tiles by writing the object's attributes into the GBA's Object Attribute Memory (OAM). In this case, as we're looking to make a 'pong'-esque game, we'll probably want at least two sprites: a paddle, and a ball. Any particular 'object' has three sets of 16-bit attributes:
- Attribute 0: includes, among other things, the y coordinate of the object, the shape of the object, and the colour mode of the object's tiles (4bpp or 8bpp).
- Attribute 1: includes, among other things, the x coordinate of the object, and the size of the object.
- Attribute 2: includes, among other things, the base tile index of the object, and the colour palette the object should use (when in 4bpp mode).
The specifics of these values can be viewed elsewhere, but essentially, the y coordinates are the lowest 8 bits of attribute 0, the x coordinates are the lowest 9 bits of attribute 1, and the colour mode defaults to 4bpp (i.e. zero = 4bpp).
The 'shape' and 'size' bits of an object define its form, and different combinations of these four bits result in different final shapes (entities more complex than this system are made up of multiple smaller objects). If an object should be larger than one tile in size, it will use different tiles for it's appearance depending on the mapping mode that is set (the 7th bit of the display control I/O register). It's easiest for us to use the 1D mapping mode, so if an object is bigger than one tile, it will fill itself using the tiles that follow its 'base tile' in memory.
With sprites explained, we're almost ready to start building. We want to use video mode 0 for this program, in which BG0–BG3 operate in 'regular' mode (we can't perform affine transformations on them). Now we just need to feed the input from the GBA's directional pad into some primitive physics code, put that all inside some sort of game loop, and we have ourselves a game!
The last pieces of this puzzle are both in I/O registers. The input state of the device can simply be read from the KEYINPUT I/O register (0x04000130
), and we can use the particulars of how this is laid out to create masks on this state to determine whether particular keys have been pressed. As for the game loop.. unfortunately, this requires one last piece of theory.
A typical game loop consists of a draw period, and an update period — in this case, we can't just choose when these occur ourselves though. If we decide to change what we want to display when the Game Boy is half way through drawing an object, we might get screen tearing (as half of the object was drawn with one set of data, and the other half with another). As a result, we need some way to synchronise our drawing and updating with the GBA's display refresh cycle.
Gameboy Intro Generator
The device gives us a little time to update after every horizontal line (or 'scanline') that it draws, but gives us even more time (around 5ms) after it's finished drawing to the whole screen. In this case, we'll just use the time available after drawing to the entire screen to do our updates — this period is called a 'V-Blank' (as opposed to a 'V-Draw', when the screen is still being drawn to vertically).
To check how far the device has drawn vertically at current, we can check the 8 bit value in the VCOUNT I/O register (at 0x04000006
), which continues increasing during the V-Blank as if scanlines were still being drawn (thus, has a range from 0 to to 227). If the count is greater than or equal to 160, we're in a V-Blank. Thus, if we wait for a V-Draw to end before we begin the 'update' stage of our game loop, we have a primitive form of synchronisation.
With this synchronisation, we finally have enough information to build our game. In this case, I've chosen to build a single-player pong-esque game (with extremely primitive physics), the commented source code of which follows.
And there we have it, our basic game is complete! Would it work on a real Game Boy Advance? Uhh, maybe. If I've made no mistakes, it should work properly, but it's entirely possible that I've messed up somewhere along the line.
Gameboy Advance Intro Maker
Conclusion
Gameboy Intro Maker
This post turned out to be a lot longer than I expected. There's a lot more to GBA development than is detailed in this post too. Like any platform, it has its interesting features and its quirks. If you'd like to know more about GBA development, or about any of the device specifics in this article — I found the following resources invaluable: Nintendo's AGB Programming Manual, GBATEK, CowBiteSpec, Tonc.