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
