Compression.SevenZipDLLDecoder.pas 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745
  1. unit Compression.SevenZipDLLDecoder;
  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. Interface to the 7-Zip Decoder DLLs, used by Setup
  8. Based on the 7-Zip source code and the 7-Zip Delphi API by Henri Gourvest
  9. https://github.com/geoffsmith82/d7zip MPL 1.1 licensed
  10. }
  11. interface
  12. uses
  13. Compression.SevenZipDecoder;
  14. function SevenZipDLLInit(const SevenZipLibrary: HMODULE): Boolean;
  15. procedure ExtractArchiveRedir(const DisableFsRedir: Boolean;
  16. const ArchiveFilename, DestDir, Password: String; const FullPaths: Boolean;
  17. const OnExtractionProgress: TOnExtractionProgress);
  18. implementation
  19. uses
  20. Classes, SysUtils, Forms, Variants,
  21. Windows, ActiveX, ComObj,
  22. Compression.SevenZipDLLDecoder.Interfaces, PathFunc,
  23. Shared.FileClass, Shared.Int64Em, Shared.SetupMessageIDs, Shared.CommonFunc,
  24. SetupLdrAndSetup.Messages, SetupLdrAndSetup.RedirFunc,
  25. Setup.LoggingFunc, Setup.MainFunc, Setup.InstFunc;
  26. type
  27. TInStream = class(TInterfacedObject, IInStream)
  28. private
  29. FFile: TFile;
  30. protected
  31. function Read(data: Pointer; size: UInt32; processedSize: PUInt32): HRESULT; stdcall;
  32. function Seek(offset: Int64; seekOrigin: UInt32; newPosition: PUInt64): HRESULT; stdcall;
  33. public
  34. constructor Create(AFile: TFile);
  35. destructor Destroy; override;
  36. end;
  37. TSequentialOutStream = class(TInterfacedObject, ISequentialOutStream)
  38. private
  39. FFile: TFile;
  40. protected
  41. function Write(data: Pointer; size: UInt32; processedSize: PUInt32): HRESULT; stdcall;
  42. public
  43. constructor Create(AFile: TFile);
  44. destructor Destroy; override;
  45. end;
  46. TArchiveOpenCallback = class(TInterfacedObject, IArchiveOpenCallback,
  47. ICryptoGetTextPassword)
  48. private
  49. FPassword: String;
  50. protected
  51. { IArchiveOpenCallback }
  52. function SetTotal(files, bytes: PUInt64): HRESULT; stdcall;
  53. function SetCompleted(files, bytes: PUInt64): HRESULT; stdcall;
  54. { ICryptoGetTextPassword - queried for on openCallback }
  55. function CryptoGetTextPassword(out password: WideString): HRESULT; stdcall;
  56. public
  57. constructor Create(const Password: String);
  58. end;
  59. TArchiveExtractCallback = class(TInterfacedObject, IArchiveExtractCallback,
  60. ICryptoGetTextPassword)
  61. private
  62. const
  63. varFileTime = 64; { Delphi lacks proper VT_FILETIME support }
  64. type
  65. TCurrent = record
  66. Path, ExpandedPath: String;
  67. HasAttrib: Boolean;
  68. Attrib: DWORD;
  69. CTime, MTime: TFileTime;
  70. outStream: ISequentialOutStream;
  71. procedure SetAttrib(const AAttrib: DWORD);
  72. end;
  73. TProgress = record
  74. Current: TCurrent;
  75. Progress, ProgressMax: UInt64;
  76. Abort: Boolean;
  77. end;
  78. TResult = record
  79. SavedFatalException: TObject;
  80. Res: HRESULT;
  81. OpRes: TNOperationResult;
  82. end;
  83. TVarTypeSet = set of varEmpty..varFileTime; { Incomplete but don't need others }
  84. var
  85. FInArchive: IInArchive;
  86. FDisableFsRedir: Boolean;
  87. FExpandedDestDir, FPassword: String;
  88. FFullPaths: Boolean;
  89. FExtractedArchiveName: String;
  90. FOnExtractionProgress: TOnExtractionProgress;
  91. FProgressAndLogQueueLock: TObject;
  92. FProgress: TProgress;
  93. FLogQueue: TStrings;
  94. FResult: TResult;
  95. function GetProperty(const index: UInt32; const propID: PROPID;
  96. const allowedTypes: TVarTypeSet; out value: OleVariant): Boolean; overload;
  97. function GetProperty(index: UInt32; propID: PROPID; out value: String): Boolean; overload;
  98. function GetProperty(index: UInt32; propID: PROPID; out value: UInt32): Boolean; overload;
  99. function GetProperty(index: UInt32; propID: PROPID; out value: Boolean): Boolean; overload;
  100. function GetProperty(index: UInt32; propID: PROPID; out value: TFileTime): Boolean; overload;
  101. protected
  102. { IProgress }
  103. function SetTotal(total: UInt64): HRESULT; stdcall;
  104. function SetCompleted(completeValue: PUInt64): HRESULT; stdcall;
  105. { IArchiveExtractCallback }
  106. function GetStream(index: UInt32; out outStream: ISequentialOutStream;
  107. askExtractMode: Int32): HRESULT; stdcall;
  108. function PrepareOperation(askExtractMode: Int32): HRESULT; stdcall;
  109. function SetOperationResult(opRes: TNOperationResult): HRESULT; stdcall;
  110. { ICryptoGetTextPassword - queried for on extractCallback }
  111. function CryptoGetTextPassword(out password: WideString): HRESULT; stdcall;
  112. public
  113. constructor Create(const InArchive: IInArchive;
  114. const DisableFsRedir: Boolean; const ArchiveFileName, DestDir, Password: String;
  115. const FullPaths: Boolean; const OnExtractionProgress: TOnExtractionProgress);
  116. destructor Destroy; override;
  117. end;
  118. TFileTimeHelper = record helper for TFileTime
  119. procedure Clear;
  120. function HasTime: Boolean;
  121. end;
  122. function SevenZipSetPassword(const Password: String; out outPassword: WideString): HRESULT;
  123. begin
  124. try
  125. if Password = '' then
  126. Exit(S_FALSE);
  127. outPassword := Password;
  128. Result := S_OK;
  129. except
  130. on E: EAbort do
  131. Result := E_ABORT
  132. else
  133. Result := E_FAIL;
  134. end;
  135. end;
  136. { TInStream }
  137. constructor TInStream.Create(AFile: TFile);
  138. begin
  139. inherited Create;
  140. FFile := AFile;
  141. end;
  142. destructor TInStream.Destroy;
  143. begin
  144. FFile.Free;
  145. inherited;
  146. end;
  147. function TInStream.Read(data: Pointer; size: UInt32;
  148. processedSize: PUInt32): HRESULT;
  149. begin
  150. try
  151. var BytesRead := FFile.Read(data^, size);
  152. if processedSize <> nil then
  153. processedSize^ := BytesRead;
  154. Result := S_OK;
  155. except
  156. on E: EAbort do
  157. Result := E_ABORT
  158. else
  159. Result := E_FAIL;
  160. end;
  161. end;
  162. function TInStream.Seek(offset: Int64; seekOrigin: UInt32;
  163. newPosition: PUInt64): HRESULT;
  164. begin
  165. try
  166. case seekOrigin of
  167. STREAM_SEEK_SET: FFile.Seek64(Integer64(offset));
  168. STREAM_SEEK_CUR: FFile.Seek64(Integer64(Int64(FFile.Position) + offset));
  169. STREAM_SEEK_END: FFile.Seek64(Integer64(Int64(FFile.Size) + offset));
  170. end;
  171. if newPosition <> nil then
  172. newPosition^ := UInt64(FFile.Position);
  173. Result := S_OK;
  174. except
  175. on E: EAbort do
  176. Result := E_ABORT
  177. else
  178. Result := E_FAIL;
  179. end;
  180. end;
  181. { TSequentialOutStream }
  182. constructor TSequentialOutStream.Create(AFile: TFile);
  183. begin
  184. inherited Create;
  185. FFile := AFile;
  186. end;
  187. destructor TSequentialOutStream.Destroy;
  188. begin
  189. FFile.Free;
  190. inherited;
  191. end;
  192. function TSequentialOutStream.Write(data: Pointer; size: UInt32;
  193. processedSize: PUInt32): HRESULT;
  194. begin
  195. try
  196. FFile.WriteBuffer(data^, size);
  197. if processedSize <> nil then
  198. processedSize^ := size;
  199. Result := S_OK;
  200. except
  201. on E: EAbort do
  202. Result := E_ABORT
  203. else
  204. Result := E_FAIL;
  205. end;
  206. end;
  207. { TArchiveOpenCallback }
  208. constructor TArchiveOpenCallback.Create(const Password: String);
  209. begin
  210. inherited Create;
  211. FPassword := Password;
  212. end;
  213. function TArchiveOpenCallback.SetCompleted(files,
  214. bytes: PUInt64): HRESULT;
  215. begin
  216. Result := S_OK;
  217. end;
  218. function TArchiveOpenCallback.SetTotal(files,
  219. bytes: PUInt64): HRESULT;
  220. begin
  221. Result := S_OK;
  222. end;
  223. function TArchiveOpenCallback.CryptoGetTextPassword(
  224. out password: WideString): HRESULT;
  225. begin
  226. { Note: have not yet seen 7-Zip actually call this, so maybe it's not really needed }
  227. Result := SevenZipSetPassword(FPassword, password);
  228. end;
  229. { TArchiveExtractCallback }
  230. procedure TArchiveExtractCallback.TCurrent.SetAttrib(const AAttrib: DWORD);
  231. begin
  232. Attrib := AAttrib;
  233. HasAttrib := True;
  234. end;
  235. constructor TArchiveExtractCallback.Create(const InArchive: IInArchive;
  236. const DisableFsRedir: Boolean; const ArchiveFileName, DestDir, Password: String;
  237. const FullPaths: Boolean; const OnExtractionProgress: TOnExtractionProgress);
  238. begin
  239. inherited Create;
  240. FInArchive := InArchive;
  241. FDisableFsRedir := DisableFsRedir;
  242. FExpandedDestDir := AddBackslash(PathExpand(DestDir));
  243. FPassword := Password;
  244. FFullPaths := FullPaths;
  245. FExtractedArchiveName := PathExtractName(ArchiveFileName);
  246. FOnExtractionProgress := OnExtractionProgress;
  247. FProgressAndLogQueueLock := TObject.Create;
  248. FLogQueue := TStringList.Create;
  249. FResult.OpRes := kOK;
  250. end;
  251. destructor TArchiveExtractCallback.Destroy;
  252. begin
  253. FResult.SavedFatalException.Free;
  254. FLogQueue.Free;
  255. FProgressAndLogQueueLock.Free;
  256. end;
  257. function TArchiveExtractCallback.SetTotal(total: UInt64): HRESULT;
  258. begin
  259. { From IArchive.h: 7-Zip can call functions for IProgress or ICompressProgressInfo functions
  260. from another threads simultaneously with calls for IArchiveExtractCallback interface }
  261. try
  262. System.TMonitor.Enter(FProgressAndLogQueueLock);
  263. try
  264. FProgress.ProgressMax := total;
  265. finally
  266. System.TMonitor.Exit(FProgressAndLogQueueLock);
  267. end;
  268. Result := S_OK;
  269. except
  270. on E: EAbort do
  271. Result := E_ABORT
  272. else
  273. Result := E_FAIL;
  274. end;
  275. end;
  276. function TArchiveExtractCallback.SetCompleted(completeValue: PUInt64): HRESULT;
  277. begin
  278. try
  279. System.TMonitor.Enter(FProgressAndLogQueueLock);
  280. try
  281. if FProgress.Abort then
  282. SysUtils.Abort;
  283. FProgress.Progress := completeValue^;
  284. finally
  285. System.TMonitor.Exit(FProgressAndLogQueueLock);
  286. end;
  287. Result := S_OK;
  288. except
  289. on E: EAbort do
  290. Result := E_ABORT
  291. else
  292. Result := E_FAIL;
  293. end;
  294. end;
  295. function TArchiveExtractCallback.GetProperty(const index: UInt32;
  296. const propID: PROPID; const allowedTypes: TVarTypeSet; out value: OleVariant): Boolean;
  297. { Raises an EOleSysError exception on error but otherwise always sets value,
  298. returning True if it's not empty }
  299. begin
  300. var Res := FInArchive.GetProperty(index, propID, value);
  301. if Res <> S_OK then
  302. OleError(Res);
  303. Result := not VarIsEmpty(Value);
  304. if Result and not (VarType(value) in allowedTypes) then
  305. OleError(E_FAIL);
  306. end;
  307. function TArchiveExtractCallback.GetProperty(index: UInt32; propID: PROPID;
  308. out value: String): Boolean;
  309. begin
  310. var varValue: OleVariant;
  311. Result := GetProperty(index, propID, [varOleStr], varValue);
  312. value := varValue;
  313. end;
  314. function TArchiveExtractCallback.GetProperty(index: UInt32; propID: PROPID;
  315. out value: Cardinal): Boolean;
  316. begin
  317. var varValue: OleVariant;
  318. Result := GetProperty(index, propID, [varUInt32], varValue);
  319. value := varValue;
  320. end;
  321. function TArchiveExtractCallback.GetProperty(index: UInt32; propID: PROPID;
  322. out value: Boolean): Boolean;
  323. begin
  324. var varValue: OleVariant;
  325. Result := GetProperty(index, propID, [varBoolean], varValue);
  326. value := varValue;
  327. end;
  328. function TArchiveExtractCallback.GetProperty(index: UInt32; propID: PROPID;
  329. out value: TFileTime): Boolean;
  330. begin
  331. var varValue: OleVariant;
  332. Result := GetProperty(index, propID, [varFileTime], varValue);
  333. if Result then
  334. value := TFileTime(TVarData(varValue).VInt64)
  335. else
  336. value.Clear;
  337. end;
  338. function TArchiveExtractCallback.GetStream(index: UInt32;
  339. out outStream: ISequentialOutStream; askExtractMode: Int32): HRESULT;
  340. begin
  341. try
  342. var NewCurrent := Default(TCurrent);
  343. if askExtractMode = kExtract then begin
  344. var Path: String;
  345. if not GetProperty(index, kpidPath, Path) then
  346. Path := PathChangeExt(FExtractedArchiveName, '');
  347. var IsDir: Boolean;
  348. GetProperty(index, kpidIsDir, IsDir);
  349. if IsDir then begin
  350. if FFullPaths then begin
  351. NewCurrent.Path := Path + '\';
  352. if not ValidateAndCombinePath(FExpandedDestDir, Path, NewCurrent.ExpandedPath) then
  353. OleError(E_ACCESSDENIED);
  354. ForceDirectories(FDisableFsRedir, NewCurrent.ExpandedPath);
  355. end;
  356. outStream := nil;
  357. end else begin
  358. var Attrib: DWORD;
  359. if GetProperty(index, kpidAttrib, Attrib) then begin
  360. if Attrib and $F0000000 <> 0 then
  361. Attrib := Attrib and $3FFF; { "PosixHighDetect", just like FileDir.cpp and similar to 7zMain.c }
  362. NewCurrent.SetAttrib(Attrib);
  363. end;
  364. GetProperty(index, kpidCTime, NewCurrent.CTime);
  365. GetProperty(index, kpidMTime, NewCurrent.MTime);
  366. if not FFullPaths then
  367. Path := PathExtractName(Path);
  368. NewCurrent.Path := Path;
  369. if not ValidateAndCombinePath(FExpandedDestDir, Path, NewCurrent.ExpandedPath) then
  370. OleError(E_ACCESSDENIED);
  371. ForceDirectories(FDisableFsRedir, PathExtractPath(NewCurrent.ExpandedPath));
  372. const ExistingFileAttr = GetFileAttributesRedir(FDisableFsRedir, NewCurrent.ExpandedPath);
  373. if (ExistingFileAttr <> INVALID_FILE_ATTRIBUTES) and
  374. (ExistingFileAttr and FILE_ATTRIBUTE_READONLY <> 0) then
  375. SetFileAttributesRedir(FDisableFsRedir, NewCurrent.ExpandedPath, ExistingFileAttr and not FILE_ATTRIBUTE_READONLY);
  376. { From IArchive.h: can also set outstream to nil to tell 7zip to skip the file }
  377. outstream := TSequentialOutStream.Create(TFileRedir.Create(FDisableFsRedir, NewCurrent.ExpandedPath, fdCreateAlways, faWrite, fsNone));
  378. NewCurrent.outStream := outStream;
  379. end;
  380. end;
  381. System.TMonitor.Enter(FProgressAndLogQueueLock);
  382. try
  383. if FProgress.Abort then
  384. SysUtils.Abort;
  385. FProgress.Current := NewCurrent;
  386. if NewCurrent.Path <> '' then
  387. FLogQueue.Append(NewCurrent.Path)
  388. finally
  389. System.TMonitor.Exit(FProgressAndLogQueueLock);
  390. end;
  391. Result := S_OK;
  392. except
  393. on E: EOleSysError do
  394. Result := E.ErrorCode;
  395. on E: EAbort do
  396. Result := E_ABORT
  397. else
  398. Result := E_FAIL;
  399. end;
  400. end;
  401. function TArchiveExtractCallback.PrepareOperation(askExtractMode: Int32): HRESULT;
  402. begin
  403. { From Client7z.cpp: PrepareOperation is called *after* GetStream has been called }
  404. Result := S_OK;
  405. end;
  406. function TArchiveExtractCallback.SetOperationResult(opRes: TNOperationResult): HRESULT;
  407. begin
  408. { From IArchive.h: Can now can close the file, set attributes, timestamps and security information }
  409. try
  410. try
  411. if opRes <> kOK then begin
  412. FResult.OpRes := opRes;
  413. Result := E_FAIL; { Make sure it doesn't continue with the next file }
  414. end else begin
  415. { GetStream is the only writer to outStream and ExpandedPath and HasAttrib so we don't need a lock because of this note from
  416. IArchive.h: 7-Zip doesn't call GetStream/PrepareOperation/SetOperationResult from different threads simultaneously }
  417. if (FProgress.Current.outStream <> nil) and (FProgress.Current.CTime.HasTime or FProgress.Current.MTime.HasTime) then
  418. SetFileTime((FProgress.Current.outStream as TSequentialOutStream).FFile.Handle,
  419. @FProgress.Current.CTime, nil, @FProgress.Current.MTime);
  420. FProgress.Current.outStream := nil; { Like 7zMain.c close the file before setting attributes - note that 7-Zip has cleared its own reference as well already }
  421. if (FProgress.Current.ExpandedPath <> '') and FProgress.Current.HasAttrib then
  422. SetFileAttributesRedir(FDisableFsRedir, FProgress.Current.ExpandedPath, FProgress.Current.Attrib);
  423. Result := S_OK;
  424. end;
  425. finally
  426. FProgress.Current.outStream := nil;
  427. end;
  428. except
  429. on E: EAbort do
  430. Result := E_ABORT
  431. else
  432. Result := E_FAIL;
  433. end;
  434. end;
  435. function TArchiveExtractCallback.CryptoGetTextPassword(
  436. out password: WideString): HRESULT;
  437. begin
  438. Result := SevenZipSetPassword(FPassword, password);
  439. end;
  440. {---}
  441. var
  442. CreateSevenZipObject: function(const clsid, iid: TGUID; var outObject): HRESULT; stdcall;
  443. function SevenZipDLLInit(const SevenZipLibrary: HMODULE): Boolean;
  444. begin
  445. CreateSevenZipObject := GetProcAddress(SevenZipLibrary, 'CreateObject');
  446. Result := Assigned(CreateSevenZipObject);
  447. end;
  448. procedure SevenZipError(const LogMessage, ExceptMessage: String);
  449. { Do not call from secondary thread. LogMessage may contain non-localized text
  450. ExceptMessage should not. }
  451. begin
  452. LogFmt('ERROR: %s', [LogMessage]); { Just like 7zMain.c }
  453. raise Exception.Create(ExceptMessage);
  454. end;
  455. procedure SevenZipWin32Error(const FunctionName: String; LastError: DWORD = 0); overload;
  456. begin
  457. if LastError = 0 then
  458. LastError := GetLastError;
  459. const Msg = Format('%s (%u)', [Win32ErrorString(LastError), LastError]);
  460. SevenZipError(Format('%s failed: %s', [FunctionName, Msg]), Msg);
  461. end;
  462. function ExtractThreadFunc(Parameter: Pointer): Integer;
  463. begin
  464. const E = TArchiveExtractCallback(Parameter);
  465. try
  466. E.FResult.Res := E.FInArchive.Extract(nil, $FFFFFFFF, 0, E);
  467. except
  468. const Ex = AcquireExceptionObject;
  469. MemoryBarrier;
  470. E.FResult.SavedFatalException := Ex;
  471. end;
  472. { Be extra sure FSavedFatalException (and everything else) is made visible
  473. prior to thread termination. (Likely redundant, but you never know...) }
  474. MemoryBarrier;
  475. Result := 0;
  476. end;
  477. procedure ExtractArchiveRedir(const DisableFsRedir: Boolean;
  478. const ArchiveFilename, DestDir, Password: String;
  479. const FullPaths: Boolean; const OnExtractionProgress: TOnExtractionProgress);
  480. function GetHandler(const Ext, NotFoundErrorMsg: String): TGUID;
  481. begin
  482. if SameText(Ext, '.7z') then
  483. Result := CLSID_Handler7z
  484. else if SameText(Ext, '.zip') then
  485. Result := CLSID_HandlerZip
  486. else if SameText(Ext, '.gz') then
  487. Result := CLSID_HandlerGzip
  488. else if SameText(Ext, '.bz2') then
  489. Result := CLSID_HandlerBZip2
  490. else if SameText(Ext, '.xz') then
  491. Result := CLSID_HandlerXz
  492. else if SameText(Ext, '.tar') then
  493. Result := CLSID_HandlerTar
  494. else if SameText(Ext, '.rar') then
  495. Result := CLSID_HandlerRar
  496. else if SameText(Ext, '.iso') then
  497. Result := CLSID_HandlerIso
  498. else if SameText(Ext, '.msi') then
  499. Result := CLSID_HandlerCompound
  500. else if SameText(Ext, '.cab') then
  501. Result := CLSID_HandlerCab
  502. else if SameText(Ext, '.rpm') then
  503. Result := CLSID_HandlerRpm
  504. else if SameText(Ext, '.vhd') then
  505. Result := CLSID_HandlerVhd
  506. else if SameText(Ext, '.vhdx') then
  507. Result := CLSID_HandlerVhdx
  508. else if SameText(Ext, '.vdi') then
  509. Result := CLSID_HandlerVDI
  510. else if SameText(Ext, '.vmdk') then
  511. Result := CLSID_HandlerVMDK
  512. else if SameText(Ext, '.wim') then
  513. Result := CLSID_HandlerWim
  514. else if SameText(Ext, '.dmg') then
  515. Result := CLSID_HandlerDmg
  516. else
  517. InternalError(NotFoundErrorMsg);
  518. end;
  519. procedure HandleProgress(const E: TArchiveExtractCallback);
  520. begin
  521. var Progress: TArchiveExtractCallback.TProgress;
  522. System.TMonitor.Enter(E.FProgressAndLogQueueLock);
  523. try
  524. Progress := E.FProgress;
  525. for var S in E.FLogQueue do
  526. LogFmt('- %s', [S]); { Just like 7zMain.c }
  527. E.FLogQueue.Clear;
  528. finally
  529. System.TMonitor.Exit(E.FProgressAndLogQueueLock);
  530. end;
  531. if Progress.Abort then
  532. Exit;
  533. var Abort := False;
  534. if (Progress.Current.Path <> '') and Assigned(E.FOnExtractionProgress) then begin
  535. { Calls to HandleProgress are already throttled so here we don't have to worry
  536. about calling the script to often }
  537. if not E.FOnExtractionProgress(E.FExtractedArchiveName, Progress.Current.Path, Progress.Progress, Progress.ProgressMax) then
  538. Abort := True;
  539. end;
  540. if not Abort and DownloadTemporaryFileOrExtractArchiveProcessMessages then
  541. Application.ProcessMessages;
  542. if Abort then begin
  543. System.TMonitor.Enter(E.FProgressAndLogQueueLock);
  544. try
  545. E.FProgress.Abort := True;
  546. finally
  547. System.TMonitor.Exit(E.FProgressAndLogQueueLock);
  548. end;
  549. end;
  550. end;
  551. function OperationResultToString(const opRes: TNOperationResult): String;
  552. begin
  553. case opRes of
  554. kOK: Result := 'OK';
  555. kUnsupportedMethod: Result := 'Unsupported method';
  556. kDataError: Result := 'Data error';
  557. kCRCError: Result := 'CRC error';
  558. kUnavailable: Result := 'Unavailable';
  559. kUnexpectedEnd: Result := 'Unexpected end';
  560. kDataAfterEnd: Result := 'Data after end';
  561. kIsNotArc: Result := 'Is not an archive';
  562. kHeadersError: Result := 'Headers error';
  563. kWrongPassword: Result := 'Wrong password';
  564. else
  565. Result := Format('Unknown operation result: %d', [Ord(opRes)]);
  566. end;
  567. end;
  568. procedure HandleResult([Ref] const Result: TArchiveExtractCallback.TResult);
  569. begin
  570. if Assigned(Result.SavedFatalException) then begin
  571. var Msg: String;
  572. if Result.SavedFatalException is Exception then
  573. Msg := (Result.SavedFatalException as Exception).Message
  574. else
  575. Msg := Result.SavedFatalException.ClassName;
  576. SevenZipError(Format('Worker thread terminated unexpectedly with exception: %s', [Msg]), Msg);
  577. end else if Result.Res = E_ABORT then
  578. Abort
  579. else begin
  580. var OpRes := Result.OpRes;
  581. if OpRes <> kOK then
  582. SevenZipError(OperationResultToString(Result.OpRes), Ord(OpRes).ToString)
  583. else if Result.Res <> S_OK then
  584. SevenZipWin32Error('Extract', Result.Res);
  585. end;
  586. end;
  587. procedure Extract(const E: TArchiveExtractCallback);
  588. begin
  589. { We're calling 7-Zip's Extract in a separate thread. This is because packing
  590. our example MyProg.exe into a (tiny) .7z and extracting it caused a problem:
  591. GetStream and PrepareOperation and SetOperationResult were *all* called by
  592. 7-Zip from a secondary thread. So we can't block our main thread as well
  593. because then we can't communicate progress to it. Having this extra thread
  594. has the added bonus of being able to communicate progress more often from
  595. SetCompleted. }
  596. var ThreadID: TThreadID; { Not used but BeginThread requires it }
  597. const ThreadHandle = BeginThread(nil, 0, ExtractThreadFunc, E, 0, ThreadID);
  598. if ThreadHandle = 0 then
  599. SevenZipWin32Error('BeginThread');
  600. try
  601. while True do begin
  602. case WaitForSingleObject(ThreadHandle, 50) of
  603. WAIT_OBJECT_0: Break;
  604. WAIT_TIMEOUT: HandleProgress(E);
  605. else
  606. SevenZipWin32Error('WaitForSingleObject');
  607. end;
  608. end;
  609. finally
  610. CloseHandle(ThreadHandle);
  611. end;
  612. HandleProgress(E);
  613. HandleResult(E.FResult);
  614. end;
  615. begin
  616. LogArchiveExtractionModeOnce;
  617. if ArchiveFileName = '' then
  618. InternalError('ExtractArchive: Invalid ArchiveFileName value');
  619. const clsid = GetHandler(PathExtractExt(ArchiveFilename),
  620. 'ExtractArchive: Unknown ArchiveFileName extension');
  621. if DestDir = '' then
  622. InternalError('ExtractArchive: Invalid DestDir value');
  623. LogFmt('Extracting archive %s to %s. Full paths? %s', [ArchiveFileName, DestDir, SYesNo[FullPaths]]);
  624. LogFmt('%s Decoder : Igor Pavlov', [SetupHeader.SevenZipLibraryName]); { Just like 7zMain.c }
  625. try
  626. { CreateObject }
  627. var InArchive: IInArchive;
  628. if CreateSevenZipObject(clsid, IInArchive, InArchive) <> S_OK then
  629. SevenZipError('Cannot get class object' { Just like Client7z.cpp }, '-1');
  630. { Open }
  631. var F: TFile := nil; { Set to nil to silence compiler }
  632. try
  633. F := TFileRedir.Create(DisableFsRedir, ArchiveFilename, fdOpenExisting, faRead, fsRead);
  634. except
  635. SevenZipWin32Error('CreateFile');
  636. end;
  637. const InStream: IInStream = TInStream.Create(F);
  638. var ScanSize: Int64 := 1 shl 23; { From Client7z.cpp }
  639. const OpenCallback: IArchiveOpenCallback = TArchiveOpenCallback.Create(Password);
  640. if InArchive.Open(InStream, @ScanSize, OpenCallback) <> S_OK then
  641. SevenZipError('Cannot open file as archive' { Just like Client7z.cpp }, '-2');
  642. { Extract }
  643. const ExtractCallback: IArchiveExtractCallback =
  644. TArchiveExtractCallback.Create(InArchive, DisableFsRedir,
  645. ArchiveFilename, DestDir, Password, FullPaths, OnExtractionProgress);
  646. Extract(ExtractCallback as TArchiveExtractCallback);
  647. Log('Everything is Ok'); { Just like 7zMain.c }
  648. except
  649. on E: EAbort do
  650. raise Exception.Create(SetupMessages[msgErrorExtractionAborted])
  651. else
  652. raise Exception.Create(FmtSetupMessage(msgErrorExtractionFailed, [GetExceptMessage]));
  653. end;
  654. end;
  655. { TFileTimeHelper }
  656. procedure TFileTimeHelper.Clear;
  657. begin
  658. { SetFileTime regards a pointer to a FILETIME structure with both members
  659. set to 0 the same as a NULL pointer and we make use of that. Note that
  660. 7-Zip may return a value with both members set to 0 as well. }
  661. dwLowDateTime := 0;
  662. dwHighDateTime := 0;
  663. end;
  664. function TFileTimeHelper.HasTime: Boolean;
  665. begin
  666. Result := (dwLowDateTime <> 0) or (dwHighDateTime <> 0);
  667. end;
  668. end.