*
*  PROGRAM NAME:  HELP2
*  AUTHOR:  RICHARD CONN
*  DATE:  23 MAY 83
*  VERSION:  4.1
*  PREVIOUS VERSIONS:  4.0 (14 JAN 83), 3.2 (6 JAN 83)
*  PREVIOUS VERSIONS:  3.1 (8 DEC 82), 3.0 (11 OCT 82), 2.0 (30 OCT 81)
*  PREVIOUS VERSIONS:  1.8 (18 OCT 81), 1.7 (10 SEP 81), 1.6 (9 SEP 81)
*  PREVIOUS VERSIONS:  1.5 (9 SEP 81), 1.4 (8 SEP 81), 1.3 (8 SEP 81)
*  PREVIOUS VERSIONS:  1.2 (7 SEP 81), 1.1 (6 OCT 80), 1.0 (18 NOV 79)
*  NOTE:  FOR USE WITH ZCPR 2.0 AND LATER
*
VERS    EQU     41

;
;       This program is Copyright (c) 1982, 1983 by Richard Conn
;       All Rights Reserved
;
;       ZCPR2 and its utilities, including this one, are released
; to the public domain.  Anyone who wishes to USE them may do so with
; no strings attached.  The author assumes no responsibility or
; liability for the use of ZCPR2 and its utilities.
;
;       The author, Richard Conn, has sole rights to this program.
; ZCPR2 and its utilities may not be sold without the express,
; written permission of the author.
;

;
;       HELP supports an online documentation system under ZCPR2.  Refer
; to the file HELP.HLP for more details.
;

;
;  CP/M Constants
;
FALSE           EQU     0
TRUE            EQU     NOT FALSE

UDFLAG          EQU     4       ; ADDRESS OF USER/DISK FLAG
BDOS            EQU     5       ; ADDRESS OF BDOS ENTRY POINT
FCB             EQU     5CH     ; ADDRESS OF FILE CONTROL BLOCK
BUFF            EQU     80H     ; ADDRESS OF DMA BUFFER

CR              EQU     0DH     ; <CR>
LF              EQU     0AH     ; <LF>
FF              EQU     'L'-40H ; CTRL-L = FORM FEED
CTRLZ           EQU     'Z'-40H ; CTRL-Z
CTRLC           EQU     'C'-40H ; CTRL-C

;
;  HELP Control Characters
;
SECT$CHAR       EQU     ':'     ; DEFINED TO BE COLON
BACKUP$CHAR     EQU     'L'     ; BACK UP TO PREVIOUS FRAME CHAR
START$CHAR      EQU     'S'     ; JUMP TO START OF INFORMATION CHAR
MENU$CHAR       EQU     'M'     ; CHAR TO ABORT TO MENU
CPM$ABORT$CHAR  EQU     CTRLC   ; CHAR TO EXIT
LEVEL$RET$CHAR  EQU     '^'     ; RETURN TO PREVIOUS HELP LEVEL
ROOT$CHAR       EQU     '.'     ; RETURN TO ROOT OF HELP
PRINT$CHAR      EQU     'P'     ; PRINT CURRENT INFORMATION SECTION

;
;  Lines/Screen on CRT
;
LINES$PER$SCREEN        EQU     24      ; ASSUME 24 LINES/SCREEN

;
;  Number of File Names per Line
;
NAMES$PER$LINE          EQU     6       ; 6 NAMES (SPACE = 6*11 COLS)

;
;  Set this to TRUE if all files must be of type HLP
;
FORCE$HLP               EQU     TRUE    ; TRUE IF FILES MUST BE OF TYPE HLP

;
;  Number of Nodes (Levels) in Help File Tree
;
HELP$MAX                EQU     10      ; DEFAULT = 10 (SPACE=11*HELP$MAX)


;
; MACROS TO PROVIDE Z80 EXTENSIONS
;   MACROS INCLUDE:
;
$-MACRO                 ;FIRST TURN OFF THE EXPANSIONS
;
;       JR      - JUMP RELATIVE
;       JRC     - JUMP RELATIVE IF CARRY
;       JRNC    - JUMP RELATIVE IF NO CARRY
;       JRZ     - JUMP RELATIVE IF ZERO
;       JRNZ    - JUMP RELATIVE IF NO ZERO
;       DJNZ    - DECREMENT B AND JUMP RELATIVE IF NO ZERO
;       LDIR    - MOV @HL TO @DE FOR COUNT IN BC
;       LXXD    - LOAD DOUBLE REG DIRECT
;       SXXD    - STORE DOUBLE REG DIRECT
;
;
;
;       @GENDD MACRO USED FOR CHECKING AND GENERATING
;       8-BIT JUMP RELATIVE DISPLACEMENTS
;
@GENDD  MACRO   ?DD     ;;USED FOR CHECKING RANGE OF 8-BIT DISPLACEMENTS
       IF (?DD GT 7FH) AND (?DD LT 0FF80H)
       DB      100H    ;Displacement Range Error on Jump Relative
       ELSE
       DB      ?DD
       ENDIF
       ENDM
;
;
; Z80 MACRO EXTENSIONS
;
JR      MACRO   ?N      ;;JUMP RELATIVE
       DB      18H
       @GENDD  ?N-$-1
       ENDM
;
JRC     MACRO   ?N      ;;JUMP RELATIVE ON CARRY
       DB      38H
       @GENDD  ?N-$-1
       ENDM
;
JRNC    MACRO   ?N      ;;JUMP RELATIVE ON NO CARRY
       DB      30H
       @GENDD  ?N-$-1
       ENDM
;
JRZ     MACRO   ?N      ;;JUMP RELATIVE ON ZERO
       DB      28H
       @GENDD  ?N-$-1
       ENDM
;
JRNZ    MACRO   ?N      ;;JUMP RELATIVE ON NO ZERO
       DB      20H
       @GENDD  ?N-$-1
       ENDM
;
DJNZ    MACRO   ?N      ;;DECREMENT B AND JUMP RELATIVE ON NO ZERO
       DB      10H
       @GENDD  ?N-$-1
       ENDM
;
LDIR    MACRO           ;;LDIR
       DB      0EDH,0B0H
       ENDM
;
LDED    MACRO   ?N      ;;LOAD DE DIRECT
       DB      0EDH,05BH
       DW      ?N
       ENDM
;
LBCD    MACRO   ?N      ;;LOAD BC DIRECT
       DB      0EDH,4BH
       DW      ?N
       ENDM
;
SDED    MACRO   ?N      ;;STORE DE DIRECT
       DB      0EDH,53H
       DW      ?N
       ENDM
;
SBCD    MACRO   ?N      ;;STORE BC DIRECT
       DB      0EDH,43H
       DW      ?N
       ENDM
;
; END OF Z80 MACRO EXTENSIONS
;

       ORG     100H

;
;  Branch to Start of Program
;
       jmp     start

;
;******************************************************************
;
;  SINSFORM -- ZCPR2 Utility Standard General Purpose Initialization Format
;
;       This data block precisely defines the data format for
; initial features of a ZCPR2 system which are required for proper
; initialization of the ZCPR2-Specific Routines in SYSLIB.
;

;
;  EXTERNAL PATH DATA
;
EPAVAIL:
       DB      0FFH    ; IS EXTERNAL PATH AVAILABLE? (0=NO, 0FFH=YES)
EPADR:
       DW      40H     ; ADDRESS OF EXTERNAL PATH IF AVAILABLE

;
;  INTERNAL PATH DATA
;
INTPATH:
       DB      0,0     ; DISK, USER FOR FIRST PATH ELEMENT
                       ; DISK = 1 FOR A, '$' FOR CURRENT
                       ; USER = NUMBER, '$' FOR CURRENT
       DB      0,0
       DB      0,0
       DB      0,0
       DB      0,0
       DB      0,0
       DB      0,0
       DB      0,0     ; DISK, USER FOR 8TH PATH ELEMENT
       DB      0       ; END OF PATH

;
;  MULTIPLE COMMAND LINE BUFFER DATA
;
MCAVAIL:
       DB      0FFH    ; IS MULTIPLE COMMAND LINE BUFFER AVAILABLE?
MCADR:
       DW      0FF00H  ; ADDRESS OF MULTIPLE COMMAND LINE BUFFER IF AVAILABLE

;
;  DISK/USER LIMITS
;
MDISK:
       DB      4       ; MAXIMUM NUMBER OF DISKS
