mode_switch_pro.cpp 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752
  1. #include "mode_switch_pro.h"
  2. #include "triton.h"
  3. #include "gamepad_util.h"
  4. #include "config.h"
  5. #include "haptics.h"
  6. #include "bonds.h"
  7. #include <Adafruit_TinyUSB.h>
  8. #include <Adafruit_LittleFS.h>
  9. #include <InternalFileSystem.h>
  10. #include <Arduino.h>
  11. #include <string.h>
  12. using namespace Adafruit_LittleFS_Namespace;
  13. SwitchProController g_switchPro;
  14. // Switch full-report (0x30) cadence. The console integrates the 3 IMU samples per report assuming ~5 ms/sample
  15. // (3 samples / 15 ms genuine); too high a rate over-integrates the gyro. 120 Hz is drift-free and lower-latency
  16. // (default); 66 Hz is the genuine compat fallback; "full" is the 4 ms PC rate (lowest latency).
  17. #define SW_PRO_REPORT_MS 15u // 66 Hz
  18. #define SW_PRO_REPORT_MS_120 8u // ~120 Hz
  19. // SC2 accel is +/-2g (16384/g); /4 -> the genuine Pro +/-8g (4096/g) the Switch cal expects
  20. #define SW_ACCEL_DIV 4
  21. uint8_t g_swProRate =
  22. 1; // 0 = 66Hz, 1 = 120Hz (default), 2 = full (~250Hz / USB_STREAM_MS)
  23. uint8_t g_swGyroScale10 = 10; // gyro sensitivity x10 (10 = 1.0x)
  24. // Persist the two Switch Pro motion settings in their own tiny file so they never trigger a global-Cfg reset.
  25. #define SWPRO_CFG_FILE "/swprocfg.bin"
  26. void swProSaveCfg()
  27. {
  28. uint8_t b[3] = { 0x01, g_swProRate,
  29. g_swGyroScale10 }; // [ver][rate 0/1/2][gyroScale x10]
  30. InternalFS.remove(SWPRO_CFG_FILE);
  31. File f(InternalFS);
  32. if (f.open(SWPRO_CFG_FILE, FILE_O_WRITE)) {
  33. f.write(b, sizeof b);
  34. f.close();
  35. }
  36. }
  37. static void swProLoadCfg()
  38. {
  39. File f(InternalFS);
  40. uint8_t b[3];
  41. if (f.open(SWPRO_CFG_FILE, FILE_O_READ)) {
  42. if (f.read(b, 3) == 3 && b[0] == 0x01) {
  43. // 0=66Hz, 1=120Hz, 2=full; bad value -> default 120Hz
  44. g_swProRate = (b[1] <= 2) ? b[1] : 1;
  45. g_swGyroScale10 =
  46. (b[2] >= 5 && b[2] <= 30) ?
  47. b[2] :
  48. 10; // sane bounds (0.5x..3.0x)
  49. }
  50. f.close();
  51. }
  52. }
  53. // Scale a gyro axis by g_swGyroScale10/10, clamped to int16 (3x can saturate at high rates -- expected).
  54. static int16_t gscale(int16_t v)
  55. {
  56. if (g_swGyroScale10 == 10)
  57. return v;
  58. int32_t s = (int32_t)v * (int32_t)g_swGyroScale10 / 10;
  59. if (s > 32767)
  60. s = 32767;
  61. else if (s < -32768)
  62. s = -32768;
  63. return (int16_t)s;
  64. }
  65. static const uint8_t SWPRO_HID_DESC[] = {
  66. 0x05, 0x01, 0x15, 0x00, 0x09, 0x04, 0xA1, 0x01, 0x85, 0x30, 0x05, 0x01,
  67. 0x05, 0x09, 0x19, 0x01, 0x29, 0x0A, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01,
  68. 0x95, 0x0A, 0x55, 0x00, 0x65, 0x00, 0x81, 0x02, 0x05, 0x09, 0x19, 0x0B,
  69. 0x29, 0x0E, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x04, 0x81, 0x02,
  70. 0x75, 0x01, 0x95, 0x02, 0x81, 0x03, 0x0B, 0x01, 0x00, 0x01, 0x00, 0xA1,
  71. 0x00, 0x0B, 0x30, 0x00, 0x01, 0x00, 0x0B, 0x31, 0x00, 0x01, 0x00, 0x0B,
  72. 0x32, 0x00, 0x01, 0x00, 0x0B, 0x35, 0x00, 0x01, 0x00, 0x15, 0x00, 0x27,
  73. 0xFF, 0xFF, 0x00, 0x00, 0x75, 0x10, 0x95, 0x04, 0x81, 0x02, 0xC0, 0x0B,
  74. 0x39, 0x00, 0x01, 0x00, 0x15, 0x00, 0x25, 0x07, 0x35, 0x00, 0x46, 0x3B,
  75. 0x01, 0x65, 0x14, 0x75, 0x04, 0x95, 0x01, 0x81, 0x42, 0x05, 0x09, 0x19,
  76. 0x0F, 0x29, 0x12, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x04, 0x81,
  77. 0x02, 0x75, 0x08, 0x95, 0x34, 0x81, 0x03, 0x06, 0x00, 0xFF, 0x85, 0x21,
  78. 0x09, 0x01, 0x75, 0x08, 0x95, 0x3F, 0x81, 0x03, 0x85, 0x81, 0x09, 0x02,
  79. 0x75, 0x08, 0x95, 0x3F, 0x81, 0x03, 0x85, 0x01, 0x09, 0x03, 0x75, 0x08,
  80. 0x95, 0x3F, 0x91, 0x83, 0x85, 0x10, 0x09, 0x04, 0x75, 0x08, 0x95, 0x3F,
  81. 0x91, 0x83, 0x85, 0x80, 0x09, 0x05, 0x75, 0x08, 0x95, 0x3F, 0x91, 0x83,
  82. 0x85, 0x82, 0x09, 0x06, 0x75, 0x08, 0x95, 0x3F, 0x91, 0x83, 0xC0
  83. };
  84. #define JC_BTN_Y (1u << 0)
  85. #define JC_BTN_X (1u << 1)
  86. #define JC_BTN_B (1u << 2)
  87. #define JC_BTN_A (1u << 3)
  88. #define JC_BTN_R (1u << 6)
  89. #define JC_BTN_ZR (1u << 7)
  90. #define JC_BTN_MINUS (1u << 8)
  91. #define JC_BTN_PLUS (1u << 9)
  92. #define JC_BTN_RSTICK (1u << 10)
  93. #define JC_BTN_LSTICK (1u << 11)
  94. #define JC_BTN_HOME (1u << 12)
  95. #define JC_BTN_DOWN (1u << 16)
  96. #define JC_BTN_UP (1u << 17)
  97. #define JC_BTN_RIGHT (1u << 18)
  98. #define JC_BTN_LEFT (1u << 19)
  99. #define JC_BTN_L (1u << 22)
  100. #define JC_BTN_ZL (1u << 23)
  101. static uint32_t codeToJc(uint8_t c, uint32_t fA, uint32_t fB, uint32_t fX,
  102. uint32_t fY)
  103. {
  104. switch (c) {
  105. case 1:
  106. return fA;
  107. case 2:
  108. return fB;
  109. case 3:
  110. return fX;
  111. case 4:
  112. return fY;
  113. case 5:
  114. return JC_BTN_L;
  115. case 6:
  116. return JC_BTN_R;
  117. case 7:
  118. return JC_BTN_LSTICK;
  119. case 8:
  120. return JC_BTN_RSTICK;
  121. case 9:
  122. return JC_BTN_MINUS;
  123. case 10:
  124. return JC_BTN_PLUS;
  125. case 11:
  126. return JC_BTN_HOME;
  127. case 12:
  128. return JC_BTN_UP;
  129. case 13:
  130. return JC_BTN_DOWN;
  131. case 14:
  132. return JC_BTN_LEFT;
  133. case 15:
  134. return JC_BTN_RIGHT;
  135. default:
  136. return 0;
  137. }
  138. }
  139. // NSLOT Pro-Controller HIDs (one per bond slot) + per-slot handshake state (timer, report-mode gate,
  140. // subcommand-reply FIFO), per-slot reply queue indices, per-slot last-stream millis, per-slot MAC, and
  141. // per-slot user-cal SPI mirror.
  142. static Adafruit_USBD_HID g_swPro[NSLOT];
  143. static unsigned long g_swProLastMs[NSLOT] = { 0 };
  144. // 0 until the host's subcommand 0x03 selects 0x30 -> THEN we stream input
  145. static uint8_t g_swProReportMode[NSLOT] = { 0 };
  146. static uint8_t g_jcTimer[NSLOT] = { 0 };
  147. // Per-slot MAC. The console uses the BT addr it reads from subcommand 0x02 to identify the controller;
  148. // real Pro Controllers each have their own. Last byte varies per slot.
  149. static const uint8_t JC_MAC_BASE[5] = { 0x7C, 0xBB, 0x8A, 0x00, 0x00 };
  150. static uint8_t g_jcMac[NSLOT][6];
  151. static bool g_jcMacInit = false;
  152. static void initJcMacs()
  153. {
  154. if (g_jcMacInit)
  155. return;
  156. for (int s = 0; s < NSLOT; s++) {
  157. memcpy(g_jcMac[s], JC_MAC_BASE, 5);
  158. g_jcMac[s][5] = (uint8_t)(0x10 + s);
  159. }
  160. g_jcMacInit = true;
  161. }
  162. static uint16_t jcRumbleAmp(const uint8_t r[4])
  163. {
  164. // Nintendo packs a high band (r[0],r[1]) and low band (r[2],r[3]) of frequency+amplitude. We only need a
  165. // magnitude for the Steam motor, so pull the two amplitude fields: HF amp = r[1]>>1, LF amp = r[3]&0x3F.
  166. // Every canonical idle/neutral frame (00 01 40 40, 00 00 01 40, all-zero) decodes to 0 this way, so a steady
  167. // idle rumble stream maps to "off" instead of latching the motor on. No forced floor: 0 amplitude -> 0.
  168. uint8_t hf = r[1] >> 1; // high-band amplitude (neutral 0x01 -> 0)
  169. uint8_t lf = r[3] & 0x3F; // low-band amplitude (neutral 0x40 -> 0)
  170. uint8_t a = hf > lf ? hf : lf; // 0..0x7F
  171. return (uint16_t)a << 9; // scale to ~16-bit motor speed
  172. }
  173. // Per-slot: each Pro Controller has its own rumble stream, so the "last" relay tracking must be per-slot.
  174. static uint16_t g_jcLastLo[NSLOT] = { 0 };
  175. static uint16_t g_jcLastHi[NSLOT] = { 0 };
  176. static void jcRumble(uint8_t slot, const uint8_t *p, uint16_t pn)
  177. {
  178. if (pn < 9)
  179. return; // [timer][left rumble x4][right rumble x4]
  180. uint16_t lo = jcRumbleAmp(p + 1), hi = jcRumbleAmp(p + 5);
  181. // only relay on change: the Switch streams rumble every frame; re-sending
  182. // unchanged values would flood the RF relay and loop the motor
  183. if (lo == g_jcLastLo[slot] && hi == g_jcLastHi[slot])
  184. return;
  185. g_jcLastLo[slot] = lo;
  186. g_jcLastHi[slot] = hi;
  187. hapticSteamRumble(lo, hi, slot);
  188. }
  189. static int jcStick12(int16_t v, bool inv)
  190. { // steam int16 (center 0) -> 12-bit (center 0x800), clamped
  191. int a = 2048 + (inv ? -((int)v >> 4) : ((int)v >> 4));
  192. return a < 0 ? 0 : (a > 4095 ? 4095 : a);
  193. }
  194. // pack two 12-bit axes into 3 bytes (both normal; Switch Y polarity is opposite DS4)
  195. static void jcPackStick(uint8_t s[3], int16_t x, int16_t y)
  196. {
  197. int X = jcStick12(x, false), Y = jcStick12(y, false);
  198. s[0] = (uint8_t)(X & 0xFF);
  199. s[1] = (uint8_t)(((Y & 0x0F) << 4) | ((X >> 8) & 0x0F));
  200. s[2] = (uint8_t)((Y >> 4) & 0xFF);
  201. }
  202. // Standard input-report prefix [0..11] (timer, battery/conn, 3 button bytes, both packed sticks, vibrator),
  203. // shared by the streamed 0x30 report and the 0x21 subcommand-reply reports the host reads during init.
  204. static void jcInputPrefix(uint8_t slot, uint8_t *out)
  205. {
  206. uint32_t b = g_in[slot].buttons;
  207. if (g_qamMap && (b & TB_QAM)) {
  208. b &= ~(uint32_t)TB_QAM;
  209. b |= tritonFromCode(g_qamMap);
  210. }
  211. if ((b & CHORD_BACK4) == CHORD_BACK4)
  212. b &= ~(uint32_t)(TB_A | TB_B | TB_X | TB_Y);
  213. uint32_t fA = g_abSwap ? JC_BTN_B : JC_BTN_A,
  214. fB = g_abSwap ? JC_BTN_A : JC_BTN_B;
  215. uint32_t fX = g_abSwap ? JC_BTN_Y : JC_BTN_X,
  216. fY = g_abSwap ? JC_BTN_X : JC_BTN_Y;
  217. uint32_t jc = 0;
  218. if (b & TB_Y)
  219. jc |= fY;
  220. if (b & TB_B)
  221. jc |= fB;
  222. if (b & TB_A)
  223. jc |= fA;
  224. if (b & TB_X)
  225. jc |= fX;
  226. if (b & TB_LB)
  227. jc |= JC_BTN_L;
  228. if (b & TB_RB)
  229. jc |= JC_BTN_R;
  230. if ((g_in[slot].lt >= SW_TRIG_ON) || (b & 0x8000000u))
  231. jc |= JC_BTN_ZL;
  232. if ((g_in[slot].rt >= SW_TRIG_ON) || (b & 0x800000u))
  233. jc |= JC_BTN_ZR;
  234. if (b & TB_VIEW)
  235. jc |= JC_BTN_PLUS;
  236. if (b & TB_MENU)
  237. jc |= JC_BTN_MINUS;
  238. if (b & TB_L3)
  239. jc |= JC_BTN_LSTICK;
  240. if (b & TB_R3)
  241. jc |= JC_BTN_RSTICK;
  242. if (b & TB_STEAM)
  243. jc |= JC_BTN_HOME;
  244. if (b & TB_DDN)
  245. jc |= JC_BTN_DOWN;
  246. if (b & TB_DUP)
  247. jc |= JC_BTN_UP;
  248. if (b & TB_DRT)
  249. jc |= JC_BTN_RIGHT;
  250. if (b & TB_DLF)
  251. jc |= JC_BTN_LEFT;
  252. if (b & TB_L4)
  253. jc |= codeToJc(g_back[0], fA, fB, fX, fY);
  254. if (b & TB_R4)
  255. jc |= codeToJc(g_back[1], fA, fB, fX, fY);
  256. if (b & TB_L5)
  257. jc |= codeToJc(g_back[2], fA, fB, fX, fY);
  258. if (b & TB_R5)
  259. jc |= codeToJc(g_back[3], fA, fB, fX, fY);
  260. out[0] = g_jcTimer[slot]++;
  261. // battery full+charging (hi nibble 0x9), connection_info=1 (lo nibble): a wired/charging Pro Controller.
  262. // A real Switch reads this to show the pad as connected; Steam/hid-nintendo accept it too.
  263. out[1] = 0x91;
  264. out[2] = (uint8_t)(jc);
  265. out[3] = (uint8_t)(jc >> 8);
  266. out[4] = (uint8_t)(jc >> 16);
  267. // charging_grip bit (button "common" byte, bit7): genuine Pro Controller always sets it on USB; real
  268. // Switch uses it to recognise a wired controller. Not a button, so hid-nintendo ignores it.
  269. out[3] |= 0x80;
  270. jcPackStick(out + 5, g_in[slot].lx, g_in[slot].ly);
  271. jcPackStick(out + 8, g_in[slot].rx, g_in[slot].ry);
  272. // rumble_input_report echo: genuine pad emits 0x09..0x0C; some Switch firmware expects this nonzero.
  273. out[11] = 0x09;
  274. }
  275. static void switchProBuild(uint8_t slot, uint8_t out[63])
  276. {
  277. memset(out, 0, 63);
  278. jcInputPrefix(slot, out);
  279. // Gyro slot order follows hid-nintendo: raw+6 = ROLL, raw+8 = PITCH, raw+10 = YAW. Source routing (a proper
  280. // rotation, det +1): SwitchX<-+gy, SwitchY<--gx, SwitchZ<-+gz.
  281. //
  282. // The accel MUST use the SAME signed permutation as the gyro. Steam reads the gyro raw and ignores this, but the
  283. // Switch console FUSES accel (gravity) with gyro to anchor absolute orientation -- if the accel frame has the
  284. // opposite handedness, the gravity-correction term points the wrong way and the attitude estimate latches into a
  285. // rotated solution (intermittent ~45deg axis-mixing, per-unit roll bias, cleared only by a replug). So accel Y
  286. // must be -ax to match the gyro's -gx pitch axis; resting gravity then lands cleanly on Switch Z (the up axis).
  287. // The SC2 accelerometer is +/-2g full-scale (~16384 counts = 1g). The factory cal we present (sensitivity 0x4000)
  288. // tells the Switch it's a genuine +/-8g Pro Controller (4096 counts = 1g), so divide by SW_ACCEL_DIV=4 to report
  289. // ~1g. Without this the console reads gravity as ~4g, REJECTS the accel for drift correction (it must be linear
  290. // accel, not gravity), and gyro roll error accumulates into a slow ~45deg lean. Gyro is left at native scale.
  291. // accel X <- +ay, accel Y <- -ax, accel Z <- +az (signs match gyro)
  292. int16_t aX = (int16_t)(g_in[slot].ay / SW_ACCEL_DIV);
  293. int16_t aY = (int16_t)((-(int16_t)g_in[slot].ax) / SW_ACCEL_DIV);
  294. int16_t aZ = (int16_t)(g_in[slot].az / SW_ACCEL_DIV);
  295. // gyro outputs scaled by the user sensitivity factor
  296. int16_t groll = gscale((int16_t)g_in[slot].gy);
  297. int16_t gpitch = gscale((int16_t)(-(int16_t)g_in[slot].gx));
  298. int16_t gyaw = gscale((int16_t)g_in[slot].gz);
  299. for (int k = 0; k < 3; k++) {
  300. int o = 12 + k * 12;
  301. out[o + 0] = aX & 0xFF;
  302. out[o + 1] = (aX >> 8) & 0xFF;
  303. out[o + 2] = aY & 0xFF;
  304. out[o + 3] = (aY >> 8) & 0xFF;
  305. out[o + 4] = aZ & 0xFF;
  306. out[o + 5] = (aZ >> 8) & 0xFF;
  307. out[o + 6] = groll & 0xFF;
  308. out[o + 7] = (groll >> 8) & 0xFF;
  309. out[o + 8] = gpitch & 0xFF;
  310. out[o + 9] = (gpitch >> 8) & 0xFF;
  311. out[o + 10] = gyaw & 0xFF;
  312. out[o + 11] = (gyaw >> 8) & 0xFF;
  313. }
  314. }
  315. // --- Canonical factory SPI dumps the host reads for calibration. Neutral IMU + centered sticks so a fresh
  316. // "device" calibrates sane; user-cal regions (0x80xx) read 0xFF so the host falls back to these factory blocks.
  317. static const uint8_t SPI_IMU_CAL[24] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  318. 0x00, 0x40, 0x00, 0x40, 0x00, 0x40,
  319. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  320. 0x3B, 0x34, 0x3B, 0x34, 0x3B, 0x34 };
  321. static const uint8_t SPI_PARAMS1[24] = { 0x50, 0xFD, 0x00, 0x00, 0xC6, 0x0F,
  322. 0x0F, 0x30, 0x61, 0x96, 0x30, 0xF3,
  323. 0xD4, 0x14, 0x54, 0x41, 0x15, 0x54,
  324. 0xC7, 0x79, 0x9C, 0x33, 0x36, 0x63 };
  325. static const uint8_t SPI_PARAMS2[18] = { 0x0F, 0x30, 0x61, 0x96, 0x30, 0xF3,
  326. 0xD4, 0x14, 0x54, 0x41, 0x15, 0x54,
  327. 0xC7, 0x79, 0x9C, 0x33, 0x36, 0x63 };
  328. static const uint8_t SPI_COLOR[13] = { 0x32, 0x32, 0x32, 0xE6, 0xE6, 0xE6, 0x32,
  329. 0x32, 0x32, 0x32, 0x32, 0x32, 0xFF };
  330. // built at boot: left[9]+right[9] factory stick calibration (packed 12-bit). Shared across slots -- it's a
  331. // factory stub the host reads to seed its cal, and we present the same neutral stub for all controllers.
  332. static uint8_t g_spiStickCal[18];
  333. static void jcPack12(uint8_t *o9, const uint16_t v[6])
  334. { // pack 6 12-bit values into 9 bytes (Switch stick-cal format)
  335. o9[0] = v[0] & 0xFF;
  336. o9[1] = ((v[1] & 0x0F) << 4) | ((v[0] >> 8) & 0x0F);
  337. o9[2] = (v[1] >> 4) & 0xFF;
  338. o9[3] = v[2] & 0xFF;
  339. o9[4] = ((v[3] & 0x0F) << 4) | ((v[2] >> 8) & 0x0F);
  340. o9[5] = (v[3] >> 4) & 0xFF;
  341. o9[6] = v[4] & 0xFF;
  342. o9[7] = ((v[5] & 0x0F) << 4) | ((v[4] >> 8) & 0x0F);
  343. o9[8] = (v[5] >> 4) & 0xFF;
  344. }
  345. static void jcBuildStickCal()
  346. {
  347. const uint16_t C = 2048, R = 1800; // center, +/- range per axis
  348. uint16_t L[6] = {
  349. R, R, C, C, R, R
  350. }; // left order: max(x,y), center(x,y), min(x,y)
  351. uint16_t Rr[6] = {
  352. C, C, R, R, R, R
  353. }; // right order: center(x,y), min(x,y), max(x,y)
  354. jcPack12(&g_spiStickCal[0], L);
  355. jcPack12(&g_spiStickCal[9], Rr);
  356. }
  357. // Manual-pairing (subcommand 0x01) reply payloads. A real Switch runs this 3-stage BT key exchange over USB so it
  358. // can register the pad (Steam/hid-nintendo do not). The Switch only validates the shape, not the key contents. Each
  359. // is the 31-byte reply body following the 0x21 input prefix + ack(0x81) + echoed-subcommand(0x01).
  360. // type 1: echo type + controller BT addr + "Pro Controller" name
  361. // 31-byte payload uses the per-slot MAC; we fill this on demand.
  362. static const uint8_t BT_PAIR_1_BODY[7] = { 0x01, 0x00, 0x00, 0x00,
  363. 0x00, 0x00, 0x00 };
  364. static const uint8_t BT_PAIR_2[31] = { // type 2: LTK exchange
  365. 0x02, 0xE5, 0xC8, 0xE4, 0x92, 0x05, 0xFF, 0xC9, 0x8A, 0x7D, 0xEA,
  366. 0x15, 0xF6, 0x19, 0xBA, 0x82, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00,
  367. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
  368. };
  369. static const uint8_t BT_PAIR_3[31] = {
  370. 0x03
  371. }; // type 3: save pairing (all zero body)
  372. // User-calibration SPI mirror (0x8000-0x80FF). A real Pro Controller stores the gyro/accel (and stick) calibration
  373. // the Switch writes during "Calibrate Motion Controls" here, then reads it back and applies it -- that's how a
  374. // resting IMU/neutral offset gets cancelled. Per-slot: each Pro Controller has its own SPI mirror, so each
  375. // gets its own persisted file (NSLOT files; the same 0x100 bytes as before, partitioned by slot index).
  376. #define SWCAL_FILE_BASE "/swimucal"
  377. static uint8_t g_userCal[NSLOT][0x100];
  378. static bool g_userCalLoaded[NSLOT] = { false, false, false, false };
  379. static void swCalFileName(uint8_t slot, char *out)
  380. {
  381. // "/swimucal0.bin" .. "/swimucal3.bin"
  382. const char *base = SWCAL_FILE_BASE;
  383. int i = 0;
  384. while (base[i]) {
  385. out[i] = base[i];
  386. i++;
  387. }
  388. out[i++] = (char)('0' + slot);
  389. out[i++] = '.';
  390. out[i++] = 'b';
  391. out[i++] = 'i';
  392. out[i++] = 'n';
  393. out[i] = '\0';
  394. }
  395. static void loadUserCal()
  396. {
  397. for (int s = 0; s < NSLOT; s++) {
  398. memset(g_userCal[s], 0xFF, sizeof g_userCal[s]);
  399. char fn[16];
  400. swCalFileName((uint8_t)s, fn);
  401. File f(InternalFS);
  402. if (f.open(fn, FILE_O_READ)) {
  403. f.read(g_userCal[s], sizeof g_userCal[s]);
  404. f.close();
  405. }
  406. g_userCalLoaded[s] = true;
  407. }
  408. }
  409. static void saveUserCal(uint8_t slot)
  410. {
  411. char fn[16];
  412. swCalFileName(slot, fn);
  413. InternalFS.remove(fn);
  414. File f(InternalFS);
  415. if (f.open(fn, FILE_O_WRITE)) {
  416. f.write(g_userCal[slot], sizeof g_userCal[slot]);
  417. f.close();
  418. }
  419. }
  420. // jcSpiWrite runs in the USB ISR (via jcSet). NEVER do flash I/O here -- a blocking LittleFS erase/write in the
  421. // interrupt wedges USB + the RF poll and corrupts state (device drops into a bad state needing a replug). Only
  422. // update the RAM mirror and flag it dirty; task() (main loop) does the actual save, debounced so a calibration
  423. // write-burst coalesces into one flash write. Per-slot: each Pro Controller's ISR write targets its own mirror.
  424. static volatile bool g_userCalDirty[NSLOT] = { false, false, false, false };
  425. static volatile unsigned long g_userCalDirtyMs[NSLOT] = { 0 };
  426. static void jcSpiWrite(uint8_t slot, uint32_t addr, uint8_t len,
  427. const uint8_t *data, uint16_t avail)
  428. {
  429. bool changed = false;
  430. for (uint8_t i = 0; i < len && i < avail; i++) {
  431. uint32_t a = addr + i;
  432. if (a >= 0x8000 && a < 0x8100) {
  433. if (g_userCal[slot][a - 0x8000] != data[i]) {
  434. g_userCal[slot][a - 0x8000] = data[i];
  435. changed = true;
  436. }
  437. }
  438. }
  439. if (changed) {
  440. g_userCalDirty[slot] = true;
  441. g_userCalDirtyMs[slot] = millis();
  442. } // defer the flash write to task()
  443. }
  444. static void spiRead(uint8_t slot, uint32_t addr, uint8_t len, uint8_t *dst)
  445. {
  446. for (uint8_t i = 0; i < len; i++) {
  447. uint32_t a = addr + i;
  448. uint8_t v = 0xFF;
  449. if (a >= 0x6020 && a < 0x6020 + 24)
  450. v = SPI_IMU_CAL[a - 0x6020];
  451. else if (a >= 0x603D && a < 0x603D + 18)
  452. v = g_spiStickCal[a - 0x603D];
  453. else if (a >= 0x6050 && a < 0x6050 + 13)
  454. v = SPI_COLOR[a - 0x6050];
  455. else if (a >= 0x6080 && a < 0x6080 + 24)
  456. v = SPI_PARAMS1[a - 0x6080];
  457. else if (a >= 0x6098 && a < 0x6098 + 18)
  458. v = SPI_PARAMS2[a - 0x6098];
  459. else if (a >= 0x8000 && a < 0x8100)
  460. // persisted user cal (0xFF blank -> factory fallback)
  461. v = g_userCal[slot][a - 0x8000];
  462. dst[i] = v;
  463. }
  464. }
  465. // Reply FIFO: the host's handshake/subcommand reports arrive in the USB ISR (jcSet); we enqueue the canonical
  466. // 0x81/0x21 reply and drain it from task() where sendReport is safe. SPSC ring, volatile indices. Per-slot: each
  467. // Pro Controller runs its own handshake state machine, so each has its own reply queue. Every reply is sent as
  468. // a full 63-byte report (64 with the id): macOS IOHIDManager silently DROPS any input report shorter than the
  469. // descriptor-declared length (0x3F), so short 0x81/0x21 replies never reached the host.
  470. #define JC_REPLEN 63
  471. struct JcRep {
  472. uint8_t rid;
  473. uint8_t data[JC_REPLEN];
  474. };
  475. #define JCQ_N 8
  476. static volatile uint8_t g_jcQh[NSLOT] = { 0, 0, 0, 0 };
  477. static volatile uint8_t g_jcQt[NSLOT] = { 0, 0, 0, 0 };
  478. static JcRep g_jcQ[NSLOT][JCQ_N];
  479. static void jcEnq(uint8_t slot, uint8_t rid, const uint8_t *d, uint8_t len)
  480. {
  481. uint8_t t = g_jcQt[slot];
  482. uint8_t nt = (uint8_t)((t + 1) % JCQ_N);
  483. if (nt == g_jcQh[slot])
  484. return; // full -> drop; the host re-requests on timeout
  485. if (len > JC_REPLEN)
  486. len = JC_REPLEN;
  487. g_jcQ[slot][t].rid = rid;
  488. memset(g_jcQ[slot][t].data, 0, JC_REPLEN);
  489. memcpy(g_jcQ[slot][t].data, d, len); // zero-pad to full report length
  490. g_jcQt[slot] = nt;
  491. }
  492. // Build the 0x21 reply (standard input prefix + ACK + echoed subcommand id + reply data) for a report-0x01 subcommand.
  493. static void jcSubcmd(uint8_t slot, uint8_t sub, const uint8_t *args,
  494. uint16_t alen)
  495. {
  496. uint8_t p[JC_REPLEN];
  497. memset(p, 0, sizeof p);
  498. jcInputPrefix(slot, p);
  499. p[13] = sub;
  500. switch (sub) {
  501. // manual BT pairing: 3-stage key exchange a real Switch runs over USB
  502. case 0x01: {
  503. uint8_t t = (alen >= 1) ? args[0] : 3;
  504. p[12] = 0x81; // pairing ACK
  505. if (t == 1) {
  506. // echo type + this slot's MAC + "Pro Controller" name
  507. p[14] = BT_PAIR_1_BODY[0];
  508. memcpy(&p[15], g_jcMac[slot], 6);
  509. p[21] = 0x00;
  510. p[22] = 0x25;
  511. p[23] = 0x08;
  512. // "Pro Controller" = 50 72 6F 20 43 6F 6E 74 72 6F 6C 6C 65 72
  513. static const uint8_t NAME[14] = {
  514. 0x50, 0x72, 0x6F, 0x20, 0x43, 0x6F, 0x6E,
  515. 0x74, 0x72, 0x6F, 0x6C, 0x6C, 0x65, 0x72
  516. };
  517. memcpy(&p[24], NAME, 14);
  518. p[38] = 0x00;
  519. p[39] = 0x00;
  520. p[40] = 0x00;
  521. p[41] = 0x00;
  522. p[42] = 0x00;
  523. p[43] = 0x68;
  524. } else if (t == 2) {
  525. memcpy(&p[14], BT_PAIR_2, 31);
  526. } else {
  527. memcpy(&p[14], BT_PAIR_3, 31);
  528. }
  529. break;
  530. }
  531. case 0x02: // request device info
  532. p[12] = 0x82;
  533. p[14] = 0x03;
  534. // firmware version 3.72 (genuine Pro Controller value)
  535. p[15] = 0x48;
  536. p[16] = 0x03; // controller type: Pro Controller
  537. p[17] = 0x02; // fixed
  538. memcpy(&p[18], g_jcMac[slot], 6);
  539. p[24] = 0x01; // colors stored in SPI (0x6050)
  540. p[25] = 0x01; // fixed
  541. break;
  542. case 0x10: { // SPI flash read -> echo [addr][len] then the data
  543. if (alen < 5) {
  544. p[12] = 0x80;
  545. break;
  546. }
  547. uint32_t a = (uint32_t)args[0] | ((uint32_t)args[1] << 8) |
  548. ((uint32_t)args[2] << 16) |
  549. ((uint32_t)args[3] << 24);
  550. uint8_t rl = args[4];
  551. if (rl > 0x1D)
  552. rl = 0x1D;
  553. p[12] = 0x90;
  554. p[14] = args[0];
  555. p[15] = args[1];
  556. p[16] = args[2];
  557. p[17] = args[3];
  558. p[18] = rl;
  559. spiRead(slot, a, rl, &p[19]);
  560. break;
  561. }
  562. // SPI flash write -> persist user calibration so Switch motion-cal sticks
  563. case 0x11:
  564. if (alen >= 5) {
  565. uint32_t a = (uint32_t)args[0] |
  566. ((uint32_t)args[1] << 8) |
  567. ((uint32_t)args[2] << 16) |
  568. ((uint32_t)args[3] << 24);
  569. jcSpiWrite(slot, a, args[4], &args[5],
  570. (alen > 5) ? (uint16_t)(alen - 5) : 0);
  571. }
  572. p[12] = 0x80; // write ACK
  573. break;
  574. // set input report mode (0x30 = standard full) -> begin streaming
  575. case 0x03:
  576. if (alen >= 1 && args[0] == 0x30)
  577. g_swProReportMode[slot] = 0x30;
  578. p[12] = 0x80;
  579. break;
  580. // trigger buttons elapsed time -> canned reply (genuine pad returns data)
  581. case 0x04:
  582. p[12] = 0x83;
  583. p[14] = 0x00;
  584. p[15] = 0xCC;
  585. p[16] = 0x00;
  586. p[17] = 0xEE;
  587. p[18] = 0x00;
  588. p[19] = 0xFF;
  589. break;
  590. case 0x21:
  591. p[12] = 0xA0;
  592. break; // set NFC/IR config
  593. default:
  594. p[12] = 0x80;
  595. // 0x06/0x08/0x30/0x38/0x40/0x41/0x48/... generic positive ACK
  596. break;
  597. }
  598. jcEnq(slot, 0x21, p, JC_REPLEN);
  599. }
  600. // USB-ISR callback for the Pro Controller's OUT endpoint. Per-slot dispatch via per-instance callback
  601. // (jcSet##N closes over the slot index). Report 0x80 = USB handshake; 0x01 = subcommand.
  602. static void jcSetCommon(uint8_t slot, uint8_t rid, hid_report_type_t type,
  603. uint8_t const *b, uint16_t n)
  604. {
  605. if (type != HID_REPORT_TYPE_OUTPUT || n < 1)
  606. return;
  607. uint8_t id;
  608. const uint8_t *p;
  609. uint16_t pn;
  610. if (rid == 0) {
  611. id = b[0];
  612. p = b + 1;
  613. pn = (uint16_t)(n - 1);
  614. } // EP-OUT: id is the first payload byte
  615. else {
  616. id = rid;
  617. p = b;
  618. pn = n;
  619. } // control SET_REPORT: id already split out
  620. if (id == 0x80) {
  621. if (pn < 1)
  622. return;
  623. if (p[0] == 0x01) {
  624. uint8_t d[9] = { 0x01,
  625. 0x00,
  626. 0x03,
  627. g_jcMac[slot][0],
  628. g_jcMac[slot][1],
  629. g_jcMac[slot][2],
  630. g_jcMac[slot][3],
  631. g_jcMac[slot][4],
  632. g_jcMac[slot][5] };
  633. jcEnq(slot, 0x81, d, 9);
  634. } // device type + MAC
  635. else if (p[0] == 0x02) {
  636. uint8_t d[1] = { 0x02 };
  637. jcEnq(slot, 0x81, d, 1);
  638. } // handshake
  639. else if (p[0] == 0x03) {
  640. uint8_t d[1] = { 0x03 };
  641. jcEnq(slot, 0x81, d, 1);
  642. } // set baudrate
  643. // 0x04 force-USB / 0x05 enable-timeout / 0x06 reset: no reply expected
  644. return;
  645. }
  646. if (id == 0x01) { // [timer][rumble x8][subcmd][args...]
  647. if (pn < 10)
  648. return;
  649. jcRumble(slot, p, pn);
  650. jcSubcmd(slot, p[9], p + 10,
  651. (pn > 10) ? (uint16_t)(pn - 10) : 0);
  652. return;
  653. }
  654. if (id == 0x10) {
  655. jcRumble(slot, p, pn);
  656. return;
  657. }
  658. // report 0x82: ignored
  659. }
  660. #define JCCB(N) \
  661. static void jcSet##N(uint8_t r, hid_report_type_t t, uint8_t const *b, \
  662. uint16_t n) \
  663. { \
  664. jcSetCommon(N, r, t, b, n); \
  665. }
  666. // clang-format off
  667. JCCB(0)
  668. JCCB(1)
  669. JCCB(2)
  670. JCCB(3)
  671. // clang-format on
  672. typedef void (*jc_setcb_t)(uint8_t, hid_report_type_t, uint8_t const *,
  673. uint16_t);
  674. static jc_setcb_t const JC_SETCB[NSLOT] = { jcSet0, jcSet1, jcSet2, jcSet3 };
  675. void SwitchProController::begin()
  676. {
  677. USBDevice.setID(0x057E, 0x2009);
  678. // bumped from 0x0211 for 1ms poll interval (Windows caches config by VID:PID:bcdDevice). Bumped
  679. // 0x0212 -> 0x0213 for the 4-Pro enumeration (3 more HID interfaces vs the original).
  680. USBDevice.setDeviceVersion(0x0213);
  681. USBDevice.setManufacturerDescriptor("Nintendo Co., Ltd.");
  682. USBDevice.setProductDescriptor("Pro Controller");
  683. jcBuildStickCal();
  684. // restore any persisted Switch motion calibration (per-unit IMU bias correction). Per-slot files.
  685. loadUserCal();
  686. swProLoadCfg(); // restore report-rate + gyro-scale settings
  687. initJcMacs();
  688. for (int s = 0; s < NSLOT; s++) {
  689. g_swPro[s].enableOutEndpoint(true);
  690. // answer the Nintendo USB handshake + subcommands (else Steam never binds it)
  691. g_swPro[s].setReportCallback(NULL, JC_SETCB[s]);
  692. g_swPro[s].setReportDescriptor(SWPRO_HID_DESC,
  693. sizeof SWPRO_HID_DESC);
  694. // 1ms bInterval so the RF rate is the only latency limit (matches Xbox)
  695. g_swPro[s].setPollInterval(1);
  696. g_swPro[s].begin();
  697. }
  698. }
  699. void SwitchProController::task()
  700. {
  701. for (int s = 0; s < NSLOT; s++) {
  702. if (!g_swPro[s].ready())
  703. continue;
  704. // Deferred user-cal flash write (queued by the USB ISR; debounced so a calibration write-burst
  705. // is one save). Per-slot: each Pro Controller saves its own mirror.
  706. if (g_userCalDirty[s] &&
  707. (unsigned long)(millis() - g_userCalDirtyMs[s]) > 250u) {
  708. g_userCalDirty[s] = false;
  709. saveUserCal((uint8_t)s);
  710. }
  711. // drain handshake/subcommand replies first (ordered) -- one report per slot per call so a
  712. // bursty host-init doesn't starve the streamed 0x30
  713. if (g_jcQh[s] != g_jcQt[s]) {
  714. JcRep *r = &g_jcQ[s][g_jcQh[s]];
  715. if (g_swPro[s].sendReport(r->rid, r->data, JC_REPLEN))
  716. g_jcQh[s] = (uint8_t)((g_jcQh[s] + 1) % JCQ_N);
  717. // one report per slot per call; the rest next loop
  718. continue;
  719. }
  720. // not until the host has finished init + selected 0x30
  721. if (g_swProReportMode[s] != 0x30)
  722. continue;
  723. // The Switch integrates the report's 3 IMU samples by SAMPLE COUNT at a fixed ~5 ms/sample (it ignores
  724. // the timer byte and assumes a 3-samples-per-15ms genuine cadence). Streaming faster (e.g. 250 Hz x 3
  725. // = 750 samples/s) over-credits gyro rotation ~3.75x, so residual bias accumulates into the slow
  726. // orientation lean that builds over minutes and resets on replug. 15 ms matches the genuine push,
  727. // making integration 1:1.
  728. uint32_t interval = (g_swProRate == 2) ? USB_STREAM_MS :
  729. (g_swProRate == 1) ? SW_PRO_REPORT_MS_120 :
  730. SW_PRO_REPORT_MS;
  731. if (millis() - g_swProLastMs[s] < interval)
  732. continue;
  733. g_swProLastMs[s] = millis();
  734. uint8_t p[63];
  735. switchProBuild((uint8_t)s, p);
  736. g_swPro[s].sendReport(0x30, p, sizeof p);
  737. }
  738. }