LDS03: Load sectors from disk

LDS was the start of a disk recovery system. It never came off the ground since I had migrated to Linux completely around that time. It was started because a friend asked me if I could read the contents off a pair of old 5,25" floppy disks.
In order to come up with a general solution, I decided to make the 'lds' program. It would analyze the bootsector and after that, try to get as much off the disk as possible. If necessary, by using repeated attempts and then statistically determine the most probable data representation of lost data.

After some weeks, the swamp got deeper and deeper. So I decided to get bold and solve the problem by knife and scissor. I cut the top of the plastic envelope of the floppy disk, took out the magnetic medium and transplanted it into a known good envelope. I put the mechanically recovered disk in a suitable drive and tried a 'COPY' operation. Over 95% of the contents came over undamaged... So much for doing things the systematic way.

LDS03: the source

The disk repair program to be, that never even came close to being...

      
MODULE lds03;

(*  02 : add some more functionality                            OK : March 10, 2003 *)
(*  03 : add colours to the screen before adding functions      OK : *)

FROM    Display     IMPORT  displayAttr, Goto, ClrEOL, ClrEOS, Write;
FROM    InOut       IMPORT  Read, ReadCard, (*Write,*) WriteCard, WriteLn, WriteString,
                            WriteHex;
FROM    SYSTEM      IMPORT  ASSEMBLER;
FROM    System      IMPORT  Terminate;
FROM    Keyboard    IMPORT  KeyPressed;
FROM    Xchar       IMPORT  KeyW8, Print;

TYPE    Formats = (SSLD, SSSD, DSLD, DSSD, DSDD, DSHD, DSQD);

VAR     MediaDescriptor, FilesMax, FATcount,
        CouldBe, ProbNot, ReservedSectors,
        SectorsPerFAT,
        SectorPerCluster, BytesPerSector, MaxTrack,
        MaxSector, MaxHead, TotalSectors, SpC,
        MaxDrive, Drive, err, DriveType, Sector             : CARDINAL;

        SectBuff                                            : ARRAY [0..511] OF CHAR;


PROCEDURE WriteStatus (errorcode : CARDINAL);

VAR     OldValue        : CARDINAL;

BEGIN
    OldValue := displayAttr;
    Goto (24, 9);           ClrEOL;
    displayAttr := 4;       Goto (24, 9);
    IF errorcode > 0 THEN   Write (07C)   END;
    CASE errorcode OF
       0 :  displayAttr := 14;          Print ('OK');     |
       1 :  Print ('Invalid function requested');         |
       2 :  Print ('Could not find address mark');        |
       3 :  Print ('Disk is write protected');            |
       4 :  Print ('Sector not found');                   |
       5 :  Print ('Reset failed');                       |
       6 :  Print ('The disk was changed');               |
       7 :  Print ('Bad parameter table');                |
       8 :  Print ('DMA controller overflow');            |
       9 :  Print ('Cannot read outside one track');      |
      10 :  Print ('Bad sector');                         |
      11 :  Print ('Bad track');                          |
      12 :  Print ('Media type not found');               |
      13 :  Print ('Invalid number of sectors found');    |
      14 :  Print ('Control data address mark found');    |
      15 :  Print ('DMA arbitration level out of range'); |
      16 :  Print ('Uncorrectable CRC/EEC on read');      |
      32 :  Print ('Floppy controller error');            |
      64 :  Print ('Track not found');                    |
     128 :  Print ('Drive does not respond');             |
     170 :  Print ('Drive not ready');                    |
     187 :  Print ('Undefined error');                    |
     204 :  Print ('Write fault');                        |
     224 :  Print ('Controller status error');            |
     255 :  Print ('Sense operation failed')
    ELSE
        Print ('Error');  WriteCard (errorcode, 4)
    END;
    displayAttr := OldValue
END WriteStatus;


PROCEDURE ResetDrive (drive  :  CARDINAL);      (*  Reset diskdrive and controller after an error.  *)

BEGIN
    err := 0;
    ASM
        MOV   AH, 0
        MOV   DL, drive
        INT   013H
        MOV   err, AH
    END
END ResetDrive;


