TITLE   'LRUNZ  Library Run for ZCPR2 -- a utility for .LBR files'
VERSION EQU     1$0     ;82-08-06 Initial source release
       PAGE    60
;
; Requires MAC for assembly.  Due to the complexity of
; the relocation macros, this program may take a while
; to assemble.  Be prepared for periods of no disk activity
; on both passes before pressing panic button.  G.P.N.
;

;--------------------------NOTICE------------------------------
;
;   (c) Copyright 1982  Gary P. Novosielski
;       All rights reserved.
;
;   The following features courtesy of Ron Fowler:
;       1) command line reparsing and repacking (this allows
;       the former load-only program to become a load & run
;       utility).
;       2) code necessary to actually execute the loaded file
;       3) the HELP facility (LRUN with no arguments)
;       4) modified error routines to avoid warm-boot delay
;          (return to CCP directly instead)
;
;       Permission to distribute this program in source or
;       object form without prior written aproval is granted
;       only under the following conditions.
;
;               1. No charge is imposed for the program.
;               2. Charges for incidental costs including
;                  but not limited to media, postage, tele-
;                  communications, and data storage do not
;                  exceed those costs actually incurred.
;               3. This Notice and any copright notices in
;                  the object code remain intact
;
;                       (signed)  Gary P. Novosielski
;
;--------------------------------------------------------------
;
; LRUN is intended to be used in conjunction with libraries
; created with LU.COM, a library utility based upon the
; groundwork laid by Michael Rubenstien, with some additional
; inspiration from Leor Zolman's CLIB librarian for .CRL files.
;
; The user can place the less frequently used command (.COM)
; files in a library to save space, and  still be able to run
; them when required, by typing:
;       LRUN <normal command line>.
; The name of the library can be specified, but the greatest
; utility will be achieved by placing all commands in one
; library called COMMAND.LBR, or some locally defined name,
; and always letting LRUN use that name as the default.
;

;Syntax:
;       LRUN [-<lbrname>] <command> [<parameters>]
;
;where:
;<lbrname>      is the optional library name.  In the
;               distrubution version, this defaults to
;               COMMAND.LBR.  If the user wishes to use a
;               different name for the default, the 8-byte
;               literal at DFLTNAM below may be changed to
;               suit local requirements. The current drive
;               is searched for the .LBR file, and if not
;               found there, the A: drive is searched.
;               **Note that the leading minus sign (not a part
;               of the name) is required to indicate an
;               override library name is being entered.
;
;<command>      is the name of the .COM file in the library
;
;<line>         is the (possibly empty) set of parameters
;               which are to be passed to <command>, as in
;               normal CP/M syntax.  Notice that if the
;               library name is defaulted, the syntax is
;               simply:
;     LRUN <command line>
;               which is just the normal command line with
;               LRUN prefixed to it.
;
;Wheel Mod      If the 'CMDRUN' facility of ZCPR3 is enabled
               then it is very handy to take all of your less
               often used command files and place them in a
               'COMMAND.LBR' and rename LRUNZ to CMDRUN.
               Then whenever a command is not found by the
               CCP if will run 'CMDRUN' (actually LRUNZ3) which
               will look in the 'COMMAND.LBR' and run the command.
               Furthermore if you enable the 'ROOTONLY' ZCPR3 option
               then the CCP will automatically look at A:15 only
               for 'CMDRUN' which will then look for 'COMMAND.LBR'
               and run the command. This change to enable the
               Wheel Byte is almost an exact copy of PIP-ZCPR by
               John D'Amico

                                       Richard Cole

;
; 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
;

