Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/Windows/Hid/DualSense.cpp
5692 views
1
// Some info from
2
// https://controllers.fandom.com/wiki/Sony_DualSense
3
// https://github.com/ds4windowsapp/DS4Windows/blob/65609b470f53a4f832fb07ac24085d3e28ec15bd/DS4Windows/DS4Library/InputDevices/DualSenseDevice.cs#L905
4
5
// Bluetooth information
6
// When connecting via bluetooth, outReportSize is 547. However, you can send a smaller 78-byte format instead,
7
// and if you do, you'll get smaller reports back. They are organized more like the USB messages.
8
9
#include <cstring>
10
11
#include "Windows/Hid/DualShock.h"
12
#include "Windows/Hid/DualSense.h"
13
#include "Windows/Hid/HidInputDevice.h"
14
#include "Windows/Hid/HidCommon.h"
15
16
struct DualSenseInputReportUSB {
17
u8 reportId; // Must be 1
18
19
u8 lx;
20
u8 ly;
21
u8 rx;
22
u8 ry;
23
24
u8 l2_analog;
25
u8 r2_analog;
26
27
u8 frameCounter; // 7
28
29
u8 buttons[3]; // 8-10
30
u8 pad[5]; // 11,12,13,14,15
31
32
s16 gyroscope[3];
33
s16 accelerometer[3];
34
35
// There's more stuff after here.
36
};
37
38
#pragma pack(push, 1)
39
40
struct DualSenseInputReportBT {
41
u8 reportId; // Must be 0x31
42
u8 seq_tag; // Sequence tag / Transaction header
43
44
u8 lx;
45
u8 ly;
46
u8 rx;
47
u8 ry;
48
u8 l2_analog;
49
u8 r2_analog;
50
51
u8 frameCounter; // Offset 8
52
53
// The BT struct differs from the USB one here:
54
// USB has buttons immediately after frameCounter. BT has 6 bytes of status/battery data first.
55
u8 unknown_status[6];
56
57
u8 buttons[3]; // Offset 15, 16, 17
58
u8 device_extra; // Offset 18 (often contains power/battery status)
59
60
u8 pad[3]; // Offset 19, 20, 21
61
s16 gyroscope[3];
62
s16 accelerometer[3];
63
64
// There's more stuff after here.
65
};
66
67
#pragma pack(pop)
68
69
#pragma pack(push,1)
70
struct DualSenseOutputReport {
71
u8 reportId;
72
u8 flags1;
73
u8 flags2;
74
u8 rumbleRight;
75
u8 rumbleLeft;
76
u8 pad[2];
77
u8 muteLED;
78
u8 micMute; // 10
79
u8 other[32];
80
u8 enableLightbar;
81
u8 fade;
82
u8 brightness;
83
u8 playerLights;
84
u8 lightbarRed;
85
u8 lightbarGreen;
86
u8 lightbarBlue;
87
};
88
static_assert(sizeof(DualSenseOutputReport) == 48);
89
90
static void FillDualSenseOutputReport(DualSenseOutputReport *report, bool lightsOn) {
91
report->reportId = 2;
92
report->flags1 = 0x0C;
93
report->flags2 = 0x15;
94
report->muteLED = 1;
95
// Turn on the lights.
96
report->playerLights = lightsOn ? 1 : 0;
97
report->enableLightbar = 1;
98
report->brightness = lightsOn ? 0 : 2; // 0 = high, 1 = medium, 2 = low
99
report->lightbarRed = lightsOn ? LED_R : 0;
100
report->lightbarGreen = lightsOn ? LED_G : 0;
101
report->lightbarBlue = lightsOn ? LED_B : 0;
102
}
103
104
static bool SendReport(HANDLE handle, const DualSenseOutputReport &report, int outReportSize) {
105
if (outReportSize == sizeof(DualSenseOutputReport)) {
106
// USB case: Just write the plain struct.
107
return WriteReport(handle, report);
108
} else if (outReportSize >= 547) {
109
_dbg_assert_(outReportSize == 547);
110
// BT case. Not as fun! NOTE: We use the small size method.
111
std::vector<uint8_t> buffer;
112
buffer.resize(78);
113
114
// 1. Report ID 0x31 is required for Extended Features (Rumble/LED) over BT
115
buffer[0] = 0x31;
116
// 2. Bluetooth Header: 0x02 sets the "tag" for the controller to process the report
117
buffer[1] = 0x02;
118
119
// We skip report.reportId because buffer[0] is already 0x31
120
// Copy everything after reportId into buffer starting at index 2
121
memcpy(&buffer[2], (uint8_t*)&report + 1, sizeof(DualSenseOutputReport) - 1);
122
buffer[3] = 0x15;
123
124
// Calculate CRC over the first 74 bytes (0 to 73)
125
// This function includes the 0xA2 "hidden" seed internally
126
uint32_t crc = ComputeDualSenseBTCRC(buffer.data(), 74);
127
128
// Append CRC in Little Endian
129
memcpy(buffer.data() + 74, &crc, 4);
130
return WriteReport(handle, buffer.data(), buffer.size());
131
} else {
132
ERROR_LOG(Log::System, "SendReport: Unexpected outReportSize: %d", outReportSize);
133
return false;
134
}
135
}
136
137
// Sends initialization packet to DualSense
138
bool InitializeDualSense(HANDLE handle, int outReportSize) {
139
DualSenseOutputReport report{};
140
FillDualSenseOutputReport(&report, true);
141
return SendReport(handle, report, outReportSize);
142
}
143
144
bool ShutdownDualsense(HANDLE handle, int outReportSize) {
145
if (outReportSize != sizeof(DualSenseOutputReport)) {
146
return false;
147
}
148
DualSenseOutputReport report{};
149
FillDualSenseOutputReport(&report, false);
150
return SendReport(handle, report, outReportSize);
151
}
152
153
// Templated to handle different DualSense struct layouts.
154
template<class T>
155
static void ReadReport(HIDControllerState* state, u32 *buttons, const T& report) {
156
// Center the sticks.
157
state->stickAxes[HID_STICK_LX] = report.lx - 128;
158
state->stickAxes[HID_STICK_LY] = report.ly - 128;
159
state->stickAxes[HID_STICK_RX] = report.rx - 128;
160
state->stickAxes[HID_STICK_RY] = report.ry - 128;
161
162
// Copy over the triggers.
163
state->triggerAxes[HID_TRIGGER_L2] = report.l2_analog;
164
state->triggerAxes[HID_TRIGGER_R2] = report.r2_analog;
165
166
const float accelScale = (1.0f / 8192.0f) * 9.81f;
167
// We need to remap the axes a bit.
168
state->accValid = true;
169
state->accelerometer[0] = -report.accelerometer[2] * accelScale;
170
state->accelerometer[1] = -report.accelerometer[0] * accelScale;
171
state->accelerometer[2] = report.accelerometer[1] * accelScale;
172
*buttons = 0;
173
memcpy(buttons, &report.buttons[0], 3);
174
}
175
176
bool ReadDualSenseInput(HANDLE handle, HIDControllerState *state, int inReportSize) {
177
if (inReportSize > 1024) {
178
return false;
179
}
180
BYTE inputReport[1024]{};
181
182
DWORD bytesRead = 0;
183
if (!ReadFile(handle, inputReport, inReportSize, &bytesRead, nullptr)) {
184
const u32 error = GetLastError();
185
return false;
186
}
187
188
if (bytesRead < 14) {
189
return false;
190
}
191
192
u32 buttons{};
193
194
// OK, check the first byte to figure out what we're dealing with here.
195
if (inputReport[0] == 0x1 && inReportSize == 64) {
196
// A valid USB packet.
197
DualSenseInputReportUSB report;
198
memcpy(&report, inputReport, sizeof(report));
199
ReadReport(state, &buttons, report);
200
// Fall through to button processing
201
} else if (inputReport[0] == 0x31 && inReportSize >= 547) {
202
// A valid bluetooth packet, large format. Probably we can delete this, see the note
203
// at the top of thie file. The layout is a bit different!
204
// A valid USB packet.
205
DualSenseInputReportBT report;
206
memcpy(&report, inputReport, sizeof(report));
207
ReadReport(state, &buttons, report);
208
209
// Fall through to button processing
210
} else if (inputReport[0] == 0x31 && inReportSize == 78) {
211
// Bluetooth packet, short and fast format.
212
DualSenseInputReportUSB report;
213
// Note: These bluetooth packets are offset from the USB packets by 1.
214
memcpy(((uint8_t *)&report) + 1, inputReport + 2, sizeof(report) - 1);
215
ReadReport(state, &buttons, report);
216
217
// Fall through to button processing
218
} else if (inputReport[0] == 0x1 && inReportSize == 78) {
219
// Simple BT packet. This shouldn't happen if we correctly initialize the gamepad.
220
buttons = 0;
221
// Fall through to button processing
222
} else {
223
// Unknown packet (simple BT?). Ignore.
224
WARN_LOG(Log::System, "Unexpected: %02x type, %d size", inputReport[0], (int)inReportSize);
225
return true;
226
}
227
228
// Shared button handling.
229
230
// Clear out and re-fill the DPAD, it works differently somehow
231
const u32 hat = buttons & 0xF;
232
buttons &= ~0xF;
233
buttons |= DecodePSHatSwitch(hat);
234
235
state->buttons = buttons;
236
return true;
237
}
238
239