home *** CD-ROM | disk | FTP | other *** search
- 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.
-