program innounp;
{$APPTYPE CONSOLE}
{$INCLUDE 'InnoUnpMod.inc'}
uses
  Windows, SysUtils, Classes, StrUtils,
  DelphiCompat in 'DelphiCompat.pas',
  Main in 'Main.pas',
  MainInfo in 'MainInfo.pas',
  PathFunc in 'PathFunc.pas',
  {$IFDEF INNOSETUPFILE}
  InnoSetupFile in 'InnoSetupFile.pas',
  {$ENDIF}
  MyTypes in 'MyTypes.pas',
  Struct in 'Struct.pas',
  StructJoin in 'StructJoin.pas',
  Msgs in 'Msgs.pas',
  MsgIds in 'MsgIds.pas',
  CmnFunc2 in 'CmnFunc2.pas',
  SetupEnt in 'SetupEnt.pas',
  Extract in 'Extract.pas',
  Extract4000 in 'Extract4000.pas',
  Extract6400 in 'Extract6400.pas',
  Int64Em in 'Int64Em.pas',
  InstFunc in 'InstFunc.pas',
  ZLibinno in 'zlibinno.pas',
  BZlib in 'bzlib.pas',
  LZMADecomp in 'LZMADecomp.pas',
  Compress in 'Compress.pas',
  Compress67 in 'Compress67.pas',
  RebuildScript in 'RebuildScript.pas',
  FileClass in 'FileClass.pas',
  CallOptimizer in 'CallOptimizer.pas',
  FileNull in 'FileNull.pas',
  SetupLdr in 'SetupLdr.pas',
  CustomVersions in 'CustomVersions.pas',
  {$I CompressList.inc}
  ;

{$R version.res}

const
{$I 'VersionInfo.inc'}

type
  TOverwriteAction = (oaOverwrite, oaSkip, oaAsk, oaAbort);
  TCommandAction=(caInstallInfo, {caListFiles,} caVerboseList, caExtractFiles, caWriteCode);
  {$IFDEF INNOSETUPFILE}
  TInnoSetupFileEx = class(TInnoSetupFile)
  public
    constructor Create;
    procedure Log(Sender: TObject; const Msg: string);
  end;
  {$ELSE}
var
  WarnOnMod:boolean=false;
  IsUnknownVersion:boolean=false;
  OverwriteAction: TOverwriteAction;
  SetupSizes: TMySetupInfo;
  {$ENDIF}

var
// Run parameters
  SetupFileName: string;
  FileMasks: array of string;
  Password: string;
  StripPaths:boolean=false;
  OutDir:string='';
  BaseDirToStrip:string='';
  AttemptUnpackUnknown:boolean=true;
  ExtractAllCopies:boolean=false;
  AutoYes: Boolean = false;
  ExtractTestOnly: Boolean = false;
  QuietExtract: Boolean = false;
  ExtractEmbedded: Boolean = false;
  InteractiveMode: Boolean = true;
  CommandAction: TCommandAction;

{$IFDEF INNOSETUPFILE}
constructor TInnoSetupFileEx.Create;
begin
  inherited Create;
  OnLog := Self.Log;
end;

procedure TInnoSetupFileEx.Log(Sender: TObject; const Msg: string);
begin
  Main.writeln(Info, Msg);
end;

{$ELSE}
function FindVerObject(Info: TMainInfo; var IsUnknown: boolean) : TInnoVer;
begin
  Result := nil;
  IsUnknown := True;

  // ISX versions currently are not supported
  if (Info.VerIsISX) then Exit;

  // First try to find direct match
  Result := TInnoVer.FindVersionExact(Info.Ver, Info.VerIsUnicode, Info.VerIsRT);
  IsUnknown := Result = nil;
  if (Result = nil) and AttemptUnpackUnknown then
    // Try to find closest match (version higher or equal)
    Result := TInnoVer.FindVersionApproximate(Info.Ver, Info.VerIsUnicode, Info.VerIsRT);
end;

function IsCompatibleVersion(Info: TMainInfo; const TestID: TSetupID; var VerObject: TInnoVer):boolean;
const
  SignBegin='Inno Setup Setup Data (';
  SignBeginIsx='My Inno Setup Extensions Setup Data (';
  Digits=['0'..'9'];
var
  s: string;
  i,j:integer;
  Ver1,Ver2,Ver3:integer;
  FileVersion: string;
begin
  s:= string(TestID); Result:=false;
  if pos(SignBegin,s)=1 then i:=length(SignBegin)+1
  else if pos(SignBeginIsx,s)=1 then i:=length(SignBeginIsx)+1
  else exit;
  if not CharInSet(s[i], ['1','2','3','4','5','6']) or (s[i+1]<>'.') then exit;
  j:=i; while (j<=sizeof(TSetupID)) and (s[j]<>')') do inc(j);
  if (j>sizeof(TSetupID)) then exit;
  FileVersion:=copy(s,i,j-i);
  if FileVersion='3.0.6.1' then FileVersion:='3.0.8'; // ugly hack to support ISX 3.0.6.1 at low cost
  if not (CharInSet(FileVersion[1], Digits) and (FileVersion[2]='.')) then exit;
  Val(copy(FileVersion,1,1),Ver1,i); if i<>0 then exit;
  Val(copy(FileVersion,3,1),Ver2,i); if i<>0 then exit;
  if FileVersion[4]<>'.' then exit;
  j:=5; while (j<=length(FileVersion)) and CharInSet(FileVersion[j], Digits) do inc(j);
  Val(copy(FileVersion,5,j-5),Ver3,i); if i<>0 then exit;
  if (j<=length(FileVersion)) then WarnOnMod:=true;
  Info.Ver:=Ver1*1000+Ver2*100+Ver3;

  Info.VerIsUnicode := (Info.Ver >= 6000) or (PosEx('(u)', s, Length(SignBegin)) > 0) or (PosEx('(U)', s, Length(SignBegin)) > 0);
  Info.VerIsRT := CheckResToolsVersion(SetupFileName);

  VerObject := FindVerObject(Info, IsUnknownVersion);
  Result := VerObject <> nil;
end;

function GetBlockReaderClass(Info: TMainInfo) : TCompressedBlockReaderClass;
begin
  Result := nil;

  if (Info.Ver <= 4008) then Result := TZlibBlockReader4008
  else if (Info.Ver >= 4009) and (Info.Ver <= 4105) then Result := TZlibBlockReader4107
  // version mismatch (4105 vs. 4107) is ok
  else if (Info.Ver > 4105) and (Info.Ver < 6700) then Result := Compress.TCompressedBlockReader
  else if (Info.Ver >= 6700) then Result := Compress67.TCompressedBlockReader;