PROCEDURE ReadSector (Sector : CARDINAL; VAR buffer : ARRAY OF CHAR) : BOOLEAN;

VAR     trk, sct, hd    : CARDINAL;

BEGIN
    trk := Sector DIV SpC;
    sct := (Sector MOD SpC) + 1;
    IF sct > MaxSector THEN
        hd  := 1;
        DEC (sct, MaxSector)
    ELSE
        hd := 0
    END;
    err := 0;
    ASM
        MOV  AL, 1
        MOV  AH, 2
        MOV  DL, Drive
        MOV  DH, hd
        MOV  CL, sct
        MOV  CH, trk
        LES  BX, buffer
        INT  013H
        MOV  err, AH
    END;
    WriteStatus (err);
    IF err > 0 THEN
        ResetDrive (Drive);
        RETURN FALSE
    ELSE
        RETURN TRUE
    END
END ReadSector;


PROCEDURE ScanDisk (): BOOLEAN;

VAR     sector, status      : CARDINAL;

BEGIN
    FOR Sector := 0 TO TotalSectors -1 DO
        FillScreen;
        IF ReadSector (sector, SectBuff) = FALSE THEN   KeyW8 (' ')  END;
        IF KeyPressed () = TRUE THEN  RETURN FALSE  END
    END;
    RETURN TRUE
END ScanDisk;



PROCEDURE CheckDrive (drv : CARDINAL);

(*  Determine what kind of drive (i.e HARDWARE) there is in this computer *)

BEGIN
    err := 0;
    ASM
        MOV   AH, 8
        MOV   DL, drv
        INT   013H
        MOV   err, AH
        MOV   DriveType, BL
    END
END CheckDrive;


PROCEDURE SetFormat () : BOOLEAN;       (*  Specify to the OS what kind of floppy in in the drive   *)

BEGIN
    err := 0;
    ASM
        MOV   AH, 018H
        MOV   CH, MaxTrack
        MOV   CL, MaxSector
        MOV   DL, Drive
        INT   013H
        MOV   err, AH
    END;
    IF err > 0 THEN
        RETURN FALSE
    ELSE
        RETURN TRUE
    END
END SetFormat;


PROCEDURE SetParams (format  : Formats);

BEGIN
    CASE format OF
      SSLD : TotalSectors :=  320;   MaxTrack := 39;   MaxHead := 0;   MaxSector :=  8;      |
      DSLD : TotalSectors :=  640;   MaxTrack := 39;   MaxHead := 1;   MaxSector :=  8;      |
      SSSD : TotalSectors :=  360;   MaxTrack := 39;   MaxHead := 0;   MaxSector :=  9;      |
      DSSD : TotalSectors :=  720;   MaxTrack := 39;   MaxHead := 1;   MaxSector :=  9;      |
      DSDD : TotalSectors := 1440;   MaxTrack := 79;   MaxHead := 1;   MaxSector :=  9;      |
      DSHD : TotalSectors := 2400;   MaxTrack := 79;   MaxHead := 1;   MaxSector := 15;      |
      DSQD : TotalSectors := 2880;   MaxTrack := 79;   MaxHead := 1;   MaxSector := 18
    END;
    SpC := MaxSector * (MaxHead + 1)
END SetParams;


PROCEDURE GetKey (str : ARRAY OF CHAR) : CHAR;

VAR     key         : CHAR;
        index       : CARDINAL;

BEGIN
    LOOP
        Read (key);
        key := CAP (key);
        FOR index := 0 TO HIGH (str) DO
            IF str [index] = key THEN   EXIT   END
        END;
        Write (07C)
    END;
    RETURN key
END GetKey;


PROCEDURE ManualMode;

VAR     key         : CHAR;

BEGIN
    CASE DriveType OF
     1 : UserMessage (6);       key := GetKey ('ABCD');         |
     2 : UserMessage (7);       key := GetKey ('ABCDEF');       |
     3 : key := 'G'
    ELSE
        UserMessage (8);        key := GetKey ('GH')
    END;
    CASE key OF
      'A' : SetParams (SSLD);   |
      'B' : SetParams (DSLD);   |
      'C' : SetParams (SSSD);   |
      'D' : SetParams (DSSD);   |
      'E' : SetParams (DSDD);   |
      'F' : SetParams (DSHD);   |
      'G' : SetParams (DSDD)
    ELSE
        SetParams (DSQD)
    END
