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


(*
 * Copyright 1987, 1990 Samuel H. Smith;  All rights reserved
 *
 * This is a component of the ProDoor System.
 * Do not distribute modified versions without my permission.
 * Do not remove or alter this notice or any other copyright notice.
 * If you use this in your own program you must distribute source code.
 * Do not use any of this in a commercial product.
 *
 *)

(*
 * ZipTV - zipfile text view utility/door
 *
 *)

{$I prodef.inc}

{$M 5000,0,0} {minstack,minheap,maxheap}

{$D+}    {Global debug information}
{$L+}    {Local debug information}

{ $r+,s+}

program ZipTV;

Uses
   Dos, DosMem, MiniCrt, Mdosio, Tools, CInput;

const
   version = 'ZipTV:  ZIP Text Viewer v2.1 of 04-22-90;  (C) 1990 S.H.Smith';


(* ----------------------------------------------------------- *)
(*
 * ZIPfile layout declarations
 *
 *)

type
   signature_type = longint;

const
   local_file_header_signature = $04034b50;

type
   local_file_header = record
      version_needed_to_extract:    word;
      general_purpose_bit_flag:     word;
      compression_method:           word;
      last_mod_file_time:           word;
      last_mod_file_date:           word;
      crc32:                        longint;
      compressed_size:              longint;
      uncompressed_size:            longint;
      filename_length:              word;
      extra_field_length:           word;
   end;

const
   {general_purpose_bit_flag bit values}
   GP_encrypted   = 1;     {file is encrypted}
   GP_8K_dict     = 2;     {8k implode dictionary}
   GP_lit_tree    = 4;     {literal implode tree is present}


const
   central_file_header_signature = $02014b50;

type
   central_directory_file_header = record
      version_made_by:                 word;
      version_needed_to_extract:       word;
      general_purpose_bit_flag:        word;
      compression_method:              word;
      last_mod_file_time:              word;
      last_mod_file_date:              word;
      crc32:                           longint;
      compressed_size:                 longint;
      uncompressed_size:               longint;
      filename_length:                 word;
      extra_field_length:              word;
      file_comment_length:             word;
      disk_number_start:               word;
      internal_file_attributes:        word;
      external_file_attributes:        longint;
      relative_offset_local_header:    longint;
   end;

const
   end_central_dir_signature = $06054b50;

type
   end_central_dir_record = record
      number_this_disk:                         word;
      number_disk_with_start_central_directory: word;
      total_entries_central_dir_on_this_disk:   word;
      total_entries_central_dir:                word;
      size_central_directory:                   longint;
      offset_start_central_directory:           longint;
      zipfile_comment_length:                   word;
   end;

const
   compression_methods: array[0..7] of string[8] =
      (' Stored ',
       ' Shrunk ',
       'Reduce-1', 'Reduce-2', 'Reduce-3', 'Reduce-4',
       'Imploded',
       '    ?   ');


(* ----------------------------------------------------------- *)
(*
 * input file variables
 *
 *)

const
   uinbufsize = 512;    {input buffer size}

var
   zipeof:     boolean;

   csize:      longint;
   cusize:     longint;
   cmethod:    integer;
   cflags:     word;

   inbuf:      array[1..uinbufsize] of byte;
   inpos:      integer;
   incnt:      integer;
   pc:         byte;
   pcbits:     byte;
   pcbitv:     byte;
   zipfd:      dos_handle;
   zipfn:      dos_filename;



(* ----------------------------------------------------------- *)
(*
 * output stream variables
 *
 *)

const
   hsize =     8192;    {must be 8192 for 13 bit shrinking}

   max_binary = 50;     {non-printing count before binary file trigger}
   max_linelen = 200;   {line length before binary file triggered}

   maxlines: integer = 500;
                        {maximum lines per session}

var
   uoutbuf:             string[max_linelen];    {disp line buffer}
   binary_count:        integer;                {non-text chars so far}

   outbuf:              array[0..hsize] of byte; {must be >= 8192 for look-back}
   outpos:              longint;                 {absolute position in outfile}


(* ----------------------------------------------------------- *)
(*
 * other working storage
 *
 *)

var
   expand_files:        boolean;
   header_present:      boolean;
   default_pattern:     string20;
   pattern:             string20;
   action:              string20;



(* ----------------------------------------------------
 *
 *  Zipfile input/output handlers
 *
 *)

procedure skip_rest;
begin
   dos_lseek(zipfd,csize-incnt,seek_cur);
   zipeof := true;
   csize := 0;
   incnt := 0;
end;

procedure skip_csize;
begin
   incnt := 0;
   skip_rest;
end;


(* ------------------------------------------------------------- *)
procedure ReadByte(var x: byte);
begin
   if incnt = 0 then
   begin
      if csize = 0 then
      begin
         zipeof := true;
         exit;
      end;

      inpos := sizeof(inbuf);
      if inpos > csize then
         inpos := csize;
      incnt := dos_read(zipfd,inbuf,inpos);

      inpos := 1;
      dec(csize,incnt);
   end;

   x := inbuf[inpos];
   inc(inpos);
   dec(incnt);
end;


(* ------------------------------------------------------------- *)
procedure ReadBits(bits: integer; var x: integer);
   {read the specified number of bits}
var
   bit:     integer;
   bitv:    integer;

begin

(****
write('readbits n=',bits,' b=');
****)

   x := 0;
   bitv := 1;

   for bit := 0 to bits-1 do
   begin

      if pcbits > 0 then
      begin
         dec(pcbits);
         pcbitv := pcbitv shl 1;
      end
      else

      begin
         ReadByte(pc);
         pcbits := 7;
         pcbitv := 1;
      end;

      if (pc and pcbitv) <> 0 then
         x := x or bitv;

      bitv := bitv shl 1;
   end;

(****
writeln(' -> ',x,' = ',binary(x));
*****)

end;


(* ---------------------------------------------------------- *)
procedure get_string(len: integer; var s: string);
var
   n: integer;
begin
   if len <= 255 then
      n := dos_read(zipfd,s[1],len)
   else
   begin
      n := dos_read(zipfd,s[1],255);
      dos_lseek(zipfd,len-255,seek_cur);
      len := 255;
   end;

   s[0] := chr(len);
end;


(* ------------------------------------------------------------- *)
procedure OutByte (c: integer);
   (* output each character from archive to screen *)

   procedure flushbuf;
   begin
      disp(uoutbuf);
      uoutbuf := '';
   end;

   procedure addchar;
   begin
      inc(uoutbuf[0]);
      uoutbuf[length(uoutbuf)] := chr(c);
   end;

   procedure not_text;
   begin
      newline;
      displn('This is not a text file!');
      linenum := 1000;
      skip_rest;
   end;
   
begin
   outbuf[outpos mod sizeof(outbuf)] := c;
   inc(outpos);

