Compress.pas 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. unit Compress;
  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. Abstract compression classes, and some generic compression-related functions
  8. $jrsoftware: issrc/Projects/Compress.pas,v 1.14 2010/09/07 03:09:36 jr Exp $
  9. }
  10. interface
  11. uses
  12. Windows, SysUtils, Int64Em, FileClass;
  13. type
  14. ECompressError = class(Exception);
  15. ECompressDataError = class(ECompressError);
  16. ECompressInternalError = class(ECompressError);
  17. TCompressorProps = class
  18. end;
  19. TCompressorProgressProc = procedure(BytesProcessed: Cardinal) of object;
  20. TCompressorWriteProc = procedure(const Buffer; Count: Longint) of object;
  21. TCustomCompressorClass = class of TCustomCompressor;
  22. TCustomCompressor = class
  23. private
  24. FEntered: Integer;
  25. FProgressProc: TCompressorProgressProc;
  26. FWriteProc: TCompressorWriteProc;
  27. protected
  28. procedure DoCompress(const Buffer; Count: Longint); virtual; abstract;
  29. procedure DoFinish; virtual; abstract;
  30. property ProgressProc: TCompressorProgressProc read FProgressProc;
  31. property WriteProc: TCompressorWriteProc read FWriteProc;
  32. public
  33. constructor Create(AWriteProc: TCompressorWriteProc;
  34. AProgressProc: TCompressorProgressProc; CompressionLevel: Integer;
  35. ACompressorProps: TCompressorProps); virtual;
  36. procedure Compress(const Buffer; Count: Longint);
  37. procedure Finish;
  38. end;
  39. TDecompressorReadProc = function(var Buffer; Count: Longint): Longint of object;
  40. TCustomDecompressorClass = class of TCustomDecompressor;
  41. TCustomDecompressor = class
  42. private
  43. FReadProc: TDecompressorReadProc;
  44. protected
  45. property ReadProc: TDecompressorReadProc read FReadProc;
  46. public
  47. constructor Create(AReadProc: TDecompressorReadProc); virtual;
  48. procedure DecompressInto(var Buffer; Count: Longint); virtual; abstract;
  49. procedure Reset; virtual; abstract;
  50. end;
  51. TStoredCompressor = class(TCustomCompressor)
  52. protected
  53. procedure DoCompress(const Buffer; Count: Longint); override;
  54. procedure DoFinish; override;
  55. end;
  56. TStoredDecompressor = class(TCustomDecompressor)
  57. public
  58. procedure DecompressInto(var Buffer; Count: Longint); override;
  59. procedure Reset; override;
  60. end;
  61. TCompressedBlockWriter = class
  62. private
  63. FCompressor: TCustomCompressor;
  64. FFile: TFile;
  65. FStartPos: Integer64;
  66. FTotalBytesStored: Cardinal;
  67. FInBufferCount, FOutBufferCount: Cardinal;
  68. FInBuffer, FOutBuffer: array[0..4095] of Byte;
  69. procedure CompressorWriteProc(const Buffer; Count: Longint);
  70. procedure DoCompress(const Buf; var Count: Cardinal);
  71. procedure FlushOutputBuffer;
  72. public
  73. constructor Create(AFile: TFile; ACompressorClass: TCustomCompressorClass;
  74. CompressionLevel: Integer; ACompressorProps: TCompressorProps);
  75. destructor Destroy; override;
  76. procedure Finish;
  77. procedure Write(const Buffer; Count: Cardinal);
  78. end;
  79. TCompressedBlockReader = class
  80. private
  81. FDecompressor: TCustomDecompressor;
  82. FFile: TFile;
  83. FInBytesLeft: Cardinal;
  84. FInitialized: Boolean;
  85. FInBufferNext: Cardinal;
  86. FInBufferAvail: Cardinal;
  87. FInBuffer: array[0..4095] of Byte;
  88. function DecompressorReadProc(var Buffer; Count: Longint): Longint;
  89. procedure ReadChunk;
  90. public
  91. constructor Create(AFile: TFile; ADecompressorClass: TCustomDecompressorClass);
  92. destructor Destroy; override;
  93. procedure Read(var Buffer; Count: Cardinal);
  94. end;
  95. function GetCRC32(const Buf; BufSize: Cardinal): Longint;
  96. procedure TransformCallInstructions(var Buf; Size: Integer;
  97. const Encode: Boolean; const AddrOffset: LongWord);
  98. function UpdateCRC32(CurCRC: Longint; const Buf; BufSize: Cardinal): Longint;
  99. implementation
  100. const
  101. SCompressorStateInvalid = 'Compressor state invalid';
  102. SStoredDataError = 'Unexpected end of stream';
  103. SCompressedBlockDataError = 'Compressed block is corrupted';
  104. var
  105. CRC32TableInited: BOOL;
  106. CRC32Table: array[Byte] of Longint;
  107. procedure InitCRC32Table;
  108. var
  109. CRC: Longint;
  110. I, N: Integer;
  111. begin
  112. for I := 0 to 255 do begin
  113. CRC := I;
  114. for N := 0 to 7 do begin
  115. if Odd(CRC) then
  116. CRC := (CRC shr 1) xor Longint($EDB88320)
  117. else
  118. CRC := CRC shr 1;
  119. end;
  120. Crc32Table[I] := CRC;
  121. end;
  122. end;
  123. function UpdateCRC32(CurCRC: Longint; const Buf; BufSize: Cardinal): Longint;
  124. var
  125. P: ^Byte;
  126. begin
  127. if not CRC32TableInited then begin
  128. InitCRC32Table;
  129. InterlockedExchange(Integer(CRC32TableInited), Ord(True));
  130. end;
  131. P := @Buf;
  132. while BufSize <> 0 do begin
  133. CurCRC := CRC32Table[Lo(CurCRC) xor P^] xor (CurCRC shr 8);
  134. Dec(BufSize);
  135. Inc(P);
  136. end;
  137. Result := CurCRC;
  138. end;
  139. function GetCRC32(const Buf; BufSize: Cardinal): Longint;
  140. begin
  141. Result := UpdateCRC32(Longint($FFFFFFFF), Buf, BufSize) xor Longint($FFFFFFFF);
  142. end;
  143. procedure TransformCallInstructions(var Buf; Size: Integer;
  144. const Encode: Boolean; const AddrOffset: LongWord);
  145. { [Version 3] Converts relative addresses in x86/x64 CALL and JMP instructions
  146. to absolute addresses if Encode is True, or the inverse if Encode is False. }
  147. type
  148. PByteArray = ^TByteArray;
  149. TByteArray = array[0..$7FFFFFFE] of Byte;
  150. var
  151. P: PByteArray;
  152. I: Integer;
  153. Addr, Rel: LongWord;
  154. begin
  155. if Size < 5 then
  156. Exit;
  157. Dec(Size, 4);
  158. P := @Buf;
  159. I := 0;
  160. while I < Size do begin
  161. { Does it appear to be a CALL or JMP instruction with a relative 32-bit
  162. address? }
  163. if (P[I] = $E8) or (P[I] = $E9) then begin
  164. Inc(I);
  165. { Verify that the high byte of the address is $00 or $FF. If it isn't,
  166. then what we've encountered most likely isn't a CALL or JMP. }
  167. if (P[I+3] = $00) or (P[I+3] = $FF) then begin
  168. { Change the lower 3 bytes of the address to be relative to the
  169. beginning of the buffer, instead of to the next instruction. If
  170. decoding, do the opposite. }
  171. Addr := (AddrOffset + LongWord(I) + 4) and $FFFFFF; { may wrap, but OK }
  172. Rel := P[I] or (P[I+1] shl 8) or (P[I+2] shl 16);
  173. if not Encode then
  174. Dec(Rel, Addr);
  175. { For a slightly higher compression ratio, we want the resulting high
  176. byte to be $00 for both forward and backward jumps. The high byte
  177. of the original relative address is likely to be the sign extension
  178. of bit 23, so if bit 23 is set, toggle all bits in the high byte. }
  179. if Rel and $800000 <> 0 then
  180. P[I+3] := not P[I+3];
  181. if Encode then
  182. Inc(Rel, Addr);
  183. P[I] := Byte(Rel);
  184. P[I+1] := Byte(Rel shr 8);
  185. P[I+2] := Byte(Rel shr 16);
  186. end;
  187. Inc(I, 4);
  188. end
  189. else
  190. Inc(I);
  191. end;
  192. end;
  193. { TCustomCompressor }
  194. constructor TCustomCompressor.Create(AWriteProc: TCompressorWriteProc;
  195. AProgressProc: TCompressorProgressProc; CompressionLevel: Integer;
  196. ACompressorProps: TCompressorProps);
  197. begin
  198. inherited Create;
  199. FWriteProc := AWriteProc;
  200. FProgressProc := AProgressProc;
  201. end;
  202. procedure TCustomCompressor.Compress(const Buffer; Count: Longint);
  203. begin
  204. if FEntered <> 0 then
  205. raise ECompressInternalError.Create(SCompressorStateInvalid);
  206. Inc(FEntered);
  207. DoCompress(Buffer, Count);
  208. Dec(FEntered);
  209. end;
  210. procedure TCustomCompressor.Finish;
  211. begin
  212. if FEntered <> 0 then
  213. raise ECompressInternalError.Create(SCompressorStateInvalid);
  214. Inc(FEntered);
  215. DoFinish;
  216. Dec(FEntered);
  217. end;
  218. { TCustomDecompressor }
  219. constructor TCustomDecompressor.Create(AReadProc: TDecompressorReadProc);
  220. begin
  221. inherited Create;
  222. FReadProc := AReadProc;
  223. end;
  224. { TStoredCompressor }
  225. procedure TStoredCompressor.DoCompress(const Buffer; Count: Longint);
  226. begin
  227. WriteProc(Buffer, Count);
  228. if Assigned(ProgressProc) then
  229. ProgressProc(Count);
  230. end;
  231. procedure TStoredCompressor.DoFinish;
  232. begin
  233. end;
  234. { TStoredDecompressor }
  235. procedure TStoredDecompressor.DecompressInto(var Buffer; Count: Longint);
  236. var
  237. P: ^Byte;
  238. NumRead: Longint;
  239. begin
  240. P := @Buffer;
  241. while Count > 0 do begin
  242. NumRead := ReadProc(P^, Count);
  243. if NumRead = 0 then
  244. raise ECompressDataError.Create(SStoredDataError);
  245. Inc(P, NumRead);
  246. Dec(Count, NumRead);
  247. end;
  248. end;
  249. procedure TStoredDecompressor.Reset;
  250. begin
  251. end;
  252. { TCompressedBlockWriter }
  253. type
  254. TCompressedBlockHeader = packed record
  255. StoredSize: LongWord; { Total bytes written, including the CRCs }
  256. Compressed: Boolean; { True if data is compressed, False if not }
  257. end;
  258. constructor TCompressedBlockWriter.Create(AFile: TFile;
  259. ACompressorClass: TCustomCompressorClass; CompressionLevel: Integer;
  260. ACompressorProps: TCompressorProps);
  261. var
  262. HdrCRC: Longint;
  263. Hdr: TCompressedBlockHeader;
  264. begin
  265. inherited Create;
  266. FFile := AFile;
  267. if Assigned(ACompressorClass) and (CompressionLevel <> 0) then
  268. FCompressor := ACompressorClass.Create(CompressorWriteProc, nil,
  269. CompressionLevel, ACompressorProps);
  270. FStartPos := AFile.Position;
  271. { Note: These will be overwritten by Finish }
  272. HdrCRC := 0;
  273. AFile.WriteBuffer(HdrCRC, SizeOf(HdrCRC));
  274. Hdr.StoredSize := 0;
  275. Hdr.Compressed := False;
  276. AFile.WriteBuffer(Hdr, SizeOf(Hdr));
  277. end;
  278. destructor TCompressedBlockWriter.Destroy;
  279. begin
  280. FCompressor.Free;
  281. inherited;
  282. end;
  283. procedure TCompressedBlockWriter.FlushOutputBuffer;
  284. { Flushes contents of FOutBuffer into the file, with a preceding CRC }
  285. var
  286. CRC: Longint;
  287. begin
  288. CRC := GetCRC32(FOutBuffer, FOutBufferCount);
  289. FFile.WriteBuffer(CRC, SizeOf(CRC));
  290. Inc(FTotalBytesStored, SizeOf(CRC));
  291. FFile.WriteBuffer(FOutBuffer, FOutBufferCount);
  292. Inc(FTotalBytesStored, FOutBufferCount);
  293. FOutBufferCount := 0;
  294. end;
  295. procedure TCompressedBlockWriter.CompressorWriteProc(const Buffer; Count: Longint);
  296. var
  297. P: ^Byte;
  298. Bytes: Cardinal;
  299. begin
  300. P := @Buffer;
  301. while Count > 0 do begin
  302. Bytes := Count;
  303. if Bytes > SizeOf(FOutBuffer) - FOutBufferCount then
  304. Bytes := SizeOf(FOutBuffer) - FOutBufferCount;
  305. Move(P^, FOutBuffer[FOutBufferCount], Bytes);
  306. Inc(FOutBufferCount, Bytes);
  307. if FOutBufferCount = SizeOf(FOutBuffer) then
  308. FlushOutputBuffer;
  309. Dec(Count, Bytes);
  310. Inc(P, Bytes);
  311. end;
  312. end;
  313. procedure TCompressedBlockWriter.DoCompress(const Buf; var Count: Cardinal);
  314. begin
  315. if Count > 0 then begin
  316. if Assigned(FCompressor) then
  317. FCompressor.Compress(Buf, Count)
  318. else
  319. CompressorWriteProc(Buf, Count);
  320. end;
  321. Count := 0;
  322. end;
  323. procedure TCompressedBlockWriter.Write(const Buffer; Count: Cardinal);
  324. var
  325. P: ^Byte;
  326. Bytes: Cardinal;
  327. begin
  328. { Writes are buffered strictly as an optimization, to avoid feeding tiny
  329. blocks to the compressor }
  330. P := @Buffer;
  331. while Count > 0 do begin
  332. Bytes := Count;
  333. if Bytes > SizeOf(FInBuffer) - FInBufferCount then
  334. Bytes := SizeOf(FInBuffer) - FInBufferCount;
  335. Move(P^, FInBuffer[FInBufferCount], Bytes);
  336. Inc(FInBufferCount, Bytes);
  337. if FInBufferCount = SizeOf(FInBuffer) then
  338. DoCompress(FInBuffer, FInBufferCount);
  339. Dec(Count, Bytes);
  340. Inc(P, Bytes);
  341. end;
  342. end;
  343. procedure TCompressedBlockWriter.Finish;
  344. var
  345. Pos: Integer64;
  346. HdrCRC: Longint;
  347. Hdr: TCompressedBlockHeader;
  348. begin
  349. DoCompress(FInBuffer, FInBufferCount);
  350. if Assigned(FCompressor) then
  351. FCompressor.Finish;
  352. if FOutBufferCount > 0 then
  353. FlushOutputBuffer;
  354. Pos := FFile.Position;
  355. FFile.Seek64(FStartPos);
  356. Hdr.StoredSize := FTotalBytesStored;
  357. Hdr.Compressed := Assigned(FCompressor);
  358. HdrCRC := GetCRC32(Hdr, SizeOf(Hdr));
  359. FFile.WriteBuffer(HdrCRC, SizeOf(HdrCRC));
  360. FFile.WriteBuffer(Hdr, SizeOf(Hdr));
  361. FFile.Seek64(Pos);
  362. end;
  363. { TCompressedBlockReader }
  364. constructor TCompressedBlockReader.Create(AFile: TFile;
  365. ADecompressorClass: TCustomDecompressorClass);
  366. var
  367. HdrCRC: Longint;
  368. Hdr: TCompressedBlockHeader;
  369. P: Integer64;
  370. begin
  371. inherited Create;
  372. FFile := AFile;
  373. if (AFile.Read(HdrCRC, SizeOf(HdrCRC)) <> SizeOf(HdrCRC)) or
  374. (AFile.Read(Hdr, SizeOf(Hdr)) <> SizeOf(Hdr)) then
  375. raise ECompressDataError.Create(SCompressedBlockDataError);
  376. if HdrCRC <> GetCRC32(Hdr, SizeOf(Hdr)) then
  377. raise ECompressDataError.Create(SCompressedBlockDataError);
  378. P := AFile.Position;
  379. Inc64(P, Hdr.StoredSize);
  380. if Compare64(P, AFile.Size) > 0 then
  381. raise ECompressDataError.Create(SCompressedBlockDataError);
  382. if Hdr.Compressed then
  383. FDecompressor := ADecompressorClass.Create(DecompressorReadProc);
  384. FInBytesLeft := Hdr.StoredSize;
  385. FInitialized := True;
  386. end;
  387. destructor TCompressedBlockReader.Destroy;
  388. var
  389. P: Integer64;
  390. begin
  391. FDecompressor.Free;
  392. if FInitialized then begin
  393. { Must seek ahead if the caller didn't read everything that was originally
  394. compressed, or if it did read everything but zlib is in a "CHECK" state
  395. (i.e. it didn't read and verify the trailing adler32 yet due to lack of
  396. input bytes). }
  397. P := FFile.Position;
  398. Inc64(P, FInBytesLeft);
  399. FFile.Seek64(P);
  400. end;
  401. inherited;
  402. end;
  403. procedure TCompressedBlockReader.ReadChunk;
  404. var
  405. CRC: Longint;
  406. Len: Cardinal;
  407. begin
  408. { Read chunk CRC }
  409. if FInBytesLeft < SizeOf(CRC) + 1 then
  410. raise ECompressDataError.Create(SCompressedBlockDataError);
  411. FFile.ReadBuffer(CRC, SizeOf(CRC));
  412. Dec(FInBytesLeft, SizeOf(CRC));
  413. { Read chunk data }
  414. Len := FInBytesLeft;
  415. if Len > SizeOf(FInBuffer) then
  416. Len := SizeOf(FInBuffer);
  417. FFile.ReadBuffer(FInBuffer, Len);
  418. Dec(FInBytesLeft, Len);
  419. FInBufferNext := 0;
  420. FInBufferAvail := Len;
  421. if CRC <> GetCRC32(FInBuffer, Len) then
  422. raise ECompressDataError.Create(SCompressedBlockDataError);
  423. end;
  424. function TCompressedBlockReader.DecompressorReadProc(var Buffer;
  425. Count: Longint): Longint;
  426. var
  427. P: ^Byte;
  428. Bytes: Cardinal;
  429. begin
  430. Result := 0;
  431. P := @Buffer;
  432. while Count > 0 do begin
  433. if FInBufferAvail = 0 then begin
  434. if FInBytesLeft = 0 then
  435. Break;
  436. ReadChunk;
  437. end;
  438. Bytes := Count;
  439. if Bytes > FInBufferAvail then
  440. Bytes := FInBufferAvail;
  441. Move(FInBuffer[FInBufferNext], P^, Bytes);
  442. Inc(FInBufferNext, Bytes);
  443. Dec(FInBufferAvail, Bytes);
  444. Inc(P, Bytes);
  445. Dec(Count, Bytes);
  446. Inc(Result, Bytes);
  447. end;
  448. end;
  449. procedure TCompressedBlockReader.Read(var Buffer; Count: Cardinal);
  450. begin
  451. if Assigned(FDecompressor) then
  452. FDecompressor.DecompressInto(Buffer, Count)
  453. else begin
  454. { Not compressed -- call DecompressorReadProc directly }
  455. if Cardinal(DecompressorReadProc(Buffer, Count)) <> Count then
  456. raise ECompressDataError.Create(SCompressedBlockDataError);
  457. end;
  458. end;
  459. end.