2
0

Compression.Base.pas 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522
  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.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: Cardinal) 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: Cardinal); 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: Cardinal);
  36. procedure Finish;
  37. end;
  38. TDecompressorReadProc = function(var Buffer; Count: Cardinal): Cardinal 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: Cardinal); 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: Cardinal); override;
  54. procedure DoFinish; override;
  55. end;
  56. TStoredDecompressor = class(TCustomDecompressor)
  57. public
  58. procedure DecompressInto(var Buffer; Count: Cardinal); override;
  59. procedure Reset; override;
  60. end;
  61. TCompressedBlockWriter = class
  62. private
  63. FCompressor: TCustomCompressor;
  64. FFile: TFile;
  65. FStartPos: Int64;
  66. FTotalBytesStored: Int64;
  67. FInBufferCount, FOutBufferCount: Cardinal;
  68. FInBuffer, FOutBuffer: array[0..4095] of Byte;
  69. FEncrypt: Boolean;
  70. FCryptContext: TChaCha20Context;
  71. procedure CompressorWriteProc(const Buffer; Count: Cardinal);
  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: Int64;
  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: Cardinal): Cardinal;
  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): Integer;
  104. procedure TransformCallInstructions(var Buf; Size: Cardinal;
  105. const Encode: Boolean; const AddrOffset: UInt32);
  106. function UpdateCRC32(CurCRC: Integer; const Buf; BufSize: Cardinal): Integer;
  107. implementation
  108. uses
  109. UnsignedFunc;
  110. const
  111. SCompressorStateInvalid = 'Compressor state invalid';
  112. SStoredDataError = 'Unexpected end of stream';
  113. SCompressedBlockDataError = 'Compressed block is corrupted';
  114. var
  115. CRC32TableInited: BOOL;
  116. CRC32Table: array[Byte] of Integer;
  117. procedure InitCRC32Table;
  118. var
  119. CRC: Integer;
  120. I, N: Integer;
  121. begin
  122. for I := 0 to 255 do begin
  123. CRC := I;
  124. for N := 0 to 7 do begin
  125. if Odd(CRC) then
  126. CRC := (CRC shr 1) xor Integer($EDB88320)
  127. else
  128. CRC := CRC shr 1;
  129. end;
  130. Crc32Table[I] := CRC;
  131. end;
  132. end;
  133. function UpdateCRC32(CurCRC: Integer; const Buf; BufSize: Cardinal): Integer;
  134. begin
  135. if not CRC32TableInited then begin
  136. InitCRC32Table;
  137. InterlockedExchange(Integer(CRC32TableInited), Ord(True));
  138. end;
  139. var P: PByte := @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): Integer;
  148. begin
  149. Result := UpdateCRC32(Integer($FFFFFFFF), Buf, BufSize) xor Integer($FFFFFFFF);
  150. end;
  151. procedure TransformCallInstructions(var Buf; Size: Cardinal;
  152. const Encode: Boolean; const AddrOffset: UInt32);
  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. var
  156. P: PByte;
  157. I: Cardinal;
  158. Addr, Rel: UInt32;
  159. begin
  160. if Size < 5 then
  161. Exit;
  162. Dec(Size, 4);
  163. P := @Buf;
  164. I := 0;
  165. while I < Size do begin
  166. { Does it appear to be a CALL or JMP instruction with a relative 32-bit
  167. address? }
  168. if (P[I] = $E8) or (P[I] = $E9) then begin
  169. Inc(I);
  170. { Verify that the high byte of the address is $00 or $FF. If it isn't,
  171. then what we've encountered most likely isn't a CALL or JMP. }
  172. if (P[I+3] = $00) or (P[I+3] = $FF) then begin
  173. { Change the lower 3 bytes of the address to be relative to the
  174. beginning of the buffer, instead of to the next instruction. If
  175. decoding, do the opposite. }
  176. Addr := (AddrOffset + I + 4) and $FFFFFF; { may wrap, but OK }
  177. Rel := P[I] or (P[I+1] shl 8) or (P[I+2] shl 16);
  178. if not Encode then
  179. Dec(Rel, Addr);
  180. { For a slightly higher compression ratio, we want the resulting high
  181. byte to be $00 for both forward and backward jumps. The high byte
  182. of the original relative address is likely to be the sign extension
  183. of bit 23, so if bit 23 is set, toggle all bits in the high byte. }
  184. if Rel and $800000 <> 0 then
  185. P[I+3] := not P[I+3];
  186. if Encode then
  187. Inc(Rel, Addr);
  188. P[I] := Byte(Rel);
  189. P[I+1] := Byte(Rel shr 8);
  190. P[I+2] := Byte(Rel shr 16);
  191. end;
  192. Inc(I, 4);
  193. end
  194. else
  195. Inc(I);
  196. end;
  197. end;
  198. { TCustomCompressor }
  199. constructor TCustomCompressor.Create(AWriteProc: TCompressorWriteProc;
  200. AProgressProc: TCompressorProgressProc; CompressionLevel: Integer;
  201. ACompressorProps: TCompressorProps);
  202. begin
  203. inherited Create;
  204. FWriteProc := AWriteProc;
  205. FProgressProc := AProgressProc;
  206. end;
  207. procedure TCustomCompressor.Compress(const Buffer; Count: Cardinal);
  208. begin
  209. if FEntered <> 0 then
  210. raise ECompressInternalError.Create(SCompressorStateInvalid);
  211. Inc(FEntered);
  212. DoCompress(Buffer, Count);
  213. Dec(FEntered);
  214. end;
  215. procedure TCustomCompressor.Finish;
  216. begin
  217. if FEntered <> 0 then
  218. raise ECompressInternalError.Create(SCompressorStateInvalid);
  219. Inc(FEntered);
  220. DoFinish;
  221. Dec(FEntered);
  222. end;
  223. { TCustomDecompressor }
  224. constructor TCustomDecompressor.Create(AReadProc: TDecompressorReadProc);
  225. begin
  226. inherited Create;
  227. FReadProc := AReadProc;
  228. end;
  229. { TStoredCompressor }
  230. procedure TStoredCompressor.DoCompress(const Buffer; Count: Cardinal);
  231. begin
  232. WriteProc(Buffer, Count);
  233. if Assigned(ProgressProc) then
  234. ProgressProc(Count);
  235. end;
  236. procedure TStoredCompressor.DoFinish;
  237. begin
  238. end;
  239. { TStoredDecompressor }
  240. procedure TStoredDecompressor.DecompressInto(var Buffer; Count: Cardinal);
  241. begin
  242. var P: PByte := @Buffer;
  243. while Count > 0 do begin
  244. var NumRead := ReadProc(P^, Count);
  245. if NumRead = 0 then
  246. raise ECompressDataError.Create(SStoredDataError);
  247. Inc(P, NumRead);
  248. Dec(Count, NumRead);
  249. end;
  250. end;
  251. procedure TStoredDecompressor.Reset;
  252. begin
  253. end;
  254. { TCompressedBlockWriter }
  255. type
  256. TCompressedBlockHeader = packed record
  257. StoredSize: Int64; { Total bytes written, including the CRCs }
  258. Compressed: Boolean; { True if data is compressed, False if not }
  259. end;
  260. constructor TCompressedBlockWriter.Create(AFile: TFile;
  261. ACompressorClass: TCustomCompressorClass; CompressionLevel: Integer;
  262. ACompressorProps: TCompressorProps);
  263. var
  264. HdrCRC: Integer;
  265. Hdr: TCompressedBlockHeader;
  266. begin
  267. inherited Create;
  268. FFile := AFile;
  269. if Assigned(ACompressorClass) and (CompressionLevel <> 0) then
  270. FCompressor := ACompressorClass.Create(CompressorWriteProc, nil,
  271. CompressionLevel, ACompressorProps);
  272. FStartPos := AFile.Position;
  273. { Note: These will be overwritten by Finish }
  274. HdrCRC := 0;
  275. AFile.WriteBuffer(HdrCRC, SizeOf(HdrCRC));
  276. Hdr.StoredSize := 0;
  277. Hdr.Compressed := False;
  278. AFile.WriteBuffer(Hdr, SizeOf(Hdr));
  279. end;
  280. destructor TCompressedBlockWriter.Destroy;
  281. begin
  282. FCompressor.Free;
  283. inherited;
  284. end;
  285. procedure TCompressedBlockWriter.InitEncryption(const CryptKey: TSetupEncryptionKey;
  286. const EncryptionBaseNonce: TSetupEncryptionNonce; const SpecialCryptContextType: TSpecialCryptContextType);
  287. begin
  288. InitCryptContext(CryptKey, EncryptionBaseNonce, SpecialCryptContextType, FCryptContext);
  289. FEncrypt := True;
  290. end;
  291. procedure TCompressedBlockWriter.FlushOutputBuffer;
  292. { Flushes contents of FOutBuffer into the file, with a preceding CRC }
  293. var
  294. CRC: Integer;
  295. begin
  296. if FEncrypt then
  297. XChaCha20Crypt(FCryptContext, FOutBuffer, FOutBuffer, FOutBufferCount);
  298. CRC := GetCRC32(FOutBuffer, FOutBufferCount);
  299. FFile.WriteBuffer(CRC, SizeOf(CRC));
  300. Inc(FTotalBytesStored, SizeOf(CRC));
  301. FFile.WriteBuffer(FOutBuffer, FOutBufferCount);
  302. Inc(FTotalBytesStored, FOutBufferCount);
  303. FOutBufferCount := 0;
  304. end;
  305. procedure TCompressedBlockWriter.CompressorWriteProc(const Buffer; Count: Cardinal);
  306. begin
  307. var P: PByte := @Buffer;
  308. while Count > 0 do begin
  309. var Bytes := Count;
  310. if Bytes > SizeOf(FOutBuffer) - FOutBufferCount then
  311. Bytes := SizeOf(FOutBuffer) - FOutBufferCount;
  312. UMove(P^, FOutBuffer[FOutBufferCount], Bytes);
  313. Inc(FOutBufferCount, Bytes);
  314. if FOutBufferCount = SizeOf(FOutBuffer) then
  315. FlushOutputBuffer;
  316. Dec(Count, Bytes);
  317. Inc(P, Bytes);
  318. end;
  319. end;
  320. procedure TCompressedBlockWriter.DoCompress(const Buf; var Count: Cardinal);
  321. begin
  322. if Count > 0 then begin
  323. if Assigned(FCompressor) then
  324. FCompressor.Compress(Buf, Count)
  325. else
  326. CompressorWriteProc(Buf, Count);
  327. end;
  328. Count := 0;
  329. end;
  330. procedure TCompressedBlockWriter.Write(const Buffer; Count: Cardinal);
  331. begin
  332. { Writes are buffered strictly as an optimization, to avoid feeding tiny
  333. blocks to the compressor }
  334. var P: PByte := @Buffer;
  335. while Count > 0 do begin
  336. var Bytes := Count;
  337. if Bytes > SizeOf(FInBuffer) - FInBufferCount then
  338. Bytes := SizeOf(FInBuffer) - FInBufferCount;
  339. UMove(P^, FInBuffer[FInBufferCount], Bytes);
  340. Inc(FInBufferCount, Bytes);
  341. if FInBufferCount = SizeOf(FInBuffer) then
  342. DoCompress(FInBuffer, FInBufferCount);
  343. Dec(Count, Bytes);
  344. Inc(P, Bytes);
  345. end;
  346. end;
  347. procedure TCompressedBlockWriter.Finish;
  348. var
  349. HdrCRC: Integer;
  350. Hdr: TCompressedBlockHeader;
  351. begin
  352. DoCompress(FInBuffer, FInBufferCount);
  353. if Assigned(FCompressor) then
  354. FCompressor.Finish;
  355. if FOutBufferCount > 0 then
  356. FlushOutputBuffer;
  357. var Pos := FFile.Position;
  358. FFile.Seek(FStartPos);
  359. Hdr.StoredSize := FTotalBytesStored;
  360. Hdr.Compressed := Assigned(FCompressor);
  361. HdrCRC := GetCRC32(Hdr, SizeOf(Hdr));
  362. FFile.WriteBuffer(HdrCRC, SizeOf(HdrCRC));
  363. FFile.WriteBuffer(Hdr, SizeOf(Hdr));
  364. FFile.Seek(Pos);
  365. end;
  366. { TCompressedBlockReader }
  367. constructor TCompressedBlockReader.Create(AFile: TFile;
  368. ADecompressorClass: TCustomDecompressorClass);
  369. var
  370. HdrCRC: Integer;
  371. Hdr: TCompressedBlockHeader;
  372. begin
  373. inherited Create;
  374. FFile := AFile;
  375. if (AFile.Read(HdrCRC, SizeOf(HdrCRC)) <> SizeOf(HdrCRC)) or
  376. (AFile.Read(Hdr, SizeOf(Hdr)) <> SizeOf(Hdr)) then
  377. raise ECompressDataError.Create(SCompressedBlockDataError);
  378. if HdrCRC <> GetCRC32(Hdr, SizeOf(Hdr)) then
  379. raise ECompressDataError.Create(SCompressedBlockDataError);
  380. if (Hdr.StoredSize < 0) or (AFile.Position > AFile.Size - Hdr.StoredSize) 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. begin
  389. FDecompressor.Free;
  390. if FInitialized then begin
  391. { Must seek ahead if the caller didn't read everything that was originally
  392. compressed, or if it did read everything but zlib is in a "CHECK" state
  393. (i.e. it didn't read and verify the trailing adler32 yet due to lack of
  394. input bytes). }
  395. var P := FFile.Position;
  396. Inc(P, FInBytesLeft);
  397. FFile.Seek(P);
  398. end;
  399. inherited;
  400. end;
  401. procedure TCompressedBlockReader.InitDecryption(const CryptKey: TSetupEncryptionKey;
  402. const EncryptionBaseNonce: TSetupEncryptionNonce; const SpecialCryptContextType: TSpecialCryptContextType);
  403. begin
  404. InitCryptContext(CryptKey, EncryptionBaseNonce, SpecialCryptContextType, FCryptContext);
  405. FDecrypt := True;
  406. end;
  407. procedure TCompressedBlockReader.ReadChunk;
  408. var
  409. CRC: Integer;
  410. Len: Cardinal;
  411. begin
  412. { Read chunk CRC }
  413. if FInBytesLeft < SizeOf(CRC) + 1 then
  414. raise ECompressDataError.Create(SCompressedBlockDataError);
  415. FFile.ReadBuffer(CRC, SizeOf(CRC));
  416. Dec(FInBytesLeft, SizeOf(CRC));
  417. { Read chunk data }
  418. if FInBytesLeft > SizeOf(FInBuffer) then
  419. Len := SizeOf(FInBuffer)
  420. else
  421. Len := Cardinal(FInBytesLeft);
  422. FFile.ReadBuffer(FInBuffer, Len);
  423. Dec(FInBytesLeft, Len);
  424. FInBufferNext := 0;
  425. FInBufferAvail := Len;
  426. if CRC <> GetCRC32(FInBuffer, Len) then
  427. raise ECompressDataError.Create(SCompressedBlockDataError);
  428. if FDecrypt then
  429. XChaCha20Crypt(FCryptContext, FInBuffer, FInBuffer, Len);
  430. end;
  431. function TCompressedBlockReader.DecompressorReadProc(var Buffer;
  432. Count: Cardinal): Cardinal;
  433. begin
  434. Result := 0;
  435. var P: PByte := @Buffer;
  436. while Count > 0 do begin
  437. if FInBufferAvail = 0 then begin
  438. if FInBytesLeft = 0 then
  439. Break;
  440. ReadChunk;
  441. end;
  442. var Bytes := Count;
  443. if Bytes > FInBufferAvail then
  444. Bytes := FInBufferAvail;
  445. UMove(FInBuffer[FInBufferNext], P^, Bytes);
  446. Inc(FInBufferNext, Bytes);
  447. Dec(FInBufferAvail, Bytes);
  448. Inc(P, Bytes);
  449. Dec(Count, Bytes);
  450. Inc(Result, Bytes);
  451. end;
  452. end;
  453. procedure TCompressedBlockReader.Read(var Buffer; Count: Cardinal);
  454. begin
  455. if Assigned(FDecompressor) then
  456. FDecompressor.DecompressInto(Buffer, Count)
  457. else begin
  458. { Not compressed -- call DecompressorReadProc directly }
  459. if DecompressorReadProc(Buffer, Count) <> Count then
  460. raise ECompressDataError.Create(SCompressedBlockDataError);
  461. end;
  462. end;
  463. end.