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