MUSER:
       DB      31      ; MAXIMUM USER NUMBER

;
;  FLAGS TO PERMIT LOG IN FOR DIFFERENT USER AREA OR DISK
;
DOK:
       DB      0FFH    ; ALLOW DISK CHANGE? (0=NO, 0FFH=YES)
UOK:
       DB      0FFH    ; ALLOW USER CHANGE? (0=NO, 0FFH=YES)

;
;  PRIVILEGED USER DATA
;
PUSER:
       DB      10      ; BEGINNING OF PRIVILEGED USER AREAS
PPASS:
       DB      'chdir',0       ; PASSWORD FOR MOVING INTO PRIV USER AREAS
       DS      41-($-PPASS)    ; 40 CHARS MAX IN BUFFER + 1 for ending NULL

;
;  CURRENT USER/DISK INDICATOR
;
CINDIC:
       DB      '$'     ; USUAL VALUE (FOR PATH EXPRESSIONS)

;
;  DMA ADDRESS FOR DISK TRANSFERS
;
DMADR:
       DW      80H     ; TBUFF AREA

;
;  NAMED DIRECTORY INFORMATION
;
NDRADR:
       DW      00000H  ; ADDRESS OF MEMORY-RESIDENT NAMED DIRECTORY
NDNAMES:
       DB      64      ; MAX NUMBER OF DIRECTORY NAMES
DNFILE:
       DB      'NAMES   '      ; NAME OF DISK NAME FILE
       DB      'DIR'           ; TYPE OF DISK NAME FILE

;
;  REQUIREMENTS FLAGS
;
EPREQD:
       DB      0FFH    ; EXTERNAL PATH?
MCREQD:
       DB      000H    ; MULTIPLE COMMAND LINE?
MXREQD:
       DB      000H    ; MAX USER/DISK?
UDREQD:
       DB      000H    ; ALLOW USER/DISK CHANGE?
PUREQD:
       DB      000H    ; PRIVILEGED USER?
CDREQD:
       DB      000H    ; CURRENT INDIC AND DMA?
NDREQD:
       DB      000H    ; NAMED DIRECTORIES?
Z2CLASS:
       DB      5       ; CLASS 5
       DB      'ZCPR2'
       DS      10      ; RESERVED

;
;  END OF SINSFORM -- STANDARD DEFAULT PARAMETER DATA
;
;******************************************************************
;

;
;  DEFAULT FILE NAME
;
DEFFN:
       DB      'HELP    '
DEFTYP:
       DB      'HLP'

;
;  Start of Program
;
START:
       LXI     H,0     ; GET SP
       DAD     SP
       SHLD    STACK
       LXI     SP,STACK        ; NEW STACK
       MVI     E,0FFH          ; GET CURRENT USER NUMBER
       MVI     C,32
       CALL    BDOS
       STA     CUR$USER        ; CURRENT USER NUMBER
       LDA     BDOS+2  ; BASE PAGE OF BDOS
       SUI     10      ; 2K + 2 PAGES
       STA     TPA$END
       XRA     A       ; A=0
       STA     DFFLG   ; TURN OFF DEFAULT FILE FLAG
       STA     HELP$LEVEL      ; SET HELP LEVEL TO 0 (NO RETURN FILE)
       LXI     D,HELPMS        ; PRINT OPENING MSG
       CALL    PRINT$MESSAGE
       LXI     H,FCB+1 ; CHECK FOR FILE NAME
       MOV     A,M
       CPI     ' '     ; NONE?
       JRNZ    START1

*  INSERT 'HELP.HLP' INTO FCB OR CLEAR FCB
       LXI     D,DEFFN ; PT TO DEFAULT NAME
       MVI     B,11    ; 11 BYTES
       XCHG
       CALL    MOVE    ; MOVE (HL) TO (DE) FOR (B) BYTES
       MVI     A,1     ; TURN ON DEFAULT FILE FLAG
       STA     DFFLG

*  START/RESTART HELP PROGRAM (START ON INITIAL ENTRY, RESTART ON NODE LOAD)
START1:
       LXI     SP,STACK        ; SET STACK POINTER

*  CLEAR NON-NAME/TYPE BYTES IN FCB
       LXI     H,FCB   ; INITIAL ZERO
       MVI     M,0     ; STORE 0 FOR DRIVE (CURRENT LOGGED-IN)
       LXI     D,12    ; SKIP TO EXTENT
       DAD     D
       MVI     B,24    ; FILL 24 BYTES
FCB$FILL:
       MVI     M,0     ; ZERO FILL
       INX     H       ; PT TO NEXT
       DJNZ    FCB$FILL

*  CHECK FOR WILD CARDS IN FILE NAME -- ERROR IF SO
       LXI     H,FCB+1 ; PT TO FIRST BYTE OF FILE NAME
       MVI     B,11    ; 11 BYTES
FCB$WILD$CK:
       MOV     A,M     ; GET BYTE
       ANI     7FH     ; MASK
       CPI     '?'     ; WILD?
       JRZ     FCB$WILD$ERROR
       INX     H       ; PT TO NEXT
       DJNZ    FCB$WILD$CK

*  CHECK FOR FILE TYPE
       LXI     H,FCB+9 ; CHECK FOR FILE TYPE

       IF      NOT FORCE$HLP   ; IF FILE TYPE MAY NOT BE HLP
       MOV     A,M     ; CHECK FOR FILE TYPE SPECIFIED
       CPI     ' '     ; NONE?
       JRNZ    START2
       ENDIF

*  PLACE DEFAULT FILE TYPE OF '.HLP' IN FCB
DEFAULT$EXT:
       LXI     D,DEFTYP        ; DEFAULT FILE TYPE
       MVI     B,3
       XCHG
       CALL    MOVE    ; MOVE (HL) TO (DE) FOR (B) BYTES

*  OPEN FILE
START2:
       LDA     DFFLG   ; CHECK FOR DEFAULT
       ORA     A       ; 0=NO
       JNZ     HELP$DEF        ; DISPLAY DEFAULT HELP INFO WITH FILE NAMES
       LXI     H,INTPATH       ; SET BASE ADDRESS OF PATH
       LDA     EPAVAIL         ; EXTERNAL PATH AVAILABLE
       ORA     A               ; 0=NO
       JRZ     START2A
       LHLD    EPADR           ; GET ADDRESS OF EXTERNAL PATH
START2A:
       LXI     D,FCB           ; PT TO FCB
       CALL    FNDFILE         ; SEARCH FOR FILE
       JNZ     START3          ; FILE FOUND, SO PROCESS
       LDA     EPAVAIL         ; SEARCH ALONG EXTERNAL PATH?
       ORA     A               ; 0=NO
       JRZ     START2B
       LXI     H,INTPATH       ; SELECT INTERNAL PATH THEN
       LXI     D,FCB           ; PT TO FCB
       CALL    FNDFILE         ; SEARCH
       JNZ     START3          ; FILE FOUND

*  FILE NOT FOUND -- FATAL ERROR
START2B:
       LXI     D,ERR1  ; FILE NOT FOUND
       CALL    PRINT$MESSAGE
       JMP     HELP$EXIT

*  FILE CONTAINS WILD CARDS -- FATAL ERROR
FCB$WILD$ERROR:
       LXI     D,WILD$ERR      ; WILD CARD
       CALL    PRINT$MESSAGE
       JMP     HELP$EXIT

*
*  DISPLAY DEFAULT HELP FILE INFORMATION
*
HELP$DEF:
       LXI     D,HELP$DEF$MSG  ; PRINT MESSAGE
       CALL    PRINT$MESSAGE
       CALL    SET$COUNT       ; SET COUNTER
       CALL    RESET$SYSTEM    ; RETURN HOME
       CALL    PRINT$HELP$FILES        ; PRINT HELP FILES AT HOME
       LDA     EPAVAIL         ; EXTERNAL PATH?
       LHLD    EPADR           ; EXTERNAL PATH ADDRESS
       ORA     A               ; 0=NO EXTERNAL PATH
       CNZ     HDFF1
       CALL    RESET$SYSTEM    ; RETURN HOME
       LXI     H,INTPATH       ; FOLLOW INTERNAL PATH
       CALL    HDFF1           ; SEARCH IT
       CALL    RESET$SYSTEM    ; RESET CALLING DISK/USER NUMBER
       LXI     D,HELP$EDEF$MSG ; PRINT END MESSAGE
       CALL    PRINT$MESSAGE
       CALL    CHAR$IN         ; GET ANY CHAR FOR RESPONSE
       CPI     CTRLC           ; ABORT?
       JZ      HELP$EXIT
       XRA     A               ; TURN OFF DEFAULT
       STA     DFFLG
       LXI     H,DEFFN         ; SET DEFAULT FILE NAME
       LXI     D,FCB+1
       MVI     B,11            ; 11 BYTES
       CALL    MOVE
       JMP     START2          ; PROCESS DEFAULT HELP FILE

