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

Несмотря на наличие «волшебной кнопки», которая просто делала полный дамп памяти компьютера и позволяла хоть как-то сохранить программу на дискету, среди специалистов считалось хорошим тоном создавать дисковые версии игр с сохранением оригинальной загрузочной картинки и прочих атрибутов.
В этой статье я расскажу, как выполнить такую адаптация на примере игры Pac-Man [1], а именно, оригинального образа Pac-Man.tzx [2].
Несмотря на то, что в былые времена вся такая работа делалась непосредственно на ZX Spectrum (за отсутствием других вариантов), я буду адаптировать игру с использованием эмулятора и утилит командной строки. Основная причина в том, что особенно поначалу процесс адаптации состоит из большого количества проб и ошибок, и он проходит гораздо менее болезненно, если его автоматизировать. Всё то же самое можно проделать и непосредственно на спектруме.
В первой части мы будем использовать следующие инструменты:
Поскольку файл картинки и данных загружается без заголовочного блока (17 байт с именем и типом файла), это означает, что загрузчик написан в машинных кодах. Нужно найти, где эти коды располагаются и с какого адреса запускаются.
Есть несколько способов посмотреть на код загрузчика:
Самый простой — начать загружать программу, дождаться, пока загрузчик запустится, и остановить его нажатием клавиши Space. Во многих случаях это работает, но в случае с Pacman, как и во многих других, это приводит к сбросу.
Следующий способ — загрузить программу с использованием MERGE "" вместо LOAD "". В отличие от LOAD, MERGE игнорирует автозапуск программы. В случае с Pac-Man загрузка через MERGE приводит к зависанию компьютера с характерным сдвигом экрана влево. Это связано с тем, что вместо того, чтобы выполнять программу построчно, MERGE пытается разобрать её целиком и слить с уже загруженной программой. Однако, если в программе есть блок с машинными кодами, который нарушает синтаксис программы, это приводит к сбою.
Если не хочется ломать голову, можно преобразовать образ ленты из TZX в TAP и воспользоваться утилитой listbasic, которая поставляется вместе с Fuse:
$ tzx2tap Pac-Man.tzx
$ listbasic Pac-Man.tap
1 RANDOMIZE USR (PEEK 23635+256*PEEK 23636+91)
Адрес 23635 ($5C53) соответствует системной переменной PROG [5], которая содержит начальный адрес области бейсика. Таким образом, точка входа в загрузчик смещена на 91 байт относительно области бейсика.
Ещё один способ посмотреть на загрузчик описан в статье Desativando a autoexecução de um programa BASIC [6]. В отладчике Fuse [7] нужно поставить точку останова br 2053, загрузить программу, а когда загрузка закончится и выполнение кода прервётся, выполнить записать set 23619 128. Это предотвратит автозапуск программы и позволит выйти в бейсика.
Зная смещение точки входа относительно области бейсика, можно рассчитать её абсолютный адрес. В случае с ZX Spectrum 48К без загруженной TR-DOS, область бейсика начинается с адреса 23755 ($5CCB). Следовательно, загрузчик будет начинаться с адреса 23755 + 91 = 23846 ($5D26).
Для начала достаточно поставить точку останова на начальном адресе и посмотреть на машинные коды. В Fuse можно сделать br 23846 и начать загружать программу. Как только загрузчик начнёт выполняться, эмулятор остановится:

В случае, когда загрузчик совсем простой, достаточно посмотреть на дизассемблированный код в средней панели и понять, что куда загружается. Обычно код загрузки беззаголовочного файла выглядит приблизительно так:
LD IX, $8000 ; начальный адрес загрузки
LD DE, $4000 ; длина загружаемого файла
LD A, $FF ; индикатор тела файла
CALL $0556 ; вызов LD-BYTES
JP $8000 ; переход в программу
В более сложном случае с выполнением кода нужно разбираться по шагам и делать пометки. Для этого хорошо подходит набор утилит SkoolKit [4]. Если задаться целью, с её помощью игру можно разобрать до последнего винтика (сообщения, спрайта, звука). Как это делается, подробно описано в документации [8].
Если кратко, нужно сделать следующее:
Pac-Man.z80 памяти компьютера, используя tap2sna.py или возможности эмулятора.Pac-Man.ctl с начальным набором инструкций для дизассемблирования:
i 16384 Ignore for now
c $5D26 Loader
sna2skool.py -H -c Pac-Man.ctl Pac-Man.z80 > Pac-Man.skool.В результате, после первого прохода получаем следующее (комментарии мои, адреса опущены):
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
Всё, что из этого действительно важно, это то, что расшифрованный загрузчик находится по адресу PROG + $35. Это значит, что если мы поставим точку останова br 23808, то к этот момент расшифровка уже выполнится мы увидим расшифрованный загрузчик:

Эта программа уже гораздо более похожа на типичный случай, упомянутый выше. В регистры IX и DE загружается значение $4000 (16384), делается что-то ещё и передаётся управление подпрограмме ПЗУ по адресу $055A (это на несколько байт ниже чем стандартная точка входа в LD-BYTES [9]). Похоже, такой подход реализует какую-то защиту от копирования, т.к. стандартной процедурой этот файл не загружается и некоторые копировщики его не понимают.
Осталось разобраться, как же вызывается программа после загрузки. Вместо привычного CALL LD-BYTES и JP здесь используется LD SP, XXXX и JP LD-BYTES. Первый (обычныйы) вариант работает следующим образом:
CALL кладёт на стек текущее значение программного счётчика (PC).RET) значение со стека снимается и происходит переход в вызывающую программу.Почему здесь сделано иначе? Дело в том, что Pac-Man совместим с ZX Spectrum 16K и занимает абсолютно всю оперативную память (см. размер файла выше). Таким образом, загружаясь, программа затирает собой и загрузчик, и стек, где бы они ни находились. Если бы мы хотели перейти из ПЗУ в загрузчик с использованием стека и далее вызывать загруженную программу через JP, на момент окончания загрузки ни адреса, по которому находится JP, ни самой инструкции в памяти уже не было бы.
Вместо этого указатель стека перемещается на область памяти, по которому после загрузки окажется адрес точки входа в программу, и процессор, не заметив подмены, снимет его со стека по новому указателю и перейдёт по указанному адресу.
Полный результат дизассемблирования [10] можно посмотреть в репозитории проекта [11] на гитхабе.
В результате изучения загрузчика мы выяснили следующее:
$5D7C, куда и передаётся управление.В следующих частях я расскажу, о том как подготовить файлы для записи на диск и написать загрузчик моноблочного файла на ассемблере.
Автор: DeadMoroz
Источник [15]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/317270
Ссылки в тексте:
[1] Pac-Man: http://www.worldofspectrum.org/infoseekid.cgi?id=0003581
[2] Pac-Man.tzx: http://www.worldofspectrum.org/pub/sinclair/games/p/Pac-Man.tzx.zip
[3] Fuse: https://sourceforge.net/projects/fuse-emulator/
[4] SkoolKit: http://skoolkit.ca/
[5] PROG: https://skoolkid.github.io/rom/asm/5C53.html
[6] Desativando a autoexecução de um programa BASIC: http://cantinhotk90x.blogspot.com/2012/06/desativando-autoexecucao-de-um-programa.html
[7] отладчике Fuse: http://manpages.ubuntu.com/manpages/bionic/man1/fuse.1.html#monitor/debugger
[8] документации: https://skoolkit.readthedocs.io/en/v7.1/diy.html
[9] LD-BYTES: https://skoolkid.github.io/rom/asm/0556.html
[10] результат дизассемблирования: https://github.com/morozov/pacman/tree/master/skool
[11] репозитории проекта: https://github.com/morozov/pacman
[12] Профлицей «ТРУЪ Спектрумист»: https://zxrainbow.wordpress.com
[13] Reverse engineering ZX Spectrum (Z80) games: https://mrcook.uk/reverse-engineering-zx-spectrum-games
[14] Adaptação de jogos de fita para Beta 48: http://cantinhotk90x.blogspot.com/2012/07/adaptacao-de-jogos-de-fita-para-beta-48.html
[15] Источник: https://habr.com/ru/post/451174/?utm_campaign=451174
Нажмите здесь для печати.