; Use   "gencmd serial data[m100]" when producing CMD file

; print control constants

cr		equ	00Dh	; carriage return
lf		equ	00Ah	; line feed
bell		equ	007h	; self-explanatory
bs		equ	008h	; backspace
vt		equ	00Bh	; vertical tab

; ccpm call function codes

ccpm		equ	224	; ccpm interrupt

c_read		equ	 1	; request single character from console
c_write		equ	 2	; send single character to console
c_writebuf	equ	 9	; print string terminated by '$'
drv_dpb		equ	31	; get DBP offset and segment base
drv_resetall	equ	13	; reset disk system
drv_set		equ	14	; select disk drive reg DL=disk #
				;  (0=A,1=B,etc.)
mc_max		equ	53	; find available memory space

; bdos function codes for direct BIOS calls

s_bios    	equ	50	; direct BIOS call function code
read_code  	equ	13	; read a 128 byte sector
write_code 	equ	14	; write a 128 byte sector

; program constants

blank		equ	0E5h	; data which an empty (blank)
				;  sector will contain
false		equ	-1
true		equ	 0

	cseg

start:
; init serial_number
	mov	cx,length ser_def
	mov	al,'0'
	mov	bx,offset serial_number
initlop10:
	mov	byte ptr[bx],al
	inc	bx
	loop	initlop10

; give sign on
	call	clear			; clear the monitor screen
	mov     dx,offset msg0 		; print sign on message
	call    pmsg

; prompt operator for source and destination drives
	mov	dx,offset msg12		; send source message to
	call	pmsg			;  console
	call	get_char		; get disk code
	sub	al,'A'			; correct it
	mov	dskS,al			; store code for source disk
	mov	dx,offset msg13		; send destination message
	call	pmsg			;  to console
	call	get_char		; get disk code
	sub	al,'A'			; correct it
	mov	dskD,al			; store code for dest disk

; prompt operator for source disk
	mov	dx,offset msg14		; send message
	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.
	mov	cl,drv_resetall		; reset disks
	int	ccpm

; 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.
	mov	al,dskS			; select source disk
	mov	disk,al
	mov	cl,s_bios
	mov	dx,offset sel_dsk_tab
	int	ccpm

; when the "select disk" function is performed using a direct BIOS call
;  the offset of the DPH (in system's extra segment) is returned in the
;  BX register.  the first word of the DPH is the offset of the sector
;  translation table for the system.
	mov	xlt_offset,bx		; save offset of system's
					;  sector translation table

; move DPB into program data area
	cld
	mov     cl,drv_dpb       	; get system extra segment
	int	ccpm			;  value and offset of DPB
	push	es			; save ES value
	mov	si,bx			; init source pointer
	mov     es,data_base
	mov     di,offset DPB
	pop	ds			; get system extra seg into DS
	mov     cx,15			; init byte counter
	rep  movsb
	push	ds			; save system extra seg value
	push	es			; restore DS value
	pop	ds

; get system sector translation table into data segment of program
	pop	es			; recover extra seg value
	mov	bx,xlt_offset		; get offset of trans table
	mov	si,es:word ptr[bx]	;  into SI	
	test	si,si			; if SI = 0 then system doesn't
	jnz	has_table		;  have a translation table
	mov	bx,offset xlt_tab	;  which means that I'll have 
	mov	cx,spt			;  to init "xlt_tab" myself
	mov	al,0
initlop0:
	mov	byte ptr[bx],al
	inc	al
	inc	bx
	loop	initlop0
	mov	al,1			; set skew factor = 1
	jmps	init_addr_tab
has_table:
	push	es			; save extra seg again
	mov	bx,xlt_offset		; get offset of trans table
	mov	di,offset xlt_tab	; DI = offset of program sector
					;  translation table
	mov	cx,spt			; use sectors/track as
					;  counter value
	mov	es,data_base		; init ES value
	pop	ds			; init DS
	rep  movsb
	push	es			; restore old DS value
	pop	ds

; make sure that serial numbers that cross sector boundries will be
;  found
init_addr_tab:
	sub	ax,ax			; init address offset
	mov	si,offset xlt_tab	; SI -> xlt-tab
	mov	cx,spt			; init counter
	sub	dh,dh			; DH = 0
initlop9:
	mov	bx,offset addr_tab
	mov	dl,byte ptr[si]		; get sector number
	inc	si			; move pointer
	sub	dl,xlt_tab		; subtract 1st sector number
	add	dl,dl			; multiply by 2
	add	bx,dx			; BX -> correct table entry
	mov	word ptr[bx],ax		; put address into table
	add	ax,128			; AX = AX+bytes/sectorz
	loop	initlop9

; 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.
	mov	bx,offset xlt_tab	; BX -> xlt_tab
xltlop0:
	mov	al,byte ptr[bx]		; AL = table entry
	inc	bx			; BX -> next byte in table
	inc	al
	cmp	al,byte ptr[bx]		; see if the two numbers are in
	je	xltlop0			;  the right order
	sub	bx,offset xlt_tab	; if they aren't then calculate
					;  how long the sequence was
	mov	spb,bx			;  and save the value
; calculate skew of system sector translation table
	mov	al,xlt_tab[bx]		; get byte at xlt_tab+spb
	sub	al,xlt_tab		;  and subtract byte at xlt_tab
	mov	bx,spb			;  now divide by sectors/block
	sub	ah,ah			; prepare of division
	div	bl
	mov	skew,al
; tell operator about skew
skew_prompt:
	push	ax			; save system skew
	call	clear			; clear screen
	mov	dx,offset msg21		; send message
	call	pmsg
	pop	dx			; recover skew
	or	dl,030h			; make it ASCII
	mov	cl,c_write
	int	ccpm
; ask operator if he/she would like to change the sector skew
	mov	dx,offset msg16
	call	pmsg
	call	get_char		; get Y/N response
	cmp	al,'N'			; check for No
	je	dont_change
; ask for new skew factor
	mov	dx,offset msg17
	call	pmsg
	call	get_1num
	mov	skew,al
; make new sector translation table using skew factor supplied by
;  operator
	mov	si,offset xlt_tab	; BX is pointer into xlt_tab
	mov	bl,byte ptr[si]		; init beginning sector number
	mov	bh,bl
	mov	dl,sptbyte		; calculate sectors/track +
	add	dl,bl			;  first sector number
	mov	cl,skew			; init loop counter
	sub	ch,ch
xltlop1:
	push	cx			; save loop counter
xltlop2:
	mov	cx,spb			; init sectors/block counter
	push	bx			; save sector # registers
xltlop3:
	mov	byte ptr[si],bh		; put sector number into table
	inc	si			; increment pointer
	inc	bh			; increment sector number
	loop	xltlop3
	mov	ax,spb			; calculate new starting sector
	mov	cl,skew
	mul	cl
	pop	bx			; recover sector # registers
	add	bh,al			; add skew*spt to starting
					;  sector number
	cmp	bh,dl			; compare new starting sector #
					;  to first sector # + spt
	jb	xltlop2
	add	bl,spbbyte		; add sectors/block to 1st
					;  starting sector number
	mov	bh,bl			; init starting sector
					;  number of the next sector
					;  block
	pop	cx			; recover loop counter
	loop	xltlop1
dont_change:
; ok, now calculate many how bytes there are on a track.
;  bytes/track = sectors/track * logical sector size
	mov     ax,spt	    
	mov     bx,128
	mul     bx
	mov     bpt,ax   		; save value

	mov     cl,mc_max	    	; find 64k or less of free
	mov     dx,offset mcb		;  memory
	int     ccpm

; check the disk for blank tracks starting at last track. blank
;  tracks won't be copied
	mov	dx,offset msg1		; tell operator about search
	call	pmsg
	cld			       	; set direction flag forward
	mov	dx,offset msg7		; send 'Reading' message
	call	pmsg
	mov     cx,last_trk    		; initialize track counter
scan_loop:
	push    cx	     		; save track counter
	mov     track,cl       		; get current track
	mov	al,cl			; make track number ascii
	call	make_ascii
	mov	dx,offset ascii_trk	; print it
	call	pmsg
	mov     dma_addr,0   		; initialize dma offset
	call    seek	   		; seek to track
	call    read	   		; read a track 
	mov	al,blank		; init AL with the character
					;  which is found in blank
					;  (empty) sectors
	mov	es,m_base		; init segment reg
	mov     di,0	   		; offset is set to zero
	mov     cx,bpt			; how many bytes to scan
	repe  scasb	    		; scan until ZF=1 or CX=0
	jne     data_found     		; if ZF=1 then have found data
	call	erase_trk		; erase old track message
	pop     cx	     		; otherwise scan another track
	loop    scan_loop      		;  if CX<>0

; the diskette is blank if program flow reaches this point
	mov	dx,offset error_msg0
	call	pmsg			; inform operator that the
	call	abort			;  source disk is blank and
					;  abort operation

data_found:
	pop     cx	     		; if data is found then save
	mov     last_trk,cx    		;  the number of the non-blank
					;  track
; 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 needed.
	mov	ax,m_length		; calculate number of bytes
	mov	bx,16			;  available to be used for
	mul	bx			;  a buffer area
	mov	bx,bpt
	div	bx
	mov	cx,last_trk		; CX will contain number of
	sub	cx,off			;  tracks to be copied
	cmp     ax,cx      		; if block1 track count<lst_trk
	jl      over0			;  then get block2 track count 
	mov     B1_trk_cnt,cx		;  last_trk, block_cnt=
	mov     block_cnt,1 		;  1 and jump to new_disk  
	jmps    over07
over0:
	dec	ax
	mov     B1_trk_cnt,ax
	sub	dx,dx  
	mov	bx,ax			; bx holds divisor
	mov     ax,cx			;  hold dividend in division
					;  operation
	div	bx	 		; do division
	mov	block_cnt,ax		; move result to memory
	mov	B2_trk_cnt,dx		; remainder = block2 track
					;  count
over07:
; prompt operator for number of serial fields on source disk
	call	get_cnt
	cmp	al,0			; if count = 0 then don't ask
	jz	over09			;  for a 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			; clear monitor screen
	mov	dx,offset msg18		; ask question
	call	pmsg
	mov	ver_freq,0		; init verification frequency
					;  value
	call	get_char		; get yes/no answer
	cmp	al,'N'			; check for no
	je	over01			; if no then dont verify
; ask operator for frequency of verification
	mov	dx,offset msg19		; send message
	call	pmsg
	call	get_2num		; get response from operator
	mov	ver_freq,al		; save value
over01:

; clear monitor screen
	call	clear

	mov	dsk_cnt,0		; init diskette counter

; this portion of code does the copying and serializing
new_disk:
	cld    				; move current serial number
	mov     es,data_base		;  into insert serial message
	mov     cx,length ser_def
	mov     si,offset serial_number
	mov     di,offset serial_1
	rep  movsb

	mov	dx,offset msg3		; prompt operator for
	call    pmsg_wait		;  destination diskette

; home both disk drives
	call	selectD
	call	home
	call	selectS
	call	home

	mov     ax,off			; don't copy reserved tracks
	mov     trackS,al
	mov     trackD,al

	mov     ax,B1_trk_cnt		; init track counter
	mov     track_cnt,ax

	mov     tog0,0	    		; decides whether to use
		       			;  B1_trk_cnt or B2_trk_cnt

	mov     serial_cnt,0		; used to check if all the
					;  serial fields were found

	mov	dx,offset msg7		; send 'Reading' message
	call	pmsg
	call	seekS			; read in a track so that
	mov	dma_addr,0		;  there is always an extra
	call	read			;  one in memory
	call	erase_msg		; erase "Reading" message

	mov	cx,block_cnt		; init block counter

create_dsk:
	push    cx	     		; save block counter
	mov	cx,bpt	  		; intialize dma offset
	mov	dma_addr,cx
	call	selectS			; select source disk
	mov	dx,offset msg7		; send 'Reading' message
	call	pmsg
	mov	cx,track_cnt	 	; init track counter
lop0:
	push    cx	  		; save track counter
	call    seekS      		; seek track on source drive
	call    read			; read a track
	call	erase_trk		; erase old track message
	pop     cx	  		; recover track counter
	loop    lop0
	call	erase_msg		; erase "Reading" message

	call    serialize  		; insert current serial number
					;  into all serial fields in
					;  this block

	mov     dma_addr,0   		; same as above but write to
					;  destination drive
	call	selectD			; select destination drive
	mov	dx,offset msg8		; send "Writing" message to
	call	pmsg			;  console
	mov     cx,track_cnt
lop1:
	push    cx
	call    seekD
	call    write
	call	erase_trk		; erase old track message
	pop     cx
	loop    lop1
	call	erase_msg		; erase "Writing" message

; move extra track (read and searched but not written) from end of
;  buffer to the beginning of the buffer
	cld		 		; clear direction flag
	mov	cx,bpt			; init counter
	mov	ax,cx			; calculate starting address 
	mov	bx,B1_trk_cnt		;  of data to be moved
	mul	bx
	mov	si,ax
	mov	di,0			; where block is to be moved
	mov	ax,m_base		; init segment registers
	push	ds			; save old values
	push	es
	mov	ds,ax			; move in new values
	mov	es,ax
	rep  movsb			; do the move
	pop	es			; recover values of segment
	pop	ds			;  registers

	pop	cx			; recover block counter
	loop    create_dsk	 	; do again if not last block
	cmp	tog0,1			; check if we have reached this
					;  point before
	je	done			; if yes then we are done
	mov	tog0,1			; no, so toggle tog0
	mov	ax,B2_trk_cnt		; if B2_trk_cnt=0
	cmp	ax,0			;  then we are done
	je	 done
	mov	track_cnt,ax		; else do above again but with
	mov	cx,1			;  B2_trk_cnt = track count
	jmp	create_dsk
done:
	mov	dx,offset msg8		; send "Writing" message
	call	pmsg			;  to console
	call	seekD			; write last track to dest
	call	write			;  drive

	mov	al,count_num		; check if all serial fields
	cmp	al,serial_cnt		;  were found
	je	serial_ok		; if not then there is an error
	mov	dx,offset error_msg3	; inform operator of error
	call	pmsg
	call	abort			; abort operation
serial_ok:

	call	erase_msg		; erase "Writing" message

	inc	dsk_cnt			; increment diskette counter

	mov	ax,dsk_cntword		; get number of diskettes done
	mov	cx,ver_freqword		; get verification frequency
	jcxz	dont_verify		; don't verify if CX = 0
	div	cl			; see if it's time to verify
	test	ah,ah			; if DX = 0 then it's time
	jnz	dont_verify
; do the verification
	mov	dx,offset msg20		; send "Verifying" message
	call	pmsg
	mov	cx,last_trk		; calculate number of tracks to
	mov	ax,off			;  compare
	sub	cx,ax
	inc	cx			; correct result
	mov	trackS,al
	mov	trackD,al
verlop0:
	push	cx			; save track counter
	call	selectD			; select dest disk
	call	seekD			; seek to track on dest disk
	mov	dma_addr,0		; init offset of 1st buffer
	call	read			; read a track from source disk
	call	erase_trk		; erase old track message
	call	selectS			; select source drive
	call	seekS			; seek to current track
	mov	cx,bpt			; init 2nd buffer offset
	push	cx			; temp save
	mov	dma_addr,cx
	call	read			; read a track from dest disk
	call	erase_trk		; erase old track message
	pop	cx			; recover track data length
					;  into byte counter
	sub	si,si			; init 1st pointer
	mov	di,cx			; init 2nd pointer
verlop1:
	push	ds			; save value of data seg reg
	mov	ax,m_base		; get extra seg reg value
	mov	es,ax			; init ES
	mov	ds,ax			; init DS
	repe  cmpsb			; verify
	pop	ds			; recover data seg reg value
	je	verlop2			; if ZF = 0 then verification
					;  failed.  inform operator
	dec	di			; correct 2nd buffer pointer
	push	si			; save pointer to 1st buffer
	push	cx			; save byte pointer
	mov	si,offset ser_def	; SI -> to ser_def
	mov	cx,length ser_def	; init counter
	repe  cmpsb			; do compare
	jne	verlop3
	pop	cx			; recover byte counter
	sub	cx,length ser_def-1	; correct it
	pop	si			; recover 1st buffer pointer
	add	si,length ser_def-1	; correct it
	jmps	verlop1
; verification failed so inform operator to try it again
verlop3:
	call	clear
	mov	dx,offset error_msg7
	call	pmsg
	jmp	new_disk
verlop2:
	pop	cx			; recover track counter
	loop	verlop0

dont_verify:
	call	clear			; clear monitor screen
	cld			 	; insert current serial number
	mov	cx,length ser_def	;  into remove message
	mov	es,data_base
	mov	si,offset serial_number
	mov	di,offset serial_2
	rep  movsb

	mov	dx,offset msg4		; prompt operator to remove
	call	pmsg			;  copy

	call    inc_serial	  	; increment current serial
					;  number

	jmp	new_disk		; do another disk


; read a track from the selected disk
read:
	mov	io_tab,read_code
	jmps    track_io

; write a track to the selected disk drive
write:
	mov	io_tab,write_code
	jmps    track_io

; do track input or output on selected disk drive
track_io:
	mov	cx,4			; initialize io retry counter
io_retry:
	push    cx		  	; save retry counter
	call    track_setup	 	; init dma base address
	mov	sector,0		; start with sector #0
	mov	cx,spt			; init sector counter
io_lop:
	push    cx		  	; save sector counter
	call    sectran			; perform sector translation
	mov	dx,offset set_sector	; set sector thru ccpm
	mov	cl,s_bios
	int	ccpm

	mov	dx,offset set_dma_offset ; set dma offset thru ccpm
	mov	cl,s_bios
	int	ccpm

	mov	cl,s_bios	  	; set read/write func thru ccpm
	mov	dx,offset io_tab
	int	ccpm
	test    al,al		; io error if not zero
	jnz	io_error		; tell operator about io error
	inc	sector			; next sector to read
	pop	cx		  	; recover sector counter
	loop    io_lop

	pop	cx		  	; clean up stack
	mov	ax,bpt			; init dma_addr for next
	add	dma_addr,ax		;  track
	ret	
io_error:
	pop	cx		  	; clean up stack
	call	home			; home disk
	pop	cx		  	; recover retry counter
	loop    io_retry
	ret
 
track_setup:
	mov	ax,m_base		; initialize dma base address
	mov	dma_base,ax
	mov	cl,s_bios
	mov	dx,offset set_dma_base
	int	ccpm
	ret

; do logical to physical sector translation
sectran:
	sub	ah,ah
	mov	al,sector
	mov	bx,offset xlt_tab
	xlat	xlt_tab
	mov	tsector,al
	sub	ah,ah
	sub	al,xlt_tab
	add	al,al
	mov	bx,offset addr_tab
	add	bx,ax
	mov	ax,word ptr[bx]
	add	ax,dma_addr
	mov	dma_offset,ax
	ret

; home the selected drive
home:
	mov	track,0
	jmps	seek

; seek to the selected track on the source drive
seekS:
	mov	al,trackS
	inc	trackS
	jmps	display_trk

; seek to the selected track on the destination drive
seekD:
	mov	al,trackD
	inc	trackD
display_trk:
	mov	track,al
	call	make_ascii			; make track # ascii
	mov	dx,offset ascii_trk		; send track number to
	call	pmsg				;  console

; seek to the selected track on the selected drive
;  "track" = desired track #
seek:
	mov	cl,s_bios
	mov	dx,offset seek_trk_tab
	int	ccpm
	ret

; select source disk
selectS:
	mov	dl,dskS
	jmps	select

; select destination disk
selectD:
	mov	dl,dskD

; select a disk drive for future operations
select:
	mov	cl,drv_set
	int	ccpm
	ret

; get starting serial number from operator
get_serial:
	mov	dx,offset msg2			; send prompt message
	call	pmsg				;  to console

	mov	dx,offset con_buf0	; point to console input buffer
	mov	cl,10			; CP/M read console buffer
	int	ccpm
	cmp	buf0_len,length ser_def ; check for correct number
						;  of digits
	jne	bad_serial
	ret

bad_serial:
	mov	error_msg2,length ser_def
	or	error_msg2,030h			; make it ASCII
	mov	dx,offset error_msg1
	call	pmsg
	jmps	get_serial

; 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:
	mov	dx,offset msg6			; send prompt message
	call	pmsg				;  to console
	call	get_2num			; get a number (0-99)
						; from operator
	mov	count_num,al			; save number
	ret

; input two ASCII numbers into buffer 1 and translate them into a 
;  binary number which is returned in the AL register
get_2num:
	mov	dx,offset con_buf1		; get address of buffer
	mov	cl,10
	int	ccpm
	cmp	buf1_len,2			; can't have more than
	jbe	input_ok0			;  two digits 0-99
input_err0:
	mov	dx,offset error_msg6		; tell operator about
	call	pmsg				;  error and try again
	jmp	get_2num
input_ok0:
	cmp	buf1_len,0			; need at least one
	je	input_err0			;  digit. tell operator
						;  if there isn't
	mov	bx,offset input_num		; bx -> input buffer
	mov	cl,buf1_len			; (cl) = digit count
	sub	ch,ch				; zero upper half of cx
	add	bx,cx				; make bx point to
	dec	bx				;  units digit of input

	mov	dh,byte ptr[bx]			; get units digit
	and	dh,00Fh				; mask off upper nybble
	loop	tens				; if (cx) <> 2 then get
						;  tens digit
	jmp	got_it				; only had one digit
tens:
	dec	bx				; make bx -> to tens
						;  digit
	mov	dl,byte ptr[bx]			; get tens digit
	and	dl,00Fh				; mask off upper nybble
	mov	al,10				; load multiplier
	mul	dl				; do multiply
	add	dh,al				; add tens to units
got_it:
	mov	al,dh				; save number
	ret

	
serialize:	; locate all occurrances of serial numbers in buffer

	cld			; set direction forward
	mov	es,m_base	; point ES: to beginning of buffer
	mov	di,0
	mov	ax,bpt
	mov	bx,track_cnt
	inc	bx		; remember that an extra track was read
	mul	bx
	mov	cx,ax
next_serial:
	mov	si,offset ser_def	; point at pattern
	lodsb			; get first char of pattern into AL
	repne scasb		; look for next occurance of AL
	jne	no_match	; if not match, then we are done

	push	di		; save scan pointer
	push	cx		; save count
	mov	cx,(length ser_def)-1	; residual length of pattern
	repe cmpsb
	pop	cx		; recover scan count
	pop	di		; recover scan pointer
	jne	next_serial	; no, rest didn't match

	push    cx		; save scan pointer
	dec	di		; move di back to start of field
	mov	si,offset serial_number ; si points to current serial
					;  number
	mov	cx,length ser_def ; counter for number of bytes to move
	rep  movsb
	pop	cx		; recover scan pointer
	inc	serial_cnt
	jmp	next_serial

no_match:
	ret

; increment the ASCII serial number

inc_serial:
	mov	cx,length ser_def	; length of serial number
	mov	bx,offset serial_number ; bx points to start of string
	add	bx,(length ser_def)-1 ; bx now points to end of string
addlop:
	push    cx		  	; save length of pointer
	mov	al,byte ptr[bx]		; get byte of serial number
	inc	al		  	; add 1 to it
	daa			 	; decimal adjust it
	lahf				; check to see if there was a
	and	ah,10h		;  carry from the lower half of
	mov	cl,4			;  the al register
	shl	ah,cl
	jc	fix		 	; if yes then increment the next
					;  byte of the serial number
	mov	byte ptr[bx],al		; else save byte and stop
	pop	cx		  	; clean up stack
	ret
fix:
	and	al,0fh			; make high nybble of byte
	or	al,30h			;  equal to "3" again
	mov	byte ptr[bx],al		; save byte back in serial
					;  number
	dec	bx		  	; point at next higher byte
	pop	cx		  	; recover length of serial
					;  number
	loop    addlop			; do again if CX<>0
	ret

; translate the 8 bit digit in AL to ASCII into "ascii_trk"
make_ascii:
	mov	dx,'  '			; fill "ascii_trk" with blanks
	mov	ascii_trk,dx
	mov	ascii_trk+2,dx
	mov	bx,offset ascii_trk+3	; BX -> "ascii_trk"+3
	mov	dl,10			; init divisor
makelop1:
	sub	ah,ah			; make AH zero	
	div	dl			; and divide by 10
	add	ah,'0'			; make remainder ascii
	mov	byte ptr[bx],ah		; store it in "ascii_trk"
	dec	bx			; move pointer to next location
	test	al,al			; see if quotient zero yet
	jnz	makelop1		; no, got more digits
	ret

; print the message at [DX] and wait for RETURN key
pmsg_wait:
	call	pmsg			; print the message
wait_1:
	mov	cl,c_read		; CP/M Console Input
	int	ccpm
	cmp	al,'C'-'@'		; see if control-C
	je	abort			; terminate program
	cmp	al,cr			; see if ASCII carriage return
	jne	wait_1			; no, try again
	ret				; back to caller

; abort the program
abort:
	mov	dx,offset abort_msg
	call	pmsg_wait
	mov	cl,0			; CP/M reboot function
	int	ccpm

; print the string whose offset is in dx and is terminated by '$'
pmsg:
	mov	cl,c_writebuf
	int	ccpm
	ret

; clears the monitor screen
clear:
	cmp	clear_on,false		; see if clear screen enable is
	je	no_clear		;  off
	mov	dx,offset clear_msg
	call	pmsg
no_clear:
	ret

; get ASCII input from operator and translate to upper case
get_char:
	mov	cl,c_read		; request single character
	int	ccpm			;  from console
	cmp	al,'C'-'@'			; check for control-C
	je	abort			; abort operation if yes
	and	al,05Fh			; xlate lower to upper chase
; echo choice back to operator
echo_choice:
	push	ax			; temp save
	mov	dl,bs			; write over last input
	mov	cl,c_write
	int	ccpm
	pop	dx			; get temp save
	push	dx
	mov	cl,c_write		; write it out
	int	ccpm
	pop	ax
	ret

; get a single ASCII number from operator and check for .
get_1num:
	mov	cl,c_read
	int	ccpm
	cmp	al,'C'-'@'			; check for control-C
	je	abort
	and	al,00Fh
	ret

; this code erases the "Reading" and "Writing" messages from the
;  monitor screen
erase_msg:
	mov	dx,offset msg10
	call	pmsg
	ret

; this code erases the old track numbers from the monitor screen
erase_trk:
	mov	dx,offset msg11
	call	pmsg
	ret

	dseg
	org	0000h

	; Page Zero Definitions

code_length	rw	1	; low 16 bits code segment length
code_length_h	rb	1	; high 8 bits code segment length
code_base	rw	1	; base paragraph of code segment
model_8080	rb	1	; 8080 model byte
data_length	rw	1	; low 16 bits data segment length
data_length_h	rb	1	; high 8 bits data segment length
data_base	rw	1	; base paragraph of data segment
		rs	1	; << reserved >>
extra_length	rw	1	; low 16 bits extra segment length
extra_length_h	rb	1	; high 8 bits extra segment length
extra_base	rw	1	; base paragraph of extra segment


	org	0100h

seek_trk_tab    db	10	; seek track function table
track	   rb	4

sel_dsk_tab	db	9	; select disk drive
disk		rb	4

set_dma_base    db	17	; set dma base address function table
dma_base	rw	2

set_dma_offset  db	12	; set dma offset address function table
dma_offset	rw	2

set_sector	db	11	; set sector function table
tsector		rb	4	; holds translated sector #
sector		rb	1	; holds untranslated sector #

io_tab		rb	1	; select read/write function table
		rw	2

bpt    rw	1		; how many bytes per track

mcb		rw	0		; memory control block
m_base	  rw	1		; base address (segment address)
m_length	dw	0FFFh 	; desired length of memory area
m_ext	   rw	1

ver_freqword	rw	0
ver_freq	db	0,0	; holds verification frequency value
dsk_cntword	rw	0
dsk_cnt		db	0,0	; number of copies made so far

B1_trk_cnt    rw	1
B2_trk_cnt    rw	1

block_cnt	rw	1

track_cnt	rw	1

tog0	    rb	1

serial_cnt    rb	1

count_num	rs	1	; holds number of serial fields
				;  to expect on source disk
dskS		db	0
dskD		db	1

skew		rb	1	; reserve storage for skew factor
spb		rw	0	; sectors/block
spbbyte		rb	2

DPB		rb	0	; Disk Parameter Block
spt		rw	0	; sector per track
sptbyte		rb	2
bsh		rb	1	; block shift factor
blm		rb	1	; block mask
exm		rb	1	; extent mask
dsm		rw	1	; total storage capacity of drive
drm		rw	1	; number of directory entries
al0		rb	1
al1		rb	1
cks		rw	1
off		rw	1	; number of reserved tracks

xlt_offset	rw	1	; offset of system translate table

xlt_tab		rb	128	; will contain sector translation table
addr_tab	rw	128	; will contain sector address offsets
				;  from beginning of track address in
				;  disk data buffer
dma_addr	rw	1	; will hold dma offset by track

trackS		rb	1
trackD		rb	1

clear_on	db	true

last_trk	dw	76

ser_def		db	'654321'

msg0		db	cr,lf,lf
		db	'-------------------------'
		db	'-------------------------'
		db	'-'
		db	cr,lf
		db	'SERIAL16          V1.0  Serial No. '
		db	'XXXX-0000-999999'
		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-86 based systems'
		db	'$'

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

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

msg7		db	'Reading Track  '
		db	'$'

msg8		db	'Writing Track  '
		db	'$'

msg9		rs	0
ascii_trk	rw	2
		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	'$'

error_msg1	db	cr,lf,lf
		db	'Serial number must be '
error_msg2	rb	1
		db	' digits long. Try again !!!  '
		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	'$'

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

msg3		db	cr,lf,lf
		db	'Insert '
serial_1	rb	length ser_def
		db	' into destination drive'
		db	cr,lf
		db	'then type "Return" to continue'
		db	cr,lf,lf
		db	'$'

msg4		db	bell,cr,lf,lf
		db	'Remove '
serial_2	rb	length ser_def
		db	' from destination drive'
		db	cr,lf,lf
		db	'$'

abort_msg	db	cr,lf,cr,lf
		db	cr,lf,'*** Serialization terminated ***'
		db	cr,lf
		db	cr,lf,'Replace disk in Drive A:'
		db	cr,lf,'	 then type ENTER to reboot ','$'

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,lf
		db	'1 = every disk is verified'
		db	cr,lf
		db	'2 = every 2nd 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	'$'

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

error_msg6	db	cr,lf,lf
		db	'Wrong. Need one or two digits'
		db	'$'

error_msg7	db	bell,cr,lf,lf
		db	'**** Verification failed ****'
		db	cr,lf,lf
		db	'Try it again.'
		db	cr,lf
		db	'$'

error_msg0	db	bell,cr,lf,lf
		db	' The Source Disk is BLANK '
		db	cr,lf
		db	'$'

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

clear_msg	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,lf,lf,lf,lf,lf
		db	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	'$'

con_buf0	db	10			; buffer used to read
buf0_len	rb	1			;  serial number
serial_number	rb	length ser_def
		rb	10-1-length ser_def

con_buf1	db	5		; another console input buffer
buf1_len	rb	1
input_num	rb	5


		db	0		; force out end of data segment
