Procházet zdrojové kódy

DS4: declare the full genuine report descriptor, not just the reports we use

Root cause for "the game sees no controller at all" while joy.cpl, Steam and
DS4Windows are fine.

A real DualShock 4's HID report descriptor is 467 bytes; the one here was a
130-byte reconstruction covering only the reports the firmware implements
(input 0x01, output 0x05, features 0x02/0x04). On Windows that is not a
cosmetic difference: the HID class driver only routes GET_FEATURE for report
ids that appear in the descriptor, and sizes the transfer from
caps.FeatureReportByteLength. The standard way a game confirms a device is a
genuine DS4 is to read feature 0x12 (MAC) or 0xA3 (firmware info) -- both of
which hidGyroGetCommon() already answers, with the correct genuine sizes --
but neither was DECLARED, so those reads failed inside Windows and never
reached the firmware. The game concludes it is not a DualShock 4 and ignores
it, while every consumer that only reads input reports (joy.cpl / DirectInput,
and SDL/Steam/DS4Windows, which treat the feature reads as optional and fall
back) works perfectly.

Replaced with the full 467-byte descriptor, verbatim from a real pad -- the
same bytes ViGEmBus feeds its emulated DS4, which games do accept as genuine.
Verified byte-identical to the reference, and that every report the firmware
builds or answers is declared at exactly the size it returns: input 0x01 = 63,
output 0x05 = 31, features 0x02 = 36, 0x12 = 15, 0x81 = 6, 0xA3 = 48.

Also aligns the clean-PS config descriptor with a real pad while we are here:
self-powered (it has a battery) and 500 mA, per the same reference. The
DualSense descriptor was already the genuine 273-byte one and declares
0x05/0x09/0x20 at the sizes ps5GetCommon returns, so it needed no change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Jari před 2 měsíci
rodič
revize
ee6f14867e
2 změnil soubory, kde provedl 51 přidání a 13 odebrání
  1. 10 6
      OpenPuck/OpenPuck.ino
  2. 41 7
      OpenPuck/mode_hidgyro.cpp

+ 10 - 6
OpenPuck/OpenPuck.ino

@@ -98,12 +98,16 @@ void usbReenumerate(uint8_t k)
 		k); // mode's fixed HIDs (if any) + k slot interfaces
 	if (s_dynWantWebusb)
 		USBDevice.addInterface(usb_web);
-	// 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));
+	// Config descriptor: clean-PS modes report what a genuine pad reports -- self-powered (it has a
+	// battery), 500 mA, and NO remote-wakeup (they have no wake mouse to wake a host with anyway). Values
+	// from a real DS4 / ViGEmBus's emulated one: bmAttributes 0xC0, bMaxPower 0xFA. Everything else keeps
+	// required(0x80) | remote_wakeup(0x20) and the core's default power, because the wake mouse needs it.
+	if (modeIsCleanPS(g_usbMode)) {
+		USBDevice.setConfigurationAttribute(0xC0);
+		USBDevice.setConfigurationMaxPower(500);
+	} else {
+		USBDevice.setConfigurationAttribute(0x80 | 0x20);
+	}
 	USBDevice.attach();
 }
 

+ 41 - 7
OpenPuck/mode_hidgyro.cpp

@@ -12,12 +12,18 @@
 
 HidGyroController g_hidGyroCtl;
 
