diff --git a/.gitignore b/.gitignore
index ff46fd9..7893907 100644
--- a/.gitignore
+++ b/.gitignore
@@ -54,3 +54,10 @@ Examples/OSX32/
*.lnk
+/Source/Packages/Android
+/Source/Packages/Linux64
+/Source/Packages/Android64
+/Source/Packages/iOSDevice64
+/Source/Packages/iOSSimARM64
+/Source/Packages/OSX64
+/Source/Packages/OSXARM64
diff --git a/DUnitX.groupproj b/DUnitX.groupproj
new file mode 100644
index 0000000..eecc804
--- /dev/null
+++ b/DUnitX.groupproj
@@ -0,0 +1,48 @@
+
+
+ {FD2B2356-90B5-43EF-93E6-1881F2A6022A}
+
+
+
+
+
+
+
+
+
+
+ Default.Personality.12
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Installer/DUnitX-Setup.iss b/Installer/DUnitX-Setup.iss
new file mode 100644
index 0000000..ba5a50e
--- /dev/null
+++ b/Installer/DUnitX-Setup.iss
@@ -0,0 +1,969 @@
+; ***************************************************************************
+;
+; DUnitX IDE Expert InnoSetup Installer
+;
+; Copyright (c) 2026 Jim McKeeth
+; Licensed under the Apache License, Version 2.0
+;
+; Compile with Inno Setup 6.x from the Installer\ sub-folder:
+; iscc DUnitX-Setup.iss
+;
+; Command-line parameters accepted at run time:
+; /DIR= Override the default installation directory
+; /VERSIONS= Non-interactive version selection:
+; all - every detected Delphi version
+; 12,13 - comma-separated generation numbers
+; (matched against registry version or
+; name substring, e.g. "13" matches
+; "Delphi 13 Florence")
+; /TASKS= Comma-separated list of tasks to perform:
+; expert - build and register the IDE Expert BPL
+; updatepaths - update IDE library/search/DCU paths
+; buildvcl - build VCL runtime packages and units
+; buildfmx - build FMX runtime packages and units
+; backup - back up Delphi registry before changes
+; testinsight - check/offer to install TestInsight
+; Default (no /TASKS): all six tasks are performed.
+; Example: /TASKS="expert,updatepaths,buildvcl"
+; /SKIPBACKUP Backward-compat: equivalent to omitting 'backup' from
+; /TASKS; overrides /TASKS even if backup is listed.
+; /SKIPTS Backward-compat: equivalent to omitting 'testinsight'
+; from /TASKS; overrides /TASKS even if listed.
+; /PLATFORMS= Non-interactive platform selection (ignored when
+; neither buildvcl nor buildfmx task is selected):
+; all - every platform (default)
+; Win32,Win64 - comma-separated platform names
+; Valid names: Win32 Win64 Win64x Android Android64
+; OSX64 OSXARM64 iOSDevice64
+; iOSSimARM64 Linux64
+;
+; A log is always written to %TEMP%\Setup Log *.txt
+; /SILENT Unattended install; selects all detected versions
+; /VERYSILENT Same as /SILENT with no progress window
+;
+; ***************************************************************************
+
+#define AppName "DUnitX"
+#define AppVersion "v0.4.2"
+#define AppPublisher "VSoftTechnologies"
+#define AppURL "https://github.com/VSoftTechnologies/DUnitX"
+
+; ---------------------------------------------------------------------------
+[Setup]
+; ---------------------------------------------------------------------------
+AppId={{B3F2A1D0-C4E5-4F67-89AB-CD1234567EF0}
+AppName={#AppName}
+AppVersion={#AppVersion}
+AppPublisher={#AppPublisher}
+AppPublisherURL={#AppURL}
+AppSupportURL={#AppURL}/issues
+AppUpdatesURL={#AppURL}/releases
+DefaultDirName={autopf}\DUnitX
+DisableProgramGroupPage=yes
+LicenseFile=..\LICENSE.txt
+OutputDir=.
+OutputBaseFilename=DUnitX-Setup
+Compression=lzma2
+SolidCompression=yes
+WizardStyle=modern
+; No elevation needed - all registry writes go to HKCU
+PrivilegesRequired=lowest
+MinVersion=6.1sp1
+; Write a log to %TEMP%\Setup Log *.txt on every run
+SetupLogging=yes
+
+; ---------------------------------------------------------------------------
+[Files]
+; ---------------------------------------------------------------------------
+
+; Expert project files (one .dproj per supported Delphi version)
+Source: "..\Expert\*.pas"; DestDir: "{app}\Expert"; Flags: ignoreversion
+Source: "..\Expert\*.dfm"; DestDir: "{app}\Expert"; Flags: ignoreversion
+Source: "..\Expert\*.dpk"; DestDir: "{app}\Expert"; Flags: ignoreversion
+Source: "..\Expert\*.dproj"; DestDir: "{app}\Expert"; Flags: ignoreversion
+Source: "..\Expert\*.res"; DestDir: "{app}\Expert"; Flags: ignoreversion
+Source: "..\Expert\*.dres"; DestDir: "{app}\Expert"; Flags: ignoreversion
+Source: "..\Expert\*.rc"; DestDir: "{app}\Expert"; Flags: ignoreversion
+Source: "..\Expert\*.ico"; DestDir: "{app}\Expert"; Flags: ignoreversion
+
+; DUnitX source files
+Source: "..\source\*.pas"; DestDir: "{app}\source"; Flags: ignoreversion
+Source: "..\source\*.inc"; DestDir: "{app}\source"; Flags: ignoreversion
+Source: "..\source\*.fmx"; DestDir: "{app}\source"; Flags: ignoreversion
+Source: "..\source\*.dfm"; DestDir: "{app}\source"; Flags: ignoreversion
+Source: "..\source\*.vlb"; DestDir: "{app}\source"; Flags: ignoreversion
+
+; Runtime package project files
+; Note: the VCL package filename is DUnitX_VLC (not VCL) - that is the
+; actual name in the repository.
+Source: "..\source\Packages\DUnitX_VLC.dpk"; DestDir: "{app}\source\Packages"; Flags: ignoreversion
+Source: "..\source\Packages\DUnitX_VLC.dproj"; DestDir: "{app}\source\Packages"; Flags: ignoreversion
+Source: "..\source\Packages\DUnitX_FMX.dpk"; DestDir: "{app}\source\Packages"; Flags: ignoreversion
+Source: "..\source\Packages\DUnitX_FMX.dproj"; DestDir: "{app}\source\Packages"; Flags: ignoreversion
+
+; ---------------------------------------------------------------------------
+[Run]
+; ---------------------------------------------------------------------------
+
+; Offer to launch the newest installed Delphi from the Finish page.
+; GetBdsExe / ShouldOfferLaunch are defined in [Code] below.
+; postinstall - adds a labelled checkbox to the Finish page (ticked by default)
+; nowait - don't hold up Setup waiting for the IDE to close
+; skipifsilent - suppressed in /SILENT and /VERYSILENT mode
+Filename: "{code:GetBdsExe}"; Description: "Launch Delphi after installation"; Flags: postinstall nowait skipifsilent; Check: ShouldOfferLaunch
+Filename: "notepad.exe"; Parameters: """{code:GetLogFile}"""; Description: "View installation log"; Flags: postinstall nowait skipifsilent
+
+; ---------------------------------------------------------------------------
+[Tasks]
+; ---------------------------------------------------------------------------
+
+Name: backup; Description: "Back up Delphi registry settings before making changes"; GroupDescription: "Options:"; Flags: checkedonce
+Name: expert; Description: "Build and register the DUnitX IDE Expert BPL"; GroupDescription: "Components:";
+Name: updatepaths; Description: "Update IDE library, search, and debug DCU paths"; GroupDescription: "Components:";
+Name: buildvcl; Description: "Build VCL runtime packages (Win32 and Win64) and units"; GroupDescription: "Components:";
+Name: buildfmx; Description: "Build FireMonkey (FMX) runtime packages and units"; GroupDescription: "Components:";
+Name: testinsight; Description: "Check for TestInsight and offer to install it if missing"; GroupDescription: "Options:"; Flags: checkedonce
+
+; ---------------------------------------------------------------------------
+[Code]
+
+// --------------------------------------------------------------------------
+// Type declarations
+// --------------------------------------------------------------------------
+
+type
+ TDelphiVersion = record
+ RegVer: String; // registry subkey, e.g. '23.0'
+ Name: String; // display name, e.g. 'Delphi 12 Athens'
+ OldBplSuffix: String; // legacy BPL suffix, e.g. '360'
+ DprojFile: String; // Expert .dproj filename
+ RootDir: String; // populated by DetectVersions
+ Detected: Boolean;
+ end;
+
+// --------------------------------------------------------------------------
+// Global state
+// --------------------------------------------------------------------------
+
+var
+ VersionTable: array of TDelphiVersion;
+ DetectedIndices: array of Integer; // indices into VersionTable
+ VersionPage: TWizardPage;
+ VersionCheckList: TNewCheckListBox;
+ PlatformPage: TWizardPage;
+ PlatformCheckList: TNewCheckListBox;
+ NewestInstalledBdsExe: String; // set during ssPostInstall; used by [Run]
+ SetupInitDir: String; // working directory at startup; used to resolve relative /log= paths
+
+const
+ BDS_KEY = 'Software\Embarcadero\BDS';
+ FMX_PLAT_COUNT = 10;
+
+// --------------------------------------------------------------------------
+// String / list helpers
+// --------------------------------------------------------------------------
+
+// Remove one entry (exact, case-insensitive) from a ';'-separated list.
+function SemiListRemove(const S, Entry: String): String;
+var Src, Part: String;
+ p: Integer;
+begin
+ Result := '';
+ Src := S;
+ while Src <> '' do begin
+ p := Pos(';', Src);
+ if p = 0 then begin Part := Src; Src := ''; end
+ else begin Part := Copy(Src, 1, p-1); Src := Copy(Src, p+1, MaxInt); end;
+ if not SameText(Part, Entry) then begin
+ if Result <> '' then Result := Result + ';';
+ Result := Result + Part;
+ end;
+ end;
+end;
+
+// Check whether an entry already exists in a ';'-separated list.
+function SemiListContains(const S, Entry: String): Boolean;
+var Src, Part: String;
+ p: Integer;
+begin
+ Result := False;
+ Src := S;
+ while Src <> '' do begin
+ p := Pos(';', Src);
+ if p = 0 then begin Part := Src; Src := ''; end
+ else begin Part := Copy(Src, 1, p-1); Src := Copy(Src, p+1, MaxInt); end;
+ if SameText(Part, Entry) then begin Result := True; Exit; end;
+ end;
+end;
+
+// --------------------------------------------------------------------------
+// Registry helpers (HKCU only)
+// --------------------------------------------------------------------------
+
+procedure RegAddToSemiList(const SubKey, ValueName, ToAdd: String);
+var Current, New_: String;
+begin
+ if not RegQueryStringValue(HKCU, SubKey, ValueName, Current) then Current := '';
+ if SemiListContains(Current, ToAdd) then Exit;
+ if Current = '' then New_ := ToAdd else New_ := Current + ';' + ToAdd;
+ RegWriteStringValue(HKCU, SubKey, ValueName, New_);
+end;
+
+procedure RegRemoveFromSemiList(const SubKey, ValueName, ToRemove: String);
+var Current, Cleaned: String;
+begin
+ if not RegQueryStringValue(HKCU, SubKey, ValueName, Current) then Exit;
+ Cleaned := SemiListRemove(Current, ToRemove);
+ if Cleaned <> Current then
+ RegWriteStringValue(HKCU, SubKey, ValueName, Cleaned);
+end;
+
+// --------------------------------------------------------------------------
+// Version table
+// --------------------------------------------------------------------------
+
+procedure BuildVersionTable;
+var i: Integer;
+begin
+ SetArrayLength(VersionTable, 17);
+ i := 0;
+ VersionTable[i].RegVer:='7.0'; VersionTable[i].Name:='Delphi 2010';
+ VersionTable[i].OldBplSuffix:='210'; VersionTable[i].DprojFile:='DUnitX_IDE_Expert_2010.dproj'; Inc(i);
+ VersionTable[i].RegVer:='8.0'; VersionTable[i].Name:='Delphi XE';
+ VersionTable[i].OldBplSuffix:='220'; VersionTable[i].DprojFile:='DUnitX_IDE_Expert_XE.dproj'; Inc(i);
+ VersionTable[i].RegVer:='9.0'; VersionTable[i].Name:='Delphi XE2';
+ VersionTable[i].OldBplSuffix:='230'; VersionTable[i].DprojFile:='DUnitX_IDE_Expert_XE2.dproj'; Inc(i);
+ VersionTable[i].RegVer:='10.0'; VersionTable[i].Name:='Delphi XE3';
+ VersionTable[i].OldBplSuffix:='240'; VersionTable[i].DprojFile:='DUnitX_IDE_Expert_XE3.dproj'; Inc(i);
+ VersionTable[i].RegVer:='11.0'; VersionTable[i].Name:='Delphi XE4';
+ VersionTable[i].OldBplSuffix:='250'; VersionTable[i].DprojFile:='DUnitX_IDE_Expert_XE4.dproj'; Inc(i);
+ VersionTable[i].RegVer:='12.0'; VersionTable[i].Name:='Delphi XE5';
+ VersionTable[i].OldBplSuffix:='260'; VersionTable[i].DprojFile:='DUnitX_IDE_Expert_XE5.dproj'; Inc(i);
+ VersionTable[i].RegVer:='13.0'; VersionTable[i].Name:='Delphi XE6';
+ VersionTable[i].OldBplSuffix:='270'; VersionTable[i].DprojFile:='DUnitX_IDE_Expert_XE6.dproj'; Inc(i);
+ VersionTable[i].RegVer:='14.0'; VersionTable[i].Name:='Delphi XE7';
+ VersionTable[i].OldBplSuffix:='280'; VersionTable[i].DprojFile:='DUnitX_IDE_Expert_XE7.dproj'; Inc(i);
+ VersionTable[i].RegVer:='15.0'; VersionTable[i].Name:='Delphi XE8';
+ VersionTable[i].OldBplSuffix:='290'; VersionTable[i].DprojFile:='DUnitX_IDE_Expert_XE8.dproj'; Inc(i);
+ VersionTable[i].RegVer:='17.0'; VersionTable[i].Name:='Delphi 10 Seattle';
+ VersionTable[i].OldBplSuffix:='300'; VersionTable[i].DprojFile:='DUnitX_IDE_Expert_D10Seattle.dproj'; Inc(i);
+ VersionTable[i].RegVer:='18.0'; VersionTable[i].Name:='Delphi 10.1 Berlin';
+ VersionTable[i].OldBplSuffix:='310'; VersionTable[i].DprojFile:='DUnitX_IDE_Expert_D10Berlin.dproj'; Inc(i);
+ VersionTable[i].RegVer:='19.0'; VersionTable[i].Name:='Delphi 10.2 Tokyo';
+ VersionTable[i].OldBplSuffix:='320'; VersionTable[i].DprojFile:='DUnitX_IDE_Expert_D10Tokyo.dproj'; Inc(i);
+ VersionTable[i].RegVer:='20.0'; VersionTable[i].Name:='Delphi 10.3 Rio';
+ VersionTable[i].OldBplSuffix:='330'; VersionTable[i].DprojFile:='DUnitX_IDE_Expert_D10Rio.dproj'; Inc(i);
+ VersionTable[i].RegVer:='21.0'; VersionTable[i].Name:='Delphi 10.4 Sydney';
+ VersionTable[i].OldBplSuffix:='340'; VersionTable[i].DprojFile:='DUnitX_IDE_Expert_Sydney.dproj'; Inc(i);
+ VersionTable[i].RegVer:='22.0'; VersionTable[i].Name:='Delphi 11 Alexandria';
+ VersionTable[i].OldBplSuffix:='350'; VersionTable[i].DprojFile:='DUnitX_IDE_Expert_D11Alexandria.dproj';Inc(i);
+ VersionTable[i].RegVer:='23.0'; VersionTable[i].Name:='Delphi 12 Athens';
+ VersionTable[i].OldBplSuffix:='360'; VersionTable[i].DprojFile:='DUnitX_IDE_Expert_D12Athens.dproj'; Inc(i);
+ VersionTable[i].RegVer:='37.0'; VersionTable[i].Name:='Delphi 13 Florence';
+ VersionTable[i].OldBplSuffix:='370'; VersionTable[i].DprojFile:='DUnitX_IDE_Expert_D13Florence.dproj';
+end;
+
+function DetectVersions: Integer;
+var i, n: Integer;
+ RootDir: String;
+begin
+ BuildVersionTable;
+ n := 0;
+ SetArrayLength(DetectedIndices, 0);
+ for i := 0 to GetArrayLength(VersionTable) - 1 do begin
+ VersionTable[i].Detected := False;
+ VersionTable[i].RootDir := '';
+ if RegQueryStringValue(HKCU, BDS_KEY + '\' + VersionTable[i].RegVer,
+ 'RootDir', RootDir) then begin
+ RootDir := RemoveBackslash(RootDir);
+ if (RootDir <> '') and DirExists(RootDir) then begin
+ VersionTable[i].Detected := True;
+ VersionTable[i].RootDir := RootDir;
+ SetArrayLength(DetectedIndices, n + 1);
+ DetectedIndices[n] := i;
+ Inc(n);
+ end;
+ end;
+ end;
+ Result := n;
+end;
+
+// --------------------------------------------------------------------------
+// Build helpers
+// --------------------------------------------------------------------------
+
+// Run rsvars.bat and capture the BDSCOMMONDIR environment variable.
+function GetBDSCommonDir(const RsvarsBat: String;
+ var BDSCommonDir: String): Boolean;
+var TempBat, TempOut, Line: String;
+ Content: AnsiString;
+ ResultCode, p: Integer;
+begin
+ Result := False;
+ TempBat := ExpandConstant('{tmp}\dunitx_rsvars.bat');
+ TempOut := ExpandConstant('{tmp}\dunitx_rsvars.txt');
+
+ SaveStringToFile(TempBat,
+ '@echo off' + #13#10 +
+ 'call "' + RsvarsBat + '" >nul 2>&1' + #13#10 +
+ 'echo BDSCOMMONDIR=%BDSCOMMONDIR%' + #13#10,
+ False);
+
+ Exec(ExpandConstant('{cmd}'),
+ '/c ""' + TempBat + '" > "' + TempOut + '" 2>&1"',
+ '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
+
+ if LoadStringFromFile(TempOut, Content) then begin
+ Line := String(Content);
+ p := Pos('BDSCOMMONDIR=', Line);
+ if p > 0 then begin
+ Line := Copy(Line, p + Length('BDSCOMMONDIR='), MaxInt);
+ p := Pos(#13, Line); if p > 0 then Line := Copy(Line, 1, p - 1);
+ p := Pos(#10, Line); if p > 0 then Line := Copy(Line, 1, p - 1);
+ BDSCommonDir := Trim(Line);
+ Result := BDSCommonDir <> '';
+ end;
+ end;
+
+ DeleteFile(TempBat);
+ DeleteFile(TempOut);
+end;
+
+// Log each non-empty line from a multi-line string with an optional prefix.
+procedure LogLines(const Prefix, S: String);
+var Rest, Line: String;
+ p: Integer;
+begin
+ Rest := S;
+ while Rest <> '' do begin
+ p := Pos(#10, Rest);
+ if p = 0 then begin Line := Rest; Rest := ''; end
+ else begin Line := Copy(Rest, 1, p - 1);
+ Rest := Copy(Rest, p + 1, MaxInt); end;
+ // Strip trailing CR
+ if (Length(Line) > 0) and (Line[Length(Line)] = #13) then
+ Line := Copy(Line, 1, Length(Line) - 1);
+ if Trim(Line) <> '' then
+ Log(Prefix + Line);
+ end;
+end;
+
+// Build a .dproj via msbuild, loading the Delphi environment from rsvars.bat.
+// Returns True on success (msbuild exit code 0). Output is captured to the
+// Setup log file.
+function BuildDproj(const RsvarsBat, DprojPath, Config, Plat: String): Boolean;
+var TempBat, TempOut: String;
+ Content: AnsiString;
+ ResultCode: Integer;
+begin
+ TempBat := ExpandConstant('{tmp}\dunitx_build.bat');
+ TempOut := ExpandConstant('{tmp}\dunitx_build_out.txt');
+
+ SaveStringToFile(TempBat,
+ '@echo off' + #13#10 +
+ 'call "' + RsvarsBat + '"' + #13#10 +
+ 'msbuild "' + DprojPath + '"' +
+ ' /p:Config=' + Config + ' /p:Platform=' + Plat +
+ ' /nologo /v:minimal' + #13#10,
+ False);
+
+ Exec(ExpandConstant('{cmd}'),
+ '/c ""' + TempBat + '" > "' + TempOut + '" 2>&1"',
+ '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
+
+ if LoadStringFromFile(TempOut, Content) then
+ LogLines(' [msbuild] ', String(Content));
+
+ DeleteFile(TempBat);
+ DeleteFile(TempOut);
+ Result := (ResultCode = 0);
+end;
+
+// Return the i-th FMX platform name (0-based).
+function FmxPlatform(const i: Integer): String;
+begin
+ case i of
+ 0: Result := 'Win32';
+ 1: Result := 'Win64';
+ 2: Result := 'Win64x';
+ 3: Result := 'Android';
+ 4: Result := 'Android64';
+ 5: Result := 'OSX64';
+ 6: Result := 'OSXARM64';
+ 7: Result := 'iOSDevice64';
+ 8: Result := 'iOSSimARM64';
+ 9: Result := 'Linux64';
+ else
+ Result := '';
+ end;
+end;
+
+// --------------------------------------------------------------------------
+// Platform selection resolution
+// --------------------------------------------------------------------------
+
+function GetSelectedPlatforms: array of String;
+var PlatformsParam: String;
+ n, i, p: Integer;
+ Token, Src: String;
+begin
+ SetArrayLength(Result, 0);
+ n := 0;
+ PlatformsParam := Trim(ExpandConstant('{param:PLATFORMS|}'));
+
+ // Silent without /PLATFORMS, or /PLATFORMS=all → every platform
+ if (WizardSilent and (PlatformsParam = '')) or SameText(PlatformsParam, 'all') then begin
+ SetArrayLength(Result, FMX_PLAT_COUNT);
+ for i := 0 to FMX_PLAT_COUNT - 1 do
+ Result[i] := FmxPlatform(i);
+ Exit;
+ end;
+
+ // /PLATFORMS=Win32,Win64 → parse comma-separated list
+ if PlatformsParam <> '' then begin
+ Src := PlatformsParam;
+ while Src <> '' do begin
+ p := Pos(',', Src);
+ if p = 0 then begin Token := Trim(Src); Src := ''; end
+ else begin Token := Trim(Copy(Src, 1, p-1));
+ Src := Trim(Copy(Src, p+1, MaxInt)); end;
+ for i := 0 to FMX_PLAT_COUNT - 1 do
+ if SameText(FmxPlatform(i), Token) then begin
+ SetArrayLength(Result, n + 1);
+ Result[n] := FmxPlatform(i);
+ Inc(n);
+ Break;
+ end;
+ end;
+ Exit;
+ end;
+
+ // Interactive: read checklist
+ if Assigned(PlatformCheckList) then
+ for i := 0 to FMX_PLAT_COUNT - 1 do
+ if PlatformCheckList.Checked[i] then begin
+ SetArrayLength(Result, n + 1);
+ Result[n] := FmxPlatform(i);
+ Inc(n);
+ end;
+end;
+
+// --------------------------------------------------------------------------
+// Per-version installation
+// --------------------------------------------------------------------------
+
+function ShouldDoBackup: Boolean;
+begin
+ if Trim(ExpandConstant('{param:SKIPBACKUP|}')) <> '' then
+ Result := False
+ else
+ Result := WizardIsTaskSelected('backup');
+end;
+
+procedure InstallForVersion(const VerIdx: Integer; const AppDir: String);
+var
+ Ver: TDelphiVersion;
+ RsvarsBat, ExpertDproj, SourceDir, PkgDir: String;
+ BDSCommonDir, BplName, BplPath: String;
+ KnownPkgsKey, OldBplName: String;
+ EnvVarsKey, LibKey: String;
+ BackupFile: String;
+ SubkeyNames: TArrayOfString;
+ VclDproj, FmxDproj: String;
+ Platforms: array of String;
+ ResultCode, i: Integer;
+begin
+ Ver := VersionTable[VerIdx];
+ RsvarsBat := AddBackslash(Ver.RootDir) + 'bin\rsvars.bat';
+ ExpertDproj:= AddBackslash(AppDir) + 'Expert\' + Ver.DprojFile;
+ SourceDir := RemoveBackslash(AppDir) + '\source';
+ PkgDir := SourceDir + '\Packages';
+
+ WizardForm.StatusLabel.Caption :=
+ 'Preparing ' + Ver.Name + '...';
+
+ // ---- Registry backup --------------------------------------------------
+ // Skipped when /SKIPBACKUP is passed on the command line or the checkbox
+ // is unticked in the interactive wizard.
+ if ShouldDoBackup then begin
+ BackupFile := Ver.RegVer;
+ StringChangeEx(BackupFile, '.', '_', False);
+ BackupFile :=
+ ExpandConstant('{tmp}') + '\DUnitX_BDS_' + BackupFile + '_' +
+ GetDateTimeString('yyyymmdd_hhnnss', '-', ':') + '.reg';
+ Exec(ExpandConstant('{sys}') + '\reg.exe',
+ 'export "HKCU\Software\Embarcadero\BDS\' + Ver.RegVer +
+ '" "' + BackupFile + '" /y',
+ '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
+ if ResultCode = 0 then
+ Log(Ver.Name + ': registry backup saved to ' + BackupFile)
+ else
+ Log(Ver.Name + ': registry backup failed (continuing)');
+ end else
+ Log(Ver.Name + ': registry backup skipped');
+
+ // ---- Validate rsvars.bat (needed for any BPL build) ------------------
+ if WizardIsTaskSelected('expert') or WizardIsTaskSelected('buildvcl') or
+ WizardIsTaskSelected('buildfmx') then begin
+ if not FileExists(RsvarsBat) then begin
+ Log('SKIP ' + Ver.Name + ': rsvars.bat not found: ' + RsvarsBat); Exit;
+ end;
+ end;
+
+ // ---- Expert BPL build + Known Package + DUNITX env var ---------------
+ if WizardIsTaskSelected('expert') then begin
+ if not FileExists(ExpertDproj) then begin
+ Log('SKIP ' + Ver.Name + ': .dproj not found: ' + ExpertDproj); Exit;
+ end;
+
+ if not GetBDSCommonDir(RsvarsBat, BDSCommonDir) then begin
+ Log('SKIP ' + Ver.Name + ': could not read BDSCOMMONDIR from rsvars.bat');
+ Exit;
+ end;
+
+ WizardForm.StatusLabel.Caption :=
+ 'Building Expert BPL for ' + Ver.Name + '...';
+
+ BplName := ChangeFileExt(Ver.DprojFile, '.bpl');
+ BplPath := AddBackslash(BDSCommonDir) + 'dcp\Win32\Release\' + BplName;
+
+ if not BuildDproj(RsvarsBat, ExpertDproj, 'Release', 'Win32') then
+ Log('WARN ' + Ver.Name + ': Expert BPL msbuild returned non-zero');
+
+ if not FileExists(BplPath) then begin
+ Log('FAIL ' + Ver.Name + ': BPL not found at expected path: ' + BplPath);
+ Exit;
+ end;
+ Log(Ver.Name + ': Expert BPL built: ' + BplPath);
+
+ // ---- Register Known Package -----------------------------------------
+ KnownPkgsKey := BDS_KEY + '\' + Ver.RegVer + '\Known Packages';
+ OldBplName := '$(BDS)\bin\DUnitXIDEExpert' + Ver.OldBplSuffix + '.bpl';
+ RegDeleteValue(HKCU, KnownPkgsKey, OldBplName);
+ RegDeleteValue(HKCU, KnownPkgsKey,
+ '$(BDSBIN)\DUnitXIDEExpert' + Ver.OldBplSuffix + '.bpl');
+ RegWriteStringValue(HKCU, KnownPkgsKey, BplPath, 'DUnitX - IDE Expert');
+ Log(Ver.Name + ': Known Package registered');
+
+ // ---- Set DUNITX IDE environment variable ----------------------------
+ EnvVarsKey := BDS_KEY + '\' + Ver.RegVer + '\Environment Variables';
+ RegWriteStringValue(HKCU, EnvVarsKey, 'DUNITX', SourceDir);
+ Log(Ver.Name + ': DUNITX = ' + SourceDir);
+ end;
+
+ Platforms := GetSelectedPlatforms;
+
+ // ---- Build VCL package ------------------------------------------------
+ // The repository file is named DUnitX_VLC.dproj (not VCL — that is the
+ // actual filename in the repo). VCL only supports Win32 and Win64.
+ if WizardIsTaskSelected('buildvcl') then begin
+ VclDproj := PkgDir + '\DUnitX_VLC.dproj';
+ if FileExists(VclDproj) then begin
+ WizardForm.StatusLabel.Caption :=
+ 'Building DUnitX VCL packages for ' + Ver.Name + '...';
+ for i := 0 to GetArrayLength(Platforms) - 1 do
+ if SameText(Platforms[i], 'Win32') or SameText(Platforms[i], 'Win64') then begin
+ if not BuildDproj(RsvarsBat, VclDproj, 'Release', Platforms[i]) then
+ Log('WARN ' + Ver.Name + ': VCL ' + Platforms[i] + '/Release failed');
+ if not BuildDproj(RsvarsBat, VclDproj, 'Debug', Platforms[i]) then
+ Log('WARN ' + Ver.Name + ': VCL ' + Platforms[i] + '/Debug failed');
+ end;
+ end else
+ Log(Ver.Name + ': DUnitX_VLC.dproj not found, skipping VCL build');
+ end else
+ Log(Ver.Name + ': VCL build skipped (task not selected)');
+
+ // ---- Build FMX packages -----------------------------------------------
+ if WizardIsTaskSelected('buildfmx') then begin
+ FmxDproj := PkgDir + '\DUnitX_FMX.dproj';
+ if FileExists(FmxDproj) then begin
+ for i := 0 to GetArrayLength(Platforms) - 1 do begin
+ WizardForm.StatusLabel.Caption :=
+ 'Building DUnitX FMX/' + Platforms[i] + ' for ' + Ver.Name + '...';
+ // Failures on non-Windows platforms are expected if SDKs are absent
+ BuildDproj(RsvarsBat, FmxDproj, 'Release', Platforms[i]);
+ BuildDproj(RsvarsBat, FmxDproj, 'Debug', Platforms[i]);
+ end;
+ end else
+ Log(Ver.Name + ': DUnitX_FMX.dproj not found, skipping FMX build');
+ end else
+ Log(Ver.Name + ': FMX build skipped (task not selected)');
+
+ // ---- Update library paths for every platform subkey -------------------
+ if WizardIsTaskSelected('updatepaths') then begin
+ WizardForm.StatusLabel.Caption :=
+ 'Updating library paths for ' + Ver.Name + '...';
+
+ LibKey := BDS_KEY + '\' + Ver.RegVer + '\Library';
+ if RegGetSubkeyNames(HKCU, LibKey, SubkeyNames) then begin
+ for i := 0 to GetArrayLength(SubkeyNames) - 1 do begin
+ // Remove the legacy $(BDS)\source\DUnitX browsing path entry
+ RegRemoveFromSemiList(LibKey + '\' + SubkeyNames[i],
+ 'Browsing Path', '$(BDS)\source\DUnitX');
+ RegAddToSemiList(LibKey + '\' + SubkeyNames[i],
+ 'Search Path', '$(DUNITX)\Packages\$(Platform)\Release');
+ RegAddToSemiList(LibKey + '\' + SubkeyNames[i],
+ 'Browsing Path', '$(DUNITX)\Source');
+ RegAddToSemiList(LibKey + '\' + SubkeyNames[i],
+ 'Debug DCU Path', '$(DUNITX)\Packages\$(Platform)\Debug');
+ end;
+ end;
+ end;
+
+ Log(Ver.Name + ': installation complete');
+end;
+
+// --------------------------------------------------------------------------
+// TestInsight helpers
+// --------------------------------------------------------------------------
+
+function HasTestInsight(const RegVer: String): Boolean;
+var ExpertsKey: String;
+ ValueNames: TArrayOfString;
+ i: Integer;
+begin
+ Result := False;
+ ExpertsKey := BDS_KEY + '\' + RegVer + '\Experts';
+ if RegGetValueNames(HKCU, ExpertsKey, ValueNames) then
+ for i := 0 to GetArrayLength(ValueNames) - 1 do
+ if Pos('TestInsight', ValueNames[i]) > 0 then begin
+ Result := True; Exit;
+ end;
+end;
+
+procedure DownloadAndRunTestInsight;
+var ZipUrl, ZipPath, ExtractDir, SetupExe: String;
+ ResultCode: Integer;
+begin
+ ZipUrl := 'https://files.spring4d.com/TestInsight/latest/TestInsightSetup.zip';
+ ZipPath := ExpandConstant('{tmp}\TestInsightSetup.zip');
+ ExtractDir := ExpandConstant('{tmp}\TestInsightSetup');
+
+ WizardForm.StatusLabel.Caption := 'Downloading TestInsight...';
+
+ // Use PowerShell to download (no extra plugin required)
+ Exec('powershell.exe',
+ '-NoProfile -NonInteractive -Command ' +
+ '"Invoke-WebRequest -Uri ''' + ZipUrl +
+ ''' -OutFile ''' + ZipPath + ''' -UseBasicParsing"',
+ '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
+
+ if not FileExists(ZipPath) then begin
+ MsgBox(
+ 'Could not download the TestInsight installer.' + #13#10 +
+ 'Please install TestInsight manually from:' + #13#10 +
+ 'https://bitbucket.org/sglienke/testinsight',
+ mbInformation, MB_OK);
+ Exit;
+ end;
+
+ WizardForm.StatusLabel.Caption := 'Extracting TestInsight...';
+
+ if DirExists(ExtractDir) then
+ DelTree(ExtractDir, True, True, True);
+
+ Exec('powershell.exe',
+ '-NoProfile -NonInteractive -Command ' +
+ '"Expand-Archive -Path ''' + ZipPath +
+ ''' -DestinationPath ''' + ExtractDir + ''' -Force"',
+ '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
+
+ SetupExe := ExtractDir + '\TestInsightSetup.exe';
+ if not FileExists(SetupExe) then begin
+ MsgBox(
+ 'TestInsightSetup.exe was not found in the downloaded archive.' + #13#10 +
+ 'Please install TestInsight manually.',
+ mbInformation, MB_OK);
+ DeleteFile(ZipPath);
+ Exit;
+ end;
+
+ WizardForm.StatusLabel.Caption := 'Running TestInsight installer...';
+ Exec(SetupExe, '', '', SW_SHOWNORMAL, ewWaitUntilTerminated, ResultCode);
+ Log('TestInsight installer exited with code: ' + IntToStr(ResultCode));
+ DeleteFile(ZipPath);
+end;
+
+// --------------------------------------------------------------------------
+// Version selection resolution
+// --------------------------------------------------------------------------
+
+// Returns an array of VersionTable indices to install, resolved from either
+// the /VERSIONS command-line parameter, silent mode, or the wizard checklist.
+function GetSelectedVersionIndices: array of Integer;
+var VersionsParam: String;
+ n, i, j, p: Integer;
+ Token, Src: String;
+begin
+ SetArrayLength(Result, 0);
+ n := 0;
+ VersionsParam := Trim(ExpandConstant('{param:VERSIONS|}'));
+
+ // /SILENT or /VERYSILENT without explicit /VERSIONS → select all detected
+ if WizardSilent and (VersionsParam = '') then begin
+ SetArrayLength(Result, GetArrayLength(DetectedIndices));
+ for i := 0 to GetArrayLength(DetectedIndices) - 1 do
+ Result[i] := DetectedIndices[i];
+ Exit;
+ end;
+
+ // /VERSIONS=all → select all detected
+ if SameText(VersionsParam, 'all') then begin
+ SetArrayLength(Result, GetArrayLength(DetectedIndices));
+ for i := 0 to GetArrayLength(DetectedIndices) - 1 do
+ Result[i] := DetectedIndices[i];
+ Exit;
+ end;
+
+ // /VERSIONS=12,13 → match each comma-separated token against name or regver
+ if VersionsParam <> '' then begin
+ Src := VersionsParam;
+ while Src <> '' do begin
+ p := Pos(',', Src);
+ if p = 0 then begin Token := Trim(Src); Src := ''; end
+ else begin Token := Trim(Copy(Src, 1, p-1));
+ Src := Trim(Copy(Src, p+1, MaxInt)); end;
+ for i := 0 to GetArrayLength(DetectedIndices) - 1 do begin
+ j := DetectedIndices[i];
+ // Match registry version ('37.0') or name substring ('Florence', '13')
+ if SameText(VersionTable[j].RegVer, Token) or
+ (Pos(Token, VersionTable[j].Name) > 0) then begin
+ SetArrayLength(Result, n + 1);
+ Result[n] := j;
+ Inc(n);
+ Break;
+ end;
+ end;
+ end;
+ Exit;
+ end;
+
+ // Interactive mode: read the version selection checklist
+ if Assigned(VersionCheckList) then
+ for i := 0 to GetArrayLength(DetectedIndices) - 1 do
+ if VersionCheckList.Checked[i] then begin
+ SetArrayLength(Result, n + 1);
+ Result[n] := DetectedIndices[i];
+ Inc(n);
+ end;
+end;
+
+// --------------------------------------------------------------------------
+// [Run] helpers — launch newest Delphi on the Finish page
+// --------------------------------------------------------------------------
+
+function GetBdsExe(Param: String): String;
+begin
+ Result := NewestInstalledBdsExe;
+end;
+
+function ShouldOfferLaunch: Boolean;
+begin
+ Result := NewestInstalledBdsExe <> '';
+end;
+
+// Return an absolute path to the Setup log file for the [Run] "View log" entry.
+// {log} expands to the path as given on /log=, which may be a bare filename.
+// Notepad resolves relative paths against its own working directory (System32),
+// so we pre-resolve any relative path using the directory captured at startup.
+function GetLogFile(Param: String): String;
+var Path: String;
+begin
+ Path := ExpandConstant('{log}');
+ if (Length(Path) >= 2) and (Path[2] = ':') then
+ Result := Path // already absolute: C:\...
+ else if (Length(Path) >= 2) and (Path[1] = '\') and (Path[2] = '\') then
+ Result := Path // UNC path
+ else if (Length(Path) >= 1) and (Path[1] = '\') then
+ Result := Copy(SetupInitDir, 1, 2) + Path // drive-rooted: \foo -> C:\foo
+ else
+ Result := SetupInitDir + '\' + Path; // bare filename -> absolute
+end;
+
+// --------------------------------------------------------------------------
+// Process helpers
+// --------------------------------------------------------------------------
+
+// Returns True if bds.exe is currently running.
+function IsBdsRunning: Boolean;
+var
+ TempFile: String;
+ Content: AnsiString;
+ ResultCode: Integer;
+begin
+ TempFile := ExpandConstant('{tmp}\dunitx_bds_check.txt');
+ Exec(ExpandConstant('{sys}\cmd.exe'),
+ '/c tasklist /FI "IMAGENAME eq bds.exe" /NH > "' + TempFile + '" 2>&1',
+ '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
+ Result := False;
+ if LoadStringFromFile(TempFile, Content) then
+ Result := Pos('bds.exe', String(Content)) > 0;
+ DeleteFile(TempFile);
+end;
+
+// --------------------------------------------------------------------------
+// Wizard event handlers
+// --------------------------------------------------------------------------
+
+function InitializeSetup: Boolean;
+begin
+ Result := True;
+ SetupInitDir := GetCurrentDir;
+
+ // Require the Delphi IDE to be closed before installation proceeds.
+ // In silent mode the check runs once; on failure the installer aborts.
+ while IsBdsRunning do begin
+ if WizardSilent or
+ (MsgBox(
+ 'The Delphi IDE (bds.exe) is currently running.' + #13#10#13#10 +
+ 'Please close Delphi before installing DUnitX, then click Retry.' + #13#10 +
+ 'Click Cancel to abort the installation.',
+ mbError, MB_RETRYCANCEL) = IDCANCEL) then begin
+ Result := False;
+ Exit;
+ end;
+ end;
+
+ if DetectVersions = 0 then begin
+ MsgBox(
+ 'No supported Delphi installation was found in the registry.' + #13#10 +
+ 'Install Delphi 2010 or later and try again.',
+ mbError, MB_OK);
+ Result := False;
+ end;
+end;
+
+procedure InitializeWizard;
+var i: Integer;
+begin
+ VersionPage := CreateCustomPage(
+ wpSelectTasks,
+ 'Select Delphi Versions',
+ 'Choose the Delphi versions to install DUnitX into.');
+
+ VersionCheckList := TNewCheckListBox.Create(VersionPage);
+ VersionCheckList.Parent := VersionPage.Surface;
+ VersionCheckList.Left := 0;
+ VersionCheckList.Top := 0;
+ VersionCheckList.Width := VersionPage.SurfaceWidth;
+ VersionCheckList.Height := VersionPage.SurfaceHeight;
+
+ for i := 0 to GetArrayLength(DetectedIndices) - 1 do
+ VersionCheckList.AddCheckBox(
+ VersionTable[DetectedIndices[i]].Name +
+ ' (' + VersionTable[DetectedIndices[i]].RootDir + ')',
+ '', 0, True, True, False, True, nil);
+
+ // ---- Platform selection page ------------------------------------------
+ PlatformPage := CreateCustomPage(
+ VersionPage.ID,
+ 'Select Target Platforms',
+ 'Choose the platforms to compile DUnitX units for:');
+
+ PlatformCheckList := TNewCheckListBox.Create(PlatformPage);
+ PlatformCheckList.Parent := PlatformPage.Surface;
+ PlatformCheckList.Left := 0;
+ PlatformCheckList.Top := 0;
+ PlatformCheckList.Width := PlatformPage.SurfaceWidth;
+ PlatformCheckList.Height := PlatformPage.SurfaceHeight;
+
+ for i := 0 to FMX_PLAT_COUNT - 1 do
+ PlatformCheckList.AddCheckBox(FmxPlatform(i), '', 0, True, True, False, True, nil);
+end;
+
+// Skip custom pages when their CLI equivalent is supplied or in silent mode.
+function ShouldSkipPage(PageID: Integer): Boolean;
+begin
+ Result := False;
+ if PageID = VersionPage.ID then
+ Result := WizardSilent or
+ (Trim(ExpandConstant('{param:VERSIONS|}')) <> '');
+ if PageID = PlatformPage.ID then
+ Result := WizardSilent or
+ (Trim(ExpandConstant('{param:PLATFORMS|}')) <> '') or
+ (not WizardIsTaskSelected('buildvcl') and not WizardIsTaskSelected('buildfmx'));
+end;
+
+// Require at least one version to be ticked before proceeding.
+function NextButtonClick(CurPageID: Integer): Boolean;
+var i, n: Integer;
+begin
+ Result := True;
+ if CurPageID = VersionPage.ID then begin
+ n := 0;
+ for i := 0 to GetArrayLength(DetectedIndices) - 1 do
+ if VersionCheckList.Checked[i] then Inc(n);
+ if n = 0 then begin
+ MsgBox('Please select at least one Delphi version.', mbError, MB_OK);
+ Result := False;
+ end;
+ end;
+end;
+
+// Main post-install step: build BPLs and configure the IDE registry.
+procedure CurStepChanged(CurStep: TSetupStep);
+var
+ Selected: array of Integer;
+ AppDir: String;
+ i: Integer;
+ SkipTS: Boolean;
+ MissingTS: String;
+ Ver: TDelphiVersion;
+ BdsExe: String;
+begin
+ if CurStep <> ssPostInstall then Exit;
+
+ AppDir := WizardDirValue;
+ Selected := GetSelectedVersionIndices;
+
+ if GetArrayLength(Selected) = 0 then begin
+ Log('DUnitX installer: no versions selected.');
+ Exit;
+ end;
+
+ // --- Build and register each selected Delphi version ---
+ for i := 0 to GetArrayLength(Selected) - 1 do
+ InstallForVersion(Selected[i], AppDir);
+
+ // --- Find newest installed bds.exe for the Finish-page launch checkbox ---
+ NewestInstalledBdsExe := '';
+ for i := GetArrayLength(Selected) - 1 downto 0 do begin
+ BdsExe := AddBackslash(VersionTable[Selected[i]].RootDir) + 'bin\bds.exe';
+ if FileExists(BdsExe) then begin
+ NewestInstalledBdsExe := BdsExe;
+ Break;
+ end;
+ end;
+
+ // --- TestInsight check -------------------------------------------------
+ // /SKIPTS or the testinsight task being unselected suppresses the check.
+ SkipTS := (Trim(ExpandConstant('{param:SKIPTS|}')) <> '')
+ or not WizardIsTaskSelected('testinsight');
+
+ if not SkipTS then begin
+ MissingTS := '';
+ for i := 0 to GetArrayLength(Selected) - 1 do begin
+ Ver := VersionTable[Selected[i]];
+ if not HasTestInsight(Ver.RegVer) then begin
+ if MissingTS <> '' then MissingTS := MissingTS + ', ';
+ MissingTS := MissingTS + Ver.Name;
+ end;
+ end;
+
+ if MissingTS <> '' then begin
+ if WizardSilent then begin
+ // Silent mode without /SKIPTS → install automatically
+ DownloadAndRunTestInsight;
+ end else begin
+ if MsgBox(
+ 'TestInsight is not installed for: ' + MissingTS + '.' + #13#10#13#10 +
+ 'TestInsight displays real-time test results inside the IDE.' + #13#10#13#10 +
+ 'Download and run the TestInsight installer now?',
+ mbConfirmation, MB_YESNO) = IDYES then
+ DownloadAndRunTestInsight;
+ end;
+ end;
+ end;
+
+ WizardForm.StatusLabel.Caption := 'DUnitX installation complete.';
+end;
diff --git a/Installer/Installer.md b/Installer/Installer.md
new file mode 100644
index 0000000..e36cf9e
--- /dev/null
+++ b/Installer/Installer.md
@@ -0,0 +1,332 @@
+# DUnitX IDE Expert Installer
+
+`DUnitX-Setup.iss` is an [Inno Setup 6](https://jrsoftware.org/isinfo.php) project that automates the installation of [DUnitX](https://github.com/VSoftTechnologies/DUnitX/) into one or more versions of Delphi/RAD Studio.
+
+The installer is based on the [manual steps described in the wiki](https://github.com/VSoftTechnologies/DUnitX/wiki/Wizard-Installation).
+
+1. Detects installed Delphi versions, allows user selection
+2. Copies the DUnitX source tree to the chosen installation directory
+3. _(Optional)_ backup the registry settings for the selected Delphi version
+4. Removes the old Embarcadero version of the DUnitX package if it exists
+5. Removes `$(BDS)\source\DUnitX` from the IDE Browsing path
+6. Builds the Expert BPL for the selected Delphi versions using the command-line compiler
+7. Uses VCL and FMX packages to build debug and release versions of the units for all appropriate compilers
+8. Sets the `$(DUNITX)` IDE environment variable
+9. Updates the IDE's Search paths to the precompiled release units for each platform `$(DUNITX)\packages\$(Platform)\Release`
+10. Updates the IDE's Debug DCU paths to the precompiled debug units for each platform `$(DUNITX)\packages\$(Platform)\Debug`
+11. Adds the source to the IDE's Browsing path to the DUnitX source code `$(DUNITX)\source`
+12. _(Optional)_ download and install TestInsight if it isn't already installed
+13. _(Optional)_ launch the newest version of Delphi
+
+---
+
+## Requirements
+
+- [Inno Setup 6.x](https://jrsoftware.org/isdl.php) (to compile the installer)
+- One or more supported Delphi versions installed (but _not running_)
+- No elevated privileges required — all registry writes go to `HKCU`
+
+---
+
+## Building the Installer EXE
+
+Compile `DUnitX-Setup.iss` from the `Installer\` sub-folder:
+
+```cmd
+iscc DUnitX-Setup.iss
+```
+
+This produces `Installer\DUnitX-Setup.exe`.
+The script references source files via relative paths (`..\..\Expert\`, `..\source\`, etc.) so it **must** be compiled from within the repository.
+
+---
+
+## Running the Installer
+
+### Interactive (GUI)
+
+Double-click `DUnitX-Setup.exe`. The wizard guides you through:
+
+1. Accepting the license
+2. Choosing an installation directory
+3. Selecting which detected Delphi versions to install into
+4. Installation (file copy + build + registry configuration)
+5. Optional TestInsight download
+
+### Unattended / Command-line
+
+```cmd
+DUnitX-Setup.exe [/DIR=] [/VERSIONS=] [/SKIPTS] [/SKIPBACKUP] [/SILENT | /VERYSILENT]
+```
+
+| Parameter | Description |
+| ------------------ | ------------------------------------------------------------------------------ |
+| `/DIR=` | Override the default installation directory. |
+| `/VERSIONS=` | Non-interactive version selection (see below). Skips the selection page. |
+| `/SKIPTS` | Skip the TestInsight check entirely. |
+| `/SKIPBACKUP` | Skip the per-version registry backup. |
+| `/SILENT` | Run with a progress window but no wizard pages; selects all detected versions. |
+| `/VERYSILENT` | Run completely silently; selects all detected versions. |
+
+### `/VERSIONS` values
+
+| Value | Meaning |
+| ------- | ---------------------------------------------------------------------------- |
+| `all` | Install into every detected Delphi version. |
+| `13` | Install into the version whose name contains `13` (e.g. Delphi 13 Florence). |
+| `12,13` | Install into Delphi 12 Athens and Delphi 13 Florence. |
+| `37.0` | Match by registry version string exactly. |
+
+### Examples
+
+```cmd
+REM Interactive — wizard prompts for everything
+DUnitX-Setup.exe
+
+REM All detected versions, no prompts, no TestInsight
+DUnitX-Setup.exe /VERSIONS=all /SKIPTS /VERYSILENT
+
+REM Delphi 13 Florence only, interactive TestInsight prompt
+DUnitX-Setup.exe /VERSIONS=13
+
+REM Custom install location, Athens + Florence, skip TestInsight
+DUnitX-Setup.exe /DIR=C:\Libraries\DUnitX /VERSIONS=12,13 /SKIPTS
+
+REM Skip registry backup (faster CI installs)
+DUnitX-Setup.exe /VERSIONS=all /SKIPBACKUP /VERYSILENT
+```
+
+---
+
+## How It Works
+
+### 1. Detect Installed Versions
+
+On startup the installer scans `HKCU:\Software\Embarcadero\BDS\` for subkeys matching the known version table. For each subkey it:
+
+- Reads the `RootDir` registry value
+- Verifies that the directory exists on disk
+- Skips with a warning if the version is unrecognised or the directory is missing
+
+If no recognised Delphi installation is found, setup aborts immediately with an error message.
+
+### 2. Version Selection
+
+When running interactively, a wizard page lists every detected installation as a checked checkbox (all ticked by default). The user can untick versions to skip.
+
+When `/VERSIONS=` is supplied or `/SILENT`/`/VERYSILENT` is used, this page is skipped and the selection is resolved programmatically.
+
+### 3. File Installation
+
+All necessary source files are extracted from the installer to the chosen `{app}` directory:
+
+```text
+{app}\
+├── Expert\ ← .dproj/.dpk/.pas/.dfm/resources for every version
+└── source\
+ ├── *.pas / *.inc ← DUnitX core source
+ └── Packages\
+ ├── DUnitX_VLC.dproj ← VCL runtime package
+ └── DUnitX_FMX.dproj ← FMX runtime package
+```
+
+### 4. Registry Backup (optional)
+
+Before making any changes for a given Delphi version the installer exports its entire BDS registry key to a timestamped `.reg` file in `%TEMP%`. Pass `/SKIPBACKUP` to suppress this step.
+
+```cmd
+%TEMP%\DUnitX_BDS_23_0_20250101_120000.reg
+```
+
+To restore, double-click the `.reg` file or run:
+
+```cmd
+reg import %TEMP%\DUnitX_BDS_23_0_20250101_120000.reg
+```
+
+### 5. Remove Old Known Package Entry
+
+The Embarcadero-shipped stub package is removed before registering the new one:
+
+```text
+HKCU\Software\Embarcadero\BDS\{ver}\Known Packages
+ → delete value: $(BDS)\bin\DUnitXIDEExpert{OldSuffix}.bpl
+```
+
+### 6. Remove Legacy Browsing Path Entry
+
+The old source path that Embarcadero's installer added is removed from every platform's Browsing Path:
+
+```text
+HKCU\Software\Embarcadero\BDS\{ver}\Library\{platform}\Browsing Path
+ → remove: $(BDS)\source\DUnitX
+```
+
+### 7. Build the Expert BPL
+
+For each selected version:
+
+```text
+{app}\Expert\{DprojFile} → msbuild /p:Config=Release /p:Platform=Win32
+```
+
+The build is driven by `rsvars.bat` (found at `{RootDir}\bin\rsvars.bat`), which sets up the Delphi build environment including `BDSCOMMONDIR`. The installer captures `BDSCOMMONDIR` and uses it to locate the built BPL:
+
+```cmd
+{BDSCOMMONDIR}\dcp\Win32\Release\{PackageName}.bpl
+```
+
+The new BPL path is then registered as a Known Package:
+
+```text
+HKCU\Software\Embarcadero\BDS\{ver}\Known Packages
+ {full path to built .bpl} = "DUnitX - IDE Expert"
+```
+
+If the BPL is not found after a successful build, the version is skipped and the failure is logged.
+
+### 8. Build VCL and FMX Runtime Packages
+
+```text
+{app}\source\Packages\DUnitX_VLC.dproj → Win32 + Win64 × Release + Debug
+{app}\source\Packages\DUnitX_FMX.dproj → all enabled platforms × Release + Debug
+```
+
+> **Note:** the VCL package filename in the repository is `DUnitX_VLC.dproj` (not `VCL`) — that is the actual name on disk.
+
+FMX platforms attempted: `Win32`, `Win64`, `Win64x`, `Android`, `Android64`, `OSX64`, `OSXARM64`, `iOSDevice64`, `iOSSimARM64`, `Linux64`.
+
+Build failures (e.g. missing mobile SDK) are reported as warnings and do not abort the overall installation.
+
+### 9. Set the `DUNITX` Environment Variable
+
+```text
+HKCU\Software\Embarcadero\BDS\{ver}\Environment Variables
+ DUNITX = {app}\source
+```
+
+This makes `$(DUNITX)` available as an IDE macro in all project and library path settings, resolving to the `source\` folder of the installed DUnitX tree.
+
+### 10. Update Search Paths
+
+For every platform subkey under `HKCU\Software\Embarcadero\BDS\{ver}\Library\{platform}\`:
+
+```text
+Search Path += $(DUNITX)\Packages\$(Platform)\Release
+```
+
+This gives the IDE (and MSBuild) the compiled `.dcp` files for each target platform.
+
+### 11. Update Debug DCU Paths
+
+```text
+Debug DCU Path += $(DUNITX)\Packages\$(Platform)\Debug
+```
+
+Allows the debugger to step into DUnitX source code using the debug-configuration DCUs.
+
+### 12. Update Browsing Paths
+
+```text
+Browsing Path += $(DUNITX)
+```
+
+`$(DUNITX)` and `$(Platform)` are IDE macros written literally to the registry and expanded by the IDE at runtime. Each entry is only appended if not already present.
+
+### 13. TestInsight Check (optional)
+
+After all selected versions are processed the installer checks whether [TestInsight](https://bitbucket.org/sglienke/testinsight/wiki/Home) is registered in the `Experts` key for each successfully installed version:
+
+```reg
+HKCU\Software\Embarcadero\BDS\{ver}\Experts
+```
+
+If any version is missing a value whose name contains `TestInsight`, the installer:
+
+1. Lists the affected versions
+2. Prompts `[Yes / No]` to download and run the installer (interactive mode), or installs automatically (silent mode)
+3. Downloads `TestInsightSetup.zip` from `https://files.spring4d.com/TestInsight/latest/`
+4. Extracts it to `%TEMP%\TestInsightSetup\`
+5. Runs `TestInsightSetup.exe` and waits for it to finish
+
+Pass `/SKIPTS` to skip this step entirely.
+
+### 14. Launch Delphi (optional)
+
+After installation the Finish page shows a **"Launch Delphi after installation"** checkbox (ticked by default). If left ticked, clicking **Finish** launches `bds.exe` for the newest Delphi version that was successfully installed.
+
+- The checkbox only appears when at least one `bds.exe` was found on disk.
+- The IDE is launched without waiting for it to close (`nowait`).
+- In `/SILENT` or `/VERYSILENT` mode the checkbox is suppressed (`skipifsilent`).
+
+---
+
+## Version Table
+
+| Delphi name | Reg ver | BPL suffix | Expert `.dproj` |
+| -------------------- | ------- | ---------- | --------------------------------------- |
+| Delphi 2010 | 7.0 | 210 | `DUnitX_IDE_Expert_2010.dproj` |
+| Delphi XE | 8.0 | 220 | `DUnitX_IDE_Expert_XE.dproj` |
+| Delphi XE2 | 9.0 | 230 | `DUnitX_IDE_Expert_XE2.dproj` |
+| Delphi XE3 | 10.0 | 240 | `DUnitX_IDE_Expert_XE3.dproj` |
+| Delphi XE4 | 11.0 | 250 | `DUnitX_IDE_Expert_XE4.dproj` |
+| Delphi XE5 | 12.0 | 260 | `DUnitX_IDE_Expert_XE5.dproj` |
+| Delphi XE6 | 13.0 | 270 | `DUnitX_IDE_Expert_XE6.dproj` |
+| Delphi XE7 | 14.0 | 280 | `DUnitX_IDE_Expert_XE7.dproj` |
+| Delphi XE8 | 15.0 | 290 | `DUnitX_IDE_Expert_XE8.dproj` |
+| Delphi 10 Seattle | 17.0 | 300 | `DUnitX_IDE_Expert_D10Seattle.dproj` |
+| Delphi 10.1 Berlin | 18.0 | 310 | `DUnitX_IDE_Expert_D10Berlin.dproj` |
+| Delphi 10.2 Tokyo | 19.0 | 320 | `DUnitX_IDE_Expert_D10Tokyo.dproj` |
+| Delphi 10.3 Rio | 20.0 | 330 | `DUnitX_IDE_Expert_D10Rio.dproj` |
+| Delphi 10.4 Sydney | 21.0 | 340 | `DUnitX_IDE_Expert_Sydney.dproj` |
+| Delphi 11 Alexandria | 22.0 | 350 | `DUnitX_IDE_Expert_D11Alexandria.dproj` |
+| Delphi 12 Athens | 23.0 | 360 | `DUnitX_IDE_Expert_D12Athens.dproj` |
+| Delphi 13 Florence | 37.0 | 370 | `DUnitX_IDE_Expert_D13Florence.dproj` |
+
+Any detected registry version not in this table is skipped with a warning in the Inno Setup log.
+
+---
+
+## Verification
+
+After running the installer:
+
+1. Open **Registry Editor** and confirm:
+ - `HKCU\...\Known Packages` — new value whose name is the full BPL path and whose data is `DUnitX - IDE Expert`
+ - `HKCU\...\Environment Variables` — `DUNITX` = `{app}\source`
+ - `HKCU\...\Library\Win32\Search Path` — contains `$(DUNITX)\Packages\$(Platform)\Release`
+ - `HKCU\...\Library\Win32\Browsing Path` — contains `$(DUNITX)`, does **not** contain `$(BDS)\source\DUnitX`
+ - `HKCU\...\Library\Win32\Debug DCU Path` — contains `$(DUNITX)\Packages\$(Platform)\Debug`
+2. Launch RAD Studio and confirm the DUnitX wizard appears under **File > New**.
+
+---
+
+## Installed Folder Layout
+
+```text
+{app}\ ← chosen installation directory
+├── Expert\
+│ ├── DUnitX_IDE_Expert_2010.dproj
+│ │ ... (one .dproj/.dpk per supported version)
+│ └── DUnitX_IDE_Expert_D13Florence.dproj
+└── source\ ← $(DUNITX) points here
+ ├── DUnitX.TestFramework.pas
+ ├── ...
+ └── Packages\
+ ├── DUnitX_VLC.dproj
+ ├── DUnitX_FMX.dproj
+ ├── Win32\
+ │ ├── Release\ ← VCL/FMX DCUs/BPLs (Search Path)
+ │ └── Debug\ ← VCL/FMX DCUs (Debug DCU Path)
+ └── [other platforms]\
+```
+
+---
+
+## License
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+https://www.apache.org/licenses/LICENSE-2.0
diff --git a/Source/DUnitX.Exceptions.pas b/Source/DUnitX.Exceptions.pas
index f48628c..2bdd325 100644
--- a/Source/DUnitX.Exceptions.pas
+++ b/Source/DUnitX.Exceptions.pas
@@ -36,7 +36,8 @@ interface
{$ELSE}
SysUtils,
{$ENDIF}
-
DUnitX.ComparableFormat;
+
+ DUnitX.ComparableFormat;
type
ETestFrameworkException = class(Exception);
diff --git a/Source/Packages/DUnitX_FMX.dpk b/Source/Packages/DUnitX_FMX.dpk
new file mode 100644
index 0000000..1a8eb3e
--- /dev/null
+++ b/Source/Packages/DUnitX_FMX.dpk
@@ -0,0 +1,95 @@
+package DUnitX_FMX;
+
+{$R *.res}
+{$IFDEF IMPLICITBUILDING This IFDEF should not be used by users}
+{$ALIGN 8}
+{$ASSERTIONS ON}
+{$BOOLEVAL OFF}
+{$DEBUGINFO OFF}
+{$EXTENDEDSYNTAX ON}
+{$IMPORTEDDATA ON}
+{$IOCHECKS ON}
+{$LOCALSYMBOLS OFF}
+{$LONGSTRINGS ON}
+{$OPENSTRINGS ON}
+{$OPTIMIZATION ON}
+{$OVERFLOWCHECKS OFF}
+{$RANGECHECKS OFF}
+{$REFERENCEINFO OFF}
+{$SAFEDIVIDE OFF}
+{$STACKFRAMES OFF}
+{$TYPEDADDRESS OFF}
+{$VARSTRINGCHECKS ON}
+{$WRITEABLECONST OFF}
+{$MINENUMSIZE 1}
+{$IMAGEBASE $400000}
+{$DEFINE RELEASE}
+{$ENDIF IMPLICITBUILDING}
+{$DESCRIPTION 'DUnitX FMX/Multiplatform'}
+{$RUNONLY}
+{$IMPLICITBUILD ON}
+
+requires
+ rtl,
+ fmx;
+
+contains
+ DUNitX.Loggers.GUIX in '..\DUNitX.Loggers.GUIX.pas' {GUIXTestRunner},
+ DUNitX.Loggers.MobileGUI in '..\DUNitX.Loggers.MobileGUI.pas' {MobileGUITestRunner},
+ DUnitX.WeakReference in '..\DUnitX.WeakReference.pas',
+ DUnitX.Utils.XML in '..\DUnitX.Utils.XML.pas',
+ DUnitX.Utils in '..\DUnitX.Utils.pas',
+ DUnitX.Types in '..\DUnitX.Types.pas',
+ {$IFDEF MSWINDOWS}
+ DUnitX.Timeout,
+ DUnitX.Windows.Console,
+ {$ENDIF}
+ DUnitX.TestRunner in '..\DUnitX.TestRunner.pas',
+ DUnitX.TestResult in '..\DUnitX.TestResult.pas',
+ DUnitX.TestNameParser in '..\DUnitX.TestNameParser.pas',
+ DUnitX.TestFramework in '..\DUnitX.TestFramework.pas',
+ DUnitX.TestFixture in '..\DUnitX.TestFixture.pas',
+ DUnitX.TestDataProvider in '..\DUnitX.TestDataProvider.pas',
+ DUnitX.Test in '..\DUnitX.Test.pas',
+ DUnitX.ServiceLocator in '..\DUnitX.ServiceLocator.pas',
+ DUnitX.RunResults in '..\DUnitX.RunResults.pas',
+ DUnitX.ResStrs in '..\DUnitX.ResStrs.pas',
+ DUnitX.OptionsDefinition in '..\DUnitX.OptionsDefinition.pas',
+ DUnitX.MemoryLeakMonitor.Default in '..\DUnitX.MemoryLeakMonitor.Default.pas',
+ DUnitX.MacOS.Console in '..\DUnitX.MacOS.Console.pas',
+ DUnitX.Loggers.XML.xUnit in '..\DUnitX.Loggers.XML.xUnit.pas',
+ DUnitX.Loggers.XML.NUnit in '..\DUnitX.Loggers.XML.NUnit.pas',
+ DUnitX.Loggers.XML.JUnit in '..\DUnitX.Loggers.XML.JUnit.pas',
+ DUnitX.Loggers.Text in '..\DUnitX.Loggers.Text.pas',
+ DUnitX.Loggers.Null in '..\DUnitX.Loggers.Null.pas',
+ DUnitX.Loggers.Console in '..\DUnitX.Loggers.Console.pas',
+ DUnitX.Linux.Console in '..\DUnitX.Linux.Console.pas',
+ DUnitX.InternalInterfaces in '..\DUnitX.InternalInterfaces.pas',
+ DUnitX.InternalDataProvider in '..\DUnitX.InternalDataProvider.pas',
+ DUnitX.Init in '..\DUnitX.Init.pas',
+ DUnitX.Helpers in '..\DUnitX.Helpers.pas',
+ DUnitX.Generics in '..\DUnitX.Generics.pas',
+ DUnitX.FixtureResult in '..\DUnitX.FixtureResult.pas',
+ DUnitX.FixtureProvider in '..\DUnitX.FixtureProvider.pas',
+ DUnitX.FixtureBuilder in '..\DUnitX.FixtureBuilder.pas',
+ DUnitX.Filters in '..\DUnitX.Filters.pas',
+ DUnitX.FilterBuilder in '..\DUnitX.FilterBuilder.pas',
+ DUnitX.Extensibility in '..\DUnitX.Extensibility.pas',
+ DUnitX.Exceptions in '..\DUnitX.Exceptions.pas',
+ DUnitX.DUnitCompatibility in '..\DUnitX.DUnitCompatibility.pas',
+ DUnitX.Constants in '..\DUnitX.Constants.pas',
+ DUnitX.ConsoleWriter.Base in '..\DUnitX.ConsoleWriter.Base.pas',
+ DUnitX.ComparableFormat.Xml in '..\DUnitX.ComparableFormat.Xml.pas',
+ DUnitX.ComparableFormat in '..\DUnitX.ComparableFormat.pas',
+ DUnitX.ComparableFormat.Csv in '..\DUnitX.ComparableFormat.Csv.pas',
+ DUnitX.CommandLine.Parser in '..\DUnitX.CommandLine.Parser.pas',
+ DUnitX.CommandLine.Options in '..\DUnitX.CommandLine.Options.pas',
+ DUnitX.CommandLine.OptionDef in '..\DUnitX.CommandLine.OptionDef.pas',
+ DUnitX.CategoryExpression in '..\DUnitX.CategoryExpression.pas',
+ DUnitX.Banner in '..\DUnitX.Banner.pas',
+ DUnitX.AutoDetect.Console in '..\DUnitX.AutoDetect.Console.pas',
+ DUnitX.Attributes in '..\DUnitX.Attributes.pas',
+ DUnitX.Assert in '..\DUnitX.Assert.pas',
+ DUnitX.Assert.Ex in '..\DUnitX.Assert.Ex.pas';
+
+end.
diff --git a/Source/Packages/DUnitX_FMX.dproj b/Source/Packages/DUnitX_FMX.dproj
new file mode 100644
index 0000000..8d1d57d
--- /dev/null
+++ b/Source/Packages/DUnitX_FMX.dproj
@@ -0,0 +1,264 @@
+
+
+ True
+ Package
+ Release
+ FMX
+ DUnitX_FMX.dpk
+ Win32
+ {5B9C1441-943C-4EF2-95B5-19CBC6BD857A}
+ DUnitX_FMX
+ 20.3
+ 693395
+
+
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Cfg_1
+ true
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Cfg_2
+ true
+ true
+
+
+ DUnitX_FMX
+ All
+ .\$(Platform)\$(Config)
+ DUnitX FMX/Multiplatform
+ .\$(Platform)\$(Config)
+ System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace)
+ true
+ true
+ true
+
+
+ Debug
+ None
+ activity-1.7.2.dex.jar;annotation-experimental-1.4.1.dex.jar;annotation-jvm-1.8.1.dex.jar;annotations-13.0.dex.jar;appcompat-1.2.0.dex.jar;appcompat-resources-1.2.0.dex.jar;billing-7.1.1.dex.jar;biometric-1.1.0.dex.jar;browser-1.4.0.dex.jar;cloud-messaging.dex.jar;collection-jvm-1.4.2.dex.jar;concurrent-futures-1.1.0.dex.jar;core-1.15.0.dex.jar;core-common-2.2.0.dex.jar;core-ktx-1.15.0.dex.jar;core-runtime-2.2.0.dex.jar;cursoradapter-1.0.0.dex.jar;customview-1.0.0.dex.jar;documentfile-1.0.0.dex.jar;drawerlayout-1.0.0.dex.jar;error_prone_annotations-2.9.0.dex.jar;exifinterface-1.3.6.dex.jar;firebase-annotations-16.2.0.dex.jar;firebase-common-20.3.1.dex.jar;firebase-components-17.1.0.dex.jar;firebase-datatransport-18.1.7.dex.jar;firebase-encoders-17.0.0.dex.jar;firebase-encoders-json-18.0.0.dex.jar;firebase-encoders-proto-16.0.0.dex.jar;firebase-iid-interop-17.1.0.dex.jar;firebase-installations-17.1.3.dex.jar;firebase-installations-interop-17.1.0.dex.jar;firebase-measurement-connector-19.0.0.dex.jar;firebase-messaging-23.1.2.dex.jar;fmx.dex.jar;fragment-1.2.5.dex.jar;google-play-licensing.dex.jar;interpolator-1.0.0.dex.jar;javax.inject-1.dex.jar;kotlin-stdlib-1.8.22.dex.jar;kotlin-stdlib-common-1.8.22.dex.jar;kotlin-stdlib-jdk7-1.8.22.dex.jar;kotlin-stdlib-jdk8-1.8.22.dex.jar;kotlinx-coroutines-android-1.6.4.dex.jar;kotlinx-coroutines-core-jvm-1.6.4.dex.jar;legacy-support-core-utils-1.0.0.dex.jar;lifecycle-common-2.6.2.dex.jar;lifecycle-livedata-2.6.2.dex.jar;lifecycle-livedata-core-2.6.2.dex.jar;lifecycle-runtime-2.6.2.dex.jar;lifecycle-service-2.6.2.dex.jar;lifecycle-viewmodel-2.6.2.dex.jar;lifecycle-viewmodel-savedstate-2.6.2.dex.jar;listenablefuture-1.0.dex.jar;loader-1.0.0.dex.jar;localbroadcastmanager-1.0.0.dex.jar;okio-jvm-3.4.0.dex.jar;play-services-ads-22.2.0.dex.jar;play-services-ads-base-22.2.0.dex.jar;play-services-ads-identifier-18.0.0.dex.jar;play-services-ads-lite-22.2.0.dex.jar;play-services-appset-16.0.1.dex.jar;play-services-base-18.5.0.dex.jar;play-services-basement-18.4.0.dex.jar;play-services-cloud-messaging-17.0.1.dex.jar;play-services-location-21.0.1.dex.jar;play-services-maps-18.1.0.dex.jar;play-services-measurement-base-20.1.2.dex.jar;play-services-measurement-sdk-api-20.1.2.dex.jar;play-services-stats-17.0.2.dex.jar;play-services-tasks-18.2.0.dex.jar;print-1.0.0.dex.jar;profileinstaller-1.3.0.dex.jar;room-common-2.2.5.dex.jar;room-runtime-2.2.5.dex.jar;savedstate-1.2.1.dex.jar;sqlite-2.1.0.dex.jar;sqlite-framework-2.1.0.dex.jar;startup-runtime-1.1.1.dex.jar;tracing-1.2.0.dex.jar;transport-api-3.0.0.dex.jar;transport-backend-cct-3.1.8.dex.jar;transport-runtime-3.1.8.dex.jar;user-messaging-platform-2.0.0.dex.jar;vectordrawable-1.1.0.dex.jar;vectordrawable-animated-1.1.0.dex.jar;versionedparcelable-1.1.1.dex.jar;viewpager-1.0.0.dex.jar;work-runtime-2.7.0.dex.jar
+ package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey=;minSdkVersion=23;targetSdkVersion=35
+
+
+ Debug
+ None
+ activity-1.7.2.dex.jar;annotation-experimental-1.4.1.dex.jar;annotation-jvm-1.8.1.dex.jar;annotations-13.0.dex.jar;appcompat-1.2.0.dex.jar;appcompat-resources-1.2.0.dex.jar;billing-7.1.1.dex.jar;biometric-1.1.0.dex.jar;browser-1.4.0.dex.jar;cloud-messaging.dex.jar;collection-jvm-1.4.2.dex.jar;concurrent-futures-1.1.0.dex.jar;core-1.15.0.dex.jar;core-common-2.2.0.dex.jar;core-ktx-1.15.0.dex.jar;core-runtime-2.2.0.dex.jar;cursoradapter-1.0.0.dex.jar;customview-1.0.0.dex.jar;documentfile-1.0.0.dex.jar;drawerlayout-1.0.0.dex.jar;error_prone_annotations-2.9.0.dex.jar;exifinterface-1.3.6.dex.jar;firebase-annotations-16.2.0.dex.jar;firebase-common-20.3.1.dex.jar;firebase-components-17.1.0.dex.jar;firebase-datatransport-18.1.7.dex.jar;firebase-encoders-17.0.0.dex.jar;firebase-encoders-json-18.0.0.dex.jar;firebase-encoders-proto-16.0.0.dex.jar;firebase-iid-interop-17.1.0.dex.jar;firebase-installations-17.1.3.dex.jar;firebase-installations-interop-17.1.0.dex.jar;firebase-measurement-connector-19.0.0.dex.jar;firebase-messaging-23.1.2.dex.jar;fmx.dex.jar;fragment-1.2.5.dex.jar;google-play-licensing.dex.jar;interpolator-1.0.0.dex.jar;javax.inject-1.dex.jar;kotlin-stdlib-1.8.22.dex.jar;kotlin-stdlib-common-1.8.22.dex.jar;kotlin-stdlib-jdk7-1.8.22.dex.jar;kotlin-stdlib-jdk8-1.8.22.dex.jar;kotlinx-coroutines-android-1.6.4.dex.jar;kotlinx-coroutines-core-jvm-1.6.4.dex.jar;legacy-support-core-utils-1.0.0.dex.jar;lifecycle-common-2.6.2.dex.jar;lifecycle-livedata-2.6.2.dex.jar;lifecycle-livedata-core-2.6.2.dex.jar;lifecycle-runtime-2.6.2.dex.jar;lifecycle-service-2.6.2.dex.jar;lifecycle-viewmodel-2.6.2.dex.jar;lifecycle-viewmodel-savedstate-2.6.2.dex.jar;listenablefuture-1.0.dex.jar;loader-1.0.0.dex.jar;localbroadcastmanager-1.0.0.dex.jar;okio-jvm-3.4.0.dex.jar;play-services-ads-22.2.0.dex.jar;play-services-ads-base-22.2.0.dex.jar;play-services-ads-identifier-18.0.0.dex.jar;play-services-ads-lite-22.2.0.dex.jar;play-services-appset-16.0.1.dex.jar;play-services-base-18.5.0.dex.jar;play-services-basement-18.4.0.dex.jar;play-services-cloud-messaging-17.0.1.dex.jar;play-services-location-21.0.1.dex.jar;play-services-maps-18.1.0.dex.jar;play-services-measurement-base-20.1.2.dex.jar;play-services-measurement-sdk-api-20.1.2.dex.jar;play-services-stats-17.0.2.dex.jar;play-services-tasks-18.2.0.dex.jar;print-1.0.0.dex.jar;profileinstaller-1.3.0.dex.jar;room-common-2.2.5.dex.jar;room-runtime-2.2.5.dex.jar;savedstate-1.2.1.dex.jar;sqlite-2.1.0.dex.jar;sqlite-framework-2.1.0.dex.jar;startup-runtime-1.1.1.dex.jar;tracing-1.2.0.dex.jar;transport-api-3.0.0.dex.jar;transport-backend-cct-3.1.8.dex.jar;transport-runtime-3.1.8.dex.jar;user-messaging-platform-2.0.0.dex.jar;vectordrawable-1.1.0.dex.jar;vectordrawable-animated-1.1.0.dex.jar;versionedparcelable-1.1.1.dex.jar;viewpager-1.0.0.dex.jar;work-runtime-2.7.0.dex.jar
+ package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey=;minSdkVersion=23;targetSdkVersion=35
+
+
+ Debug
+ CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSCameraUsageDescription=The reason for accessing the camera;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSMotionUsageDescription=The reason for accessing the accelerometer;NSDesktopFolderUsageDescription=The reason for accessing the Desktop folder;NSDocumentsFolderUsageDescription=The reason for accessing the Documents folder;NSDownloadsFolderUsageDescription=The reason for accessing the Downloads folder;NSNetworkVolumesUsageDescription=The reason for accessing files on a network volume;NSRemovableVolumesUsageDescription=The reason for accessing files on a removable volume;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple's speech recognition servers;ITSAppUsesNonExemptEncryption=false;NSBluetoothAlwaysUsageDescription=The reason for accessing the Bluetooth interface
+
+
+ Debug
+ CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSCameraUsageDescription=The reason for accessing the camera;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSMotionUsageDescription=The reason for accessing the accelerometer;NSDesktopFolderUsageDescription=The reason for accessing the Desktop folder;NSDocumentsFolderUsageDescription=The reason for accessing the Documents folder;NSDownloadsFolderUsageDescription=The reason for accessing the Downloads folder;NSNetworkVolumesUsageDescription=The reason for accessing files on a network volume;NSRemovableVolumesUsageDescription=The reason for accessing files on a removable volume;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple's speech recognition servers;ITSAppUsesNonExemptEncryption=false;NSBluetoothAlwaysUsageDescription=The reason for accessing the Bluetooth interface
+
+
+ Debug
+ Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace)
+ true
+ CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=
+ 1033
+
+
+ Debug
+ Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace)
+ true
+ CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=
+ 1033
+
+
+ Debug
+ None
+ $(MSBuildProjectName)
+ true
+ CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;NSLocationAlwaysAndWhenInUseUsageDescription=The reason for accessing the location information of the user;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSPhotoLibraryAddUsageDescription=The reason for adding to the photo library;NSCameraUsageDescription=The reason for accessing the camera;NSFaceIDUsageDescription=The reason for accessing the face id;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSSiriUsageDescription=The reason for accessing Siri;ITSAppUsesNonExemptEncryption=false;NSBluetoothAlwaysUsageDescription=The reason for accessing bluetooth;NSBluetoothPeripheralUsageDescription=The reason for accessing bluetooth peripherals;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSMotionUsageDescription=The reason for accessing the accelerometer;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple's speech recognition servers
+ iPhoneAndiPad
+
+
+ None
+ true
+ CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;NSLocationAlwaysAndWhenInUseUsageDescription=The reason for accessing the location information of the user;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSPhotoLibraryAddUsageDescription=The reason for adding to the photo library;NSCameraUsageDescription=The reason for accessing the camera;NSFaceIDUsageDescription=The reason for accessing the face id;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSSiriUsageDescription=The reason for accessing Siri;ITSAppUsesNonExemptEncryption=false;NSBluetoothAlwaysUsageDescription=The reason for accessing bluetooth;NSBluetoothPeripheralUsageDescription=The reason for accessing bluetooth peripherals;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSMotionUsageDescription=The reason for accessing the accelerometer;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple's speech recognition servers
+ iPhoneAndiPad
+
+
+ true
+ true
+ DEBUG;$(DCC_Define)
+ true
+ true
+ false
+ true
+ true
+
+
+ false
+
+
+ 0
+ RELEASE;$(DCC_Define)
+ false
+ 0
+
+
+ true
+ 1033
+
+
+
+ MainSource
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Base
+
+
+ Cfg_1
+ Base
+
+
+ Cfg_2
+ Base
+
+
+
+ Delphi.Personality.12
+ Package
+
+
+
+ DUnitX_FMX.dpk
+
+
+
+
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ False
+ True
+ True
+
+
+ 12
+
+
+
+
+
diff --git a/Source/Packages/DUnitX_FMX_nonshared.a b/Source/Packages/DUnitX_FMX_nonshared.a
new file mode 100644
index 0000000..e6d45bc
Binary files /dev/null and b/Source/Packages/DUnitX_FMX_nonshared.a differ
diff --git a/Source/Packages/DUnitX_VLC.dpk b/Source/Packages/DUnitX_VLC.dpk
new file mode 100644
index 0000000..df54c80
--- /dev/null
+++ b/Source/Packages/DUnitX_VLC.dpk
@@ -0,0 +1,95 @@
+package DUnitX_VLC;
+
+{$R *.res}
+{$IFDEF IMPLICITBUILDING This IFDEF should not be used by users}
+{$ALIGN 8}
+{$ASSERTIONS ON}
+{$BOOLEVAL OFF}
+{$DEBUGINFO OFF}
+{$EXTENDEDSYNTAX ON}
+{$IMPORTEDDATA ON}
+{$IOCHECKS ON}
+{$LOCALSYMBOLS OFF}
+{$LONGSTRINGS ON}
+{$OPENSTRINGS ON}
+{$OPTIMIZATION ON}
+{$OVERFLOWCHECKS OFF}
+{$RANGECHECKS OFF}
+{$REFERENCEINFO OFF}
+{$SAFEDIVIDE OFF}
+{$STACKFRAMES OFF}
+{$TYPEDADDRESS OFF}
+{$VARSTRINGCHECKS ON}
+{$WRITEABLECONST OFF}
+{$MINENUMSIZE 1}
+{$IMAGEBASE $400000}
+{$DEFINE RELEASE}
+{$ENDIF IMPLICITBUILDING}
+{$DESCRIPTION 'DUnitX VCL/Windows'}
+{$RUNONLY}
+{$IMPLICITBUILD ON}
+
+requires
+ rtl,
+ vcl,
+ vclactnband;
+
+contains
+ DUnitX.Loggers.GUI.VCL in '..\DUnitX.Loggers.GUI.VCL.pas' {GUIVCLTestRunner},
+ DUnitX.Loggers.GUI.VCL.RichEdit in '..\DUnitX.Loggers.GUI.VCL.RichEdit.pas',
+ DUnitX.Windows.Console in '..\DUnitX.Windows.Console.pas',
+ DUnitX.WeakReference in '..\DUnitX.WeakReference.pas',
+ DUnitX.Utils.XML in '..\DUnitX.Utils.XML.pas',
+ DUnitX.Utils in '..\DUnitX.Utils.pas',
+ DUnitX.Types in '..\DUnitX.Types.pas',
+ DUnitX.Timeout in '..\DUnitX.Timeout.pas',
+ DUnitX.TestRunner in '..\DUnitX.TestRunner.pas',
+ DUnitX.TestResult in '..\DUnitX.TestResult.pas',
+ DUnitX.TestNameParser in '..\DUnitX.TestNameParser.pas',
+ DUnitX.TestFramework in '..\DUnitX.TestFramework.pas',
+ DUnitX.TestFixture in '..\DUnitX.TestFixture.pas',
+ DUnitX.TestDataProvider in '..\DUnitX.TestDataProvider.pas',
+ DUnitX.Test in '..\DUnitX.Test.pas',
+ DUnitX.ServiceLocator in '..\DUnitX.ServiceLocator.pas',
+ DUnitX.RunResults in '..\DUnitX.RunResults.pas',
+ DUnitX.ResStrs in '..\DUnitX.ResStrs.pas',
+ DUnitX.OptionsDefinition in '..\DUnitX.OptionsDefinition.pas',
+ DUnitX.MemoryLeakMonitor.FastMM4 in '..\DUnitX.MemoryLeakMonitor.FastMM4.pas',
+ DUnitX.MemoryLeakMonitor.Default in '..\DUnitX.MemoryLeakMonitor.Default.pas',
+ DUnitX.MacOS.Console in '..\DUnitX.MacOS.Console.pas',
+ DUnitX.Loggers.XML.xUnit in '..\DUnitX.Loggers.XML.xUnit.pas',
+ DUnitX.Loggers.XML.NUnit in '..\DUnitX.Loggers.XML.NUnit.pas',
+ DUnitX.Loggers.XML.JUnit in '..\DUnitX.Loggers.XML.JUnit.pas',
+ DUnitX.Loggers.Text in '..\DUnitX.Loggers.Text.pas',
+ DUnitX.Loggers.Null in '..\DUnitX.Loggers.Null.pas',
+ DUnitX.Loggers.Console in '..\DUnitX.Loggers.Console.pas',
+ DUnitX.Linux.Console in '..\DUnitX.Linux.Console.pas',
+ DUnitX.InternalInterfaces in '..\DUnitX.InternalInterfaces.pas',
+ DUnitX.InternalDataProvider in '..\DUnitX.InternalDataProvider.pas',
+ DUnitX.Init in '..\DUnitX.Init.pas',
+ DUnitX.Helpers in '..\DUnitX.Helpers.pas',
+ DUnitX.Generics in '..\DUnitX.Generics.pas',
+ DUnitX.FixtureResult in '..\DUnitX.FixtureResult.pas',
+ DUnitX.FixtureProvider in '..\DUnitX.FixtureProvider.pas',
+ DUnitX.FixtureBuilder in '..\DUnitX.FixtureBuilder.pas',
+ DUnitX.Filters in '..\DUnitX.Filters.pas',
+ DUnitX.FilterBuilder in '..\DUnitX.FilterBuilder.pas',
+ DUnitX.Extensibility in '..\DUnitX.Extensibility.pas',
+ DUnitX.Exceptions in '..\DUnitX.Exceptions.pas',
+ DUnitX.DUnitCompatibility in '..\DUnitX.DUnitCompatibility.pas',
+ DUnitX.Constants in '..\DUnitX.Constants.pas',
+ DUnitX.ConsoleWriter.Base in '..\DUnitX.ConsoleWriter.Base.pas',
+ DUnitX.ComparableFormat.Xml in '..\DUnitX.ComparableFormat.Xml.pas',
+ DUnitX.ComparableFormat in '..\DUnitX.ComparableFormat.pas',
+ DUnitX.ComparableFormat.Csv in '..\DUnitX.ComparableFormat.Csv.pas',
+ DUnitX.CommandLine.Parser in '..\DUnitX.CommandLine.Parser.pas',
+ DUnitX.CommandLine.Options in '..\DUnitX.CommandLine.Options.pas',
+ DUnitX.CommandLine.OptionDef in '..\DUnitX.CommandLine.OptionDef.pas',
+ DUnitX.CategoryExpression in '..\DUnitX.CategoryExpression.pas',
+ DUnitX.Banner in '..\DUnitX.Banner.pas',
+ DUnitX.AutoDetect.Console in '..\DUnitX.AutoDetect.Console.pas',
+ DUnitX.Attributes in '..\DUnitX.Attributes.pas',
+ DUnitX.Assert in '..\DUnitX.Assert.pas',
+ DUnitX.Assert.Ex in '..\DUnitX.Assert.Ex.pas';
+
+end.
diff --git a/Source/Packages/DUnitX_VLC.dproj b/Source/Packages/DUnitX_VLC.dproj
new file mode 100644
index 0000000..a5e7e89
--- /dev/null
+++ b/Source/Packages/DUnitX_VLC.dproj
@@ -0,0 +1,202 @@
+
+
+ True
+ Package
+ Release
+ VCL
+ DUnitX_VLC.dpk
+ Win32
+ {327D6780-81EC-4EC6-9EEC-A22C3C4C4835}
+ DUnitX_VLC
+ 20.3
+ 3
+
+
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Cfg_1
+ true
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Cfg_2
+ true
+ true
+
+
+ DUnitX_VLC
+ All
+ .\$(Platform)\$(Config)
+ .\$(Platform)\$(Config)
+ System;Xml;Data;Datasnap;Web;Soap;Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;$(DCC_Namespace)
+ true
+ true
+ true
+
+
+ Debug
+ Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace)
+ true
+ CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=
+ 1033
+
+
+ Debug
+ Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace)
+ true
+ CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=
+ 1033
+
+
+ true
+ true
+ DEBUG;$(DCC_Define)
+ true
+ true
+ false
+ true
+ true
+
+
+ false
+
+
+ 0
+ RELEASE;$(DCC_Define)
+ false
+ 0
+
+
+ DUnitX VCL/Windows
+ true
+ 1033
+
+
+
+ MainSource
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Base
+
+
+ Cfg_1
+ Base
+
+
+ Cfg_2
+ Base
+
+
+
+ Delphi.Personality.12
+ Package
+
+
+
+ DUnitX_VLC.dpk
+
+
+
+
+ False
+ False
+ False
+ False
+ False
+ True
+ True
+ False
+ False
+ False
+
+
+ 12
+
+
+
+
+