Compression.Base.pas 16 KB

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