The Jerusalem PostYisrael Beytenu’s Liberman denies that he wants position of Knesset speakerESPN DeportesAsí se juega un clásico: el Bayern empata de inmediato ante DortmundESPNMan United implode at Chelsea, and, make no mistake, they are in a Premier League relegation battlePunchWale Tinubu joins Tinubu for Umrah in Saudi ArabiaDaily MaverickSA VICTORY : Proteas battle back to claim epic series win over Australia at NewlandsZDF heuteAktuelle Pressemitteilungen des ZDFVarietyJoshua Jackson Teases ‘Sex, Drugs and Rock ‘n’ Roll’ in New HBO Max Series ‘How to Survive Without Me’: ‘I’m Getting Too Old For This S—’RFI 中文糟糕的中期选举结果 可能促使特朗普对伊朗发动大规模攻击VanguardClashes break out as hundreds of thousands protest in FranceХабрПрограммируем без IDE или применение qwen и qwen coder для написания софтаn-tvStreit um Verfassungsgericht: Polnischer Präsident erhebt schwere Vorwürfe gegen TuskDeadline‘It: Welcome to Derry’ Surprise Halloween Episode Now Streaming On HBO Max
The Daily Newsstand · Free, Always
Saturday, October 31, 2026

[Перевод] Классическая Mac OS на Nintendo Wii: какие баги Apple пришлось обойти по дороге

Translate

Сегодня один из моих проектов дошёл до приятной вехи. Я портирую классическую Mac OS на PowerPC-системы с чипсетами, которые ласково называю ArtX: Nintendo GameCube, Wii, а со временем и Wii U. Интегрированный чипсет Flipper в GameCube проектировала именно ArtX: он был практически готов, когда компанию купила ATI. Vegas в Wii (и его поздняя ревизия Bollywood) и Latte в Wii U — прямые его потомки. Так вот, порт впервые дошёл до рабочего стола. Скорее всего, это первый случай со времён клонов на CHRP, когда кто-то за пределами Apple довёл классическую Mac OS на новой системе до рабочего стола. До публичного релиза ещё очень далеко, но про трудности, с которыми я уже столкнулся, рассказать хочется.

Видео: Mac OS 7.6 загружается на Wii

Mac OS 9.2.2 загрузилась на Wii

Mac OS 9.2.2 загрузилась на Wii

Но сначала: зачем вообще портировать классическую Mac OS на это железо? NT я туда, разумеется, уже портировал. Попутно у меня завелось несколько ноутбуков Mac на PowerPC, и на них я тоже в итоге портировал NT; работа над одним портом улучшала другие, и так далее. Так я познакомился с классической Mac OS и влюбился в её корявость, если можно так выразиться: хаки поверх хаков поверх хаков, тянущиеся с 1980-х, я иначе как корявостью назвать не могу. Не говоря уже о том, что m68k-часть TBXI весь период PowerPC называлась «SuperMario». Интересно, кто-то из разработчиков в начале 90-х был фанатом?

И для протокола: я начал проект 22 июня 2025 года (тогда я форкнул апстримный OpenBIOS). Никаких агентов для кода и LLM я не использовал: весь код, реверс и отладка сделаны мной самим. Я занимаюсь такими проектами ради удовольствия и чтобы учиться. С «ИИ» не было бы ни удовольствия, ни учёбы.

Первая загрузка

Начнём с самого начала загрузки. Классическая Mac OS эпохи «New World» стартует с загрузчика Trampoline, который работает в среде Open Firmware. Значит, нужно было портировать OpenBIOS. К счастью, примерно тогда же Goldfish64 начал свой порт Mac OS X на Wii и сделал порт OpenBIOS, из которого я мог взять часть драйверов. Напрямую его порт я использовать не хотел по ряду причин: он целился только в Wii и Wii U, а мне нужен был ещё и GameCube. Чтобы упростить разработку драйверов (прежде всего драйвера дисплея), я решил переиспользовать существующий код и переделал свой загрузчик ARC-прошивки от порта NT, чтобы он грузил big-endian ELF.

Для первого запуска хватало пары дисковых драйверов, поэтому я портировал свои два драйвера для устройств на шине EXI (так Nintendo и Macronix называют SPI). Первый — для SD Gecko, это просто сквозной переходник в слот карты памяти GameCube, через который SD-карта работает по SPI. Второй — для EXI-IDE. Этот драйвер, честно говоря, написан под мою неточную эмуляцию в моей ветке Dolphin, с настоящим железом он расходится: так можно тестировать в эмуляторе со всеми вытекающими возможностями низкоуровневой отладки.

С форматом данных на диске я не определился. В моём прошлом порте NT эти драйверы работали целиком через образы дисков на файловой системе FAT: нужна была совместимость с существующими homebrew-загрузчиками, которые запускают исполняемые файлы с FAT. Но у FAT32 образ ограничен 4 ГБ, а поздним версиям классической Mac OS нужно больше. Поэтому для первых тестов я просто взял файл TBXI «Mac OS ROM» и положил прямо на FAT как \ppc\bootinfo.txt, чтобы OpenBIOS загрузил его при старте.

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

Патчи загрузчика

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

Кроме того, Trampoline ожидает, что физические адреса начиная с 0x8000_0000 относятся к вводу-выводу, отображённому в память (на системах, где этот код работал раньше, это было адресное пространство шины PCI). На этих системах там ничего подобного нет. На Wii U этот адрес вообще приходится на часть оперативной памяти. Сломалось и ещё одно предположение: на этих системах между областями оперативной памяти в физическом адресном пространстве есть дыры, и часть из них частично занята MMIO.

