{ baziniai ‘od‘iai sudaro nedidelŠ programos dalŤ. Daug komentar—, skai‡i—.
  kintam—j— vardai ilgi }
Program Shrinker;

{$M 10240, 0, 0}
{$F+}

{ Shrink.Pas version 1.2  (C) Copyright 1989 by R. P. Byrne                   }
{                                                                             }
{   Compress a set of input files into a Zip file using Lempel-Ziv-Welch      }
{   (LZW) compression techniques (the "shrink" method).                       }

Uses  Dos,
      Crt,
      MemAlloc,
      StrProcs;

Const
   CopyRight = 'Shrink (C) Copyright 1989 by R. P. Byrne';
   Version   = 'Version 1.2 - Compiled on March 11, 1989';

Const

   BUFSIZE     =  10240;   { Use 10K file buffers                             }
   MINBITS     =      9;   { Starting code size of 9 bits                     }
   MAXBITS     =     13;   { Maximum code size of 13 bits                     }
   TABLESIZE   =   8191;   { We'll need 4K entries in table                   }
   SPECIAL     =    256;   { Special function code                            }
   INCSIZE     =      1;   { Code indicating a jump in code size              }
   CLEARCODE   =      2;   { Code indicating code table has been cleared      }
   FIRSTENTRY  =    257;   { First available table entry                      }
   UNUSED      =     -1;   { Prefix indicating an unused code table entry     }

   STDATTR     =    $23;   { Standard file attribute for DOS Find First/Next  }

Const
   LOCAL_FILE_HEADER_SIGNATURE = $04034B50;

Type
   Local_File_Header_Type = Record
                               Signature              :  LongInt;
                               Extract_Version_Reqd   :  Word;
                               Bit_Flag               :  Word;
                               Compress_Method        :  Word;
                               Last_Mod_Time          :  Word;
                               Last_Mod_Date          :  Word;
                               Crc32                  :  LongInt;
                               Compressed_Size        :  LongInt;
                               Uncompressed_Size      :  LongInt;
                               Filename_Length        :  Word;
                               Extra_Field_Length     :  Word;
                            end;

{ Define the Central Directory record types }

Const
   CENTRAL_FILE_HEADER_SIGNATURE = $02014B50;

Type
   Central_File_Header_Type = Record
                                 Signature            :  LongInt;
                                 MadeBy_Version       :  Word;
                                 Extract_Version_Reqd :  Word;
                                 Bit_Flag             :  Word;
                                 Compress_Method      :  Word;
                                 Last_Mod_Time        :  Word;
                                 Last_Mod_Date        :  Word;
                                 Crc32                :  LongInt;
                                 Compressed_Size      :  LongInt;
                                 Uncompressed_Size    :  LongInt;
                                 Filename_Length      :  Word;
                                 Extra_Field_Length   :  Word;
                                 File_Comment_Length  :  Word;
                                 Starting_Disk_Num    :  Word;
                                 Internal_Attributes  :  Word;
                                 External_Attributes  :  LongInt;
                                 Local_Header_Offset  :  LongInt;
                              End;

Const
   END_OF_CENTRAL_DIR_SIGNATURE = $06054B50;

Type
   End_of_Central_Dir_Type =  Record
                                 Signature               :  LongInt;
                                 Disk_Number             :  Word;
                                 Central_Dir_Start_Disk  :  Word;
                                 Entries_This_Disk       :  Word;
                                 Total_Entries           :  Word;
                                 Central_Dir_Size        :  LongInt;
                                 Start_Disk_Offset       :  LongInt;
                                 ZipFile_Comment_Length  :  Word;
                              end;

Const
   Crc_32_Tab : Array[0..255] of LongInt = (
$00000000, $77073096, $ee0e612c, $990951ba, $076dc419, $706af48f, $e963a535, $9e6495a3,
$0edb8832, $79dcb8a4, $e0d5e91e, $97d2d988, $09b64c2b, $7eb17cbd, $e7b82d07, $90bf1d91,
$1db71064, $6ab020f2, $f3b97148, $84be41de, $1adad47d, $6ddde4eb, $f4d4b551, $83d385c7,
$136c9856, $646ba8c0, $fd62f97a, $8a65c9ec, $14015c4f, $63066cd9, $fa0f3d63, $8d080df5,
$3b6e20c8, $4c69105e, $d56041e4, $a2677172, $3c03e4d1, $4b04d447, $d20d85fd, $a50ab56b,
$35b5a8fa, $42b2986c, $dbbbc9d6, $acbcf940, $32d86ce3, $45df5c75, $dcd60dcf, $abd13d59,
$26d930ac, $51de003a, $c8d75180, $bfd06116, $21b4f4b5, $56b3c423, $cfba9599, $b8bda50f,
$2802b89e, $5f058808, $c60cd9b2, $b10be924, $2f6f7c87, $58684c11, $c1611dab, $b6662d3d,
$76dc4190, $01db7106, $98d220bc, $efd5102a, $71b18589, $06b6b51f, $9fbfe4a5, $e8b8d433,
$7807c9a2, $0f00f934, $9609a88e, $e10e9818, $7f6a0dbb, $086d3d2d, $91646c97, $e6635c01,
$6b6b51f4, $1c6c6162, $856530d8, $f262004e, $6c0695ed, $1b01a57b, $8208f4c1, $f50fc457,
$65b0d9c6, $12b7e950, $8bbeb8ea, $fcb9887c, $62dd1ddf, $15da2d49, $8cd37cf3, $fbd44c65,
$4db26158, $3ab551ce, $a3bc0074, $d4bb30e2, $4adfa541, $3dd895d7, $a4d1c46d, $d3d6f4fb,
$4369e96a, $346ed9fc, $ad678846, $da60b8d0, $44042d73, $33031de5, $aa0a4c5f, $dd0d7cc9,
$5005713c, $270241aa, $be0b1010, $c90c2086, $5768b525, $206f85b3, $b966d409, $ce61e49f,
$5edef90e, $29d9c998, $b0d09822, $c7d7a8b4, $59b33d17, $2eb40d81, $b7bd5c3b, $c0ba6cad,
$edb88320, $9abfb3b6, $03b6e20c, $74b1d29a, $ead54739, $9dd277af, $04db2615, $73dc1683,
$e3630b12, $94643b84, $0d6d6a3e, $7a6a5aa8, $e40ecf0b, $9309ff9d, $0a00ae27, $7d079eb1,
$f00f9344, $8708a3d2, $1e01f268, $6906c2fe, $f762575d, $806567cb, $196c3671, $6e6b06e7,
$fed41b76, $89d32be0, $10da7a5a, $67dd4acc, $f9b9df6f, $8ebeeff9, $17b7be43, $60b08ed5,
$d6d6a3e8, $a1d1937e, $38d8c2c4, $4fdff252, $d1bb67f1, $a6bc5767, $3fb506dd, $48b2364b,
$d80d2bda, $af0a1b4c, $36034af6, $41047a60, $df60efc3, $a867df55, $316e8eef, $4669be79,
$cb61b38c, $bc66831a, $256fd2a0, $5268e236, $cc0c7795, $bb0b4703, $220216b9, $5505262f,
$c5ba3bbe, $b2bd0b28, $2bb45a92, $5cb36a04, $c2d7ffa7, $b5d0cf31, $2cd99e8b, $5bdeae1d,
$9b64c2b0, $ec63f226, $756aa39c, $026d930a, $9c0906a9, $eb0e363f, $72076785, $05005713,
$95bf4a82, $e2b87a14, $7bb12bae, $0cb61b38, $92d28e9b, $e5d5be0d, $7cdcefb7, $0bdbdf21,
$86d3d2d4, $f1d4e242, $68ddb3f8, $1fda836e, $81be16cd, $f6b9265b, $6fb077e1, $18b74777,
$88085ae6, $ff0f6a70, $66063bca, $11010b5c, $8f659eff, $f862ae69, $616bffd3, $166ccf45,
$a00ae278, $d70dd2ee, $4e048354, $3903b3c2, $a7672661, $d06016f7, $4969474d, $3e6e77db,
$aed16a4a, $d9d65adc, $40df0b66, $37d83bf0, $a9bcae53, $debb9ec5, $47b2cf7f, $30b5ffe9,
$bdbdf21c, $cabac28a, $53b39330, $24b4a3a6, $bad03605, $cdd70693, $54de5729, $23d967bf,
$b3667a2e, $c4614ab8, $5d681b02, $2a6f2b94, $b40bbe37, $c30c8ea1, $5a05df1b, $2d02ef8d
);

Type

   { Define data types needed to implement a code table for LZW compression   }
   CodeRec     =  Record                { Code Table record format...         }
                     Child   : Integer; { Addr of 1st suffix for this prefix  }
                     Sibling : Integer; { Addr of next suffix in chain        }
                     Suffix  : Byte;    { Suffix character                    }
                  end {CodeRec};
   CodeArray   =  Array[0..TABLESIZE] of CodeRec; { Define the code table     }
   TablePtr    =  ^CodeArray;                     { Allocate dynamically      }

   { Define data types needed to implement a free node list                   }
   FreeListPtr    =  ^FreeListArray;
   FreeListArray  =  Array[FIRSTENTRY..TABLESIZE] of Word;

   { Define data types needed to implement input and output file buffers      }
   BufArray    =  Array[1..BUFSIZE] of byte;
   BufPtr      =  ^BufArray;

   { Define the structure of a DOS Disk Transfer Area (DTA)                   }
   DTARec      =  Record
                     Filler   :  Array[1..21] of Byte;
                     Attr     :  Byte;
                     Time     :  Word;
                     Date     :  Word;
                     Size     :  LongInt;
                     Name     :  String[12];
                  end {DtaRec};

   { Define data types needed to implement a sorted singly linked list to     }
   { hold the names of all files to be compressed                             }
   NameStr      = String[12];
   PathStr      = String[64];
   NodePtr      = ^NameList;
   NameList     = Record                  { Linked list node structure...     }
                     Path : PathStr;      { Path of input file                }
                     Name : NameStr;      { Name of input file                }
                     Size : LongInt;      { Size in bytes of input file       }
                     Date : Word;         { Date stamp of input file          }
                     Time : Word;         { Time stamp of input file          }
                     Next : NodePtr;      { Next node in linked list          }
                  end {NameList};

Var
   InFileSpecs :  Array[1..20] of String;    { Input file specifications      }
   MaxSpecs    :  Word;          { Total number of filespecs to be Zipped     }
   OutFileName :  String;        { Name of resulting Zip file                 }

   InFile,                       { I/O file variables                         }
   OutFile     :  File;

   InBuf,                        { I/O buffers                                }
   OutBuf      :  BufPtr;
   InBufIdx,                     { Points to next char in buffer to be read   }
   OutBufIdx   :  Word;          { Points to next free space in output buffer }
   MaxInBufIdx :  Word;          { Count of valid chars in input buffer       }

   InputEof    :  Boolean;       { End of file indicator                      }

   Crc32Val    :  LongInt;       { CRC calculation variable                   }
   CodeTable   :  TablePtr;      { Points to code table for LZW compression   }

   FreeList    :  FreeListPtr;   { Table of free code table entries           }
   NextFree    :  Word;          { Index into free list table                 }

   ClearList   :  Array[0..1023] of Byte;  { Bit mapped structure used in     }
                                           {    during adaptive resets        }
   CodeSize    :  Byte;     { Size of codes (in bits) currently being written }
   MaxCode     :  Word;   { Largest code that can be written in CodeSize bits }

   LocalHdr    :  Local_File_Header_Type;
   LocalHdrOfs :  LongInt;  { Offset within output file of the local header   }
   CentralHdr  :  Central_File_Header_Type;
   EndHdr      :  End_of_Central_Dir_Type;

   FirstCh     :  Boolean;  { Flag indicating the START of a shrink operation }
   TableFull   :  Boolean;  { Flag indicating a full symbol table             }

   SaveByte    :  Byte;     { Output code buffer                              }
   BitsUsed    :  Byte;     { Index into output code buffer                   }

   BytesIn     :  LongInt;  { Count of input file bytes processed             }
   BytesOut    :  LongInt;  { Count of output bytes                           }

   ListHead    :  NodePtr;  { Pointer to head of linked list                  }

   TenPercent  :  LongInt;

{ --------------------------------------------------------------------------- }
{ Houskeeping stuff (error routines and initialization of program variables)  }
{ --------------------------------------------------------------------------- }

Procedure Syntax;
Begin
   Writeln('Shrink.Exe');
   Writeln('   Usage:   Shrink zipfilename [filespec [...]]');
   Writeln;
   Writeln('   A filespec is defined as [d:][\path\]name');
   Writeln('   where ''name'' may contain DOS wildcard characters.');
   Writeln;
   Writeln('   Multiple filespecs may be entered up to a maximum of 20.');
   Writeln;
   Writeln('   If no filespecs are entered, *.* is assumed.');
   Writeln;
   Halt(255);
end {Syntax};

{ --------------------------------------------------------------------------- }

Procedure Fatal(Msg : String);
Begin
   Writeln;
   Writeln;
   Writeln('Shrink.Exe');
   Writeln('   Error: ', Msg);
   Writeln('   Program halted');
   Writeln;
   Writeln;
   Halt(128);
end {Fatal};

{ --------------------------------------------------------------------------- }

Procedure AddToList(PathSpec : PathStr; DTA : DTARec);
{ Add an entry to a linked list of filenames to be crunched.  Maintain        }
{ sorted order (standard ASCII collating sequence) by filename                }
Var
   MemError : Word;
   NewNode  : NodePtr;
   Done     : Boolean;
   ListNode : NodePtr;
Begin
   { Allocate a new node                                                      }
   MemError := Malloc(NewNode, SizeOf(NewNode^));
   If MemError <> 0 then
      Fatal('Not enough memory to process all filenames!');

   { Populate the fields of the new node                                      }
   NewNode^.Path := PathSpec;
   NewNode^.Name := DTA.Name;
   NewNode^.Size := DTA.Size;
   NewNode^.Date := DTA.Date;
   NewNode^.Time := DTA.Time;
   NewNode^.Next := NIL;

   { Find the proper location in the list at which to insert the new node     }
   If ListHead = NIL then
      ListHead := NewNode
   else
      If DTA.Name < ListHead^.Name then begin
         NewNode^.Next := ListHead;
         ListHead      := NewNode;
      end {then}
      else begin
         Done     := FALSE;
         ListNode := ListHead;
         While NOT Done do begin
            If ListNode^.Name = DTA.Name then begin
               ListNode^.Path := PathSpec;
               MemError := Dalloc(NewNode);
               Done := TRUE;
            end {then}
            else
               If ListNode^.Next = NIL then begin
                  ListNode^.Next := NewNode;
                  Done := TRUE;
               end {then}
               else
                  If ListNode^.Next^.Name > DTA.Name then begin
                     NewNode^.Next  := ListNode^.Next;
                     ListNode^.Next := NewNode;
                     Done := TRUE;
                  end {then}
                  else
                     ListNode := ListNode^.Next;
         end {while};
      end {if};
end {AddToList};

{ --------------------------------------------------------------------------- }

Procedure GetNames;
{ Expand input file specifications.  Store the name of each file to be        }
{ compressed in a sorted, singly linked list                                  }
Var
   DosDTA   : DTARec;
   I        : Word;
   InPath   : String;
Begin
   ListHead := NIL;
   For I := 1 to MaxSpecs do begin   { Loop through all input file specs      }
      InPath := Upper(PathOnly(InFileSpecs[I]));
      FindFirst(InFileSpecs[I], STDATTR, SearchRec(DosDTA));
      While DosError = 0 do begin    { Loop through all matching files        }
         If (NOT SameFile(InPath + DosDTA.Name, OutFileName)) then
            AddToList(InPath, DosDTA);
         FindNext(SearchRec(DosDTA));
      end {while};
   end {for};
end {GetNames};

{ --------------------------------------------------------------------------- }

Function ParamCheck : Boolean;
{ Verify all command line parameters                                          }
Var
   SearchBuf : SearchRec;
   OutPath   : String;
   Ch        : Char;
   I         : Word;
Begin

   If ParamCount < 1 then Syntax;
   If ParamCount > 21 then begin
      Writeln('Too many command line parameters entered!');
      Syntax;
   end {if};

   OutFileName := Upper(ParamStr(1));
   If Pos('.', OutFileName) = 0 then
      OutFileName := Concat(OutFileName, '.ZIP');

   FindFirst(OutFileName, STDATTR, SearchBuf);
   If DosError = 0 then begin
      Write(OutFileName, ' already exists!  Overwrite it (Y/N, Enter=N)? ');
      Ch := ReadKey;
      Writeln(Ch);
      Writeln;
      If UpCase(Ch) <> 'Y' then begin
         Writeln;
         Writeln('Program aborted!');
         Halt;
      end {if};
   end {if};

   If ParamCount = 1 then begin
      InFileSpecs[1] := '*.*';
      MaxSpecs := 1;
   end {then}
   else
      For I := 2 to ParamCount do begin
         InFilespecs[Pred(I)] := ParamStr(I);
         MaxSpecs := Pred(I);
      end {for};

   GetNames;

End {ParamCheck};

{ --------------------------------------------------------------------------- }
{ Running 32 Bit CRC update function                                          }
{ --------------------------------------------------------------------------- }

Function UpdC32(Octet: Byte; Crc: LongInt) : LongInt;
Var
   L : LongInt;
   W : Array[1..4] of Byte Absolute L;
Begin

   UpdC32 := Crc_32_Tab[Byte(Crc XOR LongInt(Octet))] XOR ((Crc SHR 8) AND $00FFFFFF);

end {UpdC32};

{ --------------------------------------------------------------------------- }
{ I/O Support routines                                                        }
{ --------------------------------------------------------------------------- }

Procedure GetBuffers;
{ Allocate Input and Output buffers                                           }
Var
   MemError : Word;
Begin
   MemError := Malloc(InBuf, Sizeof(InBuf^));
   If MemError <> 0 then
      Fatal(Concat('Cannot allocate Input buffer',
                   #13#10,
                   '           DOS Return Code on allocation request was ',
                   IntStr(MemError, 0)));

   MemError := Malloc(OutBuf, Sizeof(OutBuf^));
   If MemError <> 0 then
      Fatal(Concat('Cannot allocate Output buffer',
                   #13#10,
                   '           DOS Return Code on allocation request was ',
                   IntStr(MemError, 0)));
End {GetBuffers};

{ --------------------------------------------------------------------------- }

Procedure DropBuffers;
{ Deallocate input and output buffers                                         }
Var
   MemError : Word;
Begin
   MemError := Dalloc(InBuf);
   MemError := Dalloc(OutBuf);
end {DropBuffers};

{ --------------------------------------------------------------------------- }

Procedure OpenOutput;
Var
   RC : Integer;
Begin
   Assign(OutFile, OutFileName);
   FileMode := 66;
   {$I-} ReWrite(OutFile, 1); {$I+}
   RC := IOResult;
   If RC <> 0 then
      Fatal(Concat('Cannot open output file',
                   #13#10,
                   '           Return Code was ',
                   IntStr(RC, 0)));
End {OpenOutput};

{ --------------------------------------------------------------------------- }

Function OpenInput(InFileName : String) : Boolean;
Var
   RC : Integer;
Begin
   Assign(InFile, InFileName);
   FileMode := 64;
   {$I-} Reset(InFile, 1); {$I+}
   OpenInput := (IOResult = 0);
End {OpenInput};

{ --------------------------------------------------------------------------- }

Procedure CloseOutput;
Var
   RC : Integer;
Begin
   {$I-} Close(OutFile) {$I+};
   RC := IOResult;
end {CloseOutput};

{ --------------------------------------------------------------------------- }

Procedure CloseInput;
Var
   RC : Integer;
Begin
   {$I-} Close(InFile)  {$I+};
   RC := IOResult;
end {CloseInput};

{ --------------------------------------------------------------------------- }

Procedure Read_Block;
{ Read a "block" of data into our our input buffer                            }
Begin
   BlockRead(InFile, InBuf^[1], SizeOf(InBuf^), MaxInBufIdx);
   If MaxInBufIdx = 0 then
      InputEof := TRUE
   else
      InputEOF := FALSE;
   InBufIdx := 1;
end {Read_Block};

{ --------------------------------------------------------------------------- }

Procedure Write_Block;
{ Write a block of data from the output buffer to our output file             }
Begin
   BlockWrite(OutFile, OutBuf^[1], Pred(OutBufIdx));
   OutBufIdx := 1;
end {Write_Block};

{ --------------------------------------------------------------------------- }

Procedure PutChar(B : Byte);
{ Put one character into our output buffer                                    }
Begin
   OutBuf^[OutBufIdx] := B;
   Inc(OutBufIdx);
   If OutBufIdx > SizeOf(OutBuf^) then
      Write_Block;
   Inc(BytesOut);
end {PutChar};

{ --------------------------------------------------------------------------- }

Procedure FlushOutput;
{ Write any data sitting in our output buffer to the output file              }
Begin
   If OutBufIdx > 1 then
      Write_Block;
End {FlushOutput};

{ --------------------------------------------------------------------------- }

Procedure PutCode(Code : Integer);
{ Assemble coded bytes for output                                             }
Var
   PutCharAddr : Pointer;
Begin
   PutCharAddr := @PutChar;

   Inline(
                            {;  Register useage:}
                            {;}
                            {;  AX - holds Code}
                            {;  BX - BH is a work register, BL holds SaveByte}
                            {;  CX - holds our loop counter CodeSize}
                            {;  DX - holds BitsUsed}
                            {;}
     $8B/$46/<Code/         {                mov         ax,[bp+<Code]}
     $31/$DB/               {                xor         bx,bx}
     $89/$D9/               {                mov         cx,bx}
     $89/$DA/               {                mov         dx,bx}
     $8A/$1E/>SaveByte/     {                mov         bl,[>SaveByte]}
     $8A/$0E/>CodeSize/     {                mov         cl,[>CodeSize]}
     $8A/$16/>BitsUsed/     {                mov         dl,[>BitsUsed]}
     $3D/$FF/$FF/           {                cmp         ax,-1               ;Any work to do?}
     $75/$0D/               {                jnz         Repeat              ;Yup, go do it}
     $80/$FA/$00/           {                cmp         dl,0                ;Any leftovers?}
     $74/$3A/               {                jz          AllDone             ;Nope, we're done}
     $53/                   {                push        bx                  ;Yup...push leftovers}
     $0E/                   {                push        cs}
     $FF/$96/>PutCharAddr/  {                call        [bp+>PutCharAddr]   ;   and send to output}
     $EB/$32/               {                jmp short   AllDone}
                            {;}
     $30/$FF/               {Repeat:         xor         bh,bh               ;Zero out BH}
     $D1/$D8/               {                rcr         ax,1                ;Get low order bit into CY flag}
     $73/$02/               {                jnc         SkipBit             ;Was the bit set?}
     $FE/$C7/               {                inc         bh                  ;Yes, xfer to BH}
     $87/$D1/               {SkipBit:        xchg        cx,dx               ;Swap CX & DX}
     $D2/$E7/               {                shl         bh,cl               ;Shift bit over}
     $87/$D1/               {                xchg        cx,dx               ;Put CX & DX back where they were}
     $42/                   {                inc         dx                  ;Bump count of bit positions used}
     $08/$FB/               {                or          bl,bh               ;Transfer bit to output byte (SaveByte)}
     $83/$FA/$08/           {                cmp         dx,8                ;Full byte yet?}
     $72/$12/               {                jb          GetNext             ;Nope, go get more code bits}
     $50/                   {                push        ax                  ;Yup, save regs in preparation}
     $53/                   {                push        bx                  ;    for call to output routine}
     $51/                   {                push        cx}
     $52/                   {                push        dx}
     $53/                   {                push        bx                  ;Push byte to output onto stack}
     $0E/                   {                push        cs}
     $FF/$96/>PutCharAddr/  {                call        [bp+>PutCharAddr]   ;   and call the output routine}
     $5A/                   {                pop         dx}
     $59/                   {                pop         cx}
     $5B/                   {                pop         bx}
     $58/                   {                pop         ax}
     $31/$DB/               {                xor         bx,bx               ;Prepare SaveByte for next byte}
     $89/$DA/               {                mov         dx,bx               ;Set BitsUsed to zero}
     $E2/$D6/               {GetNext:        loop        Repeat              ;Repeat for all code bits}
                            {;}
     $88/$1E/>SaveByte/     {                mov         [>SaveByte],bl      ;Put SaveByte and BitsUsed}
     $88/$16/>BitsUsed);    {                mov         [>BitsUsed],dl      ;   back in memory}
                            {;}
                            {AllDone:}

end {Putcode};

{ --------------------------------------------------------------------------- }
{ The following routines are used to allocate, initialize, and de-allocate    }
{ various dynamic memory structures used by the LZW compression algorithm     }
{ --------------------------------------------------------------------------- }

Procedure Build_Data_Structures;
Var
   Code  :  Word;
Begin
   Code  := Malloc(CodeTable, SizeOf(CodeTable^)) OR
            Malloc(FreeList,  SizeOf(FreeList^ ));
   If Code <> 0 then
      Fatal('Not enough memory to allocate LZW data structures!');
end {Build_Data_Structures};

{ --------------------------------------------------------------------------- }

Procedure Destroy_Data_Structures;
Var
   Code  :  Word;
Begin
   Code := Dalloc(CodeTable);
   Code := Dalloc(FreeList);
end {Destroy_Data_Structures};

{ --------------------------------------------------------------------------- }

Procedure Initialize_Data_Structures;
Var
   I  :  Word;
Begin
   For I := 0 to TableSize do begin
      With CodeTable^[I] do begin
         Child     := -1;
         Sibling   := -1;
         If I <= 255 then
            Suffix := I;
      end {with};
      If I >= 257 then
         FreeList^[I] := I;
   end {for};

   NextFree  := FIRSTENTRY;
   TableFull := FALSE;

end {Initialize_Data_Structures};

{ --------------------------------------------------------------------------- }
{ The following routines handle manipulation of the LZW Code Table            }
{ --------------------------------------------------------------------------- }

Procedure Prune(Parent : Word);
{ Prune leaves from a subtree - Note: this is a recursive procedure }
Var
   CurrChild   : Integer;
   NextSibling : Integer;
Begin
   CurrChild := CodeTable^[Parent].Child;
   { Find first Child that has descendants .. clear any that don't }
   While (CurrChild <> -1) AND (CodeTable^[CurrChild].Child = -1) do begin
      CodeTable^[Parent].Child := CodeTable^[CurrChild].Sibling;
      CodeTable^[CurrChild].Sibling := -1;
      { Turn on ClearList bit to indicate a cleared entry }
      ClearList[CurrChild DIV 8] := (ClearList[CurrChild DIV 8] OR (1 SHL (CurrChild MOD 8)));
      CurrChild := CodeTable^[Parent].Child;
   end {while};

   If CurrChild <> -1 then begin   { If there are any children left ...}
      Prune(CurrChild);
      NextSibling := CodeTable^[CurrChild].Sibling;
      While NextSibling <> -1 do begin
         If CodeTable^[NextSibling].Child = -1 then begin
            CodeTable^[CurrChild].Sibling := CodeTable^[NextSibling].Sibling;
            CodeTable^[NextSibling].Sibling := -1;
            { Turn on ClearList bit to indicate a cleared entry }
            ClearList[NextSibling DIV 8] := (ClearList[NextSibling DIV 8] OR (1 SHL (NextSibling MOD 8)));
            NextSibling := CodeTable^[CurrChild].Sibling;
         end {then}
         else begin
            CurrChild := NextSibling;
            Prune(CurrChild);
            NextSibling := CodeTable^[CurrChild].Sibling;
         end {if};
      end {while};
   end {if};

end {Prune};

{ --------------------------------------------------------------------------- }

Procedure Clear_Table;
Var
   Node : Word;
Begin
   FillChar(ClearList, SizeOf(ClearList), $00);
   { Remove all leaf nodes by recursively pruning subtrees}
   For Node := 0 to 255 do
      Prune(Node);
   { Next, re-initialize our list of free table entries }
   NextFree := Succ(TABLESIZE);
   For Node := TABLESIZE downto FIRSTENTRY do begin
      If (ClearList[Node DIV 8] AND (1 SHL (Node MOD 8))) <> 0 then begin
         Dec(NextFree);
         FreeList^[NextFree] := Node;
      end {if};
   end {for};
   If NextFree <= TABLESIZE then
      TableFull := FALSE;
end {Clear_Table};

{ --------------------------------------------------------------------------- }

Procedure Table_Add(Prefix : Word; Suffix : Byte);
Var
   FreeNode : Word;
Begin
   If NextFree <= TABLESIZE then begin
      FreeNode := FreeList^[NextFree];
      Inc(NextFree);
      CodeTable^[FreeNode].Child := -1;
      CodeTable^[FreeNode].Sibling := -1;
      CodeTable^[FreeNode].Suffix := Suffix;
      If CodeTable^[Prefix].Child  = -1 then
         CodeTable^[Prefix].Child := FreeNode
      else begin
         Prefix := CodeTable^[Prefix].Child;
         While CodeTable^[Prefix].Sibling <> -1 do
            Prefix := CodeTable^[Prefix].Sibling;
         CodeTable^[Prefix].Sibling := FreeNode;
      end {if};
   end {if};

   If NextFree > TABLESIZE then
      TableFull := TRUE;
end {Table_Add};

{ --------------------------------------------------------------------------- }

Function Table_Lookup(    TargetPrefix : Integer;
                          TargetSuffix : Byte;
                      Var FoundAt      : Integer   ) : Boolean;
{ --------------------------------------------------------------------------- }
{ Search for a Prefix:Suffix pair in our Symbol table.  If found, return the  }
{ index value where found.  If not found, return FALSE and set the VAR parm   }
{ FoundAt to -1.                                                              }
{ --------------------------------------------------------------------------- }
Begin
   Inline(
                            {;}
                            {; Lookup an entry in the Hash Table.  If found, return TRUE and set the VAR}
                            {; parameter FoundAt with the index of the entry at which the match was found.}
                            {; If not found, return FALSE and plug a -1 into the FoundAt var.}
                            {;}
                            {;}
                            {; Register usage:}
                            {;   AX - varies                     BL - holds target suffix character}
                            {;                                   BH - If search fails, determines how to}
                            {;                                        add the new entry}
                            {;   CX - not used                   DX - holds size of 1 table entry (5)}
                            {;   DI - varies                     SI - holds offset of 1st table entry}
                            {;   ES - seg addr of hash table     DS - program's data segment}
                            {;}
                            {;}
     $8A/$5E/<TargetSuffix/ {            mov byte    bl,[bp+<TargetSuffix]   ;Target Suffix character}
     $8B/$46/<TargetPrefix/ {            mov word    ax,[bp+<TargetPrefix]   ;Index into table}
     $BA/$05/$00/           {            mov         dx,5                    ;5 byte table entries}
     $F7/$E2/               {            mul         dx                      ;AX now an offset into table}
     $C4/$3E/>CodeTable/    {            les         di,[>CodeTable]         ;Hash table address}
     $89/$FE/               {            mov         si,di                   ;save offset in SI}
     $01/$C7/               {            add         di,ax                   ;es:di points to table entry}
                            {;}
     $B7/$00/               {            mov         bh,0                    ;Chain empty flag (0=empty)}
     $26/$83/$3D/$FF/       {        es: cmp word    [di],-1                 ;Anything on the chain?}
     $74/$33/               {            jz          NotFound                ;Nope, search fails}
     $B7/$01/               {            mov         bh,1                    ;Chain empty flag (1=not empty)}
                            {;}
     $26/$8B/$05/           {        es: mov word    ax,[di]                 ;Get index of 1st entry in chain}
     $89/$46/<TargetPrefix/ {Loop:       mov word    [bp+<TargetPrefix],ax   ;Save index for later}
     $BA/$05/$00/           {            mov         dx,5}
     $F7/$E2/               {            mul         dx                      ;convert index to offset}
     $89/$F7/               {            mov         di,si                   ;es:di points to start of table}
     $01/$C7/               {            add         di,ax                   ;es:di points to table entry}
                            {;}
     $26/$3A/$5D/$04/       {        es: cmp byte    bl,[di+4]               ;match on suffix?}
     $74/$0D/               {            jz          Found                   ;Yup, search succeeds}
                            {;}
     $26/$83/$7D/$02/$FF/   {        es: cmp word    [di+2],-1               ;any more entries in chain?}
     $74/$15/               {            jz          NotFound                ;nope, search fails}
                            {;}
     $26/$8B/$45/$02/       {        es: mov word    ax,[di+2]               ;get index of next chain entry}
     $EB/$E1/               {            jmp short   Loop                    ;   and keep searching}
                            {;}
     $C6/$46/$FF/$01/       {Found:      mov byte    [bp-1],1                ;return TRUE}
     $C4/$7E/<FoundAt/      {            les         di,[bp+<FoundAt]        ;get address of Var parameter}
     $8B/$46/<TargetPrefix/ {            mov word    ax,[bp+<TargetPrefix]   ;get index of entry where found}
     $26/$89/$05/           {        es: mov         [di],ax                 ;and store it}
     $EB/$0C/               {            jmp short   Done}
                            {;}
     $C6/$46/$FF/$00/       {NotFound:   mov byte    [bp-1],0                ;return FALSE}
     $C4/$7E/<FoundAt/      {            les         di,[bp+<FoundAt]        ;get address of Var parameter}
     $26/$C7/$05/$FF/$FF);  {        es: mov word    [di],-1                 ;and store a -1 in it}
                            {;}
                            {Done:}
                            {;}

end {Table_Lookup};

{ --------------------------------------------------------------------------- }
{ These routines build the Header structures for the ZIP file                 }
{ --------------------------------------------------------------------------- }

Procedure Begin_ZIP(ListPtr : NodePtr);
{ Write a dummy header to the zip.  Include as much info as is currently      }
{ known (we'll come back and fill in the rest later...)                       }
Begin
   LocalHdrOfs := FilePos(OutFile);       { Save file position for later use  }
   With LocalHdr do begin
      Signature := LOCAL_FILE_HEADER_SIGNATURE;
      Extract_Version_Reqd := 10;
      Bit_Flag := 0;
      Compress_Method := 1;
      Last_Mod_Time := ListPtr^.Time;
      Last_Mod_Date := ListPtr^.Date;
      Crc32 := 0;
      Compressed_Size := 0;
      Uncompressed_Size := ListPtr^.Size;
      FileName_Length := Length(ListPtr^.Name);
      Extra_Field_Length := 0;
   end {with};
   Move(LocalHdr, OutBuf^, SizeOf(LocalHdr)); { Put header into output buffer }
   OutBufIdx := Succ(SizeOf(LocalHdr));   {...adjust buffer index accordingly }
   Move(ListPtr^.Name[1], OutBuf^[OutBufIdx], Length(ListPtr^.Name));
   Inc(OutBufIdx, Length(ListPtr^.Name));
   FlushOutput;                           { Write it now                      }
End {Begin_ZIP};

{ --------------------------------------------------------------------------- }

Procedure Update_ZIP_Header(ListPtr : NodePtr);
{ Update the zip's local header with information that we now possess.  Check  }
{ to make sure that our shrinker actually produced a smaller file.  If not,   }
{ scrap the shrunk data, modify the local header accordingly, and just copy   }
{ the input file to the output file (compress method 0 - Storing).            }
Var
   EndPos : LongInt;
   Redo   : Boolean;
Begin
   Redo := FALSE;                            { Set REDO flag to false         }
   EndPos := FilePos(OutFile);               { Save current file position     }

   Seek(OutFile, LocalHdrOfs);               { Rewind back to file header     }

   With LocalHdr do begin
                                             { Update compressed size field   }
      Compressed_Size := EndPos - LocalHdrOfs - SizeOf(LocalHdr) - Filename_Length;
      Crc32 := Crc32Val;                     { Update CRC value               }
                                             { Have we compressed the file?   }
      Redo := (Compressed_Size >= Uncompressed_Size);
      If Redo then begin                     { No...                          }
         Compress_Method := 0;                  { ...change stowage type      }
         Compressed_Size := Uncompressed_Size;  { ...update compressed size   }
      end {if};

   end {with};

   Move(LocalHdr, OutBuf^, SizeOf(LocalHdr)); { Put header into output buffer }
   OutBufIdx := Succ(SizeOf(LocalHdr));   {...adjust buffer index accordingly }
   Move(ListPtr^.Name[1], OutBuf^[OutBufIdx], Length(ListPtr^.Name));
   Inc(OutBufIdx, Length(ListPtr^.Name));
   FlushOutput;                           { Write it now                      }

   If Redo then begin
      { If compression didn't make a smaller file, then ...                   }
      Seek(InFile, 0);                       { Rewind the input file          }
      InputEof := FALSE;                     { Reset EOF indicator            }
      Read_Block;                            { Prime the input buffer         }
      While NOT InputEof do begin            { Copy input to output           }
         BlockWrite(OutFile, InBuf^, MaxInBufIdx);
         Read_Block;
      end {while};
      Truncate(Outfile);                     { Truncate output file           }
   end {then}
   else begin
      { Compression DID make a smaller file ...                               }
      Seek(OutFile, FileSize(OutFile));   { Move output file pos back to eof  }
   end {if};
End {Update_ZIP_Header};

{ --------------------------------------------------------------------------- }

Procedure Build_Central_Dir;
{ Revisit each local file header to build the Central Directory.  When done,  }
{ build the End of Central Directory record.                                  }
Var
   BytesRead : Word;
   SavePos   : LongInt;
   HdrPos    : LongInt;
   CenDirPos : LongInt;
   Entries   : Word;
   FileName  : String;
Begin
   Entries := 0;
   CenDirPos := FilePos(Outfile);
   Seek(OutFile, 0);             { Rewind output file }
   HdrPos := FilePos(OutFile);
   BlockRead(OutFile, LocalHdr, SizeOf(LocalHdr), BytesRead);
   Repeat
      BlockRead(OutFile, FileName[1], LocalHdr.FileName_Length, BytesRead);
      FileName[0] := Chr(LocalHdr.FileName_Length);
      SavePos := FilePos(OutFile);

      With CentralHdr do begin
         Signature := CENTRAL_FILE_HEADER_SIGNATURE;
         MadeBy_Version := LocalHdr.Extract_Version_Reqd;
         Move(LocalHdr.Extract_Version_Reqd, Extract_Version_Reqd, 26);
         File_Comment_Length := 0;
         Starting_Disk_Num := 0;
         Internal_Attributes := 0;
         External_Attributes := ARCHIVE;
         Local_Header_Offset := HdrPos;
         Seek(OutFile, FileSize(OutFile));
         BlockWrite(Outfile, CentralHdr, SizeOf(CentralHdr));
         BlockWrite(OutFile, FileName[1], Length(FileName));
         Inc(Entries);
      end {with};

      Seek(OutFile, SavePos + LocalHdr.Compressed_Size);
      HdrPos := FilePos(OutFile);
      BlockRead(OutFile, LocalHdr, SizeOf(LocalHdr), BytesRead);
   Until LocalHdr.Signature = CENTRAL_FILE_HEADER_SIGNATURE;

   Seek(OutFile, FileSize(OutFile));

   With EndHdr do begin
      Signature := END_OF_CENTRAL_DIR_SIGNATURE;
      Disk_Number := 0;
      Central_Dir_Start_Disk := 0;
      Entries_This_Disk := Entries;
      Total_Entries := Entries;
      Central_Dir_Size := CenDirPos - FileSize(OutFile);
      Start_Disk_Offset := CenDirPos;
      ZipFile_Comment_Length := 0;
      BlockWrite(Outfile, EndHdr, SizeOf(EndHdr));
   end {with};

end {Build_Central_Dir};

{ --------------------------------------------------------------------------- }
{ The actual Crunching algorithm                                              }
{ --------------------------------------------------------------------------- }

Procedure Shrink(Suffix : Integer);
Const
   LastCode    : Integer = 0;   { Typed constant, so value retained across calls }
Var
   WhereFound   : Integer;
   CrunchRatio  : LongInt;
Begin
   If FirstCh then begin         { If just getting started ...                }
      SaveByte := $00;           { Initialize our output code buffer          }
      BitsUsed := 0;
      CodeSize := MINBITS;       {     Initialize code size to minimum        }
      MaxCode  := (1 SHL CodeSize) - 1;
      LastCode := Suffix;        {     get first character from input,        }
      FirstCh  := FALSE;         {     and reset the first char flag.         }
   end {then}
   else begin
      If Suffix <> -1 then begin { If there's work to do ...                  }
         If TableFull then begin
            { Ok, lets clear the code table (adaptive reset)            }
            Putcode(LastCode);
            PutCode(SPECIAL);
            Putcode(CLEARCODE);
            Clear_Table;
            Table_Add(LastCode, Suffix);
            LastCode := Suffix;
         end {then}
         else begin
            If Table_Lookup(LastCode, Suffix, WhereFound) then begin
               { If LastCode:Suffix pair is found in the code table, then ...    }
               { ... set LastCode to the entry where the pair is located         }
               LastCode  := WhereFound;
            end {then}
            else begin
               { Not in table                                                    }
               PutCode(LastCode);            { Write current LastCode code       }
               Table_Add(LastCode, Suffix);  { Attempt to add to code table      }
               LastCode := Suffix;           { Reset LastCode code for new char  }
               If (FreeList^[NextFree] > MaxCode) and (CodeSize < MaxBits) then begin
                  { Time to increase the code size and change the max. code      }
                  PutCode(SPECIAL);
                  PutCode(INCSIZE);
                  Inc(CodeSize);
                  MaxCode := (1 SHL CodeSize) -1;
               end {if};
            end {if};
         end {if};
      end {then}
      else begin                    { Nothing to crunch...must be EOF on input   }
         PutCode(LastCode);         { Write last prefix code                     }
         PutCode(-1);               { Tell putcode to flush remaining bits       }
         FlushOutput;               { Flush our output buffer                    }
      end {if};
   end {if};
end {Crunch};

{ --------------------------------------------------------------------------- }

Procedure Process_Input(Source : String);
Var
   I       : Word;
   PctDone : Integer;
Begin
   If Source = '' then
      Shrink(-1)
   else
      For I := 1 to Length(Source) do begin
         Inc(BytesIn);
         If (Pred(BytesIn) MOD TenPercent) = 0 then begin
            PctDone := Round( 100 * ( BytesIn / FileSize(InFile)));
            GotoXY(WhereX - 4, WhereY);
            Write(PctDone:3, '%');
         end {if};
         CRC32Val := UpdC32(Ord(Source[I]), CRC32Val);
         Shrink(Ord(Source[I]));
      end {for};
end {Process_Input};

{ --------------------------------------------------------------------------- }
{ This routine handles processing for one input file                          }
{ --------------------------------------------------------------------------- }

Procedure Process_One_File;
Var
   OneString : String;
   Remaining : Word;
Begin

   Read_Block;                { Prime the input buffer                        }
   FirstCh   := TRUE;         { 1st character flag for Crunch procedure       }
   Crc32Val  := $FFFFFFFF;

   TenPercent := FileSize(InFile) DIV 10;

   While NOT InputEof do begin
      Remaining := Succ(MaxInBufIdx - InBufIdx);

      If Remaining > 255 then
         Remaining := 255;

      If Remaining = 0 then
         Read_Block
      else begin
         Move(InBuf^[InBufIdx], OneString[1], Remaining);
         OneString[0] := Chr(Remaining);
         Inc(InBufIdx, Remaining);
         Process_Input(OneString);
      end {if};

   end {while};

   Crc32Val := NOT Crc32Val;

   Process_Input('');     { This forces EOF processing }

end {Process_One_File};

{ --------------------------------------------------------------------------- }

Procedure Process_All_Files;
Var
   InPath   : String;
   ComprPct : Word;
   ListNode : NodePtr;
Begin
   If ListHead = NIL then begin
      Writeln;
      Writeln('There are no files to shrink!');
      Writeln;
      Halt;
   end {if};

   OpenOutput;

   ListNode := ListHead;
   While ListNode <> NIL do begin
      If OpenInput(Concat(ListNode^.Path, ListNode^.Name)) then begin
         Write('Processing ', ListNode^.Name, ' ');
         While WhereX < 28 do
            Write('.');
         Write('    ');
         BytesIn := 1; BytesOut := 1;
         TenPercent := FileSize(InFile) DIV 10;
         Initialize_Data_Structures;
         Begin_ZIP(ListNode);
         Process_One_File;
         Update_ZIP_Header(ListNode);
         CloseInput;
         If LocalHdr.Uncompressed_Size > 0 then
            ComprPct := Round((100.0 * (LocalHdr.Uncompressed_Size - LocalHdr.Compressed_Size)) / LocalHdr.Uncompressed_Size)
         else
            ComprPct := 0;
         GotoXY(WhereX - 4, WhereY);
         ClrEol;
         Writeln(' done (compression = ', ComprPct:2, '%)');
      end {then}
      else
         Writeln('Could not open ', ListNode^.Name, '.  Skipping this file ...');
      ListNode := ListNode^.Next;
   end {while};
   Build_Central_Dir;
   CloseOutput;
End {Process_All_Files};

{ --------------------------------------------------------------------------- }
{ Main Program (driver)                                                       }
{ --------------------------------------------------------------------------- }

Begin
   Assign(Output, '');        { Reset output to DOS stdout device             }
   Rewrite(Output);
   Writeln;
   Writeln(Copyright);
   Writeln(Version);
   Writeln;
   If ParamCheck then begin
      GetBuffers;              { Allocate input and output buffers ...        }
      Build_Data_Structures;   { ... and other data structures required       }
      Process_All_Files;       { Crunch the file                              }
      DropBuffers;             { Be polite and de-allocate Buffer memory and  }
      Destroy_Data_Structures; {    other allocated data structures           }
   end {if};
End.
