;  PROGRAM:  MCOPY
;  AUTHOR:  RICHARD CONN
;  VERSION:  3.0
;  DATE:  16 JAN 83
;  PREVIOUS VERSIONS:  2.8 (14 JAN 83), 2.7 (11 JAN 83)
;  PREVIOUS VERSIONS:  2.6 (9 JAN 83), 2.5 (8 JAN 83), 2.4 (7 JAN 83)
;  PREVIOUS VERSIONS:  2.3 (6 JAN 83), 2.2 (19 DEC 82)
;  PREVIOUS VERSIONS:  2.1 (7 DEC 82), 2.0 (14 NOV 82), 1.7 (21 JULY 82)
;  PREVIOUS VERSIONS:  1.6 (12 JULY 82), 1.5 (12 JULY 82)
;  PREVIOUS VERSIONS:  1.4 (10 JULY 82), 1.3 (9 JULY 82)
;  PREVIOUS VERSIONS:  1.0 (27 Oct 80), 1.1 (2 NOV 80), 1.2 (11 APR 81)
VERS    equ     30

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


;
;       MCOPY is a program which repeatedly copies a file from drive
; A: onto drive B:.  It prompts the user to mount a disk in drive B:,
; copies the file from drive A: to drive B:, verifies the copy (if not
; overridden), and then performs the function again.
;
;       MCOPY performs its function in the following steps:
;               1.  If CP/M 2.x or MP/M, MCOPY determines the attributes
; of the destination file (if it exists) and clears them (file becomes
; R/W and DIR)
;               2.  MCOPY deletes the destination file (if it exists)
;               3.  MCOPY copies the source file to the destination
;               4.  If CP/M 2.x or MP/M, MCOPY determines the attributes
; of the source file and makes the attributes of the destination file
; identical to those of the source
;               5.  MCOPY reads both the source and destination files and
; compares them byte-for-byte
;

;  CP/M Constants
CPM     EQU     0       ; CP/M WARM BOOT
BDOSE   EQU     CPM+5   ; BDOS ENTRY POINT
FCB     EQU     CPM+5CH ; SPECIFIED FCB
BUFF    EQU     CPM+80H ; DEFAULT BUFFER AND INPUT LINE

;  ASCII Constants, et al
ON      EQU     0FFH    ; ON CODE
OFF     EQU     0       ; OFF CODE
CR      EQU     0DH     ; <CR>
LF      EQU     0AH     ; <LF>
CTRLC   EQU     'C'-'@' ; ^C
CTRLZ   EQU     'Z'-'@' ; ^Z
OPTC    EQU     '/'     ; OPTION DELIMITER
DIV     EQU     '!'     ; COPY/VERIFY PHASE DELIMITER
FLIMIT  EQU     1024    ; 1024 FILES PERMITTED

;
;  SYSLIB ROUTINES
;
       EXT     CLINE,COMPHD,ZGPINS,RETUD,LOGUD,BLINE
       EXT     ZFNAME,DPARAMS,DIRF,DIRFS,FSIZE,DFREE
       EXT     DIRPACK,INITFCB,F$EXIST
       EXT     EVAL,CRCCLR,CRCUPD,CRCDONE
       EXT     BDOS,CIN,COUT
       EXT     F$DELETE,F$OPEN,F$MAKE,F$CLOSE,F$READ,F$WRITE
       EXT     PHLDC,PADC,PSTR,PRINT
       EXT     MOVEB,CAPS,CRLF
       EXT     CODEND

;
;  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      0FFH    ; MULTIPLE COMMAND LINE?
MXREQD:
       DB      0FFH    ; MAX USER/DISK?
UDREQD:
       DB      0FFH    ; ALLOW USER/DISK CHANGE?
PUREQD:
       DB      0FFH    ; PRIVILEGED USER?
CDREQD:
       DB      0FFH    ; CURRENT INDIC AND DMA?
NDREQD:
       DB      0FFH    ; NAMED DIRECTORIES?
Z2CLASS:
       DB      2       ; CLASS 2
       DB      'ZCPR2'
       DS      10      ; RESERVED

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

;
;  USER-DEFINABLE INITIAL FLAG CONDITIONS
;    THE DEFAULT CONDITIONS FOR MCOPY MAY BE READILY PATCHED BY THE USER
; VIA DDT FOR HIS DESIRED DEFAULT VALUES
;
DVERFLG:
       DB      ON      ; SET VERIFY OPTION
DINSP:
       DB      OFF     ; SET NO INSPECT
DQUIET:
       DB      OFF     ; SET NO QUIET OPERATION
DNCOPY:
       DB      OFF     ; SET NO MULTIPLE COPIES BY DEFAULT
DDDISK:
       DB      'C'-'A' ; DEFAULT DESTINATION DISK IS C
DDUSER:
       DB      0       ; DEFAULT DESTINATION USER IS 0

;
;  BEGINNING OF MCOPY PROGRAM
;
START:
       CALL    ZGPINS  ; INIT ZCPR2 BUFFERS
;
;  PRINT BANNER
;
       CALL    BANNER
;
;  SET DEFAULT FLAGS
;
       LDA     DVERFLG ; VERIFY
       STA     VERFLG
       LDA     DINSP   ; INSPECT
       STA     INSP
       LDA     DQUIET  ; QUIET
       STA     QUIET
       LDA     DNCOPY  ; MULTIPLE COPIES
       STA     NCOPY
       LDA     DDDISK  ; GET DEFAULT DEST DISK
       STA     DDISK   ; SET DEST DISK
       LDA     DDUSER  ; GET DEFAULT DEST USER
       STA     DUSER   ; SET DEST USER
;
;  OBTAIN AND SAVE CURRENT USER AND DISK
;
       CALL    RETUD   ; GET USER/DISK
       MOV     A,B     ; SAVE DISK
       STA     CDISK
       STA     SDISK   ; SET DEFAULT SOURCE DISK
       MOV     A,C     ; SAVE USER
       STA     CUSER
       STA     SUSER   ; SAVE DEFAULT SOURCE USER
       LXI     H,BUFF  ; PT TO COMMAND LINE CHAR COUNT
       CALL    CLINE   ; SAVE COMMAND LINE AS STRING
;
;  SET OTHER FLAGS
;
       XRA     A       ; A=0
       STA     EXIST   ; TURN OFF EXIST TEST
;
;  CHECK FOR EMPTY COMMAND LINE AND PROCESS COMMAND MODE IF SO
;    ON ENTRY, HL PTS TO FIRST CHAR OF STRING FROM CLINE
;
START1:
       MOV     A,M     ; GET CHAR
       ORA     A       ; EOL?
       JZ      MRUNNER ; INTERACTIVE COMMAND SESSION
       INX     H       ; PT TO NEXT
       CPI     ' '     ; JUST SPACES?
       JZ      START1
;
;  COMMAND LINE WAS NOT EMPTY -- CHECK FOR HELP REQUEST
;
       DCX     H       ; PT TO FIRST CHAR
       CPI     '/'     ; IF OPENING OPTION, MUST BE HELP
       JZ      MHELP
;
;  SEE IF OPTIONS ARE AVAILABLE IN THE COMMAND LINE
;
       SHLD    MFPTR   ; SET PTR TO FIRST CHAR OF FILE NAME SPECS
;
;  SKIP TO END OF FILE NAME SPECS
;
START2:
       MOV     A,M     ; SKIP TO <SP> OR EOL
       INX     H       ; PT TO NEXT
       CPI     ' '+1   ; <SP> OR LESS?
       JNC     START2
       ORA     A       ; AT EOL?
       JZ      MCOPY0  ; PERFORM DEFAULT MCOPY FUNCTION IF AT EOL
;
;  SCAN FOR OPTION
;
OPTION:
       MOV     A,M     ; GET OPTION CHAR
       ORA     A       ; EOL?
       JZ      MCOPY0  ; DO MCOPY
       INX     H       ; PT TO NEXT
       PUSH    H       ; SAVE PTR
       LXI     H,OPTTAB        ; PT TO OPTION TABLE
       CALL    CMDER   ; PROCESS COMMAND
       POP     H       ; GET PTR
       JMP     OPTION