Исправил я это так: пропатчил инициализацию этого MMIO-отображения в списке участков памяти ядра, чтобы физический адрес 0x0100_0000 (середина одной из областей RAM) отображался на виртуальный 0xE000_0000 с длиной 0x1000_0000. Это отображение целиком покрывает MMIO, плюс конец одной области RAM и (на системах после GameCube) начало другой. (Возможно, позже я это поменяю: на Wii U физические адреса 0xD000_0000-0xEFFF_FFFF отображены на часть GPU Radeon R7xx под названием HDP. Через неё можно смотреть на физическую память с определённым перемешиванием (swizzling) и/или сменой порядка байтов, так что текстура в каком-нибудь GPU-специфичном формате выглядит как линейный фреймбуфер. Зависит от того, понадобится ли мне этот блок.) Функция, которая в Trampoline настраивает отображение, в какой-то момент поменялась, так что в итоге я распознаю старую версию и перехватываю функцию, чтобы обойти разницу: ранние версии использовали 16-битный счётчик страниц, поздние — 32-битный. Вдобавок старые версии Trampoline безусловно создают отображения 1:1 для физической памяти выше 0x8000_0000; их нужно убрать, потому что они конфликтуют с отображениями, настроенными раньше.

Дальше идёт набор разнородных патчей против кода, который рассчитывает на конкретное железо. Среди них: убрать установку битовых флагов, означающих наличие микроконтроллера, который есть во всех Macintosh на PowerPC; убрать попытку пересчитать частоту шины по определённому аппаратному таймеру; убрать разные записи в регистры и поправки, связанные с конкретным контроллером прерываний (он little-endian, потому что сидит на шине PCI, и здесь его, понятно, нет).

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

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

Патчи ядра и кода Apple

Как я уже говорил, инициализация ядра перехватывается, чтобы наложить дополнительные патчи. В самом ядре патч всего один. В ядре есть несколько драйверов контроллеров прерываний; выбранный пропатчен под контроллер Flipper, а один из неиспользуемых принесён в жертву, чтобы освободить под это место. Главная разница в том, что у ожидаемого контроллера (чей драйвер патчится) есть legacy-режим, в котором он поднимает прерывание PowerPC при любом изменении прерываний, включая их снятие. Обходится это так: поднятые прерывания маскируются в ядре и потом размаскируются в обработчиках прерываний драйверов.

Дополнительные патчи нужны в m68k-коде «SuperMario» и в нативных исполняемых файлах PowerPC. В m68k-коде есть несколько путей, которые напрямую читают и пишут регистры нативного контроллера прерываний, — их надо пропатчить. Указатели на функции включения и выключения аудиопрерываний нужно заменить заглушками (в той таблице уже есть запись, указывающая на заглушку, что удобно). Функция, которая рано при загрузке инициализирует ADB и микроконтроллер управления питанием, не проверяет, есть ли это железо на самом деле, и если его нет, затирает часть нижней памяти. В функции инициализации аппаратных прерываний есть баг из-за неудачного копипаста. Задумано было пропустить код инициализации прерывания второго последовательного порта, если такого железа нет, но разработчики, копируя код из блока для первого порта, забыли поменять метку перехода. В результате, если второго последовательного порта нет, получается бесконечный цикл. В нативном коде тоже хватает мест, которые читают и пишут регистры контроллера прерываний, и их нужно правильно пропатчить. Код инициализации драйвера USBShimKeyboard не обрабатывает ситуацию, когда USB-контроллера нет вовсе, и в этом случае вызывает NULL-указатель внутри эмулятора m68k. Здесь же целиком заменяется упомянутый выше драйвер управления питанием (пока я использую драйвер-заглушку из BlueBox, но собираюсь написать свой, чтобы нормально поддержать выключение и перезагрузку).

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

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

На этом месте я начал писать драйвер блочного ввода-вывода для устройств 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-устройство, требующее отдельной обработки. С первым всё просто: поправить дерево устройств в коде инициализации моего драйвера. Второе интереснее: пришлось копаться во внутренних структурах и в трёх разных местах (включая дерево устройств) исправить индекс, чтобы он был ненулевым и выполнялась нужная ветка кода.

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

После этого я впервые увидел довольный значок Mac, но дальше меня отверг загрузочный сектор HFS: что-то вернуло ошибку, и он просто вышел. Оказалось, баг в моём собственном драйвере. Я отлаживался в Dolphin с переделанным устройством EXI-IDE. В режиме PIO чтение и запись у IDE-устройств идут 16-битными словами, а я не ожидал, что моему коду передадут совершенно невыровненный нечётный адрес буфера (младшие 4 бита равны 1). Пришлось поменять низкоуровневый драйвер, чтобы для таких адресов он даже не пытался использовать EXI DMA. Проявлялся баг так: один байт фактически терялся при записи, и все остальные данные съезжали на байт. По крайней мере, так было при чтении заголовка resource fork, и падала проверка корректности этих данных, что сильно облегчило отладку (в итоге я сдампил оперативную память и сравнил с ожидаемыми данными из образа диска, который использовал).

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

Mac OS 7.6 загрузилась в моей переделанной ветке Dolphin

Mac OS 7.6 загрузилась в моей переделанной ветке Dolphin

Настоящее железо

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

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

Как я уже говорил, до релиза порту ещё очень далеко. Дальше на очереди — поднять SD-контроллер Wii, затем USB-контроллер (кое-какой реверс по нему я уже сделал; знаю, что теперь есть навайбкоженный драйвер EHCI, но для реверса он, скорее всего, будет разве что подспорьем), а затем запустить порт на Wii U. А когда-нибудь хочу поиграть с патчами, которые пробьют хотя бы часть ограничений по объёму памяти.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.