unit InnoSetupFile;

interface

uses
  Windows, SysUtils, Classes, StrUtils, DelphiCompat, MyTypes, Struct, StructJoin,
  Msgs, MsgIds, PathFunc, CmnFunc2, SetupEnt, Extract, Extract4000, Extract6400, Main,
  MainInfo, Int64Em, InstFunc, ZLibInno, BZlib, LZMADecomp,
  Compress, Compress67, RebuildScript, FileClass, FileNull, CustomVersions, SetupLdr,
  {$I CompressList.inc}
  ;

type
  TCompresssedBlockReaderClass = class of TAbstractBlockReader;
  TOverwriteAction = (oaOverwrite, oaSkip, oaAsk, oaAbort);

type
  EInnoSetupFileError = class(EInOutError);

  TExtractFlag = (efAssumeYes, efStripPaths);
  TExtractFlags = set of TExtractFlag;
  TLogEvent = procedure(Sender: TObject; const Msg: string) of object;
  TProgressEvent = procedure(Sender: TObject; const ProcessedBytes, TotalBytes: int64) of object;
  TAbortInitCode = (aicNone, aicOK, aicFileIsCorrupt, aicWrongPassword);

  TFileInfoFlag = (fifEncrypted);
  TFileInfoFlags = set of TFileInfoFlag;
  TFileInfo = record
    Valid: boolean;
    FileName: string;
    Size: int64;
    Date: TDateTime;
    Attr: DWORD;
    Flags: TFileInfoFlags;
  end;

  TInnoSetupFile = class
  // Options
  private
    fSetupFileName: string;
    fFileMasks: TStringList;
    fPassword: string;
    fOutDir: string;
    fStripPaths: boolean;
    fBaseDirToStrip: string;
    fAttemptUnpackUnknown: boolean;
    fExtractAllCopies: boolean;
    fAutoYes: boolean;
    fExtractTestOnly: boolean;
    fOnLog: TLogEvent;
    fOnExtractionProgress: TProgressEvent;
    procedure SetSetupFileName(const Value: string);
    procedure SetPassword(const Value: string);
    procedure SetBaseDirToStrip(const Value: string);
    procedure SetOutDir(const Value: string);
    procedure SetStripPaths(const Value: boolean);
    procedure SetAttemptUnpackUnknown(const Value: boolean);
    procedure SetExtractAllCopies(const Value: boolean);
    procedure SetAutoYes(const Value: boolean);
    procedure SetExtractTestOnly(const Value: boolean);
  protected
    property AutoYes: boolean read fAutoYes write SetAutoYes;
    property ExtractTestOnly: boolean read fExtractTestOnly write SetExtractTestOnly;
    property StripPaths: boolean read fStripPaths write SetStripPaths;
    property OutDir: string read fOutDir write SetOutDir;
  // Runtime stuff
  private
    fActive: boolean;
    fInfo: TMainInfo;
    fCompressionMethod: string;
    fTotalsRead: boolean;
    fTotalFileSize: int64;
    fTotalFiles: integer;
    fTotalEncryptedFiles: integer;
    fMaxSlice: integer;
    fWrite: string;
    fFileExtractor: TFileExtractor;
    fProgressCurrent: int64;
    fProgressTotal: int64;
    fInteractiveMode: boolean;
    fQuietExtract: boolean;
    fExtractEmbedded: boolean;
    fAbortInitCode: TAbortInitCode;
    fValidFileIndexes: array of integer;
    procedure SetActive(const Value: boolean);
    function GetCompressionMethod: string;
    function GetMaxSlice: integer;
    function GetTotalEncryptedFiles: integer;
    function GetTotalFiles: integer;
    function GetTotalFileSize: int64;
    function GetFileCount: integer;
    function GetFileInfo(Index: integer): TFileInfo;
    function GetValidFileCount: integer;
    function GetValidFileInfo(Index: integer): TFileInfo;
  protected
    TheBlockReader: TCompresssedBlockReaderClass;
    WarnOnMod: boolean;
    IsUnknownVersion: boolean;
    OverwriteAction: TOverwriteAction;
    SetupSizes: TMySetupInfo;
    OnlyOneFile: integer;
    DestinationFileName: string;
    property ProgressCurrent: int64 read fProgressCurrent;
    property ProgressTotal: int64 read fProgressTotal;
  protected
    function FindVerObject(var IsUnknown: boolean) : TInnoVer;
    function IsCompatibleVersion(const TestID: TSetupID; var VerObject: TInnoVer):boolean;
    function GetBlockReaderClass() : TCompressedBlockReaderClass;
    procedure CreateEntryLists;
    procedure SetupLdr;
    procedure AbortInit(Code: TAbortInitCode; const Msg: TSetupMessageID);
    procedure InitializeSetup;
    function AskFileOverwrite(DestFile: string) : boolean;
    procedure ProcessFileEntry(const FileExtractor: TFileExtractor; const CurFile: PSetupFileEntry);
    function ShouldProcessFileEntry(AFile: PSetupFileEntry):boolean;
    function CreateFileExtractor: TFileExtractor;
    procedure CopyFiles;
    procedure ReadTotals;
    procedure Write(const Msg: string);
    procedure Writeln(const Msg: string);
    procedure ResetExtractionProgress(TotalSize: int64);
    procedure DoExtractionProgress; virtual;
    procedure ExtractorProgressProc(Bytes: int64); virtual;
    procedure ExtractorNotification(Info: TMainInfo; MessageText: String);
    procedure RecalculateValidFiles;
  public
    constructor Create;
    destructor Destroy; override;
    procedure Clear; virtual;
    procedure Open;
    procedure Close;
    procedure Extract(const OutDir: string; Flags: TExtractFlags);
    procedure ExtractFile(const DestFileName: string; Flags: TExtractFlags; FileIndex: integer);
      // Note: Works, but is prohibitively slow due to solid archives
    procedure ExtractValidFile(const DestFileName: string; Flags: TExtractFlags; ValidFileIndex: integer);
      // Note: Works, but is prohibitively slow due to solid archives
    procedure Test;
    property Active: boolean read fActive write SetActive;
    // Input parameters
    property SetupFileName: string read fSetupFileName write SetSetupFileName;
    property Password: string read fPassword write SetPassword;
    property ExtractAllCopies: boolean read fExtractAllCopies write SetExtractAllCopies;
    property AttemptUnpackUnknown: boolean read fAttemptUnpackUnknown write SetAttemptUnpackUnknown;
    property InteractiveMode: boolean read fInteractiveMode write fInteractiveMode;
    property QuietExtract: boolean read fQuietExtract write fQuietExtract;
    property ExtractEmbedded: boolean read fExtractEmbedded write fExtractEmbedded;
    property OnLog: TLogEvent read fOnLog write fOnLog;
    property OnExtractionProgress: TProgressEvent read fOnExtractionProgress write fOnExtractionProgress;
    // Output values
    property Info: TMainInfo read fInfo;
    property CompressionMethod: string read GetCompressionMethod;
    property TotalFileSize: int64 read GetTotalFileSize;
    property TotalFiles: integer read GetTotalFiles;
    property TotalEncryptedFiles: integer read GetTotalEncryptedFiles;
    property MaxSlice: integer read GetMaxSlice;
    property FileCount: integer read GetFileCount;
    property Files[Index: integer]: TFileInfo read GetFileInfo; default;
    property ValidFileCount: integer read GetValidFileCount;
    property ValidFiles[Index: integer]: TFileInfo read GetValidFileInfo;
    // Extra stuff for Extract
    property FileMasks: TStringList read fFileMasks;
    property BaseDirToStrip: string read fBaseDirToStrip write SetBaseDirToStrip;
    property AbortInitCode: TAbortInitCode read fAbortInitCode;
  end;

implementation

const
{$I 'VersionInfo.inc'}

procedure InternalExtractorProgressProc(Bytes: Cardinal; UserData: Pointer);
var
  Inno: TInnoSetupFile;
begin
  if UserData <> nil then begin
    Inno := TInnoSetupFile(UserData);
    Inno.ExtractorProgressProc(Bytes);
  end;
end;

procedure TInnoSetupFile.SetActive(const Value: boolean);
begin
  if Value <> Active then
    if Value then
      Open
    else
      Close;
end;

procedure TInnoSetupFile.SetAttemptUnpackUnknown(const Value: boolean);
begin
  fAttemptUnpackUnknown := Value;
  Close;
end;

procedure TInnoSetupFile.SetAutoYes(const Value: boolean);
begin
  fAutoYes := Value;
  // Does not influence the loaded file -> no need to change Active
end;

procedure TInnoSetupFile.SetBaseDirToStrip(const Value: string);
begin
  fBaseDirToStrip := Value;
  // Does not influence the loaded file -> no need to change Active
end;

procedure TInnoSetupFile.SetExtractAllCopies(const Value: boolean);
begin
  fExtractAllCopies := Value;
  Close;
end;

procedure TInnoSetupFile.SetExtractTestOnly(const Value: boolean);
begin
  fExtractTestOnly := Value;
  // Does not influence the loaded file -> no need to change Active
