Хабрахабр

Адаптация программ для ZX Spectrum к TR-DOS современными средствами. Часть 1

Это значит, что загрузка с каждого типа носителя требовала отдельной реализации и в большинстве случаев программу нельзя было просто так скопировать с кассеты на дискету. В отличие от современных компьютеров, на спектрумах понятия файловой системы не было как такового. Однако ситуация осложнялась тем, что во многих играх (как фирменных так и взломанных) загрузчики были написаны в машинных кодах и иногда содержали защиту от копирования. В случаях, когда загрузчик программы был написан на бейсике, его можно было адаптировать к TR-DOS довольно простой доработкой.

25" Floppy"/> <img src="http://orion-int.ru/wp-content/uploads/2019/05/adaptaciya-programm-dlya-zx-spectrum-k-tr-dos-sovremennymi-sredstvami-chast-1.jpg" alt="5.

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

В этой статье я расскажу, как выполнить такую адаптация на примере игры Pac-Man, а именно, оригинального образа Pac-Man.tzx.

Инструменты

Основная причина в том, что особенно поначалу процесс адаптации состоит из большого количества проб и ошибок, и он проходит гораздо менее болезненно, если его автоматизировать. Несмотря на то, что в былые времена вся такая работа делалась непосредственно на ZX Spectrum (за отсутствием других вариантов), я буду адаптировать игру с использованием эмулятора и утилит командной строки. Всё то же самое можно проделать и непосредственно на спектруме.

В первой части мы будем использовать следующие инструменты:

  1. Эмулятор Fuse для отладки и тестирования.
  2. SkoolKit для дизассемблирования.

Отключение автозапуска в загрузчике

Нужно найти, где эти коды располагаются и с какого адреса запускаются. Поскольку файл картинки и данных загружается без заголовочного блока (17 байт с именем и типом файла), это означает, что загрузчик написан в машинных кодах.

Есть несколько способов посмотреть на код загрузчика:

  1. Во многих случаях это работает, но в случае с Pacman, как и во многих других, это приводит к сбросу. Самый простой — начать загружать программу, дождаться, пока загрузчик запустится, и остановить его нажатием клавиши Space.

  2. В отличие от LOAD, MERGE игнорирует автозапуск программы. Следующий способ — загрузить программу с использованием MERGE "" вместо LOAD "". Это связано с тем, что вместо того, чтобы выполнять программу построчно, MERGE пытается разобрать её целиком и слить с уже загруженной программой. В случае с Pac-Man загрузка через MERGE приводит к зависанию компьютера с характерным сдвигом экрана влево. Однако, если в программе есть блок с машинными кодами, который нарушает синтаксис программы, это приводит к сбою.

  3. Если не хочется ломать голову, можно преобразовать образ ленты из TZX в TAP и воспользоваться утилитой listbasic, которая поставляется вместе с Fuse:

    $ tzx2tap Pac-Man.tzx
    $ listbasic Pac-Man.tap 1 RANDOMIZE USR (PEEK 23635+256*PEEK 23636+91)

    Таким образом, точка входа в загрузчик смещена на 91 байт относительно области бейсика. Адрес 23635 ($5C53) соответствует системной переменной PROG, которая содержит начальный адрес области бейсика.

  4. В отладчике Fuse нужно поставить точку останова br 2053, загрузить программу, а когда загрузка закончится и выполнение кода прервётся, выполнить записать set 23619 128. Ещё один способ посмотреть на загрузчик описан в статье Desativando a autoexecução de um programa BASIC. Это предотвратит автозапуск программы и позволит выйти в бейсика.

Дизассемблирование загрузчика

В случае с ZX Spectrum 48К без загруженной TR-DOS, область бейсика начинается с адреса 23755 ($5CCB). Зная смещение точки входа относительно области бейсика, можно рассчитать её абсолютный адрес. Следовательно, загрузчик будет начинаться с адреса 23755 + 91 = 23846 ($5D26).

В Fuse можно сделать br 23846 и начать загружать программу. Для начала достаточно поставить точку останова на начальном адресе и посмотреть на машинные коды. Как только загрузчик начнёт выполняться, эмулятор остановится:

Debugger

Обычно код загрузки беззаголовочного файла выглядит приблизительно так: В случае, когда загрузчик совсем простой, достаточно посмотреть на дизассемблированный код в средней панели и понять, что куда загружается.

LD IX, $8000 ; начальный адрес загрузки
LD DE, $4000 ; длина загружаемого файла
LD A, $FF ; индикатор тела файла
CALL $0556 ; вызов LD-BYTES
JP $8000 ; переход в программу

