; AUTOGENERATED COPYRIGHT HEADER START
; Copyright (C) 2022-2023 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
; AUTOGENERATED COPYRIGHT HEADER END

#define MyAppName "@PROJECT_TITLE@"
#define MyAppVersion "@PROJECT_VERSION@"
#define MyAppVersionText "@_VERSION@"
#define MyAppPublisher "Xaymars Technology Workshop"
#define MyAppURL "https://xaymar.com/"
#define MyAppCopyright "@PROJECT_COPYRIGHT@"

[Setup]
; NOTE: The value of AppId uniquely identifies this application.
; Do not use the same AppId value in installers for other applications.
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)

; Application Information
AppId={{DE56A03A-C8A4-474B-83B0-CFD270262D38}}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
AppMutex=@PROJECT_NAME@,Global\@PROJECT_NAME@

; Versioning
VersionInfoProductName={#MyAppName}
VersionInfoProductVersion={#MyAppVersion}
VersionInfoVersion={#MyAppVersion}
VersionInfoProductTextVersion={#MyAppVersionText}
VersionInfoTextVersion={#MyAppVersionText}
VersionInfoCompany={#MyAppPublisher}
VersionInfoCopyright={#MyAppCopyright}
VersionInfoDescription={#MyAppName} Setup

; Architecture (Platform is always Windows)
ArchitecturesInstallIn64BitMode=x64 arm64 ia64
ArchitecturesAllowed=@ARCH@

; Installation Modes
AlwaysShowComponentsList=no
UsePreviousTasks=no
UsePreviousSetupType=no
UsePreviousPrivileges=no
PrivilegesRequired=admin
PrivilegesRequiredOverridesAllowed=dialog commandline

; Wizard Information
WizardStyle=modern
WizardResizable=yes
SetupIconFile="@PROJECT_SOURCE_DIR@/media/icon.ico"
SetupMutex=@PROJECT_NAME@-Setup,Global\@PROJECT_NAME@-Setup
UninstallDisplayIcon={uninstallexe}

; Directory Choices
UsePreviousAppDir=no
DisableDirPage=no
DirExistsWarning=no
DefaultDirName={code:GetDefaultDirectory}
AppendDefaultDirName=yes
DisableProgramGroupPage=no
DefaultGroupName={#MyAppName}
AppendDefaultGroupName=yes

; Other Information
Uninstallable=yes
CreateUninstallRegKey=not IsPortableMode()
ChangesEnvironment=yes
AllowNoIcons=yes
LicenseFile="@ISS_SOURCE_DIR@/LICENSE"
OutputDir="@ISS_PACKAGE_DIR@"
OutputBaseFilename=@PACKAGE_NAME@-@_PACKAGE_SUFFIX_OVERRIDE@
Compression=lzma2/ultra64
SolidCompression=yes
LZMAAlgorithm=1

[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"

[Components]
name: "startmenu"; Description: "Start Menu Entries"

[Files]
Source: "@ISS_FILES_DIR@/data/obs-plugins/@PROJECT_NAME@/*"; DestDir: "{code:GetOutputDataDir}"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "@ISS_FILES_DIR@/obs-plugins/@D_PLATFORM_BITS@bit/*"; DestDir: "{code:GetOutputBinaryDir}"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "@PROJECT_SOURCE_DIR@/templates/windows/msvc-redist-helper.exe"; DestDir: "{app}"; DestName: "msvc-redist-helper.exe"; Flags: ignoreversion dontcopy noencryption
; NOTE: Don't use "Flags: ignoreversion" on any shared system files

[Icons]
Name: "{group}\{cm:ProgramOnTheWeb,{#MyAppName}}"; Filename: "{#MyAppURL}"; Components: startmenu
Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}"; Components: startmenu

[Code]
// ------------------------------------------------------------------------------------------------------------------ //
#ifdef UNICODE
  #define AW "W"
#else
  #define AW "A"
#endif

type
	WPARAM = UINT_PTR;
	LPARAM = INT_PTR;
	LRESULT = INT_PTR;

const
	SMTO_ABORTIFHUNG = 2;
	WM_WININICHANGE = $001A;
	WM_SETTINGCHANGE = WM_WININICHANGE;

function user32_SendTextMessageTimeoutA(
	hWnd: HWND;
	Msg: UINT;
	wParam: WPARAM;
	lParam: PAnsiChar;
	fuFlags: UINT;
	uTimeout: UINT;
	out lpdwResult: DWORD): LRESULT;
	external 'SendMessageTimeoutA@user32.dll stdcall';
	
procedure RefreshEnvironment();
var
	S: AnsiString;
	MsgResult: DWORD;
begin
	S := 'Environment';
	user32_SendTextMessageTimeoutA(HWND_BROADCAST, WM_SETTINGCHANGE, 0,
		PAnsiChar(S), SMTO_ABORTIFHUNG, 5000, MsgResult);
end;

// ------------------------------------------------------------------------------------------------------------------ //
function ParamExists(Name: String): Boolean;
var
	Idx: Integer;
	Limit: Integer;
	Param: String;
begin
	Limit := ParamCount()
	for Idx := 1 to Limit do begin
		Param := ParamStr(Idx);
		if SameText(Param, '/' + Name) then begin
			Result := True;
			exit;
		end;
	end;
	Result := False;
end;

// ------------------------------------------------------------------------------------------------------------------ //
var
	oModePageSystemChoice: TNewRadioButton;
	oModePageUserChoice: TNewRadioButton;
	oModePagePortableChoice: TNewRadioButton;
	bIsPortableMode: Boolean;
	bIsUserMode: Boolean;
	bIsSystemMode: Boolean;

// InnoSetup
procedure InitializeWizard; forward;
function ShouldSkipPage(PageID: Integer): Boolean; forward;
function PrepareToInstall(var NeedsRestart: Boolean): String; forward;
function GetDefaultDirectory(Value: String): String; forward;
function GetOutputBinaryDir(Value: String): String; forward;
function GetOutputDataDir(Value: String): String; forward;

// Which installation is present?
function IsSystemInstallPresent(): Boolean; forward;
function GetSystemUninstaller(): String; forward;
function IsUserInstallPresent(): Boolean; forward;
function GetUserUninstaller(): String; forward;

// Which installation mode is selected?
function IsSystemMode(): Boolean; forward;
function IsUserMode(): Boolean; forward;
function IsPortableMode(): Boolean; forward;

// Other Metadata
function AppRegistryKey(): String; forward;
function GetUninstallerPath(): String; forward;
function IsUpgrade(): Boolean; forward;
function UninstallOldVersion(): Integer; forward;

// Mode Page
function CreateModePage: TWizardPage; forward;
procedure OnModePagePortableChoiceClick(Sender: TObject); forward;
procedure OnModePageSystemChoiceClick(Sender: TObject); forward;

// ------------------------------------------------------------------------------------------------------------------ //
function InitializeSetup(): Boolean;
begin
	if ParamExists('Portable') then begin
		bIsSystemMode := False;
		bIsUserMode := False;
		bIsPortableMode := True;
	end else begin
		bIsSystemMode := IsAdmin();
		bIsUserMode := not IsAdmin();
		bIsPortableMode := False;
	end;

	Result := True;
end;

// ------------------------------------------------------------------------------------------------------------------ //
procedure InitializeWizard;
var
	oModePage: TWizardPage;
begin
	oModePage := CreateModePage();

	if (IsPortableMode()) then begin
		WizardSelectComponents('!startmenu');
	end else begin
		WizardSelectComponents('startmenu');
	end;
end;

// ------------------------------------------------------------------------------------------------------------------ //
function ShouldSkipPage(PageID: Integer): Boolean;
begin
	Result := False;
	if (PageID = wpSelectDir) then begin
		Result := not IsPortableMode();
	end else if (PageID = wpSelectComponents) then begin
		Result := True;
	end else if (PageID = wpSelectProgramGroup) then begin
		Result := not WizardIsComponentSelected('startmenu');
	end;
end;

// ------------------------------------------------------------------------------------------------------------------ //
function PrepareToInstall(var NeedsRestart: Boolean): String;
var
	iResultCode: Integer;
	sPluginsPath: String;
	sPluginsDataPath: String;
begin
	// Attempt to remove old version if it exists.
	if (IsUpgrade()) then begin
		UninstallOldVersion();
	end;

	// Also ensure that we have the necessary prerequisites installed to run the program.
	ExtractTemporaryFile('msvc-redist-helper.exe');
	Exec(ExpandConstant('{tmp}\msvc-redist-helper.exe'), '2019', '', SW_HIDE, ewWaitUntilTerminated, iResultCode);

	// If this is a User install, register the necessary environment changes.
	if (IsUserMode()) then begin
		sPluginsPath := ExpandConstant('{userpf}\obs-studio\plugins\%module%\bin\');
		StringChangeEx(sPluginsPath, '\', '/', True);
		RegWriteExpandStringValue(HKEY_CURRENT_USER, 'Environment', 'OBS_PLUGINS_PATH', sPluginsPath);

		sPluginsDataPath := ExpandConstant('{userpf}\obs-studio\plugins\%module%\data\');
		StringChangeEx(sPluginsDataPath, '\', '/', True);
		RegWriteExpandStringValue(HKEY_CURRENT_USER, 'Environment', 'OBS_PLUGINS_DATA_PATH', sPluginsDataPath);

		RefreshEnvironment();
	end;
end;

// ------------------------------------------------------------------------------------------------------------------ //
function GetDefaultDirectory(Value: String): String;
var
	sPath: String;
begin
	// In all other cases, change depending on the chosen install method.
	if (IsSystemMode()) then begin
		// Default to ProgramData/obs-studio/@PROJECT_NAME@
		Result := ExpandConstant('{commonappdata}\obs-studio\plugins\@PROJECT_NAME@');
	end else if (IsUserMode()) then begin 
		Result := ExpandConstant('{userpf}\obs-studio\plugins\@PROJECT_NAME@');
	end else begin
		// If a path was given as an argument, use it.
		if (Value <> '') then begin
			Result := Value;
			exit;
		end;

		Result := ExpandConstant('{userdesktop}\obs-studio');
	end;
end;

// ------------------------------------------------------------------------------------------------------------------ //
function GetOutputBinaryDir(Value: String): String;
var
	sPath: String;
begin
	if (IsSystemMode()) then begin
		sPath := ExpandConstant('{app}\bin\@D_PLATFORM_BITS@bit\');
	end else if (IsUserMode()) then begin
		sPath := ExpandConstant('{app}\bin\');
	end else if (IsPortableMode()) then begin
		sPath := ExpandConstant('{app}\obs-plugins\@D_PLATFORM_BITS@bit\');
	end;
	Result := sPath;
end;

function GetOutputDataDir(Value: String): String;
var
	sPath: String;
begin
	if (IsSystemMode()) then begin
		sPath := ExpandConstant('{app}\data\');
	end else if (IsUserMode()) then begin
		sPath := ExpandConstant('{app}\data\@PROJECT_NAME@\');
	end else if (IsPortableMode()) then begin
		sPath := ExpandConstant('{app}\data\obs-plugins\@PROJECT_NAME@\');
	end;
	Result := sPath;
end;

// ------------------------------------------------------------------------------------------------------------------ //
function GetSystemUninstaller(): String;
var
	sPath : String;
begin
	Result := '';
	if (RegQueryStringValue(HKLM64, AppRegistryKey(), 'UninstallString', sPath)) then begin
		Result := sPath;
	end;
end;

function IsSystemInstallPresent(): Boolean;
var
	sPath : String;
begin
	sPath := GetSystemUninstaller();
	if (sPath <> '') then begin
		Result := True;
	end else begin
		Result := False;
	end;
end;

function GetUserUninstaller(): String;
var
	sPath : String;
begin
	Result := '';
	if (RegQueryStringValue(HKCU64, AppRegistryKey(), 'UninstallString', sPath)) then begin
		Result := sPath;
	end;	
end;

function IsUserInstallPresent(): Boolean;
var
	sPath : String;
begin
	sPath := GetUserUninstaller();
	if (sPath <> '') then begin
		Result := True;
	end else begin
		Result := False;
	end;
end;

function GetUninstallerPath(): String;
begin
	if (IsSystemMode()) then begin
		Result := GetSystemUninstaller();
	end else if (IsUserMode()) then begin
		Result := GetUserUninstaller();
	end else begin
		Result := '';
	end;
end;

// ------------------------------------------------------------------------------------------------------------------ //
function IsSystemMode(): Boolean;
begin
	Result := bIsSystemMode;
end;

function IsUserMode(): Boolean;
begin
	Result := bIsUserMode;
end;

function IsPortableMode(): Boolean;
begin
	Result := bIsPortableMode;
end;

// ------------------------------------------------------------------------------------------------------------------ //
function AppRegistryKey(): String;
begin
	Result := ExpandConstant('Software\Microsoft\Windows\CurrentVersion\Uninstall\{#emit SetupSetting('AppId')}_is1');
end;

// ------------------------------------------------------------------------------------------------------------------ //
function IsUpgrade(): Boolean;
begin
	Result := (not IsPortableMode()) and (GetUninstallerPath() <> '');
end;

// ------------------------------------------------------------------------------------------------------------------ //
function UninstallOldVersion(): Integer;
var
	sUninstallerPath: String;
	iResultCode: Integer;
begin
	Result := 0;
	sUninstallerPath := GetUninstallerPath();
	if (sUninstallerPath <> '') then begin
		sUninstallerPath := RemoveQuotes(sUninstallerPath);
		if Exec(sUninstallerPath, '/VERYSILENT /NORESTART /SUPPRESSMSGBOXES', '', SW_HIDE, ewWaitUntilTerminated, iResultCode) then begin
			Result := iResultCode
		end else begin
			Result := 1
		end;
	end;
end;

// ------------------------------------------------------------------------------------------------------------------ //
procedure OnModePageSystemChoiceClick(Sender: TObject);
begin
	if (not oModePageSystemChoice.Enabled) then begin
		OnModePagePortableChoiceClick(Sender);
	end else begin
		oModePageSystemChoice.Checked := True;
		oModePageUserChoice.Checked := False;
		oModePagePortableChoice.Checked := False;
		bIsSystemMode := True;
		bIsUserMode := False;
		bIsPortableMode := False;

		WizardSelectComponents('startmenu');
	end;
end;

procedure onModePageUserChoiceClick(Sender: TObject);
begin
	if (not oModePageUserChoice.Enabled) then begin
		OnModePagePortableChoiceClick(Sender);
	end else begin
		oModePageSystemChoice.Checked := False;
		oModePageUserChoice.Checked := True;
		oModePagePortableChoice.Checked := False;
		bIsSystemMode := False;
		bIsUserMode := True;
		bIsPortableMode := False;
		
		WizardSelectComponents('startmenu');
	end;
end;

procedure OnModePagePortableChoiceClick(Sender: TObject);
begin
	oModePageSystemChoice.Checked := False;
	oModePageUserChoice.Checked := False;
	oModePagePortableChoice.Checked := True;
	bIsSystemMode := False;
	bIsUserMode := False;
	bIsPortableMode := True;
	
	WizardSelectComponents('!startmenu');
end;

function CreateModePage: TWizardPage;
var
	oPage: TWizardPage;
	oSystemPanel: TPanel;
	oSystemChoice: TNewRadioButton;
	oSystemText: TLabel;
	oSystemWarningText: TLabel;
	oUserPanel: TPanel;
	oUserChoice: TNewRadioButton;
	oUserText: TLabel;
	oUserWarningText: TLabel;
	oPortablePanel: TPanel;
	oPortableChoice: TNewRadioButton;
	oPortableText: TLabel;
	oPortableWarningText: TLabel;
	oFractionalHeight: Integer;
begin
	// Build a page that asks a user if they want to use Movable or Static installation.
	oPage := CreateCustomPage(wpLicense,
		'Installation Mode',
		'Select how to install StreamFX on your System');
	oFractionalHeight := ScaleY((oPage.SurfaceHeight - 10) / 3);

	// System-Wide installation
	if (True) then begin
		oSystemPanel := TPanel.Create(oPage);
		oSystemPanel.Parent := oPage.Surface;
		oSystemPanel.ParentBackground := False;
		oSystemPanel.Left := ScaleX(5);
		oSystemPanel.Top := ScaleY(5);
		oSystemPanel.Width := oPage.SurfaceWidth - ScaleX(10);
		oSystemPanel.Height := oFractionalHeight;
		oSystemPanel.Anchors := [akLeft, akTop, akRight];
		oSystemPanel.Color := clWindow;
		oSystemPanel.BevelKind := bkTile;
		oSystemPanel.BevelInner := bvNone;
		oSystemPanel.BevelOuter := bvRaised;
		oSystemPanel.BevelWidth := 1;
		oSystemPanel.OnClick := @OnModePageSystemChoiceClick;

		oSystemChoice := TNewRadioButton.Create(oSystemPanel);
		oModePageSystemChoice := oSystemChoice;
		oSystemChoice.Parent := oSystemPanel;
		oSystemChoice.ParentBackground := False;
		oSystemChoice.Left := ScaleX(5);
		oSystemChoice.Top := ScaleY(5);
		oSystemChoice.Width := oSystemPanel.Width - ScaleX(10);
		oSystemChoice.Height := ScaleY(20);
		oSystemChoice.Anchors := [akLeft, akTop, akRight];
		oSystemChoice.Caption := 'All Users';
		oSystemChoice.Font.Style := [fsBold];
		oSystemChoice.OnClick := @OnModePageSystemChoiceClick;

		oSystemText := TLabel.Create(oSystemPanel);
		oSystemText.Parent := oSystemPanel;
		oSystemText.AutoSize := False;
		oSystemText.Left := ScaleX(5);
		oSystemText.Top := ScaleY(5) + oSystemChoice.Top + oSystemChoice.Height;
		oSystemText.Width := oSystemPanel.Width - ScaleX(10);
		oSystemText.Height := oSystemPanel.Height - ScaleX(5) - oSystemText.Top;
		oSystemText.Anchors := [akLeft, akTop, akRight];
		oSystemText.WordWrap := True
		oSystemText.Caption := 'Install for all users of this System, which will require Administrator rights for future updates. May cause problems with Portable and Current User installations.';
		oSystemText.OnClick := @OnModePageSystemChoiceClick;
		
		// Not available without Administrator rights.
		if (not IsAdmin()) then begin
			oSystemWarningText := TLabel.Create(oSystemPanel);
			oSystemWarningText.Parent := oSystemPanel;
			oSystemWarningText.AutoSize := False;
			oSystemWarningText.Left := ScaleX(5);
			oSystemWarningText.Top := oSystemPanel.Height - ScaleY(5) - ScaleY(15);
			oSystemWarningText.Width := oSystemPanel.Width - ScaleX(10);
			oSystemWarningText.Height := ScaleY(15);
			oSystemWarningText.Anchors := [akLeft, akBottom, akRight];
			oSystemWarningText.WordWrap := True
			oSystemWarningText.Font.Color := clRed;
			oSystemWarningText.Font.Style := [fsBold];
			oSystemWarningText.Caption := 'Please launch the installer with Administrator rights.';

			oSystemPanel.Enabled := False;
			oSystemChoice.Enabled := False;
			oSystemChoice.Checked := False;
			oSystemText.Enabled := False;
		end else if (IsUserInstallPresent()) then begin
			oSystemWarningText := TLabel.Create(oSystemPanel);
			oSystemWarningText.Parent := oSystemPanel;
			oSystemWarningText.AutoSize := False;
			oSystemWarningText.Left := ScaleX(5);
			oSystemWarningText.Top := oSystemPanel.Height - ScaleY(5) - ScaleY(15);
			oSystemWarningText.Width := oSystemPanel.Width - ScaleX(10);
			oSystemWarningText.Height := ScaleY(15);
			oSystemWarningText.Anchors := [akLeft, akBottom, akRight];
			oSystemWarningText.WordWrap := True
			oSystemWarningText.Font.Color := $007EFF;
			oSystemWarningText.Font.Style := [fsBold];
			oSystemWarningText.Caption := 'A conflicting installation was detected which may cause issues.';
		end;
	end;

	// Local-User installation
	if (True) then begin
		oUserPanel := TPanel.Create(oPage);
		oUserPanel.Parent := oPage.Surface;
		oUserPanel.ParentBackground := False;
		oUserPanel.Left := ScaleX(5);
		oUserPanel.Top := ScaleY(5) + oSystemPanel.Top + oSystemPanel.Height;
		oUserPanel.Width := oPage.SurfaceWidth - ScaleX(10);
		oUserPanel.Height := oFractionalHeight;
		oUserPanel.Anchors := [akLeft, akTop, akRight];
		oUserPanel.Color := clWindow;
		oUserPanel.BevelKind := bkTile;
		oUserPanel.BevelInner := bvNone;
		oUserPanel.BevelOuter := bvRaised;
		oUserPanel.BevelWidth := 1;
		oUserPanel.OnClick := @OnModePageUserChoiceClick;

		oUserChoice := TNewRadioButton.Create(oUserPanel);
		oModePageUserChoice := oUserChoice;
		oUserChoice.Parent := oUserPanel;
		oUserChoice.ParentBackground := False;
		oUserChoice.Left := ScaleX(5);
		oUserChoice.Top := ScaleY(5);
		oUserChoice.Width := oUserPanel.Width - ScaleX(10);
		oUserChoice.Height := ScaleY(20);
		oUserChoice.Anchors := [akLeft, akTop, akRight];
		oUserChoice.Caption := 'Current User';
		oUserChoice.Font.Style := [fsBold];
		oUserChoice.OnClick := @OnModePageUserChoiceClick;

		oUserText := TLabel.Create(oUserPanel);
		oUserText.Parent := oUserPanel;
		oUserText.AutoSize := False;
		oUserText.Left := ScaleX(5);
		oUserText.Top := ScaleY(5) + oUserChoice.Top + oUserChoice.Height;
		oUserText.Width := oUserPanel.Width - ScaleX(10);
		oUserText.Height := oUserPanel.Height - ScaleX(5) - oUserText.Top;
		oUserText.Anchors := [akLeft, akTop, akRight];
		oUserText.WordWrap := True
		oUserText.Caption := 'Install for the current user only, which will allow you to use @PROJECT_NAME@. Updating will not require Administrator rights.';
		oUserText.OnClick := @OnModePageUserChoiceClick;
		
		// Not available with Administrator rights.
		if (IsAdmin()) then begin
			oUserWarningText := TLabel.Create(oUserPanel);
			oUserWarningText.Parent := oUserPanel;
			oUserWarningText.AutoSize := False;
			oUserWarningText.Left := ScaleX(5);
			oUserWarningText.Top := oUserPanel.Height - ScaleY(5) - ScaleY(15);
			oUserWarningText.Width := oUserPanel.Width - ScaleX(10);
			oUserWarningText.Height := ScaleY(15);
			oUserWarningText.Anchors := [akLeft, akBottom, akRight];
			oUserWarningText.WordWrap := True
			oUserWarningText.Font.Color := clRed;
			oUserWarningText.Font.Style := [fsBold];
			oUserWarningText.Caption := 'Please launch the Installer without Administrator rights.';

			oUserPanel.Enabled := False;
			oUserChoice.Enabled := False;
			oUserChoice.Checked := False;
			oUserText.Enabled := False;
		end else if (IsSystemInstallPresent()) then begin
			oUserWarningText := TLabel.Create(oUserPanel);
			oUserWarningText.Parent := oUserPanel;
			oUserWarningText.AutoSize := False;
			oUserWarningText.Left := ScaleX(5);
			oUserWarningText.Top := oUserPanel.Height - ScaleY(5) - ScaleY(15);
			oUserWarningText.Width := oUserPanel.Width - ScaleX(10);
			oUserWarningText.Height := ScaleY(15);
			oUserWarningText.Anchors := [akLeft, akBottom, akRight];
			oUserWarningText.WordWrap := True
			oUserWarningText.Font.Color := $007EFF;
			oUserWarningText.Font.Style := [fsBold];
			oUserWarningText.Caption := 'A conflicting system wide installation was detected.';

			oUserPanel.Enabled := False;
			oUserChoice.Enabled := False;
			oUserChoice.Checked := False;
			oUserText.Enabled := False;
		end;
	end;

	// Portable
	if (True) then begin
		oPortablePanel := TPanel.Create(oPage);
		oPortablePanel.Parent := oPage.Surface;
		oPortablePanel.ParentBackground := False;
		oPortablePanel.Left := ScaleX(5);
		oPortablePanel.Top := ScaleY(5) + oUserPanel.Top + oUserPanel.Height;
		oPortablePanel.Width := oPage.SurfaceWidth - ScaleX(10);
		oPortablePanel.Height := oFractionalHeight;
		oPortablePanel.Anchors := [akLeft, akTop, akRight];
		oPortablePanel.Color := clWindow;
		oPortablePanel.BevelKind := bkTile;
		oPortablePanel.BevelInner := bvNone;
		oPortablePanel.BevelOuter := bvRaised;
		oPortablePanel.BevelWidth := 1;
		oPortablePanel.OnClick := @OnModePagePortableChoiceClick;

		oPortableChoice := TNewRadioButton.Create(oPortablePanel);
		oModePagePortableChoice := oPortableChoice;
		oPortableChoice.Parent := oPortablePanel;
		oPortableChoice.ParentBackground := False;
		oPortableChoice.Left := ScaleX(5);
		oPortableChoice.Top := ScaleY(5);
		oPortableChoice.Width := oPortablePanel.Width - ScaleX(10);
		oPortableChoice.Height := ScaleY(20);
		oPortableChoice.Anchors := [akLeft, akTop, akRight];
		oPortableChoice.Caption := 'Portable';
		oPortableChoice.Font.Style := [fsBold];
		oPortableChoice.OnClick := @OnModePagePortableChoiceClick;

		oPortableText := TLabel.Create(oPortablePanel);
		oPortableText.Parent := oPortablePanel;
		oPortableText.AutoSize := False;
		oPortableText.Left := ScaleX(5);
		oPortableText.Top := ScaleY(5) + oPortableChoice.Top + oPortableChoice.Height;
		oPortableText.Width := oPortablePanel.Width - ScaleX(10);
		oPortableText.Height := oPortablePanel.Height - ScaleX(5) - oPortableText.Top;
		oPortableText.Anchors := [akLeft, akTop, akRight];
		oPortableText.WordWrap := True
		oPortableText.Caption := 'Install for a portable OBS Studio environment.';
		oPortableText.OnClick := @OnModePagePortableChoiceClick;
		
		// Warn about Administrator rights
		if (IsAdmin()) then begin
			oPortableWarningText := TLabel.Create(oPortablePanel);
			oPortableWarningText.Parent := oPortablePanel;
			oPortableWarningText.AutoSize := False;
			oPortableWarningText.Left := ScaleX(5);
			oPortableWarningText.Top := oPortablePanel.Height - ScaleY(5) - ScaleY(15);
			oPortableWarningText.Width := oPortablePanel.Width - ScaleX(10);
			oPortableWarningText.Height := ScaleY(15);
			oPortableWarningText.Anchors := [akLeft, akBottom, akRight];
			oPortableWarningText.WordWrap := True
			oPortableWarningText.Font.Color := $007EFF;
			oPortableWarningText.Font.Style := [fsBold];
			oPortableWarningText.Caption := 'Portable installation with Administrator rights may cause unforeseen consequences. Proceed with caution.';
		end;
	end;

	oSystemChoice.Checked := bIsSystemMode;
	oUserChoice.Checked := bIsUserMode;
	oPortableChoice.Checked := bIsPortableMode;

	Result := oPage;
end;
