Skip to content

Instantly share code, notes, and snippets.

@adamf
Last active June 6, 2026 03:55
Show Gist options
  • Select an option

  • Save adamf/da0fe07d700cd84b5a167f65b19a738b to your computer and use it in GitHub Desktop.

Select an option

Save adamf/da0fe07d700cd84b5a167f65b19a738b to your computer and use it in GitHub Desktop.
Getting OS 9 on a Raspberry Pi 5 to run old games

Mac OS 9 on a Raspberry Pi 5 — A Kid-Friendly Retro Game Machine

This guide turns a Raspberry Pi 5 into an appliance that powers on straight into Mac OS 9.2.1, with working sound, a big-button game launcher a young child can operate alone, and a clean way to keep adding classic games (CD images, floppy images, installers).

It is written so someone comfortable with a Linux terminal — but new to QEMU and classic Macs — can follow it end to end.

What you provide: You must supply your own Mac OS 9 install media and game images. Mac OS 9 and its ROMs remain Apple's copyrighted property; this guide does not cover obtaining them.


Table of contents

  1. What you need
  2. Why a custom QEMU build (qemu-screamer)
  3. Build qemu-screamer on the Pi
  4. Install Mac OS 9.2.1
  5. The working launch command
  6. Boot the Pi straight into Mac OS 9 (appliance mode)
  7. Getting content into Mac OS 9
  8. Virtual CDs: running games that need their disc
  9. The kid-friendly Launcher
  10. Per-game launch scripts
  11. Troubleshooting
  12. Appendix: all the scripts in one place

1. What you need

  • Raspberry Pi 5 (4 GB or 8 GB), running 64-bit Raspberry Pi OS (Bookworm) with the desktop.
  • A Mac OS 9.2.x install ISO (this guide uses 9.2.1).
  • Your game images (.iso, hybrid Mac/PC discs, etc.).
  • Patience for one from-source compile (~20–40 minutes on a Pi 5).

A note on paths: this guide assumes your Linux username is adamf and that your working files live under ~/macos9. Substitute your own username throughout (whoami will tell you what it is).


2. Why a custom QEMU build (qemu-screamer)

The stock qemu-system-ppc you can install with apt emulates a PowerMac G4 (mac99) perfectly well — except it has no working sound. The PowerMac's audio chip ("Screamer") is not in mainline QEMU. To get sound in Mac OS 9 you must build a community fork that adds the Screamer device and ships a matching, sound-enabled OpenBIOS.

A pleasant surprise: the screamer-v9.1.0 branch is rebased onto a modern QEMU and its bundled pc-bios already handles video and color depth correctly — so on this build you do not need any separate VGA/256-color driver patch that older guides mention. One build gets you sound and correct video.


3. Build qemu-screamer on the Pi

3.1 Install build dependencies

sudo apt update
sudo apt install -y git build-essential ninja-build meson pkg-config python3 \
  libglib2.0-dev libpixman-1-dev libsdl2-dev libgtk-3-dev libvte-2.91-dev \
  libpulse-dev libslirp-dev libfdt-dev zlib1g-dev flex bison

