ZX-Ревю 1993 №1-2 1992 г.

Защита программ - продолжаем публикацию материалов, посвященных защите программного обеспечения от просмотра и копирования. Непосредственная тема сегодняшней статьи связана с подробным разбором стандартных процедур ПЗУ, осуществляющих загрузку данных с магнитофона.


ЗАЩИТА ПРОГРАММ

Мы продолжаем публикацию материалов, посвященных защите программного обеспечения от просмотра и копирования. Непосредственная тема сегодняшней статьи связана с подробным разбором стандартных процедур ПЗУ, осуществляющих загрузку данных с магнитофона.

(ПРОДОЛЖЕНИЕ)

Начало см. в '^-РЕВЮ-92": N 1,2 - с. 9-16 N 3,4 - с. 53-60 N 5,6 - с. 97-104 N 7,8 - с. 141-146 N 9,10 - с. 185-192 N 11,12 - с. 230-237.

1.1 Процедуры в машинных кодах, реализующие загрузку программ с

магнитофона.

Мы с Вами рассмотрели систему кодирования информации на магнитной ленте. Теперь приступим к рассмотрению процедур ПЗУ, реализующих ввод программ "Спектрума" с магнитофона.

Для того, чтобы точно оценить временные характеристики операций, реализующих необходимые задержки в цикле загрузки, необходимо знать время исполнения отдельных инструкций процессора Z-80. Как Вам уже вероятно известно, скорость работы процессора на несколько порядков выше максимальной скорости записи кодированных логических единиц информации на магнитной ленте. Поэтому программа должна выполнять задержку после каждого записываемого на магнитофон бита данных.

Время задержки удобно считать не в секундах, а в циклах тактового генератора, который синхронизирует работу процессора Z-80. У каждой команды процессора есть своя продолжительность, которая может быть измерена в этих циклах. Впрочем, не для всех команд это величина постоянная. Так, например, для команд, связанных с каким-либо условием, (например JR NZ и т.п.) число циклов обычно зависит от того, выполняется или нет данное условие и для каждого случая различно, поэтому нам необходимо учитывать и этот факт и рассматривать соответствующие приемы задержки, используемые в данной процедуре обработки информации, поступающие с магнитной ленты.

Информацию о количестве циклов по каждой команде процессора Вы можете получить из справочника "ИНФОРКОМа" по микропроцессору Z-80, см. книгу "Программирование в машинных кодах. Первые шаги. Практикум. Справочник".

Процедура, выполняющая загрузку информации с магнитофона, начинается в ПЗУ с адреса 0556H = 1366 DEC. Ее действие заключается в многократной проверке шестого бита порта 254 и в определении временного интервала между двумя следующими друг за другом фронтами.

Таким образом, при чтении блока данных с ленты важнейшим является точное определение интервала между фронтами сигнала. Эту работу выполняет специализированная процедура, размещенная в ПЗУ компьютера под адресом 05E3H = 1507. Эту точку входа в ПЗУ можно использовать, когда надо установить, сколько времени прошло от момента вызова процедуры до обнаружения двух, следующих один за другим, смен уровней сигналов в гнезде EAR (и насколько оба они соответствуют допустимым пределам требуемых временных интервалов).

Второй важной точкой входа в ПЗУ является специализированная процедура, позволяющая определить, сколько времени прошло с момента вызова процедуры до получения первого фронта. Она размещается по адресу 05E7H = 1511. Вызов этой процедуры выполняется после помещения в регистр B временной константы, ограничивающей количество проверок порта 254.

Если оказывается, что время ожидания смены фронтов сигнала превышено или будет установлен факт нажатия клавиши BREAK, происходит сброс флага C флагового регистра F. Для распознавания каждой из этих двух ситуаций служит флаг Z. Если он обнулен, значит во время работы данной процедуры была нажата клавиша BREAK.

Мы начнем рассмотрение процедуры загрузки с изучения этих подпрограмм. Названия процедур стали общепринятыми после выхода в свет книги Я.Логана и Ф.О'Хары "Полный дисассемблер ПЗУ Спектрума". Те же названия использованы и в наиболее полном описании работы "Спектрума" с магнитофоном Анджея Каллофа ("Спектрум и магнитофон") и мы сохраняем те же наименования процедур.

МЕТКА

АССЕМБЛЕР ЦИКЛЫ

1

LD-EDGE-2

CALL LD-EDGE 1

17

2

RET NC

11,5

3

LD-EDGE-1

LD A,#16

7

4

LD-DELAY

DEC A

1

5

JR NZ,LD-DELAY

12,7

6

AND A

4

Как Вы уже заметили, LD-EDGE-2 обеспечивает двукратный вызов процедуры LD-EDGE-1. Перед тем, как приступить к проверке порта, выжидается 358 тактов работы Z-80. процессор слишком быстродействующий по сравнению со временем, в течение которого на основе активного сигнала от ULA, стабилизируется состояние шестого бита порта 254.

В последней колонке приведено время затрачиваемое на выполнение каждой операции, измеренное в тактах микропроцессора. Так, в случае выполнения операции возврата из подпрограммы, в зависимости от состояния флага переноса C - RET NC -затрачиваются 5 или 11 тактов микропроцессора, в связи с тем, выполняется условие, или нет. По аналогичной причине мы приводим два значения и для процедуры условного перехода JR NZ, поскольку время, затрачиваемое на ее выполнение, может быть различным в зависимости от выполнения или невыполнения условия.

После начала работы процедуры LD-EDGE-1, в аккумулятор загружается шестнадцатеричное значение 16H. Благодаря этому мы организуем цикл задержки, основанный на уменьшении аккумулятора на единицу и сравнении этого значения с нулем. Если ноль не достигнут, мы продолжаем уменьшать аккумулятор на единицу, обеспечивая задержку. Так происходит ровно 7 + 21 X 16 + 4 + 7 + 4 = 358 тактов микропроцессора.

Командой AND A сбрасывается флаг C, который, как мы знаем, выполняет функции маркера.

После обнуления маркера проверяется, достигла ли временная константа в регистре B значения нуля. Обратите внимание, что в противоположность процедурам записи, здесь значение регистра B увеличено на единицу.

7 LD-SAMPLE INC B ;4

8 RET Z ;11,7

Следующим этапом идет контроль нажатия клавиши BREAK. Для этого мы считываем числовое значение из порта 254 и, после ротации данного значения (причем интересующий нас бит смещается во флаг переноса С), мы осуществляем продолжение работы, либо выходим из данной процедуры. Все зависит от того, была ли нажата клавиша BREAK.

