Spectrum-совместимые компьютеры «Байт»,«Байт-01» и прочие раритеты

7 / 2 208

Защита "Церикопик" и внутренняя структура загрузчика "Байтовских" игр

Что такое "Церикопик"?

Отличительной особенностью всех игр была защита от загрузки на обычном Спектруме (логотип - "Церикопик"):

Церикопик

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

Церикопик

При загрузке на обычных Спектрумах всё выглядит следующим образом:

Попытка загрузки программы с «Церикопиком» на «обычном» ZX-Spectrum

Одним словом, беда!

Но, несмотря на своё пакостное назначение, "Церикопик" был нарисован симпатично.

Как работает Церикопик

На разных версиях кассет загрузчики с Церикопиками были разные. Различия состояли в реализации визуальных эффектов при загрузке на компьютерах, отличных от "Байта".

В "старых" кассетах при запуске не на "Байте" церикопик выводил себя на экран и делал "сброс" компьютера.

В "новых" кассетах церикопик выводил дополнительно надпись "Покупайте компьютер Байт!", пищал и только потом делал "сброс".

В качестве примера работы защиты с "Церикопиком" я приведу особенности защиты 1991 года - т.е. "Церикопика" с "новых" кассет.

Программы с Церикопиком были с однотипными загрузчиками. Сам загрузчик был интегрирован в бейсик-файл и защищён несколькими "ксорками", а также стоит защита от взлома простейшими мониторами типа MONS и т.п. Она состоит из многочисленных команд зануления свободной области памяти. Кроме этого в Церикопике есть процедуры проверки целостности памяти. Если они обнаруживают, что в памяти компьютера находится нечто подозрительное, то даётся команда сброса компьютера. Однако эти защиты легко обходятся при помощи отладчика STS. После "расксоривания" загрузчик выводит на экран "Церикопика" и проверяет, программа загружена на Байте или нет?

Это делается несколькими способами. Во-первых, проверяется процедура обработки немаскируемого прерывания по адресу #0066. Если там находится команда RETN (код команды #ED, #45), то Церикопик считает эту проверку выполненной. Ещё он допускает, что по адресу #0066 может находиться следующий код:

#0066 PUSH AF
      PUSH HL
      LD HL,(#5CB0)
      LD A,H
      OR L
      JR NZ,#006E (код #20,#FF)
#006E EQU $-1
      JP (HL)
      POP HL
      POP AF
      RETN
Для сравнения в ПЗУ BASIC-48 оригинального Спектрума эта процедура имеет вид:
#0066 PUSH AF
      PUSH HL
      LD HL,(#5CB0)
      LD A,H
      OR L
      JR NZ,#0070 (код #20,#01)
      JP (HL)
#0070 POP HL
      POP AF
      RETN
Отличие состоит в одном байте.

Вторая проверка заключается в проверке значения байта по адресу #3870 в ПЗУ компьютера. В "Байте" по этому адресу находится значение #6B. В оригинальном Спектруме тут находится #FD.

Третья проверка, самая интересная. Примерным образом вычисляется быстродействие компьютера в нижней памяти (обращение к которой тормозится видеопроцессором) и в верхней (быстрой памяти) и сравнивается с быстродействием "Байта". В случае несовпадения вычисленных значений - fail :(. Для оценки быстродействия при разрешённых прерываниях проверяется за сколько времени поменяется значение системной переменной FRAMES (адрес - #5C78, #5C79):

      LD DE,(#5C78) ;ждём, пока счётчик FRAMES поменяет
#619E XOR A         ;значение
      LD HL,(#5C78)
      SBC HL,DE
      JP Z,#619E

      LD DE,(#5C78) ;начальное значение FRAMES
      LD BC,0       ;начальное значение счётчика - 0
#61AF INC BC        ;увеличиваем счётчик
      LD HL,(#5C78) ;проверяем, не поменялось ли
      XOR A         ;значение FRAMES
      SBC HL,DE
      JR Z,#61AF    ;повторяем, пока FRAMES не изменится
      RET

;на выходе в BC будет некое условное значение
;быстродействия компьютера в той области памяти,
;в которой расположена данная подпрограмма.
Эта процедура вызывается из разных областей памяти, и потом происходит сравнение полученных значений с "эталонными". Без этой проверки для работы Церикопика на том же Пентагоне достаточно было бы поменять ПЗУ на байтовское, но увы...

Полный текст процедуры проверки на быстродействие:

;При переходе сюда прерывания должны быть разрешены!
#615C LD HL,#61A8     ;перенос процедуры оценки быстродействия в #FE00
      LD DE,#FE00
      LD BC,#0030
      LDIR

;Оценка быстродействия компьютера в области памяти #4000-#7FFF
      CALL #619A      ;дожидаемся смены FRAMES
      CALL #619A      ;для надёжности ещё раз ждём смены FRAMES
      CALL #61A8      ;вызов процедуры оценки быстродействия в области памяти
                      ;#4000-#7FFF
      LD HL,#0080     ;За счёт "торможения" процессора при обращении к области памяти
      ADD HL,BC       ;#4000-#7FFF полученное значение быстродействия будет меньше, чем
      PUSH HL         ;для области памяти #C000-#FFFF примерно на #80 "условных единиц",
                      ;поэтому "компенсируем" разницу быстродействий, добавляя число #80.

;Оценка быстродействия компьютера в области памяти #C000-#FFFF
      CALL #619A      ;дожидаемся смены FRAMES
      CALL #619A      ;для надёжности ещё раз ждём смены FRAMES
      CALL #FE00      ;вызов процедуры оценки быстродействия в области памяти
                      ;#C000-#FFFF
      XOR A
      POP HL          ;Компенсированное быстродействие в области памяти #4000-#7FFF.
      SBC HL,BC       ;Сравнение посчитанного быстродействия в областях памяти
                      ;#4000-#7FFF (HL) и #C000-#FFFF (BC).
                      ;Для "Байта" разница между значениями должна быть больше чем #80 единиц.
                      ;За счёт "компенсирования" быстродействия (ранее мы увеличивали HL на #80) получим HL<BC.
                      ;Для других Спектрумов с общим полем памяти, значения быстродействия,
                      ;возвращаемые процедурой, будут одинаковы, поэтому в этом случае получим HL>=BC,
                      ;и в результате выполнения команды SBC HL,BC будет сброшен флаг C,
      JP NC,#618C     ;Тест провален, переход на процедуру вывода сообщения
                      ;"ПОКУПАЙТЕ КОМПЬЮТЕР БАЙТ" и дальнейший сброс компьютера.

...   тест прошёл успешно, продолжаем выполнение программы
      конец

;Процедура ожидания смены значения FRAMES
#619A LD DE,(#5C78) ;ждём, пока счётчик FRAMES поменяет
#619E XOR A         ;значение
      LD HL,(#5C78)
      SBC HL,DE
      JP Z,#619E

;Процедура оценки быстродействия
;На выходе в BC будет некое значение, оценивающее время работы
;этой процедуры в памяти
#61A8 LD DE,(#5C78) ;начальное значение FRAMES
      LD BC,0       ;начальное значение счётчика - 0
#61AF INC BC        ;увеличиваем счётчик
      LD HL,(#5C78) ;проверяем, не поменялось ли
      XOR A         ;значение FRAMES
      SBC HL,DE
      JR Z,#61AF    ;повторяем, пока FRAMES не изменится
      RET

В "новых" кассетах в Церикопике есть одна очень неприятная особенность. Дело в том, что при "расксоривании" тела Церикопика делается переход в ПЗУ на адрес #3D87, откуда какими-то хитрыми путями делается переход в область #4080, куда предварительно помещается команда перехода обратно в тело Церикопика. Всё бы ничего, но в компьютерах с установленным Beta Disk Interface при обращении по адресу #3D87 включается ПЗУ с TR-DOS, и мы попадаем в него. Естественно, никакого перехода по адресу #4080 при это не делается, и Церикопик не работает даже на самих "Байтах".

Попытка загрузки программы с Церикопиком на компьютере с Beta Disk Interface

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

Далее после всех проверок грузится с ленты игровая заставка. Во всех заставках две нижние линии знакомест закрашены зелёным цветом. Поначалу я думал, что это такая "кривая" адаптация игр, что даже картинку не могли нормально сохранить. Но потом выяснилось, что как раз в этом месте заставки находится дальнейший загрузчик кодовых блоков игры. После загрузки заставки управление передаётся на него. А чтобы коды загрузчика сильно не портили внешний вид картинки, они закрашены атрибутами зелёного цвета. Во всех играх загрузчик находится в экранной области памяти по адресу #50C0.

Далее загрузка идёт в каждой игре по-разному.

Кроме этой защиты есть ещё одна защита от загрузки кодовых блоков стандартными процедурами из ПЗУ. Суть её в том, что данные в кодовых блоках тоже "заксорены", как в Церикопике. Поэтому если попытаться прочитать кодовый блок игры стандартной процедурой из ПЗУ в результате получим "мусор", не несущий никакой информации. Раскодирование данных производится "на лету" при загрузке файла своим собственным загрузчиком, который находится внутри тела Церикопика (во всех играх загрузчик переносится по адресу #FE00). Мало того, в каждой программе данные закодированы с разными ключами, т.е. нельзя взять одну процедуру загрузки и загрузить при её помощи любой файл с байтовской кассеты - надо брать именно те процедуры загрузки, которые написаны для конкретного файла. Но и это ещё не всё. На разных кассетах одни и те же игры закодированы по-разному. Т.е. к примеру "Арканоид" с одной кассеты может быть закодирован с другим ключом, чем "Арканоид" с кассеты другого "Байта".

Способы обхождения защиты с Церикопиком

В детстве меня очень злило, что я не могу поиграться в "Байтовские" игры у себя на компьютере (у меня тогда был "Ленинград"), и я написал программу для снятия этой защиты. Так как все бейсик-блоки Байтовских игр были одинаковой длины и одинаковой структуры, можно было легко раскодировать тело загрузчика и убрать все проверки, что и делала моя программа. В результате на ленту выгружался "нормальный" загрузчик, который работал на любом Спектруме. Программа называется "Kopik" и на данный момент позволяет убирать защиту с Церикопиком как со "старых", так и с "новых" байтовских кассет.

Демонстрация работы программы:

Снимаем защиту с ″Церикопиком″ при помощи программы Kopik

Как оказалось, я был не одинок в своих порочных мечтах. В 1991 году львовские хакеры написали аналогичную программу для обхода защиты с Церикопиком - Cerikiller. Только принцип её работы несколько другой - она загружается пепосредственно перед загрузкой Церикопика и сама перехватывает его загрузку, а дальше раскодирует и обходит защиту, как это сделано у меня в программе Kopik, дальше загрузка идёт как обычно. Т.е. программа Cerikiller убирает защиту "на лету".

Скачать программы Cerikiller и Kopik можно в конце этой страницы.

Особенность формирования бейсик-загрузчика Церикопика

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

Это звучит так:

Интересный эффект. Я расскажу как такое можно сделать самому:

Начну со строения файла на ленте. В начале файла дописывается байт типа файла (0 - для заголовка и #FF - для блока данных) и один байт дописывается в конец файла. Это байт паритета. Служит для контроля правильности загрузки файла.

Структура файла на ленте:

Байт типа файла (0 или #FF) Тело файла (эти байты непосредственно загружаются в память) Байт паритета

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

Тут мы подходим к главному - процедура загрузки не контролирует, дошли ли мы до конца файла при загрузке или нет. Загрузка идёт по счётчику нужных для загрузки байт (подаётся в регистре DE при обращении к процедуре загрузки по адресу #0556 в ПЗУ компьютера). То есть логика работы процедуры загрузки такова:
- грузим первый встреченный байт файла и считаем, что это байт типа файла;
- сравниваем этот байт с заданным на входе в процедуру байтом типа файла. Если равны, то грузим дальше;
- грузим по счётчику нужное количество байт в память, параллельно считаем для загруженной информации байт паритета;
- если счётчик байтов окончился, то считаем, что загрузка данных окончена и грузим ещё один байт, который считаем байтом паритета (который был сохранён в файле процедурой записи);
- сравниваем вычисленный байт паритета и загруженный байт паритета. Если они равны - загрузка окончена успешно.

Из этого следует, что на самом деле файл может быть гораздо большего объёма чем нужное нам для загрузки количество байт. Для успешной загрузки (без ошибки паритета) нам надо всего лишь сделать файл следующей структуры (так сделано в церикопике):

1544 байта полезных данных байт паритета для предыдущих 1544 байт данных 624 байта нулей (тот самый пищащий звук)
Итого 2169 байт.

При этом в заголовке церикопика указана его длина 1544 байта. При загрузке такого файла система даст команду на загрузку 1544 байт (эти данные она берёт из заголовка). Они успешно загрузятся без ошибки, т.к. после них в файле будет находиться предварительно вычисленный байт паритета для них. Он вычисляется по XOR:

       LD HL,START ;адрес начала блока данных в памяти
       LD BC,1544  ;длина блока данных
       LD D,#FF    ;начальное значение байта паритета.
                   ;Оно должно быть 0, но в байт паритета
                   ;обязательно должно входить значение байта
                   ;типа файла. Тип файла = #FF, значит
                   ;0 XOR #FF = #FF
L1     LD A,(HL)   ;берём байт из памяти
       XOR D       ;по XOR вычисляем байт паритета
       LD D,A      ;сохраняем в регистр D
       INC HL      ;следующий адрес в памяти
       DEC BC      ;уменьшаем счётчик
       LD A,B      ;повторяем, пока счётчик не станет = 0
       OR C
       JR NZ,L1

;на выходе в регистре D будет значение байта паритета
;для указанного участка данных

Полученный файл длиной 2169 байт записываем на ленту, и Церикопик с "пищанием" в конце файла готов!

Скачать программы Kopik и Cerikiller:
Программа Cerikiller (в tap-формате)
Программа Kopik (в tap-формате)
Версия от 19.07.2014Программа автоматически распознаёт тип защиты и снимает её. Поддерживаются три версии "Церикопика" - 1990 года, 1991 года и Церикопик с кассет "Набор 1" - "Набор 11".