end;

procedure CreateEntryLists(Info: TMainInfo);
begin
  Info.Clear;
end;

procedure SetupLdr(Info: TMainInfo);
var
  SourceF: TFile;
  OffsetTable: TSetupLdrOffsetTable;
  IsCompatible: boolean;
  TestID: TSetupID;
  VerObject: TInnoVer;
begin
  Info.SetupLdrOriginalFilename:=SetupFilename;
  try
    SourceF := TFile.Create(SetupFilename, fdOpenExisting, faRead, fsRead);
    try
      if not (GetSetupLdrOffsetTableFromFile(SourceF, OffsetTable) or
        GetSetupLdrOffsetTableFromResource(SetupFilename, SourceF, OffsetTable)) then
      begin
        if SourceF.CappedSize<sizeof(TestID) then SetupCorruptError;
        OffsetTable.Offset0:=0;
        OffsetTable.Offset1:=0;
        Info.SetupLdrMode:=false;
      end;
      SourceF.Seek(OffsetTable.Offset0);
      SourceF.ReadBuffer(TestID, SizeOf(TestID));

      IsCompatible:=IsCompatibleVersion(Info, TestID, VerObject);
      if AttemptUnpackUnknown and IsUnknownVersion and IsCompatible then begin
        writeln(Info, 'Signature detected: '+TestID);
        writeln(Info, 'This is not directly supported, but i''ll try to unpack it as version '+IntToStr(VerObject.VerSupported));
      end;
      if not IsCompatible then begin
        if not Info.SetupLdrMode then SetupCorruptError;
        writeln(Info, 'Signature detected: '+TestID+'. This is not a supported version.');
        raise EFatalError.Create('1');
      end else if IsVersionSuspicious(Info) then
        writeln(Info, '; Inno Setup Version specified: '+IntToStr(Info.Ver))
      else begin
        write(Info, '; Inno Setup Version detected: '+IntToStr(Info.Ver));
        if (Info.VerIsUnicode) then write(Info, ' (Unicode)');
        if (Info.VerIsRT) then write(Info, ' (Custom)');
        writeln(Info, '');
      end;
      if WarnOnMod then writeln(Info, 'Signature: '+TestID);

// Extract the embedded setup exe      // causes problems. disabled for now.
      {if ExtractEmbedded then begin
        SourceF.Seek(OffsetTable.OffsetEXE);
        P := nil;

        try
          GetMem(P, OffsetTable.UncompressedSizeEXE);
          FillChar(P^, OffsetTable.UncompressedSizeEXE, 0);
          try
            Reader := TCompressedBlockReader.Create(SourceF, TLZMADecompressor);
            try
              Reader.Read(P^, OffsetTable.UncompressedSizeEXE);
            finally
              Reader.Free;
            end;
          except
            on ECompressDataError do
              SetupCorruptError;
          end;
          TransformCallInstructions(P^, OffsetTable.UncompressedSizeEXE, False);
          if GetCRC32(P^, OffsetTable.UncompressedSizeEXE) <> OffsetTable.CRCEXE then
            SetupCorruptError;
        finally
          SetLength(s,OffsetTable.UncompressedSizeEXE);
          Move(p^,s[1],OffsetTable.UncompressedSizeEXE);
          FreeMem(P);
          AddFakeFile('embedded\setup.exe',s);
        end;
      end;}
    finally
      FreeAndNil(SourceF);
    end;
    Info.SetupLdrOffset0 := OffsetTable.Offset0;
    Info.SetupLdrOffset1 := OffsetTable.Offset1;
  except
    on E:Exception do begin
      if E is EMessage then raise;
      if E is EFatalError then raise;

      write(Info, 'Can not open or read the specified file: "'+SetupFileName+'"');
      if (E is EInOutError) then
        writeln(Info, ' : '+SysErrorMessage(EInOutError(E).ErrorCode))
      else if (E is EFileError) then
        writeln(Info, ' : '+SysErrorMessage(EFileError(E).ErrorCode))
      else begin
        writeln(Info, '');
        writeln(Info, 'Exception class '+E.ClassName+' with message: '+E.Message);
      end;
      raise EFatalError.Create('1');
    end;
  end;
end;

procedure AbortInit(Info: TMainInfo; const Msg: TSetupMessageID);
begin
  writeln(Info, 'Critical error: '+SetupMessages[Msg]);
//  MsgBox(SetupMessages[Msg], '', mbCriticalError, MB_OK);
  Abort;
end;

procedure InitializeSetup(Info: TMainInfo);
var
  SetupFile: TFile;
  SetupEncryptionHeaderCRC: Longint;
  pFileEntry: PSetupFileEntry;
  pFileLocationEntry: PSetupFileLocationEntry;
  pRegistryEntry: PSetupRegistryEntry;
  pRunEntry: PSetupRunEntry;
  pIconEntry: PSetupIconEntry;
  pTaskEntry: PSetupTaskEntry;
  pComponentEntry: PSetupComponentEntry;
  pTypeEntry: PSetupTypeEntry;
  pCustomMessageEntry: PSetupCustomMessageEntry;
  pLanguageEntry: PSetupLanguageEntry;
  pDirEntry: PSetupDirEntry;
  pSigKeyEntry: PSetupISSigKeyEntry;
  pIniEntry: PSetupIniEntry;
  pDeleteEntry: PSetupDeleteEntry;
  RealReader: TAbstractBlockReader;
  Reader: TCacheReader;
  p:pointer;
  i:integer;

  procedure ReadString(const R: TAbstractBlockReader; var Str: AnsiString);
  var
    Len: Longint;
  begin
    R.Read(Len, SizeOf(Len));
    SetLength(Str, Len);
    R.Read((@Str[1])^, Len);
  end;

  procedure SkipEntries(const Count: integer; const HeaderInfo: TMySetupHeaderInfo);
  var
    i: integer;
  begin
    for i:=0 to Count-1 do
      SECompressedBlockSkip(Reader, HeaderInfo);
  end;

  procedure ReadImageList(const R: TAbstractBlockReader; DestList: TStrings);
  var
    i, Len: Longint;
    Img: AnsiString;
  begin
    // In version 5.6.0 IS changed single wizard image to multiple images
    // but version signature was not changed (still 5.5.7)
    // Bacause of this (BAD decision) we have to rely on this heuristics to
    // distinguish if with have one image of several ones
    R.Read(Len, SizeOf(Len));
    if (Len < 20) then // this is probably an image counter
      for i := 0 to Len - 1 do
      begin
        ReadString(R, Img);
        DestList.Add( {$IFDEF UNICODE} string {$ENDIF} (Img));
      end
    else begin
      // It looks like we have single image and we've already read its length
      SetLength(Img, Len);
      R.Read((@Img[1])^, Len);
      DestList.Add( {$IFDEF UNICODE} string {$ENDIF} (Img));
    end;
  end;

  procedure ReadWizardAndBzipDll;
  begin
    { Wizard image }
    ReadImageList(Reader, Info.WizardImages);

    if (Info.Ver >= 2001) then
    begin
      ReadImageList(Reader, Info.WizardSmallImages);
    end;

    { Decompressor DLL }
    Info.DecompDll:='';
    if (Info.SetupHeader.CompressMethod = cmBzip) or
       ((Info.SetupHeader.CompressMethod = cmLZMA) and (Info.Ver=4105)) or
       ((Info.SetupHeader.CompressMethod = cmZip) and (Info.Ver>=4206)) then
    begin
      ReadString(Reader, Info.DecompDll);
    end;
  end;
