Compression.bzlib.pas 11 KB

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