An interactive course · devkitPro & libogc

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.

Stage 1

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.

Modules 01–03Difficulty BeginnerPrerequisite some C helps
Module 01 · Stage 1

What homebrew is

GoalKnow exactly what "homebrew" means, why writing your own GameCube software is a legitimate craft distinct from piracy, and what two decades of the scene have built.

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.)

Analogy · Cooking in someone else's kitchen

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.

Licensed development (2001) Nintendo SDK under NDA official dev kit HW leased, not sold Homebrew (today) devkitPPC + libogc community docs your C code same on both paths PowerPC machine code Gekko can't tell the difference 🎮
Two roads, one destination. A licensed studio compiled C against Nintendo's SDK; you'll compile C against libogc with a free compiler. Both roads end in PowerPC machine code, and the Gekko executes yours exactly as willingly as it executed Metroid Prime. The hardware has no opinion about who wrote the program — only the boot path does, which is why Module 07 exists.

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.

Key takeaways
  • 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.
Module 02 · Stage 1

The toolchain

GoalUnderstand what a cross-compiler is, what each piece of the devkitPro toolchain does, and why code built on your x86/ARM laptop runs on a PowerPC console.

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.

Analogy · The translator who never travels

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:

devkitPro
The umbrella project and its package repositories. Also lends its name to the installer and the /opt/devkitpro directory everything lives in.
devkitPPC
The PowerPC cross-toolchain itself: GCC (C/C++ compiler), binutils (assembler, linker, object-file tools) and a C standard library, all built to target powerpc-eabi — bare-metal PowerPC with no operating system assumed.
dkp-pacman
The package manager (a port of Arch Linux's pacman) that installs and updates everything above. One command pulls the whole GameCube kit; another updates it.
libogc
The hardware library — Module 03's subject. The "ogc" is exactly what you'd guess.
gamecube-tools
Small host-side utilities, most importantly elf2dol, which converts the linker's output into the console's executable format (Module 06's subject).
gamecube-examples
A tree of small, working sample projects — video, audio, controller, network — that double as templates. Your first build in Module 04 is one of these.

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.

HOST — your computer (x86-64 / ARM) main.c your source powerpc-eabi-gcc runs here… also here: make, dkp-pacman, elf2dol, objdump, your editor, Dolphin PowerPC machine code TARGET — the GameCube (PowerPC) Gekko @ 485 MHz …code runs here 24 MB MEM1 + ARAM where the DOL lands no OS, no loader of its own — your program owns the whole machine from _start onward
Host and target. Every tool runs on your computer; only the finished machine code crosses to the console. Dolphin blurs the line delightfully — it makes your host pretend to be the target, which is why the edit-compile-test loop in Module 07 can be seconds long.

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.

Key takeaways
  • A cross-compiler runs on your machine but emits code for another — here, powerpc-eabi-gcc emitting 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.
  • elf2dol and the examples tree round out the kit — both star later in Stage 2.
Module 03 · Stage 1

The libraries

GoalMeet libogc — the community's stand-in for Nintendo's SDK — learn which of its subsystems drives which piece of hardware, and know what to reach for above and beside it.

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 subsystemHardware it drivesCovered byYou call it for…
VIDEO_* / VIVideo Interface — scan-out of the XFB to the TVGraphics Part IIvideo modes, framebuffers, vsync
GX_*Flipper's 3D pipeline — vertices, TEV, texturesGraphics Part IIevery triangle you draw
PAD_*Serial Interface — the four controller portsthis course, Module 08buttons, sticks, rumble
AESND_* / ASND_*The audio DSP + AI streamingAudio Parts I–IImixing voices, playing samples
DVD_*The disc drive's DI interfaceDisc Part IIreading from an actual disc
CARD_*Memory-card slots (EXI bus)this course, Module 10save files
AR_* / ARAMThe 16 MB auxiliary RAMCPU & Audiostaging assets off the main heap
LWP_*, OSTickGekko itself — threads, timers, caches, interruptsCPU Part IIthe "OS layer" your program stands on
Analogy · The building superintendent

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.

your game the only part you have to write GRRLIB · SDL ports… optional comfort layers libfat · decoders… optional services libogc VI · GX · PAD · AESND · DVD · CARD · EXI · threads · IRQs newlib printf, malloc… the machine Gekko · Flipper · DSP · DI · SI · EXI — the registers Parts I–III taught you
The stack you'll stand on. Nothing here is Nintendo's; everything below your game is open source. And because Parts I–III taught you the bottom layer, libogc will never feel like magic — VIDEO_WaitVSync() is just a polite way of saying "block until the VI finishes the field".
Key takeaways
  • 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.
Stage 2

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.

Modules 04–07Difficulty Beginner — follow alongLabs 3 interactive
Module 04 · Stage 2

Set up your machine — Windows, macOS or Linux

GoalInstall devkitPPC, libogc and the GameCube tools on your OS, verify the install by building a real example, and have Dolphin ready to run what you build.

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.
devkitPro MSYS shell
# 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:

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):

~/.zshrc
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:

shell
# 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:

any OS — devkitPro shell / terminal
$ 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.

Key takeaways
  • 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/DEVKITPPC unset 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.
Module 05 · Stage 2

Hello, world

GoalRead the canonical GameCube hello-world line by line, and recognise that every call in it is a concept from Parts I–III wearing a function name.

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:

source/main.c
#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_GetPreferredMode asks 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 hooks printf's output and renders characters straight into the XFB. No GPU involved — which is why hello-world doesn't need a single GX_* 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.
Analogy · Opening the theatre yourself

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.

First-frame lab — the hello-world boot sequenceCanvas · simulation
state powered off vsyncs
A dramatisation of the real boot order: video init (black) → console init (cursor) → printf → the WaitVSync loop. START calls exit(0), handing control back to the loader.
Key takeaways
  • With no OS, your program boots the display itself: init the VI, allocate an XFB, configure a mode, un-blank.
  • printf reaches the TV because libogc's console renders text into the framebuffer — newlib formats, libogc draws.
  • MEM_K0_TO_K1 is 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.
Module 06 · Stage 2

From .c to .dol — the build & the executable format

GoalFollow your source through compile, link and convert; then open the DOL format — the GameCube's native executable, shared by every retail game — field by field.

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:

main.c
your source
main.o
compile: PowerPC object code
hello.elf
link: + crt0 + libogc, symbols kept
hello.dol
elf2dol: header + raw sections
  • Compile. powerpc-eabi-gcc -c turns each .c file into a .o object file of PowerPC machine code with unresolved references ("I call something named VIDEO_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. elf2dol strips 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.

Build-pipeline lab — watch make workCanvas · animation
newest artefact main.c size 1.2 KB
Sizes are illustrative. Notice the ELF is far bigger than the DOL: symbols and debug info for you, none of it needed by the console.

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.)

DOL-header lab — inspect a real header, byte by byteinteractive
A synthetic but format-correct header. All values are big-endian, because the target is a big-endian PowerPC — your x86 laptop would read every one of these numbers byte-reversed, a classic porting gotcha.
Analogy · The moving-day manifest

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.

Key takeaways
  • The pipeline is compile → link → convert: gcc makes objects, the linker builds an ELF against libogc, elf2dol emits 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).
