GBA Multiboot Protocol — Host / Master Perspective¶
Goal: describe everything a host (e.g. a Raspberry Pi Pico) must send and
receive over the link cable to boot a stock GBA that is sitting in its BIOS
multiboot-wait state. This synthesizes the sender behaviour of BIOS SWI 0x25
(swi_25_multiboot.md) plus the early handshake that a real cartridge game
performs in software before calling that SWI. The host performs both phases.
Status: draft. The encrypted-transfer and CRC math (Phase 4–5) are extracted directly from the BIOS and are authoritative. The early handshake framing (Phase 1–3) is reconstructed from the standard protocol and must be cross-checked against GBATEK's "Multiboot" chapter and a real hardware capture. Points needing verification are marked ⚠.
Roles and electrical setup¶
- The host is the MASTER: it supplies the serial clock and initiates every transfer. The GBA is the slave, clocked externally.
- All exchanges are full-duplex: every word the master shifts out simultaneously shifts a word in from the slave. The slave's reply to word N is what the master reads after transfer N completes.
- Two link modes are usable, matching SWI 0x25's
r1: - Normal 32-bit (
r1 = 0@256 KHz,r1 = 2@2 MHz): one 32-bit word per exchange overSIODATA32. - MultiPlay 16-bit (
r1 = 1): 16-bit words overSIOMULTI, up to 3 slaves. A single-slave Pico host will normally use Normal 32-bit.
Register cheat-sheet (as the GBA drives them; the Pico mirrors the wire)¶
| Addr | Name | Use |
|---|---|---|
| 0x4000120 | SIODATA32 (low) / SIOMULTI0 | 32-bit send data / slave-0 reply |
| 0x4000122 | SIODATA32 (high) / SIOMULTI1 | slave-1 reply (multiplay) |
| 0x4000124 | SIOMULTI2 | slave-2 reply (multiplay) |
| 0x4000126 | SIOMULTI3 | slave-3 reply (multiplay) |
| 0x4000128 | SIOCNT | mode / start / busy control |
| 0x400012A | SIODATA8 / SIOMLT_SEND | 8/16-bit send register |
On the wire the host just needs to shift 32-bit (or 16-bit) words with the GBA as SPI-like slave, MSB/word semantics per GBA SIO. The register names above are what the GBA side touches; the host reproduces the same word stream.
Phase 1 — Recognition handshake ⚠ (game-side, reconstructed)¶
The slave GBA, in BIOS multiboot-wait, repeatedly replies with a fixed value. The master polls until the link is recognised:
- Master sends
0x6202, reads slave reply. When the slave answers0x7202the link is up. (Some implementations repeat0x6202until0x7202.) - Master sends
0x6102; the slave replies with a word whose low byte encodes which client slots are present (bits 1–3). The master records a client mask — this is the value that ends up inMultiBootParam.client_bit(offset 0x1E) and drives which slaves are addressed.
⚠ Verify exact magic words and the client-bit encoding against GBATEK.
Phase 2 — Header transfer (0xC0 bytes) ⚠¶
The master sends the first 0xC0 (192) bytes of the multiboot image — the
cartridge/ROM header (entry branch, Nintendo logo, title, fixed value 0x96,
etc.). This is sent unencrypted, as 0x60 halfword sends (multiplay) or 0x30
word sends (normal). The slave BIOS copies these bytes to 0x02000000.
- The header must contain a valid Nintendo logo and the header checksum byte, exactly like a normal cartridge header, or the slave BIOS rejects it.
- Main data begins at image offset 0xC0, landing at slave address
0x020000C0.
Phase 3 — Palette / seed exchange ⚠¶
The master sends a palette command 0x63pp, where pp is a palette byte in
the range 0x81–0xC1 (bit7 set; bits 0–6 = a palette index used for the boot
animation colour). The slave replies with 0x73cc, where cc is a byte the
master keeps.
From these exchanged bytes the two sides derive the shared quantities the
BIOS uses (seed math confirmed by decompilation, 2026-07-08 — see below):
- palette_data (MultiBootParam offset 0x1C) = the palette byte pp.
- client_data[] / handshake_data (offsets 0x18–0x1B, 0x14) = bytes computed
from the slave's cc reply. These become the encryption LCG seed.
The BIOS seeds its rolling key c from the 32-bit value at param+0x18 with its
lowest byte overwritten by palette_data (param+0x1C) — confirmed at 0x2956:
key = *(u32*)(param+0x18); *(u8*)&key = *(u8*)(param+0x1C). The client_data
bytes (param+0x05/+0x06/+0x07) are copied from the slave's reply halfwords, and
are each forced to 0xFF in MultiPlay mode (0x2990). The host's
m = 0xFFFF0000 | (cc<<8) | pp reproduces exactly this seed.
The master also sends a handshake byte (MultiBootParam.handshake_data,
offset 0x14) that the slave echoes to prove synchronisation before the main
transfer starts.
Phase 4 — Encrypted main transfer (BIOS-authoritative)¶
This is what BIOS SWI 0x25 does; the host reproduces it exactly.
Constants (extracted from the BIOS)¶
The cipher and CRC constants are literal slices of the ASCII string
"// Coded by Kawasedo" stored in the BIOS:
| Mode | LCG multiplier | XOR const K |
CRC16 init | CRC16 poly |
|---|---|---|---|---|
| Normal 32-bit | 0x6F646573 | 0x43202F2F | 0xC387 | 0xC37B |
| MultiPlay 16-bit | 0x6F646573 | 0x6465646F | 0xFFF8 | 0xA517 |
Priming word¶
Before the payload, the master sends one word:
This tells the slave the payload size (in words, biased by 52).main_data_length
= (boot_endp - boot_srcp) rounded down to a multiple of 8 (mask 0x3FFF8);
the maximum image size is 256 KB.
Per-word encryption¶
Maintain a 32-bit rolling key c (seeded in Phase 3). For each plaintext word
m located at source offset off = p - boot_srcp (so it lands at slave address
dest = 0x020000C0 + off):
c = (c * 0x6F646573 + 1) & 0xFFFFFFFF ; LCG step, done BEFORE each word
enc = m XOR c XOR ( ( (-dest) & 0xFFFFFFFF ) XOR K )
enc. Binding the ciphertext to dest means the words are position-locked;
they cannot be reordered or shifted.
CRC accumulation (over PLAINTEXT)¶
Accumulate a bit-serial CRC16 over each plaintext m (not the ciphertext):
crc = init ; 0xC387 or 0xFFF8
for each word m:
d = m
repeat 32 times:
b = (crc XOR d) & 1
crc = crc >> 1
if b: crc ^= poly ; 0xC37B or 0xA517
d = d >> 1
Transfer loop bookkeeping¶
- Wait for each SIO transfer's Start/Busy bit to clear before starting the next.
- After each word, read the slave's reply and confirm it echoes the expected handshake token; a wrong or missing echo aborts the boot.
- Insert a short delay (~150 CPU cycles on the GBA; a few µs) between words so the slave can latch each one. On a Pico, pace transfers to the slave's ability to keep up rather than free-running the clock.
- Advance the source pointer by 4 bytes/word (normal) or by 2 bytes per 16-bit SIO exchange (multiplay).
Phase 5 — Final CRC handshake (BIOS-authoritative math, ⚠ token order)¶
After the last data word:
1. The master signals end-of-data and both sides exchange their computed 16-bit
crc. The slave has run the identical CRC over the data it received.
2. A short ASCII-token exchange sequences the finish. The BIOS uses the bytes
'e'=0x65, 'f'=0x66, 's'=0x73, 't'=0x74, 'u'=0x75 as state markers.
3. If the tokens and the 16-bit CRC agree, the slave jumps to the downloaded
image (entry at 0x02000000, the header's branch) and SWI 0x25 returns 0 on
the master. Any mismatch returns 1.
⚠ The exact ordering (which side emits 'e'/'f'/'s'/'t'/'u' first, and whether
the CRC is sent low-half-then-high-half) needs a real link capture to pin down.
Minimum host (Pico) checklist¶
- Bring up SIO as master in Normal 32-bit mode; clock the GBA slave.
- Poll
0x6202→ wait for0x7202; then0x6102→ record client mask. - Stream the 0xC0-byte header unencrypted (valid Nintendo logo + header checksum).
- Send
0x63pppalette command; capture0x73cc; derive the shared seed +handshake_dataexactly as the BIOS does. ⚠ (verify the byte math) - Send the priming word
(len/4 - 0x34). - Stream the payload, per word: LCG-step the key,
enc = m ^ c ^ ((-dest) ^ K), update CRC16 over plaintext, send, verify slave echo. - Exchange the final CRC + ASCII tokens; success if they match.
Cross-check list before trusting on hardware¶
- Phase 1–3 magic words / seed derivation vs GBATEK "Multiboot".
- ~~Whether CRC poly
0xC37B/0xA517maps to Normal/MultiPlay as stated~~ — confirmed by decompilation (2026-07-08): the BIOS selects Normal→0xC37Band MultiPlay→0xA517in a single mode branch, alongside CRC inits0xC387/0xFFF8and XOR keys0x43202F2F/0x6465646F. Not swapped. - The
-dest(two's-complement of the destination address) term in the cipher — confirm on hardware that a mis-set base address breaks the boot as predicted. - Exact SIOCNT bit meanings for the clock-speed selection (
0x1089vs0x108B).