Jelajahi Sumber

Merge branch '7zdll'

Martijn Laan 3 bulan lalu
induk
melakukan
efe30f80d5

+ 3 - 47
Examples/CodeDownloadFiles.iss

@@ -2,11 +2,10 @@
 ;
 ; This script shows how the CreateDownloadPage support function can be used to
 ; download temporary files while showing the download progress to the user.
-; One of the files is a 7-Zip archive and the script shows how to extract it.
 ;
 ; To verify the downloaded files, this script shows two methods:
-; -For innosetup-latest.exe and MyProg-ExtraReadmes.7z: using the Inno Setup
-;  Signature Tool, the [ISSigKeys] section, and the issigverify flag
+; -For innosetup-latest.exe: using the Inno Setup Signature Tool, the [ISSigKeys]
+;  section, and the issigverify flag
 ; -For iscrypt.dll: using a simple SHA256 check
 ; Using the Inno Setup Signature Tool has the benefit that the script does not
 ; need to be changed when the downloaded file changes, so any installers built
@@ -35,7 +34,6 @@ Source: "Readme.txt"; DestDir: "{app}"; Flags: isreadme;
 ; These files will be downloaded
 Source: "{tmp}\innosetup-latest.exe"; DestDir: "{app}"; Flags: external ignoreversion issigverify
 Source: "{tmp}\ISCrypt.dll"; DestDir: "{app}"; Flags: external ignoreversion
-Source: "{tmp}\MyProg-ExtraReadmes\*"; Excludes: "*.issig"; DestDir: "{app}"; Flags: external recursesubdirs ignoreversion issigverify
 
 [Icons]
 Name: "{group}\My Program"; Filename: "{app}\MyProg.exe"
@@ -43,27 +41,11 @@ Name: "{group}\My Program"; Filename: "{app}\MyProg.exe"
 [Code]
 var
   DownloadPage: TDownloadWizardPage;
-  ExtractionPage: TExtractionWizardPage;
-
-function OnDownloadProgress(const Url, FileName: String; const Progress, ProgressMax: Int64): Boolean;
-begin
-  if Progress = ProgressMax then
-    Log(Format('Successfully downloaded file to {tmp}: %s', [FileName]));
-  Result := True;
-end;
-
-function OnExtractionProgress(const ArchiveName, FileName: String; const Progress, ProgressMax: Int64): Boolean;
-begin
-  if Progress = ProgressMax then
-    Log(Format('Successfully extracted archive to {tmp}\MyProg-ExtraReadmes: %s', [ArchiveName]));
-  Result := True;
-end;
 
 procedure InitializeWizard;
 begin
-  DownloadPage := CreateDownloadPage(SetupMessage(msgWizardPreparing), SetupMessage(msgPreparingDesc), @OnDownloadProgress);
+  DownloadPage := CreateDownloadPage(SetupMessage(msgWizardPreparing), SetupMessage(msgPreparingDesc), nil);
   DownloadPage.ShowBaseNameInsteadOfUrl := True;
-  ExtractionPage := CreateExtractionPage(SetupMessage(msgWizardPreparing), SetupMessage(msgPreparingDesc), @OnExtractionProgress);
 end;
 
 function NextButtonClick(CurPageID: Integer): Boolean;
@@ -74,7 +56,6 @@ begin
     DownloadPage.Add('https://jrsoftware.org/download.php/is.exe?dontcount=1', 'innosetup-latest.exe', '');
     DownloadPage.Add('https://jrsoftware.org/download.php/is.exe.issig?dontcount=1', 'innosetup-latest.exe.issig', '');
     DownloadPage.Add('https://jrsoftware.org/download.php/iscrypt.dll?dontcount=1', 'ISCrypt.dll', '2f6294f9aa09f59a574b5dcd33be54e16b39377984f3d5658cda44950fa0f8fc');
-    DownloadPage.Add('https://jrsoftware.org/download.php/myprog-extrareadmes.7z?dontcount=1', 'MyProg-ExtraReadmes.7z', '');
     DownloadPage.Show;
     try
       try
@@ -91,31 +72,6 @@ begin
     finally
       DownloadPage.Hide;
     end;
-
-    if not Result then
-      Exit;
-
-    ExtractionPage.Clear;
-    ExtractionPage.Add(ExpandConstant('{tmp}\MyProg-ExtraReadmes.7z'), ExpandConstant('{tmp}\MyProg-ExtraReadmes'), True);
-    ExtractionPage.Show;
-    try
-      try
-        // Extracts the archive to {tmp}\MyProg-ExtraReadmes
-        // Please see the Extract7ZipArchive topic in the help file for limitations before using this for you own archives
-        // Note that each file in this archive comes with an .issig signature file
-        // These signature files are used by the [Files] section to verify the archive's content
-        ExtractionPage.Extract;
-        Result := True;
-      except
-        if ExtractionPage.AbortedByUser then
-          Log('Aborted by user.')
-        else
-          SuppressibleMsgBox(AddPeriod(GetExceptionMessage), mbCriticalError, MB_OK, IDOK);
-        Result := False;
-      end;
-    finally
-      ExtractionPage.Hide;
-    end;
   end else
     Result := True;
 end;

+ 101 - 0
Examples/CodeDownloadFiles2.iss

@@ -0,0 +1,101 @@
+; -- CodeDownloadFiles2.iss --
+; Same as CodeDownloadFiles1.iss but additionally downloads a 7-Zip archive and
+; shows how to verify and extract it.
+
+[Setup]
+AppName=My Program
+AppVersion=1.5
+WizardStyle=modern
+DefaultDirName={autopf}\My Program
+DefaultGroupName=My Program
+UninstallDisplayIcon={app}\MyProg.exe
+OutputDir=userdocs:Inno Setup Examples Output
+;Use "ArchiveExtraction=enhanced/nopassword" if your archive has large files
+;Use "ArchiveExtraction=enhanced" if your archive has large files *and* a password
+;Use "ArchiveExtraction=full" if your archive is not a .7z file but for example a .zip file
+ArchiveExtraction=basic
+
+[ISSigKeys]
+Name: "mykey"; \
+  KeyID:   "def020edee3c4835fd54d85eff8b66d4d899b22a777353ca4a114b652e5e7a28"; \
+  PublicX: "515dc7d6c16d4a46272ceb3d158c5630a96466ab4d948e72c2029d737c823097"; \
+  PublicY: "f3c21f6b5156c52a35f6f28016ee3e31a3ded60c325b81fb7b1f88c221081a61"
+
+[Files]
+; Place any regular files here
+Source: "MyProg.exe"; DestDir: "{app}";
+Source: "MyProg.chm"; DestDir: "{app}";
+Source: "Readme.txt"; DestDir: "{app}"; Flags: isreadme;
+; These files will be downloaded
+Source: "{tmp}\innosetup-latest.exe"; DestDir: "{app}"; Flags: external ignoreversion issigverify
+Source: "{tmp}\ISCrypt.dll"; DestDir: "{app}"; Flags: external ignoreversion
+Source: "{tmp}\MyProg-ExtraReadmes\*"; Excludes: "*.issig"; DestDir: "{app}"; Flags: external recursesubdirs ignoreversion issigverify
+
+[Icons]
+Name: "{group}\My Program"; Filename: "{app}\MyProg.exe"
+
+[Code]
+var
+  DownloadPage: TDownloadWizardPage;
+  ExtractionPage: TExtractionWizardPage;
+
+procedure InitializeWizard;
+begin
+  DownloadPage := CreateDownloadPage(SetupMessage(msgWizardPreparing), SetupMessage(msgPreparingDesc), nil);
+  DownloadPage.ShowBaseNameInsteadOfUrl := True;
+  ExtractionPage := CreateExtractionPage(SetupMessage(msgWizardPreparing), SetupMessage(msgPreparingDesc), nil);
+end;
+
+function NextButtonClick(CurPageID: Integer): Boolean;
+begin
+  if CurPageID = wpReady then begin
+    DownloadPage.Clear;
+    // Use AddEx to specify a username and password
+    DownloadPage.Add('https://jrsoftware.org/download.php/is.exe?dontcount=1', 'innosetup-latest.exe', '');
+    DownloadPage.Add('https://jrsoftware.org/download.php/is.exe.issig?dontcount=1', 'innosetup-latest.exe.issig', '');
+    DownloadPage.Add('https://jrsoftware.org/download.php/iscrypt.dll?dontcount=1', 'ISCrypt.dll', '2f6294f9aa09f59a574b5dcd33be54e16b39377984f3d5658cda44950fa0f8fc');
+    DownloadPage.Add('https://jrsoftware.org/download.php/myprog-extrareadmes.7z?dontcount=1', 'MyProg-ExtraReadmes.7z', '');
+    DownloadPage.Show;
+    try
+      try
+        // Downloads the files to {tmp}
+        DownloadPage.Download;
+        Result := True;
+      except
+        if DownloadPage.AbortedByUser then
+          Log('Aborted by user.')
+        else
+          SuppressibleMsgBox(AddPeriod(GetExceptionMessage), mbCriticalError, MB_OK, IDOK);
+        Result := False;
+      end;
+    finally
+      DownloadPage.Hide;
+    end;
+
+    if not Result then
+      Exit;
+
+    ExtractionPage.Clear;
+    // Use AddEx to specify a password
+    ExtractionPage.Add(ExpandConstant('{tmp}\MyProg-ExtraReadmes.7z'), ExpandConstant('{tmp}\MyProg-ExtraReadmes'), True);
+    ExtractionPage.Show;
+    try
+      try
+        // Extracts the archive to {tmp}\MyProg-ExtraReadmes
+        // Note that each file in the MyProg-ExtraReadmes.7z example archive comes with an .issig signature file
+        // These signature files are used by the [Files] section to verify the archive's content
+        ExtractionPage.Extract;
+        Result := True;
+      except
+        if ExtractionPage.AbortedByUser then
+          Log('Aborted by user.')
+        else
+          SuppressibleMsgBox(AddPeriod(GetExceptionMessage), mbCriticalError, MB_OK, IDOK);
+        Result := False;
+      end;
+    finally
+      ExtractionPage.Hide;
+    end;
+  end else
+    Result := True;
+end;

TEMPAT SAMPAH
Files/is7z.dll


+ 6 - 0
Files/is7z.dll.issig

@@ -0,0 +1,6 @@
+format issig-v1
+file-size 1089168
+file-hash aaee107184d026680517e27e89ace2d308d69c08489312d3d6a30f9d5c726472
+key-id def0147c3bbc17ab99bf7b7a9c2de1390283f38972152418d7c2a4a7d7131a38
+sig-r aa9fda0b187df585031c04cfe40182c2d40992a265f710641e5aa4091e21884e
+sig-s 8b155e8e4a775f1c2cb3edb1b7a32ccba9713d7c112e64504e4df5b2c50a8e72

TEMPAT SAMPAH
Files/is7zxa.dll


+ 6 - 0
Files/is7zxa.dll.issig

@@ -0,0 +1,6 @@
+format issig-v1
+file-size 261776
+file-hash e434229ac686914082a2a350bb4ebc5a3c1d9f5e041d0cdb7fa089d24a2766ab
+key-id def0147c3bbc17ab99bf7b7a9c2de1390283f38972152418d7c2a4a7d7131a38
+sig-r 8b7213b902f4780b402e5ce1f8b9f78d3792dc75cd4ff9c8d0a379b7974dde1c
+sig-s 9c638a1d18258ee57449d4af4402bee44ad5df87af5620fb2e3250abf9ae9309

TEMPAT SAMPAH
Files/is7zxr.dll


+ 6 - 0
Files/is7zxr.dll.issig

@@ -0,0 +1,6 @@
+format issig-v1
+file-size 213136
+file-hash 98d89fe65b5b5695bf227ecea33a43ce5e9bd67e1801cd5b256265b276b366fe
+key-id def0147c3bbc17ab99bf7b7a9c2de1390283f38972152418d7c2a4a7d7131a38
+sig-r 142c9a1002c78155d809fb4c5874bdfb77b27ba94ce4263d7a8e7cddff92783c
+sig-s d504edd91cc2aa40ccd7429bb6234af82c9d4392ce5a358fc36cf3bf284a4df2

+ 43 - 2
ISHelp/isetup.xml

@@ -134,7 +134,7 @@ Inno Setup is a <i>free</i> installer for Windows programs by Jordan Russell and
 <li>Complete <link topic="setup_uninstallable">uninstall</link> capabilities.</li>
 
 <li>Installation of <link topic="filessection">files</link>:<br/>
-Includes integrated support for "deflate", bzip2, and 7-Zip LZMA/LZMA2 file <link topic="setup_compression">compression</link>. The installer has the ability to compare file version info, replace in-use files, use shared file counting, register DLL/OCX's and type libraries, and install fonts.</li>
+Includes integrated support for "deflate", bzip2, and 7-Zip LZMA/LZMA2 file <link topic="setup_compression">compression</link>. The installer has the ability to compare file version info, replace in-use files, use shared file counting, register DLL/OCX's and type libraries, install fonts, download files, and extract archives.</li>
 
 <li>Creation of <link topic="iconssection">shortcuts</link> anywhere, including in the Start Menu and on the desktop.</li>
 
@@ -1066,6 +1066,7 @@ DefaultGroupName=My Program
 <li><link topic="setup_appversion"><b>AppVersion</b></link></li>
 <li><link topic="setup_architecturesallowed">ArchitecturesAllowed</link></li>
 <li><link topic="setup_architecturesinstallin64bitmode">ArchitecturesInstallIn64BitMode</link></li>
+<li><link topic="setup_archiveextraction">ArchiveExtraction</link></li>
 <li><link topic="setup_changesassociations">ChangesAssociations</link></li>
 <li><link topic="setup_changesenvironment">ChangesEnvironment</link></li>
 <li><link topic="setup_closeapplications">CloseApplications</link></li>
@@ -4302,7 +4303,7 @@ Keep the default set of selected tasks, but deselect the "desktopicon" task:<br/
 
 <li>Julian Seward: Creator of the <extlink href="http://www.bzip.org/">bzlib</extlink> compression library that Inno Setup uses.</li>
 
-<li>Igor Pavlov: Creator of the <extlink href="http://www.7-zip.org/sdk.html">LZMA</extlink> compression library that Inno Setup uses.</li>
+<li>Igor Pavlov: Creator of the <extlink href="https://7-zip.org/">7-Zip</extlink> and <extlink href="http://www.7-zip.org/sdk.html">LZMA SDK</extlink> libraries that Inno Setup uses.</li>
 
 <li>Vince Valenti: Most of the code for the "Window" <tt>[Setup]</tt> section directives (1.12.4).</li>
 
@@ -4717,6 +4718,46 @@ Name: portablemode; Description: "Portable Mode"</pre></example>
 </body>
 </setuptopic>
 
+<setuptopic directive="ArchiveExtraction">
+<keyword value=".7z" />
+<keyword value=".zip" />
+<setupvalid><tt>basic</tt><br/>
+<tt>enhanced/nopassword</tt><br/>
+<tt>enhanced</tt><br/>
+<tt>full</tt></setupvalid>
+<setupdefault><tt>basic</tt></setupdefault>
+<body>
+<p>This specifies the method of archive extraction used by support functions <link topic="isxfunc_ExtractArchive">ExtractArchive</link> and <link topic="isxfunc_CreateExtractionPage">CreateExtractionPage</link>.</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</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>The following table summarizes the differences between these methods.</p>
+<indent>
+<table>
+<tr><td></td><td><u>Memory Requirements</u></td><td><u>Password-protected Archives</u></td><td><u>Setup Size Increase</u></td><td><u>Archive Formats</u></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>101 KB</td><td>.7z</td></tr>
+<tr><td><tt>enhanced</tt></td><td>Normal</td><td>Yes</td><td>124 KB</td><td>.7z</td></tr>
+<tr><td><tt>full</tt></td><td>Normal</td><td>Yes</td><td>453 KB</td><td>.7z, .zip, .gz, .bz2, .xz, .tar, .rar, .iso, .msi, .cab, .rpm, .vhd, .vhdx, .vdi, .vmdk, .wim, .dmg</td></tr>
+</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>
+</indent>
+<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>The <tt>basic</tt> method has the following additional limitations, as written by Igor Pavlov in the LZMA SDK:</p>
+<ul>
+<li>It does not support PPMd and BZip2 methods.</li>
+<li>It converts original UTF-16 Unicode file names to UTF-8 Unicode file names.</li>
+<li>It decodes whole solid block from 7z archive to RAM. The RAM consumption can be high.</li>
+</ul>
+<p><b>See also:</b><br/>
+<link topic="isxfunc_ExtractArchive">ExtractArchive</link><br/>
+<link topic="isxfunc_CreateExtractionPage">CreateExtractionPage</link>
+</p>
+</body>
+</setuptopic>
+
 <setuptopic directive="Compression">
 <keyword value="zip" />
 <keyword value="bzip" />

+ 1 - 0
ISHelp/isxclasses.pas

@@ -788,6 +788,7 @@ TExtractionWizardPage = class(TOutputProgressWizardPage)
   property AbortButton: TNewButton; read;
   property AbortedByUser: Boolean; read;
   procedure Add(const ArchiveFileName, DestDir: String; const FullPaths: Boolean);
+  procedure AddEx(const ArchiveFileName, DestDir, Password: String; const FullPaths: Boolean);
   procedure Clear;
   procedure Extract;
   property ShowArchiveInsteadOfFile: Boolean; read write;

+ 1 - 0
ISHelp/isxclasses_wordlists_generated.pas

@@ -106,6 +106,7 @@ var
     'function Write(Buffer: AnyString; ByteCount: Longint): Longint;',
     'procedure Add(ArchiveFileName, DestDir: String; FullPaths: Boolean);',
     'procedure Add(Url, BaseName, RequiredSHA256OfFile: String);',
+    'procedure AddEx(ArchiveFileName, DestDir, Password: String; FullPaths: Boolean);',
     'procedure AddEx(Url, BaseName, RequiredSHA256OfFile, UserName, Password: String);',
     'procedure AddStrings(Strings: TStrings);',
     'procedure Animate;',

+ 12 - 26
ISHelp/isxfunc.xml

@@ -1830,7 +1830,7 @@ end;</pre></example>
 <link topic="isxfunc_DownloadTemporaryFileDate">DownloadTemporaryFileDate</link><br />
 <link topic="isxfunc_CreateDownloadPage">CreateDownloadPage</link><br />
 <link topic="isxfunc_ExtractTemporaryFile">ExtractTemporaryFile</link><br />
-<link topic="isxfunc_Extract7ZipArchive">Extract7ZipArchive</link></p></seealso>
+<link topic="isxfunc_ExtractArchive">ExtractArchive</link></p></seealso>
         <example><pre>
 [Code]
 function OnDownloadProgress(const Url, Filename: String; const Progress, ProgressMax: Int64): Boolean;
@@ -1876,30 +1876,15 @@ end;</pre>
 <p>See <link topic="isxfunc_DownloadTemporaryFile">DownloadTemporaryFile</link> for other considerations.</p></description>
       </function>
       <function>
-        <name>Extract7ZipArchive</name>
-        <prototype>procedure Extract7ZipArchive(const ArchiveFileName, DestDir: String; const FullPaths: Boolean; const OnExtractionProgress: TOnExtractionProgress);</prototype>
-        <description><p>Extracts the specified 7-Zip archive to the specified directory, with or without using path names.</p>
+        <name>ExtractArchive</name>
+        <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>
 <p>An exception will be raised if there was an error.</p>
-<p>The archive must not be encrypted.</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>Set OnExtractionProgress to a function to be informed of progress, or <tt>nil</tt> otherwise.</p></description>
         <remarks><p>TOnExtractionProgress is defined as:</p>
 <p><tt>TOnExtractionProgress = function(const ArchiveName, FileName: String; const Progress, ProgressMax: Int64): Boolean;</tt></p>
-<p>Return True to allow the extraction to continue, False otherwise.</p>
-<p><tt>Extract7ZipArchive</tt> uses an embedded version of the &quot;7z ANSI-C Decoder&quot; from the LZMA SDK by Igor Pavlov, as-is, except that Unicode support and error messages were improved and that it outputs memory requirements.</p>
-<p>All output of the decoder is logged if logging is enabled, including error messages but excluding empty lines.</p></remarks>
-        <limitations><p>The decoder has the following limitations, as written by Igor Pavlov in the LZMA SDK:</p>
-<ul>
-<li>It reads only &quot;FileName&quot;, &quot;Size&quot;, &quot;LastWriteTime&quot; and &quot;CRC&quot; information for each file in archive.</li>
-<li>It does not support PPMd and BZip2 methods.</li>
-<li>It converts original UTF-16 Unicode file names to UTF-8 Unicode file names.</li>
-<li>It decodes whole solid block from 7z archive to RAM. The RAM consumption can be high.</li>
-</ul>
-<p>To expand on his comments about RAM consumption: 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 <tt>Extract7ZipArchive</tt> requires at least 1 GB of RAM. Consider using a different solution for extracting large files, such as embedding 7-Zip itself, which does not use as much RAM, into your installation.</p>
-<p>Additionally he wrote:</p>
-<ul>
-<li>You can create .7z archive with 7z.exe, 7za.exe or 7zr.exe:<br />
-<tt>7z.exe a archive.7z *.htm -r -mx -m0fb=255</tt></li>
-</ul></limitations>
+<p>Return True to allow the extraction to continue, False otherwise.</p></remarks>
         <seealso><p><link topic="isxfunc_CreateExtractionPage">CreateExtractionPage</link><br />
 <link topic="isxfunc_CreateDownloadPage">CreateDownloadPage</link><br />
 <link topic="isxfunc_DownloadTemporaryFile">DownloadTemporaryFile</link><br />
