TITLE   'LRUN  Library Run--a utility for .LBR files'
VERSION EQU     2$0     ;82-11-19 Added equates for user
;                        area to search for command.lbr.
;
;               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 Rubenstein, 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.
;
;--------------------------------------------------------------
;               USER MODIFIABLE EQUATES
;
;       Define a secondary search drive and user if .LBR is
;       not found after initial search of current area:
;
SSDRV:  EQU     'A'     ;Valid values are 'A' through 'P'.
SSUSR:  EQU     0       ;Valid values are  0  through 31.
;
;Default library may also be modified.  See label DFLTNAM.
;--------------------------------------------------------------
;
QUERY   SET     -1
@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)
;
CCPIN   ORG     TPA
       JMP     INTRO           ;Jump around signon
;
SIGNON:
       DB      'LRUN Ver '     ;Signon message
       DB      VERSION/10+'0'
       DB      '.'
       DB      VERSION MOD 10+'0'
       DB      CR,LF
       DB      ' Copyright (c) 1982  Gary P. Novosielski '
       DB      '$',CTRL AND 'Z'
;
INTRO:
       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
;
FIXLOOP:
;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.
;
       MOV     A,B
       ORA     C               ;test if finished
       JZ      FIXDONE
       DCX     B               ;count down
       MOV     A,E
       ANI     07H             ;on 8-byte boundry?
       JNZ     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
       JNC     NEXTADR
;
;CARRY was = 1.  Fix this byte.
       LDAX    D
       ADD     H               ;(H) is the page offset
       STAX    D
;
NEXTADR INX     D
       JMP     FIXLOOP
;
FIXDONE:
;Finished.  Jump to the first address in the new
;segment in high memory.
;
;First adjust the stack.  One garbage word was
;left by fixloop.
       INX     SP
       INX     SP
;
;(HL) still has the page address
       MOV     L,A     ;move zero to l
       PCHL            ;Stack is valid
SETUP:
;Any one-shot initialization code goes here.
;
       LXI     H,NOLOAD
       SHLD    CCPIN+1         ;Prevent reentry
;
;
       CPM     VER             ;Test version of CP/M in use
       CPI     20H             ;2.0 or better?
       JC      BADVER          ;No, bitch and quit.
;
       CPM     USR,QUERY       ;What's the current user area?
       STA     ENTUSR          ;Save for later.
;
       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.
;
;
DIROPN: CPM     OPN,LBRFIL      ;Open for directory read.
       INR     A               ;Was it found?
       JNZ     DIROK           ;yes, ok
       LXI     H,LBRFIL        ;No, test drive spec
       MOV     A,M             ; to see if it's
       ORA     A               ; explicit
       JNZ     NODIR           ;It is explicit.  Out of luck
       MVI     M,SSDRV-'@'     ;Look on secondary drive,
       CPM     USR,SSUSR       ; in secondary user.
       JMP     DIROPN          ; before giving up.
;
DIROK:
       CPM     DMA,TBUFF
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
       JNZ     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
       JMP     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
       JZ      FINDMBRL
;
       LXI     D,32            ;No match, point to next one.
       DAD     D
       JMP     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
       JZ      SCANBK
       CPI     '-'             ;library name specifier?
       JNZ     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
       JNZ     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
       JNZ     COMPEXIT
       INX     D
       INX     H
       DCR     B
       JNZ     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
       DCR     B
       JNZ     NITLP1
       MVI     B,4     ;zero others
NITLP2: MVI     M,0
       INX     H
       DCR     B
       JNZ     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
       JMP     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
       JZ      GETFT
       INX     D
       DCR     C
       JMP     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?
       JZ      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?
       JNZ     GETNAM  ;nope - keep filling
       JMP     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
       JNZ     QFILL   ;fall thru to ingore rest of name
GETDEL: CALL    GETCH   ;pointing to a delimiter?
       RZ              ;yup - all done
       INX     H       ;nope - ignore another one
       JMP     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:
;
BADVER:
       CALL    ABEND
       DB      'Can''t run under CP/M 1.4'
       DB      '$'
NODIR:
       CALL    ABEND
       DB      'Library not found'
       DB      '$'
FISHY:
       CALL    ABEND
       DB      'Name after "-" isn''t a library'
       DB      '$'
NOMEMB:
       CALL    ABEND
       DB      'Command not in directory'
       DB      '$'
NOLOAD:
       CALL    ABEND
       DB      'No program in memory'
       DB      '$'
NOFIT:
       CALL    ABEND
       DB      'Program too large to load'
       DB      '$'
;
COMLIT: DB      'COM'
;
DFLTNAM:DB      'COMMAND ' ; <---change this if you like---
LBRLIT: DB      'LBR'
;
ABEND:
       LDA     ENTUSR
       MOV     E,A
       CPM     USR             ;Reset to entry user.
       CPM     MSG,NEWLIN
       POP     D
       CPM     MSG
       CPM     DEL,SUBFILE
       CPM     MSG,ABTMSG
       JMP     EXIT
ABTMSG: DB      '...ABORTED.$'
NEWLIN: DB      CR,LF,'$'
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.
;*************************************************
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.
LBROPN:
; The library file is still open.  The open FCB has been
; moved up here into high memory with the loader code.
;
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
R      <JZ     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?
R      <JNZ    ERR>            ;No, bail out.
;
R      <LHLD   RANDOM>         ;Increment random record field
       INX     H
R      <SHLD   RANDOM>
;
R      <JMP    LOADLOOP>       ;Until done.
;
ERR:
       MVI     A,( JMP )       ;Prevent execution of bad code
       STA     TPA
R      <LXI    H,ERRX>
       SHLD    TPA+1
R      <JMP    LOADED>         ;Execute dummy program instead
ERRX:
       LXI     H,BOOT          ;One more time, but this time
       SHLD    TPA+1           ;Jump to BOOT
;
R      <LXI    D,LDMSG>
       CPM     MSG
R      <LXI    D,SUBFILE>      ;Abort SUBMIT if in progress
       CPM     DEL
LOADED:
R      <LDA    ENTUSR>
       MOV     E,A
       CPM     USR             ;Restore USR number from setup.
       CPM     DMA,TBUFF       ;Restore DMA adrs for user pgm
       CPM     CON,LF          ;Turn up a new line on console
       JMP     TPA
;
LDMSG:
       DB      'BAD LOAD$'
INDEX   DW      0
LENX    DW      0
ENTUSR  DB      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