Contributor: ARNE DE.BRUIJN

{
> 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),' ');
   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.