Обмен опытом - Как сделать дисковую версию программы ?..

Adventurer #06
                (C) RAY
    (C) Музыка PHANTOM LORD/ACCEPT CORP.
╔───────────────────────────────────────╗
│ ──── КАК СДЕЛАТЬ ДИСКОВУЮ ВЕРСИЮ ──── │
╚───────────────────────────────────────╝

     Вам, безусловно, знакома такая ситу-
ация:  при  загрузке  некоторой программы
(неважно, игра или системка) выдается со-
общение "Disked by ...", a затем, при по-
пытке,  скажем,  загрузить что-то, вы ви-
дите  помаргивающий  бордер.  Какие слова
будут сказаны в адрес того самого "диске-
тизатора",  надеюсь, вы сами догадаетесь.
В  общем,  я  считаю так : можно писать в
загрузчике,  что  программа дискетирована
только тогда, когда сделана ПОЛНАЯ ДИСКО-
ВАЯ версия. Если же просто приляпан  дис-

ковый  loader,  то уж лучше ничего не пи-
сать  или  отметить,  что-де не полностью
прога дискетирована. Правда последний ва-
риант мало кого может устроить, поэтому я
поделюсь  с  вами некоторой информацией о
том, как сделать полную  дисковую  версию
программы.
     Итак, у вас есть некий кодовый блок,
в котором надо изменить загрузку/выгрузку
каких-либо данных с ленты на диск. Снача-
ла  смотрим  адрес загрузки блока. Обычно
на единицу меньше этого адреса устанавли-
вается  стек.  (ВНИМАНИЕ : убедитесь, что
после запуска программа не изменяет свое-
го  положения  в  памяти, в частности, не
перебрасывает ли себя ниже этого адреса!)
Обычно  минимальный  адрес бывает  24500.
Если  у  блока  такой адрес или больший -
все О.к., 90%, что проблем  с  переделкой
под диск не возникнет.  Если же нет - си-
туация чуть более сложная и ее рассмотрим
позже (в этой статье рассматривается дис-
кетизация стандартными методами TR-DOS). 

     Перед тем, как искать точки входа  в
ленточные процедуры загрузки/записи,  по-
лезно просмотреть всю память  на  предмет
нахождения незанятого  программой  места.
Как  показала  практика,  в  любой  проге
всегда найдется байт 150..200  свободного
места (можно кусками, байт по 45). А это-
го с лихвой хватит для наших целей.  Если
же нет - можно использовать место,  кото-
рое занимает BASIC-загрузчик.
     Теперь начинаем искать  точки входа.
Тут тоже может быть два варианта:
1) программа использует собственные  про-
   цедуры загрузки/записи;
2) программа  пользуется  процедурами  из
ПЗУ.
     В первом случае искать  точки  входа
труднее, но зато не нужно будет потом на-
ходить  свободное место в программе, т.к.
новые  подпрограммы  как раз уместятся на
месте старых.
     Во  втором  случае  можно попытаться
найти вызовы ПЗУ.

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

       мнемоника       байты для поиска
─────────────────────────────────────────
SAVE:  CALL 1218           CD C2 04
       CALL 1222           CD C6 04

LOAD:  CALL 1366           CD 56 05
       CALL 2050           CD 02 08
       CALL 1378           CD 62 05
       CALL 1889           CD 61 07
─────────────────────────────────────────
Кроме этого  встречаются  и  экзотические
способы:
       CALL #0605
       где SAVE или LOAD определяется
       по ячейке 23688 = 0 - SAVE
                         1 - LOAD 

     Еще более интересный способ:
SAVE: CALL 2416,
где
      IX - первые 17 байт - заголовок,
затем данные;
      длина задается так:

      LD DE, длина
      LD (IX+11),D
      LD (IX+12),E .

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

      код     мнемоника
      ──────────────────
      08      EX  AF,AF'
      13      INC DE
      DD 2B   DEC IX
      F3      DI 

