Compiler.CompressionHandler.pas 9.2 KB

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