{*********************************************************************}
{*                            D F P . P A S                          *}
{*-------------------------------------------------------------------*}
{*    Task          : Formats 3.5" and 5.25" diskettes               *}
{*-------------------------------------------------------------------*}
{*    Author        : Michael Tischer                                *}
{*    Developed on  : 08/23/91                                       *}
{*    Last update   : 03/03/92                                       *}
{*********************************************************************}

program DFP;

Uses Dos;                                     { Add Crt and Dos units }

{-- Constants --------------------------------------------------------}

const NO_DRIVE    = 0;                                     { No drive }
      DD_525      = 1;                              { Drive: 5.25" DD }
      HD_525      = 2;                              { Drive: 5.25" HD }
      DD_35       = 3;                              { Drive: 3.5" DD  }
      HD_35       = 4;                              { Drive: 3.5" HD  }
      MaxNumTries = 5;                      { Maximum number of tries }

{-- Type declarations ------------------------------------------------}

type DdptType = array[ 0..10 ] of byte;          { Structure for DDPT }
     DdptPtr = ^DdptType;                           { Pointer to DDPT }

     PhysDataType = record               { Physical format parameters }
       DSides,                 { Desired number of sides for diskette }
       STrax,                            { Number of tracks per side }
       TSectors : byte;                 { Number of sectors per track }
       DDPT     : DdptPtr;    { Pointer to disk drive parameter table }
     end;

     LogDataType = record                     { DOS format parameters }
       Media,                                            { Media byte }
       Cluster,                          { Number sectors per cluster }
       FAT,                              { Number sectors for the FAT }
       RootSize : byte;               { Entries in the root directory }
    end;

    TrackBfType = array[ 1..18, 1..512 ] of byte;  { Buffer for track }

{-- Initialized global variables -------------------------------------}

const {-- Predefined tables for the individual formats ---------------}


      {-- Defaults for the BOOT sector with load program -------------}

      BootMask  : array[ 1..102 ] of byte =
                   ( $EB, $35,                { 0000   JMP 0037       }
                     $90,                     { 0002   NOP            }
                     {-- Data of the BPB -----------------------------}

                     $50, $43, $49, $4E, $54, $45, $52, $4E,
                     $00, $00, $00, $01, $00, $00, $00, $00,
                     $00, $00, $00, $00, $00, $00, $00, $00,
                     $00, $00, $00, $00, $00, $00, $00, $00,
                     $00, $00, $00, $00, $00, $00, $00, $00,
                     $00, $00, $00, $00, $00, $00, $00, $00,
                     $00, $00, $00, $00,

                     {-- Actual load program -------------------------}

                     $FA,                    { 0037   CLI             }
                     $B8, $30, $00,          { 0038   MOV     AX,0030 }
                     $8E, $D0,               { 003B   MOV     SS,AX   }
                     $BC, $FC, $00,          { 003D   MOV     SP,00FC }
                     $FB,                    { 0040   STI             }
                     $0E,                    { 0041   PUSH    CS      }
                     $1F,                    { 0042   POP     DS      }
                     $BE, $66, $7C,          { 0043   MOV     SI,7C66 }
                     $B4, $0E,               { 0046   MOV     AH,0E   }
                     $FC,                    { 0048   CLD             }
                     $AC,                    { 0049   LODSB           }
                     $0A, $C0,               { 004A   OR      AL,AL   }
                     $74, $04,               { 004C   JZ      0052    }
                     $CD, $10,               { 004E   INT     10      }
                     $EB, $F7,               { 0050   JMP     0049    }
                     $B4, $01,               { 0052   MOV     AH,01   }
                     $CD, $16,               { 0054   INT     16      }
                     $74, $06,               { 0056   JZ      005E    }
                     $B4, $00,               { 0058   MOV     AH,00   }
                     $CD, $16,               { 005A   INT     16      }
                     $EB, $F4,               { 005C   JMP     0052    }
                     $B4, $00,               { 005E   MOV     AH,00   }
                     $CD, $16,               { 0060   INT     16      }
                     $33, $D2,               { 0062   XOR     DX,DX   }
                     $CD, $19 );             { 0064   INT     19      }

      BootMes : string =
       #13#10'DFP  -  (C) 1992 by Michael Tischer'+ #13#10 +
       #13#10'Defective diskette or non-system diskette'#13#10 +
       'Please change diskettes and press any key . . .' +
       #13#10;

{-- Non-initialized global variables ---------------------------------}

var CurDrive     : byte;       { Number of drive to be formatted 0, 1 }
    CurDriveType : byte;                    { Current disk drive type }
    PData        : PhysDataType;        { Physical format information }
    LData        : LogDataType;          { Logical format information }
    POldDDPT     : pointer;                     { Pointer to old DDPT }
    OK           : boolean;                   { Flag for program flow }
    ExitCode     : word;            { Return value to calling process }
    Param        : string;           { for evaluation of command line }

{*********************************************************************}
{* GetDriveType  : Gets disk drive type.                             *}
{* Input         : DRIVE = Drive number (0, 1 etc.)                  *}
{* Output        : Drive code as constant (DD_525, HD_525 etc.)      *}
{*********************************************************************}

function GetDriveType( Drive : byte ) : byte;

var Regs     : Registers;    { Processor registers for interrupt call }

begin
  Regs.ah := $08;                    { Function: Determine drive type }
  Regs.dl := Drive;                                    { Drive number }
  intr( $13, Regs );                            { Call BIOS interrupt }
  if ( Regs.flags and fcarry = 0 ) then {Call completed without error?}
    GetDriveType := Regs.bl                              { Drive type }
  else
    GetDriveType := DD_525;          { Function 08H of interrupt does }
end;                                { not exist => Computer type = XT }

{*********************************************************************}
{* ResetDisk     : Disk reset on all drives.                         *}
{* Input         : None                                              *}
{* Output        : None                                              *}
{* Info          : Regardless of drive number loaded in DL, reset    *}
{*                 executed on all drives.                           *}
{*********************************************************************}

procedure DiskReset;

var Regs : Registers;        { Processor registers for interrupt call }

begin
  with Regs do
    begin
      ah := $00;                 { Function number for interrupt call }
      dl := 0;                                  { Drive a: (see Info) }
    end;
  intr( $13, Regs );                                 { Interrupt call }
end;

{*********************************************************************}
{* GetFormatParameter: Determines the logical and physical           *}
{*                     parameters necessary for formatting.          *}
{* Input         : FORMSTRING = Desired capacity as string           *}
{*                              "360", "1200", "720", "1440"         *}
{*                 DRIVETYPE  = Drive code as returned from          *}
{*                              GetDriveType                         *}
{*                 PDATA      = Loaded by procedure with the         *}
{*                              specifications of the physical       *}
{*                              format                               *}
{*                 LDATA      = Like PDATA, only DOS specs           *}
{* Output        : TRUE, if the format is possible, otherwise FALSE  *}
{* Info          : New formats can be added by extending this        *}
{*                 procedure                                         *}
{*********************************************************************}

function GetFormatParameter(     FormString : string;
                                 DriveType  : byte;
                             var PData      : PhysDataType;
                             var LData      : LogDataType  ) : boolean;

const DDPT_360  : DdptType = ( $DF, $02, $25, $02, $09, $2A,
                               $FF, $50, $F6, $0F, $08 );
      DDPT_1200 : DdptType = ( $DF, $02, $25, $02, $0F, $1B,
                               $FF, $54, $F6, $0F, $08 );
      DDPT_720  : DdptType = ( $DF, $02, $25, $02, $09, $2A,
                               $FF, $50, $F6, $0F, $08 );
      DDPT_1440 : DdptType = ( $DF, $02, $25, $02, $12, $1B,
                               $FF, $6C, $F6, $0F, $08 );

      LOG_360  : LogDataType = ( Media : $FD; Cluster  : 2;
                                 FAT   : 2;   RootSize : $70 );
      LOG_1200 : LogDataType = ( Media : $F9; Cluster  : 1;
                                 FAT   : 7;   RootSize : $E0 );
      LOG_720  : LogDataType = ( Media : $F9; Cluster  : 2;
                                 FAT   : 3;   RootSize : $70 );
      LOG_1440 : LogDataType = ( Media : $F0; Cluster  : 1;
                                 FAT   : 9;   RootSize : $E0 );

      PHYS_360  : PhysDataType = ( DSides   :  2; STrax : 40;
                                   TSectors :  9; DDPT   : @DDPT_360 );
      PHYS_1200 : PhysDataType = ( DSides   :  2; STrax : 80;
                                   TSectors : 15; DDPT   : @DDPT_1200);
      PHYS_1440 : PhysDataType = ( DSides   :  2; STrax : 80;
                                   TSectors : 18; DDPT   : @DDPT_1440);
      PHYS_720  : PhysDataType = ( DSides   :  2; STrax : 80;
                                   TSectors :  9; DDPT   : @DDPT_720 );

begin
  if ( FormString = '1200' ) then                 { 1.2 Meg on 5.25"? }
    if ( DriveType = HD_525 ) then    { Format compatible with drive? }
      begin                                      { Yes, set parameter }
        PData := PHYS_1200;
        LData := LOG_1200;
        GetFormatParameter := true;               { End without error }
      end
    else
      GetFormatParameter := false     { Drive and format incompatible }
  else if ( FormString = '360' ) then                         { 360K? }
    if ( DriveType = HD_525 ) or ( DriveType = DD_525 ) then
      begin              { Format and drive compatible, set parameter }
        PData := PHYS_360;
        LData := LOG_360;
        GetFormatParameter := true;               { End without error }
      end
    else
      GetFormatParameter := false     { Drive and format incompatible }
  else if ( FormString = '1440' ) then            { 1.44 Meg on 3.5"? }
    if ( DriveType = HD_35 ) then     { Format compatible with drive? }
      begin                                     { Yes, set parameters }
        PData := PHYS_1440;
        LData := LOG_1440;
        GetFormatParameter := true;               { End without error }
      end
    else
      GetFormatParameter := false     { Drive and format incompatible }
  else if ( FormString = '720' ) then                 { 720K on 3.5"? }
    if ( DriveType = HD_35 ) or ( DriveType = DD_35 ) then
      begin             { Format and drive compatible, set parameters }
        PData := PHYS_720;
        LData := LOG_720;
        GetFormatParameter := true;               { End without error }
      end
    else
      GetFormatParameter := false     { Drive and format incompatible }
  else
    GetFormatParameter := false;           { Invalid format specified }
end;

{*********************************************************************}
{* DiskPrepare   : Prepare drive, set data transfer rate.            *}
{* Input         : DRIVE = Drive number                              *}
{*                 PDATA = Physical parameters                       *}
{* Output        : None                                              *}
{*********************************************************************}

procedure DiskPrepare( Drive : byte; PData : PhysDataType );

var Regs : Registers;        { Processor registers for interrupt call }

begin
  {-- Set media type for format call ---------------------------------}

  with Regs do
    begin
      ah := $18;                 { Function number for interrupt call }
      ch := PData.STrax - 1;              { Number of tracks per side }
      cl := PData.TSectors;             { Number of sectors per track }
      dl := Drive;                                     { Drive number }
    end;
  intr( $13, Regs );                                 { Interrupt call }
end;

{*********************************************************************}
{* FormatTrack   : Formats a track.                                  *}
{* Input         : See below                                         *}
{* Output        : Error status                                      *}
{*********************************************************************}

function Formattrack( DriveNum,               { The disk drive number }
                      SideNum,                      { The side number }
                      TrackF,                 { Track to be formatted }
                      SecPTr      : byte ) : byte;{ Sectors per track }

type FormatTyp = record
                   DTrack, DSideNum, DCounter, DLength : byte;
                 end;

var Regs      : Registers;   { Processor registers for interrupt call }
    DataField : array[ 1..18 ] of FormatTyp;     { Maximum 18 sectors }
    Counter   : byte;                                  { Loop counter }
    Attempts  : byte;                       { Maximum number of tries }

begin
  for Counter := 1 to SecPTr do
    with DataField[ Counter ] do
      begin
        DTrack := TrackF;                              { Track number }
        DSideNum := SideNum;                          { Diskette side }
        DCounter := Counter;                          { Sector number }
        DLength := 2;              { Number of bytes per sector (512) }
      end;
  Attempts := MaxNumTries;              { Set maximum number of tries }
  repeat
    with Regs do
      begin
        ah := 5;                 { Function number for interrupt call }
        al := SecPTr;               { Number of sectors for one track }
        es := Seg( DataField );               { Address of data field }
        bx := Ofs( DataField );               { to register es:bx     }
        dh := SideNum;                                  { Side number }
        dl := DriveNum;                                { Drive number }
        ch := TrackF;                                  { Track number }
      end;
    intr( $13, Regs );                          { Call BIOS interrupt }
    if ( Regs.flags and fcarry = 1 ) then                    { Error? }
      DiskReset;                 { Yes --> Disk reset before next try }
    dec( Attempts );
  until ( Regs.flags and fcarry = 0 ) or ( Attempts = 0 );
  Formattrack := Regs.ah;                         { Read error status }
end;

{*********************************************************************}
{* VerifyTrack   : Verify track                                      *}
{* Input         : Drive, side, track, sector number                 *}
{* Output        : Error code (0=OK)                                 *}
{*********************************************************************}

function VerifyTrack( DriveNum, SideNum, TrackF, TSectors : byte ) : byte;

var Attempts   : byte;                      { Maximum number of tries }
    Regs       : Registers;  { Processor registers for interrupt call }
    TrackBuffer : TrackBfType;                   { Memory for a track }

begin
  Attempts := MaxNumTries;              { Set maximum number of tries }
  repeat
    with Regs do
      begin
        ah := $04;               { Function number for interrupt call }
        al := TSectors;                 { Number of sectors per track }
        ch := TrackF;                                  { Track number }
        cl := 1;                                  { Start at sector 1 }
        dl := DriveNum;                                { Drive number }
        dh := SideNum;                                  { Side number }
        es := Seg( TrackBuffer );                { Address for buffer }
        bx := Ofs( TrackBuffer );
      end;
    intr( $13, Regs );                          { Call BIOS interrupt }
    if ( Regs.flags and fcarry = 1 ) then                    { Error? }
      DiskReset;                 { Yes --> Disk reset before next try }
    dec( Attempts );
  until ( Regs.flags and fcarry = 0 ) or ( Attempts = 0 );
  VerifyTrack := Regs.ah;
end;

{*********************************************************************}
{* WriteTrack    : Write track                                       *}
{* Input         : Drive, side, track, start sector, number, data    *}
{* Output        : Error code (0=OK)                                 *}
{*********************************************************************}

function WriteTrack(     DriveNum, SideNum, TrackF,
                         Start, SecPTr           : byte;
                     var Buffer ) : byte;

var Attempts : byte;                        { Maximum number of tries }
    Regs     : Registers;    { Processor registers for interrupt call }

begin
  Attempts := MaxNumTries;              { Set maximum number of tries }
  repeat
    with Regs do
      begin
        ah := $03;               { Function number for interrupt call }
        al := SecPTr;                   { Number of sectors per track }
        ch := TrackF;                                  { Track number }
        cl := Start;                              { Start at sector 1 }
        dl := DriveNum;                                { Drive number }
        dh := SideNum;                                  { Side number }
        es := Seg( Buffer );                     { Address for buffer }
        bx := Ofs( Buffer );
      end;
    intr( $13, Regs );                          { Call BIOS interrupt }
    if ( Regs.flags and fcarry = 1 ) then                    { Error? }
      DiskReset;                 { Yes --> Disk reset before next try }
    dec( Attempts );
  until ( Regs.flags and fcarry = 0 ) or ( Attempts = 0 );
  WriteTrack := Regs.ah;
end;

{*********************************************************************}
{* PhysicalFormat: Physical formatting of the diskette (Division     *}
{*                 into tracks, sectors).                            *}
{* Input         : DRIVE  = Drive code                               *}
{*                 PDATA  = Physical parameters                      *}
{*                 VERIFY = TRUE, If verify is to be executed        *}
{* Output        : FALSE if error, otherwise TRUE                    *}
{*********************************************************************}

function PhysicalFormat( Drive  : byte;
                         PData  : PhysDataType;
                         Verify : boolean ) : boolean;

var Attempts : byte;                        { Maximum number of tries }
    Regs     : Registers;    { Processor registers for interrupt call }
    TrackF,                                           { Current track }
    SideNum,                                           { Current side }
    Stat     : byte;               { Return value of called functions }


begin
  {-- Format a diskette track by track -------------------------------}

  for TrackF := 0 to PData.STrax - 1 do          { Execute all tracks }
    for SideNum := 0 to PData.DSides - 1 do       { Execute all sides }
      begin
        Write( #13'Track: ', TrackF: 2, '  Side: ', SideNum: 2 );
        {-- A maximum of 5 tries to format a track -------------------}

        Attempts := MaxNumTries;        { Set maximum number of tries }
        repeat
          Stat := FormatTrack( Drive, SideNum, TrackF, PData.TSectors );
          if ( Stat = 3 ) then          { Diskette write/protected? }
            begin
              PhysicalFormat := false;     { End procedure with error }
              WriteLn( #13'Diskette is write/protected' );
              exit;                                   { End procedure }
            end;
          if ( Stat = 0 ) and Verify then
            Stat := VerifyTrack( Drive, SideNum, TrackF, PData.TSectors );
          dec( Attempts );
          if ( Stat > 0 ) then                  { Format unsuccessful }
            DiskReset;
        until ( Stat = 0 ) or ( Attempts = 0 );
        if ( Stat > 0 )  then               { Error during formatting }
          begin
            PhysicalFormat := false;       { End procedure with error }
            WriteLn( #13'Track defective      ' );
            exit;                                     { End procedure }
          end;
      end;
  PhysicalFormat := true;             { Procedure ended without error }
end;

{*********************************************************************}
{* LogicalFormat : Logical formatting of diskette (Writing boot      *}
{*                 sector, FAT and root directory)                   *}
{* Input         : DRIVE = Drive number                              *}
{*                 PDATA = Physical formatting information           *}
{*                 LDATA = Logical formatting information            *}
{* Output        : TRUE, if no error occurs                          *}
{*********************************************************************}

function LogicalFormat( Drive : byte;
                        PData : PhysDataType;
                        LData : LogDataType ) : boolean;

var Stat         : byte;               { Feedback of called functions }
    TotalNoSectors : word;                  { Total number of sectors }
    i              : byte;                             { Loop counter }
    CurSector,
    CurSide,
    CurTrack       : byte;
    SecPTr         : integer;        { Number of tracks to be written }
    TrackBuffer     : TrackBfType;               { Memory for a track }

begin
  fillchar( TrackBuffer, word( PData.TSectors ) * 512, 0 );{ Empty buf}

  {-- Bootsector: Fixed part -----------------------------------------}

  move( BootMask, TrackBuffer, 102 );         { Copy boot sector mask }
  move( BootMes[1], TrackBuffer[ 1, 103 ],          { Copy boot texts }
        ord(BootMes[0]) );
  TrackBuffer[ 1, 511 ] := $55;           { End marker of boot sector }
  TrackBuffer[ 1, 512 ] := $AA;

  {-- Bootsector: Variable part --------------------------------------}

  TotalNoSectors := PData.STrax * PData.TSectors * Pdata.DSides;
  TrackBuffer[ 1, 14 ] := LData.Cluster;               { Cluster size }
  TrackBuffer[ 1, 18 ] := LData.RootSize; { Num. entries in root dir. }
  TrackBuffer[ 1, 20 ] := lo( TotalNoSectors );{ Total number sectors }
  TrackBuffer[ 1, 21 ] := hi( TotalNoSectors );{ on the diskette      }
  TrackBuffer[ 1, 22 ] := LData.Media;             { Media descriptor }
  TrackBuffer[ 1, 23 ] := LData.FAT;                    { Size of FAT }
  TrackBuffer[ 1, 25 ] := PData.TSectors;         { Sectors per track }
  TrackBuffer[ 1, 27 ] := PData.DSides;             { Number of sides }

  {-- Make FAT and FAT copy (Contents 00)-----------------------------}

  TrackBuffer[ 2, 1 ] := LData.Media;                { Create 1st FAT }
  TrackBuffer[ 2, 2 ] := $FF;
  TrackBuffer[ 2, 3 ] := $FF;
  TrackBuffer[ LData.FAT + 2, 1 ] := LData.Media;    { Create 2nd FAT }
  TrackBuffer[ LData.FAT + 2, 2 ] := $FF;
  TrackBuffer[ LData.FAT + 2, 3 ] := $FF;

  {-- Write boot sector and FAT --------------------------------------}

  Stat := WriteTrack( Drive, 0, 0, 1, PData.TSectors, TrackBuffer );
  if Stat <> 0 then
    LogicalFormat := FALSE

  {-- No error, write root directory ---------------------------------}

  else
    begin
      fillchar( TrackBuffer, 512, 0 );                 { Empty sector }
      CurSector := PData.TSectors;     { Write first track completely }
      CurTrack := 0;                                  { Current track }
      CurSide := 0;                                    { Current side }

      {-- Determine number of remaining sectors and write ------------}

      SecPTr := LData.FAT * 2 + ( LData.Rootsize * 32 div 512 ) +
                1 - PData.TSectors;

      i := 1;
      repeat
        inc( CurSector );                               { Next sector }
        if ( CurSector > PData.TSectors ) then         { Track ended? }
          begin
            CurSector := 1;                  { Continue with sector 1 }
            inc( CurSide );                              { Next side? }
            if ( CurSide = PData.DSides ) then      { Side 2 already? }
              begin
                CurSide := 0;                        { Back to side 0 }
                inc( CurTrack );
              end;
          end;
        Stat := WriteTrack( Drive, CurSide, CurTrack,
                              CurSector, 1, TrackBuffer );
        inc( i );
      until ( i > SecPTr ) or ( Stat <> 0 );
      LogicalFormat := ( Stat = 0 )
    end;
end;

{*********************************************************************}
{*                         MAIN PROGRAM                              *}
{*********************************************************************}

begin
  WriteLn( 'DFP  -  (c) 1992 by Michael Tischer'#13#10 );
  if paramcount > 1 then                      { Parameters specified? }
    begin { Yes, evaluate }
      Param := paramstr( 1 );    { Determine drive ( 0 = a:, 1 = b: ) }
      CurDrive := ord( upcase( Param[ 1 ] ) ) - 65;
      CurDriveType := GetDriveType( CurDrive );{ Type of current drive}
      if ( CurDriveType > 0 ) then                 { Drive available? }
        begin                      { Yes --> Program can be continued }
          if GetFormatParameter( paramstr( 2 ), CurDriveType,
                                 PData, LData) then
            begin                   { Format and drive are compatible }
              DiskPrepare( CurDrive, PData );
              GetIntVec( $1E, POldDDPT );            { Store old DDPT }
              SetIntVec( $1E, PData.DDPT );            { Set new DDPT }

              Param := paramstr( 3 );
              ok := PhysicalFormat( CurDrive, PData,
                                    upcase( Param[ 1 ] ) <> 'N' );
              if ok then
                begin
                  Write( #13'Write boot sector and FAT         ' );
                  ok := LogicalFormat( CurDrive, PData, LData )
                end;

              {-- Evaluation of formatting process -------------------}

              if ok then
                begin
                  WriteLn( #13'Formatting OK                     ' );
                  ExitCode := 0;
                end
              else
                begin
                  WriteLn( #13'Error - format cancelled' );
                  ExitCode := 1;
                end;
              SetIntVec( $1E, POldDDPT );          { Restore old DDPT }
            end
          else
            begin
              WriteLn( 'This drive does not support that format' );
              ExitCode := 2;        { Return value to calling process }
            end
        end
      else
        begin
          WriteLn( 'The specified disk drive does not exist');
          ExitCode := 3;            { Return value to calling process }
        end
    end
  else
    begin
      writeln( 'Syntax: DFP Drive Format [ NV ]' );
      writeln( '            ³       ³       ³' );
      writeln( '         ___³____   ³       ³' );
      writeln( '         A: or B:   ³       ³' );
      writeln( '     _______________³____   ³' );
      writeln( '     360, 720, 1200, 1440   ³' );
      writeln( '                 ___________³__' );
      writeln( '                 NV = No verify' );
      ExitCode := 4;              { Return value to calling process }
    end;
  Halt( ExitCode );
end.
