Compression.bzlib.pas 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. unit Compression.bzlib;
  2. {
  3. Inno Setup
  4. Copyright (C) 1997-2010 Jordan Russell
  5. Portions by Martijn Laan
  6. For conditions of distribution and use, see LICENSE.TXT.
  7. Declarations for some bzlib2 functions & structures
  8. }
  9. interface
  10. uses
  11. Windows, SysUtils, Compression.Base;
  12. function BZInitCompressFunctions(Module: HMODULE): Boolean;
  13. function BZInitDecompressFunctions(Module: HMODULE): Boolean;
  14. type
  15. { Must keep in sync with bzlib.h }
  16. TBZAlloc = function(AppData: Pointer; Items, Size: Integer): Pointer; stdcall;
  17. TBZFree = procedure(AppData, Block: Pointer); stdcall;
  18. TBZStreamRec = record
  19. next_in: Pointer;
  20. avail_in: Cardinal;
  21. total_in: Cardinal;
  22. total_in_hi: Cardinal;
  23. next_out: Pointer;
  24. avail_out: Cardinal;
  25. total_out: Cardinal;
  26. total_out_hi: Cardinal;
  27. State: Pointer;
  28. zalloc: TBZAlloc;
  29. zfree: TBZFree;
  30. AppData: Pointer;
  31. end;
  32. TBZCompressor = class(TCustomCompressor)
  33. private
  34. FCompressionLevel: Integer;
  35. FInitialized: Boolean;
  36. FStrm: TBZStreamRec;
  37. FBuffer: array[0..65535] of Byte;
  38. procedure EndCompress;
  39. procedure FlushBuffer;
  40. procedure InitCompress;
  41. protected
  42. procedure DoCompress(const Buffer; Count: Cardinal); override;
  43. procedure DoFinish; override;
  44. public
  45. constructor Create(AWriteProc: TCompressorWriteProc;
  46. AProgressProc: TCompressorProgressProc; CompressionLevel: Integer;
  47. ACompressorProps: TCompressorProps); override;
  48. destructor Destroy; override;
  49. end;
  50. TBZDecompressor = class(TCustomDecompressor)
  51. private
  52. FInitialized: Boolean;
  53. FStrm: TBZStreamRec;
  54. FReachedEnd: Boolean;
  55. FBuffer: array[0..65535] of Byte;
  56. FHeapBase, FHeapNextFree: Pointer;
  57. function Malloc(Bytes: Cardinal): Pointer;
  58. public
  59. constructor Create(AReadProc: TDecompressorReadProc); override;
  60. destructor Destroy; override;
  61. procedure DecompressInto(var Buffer; Count: Cardinal); override;
  62. procedure Reset; override;
  63. end;
  64. implementation
  65. var
  66. BZ2_bzCompressInit: function(var strm: TBZStreamRec;
  67. blockSize100k, verbosity, workFactor: Integer): Integer; stdcall;
  68. BZ2_bzCompress: function(var strm: TBZStreamRec;
  69. action: Integer): Integer; stdcall;
  70. BZ2_bzCompressEnd: function(var strm: TBZStreamRec): Integer; stdcall;
  71. BZ2_bzDecompressInit: function(var strm: TBZStreamRec;
  72. verbosity, small: Integer): Integer; stdcall;
  73. BZ2_bzDecompress: function(var strm: TBZStreamRec): Integer; stdcall;
  74. BZ2_bzDecompressEnd: function(var strm: TBZStreamRec): Integer; stdcall;
  75. const
  76. BZ_RUN = 0;
  77. BZ_FLUSH = 1;
  78. BZ_FINISH = 2;
  79. BZ_OK = 0;
  80. BZ_RUN_OK = 1;
  81. BZ_FLUSH_OK = 2;
  82. BZ_FINISH_OK = 3;
  83. BZ_STREAM_END = 4;
  84. BZ_SEQUENCE_ERROR = (-1);
  85. BZ_PARAM_ERROR = (-2);
  86. BZ_MEM_ERROR = (-3);
  87. BZ_DATA_ERROR = (-4);
  88. BZ_DATA_ERROR_MAGIC = (-5);
  89. BZ_IO_ERROR = (-6);
  90. BZ_UNEXPECTED_EOF = (-7);
  91. BZ_OUTBUFF_FULL = (-8);
  92. BZ_CONFIG_ERROR = (-9);
  93. SBzlibDataError = 'bzlib: Compressed data is corrupted';
  94. SBzlibInternalError = 'bzlib: Internal error. Code %d';
  95. SBzlibAllocError = 'bzlib: Too much memory requested';
  96. function BZInitCompressFunctions(Module: HMODULE): Boolean;
  97. begin
  98. BZ2_bzCompressInit := GetProcAddress(Module, 'BZ2_bzCompressInit');
  99. BZ2_bzCompress := GetProcAddress(Module, 'BZ2_bzCompress');
  100. BZ2_bzCompressEnd := GetProcAddress(Module, 'BZ2_bzCompressEnd');
  101. Result := Assigned(BZ2_bzCompressInit) and Assigned(BZ2_bzCompress) and
  102. Assigned(BZ2_bzCompressEnd);
  103. if not Result then begin
  104. BZ2_bzCompressInit := nil;
  105. BZ2_bzCompress := nil;
  106. BZ2_bzCompressEnd := nil;
  107. end;
  108. end;
  109. function BZInitDecompressFunctions(Module: HMODULE): Boolean;
  110. begin
  111. BZ2_bzDecompressInit := GetProcAddress(Module, 'BZ2_bzDecompressInit');
  112. BZ2_bzDecompress := GetProcAddress(Module, 'BZ2_bzDecompress');
  113. BZ2_bzDecompressEnd := GetProcAddress(Module, 'BZ2_bzDecompressEnd');
  114. Result := Assigned(BZ2_bzDecompressInit) and Assigned(BZ2_bzDecompress) and
  115. Assigned(BZ2_bzDecompressEnd);
  116. if not Result then begin
  117. BZ2_bzDecompressInit := nil;
  118. BZ2_bzDecompress := nil;
  119. BZ2_bzDecompressEnd := nil;
  120. end;
  121. end;
  122. function BZAllocMem(AppData: Pointer; Items, Size: Integer): Pointer; stdcall;
  123. begin
  124. try
  125. GetMem(Result, Items * Size);
  126. except
  127. { trap any exception, because zlib expects a NULL result if it's out
  128. of memory }
  129. Result := nil;
  130. end;
  131. end;
  132. procedure BZFreeMem(AppData, Block: Pointer); stdcall;
  133. begin
  134. FreeMem(Block);
  135. end;
  136. function Check(const Code: Integer; const ValidCodes: array of Integer): Integer;
  137. begin
  138. if Code = BZ_MEM_ERROR then
  139. OutOfMemoryError;
  140. Result := Code;
  141. for var I := Low(ValidCodes) to High(ValidCodes) do
  142. if ValidCodes[I] = Code then
  143. Exit;
  144. raise ECompressInternalError.CreateFmt(SBzlibInternalError, [Code]);
  145. end;
  146. procedure InitStream(var strm: TBZStreamRec);
  147. begin
  148. FillChar(strm, SizeOf(strm), 0);
  149. with strm do begin
  150. zalloc := BZAllocMem;
  151. zfree := BZFreeMem;
  152. end;
  153. end;
  154. { TBZCompressor }
  155. constructor TBZCompressor.Create(AWriteProc: TCompressorWriteProc;
  156. AProgressProc: TCompressorProgressProc; CompressionLevel: Integer;
  157. ACompressorProps: TCompressorProps);
  158. begin
  159. inherited;
  160. FCompressionLevel := CompressionLevel;
  161. InitCompress;
  162. end;
  163. destructor TBZCompressor.Destroy;
  164. begin
  165. EndCompress;
  166. inherited;
  167. end;
  168. procedure TBZCompressor.InitCompress;
  169. begin
  170. if not FInitialized then begin
  171. InitStream(FStrm);
  172. FStrm.next_out := @FBuffer;
  173. FStrm.avail_out := SizeOf(FBuffer);
  174. Check(BZ2_bzCompressInit(FStrm, FCompressionLevel, 0, 0), [BZ_OK]);
  175. FInitialized := True;
  176. end;
  177. end;
  178. procedure TBZCompressor.EndCompress;
  179. begin
  180. if FInitialized then begin
  181. FInitialized := False;
  182. BZ2_bzCompressEnd(FStrm);
  183. end;
  184. end;
  185. procedure TBZCompressor.FlushBuffer;
  186. begin
  187. if FStrm.avail_out < SizeOf(FBuffer) then begin
  188. WriteProc(FBuffer, Cardinal(SizeOf(FBuffer) - FStrm.avail_out));
  189. FStrm.next_out := @FBuffer;
  190. FStrm.avail_out := SizeOf(FBuffer);
  191. end;
  192. end;
  193. procedure TBZCompressor.DoCompress(const Buffer; Count: Cardinal);
  194. begin
  195. InitCompress;
  196. FStrm.next_in := @Buffer;
  197. FStrm.avail_in := Count;
  198. while FStrm.avail_in > 0 do begin
  199. Check(BZ2_bzCompress(FStrm, BZ_RUN), [BZ_RUN_OK]);
  200. if FStrm.avail_out = 0 then
  201. FlushBuffer;
  202. end;
  203. if Assigned(ProgressProc) then
  204. ProgressProc(Count);
  205. end;
  206. procedure TBZCompressor.DoFinish;
  207. begin
  208. InitCompress;
  209. FStrm.next_in := nil;
  210. FStrm.avail_in := 0;
  211. { Note: This assumes FStrm.avail_out > 0. This shouldn't be a problem since
  212. Compress always flushes when FStrm.avail_out reaches 0. }
  213. while Check(BZ2_bzCompress(FStrm, BZ_FINISH), [BZ_FINISH_OK, BZ_STREAM_END]) <> BZ_STREAM_END do
  214. FlushBuffer;
  215. FlushBuffer;
  216. EndCompress;
  217. end;
  218. { TBZDecompressor }
  219. { Why does TBZDecompressor use VirtualAlloc instead of GetMem?
  220. It IS 4.0.1 it did use GetMem and allocate blocks on demand, but thanks to
  221. Delphi's flawed memory manager this resulted in crippling memory
  222. fragmentation when Reset was called repeatedly (e.g. when an installation
  223. contained thousands of files and solid decompression was disabled) while
  224. Setup was allocating other small blocks (e.g. FileLocationFilenames[]), and
  225. eventually caused Setup to run out of virtual address space.
  226. So, it was changed to allocate only one chunk of virtual address space for
  227. the entire lifetime of the TBZDecompressor instance. It divides this chunk
  228. into smaller amounts as requested by bzlib. As IS only creates one instance
  229. of TBZDecompressor, this change should completely eliminate the
  230. fragmentation issue. }
  231. const
  232. DecompressorHeapSize = $600000;
  233. { 6 MB should be more than enough; the most I've seen bzlib 1.0.2's
  234. bzDecompress* allocate is 64116 + 3600000 bytes, when decompressing data
  235. compressed at level 9 }
  236. function DecompressorAllocMem(AppData: Pointer; Items, Size: Integer): Pointer; stdcall;
  237. begin
  238. Result := TBZDecompressor(AppData).Malloc(Cardinal(Items * Size));
  239. end;
  240. procedure DecompressorFreeMem(AppData, Block: Pointer); stdcall;
  241. begin
  242. { Since bzlib doesn't repeatedly deallocate and allocate blocks during a
  243. decompression run, we don't have to handle frees. }
  244. end;
  245. constructor TBZDecompressor.Create(AReadProc: TDecompressorReadProc);
  246. begin
  247. inherited Create(AReadProc);
  248. FHeapBase := VirtualAlloc(nil, DecompressorHeapSize, MEM_RESERVE, PAGE_NOACCESS);
  249. if FHeapBase = nil then
  250. OutOfMemoryError;
  251. FHeapNextFree := FHeapBase;
  252. FStrm.AppData := Self;
  253. FStrm.zalloc := DecompressorAllocMem;
  254. FStrm.zfree := DecompressorFreeMem;
  255. FStrm.next_in := @FBuffer;
  256. FStrm.avail_in := 0;
  257. Check(BZ2_bzDecompressInit(FStrm, 0, 0), [BZ_OK]);
  258. FInitialized := True;
  259. end;
  260. destructor TBZDecompressor.Destroy;
  261. begin
  262. if FInitialized then
  263. BZ2_bzDecompressEnd(FStrm);
  264. if Assigned(FHeapBase) then
  265. VirtualFree(FHeapBase, 0, MEM_RELEASE);
  266. inherited Destroy;
  267. end;
  268. function TBZDecompressor.Malloc(Bytes: Cardinal): Pointer;
  269. begin
  270. { Round up to dword boundary if necessary }
  271. if Bytes mod 4 <> 0 then
  272. Inc(Bytes, 4 - Bytes mod 4);
  273. { Did bzlib request more memory than we reserved? This shouldn't happen
  274. unless this unit is used with a different version of bzlib that allocates
  275. more memory. }
  276. const HeapSize = NativeUInt(PByte(FHeapNextFree) - PByte(FHeapBase)) + Bytes;
  277. if HeapSize > DecompressorHeapSize then
  278. raise ECompressInternalError.Create(SBzlibAllocError);
  279. if VirtualAlloc(FHeapNextFree, Bytes, MEM_COMMIT, PAGE_READWRITE) = nil then
  280. Result := nil
  281. else begin
  282. Result := FHeapNextFree;
  283. Inc(PByte(FHeapNextFree), Bytes);
  284. end;
  285. end;
  286. procedure TBZDecompressor.DecompressInto(var Buffer; Count: Cardinal);
  287. begin
  288. FStrm.next_out := @Buffer;
  289. FStrm.avail_out := Count;
  290. while FStrm.avail_out > 0 do begin
  291. if FReachedEnd then { unexpected EOF }
  292. raise ECompressDataError.Create(SBzlibDataError);
  293. if FStrm.avail_in = 0 then begin
  294. FStrm.next_in := @FBuffer;
  295. FStrm.avail_in := ReadProc(FBuffer, SizeOf(FBuffer));
  296. { Unlike zlib, bzlib does not return an error when avail_in is zero and
  297. it still needs input. To avoid an infinite loop, check for this and
  298. consider it a data error. }
  299. if FStrm.avail_in = 0 then
  300. raise ECompressDataError.Create(SBzlibDataError);
  301. end;
  302. case Check(BZ2_bzDecompress(FStrm), [BZ_OK, BZ_STREAM_END, BZ_DATA_ERROR, BZ_DATA_ERROR_MAGIC]) of
  303. BZ_STREAM_END: FReachedEnd := True;
  304. BZ_DATA_ERROR, BZ_DATA_ERROR_MAGIC: raise ECompressDataError.Create(SBzlibDataError);
  305. end;
  306. end;
  307. end;
  308. procedure TBZDecompressor.Reset;
  309. begin
  310. FStrm.next_in := @FBuffer;
  311. FStrm.avail_in := 0;
  312. { bzlib doesn't offer an optimized 'Reset' function like zlib }
  313. BZ2_bzDecompressEnd(FStrm);
  314. FHeapNextFree := FHeapBase; { discard previous allocations }
  315. Check(BZ2_bzDecompressInit(FStrm, 0, 0), [BZ_OK]);
  316. FReachedEnd := False;
  317. end;
  318. end.