program darc2;
{$R-$U-$C-$K-}
{
  Program:      DIRARC.PAS
  Version:      2.0
  Date:         6/1/86
  Author:       Steve Fox, Albuquerque ROS (505)299-5974
  Revision:     David W. Carroll, High Sierra RBBS (209) 296-3534
  Credits:      Based heavily on DARC.PAS and intended as a companion to
                that program.
  Description:  Display the directory of an archive created by version 4.30
                or earlier of the ARC utility (copyright 1985 by System
                Enhancement Associates) in a format similar to the "v"erbose
                command.  Some minor differences in the computed values of the
                stowage factors may be noted due to rounding.
  Upadtes: 2.0  Supports ARC512 added modes. Displays mode number as item "T"
                as well as complete text description of arc mode.
  Language:     Turbo Pascal Version 3.0 and later (either MS-DOS or CP/M).
  Usage:        DIRARC arcname
                where arcname is the path/file name of the archive file.  If
                the file extent is omitted, .ARC is assumed.
}
const
  BLOCKSIZE = 128;
  arcmarc   = 26;                      { special archive marker }
  arcver    = 8;                       { archive header version code }
  strlen    = 80;                      { standard string length }
  fnlen     = 12;                      { file name length - 1 }
type
  long      = record                   { used to simulate long (4 byte) integers }
                l, h : integer
              end;
  Str10     = string[10];
  StrStd    = string[strlen];
  fntype    = array [0..fnlen] of char;
  buftype   = array [1..BLOCKSIZE] of byte;
  heads     = record
                name   : fntype;
                size   : long;
                date   : integer;
                time   : integer;
                crc    : integer;
                length : long
              end;
  hexvalue  = string[2];
var
  endfile   : boolean;
  hdrver    : byte;
  arcptr    : integer;
  arcname,
  extname   : StrStd;
  arcbuf    : buftype;
  arcfile   : file;

function hexval(bt : byte) : hexvalue;
{ Convert 8 bit value to hex }
  const
    hexcnv : array[0..15] of char = '0123456789ABCDEF';
  begin
    hexval := hexcnv[bt shr 4] + hexcnv[bt and $0F]
  end;

function pad(stg : StrStd; i : integer) : StrStd;
{ Pad string with spaces to length of i }
  var
    j : integer;
  begin
    j := length(stg);
    FillChar(stg[succ(j)], i - j, ' ');
    stg[0] := chr(i);
    pad := stg
  end;

function intstr(n, w: integer): Str10;
{ Return a string value (width 'w')for the input integer ('n') }
  var
    stg: Str10;
  begin
    str(n:w, stg);
    intstr := stg
  end;

procedure abort(msg : StrStd);
{ terminate the program with an error message }
  begin
    writeln('ABORT: ', msg);
    halt
  end;

function fn_to_str(var fn : fntype) : StrStd;
{ convert strings from C format (trailing 0) to
  Turbo Pascal format (leading length byte). }
  var
    s : StrStd;
    i : integer;
  begin
    s := '';
    i := 0;
    while fn[i] <> #0 do
      begin
        s := s + fn[i];
        i := succ(i)
      end;
    fn_to_str := s
  end;

function unsigned_to_real(u : integer) : real;
{ convert unsigned integer to real }
{ note: INT is a function that returns a REAL!!!}
  begin
    if u >= 0
      then unsigned_to_real := Int(u)
    else if u = $8000
      then unsigned_to_real := 32768.0
      else unsigned_to_real := 65536.0 + u
  end;

function long_to_real(l : long) : real;
{ convert long integer to a real }
{ note: INT is a function that returns a REAL!!! }
  const
    rcon = 65536.0;
  var
    r : real;
    s : (POS, NEG);
  begin
    if l.h >= 0
      then
        begin
          r := Int(l.h) * rcon;
          s := POS
        end
      else
        begin
          s := NEG;
          if l.h = $8000
            then r := rcon * rcon
            else r := Int(-l.h) * rcon
        end;
    r := r + unsigned_to_real(l.l);
    if s = NEG
      then long_to_real := -r
      else long_to_real := r
  end;

procedure Read_Block;
{ read a block from the archive file }
  begin
    if EOF(arcfile)
      then endfile := TRUE
      else BlockRead(arcfile, arcbuf, 1);
    arcptr := 1
  end;

function get_arc : byte;
{ read 1 character from the archive file }
  begin
    if endfile
      then get_arc := 0
      else
        begin
          get_arc := arcbuf[arcptr];
          if arcptr = BLOCKSIZE
            then Read_Block
            else arcptr := succ(arcptr)
        end
  end;

procedure fread(var buf; reclen : integer);
{ read a record from the archive file }
  var
    i : integer;
    b : array [1..strlen] of byte absolute buf;
  begin
    for i := 1 to reclen
      do b[i] := get_arc
  end;

function readhdr(var hdr : heads) : boolean;
{ read a file header from the archive file }
{ FALSE = eof found; TRUE = header found }
  var
    try  : integer;
    name : fntype;
  begin
    try := 10;
    if endfile
      then
        begin
          readhdr := FALSE;
          exit
        end;
    while get_arc <> arcmarc do
      begin
        if try = 0
          then abort(arcname + ' is not an archive');
        try := pred(try);
        writeln(arcname, ' is not an archive, or is out of sync');
        if endfile
          then abort('Archive length error')
      end;

    hdrver := get_arc;
    if hdrver < 0
      then abort('Invalid header in archive ' + arcname);
    if hdrver = 0
      then
        begin                          { special end of file marker }
          readhdr := FALSE;
          exit
      end;
    if hdrver > arcver
      then
        begin
          fread(name, fnlen);
          writeln('Cannot handle file ', fn_to_str(name), ' in archive ',
            arcname);
          writeln('You need a newer version of this program.');
          halt
        end;

    if hdrver = 1
      then
        begin
          fread(hdr, sizeof(heads) - sizeof(long));
          hdrver := 2;
          hdr.length := hdr.size
        end
      else fread(hdr, sizeof(heads));

    readhdr := TRUE
  end;

procedure PrintHeading;
  begin
    writeln;
    writeln('Turbo Pascal DIRARC Utility');
    writeln('Version 2.0, 6/1/86');
    writeln('Lists the directory of .ARC files ');
    writeln('created with ARC version 5.12 and earlier');
    writeln
  end;

procedure GetArcName;
{ get the name of the archive file }
  var
    i : integer;
  begin
    if ParamCount = 1
      then arcname := ParamStr(1)
    else if ParamCount > 1
      then abort('Too many parameters')
      else
        begin
          write('Enter archive filename: ');
          readln(arcname);
          if arcname = ''
            then abort('No file name entered');
          writeln;
          writeln
        end;
    for i := 1 to length(arcname) do
      arcname[i] := UpCase(arcname[i]);
    if pos('.', arcname) = 0
      then arcname := arcname + '.ARC'
  end;

function int_time(time : integer) : StrStd;
{ Convert integer format time to printable string }
  var
    ampm : char;
    hour, minute : integer;
    line : string[6];
  begin
    minute := (time shr 5) and $003F;
    hour   := time shr 11;
    if hour > 12
      then
        begin
          hour := hour - 12;
          ampm := 'p'
        end
      else ampm := 'a';
    if hour = 0
      then hour := 12;
    line := intstr(hour, 2) + ':' + intstr(minute, 2) + ampm;
    if line[4] = ' '
      then line[4] := '0';
    int_time := line
  end;

function int_date(date : integer) : StrStd;
{ Convert standard integer format date to printable string }
  const
    month_name : array[1..12] of string[3] =
      ('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec');
  var
    day, month, year : integer;
    line : string[9];
  begin
    day   := date and $001F;
    month := (date shr 5) and $000F;
    year  := (date shr 9 + 80) mod 100;
    if month in [1..12]
      then line := month_name[month]
      else line := '   ';
    line := intstr(day, 2) + ' ' + line + ' ' + intstr(year, 2);
    if line[8] = ' '
      then line[8] := '0';
    int_date := line
  end;

procedure open_arc;
{ open the archive file for input processing }
  begin
    {$I-} assign(arcfile, arcname); {$I+}
    if IOresult <> 0
      then abort('Cannot open archive file.');
    {$I-} reset(arcfile); {$I+}
    if IOresult <> 0
      then abort('Cannot open archive file.');
    endfile := FALSE;
    Read_Block
  end;

procedure close_arc;
{ close the archive file }
  begin
    close(arcfile)
  end;

procedure directory;
  const
    stowage : array[1..8] of string[8] =
      (' -None- ', ' -None- ', ' Packed ', 'Squeezed', 'LZCrunch', 'LZCrunch',
      'LZW Pack','Dynam LZ');
  var
    i, total_files, sf : integer;
    size_org, size_now, next_ptr, total_length, total_size : real;
    stg_time, stg_date : Str10;
    hdr : heads;
  begin
    writeln('Name          Length    Stowage  T   SF   Size now  Date       Time    CRC');
    writeln('============  ========  ======== =  ====  ========  =========  ======  ====');
    total_files  := 0;
    next_ptr     := 0.0;
    total_size   := 0.0;
    total_length := 0.0;
    open_arc;
    while readhdr(hdr) do
      begin
        extname := fn_to_str(hdr.name);
        total_files := succ(total_files);
        size_org := long_to_real(hdr.length);
        total_length := total_length + size_org;
        size_now := long_to_real(hdr.size);
        total_size := total_size + size_now;
        stg_time := int_time(hdr.time);
        stg_date := int_date(hdr.date);
        if size_org > 0
          then sf := round(100.0 * (size_org - size_now) / size_org)
          else sf := 0;
        writeln(
          pad(extname, 12),
          size_org:10:0,
          stowage[hdrver]:10,
          hdrver:2,
          sf:5, '%',
          size_now:10:0,
          stg_date:11,
          stg_time:8,
          hexval(hi(hdr.crc)):4, hexval(lo(hdr.crc)):2);
        next_ptr := next_ptr + size_now + 29.0;
        i := trunc(next_ptr / 128.0);
        seek(arcfile, i);
        Read_Block;
        arcptr := succ(round(next_ptr - 128.0 * i))
      end;
    close_arc;
    writeln('        ====  ========              ====  ========');
    if total_length > 0
      then sf := round(100.0 * (total_length - total_size) / total_length)
      else sf := 0;
    writeln(
      'Total',
      total_files:7,
      total_length:10:0,
      ' ':10,
      '  ',
      sf:5, '%',
      total_size:10:0)
  end;

begin
  PrintHeading;                        { print a heading }
  GetArcName;                          { get the archive file name }
  directory
end.
