SWI 0x25 — MultiBoot¶
- Entry:
0x000028CE(THUMB; jump-table value0x000028CF, low bit = THUMB) - Status: verified-static (full BIOS decompilation 2026-07-08 pins the cipher/CRC/seed/token logic; the host-side protocol is hardware-proven via the working Pico multiboot; the SWI as GBA-master still needs a second GBA to exercise end-to-end)
Summary¶
Boots one or more slave GBAs over the link cable by downloading a program image into their EWRAM. This SWI runs on the MASTER / sender side — the GBA that already holds the program transmits it; the slaves are sitting in their own BIOS multiboot-wait loop acting as SIO slaves. (The task brief described it as the client/receiver; the code is unambiguously the sender — see Open questions.)
The caller must have already performed the early handshake in game code
(0x6202/0x7202 recognition, the 0xC0-byte header transfer, and the palette/seed
exchange) and filled in a MultiBootParam block. This SWI performs only the
encrypted main-data download followed by the final CRC handshake. It
encrypts each word with a Kawasedo LCG, drives the SIO registers to shift it to
the slave(s), accumulates a CRC16 over the plaintext, verifies the slave echoes
the expected handshake tokens, and finally exchanges the CRC. It returns 0 on
success or 1 on any failure.
Parameters¶
| Reg | In | Meaning |
|---|---|---|
| r0 | ptr | Pointer to a MultiBootParam structure in EWRAM/IWRAM (see layout below) |
| r1 | 0/1/2 | Transfer mode: 0 = Normal 32-bit @256 KHz, 1 = MultiPlay 16-bit, 2 = Normal 32-bit @2 MHz. Values > 2 return an error. |
Returns¶
| Reg | Out | Meaning |
|---|---|---|
| r0 | 0 | Transfer + CRC verification succeeded |
| r0 | 1 | Failure (bad parameters, DMA active, slave dropped out, or CRC mismatch) |
On exit the routine also writes the result code into the first five words of the
MultiBootParam block (offsets 0x00–0x13) and leaves its CRC/working fields
(0x38, 0x3C, 0x40) set to the result.
Clobbered registers¶
r0–r7, r12, lr are used. The pushed set (r1,r3–r7,lr) is restored; the routine also briefly switches to ARM system mode with IRQ+FIQ disabled (CPSR = 0xDF) during the transfer and restores the caller's mode on the way out.
MultiBootParam layout (inferred from field offsets touched)¶
Only fields actually read/written by this SWI are listed. Names in italics
match the conventional GBATEK field names; unnamed offsets are BIOS scratch.
The image is assumed to load at 0x02000000; the 0xC0-byte header occupies
0x02000000–0x020000BF, so main data lands at 0x020000C0 onward.
| Off | Size | Field | Role in this SWI |
|---|---|---|---|
| 0x00 | 4 | reserved1[0] | Working LCG seed c; overwritten with result on exit |
| 0x04 | 1 | (reserved) | Holds a handshake byte during transfer |
| 0x05–0x07 | 1×3 | (reserved) | Store the SIOMULTI1/2/3 slave responses (multiplay) |
| 0x08 | 1 | (scratch) | Client-select mask, derived from client_bit |
| 0x0C | 4 | (scratch) | Main-data byte length, later reused as the end pointer |
| 0x10 | 4 | (scratch) | Rolling source pointer |
| 0x12–0x13 | 1×2 | (scratch) | Used by the "already-multibooted" reboot edge case |
| 0x14 | 1 | handshake_data | Read; copied to 0x04 in multiplay mode |
| 0x18 | 4 | probe_count / client_data[] | High 3 bytes seed the encryption LCG |
| 0x1C | 1 | palette_data | Low byte of the encryption LCG seed |
| 0x1E | 1 | client_bit | Bit-field selecting which/how many slaves are present |
| 0x20 | 4 | boot_srcp | Start of MAIN data (immediately after the 0xC0 header) |
| 0x24 | 4 | boot_endp | End of MAIN data |
| 0x38 | 2 | (BIOS internal) | Running CRC16 accumulator |
| 0x3A | 2 | (BIOS internal) | Mode flag: 0 = multiplay, non-zero = normal |
| 0x3C | 2 | (BIOS internal) | Scratch |
| 0x3E | 2 | (BIOS internal) | CRC16 polynomial for this transfer |
| 0x40 | 4 | (BIOS internal) | Encryption XOR constant K |
Algorithm¶
1. Validation (any failure → return 1)¶
- The
MultiBootParampointer must lie in RAM; the BIOS checks that the block (pointer .. pointer+0xFF) and the boot source buffer fall inside a valid memory region (top byte stripped, range-tested against the 0x0?000000 space). r1must be 0, 1 or 2.- No DMA channel may be active. The routine ORs the four
DMAxCNT_Hhigh halfwords (0x40000BA,0x40000C6,0x40000D2,0x40000DE) and fails if bit 15 (DMA enable) is set on any channel.
2. Mode / constant setup¶
From r1 it computes the mode flag at param+0x3A (0 for multiplay, non-zero for
normal) and selects the SIO configuration and the three transfer constants:
| Mode (r1) | param+0x3A | SIOCNT written | CRC16 init | CRC16 poly | XOR const K |
|---|---|---|---|---|---|
| 0 Normal 256 KHz | 0x1089 | 0x1089 | 0xC387 | 0xC37B | 0x43202F2F |
| 2 Normal 2 MHz | 0x108B | 0x108B | 0xC387 | 0xC37B | 0x43202F2F |
| 1 MultiPlay | 0 | 0x2083 | 0xFFF8 | 0xA517 | 0x6465646F |
0x1089/0x108B select Normal-serial 32-bit mode (bit 12 set; bit 1 = 2 MHz clock
for r1=2). 0x2083 selects MultiPlay mode (bit 13 set) at 115200 baud.
The XOR constants and the LCG multiplier are literal slices of the ASCII string
"// Coded by Kawasedo" embedded at 0x2D34:
- K (normal) = 0x43202F2F = bytes "// C" region
- K (multiplay) = 0x6465646F = bytes "oded"
- LCG multiplier = 0x6F646573 = bytes "sedo" (the tail of "Kawasedo")
3. Encryption LCG (Kawasedo cipher)¶
A 32-bit rolling key c is seeded from the handshake results: the word at
param+0x18 with its lowest byte replaced by palette_data (param+0x1C). For each
outgoing word the key advances by a linear congruential step:
The plaintext word m at source address p is encrypted as:
dest = 0x020000C0 + (p - boot_srcp) ; where the word lands in the slave
enc = m XOR c XOR ( (-dest) XOR K )
i.e. the ciphertext depends on the running LCG key and on the destination address of that word, which prevents a slave from being fed a shifted image.
4. CRC16 over plaintext¶
A bit-serial CRC16 is accumulated over every plaintext word (32 iterations, LSB-first) using the mode's polynomial:
for each of 32 bits:
b = (crc XOR data) & 1
crc = crc >> 1
if b: crc = crc XOR poly ; poly = 0xC37B (normal) or 0xA517 (multiplay)
data = data >> 1
crc starts at 0xC387 (normal) or 0xFFF8 (multiplay).
5. Word-by-word transmission loop¶
- A priming word equal to
(main_length/4) - 0x34is sent first (tells the slave the payload size in words, biased by 52). - For each word: the plaintext is fed to the CRC, the ciphertext is computed,
the routine waits for the previous SIO transfer to finish (polls SIOCNT
Start/Busy bit 7), verifies the slave's echoed handshake byte, then writes the
ciphertext to
SIODATA32(0x4000120) /SIOMLT_SEND(0x400012A) and starts the transfer by writing SIOCNT (0x4000128). In multiplay mode the receivedSIOMULTI0..3words (0x4000120–0x4000126) are read back to confirm each slave. The source pointer advances by 4 bytes per word in normal mode and by 2 bytes per SIO exchange in multiplay mode (two 16-bit halves per word). - Between transfers a short spin delay (~150 loop iterations) is inserted so the slave has time to latch each word.
6. Final CRC handshake¶
After the last data word, the master signals end-of-data and exchanges the
computed 16-bit CRC with the slave using a small ASCII-token handshake
('e'=0x65, 'f'=0x66, 's'=0x73, 't'=0x74, 'u'=0x75 sequence the state
machine). The slave computes the same CRC over what it received; if the tokens
and CRC agree the master returns 0, otherwise 1.
Edge cases & known bugs¶
- DMA active → immediate failure (guards against corruption of the SIO FIFO).
- Slave stops echoing the expected handshake token mid-transfer → failure.
- CRC mismatch at the end → failure.
r1 > 2→ failure;r1 = 2is an undocumented Normal-2 MHz variant.- There is a special path (
0x03007FFBflag = 1) taken when the master itself is running from a multiboot image and certain param bytes are set: it patches param+0x12 and jumps into a routine that treats0x02000000as an entry point. This is a re-boot / chained-boot corner case, not the normal flow. - No length-zero guard was observed; a zero-length image would send only the priming word and immediately enter the CRC handshake (behaviour unverified).
Cycle count¶
TBD (hardware). Cost is dominated by the per-word loop: one 32-iteration CRC, one 32-bit multiply for the LCG, an SIO transfer (bounded by the link clock, 256 KHz / 2 MHz / 115200 baud), plus a ~150-cycle inter-word spin delay. For a full 256 KB image (~65536 words) this is many milliseconds and is link-clock bound, not CPU bound.
Open questions (need hardware verification)¶
- Master vs. client role: static analysis says this SWI is the sender (drives the clock, encrypts, sends). GBATEK also documents SWI 25h as the master-side call. The task brief's "client/receiver" framing appears inverted.
- ~~CRC poly ↔ mode mapping~~ — resolved by decompilation (2026-07-08).
At the once-per-session setup (near 0x2938) a single
if (mode==normal)selects, together and consistently: Normal → poly0xC37B, CRC init0xC387, XOR key0x43202F2F; MultiPlay → poly0xA517, CRC init0xFFF8, XOR key0x6465646F. The poly is not swapped. The bit-serial CRC core (0x2864) isfor 32 bits: if ((crc^data)&1) crc = (crc>>1) ^ poly; else crc >>= 1. - Exact meaning of the
(length/4) - 0x34priming bias and the extra 0x34-word book-keeping at the loop boundaries. - Final ASCII-token handshake — FSM extracted (0x2ED4+): the end tokens are
'e'=0x65 and'f'=0x66 (CRC-exchange markers), then't'=0x74 /'u'=0x75 emitted after the 16-bit CRC compare (fail / OK). Precise master-vs-slave emission order still needs a link-cable capture. - ~~The seed derivation~~ — confirmed (0x2956):
key = word@(param+0x18), then*(u8*)&key = *(u8*)(param+0x1C)(palette_data). Theclient_databytes at param+0x05/+0x06/+0x07 are loaded from the slave's reply halfwords (forced to 0xFF each in MultiPlay). Per-word the key steps by× 0x6F646573 + 1, andenc = plaintext ^ key ^ (−dest_addr) ^ XORkey— matching the working host.
GBATEK cross-reference¶
Agrees with GBATEK's SWI 25h (MultiBoot) description: same MultiBootParam
struct, same r1 modes, same "// Coded by Kawasedo" cipher, same CRC16 approach.
Adds concrete extracted constants: LCG multiplier 0x6F646573, CRC polynomials
0xC37B/0xA517, CRC seeds 0xC387/0xFFF8, XOR constants 0x43202F2F/
0x6465646F, the destination-address term 0x020000C0 + offset, the SIOCNT
values 0x1089/0x108B/0x2083, and the DMA-active precondition check.
See multiboot_protocol.md for the host-side view.