I've broken this down into parts:

Development environment

Since I first need to be able to compile code for the GBA, the logical first step is to get one of the development systems installed. As I use GNU/Linux, I installed the GNU/Linux version of the DevKitAdv (for which, there's a Windows version available at their Download page. I have no idea how to install the Windows version, though. Apparently, there's also a Mac OS X version. Choose your OS, eh?

A few notes on the install of that, also. I don't know if the Windows version is subject to the same problems, but the GNU/Linux version requires an install of the crtls package, which contains to files, namely crt0.S and lnkscript. The first one needs to be compiled like this:

arm-agb-elf-as -o crt0.o crt0.S

That will make a crt0.o file, which is linked by the Makefile in my project directory. My Makefile looks like this:

GBA=arm-agb-elf-

OPTS=-nostartfiles -T lnkscript

all: foo.gba

foo.gba: foo.elf
        $(GBA)objcopy -O binary foo.elf foo.gba

foo.elf: foo.o crt0.o
        $(GBA)gcc $(OPTS) -o foo.elf crt0.o foo.o

foo.o: foo.c gba.h keys.h pic.h screenmodes.h
        $(GBA)gcc $(OPTS) -c -o foo.o foo.c

Now, if you've never used a Makefile, I'll explain that. The first line sets a variable GBA to the value 'arm-agb-elf-'. Next one sets an OPTS variable to '-nostartfiles -t lnkscript'. The next line says that if you want to make 'all', you need to have foo.gba made. Basically, this just means that if you type 'make all' it will make foo.gba. The next block says that to make foo.gba, you need to already have foo.elf, and once you do, run the command '$(GBA)objcopy -O binary foo.elf foo.gba' The dollar sign and parentheses expand to the value of the variable, so that first $(GBA)objcopy becomes arm-agb-elf-objcopy. From what I understand, objcopy will just copy out the actual code of foo.elf (which has a header and all sorts of extra data) into foo.gba, which is a valid gba rom.

The basic format of a Makefile is:

target: requirement requirement2 ...
[tab]command1
[tab]command2
[tab]...

(where target is the thing you want to make, requirement(s) are the files that need to be there before you can make it, and command(s) are the things to run once you have them.

Just getting code to run

References:
Good tutorial
Some tutorials

Okay, so far, I'm (somewhat) familiar with two of the six video modes the GBA has to offer. Not surprisingly, they're the most straightforward of the modes. Here's my code:foo. It includes the crtls package, the header files, and the Makefile. All you really need is a development environment.

I think my code is pretty easy to follow, so I'll leave that as is, and explain the header files.

To get a picture into this format, I used the GNU/Linux program Electric Eyes to make sure it was smaller that 240 pixels wide by 160 pixels tall, then saved it into .ppm format. If I start developing my Bitmap code again, I may have it so you can save it as .bmp from MS Paint, but I don't see that as very important to this project, at least not just yet. When I saved it as .ppm, it was 24 bit format, where I needed 15 bit format, so I wrote a program to convert from 24 bit ppm to 15 bit ppm, and then I wrote another program to read that 15 bit ppm and make it into a header file. All my .ppm programs can be downloaded here. I'll come up with a better (easier) way to do this later.

Video

Jeremy compiled a program for displaying text on the GBA screen. You can get it here. It uses screen mode 3, and uses a font I made a long time ago for the GNU/Linux console. It takes x and y coordinates, a foreground and background color, and prints a string on the screen in characters 8 pixels wide by 10 high. It's been very useful in debugging, since I can now print onscreen the values of variables as the code runs.

Audio

References:

More sound tutorials

I know that the GBA is capable of playing 44100 Hz sound samples in stereo, but it only supports 8-bit sound data. I suppose the sound output from the handheld game console won't be CD-quality then...

Jason W. has apparently got audio playing, but I haven't gotten any code from him yet.

MP3 decoding

References:
Tutorial on getting malloc() to work

Okay, I haven't actually done anything major with it yet. I just tried to compile it with the GBA compiler, just to see what would happen. It didn't seem to recognize exit(), free(), or read() and write(). This is not much of a problem, as exit() can be changed to return() with no problems, and with free(), I'm sure there's some sort of mechanism for malloc'ing on the GBA, and so there must be one for free'ing as well. I'll probably just have to change the name. update: I found a site that mentions it. I haven't read it yet, but it should prove valuable As for read() and write(), it sounds like it's trying to use system calls to read a file. This will just have to be rewritten to read memory instead of a file. It shouldn't be too hard at all. I'll worry more about getting the GBA audio hardware to work for now, though.

Great news, I just created a quick stub file (stubs.c) with the following contents:

int AgbMain(void){
   return(0);
}

int read(int fd, void *foo, int size);
int read(int fd, void *foo, int size){
   return size;
}

int write(int fd, void *foo, int size);
int write(int fd, void *foo, int size){
   return size;
}
It compiled just fine with the crt0.o and lnkscript files, no more complaining about malloc() or anything like that. The compiled mpglib.gba file is 128920 bytes (quite small, if you ask me), and I haven't even optimized it yet by removing the parts I don't need (16 bit decoding, downsampling, etc.) On the other hand, I haven't put in my code for anything, either. I am reasonably confident I can fit this whole bit o' code into 256kB, possibly on the controller chip itself. More info to come.

Okay, I looked a bit more into the code, and I'm really happy. The read() and write() operations I had to deal with are just there to copy memory from a buffer to stdin and stdout. This means that all I have to do is replace the read/write operations with memcpy operations in the 372 byte main.c file. How much more convenient can you get?

ROM format

Since I've left behind my model that uses external storage media to store the mp3 files, I must find some way of letting them coexist in the ROM itself. When I needed data in the code before, I've converted it into a header file defining an array, and then compiled it directly into the code as an array. Obviously, this is not the optimal way of attaching multiple files that may be several megabytes.

I respected the method used in PocketNES (a NES emulator for the gameboy advance), which is that the compiled executable is distributed, and a separate program is used to attach the files to the end of the executable. It's still not quite as nice as having separate files and a filesystem, but in theory, you could add/remove/reorder the files that are already attached, like a filesystem.

First, I had to get a pointer to point past the end of the file so that it would be pointing at the attached data once a file was attached. This was not obvious to me, and the best way of doing it that I could think of was to set a pointer to point at a sentinel value ( 0x11223344 is one I used), and then use another program to change that value so that it points at a memory location that an attached file will load at. Hmm... maybe an example is in order.

Say my compiled GBA rom file (program) is 1234 bytes in size ( 0x4d2 in hex ), and I want to attach a string (perhaps to display a message). I know if I attach the string directly to the end of the file, it will be at offset 0x4d2 relative to the beginning of the file. With a bit of tinkering (and excessive use of Jeremy's text-displaying code), I found that the ROM is loaded at offset 0x08000000 in the GBA memory map. So, the string value I want to display will be at 0x080004d2 when the program is run.

I wrote a program to scan the executable ROM image for the sentinel (0x11223344) and change it based on the file size (0x080004d2 in this example), being careful to check to be sure it was only found once, and various other things like that. Now, to access this, I set a pointer in the C code like so:

char *string = (char *) 0x11223344;

That works fine for a single bit of data, but I want a dynamic length list of files with varying sizes. I figured a doubly-linked list would be the way to go. I wrote a program that will create structures of the following form:

typedef struct romlist {
   struct romlist *prev;
   struct romlist *next;
   char file;
} romlist ;

Also, I decided it would be nice to know how many nodes were in the list without traversing it, so the code now uses two sentinel values. One is a short int signifying how many files were attached. The other is a pointer (type romlist *) to the first node in the list.

In order to make it easier to figure out where attached files began/ended when looking at a ROM with files already attached, I decided it would be nice to place breaks between the nodes in memory, so each node begins with four zero bytes (or a four-byte precision zero if you prefer). The pointers are set so that this is transparent when coding, but it should help figure out where files begin or end for a program that reads the rom after files have been attached.

I thought my layout was flawed somehow when I wrote the code and it didn't work, but since then, I've figured out that for some reason, when the GBA (emulator) loads the addresses from the linked list, it strips off the MSB, and I have to binary-or it with 0x08000000 to fix it. I'm not sure why, and I'm not sure if this happens on the actual GBA hardware, since Eric is borrowing it right now.

The current format of a ROM with two files attached follows:

offset			size	meaning
------			----	-------
0000			xxxx	ROM image as compiled by DevKitAdv (with sentinels 'fixed')
xxxx			2	Short int representing the number of files attached.
xxxx+2			4	Set to zero (reserved)
xxxx+6			4	Ptr to previous node (zero since it's the first)
xxxx+10			4	Ptr to next node (in this case, xxxx+14+yyyy+4 | 0x08000000)
xxxx+14			yyyy	Data from file 1
xxxx+14+yyyy		4	Set to zero (reserved)
xxxx+14+yyyy+4		4	Ptr to previous node (in this case, xxxx+6 | 0x08000000)
xxxx+14+yyyy+8		4	Ptr to next node (a dummy node, xxxx+14+yyyy+12+zzzz+4 | 0x08000000)
xxxx+14+yyyy+12		zzzz	Data from file 2
xxxx+14+yyyy+12+zzzz	4	Set to zero (reserved)
xxxx+14+yyyy+12+zzzz+4	4	Ptr to previous node ( xxxx+14+yyyy+4 | 0x08000000)
xxxx+14+yyyy+12+zzzz+8	4	Ptr to next node (zero since it's the last)
xxxx+14+yyyy+12+zzzz+12	1+	useless byte(s) so that the struct points at something