end;

procedure TInnoSetupFile.SetOutDir(const Value: string);
begin
  fOutDir := Value;
  // Does not influence the loaded file -> no need to change Active
end;

procedure TInnoSetupFile.SetPassword(const Value: string);
begin
  fPassword := Value;
  Close;
end;

procedure TInnoSetupFile.SetSetupFileName(const Value: string);
begin
  fSetupFileName := Value;
  Close;
end;

procedure TInnoSetupFile.SetStripPaths(const Value: boolean);
begin
  fStripPaths := Value;
  // Does not influence the loaded file -> no need to change Active
end;

function TInnoSetupFile.FindVerObject(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 TInnoSetupFile.IsCompatibleVersion(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','7']) 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(IsUnknownVersion);
  Result := VerObject <> nil;
end;

function TInnoSetupFile.GetBlockReaderClass() : 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 TInnoSetupFile.Open;
var
  ReconstructedScript: AnsiString;
begin
  Clear;
  try
    CreateEntryLists;
    SetupLdr;
    InitializeSetup;
    if ExtractEmbedded then AddEmbeddedFiles(Info);
    RenameFiles(Info, ExtractAllCopies); // all the fake files must be added before this
    ReconstructedScript := GetInstallScript(Info);
    AddFakeFile(Info, 'install_script.iss', ReconstructedScript, true);
    fActive := True;
    RecalculateValidFiles;
  except
    on E: EAbort do
      Raise;
    on E: EInnoSetupFileError do
      Raise;
    on E: Exception do begin
      if IsUnknownVersion then
        raise EInnoSetupFileError.Create('Unpacking failed. This version is not supported.')
      else if E.Message=SSetupFileCorrupt then
        raise EInnoSetupFileError.Create('The setup files are corrupted or made by incompatible version. Maybe it''s not an Inno Setup installation at all.')
      else if E.Message='' then //SetupMessages[msgSourceIsCorrupted] then
        raise EInnoSetupFileError.Create('Setup files are corrupted.')
      else if E.Message='1' then
        raise EInnoSetupFileError.Create('Service exception.')
      else
        raise;
    end;
  end;
end;

procedure TInnoSetupFile.Write(const Msg: string);
begin
  fWrite := fWrite + Msg;
end;

procedure TInnoSetupFile.Writeln(const Msg: string);
begin
  if Assigned(OnLog) then
    OnLog(Self, fWrite + Msg);
  fWrite := '';
end;

procedure TInnoSetupFile.Test;
begin
  if not Active then Open;
  Self.OutDir := '';
  Self.AutoYes := True;
  Self.StripPaths := False;
  Self.ExtractTestOnly := True;
  Self.OverwriteAction := oaAsk;
  CopyFiles;
end;

constructor TInnoSetupFile.Create;
begin
  inherited Create;
  fAttemptUnpackUnknown := True;
  fFileMasks := TStringList.Create;
  fInfo := TMainInfo.Create;
  fInfo.StdOutputHandle := INVALID_HANDLE_VALUE;
  CreateEntryLists;
  Clear;
end;

procedure TInnoSetupFile.CreateEntryLists;
begin
  Info.Clear;
end;

procedure TInnoSetupFile.Clear;
begin
  fActive := False;
  fTotalsRead := False;
  TheBlockReader := nil;
  WarnOnMod := False;
  IsUnknownVersion := False;
  OverwriteAction := oaAsk;
  OnlyOneFile := -1;
  FreeAndNil(fFileExtractor);
  fWrite := '';
  Info.Clear;
end;

procedure TInnoSetupFile.Close;
begin
  Clear;
end;

function TInnoSetupFile.GetCompressionMethod: string;
begin
  ReadTotals;
  Result := fCompressionMethod;
end;

function TInnoSetupFile.GetFileCount: integer;
begin
  if not Active then Open;
  Result := Info.Entries[seFile].Count;
end;

function TInnoSetupFile.GetFileInfo(Index: integer): TFileInfo;
var
  entry: PSetupFileEntry;
  loc: PSetupFileLocationEntry;
  systime:TSystemTime;
  TimeStamp:TFileTime;
begin
  if not Active then Open;
  Result.Valid := True;
  Result.Flags := [];
  if (Index < 0) or (Index >= FileCount) then
    raise EListError.Create('List index out of bounds.');
  entry := PSetupFileEntry(Info.Entries[seFile][Index]);
  if ((entry^.FileType in [ftUninstExe, ftRegSvrExe]) or not (entry^.FileType in [ftUserFile, ftFakeFile]))
    and not ExtractEmbedded then Result.Valid := False;
  if (entry^.LocationEntry = -1) then begin
    Result.Valid := False;
    Result.Size := -1;
    Result.Date := 0;
  end
  else begin
    loc := PSetupFileLocationEntry(Info.Entries[seFileLocation][entry^.LocationEntry]);
    if not ExtractAllCopies and (loc^.PrimaryFileEntry <> -1) and (loc^.PrimaryFileEntry <> Index) then Result.Valid := False;
    if Info.TimeStampsInUTC then FileTimeToLocalFileTime(loc^.TimeStamp, TimeStamp)
    else TimeStamp := loc^.TimeStamp;
    FileTimeToSystemTime(TimeStamp, systime);
    Result.Size := Int64(loc^.OriginalSize);
    Result.Date := SystemTimeToDateTime(systime);
    if foChunkEncrypted in loc^.Flags then
      Result.Flags := Result.Flags + [fifEncrypted];
  end;
  Result.FileName := entry^.SourceFilename;
  Result.Attr := entry^.Attribs;
end;

function TInnoSetupFile.GetMaxSlice: integer;
begin
  ReadTotals;
  Result := fMaxSlice;
end;

function TInnoSetupFile.GetTotalEncryptedFiles: integer;
begin
  ReadTotals;
  Result := fTotalEncryptedFiles;
end;

function TInnoSetupFile.GetTotalFiles: integer;
begin
  ReadTotals;
  Result := fTotalFiles;
end;

function TInnoSetupFile.GetTotalFileSize: int64;
begin
  ReadTotals;
  Result := fTotalFileSize;
end;

function TInnoSetupFile.GetValidFileCount: integer;
begin
  if not Active then Open;
  Result := Length(fValidFileIndexes);
end;

function TInnoSetupFile.GetValidFileInfo(Index: integer): TFileInfo;
begin
  if not Active then Open;
  if (Index < 0) or (Index >= ValidFileCount) then
    raise EListError.Create('List index out of bounds.');
  Result := Self.Files[fValidFileIndexes[Index]];
end;

procedure TInnoSetupFile.SetupLdr;
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(TestID, VerObject);
      if AttemptUnpackUnknown and IsUnknownVersion and IsCompatible then begin
        writeln('Signature detected: '+TestID);
        writeln('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('Signature detected: '+TestID+'. This is not a supported version.');
        raise EFatalError.Create('1');
      end else if IsVersionSuspicious(Info) then
        writeln('; Inno Setup Version specified: '+IntToStr(Info.Ver))
      else begin
        write('; Inno Setup Version detected: '+IntToStr(Info.Ver));
        if (Info.VerIsUnicode) then write(' (Unicode)');
        if (Info.VerIsRT) then write(' (Custom)');
        writeln('');
      end;
      if WarnOnMod then writeln('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('Can not open or read the specified file: "'+SetupFileName+'"');
      if (E is EInOutError) then
        writeln(' : '+SysErrorMessage(EInOutError(E).ErrorCode))
      else if (E is EFileError) then
        writeln(' : '+SysErrorMessage(EFileError(E).ErrorCode))
      else begin
        writeln('');
        writeln('Exception class '+E.ClassName+' with message: '+E.Message);
      end;
      raise EFatalError.Create('1');
    end;
  end;
end;

procedure TInnoSetupFile.AbortInit(Code: TAbortInitCode; const Msg: TSetupMessageID);
begin
  fAbortInitCode := Code;
  writeln('Critical error: '+SetupMessages[Msg]);
//  MsgBox(SetupMessages[Msg], '', mbCriticalError, MB_OK);
  Abort;
end;

procedure TInnoSetupFile.InitializeSetup;
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;

  fAbortInitCode := aicNone;
  SetupFile := TFile.Create(SetupFilename, fdOpenExisting, faRead, fsRead);
  try
    SetupFile.Seek(Info.SetupLdrOffset0);
    if SetupFile.Read(TestID, SizeOf(TestID)) <> SizeOf(TestID) then
      AbortInit(aicFileIsCorrupt, 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();

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

      if IsVersionSuspicious(Info) then begin
        HeuristicVersionFinder(Info, Reader);
        write('; Version detected: '+IntToStr(Info.Ver));
        if (Info.VerIsUnicode) then writeln(' (Unicode)') else writeln('');
      end;
      VerObject := FindVerObject(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;

        fAbortInitCode := aicOK;
      finally
        Reader.Free; RealReader.Free;
      end;
    except
      on ECompressDataError do
        AbortInit(aicFileIsCorrupt, msgSetupFileCorrupt);
    end;
  finally
    FreeAndNil(SetupFile);
  end;
end;

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

  writeln('File "' + DestFile + '" already exists. Overwrite?');
  repeat
    write('(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 TInnoSetupFile.ProcessFileEntry(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]);
  if (OnlyOneFile >= 0) and (Self.DestinationFileName <> '') then
    DestFile := DestinationFileName
  else begin
    DestFile:=CurFile^.SourceFileName;
    if length(BaseDirToStrip)>0 then delete(DestFile,1,length(BaseDirToStrip));
    if StripPaths then DestFile:=ExtractFileName(DestFile);
    if OutDir <> '' then DestFile := IncludeTrailingPathDelimiter(OutDir) + DestFile;
  end;

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

  // Ask password if file is encrypted
  if (InteractiveMode) and (foChunkEncrypted in CurFileLocation^.Flags) and (FileExtractor.CryptKey = '') then
    repeat
      writeln('Type in a password (empty string to quit)');
      readln(PasswdStr);
      if (PasswdStr = '') then break;
      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('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 }
        ResetExtractionProgress(Int64(CurFileLocation^.OriginalSize));
        FileExtractor.SeekTo(CurFileLocation^);
        FileExtractor.DecompressFile(CurFileLocation^, DestF, InternalExtractorProgressProc, Self);
      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 TInnoSetupFile.ShouldProcessFileEntry(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 FileMasks.Count>0 then begin
    for i:=0 to FileMasks.Count-1 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 TInnoSetupFile.ExtractorNotification(Info: TMainInfo; MessageText: String);
begin
  if (not QuietExtract) then
    Main.writeln(Info, MessageText);
end;

procedure TInnoSetupFile.ExtractorProgressProc(Bytes: int64);
begin
  Self.fProgressCurrent := Self.fProgressCurrent + Bytes;
  Self.DoExtractionProgress;
end;

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

  fFileExtractor.OnNotification := ExtractorNotification;
  fFileExtractor.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
          (fFileExtractor as TFileExtractor6400).Is64CryptKey := ChaCha20Key;
          PasswordOK := True;
        end
      end;
      if PasswordOK then
        fFileExtractor.CryptKey := Password
      else if (InteractiveMode) then
        writeln('Warning: ' + SetupMessages[msgIncorrectPassword])
      else
        AbortInit(aicWrongPassword, msgIncorrectPassword);
    end;
  end;
  Result := fFileExtractor;
end;

destructor TInnoSetupFile.Destroy;
begin
  Clear;
  FreeAndNil(fFileMasks);
  FreeAndNil(fInfo);
  inherited;
end;

procedure TInnoSetupFile.DoExtractionProgress;
begin
  if Assigned(OnExtractionProgress) then
    OnExtractionProgress(Self, ProgressCurrent, ProgressTotal);
end;

procedure TInnoSetupFile.Extract(const OutDir: string; Flags: TExtractFlags);
begin
  if not Active then Open;
  Self.OutDir := OutDir;
  if OutDir <> '' then MakeDir(OutDir);
  Self.AutoYes := efAssumeYes in Flags;
  Self.StripPaths := efStripPaths in Flags;
  Self.ExtractTestOnly := False;
  Self.OverwriteAction := oaAsk;
  CopyFiles;
end;

procedure TInnoSetupFile.ExtractFile(const DestFileName: string; Flags: TExtractFlags; FileIndex: integer);
begin
  try
    Self.OnlyOneFile := FileIndex;
    Self.DestinationFileName := DestFileName;
    Extract(OutDir, Flags);
  finally
    Self.OnlyOneFile := -1;
    Self.DestinationFileName := '';
  end;
end;

procedure TInnoSetupFile.ExtractValidFile(const DestFileName: string; Flags: TExtractFlags; ValidFileIndex: integer);
begin
  if not Active then Open;
  if (ValidFileIndex < 0) or (ValidFileIndex >= ValidFileCount) then
    raise EListError.Create('List index out of bounds.');
  ExtractFile(DestFileName, Flags, fValidFileIndexes[ValidFileIndex]);
end;

procedure TInnoSetupFile.CopyFiles;
{ Copies all the application's files }
var
  CurDirNumber: Integer;
  CurDir: PSetupDirEntry;
  DestFile: string;
  CurFileNumber: Integer;
  CurFile: PSetupFileEntry;
  loc:PSetupFileLocationEntry;
  FileExtractor: TFileExtractor;
begin
  OverwriteAction := oaAsk;
  if (AutoYes) then OverwriteAction := oaOverwrite;
  FileExtractor := CreateFileExtractor;
  try
    if True
      and (not ExtractTestOnly)
      and (not (True
        and (OnlyOneFile >= 0)
        and (DestinationFileName <> '')
        ))
      and (not StripPaths)
    then
    begin
      for CurDirNumber := 0 to Info.Entries[seDir].Count-1 do
      begin
        CurDir := PSetupDirEntry(Info.Entries[seDir][CurDirNumber]);
        DestFile := ExcludeTrailingPathDelimiter(CurDir.DirName);
        if DestFile = '' then
          Continue;
        if OutDir <> '' then
          DestFile := IncludeTrailingPathDelimiter(OutDir) + DestFile;
        if not DirectoryExists(DestFile) then
          ForceDirectories(DestFile);
      end;
    end;
    for CurFileNumber := 0 to Info.Entries[seFile].Count-1 do begin
      if (OnlyOneFile >= 0) and (OnlyOneFile <> CurFileNumber) then continue;
      CurFile := PSetupFileEntry(Info.Entries[seFile][CurFileNumber]);
      if not ShouldProcessFileEntry(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('#'+IntToStr(CurFileNumber)+' '+SourceFilename);

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

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

// Redistributes file name parameters to how they are in the iss script (DestName => Source,DestDir(,DestName))
// Also handles the duplicate names and/or contents
procedure TInnoSetupFile.ReadTotals;
var
  i: integer;
begin
  if not Active then Open;
  if not fTotalsRead then begin
    fCompressionMethod := GetCompressMethodName(Info, Info.SetupHeader.CompressMethod);
    fTotalFileSize := 0;
    fTotalFiles := 0;
    fTotalEncryptedFiles := 0;
    fMaxSlice := 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;
            fTotalFileSize := fTotalFileSize + OriginalSize;
            if LastSlice > fMaxSlice then fMaxSlice := LastSlice;
            if foChunkEncrypted in Flags then Inc(fTotalEncryptedFiles);
          end;
        Inc(fTotalFiles);
      end;
    fTotalsRead := True;
  end;
end;

procedure TInnoSetupFile.RecalculateValidFiles;
var
  i, n: integer;
begin
  n := 0;
  if Active then
  begin
    SetLength(fValidFileIndexes, Self.FileCount);
    for i := 0 to Pred(Self.FileCount) do
    begin
      if Self.Files[i].Valid then
      begin
        fValidFileIndexes[n] := i;
        Inc(n);
      end;
    end;
  end;
  SetLength(fValidFileIndexes, n);
end;

procedure TInnoSetupFile.ResetExtractionProgress(TotalSize: int64);
begin
  fProgressCurrent := 0;
  fProgressTotal := TotalSize;
  DoExtractionProgress;
end;

end.