@SYS    SET     0
@KEY    SET     1
@CON    SET     2
@RDR    SET     3
@PUN    SET     4
@LST    SET     5
@DIO    SET     6
@RIO    SET     7
@SIO    SET     8
@MSG    SET     9
@INP    SET     10
@RDY    SET     11
@VER    SET     12
@LOG    SET     13
@DSK    SET     14
@OPN    SET     15
@CLS    SET     16
@DIR    SET     17
@NXT    SET     18
@DEL    SET     19
@FRD    SET     20
@FWR    SET     21
@MAK    SET     22
@REN    SET     23
@CUR    SET     25
@DMA    SET     26
@CHG    SET     30
@USR    SET     32
@RRD    SET     33
@RWR    SET     34
@SIZ    SET     35
@REC    SET     36
@LOGV   SET     37      ;2.2 only
@RWR0   SET     40      ;2.2 only
;
CPMBASE EQU     0
BOOT    SET     CPMBASE
BDOS    SET     BOOT+5
TFCB    EQU     BOOT+5CH
TFCB1   EQU     TFCB
TFCB2   EQU     TFCB+16
TBUFF   EQU     BOOT+80H
TPA     EQU     BOOT+100H
CTRL    EQU     ' '-1           ;Ctrl char mask
CR      SET     CTRL AND 'M'
LF      SET     CTRL AND 'J'
TAB     SET     CTRL AND 'I'
FF      SET     CTRL AND 'L'
BS      SET     CTRL AND 'H'
FALSE   SET     0
TRUE    SET     NOT FALSE
;
CPM     MACRO   FUNC,OPERAND,CONDTN
       LOCAL   PAST
       IF      NOT NUL CONDTN
       DB      ( J&CONDTN ) XOR 8
       DW      PAST
       ENDIF           ;;of not nul condtn
       IF      NOT NUL OPERAND
       LXI     D,OPERAND
       ENDIF           ;;of not nul operand
       IF      NOT NUL FUNC
       MVI     C,@&FUNC
       ENDIF
       CALL    BDOS
PAST:
       ENDM
;
BLKMOV  MACRO   DEST,SRCE,LEN,COND
       LOCAL   PAST
       JMP     PAST
@BMVSBR:
       MOV     A,B
       ORA     C
       RZ
       DCX     B
       MOV     A,M
       INX     H
       STAX    D
       INX     D
       JMP     @BMVSBR
BLKMOV  MACRO   DST,SRC,LN,CC
       LOCAL   PST
       IF      NOT NUL CC
       DB      ( J&CC ) XOR 8
       DW      PST
       ENDIF
       IF      NOT NUL DST
       LXI     D,DST
       ENDIF
       IF      NOT NUL SRC
       LXI     H,SRC
       ENDIF
       IF      NOT NUL LN
       LXI     B,LN
       ENDIF
       CALL    @BMVSBR
       IF      NOT NUL CC
PST:
       ENDIF
       ENDM
PAST:   BLKMOV  DEST,SRCE,LEN,COND
       ENDM

;
OVERLAY SET     0
; Macro Definitions
;
RTAG    MACRO   LBL
??R&LBL EQU     $+2-@BASE
       ENDM
;
RGRND   MACRO   LBL
??R&LBL EQU     0FFFFH
       ENDM
;
R       MACRO   INST
@RLBL   SET     @RLBL+1
       RTAG    %@RLBL
       INST-@BASE
       ENDM
;
NXTRLD  MACRO   NN
@RLD    SET     ??R&NN
@NXTRLD SET     @NXTRLD + 1
       ENDM
;
;
; Enter here from Console Command Processor (CCP)
;
       ORG     TPA
CCPIN:
;
;  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      0F900H  ; ADDRESS OF MULTIPLE COMMAND LINE BUFFER IF AVAILABLE

;
;  DISK/USER LIMITS
;
MDISK:
       DB      4       ; MAXIMUM NUMBER OF DISKS
MUSER:
       DB      15      ; 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      15      ; BEGINNING OF PRIVILEGED USER AREAS
PPASS:
       DB      'SYSTEM',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      0F800H  ; ADDRESS OF MEMORY-RESIDENT NAMED DIRECTORY
NDNAMES:
       DB      14      ; MAX NUMBER OF DIRECTORY NAMES
DNFILE:
       DB      'SYS     '      ; NAME OF DISK NAME FILE
       DB      'NDR'           ; TYPE OF DISK NAME FILE

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


;
;  END OF SINSFORM -- STANDARD DEFAULT PARAMETER DATA
;
;******************************************************************
;
DFLTNAM:
       DB      'COMMAND ' ; <---change this if you like---
LBRLIT:
       DB      'LBR'
