unit RebuildScript;

interface

uses
  MyTypes, MainInfo;

function GetInstallScript(Info: TMainInfo):AnsiString;
function GetLanguageFile(Info: TMainInfo; i:integer): AnsiString;
function GetCompressMethodName(Info: TMainInfo; Method: TSetupCompressMethod):string;
procedure AddEmbeddedFiles(Info: TMainInfo);

implementation

uses Windows, SysUtils, Classes, Main, PathFunc, MD5, SHA1, Struct;

type
  TScriptBuilder = class
  private
    Info: TMainInfo;
    Res: TBigString;
    CurSlice: Integer;

    procedure Print(const s: TTextData);
    procedure PrintLn(const s: TTextData);

    procedure StrParam(DisplayName, Value:TTextData; UseQuotes: boolean = true);
    procedure IntParam(DisplayName:TTextData; Value:Int64);
    procedure StrConst(DisplayName, Value:TTextData; ShowAlways:boolean=false);
    procedure IntConst(DisplayName:TTextData; Value:Int64);
    procedure NewLine();
    procedure PrintComment(Text: String);
    procedure PrintSectionHeader(SectionName: String; PrependEmptyLine: boolean = true);
    procedure PrintFlagsParam(FlagsStr: String);
    procedure PrintVersions(MinVersion, OnlyBelowVersion: TMySetupVersionData);

    procedure PrintSetupHeader(const sh: TSetupHeader);
    procedure PrintRegistryEntry(const re: TSetupRegistryEntry);
    procedure PrintRunEntry(const re:TSetupRunEntry);
    procedure PrintTaskEntry(const te:TSetupTaskEntry);
    procedure PrintComponentEntry(const ce:TSetupComponentEntry);
    procedure PrintTypeEntry(const te:TSetupTypeEntry);
    procedure PrintFileEntry(const fe:TSetupFileEntry);
    procedure PrintDeleteEntry(const de: TSetupDeleteEntry);
    procedure PrintCustomMessageEntry(const ce:TSetupCustomMessageEntry);
    procedure PrintIconEntry(const ie:TSetupIconEntry);
    procedure PrintDirEntry(const de: TSetupDirEntry);
    procedure PrintIniEntry(const ie: TSetupIniEntry);
    procedure PrintLanguageEntry(const le:TSetupLanguageEntry);

    procedure PrintLangOptions(const le:TSetupLanguageEntry);
  public
    function RebuildScript() : AnsiString;
    function GetLanguageFile(i: Integer) : AnsiString;
  end;

function VerToStr(Ver: Cardinal; ServicePack: Word): String;
var
  Digits: Integer;
begin
  with TSetupVersionDataVersion(Ver) do begin
    Digits := 2;
    if Minor mod 10 = 0 then begin
      Dec (Digits);
      Minor := Minor div 10;
    end;
    FmtStr (Result, '%d.%.*d', [Major, Digits, Minor]);
    if Build <> 0 then
      Result := Result + Format('.%d', [Build]);
    if ServicePack <> 0 then begin
      Result := Result + ' Service Pack ' + IntToStr(Hi(ServicePack));
      if Lo(ServicePack) <> 0 then
        Result := Result + Format('.%d', [Lo(ServicePack)]);
    end;
  end;
end;

function RegRootToStr(const re:TSetupRegistryEntry):string;
const
  HKEY_A = 1;  // equals HKLM in administrative install mode, HKCU otherwise
begin
  with re do begin
    if rootkey=HKEY_CLASSES_ROOT then Result:='HKCR'
    else if rootkey=HKEY_CURRENT_USER then Result:='HKCU'
    else if rootkey=HKEY_LOCAL_MACHINE then Result:='HKLM'
    else if rootkey=HKEY_USERS then Result:='HKU'
    else if rootkey=HKEY_CURRENT_CONFIG then Result:='HKCC'
    else if rootkey=HKEY_A then Result:='HKA'
    else Result:='';
    if Result<>'' then
      if ro32bit in Options then Result:=Result+'32'
      else if ro64bit in Options then Result:=Result+'64';
  end;
end;

function DoubleQuotes(s:TTextData):TTextData;
var i:integer;
begin
  for i:=length(s) downto 1 do
    if s[i]='"' then Insert('"',s,i);
  Result:=s;
end;

function Hash2Str(Hash: TSetupHash) : string;
begin
  case Hash.HashType of
    htMD5:  Result := MD5DigestToString(Hash.MD5);
    htSHA1: Result := SHA1DigestToString(Hash.SHA1);
  else
    Result := '<Unknown>';  
  end;
end;

function SaltToStr(Salt: array of byte) : string;
var
  i: integer;
begin
  Result := '';
  for i := Low(Salt) to High(Salt) do
  begin
    Result := Result + IntToHex(Salt[i], 2);
  end;
  Result := LowerCase(Result);
end;

function ProcArcsToStr(Arcs: TMySetupProcessorArchitectures) : String;
  function ArchStr(a: TSetupProcessorArchitecture):string;
  begin
    case a of
      paX86         : Result:='x86';
      paAMD64       : Result:='x64';
      paIA64        : Result:='ia64';
    else Result:='';
    end;
  end;
var
  pa: TSetupProcessorArchitecture;
  t: String;
