TR-DOS для начинающих - Окончание.

ZX Review #7-8-9-10
┌──────────────────────────────┐
│                              │
│    TR-DOS ДЛЯ НАЧИНАЮЩИХ     │
│                              │
└──────────────────────────────┘

Music by ZET

(c) В.Сироткин

    ОКОНЧАНИЕ. Начало см. в ZX
 РЕВЮ 1996 NN 1-2, 4-5, 6, 7-8;
 ZX РЕВЮ 1997 NN 1-2, 3-4, 5-6.


 ВОССТАНОВЛЕНИЕ СБОЙНЫХ ДИСКОВ

Сбой в  дисках бывает 3-х типов:
   1. Логический - изменение или
исчезновение данных в секторах.
   2. Системный - разрушение или
изменение системных  разметочных
меток.
   3. Физический - в  результате
механических повреждений.

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

   Вам надо  будет  находить  на
секторах  файлы  БЕЙСИКА  каждой
программы (а в большинстве своем
программы обязательно имеют БЕЙ-
СИК-файл).  Вписывать  в  сектор
каталога его  данные - на  каком
секторе, на какой дорожке он ле-
жит и какова его длина. Из  БЕЙ-
СИК-части узнавать, какие файлы,
куда и какой длины загружаются.

   И тут два варианта:
   1. Если все  файлы  программы
записаны непрерывно, и вам  ста-
нут известны все данные загрузки
последующих файлов, то  вам  по-
везло! Останется лишь  арифмети-
ка - сложение длин файлов, номе-
ров секторов и дорожек и  запись
всего этого в виде заголовков  в
каталог.
   2. Второй вариант - это когда
файлы разбросаны по диску, пере-
межаясь  с  файлами  из   других
программ, или когда не  известны
ни адрес загрузки в ОЗУ, ни дли-
на. Тогда вам можно только посо-
чувствовать. Попытаться  восста-
новить программы в  этом  случае
можно, но это только методом ты-
ка (искать экранные  файлы, тек-
сты сообщений программы и т.д. и
т.п.)
   Если у вас  оказался  сбойный
восьмой сектор, то тут  проще...
Копируете все программы с  этого
диска на другой, а этот диск мо-
жете переразметить.
   Если же наступает полный крах
сектора (а  это  случается, если
изменяется байт по смещению  #Е7
от начала  сектора), и  диск  не
читается ни  системой, ни  копи-
ровщиками, то  тогда  загружаете
сектор через функцию ТРДОС в па-
мять, и восстановив этот прокля-
тый байт и байт "тип  диска"  по
смещению #E3, переписываете сек-
тор из памяти на место, а  потом
поступаете так  же, как  описано
выше.

   Избавиться от  головной  боли
при случайном разрушении  0  до-
рожки вам поможет следующая про-
граммка.
   У этой  программы  две  точки
старта :
   START_S - СОХРАНЕНИЕ ноль до-
рожки диска на 164 дорожку  (164
дорожка размечается автоматичес-
ки  при  сохранении)
   START_L - ВОССТАНОВЛЕНИЕ  ин-
формации со 164 дорожки на НУЛЕ-
ВУЮ дорожку  (с  предварительным
форматированием 0 дорожки).
   Сначала вы обрабатываете свой
диск  СОХРАНЕНИЕМ,  а  в  случае
сбоя по нулевой дорожке или слу-
чайного стирания каталога запус-
каете ВОССТАНОВЛЕНИЕ - и все то,
что было  на  момент  сохранения
диска, восстановится со 164  до-
рожки (82 цилиндра).
   Дорожка, где сохраняется  ин-
формация, недоступна  системе  и
программно недоступна  ни  копи-
ровщикам, ни "ДИСК ДОКТОРам".
   Так что не  беспокойтесь, что