;
; MAIN SEARCH LOOP
;
HDFF1:
       LDA     CINDIC          ;GET CURRENT DISK INDICATOR
       MOV     B,A             ;... IN B
       MOV     A,M             ;GET DRIVE
       ANI     7FH             ;MASK MSB
       ORA     A               ;0=DONE=COMMAND NOT FOUND
       RZ                      ;END OF PATH?
;
; LOOK FOR COMMAND IN DIRECTORY PTED TO BY HL; DRIVE IN A
;
       CMP     B
       JRNZ    HDFF2           ;SKIP DEFAULT DRIVE SELECTION IF SO
       LDA     UDFLAG          ;GET DEFAULT USER/DISK
       ANI     0FH             ;MASK FOR DEFAULT DISK
       INR     A               ;PREP FOR FOLLOWING DCR A
HDFF2:
       DCR     A               ;ADJUST PATH 1 TO 0 FOR A, ETC
       MOV     E,A             ;DISK NUMBER IN E
       MVI     C,14            ;SELECT DISK FCT
       CALL    BENTRY          ;SELECT DRIVE
       INX     H               ;PT TO USER NUMBER
       MOV     A,M             ;GET USER NUMBER
       INX     H               ;PT TO NEXT PATH ELEMENT
       ANI     7FH             ;MASK OUT MSB
       CMP     B
       JRNZ    HDFF3           ;DO NOT SELECT CURRENT USER IF SO
       LDA     CUR$USER        ;GET ORIGINAL USER NUMBER
HDFF3:
       MOV     E,A             ;SELECT USER
       MVI     C,32
       CALL    BENTRY
       PUSH    H               ;SAVE PATH PTR
       CALL    PRINT$HELP$FILES
       POP     H               ;GET PATH PTR
       JR      HDFF1

*
*  SET FILE NAME COUNTER
*
SET$COUNT:
       MVI     A,NAMES$PER$LINE        ; NUMBER OF FILE NAMES/LINE
       STA     NAME$COUNT
       RET

*
*  PRINT NAMES OF HELP FILES
*
PRINT$HELP$FILES:
       LXI     H,FCB           ; MAKE FCB = *.HLP
       MVI     M,0             ; BEGINNING 0 FOR DEFAULT DISK
       MVI     B,8             ; FILL 8 ?'S
       INX     H               ; PT TO FIRST CHAR
PHF1:
       MVI     M,'?'           ; '?' FILL
       INX     H               ; PT TO NEXT
       DJNZ    PHF1
       LXI     D,DEFTYP        ; COPY DEFAULT FILE TYPE
       MVI     B,3             ; 3 BYTES
       XCHG                    ; EXCHANGE
       CALL    MOVE            ; COPY
       XCHG                    ; RESTORE PTR
       MVI     B,24            ; FILL REST WITH 0'S
PHF2:
       MVI     M,0             ; 0 FILL
       INX     H               ; PT TO NEXT
       DJNZ    PHF2

*  SEARCH FOR FIRST FILE
       LXI     D,FCB           ; PT TO FCB
       MVI     C,17            ; SEARCH FOR FIRST
       CALL    BDOS
       CPI     0FFH            ; NONE?
       RZ                      ; DONE IF SO

*  PRINT CURRENT AND SEARCH FOR NEXT
PHF3:
       CALL    PRINT$HFN       ; PRINT HELP FILE NAME
       LXI     D,FCB           ; PT TO FCB
       MVI     C,18            ; SEARCH FOR NEXT
       CALL    BDOS
       CPI     0FFH            ; DONE?
       JRNZ    PHF3
       RET

*
*  PRINT NAME OF FILE WHOSE BUFF OFFSET IS IN A
*
PRINT$HFN:
       RRC                     ; A=A*32
       RRC
       RRC                     ; RATHER THAN 5 LEFT, I DO 3 RIGHT (NEAT, HUH?)
       ANI     60H             ; MASK ALL BUT INTERESTING PART
       LXI     H,BUFF          ; PT TO BUFFER
       ADD     L               ; PT TO ENTRY
       MOV     L,A
       MOV     A,H
       ACI     0
       MOV     H,A             ; HL PTS TO ENTRY
       INX     H               ; HL PTS TO HELP FILE NAME
       MVI     B,8             ; 8 CHARS
PHFN1:
       MOV     A,M             ; GET CHAR
       ANI     7FH             ; MASK
       INX     H               ; PT TO NEXT
       CALL    CHAR$OUT        ; PRINT IT
       DJNZ    PHFN1
       MVI     A,' '           ; TRAILING SPACES
       CALL    CHAR$OUT        ; 3 OF THEM
       CALL    CHAR$OUT
       CALL    CHAR$OUT
       LDA     NAME$COUNT      ; COUNT DOWN
       DCR     A
       STA     NAME$COUNT
       RNZ
       CALL    SET$COUNT       ; RESET COUNT
       CALL    CRLF            ; NEW LINE
       RET

*
*  LOAD HELP FILE INFORMATION
*
START3:
       LXI     D,FCB           ; OPEN FILE
       MVI     C,15            ; BDOS FILE OPEN
       CALL    BDOS
       LXI     H,HELP$BUF      ; PT TO BUFFER
       SHLD    NEXT$ADR        ; SET PTR

*  READ RECORDS UNTIL EOF
START4:
       CALL    READ$RECORD     ; READ INFO
       JRZ     START4  ; NOT DONE IF ZERO RETURN
       LXI     D,FCB   ; CLOSE FILE
       MVI     C,16    ; CLOSE
       CALL    BDOS
       CALL    RESET$SYSTEM    ; RESTORE CURRENT DISK AND USER IF CHANGED

*
*  START OF HELP PROGRAM
*
HELP:
       LXI     SP,STACK        ; RESET STACK
       MVI     A,0     ; SET NO FRAME
       STA     FRAME$NUMBER
       LXI     H,HELP$BUF      ; PT TO BUFFER
       MOV     A,M     ; NO HEADER SECTION?
       ANI     7FH     ; MASK OUT MSB
       CPI     SECT$CHAR
       JRNZ    HELP1   ; HEADER SECTION EXISTS
       CALL    PRINT$INFO      ; PRINT HELP INFO PTED TO BY HL
       LDA     HELP$LEVEL      ; CHECK TO SEE IF WE ARE NOT AT LEVEL 0
       ORA     A       ; 0=LEVEL 0
       JRZ     HELP$EXIT       ; ABORT IF SO
       JMP     LEVEL$RETURN    ; GO TO PREVIOUS LEVEL IF NOT

*  EXIT POINT FOR ANY EXIT FROM THE REST OF THE HELP PROGRAM
HELP$EXIT:
       CALL    RESET$SYSTEM    ; RESET CALLING DISK/USER NUMBER
       LHLD    STACK   ; GET CP/M SP
       SPHL
       RET             ; DONE

*  PRINT HEADER INFORMATION AND SELECT AN OPTION
HELP1:
       CALL    PRINT$HEADER    ; PRINT HEADER
       PUSH    B       ; SAVE C (NUMBER OF VALID SELECTIONS)
       CALL    CRLF1   ; NEW LINE
       CALL    PR$LEVEL        ; PRINT LEVEL NUMBER
       LXI     D,PROMPT1$MESSAGE       ; PRINT PROMPT
       CALL    PRINT$MESSAGE
       LXI     D,PROMPT2$MESSAGE       ; LEVEL COMMAND
       LDA     HELP$LEVEL      ; CURRENT LEVEL = 0?
       ORA     A       ; SET FLAGS
       JRZ     HELP1A
       CALL    PRINT$MESSAGE
HELP1A:
       LXI     D,PROMPT3$MESSAGE
       CALL    PRINT$MESSAGE
       POP     B       ; GET C
       CALL    CHAR$IN         ; GET RESPONSE
       CPI     CTRLC           ; EXIT
       JRZ     HELP$EXIT
       CPI     ROOT$CHAR       ; GO TO ROOT
       JZ      GO$ROOT
       CPI     LEVEL$RET$CHAR  ; RETURN TO PREVIOUS LEVEL
       JZ      LEVEL$RETURN
       PUSH    PSW     ; SAVE CHAR
       CALL    CRLF1
       POP     PSW     ; GET CHAR
       SUI     'A'-1           ; ADJUST FOR COUNT
       MOV     B,A             ; SAVE COUNT
       JRZ     BAD$RESPONSE
       JRNC    HELP2

*  INVALID RESPONSE
BAD$RESPONSE:
       LXI     D,ERR2  ; INVALID RESPONSE
       CALL    PRINT$MESSAGE
       JR      HELP1

*  VALID RESPONSE -- LOOK FOR AND PRINT INFORMATION SECTION
HELP2:
       INR     C       ; 1 MORE THAN NUMBER OF POSSIBLE SELECTIONS
       CMP     C       ; GREATER THAN NUMBER OF POSSIBLE SELECTIONS?
       JRNC    BAD$RESPONSE
       LHLD    FIRST$ENTRY     ; GET PTR TO FIRST ENTRY

*  PRINT INFORMATION WHEN COUNT IS ZERO
HELP3:
       DJNZ    HELP4
       CALL    PRINT$INFO      ; PRINT INFO PTED TO BY HL
       JR      HELP1

*  LOCATE NEXT INFORMATION SECTION
HELP4:
       MOV     A,M     ; <CTRL-Z>?
       ANI     7FH     ; MASK OUT MSB
       INX     H       ; PT TO NEXT BYTE
       CPI     CTRLZ
       JZ      HELP$ERR        ; HELP FILE FORMAT ERROR
       CPI     LF      ; LINE FEED (WS FILE)?
       JRZ     HELP5
       CPI     CR      ; <CR>?
       JRNZ    HELP4
       INX     H       ; 1ST BYTE OF NEXT LINE
HELP5:
       MOV     A,M     ; GET CHAR
       ANI     7FH     ; MASK OUT MSB
       CPI     SECT$CHAR       ; NEW SECTION?
       JRZ     HELP3   ; CONTINUE LOOP IF SO
       CPI     CTRLZ   ; EOF?
       JRNZ    HELP4   ; CONTINUE IF NOT

*  ERROR -- REACHED END OF HELP FILE
HELP$ERR:
       LXI     D,ERR3  ; FORMAT ERROR
       CALL    PRINT$MESSAGE
       JMP     HELP1


*********************************************************
*                                                       *
*  HELP SUPPORT ROUTINE SECTION                         *
*                                                       *
*********************************************************

*
*  RESTORE CURRENT DISK AND CURRENT USER
*
RESET$SYSTEM:
       LDA     UDFLAG  ; GET DISK
       ANI     0FH     ; GET DISK NUMBER
       MOV     E,A     ; DISK IN E
       MVI     C,14    ; SELECT DISK
       CALL    BDOS
       LDA     CUR$USER        ; SET USER
       MOV     E,A     ; USER IN E
       MVI     C,32    ; SET USER CODE
       CALL    BDOS
       RET

*
*  FNDFILE -- LOOK FOR FILE ALONG ZCPR2 PATH
*  INPUT PARAMETERS:  HL = BASE ADDRESS OF PATH, DE = PTR TO FCB OF FILE
*  OUTPUT PARAMETERS:  A=0 AND ZERO FLAG SET IF NOT FOUND, NZ IF FOUND
*
FNDFILE:
       SHLD    PATH            ;SAVE PATH BASE ADDRESS
       MVI     C,17            ;SEARCH FOR FIRST
       CALL    BENTRY          ;LOOK FOR FILE
       INR     A               ;SET FLAG
       JRNZ    FF5             ;FOUND IT -- RETURN FOUND FLAG
       XCHG                    ;HL=FCB PTR
       SHLD    FCBPTR          ;SAVE IT
       LHLD    PATH            ;PT TO PATH FOR FAILURE POSSIBILITY
;
; MAIN SEARCH LOOP
;
FF1:
       LDA     CINDIC          ;GET CURRENT USER/DISK INDICATOR
       MOV     B,A             ;... IN B
       MOV     A,M             ;GET DRIVE
       ANI     7FH             ;MASK MSB
       ORA     A               ;0=DONE=COMMAND NOT FOUND
       JRNZ    FF2             ;NO ERROR ABORT?
;
; FILE NOT FOUND ERROR
;
       XRA     A               ;ZERO FLAG MEANS NOT FOUND
       RET
;
; LOOK FOR COMMAND IN DIRECTORY PTED TO BY HL; DRIVE IN A
;
FF2:
       CMP     B               ;CURRENT DI
SK?
       JRNZ    FF3             ;SKIP DEFAULT DRIVE SELECTION IF SO
       LDA     UDFLAG          ;GET DEFAULT USER/DISK
       ANI     0FH             ;MASK FOR DEFAULT DISK
       INR     A               ;PREP FOR FOLLOWING DCR A
FF3:
       DCR     A               ;ADJUST PATH 1 TO 0 FOR A, ETC
       MOV     E,A             ;DISK NUMBER IN E
       MVI     C,14            ;SELECT DISK FCT
       CALL    BENTRY          ;SELECT DRIVE
       INX     H               ;PT TO USER NUMBER
       MOV     A,M             ;GET USER NUMBER
       ANI     7FH             ;MASK OUT MSB
       INX     H               ;PT TO NEXT ENTRY IN PATH
       CMP     B               ;CURRENT USER?
       JRNZ    FF4             ;DO NOT SELECT CURRENT USER IF SO
       LDA     CUR$USER        ;GET ORIGINAL USER NUMBER
FF4:
       MOV     E,A             ;SELECT USER
       MVI     C,32
       CALL    BENTRY
       XCHG                    ;SAVE PTR TO NEXT PATH ELEMENT IN DE
       LHLD    FCBPTR          ;GET PTR TO FCB
       XCHG                    ;... IN DE, PATH PTR IN HL
       MVI     C,17            ;SEARCH FOR FIRST
       CALL    BENTRY          ;LOOK FOR FILE
       INR     A               ;SET FLAG
       JRZ     FF1             ;CONTINUE PATH SEARCH IF SEARCH FAILED
;
; FILE FOUND -- PERFORM SYSTEM TEST AND PROCEED IF APPROVED
;
FF5:
       MVI     A,0FFH          ;SET OK RETURN
       ORA     A
       RET

;
;  BDOS ROUTINE
;
BENTRY:
       PUSH    H       ;SAVE REGS
       PUSH    D
       PUSH    B
       CALL    BDOS
       POP     B       ;GET REGS
       POP     D
       POP     H
       RET

* BUFFERS
FCBPTR:
       DS      2       ;POINTER TO FCB FOR FILE SEARCH
PATH:
       DS      2       ;BASE ADDRESS OF PATH

*
*  INPUT CHAR; CHAR IS IN A
*
CHAR$IN:
       PUSH B ! PUSH D ! PUSH H
       MVI     C,1     ; READ CHAR
       CALL    BDOS
       POP H ! POP D ! POP B
       PUSH    PSW     ; SAVE CHAR
       CALL    CRLF1
       POP     PSW     ; RESTORE CHAR
*
*  CAPITALIZE CHAR IN A
*
CAPS:
       ANI     7FH     ; MASK OUT MSB
       CPI     61H     ; LESS THAN SMALL A?
       RC
       CPI     7BH     ; LESS THAN LEFT BRACE?
       RNC
       ANI     5FH     ; CAPITALIZE
       RET

*
*  PRINT CHAR IN A ON CON:
*
CHAR$OUT:
       PUSH PSW ! PUSH B ! PUSH D ! PUSH H
       MVI     C,2     ; WRITE
       MOV     E,A     ; CHAR IN E
       CALL    BDOS
       POP H ! POP D ! POP B ! POP PSW
       RET

*
*  PRINT ERROR MSG PTED TO BY DE; ENDS IN '$'
*
PRINT$MESSAGE:
       PUSH B ! PUSH D ! PUSH H
       MVI     C,9     ; PRINT BUFFER
       CALL    BDOS
       POP H ! POP D ! POP B
       RET

*
*  MOVE BYTES PTED TO BY HL TO AREA PTED TO BY DE; B BYTES TO MOVE
*
MOVE:
       MOV     A,M     ; GET BYTE
       ANI     7FH     ; MASK OFF MSB -- IN CASE A WS FILE
       STAX    D       ; PUT BYTE
       INX     H       ; PT TO NEXT
       INX     D
       DJNZ    MOVE
       RET