;  COMMAND PROCESSOR -- COMMAND LETTER IN A, HL PTS TO TABLE
CMDER:
       PUSH    B       ; SAVE BC
       CALL    CAPS    ; CAPITALIZE COMMAND
       MOV     B,A     ; COMMAND IN B
CMDER1:
       MOV     A,M     ; GET COMMAND LETTER
       ORA     A       ; DONE?
       JZ      CMDER2
       CMP     B       ; MATCH?
       JNZ     CMDER3
CMDER2:
       INX     H       ; PT TO ADDRESS
       MOV     E,M     ; GET IT IN DE
       INX     H
       MOV     D,M
       XCHG            ; HL PTS TO COMMAND ADDRESS
       POP     B       ; RESTORE BC
       PCHL            ; RUN COMMAND
CMDER3:
       INX     H       ; SKIP TO NEXT ENTRY IN TABLE
       INX     H
       INX     H
       JMP     CMDER1

;  OPTION COMMAND TABLE
OPTTAB:
       DB      ' '     ; DONE
       DW      OPTS
       DB      OPTC    ; SKIP OPTC
       DW      OPTS
       DB      'E'     ; EXIST TEST
       DW      OPTE
       DB      'I'     ; INSPECT
       DW      OPTI
       DB      'M'     ; MULTIPLE COPY
       DW      OPTM
       DB      'Q'     ; QUIET
       DW      OPTQ
       DB      'V'     ; VERIFY
       DW      OPTV
       DB      0       ; END OF TABLE
       DW      OHELP

;  INVALID OPTION CHAR -- CLEAR STACK (RET ADR AND HL) AND PRINT HELP
OHELP:
       POP     H       ; CLEAR RET ADR
       POP     H       ; CLEAR HL

;  PRINT HELP MESSAGE
MHELP:
       CALL    PRINT
       DB      'MCOPY -- Multiple File Copy Program',CR,LF
       DB      '  MCOPY copies files from the disk on Drive A: to several'
       DB      CR,LF,'other disks, successively mounted on Drive '
       DB      'B:',CR,LF
       DB      '  MCOPY command line is:',CR,LF,LF
       DB      '       MCOPY [dir:=][dir:]filename.typ[,[dir:]fn.typ][,...]'
       DB      ' [ooo]',CR,LF,LF
       DB      'where options are enclosed by "[]", "dir:" is a named dir of '
       DB      'the form:',CR,LF
       DB      '       direct: (named dir) or du: (disk/user)',CR,LF
       DB      '"filename.typ" is the ambiguous file spec of the files to '
       DB      'copy ',CR,LF
       DB      'and "o" is none or more of:',CR,LF
       DB      '       E -- Test of Existence of File and Allow User to '
       db      'Approve',CR,LF
       DB      '       I -- Allow User to Approve Each File (Inspect)',CR,LF
       DB      '       M -- Enable Multiple Copy Feature',CR,LF
       DB      '       Q -- Quiet Operation (No Activity Display)',CR,LF
       DB      '       V -- Disable Automatic Verify',CR,LF
       DB      '       ? -- Print this HELP information',CR,LF
       DB      '  If "dir:=" is specified, MCOPY copies to the indicated '
       DB      'directory,',CR,LF
       DB      'else it copies to the default directory.',CR,LF
       DB      '  The user may interact directly with MCOPY by using just'
       DB      CR,LF,'"MCOPY" as his command.',CR,LF
       DB      0
       RET             ; RETURN TO ZCPR2

;  VERIFY FLAG TOGGLE OPTION
OPTV:
       CALL    VT      ; TOGGLE VERFLG
;  SKIP OPTION
OPTS:
       RET

;  EXIST TEST TOGGLE OPTION
OPTE:
       CALL    ET      ; TOGGLE EXIST
       RET

;  NCOPY FLAG TOGGLE OPTION
OPTM:
       CALL    MT      ; TOGGLE NCOPY
       RET

;  INSPECT FLAG TOGGLE OPTION
OPTI:
       CALL    IT      ; TOGGLE INSPECT
       RET

;  QUIET FLAG TOGGLE OPTION
OPTQ:
       CALL    QT      ; TOGGLE QUIET
       RET

;
;  **** INTERACTIVE MCOPY LOOP ****
;
MRUNNER:
       LXI     SP,STACK        ; RESET STACK
       CALL    PRINT
       DB      CR,LF,'MCOPY Status: ',0
       LDA     EXIST   ; EXISTENCE TEST
       ORA     A       ; 0=NO
       MVI     A,'E'   ; PREP FOR CHAR
       CALL    PMODE
       LDA     INSP    ; FILE INSPECTION
       ORA     A       ; 0=NO
       MVI     A,'I'   ; PREP FOR CHAR
       CALL    PMODE
       LDA     NCOPY   ; MULTIPLE COPIES
       ORA     A       ; 0=NO
       MVI     A,'M'   ; PREP FOR CHAR
       CALL    PMODE
       LDA     QUIET   ; QUIET MODE
       ORA     A       ; 0=NO
       MVI     A,'Q'   ; PREP FOR CHAR
       CALL    PMODE
       LDA     VERFLG  ; VERIFY
       ORA     A       ; 0=NO
       MVI     A,'V'   ; PREP FOR CHAR
       CALL    PMODE
       CALL    PRINT
       DB      ' -- MCOPY Command (? for Help)? ',0
       CALL    CIN     ; GET RESPONSE
       CALL    COUT    ; ECHO
       LXI     H,CMDTBL        ; PT TO MCOPY COMMAND TABLE
       CALL    CMDER   ; PROCESS COMMANDS
       JMP     MRUNNER ; CONTINUE
;
;  MCOPY COMMAND TABLE AND EXECUTED ROUTINES
;
CMDTBL:
       DB      'C'     ; COPY
       DW      CMDC
       DB      'D'     ; DIRECTORY
       DW      CMDD
       DB      'E'     ; EXISTENCE TEST
       DW      CMDE
       DB      'F'     ; FREE SPACE
       DW      CMDF
       DB      'I'     ; INSPECT
       DW      CMDI
       DB      'L'     ; LOG IN DISK
       DW      CMDL
       DB      'M'     ; MULTIPLE COPIES
       DW      CMDM
       DB      'Q'     ; QUIET
       DW      CMDQ
       DB      'S'     ; STATUS
       DW      CMDS
       DB      'V'     ; VERIFY
       DW      CMDV
       DB      'X'     ; EXIT
       DW      CMDX
       DB      'C'-'@' ; ^C
       DW      CMDX
       DB      0       ; END OF TABLE
       DW      MCPYHLP ; HELP MESSAGE

;  COPY COMMAND TO COPY A SET OF FILES
CMDC:
       CALL    PRINT
       DB      0DH,0AH,'       File Spec (<CR>=Abort)? ',0
       LXI     H,INBUF ; INPUT LINE BUFFER
       MVI     A,0FFH  ; CAPITALIZE INPUT
       CALL    BLINE   ; GET LINE FROM USER
       ORA     A       ; ABORT IF NONE
       RZ
CMDC1:
       MOV     A,M     ; SKIP OVER SPACES
       INX     H       ; PT TO NEXT
       CPI     ' '
       JZ      CMDC1
       DCX     H       ; PT TO NON-SPACE CHAR
       ORA     A       ; EOL?
       RZ
       SHLD    MFPTR   ; PT TO FIRST CHAR OF FILE NAME SPEC
       CALL    COPY    ; COPY FILES
       RET

;  DISPLAY DIRECTORY
CMDD:
       CALL    PRINT
       DB      CR,LF,'** Directory Display **',0
       MVI     C,13    ; RESET SYSTEM
       CALL    BDOS
       LXI     H,0     ; SET TOTAL FILE SIZES
       SHLD    FTOTAL
       CALL    PRINT
       DB      CR,LF,' File Spec (<CR>=',0
       LDA     DDISK   ; PRINT DISK/USER
       ADI     'A'
       CALL    COUT
       LDA     DUSER
       CALL    PADC
       CALL    PRINT
       DB      ':*.*)? ',0
       LXI     H,INBUF ; INPUT LINE BUFFER
       MVI     A,0FFH  ; CAPITALIZE INPUT
       CALL    BLINE   ; GET LINE FROM USER
       ORA     A       ; WILD IF NONE
       JNZ     CMDD1
       LXI     D,WILD  ; MAKE FILE SPEC WILD
       XCHG            ; COPY INTO BUFFER
       MVI     B,4     ; 4 BYTES
       CALL    MOVEB
       XCHG            ; PT TO FIRST CHAR WITH HL
CMDD1:
       PUSH    H       ; SAVE PTR
       CALL    CODEND  ; GET SCRATCH AREA ADDRESS
       MOV     B,H     ; ... IN BC
       MOV     C,L
       POP     H
       LXI     D,FCBS  ; LOAD FCB
       CALL    ZFNAME  ; EXTRACT FILE NAME INFO
       JZ      CMDUDER ; ERROR?
       MOV     A,B     ; GET DISK
       CPI     0FFH    ; CURRENT DISK?
       JNZ     CMDD2
       LDA     DDISK   ; GET DEST DISK
       INR     A       ; ADJUST
CMDD2:
       DCR     A       ; ADJUST FOR SELECT
       STA     DDISK   ; SET DEST DISK
       MOV     A,C     ; GET USER
       CPI     '?'     ; '?' IS INVALID
       JZ      CMDUDER
       CPI     0FFH    ; CURRENT USER?
       JNZ     CMDD3
       LDA     DUSER   ; GET DEST USER
CMDD3:
       STA     DUSER   ; SET DEST USER
       CALL    LOGD    ; LOG IN DEST
       CALL    CODEND  ; HL PTS TO BUFFER
       LXI     D,FCBS  ; DE PTS TO FCB
       LDA     DUSER   ; GET USER
       ORI     0C0H    ; SELECT BOTH NON-SYS AND SYS FILES
       CALL    DIRFS   ; LOAD WITH SIZING INFO
       JZ      CMDTPA  ; TPA OVERFLOW
       MOV     A,B     ; ANY FILES?
       ORA     C       ; 0=NONE
       JNZ     CMDD4
       CALL    PRINT
       DB      CR,LF,'No Matching Files',0
       RET
CMDD4:
       XRA     A       ; SET COUNT
       STA     BCNT
       CALL    CRLF    ; NEW LINE
CMDD5:
       INX     H       ; PT TO FILE NAME
       CALL    PRFN    ; PRINT FILE NAME
       DCX     H       ; PT TO USER
       CALL    FSIZE   ; COMPUTE FILE SIZE
       PUSH    H       ; SAVE HL
       LHLD    FTOTAL  ; GET ACCUMULATED TOTAL
       DAD     D       ; ADD IN NEW FILE
       SHLD    FTOTAL  ; NEW TOTAL
       POP     H       ; GET PTR
       XCHG            ; FILE SIZE IN HL
       CALL    PHLDC   ; PRINT AS DECIMAL
       MVI     A,'K'
       CALL    COUT
       LXI     H,16    ; PT TO NEXT ENTRY
       DAD     D       ; HL PTS TO NEXT ENTRY
       DCX     B       ; COUNT DOWN
       MOV     A,B     ; DONE?
       ORA     C
       JZ      CMDD6
       LDA     BCNT    ; NEW LINE COUNT
       INR     A
       STA     BCNT
       ANI     3       ; NEW LINE EVERY 4 ENTRIES
       JZ      CMDD5A
       CALL    PRINT   ; PRINT FENCE SINCE NOT NEW LINE
       DB      '  ',0
       JMP     CMDD5
CMDD5A:
       CALL    CRLF    ; NEW LINE
       JMP     CMDD5
CMDD6:
       CALL    PRINT
       DB      CR,LF,'** ',0
       LHLD    FTOTAL  ; PRINT TOTAL SPACE USED
       CALL    PHLDC   ; PRINT AS DECIMAL
       CALL    PRINT
       DB      'K Occupied by Displayed Files, ',0
       CALL    DFREE   ; COMPUTE AMOUNT OF FREE SPACE LEFT ON DISK
       XCHG            ; HL=FREE SPACE
       CALL    PHLDC   ; PRINT AS DEC
       CALL    PRINT
       DB      'K Remaining on Disk ',0
       LDA     DDISK   ; PRINT DISK LETTER
       ADI     'A'
       CALL    COUT
       CALL    PRINT
       DB      ' **',0
       RET
CMDUDER:
       CALL    PRINT
       DB      CR,LF,'Invalid User or Disk Specified',0
       RET
CMDTPA:
       CALL    PRINT
       DB      CR,LF,'TPA Overflow',0
       RET
WILD:   DB      '*.*',0

;  COMPUTE AMOUNT OF FREE SPACE LEFT ON DESTINATION DISK
CMDF:
       CALL    PRINT
       DB      CR,LF,'** Free Space Data **',0
       MVI     C,13    ; RESET SYSTEM
       CALL    BDOS
       CALL    PRINT
       DB      CR,LF,' Disk (<CR>=',0
       LDA     DDISK   ; GET DEST DISK
       ADI     'A'     ; CONVERT TO LETTER
       CALL    COUT
       CALL    PRINT
       DB      ')? ',0
       CALL    CIN     ; GET RESPONSE
       CALL    CAPS
       CALL    COUT
       CALL    CRLF    ; NEW LINE
       CPI     CR      ; SOURCE DISK?
       JZ      CMDF1
       CPI     ' '     ; SOURCE DISK?
       JZ      CMDF1
       SUI     'A'     ; CONVERT TO DISK NUMBER
       STA     DDISK   ; SET DISK
       JC      CMDFER
       MOV     B,A     ; SAVE IN B
       LDA     MDISK   ; COMPARE AGAINST MAX
       CMP     B
       JC      CMDFER
CMDF1:
       CALL    LOGD    ; LOG IN DEST
       CALL    DPARAMS ; GET DISK PARAMETERS
       CALL    DFREE   ; COMPUTE FREE SPACE
       XCHG            ; HL=SPACE
       CALL    CRLF
       CALL    PHLDC   ; PRINT AS DECIMAL
       CALL    PRINT
       DB      'K Bytes Remaining on Disk ',0
       LDA     DDISK
       ADI     'A'
       CALL    COUT
       RET
CMDFER:
       CALL    PRINT
       DB      CR,LF,'Error -- Disk Letter Invalid',0
       RET

;  TOGGLE INSPECT
CMDI:
       CALL    ITOG    ; TOGGLE WITH MESSAGE
       RET

;  LOG IN NEW USER/DISK
CMDL:
       CALL    PRINT
       DB      CR,LF,'Select Current Disk/User --',0
       LXI     H,CDISK ; PT TO ENTRY
       CALL    CMDL0
       CALL    PRINT
       DB      CR,LF,'Select Source Disk/User --',0
       LXI     H,SDISK ; PT TO ENTRY
       CALL    CMDL0
       CALL    PRINT
       DB      CR,LF,'Select Destination Disk/User --',0
       LXI     H,DDISK ; PT TO ENTRY
CMDL0:
       CALL    PRINT
       DB      CR,LF,' New Disk (<CR>=',0
       MOV     A,M     ; GET DISK
       ADI     'A'
       CALL    COUT
       CALL    PRINT
       DB      ')? ',0
       CALL    CIN     ; GET RESPONSE
       CALL    CAPS
       CALL    COUT
       CALL    CRLF
       CPI     CR
       JZ      CMDL1
       CPI     ' '
       JZ      CMDL1
       SUI     'A'     ; CONVERT TO NUMBER
       JC      CMDLER
       MOV     B,A
       LDA     MDISK   ; CHECK AGAINST MAX
       CMP     B
       JC      CMDLER
       MOV     A,B     ; GET SELECTED DISK
       MOV     M,A     ; PUT DISK
CMDL1:
       INX     H       ; PT TO USER
