(* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is TurboPower Abbrevia * * The Initial Developer of the Original Code is * TurboPower Software * * Portions created by the Initial Developer are Copyright (C) 1997-2002 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * ***** END LICENSE BLOCK ***** *) {*********************************************************} {* ABBREVIA: AbDfBase.pas *} {*********************************************************} {* Deflate base unit *} {*********************************************************} unit AbDfBase; {$I AbDefine.inc} interface uses SysUtils, Classes; type PAbDfLongintList = ^TAbDfLongintList; TAbDfLongintList = array [0..pred(MaxInt div sizeof(longint))] of longint; const dfc_CodeLenCodeLength = 7; dfc_LitDistCodeLength = 15; dfc_MaxCodeLength = 15; const dfc_MaxMatchLen = 258; {lengths are 3..258 for deflate} dfc_MaxMatchLen64 = 64 * 1024; {lengths are 3..65536 for deflate64} const dfc_LitExtraOffset = 257; dfc_LitExtraBits : array [0..30] of byte = (0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 16, 99, 99); { note: the last two are required to avoid going beyond the end} { of the array when generating static trees} dfc_DistExtraOffset = 0; dfc_DistExtraBits : array [0..31] of byte = (0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14); { note: the last two are only use for deflate64} dfc_LengthBase : array [0..28] of word = (3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 3); { note: the final 3 is correct for deflate64; for symbol 285,} { lengths are stored as (length - 3)} { for deflate it's very wrong, but there's special code in} { the (de)compression code to cater for this} dfc_DistanceBase : array [0..31] of word = (1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 32769, 49153); dfc_CodeLengthIndex : array [0..18] of byte = (16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15); const dfc_CanUseStored = $01; dfc_CanUseStatic = $02; dfc_CanUseDynamic = $04; dfc_UseLazyMatch = $08; dfc_UseDeflate64 = $10; dfc_UseAdler32 = $20; dfc_CanUseHuffman = dfc_CanUseStatic or dfc_CanUseDynamic; dfc_TestOnly = $40000000; type TAbProgressStep = procedure (aPercentDone : integer) of object; {-progress metering of deflate/inflate; abort with AbortProgress} TAbDeflateHelper = class private FAmpleLength : longint; FChainLength : longint; FLogFile : string; FMaxLazy : longint; FOnProgressStep : TAbProgressStep; FOptions : longint; FPartSize : Int64; FSizeCompressed : Int64; FSizeNormal : Int64; FStreamSize : Int64; FWindowSize : longint; FZipOption : AnsiChar; protected procedure dhSetAmpleLength(aValue : longint); procedure dhSetChainLength(aValue : longint); procedure dhSetLogFile(const aValue : string); procedure dhSetMaxLazy(aValue : longint); procedure dhSetOnProgressStep(aValue : TAbProgressStep); procedure dhSetOptions(aValue : longint); procedure dhSetWindowSize(aValue : longint); procedure dhSetZipOption(aValue : AnsiChar); public constructor Create; procedure Assign(aHelper : TAbDeflateHelper); property AmpleLength : longint read FAmpleLength write dhSetAmpleLength; property ChainLength : longint read FChainLength write dhSetChainLength; property LogFile : string read FLogFile write dhSetLogFile; property MaxLazyLength : longint read FMaxLazy write dhSetMaxLazy; property Options : longint read FOptions write dhSetOptions; property PartialSize : Int64 read FPartSize write FPartSize; property PKZipOption : AnsiChar read FZipOption write dhSetZipOption; property StreamSize : Int64 read FStreamSize write FStreamSize; property WindowSize : longint read FWindowSize write dhSetWindowSize; property CompressedSize : Int64 read FSizeCompressed write FSizeCompressed; property NormalSize : Int64 read FSizeNormal write FSizeNormal; property OnProgressStep : TAbProgressStep read FOnProgressStep write dhSetOnProgressStep; end; type TAbLineDelimiter = (ldCRLF, ldLF); TAbLogger = class(TStream) private FBuffer : PAnsiChar; FCurPos : PAnsiChar; FLineDelim : TAbLineDelimiter; FStream : TFileStream; protected function logWriteBuffer : boolean; public constructor Create(const aLogName : string); destructor Destroy; override; function Read(var Buffer; Count : longint) : longint; override; function Seek(const Offset : Int64; Origin : TSeekOrigin) : Int64; override; function Write(const Buffer; Count : longint) : longint; override; procedure WriteLine(const S : string); procedure WriteStr(const S : string); property LineDelimiter : TAbLineDelimiter read FLineDelim write FLineDelim; end; type TAbNodeManager = class private FFreeList : pointer; FNodeSize : cardinal; FNodesPerPage : cardinal; FPageHead : pointer; FPageSize : cardinal; protected function nmAllocNewPage : pointer; public constructor Create(aNodeSize : cardinal); destructor Destroy; override; function AllocNode : pointer; function AllocNodeClear : pointer; procedure FreeNode(aNode : pointer); end; {---exception classes---} type EAbAbortProgress = class(Exception); EAbPartSizedInflate = class(Exception); EAbInflatePasswordError = class(Exception); EAbInternalInflateError = class(Exception); EAbInflateError = class(Exception) public constructor Create(const aMsg : string); constructor CreateUnknown(const aMsg : string; const aErrorMsg : string); end; EAbInternalDeflateError = class(Exception); EAbDeflateError = class(Exception) public constructor Create(const aMsg : string); constructor CreateUnknown(const aMsg : string; const aErrorMsg : string); end; {---aborting a process---} procedure AbortProgress; {---calculation of checksums---} procedure AbUpdateAdlerBuffer(var aAdler : longint; var aBuffer; aCount : integer); procedure AbUpdateCRCBuffer(var aCRC : longint; var aBuffer; aCount : integer); implementation uses AbUtils; {===TAbDeflateHelper=================================================} constructor TAbDeflateHelper.Create; begin inherited Create; FAmpleLength := 8; FChainLength := 32; {FLogFile := '';} FMaxLazy := 16; {FOnProgressStep := nil;} FOptions := $F; {FStreamSize := 0;} FWindowSize := 32 * 1024; FZipOption := 'n'; end; {--------} procedure TAbDeflateHelper.Assign(aHelper : TAbDeflateHelper); begin FAmpleLength := aHelper.FAmpleLength; FChainLength := aHelper.FChainLength; FLogFile := aHelper.FLogFile; FMaxLazy := aHelper.FMaxLazy; FOnProgressStep := aHelper.FOnProgressStep; FOptions := aHelper.FOptions; FPartSize := aHelper.FPartSize; FStreamSize := aHelper.FStreamSize; FWindowSize := aHelper.FWindowSize; FZipOption := aHelper.FZipOption; end; {--------} procedure TAbDeflateHelper.dhSetAmpleLength(aValue : longint); begin if (aValue <> AmpleLength) then begin if (aValue <> -1) and (aValue < 4) then aValue := 4; FAmpleLength := aValue; FZipOption := '?'; end; end; {--------} procedure TAbDeflateHelper.dhSetChainLength(aValue : longint); begin if (aValue <> ChainLength) then begin if (aValue <> -1) and (aValue < 4) then aValue := 4; FChainLength := aValue; FZipOption := '?'; end; end; {--------} procedure TAbDeflateHelper.dhSetLogFile(const aValue : string); begin FLogFile := aValue; end; {--------} procedure TAbDeflateHelper.dhSetMaxLazy(aValue : longint); begin if (aValue <> MaxLazyLength) then begin if (aValue <> -1) and (aValue < 4) then aValue := 4; FMaxLazy := aValue; FZipOption := '?'; end; end; {--------} procedure TAbDeflateHelper.dhSetOnProgressStep(aValue : TAbProgressStep); begin FOnProgressStep := aValue; end; {--------} procedure TAbDeflateHelper.dhSetOptions(aValue : longint); begin if (aValue <> Options) then begin FOptions := aValue; FZipOption := '?'; end; end; {--------} procedure TAbDeflateHelper.dhSetWindowSize(aValue : longint); var NewValue : longint; begin if (aValue <> WindowSize) then begin {calculate the window size rounded to nearest 1024 bytes} NewValue := ((aValue + 1023) div 1024) * 1024; {if the new window size is greater than 32KB...} if (NewValue > 32 * 1024) then {if the Deflate64 option is set, force to 64KB} if ((Options and dfc_UseDeflate64) <> 0) then NewValue := 64 * 1024 {otherwise, force to 32KB} else NewValue := 32 * 1024; {set the new window size} FWindowSize := NewValue; end; end; {--------} procedure TAbDeflateHelper.dhSetZipOption(aValue : AnsiChar); begin {notes: The original Abbrevia code used the following table for setting the equivalent values: Good Lazy Chain UseLazy Option 4 4 4 N s ^ 4 5 8 N | 4 6 32 N f faster 4 4 16 Y slower 8 16 32 Y n | 8 16 128 Y | 8 32 256 Y | 32 128 1024 Y | 32 258 4096 Y x V The new Abbrevia 3 code follows these values to a certain extent. } {force to lower case} if ('A' <= aValue) and (aValue <= 'Z') then aValue := AnsiChar(ord(aValue) + ord('a') - ord('A')); {if the value has changed...} if (aValue <> PKZipOption) then begin {switch on the new value...} case aValue of '0' : {no compression} begin FZipOption := aValue; FOptions := (FOptions and (not $0F)) or dfc_CanUseStored; FAmpleLength := 8; { not actually needed} FChainLength := 32; { not actually needed} FMaxLazy := 16; { not actually needed} end; '2' : {hidden option: Abbrevia 2 compatibility} begin FZipOption := aValue; FOptions := FOptions or $0F; FAmpleLength := 8; FChainLength := 32; FMaxLazy := 16; end; 'f' : {fast compression} begin FZipOption := aValue; FOptions := FOptions or $07; { no lazy matching} FAmpleLength := 4; FChainLength := 32; FMaxLazy := 6; end; 'n' : {normal compression} begin FZipOption := aValue; FOptions := FOptions or $0F; FAmpleLength := 16; FChainLength := 32; FMaxLazy := 24; end; 's' : {super fast compression} begin FZipOption := aValue; FOptions := FOptions or $07; { no lazy matching} FAmpleLength := 4; FChainLength := 4; FMaxLazy := 4; end; 'x' : {maximum compression} begin FZipOption := aValue; FOptions := FOptions or $0F; FAmpleLength := 64;{32;} FChainLength := 4096; FMaxLazy := 258; end; end; end; end; {====================================================================} {===TAbLogger========================================================} const LogBufferSize = 4096; {--------} constructor TAbLogger.Create(const aLogName : string); begin Assert(aLogName <> '', 'TAbLogger.Create: a filename must be provided for the logger'); {create the ancestor} inherited Create; {set the default line terminator} {$IFDEF MSWINDOWS} FLineDelim := ldCRLF; {$ENDIF} {$IFDEF UNIX} FLineDelim := ldLF; {$ENDIF} {create and initialize the buffer} GetMem(FBuffer, LogBufferSize); FCurPos := FBuffer; {create the log file} FStream := TFileStream.Create(aLogName, fmCreate); end; {--------} destructor TAbLogger.Destroy; begin {if there is a buffer ensure that it is flushed before freeing it} if (FBuffer <> nil) then begin if (FCurPos <> FBuffer) then logWriteBuffer; FreeMem(FBuffer, LogBufferSize); end; {free the stream} FStream.Free; {destroy the ancestor} inherited Destroy; end; {--------} function TAbLogger.logWriteBuffer : boolean; var BytesToWrite : longint; BytesWritten : longint; begin BytesToWrite := FCurPos - FBuffer; BytesWritten := FStream.Write(FBuffer^, BytesToWrite); if (BytesWritten = BytesToWrite) then begin Result := true; FCurPos := FBuffer; end else begin Result := false; if (BytesWritten <> 0) then begin Move(FBuffer[BytesWritten], FBuffer^, BytesToWrite - BytesWritten); FCurPos := FBuffer + (BytesToWrite - BytesWritten); end; end; end; {--------} function TAbLogger.Read(var Buffer; Count : longint) : longint; begin Assert(false, 'TAbLogger.Read: loggers are write-only, no reading allowed'); Result := 0; end; {--------} function TAbLogger.Seek(const Offset : Int64; Origin : TSeekOrigin) : Int64; begin case Origin of soBeginning : begin end; soCurrent : if (Offset = 0) then begin Result := FStream.Position + (FCurPos - FBuffer); Exit; end; soEnd : if (Offset = 0) then begin Result := FStream.Position + (FCurPos - FBuffer); Exit; end; end; Assert(false, 'TAbLogger.Seek: loggers are write-only, no seeking allowed'); Result := 0; end; {--------} function TAbLogger.Write(const Buffer; Count : longint) : longint; var UserBuf : PAnsiChar; BytesToGo : longint; BytesToWrite : longint; begin {reference the user's buffer as a PChar} UserBuf := @Buffer; {start the counter for the number of bytes written} Result := 0; {if needed, empty the internal buffer into the underlying stream} if (LogBufferSize = FCurPos - FBuffer) then if not logWriteBuffer then Exit; {calculate the number of bytes to copy this time from the user's buffer to the internal buffer} BytesToGo := Count; BytesToWrite := LogBufferSize - (FCurPos - FBuffer); if (BytesToWrite > BytesToGo) then BytesToWrite := BytesToGo; {copy the bytes} Move(UserBuf^, FCurPos^, BytesToWrite); {adjust the counters} inc(FCurPos, BytesToWrite); dec(BytesToGo, BytesToWrite); inc(Result, BytesToWrite); {while there are still more bytes to copy, do so} while (BytesToGo <> 0) do begin {advance the user's buffer} inc(UserBuf, BytesToWrite); {empty the internal buffer into the underlying stream} if not logWriteBuffer then Exit; {calculate the number of bytes to copy this time from the user's buffer to the internal buffer} BytesToWrite := LogBufferSize; if (BytesToWrite > BytesToGo) then BytesToWrite := BytesToGo; {copy the bytes} Move(UserBuf^, FCurPos^, BytesToWrite); {adjust the counters} inc(FCurPos, BytesToWrite); dec(BytesToGo, BytesToWrite); inc(Result, BytesToWrite); end; end; {--------} procedure TAbLogger.WriteLine(const S : string); const cLF : AnsiChar = ^J; cCRLF : array [0..1] of AnsiChar = ^M^J; begin if (length(S) > 0) then Write(S[1], length(S)); case FLineDelim of ldLF : Write(cLF, sizeof(cLF)); ldCRLF : Write(cCRLF, sizeof(cCRLF)); end; end; {--------} procedure TAbLogger.WriteStr(const S : string); begin if (length(S) > 0) then Write(S[1], length(S)); end; {====================================================================} {===Calculate checksums==============================================} procedure AbUpdateAdlerBuffer(var aAdler : longint; var aBuffer; aCount : integer); var S1 : LongWord; S2 : LongWord; i : integer; Buffer : PAnsiChar; BytesToUse : integer; begin {Note: this algorithm will *only* work if the buffer is 4KB or less, which is why we go to such lengths to chop up the user buffer into usable chunks of 4KB. However, for Delphi 3 there is no proper 32-bit longword. Although the additions pose no problems in this situation, the mod operations below (especially for S2) will be signed integer divisions, producing an (invalid) signed result. In this case, the buffer is chopped up into 2KB chunks to avoid any signed problems.} {split the current Adler checksum into its halves} S1 := LongWord(aAdler) and $FFFF; S2 := LongWord(aAdler) shr 16; {reference the user buffer as a PChar: it makes it easier} Buffer := @aBuffer; {while there's still data to checksum...} while (aCount <> 0) do begin {calculate the number of bytes to checksum this time} {$IFDEF HasLongWord} BytesToUse := 4096; {$ELSE} BytesToUse := 2048; {$ENDIF} if (BytesToUse > aCount) then BytesToUse := aCount; {checksum the bytes} for i := 0 to pred(BytesToUse) do begin inc(S1, ord(Buffer^)); inc(S2, S1); inc(Buffer); end; {recalibrate the Adler checksum halves} S1 := S1 mod 65521; S2 := S2 mod 65521; {calculate the number of bytes still to go} dec(aCount, BytesToUse); end; {join the halves to produce the complete Adler checksum} aAdler := longint((S2 shl 16) or S1); end; {--------} procedure AbUpdateCRCBuffer(var aCRC : longint; var aBuffer; aCount : integer); var i : integer; CRC : LongWord; Buffer : PAnsiChar; begin {$R-}{$Q-} {reference the user buffer as a PChar: it makes it easier} Buffer := @aBuffer; {get the current CRC as a local variable, it's faster} CRC := aCRC; {checksum the bytes in the buffer} for i := 0 to pred(aCount) do begin CRC := AbCrc32Table[byte(CRC) xor byte(Buffer^)] xor (CRC shr 8); inc(Buffer); end; {return the new CRC} aCRC := CRC; {$R+}{$Q+} end; {====================================================================} {===EAbInflateError==================================================} constructor EAbInflateError.Create(const aMsg : string); begin inherited Create( 'Abbrevia inflate error, possibly a corrupted compressed stream. ' + '(Internal cause: ' + aMsg + ')'); end; {--------} constructor EAbInflateError.CreateUnknown(const aMsg : string; const aErrorMsg : string); begin inherited Create(aMsg + ': ' + aErrorMsg); end; {====================================================================} {===EAbDeflateError==================================================} constructor EAbDeflateError.Create(const aMsg : string); begin inherited Create( 'Abbrevia deflate error. ' + '(Internal cause: ' + aMsg + ')'); end; {--------} constructor EAbDeflateError.CreateUnknown(const aMsg : string; const aErrorMsg : string); begin inherited Create(aMsg + ': ' + aErrorMsg); end; {====================================================================} {===Node manager=====================================================} const PageSize = 8 * 1024; type PGenericNode = ^TGenericNode; TGenericNode = packed record gnNext : PGenericNode; gnData : record end; end; {--------} constructor TAbNodeManager.Create(aNodeSize : cardinal); const Gran = sizeof(pointer); Mask = not (Gran - 1); begin {create the ancestor} inherited Create; {save the node size rounded to nearest 4 bytes} if (aNodeSize <= sizeof(pointer)) then aNodeSize := sizeof(pointer) else aNodeSize := (aNodeSize + Gran - 1) and Mask; FNodeSize := aNodeSize; {calculate the page size (default 1024 bytes) and the number of nodes per page; if the default page size is not large enough for two or more nodes, force a single node per page} FNodesPerPage := (PageSize - sizeof(pointer)) div aNodeSize; if (FNodesPerPage > 1) then FPageSize := PageSize else begin FNodesPerPage := 1; FPagesize := aNodeSize + sizeof(pointer); end; end; {--------} destructor TAbNodeManager.Destroy; var Temp : pointer; begin {dispose of all the pages, if there are any} while (FPageHead <> nil) do begin Temp := PGenericNode(FPageHead)^.gnNext; FreeMem(FPageHead, FPageSize); FPageHead := Temp; end; {destroy the ancestor} inherited Destroy; end; {--------} function TAbNodeManager.AllocNode : pointer; begin Result := FFreeList; if (Result = nil) then Result := nmAllocNewPage else FFreeList := PGenericNode(Result)^.gnNext; end; {--------} function TAbNodeManager.AllocNodeClear : pointer; begin Result := FFreeList; if (Result = nil) then Result := nmAllocNewPage else FFreeList := PGenericNode(Result)^.gnNext; FillChar(Result^, FNodeSize, 0); end; {--------} procedure TAbNodeManager.FreeNode(aNode : pointer); begin {add the node (if non-nil) to the top of the free list} if (aNode <> nil) then begin PGenericNode(aNode)^.gnNext := FFreeList; FFreeList := aNode; end; end; {--------} function TAbNodeManager.nmAllocNewPage : pointer; var NewPage : PAnsiChar; i : integer; FreeList : pointer; NodeSize : integer; begin {allocate a new page and add it to the front of the page list} GetMem(NewPage, FPageSize); PGenericNode(NewPage)^.gnNext := FPageHead; FPageHead := NewPage; {now split up the new page into nodes and push them all onto the free list; note that the first 4 bytes of the page is a pointer to the next page, so remember to skip over it} inc(NewPage, sizeof(pointer)); FreeList := FFreeList; NodeSize := FNodeSize; for i := 0 to pred(FNodesPerPage) do begin PGenericNode(NewPage)^.gnNext := FreeList; FreeList := NewPage; inc(NewPage, NodeSize); end; {return the top of the list} Result := FreeList; FFreeList := PGenericNode(Result)^.gnNext; end; {====================================================================} {====================================================================} procedure AbortProgress; begin raise EAbAbortProgress.Create('Abort'); end; {====================================================================} end.