*
*  READ RECORD FROM DISK; NEXT$ADR CONTAINS ADDRESS TO READ TO
*       ON RETURN, BDOS ERROR CODE IS IN A (0=NO ERROR)
*
READ$RECORD:
       MVI     C,20    ; READ NEXT RECORD
       LXI     D,FCB   ; PT TO FCB
       CALL    BDOS
       PUSH    PSW     ; SAVE RETURN CODE
       LHLD    NEXT$ADR        ; PT TO LOAD ADDRESS
       LDA     TPA$END ; CHECK AGAINST END PAGE OF TPA
       CMP     H       ; IF AT SAME PAGE, YES
       JZ      READ$ERROR
       LXI     D,BUFF  ; PT TO BUFFER TO LOAD FROM
       MVI     B,128   ; NUMBER OF BYTES TO MOVE
       XCHG
       CALL    MOVE
       XCHG
       MVI     M,CTRLZ ; STORE ENDING CTRLZ IN CASE OF EOF
       POP     PSW     ; GET RETURN CODE
       ORA     A       ; DONE?  <>0 IF SO

*  READ DONE -- SAVE PTR TO NEXT BLOCK
       SHLD    NEXT$ADR        ; SET NEXT ADDRESS
       RET

READ$ERROR:
       LXI     D,READERR
       CALL    PRINT$MESSAGE
       JMP     HELP$EXIT

*
*  PRINT ONE LINE OF INFO SECTION; HL PTS TO LINE UPON ENTRY;
*       HL PTS TO FIRST CHAR OF NEXT LINE UPON EXIT
*
PRINT$LINE:
       MOV     A,M     ; GET CHAR
       ANI     7FH     ; MASK OUT MSB
       CPI     CR      ; EOL?
       JRZ     CRLF
       CPI     LF      ; LINE FEED? (WS FILE)
       JRZ     CRLF0
       CPI     CTRLZ   ; END OF FILE?
       JRZ     CRLFC   ; DONE IF SO
       CALL    CHAR$OUT        ; PRINT CHAR
       INX     H       ; PT TO NEXT
       JR      PRINT$LINE

*
*  PRINT CRLF, PT TO FIRST CHAR OF NEXT LINE, AND PAGE IF NECESSARY
*
CRLF:
       INX     H       ; PT TO LF
CRLF0:
       INX     H       ; PT TO 1ST CHAR OF NEXT LINE
CRLFC:
       CALL    CRLF1   ; PRINT CRLF
       LDA     LINE$CNT        ; GET LINE COUNT
       DCR     A
       STA     LINE$CNT
       RNZ             ; OK -- CONTINUE
       MOV     A,M     ; SET MSB OF FIRST CHAR OF NEXT LINE
       ORI     80H
       MOV     M,A     ; MSB IS SET FOR LATER BACKUP
FRAME$PAUSE:
       CALL    PR$LEVEL        ; PRINT LEVEL NUMBER
       LDA     FRAME$NUMBER    ; INCREMENT FRAME NUMBER
       INR     A
       STA     FRAME$NUMBER
       LXI     D,PAGEMS
       CALL    PRINT$MESSAGE   ; PRINT PAGE MESSAGE
       LXI     D,PAGE1MS       ; NOT LEVEL 0?
       LDA     HELP$LEVEL      ; GET LEVEL NUMBER
       ORA     A       ; SET FLAGS
       JRZ     FP1
       CALL    PRINT$MESSAGE
FP1:
       LXI     D,PAGE2MS
       CALL    PRINT$MESSAGE
       CALL    CHAR$IN ; GET RESPONSE
       CPI     MENU$CHAR       ; ABORT?
       JZ      HELP    ; START OVER IF SO
       CPI     CPM$ABORT$CHAR  ; ABORT TO OS
       JZ      HELP$EXIT
       CPI     PRINT$CHAR      ; PRINT INFO SECTION?
       JZ      LIST1$INFO
       CPI     ROOT$CHAR       ; GO TO ROOT
       JZ      GO$ROOT
       CPI     LEVEL$RET$CHAR  ; RETURN TO HIGHER LEVEL
       JZ      LEVEL$RETURN
       CPI     BACKUP$CHAR     ; BACK UP?
       JZ      FRAME$BACKUP
       CPI     START$CHAR      ; JUMP TO START OF INFO
       JZ      INFO$START
FRAME$RESUME:
       SHLD    START$OF$FRAME
       CALL    SET$LINE$CNT
       CALL    CRLF1   ; NEW LINE
       RET

*  JUMP TO START OF INFORMATION
INFO$START:
       LHLD    START$OF$INFO   ; PT TO START OF INFO
       MVI     A,1             ; RESET FRAME COUNT
       STA     FRAME$NUMBER
       JMP     FRAME$RESUME    ; CONTINUE PROCESSING

*  BACK UP TO PREVIOUS FRAME
FRAME$BACKUP:
       CALL    BOI$CHECK       ; AT BEGINNING OF INFORMATION?
       JRNZ    FB1             ; CONTINUE IF NOT
       JMP     FRAME$PAUSE
FB1:
       DCX     H       ; BACK UP UNTIL BYTE WITH MSB SET IS FOUND
       MOV     A,M     ; GET BYTE
       ANI     80H
       JRZ     FB1
       LDA     FRAME$NUMBER    ; DECREMENT FRAME NUMBER
       DCR     A               ; BACK UP TO CURRENT FRAME NUMBER
       DCR     A               ; BACK UP TO PREVIOUS FRAME NUMBER
       STA     FRAME$NUMBER
       JMP     FRAME$RESUME    ; CONTINUE PROCESSING
*
*  PRINT CR AND LF ONLY
*
CRLF1:
       MVI     A,CR    ; PRINT CR
       CALL    CHAR$OUT
       MVI     A,LF    ; PRINT LF
       JMP     CHAR$OUT

*
*  SET LINE$CNT VARIABLE TO SCREEN SIZE
*
SET$LINE$CNT:
       MVI     A,LINES$PER$SCREEN-1
       STA     LINE$CNT
       RET

*
*  PRINT THE HEADER SECTION AND LOAD FIRST$ENTRY PTR
*    ON RETURN, C=NUMBER OF POSSIBLE SELECTIONS
*
PRINT$HEADER:
       MVI     A,0     ; SET NO FRAME
       STA     FRAME$NUMBER
       LXI     H,HELP$BUF
       CALL    SET$LINE$CNT
       LDA     LINE$CNT
       DCR     A
       STA     LINE$CNT
       MVI     A,'A'   ; INIT SELECTION CHAR
       STA     SEL$CHAR
       LXI     D,SELECTMS
       CALL    PRINT$MESSAGE
       MVI     C,0     ; COUNT NUMBER OF SELECTIONS

* PRINT LINE UNTIL FIRST INFORMATION SECTION FOUND
PH1:
       MOV     A,M     ; GET CHAR
       ANI     7FH     ; MASK OUT MSB
       CPI     SECT$CHAR
       JRZ     PH2
       CPI     CTRLZ   ; EOF? -- ABORT
       JZ      HELP$EXIT
       INR     C       ; INCREMENT SELECTION COUNT
       LDA     SEL$CHAR        ; DISPLAY SELECTION CHAR
       CALL    CHAR$OUT
       INR     A       ; INCR CHAR
       STA     SEL$CHAR
       MVI     A,'.'
       CALL    CHAR$OUT
       MVI     A,' '
       CALL    CHAR$OUT
       CALL    PRINT$LINE      ; PRINT HEADER LINE
       JR      PH1

*  SAVE PTR TO FIRST ENTRY
PH2:
       SHLD    FIRST$ENTRY
       LDA     LINE$CNT        ; GET COUNT OF REMAINING LINES
       MOV     B,A             ; ... IN B
       ORA     A               ; ANY LEFT?
       RZ
PH3:
       CALL    CRLF1           ; NEW LINE
       DJNZ    PH3
       RET

*
*  PRINT AN INFORMATION SECTION
*    INFORMATION SECTION IS PTED TO BY HL
*
PRINT$INFO:
       SHLD    START$OF$INFO   ; SET START OF INFORMATION POINTER
       CALL    LOAD$NODE       ; LOAD NEW NODE IF DUAL SECT$CHAR
       SHLD    START$OF$FRAME  ; SET FRAME POINTER
       MOV     A,M     ; SET MSB
       ORI     80H
       MOV     M,A
       CALL    SET$LINE$CNT
       MVI     A,1             ; A=1
       STA     FRAME$NUMBER    ; SET FRAME NUMBER
