Inno-Setup-issrc/Projects/Src/Compression.bzlib.pas

363 lines
11 KiB
ObjectPascal

unit Compression.bzlib;
{
Inno Setup
Copyright (C) 1997-2010 Jordan Russell
Portions by Martijn Laan
For conditions of distribution and use, see LICENSE.TXT.
Declarations for some bzlib2 functions & structures
}
interface
uses
Windows, SysUtils, Compression.Base;
function BZInitCompressFunctions(Module: HMODULE): Boolean;
function BZInitDecompressFunctions(Module: HMODULE): Boolean;
type
TBZAlloc = function(AppData: Pointer; Items, Size: Cardinal): Pointer; stdcall;
TBZFree = procedure(AppData, Block: Pointer); stdcall;
TBZStreamRec = record
next_in: Pointer;
avail_in: Integer;
total_in: Integer;
total_in_hi: Integer;
next_out: Pointer;
avail_out: Integer;
total_out: Integer;
total_out_hi: Integer;
State: Pointer;
zalloc: TBZAlloc;
zfree: TBZFree;
AppData: Pointer;
end;
TBZCompressor = class(TCustomCompressor)
private
FCompressionLevel: Integer;
FInitialized: Boolean;
FStrm: TBZStreamRec;
FBuffer: array[0..65535] of Byte;
procedure EndCompress;
procedure FlushBuffer;
procedure InitCompress;
protected
procedure DoCompress(const Buffer; Count: Longint); override;
procedure DoFinish; override;
public
constructor Create(AWriteProc: TCompressorWriteProc;
AProgressProc: TCompressorProgressProc; CompressionLevel: Integer;
ACompressorProps: TCompressorProps); override;
destructor Destroy; override;
end;
TBZDecompressor = class(TCustomDecompressor)
private
FInitialized: Boolean;
FStrm: TBZStreamRec;
FReachedEnd: Boolean;
FBuffer: array[0..65535] of Byte;
FHeapBase, FHeapNextFree: Pointer;
function Malloc(Bytes: Cardinal): Pointer;
public
constructor Create(AReadProc: TDecompressorReadProc); override;
destructor Destroy; override;
procedure DecompressInto(var Buffer; Count: Longint); override;
procedure Reset; override;
end;
implementation
var
BZ2_bzCompressInit: function(var strm: TBZStreamRec;
blockSize100k, verbosity, workFactor: Integer): Integer; stdcall;
BZ2_bzCompress: function(var strm: TBZStreamRec;
action: Integer): Integer; stdcall;
BZ2_bzCompressEnd: function(var strm: TBZStreamRec): Integer; stdcall;
BZ2_bzDecompressInit: function(var strm: TBZStreamRec;
verbosity, small: Integer): Integer; stdcall;
BZ2_bzDecompress: function(var strm: TBZStreamRec): Integer; stdcall;
BZ2_bzDecompressEnd: function(var strm: TBZStreamRec): Integer; stdcall;
const
BZ_RUN = 0;
BZ_FLUSH = 1;
BZ_FINISH = 2;
BZ_OK = 0;
BZ_RUN_OK = 1;
BZ_FLUSH_OK = 2;
BZ_FINISH_OK = 3;
BZ_STREAM_END = 4;
BZ_SEQUENCE_ERROR = (-1);
BZ_PARAM_ERROR = (-2);
BZ_MEM_ERROR = (-3);
BZ_DATA_ERROR = (-4);
BZ_DATA_ERROR_MAGIC = (-5);
BZ_IO_ERROR = (-6);
BZ_UNEXPECTED_EOF = (-7);
BZ_OUTBUFF_FULL = (-8);
BZ_CONFIG_ERROR = (-9);
SBzlibDataError = 'bzlib: Compressed data is corrupted';
SBzlibInternalError = 'bzlib: Internal error. Code %d';
SBzlibAllocError = 'bzlib: Too much memory requested';
function BZInitCompressFunctions(Module: HMODULE): Boolean;
begin
BZ2_bzCompressInit := GetProcAddress(Module, 'BZ2_bzCompressInit');
BZ2_bzCompress := GetProcAddress(Module, 'BZ2_bzCompress');
BZ2_bzCompressEnd := GetProcAddress(Module, 'BZ2_bzCompressEnd');
Result := Assigned(BZ2_bzCompressInit) and Assigned(BZ2_bzCompress) and
Assigned(BZ2_bzCompressEnd);
if not Result then begin
BZ2_bzCompressInit := nil;
BZ2_bzCompress := nil;
BZ2_bzCompressEnd := nil;
end;
end;
function BZInitDecompressFunctions(Module: HMODULE): Boolean;
begin
BZ2_bzDecompressInit := GetProcAddress(Module, 'BZ2_bzDecompressInit');
BZ2_bzDecompress := GetProcAddress(Module, 'BZ2_bzDecompress');
BZ2_bzDecompressEnd := GetProcAddress(Module, 'BZ2_bzDecompressEnd');
Result := Assigned(BZ2_bzDecompressInit) and Assigned(BZ2_bzDecompress) and
Assigned(BZ2_bzDecompressEnd);
if not Result then begin
BZ2_bzDecompressInit := nil;
BZ2_bzDecompress := nil;
BZ2_bzDecompressEnd := nil;
end;
end;
function BZAllocMem(AppData: Pointer; Items, Size: Cardinal): Pointer; stdcall;
begin
try
GetMem(Result, Items * Size);
except
{ trap any exception, because zlib expects a NULL result if it's out
of memory }
Result := nil;
end;
end;
procedure BZFreeMem(AppData, Block: Pointer); stdcall;
begin
FreeMem(Block);
end;
function Check(const Code: Integer; const ValidCodes: array of Integer): Integer;
var
I: Integer;
begin
if Code = BZ_MEM_ERROR then
OutOfMemoryError;
Result := Code;
for I := Low(ValidCodes) to High(ValidCodes) do
if ValidCodes[I] = Code then
Exit;
raise ECompressInternalError.CreateFmt(SBzlibInternalError, [Code]);
end;
procedure InitStream(var strm: TBZStreamRec);
begin
FillChar(strm, SizeOf(strm), 0);
with strm do begin
zalloc := BZAllocMem;
zfree := BZFreeMem;
end;
end;
{ TBZCompressor }
constructor TBZCompressor.Create(AWriteProc: TCompressorWriteProc;
AProgressProc: TCompressorProgressProc; CompressionLevel: Integer;
ACompressorProps: TCompressorProps);
begin
inherited;
FCompressionLevel := CompressionLevel;
InitCompress;
end;
destructor TBZCompressor.Destroy;
begin
EndCompress;
inherited;
end;
procedure TBZCompressor.InitCompress;
begin
if not FInitialized then begin
InitStream(FStrm);
FStrm.next_out := @FBuffer;
FStrm.avail_out := SizeOf(FBuffer);
Check(BZ2_bzCompressInit(FStrm, FCompressionLevel, 0, 0), [BZ_OK]);
FInitialized := True;
end;
end;
procedure TBZCompressor.EndCompress;
begin
if FInitialized then begin
FInitialized := False;
BZ2_bzCompressEnd(FStrm);
end;
end;
procedure TBZCompressor.FlushBuffer;
begin
if FStrm.avail_out < SizeOf(FBuffer) then begin
WriteProc(FBuffer, SizeOf(FBuffer) - FStrm.avail_out);
FStrm.next_out := @FBuffer;
FStrm.avail_out := SizeOf(FBuffer);
end;
end;
procedure TBZCompressor.DoCompress(const Buffer; Count: Longint);
begin
InitCompress;
FStrm.next_in := @Buffer;
FStrm.avail_in := Count;
while FStrm.avail_in > 0 do begin
Check(BZ2_bzCompress(FStrm, BZ_RUN), [BZ_RUN_OK]);
if FStrm.avail_out = 0 then
FlushBuffer;
end;
if Assigned(ProgressProc) then
ProgressProc(Count);
end;
procedure TBZCompressor.DoFinish;
begin
InitCompress;
FStrm.next_in := nil;
FStrm.avail_in := 0;
{ Note: This assumes FStrm.avail_out > 0. This shouldn't be a problem since
Compress always flushes when FStrm.avail_out reaches 0. }
while Check(BZ2_bzCompress(FStrm, BZ_FINISH), [BZ_FINISH_OK, BZ_STREAM_END]) <> BZ_STREAM_END do
FlushBuffer;
FlushBuffer;
EndCompress;
end;
{ TBZDecompressor }
{ Why does TBZDecompressor use VirtualAlloc instead of GetMem?
It IS 4.0.1 it did use GetMem and allocate blocks on demand, but thanks to
Delphi's flawed memory manager this resulted in crippling memory
fragmentation when Reset was called repeatedly (e.g. when an installation
contained thousands of files and solid decompression was disabled) while
Setup was allocating other small blocks (e.g. FileLocationFilenames[]), and
eventually caused Setup to run out of virtual address space.
So, it was changed to allocate only one chunk of virtual address space for
the entire lifetime of the TBZDecompressor instance. It divides this chunk
into smaller amounts as requested by bzlib. As IS only creates one instance
of TBZDecompressor, this change should completely eliminate the
fragmentation issue. }
const
DecompressorHeapSize = $600000;
{ 6 MB should be more than enough; the most I've seen bzlib 1.0.2's
bzDecompress* allocate is 64116 + 3600000 bytes, when decompressing data
compressed at level 9 }
function DecompressorAllocMem(AppData: Pointer; Items, Size: Cardinal): Pointer; stdcall;
begin
Result := TBZDecompressor(AppData).Malloc(Items * Size);
end;
procedure DecompressorFreeMem(AppData, Block: Pointer); stdcall;
begin
{ Since bzlib doesn't repeatedly deallocate and allocate blocks during a
decompression run, we don't have to handle frees. }
end;
constructor TBZDecompressor.Create(AReadProc: TDecompressorReadProc);
begin
inherited Create(AReadProc);
FHeapBase := VirtualAlloc(nil, DecompressorHeapSize, MEM_RESERVE, PAGE_NOACCESS);
if FHeapBase = nil then
OutOfMemoryError;
FHeapNextFree := FHeapBase;
FStrm.AppData := Self;
FStrm.zalloc := DecompressorAllocMem;
FStrm.zfree := DecompressorFreeMem;
FStrm.next_in := @FBuffer;
FStrm.avail_in := 0;
Check(BZ2_bzDecompressInit(FStrm, 0, 0), [BZ_OK]);
FInitialized := True;
end;
destructor TBZDecompressor.Destroy;
begin
if FInitialized then
BZ2_bzDecompressEnd(FStrm);
if Assigned(FHeapBase) then
VirtualFree(FHeapBase, 0, MEM_RELEASE);
inherited Destroy;
end;
function TBZDecompressor.Malloc(Bytes: Cardinal): Pointer;
begin
{ Round up to dword boundary if necessary }
if Bytes mod 4 <> 0 then
Inc(Bytes, 4 - Bytes mod 4);
{ Did bzlib request more memory than we reserved? This shouldn't happen
unless this unit is used with a different version of bzlib that allocates
more memory. Note: The funky Cardinal casts are there to convince
Delphi (2) to do an unsigned compare. }
if Cardinal(Cardinal(FHeapNextFree) - Cardinal(FHeapBase) + Bytes) > Cardinal(DecompressorHeapSize) then
raise ECompressInternalError.Create(SBzlibAllocError);
if VirtualAlloc(FHeapNextFree, Bytes, MEM_COMMIT, PAGE_READWRITE) = nil then
Result := nil
else begin
Result := FHeapNextFree;
Inc(Cardinal(FHeapNextFree), Bytes);
end;
end;
procedure TBZDecompressor.DecompressInto(var Buffer; Count: Longint);
begin
FStrm.next_out := @Buffer;
FStrm.avail_out := Count;
while FStrm.avail_out > 0 do begin
if FReachedEnd then { unexpected EOF }
raise ECompressDataError.Create(SBzlibDataError);
if FStrm.avail_in = 0 then begin
FStrm.next_in := @FBuffer;
FStrm.avail_in := ReadProc(FBuffer, SizeOf(FBuffer));
{ Unlike zlib, bzlib does not return an error when avail_in is zero and
it still needs input. To avoid an infinite loop, check for this and
consider it a data error. }
if FStrm.avail_in = 0 then
raise ECompressDataError.Create(SBzlibDataError);
end;
case Check(BZ2_bzDecompress(FStrm), [BZ_OK, BZ_STREAM_END, BZ_DATA_ERROR, BZ_DATA_ERROR_MAGIC]) of
BZ_STREAM_END: FReachedEnd := True;
BZ_DATA_ERROR, BZ_DATA_ERROR_MAGIC: raise ECompressDataError.Create(SBzlibDataError);
end;
end;
end;
procedure TBZDecompressor.Reset;
begin
FStrm.next_in := @FBuffer;
FStrm.avail_in := 0;
{ bzlib doesn't offer an optimized 'Reset' function like zlib }
BZ2_bzDecompressEnd(FStrm);
FHeapNextFree := FHeapBase; { discard previous allocations }
Check(BZ2_bzDecompressInit(FStrm, 0, 0), [BZ_OK]);
FReachedEnd := False;
end;
end.