浏览代码

Merge branch 'main' into files-downloadarchive

Martijn Laan 2 月之前
父节点
当前提交
96af608ad8

+ 4 - 4
ISHelp/isetup.xml

@@ -1749,7 +1749,7 @@ Instructs Setup to proceed to comparing time stamps (last write/modified time) i
 </flag>
 </flag>
 <flag name="extractarchive">
 <flag name="extractarchive">
 <p>This flag instructs Setup not to copy an existing archive file, but instead to extract it. Optionally use the <tt>ExtractArchivePassword</tt> parameter to specify a password.</p>
 <p>This flag instructs Setup not to copy an existing archive file, but instead to extract it. Optionally use the <tt>ExtractArchivePassword</tt> parameter to specify a password.</p>
-<p>The supported archive formats, beyond .7z, and the support for password-protected archives, depend on the <link topic="setup_archiveextraction">ArchiveExtraction</link> [Setup] section directive, that must not be set to <tt>basic</tt>.</p>
+<p>The supported archive formats, beyond .7z, and the support for password-protected and multi-volume archives, depend on the <link topic="setup_archiveextraction">ArchiveExtraction</link> [Setup] section directive, that must not be set to <tt>basic</tt>.</p>
 <p>This flag must be combined with the <tt>external</tt> and <tt>ignoreversion</tt> flags, meaning it should only be used on files private to your application, <i>never</i> on shared system files.</p>
 <p>This flag must be combined with the <tt>external</tt> and <tt>ignoreversion</tt> flags, meaning it should only be used on files private to your application, <i>never</i> on shared system files.</p>
 <p>This flag is usually combined with the <tt>recursesubdirs</tt> and <tt>createallsubdirs</tt> flags.</p>
 <p>This flag is usually combined with the <tt>recursesubdirs</tt> and <tt>createallsubdirs</tt> flags.</p>
 <p>Using a solid archive is not recommended; extraction performance may degrade depending on the solid block size.</p>
 <p>Using a solid archive is not recommended; extraction performance may degrade depending on the solid block size.</p>
@@ -4788,7 +4788,7 @@ ExtraDiskSpaceRequired=1_048_576</pre></example>
 <p><tt>basic</tt> uses an embedded version of the "7z ANSI-C Decoder" from the LZMA SDK by Igor Pavlov, as-is, except that Unicode support and error messages were improved and that it outputs memory requirements. It only supports .7z archives that are not password-protected.</p>
 <p><tt>basic</tt> uses an embedded version of the "7z ANSI-C Decoder" from the LZMA SDK by Igor Pavlov, as-is, except that Unicode support and error messages were improved and that it outputs memory requirements. It only supports .7z archives that are not password-protected.</p>
 <p><tt>enhanced/nopassword</tt> internally uses 7zxr.dll from the 7-Zip source code by Igor Pavlov, as-is, except that it was recompiled, code-signed, and renamed to is7zxr.dll. Compared to <tt>basic</tt>, it has lower memory requirements for archives that contain large files but increases the size of the Setup file(s). It still only supports .7z archives that are not password-protected.</p>
 <p><tt>enhanced/nopassword</tt> internally uses 7zxr.dll from the 7-Zip source code by Igor Pavlov, as-is, except that it was recompiled, code-signed, and renamed to is7zxr.dll. Compared to <tt>basic</tt>, it has lower memory requirements for archives that contain large files but increases the size of the Setup file(s). It still only supports .7z archives that are not password-protected.</p>
 <p><tt>enhanced</tt> uses 7zxa.dll instead of 7zxr.dll, recompiled, code-signed, and renamed to is7zxa.dll. It still only supports .7z archives, but they may be password-protected.</p>
 <p><tt>enhanced</tt> uses 7zxa.dll instead of 7zxr.dll, recompiled, code-signed, and renamed to is7zxa.dll. It still only supports .7z archives, but they may be password-protected.</p>