@@ -2703,18 +2688,19 @@ Page := CreateOutputMsgMemoPage(wpWelcome,
       <function>
         <name>CreateExtractionPage</name>
         <prototype>function CreateExtractionPage(const ACaption, ADescription: String; const OnExtractionProgress: TOnExtractionProgress): TExtractionWizardPage;</prototype>
-        <description><p>Creates a wizard page to extract 7-Zip 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>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>
         <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>
 <p>Always put the <tt>Hide</tt> call inside the <tt>finally</tt> part of a <tt>try..finally</tt> language construct, as demonstrated in <i>CodeDownloadFiles.iss</i>. Not calling <tt>Hide</tt> will result in the wizard being permanently stuck on the progress page.</p>
-<p>To add a new archive to extract, call the <tt>Add</tt> method. Always call the <tt>Clear</tt> method before adding the first file.</p>
+<p>To add a new archive to extract, call the <tt>Add</tt> method or the <tt>AddEx</tt> method if the archive is password-protected. Always call the <tt>Clear</tt> method before adding the first file.</p>
 <p>To start the extraction, call the <tt>Extract</tt> method. An exception will be raised if there was an error. Otherwise, <tt>Extract</tt> returns the number of archives extracted.</p>
 <p>Set the <tt>ShowArchiveInsteadFile</tt> property to <tt>True</tt> to show the name of the archive which is being extracted to the user instead of the names of the files inside the archive.</p>
-<p>See <link topic="isxfunc_Extract7ZipArchive">Extract7ZipArchive</link> for other considerations and the definition of <tt>TOnExtractionProgress</tt>.</p></remarks>
-        <example><p>See <i>CodeDownloadFiles.iss</i> for an example.</p></example>
+<p>See <link topic="isxfunc_ExtractArchive">ExtractArchive</link> for other considerations and the definition of <tt>TOnExtractionProgress</tt>.</p></remarks>
+        <example><p>See <i>CodeDownloadFiles2.iss</i> for an example.</p></example>
         <seealso><p><link topic="scriptclasses" anchor="TExtractionWizardPage">TExtractionWizardPage</link><br />
-<link topic="isxfunc_Extract7ZipArchive">Extract7ZipArchive</link><br />
+<link topic="isxfunc_ExtractArchive">ExtractArchive</link><br />
 <link topic="isxfunc_CreateOutputProgressPage">CreateOutputProgressPage</link></p></seealso>
       </function>
       <function>

+ 0 - 7
ISHelp/isxfunc.xsl

@@ -107,13 +107,6 @@
 </xsl:when>
 </xsl:choose>
 
-<xsl:choose>
-<xsl:when test="limitations">
-<p margin="no"><b>Limitations:</b></p>
-<xsl:apply-templates select="limitations"/>
-</xsl:when>
-</xsl:choose>
-
 <xsl:choose>
 <xsl:when test="example">
 <p margin="no"><b>Example:</b></p>

+ 2 - 0
Projects/Bin/synch-isfiles.bat

@@ -6,6 +6,8 @@ echo - Synching files from Files to Projects\Bin
 
 copy ..\..\Files\Default.isl
 copy ..\..\Files\ISPPBuiltins.iss
+copy ..\..\Files\is7z*.dll
+copy ..\..\Files\is7z*.dll.issig
 copy ..\..\Files\is*zip.dll
 copy ..\..\Files\isbzip.dll.issig
 copy ..\..\Files\is*zlib.dll

+ 3 - 1
Projects/Setup.dpr

@@ -98,7 +98,9 @@ uses
   Setup.ScriptFunc.HelperFunc in 'Src\Setup.ScriptFunc.HelperFunc.pas',
   ECDSA in '..\Components\ECDSA.pas',
   ISSigFunc in '..\Components\ISSigFunc.pas',
-  StringScanner in '..\Components\StringScanner.pas';
+  StringScanner in '..\Components\StringScanner.pas',
+  Compression.SevenZipDLLDecoder in 'Src\Compression.SevenZipDLLDecoder.pas',
+  Compression.SevenZipDLLDecoder.Interfaces in 'Src\Compression.SevenZipDLLDecoder.Interfaces.pas';
 
 {$SETPEOSVERSION 6.1}
 {$SETPESUBSYSVERSION 6.1}

+ 2 - 0
Projects/Setup.dproj

@@ -171,6 +171,8 @@
         <DCCReference Include="..\Components\ECDSA.pas"/>
         <DCCReference Include="..\Components\ISSigFunc.pas"/>
         <DCCReference Include="..\Components\StringScanner.pas"/>
+        <DCCReference Include="Src\Compression.SevenZipDLLDecoder.pas"/>
+        <DCCReference Include="Src\Compression.SevenZipDLLDecoder.Interfaces.pas"/>
         <BuildConfiguration Include="Base">
             <Key>Base</Key>
         </BuildConfiguration>

+ 1 - 0
Projects/Src/Compiler.ScriptClasses.pas

@@ -567,6 +567,7 @@ begin
     RegisterProperty('AbortedByUser', 'Boolean', iptr);
     RegisterProperty('ShowArchiveInsteadOfFile', 'Boolean', iptrw);
     RegisterMethod('procedure Add(const ArchiveFileName, DestDir: String; const FullPaths: Boolean)');
+    RegisterMethod('procedure AddEx(const ArchiveFileName, DestDir, Password: String; const FullPaths: Boolean)');
     RegisterMethod('procedure Clear');
     RegisterMethod('procedure Extract');
     RegisterMethod('procedure Show'); { Without this TOutputProgressWizardPage's Show will be called }

+ 1 - 0
Projects/Src/Compiler.ScriptFunc.pas

@@ -152,6 +152,7 @@ begin
   ObsoleteFunctionWarnings.Add('IsTaskSelected', Format(SCompilerCodeFunctionRenamed, ['IsTaskSelected', 'WizardIsTaskSelected']));
   ObsoleteFunctionWarnings.Add('IsX64', Format(SCompilerCodeFunctionDeprecatedWithAlternativeAndDocs, ['IsX64', 'IsX64OS', 'IsX64Compatible', 'Architecture Identifiers']));
   ObsoleteFunctionWarnings.Add('FileCopy', Format(SCompilerCodeFunctionRenamed, ['FileCopy', 'CopyFile']));
+  ObsoleteFunctionWarnings.Add('Extract7ZipArchive', Format(SCompilerCodeFunctionRenamed, ['Extract7ZipArchive', 'ExtractArchive']));
 
   RegisterConst('MaxInt', MaxInt);
 

+ 23 - 1
Projects/Src/Compiler.SetupCompiler.pas

@@ -2588,6 +2588,17 @@ begin
         ProcessExpressionParameter(KeyName, LowerCase(Value),
           EvalArchitectureIdentifier, False, SetupHeader.ArchitecturesInstallIn64BitMode);
       end;
+    ssArchiveExtraction: begin
+        Value := LowerCase(Trim(Value));
+        if Value = 'enhanced/nopassword' then begin
+          SetupHeader.SevenZipLibraryName := 'is7zxr.dll'
+        end else if Value = 'enhanced' then begin
+          SetupHeader.SevenZipLibraryName := 'is7zxa.dll'
+        end else if Value = 'full' then
+          SetupHeader.SevenZipLibraryName := 'is7z.dll'
+        else if Value <> 'basic' then
+          Invalid;
+      end;
     ssASLRCompatible: begin
         ASLRCompatible := StrToBool(Value);
       end;
@@ -6711,7 +6722,7 @@ var
   ExeFile: TFile;
   LicenseText, InfoBeforeText, InfoAfterText: AnsiString;
   WizardImages, WizardSmallImages: TObjectList<TCustomMemoryStream>;
-  DecompressorDLL: TMemoryStream;
+  DecompressorDLL, SevenZipDLL: TMemoryStream;
 
   SetupLdrOffsetTable: TSetupLdrOffsetTable;
   SizeOfExe, SizeOfHeaders: Longint;
@@ -6821,6 +6832,8 @@ var
         WriteStream(WizardSmallImages[J], W);
       if SetupHeader.CompressMethod in [cmZip, cmBzip] then
         WriteStream(DecompressorDLL, W);
+      if SetupHeader.SevenZipLibraryName <> '' then
+        WriteStream(SevenZipDLL, W);
 
       W.Finish;
     finally
@@ -7518,6 +7531,7 @@ begin
   WizardSmallImages := nil;
   SetupE32 := nil;
   DecompressorDLL := nil;
+  SevenZipDLL := nil;
 
   try
     Finalize(SetupHeader);
@@ -8038,6 +8052,13 @@ begin
         end;
     end;
 
+    { Read 7-Zip DLL. Must be done after [Files] is parsed, since
+      SetupHeader.SevenZipLibraryName isn't set until then }
+    if SetupHeader.SevenZipLibraryName <> '' then begin
+      AddStatus(Format(SCompilerStatusReadingFile, [SetupHeader.SevenZipLibraryName]));
+      SevenZipDLL := CreateMemoryStreamFromFile(CompilerDir + SetupHeader.SevenZipLibraryName);
+    end;
+
     { Add default types if necessary }
     if (ComponentEntries.Count > 0) and (TypeEntries.Count = 0) then begin
       AddDefaultSetupType(DefaultTypeEntryNames[0], [], ttDefaultFull);
@@ -8217,6 +8238,7 @@ begin
     CallPreprocessorCleanupProc;
     UsedUserAreas.Clear;
     WarningsList.Clear;
+    SevenZipDLL.Free;
     DecompressorDLL.Free;
     SetupE32.Free;
     WizardSmallImages.Free;

+ 156 - 0
Projects/Src/Compression.SevenZipDLLDecoder.Interfaces.pas

@@ -0,0 +1,156 @@
+unit Compression.SevenZipDLLDecoder.Interfaces;
+
+{
+  Inno Setup
+  Copyright (C) 1997-2025 Jordan Russell
+  Portions by Martijn Laan
+  For conditions of distribution and use, see LICENSE.TXT.
+
+  Minimal extraction interfaces from 7-Zip Decoder DLLs
+}
+
+interface
+
+{$MINENUMSIZE 4}
+
+uses
+  Winapi.ActiveX;
+
+const
+  { From Guid.txt }
+  CLSID_HandlerZip: TGUID = '{23170F69-40C1-278A-1000-000110010000}';
+  CLSID_HandlerBZip2: TGUID = '{23170F69-40C1-278A-1000-000110020000}';
+  CLSID_HandlerRar: TGUID = '{23170F69-40C1-278A-1000-000110030000}';
+  CLSID_HandlerArj: TGUID = '{23170F69-40C1-278A-1000-000110040000}';
+  CLSID_HandlerZ: TGUID = '{23170F69-40C1-278A-1000-000110050000}';
+  CLSID_HandlerLzh: TGUID = '{23170F69-40C1-278A-1000-000110060000}';
+  CLSID_Handler7z: TGUID = '{23170F69-40C1-278A-1000-000110070000}';
+  CLSID_HandlerCab: TGUID = '{23170F69-40C1-278A-1000-000110080000}';
+  CLSID_HandlerNsis: TGUID = '{23170F69-40C1-278A-1000-000110090000}';
+  CLSID_HandlerLzma: TGUID = '{23170F69-40C1-278A-1000-0001100A0000}';
+  CLSID_HandlerLzma86: TGUID = '{23170F69-40C1-278A-1000-0001100B0000}';
+  CLSID_HandlerXz: TGUID = '{23170F69-40C1-278A-1000-0001100C0000}';
+  CLSID_HandlerPpmd: TGUID = '{23170F69-40C1-278A-1000-0001100D0000}';
+  CLSID_HandlerZstd: TGUID = '{23170F69-40C1-278A-1000-0001100E0000}';
+  CLSID_HandlerLVM: TGUID = '{23170F69-40C1-278A-1000-000110BF0000}';
+  CLSID_HandlerAVB: TGUID = '{23170F69-40C1-278A-1000-000110C00000}';
+  CLSID_HandlerLP: TGUID = '{23170F69-40C1-278A-1000-000110C10000}';
+  CLSID_HandlerSparse: TGUID = '{23170F69-40C1-278A-1000-000110C20000}';
+  CLSID_HandlerAPFS: TGUID = '{23170F69-40C1-278A-1000-000110C30000}';
+  CLSID_HandlerVhdx: TGUID = '{23170F69-40C1-278A-1000-000110C40000}';
+  CLSID_HandlerBase64: TGUID = '{23170F69-40C1-278A-1000-000110C50000}';
+  CLSID_HandlerCOFF: TGUID = '{23170F69-40C1-278A-1000-000110C60000}';
+  CLSID_HandlerExt: TGUID = '{23170F69-40C1-278A-1000-000110C70000}';
+  CLSID_HandlerVMDK: TGUID = '{23170F69-40C1-278A-1000-000110C80000}';
+  CLSID_HandlerVDI: TGUID = '{23170F69-40C1-278A-1000-000110C90000}';
+  CLSID_HandlerQcow: TGUID = '{23170F69-40C1-278A-1000-000110CA0000}';
+  CLSID_HandlerGPT: TGUID = '{23170F69-40C1-278A-1000-000110CB0000}';
+  CLSID_HandlerRar5: TGUID = '{23170F69-40C1-278A-1000-000110CC0000}';
+  CLSID_HandlerIHex: TGUID = '{23170F69-40C1-278A-1000-000110CD0000}';
+  CLSID_HandlerHxs: TGUID = '{23170F69-40C1-278A-1000-000110CE0000}';
+  CLSID_HandlerTE: TGUID = '{23170F69-40C1-278A-1000-000110CF0000}';
+  CLSID_HandlerUEFIc: TGUID = '{23170F69-40C1-278A-1000-000110D00000}';
+  CLSID_HandlerUEFIs: TGUID = '{23170F69-40C1-278A-1000-000110D10000}';
+  CLSID_HandlerSquashFS: TGUID = '{23170F69-40C1-278A-1000-000110D20000}';
+  CLSID_HandlerCramFS: TGUID = '{23170F69-40C1-278A-1000-000110D30000}';
+  CLSID_HandlerAPM: TGUID = '{23170F69-40C1-278A-1000-000110D40000}';
+  CLSID_HandlerMslz: TGUID = '{23170F69-40C1-278A-1000-000110D50000}';
+  CLSID_HandlerFlv: TGUID = '{23170F69-40C1-278A-1000-000110D60000}';
+  CLSID_HandlerSwf: TGUID = '{23170F69-40C1-278A-1000-000110D70000}';
+  CLSID_HandlerSwfc: TGUID = '{23170F69-40C1-278A-1000-000110D80000}';
+  CLSID_HandlerNtfs: TGUID = '{23170F69-40C1-278A-1000-000110D90000}';
+  CLSID_HandlerFat: TGUID = '{23170F69-40C1-278A-1000-000110DA0000}';
+  CLSID_HandlerMbr: TGUID = '{23170F69-40C1-278A-1000-000110DB0000}';
+  CLSID_HandlerVhd: TGUID = '{23170F69-40C1-278A-1000-000110DC0000}';
+  CLSID_HandlerPe: TGUID = '{23170F69-40C1-278A-1000-000110DD0000}';
+  CLSID_HandlerElf: TGUID = '{23170F69-40C1-278A-1000-000110DE0000}';
+  CLSID_HandlerMachO: TGUID = '{23170F69-40C1-278A-1000-000110DF0000}';
+  CLSID_HandlerUdf: TGUID = '{23170F69-40C1-278A-1000-000110E00000}';
+  CLSID_HandlerXar: TGUID = '{23170F69-40C1-278A-1000-000110E10000}';
+  CLSID_HandlerMub: TGUID = '{23170F69-40C1-278A-1000-000110E20000}';
+  CLSID_HandlerHfs: TGUID = '{23170F69-40C1-278A-1000-000110E30000}';
+  CLSID_HandlerDmg: TGUID = '{23170F69-40C1-278A-1000-000110E40000}';
+  CLSID_HandlerCompound: TGUID = '{23170F69-40C1-278A-1000-000110E50000}';
+  CLSID_HandlerWim: TGUID = '{23170F69-40C1-278A-1000-000110E60000}';
+  CLSID_HandlerIso: TGUID = '{23170F69-40C1-278A-1000-000110E70000}';
+  CLSID_HandlerChm: TGUID = '{23170F69-40C1-278A-1000-000110E90000}';
+  CLSID_HandlerSplit: TGUID = '{23170F69-40C1-278A-1000-000110EA0000}';
+  CLSID_HandlerRpm: TGUID = '{23170F69-40C1-278A-1000-000110EB0000}';
+  CLSID_HandlerDeb: TGUID = '{23170F69-40C1-278A-1000-000110EC0000}';
+  CLSID_HandlerCpio: TGUID = '{23170F69-40C1-278A-1000-000110ED0000}';
+  CLSID_HandlerTar: TGUID = '{23170F69-40C1-278A-1000-000110EE0000}';
+  CLSID_HandlerGZip: TGUID = '{23170F69-40C1-278A-1000-000110EF0000}';
+
+  { From PropID.h}
+  kpidPath = 3;
+  kpidIsDir = 6;
+  kpidAttrib = 9;
+  kpidCTime = 10;
+  kpidMTime = 12;
+
+  { From IArchive.h}
+  kExtract = 0;
+
+type
+  { From IStream.h }
+  ISequentialInStream = interface(IUnknown)
+  ['{23170F69-40C1-278A-0000-000300010000}'] { From Guid.txt }
+    function Read(data: Pointer; size: UInt32; processedSize: PUInt32): HRESULT; stdcall;
+  end;
+
+  ISequentialOutStream = interface(IUnknown)
+  ['{23170F69-40C1-278A-0000-000300020000}']
+    function Write(data: Pointer; size: UInt32; processedSize: PUint32): HRESULT; stdcall;
+  end;
+
+  IInStream = interface(ISequentialInStream)
+  ['{23170F69-40C1-278A-0000-000300030000}']
+    function Seek(offset: Int64; seekOrigin: UInt32; newPosition: PUInt64): HRESULT; stdcall;
+  end;
+
+  { From IProgress.h }
+  IProgress = interface(IUnknown)
+  ['{23170F69-40C1-278A-0000-000000050000}']
+    function SetTotal(total: UInt64): HRESULT; stdcall;
+    function SetCompleted(completeValue: PUInt64): HRESULT; stdcall;
+  end;
+
+  { From IArchive.h }
+  TNOperationResult = (kOK, kUnsupportedMethod, kDataError, kCRCError,
+    kUnavailable, kUnexpectedEnd, kDataAfterEnd, kIsNotArc, kHeadersError,
+    kWrongPassword);
+
+  IArchiveOpenCallback = interface
+  ['{23170F69-40C1-278A-0000-000600100000}']
+    function SetTotal(files, bytes: PUInt64): HRESULT; stdcall;
+    function SetCompleted(files, bytes: PUInt64): HRESULT; stdcall;
+  end;
+
+  IArchiveExtractCallback = interface(IProgress)
+  ['{23170F69-40C1-278A-0000-000600200000}']
+    function GetStream(index: UInt32; out outStream: ISequentialOutStream;
+      askExtractMode: Int32): HRESULT; stdcall;
+    function PrepareOperation(askExtractMode: Int32): HRESULT; stdcall;
+    function SetOperationResult(opRes: TNOperationResult): HRESULT; stdcall;
+  end;
+
+  IInArchive = interface
+  ['{23170F69-40C1-278A-0000-000600600000}']
+    function Open(stream: IInStream; const maxCheckStartPosition: PInt64;
+      openCallback: IUnknown): HRESULT; stdcall;
+    procedure Dummy1;
+    procedure Dummy2;
+    function GetProperty(index: UInt32; propID: PROPID; out value: OleVariant): HRESULT; stdcall;
+    function Extract(indices: Pointer; numItems: UInt32; testMode: Integer;
+      extractCallback: IArchiveExtractCallback): HRESULT; stdcall;
+  end;
+
+  { From IPassword.h }
+  ICryptoGetTextPassword = interface(IUnknown)
+  ['{23170F69-40C1-278A-0000-000500100000}']
+    function CryptoGetTextPassword(out password: WideString): HRESULT; stdcall;
+  end;
+
+implementation
+
+end.

+ 745 - 0
Projects/Src/Compression.SevenZipDLLDecoder.pas

@@ -0,0 +1,745 @@
+unit Compression.SevenZipDLLDecoder;
+
+{
+  Inno Setup
+  Copyright (C) 1997-2025 Jordan Russell
+  Portions by Martijn Laan
+  For conditions of distribution and use, see LICENSE.TXT.
+
+  Interface to the 7-Zip Decoder DLLs, used by Setup
+
+  Based on the 7-Zip source code and the 7-Zip Delphi API by Henri Gourvest
+  https://github.com/geoffsmith82/d7zip MPL 1.1 licensed
+}
+
+interface
+
+uses
+  Compression.SevenZipDecoder;
+
+function SevenZipDLLInit(const SevenZipLibrary: HMODULE): Boolean;
+
+procedure ExtractArchiveRedir(const DisableFsRedir: Boolean;
+  const ArchiveFilename, DestDir, Password: String; const FullPaths: Boolean;
+  const OnExtractionProgress: TOnExtractionProgress);
+
+implementation
+
+uses
+  Classes, SysUtils, Forms, Variants,
+  Windows, ActiveX, ComObj,
+  Compression.SevenZipDLLDecoder.Interfaces, PathFunc,
+  Shared.FileClass, Shared.Int64Em, Shared.SetupMessageIDs, Shared.CommonFunc,
+  SetupLdrAndSetup.Messages, SetupLdrAndSetup.RedirFunc,
+  Setup.LoggingFunc, Setup.MainFunc, Setup.InstFunc;
+
+type
+  TInStream = class(TInterfacedObject, IInStream)
+  private
+    FFile: TFile;
+  protected
+    function Read(data: Pointer; size: UInt32; processedSize: PUInt32): HRESULT; stdcall;
+    function Seek(offset: Int64; seekOrigin: UInt32; newPosition: PUInt64): HRESULT; stdcall;
+  public
+    constructor Create(AFile: TFile);
+    destructor Destroy; override;
+  end;
+
+  TSequentialOutStream = class(TInterfacedObject, ISequentialOutStream)
+  private
+    FFile: TFile;
+  protected
+    function Write(data: Pointer; size: UInt32; processedSize: PUInt32): HRESULT; stdcall;
+  public
+    constructor Create(AFile: TFile);
+    destructor Destroy; override;
+  end;
+
+  TArchiveOpenCallback = class(TInterfacedObject, IArchiveOpenCallback,
+    ICryptoGetTextPassword)
+  private
+    FPassword: String;
+  protected
+    { IArchiveOpenCallback }
+    function SetTotal(files, bytes: PUInt64): HRESULT; stdcall;
+    function SetCompleted(files, bytes: PUInt64): HRESULT; stdcall;
+    { ICryptoGetTextPassword - queried for on openCallback }
+    function CryptoGetTextPassword(out password: WideString): HRESULT; stdcall;
+  public
+    constructor Create(const Password: String);
+  end;
+
+  TArchiveExtractCallback = class(TInterfacedObject, IArchiveExtractCallback,
+    ICryptoGetTextPassword)
+  private
+    const
+      varFileTime = 64; { Delphi lacks proper VT_FILETIME support }
+    type
+      TCurrent = record
+        Path, ExpandedPath: String;
+        HasAttrib: Boolean;
+        Attrib: DWORD;
+        CTime, MTime: TFileTime;
+        outStream: ISequentialOutStream;
+        procedure SetAttrib(const AAttrib: DWORD);
+      end;
+      TProgress = record
+        Current: TCurrent;
+        Progress, ProgressMax: UInt64;
+        Abort: Boolean;
+      end;
+      TResult = record
+        SavedFatalException: TObject;
+        Res: HRESULT;
+        OpRes: TNOperationResult;
+      end;
+      TVarTypeSet = set of varEmpty..varFileTime; { Incomplete but don't need others }
+    var
+      FInArchive: IInArchive;
+      FDisableFsRedir: Boolean;
+      FExpandedDestDir, FPassword: String;
+      FFullPaths: Boolean;
+      FExtractedArchiveName: String;
+      FOnExtractionProgress: TOnExtractionProgress;
+      FProgressAndLogQueueLock: TObject;
+      FProgress: TProgress;
+      FLogQueue: TStrings;
+      FResult: TResult;
+    function GetProperty(const index: UInt32; const propID: PROPID;
+      const allowedTypes: TVarTypeSet; out value: OleVariant): Boolean; overload;
+    function GetProperty(index: UInt32; propID: PROPID; out value: String): Boolean; overload;
+    function GetProperty(index: UInt32; propID: PROPID; out value: UInt32): Boolean; overload;
+    function GetProperty(index: UInt32; propID: PROPID; out value: Boolean): Boolean; overload;
+    function GetProperty(index: UInt32; propID: PROPID; out value: TFileTime): Boolean; overload;
+  protected
+    { IProgress }
+    function SetTotal(total: UInt64): HRESULT; stdcall;
+    function SetCompleted(completeValue: PUInt64): HRESULT; stdcall;
+    { IArchiveExtractCallback }
+    function GetStream(index: UInt32; out outStream: ISequentialOutStream;
+      askExtractMode: Int32): HRESULT; stdcall;
+    function PrepareOperation(askExtractMode: Int32): HRESULT; stdcall;
+    function SetOperationResult(opRes: TNOperationResult): HRESULT; stdcall;
+    { ICryptoGetTextPassword - queried for on extractCallback }
+    function CryptoGetTextPassword(out password: WideString): HRESULT; stdcall;
+  public
+    constructor Create(const InArchive: IInArchive;
+      const DisableFsRedir: Boolean; const ArchiveFileName, DestDir, Password: String;
+      const FullPaths: Boolean; const OnExtractionProgress: TOnExtractionProgress);
+    destructor Destroy; override;
+  end;
+
+  TFileTimeHelper = record helper for TFileTime
+    procedure Clear;
+    function HasTime: Boolean;
+  end;
+
+function SevenZipSetPassword(const Password: String; out outPassword: WideString): HRESULT;
+begin
+  try
+    if Password = '' then
+      Exit(S_FALSE);
+    outPassword := Password;
+    Result := S_OK;
+  except
+    on E: EAbort do
+      Result := E_ABORT
+    else
+      Result := E_FAIL;
+  end;
+end;
+
+{ TInStream }
+
+constructor TInStream.Create(AFile: TFile);
+begin
+  inherited Create;
+  FFile := AFile;
+end;
+
+destructor TInStream.Destroy;
+begin
+  FFile.Free;
+  inherited;
+end;
+
+function TInStream.Read(data: Pointer; size: UInt32;
+  processedSize: PUInt32): HRESULT;
+begin
+  try
+    var BytesRead := FFile.Read(data^, size);
+    if processedSize <> nil then
+      processedSize^ := BytesRead;
+    Result := S_OK;
+  except
+    on E: EAbort do
+      Result := E_ABORT
+    else
+      Result := E_FAIL;
+  end;
+end;
+
+function TInStream.Seek(offset: Int64; seekOrigin: UInt32;
+  newPosition: PUInt64): HRESULT;
+begin
+  try
+    case seekOrigin of
+      STREAM_SEEK_SET: FFile.Seek64(Integer64(offset));
+      STREAM_SEEK_CUR: FFile.Seek64(Integer64(Int64(FFile.Position) + offset));
+      STREAM_SEEK_END: FFile.Seek64(Integer64(Int64(FFile.Size) + offset));
+    end;
+    if newPosition <> nil then
+      newPosition^ := UInt64(FFile.Position);
+    Result := S_OK;
+  except
+    on E: EAbort do
+      Result := E_ABORT
+    else
+      Result := E_FAIL;
+  end;
+end;
+
+{ TSequentialOutStream }
+
+constructor TSequentialOutStream.Create(AFile: TFile);
+begin
+  inherited Create;
+  FFile := AFile;
+end;
+
+destructor TSequentialOutStream.Destroy;
+begin
+  FFile.Free;
+  inherited;
+end;
+
+function TSequentialOutStream.Write(data: Pointer; size: UInt32;
+  processedSize: PUInt32): HRESULT;
+begin
+  try
+    FFile.WriteBuffer(data^, size);
+    if processedSize <> nil then
+      processedSize^ := size;
+    Result := S_OK;
+  except
+    on E: EAbort do
+      Result := E_ABORT
+    else
+      Result := E_FAIL;
+  end;
+end;
+
+{ TArchiveOpenCallback }
+
+constructor TArchiveOpenCallback.Create(const Password: String);
+begin
+  inherited Create;
+  FPassword := Password;
+end;
+
+function TArchiveOpenCallback.SetCompleted(files,
+  bytes: PUInt64): HRESULT;
+begin
+  Result := S_OK;
+end;
+
+function TArchiveOpenCallback.SetTotal(files,
+  bytes: PUInt64): HRESULT;
+begin
+  Result := S_OK;
+end;
+
+function TArchiveOpenCallback.CryptoGetTextPassword(
+  out password: WideString): HRESULT;
+begin
+  { Note: have not yet seen 7-Zip actually call this, so maybe it's not really needed }
+  Result := SevenZipSetPassword(FPassword, password);
+end;
+
+{ TArchiveExtractCallback }
+
+procedure TArchiveExtractCallback.TCurrent.SetAttrib(const AAttrib: DWORD);
+begin
+  Attrib := AAttrib;
+  HasAttrib := True;
+end;
+
+constructor TArchiveExtractCallback.Create(const InArchive: IInArchive;
+  const DisableFsRedir: Boolean; const ArchiveFileName, DestDir, Password: String;
+  const FullPaths: Boolean; const OnExtractionProgress: TOnExtractionProgress);
+begin
+  inherited Create;
+  FInArchive := InArchive;
+  FDisableFsRedir := DisableFsRedir;
+  FExpandedDestDir := AddBackslash(PathExpand(DestDir));
+  FPassword := Password;
+  FFullPaths := FullPaths;
+  FExtractedArchiveName := PathExtractName(ArchiveFileName);
+  FOnExtractionProgress := OnExtractionProgress;
+  FProgressAndLogQueueLock := TObject.Create;
+  FLogQueue := TStringList.Create;
+  FResult.OpRes := kOK;
+end;
+
+destructor TArchiveExtractCallback.Destroy;
+begin
+  FResult.SavedFatalException.Free;
+  FLogQueue.Free;
+  FProgressAndLogQueueLock.Free;
+end;
+
+function TArchiveExtractCallback.SetTotal(total: UInt64): HRESULT;
+begin
+  { From IArchive.h: 7-Zip can call functions for IProgress or ICompressProgressInfo functions
+    from another threads simultaneously with calls for IArchiveExtractCallback interface }
+  try
+    System.TMonitor.Enter(FProgressAndLogQueueLock);
+    try
+      FProgress.ProgressMax := total;
+    finally
+      System.TMonitor.Exit(FProgressAndLogQueueLock);
+    end;
+    Result := S_OK;
+  except
+    on E: EAbort do
+      Result := E_ABORT
+    else
+      Result := E_FAIL;
+  end;
+end;
+
+function TArchiveExtractCallback.SetCompleted(completeValue: PUInt64): HRESULT;
+begin
+  try
+    System.TMonitor.Enter(FProgressAndLogQueueLock);
+    try
+      if FProgress.Abort then
+        SysUtils.Abort;
+      FProgress.Progress := completeValue^;
+    finally
+      System.TMonitor.Exit(FProgressAndLogQueueLock);
+    end;
+    Result := S_OK;
+  except
+    on E: EAbort do
+      Result := E_ABORT
+    else
+      Result := E_FAIL;
+  end;
+end;
+
+function TArchiveExtractCallback.GetProperty(const index: UInt32;
+  const propID: PROPID; const allowedTypes: TVarTypeSet; out value: OleVariant): Boolean;
+{ Raises an EOleSysError exception on error but otherwise always sets value,
+  returning True if it's not empty }
+begin
+  var Res := FInArchive.GetProperty(index, propID, value);
+  if Res <> S_OK then
+    OleError(Res);
+  Result := not VarIsEmpty(Value);
+  if Result and not (VarType(value) in allowedTypes) then
+    OleError(E_FAIL);
+end;
+
+function TArchiveExtractCallback.GetProperty(index: UInt32; propID: PROPID;
+  out value: String): Boolean;
+begin
+  var varValue: OleVariant;
+  Result := GetProperty(index, propID, [varOleStr], varValue);
+  value := varValue;
+end;
+
+function TArchiveExtractCallback.GetProperty(index: UInt32; propID: PROPID;
+  out value: Cardinal): Boolean;
+begin
+  var varValue: OleVariant;
+  Result := GetProperty(index, propID, [varUInt32], varValue);
+  value := varValue;
+end;
+
+function TArchiveExtractCallback.GetProperty(index: UInt32; propID: PROPID;
+  out value: Boolean): Boolean;
+begin
+  var varValue: OleVariant;
+  Result := GetProperty(index, propID, [varBoolean], varValue);
+  value := varValue;
+end;
+
+function TArchiveExtractCallback.GetProperty(index: UInt32; propID: PROPID;
+  out value: TFileTime): Boolean;
+begin
+  var varValue: OleVariant;
+  Result := GetProperty(index, propID, [varFileTime], varValue);
+  if Result then
+    value := TFileTime(TVarData(varValue).VInt64)
+  else
+    value.Clear;
+end;
+
+function TArchiveExtractCallback.GetStream(index: UInt32;
+  out outStream: ISequentialOutStream; askExtractMode: Int32): HRESULT;
+begin
+  try
+    var NewCurrent := Default(TCurrent);
+    if askExtractMode = kExtract then begin
+      var Path: String;
+      if not GetProperty(index, kpidPath, Path) then
+        Path := PathChangeExt(FExtractedArchiveName, '');
+      var IsDir: Boolean;
+      GetProperty(index, kpidIsDir, IsDir);
+      if IsDir then begin
+        if FFullPaths then begin
+          NewCurrent.Path := Path + '\';
+          if not ValidateAndCombinePath(FExpandedDestDir, Path, NewCurrent.ExpandedPath) then
+            OleError(E_ACCESSDENIED);
+          ForceDirectories(FDisableFsRedir, NewCurrent.ExpandedPath);
+        end;
+        outStream := nil;
+      end else begin
+        var Attrib: DWORD;
+        if GetProperty(index, kpidAttrib, Attrib) then begin
+          if Attrib and $F0000000 <> 0 then
+            Attrib := Attrib and $3FFF; { "PosixHighDetect", just like FileDir.cpp and similar to 7zMain.c }
+          NewCurrent.SetAttrib(Attrib);
+        end;
+        GetProperty(index, kpidCTime, NewCurrent.CTime);
+        GetProperty(index, kpidMTime, NewCurrent.MTime);
+        if not FFullPaths then
+          Path := PathExtractName(Path);
+        NewCurrent.Path := Path;
+        if not ValidateAndCombinePath(FExpandedDestDir, Path, NewCurrent.ExpandedPath) then
+          OleError(E_ACCESSDENIED);
+        ForceDirectories(FDisableFsRedir, PathExtractPath(NewCurrent.ExpandedPath));
+        const ExistingFileAttr = GetFileAttributesRedir(FDisableFsRedir, NewCurrent.ExpandedPath);
+        if (ExistingFileAttr <> INVALID_FILE_ATTRIBUTES) and
+           (ExistingFileAttr and FILE_ATTRIBUTE_READONLY <> 0) then
+          SetFileAttributesRedir(FDisableFsRedir, NewCurrent.ExpandedPath, ExistingFileAttr and not FILE_ATTRIBUTE_READONLY);
+        { From IArchive.h: can also set outstream to nil to tell 7zip to skip the file }
+        outstream := TSequentialOutStream.Create(TFileRedir.Create(FDisableFsRedir, NewCurrent.ExpandedPath, fdCreateAlways, faWrite, fsNone));
+        NewCurrent.outStream := outStream;
+      end;
+    end;
+    System.TMonitor.Enter(FProgressAndLogQueueLock);
+    try
+      if FProgress.Abort then
+        SysUtils.Abort;
+      FProgress.Current := NewCurrent;
+      if NewCurrent.Path <> '' then
+        FLogQueue.Append(NewCurrent.Path)
+    finally
+      System.TMonitor.Exit(FProgressAndLogQueueLock);
+    end;
+    Result := S_OK;
+  except
+    on E: EOleSysError do
+      Result := E.ErrorCode;
+    on E: EAbort do
+      Result := E_ABORT
+    else
+      Result := E_FAIL;
+  end;
+end;
+
+function TArchiveExtractCallback.PrepareOperation(askExtractMode: Int32): HRESULT;
+begin
+  { From Client7z.cpp: PrepareOperation is called *after* GetStream has been called }
+  Result := S_OK;
+end;
+
+function TArchiveExtractCallback.SetOperationResult(opRes: TNOperationResult): HRESULT;
+begin
+  { From IArchive.h: Can now can close the file, set attributes, timestamps and security information }
+  try
+    try
+      if opRes <> kOK then begin
+        FResult.OpRes := opRes;
+        Result := E_FAIL; { Make sure it doesn't continue with the next file }
+      end else begin
+        { GetStream is the only writer to outStream and ExpandedPath and HasAttrib so we don't need a lock because of this note from
+          IArchive.h: 7-Zip doesn't call GetStream/PrepareOperation/SetOperationResult from different threads simultaneously }
+        if (FProgress.Current.outStream <> nil) and (FProgress.Current.CTime.HasTime or FProgress.Current.MTime.HasTime) then
+          SetFileTime((FProgress.Current.outStream as TSequentialOutStream).FFile.Handle,
+            @FProgress.Current.CTime, nil, @FProgress.Current.MTime);
+        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 }
+        if (FProgress.Current.ExpandedPath <> '') and FProgress.Current.HasAttrib then
+          SetFileAttributesRedir(FDisableFsRedir, FProgress.Current.ExpandedPath, FProgress.Current.Attrib);
+        Result := S_OK;
+      end;
+    finally
+      FProgress.Current.outStream := nil;
+    end;
+  except
+    on E: EAbort do
+      Result := E_ABORT
+    else
+      Result := E_FAIL;
+  end;
+end;
+
+function TArchiveExtractCallback.CryptoGetTextPassword(
+  out password: WideString): HRESULT;
+begin
+  Result := SevenZipSetPassword(FPassword, password);
+end;
+
+{---}
+
+var
+  CreateSevenZipObject: function(const clsid, iid: TGUID; var outObject): HRESULT; stdcall;
+
+function SevenZipDLLInit(const SevenZipLibrary: HMODULE): Boolean;
+begin
+  CreateSevenZipObject := GetProcAddress(SevenZipLibrary, 'CreateObject');
+  Result := Assigned(CreateSevenZipObject);
+end;
+
+procedure SevenZipError(const LogMessage, ExceptMessage: String);
+{ Do not call from secondary thread. LogMessage may contain non-localized text
+  ExceptMessage should not. }
+begin
+  LogFmt('ERROR: %s', [LogMessage]); { Just like 7zMain.c }
+  raise Exception.Create(ExceptMessage);
+end;
+
+procedure SevenZipWin32Error(const FunctionName: String; LastError: DWORD = 0); overload;
+begin
+  if LastError = 0 then
+    LastError := GetLastError;
+  const Msg = Format('%s (%u)', [Win32ErrorString(LastError), LastError]);
+  SevenZipError(Format('%s failed: %s', [FunctionName, Msg]), Msg);
+end;
+
+function ExtractThreadFunc(Parameter: Pointer): Integer;
+begin
+  const E = TArchiveExtractCallback(Parameter);
+  try
+    E.FResult.Res := E.FInArchive.Extract(nil, $FFFFFFFF, 0, E);
+  except
+    const Ex = AcquireExceptionObject;
+    MemoryBarrier;
+    E.FResult.SavedFatalException := Ex;
+  end;
+  { Be extra sure FSavedFatalException (and everything else) is made visible
+    prior to thread termination. (Likely redundant, but you never know...) }
+  MemoryBarrier;
+  Result := 0;
+end;
+
+procedure ExtractArchiveRedir(const DisableFsRedir: Boolean;
+  const ArchiveFilename, DestDir, Password: String;
+  const FullPaths: Boolean; const OnExtractionProgress: TOnExtractionProgress);
+
+  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
+      InternalError(NotFoundErrorMsg);
+  end;
+
+  procedure HandleProgress(const E: TArchiveExtractCallback);
+  begin
+    var Progress: TArchiveExtractCallback.TProgress;
+
+    System.TMonitor.Enter(E.FProgressAndLogQueueLock);
+    try
+      Progress := E.FProgress;
+      for var S in E.FLogQueue do
+        LogFmt('- %s', [S]); { Just like 7zMain.c }
+      E.FLogQueue.Clear;
+    finally
+      System.TMonitor.Exit(E.FProgressAndLogQueueLock);
+    end;
+
+    if Progress.Abort then
+      Exit;
+
+    var Abort := False;
+
+    if (Progress.Current.Path <> '') and Assigned(E.FOnExtractionProgress) then begin
+      { Calls to HandleProgress are already throttled so here we don't have to worry
+        about calling the script to often }
+      if not E.FOnExtractionProgress(E.FExtractedArchiveName, Progress.Current.Path, Progress.Progress, Progress.ProgressMax) then
+        Abort := True;
+    end;
+
+    if not Abort and DownloadTemporaryFileOrExtractArchiveProcessMessages then
+      Application.ProcessMessages;
+
+    if Abort then begin
+      System.TMonitor.Enter(E.FProgressAndLogQueueLock);
+      try
+        E.FProgress.Abort := True;
+      finally
+        System.TMonitor.Exit(E.FProgressAndLogQueueLock);
+      end;
+    end;
+  end;
+
+  function OperationResultToString(const opRes: TNOperationResult): String;
+  begin
+    case opRes of
+      kOK: Result := 'OK';
+      kUnsupportedMethod: Result := 'Unsupported method';
+      kDataError: Result := 'Data error';
+      kCRCError: Result := 'CRC error';
+      kUnavailable: Result := 'Unavailable';
+      kUnexpectedEnd: Result := 'Unexpected end';
+      kDataAfterEnd: Result := 'Data after end';
+      kIsNotArc: Result := 'Is not an archive';
+      kHeadersError: Result := 'Headers error';
+      kWrongPassword: Result := 'Wrong password';
+    else
+      Result := Format('Unknown operation result: %d', [Ord(opRes)]);
+    end;
+  end;
+
+  procedure HandleResult([Ref] const Result: TArchiveExtractCallback.TResult);
+  begin
+    if Assigned(Result.SavedFatalException) then begin
+      var Msg: String;
+      if Result.SavedFatalException is Exception then
+        Msg := (Result.SavedFatalException as Exception).Message
+      else
+        Msg := Result.SavedFatalException.ClassName;
+      SevenZipError(Format('Worker thread terminated unexpectedly with exception: %s', [Msg]), Msg);
+    end else if Result.Res = E_ABORT then
+      Abort
+    else begin
+      var OpRes := Result.OpRes;
+      if OpRes <> kOK then
+        SevenZipError(OperationResultToString(Result.OpRes), Ord(OpRes).ToString)
+      else if Result.Res <> S_OK then
+        SevenZipWin32Error('Extract', Result.Res);
+    end;
+  end;
+
+  procedure Extract(const E: TArchiveExtractCallback);
+  begin
+    { We're calling 7-Zip's Extract in a separate thread. This is because packing
+      our example MyProg.exe into a (tiny) .7z and extracting it caused a problem:
+      GetStream and PrepareOperation and SetOperationResult were *all* called by
+      7-Zip from a secondary thread. So we can't block our main thread as well
+      because then we can't communicate progress to it. Having this extra thread
+      has the added bonus of being able to communicate progress more often from
+      SetCompleted. }
+
+    var ThreadID: TThreadID; { Not used but BeginThread requires it }
+    const ThreadHandle = BeginThread(nil, 0, ExtractThreadFunc, E, 0, ThreadID);
+    if ThreadHandle = 0 then
+      SevenZipWin32Error('BeginThread');
+
+    try
+      while True do begin
+        case WaitForSingleObject(ThreadHandle, 50) of
+          WAIT_OBJECT_0: Break;
+          WAIT_TIMEOUT: HandleProgress(E);
+        else
+          SevenZipWin32Error('WaitForSingleObject');
+        end;
+      end;
+    finally
+      CloseHandle(ThreadHandle);
+    end;
+
+    HandleProgress(E);
+    HandleResult(E.FResult);
+  end;
+
+begin
+  LogArchiveExtractionModeOnce;
+
+  if ArchiveFileName = '' then
+    InternalError('ExtractArchive: Invalid ArchiveFileName value');
+  const clsid = GetHandler(PathExtractExt(ArchiveFilename),
+    'ExtractArchive: Unknown ArchiveFileName extension');
+  if DestDir = '' then
+    InternalError('ExtractArchive: Invalid DestDir value');
+
+  LogFmt('Extracting archive %s to %s. Full paths? %s', [ArchiveFileName, DestDir, SYesNo[FullPaths]]);
+
+  LogFmt('%s Decoder : Igor Pavlov', [SetupHeader.SevenZipLibraryName]); { Just like 7zMain.c }
+
+  try
+    { CreateObject }
+    var InArchive: IInArchive;
+    if CreateSevenZipObject(clsid, IInArchive, InArchive) <> S_OK then
+      SevenZipError('Cannot get class object' { Just like Client7z.cpp }, '-1');
+
+    { Open }
+    var F: TFile := nil; { Set to nil to silence compiler }
+    try
+      F := TFileRedir.Create(DisableFsRedir, ArchiveFilename, fdOpenExisting, faRead, fsRead);
+    except
+      SevenZipWin32Error('CreateFile');
+    end;
+    const InStream: IInStream = TInStream.Create(F);
+    var ScanSize: Int64 := 1 shl 23; { From Client7z.cpp }
+    const OpenCallback: IArchiveOpenCallback = TArchiveOpenCallback.Create(Password);
+    if InArchive.Open(InStream, @ScanSize, OpenCallback) <> S_OK then
+      SevenZipError('Cannot open file as archive' { Just like Client7z.cpp }, '-2');
+
+    { Extract }
+    const ExtractCallback: IArchiveExtractCallback =
+      TArchiveExtractCallback.Create(InArchive, DisableFsRedir,
+        ArchiveFilename, DestDir, Password, FullPaths, OnExtractionProgress);
+    Extract(ExtractCallback as TArchiveExtractCallback);
+
+    Log('Everything is Ok'); { Just like 7zMain.c }
+  except
+    on E: EAbort do
+      raise Exception.Create(SetupMessages[msgErrorExtractionAborted])
+    else
+      raise Exception.Create(FmtSetupMessage(msgErrorExtractionFailed, [GetExceptMessage]));
+  end;
+end;
+
+{ TFileTimeHelper }
+
+procedure TFileTimeHelper.Clear;
+begin
+  { SetFileTime regards a pointer to a FILETIME structure with both members
+    set to 0 the same as a NULL pointer and we make use of that. Note that
+    7-Zip may return a value with both members set to 0 as well. }
+  dwLowDateTime := 0;
+  dwHighDateTime := 0;
+end;
+
+function TFileTimeHelper.HasTime: Boolean;
+begin
+  Result := (dwLowDateTime <> 0) or (dwHighDateTime <> 0);
+end;
+
+end.

+ 11 - 4
Projects/Src/Compression.SevenZipDecoder.pas

@@ -16,7 +16,7 @@ type
   TOnExtractionProgress = function(const ArchiveName, FileName: string; const Progress, ProgressMax: Int64): Boolean of object;
 
 procedure Extract7ZipArchiveRedir(const DisableFsRedir: Boolean;
-  const ArchiveFileName, DestDir: String; const FullPaths: Boolean;
+  const ArchiveFileName, DestDir, Password: String; const FullPaths: Boolean;
   const OnExtractionProgress: TOnExtractionProgress);
 
 implementation
@@ -283,7 +283,7 @@ begin
     end;
   end;
 
-  if not Abort and DownloadTemporaryFileOrExtract7ZipArchiveProcessMessages then
+  if not Abort and DownloadTemporaryFileOrExtractArchiveProcessMessages then
     Application.ProcessMessages;
 
   if Abort then
@@ -291,9 +291,11 @@ begin
 end;
 
 procedure Extract7ZipArchiveRedir(const DisableFsRedir: Boolean;
-  const ArchiveFileName, DestDir: String; const FullPaths: Boolean;
+  const ArchiveFileName, DestDir, Password: String; const FullPaths: Boolean;
   const OnExtractionProgress: TOnExtractionProgress);
 begin
+  LogArchiveExtractionModeOnce;
+
   if ArchiveFileName = '' then
     InternalError('Extract7ZipArchive: Invalid ArchiveFileName value');
   if DestDir = '' then
@@ -301,8 +303,13 @@ begin
 
   LogFmt('Extracting 7-Zip archive %s to %s. Full paths? %s', [ArchiveFileName, DestDir, SYesNo[FullPaths]]);
 
-  if not ForceDirectories(DisableFsRedir, DestDir) then
+  if Password <> '' then begin
+    Log('ERROR: Password not supported by basic archive extraction'); { Just like 7zMain.c }
+    raise Exception.Create(FmtSetupMessage(msgErrorExtractionFailed, ['-2']))
+  end else if not ForceDirectories(DisableFsRedir, DestDir) then begin
+    Log('ERROR: Failed to create destination directory'); { Just like 7zMain.c }
     raise Exception.Create(FmtSetupMessage(msgErrorExtractionFailed, ['-1']));
+  end;
 
   State.DisableFsRedir := DisableFsRedir;
   State.ExpandedArchiveFileName := PathExpand(ArchiveFileName);

+ 1 - 1
Projects/Src/Setup.Install.pas

@@ -3492,7 +3492,7 @@ begin
     end;
   end;
 
-  if not Abort and DownloadTemporaryFileOrExtract7ZipArchiveProcessMessages then
+  if not Abort and DownloadTemporaryFileOrExtractArchiveProcessMessages then
     Application.ProcessMessages;
 
   if Abort then

+ 45 - 6
Projects/Src/Setup.MainFunc.pas

@@ -153,7 +153,7 @@ var
   DisableCodeConsts: Integer;
   SetupExitCode: Integer;
   CreatedIcon: Boolean;
-  RestartInitiatedByThisProcess, DownloadTemporaryFileOrExtract7ZipArchiveProcessMessages: Boolean;
+  RestartInitiatedByThisProcess, DownloadTemporaryFileOrExtractArchiveProcessMessages: Boolean;
   InstallModeRootKey: HKEY;
 
   CodeRunner: TScriptRunner;
@@ -185,6 +185,7 @@ function GetPreviousLanguage(const ExpandedAppID: String): Integer;
 procedure InitializeAdminInstallMode(const AAdminInstallMode: Boolean);
 procedure Initialize64BitInstallMode(const A64BitInstallMode: Boolean);
 procedure Log64BitInstallMode;
+procedure LogArchiveExtractionModeOnce;
 procedure InitializeCommonVars;
 procedure InitializeSetup;
 procedure InitializeWizard;
@@ -245,7 +246,7 @@ uses
   Setup.WizardForm, Setup.DebugClient, Shared.VerInfoFunc, Setup.FileExtractor,
   Shared.FileClass, Setup.LoggingFunc,
   SimpleExpression, Setup.Helper, Setup.SpawnClient, Setup.SpawnServer,
-  Setup.DotNetFunc, Shared.TaskDialogFunc, Setup.MainForm;
+  Setup.DotNetFunc, Shared.TaskDialogFunc, Setup.MainForm, Compression.SevenZipDLLDecoder;
 
 var
   ShellFolders: array[Boolean, TShellFolderID] of String;
@@ -256,7 +257,7 @@ var
   SHGetKnownFolderPathFunc: function(const rfid: TGUID; dwFlags: DWORD; hToken: THandle;
     var ppszPath: PWideChar): HRESULT; stdcall;
 
-  DecompressorDLLHandle: HMODULE;
+  DecompressorDLLHandle, SevenZipDLLHandle: HMODULE;
 
 type
   TDummyClass = class
@@ -2514,11 +2515,23 @@ begin
   LogFmt('64-bit install mode: %s', [SYesNo[Is64BitInstallMode]]);
 end;
 
+var
+  LoggedArchiveExtractionMode: Boolean;
+
+procedure LogArchiveExtractionModeOnce;
+begin
+  if not LoggedArchiveExtractionMode then begin
+    LogFmt('Archive extraction mode: %s',
+      [IfThen(SetupHeader.SevenZipLibraryName <> '', Format('Using %s', [SetupHeader.SevenZipLibraryName]), 'Basic')]);
+    LoggedArchiveExtractionMode := True;
+  end;
+end;
+
 procedure InitializeSetup;
 { Initializes various vars used by the setup. This is called in the project
   source. }
 var
-  DecompressorDLL: TMemoryStream;
+  DecompressorDLL, SevenZipDLL: TMemoryStream;
 
   function ExtractLongWord(var S: String): LongWord;
   var
@@ -2610,6 +2623,20 @@ var
     end;
   end;
 
+  procedure LoadSevenZipDLL;
+  var
+    Filename: String;
+  begin
+    Filename := AddBackslash(TempInstallDir) + '_isetup\_is7z.dll';
+    SaveStreamToTempFile(SevenZipDLL, Filename);
+    FreeAndNil(SevenZipDLL);
+    SevenZipDLLHandle := SafeLoadLibrary(Filename, SEM_NOOPENFILEERRORBOX);
+    if SevenZipDLLHandle = 0 then
+      InternalError(Format('Failed to load DLL "%s"', [Filename]))
+    else if not SevenZipDLLInit(SevenZipDLLHandle) then
+      InternalError('SevenZipDLLInit failed');
+  end;
+
 var
   Reader: TCompressedBlockReader;
 
@@ -3128,6 +3155,12 @@ begin
           DecompressorDLL := TMemoryStream.Create;
           ReadFileIntoStream(DecompressorDLL, Reader);
         end;
+        { SevenZip DLL }
+        SevenZipDLL := nil;
+        if SetupHeader.SevenZipLibraryName <> '' then begin
+          SevenZipDLL := TMemoryStream.Create;
+          ReadFileIntoStream(SevenZipDLL, Reader);
+        end;
       finally
         Reader.Free;
       end;
@@ -3211,6 +3244,10 @@ begin
   if SetupHeader.CompressMethod in [cmZip, cmBzip] then
     LoadDecompressorDLL;
 
+  { Extract "_is7z.dll" to TempInstallDir, and load it }
+  if SetupHeader.SevenZipLibraryName <> '' then
+    LoadSevenZipDLL;
+
   { Start RestartManager session }
   if InitCloseApplications or
      ((shCloseApplications in SetupHeader.Options) and not InitNoCloseApplications) then begin
@@ -3492,11 +3529,13 @@ begin
   if RmSessionStarted then
     RmEndSession(RmSessionHandle);
 
-  { Free the _isdecmp.dll handle }
+  { Free the _isdecmp.dll and _is7z.dll handles }
   if DecompressorDLLHandle <> 0 then
     FreeLibrary(DecompressorDLLHandle);
+  if SevenZipDLLHandle <> 0 then
+    FreeLibrary(SevenZipDLLHandle);
 
-  { Free the shfolder.dll handles }
+  { Free the shfolder.dll handle }
   UnloadSHFolderDLL;
 
   { Remove TempInstallDir, stopping the 64-bit helper first if necessary }

+ 1 - 0
Projects/Src/Setup.ScriptClasses.pas

@@ -340,6 +340,7 @@ begin
   with CL.Add(TExtractionWizardPage) do
   begin
     RegisterMethod(@TExtractionWizardPage.Add, 'Add');
+    RegisterMethod(@TExtractionWizardPage.AddEx, 'AddEx');
     RegisterMethod(@TExtractionWizardPage.Clear, 'Clear');
     RegisterMethod(@TExtractionWizardPage.Extract, 'Extract');
     RegisterMethod(@TExtractionWizardPage.Show, 'Show');

+ 17 - 7
Projects/Src/Setup.ScriptDlg.pas

@@ -204,7 +204,7 @@ type
   end;
   
   TArchive = class
-    FileName, DestDir: String;
+    FileName, DestDir, Password: String;
     FullPaths: Boolean;
   end;
   TArchives = TObjectList<TArchive>;
@@ -224,6 +224,7 @@ type
       destructor Destroy; override;
       procedure Initialize; override;
       procedure Add(const ArchiveFileName, DestDir: String; const FullPaths: Boolean);
+      procedure AddEx(const ArchiveFileName, DestDir, Password: String; const FullPaths: Boolean);
       procedure Clear;
       procedure Extract;
       property OnExtractionProgress: TOnExtractionProgress write FOnExtractionProgress;
@@ -240,7 +241,7 @@ uses
   StrUtils,
   Shared.Struct, Setup.MainFunc, Setup.SelectFolderForm, SetupLdrAndSetup.Messages,
   Shared.SetupMessageIDs, PathFunc, Shared.CommonFunc.Vcl, Shared.CommonFunc,
-  BrowseFunc, Setup.LoggingFunc, Setup.InstFunc;
+  BrowseFunc, Setup.LoggingFunc, Setup.InstFunc, Compression.SevenZipDLLDecoder;
 
 const
   DefaultLabelHeight = 14;
@@ -251,7 +252,7 @@ const
 
 procedure SetCtlParent(const AControl, AParent: TWinControl);
 { Like assigning to AControl.Parent, but puts the control at the *bottom* of
-  the z-order instead of the top, for MSAA compatibility. }
+  the z-order instead of the top, for MSAA compatibility }
 var
   OldVisible: Boolean;
 begin
@@ -1071,7 +1072,7 @@ begin
 
   Result := 0;
   for var F in FFiles do begin
-    { Don't need to set DownloadTemporaryFileOrExtract7ZipArchiveProcessMessages before downloading since we already process messages ourselves. }
+    { Don't need to set DownloadTemporaryFileOrExtractArchiveProcessMessages before downloading since we already process messages ourselves }
     SetDownloadCredentials(F.UserName, F.Password);
     Result := Result + DownloadTemporaryFile(F.Url, F.BaseName, F.RequiredSHA256OfFile, InternalOnDownloadProgress);
   end;
@@ -1167,9 +1168,15 @@ end;
 
 procedure TExtractionWizardPage.Add(const ArchiveFileName, DestDir: String; const FullPaths: Boolean);
 begin
-  var A := TArchive.Create;
+  AddEx(ArchiveFileName, DestDir, '', FullPaths);
+end;
+
+procedure TExtractionWizardPage.AddEx(const ArchiveFileName, DestDir, Password: String; const FullPaths: Boolean);
+begin
+  const A = TArchive.Create;
   A.FileName := ArchiveFileName;
   A.DestDir := DestDir;
+  A.Password := Password;
   A.FullPaths := FullPaths;
   FArchives.Add(A);
 end;
@@ -1184,8 +1191,11 @@ begin
   FAbortedByUser := False;
 
   for var A in FArchives do begin
-    { Don't need to set DownloadTemporaryFileOrExtract7ZipArchiveProcessMessages before extraction since we already process messages ourselves. }
-    Extract7ZipArchiveRedir(ScriptFuncDisableFsRedir, A.FileName, A.DestDir, A.FullPaths, InternalOnExtractionProgress);
+    { Don't need to set DownloadTemporaryFileOrExtractArchiveProcessMessages before extraction since we already process messages ourselves }
+    if SetupHeader.SevenZipLibraryName <> '' then
+      ExtractArchiveRedir(ScriptFuncDisableFsRedir, A.FileName, A.DestDir, A.Password, A.FullPaths, InternalOnExtractionProgress)
+    else
+      Extract7ZipArchiveRedir(ScriptFuncDisableFsRedir, A.FileName, A.DestDir, A.Password, A.FullPaths, InternalOnExtractionProgress);
   end;
 end;
 

+ 16 - 3
Projects/Src/Setup.ScriptFunc.pas

@@ -29,7 +29,7 @@ uses
   Setup.WizardForm, Shared.VerInfoFunc, Shared.SetupTypes,
   Shared.Int64Em, Setup.LoggingFunc, Setup.SetupForm, Setup.RegDLL, Setup.Helper,
   Setup.SpawnClient, Setup.DotNetFunc, Setup.MainForm,
-  Shared.DotNetVersion, Setup.MsiFunc, Compression.SevenZipDecoder,
+  Shared.DotNetVersion, Setup.MsiFunc, Compression.SevenZipDecoder, Compression.SevenZipDLLDecoder,
   Setup.DebugClient, Shared.ScriptFunc, Setup.ScriptFunc.HelperFunc;
 
 type
@@ -1784,9 +1784,22 @@ var
       var AscendingTrySizes := Stack.GetIntArray(PStart-4);
       Stack.SetBool(PStart, TBitmapImage(Stack.GetClass(PStart-1)).InitializeFromIcon(0, PChar(Stack.GetString(PStart-2)), Stack.GetInt(PStart-3), AscendingTrySizes));
     end);
-    RegisterScriptFunc('EXTRACT7ZIPARCHIVE', procedure(const Caller: TPSExec; const OrgName: AnsiString; const Stack: TPSStack; const PStart: Cardinal)
+    RegisterScriptFunc(['Extract7ZipArchive', 'ExtractArchive'], procedure(const Caller: TPSExec; const OrgName: AnsiString; const Stack: TPSStack; const PStart: Cardinal)
     begin
-      Extract7ZipArchiveRedir(ScriptFuncDisableFsRedir, Stack.GetString(PStart), Stack.GetString(PStart-1), Stack.GetBool(PStart-2), TOnExtractionProgress(Stack.GetProc(PStart-3, Caller)));
+      var Password: String;
+      var FullDirsItemNo: Longint;
+      if OrgName = 'Extract7ZipArchive' then begin
+        Password := '';
+        FullDirsItemNo := PStart-2;
+      end else begin
+        Password := Stack.GetString(PStart-2);
+        FullDirsItemNo := PStart-3;
+      end;
+
+      if SetupHeader.SevenZipLibraryName <> '' then
+        ExtractArchiveRedir(ScriptFuncDisableFsRedir, Stack.GetString(PStart), Stack.GetString(PStart-1), Password, Stack.GetBool(FullDirsItemNo), TOnExtractionProgress(Stack.GetProc(FullDirsItemNo-1, Caller)))
+      else
+        Extract7ZipArchiveRedir(ScriptFuncDisableFsRedir, Stack.GetString(PStart), Stack.GetString(PStart-1), Password, Stack.GetBool(FullDirsItemNo), TOnExtractionProgress(Stack.GetProc(FullDirsItemNo-1, Caller)));
     end);
     RegisterScriptFunc('DEBUGGING', procedure(const Caller: TPSExec; const OrgName: AnsiString; const Stack: TPSStack; const PStart: Cardinal)
     begin

+ 3 - 3
Projects/Src/Setup.WizardForm.pas

@@ -2,7 +2,7 @@ unit Setup.WizardForm;
 
 {
   Inno Setup
-  Copyright (C) 1997-2024 Jordan Russell
+  Copyright (C) 1997-2025 Jordan Russell
   Portions by Martijn Laan
   For conditions of distribution and use, see LICENSE.TXT.
 
@@ -1844,12 +1844,12 @@ begin
       WizardForm.Visible := True;
     WizardForm.Update;
     try
-      DownloadTemporaryFileOrExtract7ZipArchiveProcessMessages := True;
+      DownloadTemporaryFileOrExtractArchiveProcessMessages := True;
       CodeNeedsRestart := False;
       Result := CodeRunner.RunStringFunctions('PrepareToInstall', [@CodeNeedsRestart], bcNonEmpty, True, '');
       PrepareToInstallNeedsRestart := (Result <> '') and CodeNeedsRestart;
     finally
-      DownloadTemporaryFileOrExtract7ZipArchiveProcessMessages := False;
+      DownloadTemporaryFileOrExtractArchiveProcessMessages := False;
       UpdateCurPageButtonState;
     end;
     if WindowState <> wsMinimized then  { VCL bug workaround }

+ 2 - 1
Projects/Src/Shared.ScriptFunc.pas

@@ -2,7 +2,7 @@ unit Shared.ScriptFunc;
 
 {
   Inno Setup
-  Copyright (C) 1997-2024 Jordan Russell
+  Copyright (C) 1997-2025 Jordan Russell
   Portions by Martijn Laan
   For conditions of distribution and use, see LICENSE.TXT.
 
@@ -540,6 +540,7 @@ initialization
     'function IsMsiProductInstalled(const UpgradeCode: String; const PackedMinVersion: Int64): Boolean;',
     'function InitializeBitmapImageFromIcon(const BitmapImage: TBitmapImage; const IconFilename: String; const BkColor: TColor; const AscendingTrySizes: TArrayOfInteger): Boolean;',
     'procedure Extract7ZipArchive(const ArchiveFileName, DestDir: String; const FullPaths: Boolean; const OnExtractionProgress: TOnExtractionProgress);',
+    'procedure ExtractArchive(const ArchiveFilename, DestDir, Password: String; const FullPaths: Boolean; const OnExtractionProgress: TOnExtractionProgress);',
     'function Debugging: Boolean;',
     'function StringJoin(const Separator: String; const Values: TArrayOfString): String;',
     'function StringSplit(const S: String; const Separators: TArrayOfString; const Typ: TSplitType): TArrayOfString;',

+ 1 - 0
Projects/Src/Shared.SetupSectionDirectives.pas

@@ -44,6 +44,7 @@ type
     ssAppVersion,
     ssArchitecturesAllowed,
     ssArchitecturesInstallIn64BitMode,
+    ssArchiveExtraction,
     ssASLRCompatible,
     ssBackColor,
     ssBackColor2,

+ 3 - 2
Projects/Src/Shared.Struct.pas

@@ -86,7 +86,7 @@ const
     ('Unknown', 'x86', 'x64', 'Arm32', 'Arm64');
 
 const
-  SetupHeaderStrings = 33;
+  SetupHeaderStrings = 34;
   SetupHeaderAnsiStrings = 4;
 type
   TSetupHeader = packed record
@@ -97,7 +97,8 @@ type
       DefaultUserInfoSerial, AppReadmeFile, AppContact, AppComments,
       AppModifyPath, CreateUninstallRegKey, Uninstallable, CloseApplicationsFilter,
       SetupMutex, ChangesEnvironment, ChangesAssociations,
-      ArchitecturesAllowed, ArchitecturesInstallIn64BitMode, CloseApplicationsFilterExcludes: String;
+      ArchitecturesAllowed, ArchitecturesInstallIn64BitMode, CloseApplicationsFilterExcludes,
+      SevenZipLibraryName: String;
     LicenseText, InfoBeforeText, InfoAfterText, CompiledCodeText: AnsiString;
     NumLanguageEntries, NumCustomMessageEntries, NumPermissionEntries,
       NumTypeEntries, NumComponentEntries, NumTaskEntries, NumDirEntries,

+ 3 - 0
README.md

@@ -193,6 +193,9 @@ Precompiled executables and libraries
 
 The source code contains several precompiled and signed executables and libraries:
 
+**Files\is7z.dll**, **Files\is7zxa.dll**, **Files\is7zxr.dll** - Compiled by
+Visual Studio 2022 from 7-Zip source code in the is7z repository.
+
 **Files\isbunzip.dll**, **Files\isbzip.dll** - Compiled by Visual Studio 2005
 from the bzlib directory in the iscompress repository.
 

+ 4 - 0
setup.iss

@@ -139,6 +139,9 @@ Source: "files\WizClassicImage.bmp"; DestDir: "{app}"; Flags: ignoreversion touc
 Source: "files\WizClassicImage-IS.bmp"; DestDir: "{app}"; Flags: ignoreversion touch
 Source: "files\WizClassicSmallImage.bmp"; DestDir: "{app}"; Flags: ignoreversion touch
 Source: "files\WizClassicSmallImage-IS.bmp"; DestDir: "{app}"; Flags: ignoreversion touch
+Source: "files\is7z.dll"; DestDir: "{app}"; Flags: ignoreversion issigverify signcheck touch
+Source: "files\is7zxa.dll"; DestDir: "{app}"; Flags: ignoreversion issigverify signcheck touch
+Source: "files\is7zxr.dll"; DestDir: "{app}"; Flags: ignoreversion issigverify signcheck touch
 Source: "files\iszlib.dll"; DestDir: "{app}"; Flags: ignoreversion issigverify signcheck touch
 Source: "files\iszlib.dll.issig"; DestDir: "{app}"; Flags: ignoreversion touch
 Source: "files\isunzlib.dll"; DestDir: "{app}"; Flags: ignoreversion issigverify signcheck touch
@@ -164,6 +167,7 @@ Source: "Examples\CodeClasses.iss"; DestDir: "{app}\Examples"; Flags: ignorevers
 Source: "Examples\CodeDlg.iss"; DestDir: "{app}\Examples"; Flags: ignoreversion touch
 Source: "Examples\CodeDll.iss"; DestDir: "{app}\Examples"; Flags: ignoreversion touch
 Source: "Examples\CodeDownloadFiles.iss"; DestDir: "{app}\Examples"; Flags: ignoreversion touch
+Source: "Examples\CodeDownloadFiles2.iss"; DestDir: "{app}\Examples"; Flags: ignoreversion touch
 Source: "Examples\CodeExample1.iss"; DestDir: "{app}\Examples"; Flags: ignoreversion touch
 Source: "Examples\CodePrepareToInstall.iss"; DestDir: "{app}\Examples"; Flags: ignoreversion touch
 Source: "Examples\Components.iss"; DestDir: "{app}\Examples"; Flags: ignoreversion touch

+ 23 - 10
whatsnew.htm

@@ -82,7 +82,7 @@ Source: "{src}\Extra\*.chm"; DestDir: "{app}"; Flags: issigverify external; \
     </li>
   </ul>
   </li>
-  <li>New Inno Setup Signature Tool:</li>
+  <li>New Inno Setup Signature Tool:
   <ul>
     <li>Added <tt>ISSigTool.exe</tt>, a new command-line utility designed to sign files using ECDSA P-256 cryptographic signatures.</li>
     <li>Offers commands to sign and verify files, to export public keys and to generate private keys.</li>
@@ -94,7 +94,7 @@ issigtool --key-file="MyKey.ispublickey" verify "MyProg.dll"</pre>
     </li>
   </ul>
   </li>
-  <li>Updated documentation:</li>
+  <li>Updated documentation:
   <ul>
     <li>Added new <a href="https://jrsoftware.org/ishelp/index.php?topic=issigkeyssection">[ISSigKeys] section</a> topic.</li>
     <li>Updated <a href="https://jrsoftware.org/ishelp/index.php?topic=filessection">[Files] section</a> topic.</li>
@@ -102,23 +102,36 @@ issigtool --key-file="MyKey.ispublickey" verify "MyProg.dll"</pre>
     <li>Added new <a href="https://jrsoftware.org/ishelp/index.php?topic=issigtool">Inno Setup Signature Tool</a> topic.</li>
   </ul>
   </li>
-  <li>Updated Pascal Scripting:</li>
+  <li>Updated Pascal Scripting:
   <ul>
     <li>Added new <tt>ISSigLoadTextFromFile</tt> and <tt>ISSigVerify</tt> support functions.</li>
   </ul>
   </li>
 </ul>
+<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.</p>
+<p>All of this is optional and does <i>not</i> increase the size of Setup if not used.</p>
+<ul>
+  <li>Added new <tt>[Setup]</tt> section directive <tt>ArchiveExtraction</tt> to specify the method of archive extraction used by support functions <tt>ExtractArchive</tt> and <tt>CreateExtractionPage</tt>:
+    <ul>
+    <li><tt>basic</tt> (default) is the method introduced by Inno Setup 6.4.0. It only supports .7z archives that are not password-protected.</li>
+    <li><tt>enhanced/nopassword</tt> is a new method and 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.</li>
+    <li><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.</li>
+    <li><tt>full</tt> uses 7z.dll instead of 7zxa.dll, recompiled, code-signed, and renamed to is7z.dll. It supports multiple archive formats (.7z, .zip, .rar, and more), although not as many as the original 7z.dll, to reduce its size.</li>
+    </ul>
+    New documentation <a href="https://jrsoftware.org/ishelp/index.php?topic=setup_archiveextraction">ArchiveExtraction</a> topic has a table summarizing the differences between these methods.
+  </li>
+  <li>Added example script <i>CodeDownloadFiles2.iss</i> to demonstrate how to use the <tt>CreateExtractionPage</tt> support function to verify and extract a downloaded archive.</li>
+  <li>Support function <tt>ExtractArchive</tt> is new and replaces <tt>Extract7ZipArchive</tt>. The old name is still supported, but it is recommended to update your scripts to the new name and the compiler will issue a warning if you don't.</li>
+  <li>Archive extraction now overwrites read-only files which already exist in the destination directory without prompting the user. Previously this would cause an extraction error.</li>
+  <li>Archive extraction now honors the file system redirection state set by 64-bit install mode and support function <tt>EnableFsRedirection</tt>.</li>
+  <li>Added new <tt>AddEx</tt> function to support class <tt>TExtractionWizardPage</tt> to add password-protected archives.</li>
+</ul>
 <span class="head2">Other changes</span>
 <ul>
   <li>Compiler IDE: the <i>Find in Files</i> result list will now update its line numbers when you add or delete lines.</li>
   <li><tt>[Files]</tt> section parameter <tt>Excludes</tt> can now be combined with the <tt>external</tt> flag.</li>
-  <li>Pascal Scripting changes:
-  <ul>
-    <li>Added new <tt>GetSHA256OfStream</tt> support function.</li>
-    <li><tt>Extract7ZipArchive</tt> now honors the file system redirection state set by <tt>EnableFsRedirection</tt> or 64-bit install mode.</li>
-  </ul>
-  </li>
-  <li>Example script <i>CodeDownloadFiles.iss</i> now also demonstrates how to use the <tt>CreateExtractionPage</tt> support function to extract a 7-Zip archive. See the <a href="https://jrsoftware.org/ishelp/index.php?topic=isxfunc_extract7ziparchive">Extract7ZipArchive help topic</a> for information about this function's limitations.</li>
+  <li>Pascal Scripting change: Added new <tt>GetSHA256OfStream</tt> support function.</li>
   <li>Documentation improvements.</li>
 </ul>