iS-DOS/TASiS ч.2
Как "отвязываться" от системы
и включать её обратно. Принцип загрузки
блока кодов и возврата в оболочку
Теперь, на основе вышеизложенного, мы
имеем достаточно информации для того,чтобы
писать игры под iS-DOS/TASiS, практически
не оглядываясь на такие ограничения памя─
ти,как наличие в ОЗУ ядра ОС или системных
переменных.Можно даже писать универсальные
игры, которые можно запускать хоть под
iS-DOS, хоть под TR-DOS. Для этого всего
лишь нужно, чтобы итоговые "кодовые" файлы
формировались отдельно (как в старых лен─
точных версиях, причём для основного ОЗУ
файлы отдельно и для записи оверлеев в
страницы тоже отдельные файлы). Тогда для
версий под разные ОС просто надо будет на─
писать свои загрузчики (в случае с TR-DOS
- прямо на бейсике через RANDOMIZE USR
15619:REM:LOAD 'filename" CODE adress ), и
дело сделано (для 128К и выше версий игр
неплохо бы предусмотреть гибкую таблицу
используемых страниц с настройкой портов
управления ими). Что же касается iS-DOS,
то тут алгоритм таков.
Чтобы отключить систему и перейти к
"полноправному" использованию всего досту─
пного адресного пространства, достаточно:
1.Запретить прерывания, сохранив зна─
чение используемого системой вектора пре─
рываний для последующего восстановления, а
затем переназначить его для использования
в прерываниях пользователя.
2.Сохранить где-нибудь указатель стека
в системе и переназначить куда нужно поль─
зователю.
3.Сохранить в верхней памяти (путём
простого копирования или переключения
страниц) ядро системы и при необходимости
обеспечить по адресу #C000 либо страницу
0, либо страницу из нижних 128 КБ. Если
возможно сохранение ядра, по размерам не
превышающего 16 КБ (т.е. вписывающегося в
страницу в области с #C000), то перед этим
необходимо средствами системы (о которых
будет рассказано в соответствующем разде─
ле) организовать проверку значения нижней
границы ядра.
4.Если есть необходимость, то используя
данные из вектора системы - восстановить
стандартную конфигурацию ОЗУ с Бейсиком-
48. Для TASiS может потребоваться переход
в экранный режим 6912 и стандартную пали─
тру. Если это осуществлять через систему
(для чего есть соответствующие системные
вызовы), то делать это надо до отключения
ядра.
Соответственно, для включения системы
обратно необходимо:
1.Восстановить ядро в необходимой стра─
нице по адресу #C000 (для TASiS - это
страница 0).
2.Восстановить необходимую для Chic ко─
нфигурацию памяти (включить ОЗУ по адресу
#0000 в соответствии с вектором системы).
3.Восстановить сохраненные указатель
стека и вектор прерываний. Разрешить пре─
рывания.
4.В случае с TASiS при необходимости
восстановить прежние экранный режим и па─
литру (после выполнения пункта 3 это уже
можно делать через системные вызовы).
5.При необходимости - выход обратно в
оболочку системы (об этом ниже). В случае
выхода в оболочку в системе TASiS, прежние
системные экранный режим и палитра восста─
навливаются автоматически, а значит, п. 4
отпадает.
А теперь более конкретно.
Для 48К игрушек, не знающих о существо─
вании порта#7FFD, альтернативного экрана
и прочего, всё предельно просто: не испо─
льзуем страницу#00 (или ту страницу, что
включена штатно с адреса#C000 ) вообще.
Вместо неё достаточно подставить туда лю─
бую другую страницу или с предварительно
загруженным туда кодом игры, или со скопи─
рованным туда после подстановки. А точнее,
если блок кодов игры временно умещается в
промежуток между концом загрузчика и ад─
ресом#C000, то алгоритм таков:
1.Проверяем, не вылезает ли ядро ниже
#C000.
2.Грузим блок кодов в свободное прост─
ранство.
3.Отключаем прерывания, переназначаем
стек и вектор прерывания.
4.Включаем в #C000 свободную страницу и
переносим посредством LDIR или LDDR (или
распаковкой) блок кодов игры на нужные ад─
реса,а затем - переконфигурирование адрес─
ного пространства, графики и палитры, и
CALL game.
Если блок кодов слишком длинный и не
влезает в свободное пространство, то тогда
делаем по-другому. Средства системы позво─
ляют грузить файлы по частям в любой про─
извольной последовательности и кусками
выбранной длины.Алгоритм в случае с iS-DOS
Chic такой:
1.Проверяем, не вылезает ли ядро ниже
#C000.
2.Грузим "хвост" блока кодов, который
должен располагаться выше #C000 в свобод─
ное пространство, а затем копируем его в
страницу, которая будет включена вместо
страницы с ядром.
3.Грузим остальную часть блока кодов с
адреса загрузки до #C000.
4.Отключаем прерывания, переназначаем
стек и вектор прерывания.
5.Включаем в #C000 ту свободную страни─
цу, куда грузили "хвост" кодового файла, а
затем производим переконфигурирование ад─
ресного пространства, графики и палитры, и
CALL game.
Очень похожа методика действий и для
программ,использующих 128КБ-страницы.Точно
так же кодовые блоки в виде оверлейных
файлов предварительно загружаются в нужные
страницы. При этом надо исключить из ис─
пользования страницу с ядром, либо времен─
но сохранять его в верхней памяти.
Проиллюстрирую вышеприведённую инфор─
мацию в кодовых примерах. Как уже упоми─
налось, система вызывается через единую
точку входаRST #10. При этом номер функ─
ции содержится в регистреC, а в остальных
регистрах при необходимости передаются до─
полнительные данные. На выходе получаем
признакNC - вызов отработан успешно.В ос─
тальных регистрах по необходимости могут
располагаться возвращаемые данные. Если
на выходе признакC, то это признак ошиб─
ки,тогда в регистреA - номер ошибки. Если
при выходе в оболочку установить флагC и
в регистрA записать любой номер, то выход
в оболочку произойдет с сообщением"Ошибка
{указанный номер}". Так, например, номер
ошибки при нехватке памяти -130.
Следующий вызов нам необходим для пред─
варительной оценки среды (в данном случае
для определения - не опустилось ли ядро
ниже#C000 ):
Рестарт $g_cnfg (регистр C=#10 ).Вход─
ных параметров нет. На выходе в альтерна─
тивном регистреHL' адрес вектора конфигу─
рации ядра ОС (другими словами - указатель
на расположение системных переменных). Там
есть много полезного. Но сейчас нас инте─
ресуют данные по смещению5 - там хранится
указатель на нижнюю планку КЭШа (ниже ко─
торой начинается область пользователя).
Зная это, мы можем организовать проверку
свободного места:
freesp
LD C,$g_cnfg;или можно прямо LD C,#10
RST #10
EXX;"открываем" альтернативные
;регистры для доступа к HL'
LD BC,5+1;+1 вместо INC HL ниже
ADD HL,BC;устанавливаем указатель
;вектора системы на границу КЭШа
;INC HL ;адресуем старший байт
LD A,(HL)
CP #C0;если граница КЭШа < #C000,
;то установка флага C
LD A,130;номер сообщения об ошибке
;(нехватка памяти)
RET C;выход в оболочку с сообщением
;об ошибке в случае нехватки
...;если всё ОК, продолжаем программу
;(если необходимо)
RET;необходимо, если оформляем как
;подпрограмму
А само основное тело загрузчика блока
кодов (для простоты берём только 48К ва─
риант) и отключение системы будут выгля─
деть примерно так:
ORG 24000;начало COM-файла
CALL freesp;проверка ниж.границы ядра
RET C;если мало,выходим с ошибкой 130
;(устанавливается в подпрограмме)
CALL loadfl;грузим кодовый блок в
;область пользователя
;(разберём отдельно)
RET C;возврат в оболочку, если при
;загрузке возникла ошибка
CALL scrpal;включаем режим 6912 и
;стандартную палитру
DI;начинаем процедуру отключения
;системы
LD (buffer),SP
LD SP,...;переназначаем стек
LD A,#3B
LD I,A
IM 1
LD A,#11
LD BC,#7FFD
OUT (C),A;включаем ПЗУ-48 и страницу
;RAM #01 (для примера)
LD HL,...
LD DE,usrbuf
LD BC,...
LDIR;или LDDR. Перемещаем загруженный
;код игры на "законное" место
EI
CALL usrbuf;запуск игры
;ну а тут, если игрушка из блока кодов
;выходит обратно по простому RET,
;может располагаться процедура возврата в
;оболочку TASiS:
DI
XOR A
LD I,A
IM 2
LD A,#08
LD BC,#7FFD
OUT (C),A;включаем BAS-128,где вместо
;него давно сконфигурирована страница ОЗУ,
;альтернативную экранную страницу
;и RAM 0 по адресу #C000
LD SP,(buffer);восстанавливаем стек
EI;теперь система вновь работает
XOR A;устанавливаем флаг Z
;(для выполнения оболочкой
;сопутствующей команды)
;OR A ;сбрасываем флаг CY для
;"безошибочного" выхода
LD A,#F4;команда оболочке для
;перерисовки панели (ведь на
;экране после игры - "мусор")
RET;выход в оболочку. Графический
;режим и палитра в TASiS
;восстановятся при этом автоматически
buffer
DEFW 0
...
usrbuf
Если по каким-то причинам восстановить
стек невозможно, то вместо конечногоRET
можно воспользоваться прямым рестартом вы─
зова оболочки, предварительно проделав вы─
шеприведённые манипуляции с флагамиZ,CY и
регистромA:
Рестарт$shout (регистр C=#84 ).Выход в
оболочку с выполнением спецкоманды. Вход─
ные данные: если флагCY установлен, то в
регистре A - код ошибки (обработку ошибок
мы разбирали на примере ошибки нехватки
памяти130 ). А если флаг CY сброшен, а
Z - поднят, то происходит выход в оболочку
с выполнением спецкоманды, указанной в ре─
гистре A. В нашем случае нам достаточно
команды #F4 - перепечатка панели. В этом
случае система сама восстановит нужный ей
указатель стека. Минус тут только один -
нельзя будет запускаться из BAT-файлов.
Точнее, можно, но после выхода таким "кри─
вым" способом дальнейшее исполнение BAT-
файла просто прервётся, и вы окажетесь в
оболочке.
В TASiS есть ещё один способ выхода в
оболочку. Его следует применять в тех слу─
чаях, когда, к примеру, вы адаптировали
под систему обычную ZX-игрушку, которая не
знает никаких выходов из самой себя обрат─
но в вызвавшую её блок кодов подпрограмму,
а просто "зациклена" в своей игровой сре─
де. В этом случае просто необходимо вос─
пользоваться резидентом - возможностью вы─
зова программы пользователя, размещённой
в верхней памяти в страницеRAM #1F опре─
делённым образом, по нажатии кнопки
"RESET". Конкретно структура резидентной
страницы следующая.
Чтобы программа, помещённая в страницу
#1F, была распознана как резидент, она
должна быть оформлена специальным образом.
В соответствие с этим, страница имеет сле─
дующую структуру (далее указывается смеще─
ние от начала страницы):
#0000 - код #C3 (команда JP nnnn)
#0001-#0002 - адрес перехода для JP nnnn
(рассчитывается по формуле:
#C000+относительный адрес начала
программы в странице).
#0003-#ЗFFC - свободное место
непосредственно под программу.
#ЗFFD - контрольная сумма (КС) всей
страницы (то есть с #0000 до #ЗFFF).
#ЗFFE - всегда должен быть равен #55.
#ЗFFF - всегда должен быть равен #AA.
При создании резидента КС рассчитывает─
ся путем сложения без учёта переноса (то
есть по команде ADD) одного за другим
всех байтов страницы, а затем вычитания
полученной суммы из нуля (по команде NEG
процессора). При этом, так как в процессе
расчёта контрольной суммы она ещё не зане─
сена в байт#ЗFFD, он перед началом под─
счёта КС должен быть равен#00(!). И этот
момент обязательно должен быть учтён при
возникновении необходимости пересчета КС.
Скомпонованная таким образом страница
будет успешно распознана как резидентная,
и прошивка ПЗУ начнёт процедуру запуска
резидента, которая состоит из следующего:
1)По адресу #C000 включается резидент─
ная страница #1F.
2)По адресу #8000 в обеих картах памя─
ти (и при ROM2=0, и при ROM2=1) включается
страница RAM #02.
3)Передается управление резиденту на
адрес #С000, где должна располагаться ко─
манда резидента JP nnnn.
После передачи управления резиденту ар─
хитектура выглядит так:
1)Расположение стека (значение SP) -
не определено. Остаётся на усмотрение ре─
зидента.
2)Карта памяти помимо адреса #8000 -
#BFFF (и страницы #1F с запущенным резиде─
нтом по адресу #С000) - не определена.Так─
же остается на усмотрение резидента.
3)Состояние палитры - не определено.
Также остается на усмотрение резидента.
4)Прерывания находятся в режиме IM 2,
включены. Вектор прерывания равен #82FF
(I=#82). Таким образом, прерывания надо
либо запретить, либо, если работа с ними в
короткий период работы резидента (пока он
устанавливает основную программу) необхо─
дима, например, для установки палитры, то
надо установить в #82FF-#8300 адрес под─
программы обработчика прерываний. В рези─
денте Honey Commander там стоит указатель
на ближайший RET.
Таким образом, программист при разра─
ботке резидента не связан никаким правила─
ми и ограничениями, так как резидент не
привязан ни к какой операционной системе
и не обязан учитывать её особенности. Ка─
кова будет конфигурация компьютера после
рестарта в резидент, определяется самим
резидентом.
В нашем случае будет необходимо в пер─
вичную процедуру загрузки игры сохранить в
страницу #1F резидентную подпрограмму,
данные о значении стека, режима прерыва─
ний, конфигурации адресного пространства и
просчитать контрольную сумму. А дальше
после команды "RESET" эта подпрограмма по
сохранённым данным установит ядро на мес─
то, переназначит карту памяти. В общем -
сделает практически всё то, что описано
выше при выходе в оболочку. Остаётся доба─
вить, что стандартно резиденты в TASiS со─
храняют область ОЗУ с #C000 по #FFFF в
страницу#1C, поэтому без наличия дополни─
тельной необходимости рекомендуется в сво─
их программах при создании резидента также
использовать эту страницу.
Теперь для того, чтобы успешно написать
и запустить собственную игру под iS-DOS/
TASiS, нам осталось разобрать всего нес─
колько системных вызовов.
Открытие и загрузка файлов в iS-DOS
В приведённом выше примере загрузчика
мы временно обошли стороной вопросы собст─
венно загрузки данных с диска и переключе─
ния графики, оставив их напоследок и обо─
значив их вызовами подпрограмм типа"CALL
loadfl" и"CALL scrpal" соответственно.Те─
перь же остановимся на них конкретно. Для
начала о загрузке с диска.
Существует множество способов и возмож─
ностей оперирования файлами, каталогами,
подкаталогами и фрагментами файлов на лю─
бых логических дисковых устройствах (вне
зависимости от физических носителей, так
как вся основная работа идёт через соотве─
тствующие драйвера),включая поиск,сортиро─
вку по шаблону, создание,удаление,переиме─
нование, добавление и "отрезание" частей,
последовательный и произвольный доступ и
многое другое. Но описание всех возможнос─
тей - это отдельный труд большого объёма.
Желающие, заинтересовавшиеся системой,смо─
гут самостоятельно изучить все рестарты,
благо система хорошо документирована и её
описание доступно в сети. А приведённые
в этой статье примеры позволят понять ос─
новные принципы и дальше уже уверенно рас─
ширять свои навыки самостоятельно. Поэтому
далее будет разбираться загрузка файлов по
упрощённой схеме.
А именно - предполагается, что все дей─
ствия происходят в текущем подкаталоге,
откуда был загружен управляющий кодовый
COM-файл нашей исполняемой программы (а по
умолчанию рестарты работают именно в таком
подкаталоге), нам заранее известны имя и
размер файла, а также мы знаем, куда его
или его части грузить.
Загрузка файла (как и любые иные опера─
ции с файлами) состоит в iS-DOS из двух
этапов: поиск/открытие файла и собственно
операция обмена данными с ним. Соответст─
венно этим этапам приводим системные вызо─
вы:
Рестарт$fopen (регистр C=#25 ).Поиск и
открытие файла по имени и расширению. На
входе вHL - указатель на адрес 11-байто─
вого (8 байт имени и 3 байта расширения)
описателя файла. Вообще стандартное опи─
сание файла составляет 32 байта (их описа─
ние ниже), но для входа используются толь─
ко 11 байт непосредственно имени.Если файл
найден, то он открывается. Если найденный
файл - подкаталог,то происходит его откры─
тие и переход в него. На выходе: если всё
прошло без ошибок (флагCY сброшен), то в
регистреA:
A=#00 - открыт файл;
A=#20 - открыт каталог.
В альтернативном регистреHL' - указа─
тель на адрес системного 32-байтового опи─
сателя файла, куда по итогам работы реста─
рта помещены данные текущего открывшегося
файла.
Возможные ошибки (флагCY установлен):
A=81 - файл не найден;
A=85 - ломаный блок описателя сегментов
(текущего или искомого каталога);
A=86 - ломаный каталог.
Ошибки 85 и 86 могут возникнуть в слу─
чае порчи файловой системы на устройстве в
результате каких-то иных, внешних для про─
граммы обстоятельств и в обычных условиях
возникать не должны.
Значения 32-байтового описателя файла
следующие:
+0 (8 байт) - имя.
+8 (3 байта) - расширение.
+11 (1 байт) - флаговый регистр состояния
файла. Биты (0/1):
0 -удалён/существует
2 -защищён от чтения (1)
3 -защищён от записи (1)
4 -видимый/скрытый файл
5 -файл/каталог (корневой файл)
6 -сегментированный/непрерывный
7 -защищён от удаления (1)
+12 (2 байта)- адрес загрузки по умолчанию
+14 (3 байта) - длина в байтах.
+17 (2 байта) - номер блока описателя сег─
мента (для непрерывного файла - номер
нулевого блока файла).
+19 (1 байт) - байт "Special": использует─
ся, как правило, в системных файлах для
начальной загрузки или реконфигурирова─
ния.Как правило,биты0..2 (диапазон зна─
чений 0..7) содержат номер уровня систе─
мы в SYS-файлах при подгрузке/замене но─
вых уровней к ядру.В TASiS бит3 (значе─
ние байта=8) - признак вывода не собст─
венного,а внутреннего 38-байтового имени
на файловую панель оболочки.
+20 (2 байта) - в обычных файлах не испо─
льзуется.В системном файле ядра ОС (опи─
сательis_dos.sys )содержит используемый
загрузчиком адрес установки стека SP.
+22 (1 байт) - в обычных файлах не исполь─
зуется. В системном файле ядра ОС (опи─
сательis_dos.sys )содержит значение ве─
ктора прерывания в системе, передаваемое
в регистр I загрузчиком при первоначаль─
ной установке системы (#3B в Classic,
#06 в Chic, #00 в TASiS).
+23 (3 байта) - резерв.
+26 (2 байта) - контрольная сумма файла.
+28 (2 байта) - время.
+30 (2 байта) - дата.
Рестарт$rpart (регистр C=#29 ). Чтение
файла или его фрагмента.
Входные данные:
DE - сколько байт читаем,
AHL - смещение от начала файла в байтах,
откуда начинаем чтение,
IX - адрес в ОЗУ, куда читаем.
Возможные ошибки на выходе (флагCY=1):
A=100 - попытка чтения за концом файла
(т.е. AHL+DE больше длины файла, кото─
рую, как описано выше, можно достать из
описателя в HL'+14).
A=106 - файл не открыт.
A=170 - чтение 0 байт (DE=0).
A=171 - файл защищён от чтения.
A=7 - ошибка чтения/записи (как правило,
физическая. Возвращается драйвером низ─
кого уровня).
Отдельно напоминаем, что данный рестарт
не проверяет,в какую системную область ОЗУ
мы грузим файл. И определять, не затрёт ли
файл ядро системы,должен программист. Если
же вы уверены в своих действиях и в том,
что загружаемый файл ничего лишнего не за─
трёт, то процедура загрузки файла "с нуля"
в ОЗУ (предположим, что его длина не пре─
вышает 65535 байт, т. е. занимает не более
16 бит в описателе) будет выглядеть так:
loadfl
LD HL,filename;указатель на 11-байто─
;вый шаблон имени файла
LD C,$fopen;или можно прямо LD C,#25
RST #10
RET C;если ошибка, выходим
EXX;получаем указатель на описатель
PUSH HL;открытого файла
POP IX;переносим указатель для
;удобства в индексный регистр
;при желании можем организовать проверку
;контрольной суммы файла на случай возмож─
;ной подмены (контрольную сумму можно
;узнать посредством системных утилит ОС)
LD A,(filename+26);берём 1-й байт
;шаблона контрольной суммы
CP (IX+26);сравниваем с аналогичным
;байтом из описателя
;открытого файла
JP NZ,error;переходим на процедуру
;выхода по ошибке (которая
;установит нужные флаги и
;значения регистров)
LD A,(filename+27)
CP (IX+27)
JP NZ,error;аналогично со вторым
;байтом контрольной суммы
;подготавливаем данные для загрузки:
LD E,(IX+14);достаем из описателя
;длину файла (меньшую,
;чем 65536 байт)
LD D,(IX+15);если мы заранее знаем
;длину загружаемого файла,
;то просто делаем LD DE,filesize
XOR A;записываем нули в смещение от
;начала файла. Если мы
LD HL,0;грузим часть файла (например,
;для переброски в страницу ОЗУ),
;то записываем соответствующие
;значения в AHL (смещение)
;и DE (длина).
LD IX,ramadr;куда грузим. Если
;планируется вызывать
;загрузку по частям
;несколько раз, значение IX
;надо временно сохранять.
LD C,$rpart;или можно прямо LD C,#29
RST #10
RET;файл загружен, возвращаемся в
;исходную программу из подпрограммы
;загрузки. На выходе должна стоять
;проверка флага CY на предмет ошибки.
;Если же в подпрограмме планируется
;продолжение,например для загрузки другого
;фрагмента файла, то тут надо поставить
;RET C - выход по ошибке
error
;обработчик выхода по ошибке контр. суммы
SCF;устанавливаем признак ошибки -
;флаг CY=1
LD A,81;укажем, что "файл не найден"
RET
filename
...;11 (минимум) или больше (до 32)
;байт шаблона имени файла
Таким образом, зная структуру описателя
файла и два выше разобранных системных ре─
старта, мы можем загружать целиком или по
частям любой файл. В конце раздела только
для примера приведу аналогичный рестарт на
запись в уже существующий файл определен─
ной длины:
Рестарт$wpart (регистр C=#2A) - запись
DE байт в текущий открытый файл со смеще─
ния AHL байт от начала файла с адреса в
IX. Его, как и иные возможности по записи
и созданию файлов, предлагается изучить
самостоятельно.
Other articles: