Program Read_PC;
{    Author:  TS Kelso
       Date:  22 February 1986
Description:  This program is designed to read IBM PC diskettes (single or
              double-sided) on a CP/M system and transfer them to a CP/M
              file.  This is particularly useful for transferring data files
              from MS-DOS/PC-DOS to CP/M computers.  Program requires that
              the CP/M system be capable of reading comparable format CP/M
              diskettes.
       NOTE:  Start address for compilation must be 2500H or greater!!}

{This program is placed in the public domain by the author and is available
 for unrestricted use by individuals as long as this notice is maintained.
 This program may not be used for any commercial purpose without express
 written permission from the author.}

label
  exit;
const
  DMA_Address = $2100;
  bytes       = 512;
  SPT         = 9;         {Sectors per track}
  BPS         = 4;         {Blocks per sector}
  RSS         = 1;         {Reserved sectors}
  FATS        = 2;         {Number of FAT sectors}
  NOF         = 2;         {Number of FATs}
  TPS         = 40;        {Tracks per side}
{Configure to satisfy system requirements}
  LDrive      = 'A';       {Low system drive -- System dependent}
  HDrive      = 'D';       {High system drive -- System dependent}
  target      = 'C';       {CP/M target drive}
  source      = 'D';       {IBM diskette source drive}
type
  string12    = string[12];
var
  DMA         : array [1..bytes] of byte absolute $2100;
  FAT         : array [1..NOF,1..1024] of byte;
  Dir         : array [1..bytes] of byte;
  MD          : byte;      {Media Descriptor Byte}
  sides,                   {Number of sides}
  SPC,                     {Sectors per cluster}
  SPD         : integer;   {Sectors per directory}
  response    : char;
  valid       : boolean;

Function Select_Disk(arg : char) : boolean;
  var
    param : integer;
  begin
  if arg in [LDrive..HDrive] then
    begin
    param := ord(arg)-ord('A');
    BDOS(14,param);
    Select_Disk := true;
    end {if}
  else
    begin
    GotoXY(1,24);
    ClrEOL;
    write('Disk Select Error -- Invalid Drive');
    Delay(1000);
    Select_Disk := false;
    end; {else}
  end; {Function Select_Disk}

Function Set_Track(arg : integer) : boolean;
  begin
  if arg in [0..sides*TPS-1] then
    begin
    BIOS(9,arg);
    Set_Track := true;
    end {if}
  else
    begin
    GotoXY(1,24);
    ClrEOL;
    write('Track Select Error -- Not in range 0-',TPS-1);
    Delay(1000);
    Set_Track := false;
    end; {else}
  end; {Function Set_Track}

Procedure Set_DMA(arg : integer);
  begin
  BDOS(26,arg);
  end; {Procedure Set_DMA}

Procedure Set_CPM_Sector(arg : integer);
  begin
  BIOS(10,arg);
  end; {Procedure Set_Sector}

Function Read_Sector(arg1,arg2 : integer) : boolean;
  var
    n1,n2  : integer;
    result : boolean;
  begin
  GotoXY(1,23);
  ClrEOL;
  write('Track ',arg1,', Sector ',arg2);
  result := Set_Track(arg1);
  if arg2 in [1..SPT] then
    begin
    for n1 := 1 to BPS do
      begin
      Set_DMA(DMA_Address + (n1-1)*$80);
      n2 := (arg2-1)*BPS + n1;
      Set_CPM_Sector(n2);
      Delay(10);
      if BIOS(12) <> 0 then
        begin
        GotoXY(1,24);
        ClrEOL;
        write('CP/M Sector ',n2,' read failed');
        Delay(1000);
        result := false;
        end; {if BIOS(12)}
      end; {for n1}
    end {if}
  else
    begin
    GotoXY(1,24);
    ClrEOL;
    write('Sector Select Error -- Not in range 1-',SPT);
    Delay(1000);
    result := false;
    end; {else}
  Read_Sector := result;
  end; {Function Read_Sector}

Procedure Display_Sector;
  const
    columns = 64;
  var
    i1,i2,
    pos,
    lines : integer;
  begin
  lines := bytes div columns;
  for i1 := 0 to lines-1 do
    begin
    for i2 := 1 to columns do
      begin
      pos := columns*i1 + i2;
      if chr(DMA[pos]) in [' '..'~'] then
        write(chr(DMA[pos]))
      else
        write('.');
      if i2 mod 16 = 0 then
        write('  ');
      end; {for i2}
    writeln;
    end; {for i1}
  end; {Procedure Display_Sector}

Procedure Transfer_to_FAT(index1,index2 : integer);
  var
    k : integer;
  begin
  for k := 1 to bytes do
    FAT[index1,(index2-1)*bytes+k] := DMA[k];
  end; {Procedure Transfer_to_FAT}

Procedure Read_FAT(number : integer);
  var
    start,i : integer;
  begin
  start := RSS + (number-1)*FATS;
  for i := 1 to FATS do
    begin
    valid := Read_Sector(0,start+i);
    if valid then
      Transfer_to_FAT(number,i);
    end; {for i}
  end; {Procedure Read_FAT}

Function Compare_FATs : boolean;
  const
    total   : integer = 0;
  var
    i       : integer;
    result  : boolean;
  begin
  result := true;
  for i := 1 to FATS*bytes do
      if FAT[1,i] <> FAT[2,i] then
        begin
        total := total + 1;
        result := false;
        end; {if}
  if not result then
    begin
    GotoXY(1,24);
    ClrEOL;
    write('File Allocation Table Error -- FATs do not compare!');
    Delay(1000);
    GotoXY(1,24);
    ClrEOL;
    write('Total disagreements = ',total);
    Delay(1000);
    end; {if}
  Compare_FATs := result;
  end; {Function Compare_FATs}

Function Convert_Filename(param : integer) : string12;
  var
    name : string12;
    k    : integer;
    next : char;
  begin
  name := '';
  for k := 1 to 8 do
    begin
    next := Chr(Dir[param+k]);
    if next <> ' ' then
      name := name + next;
    end; {for}
  name := name + '.';
  for k := 9 to 11 do
    begin
    next := Chr(Dir[param+k]);
    if next <> ' ' then
      name := name + next;
    end; {for}
  Convert_Filename := name;
  end; {Function Convert_Filename}

Function Convert_Date(param : integer) : string12;
  const
    months = 'JanFebMarAprMayJunJulAugSepOctNovDec';
  var
    date     : string12;
    mo,dy,yr : integer;
    next     : string[2];
  begin
  mo := ((Dir[param+26] and 1) shl 3) or (Dir[param+25] shr 5);
  dy := (Dir[param+25] and $1F);
  yr := (Dir[param+26] shr 1) + 80;
  Str(dy:2,next);
  if next[1] = ' ' then
    next[1] := '0';
  if mo in [1..12] then
    begin
    date := next + '-' + Copy(months,(mo-1)*3+1,3) + '-';
    Str(yr:2,next);
    date := date + next;
    end
  else
    date := ' No Date ';
  Convert_Date := date;
  end; {Function Convert_Date}

Function Convert_Time(param : integer) : string12;
  var
    time     : string12;
    hr,mi,sc : integer;
    next     : string[2];
  begin
  mi := ((Dir[param+24] and 7) shl 3) or (Dir[param+23] shr 5);
  sc := (Dir[param+23] and $1F) shl 1;
  hr := (Dir[param+24] shr 3);
  Str(hr:2,next);
  if next[1] = ' ' then
    next[1] := '0';
  time := next + ':';
  Str(mi:2,next);
  if next[1] = ' ' then
    next[1] := '0';
  time := time + next + ':';
  Str(sc:2,next);
  if next[1] = ' ' then
    next[1] := '0';
  time := time + next;
  Convert_Time := time;
  end; {Function Convert_Time}

Function Convert_Size(param : integer) : real;
  begin
  Convert_Size := 16777216.0*Dir[param+32] + 65536.0*Dir[param+31]
                     + 256.0*Dir[param+30] +         Dir[param+29];
  end; {Function Convert_Size}

Function Convert(param : integer) : integer;
  begin
  Convert := SPC*(param - 2) + RSS + NOF*FATS + SPD + 1;
  end; {Function Convert}

Function Next_Cluster(param : integer) : integer;
  var
    next : integer;
  begin
  next := (3*param div 2) + 1;
  next := 256*FAT[1,next+1] + FAT[1,next];
  if param mod 2 = 0 then
    next := next and $0FFF
  else
    next := next shr 4;
  Next_Cluster := next;
  end; {Function Next_Cluster}

Function Max(arg1,arg2 : real) : real;
  begin
  if arg1 >= arg2 then
    Max := arg1
  else
    Max := arg2;
  end; {Function Max}

Procedure Transfer_File(arg : integer);
  var
    outfile        : file;
    filename       : string12;
    size           : real;
    m,track,sector,
    start,blocks,
    cluster        : integer;
    done,result    : boolean;
  begin
  done := false;
  filename := Convert_Filename(arg);
  Assign(outfile,target+':'+filename);
  Rewrite(outfile);
  size := Convert_Size(arg);
  GotoXY(1,21);
  ClrEOL;
  write('File being transferred:  ',
         filename,'  ',Convert_Time(arg),'  ',
         Convert_Date(arg),'  ',size:8:0,' bytes');
  cluster := 256*Dir[arg+28] + Dir[arg+27];
  GotoXY(1,22);
  ClrEOL;
  write('Cluster ',cluster:3);
  repeat
    start := Convert(cluster);
    for m := start to start+SPC-1 do
      begin
      track := (m-1) div SPT;
      if (sides = 2) then
        track := Abs((track mod 2)*(sides*TPS-1) - (track div 2));
      sector := ((m-1) mod SPT) + 1;
      result := Read_Sector(track,sector);
      if size <> 0 then
        begin
        if size < bytes then
          begin
          blocks := Trunc((size-1)/128) + 1;
          BlockWrite(outfile,DMA,blocks);
          end {if size < bytes}
        else
          BlockWrite(outfile,DMA,4);
        size := Max(0,size - bytes);
        end; {if size <> 0}
    end; {for m}
    if Next_Cluster(cluster) >= $FF8 then
      done := true
    else
      begin
      cluster := Next_Cluster(cluster);
      GotoXY(1,22);
      ClrEOL;
      write('Cluster ',cluster:3);
      end;
  until done;
  Close(outfile);
  end; {Procedure Transfer_File}

Procedure Check_Entries;
  var
    offset,j      : integer;
    check1,check2 : byte;
  begin
  for j := 1 to (bytes div 32) do
    begin
    offset := (j-1)*32;
    check1 := Dir[offset+1];
    check2 := Dir[offset+12];
    if not (check1 in [$00,$2E,$E5]) and not (check2 in [$08,$10]) then
      Transfer_File(offset);
    end; {for j}
  end; {Procedure Check_Entries}

Procedure Load_Directory;
  var
    k : integer;
  begin
  for k := 1 to bytes do
    Dir[k] := DMA[k];
  end; {Procedure Load_Directory}

Procedure Search_Directory;
  var
    track,
    sector,
    start,i : integer;
    result  : boolean;
  begin
  start := RSS + NOF*FATS;
  for i := 1 to SPD do
    begin
    GotoXY(1,20);
    ClrEOL;
    write('Directory Sector ',i);
    sector := start + i;
    track := (sector-1) div SPT;
    sector := ((sector-1) mod SPT) + 1;
    if (sides = 2) then
      track := Abs((track mod 2)*(sides*TPS-1) - (track div 2));
    result := Read_Sector(track,sector);
    Load_Directory;
    Check_Entries;
    end; {for i}
  end; {Procedure Search_Directory}

BEGIN

  ClrScr;
  writeln('This program is designed to read IBM PC/XT diskettes,');
  writeln('either SS or DS, and transfer the files on that diskette');
  writeln('to a CP/M formatted diskette.  While written for the H-89');
  writeln('using the Magnolia disk controller, it should work on any');
  writeln('CP/M system which supports a format compatible with the IBM');
  writeln('format.  It should also work for MS-DOS diskettes.');
  writeln;

{Ensure system is prepared to read IBM format diskette}
  writeln('Did you set the target drive to read IBM compatible format');
  write('before running this program?  ');
  repeat
    read(kbd,response);
    response := Upcase(response);
    valid := true;
    case response of
      'Y' : writeln('Yes');
      'N' : begin
            writeln('No');
            writeln;
            writeln('You must exit and configure target drive.');
            goto exit;
            end; {No}
    else
            valid := false;
    end; {case}
  until valid;
  writeln;

{Specify drives to read IBM diskette from and write CP/M files on}
  writeln('Insert CP/M (target) diskette in Drive ',target,': and IBM PC/XT');
  writeln('(source) diskette in Drive ',source,':.');
  writeln;
  write('Hit any key to begin.');
  read(kbd,response);
  valid := Select_Disk(source);
  writeln;

{Read FATs and compare}
  GotoXY(1,16);
  ClrEOL;
  writeln('Reading FAT Number 1');
  Read_FAT(1);
  GotoXY(1,16);
  ClrEOL;
  writeln('Reading FAT Number 2');
  Read_FAT(2);
  GotoXY(1,16);
  ClrEOL;
  write('Comparing FATs -- ');
  valid := Compare_FATs;
  if valid then
    writeln('Successful compare')
  else
    goto exit;

{Determine Media Type and set media-peculiar parameters}
  MD := FAT[1,1];
  case MD of
    $FC : begin
          sides := 1;
          SPC := 1;
          SPD := 4;
          end; {MD = $FC}
    $FD : begin
          sides := 2;
          SPC := 2;
          SPD := 7;
          end; {MD = $FD}
  else
          begin
          GotoXY(1,24);
          ClrEOL;
          write('Unrecognized Media Descriptor Byte');
          Delay(1000);
          goto exit;
          end; {else}
  end; {case}

{Transfer files}
  Search_Directory;

  Set_DMA($0080);

  GotoXY(1,24);
  ClrEOL;
  writeln('File transfer completed.');

exit:

END.
