diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fa68cc85..f6b777a6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -79,10 +79,22 @@ jobs: run: | curl -L -o "aom.7z" "https://github.com/Xaymar/aom/releases/download/v${{ env.LIBAOM_VERSION }}/aom-windows-64-shared.7z" 7z x -y -o"build/libaom/" "aom.7z" - - name: "StreamFX: Configure" + - name: "Code Signing" + if: ${{ github.repository_owner == 'Xaymar' }} + id: codesign shell: bash + run: | + # Restore the Certificate back into a file. + echo "${{ secrets.CODESIGN_CERT_WIN }}" | base64 --decode > "${{ github.workspace }}/cert.pfx" + echo "::set-output name=cmake_args::-DENABLE_CODESIGN=ON -DCODESIGN_TIMESTAMPS=OFF" + - name: "StreamFX: Configure" + shell: bash + env: + CODESIGN_CERT_PASS: ${{ secrets.CODESIGN_CERT_WIN_PASSWORD }} + CODESIGN_CERT_FILE: ${{ github.workspace }}/cert.pfx run: | cmake -H. -B"build/temp" \ + ${{ steps.codesign.outputs.cmake_args }} \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DCMAKE_INSTALL_PREFIX="build/distrib" \ -DPACKAGE_NAME="streamfx-${{ matrix.id }}" \ @@ -109,9 +121,20 @@ jobs: mkdir build/package cmake --build "build/temp" --config RelWithDebInfo --target PACKAGE_7Z cmake --build "build/temp" --config RelWithDebInfo --target PACKAGE_ZIP - - name: "StreamFX: Package Installer" + - name: "StreamFX: Signed Installer Preparation" + if: ${{ github.repository_owner == 'Xaymar' }} + id: codesign_install + shell: bash run: | - & "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" /V10 ".\build\temp\installer.iss" + if [[ -f "${{ github.workspace }}/cert.pfx" ]]; then + signtool=$(awk 'match($0, /^;signtool=(.+)$/, ary) {print ary[1]}' "${{ github.workspace }}/build/temp/installer.iss") + echo "::set-output name=iscc_signtool::${signtool}" + fi + - name: "StreamFX: Package Installer" + shell: cmd + run: | + echo '"C:\Program Files (x86)\Inno Setup 6\ISCC.exe" /V10 "/Ssigntool=${{ steps.codesign_install.outputs.iscc_signtool }} $p" ".\build\temp\installer.iss"' + "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" /V10 "/Ssigntool=${{ steps.codesign_install.outputs.iscc_signtool }} $p" ".\build\temp\installer.iss" - name: "Artifacts" uses: actions/upload-artifact@v1 with: diff --git a/CMakeLists.txt b/CMakeLists.txt index e68b0729..62cf5d76 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2094,10 +2094,22 @@ if(NOT ${PREFIX}OBS_NATIVE) get_filename_component(ISS_MSVCHELPER_PATH "${msvc-redist-helper_BUILD_DIR}" ABSOLUTE) file(TO_NATIVE_PATH "${ISS_MSVCHELPER_PATH}" ISS_MSVCHELPER_PATH) - configure_file( - "templates/installer.iss.in" - "installer.iss" - ) + if(HAVE_CODESIGN) + codesign_command_win32(SHA1 RETURN_BIN ISS_CODESIGN_BIN_SHA1 RETURN_ARGS ISS_CODESIGN_CMD_SHA1) + codesign_command_win32(SHA2 APPEND RETURN_BIN ISS_CODESIGN_BIN_SHA2 RETURN_ARGS ISS_CODESIGN_CMD_SHA2) + list(JOIN ISS_CODESIGN_CMD_SHA1 " " ISS_CODESIGN_CMD_SHA1) + list(JOIN ISS_CODESIGN_CMD_SHA2 " " ISS_CODESIGN_CMD_SHA2) + + configure_file( + "templates/installer-signed.iss.in" + "installer.iss" + ) + else() + configure_file( + "templates/installer.iss.in" + "installer.iss" + ) + endif() endif() # Apple MacOS diff --git a/templates/installer-signed.iss.in b/templates/installer-signed.iss.in new file mode 100644 index 00000000..b2c3d6f2 --- /dev/null +++ b/templates/installer-signed.iss.in @@ -0,0 +1,360 @@ +; Script generated by the Inno Setup Script Wizard. +; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! + +#define MyAppName "@PROJECT_FULL_NAME@" +#define MyAppVersion "@VERSION_MAJOR@.@VERSION_MINOR@.@VERSION_PATCH@.@VERSION_TWEAK@" +#define MyAppVersionText "@VERSION_STRING@" +#define MyAppPublisher "Xaymars Technology Workshop" +#define MyAppURL "https://xaymar.com/" +#define MyAppCopyright "@PROJECT_COPYRIGHT_YEARS@ @PROJECT_AUTHORS@" + +[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={#MyAppName} + +; 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 +UsePreviousPrivileges=no +PrivilegesRequired=admin +PrivilegesRequiredOverridesAllowed=dialog commandline + +; Wizard Information +WizardStyle=modern +WizardResizable=yes +SetupIconFile="@PROJECT_SOURCE_DIR@/media/icon.ico" +UninstallDisplayIcon={uninstallexe} + +; Code Signing +;signtool=@ISS_CODESIGN_BIN_SHA1@ +SignTool=signtool @ISS_CODESIGN_CMD_SHA1@ $f +SignTool=signtool @ISS_CODESIGN_CMD_SHA2@ $f +SignedUninstaller=yes + +; Other Information +UsePreviousAppDir=no +Uninstallable=not IsPortablePagePortableChoiceChecked() +DefaultDirName={code:GetDefaultDirectory} +DefaultGroupName={#MyAppName} +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" + +[Files] +Source: "@ISS_FILES_DIR@/*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs +Source: "@PROJECT_SOURCE_DIR@/templates/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}" +Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}" + +[Code] +// ------------------------------------------------------------------------------------------------------------------ // +var + oPortablePageStaticChoice: TNewRadioButton; + oPortablePagePortableChoice: TNewRadioButton; + +function FindRegistryKey(): String; forward; +function GetDefaultDirectory(Value: String): String; forward; +function GetUninstallerPath(): String; forward; +function IsUpgrade(): Boolean; forward; +function IsPortablePagePortableChoiceChecked(): Boolean; forward; +function UninstallOldVersion(): Integer; forward; +procedure OnPortablePagePortableChoiceClick(Sender: TObject); forward; +procedure OnPortablePageStaticChoiceClick(Sender: TObject); forward; +function CreatePortablePage: TWizardPage; forward; +procedure InitializeWizard; forward; +function ShouldSkipPage(PageID: Integer): Boolean; forward; +function PrepareToInstall(var NeedsRestart: Boolean): String; forward; + +// ------------------------------------------------------------------------------------------------------------------ // +function FindRegistryKey(): String; +begin + Result := ExpandConstant('Software\Microsoft\Windows\CurrentVersion\Uninstall\{#emit SetupSetting('AppId')}_is1'); +end; + +// ------------------------------------------------------------------------------------------------------------------ // +function GetDefaultDirectory(Value: String): String; +var + sInstallPath: String; +begin + // 1. Use the path we were given on call. + sInstallPath := Value; + + // 2. If that was empty, try and find it ourselves from the registry. + if (sInstallPath = '') then + RegQueryStringValue(HKA64, FindRegistryKey(), 'InstallLocation', sInstallPath); + + // 2. If empty, try and find the "Local Machine" installation of OBS Studio. + if (sInstallPath = '') then + RegQueryStringValue(HKLM64, 'SOFTWARE\OBS Studio', '', sInstallPath); + + // 3. If empty, try and find the "Current User" installation of OBS Studio. + if (sInstallPath = '') then + RegQueryStringValue(HKCU64, 'SOFTWARE\OBS Studio', '', sInstallPath); + + // 6. If empty, just use the default path. + if (sInstallPath = '') then + sInstallPath := ExpandConstant('{commonpf}\obs-studio'); + + Result := sInstallPath +end; + +// ------------------------------------------------------------------------------------------------------------------ // +function GetUninstallerPath(): String; +var + sRegistryKey: String; + sUninstallerPath: String; +begin + sRegistryKey := FindRegistryKey(); + + RegQueryStringValue(HKLM64, sRegistryKey, 'UninstallString', sUninstallerPath); + + if (sUninstallerPath = '') then + RegQueryStringValue(HKCU64, sRegistryKey, 'UninstallString', sUninstallerPath); + + if (sUninstallerPath = '') then + RegQueryStringValue(HKLM32, sRegistryKey, 'UninstallString', sUninstallerPath); + + if (sUninstallerPath = '') then + RegQueryStringValue(HKCU32, sRegistryKey, 'UninstallString', sUninstallerPath); + + Result := sUninstallerPath; +end; + +// ------------------------------------------------------------------------------------------------------------------ // +function IsUpgrade(): Boolean; +begin + Result := (not IsPortablePagePortableChoiceChecked()) 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 OnPortablePagePortableChoiceClick(Sender: TObject); +begin + oPortablePageStaticChoice.Checked := False; + oPortablePagePortableChoice.Checked := True; +end; + +procedure OnPortablePageStaticChoiceClick(Sender: TObject); +begin + if (not oPortablePageStaticChoice.Enabled) then begin + OnPortablePagePortableChoiceClick(Sender); + end else begin + oPortablePageStaticChoice.Checked := True; + oPortablePagePortableChoice.Checked := False; + end; +end; + +function IsPortablePagePortableChoiceChecked(): Boolean; +begin + Result := oPortablePagePortableChoice.Checked; +end; + +function CreatePortablePage: TWizardPage; +var + oPage: TWizardPage; + oStaticPanel: TPanel; + oStaticChoice: TNewRadioButton; + oStaticText: TLabel; + oStaticWarningText: TLabel; + oPortablePanel: TPanel; + oPortableChoice: TNewRadioButton; + oPortableText: TLabel; +begin + // Build a page that asks a user if they want to use Movable or Static installation. + oPage := CreateCustomPage(wpLicense, + 'Installation Type', + 'Select how to install StreamFX on your System'); + + oStaticPanel := TPanel.Create(oPage); + oStaticPanel.Parent := oPage.Surface; + oStaticPanel.ParentBackground := False; + oStaticPanel.Left := ScaleX(5); + oStaticPanel.Top := ScaleY(5); + oStaticPanel.Width := oPage.SurfaceWidth - ScaleX(10); + oStaticPanel.Height := ScaleY(100); + oStaticPanel.Anchors := [akLeft, akTop, akRight]; + oStaticPanel.Color := clWindow; + oStaticPanel.BevelKind := bkTile; + oStaticPanel.BevelInner := bvNone; + oStaticPanel.BevelOuter := bvRaised; + oStaticPanel.BevelWidth := 1; + oStaticPanel.OnClick := @OnPortablePageStaticChoiceClick; + + oStaticChoice := TNewRadioButton.Create(oStaticPanel); + oPortablePageStaticChoice := oStaticChoice; + oStaticChoice.Parent := oStaticPanel; + oStaticChoice.ParentBackground := False; + oStaticChoice.Left := ScaleX(5); + oStaticChoice.Top := ScaleY(5); + oStaticChoice.Width := oStaticPanel.Width - ScaleX(10); + oStaticChoice.Height := ScaleY(20); + oStaticChoice.Anchors := [akLeft, akTop, akRight]; + oStaticChoice.Caption := 'Static'; + oStaticChoice.Font.Style := [fsBold]; + oStaticChoice.OnClick := @OnPortablePageStaticChoiceClick; + + oStaticText := TLabel.Create(oStaticPanel); + oStaticText.Parent := oStaticPanel; + oStaticText.AutoSize := False; + oStaticText.Left := ScaleX(5); + oStaticText.Top := ScaleY(5) + oStaticChoice.Top + oStaticChoice.Height; + oStaticText.Width := oStaticPanel.Width - ScaleX(10); + oStaticText.Height := oStaticPanel.Height - ScaleX(5) - oStaticText.Top; + oStaticText.Anchors := [akLeft, akTop, akRight]; + oStaticText.WordWrap := True + oStaticText.Caption := 'Install for use in a static version of OBS Studio, with all necessary features to support it.'; + oStaticText.OnClick := @OnPortablePageStaticChoiceClick; + + oPortablePanel := TPanel.Create(oPage); + oPortablePanel.Parent := oPage.Surface; + oPortablePanel.ParentBackground := False; + oPortablePanel.Left := ScaleX(5); + oPortablePanel.Top := ScaleY(5) + oStaticPanel.Top + oStaticPanel.Height; + oPortablePanel.Width := oPage.SurfaceWidth - ScaleX(10); + oPortablePanel.Height := ScaleY(100); + oPortablePanel.Anchors := [akLeft, akTop, akRight]; + oPortablePanel.Color := clWindow; + oPortablePanel.BevelKind := bkTile; + oPortablePanel.BevelInner := bvNone; + oPortablePanel.BevelOuter := bvRaised; + oPortablePanel.BevelWidth := 1; + oPortablePanel.OnClick := @OnPortablePagePortableChoiceClick; + + oPortableChoice := TNewRadioButton.Create(oPortablePanel); + oPortablePagePortableChoice := 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 := @OnPortablePagePortableChoiceClick; + + 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 use in portable or multi-environment scenarios, which require StreamFX to not be tied to the System itself. The uninstaller, automatic updates and other system-dependent features will be unavailable.'; + oPortableText.OnClick := @OnPortablePagePortableChoiceClick; + + if (not IsAdmin()) then begin + oStaticWarningText := TLabel.Create(oStaticPanel); + oStaticWarningText.Parent := oStaticPanel; + oStaticWarningText.AutoSize := False; + oStaticWarningText.Left := ScaleX(5); + oStaticWarningText.Top := oPortablePanel.Height - ScaleY(5) - ScaleY(15); + oStaticWarningText.Width := oPortablePanel.Width - ScaleX(10); + oStaticWarningText.Height := ScaleY(15); + oStaticWarningText.Anchors := [akLeft, akBottom, akRight]; + oStaticWarningText.WordWrap := True + oStaticWarningText.Font.Color := clRed; + oStaticWarningText.Font.Style := [fsBold]; + oStaticWarningText.Caption := 'Please launch the Installer as Administrator for static installations.'; + + oStaticPanel.Enabled := False; + oStaticChoice.Enabled := False; + oStaticText.Enabled := False; + + oStaticChoice.Checked := False; + oPortableChoice.Checked := True; + end else begin + oStaticChoice.Checked := True; + oPortableChoice.Checked := False; + end; + + Result := oPage; +end; + +procedure InitializeWizard; +var + oPortablePage: TWizardPage; +begin + oPortablePage := CreatePortablePage(); +end; + +// ------------------------------------------------------------------------------------------------------------------ // +function ShouldSkipPage(PageID: Integer): Boolean; +begin + Result := False; + if (PageID = wpSelectDir) then begin + if (not IsPortablePagePortableChoiceChecked()) then begin + if (IsUpgrade()) then begin + Result := True; + Exit; + end; + end; + end; +end; + +// ------------------------------------------------------------------------------------------------------------------ // +function PrepareToInstall(var NeedsRestart: Boolean): String; +var + iResultCode: Integer; +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); +end;