Для этого хорошо подходит набор утилит SkoolKit. В более сложном случае с выполнением кода нужно разбираться по шагам и делать пометки. Как это делается, подробно описано в документации. Если задаться целью, с её помощью игру можно разобрать до последнего винтика (сообщения, спрайта, звука).

Если кратко, нужно сделать следующее:

  1. Сделать снапшот Pac-Man.z80 памяти компьютера, используя tap2sna.py или возможности эмулятора.
  2. Создать контрольный файл Pac-Man.ctl с начальным набором инструкций для дизассемблирования:

    i 16384 Ignore for now
    c $5D26 Loader

  3. Запустить дизассемблирование: sna2skool.py -H -c Pac-Man.ctl Pac-Man.z80 > Pac-Man.skool.
  4. В ходе изучения кода добавлять новые инструкции и комментарии в контрольный файл.
  5. Повторять до полного просветления.

В результате, после первого прохода получаем следующее (комментарии мои, адреса опущены):

ORG $5D26 ; те самые 23846, определённые выше ; Запрет прерываний
DI
IM 1 ; Расшифровка загрузчика
LD D, IYh ;
LD E, IYl ;
LD B, $25 ; Длина зашифрованного загрузчика
EX DE, HL ;
LD DE, $0019 ;
ADD HL, DE ; На этом этапе HL содержит $5C53 (адрес переменной PROG)
LD E, (HL) ; Загружаем значение PROG в DE и IX
INC HL ;
LD D, (HL) ;
LD IXh, D ;
LD IXl, E ;
LD A, (IX+$7F) ; Загружаем ключ расшифровки в аккумулятор (находится в $7F-м байте ; относительно PROG)
LD HL, $0035 ; Начало зашифрованного загрузчика ($35 байт относительно PROG)
ADD HL, DE ;
PUSH HL ; Сохраняем адрес загрузчика на стеке
XOR (HL) ; Цикл расшифровки загрузчика
LD (HL), A ;
INC HL ;
DJNZ $5D43 ; Конец цикла
AND (HL) ; RET NZ ; По окончании расшифровки переходим в загрузчик по адресу на стеке ; Ключ для расшифровки
DEFB $77

Расшифровка загрузчика

Это значит, что если мы поставим точку останова br 23808, то к этот момент расшифровка уже выполнится мы увидим расшифрованный загрузчик: Всё, что из этого действительно важно, это то, что расшифрованный загрузчик находится по адресу PROG + $35.

Loader

В регистры IX и DE загружается значение $4000 (16384), делается что-то ещё и передаётся управление подпрограмме ПЗУ по адресу $055A (это на несколько байт ниже чем стандартная точка входа в LD-BYTES). Эта программа уже гораздо более похожа на типичный случай, упомянутый выше. стандартной процедурой этот файл не загружается и некоторые копировщики его не понимают. Похоже, такой подход реализует какую-то защиту от копирования, т.к.

Точка входа в программу

Вместо привычного CALL LD-BYTES и JP здесь используется LD SP, XXXX и JP LD-BYTES. Осталось разобраться, как же вызывается программа после загрузки. Первый (обычныйы) вариант работает следующим образом:

  1. CALL кладёт на стек текущее значение программного счётчика (PC).
  2. Управление передаётся вызываемой подпрограмме.
  3. При возврате из подпрограммы (RET) значение со стека снимается и происходит переход в вызывающую программу.

Дело в том, что Pac-Man совместим с ZX Spectrum 16K и занимает абсолютно всю оперативную память (см. Почему здесь сделано иначе? Таким образом, загружаясь, программа затирает собой и загрузчик, и стек, где бы они ни находились. размер файла выше). Если бы мы хотели перейти из ПЗУ в загрузчик с использованием стека и далее вызывать загруженную программу через JP, на момент окончания загрузки ни адреса, по которому находится JP, ни самой инструкции в памяти уже не было бы.

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

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

Итого

В результате изучения загрузчика мы выяснили следующее:

  1. Беззаголовочный файл длиной 16384 байт загружается по адресу 16384 (в экранную область, что в общем-то очевидно в процессе загрузки).
  2. По окончании загрузки указатель стека находится по адресу $5D7C, куда и передаётся управление.

В следующих частях я расскажу, о том как подготовить файлы для записи на диск и написать загрузчик моноблочного файла на ассемблере.

Ссылки по теме:

  1. Профлицей «ТРУЪ Спектрумист».
  2. Reverse engineering ZX Spectrum (Z80) games.
  3. Adaptação de jogos de fita para Beta 48.
Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть