diff --git a/.github/scripts/setup_windows_audio.ps1 b/.github/scripts/setup_windows_audio.ps1 new file mode 100644 index 00000000..17747f0f --- /dev/null +++ b/.github/scripts/setup_windows_audio.ps1 @@ -0,0 +1,118 @@ +# Based on LABSN/sound-ci-helpers windows/setup_sound.ps1: +# https://github.com/LABSN/sound-ci-helpers/blob/20a2b9bbd21fd005d8fe89933901506dba84ea4e/windows/setup_sound.ps1 +# +# The original helper downloaded VB-CABLE Pack43, trusted the VB-Audio +# certificate, and installed vbMmeCable64_win7.inf with devcon. This local +# version keeps that behavior without cloning the helper repository at CI +# runtime. The vendored devcon.exe is from: +# https://github.com/LABSN/sound-ci-helpers/blob/20a2b9bbd21fd005d8fe89933901506dba84ea4e/windows/devcon.exe + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version 2.0 + +$driverUrl = "https://download.vb-audio.com/Download_CABLE/VBCABLE_Driver_Pack43.zip" +$expectedSha256 = "66fd0a4d9f4896ff41632b7e3d53892c085c4561f53e8ae8d0f0bc10eedd1cdd" +$devconExpectedSha256 = "97cff42f8c0fe4fbdf991273159516bf78090625a933c3983ebd6f62284e329a" +$hardwareId = "VBAudioVACWDM" +$devcon = Join-Path $PSScriptRoot "..\tools\windows\devcon.exe" +$runnerTemp = $env:RUNNER_TEMP +if ([string]::IsNullOrEmpty($runnerTemp)) { + $runnerTemp = [System.IO.Path]::GetTempPath() +} +$workDir = Join-Path $runnerTemp "vbcable" +$cacheDir = $env:VBCABLE_CACHE_DIR +if ([string]::IsNullOrEmpty($cacheDir)) { + $cacheDir = Join-Path $runnerTemp "vbcable-cache" +} +$zipName = "VBCABLE_Driver_Pack43.zip" +$cachedZipPath = Join-Path $cacheDir $zipName +$zipPath = Join-Path $workDir $zipName +$extractDir = Join-Path $workDir "driver" + +function Test-ExpectedHash { + param( + [Parameter(Mandatory = $true)] + [string] $Path, + [Parameter(Mandatory = $true)] + [string] $ExpectedSha256 + ) + + if (!(Test-Path $Path)) { + return $false + } + + $actualSha256 = (Get-FileHash -Algorithm SHA256 -Path $Path).Hash.ToLowerInvariant() + return $actualSha256 -eq $ExpectedSha256 +} + +function Invoke-Checked { + param( + [Parameter(Mandatory = $true)] + [string] $FilePath, + [Parameter(ValueFromRemainingArguments = $true)] + [string[]] $Arguments + ) + + Write-Host "Running: $FilePath $($Arguments -join ' ')" + & $FilePath @Arguments + if ($LASTEXITCODE -ne 0) { + throw "$FilePath failed with exit code $LASTEXITCODE." + } +} + +if (!(Test-ExpectedHash -Path $devcon -ExpectedSha256 $devconExpectedSha256)) { + $actualSha256 = if (Test-Path $devcon) { + (Get-FileHash -Algorithm SHA256 -Path $devcon).Hash.ToLowerInvariant() + } else { + "" + } + throw "Unexpected devcon.exe hash. Expected $devconExpectedSha256, got $actualSha256." +} + +New-Item -ItemType Directory -Force -Path $workDir, $cacheDir | Out-Null +if (Test-Path $extractDir) { + Remove-Item -Recurse -Force $extractDir +} + +if (Test-ExpectedHash -Path $cachedZipPath -ExpectedSha256 $expectedSha256) { + Write-Host "Using cached VB-CABLE package: $cachedZipPath" + Copy-Item -Force $cachedZipPath $zipPath +} else { + if (Test-Path $cachedZipPath) { + Write-Host "Ignoring cached VB-CABLE package with unexpected hash: $cachedZipPath" + Remove-Item -Force $cachedZipPath + } + + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + Write-Host "Downloading $driverUrl" + Invoke-WebRequest -Uri $driverUrl -OutFile $zipPath -MaximumRetryCount 3 -RetryIntervalSec 2 + + if (!(Test-ExpectedHash -Path $zipPath -ExpectedSha256 $expectedSha256)) { + $actualSha256 = (Get-FileHash -Algorithm SHA256 -Path $zipPath).Hash.ToLowerInvariant() + throw "Unexpected VB-CABLE package hash. Expected $expectedSha256, got $actualSha256." + } + + Copy-Item -Force $zipPath $cachedZipPath +} + +Expand-Archive -LiteralPath $zipPath -DestinationPath $extractDir + +$catalogPath = Join-Path $extractDir "vbaudio_cable64_win7.cat" +$signature = Get-AuthenticodeSignature -FilePath $catalogPath +if ($null -eq $signature.SignerCertificate) { + throw "Could not read signer certificate from $catalogPath." +} + +$certPath = Join-Path $workDir "vbcable.cer" +[System.IO.File]::WriteAllBytes( + $certPath, + $signature.SignerCertificate.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert) +) + +& certutil.exe -addstore -f "TrustedPublisher" $certPath +if ($LASTEXITCODE -ne 0) { + throw "certutil.exe failed with exit code $LASTEXITCODE." +} + +$infPath = Join-Path $extractDir "vbMmeCable64_win7.inf" +Invoke-Checked $devcon "install" $infPath $hardwareId diff --git a/.github/tools/windows/devcon.exe b/.github/tools/windows/devcon.exe new file mode 100644 index 00000000..ae9a3896 Binary files /dev/null and b/.github/tools/windows/devcon.exe differ diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3be7d15a..068018c9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,8 +25,18 @@ jobs: run: pulseaudio -D --start --exit-idle-time=-1 if: matrix.os == 'ubuntu-24.04' + - name: Cache VB-CABLE package (Windows) + uses: actions/cache@v4 + with: + path: ${{ runner.temp }}\vbcable-cache + key: vbcable-driver-pack43-${{ runner.os }}-66fd0a4d9f4896ff41632b7e3d53892c085c4561f53e8ae8d0f0bc10eedd1cdd + if: ${{ matrix.os == 'windows-2025' }} + - name: Install virtual audio devices (Windows) - run: git clone https://github.com/LABSN/sound-ci-helpers && powershell sound-ci-helpers/windows/setup_sound.ps1 + shell: pwsh + env: + VBCABLE_CACHE_DIR: ${{ runner.temp }}\vbcable-cache + run: .\.github\scripts\setup_windows_audio.ps1 if: ${{ matrix.os == 'windows-2025' }} - name: Allow microphone access to all apps (Windows) @@ -102,4 +112,3 @@ jobs: - name: Check format shell: bash run: cmake --build build --target clang-format-check -