;
SIGNON:
       DB      'LRUNZ  Version '       ;Signon message
       DB      VERSION/10+'0'
       DB      '.'
       DB      VERSION MOD 10+'0'
       DB      CR,LF
       DB      '$',CTRL AND 'Z'
       DB      ' Copyright (c) 1982  Gary P. Novosielski '
       DB      CR,LF
       DB      ' Modified for Use Under ZCPR2 by Richard Conn'
       DB      '$',CTRL AND 'Z'
;
;
;  START OF PROGRAM
;
START:
       LDA     WHEEL           ;load wheel byte
       MVI     E,9
       CMP     E               ;binary subtract
       JC      MSGA            ;jump to abort message if carry flag set
       JMP     START1          ;jump to lrunz if not aborted
MSG:
       DB      'Wheel Byte Not Set ==> Aborting $'
MSGA:
       MVI     C,09H           ;bdos print function
       LXI     D,MSG           ;pointer to abort message
       CALL    05H             ;do it
       JMP     0000H           ;warm boot if aborted
START1:
       LXI     H,0             ;get the CCP entry stackpointer
       DAD     SP              ;(used only if HELP request
       SHLD    SPSAVE          ; is encountered)
       CPM     MSG,SIGNON;     ;Display signon
       CALL    SETUP           ;initialize.
       LHLD    BDOS+1          ;find top of memory
       MOV     A,H             ;page address
                               ;Form destination...
       SUI     PAGES           ;...address in
       MOV     D,A             ;DE pair.
       MVI     E,0
       PUSH    D               ;save on stack
;
       BLKMOV  ,@BASE,SEGLEN   ;Move the active segment.
;
;The segment is now moved to high memory, but not
;properly relocated.  The bit table which specifies
;which addresses need to be adjusted is located
;just after the last byte of the source segment,
;so (HL) is now pointing at it.
       POP     D       ;beginning of newly moved code.
       LXI     B,SEGLEN;length of segment
       PUSH    H       ;save pointer to reloc info
       MOV     H,D     ;offset page address
;
;Scan through the newly moved code, and adjust any
;page addresses by adding (H) to them.  The word on
;top of the stack points to the next byte of the
;relocation bit table.  Each bit in the table
;corresponds to one byte in the destination code.
;A value of 1 indicates the byte is to be adjusted.
;A value of 0 indicates the byte is to be unchanged.
;
;Thus one byte of relocation information serves to
;mark 8 bytes of object code.  The bits which have
;not been used yet are saved in L until all 8
;are used.
;
FIXLOOP:
       MOV     A,B
       ORA     C               ;test if finished
       JRZ     FIXDONE
       DCX     B               ;count down
       MOV     A,E
       ANI     07H             ;on 8-byte boundry?
       JRNZ    NEXTBIT
;
NEXTBYT:
;Get another byte of relocation bits
       XTHL
       MOV     A,M
       INX     H
       XTHL
       MOV     L,A             ;save in register L
;
NEXTBIT:
       MOV     A,L             ;remaining bits from L
       RAL                     ;next bit to CARRY
       MOV     L,A             ;save the rest
       JRNC    NEXTADR
;
;CARRY was = 1.  Fix this byte.
       LDAX    D
       ADD     H               ;(H) is the page offset
       STAX    D
;
NEXTADR:
       INX     D
       JR      FIXLOOP
;
;Finished.  Jump to the first address in the new
;segment in high memory.
;
;First adjust the stack.  One garbage word was
;left by fixloop.
;
FIXDONE:
       INX     SP
       INX     SP
;
;(HL) still has the page address
;
       MOV     L,A     ;move zero to l
;
;  Set User/Disk for Return
;
       LDA     CDISK   ;Set Disk
       MOV     B,A
       LDA     CUSER   ;Set User
       MOV     C,A
       PCHL            ;Stack is valid
;
;Any one-shot initialization code goes here.
;
SETUP:
       LXI     H,NOLOAD
       SHLD    CCPIN+1         ;Prevent reentry
;
       CALL    REPARS          ;Re-parse command line
;
       LXI     D,MEMBER+9      ;Check member filetype
       LDAX    D
       CPI     ' '             ;If blank,
       BLKMOV  ,COMLIT,3,Z     ; default to COM.
