7b99b41ede
Much of design/ described work that has since shipped. Trim each doc to
its durable rationale + still-open items (the code is the source of truth
for shipped detail; git history holds the full originals).
- Shipped plans -> status stubs: stats-capture, gamestream-host-plan,
apple-stage2-presenter, windows-service.
- Trimmed completed-out / open-kept: implementation-plan, hdr-pipeline,
host-latency, gpu-contention (fixed stale status table), game-library,
linux-setup (fixed m0->spike + stale zero-copy claim),
session-aware-host-followups, windows-client-bootstrap,
windows-dualsense-{scoping,game-detection}, windows-virtual-display,
security-review (per-finding status table; #12 still open),
apollo-comparison (shipped backlog collapsed to one-liners).
- Windows-host cluster consolidated: windows-host.md -> redirect into
windows-host-rewrite.md (whose stale scorecard is corrected -- goal1 is
merged, M4 done); windows-secure-desktop.md archived (now a fallback
behind IDD-push primary).
- Kept evergreen: ci.md, gamescope-multiuser.md, windows-build-and-packaging.md.
- New design/README.md: per-doc status table + consolidated open-items
roll-up so nothing is tracked in only one buried doc.
- Repoint 5 code comments to the archived secure-desktop doc path.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
144 lines
9.7 KiB
Markdown
144 lines
9.7 KiB
Markdown
# Windows virtual DualSense — game detection handoff
|
|
|
|
> **Status:** Identity fix SHIPPED (commits `6db3525`, `aa159df`, `4a73102`) —
|
|
> `crates/punktfunk-host/src/inject/windows/dualsense_windows.rs` (`create_swdevice`). This doc is trimmed
|
|
> to the root-cause analysis, the SwDeviceCreate identity rationale, the GameInput fallback design, and the
|
|
> still-open on-glass Cyberpunk verification. The implementation walkthrough, probe tooling, and the
|
|
> (now-fixed) secondary driver gaps are cut — the shipped code is the source of truth.
|
|
|
|
Goal: get the host's virtual DualSense **detected and usable in games** (Cyberpunk's native PS5 path +
|
|
others) on the Windows host. Run the decisive experiments **on the interactive desktop of the Windows host**
|
|
(`.173`) — not over SSH.
|
|
|
|
## Where it works / where it doesn't
|
|
|
|
- **Input works.** Client → host → virtual DualSense → games read input (verified in Steam's controller
|
|
test).
|
|
- **The HID is a CORRECT, COMPLETE DualSense.** SDL3 reports the live device as
|
|
`name='DualSense Wireless Controller' vid=0x054C pid=0x0CE6 isGamepad=True gamepadType=PS5`. SDL = HIDAPI =
|
|
what Steam (and many games) build on → that's why Steam works. This is **not** a descriptor/feature-report
|
|
problem.
|
|
- **Cyberpunk's native DualSense path does NOT detect it at all** (Steam Input was off — Cyberpunk was
|
|
reading the raw HID). This is the problem the identity fix targets; on-glass confirmation is still open.
|
|
|
|
## Root cause — the PnP identity, not the HID descriptor (CONFIRMED, run live in console session 3)
|
|
|
|
The break is the device's **PnP identity / device-interface path**, not the HID descriptor or feature
|
|
reports. `hidclass` derives the HID child's path token and its `HID\VID_054C&PID_0CE6` hardware-ids from the
|
|
**parent bus device's hardware-id**. Our parent is the software (SWD) devnode `SWD\PUNKTFUNK\PF_PAD_0` whose
|
|
hardware-id is `pf_dualsense` (no VID/PID), so hidclass emits only the *VendorID+usage* fallback and **no
|
|
PID**. Measured on this box (one virtual pad live + one real 8BitDo present):
|
|
|
|
HID-child hardware-ids (`DEVPKEY_Device_HardwareIds`, CompatibleIds empty):
|
|
`HID\pf_dualsense` · `HID\VID_054C&UP:0001_U:0005` · `HID_DEVICE_SYSTEM_GAME` · `HID_DEVICE_UP:0001_U:0005`
|
|
· `HID_DEVICE` — **note the absent `HID\VID_054C&PID_0CE6`.** `HIDD_ATTRIBUTES` itself is correct (VID 054C
|
|
/ PID 0CE6), which is why attribute-readers work.
|
|
|
|
Device-interface paths (from `HKLM\SYSTEM\CurrentControlSet\Control\DeviceClasses\{4d1e55b2-…}`):
|
|
|
|
| Device | HID interface path |
|
|
| --- | --- |
|
|
| **Ours (virtual)** | `\\?\HID#punktfunk#1&ca418da&0&0000#{…}` — **no `VID_/PID_` token** |
|
|
| Real DualShock 4 (USB, registry remnant) | `\\?\HID#VID_054C&PID_05C4&REV_0100#…` |
|
|
| Real DualSense (BT, registry remnant) | `\\?\HID#{00001124-…}_VID&0002054c_PID&0ce6#…` |
|
|
|
|
**Cross-API enumeration matrix (the decisive experiment — impossible over SSH, run live in the console):**
|
|
|
|
| API | Sees our virtual DS5? | Identity reported | Reads from |
|
|
| --- | --- | --- | --- |
|
|
| SDL3 / HIDAPI | ✅ | 054C:0CE6, type=PS5 | `HIDD_ATTRIBUTES` → Steam works |
|
|
| RawInput | ✅ | 054C:0CE6 | `HIDD_ATTRIBUTES` |
|
|
| WGI `RawGameController` | ✅ | 054C:0CE6 | `HIDD_ATTRIBUTES` |
|
|
| WGI `Gamepad` | ❌ empty | — | (empty for *all* pads on this box — no Xbox-profile pad; not DS-specific) |
|
|
| **MS GameInput** | ✅ enumerates it | **vid=0x0000 pid=0x0000** | **PnP path / hardware-ids** |
|
|
| Cyberpunk native PS5 | ❌ | — | needs the DS5 VID/PID identity |
|
|
|
|
The GameInput result is the clincher: it **does** enumerate our pad — descriptor fingerprint matches exactly
|
|
(15 buttons, 6 axes, 1 hat, usage Game Pad 0x05) — but reports **vid/pid = 0**, while it reads the real
|
|
8BitDo's `vid=0x3434` correctly. So GameInput (and, by the same logic, a native PS5 path) takes VID/PID from
|
|
the **PnP device path / hardware-ids, NOT from `HIDD_ATTRIBUTES`**. Everything that reads attributes directly
|
|
(SDL / RawInput / WGI-raw) is fine; everything that keys off the device *identity/path* (GameInput, native
|
|
DualSense detection) sees a generic, unidentified gamepad → no PS5 path.
|
|
|
|
**⇒ The fix must put `VID_054C&PID_0CE6` into the device-interface path and the `HID\VID&PID` hardware-ids**
|
|
(give the device a real-USB-like PnP identity), not merely correct `HIDD_ATTRIBUTES`.
|
|
|
|
## The fix — SwDeviceCreate identity (shipped)
|
|
|
|
`create_swdevice` sets the identity via **`SW_DEVICE_CREATE_INFO` struct fields** (NOT `pProperties` — a
|
|
`DEVPROPERTY` write of these PnP-owned identity keys is empirically *ignored*; the create-time struct fields
|
|
are the supported lever, confirmed on `.173`):
|
|
|
|
- **`pszzCompatibleIds`** = `USB\VID_054C&PID_0CE6`, `USB\Class_03&SubClass_00&Prot_00`, `USB\Class_03`
|
|
(Windows appends `SWD\Generic`). HIDAPI/SDL/libScePad walk HID-child → `CM_Get_Parent` → this parent's
|
|
CompatibleIds and string-match `"USB"` → **`bus_type` now resolves to USB** (was UNKNOWN).
|
|
- **`pszzHardwareIds`** = `pf_dualsense` **first** (so the INF still binds our UMDF driver), then
|
|
`USB\VID_054C&PID_0CE6&REV_0100`, `USB\VID_054C&PID_0CE6`. hidclass then derives the real-DS5 child ids
|
|
**`HID\VID_054C&PID_0CE6[&REV_0100]`** (previously only `HID\VID_054C&UP:0001_U:0005`).
|
|
- **`pContainerId`** = a deterministic per-pad GUID `{50464453-0000-0000-0000-00000000000<idx>}` ("PFDS")
|
|
— avoids the null-sentinel-ContainerId `xinput1_4` slot-skip bug, and groups the pad's devnodes.
|
|
|
|
**Validated live** (real shipping path, `dualsense-windows-test --index 1` alongside the running service's
|
|
pad 0): INF still binds (`Service=MsHidUmdf`), parent CompatibleIds/HardwareIds + per-pad ContainerId set,
|
|
the HID child gains `HID\VID_054C&PID_0CE6`, and the HIDAPI parent-walk reports **bus_type=USB**. SDL /
|
|
RawInput / WGI `RawGameController` identity stays correct (054C:0CE6).
|
|
|
|
**Why this may still not satisfy GameInput / a native PS5 path:** GameInput parses VID/PID from the HID
|
|
child's **instance path** (`HID\punktfunk\1&…`), which carries no `VID_…&PID_…` token; neither CompatibleIds
|
|
nor HardwareIds change the instance path. Only a real USB-bus instance path (`HID\VID_054C&PID_0CE6\…`) does —
|
|
i.e. a **ViGEm-style KMDF USB-emulating bus driver** (see fallback below). Prior art (HIDMaestro) shows pure
|
|
user-mode pads ARE accepted by WGI/GameInput, so other parity (descriptor / strings / mapping) may matter
|
|
more than a genuine USB bus.
|
|
|
|
## GameInput fallback design (rank-3, only if needed)
|
|
|
|
If a target title uses **GameInput** AND the shipped identity fix above doesn't satisfy it, the last-resort
|
|
option is a **rank-3 KMDF USB-emulating bus driver** (the way ViGEmBus presents a real-looking device)
|
|
instead of SwDeviceCreate + UMDF-HID — it produces a genuine `HID\VID_054C&PID_0CE6\…` instance path, the one
|
|
thing GameInput keys off. Pursue this only if required by a target title; it is heavier than the user-mode
|
|
path and HIDMaestro suggests user-mode pads can be made acceptable to GameInput without it.
|
|
|
|
## On-glass Cyberpunk verification procedure (open — only the user can run it)
|
|
|
|
Must run on the interactive desktop (RDP in or run locally) — WGI / RawInput / GameInput enumeration returns
|
|
**empty from a headless SSH session** (no window/message pump); only HIDAPI works headless.
|
|
|
|
1. Free the service's pad 0 so only the new-identity pad is present:
|
|
`sc stop PunktfunkHost`
|
|
2. Spawn a single virtual DS5 carrying the new identity (cycles Cross/stick so input is visible):
|
|
`target\debug\punktfunk-host.exe dualsense-windows-test --index 0 --seconds 600`
|
|
3. Launch **Cyberpunk 2077 with Steam Input OFF** (so the game reads the raw HID). Check the in-game
|
|
glyphs/prompt **switch to DualSense**.
|
|
4. Restore the service afterward: redeploy the release + restart with `scripts\windows\deploy-host.ps1`.
|
|
|
|
## Key code
|
|
|
|
| What | File |
|
|
| --- | --- |
|
|
| Host backend (`create_swdevice`, the `Global\pfds-shm-<idx>` section, write_state/service/pump) | `crates/punktfunk-host/src/inject/windows/dualsense_windows.rs` |
|
|
| UMDF driver (HID descriptor, feature reports, `on_output_report`) | `packaging/windows/drivers/pf-dualsense/src/lib.rs` |
|
|
| Shared report codec (`serialize_state` input, `parse_ds_output` feedback) | `crates/punktfunk-host/src/inject/proto/dualsense_proto.rs` |
|
|
| Pad seam (`PadBackend`, `pump` → rumble `0xCA` / hidout `0xCD`) | `crates/punktfunk-host/src/punktfunk1.rs` |
|
|
|
|
## Open items
|
|
|
|
1. **Decisive on-glass Cyberpunk test — pending execution.** Launch Cyberpunk 2077 with Steam Input OFF
|
|
against a virtual DS5 carrying the new identity; verify the in-game glyphs switch to DualSense (procedure
|
|
above).
|
|
2. **GameInput rank-3 KMDF USB-emulating bus-driver fallback — optional.** Only if a GameInput-only title
|
|
needs the real VID/PID and the shipped SwDeviceCreate identity fix doesn't satisfy it.
|
|
|
|
## Facts proven (don't re-litigate)
|
|
|
|
- `SwDeviceCreate` requirements: enumerator must have **no underscore** (`punktfunk`); the completion
|
|
**callback is mandatory** (NULL → E_INVALIDARG). Per-session device works; auto-removed on disconnect.
|
|
- The identity keys must be set via the **`SW_DEVICE_CREATE_INFO` struct fields**, not `pProperties` — a
|
|
`DEVPROPERTY` write of the PnP-owned identity keys is ignored.
|
|
- HID descriptor + feature reports are DS5-accurate enough that **SDL identifies it as PS5**.
|
|
- The `IOCTL_HID_GET_STRING` and `DS_FEATURE_CALIBRATION` (42 → 41 bytes) driver gaps were fixed + shipped;
|
|
the driver answers `HidD_GetManufacturer/Product/SerialNumberString` with distinct strings. (Detail in
|
|
`packaging/windows/drivers/pf-dualsense/src/lib.rs`.)
|
|
- Host-side rumble works end to end (driver captures the game's `0x02`, `parse_ds_output` extracts the
|
|
motors, host forwards `0xCA`); the client (macOS) rendering of `0xCA` onto the physical pad is a separate
|
|
open bug, not part of game detection.
|