var
  VerObject: TInnoVer;
  TestID: TSetupID;
  TheBlockReader: TCompressedBlockReaderClass;
begin
  { Read SETUP.0, or from EXE }

  // If SetupLdrMode=false, we're running on setup.0 (whatever the actual name is}
  // because the 'real' setup.exe (i.e. not the SetupLdr exe) would not pass the check
  // in SetupLdr proc
  // As a bonus, since 4.1.7 setup.0 must have the same base name as setup.exe
//  SetupFilename := SetupLdrOriginalFilename;

  SetupFile := TFile.Create(SetupFilename, fdOpenExisting, faRead, fsRead);
  try
    SetupFile.Seek(Info.SetupLdrOffset0);
    if SetupFile.Read(TestID, SizeOf(TestID)) <> SizeOf(TestID) then
      AbortInit(Info, msgSetupFileCorruptOrWrongVer);

    { Encryption header in version 6.5 }
    if Info.Ver >= 6500 then
    begin
      if SetupFile.Read(SetupEncryptionHeaderCRC, SizeOf(SetupEncryptionHeaderCRC)) <> SizeOf(SetupEncryptionHeaderCRC) then
        AbortInit(aicFileIsCorrupt, msgSetupFileCorruptOrWrongVer);
      if SetupFile.Read(Info.SetupEncryptionHeader, SizeOf(Info.SetupEncryptionHeader)) <> SizeOf(Info.SetupEncryptionHeader) then
        AbortInit(aicFileIsCorrupt, msgSetupFileCorruptOrWrongVer);
      if SetupEncryptionHeaderCRC <> GetCRC32(Info.SetupEncryptionHeader, SizeOf(Info.SetupEncryptionHeader)) then
        AbortInit(aicFileIsCorrupt, msgSetupFileCorruptOrWrongVer);
      if Info.SetupEncryptionHeader.EncryptionUse = euFull then
        AbortInit(aicFileIsCorrupt, msgSetupFileCorruptOrWrongVer); //'The full encryption mode is not supported!'
    end;
    
    try
      TheBlockReader := GetBlockReaderClass(Info);

      RealReader := TheBlockReader.Create(SetupFile, TLZMA1Decompressor);
      Reader := TCacheReader.CreateCache(RealReader);
      if IsVersionSuspicious(Info) then begin
        HeuristicVersionFinder(Info, Reader);
        write(Info, '; Version detected: '+IntToStr(Info.Ver));
        if (Info.VerIsUnicode) then writeln(Info, ' (Unicode)') else writeln(Info, '');
      end;
      VerObject := FindVerObject(Info, IsUnknownVersion);
