Классическая Mac OS считает настоящий PowerMac устроенным примерно так: есть шина PCI, а за ней, начиная с адреса 0x8000_0000, лежит пространство ввода-вывода, отображённое в память. На плате обязательно присутствует микроконтроллер ADB и управления питанием. Контроллер прерываний работает в legacy-режиме и поднимает прерывание PowerPC при каждом изменении состояния, а не только когда появляется новое событие. И, конечно, имеется SCSI-устройство, для которого в загрузочном коде предусмотрена отдельная ветка.

На Nintendo Wii нет ничего из этого. Другая карта физических адресов, другой чипсет, контроллер прерываний Flipper, а диск подключён через шину EXI, которую Nintendo и Macronix называют SPI. Поэтому порт классической Mac OS на Wii — это не столько перенос кода, сколько последовательное обнаружение мест, где операционная система молча верит в несуществующее железо, и замена этих верований на совместимые слои.

Разработчик Rairii опубликовал технический отчёт об этом проекте в блоге Virtually Fun 29 октября 2026 года. Он начал работу 22 июня 2025 года, когда сделал форк OpenBIOS, и подчёркивает, что весь код, реверс и отладку выполнил сам, без агентов и языковых моделей. Русскоязычный перевод отчёта доступен на Хабре. Это не рецензируемое исследование и не формальный эксперимент, а свидетельство разработчика о собственном процессе. Независимого воспроизведения результата пока нет, и сам автор говорит, что до публичного релиза ещё далеко.

Зачем вообще загружать Mac OS на консоли Nintendo

Короткий ответ — потому что это технически увлекательно и позволяет увидеть границы, на которых старая система ломается. Rairii уже портировал Windows NT на эти же платформы, и во время той работы у него появились ноутбуки Mac на PowerPC, на которые он тоже перенёс NT. Один порт улучшал понимание другого. По его словам, именно так он познакомился с классической Mac OS и влюбился в её корявость — в оригинале это слово jank, хаки поверх хаков, тянущиеся с 1980-х.

Показательная деталь: m68k-часть TBXI весь период PowerPC называлась SuperMario. Автору это кажется забавным, но для его задачи название полезно как ориентир. Загрузка идёт от Trampoline, который работает в среде Open Firmware и передаёт управление ядру, а уже внутри живут и m68k-код SuperMario, и нативные исполняемые файлы PowerPC. Патчить приходится все эти слои.

Целевыми платформами стали GameCube, Wii и, в перспективе, Wii U. Чипсет Flipper в GameCube проектировала ArtX, когда компанию купила ATI. Vegas в Wii, его поздняя ревизия Bollywood и Latte в Wii U — прямые наследники этой архитектуры. Поэтому автор объединяет их под общим именем «чипсеты ArtX».

Карта памяти, которой нет

Первый барьер возник до загрузки ядра. Trampoline строит для ядра список участков памяти и записи таблицы страниц, и в этом коде зашиты предположения о раскладке физического адресного пространства, верные для обычных PowerPC-компьютеров, но не для консолей Nintendo.

Автор приводит два примера. Во-первых, физический адрес одного буфера вычислялся как недопустимый. Во-вторых, Trampoline считает, что начиная с 0x8000_0000 расположено отображённое в память пространство ввода-вывода, на прежних системах это была область PCI. На Wii U тот же диапазон — часть оперативной памяти. Между областями RAM на этих системах есть дыры, частично занятые MMIO.

Решение Rairii — пропатчить инициализацию MMIO-отображения в списке участков памяти ядра. Физический адрес 0x0100_0000, середина одной из областей RAM, отображается на виртуальный 0xE000_0000 с длиной 0x1000_0000. Такое окно покрывает весь MMIO, конец одной области RAM и, на системах после GameCube, начало следующей.

На этом проблемы с картой памяти не закончились. Функция инициализации отображения в разных версиях Trampoline менялась: ранние использовали 16-битный счётчик страниц, поздние — 32-битный. Автор распознаёт старую версию и перехватывает функцию. Кроме того, старые версии загрузчика безусловно создают отображения один к одному для физической памяти выше 0x8000_0000, а они конфликтуют с уже настроенными отображениями. Их пришлось удалить.

Отдельное соображение касается Wii U. Там физический диапазон 0xD000_0000–0xEFFF_FFFF отображён на часть GPU Radeon R7xx под названием HDP. Через него можно смотреть на физическую память с перемешиванием или сменой порядка байтов, так что текстура в GPU-специфичном формате выглядит как линейный фреймбуфер. Автор допускает, что это окно может пригодиться позже, но окончательного решения пока не принял.

Дерево устройств как сцена с подменой актёров

Trampoline принимает дерево устройств от прошивки и строит на его основе ожидания о том, какое железо ему предстоит. Rairii меняет это дерево так, чтобы загрузчик видел знакомую картину, а не реальную консоль.

