Хабрахабр

Адаптация программ для 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.
Теги
Показать больше

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

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

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

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