(********
if debug then begin
if c = 13 then else
if c = 10 then begin
   if nomore then
      skip_rest
   else
      newline;
end else write(chr(c));
writeln(' [outbyte c=',c:3,' outpos=',outpos-1:5,']');
if keypressed and (readkey=#27) then halt;
exit; end;
********)

   case c of
   10:  begin
           if linenum < 1000 then
           begin
              flushbuf;
              newline;

              dec(maxlines);
              if (maxlines < 1) and (not dump_user) then
              begin
                  newline;
                  displn('You''ve seen enough.  Please download this file if you want to see more.');
                  dump_user := true;
              end;
           end;

           if nomore or dump_user then
              skip_rest;
        end;

   13:   ;

   26: begin
          flushbuf;
          skip_rest;         {jump to nomore mode on ^z}
       end;

   8,9,32..255:
       begin
          if length(uoutbuf) >= max_linelen then
          begin
             flushbuf;
             if csize > 10 then
                not_text;
          end;

          if linenum < 1000 then   {stop display on nomore}
             addchar;
       end;

   else
      begin
         if binary_count < max_binary then
            inc(binary_count)
         else
         if csize > 10 then
            not_text;
      end;
   end;

end;


(* ------------------------------------------------------------- *)
(*
 * The Reducing algorithm is actually a combination of two
 * distinct algorithms.  The first algorithm compresses repeated
 * byte sequences, and the second algorithm takes the compressed
 * stream from the first algorithm and applies a probabilistic
 * compression method.
 *
 *)

procedure unReduce;
   {expand probablisticly reduced data}

   type
      Sarray = array[0..255] of string[64];

   var
      factor:     integer;
      followers:  ^Sarray;
      ExState:    integer;
      C:          integer;
      V:          integer;
      Len:        integer;

   const
      Lmask:   array[1..4] of integer = ($7f,$3f,$1f,$0f);
      Fcase:   array[1..4] of integer = (127, 63, 31, 15);
      Dshift:  array[1..4] of integer = (7,6,5,4);
      Dand:    array[1..4] of integer = ($01,$03,$07,$0f);


   procedure Expand(c: byte);
   const
      DLE = 144;
   var
      op:   longint;
      i:    integer;

   begin

      case ExState of
           0:  if C <> DLE then
                   OutByte(C)
               else
                   ExState := 1;

           1:  if C <> 0 then
               begin
                   V := C;
                   Len := V and Lmask[factor];
                   if Len = Fcase[factor] then
                     ExState := 2
                   else
                     ExState := 3;
               end
               else
               begin
                   OutByte(DLE);
                   ExState := 0;
               end;

           2:  begin
                  inc(Len,C);
                  ExState := 3;
               end;

           3:  begin
                  op := outpos - C - 1 - ((V shr Dshift[factor]) and
                                          Dand[factor]) * 256;

                  for i := 0 to Len+2 do
                  begin
                     if op < 0 then
                        OutByte(0)
                     else
                        OutByte(outbuf[op mod sizeof(outbuf)]);
                     inc(op);
                  end;

                  ExState := 0;
               end;
      end;
   end;


   procedure LoadFollowers;
   var
      x: integer;
      i: integer;
      b: integer;
   begin
      for x := 255 downto 0 do
      begin
         ReadBits(6,b);
         followers^[x][0] := chr(b);

         for i := 1 to length(followers^[x]) do
         begin
            ReadBits(8,b);
            followers^[x][i] := chr(b);
         end;
      end;
   end;


   function B(x: byte): word;
      {number of bits needed to encode the specified number}
   begin
      case x-1 of
         0..1:    B := 1;
         2..3:    B := 2;
         4..7:    B := 3;
         8..15:   B := 4;
        16..31:   B := 5;
        32..63:   B := 6;
        64..127:  B := 7;
      else        B := 8;
      end;
   end;


(* ----------------------------------------------------------- *)
var
   lchar:   integer;
   lout:    integer;
   I:       integer;
   mem:     longint;

begin
   mem := (sizeof(followers^)+100) - dos_maxavail;
   if mem > 0 then
   begin
      displn(ltoa(mem)+' more bytes of RAM needed to UnReduce!');
      skip_csize;
      exit;
   end;

   factor := cmethod - 1;
   if (factor < 1) or (factor > 4) then
   begin
      skip_csize;
      exit;
   end;

   dos_getmem(followers,sizeof(followers^));
   ExState := 0;
   LoadFollowers;
   lchar := 0;

   while (not zipeof) and (outpos < cusize) and (not dump_user) do
   begin

      if followers^[lchar] = '' then
         ReadBits( 8,lout )
      else

      begin
         ReadBits(1,lout);
         if lout <> 0 then
            ReadBits( 8,lout )
         else
         begin
            ReadBits( B(length(followers^[lchar])), I );
            lout := ord( followers^[lchar][I+1] );
         end;
      end;

      Expand( lout );
      lchar := lout;
   end;

   dos_freemem(followers);
end;



(* ------------------------------------------------------------- *)
(*
 * UnShrinking
 * -----------
 *
 * Shrinking is a Dynamic Ziv-Lempel-Welch compression algorithm
 * with partial clearing.  The initial code size is 9 bits, and
 * the maximum code size is 13 bits.  Shrinking differs from
 * conventional Dynamic Ziv-lempel-Welch implementations in several
 * respects:
 *
 * 1)  The code size is controlled by the compressor, and is not
 *     automatically increased when codes larger than the current
 *     code size are created (but not necessarily used).  When
 *     the decompressor encounters the code sequence 256
 *     (decimal) followed by 1, it should increase the code size
 *     read from the input stream to the next bit size.  No
 *     blocking of the codes is performed, so the next code at
 *     the increased size should be read from the input stream
 *     immediately after where the previous code at the smaller
 *     bit size was read.  Again, the decompressor should not
 *     increase the code size used until the sequence 256,1 is
 *     encountered.
 *
 * 2)  When the table becomes full, total clearing is not
 *     performed.  Rather, when the compresser emits the code
 *     sequence 256,2 (decimal), the decompressor should clear
 *     all leaf nodes from the Ziv-Lempel tree, and continue to
 *     use the current code size.  The nodes that are cleared
 *     from the Ziv-Lempel tree are then re-used, with the lowest
 *     code value re-used first, and the highest code value
 *     re-used last.  The compressor can emit the sequence 256,2
 *     at any time.
 *
 *)

