;*****************************************************************************
;
;  COPY.ASM
;	Disk copy, verify, and format utility for CP/M on an Altair 8800
;	running the FDC+ with HD floppy drive option (780K). Write verify
;	for copy and format is left to CP/M and is therefore controlled by
;	the write verify bit in the MODE byte (40h bit at offset of 34h
;	into the BIOS).
;
;	Rev	  Date	    Desc
;	1.0	06/07/20  M. Douglas, Original
;
;	1.1	06/25/20  M. Douglas
;			  Look at drive type parameter in the BIOS and set
;			  the number of tracks to 77 or 80 based on the
;			  drive type.
;
;	1.2	07/06/20  M. Douglas
;			  Fix track error display to properly convert three
;			  digit track numbers. Update to use 149 tracks
;			  instead of 160 with 80 sectors per track vs 78 to
;			  match the BIOS 1.2 update. Eliminate the drive
;			  type check.
;
;*****************************************************************************

; Drive equates

NUMDRVS	equ	4		;number of drives supported
NUMTRKS	equ	149		;number of tracks
NUMSECS	equ	80		;80 CP/M sectors per track
SECLEN	equ	128		;sector length
TRKLEN	equ	(NUMSECS*SECLEN)   ;length of track
SYSTRKS	equ	1		;number of system tracks
DATATRK	equ	1		;starting data track

; CP/M equates

PRINT	equ	9		;BDOS write string to console
RDLINE	equ	10		;BDOS read a line from the console
BDOS	equ	5		;BDOS entry point
IBUFLEN	equ	80		;length of input buffer
WBOOT	equ	0		;warm boot vector
CDISK	equ	4		;CPM current disk

; BIOS Extended Function equates

EFDRIVE	equ	0FFh		;drive of FFh signals extended function
EFWRITE	equ	01h		;extended function: write w/o pre-read

; Misc equates

CR	equ	13		;ascii carriage return
LF	equ	10		;ascii line feed
CTRLC	equ	3		;ascii control-c

;  BIOS Jump Table Offsets. This table is used to compute BIOS entry
;     points by adding the symbols below to the warm boot entry address
;     stored by CP/M at location 1 on page zero.
	
	org	0
bWboot	ds	3		;warm start
bConSt	ds	3		;console status
bConIn	ds	3		;console character in 
bConOut	ds	3		;console character out 
bList	ds	3		;list character out 
bPunch	ds	3		;punch character out 
bReader	ds	3		;reader character out
bHome	ds	3		;move head to home position
bSelDsk	ds	3		;select disk
bSetTrk	ds	3		;set track number
bSetSec	ds	3		;set sector number
bSetDma	ds	3		;set dma address
bRead	ds	3		;read disk
bWrite	ds	3		;write disk

	org	0100h		;CP/M load and entry address
;-----------------------------------------------------------------------------
;  Program entry and initialization
;-----------------------------------------------------------------------------
	lxi	h,0		;save stack pointer by HL=SP+0
	dad	sp		;HL=current stack pointer from CP/M
	shld	stack		;save for restore later
	lxi	sp,ourStk	;setup our own stack

	lxi	d,mBanner	;display startup message
	call	dispMsg

; Determine the number of tracks we can buffer without clobbering BDOS.
;    Save in maxBuf. 

	lhld	BDOS+1		;HL=address of BDOS entry
	mvi	l,0		;l=number of tracks that fit
	mvi	a,trkBuf/256	;a=msb of track buffer

sizeLp	inr	l		;count a track
	adi	TRKLEN/256+1	;round up MSB of track length
	cmp	h		;reached or passed BDOS yet?
	jc	sizeLp		;no, keep counting

	dcr	l		;back up one track
	mov	a,l		;maxBuf = maximum tracks in trkBuf	
	sta	maxBuf

; If the BIOS supports the "Write without Read" extended function via the
;   selDsk entry point, patch the call to the BIOS Write function to use
;   the "Write without Read" extended function instead.

	mvi	a,EFDRIVE	;A=extended function request
	mov	b,a		;B=invalid function number
	call	selDsk		;call BIOS selDsk entry
	
	mov	a,l		;see if BIOS supports extended functions
	cpi	2		;BIOS must support functions 0 and 1
	jc	dspMenu		;support not provided, used normal WRITE

	lxi	h,bSelDsk	;patch write to call selDsk instead
	shld	biosWrt

