17

One of my favorite features of the Game Boy Advance growing up was the ability for some games to be played multiplayer with just one cartridge - the first player would have the cartridge and all the other Game Boys would download their side of the client from them. Wanting to replicate this myself, I later learned that this was accomplished with an external 256 KB RAM bank, starting at 0x02000000, that would hold a program that was sent to it via the cable.

However, bafflingly enough, I can't seem to find any resources about how to actually send such a program over the cable. The official programmer's guide from Nintendo is full of information about the different communications modes available to it (multiplayer link, UART, JOY BUS, etc.) and how to operate each of them, but it gives no indication of what communication mode you'd have to use and what bits you would have to send in order to get another Game Boy at the boot screen to start receiving a program. Most of the online resources I've consulted have been similar - lots of information about communication modes, but nothing filled in about the protocol. The most I've been able to find are sources calling it "multiboot", and one piece of ARM MBED source code that runs through the protocol via an ARM microcontroller board.

I know that it's been done, since hobbyists have been doing it for decades, so is there any definitive, general information on how to do it posted anywhere?

2
  • Can you describe more what kind of answer you want? I looked at the code and in general it is quite simple, the code fully describes and documents how it is done. It just sends the commands and the executable payload over a synchronous serial interface. The payload is just transmitted in slightly obfuscated form based on a key value coming from the receiving unit.
    – Justme
    Apr 7, 2020 at 15:33
  • @Justme I'm looking for documentation with the same style and clarity as the other extant documentation on Game Boy Advance functions, going over things like acceptable ranges, edge cases, etc. If that doesn't exist, then I would accept a code sample of doing this from a host GBA.
    – TheHans255
    Apr 7, 2020 at 15:43

1 Answer 1

11

According to the documentation for the no$ emulators (https://www.problemkaputt.de/gbatek.htm), the core of the GBA's multiboot functionality revolves a BIOS syscall, SWI 0x25. The GBA's boot firmware will naturally look for and start communicating on any connection mode it supports (be it the normal serial mode, MultiPlay mode, or JOYBUS mode) - after the host GBA establishes a communcation session and sends handshake and header data, this syscall will send the multiboot payload to each connected GBA, verify with a CRC checksum, and begin execution.

Payload Structure

The multiboot payload resides at 0x02000000 on the client GBA, which is the location corresponding to 256KB of external work RAM. The first 192 bytes are the same as a cartridge payload (entry point, Nintendo logo, game title, etc.), followed by these entries specific to multiboot payloads:

Address Bytes Label Comment
0x0c0 4 RAM Entry Point CPU will begin execution here if mulitboot was installed from another GBA
0x0c4 1 Boot Mode Leave at 0 - the BIOS will overwrite this depending on how the payload was downloaded
0x0c5 1 Client ID number Leave at 0 - the BIOS will overwrite this with the player number minus 1 (e.g. player 4 will get 0x03 here)
0x0c6 26 Not used
0x0e0 4 JOYBUS Entry Point CPU will begin execution here if multiboot was installed via JOYBUS

As with the cartridge header, these entry points should be 32-bit ARM branch instructions leading to the actual start of the code (e.g. B ram_start). Execution in general should be relative to 0x02000000 instead of 0x08000000 as it is when executing from cartridge (you will likely compile the Multiboot payload separately and include the binary in your cartridge)

The cartridge header portion is not particularly special - in fact, since the cartridge header and remaining payload are sent in separate steps, you can plan on just sending the actual cartridge header here. The ROM entry point, normally found at the start of the cartridge header, will be ignored during multiboot.

Transfer Protocol

The latter half of the multiboot protocol (sending the payload and executing it) are handled by the SWI 0x25 syscall. The first half (initiating the session and sending the cartridge header) must be handled by the session host:

  1. Prepare a "Multiboot Parameter Structure" in RAM. It should be 0x4c bytes long and contain the following fields at these addresses:
Address Bytes Label Comment
0x14 1 handshake_data Will be filled in later
0x19 3 client_data Will be filled in later using random data send by the clients
0x1c 1 palette_data Data controlling the color of the Nintendo logo on client devices - of the form 0b1CCCDSS1, where C=color, D=direction, and S=speed
0x1e 1 client_bit Will be filled in with each of bits 1-3 set if client 1-3 is detected
0x20 4 boot_srcp The start address of the payload after the cartridge header
0x24 4 boot_endp The exclusive end address of the payload. The total transfer length should be a multiple of 0x10, with a minimum of 0x100 and a maximum of 0x3ff40
  1. Initiate a multiplayer communication session, using either Normal mode for a single client or MultiPlay mode for multiple clients. This is accomplished using the RCNT and SIOCNT registers as with a multiplayer game session.

  2. Send the word 0x6200 repeatedly until all detected clients respond with 0x720X, where X is their client number (1-3). If they fail to do this after 16 tries, delay 1/16s and go back to step 2.

    • Note that throughout this process, any clients that are not connected will always respond with 0xFFFF - be sure to ignore them.
  3. Fill in client_bit in the multiboot parameter structure (with bits 1-3 set according to which clients responded). Send the word 0x610Y, where Y is that same set of set bits.

  4. Send the cartridge header, 16 bits at a time, in little endian order. After each 16-bit send, the clients will respond with 0xNN0X, where NN is the number of words remaining and X is the client number. (Note that if transferring in the single-client 32-bit mode, you still need to send only 16 bits at a time).

  5. Send 0x6200, followed by 0x620Y again

  6. Send 0x63PP repeatedly, where PP is the palette_data you have picked earlier. Do this until the clients respond with 0x73CC, where CC is a random byte. Store these bytes in client_data in the parameter structure.

  7. Calculate the handshake_data byte and store it in the parameter structure. This should be calculated as 0x11 + the sum of the three client_data bytes. Send 0x64HH, where HH is the handshake_data.

  8. Call SWI 0x25, with r0 set to the address of the multiboot parameter structure and r1 set to the communication mode (0 for normal, 1 for MultiPlay).

  9. Upon return, r0 will be either 0 for success, or 1 for failure. If successful, all clients have received the multiboot program successfully and are now executing it - you can begin either further data transfer or a multiplayer game from here.

Code sample on Github using the Rust GBA crate: https://github.com/TheHans255/rust-gba-multiboot-test.

Reference:

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .