1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21/* This driver supports both simplified reports and the extended input reports enabled by Steam.
22 Code and logic contributed by Valve Corporation under the SDL zlib license.
23*/
24#include "SDL_internal.h"
25
26#ifdef SDL_JOYSTICK_HIDAPI
27
28#include "../../SDL_hints_c.h"
29#include "../SDL_sysjoystick.h"
30#include "SDL_hidapijoystick_c.h"
31#include "SDL_hidapi_rumble.h"
32
33#ifdef SDL_JOYSTICK_HIDAPI_PS4
34
35// Define this if you want to log all packets from the controller
36#if 0
37#define DEBUG_PS4_PROTOCOL
38#endif
39
40// Define this if you want to log calibration data
41#if 0
42#define DEBUG_PS4_CALIBRATION
43#endif
44
45#define BLUETOOTH_DISCONNECT_TIMEOUT_MS 500
46
47#define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8))
48#define LOAD32(A, B, C, D) ((((Uint32)(A)) << 0) | \
49 (((Uint32)(B)) << 8) | \
50 (((Uint32)(C)) << 16) | \
51 (((Uint32)(D)) << 24))
52
53enum
54{
55 SDL_GAMEPAD_BUTTON_PS4_TOUCHPAD = 11
56};
57
58typedef enum
59{
60 k_EPS4ReportIdUsbState = 1,
61 k_EPS4ReportIdUsbEffects = 5,
62 k_EPS4ReportIdBluetoothState1 = 17,
63 k_EPS4ReportIdBluetoothState2 = 18,
64 k_EPS4ReportIdBluetoothState3 = 19,
65 k_EPS4ReportIdBluetoothState4 = 20,
66 k_EPS4ReportIdBluetoothState5 = 21,
67 k_EPS4ReportIdBluetoothState6 = 22,
68 k_EPS4ReportIdBluetoothState7 = 23,
69 k_EPS4ReportIdBluetoothState8 = 24,
70 k_EPS4ReportIdBluetoothState9 = 25,
71 k_EPS4ReportIdBluetoothEffects = 17,
72 k_EPS4ReportIdDisconnectMessage = 226,
73} EPS4ReportId;
74
75typedef enum
76{
77 k_ePS4FeatureReportIdGyroCalibration_USB = 0x02,
78 k_ePS4FeatureReportIdCapabilities = 0x03,
79 k_ePS4FeatureReportIdGyroCalibration_BT = 0x05,
80 k_ePS4FeatureReportIdSerialNumber = 0x12,
81} EPS4FeatureReportID;
82
83typedef struct
84{
85 Uint8 ucLeftJoystickX;
86 Uint8 ucLeftJoystickY;
87 Uint8 ucRightJoystickX;
88 Uint8 ucRightJoystickY;
89 Uint8 rgucButtonsHatAndCounter[3];
90 Uint8 ucTriggerLeft;
91 Uint8 ucTriggerRight;
92 Uint8 rgucTimestamp[2];
93 Uint8 _rgucPad0[1];
94 Uint8 rgucGyroX[2];
95 Uint8 rgucGyroY[2];
96 Uint8 rgucGyroZ[2];
97 Uint8 rgucAccelX[2];
98 Uint8 rgucAccelY[2];
99 Uint8 rgucAccelZ[2];
100 Uint8 _rgucPad1[5];
101 Uint8 ucBatteryLevel;
102 Uint8 _rgucPad2[4];
103 Uint8 ucTouchpadCounter1;
104 Uint8 rgucTouchpadData1[3];
105 Uint8 ucTouchpadCounter2;
106 Uint8 rgucTouchpadData2[3];
107} PS4StatePacket_t;
108
109typedef struct
110{
111 Uint8 ucRumbleRight;
112 Uint8 ucRumbleLeft;
113 Uint8 ucLedRed;
114 Uint8 ucLedGreen;
115 Uint8 ucLedBlue;
116 Uint8 ucLedDelayOn;
117 Uint8 ucLedDelayOff;
118 Uint8 _rgucPad0[8];
119 Uint8 ucVolumeLeft;
120 Uint8 ucVolumeRight;
121 Uint8 ucVolumeMic;
122 Uint8 ucVolumeSpeaker;
123} DS4EffectsState_t;
124
125typedef struct
126{
127 Sint16 bias;
128 float scale;
129} IMUCalibrationData;
130
131/* Rumble hint mode:
132 * "0": enhanced features are never used
133 * "1": enhanced features are always used
134 * "auto": enhanced features are advertised to the application, but SDL doesn't touch the controller state unless the application explicitly requests it.
135 */
136typedef enum
137{
138 PS4_ENHANCED_REPORT_HINT_OFF,
139 PS4_ENHANCED_REPORT_HINT_ON,
140 PS4_ENHANCED_REPORT_HINT_AUTO
141} HIDAPI_PS4_EnhancedReportHint;
142
143typedef struct
144{
145 SDL_HIDAPI_Device *device;
146 SDL_Joystick *joystick;
147 bool is_dongle;
148 bool is_nacon_dongle;
149 bool official_controller;
150 bool sensors_supported;
151 bool lightbar_supported;
152 bool vibration_supported;
153 bool touchpad_supported;
154 bool effects_supported;
155 HIDAPI_PS4_EnhancedReportHint enhanced_report_hint;
156 bool enhanced_reports;
157 bool enhanced_mode;
158 bool enhanced_mode_available;
159 Uint8 report_interval;
160 bool report_sensors;
161 bool report_touchpad;
162 bool report_battery;
163 bool hardware_calibration;
164 IMUCalibrationData calibration[6];
165 Uint64 last_packet;
166 int player_index;
167 Uint8 rumble_left;
168 Uint8 rumble_right;
169 bool color_set;
170 Uint8 led_red;
171 Uint8 led_green;
172 Uint8 led_blue;
173 Uint16 gyro_numerator;
174 Uint16 gyro_denominator;
175 Uint16 accel_numerator;
176 Uint16 accel_denominator;
177 Uint64 sensor_ticks;
178 Uint16 last_tick;
179 Uint16 valid_crc_packets; // wrapping counter
180 PS4StatePacket_t last_state;
181} SDL_DriverPS4_Context;
182
183static bool HIDAPI_DriverPS4_InternalSendJoystickEffect(SDL_DriverPS4_Context *ctx, const void *effect, int size, bool application_usage);
184
185static void HIDAPI_DriverPS4_RegisterHints(SDL_HintCallback callback, void *userdata)
186{
187 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS4, callback, userdata);
188}
189
190static void HIDAPI_DriverPS4_UnregisterHints(SDL_HintCallback callback, void *userdata)
191{
192 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS4, callback, userdata);
193}
194
195static bool HIDAPI_DriverPS4_IsEnabled(void)
196{
197 return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS4, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));
198}
199
200static int ReadFeatureReport(SDL_hid_device *dev, Uint8 report_id, Uint8 *report, size_t length)
201{
202 SDL_memset(report, 0, length);
203 report[0] = report_id;
204 return SDL_hid_get_feature_report(dev, report, length);
205}
206
207static bool HIDAPI_DriverPS4_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
208{
209 Uint8 data[USB_PACKET_LENGTH];
210 int size;
211
212 if (type == SDL_GAMEPAD_TYPE_PS4) {
213 return true;
214 }
215
216 if (HIDAPI_SupportsPlaystationDetection(vendor_id, product_id)) {
217 if (device && device->dev) {
218 size = ReadFeatureReport(device->dev, k_ePS4FeatureReportIdCapabilities, data, sizeof(data));
219 if (size == 48 && data[2] == 0x27) {
220 // Supported third party controller
221 return true;
222 } else {
223 return false;
224 }
225 } else {
226 // Might be supported by this driver, enumerate and find out
227 return true;
228 }
229 }
230
231 return false;
232}
233
234static void SetLedsForPlayerIndex(DS4EffectsState_t *effects, int player_index)
235{
236 /* This list is the same as what hid-sony.c uses in the Linux kernel.
237 The first 4 values correspond to what the PS4 assigns.
238 */
239 static const Uint8 colors[7][3] = {
240 { 0x00, 0x00, 0x40 }, // Blue
241 { 0x40, 0x00, 0x00 }, // Red
242 { 0x00, 0x40, 0x00 }, // Green
243 { 0x20, 0x00, 0x20 }, // Pink
244 { 0x02, 0x01, 0x00 }, // Orange
245 { 0x00, 0x01, 0x01 }, // Teal
246 { 0x01, 0x01, 0x01 } // White
247 };
248
249 if (player_index >= 0) {
250 player_index %= SDL_arraysize(colors);
251 } else {
252 player_index = 0;
253 }
254
255 effects->ucLedRed = colors[player_index][0];
256 effects->ucLedGreen = colors[player_index][1];
257 effects->ucLedBlue = colors[player_index][2];
258}
259
260static bool ReadWiredSerial(SDL_HIDAPI_Device *device, char *serial, size_t serial_size)
261{
262 Uint8 data[USB_PACKET_LENGTH];
263 int size;
264
265 size = ReadFeatureReport(device->dev, k_ePS4FeatureReportIdSerialNumber, data, sizeof(data));
266 if (size >= 7 && (data[1] || data[2] || data[3] || data[4] || data[5] || data[6])) {
267 (void)SDL_snprintf(serial, serial_size, "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x",
268 data[6], data[5], data[4], data[3], data[2], data[1]);
269 return true;
270 }
271 return false;
272}
273
274static bool HIDAPI_DriverPS4_InitDevice(SDL_HIDAPI_Device *device)
275{
276 SDL_DriverPS4_Context *ctx;
277 Uint8 data[USB_PACKET_LENGTH];
278 int size;
279 char serial[18];
280 SDL_JoystickType joystick_type = SDL_JOYSTICK_TYPE_GAMEPAD;
281
282 ctx = (SDL_DriverPS4_Context *)SDL_calloc(1, sizeof(*ctx));
283 if (!ctx) {
284 return false;
285 }
286 ctx->device = device;
287
288 ctx->gyro_numerator = 1;
289 ctx->gyro_denominator = 16;
290 ctx->accel_numerator = 1;
291 ctx->accel_denominator = 8192;
292
293 device->context = ctx;
294
295 if (device->serial && SDL_strlen(device->serial) == 12) {
296 int i, j;
297
298 j = -1;
299 for (i = 0; i < 12; i += 2) {
300 j += 1;
301 SDL_memmove(&serial[j], &device->serial[i], 2);
302 j += 2;
303 serial[j] = '-';
304 }
305 serial[j] = '\0';
306 } else {
307 serial[0] = '\0';
308 }
309
310 // Check for type of connection
311 ctx->is_dongle = (device->vendor_id == USB_VENDOR_SONY && device->product_id == USB_PRODUCT_SONY_DS4_DONGLE);
312 if (ctx->is_dongle) {
313 ReadWiredSerial(device, serial, sizeof(serial));
314 ctx->enhanced_reports = true;
315 } else if (device->vendor_id == USB_VENDOR_SONY && device->product_id == USB_PRODUCT_SONY_DS4_STRIKEPAD) {
316 ctx->enhanced_reports = true;
317
318 } else if (device->vendor_id == USB_VENDOR_SONY) {
319 if (device->is_bluetooth) {
320 // Read a report to see if we're in enhanced mode
321 size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 16);
322#ifdef DEBUG_PS4_PROTOCOL
323 if (size > 0) {
324 HIDAPI_DumpPacket("PS4 first packet: size = %d", data, size);
325 } else {
326 SDL_Log("PS4 first packet: size = %d", size);
327 }
328#endif
329 if (size > 0 &&
330 data[0] >= k_EPS4ReportIdBluetoothState1 &&
331 data[0] <= k_EPS4ReportIdBluetoothState9) {
332 ctx->enhanced_reports = true;
333 }
334 } else {
335 ReadWiredSerial(device, serial, sizeof(serial));
336 ctx->enhanced_reports = true;
337 }
338 } else {
339 // Third party controllers appear to all be wired
340 ctx->enhanced_reports = true;
341 }
342
343 if (device->vendor_id == USB_VENDOR_SONY) {
344 ctx->official_controller = true;
345 ctx->sensors_supported = true;
346 ctx->lightbar_supported = true;
347 ctx->vibration_supported = true;
348 ctx->touchpad_supported = true;
349 } else {
350 // Third party controller capability request
351 size = ReadFeatureReport(device->dev, k_ePS4FeatureReportIdCapabilities, data, sizeof(data));
352 // Get the device capabilities
353 if (size == 48 && data[2] == 0x27) {
354 Uint8 capabilities = data[4];
355 Uint8 device_type = data[5];
356 Uint16 gyro_numerator = LOAD16(data[10], data[11]);
357 Uint16 gyro_denominator = LOAD16(data[12], data[13]);
358 Uint16 accel_numerator = LOAD16(data[14], data[15]);
359 Uint16 accel_denominator = LOAD16(data[16], data[17]);
360
361#ifdef DEBUG_PS4_PROTOCOL
362 HIDAPI_DumpPacket("PS4 capabilities: size = %d", data, size);
363#endif
364 if (capabilities & 0x02) {
365 ctx->sensors_supported = true;
366 }
367 if (capabilities & 0x04) {
368 ctx->lightbar_supported = true;
369 }
370 if (capabilities & 0x08) {
371 ctx->vibration_supported = true;
372 }
373 if (capabilities & 0x40) {
374 ctx->touchpad_supported = true;
375 }
376
377 switch (device_type) {
378 case 0x00:
379 joystick_type = SDL_JOYSTICK_TYPE_GAMEPAD;
380 break;
381 case 0x01:
382 joystick_type = SDL_JOYSTICK_TYPE_GUITAR;
383 break;
384 case 0x02:
385 joystick_type = SDL_JOYSTICK_TYPE_DRUM_KIT;
386 break;
387 case 0x04:
388 joystick_type = SDL_JOYSTICK_TYPE_DANCE_PAD;
389 break;
390 case 0x06:
391 joystick_type = SDL_JOYSTICK_TYPE_WHEEL;
392 break;
393 case 0x07:
394 joystick_type = SDL_JOYSTICK_TYPE_ARCADE_STICK;
395 break;
396 case 0x08:
397 joystick_type = SDL_JOYSTICK_TYPE_FLIGHT_STICK;
398 break;
399 default:
400 joystick_type = SDL_JOYSTICK_TYPE_UNKNOWN;
401 break;
402 }
403
404 if (gyro_numerator && gyro_denominator) {
405 ctx->gyro_numerator = gyro_numerator;
406 ctx->gyro_denominator = gyro_denominator;
407 }
408 if (accel_numerator && accel_denominator) {
409 ctx->accel_numerator = accel_numerator;
410 ctx->accel_denominator = accel_denominator;
411 }
412 } else if (device->vendor_id == USB_VENDOR_RAZER) {
413 // The Razer Raiju doesn't respond to the detection protocol, but has a touchpad and vibration
414 ctx->vibration_supported = true;
415 ctx->touchpad_supported = true;
416 }
417 }
418 ctx->effects_supported = (ctx->lightbar_supported || ctx->vibration_supported);
419
420 if (device->vendor_id == USB_VENDOR_NACON_ALT &&
421 device->product_id == USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS4_WIRELESS) {
422 ctx->is_nacon_dongle = true;
423 }
424
425 if (device->vendor_id == USB_VENDOR_PDP &&
426 (device->product_id == USB_PRODUCT_VICTRIX_FS_PRO ||
427 device->product_id == USB_PRODUCT_VICTRIX_FS_PRO_V2)) {
428 /* The Victrix FS Pro V2 reports that it has lightbar support,
429 * but it doesn't respond to the effects packet, and will hang
430 * on reboot if we send it.
431 */
432 ctx->effects_supported = false;
433 }
434
435 device->joystick_type = joystick_type;
436 device->type = SDL_GAMEPAD_TYPE_PS4;
437 if (ctx->official_controller) {
438 HIDAPI_SetDeviceName(device, "PS4 Controller");
439 }
440 HIDAPI_SetDeviceSerial(device, serial);
441
442 // Prefer the USB device over the Bluetooth device
443 if (device->is_bluetooth) {
444 if (HIDAPI_HasConnectedUSBDevice(device->serial)) {
445 return true;
446 }
447 } else {
448 HIDAPI_DisconnectBluetoothDevice(device->serial);
449 }
450 if ((ctx->is_dongle || ctx->is_nacon_dongle) && serial[0] == '\0') {
451 // Not yet connected
452 return true;
453 }
454 return HIDAPI_JoystickConnected(device, NULL);
455}
456
457static int HIDAPI_DriverPS4_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
458{
459 return -1;
460}
461
462static bool HIDAPI_DriverPS4_LoadOfficialCalibrationData(SDL_HIDAPI_Device *device)
463{
464 SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
465 int i, tries, size;
466 bool have_data = false;
467 Uint8 data[USB_PACKET_LENGTH];
468
469 if (!ctx->official_controller) {
470#ifdef DEBUG_PS4_CALIBRATION
471 SDL_Log("Not an official controller, ignoring calibration");
472#endif
473 return false;
474 }
475
476 for (tries = 0; tries < 5; ++tries) {
477 // For Bluetooth controllers, this report switches them into advanced report mode
478 size = ReadFeatureReport(device->dev, k_ePS4FeatureReportIdGyroCalibration_USB, data, sizeof(data));
479 if (size < 35) {
480#ifdef DEBUG_PS4_CALIBRATION
481 SDL_Log("Short read of calibration data: %d, ignoring calibration", size);
482#endif
483 return false;
484 }
485
486 if (device->is_bluetooth) {
487 size = ReadFeatureReport(device->dev, k_ePS4FeatureReportIdGyroCalibration_BT, data, sizeof(data));
488 if (size < 35) {
489#ifdef DEBUG_PS4_CALIBRATION
490 SDL_Log("Short read of calibration data: %d, ignoring calibration", size);
491#endif
492 return false;
493 }
494 }
495
496 // In some cases this report returns all zeros. Usually immediately after connection with the PS4 Dongle
497 for (i = 0; i < size; ++i) {
498 if (data[i]) {
499 have_data = true;
500 break;
501 }
502 }
503 if (have_data) {
504 break;
505 }
506
507 SDL_Delay(2);
508 }
509
510 if (have_data) {
511 Sint16 sGyroPitchBias, sGyroYawBias, sGyroRollBias;
512 Sint16 sGyroPitchPlus, sGyroPitchMinus;
513 Sint16 sGyroYawPlus, sGyroYawMinus;
514 Sint16 sGyroRollPlus, sGyroRollMinus;
515 Sint16 sGyroSpeedPlus, sGyroSpeedMinus;
516
517 Sint16 sAccXPlus, sAccXMinus;
518 Sint16 sAccYPlus, sAccYMinus;
519 Sint16 sAccZPlus, sAccZMinus;
520
521 float flNumerator;
522 float flDenominator;
523 Sint16 sRange2g;
524
525#ifdef DEBUG_PS4_CALIBRATION
526 HIDAPI_DumpPacket("PS4 calibration packet: size = %d", data, size);
527#endif
528
529 sGyroPitchBias = LOAD16(data[1], data[2]);
530 sGyroYawBias = LOAD16(data[3], data[4]);
531 sGyroRollBias = LOAD16(data[5], data[6]);
532
533 if (device->is_bluetooth || ctx->is_dongle) {
534 sGyroPitchPlus = LOAD16(data[7], data[8]);
535 sGyroYawPlus = LOAD16(data[9], data[10]);
536 sGyroRollPlus = LOAD16(data[11], data[12]);
537 sGyroPitchMinus = LOAD16(data[13], data[14]);
538 sGyroYawMinus = LOAD16(data[15], data[16]);
539 sGyroRollMinus = LOAD16(data[17], data[18]);
540 } else {
541 sGyroPitchPlus = LOAD16(data[7], data[8]);
542 sGyroPitchMinus = LOAD16(data[9], data[10]);
543 sGyroYawPlus = LOAD16(data[11], data[12]);
544 sGyroYawMinus = LOAD16(data[13], data[14]);
545 sGyroRollPlus = LOAD16(data[15], data[16]);
546 sGyroRollMinus = LOAD16(data[17], data[18]);
547 }
548
549 sGyroSpeedPlus = LOAD16(data[19], data[20]);
550 sGyroSpeedMinus = LOAD16(data[21], data[22]);
551
552 sAccXPlus = LOAD16(data[23], data[24]);
553 sAccXMinus = LOAD16(data[25], data[26]);
554 sAccYPlus = LOAD16(data[27], data[28]);
555 sAccYMinus = LOAD16(data[29], data[30]);
556 sAccZPlus = LOAD16(data[31], data[32]);
557 sAccZMinus = LOAD16(data[33], data[34]);
558
559 flNumerator = (float)(sGyroSpeedPlus + sGyroSpeedMinus) * ctx->gyro_denominator / ctx->gyro_numerator;
560 flDenominator = (float)(SDL_abs(sGyroPitchPlus - sGyroPitchBias) + SDL_abs(sGyroPitchMinus - sGyroPitchBias));
561 if (flDenominator != 0.0f) {
562 ctx->calibration[0].bias = sGyroPitchBias;
563 ctx->calibration[0].scale = flNumerator / flDenominator;
564 }
565
566 flDenominator = (float)(SDL_abs(sGyroYawPlus - sGyroYawBias) + SDL_abs(sGyroYawMinus - sGyroYawBias));
567 if (flDenominator != 0.0f) {
568 ctx->calibration[1].bias = sGyroYawBias;
569 ctx->calibration[1].scale = flNumerator / flDenominator;
570 }
571
572 flDenominator = (float)(SDL_abs(sGyroRollPlus - sGyroRollBias) + SDL_abs(sGyroRollMinus - sGyroRollBias));
573 if (flDenominator != 0.0f) {
574 ctx->calibration[2].bias = sGyroRollBias;
575 ctx->calibration[2].scale = flNumerator / flDenominator;
576 }
577
578 sRange2g = sAccXPlus - sAccXMinus;
579 ctx->calibration[3].bias = sAccXPlus - sRange2g / 2;
580 ctx->calibration[3].scale = (2.0f * ctx->accel_denominator / ctx->accel_numerator) / sRange2g;
581
582 sRange2g = sAccYPlus - sAccYMinus;
583 ctx->calibration[4].bias = sAccYPlus - sRange2g / 2;
584 ctx->calibration[4].scale = (2.0f * ctx->accel_denominator / ctx->accel_numerator) / sRange2g;
585
586 sRange2g = sAccZPlus - sAccZMinus;
587 ctx->calibration[5].bias = sAccZPlus - sRange2g / 2;
588 ctx->calibration[5].scale = (2.0f * ctx->accel_denominator / ctx->accel_numerator) / sRange2g;
589
590 ctx->hardware_calibration = true;
591 for (i = 0; i < 6; ++i) {
592#ifdef DEBUG_PS4_CALIBRATION
593 SDL_Log("calibration[%d] bias = %d, sensitivity = %f", i, ctx->calibration[i].bias, ctx->calibration[i].scale);
594#endif
595 // Some controllers have a bad calibration
596 if (SDL_abs(ctx->calibration[i].bias) > 1024 || SDL_fabsf(1.0f - ctx->calibration[i].scale) > 0.5f) {
597#ifdef DEBUG_PS4_CALIBRATION
598 SDL_Log("invalid calibration, ignoring");
599#endif
600 ctx->hardware_calibration = false;
601 }
602 }
603 } else {
604#ifdef DEBUG_PS4_CALIBRATION
605 SDL_Log("Calibration data not available");
606#endif
607 }
608 return ctx->hardware_calibration;
609}
610
611static void HIDAPI_DriverPS4_LoadCalibrationData(SDL_HIDAPI_Device *device)
612{
613 SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
614 int i;
615
616 if (!HIDAPI_DriverPS4_LoadOfficialCalibrationData(device)) {
617 for (i = 0; i < SDL_arraysize(ctx->calibration); ++i) {
618 ctx->calibration[i].bias = 0;
619 ctx->calibration[i].scale = 1.0f;
620 }
621 }
622
623 // Scale the raw data to the units expected by SDL
624 for (i = 0; i < SDL_arraysize(ctx->calibration); ++i) {
625 double scale = ctx->calibration[i].scale;
626
627 if (i < 3) {
628 scale *= ((double)ctx->gyro_numerator / ctx->gyro_denominator) * SDL_PI_D / 180.0;
629
630 if (device->vendor_id == USB_VENDOR_SONY &&
631 device->product_id == USB_PRODUCT_SONY_DS4_STRIKEPAD) {
632 // The Armor-X Pro seems to only deliver half the rotation it should
633 scale *= 2.0;
634 }
635 } else {
636 scale *= ((double)ctx->accel_numerator / ctx->accel_denominator) * SDL_STANDARD_GRAVITY;
637
638 if (device->vendor_id == USB_VENDOR_SONY &&
639 device->product_id == USB_PRODUCT_SONY_DS4_STRIKEPAD) {
640 /* The Armor-X Pro seems to only deliver half the acceleration it should,
641 * and in the opposite direction on all axes */
642 scale *= -2.0;
643 }
644 }
645 ctx->calibration[i].scale = (float)scale;
646 }
647}
648
649static float HIDAPI_DriverPS4_ApplyCalibrationData(SDL_DriverPS4_Context *ctx, int index, Sint16 value)
650{
651 IMUCalibrationData *calibration = &ctx->calibration[index];
652
653 return ((float)value - calibration->bias) * calibration->scale;
654}
655
656static bool HIDAPI_DriverPS4_UpdateEffects(SDL_DriverPS4_Context *ctx, bool application_usage)
657{
658 DS4EffectsState_t effects;
659
660 SDL_zero(effects);
661
662 if (ctx->vibration_supported) {
663 effects.ucRumbleLeft = ctx->rumble_left;
664 effects.ucRumbleRight = ctx->rumble_right;
665 }
666
667 if (ctx->lightbar_supported) {
668 // Populate the LED state with the appropriate color from our lookup table
669 if (ctx->color_set) {
670 effects.ucLedRed = ctx->led_red;
671 effects.ucLedGreen = ctx->led_green;
672 effects.ucLedBlue = ctx->led_blue;
673 } else {
674 SetLedsForPlayerIndex(&effects, ctx->player_index);
675 }
676 }
677 return HIDAPI_DriverPS4_InternalSendJoystickEffect(ctx, &effects, sizeof(effects), application_usage);
678}
679
680static void HIDAPI_DriverPS4_TickleBluetooth(SDL_HIDAPI_Device *device)
681{
682 SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
683
684 if (ctx->enhanced_reports) {
685 // This is just a dummy packet that should have no effect, since we don't set the CRC
686 Uint8 data[78];
687
688 SDL_zeroa(data);
689
690 data[0] = k_EPS4ReportIdBluetoothEffects;
691 data[1] = 0xC0; // Magic value HID + CRC
692
693 if (SDL_HIDAPI_LockRumble()) {
694 SDL_HIDAPI_SendRumbleAndUnlock(device, data, sizeof(data));
695 }
696 } else {
697#if 0 /* The 8BitDo Zero 2 has perfect emulation of a PS4 controller, except it
698 * only sends reports when the state changes, so we can't disconnect here.
699 */
700 // We can't even send an invalid effects packet, or it will put the controller in enhanced mode
701 if (device->num_joysticks > 0) {
702 HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
703 }
704#endif
705 }
706}
707
708static void HIDAPI_DriverPS4_SetEnhancedModeAvailable(SDL_DriverPS4_Context *ctx)
709{
710 if (ctx->enhanced_mode_available) {
711 return;
712 }
713 ctx->enhanced_mode_available = true;
714
715 if (ctx->touchpad_supported) {
716 SDL_PrivateJoystickAddTouchpad(ctx->joystick, 2);
717 ctx->report_touchpad = true;
718 }
719
720 if (ctx->sensors_supported) {
721 SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO, (float)(1000 / ctx->report_interval));
722 SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL, (float)(1000 / ctx->report_interval));
723 }
724
725 if (ctx->official_controller) {
726 ctx->report_battery = true;
727 }
728
729 HIDAPI_UpdateDeviceProperties(ctx->device);
730}
731
732static void HIDAPI_DriverPS4_SetEnhancedMode(SDL_DriverPS4_Context *ctx)
733{
734 HIDAPI_DriverPS4_SetEnhancedModeAvailable(ctx);
735
736 if (!ctx->enhanced_mode) {
737 ctx->enhanced_mode = true;
738
739 // Switch into enhanced report mode
740 HIDAPI_DriverPS4_UpdateEffects(ctx, false);
741 }
742}
743
744static void HIDAPI_DriverPS4_SetEnhancedReportHint(SDL_DriverPS4_Context *ctx, HIDAPI_PS4_EnhancedReportHint enhanced_report_hint)
745{
746 switch (enhanced_report_hint) {
747 case PS4_ENHANCED_REPORT_HINT_OFF:
748 // Nothing to do, enhanced mode is a one-way ticket
749 break;
750 case PS4_ENHANCED_REPORT_HINT_ON:
751 HIDAPI_DriverPS4_SetEnhancedMode(ctx);
752 break;
753 case PS4_ENHANCED_REPORT_HINT_AUTO:
754 HIDAPI_DriverPS4_SetEnhancedModeAvailable(ctx);
755 break;
756 }
757 ctx->enhanced_report_hint = enhanced_report_hint;
758}
759
760static void HIDAPI_DriverPS4_UpdateEnhancedModeOnEnhancedReport(SDL_DriverPS4_Context *ctx)
761{
762 ctx->enhanced_reports = true;
763
764 if (ctx->enhanced_report_hint == PS4_ENHANCED_REPORT_HINT_AUTO) {
765 HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_ON);
766 }
767}
768
769static void HIDAPI_DriverPS4_UpdateEnhancedModeOnApplicationUsage(SDL_DriverPS4_Context *ctx)
770{
771 if (ctx->enhanced_report_hint == PS4_ENHANCED_REPORT_HINT_AUTO) {
772 HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_ON);
773 }
774}
775
776static void SDLCALL SDL_PS4EnhancedReportsChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
777{
778 SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)userdata;
779
780 if (ctx->device->is_bluetooth) {
781 if (hint && SDL_strcasecmp(hint, "auto") == 0) {
782 HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_AUTO);
783 } else if (SDL_GetStringBoolean(hint, true)) {
784 HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_ON);
785 } else {
786 HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_OFF);
787 }
788 } else {
789 HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_ON);
790 }
791}
792
793static void SDLCALL SDL_PS4ReportIntervalHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
794{
795 const int DEFAULT_REPORT_INTERVAL = 4;
796 SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)userdata;
797 int new_report_interval = DEFAULT_REPORT_INTERVAL;
798
799 if (hint) {
800 int report_interval = SDL_atoi(hint);
801 switch (report_interval) {
802 case 1:
803 case 2:
804 case 4:
805 // Valid values
806 new_report_interval = report_interval;
807 break;
808 default:
809 break;
810 }
811 }
812
813 if (new_report_interval != ctx->report_interval) {
814 ctx->report_interval = (Uint8)new_report_interval;
815
816 HIDAPI_DriverPS4_UpdateEffects(ctx, false);
817 SDL_LockJoysticks();
818 SDL_PrivateJoystickSensorRate(ctx->joystick, SDL_SENSOR_GYRO, (float)(1000 / ctx->report_interval));
819 SDL_PrivateJoystickSensorRate(ctx->joystick, SDL_SENSOR_ACCEL, (float)(1000 / ctx->report_interval));
820 SDL_UnlockJoysticks();
821 }
822}
823
824static void HIDAPI_DriverPS4_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
825{
826 SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
827
828 if (!ctx->joystick) {
829 return;
830 }
831
832 ctx->player_index = player_index;
833
834 // This will set the new LED state based on the new player index
835 // SDL automatically calls this, so it doesn't count as an application action to enable enhanced mode
836 HIDAPI_DriverPS4_UpdateEffects(ctx, false);
837}
838
839static bool HIDAPI_DriverPS4_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
840{
841 SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
842
843 SDL_AssertJoysticksLocked();
844
845 ctx->joystick = joystick;
846 ctx->last_packet = SDL_GetTicks();
847 ctx->report_sensors = false;
848 ctx->report_touchpad = false;
849 ctx->rumble_left = 0;
850 ctx->rumble_right = 0;
851 ctx->color_set = false;
852 SDL_zero(ctx->last_state);
853
854 // Initialize player index (needed for setting LEDs)
855 ctx->player_index = SDL_GetJoystickPlayerIndex(joystick);
856
857 // Initialize the joystick capabilities
858 joystick->nbuttons = 11;
859 if (ctx->touchpad_supported) {
860 joystick->nbuttons += 1;
861 }
862 joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
863 joystick->nhats = 1;
864
865 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS4_REPORT_INTERVAL,
866 SDL_PS4ReportIntervalHintChanged, ctx);
867 SDL_AddHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS,
868 SDL_PS4EnhancedReportsChanged, ctx);
869 return true;
870}
871
872static bool HIDAPI_DriverPS4_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
873{
874 SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
875
876 if (!ctx->vibration_supported) {
877 return SDL_Unsupported();
878 }
879
880 ctx->rumble_left = (low_frequency_rumble >> 8);
881 ctx->rumble_right = (high_frequency_rumble >> 8);
882
883 return HIDAPI_DriverPS4_UpdateEffects(ctx, true);
884}
885
886static bool HIDAPI_DriverPS4_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
887{
888 return SDL_Unsupported();
889}
890
891static Uint32 HIDAPI_DriverPS4_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
892{
893 SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
894 Uint32 result = 0;
895
896 if (ctx->enhanced_mode_available) {
897 if (ctx->lightbar_supported) {
898 result |= SDL_JOYSTICK_CAP_RGB_LED;
899 }
900 if (ctx->vibration_supported) {
901 result |= SDL_JOYSTICK_CAP_RUMBLE;
902 }
903 }
904
905 return result;
906}
907
908static bool HIDAPI_DriverPS4_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
909{
910 SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
911
912 if (!ctx->lightbar_supported) {
913 return SDL_Unsupported();
914 }
915
916 ctx->color_set = true;
917 ctx->led_red = red;
918 ctx->led_green = green;
919 ctx->led_blue = blue;
920
921 return HIDAPI_DriverPS4_UpdateEffects(ctx, true);
922}
923
924static bool HIDAPI_DriverPS4_InternalSendJoystickEffect(SDL_DriverPS4_Context *ctx, const void *effect, int size, bool application_usage)
925{
926 Uint8 data[78];
927 int report_size, offset;
928
929 if (!ctx->effects_supported) {
930 // We shouldn't be sending packets to this controller
931 return SDL_Unsupported();
932 }
933
934 if (!ctx->enhanced_mode) {
935 if (application_usage) {
936 HIDAPI_DriverPS4_UpdateEnhancedModeOnApplicationUsage(ctx);
937 }
938
939 if (!ctx->enhanced_mode) {
940 // We're not in enhanced mode, effects aren't allowed
941 return SDL_Unsupported();
942 }
943 }
944
945 SDL_zeroa(data);
946
947 if (ctx->device->is_bluetooth && ctx->official_controller) {
948 data[0] = k_EPS4ReportIdBluetoothEffects;
949 data[1] = 0xC0 | ctx->report_interval; // Magic value HID + CRC, also sets update interval
950 data[3] = 0x03; // 0x1 is rumble, 0x2 is lightbar, 0x4 is the blink interval
951
952 report_size = 78;
953 offset = 6;
954 } else {
955 data[0] = k_EPS4ReportIdUsbEffects;
956 data[1] = 0x07; // Magic value
957
958 report_size = 32;
959 offset = 4;
960 }
961
962 SDL_memcpy(&data[offset], effect, SDL_min((sizeof(data) - offset), (size_t)size));
963
964 if (ctx->device->is_bluetooth) {
965 // Bluetooth reports need a CRC at the end of the packet (at least on Linux)
966 Uint8 ubHdr = 0xA2; // hidp header is part of the CRC calculation
967 Uint32 unCRC;
968 unCRC = SDL_crc32(0, &ubHdr, 1);
969 unCRC = SDL_crc32(unCRC, data, (size_t)(report_size - sizeof(unCRC)));
970 SDL_memcpy(&data[report_size - sizeof(unCRC)], &unCRC, sizeof(unCRC));
971 }
972
973 if (SDL_HIDAPI_SendRumble(ctx->device, data, report_size) != report_size) {
974 return SDL_SetError("Couldn't send rumble packet");
975 }
976 return true;
977}
978
979static bool HIDAPI_DriverPS4_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *effect, int size)
980{
981 SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
982
983 return HIDAPI_DriverPS4_InternalSendJoystickEffect(ctx, effect, size, true);
984}
985
986static bool HIDAPI_DriverPS4_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
987{
988 SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
989
990 HIDAPI_DriverPS4_UpdateEnhancedModeOnApplicationUsage(ctx);
991
992 if (!ctx->sensors_supported || (enabled && !ctx->enhanced_mode)) {
993 return SDL_Unsupported();
994 }
995
996 if (enabled) {
997 HIDAPI_DriverPS4_LoadCalibrationData(device);
998 }
999 ctx->report_sensors = enabled;
1000
1001 return true;
1002}
1003
1004static void HIDAPI_DriverPS4_HandleStatePacket(SDL_Joystick *joystick, SDL_hid_device *dev, SDL_DriverPS4_Context *ctx, PS4StatePacket_t *packet, int size)
1005{
1006 static const float TOUCHPAD_SCALEX = 1.0f / 1920;
1007 static const float TOUCHPAD_SCALEY = 1.0f / 920; // This is noted as being 944 resolution, but 920 feels better
1008 Sint16 axis;
1009 bool touchpad_down;
1010 int touchpad_x, touchpad_y;
1011 Uint64 timestamp = SDL_GetTicksNS();
1012
1013 if (size > 9 && ctx->report_touchpad && ctx->enhanced_reports) {
1014 touchpad_down = ((packet->ucTouchpadCounter1 & 0x80) == 0);
1015 touchpad_x = packet->rgucTouchpadData1[0] | (((int)packet->rgucTouchpadData1[1] & 0x0F) << 8);
1016 touchpad_y = (packet->rgucTouchpadData1[1] >> 4) | ((int)packet->rgucTouchpadData1[2] << 4);
1017 SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, touchpad_down, touchpad_x * TOUCHPAD_SCALEX, touchpad_y * TOUCHPAD_SCALEY, touchpad_down ? 1.0f : 0.0f);
1018
1019 touchpad_down = ((packet->ucTouchpadCounter2 & 0x80) == 0);
1020 touchpad_x = packet->rgucTouchpadData2[0] | (((int)packet->rgucTouchpadData2[1] & 0x0F) << 8);
1021 touchpad_y = (packet->rgucTouchpadData2[1] >> 4) | ((int)packet->rgucTouchpadData2[2] << 4);
1022 SDL_SendJoystickTouchpad(timestamp, joystick, 0, 1, touchpad_down, touchpad_x * TOUCHPAD_SCALEX, touchpad_y * TOUCHPAD_SCALEY, touchpad_down ? 1.0f : 0.0f);
1023 }
1024
1025 if (ctx->last_state.rgucButtonsHatAndCounter[0] != packet->rgucButtonsHatAndCounter[0]) {
1026 {
1027 Uint8 data = (packet->rgucButtonsHatAndCounter[0] >> 4);
1028
1029 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data & 0x01) != 0));
1030 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data & 0x02) != 0));
1031 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data & 0x04) != 0));
1032 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data & 0x08) != 0));
1033 }
1034 {
1035 Uint8 hat;
1036 Uint8 data = (packet->rgucButtonsHatAndCounter[0] & 0x0F);
1037
1038 switch (data) {
1039 case 0:
1040 hat = SDL_HAT_UP;
1041 break;
1042 case 1:
1043 hat = SDL_HAT_RIGHTUP;
1044 break;
1045 case 2:
1046 hat = SDL_HAT_RIGHT;
1047 break;
1048 case 3:
1049 hat = SDL_HAT_RIGHTDOWN;
1050 break;
1051 case 4:
1052 hat = SDL_HAT_DOWN;
1053 break;
1054 case 5:
1055 hat = SDL_HAT_LEFTDOWN;
1056 break;
1057 case 6:
1058 hat = SDL_HAT_LEFT;
1059 break;
1060 case 7:
1061 hat = SDL_HAT_LEFTUP;
1062 break;
1063 default:
1064 hat = SDL_HAT_CENTERED;
1065 break;
1066 }
1067 SDL_SendJoystickHat(timestamp, joystick, 0, hat);
1068 }
1069 }
1070
1071 if (ctx->last_state.rgucButtonsHatAndCounter[1] != packet->rgucButtonsHatAndCounter[1]) {
1072 Uint8 data = packet->rgucButtonsHatAndCounter[1];
1073
1074 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x01) != 0));
1075 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x02) != 0));
1076 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data & 0x10) != 0));
1077 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x20) != 0));
1078 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x40) != 0));
1079 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data & 0x80) != 0));
1080 }
1081
1082 /* Some fightsticks, ex: Victrix FS Pro will only this these digital trigger bits and not the analog values so this needs to run whenever the
1083 trigger is evaluated
1084 */
1085 if (packet->rgucButtonsHatAndCounter[1] & 0x0C) {
1086 Uint8 data = packet->rgucButtonsHatAndCounter[1];
1087 packet->ucTriggerLeft = (data & 0x04) && packet->ucTriggerLeft == 0 ? 255 : packet->ucTriggerLeft;
1088 packet->ucTriggerRight = (data & 0x08) && packet->ucTriggerRight == 0 ? 255 : packet->ucTriggerRight;
1089 }
1090
1091 if (ctx->last_state.rgucButtonsHatAndCounter[2] != packet->rgucButtonsHatAndCounter[2]) {
1092 Uint8 data = (packet->rgucButtonsHatAndCounter[2] & 0x03);
1093
1094 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x01) != 0));
1095 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS4_TOUCHPAD, ((data & 0x02) != 0));
1096 }
1097
1098 axis = ((int)packet->ucTriggerLeft * 257) - 32768;
1099 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
1100 axis = ((int)packet->ucTriggerRight * 257) - 32768;
1101 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
1102 axis = ((int)packet->ucLeftJoystickX * 257) - 32768;
1103 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
1104 axis = ((int)packet->ucLeftJoystickY * 257) - 32768;
1105 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
1106 axis = ((int)packet->ucRightJoystickX * 257) - 32768;
1107 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
1108 axis = ((int)packet->ucRightJoystickY * 257) - 32768;
1109 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
1110
1111 if (size > 9 && ctx->report_battery && ctx->enhanced_reports) {
1112 SDL_PowerState state;
1113 int percent;
1114 Uint8 level = (packet->ucBatteryLevel & 0x0F);
1115
1116 if (packet->ucBatteryLevel & 0x10) {
1117 if (level <= 10) {
1118 state = SDL_POWERSTATE_CHARGING;
1119 percent = SDL_min(level * 10 + 5, 100);
1120 } else if (level == 11) {
1121 state = SDL_POWERSTATE_CHARGED;
1122 percent = 100;
1123 } else {
1124 state = SDL_POWERSTATE_UNKNOWN;
1125 percent = 0;
1126 }
1127 } else {
1128 state = SDL_POWERSTATE_ON_BATTERY;
1129 percent = SDL_min(level * 10 + 5, 100);
1130 }
1131 SDL_SendJoystickPowerInfo(joystick, state, percent);
1132 }
1133
1134 if (size > 9 && ctx->report_sensors) {
1135 Uint16 tick;
1136 Uint16 delta;
1137 Uint64 sensor_timestamp;
1138 float data[3];
1139
1140 tick = LOAD16(packet->rgucTimestamp[0], packet->rgucTimestamp[1]);
1141 if (ctx->last_tick < tick) {
1142 delta = (tick - ctx->last_tick);
1143 } else {
1144 delta = (SDL_MAX_UINT16 - ctx->last_tick + tick + 1);
1145 }
1146 ctx->sensor_ticks += delta;
1147 ctx->last_tick = tick;
1148
1149 // Sensor timestamp is in 5.33us units
1150 sensor_timestamp = (ctx->sensor_ticks * SDL_NS_PER_US * 16) / 3;
1151
1152 data[0] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 0, LOAD16(packet->rgucGyroX[0], packet->rgucGyroX[1]));
1153 data[1] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 1, LOAD16(packet->rgucGyroY[0], packet->rgucGyroY[1]));
1154 data[2] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 2, LOAD16(packet->rgucGyroZ[0], packet->rgucGyroZ[1]));
1155 SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, data, 3);
1156
1157 data[0] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 3, LOAD16(packet->rgucAccelX[0], packet->rgucAccelX[1]));
1158 data[1] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 4, LOAD16(packet->rgucAccelY[0], packet->rgucAccelY[1]));
1159 data[2] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 5, LOAD16(packet->rgucAccelZ[0], packet->rgucAccelZ[1]));
1160 SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, data, 3);
1161 }
1162
1163 SDL_memcpy(&ctx->last_state, packet, sizeof(ctx->last_state));
1164}
1165
1166static bool VerifyCRC(Uint8 *data, int size)
1167{
1168 Uint8 ubHdr = 0xA1; // hidp header is part of the CRC calculation
1169 Uint32 unCRC, unPacketCRC;
1170 Uint8 *packetCRC = data + size - sizeof(unPacketCRC);
1171 unCRC = SDL_crc32(0, &ubHdr, 1);
1172 unCRC = SDL_crc32(unCRC, data, (size_t)(size - sizeof(unCRC)));
1173
1174 unPacketCRC = LOAD32(packetCRC[0],
1175 packetCRC[1],
1176 packetCRC[2],
1177 packetCRC[3]);
1178 return (unCRC == unPacketCRC);
1179}
1180
1181static bool HIDAPI_DriverPS4_IsPacketValid(SDL_DriverPS4_Context *ctx, Uint8 *data, int size)
1182{
1183 switch (data[0]) {
1184 case k_EPS4ReportIdUsbState:
1185 if (size == 10) {
1186 // This is non-enhanced mode, this packet is fine
1187 return true;
1188 }
1189
1190 if (ctx->is_nacon_dongle && size >= (1 + sizeof(PS4StatePacket_t))) {
1191 // The report timestamp doesn't change when the controller isn't connected
1192 PS4StatePacket_t *packet = (PS4StatePacket_t *)&data[1];
1193 if (SDL_memcmp(packet->rgucTimestamp, ctx->last_state.rgucTimestamp, sizeof(packet->rgucTimestamp)) == 0) {
1194 return false;
1195 }
1196 if (ctx->last_state.rgucAccelX[0] == 0 && ctx->last_state.rgucAccelX[1] == 0 &&
1197 ctx->last_state.rgucAccelY[0] == 0 && ctx->last_state.rgucAccelY[1] == 0 &&
1198 ctx->last_state.rgucAccelZ[0] == 0 && ctx->last_state.rgucAccelZ[1] == 0) {
1199 // We don't have any state to compare yet, go ahead and copy it
1200 SDL_memcpy(&ctx->last_state, &data[1], sizeof(PS4StatePacket_t));
1201 return false;
1202 }
1203 }
1204
1205 /* In the case of a DS4 USB dongle, bit[2] of byte 31 indicates if a DS4 is actually connected (indicated by '0').
1206 * For non-dongle, this bit is always 0 (connected).
1207 * This is usually the ID over USB, but the DS4v2 that started shipping with the PS4 Slim will also send this
1208 * packet over BT with a size of 128
1209 */
1210 if (size >= 64 && !(data[31] & 0x04)) {
1211 return true;
1212 }
1213 break;
1214 case k_EPS4ReportIdBluetoothState1:
1215 case k_EPS4ReportIdBluetoothState2:
1216 case k_EPS4ReportIdBluetoothState3:
1217 case k_EPS4ReportIdBluetoothState4:
1218 case k_EPS4ReportIdBluetoothState5:
1219 case k_EPS4ReportIdBluetoothState6:
1220 case k_EPS4ReportIdBluetoothState7:
1221 case k_EPS4ReportIdBluetoothState8:
1222 case k_EPS4ReportIdBluetoothState9:
1223 // Bluetooth state packets have two additional bytes at the beginning, the first notes if HID data is present
1224 if (size >= 78 && (data[1] & 0x80)) {
1225 if (VerifyCRC(data, 78)) {
1226 ++ctx->valid_crc_packets;
1227 } else {
1228 if (ctx->valid_crc_packets > 0) {
1229 --ctx->valid_crc_packets;
1230 }
1231 if (ctx->valid_crc_packets >= 3) {
1232 // We're generally getting valid CRC, but failed one
1233 return false;
1234 }
1235 }
1236 return true;
1237 }
1238 break;
1239 default:
1240 break;
1241 }
1242 return false;
1243}
1244
1245static bool HIDAPI_DriverPS4_UpdateDevice(SDL_HIDAPI_Device *device)
1246{
1247 SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
1248 SDL_Joystick *joystick = NULL;
1249 Uint8 data[USB_PACKET_LENGTH * 2];
1250 int size;
1251 int packet_count = 0;
1252 Uint64 now = SDL_GetTicks();
1253
1254 if (device->num_joysticks > 0) {
1255 joystick = SDL_GetJoystickFromID(device->joysticks[0]);
1256 }
1257
1258 while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {
1259#ifdef DEBUG_PS4_PROTOCOL
1260 HIDAPI_DumpPacket("PS4 packet: size = %d", data, size);
1261#endif
1262 if (!HIDAPI_DriverPS4_IsPacketValid(ctx, data, size)) {
1263 continue;
1264 }
1265
1266 ++packet_count;
1267 ctx->last_packet = now;
1268
1269 if (!joystick) {
1270 continue;
1271 }
1272
1273 switch (data[0]) {
1274 case k_EPS4ReportIdUsbState:
1275 HIDAPI_DriverPS4_HandleStatePacket(joystick, device->dev, ctx, (PS4StatePacket_t *)&data[1], size - 1);
1276 break;
1277 case k_EPS4ReportIdBluetoothState1:
1278 case k_EPS4ReportIdBluetoothState2:
1279 case k_EPS4ReportIdBluetoothState3:
1280 case k_EPS4ReportIdBluetoothState4:
1281 case k_EPS4ReportIdBluetoothState5:
1282 case k_EPS4ReportIdBluetoothState6:
1283 case k_EPS4ReportIdBluetoothState7:
1284 case k_EPS4ReportIdBluetoothState8:
1285 case k_EPS4ReportIdBluetoothState9:
1286 // This is the extended report, we can enable effects now in auto mode
1287 HIDAPI_DriverPS4_UpdateEnhancedModeOnEnhancedReport(ctx);
1288
1289 // Bluetooth state packets have two additional bytes at the beginning, the first notes if HID is present
1290 HIDAPI_DriverPS4_HandleStatePacket(joystick, device->dev, ctx, (PS4StatePacket_t *)&data[3], size - 3);
1291 break;
1292 default:
1293#ifdef DEBUG_JOYSTICK
1294 SDL_Log("Unknown PS4 packet: 0x%.2x", data[0]);
1295#endif
1296 break;
1297 }
1298 }
1299
1300 if (device->is_bluetooth) {
1301 if (packet_count == 0) {
1302 // Check to see if it looks like the device disconnected
1303 if (now >= (ctx->last_packet + BLUETOOTH_DISCONNECT_TIMEOUT_MS)) {
1304 // Send an empty output report to tickle the Bluetooth stack
1305 HIDAPI_DriverPS4_TickleBluetooth(device);
1306 ctx->last_packet = now;
1307 }
1308 } else {
1309 // Reconnect the Bluetooth device once the USB device is gone
1310 if (device->num_joysticks == 0 &&
1311 !HIDAPI_HasConnectedUSBDevice(device->serial)) {
1312 HIDAPI_JoystickConnected(device, NULL);
1313 }
1314 }
1315 }
1316
1317 if (ctx->is_dongle || ctx->is_nacon_dongle) {
1318 if (packet_count == 0) {
1319 if (device->num_joysticks > 0) {
1320 // Check to see if it looks like the device disconnected
1321 if (now >= (ctx->last_packet + BLUETOOTH_DISCONNECT_TIMEOUT_MS)) {
1322 HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
1323 }
1324 }
1325 } else {
1326 if (device->num_joysticks == 0) {
1327 char serial[18];
1328 size = ReadFeatureReport(device->dev, k_ePS4FeatureReportIdSerialNumber, data, sizeof(data));
1329 if (size >= 7 && (data[1] || data[2] || data[3] || data[4] || data[5] || data[6])) {
1330 (void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x",
1331 data[6], data[5], data[4], data[3], data[2], data[1]);
1332 HIDAPI_SetDeviceSerial(device, serial);
1333 }
1334 HIDAPI_JoystickConnected(device, NULL);
1335 }
1336 }
1337 }
1338
1339 if (packet_count == 0 && size < 0 && device->num_joysticks > 0) {
1340 // Read error, device is disconnected
1341 HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
1342 }
1343 return (size >= 0);
1344}
1345
1346static void HIDAPI_DriverPS4_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
1347{
1348 SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
1349
1350 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS4_REPORT_INTERVAL,
1351 SDL_PS4ReportIntervalHintChanged, ctx);
1352 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS,
1353 SDL_PS4EnhancedReportsChanged, ctx);
1354
1355 ctx->joystick = NULL;
1356
1357 ctx->report_sensors = false;
1358 ctx->enhanced_mode = false;
1359 ctx->enhanced_mode_available = false;
1360}
1361
1362static void HIDAPI_DriverPS4_FreeDevice(SDL_HIDAPI_Device *device)
1363{
1364}
1365
1366SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS4 = {
1367 SDL_HINT_JOYSTICK_HIDAPI_PS4,
1368 true,
1369 HIDAPI_DriverPS4_RegisterHints,
1370 HIDAPI_DriverPS4_UnregisterHints,
1371 HIDAPI_DriverPS4_IsEnabled,
1372 HIDAPI_DriverPS4_IsSupportedDevice,
1373 HIDAPI_DriverPS4_InitDevice,
1374 HIDAPI_DriverPS4_GetDevicePlayerIndex,
1375 HIDAPI_DriverPS4_SetDevicePlayerIndex,
1376 HIDAPI_DriverPS4_UpdateDevice,
1377 HIDAPI_DriverPS4_OpenJoystick,
1378 HIDAPI_DriverPS4_RumbleJoystick,
1379 HIDAPI_DriverPS4_RumbleJoystickTriggers,
1380 HIDAPI_DriverPS4_GetJoystickCapabilities,
1381 HIDAPI_DriverPS4_SetJoystickLED,
1382 HIDAPI_DriverPS4_SendJoystickEffect,
1383 HIDAPI_DriverPS4_SetJoystickSensorsEnabled,
1384 HIDAPI_DriverPS4_CloseJoystick,
1385 HIDAPI_DriverPS4_FreeDevice,
1386};
1387
1388#endif // SDL_JOYSTICK_HIDAPI_PS4
1389
1390#endif // SDL_JOYSTICK_HIDAPI
1391