В марте-мае этого года я потратил несколько недель (по вечерам и выходным) на портирование игрушки Lode Runner с БК-0010 на УКНЦ.
Скриншот меню портированной версии:
Игровой экран портированной версии:
БК-0010 это бытовой компьютер конца 1980-х и начала 1990-х, и отчасти школьный компьютер (классы КУВТ-86). УКНЦ это школьный компьютер 1990-х. БК и УКНЦ частично совместимы по архитектуре и системе команд — оба компьютера восходят к семейству PDP-11.
Выбор игры
До этого года я не писал ничего серьёзного под УКНЦ, но приходилось разбираться в машинном коде. Было желание написать что-нибудь, но обычно у меня большие проблемы со свободным временем, так что сделать что-либо с нуля вряд ли получилось бы. И для начала лучше брать задачу попроще. При портировании объём работы обычно намного меньше чем при написании с нуля — сталкиваешься в основном с проблемами несовместимости двух систем.
На форуме zx.pk.ru (одна из площадок где тусуются любители ретрокомпьютинга вообще и PDP-11 совместимых машин в частности) участник hobot расхваливал реализацию Lode Runner на БК — собственно это и стало поводом, для начала я попробовал «посмотреть» на код игрушки, ну и втянулся.
Меню оригинальной версии:
Игровой экран оригинальной версии (на цветном мониторе):
Обратная разработка
Пару недель по вечерам и выходным я потратил время на анализ и дизассемблирование. В эмуляторе BKBTL добавил возможность собирать трассу — то есть, каждая инструкция дизассемблируется и сохраняется в текстовый файл.
Делаю прогон участка который меня интересует с записью трассы, потом сворачиваю трассу (sort & uniq) — получаю фрагменты логики. Добавляю к этому комментарии, получаю постепенно общий файл.
Звучит просто, но на самом деле это довольно сложная работа, основанная на догадках и их подтверждении или опровержении. Например, смотрим что адрес 001756 перед началом игры получает значение 10, потом декрементируется, при достижении 0 игра заканчивается — видимо, это количество жизней. Находим этому подтверждение, расставляем комментарии по тексту где встречается этот адрес. Это довольно простой пример, в более сложных случаях я потратил много времени на то чтобы догадаться что к чему.
Когда полученный объём стал достаточно большим (40+ КБ текста, больше 1500 строк) и я разобрался хотя бы в общих чертах что к чему, как хранится и выводится — стал думать как это перевести на УКНЦ.
Здесь можно посмотреть на итоговый листинг, полученный в результате дизассемблирования:
github.com/nzeemin/uknc-loderunner/blob/master/original/loderunner.lst
Лабиринт
Каждый лабиринт — это 20 строк по 30 блоков, всего 600 блоков.
Тип блока кодируется числом от 0 до 7 — три бита, триплет. На одно 16-разрядное слово получается 5 полных триплетов.
В работе с PDP-11 like машинами повсеместно используется 8-ричная система, поэтому использовать триплеты довольно удобно.
В итоге, каждый лабиринт укладывается в 240 байт.
Типы блоков:
; 0 -- пусто
; 1 -- сплошная стена
; 2 -- кирпичная стена
; 3 -- верёвка
; 4 -- чёрт
; 5 -- человек
; 6 -- сундук
; 7 -- лестница
Спрайты этих объектов расположены в порядке нумерации типов блоков. Когда раскодируется лабиринт, одновременно создаётся «образ лабиринта» в памяти (по байту на блок), и тут же рисуется начальное состояние лабиринта на экране.
Организация экрана
На БК экран адресуется непосредственно обращением к памяти, строки идут одна за другой, по сути это «framebuffer», и я бы сказал что это прекрасно — очень удобно для программирования графики. Строка БК — 256 цветных пикселей, 64 байта на строку. А вот то, сколько пикселей в строке, зависит от того как вы подключили монитор:
если по чёрно-белому выходу, то это 512 ч/б пикселей в строке (1 бит на пиксел, 8 пикселей на байт),
а если по цветному выходу, то это 256 цветных пикселей в строке (2 бита на пиксел, 4 пиксела на байт).
На УКНЦ же организация экрана совсем другая, и намного более сложная. Экран лежит в трёх блоках памяти, трёх «планах». И каждый пиксель это три бита, по биту в каждом плане — получаем 8 цветов. На УКНЦ у нас есть несколько видеорежимов — 640 × 288, 320 × 288, 160 × 288, точнее, у нас всегда ровно 288 строк и к каждой отдельной строке можно применить свой делитель, получая разное разрешение по горизонтали. Для центрального процессора (ЦП) планы экрана не доступны непосредственно, только через обращение к портам. Причём, для ЦП доступны только два плана из трёх.
В данном случае мне хорошо подходил режим 320 × 288 — строка получается в 320 цветных пикселей длиной 80 байт в каждом из трёх планов. Если использовать два плана, то пиксели получаются тоже четырёхцветные — почти как на БК.
Синтез
Начал писать примеры на ассемблере УКНЦ и несколько приуныл — потому что цикл «скомпилил — слинковал — запустил» получается довольно медленный. Проблема в инструментах. Кросс-ассемблер MACRO11 есть, хоть он и несколько глючный. А вот кросс-линкера нет. Но к счастью, не так давно Patron выложил консольную RT-11: zx-pk.ru/showthread.php?t=24755 — по сути это эмулятор PDP11-совместимой машины, взаимодействующий с командной строкой ОС как с терминалом. Тем самым, стала возможной компиляция и линковка родными средствами RT-11. Это я считаю настоящий прорыв, резко ускорило работу.
После этого дело пошло, сделал отрисовку рамки игрового поля, отрисовку спрайтов, разобрался как биты в спрайтах нужно перемешать (для перемешивания написал программу на C#), затем блоками стал переносить код из общего файла с дизасмом в новые исходники. Взял дамп памяти с БК, выделил блок где лежат уровни, RT11-утилитой DUMP сделал текстовик под уровни.
Сначала перенёс блок кода который выводит уровень, на этом отладил вывод спрайтов. Потом игровую логику стал переносить. Т.е. в целом перенос практически один-к-одному, за исключением мест где вывод на экран идёт. Поэтому есть места в логике которые я не понимаю как работают (тот же AI чёртиков), но это и не важно — главное что работают.
В итоге, к началу мая (когда основная работа меня снова поглотила) получился работающий вариант, хотя и без звука.
Фото работающей игры на реальной машине (спасибо hobot):
Ссылки
- zx-pk.ru/showthread.php?t=24886 — тред по этой теме на zx.pk.ru
Автор: nzeemin