//      if VerObject=nil then exit; // that should not happen

      VerObject.SetupSizes(SetupSizes);
      try
        { Header }
        p:=AllocMem(SetupSizes.SetupHeader.Size);
        SECompressedBlockRead(Reader, p^, SetupSizes.SetupHeader);
        VerObject.UnifySetupHeader(p^, Info.SetupHeader);

        if Info.Ver >= 6500 then
        begin
          Info.SetupHeader.EncryptionUsed := Info.SetupEncryptionHeader.EncryptionUse = euFiles;
          Info.SetupHeader.Is64Encryption.PasswordTest := Info.SetupEncryptionHeader.PasswordTest;
          Move(Info.SetupEncryptionHeader.KDFSalt, Info.SetupHeader.Is64Encryption.EncryptionKDFSalt, sizeof(Info.SetupHeader.Is64Encryption.EncryptionKDFSalt));
          Info.SetupHeader.Is64Encryption.EncryptionKDFIterations := Info.SetupEncryptionHeader.KDFIterations;
          Move(Info.SetupEncryptionHeader.BaseNonce, Info.SetupHeader.Is64Encryption.EncryptionBaseNonce, sizeof(Info.SetupHeader.Is64Encryption.EncryptionBaseNonce));
        end;

        Info.SetUpMinVersion:=TSetupVersionData(Info.SetupHeader.MinVersion);
        FreeMem(p);
        if Info.Ver<4000 then begin // language options, wizard images and compressor dll are stored here in 3.x
          SECompressedBlockSkip(Reader, SetupSizes.SetupLanguageEntry);
          ReadWizardAndBzipDll;
        end;

        { Language entries }
        if Info.Ver>=4000 then
        begin
          p:=AllocMem(SetupSizes.SetupLanguageEntry.Size);
          for i:=0 to Info.SetupHeader.NumLanguageEntries-1 do begin
            SECompressedBlockRead(Reader, p^, SetupSizes.SetupLanguageEntry);
            pLanguageEntry:=AllocMem(sizeof(TSetupLanguageEntry));
            VerObject.UnifyLanguageEntry(p^, pLanguageEntry^);
            Info.Entries[seLanguage].Add(pLanguageEntry);
          end;
          FreeMem(p);
        end;

        { CustomMessage entries }
        if Info.Ver>=4201 then
        begin
          p:=AllocMem(SetupSizes.SetupCustomMessageEntry.Size);
          for i:=0 to Info.SetupHeader.NumCustomMessageEntries-1 do begin
            SECompressedBlockRead(Reader, p^, SetupSizes.SetupCustomMessageEntry);
            pCustomMessageEntry:=AllocMem(sizeof(TSetupCustomMessageEntry));
            VerObject.UnifyCustomMessageEntry(p^, pCustomMessageEntry^);
            Info.Entries[seCustomMessage].Add(pCustomMessageEntry);
          end;
          FreeMem(p);
        end;

        { Permission entries }
        if Info.Ver>=4100 then
          SkipEntries(Info.SetupHeader.NumPermissionEntries, SetupSizes.SetupPermissionEntry);

        { Type entries }
        if (SetupSizes.SetupTypeEntry.Size > 0) then
        begin
          p:=AllocMem(SetupSizes.SetupTypeEntry.Size);
          for i:=0 to Info.SetupHeader.NumTypeEntries-1 do begin
            SECompressedBlockRead(Reader, p^, SetupSizes.SetupTypeEntry);
            pTypeEntry:=AllocMem(sizeof(TSetupTypeEntry));
            VerObject.UnifyTypeEntry(p^, pTypeEntry^);
            Info.Entries[seType].Add(pTypeEntry);
          end;
          FreeMem(p);
        end;

        { Component entries }
        if (SetupSizes.SetupComponentEntry.Size > 0) then
        begin
          p:=AllocMem(SetupSizes.SetupComponentEntry.Size);
          for i:=0 to Info.SetupHeader.NumComponentEntries-1 do begin
            SECompressedBlockRead(Reader, p^, SetupSizes.SetupComponentEntry);
            pComponentEntry:=AllocMem(sizeof(TSetupComponentEntry));
            VerObject.UnifyComponentEntry(p^, pComponentEntry^);
            Info.Entries[seComponent].Add(pComponentEntry);
          end;
          FreeMem(p);
        end;

        { Task entries }
        if (SetupSizes.SetupTaskEntry.Size > 0) then
        begin
          p:=AllocMem(SetupSizes.SetupTaskEntry.Size);
          for i:=0 to Info.SetupHeader.NumTaskEntries-1 do begin
            SECompressedBlockRead(Reader, p^, SetupSizes.SetupTaskEntry);
            pTaskEntry:=AllocMem(sizeof(TSetupTaskEntry));
            VerObject.UnifyTaskEntry(p^,pTaskEntry^);
            Info.Entries[seTask].Add(pTaskEntry);
          end;
          FreeMem(p);
        end;

        { Dir entries }
        p:=AllocMem(SetupSizes.SetupDirEntry.Size);
        for i:=0 to Info.SetupHeader.NumDirEntries-1 do begin
          SECompressedBlockRead(Reader, p^, SetupSizes.SetupDirEntry);
          pDirEntry:=AllocMem(sizeof(TSetupDirEntry));
          VerObject.UnifyDirEntry(p^,pDirEntry^);
          Info.Entries[seDir].Add(pDirEntry);
        end;
        FreeMem(p);

        { ISSigKey entries }
        if Info.Ver >= 6500 then begin
          p:=AllocMem(SetupSizes.SetupISSigKeyEntry.Size);
          for i:=0 to Info.SetupHeader.NumISSigKeyEntries-1 do begin
            SECompressedBlockRead(Reader, p^, SetupSizes.SetupISSigKeyEntry);
            pSigKeyEntry:=AllocMem(sizeof(TSetupISSigKeyEntry));
            VerObject.UnifySetupISSigKeyEntry(p^,pSigKeyEntry^);
            Info.Entries[seKeySig].Add(pSigKeyEntry);
          end;
          FreeMem(p);
        end;

        { File entries }
        p:=AllocMem(SetupSizes.SetupFileEntry.Size);
        for i:=0 to Info.SetupHeader.NumFileEntries-1 do begin
          SECompressedBlockRead(Reader, p^, SetupSizes.SetupFileEntry);
          pFileEntry:=AllocMem(sizeof(TSetupFileEntry));
          VerObject.UnifyFileEntry(p^,pFileEntry^);
          Info.Entries[seFile].Add(pFileEntry);
        end;
        FreeMem(p);

        { Icon entries }
        p:=AllocMem(SetupSizes.SetupIconEntry.Size);
        for i:=0 to Info.SetupHeader.NumIconEntries-1 do begin
          SECompressedBlockRead(Reader, p^, SetupSizes.SetupIconEntry);
          pIconEntry:=AllocMem(sizeof(TSetupIconEntry));
          VerObject.UnifyIconEntry(p^,pIconEntry^);
          Info.Entries[seIcon].Add(pIconEntry);
        end;
        FreeMem(p);

        { INI entries }
        p:=AllocMem(SetupSizes.SetupIniEntry.Size);
        for i:=0 to Info.SetupHeader.NumIniEntries-1 do begin
          SECompressedBlockRead(Reader, p^, SetupSizes.SetupIniEntry);
          pIniEntry:=AllocMem(sizeof(TSetupIniEntry));
          VerObject.UnifyIniEntry(p^,pIniEntry^);
          Info.Entries[seIni].Add(pIniEntry);
        end;
        FreeMem(p);

        { Registry entries }
        p:=AllocMem(SetupSizes.SetupRegistryEntry.Size);
        for i:=0 to Info.SetupHeader.NumRegistryEntries-1 do begin
          SECompressedBlockRead(Reader, p^, SetupSizes.SetupRegistryEntry);
          pRegistryEntry:=AllocMem(sizeof(TSetupRegistryEntry));
          VerObject.UnifyRegistryEntry(p^,pRegistryEntry^);
          Info.Entries[seRegistry].Add(pRegistryEntry);
        end;
        FreeMem(p);

        { InstallDelete entries }
        p:=AllocMem(SetupSizes.SetupDeleteEntry.Size);
        for i:=0 to Info.SetupHeader.NumInstallDeleteEntries-1 do begin
          SECompressedBlockRead(Reader, p^, SetupSizes.SetupDeleteEntry);
          pDeleteEntry:=AllocMem(sizeof(TSetupDeleteEntry));
          VerObject.UnifyDeleteEntry(p^, pDeleteEntry^);
          Info.Entries[seInstallDelete].Add(pDeleteEntry);
        end;
        FreeMem(p);

        { UninstallDelete entries }
        p:=AllocMem(SetupSizes.SetupDeleteEntry.Size);
        for i:=0 to Info.SetupHeader.NumUninstallDeleteEntries-1 do begin
          SECompressedBlockRead(Reader, p^, SetupSizes.SetupDeleteEntry);
          pDeleteEntry:=AllocMem(sizeof(TSetupDeleteEntry));
          VerObject.UnifyDeleteEntry(p^, pDeleteEntry^);
          Info.Entries[seUninstallDelete].Add(pDeleteEntry);
        end;
        FreeMem(p);

        { Run entries }
        p:=AllocMem(SetupSizes.SetupRunEntry.Size);
        for i:=0 to Info.SetupHeader.NumRunEntries-1 do begin
          SECompressedBlockRead(Reader, p^, SetupSizes.SetupRunEntry);
          pRunEntry:=AllocMem(sizeof(TSetupRunEntry));
          VerObject.UnifyRunEntry(p^,pRunEntry^);
          Info.Entries[seRun].Add(pRunEntry);
        end;
        FreeMem(p);

        { UninstallRun entries }
        p:=AllocMem(SetupSizes.SetupRunEntry.Size);
        for i:=0 to Info.SetupHeader.NumUninstallRunEntries-1 do begin
          SECompressedBlockRead(Reader, p^, SetupSizes.SetupRunEntry);
          pRunEntry:=AllocMem(sizeof(TSetupRunEntry));
          VerObject.UnifyRunEntry(p^,pRunEntry^);
          Info.Entries[seUninstallRun].Add(pRunEntry);
        end;
        FreeMem(p);

        if Info.Ver>=4000 then ReadWizardAndBzipDll;

        RealReader.Free; Reader.Free;

        RealReader := TheBlockReader.Create(SetupFile, TLZMA1Decompressor);
        Reader:=TCacheReader.CreateCache(RealReader);

        { File location entries }
        p:=AllocMem(SetupSizes.SetupFileLocationEntry.Size);
        for i:=0 to Info.SetupHeader.NumFileLocationEntries-1 do begin
          SECompressedBlockRead(Reader, p^, SetupSizes.SetupFileLocationEntry);
          pFileLocationEntry:=AllocMem(sizeof(TSetupFileLocationEntry));
          VerObject.UnifyFileLocationEntry(p^,pFileLocationEntry^);
          if Info.Ver<4000 then with pFileLocationEntry^ do begin
            Dec(FirstSlice);
            Dec(LastSlice);
          end;
          Info.Entries[seFileLocation].Add(pFileLocationEntry);
        end;
        FreeMem(p);

        for i:=0 to Info.Entries[seFileLocation].Count-1 do
          if foTimeStampInUTC in PSetupFileLocationEntry(Info.Entries[seFileLocation][i])^.Flags then begin
            Info.TimeStampsInUTC:=true;
            break;
          end;

      finally
        Reader.Free; RealReader.Free;
      end;
    except
      on ECompressDataError do
        AbortInit(Info, msgSetupFileCorrupt);
    end;
  finally
    FreeAndNil(SetupFile);
  end;
