[Перевод] Реверс-инжиниринг формата данных кабельного канала Sega
Введение
Sega Channel — это сервис games-on-demand, предоставлявший абонентам кабельного телевидения ежемесячный доступ к библиотеке из примерно пятидесяти игр для Sega Genesis за помесячную оплату (обычно 10–15 долларов, в зависимости от поставщика услуг кабельного телевидения). Он работал с июня 1994 года (общий доступ по всем США включили в декабре 1994 года) по июнь 1998 года. Абонентам предоставлялся картридж-адаптер, подключавший их консоль Genesis к линии кабельного телевидения. При включении консоли картридж искал сигнал Sega Channel, а затем скачивал меню игр. Этот процесс обычно занимал примерно двадцать секунд. Затем пользователь выбирал игру и примерно минуту ждал, пока она загрузится в ОЗУ адаптера. Далее игра работала точно так же, как картридж из магазина. Отключение системы или нажатие кнопки меню на адаптере удаляло скачанную игру, но данные сохранений игры оставались в нём, пока пользователь не скачивал другую игру. Наряду с продававшимися в рознице играми в Sega Channel был раздел «Test Drives», в нём пользователи могли играть в урезанные по времени или контенту версии игр, которые позже выпускались в розничную продажу. Кроме того, было несколько игр, доступных только через Sega Channel, хотя по большей мере это были проекты, в качестве которых Sega или издатель были не уверены, а потому не хотели выпускать их на физических картриджах. Успех Sega Channel был довольно умеренным, на пике популярности на него были подписаны примерно 250 тысяч абонентов.
Адаптер Sega Channel
Данные Sega Channel передавались абонентам кабельного телевидения довольно запутанным путём. Сначала сотрудники Sega Channel выбирали линейку игр и другой контент (подсказки, руководства, новости, оцифрованный фан-арт и так далее) на месяц. Затем они отправляли всё компании Foley Hi-Tech, которая создавала графику/анимации меню игр и вставляла весь ежемесячный контент. В результате получался файл размером примерно 60 МБ, называвшийся «игровым образом», который прожигался на CD и передавался по спутнику в Денвер, штат Колорадо. Далее CD устанавливался на компьютер игрового сервера, который непрерывно циклически передавал данные игр по спутниковой связи. Центральные кабельные станции по всем США получали спутниковую передачу и отправляли её абонентам. Непрерывной циклической передачей данных в то время обеспечивалась «интерактивность», ведь поставщики услуг кабельного телевидения могли только передавать данные всем абонентам, но не получать их (то есть не знали, какую игру хочет скачать конкретный абонент). Когда пользователь выбирал игру из меню, адаптер просматривал поток данных Sega Channel и извлекал данные для выбранной игры. Игровой образ размером примерно 60 МБ передавался с частотой примерно 12 Мбит/с на двух несущих частотах по 6 Мбит/с. Если проблем с сигналом не было, пользователю всегда удавалось запустить выбранную игру меньше, чем за минуту (один цикл передачи данных). Меню скачивалось быстрее, потому что для ускорения скачивания в поток данных добавлялись его дополнительные копии.
Схема работы сервиса Sega Channel
Игровой образ
В ноябре 2024 года пользователь RisingFromRuins с форума Sonic Retro заявил, что перебирая кучу PC-оборудования, купленного в этом году, нашёл CD игрового образа Sega Channel за сентябрь 1996 года. Он опубликовал фотографии CD и загрузил копию файла игрового образа с диска. Я подумал, что будет любопытно проверить, смогу ли извлечь данные из файла и посмотреть, есть ли там какие-то эксклюзивные игры или прототипы. Всегда интересно увидеть игры, в которые никто не мог поиграть более двадцати пяти лет.
Коробка CD игрового образа Sega Channel
CD игрового образа Sega Channel
Процесс
Первым делом я решил проверить содержимое файла образа в шестнадцатеричном редакторе. Все игры для Genesis имеют стандартизированный ASCII-заголовок, и я решил, что подсказки по играм/скачиваемые руководства тоже должны быть читаемыми. К сожалению, просмотрев файл образа, я не нашёл в нём ничего читаемого. Я предположил, что файл был или скрэмблирован, или зашифрован.
Тут мне сильно повезло. В 2017 году пользователь tdijital загрузил CD с бэкапом компании Foley Hi-Tech, на котором были материалы по разработке для Sega Channel. Другие пользователи уже изучили часть содержимого CD и нашли интересное, в том числе соревновательные версии Primal Rage, пару игр-викторин из японской версии Sega Channel и автономные ROM демо меню за декабрь 1994 года — январь 1996 года, которые можно было запускать в эмуляторе. Однако, насколько я знаю, никто не исследовал инструментарий разработчика с этого диска. Я подумал, что если мне хочется извлечь данные из файла игрового образа, то будет проще выполнить реверс-инжиниринг инструментария для создания игрового образа, чем заниматься реверс-инжинирингом кода скачивания на стороне Genesis и надеяться, что в процессе передачи данных файл образа по большей мере остался целым.
Путём проб и ошибок я разобрался, как создавали файлы образов. Сначала разработчики добавляли все игры, описания, тексты новостей, арт, музыку и так далее при помощи программы MENUMAKR. В результате получался двоичный файл меню и файл скрипта (названный MENUSPIN.BAT, хоть он и не был пакетным), который содержал пути к файлам ROM, смещения данных, количество включаемых копий ROM и другие метаданные для каждой игры.
Затем они запускали программу PKSPREAD, написанную Scientific Atlanta (компанией-поставщиком кабельного оборудования, с которой Sega заключила соглашение для создания оборудования вещания Sega Channel и картриджа-адаптера). Эта программа проверяла содержимое файла MENUSPIN.BAT и выводила двоичный файл PMAP.DAT, содержавший инструкции о том, как разделять и обрабатывать меню и данные игр для передачи.
PKSPREAD
Далее они запускали ещё одну написанную Scientific Atlanta программу под названием NSF, которая использовала файл PMAP.DAT для кодирования всех входных файлов и создания файла игрового образа. Я решил, что если хочу декодировать файл образа, нужно заняться этой программой.
NSF
Здесь мне ещё раз крупно повезло. Обнаружилось, что NSF.EXE компилировался в режиме отладки с отключенными оптимизациями и встроенными в EXE символами. Благодаря этому его должно быть легко подвергнуть реверс-инжинирингу. К сожалению, IDA Pro не смогла автоматически перехватить отладочные символы из исполняемого файла, поэтому мне пришлось открыть NSF в Turbo Debugger (программа компилировалась с помощью Borland C++ 4.1) и скопировать их вручную. К счастью, программа была довольно маленькой, поэтому это не заняло много времени.
Далее можно было сразу перейти к написанию программы-декодера, но я подумал, что лучше будет сначала написать программу, эквивалентную NSF.EXE. Таким образом, если я смогу создать побайтово идентичный файл образа с тем, который был создан DOS-утилитой NSF.EXE, то точно буду знать, что алгоритм подобран корректно. Я создал тестовый файл игрового образа (показанный выше на скриншотах PKSPREAD и NSF) и приступил к их сопоставлению. Мне понадобился день работы и ещё пара вечеров отладки, но я всё же написал эквивалентную NSF.EXE программу на C, и результаты их работы совпали. Работал я так: на одном мониторе открыл IDA, а на другом — Visual Studio, и старался, чтобы ассемблерный код в них совпадал максимально близко. Это привело к паре любопытных наблюдений. Тот, кто писал NSF, вероятно, был новичком в C. Кроме того, он потратил приличное количество времени на то, чтобы всё индексировалось с единицы, а не с нуля (возможно, фанат Pascal?). Стоит отметить, что я не предпринимал усилий к тому, чтобы скомпилированный двоичный файл совпадал с оригинальным. На самом деле, он даже, вероятно, не скомпилируется как файл C в Borland C++ 4, потому что я использовал везде объявления C99 и типы stdint.h.
Упрощённое объяснение работы NSF
Функция GetData получает из файла PMAP.DAT информацию, например, о том, какой файл открывать для какого пакета, где искать в файле пакет, а также метаданные Sega Channel (ID файла, сколько можно играть в игру, если это ограниченное по времени демо, и так далее). Затем она загружает 246-байтный блок данных из файла данных.
Функция LoadFrame добавляет заголовок в начало пакета данных и реверсирует все байты данных (не знаю, связано ли это с передачей данных или сделано просто для обфускации содержимого пакетов). Также она перемежает данные игр кодами корректировки ошибок BCH и битами чётности. Затем 288 байтов используются для хранения 246 байтов данных. Вот, как выглядит структура заголовков: (все они представляют собой многобитовые поля с обратным порядком битов):
Номер бита (индексация с 0)
Длина в битах
Элемент
Описание
0
27
Неизвестно
Не добавляется NSF; возможно, добавляется сервером при передаче?
27
2
Заполнитель
Всегда 0.
29
1
GameTimeSync
1, когда поле GameTimeBit содержит 15-й бит значения таймаута игры.
30
1
GameTimeBit
Содержит один бит значения таймаута игры (длительности разрешённой игры с интервалами в 10 секунд), засекаемого для конкретного файла в обратном порядке битов (первым идёт старший бит).
31
7
ServiceID
Обычно равен 1. Возможно, как-то связан с Express Games (новыми играми, которые можно по отдельности заказывать у оператора кабельного телевидения за дополнительную плату).
38
14
FileID
Уникальный идентификатор каждого файла в игровом образе. Меню — это всегда файл 0.
52
15
Адрес
Место в ОЗУ, куда адаптер должен скачивать файл.
67
16
HeaderCRC
16-битная CRC битов 27–66 заголовка.
83
56
Копия заголовка
Копия битов 27–82 заголовка.
Функция InterLeave разбивает пакет на блоки по 450 битов и скрэмблирует биты для каждого блока. Думаю, это нужно было для обфускации потока данных, потому что других практичных применений этого вообразить не могу.
Описанные выше этапы повторяются для остальных 9 пакетов.
Все 10 пакетов объединяются в 2880-байтный кадр, и данные перемежаются таким образом, что первые два байта относятся к пакету 0, следующие два — к пакету 1, и так далее. Затем кадр записывается на диск.
Этот процесс повторяется, пока не будет создан полный файл образа.
После того, как я разобрался в работе NSF, было довольно легко написать программу-декодер, которая откатывает все эти этапы в обратном порядке и выводит отдельные файлы данных. Однако выдаваемые моим декодером данные всё равно не были валидными данными ROM Genesis:
Скриншот игры и файла образа и обычный ROM
К счастью, причиной этого было всего лишь сжатие игровых данных перед передачей. Инструмент для сжатия ROM находился на CD Foley Hi-Tech (GAMEEDIT.EXE), но мне не пришлось выполнять его реверс-инжиниринг, потому что пользователь GitHub Octocontrabass уже сделал это. При помощи его инструмента unsa я распаковал все файлы .SA в стандартные файлы ROM.
Находки
Самыми примечательными играми из файла образа стали две эксклюзивные игры, транслировавшиеся в сентябре 1996 года: Chessmaster и Klondike. Chessmaster — это шахматы, выпущенные для многих платформ 90-х (но не для Genesis), а Klondike — это пасьянс, созданный Sega для Sega Channel и написанный известным разработчиком Pitfall Дэвидом Крейном.
Вот разбитый на категории список всех игр из файла образа:
Список игр Sega Channel за сентябрь 1996 года
Эксклюзивные
Новые сборки
Известные дампы с другим заполнителем
Flashback — The Quest for Identity (USA) (En, Fr)
Phantasy Star II (USA, Europe) (Rev A)
Punisher, The (USA)
Известные дампы с добавленными водяными знаками
Alex Kidd in the Enchanted Castle (USA)
Berenstain Bears' Camping Adventure, The (USA)
Ecco — The Tides of Time (USA)
Richard Scarry’s Busytown (USA)
Sonic & Knuckles (World)
Strider (USA, Europe)
World Series Baseball (USA)
Полностью совпадающие известные дампы
Aaahh!!! Real Monsters (USA)
Arcus Odyssey (USA)
B.O. B. (USA, Europe)
Ballz 3D — The Battle of the Ballz (USA, Europe)
Castlevania — Bloodlines (USA)
College Football’s National Championship II (USA)
Dr. Robotnik’s Mean Bean Machine (USA)
Fatal Labyrinth (USA, Europe) (En, Ja)
G-LOC — Air Battle (World)
Generations Lost (USA, Europe)
Granada (Japan, USA) (En) (Rev A)
Hard Drivin' (World)
Junction (Japan, USA) (En)
Jungle Strike (USA, Europe)
M-1 Abrams Battle Tank (USA, Europe)
Marsupilami (USA) (En, Fr, De, Es, It)
Mega Bomberman (USA)
NCAA Football (USA)
Pele! (USA, Europe)
PGA Tour Golf III (USA, Europe)
Rugby World Cup 95 (USA, Europe) (En, Fr, It)
Saint Sword (USA)
Shinobi III — Return of the Ninja Master (USA)
Space Harrier II (World)
Streets of Rage 2 (USA)
Taz-Mania (World)
ToeJam & Earl (USA, Europe, Korea) (En)
Ultimate QIX (USA)
Valis (USA)
Vectorman (USA) (Sega Smash Pack)
X-Men (USA)
Ys III (USA)
Zoop (USA)
После публикации моих находок Black Squirrel с Sonic Retro выяснил, что можно запустить в эмуляторе меню за сентябрь 1996 года, скопировав байты 0–0×1003FF с одного из ROM демо-картриджа Sega Channel и добавив в конец данные меню из игрового образа. Очевидно, что функция скачивания не работает, но изучить меню всё равно любопытно.
Последней частью контента из файла образа стал ROM с инструкциями к играм. Он выглядит как обычный файл ROM Genesis, но когда я попробовал запустить его в эмуляторе, то получил только чёрный экран. Оказалось, что проблема заключается в том, что ROM скомпонован с базовым адресом 0×100000 вместо 0, как на обычном картридже. Похоже, он должен был запускаться непосредственно из памяти адаптера Sega Channel без отображения в адресное пространство обычного картриджа. Могу только предположить, что это как-то связано с функциональностью перехода к руководству по конкретной игре из игрового меню. Я заставил его работать в эмуляторе, добавив нули после заголовка, пока код не выровнялся с векторами в заголовке. При запуске подобным образом мы видим предназначенное для разработчиков меню с внутренними именами каждой игры. На этом этапе весь контент из файла игрового образа можно запускать в эмуляторе.
Заключение
Должен поблагодарить Tdijital за публикацию CD-бэкапа для разработчиков Sega Channel, Octocontrabass за реверс-инжиниринг формата сжатия .SA и тех, кто скомпилировал в Scientific Atlanta файл NSF.EXE в режиме отладки. Этот проект оказался бы гораздо сложнее без их помощи. Больше всего мне бы хотелось поблагодарить RisingFromRuins за публикацию файла игрового образа. Надеюсь, все, кто найдет диск с игровым образом Sega Channel, последуют его примеру.