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

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:
2026-06-26 14:33:34 +00:00
parent 8e87e617df
commit bdfab8e0d5
12 changed files with 257 additions and 61 deletions
+137
View File
@@ -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)" }
+5 -5
View File
@@ -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)
+9 -9
View File
@@ -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
View File
@@ -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
View File
@@ -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"
+13 -12
View File
@@ -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"
+19 -13
View File
@@ -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" }
+33 -14
View File
@@ -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);
+4 -4
View File
@@ -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