
bdos		equ	00005h		; BDOS entry address

jump$table	equ	00001h		; address of address of CP/M
					;  jump table
TOM		equ	00006h		; address of address of top
					;  of memory
cr		equ	00Dh		; carriage return
lf		equ	00Ah		; line feed
vt		equ	00Bh		; vertical tab
ht		equ	009h		; horizontal tab
bs		equ	008h		; backspace
bell		equ	007h		; sound bell
blank		equ	0E5h		; if whole sector contains this
					;  for data then sector is
					;  considered empty or blank
; BDOS function codes

inputf		equ	00Ah		; input from console into 
					;  buffer until 'CR' is sensed
printf		equ	009h		; print to console from buffer
					;  until '$' is sensed
char$in		equ	001h		; input single character from
					;  console
char$out	equ	002h		; send single character to
					;  console
reset$dsk	equ	00Dh		; reset disk system
reboot		equ	000h		; system reboot (warm) function


; more program constants

ser$def$len	equ	6		; length of serial field

	org	0100h

	jmp	start

wboot		ds	3		; jump to warm boot routine
conin		ds	3		; read character from console
conout		ds	3		; send character to console
const		ds	3		; check for console char ready
list		ds	3		; send character to list device
punch		ds	3		; send character to punch 
reader		ds	3		; get character from reader
home		ds	3		; move to track 00 on selected
					;  drive
seldsk		ds	3		; select disk drive
					;  reg C=disk # (0=A,1=B,etc.)
settrkf		ds	3		; select track function
					;  reg BC=track #
setsecf		ds	3		; select sector function
	‰			;  reg C = sector #
setdma		ds	3		; set dma address function
					;  reg BC=address
readf		ds	3		» read disk at select track
					;  and sector, into memory
wòitef		ds	3		; write to disk from memory
					;  at dma address to selected
					;  track and sector
listst		ds	3		; return list status
sectran		ds	3		; sector translate routine

start:
	lxi	sp,stack		; init stack pointer

; fill program jump table with proper information
	lhld	jump$table		; get starting address
					;  of system jump table
	lxi	d,wboot			; get address of our jump
					;  table
	mvi	b,3*16			; init byte counter
	call	mov$blk			; fill our jump table

; initialize ser$num to zero
	lxi	h,ser$num
	mvi	b,ser$def$len
initlop07:
	mvi	m,'0'
	inx	h
	dcr	b
	jnz	initlop07

; give sign on 
	call	clear			; clear the monitor
	lxi	d,msg0			; get address of char string
	call	pmsg			; print string

; prompt operator for source and destination drives
	lxi	d,msg12			; send source message to
	call	pmsg			;  console
	call	get$char		; get disk code
	sui	'A'			; correct it
	sta	dskS			; store code of source disk
	lxi	d,msg13			; send destination message
	call	pmsg			;  to console
	call	get$char		; get disk code
	sui	'A'			; correct it
	sta	dskD			; store code of dest disk

; prompt operator for source disk
	lxi	d,msg14			; get message address
	call	pmsg$wait

; the reset function clears out any previous information pertaining
;  to format, sector size, density, etc. and the next time a disk is
;  selected, those values will be reinitialized.
	mvi	c,reset$dsk
	call	bdos

; when a disk is selected CP/M initializes the Disk Parameter Header
;  and the Disk Parameter Block to conform with the format of the
;  selected disk
	call	selectS			; select source disk

; when the "select disk" function is performed the address of the DPH
;  (Disk Parameter Header) is returned in the HL register pair.  the
;  1st word in the DPH is the address of the system's sector
;  translation table.
	mov	a,m			; get low byte of table address
	sta	xlt$offset		; save it
	inx	h			; get high byte of address
	mov	a,m
	sta	xlt$offset+1		; save it

; the DPH also contains the address of the DPB (Disk Parameter Block).
	lxi	d,9			; the DPB address is 10 bytes
	dad	d			;  10 bytes in DPH
	mov	a,m			; get lower half of address
	sta	DPB$addr		; store it
	inx	h			; get upper half of address
	mov	a,m
	sta	DPB$addr+1		; store it

; move DPB (Disk Parameter Block) into program data area
	lxi	d,DPB			; DE points to program DPB
	lhld	DPB$addr		; HL points to system DPB
	mvi	b,15			; B is a counter
	call	mov$blk			; do the move

; get system sector translation table into data segment of program
	lhld	xlt$offset		; get address of trans table
	mov	a,h			; if HL = 0 then system doesn't
	ora	l			;  have a translation table
	jnz	has$table		;  which means that I'll have
	lda	spt			;  to init "xlt$tab" myself
	mov	c,a
	mov	a,0
	lxi	h,xlt$tab
initlop0:
	mov	m,a
	inr	a
	inx	h
	dcr	c
	jnz	initlop0
	mov	a,1			; set skew factor = 1
	jmp	skew$prompt
has$table:
	lhld	xlt$offset		; get address of system's
	xchg				;  translation table
	lxi	h,xlt$tab		; DE -> system table
					; HL -> program table
	lda	spt			; init counter
	mov	c,a
initlop1:
	ldax	d			; get a byte
	mov	m,a			; store it
	inx	d			; move pointers
	inx	h
	dcr	c			; decrement counter
	jnz	initlop1		; loop if C <> 0

; depending on the system, each physical sector on a diskette can
;  be made up of a number of logical sectors (128 bytes long).  the
;  following code analyses the system sector translate table to
;  determine the number of logical sectors in a physical sector.
	lxi	h,xlt$tab
	mvi	c,0			; init counter
xltlop0:
	inr	c			; increment counter
	mov	a,m
	inr	a
	inx	h			; HL -> next byte in table
	cmp	m			; see if the two numbers are in
	jz	xltlop0			;  the right order
	mov	a,c			; save the sectors/block value
	sta	spb
; calculate skew of system sector translation table
	mov	a,m			; get byte at xlt$tab+spb
	lxi	h,xlt$tab
	sub	m
	call	divide			; divide A by C
; tell operator about skew
skew$prompt:
	push	b			; save system skew
	call	clear			; blank monitor screen
	lxi	d,msg21			; send message
	call	pmsg
	pop	psw			; recover skew
	ori	030h			; make it ASCII
	mov	e,a			; send it console
	mvi	c,char$out
	call	bdos
; ask operator if he/she would like to change the sector skew
	lxi	d,msg16
	call	pmsg
	call	get$char
	cpi	'N'
	jz	dont$change
; ask for new skew factor
	lxi	d,msg17
	call	pmsg
	call	get$1num
	sta	skew
; make new sector translation table using skew factor supplied by
;  operator
	lxi	h,xlt$tab		;  HL -> "xlt$tab"
	mov	b,m			; init beginning sector number
	mov	c,b
	lda	skew			; get skew factor to init
	push	psw			;  loop counter
	lda	spt			; get sectors/track + beginning
	add	c			;  sector # into reg D
	mov	d,a
xltlop1:
	push	b			; save loop counter and 1st
					;  sector #
	lda	spb			; init sectors/block counter
	mov	e,a
	mov	a,b			; get current starting sector
					;  of block into reg A
xltlop2:
	mov	m,a			; put sector number into table
	inx	h			; increment pointer
	inr	a			; increment sector number
	dcr	e			; decrement spb counter
	jnz	xltlop2
	lda	spb			; calcualte new starting sector
	mov	c,a
	lda	skew
	call	mult
	pop	b			; recover starting sector #
	push	b			;  and save it again
	add	b			; A = spb*skew+current sector
					;  number
	cmp	d			; see if new sector # > spt
	jz	xltlop3			; go to xltlop3 if 0
	jnc	xltlop3
	pop	b			; recover starting sector #
					;  and skew then move new
	mov	b,a			;  starting sector # into B
	jmp	xltlop1			; do it again
xltlop3:
	pop	b			; recover current and starting
					;  sector #s
	lda	spb			; get sectors/block
	add	c			; add it to starting sector #
	mov	b,a			; init both sector registers
	mov	c,a
	pop	psw			; recover block counter
	dcr	a
	jnz	xltlop1

dont$change:
; ok, now calculate how many bytes there are on a track.
;  bytes/track = sectors/track * logical sector size.
	lda	spt			; A=sectors/track
	mvi	d,0			; init DE=sectors/track
	mov	e,a
	lxi	h,0			; init HL=0
	mvi	b,128			; init addition counter
multlop0:
	dad	d			; HL=HL+DE
	dcr	b
	jnz	multlop0
	shld	bpt

; search for blank tracks starting with last$trk
; the portion of the source disk which is empty (blank) isn't copied
	lxi	d,msg1			; tell operator about search
	call	pmsg
	lxi	d,msg7			; send "Reading" message
	call	pmsg
	lda	last$trk		; get value of 'last$track'
	mvi	b,0			; B=0
	mov	c,a			; C=A init track counter
scan$loop:
	push	b			; save track counter
	call	settrkf			; set track number
	pop	b			; get track number into reg A
	push	b
	mov	a,c			; make track number ASCII
	call	make$ascii
	lxi	d,ascii$trk		; print it
	call	pmsg
	lxi	h,trk$buff		; init dma address
	shld	dma$base
	push	h			; temp save HL (dma address)
	call	read		; read a track into 'trk$buff'
; check to see if the track that was just read is blank
	pop	d			; recover dma address
	lhld	bpt			; get byte counter
	xchg				; HL -> "trk$buff" 
					; DE = byte counter
blank$scan:
	push	d			; save byte counter
	mov	a,m			; A=(HL)
	cpi	blank			; if A<>blank then track isn't
	jnz	data$found		;  empty
	inx	h			; HL points to next byte
	pop	d			; recover byte counter
	dcx	d			; decrement byte counter
	mov	a,d			; see DE = 0
	ora	e
	jnz	blank$scan		; do again if DE <> 0
	call	erase$trk		; erase old track number
	pop	b			; recover track counter
	dcr	c			; decrement counter
	jnz	scan$loop		; do again if C <> 0

; the diskette is blank if program flow reaches this point
	lxi	d,error$msg0		; inform operator that source
	call	pmsg			;  diskette is blank and
	call	abort			;  abort operation

; 'last$trk' will reflect which track was found to contain data
data$found:
	pop	b			; clear stack
	pop	b			; recover track counter
	mov	a,c			; move track count to reg A
	sta	last$trk		; save track count

; the information on the source disk will more than likely need to be
;  paged into memory.  the following code calculates how many pages
;  or blocks will be need.

; calculate amount of memory available to be used for a buffer area
;  to hold the track data
	lxi	d,trk$buff
	lhld	TOM			; get end of TPA address
	mov	a,l			; now find the difference
	sub	e
	mov	l,a
	mov	a,h
	sbb	d
	mov	h,a			; HL = HL-DE
; calculate number of tracks that will fit in that space
	xra	a			; clear carry flag
	lda	spt			; reg A = sectors/track
	rar				; divide SPT by 2
	mov	c,a			; reg C = SPT/2
	mov	a,h			; reg A = available mem/256
	call	divide			; B = A/C
	dcr	b			; leave room for extra track
; you probably realize that I'am going to need two page sizes
;  (tracks/page) to read in the diskette.
;  example:
;   let's say that after scaning the diskette for blank tracks and
;   taking into account reserved tracks which won't be copied, we
;   find that there are 33 tracks with data.  next we calculate that
;   there is enough spare memory in our system to hold 9 tracks of
;   data from the disk (page size=9).  that means that we can read
;   3 pages of 9 tracks each and then 1 page of 6 tracks (33=3*9+1*6).
;   that's is why there are two track/block variables, "B1$cnt" and
;   "B2$cnt".
	lda	OFF			; get number of reserved tracks
	mov	c,a			; C = A
	lda	last$trk		; get last track with data
	sub	c			; A = # of tracks with data
	cmp	b			; if last$trk<b1$cnt then
	jp	over0			;  b1$cnt=last$trk
	sta	b1$cnt
	mvi	b,1			; will only need 1 page
	sub	a			;  and no block2 count
	jmp	b2$too
over0:
	push	psw			; temp save last$trk
	mov	a,b			; save "B1$cnt"
	sta	B1$cnt
	pop	psw			; recover "last$trk"
	mov	c,b			; prepare for division
	call	divide			; calcualte how many "B1$cnt"
					;  pages there are
b2$too:
	sta	b2$cnt			; block2 track count=remainder
	mov	a,b			; save block count
	sta	block$cnt

; prompt operator for total number of serial numbers on disk
	call	get$cnt
	cpi	0			; if count = 0 then don't ask
	jz	over09			;  for starting serial number

; prompt operator for starting serial number
	call	get$serial
over09:
; ask operator if he/she wants to verify the copy against the source
;  disk and if so, how often
	call	clear
	lxi	d,msg18			; ask question
	call	pmsg
	sub	a			; init verification frequency
	sta	ver$freq		;  value
	call	get$char		; get Y/N answer
	cpi	'N'			; check for No
	jz	over1
; ask operator for frequency of verification
	lxi	d,msg19			; send message
	call	pmsg
	call	get$2num
	sta	ver$freq		; save value
over1:

; clear monitor screen
	call	clear

	sub	a			; init diskette counter
	sta	dsk$cnt

; copy and serialization portion of program
new$disk:
	lxi	h,ser$num		; move current serial number
	lxi	d,serial1		;  into 'Insert' message
	mvi	b,ser$def$len
	call	mov$blk

	lxi	d,msg3			; prompt operator for
	call	pmsg$wait		;  destination diskette

; home both disk drives
	call	selectD			; select destination drive
	call	home			; home it
	call	selectS			; select source drive
	call	home			; home it

	sub	a			; init serial field counter
	sta	ser$cnt

	sub	a			; decides whether to use
	sta	tog0			;  b1$cnt or b2$cnt

	lda	b1$cnt			; init track counter
	sta	trk$cnt

	lda	OFF			; get number of reserved tracks
	sta	trackS			; init source disk track
					;  register
	sta	trackD			; init destination disk
					;  track register

	lxi	d,msg7			; send "Reading" message
	call	pmsg
	call	seekS			; seek
	lxi	h,trk$buff		; init dma address and read in
	shld	dma$base		;  1st track so that there is
	call	read		;  always a extra one in memory
					;  memory
	call	erase$msg		; erase "Reading" message

	lda	block$cnt		; get block counter
	mov	b,a
create$dsk:
	push	b			; save block counter
	call	selectS			; select source drive
	lhld	bpt			; calculate dma address
	lxi	d,trk$buff
	dad	d
	shld	dma$base		; save dma address
	lxi	d,msg7			; send 'Reading' message
	call	pmsg
	lda	trk$cnt			; init track counter
	mov	b,a
lop0:
	push	b			; save track counters
	call	seekS			; set track to read
	call	read		; read track
	call	erase$trk		; erase old track number
	call	track$setup		; move dma base address
	pop	b			; recover counter
	dcr	b
	jnz	lop0
	call	erase$msg		; erase "Reading" message

	call	serialize		; insert serial number

	call	selectD			; select destination drive
	lxi	h,trk$buff		; init dma address
	shld	dma$base
	lxi	d,msg8			; send 'Writing message
	call	pmsg
	lda	trk$cnt			; init track counter
	mov	b,a
lop1:
	push	b			; save track counter
	call	seekD			; set track to write
	call	write		; write track
	call	erase$trk		; erase old track number
	call	track$setup		; move dma base address
	pop	b			; recover track counter
	dcr	b
	jnz	lop1
	call	erase$msg		; erase "Writing message

; move extra track (read and searched but not written) to beginning
;  of buffer
	lhld	dma$base		; get address of unwritten
					;  track
	xchg				; DE=HL, DE-> beginning of
					;  extra track
	lhld	bpt			; get length of track
	push	h			; BC=HL
	pop	b
	lxi	h,trk$buff		; HL-> beginning of buffer
movlop5:
	ldax	d			; get byte
	mov	m,a			; move it
	inx	h			; increment pointers
	inx	d
	dcx	b			; decrement byte counter
	mov	a,b			; am I done ?	
	ora	c
	jnz	movlop5

	pop	b			; recover block counter 
	dcr	b			; decrement counter
	jnz	create$dsk		; do again if B<>0

	lda	tog0			; check toggle
	cpi	1
	jz	done			; if tog0=1 then the copy
					;  process is done
	mvi	a,1			; if tog0<>1 then need to
	sta	tog0			;  finish copy with 
	lda	b2$cnt			;  trk$cnt=b2$cnt
	cpi	0			;  but if b2$cnt=0 then
	jz	done			;  we are done
	sta	trk$cnt
	mvi	b,1			; reint block counter
	jmp	create$dsk		;  and finish copying
done:
	lxi	d,msg8			; send 'Writing message
	call	pmsg
	call	seekD			; write the extra track that
	call	write		;  was read in beginning

	lda	ser$cnt			; check if all the serial
	mov	b,a			;  fields were found
	lda	ser$num$cnt
	cmp	b
	jz	serial$ok
	lxi	d,error$msg3		; inform operator of error
	call	pmsg
	call	abort			; abort operation
serial$ok:

	call	erase$msg		; erase "Writing" message

	lxi	h,dsk$cnt		; get and increment diskette
	inr	m			;  counter
	mov	b,m
	push	b			; save it on stack

	lda	ver$freq		; get verification frequency
	cpi	0			; see if "ver$freq" = 0
	jz	dont$verify		; don't verify if yes
	mov	c,a			; "ver$freq" is divisor
	pop	psw			; "dsk$cnt" is quotient
	call	divide			; do division
	cpi	0			; if A<>0 then don't verify
	jnz	dont$verify
; do the verification
	lxi	d,msg20			; send "Verifying" message
	call	pmsg
	lda	off			; calculate # of tracks to
	sta	trackS			;  verify
	sta	trackD
	mov	b,a
	lda	last$trk
	sub	b
	inr	a
	mov	c,a			; init counter
verlop0:
	push	b			; save track counter
	call	selectD			; select dest disk
	call	seekD			; seek to current track
	lxi	h,trk$buff		; init dma address
	shld	dma$base
	call	read		; read a track from dest drive
	call	erase$trk		; erase old track message
	call	selectS			; select source disk
	call	seekS			; seek to current track
	lxi	h,trk$buff		; init dma address
	push	h			; save pointer to 1st buffer
	xchg
	lhld	bpt
	push	h			; save bytes/track value
	dad	d
	push	h			; save pointer to 2nd buffer
	shld	dma$base
	call	read		; read track from source disk
	call	erase$trk		; erase old track message
	pop	d			; recover pointer to 2nd buffer
	pop	b			; recover bytes/track value
	pop	h			; recover pointer to 1st buffer
verlop1:
	ldax	d			; get byte from dest buffer
	cmp	m			; compare it against byte
					;  from source buffer
	jnz	verlop3			; jump if not equal
	inx	d			; increment pointers
	inx	h
	dcx	b			; decrement byte counter and
	mov	a,b			;  check for BC = 0
	ora	c
	jnz	verlop1
verlop2:
	pop	b			; recover track counter
	dcr	c			; decrement counter
	jnz	verlop0
	jmp	dont$verify
verlop3:
	mvi	a,ser$def$len		; get length of serial
					;  definition
	push	h			; save dest pointer
	lxi	h,ser$def		; HL -> serial definition
verlop4:
	sta	cnter			; save value of counter
	ldax	d			; get byte from source buffer
	cmp	m			; see it equals byte from
	jnz	ver$error		;  serial definition
	inx	d			; increment pointers
	inx	h
	dcx	b			; decrement buffer byte counter
	mov	a,b			; and check for BC = 0
	ora	c
	jz	verlop2
	lda	cnter			; get counter
	dcr	a			; decrement byte counter
	jnz	verlop4			; do again if C <> 0
	pop	h			; recover dest pointer
	push	b			; save buffer byte counter
	lxi	b,ser$def$len		; correct it
	dad	b
	pop	b			; recover byte counter
	jmp	verlop1
; found a verification error so tell operator and try again
ver$error:
	call	clear			; clear monitor screen
	lxi	d,error$msg7
	call	pmsg
	jmp	new$disk
dont$verify:
	lxi	h,ser$num		; insert serial number into
	lxi	d,serial2		;  'Remove' message
	mvi	b,ser$def$len
	call	mov$blk

	call	clear			; clear monitor screen
	lxi	d,msg4			; send 'Remove' message to
	call	pmsg			; send to console

	call	inc$serial		; increment serial number

	jmp	new$disk		; do it again

error:
	lxi	d,errormsg3		; inform operator of error
	call	pmsg$wait		; send message to console

	jmp	wboot			; abort program

track$setup:
	lhld	bpt
	xchg
	lhld	dma$base
	dad	d
	shld	dma$base
	ret

inc$serial:
	mvi	b,ser$def$len		; B = length of serial number
	lxi	h,ser$num+ser$def$len-1 ; HL points to end of serial
					;  number
addlop0:
	push	b			; save byte counter
	mov	a,m			; get byte of serial number
	inr	a			; increment byte
	daa				; decimal adjust result
	push	psw			; need to check AC (auxiliary
	pop	b			;  carry) flag to see if there
	mov	a,c			;  was a carry from the low
	ani	010h			;  nybble to the high nybble
	jnz	fix			; increment the next byte if
					;  there was a carry
	mov	m,b			; else save serial number byte
	pop	b			;  and stop
	ret
fix:
	mov	a,b			; recover serial number byte
	ani	00Fh			; mask off upper nybble
	ori	030h			; make high nybble '3' again
	mov	m,a			; store byte
	dcx	h			; point to next byte
	pop	b			; recover byte counter
	dcr	b			; decrement byte counter
	jnz	addlop0			; do again if B<>0
	ret

; search for all occurances of 'ser$def' and insert serial number
serialize:
	lhld	dma$base		; get address of end of buffer
	dcx	h			; HL->end of trk$buff
	mov	a,m			; save last byte in trk$buff
	push	psw
	push	h			; save address of byte
	mvi	m,0			; put 00h at end of buffer
	inx	h			; HL -> end of "trk$buff" + 1
	xchg				; DE = HL
	lxi	h,trk$buff		; HL points to trk$buff
serlop0:
	lda	ser$def			; get 1st digit of ser$def
					;  into the source register
	cmp	m			; compare source with (HL)
	jz	serlop2			; if equal then check further
	inx	h			; increment HL
	mov	a,h			; am I done ?
	cmp	d
	jnz	serlop0
	mov	a,l
	cmp	e
	jnz	serlop0
	pop	h			; restore last byte of trk$buff
	pop	psw
	mov	m,a
	ret
serlop2:
	push	d			; save pointer to end of buffer
	push	h			; save 'trk$buff' pointer
	inx	h			; HL points to next byte
	lxi	d,ser$def+1		; DE points to 'ser$def'+1
	mvi	b,ser$def$len-1		; B = length 'ser$def'-1
serlop3:
	ldax	d			; get next byte of 'ser$def'
	cmp	m			; compare with where HL points
	jnz	not$ser$field		; if not equal stop compare
	inx	h			; increment HL
	inx	d			; increment DE
	dcr	b			; decrement byte counter
	jnz	serlop3			; do again if B<>0
; found a serial field so increment field counter
	lxi	h,ser$cnt
	inr	m
; no move current serial number into track data
	pop	h			; recover 'trk$buff' pointer
	lxi	d,ser$num		; DE points to current serial
					;  number
	mvi	b,ser$def$len		; init byte counter
serlop4:
	ldax	d			; get serial number byte
	mov	m,a			; move to where HL points
	inx	d			; increment DE
	inx	h			; increment HL
	dcr	b			; decrement byte counter
	jnz	serlop4			; if B<>0 do again
	pop	d			; recover end of buffer pointer
	jmp	serlop0			; continue with serialization
not$ser$field:
	pop	h			; recover trk$buff pointer
	inx	h			; HL points to next byte
	pop	d			; recover end of buffer
					;  pointer
	jmp	serlop0			; continue with serialization

; read a track from disk (track and disk drive already selected)
read:
	lda	spt			; source = sectors/track
	mov	b,a			; B=A  loop counter
	mvi	c,0			; init sector counter to 0
rtrklop0:
	push	b			; save loop counter on stack
	call	setsec			; set sector to read
	call	setdma			; set dma address
	call	readf			; read at selected track
					;  and sector
	cpi	000h			; check for read error
	jnz	rd$error		; if yes then tell operator
	pop	b			; recover loop and sector
					;  counters
	inr	c			; increment sector counter
	dcr	b
	jnz	rtrklop0		; do again if B<>0
	ret
rd$error:
	lxi	d,errormsg4		; prompt operator about error
	call	pmsg
	call	abort			; abort operation

; write a track to disk (track and disk already selected)
write:
	lda	spt			; source = sectors/track
	mov	b,a			; B=A
	mvi	c,0			; init sector counter
wtrklop0:
	push	b			; save loop and sector counters
	call	setsec			; set sector thru BDOS
	call	setdma			; set dma address thru BDOS
	call	writef			; write sector to disk
	cpi	000h			; check for write error
	jnz	wr$error		; if yes then tell operator
	pop	b			; recover loop and sector
					;  counters
	inr	c			; increment sector counter
	dcr	b			; decrement loop counter
	jnz	wtrklop0		; do again if B<>0
	ret
wr$error:
	lxi	d,errormsg5		; prompt operator about error
	call	pmsg
	call	abort			; abort operation

; do sector translation and set sector
setsec:
	mvi	b,0			; B=0
	lxi	h,xlt$tab		; HL -> translation table
	dad	b			; HL points to translated
					;  sector number
	mov	c,m			; C = translated sector number
	push	b
	call	setsecf			; set sector thru BDOS
	pop	b
	mov	a,c
	lxi	h,xlt$tab
	sub	m
	add	a
	mov	c,a
	lxi	h,addr$tab
	dad	b
	mov	c,m
	inx	h
	mov	b,m
	lhld	dma$base
	dad	b
	push	h
	pop	b
	ret

; move block (HL) to block (DE) with length of B
mov$blk:
	mov	a,m
	stax	d
	inx	d
	inx	h
	dcr	b
	jnz	mov$blk
	ret

; select source disk
selectS:
	lda	dskS
	jmp	select

; select destination disk
selectD:
	lda	dskD
select:
	mov	c,a
	call	seldsk
	ret

; select track on source disk
seekS:
	lxi	h,trackS
	jmp	seek

; select track on destination disk
seekD:
	lxi	h,trackD
seek:
	mov	c,m
	inr	m
	mvi	b,0
	push	b
	mov	a,c
	call	make$ascii
	lxi	d,ascii$trk
	call	pmsg
	pop	b			; recover track number
	call	settrkf
	ret

; print string on console
pmsg:
	mvi	c,printf
	call	bdos
	ret

; print string on console and wait for carriage return
pmsg$wait:
	call	pmsg
waitlop:
	mvi	c,char$in
	call	bdos
	cpi	'C'-'@'			; check for control-C
	jz	abort			; abort if yes
	cpi	cr
	jnz	waitlop
	ret

; abort the program
abort:
	lxi	d,abort$msg
	call	pmsg$wait
	mvi	c,reboot		; BDOS system reboot function
	call	bdos

; reads in starting serial number from console
get$serial:
	lxi	d,msg2			; DE = operator prompt address
	call	pmsg			; send it to console
	mvi	c,inputf		; C = input string BDOS code
	lxi	d,con$buff0		; DE = address of console buff
	call	bdos			; BDOS call
	lda	buff0len		; check to see if got right
	cpi	ser$def$len		;  number of digits
	rz				; yes so can return
	mvi	a,ser$def$len		; give operator correct
	ori	030h			;  number of digits to enter
	sta	errormsg2
	lxi	d,errormsg1		; tell operator about error
	call	pmsg
	jmp	get$serial		; do again

; get from the operator how  many serial fields are on the source
;  diskette.  this is a safeguard to help prevent the wrong disk
;  from being used.
get$cnt:
	lxi	d,msg6			; send prompt to console
	call	pmsg
	call	get$2num		; get a number (0-99) from
					;  operator
	sta	ser$num$cnt		; save number
	ret

; input two ASCII numbers into buffer 1 and translate them into a
;  binary number which is returned in the A register.
get$2num:
	mvi	c,inputf		; tell bdos to get input
	lxi	d,con$buff1		; DE=address of input buffer
	call	bdos
	lda	buff1len		; how many characters were read
	cpi	3			; check for more than two
	jm	input$ok0		; if two or less then OK
input$err0:
	lxi	d,errormsg6		; tell operator about problem
	call	pmsg
	jmp	get$2num		; try again
input$ok0:
	cpi	0			; need at least one digit.
	jz	input$err0		;  tell operator if not
	mov	c,a			; going to make HL->end of
	mvi	b,0			;  input string
	lxi	h,count$num
	dad	b
	dcx	h
	mov	a,m			; get units digit
	ani	00Fh			; mask off upper nybble of
					;  units digit
	dcr	c			; decrement digit counter
	jz	units			; if C=0 then only had one
					;  digit so can exit
	push	psw			; save units digit
	dcx	h			; make HL -> next digit
	mov	a,m			; get tens digit
	ani	00Fh			; mask off upper nybble
	mvi	c,10			; init multiplier
	call	mult			; A = A*C
	pop	b			; get units digit back
	add	b			; add tens and units digits
units:
	ret

; get a single ASCII number from operator and check for control-C
get$1num:
	mvi	c,char$in
	call	bdos
	cpi	'C'-'@'
	jz	abort
	ani	00Fh
	ret

; this code erases the "Reading" and "Writing" messages from the
;  monitor screen
erase$msg:
	lxi	d,msg10
	call	pmsg
	ret

; this code erases the old track numbers from the monitor screen
erase$trk:
	lxi	d,msg11
	call	pmsg
	ret

; clear monitor screen
clear:
	lxi	d,clear$scn		; get address of char string
	call	pmsg			; call print routine
	ret

; translate the 8 bit number in A to ASCII into "ascii$trk"
make$ascii:
	lxi	h,'  '			; fill "ascii$trk" with blanks
	shld	ascii$trk
	shld	ascii$trk+2
	lxi	h,ascii$trk+3		; HL -> "ascii$trk"+3
makelop0:
	mvi	c,10			; init divisor
	call	divide			; B=A/C remainder in A
	adi	'0'			; make remainder ASCII
	mov	m,a			; store it in "ascii$trk"
	dcx	h			; move pointer to next location
	mov	a,b			; get qoutient into A
	cpi	0			; is qoutient zero yet ?
	jnz	makelop0		; no, have more digits
	ret

; get ASCII input from operator and translate it to upper case
get$char:
	mvi	c,char$in		; iniô BDOS call
	call	bdos
	cpi	'C'-'@'			; check for control-C
	jz	abort			; abort if yes
	ani	05Fh			; xlate lower to upper case
; echo choice back to operator
echo$choice:
	push	psw			; temp save
	mvi	e,bs			; backspace over last input
	mvi	c,char$out		; send backspace to console
	call	bdos
	pop	psw			; recover old value
	push	psw			; and save it again
	mov	e,a			; send character to console
	mvi	c,char$out
	call	bdos	
	pop	psw			; restore old value
	ret

; divide number in register A by number in register C and return
;  quotent in register B with remainder in register A.
divide:
	mvi	b,0			; set result result reg = 0
divlop0:
	cmp	c			; if C>A then quit
	rc
	sub	c			; A = A-C
	inr	b			; increment result register
	jmp	divlop0
	ret

; multiply register A by register C and return result in register A
;  assume that A*C will be <= 255
mult:
	mov	b,a
	sub	a			; zero out result register
	dcr	c			; check for C = 0
	jm	dont$mult
	inr	c	
multlop1:
	add	b
	dcr	c
	jnz	multlop1
dont$mult:
	ret


; **** Data Area **** ;


cnter		ds	1		; will contain a counter value

sector		ds	1		; sector number held here

tog0		ds	1

trackS		db	76		; source disk track register
trackD		db	76		; destination disk track reg

dskS		ds	1		; code for source disk
dskD		ds	1		; code for destination disk

trk$cnt		ds	1		; how many tracks in block to
					;  read or write

ver$freq	ds	1		; holds	verification frequency
dsk$cnt		ds	1		; number of copies nade so far

skew		ds	1		; storage for skew factor
spb		ds	1		; sectors/ block storage
xlt$tab		ds	128		; sector translation table
addr$tab	dw	0000h,0680h,0480h,0B00h,0280h,0900h,0080h
		dw	0700h,0500h,0B80h,0300h,0980h,0100h,0780h
		dw	0580h,0C00h,0380h,0A00h,0180h,0800h,0600h
		dw	0C80h,0400h,0A80h,0200h,0880h

bpt		ds	2		; number of bytes/track

b1$cnt		ds	1		; number of tracks in 1st block
b2$cnt		ds	1		; number of tracks in 2nd block
block$cnt	ds	1		; how blocks of b1$cnt size

ser$def		db	'654321'	; serial field on master disk

con$buff0	db	10		; console input buffer
buff0len	ds	1
ser$num		ds	10

con$buff1	db	5		; console input buffer
buff1len	ds	1
count$num	ds	5

ser$num$cnt	ds	1		; how many serial fields
					;  are there on the source disk
ser$cnt		ds	1		; counter for number of serial
					;  fields
last$trk	db	76		; be sure this is right for
					;  your system

dma$base	ds	2		; save area for current dma
					;  base address

dma$addr	ds	2		; save area for current dma
					;  address

clear$scn	db	cr,lf,lf,lf,lf,lf,lf,lf,lf,lf,lf,lf,lf,lf,lf
		db	lf,lf,lf,lf,lf,lf,lf,lf,lf,lf
		db	vt,vt,vt,vt,vt,vt,vt,vt,vt,vt,vt,vt,vt,vt,vt
		db	vt,vt,vt,vt,vt,vt,vt,vt,vt
		db	'$'

msg0		db	cr,lf,lf
		db	'-------------------------'
		db	'-------------------------'
		db	'-'
		db	cr,lf
		db	'SERIAL8           V1.0  Serial No. '
		db	' SER-0000-000002'
		db	cr,lf
		db	'Copyright (C) 1982'
		db	cr,lf
		db	'Digital Research, Inc.          '
		db	'All Rights Reserved'
		db	cr,lf
		db	'-------------------------'
		db	'-------------------------'
		db	'-'
		db	cr,lf,lf
		db	'Diskette Generation Utility',cr,lf
		db	'for CP/M-80 based systems'
		db	'$'

msg14		db	cr,lf,lf
		db	'Insert Source Disk '
		db	'and type "Return" to continue'
		db	cr,lf,lf
		db	'$'

msg1		db	cr,lf,lf
		db	'Checking for blank tracks'
		db	cr,lf,lf
		db	'$'

msg2		db	cr,lf,lf
		db	'Enter starting serial number  '
		db	'$'

msg3		db	cr,lf,lf
		db	'Insert '
serial1		ds	ser$def$len
		db	' into destination drive'
		db	cr,lf
		db	' and type "Return" to continue'
		db	cr,lf,lf
		db	'$'

msg4		db	bell,cr,lf,lf
		db	'Remove '
serial2		ds	ser$def$len
		db	' from destination drive'
		db	cr,lf,lf
		db	'$'

msg6		db	bell,cr,lf,lf
		db	'Enter number of serial fields on'
		db	' source disk  '
		db	'$'

msg7		db	'Reading Track '
		db	'$'

msg8		db	'Writing Track '
		db	'$'

msg9		ds	0
ascii$trk	db	'    '
		db	' '
		db	'$'

msg10		db	bs,bs,bs,bs,bs,bs,bs,bs,bs,bs
		db	bs,bs,bs,bs,bs,bs
msg11		db	bs,bs,bs,bs,bs
		db	'$'

msg12		db	cr,lf,lf,lf
		db	'Source Drive is (A,B,etc) ? '
		db	'$'

msg13		db	cr,lf,lf
		db	'Destination Drive is (A,B,etc) ? '
		db	'$'

msg16		db	cr,lf,lf
		db	'Would you like to change the skew factor'
		db	' (Y/N) ? '
		db	'$'

msg17		db	cr,lf,lf
		db	'Enter skew factor (1-9) ? '
		db	'$'

msg18		db	cr,lf,lf
		db	'Do you want to perform copy verification'
		db	' (Y/N) ? '
		db	'$'

msg19		db	cr,lf,lf
		db	'1 = every disk is verified'
		db	cr,lf
		db	'2 = every other disk is verified'
		db	cr,lf
		db	'3 = every 3rd disk is verified'
		db	cr,lf,'"',cr,lf,'"',cr,lf,'"',cr,lf
		db	'99 = every 99th disk is verified'
		db	cr,lf,lf
		db	'How often do you want the verification to'
		db	' occur (1-99) ? '
		db	'$'

msg20		db	'Verifying Track '
		db	'$'

msg21		db	cr,lf,lf
		db	'The current sector skew is '
		db	'$'


errormsg0	db	bell,cr,lf,lf
		db	'Source Diskette is blank'
		db	cr,lf,lf
		db	'$'

errormsg1	db	cr,lf,lf
		db	'Serial number must be '
errormsg2	ds	1
		db	' digits long. Try again.'
		db	cr,lf
		db	'$'

errormsg3	db	bell,cr,lf,lf
		db	'Did not find all of the required serial '
		db	'fields'
		db	cr,lf
		db	'$'


errormsg4	db	bell,cr,lf,lf,lf
		db	'****** A read error has occurred ******'
		db	cr,lf
		db	'$'

errormsg5	db	bell,cr,lf,lf,lf
		db	'****** A write error has occurred ******'
		db	cr,lf
		db	'$'

errormsg6	db	cr,lf,lf
		db	'Need one or two digits. Try again.'
		db	'$'

error$msg7	db	bell,cr,lf,lf
		db	'**** Verification failed ****'
		db	cr,lf
		db	'$'

abort$msg	db	cr,lf,lf,lf
		db	'*** Serialization terminated ***'
		db	cr,lf,lf
		db	'Replace system disk in Drive A:'
		db	cr,lf
		db	'then type "Return" to reboot'
		db	'$'

xlt$offset	ds	2		; address of sector translation
					;table
dpb$addr	ds	2		; address of dpb

; Disk Parameter Block
dpb		ds	0		; DPB will be moved here
spt		ds	2		; sectors/track
		ds	11		; not important
off		ds	1		; number of reserved tracks
		ds	1		; not important

		ds	64		; stack area
stack		ds	0


trk$buff	ds	0		; track buffer


	end