begin
  Result := '';
  for pa:=Low(pa) to High(pa) do
    if pa in Arcs then begin
      t := ArchStr(pa);
      if t<>'' then Result := Result + t + ' ';
    end;
  Result := Trim(Result);
end;

function GetCompressMethodName(Info: TMainInfo; Method: TSetupCompressMethod):string;
begin
  case Method of
    cmStored: Result:='none';
    cmZip: Result:='zip';
    cmBzip: Result:='bzip2';
    cmLZMA: Result:='lzma';
    cmLZMA2: Result:='lzma2';
  end;
end;

function GetInnoVersionStr(Info: TMainInfo) : string;
var
  Ver1, Ver2, Ver3: Integer;
begin
  Ver1 := Info.Ver div 1000;
  Ver2 := (Info.Ver div 100) - (Ver1 * 10);
  Ver3 := Info.Ver mod 100; 

  Result := Format('%d.%d.%d', [Ver1, Ver2, Ver3]);
  if (Info.VerIsUnicode) then
    Result := Result + ' (Unicode)';
end;

function GetBaseFileName(Info: TMainInfo) : string;
var
  name: string;
begin
  name := Info.SetupHeader.BaseFilename;
  if (name = '') then
  begin
    name := ExtractFileName(Info.SetupLdrOriginalFilename);
	// Remove .exe extenstion if have one
	if (Pos('.exe', name) = Length(name) - 3) then
	  name := Copy(name, 1, Length(name) - 4);
  end;
  Result := name;
end;

function MaybeToRtf(const Name: string; const Contents: TBinaryData):string;
begin
  if copy(Contents, 1, 5)='{\rtf' then
    Result:=ChangeFileExt(Name, '.rtf')
  else
    Result:=Name;
end;

procedure AddFakeRtfOrTxtFile(Info: TMainInfo; const Name: string; const Contents: TBinaryData);
begin
  AddFakeFile(Info, MaybeToRtf(Name, Contents), Contents);
end;

type
  TImageFormat = (imfBMP, imfPNG);
const
  ImageFormatExtensions: array[TImageFormat] of string = (
    '.bmp', //imfBMP
    '.png' //imfPNG
  );

function GetImageFormat(const Contents: string): TImageFormat;
const
  PngHeader = #137#80#78#71#13#10#26#10;
begin
  Result := imfBMP;
  if Copy(Contents, 1, Length(PngHeader)) = PngHeader then
    Result := imfPNG;
end;

function GetWizardImageFileName(Index: integer; IsSmallImage: Boolean; ImageFormat: TImageFormat) : String;
const
  BaseFileName: array[boolean] of string = (
    'WizardImage',
    'WizardSmallImage'
    );
begin
  Result := Format('embedded\%s%u%s', [BaseFileName[IsSmallImage], Index, ImageFormatExtensions[ImageFormat]])
end;

procedure AddEmbeddedFiles(Info: TMainInfo);
var i:integer;
begin
  with Info.SetupHeader do begin      // mirror these filenames in procedures above
    AddFakeRtfOrTxtFile(Info, 'embedded\License.txt', LicenseText);
    AddFakeRtfOrTxtFile(Info, 'embedded\InfoBefore.txt', InfoBeforeText);
    AddFakeRtfOrTxtFile(Info, 'embedded\InfoAfter.txt', InfoAfterText);
    AddFakeFile(Info, 'embedded\CompiledCode.bin', CompiledCodeText);
  end;
  for i:= 0 to Info.WizardImages.Count - 1 do
    AddFakeFile(Info, GetWizardImageFileName(i, false, GetImageFormat(Info.WizardImages[i])), {$IFDEF UNICODE} AnsiString {$ENDIF} (Info.WizardImages[i]));
  for i:= 0 to Info.WizardSmallImages.Count - 1 do
    AddFakeFile(Info, GetWizardImageFileName(i, true, GetImageFormat(Info.WizardSmallImages[i])), {$IFDEF UNICODE} AnsiString {$ENDIF} (Info.WizardSmallImages[i]));
  AddFakeFile(Info, 'embedded\decompressor.dll', Info.DecompDll);

  for i:=0 to Info.SetupHeader.NumLanguageEntries-1 do
    with PSetupLanguageEntry(Info.Entries[SeLanguage][i])^ do begin
      AddFakeFile(Info, 'embedded\'+Name+'.isl', GetLanguageFile(Info, i));
      if LicenseText<>'' then AddFakeRtfOrTxtFile(Info, 'embedded\'+Name+'License.txt', LicenseText);
      if InfoBeforeText<>'' then AddFakeRtfOrTxtFile(Info, 'embedded\'+Name+'InfoBefore.txt', InfoBeforeText);
      if InfoAfterText<>'' then AddFakeRtfOrTxtFile(Info, 'embedded\'+Name+'InfoAfter.txt', InfoAfterText);
    end;
end;

function GetInstallScript(Info: TMainInfo): AnsiString;
var
  Builder: TScriptBuilder;
begin
  Builder := TScriptBuilder.Create;
  try
    Builder.Info := Info;
    Result := Builder.RebuildScript();
  finally
    Builder.Free;
  end;    
end;

function GetLanguageFile(Info: TMainInfo; i: Integer): AnsiString;
var
  Builder: TScriptBuilder;
begin
  Builder := TScriptBuilder.Create;
  try
    Builder.Info := Info;
    Result := Builder.GetLanguageFile(i);
  finally
    Builder.Free;
  end;
end;

{ TScriptBuilder }

procedure TScriptBuilder.Print(const s: TTextData);
begin
  Res.AppendString(s);
end;

procedure TScriptBuilder.PrintLn(const s: TTextData);
begin
  Res.AppendString(s+#13#10);
end;

procedure TScriptBuilder.StrParam(DisplayName, Value:TTextData; UseQuotes: boolean = true);
begin
  if Value<>'' then
  begin
    if UseQuotes then
      Print(DisplayName + ': "' + DoubleQuotes(Value) + '"; ')
    else
      Print(DisplayName + ': ' + Value + '; ');
  end;
end;

procedure TScriptBuilder.IntParam(DisplayName:TTextData; Value:Int64);
begin
  if Value <> 0 then StrParam(DisplayName, IntToStr(Value), True);
end;

procedure TScriptBuilder.StrConst(DisplayName, Value:TTextData; ShowAlways:boolean=false);
begin
  if ShowAlways or (Value<>'') then PrintLn(DisplayName+'='+Value);
end;

procedure TScriptBuilder.IntConst(DisplayName:TTextData; Value:Int64);
begin
  StrConst(DisplayName, IntToStr(Value));
end;

procedure TScriptBuilder.NewLine();
begin
  PrintLn('');
end;

procedure TScriptBuilder.PrintComment(Text: String);
begin
  PrintLn('; ' + Text);
end;

procedure TScriptBuilder.PrintSectionHeader(SectionName: String; PrependEmptyLine: boolean = true);
begin
  if (PrependEmptyLine) then NewLine;
  PrintLn('[' + SectionName + ']');
end;

procedure TScriptBuilder.PrintFlagsParam(FlagsStr: String);
begin
  if (FlagsStr <> '') then
    Print('Flags: ' + FlagsStr);
  NewLine;
end;

procedure TScriptBuilder.PrintVersions(MinVersion, OnlyBelowVersion: TMySetupVersionData);
begin
  with MinVersion do
    if (WinVersion>$04000000) or (NTVersion>$04000000) then
      StrParam('MinVersion', VerToStr(WinVersion,0) + ',' + VerToStr(NTVersion,NTServicePack), False);
  with OnlyBelowVersion do
    if (WinVersion<>0) or (NTVersion<>0) then
      StrParam('OnlyBelowVersion', VerToStr(WinVersion,0) + ',' + VerToStr(NTVersion,NTServicePack), False);
end;

function TScriptBuilder.RebuildScript: AnsiString;
var
  i:integer;
begin
  Res.Init;

  PrintSetupHeader(Info.SetupHeader);

  CurSlice:=0;
  if Info.Entries[seFile].Count>0 then begin
    PrintSectionHeader('Files');
    for i:=0 to Info.Entries[seFile].Count-1 do
      PrintFileEntry(PSetupFileEntry(Info.Entries[seFile][i])^);
  end;
  if Info.Entries[seDir].Count>0 then begin
    PrintSectionHeader('Dirs');
    for i:=0 to Info.Entries[seDir].Count-1 do
      PrintDirEntry(PSetupDirEntry(Info.Entries[seDir][i])^);
  end;
  if Info.Entries[seRegistry].Count>0 then begin
    PrintSectionHeader('Registry');
    for i:=0 to Info.Entries[seRegistry].Count-1 do
      PrintRegistryEntry(PSetupRegistryEntry(Info.Entries[seRegistry][i])^);
  end;
  if Info.Entries[seIni].Count>0 then begin
    PrintSectionHeader('INI');
    for i:=0 to Info.Entries[seIni].Count-1 do
      PrintIniEntry(PSetupIniEntry(Info.Entries[seIni][i])^);
  end;
  if Info.Entries[seRun].Count>0 then begin
    PrintSectionHeader('Run');
    for i:=0 to Info.Entries[seRun].Count-1 do
      PrintRunEntry(PSetupRunEntry(Info.Entries[seRun][i])^);
  end;
  if Info.Entries[seUninstallRun].Count>0 then begin
    PrintSectionHeader('UninstallRun');
    for i:=0 to Info.Entries[seUninstallRun].Count-1 do
      PrintRunEntry(PSetupRunEntry(Info.Entries[seUninstallRun][i])^);
  end;
  if Info.Entries[seIcon].Count>0 then begin
    PrintSectionHeader('Icons');
    for i:=0 to Info.Entries[seIcon].Count-1 do
      PrintIconEntry(PSetupIconEntry(Info.Entries[seIcon][i])^);
  end;
  if Info.Entries[seTask].Count>0 then begin
    PrintSectionHeader('Tasks');
    for i:=0 to Info.Entries[seTask].Count-1 do
      PrintTaskEntry(PSetupTaskEntry(Info.Entries[seTask][i])^);
  end;
  if Info.Entries[seComponent].Count>0 then begin
    PrintSectionHeader('Components');
    for i:=0 to Info.Entries[seComponent].Count-1 do
      PrintComponentEntry(PSetupComponentEntry(Info.Entries[seComponent][i])^);
  end;
  if Info.Entries[seInstallDelete].Count>0 then begin
    PrintSectionHeader('InstallDelete');
    for i:=0 to Info.Entries[seInstallDelete].Count-1 do
      PrintDeleteEntry(PSetupDeleteEntry(Info.Entries[seInstallDelete][i])^);
  end;
  if Info.Entries[seUninstallDelete].Count>0 then begin
    PrintSectionHeader('UninstallDelete');
    for i:=0 to Info.Entries[seUninstallDelete].Count-1 do
      PrintDeleteEntry(PSetupDeleteEntry(Info.Entries[seUninstallDelete][i])^);
  end;
  if Info.Entries[seType].Count>0 then begin
    PrintSectionHeader('Types');
    for i:=0 to Info.Entries[seType].Count-1 do
      PrintTypeEntry(PSetupTypeEntry(Info.Entries[seType][i])^);
  end;
  if Info.Entries[seCustomMessage].Count>0 then begin
    PrintSectionHeader('CustomMessages');
    for i:=0 to Info.Entries[seCustomMessage].Count-1 do
      PrintCustomMessageEntry(PSetupCustomMessageEntry(Info.Entries[seCustomMessage][i])^);
  end;
  if (Info.Entries[seLanguage].Count>0) then begin
    PrintSectionHeader('Languages');
    PrintComment('These files are stubs');
    PrintComment('To achieve better results after recompilation, use the real language files');
    for i:=0 to Info.Entries[seLanguage].Count-1 do
      PrintLanguageEntry(PSetupLanguageEntry(Info.Entries[seLanguage][i])^);
  end;
  Result := Res.CopyAsUTF8String(True);
  Res.Destroy;
end;

procedure TScriptBuilder.PrintSetupHeader(const sh: TSetupHeader);
  function Priv2Str(Priv: TMySetupPrivileges) : string;
  begin
    case Priv of
      prNone        : Result:='none';
      prPowerUser   : Result:='poweruser';
      prLowest      : Result:='lowest';
    else Result:='';
    end;
  end;
  function DisPage2Str(a: TMySetupDisablePage):string;
  begin
    case a of
      dpAuto   : Result:='auto';
      dpNo     : Result:='no';
      dpYes    : Result:='yes';
    else Result:='';
    end;
  end;
  function LangDlg2Str (a : TMySetupShowLanguageDialog) : string;
  begin
    case a of
      slYes    : Result:='yes';
      slNo     : Result:='no';
      slAuto   : Result:='auto';
      else Result:='';
    end;
  end;
  procedure AppendToStr(var Dest: string; const Value, Separator: string);
  begin
    if Dest = '' then
      Dest := Value
    else
      Dest := Dest + Separator + Value;
  end;
  function GetImageFileList(ImgList: TStrings; IsSmalImage: boolean) : String;
  var
    i: Integer;
    NameList: TStrings;
  begin
    if (ImgList.Count = 0) then Exit;
    NameList := TStringList.Create;
    NameList.Delimiter := ',';
    for i := 0 to ImgList.Count - 1 do
      NameList.Add(GetWizardImageFileName(i, IsSmalImage, GetImageFormat(ImgList[i])));
    Result := NameList.DelimitedText;  
    NameList.Free;
  end;

var
  s: string;
begin
  StrConst(';InnoSetupVersion', GetInnoVersionStr(Info));

  PrintSectionHeader('Setup');
  StrConst('AppName', GetCustomMessage(Info, sh.AppName), True);
  StrConst('AppVerName', GetCustomMessage(Info, sh.AppVerName));
  StrConst('AppId', sh.AppId, True);
  StrConst('AppVersion', sh.AppVersion);
  StrConst('AppPublisher', sh.AppPublisher);
  StrConst('AppPublisherURL', sh.AppPublisherURL);
  StrConst('AppSupportPhone', sh.AppSupportPhone);
  StrConst('AppCopyright', sh.AppCopyright);
  StrConst('AppSupportURL', sh.AppSupportURL);
  StrConst('AppUpdatesURL', sh.AppUpdatesURL);
  StrConst('AppMutex', sh.AppMutex);
  StrConst('AppComments', sh.AppComments);
  StrConst('AppModifyPath', sh.AppModifyPath);
  if (shCreateAppDir in sh.Options) then
    StrConst('DefaultDirName', sh.DefaultDirName)
  else
    StrConst('CreateAppDir', 'no');
  if (sh.DefaultGroupName <> '(Default)') then
    StrConst('DefaultGroupName', sh.DefaultGroupName);
  StrConst('OutputBaseFilename',  GetBaseFilename(Info));
  StrConst('UninstallDisplayIcon', sh.UninstallDisplayIcon);
  StrConst('UninstallDisplayName', sh.UninstallDisplayName);
  if (sh.UninstallFilesDir <> '{app}') then
    StrConst('UninstallFilesDir', sh.UninstallFilesDir);
  StrConst('Compression', GetCompressMethodName(Info, sh.CompressMethod));
  if sh.EncryptionUsed then
    StrConst('; Encryption', 'yes');
  if (Info.Ver > 4202) and (sh.EncryptionUsed or (shPassword in sh.Options)) then
  begin
    if (Info.Ver < 6400) then
    begin
      StrConst('; PasswordHash', Hash2Str(sh.PasswordHash));
      StrConst('; PasswordSalt', SaltToStr(sh.PasswordSalt));
    end
    else
    begin
      StrConst('; PasswordTest', IntToStr(sh.Is64Encryption.PasswordTest));
      StrConst('; EncryptionKDFSalt', SaltToStr(sh.Is64Encryption.EncryptionKDFSalt));
      StrConst('; EncryptionKDFIterations', IntToStr(sh.Is64Encryption.EncryptionKDFIterations));
      //StrConst('; ', EncryptionBaseNonce: TSetupEncryptionNonce;
    end;
  end;  
  StrConst('ArchitecturesAllowed', sh.ArchitecturesAllowed);
  StrConst('ArchitecturesInstallIn64BitMode', sh.ArchitecturesInstallIn64BitMode);
  if (Info.Ver<5310) and not(shUninstallable in sh.Options) then
    StrConst('Uninstallable', 'no')
  else if not SameText(sh.Uninstallable, 'yes') then
    StrConst('Uninstallable', sh.Uninstallable);
  if (sh.PrivilegesRequired <> prAdmin) then
    StrConst('PrivilegesRequired', Priv2Str(sh.PrivilegesRequired));
  if (Info.Ver>=6000) then begin
    s:='';
    if proDialog in sh.PrivilegesRequiredOverridesAllowed then AppendToStr(s, 'dialog', ', ');
    if proCommandLine in sh.PrivilegesRequiredOverridesAllowed then AppendToStr(s, 'commandline', ', ');
    StrConst('PrivilegesRequiredOverridesAllowed', s);
    if not (shUsePreviousPrivileges in sh.Options) then StrConst('UsePreviousPrivileges', 'no');
  end;
  if (sh.ExtraDiskSpaceRequired > 0) then
    IntConst('ExtraDiskSpaceRequired', sh.ExtraDiskSpaceRequired);
  if (sh.DisableDirPage <> dpNo) then
    StrConst('DisableDirPage', DisPage2Str(sh.DisableDirPage));
  if (sh.DisableProgramGroupPage <> dpNo) then
    StrConst('DisableProgramGroupPage', DisPage2Str(sh.DisableProgramGroupPage));
  StrConst('ChangesAssociations', sh.ChangesAssociations);
  if (Info.Ver>=4000) then
    StrConst('ShowLanguageDialog', LangDlg2Str(sh.ShowLanguageDialog));
  if (shAllowNoIcons in sh.Options) then
    StrConst('AllowNoIcons', 'yes');
  // mirror these filenames in AddEmbeddedFiles()
  with sh do begin
    if LicenseText<>'' then StrConst('LicenseFile', MaybeToRtf('embedded\License.txt', LicenseText));
    if InfoBeforeText<>'' then StrConst('InfoBeforeFile', MaybeToRtf('embedded\InfoBefore.txt', InfoBeforeText));
    if InfoAfterText<>'' then StrConst('InfoAfterFile', MaybeToRtf('embedded\InfoAfter.txt', InfoAfterText));
  end;
  if (Info.Ver >= 6000) then begin
    if (Info.Ver >= 6600) then begin // support of WizardStyle
      if (shWizardModern in sh.Options) then
        s := 'modern'
      else
        s := 'classic';
      case sh.WizardDarkStyle of
        wdsDark:
          s := s + ' dark';
        wdsDynamic:
          s := s + ' dynamic';
        else
          s := s + ' light';
      end;
    end
    else if sh.WizardStyle = wsModern then
      s := 'modern'
    else
      s := 'classic';
    StrConst('WizardStyle', s);
  end;
  StrConst('WizardImageFile', GetImageFileList(Info.WizardImages, false));
  StrConst('WizardSmallImageFile', GetImageFileList(Info.WizardSmallImages, true));
  if Info.TimeStampsInUTC then StrConst(';TimeStampsInUTC', 'yes');
end;

procedure TScriptBuilder.PrintRegistryEntry(const re: TSetupRegistryEntry);
  function OptStr(Opt: TSetupRegistryOption):string;
  begin
    case Opt of
      roCreateValueIfDoesntExist         : Result:='createvalueifdoesntexist';
      roUninsDeleteValue                 : Result:='uninsdeletevalue';
      roUninsClearValue                  : Result:='uninsclearvalue';
      roUninsDeleteEntireKey             : Result:='uninsdeletekey';
      roUninsDeleteEntireKeyIfEmpty      : Result:='uninsdeletekeyifempty';
      roPreserveStringType               : Result:='preservestringtype';
      roDeleteKey                        : Result:='deletekey';
      roDeleteValue                      : Result:='deletevalue';
      roNoError                          : Result:='noerror';
      roDontCreateKey                    : Result:='dontcreatekey';
    else Result:='';
    end;
  end;
var
  s,t:string;
  i:dword;
  o:TSetupRegistryOption;
  vType: string;
begin
  with re do begin
    StrParam('Root', RegRootToStr(re), false);
    StrParam('Subkey', Subkey);
    StrParam('ValueName', ValueName);
    t:='';
    if Typ<>rtNone then begin
      case Typ of
        rtString: begin vType:='String'; t:=ValueData; end;
        rtExpandString: begin vType:='ExpandSZ'; t:=ValueData; end;
        rtDword: begin vType:='Dword';
          if Info.Ver>=4104 then t:=ValueData else begin
            Move(ValueData[1],i,4); t:=Format('$%x', [i]);
          end;
        end;
        rtQWord: begin vType:='Qword'; t := ValueData; end;
        rtBinary: begin vType:='Binary';
          t:='';
          for i:=1 to length(ValueData) do t:=t+IntToHex(byte(ValueData[i]),2)+' ';
          if length(t)>0 then SetLength(t,length(t)-1);
        end;
        rtMultiString: begin vType:='MultiSZ'; t:=ValueData;
          repeat
            i:=pos(#0,t);
            if i=0 then break;
            t[i]:='{'; insert('break}',s,i+1);
          until false;
        end;
        else vType:='Unknown';
      end;
    end;
    StrParam('ValueType', vType, False);
    StrParam('ValueData',t);
    StrParam('Components',Components, false);
    StrParam('Tasks',Tasks, false);
    StrParam('Languages',Languages);
    StrParam('Check',Check);
    StrParam('BeforeInstall',BeforeInstall);
    StrParam('AfterInstall',AfterInstall);

    PrintVersions(MinVersion, OnlyBelowVersion);

    s:='';
    for o:=Low(o) to High(o) do if o in Options then begin
      t:=OptStr(o);
      if t<>'' then s:=s+t+' ';
    end;
    PrintFlagsParam(s);
  end;
end;

procedure TScriptBuilder.PrintRunEntry(const re:TSetupRunEntry);
  function OptStr(Opt: TSetupRunOption):string;
  begin
    case Opt of
      roShellExec           : Result:='shellexec';
      roSkipIfDoesntExist   : Result:='skipifdoesntexist';
      roPostInstall         : Result:='postinstall';
      roUnchecked           : Result:='unchecked';
      roSkipIfSilent        : Result:='skipifsilent';
      roSkipIfNotSilent     : Result:='skipifnotsilent';
      roHideWizard          : Result:='hidewizard';
      roRun32Bit            : Result:='32bit';
      roRun64Bit            : Result:='64bit';
      roRunAsOriginalUser   : Result:='';
    else Result:='';
    end;
  end;
var
  s, t: string;
  o:TSetupRunOption;
begin
  with re do begin
    StrParam('Filename',Name);
    StrParam('Parameters',Parameters);
    StrParam('WorkingDir',WorkingDir);
    StrParam('RunOnceId',RunOnceId);
    StrParam('StatusMsg',StatusMsg);
    StrParam('Description',Description);
    StrParam('Components',Components, false);
    StrParam('Tasks',Tasks, false);
    StrParam('Languages',Languages);
    StrParam('Check',Check);
    StrParam('BeforeInstall',BeforeInstall);
    StrParam('AfterInstall',AfterInstall);

    PrintVersions(MinVersion, OnlyBelowVersion);

    for o:=Low(o) to High(o) do if o in Options then begin
      t:=OptStr(o);
      if t<>'' then s:=s+t+' ';
    end;
    case Wait of
      rwNoWait: s := s + 'nowait';
      rwWaitUntilIdle: s := s + 'waituntilidle';
    end;
    PrintFlagsParam(s);
  end;
end;

procedure TScriptBuilder.PrintTaskEntry(const te:TSetupTaskEntry);
begin
  with te do begin
    StrParam('Name',Name);
    StrParam('Description',Description);
    StrParam('GroupDescription',GroupDescription);
    StrParam('Components',Components);
    StrParam('Languages',Languages);
    StrParam('Check',Check);

    PrintVersions(MinVersion, OnlyBelowVersion);

    NewLine;
  end;
end;

procedure TScriptBuilder.PrintComponentEntry(const ce:TSetupComponentEntry);
var
  s:string;
  i:integer;
begin
  with ce do begin
    StrParam('Name',Name);
    StrParam('Description',Description);
    s:=Types;
    for i:=1 to length(s) do if s[i]=',' then s[i]:=' ';
    StrParam('Types',s);
    IntParam('ExtraDiskSpaceRequired', ExtraDiskSpaceRequired);
    StrParam('Languages',Languages);
    StrParam('Check',Check);

    PrintVersions(MinVersion, OnlyBelowVersion);

    NewLine;
  end;
end;

procedure TScriptBuilder.PrintTypeEntry(const te:TSetupTypeEntry);
begin
  with te do begin
    StrParam('Name',Name);
    StrParam('Description',Description);
    StrParam('Languages',Languages);
    StrParam('Check',Check);

    PrintVersions(MinVersion, OnlyBelowVersion);

    NewLine;
  end;
end;

procedure TScriptBuilder.PrintFileEntry(const fe:TSetupFileEntry);
  function OptStr(Opt: TSetupFileOption):string;
  begin
    case Opt of
      foConfirmOverwrite         : Result:='confirmoverwrite';
      foUninsNeverUninstall      : Result:='uninsneveruninstall';
      foRestartReplace           : Result:='restartreplace';
      foDeleteAfterInstall       : Result:='deleteafterinstall';
      foRegisterServer           : Result:='regserver';
      foRegisterTypeLib          : Result:='regtypelib';
      foSharedFile               : Result:='sharedfile';
      foCompareTimeStamp         : Result:='comparetimestamp';
      foFontIsntTrueType         : Result:='fontisnttruetype';
      foSkipIfSourceDoesntExist  : Result:='skipifsourcedoesntexist';
      foOverwriteReadOnly        : Result:='overwritereadonly';
      foOverwriteSameVersion     : Result:='';
      foCustomDestName           : Result:='';
      foOnlyIfDestFileExists     : Result:='onlyifdestfileexists';
      foNoRegError               : Result:='noregerror';
      foUninsRestartDelete       : Result:='uninsrestartdelete';
      foOnlyIfDoesntExist        : Result:='onlyifdoesntexist';
      foIgnoreVersion            : Result:='ignoreversion';
      foPromptIfOlder            : Result:='promptifolder';
      foDontCopy                 : Result:='dontcopy';
      foUninsRemoveReadOnly      : Result:='uninsremovereadonly';
      foRecurseSubDirsExternal   : Result:='';
      fo32bit                    : Result:='32bit';
      fo64bit                    : Result:='64bit';
      foExternalSizePreset       : Result:='';
      foSetNTFSCompression       : Result:='setntfscomptression';
      foUnsetNTFSCompression     : Result:='unsetntfscomptression';
      foGacInstall               : Result:='gacinstall';
    else Result:='';
    end;
  end;
  function SignStr(Sign: TSetupFileLocationSign):string;
  begin
    case Sign of
      fsYes   : Result:='sign';
      fsOnce  : Result:='signonce';
      fsCheck : Result:='signcheck';
    else Result:='';
    end;
  end;
var
  s,t,SignatureStr:string;
  o:TSetupFileOption;
  Opts:TMySetupFileOptions;
begin
  if (fe.FileType <> ftUserFile) then exit;
  with fe do begin
    if LocationEntry<>-1 then
      with PSetupFileLocationEntry(Info.Entries[seFileLocation][LocationEntry])^ do begin
        if FirstSlice<>LastSlice then
          PrintComment('the following file spans ' + GetSliceName(Info, FirstSlice) + ' to ' + GetSliceName(Info, LastSlice))
        else if FirstSlice<>CurSlice then
          PrintComment('the following file starts on ' + GetSliceName(Info, FirstSlice));
        CurSlice:=LastSlice;
        SignatureStr := SignStr(Sign);
    end
    else SignatureStr := '';
    if (LocationEntry=-1) then Print('; ');
    StrParam('Source',SourceFilename);
    StrParam('DestDir',RemoveBackslashUnlessRoot(DestDir));
    StrParam('DestName',DestName);
    StrParam('FontInstall',InstallFontName);
    StrParam('Components',Components, false);
    StrParam('Tasks',Tasks, false);
    StrParam('Languages',Languages);
    StrParam('Check',Check);
    StrParam('BeforeInstall',BeforeInstall);
    StrParam('AfterInstall',AfterInstall);

    PrintVersions(MinVersion, OnlyBelowVersion);

    s:='';
    Opts:=Options;
    if foDeleteAfterInstall in Opts then Exclude(Opts, foUninsNeverUninstall);
    for o:=Low(o) to High(o) do if o in Opts then begin
      t:=OptStr(o);
      if t<>'' then s:=s+t+' ';
    end;
    if SignatureStr <> '' then s := s + SignatureStr + ' ';
    PrintFlagsParam(s);
  end;
end;

procedure TScriptBuilder.PrintDeleteEntry(const de: TSetupDeleteEntry);
  function TypeStr(Typ: TSetupDeleteType):string;
  begin
    case Typ of
      dfFiles             : Result:='files';
      dfFilesAndOrSubdirs : Result:='filesandordirs';
      dfDirIfEmpty        : Result:='dirifempty';
    end;  
  end;
begin
  with de do begin
    StrParam('Type', TypeStr(DeleteType), false);
    StrParam('Name',Name);
    StrParam('Components',Components, false);
    StrParam('Tasks',Tasks, false);
    StrParam('Languages',Languages);
    StrParam('Check',Check, false);
    StrParam('BeforeInstall',BeforeInstall);
    StrParam('AfterInstall',AfterInstall);
    NewLine;
  end;
end;

procedure TScriptBuilder.PrintCustomMessageEntry(const ce:TSetupCustomMessageEntry);
var
  s,v:TTextData;
  {$IFNDEF UNICODE}
  i: integer;
  {$ENDIF}
begin
  with ce do begin
    s := Name;
    if (LangIndex >= 0) then
      s:=PSetupLanguageEntry(Info.Entries[seLanguage][LangIndex])^.Name+'.'+s;
    {$IFDEF UNICODE}
    v := StringReplace(Value, #13#10, '%n', [rfReplaceAll]);
    {$ELSE}
    v := Value;
    for i := 1 to Pred(Length(v)) do
    begin
      if (Ord(v[i]) = 13) and (Ord(v[i+1]) = 10) then
      begin
        v[i] := '%';
        v[i+1] := 'n';
      end;
    end;
    {$ENDIF}
    StrConst(s, v);
  end;
end;

procedure TScriptBuilder.PrintIconEntry(const ie:TSetupIconEntry);
var
  s:string;
begin
  with ie do begin
    StrParam('Name',IconName);
    StrParam('Filename',Filename);
    StrParam('Parameters',Parameters);
    StrParam('WorkingDir',WorkingDir);
    StrParam('IconFilename',IconFilename);
    if IconIndex<>0 then StrParam('IconIndex', IntToStr(IconIndex), false);
    StrParam('Comment',Comment);
    StrParam('Components',Components, false);
    StrParam('Tasks',Tasks, false);
    StrParam('Languages',Languages);
    StrParam('Check',Check);
    StrParam('BeforeInstall',BeforeInstall);
    StrParam('AfterInstall',AfterInstall);

    PrintVersions(MinVersion, OnlyBelowVersion);

    s:='';
    case CloseOnExit of
      icYes: s:=s+'closeonexit ';
      icNo: s:=s+'dontcloseonexit ';
    end;
    case ShowCmd of
      SW_SHOWMAXIMIZED: s:=s+'runmaximized ';
      SW_SHOWMINNOACTIVE: s:=s+'runminimized ';
    end;
    PrintFlagsParam(s);
  end;
end;

procedure TScriptBuilder.PrintDirEntry(const de: TSetupDirEntry);
  function OptStr(Opt: TSetupDirOption):string;
  begin
    case Opt of
      doUninsNeverUninstall      : Result:='uninsneveruninstall';
      doDeleteAfterInstall       : Result:='deleteafterinstall';
      doUninsAlwaysUninstall     : Result:='uninsalwaysuninstall';
      doSetNTFSCompression       : Result:='setntfscomptression';
      doUnsetNTFSCompression     : Result:='unsetntfscomptression';
    else Result:='';
    end;
  end;
var
  s,t:string;
  Opts: TMySetupDirOptions;
  o: TSetupDirOption;
begin
  StrParam('Name', de.DirName);
  Opts := de.Options;
  for o:=Low(o) to High(o) do if o in Opts then begin
    t := OptStr(o);
    if t<>'' then s := s + t + ' ';
  end;
  PrintFlagsParam(s);
end;

procedure TScriptBuilder.PrintIniEntry(const ie: TSetupIniEntry);
  function OptStr(Opt: TSetupIniOption):string;
  begin
    case Opt of
      ioCreateKeyIfDoesntExist      : Result:='createkeyifdoesntexist';
      ioUninsDeleteEntry            : Result:='uninsdeleteentry';
      ioUninsDeleteEntireSection    : Result:='uninsdeletesection';
      ioUninsDeleteSectionIfEmpty   : Result:='uninsdeletesectionifempty';
    else Result:='';
    end;
  end;
var
  s,t:string;
  Opts: TMySetupIniOptions;
  o: TSetupIniOption;
begin
  StrParam('FileName', ie.Filename);
  StrParam('Section', ie.Section);
  StrParam('Key', ie.Entry);
  StrParam('String', ie.Value);

  Opts := ie.Options;
  for o:=Low(o) to High(o) do if o in Opts then begin
    t := OptStr(o);
    if t<>'' then s := s + t + ' ';
  end;
  PrintFlagsParam(s);
end;

procedure TScriptBuilder.PrintLanguageEntry(const le:TSetupLanguageEntry);
begin
  with le do begin                                      // these filenames must be mirrored in AddEmbeddedFiles()
    StrParam('Name', Name);
    StrParam('MessagesFile', 'embedded\'+Name+'.isl');
    if LicenseText<>'' then StrParam('LicenseFile', MaybeToRtf('embedded\'+Name+'License.txt', LicenseText));
    if InfoBeforeText<>'' then StrParam('InfoBeforeFile', MaybeToRtf('embedded\'+Name+'InfoBefore.txt', InfoBeforeText));
    if InfoAfterText<>'' then StrParam('InfoAfterFile', MaybeToRtf('embedded\'+Name+'InfoAfter.txt', InfoAfterText));
  end;
  NewLine;
end;

procedure TScriptBuilder.PrintLangOptions(const le:TSetupLanguageEntry);
begin
  with le do begin
    StrConst('LanguageName', LanguageName);
    StrConst('LanguageID', '$'+IntToHex(LanguageID,4));
    IntConst('LanguageCodePage', LanguageCodePage);
    StrConst('DialogFontName', DialogFontName);
    StrConst('TitleFontName', TitleFontName);
    StrConst('WelcomeFontName', WelcomeFontName);
    StrConst('CopyrightFontName', CopyrightFontName);
    IntConst('DialogFontSize', DialogFontSize);
    IntConst('TitleFontSize', TitleFontSize);
    IntConst('WelcomeFontSize', WelcomeFontSize);
    IntConst('CopyrightFontSize', CopyrightFontSize);
    if (RightToLeft) then StrConst('RightToLeft', 'yes');
  end;
end;

function TScriptBuilder.GetLanguageFile(i: Integer) : AnsiString;
begin
  Res.Init;
  PrintSectionHeader('LangOptions', False);
  PrintLangOptions(PSetupLanguageEntry(Info.Entries[seLanguage][i])^);
  Result := Res.CopyAsUTF8String(True);
  Res.Destroy;
end;


end.