end;

function AskFileOverwrite(Info: TMainInfo; DestFile: string) : boolean;
var
  Resp: string;
begin
  Result := (OverwriteAction = oaOverwrite);
  if (OverwriteAction <> oaAsk) then Exit;

  Writeln(Info, 'File "' + DestFile + '" already exists. Overwrite?');
  repeat
    Write(Info, '(Y)es / (N)o / (A)lways / (S)kip all / (Q)uit ?');
    Readln(Resp);
    if (Resp = '') then continue;

    case UpCase(Resp[1]) of
      'Y': begin Result := True; break; end;
      'N': break;
      'A': begin Result := True; OverwriteAction := oaOverwrite; break; end;
      'S': begin OverwriteAction := oaSkip; break; end;
      'Q': begin OverwriteAction := oaAbort; break; end;
    end;
  until False;
end;

procedure ExtractorProgressProc(Bytes: Cardinal; UserData: Pointer);
begin
end;

procedure ProcessFileEntry(Info: TMainInfo; const FileExtractor: TFileExtractor;
  const CurFile: PSetupFileEntry);
var
  CurFileLocation: PSetupFileLocationEntry;
  DestFile, TempFile: String;
  DestF: TFile;
  CurFileDate: TFileTime;
  PasswdStr: String; 
  ChaCha20Key: TSetupEncryptionKey;
begin
  if CurFile^.LocationEntry = -1 then Exit;
  CurFileLocation := PSetupFileLocationEntry(Info.Entries[seFileLocation][CurFile^.LocationEntry]);
  DestFile:=CurFile^.SourceFileName;

  if length(BaseDirToStrip)>0 then delete(DestFile,1,length(BaseDirToStrip));

  if StripPaths then DestFile:=ExtractFileName(DestFile);

  // Check if file already exists and ask if necessary
  if (InteractiveMode) and (OverwriteAction = oaAsk) and FileExists(DestFile) then
    if not AskFileOverwrite(Info, DestFile) then Exit;

  // Ask password if file is encrypted
  if (InteractiveMode) and (foChunkEncrypted in CurFileLocation^.Flags) and (FileExtractor.CryptKey = '') then
    repeat
      writeln(Info, 'Type in a password (empty string to quit)');
      readln(PasswdStr);
      if (Info.Ver < 6400) then
      begin
        PasswdStr := string(FixPasswordEncoding(Info, PasswdStr)); // For proper Unicode/Ansi support
        if TestPassword(Info, PasswdStr) then
        begin
          FileExtractor.CryptKey := PasswdStr;
          break;
        end;
      end
      else // new password handling in IS version 6.4
      begin
        GenerateEncryptionKey(PasswdStr, Info.SetupHeader.Is64Encryption.EncryptionKDFSalt, Info.SetupHeader.Is64Encryption.EncryptionKDFIterations, ChaCha20Key);
        if TestPasswordIs64(Info, ChaCha20Key) then
        begin
          TFileExtractor6400(FileExtractor).Is64CryptKey := ChaCha20Key;
          FileExtractor.CryptKey := PasswdStr;
          break;
        end;
      end;
      writeln(Info, 'Wrong password');
    until false;

  if (ExtractTestOnly) then
    DestF := TNullFile.Create()
  else begin
    TempFile := GenerateUniqueName(ExtractFilePath(ExpandFilename(DestFile)), '.tmp');
    MakeDir(ExtractFilePath(TempFile));
    DestF := TFile.Create(TempFile, fdCreateAlways, faWrite, fsNone);
  end;

  try
    try
      if CurFile^.FileType<>ftFakeFile then begin
        { Decompress a file }
        FileExtractor.SeekTo(CurFileLocation^);
        FileExtractor.DecompressFile(CurFileLocation^, DestF, ExtractorProgressProc, nil);
      end else begin
        DestF.WriteBuffer(CurFileLocation^.Contents[1],length(CurFileLocation^.Contents));
      end;

      { Set time/date stamp }
      CurFileDate:=CurFileLocation^.TimeStamp;
      if not Info.TimeStampsInUTC then LocalFileTimeToFileTime(CurFileLocation^.TimeStamp, CurFileDate);
      SetFileTime(DestF.Handle, @CurFileDate, nil, @CurFileDate);
    finally
      FreeAndNil(DestF);
    end;

    if (not ExtractTestOnly) then
    begin
      DeleteFile(DestFile);

      if not MoveFile(PChar(TempFile), PChar(DestFile)) then
        Win32ErrorMsg('MoveFile');
    end;
  finally
    DeleteFile(TempFile);
  end;
