Explorar o código

Guarantee the clean PlayStation modes are non-composite

The "game/clean" DS4/DualSense modes (7/8) already dropped the wake mouse,
WebUSB and CDC, but two things could still make the device look like more
than a plain Sony pad to a host that classifies it -- GameInput /
Windows.Gaming.Input and the native-PlayStation paths in games look at the
whole device, not just the VID/PID, and refuse the PS path for a composite.

 - maxSlots() allowed up to NSLOT gamepad interfaces in the clean modes, so
   a second connected controller made the device composite again (MI_00 /
   MI_01 on Windows). The clean modes are now deliberately single-pad; use
   MODE_PS5 / MODE_HIDGYRO (composite either way, and they keep the panel)
   for several controllers at once.
 - usbReenumerate() advertised remote-wakeup unconditionally. Clean modes
   have no wake mouse to wake a host with, and a genuine DualSense/DS4 does
   not claim the capability, so they now advertise 0x80 only -- the same
   reasoning the static PS3 path already applied.

Also spell the composite/non-composite split out in the README and in the
panel's mode-switch warning, including the single-controller trade-off and
the D-pad chords that reach the clean modes without the panel.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Jari hai 2 meses
pai
achega
571864ba4b
Modificáronse 5 ficheiros con 41 adicións e 11 borrados
  1. 6 1
      OpenPuck/OpenPuck.ino
  2. 10 3
      OpenPuck/mode_hidgyro.cpp
  3. 10 3
      OpenPuck/mode_ps5.cpp
  4. 14 3
      README.md
  5. 1 1
      docs/index.html

+ 6 - 1
OpenPuck/OpenPuck.ino

@@ -98,7 +98,12 @@ void usbReenumerate(uint8_t k)
 		k); // mode's fixed HIDs (if any) + k slot interfaces
 	if (s_dynWantWebusb)
 		USBDevice.addInterface(usb_web);
-	USBDevice.setConfigurationAttribute(0x80 | 0x20);
+	// bmAttributes: required(0x80) | remote_wakeup(0x20). Clean-PS modes advertise 0x80 only, like the
+	// genuine pad -- they have no wake mouse to wake a host with anyway, and a classifier that compares the
+	// config descriptor against a real DualSense/DS4 should find nothing extra. (Same reasoning as the
+	// static PS3 path in setup().)
+	USBDevice.setConfigurationAttribute(
+		modeIsCleanPS(g_usbMode) ? 0x80 : (0x80 | 0x20));
 	USBDevice.attach();
 }
 

+ 10 - 3
OpenPuck/mode_hidgyro.cpp

@@ -195,11 +195,18 @@ static void hidGyroBuild(uint8_t usbSlot, uint8_t slot, uint8_t out[63])
 void HidGyroController::begin()
 {
 }
-// HID budget: clean DS4 (MODE_DS4_GAME) has no wake mouse; normal HIDGYRO keeps it (1 HID).
 uint8_t HidGyroController::maxSlots() const
 {
-	uint8_t cap = modeIsCleanPS(g_usbMode) ? (uint8_t)CFG_TUD_HID :
-						 (uint8_t)(CFG_TUD_HID - 1);
+	// Clean-PS modes exist to present exactly what a host that CLASSIFIES the USB device expects of a real
+	// Sony pad -- GameInput / Windows.Gaming.Input and the native-PlayStation paths in games look at the
+	// whole device, not just the VID/PID, and refuse the PS glyph path for a composite. One connected
+	// controller = one HID interface = a non-composite device (no MI_xx on Windows). A SECOND controller
+	// would add a second interface and make it composite again, so the clean modes are deliberately
+	// single-pad; use MODE_PS5 / MODE_HIDGYRO (composite either way, and they keep the panel + host-wake)
+	// when you want several controllers at once.
+	if (modeIsCleanPS(g_usbMode))
+		return 1;
+	uint8_t cap = (uint8_t)(CFG_TUD_HID - 1);
 	return cap < NSLOT ? cap : (uint8_t)NSLOT;
 }
 void HidGyroController::usbIdentity()

+ 10 - 3
OpenPuck/mode_ps5.cpp

@@ -200,11 +200,18 @@ static void ps5Build(uint8_t usbSlot, uint8_t slot, uint8_t out[63])
 void Ps5Controller::begin()
 {
 }