Контроллер прерываний подделывается под один из распознаваемых загрузчиком вариантов — тот самый little-endian контроллер, о котором говорилось выше. По словам автора, на уровне регистров он ближе всего к контроллеру Flipper, присутствующему во всех чипсетах ArtX, хотя на Wii U его изменили ради поддержки дополнительных ядер PowerPC. Адрес фреймбуфера в дереве устройств меняется на некэшируемый виртуальный адрес из диапазона 0xExxx_xxxx. В конец дерева добавляется поддельное устройство управления питанием, совместимое с Mac99. Это нужно, чтобы Trampoline подготовил для него драйвер, который позже можно будет заменить.

Список патчей против кода, рассчитывающего на конкретное железо, этим не ограничивается. Автор убирает установку битовых флагов, означающих наличие микроконтроллера, который есть во всех PowerPC Macintosh. Убирает попытку пересчитать частоту шины по определённому аппаратному таймеру. Убирает записи в регистры и поправки, связанные с конкретным контроллером прерываний, который сидит на PCI и потому здесь отсутствует.

Ядро, SuperMario и копипаста, превратившаяся в бесконечный цикл

Инициализация ядра перехватывается, чтобы наложить дополнительные патчи после того, как всё окажется в памяти. В самом ядре такой патч один, но он важен. Ядро содержит несколько драйверов контроллеров прерываний. Выбранный драйвер пропатчивается под Flipper, а один из неиспользуемых приносится в жертву, чтобы освободить место для изменённого кода.

Ключевое различие в поведении прерываний автор описывает так: ожидаемый контроллер имеет legacy-режим, в котором он поднимает прерывание PowerPC при любом изменении состояния прерываний, включая их снятие. На Flipper семантика другая. Обход — маскировать поднятые прерывания в ядре, а затем размаскировать их в обработчиках прерываний драйверов.

Дальше начинаются патчи m68k-кода SuperMario и нативных исполняемых файлов PowerPC. В m68k-коде есть несколько путей, которые напрямую читают и пишут регистры контроллера прерываний, их нужно пропатчить. Указатели на функции включения и выключения аудиопрерываний заменяются заглушками; в таблице уже есть запись, указывающая на заглушку, что упрощает задачу.

Среди находок в коде Apple две особенно показательны для того, как старые системы обрастали неявными допущениями. Функция, которая рано при загрузке инициализирует ADB и микроконтроллер управления питанием, не проверяет, есть ли это железо на самом деле, и если его нет, затирает часть нижней памяти. А в функции инициализации аппаратных прерываний есть баг из-за копипасты: задумывалось пропустить код инициализации прерывания второго последовательного порта, если такого железа нет, но разработчики, копируя код из блока для первого порта, забыли поменять метку перехода. Если второго последовательного порта нет, получается бесконечный цикл.

В нативном коде тоже есть обращения к регистрам контроллера прерываний. Дополнительно всплывает USBShimKeyboard: его код инициализации не обрабатывает ситуацию, когда USB-контроллера нет вовсе, и в этом случае вызывает NULL-указатель внутри эмулятора m68k. Драйвер управления питанием, подготовленный через поддельное устройство в дереве, заменяется целиком. Пока Rairii использует драйвер-заглушку из BlueBox, но планирует написать собственный для нормального выключения и перезагрузки.

Этого набора хватает, чтобы TBXI догрузился до мигающей дискеты. Дальше автор написал драйвер дисплея, опираясь на virtio-драйвер Elliott Nunn, и драйвер портов контроллеров GameCube, портировав свой драйвер из NT, чтобы двигать курсор мыши без USB.

Диск, который притворяется SCSI

Следующий барьер — блочный ввод-вывод. Rairii начал писать драйвер для устройств EXI, но застрял, размышляя о формате диска и устройстве драйвера. Потом появился образ Mac OS для CHRP, и он отвлёкся на патчи поздних образов TBXI для её загрузки. Логика была такая: запуск 7.x пригодится на GameCube, которому не хватает памяти даже на минимальные требования 8.0.

Почти через год он вернулся, реализовал в OpenBIOS собственный формат диска и решил сделать драйвер, который притворяется SCSI. Так поступали и другие драйверы, прежде всего для классической Mac OS на разных SATA-контроллерах.

С поддельным SCSI всплыло несколько проблем. Пришлось добавить патч в m68k-код, чтобы драйвер диска принудительно загружался как драйвер нового типа. Речь о драйвере, который загружается с самого диска и реализует дисковые устройства поверх размеченного блочного устройства. Старый тип устарел уже к выходу первых PowerPC-систем, и поддельный SCSI-драйвер автора не поддерживает старые вызовы; поэтому он и падал, когда его пытались загрузить как драйвер старого типа.

Ещё одна любопытная особенность — в драйвере оптических дисков. Он эмулирует 512-байтовые секторы на устройствах с секторами по 2048 или 2352 байта, для чего делает вызов чтения с тремя буферами scatter-gather, но никогда не проверяет, поддерживает ли нижележащий SCSI-драйвер scatter-gather вообще. Автору пришлось реализовать такую поддержку для чтения.

Мигающая дискета всё равно не исчезла. Отладка показала, что отбраковывается загрузочное устройство: вызывается PowerPC-функция StartLib!GetStartupDevice, а она делает предположения и о дереве устройств, и о наличии встроенного SCSI-устройства, требующего отдельной обработки. Первое решается правкой дерева устройств в коде инициализации драйвера. Второе сложнее: пришлось копаться во внутренних структурах и исправить индекс в трёх местах, включая дерево устройств, чтобы он был ненулевым и выполнялась нужная ветка.