end;

function ShouldProcessFileEntry(Info: TMainInfo; AFile: PSetupFileEntry):boolean;
var
  i:integer;
  FilePath,MaskPath,FileName,MaskName:string;
begin
  Result:=false;
  if not ExtractEmbedded and (AFile^.FileType in [ftUninstExe, ftRegSvrExe]) then exit;
  if (AFile^.LocationEntry=-1) then exit;
  if length(BaseDirToStrip)>0 then
    if pos(BaseDirToStrip, AnsiLowercase(AFile^.SourceFileName))<>1 then exit;
  if length(FileMasks)>0 then begin
    for i:=0 to High(FileMasks) do begin
      MaskPath:=AnsiLowercase(PathExtractPath(FileMasks[i]));
      FilePath:=AnsiLowercase(PathExtractPath(AFile^.SourceFileName)); //AFile^.DestDir));
      FileName:=AnsiLowercase(PathExtractName(AFile^.SourceFileName));
//      if AFile^.DestName<>'' then FileName:=AnsiLowercase(PathExtractName(AFile^.DestName));
      MaskName:=AnsiLowercase(PathExtractName(FileMasks[i]));
      if MaskPath ='' then
        Result:=WildcardMatch(PChar(FileName),PChar(AnsiLowercase(FileMasks[i])))
      else if IsWildcard(MaskName) then
        Result := WildcardMatch(PChar(FilePath + FileName), PChar(MaskPath + MaskName))
      else
        Result := WildcardMatch(PChar(FilePath), PChar(MaskPath)) and (FileName = MaskName);
//      else Result:=false;
      if Result then break;
    end;
  end else Result:=true;
end;

procedure ExtractorNotification(Info: TMainInfo; MessageText: String);
begin
  if (not QuietExtract) then
    writeln(Info, MessageText);
end;

function CreateFileExtractor(Info: TMainInfo) : TFileExtractor;
const
  DecompClasses: array[TSetupCompressMethod] of TCustomDecompressorClass =
    (TStoredDecompressor, TZDecompressor, TBZDecompressor, TLZMA1Decompressor, TLZMA2Decompressor);
var
  ChaCha20Key: TSetupEncryptionKey;
  PasswordOK: boolean;
begin
  if (Info.Ver <= 4000) then
    Result := TFileExtractor4000.Create(TZDecompressor, Info)
  else if (Info.Ver >= 6400) then
    Result := TFileExtractor6400.Create(DecompClasses[Info.SetupHeader.CompressMethod], Info)
  else
    Result := TFileExtractor.Create(DecompClasses[Info.SetupHeader.CompressMethod], Info);

  Result.OnNotification := ExtractorNotification;
  Result.Interactive := InteractiveMode;

  if Info.SetupHeader.EncryptionUsed and (Password <> '') then
  begin
    PasswordOK := False;
    if (Info.Ver < 6400) then
    begin
      if TestPassword(Info, Password) then
      begin
        PasswordOK := True;
      end;
    end
    else // new password handling in IS version 6.4
    begin
      GenerateEncryptionKey(Password, Info.SetupHeader.Is64Encryption.EncryptionKDFSalt, Info.SetupHeader.Is64Encryption.EncryptionKDFIterations, ChaCha20Key);
      if TestPasswordIs64(Info, ChaCha20Key) then
      begin
        (Result as TFileExtractor6400).Is64CryptKey := ChaCha20Key;
        PasswordOK := True;
      end
    end;
    if PasswordOK then
      Result.CryptKey := Password
    else if (InteractiveMode) then
      writeln(Info, 'Warning: ' + SetupMessages[msgIncorrectPassword])
    else
      AbortInit(Info, msgIncorrectPassword);
  end;
end;