;
       LXI     D,LBRFIL+9      ;Check library filetype
       LDAX    D
       CPI     ' '             ;If blank,
       BLKMOV  ,LBRLIT,3,Z     ; default to LBR
;
       LXI     D,LBRFIL+1      ;Check name
       LDAX    D
       CPI     ' '             ;If blank,
       BLKMOV  ,DFLTNAM,8,Z    ; use default name.
;
;  Set Current Disk and User and then Search Along ZCPR2 Path for
;    the Library File
;
       CPM     CUR             ;Get Current Disk
       STA     CDISK           ;Save It
       CPM     USR,0FFH        ;Get Current User
       STA     CUSER           ;Save It
       CPM     OPN,LBRFIL      ;Try to Open Library File Now
       INR     A               ;Zero Means Not Found
       JRNZ    DIROK           ;Process if Found
       XRA     A               ;Set to Select Current Disk
       STA     LBRFIL
       LXI     H,INTPATH       ;Point to Internal Path
       LDA     EPAVAIL         ;External Path Available?
       ORA     A               ;Zero = No
       JRZ     INPATH
       LHLD    EPADR           ;Get Address of External Path
INPATH:
       MOV     A,M             ;Get Drive
       ORA     A               ;0=Command Not Found
       JZ      NODIR           ;Not Found
       ANI     7FH             ;Mask MSB
       MOV     B,A             ;Save in B for a While
       LDA     CINDIC          ;Get Current Indicator
       CMP     B               ;Compare for Current
       JRNZ    INPATH1         ;Good Disk In B
       LDA     CDISK           ;Get Current Disk
       INR     A               ;Add 1 to be good
       MOV     B,A             ;Disk In B
INPATH1:
       DCR     B               ;Disk in Range 0-15
       INX     H               ;Pt to User Number
       MOV     A,M             ;Get User Number
       INX     H               ;Pt to Next Disk
       PUSH    H               ;Save Ptr
       ANI     7FH             ;Mask User Number
       MOV     C,A             ;User in C for now
       LDA     CINDIC          ;Compare to Current Indicator
       CMP     C               ;See if Current User
       JRNZ    INPATH2         ;Good User In C
       LDA     CUSER           ;User Current User
       MOV     C,A             ;User in C
INPATH2:
       MOV     E,B             ;Select Disk
       PUSH    B               ;Save User in C
       CPM     DSK             ;Set Disk
       POP     D               ;Get User in E
       CPM     USR             ;Set User
       CPM     DIR,LBRFIL      ;See if File Exists in this Directory
       POP     H               ;Restore Path Pointer
       INR     A               ;Error Code (Not Found) is Zero
       JRZ     INPATH          ;Continue Thru Path
DIROPN:
       CPM     OPN,LBRFIL      ;Open for directory read.
DIROK:
       CPM     DMA,TBUFF       ;Set DMA for Block Read
FINDMBR:
       CPM     FRD,LBRFIL      ;Read the directory
       ORA     A
       JNZ     FISHY           ;Empty file, Give up.
       LXI     H,TBUFF
       MOV     A,M
       ORA     A
       JNZ     FISHY           ;Directory not active??
       MVI     B,8+3           ;Check for blanks
       MVI     A,' '
VALIDLOOP:
       INX     H
       CMP     M
       JNZ     FISHY
       DCR     B
       JRNZ    VALIDLOOP
;
       LHLD    TBUFF+1+8+3     ;Index must be 0000
       MOV     A,H
       ORA     L
       JNZ     FISHY
;
       LHLD    TBUFF+1+8+3+2   ;Get directory size
       DCX     H               ;We already read one.
       PUSH    H               ;Save on stack
       JR      FINDMBRN        ;Jump into loop
FINDMBRL:
       POP     H               ;Read sector count from TOS
       MOV     A,H
       ORA     L               ;0 ?
       JZ      NOMEMB          ;Member not found in library
       DCX     H               ;Count down
       PUSH    H               ;and put it back.
       CPM     FRD,LBRFIL      ;Get next directory sector
       ORA     A
       JNZ     FISHY


FINDMBRN:
       LXI     H,TBUFF         ;Point to buffer.
       MVI     C,128/32        ;Number of directory entries
