Когда-то давно я писал эмулятор ZX Spectrum под PalmOS. Единственным форматом файлов, который поддерживал мой эмулятор, был формат SNA. Он был выбран в силу своей простоты и достаточной распространённости. Несмотря на то, что SNA грузится моментально, в этом формате почти всегда отсутствуют заставки, игры уже настроены под определённый тип машины и т.д. Я решил хотя бы частично решить данные проблемы, и для своего эмулятора я подготовил пачку "идеальных" снепшотов. Я делал их вручную из файлов TZX. Процесс заключался в следующем: я начинал загружать TZX, выходил в отладчик и далее трассировал загрузку до того момента, пока с "ленты" не загружался последний байт. После этого я сохранял SNA. И вроде всё хорошо, но сохранённую в этих снепшотах заставку невозможно было разглядеть, так как игра сразу же стартовала. Я выкрутился тем, что внедрял в эти снепшоты коротенькую процедуру, которая крутилась в цикле и ждала нажатия любой клавиши. Эту процедуру я клал на стек, пересчитывал адрес запуска и сохранял модифицированный снепшот. Таким образом, после загрузки такого обработанного снепшота мы видели на экране заставку и могли её рассматривать до тех пор, пока не была нажата клавиша. Около десятка байт, записанных на стеке, в данных условиях я не считаю слишком большой ценой.
Всё это я делал вручную, производя расчёты на калькуляторе и меняя байты снепшота прямо в HEX-редакторе. Потом, помнится, мне несколько раз нужно было получить такие же снепшоты с паузой перед запуском, но мне было лень заново разбираться, какие я расчёты производил и как всё делал. И вот сейчас я решил всё восстановить и автоматизировать процесс, чтобы любой желающим мог вставлять паузу перед запуском снепшота. Такие снепшоты могут использоваться, например, для прошивания их в ROM-диск или картридж. А кроме того, новая прошивка для Scropion ZS-256 научилась запускать SNA-файлы непосредственно с жёсткого диска. В общем, применение найти можно.
В результате у меня получится скрипт, написанный на языке Python. Использовать его достаточно просто:
Код:
C:\>python pause2sna.py filename.sna
На выходе мы моментально получим файл filename_paused.sna с установленной паузой.
Скрипт поддерживает несколько ключей. И в зависимости от них, процедура паузы может помещаться в разные места памяти, а также изменяться в зависимости от других условий. И прежде чем перейти к описанию ключей, я приведу текст самой процедуры. В её самом "жирном" виде процедура выглядит так:
Код:
PUSH AF
XOR A
M1 IN A,(#FE)
OR #E0
INC A
JR Z,M1
POP AF
EI
JP START
Итого 14 байт
Очевидно, что некоторые команды могут быть сокращены. Так, чаще всего нам не потребуется сохранение регистровой пары AF (PUSH и POP). А если прерывания были запрещены, то нам не потребуется их разрешать (EI). Если регистр A перед записью снепшота и так содержал 0, то команда XOR будет лишней. А кроме того, у нас есть возможность поместить процедуру паузы на месте кассетного загрузчика, непосредственно перед текущим адресом запуска, и тогда JP START тоже будет лишней. В итоге процедура превратится просто в:
Код:
M1 IN A,(#FE)
OR #E0
INC A
JR Z,M1
Итого 7 байт
Что касается команд XOR A и EI, то на их наличие из отсутствие в процедуре мы непосредственно повлиять не можем - они по необходимости вставляются автоматически. XOR A добавляется, если в снепшоте регистр A не равен нулю. А EI добавляется, если в перед сохранением снепшота были разрешены прерывания. При этом после вставления паузы прерывания в самом снепшоте запрещаются.
Теперь перейдём непосредственно к ключам. Собственно, их всего три:
Этот ключ выводит небольшое описание по формату использования скрипта и его ключам.
Этот ключ не имеет дополнительных параметров, в просто своим наличием указывается скрипту добавить в процедуру команды PUSH AF и POP AF для сохранения регистра А и флагового регистра F. Это может потребоваться, если снепшот снимается где-то в середине игры, и исходное значение этих регистров, которые портятся процедурой паузы, является критичным.
Этот ключ указывает скрипту где нужно разместить процедуру паузы. По умолчанию процедура размещается в области стека, сразу ниже его вершины. Это хорошо с той точки зрения, что с высокой степенью вероятности данная процедура не затрёт ни единого байта каких-то полезных данных. Но основной минус заключается в том, что такой снепшот более нельзя модифицировать, записывая что-то на стек. Например, запускальщик файлов SNA на Scorpion ZS-256 перед запуском некоторые данные помещает на стек. Таким образом, данные снепшоты не смогут быть запущены на Скорпионе. Также снепшот окажется неработоспособным, если его второй раз прогнать через данный скрипт, поставив вторую паузу поверх уже существующей способом по умолчанию.
Выходом может служить вариант, когда вместо адреса мы укажем ноль (-а 0). В этом случае скрипт отступит несколько байт назад относительно текущего адреса запуска и поместит адрес запуска на начало процедуры. Такой вариант можно использовать с случае сохранения снепшота сразу после загрузки данных с ленты. В этом случае процедура паузы затрёт несколько байт кассетного загрузчика, который уже больше не понадобится.
Ну, и если вы знаете точное место в ОЗУ, где нет никаких данных и куда можно поместить процедуру паузы, то вы можете указать этот адрес. Например, это может быть адрес буфера принтера (-а 23296) или область где-то в экранном ОЗУ (-а 16384). Если вы укажете слишком высокий адрес в районе 65535, куда не поместится скрипт, то он ругнётся и укажет максимальное значение допустимого адреса.
Если же указать адрес, расположенный в пределах ПЗУ (кроме 0), то процедура будет размещена в области стека, как и есть по умолчанию.
Для желающих поэкспериментировать помимо самого скрипта прилагаю к этому сообщению снепшот игры Marauder, как раз сохранённый сразу же после загрузки последнего байта с "ленты" (TZX).