2013年,一个大二学生在网上发帖说想干一件事——把Mac OS X移植到任天堂Wii上。
没人鸟他。
8年后的2021年,有人在Reddit上认真回复:"这件事发生的概率是零。"
又过了5年。
2026年4月8日,这个大二学生(现在已经是资深工程师了)在GitHub Pages上发了一篇文章,标题简单到不像话——
"I ported Mac OS X to the Nintendo Wii"
苹果公司在2001年发布的操作系统,跑在任天堂2006年发布的游戏机上。
中间隔了25年。
中间隔着两家公司、两种产品哲学、无数行代码。
但它就是跑起来了。
一个"零概率"项目的起点
Bryan Keller第一次动这个念头是在2013年。
那时候他还是个大学生,对底层开发几乎一无所知。把Mac OS X这种级别的系统移植到一台游戏机上?这想法就像是说"我想把飞机引擎装到自行车上"——技术上不是不可能,但正常人不会这么想。
这一想,就想了10年。
日常工作已经够复杂了,没必要再给自己找事。但2025年,他看到Windows NT被移植到Wii上,突然又坐不住了。
"就算我经验不足搞失败了,至少也能学点东西。"
就这么一句话,他开始干。
他给自己定了个规矩:写自己的bootloader,而不是移植Open Firmware或者BootX。为什么?因为Mac OS X一旦跑起来,根本不依赖这两者。多余的代码只会增加复杂度。
而他只需要让系统跑在一台特定的机器上——Wii。
88MB内存,够吗?
先看硬件。
Wii用的是PowerPC 750CL处理器。这颗芯片的血统很纯——它是G3 iBook和部分G3 iMac所用处理器的后代。CPU层面,没问题。
关键是内存。
Wii的内存配置很奇葩:88MB,分成两部分。24MB是1T-SRAM(MEM1),速度快但容量小;64MB是GDDR3 SDRAM(MEM2),慢但容量大。
而Mac OS X 10.0 Cheetah官方要求多少?
128MB。
差了40MB,将近一半。
换别人可能就放弃了。但Bryan做了个测试——用QEMU模拟64MB内存启动Cetah。居然能跑。
"那行,先这么着。"
boot_args结构体里要填device tree,他就硬编码了一个。bootloader要加载kernel,他就自己写了解析Mach-O文件的代码。
每一步都是坑,每一步都像在黑暗中摸索。
屏幕黑了,然后呢?
第一次跳转到kernel的时候,屏幕黑了。
串口调试输出也停了。
这意味着什么?Kernel可能正在跑,也可能已经崩了。没有任何反馈。
怎么知道进行到哪了?
Bryan想了个损招——改kernel代码,让它控制Wii前面板的LED灯。
在PowerPC汇编里,往特定内存地址写值就能点亮LED。他把kernel里各个函数的地址和源码交叉对照,找到合适的位置,替换成LED控制指令。
然后观察LED亮不亮。
亮到哪一步,就知道跑到哪一步。
这个过程他画了张流程图:
1. start.s: start
2. start.s: allStart
3. start.s: nextPVR
...
10. ppc_init.c: ppcInit
11. pe_init.c: PE_INIT_PLATFORM
12. device_tree.c: find_entry (crash with 300 exception)
跑到第12步,崩了。
原因是device tree没传对。
自己造一个设备树
device tree是什么?
想象一棵倒过来的树。树根代表整台机器,子节点代表CPU、内存、显卡、USB控制器等各种硬件。操作系统靠这棵树"认识"自己身上有哪些零件。
在真正的Mac上,Open Firmware会在启动时自动扫描硬件、生成这棵树。但Wii没有Open Firmware,而且Wii的硬件结构完全不一样——它用的是一块叫Hollywood的系统级芯片,用IPC和ARM协处理器(代号Starlet)通信。
没有现成的,那就自己造。
Bryan直接从Wii Linux项目抄——不对,是"借鉴"。他把device tree硬编码进bootloader里,核心就三个节点:
/
├── cpus
│ └── PowerPC,750
└── memory
就这俩。CPU和内存。
其他的,后面再补。
填好device tree指针,boot_args结构体一塞——过了。
kernel不崩了,开始往下跑。
然后卡在"Still waiting for root device"。
写驱动,从零开始
Mac OS X的驱动模型叫IOKit。Bryan之前没接触过这东西,花了好一阵子才搞明白里面的弯弯绕。
简单说,IOKit有两类东西:Driver和Nub。
Driver管具体设备,比如一块网卡。Nub是个"接口",让Driver能连到系统总线上。
在真正的Mac上,PCI总线由IOPCIBridge驱动扫描,发现一块网卡,就创建一个IOPCIDevice Nub。网卡的Driver看到这个Nub,附着上去,就能通信了。
但Wii不用PCI。
它用Hollywood SoC,定制芯片,没有现成的IOKit驱动家族可用。
那就自己写。
Bryan设计了一套自己的驱动架构:
NintendoWiiHollywood(主驱动)
├── NintendoWiiHollywoodDevice(Nub)
│ ├── NintendoWiiSDCard(SD卡驱动)
│ └── NintendoWiiFramebuffer(显卡驱动)
每一个子设备都是一个Nub,每一个Nub都可以挂新的Driver。
先从SD卡开始写。
SD卡驱动要实现IOBlockStorageDevice的一大堆虚方法。大部分都是硬编码返回值——厂商字符串、块大小、最大传输量之类的。真正难的是读写操作。
Wii的SD卡通过Starlet协处理器上的MINI程序控制。Bryan要在PowerPC端写特定的内存地址发命令,MINI执行完再把结果写到另一块内存。
这中间有个坑:缓存。
SD卡读出来的数据可能还留在cache里没刷到RAM,PowerPC读的时候读的是cache里的旧数据。
解决方案:用非缓存的内存区域做缓冲区。
颜色是洋红色的
SD卡能动了。
接下来是显卡。
Mac OS X的GUI需要一个真正的帧缓冲驱动。Bryan写了NintendoWiiFramebuffer,继承自IOFramebuffer。
但有个问题——颜色全变了。
洋红色。所有的颜色都是洋红色。
为什么?
Wii的视频编码器是为模拟电视信号优化的,它expect的是YUV格式的像素数据。但Mac OS X写的是RGB。
一个要YUV,一个给RGB。
颜色能不诡异吗?
解决方案:双缓冲。
一块RGB帧缓冲给Mac OS X用,一块YUV帧缓冲给Wii硬件用。每秒60次,把RGB转成YUV写进去。
这活儿Wii Linux项目很多年前就干过。Bryan直接照搬思路。
颜色终于正常了。
Mac OS X的图标、菜单、Finder——都出来了。
但有个问题:没法操作。
没有键盘,没有鼠标。
USB这道坎
Wii的后置USB口是USB 1.1 OHCI标准。Bryan本想直接用Mac OS X自带的AppleUSBOHCI驱动。
结果撞墙三道。
第一道:Mac OS X 10.0(Cheetah)的IOUSBFamily源码没开源。10.2(Jaguar)之后的才有。没有源码,出了问题没法调试。
第二道:AppleUSBOHCI的驱动配置要求provider必须是IOPCIDevice。Wii不用PCI,根本不会创建这种Nub。匹配不上,驱动根本不启动。
第三道:就算强行让它启动,还有字节序的问题。AppleUSBOHCI在软件层做字节序转换,但Wii的硬件层已经做过一次了。双重转换,数据全乱。
怎么办?
第一道坎:Bryan上IRC求助。在一个古早的CVS仓库里找到了Cheetah时代的IOUSBFamily源码。
第二道坎:写了个假的IOPCIDevice Nub,骗过驱动匹配。
第三道坎:用Ghidra反汇编,一行一行找字节交换指令,注释掉。
"手打的AppleUSBOHCI二进制脆弱到几乎不可能维护,而且几乎肯定是错的。"
他自己在文章里写的。
但最后居然跑通了。
键盘亮了,鼠标能动了。
Wii变成了一台Mac。
经济舱里的kernel panic
读文章的时候注意到一张图——
一个经济舱座位,小桌板上放着MacBook,屏幕上是串口调试输出。
注释写的是:"在修复USB bug的时候"。
另一张照片——夏威夷的酒店里,Wii接在电视上,屏幕上是Mac OS X的桌面。
他带着Wii去旅行,因为"项目正到关键时刻,停不下来"。
一个"不可能"的故事
文章开头,Bryan专门引用了那条Reddit评论:
"这件事发生的概率是零。"
"Feeling encouraged."
他用了一个词:encouraged。
被"鼓励"到了。
这就是整篇文章的张力所在。
一个人,十几年的念头,断断续续的推进。没有团队,没有预算,没有任何商业目的。
只有一个问题:能不能做到。
最后他做到了。
"那些看起来遥不可及的项目,恰恰是值得去做的。"
文章写于2026年4月。
【锐评】:这年头敢在GitHub Pages发这种纯技术长文的人不多了,更难得的是他把"如何把不可能变成可能"写得像侦探小说一样好看。技术细节足够硬,故事节奏足够爽,结尾那句"encouraged"简直是点睛之笔。
参考链接:
https://bryankeller.github.io/2026/04/08/porting-mac-os-x-nintendo-wii.html