[Back to DRIVES SWAG index] [Back to Main SWAG index] [Original]

    {
    > I'm programming in BP 7.0 and would like to know how one can read/write
    > some clusters, sectors and boot records from a disk (hard or flop).

    You need the dos interrupts $25 and $26. But, they have a quirk, they don't
    pop off the flags register (which is stored by the INT instruction).
    Also, you need to know which calling method you need, with 16 bit or 32 bit
    cluster numbers.
    }
    { Some proc's to read/write sectors, PD by Arne de Bruijn }
    uses Dos,Strings;
    type
     JmpRec=record       { The starting jump in the bootsector }
      Code:byte; { $E9 XX XX / $EB XX XX}
      case byte of
       0:(Adr1:byte; NOP:byte);
       1:(Adr2:word);
     end;
     BpbRec=record       { The Bios Data Block (returned by DOS, and stored in }
    BytesPSec:word;    { the bootsector }  SecPClus:byte;
      ResSec:word;
      NrFATs:byte;
      NrROOT:word;
      TotalSec:word;
      MDB:byte;
      SecPFAT:word;
      SecPSpoor:word;
      NrHead:word;
      HidSec32:longint;
      TotalSec32:longint;
     end;
     BootSec=record      { The bootsector format in DOS }
      JmpCode:JmpRec;
      Name:array[0..7] of char; { Isn't meaningfull at all, just FORMAT prg name }
      Bpb:BpbRec;
     end;
     BootSecP=^BootSec;
    var
     BigPart:boolean;                 { 32-bit sectors? }
     Drive:byte;                      { which drive are we using }
     ROOTSec,FATSec,DataSec:longint;  { Some starting sectors }
     FAT12:boolean;                   { 12-bit FAT? }
     LastSecNo:longint;               { Save last sector number... }
     LastError:word;                  { ... and error code for error report }

    function ReadSec(SecNo:longint; var Buf):boolean; assembler;
    { Read a sector using DOS int 25h }
    { Parameters: }
    {  SecNo   Sector number to read }
    {  Buf     Your buffer to receive the data (512 bytes will be stored here, }
    {          make sure you have enough space allocated!) }
    { Returns TRUE if success, else FALSE }
    { Uses global boolean BigPart to choose between 16-bit (false) and }
    { 32-bit (true) sector number calling }
    { Uses global byte Drive to choose the drive to read from. 0=A:, 1=B: etc. }
    var
     ParBuf:array[0..9] of byte;
     { Buffer to hold parameters on 32-bit sector call: }
     {  ofs size         meaning }
     {   0   4 (longint) sectornumber }
     {   4   2 (word)    number of sectors to read (set to 1 in this proc.) }
     {   6   4 (pointer) address of buffer }
    asm
     { Copy sectornumber to global var for error report }
     mov ax,word ptr [SecNo]
     mov word ptr [LastSecNo],ax
     mov ax,word ptr [SecNo+2]
     mov word ptr [LastSecNo+2],ax
     push ds                { Store DS register (needs to be preserved in TP/BP) }
     mov al,Drive           { Load Drive no. from global var (DS points still to }
                            { data segment }
     push ax                { Store it on stack }
     cmp BigPart,0          { Must we use 32-bit calling? }
     jne @DoBig             { Yes -> goto @DoBig,  No -> continue with 16-bit }
     lds bx,Buf             { Load address of buffer (Buf) }
     mov cx,1               { Number of sectors to read }
     mov dx,word ptr [SecNo] { Get number of sector to read (SecNr) }
     jmp @DosRead           { goto @DosRead, skip the 32-bit part }
    @DoBig:
     cld                    { Store forwards in parameter buffer }
     mov ax,ss              { Load address of parameter buffer (ParBuf) }
     mov es,ax
     mov ds,ax
     lea di,ParBuf          { Still loading... }
     mov bx,di              { Save offset of parameter buffer in BX }
     mov ax,word ptr [SecNo] { Get number of sector to read (lo 16-bit part) }
     stosw {Lo SecNr}       { Store in our buffer }
     mov ax,word ptr [SecNo+2] { Get number of sector to read (hi 16-bit part) }
     stosw {Hi SecNr}       { Store in buffer }
     mov ax,1               { Sectors to read }
     stosw                  { Store in buffer }
     mov ax,word ptr [Buf]  { Get offset of buffer (Buf) }
     stosw {Offset Buffer}  { Store in buffer }
     mov ax,word ptr [Buf+2] { Get segment of buffer (Buf) }
     stosw {Segment Buffer} { Store in buffer }
     mov cx,-1              { Indicate use of 32-bit calling }
    @DosRead:               { Actual interrupt calling starts }
     pop ax                 { Get drive number from stack }
     push bp                { Save BP (must be preserved in TP/BP) }
     int 25h                { DOS function: read sector(s) }
     mov al,1               { Assume success (TRUE, ordinal 1) }
     sbb al,0               { Subtract one if carry flag high (set on error by
                            { DOS) }
     popf                   { Get flags back DOS had forgotten to do }
     pop bp                 { Get BP back }
     pop ds                 { Get DS back }
     mov LastError,ax       { Save the errorcode in global var for errorreporting }
    end;                    { Return to caller, al contains return code }
                            { (0=FALSE, 1=TRUE) }

    function WriteSec(SecNo:longint; var Buf):boolean; assembler;
    { Same as above, but WRITES a sector with contents of Buf }
    { USE WITH CAUNTION! YOU CAN DESTROY IMPORTANT DATA WITH THIS! }
    { (not commented, is exactly the same as ReadSec, only uses INT 26h } { instead
    of INT 25h) }var
     ParBuf:array[0..9] of byte;
    asm
     mov ax,word ptr [SecNo]
     mov word ptr [LastSecNo],ax
     mov ax,word ptr [SecNo+2]
     mov word ptr [LastSecNo+2],ax
     push ds
     mov al,Drive
     push ax
     cmp BigPart,0
     jne @DoBig
     lds bx,Buf
     mov cx,1
     mov dx,word ptr [SecNo]
     jmp @DosRead
    @DoBig:
     cld
     mov ax,ss
     mov es,ax
     mov ds,ax
     lea di,ParBuf
     mov bx,di
     mov ax,word ptr [SecNo]
     stosw {Lo SecNr}
     mov ax,word ptr [SecNo+2]
     stosw {Hi SecNr}
     mov ax,1
     stosw {Aantal Sectors}
     mov ax,word ptr [Buf]
     stosw {Offset Buffer}
     mov ax,word ptr [Buf+2]
     stosw {Segment Buffer}
     mov cx,-1
    @DosRead:
     pop ax
     push bp
     int 26h
     mov al,1
     sbb al,0
     popf
     pop bp
     pop ds
     mov LastError,ax
    end;

    procedure DiskRError;
    begin
     WriteLn('Error reading disk! Sector:',LastSecNo,' Errorcode:',LastError);
     Halt(1);
    end;

    var
     Bpb:BpbRec; { Global copy of Bios Parameter block, for ClusToSec }

    function ClusToSec(C:word):longint;
    { Convert clusternumber to sector number, because the cluster is often bigger }
    { than one sector, you need to read multiple succeeding sectors to read the }
    { whole cluster (number of sectors in a cluster is in BPB (BPB.SecPClus)) }
    { Uses global BpbRec Bpb and global longint DATASec }
    begin
     ClusToSec:=((C-2)*Bpb.SecPClus)+DATASec;
    end;

    const
     SizeBpb=SizeOf(BpbRec);
     { Needed for assembly part, AFAIK you can't get the size of a structure }
     { (record) in an asm..end; block (shame on you, Borland (or on me :-) )) }
    var
     Buf:pointer;
     S:string[1]; { To store driveletter }
     I:byte;
    begin
     I:=0;
     asm
      mov ax,3000h   { Get DOS version }
      int 21h
      cmp al,3
      jb @BadDos
      ja @DosOk
      cmp ah,20
      jae @DosOk
     @BadDos:        { Lower than 3.2? }
      mov I,1        { Set flag }
     @DosOk:
     end;
     if I=1 then
      begin WriteLn('Sorry, need DOS version 3.2 or higher!'); Halt(1); end;
     if ParamCount=0 then
      begin
       WriteLn(ParamStr(0),' <driveletter>');
       Halt(1);
      end;
     S:=ParamStr(1);
     case UpCase(S[1]) of
      'A'..'Z':;
     else
      begin
       WriteLn('Bad drive!');
       Halt(1);
      end;
     end;
     Drive:=Ord(UpCase(S[1]))-65;
     GetMem(Buf,512);
     asm
      push ds                   { Copy DS }
      pop es                    { to ES }
      push ds                   { Save DS }
      mov ax,440dh              { DOS function 44h (IOCTL), subfunction 0Dh }
                                { (blockdriver control) }
      mov bl,Drive              { Driveno. }
      inc bl                    { Incrase by 1 (0=default, 1=A:) }
      mov cx,860h               { subsubfunction 860h (get information) }
      lds dx,Buf                { Load address of buffer where to store result }
      int 21h                   { Call DOS subfunction }
      mov al,1                  { Assume error }
      jc @EndR                  { Got error? Yes -> goto @EndR }
      mov si,dx                 { Set SI on offset parameterblock }
      mov al,2                  { Assume floppy }
      cmp byte ptr [si+1],5     { Is it a harddisk? }
      jne @EndR                 { No -> goto @EndR }
      mov cx,SizeBpb            { Get size of BPB record }
      add si,7                  { Starts at offset 7 in DOS parameter block }
      lea di,Bpb                { Get address of our global BPB block }
      cld                       { Store forwards }
      rep movsb                 { Copy BPB from DOS to ours }
      xor al,al                 { No errors }
     @EndR:                     { AL contains errorcode: 0=no err., 1=DOS err, }
                                { 2=it's a floppy (need something special) }
      pop ds                    { Restore DS }
      mov I,al                  { Save result }
     end;
     case I of
      0:BigPart:=Bpb.TotalSec=0; { It's a harddisk, 16-bit field is 0 for 32-bit }
                                 { access }   1:                         { Error
    from DOS, report }   begin
        WriteLn('Can''t get parameter block for drive ',chr(Drive+65),'!');
        Halt(1);
       end;
      2:                         { It's a floppy. DOS' bpb is only right }
                                 { for the largest disk size, so we need to read }
                                 { it ourself }
       begin
        BigPart:=false;          { No 32-bit sectors on floppies }
        if not ReadSec(0,Buf^) then  { Read bootsector (sector 0) }
         DiskRError;                     { Show error if we got one }
        Bpb:=BootSecP(Buf)^.Bpb;       { Copy BPB }
       end;
     end;
     with Bpb do  {Store some handy information for accessing the disk ourself }
      begin
       FATSec:=ResSec;                     { Starting FAT sector }
       ROOTSec:=FATSec+(NrFATs*SecPFAT);   { Starting ROOT directory sector }
       DATASec:=ROOTSec+(NrROOT shr 4);    { Starting DATA sector }
       if not BigPart then                 { Is it a 12-bit FAT? }
        FAT12:=((TotalSec-DATASec) div SecPClus)<4087 { Yes if less than 4087 sec}
       else
        FAT12:=false;                      { Not with 32-bit sectors }
      end;
     FreeMem(Buf,512);
     { do what you want }
    end.

[Back to DRIVES SWAG index] [Back to Main SWAG index] [Original]