procedure unShrink;

const
   max_bits =  13;
   init_bits = 9;
   first_ent = 257;
   clear =     256;
   
type
   hsize_array_integer = array[0..hsize] of integer;
   hsize_array_byte    = array[0..hsize] of byte;

var
   cbits:      integer;
   maxcode:    integer;
   free_ent:   integer;
   maxcodemax: integer;
   offset:     integer;
   sizex:      integer;
   prefix_of:  ^hsize_array_integer;
   suffix_of:  ^hsize_array_byte;
   stack:      hsize_array_byte absolute outbuf;
   stackp:     integer;
   finchar:    integer;
   code:       integer;
   oldcode:    integer;
   incode:     integer;


   (* ------------------------------------------------------------- *)
   procedure partial_clear;
   var
      pr:   integer;
      cd:   integer;

   begin
      {mark all nodes as potentially unused}
      for cd := first_ent to free_ent-1 do
         word(prefix_of^[cd]) := prefix_of^[cd] or $8000;


      {unmark those that are used by other nodes}
      for cd := first_ent to free_ent-1 do
      begin
         pr := prefix_of^[cd] and $7fff;    {reference to another node?}
         if pr >= first_ent then            {flag node as referenced}
            prefix_of^[pr] := prefix_of^[pr] and $7fff;
      end;


      {clear the ones that are still marked}
      for cd := first_ent to free_ent-1 do
         if (prefix_of^[cd] and $8000) <> 0 then
            prefix_of^[cd] := -1;


      {find first cleared node as next free_ent}
      free_ent := first_ent;
      while (free_ent < maxcodemax) and (prefix_of^[free_ent] <> -1) do
         inc(free_ent);
   end;



(* ------------------------------------------------------------- *)
var
   mem:  longint;
begin
   mem := (sizeof(prefix_of^)+sizeof(suffix_of^)+ 100) - dos_maxavail;

   if mem > 0 then
   begin
      displn(ltoa(mem)+' more bytes of RAM needed to UnShrink!');
      skip_csize;
      exit;
   end;


   {allocate heap storage}
   dos_getmem(prefix_of,sizeof(prefix_of^));
   dos_getmem(suffix_of,sizeof(suffix_of^));


   {decompress the file}
   maxcodemax := 1 shl max_bits;
   cbits := init_bits;
   maxcode := (1 shl cbits)- 1;
   free_ent := first_ent;
   offset := 0;
   sizex := 0;

   fillchar(prefix_of^,sizeof(prefix_of^),$FF);
   for code := 255 downto 0 do
   begin
      prefix_of^[code] := 0;
      suffix_of^[code] := code;
   end;

   ReadBits(cbits,oldcode);
   finchar := oldcode;
   if zipeof then
      exit;

   OutByte(finchar);

   stackp := 0;

   while (not zipeof) and (not dump_user) do
   begin
      ReadBits(cbits,code);

      while code = clear do
      begin
         ReadBits(cbits,code);

         case code of
            1: begin
                  inc(cbits);
                  if cbits = max_bits then
                     maxcode := maxcodemax
                  else
                     maxcode := (1 shl cbits) - 1;
               end;

            2: partial_clear;
         end;

         ReadBits(cbits,code);
      end;


      {special case for KwKwK string}
      incode := code;
      if prefix_of^[code] = -1 then
      begin
         stack[stackp] := finchar;
         inc(stackp);
         code := oldcode;
      end;


      {generate output characters in reverse order}
      while (code >= first_ent) and (stackp < sizeof(stack)-1) do
      begin
         stack[stackp] := suffix_of^[code];
         inc(stackp);
         code := prefix_of^[code];
      end;

      finchar := suffix_of^[code];
      stack[stackp] := finchar;
      inc(stackp);


      {and put them out in forward order}
      while (stackp > 0) do
      begin
         outpos := stackp; {required to preserve shared buffer/stack}
         dec(stackp);
         OutByte(stack[stackp]);
      end;


      {generate new entry}
      code := free_ent;
      if code < maxcodemax then
      begin
         prefix_of^[code] := oldcode;  {previous code}
         suffix_of^[code] := finchar;  {final character from this code}
         while (free_ent < maxcodemax) and (prefix_of^[free_ent] <> -1) do
            inc(free_ent);
      end;


      {remember previous code}
      oldcode := incode;
   end;


   {release heap storage}
   dos_freemem(suffix_of);
   dos_freemem(prefix_of);
end;


(* ------------------------------------------------------------- *)
(*
 * Imploding
 * ---------
 *
 * The Imploding algorithm is actually a combination of two distinct
 * algorithms.  The first algorithm compresses repeated byte sequences
 * using a sliding dictionary.  The second algorithm is used to compress
 * the encoding of the sliding dictionary ouput, using multiple
 * Shannon-Fano trees.
 *
 *)

procedure unImplode;
   {expand imploded data}

   const
      maxSF = 256;

   type
      sf_entry = record
                    Code:       word;
                    Value:      byte;
                    BitLength:  byte;
                 end;

      sf_tree = record  {a shannon-fano tree}
         entry:         array[0..maxSF] of sf_entry;
         entries:       integer;
         MaxLength:     integer;
      end;

      sf_treep = ^sf_tree;

   var
      lit_tree:               sf_treep;
      length_tree:            sf_treep;
      distance_tree:          sf_treep;
      lit_tree_present:       boolean;
      eightK_dictionary:      boolean;
      minimum_match_length:   integer;
      dict_bits:              integer;


   (* ----------------------------------------------------------- *)
   procedure LoadTree(var tree: sf_treep;
                      treesize: integer);
      {allocate and load a shannon-fano tree from the compressed file}

      procedure SortLengths;
         {Sort the Bit Lengths in ascending order, while retaining the order
          of the original lengths stored in the file}
      var
         x:       integer;
         gap:     integer;
         t:       sf_entry;
         noswaps: boolean;
         a,b:     word;

      begin
         gap := treesize div 2;

         with tree^ do
         repeat
            repeat
               noswaps := true;
               for x := 0 to (treesize-1)-gap do
               begin
                  a := entry[x].BitLength;
                  b := entry[x+gap].BitLength;
                  if (a > b) or
                     ((a = b) and (entry[x].Value > entry[x+gap].Value)) then
                  begin
                     t := entry[x];
                     entry[x] := entry[x+gap];
                     entry[x+gap] := t;
                     noswaps := false;
                  end;
               end;
            until noswaps;

            gap := gap div 2;
         until gap < 1;
      end;


      procedure ReadLengths;
      var
         treeBytes:  integer;
         i:          integer;
         num,len:    integer;
      begin
         {get number of bytes in compressed tree}
         ReadBits(8,treeBytes);
         inc(treeBytes);
         i := 0;
         with tree^ do
         begin
            MaxLength := 0;

            {High 4 bits: Number of values at this bit length + 1. (1 - 16)
             Low  4 bits: Bit Length needed to represent value + 1. (1 - 16)}
            while treeBytes > 0 do
            begin
               ReadBits(4,len);  inc(len);
               ReadBits(4,num);  inc(num);

               while num > 0 do
               with entry[i] do
               begin
                  if len > MaxLength then
                     MaxLength := len;
                  BitLength := len;
                  Value := i;
                  inc(i);
                  dec(num);
               end;

               dec(treeBytes);
            end;
         end;
      end;

      procedure GenerateTrees;
         {Generate the Shannon-Fano trees}
      var
         Code:          word;
         CodeIncrement: integer;
         LastBitLength: integer;
         i:             integer;
      begin
         Code := 0;
         CodeIncrement := 0;
         LastBitLength := 0;

         i := treesize - 1;   {either 255 or 63}
         with tree^ do
         while i >= 0 do
         begin
            inc(Code,CodeIncrement);
            if entry[i].BitLength <> LastBitLength then
            begin
               LastBitLength := entry[i].BitLength;
               CodeIncrement := 1 shl (16 - LastBitLength);
            end;

            entry[i].Code := Code;
            dec(i);
         end;
      end;

      procedure ReverseBits;
         {Reverse the order of all the bits in the above ShannonCode[]
          vector, so that the most significant bit becomes the least
          significant bit. For example, the value 0x1234 (hex) would become
          0x2C48 (hex).}
      var
         i:    integer;
         mask: word;
         revb: word;
         v:    word;
         o:    word;
         b:    integer;

      begin
         for i := 0 to treesize-1 do
         begin
            {get original code}
            o := tree^.entry[i].Code;

            {reverse each bit}
            mask := $0001;
            revb := $8000;
            v := 0;
            for b := 0 to 15 do
            begin
               {if bit set in mask, then substitute reversed bit}
               if (o and mask) <> 0 then
                  v := v or revb;

               {advance to next bit}
               revb := revb shr 1;
               mask := mask shl 1;
            end;

            {store reversed bits}
            tree^.entry[i].Code := v;
         end;
      end;

   begin
      dos_getmem(tree,sizeof(tree^));
      tree^.entries := treesize;
      ReadLengths;
      SortLengths;
      GenerateTrees;
      ReverseBits;
   end;


   (* ----------------------------------------------------------- *)
   procedure LoadTrees;
   begin
      eightK_dictionary := (cflags and GP_8k_dict)  <> 0;
      lit_tree_present  := (cflags and GP_lit_tree) <> 0;

      if eightK_dictionary then
         dict_bits := 7
      else
         dict_bits := 6;

      if lit_tree_present then
      begin
         minimum_match_length := 3;
         LoadTree(lit_tree,256);
      end
      else
         minimum_match_length := 2;

      LoadTree(length_tree,64);
      LoadTree(distance_tree,64);
   end;


   (* ----------------------------------------------------------- *)
   procedure ReadTree(tree: sf_treep;
                      var dest: integer);
      {read next byte using a shannon-fano tree}
   var
      bits: integer;
      cv:   word;
      b:    integer;
      cur:  integer;

   begin
      bits := 0;
      cv := 0;
      cur := 0;
      dest := -1; {in case of error}

      with tree^ do
      while true do
      begin
         ReadBits(1,b);
         cv := cv or (b shl bits);
         inc(bits);

         while entry[cur].BitLength < bits do
         begin
            inc(cur);
            if cur >= entries then
               exit;
         end;

         while entry[cur].BitLength = bits do
         begin
            if entry[cur].Code = cv then
            begin
               dest := entry[cur].Value;
               exit;
            end;

            inc(cur);
            if cur >= entries then
               exit;
         end;
      end;

   end;


