Compiler.CompressionHandler.pas 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  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, Shared.EncryptionFunc, 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;
  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;
  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. var
  175. MinBytesLeft: Cardinal;
  176. begin
  177. EndChunk;
  178. { If there isn't enough room left to start a new chunk on the current slice,
  179. start a new slice }
  180. MinBytesLeft := SizeOf(ZLIBID);
  181. Inc(MinBytesLeft); { for at least one byte of data }
  182. if FSliceBytesLeft < MinBytesLeft then
  183. NewSlice('');
  184. FChunkFirstSlice := FCurSlice;
  185. FChunkStartOffset := FDestFile.Position - FSliceBaseOffset;
  186. FDestFile.WriteBuffer(ZLIBID, SizeOf(ZLIBID));
  187. Dec(FSliceBytesLeft, SizeOf(ZLIBID));
  188. FChunkBytesRead := To64(0);
  189. FChunkBytesWritten := To64(0);
  190. FInitialBytesCompressedSoFar := FCompiler.GetBytesCompressedSoFar;
  191. SelectCompressor;
  192. FChunkEncrypted := AUseEncryption;
  193. if AUseEncryption then
  194. InitCryptContext(ACryptKey, FCompiler.GetEncryptionBaseNonce, FChunkStartOffset, FChunkFirstSlice, FCryptContext);
  195. FChunkStarted := True;
  196. end;
  197. procedure TCompressionHandler.EndChunk;
  198. begin
  199. if not FChunkStarted then
  200. Exit;
  201. FChunkStarted := False;
  202. FCompressor.Finish;
  203. { In case we didn't get a ProgressProc call after the final block: }
  204. FCompiler.SetBytesCompressedSoFar(FInitialBytesCompressedSoFar);
  205. FCompiler.AddBytesCompressedSoFar(FChunkBytesRead);
  206. FCompiler.CallIdleProc;
  207. end;
  208. procedure TCompressionHandler.CompressFile(const SourceFile: TFile;
  209. Bytes: Integer64; const CallOptimize: Boolean; out SHA256Sum: TSHA256Digest);
  210. var
  211. Context: TSHA256Context;
  212. AddrOffset: LongWord;
  213. BufSize: Cardinal;
  214. Buf: array[0..65535] of Byte;
  215. { ^ *must* be the same buffer size used in Setup (TFileExtractor), otherwise
  216. the TransformCallInstructions call will break }
  217. begin
  218. SHA256Init(Context);
  219. AddrOffset := 0;
  220. while True do begin
  221. BufSize := SizeOf(Buf);
  222. if (Bytes.Hi = 0) and (Bytes.Lo < BufSize) then
  223. BufSize := Bytes.Lo;
  224. if BufSize = 0 then
  225. Break;
  226. SourceFile.ReadBuffer(Buf, BufSize);
  227. Inc64(FChunkBytesRead, BufSize);
  228. Dec64(Bytes, BufSize);
  229. SHA256Update(Context, Buf, BufSize);
  230. if CallOptimize then begin
  231. TransformCallInstructions(Buf, BufSize, True, AddrOffset);
  232. Inc(AddrOffset, BufSize); { may wrap, but OK }
  233. end;
  234. FCompressor.Compress(Buf, BufSize);
  235. end;
  236. SHA256Sum := SHA256Final(Context);
  237. end;
  238. procedure TCompressionHandler.WriteProc(const Buf; BufSize: Longint);
  239. var
  240. P, P2: Pointer;
  241. S: Cardinal;
  242. begin
  243. FCompiler.CallIdleProc;
  244. P := @Buf;
  245. while BufSize > 0 do begin
  246. S := BufSize;
  247. if FSliceBytesLeft = 0 then
  248. NewSlice('');
  249. if S > Cardinal(FSliceBytesLeft) then
  250. S := FSliceBytesLeft;
  251. if not FChunkEncrypted then
  252. FDestFile.WriteBuffer(P^, S)
  253. else begin
  254. { Using encryption. Can't modify Buf in place so allocate a new,
  255. temporary buffer. }
  256. GetMem(P2, S);
  257. try
  258. XChaCha20Crypt(FCryptContext, P^, P2^, S);
  259. FDestFile.WriteBuffer(P2^, S)
  260. finally
  261. FreeMem(P2);
  262. end;
  263. end;
  264. Inc64(FChunkBytesWritten, S);
  265. Inc(Cardinal(P), S);
  266. Dec(BufSize, S);
  267. Dec(FSliceBytesLeft, S);
  268. end;
  269. end;
  270. procedure TCompressionHandler.ProgressProc(BytesProcessed: Cardinal);
  271. begin
  272. FCompiler.AddBytesCompressedSoFar(BytesProcessed);
  273. FCompiler.CallIdleProc;
  274. end;
  275. end.