Skip to content

Commit 37b3100

Browse files
fix: harden uninstall and add broker service tests
- Add Windows broker service unit tests with DPAPI, WinHTTP, and file persistence failure injection via a private compiled test hook - Replace pnputil label parsing with DISM/CIM-based driver discovery in the uninstall script - Add post-uninstall assertion to fail if broker service, device, or driver package remains - Rename release artifact to include AMD64 architecture tag - Document current release limits (Steam Share button, ARM64, offline grace period) - Harden WiX custom action uninstall return from ignore to check
1 parent 766a0e2 commit 37b3100

14 files changed

Lines changed: 1211 additions & 102 deletions

.github/workflows/ci.yml

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -363,10 +363,12 @@ jobs:
363363
throw "OpenCppCoverage.exe was not found."
364364
}
365365
366+
# The broker test hook compiles a private copy only for failure injection.
366367
& $openCppCoverage `
367368
--sources "$env:GITHUB_WORKSPACE\examples" `
368369
--sources "$env:GITHUB_WORKSPACE\src" `
369370
--sources "$env:GITHUB_WORKSPACE\tools" `
371+
--excluded_sources "$env:GITHUB_WORKSPACE\src\platform\windows\broker" `
370372
"--export_type=cobertura:$env:GITHUB_WORKSPACE\cmake-build-ci\reports\coverage.xml" `
371373
--working_dir "$env:GITHUB_WORKSPACE\cmake-build-ci\tests" `
372374
-- `
@@ -413,11 +415,13 @@ jobs:
413415
GCOV_EXECUTABLE: ${{ matrix.gcov_executable }}
414416
MSYS2_PATH_TYPE: inherit
415417
run: |
418+
# The broker test hook compiles a private copy only for failure injection.
416419
uv run --project ../third-party/lizardbyte-common --locked --no-sync gcovr . -r .. \
417420
--filter ../examples/ \
418421
--filter ../src/ \
419422
--filter ../tools/ \
420423
--gcov-executable "${GCOV_EXECUTABLE}" \
424+
--exclude ../src/platform/windows/broker/ \
421425
--exclude ../tests/ \
422426
--exclude ../third-party/ \
423427
--exclude-noncode-lines \
@@ -590,7 +594,8 @@ jobs:
590594
New-Item -ItemType Directory -Force -Path artifacts | Out-Null
591595
Copy-Item `
592596
-LiteralPath .\cmake-build-driver\cpack_artifacts\libvirtualhid.msi `
593-
-Destination .\artifacts\libvirtualhid-Windows-Driver-installer.msi
597+
-Destination `
598+
".\artifacts\libvirtualhid-Windows-AMD64-driver-installer.msi"
594599
595600
- name: Export Azure driver signing certificate
596601
if: >-
@@ -635,6 +640,39 @@ jobs:
635640
files-folder-recurse: false
636641
signing-account-name: ${{ vars.AZURE_SIGNING_ACCOUNT }}
637642

643+
- name: Validate release signing identities
644+
if: >-
645+
github.event_name == 'push' &&
646+
needs.setup_release.outputs.publish_release == 'true' &&
647+
vars.AZURE_SIGNING_ACCOUNT != ''
648+
shell: pwsh
649+
run: |
650+
$catalogPath = Join-Path `
651+
$env:GITHUB_WORKSPACE `
652+
"cmake-build-driver\src\platform\windows\driver\package\$env:DRIVER_BUILD_CONFIG\libvirtualhid.cat"
653+
$installerPath = Get-ChildItem -LiteralPath .\artifacts -Filter *.msi |
654+
Select-Object -ExpandProperty FullName -First 1
655+
if (!$installerPath) {
656+
throw "The signed Windows driver installer was not found."
657+
}
658+
659+
$catalogSignature = Get-AuthenticodeSignature -FilePath $catalogPath
660+
$installerSignature = Get-AuthenticodeSignature -FilePath $installerPath
661+
foreach ($signature in @($catalogSignature, $installerSignature)) {
662+
if ($signature.Status -ne "Valid" -or !$signature.SignerCertificate) {
663+
throw "A release signature is invalid: $($signature.StatusMessage)"
664+
}
665+
}
666+
if ($catalogSignature.SignerCertificate.Subject -cne `
667+
$installerSignature.SignerCertificate.Subject) {
668+
throw "The catalog and MSI were signed with different identities."
669+
}
670+
Write-Host (
671+
"Validated release signer " +
672+
"$($installerSignature.SignerCertificate.Subject) " +
673+
"[$($installerSignature.SignerCertificate.Thumbprint)]."
674+
)
675+
638676
- name: Debug wix
639677
if: always()
640678
shell: pwsh
@@ -771,12 +809,33 @@ jobs:
771809
run: |
772810
mkdir -p artifacts
773811
for name in Linux-GCC Linux-Clang macOS Windows-MinGW-UCRT64 Windows-MSVC; do
812+
release_name="${name}"
813+
case "${name}" in
814+
Windows-MinGW-UCRT64) release_name="Windows-AMD64-MinGW-UCRT64" ;;
815+
Windows-MSVC) release_name="Windows-AMD64-MSVC" ;;
816+
esac
774817
zip -r \
775-
"artifacts/libvirtualhid-${{ needs.setup_release.outputs.release_tag }}-${name}.zip" \
818+
"artifacts/libvirtualhid-${release_name}.zip" \
776819
"install-${name}"
777820
done
778821
cp windows-driver-installer/*.msi artifacts/
779822
823+
- name: Validate release metadata
824+
env:
825+
RELEASE_COMMIT: ${{ needs.setup_release.outputs.release_commit }}
826+
RELEASE_TAG: ${{ needs.setup_release.outputs.release_tag }}
827+
RELEASE_VERSION: ${{ needs.setup_release.outputs.release_version }}
828+
run: |
829+
test -n "${RELEASE_TAG}"
830+
test -n "${RELEASE_VERSION}"
831+
test "${RELEASE_COMMIT}" = "${GITHUB_SHA}"
832+
test -s "artifacts/libvirtualhid-Linux-GCC.zip"
833+
test -s "artifacts/libvirtualhid-Linux-Clang.zip"
834+
test -s "artifacts/libvirtualhid-macOS.zip"
835+
test -s "artifacts/libvirtualhid-Windows-AMD64-driver-installer.msi"
836+
test -s "artifacts/libvirtualhid-Windows-AMD64-MinGW-UCRT64.zip"
837+
test -s "artifacts/libvirtualhid-Windows-AMD64-MSVC.zip"
838+
780839
- name: Create/Update GitHub Release
781840
if: needs.setup_release.outputs.publish_release == 'true'
782841
uses: LizardByte/actions/actions/release_create@d0ae7f82215a479fe2b74f4088c53ee6460513dd # v2026.728.214955
@@ -786,7 +845,7 @@ jobs:
786845
draft: true
787846
generateReleaseNotes: ${{ needs.setup_release.outputs.release_generate_release_notes }}
788847
name: ${{ needs.setup_release.outputs.release_tag }}
789-
prerelease: true
848+
prerelease: false
790849
tag: ${{ needs.setup_release.outputs.release_tag }}
791850
token: ${{ secrets.GH_BOT_TOKEN }}
792851
virustotal_api_key: ${{ secrets.VIRUSTOTAL_API_KEY }}

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,17 @@ The library is designed around gamepad use first because remote streaming hosts
118118
are the first consumer class. Non-gamepad device types are available through the
119119
same API where the backend exposes them.
120120
121+
## ⚠️ Known Windows Limitations
122+
123+
- Steam does not expose the Xbox Series Share button from the VHF child through
124+
the same Xbox HIDAPI path used by physical controllers. That path requires a
125+
non-VHF Xbox HIDAPI/GIP transport.
126+
- PlayStation and Nintendo rumble parsing is covered by protocol and installed
127+
driver tests but has not yet completed broad validation with real client
128+
applications.
129+
- The published Windows driver installer is AMD64-only. Windows ARM64 release
130+
packages require a different Microsoft driver-signing path.
131+
121132
## 🔁 Alternatives
122133
123134
Alternatives exist if `libvirtualhid` does not meet your needs.

cmake/packaging/wix_resources/libvirtualhid-driver-installer-patch.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@
1616
Directory="INSTALL_ROOT"
1717
ExeCommand=""[WindowsFolder]System32\WindowsPowerShell\v1.0\powershell.exe" -WindowStyle Hidden -NoProfile -ExecutionPolicy Bypass -File "[INSTALL_ROOT]scripts\windows\uninstall-driver.ps1" -Force -RemoveCertificateSubject "CN=libvirtualhid CI Test Driver Signing""
1818
Execute="deferred"
19-
Return="ignore"
19+
Return="check"
2020
Impersonate="no" />
2121
<CustomAction Id="CA_LibVirtualHidUninstallDriverSilent"
2222
Directory="INSTALL_ROOT"
2323
ExeCommand="&quot;[WindowsFolder]System32\WindowsPowerShell\v1.0\powershell.exe&quot; -WindowStyle Hidden -NoProfile -ExecutionPolicy Bypass -File &quot;[INSTALL_ROOT]scripts\windows\uninstall-driver.ps1&quot; -Force -RemoveCertificateSubject &quot;CN=libvirtualhid CI Test Driver Signing&quot;"
2424
Execute="deferred"
25-
Return="ignore"
25+
Return="check"
2626
Impersonate="no" />
2727

2828
<InstallExecuteSequence>

docs/store-review-validation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ Expected result:
6060
## Manual Review Steps
6161

6262
1. Install the released, production-signed
63-
`libvirtualhid-Windows-Driver-installer.msi`.
63+
`libvirtualhid-Windows-AMD64-driver-installer.msi`.
6464
2. Reboot only if Windows reports that a reboot is required.
6565
3. Open PowerShell.
6666
4. Run the required validation tool from the submission notes.

docs/windows-driver.md

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,13 @@ with a service SID. The service `ImagePath` is stored as a literal quoted path,
159159
and installation fails if the registry value is not safely quoted. This avoids
160160
CWE-428 unquoted-service-path escalation when the install root contains spaces.
161161
The install helper also clears any legacy broker service `Environment` value so
162-
licensing configuration cannot be overridden on the user's machine. The uninstall
163-
helper stops and deletes that service before removing the driver package.
162+
licensing configuration cannot be overridden on the user's machine. The
163+
uninstall helper stops and deletes that service before removing the driver
164+
package. It discovers staged OEM INF names through language-neutral DISM and
165+
CIM objects instead of parsing localized `pnputil` labels. Uninstall fails if a
166+
command fails or if the broker service, root device, or staged driver package
167+
is still present after cleanup, so the MSI cannot silently report a complete
168+
removal while driver state remains.
164169

165170
The installed-driver test fails if the root device is not started, if
166171
`\\.\LibVirtualHid` cannot be opened, or if a held `gamepad_adapter` instance
@@ -322,6 +327,20 @@ do not alter the public platform-neutral profile API.
322327
Consumers that display raw HID strings may still show the Windows VHF product
323328
label because VHF does not provide a product/manufacturer string callback.
324329

330+
### Current Release Limits
331+
332+
- Steam does not expose the Xbox Series Share button from the VHF child through
333+
the same Xbox HIDAPI path used by physical controllers. Supporting that path
334+
requires a non-VHF Xbox HIDAPI/GIP transport.
335+
- PlayStation and Nintendo rumble parsing is covered by protocol and installed
336+
driver tests, but has not yet completed broad validation with real client
337+
applications.
338+
- The published Windows driver installer is AMD64-only. Windows ARM64 release
339+
packages require a Microsoft dashboard signing path that is not part of the
340+
current Azure Trusted Signing workflow.
341+
- Every production gamepad creation requires a successful online license
342+
validation response. There is no offline grace period.
343+
325344
## Signing
326345

327346
Windows driver packages require a signed catalog for normal installation.

0 commit comments

Comments
 (0)