fix(drivers): per-pad MAC + USB serial in pf-dualsense — SDL/Steam dedup by serial
ci / web (push) Successful in 59s
ci / docs-site (push) Successful in 1m8s
decky / build-publish (push) Successful in 17s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 8s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 7s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 7s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 9s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 8s
ci / bench (push) Successful in 5m31s
arch / build-publish (push) Successful in 10m59s
android / android (push) Successful in 13m54s
windows-drivers / probe-and-proto (push) Successful in 23s
windows-drivers / driver-build (push) Successful in 1m27s
ci / rust (push) Successful in 17m32s
deb / build-publish (push) Successful in 11m37s
docker / deploy-docs (push) Successful in 51s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 19m36s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 19m24s
windows-host / package (push) Successful in 14m21s
apple / swift (push) Has been cancelled
apple / screenshots (push) Has been cancelled
ci / web (push) Successful in 59s
ci / docs-site (push) Successful in 1m8s
decky / build-publish (push) Successful in 17s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 8s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 7s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 7s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 9s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 8s
ci / bench (push) Successful in 5m31s
arch / build-publish (push) Successful in 10m59s
android / android (push) Successful in 13m54s
windows-drivers / probe-and-proto (push) Successful in 23s
windows-drivers / driver-build (push) Successful in 1m27s
ci / rust (push) Successful in 17m32s
deb / build-publish (push) Successful in 11m37s
docker / deploy-docs (push) Successful in 51s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 19m36s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 19m24s
windows-host / package (push) Successful in 14m21s
apple / swift (push) Has been cancelled
apple / screenshots (push) Has been cancelled
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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,
|
/// 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
|
/// 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
|
/// §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.
|
/// truth this mirrors). Anything else echoes the latched command.
|
||||||
fn deck_feature_reply() -> [u8; 64] {
|
fn deck_feature_reply() -> [u8; 64] {
|
||||||
let last = LAST_SET_FEATURE.lock().map(|g| *g).unwrap_or([0u8; 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
|
// Per-pad unit id "PF" + the pad index the host stamped into the section — matches
|
||||||
// channel hasn't attached yet) — matches steam_proto::deck_unit_id / deck_serial, so two
|
// steam_proto::deck_unit_id / deck_serial, so two virtual Decks never collide in Steam's eyes.
|
||||||
// virtual Decks never collide in Steam's eyes.
|
let unit_id: u32 = 0x5046_0000 | pad_index() as u32;
|
||||||
let idx = CHANNEL
|
|
||||||
.data()
|
|
||||||
.map(|v| v.read_u32(OFF_PAD_INDEX))
|
|
||||||
.unwrap_or(0)
|
|
||||||
& 0xFF;
|
|
||||||
let unit_id: u32 = 0x5046_0000 | idx;
|
|
||||||
// Steam validates the unit serial's PREFIX before accepting it: a "PF"-leading serial is
|
// 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
|
// 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
|
// 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
|
// 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
|
// for the Edge PID regardless of the firmware version at 0x20[44..46]); DualShock 4 uses
|
||||||
// 0x02/0x12/0xa3.
|
// 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, 0x05) => &DS_FEATURE_CALIBRATION,
|
||||||
(0 | 2, 0x09) => &DS_FEATURE_PAIRING,
|
(0 | 2, 0x09) => &ds_pairing,
|
||||||
(0 | 2, 0x20) => &DS_FEATURE_FIRMWARE,
|
(0 | 2, 0x20) => &DS_FEATURE_FIRMWARE,
|
||||||
(1, 0x02) => &DS4_FEATURE_CALIBRATION,
|
(1, 0x02) => &DS4_FEATURE_CALIBRATION,
|
||||||
(1, 0x12) => &DS4_FEATURE_PAIRING,
|
(1, 0x12) => &ds4_pairing,
|
||||||
(1, 0xA3) => &DS4_FEATURE_FIRMWARE,
|
(1, 0xA3) => &DS4_FEATURE_FIRMWARE,
|
||||||
(_, other) => {
|
(_, other) => {
|
||||||
dbglog!("[pf-ds] GET_FEATURE unknown report id 0x{other:02x}");
|
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(),
|
3 => "Valve Software".into(),
|
||||||
_ => "Sony Interactive Entertainment".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 {
|
2 | 0x0010 => match devtype {
|
||||||
1 => "DEADBEEF0001".into(),
|
1 => format!("DEADBEEF00{:02X}", 0x01u8.wrapping_add(pad_index())),
|
||||||
2 => "35533AD6E775".into(),
|
2 => format!("35533AD6E7{:02X}", 0x75u8.wrapping_add(pad_index())),
|
||||||
// Per-pad Deck serial — must agree with deck_feature_reply's 0xAE answer (Steam
|
3 => format!("FVPF{:08X}", 0x5046_0000u32 | pad_index() as u32),
|
||||||
// reads both and uses the serial to identify units).
|
_ => format!("35533AD6E7{:02X}", 0x74u8.wrapping_add(pad_index())),
|
||||||
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(),
|
|
||||||
},
|
},
|
||||||
_ => match devtype {
|
_ => match devtype {
|
||||||
1 => "Wireless Controller".into(),
|
1 => "Wireless Controller".into(),
|
||||||
|
|||||||
Reference in New Issue
Block a user