Findings from six real-world PS3-console-to-DS3 USB link-layer captures
(CircumSpector/Research, "Sony DualShock 3":
CECHZC2E-A1, CECHZC2U-A2 and SIXAXIS consoles), analysed with a local
tshark (the Wireshark MCP analyze_pcap tool cannot dissect USB pcaps -
see the recipe at the bottom).
This informed the soft-fail MAC discovery and USB startup rework for
issue #321; nothing here
changes behaviour for a genuine, fully-compliant controller.
Identical across all three consoles and all six samples:
SET_IDLE(class request0x0A). A genuine DS3 STALLs this - not worth replicating.GET_REPORT Feature 0x01(identification, see issue #50).GET_REPORT Feature 0xF2(device Bluetooth MAC address).GET_REPORT Feature 0xF5(host Bluetooth MAC address the device is currently paired to).- Only when the host radio address differs from what the device already
has:
SET_REPORT Feature 0xF5(pairing request), then a verifyingGET_REPORT Feature 0xF527-75 ms later (varies by sample). 0xEF/0xF8calibration-page reads (motion sensor calibration data; DsHidMini soft-reads page0xA0via Feature0xEFon USB start and caches it there, seeMOTION.md;0xF8is still unused). This step is USB-only. Every capture below is a USB link-layer trace; there is no Bluetooth equivalent because the PS3 never revisits0x01/0xEF/0xF8/0xF7once a pad is paired - it reads them once over the cable during pairing and never again over the air. See "Bluetooth has no equivalent read" below.SET_REPORT Output 0x01on EP0 (control endpoint), 48 bytes, no report ID, all zeros. This is the pre-enable output report and the reason DsHidMini now sends an equivalent EP0 report duringDsUsb_PrepareHardwareinstead of the historical interrupt-OUT write.GET_REPORT Feature 0xF7(unknown purpose, not emulated).- The console waits for the PS button to be pressed. The disabled pad
emits exactly one input report during this wait (Report ID
0x01, PS-button bit set), then goes quiet again - no other feature or output traffic happens while waiting. SET_REPORT Feature 0xF4 42 0C 00 00(enable/start streaming).- LED-state report on EP0 (
SET_REPORT Output 0x01), sent twice in a row, mirrored by DsHidMini as a single post-enable EP0 report inDsUsb_D0Entry. - From here on, everything (LEDs, rumble) is sent on interrupt OUT only:
~2900 output transfers observed per sample, locked to the console's
60 Hz vsync, and resent every frame even when unchanged - the
console never rate-limits or diffs output reports. Two EP0 state
refreshes recur on ~5 s and ~9 s timers for the whole session (see LED
timing below); no other feature requests occur after
0xF4. Interrupt OUT sees zero NAKs or retries in any sample. - On unplug/disable:
SET_REPORT Feature 0xF4 42 0B 00 00, preceded by one more all-zero EP0 report (same shape as step 7).
No SET_IDLE, 0xF7, or 0xF8 traffic is emulated by DsHidMini. Feature
0xEF page 0xA0 is soft-read on USB start for motion calibration (failure
does not abort device start) and cached on the USB devnode. They remain
documented here so future quirk work does not need to re-derive them from
the pcaps.
An earlier pass of this driver (PR 547 / issue #217) built a Bluetooth
0x53 SET / 0x43 GET Feature transport and used it to re-run steps 2 and
6 wirelessly, on the mistaken assumption that a Bluetooth pcap had shown the
same reads. It had not: every capture cited in this document, including the
one literally named ..._Bluetooth_Pairing.txt, is a USB link-layer
trace of the console pairing a pad over the cable - none of them contain a
Bluetooth HID transaction. Checked against Linux's hid-sony
(sixaxis_set_operational_bt), the USB Host Shield PS3BT library, and a
clean-room DS3 emulator (OpenPuck), the only Bluetooth Feature traffic any
of them ever sends is SET_REPORT on 0xF4 (see below); none of them issue
GET_REPORT for 0x01, 0xEF, 0xF2, 0xF5, 0xF7, or 0xF8 over
Bluetooth, and nothing in this repository's captures proves the pad answers
one. DsHidMini therefore no longer asks a Bluetooth-connected pad for its
identification or EEPROM; it reads back whichever USB instance last cached
them for that pad's Bluetooth MAC (DsDevice_ReadCachedWiredProperties,
driver/Device.c), matching how the PS3 itself only ever reads them over
USB, while pairing.
Linux hid-sony and the USB Host Shield library both send SET_REPORT
Feature 0xF4, payload 42 03 00 00, right after connecting over
Bluetooth - a different payload than the USB 42 0C 00 00 in step 10 above.
DsHidMini's existing post-connect workaround (DsBth_Ds3SixaxisInit) already
sends the 42 03 form, but only as a fallback after 1 second with no input
report at all; it does not run on every connect and does not explain a
correctly-streaming pad whose gyro axis alone reads a near-zero constant.
Whether some form of 0xF4 is required for the gyro axis specifically
(as opposed to the whole input stream) is unconfirmed pending a live capture
against a genuine pad exhibiting the symptom; see the session log in
MOTION.md.
- EP0 (control endpoint) output report: 48 bytes, no report ID byte.
- Interrupt OUT output report: 49 bytes, byte 0 is report ID
0x01(DS3_USB_HID_OUTPUT_REPORT_SIZE/G_Ds3UsbHidOutputReport). - Input reports: observed at roughly 100 Hz while streaming.
- Motor durations (interrupt OUT bytes 2 and 4) are always
0x96, never0xFF, across every sample and every rumble instruction observed. - The small (right) motor strength byte is strictly
0x00or0x01(on/off only); the big (left) motor strength byte is a full 0-255 power value. - Bytes
[6..7]of the interrupt OUT report (observed asff 77,ff 7f, or00 00depending on sample) are an inserted0xFFfollowed by the EEPROM gyro calibration byte (page0xA0offset 14-15), not a verbatim copy of that page. One console/pad pairing (B1) reads these back as zero, so DsHidMini sending zeros here is a valid, real-world value, not a gap. A SIXAXIS gets them at bytes[4..5]instead - seeMOTION.md. - Unused LED pattern blocks (for player slots that are off) are all-zero.
- Initial LED effect (set immediately at enable):
ff 00 01 00 01. - Roughly 5 seconds after enable, the console switches to
ff 27 10 32 32and stays there for the remainder of the session. This is relevant to the (separate) LED-handling-unification plan's "static effect" assumption - not changed by this work, just recorded here.
-
Retro Fighters Defender (OG), in its native
054C:0CDAUSB mode, exposes only an interrupt IN endpoint - there is no interrupt OUT pipe at all. This is the primary real-world caseDsUsb_PrepareHardware's pipe validation now tolerates (falls back toDsUsbOutputReportTransportControlEndpoint, seedriver/DsUsb.canddriver/DsCommon.h).054C:0CDAis not a DualShock 3 identity at all - it is the PlayStation Classic controller identity (49-byte report descriptor, one IN endpoint, digital buttons only). It already works on Windows as a plain HID gamepad without DsHidMini, and binding DsHidMini to it would misparse PS-Classic input reports as DS3 reports, so it is intentionally not added todshidmini.inf. The dongle's other two modes, XInput (045E:028E) and generic ShanWan DirectInput (2563:0575, 137-byte descriptor with the0x2621PS3 "magic" feature), also already work without DsHidMini. Whether the OG dongle can ever present a054C:0268DS3 identity on a real PS3 (like the BT variant below) is unconfirmed and would need a hardware capture to settle. -
Retro Fighters Defender (Bluetooth Edition) starts a PS3 USB session enumerated as a DualShock 4 (
054C:05C4,bcdDevice 0x0221, 467-byte report descriptor - a genuine DS4 isbcdDevice 0x0100with a 483-byte descriptor, so the two are distinguishable without touching the controller). In that identity it answers both0xF2and0xF5correctly, never STALLsSET_IDLE, and starts streaming input reports before the console ever sends0xF4. The actual host-detection trick, confirmed from a real PS3 capture (2024-05-14_PS3-plugin-and-rumble.pcap, CircumSpector/Research), is a DS4 HID feature report:- PS3 does
SET_PROTOCOL,SET_IDLE, reads one interrupt IN report from the DS4 identity, then sendsSET_REPORT Feature 0x14, 17 bytes:14 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00. - The device stops answering interrupt IN, drops off the bus, and
roughly 1 second later re-enumerates as a DualShock 3
(
054C:0268,bcdUSB 1.10,bcdDevice 0x0100, 148-byte descriptor, interrupt EP1 IN + EP2 OUT). - In DS3 mode it answers
GET 0xF5with its stored host MAC and otherwise follows the normal DS3 startup sequence documented above (0xF4, EP0 output report, interrupt OUT at ~16 ms).
2024-05-14_DS4Rev1-on-PS3.pcapconfirms the PS3 sends the exact same0x14report to a genuine DS4 every ~1 second for the whole session - the DS4 simply ignores it - so replaying this report from a Windows-side tool is harmless to real DS4 controllers.2024-05-04_Windows-PC-plugin-capture.pcapngconfirms Windows itself never sendsFeature 0x14, which is the only reason the Defender BT never leaves DS4 mode when plugged into a PC. Replaying that report viaHidD_SetFeatureis the right USB packet (Feature, report ID0x14, 17 bytes starting14 02 …), but a successfulHidD_SetFeatureonly means the transfer was ACKed. A live Defender in DS4 mode (054C:05C4bcdDevice 0x0221) can ACK the report and stay on the bus; GET Feature0x14times out (ERROR_SEM_TIMEOUT/ 121). The PS3 sent the probe a few milliseconds afterSET_IDLE, before sustained interrupt IN. A late probe from an already-streaming Windows session is often ignored. ControlApp therefore treats disappearance of the DS4 identity plus appearance ofUSB\VID_054C&PID_0268as the only success signal, sends the probe immediately on HID arrival (auto-switch or a pending retry), and cycles the USB port (administrator) when a late button click is ignored so the next enumeration can be probed while it still looks like a PS3 attach.dshidmini.infalready binds the DS3 identity. A follow-up on the same capture pads, with054C:05C4REV_0221Zadig-bound to WinUSB (serviceWinUSB, no HidUsb child, no interrupt IN started), sent the PS3 EP0 sequence withresearch/ds3-motion/probe/WinUsbRaw.cs:SET_PROTOCOL(report protocol),SET_IDLE, then Feature0x14(17 bytes14 02 00…).WinUsb_ControlTransfersucceeded for that sequence and for the variants Feature0x14alone,SET_PROTOCOL+0x14, and one interrupt IN (64-byte DS4 input starting01 80 80 80…) then0x14. None of them detached the DualShock 4 identity or producedUSB\VID_054C&PID_0268. So a late Windows host - even when it owns EP0 and does not start HID polling - is not equivalent to the PS3 attach window. The remaining gap is whatever Windows already sent during the original hidusb / Zadig enumeration (and possiblybcdUSB 2.00vs the PS3 session), not a mangled0x14payload.The relevant
tsharkfilters used against the captures above (run locally; see theanalyze_pcaplimitation note below):tshark -r 2024-05-14_PS3-plugin-and-rumble.pcap -Y "usb.device_address == <addr> && usb.control_transfer" -T fields -e frame.number -e usb.device_address -e usb.setup.bRequest -e usb.setup.wValue -e usb.capdata tshark -r 2024-05-14_PS3-plugin-and-rumble.pcap -Y "usb.src == 'host' && usbll.data && usbll.pid == 0x2d" -T fields -e frame.number -e usbll.data - PS3 does
-
Linux
hid-sonyand SDL both treat a failed0xF2as fatal to Bluetooth-address discovery (but not to basic HID functionality), never sendFeature 0xF4over USB at all, force all USB output through EP0 without a report ID, and defer their very first output report until after the first input report has been seen - because some ShanWan-brand clones rumble continuously and unprompted on interrupt OUT the moment the pipe is opened.
These observations are why DsHidMini's fallback for a device that never
answers 0xF2 synthesizes a deterministic MAC address (FNV-1a hash of the
device's PnP instance ID, locally-administered bit set) instead of failing
device start, and why the interrupt OUT pipe itself is now optional.
The bundled Wireshark MCP analyze_pcap tool could not dissect these
captures (USB link-layer, not IP/TCP) and mis-quoted the tshark.exe path
on this machine. Using a local Wireshark install's tshark.exe directly
worked well:
# Dump every USB frame with the fields that matter for HID class requests
& 'C:\Program Files\Wireshark\tshark.exe' -r capture.pcapng `
-Y usb `
-T fields `
-e frame.number -e frame.time_relative -e usbll.src -e usbll.dst `
-e usb.setup.bRequest -e usb.setup.wValue -e usb.setup.wLength `
-e usbhid.setup.ReportType -e usbhid.setup.ReportID `
-e usbll.data
# usbll.src of the form "<address>.1" (endpoint 1) identifies interrupt IN
# input reports; PowerShell-side filtering on that pattern was more
# reliable than trying to express it as a tshark display filter directly
# (embedded quoting gets mangled by PowerShell either way).Field notes:
usb.setup.bRequest/usb.setup.wValuedecode the HID class request (GET_REPORT=0x01,SET_REPORT=0x09;wValuehigh byte is the report type -0x03Feature,0x01Output - low byte the report ID).usbhid.setup.ReportID/usb.setup.wLengthare the quickest way to spot the 48-byte, ID-less EP0 output reports versus the 49-byte, ID-prefixed interrupt OUT ones.usbll.datagives the raw payload bytes for manual inspection once a frame of interest has been located.