Hello,
I've been interested into GBA game development because it's a fairly interesting device to work with. It features an ARM cpu with TDMI extensions.
Here are the steps to begin with:
1. You need a GCC toolchain.
I've been trying to make it work with LLVM too but it looks like Clang does not support GCC's linker script extensions and directives, which are required to build the CRT0 object.
You need to do this because the AGB system does not have a runtime.
At the time, games were mostly written in pure assembly or C without the standard library.
Building binutils
I used latest binutils (2.36.1 at the moment of writing this thread) and used this configuration:
# Assuming we're in build/ directory
../configure --target=arm-none-eabi --program-prefix=gba-
In older GCC (prior to 4.8) you would use arm-agb-elf
as target.
As you can guess, this is an embedded SoC device, so eabi
is fine.
Building GCC and GDC
You need both GCC and GDC.
To build them we need to disable a lot of stuff and use newlib instead of the standard glibc (since it is an embedded device).
Make sure you have libmpc, limpfr and libgmp installed in your system before building.
I used latest GCC (11.1.0) and configured it like this:
# Assuming we're in build/ directory
../configure \
--target=arm-none-eabi \
--program-prefix=gba- \
--enable-languages=c,d \
--with-newlib \
--with-multilib-list=rmprofile \
--disable-decimal-float \
--disable-libffi \
--disable-libgomp \
--disable-libmudflap \
--disable-libquadmath \
--disable-libssp \
--disable-libstdcxx-pch \
--disable-nls \
--disable-shared \
--disable-threads \
--disable-tls
Notice the line --with-multilib-list=rmprofile
: without this line, the libssp needed for the build of the host toolchain would fail.
Reference: https://forums.gentoo.org/viewtopic-t-1077292-start-0.html
Building newlib
Since we specified a custom program prefix (gba-
in my case) we need to set the required environment variables so that the configure script of newlib does not break.
I used latest newlib (4.1.0).
# Assuming we're in build/ directory
CC_FOR_TARGET=/absolute/path/to/gba-gcc \
GCC_FOR_TARGET=/absolute/path/to/gba-gcc \
AR_FOR_TARGET=/absolute/path/to/gba-ar \
AS_FOR_TARGET=/absolute/path/to/gba-as \
LD_FOR_TARGET=/absolute/path/to/gba-ld \
NM_FOR_TARGET=/absolute/path/to/gba-nm \
OBJCOPY_FOR_TARGET=/absolute/path/to/gba-objcopy \
OBJDUMP_FOR_TARGET=/absolute/path/to/gba-objdump \
RANLIB_FOR_TARGET=/absolute/path/to/gba-ranlib \
READELF_FOR_TARGET=/absolute/path/to/gba-readelf \
STRIP_FOR_TARGET=/absolute/path/to/gba-strip \
../configure \
--target=arm-none-eabi \
--program-prefix=gba-
2. Set up your build script
The required steps for this are simple.
Use an updated crt0
Versions of Jeff Frohwein's crt0.s prior to 1.28 do not seem to work with latest GCC.
If you're using GCC prior to 4.8 older versions should work too.
Here is the referenced crt0.s
Use an updated linker script
Same as the previous: versions prior to 1.3 do not seem to work with latest GCC.
Here is the referenced linker script
Build chain
We need to produce the object files and then link them together with GCC.
Here is a simple hello world program that paints the whole screen with red:
@nogc:
enum VRAM = cast(ushort*) 0x6000000;
enum SCREEN_WIDTH = 240;
enum SCREEN_HEIGHT = 160;
enum FRAME_SEL_BIT = 0x10;
enum BG2_ENABLE = 0x400;
enum REG_DISP_CTL = cast(ushort*) 0x4000000;
extern(C) int main() {
int i;
*REG_DISP_CTL = 3 | BG2_ENABLE;
*REG_DISP_CTL &= ~FRAME_SEL_BIT;
for(i = 0; i < SCREEN_WIDTH * SCREEN_HEIGHT; i++) {
VRAM[i] = 31;
}
for(;;) { }
return 0;
}
The example was written after this C example:
#include <stdint.h>
uint16_t *fb = (void*)0x6000000;
const int xsz = 240;
const int ysz = 160;
#define FRAME_SEL_BIT 0x10
#define BG2_ENABLE 0x400
int main(void) {
int i;
static volatile uint16_t * const reg_disp_ctl = (void*)0x4000000;
*reg_disp_ctl = 3 | BG2_ENABLE;
*reg_disp_ctl &= ~FRAME_SEL_BIT;
for(i=0; i<xsz * ysz; i++) {
fb[i] = 31;
}
for(;;);
return 0;
}
Notice that _start
is already defined by crt0, which handles I/O initialization and other useful init routines (for example, stop sound if cartridge is removed).
Both main
and AgbMain
are valid entry points of the program.
First you need to build your D files.
Since we're excluding the D runtime, make sure you have at least an empty object.d in your project.
# You would use gba-gcc with the same options for .c files.
# The crt0 and the linker script seem to support C++ as well.
gba-gdc test.d \
-O3 \
-fomit-frame-pointer \
-marm \
-mcpu=arm7tdmi \
-fno-druntime \
-c \
-pedantic \
-Wall
As you can notice we don't need the D runtime here.
Then, assemble the crt0:
gba-as crt0.S -o crt0.o
Now that we have built the two object files, we can just link them together and produce the ELF binary; then we create the GBA image with objcopy.
gba-gcc \
-o out.elf crt0.o test.o \
-Tlscript.ld \
-nostartfiles \
-lm
gba-objcopy -O binary out.elf test.gba
At this point you can just load it into an emulator.
3. Optional steps
Edit game metadata
Game fields like title and author are specified by crt0.s but you need to update the checksum flag accordingly.
If you don't want to set them manually you can use gbafix.
4. Notice
At the moment, the binary is built correctly, but it does not seem to link correctly when using D.
Using GCC (for the C example above) instead of GDC with same options works so my guess is that the linker script needs to be updated.
I think I need help for this; do you have any suggestions?
Thank you in advance.