PI1:
       CALL    PRINT$LINE      ; PRINT LINE FROM INFO FILE
       MOV     A,M     ; DONE?
       ANI     7FH     ; MASK OUT MSB
       CPI     CTRLZ   ; EOF?
       JRZ     PI2
       CPI     SECT$CHAR       ; NEXT SECTION
       JRZ     PI2
       CPI     FF      ; FORM FEED?
       JRNZ    PI1
       INX     H       ; PT TO CHAR AFTER FORM FEED
       CALL    FORM$FEED       ; FEED SCREEN
       JR      PI1

*  FORM FEED SCREEN
FORM$FEED:
       LDA     LINE$CNT        ; GET LINE COUNT
       MOV     B,A     ; ... IN B
FEED$LOOP:
       PUSH    B       ; SAVE B
       CALL    CRLFC   ; NEW LINE
       POP     B       ; GET B
       DJNZ    FEED$LOOP
       RET

*  END OF INFO
PI2:
       MOV     A,M     ; SET MSB OF NEXT BYTE
       ORI     80H
       MOV     M,A
PI2A:
       CALL    CRLF1   ; NEW LINE
       LDA     LINE$CNT        ; COUNT DOWN
       DCR     A
       STA     LINE$CNT
       JRNZ    PI2A
PI2$MSG:
       CALL    PR$LEVEL        ; PRINT LEVEL NUMBER
       LDA     FRAME$NUMBER    ; INCREMENT FRAME NUMBER
       INR     A
       STA     FRAME$NUMBER
       LXI     D,ENDMS         ; PRINT END OF INFORMATION MSG
       CALL    PRINT$MESSAGE
       LXI     D,PAGE1MS       ; PRINT LEVEL UP MESSAGE OPTIONALLY
       LDA     HELP$LEVEL      ; GET CURRENT HELP LEVEL
       ORA     A       ; SET FLAGS
       JRZ     PI2$MSG1
       CALL    PRINT$MESSAGE
PI2$MSG1:
       LXI     D,PAGE2MS       ; PRINT REST OF INFO MESSAGE
       CALL    PRINT$MESSAGE
       CALL    CHAR$IN ; GET ANY CHAR
       CPI     MENU$CHAR       ; MENU ABORT
       JZ      HELP
       CPI     CPM$ABORT$CHAR  ; ABORT TO OS
       JZ      HELP$EXIT
       CPI     PRINT$CHAR      ; PRINT INFORMATION SECTION?
       JZ      LIST1$INFO
       CPI     ROOT$CHAR       ; GO TO ROOT
       JZ      GO$ROOT
       CPI     LEVEL$RET$CHAR  ; RETURN TO HIGHER LEVEL
       JZ      LEVEL$RETURN
       CPI     BACKUP$CHAR     ; BACK UP FROM EOI?
       JZ      PI2$BACKUP
       CPI     START$CHAR      ; START OF INFO?
       JZ      PI2$START
       JMP     SET$LINE$CNT    ; RESET LINE COUNT IN CASE OF ALL

*  JUMP TO START OF INFO
PI2$START:
       LHLD    START$OF$INFO   ; PT TO START OF INFO
       CALL    FRAME$RESUME    ; RESET POINTERS
       MVI     A,1             ; RESET FRAME COUNT
       STA     FRAME$NUMBER
       JMP     PI1     ; CONTINUE PROCESSING

*  BACK UP TO PREVIOUS FRAME
PI2$BACKUP:
       CALL    BOI$CHECK       ; AT BEGINNING OF INFORMATION?
       JZ      PI2$MSG
PI2$BACK:
       CALL    FB1     ; BACK UP TO PREVIOUS FRAME
       JMP     PI1     ; CONTINUE PROCESSING

*
*  CHECK FOR POSITION AT BEGINNING OF INFORMATION SECTION
*    IF SO, PRINT BACKUP ERROR MESSAGE AND RETURN W/ZERO SET
*
BOI$CHECK:
       LHLD    START$OF$INFO   ; START ADDRESS
       XCHG                    ; ... IN DE
       LHLD    START$OF$FRAME  ; FRAME ADDRESS
       MOV     A,D             ; EQUAL?
       CMP     H
       RNZ
       MOV     A,E
       CMP     L
       RNZ
       LXI     D,BACKERR       ; BACKUP ERROR
       CALL    PRINT$MESSAGE
       XRA     A               ; ZERO FLAG SET
       STA     FRAME$NUMBER    ; SET FRAME NUMBER
       RET

**************************************************************
*  THIS BODY OF CODE LISTS INFORMATION FROM HELP2 TO THE
*  PRINTER
**************************************************************

*
*  LIST ONE LINE OF INFO SECTION; HL PTS TO LINE UPON ENTRY;
*       HL PTS TO FIRST CHAR OF NEXT LINE UPON EXIT
*
LIST$LINE:
       MOV     A,M     ; GET CHAR
       ANI     7FH     ; MASK OUT MSB
       CPI     CR      ; EOL?
       JRZ     LCRLF
       CPI     LF      ; LINE FEED? (WS FILE)
       JRZ     LCRLF0
       CPI     CTRLZ   ; END OF FILE?
       JRZ     LCRLFC  ; DONE IF SO
       CALL    LST$OUT ; PRINT CHAR
       RZ              ; ABORT
       INX     H       ; PT TO NEXT
       JR      LIST$LINE

*
*  LIST CRLF, PT TO FIRST CHAR OF NEXT LINE, AND PAGE IF NECESSARY
*
LCRLF:
       INX     H       ; PT TO LF
LCRLF0:
       INX     H       ; PT TO 1ST CHAR OF NEXT LINE
LCRLFC:
       CALL    LCRLF1  ; PRINT CRLF
       LDA     LINE$CNT        ; GET LINE COUNT
       DCR     A
       STA     LINE$CNT
       JRNZ    LNOABT  ; OK -- CONTINUE
LCRLF1:
       MVI     A,CR    ; SEND <CRLF> TO PRINTER
       CALL    LST$OUT
       RZ              ; ABORT
       MVI     A,LF    ; FALL THRU TO LST$OUT

*  PRINT CHARACTER IN A ON PRINTER; AFFECT NO REGISTERS
LST$OUT:
       PUSH    H       ; SAVE REGS
       PUSH    D
       PUSH    B
       MOV     E,A     ; CHAR IN E
       MVI     C,5     ; BDOS PRINTER OUTPUT ROUTINE
       CALL    BDOS
       MVI     E,0FFH  ; CONDITIONAL INPUT
       MVI     C,6     ; DIRECT CONSOLE I/O
       CALL    BDOS
       POP     B       ; RESTORE REGS
       POP     D
       POP     H
       CPI     CTRLC   ; ABORT?
       RET
LNOABT:
       MVI     A,0FFH  ; SET NO ABORT RETURN
       ORA     A       ; SET FLAGS
       RET

*
*  LIST THE CURRENT INFORMATION SECTION
*    INFORMATION SECTION IS PTED TO BY START$OF$INFO
*
LIST1$INFO:
       LXI     D,PRINT$WAIT$MSG
       CALL    PRINT$MESSAGE
       LHLD    START$OF$INFO   ; PREPARE TO LIST ENTIRE INFO SECTION
       XRA     A               ; CLEAR FRAME LIST FLAG
       STA     LFR$FLAG
       CALL    CHAR$IN         ; GET USER INPUT
       CPI     'S'             ; SCREEN DISPLAY ONLY
       JRNZ    LIST2$INFO
       LHLD    START$OF$FRAME  ; LIST CURRENT FRAME ONLY
       MVI     A,0FFH          ; SET FLAG
       STA     LFR$FLAG        ; LIST FRAME ONLY
LIST2$INFO:
       CPI     CTRLC           ; CHECK FOR ^C TO ABORT
       CNZ     LIST$INFO       ; DO PRINTOUT
       LHLD    START$OF$FRAME  ; RETURN TO FRAME WE WERE ON
       CALL    FRAME$RESUME
       LDA     FRAME$NUMBER    ; ADJUST FRAME NUMBERING
       DCR     A
       STA     FRAME$NUMBER
       JMP     PI1             ; RESUME AT PI1