Module 07 · Stage 2

Run it — Dolphin first, hardware after

GoalEstablish the fast development loop in Dolphin, then survey the real-hardware boot routes — what each needs and which to choose.

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 main and VIDEO_Init instead 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:

no console needed
Dolphin

Open the DOL/ELF directly. Instant, free, with a debugger attached. Where all development happens regardless of what you own.

needs · just your PC
the standard kit
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".

needs · console + SD adapter + entry point
entry points
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.

needs · a compatible game save, or a Datel disc
the deluxe path
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.

needs · console surgery or a pre-modded unit
the family trick
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.

needs · a softmodded Wii
the historic routes
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.

needs · rare accessories & patience

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.

Analogy · The dress rehearsal and the opening night

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.

Key takeaways
  • 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).
Stage 3

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.

Modules 08–11Difficulty IntermediateLabs 3 interactive · 1 playable
Module 08 · Stage 3

Reading the controller

GoalMaster the PAD API: the scan-per-frame model, the 16-bit button mask, edge-versus-level reads, and the analog inputs.

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.

Controller lab — the live PAD_ButtonsHeld maskCanvas · interactive
keys · XA ZB SX DY QL WR EZ START ←↑↓→stick / d-pad
PAD_ButtonsHeld(0) 0x0000 binary 0000000000000 stick 0, 0
0 (nothing held)
A USB gamepad works too (press a button on it to wake the Gamepad API). The bit assignments shown are libogc's real ones — this is exactly the value your C code receives.
Analogy · The photograph, not the doorbell

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.

Key takeaways
  • 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.
Module 09 · Stage 3

Putting pixels on screen

