Info Guide
#11
05 июля 2015 |
|
Системки - iS-DOS/TASiS: как писать игры под iS-DOS/TASiS (часть 2).
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. Его, как и иные возможности по записи и созданию файлов, предлагается изучить самостоятельно.
Другие статьи номера:
Похожие статьи:
В этот день... 21 ноября