Title 'SDF : Speedy Disk Formatter'

; **************************************************************************
; * THIS PROGRAM IS PUBLIC DOMAIN. You Are Free To Use It And Redistribute *
; * It, As Long As No Profit Is Made Out Of It. Redistribution Costs May   *
; * Not Exceed $5. This Notice May Not Be Removed Or Altered In Any Way.   *
; * Use At Your Own Risk. Author Will Not Be Held Responsible For Any Dama-*
; * ge That Might Result Of Use Or Misuse. Enjoy.			   *
; **************************************************************************

; This program is inspired from QDR by Vernon D. Buerg, which unfortunately
; has a bug in handling 3.5" floppies.

; Better late than never, I dedicate this program to Ward Christensen, who
; showed us the way in the good old CP/M days ...

; Jacques Pierson, Belgrade (Belgium), November 1987.
; CIS 76446,1516

;
M24	=	0	;set to one at work, for an Olivetti M24 (ATT 6300) 
;
bell	equ	7
bs	equ	8
lf	equ	0Ah
cr	equ	0Dh
escape	equ	1Bh
ulc	equ	0C9h	;graphic char - Upper Left Corner
llc	equ	0C8h	;Lower Left Corner
urc	equ	0BBh	;Upper Right Corner
lrc	equ	0BCh	;Lower Right Corner
vb	equ	0BAh	;Vertical Border
hb	equ	0CDh	;Horizontal Border
;
hsr	equ	0EFh	;Head Step Rate : 4 ms
hst	equ	0	;Head Settle Time : 0 ms
;


SDF	segment
	assume ds:SDF, ss:SDF ,cs:SDF ,es:SDF

	org	0100h		;make this a COM file


start:	jmp	GetCmdLine

; First describe our Disk Base Parms. We will make INT 1Eh to point here
; while formatting. Put at head of program so people without an assembler
; can easily patch this area w/ DEBUG.

OurDBP	equ	$

	db	hsr		;high nibble : heads step rate
				;low nibble, head unload time
	db	2		;DMA mode
	db	255		;clock ticks before stopping drive motor
	db	2		;sector size - 2 => 512 bytes
	db	9		;How many sectors per track
	db	42		;Gap length between sectors for R/W ops
	db	-1		;data length - Not used
	db	80		;Gap length when formatting
	db	0F6h		;Formatting char. Dos likes F6's
	db	hst 		;Head settle time in ms.
	db	1		;Motor speed up time in 1/4 sec.

Welcome	db	cr,lf,'Speedy Disk Formatter, V1.0, '
	db	'(c) Jacques Pierson, Nov 1987$'

UsageMessage	equ	$
	db	cr,lf,lf,'Usage:  SDF drv: [switches]',cr,lf
	db	'drv: mandatory and is either A: or B:,',cr,lf
	db	'No switch => format standard 360K diskette, no verify',cr,lf
	db	'/Q        => format Quad density (3',0abh,'" 720K disk)',cr,lf
	db	'/V        => force Verify after format',cr,lf
	db	'Multiple switches may be combined, e.g. SDF b:/q/v.'
	db	cr,lf,lf,'$'

DriveMissing	db	cr,lf,lf,'Invalid or missing Drive Spec !$'

WrongSwitch	db	cr,lf,lf,'Unknown /switch in command line !$'

InsertDisk	db	cr,lf,lf,'Insert diskette in drive '
Drive		db	'A, and press ENTER when ready ...$'

Again		db	cr,lf,lf,'Format complete,'
Clusters	db	'      K available to user.'
		db	cr,lf,'Press ENTER to format another diskette, '
		db	'or ESCape to quit...$'

Formatting	db	cr,lf,lf,'Formatting '
DiskType	db	'DSDD, Without$'
Verify		db	' Verify.',cr,lf,'$'

Track		db	cr,'Track: '
TrackNr		db	'   Side: '
SideNr		db	'   $'

ShowRetry	db	bs,'R$'

WritingBoot	db	' - writing BOOT$'
WritingFAT	db	', FATs$'
WritingDIR	db	', DIR.$'

ErrorTbl	db	1,'Invalid command                '
		db	2,'Address mark not found.        '
		db	4,'Requested sector not found.    '
		db	8,'DMA overrun on operation.      '
		db	10h,'Bad CRC on diskette read/write.'
		db	20h,'Controller failure.            '
		db	40h,'SEEK operation failed.         '
EndOfErrTbl	equ	$

FatalErr	db	bell,cr,lf,lf,'Door Open, Write-protected disk,'
		db	' or Drive Fails to respond.'
		db	cr,lf,'Please fix problem and hit Enter to continue,'
		db	'or ESCape to abort...$'

BadClusters	db	cr,lf,lf,'Bad sectors :'
BadClNr		db	'       Cluster(s) disabled.',cr,lf,lf,'$'
;
ErrBadSec	db	bs,bs,', Sec: '
ErrSec		db	' , Error '
ErrCode		db	'   hex : '
ErrMsg		db	'Unknown error.                 ',cr,lf,'$'
;
;
; Flags, set according to switches
;
OptionsFlag	db	0	;default is No Verify, Normal Disk
CtrlBreak	db	0	;Ctrl-Break flag on entry
;
; Disk characteristics, other parms are found in BootSector
;
DirSecs		dw	7	;DIRectory size in sectors
DskTrks		db	40	;Cyls/disk
CurTrk		db	-1	;Current track
CurDrv		db	0	;Current drive, binary, BIOS way (A:=0,..)

	even			;Align for those w/8086 processors

RetryCount	dw	0	;Count of retries
MaxTries	dw	2	;Maximum retries attempted
BadSecCnt	dw	0	;Count of Bad Sectors
NextSec		dw	0	;Temp storage for next sector #
INT1Eoff	dw	0	;INT 1Eh offset
INT1Eseg	dw	0	;INT 1Eh segment
INT23off	dw	0	;Ctrl-Break handler offset
INT23seg	dw	0	;Ctrl-Break segment
;
HexTbl		db	'0123456789abcdef'
;
ID_tbl	equ	$		;table of ID's for track 0, side 0
Side0		db	0,0,1,2
		db	0,0,2,2
		db	0,0,3,2
		db	0,0,4,2
		db	0,0,5,2
		db	0,0,6,2
		db	0,0,7,2
		db	0,0,8,2
		db	0,0,9,2		;end of side 0

Side1		db	0,1,1,2		;side 1 now
		db	0,1,2,2
		db	0,1,3,2
		db	0,1,4,2
		db	0,1,5,2
		db	0,1,6,2
		db	0,1,7,2
		db	0,1,8,2
		db	0,1,9,2		;end of track, side 1
;
;
; Anatomy of our Boot sector
;
BootSector	equ	$

		jmp	BootMsg
 
		db	'SDF  v01'	;OEM name, 8 chars
		dw	512		;Sector size
		db	2		;Cluster size in sectors
		dw	1		;Reserved sectors
		db	2		;Number of FATs
DirSize		dw	112		;Directory entries
TotSecs		dw	720		;Total sectors
MediaDes	db	0FDh		;Media descriptor
FatSize		dw	2		;FAT size in sectors
		dw	9		;Sectors per Track
		dw	2		;Number of heads
		dw	0		;Number of hidden sectors
		db	0		;filler
		db	0		;head
		db	10		;length	of BIOS	file
		db	hsr		;The DPB, remember ?
		db	2
		db	25h
		db	2
		db	9
		db	42
		db	-1
		db	80
		db	0F6h
		db	hst
		db	1

NoBootMsg	equ	$

	db	cr,lf,lf
	db	ulc,50 DUP (hb),urc,cr,lf
	db	vb,'   This diskette has been formatted by SDF and    ',vb,cr,lf
	db	vb,'    contains no system to boot from. Either       ',vb,cr,lf
	db	vb,'  remove it and hit a key to boot from hard disk  ',vb,cr,lf
	db	vb,'  if any, or replace it with another disk with a  ',vb,cr,lf
	db	vb,'   system to boot from and hit a key when ready.  ',vb,cr,lf
	db	llc,50 DUP (hb),lrc,cr,lf,lf,0


BootMsg:
	cli				;no interrupts
	cld				;forward direction
	mov	AX,07C0h		;Boot runtime address
	mov	DS,AX			;to DS
	mov	ES,AX
	mov	SS,AX
	mov	SP,0			;temp stack at top of segment 
	mov	SI,(OFFSET NoBootMsg - OFFSET BootSector) ;Msg ptr
BNextChar:
	lodsb				;Get byte fm msg
	or	AL,AL			;a null ?
	je	ReBoot			;yes, print msg done
	mov	AH,0Eh			;print char, no bells and whistles
	int	10h			;Bios output char INT
	jmp	SHORT BNextchar		;loop for all string
;
ReBoot:	xor	AH,AH			;wait for key hit
	int	16h

	int	19h			;and try booting again 


BEndOfStuff	equ	$

	db	(510 - (BEndOfStuff - BootSector)) DUP (0)	;filler

	db	55h,0AAh		;DOS disk signature
;
; End of boot sector
;
; 
; First sector of File Allocation Table
;
FatSec1	equ	$
	db	0FDh			;FAT - Media descriptor
	db	0FFh
	db	0FFh
	db	509 DUP (0)		;to get a full sector

FatSec2	db	512 DUP (0)		;FAT, second sector
	db	512 DUP (0)		;FAT, third sector
;
DirSec1	equ	$
DskLbl	db	'SDF--------'		;Dir entry for disk label, 11 char
	db	28h			;Attributes : Label + Archive
	db	10 DUP (0)
TimeLo	db	0
TimeHi	db	0
Date	dw	0
	db	486 DUP (0)		;full sector
;
DirSec2	db	512 DUP (0)		;2nd and other sectors
;
;
; Control-Break handler : DO NOT give user a chance to exit w/^C or Break
; without resetting the environement...
;
ControlBreak: 

	iret				;trap ^C and simply return
;
; Let's go to it now
;
GetCmdLine:

	mov	DX,offset Welcome	;print welcome msg
	call	PrintString
	mov	SI,80H			;Peek at input bfr char count
	lodsb				;get byte
	cbw				;make word
	or	AX,AX			;any option ?
	jne	GCL1			;yes
Usage:	mov	DX,offset UsageMessage
	call	PrintString		;Print and fall thru ErrorExit
;
; Error Exit, w/ return code 1, Normal Exit, w/ return code 0
;
ErrorExit:
	mov	AL,1
	jmp	SHORT	DoExit
;
Abort:	call	Restore			;Restore INT 1E, INT 35
;
Exit:	mov	AL,0			;normal exit, no error
DoExit:	mov	AH,4Ch			;Program Exit
	int	21h
;
;
; Carry on parsing command line
;
GCL1:	mov	CX,AX			;got something in cmd line, count to CX
	call	ParseSwitches		;Parse cmd line "/" switches
	mov	SI,81h			;Get drive spec
GCL2:	lodsb
	cmp	AL,' '			;skip spaces
	loopz	GCL2
	jcxz	Usage			;Nuts, give Usage Msg
	cmp	AL,cr
	jz	Usage
	and	AL,5fh			;Make drive letter uppercase
	mov	Drive,AL		;Put drive letter in msg
	sub	AL,'A'			;make binary, BIOS way
	mov	CurDrv,AL		;Store in current drive
	cmp	AL,1			;Drive MUST be 0 or 1
	jg	BadDrive
	lodsb
	cmp	AL,':'			;Make sure this WAS a drive spec
	jne	BadDrive		;no, it was not
	jmp	Setup			;Yes, indeed
BadDrive:
	mov	DX,OFFSET DriveMissing
	call	PrintString
	jmp	Usage
;
;
A$Ret:	ret
;
; Parse command line switches
;
ParseSwitches:
	mov	DI,81h			;peek at cmd line
PS1:	mov	AL,'/'
	repne	scasb			;search for "/", length in CX
	jne	A$Ret			
	jcxz	A$Ret			;none, or no more
	mov	BYTE PTR [DI-1],cr	;Got a "/", put a CR for later use
	cmp	BYTE PTR [DI-2],' '	;Previous char was space ?
	jne	PS2			;no
	mov	BYTE PTR [DI-2],cr	;yes, force to CR
PS2:	mov	SI,DI
	lodsb				;get switch
	and	AL,5Fh			;Force uppercase
PS3:	cmp	AL,'V'			;/Verify ?
	jne	PS4			;no
	mov	OptionsFlag,1		;yes, remember that
	mov	WORD PTR DskLbl+6,'V+'	;put in disk label
	mov	BYTE PTR DiskType+10,'$' ;truncate "Without" msg
	jmp	PS1			;parse next
PS4:	cmp	AL,'Q'			;/Quad density ?
	jne	PS5			;no, unknown switch
	mov	BYTE PTR DiskType+2,'Q'	;Flag, and Description
	mov	DskTrks,80		;80 tracks
	mov	FatSize,3		;FAT is 3 sectors long
	mov	TotSecs,1440		;1440 sectors
	mov	MediaDes,0F9h		;to Boot Sector
	mov	WORD PTR DskLbl+8,'Q+'	;put in disk label
	jmp	PS1			;parse next switch
PS5:	mov	DX,OFFSET WrongSwitch
	call	PrintString
	jmp	Usage	

;
; Restore : Restore the world as it was on entry
;
Restore: lds	DX,DWORD PTR INT1Eoff	;previous INT 1Eh vector
	mov	AX,251Eh		;restore it
	int	21h

	mov	AH,0			;Reset Disk System
	int	13h

	lds	DX,DWORD PTR INT23off	;Original DOS CtrlBreak handler
	mov	AX,2523h		;restore it
	int	21h

	mov	AX,3301h		;set Ctrl-Break flag
	mov	DL,CtrlBreak		;restore as was on entry
	int	21h
	ret
;
;
; Setup : Set up the world
;
Setup:	mov	AH,AL			;colon to AH
	mov	AL,CurDrv
	add	AL,'A'
	mov	WORD PTR DskLbl+4,AX	;put DriveSpec in disk label

	mov	AX,3300h		;Get CtrlBreak flag
	int	21h
	mov	CtrlBreak,DL		;save flag
	mov	AX,3301h		;set flag
	xor	DL,DL			;set checking OFF
	int	21h

	push	DS
	xor	AX,AX			;point to low memory
	mov	DS,AX
	push	BX			;Crazy MASM V.2 wants this..
	mov	BX,(23h *4)		;..whilst LDS SI,(23h*4) is perfectly
	lds	SI,DWORD PTR [BX]	;..legitimate to get INT 23h vector
	pop	BX
	mov	CS:INT23off,SI		;store offset
	mov	AX,DS
	mov	CS:INT23seg,AX		;store segment
	pop	DS
	mov	DX,OFFSET ControlBreak	;our handler
	mov	AX,2523h		;Set Interrupt Vector 23h
	int	21h

	push	DS
	xor	AX,AX			;point to low memory
	mov	DS,AX
	push	BX			;MASM V.2 striking again !!
	mov	BX,(1Eh * 4)
	lds	SI,DWORD PTR [BX]	;get INT 1E vector
	pop	BX
	mov	CS:INT1Eoff,SI		;store offset
	mov	AX,DS
	mov	CS:INT1Eseg,AX		;store segment
	pop	DS
	mov	DX,OFFSET OurDBP	;our DBP
	mov	AX,251Eh		;Set Interrupt Vector 1Eh
	int	21h
	mov	AH,0			;Reset Disk System
	int	13h			;our DBP is now in effect
					;fall thru format loop
;
Do_It:	mov	DX,OFFSET InsertDisk	;say "Insert diskette in drive.."
	call	PrintString
;
GetChar:mov	AH,0			;get char function
	int	16h
	cmp	AL,escape
	jne	GC0
	jmp	Abort
GC0:	cmp	AL,cr
	jne	GetChar			;Escape or CR only !
	mov	DX,OFFSET Formatting	;"Formatting..."
	call	PrintString
	mov	DX,OFFSET Verify
	call	PrintString
	mov	BadSecCnt,0		;no bad sector so far
	call	PrepareFat
;
; Format all disk now
;
	call	DoFormat		;Here we go
	mov	AX,BadSecCnt		;any bad sector ?
	or	AX,AX
	je	NoBadSec		;no
	mov	SI,OFFSET BadClNr	;Pointer to decimal places
	call	AX2dec			;convert to decimal
	mov	DX,OFFSET BadClusters	;Bad Clusters msg
	call	PrintString	
;
; Write Boot, FAT, DIR
;
NoBadSec:call	WriteBFD		;write Boot, Fat, Dir
;
; Show available space
;
	mov	AH,0Dh			;reset disk system
	int	21h
	mov	AH,36h			;get free space
	mov	DL,CurDrv		;for our drive
	inc	DL			;DOS way
	int	21h
	mov	AX,BX			;clusters to AX
	mov	SI,OFFSET Clusters
	call	AX2dec			;convert and fall thru print
;
; Done - prompt for another disk to format
;
	mov	DX,OFFSET Again		;"Hit enter to format another..."
	call	PrintString
	call	Whistle			;Wake him up
	jmp	GetChar			;get char


;
; Prepare first FAT sector - This stuff required for multiple formatting
;
PrepareFat:
	sub	AX,AX			;clear FAT to all zeros
	mov	DX,FatSize		;How many sectors ?
	mov	DI,OFFSET FatSec1
PF1:	mov	CX,512/2		;sector size, in words
	repz	stosw			;for speed
	dec	DX			;next FAT
	jnz	PF1
	mov	AL,MediaDes		;get Media Descriptor from Boot Sec
	mov	BYTE PTR FatSec1,AL		;move in place
	mov	WORD PTR FatSec1+1,0FFFFh
	ret

;
; Here to REALLY format a disk
;
DoFormat:
	mov	CurTrk,-1		;Initialize track nr
NextTrk:				;Prepare table for Track/sect. ID's
	inc	CurTrk			;Next track
	mov	CH,CurTrk		;to CH
	cmp	CH,DskTrks		;All done ?
	jb	NT1			;not yet
	ret				;yes, return
NT1:	mov	DI,OFFSET ID_tbl	;Point to table
	mov	AL,18			;18 sectors per Cyl
NT2:	mov	[DI],CH			;move Track # in table
	add	DI,4			;point to next entry
	dec	AL			;all done ?
	jnz	NT2			;No, loop
	mov	AH,1			;Give user a chance to abort
	int	16h			;Keyboard hit ?
	jz	NT3			;no
	cmp	AL,escape		;yes, Escape to abort ?
	jne	NT3
	jmp	Abort
;
NT3:	mov	AX,0509h		;Format Track, 9 sectors/trk
	mov	BX,OFFSET Side0		;track id table address, side 0
	mov	DL,CurDrv		;current drive
	mov	DH,0			;Side 0
	mov	CL,1			;First sec is #1
	call	PrintProgress		;Print progress (Track#, Side #)
	int	13h			;Format track, side 0
	jnc	NT3V			;No Carry, no error
	call	Retry			;Carry set, Shall we retry ?
	jnc	NT3			;yes, if we come here w/Carry clear
					;else, give up
NT3V:	test	OptionsFlag,1		;Should we verify ?
	jz	NT4			;No, next side
;
NT3R:	mov	AX,0409h		;Verify, 9 sectors per track
	int	13h
	jnc	NT4			;No carry, no error
	call	Retry			;Carry set, retry ?
	jnc	NT3R			;Yes, if Carry clear, else give up
NT4:	mov	DH,1			;Side 1
	mov	AX,0509h		;Format Track, 9 sectors/trk
	mov	BX,OFFSET Side1		;track id table address, side 1
	mov	DL,CurDrv		;current drive
	mov	CL,1			;First sec is #1
	call	PrintProgress		;Print progress (Track#, Side #)
	int	13h			;Format track, side 1
	jnc	NT4V			;no error
	call	Retry			;Carry set, error
	jnc	NT4			;Retry if Carry clear, else give up
NT4V:	test	OptionsFlag,1		;Should we verify ?
	jz	NextTrk			;no, next track
;
NT4R:	mov	AX,0409h		;Verify, 9 sectors per track
	int	13h
	jc	NT5			;error !
	jmp	NextTrk			;no error, next track
NT5:	call	Retry			;Carry set, retry ?
	jnc	NT4R			;yes
	jmp	NextTrk			;no, next track
;
Retry:	cmp	AH,3			;Door open/Write protected ?
	je	R1			;yes
	cmp	AH,80h			;Attachment failed to respond ?
	jne	R2			;no, other error
R1:	mov	DX,OFFSET FatalErr	;tell him he'd better close the door
	call	PrintString		;..or remove the write-protect tab
	pop	AX			;clean up stack, we won't return
	pop	AX			;two calls to clean up
	jmp	GetChar			;OK, we may restart now
R2:	push	DX
	mov	DX,OFFSET ShowRetry	;Show 'R' to indicate a retry
	call	PrintString
	pop	DX
	inc	RetryCount
	mov	DI,MaxTries		;limit exceeded ?
	cmp	RetryCount,DI
	ja	SecIsBad		;Yes, this one is really bad
	mov	AH,0			;No, Reset Disk System
	int	13h
	clc				;clear carry
	ret				;and return to try again
;
; If we come here, we really have to deal with a bad sector
;
SecIsBad: call	ShowBadSec
	inc	BadSecCnt		;count it
	call	MarkBad			;Mark whole cluster
	mov	RetryCount,0		;Reset retry count
	stc				;Set Carry to indicate "give up"
	ret				;go process next side or track
;
;
; Mark a bad sector (cluster) in FAT
;
MarkBad:push	AX
	push	BX
	push	CX
	push	DX
	push	DI
	push	SI
	push	DS			;We have to fetch..
	mov	AX,40h			;.. the faulty sector
	mov	DS,AX			;.. in the BIOS area
	mov	BX,47h
	mov	CL,BYTE PTR [BX]	;Get sector number
	pop	DS
	mov	AL,CH			;Track # to AL; AH is already 0
	xor	CH,CH
	cmp	DH,0			;Side 0 ?
	je	MB1
	add	CL,9			;Side 1, add 9 sects for side 0
MB1:	shl	AX,1			;* 2 heads
	mov	BX,9			;* 9 sectors per track
	mul	BX			;compute absolute sector #
	add	AX,CX
	sub	AX,FatSize		;minus 1st FAT size
	sub	AX,FatSize		;minus 2nd FAT size
	sub	AX,DirSecs		;minus Directory size
	mov	BL,2			;disk heads
	div	BX
	add	AX,1
	mov	CX,3
	mul	CX
	shr	AX,1			; / 2
	mov	DI,AX
	lea	SI,[DI+FatSec1]		;pointer into FAT
	mov	DI,SI
	lodsw				;get cluster entry
	mov	DX,0FF70h		;12 bits, left aligned
	jb	MB2			;if even
	mov	DX,0FF7h		;if odd
MB2:	or	AX,DX			;merge w/ word to mark bad
	stosw				;store back in FAT
	pop	SI
	pop	DI
	pop	DX
	pop	CX
	pop	BX
	pop	AX
	ret
;
; Write Boot sector, then FATs, then DIR.
;
WriteBFD:
	mov	DX,OFFSET WritingBoot
	call	PrintString
	mov	AL,CurDrv
	lea	BX,BootSector		;boot sector
	mov	CX,1			;1 sector to write
	mov	DX,0			;absolute sector 0
	call	AbsSecWrite
;
; Write FAT(s)
;
	push	DX			;Preserve Sector Nr
	mov	DX,OFFSET WritingFAT	;"FATs"
	call	PrintString
	pop	DX
	lea	BX,FatSec1		;point to FAT 1st sector
	mov	CX,1			;1 sector write
	inc	DX			;DX knows which
	mov	AL,CurDrv		;current drive
	call	AbsSecWrite
	mov	CX,FatSize		;sectors/FAT
	dec	CX			;One written already
	lea	BX,FatSec2		;get FAT 2nd sector
	mov	AL,CurDrv		;current drive
	inc	DX			;next sector(s)
	call	AbsSecWrite
	add	DX,CX			;count sectors written this time
;
; Write second FAT
;
	mov	AL,CurDrv		;current drive
	mov	BX,OFFSET FatSec1	;FAT
	mov	CX,1			;1 sector, DX knows which
	call	AbsSecWrite
	mov	CX,FatSize		;How many remaining ?
	dec	CX			;One already written
	mov	BX,OFFSET FatSec2	;point to FAT 2nd sector
	mov	AL,CurDrv		;current drive
	inc	DX			;next FAT sector(s)
	call	AbsSecWrite
	add	DX,CX
	mov	NextSec,DX		;remember next sector to write
;
; Write Directory
;
	mov	DX,OFFSET WritingDIR	;"DIRectory"
	call	PrintString
;
; Update Disk Label Date and Time
;
	mov	AH,2Ch			;Get Time
	int	21h
	mov	BX,CX			;Minutes, Seconds
	mov	AX,CX
	mov	CL,3
	shl	CH,CL			;Shift to move hour in place
	shr	BL,CL			;Shift to keep high 3 bits of min
	add	CH,BL			;merge
	mov	TimeHi,CH		;move in place
	and	AL,7			;keep min. lower 3 bits
	shl	AL,1			;shift
	mov	CL,5
	shr	DX,CL			;Shift seconds
	add	DH,AL			;merge
	mov	TimeLo,DH		;move in place
	mov	AH,2Ah			;Get Date
	int	21h
	sub	CX,1980			;From 1980 on
	shl	CL,1
	mov	BX,DX
	mov	AH,CL
	mov	CL,3
	shr	DH,CL
	add	AH,DH
	mov	CL,5
	shl	BH,CL
	add	BH,DL
	mov	AL,BH
	mov	Date,AX			;Move date in place
	mov	AL,CurDrv		;current drive
	mov	BX,OFFSET DirSec1	;First sector of DIR
	mov	DX,NextSec		;current sector
	mov	CX,1			;1 sector to write
	call	AbsSecWrite
	mov	AL,CurDrv
	mov	BX,OFFSET DirSec2
	mov	CX,DirSecs		;How many sectors
	dec	CX			;One already written
WD1:	push	CX
	mov	CX,1			;1 sector write
	inc	DX			;next one
	call	AbsSecWrite
	pop	CX			;how many remaining ?
	loop	WD1
	ret				;Done, next disk please.
;
; Print progress : Track nn Side n - inline decimal conversion
;
PrintProgress:
	push	AX
	push	DX
	mov	AL,DH			;Side #
	add	AL,'0'			;Brute force ascify
	mov	SideNr,al		;Put side # in msg
	mov	AL,CH			;get track #
	aam				;THE trick
	xchg	AL,AH			;swap
	add	AX,'00'			;Ascify
	mov	WORD PTR TrackNr,AX	;Put track # in msg
	mov	DX,OFFSET Track		;say "Track..., Side..."
	call	PrintString
	pop	DX
	pop	AX
	ret
;
; Here to show that we have a bad sector
;
ShowBadSec:
	push	AX
	push	BX
	push	CX
	push	DX
	push	DI
	push	SI
	mov	DX,AX			;Save AX for further hex printing
	sub	BX,BX
	mov	BL,AH			;remember error code
	mov	DI,OFFSET ErrorTbl	;Table for INT 13 error codes & msgs
	mov	AL,AH			;Error code to AL
	MOV	CX,OFFSET EndOfErrTbl
	sub	CX,DI			;compute table length
	repnz	scasb			;search for error code
	jnz	SBS1			;Not found, "Unknown Error"
	mov	SI,DI			;Got it, Ptr to SI
	mov	CX,31			;err msg length
	mov	DI,OFFSET ErrMsg	;Point to destination
	repz	movsb			;Move Err Msg in place
SBS1:	mov	AL,DH			;Get AH : error code
	call	AL2hex			;Convert
	mov	WORD PTR ErrCode,AX	;move AH (hex) in msg
	push	DS
	mov	AX,40h			;Point to BIOS data area
	mov	DS,AX
	mov	BX,47h			;the right place in BIOS area
	mov	AL,BYTE PTR [BX]	;BIOS knows which sector causes problem
	pop	DS
	add	AL,'0'			;ascify
	mov	ErrSec,AL		;move sec # in msg
	mov	DX,OFFSET ErrBadSec	;give error msg
	call	PrintString
	pop	SI
	pop	DI
	pop	DX
	pop	CX
	pop	BX
	pop	AX
	ret
;
;
; AL2hex : binary AL to 2 hex digits in AX conversion
;
AL2hex:	push	BX
	push	CX
	push	DX
	push	SI
	mov	BL,AL			;will use BX as offset to tbl
	mov	CL,4			;bits to shift
	sub	BH,BH
	shr	BL,CL
	and	AL,0Fh			;mask off bits
	sub	AH,AH
	mov	SI,OFFSET HexTbl	;point to hex tbl
	add	SI,BX
	mov	DL,[BX+HexTbl]
	mov	BX,AX
	mov	DH,[BX+HexTbl]
	mov	AX,DX
	pop	SI
	pop	DX
	pop	CX
	pop	BX
	ret
;
;
; AX2dec : routine to convert AX to decimal string at DS:SI
;
AX2dec:	push	AX
	push	CX
	push	SI
	xor	CL,CL			;clear 10000 counter
s10000:	inc	CL
	sub	AX,10000
	jnc	s10000
	add	AX,10000		;one SUB too much
	add	CL,'0'-1		;adjust and ascify
	mov	[SI],CL			;store in string
	inc	SI
	xor	CL,CL			;clear 1000 counter
s1000:	inc	CL
	sub	AX,1000
	jnc	s1000
	add	AX,1000			;one SUB too much
	add	CL,'0'-1		;adjust and ascify
	mov	[SI],CL			;store in string
	inc	SI
	xor	CL,CL			;clear 100 counter
s100:	inc	CL
	sub	AX,100
	jnc	s100
	add	AX,100			;one SUB too much
	add	CL,'0'-1		;adjust and ascify
	mov	[SI],CL			;store in string
	inc	SI
	aam				;Convert value <100
	xchg	AL,AH			;swap
	add	AX,'00'			;Ascify
	mov	[SI],AX
	pop	SI			;restore string ptr
	mov	CX,5			; 5 bytes to search
AX2D1:	cmp	BYTE PTR [SI],'0'	;leading Zero ?
	jne	AX2D2			;no or no more
	mov	BYTE PTR [SI],' '	;yes, make leading space
	inc	SI
	loop	AX2D1			;loop for all
AX2D2:	pop	CX
	pop	AX
	ret
;
;
; PrintString : print string pointed by DX, terminated by '$'
;
PrintString:

	push	ax
	mov	ah,9
	int	21h
	pop	ax
	ret
;
; Absolute sector Write
;
AbsSecWrite:

	push	AX
	push	BX
	push	CX
	push	DX
	int	26h			;absolute sector write
	jnb	ASW1			;no carry, no error
	xchg	CX,DX
	mov	DH,DL
	call	ShowBadSec
ASW1:	popf
	pop	DX
	pop	CX
	pop	BX
	pop	AX
	ret
;
; Absolute Sector Read 
;
AbsSecRead:

	push	AX
	push	BX
	push	CX
	push	DX
	int	25h		;absolute sector read
	jnb	ASR1		;no carry, no error
	xchg	CX,DX
	mov	DH,DL
	call	ShowBadSec
ASR1:	popf
	pop	DX
	pop	CX
	pop	BX
	pop	AX
	ret
;
; Whistle - Wolf's whistle
;
i8253	equ	40h		;Timer chip base address
i8255	equ	60h		;PPI chip base address
;
Whistle:
	push	AX
	mov	AL,0B6h		;Counter 2, 2 bytes count, mode 3
	out	(i8253+3),AL	;Write Mode Word
	mov	AX,1000		;Divisor
	push	AX
	out	(i8253+2),AL	;load lo byte
	xchg	AH,AL
	out	(i8253+2),AL	;load hi byte
	in	AL,(i8255+1)	;8255, port B
	or	AL,3		;turn on bottom two bits, spkr on
	out	(i8255+1),AL
	pop	AX
W1:	call	SwLoop		;soft timing loop
	dec	AX
	out	(i8253+2),AL	;load lo byte
	xchg	AH,AL
	out	(i8253+2),AL	;load hi byte
	xchg	AH,AL
	jne	W1		;loop
	in	AL,(i8255+1)	;8255, port B
	and	AL,NOT 3	;turn off bottom two bits, spkr off
	out	(i8255+1),AL
	pop	AX
	ret
;
; Soft loop for Whistle
;
SwLoop:	push	CX
	mov	CX,100
SWL:	loop	SWL
	pop	CX
	ret
;
	SDF	ends
;
end	start