-// Genuine DualShock 4 (054C:05C4) USB report descriptor. Input report 0x01 declares 63 payload bytes
-// (4 stick bytes, hat + 14 buttons + 6-bit counter, 2 trigger bytes, 54 vendor bytes) = 64 on the wire with
-// the id, matching what hidGyroBuild() sends. Do NOT collapse the stick block into the hat item: hosts that
-// parse the descriptor (Windows RawInput/HIDClass, macOS IOHIDManager) size every read from the DECLARED
-// length, so a short declaration both shifts all field offsets and makes SDL's PS4 driver drop the report
-// (HIDAPI_DriverPS4_IsPacketValid requires size >= 64) -- the whole pad reads as dead.
+// Genuine DualShock 4 (054C:05C4) USB HID report descriptor -- all 467 bytes, verbatim from a real pad
+// (same bytes ViGEmBus feeds its emulated DS4, which games accept as genuine).
+//
+// The input/output reports are the ones we actually use -- input 0x01 = 63 payload bytes (+ id = 64 on the
+// wire, what hidGyroBuild() sends) and output 0x05 = 31 -- but the long tail of FEATURE declarations is not
+// decoration, it is load-bearing on Windows: the HID class driver only routes GET_FEATURE for report ids that
+// appear HERE, and sizes the transfer from caps.FeatureReportByteLength. A game identifying a DS4 does that
+// by reading feature 0x12 (MAC) / 0xA3 (firmware info) -- the two hidGyroGetCommon() answers -- so with a
+// descriptor that declares only 0x02/0x04 those reads fail inside Windows, never reach us, and the game
+// concludes the device is not a DualShock 4: no controller at all, even though input reports flow fine
+// (joy.cpl, Steam and DS4Windows all work, since they read input reports and treat the feature reads as
+// optional). Do not trim this back to "just the reports we implement".
 static const uint8_t GYRO_HID_DESC[] = {
 	0x05, 0x01, 0x09, 0x05, 0xA1, 0x01, 0x85, 0x01, 0x09, 0x30, 0x09, 0x31,
 	0x09, 0x32, 0x09, 0x35, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x75, 0x08, 0x95,
@@ -29,7 +35,35 @@ static const uint8_t GYRO_HID_DESC[] = {
 	0x34, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x75, 0x08, 0x95, 0x02, 0x81, 0x02,
 	0x06, 0x00, 0xFF, 0x09, 0x21, 0x95, 0x36, 0x81, 0x02, 0x85, 0x05, 0x09,
 	0x22, 0x95, 0x1F, 0x91, 0x02, 0x85, 0x04, 0x09, 0x23, 0x95, 0x24, 0xB1,
-	0x02, 0x85, 0x02, 0x09, 0x24, 0x95, 0x24, 0xB1, 0x02, 0xC0
+	0x02, 0x85, 0x02, 0x09, 0x24, 0x95, 0x24, 0xB1, 0x02, 0x85, 0x08, 0x09,
+	0x25, 0x95, 0x03, 0xB1, 0x02, 0x85, 0x10, 0x09, 0x26, 0x95, 0x04, 0xB1,
+	0x02, 0x85, 0x11, 0x09, 0x27, 0x95, 0x02, 0xB1, 0x02, 0x85, 0x12, 0x06,
+	0x02, 0xFF, 0x09, 0x21, 0x95, 0x0F, 0xB1, 0x02, 0x85, 0x13, 0x09, 0x22,
+	0x95, 0x16, 0xB1, 0x02, 0x85, 0x14, 0x06, 0x05, 0xFF, 0x09, 0x20, 0x95,
+	0x10, 0xB1, 0x02, 0x85, 0x15, 0x09, 0x21, 0x95, 0x2C, 0xB1, 0x02, 0x06,
+	0x80, 0xFF, 0x85, 0x80, 0x09, 0x20, 0x95, 0x06, 0xB1, 0x02, 0x85, 0x81,
+	0x09, 0x21, 0x95, 0x06, 0xB1, 0x02, 0x85, 0x82, 0x09, 0x22, 0x95, 0x05,
+	0xB1, 0x02, 0x85, 0x83, 0x09, 0x23, 0x95, 0x01, 0xB1, 0x02, 0x85, 0x84,
+	0x09, 0x24, 0x95, 0x04, 0xB1, 0x02, 0x85, 0x85, 0x09, 0x25, 0x95, 0x06,
+	0xB1, 0x02, 0x85, 0x86, 0x09, 0x26, 0x95, 0x06, 0xB1, 0x02, 0x85, 0x87,
+	0x09, 0x27, 0x95, 0x23, 0xB1, 0x02, 0x85, 0x88, 0x09, 0x28, 0x95, 0x22,
+	0xB1, 0x02, 0x85, 0x89, 0x09, 0x29, 0x95, 0x02, 0xB1, 0x02, 0x85, 0x90,
+	0x09, 0x30, 0x95, 0x05, 0xB1, 0x02, 0x85, 0x91, 0x09, 0x31, 0x95, 0x03,
+	0xB1, 0x02, 0x85, 0x92, 0x09, 0x32, 0x95, 0x03, 0xB1, 0x02, 0x85, 0x93,
+	0x09, 0x33, 0x95, 0x0C, 0xB1, 0x02, 0x85, 0xA0, 0x09, 0x40, 0x95, 0x06,
+	0xB1, 0x02, 0x85, 0xA1, 0x09, 0x41, 0x95, 0x01, 0xB1, 0x02, 0x85, 0xA2,
+	0x09, 0x42, 0x95, 0x01, 0xB1, 0x02, 0x85, 0xA3, 0x09, 0x43, 0x95, 0x30,
+	0xB1, 0x02, 0x85, 0xA4, 0x09, 0x44, 0x95, 0x0D, 0xB1, 0x02, 0x85, 0xA5,
+	0x09, 0x45, 0x95, 0x15, 0xB1, 0x02, 0x85, 0xA6, 0x09, 0x46, 0x95, 0x15,
+	0xB1, 0x02, 0x85, 0xF0, 0x09, 0x47, 0x95, 0x3F, 0xB1, 0x02, 0x85, 0xF1,
+	0x09, 0x48, 0x95, 0x3F, 0xB1, 0x02, 0x85, 0xF2, 0x09, 0x49, 0x95, 0x0F,
+	0xB1, 0x02, 0x85, 0xA7, 0x09, 0x4A, 0x95, 0x01, 0xB1, 0x02, 0x85, 0xA8,
+	0x09, 0x4B, 0x95, 0x01, 0xB1, 0x02, 0x85, 0xA9, 0x09, 0x4C, 0x95, 0x08,
+	0xB1, 0x02, 0x85, 0xAA, 0x09, 0x4E, 0x95, 0x01, 0xB1, 0x02, 0x85, 0xAB,
+	0x09, 0x4F, 0x95, 0x39, 0xB1, 0x02, 0x85, 0xAC, 0x09, 0x50, 0x95, 0x39,
+	0xB1, 0x02, 0x85, 0xAD, 0x09, 0x51, 0x95, 0x0B, 0xB1, 0x02, 0x85, 0xAE,
+	0x09, 0x52, 0x95, 0x01, 0xB1, 0x02, 0x85, 0xAF, 0x09, 0x53, 0x95, 0x02,
+	0xB1, 0x02, 0x85, 0xB0, 0x09, 0x54, 0x95, 0x3F, 0xB1, 0x02, 0xC0
 };
 #define DS4_TOUCH_H 942
 #define DS4_STATUS_USB 0x1B // cable + level 11 (full)