-// HID budget: clean PS modes have no wake mouse (CFG_TUD_HID slots); normal PS5 keeps the wake mouse (1 HID).
 uint8_t Ps5Controller::maxSlots() const
 {
-	uint8_t cap = modeIsCleanPS(g_usbMode) ? (uint8_t)CFG_TUD_HID :
-						 (uint8_t)(CFG_TUD_HID - 1);
+	// Clean-PS modes exist to present exactly what a host that CLASSIFIES the USB device expects of a real
+	// Sony pad -- GameInput / Windows.Gaming.Input and the native-PlayStation paths in games look at the
+	// whole device, not just the VID/PID, and refuse the PS glyph path for a composite. One connected
+	// controller = one HID interface = a non-composite device (no MI_xx on Windows). A SECOND controller
+	// would add a second interface and make it composite again, so the clean modes are deliberately
+	// single-pad; use MODE_PS5 / MODE_HIDGYRO (composite either way, and they keep the panel + host-wake)
+	// when you want several controllers at once.
+	if (modeIsCleanPS(g_usbMode))
+		return 1;
+	uint8_t cap = (uint8_t)(CFG_TUD_HID - 1);
 	return cap < NSLOT ? cap : (uint8_t)NSLOT;
 }
 void Ps5Controller::usbIdentity()

+ 14 - 3
README.md

@@ -44,9 +44,20 @@ Similarly you can hold all 4 back buttons and press Y to switch (teehee) over to
 | WebUSB panel → mode 11 | DirectInput (flight/space sims) | Every axis at once, as two DirectInput joysticks |
 | WebUSB panel → mode 12 | SInput (SDL-native) | Sticks + analog triggers + gyro + both trackpads + battery |
 
-**PlayStation modes.** Modes 5 (DualSense) and 6 (DS4) also come in "game/clean" variants — panel modes 7 and
-8 — which drop the wake-mouse and WebUSB interfaces so the device enumerates as *nothing but* a PlayStation
-pad, which is what titles that classify controllers by their interface list want. A few host-side caveats:
+**PlayStation modes.** Modes 5 (DualSense) and 6 (DS4) are USB *composite* devices: alongside the pad they
+carry the wake-mouse HID and the WebUSB vendor interface, so Windows enumerates them as
+`VID_054C&PID_0CE6&MI_00` and friends. Steam, SDL and DS4Windows read the pad's HID reports directly and don't
+care — but hosts that **classify** the device do: GameInput / `Windows.Gaming.Input`, and the native-PlayStation
+paths in games (Fortnite, Pragmata, …) refuse the PlayStation path for a composite even with the correct
+VID/PID. **If a game with native PS support can't see the pad, that's why — use the "game/clean" variants,
+panel modes 7 (DualSense) and 8 (DS4).** Those enumerate as a single HID interface and nothing else: no
+wake-mouse, no WebUSB, no CDC, and no remote-wakeup bit, i.e. non-composite. The cost is that the panel can't
+reach the device in those modes (chord back with back-4 + A), and they are deliberately **single-pad** — a
+second controller would add a second interface and make the device composite again, so use mode 5/6 if you want
+several controllers at once. By default the D-pad chords land on them: back-4 + D-pad **up** = clean DS4,
+back-4 + D-pad **right** = clean DualSense.
+
+A few further host-side caveats:
 
 * **PS4 / PS5 consoles reject them.** Those consoles only accept pads that pass Sony's authentication
   challenge, which needs a real auth chip; the DS4/DS5 modes are for PCs (and PC emulators). On a PS4 the

+ 1 - 1
docs/index.html

@@ -2317,7 +2317,7 @@ for(const sel of document.querySelectorAll("select.chordD")){
 for(const b of document.querySelectorAll(".modebtn")){
   b.onclick=()=>{ const m=+b.dataset.mode;
     const clean=(m===7||m===8||m===9);
-    const msg="Switch to "+MODE_NAMES[m]+"? The copycat will reboot."+(clean?"\n\nNOTE: the game/clean PlayStation modes (and PS3) drop WebUSB + host-wake, so THIS PANEL WILL DISCONNECT and can't reach the device while it's in this mode. To get back, chord on the controller: hold all four back paddles (L4+R4+L5+R5) + A to return to Steam mode. The back4+D-pad chords are how you get back INTO these modes without the panel — check their assignments in the chords card first.":"");
+    const msg="Switch to "+MODE_NAMES[m]+"? The copycat will reboot."+(clean?"\n\nNOTE: the game/clean PlayStation modes (and PS3) drop WebUSB + host-wake, so THIS PANEL WILL DISCONNECT and can't reach the device while it's in this mode. They enumerate as a single non-composite HID pad (which is what games with native PlayStation support require) and are therefore SINGLE-CONTROLLER: use mode 5/6 for several controllers at once. To get back, chord on the controller: hold all four back paddles (L4+R4+L5+R5) + A to return to Steam mode. The back4+D-pad chords are how you get back INTO these modes without the panel — check their assignments in the chords card first.":"");
     if(confirm(msg)){ send([0x03, m]); log("mode switch requested — device will reboot"); } };
 }
 updateUf2UI();