Ошибка на один байт

После этих исправлений Rairii впервые увидел довольный значок Mac, но дальше его отверг загрузочный сектор HFS. Оказалось, это баг в его собственном драйвере, и он поучителен сам по себе.

Автор отлаживался в своей изменённой ветке эмулятора Dolphin с EXI-IDE. В режиме PIO чтение и запись у IDE-устройств идут 16-битными словами. Rairii не ожидал, что его коду передадут совершенно невыровненный нечётный адрес буфера с младшими четырьмя битами, равными единице. Пришлось менять низкоуровневый драйвер, чтобы для таких адресов он даже не пытался использовать EXI DMA.

Проявлялся баг так: один байт фактически терялся при записи, а остальные данные съезжали на байт. По крайней мере, так было при чтении заголовка resource fork, и падала проверка корректности этих данных, что сильно облегчило отладку. В итоге автор сдампил оперативную память и сравнил с ожидаемыми данными из образа диска.

После исправления Mac OS 9.2.2 начала грузиться в изменённой ветке Dolphin, очень медленно. Прошёл час, прежде чем она упала. Тогда автор попробовал 7.6, она тоже упала, но раньше. Rairii предположил, что падение происходит при загрузке MacsBug, убрал его и минут через двадцать увидел рабочий стол.

Настоящее железо и два объяснения

На следующее утро автор проверил запуск на реальном оборудовании с SD-картой по SPI. Mac OS 7.6 загрузилась примерно за 40 секунд, хотя этот режим доступа к SD-карте должен быть самым медленным. Свой прошлый опыт — драйвер SD по SPI для NT — он считает скорее исключением: в видео установки NT на GameCube копирование файлов в текстовой части установки ускорено в 64 раза. Разница в том, что здесь Rairii занимает весь процессор на время передачи. Другого варианта нет: если вернуть значение, означающее асинхронную передачу, вызывающий код начнёт в цикле активно ждать прерываний до её завершения, хотя при этом проверяет все контроллеры в системе.

Позже выяснились два обстоятельства, которые в отчёте автора стоят рядом, но имеют разный статус. Первое — MacsBug падал в Dolphin из-за ошибки эмуляции, а на реальном железе загружается без сбоя. Второе — Mac OS 9.2.2 падала при загрузке из-за Open Transport. Стоило убрать его из папки Extensions, и система загрузилась. Причину Rairii обозначает как предположение: Open Transport, возможно, не умеет работать, когда в системе нет вообще ни последовательного, ни сетевого устройства.

Это различие важно. Первое — наблюдаемый факт о разнице между эмулятором и железом. Второе — гипотеза о причине сбоя, которую автор сам не выдаёт за установленную.

Что остаётся открытым

Отчёт заканчивается списком следующих шагов: поднять SD-контроллер Wii, затем USB-контроллер, для которого уже сделана часть реверса, а затем запустить порт на Wii U. В перспективе Rairii хочет поиграть с патчами, которые пробьют хотя бы часть ограничений по объёму памяти.

Из этого не следует, что порт уже готов. Успешная загрузка 7.6 до рабочего стола — результат конкретной конфигурации, а не доказательство общей стабильности или совместимости с приложениями и периферией. Основной источник — свидетельство самого разработчика, без независимого воспроизведения и рецензирования. Проверка охватывает описанные этапы, и переносить результат на Wii U или считать работу завершённой нельзя.

Отдельный технический контекст даёт репозиторий universal-tbxi-patchset от Wack0. Это набор патчей для образов TBXI версии 1.2 и выше, восстанавливающий удалённые интерфейсы, которые нужны для загрузки более ранних версий классической Mac OS. В README сказано, что с подходящим системным расширителем такой пропатченный TBXI может попытаться загрузить System 7.5 и выше с переменным успехом, а некоторые расширения и панели управления могут вызывать сбои. Этот проект касается совместимости версий внутри самой Mac OS, а не порта на Wii, но он напоминает, что понятие «загрузилась до рабочего стола» не тождественно «стабильно работает».

История Rairii интересна не только как инженерный курьёз. Она показывает, что совместимость — это не абстрактное свойство, а набор конкретных допущений, рассеянных по загрузчику, ядру, m68k-коду и драйверам. Каждое из них когда-то было верным для конкретного компьютера Apple, а на другой платформе превратилось в ошибку. Чтобы Mac OS увидела в Wii знакомый PowerMac, пришлось не только написать драйверы, но и аккуратно переписать сами ожидания системы о том, каким должен быть мир.

Похожую логику — как неявные допущения влияют на результат — можно проследить и в более привычных академических задачах: например, в том, какие методы исследования бывают в магистерской диссертации и почему их выбор зависит от допущений о данных и способах проверки.

Источники и литература

Схема показывает, как последовательные слои совместимости обходят допущения классической Mac OS о памяти, прерываниях и диске

Фото: Ruben Boekeloo / Pexels