;;
 ; @(#) fddrv.asm - Bad-zero-track floppy disk driver (TSR).
 ; (c) 1997 Ivan Maidanski <ivmai@chat.ru> http://ivmai.chat.ru
 ; Freeware program source. All rights reserved.
 ;;
 ; Language: ASM8086 (for IBM PC/AT, MS-DOS v3.0+, COM file)
 ; Tested with: Turbo Assembler v3.1, Turbo Link v5.1
 ; Last modified: 1997-06-28 15:45:00 GMT+04:00
 ;;
NAME Bad_Zero_Track_Floppy_Disk_Driver

 ; tasm fddrv.asm
 ; tlink /t fddrv.obj

 ; Comment: This driver enables You to use floppy disks with bad zero track.

.MODEL SMALL

.CODE
.8086

Null LABEL NEAR

 ; Program constants
DriverID= 0C184h
FDDriveCnt= 4h
RepeatCnt= 8h ; amount of cylinders to try

 ; PSP segment
 ORG 2Ch
PSP@EnvSeg DW ? ; environment segment
 ORG 80h
PSP@CmdLine DB 80h DUP(?) ; program command line

 ; RAM-resident part variables
 ORG 6Ch
Int13hHandler@Old DD ?
TrackMapTable DW FDDriveCnt DUP(?)

 ; RAM-resident part code shifted beginning
TSRBegin= $

 ; Program start point
 ORG 100h
Start:
 cmp sp,OFFSET StackTop
 jae Start@CRef
 sub bp,bp ; abort program on no memory
 push es
 push bp
 retf
Start@CRef:
 jmp Main
 EVEN

 ; RAM-resident part code
ASSUME ds:NOTHING,es:NOTHING
TSRShift= $-TSRBegin

Int13hHandler PROC FAR
 ; Comment: BIOS disk i/o intercept handler
 ; Expects: ah - function number (2h,3h,4h,5h,27h), al - sector count,
 ; ch - cylinder number, cl - sector number, dl - drive, dh - head number,
 ; es:[bx] - buffer, bx - DriverID (if ah=27h)
 ; Returns: CF - error flag, ah - error code,
 ; al - number of sectors proceeded,
 ; bx=0 and es - TSR segment (if ah=27h and bx - DriverID)
 ; Uses: - (ax,CF)/(ax,bx,es)
 pushf
 cmp ah,1Ah ; service function ?
 jae Int13hHandler@Service
 cmp dl,FDDriveCnt ; check FDD number range
 jb Int13hHandler@CheckFunc
Int13hHandler@FarJump:
 popf
 jmp cs:[Int13hHandler@Old]
 EVEN
Int13hHandler@Service:
 cmp ah,27h ; Installation check ?
 jne Int13hHandler@FarJump
 cmp bx,DriverID
 jne Int13hHandler@FarJump
 popf
 mov ax,100h
 sub bx,bx
 push cs ; return TSR segment
 pop es
 push di
 jmp Int13hHandler@StoreErrRet
 EVEN
Int13hHandler@CheckFunc:
 cmp ah,2h ; check for ReadSectors
 jb Int13hHandler@FarJump
 cmp ah,6h ; check for FormatTrack
 jae Int13hHandler@FarJump
 popf
 sti ; enable interrupts
 push di
 mov di,sp ; clear error flag
 and byte ptr ss:[di+6h],0FEh
 cmp ah,5h ; format zero track ?
 jne Int13hHandler@GetMapParam
 test cx,0FFC0h
 jne Int13hHandler@GetMapParam
 cmp dh,0
 je Int13hHandler@QueryParams
Int13hHandler@GetMapParam:
 mov di,dx ; get current track mapping parameters
 and di,0FFh
 shl di,1h
 add di,OFFSET TrackMapTable
 mov di,cs:[di]
 inc di ; no mapping yet ?
 jz Int13hHandler@AnalyzeDisk
 and di,0FFC0h
 push cx ; adjust values
 and cx,3Fh
 or di,cx
 pop cx
 test cx,0FFC0h ; zero cylinder ?
 xchg cx,di
 jz Int13hHandler@AccessDisk
 cmp cx,di ; work cylinder ?
 mov cx,di
 jne Int13hHandler@AccessDisk
 and cx,3Fh
 EVEN
Int13hHandler@AccessDisk:
 cmp ah,5h ; prepare track layout for format operation ?
 jne Int13hHandler@AccessDiskCall
 push ax
 push di
 push cx
 mov di,bx
 mov cl,al
 mov ah,dh
 mov al,ch
 mov ch,0
 cld
 EVEN
Int13hHandler@AccessFillLoop:
 stosw
 inc di
 inc di
 loop Int13hHandler@AccessFillLoop
 pop cx
 pop di
 pop ax
 EVEN
Int13hHandler@AccessDiskCall:
 pushf
 call cs:[Int13hHandler@Old]
 cmp ah,0
 mov cx,di
 jne Int13hHandler@ErrRetCref
 jmp Int13hHandler@Ret
 EVEN
Int13hHandler@ErrRetCref:
 jmp Int13hHandler@ErrRet
 EVEN
Int13hHandler@AnalyzeDisk:
 cmp cx,1h ; read boot sector ?
 jne Int13hHandler@CannotAccess
 cmp dh,0
 jne Int13hHandler@CannotAccess
 cmp ah,2h
 jne Int13hHandler@CannotAccess
 cmp al,0 ; nothing to do ?
 jne Int13hHandler@QueryParams
 mov ax,100h ; set "bad request" error flag
 jmp Int13hHandler@StoreErrRet
 EVEN
Int13hHandler@CannotAccess:
 mov ax,600h ; cannot access if disk changed
 jmp Int13hHandler@StoreErrRet
 EVEN
Int13hHandler@QueryParams:
 push ax
 push dx ; query drive parameters
 push es
 push bx
 mov ah,8h
 pushf
 call cs:[Int13hHandler@Old]
 pop bx
 pop es
 pop ax
 jnc Int13hHandler@SkipDefault
 mov cx,4F12h ; 18 sectors and 80 cylinders on DS disk by default
 mov dh,1h
Int13hHandler@SkipDefault:
 mov dl,al
 mov ax,cx ; compute cylinder step
 rol al,1h
 rol al,1h
 xchg al,ah
 and ah,3h
 inc ax
 mov ch,RepeatCnt
 div ch
 xchg al,cl
 xchg ch,cl
 mov di,cx
 mov cl,dh
 mov ch,0
 mov dh,0
 EVEN
Int13hHandler@TestTrack:
 pop ax
 cmp ah,5h ; format operation ?
 push ax
 jne Int13hHandler@ReadBoot
 push di ; prepare current track layout
 push cx
 mov di,bx
 mov cl,al
 mov ah,dh
 mov al,ch
 mov ch,0
 cld
 EVEN
Int13hHandler@FillLoop:
 stosw
 inc di
 inc di
 loop Int13hHandler@FillLoop
 pop cx
 pop di
 pop ax
 push ax
 pushf ; format track
 call cs:[Int13hHandler@Old]
 cmp ah,6h ; disk change line active ?
 je Int13hHandler@FailCrefDup
 pop ax
 push ax
 dec ah ; verify track
 EVEN
Int13hHandler@ReadBoot:
 push cx
 mov cl,1h
 pushf
 call cs:[Int13hHandler@Old]
 cmp ah,0
 pop cx
 je Int13hHandler@AccessOK
 cmp ah,3h ; write protected disk ?
 je Int13hHandler@FailCrefDup
 cmp ah,6h ; disk change line active ?
 je Int13hHandler@FailCrefDup
 cmp ah,9h ; DMA overrun ?
 je Int13hHandler@FailCrefDup
 sub ax,ax ; reset controller
 pushf
 call cs:[Int13hHandler@Old]
 pop ax
 cmp ah,5h ; format operation ?
 push ax
 jne Int13hHandler@ReadAgain
 pushf ; format track again
 call cs:[Int13hHandler@Old]
 cmp ah,6h ; disk change line active ?
Int13hHandler@FailCrefDup: ; (ZF=1)
 je Int13hHandler@FailCref
 sub ax,ax ; reset controller
 pushf
 call cs:[Int13hHandler@Old]
 pop ax
 push ax
 dec ah ; verify track again
 EVEN
Int13hHandler@ReadAgain:
 push cx
 mov cl,1h
 pushf
 call cs:[Int13hHandler@Old]
 cmp ah,0
 pop cx
 jne Int13hHandler@CheckError
 EVEN
Int13hHandler@AccessOK:
 pop ax
 cmp ah,5h ; go to next head ?
 push ax
 mov ah,0
 jne Int13hHandler@CheckBoot
 cmp dh,cl ; last head ?
 jae Int13hHandler@StoreMapParam
 inc dh
 jmp Int13hHandler@TestTrack
 EVEN
Int13hHandler@CheckBoot:
 cmp byte ptr es:[bx],0EBh ; check boot sector signature
 jne Int13hHandler@NextCylinder
 cmp byte ptr es:[bx+15h],0F0h
 jb Int13hHandler@NextCylinder
 test byte ptr es:[bx+0Bh],7Fh
 jnz Int13hHandler@NextCylinder
 EVEN
Int13hHandler@StoreMapParam:
 add sp,2h
 mov di,dx ; store track mapping parameters
 push cx
 and di,3Fh
 and cl,0C0h
 shl di,1h
 add di,OFFSET TrackMapTable
 mov cs:[di],cx
 pop cx
 jmp short Int13hHandler@Ret
 EVEN
Int13hHandler@CheckError:
 cmp ah,6h ; disk change line active ?
Int13hHandler@FailCref: ; (ZF=1)
 je Int13hHandler@Fail
 cmp ah,20h ; permament error ?
 jae Int13hHandler@Fail
 cmp ah,8h ; DMA failure ?
 je Int13hHandler@Fail
 cmp ah,0Bh ; bad cylinder detected ?
 je Int13hHandler@NextCylinder
 test ah,1h ; permament error ?
 jnz Int13hHandler@Fail
 EVEN
Int13hHandler@NextCylinder:
 dec di
 test di,03Fh ; continue ?
 jz Int13hHandler@BadDisk
 mov ax,di ; compute next cylinder number
 add ch,ah
 jnc Int13hHandler@TestTrackCref
 add cl,40h
Int13hHandler@TestTrackCref:
 jmp Int13hHandler@TestTrack
 EVEN
Int13hHandler@BadDisk:
 mov di,dx ; set "no mapping" flag
 shl di,1h
 add di,OFFSET TrackMapTable
 mov word ptr cs:[di],0
 EVEN
Int13hHandler@Fail:
 add sp,2h ; restore old values
 mov cx,1h
 EVEN
Int13hHandler@ErrRet:
 cmp ah,6h ; disk change line active ?
 jne Int13hHandler@StoreErrRet
 mov di,dx ; reset zero track mapping parameters
 and di,0FFh
 shl di,1h
 add di,OFFSET TrackMapTable
 mov cs:[di],0FFFFh ; set "drive has not been accessed" flag
 EVEN
Int13hHandler@StoreErrRet:
 mov di,sp ; store error flag
 or byte ptr ss:[di+6h],1h
 EVEN
Int13hHandler@Ret:
 pop di
 iret
Int13hHandler ENDP

TSRLength= ($-Null)-TSRShift

 ; Non-resident part code
ASSUME ds:@CODE,es:NOTHING

Main PROC NEAR
 ; Comment: -
 ; Expects: ds=es=cs
 ; Returns: - (not applicable)
 ; Uses: - (all)
 mov sp,OFFSET StackTop
 mov ah,4Ah ; shrink memory block
 mov bx,(ProgramLength+0Fh)/10h ; size of program in paragraphs
 int 21h
 call TestDOSVer
 call DisableBreak
 mov ah,9h ; print introdation
 mov dx,OFFSET Main@MsgName
 int 21h
 mov si,OFFSET Main@Options ; get all program options
 call AnalyzeCMDLine
 test al,0Ch ; show help information ?
 jz Main@NoHelp
 mov ah,9h ; write help page to STDOUT
 mov dx,OFFSET Main@HelpPage
 int 21h
 jmp short Main@Exit
Main@NoHelp:
 push ax ; preserve options presence byte
 call CheckPresence
 pop ax
 jnc Main@Already
 test al,1h ; -U ?
 jz Main@Inst
 mov ah,9h ; print error message
 mov dx,OFFSET Main@MsgNot
 int 21h
 mov ax,4C01h
 int 21h
Main@Inst:
 test al,2h ; -I ?
 mov dx,OFFSET Main@MsgInfo
 jz Main@Print
 call FreeEnvSeg
 call SetUpTSR
 mov ah,9h ; print OK message
 mov dx,OFFSET Main@MsgOK
 int 21h
 mov ax,3100h ; terminate but stay resident
 mov dx,(TSRLength+0Fh)/10h
 int 21h
Main@Already:
 test al,1h ; -U ?
 mov dx,OFFSET Main@MsgStatus
 jz Main@Print
 call ReleaseTSR
 mov ah,49h ; free RAM-resident part memory
 int 21h
 mov dx,OFFSET Main@MsgUn
Main@Print:
 mov ah,9h ; print info
 int 21h
Main@Exit:
 mov ax,4C00h
 int 21h
Main@Options DB '?HIU',0
Main ENDP

TestDOSVer PROC NEAR
 ; Comment: Tests for MS-DOS 3.0 or later
 ; Expects: ds=cs=PSP segment
 ; Returns: - (may not be applicable)
 ; Uses: ax,bx,cx
 mov ax,3000h ; get DOS version (al=0)
 int 21h
 cmp al,3h
 jae TestDOSVer@Ret
 mov ah,9h
 mov dx,OFFSET TestDOSVer@Msg
 int 21h
 int 20h ; CS must be the same as the PSP
TestDOSVer@Ret:
 ret
TestDOSVer ENDP

DisableBreak PROC NEAR
 ; Comment: disable aborting program by 23h or 24h interrupts
 ; Expects: ds=cs
 ; Returns: -
 ; Uses: ax,dx
 mov ax,2523h ; set up empty int 23h handler ; Ignore CTRL+C (BREAK)
 mov dx,OFFSET DisableBreak@CtrlC
 int 21h
 mov ax,2524h ; set up new int 24h handler ; Fail operation
 mov dx,OFFSET DisableBreak@CritErr
 int 21h
 ret
DisableBreak@CritErr:
 mov al,3h ; - fail
DisableBreak@CtrlC:
 iret
DisableBreak ENDP

FreeEnvSeg PROC NEAR
 ; Comment: Free program environment segment
 ; Expects: -
 ; Returns: -
 ; Uses: ax,bx,es
 mov ah,51h ; get PSP segment
 int 21h
 mov es,bx
 sub ax,ax
 xchg ax,es:[PSP@EnvSeg]
 test ax,ax ; check for presence of PSP
 jz FreeEnvSeg@Ret
 mov es,ax
 mov ah,49h
 int 21h
FreeEnvSeg@Ret:
 ret
FreeEnvSeg ENDP

AnalyzeCMDLine PROC NEAR
 ; Comment: get options presence word
 ; Expects: ds:[si] - options letters string (in upper case), es - PSP seg
 ; Returns: ax - options presence word, es:[di] - last option argument
 ; Uses: ax,bx,si,di
 cld
 sub bx,bx ; initialize flags
 lodsb
AnalyzeCMDLine@Loop:
 mov ah,al
 call GetOption
 rcl bx,1h ; store presence flag
 lodsb
 test al,al ; end of string?
 jnz AnalyzeCMDLine@Loop
 mov ax,bx
 ret
AnalyzeCMDLine ENDP

GetOption PROC NEAR
 ; Comment: get option (/X or -X, case insensitive)
 ; Expects: ah - letter for which to scan (in upper case), es - PSP seg
 ; Returns: CF - "found" flag, es:[di] - found option argument
 ; Uses: al,di
 mov di,OFFSET PSP@CmdLine
 mov al,es:[di] ; get command line length
 test al,al ; clear "found" flag (CF=0)
 jz GetOption@Ret ; if none, return
 inc di
GetOption@Loop:
 mov al,es:[di]
 inc di
 cmp al,' ' ; whitespace or end of command line?
 je GetOption@Loop
 cmc ; clear "found" flag if (al<' ')
 jnc GetOption@Ret
 cmp al,'/'
 je GetOption@Analyze
 cmp al,'-'
 jne GetOption@Skip
GetOption@Analyze:
 mov al,es:[di]
 inc di
 cmp al,' ' ; whitespace or end of command line?
 je GetOption@Loop
 cmc ; clear "found" flag if (al<' ')
 jnc GetOption@Ret
 cmp al,'a' ; check for lower case
 jb GetOption@Upper
 cmp al,'z'
 ja GetOption@Upper
 sub al,'a'-'A' ; convert lower to upper case symbol
GetOption@Upper:
 cmp al,ah
 stc ; set "found" flag
 je GetOption@Ret
GetOption@Skip:
 mov al,es:[di]
 inc di
 cmp al,' ' ; whitespace or end of command line?
 je GetOption@Loop
 cmc ; clear "found" flag if (al<' ')
 jc GetOption@Skip
GetOption@Ret:
 ret
GetOption ENDP

CheckPresence PROC NEAR
 ; Comment: Check whether Driver already installed
 ; Expects: -
 ; Returns: CF=1 - Driver not installed, es - TSR segment (if CF=0)
 ; Uses: ax,bx,es
 mov ah,27h ; installation check
 mov bx,DriverID
 int 13h
 dec ah
 jnz CheckPresence@Error
 test bx,bx ; ID accepted ?
 jz CheckPresence@Ret
CheckPresence@Error:
 stc
CheckPresence@Ret:
 ret
CheckPresence ENDP

SetUpTSR PROC NEAR
 ; Comment: initialize variables and set up new interrupt handlers
 ; Expects: ds=cs
 ; Returns: - (may not be applicable)
 ; Uses: ax,bx,cx,si,di,es
 cld ; reset TrackMapTable
 push ds
 pop es
 mov di,OFFSET TrackMapTable
 mov cx,FDDriveCnt
 mov ax,0FFFFh
 rep stosw
 mov si,OFFSET TSRBegin+TSRShift ; move resident part code
 mov di,OFFSET TSRBegin
 mov cx,(TSRLength-(TSRBegin-Null)+1h)/2h
 rep movsw
 mov ax,3513h ; store old int 13h handler addr
 int 21h
 mov word ptr [Int13hHandler@Old],bx
 mov word ptr [Int13hHandler@Old+2h],es
 mov ax,2513h ; set up new int 13h handler
 mov dx,OFFSET Int13hHandler-TSRShift
 int 21h
 ret
SetUpTSR ENDP

ReleaseTSR PROC NEAR
 ; Comment: check and restore old interrupt handlers
 ; Expects: ds=cs, es - TSR segment
 ; Returns: - (may not be applicable)
 ; Uses: ax,bx,dx
 mov dx,es
 mov ax,3513h ; check current int 13h handler
 int 21h
 mov ax,es
 cmp ax,dx
 jne ReleaseTSR@Err
 cmp bx,OFFSET Int13hHandler-TSRShift
 je ReleaseTSR@Restore
ReleaseTSR@Err:
 mov ah,9h ; print error message
 mov dx,OFFSET ReleaseTSR@Msg
 int 21h
 mov ax,4C02h ; abort program
 int 21h
ReleaseTSR@Restore:
 mov es,dx
 push ds
 mov ax,2513h ; restore old int 13h handler
 lds dx,es:[Int13hHandler@Old]
 int 21h
 pop ds
 ret
ReleaseTSR ENDP

 ; Program messages
Main@MsgName DB 'Bad-zero-track Floppy Disk Driver v1.4',0Dh,0Ah,'$'
Main@MsgInfo DB 'For help, use option "/?".',0Dh,0Ah,'$'
Main@MsgOK DB 'Driver installed successfully.',0Dh,0Ah,'$'
Main@MsgUn DB 'Driver removed from memory successfully.',0Dh,0Ah,'$'
Main@MsgStatus DB 'Driver installed.',0Dh,0Ah,'$'
ReleaseTSR@Msg DB 'Driver cannot be uninstalled now.',0Dh,0Ah,'$'
Main@MsgNot DB 'Error: Driver not installed.',0Dh,0Ah,'$'
TestDOSVer@Msg DB 'Requires MS-DOS 3.0 or later.',0Dh,0Ah,'$'
Main@HelpPage DB 0Dh,0Ah
              DB 'This driver enables you to format and use'
              DB ' floppy disks with bad zero track.',0Dh,0Ah
              DB 0Dh,0Ah
              DB 'Available options:',0Dh,0Ah
              DB ' -H ',' Display this help page',0Dh,0Ah
              DB ' -I ',' Install driver (as a TSR)',0Dh,0Ah
              DB ' -U ',' Uninstall driver',0Dh,0Ah
              DB 0Dh,0Ah,'$'

 ; Program stack
 EVEN
 DB 100h DUP(?)
StackTop LABEL WORD

 ; End of program
ProgramLength= $-Null
END Start