(* ----------------------------------------------------------- *)
var
   lout:       integer;
   mem:        longint;
   op:         longint;
   Length:     integer;
   Distance:   integer;
   i:          integer;

begin
   mem := (sizeof(sf_tree)*3+100) - dos_maxavail;
   if mem > 0 then
   begin
      displn(ltoa(mem)+' more bytes of RAM needed to UnImplode!');
      skip_csize;
      exit;
   end;

   LoadTrees;

   while (not zipeof) and (outpos < cusize) and (not dump_user) do
   begin
      ReadBits(1,lout);

      if lout <> 0 then    {encoded data is literal data}
      begin
         if lit_tree_present then
            ReadTree(lit_tree,lout)   {use Literal Shannon-Fano tree}
         else
            ReadBits(8,lout);

         OutByte(lout);
      end
      else

      begin          {encoded data is sliding dictionary match}
         readBits(dict_bits,lout);
         Distance := lout;

         ReadTree(distance_tree,lout);
         Distance := Distance or (lout shl dict_bits);
         {using the Distance Shannon-Fano tree, read and decode the
            upper 6 bits of the Distance value}

         ReadTree(length_tree,Length);
         {using the Length Shannon-Fano tree, read and decode the Length value}

         inc(Length,Minimum_Match_Length);
         if Length = (63 + Minimum_Match_Length) then
         begin
            ReadBits(8,lout);
            inc(Length,lout);
         end;

         {move backwards Distance+1 bytes in the output stream, and copy
          Length characters from this position to the output stream.
          (if this position is before the start of the output stream,
          then assume that all the data before the start of the output
          stream is filled with zeros)}

         op := outpos - Distance - 1;
         for i := 1 to Length do
         begin
            if op < 0 then
               OutByte(0)
            else
               OutByte(outbuf[op mod sizeof(outbuf)]);
            inc(op);
         end;
      end;
   end;

   if lit_tree_present then
      dos_freemem(lit_tree);
   dos_freemem(distance_tree);
   dos_freemem(length_tree);
end;



(* ---------------------------------------------------------- *)
(*
 * This procedure displays the text contents of a specified archive
 * file.  The filename must be fully specified and verified.
 *
 *)
procedure viewfile;
var
   b: byte;

begin
   newline;
   {default_color;}
   binary_count := 0;
   pcbits := 0;
   incnt := 0;
   outpos := 0;
   uoutbuf := '';
   zipeof := false;

   if (cflags and GP_encrypted) <> 0 then
   begin
      displn('File is encrypted.');
      skip_csize;
      exit;
   end;

   case cmethod of
      0:    {stored}
            while (not zipeof) and (not dump_user) do
            begin
               ReadByte(b);
               OutByte(b);
            end;

      1:    UnShrink;

      2..5: UnReduce;

      6:    UnImplode;

      else  begin
               displn('Unknown compression method.');
               skip_csize;
            end;
   end;

   if nomore=false then
      newline;

   linenum := 1;
end;


(* ---------------------------------------------------------- *)
procedure _itoa(i: integer; var sp);
var
   s: array[1..2] of char absolute sp;
begin
   s[1] := chr( (i div 10) + ord('0'));
   s[2] := chr( (i mod 10) + ord('0'));
end;

function format_date(date: word): string8;
const
   s:       string8 = 'mm-dd-yy';
begin
   _itoa(((date shr 9) and 127)+80, s[7]);
   _itoa( (date shr 5) and 15,  s[1]);
   _itoa( (date      ) and 31,  s[4]);
   format_date := s;
end;

function format_time(time: word): string8;
const
   s:       string8 = 'hh:mm:ss';
begin
   _itoa( (time shr 11) and 31, s[1]);
   _itoa( (time shr  5) and 63, s[4]);
   _itoa( (time shl  1) and 63, s[7]);
   format_time := s;
end;


(* ---------------------------------------------------------- *)
procedure process_local_file_header;
var
   n:             word;
   rec:           local_file_header;
   filename:      string;
   extra:         string;
   fpos:          longint;

begin
   dos_lseek(zipfd,0,seek_cur);
   fpos := dos_tell;

   while (dump_user = false) do
   begin
      set_function(fun_arcview);

      dos_lseek(zipfd,fpos,seek_start);
      n := dos_read(zipfd,rec,sizeof(rec));
      get_string(rec.filename_length,filename);
      filename := remove_path(filename);
      stoupper(filename);
      get_string(rec.extra_field_length,extra);
      csize := rec.compressed_size;
      cusize := rec.uncompressed_size;
      cmethod := rec.compression_method;
      cflags := rec.general_purpose_bit_flag;


      (* exclude the file if outside current pattern *)
      if nomore or (not wildcard_match(pattern,filename)) then
      begin
         skip_csize;
         exit;
      end;

      (* display file information headers if needed *)
      if not header_present then
      begin
         header_present := true;

         newline;
         disp(' File Name    Length   Method     Date      Time');
         if expand_files then disp('    (Enter) or (S)kip, (V)iew');
         newline;

         disp('------------  ------  --------  --------  --------');
         if expand_files then disp('  -------------------------');
         newline;
      end;


      (* display file information *)
      disp(ljust(filename,12)+' '+
           rjust(ltoa(rec.uncompressed_size),7)+'  '+
           compression_methods[rec.compression_method]+'  '+
           format_date(rec.last_mod_file_date)+'  '+
           format_time(rec.last_mod_file_time));

      if not expand_files then
      begin
         skip_csize;
         newline;
         exit;
      end;


      (* determine action to perform on this member file *)
      action := 'S';
      disp('  Action? ');
      input(action,1);
      stoupper(action);

      case action[1] of
         'S':
            begin
               displn(' [Skip]');
               skip_csize;
               exit;
            end;

         'V','R':
            begin
               displn(' [View]');
               viewfile;

               header_present := false;

            {  make_log_entry('View archive member ('+extname
                                        +') from ('+remove_path(arcname)
                                        +')',true); }
            end;

         'Q':
            begin
               displn(' [Quit]');
               dos_lseek(zipfd,0,seek_end);
               exit;
            end;

         else
            displn(' [Type S, V or Q!]');
      end;
   end;
end;


(* ---------------------------------------------------------- *)
procedure process_central_file_header;
var
   n:             word;
   rec:           central_directory_file_header;
   filename:      string;
   extra:         string;
   comment:       string;

begin
   n := dos_read(zipfd,rec,sizeof(rec));
   get_string(rec.filename_length,filename);
   get_string(rec.extra_field_length,extra);
   get_string(rec.file_comment_length,comment);
  {dos_lseek(zipfd,rec.compressed_size,seek_cur);}
end;


(* ---------------------------------------------------------- *)
procedure process_end_central_dir;
var
   n:             word;
   rec:           end_central_dir_record;
   comment:       string;

begin
   n := dos_read(zipfd,rec,sizeof(rec));
   get_string(rec.zipfile_comment_length,comment);
end;


(* ---------------------------------------------------------- *)
procedure process_headers;
var
   sig:  longint;

begin
   dos_lseek(zipfd,0,seek_start);
   header_present := false;

   while (not dump_user) do
   begin
      if nomore or (dos_read(zipfd,sig,sizeof(sig)) <> sizeof(sig)) then
         exit
      else

      if sig = local_file_header_signature then
         process_local_file_header
      else

      if sig = central_file_header_signature then
         process_central_file_header
      else

      if sig = end_central_dir_signature then
      begin
         process_end_central_dir;
         exit;
      end

      else
      begin
         displn('Invalid Zipfile Header');
         exit;
      end;
   end;

end;


(* ---------------------------------------------------------- *)
procedure select_pattern;
begin
   default_pattern := '*.*';

   while true do
   begin
      newline;
      disp(remove_path(zipfn));
      get_def(': View member filespec:', enter_eq+default_pattern+'? ');
      
      get_nextpar;
      pattern := par;
      stoupper(pattern);
      if length(pattern) = 0 then
         pattern := default_pattern;

      if (pattern = 'none') or (pattern = 'Q') or dump_user then
         exit;
   
      process_headers;
   
      default_pattern := 'none';
   end;
end;


(* ---------------------------------------------------------- *)
procedure view_zipfile;
begin
   zipfd := dos_open(zipfn,open_read);
   if zipfd = dos_error then
      exit;

   if expand_files then
      select_pattern
   else
   begin
      pattern := '*.*';
      process_headers;
   end;

   dos_close(zipfd);
end;



(* ---------------------------------------------------------- *)
procedure process_zipfile(name: filenames);
var
   mem:    longint;

begin
   linenum := 1;
   cmdline := '';
   expand_files := false;
   zipfn := name;
   view_zipfile;

   newline;
   get_def('View text files in this zipfile:','(Enter)=yes? ');

   (* process text viewing if desired *)
   get_nextpar;
   if par[1] <> 'N' then
   begin
      expand_files := true;
      view_zipfile;
   end;
end;


(*
 * main program
 *
 *)

var
   i:    integer;
   n:    integer;
   par:  anystring;

begin
   gotoxy(60,scroll_line+1);
   reverseVideo;
   disp(' ZipTV ');

   SetScrollPoint(scroll_line);
   gotoxy(1,23);  lowVideo;
   linenum := 1;

   if paramcount = 0 then
   begin
      displn(version);
{     newline;
      displn('Courtesy of:  S.H.Smith  and  The Tool Shop BBS,  (602) 279-2673.');
      newline;  }

      displn('Usage:  ziptv [-Pport] [-Tminutes] [-Llines] [-Mlines] FILE[.zip]');

{     newline;
      displn('-Pn   enables com port COMn and monitors carrier');
      displn('-Tn   allows user to stay in program for n minutes');
      displn('-Ln   sets lines per screen');
      displn('-Mn   sets maximum lines per session');
}
      halt;
   end;

   for i := 1 to paramcount do
   begin
      par := paramstr(i);
      n := atoi(copy(par,3,5));

      if par[1] = '-' then
         case upcase(par[2]) of
            'P':  opencom(n);
            'T':  tlimit := n;      {time limit}
            'L':  user.pagelen := n;
            'M':  maxlines := n;
         end
      else

      begin
        if pos('.',par) = 0 then
            par := par + '.ZIP';

        if dos_exists(par) then
            process_zipfile(par)
        else
            displn('File not found: '+par);
      end;
   end;

   newline;
   displn(version);
   closecom;
end.



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