fix(windows-installer): build pf-vdisplay from source in CI; ASCII scripts; upgrade-safe web console
windows-drivers / probe-and-proto (push) Successful in 24s
apple / swift (push) Successful in 1m4s
windows-drivers / driver-build (push) Successful in 1m8s
android / android (push) Successful in 4m4s
ci / rust (push) Successful in 4m39s
ci / web (push) Successful in 50s
ci / docs-site (push) Successful in 53s
apple / screenshots (push) Successful in 5m10s
windows-host / package (push) Failing after 5m35s
deb / build-publish (push) Successful in 2m29s
decky / build-publish (push) Successful in 13s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 5s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 4s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 3s
ci / bench (push) Successful in 4m42s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 8m57s
docker / deploy-docs (push) Successful in 17s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 8m46s
windows-drivers / probe-and-proto (push) Successful in 24s
apple / swift (push) Successful in 1m4s
windows-drivers / driver-build (push) Successful in 1m8s
android / android (push) Successful in 4m4s
ci / rust (push) Successful in 4m39s
ci / web (push) Successful in 50s
ci / docs-site (push) Successful in 53s
apple / screenshots (push) Successful in 5m10s
windows-host / package (push) Failing after 5m35s
deb / build-publish (push) Successful in 2m29s
decky / build-publish (push) Successful in 13s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 5s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 4s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 3s
ci / bench (push) Successful in 4m42s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 8m57s
docker / deploy-docs (push) Successful in 17s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 8m46s
The pf-vdisplay virtual-display driver shipped as a checked-in PREBUILT binary
that went stale - two field failures on a fresh install (live-repro'd on a
German-locale Dell laptop):
* Bug A (every box): a repo-wide rename edited the vendored pf_vdisplay.inf
but never re-signed pf_vdisplay.cat, so the catalog stopped covering the INF
-> `pnputil /add-driver` fails SPAPI_E_FILE_HASH_NOT_IN_CATALOG -> driver
never installs -> every session dies "pf-vdisplay driver interface not
found".
* the prebuilt binary also predated IOCTL_SET_RENDER_ADAPTER (added to the
driver source after the vendor freeze) that the host needs to pin the IDD
render GPU on hybrid/Optimus boxes.
Fix: build the driver FROM SOURCE every release (build-pf-vdisplay.ps1, wired
into pack-host-installer.ps1) so .dll/.inf/.cat are always in lockstep and
current driver features ship. The runner's clang 22 made the driver's pinned
bindgen 0.71 emit opaque structs (157 layout-assert errors), so bump the
vendored wdk-sys/wdk-build bindgen 0.71 -> 0.72 (+ lock). The build self-signs
the driver per build (installer trusts the bundled .cer); a stable
DRIVER_CERT_PFX_B64 secret can override.
* Bug B (non-English boxes): the installer runs install-pf-vdisplay.ps1 etc.
via powershell.exe (5.1), which reads a BOM-less script in the ANSI codepage
- an em-dash's trailing 0x94 byte becomes a curly quote on German
Windows-1252 and the script aborts "unterminated string", so the driver
never installed (the gamepad script survived only because it was already
ASCII). Scrub every installer-run .ps1/.cmd to ASCII + add a CI gate that
fails on any non-ASCII so it can't regress.
* Bug C (upgrades): nothing stopped the OLD web console before re-registering
its task, so a stale server kept :3000 (the new one restart-looped on
EADDRINUSE) and served a broken old bundle (500 on /login). Stop + reap it
(runtime-agnostic, by the :3000 listener owner) in web-setup.ps1 and in the
.iss before the file copy + on uninstall.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Build + sign the pf-vdisplay UMDF IddCx virtual-display driver FROM SOURCE, in CI, and stage it for the
|
||||
host installer. This REPLACES the old vendored-prebuilt-binary model (packaging/windows/pf-vdisplay/) -
|
||||
the binary went stale (frozen mid-June while the driver source kept moving), which silently shipped two
|
||||
field bugs: (1) the catalog no longer covered the edited INF (pnputil SPAPI_E_FILE_HASH_NOT_IN_CATALOG on
|
||||
every box), and (2) the binary predated IOCTL_SET_RENDER_ADAPTER that the host needs to pin the IDD render
|
||||
GPU on hybrid/Optimus boxes. Building every release from source keeps the .dll/.inf/.cat in lockstep and
|
||||
ships current driver features.
|
||||
|
||||
.DESCRIPTION
|
||||
Mirrors packaging/windows/drivers/deploy-dev.ps1 but for CI (release build, output to -Out, cert from a
|
||||
secret OR a fresh self-signed). Steps: cargo build (the wdk-sys/windows-drivers-rs driver workspace) ->
|
||||
CLEAR the FORCE_INTEGRITY PE bit (wdk-build links /INTEGRITYCHECK, which a non-EV cert can't satisfy) ->
|
||||
sign the .dll -> stampinf a strictly-increasing DriverVer into the INF -> Inf2Cat the catalog -> sign the
|
||||
catalog -> export the public .cer. Output (-Out): pf_vdisplay.{dll,inf,cat} + punktfunk-driver.cer.
|
||||
|
||||
Requires the WDK build env: cargo + the x64 MSVC toolset, an LLVM compatible with the driver's bindgen
|
||||
(>= 0.72 supports current clang), LIBCLANG_PATH, and the Windows 10/11 WDK (the runner has these). Sets
|
||||
Version_Number for wdk-build if the caller didn't.
|
||||
|
||||
.EXAMPLE
|
||||
pwsh -File build-pf-vdisplay.ps1 -Out C:\t\pfvd -DriverVer 9.9.0626.1612
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$DriversDir = (Join-Path $PSScriptRoot 'drivers'),
|
||||
[Parameter(Mandatory = $true)][string]$Out,
|
||||
[string]$DriverVer, # default: 9.9.MMdd.HHmm (strictly-increasing)
|
||||
[string]$CertPfxB64 = $env:DRIVER_CERT_PFX_B64, # optional stable driver-signing cert (CI secret)
|
||||
[string]$CertPassword = $env:DRIVER_CERT_PASSWORD,
|
||||
[switch]$SkipBuild # reuse an existing target\...\release\pf_vdisplay.dll
|
||||
)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
$PSNativeCommandUseErrorActionPreference = $false
|
||||
|
||||
$DriversDir = (Resolve-Path $DriversDir).Path
|
||||
$inx = Join-Path $DriversDir 'pf-vdisplay\pf_vdisplay.inx'
|
||||
$clear = Join-Path $PSScriptRoot 'clear-force-integrity.ps1'
|
||||
if (-not (Test-Path $inx)) { throw "no pf_vdisplay.inx under $DriversDir" }
|
||||
|
||||
# --- WDK build env (wdk-build needs Version_Number; bindgen needs LIBCLANG_PATH) --------------
|
||||
if (-not $env:Version_Number) { $env:Version_Number = '10.0.26100.0' }
|
||||
if (-not $env:LIBCLANG_PATH -and (Test-Path 'C:\Program Files\LLVM\bin\libclang.dll')) {
|
||||
$env:LIBCLANG_PATH = 'C:\Program Files\LLVM\bin'
|
||||
}
|
||||
# Isolate the driver's CARGO_TARGET_DIR from the host's (CI sets a shared C:\t): the driver is a
|
||||
# SEPARATE workspace (own [workspace] + an explicit --target via .cargo/config), so give it its own
|
||||
# output tree both to avoid cross-workspace churn and to make the .dll path predictable here.
|
||||
$drvTarget = Join-Path (Split-Path -Parent $Out) 'pfvd-target'
|
||||
$dll = Join-Path $drvTarget 'x86_64-pc-windows-msvc\release\pf_vdisplay.dll'
|
||||
|
||||
# --- 1. build (release) -----------------------------------------------------------------------
|
||||
if (-not $SkipBuild) {
|
||||
Write-Host "==> cargo build --release (pf-vdisplay) in $DriversDir (target -> $drvTarget)"
|
||||
$prevTarget = $env:CARGO_TARGET_DIR
|
||||
$env:CARGO_TARGET_DIR = $drvTarget
|
||||
Push-Location $DriversDir
|
||||
& cargo build --release
|
||||
$rc = $LASTEXITCODE
|
||||
Pop-Location
|
||||
if ($prevTarget) { $env:CARGO_TARGET_DIR = $prevTarget } else { Remove-Item Env:\CARGO_TARGET_DIR -ErrorAction SilentlyContinue }
|
||||
if ($rc -ne 0) { throw "pf-vdisplay cargo build failed ($rc)" }
|
||||
}
|
||||
if (-not (Test-Path $dll)) { throw "driver not built: $dll" }
|
||||
|
||||
# --- 2. WDK sign tools ------------------------------------------------------------------------
|
||||
$kits = 'C:\Program Files (x86)\Windows Kits\10\bin'
|
||||
function Find-Tool([string]$name, [string]$arch) {
|
||||
(Get-ChildItem "$kits\*\$arch\$name" -ErrorAction SilentlyContinue | Sort-Object FullName | Select-Object -Last 1).FullName
|
||||
}
|
||||
$signtool = Find-Tool 'signtool.exe' 'x64'
|
||||
$stampinf = Find-Tool 'stampinf.exe' 'x64'
|
||||
$inf2cat = Find-Tool 'Inf2Cat.exe' 'x86'
|
||||
foreach ($t in @($signtool, $stampinf, $inf2cat)) {
|
||||
if (-not $t) { throw 'a WDK tool (signtool/stampinf/Inf2Cat) was not found - install the Windows 10/11 WDK.' }
|
||||
}
|
||||
|
||||
# --- 3. signing cert (supplied stable pfx OR fresh self-signed) -------------------------------
|
||||
$cleanupCert = $null
|
||||
if ($CertPfxB64) {
|
||||
Write-Host '==> signing with supplied driver cert (DRIVER_CERT_PFX_B64)'
|
||||
$pfx = Join-Path $Out '..\driver-signing.pfx'
|
||||
[IO.File]::WriteAllBytes($pfx, [Convert]::FromBase64String($CertPfxB64))
|
||||
$sec = if ($CertPassword) { ConvertTo-SecureString $CertPassword -AsPlainText -Force } else { $null }
|
||||
$signArgs = @('/f', $pfx); if ($CertPassword) { $signArgs += @('/p', $CertPassword) }
|
||||
$pubForCer = if ($sec) { Get-PfxCertificate -FilePath $pfx -Password $sec } else { Get-PfxCertificate -FilePath $pfx }
|
||||
}
|
||||
else {
|
||||
Write-Host '==> no DRIVER_CERT_PFX_B64 -> generating a fresh self-signed driver cert (the installer trusts the bundled .cer at install time)'
|
||||
$cleanupCert = New-SelfSignedCertificate -Type CodeSigningCert -Subject 'CN=punktfunk-driver' `
|
||||
-CertStoreLocation Cert:\CurrentUser\My -KeyExportPolicy Exportable -NotAfter (Get-Date).AddYears(10)
|
||||
$signArgs = @('/sha1', $cleanupCert.Thumbprint)
|
||||
$pubForCer = $cleanupCert
|
||||
}
|
||||
|
||||
# --- 4. stage + clear FORCE_INTEGRITY + sign + cat --------------------------------------------
|
||||
if (Test-Path $Out) { Remove-Item $Out -Recurse -Force }
|
||||
New-Item -ItemType Directory -Force -Path $Out | Out-Null
|
||||
$sDll = Join-Path $Out 'pf_vdisplay.dll'
|
||||
$sInf = Join-Path $Out 'pf_vdisplay.inf'
|
||||
$sCat = Join-Path $Out 'pf_vdisplay.cat'
|
||||
$sCer = Join-Path $Out 'punktfunk-driver.cer'
|
||||
Copy-Item $dll $sDll -Force
|
||||
Copy-Item $inx $sInf -Force # stampinf rewrites this copy in place
|
||||
|
||||
# Clear FORCE_INTEGRITY BEFORE signing (it edits the PE, invalidating any signature).
|
||||
& powershell -NoProfile -ExecutionPolicy Bypass -File $clear -Path $sDll | Out-Null
|
||||
|
||||
if (-not $DriverVer) { $now = Get-Date; $DriverVer = '9.9.{0}.{1}' -f $now.ToString('MMdd'), $now.ToString('HHmm') }
|
||||
|
||||
& $signtool sign /fd SHA256 @signArgs $sDll | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) { throw "signtool sign (dll) failed ($LASTEXITCODE)" }
|
||||
& $stampinf -f $sInf -d '*' -a 'amd64' -u '2.15.0' -v $DriverVer | Out-Null
|
||||
& $inf2cat /driver:$Out /os:10_X64 /uselocaltime | Out-Null
|
||||
if (-not (Test-Path $sCat)) { throw "Inf2Cat did not produce $sCat" }
|
||||
& $signtool sign /fd SHA256 @signArgs $sCat | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) { throw "signtool sign (cat) failed ($LASTEXITCODE)" }
|
||||
Export-Certificate -Cert $pubForCer -FilePath $sCer | Out-Null
|
||||
if ($cleanupCert) { Remove-Item "Cert:\CurrentUser\My\$($cleanupCert.Thumbprint)" -Force -ErrorAction SilentlyContinue }
|
||||
|
||||
# --- 5. guard: assert the freshly-built catalog covers the inf + dll ---------------------------
|
||||
# (Built-from-source can't drift, but this catches a botched stampinf/Inf2Cat ordering before it ships.)
|
||||
$cat = Test-FileCatalog -CatalogFilePath $sCat -Path $Out -FilesToSkip 'pf_vdisplay.cat', 'punktfunk-driver.cer' -Detailed -ErrorAction SilentlyContinue
|
||||
if ($cat) {
|
||||
$covered = @($cat.CatalogItems.Keys)
|
||||
foreach ($need in @('pf_vdisplay.inf', 'pf_vdisplay.dll')) {
|
||||
if (-not ($covered | Where-Object { $_ -like "*$need" })) {
|
||||
throw "catalog coverage guard: $need is NOT in $sCat (stampinf/Inf2Cat ordering bug?)"
|
||||
}
|
||||
}
|
||||
Write-Host " catalog covers pf_vdisplay.inf + pf_vdisplay.dll (status=$($cat.Status))"
|
||||
}
|
||||
|
||||
Write-Host "==> built + signed pf-vdisplay DriverVer=$DriverVer -> $Out"
|
||||
Get-ChildItem $Out -File | ForEach-Object { " $($_.Name) ($($_.Length) bytes)" }
|
||||
@@ -1,10 +1,10 @@
|
||||
# Clear the PE FORCE_INTEGRITY bit (IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY = 0x0080) from a driver DLL.
|
||||
#
|
||||
# windows-drivers-rs / wdk-build links UMDF drivers with /INTEGRITYCHECK (sets the bit) UNCONDITIONALLY
|
||||
# (wdk-build configure_binary_build → cargo::rustc-cdylib-link-arg=/INTEGRITYCHECK; no opt-out). With the
|
||||
# (wdk-build configure_binary_build -> cargo::rustc-cdylib-link-arg=/INTEGRITYCHECK; no opt-out). With the
|
||||
# bit set, Windows Code Integrity refuses to load a binary whose signature doesn't chain to a Microsoft
|
||||
# root (errors 3004/3089) — so a SELF-SIGNED driver won't load. Clearing the bit (then re-signing) lets a
|
||||
# self-signed driver load under Secure Boot — the same recipe the punktfunk gamepad drivers use, here as a
|
||||
# root (errors 3004/3089) - so a SELF-SIGNED driver won't load. Clearing the bit (then re-signing) lets a
|
||||
# self-signed driver load under Secure Boot - the same recipe the punktfunk gamepad drivers use, here as a
|
||||
# deterministic, idempotent, reusable step instead of a hand-run patch.
|
||||
#
|
||||
# Order in the packaging flow: cargo build -> THIS -> signtool (sign .dll) -> Inf2Cat (.cat) -> sign .cat.
|
||||
@@ -28,7 +28,7 @@ $FORCE_INTEGRITY = 0x0080
|
||||
$dllchar = [BitConverter]::ToUInt16($b, $off)
|
||||
|
||||
if (($dllchar -band $FORCE_INTEGRITY) -eq 0) {
|
||||
Write-Host ("clear-force-integrity: already clear (DllCharacteristics=0x{0:X4}) — no change: $Path" -f $dllchar)
|
||||
Write-Host ("clear-force-integrity: already clear (DllCharacteristics=0x{0:X4}) - no change: $Path" -f $dllchar)
|
||||
} else {
|
||||
$new = [uint16]($dllchar -band (-bnot $FORCE_INTEGRITY))
|
||||
[BitConverter]::GetBytes($new).CopyTo($b, $off)
|
||||
@@ -36,7 +36,7 @@ if (($dllchar -band $FORCE_INTEGRITY) -eq 0) {
|
||||
Write-Host ("clear-force-integrity: cleared FORCE_INTEGRITY 0x{0:X4} -> 0x{1:X4} in $Path" -f $dllchar, $new)
|
||||
}
|
||||
|
||||
# Verify on disk (re-read) — the assertion.
|
||||
# Verify on disk (re-read) - the assertion.
|
||||
$v = [BitConverter]::ToUInt16([IO.File]::ReadAllBytes($Path), $off)
|
||||
if (($v -band $FORCE_INTEGRITY) -ne 0) { throw ("FORCE_INTEGRITY still set after clear (0x{0:X4})" -f $v) }
|
||||
Write-Host ("clear-force-integrity: verified DllCharacteristics=0x{0:X4}, FORCE_INTEGRITY clear." -f $v)
|
||||
|
||||
Generated
+9
-9
@@ -69,9 +69,9 @@ checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.71.1"
|
||||
version = "0.72.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3"
|
||||
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cexpr",
|
||||
@@ -394,6 +394,13 @@ version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "pf-driver-proto"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pf-vdisplay"
|
||||
version = "0.0.1"
|
||||
@@ -407,13 +414,6 @@ dependencies = [
|
||||
"windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pf-driver-proto"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.17"
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ path = "src/lib.rs"
|
||||
version = "1.0.97"
|
||||
|
||||
[dependencies.bindgen]
|
||||
version = "0.71.0"
|
||||
version = "0.72"
|
||||
|
||||
[dependencies.camino]
|
||||
version = "1.1.9"
|
||||
|
||||
+1
-1
@@ -67,7 +67,7 @@ version = "=0.5.1"
|
||||
version = "1.0.97"
|
||||
|
||||
[build-dependencies.bindgen]
|
||||
version = "0.71.0"
|
||||
version = "0.72"
|
||||
|
||||
[build-dependencies.cargo_metadata]
|
||||
version = "0.19.2"
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Install the bundled pf-vdisplay (punktfunk) virtual-display driver — our all-Rust IddCx replacement
|
||||
for SudoVDA. Runs ELEVATED at setup time (invoked from the installer's [Run] section). Best-effort:
|
||||
warns and exits 0 on any failure — the host degrades to a physical display without a virtual display,
|
||||
so a driver hiccup must never abort the whole install.
|
||||
Install the bundled pf-vdisplay (punktfunk) virtual-display driver - our own all-Rust UMDF IddCx
|
||||
indirect-display driver, built from source per release (packaging/windows/build-pf-vdisplay.ps1).
|
||||
Runs ELEVATED at setup time (invoked from the installer's [Run] section). Best-effort: warns and exits
|
||||
0 on any failure - the host degrades to a physical display without a virtual display, so a driver
|
||||
hiccup must never abort the whole install.
|
||||
|
||||
.DESCRIPTION
|
||||
-Dir holds the staged payload (pf_vdisplay.inf/.cat/.dll + signing .cer + nefconc.exe). Steps:
|
||||
1. Trust the self-signed driver cert (machine Root + TrustedPublisher) so PnP installs it silently
|
||||
(the same punktfunk-ds-test cert the gamepad drivers ship).
|
||||
2. Create the ROOT device node IF ABSENT (gated — a blind re-create spawns a phantom duplicate, and
|
||||
the host's open_device() binds interface index 0; crates/punktfunk-host/src/vdisplay/sudovda.rs).
|
||||
ALWAYS via nefconc (a clean ROOT\DISPLAY node) — NEVER devgen, which makes persistent SWD\DEVGEN
|
||||
(the punktfunk-driver cert the build signs the driver + catalog with).
|
||||
2. Create the ROOT device node IF ABSENT (gated - a blind re-create spawns a phantom duplicate, and
|
||||
the host's open_device() binds interface index 0; crates/punktfunk-host/src/vdisplay/windows/pf_vdisplay.rs).
|
||||
ALWAYS via nefconc (a clean ROOT\DISPLAY node) - NEVER devgen, which makes persistent SWD\DEVGEN
|
||||
software devices that survive reboot + registry deletion and resurrect on every driver install.
|
||||
3. Stage + bind the driver (pnputil /add-driver /install — modern, in-box, idempotent).
|
||||
3. Stage + bind the driver (pnputil /add-driver /install - modern, in-box, idempotent).
|
||||
|
||||
Class/ClassGuid are read from the .inf so they always match the shipped driver.
|
||||
|
||||
@@ -52,11 +53,11 @@ if ($cer) {
|
||||
certutil.exe -addstore -f Root "$($cer.FullName)" | Out-Null
|
||||
certutil.exe -addstore -f TrustedPublisher "$($cer.FullName)" | Out-Null
|
||||
}
|
||||
else { Write-Warning "no .cer in $Dir — driver may not install silently (untrusted publisher)" }
|
||||
else { Write-Warning "no .cer in $Dir - driver may not install silently (untrusted publisher)" }
|
||||
|
||||
# 2) Create the root device node only if it isn't already there. nefconc, NEVER devgen.
|
||||
if (Test-PfVdisplayPresent) {
|
||||
Write-Host "pf-vdisplay device node already present — leaving it as-is."
|
||||
Write-Host "pf-vdisplay device node already present - leaving it as-is."
|
||||
}
|
||||
elseif ($nef) {
|
||||
$infText = Get-Content -Raw $inf.FullName
|
||||
@@ -69,7 +70,7 @@ elseif ($nef) {
|
||||
Write-Warning "nefconc --create-device-node returned $LASTEXITCODE"
|
||||
}
|
||||
}
|
||||
else { Write-Warning "nefconc.exe not found in $Dir — cannot create the pf-vdisplay device node." }
|
||||
else { Write-Warning "nefconc.exe not found in $Dir - cannot create the pf-vdisplay device node." }
|
||||
|
||||
# 3) Stage + bind the driver (idempotent; re-staging the same .inf is harmless).
|
||||
Write-Host "==> pnputil /add-driver $($inf.Name) /install"
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
.DESCRIPTION
|
||||
From a release `cargo build -p punktfunk-host --features nvenc` output (the exe), this:
|
||||
1. resolves a code-signing cert (supplied stable .pfx from CI secrets OR an ephemeral self-signed
|
||||
CN=unom — same scheme as the client's pack-msix.ps1) and exports the public .cer,
|
||||
CN=unom - same scheme as the client's pack-msix.ps1) and exports the public .cer,
|
||||
2. signs the inner punktfunk-host.exe,
|
||||
3. stages the pf-vdisplay virtual-display driver bundle (unless -NoDriver),
|
||||
4. runs ISCC to build punktfunk-host-setup-<ver>.exe,
|
||||
@@ -52,7 +52,7 @@ function Find-Iscc {
|
||||
}
|
||||
$c = Get-Command iscc -ErrorAction SilentlyContinue
|
||||
if ($c) { return $c.Source }
|
||||
throw "ISCC.exe (Inno Setup 6, any 6.x) not found — install it (choco install innosetup -y)."
|
||||
throw "ISCC.exe (Inno Setup 6, any 6.x) not found - install it (choco install innosetup -y)."
|
||||
}
|
||||
function Find-SdkTool([string]$name) {
|
||||
$root = 'C:\Program Files (x86)\Windows Kits\10\bin'
|
||||
@@ -60,7 +60,7 @@ function Find-SdkTool([string]$name) {
|
||||
Where-Object { $_.FullName -match '\\(10\.0\.\d+\.\d+)\\x64\\' } |
|
||||
Sort-Object { [version]([regex]::Match($_.FullName, '\\(10\.0\.\d+\.\d+)\\x64\\').Groups[1].Value) } |
|
||||
Select-Object -Last 1
|
||||
if (-not $hit) { throw "$name not found under $root — install the Windows 10/11 SDK." }
|
||||
if (-not $hit) { throw "$name not found under $root - install the Windows 10/11 SDK." }
|
||||
$hit.FullName
|
||||
}
|
||||
$iscc = Find-Iscc
|
||||
@@ -103,7 +103,7 @@ function Sign-File([string]$Path) {
|
||||
if ($PfxPassword) { $signArgs += @('/p', $PfxPassword) }
|
||||
& $signtool ($signArgs + @('/tr', 'http://timestamp.digicert.com', '/td', 'SHA256', $Path))
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Warning "timestamped sign failed for $Path — retrying without a timestamp"
|
||||
Write-Warning "timestamped sign failed for $Path - retrying without a timestamp"
|
||||
& $signtool ($signArgs + @($Path))
|
||||
if ($LASTEXITCODE -ne 0) { throw "signtool sign failed for $Path ($LASTEXITCODE)" }
|
||||
}
|
||||
@@ -140,12 +140,18 @@ $defines = @(
|
||||
"/DReadme=$readme"
|
||||
)
|
||||
|
||||
# --- stage the pf-vdisplay virtual-display driver bundle --------------------------------------
|
||||
# pf-vdisplay is our all-Rust IddCx driver (packaging/windows/drivers/), vendored signed under
|
||||
# packaging/windows/pf-vdisplay/. It replaced the vendored SudoVDA C++ driver.
|
||||
# --- build (from source) + stage the pf-vdisplay virtual-display driver -----------------------
|
||||
# pf-vdisplay is our all-Rust IddCx driver (packaging/windows/drivers/). It is now BUILT FROM SOURCE
|
||||
# every release (build-pf-vdisplay.ps1) instead of shipping a checked-in prebuilt binary: the vendored
|
||||
# binary went stale (its .cat stopped covering an edited .inf -> pnputil SPAPI_E_FILE_HASH_NOT_IN_CATALOG
|
||||
# on every box, and it predated IOCTL_SET_RENDER_ADAPTER the host needs on hybrid/Optimus GPUs). Building
|
||||
# here keeps the .dll/.inf/.cat in lockstep + ships current driver features. stage-pf-vdisplay.ps1 then
|
||||
# adds the fetched nefcon device tool. (Needs the WDK build env; -NoDriver skips it for a WDK-less pack.)
|
||||
if (-not $NoDriver) {
|
||||
$built = Join-Path $OutDir 'pfvd-built'
|
||||
& (Join-Path $here 'build-pf-vdisplay.ps1') -Out $built
|
||||
$stage = Join-Path $OutDir 'stage'
|
||||
& (Join-Path $here 'stage-pf-vdisplay.ps1') -OutDir $stage
|
||||
& (Join-Path $here 'stage-pf-vdisplay.ps1') -OutDir $stage -VendorDir $built
|
||||
Copy-Item (Join-Path $here 'install-pf-vdisplay.ps1') (Join-Path $stage 'install-pf-vdisplay.ps1') -Force
|
||||
$defines += "/DStageDir=$stage"
|
||||
}
|
||||
@@ -165,12 +171,12 @@ if (-not $NoDriver) {
|
||||
$defines += "/DGamepadStageDir=$gpStage"
|
||||
Write-Host "==> staged vendored gamepad UMDF drivers from $gpVendor"
|
||||
}
|
||||
else { Write-Warning "no vendored gamepad drivers under $gpVendor — installer built WITHOUT them" }
|
||||
else { Write-Warning "no vendored gamepad drivers under $gpVendor - installer built WITHOUT them" }
|
||||
}
|
||||
|
||||
# --- stage the FFmpeg shared DLLs (AMD/Intel AMF/QSV build) ------------------------------------
|
||||
# A host built with --features amf-qsv link-imports avcodec/avutil/swscale/... so the shared DLLs
|
||||
# MUST sit next to the exe (it won't start otherwise). Bundle them from $FfmpegDir\bin — the same
|
||||
# MUST sit next to the exe (it won't start otherwise). Bundle them from $FfmpegDir\bin - the same
|
||||
# BtbN gpl-shared tree the build linked against. A nvenc/software-only build doesn't import them, so
|
||||
# this is a harmless extra there; skipped entirely when $FfmpegDir is unset.
|
||||
$ffmpegBinSrc = if ($FfmpegDir) { Join-Path $FfmpegDir 'bin' } else { $null }
|
||||
@@ -190,7 +196,7 @@ else { Write-Host "no FFMPEG_DIR\bin -> installer built WITHOUT FFmpeg DLLs (nve
|
||||
# The console runs as the PunktfunkWeb scheduled task (`bun {app}\web\.output\server\index.mjs`),
|
||||
# auto-wired to the host's loopback mgmt API. Stage everything ISCC reads into $OutDir (the
|
||||
# non-WOW64-redirected C:\t area, same reason as the .iss/host.env staging above). The .output is
|
||||
# self-contained (Nitro noExternals — deps bundled + tree-shaken, no node_modules), so bun runs it
|
||||
# self-contained (Nitro noExternals - deps bundled + tree-shaken, no node_modules), so bun runs it
|
||||
# directly; omitted when -WebDir/-BunExe are unset (host-only installer, e.g. a local debug pack).
|
||||
if ($WebDir -and (Test-Path $WebDir) -and $BunExe -and (Test-Path $BunExe)) {
|
||||
$webStage = Join-Path $OutDir 'web'
|
||||
@@ -213,7 +219,7 @@ else { Write-Host "no -WebDir/-BunExe -> installer built WITHOUT the web console
|
||||
|
||||
# --- build + stage the HDR Vulkan layer (pf-vkhdr-layer) --------------------------------------
|
||||
# A tiny always-on Vulkan implicit layer (cdylib) that advertises HDR10/scRGB surface formats on the
|
||||
# virtual display so Vulkan games (Doom: The Dark Ages, etc.) can enable HDR while streaming — the
|
||||
# virtual display so Vulkan games (Doom: The Dark Ages, etc.) can enable HDR while streaming - the
|
||||
# NVIDIA/AMD ICDs hide HDR formats on an indirect display even though they accept+present a forced HDR
|
||||
# swapchain there. Self-gated on the display's actual advanced-color state, so it's a no-op on SDR.
|
||||
# Standalone crate (own [workspace]); built here and registered by the installer. Skipped if cargo
|
||||
@@ -239,7 +245,7 @@ if (Test-Path (Join-Path $layerSrc 'Cargo.toml')) {
|
||||
$defines += "/DVkLayerDir=$layerStage"
|
||||
Write-Host "==> staged pf-vkhdr-layer -> $layerStage"
|
||||
}
|
||||
else { Write-Warning "pf-vkhdr-layer build failed ($layerExit) — installer built WITHOUT the HDR Vulkan layer" }
|
||||
else { Write-Warning "pf-vkhdr-layer build failed ($layerExit) - installer built WITHOUT the HDR Vulkan layer" }
|
||||
}
|
||||
else { Write-Host "no pf-vkhdr-layer crate -> installer built WITHOUT the HDR Vulkan layer" }
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
#ifndef Readme
|
||||
#define Readme "README.md"
|
||||
#endif
|
||||
; The web console launcher (the PunktfunkWeb task action) + its post-install provisioner — committed
|
||||
; The web console launcher (the PunktfunkWeb task action) + its post-install provisioner - committed
|
||||
; scripts staged next to the .iss by pack-host-installer.ps1 (absolute paths passed in).
|
||||
#ifndef WebRunCmd
|
||||
#define WebRunCmd "..\..\scripts\windows\web-run.cmd"
|
||||
@@ -44,19 +44,19 @@
|
||||
#ifdef GamepadStageDir
|
||||
#define WithGamepad
|
||||
#endif
|
||||
; FfmpegBin (a dir of FFmpeg shared DLLs) is optional — present when the host is built with
|
||||
; FfmpegBin (a dir of FFmpeg shared DLLs) is optional - present when the host is built with
|
||||
; --features amf-qsv (the AMD/Intel AMF/QSV encode backend link-imports the FFmpeg libs).
|
||||
#ifdef FfmpegBin
|
||||
#define WithFfmpeg
|
||||
#endif
|
||||
; WebDir (the built web .output tree) + BunExe (a portable bun.exe) are passed together by
|
||||
; pack-host-installer.ps1 to bundle the management console. Both required → WithWeb.
|
||||
; pack-host-installer.ps1 to bundle the management console. Both required -> WithWeb.
|
||||
#ifdef WebDir
|
||||
#ifdef BunExe
|
||||
#define WithWeb
|
||||
#endif
|
||||
#endif
|
||||
; VkLayerDir (the staged pf-vkhdr-layer: pf_vkhdr_layer.dll + .json) is optional — present when the
|
||||
; VkLayerDir (the staged pf-vkhdr-layer: pf_vkhdr_layer.dll + .json) is optional - present when the
|
||||
; HDR Vulkan layer was built. It lets Vulkan games (Doom: The Dark Ages, etc.) enable HDR over the
|
||||
; virtual display (the ICD won't advertise HDR there; the layer injects the surface formats, self-
|
||||
; gated on the display's actual HDR state).
|
||||
@@ -94,7 +94,7 @@ Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
Name: "installdriver"; Description: "Install the pf-vdisplay virtual display driver (required for native-resolution streaming)"
|
||||
#endif
|
||||
#ifdef WithGamepad
|
||||
Name: "installgamepad"; Description: "Install the virtual gamepad drivers (DualSense / DualShock 4 / Xbox 360 — no ViGEmBus needed)"
|
||||
Name: "installgamepad"; Description: "Install the virtual gamepad drivers (DualSense / DualShock 4 / Xbox 360 - no ViGEmBus needed)"
|
||||
#endif
|
||||
#ifdef WithVkLayer
|
||||
Name: "installhdrlayer"; Description: "Install the HDR Vulkan layer (lets Vulkan games like Doom use HDR on the virtual display)"
|
||||
@@ -106,15 +106,15 @@ Source: "{#BinDir}\punktfunk-host.exe"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "{#HostEnv}"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "{#Readme}"; DestDir: "{app}"; DestName: "README.txt"; Flags: ignoreversion
|
||||
#ifdef WithFfmpeg
|
||||
; FFmpeg shared DLLs (avcodec/avutil/swscale/...) laid down next to the exe — the AMD/Intel
|
||||
; FFmpeg shared DLLs (avcodec/avutil/swscale/...) laid down next to the exe - the AMD/Intel
|
||||
; (AMF/QSV) encode backend link-imports them, so the exe won't start without them. NVENC/software-
|
||||
; only builds simply omit this block.
|
||||
Source: "{#FfmpegBin}\*.dll"; DestDir: "{app}"; Flags: ignoreversion
|
||||
#endif
|
||||
#ifdef WithWeb
|
||||
; The web management console: the self-contained Nitro SSR bundle (.output = server + public; deps
|
||||
; bundled in, no node_modules) → {app}\web\.output, a portable bun runtime → {app}\bun\bun.exe, and
|
||||
; the launcher the PunktfunkWeb task runs → {app}\web\web-run.cmd. web-setup.ps1 (the provisioner)
|
||||
; bundled in, no node_modules) -> {app}\web\.output, a portable bun runtime -> {app}\bun\bun.exe, and
|
||||
; the launcher the PunktfunkWeb task runs -> {app}\web\web-run.cmd. web-setup.ps1 (the provisioner)
|
||||
; goes to {tmp} and is removed after install.
|
||||
Source: "{#WebDir}\*"; DestDir: "{app}\web\.output"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
Source: "{#BunExe}"; DestDir: "{app}\bun"; DestName: "bun.exe"; Flags: ignoreversion
|
||||
@@ -180,7 +180,7 @@ Filename: "{app}\punktfunk-host.exe"; Parameters: "service uninstall"; Flags: ru
|
||||
; Stop + remove the PunktfunkWeb task and its firewall rule (leaves %ProgramData%\punktfunk config,
|
||||
; like the host uninstall does).
|
||||
Filename: "powershell.exe"; \
|
||||
Parameters: "-NoProfile -ExecutionPolicy Bypass -Command ""Stop-ScheduledTask -TaskName PunktfunkWeb -ErrorAction SilentlyContinue; Unregister-ScheduledTask -TaskName PunktfunkWeb -Confirm:$false -ErrorAction SilentlyContinue; Get-NetFirewallRule -Name 'PunktfunkWeb-TCP-3000' -ErrorAction SilentlyContinue | Remove-NetFirewallRule"""; \
|
||||
Parameters: "-NoProfile -ExecutionPolicy Bypass -Command ""Stop-ScheduledTask -TaskName PunktfunkWeb -ErrorAction SilentlyContinue; Get-NetTCPConnection -LocalPort 3000 -State Listen -ErrorAction SilentlyContinue | ForEach-Object { Stop-Process -Id $_.OwningProcess -Force -ErrorAction SilentlyContinue }; Unregister-ScheduledTask -TaskName PunktfunkWeb -Confirm:$false -ErrorAction SilentlyContinue; Get-NetFirewallRule -Name 'PunktfunkWeb-TCP-3000' -ErrorAction SilentlyContinue | Remove-NetFirewallRule"""; \
|
||||
Flags: runhidden waituntilterminated; RunOnceId: "PunktfunkWebCleanup"
|
||||
#endif
|
||||
|
||||
@@ -188,7 +188,7 @@ Filename: "powershell.exe"; \
|
||||
#ifdef WithWeb
|
||||
var
|
||||
WebPwPage: TInputQueryWizardPage;
|
||||
FreshWebInstall: Boolean; { captured at start — web-setup creates the file mid-run }
|
||||
FreshWebInstall: Boolean; { captured at start - web-setup creates the file mid-run }
|
||||
|
||||
function WebPasswordPath: String;
|
||||
begin
|
||||
@@ -196,7 +196,7 @@ begin
|
||||
end;
|
||||
|
||||
{ Pre-fill the console password field with a crypto-strong default (Inno has no RNG): a one-shot
|
||||
PowerShell writes 12 random bytes as dashed hex; strip the dashes → a 24-char hex password. }
|
||||
PowerShell writes 12 random bytes as dashed hex; strip the dashes -> a 24-char hex password. }
|
||||
procedure GenerateRandomWebPassword(var Pw: String);
|
||||
var
|
||||
ResultCode: Integer;
|
||||
@@ -229,7 +229,7 @@ begin
|
||||
WebPwPage := CreateInputQueryPage(wpSelectTasks,
|
||||
'Web console', 'Set the punktfunk web console login password',
|
||||
'The management console is served on http://this-computer:3000 and is login-gated. Keep the ' +
|
||||
'secure password generated below (it is shown again on the final page) or enter your own — you ' +
|
||||
'secure password generated below (it is shown again on the final page) or enter your own - you ' +
|
||||
'can change it later in %ProgramData%\punktfunk\web-password.');
|
||||
WebPwPage.Add('Console password:', False); { visible, so the admin can read the generated default }
|
||||
DefaultPw := '';
|
||||
@@ -239,7 +239,7 @@ end;
|
||||
|
||||
function ShouldSkipPage(PageID: Integer): Boolean;
|
||||
begin
|
||||
{ On upgrade the password already exists — keep it, don't re-prompt. }
|
||||
{ On upgrade the password already exists - keep it, don't re-prompt. }
|
||||
Result := (PageID = WebPwPage.ID) and (not FreshWebInstall);
|
||||
end;
|
||||
|
||||
@@ -264,7 +264,7 @@ end;
|
||||
function WebSetupParams(Param: String): String;
|
||||
begin
|
||||
{ Pass the password to web-setup.ps1 via a temp file, not the cmdline (which lands in the install
|
||||
log). Only on a fresh install — on upgrade web-setup keeps the existing file. }
|
||||
log). Only on a fresh install - on upgrade web-setup keeps the existing file. }
|
||||
Result := '-AppDir "' + ExpandConstant('{app}') + '"';
|
||||
if FreshWebInstall then
|
||||
Result := Result + ' -PasswordFile "' + ExpandConstant('{tmp}\webpw.txt') + '"';
|
||||
@@ -287,12 +287,31 @@ begin
|
||||
'', SW_HIDE, ewWaitUntilTerminated, ResultCode);
|
||||
end;
|
||||
|
||||
#ifdef WithWeb
|
||||
{ Stop a running web console + free :3000 BEFORE the file copy, so the old server doesn't lock
|
||||
.output / web-run.cmd / bun.exe and the new task can bind. Killing the :3000 listener owner is
|
||||
runtime-agnostic (an early install may have run node, the current one runs bun). web-setup.ps1
|
||||
repeats this idempotently after the copy. Best-effort; a fresh install is a no-op. }
|
||||
procedure StopWebConsole;
|
||||
var
|
||||
ResultCode: Integer;
|
||||
begin
|
||||
Exec('powershell.exe',
|
||||
'-NoProfile -ExecutionPolicy Bypass -Command "' +
|
||||
'$ErrorActionPreference=''SilentlyContinue''; ' +
|
||||
'Stop-ScheduledTask -TaskName PunktfunkWeb; ' +
|
||||
'Get-NetTCPConnection -LocalPort 3000 -State Listen | ForEach-Object { Stop-Process -Id $_.OwningProcess -Force }"',
|
||||
'', SW_HIDE, ewWaitUntilTerminated, ResultCode);
|
||||
end;
|
||||
#endif
|
||||
|
||||
procedure CurStepChanged(CurStep: TSetupStep);
|
||||
begin
|
||||
if CurStep = ssInstall then
|
||||
begin
|
||||
StopHostServiceAndWait;
|
||||
#ifdef WithWeb
|
||||
StopWebConsole; { upgrade-safe: free :3000 + unlock the web files before the copy }
|
||||
{ Stash the chosen password for web-setup.ps1 (fresh install only); the temp copy is auto-cleaned. }
|
||||
if FreshWebInstall then
|
||||
SaveStringToFile(ExpandConstant('{tmp}\webpw.txt'), Trim(WebPwPage.Values[0]), False);
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
.DESCRIPTION
|
||||
pf-vdisplay (our all-Rust IddCx virtual display) is built from packaging/windows/drivers/, and
|
||||
the SIGNED output (pf_vdisplay.dll/.inf/.cat + punktfunk-driver.cer) is VENDORED under
|
||||
packaging/windows/pf-vdisplay/ (signer punktfunk-ds-test — shared with the gamepad drivers — Class=
|
||||
packaging/windows/pf-vdisplay/ (signer punktfunk-ds-test - shared with the gamepad drivers - Class=
|
||||
Display, HWID root\pf_vdisplay). Rebuild + re-vendor with
|
||||
packaging/windows/drivers/deploy-dev.ps1 when the driver source changes, then copy the staged
|
||||
pf_vdisplay.{dll,inf,cat} over the vendored copies. nefcon publishes a pinned release, so we fetch +
|
||||
SHA-256-verify it (it provides nefconc.exe, used to create the root-enumerated device node — pnputil
|
||||
SHA-256-verify it (it provides nefconc.exe, used to create the root-enumerated device node - pnputil
|
||||
can't).
|
||||
|
||||
Output (consumed by punktfunk-host.iss): -OutDir gets pf_vdisplay.inf/.cat/.dll + punktfunk-driver.cer
|
||||
@@ -36,7 +36,7 @@ New-Item -ItemType Directory -Force -Path $OutDir | Out-Null
|
||||
|
||||
# --- vendored pf-vdisplay driver --------------------------------------------------------------
|
||||
$inf = Get-ChildItem -Path $VendorDir -Filter pf_vdisplay.inf -ErrorAction SilentlyContinue | Select-Object -First 1
|
||||
if (-not $inf) { throw "no vendored pf_vdisplay.inf under $VendorDir — re-vendor via drivers/deploy-dev.ps1" }
|
||||
if (-not $inf) { throw "no vendored pf_vdisplay.inf under $VendorDir - re-vendor via drivers/deploy-dev.ps1" }
|
||||
Copy-Item (Join-Path $VendorDir '*') $OutDir -Force
|
||||
Write-Host "==> vendored pf-vdisplay staged from $VendorDir"
|
||||
|
||||
@@ -54,7 +54,7 @@ try {
|
||||
}
|
||||
Write-Host " sha256 ok ($got)"
|
||||
}
|
||||
else { Write-Warning "no pinned nefcon SHA-256 — computed $got (PIN THIS in stage-pf-vdisplay.ps1)" }
|
||||
else { Write-Warning "no pinned nefcon SHA-256 - computed $got (PIN THIS in stage-pf-vdisplay.ps1)" }
|
||||
Expand-Archive -Path $zip -DestinationPath $work -Force
|
||||
$nefc = Get-ChildItem -Path $work -Recurse -Filter 'nefconc.exe' |
|
||||
Where-Object { $_.FullName -match '(?i)\\x64\\' } | Select-Object -First 1
|
||||
|
||||
Reference in New Issue
Block a user