{$R-,S-,I-,B-,F-,O+} {--------------------------------------------------------- BIOS disk I/O routines for floppy drives. Supports DOS real mode, DOS protected mode, and Windows. Requires TP6, TPW, or BP7. All functions are for floppy disks only; no hard drives. See the individual types and functions in the interface of this unit for more information. See the FMT.PAS sample program for an example of formatting disks. For status code definitions, see the implementation of function GetStatusStr. --------------------------------------------------------- Based on a unit provided by Henning Jorgensen of Denmark. Modified and cleaned up by TurboPower Software for pmode and Windows operation. TurboPower Software P.O. Box 49009 Colorado Springs, CO 80949-9009 CompuServe: 76004,2611 Version 1.0 10/25/93 Version 1.1 10/29/93 fix a dumb bug in the MediaArray check ---------------------------------------------------------} unit BDisk; {-BIOS disk I/O routines for floppy drives} interface const MaxRetries : Byte = 3; {Number of automatic retries for read, write, verify, format} type DriveNumber = 0..7; {Acceptable floppy drive numbers} {Generally, 0 = A, 1 = B} DriveType = 0..4; {Floppy drive or disk types} {0 = unknown or error 1 = 360K 2 = 1.2M 3 = 720K 4 = 1.44M} VolumeStr = String[11]; {String for volume labels} FormatAbortFunc = {Prototype for format abort func} function (Track : Byte; {Track number being formatted, 0..MaxTrack} MaxTrack : Byte; {Maximum track number for this format} Kind : Byte {0 = format beginning} {1 = formatting Track} {2 = verifying Track} {3 = writing boot and FAT} {4 = format ending, Track = format status} ) : Boolean; {Return True to abort format} procedure ResetDrive(Drive : DriveNumber); {-Reset drive system (function $00). Call after any other disk function fails} function GetDiskStatus : Byte; {-Get status of last int $13 operation (function $01)} function GetStatusStr(ErrNum : Byte) : String; {-Return message string for any of the status codes used by this unit.} function GetDriveType(Drive : DriveNumber) : DriveType; {-Get drive type (function $08). Note that this returns the type of the *drive*, not the type of the diskette in it. GetDriveType returns 0 for an invalid drive.} function AllocBuffer(var P : Pointer; Size : Word) : Boolean; {-Allocate a buffer useable in real and protected mode. Buffers passed to ReadSectors and WriteSectors in pmode *MUST* be allocated by using this function. AllocBuffer returns False if sufficient memory is not available. P is also set to nil in that case.} procedure FreeBuffer(P : Pointer; Size : Word); {-Free buffer allocated by AllocBuffer. Size must match the size originally passed to AllocBuffer. FreeBuffer does nothing if P is nil.} function ReadSectors(Drive : DriveNumber; Track, Side, SSect, NSect : Byte; var Buffer) : Byte; {-Read absolute disk sectors (function $02). Track, Side, and SSect specify the location of the first sector to read. NSect is the number of sectors to read. Buffer must be large enough to hold these sectors. ReadSectors returns a status code, 0 for success.} function WriteSectors(Drive : DriveNumber; Track, Side, SSect, NSect : Byte; var Buffer) : Byte; {-Write absolute disk sectors (function $03). Track, Side, and SSect specify the location of the first sector to write. NSect is the number of sectors to write. Buffer must contain all the data to write. WriteSectors returns a status code, 0 for success.} function VerifySectors(Drive : DriveNumber; Track, Side, SSect, NSect : Byte) : Byte; {-Verify absolute disk sectors (function $04). This tests a computed CRC with the CRC stored along with the sector. Track, Side, and SSect specify the location of the first sector to verify. NSect is the number of sectors to verify. VerifySectors returns a status code, 0 for success. Don't call VerifySectors on PC/XTs and PC/ATs with a BIOS from 1985. It will overwrite the stack.} function FormatDisk(Drive : DriveNumber; DType : DriveType; Verify : Boolean; MaxBadSects : Byte; VLabel : VolumeStr; FAF : FormatAbortFunc) : Byte; {-Format drive that contains a disk of type DType. If Verify is True, each track is verified after it is formatted. MaxBadSects specifies the number of sectors that can be bad before the format is halted. If VLabel is not an empty string, FormatDisk puts the BIOS-level volume label onto the diskette. It does *not* add a DOS-level volume label. FAF is a user function hook that can be used to display status during the format, and to abort the format if the user so chooses. Parameters passed to this function are described in FormatAbortFunc above. FormatDisk also writes a boot sector and empty File Allocation Tables for the disk. FormatDisk returns a status code, 0 for success.} function EmptyAbortFunc(Track : Byte; MaxTrack : Byte; Kind : Byte) : Boolean; {-Do-nothing abort function for FormatDisk} {========================================================================} implementation uses {$IFDEF DPMI} WinApi, Dos; {$DEFINE pmode} {$ELSE} {$IFDEF Windows} WinApi, WinDos; {$DEFINE pmode} {$ELSE} Dos; {$UNDEF pmode} {$ENDIF} {$ENDIF} {$IFDEF Windows} type Registers = TRegisters; DateTime = TDateTime; {$ENDIF} type DiskRec = record SSZ : Byte; {Sector size} SPT : Byte; {Sectors/track} TPD : Byte; {Tracks/disk} SPF : Byte; {Sectors/FAT} DSC : Byte; {Directory sectors} FID : Byte; {Format id for FAT} BRD : array[0..13] of Byte; {Variable boot record data} end; DiskRecs = array[1..4] of DiskRec; SectorArray = array[0..511] of Byte; const DData : DiskRecs = {BRD starts at offset 13 of FAT} ((SSZ : $02; SPT : $09; TPD : $27; SPF : $02; DSC : $07; FID : $FD; {5.25" - 360K} BRD : ($02, $01, $00, $02, $70, $00, $D0, $02, $FD, $02, $00, $09, $00, $02)), (SSZ : $02; SPT : $0F; TPD : $4F; SPF : $07; DSC : $0E; FID : $F9; {5.25" - 1.2M} BRD : ($01, $01, $00, $02, $E0, $00, $60, $09, $F9, $07, $00, $0F, $00, $02)), (SSZ : $02; SPT : $09; TPD : $4F; SPF : $03; DSC : $07; FID : $F9; {3.50" - 720K} BRD : ($02, $01, $00, $02, $70, $00, $A0, $05, $F9, $03, $00, $09, $00, $02)), (SSZ : $02; SPT : $12; TPD : $4F; SPF : $09; DSC : $0E; FID : $F0; {3.50" - 1.44M} BRD : ($01, $01, $00, $02, $E0, $00, $40, $0B, $F0, $09, $00, $12, $00, $02))); BootRecord : SectorArray = {Standard boot program} ($EB, $34, $90, $41, $4D, $53, $54, $20, $33, $2E, $30, $00, $02, $01, $01, $00, $02, $E0, $00, $40, $0B, $F0, $09, $00, $12, $00, $02, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $12, $00, $00, $00, $00, $01, $00, $FA, $33, $C0, $8E, $D0, $BC, $00, $7C, $16, $07, $BB, $78, $00, $36, $C5, $37, $1E, $56, $16, $53, $BF, $2B, $7C, $B9, $0B, $00, $FC, $AC, $26, $80, $3D, $00, $74, $03, $26, $8A, $05, $AA, $8A, $C4, $E2, $F1, $06, $1F, $89, $47, $02, $C7, $07, $2B, $7C, $FB, $CD, $13, $72, $67, $A0, $10, $7C, $98, $F7, $26, $16, $7C, $03, $06, $1C, $7C, $03, $06, $0E, $7C, $A3, $3F, $7C, $A3, $37, $7C, $B8, $20, $00, $F7, $26, $11, $7C, $8B, $1E, $0B, $7C, $03, $C3, $48, $F7, $F3, $01, $06, $37, $7C, $BB, $00, $05, $A1, $3F, $7C, $E8, $9F, $00, $B8, $01, $02, $E8, $B3, $00, $72, $19, $8B, $FB, $B9, $0B, $00, $BE, $D6, $7D, $F3, $A6, $75, $0D, $8D, $7F, $20, $BE, $E1, $7D, $B9, $0B, $00, $F3, $A6, $74, $18, $BE, $77, $7D, $E8, $6A, $00, $32, $E4, $CD, $16, $5E, $1F, $8F, $04, $8F, $44, $02, $CD, $19, $BE, $C0, $7D, $EB, $EB, $A1, $1C, $05, $33, $D2, $F7, $36, $0B, $7C, $FE, $C0, $A2, $3C, $7C, $A1, $37, $7C, $A3, $3D, $7C, $BB, $00, $07, $A1, $37, $7C, $E8, $49, $00, $A1, $18, $7C, $2A, $06, $3B, $7C, $40, $38, $06, $3C, $7C, $73, $03, $A0, $3C, $7C, $50, $E8, $4E, $00, $58, $72, $C6, $28, $06, $3C, $7C, $74, $0C, $01, $06, $37, $7C, $F7, $26, $0B, $7C, $03, $D8, $EB, $D0, $8A, $2E, $15, $7C, $8A, $16, $FD, $7D, $8B, $1E, $3D, $7C, $EA, $00, $00, $70, $00, $AC, $0A, $C0, $74, $22, $B4, $0E, $BB, $07, $00, $CD, $10, $EB, $F2, $33, $D2, $F7, $36, $18, $7C, $FE, $C2, $88, $16, $3B, $7C, $33, $D2, $F7, $36, $1A, $7C, $88, $16, $2A, $7C, $A3, $39, $7C, $C3, $B4, $02, $8B, $16, $39, $7C, $B1, $06, $D2, $E6, $0A, $36, $3B, $7C, $8B, $CA, $86, $E9, $8A, $16, $FD, $7D, $8A, $36, $2A, $7C, $CD, $13, $C3, $0D, $0A, $4E, $6F, $6E, $2D, $53, $79, $73, $74, $65, $6D, $20, $64, $69, $73, $6B, $20, $6F, $72, $20, $64, $69, $73, $6B, $20, $65, $72, $72, $6F, $72, $0D, $0A, $52, $65, $70, $6C, $61, $63, $65, $20, $61, $6E, $64, $20, $73, $74, $72, $69, $6B, $65, $20, $61, $6E, $79, $20, $6B, $65, $79, $20, $77, $68, $65, $6E, $20, $72, $65, $61, $64, $79, $0D, $0A, $00, $0D, $0A, $44, $69, $73, $6B, $20, $42, $6F, $6F, $74, $20, $66, $61, $69, $6C, $75, $72, $65, $0D, $0A, $00, $49, $4F, $20, $20, $20, $20, $20, $20, $53, $59, $53, $4D, $53, $44, $4F, $53, $20, $20, $20, $53, $59, $53, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $55, $AA); MediaArray : array[DriveType, 1..2] of Byte = (($00, $00), {Unknown disk} ($01, $02), {360K disk} ($00, $03), {1.2M disk} ($00, $04), {720K disk} ($00, $04)); {1.44M disk} {$IFDEF pmode} type DPMIRegisters = record DI : LongInt; SI : LongInt; BP : LongInt; Reserved : LongInt; BX : LongInt; DX : LongInt; CX : LongInt; AX : LongInt; Flags : Word; ES : Word; DS : Word; FS : Word; GS : Word; IP : Word; CS : Word; SP : Word; SS : Word; end; function GetRealSelector(RealPtr : Pointer; Limit : Word) : Word; {-Set up a selector to point to RealPtr memory} type OS = record O, S : Word; end; var Status : Word; Selector : Word; Base : LongInt; begin GetRealSelector := 0; Selector := AllocSelector(0); if Selector = 0 then Exit; {Assure a read/write selector} Status := ChangeSelector(CSeg, Selector); Base := (LongInt(OS(RealPtr).S) shl 4)+LongInt(OS(RealPtr).O); if SetSelectorBase(Selector, Base) = 0 then begin Selector := FreeSelector(Selector); Exit; end; Status := SetSelectorLimit(Selector, Limit); GetRealSelector := Selector; end; procedure GetRealIntVec(IntNo : Byte; var Vector : Pointer); Assembler; asm mov ax,0200h mov bl,IntNo int 31h les di,Vector mov word ptr es:[di],dx mov word ptr es:[di+2],cx end; function RealIntr(IntNo : Byte; var Regs : DPMIRegisters) : Word; Assembler; asm xor bx,bx mov bl,IntNo xor cx,cx {StackWords = 0} les di,Regs mov ax,0300h int 31h jc @@ExitPoint xor ax,ax @@ExitPoint: end; {$ENDIF} procedure Int13Call(var Regs : Registers); {-Call int $13 for real or protected mode} {$IFDEF pmode} var Base : LongInt; DRegs : DPMIRegisters; {$ENDIF} begin {$IFDEF pmode} {This pmode code is valid only for the AH values used in this unit} FillChar(DRegs, SizeOf(DPMIRegisters), 0); DRegs.AX := Regs.AX; DRegs.BX := Regs.BX; DRegs.CX := Regs.CX; DRegs.DX := Regs.DX; case Regs.AH of 2, 3, 5 : {Calls that use ES as a buffer segment} begin Base := GetSelectorBase(Regs.ES); if (Base <= 0) or (Base > $FFFF0) then begin Regs.Flags := 1; Regs.AX := 1; Exit; end; DRegs.ES := Base shr 4; end; end; if RealIntr($13, DRegs) <> 0 then begin Regs.Flags := 1; Regs.AX := 1; end else begin Regs.Flags := DRegs.Flags; Regs.AX := DRegs.AX; Regs.BX := DRegs.BX; {BX is returned by GetDriveType function only} end; {$ELSE} Intr($13, Regs); {$ENDIF} end; function GetDriveType(Drive : DriveNumber) : DriveType; var Regs : Registers; begin Regs.AH := $08; Regs.DL := Drive; Int13Call(Regs); if Regs.AH = 0 then GetDriveType := Regs.BL else GetDriveType := 0; end; function GetDiskStatus : Byte; var Regs : Registers; begin Regs.AH := $01; Int13Call(Regs); GetDiskStatus := Regs.AL; end; function GetStatusStr(ErrNum : Byte) : String; var NumStr : string[3]; begin case ErrNum of {Following codes are defined by the floppy BIOS} $00 : GetStatusStr := ''; $01 : GetStatusStr := 'Invalid command'; $02 : GetStatusStr := 'Address mark not found'; $03 : GetStatusStr := 'Disk write protected'; $04 : GetStatusStr := 'Sector not found'; $06 : GetStatusStr := 'Floppy disk removed'; $08 : GetStatusStr := 'DMA overrun'; $09 : GetStatusStr := 'DMA crossed 64KB boundary'; $0C : GetStatusStr := 'Media type not found'; $10 : GetStatusStr := 'Uncorrectable CRC error'; $20 : GetStatusStr := 'Controller failed'; $40 : GetStatusStr := 'Seek failed'; $80 : GetStatusStr := 'Disk timed out'; {Following codes are added by this unit} $FA : GetStatusStr := 'Format aborted'; $FB : GetStatusStr := 'Invalid media type'; $FC : GetStatusStr := 'Too many bad sectors'; $FD : GetStatusStr := 'Disk bad'; $FE : GetStatusStr := 'Invalid drive or type'; $FF : GetStatusStr := 'Insufficient memory'; else Str(ErrNum, NumStr); GetStatusStr := 'Unknown error '+NumStr; end; end; procedure ResetDrive(Drive : DriveNumber); var Regs : Registers; begin Regs.AH := $00; Regs.DL := Drive; Int13Call(Regs); end; function AllocBuffer(var P : Pointer; Size : Word) : Boolean; var L : LongInt; begin {$IFDEF pmode} L := GlobalDosAlloc(Size); if L <> 0 then begin P := Ptr(Word(L and $FFFF), 0); AllocBuffer := True; end else begin P := nil; AllocBuffer := False end; {$ELSE} if MaxAvail >= Size then begin GetMem(P, Size); AllocBuffer := True; end else begin P := nil; AllocBuffer := False; end; {$ENDIF} end; procedure FreeBuffer(P : Pointer; Size : Word); begin if P = nil then Exit; {$IFDEF pmode} Size := GlobalDosFree(LongInt(P) shr 16); {$ELSE} FreeMem(P, Size); {$ENDIF} end; function CheckParms(DType : DriveType; Drive : DriveNumber) : Boolean; {-Make sure drive and type are within range} begin CheckParms := False; if (DType < 1) or (DType > 4) then Exit; if (Drive > 7) then Exit; CheckParms := True; end; function SubfSectors(SubFunc : Byte; Drive : DriveNumber; Track, Side, SSect, NSect : Byte; var Buffer) : Byte; {-Code shared by ReadSectors, WriteSectors, VerifySectors, FormatTrack} var Tries : Byte; Done : Boolean; Regs : Registers; begin Tries := 1; Done := False; repeat Regs.AH := SubFunc; Regs.AL := NSect; Regs.CH := Track; Regs.CL := SSect; Regs.DH := Side; Regs.DL := Drive; Regs.ES := Seg(Buffer); Regs.BX := Ofs(Buffer); Int13Call(Regs); if Regs.AH <> 0 then begin ResetDrive(Drive); Inc(Tries); if Tries > MaxRetries then Done := True; end else Done := True; until Done; SubfSectors := Regs.AH; end; function ReadSectors(Drive : DriveNumber; Track, Side, SSect, NSect : Byte; var Buffer) : Byte; begin ReadSectors := SubfSectors($02, Drive, Track, Side, SSect, NSect, Buffer); end; function WriteSectors(Drive : DriveNumber; Track, Side, SSect, NSect : Byte; var Buffer) : Byte; begin WriteSectors := SubfSectors($03, Drive, Track, Side, SSect, NSect, Buffer); end; function VerifySectors(Drive : DriveNumber; Track, Side, SSect, NSect : Byte) : Byte; var Dummy : Byte; begin VerifySectors := SubfSectors($04, Drive, Track, Side, SSect, NSect, Dummy); end; function SetDriveTable(DType : DriveType) : Boolean; {-Set drive table parameters for formatting} var P : Pointer; DBSeg : Word; DBOfs : Word; begin SetDriveTable := False; {$IFDEF pmode} GetRealIntVec($1E, P); DBSeg := GetRealSelector(P, $FFFF); if DBSeg = 0 then Exit; DBOfs := 0; {$ELSE} GetIntVec($1E, P); DBSeg := LongInt(P) shr 16; DBOfs := LongInt(P) and $FFFF; {$ENDIF} {Set gap length for formatting} case DType of 1 : Mem[DBSeg:DBOfs+7] := $50; {360K} 2 : Mem[DBSeg:DBOfs+7] := $54; {1.2M} 3, 4 : Mem[DBSeg:DBOfs+7] := $6C; {720K or 1.44M} end; {Set max sectors/track} Mem[DBSeg:DBOfs+4] := DData[DType].SPT; {$IFDEF pmode} DBSeg := FreeSelector(DBSeg); {$ENDIF} SetDriveTable := True; end; function GetMachineID : Byte; {-Return machine ID code} {$IFDEF pmode} var SegFFFF : Word; {$ENDIF} begin {$IFDEF pmode} SegFFFF := GetRealSelector(Ptr($FFFF, $0000), $FFFF); if SegFFFF = 0 then GetMachineID := 0 else begin GetMachineID := Mem[SegFFFF:$000E]; SegFFFF := FreeSelector(SegFFFF); end; {$ELSE} GetMachineID := Mem[$FFFF:$000E]; {$ENDIF} end; function IsATMachine : Boolean; {-Return True if AT or better machine} begin IsATMachine := False; if Lo(DosVersion) >= 3 then case GetMachineId of $FC, $F8 : {AT or PS/2} IsATMachine := True; end; end; function GetChangeLineType(Drive : DriveNumber; var CLT : Byte) : Byte; {-Return change line type of drive} var Regs : Registers; begin Regs.AH := $15; Regs.DL := Drive; Int13Call(Regs); if (Regs.Flags and FCarry) <> 0 then begin GetChangeLineType := Regs.AH; CLT := 0; end else begin GetChangeLineType := 0; CLT := Regs.AH; end; end; function SetFloppyType(Drive : DriveNumber; FType : Byte) : Byte; {-Set floppy type for formatting} var Tries : Byte; Done : Boolean; Regs : Registers; begin Tries := 1; Done := False; repeat Regs.AH := $17; Regs.AL := FType; Regs.DL := Drive; Int13Call(Regs); if Regs.AH <> 0 then begin ResetDrive(Drive); Inc(Tries); if Tries > MaxRetries then Done := True; end else Done := True; until Done; SetFloppyType := Regs.AH; end; function SetMediaType(Drive : DriveType; TPD : Byte; SPT : Byte) : Byte; {-Set media type for formatting} var Regs : Registers; begin Regs.AH := $18; Regs.DL := Drive; Regs.CH := TPD; Regs.CL := SPT; Int13Call(Regs); SetMediaType := Regs.AH; end; function FormatDisk(Drive : DriveNumber; DType : DriveType; Verify : Boolean; MaxBadSects : Byte; VLabel : VolumeStr; FAF : FormatAbortFunc) : Byte; label ExitPoint; type CHRNRec = record CTrack : Byte; {Track 0..?} CSide : Byte; {Side 0..1} CSect : Byte; {Sector 1..?} CSize : Byte; {Size 0..?} end; CHRNArray = array[1..18] of CHRNRec; FATArray = array[0..4607] of Byte; var Tries : Byte; Track : Byte; Side : Byte; Sector : Byte; RWritten : Byte; RTotal : Byte; FatNum : Byte; BadSects : Byte; ChangeLine : Byte; DiskType : Byte; Status : Byte; Done : Boolean; Trash : Word; DT : DateTime; VDate : LongInt; Regs : Registers; BootPtr : ^SectorArray; CHRN : ^CHRNArray; FATs : ^FATArray; procedure MarkBadSector(Track, Side, Sector : Byte); const BadMark = $FF7; {Bad cluster mark} var CNum : Integer; {Cluster number} FOfs : Word; {Offset into fat for this cluster} FVal : Word; {FAT value for this cluster} OFVal : Word; {Old FAT value for this cluster} begin CNum := (((((Track*2)+Side)*DData[DType].SPT)+Sector-RTotal-2) div DData[DType].BRD[0])+2; if CNum > 1 then begin {Sector is in data space} FOfs := (CNum*3) div 2; Move(FATs^[FOfs], FVal, 2); if Odd(CNum) then OFVal := (FVal and (BadMark shl 4)) else OFVal := (FVal and BadMark); if OFVal = 0 then begin {Not already marked bad, mark it} if Odd(CNum) then FVal := (FVal or (BadMark shl 4)) else FVal := (FVal or BadMark); Move(FVal, FATs^[FOfs], 2); {Add to bad sector count} Inc(BadSects, DData[DType].BRD[0]); end; end; end; begin {Validate parameters. Can't do anything unless these are reasonable} if not CheckParms(DType, Drive) then Exit; {Initialize buffer pointers in case of failure} FATs := nil; CHRN := nil; BootPtr := nil; {Status proc: starting format} if FAF(0, DData[DType].TPD, 0) then begin Status := $FA; goto ExitPoint; end; {Error code for invalid drive or media type} Status := $FE; case GetDriveType(Drive) of 1 : {360K drive formats only 360K disks} if DType <> 1 then goto ExitPoint; 2 : {1.2M drive formats 360K or 1.2M disk} if DType > 2 then goto ExitPoint; 3 : {720K drive formats only 720K disks} if DType <> 3 then goto ExitPoint; 4 : {1.44M drive formats 720K or 1.44M disks} if Dtype < 3 then goto ExitPoint; else goto ExitPoint; end; {Error code for out-of-memory or DPMI error} Status := $FF; {Allocate buffers} if not AllocBuffer(Pointer(FATs), SizeOf(FATArray)) then goto ExitPoint; if not AllocBuffer(Pointer(CHRN), SizeOf(CHRNArray)) then goto ExitPoint; if not AllocBuffer(Pointer(BootPtr), SizeOf(BootRecord)) then goto ExitPoint; {Initialize boot record} Move(BootRecord, BootPtr^, SizeOf(BootRecord)); Move(DData[DType].BRD, BootPtr^[13], 14); {Initialize the FAT table} FillChar(FATs^, SizeOf(FATArray), 0); FATs^[0] := DData[DType].FID; FATs^[1] := $FF; FATs^[2] := $FF; {Set drive table parameters by patching drive table in memory} if not SetDriveTable(DType) then goto ExitPoint; {On AT class machines, set format parameters via BIOS} if IsATMachine then begin {Get change line type: 1 -> 360K drive, 2 -> 1.2M or 3.5" drive} Status := GetChangeLineType(Drive, ChangeLine); if Status <> 0 then goto ExitPoint; if (ChangeLine < 1) or (ChangeLine > 2) then begin Status := 1; goto ExitPoint; end; {Determine floppy type for SetFloppyType call} DiskType := MediaArray[DType, ChangeLine]; if DiskType = 0 then begin Status := $FB; goto ExitPoint; end; {Set floppy type for drive} Status := SetFloppyType(Drive, DiskType); if Status <> 0 then goto ExitPoint; {Set media type for format} Status := SetMediaType(Drive, DData[DType].TPD, DData[DType].SPT); if Status <> 0 then goto ExitPoint; end; {Format each sector} ResetDrive(Drive); BadSects := 0; for Track := 0 to DData[DType].TPD do begin {Status proc: formatting track} if FAF(Track, DData[DType].TPD, 1) then begin Status := $FA; goto ExitPoint; end; for Side := 0 to 1 do begin {Initialize CHRN for this sector} for Sector := 1 to DData[DType].SPT do with CHRN^[Sector] do begin CTrack := Track; CSide := Side; CSect := Sector; CSize := DData[DType].SSZ; end; {Format this sector, with retries} Status := SubfSectors($05, Drive, Track, Side, 1, DData[DType].SPT, CHRN^); if Status <> 0 then goto ExitPoint; end; if Verify then begin {Status proc: verifying track} if FAF(Track, DData[DType].TPD, 2) then begin Status := $FA; goto ExitPoint; end; for Side := 0 to 1 do {Verify the entire track} if VerifySectors(Drive, Track, Side, 1, DData[DType].SPT) <> 0 then begin if Track = 0 then begin {Disk bad} Status := $FD; goto ExitPoint; end; for Sector := 1 to DData[DType].SPT do if VerifySectors(Drive, Track, Side, Sector, 1) <> 0 then begin MarkBadSector(Track, Side, Sector); if BadSects > MaxBadSects then begin Status := $FC; goto ExitPoint; end; end; end; end; end; {Status proc: writing boot and FAT} if FAF(0, DData[DType].TPD, 3) then begin Status := $FA; goto ExitPoint; end; {Write boot record} Status := WriteSectors(Drive, 0, 0, 1, 1, BootPtr^); if Status <> 0 then begin Status := $FD; goto ExitPoint; end; {Write FATs and volume label} Track := 0; Side := 0; Sector := 2; FatNum := 0; RTotal := (2*DData[DType].SPF)+DData[DType].DSC; for RWritten := 0 to RTotal-1 do begin if Sector > DData[DType].SPT then begin Sector := 1; Inc(Side); end; if RWritten < (2*DData[DType].SPF) then begin if FatNum > DData[DType].SPF-1 then FatNum := 0; end else begin FillChar(FATs^, 512, 0); if ((VLabel <> '') and (RWritten = 2*DData[DType].SPF)) then begin {Put in volume label} for Trash := 1 to Length(VLabel) do VLabel[Trash] := Upcase(VLabel[Trash]); while Length(VLabel) < 11 do VLabel := VLabel+' '; Move(VLabel[1], FATs^, 11); FATs^[11] := 8; GetDate(DT.Year, DT.Month, DT.Day, Trash); GetTime(DT.Hour, DT.Min, DT.Sec, Trash); PackTime(DT, VDate); Move(VDate, FATs^[22], 4); end; FatNum := 0; end; if WriteSectors(Drive, Track, Side, Sector, 1, FATs^[FatNum*512]) <> 0 then begin Status := $FD; goto ExitPoint; end; Inc(Sector); Inc(FatNum); end; {Success} Status := 0; ExitPoint: FreeBuffer(BootPtr, SizeOf(BootRecord)); FreeBuffer(CHRN, SizeOf(CHRNArray)); FreeBuffer(FATs, SizeOf(FATArray)); {Status proc: ending format} Done := FAF(Status, DData[DType].TPD, 4); FormatDisk := Status; end; function EmptyAbortFunc(Track, MaxTrack : Byte; Kind : Byte) : Boolean; begin EmptyAbortFunc := False; end; end. { ------------------------------- DEMO PROGRAM -------------------- } { ------------------------------- CUT HERE ---------------------} {$R-,S-,I-} program Fmt; {-Simple formatting program to demonstate DISKB unit} uses {$IFDEF Windows} WinCrt, {$ENDIF} BDisk; const ESC = #27; CR = #13; type CharSet = set of Char; var DLet : Char; DTyp : Char; Verf : Char; GLet : Char; DNum : Byte; Status : Byte; VStr : VolumeStr; const DriveTypeName : array[DriveType] of string[5] = ('other', '360K', '1.2M', '720K', '1.44M'); {$IFNDEF Windows} function ReadKey : Char; assembler; {-Low budget readkey routine} asm xor ah,ah int 16h end; {$ENDIF} function GetKey(Prompt : String; OKSet : CharSet) : Char; {-Get and return a key in the OKSet} var Ch : Char; begin Write(Prompt); repeat Ch := Upcase(ReadKey); if Ch = ESC then begin WriteLn; Halt; end; until (Ch in OKSet); if Ch <> CR then Write(Ch); WriteLn; GetKey := Ch; end; function AbortFunc(Track, MaxTrack : Byte; Kind : Byte) : Boolean; far; {-Display formatting status. Could check for abort here too} begin case Kind of 0 : {Format beginning} Write('Formatting '); 1 : {Formatting track} Write(^H^H^H^H, ((Track*100) div MaxTrack):3, '%'); 2 : {Verifying track} Write(^H, 'V'); 3 : {Writing boot and FAT} Write(^H^H^H^H^H^H^H^H^H^H^H^H^H^H^H, 'Writing boot and FAT'); 4 : {Format ending} begin Write(^H^H^H^H^H^H^H^H^H^H^H^H^H^H^H^H^H^H^H^H); {Track returns final status code in this case} if Track = 0 then WriteLn('Formatted successfully') else WriteLn('Format failed: ', GetStatusStr(Track)); end; end; AbortFunc := False; end; begin WriteLn('Floppy Formatter: to exit'); {Get formatting parameters} DLet := GetKey('Drive to format? (A or B): ', ['A'..'B']); DTyp := GetKey('Disk type? (1=360K, 2=1.2M, 3=720K, 4=1.44M): ', ['1'..'4']); Verf := GetKey('Verify? (Y or N) ', ['N', 'Y']); Write('Volume label? '); ReadLn(VStr); GLet := GetKey('Insert disk and press ', [#13]); {Compute drive number} DNum := Byte(DLet)-Byte('A'); WriteLn('Drive type is ', DriveTypeName[GetDriveType(DNum)]); Status := FormatDisk(DNum, {drive number} Byte(DTyp)-Byte('0'), {format type} (Verf = 'Y'), {verify?} 10, {max bad sectors} VStr, {volume label} AbortFunc); {abort function} {AbortFunc reports the status} end.