Two of these are easy to overlook and cause silent failures later:

  • libpulse-dev — without it the build can't talk to the Pi's audio (PipeWire's PulseAudio layer), and you get a working emulator with no sound.
  • libslirp-dev — required for -netdev user networking; without it the guest has no network.

3.2 Clone the right branch

Use screamer-v9.1.0, not the bare screamer branch (which is old QEMU 4.2-era code that won't compile on the Pi's toolchain):

cd ~
git clone --recursive -b screamer-v9.1.0 https://github.com/mcayland/qemu.git qemu-screamer
cd qemu-screamer

--recursive matters: it pulls the screamer-enabled OpenBIOS submodule.

3.3 Configure and build

mkdir build && cd build
../configure \
  --target-list=ppc-softmmu \
  --audio-drv-list=pa,sdl \
  --enable-pa --enable-sdl --enable-gtk --enable-slirp \
  --disable-werror
make -j4
  • --disable-werror stops a stray compiler warning (common when newer GCC meets older code) from aborting the whole build.
  • If configure reports a missing dependency, it names it — install the matching -dev package and re-run.
  • The finished binary lands at ~/qemu-screamer/build/qemu-system-ppc (modern QEMU is ninja-based and outputs to build/).

3.4 The pc-bios you must use

The sound-enabled QEMU must use the OpenBIOS that ships with it; the stock one produces no sound. That BIOS is right inside your build, so simply point -L at it:

~/qemu-screamer/build/pc-bios

Verification trick: once Mac OS 9 is running, open the Sound control panel → Output tab. If it lists "Spatializer Audio Laboratories", you are running the correct screamer OpenBIOS. If it only shows "built-in," your -L is pointing at the wrong BIOS folder and you'll get silence.


4. Install Mac OS 9.2.1

Create a hard-disk image for the OS to live on:

mkdir -p ~/macos9
qemu-img create -f qcow2 ~/macos9/macos9.img 4G

Boot from the install ISO (note -boot d = boot from CD):

export QEMU_AUDIO_DRV=pa
~/qemu-screamer/build/qemu-system-ppc \
  -M mac99,via=pmu -m 896 \
  -L /home/adamf/qemu-screamer/build/pc-bios \
  -prom-env 'vga-ndrv?=true' \
  -hda /home/adamf/macos9/macos9.img \
  -cdrom /home/adamf/macos9/MacOS921.iso \
  -boot d -g 1024x768x32 -display sdl \
  -device usb-kbd -device usb-mouse \
  -netdev user,id=net0 -device sungem,netdev=net0

Then, inside the emulator:

  1. Open Utilities → Drive Setup, select the uninitialized disk, click Initialize, and format it as Mac OS Extended.
  2. Run Mac OS Install and install onto that freshly formatted disk.
  3. When it finishes, Special → Shut Down.

After install, you boot from the hard disk by switching -boot d to -boot c and removing the -cdrom line (see the next section).

Sound prerequisite: keep guest RAM under 1024 MB (we use -m 896), and after first boot open the Memory control panel and turn Virtual Memory ON. The screamer audio will not work without both.


5. The working launch command

This is the everyday command once the OS is installed. Save it as a script (~/macos9/start-os9.sh).

#!/bin/bash
export QEMU_AUDIO_DRV=pa
~/qemu-screamer/build/qemu-system-ppc \
  -M mac99,via=pmu -m 896 \
  -L /home/adamf/qemu-screamer/build/pc-bios \
  -prom-env 'vga-ndrv?=true' \
  -hda /home/adamf/macos9/macos9.img \
  -boot c -g 1024x768x32 -display sdl \
  -device usb-kbd -device usb-mouse \
  -netdev user,id=net0 -device sungem,netdev=net0 \
  -full-screen
sudo poweroff   # optional: power the Pi off when Mac OS 9 is shut down

Make it executable: chmod +x ~/macos9/start-os9.sh

Key choices, and why:

Flag Why it's there
QEMU_AUDIO_DRV=pa This screamer fork predates QEMU's modern audio API, so -audiodev errors out. The environment variable is the correct way to route sound to PipeWire.
no -device screamer On this fork the Screamer is wired into the mac99 machine automatically. Adding it as a device is unnecessary (and it isn't a pluggable device).
-m 896 Under the 1024 MB ceiling required for sound.
-display sdl The default GTK/Wayland path makes the mouse uncontrollable in Mac OS 9. SDL fixes the pointer.
-device usb-mouse (not usb-tablet) For 9.2.x, a plain USB mouse + keyboard is the reliable combo. The tablet is experimental and tends to freeze.
-full-screen Hides the Linux desktop so it feels like a real Mac.
sudo poweroff Makes "Shut Down" in Mac OS 9 power off the Pi, like real hardware. Optional.

For passwordless poweroff, add a sudoers rule with sudo visudo:

adamf ALL=(ALL) NOPASSWD: /sbin/poweroff

6. Boot the Pi straight into Mac OS 9 (appliance mode)

Goal: power on → Mac OS 9 fullscreen, no Linux visible, no typing.

6.1 Auto-login to the desktop

sudo raspi-config

System Options → Boot / Auto Login → Desktop Autologin. While you're there, Display Options → Screen Blanking → disable, so the screen doesn't blank during play.

6.2 Auto-launch on login (compositor-agnostic)

Raspberry Pi OS Bookworm defaults to the labwc Wayland compositor (Wayfire on older releases). Rather than edit a compositor-specific config, use the freedesktop autostart folder, which works on X11, Wayfire, and labwc:

mkdir -p ~/.config/autostart
nano ~/.config/autostart/macos9.desktop
[Desktop Entry]
Type=Application
Name=Mac OS 9
Exec=/home/adamf/macos9/start-os9.sh
Terminal=false
X-GNOME-Autostart-enabled=true

Reboot. The Pi should go: power-on → (brief desktop flash) → Mac OS 9 fullscreen. Set a black desktop wallpaper in Linux to hide that momentary flash.


7. Getting content into Mac OS 9

Mac files carry resource forks and type/creator codes that plain Linux file copies strip away. Data files (images, ROMs, save files) survive a raw copy; applications do not — they arrive as dead, blank-icon files. Choose the transfer method accordingly.

7.1 The exchange disk (best for data files and archives)

A second disk image you pass between Linux and Mac OS 9 like a USB stick.

qemu-img create -f raw ~/macos9/exchange.img 2G

Add it to the launch command:

-drive file=/home/adamf/macos9/exchange.img,format=raw,media=disk

Format it once, inside Mac OS 9 with Drive Setup (an OS-9-initialized disk gets the Apple partition map QEMU expects; a bare mkfs from Linux often won't mount).

Golden rule: never mount the exchange disk in Linux and Mac OS 9 at the same time — that corrupts it. Shut QEMU down first.

Mounting it on the Pi (QEMU off):

sudo losetup -fP --show ~/macos9/exchange.img   # prints e.g. /dev/loop0
lsblk /dev/loop0                                 # find the HFS+ partition (usually p2)
sudo mkdir -p /mnt/exchange
sudo mount -t hfsplus /dev/loop0p2 /mnt/exchange
# ...copy files...
sudo umount /mnt/exchange
sudo losetup -d /dev/loop0

If it mounts read-only or complains it wasn't cleanly unmounted, add -o force. If hfsplus is unknown, sudo apt install hfsprogs && sudo modprobe hfsplus.

.sit archives (StuffIt — the classic Mac equivalent of .zip) are a great fit for this route: they're a single data-fork file, so they survive the Linux copy intact, and you expand them inside Mac OS 9 with StuffIt Expander (drag the file onto its icon if double-click doesn't work).

7.2 FTP (best for applications — preserves resource forks)

For transferring whole apps, FTP with MacBinary keeps the resource fork intact. The robust direction through QEMU's NAT is to run the FTP server on the Pi and an FTP client inside Mac OS 9 (Fetch/Anarchie) in passive mode — that needs no port forwarding at all. Alternatively, run NetPresenz in Mac OS 9 and forward a port:

-netdev user,id=net0,hostfwd=tcp::2121-:21 -device sungem,netdev=net0

7.3 Mounting game ISOs and hot-swapping discs

Attach an ISO as a CD:

-cdrom /home/adamf/macos9/games/somegame.iso

To swap discs without rebooting, expose the QEMU monitor and use change. Add a monitor socket to the launch command:

-monitor unix:/tmp/qemu-mon,server,nowait

Then from any shell:

echo "change ide1-cd0 /home/adamf/macos9/games/nextgame.iso" | socat - UNIX-CONNECT:/tmp/qemu-mon

(If change says no such device, run info block in the monitor to see the real CD device name.)

7.4 Floppy images

The emulated G4 has no floppy drive (-fda fails on mac99). Handle floppy images entirely inside Mac OS 9: transfer the .img/.dsk in, then drag it onto Disk Copy (built in, version 6.3.3), which mounts it like an inserted floppy. .smi files are self-mounting — just double-click. To extract files from an HFS floppy on the Pi instead: hmount floppy.img, hls, hcopy -m "File" ./File, humount (from the hfsutils package; -m preserves the resource fork).


8. Virtual CDs: running games that need their disc

Most classic games expect their CD in the drive at runtime and won't install fully to disk. Swapping physical CDs by hand is painful and not something a child can do. The fix is the Virtual CD/DVD-ROM Utility (from Macintosh Garden), which mounts an ISO file so games believe a real disc is present — it passes most copy-protection checks that plain Disk Copy mounts fail.

Setup, once:

  1. Get the Utility into Mac OS 9 and expand it (it includes a companion AutoTyper).
  2. Run each game ISO through the AutoTyper once — this flags the file so it mounts as a CD when opened (double-click or via a script).
  3. Store all your game ISOs on a big second disk image (games.img, format in Drive Setup) so Mac OS 9 always sees the files.

Hybrid discs mount two volumes. A Mac/PC hybrid ISO appears as two desktop icons: the sane-named, disk-icon volume is the HFS (Mac) side — use this one for launching; the short-named, DVD-icon volume is the ISO 9660 side (names get truncated/uppercased, e.g. ODELL_DOWN_UNDER) — ignore it for launching, but remember it when ejecting (see below).

Note: a few games used deeper disc-geometry protection and may still refuse a virtual mount. For those rare cases, mount that one game's ISO via -cdrom at boot.


9. The kid-friendly Launcher

Mac OS 9 has a built-in Launcher that is perfect for a young child: a window of big, single-click buttons.

  1. Make an alias of each game (or each launch script — see next section): select it, File → Make Alias.
  2. Drop the aliases into System Folder → Launcher Items.
  3. Open Launcher (Control Panels), ⌘-click the window, choose Large buttons.
  4. In General Controls, tick Show Launcher at system startup.

Now: power on → Mac OS 9 → full screen of big game buttons → she taps one to play.

To give each button the game's real artwork: Get Info (⌘-I) on the game/CD, click its icon top-left, ⌘-C; then Get Info on your launcher app, click its icon, ⌘-V. (Set icons after the file is in Mac OS 9 — copying through the Linux mount strips them.)


10. Per-game launch scripts

For a seamless one-tap experience — especially for games that need their CD mounted before the app starts — use a small AppleScript per game that mounts the disc, launches the game, waits for it to quit, then ejects everything. Each script is saved as an application and placed in the Launcher.

Dependencies (install into System Folder → Scripting Additions):

  • Jon's Commands — sets screen resolution and color depth.
  • ProcessInfo — helps the wait-loop detect when the game has quit.

Both are on Macintosh Garden, distributed as .sit.

Saving the script correctly

In Script Editor, File → Save As, and set Kind = Application (not "Script", which opens in the editor when clicked; not "Run Only", which strips the source you still want to edit). Tick Never Show Startup Screen. Confirm with Get Info that it reads "Kind: application program".

Template A — self-contained CD game

The game app lives on the mounted CD.

-- (1) Mount the CD (an AutoTyped ISO on your games disk)
open file "Games:RR_1st_Grade_Mac.iso"
delay 3  -- let the virtual CD appear

-- (2) Set display (copy the exact depth/resolution line from Jon's Commands;
--     256 colors = depth 8; pick 640x480 or 800x600 to match the game)

-- (3) Launch the app ON THE MOUNTED CD (path starts at the Mac volume's name)
tell application "Finder"
    open file "Reader Rabbit 1st Grade:Reader Rabbit 1st Grade"
end tell

-- (4) Wait for it to quit
delay 5
repeat while (exists process "Reader Rabbit 1st Grade")
    delay 2
end repeat

-- (5) Clean up: eject every removable disc (handles both hybrid volumes)
tell application "Finder"
    eject (every disk whose ejectable is true)
end tell

Template B — installed game that needs its CD for data

The app is installed on the hard disk; the CD must be mounted first so the app finds its data. Only the launch path changes (it points at the installed app), and the mount-before-launch order — which the script guarantees — is what makes it work.

-- (1) Mount the data CD
open file "Games:JumpStart 1st Grade 2000.iso"
delay 3

-- (2) Set display (Jon's Commands; depth 8 for 256-color titles)

-- (3) Launch the INSTALLED app on the hard disk
tell application "Finder"
    open file "Macintosh HD:Games:JumpStart 1st Grade:JumpStart 1st Grade"
end tell

-- (4) Wait for it to quit
delay 5
repeat while (exists process "JumpStart 1st Grade")
    delay 2
end repeat

-- (5) Clean up
tell application "Finder"
    eject (every disk whose ejectable is true)
end tell

Tips that save real debugging time

  • Get exact disc names by running, while mounted: tell application "Finder" to get name of every disk. Hybrid ISO volume names are often truncated in Get Info, so don't type them from memory.
  • Eject everything (eject (every disk whose ejectable is true)) is more robust than naming volumes — it clears both halves of a hybrid disc automatically.
  • If the wait-loop never ends, the process name is wrong; use ProcessInfo to find the real one.
  • Your three button types — self-contained CD (alias of AutoTyped ISO or Template A), installed-plus-CD (Template B), and fully-installed (alias of the app) — all look identical to the child. Only the wiring differs.

11. Troubleshooting

No sound. Check the Sound control panel shows "Spatializer Audio Laboratories" (confirms correct OpenBIOS via -L); confirm QEMU_AUDIO_DRV=pa; confirm guest RAM < 1024 MB and Virtual Memory is ON. Expect the audio to be somewhat crackly/choppy even when correct — that's normal for the screamer fork. Make QEMU the only thing using the Pi's audio.

Mouse drifts or becomes uncontrollable. Use -display sdl. Use -device usb-mouse, not usb-tablet. Ctrl+Alt+G releases a captured pointer back to Linux.

make fails on the build. With --disable-werror most warnings won't stop it; if a hard error appears, it's usually one file needing a small patch on newer GCC.

Login loop in Raspberry Pi OS (screen flashes back to the login prompt). Common causes, checked from a text console (Ctrl+Alt+F2): a root-owned ~/.Xauthority (from running a GUI app with sudo) — chown it back or delete it; a full disk (df -h); or missing desktop packages — sudo apt install --reinstall raspberrypi-ui-mods lxsession openbox lightdm after sudo dpkg --configure -a.

Autostart .desktop doesn't fire. Verify the Exec= path and that the script is executable; test the script by hand first. As a fallback, a systemd user service launches independently of the compositor.

Game screen is garbled. It likely needs 256 colors — set the depth in the Monitors control panel (and in the per-game script via Jon's Commands).


Appendix: all the scripts in one place

~/macos9/start-os9.sh (everyday launch / appliance)

#!/bin/bash
export QEMU_AUDIO_DRV=pa
~/qemu-screamer/build/qemu-system-ppc \
  -M mac99,via=pmu -m 896 \
  -L /home/adamf/qemu-screamer/build/pc-bios \
  -prom-env 'vga-ndrv?=true' \
  -hda /home/adamf/macos9/macos9.img \
  -boot c -g 1024x768x32 -display sdl \
  -device usb-kbd -device usb-mouse \
  -netdev user,id=net0 -device sungem,netdev=net0 \
  -full-screen
sudo poweroff

~/.config/autostart/macos9.desktop

[Desktop Entry]
Type=Application
Name=Mac OS 9
Exec=/home/adamf/macos9/start-os9.sh
Terminal=false
X-GNOME-Autostart-enabled=true

Add-ons for the launch command when you need them

# exchange disk (data transfer)
-drive file=/home/adamf/macos9/exchange.img,format=raw,media=disk

# games disk (holds AutoTyped ISOs)
-drive file=/home/adamf/macos9/games.img,format=raw,media=disk

# a game CD at boot (for the rare game that refuses a virtual mount)
-cdrom /home/adamf/macos9/games/somegame.iso

# monitor socket (scripted disc hot-swap)
-monitor unix:/tmp/qemu-mon,server,nowait

# FTP port forward (if running NetPresenz inside Mac OS 9)
-netdev user,id=net0,hostfwd=tcp::2121-:21 -device sungem,netdev=net0

Mount/unmount the exchange disk on Linux (QEMU OFF)

sudo losetup -fP --show ~/macos9/exchange.img
lsblk /dev/loop0
sudo mount -t hfsplus /dev/loop0p2 /mnt/exchange     # add -o force if needed
# ...copy files...
sudo umount /mnt/exchange
sudo losetup -d /dev/loop0

Built and tested on a Raspberry Pi 5 (64-bit Raspberry Pi OS, Bookworm) with the qemu-screamer screamer-v9.1.0 fork and Mac OS 9.2.1. The screamer build's bundled pc-bios provided correct video and color depth, so no separate VGA driver patch was needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment