From 814084c5360cdaec50b2a13d10c71554296b1e46 Mon Sep 17 00:00:00 2001 From: BurntToasters <61037367+BurntToasters@users.noreply.github.com> Date: Wed, 29 Jul 2026 00:20:34 -0700 Subject: [PATCH] B2 --- CHANGELOG.md | 14 ++++--- flatpak/run.rosie.dacx.yaml | 2 +- lib/services/self_update_service.dart | 38 ++++++++++++++----- linux/packaging/control.template | 2 +- package-lock.json | 4 +- package.json | 2 +- pubspec.yaml | 2 +- run.rosie.dacx.metainfo.xml | 5 +++ .../self_update_windows_validation_test.dart | 30 ++++++++++++++- 9 files changed, 78 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aeaacdf..799e631 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,11 @@ | Windows | macOS | Linux | | :--- | :--- | :--- | -| **MSI:** [x64](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.1/Dacx-Windows-x64.msi) | **[Universal DMG](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.1/Dacx-macOS.dmg)** | **AppImage:** [x64](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.1/Dacx-Linux-x86_64.AppImage) | -| | **[Universal ZIP](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.1/Dacx-macOS.zip)** | **DEB:** [x64](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.1/Dacx-Linux-amd64.deb) | -| | | **RPM:** [x64](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.1/Dacx-Linux-x86_64.rpm) | -| | | **Flatpak:** [x64](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.1/Dacx-Linux-x86_64.flatpak) | -| | | **TAR (Generic Linux):** [x64](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.1/Dacx-Linux-x86_64.tar.gz) | +| **MSI:** [x64](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.2/Dacx-Windows-x64.msi) | **[Universal DMG](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.2/Dacx-macOS.dmg)** | **AppImage:** [x64](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.2/Dacx-Linux-x86_64.AppImage) | +| | **[Universal ZIP](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.2/Dacx-macOS.zip)** | **DEB:** [x64](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.2/Dacx-Linux-amd64.deb) | +| | | **RPM:** [x64](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.2/Dacx-Linux-x86_64.rpm) | +| | | **Flatpak:** [x64](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.2/Dacx-Linux-x86_64.flatpak) | +| | | **TAR (Generic Linux):** [x64](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.2/Dacx-Linux-x86_64.tar.gz) | > [!IMPORTANT] > The `.asc` files are my normal GPG signatures which you can verify using my GPG Public Key: https://tuxedo.rosie.run/GPG/BurntToasters_0xF2FBC20F_public.asc. @@ -18,6 +18,10 @@ ### ℹ️ Enjoying Dacx? Consider [❤️ Supporting Me! ❤️](https://rosie.run/support) +## Changes in `v0.11.1-beta.2:` + +- **Fix - Windows updater Authenticode check:** Self-update failed with `Get-AuthenticodeSignature ... LiteralPath is null` because PowerShell `-Command ` does not populate `$args` from trailing process argv. The MSI path is now embedded (and single-quote-escaped) in the verification script. Sorry about that! This is why we have betas lol :P + ## Changes in `v0.11.1-beta.1:` - **Fix - Windows tray:** Minimize-to-tray left an empty notification-area slot because the tray API requires a multi-size `.ico`, not a PNG; tray and bundled assets now use `assets/icon/icon.ico` generated from `assets/icon/icon.png`. diff --git a/flatpak/run.rosie.dacx.yaml b/flatpak/run.rosie.dacx.yaml index 1e128b7..7aa71d9 100644 --- a/flatpak/run.rosie.dacx.yaml +++ b/flatpak/run.rosie.dacx.yaml @@ -1,4 +1,4 @@ -# x-version: 0.11.1-beta.1 +# x-version: 0.11.1-beta.2 app-id: run.rosie.dacx runtime: org.freedesktop.Platform runtime-version: "25.08" diff --git a/lib/services/self_update_service.dart b/lib/services/self_update_service.dart index 04e1704..3203799 100644 --- a/lib/services/self_update_service.dart +++ b/lib/services/self_update_service.dart @@ -872,6 +872,34 @@ class SelfUpdateService { return hash.toLowerCase(); } + /// Escapes a path for use inside a PowerShell single-quoted string. + @visibleForTesting + static String escapePowerShellSingleQuoted(String value) { + return value.replaceAll("'", "''"); + } + + /// Builds the `-Command` script that reads Authenticode for [msiPath]. + /// + /// The MSI path is embedded in the script. PowerShell's `-Command ` + /// form does **not** populate `$args` from trailing process argv (so + /// `-LiteralPath $args[0]` always sees null and breaks Windows self-update). + @visibleForTesting + static String buildWindowsAuthenticodeCommand(String msiPath) { + final pathLiteral = escapePowerShellSingleQuoted(msiPath); + return [ + r"$ErrorActionPreference = 'Stop';", + r"$securityModule = Join-Path $PSHOME 'Modules\Microsoft.PowerShell.Security\Microsoft.PowerShell.Security.psd1';", + r"Import-Module -Name $securityModule -Force -ErrorAction Stop;", + "\$sig = Get-AuthenticodeSignature -LiteralPath '$pathLiteral';", + r"if ($null -eq $sig) { Write-Output 'UnknownError|||Get-AuthenticodeSignature returned null'; exit 1 }", + r"$thumb = if ($sig.SignerCertificate) { $sig.SignerCertificate.Thumbprint } else { '' };", + r"$publisher = if ($sig.SignerCertificate) { $sig.SignerCertificate.GetNameInfo([System.Security.Cryptography.X509Certificates.X509NameType]::SimpleName, $false) } else { '' };", + r"$status = if ($null -ne $sig.Status) { $sig.Status.ToString() } else { 'UnknownError' };", + r"$message = if ($null -ne $sig.StatusMessage) { $sig.StatusMessage } else { '' };", + r"Write-Output ($status + '|' + $thumb + '|publisher:' + $publisher + '|' + $message)", + ].join(' '); + } + Future _validateWindowsInstallerSignature(File file) async { final expected = normalizeCertificateThumbprint( _expectedWindowsSignerThumbprintOverride ?? @@ -889,21 +917,13 @@ class SelfUpdateService { ); } - final authenticodeCommand = [ - r"$securityModule = Join-Path $PSHOME 'Modules\Microsoft.PowerShell.Security\Microsoft.PowerShell.Security.psd1';", - r"Import-Module -Name $securityModule -Force -ErrorAction Stop;", - r"$sig = Get-AuthenticodeSignature -LiteralPath $args[0];", - r"$thumb = if ($sig.SignerCertificate) { $sig.SignerCertificate.Thumbprint } else { '' };", - r"$publisher = if ($sig.SignerCertificate) { $sig.SignerCertificate.GetNameInfo([System.Security.Cryptography.X509Certificates.X509NameType]::SimpleName, $false) } else { '' };", - r"Write-Output ($sig.Status.ToString() + '|' + $thumb + '|publisher:' + $publisher + '|' + $sig.StatusMessage)", - ].join(' '); + final authenticodeCommand = buildWindowsAuthenticodeCommand(file.path); final result = await _processRun(WindowsSystemPaths.powershell(), [ '-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', authenticodeCommand, - file.path, ]); if (result.exitCode != 0) { return SelfUpdateResult( diff --git a/linux/packaging/control.template b/linux/packaging/control.template index 9fe414c..bcf572d 100644 --- a/linux/packaging/control.template +++ b/linux/packaging/control.template @@ -1,5 +1,5 @@ Package: dacx -Version: 0.11.1~beta.1 +Version: 0.11.1~beta.2 Section: sound Priority: optional Architecture: amd64 diff --git a/package-lock.json b/package-lock.json index d11a4b6..8a3c380 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "dacx", - "version": "0.11.1-beta.1", + "version": "0.11.1-beta.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "dacx", - "version": "0.11.1-beta.1", + "version": "0.11.1-beta.2", "license": "GPL-3.0-only", "devDependencies": { "cross-spawn": "^7.0.6", diff --git a/package.json b/package.json index 3844f6c..e1c7daa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dacx", - "version": "0.11.1-beta.1", + "version": "0.11.1-beta.2", "private": true, "description": "Fast, lightweight cross-platform music and video player for Windows, macOS, and Linux.", "license": "GPL-3.0-only", diff --git a/pubspec.yaml b/pubspec.yaml index 19ca221..8b8a8da 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: dacx description: "Quick, lightweight cross-platform media player." publish_to: 'none' -version: 0.11.1-beta.1+1101 +version: 0.11.1-beta.2+1101 environment: sdk: ^3.10.7 diff --git a/run.rosie.dacx.metainfo.xml b/run.rosie.dacx.metainfo.xml index 40f6234..5095307 100644 --- a/run.rosie.dacx.metainfo.xml +++ b/run.rosie.dacx.metainfo.xml @@ -87,6 +87,11 @@ application/pls+xml + + +

Fix Windows self-update Authenticode verification (MSI path was not passed into PowerShell correctly).

+
+

Fix Windows system tray icon (multi-size ICO). macOS menu bar tray template icon. Unified Windows icon.ico from icon.png. FVM setup repairs SDK and regenerates l10n.

diff --git a/test/services/self_update_windows_validation_test.dart b/test/services/self_update_windows_validation_test.dart index 1134073..29a53b2 100644 --- a/test/services/self_update_windows_validation_test.dart +++ b/test/services/self_update_windows_validation_test.dart @@ -284,12 +284,18 @@ void main() { ); final result = await svc.validateWindowsInstallerSignatureForTesting(msi); expect(result.outcome, SelfUpdateOutcome.spawned); + final joined = invokedArguments?.join(' ') ?? ''; expect( - invokedArguments?.join(' '), + joined, contains( r'Modules\Microsoft.PowerShell.Security\Microsoft.PowerShell.Security.psd1', ), ); + // Path must be embedded in -Command; trailing argv is not $args for + // PowerShell -Command and previously made LiteralPath null. + expect(joined, contains(msi.path)); + expect(joined, isNot(contains(r'$args[0]'))); + expect(invokedArguments, isNot(contains(msi.path))); }); test('returns signatureInvalid when PowerShell exits non-zero', () async { @@ -305,4 +311,26 @@ void main() { expect(result.message, contains('access denied')); }); }); + + group('SelfUpdateService.buildWindowsAuthenticodeCommand', () { + test('embeds MSI path and escapes single quotes', () { + final command = SelfUpdateService.buildWindowsAuthenticodeCommand( + r"C:\Users\O'Brien\AppData\Local\Dacx\updates\Dacx.msi", + ); + expect( + command, + contains( + r"Get-AuthenticodeSignature -LiteralPath 'C:\Users\O''Brien\AppData\Local\Dacx\updates\Dacx.msi'", + ), + ); + expect(command, isNot(contains(r'$args[0]'))); + }); + + test('escapePowerShellSingleQuoted doubles apostrophes', () { + expect( + SelfUpdateService.escapePowerShellSingleQuoted("a'b'c"), + "a''b''c", + ); + }); + }); }