*
LIST$INFO:
       LXI     D,PRINT$LMSG    ; PRINT ABORT OPTION MESSAGE
       CALL    PRINT$MESSAGE
       CALL    SET$LINE$CNT
LI1:
       CALL    LIST$LINE       ; LIST LINE FROM INFO FILE
       CPI     CTRLC   ; ABORT?
       RZ
       MOV     A,M     ; DONE?
       ANI     7FH     ; MASK OUT MSB
       CPI     CTRLZ   ; EOF?
       RZ              ; RESUME IF AT END OF INFO
       CPI     SECT$CHAR       ; NEXT SECTION
       RZ              ; RESUME IF AT END OF INFO
       CPI     FF      ; FORM FEED?
       JRNZ    LI1
       INX     H       ; PT TO CHAR AFTER FORM FEED
       CALL    LFORM$FEED      ; FEED PRINTER
       LDA     LFR$FLAG        ; LIST FRAME ONLY?
       ORA     A               ; 0=NO
       RNZ
       JR      LI1

*  FORM FEED SCREEN
LFORM$FEED:
       CALL    LCRLFC  ; NEW LINE
       RET

**************************************************************
*  END OF BODY OF CODE WHICH LISTS INFORMATION FROM HELP2 TO
*  THE PRINTER
**************************************************************

*
*  AT THE BEGINNING OF AN INFORMATION SECTION (HL PTS TO FIRST CHAR)
*    CHECK TO SEE IF ANOTHER SECT$CHAR FOLLOWS, AND, IF SO, LOAD THE
*    SPECIFIED FILE AS A NEW NODE AND BEGIN PROCESSING IT
*
LOAD$NODE:
       INX     H       ; PT TO POSSIBLE 2ND SECT$CHAR
       MOV     A,M     ; GET IT
       DCX     H       ; PREP FOR RETURN
       ANI     7FH     ; MASK MSB
       CPI     SECT$CHAR       ; ANOTHER ONE?
       RNZ             ; PROCESS NORMALLY IF NOT

*  WE HAVE A NEW NODE -- CHECK TO SEE IF WE CAN NEST AGAIN
       LDA     HELP$LEVEL      ; GET CURRENT HELP LEVEL
       CPI     HELP$MAX        ; AT MAXIMUM?
       JRNZ    LOAD$NODE1
       LXI     D,LEVELERR      ; LEVEL ERROR MESSAGE
       CALL    PRINT$MESSAGE
       JMP     HELP$EXIT

*  WE HAVE NOT REACHED LEVEL LIMIT, SO CONTINUE
*  AT THIS TIME, A=HELP LEVEL INDEX AND HL = PTR TO CURRENT SECTION (::)
LOAD$NODE1:

*  SAVE CURRENT HELP FILE NAME FOR RETURN
       INX     H       ; PT TO SECTION SECT$CHAR
       INX     H       ; NOW POINTING TO FILE NAME
       PUSH    H       ; SAVE PTR
       CALL    COMP$HELP$NAME$PTR      ; HL=POINTER TO STACK ELT INDEXED BY A
       XCHG            ; DE=ADDRESS OF NEXT ELEMENT

*  COPY CURRENT NODE ELEMENT NAME INTO NEXT STACK ELEMENT
       LXI     H,FCB+1 ; PT TO FILE NAME
       MVI     B,11    ; 11 BYTES
       CALL    MOVE

*  INCREMENT HELP LEVEL
       LDA     HELP$LEVEL      ; GET OLD LEVEL
       INR     A       ; SET NEW LEVEL
       STA     HELP$LEVEL

*  SET UP FCB FOR NEW FILE
       LXI     D,LOADING$MSG
       CALL    PRINT$MESSAGE
       POP     H       ; GET PTR TO NEW FILE NAME
       LXI     D,FCB+1 ; PT TO FCB NAME
       MVI     B,8     ; 8 CHARS MAX
       CALL    LOAD$FCB        ; PLACE INTO FCB WITH ERROR CHECKING
       MVI     A,'.'   ; DECIMAL BETWEEN FILE NAME AND TYPE
       CALL    CHAR$OUT
       MVI     B,3     ; 3 CHARS MAX FOR TYPE
       CALL    LOAD$FCB        ; PLACE INTO FCB WITH ERROR CHECKING
       CALL    CRLF1   ; NEW LINE
       JMP     START1  ; LOAD NEW HELP FILE

*
*  LOAD FCB PTED TO BY DE WITH "NORMAL" FILE NAME PTED TO BY HL FOR B BYTES
*
LOAD$FCB:
       MOV     A,M     ; GET CHAR
       INX     H       ; PT TO NEXT
       CPI     '.'     ; DONE IF DECIMAL
       JRZ     LOAD$FCB$FILL
       CPI     ' '+1   ; DONE IF < <SP>
       JRC     LOAD$FCB$FILL
       CALL    CAPS    ; CAPITALIZE
       CALL    CHAR$OUT        ; PRINT FILE NAME AND TYPE
       STAX    D       ; STORE CHAR
       INX     D       ; PT TO NEXT
       DJNZ    LOAD$FCB
       MOV     A,M     ; CHECK FOR ERROR
       ANI     7FH     ; MASK MSB
       INX     H       ; PT TO NEXT CHAR
       CPI     '.'     ; OK IF '.'
       RZ
       CPI     ' '+1   ; OK IF <SP>
       RC
       LXI     D,LOADERR
       CALL    PRINT$MESSAGE
       JMP     HELP$EXIT
LOAD$FCB$FILL:
       MOV     C,A     ; SAVE CHAR THAT TERMINATED STRING
LOAD$FCB$LOOP:
       MVI     A,' '   ; <SP> FILL REST OF FCB
       STAX    D       ; STORE <SP>
       INX     D       ; PT TO NEXT
       DJNZ    LOAD$FCB$LOOP
       MOV     A,C     ; GET CHAR THAT TERMINATED STRING
       RET

*
*  GO TO ROOT
*
GO$ROOT:
       LDA     HELP$LEVEL      ; AT ROOT?
       ORA     A       ; 0=YES
       JZ      HELP    ; RETURN TO HELP
       MVI     A,0     ; SET ROOT INDEX
       JR      GORET

*
*  RETURN TO PREVIOUS HELP LEVEL
*
LEVEL$RETURN:
       LDA     HELP$LEVEL      ; ARE WE AT THE LOWEST LEVEL?
       ORA     A       ; 0=YES
       JRNZ    LRET
       LXI     D,LRETERR
       CALL    PRINT$MESSAGE
       JMP     HELP

*  SET NEW HELP LEVEL
LRET:
       DCR     A       ; DOWN 1

*  GO TO HELP LEVEL INDEXED IN A
GORET:
       STA     HELP$LEVEL      ; SET NEW HELP LEVEL
       CALL    COMP$HELP$NAME$PTR      ; HL=POINTER TO TARGET HELP FILE NAME
       PUSH    H       ; SAVE PTR TO FILE NAME
       LXI     D,LOADING$MSG   ; PRINT NAME OF FILE TO BE LOADED
       CALL    PRINT$MESSAGE
       MVI     B,8     ; 8 CHARS TO FILE NAME
GORET$NAME:
       MOV     A,M     ; GET CHAR
       CPI     ' '     ; END OF NAME?
       INX     H       ; PT TO NEXT
       JRZ     GORET$NAME0
       CALL    CHAR$OUT        ; PRINT FILE NAME
       DJNZ    GORET$NAME
       JMP     GORET$NAME1
GORET$NAME0:
       DCR     B       ; COUNT DOWN
       JRZ     GORET$NAME1
       INX     H       ; SKIP NEXT SPACE
       JR      GORET$NAME0
GORET$NAME1:
       MVI     A,'.'   ; PRINT DECIMAL
       CALL    CHAR$OUT
       MVI     B,3     ; PRINT FILE TYPE
GORET$NAME2:
       MOV     A,M     ; GET CHAR
       INX     H       ; PT TO NEXT
       CALL    CHAR$OUT        ; PRINT IT
       DJNZ    GORET$NAME2
       CALL    CRLF1   ; NEW LINE
       POP     H       ; GET PTR TO FILE NAME
       LXI     D,FCB+1 ; COPY ELEMENT INTO FCB
       MVI     B,11    ; 11 BYTES
       CALL    MOVE
       JMP     START1  ; LOAD ENTRY

