НЕСТАНДАРТНЫЕ МАГНИТОФОННЫЕ ЗАГРУЗЧИКИ.
Пишу вот по какому поводу. Все началось с того, что я слишком внимательно читал 6-ой номер ZX-ревю за 1994 год и в разделе "Читатель-читателю" наткнулся на статью "Нестандартные загрузчики". Появилась идея создать универсальную процедуру загрузки программ с магнитофона эффективным и оригинальным образом.
ИФК: Большинство читателей, которые пользуются дисководами, возможно, сталкивались с программами, которые в процессе загрузки еще и выводят текст на экран, проигрывают музыку и т.п. К таким можно отнести BATTLE COMMAND и INSULT MEGADEMO. Так вот, Сергей предлагает дать возможность использовать такие эффекты при загрузке с магнитофона. Но дадим слово автору.
При использовании метода, описанного ниже, можно во время загрузки блока кодов выполнять любую программу пользователя (естественно, она будет выполняться медленнее). Например, загружая игру, можно одновременно читать ее описание, которое постепенно будет выводиться на экран; можно грузить части программы в любое место памяти, тут же их декодировать и запускать (конечно, они должны вызывать процедуру загрузки). И это в одном неразрывном кодовом блоке.
Процедура загрузки файлов с магнитофона с параллельной работой программы пользователя Written by Kolotow Sergey Copyright SerzhSoft from Shadrinsk
10 |
ORG |
30000 |
|
;Считывание первого встреченного кодового файла с заголовком |
20 FILE |
LD |
IX,HEADER |
адрес загрузки заголовка файла |
30 |
LD |
DE,17 ; |
длина заголовка |
40 |
XOR |
A ; |
загружаться будет заголовок |
50 |
SCF |
|
выполнять чтение, а не проверку файла |
60 |
CALL |
1366 ; |
вызов процедуры чтения в ПЗУ |
70 |
JR |
NC,FILE |
если возникла ошибка, то повторить |
80 |
LD |
A,(HEADER) |
взяли первый байт считанного заголовка |
90 |
CP |
3 |
это блок кодов? |
100 |
JR |
NZ,FILE |
если нет, повторить загрузку |
110 |
LD |
IX, (START) |
адрес загрузки блока кодов |
120 |
LD |
DE,(LENGTH) |
длина блока кодов |
130 |
LD |
A,255 ; |
загружаем файл |
140 |
SCF |
|
загрузка, а не проверка |
;Процедура загрузки, аналогичная CALL 2050
150 LOADER CALL PROC ;вызвать процедуру загрузки
160 |
CALL |
#053F |
;восстановить бордюр и, если |
; клавиша |
BREAK, |
то выдать |
сообщение об этом |
170 |
RET |
C |
; выход, если нет ошибки |
180 |
RST |
8 |
;иначе - печать сообщения |
190 |
DEFB |
26 |
; "Tape loading error" |
Главная процедура загрузки
На входе: IX - адрес начала загрузки файла DE - длина загружаемого файла A - флаг файл/заголовок: #00 - заголовок #FF - файл флаг C=1 - если загрузка (команда LOAD) флаг C=0 - если проверка (команда VERIFY)
200 |
PROC DI |
|
|
;запретить прерывания |
210 |
DEC |
DE |
|
;уменьшить длину файла на 1 |
220 |
LD |
(CNTLEN) |
, DE |
;и запомнить полученное значение |
230 |
LD |
DE,1 |
|
;будем читать один байт |
240 |
INC |
D |
|
;подготовка регистров, как и в ПЗУ |
250 |
EX |
AF,AF' |
|
|
260 |
DEC |
D |
|
|
270 |
CALL |
#0562 |
|
;загрузка байта процедурой ПЗУ |
280 |
LD |
A, 127 |
|
;проверяем нажата ли клавиша SPACE |
290 |
IN |
A, (254) |
|
|
300 |
RRA |
|
|
|
310 |
RET |
NC |
|
; если да, то аварийный выход |
320 |
LD |
A, E |
|
;первый байт считался нормально ? |
330 |
CP |
1 |
|
|
340 |
RET |
NC |
|
; если нет, то выход с ошибкой |
350 |
EXX |
|
|
;переключили набор регистров |
360 |
PUSH |
HL |
|
;сохранили HL' для нормального завершения |
370 |
LD |
(MEM_SP) |
, SP |
;запомнили указатель стека |
380 |
CALL |
MYPROC |
|
;вызов процедуры пользователя |
390 |
LPLOAD CALL |
LOAD1B |
|
;продолжать загрузку до конца файла по |
400
JR
LPLOAD
;одному байту
;Процедура считывания одного байта
410 LOAD1B PUSH AF
;сохранили AF на стеке
420 |
EXX |
|
; поменяли набор регистров |
430 |
LD |
E, 1 |
;загружать один байт |
440 |
CALL |
#05A9 |
; процедура загрузки из ПЗУ |
450 |
CP |
H |
; ошибка? |
460 |
JR |
NZ,QUIT |
;если да, то закончить загрузку |
470 |
PUSH |
HL |
;сохранить HL |
480 |
LD |
HL,(CNTLEN) |
;уменьшить счетчик длины файла |
490 |
DEC |
HL |
|
500 |
LD |
(CNTLEN),HL |
|
510 |
LD |
A, H |
;проверить не достиг ли он 0 |
520 |
OR |
L |
|
530 |
POP |
HL |
;восстановить HL |
540 |
EXX |
|
;сменить набор регистров |
550 |
JR |
Z,QUIT |
; выход, если счетчик обнулился |
560 |
POP |
AF |
;восстановить AF |
570 |
RET |
|
; возврат из подпрограммы |
;Закончить загрузку и выйти
SP,(MEM_SP) ;восстановили указатель стека HL ;восстановили HL'
;поменяли регистры A,H ;установка флагов
1 ;правильности/неправильности считывания
; возврат в точку вызова Процедура пользователя, "разбавленная" командами CALL LOAD1B -одного байта.
В распоряжении пользователя находятся регистры AF, BC, DE, HL, (IX-1) указывает на предыдущий загруженный байт L' - текущий загруженный байт
IX - адрес, куда запишется текущий байт командой CALL LOAD1B
При выходе из этой процедуры загрузка файла продолжится как ни в чем не бывало.
;сейчас никакой процедуры пользователя нет 0 ; счетчик длины загружаемого файла
0 ; ячейка сохранения указателя стека
17 ;буфер для загрузки заголовка
HEADER+13 ;адрес загрузки файла в заголовке
64 0 MYPROC RET
650 CNTLEN DEFW
660 MEM_SP DEFW
67 0 HEADER DEFS
680 START EQU
690 LENGTH EQU
HEADER+11 ;длина файла в заголовке
Приводим дами памяти приведенной программы:
7530 |
DD |
21 |
A9 |
75 |
11 |
11 |
00 |
AF |
92 |
7538 |
37 |
CD |
56 |
05 |
30 |
F2 |
3A |
A9 |
11 |
7540 |
75 |
FE |
03 |
20 |
EB |
DD |
2A |
B6 |
F3 |
7548 |
75 |
ED |
5B |
B4 |
75 |
3E |
FF |
37 |
17 |
7550 |
CD |
59 |
75 |
CD |
3F |
05 |
D8 |
CF |
18 |
7558 |
1A |
F3 |
1B |
ED |
53 |
A5 |
75 |
11 |
60 |
7560 |
01 |
00 |
14 |
08 |
15 |
CD |
62 |
05 |
3B |
7568 |
3E |
7F |
DB |
FE |
1F |
D0 |
7B |
FE |
DB |
7570 |
01 |
D0 |
D9 |
E5 |
ED |
73 |
A7 |
75 |
F0 |
7578 |
CD |
A4 |
75 |
CD |
80 |
75 |
18 |
FB |
A8 |
7580 |
F5 |
D9 |
1E |
01 |
CD |
A9 |
05 |
BC |
19 |
7588 |
20 |
10 |
E5 |
2A |
A5 |
75 |
2B |
22 |
A3 |
7590 |
A5 |
75 |
7C |
B5 |
E1 |
D9 |
28 |
02 |
34 |
7598 |
F1 |
C9 |
ED |
7B |
A7 |
75 |
E1 |
D9 |
05 |
7 5A0 |
7C |
FE |
01 |
C9 |
C9 |
00 |
00 |
00 |
22 |
7 5A8 |
00 |
00 |
00 |
00 |
00 |
00 |
00 |
00 |
1D |
75B0 |
00 |
00 |
00 |
00 |
00 |
00 |
00 |
00 |
25 |
75B8 |
00 |
00 |
00 |
00 |
00 |
00 |
00 |
00 |
2D |
Описанная выше процедура имеет несколько точек входа:
1. Метка FILE - считывание первого попавшегося блока кодов с заголовком. При ошибке загрузки или нажатии BREAK (SPACE) выдаются соответствующие сообщения.
Вызывает процедуру ПЗУ по адресу 1366 (#0556) и затем переходит на метку LOADER.
2. Метка LOADER - загрузка, аналогичная CALL 2050 с выдачей сообщений об ошибке или нажатии BREAK.
Вход: IX - адрес загрузки блока кодов DE - длина блока кодов A=#00 - заголовок A=#FF - файл флаг ^включен - загрузка
выключен - проверка
3. Метка PROC - головная процедура загрузки. Ее вызывают все вышестоящие части программы. Входные данные аналогичны предыдущему пункту. Отличие состоит в том, что сообщения об ошибках не выдаются, хотя флаги и устанавливаются.
На выходе: флаг C включен - считывание данных произошло без ошибок; флаг C выключен - произошла ошибка. Во время запуска программы из любой точки входа выполняется следующее:
1. Считывается первый байт.
2. Вызывается процедура пользователя.
3. Если произошел возврат из пользовательской процедуры, то продолжается "обыкновенная" загрузка блока кодов до конца.
4. При появлении ошибки или нажатии BREAK производится экстренный выход из программы.
Процедура пользователя должна регулярно вызывать CALL LOAD1B. Ограничения, накладываемые на процедуру пользователя:
1. В распоряжении пользователя регистры:
AF, BC, DE, HL - при выполнении CALL LOAD1B они не будут портится;
B', E' - испортятся после CALL LOAD1B;
H' - контроль правильности загрузки - лучше не трогать;
L' - загруженный, но еще не записанный в память байт. Не менять!
D' - перед CALL LOAD1B всегда равен 0. Не менять!
C' - работа с бордюром. Не менять!
2. (IX-1) - указывает на предыдущий загруженный байт;
IX - указывает, куда будет записан только что загруженный байт;
3. IY - использовать, конечно, возможно, но лучше с BASIC'ом не конфликтовать;
4. SP - вообще лучше не трогать - плохо будет;
5. Во время работы программы, прерывания запрещены и разрешать их настоятельно не рекомендуется;
6. Вся процедура должна "разбавляться" командами CALL LOAD1B - чтение одного байта.
Для примера дальше приводится листинг процедуры пользователя. Для ее вызова необходимо заменить строку MYPROC RET на MYPROC JP PRINT
700 PRINT CALL LOAD1B
710 LD HL,(TXTADR) ;адрес следующего символа
72 0 LD A,(HL) ;взяли символ
730 INC HL ; адрес следующего символа
740 |
|
CP |
255 |
; |
конец текста? |
750 |
|
RET |
Z |
; |
выход, если да |
760 |
|
LD |
DE,PRINT |
; |
заносим на стек адрес процедуры PRINT, |
770 |
|
PUSH |
DE |
; |
чтобы возвратиться по RET |
780 |
|
CP |
6 |
; |
символ табуляции? |
790 |
|
JR |
Z,SPCTAB |
; |
переход |
800 |
|
CP |
22 |
; |
смена позиции курсора? |
810 |
|
JR |
Z,CH_POS |
|
|
|
820 |
|
CP |
16 |
; |
смена атрибута печати? |
830 |
|
JR |
Z,CH_ATR |
|
|
|
840 |
|
CP |
30 |
; |
код паузы? |
850 |
|
JR |
Z,PAUSE |
|
|
|
860 |
|
CP |
31 |
; |
код вызова пользовательской процедуры? |
870 |
|
JR |
Z,USERPR |
|
|
|
880 |
|
LD |
(TXTADR), |
HL ; |
сохранили адрес текста |
890 |
|
JR |
NC,PUTCHR |
; |
печать, если код символа больше 31 |
900 |
|
LD |
HL,(C_COOR) |
считали координаты |
910 |
|
CP |
13 |
; |
если перевод строки, |
92 0 |
|
JR |
Z,ENTER |
; |
то |
выполнить |
930 |
|
CP |
8 |
; |
если шаг назад, |
940 |
|
JR |
Z,BACK |
; |
то |
выполнить |
950 |
|
RET |
|
|
|
|
960 |
SPCTAB LD |
C, (HL) |
; |
процедура повторения печати одинаковых |
; символов |
|
|
|
|
970 |
|
INC |
HL |
; |
в C - число символов |
980 |
|
LD |
A, (HL) |
; |
в A - код символа |
990 |
|
PUSH |
HL |
|
|
|
1000 |
LP1 |
PUSH |
AF |
|
|
|
1010 |
|
CALL |
PUTCHR |
; |
печать символа |
1020 |
|
POP |
AF |
|
|
|
1030 |
|
DEC |
C |
; |
все распечатали? |
1040 |
|
JR |
NZ,LP 1 |
; |
повторить, если не кончили |
1050 |
|
POP |
HL |
|
|
|
1060 |
|
JR |
GO1 |
|
переход на завершение |
1070 |
PAUSE LD |
C, (HL) |
|
процедура паузы |
1080 |
|
INC |
HL |
|
в BC - число считываемых байт |
1090 |
|
LD |
B, (HL) |
|
|
|
1100 |
|
INC |
HL |
|
|
|
1110 |
|
LD |
(TXTADR), |
HL |
|
|
1120 |
LPPAUS CALL |
LOAD1B |
|
|
|
1130 |
|
DEC |
BC |
|
|
|
1140 |
|
LD |
A, B |
|
|
|
1150 |
|
OR |
C |
|
|
|
1160 |
|
JR |
NZ,LPPAUS |
|
|
|
1170 |
|
RET |
|
|
|
|
1180 |
USERPR LD |
E,(HL) |
|
процедура вызова пользовательской |
1190 |
|
INC |
HL |
|
функции |
1200 |
|
LD |
D,(HL) |
|
в DE - адрес процедуры |
1210 |
|
INC |
HL |
|
|
|
1220 |
|
LD |
(TXTADR), |
HL |
|
|
1230 |
|
PUSH |
DE |
; |
занесли на стек адрес процедуры |
1240 |
|
RET |
; выход на |
процедуру |
1250 |
CH_ |
POS LD |
A, (HL) |
|
процедура смена позиции курсора |
1260 |
|
AND |
24 |
|
- |
|
1270 |
|
OR |
64 |
|
|
|
1280 |
|
LD |
D,A |
|
|
|
1290 |
|
LD |
A, (HL) |
|
|
Расчет адреса верхней |
1300 |
|
AND |
7 |
|
|
линии знакоместа в новой |
1310 |
|
RRCA |
|
|
|
позиции вывода |
1320 |
|
RRCA |
|
|
|
|
1330 |
|
RRCA |
|
|
|
|
1340 |
|
INC |
HL |
|
|
|
1350 |
|
OR |
(HL) |
|
|
|
1360 |
|
LD |
E,A |
|
- |
|
(C_COOR),DE HL
(TXTADR),HL
; смена атрибута печати A ;занести атрибут в переменную
A, (HL) (ATTR),
GO1
A, L
224
A,32
L,A
NC,GO3
GO2
A, L
L
A
NZ,GO3 A, H
8
H,A
;процедура возврата каретки
; процедура возврата курсора на одну ; позицию назад
NC,GO3
GO4
L,A
H, 0
HL, HL
HL, HL
HL, HL
DE,(CHARS)
HL, DE
DE,(C_COOR)
DE
B, 8
LOAD1B A, (HL) (DE),A HL D
LP2
LOAD1B D
A, D
3 88 D,A
A,(ATTR) (DE),A HL
L
NZ,GO3 A, H A,8 H,A
88
C, GO3 H, 64
(C_COOR),HL
TEXT ; адрес следующего символа
#4000 ;адрес вывода на экран
7 ; атрибуты печати
; процедура печати символа
2010 |
CHARS DEFW |
15360 ;адрес |
2020 |
USER_1 LD |
C, (HL) ;пример |
2030 |
INC |
HL |
2040 |
LD |
B,(HL) |
2050 |
INC |
HL |
2060 |
LD |
(TXTADR) , HL |
2070 |
LPUSR1 CALL |
LOAD1B |
2080 |
LD |
A, R |
2090 |
OUT |
(254),A |
2100 |
DEC |
BC |
2110 |
LD |
A, B |
2120 |
OR |
C |
2130 |
JR |
NZ,LPUSR1 |
2140 |
RET |
|
2150 |
TEXTDEFB |
30 |
2160 |
DEFW |
2048 |
2170 |
DEFB |
22,0, 0,16, 7 |
2180 |
DEFB |
6,32,"-" |
2190 |
DEFM |
"HELLO, ANYBODY!" |
2200 |
DEFB |
13,6,32,"=" |
2210 |
DEFB |
31 |
2220 |
DEFW |
USER_1,500 |
2230 |
DEFB |
13,16,#38 |
2240 |
DEFM |
"IT'S ME - SERZHSOFT" |
2250 |
DEFB |
31 |
2260 |
DEFW |
USER_1,4 00 |
2270 |
DEFB |
8, 8,8,8 |
2280 |
DEFM |
"COMPANY" |
2290 |
DEFB |
255 |
Итак, в тексте могут встречаться следующие управляющие коды:
#FF |
конец текста |
#10 #NN |
новый атрибут печати #NN |
#1E #LOW #HIGH |
считать (LOW+256*HIGH) байт |
#1F #LOW #HIGH |
вызвать процедуру пользователя по адресу(LOW+256*HIGH) |
#06 #NN #MM |
печать #NN раз символ с кодом #MM |
#16 #YY #XX |
переход к координатам печати #XX, #YY |
#0D |
возврат каретки + перевод строки |
#08 |
возврат на одну позицию назад |
Дальше идет дамп процедуры вместе с процедурой загрузки:
7530 |
DD |
21 |
AB |
75 |
11 |
11 |
00 |
AF |
94 |
7538 |
37 |
CD |
56 |
05 |
30 |
F2 |
3A |
AB |
13 |
7540 |
75 |
FE |
03 |
20 |
EB |
DD |
2A |
B8 |
F5 |
7548 |
75 |
ED |
5B |
B6 |
75 |
3E |
FF |
37 |
19 |
7550 |
CD |
59 |
75 |
CD |
3F |
05 |
D8 |
CF |
18 |
7558 |
1A |
F3 |
1B |
ED |
53 |
A7 |
75 |
11 |
62 |
7560 |
01 |
00 |
14 |
08 |
15 |
CD |
62 |
05 |
3B |
7568 |
3E |
7F |
DB |
FE |
1F |
D0 |
7B |
FE |
DB |
7570 |
01 |
D0 |
D9 |
E5 |
ED |
73 |
A9 |
75 |
F2 |
7578 |
CD |
A4 |
75 |
CD |
80 |
75 |
18 |
FB |
A8 |
7580 |
F5 |
D9 |
1E |
01 |
CD |
A9 |
05 |
BC |
19 |
7588 |
20 |
10 |
E5 |
2A |
A7 |
75 |
2B |
22 |
A5 |
7590 |
A7 |
75 |
7C |
B5 |
E1 |
D9 |
28 |
02 |
36 |
7598 |
F1 |
C9 |
ED |
7B |
A9 |
75 |
E1 |
D9 |
07 |
7 5A0 |
7C |
FE |
01 |
C9 |
C3 |
BC |
75 |
00 |
4D |
7 5A8 |
00 |
00 |
00 |
00 |
00 |
00 |
00 |
00 |
1D |
75B0 |
00 |
00 |
00 |
00 |
00 |
00 |
00 |
00 |
25 |
75B8 |
00 |
00 |
00 |
00 |
CD |
80 |
75 |
2A |
19 |
75C0 |
8D |
76 |
7E |
23 |
FE |
FF |
C8 |
11 |
AF |
75C8 |
BC |
75 |
D5 |
FE |
06 |
28 |
21 |
FE |
8E |
75D0 |
16 |
28 |
45 |
FE |
10 |
28 |
59 |
FE |
55 |
75D8 |
1E |
28 |
24 |
FE |
1F |
28 |
30 |
22 |
4E |
75E0 |
8D |
76 |
30 |
6B |
2A |
8F |
76 |
FE |
20 |
75E8 |
0D |
28 |
4B |
FE |
08 |
28 |
51 |
C9 |
25 |
75F0 |
4E |
23 |
7E |
E5 |
F5 |
CD |
4F |
76 |
C0 |
75F8 |
F1 |
0D |
20 |
F8 |
E1 |
18 |
2C |
4E |
F 6 |
7600 |
23 |
46 |
23 |
22 |
8D |
76 |
CD |
80 |
74 |
7608 |
75 |
0B |
78 |
B1 |
20 |
F8 |
C9 |
5E |
66 |
7610 |
23 |
56 |
23 |
22 |
8D |
76 |
D5 |
C9 |
E5 |
7618 |
7E |
E6 |
18 |
F6 |
40 |
57 |
7E |
E 6 |
FB |
7620 |
07 |
0F |
0F |
0F |
23 |
B6 |
5F |
ED |
EF |
7628 |
53 |
8F |
76 |
23 |
22 |
8D |
76 |
C9 |
07 |
7630 |
7E |
32 |
91 |
76 |
18 |
F5 |
7D |
E 6 |
CD |
7638 |
E0 |
C6 |
20 |
6F |
30 |
4B |
18 |
3F |
B5 |
7640 |
7D |
2D |
B7 |
20 |
44 |
7C |
D6 |
08 |
D5 |
7648 |
67 |
FE |
40 |
30 |
3C |
18 |
38 |
6F |
8E |
7650 |
26 |
00 |
29 |
29 |
29 |
ED |
5B |
92 |
41 |
7658 |
76 |
19 |
ED |
5B |
8F |
76 |
D5 |
06 |
85 |
7660 |
08 |
CD |
80 |
75 |
7E |
12 |
23 |
14 |
67 |
7668 |
10 |
FA |
CD |
80 |
75 |
15 |
7A |
0F |
48 |
7670 |
0F |
0F |
E6 |
03 |
F6 |
58 |
57 |
3A |
CC |
7678 |
91 |
76 |
12 |
E1 |
2C |
20 |
0A |
7C |
BA |
7680 |
C6 |
08 |
67 |
FE |
58 |
38 |
02 |
26 |
E1 |
7688 |
40 |
22 |
8F |
76 |
C9 |
A8 |
76 |
00 |
4C |
7690 |
40 |
07 |
00 |
3C |
4E |
23 |
46 |
23 |
63 |
7698 |
22 |
8D |
76 |
CD |
80 |
75 |
ED |
5F |
41 |
7 6A0 |
D3 |
FE |
0B |
78 |
B1 |
20 |
F4 |
C9 |
F8 |
7 6A8 |
1E |
00 |
08 |
16 |
00 |
00 |
10 |
07 |
71 |
76B0 |
06 |
20 |
2D |
48 |
45 |
4C |
4C |
4F |
ED |
76B8 |
2C |
20 |
41 |
4E |
59 |
42 |
4F |
44 |
37 |
7 6C0 |
59 |
21 |
0D |
06 |
20 |
3D |
1F |
94 |
D3 |
7 6C8 |
76 |
F4 |
01 |
0D |
10 |
38 |
49 |
54 |
9B |
76D0 |
27 |
53 |
20 |
4D |
45 |
20 |
2D |
20 |
DF |
76D8 |
53 |
45 |
52 |
5A |
48 |
53 |
4F |
46 |
C2 |
76E0 |
54 |
1F |
94 |
76 |
90 |
01 |
08 |
08 |
74 |
76E8 |
08 |
08 |
43 |
4F |
4D |
50 |
41 |
4E |
2C |
76F0 |
59 |
FF |
00 |
00 |
00 |
00 |
00 |
00 |
BE |
Теперь можно неограниченно расширять процедуру, но лучше загружать ее в меньшие адреса, т.к. там программы работают быстрее, хотя не на всех компьютерах. Также, можно применять чаще процедуру загрузки одного байта, если происходит сбой, и программа теряет считываемый байт. Реализуйте возможности, которые нужны, но не используйте процедуры, использующие прерывания.