-<p><tt>full</tt> uses 7z.dll instead of 7zxa.dll, recompiled, code-signed, and renamed to is7z.dll. It supports multiple archive formats, although not as many as the original 7z.dll, to reduce its size.</p>
+<p><tt>full</tt> uses 7z.dll instead of 7zxa.dll, recompiled, code-signed, and renamed to is7z.dll. It supports multiple archive formats, although not as many as the original 7z.dll, to reduce its size. It supports multi-volume archives.</p>
 <p>The following table summarizes the differences between these methods.</p>
 <p>The following table summarizes the differences between these methods.</p>
 <indent>
 <indent>
 <table>
 <table>
@@ -4796,13 +4796,13 @@ ExtraDiskSpaceRequired=1_048_576</pre></example>
 <tr><td><tt>basic</tt> (default)</td><td>High for large files*</td><td>No</td><td>0 KB</td><td>.7z</td></tr>
 <tr><td><tt>basic</tt> (default)</td><td>High for large files*</td><td>No</td><td>0 KB</td><td>.7z</td></tr>
 <tr><td><tt>enhanced/nopassword</tt></td><td>Normal</td><td>No</td><td>100 KB</td><td>.7z</td></tr>
 <tr><td><tt>enhanced/nopassword</tt></td><td>Normal</td><td>No</td><td>100 KB</td><td>.7z</td></tr>
 <tr><td><tt>enhanced</tt></td><td>Normal</td><td>Yes</td><td>123 KB</td><td>.7z</td></tr>
 <tr><td><tt>enhanced</tt></td><td>Normal</td><td>Yes</td><td>123 KB</td><td>.7z</td></tr>
-<tr><td><tt>full</tt></td><td>Normal</td><td>Yes</td><td>458 KB</td><td>.7z, .zip, .gz, .bz2, .xz, .tar, .rar, .iso, .msi, .cab, .rpm, .vhd, .vhdx, .vdi, .vmdk, .wim, .dmg</td></tr>
+<tr><td><tt>full</tt></td><td>Normal</td><td>Yes</td><td>458 KB</td><td>.7z, .zip, .gz, .bz2, .xz, .tar, .rar, .iso, .msi, .cab, .rpm, .vhd, .vhdx, .vdi, .vmdk, .wim, .dmg, .001</td></tr>
 </table>
 </table>
 <p>* = When extracting a file, at least enough memory will always be allocated to hold the <b>entire</b> file, regardless of the block size. For example, extracting a 1 GB file using the <tt>basic</tt> method requires at least 1 GB of RAM. Consider using a different method for .7z archives that contain large files: their memory requirements depend on the dictionary size only.</p>
 <p>* = When extracting a file, at least enough memory will always be allocated to hold the <b>entire</b> file, regardless of the block size. For example, extracting a 1 GB file using the <tt>basic</tt> method requires at least 1 GB of RAM. Consider using a different method for .7z archives that contain large files: their memory requirements depend on the dictionary size only.</p>
 </indent>
 </indent>
 <p>All methods overwrite read-only files which already exist in the destination directory without prompting the user.</p>
 <p>All methods overwrite read-only files which already exist in the destination directory without prompting the user.</p>
 <p>All methods restore the following file properties from the archive, if available: creation time, last modified time, attributes.</p>
 <p>All methods restore the following file properties from the archive, if available: creation time, last modified time, attributes.</p>
-<p>When using the <tt>full</tt> method to extract a compressed archive, such as <i>archive.tar.gz</i>, or a split multi-volume archive, such as <i>archive.7z.001</i>, the output will be the archive file itself (e.g., <i>archive.tar</i> or <i>archive.7z</i>) rather than the individual files contained within it.</p>
+<p>When using the <tt>full</tt> method to extract a compressed archive, such as <i>archive.tar.gz</i>, the output will be the archive file itself (e.g., <i>archive.tar</i>) rather than the individual files contained within it.</p>
 <p>The <tt>basic</tt> method has the following additional limitations, as written by Igor Pavlov in the LZMA SDK:</p>
 <p>The <tt>basic</tt> method has the following additional limitations, as written by Igor Pavlov in the LZMA SDK:</p>
 <ul>
 <ul>
 <li>It does not support PPMd and BZip2 methods.</li>
 <li>It does not support PPMd and BZip2 methods.</li>

+ 4 - 3
ISHelp/isxfunc.xml

@@ -1910,10 +1910,11 @@ end;</pre></example>
       <function>
       <function>
         <name>ExtractArchive</name>
         <name>ExtractArchive</name>
         <prototype>procedure ExtractArchive(const ArchiveFileName, DestDir, Password: String; const FullPaths: Boolean; const OnExtractionProgress: TOnExtractionProgress);</prototype>
         <prototype>procedure ExtractArchive(const ArchiveFileName, DestDir, Password: String; const FullPaths: Boolean; const OnExtractionProgress: TOnExtractionProgress);</prototype>
-        <description><p>Extracts the specified archive to the specified directory, with or without using path names.</p>
+        <description><p>Extracts the specified archive to the specified directory, with or without preserving full path names.</p>
 <p>An exception will be raised if there was an error.</p>
 <p>An exception will be raised if there was an error.</p>
-<p>The supported archive formats, beyond .7z, and the support for password-protected archives, depend on the <link topic="setup_archiveextraction">ArchiveExtraction</link> [Setup] section directive.</p>
+<p>The supported archive formats, beyond .7z, and the support for password-protected and multi-volume archives, depend on the <link topic="setup_archiveextraction">ArchiveExtraction</link> [Setup] section directive.</p>
 <p>Set OnExtractionProgress to a function to be informed of progress, or <tt>nil</tt> otherwise.</p>
 <p>Set OnExtractionProgress to a function to be informed of progress, or <tt>nil</tt> otherwise.</p>
+<p>When extracting without preservering path names (i.e. FullPaths is set to False), ensure that the archive does not contain multiple files with the same name. Doing so will help avoid file-in-use errors.</p>
 <p>See <i>CodeDownloadFiles.iss</i> for an example of archive extraction using just a [Files] entry instead.</p></description>
 <p>See <i>CodeDownloadFiles.iss</i> for an example of archive extraction using just a [Files] entry instead.</p></description>
         <remarks><p>TOnExtractionProgress is defined as:</p>
         <remarks><p>TOnExtractionProgress is defined as:</p>
 <p><tt>TOnExtractionProgress = function(const ArchiveName, FileName: String; const Progress, ProgressMax: Int64): Boolean;</tt></p>
 <p><tt>TOnExtractionProgress = function(const ArchiveName, FileName: String; const Progress, ProgressMax: Int64): Boolean;</tt></p>
