Skip to content

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 over SIODATA32.
  • MultiPlay 16-bit (r1 = 1): 16-bit words over SIOMULTI, 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:

  1. Master sends 0x6202, reads slave reply. When the slave answers 0x7202 the link is up. (Some implementations repeat 0x6202 until 0x7202.)
  2. 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 in MultiBootParam.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:

prime = (main_data_length_in_bytes / 4) - 0x34        ; 0x34 = 52 words
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 )
Send 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

  1. Bring up SIO as master in Normal 32-bit mode; clock the GBA slave.
  2. Poll 0x6202 → wait for 0x7202; then 0x6102 → record client mask.
  3. Stream the 0xC0-byte header unencrypted (valid Nintendo logo + header checksum).
  4. Send 0x63pp palette command; capture 0x73cc; derive the shared seed + handshake_data exactly as the BIOS does. ⚠ (verify the byte math)
  5. Send the priming word (len/4 - 0x34).
  6. Stream the payload, per word: LCG-step the key, enc = m ^ c ^ ((-dest) ^ K), update CRC16 over plaintext, send, verify slave echo.
  7. 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/0xA517 maps to Normal/MultiPlay as stated~~ — confirmed by decompilation (2026-07-08): the BIOS selects Normal→0xC37B and MultiPlay→0xA517 in a single mode branch, alongside CRC inits 0xC387/0xFFF8 and XOR keys 0x43202F2F/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 (0x1089 vs 0x108B).