Now write your own.
The other courses in this collection took the GameCube apart — the CPU, the GPU, the DSP, the disc drive — and showed how Dolphin puts it back together. This course closes the loop: you write code and the real machine runs it. It covers the free, open toolchain the community built (devkitPPC and libogc), the DOL executable format that every retail game also uses, full setup instructions for Windows, macOS and Linux, and every step from an empty folder to a playable program in Dolphin — and on real hardware. You'll need a computer, a C compiler's worth of patience, and nothing from Nintendo at all.
The scene & the toolkit
What homebrew is (and isn't), the cross-compiler that targets Gekko, and libogc — the community's answer to Nintendo's SDK.
First light
Install the tools on your OS, write hello-world, understand the build pipeline and the DOL format, and run the result.
Make it a game
The controller, the screen, sound and storage, and the sacred 16.7-millisecond game loop — with a playable frame-budget lab.
Ship it
DOLs versus disc images, packaging and distributing your work, and where to go once the course lets go of your hand.
The scene & the toolkit
Before touching a compiler, you should know what you're joining. This first stretch explains what console homebrew actually is, how a legal, Nintendo-free toolchain for a proprietary console can even exist, and which libraries stand in for the official SDK you don't have. Everything here connects backwards to the other four courses: the machine you're about to program is the one they dissected.
What homebrew is
Homebrew is software for a games console written by people outside the console maker's licensed-developer program. No official development kit, no signed contract, no access to Nintendo's tools or documentation — just the machine itself, publicly available knowledge about how it works, and a toolchain built by volunteers. The word covers everything from a scrolling "hello world" demo to full games, media players, emulators and system utilities like Swiss, the Swiss-army-knife launcher that modern GameCube enthusiasts treat as essential equipment.
Draw the line clearly, because the internet often smudges it: homebrew is writing your own software. It is not copying games, and this course has nothing to do with piracy. The disc course's Part II told the story of the GameCube's copy protection from the defender's side; nothing in this course involves circumventing it to copy anything. You'll build programs from your own source code with a toolchain containing no Nintendo code whatsoever, and you'll run them either in an emulator or through hardware paths designed to load unsigned, homemade software. (The usual disclaimer: this is an educational overview, not legal advice, and the details vary by jurisdiction — but "I wrote a program and ran it on hardware I own" is about as clean as the hobby gets.)
A licensed developer rents the kitchen with the chef's blessing: recipe book, branded ingredients, a hotline to the manufacturer. A homebrew developer owns the same kitchen — they bought it — but arrives with no recipe book at all. Everything they know about the oven's quirks was worked out by a community of cooks comparing notes for twenty years. The food is entirely theirs; only the kitchen is Nintendo's design. Nobody is stealing meals.
How a closed console became programmable
The GameCube launched in 2001 with no intended path for hobbyist code: the console only booted discs pressed in Nintendo's deliberately off-spec format (the disc course explains why yours can't be). The scene's history is the story of finding legitimate entry points. Early on, Phantasy Star Online's online mode — which could download and execute updates over the network — famously became a way to load community code through the broadband adapter. The homebrew that followed built gentler doors: exploitable save files that bootstrap a loader from a memory card, commercial accessories (Datel's Action Replay family shipped its own boot disc that the console accepts), and eventually modern hardware like SD-card adapters for the memory-card slots and serial port. Module 07 walks the current routes in detail.
The second half of the story is the toolchain. Volunteers ported GCC — the free C/C++ compiler — to target the GameCube's PowerPC processor, and wrote libogc, a from-scratch library that talks to the hardware the way Nintendo's SDK does, but with independent code. That work grew into devkitPro, a maintained distribution of console toolchains that today installs with a package manager on all three desktop OSes. It is the reason this course can exist: the tools are free, open, current, and contain no copyrighted Nintendo material.
What you can realistically build
Everything the machine can do is on the table, because homebrew code runs with full control of the hardware — there is no operating system underneath you (a fact Module 11 turns into a superpower). The community has produced 2D and 3D games, ports of Doom-era classics, emulators for older consoles, media software, and serious system tools. Your ceiling is the console's: 485 MHz of Gekko, the Flipper GPU's fixed-function pipeline, 43 MB of total RAM, and the DSP for sound. Those courses taught you the machine as an examiner; this one hands you the keys.
- Homebrew = your own software for the console, built without Nintendo's SDK or blessing — a separate thing from piracy entirely.
- The scene's two pillars: entry points to run unsigned code (Module 07) and a free toolchain — devkitPPC + libogc — with no Nintendo code in it.
- Homebrew runs with full hardware control; everything the other four courses described is directly programmable.
- You need no special hardware to start: the toolchain plus Dolphin is a complete development loop.
The toolchain
Your computer and the GameCube speak different machine languages. Your laptop's processor executes x86-64 or ARM instructions; the GameCube's Gekko executes 32-bit PowerPC (the CPU course spent fifteen modules on it). A normal compiler produces programs for the machine it runs on. What you need is a cross-compiler: a compiler that runs on your machine but emits code for a different one. This is utterly routine in embedded development — it's how phone apps, router firmware and Mars-rover code get built — and it's the single concept that makes console homebrew possible.
A cross-compiler is a translator living in your city who writes flawless letters in a language spoken only on a distant island. The translator never needs to visit the island; they just need to know the language perfectly. Your PC never executes a single PowerPC instruction during a build — it only writes them, and the letter is posted to the console afterwards.
The pieces, by name
The GameCube homebrew toolchain is distributed by devkitPro, the volunteer organisation that maintains cross-toolchains for many consoles. You'll see these names constantly, so meet the cast once, properly:
/opt/devkitpro directory everything lives in.powerpc-eabi — bare-metal PowerPC with no operating system assumed.elf2dol, which converts the linker's output into the console's executable format (Module 06's subject).
Every tool in devkitPPC carries the target's name as a prefix:
the compiler is powerpc-eabi-gcc, the linker
powerpc-eabi-ld, the object inspector
powerpc-eabi-objdump. The prefix is your constant
reminder that outputs are for the console, not for the machine
you're sitting at. When the build system invokes the compiler it
also passes Gekko-specific flags — you'll see
-mogc -mcpu=750 -meabi -mhard-float in the standard
makefiles — telling GCC to schedule code for the 750 core the
CPU course described and to use its hardware
floating point.
What "no operating system" means for the compiler
The eabi in the target triple means the compiler
assumes a bare-metal environment: no Linux, no
Windows, no system calls to lean on. So who provides
printf, malloc and friends? A compact
C library for embedded targets (devkitPPC ships
newlib)
implements the standard functions, and delegates the
hardware-touching parts — "actually put this character somewhere",
"give me more heap" — to hooks that libogc fills in. That's why
printf in Module 05 can appear on your TV: the C
library formats the string, and libogc's console code draws it
into video memory. The stack of responsibilities is exactly the
next module's subject.
Nothing in this toolchain is Nintendo's. GCC and binutils are GNU projects; newlib is an open embedded libc; libogc was written by the community. This matters legally (Module 01) but also practically: you can read every line of the code your program is built from, all the way down.
- A cross-compiler runs on your machine but emits code for another — here,
powerpc-eabi-gccemitting Gekko-ready PowerPC. - devkitPro is the distribution; devkitPPC is the compiler + binutils + libc; dkp-pacman installs and updates it all.
- The target is bare metal: newlib provides the C library, and libogc supplies the hardware-facing plumbing underneath it.
elf2doland the examples tree round out the kit — both star later in Stage 2.
The libraries
A compiler gives you a language; it gives you nothing to say. On a PC, the operating system provides the verbs — open a window, play a sound, read the gamepad. On the GameCube there is no operating system: after boot, your program is alone with the hardware, and every device is programmed by reading and writing its memory-mapped registers. The other courses showed you several of these register interfaces up close — the disc drive's DI, the DSP's mailboxes, Flipper's command FIFO. You could program them all by hand. You'd just spend a year on plumbing before drawing a triangle.
libogc is the library that does the plumbing. It is the homebrew ecosystem's de-facto standard runtime: a from-scratch, community-written layer that initialises the machine, owns the interrupt handlers, and wraps each hardware subsystem in a sane C API. Its coverage maps almost one-to-one onto this collection's courses:
| libogc subsystem | Hardware it drives | Covered by | You call it for… |
|---|---|---|---|
VIDEO_* / VI | Video Interface — scan-out of the XFB to the TV | Graphics Part II | video modes, framebuffers, vsync |
GX_* | Flipper's 3D pipeline — vertices, TEV, textures | Graphics Part II | every triangle you draw |
PAD_* | Serial Interface — the four controller ports | this course, Module 08 | buttons, sticks, rumble |
AESND_* / ASND_* | The audio DSP + AI streaming | Audio Parts I–II | mixing voices, playing samples |
DVD_* | The disc drive's DI interface | Disc Part II | reading from an actual disc |
CARD_* | Memory-card slots (EXI bus) | this course, Module 10 | save files |
AR_* / ARAM | The 16 MB auxiliary RAM | CPU & Audio | staging assets off the main heap |
LWP_*, OSTick… | Gekko itself — threads, timers, caches, interrupts | CPU Part II | the "OS layer" your program stands on |
Owning the whole building (bare metal) sounds glamorous until a pipe bursts at 3 a.m. libogc is the superintendent who moves in with you: it keeps the boiler (interrupts), the intercom (controllers) and the plumbing (DMA) working, so your time goes into the apartment you actually wanted to build. And unlike most supers, you can read its source when you want to know how anything really works.
Above and beside libogc
libogc is deliberately close to the metal — GX_* is
barely friendlier than Flipper's own registers (the
graphics course is genuinely the
manual for it). The ecosystem stacks convenience on top:
- GRRLIB — a much-loved 2D/3D convenience layer over GX: load a PNG, draw it at (x, y), print TTF text. The fastest route from zero to something moving on screen.
- libfat — a FAT filesystem driver, so an SD card in an adapter behaves like
fopen("sd:/assets/level1.dat"). Module 10 leans on it. - Ported middleware — the scene has ported classics of the open-source world (SDL, image and audio decoders, physics libraries), so a surprising amount of desktop C code compiles for the console with minor changes.
One absence to note: Nintendo's actual SDK. Leaked copies of it exist in murky corners, and using them contaminates a project both legally and morally — everything it enables, libogc does cleanly. The scene's healthiest projects are libogc all the way down, and this course follows suit.
VIDEO_WaitVSync() is just a polite way of saying "block until the VI finishes the field".- Bare metal means no OS verbs — libogc supplies them, wrapping every subsystem the other courses dissected.
- The mapping is nearly one-to-one: VI/GX ↔ graphics course, AESND ↔ audio course, DVD ↔ disc course, threads/caches ↔ CPU course.
- GRRLIB and libfat are the everyday comfort layers; SDL-era ports mean much desktop C code moves over easily.
- Leaked official SDKs exist; serious homebrew doesn't touch them — libogc does everything cleanly.
First light
Vocabulary over; hands on. This stretch installs the toolchain on your actual computer — Windows, macOS or Linux, each with its own tab — then writes the traditional first program, follows it through the build pipeline into the console's DOL executable format, and runs it. By the end of Stage 2 you will have watched code you compiled boot on (an emulated) GameCube. That moment does not get old.
Set up your machine — Windows, macOS or Linux
The install has the same shape everywhere, because every platform
uses the same package manager underneath: (1) get
dkp-pacman onto your system, (2) ask
it for the GameCube development group, (3) make
sure two environment variables point at the result,
(4) build an example to prove the chain works,
and (5) install Dolphin as
your test console. Pick your OS below — the page tries to guess it —
and note that installer details drift over time: if anything below
disagrees with devkitPro's
own getting-started page, trust that page; the shape
of the process is what this module teaches.
Windows gets a graphical installer that sets
up a small MSYS2-based Unix shell with pacman
preconfigured for devkitPro's repositories — you'll do your
console builds from that shell.
- Download the devkitPro installer for Windows from the devkitPro site and run it.
- When it asks which workloads you want, tick GameCube development (often listed alongside Wii, since they share the toolchain).
- Let it finish, then open the MSYS2 shell it installed (look for a devkitPro/MSYS entry in the Start menu) — not plain CMD or PowerShell.
# already installed the GameCube workload? this is a no-op. otherwise: $ pacman -S gamecube-dev # confirm the compiler exists and answers: $ powerpc-eabi-gcc --version powerpc-eabi-gcc (devkitPPC release …) …
The installer sets DEVKITPRO and
DEVKITPPC for its shell automatically. If a build
ever complains they're missing, you're probably in the wrong
terminal — return to the devkitPro shell.
macOS gets a signed .pkg installer for
devkitPro pacman (works on both Intel and Apple
Silicon Macs — check the release notes for the current
minimum macOS version). Download it from devkitPro's site or
GitHub releases and install it like any package, then use
Terminal:
# pull the whole GameCube kit: compiler, libogc, tools, examples $ sudo dkp-pacman -S gamecube-dev # the installer drops the env vars into a profile script; open a NEW # terminal window, then confirm: $ echo $DEVKITPRO $DEVKITPPC /opt/devkitpro /opt/devkitpro/devkitPPC $ powerpc-eabi-gcc --version
If the variables come back empty, add them to your shell
profile by hand (~/.zshrc on modern macOS):
export DEVKITPRO=/opt/devkitpro export DEVKITPPC=$DEVKITPRO/devkitPPC export PATH=$DEVKITPPC/bin:$PATH
On Linux you install devkitpro-pacman first —
devkitPro publishes a .deb for Debian/Ubuntu
family systems (and instructions for Arch, where the repos
plug into your existing pacman). Then it's the same one
command as everywhere else:
# 1) install devkitpro-pacman per the wiki (deb package, or repo setup on Arch) # 2) then: $ sudo dkp-pacman -S gamecube-dev # 3) log out/in (or source the profile script it installs), then: $ echo $DEVKITPRO $DEVKITPPC /opt/devkitpro /opt/devkitpro/devkitPPC $ powerpc-eabi-gcc --version
The package installs a script under
/etc/profile.d/ that exports
DEVKITPRO and DEVKITPPC for login
shells; if your distribution or shell skips those, export them
in your own shell rc exactly as in the macOS tab.
Prefer containers? devkitPro publishes Docker
images (e.g. devkitpro/devkitppc) with everything
preinstalled — a tidy option for CI or for keeping your host
clean: docker run --rm -v $PWD:/src -w /src
devkitpro/devkitppc make.
Prove it works — build a real example
Never trust an install you haven't built with. The
gamecube-examples package puts working projects under
$DEVKITPRO/examples/gamecube/; copy the template out
(the examples directory itself may need root to write into) and
build it:
$ cp -r $DEVKITPRO/examples/gamecube/template ~/gc-hello $ cd ~/gc-hello $ make template.c linking ... template.elf output ... template.dol
Those last two lines are the whole course in miniature — an ELF
and a DOL, both of which Module 06 will teach you to read. Last
step: install Dolphin (from
dolphin-emu.org; it runs on
all three OSes) and open the freshly built .dol with
it. White text on a black screen — a running GameCube program you
built — is the finish line for this module.
The classic failure is a build that stops immediately with "Please set DEVKITPPC in your environment" — which means exactly what it says: the makefile checks those variables before doing anything. Fresh terminal, correct shell (on Windows: the devkitPro MSYS shell), variables exported — that fixes virtually every first-day problem.
- One process, three skins: install dkp-pacman →
gamecube-dev→ env vars → build an example → run it in Dolphin. - Windows works inside the installer's MSYS2 shell; macOS and Linux use the native terminal with
/opt/devkitpro. DEVKITPRO/DEVKITPPCunset is the #1 beginner error; a fresh, correct shell is the fix.- Docker images exist for CI and clean hosts; devkitPro's wiki is the authority when details drift.
Hello, world
Here is the traditional first program, essentially as it appears in the devkitPro template. It looks longer than a PC hello-world because there is no operating system to inherit a screen from — you boot the display. Read it once, then we'll take it apart:
#include <stdio.h> #include <stdlib.h> #include <gccore.h> // libogc's front door static void *xfb = NULL; // the external framebuffer static GXRModeObj *rmode = NULL; // the video mode we'll run in int main(int argc, char **argv) { VIDEO_Init(); // wake the Video Interface PAD_Init(); // wake the controller ports rmode = VIDEO_GetPreferredMode(NULL); // 480i/576i/480p — asks the console xfb = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode)); console_init(xfb, 20, 20, rmode->fbWidth, rmode->xfbHeight, rmode->fbWidth * VI_DISPLAY_PIX_SZ); VIDEO_Configure(rmode); // program the VI's registers VIDEO_SetNextFramebuffer(xfb); // scan out THIS buffer VIDEO_SetBlack(FALSE); // un-blank the screen VIDEO_Flush(); VIDEO_WaitVSync(); // let one field scan out if (rmode->viTVMode & VI_NON_INTERLACE) VIDEO_WaitVSync(); printf("\x1b[2;0H"); // cursor to row 2, column 0 printf("Hello, world!\n"); while (1) { PAD_ScanPads(); // poll all four ports if (PAD_ButtonsDown(0) & PAD_BUTTON_START) exit(0); // back to the loader VIDEO_WaitVSync(); // the 60 Hz heartbeat } return 0; }
Every line is an old friend
If you've read the other courses, this program is a reunion:
VIDEO_Init/VIDEO_Configure— programming the VI, the scan-out hardware from the graphics course's EFB→XFB→TV chapter.VIDEO_GetPreferredModeasks what the console is set to (NTSC 480i, PAL 576i, progressive if available) so your program works on any region's hardware.SYS_AllocateFramebuffer— reserves memory for the XFB, the very buffer the graphics course watched Flipper copy pixels into.MEM_K0_TO_K1— pure CPU course: it converts a normal cached address into its uncached mirror. You write pixels through the uncached window so they land in real RAM immediately, where the VI's DMA can see them — instead of loitering in Gekko's data cache while the TV scans out stale memory.console_init— libogc's text console: it hooksprintf's output and renders characters straight into the XFB. No GPU involved — which is why hello-world doesn't need a singleGX_*call.PAD_ScanPads/PAD_ButtonsDown— Module 08's whole subject, previewed in two lines.VIDEO_WaitVSync— blocks until the VI finishes the current field. It's the metronome of every console game ever written, and Module 11 builds the entire concept of the frame budget on it.exit(0)— on a PC, "return to the OS". Here, "return to whatever loaded me" — Dolphin stops emulation; a hardware loader like Swiss takes the stage back. There's no OS to fall into, only the program that ran you.
Printing text on a PC is walking on stage in a running theatre —
lights on, audience seated, someone else handles everything.
This program arrives at a dark building: it unlocks the doors
(VIDEO_Init), raises the house lights
(SetBlack(FALSE)), builds a stage from lumber
(AllocateFramebuffer), and only then says
its one line. The applause is the same — but now you know who
turned everything on, because it was you.
The lab below replays that boot sequence on a mock TV: black frame
at VIDEO_Init, cursor at console_init,
your text at printf, then the vsync loop counting
frames at 60 Hz until you press START. Change the message —
it's your program now.
exit(0), handing control back to the loader.
- With no OS, your program boots the display itself: init the VI, allocate an XFB, configure a mode, un-blank.
printfreaches the TV because libogc's console renders text into the framebuffer — newlib formats, libogc draws.MEM_K0_TO_K1is the CPU course's cached/uncached distinction doing real work in line three of your first program.VIDEO_WaitVSync()is the heartbeat: everything in Module 11 grows from this one blocking call.
From .c to .dol — the build & the executable format
Typing make in Module 04 fired a four-stage pipeline.
Nothing in it is exotic — it's the standard C build everyone half
remembers, plus one console-specific final step:
- Compile.
powerpc-eabi-gcc -cturns each.cfile into a.oobject file of PowerPC machine code with unresolved references ("I call something namedVIDEO_Init, wherever that is"). - Link. The linker fuses your objects with crt0 and the libogc libraries, resolves every name to a real address, and lays the program out for the GameCube's memory map: code at the classic load address near the bottom of MEM1. Out comes an ELF — the standard Unix executable format — complete with symbol names and debug info.
- Convert.
elf2dolstrips everything the console's loaders don't need — symbols, debug data, section names — and writes the minimal format they expect: a DOL.
Keep both outputs. The .elf is your debugging
artefact — Dolphin loads it directly and can show you function
names in its debugger. The .dol is your
shipping artefact — small, loader-friendly, and the thing
hardware loaders like Swiss consume.
The DOL format — the game format itself
This is not a homebrew-only format. The disc course showed that every retail disc carries the game's main executable as a DOL, loaded by the apploader at boot. Your homebrew and Wind Waker ship in the same container — one of the tidiest facts in the whole scene.
And it is a gloriously simple container. A DOL is a 256-byte header followed by raw bytes — no compression, no symbols, no relocation, no metadata. The header is just three parallel tables and three scalars, all big-endian 32-bit values: up to 7 code ("text") sections and 11 data sections, each described by a file offset, a RAM load address, and a size; then the BSS address and size; and finally the entry point — the address the loader jumps to when everything is in place. Loading one takes a dozen lines of code: copy each section from file offset to RAM address, zero the BSS range, flush the CPU cache, jump to the entry point. That radical simplicity is why everything — the apploader, Swiss, Dolphin — happily loads DOLs.
The lab below is a real 256-byte header for our hello-world,
synthesised by this page. Click each field group to see which
bytes it owns and what they mean. (Grey 00 bytes are
unused table slots — this little program needs 1 of its 7 text
slots and 2 of its 11 data slots.)
An ELF is a moving company's full paperwork: inventory, insurance, the movers' names, which box the cutlery is in. A DOL is the note taped to the truck door: "boxes 1–3 → living room, boxes 4–5 → kitchen, clear out the garage, and the house key is under the mat at 0x80003100." The console doesn't want paperwork. It wants to know where things go and where to start.
- The pipeline is compile → link → convert: gcc makes objects, the linker builds an ELF against libogc,
elf2dolemits the DOL. - ELF = debugging (symbols, Dolphin's debugger); DOL = shipping (minimal, universal).
- A DOL is a 256-byte header + raw sections: ≤7 text, ≤11 data, BSS address/size, entry point — all big-endian u32s.
- Retail games use the exact same format; the apploader on every disc is just a DOL loader (disc course, Module 06).
Run it — Dolphin first, hardware after
Dolphin is your development console. It opens your
.dol or .elf directly — no disc image,
no ceremony: point it at the file (or drag the file onto it) and
your program boots in a window, one second after make
finishes. This tight loop is something 2001-era licensed
developers would have envied, and it's made possible by everything
the other courses' Part IIIs described: Dolphin recreates the VI,
the PAD interface, the DSP and the rest faithfully enough that
code which behaves in Dolphin almost always behaves on hardware.
- Load the ELF while developing — Dolphin reads its symbols, so the debugger shows
mainandVIDEO_Initinstead of bare addresses. Enable the debugger UI and you get breakpoints, memory view and register state — the CPU course's Part III, now pointed at your code. - Watch the log. Dolphin's log window surfaces your program's report output (and libogc's complaints) — the humble
printf-debugging channel every console dev lives in. - Map a controller — keyboard or any PC gamepad stands in for a GameCube pad. Module 08's lab shows exactly what your program will read.
Real hardware — the boot routes
Sooner or later you'll want the real thing: actual Flipper output on an actual TV. The console only boots what its IPL approves, so homebrew enters through one of a few well-worn routes — all of them ultimately loading Swiss (or another loader), which then runs DOLs from an SD card. Prices and product names drift; the categories are stable:
Dolphin
Open the DOL/ELF directly. Instant, free, with a debugger attached. Where all development happens regardless of what you own.
SD adapter + Swiss
An SD-card adapter in a memory-card slot (SD Gecko) or the serial port (SD2SP2), with Swiss on the card. Bootstrapped once via one of the entry points below, then it's "copy DOL to card, run".
Savegame exploit / boot disc
A crafted save file for certain retail games can chain-load Swiss from the memory card; Datel's Action Replay / SD Media Launcher discs boot because the console accepts them. Either one starts the chain that launches your loader.
GC Loader / modchip
An FPGA optical-drive replacement (GC Loader) or a classic modchip boots loaders straight from SD/microSD at power-on. Cleanest daily-driver setup; involves opening the console.
A Wii
Early Wiis are GameCubes in a trenchcoat (the disc course's epilogue). A softmodded Wii can run GameCube software through community loaders — often the cheapest real-hardware route, since exploitable Wiis are everywhere.
BBA / dev hardware
The scene's founding paths: streaming code over the Broadband Adapter (the PSO trick), and USB Gecko debug adapters for a hardware printf channel. Beloved, niche, still documented.
Whichever route you take, the endpoint is the same: Swiss (or a
similar loader) presents a file browser, you pick
hello.dol, and the loader does exactly what Module
06 taught — copies sections to their addresses, zeroes BSS,
jumps to the entry point. There is no signing, no approval, no
store. The DOL is the distribution format.
Dolphin is the rehearsal studio: mirrors on every wall (debugger), stop-and-repeat any scene (savestates), no audience. Real hardware is opening night — same script, but now the lighting rig is real and the timing is merciless. Rehearse in the studio daily; go to the theatre to make sure the show actually works under stage lights. Serious homebrew tests on both.
- Dolphin opens DOLs and ELFs directly — the edit-compile-test loop is seconds long, with a symbol-aware debugger attached.
- Real-hardware boot = an entry point (savegame exploit, Datel disc, modchip/GC Loader) that starts Swiss, which then runs your DOLs from SD.
- A softmodded Wii is a legitimate and often cheapest GameCube-homebrew machine.
- No signing, no gatekeeper: hand someone a .dol and they can run it. That's the whole distribution story (Module 12 refines it).
Make it a game
A program that prints text is a program; a program that reads your thumbs sixty times a second is a game. This stretch covers the four pillars of any real project — input, video, audio + storage, and the loop that binds them to the display's clock — each pillar standing on a subsystem another course already taught you.
Reading the controller
Controller input on the GameCube is refreshingly honest. There are
no events, no callbacks, no queues. Once per frame you call
PAD_ScanPads(), which polls all four
ports over the
Serial Interface
and snapshots the state of every connected pad. Then you ask
questions about the snapshot:
PAD_ButtonsHeld(0)— a 16-bit mask of what is currently down on controller 0 (a level: true every frame the button stays pressed).PAD_ButtonsDown(0)— the buttons newly pressed since the last scan (an edge: true for exactly one frame).PAD_ButtonsUp(0)— newly released, the other edge.PAD_StickX/Y(0),PAD_SubStickX/Y(0)— the main and C sticks as signed values (working range roughly ±100 after libogc's calibration);PAD_TriggerL/R(0)— the analog trigger depth.
Hold and edge is a distinction worth tattooing somewhere: use
held for continuous verbs (walking, aiming, charging) and
down for discrete ones (jump, confirm, pause). The classic
beginner bug — a menu that flickers through five options per tap —
is a Held where a Down belonged.
Each button owns one bit of the mask, with names like
PAD_BUTTON_A (0x0100), PAD_BUTTON_START
(0x1000) and PAD_TRIGGER_Z (0x0010), so tests are
single bitwise-ANDs — held & PAD_BUTTON_A — and
checking three buttons at once is one OR away. The lab shows the
live mask: drive the drawn controller with your keyboard (or plug
in any USB gamepad — the browser's Gamepad API stands in for the
Serial Interface) and watch bits flip in real time.
Desktop input is a doorbell: events ring whenever they please and you answer. Console input is a photograph: once per frame you take one picture of all sixteen buttons and both sticks, and for the next 16.7 milliseconds you reason about that frozen picture. Comparing this photo with the previous one is how "held" becomes "just pressed" — the edge is literally the difference between two snapshots.
- One
PAD_ScanPads()per frame snapshots all four controllers; everything else queries that snapshot. - Held = level (continuous actions); Down/Up = edges (discrete actions). Mixing them up is the classic menu bug.
- Buttons live in a 16-bit mask tested with bitwise AND; sticks and triggers are signed/unsigned analog values.
- The polling model fits the frame loop perfectly — input is stage one of Module 11's loop.
Putting pixels on screen
Hello-world drew text without touching the GPU, which reveals the first of three roads to the screen. Choose by ambition:
GX_* API. Everything the graphics course taught, now writable. Steep, powerful, and the only road to real 3D.Road 1 in practice — and the format surprise
Poking pixels comes with a twist the graphics course foreshadowed: the XFB does not hold RGB. It holds YUY2 — a luma/chroma format where each pair of horizontally adjacent pixels shares its colour information: four bytes carry two brightness values (Y₀, Y₁) but only one shared pair of chroma values (U, V). Television signals work in luma/chroma, the VI feeds a television, so the framebuffer speaks the TV's language and halves its memory bandwidth in the bargain (your eye is far more forgiving about colour resolution than brightness — the same fact lossy image formats exploit).
The lab lets you feel it: choose two neighbouring pixels' colours and watch them encode into four bytes and decode back. Give the pair similar colours and the round trip is invisible; give them wildly different hues and watch the shared chroma split the difference — a real artefact that real GameCube 2D homebrew deals with (the practical dodge: work in an RGB buffer, convert whole frames, or just let GX do it — roads 2 and 3 handle all of this for you).
Road 2, briefly — GX in one paragraph
A GX frame follows the shape the graphics course drew: allocate a
FIFO
and call GX_Init; describe your vertex formats;
set up the camera and load transform matrices; send vertices
between GX_Begin/GX_End; configure
TEV
stages for materials; then GX_CopyDisp copies the
finished EFB
into the XFB and you call it a frame. Every one of those nouns has
a chapter — and an interactive lab — in the
graphics course; there is no better GX
manual on the open web than understanding the hardware it drives.
The gamecube-examples tree has working GX samples to
crib from, and GRRLIB's source is a readable master-class in
"how do I make GX do the normal thing".
- Three roads: CPU-poked XFB (simple, honest), full GX (powerful, steep), GRRLIB (pragmatic 2D).
- The XFB speaks YUY2 — paired pixels share chroma because the buffer feeds a television, not a monitor.
- Shared chroma is a real artefact you can provoke — and the GPU roads handle format conversion for you.
- The graphics course is your GX textbook; the examples tree and GRRLIB source are your working references.
Sound, files & saves
Sound — the DSP works for you now
The audio course told the story of the DSP's interchangeable ucodes — and homebrew gets its own chapter in it. libogc ships a community-written mixing ucode and two API layers over it: AESND (lean, voice-based) and ASND (a friendlier layer used by ported libraries). Allocate a voice, hand it PCM sample data, set volume and pitch, and the DSP mixes it into the 48 kHz output stream exactly as the audio course described — except now the mix is yours. For music, the scene's tradition is gloriously retro: tracker modules (MOD/XM et al.), tiny files that libogc's modplay decodes on the fly — or stream and decode OGG with ported decoders.
Assets — baked in, or fetched from SD
Your game needs sprites, samples and levels, and a bare-metal program has no filesystem by default. Two idioms cover nearly all homebrew:
- Bake data into the DOL. The devkitPro makefiles convert any file you drop into the project's
data/directory into an object file with generated symbols — your PNG becomesmysprite_png[]andmysprite_png_size, linked straight into a data section (you watched those sections in Module 06's header lab). Zero I/O at runtime, perfect for jam-sized games; the cost is that every asset rides in RAM from boot. - Read from SD with libfat. One
fatInitDefault()and the SD card in your Gecko/SD2SP2 adapter mounts as a drive:fopen("sd:/mygame/level2.dat", "rb")and friends just work. Load what you need when you need it — the same streaming discipline the disc course showed retail games practising, with an SD card standing in for the miniDVD.
Saves — the memory card, like a real game
The CARD_* API speaks to memory cards over the
EXI bus:
mount, create a file with your game's identity, write your save
struct, read it back next boot. Your homebrew's save appears in
the console's memory-card manager next to the retail games' —
banner icon and all, if you provide one. (Saving to SD via libfat
is the lazier road; the memory card is the authentic
one.)
Baking assets into the DOL is running a food truck: everything you'll serve is loaded before you drive off, service is instant, but the menu is only as big as the truck. libfat streaming is a restaurant with a stocked pantry: effectively bottomless menu, but now there's a kitchen door between you and the ingredients, and you plan around fetch times. Most homebrew starts as a food truck and grows a pantry precisely when the truck gets full.
- AESND/ASND drive a community ucode on the DSP — allocate voices, feed PCM, and the 48 kHz mix is yours; tracker modules are the classic music format.
- Assets ride two roads: baked into the DOL by the makefile's data rules, or streamed from SD via libfat's
sd:/mount. CARD_*gives you honest memory-card saves that sit beside retail games' in the card manager.- All three storage paths — DOL sections, EXI/SD, memory card — are subsystems earlier courses already mapped.
The game loop & the frame budget
Every module so far has been a part; this is the whole. A GameCube game — yours, or any retail game ever pressed — is one loop:
while (1) { PAD_ScanPads(); // 1 · snapshot the players' hands update_world(dt); // 2 · physics, AI, rules — pure computation draw_world(); // 3 · issue GX commands / write pixels VIDEO_WaitVSync(); // 4 · block until the TV finishes the field }
Step 4 is the one with teeth. The
VI
delivers a field to the TV every 16.7 ms
(NTSC; 20 ms PAL), and VIDEO_WaitVSync() blocks
until it has. So the loop runs at exactly 60 Hz — if
steps 1–3 fit inside the budget. Spend 10 ms computing, wait
6.7: sixty perfect frames. Spend 17 ms — one millisecond
over — and you don't get 59 frames: you miss the
vsync, wait for the next one, and drop to
30 Hz. The budget isn't a speed limit you can nudge past;
it's a train that leaves the station with or without you.
Two habits make console timing a gift instead of a terror. First, double-buffer: allocate two XFBs and alternate — draw into one while the VI scans the other, then swap at vsync (with a single buffer, the TV can catch you mid-drawing — the tearing artefact the graphics course demonstrated). Second, exploit the machine's determinism. There is no OS, no other process, no thermal throttling, no surprise background update: the same code path costs the same microseconds every single time. If your frame fits today, it fits forever. This is why 2001 games could promise a rock-solid 60 and keep the promise — and Dolphin's Part III chapters showed how much work it takes a modern multitasking PC to imitate that stillness.
A game loop is a live TV broadcast: a new frame goes to air every 16.7 ms whether you're ready or not. A studio show rehearses until every segment fits its slot exactly — that's the determinism. Miss your slot and you don't air 4% late; you wait for the next slot while the audience watches a repeat. That's the drop to 30 Hz, and viewers feel it instantly.
The lab makes the budget physical. It's a tiny playable game — move the paddle with ←/→, A/D or the mouse — whose loop presents one frame per vsync, exactly like the code above. The slider adds simulated work to steps 2–3. Push it past the 16.7 ms line and watch the game snap from silk to judder: same game, one millisecond too greedy.
- The loop is scan → update → draw → WaitVSync; the VI's 60 Hz field rate is the clock and the contract.
- Overrun the 16.7 ms budget by any amount and you wait a whole extra field: frame rates quantise (60 → 30), they don't degrade smoothly.
- Double-buffer two XFBs to draw while the other is scanned out — swap at vsync, never tear.
- Bare metal is deterministic: a frame that fits once fits always. Budget once, trust it forever.
Ship it
A game nobody else can run is a diary. The final stretch covers packaging — when a bare DOL is enough (almost always) and what it takes to build a full disc image (and why that's rarer than you'd think) — plus distribution, licensing manners, and where your skills point next.
From DOL to disc — packaging & distribution
The default answer: ship the DOL
For almost all homebrew, the file you built in Module 06 is
the release. Every relevant runner — Dolphin, Swiss, Wii loaders —
opens a .dol directly, and users know exactly what to
do with one: drop it on the SD card, pick it in the menu. Zip it
with a README and a licence, publish it where the scene gathers
(the community's homebrew directories, or simply your own
repository), and you have shipped GameCube software. If you baked
your assets in (Module 10), it's genuinely one file.
The fancy answer: build a disc image
Sometimes you want your project to be a disc: booted by a GC Loader or modchip as if pressed, aware of the disc course's whole apparatus. Recall from that course what a bootable disc actually contains, because now it's a packing list — every item something you must provide:
DVD_* reads.
Community tools assemble all of that into a
.gcm/.iso from a directory tree plus
your DOL. The result boots in Dolphin like any image, and on
hardware through the optical-drive-replacement routes. It's a
wonderful capstone exercise — you will never look at a retail
disc's innards the same way — but be honest about the audience:
for SD-card users, the bare DOL was already perfect.
The bright line, one last time: build images of your own software, from your own files, with an open apploader. Shipping Nintendo's apploader, IPL data or any retail content in your image steps over the line that keeps this hobby healthy — and it's never necessary.
Release manners
- License it. The scene runs on source: GPL and MIT-family licences dominate, and releasing your source is both the custom and the reason the next generation learns. (You're reading a course that exists because Dolphin and libogc are open.)
- State your targets. "Tested in Dolphin and on NTSC hardware via SD2SP2 + Swiss" is one sentence and saves users an evening. Video modes are the classic gotcha — Module 05's
VIDEO_GetPreferredModepattern covers NTSC/PAL, so keep it. - Version the ELF away. Ship the DOL; keep the matching ELF archived so you can debug crash reports against the exact build.
- Credit your ingredients. libogc, GRRLIB, any ported decoder — a credits line each. Small scene, long memories, good manners.
Shipping a DOL is publishing a zine: photocopy, staple, hand it out — perfectly legitimate publishing, and how most of the scene's beloved work actually circulates. Building a disc image is commissioning a hardcover: binding, dust jacket, the works. Undeniably satisfying, occasionally worth it — but nobody ever refused to read a great zine because of the staples.
- The bare .dol is the scene's standard release unit — every loader and emulator opens it, one file, done.
- A bootable image = boot header + (open) apploader + your DOL + FST/files, assembled by community tools into a GCM/ISO.
- Never ship Nintendo's apploader or any retail bytes; open replacements exist and suffice.
- Release with source, licence, tested-targets note and credits — the customs that keep a twenty-year scene alive.
Epilogue: your turn
Here is the entire course in one chain. Trace it once with your finger; every link is now something you've either studied or built:
When the first module of any course in this collection began, the GameCube was a sealed appliance. Somewhere in the middle it became a diagram. If this course did its job, it is now something better: a target. The 485 MHz CPU whose pipelines you studied executes your opcodes. The TEV stages you toggled in a lab combine your textures. The DSP mixes your voices; the vsync you learned to fear and love paces your loop. There is no layer of the machine left that is someone else's business.
Where to go from here
- Mine the examples.
$DEVKITPRO/examples/gamecube/has a working sample for nearly every subsystem — the fastest way to learn any API is to build its example and start bending it. - Read great homebrew. Swiss's source is a masterclass in the whole machine; GRRLIB's is the friendliest GX tutorial ever written, disguised as a library.
- Use Dolphin as a microscope. Its debugger, logs and graphics tools (the Part IIIs of this collection) are as good at inspecting your game as anyone else's.
- Join the room. The devkitPro forums/wiki and the GameCube scene's communities have answered every beginner question twice a year for two decades. Ask well: post code, name your toolchain versions.
- Revisit the other courses as manuals. They were written as teardowns — CPU, graphics, audio, disc — but every one of them reads differently now that the hardware is yours to command.
A good first project, if you want one: a one-screen arcade game — paddle, ball, score, one music track, one sound effect, saves its high score to the memory card. It touches every module of this course, fits comfortably in the frame budget, bakes into a single DOL, and it is exactly the kind of thing the scene has cherished for twenty years: proof that the little purple box still says yes to anyone who learns to ask.
- You can set up a GameCube toolchain on Windows, macOS or Linux, and explain every piece of it.
- You can read a DOL header byte by byte, and narrate the pipeline that produced it.
- You can boot code in Dolphin and name every hardware route to running it for real.
- Input, pixels, sound, storage, and the 16.7 ms loop: the pillars of a real game are in your hands. The other four courses taught you the machine. This one made it yours.