;-----------------------------------------------------------------------------
; dspMenu - Main menu loop
;-----------------------------------------------------------------------------
dspMenu	lxi	d,mMenu		;display menu
	call	dispMsg

	call	readChr		;read response
	ani	5fh		;clear bit 5 ('a' to 'A')

	cpi	'A'		;copy all tracks
	cz	cmdAll

	cpi	'S'		;copy system tracks only
	cz	cmdSys

	cpi	'D'		;copy data tracks only
	cz	cmdData

	cpi	'F'		;format disk
	cz	cmdFmt

	cpi	'V'		;verify disk
	cz	cmdVfy	

	cpi	'X'		;exit
	jz	cmdExit		;warm boot CP/M to exit		

	jmp	dspMenu

;-----------------------------------------------------------------------------
; cmdExit - Exit to CP/M by warm boot.
;-----------------------------------------------------------------------------
cmdExit	xra	a		;set default drive to drive A 
	sta	CDISK		;so it matches warm boot drive
	jmp	WBOOT		;warm boot

;-----------------------------------------------------------------------------
; cmdAll - Copy all tracks
;-----------------------------------------------------------------------------
cmdAll	lxi	d,mCmdAll	;display command name
	call	dispmsg

	mvi	a,NUMTRKS	;copying all tracks on the disk
	sta	remTrks		;remaining tracks = all tracks
	xra	a		;current track = track zero
	sta	srcTrk
	sta	dstTrk

; cpyTrks - copy the number of tracks in remTrks, starting at src/dstTrk

cpyTrks	call	getSrc		;get source and destination from operator
	rnz			;user aborted
	call	getDst
	rnz

; if not single drive copy, display prompt to insert disks

	lxi	h,dstDrv	;HL->destination drive
	lda	srcDrv		;source = destination?
	cmp	m
	jz	cpyLoop		;single drive copy, skip next message

	lxi	d,mRdyCpy	;display insert disks prompt
	call	getRdy		;get ready confirmation from operator
	rnz			;user aborted

; cpyLoop - filling trkBuf from source, then writing trkBuf to destination

cpyLoop	call	ldSrc		;prompt for source if single drive
	rnz			;user aborted

	call	fillBuf		;read as many tracks as possible

	call	ldDst		;prompt for destination if single drive
	rnz			;user aborted

	call	wrtBuf		;write the full track buffer
	jnz	wrFail		;write error

	lda	remTrks		;any tracks left?
	dcr	a		;negative or 0 is now negative but > NUMTRKS
	cpi	NUMTRKS		;compare to number of tracks on disk
	jc	cpyLoop

; copy complete, display message and exit

	lxi	d,mCpyDon	;copy done message
	call	dispMsg
	xra	a		;clear command character
	ret

;-----------------------------------------------------------------------------
; cmdSys - Copy system tracks only
;-----------------------------------------------------------------------------
cmdSys	lxi	d,mCmdSys	;display command name
	call	dispmsg

	mvi	a,SYSTRKS	;copying just system tracks
	sta	remTrks		;remaining tracks = just system tracks
	xra	a		;current track = track zero
	sta	srcTrk
	sta	dstTrk

	jmp	cpyTrks		;do the copy (in cmdAll)

;-----------------------------------------------------------------------------
; cmdData - Copy data tracks only
;-----------------------------------------------------------------------------
cmdData	lxi	d,mCmdDat	;display command name
	call	dispmsg

	mvi	a,NUMTRKS-SYSTRKS  ;A=number of data tracks
	mvi	a,NUMTRKS-DATATRK  ;A=number of data tracks
	sta	remTrks		;remaining tracks = just data tracks
	mvi	a,DATATRK	;current track = first data track
	sta	srcTrk
	sta	dstTrk

	jmp	cpyTrks		;do the copy (in cmdAll)

;-----------------------------------------------------------------------------
; cmdFmt - Format the disk
;-----------------------------------------------------------------------------
cmdFmt	lxi	d,mCmdFmt	;display command name
	call	dispmsg

	mvi	a,NUMTRKS	;formatting all tracks on the disk
	sta	trkCnt		;remaining tracks = all tracks
	xra	a		;current track = track zero
	sta	dstTrk

	call	getDst		;get destination disk from operator
	rnz			;user aborted

	lxi	d,mRdyFmt	;display insert disk prompt
	call	getRdy		;get ready confirmation from operator
	rnz			;user aborted
	
; Fill track buffer with E5h byte

	lxi	h,trkBuf	;HL->track buffer
	lxi	b,TRKLEN	;BC=bytes on a track

cfFill	mvi	m,0E5h		;fill track buffer with E5h
	inx	h
	dcx	b		;decrement bytes remaining
	mov	a,b		;test for zero
	ora	c
	jnz	cfFill		;loop until filled

; Write all tracks from track buffer to format the drive

	lda	dstDrv		;select the drive
	call	selDsk

cfLoop	lxi	h,trkBuf	;HL->beginning of track buffer
	shld	trkPtr		;trkPtr->beginning of track buffer

	call	wrtTrk		;write a track from (trkPtr)
	jnz	fmtFail		;format failed

	lxi	h,dstTrk	;increment current track
	inr	m
	lxi	h,trkCnt	;decrement track counter
	dcr	m
	jnz	cfLoop

; format complete, display message and exit

	lxi	d,mFmtDon	;formatting done message
	call	dispMsg
	xra	a		;clear command character
	ret

;-----------------------------------------------------------------------------
; cmdVfy - Verify all tracks
;-----------------------------------------------------------------------------
cmdVfy	lxi	d,mCmdVfy	;display command name
	call	dispmsg

	mvi	a,NUMTRKS	;verifying all tracks on the disk
	sta	trkCnt
	xra	a		;current track = track zero
	sta	srcTrk

	call	getSrc		;get source disk from operator
	rnz			;user aborted

	lxi	d,mRdyVfy	;display insert disk prompt
	call	getRdy		;get ready confirmation from operator
	rnz			;user aborted

	lda	srcDrv		;select the drive
	call	selDsk

; vfyLoop - read all tracks which verifies sector meta-data and checksum

vfyLoop	lxi	h,trkBuf	;restart at beginning of trkBuf each time
	shld	trkPtr

	call	readTrk		;read a track

	lxi	h,srcTrk	;increment current track
	inr	m
	lxi	h,trkCnt	;decrement track counter
	dcr	m
	jnz	vfyLoop

; verify complete, display message and exit

	lxi	d,mVfyDon	;DE->verify done message
	call	dispMsg
	xra	a		;clear command character
	ret

;-----------------------------------------------------------------------------
; fillBuf - fill trkBuf with as many tracks that will fit, or the
;    remaining number of tracks to process, whichever is fewer.
; On Entry:
;    srcDrv = drive to read
;    srcTrk = track to start from
;    remTrks = number of tracks left to read
; On Exit:
;    srcTrk, remTrks updated (remTrks may go negative)
;    fillCnt = number of tracks read
;    Clobbers all
;-----------------------------------------------------------------------------
; compute fillCnt, the number of tracks to read into trkBuf

fillBuf	lda	srcDrv		;select the drive
	call	selDsk

	lxi	h,maxBuf	;set B=max tracks that fit in trkBuf
	mov	b,m

	lxi	h,remTrks	;set A&C=tracks remaining to process
	mov	a,m
	mov	c,a

	sub	b		;A=remaining-max in buffer
	mov	m,a		;save remaining tracks (may be negative)
	mov	a,b		;A=max tracks in buffer	
	jnc	doMax		;remaining >= max in buffer, do max in buffer

	mov	a,c		;otherwise, fill count=remaining tracks

doMax	sta	fillCnt		;fillCnt=number of tracks to read (saved)
	sta	trkCnt		;trkCnt=counter of tracks to read (decrements)

; fillLp - read fillCnt tracks.
	
	lxi	h,trkBuf	;HL->beginning of track buffer
	shld	trkPtr

fillLp	call	readTrk		;read a track into (trkPtr)
	
	lxi	h,srcTrk	;increment current track
	inr	m
	lxi	h,trkCnt	;decrement track counter
	dcr	m
	jnz	fillLp

	ret			;fill done, zero status

;-----------------------------------------------------------------------------
; wrtBuf - write fillCnt tracks from trkbuf
; On Entry:
;    dstDrv = drive to read
;    dstTrk = track to start from
;    fillCnt = number of tracks to write
; On Exit:
;    dstTrk updated 
;    Z set if OK, cleared for error
;    Clobbers all
;-----------------------------------------------------------------------------
wrtBuf	lda	dstDrv		;select the drive
	call	selDsk

	lda	fillCnt		
	sta	trkCnt		;trkCnt=counter of tracks to write

	lxi	h,trkBuf	;HL->beginning of track buffer
	shld	trkPtr

; wrtLoop - write loop. Write a track and verify. Repeat trkCnt times.

wrtLoop	call	wrtTrk		;write a track from (trkPtr)
	rnz			;exit if error

	lxi	h,dstTrk	;increment current track
	inr	m
	lxi	h,trkCnt	;decrement track counter
	dcr	m
	jnz	wrtLoop

	ret

;-----------------------------------------------------------------------------
; readTrk - read a track into memory at (trkPtr). Sector errors are displayed
;    and reading continues with the next sector.
; On Entry:
;    srcDrv = drive to read
;    srcTrk = track to read
;    trkPtr->location within track buffer for this track
; On Exit: 
;    trkPtr->last byte of track+1
;    Clobbers all
;-----------------------------------------------------------------------------
readTrk	call	chkQuit		;check for ctrl-c

	lda	srcTrk		;use BIOS to specify track
	call	setTrk

	mvi	a,NUMSECS	;secCnt = sector counter
	sta	secCnt

	xra	a
	sta	secNum		;starting sector number minus 1

readSec	call	nxtSec		;set next sector and DMA address
	call	read		;call BIOS read entry point
	ora	a		;test result (to be safe)
	jnz	rtErr		;read error

	lxi	h,secCnt	;decrement sector count
	dcr	m
	jnz	readSec

rtDone	mvi	a,'.'		;display '.' pacifier per track
	call	CONOUT

updtTrk	lhld	trkPtr		;HL=current track pointer
	lxi	d,TRKLEN	;DE=bytes in a track
	dad	d		;HL=start of next track in trkBuf
	shld	trkPtr

	xra	a		;return with good status
	ret

; rtErr - sector read error means the whole track is bad. Display the
;     error and resume

rtErr	call	chkQuit		;check for ctrl-c
	lxi	h,mErrTrk	;HL->where to put ascii track number
	lda	srcTrk		;A=track with error
	call	bin2dec		;insert ASCII track number
	lxi	d,mTrkErr	;display the track error message
	call	dispMsg
	jmp	rtDone		;continue

;--------------------------------------------------------------
; bin2dec - Binary byte in A to 3 ASCII digits at (HL) with
;   leading zero suppression
;--------------------------------------------------------------
bin2dec	mvi	m,'0'		;start with ASCII zero

lp100	sui	100		;count 100s
	jc	do10s		;no more 100s
	inr	m		;count the 100
	jmp	lp100

do10s	adi	100		;put back the extra 100
	inx	h		;move to 10s digit
	mvi	m,'0'		;start with ASCII zero

lp10	sui	10		;count 10s
	jc	do1s		;no more 10s
	inr	m		;count the 10
	jmp	lp10

do1s	adi	10+'0'		;compute ASCII 1s digit
	inx	h		;move to 1s digit
	mov	m,a		;store it

; suppress leading zeros

	dcx	h		;back to 100s to zero suppress
	dcx	h
	mov	a,m		;A=ASCII 100s digit
	cpi	'0'		;leading zero?
	rnz			;no
	mvi	m,' '		;else, suppress the zero

	inx	h		;leading zero in 10s digit?
	mov	a,m
	cpi	'0'		;leading zero?
	rnz			;no
	mvi	m,' '		;else, suppress the zero
	ret

;-----------------------------------------------------------------------------
; wrtTrk - write a track with from (hl). Verify is performed by the BIOS
;    if write verify is enabled in the MODE byte of the BIOS
; On Entry:
;    dstDrv = drive to write
;    dstTrk = track to write
;    HL->track buffer
; On Exit: 
;    HL->last byte of track+1 if successful
;    Z set if OK, cleared for error
;    Clobbers all
;-----------------------------------------------------------------------------
wrtTrk	call	chkQuit		;check for ctrl-c

	lda	dstTrk		;use BIOS to specify track
	call	setTrk

	mvi	a,NUMSECS	;secCnt = sector counter
	sta	secCnt

	xra	a		
	sta	secNum		;starting sector minus 1

wrtSec	call	nxtSec		;set next sector and DMA address
	call	write		;call BIOS write entry point
	ora	a		;test result (force Z flag)
	rnz			;write failure
				
	lxi	h,secCnt	;decrement sector count
	dcr	m
	jnz	wrtSec

; Write complete, display "*" pacifier, then update hl and exit

	mvi	a,'*'		;display '*' pacifier
	call	CONOUT
	jmp	updtTrk		;update trkPtr and exit (in readTrk)

;--------------------------------------------------------------
; nxtSec - Increment and wrap secNum, then call BIOS setSec
;    to set the sector for the next read. Compute the offset
;    of the new sector number from trkPtr and call BIOS
;    setDma to give the BIOS the buffer address.
; On Entry:
;    secNum = last sector accessed
;    trkPtr->address within trkBuf for current track
; On Exit:
;    secNum = new sector number, passed to BIOS
;    address within trkBuf for this sector passed to BIOS
; Clobbers all
;--------------------------------------------------------------
nxtSec	lda	secNum		;increment sector number
	inr	a
	cpi	NUMSECS+1	;past last sector?
	jc	haveSec		;valid sector

	mvi	a,1		;restart at sector 1	
havesec	sta	secNum		;save updated sector number

	call	setSec		;call BIOS setSec entry

	lda	secNum		;compute BC=128*(secNum-1)
	sui	1		;zero index and clear carry
	rar
	mov	b,a		;B=sector >> 1

	rar			;put lsbit into msbit	
	ani	80h
	mov	c,a		;BC=128*secNum

	lhld	trkPtr		;compute HL=trkPtr+sector offset
	dad	b
	call	setDma		;call BIOS setDma entry
	ret

;----------------------------------------------------------------------
; dispMsg - use the BDOS print message command to display the string
;    pointed to by DE.
; On Entry:
;    DE->message to display
; On Exit
;    clobbers all
;----------------------------------------------------------------------
dispMsg	mvi	c,PRINT		;BDOS command to display a string
	jmp	BDOS		;call BDOS

;----------------------------------------------------------------------
; readChr - read and return a single character but use the BDOS input
;   line command for "normal" look and feel. Return zero if no input
;   entered (e.g., just return pressed).
; On Entry:
;    DE->message to display
; On Exit
;    a = first character typed, zero if nothing typed
;    clobbers all
;----------------------------------------------------------------------
readChr	lxi	d,inBuf		;DE=pointer to input buffer
	mvi	a,IBUFLEN	;store max length to read
	stax	d
	mvi	c,RDLINE	;BDOS command, read line
	call	BDOS

	lxi	d,mCRLF
	call	dispMsg

	lda	inBuf+1		;get length read
	ora	a		;see if nothing entered
	rz			;if so, return zero

	lda	inBuf+2		;otherwise, return first character
	ret

;-----------------------------------------------------------------------------
; getSrc - get source drive from user, return in srcDrv, update drive
;    letter in message prompts.
; On Entry:
; On Exit: 
;    srcDrv = 0-3 if specified
;    Z set if valid drive entered, cleared otherwise
;    Clobbers all
;-----------------------------------------------------------------------------
getSrc	lxi	d,mSrc		;enter source drive prompt 
	call	dispMsg

	call	readChr		;read drive response
	ani	5fh		;clear bit 5 ('a' to 'A')
	sta	mSrcDrv		;update drive in messages
	sta	mVfyDrv
	sta	mSglSrc

	sui	'A'		;subtract ascii offset
	rc			;below 'A', exit with non-zero

	cpi	NUMDRVS		;valid drive number?
	jnc	getErr		;above 'D', exit

	sta	srcDrv		;save it

	xra	a		;return zero status
	ret

;-----------------------------------------------------------------------------
; getDst - get destination drive from user, return in dstDrv, update
;    drive letter in message prompts.
; On Entry:
; On Exit: 
;    dstDrv = 0-3 if specified
;    Z set if valid drive entered, cleared otherwise
;    Clobbers all
;-----------------------------------------------------------------------------
getDst	lxi	d,mDst		;enter source drive prompt 
	call	dispMsg

	call	readChr		;read drive response
	ani	5fh		;clear bit 5 ('a' to 'A')
	sta	mDstDrv		;update drive in messages
	sta	mFmtDrv
	sta	mSglDst

	sui	'A'		;subtract ascii offset
	rc			;below 'A', exit with non-zero

	cpi	NUMDRVS		;valid drive number?
	jnc	getErr		;above 'D', exit

	sta	dstDrv		;save it

	xra	a		;return zero status
	ret

