Вступление
Устаревшие технологии не исчезают. Они просто уходят в подполье: в архивы, на дискеты, в память тех, кто помнит, как это было. DOS-игры не просто программы. Это произведения инженерного искусства, созданные в эпоху, когда каждый байт имел значение, а каждый такт процессора, вес. Они работали на железе, которое сегодня кажется примитивным, но при этом умели то, что многим современным системам не под силу: дышать.
Моя первая игра была на диске. Она называлась Syndicate (1993, Bullfrog Productions), и я не понимал, как она работает. Я видел, как агенты стреляют, как взрываются машины, как звучит саундтрек, но не имел ни малейшего представления, что за этим стоит.
Я знал C, что такое переменные, циклы, указатели. Но я не мог объяснить, как в игре реализован путь юнита, как обрабатывается урон, как генерируется уровень. Тогда я не понимал кода, но код уже управлял мной. Спустя годы я вернулся к этим играм не как игрок, а как исследователь. И понял: они - лучшая школа программирования, которую только можно себе представить.
Современные игры скрывают свою архитектуру за слоями абстракций: виртуальные машины, движки, фреймворки. Чтобы понять, как они работают, нужно разобраться в десятках технологий.
DOS-игры - другое дело: нет виртуальных машин; нет сборщиков мусора; нет драйверов. Есть только процессор, память и код, написанный на C/C++ или ассемблере. Это делает их идеальной школой для изучения реального программирования.
Дизассемблирование таких игр - это не про взлом. Это археология программирования: вы не ломаете систему, а восстанавливаете её логику по обломкам машинного кода, как археолог, собирающий мозаику из черепков.
Именно так рождаются новые возможности:
из анализа - понимание;
из понимания - воссоздание;
из воссоздания - развитие
Люди переписывают ретро-игры, потому что:
хотят сохранить то, что ускользает от времени;
хотят понять, как это было сделано;
хотят внести своё: новую механику, новый контент, новую жизнь
Эта статья не руководство по взлому. Это пошаговое руководство по исследованию. Мы не будем запускать ZZT или Lemmings ради ностальгии. Мы будем вскрывать их код, чтобы увидеть, как они принимали решения, как реагировали на ввод, как рисовали графику.
И, возможно, научиться у них.
Что такое дизассемблирование?
Дизассемблирование - это не просто перевод байтов в ассемблер. Это вскрытие "чёрного ящика", чтобы понять, как программа принимает решения, обрабатывает ввод, рисует графику и управляет памятью.
Когда вы дизассемблируете .EXE-файл игры под DOS, вы не просто смотрите на код, вы восстанавливаете логику разработчика, его оптимизации, приёмы, даже ошибки. Вы видите, как он обходил ограничения 640 КБ, как работал с видеопамятью напрямую, как реализовывал ИИ врагов.
Для тех, кто помнит DOS
Для нас, кто рос на этих играх, дизассемблирование - это возможность вернуться к истокам. Это не ностальгия, а творческое воссоздание:
восстановить любимую игру;
добавить поддержку современных систем;
исправить баги;
расширить контент
Пример - KeeperFX, где фанаты не просто запустили Dungeon Keeper на Windows 7, 10 и 11, а переписали, расширили, оживили её. Это не ремейк - это наследие, переданное следующему поколению.
Для нового поколения
Для тех, кто вырос на Unity и Python, DOS-игры - это школа настоящего программирования. Здесь нет абстракций, нет виртуальных машин, есть только человек и процессор, в прямом диалоге. Изучая такие игры, вы:
понимаете, как работает память, порты, прерывания,
учитесь писать эффективный, предсказуемый код,
развиваете аналитическое мышление - ведь вы восстанавливаете логику по фрагментам. Это фундамент для создания: собственных ОС, драйверов, игровых движков, систем реального времени.
Дизассемблирование - это реверс-инжиниринг, востребованный навык в:
кибербезопасности;
анализе вредоносов;
восстановлении legacy-систем;
разработке совместимых решений
Игры вроде M.A.X., Z или Syndicate - не просто развлечение. Это образцы инженерного мастерства, где каждый байт на счету, а каждая инструкция результат компромисса между скоростью, памятью и возможностями железа.
Дизассемблирование таких игр - это как пройти стажировку у лучших программистов 90-х. Вы не просто читаете код - вы учитесь у него. И, возможно, однажды, создадите что-то своё, ещё лучше.
Шаг 1: Выбираем игру — от простого к сложному
Выбор игры, первый и один из самых важных шагов. От него зависит, будет ли ваш путь обучением или бесконечной головной болью. Я предложил начать с таких игр, как ZZT, Lemmings, Commander Keen, M.A.X. или Z, не потому что они "лучшие", а потому что они:
Просты по архитектуре, их движки понятны, логика линейна;
Написаны на C или смеси C и ассемблера, проще для анализа, чем чистый ассемблер;
Не имеют сложной упаковки или защиты, меньше препятствий на старте;
Достаточно документированы, в интернете есть гайды, обсуждения, скриншоты памяти
M.A.X. (1996) - тактическая стратегия с элементами RTS и RPG - отличный выбор. Её движок использует предсказуемую структуру данных для юнитов и карт, и не перегружен графикой. Это позволяет сфокусироваться на логике ИИ, системе строительства и обработке ввода, не теряясь в сложности.
Z (1996) - быстрая изометрическая стратегия - ещё один сильный кандидат. Игра написана компактно, с акцентом на производительность. Её ИИ врагов, физика движения и система уровней - отличные объекты для анализа.
Эти игры не так часто разбираются, как Duke Nukem, что делает их свежим полем для исследования, особенно на платформах, где тема Duke Nukem уже исчерпана.
Но что, если вы хотите большего вызова?
"А что, если выбрать игру на чистом ассемблере?" - Игры на чистом ассемблере - это уже не стартовая площадка, а полигон для глубокого анализа. Они подходят тем, кто уже освоил основы дизассемблирования и хочет испытать себя на коде, где каждая инструкция - результат ручной оптимизации.
Prince of Persia (1989);
RollerCoaster Tycoon (1999);
DOS-демки (demoscene) часто, 100% ассемблер.
Плюсы:
Максимальная производительность;
Прямой контроль над железом;
Чистый, оптимизированный код
Минусы:
Нет структур, функций, типов - только метки и прыжки;
Тяжело читать логику;
Тяжело отличить данные от кода;
Легко потерять контекст
"А что, если игра уже с открытым кодом? Например, LBA1 или LBA2?" - есть проекты, где разработчики официально открыли исходный код, например:
Wolfenstein 3D, Doom - id Software открыла код ещё в 90-х;
"Почему тогда дизассемблировать, если код уже есть?"
Потому что:
Открытый код ≠ оригинальный бинарник. Исходник может быть переписан, адаптирован, утерян или не соответствовать релизной версии;
Анализ бинарника учит читать "настоящий" код, как он был скомпилирован, оптимизирован, упакован;
Это тренировка для случаев, когда исходников нет, а таких случаев 99%.
Вы можете использовать открытый код как эталон. Дизассемблируйте .EXE, а потом сравните с исходником и увидите, как компилятор превращает C в ASM.
Ещё лучше, самим попробовать пересобрать игру. Возьмите исходный код (например, из репозитория LBA или Doom), скомпилируйте его с помощью Open Watcom, Turbo C++ или современного кросс-компилятора под DOS, и посмотрите, что получится. Затем сравните ваш .EXE с оригинальным релизным файлом в Ghidra или ImHex.
Вы увидите:
Какие оптимизации применил компилятор;
Как изменилась структура кода;
Где появились различия в вызовах;
Как обрабатываются данные (уровни, графика, звук)
Иногда ваша сборка будет почти идентична оригиналу и это победа. А иногда, сильно отличаться и тогда вы поймёте, что:
использовался другой компилятор;
были ручные ассемблерные вставки;
применялась упаковка;
или код модифицировался после компиляции
Это уникальный шанс увидеть разницу между "чистым" кодом и тем, что попало в руки игрокам. Именно так исследователи поняли, как работали тайминги в Doom, как оптимизирована физика в Duke Nukem 3D, или как реализован ИИ в LBA Remake.
Иногда исходный код не просто недоступен - он уничтожен.
В 2020 году в мировой прессе появились статьи о судьбе исходного кода Fallout 1 и 2. Дизайнер Тим Кейн (Tim Cain) рассказал, что ему приказали удалить весь код и наработки с личного компьютера, включая бумажные зарисовки, заметки и концепты. Компания-издатель (тогда Interplay) боялась утечки. Но спустя время, одна из основательниц Interplay, Ребекка Хайнеман заявила: код не утерян. Он сохранился. Утеряны: личные записи, пометки, логика решений тех, кто создавал игру. А сам код находится у текущего владельца прав - Bethesda и только они могут разрешить его публикацию.
Именно поэтому, реверс-инжиниринг становится актом сохранения культурного наследия. Когда доступ к исходникам закрыт, дизассемблирование - единственный способ понять, как работала игра, и передать её дух следующему поколению.
Совет: начните с малого, но думайте о большом. Выберите игру, которая:
вам нравится;
не слишком большая;
имеет понятную механику (платформер, головоломка, текстовая RPG)
Шаг 2: Инструменты — подготовка к анализу
Перед тем как загружать исполняемый файл в дизассемблер, необходимо провести предварительный анализ бинарника. Многие DOS-программы были упакованы, защищены или скомпилированы с нестандартными компиляторами. Если вы пропустите этот этап, Ghidra (или другой дизассемблер) может не распознать формат, неверно определить точку входа или вообще отказаться открыть файл.
Detect It Easy (DIE) - Анализ исполняемого файла: определение формата, компилятора, упаковщика, архитектуры. Отлично распознаёт DOS MZ-экзешники, упаковщики (например, UPX, FSG, ASPack), и может подсказать, сдвинута ли точка входа (OEP);
ImHex - Hex-редактор с анализом бинарников. Позволяет просматривать структуру, искать строки, сигнатуры. Поддерживает плагины для анализа форматов, включая MZ. Удобен для поиска ресурсов и сжатых данных;
DOSBox - Эмулятор MS-DOS для запуска и тестирования игры. Классический DOSBox (0.74) больше не развивается, но стабилен;
DOSBox-X - Современный форк DOSBox с точной эмуляцией железа, включая cycle-exact CPU. Активно развивается. Идеален для анализа игр, чувствительных к таймингам;
PCEm - Утилита для записи и воспроизведения состояния DOS-системы;
Ghidra - Дизассемблер и анализатор бинарников от NSA. Поддерживает DOS MZ-формат, но не работает с упакованными файлами;
IDA Free - Мощный дизассемблер, "золотой стандарт" реверс-инжиниринга. Поддерживает 16-битный код. Удобен для анализа после распаковки;
OllyDbg 1 и OllyDbg 2- Отладчик для анализа программ в реальном времени. Может использоваться для анализа поведения в эмуляторе;
Radare2 (r2) - Открытый фреймворк для анализа бинарников в командной строке. Поддерживает x86-16 и MZ. Отлично подходит для автоматизации;
Turbo Debugger (TD) - Входил в пакет Borland Turbo C++/Pascal/Assembler. Работает непосредственно в DOS. Позволяет ставить брейкпоинты, смотреть регистры, память. Примечание: под "свой" эмулятор надо искать стабильную сборку. "Свою" я нашел в TASM 4;
Open Watcom / Turbo C++ / Watcom C - Компиляторы для написания и компиляции кода под DOS. Позволяют воссоздать поведение оригинальной игры (Для самого Watcom, дам ссылку позже);
VS Code / Notepad++ / VS Studio и другие - Текстовые редакторы для ведения заметок и написания кода. Удобны для аннотирования функций, записи гипотез;
SoftIce - Легендарный отладчик и дизассемблер для DOS и Windows 9x. Работал на уровне ядра, позволял ставить брейкпоинты на прерывания, анализировать распаковщики, находить OEP. Требует DOS-режим или точную эмуляцию (PCEm, DOSBox-X);
Sourcer - Легендарный отладчик для DOS
Вы могли задаться вопросом: "Почему мы не используем QEMU - мощный и точный эмулятор, способный моделировать реальное железо?"
Ответ прост: наша цель не системная эмуляция, а анализ логики программы.
QEMU — отличный выбор для задач, требующих точной моделировки железа, анализа драйверов или реверса упакованных программ с защитой. Однако он слишком сложен и избыточен для первого этапа реверс-инжиниринга DOS-игр.
Шаг 3: Запускаем игру и изучаем её поведение
Перед тем как приступать к дизассемблированию, важно понять, как игра работает с точки зрения пользователя и системы. Это ваша "обратная связь", вы будете сравнивать поведение оригинала с тем, что воссоздаёте. Для этого нужно запустить игру в совместимом окружении, которое максимально точно воспроизводит оригинальную DOS-систему. Обычно это делается с помощью эмуляторов ПК, таких как DOSBox, DOSBox-X или PCEm.
Важно: поведение игры может отличаться в разных эмуляторах из-за различий в точности эмуляции железа, таймингов, обработки прерываний и доступа к памяти.
Что нужно сделать:
Подготовьте игру: убедитесь, что у вас есть оригинальный .EXE или .COM файл, а также, при необходимости, файлы данных (уровни, графика, звук);
Выберите эмулятор:
Для высокой совместимости и простоты - DOSBox;
Для точного анализа и отладки - DOSBox-X (лучшая поддержка отладочных функций);
Для записи состояния и реверс-инжиниринга в реальном времени - PCEm
Настройте эмулятор:
Смонтируйте папку с игрой как виртуальный диск;
Убедитесь, что установлены правильные настройки: CPU-циклы, видеорежим (VGA, EGA), звуковая карта (AdLib, Sound Blaster);
При необходимости, включите режим отладки
Почему поведение может отличаться:
PCEm может изменять поведение программы, так как перехватывает системные вызовы для записи состояния.
DOSBox иногда упрощает тайминги, что может повлиять на логику игр, зависящих от задержек.
DOSBox-X предлагает режимы точной эмуляции, включая cycle-exact CPU и точное поведение DMA, что критично для анализа.
Рекомендация:
Сравнивайте поведение игры в DOSBox-X (для анализа) и PCEm (для записи сценариев). Если вы видите расхождения — это не ошибка, а артефакт эмуляции.
Истинное поведение — то, которое ближе к оригинальному железу.
Что фиксировать: как вести заметки перед дизассемблированием
Прежде чем открывать Ghidra, ваша задача собрать как можно больше "подсказок" из поведения игры. Это сэкономит часы анализа. Ведите заметки в текстовом редакторе (VS Code, Notepad++) или прямо в блокноте. Чем больше вы запишете информации про игру, тем проще вам будет находить в коде ресурсы, им соответствующие, и выявлять логику их вызова.
Что стоит записать:
Графический режим - Какой режим используется? Ищите подсказки: Разрешение: 320x200 (Mode 13h), 640x480 (Mode 12h)? Количество цветов? Есть ли текстовый режим (80x25)?
Управление - Как работает ввод? Клавиатура? Мыши? Джойстик? Есть ли поддержка мыши? Используются ли прямые порты (in/out) или BIOS-вызовы?
Звук и музыка - Есть ли звук? Какой тип? AdLib? Sound Blaster? Внутренний динамик?
Структура игры - Как организовано меню? Как загружаются уровни? Есть ли сохранения? Какие строки в игре выделяются? (например: "Game over", "Level 1")
Теперь вы не просто игрок - вы аналитик. И каждая запись в блокноте - это ключ к следующему фрагменту кода.
Шаг 4: Как работает Ghidra — что происходит под капотом
Когда вы открываете .EXE-файл в Ghidra, она не просто показывает ассемблер. Она пытается восстановить вашу программу так, будто у него есть исходный код. Но исходников нет, только байты. И Ghidra должна "гадать": на основе паттернов, структур, вызовов.
Это не магия, а сложный, многоэтапный процесс, и понимание его поможет вам:
не доверять слепо результатам;
понимать, почему Ghidra ошибается;
и вручную исправлять её гипотезы.
Как Ghidra "думает", краткий пайплайн:
Разбор бинарника - Ghidra сначала определяет формат файла (DOS MZ, PE и т.д.) и загружает его в память;
Декодирование инструкций - С помощью Sleigh - внутреннего языка описания архитектур, Ghidra превращает байты в инструкции x86;
Построение графа управления (CFG) - Ghidra строит Control Flow Graph, чтобы понять, какие блоки кода ведут к каким. Без этого нельзя определить границы функций;
Преобразование в SSA (Static Single Assignment) - Каждая переменная получает уникальное имя. Это помогает анализировать, как значения передаются между инструкциями, особенно после оптимизаций;
Анализ типов и структур - Ghidra "гадает", что означают числа: указатель? смещение? часть структуры?
Генерация C-подобного кода - На основе анализа Ghidra строит декомпилированный C-код, но это не исходник - это реконструкция, основанная на предположениях.
Ghidra может ошибаться, если:
код оптимизирован;
использованы косвенные вызовы;
функции встроены (inline);
нестандартные структуры данных
Понимание того, как работает Ghidra, делает вас умнее, чем если бы вы просто нажимали "Decompile"
Шаг 5: Находим ключевые части — архитектура x86 и ассемблер в контексте реверс-инжиниринга
После запуска .EXE-файла в Ghidra вы увидите список инструкций: mov ax, 13h
, int 10h
, cmp cx, dx
. Это не случайный набор команд. Это низкоуровневый код, непосредственно выполняемый процессором. Даже если вы не работали с ассемблером напрямую, понимание основных элементов x86-архитектуры критически важно для анализа. Ведь именно здесь, на уровне регистров, прерываний и машинных инструкций, принимаются ключевые решения: как инициализируется видео, как обрабатывается ввод, как загружаются данные.
Как Ghidra видит типы данных:
Когда вы открываете .EXE-файл в Ghidra, вы увидите такие обозначения, как (примеры):
word_00401000
dword_00402000
undefined2
undefined4
Это типы данных, которые Ghidra использует для описания размера значения в памяти.
byte - 8 бит (1 байт) - одиночный байт. Эквивалент в C - char
word - 16 бит (2 байта) - "слово" в 16-битной архитектуре. Эквивалент в C - short
dword - 32 бита (4 байта) - "двойное слово". Эквивалент в C - int или long
qword - 64 бита (8 байт) - "четырёхное слово". Эквивалент в C - long long
В DOS-играх word - это "стандарт", если процессор работает в 16-битном режиме. Соответственно для 32 бит, "стандартом" будет - dword
Регистры: интерфейс между процессором и данными
Процессор x86 использует регистры - высокоскоростные ячейки, встроенные в CPU. Они служат как основное рабочее пространство для выполнения инструкций. В 16-битном режиме (real mode), в котором работали DOS-игры, ключевые регистры:
AX, BX, CX, DX - Общего назначения;
AH, AL, BH, BL и т.д. - Младшие (Low) и старшие (High) байты 16-битных регистров. Например,
AX = AH:AL
(старший байт: младший);SI, DI - Индексные регистры:
SI
(Source Index),DI
(Destination Index) - часто используются в строковых операциях (movsb
,cmpsd
);SP - Указатель стека (Stack Pointer);
BP - Базовый указатель (Base Pointer) - для доступа к локальным переменным и параметрам;
IP - Указатель команд (Instruction Pointer) — текущий адрес выполнения
В 32-битном режиме эти регистры расширены:
EAX, EBX, ECX, EDX;
ESP, EBP, ESI, EDI
В Ghidra вы можете встретить как 16-, так и 32-битные инструкции, особенно если игра была скомпилирована с поддержкой 386+.
Во второй части мы познакомимся с основными инструкциями, такими как: mov, add, cmp и другими...