CMDL2:
       PUSH    H       ; SAVE PTR
       CALL    PRINT
       DB      '       New User (<CR>=',0
       MOV     A,M     ; GET USER NUMBER
       CALL    PADC
       CALL    PRINT
       DB      ')? ',0
       LXI     H,INBUF ; GET INTO INPUT LINE BUFFER
       MVI     A,0FFH  ; CAPITALIZE
       CALL    BLINE
       POP     D       ; GET PTR TO USER
       ORA     A       ; ANY RESPONSE?
       RZ
       PUSH    D       ; SAVE PTR TO USER
       CALL    EVAL    ; EVALUATE
       POP     H       ; GET PTR TO USER
       MOV     A,D     ; GET RESULT
       ORA     A       ; MUST BE ZERO HIGH
       JNZ     CMDLUER
       LDA     MUSER   ; CHECK AGAINST MAX
       CMP     E
       JC      CMDLUER
       MOV     A,E     ; GET
NUMBER
       MOV     M,A     ; PUT USER
       RET
CMDLER:
       CALL    PRINT
       DB      CR,LF,'Invalid Disk -- Reenter',0
       JMP     CMDL0
CMDLUER:
       CALL    PRINT
       DB      CR,LF,'Invalid User -- Reenter',0
       JMP     CMDL2

;  TOGGLE EXIST
CMDE:
       CALL    ETOG    ; TOGGLE WITH MESSAGE
       RET

;  TOGGLE NCOPY
CMDM:
       CALL    MTOG    ; TOGGLE WITH MESSAGE
       RET

;  TOGGLE QUIET
CMDQ:
       CALL    QTOG    ; TOGGLE WITH MESSAGE
       RET

;  DISPLAY STATUS
CMDS:
       CALL    PRINT
       DB      CR,LF,'Current Disk/User is ',0
       LXI     H,CDISK
       CALL    CMDS1
       CALL    PRINT
       DB      CR,LF,'Source Disk/User is ',0
       LXI     H,SDISK
       CALL    CMDS1
       CALL    PRINT
       DB      CR,LF,'Destination Disk/User is ',0
       LXI     H,DDISK
CMDS1:
       MOV     A,M     ; GET DISK
       ADI     'A'
       CALL    COUT
       INX     H
       MOV     A,M     ; GET USER
       CALL    PADC
       MVI     A,':'
       CALL    COUT
       RET

;  TOGGLE VERIFY
CMDV:
       CALL    VTOG    ; TOGGLE WITH MESSAGE
       RET

;  EXIT TO CP/M
CMDX:
       CALL    PRINT
       DB      CR,LF,'** MCOPY Exiting **',0
       JMP     CPM

;  TOGGLE QUIET FUNCTION
QT:
       LDA     QUIET   ; GET FLAG
       CMA             ; FLIP IT
       STA     QUIET   ; PUT FLAG
       RET

;  TOGGLE EXIST TEST FUNCTION
ET:
       LDA     EXIST   ; GET FLAG
       CMA             ; FLIP IT
       STA     EXIST   ; PUT FLAG
       RET

;  TOGGLE NCOPY FUNCTION (MULTIPLE COPIES)
MT:
       LDA     NCOPY   ; GET FLAG
       CMA             ; FLIP IT
       STA     NCOPY   ; PUT FLAG
       RET

;  TOGGLE INSPECT FUNCTION
IT:
       LDA     INSP    ; GET FLAG
       CMA             ; FLIP IT
       STA     INSP    ; PUT FLAG
       RET

;  TOGGLE VERIFY FUNCTION
VT:
       LDA     VERFLG  ; GET FLAG
       CMA             ; FLIP IT
       STA     VERFLG  ; PUT FLAG
       RET

;  TOGGLE INSPECT MODE AND PRINT MESSAGE
ITOG:
       CALL    IT      ; TOGGLE AND FALL THRU TO PRINT
;  PRINT INSPECT MESSAGE
IMSG:
       CALL    PRINT
       DB      CR,LF,'  File Selection Inspect Mode ',0
       LDA     INSP
ENPRT:
       ORA     A       ; 0=NO
       JZ      ENPRT1
       CALL    PRINT
       DB      'Enabled',0
       RET
ENPRT1:
       CALL    PRINT
       DB      'Disabled',0
       RET

;  TOGGLE EXIST TEST AND PRINT MESSAGE
ETOG:
       CALL    ET      ; TOGGLE AND FALL THRU TO PRINT
;  PRINT EXIST STATUS
EMSG:
       CALL    PRINT
       DB      CR,LF,'  Existence Test Function ',0
       LDA     EXIST
       JMP     ENPRT

;  TOGGLE MULTIPLE COPY AND PRINT MESSAGE
MTOG:
       CALL    MT      ; TOGGLE AND FALL THRU TO PRINT
;  PRINT MCOPY STATUS
MMSG:
       CALL    PRINT
       DB      CR,LF,'  Multiple Copy Function ',0
       LDA     NCOPY
       JMP     ENPRT

;  TOGGLE QUIET AND PRINT MESSAGE
QTOG:
       CALL    QT      ; TOGGLE AND FALL THRU TO PRINT
;  PRINT QUIET STATUS
QMSG:
       CALL    PRINT
       DB      CR,LF,'  Quiet Operation ',0
       LDA     QUIET
       JMP     ENPRT

;  TOGGLE VERIFY AND PRINT MESSAGE
VTOG:
       CALL    VT      ; TOGGLE AND FALL THRU TO PRINT
;  PRINT VERIFY STATUS
VMSG:
       CALL    PRINT
       DB      CR,LF,'  Copy Verification ',0
       LDA     VERFLG
       JMP     ENPRT

;  PRINT MCOPY COMMAND HELP MESSAGE
MCPYHLP:
       CALL    PRINT
       DB      CR,LF,' MCOPY Status:  E I M Q V'
       DB      CR,LF,'These Status Characters have the following meanings:'
       DB      CR,LF,'   E - File Existence Test Mode is ON'
       DB      CR,LF,'   I - File Selection Inspect Mode is ON'
       DB      CR,LF,'   M - Multiple Copy Function Mode is ON'
       DB      CR,LF,'   Q - Quiet Mode is ON'
       DB      CR,LF,'   V - Verify Mode is ON'
       DB      CR,LF
       DB      CR,LF,'The Status Characters, as commands, toggle their '
       DB      'respective modes.'
       DB      CR,LF,'Other valid MCOPY Commands are:'
       DB      CR,LF,'   C - Copy a File or Set of Files'
       DB      CR,LF,'   D - Directory Display'
       DB      CR,LF,'   F - Compute Amount of Free Space on Disk'
       DB      CR,LF,'   L - Log in New User/Disks'
       DB      CR,LF,'   S - Display MCOPY Status (Cur and Dest User/Disk)'
       DB      CR,LF,'   X or ^C - Exit MCOPY'
       DB      0
       RET

;  PRINT CHAR IN A IF NZ, ELSE PRINT <SP>
PMODE:
       JZ      PMODE1  ; PRINT <SP>
       JMP     COUT    ; PRINT CHAR
PMODE1:
       MVI     A,' '   ; PRINT <SP>
       JMP     COUT

;
;  PRINT MCOPY BANNER
;
BANNER:
       CALL    PRINT
       DB      'MCOPY  Version '
       DB      VERS/10+'0','.',(VERS MOD 10)+'0',0
       RET

;
;  **** MCOPY of COMMAND LINE ****
;
MCOPY0:
       LXI     SP,STACK        ; SET STACK
       LDA     NCOPY   ; MULTIPLE COPIES?
       ORA     A       ; 0=NO
       JZ      NOPAUSE
       CALL    SAKCHK  ; STRIKE ANY KEY CHECK
       JZ      CPM     ; WARM BOOT IF ABORT
NOPAUSE:
       CALL    COPY    ; DO THE COPY
       JMP     CPM     ; WARM BOOT WHEN DONE

;
;  **** Begin Multiple Copy Procedure ****
;
COPY:
       LHLD    MFPTR   ; PT TO FIRST FILE NAME
       SHLD    NXTPTR  ; SET PTR TO NEXT FILE NAME
       LXI     H,0     ; HL=0
       SHLD    FCOUNT  ; ZERO FILE COUNT
       SHLD    VERCNT  ; ZERO ERROR COUNT
       LDA     EXIST   ; IF EXIST, THEN MUST NOT BE QUIET
       ORA     A       ; 0=NO EXIST
       JZ      MCOPY
       XRA     A       ; SET NO QUIET
       STA     QUIET
;
;  **** MAIN COPY LOOP ****
;
MCOPY:
       LHLD    NXTPTR  ; GET PTR TO NEXT FILE NAME
       MOV     A,M     ; GET FIRST CHAR
       CPI     ' '+1   ; DONE IF <SP> OR LESS
       JNC     MCOPY1  ; CONTINUE WITH PROCEDURE
;
;  MCOPY OF FILE SPECS IS NOW DONE
;  DONE WITH COPY PROCEDURE -- CONTINUE?
;
COPYT:
       CALL    PRINT
       DB      CR,LF,'**** MCOPY Complete ****',CR,LF,'        ',0
       LHLD    FCOUNT  ; GET FILE COUNT
       CALL    PHLDC   ; PRINT AS DECIMAL
       CALL    PRINT
       DB      ' File',0
       MOV     A,H     ; 1 FILE?
       ORA     A
       JNZ     COPYT1
       MOV     A,L
       CPI     1       ; 1 FILE?
       JZ      COPYT2
COPYT1:
       MVI     A,'s'   ; ENDING S
       CALL    COUT
COPYT2:
       CALL    PRINT
       DB      ' Copied        ',0
       LHLD    VERCNT  ; GET ERROR COUNT
       CALL    PHLDC   ; PRINT AS DECIMAL
       CALL    PRINT
       DB      ' Copy Errors',0
       LDA     NCOPY   ; MULTIPLE COPIES?
       ORA     A       ; 0=NO
       RZ
       CALL    SAKCHK  ; CHECK FOR STRIKE OF ANY KEY
       RZ              ; RETURN IF ABORT
       JMP     COPY    ; COPY AGAIN FROM THE BEGINNING
;
;  BEGIN COPY OF FILE GROUP
;
MCOPY1:
       CPI     ','     ; SKIP COMMA SEPARATOR IF THERE
       JNZ     MCPY0
       INX     H       ; PT TO CHAR AFTER COMMA
MCPY0:
       MOV     A,M     ; GET NEXT CHAR
       CPI     ' '+1   ; CHECK FOR ERROR
       JC      FORMERR
       PUSH    H       ; SAVE PTR TO NEXT FILE NAME
       CALL    CODEND  ; GET ADDRESS OF SCRATCH AREA
       MOV     B,H     ; BC=ADDRESS OF SCRATCH AREA
       MOV     C,L
       LXI     D,FCBS  ; GET POSSIBLE SOURCE FCB
       POP     H       ; GET PTR TO NEXT FILE SPEC
       CALL    ZFNAME  ; EXTRACT FILE NAME DATA
       JZ      UDERR   ; ERROR?
       MOV     A,M     ; GET DELIMITER
       CPI     '='     ; IF '=', WE HAVE A NEW DISK/USER
       JNZ     MCOPY2  ; FORM IS DIRS:FN.FT IF NO '='
;
;  FORM IS DIRD:=DIRS:FN.FT, SO SET DEST DISK/USER
;
       MOV     A,B     ; CHECK FOR ANY DISK OR USER CHANGE
       ANA     C       ; IF BOTH FF, THEN NO CHANGE
       CPI     0FFH    ; BOTH FF?
       JZ      MCPY2
       LDA     CDISK   ; SET DEST TO CURRENT SINCE A CHANGE IS EXPECTED
       STA     DDISK   ; ... IN THIS WAY, A NEW DEST OF U: OR D: IS
       LDA     CUSER   ; ... INTERPRETED AS $U: OR D$: (I.E., IF LOGGED INTO
       STA     DUSER   ; ... B, A DEST OF 1: IS B1:)
       MOV     A,B     ; CHECK FOR DISK CHANGE
       CPI     0FFH    ; 0FFH=NO CHANGE
       JZ      MCPY1
       DCR     A       ; ADJUST
       STA     DDISK   ; SET NEW DEFAULT DISK
MCPY1:
       MOV     A,C     ; CHECK FOR USER CHANGE
       CPI     0FFH    ; 0FFH=NO CHANGE
       JZ      MCPY2
       CPI     '?'     ; ALL USERS NOT PERMITTED
       JZ      UDERR
       STA     DUSER   ; SET NEW DEFAULT USER
;
;  NOW DERIVE DIRS:FN.FT FORM AFTER THE '='
;
MCPY2:
       INX     H       ; PT TO CHAR BEYOND '='
       MOV     A,M     ; GET CHAR
       CPI     ' '+1   ; FORMAT ERROR?
       JC      FORMERR
       PUSH    H       ; SAVE PTR
       CALL    CODEND  ; GET END OF CODE
       MOV     B,H     ; ... IN BC
       MOV     C,L
       POP     H       ; GET PTR TO NAME
       LXI     D,FCBS  ; LOAD FCB
       CALL    ZFNAME  ; GET SOURCE NAME
       JZ      UDERR   ; ERROR?
;
;  SAVE PTR TO NEXT CHAR AFTER DIRS:FN.FT, AND SET SOURCE DISK/USER
;
MCOPY2:
       SHLD    NXTPTR  ; SAVE PTR TO NEXT CHAR
       MOV     A,B     ; CHECK FOR NO DISK OR USER CHANGE
       ANA     C
       CPI     0FFH    ; BOTH FF?
       JZ      MCPY22
       LDA     CDISK   ; IF CHANGE IN EITHER, ASSUME OLD SOURCE TO BE CURRENT
       STA     SDISK
       LDA     CUSER
       STA     SUSER
       MOV     A,B     ; CHECK FOR DISK CHANGE
       CPI     0FFH    ; 0FFH=NO CHANGE
       JZ      MCPY21
       DCR     A       ; ADJUST
       STA     SDISK   ; SET NEW DEFAULT DISK
MCPY21:
       MOV     A,C     ; CHECK FOR USER CHANGE
       CPI     0FFH    ; 0FFH=NO CHANGE
       JZ      MCPY22
       CPI     '?'     ; ALL USERS NOT PERMITTED
       JZ      UDERR
       STA     SUSER   ; SET NEW DEFAULT USER
MCPY22:
       CALL    PRINT
       DB      CR,LF,' Copy ',0
       LDA     SDISK   ; GET NUMBER
       ADI     'A'     ; CONVERT TO LETTER
       CALL    COUT    ; PRINT
       LDA     SUSER   ; PRINT USER NUMBER
       CALL    PADC
       MVI     A,':'   ; SEPARATOR
       CALL    COUT
       MVI     A,' '
       CALL    COUT
       LXI     H,FCBS+1        ; PRINT FILE SPEC
       CALL    PRFN
       CALL    PRINT
       DB      ' to ',0
       LDA     DDISK   ; GET NUMBER
       ADI     'A'     ; CONVERT TO LETTER
       CALL    COUT    ; PRINT
       LDA     DUSER   ; PRINT USER NUMBER
       CALL    PADC
       MVI     A,':'
       CALL    COUT
       MVI     C,13    ; RESET DISK SYSTEM
       CALL    BDOS
       CALL    LOGS    ; LOG IN SOURCE USER/DISK
       LXI     D,FCBS  ; PT TO SOURCE FCB
       CALL    INITFCB ; INIT FCB
       CALL    CODEND  ; PT TO BUFFER AREA
       LDA     SUSER   ; PREPARE FLAG FOR SELECTION
       ANI     1FH     ; ONLY USER NUMBER
       ORI     0C0H    ; SELECT NON-SYS AND SYS FILES
       CALL    DIRF    ; LOAD DIR, SELECT FILES, SORT, ETC
       JZ      TPAOVFL ; TPA OVERFLOW ERROR?
       LDA     INSP    ; INSPECT FILES?
       ORA     A       ; 0=NO
       CNZ     INSPF   ; INSPECT FILES IF OPTION SELECTED
       MOV     A,B     ; CHECK FOR ANY FILES TO COPY
       ORA     C       ; 0=NONE
       JNZ     MCPY24
MCPY23:
       CALL    PRINT
       DB      CR,LF,'** NO Files Selected **'
       DB      CR,LF,'** Strike ^C to Abort, Anything Else to Continue: ',0
       CALL    CIN     ; GET RESPONSE
       CPI     'C'-'@' ; ABORT?
       JZ      COPYT   ; END TEST
       JMP     MCOPY   ; CONTINUE WITH NEXT
MCPY24:
       PUSH    H       ; SAVE PTR AND COUNT
       PUSH    B
       LXI     D,16    ; SKIP TO END OF LOADED FILES AND MARK BEGINNING OF
                       ;   WORK AREA
MCPY25:
       DAD     D       ; PT TO NEXT
       DCX     B       ; COUNT DOWN
       MOV     A,B     ; DONE?
       ORA     C
       JNZ     MCPY25
       MVI     A,64    ; SET PAGE LIMIT TO 16K
       STA     PAGLIM
       SHLD    WORKBF  ; SAVE PTR TO BEGINNING OF WORK BUFFER
       LDA     BDOSE+2 ; GET BASE PAGE OF BDOS
       SUI     10      ; GET BELOW BASE PAGE OF CCP
       SUB     H       ; COMPUTE SIZE OF BUFFER AREA
       CPI     64      ; 64 PAGES LEFT?
       JNC     PAGOK
       STA     PAGLIM  ; SET PAGE LIMIT
PAGOK:
       POP     B       ; RESTORE PTRS
       POP     H
;
;  MAIN COPYING LOOP
;    FILE NAMES ARE PTED TO BY HL AND BC=NUMBER OF FILES
;
MCPY26:
       PUSH    H       ; SAVE REGS
       PUSH    B
       CALL    MCOPYX  ; COPY SOURCE (HL) TO DESTINATION USING WORK BUFFER
       LDA     QUIET   ; CHECK FOR QUIET
       ORA     A       ; NZ=QUIET
       JNZ     MCPY27
       CALL    PRINT
       DB      CR,LF,'    Copy Complete',0
MCPY27:
       LDA     LSTCPY  ; LAST FILE COPIED?
       ORA     A       ; 0=NO
       JZ      MCPY28
       LDA     VERFLG  ; VERIFY?
       ORA     A       ; 0=NO
       CNZ     MCOPYV  ; DO VERIFY
       LHLD    FCOUNT  ; COUNT FILES
       INX     H
       SHLD    FCOUNT
MCPY28:
       POP     B       ; GET REGS
       POP     H
       LXI     D,16    ; PT TO NEXT FILE
       DAD     D       ; HL PTS TO NEXT FILE
       DCX     B       ; COUNT DOWN
       MOV     A,B
       ORA     C
       JNZ     MCPY26
       JMP     MCOPY   ; COPY NEXT FILE SPEC
;
;  COPY SOURCE FILE PTED TO BY HL TO DESTINATION
;
MCOPYX:
       XRA     A       ; SET NO COPY OF LAST FILE
       STA     LSTCPY  ; SET FLAG
       LXI     D,FCBS  ; SET SOURCE FCB
       MVI     B,12    ; 12 BYTES
       CALL    MOVEB
       CALL    INITFCB ; INIT SOURCE FCB
       LXI     D,FCBD  ; SET DESTINATION FCB
       MVI     B,12    ; 12 BYTES
       CALL    MOVEB
       CALL    DRW     ; CLEAR ATTRIBUTES IN FCB
       CALL    INITFCB ; INIT DESTINATION FCB
       CALL    LOGD    ; LOG IN DESTINATION
       LXI     D,FCBD  ; PT TO FCB
       CALL    F$EXIST ; DOES DEST EXIST?
       JZ      FNF     ; FILE NOT FOUND IF ZERO
       LDA     QUIET   ; QUIET?
       ORA     A       ; 0=NO
       JNZ     FFND
       CALL    PRINT
       DB      CR,LF,'Original File ',0
       LXI     H,FCBD+1        ; PRINT FILE NAME
       CALL    PRFN
       CALL    PRINT
       DB      ' on Destination',0
FFND:
       CALL    EATEST  ; EXIST APPROVED TEST?
       RZ              ; NOT APPROVED, SO ABORT
       CALL    DESTRW  ; MAKE DESTINATION R/W IF NOT ALREADY
       CALL    F$DELETE        ; DELETE FILE
       CALL    INITFCB         ; REINIT FCB
       JMP     FNF1    ; CREATE NEW FILE AND CONTINUE
FNF:
       LDA     QUIET   ; QUIET?
       ORA     A       ; 0=NO
       JNZ     FNF1
       CALL    PRINT
       DB      CR,LF,'No Original File ',0
       LXI     H,FCBD+1        ; PRINT FILE NAME
       CALL    PRFN
       CALL    PRINT
       DB      ' on Destination',0
       CALL    EATEST  ; EXIST APPROVED?
       RZ              ; NO?
FNF1:
       MVI     A,0FFH  ; SET COPY OF LAST FILE
       STA     LSTCPY  ; SET FLAG
       CALL    F$MAKE  ; CREATE NEW FILE
;
;  OPEN SOURCE FILE IN PREP FOR COPY
;
       CALL    CRCCLR  ; CLEAR CRC VALUE
       CALL    LOGS    ; LOG IN SOURCE DISK
       LXI     D,FCBS  ; INIT FCB
       CALL    INITFCB
       CALL    F$OPEN  ; OPEN FILE
       CALL    CRLF    ; NEW LINE
       LXI     H,0
       SHLD    RKCNT   ; SET READ K COUNT
       SHLD    WKCNT   ; SET WRITE K COUNT
;
;  THIS LOOP, WHICH STARTS AT MCPYX, COPIES THE FILE FROM SOURCE TO DEST
;
MCPYX:
       CALL    LOGS    ; LOG IN SOURCE
       LXI     D,FCBS  ; PT TO SOURCE FCB
       LHLD    WORKBF  ; PT TO BUFFER TO COPY INTO
       CALL    LOAD    ; LOAD FILE INTO WORKBF
       LDA     BCNT    ; IF COUNT=0, THEN DONE
       ORA     A
       JZ      MC2DONE
;
;  COPY TO DISK
;
MCPYD:
       CALL    LOGD    ; LOG IN DESTINATION
       LDA     QUIET   ; CHECK FOR QUIET
       ORA     A       ; Z=NOT QUIET
       JNZ     MCPYD0
       CALL    PRINT
       DB      '  Writing .....K',0
MCPYD0:
       LHLD    WORKBF  ; PT TO BUFFER
MCPYD1:
       LXI     D,BUFF  ; COPY DATA TO BUFFER
       MVI     B,128   ; 128 BYTES
       CALL    MOVEB   ; COPY IT
       LXI     D,128   ; INCR HL BY 128
       DAD     D       ; HL PTS TO NEXT BLOCK
       LXI     D,FCBD  ; WRITE TO DESTINATION FILE
       CALL    F$WRITE
       ORA     A       ; OK?
       JNZ     MCPYDERR

;  COUNT DOWN TO NEXT BLOCK
       LDA     BCNT    ; PRINT BLIPS
       ANI     7       ; MASK
       JNZ     MCPYD2
       LDA     QUIET   ; CHECK FOR QUIET
       ORA     A       ; Z=NOT QUIET
       JNZ     MCPYD2
       PUSH    H       ; SAVE HL
       LHLD    WKCNT   ; INCREMENT WRITE K COUNT
       INX     H
       SHLD    WKCNT
       CALL    PRKCNT  ; PRINT K COUNT
       POP     H
MCPYD2:
       LDA     BCNT    ; GET BLOCK COUNT
       DCR     A       ; COUNT DOWN
       STA     BCNT
       JNZ     MCPYD1
       LDA     QUIET   ; CHECK FOR QUIET OPERATION
       ORA     A       ; Z=NOT QUIET
       CZ      CRLF    ; NEW LINE
       LDA     CONT    ; CONTINUE?
       ORA     A       ; CONT IF NOT ZERO
       JNZ     MCPYX
