2026 年 10 月 29 日,开发者 Rairii 在 Virtually Fun 公布了一项技术成果。经典 Mac OS 7.6 与 9.2.2 成功运行在任天堂 Wii 主机上,并进到了图形桌面。在真机环境下,系统依靠速度最慢的 SD over SPI 模式读取镜像,冷启动耗时仅 40 秒。这是自 1990 年代 CHRP 克隆机时代落幕之后,经典 Mac OS 首次在非苹果硬件上完整启动到桌面。
Wii 搭载的 Broadway 处理器与苹果 PowerMac 同属 PowerPC 架构,二者指令集天然兼容。但在游戏机上运行封闭系统,真正的工程障碍深植于引导链。苹果固件与内核写死了大量物理地址映射,设备树假设也难以撼动。只要用开源固件构造合规的外壳骗过引导器,PowerPC 内核就能在任天堂芯片组上如期运转。
Rairii 披露,该项目始于 2025 年 6 月 22 日拉取 upstream openbios 仓库。作者在文中声明,整个项目的逆向工程、内核修改与驱动编写均由个人手工完成。整个项目未引入任何编码 agent 或大型语言模型辅助。
借道 OpenBIOS:重构内存映射骗过 Trampoline
New World 架构的经典 Mac OS 依赖名为 Trampoline 的引导程序。该程序运行在 Open Firmware 环境中,负责加载内核并交接硬件控制权。要让游戏机运行 Mac OS,首要任务是在任天堂硬件上跑起开源的 OpenBIOS。
目标硬件属于广义的 ArtX 芯片组家族。该系列始于 GameCube 的 Flipper,延续至 Wii 的 Vegas 与 Bollywood,后续还将覆盖 Wii U 的 Latte。这些芯片组的内存拓扑与苹果主板存在显著差异。
Trampoline 内部硬编码了一项假设:物理地址 0x8000_0000 以上必须是内存映射 I/O(MMIO)。这套地址在苹果设备上对应 PCI 总线空间,在任天堂芯片组上却并不存在。在将来的 Wii U 平台上,该段高位地址甚至被划归为主内存。
为了解决地址冲突,Rairii 调整了物理地址映射表。补丁把物理地址 0x0100_0000 映射到虚拟地址 0xE000_0000,长度设为 0x1000_0000。重构后的地址空间覆盖了整块 MMIO 与部分 RAM,消除了引导器寻址越界问题。 代码同时删除了旧版 Trampoline 无条件建立的等额 1:1 映射,避免映射重叠引发内核恐慌。
OpenBIOS 还需要在设备树中编造硬件假象。系统将中断控制器伪装成小端序 PCI 中断控制器,其寄存器布局与 Flipper 最为贴近。帧缓冲地址被修正为未缓存虚拟地址。设备树中还注入了一个虚构的 Mac99 兼容电源管理节点。
图:Mac OS 7.6 在修改后的 Dolphin 模拟器中启动至桌面。来源:Virtually Fun / Rairii
替换中断控制器:内核只打一处补丁
设备树伪装完成后,控制权移交给了 Mac OS 内核。面对架构差异极大的游戏主机,作者仅对内核本身打了一处补丁。这一克制策略最大限度保留了操作系统的原始逻辑。
PowerPC 版 Mac OS 内核内置了多款历史中断控制器驱动。Rairii 挑选出与设备树对应的一组驱动,将其指令覆写为匹配 Flipper 的硬件逻辑。为此,作者牺牲了镜像中一个闲置的原厂驱动,为补丁代码腾出物理空间。
硬件行为的微小偏差带来了额外挑战。目标控制器拥有一套古老的工作模式,只要中断状态发生变化就会向 CPU 抛出中断信号。这种信号跃迁并不等待电平持续置位,会导致处理器频繁陷入异常上下文。
为了稳定处理中断,作者设计了一套折中方案。内核在进入核心例程时先执行全局屏蔽(mask),由具体驱动例程处理完毕后再行解封(unmask)。屏蔽状态跃迁引发的抖动,让主板中断信号稳定适配了内核调用规约。
绕开固件暗坑:修补 m68k 模拟层与苹果陈年代码
越过内核阶段后,系统迎来了名为 SuperMario 的子系统。这是苹果为了平滑过渡到 PowerPC 平台而内置的 m68k 模拟层,贯穿了整个经典系统时代。该层包含大量直接操作老式 Mac 寄存器的底层指令。
在缺少对应外设的 Wii 上,模拟器频繁触碰空地址。音频中断控制函数指针被直接替换为空操作(stub)。原有的 ADB 键盘总线与电源管理初始化代码缺少硬件探针,外设缺席时会覆写零页内存。作者为这些早期初始化流程补上了存在性校验。
在这个过程中,开发者甚至挖出了苹果工程师当年的陈年笔误。在初始化硬件中断的函数中,存在一段复制代码导致的死循环。原代码意图在第二串口不存在时直接跳过,但程序员复制了第一串口的处理逻辑,却忘记修改跳转标号。
另一个缺陷出现在 USBShimKeyboard 组件中。该组件负责提供 USB 键盘兼容支持,却未编写缺少 USB 控制器时的回退逻辑。在没有检测到控制芯片时,程序在 m68k 模拟器内部直接调用了空指针。修正这两处历史疏漏后,屏幕上终于亮起了熟悉的问号软盘图标。
补齐三套驱动:从手柄控制到逐字节排查 IDE 错位
问号软盘亮起,意味着系统底层已就绪,正在等待块设备与用户交互。Rairii 随后编写了三套基础设备驱动:显示驱动、游戏手柄驱动以及伪 SCSI 驱动。
显示驱动参考了 Elliott Nunn 的 virtio 驱动架构,快速实现了帧缓冲呈现。控制输入则由 GameCube 原生手柄端口接管,核心代码由作者早期开发的 Windows NT 驱动移植而来。为了挂载系统磁盘镜像,作者构建了一个虚拟 SCSI 驱动。
在这个虚拟驱动的调试中,苹果光盘驱动暴露了一个未经验证的假设。系统试图利用 3 个分散集中缓冲区(scatter-gather buffer),在光盘介质上模拟 512 字节标准扇区。驱动层从未校验底层 SCSI 接口是否支持分散集中读写,直接引发了缓冲区溢出。
内核函数 StartLib!GetStartupDevice 还假定设备树中常驻内置 SCSI 节点。作者在内部数据结构与设备树等三处关键索引中写入非零值,引导程序跳入了正确的引导分支。
更隐蔽的缺陷出在自研的 IDE 驱动中。驱动使用 PIO 模式按 16 位字搬运数据,未考虑落在奇数边界上的缓冲区。只要遇到低 4 位为 1 的奇地址,驱动就会丢弃单个字节,造成后续数据整体向后漂移一字节。
这次故障在系统校验资源分支(resource fork)文件头时暴露。作者通过导出物理内存与磁盘镜像进行逐字节比对,锁定了这一字节的偏移。逐字节排查总线漂移,排除了文件系统元数据读取时的静默损坏。
40 秒进桌面:SPI 慢速模式为何跑出反常高吞吐
当所有驱动各就其位,系统的启动表现展现出意料之外的工程反差。在打了补丁的 Dolphin 模拟器中,Mac OS 9.2.2 运行了一小时才遭遇崩溃,Mac OS 7.6 崩溃得更早。拔除 MacsBug 调试工具后,模拟器耗费约 20 分钟首次进入桌面。
图:Mac OS 9.2.2 在 Wii 上成功启动至桌面。来源:Virtually Fun / Rairii
随后作者将系统部署到 Wii 实体硬件。主机通过前端 SD 卡插槽启动,运行在底层的 SD over SPI 模式下。Mac OS 7.6 仅用了 40 秒便完整进到了图形桌面。
40 秒的成绩出现在全系统吞吐最慢的存储通道上。SPI 接口的时钟频率远低于原生 SD 模式,启动耗时却比模拟器缩短了数十倍。这一反常加速揭示了经典操作系统的调度特征。
在单任务占优的旧架构中,驱动发起异步 I/O 并返回标记后,调用方程序不会主动让出 CPU。调用方在紧凑循环中盲等中断,此时没有竞争进程打断执行流。CPU 资源几乎全部分配给数据搬运逻辑。硬件层面的慢速总线,反倒因为零调度开销榨干了处理器的单核算力。
至于 Mac OS 9.2.2 的早期崩溃,问题出在扩展目录中的 Open Transport 网络栈。将该扩展移出目录后,9.2.2 同样能够顺利进入桌面。分析表明,当时的通信子系统在面对串口与网卡全数缺失的环境时,会因空指针解引用陷入死锁。
将经典 Mac OS 移植到任天堂主机,是一次深入旧时代总线规范的逆向实证。整个移植流程表明,阻碍老旧系统在新硬件上重生的核心,在于引导层对主板拓扑与外设地址的硬编码信任。只要能在固件层伪造出合规的硬件投影,二十多年前的操作系统便能跨越产品形态的界限,在陌生的硅片上如期苏醒。
参考链接:
- Virtually Fun 客座文章:My favourite PowerMac is a Nintendo Wii