копия системы будет когда-нибудь
затерта.  Единственное, что  мо-
жет произойти, так это  конфликт
с аналогичной системной програм-
мой, которая использует  тот  же
номер дорожки.  Если  же  у  вас
диск изначально размечен  на  86
цилиндров (на  172  дорожки), то
изменив  константу  в   листинге
программы со 164 на 174, вы  бу-
дете сохранять  ноль-дорожку  на
88-ом  цилиндре  (правда,  такая
"дальняя" дорожка уже  считается
ненадежной.
140.
;программа  "спасение - восстановление" системной
;дорожки диска

; в угловых скобках адреса для версии ТРДОС 5.03
; буфер для считываемой информации с диска начинается
; с 40000
; желательно перед запуском программы дать
; CLEAR 39999

 ORG  50000   ; вы можете выбрать другой адрес
              ; работы

START_S  JP   SAVING  ; на спасение
START_L  JP   RESTOR  ; на восстановление

;-- вызов ДОС-подпрограмм
DOS    PUSH IX
       JP   #3D2F
;--
TR0_UP LD   C,02     ; функция ДОС - переход
                     ; на 0 дорожку
       LD   A,0
       CALL #3D13
       LD   A,#3C    ; готовность диска 'A',
                     ; верхняя сторона
       LD   IX,#2EC2  < #2F0C >
       CALL DOS      ; исполнить
       RET
; -- чтение секторов с дорожки
READ_S LD   E,0      ;
       LD   B,16
       LD   HL,40000
       LD   C,05     ; функция ДОС - считать
                     ; 16 секторов в
       CALL #3D13    ; адрес ОЗУ с 40000
       RET

TR82   LD   C,164    ; физическое перемещение
                     ; головок
       LD   IX,#2EFB < #2F45 > 3; на  цилиндр 82
                               3; (164 дорожка)
       CALL DOS                ; исполнить
       RET

;-- форматирование дорожки
FORM   LD   A,82
       LD   (#5CD8),A ; там не должен быть ноль!
       LD   IX,#1FC1  < #1FFD >
       CALL DOS       ; отформатировать дорожку,
                      ; на которой стоим
       RET            ; номер запишем из
                      ; регистра 'E'
;-- запись секторов
SAV_S  LD   C,06      ; функция 'записать сектора
                      ; информацией из ОЗУ'
       LD   B,16
       LD   HL,40000
       LD   E,0
       CALL #3D13
       RET

;-- ПОДПРОГРАММА СОХРАНЕНИЯ
SAVING CALL TR0_UP   ; перейти на 0 дорожку
       LD   D,0
       CALL READ_S   ; прочитать все сектора
                     ; 0 дорожки
       CALL TR82     3; перейти на 82 цилиндр
       LD   E,82     ; номер цилиндра для формата
       CALL FORM     3; отформатировать 164 дорожку
       LD   D,164    ; записать на отформатирован-
                     ; ную дорожку

       CALL SAV_S    ; копию 0 дорожки из памяти
       EI            ; на всякий случай разрешим
                     ; прерывания
       RET           ; выход

;-- ПОДПРОГРАММА ВОССТАНОВЛЕНИЯ
;( на 164 дорожке должна быть копия 0 дорожки этого
;  диска)
RESTOR CALL TR0_UP   ; перейти на 0 дорожку, как на
                     ; начало отсчета
       CALL TR82     3; перейти на 82 цилиндр
       LD   D,164    ; прочитаем все сектора из
                     ; резерва
       CALL READ_S   ; в буфер
       CALL TR0_UP   ; перейдем на 0 дорожку
       LD   E,0      ; и отформатируем ее
       CALL FORM
       LD   D,0      ; запишем на нее все сектора
                     ; из буфера
       CALL SAV_S
       EI
       RET          ; выход
2
           *   *   *

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

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

   Если же сбой физический  (это
и царапины, и осыпание  слоя  на
диске), то восстановить эти мес-
та на диске уже невозможно.
   Но, в любом случае, копируете
сбойный диск  на  другой  (можно
командой ТРДОС  'COPY'), игнори-
руя сообщения системы 'SECTOR nn
ERROR', а затем пробуете восста-
новить те места на  диске, кото-
рые давали сбой.
   Написано  уже  много  хороших
программ восстановления  дисков:
это и 'DCU', и 'ADS', и 'FUCK'.

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

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

   И в заключение давайте  пого-
ворим о том, как  считывать  или
записывать диски  других  форма-
тов от других машин.
   Перво-наперво необходимо точ-
но знать, что  это  за  диск, от
какой  машины.  Как  эта  машина
размечает  диски,  какой   длины
сектора и сколько их на дорожке.
   Например, IBM-диск стандартно
размечен на сектора в  512 байт,
а число секторов на  дорожке  9,
(18 у дисков высокой  плотности,
их даже не  пытайтесь  считывать
на ваших дисководах).
   У CP-M и IS-DOS дисков размер
сектора равен 1024 и  количество
их 5.
   Чтобы прочитать сектора  дис-
ков другого формата, особой изо-
бретательности не надо:  опреде-
ляете буфер подходящих  размеров
в  ОЗУ  и  через  функцию  ТРДОС
"считывание  секторов" - читаете
содержимое в память.
   Естественно,  запускать  IBM-
файлы на СИНКЛЕРЕ вы не сможете,
но  переписать  и   использовать
текстовые  файлы  -  пожалуйста.
Или наоборот, переписывать  тек-
сты, "набитые" на СПЕККИ, в фор-
мат IBM и там их редактировать.

   Самое  трудное - это   разоб-
раться в файловой структуре этих
дисков. Как открывать каталоги и
подкаталоги IBM и IS-DOS дисков,
как проследить  по  диску  файл,
который фрагментирован, да и за-
головки файлов в  этих  системах
не похожи на ТРДОСовские.
   Для тех, кто мечтает заняться
таким направлением  программиро-
вания, я советую набрать книг по
IBM-совместимым   компьютерам  и
внимательно изучить файловую ор-
ганизацию этих машин.


      ПРИЛОЖЕНИЕ К ГЛАВЕ 5

   Всем  тем,  кто  впоследствии
будет программировать порты кон-
троллера  (подавать  команды   в
порт #1F и в порт  #FF)  необхо-
димо помнить, что не всякая пря-
мая команда контроллеру ВГ93 бу-
дет исполняться полностью с дис-
ками, работающими в ТРДОС.  Дело
в том, что формат  дисков  ТРДОС
является упрощенным, а всю прос-
тоту формата компенсирует  прог-
раммное обеспечение ПЗУ ТРДОС.

   Рекомендуемые коды для подачи
в порт #1F:

   Порт  Команд  (этими   кодами
оперирует ТРДОС).

   Запись формата - #F4
   Поиск цилиндра - #1B или #18
   Восстановление - #08 или #0B

   Прерывание     - #D0 или #D8
   Чтение адреса  - #C0
   Чтение сектора - #80
         секторов - #90
   Запись сектора - #A0
         секторов - #B0
   Шаг            - #38
   Шаг вперед     - #58
   Шаг назад      - #78

   Примеры управляющих кодов для
порта  #FF.  Диск  'A',  двойная
плотность, две стороны.

   Готовность, верхняя
                   сторона - #3C
   Готовность, нижняя
                   сторона - #2C
   Сброс, верхняя сторона  - #38

   Для  тех,  у  кого  возникнет
большое желание самому дизассем-
блировать свое ПЗУ с ТРДОС, ниже
приводится карта приблизительно-
го  распределения   пространства
ПЗУ ТРДОС (адреса  в  десятичном
исчислении).
140.
от 0 до 101 - инициализация системы
от 102 до 105 - переход на адрес 10838,
                где помещены подпрограммы
                'МАГИК ФАЙЛ'
от 864 до 939 - название и  версия ТРДОС
от 2048 до 4095 - коды #FF (свободно)
от 4099 до 4119 - текст "ИНТЕРФЕЙС 1"
от 4261 до 4432 - текст системной информа-
                  ции для LIST
от 4445 до 4519 - подпрограмма печати
от 8121 до 8131 - таблицы данных
от 10086 до 10299 - тексты сообщений
от 10380 до 10455 - таблица адресов для
                    вызова подпрограмм
                    функций ТРДОС по ре-
                    гистру 'C'
от 10673 до 10804 - тексты сообщений
от 10838 до 12038 - подпрограммы - 'МАГИ-
                    ЧЕСКИЙ ФАЙЛ'
от 12275 до 12337 - таблицы данных
от 12541 до 12786 - ключевые слова команд
                    ТРДОС
от 12797 до 15360 - код #FF (свободно)
от 15610 до конца - точки входов в ТРДОС и
                    основные подпрограммы
2
   Кроме того, в ПЗУ  ТРДОС  ис-
пользуются  команды   процессора
RST для вызова некоторых  преры-
вающих процедур, а именно:

   КОМАНДА RST #20 - вызов  под-
программы из ПЗУ SOS (после  ко-
манды RST #20 всегда следуют два
байта адреса вызываемой подпрог-
раммы SOS).

   КОМАНДА  RST #18 - напечатать
на экране  сообщение  из  адреса
M(HL), в конце сообщения байт  0
или >#7F

   КОМАНДА RST  #10  -  печатать
символ из регистра 'A'


        Листинги-Таблицы
     некоторых подпрограмм
           ПЗУ ТРДОС

   Значком '=>' помечены те  ад-
реса входов в  подпрограммы, ко-
торые упоминались в главе.

   За основу взяты адреса  ТРДОС
5.01, в угловых скобках (<хххх>)
будут указаны адреса  для  ТРДОС
5.03.

140.
ЗАПИСЬ В ПОРТ #FF (переключение сторон диска)

     #1FAF            LD   A,(#5D16)
     #1FB2            OR   #3C     ;ВЕРХ,ГОТОВНОСТЬ
     #1FB4            LD   (#5D16),A
=>   #1FB7 < #1FF3 >  OUT  (#FF),A
     #1FB9            RET
     #1FBA            LD   A,(#5D16)
     #1FBD            AND  #6F     ;НИЗ,ГОТОВНОСТЬ
     #1FBF            JR   #1FB4
________________________

ЗАПИСЬ В ПОРТ #FF

      #2EC0             OR #3C     ; верхняя
                                   ; сторона, готов
=>    #2EC2 < #2F0C >   OUT (#FF),A ; послать байт
      #2EC4             RET
________________________

ЗАПИСЬ В ПОРТ по (C)

=>   #2A09 < #2A53 >     OUT (C),A
     #2A0B               RET
________________________

СЧИТАТЬ НОМЕР ЦИЛИНДРА С ДИСКА И/ИЛИ ЗАПИСЬ В ПОРТ #3F

=>   #1DFA < #1E36 >  CALL "N_TRACK"; считать с диска
                            ; номер Цилиндра под
                            ; головками
     #1DFD          LD A,H         ; номер из 'H'
                                   ; переместить
=>   #1DFE < #1E3A >  OUT (#3F),A  ; записать в
                                   ; р.ДОРОЖКИ
     #1E00          RET
________________________

ЗАПИСЬ в порт ДОРОЖКИ, порт #3F
                 (с последующей задержкой)

=> #3E8B < #3E95 > OUT  (#3F),A  ; записать в порт
   #3E8D          LD   A,(#5CCD) ; дисковод ГОТОВ?
   #3E90          OR   A
   #3E91          RET  Z         ; да - выйти
   #3E92          XOR  A         ; не готов, так
                                 ; подождем
   #3E93          LD   (#5CCD),A
   #3E96          LD   B,#03     ; цикл ожидания
   #3E98          LD   A,#FF     ; число задержки
   #3E9A          CALL #3DF5     ; п.программа
                                 ; ожидания
   #3E9D          DJNZ #3E98
   #3E9F          RET            ; выйти
_________________________

ЗАПИСЬ в порт КОМАНД, порт #1F; (a также инициали-
зация сист.переменных и/или подача  команды  "пре-
рывание")

#2F46 < #2F90 >  LD   HL,#FFFF ; инициализируем
               LD   (#5CFA),HL ; системные перемен-
                               ; ные
               LD   (#5CFC),HL
               LD   (#5CC8),HL
               LD   (#5CCA),HL
               XOR  A
               LD   (#5D17),A
               LD   (#5D19),A
               LD   (#5D18),A
               LD   (#5D0F),A
               LD   (#5D1F),A
               LD   A,#FF
               OUT  (#FF),A
               LD   (#5C3A),A
               LD   (#5D16),A
               LD   (#5D0C),A
               LD   A,#C9
               LD   (#5CC2),A

   #2F77 < #2FC1 > LD   A,#D0   ; команда
                                ; 'прерывание'
=> #2F79 < #2FC3 > OUT  (#1F),A ; запись команды
                  RET
___________________________

ОПРОС порта #1F (порт состояния) с возможностью
перехвата результата ВТОРЫМ прерыванием по HALT

=> #2D3D < #2D87 > IN   A,(#1F) ; читать состояние
   #2D3F          AND  #7F      ; выделить биты
                                ; ошибок
   #2D41          RET  Z        ; ошибок нет
   #2D42          DEC  D        ; 'D'-1
   #2D43          PUSH HL       ; в стек
   #2D44          PUSH DE       ; в стек
   #2D45          JR   NZ,#2D31 ; если 'D'<>0 -
                                ; переход
   #2D47          HALT          ; Ждать прихода
                                ; импульса
                                ; прерывания
__________________________


ОПРОС порта #1F (состояние) с выводом на экран сообщений
(в ПЗУ ТРДОС эта часть вызывается после исполнения
команд "чтение-запись сектора")

=> #3F28 < #3F32 > EI        ; рарешить прерывания
                IN   A,(#1F) ; прочитать состояние
                LD   B,A     ; сохранить для работы
                AND  #7F     ; выделить ошибки
                RET  Z       ; ошибки нет - выйти
                LD   HL,#298E
                AND  #40      ; диск заклеен ??
                JR   NZ,#3F41 ; да - перейти
                LD   A,B
                AND  #04      ; потеря данных ??
                JR   Z,#3F96  ; да - перейти
                DEC  D        ; 'D'-1
                JR   NZ,#3F0B ; перейти выше
                              ; если 'D'<>0
                LD   HL,#2998
   #3F41        LD   A,#D0    ; прервать операцию
                OUT  (#1F),A
                LD   A,B
                AND  #01      ; запрос данных ??
                JP   NZ,#3EDD ; да - перейти
__________________________

ПОИСК ДОРОЖКИ.
           ПОИСК ЦИЛИНДРА.
                     ЗАПИСЬ КОММАНД 1-го ТИПА.
                               ВОСТАНОВЛЕНИЕ И Т.Д.
;---------  поиск  ДОРОЖКИ  -----
=> #2EF0 < #2F3A >    LD   A,#3C   ;верхняя сторона,
                                   ;готовность
   #2EFA         OUT  (#FF),A
   #2EF4         LD   A,(#5C00) ; если ДОРОЖКА
                                ; то A=0
   #2EF7         AND  #08      ; это будет номер
                               ; ЦИЛИНДРА
                               ; или ДОРОЖКИ ??
   #2EF          JR   NZ,#2F05 ; переход если номер
                               ;          ЦИЛИНДРА
   #2EFB         LD   A,C      ; номер ДОРОЖКИ
   #2EFC         OR   A
   #2EFD         RRA           ; переведем в номер
                               ; ЦИЛИНДРА
   #2EFE         LD   C,A      ; и сторону диска
   #2EFF         JR   NC,#2F05 ; переход, если
                               ; четный ЦИЛИНДР
   #2F01         LD   A,#2C    ; это нечетный ЦИ-
                               ; ЛИНДР - НИЗ
;----------- поиск ЦИЛИНДРА --------
=> #2F03 < #2F4D >  OUT  (#FF),A ; выбрать сторону
                                 ; диска
   #2F05            LD   A,C     ; номер ЦИЛИНДРА
                                 ; на поиск
   #2F06           OUT  (#7F),A ; в регистр данных
   #2F08           CALL #3DF3   ; п.программа
                                ; задержки
   #2F0B           LD   A,#1B   ; поиск с опущенной
                                ; головкой
;-- исполнить КОМАНДУ 1-го типа (диск крутится,
;--         головки  прижаты к диску.         ----
=> #2F0D < #2F57 >  OUT  (#1F),A  ; на исполнение
                                  ; КОМАНДУ
   #2F0F            IN   A,(#FF)  ; готовность.
   #2F11            AND  #80      ; команда
                                  ; выполнена?
   #2F13            JR   Z,#2F0F  ; нет - ждем
   #2F15            PUSH BC       ; сохраним
   #2F16            CALL #3DF3    ; п.программа
                                  ; задержки
   #2F19            POP  BC       ; извлечем
   #2F1A            RET           ; вернуться

;---------- подать команду ВОСТАНОВЛЕНИЕ ----
=> #2F1B < #2F65 >  LD   A,#0B    ; выход на 0
                                  ; дорожку
   #2F1D            JR   #2F0D    ; исполнить
______________________________________________

         ЗАПИСЬ МАССИВА в ПОРТ  (C).
(сюда обращается подпрограмма 'запись сектора')

; - предварительный опрос ГОТОВНОСТИ и/или СТРОБА данных -
#3FB0 < #3FBA >  LD   B,#04   ; цикл ожидания
#3FB2 < #3FBC >  IN   A,(#FF) ; опросить на готов-
                              ; ность
#3FB4            AND  #C0     ; есть Готовность
                              ; или Строб?
#3FB6            JR   NZ,#3FC7; есть - переход на
                              ; запись
#3FB8            INC  DE      ; нет, тогда главный
                              ; цикл ожидания
#3FB9            LD   A,E     ; в 'E' и 'D' число
                              ; повтора
#3FBA            OR   D
#3FBB            JR   NZ,#3FB2 ; циклу конец ?
#3FBD            DJNZ #3FB2   ;  ожиданию конец ?
#3FBF            RET          ; выйти, операция
                              ; не пошла!
;         - запись массива в порт (C) -
=> #3FC0 < #3FCA > IN   A,(#FF)  ; опросить на
                                 ; готовность
   #3FC2           AND  #C0      ; есть Готовность
                                 ;  или Строб ?
   #3FC4           JR   Z,#3FC0  ; нет - ждать
   #3FC6           RET  M        ;ВЫПОЛНЕНО ВСЕ -
                                            ВЫЙТИ
   #3FC7           OUTI          ; есть строб -
                                 ; послать байт
                                 ; в порт из M(HL)
   #3FC9           JR   #3FC0    ;вернуться за
                                 ;следующим байтом
____________________________

ЧТЕНИЕ МАССИВА ИЗ ПОРТА (C)
(сюда обращается подпрограмма 'ЧТЕНИЕ сектора')

; - предварительный опрос ГОТОВНОСТИ и/или СТРОБА данных -
#3FCB < #3FD5 >  LD   B,#04   ; цикл ожидания

140.
#3FCD < #3FD7 >  IN   A,(#FF) ; опросить на готов-
                               ; ность
#3FCF            AND  #C0      ; ГОТОВНОСТЬ? СТРОБ?
#3FD1            JR   NZ,#3FE2 ; да - переход
#3FD3            INC  DE       ; ждем определенное
#3FD4            LD   A,E      ; время
#3FD5            OR   D
#3FD6            JR   NZ,#3FCD ; время ожидания
                                 ; вышло?
#3FD8              DJNZ #3FCD    ; цикл кончился?
#3FDA              RET           ; команда не пошла,
                                 ; выйти
;     - чтение массива из порта (C) -
=> #3FDB < #3FE5 >     IN   A,(#FF) ; опросить
                                    ; на готовность
   #3FDD               AND  #C0     ; СТРОБ?
                                    ;   ГОТОВНОСТЬ?
   #3FDF               JR   Z,#3FDB ; нет - ждать
   #3FE1               RET  M       ; ВЫПОЛНЕНО ВСЕ
                                    ; - выйти
   #3FE2               INI          ; считать байт
                                    ; из порта
                                    ; в M(HL)
   #3FE4               JR   #3FDB   ; вернуться
                                    ; за следующим
                                    ; байтом
__________________________

Подпрограмма ЧТЕНИЕ-ЗАПИСЬ СЕКТОРА (первая)

=> #3F00 < #3F0A > LD   A,#A0     ; занести код
                                  ; 'ЗАПИСЬ'
   #3F02           JR   #3F06     ; переход на
                                  ; продолжение

=> #3F04 < #3F0E > LD   A,#80     ; занести код
                                    'ЧТЕНИЕ'
   #3F06           LD   (#5CFE),A ; СОХРАНИТЬ
   #3F09           LD   D,#0A     ; число попыток
   #3F0B           PUSH DE        ; спрятать
   #3F0C           DI             ; запретим
                                  ; прерывания
   #3F0D           LD   A,(#5CFF) ; ЛОГИЧЕСКИЙ
                                  ; НОМЕР СЕКТОРА
   #3F10           INC  A         ; переведем в
                                  ; физический
   #3F11           OUT  (#5F),A   ; занесем в
                                  ; РЕГ.СЕКТОРА
   #3F13           LD   HL,(#5D00); адрес буфера
   #3F16           LD   C,#7F     ; порт ДАННЫХ
   #3F18           LD   A,(#5CFE) ; достать КОМАНДУ
   #3F1B           OUT  (#1F),A   ; подадим в порт
   #3F1D           CP   #A0       ; это ЗАПИСЬ?
   #3F1F           PUSH AF        ; спрячем  флаг
                                  ; операции
   #3F20           CALL Z,#3FB0   ; ЗАПИСЬ - тогда
                                  ; запишем
   #3F23           POP  AF        ; достанем флаг
                                  ; операции
   #3F24           CALL NZ,#3FCB  ; А это ЧТЕНИЕ
                                  ; - прочтем

                   и так далее...
____________________________________

Подпрограмма СЧИТЫВАНИЕ СЕКТОРА (вторая).

=> #2ED1 < #2F1B > LD   A,E     ; логический
                                ; НОМЕР СЕКТОРА
   #2ED2           INC  A       ; переведем в
                                ; физический
   #2ED3           OUT  (#5F),A ; занесем в порт
                                ; СЕКТОРА
   #2ED5           PUSH HL      ; адрес БУФЕРА -
                                ; сохраним
   #2ED6           LD   D,#14   ; число попыток -
   #2ED8           PUSH DE      ; сохраним
   #2ED9           DI           ;
   #2EDA           LD   C,#7F   ; порт данных
   #2EDC           LD   A,#80   ; команда
                                ; ЧТЕНИЕ СЕКТОРА
   #2EDE           OUT  (#1F),A ; подадим команду
   #2EE0           CALL #3FDB   ; считаем СЕКТОР
   #2EE3           POP  DE      ; вернем число
                                ; попыток
   #2EE4           POP  HL      ; адрес БУФЕРА
   #2EE5           IN   A,(#1F) ; все нормально?
   #2EE7           AND  #7F     ; ошибок нет?
   #2EE9           RET  Z       ; выход, если нет
                                ; ошибок
   #2EEA           DEC  D       ; число попыток
                                ; сократим
   #2EEB           PUSH HL      ; опять спрячем
   #2EEC           PUSH DE
   #2EED           JR   NZ,#2ED9 ; попытки не кон-
                                 ; чились...
                                 ; читаем еще раз
   #2EEF           HALT          ; Все.
                                 ; Попытки кончились
____________________________________

Подпрограмма ЗАПИСИ СЕКТОРА (вторая)

=> #2D29 < #2D73 > LD   A,E     ; логический
                                ; НОМЕР СЕКТОРА
   #2D2A           INC  A       ; переведем в
                                ; физический
   #2D2B           OUT  (#5F),A ; занесем в порт
                                ; СЕКТОРА
   #2D2D           PUSH HL      ; адрес БУФЕРА -
                                ; сохраним
   #2D2E           LD   D,#14   ; число попыток
   #2D30           PUSH DE      ; сохраним
   #2D31           DI           ;
   #2D32           LD   C,#7F   ; порт данных
   #2D34           LD   A,#A0   ; команда
                                ; ЗАПИСЬ СЕКТОРА
   #2D36           OUT  (#1F),A ; подадим команду
   #2D38           CALL #3FC0   ; пишем   СЕКТОР
   #2D3B           POP  DE      ; вернем число по-
                                ;  пыток
   #2D3C           POP  HL      ; адрес БУФЕРА
   #2D3D           IN   A,(#1F) ; все нормально?
   #2D3F           AND  #7F     ; ошибок нет?
   #2D41           RET  Z       ; выход - если
                                ; ошибок нет
   #2D42           DEC  D       ; чило попыток
                                ; сократим
   #2D43           PUSH HL      ; опять спрячем
   #2D44           PUSH DE
   #2D45           JR   NZ,#2D31 ; попытки не
                                 ; кончились...
                                 ; будем писать еще
   #2D47           HALT          ; Все. Попытки
                                 ; кончились

___________________________________

Форматирование одной дорожки

=>#1FC1 < #1FFD >  DI          ;запретим прерывания
  #1FC2            LD A,#F4    ;команда ЗАПИСЬ
                               ; ВСЕЙ ДОРОЖКИ
  #1FC4            OUT (#1F),A ; дадим команду
  #1FC6            LD HL,#1F7D ; адрес данных,
                               ; где расположены
                               ; номера секторов
;       -- начало записи заданного формата --
=>#1FC9 < #2005 >  LD C,#7F    ; порт данных
  #1FCB            LD B,#0A    ; число записываемых
                               ; байтов
  #1FCD            LD D,4E     ; байт для записи
  #1FCF            CALL 2075   ; записать байты на
                               ; дорожку

   ... и так далее, пока не запишем весь массив ...
__________________________________

Циклическая ЗАПИСЬ в порт (C) байтов количеством
из 'B'. (используется подпрограммой ФОРМАТИРОВАНИЕ)

=>#2075 < #20B1 >  IN   A,(#FF) ; читаем готовность
  #2077            AND  #C0     ; КОНЕЦ?,
                                ; СТРОБ БАЙТА?
  #2079            JR   Z,#2075 ; нет - ждем
  #207B            RET  M       ; да, команда
                                ; исполнена - выход
  #207C            OUT  (C),D   ; если строб, то
                                ; байт в порт
  #207E            DJNZ #2075   ; все байты?
                                ; нет - вернемся
  #2080            RET          ; выйдем, если все
_____________________________


       КРАТКАЯ ТАБЛИЦА СООТВЕТСТВИЯ АДРЕСОВ ПОДПРОГРАММ

    ┌────────────┬────────────┬───────────────────────────┐
    │ ТРДОС 5.01 │ ТРДОС 5.03 │ НАЗНАЧЕНИЕ                │
    ├────────────┼────────────┼───────────────────────────┤
    │   #1DFE    │   #1E3A    │  OUT (#3F),а              │
    │   #1FB7    │   #1FF3    │  OUT (#FF),а              │
    │   #1DFA    │   #1E36    │  определить цилиндр       │
    │            │            │       под головками       │
    │   #1FC1    │   #1FFD    │  форматирование           │
    │   #1FC9    │   #2005    │  форматирование           │
    │   #2EC2    │   #2F0C    │  OUT (#FF),а              │
    │   #2A09    │   #2A53    │  OUT (C),а                │
    │   #2D3D    │   #2D87    │  IN a,(#1F), and #7F      │
    │   #2F1B    │   #2F65    │  восстановление           │
    │   #2F03    │   #2F4D    │  поиск ЦИЛИНДРА           │
    │   #2EF0    │   #2F3A    │  поиск ДОРОЖКИ            │
    │   #2ED1    │   #2F1B    │  чтение сектора           │
    │   #2F0D    │   #2F57    │  подача команд 1-го типа  │
    │   #2075    │   #20B1    │  циклическая запись       │
    │            │            │            OUT(C),D       │
    │   #28A5    │   #2FEB    │  LDIR, RET                │
    │   #2EFB    │   #2F45    │  поиск ДОРОЖКИ            │
    │   #2F46    │   #2F90    │  инициализировать SYS     │
    │            │            │                 TRDOS     │
    │   #2D29    │   #2D73    │  запись сектора           │
    │   #3ED5    │   #3EDF    │  OUT (#1F),а              │
    │   #3E8B    │   #3E95    │  OUT (#3F),а              │
    │   #3FDB    │   #3FE5    │  чтение блока ( INI )     │
    │   #3FC0    │   #3FCA    │  запись блока (OUTI )     │
    │   #3F28    │   #3F32    │  IN А,(#1F) + сообщения   │
    │   #3DAB    │   #3DB5    │  IN A,(#1F);  AND 02      │
    │   #3E30    │   #3E3A    │  IN A,(#1F);  AND 04      │
    │   #3E59    │   #3E63    │  поиск дорожки            │
    │   #3F00    │   #3F0A    │  запись сектора           │
    │   #3F04    │   #3F0E    │  чтение сектора           │
    │   #3DF3    │   #3DFE    │  задержка                 │
    │   #3E96    │   #3EA0    │  задержка                 │
    │   #3FB0    │   #3FBA    │  запись блока (OUTI )     │
    │   #3FCB    │   #3FD5    │  чтение блока ( INI )     │
    └────────────┴────────────┴───────────────────────────┘
2
           *   *   *


            ГЛАВА 6

      'МАГИЧЕСКИЕ' ФАЙЛЫ,
      или прерывание  NMI

   В  фирменных  моделях   платы
ТРДОС (БЕТА-ДИСКа) предусмотрена
специальная кнопка, т.н.  'MAGIC
BUTTON', предназначенная (по за-
думке  автора)  для  копирования
тех магнитофонных  программ, ко-
торые невозможно было  адаптиро-
вать к диску  обычными  методами
ТРДОС, т.е. программ, занимающих
все пространство ОЗУ компьютера,
затирающих системную область ОЗУ
или  имеющих  мощно   защищенный
загрузчик (программа вводится  с
магнитофона, запускается, и  при
нажатии  в  любой  момент   этой
кнопки вся область ОЗУ компьюте-
ра единым файлом сбрасывается на
диск.  После  этого   прерванная
программа снова начинает  работу
как ни в чем не бывало. На диске
же образовывается файл с  именем
"@" и типом <C>, запускать кото-
рый можно  командой  ТРДОС  GOTO
"filename" CODE. Программа  заг-
ружается и  запускается  с  того
самого места, где она  тормозну-
лась при нажатии МАГИКА).
   Что же такое эта  МАГИК-КНОП-
КА?
   У процессора Z-80 есть специ-
альный вход, т.н. 'НЕМАСКИРУЕМОЕ
ПРЕРЫВАНИЕ, или NMI', при  пода-
че на который  логического  НУЛЯ
процессор бросает свою работу  и
переходит на адрес #0066, как на
подпрограмму.

   В отличие от первого  и  вто-
рого прерываний, прерывание  NMI
невозможно запретить, и  процес-
сор в любом случае при возникно-
вении сигнала NMI  будет  испол-
нять  подпрограмму  по    адресу
#0066. Вот на этот самый вход  и
задействована та самая 'магичес-
кая кнопка' ТРДОС.
   При нажатии на  нее  на  вход
NMI процессора подается НОЛЬ.  В
этот же самый момент этот же им-
пульс переключает банки  РПЗУ  и
включает  в  адресное  простран-
ство ПЗУ ТРДОС.

   Схемотехника выборки МАГИЧЕС-
КОЙ КНОПКИ построена таким обра-
зом, что  формирование  импульса
NMI происходит в диапазоне рабо-
чих адресов процессора от  #4000
до #FFFF (это было сделано  спе-
циально, чтобы  работу  подпрог-
рамм ПЗУ, как SOS, так и  ТРДОС,
нельзя было прервать).
   Многое уже писалось  об  этой
кнопке...
   Очень многие ругают ее  почем
зря, называют обидными прозвища-
ми типа "обезьяна" и  др...  Но,
если все  же  учесть, что  часть
хороших  программ, которые  дош-
ли до нас  из-за  бугра, вскрыты
именно таким методом, то, может,
не надо ее так  ругать, а  стоит
отдать ей должное?
   Да, недостатков у этого  спо-
соба адаптации  программ  много.
Это и то, что в стековую область
программы   заносится   огромный
массив данных, и то, что наруша-
ются некоторые ячейки ОЗУ в сис-
темной области ТРДОС и  портится
экран, и т.д., и т.п.
   Но  вспомним, что  когда  был
выпущен контроллер ТРДОС, еще не
было таких программ, которые  до
последнего байта занимали бы всю
область ОЗУ компьютера. Это сей-
час программы для  СИНКЛЕРА  пи-
шут на мощных 16-и или 32-х раз-
рядных машинах, а тогда...
   Если  современную   программу
сбрасывать на диск КНОПКОЙ, то в
половине случаев она  безнадежно
портится,  а  если  в  программе
стоит  защита от   МАГИКА, то  и
диск  иногда  приходится  заново
форматировать.
   Но все же КНОПКА иногда помо-
гает, и даже  здорово  помогает!
Это и вскрытие  сильно  защищен-
ных магнитофонных загрузчиков, и
анализ сложных программ во время
их работы, и многое другое.
   Да и вообще, не в кнопке  де-
ло, а в програмном  обеспечении,
облсуживающем  эту  кнопку.  Как
было сказано, при нажатии  кноп-
ки процессор переходит на  адрес
#0066 ПЗУ ТРДОС.  В  этом  месте
стоит команда процессора 'JP ад-
рес'. Для ТРДОС 5.01  это  адрес
#2A0C, а для 5.03 и др - #2A56.
   Концепция подпрограммы МАГИКА
проста:
1. Сохранить значения  всех  ре-
   гистров процессора.
2. Сбросить в файл с именем  '@'
   область ОЗУ с #4000 до #FFFF.
3. Восстановить  значения   всех
   регистров и вернуться в прог-
   рамму.

   Ниже дан  листинг  начального
куска подпрограммы 'MAGIC SAVE'.
140.
; начало для ТРДОС 5.01 = #2A0C, для 5.03 = #2A56

        PUSH    AF   ; сохраним основной набор
                     ; регистров
        PUSH    BC
        PUSH    DE
        PUSH    HL
        PUSH    IX   ; а также индексные регистры
        PUSH    IY
        EXX          ; сохраним второй набор
                     ; регистров
        PUSH    BC
        PUSH    DE
        PUSH    HL
        EX      AF,AF'
        PUSH    AF
        LD      A,I ; регистр прерываний
        PUSH    AF
        LD      A,R ; регистр обновления
        PUSH    AF
        LD      HL,#0000
        ADD     HL,SP ; сохраним последним в стеке
                      ; 'указатель стека',
                      ; а последнее значение указа-
                      ; теля стека
                      ; потом выставим в заголовок
                      ; файла, как
        PUSH    HL    ; СТАРТОВЫЙ АДРЕС

        LD      A,#3C   ; далее идет инициализация
                        ; контроллера
        OUT     (#FF),A
        ............
2
   Дальше  программа   сохраняет
часть области экрана в 9, 10, 11
и 12-ом (!) секторах НУЛЕВОЙ до-
рожки (так  вот  куда  требуются
пустующие сектора  на  системной
дорожке) и переписывает на экран
служебную информацию  (оттуда  и
полосы на экране во время записи
МАГИКА).
   Сохраненная экранная информа-
ция затем переписывается в МАГИК
файл. Таким образом, реально эк-
ран программы не портится.
   После записи  файла  подпрог-
рамма   возвращает    управление
прерванной  программе, предвари-
тельно вернув из стека все  зна-
чения регистров  микропроцессора
и экранные куски.
   На диске же остается  файл  с
именем '@', тип 'CODE' и с объе-
мом, равным #C0 (192) сектора.
   Стартовый адрес  этого  файла
указывает  на  адрес  дна  СТЕКА
программы, а длина  программы  в
байтах  может  быть  любой, и  в
этом случае  не  играет  никакой
роли. Имя файлу потом можно  бу-
дет дать любое, но с одной  ого-
воркой.
   Если  сбрасываемая  программа
работала  во  ВТОРОМ  прерывании
процессора,  то  имя   программы
должно начинаться с символа '$'.
По этому символу  во  время  за-
пуска магик-файла ТРДОС  распоз-
нает,  какое  именно  прерывание
установить в  загружаемой  прог-
рамме.

   Запуск  МАГИК-файла  произво-
дится из ТРДОС командой

   GOTO "filename" CODE

   Кстати,  командa  'GOTO'   не
различает принадлежность  запус-
каемого файла к 'МАГИК' типу. Вы
можете  дать  команду  'GOTO'  и
обычному файлу и посмотреть, что
получится...  Так что если у вас
файлов 'МАГИК' много, то, навер-
ное, стоит завести для  них  от-
дельный диск, чтобы не  происхо-
дило  недоразумений   (программы
'БУТЕРы' распознают МАГИК файл в
каталоге по величине #C0, <объем
занимаемых  секторов>, и  только
такие файлы  запускают, как  МА-
ГИК).
   Запуск МАГИК-файла происходит
в следующем порядке: сначала за-
гружаются 27 секторов  экрана, а
затем  все  остальное, используя
ОЗУ экрана для загрузки.
   После  того,  как  весь  файл
считан, подпрограмма в  ПЗУ  бе-
рет  из  заголовка  файла  число
'СТАРТОВЫЙ АДРЕС', вводит его  в
регистр 'SP' и командами POP на-
чинает  заполнять  все  регистры
данными из стека в том  порядке,
в каком они сохранялись при соз-
дании МАГИК файла.
   В этот же самый момент анали-
зируется  имя  файла  на  символ
'$', и если он  присутствует  на
1-ом  месте  имени, подпрограмма
выставляет IM2.
   И в завершение  всего, коман-
дой 'RET' производит запуск.
   Определенный  интерес   пред-
ставляет способ, которым подпро-
грамма ТРДОС  определяет, РАЗРЕ-
ШЕНЫ или ЗАПРЕЩЕНЫ были прерыва-
ния на момент остановки програм-
мы. Делается это так:

; определение   разрешены или запрещены прерывания

 POP AF        ; вернем значение регистра
               ; прерываний,
               ; а в регистре флагов значение
               ; триггера IFF2 процессора
 LD I,A
 DI            ; для начала запретим прерывания
 JP PO, addr   ; проверим триггер IFF2 и если
               ; IFF2=1 (ФЛАГ ЧЕТНОСТИ),
               ; то дать команду ПРЕРЫВАНИЯ
               ; РАЗРЕШИТЬ.
               ; А если IFF2=0 (НЕЧЕТНОСТЬ), то
               ; ПРЕРЫВАНИЯ ЗАПРЕТИТЬ.
               . . . . . .

(кто не  имеет  представления  о
том, что такое  триггер  IFF2  и
IFF1 микропроцессора, тот  может
прочитать  об  этом   в    книге
А.ЛАРЧЕНКО  и  Н.РОДИОНОВА   "ZX
SPECTRUM  для  программистов   и
пользователей").
   Можно было бы пойти дальше и,
анализируя  содержимое  регистра
"I" микропроцессора, определять,
какое именно прерывание  было  в
программе  на  момент    нажатия
МАГИКА  (изначально  регистр 'I'
равен #3F, и если это не так, то
в 90% случаев на этот  момент  в
программе  установлено  прерыва-
ние ДВА).
   Но этот способ не  дает  100%
достоверности, и конечное  реше-
ние о типе  прерывания  остается
за нажимателем МАГИК-КНОПКИ.
   Большинство современных игро-
вых программ используют прерыва-
ние ДВА, и  если  после  запуска
'свежеиспеченного'   МАГИК-файла
(без символа '$' в имени) что-то
идет не так, или вовсе не идет -
значит,  ставьте  впереди  имени
файла значок  '$'  и  запускайте
снова.
   Если и после этого  программа
на захочет работать, значит ваша
МАГИК-кнопка запортила стек этой
программы или же  ячейки  ОЗУ  с
адреса #5C00 и  далее.  Попытай-
тесь сбросить программу  в  дру-
гой момент времени ее работы или
тогда, когда программа находится
в меню.
   В процессе своей работы прог-
рамма МАГИК безвозвратно  портит
несколько ячеек  ОЗУ  в  области
системных переменных ТРДОС, нап-
ример ячейки  с  адресом  #5C00,
#5C01 и т.д. Так что  защита  от
МАГИК-сброса в  программе  может
строиться именно на опросе  зна-
чений этих ячеек.

   Существует уже множество про-
грамм-утилит, которые предназна-
чены облегчить работу  с  МАГИК-
файлами. Это  и  пакет  программ
"@-CRACKER",  который  позволяет
не только  просматривать  МАГИК-
файл,  но  и  выводить  значения
всех сохраненных регистров и со-
держимое стека на  экран, редак-
тировать МАГИК-файл, а также де-
лить его на 3 файла с  автомати-
ческой записью на  диск  БЕЙСИК-
загрузчика, который загружает  и
запускает разделенные части. Это
и программа  "MFC v1.4", которая
компрессирует МАГИК-файл и запи-
сывает его единым блоком с  БЕЙ-
СИК-загрузчиком на диск. И  мно-
гое, многое другое.
   Можно долго и нудно спорить и
говорить о вреде  МАГИК  кнопки.
Атор этой книги склоняется к то-
му, что  в  большинстве  случаев
МАГИК - это вред. Вред не только
программе, но и самому пользова-
телю, который нажимает эту кноп-
ку...
   Но, как говорится:"Если нель-
зя, а очень хочется - то можно".
   Можно было бы на этом месте и
закрыть эту тему, но  у  некото-
рых читателей возникнет  вопрос:
"А можно ли  сделать  так, чтобы
МАГИК не портил программы?" Мож-
но. Для этого вам  придется  пе-
репрограммировать ПЗУ ТРДОС дру-
гой подпрограммой, которая  дол-
жна учитывать все недостатки су-
ществующей. А именно:
   - В родном стеке сбрасываемой
программы должно сохраняться од-
но-два  значения - это  регистры
'SP' и 'PC'.
   - Рабочий стек  для  сохране-
ния  всех  остальных   регистров
должен быть переопределен на эк-
ран  (в ущерб картинке), или  же
(что предпочтительнее) в ТЕНЕВОМ
ОЗУ.
   - Не должны затрагиваться ни-
какие ячейки ОЗУ, кроме  как  на
экране или в теневом  ОЗУ.  Хотя
это тоже не выход!

   Можно пойти  другим  путем  и
запрограммировать  ПЗУ  SOS  для
сохранения МАГИКА на  магнитофон
тремя-четырьмя кусками (для этой
цели подойдет и  ТЕНЕВОЕ  ОЗУ, у
кого оно есть).

   Единственное, что  необходимо
сделать  для  этого  -  обрезать
проводник, идущий от схемы МАГИК
КНОПКИ  к  узлу,  переключающему
микросхемы ПЗУ, чтобы при  нажа-
тии на кнопку у вас осталась ак-
тивной ПЗУ SOS.  И, конечно, на-
писать  программу-загрузчик, ко-
торый загружал бы потом эти кус-
ки и запускал программу (кстати,
имено  вот  таким 'магнитофонным
МАГИКОМ' и вскрывались изначаль-
но сильно защищеные  'фирменные'
программы на магнитофонных  лен-
тах.
   В некоторых современных  (на-
шенских) моделях контроллера Ма-
гик-кнопка подвязана на вызов из
дополнительного  ПЗУ   программы
'монитор-отладчик', который поз-
воляет оперативно  что-то  изме-
нять в остановленной программе и
записывать ее на  диск.  Вот  уж
воистину: "Голь на выдумки  хит-
ра!".
   А на некоторых  моделях  кон-
троллера Магик-кнопка вообще от-
сутствует. Ну что же, тоже "муд-
рое" решение.  Не  нравится  она
некоторым  разработчикам, и  все
тут.

           *   *   *