END ManualMode;


PROCEDURE ScanBits (word : CARDINAL) : CARDINAL;

VAR     k, result        : CARDINAL;

BEGIN
    result := 0;
    FOR k := 0 TO 15 DO
        IF k IN BITSET (word) THEN  INC (result)  END;
    END;
    RETURN result
END ScanBits;


PROCEDURE CheckBS (src : ARRAY OF CHAR);

VAR     value, i       : CARDINAL;
        char           : CHAR;

BEGIN
    CouldBe := 0;
    ProbNot := 0;
    value := ORD (src [0]);                             (*  JMP instruction *)
        IF (value = 0E9H) OR (value = 0EBH) THEN
            INC (CouldBe)
        ELSE
            INC (ProbNot, 18)
        END;
    value := 256 * ORD (src [510]) + ORD (src [511]);   (*  Bootsignature   *)
        IF value = 0AA55H THEN
            INC (CouldBe)
        ELSE
            INC (ProbNot, 16)
        END;
    value := BytesPerSector;
        IF value > 127 THEN
            IF ScanBits (value) = 1 THEN
                INC (CouldBe)
            ELSE
                INC (ProbNot, 8)
            END
        ELSE
            INC (ProbNot, 8)
        END;
    value := SectorPerCluster;
        IF ScanBits (value) = 1 THEN
            INC (CouldBe)
        ELSE
            INC (ProbNot, 4)
        END;
    IF ReservedSectors = 0 THEN
        INC (ProbNot, 4)
    ELSE
        IF ReservedSectors > 4 THEN
            INC (ProbNot, 4)
        ELSE
            INC (CouldBe)
        END
    END;
    IF (FATcount = 1) OR (FATcount = 2) THEN
        INC (CouldBe)
    ELSE
        INC (ProbNot, 4)
    END;
    IF (FilesMax # 0) AND (FilesMax MOD 16 = 0) THEN
        INC (CouldBe)
    ELSE
        INC (ProbNot, 6)
    END;
    IF MediaDescriptor < 0EDH THEN
        INC (ProbNot, 2)
    ELSE
        INC (CouldBe)
    END;
    IF SectorsPerFAT > 15 THEN
        INC (ProbNot, 6)
    ELSE
        INC (CouldBe)
    END;
    IF MaxSector > 40 THEN
        INC (ProbNot, 4)
    ELSE
        INC (CouldBe)
    END;
    IF MaxHead > 1 THEN
        INC (ProbNot, 4)
    ELSE
        INC (CouldBe)
    END;
    FOR i := 3 TO 11 DO         (*  Scan OEM name for alphanumeric data *)
        char := src [i];
        IF (char > ' ') AND (char <= '~') THEN
            INC (CouldBe)
        ELSE
            INC (ProbNot, 3)
        END
    END
END CheckBS;


PROCEDURE Init;

VAR     try, ThisOne    : CARDINAL;
        char            : CHAR;
        Flag            : BOOLEAN;

BEGIN
    displayAttr := 02H;      Goto (0,0);         ClrEOS;
    ThisOne := 0;
    LOOP
        ResetDrive (ThisOne);
        CheckDrive (ThisOne);
        IF DriveType = 0 THEN  EXIT  END;
        Write (CHR (ThisOne + ORD ('A')));
        Print (': ');
        CASE DriveType OF
          1 :   Print ('5�"/360 Kb');         |
          2 :   Print ('5�"/1.2 Mb');         |
          3 :   Print ('3�"/720 Kb');         |
          4 :   Print ('3�"/1.44 Mb');
        END;
        WriteLn;
        INC (ThisOne);
        IF ThisOne > 4 THEN  EXIT  END
    END;
    IF ThisOne = 0 THEN
        UserMessage (4);
        Terminate (2)
    ELSIF ThisOne > 4 THEN
        MaxDrive := 3
    ELSE
        MaxDrive := ThisOne - 1
    END;
    Print ('Floppy diskdrives found in this computer: ');
        WriteCard (MaxDrive + 1, 3);
        WriteLn;

    IF MaxDrive > 0 THEN
        Print ('Which drive to use? Specify driveletter : ');
        WriteLn;
        char := GetKey ('ABCD');
        Drive := ORD (char) - ORD ('A')
    ELSE
        Drive := 0
    END;
    IF Drive > MaxDrive THEN
        UserMessage (2);
        Terminate (4)
    END;

    CheckDrive (Drive);
    CASE DriveType OF           (*  Set preliminary parameters  *)
      1 :   SetParams (DSSD);       |   (*  360 Kb  *)
      2 :   SetParams (DSHD);       |   (*  1,2 Mb  *)
      3 :   SetParams (DSDD)            (*  720 Kb  *)
    ELSE
        SetParams (DSQD)                (*  1,44 Mb *)
    END;

    Goto (5, 0);
        Print ('Now I will try to read the bootsector of this disk.');
    Goto (7, 4);
        Print ('Attempt :');
    try := 1;
    LOOP
        Goto (7, 14);
            WriteCard (try, 3);
        IF ReadSector (0, SectBuff) = TRUE  THEN  EXIT  END;
        ResetDrive (Drive);
        INC (try);
        IF try > 10 THEN  EXIT  END
    END;
    Goto (9,0);
    IF try > 10 THEN
        WriteString ('Could not read first sector. Switching to manual mode.');
        ManualMode;
        ResetDrive (Drive);        IF ReadSector (0, SectBuff) = FALSE THEN  Write (07C)  END;
        ResetDrive (Drive);        IF ReadSector (0, SectBuff) = FALSE THEN  RETURN       END
    END;

    char := SectBuff [21];                  (*  get media descriptor    *)
    MediaDescriptor := ORD (char);
    Goto (20, 0);
        WriteString ('In this drive is a ');
    CASE ORD (char) OF
     0FFH : SetParams (DSLD);       WriteString ('320 Kb');     |
     0FEH : SetParams (SSLD);       WriteString ('160 Kb');     |
     0FDH : SetParams (DSSD);       WriteString ('360 Kb');     |
     0FCH : SetParams (SSSD);       WriteString ('180 Kb');     |
     0F9H : IF DriveType = 2 THEN
                SetParams (DSHD);   WriteString ('1.2 Mb')
              ELSE
                SetParams (DSDD);   WriteString ('720 Kb')
              END;                                              |
     0F0H : SetParams (DSQD);       WriteString ('1.44 Mb')
    ELSE
      UserMessage (5)
    END;
    WriteString (' format floppy disk.');    WriteLn;
    WriteString ('Press the SPACEbar to continue.');        KeyW8 (' ')
END Init;


PROCEDURE BuildScreen;

VAR     x, y    : CARDINAL;

BEGIN
    displayAttr := 02;
    Goto ( 0,  0);      ClrEOS;
    displayAttr := 0EH;
    Goto ( 0,  0);      Print ('LDS : Load Diskette Sectors');
    displayAttr := 07;
    Goto ( 2,  0);      Print ('Drive');
    Goto ( 2, 17);      Print ('tracks,');
    Goto ( 2, 28);      Print ('heads,');
    Goto ( 2, 39);      Print ('sectors/track,');
    Goto ( 2, 60);      Print ('sectors,');
    Goto ( 2, 75);      Print ('Kb');

    displayAttr := 03H;
    Goto ( 3,  0);      Print ('Track  :');
    Goto ( 4,  0);      Print ('Sector :');
    Goto ( 5,  0);      Print ('Head   :  ');
    displayAttr := 05H;
    Goto ( 7,  0);      Print ('Sector       contents:');
    x := 3;
    y := 8;
    displayAttr := 02H;
    Goto (y,x);     Write ('�');    FOR x := 4 TO 72 DO  Write ('�')  END;      Write ('�');
    x := 3;
    FOR y := 9 TO 17 DO
        Goto (y, x     );       Write ('�');    WriteCard (y - 9, 2);
        Goto (y, x + 70);       Write ('�');
    END;
    Goto (y,x);     Write ('�');    FOR x := 4 TO 72 DO  Write ('�')  END;      Write ('�');

    displayAttr := 0AH;
    Goto (19,  0);      Print ('Commands:');
    Goto (20,  0);      Print ('S/s = Sectors UP/down');
    Goto (21,  0);      Print ('T/t = Track   UP/down');
    Goto (22,  0);      Print ('H/h = toggle Head');
    Goto (20, 27);      Print ('B = Goto Bootsector');
    Goto (21, 27);      Print ('V = View Sector');
    Goto (22, 27);      Print ('F = File this sector');
    Goto (20, 50);      Print ('R = Read  �');
    Goto (21, 50);      Print ('W = Write �� current sector');
    Goto (22, 50);      Print ('E = Edit  �');
    displayAttr := 0BH;
    Goto (24,  0);      Print ('Status :');
END BuildScreen;


PROCEDURE FillScreen;

VAR     OldAttr         : CARDINAL;

BEGIN
    OldAttr := displayAttr;
    displayAttr := 02H;
    Goto (2, 6);    Write (CHR (Drive + ORD ('A')));    Write (':');
    Goto (2,11);    WriteCard (MaxTrack + 1, 5);
    Goto (2,24);    WriteCard (MaxHead + 1, 3);
    Goto (2,34);    WriteCard (MaxSector, 4);
    Goto (2,54);    WriteCard (TotalSectors, 5);
    Goto (2,69);    WriteCard (TotalSectors DIV 2, 5);

    Goto (3, 9);    WriteCard (Sector DIV SpC, 3);
    Goto (4, 9);    WriteCard ((Sector MOD SpC) MOD MaxSector + 1, 3);
    Goto (5,11);
    IF (Sector MOD SpC) >= MaxSector THEN Print ('1') ELSE Print ('0') END;
    Goto (7, 7);    WriteCard (Sector, 5);
    displayAttr := OldAttr;
END FillScreen;


PROCEDURE UserMessage (code : CARDINAL);

BEGIN
    WriteLn;
    CASE code OF
     1 :    WriteString ('Thank you for using LDS. I hope it met your wishes.');
                WriteLn;
            WriteString ('LDS is free software as defined in the GNU GPL')              |
     2 :    WriteString ("You know, just as well as I, that you simply don't have that drive.");
                WriteLn;
            WriteString ('So stop fooling around and restart LDS. Aborting NOW.');
                WriteLn;                                                                |
     3 :    WriteString ('Sorry, this driveletter is not in your computer.')            |
     4 :    WriteString ('This computer has no disks, so it is no use to continue.');
            WriteLn;
            WriteString ('Goodbye.');                                                   |
     5 :    WriteString ('Unsupported mediadescriptor found.');                         |
     6 :    WriteString ('So you have a 360 Kb floppy diskdrive.');
                WriteLn;
            WriteString ('Please specify the density of the DISK itself');
                WriteLn;
            WriteString (' A = 160 Kb      B = 320 Kb      C = 180 Kb      D = 360 Kb');
                WriteLn;
                WriteLn;                                                                |
     7 :    WriteString ('So you got a 1.2 Mb floppy diskdrive.');
                WriteLn;
            WriteString ('Please specify the density of the DISK itself');
                WriteLn;
            WriteString (' A = 160 Kb      B = 320 Kb      C = 180 Kb      D = 360 Kb');
                WriteLn;
            WriteString (' E = 720 Kb      F = 1.2 Mb');
                WriteLn;
                WriteLn;                                                                |
     8 :    WriteString ('You have a 3,5" diskdrive capable for 1.44 Mb floppies.');
                WriteLn;
            WriteString ('Please specify the density of the DISK itself:');
                WriteLn;
            WriteString (' G = 720 Kb      H = 1.44 Mb');
                WriteLn;
                WriteLn
    END;
    WriteLn
END UserMessage;


PROCEDURE ShowSector;

VAR     i, j, index         : CARDINAL;
        char                : CHAR;

BEGIN
    index := 0;
    FOR i := 0 TO 7 DO
        Goto (i + 9, 7);
        FOR j := 0 TO 63 DO
            char := SectBuff [index];
            INC (index);
            IF ORD (char) > 31 THEN
                Write (char)
            ELSE
                Write ('.')
            END
        END
    END
END ShowSector;


PROCEDURE ViewSector (VAR  buffer : ARRAY OF CHAR);

VAR     i, j, n, index,
        OldAttr             : CARDINAL;
        token               : CHAR;

BEGIN
    OldAttr := displayAttr;
    Goto (0,0);     displayAttr := 04H;     ClrEOS;
    Goto (0,0);     WriteString ('View/Edit sector contents');
    Goto (1,0);
        displayAttr := 02;
        Print ('������');
        FOR n := 0 TO 48 DO   Write ('�')   END;    Write ('�');
        FOR n := 0 TO 17 DO   Write ('�')   END;    Write ('�');
    WriteLn;
        Print ('�    � ');
        Goto (2, 55);   Write ('�');
        Goto (2, 74);   Write ('�');
        displayAttr := 0EH;
        Goto (2, 7);
            Print ('00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F');
        Goto (2, 57);
            Print ('...3...7...A...F');
        displayAttr := 02H;
    WriteLn;
        Print ('������');
        FOR n := 0 TO 48 DO   Write ('�')   END;        Write ('�');
        FOR n := 0 TO 17 DO   Write ('�')   END;        Write ('�');
    WriteLn;
    FOR i := 0 TO 17 DO
        Print ('� ');         WriteHex (i, 2);          Print (' �');
        FOR n := 0 TO 48 DO   Write (' ')   END;        Write ('�');
        FOR n := 0 TO 17 DO   Write (' ')   END;        Write ('�');
        WriteLn
    END;
        Print ('������');
        FOR n := 0 TO 48 DO   Write ('�')   END;        Write ('�');
        FOR n := 0 TO 17 DO   Write ('�')   END;        Write ('�');

    index := 0;
    FOR i := 0 TO 17 DO
        Goto (4 + i, 7);
        FOR j := 0 TO 15 DO
            token := buffer [index];
            INC (index);
            WriteHex (ORD (token), 2);
            Write (' ')
        END
    END;

    index := 0;
    displayAttr := 03H;
    FOR i := 0 TO 17 DO
        Goto (4 + i, 57);
        FOR j := 0 TO 15 DO
            token := buffer [index];
            INC (index);
            IF token >= ' ' THEN
                Write (token)
            ELSE
                Write ('.')
            END
        END
    END;
    displayAttr := OldAttr
END ViewSector;


PROCEDURE ProcessKeys;

VAR     token       : CHAR;
        Flag        : BOOLEAN;

BEGIN
    LOOP
        Read (token);
        CASE token OF
         'U', 'u' : INC (Sector);                           |
         'D', 'd' : DEC (Sector);                           |
         'S'      : INC (Sector);                           |
         's'      : DEC (Sector);                           |
         'T'      : INC (Sector, 2 * MaxSector);            |
         't'      : DEC (Sector, 2 * MaxSector);            |
         'H', 'h' : IF Sector MOD SpC >= MaxSector THEN
                        DEC (Sector, MaxSector)
                    ELSE
                        INC (Sector, MaxSector)
                    END;                                    |
         'R', 'r' : IF ReadSector (Sector, SectBuff) = TRUE THEN
                        ShowSector
                    END;                                    |
         'C', 'c' : IF ScanDisk () = FALSE THEN Write (07C) END;|
         'B', 'b' : Sector := 0;                            |
         'V', 'v', 'F', 'f',
         'W', 'w', 'E', 'e',
         033C,
         'Q', 'q' :  EXIT
        ELSE
         Write (07C)
        END;
        IF Sector > TotalSectors THEN   Sector := TotalSectors - 1  END;
        FillScreen;
    END
END ProcessKeys;


BEGIN
    Init;
    BuildScreen;
        ShowSector;
        FillScreen;
        ProcessKeys;
    ViewSector (SectBuff);
        KeyW8 (' ');
    Goto (0,0);     ClrEOS;     UserMessage (1);
END lds03.
   

Page created October 28, 2006 and