bzlib.pas 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. unit 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. $jrsoftware: issrc/Projects/bzlib.pas,v 1.14 2010/09/07 03:09:36 jr Exp $
  9. }
  10. interface
  11. uses
  12. Windows, SysUtils, Compress;
  13. function BZInitCompressFunctions(Module: HMODULE): Boolean;
  14. function BZInitDecompressFunctions(Module: HMODULE): Boolean;
  15. type
  16. TBZAlloc = function(AppData: Pointer; Items, Size: Cardinal): Pointer; stdcall;
  17. TBZFree = procedure(AppData, Block: Pointer); stdcall;
  18. TBZStreamRec = record
  19. next_in: Pointer;
  20. avail_in: Integer;
  21. total_in: Integer;
  22. total_in_hi: Integer;
  23. next_out: Pointer;
  24. avail_out: Integer;
  25. total_out: Integer;
  26. total_out_hi: Integer;
  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: Longint); 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: Longint); 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: Cardinal): 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. var
  138. I: Integer;
  139. begin
  140. if Code = BZ_MEM_ERROR then
  141. OutOfMemoryError;
  142. Result := Code;
  143. for I := Low(ValidCodes) to High(ValidCodes) do
  144. if ValidCodes[I] = Code then
  145. Exit;
  146. raise ECompressInternalError.CreateFmt(SBzlibInternalError, [Code]);
  147. end;
  148. procedure InitStream(var strm: TBZStreamRec);
  149. begin
  150. FillChar(strm, SizeOf(strm), 0);
  151. with strm do begin
  152. zalloc := BZAllocMem;
  153. zfree := BZFreeMem;
  154. end;
  155. end;
  156. { TBZCompressor }
  157. constructor TBZCompressor.Create(AWriteProc: TCompressorWriteProc;
  158. AProgressProc: TCompressorProgressProc; CompressionLevel: Integer;
  159. ACompressorProps: TCompressorProps);
  160. begin
  161. inherited;
  162. FCompressionLevel := CompressionLevel;
  163. InitCompress;
  164. end;
  165. destructor TBZCompressor.Destroy;
  166. begin
  167. EndCompress;
  168. inherited;
  169. end;
  170. procedure TBZCompressor.InitCompress;
  171. begin
  172. if not FInitialized then begin
  173. InitStream(FStrm);
  174. FStrm.next_out := @FBuffer;
  175. FStrm.avail_out := SizeOf(FBuffer);
  176. Check(BZ2_bzCompressInit(FStrm, FCompressionLevel, 0, 0), [BZ_OK]);
  177. FInitialized := True;
  178. end;
  179. end;
  180. procedure TBZCompressor.EndCompress;
  181. begin
  182. if FInitialized then begin
  183. FInitialized := False;
  184. BZ2_bzCompressEnd(FStrm);
  185. end;
  186. end;
  187. procedure TBZCompressor.FlushBuffer;
  188. begin
  189. if FStrm.avail_out < SizeOf(FBuffer) then begin
  190. WriteProc(FBuffer, SizeOf(FBuffer) - FStrm.avail_out);
  191. FStrm.next_out := @FBuffer;
  192. FStrm.avail_out := SizeOf(FBuffer);
  193. end;
  194. end;
  195. procedure TBZCompressor.DoCompress(const Buffer; Count: Longint);
  196. begin
  197. InitCompress;
  198. FStrm.next_in := @Buffer;
  199. FStrm.avail_in := Count;
  200. while FStrm.avail_in > 0 do begin
  201. Check(BZ2_bzCompress(FStrm, BZ_RUN), [BZ_RUN_OK]);
  202. if FStrm.avail_out = 0 then
  203. FlushBuffer;
  204. end;
  205. if Assigned(ProgressProc) then
  206. ProgressProc(Count);
  207. end;
  208. procedure TBZCompressor.DoFinish;
  209. begin
  210. InitCompress;
  211. FStrm.next_in := nil;
  212. FStrm.avail_in := 0;
  213. { Note: This assumes FStrm.avail_out > 0. This shouldn't be a problem since
  214. Compress always flushes when FStrm.avail_out reaches 0. }
  215. while Check(BZ2_bzCompress(FStrm, BZ_FINISH), [BZ_FINISH_OK, BZ_STREAM_END]) <> BZ_STREAM_END do
  216. FlushBuffer;
  217. FlushBuffer;
  218. EndCompress;
  219. end;
  220. { TBZDecompressor }
  221. { Why does TBZDecompressor use VirtualAlloc instead of GetMem?
  222. It IS 4.0.1 it did use GetMem and allocate blocks on demand, but thanks to
  223. Delphi's flawed memory manager this resulted in crippling memory
  224. fragmentation when Reset was called repeatedly (e.g. when an installation
  225. contained thousands of files and solid decompression was disabled) while
  226. Setup was allocating other small blocks (e.g. FileLocationFilenames[]), and
  227. eventually caused Setup to run out of virtual address space.
  228. So, it was changed to allocate only one chunk of virtual address space for
  229. the entire lifetime of the TBZDecompressor instance. It divides this chunk
  230. into smaller amounts as requested by bzlib. As IS only creates one instance
  231. of TBZDecompressor, this change should completely eliminate the
  232. fragmentation issue. }
  233. const
  234. DecompressorHeapSize = $600000;
  235. { 6 MB should be more than enough; the most I've seen bzlib 1.0.2's
  236. bzDecompress* allocate is 64116 + 3600000 bytes, when decompressing data
  237. compressed at level 9 }
  238. function DecompressorAllocMem(AppData: Pointer; Items, Size: Cardinal): Pointer; stdcall;
  239. begin
  240. Result := TBZDecompressor(AppData).Malloc(Items * Size);
  241. end;
  242. procedure DecompressorFreeMem(AppData, Block: Pointer); stdcall;
  243. begin
  244. { Since bzlib doesn't repeatedly deallocate and allocate blocks during a
  245. decompression run, we don't have to handle frees. }
  246. end;
  247. constructor TBZDecompressor.Create(AReadProc: TDecompressorReadProc);
  248. begin
  249. inherited Create(AReadProc);
  250. FHeapBase := VirtualAlloc(nil, DecompressorHeapSize, MEM_RESERVE, PAGE_NOACCESS);
  251. if FHeapBase = nil then
  252. OutOfMemoryError;
  253. FHeapNextFree := FHeapBase;
  254. FStrm.AppData := Self;
  255. FStrm.zalloc := DecompressorAllocMem;
  256. FStrm.zfree := DecompressorFreeMem;
  257. FStrm.next_in := @FBuffer;
  258. FStrm.avail_in := 0;
  259. Check(BZ2_bzDecompressInit(FStrm, 0, 0), [BZ_OK]);
  260. FInitialized := True;
  261. end;
  262. destructor TBZDecompressor.Destroy;
  263. begin
  264. if FInitialized then
  265. BZ2_bzDecompressEnd(FStrm);
  266. if Assigned(FHeapBase) then
  267. VirtualFree(FHeapBase, 0, MEM_RELEASE);
  268. inherited Destroy;
  269. end;
  270. function TBZDecompressor.Malloc(Bytes: Cardinal): Pointer;
  271. begin
  272. { Round up to dword boundary if necessary }
  273. if Bytes mod 4 <> 0 then
  274. Inc(Bytes, 4 - Bytes mod 4);
  275. { Did bzlib request more memory than we reserved? This shouldn't happen
  276. unless this unit is used with a different version of bzlib that allocates
  277. more memory. Note: The funky Cardinal casts are there to convince
  278. Delphi (2) to do an unsigned compare. }
  279. if Cardinal(Cardinal(FHeapNextFree) - Cardinal(FHeapBase) + Bytes) > Cardinal(DecompressorHeapSize) then
  280. raise ECompressInternalError.Create(SBzlibAllocError);
  281. if VirtualAlloc(FHeapNextFree, Bytes, MEM_COMMIT, PAGE_READWRITE) = nil then
  282. Result := nil
  283. else begin
  284. Result := FHeapNextFree;
  285. Inc(Cardinal(FHeapNextFree), Bytes);
  286. end;
  287. end;
  288. procedure TBZDecompressor.DecompressInto(var Buffer; Count: Longint);
  289. begin
  290. FStrm.next_out := @Buffer;
  291. FStrm.avail_out := Count;
  292. while FStrm.avail_out > 0 do begin
  293. if FReachedEnd then { unexpected EOF }
  294. raise ECompressDataError.Create(SBzlibDataError);
  295. if FStrm.avail_in = 0 then begin
  296. FStrm.next_in := @FBuffer;
  297. FStrm.avail_in := ReadProc(FBuffer, SizeOf(FBuffer));
  298. { Unlike zlib, bzlib does not return an error when avail_in is zero and
  299. it still needs input. To avoid an infinite loop, check for this and
  300. consider it a data error. }
  301. if FStrm.avail_in = 0 then
  302. raise ECompressDataError.Create(SBzlibDataError);
  303. end;
  304. case Check(BZ2_bzDecompress(FStrm), [BZ_OK, BZ_STREAM_END, BZ_DATA_ERROR, BZ_DATA_ERROR_MAGIC]) of
  305. BZ_STREAM_END: FReachedEnd := True;
  306. BZ_DATA_ERROR, BZ_DATA_ERROR_MAGIC: raise ECompressDataError.Create(SBzlibDataError);
  307. end;
  308. end;
  309. end;
  310. procedure TBZDecompressor.Reset;
  311. begin
  312. FStrm.next_in := @FBuffer;
  313. FStrm.avail_in := 0;
  314. { bzlib doesn't offer an optimized 'Reset' function like zlib }
  315. BZ2_bzDecompressEnd(FStrm);
  316. FHeapNextFree := FHeapBase; { discard previous allocations }
  317. Check(BZ2_bzDecompressInit(FStrm, 0, 0), [BZ_OK]);
  318. FReachedEnd := False;
  319. end;
  320. end.