9 LD A,#7F ;7

10 IN A,(#FE) ;11

11 RRA ; 4

12 RET NC ;11,7

Теперь мы проверяем, изменился ли шестой бит 254 порта, в соответствии со значением, сохраняемым в регистре C (именно этот бит сигнализирует о поступлении сигнала с магнитофона). Если бит не изменился, о чем свидетельствует нулевой флаг Z, то он проверяется снова.

13

14

15

XOR C AND #20

JR Z,LD-SAMPLE

4 7

32,7

Фронт сигнала извлечен, инвертируются все биты в регистре C и одновременно изменяется информация о состоянии порта, а также цвет изменяется на дополнительный цвет бордюра (красный-голубой или синий-желтый). Эти операции выполняются с помощью специальной команды инверсии. Для этого мы заносим в аккумулятор содержимое регистра C, после чего над аккумулятором выполняется команда CPL, которая инвертирует каждый его бит на противоположный. В результате получается как бы дополнение содержимого аккумулятора до 255 (в абсолютной двоичной арифметике).

После этого инвертированное значение мы возвращаем в регистр C.

16

17

18

LD A,C CPL

LD C,A

После смены цвета бордюра на экране включается флаг C регистра F, что сигнализирует об успешном результате проверки. Соответствующие промежутки времени подсчитывает процедура, вызываемая на основе приращения значения в регистре B. Каждый вызов процедуры LD-EDGE-1 длится 465 тактов микропроцессора плюс по 58 тактов на каждый дополнительный тест.

19 AND #07 ; 7

20 OR #08 ;7

21 OUT (#FE),A ; 11

22 SCF ;4

23 RET ; 10

Рассмотрим теперь основную загрузочную процедуру LD-BYTES. Она занимает в памяти объем начиная от 0556H до 05E2H. Перед началом работы процедуры в регистр DE необходимо занести длину читаемого блока, в регистр IX адрес байта, начиная с которого должна быть загружена информация. В аккумулятор заносится тип блока.

LD-BYTES может не только считывать файлы в память, но также и сравнивать их. На этом принципе основано действие БЕЙСИК-команды VERIFY. Необходимый режим работы определяется состоянием флага переноса C перед вызовом процедуры загрузки. Его установка в единицу означает считывание в память всего файла, а обнуление - сравнение. Поскольку микропроцессор может работать не более чем с шестнадцатиразрядными числами, длина читаемого блока не может превышать 65535 (FFFFH).

1

LD-BYTES

INC D

4

2

EX AF,A'F'

4

3

DEC D

4

4

DI

4

5

LD A,#0F

7

6

OUT (#FE),A

11

7

LD HL,#053F

10

8

PUSH HL

Регистр D увеличивается с целью обнуления флага Z перед сохранением его в альтернативном наборе регистров микропроцессора. После этого значение регистра D

восстанавливается и запрещаются маскированные прерывания.

Занесение в регистр A значения 0FH знаменует собой установку белого цвета бордюра путем выдачи этого значения в порт 254. В регистровую пару HL заносится адрес процедуры обработки возврата SA-LD-RET, который сохраняется на стеке.

9

IN A,(#FE)

11

10

RRA

4

11

AND #20

7

12

OR #02

7

13

LD С,А

4

14

CP A

4

Первый тест порта 254 представляет собой занесение в аккумулятор значения состояния данного порта с последующей его ротацией и выполнением операции логического "ИЛИ". Из всего байта оставляется только шестой бит, который характеризует состояние гнезда EAR. После выполнения этого цвет бордюра становится красным. После помещения в регистр C значения 22Н или 02Н включается флаг Z флагового регистра.

15 LD-BREAK RET NZ ;11,7

16 LD-START CALL LD-EDGE-1 ;17

17 JR NC,LD-BREAK ;12,7

Следующий отрезок программы представляет собой цикл, в котором программа будет задержана до нажатия клавиши BREAK или до смены состояния шестого бита порта 254. Во втором случае цвет бордюра будет изменен на голубой. Выполнение данной процедуры осуществляется многократным повторением вызова подпрограммы LD-BREAK.

18

LD HL,#0415

10

19

LD-WAIT

DJNZ LD-WAIT

10,

6

20

DEC HL

6

21

LD A,H

4

22

OR L

4

23

JR NZ,LD-WAIT

12,

7

Перед началом непосредственного чтения информации с магнитной ленты выжидается около одной секунды. Это достигается за счет создания специального цикла задержки. Длительность ожидания зависит от значения, которое занесено в регистр HL. В нашем случае оно составляет #0415.

24 CALL LD-EDGE-2 ;17

25 JR NC,LD-BREAK ;12,7

Первым делом проверяется, не передается ли еще какой-нибудь сигнал за данный промежуток времени. За время, составляющее приблизительно 14000 тактов микропроцессора должны появиться два фронта сигналов. Обратим внимание на большой излишек времени перед началом загрузки данных. В пилотирующем сигнале фронты появляются в интервале порядка 2186 тактов.

26

LD-LEADER LD В,#9С

7

27

CALL LD-EDGE-2

17

28

JR NC,LD-BREAK

12,

7

29

LD A,#C6

7

30

CP B

4

31

JR N^LD-START

12,

7

В регистр B заносится временная константа 9CH, позволяющая процедуре LD-EDGE-2 искать два фронта за время, не превышающее 7000 тактов микропроцессора. С другой стороны, после их отыскания проверяется прошло ли с момента вызова вышеуказанной процедуры до появления заднего фронта не менее, чем 3366 тактов работы микропроцессора. Таким способом отличаются фронты пилотирующего сигнала от значительно более близких пар фронтов информационных сигналов.

32 INC H ;4

33 JR NZ,LD-LEADER ;12,7

В цикле мы начинаем искать фронты пилотирующего сигнала. После извлечения 256 пар, мы переходим к ожиданию импульса синхронизации, который сигнализирует нам о начале собственно самого файла данных.

34 LD-SYNC LD В,#С9 ;7

35 CALL LD-EDGE-1 ;17

36 JR N^LD-BREAK ; 12,7

Проверяется время появления каждого нового фронта сигнала. Он должен быть обнаружен в течение 3655 тактов работы микропроцессора. Необходимая точность обеспечивается занесением в регистр B временной константы C9H и вызовом процедуры LD-EDGE-1.

37

38

39

LD А,В CP #D4

JR NC,LD-SYNC

4 7

12,7

Однако, если это произошло до истечения 1100 тактов работы процессора, значит, уже появился первый фронт синхроимпульса. Для определения его появления мы заносим в аккумулятор содержимое регистра B и сравниваем полученное значение с числовой константой D4H. В зависимости от состояния флага переноса, программа либо продолжает свою работу, либо же возвращается на начало процедуры проверки появления синхроимпульса по метке LD-SYNC. Значит, сигнал пришел слишком рано и это не тот сигнал, который нужен.

40

41

CALL LD-EDGE-1 ;17 RET NC ;11,5

После появления первого фронта синхроимпульса мы ожидаем появление второго. Это достигается за счет вызова процедуры LD-EDGE-1. Если после вызова данной процедуры, она возвращает значение флага переноса выключенным, то мы осуществляем возврат в вызвавшую программу по команде RET N^

42

43

44

LD А,С XOR #03 LD С,А

После появления обоих фронтов синхроимпульса необходимо осуществить соответствующую индикацию на экране. Это делается подготовкой в регистре C сигнализации о появлении следующего фронта синим и желтым цветами. Для этого мы загружаем в аккумулятор содержимое регистра C и осуществляем операцию исключающие "ИЛИ" над содержимым аккумулятора. После этого полученное значение вновь возвращается в регистр C.

LD Н,#00 LD В,#80 JR LD-MARKER

45

46

47

7 7 12

В регистре H создается байт четности, а в регистр B помещается временная константа для считывания первого бита первого байта, описывающего тип читаемого

файла.

Как Вы помните, значение первого байта каждого файла характеризует тип файла и должно равняться нулю для заголовка и 255 для блока данных.

48 LD-LOOP EX AF,A'F' ;4

49 JR NZ,LD-FLAG ;12,7

К этому фрагменту программа возвращается после чтения целого байта, поскольку предыдущая часть программы закончилась безусловным переходом на метку LD-MARKER. В этом участке программы флаг Z употреблен для различения байта, определяющего тип файла. Здесь также учитывается флаг C, определяющий, происходит ли процесс чтения или верификации.

50

51

52

JR NC,LD-VERIFY LD (IX+0),L JR LD-NEXT

12,7

19

12

В ходе выполнения данных операций считанный байт помещается под адресом, находящихся в IX. Для этого мы загружаем в ячейку памяти, адрес которой находится в (IX+0), содержимое регистра L. После этого осуществляется безусловный переход на метку LD-NEXT.

53

54

55

LD-FLAG

RL C XOR L RET NZ

4 4

11,5

При входе в данную процедуру в регистре A находится тип файла, который мы хотим считать, а в регистре L - первый считанный байт. Первая команда служит для временного хранения значений флага C в регистре C. Если считанный байт не совпадает с содержимым регистра A, то происходит возврат из процедуры со сброшенными флагами C и Z, что сигнализирует об ошибке.

56 LD A,C ; 4

57 RRA ; 4

58 LD C,A ;4

59 INC DE ;4

60 JR LD-DEC ;12

В случае совпадения типов восстанавливается состояние флага C и регистра C. Для этого мы загружаем содержимое регистра C в аккумулятор, осуществляем ротацию C через флаг переноса и возвращаем полученное значение из аккумулятора в регистр C. Увеличение регистра DE на единицу служит для компенсации его состояния при переходе к инструкции DEC DE при выполнении безусловного перехода на метку LD-DEC.

64 LD-NEXT INC IX ;10

65 LD-DEC DEC DE ;6

66 EX AF,A'F' ;4

Данные команды устанавливают регистры IX и DE в состояние, необходимое для правильной работы программ. После этого осуществляется сохранение флага C флагового регистра F в безопасном месте с помощью использования команды перехода к набору альтернативных регистров микропроцессора Z-80.

67 LD B,#B2 ;7

68 LD-MARKER LD L,01 ;7

После установки временной константы B2H в регистре B, обнуляется регистр L и его

младший бит устанавливается в единицу. После следующих сдвигов влево он будет постепенно перемещаться по направлению к флагу C и в конце концов окажется во флаге переноса, сигнализируя считывание всего байта.

LD-8-BITS CALL LD-EDGE-2 RET NC

69

70

;17 ;11,5

Данная процедура определяет время между двумя очередными фронтами сигналов. Для этого вызывается процедура LD-EDGE-2, которая работает в соответствии со значением временной константы занесенной в регистр В.

LD A,#CB CP B RL L

71

72

73

7 4 4

Если пара фронтов найдена за время, меньшее чем около 2400 тактов относительно вызова LD-EDGE-2, то они представляют собой ноль, а если за большее время, то единицу. Значение, получающееся после анализа информации, несет в себе флаг C.

Для правильного выполнения заданной процедуры мы загружаем в аккумулятор константу CBH, относительно которой ведется проверка содержимого регистра B.

74 LD B,#B0 ;7

75 JP NC,LD-8-BITS ;10

После загрузки временной константы для следующего бита исследуется регистр B, который устанавливается только после считывания и размещения в регистре L всех восьми битов данного байта.

76 LD A,H ; 4

77 XOR L ;4

78 LD H,A ;4

Этот участок программы осуществляет модификацию байта четности, о котором мы уже писали ранее.

79 LD A,D ; 4

80 OR E ;4

81 JR NZ,LD-LOOP ;12,7

Здесь мы проверяем все ли байты были считаны и, если должны быть считаны еще какие-либо байты, то мы возвращаемся к метке LD-LOOP. Эта проверка осуществляется с помощью сравнения содержимого регистра DE с нулем и осуществлением операции условного перехода в зависимости от состояния флага Z флагового регистра.

82 LD A,H ;4

83 CP #01 ;7

84 RET ; 10

После считывания последнего байта (четности), в регистре H должен быть ноль. Если это так, то флаг C включается, сигнализируя этим правильность окончания загрузки файла.

Так работают процедуры загрузки файлов с ленты в память компьютера. Надеемся, что этот материал окажется полезным для Вас. Тем не менее, его невозможно рассматривать в отрыве от информации по вопросам записи файлов на магнитную ленту в формате "Спектрума". Поэтому в следующей главе мы рассмотрим более подробно этот вопрос.

Глава 2. Стандартные процедуры записи информации на магнитную

ленту.

Теперь мы переходим к рассмотрению процедур, реализующих запись информации на магнитную ленту и помещенных в ПЗУ компьютера.

Начнем с процедуры SA-BYTES, служащей для записи последовательности битов на кассету. Предполагается, что в момент ее запуска в регистре DE находится длина записываемого блока, в регистре IX - адрес первого байта, а в аккумуляторе - число, определяющее тип загружаемого блока. Напомним, что для заголовка этот байт равен нулю, в то время как для блока данных он равен 255. Первая процедура записи информации на магнитную ленту начинается с адреса 04C2H. Это является ее отправной точкой.

1

SA-BYTES

LD HL,#C53F

10

2

PUSH HL

11

3

LD HL,#1F80

10

4

BIT 7,А

8

5

JR Z,SA-FLAG

12,7

6

LD HL,#0C98

10

Первый делом загружаем в регистр HL адрес процедуры возврата к БЕЙСИКу - SA-LD-RET, после чего данное значение сохраняется на стеке. Далее мы загружаем в регистр HL временные константы 1F80H и 0С98Н, которые определяют длительность пилотирующего сигнала. Эта длительность составляет пять секунд для заголовка, и две секунды для блока данных. Определить, какой тип файла мы сейчас записываем на ленту, компьютеру помогает седьмой бит аккумулятора. Напомним, что в аккумуляторе содержится число, определяющее тип блока.

7

SA-FLAG

EX AF,A'F'

4

8

INC DE

6

9

DEC IX

10

10

DI

4

11

LD A,#02

7

12

LD B,A

4

После обработки блока содержимое регистра DE увеличивается на единицу. На столько же уменьшается и содержимое регистра IX. Все это делается после вызова альтернативного набора регистров микропроцессора и дает возможность определить байт как первый в записываемом блоке.

Выключение маскируемого прерывания необходимо, чтобы программа не была прервана для опроса клавиатуры (в обычном режиме опрос клавиатуры происходит пятьдесят раз в секунду, так как именно с такой регулярностью поступают импульсы на вход INT микропроцессора). Значение 2 в аккумуляторе установит предварительное напряжение в гнезде MIC на низкий уровень и цвет рамки на красный.

13

SA-LEADER DJNZ SA-LEADER

13,8

14

OUT (#FE),A

11

15

XOR #0F

7

16

LD B,#A4

7

17

DEC L

4

18

JR NZ,SA-LEADER

12,7

Следующий блок программы генерирует пилотирующий сигнал. Цикл SA-LEADER гарантирует задержку между последующими сменами напряжения в гнезде MIC. Затем, после установки напряжения в этом гнезде, четыре младших бита аккумулятора меняются на противоположные путем выполнения операции исключавшее "ИЛИ" над содержимым аккумулятора. Это подготавливает изменение напряжения на разъеме MIC на противоположное и смену цвета бордюра с красного на голубой или наоборот. Затем

уменьшается младший байт счетчика импульсов L.

Многие пользователи "Спектрума" наверняка встречали компьютерные программы без характерных полос на бордюре, присущих стандартным процедурам загрузки. Это объясняется тем, что цвет бордюра в ходе загрузки файла не изменяется. Как Вы помните, за цвет бордюра ответственны три младшие бита компьютерного порта 254. Поэтому, если Вы будете проводить операцию исключающее "ИЛИ" только над битом, ответственным за состояние сигнала на разъеме MIC и не будете затрагивать биты цвета бордюра. Вам также удастся добиться аналогичного эффекта.

19

20 21

DEC B DEC H

JP P,SA-LEADER

4 4 10

После обнуления младшего байта счетчика импульсов L уменьшается и старший байт Н и модифицируется в соответствующей ситуации временная постоянная в регистре B с целью учета дополнительно выполненных инструкций (в данном случае это тринадцать тактов микропроцессора). Как Вы уже могли заметить, после некоторых команд стоит несколько значений, характеризующих длительность выполнения команды в циклах микропроцессора Z-80. Это объясняется тем, что, команда может быть выполнена по-разному в зависимости от того, выполняется условие или нет. И, следовательно, длительность ее выполнения для каждого из этих случаев различна. Это объясняет тот факт, что в ходе работы такого рода процедур необходимо учитывать время, затрачиваемое на дополнительно выполняемые инструкции.

22 LD B,#2F ;7

23 SA-SYNC1 DJNZ SA-SYNC1 ;10,8

24 OUT (#FE),A ;11

После того, как был выслан пилотирующий сигнал, на порт MIC идет посылка переднего фронта синхроимпульса. Задержка в цикле SA-SYNC1 составляет 667 тактов, после чего содержимое аккумулятора передается в порт 254.

25 LD A,#0D ;7

26 LD B,#37 ;7

27 SA-SYNC2 DJNZ SA-SYNC2 ;10,8

28 OUT (#FE),A ;11

После 735 тактов идет высылка заднего фронта синхроимпульса, устанавливающего цвет бордюра голубым, напряжение на разъеме MIC - высоким. Изменение цвета и управляющего напряжения осуществляется занесением в аккумулятор числовой константы 0DH. Задержка осуществляется загрузкой в регистр B временной константы 37H. После выполнения задержки в цикле SA-SYNC2, значение аккумулятора пересылается в компьютерный порт 254.

29 LD BC,#3B0E ;10

30 EX AF,A'F' ; 4

31 LD L,A ; 4

32 JP SA-START ;10

Загрузка константы 3B0EH в регистр BC представляет собой загрузку двух различных чисел в отдельные регистры данной регистровой пары. 3BH - это временная постоянная, загружаемая в регистр B, а 0EH - придает начальное значение регистру C, в котором будет сохраняться состояние последнего напряжения в гнезде MIC и цвет бордюра (начальное состояние низкое напряжение и желтый цвет бордюра). Воспроизведенный тип блока помещается в регистр L и посылается на ленту как первый байт данного блока. После этого осуществляется безусловный переход на метку SA-START.

33 SA-LOOP LD A,D

34 OR E

35 JR Z,SA-PARITY

Процедура SA-LOOP - это начало главного цикла, записывающего следующий байт. Если счетчик байтов, который организуется в регистре DE, достиг нуля, то остается записать только байт четности. Проверка на ноль осуществляется типичным способом, характерным для микропроцессора Z-80. В аккумулятор загружаем содержимое регистра D и, используя содержимое регистра Е, выполняем операцию логическое "ИЛИ". Результат операции может быть равен нулю только в том случае, если оба байта равны нулю, поэтому нам только остается проверить содержимое флага Z флагового регистра процессора для точного установления результата выполнения данной операции.

36 LD L,(IX+0) ; 19

37 SA-LOOP-P LD A,H ;4 33 XOR L ; 4 39 SA-START LD H,A ;4

Данный отрезок программы служит для получения следующего байта для высылки его на магнитофон. Он также используется для запуска созданного в регистре H байта четности. Регистр H инициируется байтом, определяющим тип записываемого блока. (Методика построения байта четности в ходе операций записи и загрузки была нами рассмотрена в главе 1 данного тома).

40 LD А,1 ;7

41 SCF ;4

42 JP SA-8-BITS ; 10

Загружаемое в аккумулятор значение единицы установит напряжение в гнезде MIC на высокий уровень и цвет бордюра станет синим. Принудительное включение флага C флагового регистра F будет выполнять роль указателя, позволяющего утверждать, что выслано уже восемь битов данного байта.

В завершение процедуры мы осуществляем безусловный переход на метку SA-8-BITS.

43 SA-PARITY LD L,H ;4

44 JR SA-LOOP-P ;12

Процедура SA-PARITY готовит высылку байта четности, как последнего байта, записываемого на магнитофонную ленту. Для этого в регистр L, который обычно используется для хранения высылаемого байта, записываем байт четности, который находится в регистре H. После этого осуществляется переход к процедуре SA-LOOP-P, которая, собственно, и осуществляет запись.

45 SA-BIT2 LD A,C ;4

46 BIT 7,B ; 8

К процедуре SA-BIT2 мы обращаемся при втором проходе цикла, записывающего одиночный бит (это осуществляется перед высылкой заднего фронта сигнала). Проверка седьмого бита в регистре B имеет цель только обнуление флага C, сигнализируя таким образом второй проход.

47 SA-BIT1 DJNZ SA-BIT1 ;10,8

48 JR NC,SA-OUT ;12,7

Следующая процедура формирует главный цикл задержки между последовательными фронтами сигналов. Флаг C несет в себе действительное значение высылаемого бита.

4 4

10,7

49 LD B,#42 ;7

50 SA-SET DJNZ SA-SET ;10,8

Перед высылкой логической единицы нам необходимо задержать работу на В55 дополнительных тактов. Для этого в регистр B мы заносим временную константу 42Н, которая организует нам необходимый цикл задержки.

51 SA-OUT OUT (#FE),A ;11

52 LD B,#3E ; 7

53 JR NZ,SA-BIT2 ;12,7

Данная процедура организует высылку заднего фронта сигнала. Это происходит после высылки переднего фронта путем занесения в регистр B константы 3EH и вызова подпрограммы SA-BIT2.

54

DEC B

4

55

XOR A

4

56

INC A

4

57

SA-8-BITS RL L

8

58

JP NZ,SA-BIT1

10

После модификации временной константы в регистре B путем уменьшения ее на единицу, обнуляется флаг Z и в аккумулятор вносится значение единицы (бордюр становится синим, а напряжение в MIC - низкого уровня). Затем, после увеличения аккумулятора на единицу, во флаг C перемещается следующий бит из регистра L. Если регистр L отличен от нуля, то он знаменует собой начало цикла SA-BIT1, причем первый вход в этот цикл выполняется с установленным флагом C, в то время как все остальные - с обнуленным. Благодаря этому только после восьми проходов регистр L достигает значения нуля.

59 DEC DE ;6

60 INC IX ; 10

61 LD B,#31 ;7

Данный отрезок программы осуществляет модификацию счетчика путем уменьшения на единицу содержимого регистра DE и увеличения на единицу содержимого регистра IX. Следующим этапом идет подготовка временной константы путем засылки в регистр B значения 31H.

62

63

64

65

LD A,#7E IN A,(#FE) RRA

RET NC

7 11 4

11,5

Здесь выполняется контроль нажатия клавиши BREAK и, если она нажата, то программа передает управление на адрес 053FH процедуре обработки возврата в БЕЙСИК SA/LD-RET.

Проверка нажатия клавиши начинается с чтения в аккумулятор значения из порта FEH. После получения значения осуществляется сдвиг аккумулятора вправо с использованием флага переноса, в который попадает нулевой бит аккумулятора. Этот бит и характеризует тот факт, была ли нажата клавиша BREAK. Завершает этот участок программы процедура условного возврата, которая, анализируя состояние флага переноса либо продолжает выполнение программы по нижеследующему маршруту, либо осуществляет возврат к процедуре выхода в БЕЙСИК.

66 LD A,D ; 4

67 INC A ;4

68 JP NZ,SA-LOOP ; 10

Контроль счетчика байтов осуществляет предварительную проверку на достижение нуля в регистре D. Для этого мы передаем в аккумулятор значение регистра D, увеличиваем аккумулятор на единицу и проверяем содержимое регистра A на наличие нуля. Переход к метке SA-LOOP выполняется также в случае достижения регистром DE значения нуля, так как необходимо еще записать байт четности.

69 LD B,#3B ;7

70 SA-DELAY DJNZ SA-DELAY ; 10,8

71 RET ; 10

После достижения в регистре DE значения #FFFF и короткой паузы управление передается к процедуре возврата к БЕЙСИКУ SA/LD-RET. Как Вы знаете, в компьютере при вычитании значений из регистра, содержащего ноль, у нас получается значение FFFFH, FFFEH, FFFDH и так далее. Пауза организуется с помощью занесения в регистр временной константы 3BH и создания цикла на основе этого значения. Последний байт процедуры SA-BITS помещается в ячейке с адресом 053EH.

Мы с Вами рассмотрели стандартные процедуры считывания и записи данных на магнитную ленту. Осталось рассмотреть процедуру обработки возврата в БЕЙСИК.

Эта процедура имеет название SA/LD-RET и используется для выхода в БЕЙСИК из подпрограмм загрузки или записи в случае успешного или неуспешного их завершения, или же нажатие клавиши BREAK. Она необходима для того, чтобы гарантировать возврат к БЕЙСИКУ из процедуры записи/считывания, как в случае их правильного окончаний, так и в случае появления ошибок или прерывания оператора, то есть клавишей BREAK. Время выполнения инструкции для этой процедуры не существенно.

1 SA/LD-RET PUSH AF

2 LD A,(#5C48)

3 AND #38

4 RRCA

5 RRCA

6 RRCA

7 OUT (#FE),A

Помещение флагового регистра на стек имеет целью сохранение флага C: обнуление его означает прерывание клавишей BREAK или ошибку чтения. Затем в аккумулятор загружается значение системной переменной BORDER и на основе ее третьего, четвертого и пятого битое воспроизводится цвет бордюра экрана, который был перед запуском соответствующей процедуры, обслуживающей магнитофон. Это осуществляется путем высылки содержимого аккумулятора в компьютерный порт 254.

8 LD A,#7E

9 IN A,(#FE)

10 RRA

11 EI

12 JR C,SA/LD-END

В этом отрезке программы еще раз проверяется, была ли нажата клавиша BREAK для того, чтобы можно было выдать соответствующее информационное сообщение. После этого мы включаем маскируемое прерывание и осуществляем условный переход в зависимости от состояния флага переноса C, который изменяется при нашей проверке нажатия клавиши BREAK. В случае, если клавиша BREAK была действительно нажата, мы осуществляем переход к процедуре SA/LD-END.

13 REPORT-D RST #08

14 DEFB #0D

Эти команды вызывают возврат к БЕЙСИКУ с выдачей сообщения:

D-BREAK-CONT REPEATS.

Данное сообщение, как Вы помните, выдается, если программа прерывается оператором с помощью клавиши BREAK. Это только один из типов сообщений, которые могут выдаваться в подобном случае. Оно говорит, что клавиша BREAK была нажата во время действия периферийной операции. Действие CONTINUE после этого оператора обычное.

15 SA/LD-END POP AF

16 RET

Первым делом мы извлекаем из стека содержимое флагового регистра. Это необходимо для воспроизведения флага переноса и правильного возврата к процедуре запуска. Следует отметить, что последний байт процедуры SA/LD-RET занимает ячейку за адресом 0555H.

Мы с Вами закончили рассмотрение процедур чтения и записи на магнитную ленту в формате "Спектрума". Данный материал мы старались построить так, чтобы он был легко доступен для начинающих и чтобы в то же время и профессионал мог почерпнуть в нем что-то новое.

В то же время, это был вынужденный шаг, поскольку цель, которую мы ставили перед собой - освоить защиту программ от копирования была бы не достигнута, если бы Вы не смогли разобраться в принципах действия стандартных загрузочных процедур, зашитых в ПЗУ "Спектрума". И, тем не менее, знание одной лишь работы стандартных подпрограмм Вам будет явно недостаточно для написания мощных защит от копирования. В следующей главе мы рассмотрим конкретную программу защиты от копирования информации на магнитной ленте. Это лишь один из многих способов защиты от копирования и мы надеемся, что читатель отнесется к изучению нашего материала творчески, то есть постарается не только вникнуть в суть публикации, но и разработает свои собственные более мощные программы.

Глава 3. Методы защиты от копирования для ПЭВМ "Спектрум".

1. Методика создания оригинальной процедуры записи информации на

магнитную ленту.

Информация, приведенная в предыдущих главах, поможет Вам поближе познакомиться со структурой хранения информации на магнитной ленте в формате "Спектрума". Однако, этого еще не достаточно для написания мощных программ защиты от копирования и тем более для создания оригинальных защищающих процедур, поскольку необходимо предварительно ознакомиться с основными существующими приемами такой защиты.

Как Вы помните, время, которое затрачивает процессор на обработку одной машинной операции несоизмеримо мало по сравнению с минимально допустимым временем, за которое можно закодировать один бит информации на магнитной ленте. Поэтому для того, чтобы правильно записать информацию на магнитный носитель, процессор вынужден делать временные задержки, оформляемые в программе в виде циклов с предварительно заданными временными константами, хранящимися в регистре В.

Таким образом, одно из направлений в создании собственной защиты - это изменение временных констант в сторону незначительного уменьшения или увеличения их значений, что позволяет сделать непригодными стандартные процедуры чтения данных, записанных в новом формате.

Для того, чтобы обеспечить надежную защиту, нет необходимости в полном изменении всех временных констант - на практике достаточно изменения одной из них. Мы можем изменить временную константу задержки, характерную для стандартного пилотирующего сигнала, стандартного синхроимпульса, а также характерную для стандартной записи логического нуля или единицы. Все это приведет к тому, что созданная и выгруженная нами программа не сможет правильно считываться стандартными процедурами, встроенными в ПЗУ компьютера.

Для того, чтобы облегчить Вашу работу по созданию собственных защит, мы хотим предложить Вам достаточно удобный способ создания программ подобного рода. Если Вы внимательно читали предыдущие главы, то наверняка обратили внимание на то, что процедуры записи и чтения состоят из большого числа подпрограмм, которые периодически вызываются в ходе работы основной процедуры. Для создания своей процедуры записи и чтения Вам фактически необходимо лишь создать головную программу, которая внесет необходимые изменения во временные константы. Остальные подпрограммы можно использовать из того стандартного набора, который уже "зашит" в ПЗУ.

Возможно, что этот путь устроит не всех и кто-то захочет написать собственные процедуры записи и чтения программ в полном объеме самостоятельно. Что ж, мы не ставим своей целью навязывать Вам свои приемы программирования, а лишь хотим ознакомить Вас с основными методами защиты.

Итак, мы предлагаем Вам для рассмотрения достаточно простой способ защиты, а именно: изменение временной константы задержки для пилотирующего сигнала. Создание собственного, отличного от стандартного, пилотирующего сигнала, позволило бы Вам защитить свою программу от несанкционированного копирования уже в начальный момент загрузки. Это объясняется тем, что все программы, записанные на магнитную ленту в формате Спектрума начинаются именно с пилотирующего сигнала.

Для создания защищенной от копирования программы, Вам необходимо исключить возможность загрузки данной программы в копировщик. Поскольку копировщик использует стандартные процедуры записи и чтения информации с магнитной ленты, то Вам необходимо блокировать его работу путем создания файла, записанного в одном из созданных Вами форматов, отличном от стандартного формата "Спектрума". Чтобы защитить программу от копирования, на практике нет необходимости в полном изменении формата записи всех блоков данных, хранимых на магнитной ленте. Для этого достаточно записать в измененном формате всего лишь один небольшой блок данных, который хранит в себе информацию, без которой не может начаться выполнение программ.

Поскольку данный файл записан в измененном формате, то его невозможно будет скопировать с помощью копировщика, а так как он несет в себе жизненно важные сведения, необходимые для правильной работы программы, то без него программа не сможет нормально функционировать, даже если попытаться отбросить его в процессе копирования.

Рассмотрим принцип формирования пилоттона в формате "Спектрума".

SA-BYTES LD HL,#053F

PUSH HL LD HL,#1F80 BIT 7,A JR Z,SA-FLAG LD HL,#0C98

SA-FLAG EX AF,A'F'

INC DE DEC IX DI

LD A,#02 LD B,A

SA-LEADER DJNZ SA-LEADER

OUT (#FE),A XOR #0F LD B,#A4 DEC L

JR NZ,SA-LEADER DEC B DEC H

JP P,SA-LEADER

Перед входом в данную процедуру предполагается, что регистры хранят информацию о записываемом файле. В регистре DE находится длина записываемого блока, в регистре IX - адрес первого байта, а в аккумуляторе - число, которое определяет тип записываемого блока, причем ноль выставляется для заголовка, a 255(0FFH) - непосредственно для блока данных.

После начала работы процедуры SA-BITS идет подготовка к формированию пилотирующего сигнала. В регистр HL могут быть занесены два числа, определяющие длительность пилотирующего сигнала. 1F80H заносится в регистр HL в случае записи заголовка - и это соответствует приблизительно пяти секундам звучания пилотирующего сигнала, а значение 0C98H заносится в данный регистр в случае записи непосредственно блока данных, что соответствует приблизительно двум секундам звучания пилотирующего сигнала. Какой тип значения заносить в регистр HL определяется путем проверки содержимого аккумулятора, точнее, его седьмого бита.

Формирование пилотирующего сигнала осуществляется процедурой SA-LEADER. Временная задержка осуществляется с помощью занесения в регистр B значения A4H. Таким образом, путем изменения содержимого аккумулятора, мы достигаем периодического перепада напряжения на выходном разъеме, связывавшем Спектрум с магнитофоном и служащим для записи информации. Если в этой процедуре значение, заносимое в регистр B, то Вам удастся сформировать свой собственный пилотирующий сигнал, отличный от стандартного, который позволит защитить Вашу программу от копирования.

После формирования нестандартного пилотирующего сигнала остается продолжить запись Вашей программы на магнитную ленту. Одним из способов осуществления этого является переход к стандартной подпрограмме SA-SYNC1, которая, если Вы правильно оформили свою процедуру записи, то стандартная подпрограмма сама закончит работу по сохранению информации на магнитном носителе и возвратится в операционную среду "Спектрума".

Теперь Вы видите, что нет ничего сложного в том, чтобы составить свою собственную программу по записи информации на магнитофон в формате "Спектрума". Для этого достаточно лишь немного модернизировать одну из стандартных процедур записи, в нашем случае SA-LEADER. Остальные процедуры записи можно использовать стандартные и, лишь когда Вы более подробно познакомитесь со структурой встроенных процедур, Вы сможете начать писать собственные программы дальнейшей обработки информации и возврата в операционную среду. Ми предлагаем Вам начинать с простейшего и постепенно совершенствовать свои приемы программирования.

2. Методика создания оригинальной процедуры чтения информации с

магнитной ленты.

Мы с Вами рассмотрели методику создания оригинальных процедур записи информации на магнитофон, используемую при защите информации от копирования. При ее рассмотрении Вам был предложен метод, позволяющий ускорить подобного рода разработки за счет использования встроенных в "Спектрум" процедур. Однако, такой метод не всегда приемлем, и поэтому, приступая к рассмотрению процедуры чтения информации с магнитной ленты, мы остановим свой выбор на создании полноценной процедуры выполняющей все функции чтения информации с магнитофона.

Процедуры чтения информации с магнитной ленты, являются более сложными в сравнении с аналогичными процедурами записи. Это объясняется тем, что основа действия процедур заключается в многократной проверке шестого бита порта 254 и в определение интервала между двумя следующими друг за другом фронтами. Таким образом, при чтении блока с ленты необходимым является точное определение интервалов между фронтами сигналов. Работу по определению этих интервалов выполняет вспомогательная процедура LD-EDGE-2, размещенная по адресу 05E3H в ПЗУ "Спектрума". Эта точка входа в ПЗУ используется, когда необходимо установить, сколько времени прошло от момента вызова процедуры до обнаружения двух, следующих одна за другим смен уровней сигналов в гнезде EAR и на сколько обе эти смены уровней сигнала соответствуют по времени допустимым пределам требуемых временных интервалов.

Второй важной точкой входа в ПЗУ является стандартная процедура LD-EDGE-1, расположенная по адресу 05E7H. Она позволяет определить, сколько времени прошло с момента вызова процедуры до извлечения первого фронта сигнала. Вызывается она после помещения в регистр B временной константы, ограничивающей количество проверок порта FEH. Превышение допустимого времени ожидания смены сигнала или определение нажатия клавиши BREAK сопровождается обнулением флага переноса. Для распознавания каждой из этих ситуаций служит флаг нуля. Если флаг Z обнулен, то в ходе работы программы была нажата клавиша BREAK, а если он включен, то произошло превышение временного интервала.

В рассматриваемой нами методике изменения стандартного пилотирующего сигнала Вам необходимо как можно более точно рассчитать необходимое время задержки для четкого поиска фронтов модернизированного пилоттона. Как мы и обещали, ниже приводится специализированная программа для загрузки с ленты файла, начинающегося с

модернизированного пилоттона.

ORG 50000

LD DE,256

LD IX,23296

LD A,255

1

LD-

-BYTES

INC D

4

2

EX AF,A'F'

4

3

DEC D

4

4

DI

4

5

LD A,#0F

7

6

OUT (#FE),A

11

7

LD HL,#053F

10

6

PUSH HL

9

IN A,(#FE)

11

10

RRA

4

11

AND #20

7

12

OR #02

7

13

LD C,A

4

14

CP A

4

15

LD-

-BREAK

RET NZ

11,

7

16

LD-

-START

CALL LD-EDGE-1

17

17

JR NC,LD-BREAK

12,

7

18

LD HL,#0415

10

19

LD-

-WAIT

DJNZ LD-WAIT

10,

6

20

DEC HL

6

21

LD A,H

4

22

OR L

4

23

JR NZ,LD-WAIT

12,

7

24

CALL LD-EDGE-2

17

25

JR NC,LD-BREAK

12,

7

26

LD-

-LEADER

LD B,#9C

7

27

CALL LD-EDGE-2

17

28

JR NC,LD-BREAK

12,

7

29

LD A,#C6

7

30

CP B

4

31

JR NC,LD-START

12,

7

32

INC H

4

33

JR NZ,LD-LEADER

12,

7

34

LD-

SYNC

LD B,#C9

7

35

CALL LD-EDGE-1

17

36

JR NC,LD-BREAK

12,

7

37

LD A,B

4

38

CP #D4

7

39

JR NC,LD-SYNC

12,

7

40

CALL LD-EDGE-1

17

41

RET NC

11,

5

42

LD A,C

4

43

XOR #03

7

44

LD C,A

4

45

LD H,#00

7

46

LD B,#B0

7

47

JR LD-MARKER

12

48

LD-

LOOP

EX AF,A'F'

4

49

JR NZ,LD-FLAG

12,

7

50

JR NC,LD-VERIFY

12,

7

51

LD (IX+C),L

19

52

JR LD-NEXT

12

53

LD-

FLAG

RL,C

4

54

XOR L

4

55

RET NZ

11,

5

56

LD A,C

4

57

RRA

4

58

LD C,A

4

59

INC DE

4

60

JR LD-DEC

12

64

LD-

NEXT

INC IX

10

65

LD-

DEC

DEC DE

6

66

EX AF,A'F'

4

57

LD B,#B2

7

68

LD-

MARKER

LD L,01

7

69

LD-

8-BITS

CALL LD-EDGE-2

17

70

RET NC

11,

5

71

LD A,#CB

7

72

CP B

4

73

RL L

4

74

LD B,#B0

7

75

JP NC,LD-8-BITS

10

76

LD A,H

4

77

XOR L

4

78

LD H,A

4

79

LD A,D

4

80

OR E

4

81

JR NZ,LD-LOOP

12,

7

82

LD A,H

4

63

CP #01

7

84

RET

10

1

LD-

EDGE-2

CALL LD-EDGE 1

17

2

RET NC

11,

5

3

LD-

EDGE-1

LD A,#16

7

4

LD-

DELAY

DEC A

4

5

JR NZ,LD-DELAY

12,

7

6

AND A

4

7

LD-

SAMPLE

INC B

4

8

RET Z

11,

7

9

LD A,#7F

7

10

IN A,(#FE)

11

11

RRA

4

12

RET NC

11,

7

13

XOR C

4

14

AND #20

7

15

JR Z,LD-SAMPLE

12,

7

16

LD A,C

4

17

CPL

4

18

LD C,A

4

19

AND #07

7

20

OR #08

7

21

OUT (#FE),A

11

22

SCF

4

23

RET

10

Приведенная выше программа полностью соответствует стандартной процедуре загрузки информации и может осуществлять чтение файлов в формате "Спектрума". Если Вы внимательно присмотритесь к данной программе в машинных кодах, то легко обнаружите, что за загрузку пилоттона ответственна лишь одна процедура LD-LEADER (в нашей программе ей соответствуют строки 26-31). Здесь временная константа 9CH позволяет процедуре LD-EDGE-2 искать два фронта сигнала за время, не превышающее 7000 тактов работы процессора. С другой стороны, после их нахождения проверяется, прошло ли с момента вызова процедуры LD-EDGE-2 до появления заднего фронта не менее, чем 3366 тактов работы процессора. Этим способом отличаются фронты пилотирующего сигнала от значительно более близких пар фронтов информационных сигналов.

Загружаемая в регистр B временная константа 9CH перед вызовом процедуры LD-EDGE-2 формирует необходимую задержку для правильной работы этой стандартной процедуры. После того, как мы возвращаемся из процедуры LD-EDGE-2, мы первым делом проверяем, была ли нажата клавиша BREAK и лишь в следующий момент сравниваем содержимое регистра B с предварительно загруженной в аккумулятор константой C6H. Команда сравнения, применяемая в этом случае, позволяет нам сравнить содержимое аккумулятора с числом, содержащимся в регистре B. В результате операции числовые значения не изменяются. Результат сказывается только на флагах регистра F. Если содержимое аккумулятора больше сравниваемого числа или равно ему, то флаг переноса будет выключен. Если же оно меньше, то флаг переноса будет равен единице.

Как Вы помните, процедура LD-EDGE-2 позволяет установить, сколько времени прошло от момента ее вызова до обнаружения двух, следующих друг за другом смен уровней сигналов в магнитофонном гнезде. Для этого в регистр B заносится константа, определяющая количество проверок магнитофонного гнезда EAR. Если изменение фронта не обнаружено, то мы выходим из процедуры LD-EDGE-2 по обнулению регистра B. Если же мы обнаружили смену фронтов в тот момент, когда регистр B еще не достиг своего нулевого значения, мы выходим из вышеуказанной процедуры и сравниваем оставшееся в регистре B значение с константой, загружаемой в аккумулятор. Это необходимо для того, чтобы проверить, прошло ли с момента вызова LD-EDGE-2 до появления заднего фронта не менее, чем 3366 тактов работы процессора.

Для того, чтобы создать собственную процедуру чтения файлов с магнитофона, Вам необходимо рассчитать, какие значения заносить в регистр B перед вызовом LD-EDGE-2 из подпрограммы LD-LEADER и с каким значением аккумулятора необходимо сравнивать остаток регистра B после отыскания фронта сигнала.

Все вышесказанное касается лишь того случая, когда Вы будете использовать метод модернизации пилотирующего сигнала, чтобы добиться его отличия от стандарта. Для каждого варианта такого сигнала Вам необходимо создавать собственную оригинальную процедуру загрузки, то есть подсчитывать время задержки.

Мы с Вами рассмотрели методику защиты от копирования с использованием нестандартного пилотирующего сигнала. Однако самой важной целью этой работы было показать Вам, что Вы в состоянии сами написать как процедуру записи программы на магнитную ленту, так и процедуру чтения. Это еще ближе знакомит Вас с работой компьютера, помогает преодолеть естественную неуверенность перед машинным кодом и, как мы надеемся, поможет Вам реализовать по новому свои способности в программировании.




СОДЕРЖАНИЕ:


  Оставте Ваш отзыв:

  НИК/ИМЯ
  ПОЧТА (шифруется)
  КОД



Темы: Игры, Программное обеспечение, Пресса, Аппаратное обеспечение, Сеть, Демосцена, Люди, Программирование

Похожие статьи:
Грей - Gray Human-Spy (Thief, часть первая).
Письмо №302 - Вологодская обл, Сокольский р-н, г Сокол
Письмо №311 - Беларусь, Витебск
Программирование - Алгоритм защиты диска от копирования.
Ликбез - о правилах хорошего тона в программировании.

В этот день...   27 апреля