home *** CD-ROM | disk | FTP | other *** search
- Program Cruncher;
-
- {$M 10240, 0, 0}
- {$F+}
-
- { Crunch.Pas version 1.0 }
- { }
- { Written by Richard P. Byrne - November 1988 }
- { }
- { Compress a set of input files into an archive using Lempel-Ziv-Welch (LZW) }
- { compression techniques. }
-
- Uses Dos,
- Crt,
- MemAlloc,
- StrProcs;
-
- Const
-
- BufSize = 10240; { Use 10K file buffers }
- CrunchBits = 12; { Maximum code size of 12 bits }
- TableSize = 4095; { We'll need 4K entries in table }
- ClearCode = 256; { Code indicating code table has been cleared }
- FirstEntry = 257; { First free table entry }
- UnUsed = -1; { Prefix indicating an unused code table entry }
-
- StdAttr = $23; { Standard file attribute for DOS Find First/Next }
-
- CrcTab : array [0..255] of Word = { Table for CRC calculation }
- ( $0000, $C0C1, $C181, $0140, $C301, $03C0, $0280, $C241,
- $C601, $06C0, $0780, $C741, $0500, $C5C1, $C481, $0440,
- $CC01, $0CC0, $0D80, $CD41, $0F00, $CFC1, $CE81, $0E40,
- $0A00, $CAC1, $CB81, $0B40, $C901, $09C0, $0880, $C841,
- $D801, $18C0, $1980, $D941, $1B00, $DBC1, $DA81, $1A40,
- $1E00, $DEC1, $DF81, $1F40, $DD01, $1DC0, $1C80, $DC41,
- $1400, $D4C1, $D581, $1540, $D701, $17C0, $1680, $D641,
- $D201, $12C0, $1380, $D341, $1100, $D1C1, $D081, $1040,
- $F001, $30C0, $3180, $F141, $3300, $F3C1, $F281, $3240,
- $3600, $F6C1, $F781, $3740, $F501, $35C0, $3480, $F441,
- $3C00, $FCC1, $FD81, $3D40, $FF01, $3FC0, $3E80, $FE41,
- $FA01, $3AC0, $3B80, $FB41, $3900, $F9C1, $F881, $3840,
- $2800, $E8C1, $E981, $2940, $EB01, $2BC0, $2A80, $EA41,
- $EE01, $2EC0, $2F80, $EF41, $2D00, $EDC1, $EC81, $2C40,
- $E401, $24C0, $2580, $E541, $2700, $E7C1, $E681, $2640,
- $2200, $E2C1, $E381, $2340, $E101, $21C0, $2080, $E041,
- $A001, $60C0, $6180, $A141, $6300, $A3C1, $A281, $6240,
- $6600, $A6C1, $A781, $6740, $A501, $65C0, $6480, $A441,
- $6C00, $ACC1, $AD81, $6D40, $AF01, $6FC0, $6E80, $AE41,
- $AA01, $6AC0, $6B80, $AB41, $6900, $A9C1, $A881, $6840,
- $7800, $B8C1, $B981, $7940, $BB01, $7BC0, $7A80, $BA41,
- $BE01, $7EC0, $7F80, $BF41, $7D00, $BDC1, $BC81, $7C40,
- $B401, $74C0, $7580, $B541, $7700, $B7C1, $B681, $7640,
- $7200, $B2C1, $B381, $7340, $B101, $71C0, $7080, $B041,
- $5000, $90C1, $9181, $5140, $9301, $53C0, $5280, $9241,
- $9601, $56C0, $5780, $9741, $5500, $95C1, $9481, $5440,
- $9C01, $5CC0, $5D80, $9D41, $5F00, $9FC1, $9E81, $5E40,
- $5A00, $9AC1, $9B81, $5B40, $9901, $59C0, $5880, $9841,
- $8801, $48C0, $4980, $8941, $4B00, $8BC1, $8A81, $4A40,
- $4E00, $8EC1, $8F81, $4F40, $8D01, $4DC0, $4C80, $8C41,
- $4400, $84C1, $8581, $4540, $8701, $47C0, $4680, $8641,
- $8201, $42C0, $4380, $8341, $4100, $81C1, $8081, $4040 );
-
- Type
-
- { Define data types needed to implement a code table for LZW compression }
- HashRec = Record { Code Table record format... }
- First : Integer; { Addr of 1st suffix for this prefix }
- Next : Integer; { Addr of next suffix in chain }
- Suffix : Char; { Suffix character }
- end {HashRec};
- HashArray = Array[0..TableSize] of HashRec; { Define the code table }
- TablePtr = ^HashArray; { Allocate dynamically }
-
- { Define data types needed to implement input and output file buffers }
- BufArray = Array[1..BufSize] of byte;
- BufPtr = ^BufArray;
-
- { Define the structure of an archive file header }
- HdrRec = Record
- ArcMark : Byte;
- ComprType: Byte;
- Name : Array[1..13] of Byte;
- Size : LongInt;
- Date : Word;
- Time : Word;
- CRC : Word;
- Len : LongInt;
- MaxBits : Byte;
- end {HdrRec};
-
- { 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
- CrcVal : Word; { CRC calculation variable }
- InFileSpecs : Array[1..20] of String; { Input file specifications }
- MaxSpecs : Word; { Total number of filespecs to be archived }
- OutFileName : String; { Name of resulting archive 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 }
- HashTable : TablePtr; { Points to code table for LZW compression }
- FreeEntry : Integer; { Index of 1st free entry in code table }
- CodeSize : Byte; { Size of codes (in bits) currently being written }
- MaxCode : Word; { Largest code that can be written in CodeSize bits }
- ArcHdr : HdrRec; { Header for an archived file }
- HdrOffset : LongInt; { Offset within output file of the ARChive header }
- FirstCh : Boolean; { Flag indicating the START of a crunch operation }
- TableFull : Boolean; { Flag indicating a full symbol table }
- Ok_to_Clear : Boolean; { Flag indicating when it's "safe" to clear table }
- BytesIn : LongInt; { Count of input file bytes processed }
- BytesOut : LongInt; { Count of crunched bytes output }
-
- SaveByte : Byte; { Output code buffer }
- BitsUsed : Byte; { Index into output code buffer }
- CodeBytes : Byte; { Used for determining when to clear the code table }
-
- ListHead : NodePtr; { Pointer to head of linked list }
-
- { --------------------------------------------------------------------------- }
- { Houskeeping stuff (error routines and initialization of program variables) }
- { --------------------------------------------------------------------------- }
-
- Procedure Syntax;
- Begin
- Writeln;
- Writeln;
- Writeln('Crunch.Exe');
- Writeln(' Usage: Crunch arcfilename [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('Crunch.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, '.ARC');
-
- FindFirst(OutFileName, StdAttr, SearchBuf);
- If DosError = 0 then begin
- Writeln;
- 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};
-
- { --------------------------------------------------------------------------- }
-
- Procedure Initialize;
- Begin
-
- { Boolean flags ... all initialized to false }
- InputEof := FALSE; { Input end of file flag }
- TableFull := FALSE; { Code Table Full flag }
- Ok_to_Clear := FALSE; { "Safe" to clear code table flag }
-
- { The next two counters are initialized to 1 to prevent division by zero }
- BytesIn := 1; { Input byte counter }
- BytesOut := 1; { Output byte counter }
-
- OutBufIdx := 1; { Output buffer index }
-
- SaveByte := 0; { These 3 variables are used to construct output }
- BitsUsed := 0; { bytes from variable sized bit codes }
- CodeBytes := 0;
-
- end {Initialize};
-
- { --------------------------------------------------------------------------- }
- { 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;
- 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/$65/ { 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/$5D/ { 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/$26/ { 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}
- $31/$C0/ { xor ax,ax}
- $A0/>CODEBYTES/ { mov al,[>CodeBytes] ;Recalculate position in un-arc}
- $FE/$C0/ { inc al ; program's input code buffer}
- $31/$C9/ { xor cx,cx ; CodeBytes :=}
- $8A/$0E/>CODESIZE/ { mov cl,[>CodeSize] ; Succ(CodeBytes) MOD CodeSize}
- $F6/$F1/ { div cl }
- $88/$E0/ { mov al,ah}
- $A2/>CODEBYTES/ { mov [>CodeBytes],al}
- $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/$C2/ {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}
- {;}
- $C6/$06/>OK_TO_CLEAR/$00/ { mov byte [>Ok_to_Clear],0 ;Test if it's ok to clear the}
- $31/$C0/ { xor ax,ax ; Code table at this time}
- $A0/>CODEBYTES/ { mov al,[>CodeBytes]}
- $04/$02/ { add al,2}
- $3A/$06/>CODESIZE/ { cmp al,[>CodeSize] ;Ok to clear table?}
- $75/$05/ { jnz AllDone ;Nope, don't set flag TRUE}
- $C6/$06/>OK_TO_CLEAR/$01);{ mov byte [>Ok_to_Clear],1 ;Ok, set flag}
- {AllDone:}
-
- end {Putcode};
-
- { --------------------------------------------------------------------------- }
- { ARChive file support routines }
- { --------------------------------------------------------------------------- }
-
- Procedure Begin_ARC(ListPtr : NodePtr);
- { Write a dummy header to the archive. Include as much info as is currently }
- { known (we'll come back and fill in the rest later...) }
- Begin
- HdrOffset := FilePos(OutFile); { Save file position for later use }
- With ArcHdr do begin
- ArcMark := $1A; { Special archive ID byte }
- ComprType := 8; { Type of compression used }
- FillChar(Name, 13, #0); { Name of file to be compressed }
- Move(ListPtr^.Name[1], Name, Ord(ListPtr^.Name[0]));
- Size := 0; { Don't know crunched size yet... }
- Date := ListPtr^.Date; { Date of input file }
- Time := ListPtr^.Time; { Time of input file }
- CRC := 0; { Don't know CRC value yet... }
- Len := ListPtr^.Size; { Original size of input file }
- MaxBits := 12; { Largest code size used }
- end {with};
- Move(ArcHdr, OutBuf^, SizeOf(HdrRec)); { Put header into output buffer }
- Inc(OutBufIdx, SizeOf(HdrRec)); {...adjust buffer index accordingly }
- FlushOutput; { Write it now }
- End {Begin_ARC};
-
- { --------------------------------------------------------------------------- }
-
- Procedure Update_ARC_Header;
- { Update the archive's header with information that we now possess. Check to }
- { make sure that our cruncher actually produced a smaller file. If not, }
- { scrap the crunched data, modify the archive header accordingly, and just }
- { copy the input file to the output file (stowage method 2 - Storing). }
- Var
- EndPos : LongInt;
- Redo : Boolean;
- Begin
- Redo := FALSE; { Set REDO flag to false }
-
- EndPos := FilePos(OutFile); { Save current file position }
- Seek(OutFile, HdrOffset); { Rewind back to file header }
-
- { Update compressed size field }
- ArcHdr.Size := Succ(EndPos - HdrOffset - SizeOf(ArcHdr));
- ArcHdr.CRC := CrcVal; { Update CRC value }
-
- Redo := (ArcHdr.Size >= ArcHdr.Len); { Have we compressed the file? }
- If Redo then begin { No... }
- ArcHdr.ComprType := 2; { ...change stowage type }
- ArcHdr.Size := ArcHdr.Len; { ...update compressed size }
- end {if};
-
- Move(ArcHdr, OutBuf^, SizeOf(HdrRec)); { Move updated header to out buf }
- Inc(OutBufIdx, SizeOf(HdrRec)); { Adjust output buffer index }
- FlushOutput; { Write updated header to file }
-
- If Redo then begin
- { If compression didn't make a smaller file, then ... }
- Seek(OutFile, Pred(FilePos(OutFile))); { Rewind output file by 1 byte }
- 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_ARC_Header};
-
- { --------------------------------------------------------------------------- }
-
- Procedure End_ARC;
- { Write the special archive end of file marker bytes to the output file }
- Const
- ArcEofStr : Array[1..2] of Byte = ($1A, $00);
- Begin
- Move(ArcEofStr, OutBuf^, SizeOf(ArcEofStr));
- Inc(OutBufIdx, SizeOf(ArcEofStr));
- FlushOutput;
- end {End_ARC};
-
- { --------------------------------------------------------------------------- }
- { code Table support routines }
- { --------------------------------------------------------------------------- }
-
- Procedure GetTable;
- { Dynamically allocate space for our code table }
- Var
- MemError : Word;
- Begin
- MemError := Malloc(HashTable, Sizeof(HashTable^));
- If MemError <> 0 then
- Fatal(Concat('Cannot allocate workspace memory',
- #13#10,
- ' DOS Return Code on allocation request was ',
- IntStr(MemError, 0)));
- end {GetTable};
-
- { --------------------------------------------------------------------------- }
-
- Procedure DropTable;
- { Give ram used by code table back to DOS }
- Var
- MemError : Word;
- Begin
- MemError := Dalloc(HashTable);
- end {DropTable};
-
- { --------------------------------------------------------------------------- }
-
- Procedure ClearTable;
- { Clear the LZW Code Table }
- Begin
- Inline(
- {;}
- {; This routine may be called to clear the code table}
- {;}
- $B9/$00/$01/ { mov cx,256 ;clear the 1st 256 entries}
- $C4/$3E/>HASHTABLE/ { les di,[>HashTable] ;ES:DI points to first table entry}
- $89/$FE/ { mov si,di ;save offset in SI for later}
- $89/$C8/ {Looper: mov ax,cx}
- $48/ { dec ax ;get index of entry to clear}
- $BA/$05/$00/ { mov dx,5 ;entries are 5 bytes long}
- $F7/$E2/ { mul dx ;convert ax to an offset}
- $89/$F7/ { mov di,si ;get starting offset of table}
- $01/$C7/ { add di,ax ;es:di points to entry to be cleared}
- $B8/$FF/$FF/ { mov ax,-1 ;clear pointers to -1}
- $FC/ { cld}
- $AB/ { stosw}
- $AB/ { stosw}
- $AA/ { stosb}
- $E2/$EB); { loop Looper}
- {Done:}
-
- FreeEntry := FirstEntry; { Set first free entry }
- CodeSize := 9; { Reset codesize to minimum }
- MaxCode := 512; { Reset max code for codesize bits }
- TableFull := FALSE; { Reset flag indicating a full table }
- End {ClearTable};
-
- { --------------------------------------------------------------------------- }
-
- Function Table_Lookup( TargetPrefix : Integer;
- TargetSuffix : Char;
- 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, attempt to add the entry to the end }
- { of the table. If the table is already full, don't add the entry, just set }
- { the boolean variable TableFull to TRUE. }
- { --------------------------------------------------------------------------- }
- Begin
- Inline(
- {;}
- {; Lookup an entry in the code 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 add unmatched prefix/suffix to end of table.}
- {;}
- {;}
- {; 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 code 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/>HASHTABLE/ { les di,[>HashTable] ;code 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/$3F/ { jmp short Done}
- {;}
- $A1/>FREEENTRY/ {NotFound: mov word ax,[>FreeEntry] ;Index of next free table entry}
- $3D/>TABLESIZE/ { cmp word ax,>Tablesize ;Any room in table}
- $72/$07/ { jb NotFull ;Yes}
- $C6/$06/>TABLEFULL/$01/ { mov byte [>TableFull],1 ;Set flag}
- $EB/$24/ { jmp short SkipAdd ;Exit without adding entry}
- $80/$FF/$00/ {NotFull: cmp bh,0 ;Adding new entry}
- $74/$06/ { jz First ;New entry is 1st one in the chain}
- $26/$89/$45/$02/ { es: mov [di+2],ax ;Append new entry to chain}
- $EB/$03/ { jmp short InitNew}
- $26/$89/$05/ {First: es: mov [di],ax ;New entry is 1st one in chain}
- $BA/$05/$00/ {InitNew: mov dx,5}
- $F7/$E2/ { mul dx ;ax = offset of new entry in table}
- $89/$F7/ { mov di,si}
- $01/$C7/ { add di,ax ;es:di points at new entry}
- $B8/$FF/$FF/ { mov ax,-1}
- $FC/ { cld}
- $AB/ { stosw ;Init FIRST ptr to -1}
- $AB/ { stosw ;Init NEXT ptr to -1}
- $88/$D8/ { mov al,bl ;al=suffix for this new entry}
- $AA/ { stosb ;Init SUFFIX field}
- $FF/$06/>FREEENTRY/ { inc word [>FreeEntry] ;point FreeEntry to next empty slot}
- $C6/$46/$FF/$00/ {SkipAdd: 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};
-
- { --------------------------------------------------------------------------- }
- { The actual Crunching algorithm }
- { --------------------------------------------------------------------------- }
-
- Procedure Crunch(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 ... }
- ClearTable; { ... clear the code table, }
- 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 Table_Lookup(LastCode, Chr(Suffix), WhereFound) then begin
- { 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. If table not full, then was added as new entry. }
- PutCode(LastCode); { Write current LastCode code }
- LastCode := Suffix; { Reset LastCode code for new char }
-
- If (FreeEntry > MaxCode) and (CodeSize < CrunchBits) then begin
- { Time to increase the code size and change the max. code }
- Inc(CodeSize);
- MaxCode := MaxCode shl 1;
- end {if};
-
- If TableFull and Ok_to_Clear then begin
- { Decide if code table should be cleared }
- CrunchRatio := (100 * (BytesIn - BytesOut)) DIV BytesIn;
- If CrunchRatio < 40 then begin
- { Ok, lets clear the code table (adaptive reset) }
- PutCode(ClearCode);
- ClearTable;
- 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};
-
- { --------------------------------------------------------------------------- }
- { This routine packs the input before passing it to the Crunch routine }
- { --------------------------------------------------------------------------- }
-
- Procedure Pack_and_Crunch(Source : String);
- Type
- StateType = (NOHIST, INREP);
- Const
- DLE = #144; { Hex 90 }
- State : Statetype = NOHIST;
- LastChar : Char = #0;
- DupCount : Byte = 0;
- Var
- I, J : Word;
- Begin
- If FirstCh then begin
- State := NOHIST;
- CrcVal := 0;
- end {if};
- If Source = '' then begin { No work to do }
- If State = INREP then { but still have some left-overs }
- If DupCount >= 3 then begin { Only pack if it's worth doing }
- Crunch(Ord(DLE));
- Crunch(DupCount);
- end {then}
- else
- For J := 1 to Pred(DupCount) do
- Crunch(Ord(LastChar));
- end {then}
- else begin { We have work to do }
- I := 1;
- While I <= Length(Source) do begin
- Inc(BytesIn);
- CRCVal := ((CRCVal shr 8) and $00FF) xor CRCTab[(CRCVal xor Ord(Source[I])) and $00FF];
- Case State of
- NOHIST : begin
- If Source[I] = DLE then begin
- Crunch(Ord(DLE));
- Crunch(0);
- end {then}
- else begin
- If (Source[I] = LastChar) and (NOT FirstCh) then begin
- State := INREP;
- DupCount := 2;
- end {then}
- else
- Crunch(Ord(Source[I]));
- end {if};
- LastChar := Source[I];
- end {NOHIST};
- INREP : begin
- If (LastChar = Source[I]) and (DupCount < 255) then
- Inc(DupCount)
- else begin
- If DupCount >= 3 then begin { Only pack if worth it }
- Crunch(Ord(DLE));
- Crunch(DupCount);
- end {then}
- else
- For J := 1 to Pred(Dupcount) do
- Crunch(Ord(LastChar));
- If Source[I] = DLE then begin
- Crunch(Ord(DLE));
- Crunch(0);
- end {then}
- else
- Crunch(Ord(Source[I]));
- State := NOHIST;
- LastChar := Source[I];
- end {if};
- end {INREP};
- end {case};
- Inc(I);
- end {while};
- end {if};
-
- If Source = '' then { If input was null (ie. EOF on input) ... }
- Crunch(-1); { then tell the Crunch proc to do EOF stuff }
-
- end {Pack_and_Crunch};
-
- { --------------------------------------------------------------------------- }
- { 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 }
-
- 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);
- Pack_and_Crunch(OneString);
- end {if};
-
- end {while};
-
- Pack_and_Crunch(''); { 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 crunch!');
- 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, '...');
- Initialize;
- Begin_ARC(ListNode);
- Process_One_File;
- Update_ARC_Header;
- CloseInput;
- If ArcHdr.Len > 0 then
- ComprPct := Round((100.0 * (ArcHdr.Len - ArcHdr.Size)) / ArcHdr.Len)
- else
- ComprPct := 0;
- Writeln('Done (', ComprPct, '%)');
- end {then}
- else
- Writeln('Could not open ', ListNode^.Name, '. Skipping this file ...');
- ListNode := ListNode^.Next;
- end {while};
- End_ARC;
- CloseOutput;
- End {Process_All_Files};
-
- { --------------------------------------------------------------------------- }
- { Main Program (driver) }
- { --------------------------------------------------------------------------- }
-
- Begin
- Assign(Output, ''); { Reset output to DOS stdout device }
- Rewrite(Output);
- If ParamCheck then begin
- Initialize; { Initialize some variables }
- GetBuffers; { Allocate input and output buffers }
- GetTable; { Initialize the code table }
- Process_All_Files; { Crunch the file }
- DropBuffers; { Be polite and de-allocate Buffer memory and }
- DropTable; { code table memory }
- end {if};
- End.
-