Это кусочек из процедуры, расположенной в
ПЗУ по адресу 1222 (#04C6). Если вы нашли
нечто подобное, значит это начало (почти)
подпрограммы записи  на  ленту.  Осталось
только найти, где осуществляется ее вызов
и  определить параметры отгружаемого бло-
ка.
     Если  ничего  из вышеприведенного не
помогло - попробуйте  поискать сообщения,
которые  программа  выдает  при  работе с
лентой.  Обычно  эти  сообщения находятся
рядом с соответствующими процедурами.

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

1) в один и тот же файл;
2) в одно и то же место на диске или
   "в себя";
3) в разные  файлы  (наиболее  предпочти-
   тельно).

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

     Рассмотрим эти случаи...

1. В один Файл:
───────────────
SAVE
       CALL   SET_NAME   ; готовим сист.
                         ; переменные
       LD     C,10       ; ищем файл
       CALL   15635
       LD     A,C
       BIT    7,C
       JR     NZ,SAV_BL  ; его нет

       LD     DE,(23878) ; нашли, rewrite
       LD     BC,#nn06
       LD     HL,адрес
       CALL   15635
       RET  

SAV_BL                   ; создаем новый
       LD     HL,адрес
       LD     DE,размер
       LD     C,11
       CALL   15635
       RET

где    адрес  - адрес начала отгружаемого
                блока;
       размер - его размер в байтах;
       nn     = его размер в секторах.

LOAD
       CALL   SET_NAME   ; готовим сист.
                         ; переменные
       LD     C,10       ; ищем файл
       CALL   15635
       LD     A,C
       BIT    7,C
       JR     Z,LD_1
; Сообщение "файл не найден" или...
       RET  

LD_1                     ; грузим...
       LD     A,C
       LD     C,8
       CALL   15635
       XOR    A
       LD     (23801),A
       LD     (23824),A
       LD     C,14
       CALL   15635
       RET
; Подпрограмма установки сист.переменных
SET_NAME
       LD     HL,NAME    ; перекидываем
       LD     DE,23773   ; имя файла
       LD     BC,8
       LDIR
       LD     A,"C"      ; тип файла м.б.
       LD     (23781),A  ; любой, кроме
                         ; "#","D","B"
       LD     A,9        ; поиск файла
       LD     (23814),A  ; по 9 символам
       RET

NAME   DEFM   "filename" 

2. В одно и то же место ("в себя"):
───────────────────────────────────
SAVE
       LD     HL,адрес
       LD     DE,(T_S)
       LD     BC,#nn06
       CALL   15635
       RET
LOAD
       LD     HL,адрес
       LD     DE,(T_S)
       LD     BC,#nn05
       CALL   15635
       RET
T_S    DEFW   #SSTT ; дорожка и сектор,
                    ; куда писать/читать

3. В разные файлы:
──────────────────
Случай  аналогичный 1,  только  процедуру
SET_NAME надо заменить на INP_NAME.

* Если  вы  дискетируете  адвентюру,   то
лучше найти в программе  процедуру  ввода

строки и использовать  ее для ввода имени
файла. Как это можно использовать - пока-
зано ниже. В противном случае вам придет-
ся написать ее самостоятельно.

NAME   EQU   ; адрес, куда считывается
             ; строка
INPUT  EQU   ; адрес процедуры считывания
             ; строки
FROM   EQU   ; параметр "адрес"
LEN    EQU   ; параметр "длина" (BYTES)

INP_NAME
       LD     HL,NAME   ; очищаем имя
       LD     DE,NAME+1 ; файла
       LD     BC,7
       LD     (HL),#20
       LDIR

       CALL   INPUT     ; см. *

       LD     A,"C"      ; тип файла м.б.
       LD     (23781),A  ; любой, кроме
                         ; "#","D","B"

       LD     A,9        ; поиск файла
       LD     (23814),A  ; по 9 символам

       LD     HL,NAME
       LD     B,8
CHECK  LD     A,(HL)
       CP     NN         ; признак конца
       JR     NZ,NEXTS   ; строки код NN
       LD     A,#20      ; заменяем на
       LD     (HL),A     ; пробел
NEXTS  INC    HL
       DJNZ   CHECK
       LD     HL,NAME
       LD     DE,23773
       LD     BC,8
       LDIR
       RET


     И в заключение хочу поделиться одной
подпрограммкой, которая позволяет сменить
дисковод:

вызов: LD     D,n        ; n-номер диска
       CALL   CNG_D      ; 0-A, 1-B, 2-C
       ....