GoalKnow your three rendering options — poking the XFB directly, the full GX pipeline, or GRRLIB — and understand the YUY2 pixel format the framebuffer actually speaks.

Hello-world drew text without touching the GPU, which reveals the first of three roads to the screen. Choose by ambition:

1 · Poke the XFB
Write pixel values straight into the framebuffer with the CPU. No GPU, no setup, total control — and total responsibility. Perfect for text, plotters, fractals, retro-style 2D and understanding what a framebuffer is.
2 · The GX pipeline
The real thing: Flipper's hardware T&L, TEV stages and texture units, driven through libogc's GX_* API. Everything the graphics course taught, now writable. Steep, powerful, and the only road to real 3D.
3 · GRRLIB
GX with the sharp edges filed off: init in one call, load a PNG, draw sprites and text. The pragmatic choice for 2D games and the gentlest on-ramp that still uses the GPU.

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).

YUY2 lab — two pixels, four bytes, one shared colourinteractive
Try two similar blues (invisible), then pure red next to pure green (the shared U/V averages them). This is why fine single-pixel colour detail smears horizontally in XFB-poked graphics.

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".

Key takeaways
  • 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.
Module 10 · Stage 3

Sound, files & saves

GoalAdd audio through the DSP's homebrew ucode, get assets into your program — baked in or from SD — and save progress to a memory card.

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 becomes mysprite_png[] and mysprite_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.)

the DOL, in RAM code + baked-in assets SD card (libfat) streamed assets, big data memory card (CARD) saves, like a retail game your running game + AESND voices on the DSP write save read save
A homebrew game's data geography. Small games bake everything into the DOL and ship as one file; bigger ones stream from the SD card exactly as retail games streamed from disc. Saves go to the memory card through the same CARD interface retail games used — your game is a first-class citizen of the machine.
Analogy · The food truck and the restaurant

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.

Key takeaways
  • 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.
Module 11 · Stage 3

The game loop & the frame budget

GoalInternalise the scan → update → draw → WaitVSync loop, the 16.7-millisecond budget it must fit in, and what missing that budget looks and feels like.

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:

the canonical 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.

Analogy · Live television

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.

Frame-budget lab — a playable loop you can overloadCanvas · playable
presenting 60 fps missed vsyncs 0 score 0
←/→, A/D or mouse to move. The meter along the bottom is your frame time against the 16.7 ms budget: green fits, red misses — and the instant it crosses the line, 60 Hz becomes 30. There is no 45.
Key takeaways
  • 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.
Stage 4

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.

Modules 12–13Difficulty Easy readingMood graduation
Module 12 · Stage 4

From DOL to disc — packaging & distribution

GoalChoose the right shipping container — bare DOL versus full disc image — understand what building a GCM actually involves, and release your work like a good citizen of the scene.

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:

boot header
The disc's identity block: game code, maker code, title. Yours to invent — homebrew traditionally picks a playful ID.
apploader
The little bootstrap program the IPL runs off the disc. Nintendo's is copyrighted, so a homebrew image needs a replacement — and the scene wrote open ones that do the same job: load the DOL, jump to it.
main DOL
Your executable. The same bytes you'd have shipped bare.
FST + files
The filesystem table and your asset files, laid out exactly as the disc course's "what's on the disc" module described — so your game can use real 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_GetPreferredMode pattern 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.
Analogy · The zine and the hardcover

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.

Key takeaways
  • 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.
Module 13 · Stage 4

Epilogue: your turn

GoalClose the collection's loop: see the whole path from your keyboard to the scanline, collect the best next steps, and go build something.

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:

main.c
your idea, in C (05)
powerpc-eabi-gcc
cross-compiled + libogc (02–03)
hello.dol
the console's own format (06)
Dolphin / Swiss
loaded & jumped to (07)
the machine
Gekko · Flipper · DSP · your loop (08–11)

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.

Course complete ✦
  • 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.

Part of the Inside the GameCube collection. Every screen, controller and build on this page is a synthetic browser simulation; the page ships no Nintendo code, tools or assets, and neither does the toolchain it teaches. Toolchain and library facts follow the devkitPro project's public documentation and the libogc source; installer and product details drift over time, so where this page and devkitpro.org disagree, trust devkitpro.org. Homebrew described here means running your own software on hardware you own — this page neither contains nor endorses tools for copying games.

Source of truth · devkitpro.org · github.com/devkitPro/libogc · github.com/dolphin-emu/dolphin