Compiler.CompressionHandler.pas 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. unit Compiler.CompressionHandler;
  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. Compression handler used by TSetupCompiler
  8. }
  9. interface
  10. uses
  11. Classes,
  12. SHA256, ChaCha20, Shared.Struct, Shared.Int64Em, Shared.FileClass, Compression.Base,
  13. Compiler.SetupCompiler;
  14. type
  15. TCompressionHandler = class
  16. private
  17. FCachedCompressors: TList;
  18. FCompiler: TSetupCompiler;
  19. FCompressor: TCustomCompressor;
  20. FChunkBytesRead: Integer64;
  21. FChunkBytesWritten: Integer64;
  22. FChunkEncrypted: Boolean;
  23. FChunkFirstSlice: Integer;
  24. FChunkStarted: Boolean;
  25. FChunkStartOffset: Longint;
  26. FCryptContext: TChaCha20Context;
  27. FCurSlice: Integer;
  28. FDestFile: TFile;
  29. FDestFileIsDiskSlice: Boolean;
  30. FInitialBytesCompressedSoFar: Integer64;
  31. FSliceBaseOffset: Cardinal;
  32. FSliceBytesLeft: Cardinal;
  33. procedure EndSlice;
  34. procedure NewSlice(const Filename: String);
  35. public
  36. constructor Create(ACompiler: TSetupCompiler; const InitialSliceFilename: String);
  37. destructor Destroy; override;
  38. procedure CompressFile(const SourceFile: TFile; Bytes: Integer64;
  39. const CallOptimize: Boolean; out SHA256Sum: TSHA256Digest);
  40. procedure EndChunk;
  41. procedure Finish;
  42. procedure NewChunk(const ACompressorClass: TCustomCompressorClass;
  43. const ACompressLevel: Integer; const ACompressorProps: TCompressorProps;
  44. const AUseEncryption: Boolean; const ACryptKey: TSetupEncryptionKey);
  45. procedure ProgressProc(BytesProcessed: Cardinal);
  46. function ReserveBytesOnSlice(const Bytes: Cardinal): Boolean;
  47. procedure WriteProc(const Buf; BufSize: Longint);
  48. property ChunkBytesRead: Integer64 read FChunkBytesRead;
  49. property ChunkBytesWritten: Integer64 read FChunkBytesWritten;
  50. property ChunkEncrypted: Boolean read FChunkEncrypted;
  51. property ChunkFirstSlice: Integer read FChunkFirstSlice;
  52. property ChunkStartOffset: Longint read FChunkStartOffset;
  53. property ChunkStarted: Boolean read FChunkStarted;
  54. property CurSlice: Integer read FCurSlice;
  55. end;
  56. implementation
  57. uses
  58. SysUtils, Compiler.Messages, Compiler.HelperFunc;
  59. constructor TCompressionHandler.Create(ACompiler: TSetupCompiler;
  60. const InitialSliceFilename: String);
  61. begin
  62. inherited Create;
  63. FCompiler := ACompiler;
  64. FCurSlice := -1;
  65. FCachedCompressors := TList.Create;
  66. NewSlice(InitialSliceFilename);
  67. end;
  68. destructor TCompressionHandler.Destroy;
  69. var
  70. I: Integer;
  71. begin
  72. if Assigned(FCachedCompressors) then begin
  73. for I := FCachedCompressors.Count-1 downto 0 do
  74. TCustomCompressor(FCachedCompressors[I]).Free;
  75. FreeAndNil(FCachedCompressors);
  76. end;
  77. FreeAndNil(FDestFile);
  78. inherited;
  79. end;
  80. procedure TCompressionHandler.Finish;
  81. begin
  82. EndChunk;
  83. EndSlice;
  84. end;
  85. procedure TCompressionHandler.EndSlice;
  86. var
  87. DiskSliceHeader: TDiskSliceHeader;
  88. begin
  89. if Assigned(FDestFile) then begin
  90. if FDestFileIsDiskSlice then begin
  91. DiskSliceHeader.TotalSize := FDestFile.Size.Lo;
  92. FDestFile.Seek(SizeOf(DiskSliceID));
  93. FDestFile.WriteBuffer(DiskSliceHeader, SizeOf(DiskSliceHeader));
  94. end;
  95. FreeAndNil(FDestFile);
  96. end;
  97. end;
  98. procedure TCompressionHandler.NewSlice(const Filename: String);
  99. function GenerateSliceFilename(const Compiler: TSetupCompiler;
  100. const ASlice: Integer): String;
  101. begin
  102. var SlicesPerDisk := Compiler.GetSlicesPerDisk;
  103. var OutputBaseFilename := Compiler.GetOutputBaseFilename;
  104. var Major := ASlice div SlicesPerDisk + 1;
  105. var Minor := ASlice mod SlicesPerDisk;
  106. if SlicesPerDisk = 1 then
  107. Result := Format('%s-%d.bin', [OutputBaseFilename, Major])
  108. else
  109. Result := Format('%s-%d%s.bin', [OutputBaseFilename, Major,
  110. Chr(Ord('a') + Minor)]);
  111. end;
  112. begin
  113. var DiskSliceSize := FCompiler.GetDiskSliceSize;
  114. EndSlice;
  115. Inc(FCurSlice);
  116. if (FCurSlice > 0) and not FCompiler.GetDiskSpanning then
  117. FCompiler.AbortCompileFmt(SCompilerMustUseDiskSpanning,
  118. [DiskSliceSize]);
  119. if Filename = '' then begin
  120. FDestFileIsDiskSlice := True;
  121. FDestFile := TFile.Create(FCompiler.GetOutputDir +
  122. GenerateSliceFilename(FCompiler, FCurSlice), fdCreateAlways, faReadWrite, fsNone);
  123. FDestFile.WriteBuffer(DiskSliceID, SizeOf(DiskSliceID));
  124. var DiskHeader: TDiskSliceHeader;
  125. DiskHeader.TotalSize := 0;
  126. FDestFile.WriteBuffer(DiskHeader, SizeOf(DiskHeader));
  127. FSliceBaseOffset := 0;
  128. FSliceBytesLeft := DiskSliceSize - (SizeOf(DiskSliceID) + SizeOf(DiskHeader));
  129. end
  130. else begin
  131. FDestFileIsDiskSlice := False;
  132. FDestFile := TFile.Create(Filename, fdOpenExisting, faReadWrite, fsNone);
  133. FDestFile.SeekToEnd;
  134. FSliceBaseOffset := FDestFile.Position.Lo;
  135. FSliceBytesLeft := Cardinal(DiskSliceSize) - FSliceBaseOffset;
  136. end;
  137. end;
  138. function TCompressionHandler.ReserveBytesOnSlice(const Bytes: Cardinal): Boolean;
  139. begin
  140. if FSliceBytesLeft >= Bytes then begin
  141. Dec(FSliceBytesLeft, Bytes);
  142. Result := True;
  143. end
  144. else
  145. Result := False;
  146. end;
  147. procedure TCompressionHandler.NewChunk(const ACompressorClass: TCustomCompressorClass;
  148. const ACompressLevel: Integer; const ACompressorProps: TCompressorProps;
  149. const AUseEncryption: Boolean; const ACryptKey: TSetupEncryptionKey);
  150. procedure SelectCompressor;
  151. var
  152. I: Integer;
  153. C: TCustomCompressor;
  154. begin
  155. { No current compressor, or changing compressor classes? }
  156. if (FCompressor = nil) or (FCompressor.ClassType <> ACompressorClass) then begin
  157. FCompressor := nil;
  158. { Search cache for requested class }
  159. for I := FCachedCompressors.Count-1 downto 0 do begin
  160. C := FCachedCompressors[I];
  161. if C.ClassType = ACompressorClass then begin
  162. FCompressor := C;
  163. Break;
  164. end;
  165. end;
  166. end;
  167. if FCompressor = nil then begin
  168. FCachedCompressors.Expand;
  169. FCompressor := ACompressorClass.Create(WriteProc, ProgressProc,
  170. ACompressLevel, ACompressorProps);
  171. FCachedCompressors.Add(FCompressor);
  172. end;
  173. end;
  174. procedure InitEncryption;
  175. begin
  176. { Create a unique nonce from the base nonce }
  177. var Nonce := FCompiler.GetEncryptionBaseNonce;
  178. Nonce.RandomXorStartOffset := Nonce.RandomXorStartOffset xor FChunkStartOffset;
  179. Nonce.RandomXorFirstSlice := Nonce.RandomXorFirstSlice xor FChunkFirstSlice;
  180. XChaCha20Init(FCryptContext, ACryptKey[0], Length(ACryptKey), Nonce, SizeOf(Nonce), 0);
  181. end;
  182. var
  183. MinBytesLeft: Cardinal;
  184. begin
  185. EndChunk;
  186. { If there isn't enough room left to start a new chunk on the current slice,
  187. start a new slice }
  188. MinBytesLeft := SizeOf(ZLIBID);
  189. Inc(MinBytesLeft); { for at least one byte of data }
  190. if FSliceBytesLeft < MinBytesLeft then
  191. NewSlice('');
  192. FChunkFirstSlice := FCurSlice;
  193. FChunkStartOffset := FDestFile.Position.Lo - FSliceBaseOffset;
  194. FDestFile.WriteBuffer(ZLIBID, SizeOf(ZLIBID));
  195. Dec(FSliceBytesLeft, SizeOf(ZLIBID));
  196. FChunkBytesRead.Hi := 0;
  197. FChunkBytesRead.Lo := 0;
  198. FChunkBytesWritten.Hi := 0;
  199. FChunkBytesWritten.Lo := 0;
  200. FInitialBytesCompressedSoFar := FCompiler.GetBytesCompressedSoFar;
  201. SelectCompressor;
  202. FChunkEncrypted := AUseEncryption;
  203. if AUseEncryption then
  204. InitEncryption;
  205. FChunkStarted := True;
  206. end;
  207. procedure TCompressionHandler.EndChunk;
  208. begin
  209. if not FChunkStarted then
  210. Exit;
  211. FChunkStarted := False;
  212. FCompressor.Finish;
  213. { In case we didn't get a ProgressProc call after the final block: }
  214. FCompiler.SetBytesCompressedSoFar(FInitialBytesCompressedSoFar);
  215. FCompiler.AddBytesCompressedSoFar(FChunkBytesRead);
  216. FCompiler.CallIdleProc;
  217. end;
  218. procedure TCompressionHandler.CompressFile(const SourceFile: TFile;
  219. Bytes: Integer64; const CallOptimize: Boolean; out SHA256Sum: TSHA256Digest);
  220. var
  221. Context: TSHA256Context;
  222. AddrOffset: LongWord;
  223. BufSize: Cardinal;
  224. Buf: array[0..65535] of Byte;
  225. { ^ *must* be the same buffer size used in Setup (TFileExtractor), otherwise
  226. the TransformCallInstructions call will break }
  227. begin
  228. SHA256Init(Context);
  229. AddrOffset := 0;
  230. while True do begin
  231. BufSize := SizeOf(Buf);
  232. if (Bytes.Hi = 0) and (Bytes.Lo < BufSize) then
  233. BufSize := Bytes.Lo;
  234. if BufSize = 0 then
  235. Break;
  236. SourceFile.ReadBuffer(Buf, BufSize);
  237. Inc64(FChunkBytesRead, BufSize);
  238. Dec64(Bytes, BufSize);
  239. SHA256Update(Context, Buf, BufSize);
  240. if CallOptimize then begin
  241. TransformCallInstructions(Buf, BufSize, True, AddrOffset);
  242. Inc(AddrOffset, BufSize); { may wrap, but OK }
  243. end;
  244. FCompressor.Compress(Buf, BufSize);
  245. end;
  246. SHA256Sum := SHA256Final(Context);
  247. end;
  248. procedure TCompressionHandler.WriteProc(const Buf; BufSize: Longint);
  249. var
  250. P, P2: Pointer;
  251. S: Cardinal;
  252. begin
  253. FCompiler.CallIdleProc;
  254. P := @Buf;
  255. while BufSize > 0 do begin
  256. S := BufSize;
  257. if FSliceBytesLeft = 0 then
  258. NewSlice('');
  259. if S > Cardinal(FSliceBytesLeft) then
  260. S := FSliceBytesLeft;
  261. if not FChunkEncrypted then
  262. FDestFile.WriteBuffer(P^, S)
  263. else begin
  264. { Using encryption. Can't modify Buf in place so allocate a new,
  265. temporary buffer. }
  266. GetMem(P2, S);
  267. try
  268. XChaCha20Crypt(FCryptContext, P^, P2^, S);
  269. FDestFile.WriteBuffer(P2^, S)
  270. finally
  271. FreeMem(P2);
  272. end;
  273. end;
  274. Inc64(FChunkBytesWritten, S);
  275. Inc(Cardinal(P), S);
  276. Dec(BufSize, S);
  277. Dec(FSliceBytesLeft, S);
  278. end;
  279. end;
  280. procedure TCompressionHandler.ProgressProc(BytesProcessed: Cardinal);
  281. begin
  282. FCompiler.AddBytesCompressedSoFar(BytesProcessed);
  283. FCompiler.CallIdleProc;
  284. end;
  285. end.