From 32e5594a9a8be1ba95b8d870ad0fc48adfbb44d9 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Tue, 14 Jul 2026 23:31:40 +0200 Subject: [PATCH] =?UTF-8?q?fix(drivers):=20per-pad=20MAC=20+=20USB=20seria?= =?UTF-8?q?l=20in=20pf-dualsense=20=E2=80=94=20SDL/Steam=20dedup=20by=20se?= =?UTF-8?q?rial?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Windows counterpart of 5c7e0afa's Linux per-pad pairing MAC: every virtual DualSense / Edge / DualShock 4 presented ONE hardcoded serial, so SDL/Steam (which dedup controllers by serial) could merge a second pad into the first. * GET_FEATURE pairing replies (DS/Edge 0x09, DS4 0x12) now carry the pad index the host stamps into the sealed section in the MAC's low octet. * GET_STRING serial strings (HidD_GetSerialNumberString — what SDL actually reads on Windows) get the same per-pad low octet, agreeing with the feature MAC. The Edge's 0x09 reply moves onto its serial-string base (0x75 = DS base + 1), fixing the pre-existing feature-vs-string mismatch. * The Deck identity already did this per-pad; its two inline index reads now share the new `pad_index()` helper. Pad 0 keeps today's serial values for DS / DS4 / Deck (no identity churn for existing single-pad setups). Verified on the windows-amd64 runner: cargo build + clippy -D warnings (pf-umdf-util / pf-xusb / pf-dualsense) + fmt clean on the pinned 1.96.0. Co-Authored-By: Claude Fable 5 --- .../windows/drivers/pf-dualsense/src/lib.rs | 64 +++++++++++-------- 1 file changed, 39 insertions(+), 25 deletions(-) diff --git a/packaging/windows/drivers/pf-dualsense/src/lib.rs b/packaging/windows/drivers/pf-dualsense/src/lib.rs index 2f61d549..59473140 100644 --- a/packaging/windows/drivers/pf-dualsense/src/lib.rs +++ b/packaging/windows/drivers/pf-dualsense/src/lib.rs @@ -341,6 +341,19 @@ fn channel_cfg() -> ChannelConfig { } } +/// The wire pad index the host stamped into the sealed section (0 while the channel hasn't +/// attached yet). Keys every per-pad identity surface: the Deck unit id + serial, the PS +/// identities' pairing MAC (feature 0x09/0x12) and USB serial string — SDL/Steam dedup +/// controllers by serial, so two virtual pads must never share one (identical serials make a +/// second pad read as the FIRST one re-appearing over another transport, and it is merged). +fn pad_index() -> u8 { + (CHANNEL + .data() + .map(|v| v.read_u32(OFF_PAD_INDEX)) + .unwrap_or(0) + & 0xFF) as u8 +} + /// Whether the world-writable bring-up file log is enabled (resolved once). OPT-IN — debug builds, /// or the `PFDS_DEBUG_LOG` (system-wide) env var — the same treatment pf-vdisplay got in audit /// §4.4: a RELEASE driver never writes the Public file (info-leak/DoS surface), and the per-report @@ -664,15 +677,9 @@ fn on_set_feature(request: &Request) -> NTSTATUS { /// truth this mirrors). Anything else echoes the latched command. fn deck_feature_reply() -> [u8; 64] { let last = LAST_SET_FEATURE.lock().map(|g| *g).unwrap_or([0u8; 64]); - // Per-pad unit id "PF" + the pad index the host stamped into the section (0 while the - // channel hasn't attached yet) — matches steam_proto::deck_unit_id / deck_serial, so two - // virtual Decks never collide in Steam's eyes. - let idx = CHANNEL - .data() - .map(|v| v.read_u32(OFF_PAD_INDEX)) - .unwrap_or(0) - & 0xFF; - let unit_id: u32 = 0x5046_0000 | idx; + // Per-pad unit id "PF" + the pad index the host stamped into the section — matches + // steam_proto::deck_unit_id / deck_serial, so two virtual Decks never collide in Steam's eyes. + let unit_id: u32 = 0x5046_0000 | pad_index() as u32; // Steam validates the unit serial's PREFIX before accepting it: a "PF"-leading serial is // REJECTED ("Invalid or missing unit serial number …") and Steam then substitutes a hash and // MANGLES the displayed name ("Steam Deck Controllerggg"). An 'F'-leading serial passes, so we @@ -746,12 +753,23 @@ fn on_get_feature(request: &Request) -> NTSTATUS { // DualSense + Edge use feature ids 0x05/0x09/0x20 (same blobs — SDL forces enhanced-rumble // for the Edge PID regardless of the firmware version at 0x20[44..46]); DualShock 4 uses // 0x02/0x12/0xa3. - let blob: &[u8] = match (device_type(), report_id) { + // The pairing replies are per-pad: the MAC (bytes 1..7, LSB first) low octet carries the pad + // index (see `pad_index` — SDL/Steam dedup controllers by this serial), agreeing with the + // GET_STRING serial in `on_get_string`. The Edge lands on its GET_STRING base (0x75 = DS + // base + 1) so its feature MAC and USB serial string agree too. + let devtype = device_type(); + let mut ds_pairing = DS_FEATURE_PAIRING; + ds_pairing[1] = ds_pairing[1] + .wrapping_add(u8::from(devtype == 2)) + .wrapping_add(pad_index()); + let mut ds4_pairing = DS4_FEATURE_PAIRING; + ds4_pairing[1] = ds4_pairing[1].wrapping_add(pad_index()); + let blob: &[u8] = match (devtype, report_id) { (0 | 2, 0x05) => &DS_FEATURE_CALIBRATION, - (0 | 2, 0x09) => &DS_FEATURE_PAIRING, + (0 | 2, 0x09) => &ds_pairing, (0 | 2, 0x20) => &DS_FEATURE_FIRMWARE, (1, 0x02) => &DS4_FEATURE_CALIBRATION, - (1, 0x12) => &DS4_FEATURE_PAIRING, + (1, 0x12) => &ds4_pairing, (1, 0xA3) => &DS4_FEATURE_FIRMWARE, (_, other) => { dbglog!("[pf-ds] GET_FEATURE unknown report id 0x{other:02x}"); @@ -785,20 +803,16 @@ fn on_get_string(request: &Request) -> NTSTATUS { 3 => "Valve Software".into(), _ => "Sony Interactive Entertainment".into(), }, + // Per-pad serials (see `pad_index`): SDL reads this via HidD_GetSerialNumberString and + // Steam dedups controllers by it. The PS strings are the pairing MAC MSB-first, so the + // low octet — the LAST two hex chars — carries the pad index, agreeing with the patched + // feature 0x09/0x12 replies in `on_get_feature`. The Deck serial must agree with + // deck_feature_reply's 0xAE answer (Steam reads both). 2 | 0x0010 => match devtype { - 1 => "DEADBEEF0001".into(), - 2 => "35533AD6E775".into(), - // Per-pad Deck serial — must agree with deck_feature_reply's 0xAE answer (Steam - // reads both and uses the serial to identify units). - 3 => { - let idx = CHANNEL - .data() - .map(|v| v.read_u32(OFF_PAD_INDEX)) - .unwrap_or(0) - & 0xFF; - format!("FVPF{:08X}", 0x5046_0000u32 | idx) - } - _ => "35533AD6E774".into(), + 1 => format!("DEADBEEF00{:02X}", 0x01u8.wrapping_add(pad_index())), + 2 => format!("35533AD6E7{:02X}", 0x75u8.wrapping_add(pad_index())), + 3 => format!("FVPF{:08X}", 0x5046_0000u32 | pad_index() as u32), + _ => format!("35533AD6E7{:02X}", 0x74u8.wrapping_add(pad_index())), }, _ => match devtype { 1 => "Wireless Controller".into(),