|
1 | | -# joypad-tester / gba |
2 | | - |
3 | | -GBA multiboot payloads that ship inside [joypad-tester](..). Two ROMs |
4 | | -build from this one source tree: |
5 | | - |
6 | | -- **`joypad_mb.gba`** — Doridian-style joybus controller + animated |
7 | | - cartoon eyes overlay. Consumed by |
8 | | - [joypad-os](https://github.com/joypad-ai/joypad-os) via submodule for |
9 | | - its GBA-as-controller mode. |
10 | | -- **`tester_mb.gba`** — Doridian-style joybus controller + an on-GBA |
11 | | - console showing live button state, with an idle screensaver that |
12 | | - bounces the joypad logo (color-cycling on each wall hit) à la the |
13 | | - GameCube tester. Embedded into joypad-tester's GameCube/Wii host so |
14 | | - testers see visual feedback on the GBA, and runnable standalone from |
15 | | - a flash cart as a pure button-tester ROM. |
16 | | - |
17 | | -The `build/` tree ships pre-built artifacts so consumers using this |
18 | | -folder as a submodule (e.g. joypad-os) don't need devkitARM unless |
19 | | -they're modifying the source: |
| 1 | +# Joypad Tester — Game Boy Advance |
| 2 | + |
| 3 | +Game Boy Advance build of the [Joypad Tester](../README.md). Two |
| 4 | +multiboot ROMs built from one source tree: one ships as the GBA-as- |
| 5 | +controller payload that joypad-os multiboots over the GameCube link |
| 6 | +cable, the other is a standalone tester ROM you can multiboot from this |
| 7 | +repo's GameCube/Wii host or run from a flash cart. |
| 8 | + |
| 9 | +## What it tests |
| 10 | + |
| 11 | +Each variant boots the same Doridian-style joybus controller loop and |
| 12 | +reports button state back to the host the moment a button changes. The |
| 13 | +two variants differ in what they render on the GBA screen: |
20 | 14 |
|
21 | 15 | ``` |
22 | | -build/joypad/joypad_mb.gba # eyes ROM (raw multiboot binary) |
23 | | -build/joypad/joypad_payload.c # (C array for embedding) |
24 | | -build/tester/tester_mb.gba # tester ROM (raw multiboot binary) |
25 | | -build/tester/tester_payload.c # (C array for embedding) |
| 16 | +joypad_mb.gba eyes overlay (cartoon eyes + emotion state machine) |
| 17 | +tester_mb.gba on-GBA text console + idle screensaver |
26 | 18 | ``` |
27 | 19 |
|
28 | | -Each `*_payload.c` exports the same symbol names: |
| 20 | +| Variant | On-GBA display | Intended host | |
| 21 | +|--------------|--------------------------------------------------------------------------------------------------|-------------------------------| |
| 22 | +| `joypad_mb` | Mode-4 framebuffer + cylinder-eye renderer + emotion state machine (port of joypad-os's `eyes_anim`) | [joypad-os](https://github.com/joypad-ai/joypad-os) (RP2040) | |
| 23 | +| `tester_mb` | 30×20 text console (live button state, 2-column layout) + idle screensaver (Mode-4 page-flipped) | This repo's GameCube/Wii host | |
29 | 24 |
|
30 | | -```c |
31 | | -extern const uint8_t joypad_payload[]; |
32 | | -extern const uint32_t joypad_payload_len; |
33 | | -``` |
| 25 | +Fields the GBA doesn't have (analog sticks, triggers, C-stick, rumble) |
| 26 | +don't exist on the wire — only the 10 face/dpad/shoulder/start/select |
| 27 | +buttons are reported. The 2-byte joybus payload carries them. |
34 | 28 |
|
35 | | -so a host can drop either file in and the embedding code doesn't have |
36 | | -to change. Pick the variant that fits your product. |
37 | | - |
38 | | -## Lineage |
39 | | - |
40 | | -The joybus handshake + main loop in `source/main.c` and |
41 | | -`source/main_tester.c` (the `0x30303030` exchange, status-bit polls, |
42 | | -SVC `0x26` BIOS reset on `JOYCNT.RST`) is taken from **Doridian's |
43 | | -[Joybus-PIO](https://github.com/Doridian/Joybus-PIO)** `gba/source/main.c` |
44 | | -(2023). That sequence is load-bearing for the cable's level-shifter MCU; |
45 | | -modifying it breaks the host's view of input. The eyes overlay and the |
46 | | -tester console run on top of it. |
47 | | - |
48 | | -Chain of credit: |
49 | | - |
50 | | -- **[gbatek](https://problemkaputt.de/gbatek.htm)** (Martin Korth) — |
51 | | - authoritative GBA hardware reference (SIO / Joybus / BIOS reset |
52 | | - semantics) |
53 | | -- **[libgbacom](https://github.com/Sage-of-Mirrors/libgbacom)** |
54 | | - (Sage-of-Mirrors, ported from VisualBoyAdvance) — original |
55 | | - reverse-engineering of the joyboot stream cipher |
56 | | -- **[gc-gba-link-cable-demo](https://github.com/FIX94/gc-gba-link-cable-demo)** |
57 | | - (FIX94) — canonical GameCube-side reference for booting a GBA over the |
58 | | - link cable |
59 | | -- **[Joybus-PIO](https://github.com/Doridian/Joybus-PIO)** (Doridian) — |
60 | | - RP2040-PIO implementation of the GameCube side, plus the GBA-side |
61 | | - controller payload these variants build on |
62 | | -- **This subtree** — eyes overlay (port of joypad-os's `eyes_anim`), |
63 | | - Mode-4 page-flipped screensaver matching the joypad-tester GC |
64 | | - variant's logo + colour cycle, two-build Makefile, robust joybus |
65 | | - reset handling (polls `JOYCNT.RST` inside the VBlank busy-wait so |
66 | | - host cmd 0xFF triggers `SystemCall(0x26)` within microseconds) |
| 29 | +## Joybus protocol |
67 | 30 |
|
68 | | -## Embedding |
| 31 | +The handshake + main loop is taken verbatim from |
| 32 | +[Doridian/Joybus-PIO](https://github.com/Doridian/Joybus-PIO)'s GBA |
| 33 | +side — `0x30303030` exchange, status-bit polls, SVC `0x26` BIOS reset |
| 34 | +on `JOYCNT.RST`. Modifying it breaks the cable's level-shifter MCU, so |
| 35 | +both variants share the same loop and only swap the on-screen renderer |
| 36 | +behind it. |
69 | 37 |
|
70 | | -Drop the appropriate `build/<variant>/<variant>_payload.c` into your |
71 | | -host firmware's source list, then feed `joypad_payload[]` / |
72 | | -`joypad_payload_len` to your joybus multiboot uploader. See |
73 | | -[joypad-os](https://github.com/joypad-ai/joypad-os)'s |
74 | | -`src/native/host/gc/gba_multiboot.c` or this repo's |
75 | | -[`../gamecube/ppc/gba.c`](../gamecube/ppc/gba.c) for reference uploader |
76 | | -implementations (Kawasedo handshake, stream cipher, polled WRITE/READ, |
77 | | -unconditional handshake-complete write). |
| 38 | +Hot-swap and host-reboot re-multiboot rely on noticing a host `cmd 0xFF` |
| 39 | +within a frame: `REG_JOYCNT.RST` is polled inside the VBlank busy-wait |
| 40 | +so any reset triggers `SystemCall(0x26)` within microseconds. |
| 41 | + |
| 42 | +## Eyes variant (`joypad_mb.gba`) |
| 43 | + |
| 44 | +Renders a pair of cartoon eyes (Mode-4 8bpp paletted framebuffer) whose |
| 45 | +gaze follows the dpad like a self-centering analog stick. An emotion |
| 46 | +state machine cycles `ACTIVE → wander → sleep` with per-emotion FG / |
| 47 | +pupil palette swaps. Designed to ship inside joypad-os as the GBA-as- |
| 48 | +controller "personality" layer — the buttons remain the product, the |
| 49 | +overlay just confirms the link is alive. |
78 | 50 |
|
79 | | -## Building (only if you change source) |
| 51 | +## Tester variant (`tester_mb.gba`) |
80 | 52 |
|
81 | | -Requires devkitPro / devkitARM: |
| 53 | +A 30-column × 20-row text console: |
82 | 54 |
|
83 | | -```bash |
84 | | -# macOS |
85 | | -brew install --cask devkitpro-pacman |
86 | | -sudo dkp-pacman -S gba-dev |
| 55 | +``` |
| 56 | +Joypad Tester — GBA |
| 57 | +GC Link: connected |
| 58 | +
|
| 59 | +Buttons: |
| 60 | + A:0 Start:0 |
| 61 | + B:0 Select:0 |
| 62 | + L:0 Up:0 |
| 63 | + R:0 Down:0 |
| 64 | + Left:0 |
| 65 | + Right:0 |
| 66 | +
|
| 67 | +Raw: 0000 |
| 68 | +``` |
87 | 69 |
|
88 | | -# Linux |
89 | | -# https://devkitpro.org/wiki/devkitPro_pacman |
| 70 | +Live indicators flip 0/1 as buttons change; `Raw: XXXX` is the hex |
| 71 | +joybus payload. After 30 seconds of no input, a Mode-4 page-flipped |
| 72 | +screensaver kicks in: the 64×54 joypad logo bounces off the screen |
| 73 | +edges, color-cycling through red / green / yellow / blue / magenta / |
| 74 | +cyan / white on every wall hit — same image, palette, and speed as the |
| 75 | +GameCube tester's idle screensaver, so testers see the same animation |
| 76 | +on both screens. |
90 | 77 |
|
91 | | -export DEVKITPRO=/opt/devkitpro |
| 78 | +If the host drops the joybus link, the variant falls back to standalone |
| 79 | +mode and the console keeps reporting button state directly from |
| 80 | +`REG_KEYINPUT` — useful as a pure flash-cart button tester. |
92 | 81 |
|
93 | | -make # build both variants |
94 | | -make joypad # build only the eyes ROM |
95 | | -make tester # build only the tester ROM |
| 82 | +## Build |
| 83 | + |
| 84 | +With devkitPro / devkitARM installed: |
| 85 | + |
| 86 | +``` |
| 87 | +make # → build/joypad/joypad_mb.gba + build/tester/tester_mb.gba |
| 88 | +make joypad # → build/joypad/joypad_mb.gba only |
| 89 | +make tester # → build/tester/tester_mb.gba only |
96 | 90 | make clean # nuke build/ |
97 | 91 | ``` |
98 | 92 |
|
99 | | -Outputs land in `build/joypad/` and `build/tester/`. Both `*_mb.gba` |
100 | | -and `*_payload.c` are committed; rebuild and commit them whenever you |
101 | | -change source. |
102 | | - |
103 | | -## Layout |
| 93 | +Without devkitPro, use the Docker image: |
104 | 94 |
|
105 | 95 | ``` |
106 | | -. |
107 | | -├── Makefile # two-variant build → build/<variant>/ |
108 | | -├── source/ |
109 | | -│ ├── main.c # eyes variant entry (joybus + eyes_anim) |
110 | | -│ ├── main_tester.c # tester variant entry (joybus + console |
111 | | -│ │ # + Mode-4 page-flipped screensaver) |
112 | | -│ ├── display.c/.h # Mode-4 framebuffer for the eyes variant |
113 | | -│ ├── eyes_anim.c/.h # cylinder-eye renderer + emotion state |
114 | | -│ │ # machine |
115 | | -│ └── platform/ |
116 | | -│ └── platform.h # stub for shared eyes_anim platform API |
117 | | -├── tools/ |
118 | | -│ └── bin2c.py # binary .gba → C array converter |
119 | | -└── build/ # committed pre-built payload artifacts |
120 | | - ├── joypad/ |
121 | | - │ ├── joypad_mb.gba |
122 | | - │ └── joypad_payload.c |
123 | | - └── tester/ |
124 | | - ├── tester_mb.gba |
125 | | - └── tester_payload.c |
| 96 | +docker run --rm -v "$PWD":/workspace -w /workspace \ |
| 97 | + devkitpro/devkitarm:latest make |
126 | 98 | ``` |
| 99 | + |
| 100 | +CI builds both variants on every push to `main` (see |
| 101 | +[`.github/workflows/verify-build.yml`](../.github/workflows/verify-build.yml)). |
| 102 | + |
| 103 | +The `build/` tree is committed so consumers using this folder as a |
| 104 | +submodule (e.g. joypad-os) don't need devkitARM unless they're |
| 105 | +modifying the source — rebuild and commit `build/<variant>/<variant>_mb.gba` |
| 106 | ++ `build/<variant>/<variant>_payload.c` whenever you change source. |
| 107 | + |
| 108 | +## Loading on hardware |
| 109 | + |
| 110 | +Two paths, depending on which variant and host: |
| 111 | + |
| 112 | +**Multiboot over GameCube link cable** — both variants. Build this |
| 113 | +repo's GameCube/Wii host (see [`../gamecube/`](../gamecube/)), connect a |
| 114 | +GBA Link Cable from GameCube SI port 2 to the GBA, leave the GBA on the |
| 115 | +"Press Start" screen with no cartridge, and start the host. The host |
| 116 | +uploads `tester_mb.gba` via Kawasedo handshake + stream cipher and the |
| 117 | +GBA boots into the tester variant. For the eyes variant, the host |
| 118 | +uploader lives in [joypad-os](https://github.com/joypad-ai/joypad-os)'s |
| 119 | +RP2040 firmware (`src/native/host/gc/gba_multiboot.c`). |
| 120 | + |
| 121 | +**Flash cart** — `tester_mb.gba` only. Drop `tester_mb.gba` onto an |
| 122 | +EZ-Flash / EverDrive / etc., boot it. Standalone fallback kicks in |
| 123 | +after the 3-second joybus handshake timeout and the variant runs as a |
| 124 | +pure on-GBA button tester. |
| 125 | + |
| 126 | +## Embedding |
| 127 | + |
| 128 | +Drop the appropriate `build/<variant>/<variant>_payload.c` into your |
| 129 | +host firmware's source list, then feed the symbols to your joybus |
| 130 | +multiboot uploader. Both variants export the same symbol names so the |
| 131 | +host's externs don't change when switching ROMs: |
| 132 | + |
| 133 | +```c |
| 134 | +extern const uint8_t joypad_payload[]; |
| 135 | +extern const uint32_t joypad_payload_len; |
| 136 | +``` |
| 137 | + |
| 138 | +See [`../gamecube/ppc/gba.c`](../gamecube/ppc/gba.c) or joypad-os's |
| 139 | +`src/native/host/gc/gba_multiboot.c` for a reference uploader |
| 140 | +implementation (Kawasedo handshake, stream cipher, polled WRITE/READ, |
| 141 | +unconditional handshake-complete write). |
| 142 | + |
| 143 | +## Releases |
| 144 | + |
| 145 | +Tagged as `gba-v<semver>` from the repo root — see |
| 146 | +[`gba/CHANGELOG.md`](CHANGELOG.md) for per-version notes. The release |
| 147 | +workflow attaches `joypad_mb.gba`, `joypad_payload.c`, `tester_mb.gba`, |
| 148 | +and `tester_payload.c` to each GitHub Release automatically. |
| 149 | + |
| 150 | +## Origin / credits |
| 151 | + |
| 152 | +Built on Doridian's [Joybus-PIO](https://github.com/Doridian/Joybus-PIO) |
| 153 | +GBA payload (MIT) — see [`LICENSE.md`](LICENSE.md). The joybus handshake |
| 154 | ++ main loop (`0x30303030` exchange, status-bit polls, SVC `0x26` BIOS |
| 155 | +reset on `JOYCNT.RST`) is taken verbatim from Doridian's `gba/source/main.c`; |
| 156 | +the eyes overlay, tester console, Mode-4 page-flipped screensaver |
| 157 | +(matching the GameCube tester's logo + color cycle), two-build Makefile, |
| 158 | +and robust joybus reset handling (polling `JOYCNT.RST` inside the VBlank |
| 159 | +busy-wait) are added on top. Joyboot stream cipher reverse-engineered by |
| 160 | +Sage-of-Mirrors' [libgbacom](https://github.com/Sage-of-Mirrors/libgbacom) |
| 161 | +(ported from VisualBoyAdvance); canonical GameCube-side multiboot |
| 162 | +reference by FIX94's |
| 163 | +[gc-gba-link-cable-demo](https://github.com/FIX94/gc-gba-link-cable-demo); |
| 164 | +hardware semantics per [gbatek](https://problemkaputt.de/gbatek.htm) |
| 165 | +(Martin Korth). Eyes overlay is a port of joypad-os's `eyes_anim`. |
0 commit comments