Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 61 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ on:
push:
branches:
- main
tags:
- 'v*'
workflow_dispatch:

jobs:
Expand All @@ -33,6 +35,16 @@ jobs:
with:
msbuild-architecture: x64

- name: Install WDK
shell: pwsh
run: |
winget install --source winget --exact --id Microsoft.WindowsWDK.10.0.26100 --accept-source-agreements --accept-package-agreements --silent

$vsWhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
$vsPath = & $vsWhere -latest -property installationPath
$vsInstaller = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\setup.exe"
& $vsInstaller modify --installPath $vsPath --add Microsoft.VisualStudio.Component.WDK --quiet --norestart

- name: Restore NuGet packages
run: msbuild -t:restore -p:RestorePackagesConfig=true

Expand All @@ -45,10 +57,58 @@ jobs:
- name: Build solution
run: msbuild WinUHid.sln /p:Configuration=${{ matrix.configuration }} /p:Platform=${{ matrix.platform }} /m /verbosity:minimal

# To enable driver signing, add a code signing certificate as a GitHub secret
# named SIGNING_CERTIFICATE (base64-encoded PFX) and SIGNING_PASSWORD.
# OV certificate is sufficient for x86/x64. ARM64 requires WHQL (EV certificate).
#
# - name: Sign driver
# if: matrix.configuration == 'Release' && secrets.SIGNING_CERTIFICATE
# shell: pwsh
# run: |
# $pfxBytes = [Convert]::FromBase64String("${{ secrets.SIGNING_CERTIFICATE }}")
# [IO.File]::WriteAllBytes("$env:TEMP\cert.pfx", $pfxBytes)
# $signtool = (Get-ChildItem "${env:ProgramFiles(x86)}\Windows Kits\10\bin\*\x64\signtool.exe" -Recurse |
# Sort-Object FullName -Descending | Select-Object -First 1).FullName
# $buildDir = "build\${{ matrix.configuration }}\${{ matrix.platform }}"
# & $signtool sign /f "$env:TEMP\cert.pfx" /p "${{ secrets.SIGNING_PASSWORD }}" /fd sha256 /td sha256 /tr http://timestamp.digicert.com "$buildDir\WinUHidDriver.dll"
# & $signtool sign /f "$env:TEMP\cert.pfx" /p "${{ secrets.SIGNING_PASSWORD }}" /fd sha256 /td sha256 /tr http://timestamp.digicert.com "$buildDir\WinUHid Driver\winuhiddriver.cat"
# Remove-Item "$env:TEMP\cert.pfx"

- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: WinUHid-${{ matrix.configuration }}-${{ matrix.platform }}
path: |
build/${{ matrix.configuration }}/${{ matrix.platform }}/WinUHid.*
WinUHid/WinUHid.h
build/${{ matrix.configuration }}/${{ matrix.platform }}/WinUHidDriver.*
build/${{ matrix.configuration }}/${{ matrix.platform }}/WinUHid Driver/
WinUHid/WinUHid.h
WinUHidDevs/WinUHidPS5.h
WinUHidDevs/WinUHidDevs.h

release:
name: Create Release
needs: build
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
permissions:
contents: write

steps:
- name: Download Release artifacts
uses: actions/download-artifact@v4
with:
pattern: WinUHid-Release-*

- name: Package for release
run: |
for dir in WinUHid-Release-*/; do
platform=$(basename "$dir" | sed 's/WinUHid-Release-//')
(cd "$dir" && zip -r "../WinUHid-${platform}.zip" .)
done

- name: Create release
uses: softprops/action-gh-release@v2
with:
files: WinUHid-*.zip
generate_release_notes: true
139 changes: 139 additions & 0 deletions BUILDING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# Building WinUHid from Source

## Prerequisites

- Visual Studio 2022 (Community or Build Tools)
- Desktop development with C++ workload
- Windows Driver Kit (WDK) component (`Microsoft.VisualStudio.Component.WDK`)
- Windows SDK 10.0.22621 or later
- Windows Driver Kit 10.0.26100 or later (`winget install Microsoft.WindowsWDK.10.0.26100`)

## Building the User-Mode Library

The user-mode library (`WinUHid.dll` + `WinUHidDevs.dll`) builds without any special setup:

```
msbuild -t:restore -p:RestorePackagesConfig=true
msbuild WinUHid.sln /p:Configuration=Release /p:Platform=x64 /m
```

The library can also be compiled with MinGW/GCC by including the source files directly. A WRL compatibility shim is needed for `wrl/wrappers/corewrappers.h` since MinGW doesn't ship those headers.

## Building the UMDF Driver

The driver project (`WinUHid Driver/`) requires the `WindowsUserModeDriver10.0` platform toolset. This is registered when you install the WDK component through the Visual Studio Installer.

### WPP Tracing (WinUHid.tmh)

The driver uses WPP tracing. The WPP preprocessor generates `WinUHid.tmh` during the build. If your build environment doesn't run WPP (e.g. building outside the full WDK pipeline), create a stub file at `WinUHid Driver/WinUHid.tmh`:

```c
#pragma once
#define WPP_INIT_TRACING(...)
#define WPP_CLEANUP(...)
#define TraceEvents(level, flags, msg, ...)
```

This disables trace logging but the driver functions normally.

### INF Processing (stampinf)

The INF file contains macros (`$ARCH$`, `$UMDFVERSION$`) that must be expanded before the driver can be installed. The WDK build pipeline runs `stampinf` automatically, but if you need to process it manually:

```
stampinf -f "WinUHid Driver\WinUHidDriver.inf" -a amd64 -u 2.23.0 -d * -v *
```

The UMDF version (`-u`) must match the WDF framework on the target system. Common values:
- Windows 10 21H2: `2.23.0`
- Windows 11 22H2+: `2.33.0`

If the version is too high for the target system, the UMDF reflector will refuse to load the driver. Check `setupapi.dev.log` for messages like "Using WDF schema version X.XX when section requires version Y.YY".

### UMDF Version Mismatch

If the driver installs but `WUDFHost.exe` never starts, and `setupapi.dev.log` shows a WDF schema version error, rebuild with headers matching the target OS:

```
WDK include path: C:\Program Files (x86)\Windows Kits\10\Include\wdf\umdf\2.23
WDK lib path: C:\Program Files (x86)\Windows Kits\10\Lib\wdf\umdf\x64\2.23
```

And set `-u 2.23.0` in stampinf.

## Signing

UMDF drivers don't require kernel-mode signing. Self-signed certificates work with Secure Boot enabled.

### For local testing

```powershell
# Create and trust a self-signed certificate
$cert = New-SelfSignedCertificate -Type CodeSigningCert -Subject "CN=WinUHid Test" -CertStoreLocation Cert:\LocalMachine\My
$store = New-Object System.Security.Cryptography.X509Certificates.X509Store("TrustedPublisher", "LocalMachine")
$store.Open("ReadWrite"); $store.Add($cert); $store.Close()

# Sign the driver
signtool sign /a /sm /s My /n "WinUHid Test" /fd sha256 WinUHidDriver.dll

# Create and sign the catalog
New-FileCatalog -Path $stagingDir -CatalogFilePath WinUHidDriver.cat -CatalogVersion 2.0
signtool sign /a /sm /s My /n "WinUHid Test" /fd sha256 WinUHidDriver.cat
```

### For distribution

- **x86/x64**: OV (Organization Validation) code signing certificate is sufficient
- **ARM64**: Requires WHQL submission through Microsoft's Hardware Developer Program (EV certificate + legal entity)

## Installation

### 1. Create the device node

The `Root\WinUHid` device node must be created through the SetupDI API (`SetupDiCreateDeviceInfoW` + `SetupDiCallClassInstaller(DIF_REGISTERDEVICE)`). The `pnputil /add-device` command doesn't reliably work for this device type.

See `Installer/RootDevCA/RootDevCA.cpp` for the reference implementation.

### 2. Install the driver package

```
stampinf -f WinUHidDriver.inf -a amd64 -u 2.23.0 -d * -v *
pnputil /add-driver WinUHidDriver.inf /install
```

If `pnputil /add-driver` shows "The third-party INF does not contain digital signature information", make sure the catalog file is signed and present alongside the INF.

### 3. Bind the driver

After creating the device node and adding the driver package:

```powershell
# PowerShell - requires P/Invoke for UpdateDriverForPlugAndPlayDevicesW from newdev.dll
UpdateDriverForPlugAndPlayDevicesW(NULL, "Root\WinUHid", "path\to\WinUHidDriver.inf", INSTALLFLAG_FORCE, &needReboot)
```

### 4. Verify

```
pnputil /enum-devices /class System | findstr WinUHid
```

Should show `Status: Started`. Confirm the device is accessible:

```powershell
[System.IO.File]::Open("\\.\WinUHid", "Open", "ReadWrite", "ReadWrite").Close()
```

Note: `Test-Path '\\.\WinUHid'` returns `False` even when the device is working. Use the file handle test above instead.

## Troubleshooting

| Symptom | Cause | Fix |
|---------|-------|-----|
| `wdf.h` not found | WDK not installed or UMDF include path not set | Install WDK component through VS Installer |
| `WinUHid.tmh` not found | WPP preprocessor not running | Create the stub file (see above) |
| `$ARCH$` / `$UMDFVERSION$` in INF | stampinf not run | Run stampinf manually |
| Device "Started" but `\\.\WinUHid` not accessible | WUDFRd service not running | `sc start WUDFRd`, then disable/enable the device |
| Device "Started" but WUDFHost not running | UMDF version mismatch | Check `setupapi.dev.log`, rebuild with matching UMDF version |
| `error 87` from WUDFCoinstaller | `$UMDFVERSION$` not expanded in INF | Run stampinf |
| `error 259` from UpdateDriver | `$ARCH$` not expanded in INF | Run stampinf with `-a amd64` |