Skip to content

SWI 0x25 — MultiBoot

  • Entry: 0x000028CE (THUMB; jump-table value 0x000028CF, 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 MultiBootParam pointer 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).
  • r1 must be 0, 1 or 2.
  • No DMA channel may be active. The routine ORs the four DMAxCNT_H high 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:

c = c * 0x6F646573 + 1          (32-bit wrap)

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) - 0x34 is 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 received SIOMULTI0..3 words (0x40001200x4000126) 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 = 2 is an undocumented Normal-2 MHz variant.
  • There is a special path (0x03007FFB flag = 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 treats 0x02000000 as 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 → poly 0xC37B, CRC init 0xC387, XOR key 0x43202F2F; MultiPlay → poly 0xA517, CRC init 0xFFF8, XOR key 0x6465646F. The poly is not swapped. The bit-serial CRC core (0x2864) is for 32 bits: if ((crc^data)&1) crc = (crc>>1) ^ poly; else crc >>= 1.
  • Exact meaning of the (length/4) - 0x34 priming 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). The client_data bytes 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, and enc = 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.