Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/Windows/Hid/SwitchPro.cpp
5692 views
1
#include "Common/Math/math_util.h"
2
#include "Windows/Hid/HidInputDevice.h"
3
#include "Windows/Hid/SwitchPro.h"
4
#include "Windows/Hid/HidCommon.h"
5
6
enum HIDButton : u32 {
7
SWITCH_PRO_BTN_Y = (1 << 0),
8
SWITCH_PRO_BTN_X = (1 << 1),
9
SWITCH_PRO_BTN_B = (1 << 2),
10
SWITCH_PRO_BTN_A = (1 << 3),
11
SWITCH_PRO_BTN_R1 = (1 << 6),
12
SWITCH_PRO_BTN_R2 = (1 << 7),
13
SWITCH_PRO_BTN_L3 = (1 << 11),
14
SWITCH_PRO_BTN_R3 = (1 << 10),
15
SWITCH_PRO_BTN_SHARE = (1 << 8),
16
SWITCH_PRO_BTN_OPTIONS = (1 << 9),
17
SWITCH_PRO_BTN_PS_BUTTON = (1 << 12),
18
SWITCH_PRO_BTN_CAPTURE = (1 << 13),
19
SWITCH_PRO_DPAD_DOWN = (1 << 16),
20
SWITCH_PRO_DPAD_UP = (1 << 17),
21
SWITCH_PRO_DPAD_RIGHT = (1 << 18),
22
SWITCH_PRO_DPAD_LEFT = (1 << 19),
23
SWITCH_PRO_BTN_L1 = (1 << 22),
24
SWITCH_PRO_BTN_L2 = (1 << 23),
25
};
26
27
static const ButtonInputMapping g_switchProInputMappings[] = {
28
{SWITCH_PRO_DPAD_UP, NKCODE_DPAD_UP},
29
{SWITCH_PRO_DPAD_DOWN, NKCODE_DPAD_DOWN},
30
{SWITCH_PRO_DPAD_LEFT, NKCODE_DPAD_LEFT},
31
{SWITCH_PRO_DPAD_RIGHT, NKCODE_DPAD_RIGHT},
32
{SWITCH_PRO_BTN_Y, NKCODE_BUTTON_4},
33
{SWITCH_PRO_BTN_X, NKCODE_BUTTON_1},
34
{SWITCH_PRO_BTN_B, NKCODE_BUTTON_2},
35
{SWITCH_PRO_BTN_A, NKCODE_BUTTON_3},
36
{SWITCH_PRO_BTN_PS_BUTTON, NKCODE_HOME},
37
{SWITCH_PRO_BTN_SHARE, NKCODE_BUTTON_9},
38
{SWITCH_PRO_BTN_OPTIONS, NKCODE_BUTTON_10},
39
{SWITCH_PRO_BTN_L1, NKCODE_BUTTON_7},
40
{SWITCH_PRO_BTN_R1, NKCODE_BUTTON_8},
41
{SWITCH_PRO_BTN_L2, NKCODE_BUTTON_L2}, // No analog triggers.
42
{SWITCH_PRO_BTN_R2, NKCODE_BUTTON_R2},
43
{SWITCH_PRO_BTN_L3, NKCODE_BUTTON_THUMBL},
44
{SWITCH_PRO_BTN_R3, NKCODE_BUTTON_THUMBR},
45
};
46
47
void GetSwitchButtonInputMappings(const ButtonInputMapping **mappings, size_t *size) {
48
*mappings = g_switchProInputMappings;
49
*size = ARRAY_SIZE(g_switchProInputMappings);
50
}
51
52
enum class SwitchProSubCmd {
53
SET_INPUT_MODE = 0x03,
54
SET_LOW_POWER_STATE = 0x08,
55
SPI_FLASH_READ = 0x10,
56
SET_LIGHTS = 0x30, // LEDs on controller
57
SET_HOME_LIGHT = 0x38,
58
ENABLE_IMU = 0x40,
59
SET_IMU_SENS = 0x41,
60
ENABLE_VIBRATION = 0x48,
61
};
62
63
constexpr int SwitchPro_INPUT_REPORT_LEN = 362;
64
constexpr int SwitchPro_OUTPUT_REPORT_LEN = 49;
65
constexpr int SwitchPro_RUMBLE_REPORT_LEN = 64;
66
67
static const u8 g_switchProCmdBufHeader[] = {0x0, 0x1, 0x40, 0x40, 0x0, 0x1, 0x40, 0x40};
68
69
constexpr int SwitchPro_IMU_DATA_OFFSET = 13; // Where IMU starts in 0x30 report
70
constexpr float ACCEL_SCALE = 0.000244f; // Raw to G (range ±8G)
71
constexpr float GYRO_SCALE = 0.070f; // Raw to deg/s (range ±2000dps)
72
73
struct SwitchProInputReport {
74
u8 reportId;
75
u8 padding;
76
u8 batteryLevel;
77
u8 buttons[3];
78
u8 lStick[3]; // 2 12-bit values.
79
u8 rStick[3]; // 2 12-bit values.
80
81
u8 vibrator_report;
82
u8 imu_data[36]; // 3 samples of 6-axis data (12 bytes each)
83
};
84
85
static void ProcessIMU(const u8 *data, HIDControllerState *state) {
86
// Data contains 3 samples to account for Bluetooth jitter.
87
// We just take the latest one (index 2).
88
const u8* s = &data[24];
89
90
// Raw values are little endian int16
91
short ax = (short)(s[0] | (s[1] << 8));
92
short ay = (short)(s[2] | (s[3] << 8));
93
short az = (short)(s[4] | (s[5] << 8));
94
95
short gx = (short)(s[6] | (s[7] << 8));
96
short gy = (short)(s[8] | (s[9] << 8));
97
short gz = (short)(s[10] | (s[11] << 8));
98
99
// Convert to physical units
100
state->accelerometer[0] = ax * ACCEL_SCALE;
101
state->accelerometer[1] = ay * ACCEL_SCALE;
102
state->accelerometer[2] = az * ACCEL_SCALE;
103
104
// TODO: Consider if these should be radians or degrees. Check what it is...
105
state->gyro[0] = gx * GYRO_SCALE;
106
state->gyro[1] = gy * GYRO_SCALE;
107
state->gyro[2] = gz * GYRO_SCALE;
108
}
109
110
struct StickCal {
111
u16 x_center, y_center;
112
u16 x_min, y_min;
113
u16 x_max, y_max;
114
};
115
116
// Assuming 'data' is the 9-byte payload starting from byte 20 of the input report
117
void DecodeCalibration(const u8* data, StickCal* cal) {
118
// These are 12-bit values packed into 9 bytes
119
cal->x_max = ((data[1] << 8) & 0xF00) | data[0];
120
cal->y_max = (data[2] << 4) | (data[1] >> 4);
121
cal->x_center = ((data[4] << 8) & 0xF00) | data[3];
122
cal->y_center = (data[5] << 4) | (data[4] >> 4);
123
cal->x_min = ((data[7] << 8) & 0xF00) | data[6];
124
cal->y_min = (data[8] << 4) | (data[7] >> 4);
125
}
126
127
static void DecodeSwitchProStick(const u8 *stickData, s8 *outX, s8 *outY) {
128
int x = ((stickData[1] & 0xF) << 8) | (stickData[0]);
129
int y = (stickData[1] >> 4) | (stickData[2] << 4);
130
131
// For some reason the values are not really centered. Let's approximate.
132
// We probably should add some low level calibration?
133
x = (x - 2048) / 12;
134
y = -(y - 1950) / 12;
135
136
*outX = (s8)clamp_value(x, -128, 127);
137
*outY = (s8)clamp_value(y, -128, 127);
138
}
139
140
// Subcommand helper
141
static bool SendSwitchSubcommand(HANDLE handle, u8 subcommand, const u8 *data, u8 len) {
142
u8 buf[64] = {0x01}; // Report ID 0x01 for subcommands
143
static u8 global_packet_num = 0;
144
buf[1] = global_packet_num++;
145
// ... Fill in rumble data (required even if zero)
146
buf[10] = subcommand;
147
if (data && len > 0) memcpy(&buf[11], data, len);
148
149
DWORD written;
150
return WriteFile(handle, buf, 64, &written, nullptr);
151
}
152
153
bool InitializeSwitchPro(HANDLE handle) {
154
// 1. USB Handshake (only needed for wired, safe for BT)
155
u8 cmd_usb_enable = 0x01;
156
DWORD w;
157
u8 handshake[2] = {0x80, 0x01};
158
WriteFile(handle, handshake, 2, &w, nullptr);
159
handshake[1] = 0x02; // Handshake 2
160
WriteFile(handle, handshake, 2, &w, nullptr);
161
162
// 2. Set Full Input Mode (0x30)
163
u8 mode = 0x30;
164
SendSwitchSubcommand(handle, 0x03, &mode, 1);
165
166
// 3. Enable IMU
167
u8 enable = 0x01;
168
SendSwitchSubcommand(handle, 0x40, &enable, 1);
169
170
return true;
171
}
172
173
bool ReadSwitchProInput(HANDLE handle, HIDControllerState *state) {
174
BYTE inputReport[SwitchPro_INPUT_REPORT_LEN]{};
175
DWORD bytesRead = 0;
176
if (!ReadFile(handle, inputReport, sizeof(inputReport), &bytesRead, nullptr)) {
177
u32 error = GetLastError();
178
return false;
179
}
180
181
// Bluetooth often uses 0x21 for sub-command responses, 0x30 for standard data
182
if (inputReport[0] != 0x30 && inputReport[0] != 0x21) return false;
183
184
const SwitchProInputReport* report = (const SwitchProInputReport*)inputReport;
185
u32 buttons = 0;
186
memcpy(&state->buttons, &report->buttons[0], 3);
187
// Decode Sticks (Keep your existing logic)
188
DecodeSwitchProStick(report->lStick, &state->stickAxes[HID_STICK_LX], &state->stickAxes[HID_STICK_LY]);
189
DecodeSwitchProStick(report->rStick, &state->stickAxes[HID_STICK_RX], &state->stickAxes[HID_STICK_RY]);
190
191
// Decode IMU if report type is 0x30
192
if (inputReport[0] == 0x30) {
193
state->accValid = true;
194
ProcessIMU(report->imu_data, state);
195
}
196
197
return true;
198
}
199
200