00-BF = standard ROM header (see GBATEK)
C0-FB = ARM code infinite loop; sets up control registers and maintains a pointer at
(IRAM) 7FFC to the user interrupt handler (this is standard boot-up stuff for GBA) and branches to
a50 in THUMB mode (game code entry point, I guess)
FC-227 = ARM code for handling interrupts. It scans to find the highest priority interrupt request, looks up the corresponding handler in the interrupt vector table (stored at
(IRAM) 28e0), calls it, and clears the request flags. If a certain interrupt flag is set (presumably is intended never to happen), it will go into a tight infinite loop (an instruction that branches to itself) - this check appears twice in the code.
228-9fb = Miscellaneous ARM code that gets copied to IRAM early on and is normally run from IRAM. The copying is done because the data bus to the ROM is only 16 bits wide, which makes it slow to run ARM code directly from the ROM. Of course, there is limited IRAM space, so only a few key routines get copied this way.
9fc = first THUMB code in the ROM
a50 = THUMB entry point
11b0 = copies values from IRAM (around
(IRAM) 2870-28d8) into LCD I/O registers
1348 = returns one of
(IRAM) 2888 - mapped to the LCD I/O registers for BG Control (see GBATEK).
1398 = return ‘character base block’ for the specified BG layer, in bytes
13cc = (r1 -
1398(r0)) / 32?
1400 = return ‘screen base block’ for the specified BG layer, in bytes
1434 = set ‘character base block’ for the specified BG layer, specified in bytes
It seems to go on like this for a while… these routines are terribly unoptimized btw, playing silly games with the stack pointer for no reason.
427c = Copies the
228-9fb code block to IRAM, at
(IRAM) 2f40-3713. Also sets up a few pointers in IRAM to some of those routines:
(IRAM) 2f30points to the routine copied from
(IRAM) 3940points to the routine copied from
6c0(text Huffman decompression);
(IRAM) 2920points to the routine copied from
(IRAM) 3944points to the routine copied from
(IRAM) 4150points to the routine copied from
(IRAM) 2918points to the routine copied from
Next come some (debugging?) wrappers for those routines. Each of these saves the input parameters on the stack, dereferences the appropriate IRAM pointer set up above in order to find the IRAM copy of the code, longcalls to the routine using the “bx ladder” (which also switches to ARM mode), then cleans up upon return. The stack values are never actually used for anything.
4338= Wrapper for
4364= Wrapper for
4388= Wrapper for
43b4= Wrapper for
43e0= Wrapper for
4408= Wrapper for
bfa73 are a handful of routines that wrap SWI instructions (BIOS calls):
bfa04- SWI 0xA (ArcTan2)
bfa08- SWI 0xE (BgAffineSet)
bfa0c- SWI 0xC (CpuFastSet)
bfa10- SWI 0xB (CpuSet)
bfa14- SWI 0x6 (Div)
bfa18- SWI 0x6 (Mod) *
bfa20- SWI 0x13 (HuffUnComp)
bfa24- SWI 0x12 (LZ77UnCompVram)
bfa28- SWI 0x11 (LZ77UnCompWram)
bfa2c- SWI 0x25 (Multiboot; transfer mode hard-coded to 1) (I guess Link Arena might use this?)
bfa34- SWI 0xF (ObjAffineSet)
bfa38- SWI 0x15 (RLUnCompVram)
bfa3C- SWI 0x14 (RLUnCompWram)
bfa40- Reset. Disable all interrupts (clear IME I/O reg); reset stack pointer to
(IRAM) 7f00; SWI 0x1 (RegisterRamReset); SWI 0x0 (SoftReset). The passed-in r0 will be forwarded to RegisterRamReset (see documentation). Note that (see GBATEK) a flag can be set ahead of time at
(IRAM) 7ffathat causes code execution to start at the beginning of WRAM instead of the beginning of the ROM o_O
bfa58- SWI 0x19 (adjust SOUNDBIAS gradually down to 0)
bfa60- SWI 0x19 (adjust SOUNDBIAS gradually up to 0x200)
bfa68- SWI 0x8 (Sqrt)
bfa6c- SWI 0x5 (VBlankIntrWait) - sets r2 to 0 first, but the SWI doesn’t seem to care?
Note: The Div SWI normally returns r0 / r1 in r0, and r0 % r1 in r1. However, the compiler is going to expect that only r0 contains meaningful information, so a separate ‘mod’ wrapper is provided that calls the SWI and then copies r1 to r0. (Div also puts abs(r0 / r1) in r3…)
The SOUNDBIAS thing… you kinda have to read GBATEK, and also understand a thing or two about how audio waveforms work.
bfa74 appears to be for dealing with save data; it configures a control register (so that the wait state for SRAM access is the maximum 8 cycles), then copies a specified (r2) number of bytes from r0 to r1.
bfab4 is identical; I can only assume that one is intended for loading and the other for saving, or something.
bfaf4 compares values instead; if all the bytes match it returns 0, otherwise a pointer to the first mismatch.
bfc4c is the “bx ladder” used for longcalls. It’s just a sequence of
bx r0; nop; bx r1; nop; etc. for each register. I assume all y’all hackers know this already, but: the idea is that you load the target address of the code you’re calling into a register, and then BL to the corresponding instruction, in order to simulate the BLX opcode that’s missing on GBA. This allows for calling into code that’s currently in the IRAM, for example, or between code at the start of the ROM and code inserted near the end (too far for BL to reach on its own).
I don’t know why the NOPs are there; these instructions don’t need to be word-aligned, as far as I can tell - that’s only needed for BX PC (since ARM addresses need to be word-aligned), and there isn’t a BX PC in the ladder. I’m guessing they were just mindlessly inserted by an assembler/linker/compiler somewhere along the way to be safe.
The code seems to end at
c57ac, the last part before the end, we see a repeating
(THUMB) bx pc; nop; (ARM) b pattern; i.e. bl’ing to those addresses switches to ARM mode and routes to those ARM routines instead. The strange part is that they branch to the ROM versions of routines from the block that gets copied to IRAM! The mappings are
After this point seems to be data; there are a lot of apparent pointer values, but they don’t seem to be LDC’d anywhere. There’s also this weird patterned data (ascending, then descending halfword values) at
c5a48, that has a lot of references to it in the code.