В детстве на ПК МК-88 с ОС Альфа-дос была пятидюймовая дискета с играми. Одна из игр — ПакМен, сделанный, судя по стартовому экрану, где-то в Киеве в 1990 году. Не оригинальный Pac-Man, а некий советский клон - с «кровавыми жуками» вместо призраков и своим характером. Решил её портировать в браузер.
Бинарник нашёлся — PACKMAN.EXE, 42 килобайта. MZ DOS executable, запускается в DOSBox.
Проблема в том, что исходников нет. Только бинарь.
Дизассемблирование
Для декомпиляции использовал Reko Decompiler. Он выдал:
- 6944-строчный листинг на x86 ASM
- приблизительный декомпилированный C
C-вариант Reko-а читается удобнее, но местами врёт — переменные без имён, логика угадана неточно. Поэтому правило: если ASM и C расходятся, верим ASM.
Дальше — ручной разбор ключевых функций: инициализация уровней, движение сущностей, AI врагов, обработка клавиатуры, коллизии, подсчёт очков.
BGI и CGA
Игра написана на Turbo C и использует BGI (Borland Graphics Interface) — стандартную графическую библиотеку для DOS-программ от Borland. В бинаре прямо прописана строка "CGA Device Driver 2.00 — Mar 21 1988" — встроенный BGI-драйвер для CGA.
CGA-режим: 320×200 пикселей, 4 цвета из фиксированной палитры. Никакого antialiasing, никаких полутонов — чистый пиксель.
BGI предоставлял набор функций для рисования:
outtextxy— вывод текста в нужной точкеbar— закрашенный прямоугольникputimage— спрайт из памятиsettextstyle— выбор шрифта: Gothic (#4) для заголовка, Triplex (#1) для текста менюsetcolor,setfillstyle— управление цветом и заливкой
Спрайты в игре — 2bpp CGA-формат: каждый байт хранит 4 пикселя, по 2 бита на пиксель. Каждый спрайт — 16×14 px, 56 байт. Всего 29 спрайтов: стены лабиринта, точки, кадры анимации ПакМена и врагов.

Что внутри
Вытащил из бинарника всё что можно, зная формулу смещения: file_offset = DS_offset + 0x400.
Интересные детали, найденные в ASM:
Количество жизней — вычисляется как 0x23 / 7 = 5.
Скорость — хранится инвертированно: нажатие 0 даёт внутреннее значение 9 (медленно), нажатие 9 — 0 (быстро). Формула: speed = '9' - key. Отображаемое значение 9 - speed.
Движение асимметрично: горизонтально 4 пикселя за шаг, вертикально 2. Это не баг — так в ASM.
AI врагов: при совпадении строки или столбца с ПакМеном — преследование, иначе — следование предопределённому паттерну из 16 направлений. Три разных паттерна, назначаются случайно при старте уровня.
Нет режима испуга. Никакого. Столкновение с врагом — смерть.
«Победа !» показывается только после прохождения всех 15 уровней. Между уровнями игра стартует следующий.
Уровень 13 — это отдельная история.
Формат уровней
Данные уровней хранятся начиная с DS:0x6FFC в исполняемом файле. Каждый уровень — 480 байт: 20 столбцов × 12 строк × 2 байта на тайл. 13-й уровень (индекс 12) - по смещению DS:0x867C (файловое 0x8A7C).
Тайлы стен лабиринта:
| ID | Символ | Значение |
|---|---|---|
| 0x02 | ╭ |
угол: верхний левый |
| 0x07 | ╮ |
угол: верхний правый |
| 0x08 | ╯ |
угол: нижний правый |
| 0x09 | ╰ |
угол: нижний левый |
| 0x0A | │ |
вертикальная стена |
| 0x0B | ═ |
горизонтальная стена |
| 0x00 | |
пустое / проходимое |
| 0x01 | · |
точка (2 очка) |
Карта 13-го уровня из бинарника:
1 | ╭══════════════════╮ строка 0 |
Уровень 13
В детстве я дошёл до 13-го уровня один-единственный раз. И обнаружил, что он непроходим — ПакМен заперт в маленьком участке лабиринта, где всего два яблочка. Жуки бегают снаружи, туда не попасть.
Игра и так довольно сложная, а без power-up и сохранений и упереться в баг - втройне обидно.
Одной из целей этого порта было проверить: правда ли 13-й уровень сломан, или я что-то не так помню? Ответ нашёлся в данных.

ПакМен выходит слева и сразу упирается в стену. Уйти можно только на одну клетку вверх — а дальше ни наискосок, ни вниз. Визуально видно: все секции лабиринта — квадраты 2×2, а та, в которую упирается ПакМен, — прямоугольник.
Когда я привёл этот прямоугольник к размеру 2×2, замысел авторов стал понятен - уровень должен был быть полностью симметричным.
Баг, скорее всего, - ошибка при ручном вводе карты уровня. В экстракторе я его исправил: восстановил угловые тайлы в левой стене и открыл проходы между горизонтальными полосами лабиринта, восстановив симметрию с правой стороной. Уровень стал проходимым.

Теперь можно пройти все уровни, с первого и до последнего. Делать этого, конечно, не буду - все-таки игра довольно сложная, но с power-up’сами - вполне.
Порт на JavaScript
Cборка не требуется - только статика - js-файлы и index.html. Можно открываеть в браузере.
1 | data.js — уровни, спрайты, палитры (генерируется из EXE) |
Спрайты декодируются при старте из сырых байт в ImageData — один раз на палитру. Текст меню в оригинале закодирован в CP866, вытащил и записал в UTF-8.
Звуки — квадратные волны через OscillatorNode, приближение к PC-спикеру.
Итого

Многое в этом проекте я разбирал сам — вручную листал листинг от Reko, сверял ASM с декомпилированным C, угадывал намерения автора и подолгу гонял игру в DOSBox, чтобы понять, как именно та или иная функция должна себя вести. Но без LLM такое восстановление по бинарнику было бы заметно тяжелее: модель помогала быстро формулировать гипотезы о незнакомых конструкциях BGI, расшифровывать неочевидные участки ассемблера и подсказывать, где искать формат данных, когда зацепиться было не за что. Всё равно занятие непростое — много раз приходилось возвращаться к одним и тем же фрагментам, перепроверять найденное в браузере и переписывать уже работающие куски, когда выяснялось, что в оригинале логика хитрее, чем казалось на первый взгляд.
Зато в конце в браузере запускается то самое, из детства. Кровавые жуки бегают по лабиринту, ПакМен ест точки, снизу написано «Очки: 0».
Исходники открыты: github.com/esix/packman.