;
;  END OF COPY LOOP
;
MC2DONE:
       CALL    LOGS    ; LOG IN SOURCE
       LXI     D,FCBS  ; CLOSE SOURCE
       CALL    F$CLOSE
       CALL    LOGD    ; LOG IN DESTINATION
       LXI     D,FCBD  ; CLOSE DESTINATION
       CALL    F$CLOSE
       CALL    CRCDONE ; GET CRCK VALUE
       SHLD    CRCVAL  ; SAVE CRC VALUE
       CALL    LOGS    ; LOG IN SOURCE DRIVE
       LXI     D,FCBS  ; FIND SOURCE
       MVI     C,17    ; SEARCH FOR FIRST
       CALL    BDOS
       RLC             ; MULTIPLY BY 32 TO GET OFFSET
       RLC
       RLC
       RLC
       RLC
       ANI     0E0H    ; MASK OUT LSB
       MOV     L,A     ; VALUE IN L
       MVI     H,0
       LXI     D,BUFF  ; ADD IN BUFFER BASE
       DAD     D
       LXI     D,FCBT
       MVI     B,16    ; MOVE 16 BYTES
       CALL    MOVEB
       CALL    LOGD    ; LOG IN DESTINATION DRIVE
       LXI     D,FCBT
       CALL    INITFCB ; INIT FCB
       MVI     C,30    ; SET FILE ATTRIBUTES
       CALL    BDOS
       RET             ; MCOPYX RETURN

;  FORMAT ERROR
FORMERR:
       CALL    PRINT
       DB      CR,LF,'MCOPY -- Format Error in Command Line'
       DB      CR,LF,'Error Starts at: ',0
       CALL    PSTR    ; PRINT ERROR
       RET

;  USER/DISK ERROR
UDERR:
       CALL    PRINT
       DB      CR,LF,'MCOPY -- Error in User Number or Disk Letter',0
       RET

;  TPA OVERFLOW
TPAOVFL:
       CALL    PRINT
       DB      CR,LF,'MCOPY -- TPA Overflow -- Aborting',0
       JMP     CPM

;  WRITE ERROR
MCPYDERR:
       CALL    PRINT
       DB      CR,LF,'MCOPY -- Error in Creating Destination File',0
       JMP     CPM

;  TEST FOR EXISTENCE REQUIREMENT AND GET USER RESPONSE
EATEST:
       LDA     EXIST   ; EXISTENCE TEST ON?
       ORA     A       ; 0=NO
       JZ      EAT1
       CALL    PRINT
       DB      CR,LF,' -- Approve Copy (Y/N/other=Y)? ',0
       CALL    CIN     ; GET RESPONSE
       CALL    CAPS
       CPI     CR      ; YES?
       JZ      EAT1    ; COPY IF SO
       CALL    COUT
       CPI     'N'     ; NO?
       JNZ     EAT1    ; COPY IF NOT NO
       CALL    PRINT
       DB      ' -- Disapproved',0
       XRA     A       ; ZERO FOR NOT APPROVED
       RET
EAT1:
       MVI     A,0FFH  ; SET NZ FOR APPROVED
       ORA     A       ; SET FLAGS
       RET
;
;  MAKE DESTINATION FCB ENTRY R/W AND DIR
;
DRW:
       LXI     H,FCBD+9        ; CLEAR ATTRIBUTES OF DEST
       MOV     A,M     ; GET IT
       ANI     7FH     ; CLEAR IT
       MOV     M,A
       INX     H       ; SAME TO NEXT
       MOV     A,M     ; GET IT AND CLEAR IT
       ANI     7FH
       MOV     M,A
       RET
DESTRW:
       CALL    DRW     ; MAKE ATTRIBUTES R/W AND NON-SYS
       LXI     D,FCBD  ; SET ATTRIBUTES
       MVI     C,30
       CALL    BDOS
       RET

;
;  LOAD BUFFER PTED TO BY HL FROM FILE WHOSE FCB IS PTED TO BY DE
;    ON OUTPUT, BCNT=NUMBER OF BLOCKS LOADED (UP TO 128) AND
;    CONT=0 IF DONE OR 128 IF NOT DONE
;
LOAD:
       XRA     A       ; A=0
       STA     BCNT    ; SET BLOCK COUNT
       STA     CONT    ; TURN OFF CONTINUATION FLAG
       LDA     QUIET   ; QUIET?
       ORA     A       ; 0=NO
       JNZ     MCPY
       CALL    PRINT
       DB      'Reading .....K',0

;  MAIN COPY LOOP
MCPY:
       CALL    F$READ  ; READ BLOCK
       ORA     A       ; END OF FILE?
       RNZ             ; RETURN
       PUSH    D       ; SAVE PTR TO FCB
       XCHG            ; SAVE PTR TO DESTINATION BUFFER IN DE
       LHLD    BDOSE+1 ; GET TOP OF TPA
       XCHG            ; ... IN DE, DEST IN HL
       MOV     A,H     ; IF SAME PAGE, WE ARE IN OVERFLOW
       CMP     D       ; D MUST BE > H
       JNC     TPAOVFL ; OVERFLOW IF D<=H
       LXI     D,BUFF  ; PT TO BUFFER TO COPY FROM
       MVI     B,128   ; COPY 128 BYTES
       XCHG            ; HL PTS TO SOURCE, DE PTS TO DESTINATION
MCPYCRC:
       MOV     A,M     ; GET BYTE
       STAX    D       ; PUT BYTE
       CALL    CRCUPD  ; UPDATE CRC
       INX     H       ; PT TO NEXT
       INX     D
       DCR     B       ; COUNT DOWN
       JNZ     MCPYCRC
       XCHG            ; HL PTS TO DESTINATION AGAIN
       POP     D       ; GET PTR TO FCB
       LDA     BCNT    ; GET BLOCK COUNT
       INR     A       ; INCREMENT IT
       STA     BCNT    ; SET IT
       PUSH    PSW     ; PRINT BLIP FOR EVERY 1K
       ANI     7       ; CHECK FOR EVERY 8 BLOCKS
       JNZ     NOBLIP
       LDA     QUIET   ; CHECK FOR QUIET
       ORA     A       ; Z=NOT QUIET
       JNZ     NOBLIP
       PUSH    H       ; SAVE HL
       LHLD    RKCNT   ; INCREMENT READ K COUNT
       INX     H
       SHLD    RKCNT
       CALL    PRKCNT  ; PRINT K COUNT
       POP     H
NOBLIP:
       LDA     PAGLIM  ; GET PAGE LIMIT
       ADD     A       ; DOUBLE IT FOR BLOCKS
       MOV     B,A     ; LIMIT IN B
       POP     PSW     ; GET BLOCK COUNT
       CMP     B       ; BUFFER FULL?
       JNZ     MCPY
       STA     CONT    ; SET CONTINUATION FLAG
       RET

;
;  VERIFY PHASE
;
MCOPYV:
       LDA     QUIET   ; CHECK FOR QUIET
       ORA     A       ; NZ=QUIET
       JNZ     MCPYV
       CALL    PRINT
       DB      ',  Verify Phase --',CR,LF,0
       LXI     H,0     ; SET READ K COUNT
       SHLD    RKCNT
MCPYV:
       CALL    CRCCLR  ; CLEAR CRCK VALUE
       CALL    LOGD    ; LOG IN DESTINATION
       LXI     D,FCBD  ; CLEAR DESTINATION FCB
       CALL    INITFCB ; INIT FCB
       CALL    F$OPEN  ; OPEN FILE

;  **** MAIN VERIFY LOOP ****
VERLOOP:
       LHLD    WOR
KBF     ; LOAD INPUT BUFFER FROM DESTINATION
       LXI     D,FCBD
       CALL    LOAD    ; LOAD AND COMPUTE CRC VALUE
       LDA     BCNT    ; DONE IF NO BYTES LOADED
       ORA     A
       JZ      VERCRC
       LDA     QUIET   ; NEW LINE IF NOT QUIET
       ORA     A       ; 0=NOT QUIET
       CZ      CRLF
       LDA     CONT    ; CONTINUE?
       ORA     A       ; 0=NO
       JNZ     VERLOOP