procedure CopyFiles(Info: TMainInfo);
{ Copies all the application's files }
var
  CurFileNumber: Integer;
  CurFile: PSetupFileEntry;
  loc:PSetupFileLocationEntry;
  FileExtractor: TFileExtractor;
begin
  OverwriteAction := oaAsk;
  if (AutoYes) then OverwriteAction := oaOverwrite;

  FileExtractor := CreateFileExtractor(Info);
  try
    for CurFileNumber := 0 to Info.Entries[seFile].Count-1 do begin
      CurFile := PSetupFileEntry(Info.Entries[seFile][CurFileNumber]);
      if not ShouldProcessFileEntry(Info, CurFile) then continue;
      loc:=PSetupFileLocationEntry(Info.Entries[seFileLocation][CurFile^.LocationEntry]);
      if not ExtractAllCopies and (loc^.PrimaryFileEntry<>-1) and (loc^.PrimaryFileEntry<>CurFileNumber) then continue;
      with CurFile^ do
        if not QuietExtract then
          writeln(Info, '#'+IntToStr(CurFileNumber)+' '+SourceFilename);

        if CurFile^.LocationEntry <> -1 then begin
          ProcessFileEntry(Info, FileExtractor, CurFile);
        end;

      if (OverwriteAction = oaAbort) then break;  
    end;  //for
  finally
    FileExtractor.Free;
  end;
end;
{$ENDIF}

procedure Usage(Info: TMainInfo);
begin
  writeln(Info, Format('innounp, the Inno Setup Unpacker. Version %d.%d.%d', [IUV_MAJOR, IUV_MINOR, IUV_MOD]));
{$IFDEF EUREKALOG}writeln(Info, 'Debug release');{$ENDIF}
  writeln(Info, 'Usage: innounp [command] [options] <setup.exe or setup.0> [@filelist] [filemask ...]');
  writeln(Info, 'Commands:');
  writeln(Info, '  (no)   display general installation info');
  writeln(Info, '  -v     verbosely list the files (with sizes and timestamps)');
  writeln(Info, '  -x     extract the files from the installation (to the current directory, also see -d)');
  writeln(Info, '  -e     extract files without paths');
  writeln(Info, '  -t     test files for integrity');
  writeln(Info, 'Options:');
  writeln(Info, '  -b     batch (non-interactive) mode - will not prompt for password or disk changes');
  writeln(Info, '  -q     do not indicate progress while extracting');
  writeln(Info, '  -m     extract internal embedded files (such as license and uninstall.exe)');
  writeln(Info, '  -pPASS decrypt the installation with a password');
  writeln(Info, '  -dDIR  extract the files into DIR (can be absolute or relative path)');
  writeln(Info, '  -cDIR  specifies that DIR is the current directory in the installation');
  writeln(Info, '  -n     don''t attempt to unpack new versions');
  writeln(Info, '  -fFILE same as -p but reads the password from FILE');
  writeln(Info, '  -a     extract all copies of duplicate files');
  writeln(Info, '  -y     assume Yes on all queries (e.g. overwrite files)');
end;

function ParseCommandLine(Info: TMainInfo):boolean;
var
  InstallNameParsed:boolean;
  i:integer;
  f:TextFile;
  s:string;
  os: AnsiString;
  PasswordFileName:string;
begin
  Result:=false; CommandAction:=caInstallInfo; InstallNameParsed:=false; SetLength(FileMasks,0);
  PasswordFileName:='';
  ExtractTestOnly := false;
  if (ParamCount<1) then exit;
  for i:=1 to ParamCount do
    if (ParamStr(i)[1]='-') and (length(ParamStr(i))>=2) then
      case UpCase(ParamStr(i)[2]) of
        'V': CommandAction:=caVerboseList;
        'X': CommandAction:=caExtractFiles;
        'E': begin CommandAction:=caExtractFiles; StripPaths:=true; end;
        'T': begin CommandAction:=caExtractFiles; ExtractTestOnly:=true; AutoYes:=true; end;
        'B': begin InteractiveMode:=false; AutoYes:=true; end;
        'Q': QuietExtract:=true;
        'M': ExtractEmbedded:=true;
        'P': Password:=copy(ParamStr(i),3,length(ParamStr(i))-2);
        'D': OutDir:=copy(ParamStr(i),3,length(ParamStr(i))-2);
        'C': BaseDirToStrip:=PathLowercase(AddBackslash(copy(ParamStr(i),3,length(ParamStr(i))-2)));
        'N': AttemptUnpackUnknown:=false;
        'F': PasswordFileName:=copy(ParamStr(i),3,length(ParamStr(i))-2);
        'A': ExtractAllCopies:=true;
        'Y': AutoYes:=true;
        else Exit;
      end
    else if not InstallNameParsed then begin SetupFileName:=ExpandFileName(ParamStr(i)); InstallNameParsed:=true; end
    else if (ParamStr(i)[1]='@') and (length(ParamStr(i))>=2) then begin
      AssignFile(f,copy(ParamStr(i),2,length(ParamStr(i))-1));
      try
        Reset(f);
        while not eof(f) do begin
          readln(f,os); s:=OemToStr(os);
          if s<>'' then begin SetLength(FileMasks,length(FileMasks)+1); FileMasks[High(FileMasks)]:=s; end;
        end;
      except
        on E: Exception do raise EFatalError.Create('2');
      end;
      CloseFile(f);
    end else begin SetLength(FileMasks,length(FileMasks)+1); FileMasks[High(FileMasks)]:=ParamStr(i); end;
  Result:=true;
  if PasswordFileName<>'' then begin
    try
      Password:=string(FileContents(PasswordFileName));
    except
      writeln(Info, 'Failed to read "'+PasswordFileName+'". No password will be used');
      Password:='';
    end;
  end;
  for i:=0 to High(FileMasks) do
    if (FileMasks[i,1]='"') and (FileMasks[i,length(FileMasks[i])]='"') then
      FileMasks[i]:=copy(FileMasks[i],2,length(FileMasks[i])-2);
end;

var
  Info: TMainInfo;
  i:integer;
  s:string;
  {$IFDEF INNOSETUPFILE}
  ISF: TInnoSetupFile;
  FI: TFileInfo;
  Flags: TExtractFlags;
  T: TDateTime;
  {$ELSE}
  systime:TSystemTime;
  TimeStamp:TFileTime;
  loc:PSetupFileLocationEntry;
  TotalFileSize: Int64;
  TotalFiles, TotalEncryptedFiles, MaxSlice:integer;
  ReconstructedScript: AnsiString;
  {$ENDIF}
begin
  {$IFDEF INNOSETUPFILE}
  ISF := TInnoSetupFileEx.Create;
  Info := ISF.Info;
  {$ELSE}
  Info := TMainInfo.Create;
  {$ENDIF}
  try
  Info.StdOutputHandle:=GetStdHandle(STD_OUTPUT_HANDLE);
  {$IFNDEF INNOSETUPFILE}
  CreateEntryLists(Info);
  {$ENDIF}
  if not ParseCommandLine(Info) then begin Usage(Info); halt(1) end;
  try
    {$IFDEF INNOSETUPFILE}
    ISF.SetupFileName := SetupFileName;
    ISF.Password := Password;
    ISF.ExtractAllCopies := ExtractAllCopies;
    ISF.AttemptUnpackUnknown := AttemptUnpackUnknown;
    ISF.ExtractEmbedded := ExtractEmbedded;
    ISF.Open;
    {$ELSE}
    if OutDir<>'' then begin MakeDir(OutDir); SetCurrentDir(OutDir) end;
    SetupLdr(Info);
    InitializeSetup(Info);
    if ExtractEmbedded then AddEmbeddedFiles(Info);

    if CommandAction<>caInstallInfo then begin // save some time
      RenameFiles(Info, ExtractAllCopies); // all the fake files must be added before this

      ReconstructedScript := GetInstallScript(Info);
      AddFakeFile(Info, 'install_script.iss', ReconstructedScript, true);
    end;
    {$ENDIF}

    case CommandAction of
    caInstallInfo:
      begin
        writeln(Info, 'Compression used: '+ {$IFDEF INNOSETUPFILE} ISF.CompressionMethod {$ELSE} GetCompressMethodName(Info, Info.SetupHeader.CompressMethod) {$ENDIF} );
        {$IFNDEF INNOSETUPFILE}
        TotalFileSize:=0; TotalFiles:=0; TotalEncryptedFiles:=0; MaxSlice:=0;
        for i:=0 to Info.Entries[seFile].Count-1 do
          with PSetupFileEntry(Info.Entries[seFile][i])^ do begin
            if LocationEntry<>-1 then
            with PSetupFileLocationEntry(Info.Entries[seFileLocation][LocationEntry])^ do begin
              if not ExtractAllCopies and (PrimaryFileEntry<>-1) and (PrimaryFileEntry<>i) then continue;
              TotalFileSize := TotalFileSize + OriginalSize;
              if LastSlice>MaxSlice then MaxSlice:=LastSlice;
              if foChunkEncrypted in Flags then Inc(TotalEncryptedFiles);
            end;
            Inc(TotalFiles);
          end;
        {$ENDIF}
        write(Info, 'Files: '+IntToStr({$IFDEF INNOSETUPFILE} ISF. {$ENDIF} TotalFiles)+' ; Bytes: '+IntToStr({$IFDEF INNOSETUPFILE} ISF. {$ENDIF} TotalFileSize));
        if {$IFDEF INNOSETUPFILE} ISF. {$ENDIF} MaxSlice>0 then writeln(Info, ' ; Disk slices: '+IntToStr({$IFDEF INNOSETUPFILE} ISF. {$ENDIF} MaxSlice+1))
        else writeln(Info, '');
        if {$IFDEF INNOSETUPFILE} ISF. {$ENDIF} TotalEncryptedFiles>0 then writeln(Info, IntToStr({$IFDEF INNOSETUPFILE} ISF. {$ENDIF} TotalEncryptedFiles)+' file(s) are encrypted');
        if length(Info.SetupHeader.CompiledCodeText)>0 then
          writeln(Info, 'Compiled Pascal script: '+IntToStr(length(Info.SetupHeader.CompiledCodeText))+' byte(s)');
      end;
    caVerboseList:
      begin
        writeln(Info, 'Size        Time              Filename');
        writeln(Info, '--------------------------------------');
        {$IFDEF INNOSETUPFILE}
        for i := 0 to ISF.FileCount-1 do begin
          FI := ISF[i];
          if FI.Valid then begin
            s := Format('%10s', [IntToStr(FI.Size)]);
            writeln(Info, s+'  '+FormatDateTime('yyyy.mm.dd hh:mm', FI.Date)+
              '  '+FI.FileName);
          end;
        end;
        {$ELSE}
        for i:=0 to Info.Entries[seFile].Count-1 do
          with PSetupFileEntry(Info.Entries[seFile][i])^ do begin
//            if not (FileType in [ftUserFile,ftFakeFile]) and not ExtractEmbedded then continue;
            if ((FileType in [ftUninstExe, ftRegSvrExe]) or not (FileType in [ftUserFile, ftFakeFile]))
              and not ExtractEmbedded then continue;
            if (LocationEntry=-1) then continue;
            loc:=PSetupFileLocationEntry(Info.Entries[seFileLocation][LocationEntry]);
            if not ExtractAllCopies and (loc^.PrimaryFileEntry<>-1) and (loc^.PrimaryFileEntry<>i) then continue;
            if Info.TimeStampsInUTC then FileTimeToLocalFileTime(loc^.TimeStamp, TimeStamp)
            else TimeStamp:=loc^.TimeStamp;
            FileTimeToSystemTime(TimeStamp, systime);
            s := Format('%10s', [IntToStr(loc^.OriginalSize)]);
            writeln(Info, s+'  '+FormatDateTime('yyyy.mm.dd hh:nn', SystemTimeToDateTime(systime))+
              '  '+SourceFileName);
          end;
        {$ENDIF}
        writeln(Info, '--------------------------------------');
      end;
    caExtractFiles:
      {$IFDEF INNOSETUPFILE}
      begin
        Flags := [];
        if AutoYes then Flags := Flags + [efAssumeYes];
        if StripPaths then Flags := Flags + [efStripPaths];
        ISF.FileMasks.Clear;
        for i := Low(FileMasks) to High(FileMasks) do
          ISF.FileMasks.Add(FileMasks[i]);
        ISF.BaseDirToStrip := BaseDirToStrip;
        T := Now;
        if ExtractTestOnly then
          ISF.Test
        else
          ISF.Extract(OutDir, Flags);
        T := Now - T;
        writeln(Info, 'Elapsed time: ' + FormatDateTime('hh:nn:ss.zzz', T));
      end;
      {$ELSE}
      CopyFiles(Info);
      {$ENDIF}
    end;
  except
    on E: EAbort do
      halt(1); //This is a silent exception so just exit with error code
    on E: Exception do
      {$IFDEF INNOSETUPFILE}
      if False then begin
      {$ELSE}
      if IsUnknownVersion then begin
        writeln(Info, 'Unpacking failed. This version is not supported.'); halt(1);
      end else if E.Message=SSetupFileCorrupt then begin
        writeln(Info, 'The setup files are corrupted or made by incompatible version. Maybe it''s not an Inno Setup installation at all.');
        writeln(Info, '('+inttohex(NativeUInt(ExceptAddr),8)+')');
{$IFDEF EUREKALOG}raise;{$ELSE}        halt(1);{$ENDIF}
      end else if E.Message='' then begin //SetupMessages[msgSourceIsCorrupted] then
        writeln(Info, 'Setup files are corrupted.');
        writeln(Info, '('+inttohex(NativeUInt(ExceptAddr),8)+')');
{$IFDEF EUREKALOG}raise;{$ELSE}        halt(1);{$ENDIF}
      {$ENDIF}
      end else begin
        if (E.Message <> '1') then // Hide service exceptions
          writeln(Info, 'Error ('+E.ClassName+') "'+E.Message+'" at address '+inttohex(NativeUInt(ExceptAddr),8));
{$IFDEF EUREKALOG}raise;{$ELSE}        halt(1);{$ENDIF}
      end;
  end;
  finally
    {$IFDEF INNOSETUPFILE}
    FreeAndNil(ISF);
    {$ELSE}
    FreeAndNil(Info);
    {$ENDIF}
    {$WARN SYMBOL_PLATFORM OFF}
    if DebugHook <> 0 then begin
    {$WARN SYMBOL_PLATFORM ON}
      System.Write('Press ENTER to quit...');
      Readln;
    end;
  end;
end.
