I Ported Mac OS X to the Nintendo Wii

A developer successfully ported Mac OS X 10.0 (Cheetah) to the Nintendo Wii, overcoming hardware limitations and technical skepticism. The project involved building a custom bootloader and patching the XNU kernel to run on the console's PowerPC architecture.
Porting Mac OS X to the Nintendo Wii
Mac OS X 10.0 (Cheetah) running natively on the Nintendo Wii
Since its launch in 2007, the Wii has seen several operating systems ported to it: Linux, NetBSD, and most-recently, Windows NT. Today, Mac OS X joins that list.
In this post, I’ll share how I ported the first version of Mac OS X, 10.0 Cheetah, to the Nintendo Wii. If you’re not an operating systems expert or low-level engineer, you’re in good company; this project was all about learning and navigating countless “unknown unknowns”. Join me as we explore the Wii’s hardware, bootloader development, kernel patching, and writing drivers - and give the PowerPC versions of Mac OS X a new life on the Nintendo Wii.
Feasibility Investigation
Before figuring out how to tackle this project, I needed to know whether it would even be possible. According to a 2021 Reddit comment:
There is a zero percent chance of this ever happening.
Feeling encouraged, I started with the basics: what hardware is in the Wii, and how does it compare to the hardware used in real Macs from the era.
Hardware Compatibility
The Wii uses a PowerPC 750CL processor - an evolution of the PowerPC 750CXe that was used in G3 iBooks and some G3 iMacs. Given this close lineage, I felt confident that the CPU wouldn’t be a blocker.
As for RAM, the Wii has a unique configuration: 88 MB total, split across 24 MB of 1T-SRAM (MEM1) and 64 MB of slower GDDR3 SDRAM (MEM2); unconventional, but technically enough for Mac OS X Cheetah, which officially calls for 128 MB of RAM but will unofficially boot with less. To be safe, I used QEMU to boot Cheetah with 64 MB of RAM and verified that there were no issues.
Other hardware I’d eventually need to support included:
- Serial debug output via a USB Gecko
- The SD card for booting the rest of the system once the kernel was running
- Interrupt controllers
- Video output via a framebuffer that lives in RAM
- The Wii’s USB ports for using a mouse and keyboard
Convinced that the Wii’s hardware wasn’t fundamentally incompatible with Mac OS X, I moved my attention to investigating the software stack I’d be porting.
Software Compatibility
Mac OS X has an open source core (Darwin, with XNU as the kernel and IOKit as the driver model), with closed-source components layered on top (Quartz, Dock, Finder, system apps and frameworks). In theory, if I could modify the open-source parts enough to get Darwin running, the closed-source parts would run without additional patches.
Porting Mac OS X would also require understanding how a real Mac boots. PowerPC Macs from the early 2000s use Open Firmware as their lowest-level software environment; for simplicity, it can be thought of as the first code that runs when a Mac is powered on. Open Firmware has several responsibilities, including:
- Hardware detection and configuration
- Device tree construction (based on the detected hardware)
- Providing useful functions for I/O, drawing, and hardware communication
- Loading and executing an operating system bootloader from the filesystem
Open Firmware eventually hands off control to BootX, the bootloader for Mac OS X. BootX prepares the system so that it can eventually pass control to the kernel. The responsibilities of BootX include:
- Reading the device tree from Open Firmware
- Loading and decoding the XNU kernel, a Mach-O executable, from the root filesystem
- Passing control to the kernel
Once XNU is running, there are no dependencies on BootX or Open Firmware. XNU continues on to initialize processors, virtual memory, IOKit, BSD, and eventually continue booting by loading and running other executables from the root filesystem.
The last piece of the puzzle was how to run my own custom code on the Wii - a trivial task thanks to the Wii being “jailbroken”, allowing anyone to run homebrew with full access to the hardware via the Homebrew Channel and BootMii.
Porting Approach
Armed with knowledge of how the boot process works on a real Mac, along with how to run low-level code on the Wii, I needed to select an approach for booting Mac OS X on the Wii. I evaluated three options:
- Port Open Firmware, use that to run unmodified BootX to boot Mac OS X
- Port BootX and modify it to not rely on Open Firmware, use that to boot Mac OS X
- Write a custom bootloader that performs the bare-minimum setup to boot Mac OS X
Since Mac OS X doesn’t depend on Open Firmware or BootX once running, spending time porting either of those seemed like an unnecessary distraction. Additionally, both Open Firmware and BootX contain added complexity for supporting many different hardware configurations - complexity that I wouldn’t need since this only needs to run on the Wii. Following in the footsteps of the Wii Linux project, I decided to write my own bootloader from scratch. The bootloader would need to, at a minimum:
- Initialize the Wii’s hardware
- Load the kernel from the SD card
- Construct a device tree and boot arguments
- Pass control to the kernel
Once the kernel was running, none of the bootloader code would matter. At that point, my focus would shift to patching the kernel and writing drivers.
Writing a Bootloader
I decided to base my bootloader on some low-level example code for the Wii called ppcskel. ppcskel puts the system into a sane initial state, and provides useful functions for common things like reading files from the SD card, drawing text to the framebuffer, and logging debug messages to a USB Gecko.
Loading the Kernel
Next, I had to figure out how to load the XNU kernel into memory so that I could pass control to it. The kernel is stored in a special binary format called Mach-O, and needs to be properly decoded before being used.
The Mach-O executable format is well-documented, and can be thought of as a list of load commands that tell the loader where to place different sections of the binary file in memory. After processing all of the kernel’s load commands, we end up with this memory layout:
0x00000000: Exception vectors
0x00011000: LC_SEGMENT __TEXT
0x002e0000: LC_SEGMENT __DATA
0x00367000: LC_SEGMENT __KLD
0x00395000: LC_SEGMENT __LINKEDIT
0x00434000: LC_SEGMENT __SYMTAB
0x004d3000: LC_SEGMENT __HEADER
The kernel file also specifies the memory address where execution should begin. Once the bootloader jumps to this address, the kernel is in full control and the bootloader is no longer running.
Calling the Kernel
To jump to the kernel-entry-point’s memory address, I needed to cast the address to a function and call it:
(*(void (*)())kernel_entry_point)(boot_args_address, MAC_OS_X_SIGNATURE);
After this code ran, the screen went black and my debug logs stopped arriving via the serial debug connection - while anticlimactic, this was an indicator that the kernel was running.
The question then became: how far was I making it into the boot process? To answer this, I had to start looking at XNU source code. The first code that runs is a PowerPC assembly _start routine. This code reconfigures the hardware, overriding all of the Wii-specific setup that the bootloader performed and, in the process, disables bootloader functionality for serial debugging and video output. Without normal debug-output facilities, I’d need to track progress a different way.
The approach that I came up with was a bit of a hack: binary-patch the kernel, replacing instructions with ones that illuminate one of the front-panel LEDs on the Wii. If the LED illuminated after jumping to the kernel, then I’d know that the kernel was making it at least that far.
Source: Hacker News