;  VERIFY DONE
VERCRC:
       LHLD    CRCVAL  ; GET OLD CRC VALUE
       XCHG            ; ... IN DE
       CALL    CRCDONE ; UPDATE COMPLETE
       CALL    COMPHD  ; COMPARE HL TO DE
       JNZ     VERERR
       LDA     QUIET   ; CHECK FOR QUIET
       ORA     A       ; NZ=QUIET
       RNZ
       CALL    PRINT
       DB      '    Verify Complete',0
       RET

;  VERIFY ERROR
VERERR:
       LHLD    VERCNT  ; INCREMENT ERROR COUNT
       INX     H
       SHLD    VERCNT
       CALL    PRINT
       DB      '    ** Verify Error **',7,0
       RET

;
;  **** MCOPY Utilities ****
;

;
;  PRINT K COUNT -- BACK UP 6 SPACES AND PRINT NUMBER IN HL FOLLOWED BY A K
;
PRKCNT:
       PUSH    B       ; SAVE BC
       MVI     B,6     ; BACK UP
       MVI     A,'H'-'@'       ; ^H=BACKSPACE
PRKCNT1:
       CALL    COUT    ; BACK UP
       DCR     B       ; COUNT DOWN
       JNZ     PRKCNT1
       CALL    PHLDC   ; PRINT AS DECIMAL
       MVI     A,'K'   ; PRINT ENDING K
       CALL    COUT
       POP     B
       RET

;
;  CHECK TO SEE IF USER WANTS TO CONTINUE
;
SAKCHK:
       CALL    PRINT
       DB      CR,LF,' Strike RETURN when Ready or ^C or A to Abort - ',0
       CALL    CIN     ; GET RESPONSE
       CALL    CRLF    ; NEW LINE
       CALL    CAPS    ; CAPITALIZE
       CPI     'C'-'@' ; ^C?
       RZ
       CPI     'A'     ; ABORT?
       RET
;
;  ALLOW USER TO INSPECT FILES FOR COPY
;    FIRST FILE NAME PTED TO BY HL, BC = NUMBER OF FILES
;    ON EXIT, BC = NUMBER OF SELECTED FILES
;
INSPF:
       PUSH    H       ; SAVE PTR TO FIRST FILE
       PUSH    B       ; SAVE FILE COUNT
       LXI     D,16    ; ENTRIES ARE 16 BYTES APART
INSPF0:
       MOV     A,M     ; MARK FILE FOR NO COPY
       ANI     7FH     ; CLEAR MSB FOR NO COPY
       MOV     M,A
       DAD     D       ; PT TO NEXT
       DCX     B       ; COUNT DOWN
       MOV     A,B     ; DONE?
       ORA     C
       JNZ     INSPF0
       POP     B       ; RESTORE AND SAVE AGAIN
       POP     H
       PUSH    H
       PUSH    B
INSPF1:
       PUSH    H       ; SAVE PTR TO FILE
       INX     H       ; PT TO FN
       CALL    CRLF    ; NEW LINE
       CALL    PRFN    ; PRINT IT
       POP     H       ; GET PTR TO FILE
       CALL    PRINT
       DB      ' -- Copy (Y/N/S=Skip Rest/<CR>=Y)? ',0
       CALL    CIN     ; GET RESPONSE
       CALL    CAPS    ; CAPITALIZE
       CALL    COUT    ; ECHO
       CPI     'S'     ; SKIP?
       JZ      INSPFA
       CPI     'N'     ; NO?
       JZ      INSPF2
       MOV     A,M     ; GET USER NUMBER
       ORI     80H     ; MARK FILE
       MOV     M,A     ; SET USER NUMBER
INSPF2:
       LXI     D,16    ; PT TO NEXT FILE
       DAD     D
       DCX     B       ; COUNT DOWN
       MOV     A,B     ; DONE?
       ORA     C
       JNZ     INSPF1
INSPFA:
       POP     B       ; GET COUNT
       POP     H       ; GET PTR TO FIRST FILE
       JMP     DIRPACK ; REPACK DIRECTORY

;
;  LOG IN SOURCE USER/DISK
;
LOGS:
       LDA     SUSER   ; USER
       MOV     C,A     ; ... IN C
       LDA     SDISK   ; DISK
       MOV     B,A     ; ... IN B
       JMP     LOGUD   ; LOG IN USER/DISK

;
;  LOG IN DESTINATION USER/DISK
;
LOGD:
       LDA     DUSER   ; USER
       MOV     C,A     ; ... IN C
       LDA     DDISK   ; DISK
       MOV     B,A     ; ... IN B
       JMP     LOGUD   ; LOG IN USER/DISK

;
;  PRINT FILE NAME
;
PRFN:
       PUSH    H       ; SAVE REGS
       PUSH    B
       MVI     B,8     ; PRINT 8 CHARS
       CALL    PRFN1
       MVI     A,'.'   ; DOT
       CALL    COUT
       MVI     B,3     ; PRINT 3 CHARS
       CALL    PRFN1
       POP     B       ; GET REGS
       POP     H
       RET
PRFN1:
       MOV     A,M     ; GET CHAR
       INX     H       ; PT TO NEXT
       CALL    COUT    ; PRINT IT
       DCR     B       ; COUNT DOWN
       JNZ     PRFN1
       RET

;
;  **** BUFFERS ****
;

;  COMMAND LINE BUFFER
BUFSIZ  EQU     200     ; SIZE OF COMMAND LINE BUFFER
INBUF:  DB      BUFSIZ  ; FOR USE WITH INPUT LINE EDITOR
       DB      0
       DS      BUFSIZ+1        ; INPUT COMMAND LINE

;  POINTERS
MFPTR:  DS      2       ; PTR TO FIRST CHAR OF NEXT FN SPEC
NXTPTR: DS      2       ; PTR TO NEXT FN SPEC IN LINE
WORKBF: DS      2       ; PTR TO BEGINNING OF WORK BUFFER

;  FLAGS COPIED FROM DEFAULTS
VERFLG: DS      1       ; VERIFY
INSP:   DS      1       ; INSPECT
QUIET:  DS      1       ; QUIET
NCOPY:  DS      1       ; MULTIPLE COPY

;  DISKS AND USERS -- ORDER IS IMPORTANT -- cUSER MUST FOLLOW cDISK
CDISK:  DS      1       ; CURRENT DISK
CUSER:  DS      1       ; CURRENT USER
SDISK:  DS      1       ; SOURCE DISK
SUSER:  DS      1       ; SOURCE USER
DDISK:  DS      1       ; DESTINATION DISK
DUSER:  DS      1       ; DESTINATION USER

;  CRC VALUE
CRCVAL: DS      2       ; CRC CHECK VALUE

;  FCBS
FCBS:   DS      40      ; SOURCE FCB
FCBD:   DS      40      ; DESTINATION FCB
FCBT:   DS      40      ; TEMPORARY FCB FOR ATTRIBUTE SETTINGS

;  COUNTS AND FLAGS
RKCNT:  DS      2       ; READ K COUNT
WKCNT:  DS      2       ; WRITE K COUNT
PAGLIM: DS      1       ; MAX NUMBER OF PAGES IN WORK BUFFER
LSTCPY: DS      1       ; LAST FILE WAS COPIED FLAG
EXIST:  DS      1       ; TEST FOR EXISTENCE FLAG
FTOTAL: DS      2       ; TOTAL SIZE OF FILES
FCOUNT: DS      2       ; NUMBER OF FILES
VERCNT: DS      2       ; ERROR COUNT
BCNT:   DS      1       ; BLOCK COUNT
CONT:   DS      1       ; CONTINUE FLAG (0=NO, 0FFH=YES)

;  STACK AREA
       DS      100     ; 50-ELEMENT STACK
STACK   EQU     $
       DB      0       ; END OF MAINLINE

       END