; getErr - invalid response, return non-zero status

getErr	ora	a		;force non-zero staus
	ret

;-----------------------------------------------------------------------------
; getRdy - prompt user for confirmation of ready. 
; On Entry:
;    DE->message prompt to display
; On Exit: 
;    Z set if user confirmed, cleared if user aborted.
;    Clobbers all
;-----------------------------------------------------------------------------
getRdy	call	dispMsg		;display passed prompt
	lxi	d,mCont		;continue? prompt
	call	dispMsg

	call	readChr		;read response
	ani	5fh		;clear bit 5 ('a' to 'A')
	cpi	'Y'		;confirmed?
	ret

;-----------------------------------------------------------------------------
; ldSrc, ldDst - prompt user to load the source or destination disk if
;    this is a single drive copy.
; On Entry:
;    srcDrv = source drive
;    dstDrv = destination drive
; On Exit: 
;    Z set if ok to continue, cleared if user aborted.
;    Clobbers all
;-----------------------------------------------------------------------------
ldSrc	lxi	d,mInsSrc	;DE->prompt user to insert source disk
	jmp	chkSngl		;go see if single drive copy

ldDst	lxi	d,mInsDst	;DE->prompt user to insert destination disk

chkSngl	lxi	h,dstDrv	;HL->destination drive
	lda	srcDrv		;source = destination?
	cmp	m
	jz	getRdy		;single drive copy, get ready from user

	xra	a		;not single drive, return zero status
	ret

;-------------------------------------------------------------------
; wrFail, fmtFail - display fail message and return to main loop
;-------------------------------------------------------------------
wrFail	lxi	d,mWrtErr
	jmp	dispMsg

fmtFail	lxi	d,mFmtErr
	jmp	dispMsg

;--------------------------------------------------------------
; chkQuit - check for the user to request abort (ctrl-c). If
;    a character is present on the console port, read it and
;    see if ctrl-c. Clobbers all.
;--------------------------------------------------------------
chkQuit	call	conSt		;check for console input
	ora	a		;set z flag
	rz			;nothing present

	call	conIn		;read the character
	ani	7Fh
	cpi	CTRLC		;abort requested?
	rnz			;no

; Ctrl-C typed while program is running. Return to drive prompt.

	lxi	sp,ourStk	;restore stack pointer
	jmp	dspMenu		;start over

;-------------------------------------------------------------------
;  BIOS function calls. Single byte parameters all come in A and
;     are moved to C as required. Address passed in hl for setdma
;     moved to B, C as required.
;-------------------------------------------------------------------
conSt	lhld	WBOOT+1		;HL->warm start entry in BIOS
	lxi	d,bConSt	;DE=offset of console status function
	dad	d
	pchl			;jump to console status function

conIn	lhld	WBOOT+1		;HL->warm start entry in BIOS
	lxi	d,bConIn	;DE=offset of console input function
	dad	d
	pchl			;jump to console input function

conOut	lhld	WBOOT+1		;HL->warm start entry in BIOS
	lxi	d,bConOut	;DE=offset of console input function
	dad	d
	mov	c,a		;character to send in c
	pchl			;jump to console outpput function

selDsk	mov	c,a		;selDsk wants param in c
	lhld	WBOOT+1		;HL->warm start entry in BIOS
	lxi	d,bSelDsk	;DE=offset of select disk function
	dad	d
	pchl			;jump to select disk function

setTrk	mov	c,a		;setTrk wants param in c
	lhld	WBOOT+1		;HL->warm start entry in BIOS
	lxi	d,bSetTrk	;DE=offset of set track function
	dad	d
	pchl			;jump to set track function

setSec	mov	c,a		;setSec wants param in c
	lhld	WBOOT+1		;HL->warm start entry in BIOS
	lxi	d,bSetSec	;DE=offset of set sector function
	dad	d
	pchl			;jump to set sector function

setDma	mov	b,h		;BC=HL
	mov	c,l
	lhld	WBOOT+1		;HL->warm start entry in BIOS
	lxi	d,bSetDma	;DE=offset of set dma function
	dad	d
	pchl			;jump to set dma function (dma addr in bc)

read	lhld	WBOOT+1		;HL->warm start entry in BIOS
	lxi	d,bRead		;DE=offset of disk read function
	dad	d
	pchl			;jump to disk read function

; The BIOS Write entry point calls either the BIOS write routine
;    or the BIOS selDsk routine if the extended function
;    "Write without Read" is supported. Initialization patches
;    the proper routine offset into biosWrt.

write	mvi	c,EFDRIVE	;C=extended function request
	mvi	b,EFWRITE	;B=write w/o pre-read
	lhld	WBOOT+1		;HL->warm start entry in BIOS

;	lxi	d,bWrite	;DE=offset of select disk function
	db	11h		;LXI D opcode
biosWrt	dw	bWrite		;offset of BIOS entry to call

	dad	d
	pchl			;jump to write or selDsk (ext func)

;-------------------------------------------------------------------
;  String Constants
;-------------------------------------------------------------------
mBanner
 db CR,LF,'COPY v1.2 for Altair 1.5Mb Floppy$'

mMenu
 db CR,LF,LF,'======  O P T I O N   T A B L E  ======',CR,LF,LF
 db '  A = Copy all tracks',CR,LF
 db '  S = Copy only system track (0)',CR,LF
 db '  D = Copy only data tracks (1-148)',CR,LF
 db '  F = Format disk',CR,LF
 db '  V = Verify disk',CR,LF
 db '  X = Exit and return to CP/M',CR,LF,LF
 db 'Enter Selection: $'

mCmdAll
 db CR,LF,'  Copy All Tracks',CR,LF,'$'
mCmdSys
 db CR,LF,'  Copy Only System Tracks',CR,LF,'$'
mCmdDat
 db CR,LF,'  Copy Only Data Tracks',CR,LF,'$'
mCmdFmt
 db CR,LF,'  Format Disk',CR,LF,'$'
mCmdVfy
 db CR,LF,'  Verify Disk',CR,LF,'$'
mSrc
 db CR,LF,'Enter source drive (A-D or X to abort): $' 
mDst
 db CR,LF,'Enter destination drive (A-D or X to abort): $' 
mRdyCpy	
 db CR,LF,'Source on drive '
mSrcDrv	
 db 'A, destination on drive '
mDstDrv	
 db 'B$'
mRdyFmt	
 db CR,LF,'Disk to format in drive '
mFmtDrv	
 db 'A$'
mRdyVfy	
 db CR,LF,'Disk to verify in drive '
mVfyDrv	
 db 'A$'
mInsSrc	
 db CR,LF,'Insert source disk in drive '
mSglSrc	
 db 'A$'
mInsDst	
 db CR,LF,'Insert destination disk in drive '
mSglDst	
 db 'A$'
mCont	
 db CR,LF,'  Continue(Y/N)? $'
mSkErr	
 db CR,LF,'*** Restore/seek failure ***',CR,LF,'$'
mWrtErr	
 db CR,LF,LF,'** Unrecoverable write error **',CR,LF,'$'
mCpyDon
 db CR,LF,LF,'Copy complete. Note any displayed errors.',CR,LF,'$'
mVfyDon
 db CR,LF,LF,'Verify complete. Note any displayed errors.',CR,LF,'$'
mFmtDon
 db CR,LF,LF,'Formatting complete',CR,LF,'$'
mFmtErr
 db CR,LF,LF,'** Formatting failed **'
mCRLF
 db CR,LF,'$'
mTrkErr
 db CR,LF,'Read error on track '
mErrTrk
 db 'xxx$'

;-------------------------------------------------------------------
;  Data Area
;-------------------------------------------------------------------	
stack	ds	2		;save area for CP/M stack pointer
inBuf	ds	IBUFLEN		;storage for command string

srcDrv	ds	1		;source drive for copy
dstDrv	ds	1		;destination drive for copy

srcTrk	ds	1		;track source disk is on
dstTrk	ds	1		;track destination disk is on

maxBuf	ds	1		;number of tracks that can be buffered
fillCnt	ds	1		;number of tracks to buffer this time
trkCnt	ds	1		;counter of tracks while processing
remTrks	ds	1		;number of tracks remaining to process
trkPtr	ds	2		;pointer into trkBuf

secNum	ds	1		;current sector number
secCnt	ds	1		;count of sectors left to process
secBuf	ds	128		;sector read buffer when verifying writes

trkErr	ds	1		;non-zero = read error on the track

	ds	64		;our stack
ourStk	equ	$
trkBuf	equ	$

	end