;
FINDMBR1:
       CALL    COMPARE         ;Check if found yet.
       JZ      GETLOC          ;Found member in .DIR
       DCR     C
       JRZ     FINDMBRL
;
       LXI     D,32            ;No match, point to next one.
       DAD     D
       JR      FINDMBR1
;
GETLOC:         ;The name was found now get index and length
       POP     B       ;Clear stack garbage
       XCHG            ;Pointer to sector address.
       MOV     E,M     ;Get First
       INX     H
       MOV     D,M
       XCHG
       SHLD    INDEX   ;Save it
       XCHG
       INX     H       ;Get Size to DE
       MOV     E,M
       INX     H
       MOV     D,M
       XCHG            ; Size to HL
       SHLD    LENX
       CALL    PACKUP  ;Repack command line arguments
       CPM     CON,CR  ;do <cr> only (look like CCP)
       RET
;               End of setup.
;
;       Utility subroutines
NEGDE:                  ;DE = -DE
       MOV     A,D
       CMA
       MOV     D,A
;
       MOV     A,E
       CMA
       MOV     E,A
       INX     D
       RET
;
;       REPARSE re-parses the fcbs from the command line,
;       to allow the "-" character to prefix the library name
;
REPARS:
       LXI     D,MEMBER        ;first reinitialize both fcbs
       CALL    NITF
       LXI     D,LBRFIL
       CALL    NITF
       LXI     H,TBUFF         ;store a null at the end of
       MOV     E,M             ; the command line (this is
       MVI     D,0             ; done
by CP/M usually, except
       XCHG                    ; in the case of a full com-
       DAD     D               ; mand line
       INX     H
       MVI     M,0
       XCHG                    ;tbuff pointer back in hl
SCANBK:
       INX     H               ;bump to next char position
       MOV     A,M             ;fetch next char
       ORA     A               ;reached a null? (no arguments)
       JZ      HELP            ;interpret as a call for help
       CPI     ' '             ;not null, skip blanks
       JRZ     SCANBK
       CPI     '-'             ;library name specifier?
       JRNZ    NOTLBR          ;skip if not
       INX     H               ;it is, skip over flag character
       LXI     D,LBRFIL        ;parse library name into FCB
       CALL    GETFN
NOTLBR:
       LXI     D,MEMBER        ;now parse the command name
       CALL    GETFN
       LXI     D,HOLD+1        ;pnt to temp storage for rest of cmd line
       MVI     B,-1            ;init a counter
CLSAVE:
       INR     B               ;bump up counter
       MOV     A,M             ;fetch a char
       STAX    D               ;move it to hold area
       INX     H               ;bump pointers
       INX     D
       ORA     A               ;test whether char was a terminator
       JRNZ    CLSAVE          ;continue moving line if not
       MOV     A,B             ;it was, get count
       STA     HOLD            ;save it in hold area
       RET
;
;       PACKUP retrieves the command line stored at
;       HOLD and moves it back to tbuff, then reparses
;       the default file control blocks so the command
;       will never know it was run from a library
;
PACKUP:
       LXI     H,HOLD          ;point to length byte of HOLD
       MOV     C,M             ;get length in BC
       MVI     B,0
       INX     B               ;bump up to because length byte doesn't
       INX     B               ;  include itself or null terminator
       BLKMOV  TBUFF           ;moving everybody to Tbuff
       LXI     H,TBUFF+1       ;point to the command tail
       LXI     D,TFCB1         ;first parse out tfcb1
       CALL    GETFN
       LXI     D,TFCB2         ;then tfcb2
       CALL    GETFN
       RET
;
;       Here when HELP is requested (indicated
;       by LRUN with no arguments)
;
HELP:
       CPM     MSG,HLPMSG      ;print the HELP message
EXIT:
       LHLD    SPSAVE          ;find CCP re-entry adrs
       SPHL                    ;fix & return
       RET
;
;       the HELP message
;
HLPMSG:
       DB      CR,LF,'Correct syntax is:'
       DB      CR,LF
       DB      LF,TAB,'LRUN [-<lbrname>] <command line>'
       DB      CR,LF
       DB      LF,'Where <lbrname> is the optional library name'
       DB      CR,LF,'(Note the preceding "-".  ) If omitted,'
       DB      CR,LF,'the default command library is used.'
       DB      LF
       DB      CR,LF,'<command line> is the name and parameters'
       DB      CR,LF,'of the command being run from the library,'
       DB      CR,LF,'just as if a separate .COM file were being run.'
       DB      CR,LF,'$'
;
;
COMPARE:                ;Test status, name and type of
       PUSH    H               ;a directory entry.
       MVI     B,1+8+3
       XCHG                    ;with the one we're
       LXI     H,MEMBER        ;looking for.
COMPAR1:
       LDAX    D
       CMP     M
       JRNZ    COMPEXIT
       INX     D
       INX     H
       DCR     B
       JRNZ    COMPAR1
COMPEXIT:                       ;Return with DE pointing to
       POP     H               ;last match + 1, and HL still
       RET                     ;pointing to beginning.
;
;
;       File name parsing subroutines
;
; getfn gets a file name from text pointed to by reg hl into
; an fcb pointed to by reg de.  leading delimeters are
; ignored.
; entry hl      first character to be scanned
;       de      first byte of fcb
; exit  hl      character following file name
;
;
;
GETFN:
       CALL    NITF    ;init 1st half of fcb
       CALL    GSTART  ;scan to first character of name
       RZ              ;end of line was found - leave fcb blank
       CALL    GETDRV  ;get drive spec. if present
       CALL    GETPS   ;get primary and secondary name
       RET


;
; nitf fills the fcb with dflt info - 0 in drive field
; all-blank in name field, and 0 in ex,s1,s2 and rc flds
;
NITF:
       PUSH    D       ;save fcb loc
       XCHG            ;move it to hl
       MVI     M,0     ;zap dr field
       INX     H       ;bump to name field
       MVI     B,11    ;zap all of name fld
NITLP1:
       MVI     M,' '
       INX     H
       DJNZ    NITLP1
       MVI     B,4     ;zero others
NITLP2:
       MVI     M,0
       INX     H
       DJNZ    NITLP2
       XCHG            ;restore hl
       POP     D       ;restore fcb pointer
       RET
;
; gstart advances the text pointer (reg hl) to the first
; non delimiter character (i.e. ignores blanks).  returns a
; flag if end of line (00h or ';') is found while scaning.
; exit  hl      pointing to first non delimiter
;       a       clobbered
;       zero    set if end of line was found
;
GSTART:
       CALL    GETCH   ;see if pointing to delim?
       RNZ             ;nope - return
       CPI     ';'     ;end of line?
       RZ              ;yup - return w/flag
       ORA     A
       RZ              ;yup - return w/flag
       INX     H       ;nope - move over it
       JR      GSTART  ;and try next char
;
; getdrv checks for the presence of a drive spec at the text
; pointer, and if present formats it into the fcb and
; advances the text pointer over it.
; entry hl      text pointer
;       de      pointer to first byte of fcb
; exit  hl      possibly updated text pointer
;       de      pointer to second (primary name) byte of fcb
;
GETDRV:
       INX     D       ;point to name if spec not found
       INX     H       ;look ahead to see if ':' present
       MOV     A,M
       DCX     H       ;put back in case not present
       CPI     ':'     ;is a drive spec present?
       RNZ             ;nope - return
       MOV     A,M     ;yup - get the ascii drive name
       SUI     'A'-1   ;convert to fcb drive spec
       DCX     D       ;point back to drive spec byte
       STAX    D       ;store spec into fcb
       INX     D       ;point back to name
       INX     H       ;skip over drive name
       INX     H       ;and over ':'
       RET
;
; getps gets the primary and secondary names into the fcb.
; entry hl      text pointer
; exit  hl      character following secondary name (if present)
;
GETPS:
       MVI     C,8     ;max length of primary name
       CALL    GETNAM  ;pack primary name into fcb
       MOV     A,M     ;see if terminated by a period
       CPI     '.'
       RNZ             ;nope - secondary name not given
                       ;return default (blanks)
       INX     H       ;yup - move text pointer over period
FTPOINT:
       MOV     A,C     ;yup - update fcb pointer to secondary
       ORA     A
       JRZ     GETFT
       INX     D
       DCR     C
       JR      FTPOINT
GETFT:
       MVI     C,3     ;max length of secondary name
       CALL    GETNAM  ;pack secondary name into fcb
       RET
;
; getnam copies a name from the text pointer into the fcb for
; a given maximum length or until a delimiter is found, which
; ever occurs first.  if more than the maximum number of
; characters is present, characters are ignored until a
; a delimiter is found.
; entry hl      first character of name to be scaned
;       de      pointer into fcb name field
;       c       maximum length
; exit  hl      pointing to terminating delimiter
;       de      next empty byte in fcb name field
;       c       max length - number of characters transfered
;
GETNAM:
       CALL    GETCH   ;are we pointing to a delimiter yet?
       RZ              ;if so, name is transfered
       INX     H       ;if not, move over character
       CPI     '*'     ;ambigious file reference?
       JRZ     AMBIG   ;if so, fill the rest of field with '?'
       STAX    D       ;if not, just copy into name field
       INX     D       ;increment name field pointer
       DCR     C       ;if name field full?
       JRNZ    GETNAM  ;nope - keep filling
       JR      GETDEL  ;yup - ignore until delimiter
AMBIG:
       MVI     A,'?'   ;fill character for wild card match
QFILL:
       STAX    D       ;fill until field is full
       INX     D
       DCR     C
       JRNZ    QFILL   ;fall thru to ingore rest of name
GETDEL:
       CALL    GETCH   ;pointing to a delimiter?
       RZ              ;yup - all done
       INX     H       ;nope - ignore antoher one
       JR      GETDEL
;
; getch gets the character pointed to by the text pointer
; and sets the zero flag if it is a delimiter.
; entry hl      text pointer
; exit  hl      preserved
;       a       character at text pointer
;       z       set if a delimiter
;
GETCH:
       MOV     A,M     ;get the character
       CPI     '.'
       RZ
       CPI     ','
       RZ
       CPI     ';'
       RZ
       CPI     ' '
       RZ
       CPI     ':'
       RZ
       CPI     '='
       RZ
       CPI     '<'
       RZ
       CPI     '>'
       RZ
       ORA     A       ;Set zero flag on end of text
       RET
;
;
; Error routines:
;
NODIR:
       CALL    ABEND
       DB      'Library not found'
       DB      '$'
FISHY:
       CALL    ABEND
       DB      'Name after "-" isn''t a library'
       DB      '$'
NOMEMB:
       LXI     H,MEMBER+1      ;Pt to first char of Command Name
       MVI     B,8             ;Print 8 Chars
NOMEMBL:
       MOV     E,M             ;Print Char
       INX     H               ;Pt to Next
       PUSH    H               ;Save Ptr and Count
       PUSH    B
       CPM     CON
       POP     B
       POP     H
       DJNZ    NOMEMBL
       CALL    ABEND1          ;No Initial New Line
       DB      ' -- Command not in directory'
       DB      '$'
NOLOAD:
       CALL    ABEND
       DB      'No program in memory'
       DB      '$'
NOFIT:
       POP     B       ;Clear Stack
       CALL    ABEND
       DB      'Program too large to load'
       DB      '$'
;
COMLIT:
       DB      'COM'
;
ABEND:
       CPM     MSG,NEWLIN
ABEND1:
       POP     D
       CPM     MSG
       CPM     DEL,SUBFILE
       CPM     MSG,ABTMSG
       JMP     EXIT
ABTMSG:
       DB      ' -- Aborting$'
NEWLIN:
       DB      CR,LF,'$'
CDISK:
       DS      1       ;CURRENT DISK BUFFER
CUSER:
       DS      1       ;CURRENT USER BUFFER
SPSAVE:
       DS      2               ;stack pointer save
;
       PAGE
;Adjust location counter to next 256-byte boundry
@BASE   ORG     ($ + 0FFH) AND 0FF00H
@RLBL   SET     0
;
; The segment to be relocated goes here.
; Any position dependent (3-byte) instructions
; are handled by the "R" macro.
;*************************************************
;
       PUSH    B       ;Save Original User/Disk
R      <LHLD   LENX>   ;Get length of .COM member to load.
       MVI     A,TPA/128
       ADD     L       ;Calculate highest address
       MOV     L,A     ;To see if it will fit in
       ADC     H       ;available memory
       SUB     L
       MOV     H,A
       REPT    7
       DAD     H
       ENDM
       XCHG
       CALL    NEGDE   ;IT'S STILL IN LOW MEMORY
R      <LXI    H,PROTECT>
       DAD     D
       JNC     NOFIT   ;Haven't overwritten it yet.
;
; The library file is still open.  The open FCB has been
; moved up here into high memory with the loader code.
;
LBROPN:
R      <LHLD   INDEX>          ;Set up for random reads
R      <SHLD   RANDOM>
       XRA     A
R      <STA    RANDOM+2>
;
       LXI     H,TPA
R      <SHLD   LOADDR>

;
; This high memory address and above, including CCP, must be
; protected from being overlaid by loaded program
;
PROTECT:
;
LOADLOOP:                       ;Load that sucker.
R      <LHLD   LENX>           ;See if done yet.
       MOV     A,L
       ORA     H
       JRZ     LOADED
       DCX     H
R      <SHLD   LENX>
;
R      <LHLD   LOADDR>         ;Increment for next time
       MOV     D,H
       MOV     E,L
       LXI     B,80H
       DAD     B
R      <SHLD   LOADDR>
       CPM     DMA             ;but use old value (DE)
;
R      <LXI    D,LBRFIL>
       CPM     RRD             ;Read the sector
       ORA     A               ;Ok?
       JRNZ    ERR             ;No, bail out.
;
R      <LHLD   RANDOM>         ;Increment random record field
       INX     H
R      <SHLD   RANDOM>
;
       JR      LOADLOOP        ;Until done.
;
ERR:
       MVI     A,( JMP )       ;Prevent execution of bad code
       STA     TPA
       LXI     H,BOOT
       SHLD    TPA+1
;
R      <LXI    D,LDMSG>
       CPM     MSG
R      <LXI    D,SUBFILE>      ;Abort SUBMIT if in progress
       CPM     DEL
LOADED:
       CPM     DMA,TBUFF       ;Restore DMA adrs for user pgm
       CPM     CON,LF          ;Turn up a new line on console
       POP     B               ;Restore Current User/Disk
       PUSH    B               ;Save it again
       MOV     E,B             ;Restore Disk
       CPM     DSK             ;Log In Disk
       POP     D               ;E=USER
       CPM     USR             ;Log In User
       JMP     TPA
;
LDMSG:
       DB      CR,LF,'Bad Load$'
INDEX:
       DW      0
LENX:
       DW      0
SUBFILE:
       DB      1,'$$$     SUB',0,0,0,0
       ;If used, this FCB will clobber the following one.
       ;but it's only used on a fatal error, anyway.
LBRFIL:
       DS      32              ;Name placed here at setup
       DB      0               ;Normal FCB plus...
OVERLAY SET     $               ;(Nothing past here but DS's)
RANDOM:
       DS      3               ;...Random access bytes
MAXMEM:
       DS      2
LOADDR:
       DS      2
;*************************************************
;End of segment to be relocated.
       IF      OVERLAY EQ 0
OVERLAY SET     $
       ENDIF
;
PAGES   EQU     ($-@BASE+0FFH)/256+8
;
SEGLEN  EQU     OVERLAY-@BASE
       ORG     @BASE+SEGLEN
       PAGE
;       Build the relocation information into a
; bit table immediately following.
;
@X      SET     0
@BITCNT SET     0
@RLD    SET     ??R1
@NXTRLD SET     2
       RGRND   %@RLBL+1        ;define one more label
;
       REPT    SEGLEN+8
       IF      @BITCNT>@RLD
       NXTRLD  %@NXTRLD        ;next value
       ENDIF
       IF      @BITCNT=@RLD
@X      SET     @X OR 1         ;mark a bit
       ENDIF
@BITCNT SET     @BITCNT + 1
       IF      @BITCNT MOD 8 = 0
       DB      @X
@X      SET     0       ;clear hold variable for more
       ELSE
@X      SET     @X SHL 1        ;not 8 yet. move over.
       ENDIF
       ENDM
;
       DB      0
HOLD:
       DB      0,0             ;0 length, null terminator
       DS      128-2           ;rest of HOLD area
MEMBER:
       DS      16
;
       END     CCPIN