CNG_D  DI
       LD     A,#C9      ; отключаем  об-
       LD     (#5CC2),A  ; работку ошибок
                         ; (см. ADV #5)

       LD     (IY+0),#FF ; сбрасываем
       XOR    A          ; все коды
       LD     (23824),A  ; ошибок

       LD     A,D        ; устанавливаем
       LD     (23833),A  ; системные пе-
       LD     (23798),A  ; ременные TRDOS

       LD     C,1        ; выбор
       CALL   #3D13      ; дисковода

       LD     C,#18      ; настройка на
       CALL   #3D13      ; диск
       EI
       RET 



P.S. Если не делать настройку на диск, то
при смене дисковода может получиться так,
что он будет читать только нулевой трек.


P.P.S. Приведенные в ADV #5 и этом номере
процедуры не являются  идеальными, хотя и
вполне работоспособными. Если кто-то зна-
ет как можно сделать  лучше (или еще луч-
ше - драйвер с прямым программирование ВГ
и обработкой ошибок, например, так: стан-
дартный вызов,  а  по завершению работы в
регистре A код ошибки),  то мы с удоволь-
ствием опубликуем его достижения. Дерзай-
те, виртуозы клавиатуры!


     На этом позволю себе закончить. Если
что-то не понятно - пишите.

         ───────────────────────── 
     Мы продолжаем цикл статей, посвящен-
ных дисковым  загрузчикам.  Слово  нашему
корреспонденту.

            (C) Nicolas Viper

               "Чем ленивее человек, тем
               больше любой его поступок
               похож на подвиг."

                       Народная мудрость


     Задержка великовата, ну а кому нынче
легко ?  Доставайте свои мозги из баночек
с растворами,  протрите,  у  кого запыли-
лись, и вникайте.

     Прежде  всего  необходимо  заметить,
что  утверждение,  будто  в проверке пра-
вильности чтения (далее: ППЧ) ДОС нам по-
мочь не может оказалось НЕВЕРНЫМ. Только,
в свою очередь,  мы  должны  помочь  ему.

Взгляните на листинг:


        ORG   40000
        DI              ;Инсталляция:
        LD    HL,#FD00  ;-Пре
        LD    A,H       ;    ры
        LD    DE,#FD01  ;      ва
        LD    BC,256    ;        ний
        LD    (HL),#FE
        LDIR
        LD    I,A
        IM    2
        CALL  MUSRUN    ;-Музыки
        EI
        LD    DE,#0100  ;-Откуда,
        LD    HL,16384  ; куда и
        LD    B,27      ; сколько будем
        CALL  LOADER    ; грузить
        JP    MUSRUN    ; Music off + RET


*************** ГРУЗИМ *****************

LOADER
        LD    IX,#2F65  ;Раскрутка
        CALL  DOS
L1
        PUSH  DE
        PUSH  BC
        LD    A,D
        OR    A
        RRA
        LD    C,A
        LD    A,#3C
        JR    NC,L2
        LD    A,#2C
L2
        LD    IX,#2F4D  ;Ставим череп на
        CALL  DOS       ;дорогу
        POP   BC
        POP   DE

        PUSH  HL
        PUSH  DE
        PUSH  BC 

        CALL  LOA_COM
        POP   BC
        POP   DE
        POP   HL
                        ;Пересчет:
        INC   H         ;- адреса
        LD    A,E       ;- сек
        INC   A         ;     то
        AND   15        ;       ра
        LD    E,A
        JR    NZ,L5     ;- дороги
        INC   D
L5
        DJNZ  L1        ;Новый сектор

        DI              ;Выход
        IM    1
        EI
        RET

Эта часть не поменялась со времен первого
пришествия. А вот самое важное:

LOA_COM
        LD    C,#5F     ;Пересчет номера
        LD    A,E       ;сектора, засыл-
        INC   A         ;ка в регистр
        LD    IX,#2A53  ;сектора ВГ-шки
        CALL  GODOS
TRY1
        PUSH  HL        ;На стек
        PUSH  DE
TRYMET
        XOR   A            ;Ошибок нет
        LD    (ERRFLG+1),A ;(пока :)
        LD    B,A          ;B=0
        LD    C,#1F     ;Команда
        LD    A,#80     ;"чтение
        LD    IX,#2A53  ;       сектора"
        CALL  DOS
        LD    C,#7F     ;Чтение сектора
        LD    IX,#3FE5
        CALL  GODOS
        POP   DE        ;Взяли со стека
        POP   HL 

ERRFLG
        LD    A,0       ;Проверка на
        OR    A         ;ошибки во время
        JR    NZ,TRY1   ;чтения. (под-
        LD    D,1       ;робнее описано
        LD    IX,#2F2F  ;ниже)
        JP    GODOS

DOS
        HALT
GODOS
        PUSH  IX
        JP    #3D2F
IMCOM                    ;Словили преры-
        LD    (STACK),SP ;вание
        PUSH  IX
        PUSH  HL         ;Тут тоже в
        PUSH  DE         ;двух словах
        PUSH  AF         ;не опишешь :)
        PUSH  BC         ;Ищите подроб-
        CALL  MUSIC      ;ности ниже.
        LD    HL,(STACK)
        LD    E,(HL)
        INC   HL     

        LD    D,(HL)
        LD    A,D
        CP    #40
        JR    NC,RETOS
        CP    #3F
        JR    NZ,PA_CNT
        POP   BC
        PUSH  BC
        LD    A,B
        LD    (ERRFLG+1),A
PA_CNT
        LD    HL,0-#2F3A
        OR    A
        ADC   HL,DE
        JR    Z,RTBR
        LD    A,#C3
POPALL
        LD    (CHANGE),A
        POP   BC
        POP   AF
        POP   DE
        POP   HL
        POP   IX
        EI 

CHANGE
        JP    #3D2F
RETOS
        LD    A,#C9
        JR    POPALL

RTBR
        LD    SP,(STACK)
        POP   AF
        EI
        JP    TRYMET

STACK   DW    0

        ORG   #C000
        INSERT  "MUSIC"

        ORG   #FEFE
        JP    IMCOM

MUSRUN  EQU   #C000
MUSIC   EQU   #C006 

По адресу #2F2F в TR-DOS:

#2F2F   IN    A,(#1F)
        AND   #7F
        RET   Z
        DEC   D
        PUSH  HL
        PUSH  DE
        JR    NZ,#2EXX   ;Неважно куда
        HALT             ;) Ловим!

Кратенько:

По поводу IMCOM : задача этой процедуры -
проверить,  когда пришло прерывание.
     Возможны варианты:
- в ОЗУ.  Тогда нам все по барабану - иг-
  раем музыку и вертаемся.
- в ПЗУ (считаем, что это ДОС!!! По-идее,
  мы в СОС не попадаем).  Тогда  тоже ва-
  рианты:
  - Схватили с  адреса  #2F3A - это  зна-
  чит,  что  мы  словили HALT (см. выше),
  что, в свою очередь, говорит о том, что

  при чтении  произошла  одна  из  "стан-
  дартных"  ошибок  -  потеря данных, не-
  совпадение контрольных сумм и т.п.
  - ...с адреса #3FXX  (XX - любое значе-
  ние) - опять-таки, по идее,  мы там мо-
  жем перехватить  только  процедуру чте-
  ния сектора.  Это  значит,  что, увы, у
  нас  неопределенное  положение  -  надо
  закончить процедуру,  но из-за задержки
  на  проигрывание  мелодии  и  т.п.   мы
  упустили время и чтение  придется  пов-
  торить.

     При возникновении ошибки,  мы кладем
по адресу (ERRFLG + 1) НЕ ноль - это  го-
ворит,  что необходимо перечитать  сектор
(см. с метки ERRFLG).

     Все  остальное  не так сложно и было
расписано  раньше.  Пишите,  если  что не
так, в редакцию или мыльте мне:

         nicky@univ.uniyar.ac.ru 

     Хотя, судя  по предыдущим публикаци-
ям,  вы,  дорогие  читатели, все равно не
обратили  на  данную  процедуру  никакого
внимания -  глюков  в  ней  было полно, а
никто так и  не  написал: "...из-за этого
Вашего #@$&$-го прога я себе дискету  ис-
портил, любимую!"

        (C)  Котов А.В. (CAV Inc.)