@@ -2724,7 +2725,7 @@ Page := CreateOutputMsgMemoPage(wpWelcome,
         <name>CreateExtractionPage</name>
         <name>CreateExtractionPage</name>
         <prototype>function CreateExtractionPage(const ACaption, ADescription: String; const OnExtractionProgress: TOnExtractionProgress): TExtractionWizardPage;</prototype>
         <prototype>function CreateExtractionPage(const ACaption, ADescription: String; const OnExtractionProgress: TOnExtractionProgress): TExtractionWizardPage;</prototype>
         <description><p>Creates a wizard page to extract archives and show progress.</p>
         <description><p>Creates a wizard page to extract archives and show progress.</p>
-<p>The supported archive formats, beyond .7z, and the support for password-protected archives, depend on the <link topic="setup_archiveextraction">ArchiveExtraction</link> [Setup] section directive.</p>
+<p>The supported archive formats, beyond .7z, and the support for password-protected and multi-volume archives, depend on the <link topic="setup_archiveextraction">ArchiveExtraction</link> [Setup] section directive.</p>
 <p>Set OnExtractionProgress to a function to be informed of progress, or <tt>nil</tt> otherwise.</p>    
 <p>Set OnExtractionProgress to a function to be informed of progress, or <tt>nil</tt> otherwise.</p>    
 <p>Unlike the other types of wizard pages, progress pages are not displayed as part of the normal page sequence (note that there is no <tt>AfterID</tt> parameter). A progress page can only be displayed programmatically by calling its <tt>Show</tt> method.</p></description>
 <p>Unlike the other types of wizard pages, progress pages are not displayed as part of the normal page sequence (note that there is no <tt>AfterID</tt> parameter). A progress page can only be displayed programmatically by calling its <tt>Show</tt> method.</p></description>
         <remarks><p>Call the <tt>Show</tt> method to activate and show the page. When you're finished with it, call the <tt>Hide</tt> method to revert to the previous page.</p>
         <remarks><p>Call the <tt>Show</tt> method to activate and show the page. When you're finished with it, call the <tt>Hide</tt> method to revert to the previous page.</p>

+ 7 - 1
Projects/Src/Compression.SevenZipDLLDecoder.Interfaces.pas

@@ -82,6 +82,7 @@ const
   CLSID_HandlerGZip: TGUID = '{23170F69-40C1-278A-1000-000110EF0000}';
   CLSID_HandlerGZip: TGUID = '{23170F69-40C1-278A-1000-000110EF0000}';
 
 
   { From PropID.h}
   { From PropID.h}
+  kpidMainSubfile = 1;
   kpidPath = 3;
   kpidPath = 3;
   kpidName = 4;
   kpidName = 4;
   kpidIsDir = 6;
   kpidIsDir = 6;
@@ -135,7 +136,12 @@ type
     function GetStream(const name: PChar; var inStream: IInStream): HRESULT; stdcall;
     function GetStream(const name: PChar; var inStream: IInStream): HRESULT; stdcall;
   end;
   end;
 
 
-    IArchiveExtractCallback = interface(IProgress)
+  IInArchiveGetStream = interface
+  ['{23170F69-40C1-278A-0000-000600400000}']
+    function GetStream(index: UInt32; var stream: ISequentialInStream ): HRESULT; stdcall;
+  end;
+
+  IArchiveExtractCallback = interface(IProgress)
   ['{23170F69-40C1-278A-0000-000600200000}']
   ['{23170F69-40C1-278A-0000-000600200000}']
     function GetStream(index: UInt32; out outStream: ISequentialOutStream;
     function GetStream(index: UInt32; out outStream: ISequentialOutStream;
       askExtractMode: Int32): HRESULT; stdcall;
       askExtractMode: Int32): HRESULT; stdcall;

+ 125 - 60
Projects/Src/Compression.SevenZipDLLDecoder.pas

@@ -93,14 +93,25 @@ type
     constructor Create(const Password: String);
     constructor Create(const Password: String);
   end;
   end;
 
 
-  TArchiveOpenCallback = class(TArchiveCallback, IArchiveOpenCallback, IArchiveOpenVolumeCallback)
-  private
-    FDisableFsRedir: Boolean;
-    FArchiveFilename: String;
+  TArchiveOpenCallback = class(TArchiveCallback, IArchiveOpenCallback)
   protected
   protected
     { IArchiveOpenCallback }
     { IArchiveOpenCallback }
     function SetTotal(files, bytes: PUInt64): HRESULT; stdcall;
     function SetTotal(files, bytes: PUInt64): HRESULT; stdcall;
     function SetCompleted(files, bytes: PUInt64): HRESULT; stdcall;
     function SetCompleted(files, bytes: PUInt64): HRESULT; stdcall;
+  end;
+
+  TArchiveOpenCallbackWithStreamBackup = class(TArchiveOpenCallback)
+  private
+    FStreamBackup: IInStream;
+  public
+    constructor Create(const Password: String; const StreamToBackup: IInStream);
+  end;
+
+  TArchiveOpenFileCallback = class(TArchiveOpenCallback, IArchiveOpenVolumeCallback)
+  private
+    FDisableFsRedir: Boolean;
+    FArchiveFilename: String;
+  protected
     { IArchiveOpenVolumeCallback - queried for by 7-Zip on IArchiveOpenCallback }
     { IArchiveOpenVolumeCallback - queried for by 7-Zip on IArchiveOpenCallback }
     function GetProperty(propID: PROPID; var value: OleVariant): HRESULT; stdcall;
     function GetProperty(propID: PROPID; var value: OleVariant): HRESULT; stdcall;
     function GetStream(const name: PChar; var inStream: IInStream): HRESULT; stdcall;
     function GetStream(const name: PChar; var inStream: IInStream): HRESULT; stdcall;
@@ -211,6 +222,49 @@ begin
   SevenZipError(ExceptMessage, LogMessage);
   SevenZipError(ExceptMessage, LogMessage);
 end;
 end;
 
 
+function GetHandler(const Filename, NotFoundErrorMsg: String): TGUID;
+begin
+  const Ext = PathExtractExt(Filename);
+  if SameText(Ext, '.7z') then
+    Result := CLSID_Handler7z
+  else if SameText(Ext, '.zip') then
+    Result := CLSID_HandlerZip
+  else if SameText(Ext, '.gz') then
+    Result := CLSID_HandlerGzip
+  else if SameText(Ext, '.bz2') then
+    Result := CLSID_HandlerBZip2
+  else if SameText(Ext, '.xz') then
+    Result := CLSID_HandlerXz
+  else if SameText(Ext, '.tar') then
+    Result := CLSID_HandlerTar
+  else if SameText(Ext, '.rar') then
+    Result := CLSID_HandlerRar
+  else if SameText(Ext, '.iso') then
+    Result := CLSID_HandlerIso
+  else if SameText(Ext, '.msi') then
+    Result := CLSID_HandlerCompound
+  else if SameText(Ext, '.cab') then
+    Result := CLSID_HandlerCab
+  else if SameText(Ext, '.rpm') then
+    Result := CLSID_HandlerRpm
+  else if SameText(Ext, '.vhd') then
+    Result := CLSID_HandlerVhd
+  else if SameText(Ext, '.vhdx') then
+    Result := CLSID_HandlerVhdx
+  else if SameText(Ext, '.vdi') then
+    Result := CLSID_HandlerVDI
+  else if SameText(Ext, '.vmdk') then
+    Result := CLSID_HandlerVMDK
+  else if SameText(Ext, '.wim') then
+    Result := CLSID_HandlerWim
+  else if SameText(Ext, '.dmg') then
+    Result := CLSID_HandlerDmg
+  else if SameText(Ext, '.001') then
+    Result := CLSID_HandlerSplit
+  else
+    InternalError(NotFoundErrorMsg);
+end;
+
 const
 const
   varFileTime = 64; { Delphi lacks proper VT_FILETIME support }
   varFileTime = 64; { Delphi lacks proper VT_FILETIME support }
 type
 type
@@ -390,13 +444,6 @@ end;
 
 
 { TArchiveOpenCallback }
 { TArchiveOpenCallback }
 
 
-constructor TArchiveOpenCallback.Create(const DisableFsRedir: Boolean; const ArchiveFilename, Password: String);
-begin
-  inherited Create(Password);
-  FDisableFsRedir := DisableFsRedir;
-  FArchiveFilename := ArchiveFilename;
-end;
-
 function TArchiveOpenCallback.SetCompleted(files,
 function TArchiveOpenCallback.SetCompleted(files,
   bytes: PUInt64): HRESULT;
   bytes: PUInt64): HRESULT;
 begin
 begin
@@ -409,17 +456,38 @@ begin
   Result := S_OK;
   Result := S_OK;
 end;
 end;
 
 
-function TArchiveOpenCallback.GetProperty(propID: PROPID; var value: OleVariant): HRESULT;
+{ TArchiveOpenCallbackWithStreamBackup }
+
+constructor TArchiveOpenCallbackWithStreamBackup.Create(const Password: String;
+  const StreamToBackup: IInStream);
+begin
+  inherited Create(Password);
+  FStreamBackup := StreamToBackup;
+end;
+
+{ TArchiveOpenFileCallback }
+
+constructor TArchiveOpenFileCallback.Create(const DisableFsRedir: Boolean; const ArchiveFilename,
+  Password: String);
+begin
+  inherited Create(Password);
+  FDisableFsRedir := DisableFsRedir;
+  FArchiveFilename := ArchiveFilename;
+end;
+
+function TArchiveOpenFileCallback.GetProperty(propID: PROPID; var value: OleVariant): HRESULT;
 begin
 begin
   { This is for multi-volume archives: when the archive is opened 7-Zip only receives a stream. It
   { This is for multi-volume archives: when the archive is opened 7-Zip only receives a stream. It
     will then use this callback to find the name of the archive (like archive.7z.001) to figure out
     will then use this callback to find the name of the archive (like archive.7z.001) to figure out
     the name of other volumes (like archive.7z.002) }
     the name of other volumes (like archive.7z.002) }
   if propID = kpidName then
   if propID = kpidName then
-    value := FArchiveFilename;
+    value := FArchiveFilename
+  else
+    value := Unassigned; { Note sure if this is really needed }
   Result := S_OK;
   Result := S_OK;
 end;
 end;
 
 
-function TArchiveOpenCallback.GetStream(const name: PChar; var inStream: IInStream): HRESULT;
+function TArchiveOpenFileCallback.GetStream(const name: PChar; var inStream: IInStream): HRESULT;
 begin
 begin
   { This is for multi-volume archives: after 7-Zip figures out the name of other volumes (like
   { This is for multi-volume archives: after 7-Zip figures out the name of other volumes (like
     archive.7z.002) it will then use this callback to open it. The callback must either return
     archive.7z.002) it will then use this callback to open it. The callback must either return
@@ -909,48 +977,6 @@ begin
     VersionBanner := '';
     VersionBanner := '';
 end;
 end;
 
 
-function GetHandler(const Ext, NotFoundErrorMsg: String): TGUID;
-begin
-  if SameText(Ext, '.7z') then
-    Result := CLSID_Handler7z
-  else if SameText(Ext, '.zip') then
-    Result := CLSID_HandlerZip
-  else if SameText(Ext, '.gz') then
-    Result := CLSID_HandlerGzip
-  else if SameText(Ext, '.bz2') then
-    Result := CLSID_HandlerBZip2
-  else if SameText(Ext, '.xz') then
-    Result := CLSID_HandlerXz
-  else if SameText(Ext, '.tar') then
-    Result := CLSID_HandlerTar
-  else if SameText(Ext, '.rar') then
-    Result := CLSID_HandlerRar
-  else if SameText(Ext, '.iso') then
-    Result := CLSID_HandlerIso
-  else if SameText(Ext, '.msi') then
-    Result := CLSID_HandlerCompound
-  else if SameText(Ext, '.cab') then
-    Result := CLSID_HandlerCab
-  else if SameText(Ext, '.rpm') then
-    Result := CLSID_HandlerRpm
-  else if SameText(Ext, '.vhd') then
-    Result := CLSID_HandlerVhd
-  else if SameText(Ext, '.vhdx') then
-    Result := CLSID_HandlerVhdx
-  else if SameText(Ext, '.vdi') then
-    Result := CLSID_HandlerVDI
-  else if SameText(Ext, '.vmdk') then
-    Result := CLSID_HandlerVMDK
-  else if SameText(Ext, '.wim') then
-    Result := CLSID_HandlerWim
-  else if SameText(Ext, '.dmg') then
-    Result := CLSID_HandlerDmg
-  else if SameText(Ext, '.001') then
-    Result := CLSID_HandlerSplit
-  else
-    InternalError(NotFoundErrorMsg);
-end;
-
 var
 var
   LoggedBanner: Boolean;
   LoggedBanner: Boolean;
 
 
@@ -964,6 +990,8 @@ end;
 
 
 function OpenArchiveRedir(const DisableFsRedir: Boolean;
 function OpenArchiveRedir(const DisableFsRedir: Boolean;
   const ArchiveFilename, Password: String; const clsid: TGUID; out numItems: UInt32): IInArchive;
   const ArchiveFilename, Password: String; const clsid: TGUID; out numItems: UInt32): IInArchive;
+const
+  DefaultScanSize: Int64 = 1 shl 23; { From Client7z.cpp }
 begin
 begin
   { CreateObject }
   { CreateObject }
   if CreateSevenZipObject(clsid, IInArchive, Result) <> S_OK then
   if CreateSevenZipObject(clsid, IInArchive, Result) <> S_OK then
@@ -977,12 +1005,47 @@ begin
     SevenZipWin32Error('CreateFile');
     SevenZipWin32Error('CreateFile');
   end;
   end;
   const InStream: IInStream = TInStream.Create(F);
   const InStream: IInStream = TInStream.Create(F);
-  var ScanSize: Int64 := 1 shl 23; { From Client7z.cpp }
-  const OpenCallback: IArchiveOpenCallback = TArchiveOpenCallback.Create(DisableFsRedir, ArchiveFileName, Password);
+  var ScanSize := DefaultScanSize;
+  const OpenCallback: IArchiveOpenCallback = TArchiveOpenFileCallback.Create(DisableFsRedir, ArchiveFileName, Password);
   if Result.Open(InStream, @ScanSize, OpenCallback) <> S_OK then
   if Result.Open(InStream, @ScanSize, OpenCallback) <> S_OK then
     SevenZipError(SetupMessages[msgArchiveIsCorrupted], 'Cannot open file as archive' { Just like Client7z.cpp });
     SevenZipError(SetupMessages[msgArchiveIsCorrupted], 'Cannot open file as archive' { Just like Client7z.cpp });
   if Result.GetNumberOfItems(numItems) <> S_OK then
   if Result.GetNumberOfItems(numItems) <> S_OK then
     SevenZipError(SetupMessages[msgArchiveIsCorrupted], 'Cannot get number of items');
     SevenZipError(SetupMessages[msgArchiveIsCorrupted], 'Cannot get number of items');
+
+  if numItems = 1 then begin
+    { Get inner archive stream if it exists - See OpenArchive.cpp CArchiveLink::Open }
+    var MainSubFile: Cardinal;
+    var SubSeqStream: ISequentialInStream;
+    if not GetProperty(Result, $FFFF, kpidMainSubfile, MainSubFile) or
+       (MainSubFile <> 0) or
+       not Supports(Result, IInArchiveGetStream) or
+       ((Result as IInArchiveGetStream).GetStream(MainSubFile, SubSeqStream) <> S_OK) or
+       (SubSeqStream = nil) or
+       not Supports(SubSeqStream, IInStream) then
+      Exit;
+    const SubStream = SubSeqStream as IInStream;
+
+    { Open inner archive }
+    var MainSubFilePath: String;
+    if not GetProperty(Result, MainSubFile, kpidPath, MainSubFilePath) then
+      Exit;
+    if MainSubFilePath = '' then
+      MainSubFilePath := PathChangeExt(ArchiveFilename, '');
+
+    const SubClsid = GetHandler(MainSubFilePath, '');
+    var SubResult: IInArchive;
+    if CreateSevenZipObject(SubClsid, IInArchive, SubResult) <> S_OK then
+      Exit;
+
+    var SubScanSize := DefaultScanSize;
+    const SubOpenCallback: IArchiveOpenCallback =
+      TArchiveOpenCallbackWithStreamBackup.Create(Password, InStream); { In tests the backup of InStream wasn't needed but better safe than sorry }
+    if (SubResult.Open(SubStream, @SubScanSize, SubOpenCallback) <> S_OK) or
+       (SubResult.GetNumberOfItems(numItems) <> S_OK) then
+      Exit;
+
+    Result := SubResult;
+  end;
 end;
 end;
 
 
 { ExtractArchiveRedir }
 { ExtractArchiveRedir }
@@ -995,7 +1058,7 @@ begin
 
 
   if ArchiveFileName = '' then
   if ArchiveFileName = '' then
     InternalError('ExtractArchive: Invalid ArchiveFileName value');
     InternalError('ExtractArchive: Invalid ArchiveFileName value');
-  const clsid = GetHandler(PathExtractExt(ArchiveFilename),
+  const clsid = GetHandler(ArchiveFilename,
     'ExtractArchive: Unknown ArchiveFileName extension');
     'ExtractArchive: Unknown ArchiveFileName extension');
   if DestDir = '' then
   if DestDir = '' then
     InternalError('ExtractArchive: Invalid DestDir value');
     InternalError('ExtractArchive: Invalid DestDir value');
@@ -1086,7 +1149,7 @@ begin
 
 
   if ArchiveFileName = '' then
   if ArchiveFileName = '' then
     InternalError('ArchiveFindFirstFile: Invalid ArchiveFileName value');
     InternalError('ArchiveFindFirstFile: Invalid ArchiveFileName value');
-  const clsid = GetHandler(PathExtractExt(ArchiveFilename),
+  const clsid = GetHandler(ArchiveFilename,
     'ArchiveFindFirstFile: Unknown ArchiveFileName extension');
     'ArchiveFindFirstFile: Unknown ArchiveFileName extension');
 
 
   LogBannerOnce;
   LogBannerOnce;
@@ -1207,4 +1270,6 @@ begin
   ArchiveFindStates.Free;
   ArchiveFindStates.Free;
 end;
 end;
 
 
+{ TArchiveOpenCallbackWithStreamBackup }
+
 end.
 end.

+ 2 - 2
whatsnew.htm

@@ -40,7 +40,7 @@ For conditions of distribution and use, see <a href="files/is/license.txt">LICEN
 
 
 <p><a name="6.5.0"></a><span class="ver">6.5.0-dev </span><span class="date">(?)</span></p>
 <p><a name="6.5.0"></a><span class="ver">6.5.0-dev </span><span class="date">(?)</span></p>
 <span class="head2">Improved archive extraction</span>
 <span class="head2">Improved archive extraction</span>
-<p>Support for extraction of downloaded archives has been improved. It's now possible to enable support for password-protected archives and for multiple archive formats other than .7z such as .zip. This can also be used to lower the memory requirements for extraction of .7z archives that contain large files. Additionally the <tt>[Files]</tt> section now supports archive extraction.</p>
+<p>Support for extraction of downloaded archives has been improved. It's now possible to enable support for password-protected archives,  multi-volume archives, and multiple archive formats other than .7z such as .zip. This can also be used to lower the memory requirements for extraction of .7z archives that contain large files. Additionally the <tt>[Files]</tt> section now supports archive extraction.</p>
 <p>All of this is optional and does <i>not</i> increase the size of Setup if not used.</p>
 <p>All of this is optional and does <i>not</i> increase the size of Setup if not used.</p>
 <ul>
 <ul>
   <li>Updated <tt>[Setup]</tt> and <tt>[Files]</tt> section:
   <li>Updated <tt>[Setup]</tt> and <tt>[Files]</tt> section:
@@ -56,7 +56,7 @@ For conditions of distribution and use, see <a href="files/is/license.txt">LICEN
     </li>
     </li>
     <li>Added new <tt>[Files]</tt> section flag <tt>extractarchive</tt> and parameter <tt>ExtractArchivePassword</tt>, intended to enable the seamless integration and extraction of archives.
     <li>Added new <tt>[Files]</tt> section flag <tt>extractarchive</tt> and parameter <tt>ExtractArchivePassword</tt>, intended to enable the seamless integration and extraction of archives.
       <ul>
       <ul>
-        <li>The supported archive formats, beyond .7z, and the support for password-protected archives, depend on the aforementioned <tt>ArchiveExtraction</tt> directive, that must not be set to <tt>basic</tt>.</li>
+        <li>The supported archive formats, beyond .7z, and the support for password-protected and multi-volume archives, depend on the aforementioned <tt>ArchiveExtraction</tt> directive, that must not be set to <tt>basic</tt>.</li>
         <li>Flag <tt>extractarchive</tt> must be combined with the <tt>external</tt> and <tt>ignoreversion</tt> flags. It is usually also combined with the <tt>recursesubdirs</tt> and <tt>createallsubdirs</tt> flags.</li>
         <li>Flag <tt>extractarchive</tt> must be combined with the <tt>external</tt> and <tt>ignoreversion</tt> flags. It is usually also combined with the <tt>recursesubdirs</tt> and <tt>createallsubdirs</tt> flags.</li>
         <li>Using a solid archive is not recommended; extraction performance may degrade depending on the solid block size.</li>
         <li>Using a solid archive is not recommended; extraction performance may degrade depending on the solid block size.</li>
         <li>Archive extraction otherwise behaves the same as external file copying. For example, it supports automatic uninstallation of extracted files and can be combined with same other flags and parameters.</li>
         <li>Archive extraction otherwise behaves the same as external file copying. For example, it supports automatic uninstallation of extracted files and can be combined with same other flags and parameters.</li>