*
*  COMPUTE POINTER TO HELP NAME ENTRY INDEXED BY HELP LEVEL IN A
*
COMP$HELP$NAME$PTR:
       CALL    COMP$OFFSET     ; COMPUTE OFFSET IN TABLE
       LXI     D,HELP$NAME$STACK       ; PT TO BASE OF HELP NAMES
       DAD     D       ; ADD IN OFFSET
       RET

*
*  COMPUTE OFFSET INTO TABLE BASED ON INDEX A
*    OFFSET = A * 11
*
COMP$OFFSET:
       MOV     L,A     ; VALUE IN HL
       MVI     H,0
       MOV     E,L     ; DE=HL
       MOV     D,H

       DAD     H       ; *2
       DAD     H       ; *4
       DAD     H       ; *8
       DAD     D       ; *9
       DAD     D       ; *10
       DAD     D       ; *11
       RET

*
*  PRINT LEVEL NUMBER
*
PR$LEVEL:
       LDA     HELP$LEVEL      ; DON'T PRINT LEVEL 0
       ORA     A       ; 0?
       JRZ     PR$FRAME
       LXI     D,LEVEL$MESSAGE ; PRINT HEADER
       CALL    PRINT$MESSAGE
       LDA     HELP$LEVEL      ; GET NUMBER
       CALL    PR$DEC          ; PRINT AS DECIMAL
       LXI     D,LEVEL2$MESSAGE        ; PRINT END HEADER
       CALL    PRINT$MESSAGE
PR$FRAME:
       LDA     FRAME$NUMBER    ; GET CURRENT FRAME NUMBER
       ORA     A       ; SET FLAGS
       RZ              ; NO FRAME?
       CALL    PR$DEC  ; PRINT AS DECIMAL
       LXI     D,LEVEL3$MESSAGE
       CALL    PRINT$MESSAGE
       RET
*  PRINT A AS DECIMAL
PR$DEC:
       PUSH    PSW     ; SAVE VALUE
       XRA     A
       STA     LD$SPACE
       POP     PSW     ; GET VALUE
       MVI     B,100   ; PRINT 1
00'S
       CALL    PDEC
       MVI     B,10    ; PRINT 10'S
       CALL    PDEC
       ADI     '0'     ; PRINT 1'S
       JMP     CHAR$OUT
PDEC:
       MVI     C,0     ; SET VALUE
PDEC1:
       SUB     B       ; SUBTRACT POWER
       JRC     PDEC2
       INR     C       ; INCREMENT VALUE
       JR      PDEC1
PDEC2:
       ADD     B       ; ADD POWER BACK IN
       MOV     B,A     ; SAVE A IN B
       LDA     LD$SPACE        ; GET LEADING <SP> FLAG
       ORA     A       ; NON-ZERO=PRINT
       JRNZ    PDEC3
       MOV     A,C     ; GET DIGIT
       STA     LD$SPACE        ; NEW FLAG
       ORA     A       ; ZERO?
       JRNZ    PDEC3   ; PRINT BYTE IN C
       MVI     A,' '   ; PRINT LEADING SPACE
       JR      PDEC4
PDEC3:
       MOV     A,C     ; GET VALUE
       ADI     '0'     ; CONVERT TO ASCII
PDEC4:
       CALL    CHAR$OUT        ; PRINT CHAR
       MOV     A,B     ; RESTORE A
       RET

*********************************************************
*  MESSAGE AND BUFFER SECTION                           *
*********************************************************

HELPMS:
       DB      'HELP for ZCPR2, Version '
       DB      (VERS/10)+'0','.',(VERS MOD 10)+'0',CR,LF,'$'
HELP$DEF$MSG:
       DB      CR,LF,'Default HELP Facility Invoked'
       DB      CR,LF,' Available HELP Files are --',CR,LF,'$'
HELP$EDEF$MSG:
       DB      CR,LF,' Type Any Character for Default Info (^C to Abort) - $'
SELECTMS:
       DB      CR,LF,'  HELP File Selections are --',CR,LF,'$'
ENDMS:
       DB      'EOI '
PAGEMS:
       DB      '^C=Exit $'             ; ABORT TO CP/M CHAR
PAGE1MS:
       DB      LEVEL$RET$CHAR,'=Level '        ; RETURN TO HIGHER NODE
       DB      ROOT$CHAR,'=Root $'             ; RETURN TO ROOT
PAGE2MS:
       DB      MENU$CHAR,'=Menu '      ; ABORT TO MENU CHAR
       DB      START$CHAR,'=Start '    ; JUMP TO START OF INFORMATION CHAR
       DB      BACKUP$CHAR,'=Last '    ; BACK UP TO PREVIOUS FRAME CHAR
       DB      PRINT$CHAR,'=Print '    ; PRINT CURRENT INFO SECTION
;       DB      'CR=Next '
       DB      '$'
PRINT$LMSG:
       DB      'Printing in Progress -- Strike ^C to Abort $'
PRINT$WAIT$MSG:
       DB      'Please Set Top-of-Form on Printer'
       DB      CR,LF,'  Strike S to Print this Screen Only, ^C to Abort, or'
       DB      CR,LF,'Any Other Char to Print Entire Information Section - $'
WILD$ERR:
       DB      CR,LF,'HELP FATAL ERROR -- File Name Contains Wild Card$'
ERR1:
       DB      CR,LF,'HELP FATAL ERROR -- File not Found$'
ERR2:
       DB      CR,LF,'HELP ERROR -- Invalid Response',CR,LF,'$'
ERR3:
       DB      CR,LF,'HELP ERROR -- EOF on HELP File',CR,LF,'$'
BACKERR:
       DB      CR,LF,'HELP ERROR -- Not Possible to Backup Before Start of '
       DB      'Info',CR,LF,'$'
LEVELERR:
       DB      CR,LF,'HELP ERROR -- Node Level Limit Reached -- Aborting'
       DB      CR,LF,'$'
LOADERR:
       DB      CR,LF,'HELP ERROR -- Invalid File Name in Load',CR,LF,'$'
LRETERR:
       DB      CR,LF,'HELP ERROR -- No Higher Level to Return to',CR,LF,'$'
READERR:
       DB      CR,LF,'HELP ERROR -- Not Enough Room for HELP File',CR,LF,'$'
LEVEL$MESSAGE:
       DB      'Level $'
LEVEL2$MESSAGE:
       DB      '/ $'
LEVEL3$MESSAGE:
       DB      ': $'
PROMPT1$MESSAGE:
       DB      'Type  ^C=Exit$'
PROMPT2$MESSAGE:
       DB      ' ',LEVEL$RET$CHAR,'=Level '
       DB      ROOT$CHAR,'=Root$'
PROMPT3$MESSAGE:
       DB      ' or Enter Selection $'
LOADING$MSG:
       DB      CR,LF,'Loading HELP File $'

LFR$FLAG:
       DS      1       ; LIST FRAME ONLY FLAG (FOR PRINT FUNCTION)
CUR$USER:
       DS      1       ; NUMBER OF CURRENT USER
TPA$END:
       DS      1       ; END PAGE ADDRESS OF TPA
START$OF$INFO:
       DS      2       ; PTR TO START OF CURRENT INFORMATION BLOCK
START$OF$FRAME:
       DS      2       ; PTR TO START OF CURRENT FRAME
SEL$CHAR:
       DS      1       ; SELECTION TABLE OPTION CHAR
FIRST$ENTRY:
       DS      2       ; PTR TO FIRST ENTRY OF INFORMATION SECTION
LINE$CNT:
       DS      1       ; LINE COUNT BUFFER
NAME$COUNT:
       DS      1       ; COUNT OF FILE NAMES/LINE
DFFLG:
       DS      1       ; DEFAULT FILE FLAG (0=NOT SEARCH FOR, 1=YES)
NEXT$ADR:
       DS      2       ; NEXT LOAD ADDRESS
LD$SPACE:
       DS      1       ; LEADING SPACE FLAG FOR DECIMAL PRINT
HELP$LEVEL:
       DS      1       ; NUMBER OF HELP LEVEL CURRENT NODE IS AT (0=BOTTOM)
FRAME$NUMBER:
       DS      1       ; NUMBER OF CURRENT FRAME

HELP$NAME$STACK:
       DS      11*HELP$MAX     ; STACK OF HELP FILE NAMES OF EACH LEVEL

       DS      80      ; STACK SPACE
STACK:
       DS      2       ; CP/M STACK PTR

*
*  Help Files are Loaded Here
*
HELP$BUF        equ     $

       END