ГЛАВА 2
СПОСОБ ЗАПИСИ ПРОГРАММ НА ЛЕНТЕ
Помните, что практически каждая программа имеет хотя бы процедуру-
загрузчик, написанную на BASIC'e. а если её не имеет, то вообще не
защищена.
Начнем со способа записи программ на ленте - припомните, что видно
и слышно во время считывания какой-либо программы. В течении 1-2 секунд
на экране видны широкие красно-синие полосы, а также слышен длительный
звук. Это так называемый пилот, который позволяет компьютеру
синхронизироваться с сигналом с ленты, который он будет считывать. Потом,
на момент, появляются тонкие мерцающие желто-фиолетовые полоски,
свидетельствующие о том, что компьютер считывает в память информацию.
Её 17 байтов - это так называемый заголовок. Появляется надпись «BYTES:»,
«PROGRAM:». «CHARACTER ARRAY:» или «NUMBER ARRAY:», а потом,
после минутного перерыва, начинается второй пилот (более короткий), после
него считывается собственно программа. Займемся теперь заголовками, так
как в них содержатся важнейшие данные о считываемых программах.
Заголовок - как мы уже вспоминаем - содержит 17 байт. Пронумеруем
их от 0 до 16 (см. рис.1). Нулевой байт обозначает тип блока. Он равен 0,
если это программа на BASIC'e; 3 - если это блок машинного кода (записан-
ный с помощью SAVE"..."CODE или SAVE"..."SCREENS, которые обознача-
ют точно то же, что и SAVE"..."CODE 16384,6912). Если же этот заголовок
предшествует набору, являющемуся массивом переменных BASIC'a
(записанный с помощью SAVE"..."DATA...), то он равен 1 - для числовых
массивов или 2 - для символьных.
Следующие 10 байтов — это имя считываемого блока, или текст,
появившийся после загрузки заголовка за надписью «PROGRAM:», «BYTES:»
или другой.
В зависмости от типа считываемого блока байты с 13 по 16 интерп-
ретируются по-разному. Начнём от заголовка программ, написанных на
BASIu'e. Байты 13 и 14 содержат номер строки старта программы, если она
была записана с помощью SAVE"..."LINE NR. Если программа была написана
без опции LINE и после считывания не стартует автоматически, то значение
этого числа больше 32767. Одним из способов нейтрализации защиты само-
стартующих программ на BASIC'e является замена этих двух байтов в заго-
ловке на число большее 32767.
Байты 15 и 16 содержат число, определяющее длину самой программы
на BASIC'e (так как SAVfc"..." или SAVE"..."LINE записывает программу вме-
сте со всеми переменными, т.е. содержимое памяти от байта, указываемого
системной переменной PROG, до байта, определенного переменной
E-LINE). Если от всей длины блока (байты 11 и 12) отнимем это число, то уз-
наем. сколько байтов в этом блоке занимают переменные BASiC'a. Это - все,
если речь идет о заголовках программ на BASIC'e. В заголовках машинного
кода («BYTES») байты 15 и 16 не используются, зато 13 и 14 составляют двух-
байтовое число, определяющее, по какому адресу надо считать следующий
за заголовком блок.
В заголовках массивов из этих четырех байтов используется только 14-й
байт, который представляет имя считываемого массива. Он записан так же,
как и имена всех переменных Basic'a(b области от VARS до E-LINE), т.е. три
самых старших бита обозначают тип переменной (здесь это числовой или
знаковый массив), а 5 младших битов - имя этой переменной.
0 Тип блока: 0 - программа
---1 . NUMBER ARRAY(чиcлoвoй массив)
1 2 - CHARACTER ARRAY(cимвoльный массив)
2 3 - BYTEStaaHHbie)
3 -
4
5 Имя
6
7
8
9
10
11 Длина Сколько байт необходимо будет считать
12 с ленты
13 Старт BYTES : адрес загрузки блока
14 PROGRAM : номер строки старта программы
ARRAY : байт 14 - имя переменной
13 - не имеет значения
15 Программа PROGRAM: длина самого BASiC'a
16 (без переменных)
BYTES, ARRAY : не используется
Рис. 3 Структура заголовка
На листинге 1 представлена программа, реализующая процедуру, дела-
ющую возможным чтение заголовков программ, записанных на ленте. После
точного ввода программы (внимательно проверьте количество пробелов в
строках с инструкциями DATA) запустите её с помощью RUN и подождите
минуту. На экране появится информация об адресах начала и конца процеду-
ры. а также о ее длине, которая должна составлять 284 байта. Если это не так
проверьте: все ли строки программы введены без ошибок. Если все данные
были верны, то на Вашей ленте окажется процедура «ЧТЕЦ», благодаря кото-
рой Вы сможете прочесть каждый заголовок. Просле записи её на ленту, её
можно удалить из памяти.
Процедура «ЧТЕЦ» будет работать правильно, независимо от того, по
какому адресу она будет загружена. Вы можете считать её с помощью:
LOAD "CZYTACZ" CODE адрес
а затем запустить (даже многократно) с помощью
RANDOMIZE USR адрес
После запуска процедура считывает по адресу 23296 (в буфер принтера)
первый встреченный заголовок. Если из-за подключенных внешних устройств
этот адрес не устраивает Вас. то можете его сменить, заменяя в строке 200
программы с листинга 1 число # 005В шестнадцатиричным адресом, по кото-
рому Вы хотели бы считывать заголовки (две первые цифры являются
младшим байтом этого адреса). Чтобы после этой замены избежать проверки
контрольной суммы в этой строке, в конце текста, взятого в кавычки, вместо
пробела и четырех цифр контрольной суммы поместите литеру «S* с четырь-
мя ведущими пробелами.
После считывания заголовка процедура считывает заключенную в нем
информацию и возвращается в BASIC, но считанного заголовка не уничтожа-
ет. следовательно, если желаете его просмотреть дополнительно, то можете
сделать это. используя функцию РЕЕК.
Однако, чтения заголовков мало, чтобы суметь взломать блоки, записан-
ные на ленте. Необходимо ещё знать: что надо сделать с этими блоками, что-
бы разместить их в памяти, не позволяя им при этом начать работу
В случае блоков типа «BYTES» достаточно, обычно, загрузить их под
принудительный адрес повыше RAMTOP (т.е. гШвыше ячейки памяти, указы-
ваемой системной переменной RAMTOP), например, с помощью:
CLEAR 29999: LOAD "" CODE 30000
Этот метод работает, если только считываемый блок не очень длинный
(может иметь максимально до 40 К). Более длинные блоки могут просто не
поместиться в память - тогда необходимо разделить их на несколько более
коротких частей. Скоро мы узнаем как это сделать.
Так же и в случае массивов - их загрузка не вызывает затруднений - до-
статочно применить обычную в таких ситуациях инструкцию LOAD "" DATA..
.Хуже, зато, выглядит считывание программ на BASIC'e. Они обычно
записываются с помощью SAVE LINE ..., а в самом начале строки, с кото-
рой должны выполняться, размещены инструкции, защищающие программу
от останова. Простейшим решением является загрузка программы не с
помощью LOAD а с помощью MERGE и". но этот способ не всегда дает
результат. Из этой безнадежной ситуации существует два принципиальных
выхода: подменить заголовок программы или использовать представленную
ниже программу «LOAD/MERGE». Первый способ основан на замене
записанного на ленте заголовка на такой же, но не вызывающий самозапуска
программы. Можно с этой же целью использовать программу «COPYCOPY» -
считать заголовок программы, нажать «BREAK», а затем инструкцией LET за-
менить её параметр START на число, большее 32767 (т.е. выполнить,
например LET l=327o8, если корректируемый заголовок был считан как пер-
вый набор). Модифицированный таким образом заголовок записываем где-
нибудь на ленте. Убираем из памяти программу «COPYCOPY» и вводим
LOAD"". Считываем сделанный только что заголовок, но сразу после его
окончания останавливаем ленту. Теперь в магнитофон вставляем кассету с
программой - так чтобы считать только текст программы, без его заголовка.
Другой способ намного проще Вводим в память (с клавиатуры или лен-
ты) программу «LOAD/MERGE», размещенную на листинге 2. После запуска
она начинает ждать первую программу на BASiC'e. которая находится на лен-
те Считывает ее совершенно так же. как инструкция LOAD"", но после за-
грузки не позволяет программе запуститься - выводит только сообщение
"О ОК" с информацией, с какой строки считанная программа должна старто-
вать
На листинге 3 представлена процедура, которая образует эту программу
из пакетов Ее определяющей инструкцией является CALL 1821 Начиная с
адреса 1541. в ROM находится процедура, интерпретирующая инструкции
SAVE. LOAD. VERIFY. MERGE. CALL 1821 прыгает прямо в середину этой
процедуры Ее начальную часть (считывающую параметры этих инструкций)
обходим, заменяя это строками 40-90 нашей процедуры, сначала с помощью
RST 48 резервируем 34 байта в области WORSP. Первые 17 байт заполняют-
ся процедурой из ROM после считывания параметров выполняемой
инструкции BASIC'a. а вторые - это место для считанного заголовка Оба этих
заголовка затем сравниваются перед тем как прочесть блок данных (проверя-
ются соответствия имени и типа блока). Адрес свободного места переносим
из регистра DE в IX. в строке 80 фиксируем в системной переменной Т-
ADDR. что нам нужна LOAD, а не. например. VERIFY, а в первом байте заго-
ловка для сравнения помещаем значение 2э5. означающее, что должен быть
считан первый встреченный блок (т.е. программа на BASIC'e) Остальное бе-
рет на себя процедура из ROM. действующая идентично LOAD"". Разница в
выполнении LOAD и «LOAD/MERGE» основано на том. что после полного
считывания программы вместо возврата по RET в интерпретатор BASIC'a. мы
переносим значение переменной NEWPCC (номер строки, к которой дол-
жен быть переход) в РСС (номер последней выполняемой строки), а также не
допускаем самозапуска программы - выполняем RST 8 с сообщением «0 ОК»
Благодаря этому переносу в выведенном сообщении будет информация о
номере строки, с которой считанная программа должна была запуститься
Листинги программ
Листинг 1
10 CLEAR 59999: LET POCZ-60000
20 LET ADR-POCZ
30 RESTORE: READ A.B.C.D.F.F
40 DATA 10,11.12.13.14.15
50 LET NR-200: RESTORE NR
60 LET S-0: READ AS: IF AS-"." THEN GO TO 130
70 FOR N=1 TO LEN AS-5 STEP 2
80 LET W=15*VAL A4(N)+VAL A4<N+1)
90 POKE ADR, W: LET ADReADR+l:LET S-S+W
100 NEXT N
110 IF VAL A4(N TO)S THEN PRINT "OSH.W STROK";NR:
STOP
120 LET NR-NR+10: GOTO 60
130 PRINT "VSE DANNYE HOROSHO......NACHALO:":
POSZ "KONIECj ":ADR-1 "'DLINA_:_";ADR-POCZ
140 SAVE "CZYTACZ" CODE POCZ.ADR-POCZ
150 REM
200 DATA "21920009F.5DD21005BDDE51111 1246"
210 DATA "00AF37CD5605DDE130F23E02CD 1531"
220 DA Г "01 Ы 1COO<U)D7EOOCDOAOCDDE5 1265'
230 DATA "Dl 1 3010Л90С D3C202A7B5CD1 E5 1231"
240 DATA -D5ED537B5C010Q00CD3C20DD46 1346"
250 DATA "OCDD4EOBCD2B2DCDE32DE1DD7E 1664"
260 DATA "00DD460EDD4E0DC5A7202BEB01 1292 '
270 DATA " 19OOCD3C20D5DD4610DD4E0FCD 136Г
280 DATA "2B2DCDE32DD1С178E6C0206EC5 1848"
290 DATA "011300CD3C20C1CD2B2DCDE32D 1280"
300 DATA " 185EFE03 203601700009 E BO 111 836"
310 DATA "0018E60D449C75676F9D9B2073 1281"
320 DATA "616D65676F2070726F6772616D 1313"
330 DATA "75200D417574 6F73 7461727420 1161"
340 DATA "2D206C696E696120D2EE180181 1239"
350 DATA "0009EB010900CD3C20C13E1FAO 997"
360 DATA "F660D7DD7E003D28033E24D73F 1383"
370 DATA "28D73E29D7E1227B5C3E0DD7C9 1538"
380 DATA "04081С2020201C000010181030 268"
390 DATA 100C0008103840380478000D41 430"
400 DATA "64726573209C61646F77616E69 1357"
410 DATA "61200D5461626C69636J 20 862"
420 DATA
Листинг 2
1 REM LOAD/MERGE TS & RD 1987
2 FOR N«60000 TO 60025:READ A:POKE N,A:NEXT N
3 RANDOMIZE USR 60000
4 DATA 1,34,0,247,213,221,225,253,54,58,
1,221,54,1,255,205,29,7,42,66,92,34,69,
92,207,255
Листинг 3
10 ;LOAD/MERGE
20 ;
30 ORG 60000
40 LD ВС,34
50 RST 48
60 PUSH DE
70 POP IX
80 LD (IY+58),1
90 LD (IX+l),225
100 CALL 1821
HOLD HL,(23618)
120 LD (23621),HL
130 RST 8
140DEFB 255