mode_switch_pro.cpp 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986
  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 "rf_link.h"
  8. #include "usb_mount.h"
  9. #include "usb_tx.h"
  10. #include <Adafruit_TinyUSB.h>
  11. #include <Adafruit_LittleFS.h>
  12. #include <InternalFileSystem.h>
  13. #include <Arduino.h>
  14. #include <string.h>
  15. #include <math.h>
  16. using namespace Adafruit_LittleFS_Namespace;
  17. SwitchProController g_switchPro;
  18. // Switch full-report (0x30) cadence: the 4 ms PC rate (lowest latency). The console integrates the 3 IMU samples
  19. // per report by sample COUNT at a fixed ~5 ms/sample, so slower cadences were once offered as drift fallbacks;
  20. // they are gone -- full rate is what everything ships with.
  21. // SC2 accel is +/-2g (16384/g); /4 -> the genuine Pro +/-8g (4096/g) the Switch cal expects
  22. #define SW_ACCEL_DIV 4
  23. // 0 = corrected gyro mapping (default, PR #189: roll x0.8, pitch/yaw x0.9 -- matches a genuine Pro Controller and
  24. // Steam mode); 1 = legacy mapping, the raw pre-#189 axes with no trim.
  25. uint8_t g_swGyroLegacy = 0;
  26. // Persist the Switch Pro motion setting in its own tiny file so it never triggers a global-Cfg reset.
  27. #define SWPRO_CFG_FILE "/swprocfg.bin"
  28. void swProSaveCfg()
  29. {
  30. // ver 0x02 = [ver][gyroLegacy]. ver 0x01 was [ver][rate][gyroScale x10]; both knobs are gone, so such a
  31. // file is simply ignored on load (defaults win) rather than migrated.
  32. uint8_t b[2] = { 0x02, g_swGyroLegacy };
  33. InternalFS.remove(SWPRO_CFG_FILE);
  34. File f(InternalFS);
  35. if (f.open(SWPRO_CFG_FILE, FILE_O_WRITE)) {
  36. f.write(b, sizeof b);
  37. f.close();
  38. }
  39. }
  40. static void swProLoadCfg()
  41. {
  42. File f(InternalFS);
  43. uint8_t b[2];
  44. if (f.open(SWPRO_CFG_FILE, FILE_O_READ)) {
  45. if (f.read(b, 2) == 2 && b[0] == 0x02)
  46. g_swGyroLegacy = b[1] ? 1 : 0;
  47. f.close();
  48. }
  49. }
  50. static const uint8_t SWPRO_HID_DESC[] = {
  51. 0x05, 0x01, 0x15, 0x00, 0x09, 0x04, 0xA1, 0x01, 0x85, 0x30, 0x05, 0x01,
  52. 0x05, 0x09, 0x19, 0x01, 0x29, 0x0A, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01,
  53. 0x95, 0x0A, 0x55, 0x00, 0x65, 0x00, 0x81, 0x02, 0x05, 0x09, 0x19, 0x0B,
  54. 0x29, 0x0E, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x04, 0x81, 0x02,
  55. 0x75, 0x01, 0x95, 0x02, 0x81, 0x03, 0x0B, 0x01, 0x00, 0x01, 0x00, 0xA1,
  56. 0x00, 0x0B, 0x30, 0x00, 0x01, 0x00, 0x0B, 0x31, 0x00, 0x01, 0x00, 0x0B,
  57. 0x32, 0x00, 0x01, 0x00, 0x0B, 0x35, 0x00, 0x01, 0x00, 0x15, 0x00, 0x27,
  58. 0xFF, 0xFF, 0x00, 0x00, 0x75, 0x10, 0x95, 0x04, 0x81, 0x02, 0xC0, 0x0B,
  59. 0x39, 0x00, 0x01, 0x00, 0x15, 0x00, 0x25, 0x07, 0x35, 0x00, 0x46, 0x3B,
  60. 0x01, 0x65, 0x14, 0x75, 0x04, 0x95, 0x01, 0x81, 0x42, 0x05, 0x09, 0x19,
  61. 0x0F, 0x29, 0x12, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x04, 0x81,
  62. 0x02, 0x75, 0x08, 0x95, 0x34, 0x81, 0x03, 0x06, 0x00, 0xFF, 0x85, 0x21,
  63. 0x09, 0x01, 0x75, 0x08, 0x95, 0x3F, 0x81, 0x03, 0x85, 0x81, 0x09, 0x02,
  64. 0x75, 0x08, 0x95, 0x3F, 0x81, 0x03, 0x85, 0x01, 0x09, 0x03, 0x75, 0x08,
  65. 0x95, 0x3F, 0x91, 0x83, 0x85, 0x10, 0x09, 0x04, 0x75, 0x08, 0x95, 0x3F,
  66. 0x91, 0x83, 0x85, 0x80, 0x09, 0x05, 0x75, 0x08, 0x95, 0x3F, 0x91, 0x83,
  67. 0x85, 0x82, 0x09, 0x06, 0x75, 0x08, 0x95, 0x3F, 0x91, 0x83, 0xC0
  68. };
  69. #define JC_BTN_Y (1u << 0)
  70. #define JC_BTN_X (1u << 1)
  71. #define JC_BTN_B (1u << 2)
  72. #define JC_BTN_A (1u << 3)
  73. #define JC_BTN_R (1u << 6)
  74. #define JC_BTN_ZR (1u << 7)
  75. #define JC_BTN_MINUS (1u << 8)
  76. #define JC_BTN_PLUS (1u << 9)
  77. #define JC_BTN_RSTICK (1u << 10)
  78. #define JC_BTN_LSTICK (1u << 11)
  79. #define JC_BTN_HOME (1u << 12)
  80. #define JC_BTN_CAPTURE (1u << 13)
  81. #define JC_BTN_DOWN (1u << 16)
  82. #define JC_BTN_UP (1u << 17)
  83. #define JC_BTN_RIGHT (1u << 18)
  84. #define JC_BTN_LEFT (1u << 19)
  85. #define JC_BTN_L (1u << 22)
  86. #define JC_BTN_ZL (1u << 23)
  87. static uint32_t codeToJc(uint8_t c, uint32_t fA, uint32_t fB, uint32_t fX,
  88. uint32_t fY)
  89. {
  90. switch (c) {
  91. case 1:
  92. return fA;
  93. case 2:
  94. return fB;
  95. case 3:
  96. return fX;
  97. case 4:
  98. return fY;
  99. case 5:
  100. return JC_BTN_L;
  101. case 6:
  102. return JC_BTN_R;
  103. case 7:
  104. return JC_BTN_LSTICK;
  105. case 8:
  106. return JC_BTN_RSTICK;
  107. case 9:
  108. return JC_BTN_MINUS;
  109. case 10:
  110. return JC_BTN_PLUS;
  111. case 11:
  112. return JC_BTN_HOME;
  113. case 18:
  114. return JC_BTN_CAPTURE; // Capture / Screenshot (Switch-only target)
  115. case 19:
  116. return JC_BTN_ZL; // left trigger
  117. case 20:
  118. return JC_BTN_ZR; // right trigger
  119. case 12:
  120. return JC_BTN_UP;
  121. case 13:
  122. return JC_BTN_DOWN;
  123. case 14:
  124. return JC_BTN_LEFT;
  125. case 15:
  126. return JC_BTN_RIGHT;
  127. default:
  128. return 0;
  129. }
  130. }
  131. // NSLOT Pro-Controller HIDs (one per bond slot) + per-slot handshake state (timer, report-mode gate,
  132. // subcommand-reply FIFO), per-slot reply queue indices, per-slot last-stream millis, per-slot MAC, and
  133. // per-slot user-cal SPI mirror.
  134. static Adafruit_USBD_HID g_swPro[NSLOT];
  135. static unsigned long g_swProLastMs[NSLOT] = { 0 };
  136. // 0 until the host's subcommand 0x03 selects 0x30 -> THEN we stream input
  137. static uint8_t g_swProReportMode[NSLOT] = { 0 };
  138. static uint8_t g_jcTimer[NSLOT] = { 0 };
  139. // Per-slot MAC. The console uses the BT addr it reads from subcommand 0x02 to identify the controller;
  140. // real Pro Controllers each have their own. Last byte varies per slot.
  141. static const uint8_t JC_MAC_BASE[5] = { 0x7C, 0xBB, 0x8A, 0x00, 0x00 };
  142. static uint8_t g_jcMac[NSLOT][6];
  143. static bool g_jcMacInit = false;
  144. static void initJcMacs()
  145. {
  146. if (g_jcMacInit)
  147. return;
  148. for (int s = 0; s < NSLOT; s++) {
  149. memcpy(g_jcMac[s], JC_MAC_BASE, 5);
  150. g_jcMac[s][5] = (uint8_t)(0x10 + s);
  151. }
  152. g_jcMacInit = true;
  153. }
  154. // Dynamic mount: the handshake/state arrays are keyed by USB slot, but the controller INPUT (and rumble target)
  155. // belongs to the bond slot the USB slot is mapped to. This resolves usbSlot -> bondSlot (identity fallback if
  156. // somehow unmapped, e.g. legacy paths).
  157. static inline uint8_t jcBondOf(uint8_t usbSlot)
  158. {
  159. int b = (usbSlot < NSLOT) ? g_usbToBond[usbSlot] : -1;
  160. return (b >= 0) ? (uint8_t)b : usbSlot;
  161. }
  162. // --- Switch HD-rumble amplitude decoder.
  163. //
  164. // When we emulate a Pro Controller, the console streams us its HD-rumble output
  165. // every frame. A genuine controller drives dual-band linear actuators from it;
  166. // OpenPuck drives the SC2's single motor, so all we need is a scalar amplitude.
  167. // This decodes the on-wire rumble format down to that amplitude.
  168. //
  169. // Format (from the public Switch controller protocol, cross-checked against
  170. // SDL's zlib-licensed hidapi_switch rumble encoder and community RE notes):
  171. // each motor carries 4 bytes = one little-endian 32-bit word whose top two bits
  172. // pick how many amplitude/frequency updates are packed and at what width:
  173. // mode 0 : no change -- hold the running amplitude
  174. // mode 1 : a single 5-bit or 7-bit update, or a 7-bit + two 5-bit burst
  175. // mode 2 : two 5-bit updates, or a 7-bit + 5-bit followed by a 5-bit update
  176. // mode 3 : three 5-bit updates
  177. // A 7-bit field is an ABSOLUTE amplitude on the documented log2 curve. A 5-bit
  178. // field is a compact command that either substitutes a preset level or nudges
  179. // the amplitude by a small step -- so the decoder carries per-motor state across
  180. // frames. Frequency fields exist but do nothing for an ERM, so we step past them
  181. // and keep only amplitude.
  182. //
  183. // The naive "read fixed bytes as amplitude" decode we shipped before ignored the
  184. // mode bits, so any game using the packed modes (Fire Emblem: Three Houses,
  185. // Crash Bandicoot, ...) decoded to spurious nonzero amplitude -> random idle/menu
  186. // buzzing. Handling every mode is what fixes that.
  187. //
  188. // Amplitude is carried in log2-linear 1/32 fixed-point over [-8.0, 0.0] -> the
  189. // integer range [-256, 0], and mapped to a 16-bit motor level via a boot-built
  190. // exp2 table so no floating point runs in the USB ISR. The curve constants below
  191. // are protocol facts (any correct decoder lands on the same numbers).
  192. enum { HDR_AMP_MIN = -256, HDR_AMP_OFF = -256 }; // -8.0 log2 units == silent
  193. // Absolute 7-bit amplitude code -> 1/32 log2 units. The documented curve is
  194. // piecewise linear with progressively finer steps toward full scale (slopes
  195. // 1/4, 1/16, 1/32); code 0 is silence.
  196. static inline int16_t hdrAmp7(uint8_t code)
  197. {
  198. if (code == 0)
  199. return HDR_AMP_MIN;
  200. if (code < 16)
  201. return (int16_t)(8 * (int)code - 248); // slope 1/4
  202. if (code < 32)
  203. return (int16_t)(2 * (int)code - 158); // slope 1/16
  204. return (int16_t)((int)code - 127); // slope 1/32
  205. }
  206. // Apply a compact 5-bit command to the running amplitude (1/32 log2 units):
  207. // 0 -> silence
  208. // 1..11 -> substitute an absolute preset: 0, -0.5, -1.0, ... -5.0
  209. // 17..22 -> step up (+0.125 for 17-19, +0.03125 for 20-22)
  210. // 26..31 -> step down (-0.03125 for 26-28, -0.125 for 29-31)
  211. // other -> amplitude unchanged (the code only carries a frequency command)
  212. static inline int16_t hdrAmp5(uint8_t code, int16_t cur)
  213. {
  214. if (code == 0)
  215. return HDR_AMP_OFF;
  216. if (code <= 11)
  217. return (int16_t)(-16 * (int)(code - 1)); // presets 0..-5.0
  218. int step = 0;
  219. if (code >= 17 && code <= 19)
  220. step = 4;
  221. else if (code >= 20 && code <= 22)
  222. step = 1;
  223. else if (code >= 26 && code <= 28)
  224. step = -1;
  225. else if (code >= 29 && code <= 31)
  226. step = -4;
  227. int v = (int)cur + step;
  228. return v < HDR_AMP_MIN ? HDR_AMP_MIN : (v > 0 ? 0 : (int16_t)v);
  229. }
  230. // exp2(units/32) scaled to a 16-bit motor level, built once at boot. The two
  231. // lowest steps are treated as silent (the curve floors out there), matching how
  232. // the neutral/idle frame -- which decodes to minimum amplitude -- reads as off.
  233. static uint16_t g_hdrLevel[257];
  234. static void hdrBuildLevels()
  235. {
  236. for (int u = HDR_AMP_MIN; u <= 0; u++) {
  237. float lin = (float)u / 32.0f;
  238. float amp = (lin >= -7.9375f) ? exp2f(lin) : 0.0f;
  239. if (amp > 1.0f)
  240. amp = 1.0f;
  241. uint32_t v = (uint32_t)(amp * 65535.0f + 0.5f);
  242. g_hdrLevel[u - HDR_AMP_MIN] = (v > 0xFFFF) ? 0xFFFF :
  243. (uint16_t)v;
  244. }
  245. }
  246. // Per-slot, per-motor (0 = left, 1 = right) running band amplitudes. The packed
  247. // 5-bit commands are relative, so this state must persist between frames.
  248. struct HdrBands {
  249. int16_t lo; // low-band amplitude, 1/32 log2 units
  250. int16_t hi; // high-band amplitude, 1/32 log2 units
  251. };
  252. static HdrBands g_hdrState[NSLOT][2];
  253. static inline void hdrReset(uint8_t slot)
  254. {
  255. g_hdrState[slot][0].lo = g_hdrState[slot][0].hi = HDR_AMP_OFF;
  256. g_hdrState[slot][1].lo = g_hdrState[slot][1].hi = HDR_AMP_OFF;
  257. }
  258. // Pull a bit-field out of the 32-bit rumble word.
  259. static inline uint8_t hdrField(uint32_t w, uint8_t shift, uint8_t width)
  260. {
  261. return (uint8_t)((w >> shift) & ((1u << width) - 1u));
  262. }
  263. // Decode one motor's 4 rumble bytes, advancing its state, and return the PEAK
  264. // motor level over the frame's updates (max across both bands and all samples).
  265. // Peak rather than final-sample keeps short pulses that a multi-update frame
  266. // packs together, while every idle/neutral frame still resolves to 0.
  267. static uint16_t hdrDecode(uint8_t slot, uint8_t motor, const uint8_t b[4])
  268. {
  269. HdrBands &s = g_hdrState[slot][motor];
  270. uint32_t w = (uint32_t)b[0] | ((uint32_t)b[1] << 8) |
  271. ((uint32_t)b[2] << 16) | ((uint32_t)b[3] << 24);
  272. uint16_t peak = 0;
  273. #define HDR_SAMPLE() \
  274. do { \
  275. uint16_t la = g_hdrLevel[s.lo - HDR_AMP_MIN]; \
  276. uint16_t ha = g_hdrLevel[s.hi - HDR_AMP_MIN]; \
  277. uint16_t lv = la > ha ? la : ha; \
  278. if (lv > peak) \
  279. peak = lv; \
  280. } while (0)
  281. switch (hdrField(w, 30, 2)) {
  282. case 0: // hold
  283. HDR_SAMPLE();
  284. break;
  285. case 1:
  286. if ((w & 0xFFFFF) == 0) { // single 5-bit update
  287. s.lo = hdrAmp5(hdrField(w, 25, 5), s.lo);
  288. s.hi = hdrAmp5(hdrField(w, 20, 5), s.hi);
  289. HDR_SAMPLE();
  290. } else if ((w & 0x3) == 0) { // single 7-bit absolute
  291. s.lo = hdrAmp7(hdrField(w, 23, 7));
  292. s.hi = hdrAmp7(hdrField(w, 9, 7));
  293. HDR_SAMPLE();
  294. } else { // 7-bit for one band + two 5-bit updates
  295. bool wantHi = (w & 1) != 0;
  296. bool isFreq = ((w >> 2) & 1) != 0;
  297. if (!isFreq) { // else the 7-bit is a frequency: ignore
  298. if (wantHi)
  299. s.hi = hdrAmp7(hdrField(w, 23, 7));
  300. else
  301. s.lo = hdrAmp7(hdrField(w, 23, 7));
  302. }
  303. HDR_SAMPLE();
  304. s.lo = hdrAmp5(hdrField(w, 18, 5), s.lo);
  305. s.hi = hdrAmp5(hdrField(w, 13, 5), s.hi);
  306. HDR_SAMPLE();
  307. s.lo = hdrAmp5(hdrField(w, 8, 5), s.lo);
  308. s.hi = hdrAmp5(hdrField(w, 3, 5), s.hi);
  309. HDR_SAMPLE();
  310. }
  311. break;
  312. case 2:
  313. if ((w & 0x3FF) == 0) { // two 5-bit updates
  314. s.lo = hdrAmp5(hdrField(w, 25, 5), s.lo);
  315. s.hi = hdrAmp5(hdrField(w, 20, 5), s.hi);
  316. HDR_SAMPLE();
  317. s.lo = hdrAmp5(hdrField(w, 15, 5), s.lo);
  318. s.hi = hdrAmp5(hdrField(w, 10, 5), s.hi);
  319. HDR_SAMPLE();
  320. } else { // 7-bit + 5-bit, then a 5-bit update
  321. if (w & 1) {
  322. s.hi = hdrAmp7(hdrField(w, 23, 7));
  323. s.lo = hdrAmp5(hdrField(w, 18, 5), s.lo);
  324. } else {
  325. s.lo = hdrAmp7(hdrField(w, 23, 7));
  326. s.hi = hdrAmp5(hdrField(w, 18, 5), s.hi);
  327. }
  328. HDR_SAMPLE();
  329. s.lo = hdrAmp5(hdrField(w, 13, 5), s.lo);
  330. s.hi = hdrAmp5(hdrField(w, 8, 5), s.hi);
  331. HDR_SAMPLE();
  332. }
  333. break;
  334. case 3: // three 5-bit updates
  335. s.lo = hdrAmp5(hdrField(w, 25, 5), s.lo);
  336. s.hi = hdrAmp5(hdrField(w, 20, 5), s.hi);
  337. HDR_SAMPLE();
  338. s.lo = hdrAmp5(hdrField(w, 15, 5), s.lo);
  339. s.hi = hdrAmp5(hdrField(w, 10, 5), s.hi);
  340. HDR_SAMPLE();
  341. s.lo = hdrAmp5(hdrField(w, 5, 5), s.lo);
  342. s.hi = hdrAmp5(hdrField(w, 0, 5), s.hi);
  343. HDR_SAMPLE();
  344. break;
  345. }
  346. #undef HDR_SAMPLE
  347. return peak;
  348. }
  349. // Per-slot: each Pro Controller has its own rumble stream, so the "last" relay tracking must be per-slot.
  350. static uint16_t g_jcLastLo[NSLOT] = { 0 };
  351. static uint16_t g_jcLastHi[NSLOT] = { 0 };
  352. static void jcRumble(uint8_t slot, const uint8_t *p, uint16_t pn)
  353. {
  354. if (pn < 9)
  355. return; // [timer][left rumble x4][right rumble x4]
  356. uint16_t lo = hdrDecode(slot, 0, p + 1), hi = hdrDecode(slot, 1, p + 5);
  357. // only relay on change: the Switch streams rumble every frame; re-sending
  358. // unchanged values would flood the RF relay and loop the motor
  359. if (lo == g_jcLastLo[slot] && hi == g_jcLastHi[slot])
  360. return;
  361. g_jcLastLo[slot] = lo;
  362. g_jcLastHi[slot] = hi;
  363. hapticSteamRumble(lo, hi,
  364. jcBondOf(slot)); // route to the mapped controller
  365. }
  366. static int jcStick12(int16_t v, bool inv)
  367. { // steam int16 (center 0) -> 12-bit (center 0x800), clamped
  368. int a = 2048 + (inv ? -((int)v >> 4) : ((int)v >> 4));
  369. return a < 0 ? 0 : (a > 4095 ? 4095 : a);
  370. }
  371. // pack two 12-bit axes into 3 bytes (both normal; Switch Y polarity is opposite DS4)
  372. static void jcPackStick(uint8_t s[3], int16_t x, int16_t y)
  373. {
  374. int X = jcStick12(x, false), Y = jcStick12(y, false);
  375. s[0] = (uint8_t)(X & 0xFF);
  376. s[1] = (uint8_t)(((Y & 0x0F) << 4) | ((X >> 8) & 0x0F));
  377. s[2] = (uint8_t)((Y >> 4) & 0xFF);
  378. }
  379. // The Switch battery/connection byte (bat_con) is NOT a 0..8 value in the high nibble. Per hid-nintendo it is:
  380. // bits[7:5] = battery capacity (0=empty .. 4=full), bit4 = charging, bit0 = host_powered (USB-powered).
  381. // A genuine pad therefore only ever emits the EVEN high-nibble levels 8/6/4/2/0 (full/medium/low/critical/empty)
  382. // -- the odd bit (bit4) is reserved for the charging flag. The old code packed a 0..8 value straight into the
  383. // high nibble, so any ODD level (1,3,5,7) set bit4 and the console showed the pad as "charging". Here we round
  384. // the percentage onto the genuine 5-level scale and return the ready-to-place EVEN nibble {0,2,4,6,8}.
  385. static uint8_t jcBatteryNibble(uint8_t bond)
  386. {
  387. if (bond >= NSLOT)
  388. return 0;
  389. uint8_t pct = g_battery[bond];
  390. uint8_t cap; // genuine 0..4 capacity
  391. if (pct >= 70)
  392. cap = 4; // full
  393. else if (pct >= 50)
  394. cap = 3; // medium
  395. else if (pct >= 30)
  396. cap = 2; // low
  397. else if (pct >= 10)
  398. cap = 1; // critical
  399. else
  400. cap = 0; // empty
  401. return (uint8_t)(cap << 1); // -> 0,2,4,6,8 (even; leaves bit4 clear)
  402. }
  403. // Standard input-report prefix [0..11] (timer, battery/conn, 3 button bytes, both packed sticks, vibrator),
  404. // shared by the streamed 0x30 report and the 0x21 subcommand-reply reports the host reads during init.
  405. static void jcInputPrefix(uint8_t slot, uint8_t *out)
  406. {
  407. uint8_t bond = jcBondOf(
  408. slot); // input data comes from the mapped controller; timer/state stay per USB slot
  409. uint32_t b = g_in[bond].buttons;
  410. // QAM (3 dots) remap -> applied via codeToJc below like a back paddle (so Capture(18)/any target work).
  411. bool qam = g_qamMap && (b & TB_QAM);
  412. if ((b & CHORD_BACK4) == CHORD_BACK4)
  413. b &= ~(uint32_t)(TB_A | TB_B | TB_X | TB_Y | TB_DUP | TB_DDN |
  414. TB_DLF | TB_DRT);
  415. uint32_t fA = g_abSwap ? JC_BTN_B : JC_BTN_A,
  416. fB = g_abSwap ? JC_BTN_A : JC_BTN_B;
  417. uint32_t fX = g_abSwap ? JC_BTN_Y : JC_BTN_X,
  418. fY = g_abSwap ? JC_BTN_X : JC_BTN_Y;
  419. uint32_t jc = 0;
  420. if (b & TB_Y)
  421. jc |= fY;
  422. if (b & TB_B)
  423. jc |= fB;
  424. if (b & TB_A)
  425. jc |= fA;
  426. if (b & TB_X)
  427. jc |= fX;
  428. if (b & TB_LB)
  429. jc |= JC_BTN_L;
  430. if (b & TB_RB)
  431. jc |= JC_BTN_R;
  432. if ((g_in[bond].lt >= SW_TRIG_ON) || (b & 0x8000000u))
  433. jc |= JC_BTN_ZL;
  434. if ((g_in[bond].rt >= SW_TRIG_ON) || (b & 0x800000u))
  435. jc |= JC_BTN_ZR;
  436. if (b & TB_VIEW)
  437. jc |= JC_BTN_PLUS;
  438. if (b & TB_MENU)
  439. jc |= JC_BTN_MINUS;
  440. if (b & TB_L3)
  441. jc |= JC_BTN_LSTICK;
  442. if (b & TB_R3)
  443. jc |= JC_BTN_RSTICK;
  444. if (b & TB_STEAM)
  445. jc |= JC_BTN_HOME;
  446. if (b & TB_DDN)
  447. jc |= JC_BTN_DOWN;
  448. if (b & TB_DUP)
  449. jc |= JC_BTN_UP;
  450. if (b & TB_DRT)
  451. jc |= JC_BTN_RIGHT;
  452. if (b & TB_DLF)
  453. jc |= JC_BTN_LEFT;
  454. if (b & TB_L4)
  455. jc |= codeToJc(g_back[0], fA, fB, fX, fY);
  456. if (b & TB_R4)
  457. jc |= codeToJc(g_back[1], fA, fB, fX, fY);
  458. if (b & TB_L5)
  459. jc |= codeToJc(g_back[2], fA, fB, fX, fY);
  460. if (b & TB_R5)
  461. jc |= codeToJc(g_back[3], fA, fB, fX, fY);
  462. if (qam)
  463. jc |= codeToJc(g_qamMap, fA, fB, fX, fY);
  464. out[0] = g_jcTimer[slot]++;
  465. // bat_con byte: [7:5]=capacity, bit4=charging, bit0=host_powered (see jcBatteryNibble). The controllers are
  466. // wireless (battery powered) from the console's view, so host_powered=0 -- setting it pins the pad the console
  467. // treats as the wired/primary device in the charging state (the first-enumerated pad showing "charging, 100%").
  468. // The charging flag reflects the controller's REAL EChargeState (2=charging); wireless pads report discharging
  469. // (1) so it stays clear, but a pad genuinely on a charger will show the bolt correctly.
  470. uint8_t chg = (bond < NSLOT && g_batteryState[bond] == 2) ? 0x10 : 0x00;
  471. out[1] = (uint8_t)((jcBatteryNibble(bond) << 4) | chg);
  472. out[2] = (uint8_t)(jc);
  473. out[3] = (uint8_t)(jc >> 8);
  474. out[4] = (uint8_t)(jc >> 16);
  475. int16_t lx, ly, rx, ry;
  476. slotSticks(bond, &lx, &ly, &rx, &ry);
  477. jcPackStick(out + 5, lx, ly);
  478. jcPackStick(out + 8, rx, ry);
  479. // rumble_input_report echo: genuine pad emits 0x09..0x0C; some Switch firmware expects this nonzero.
  480. out[11] = 0x09;
  481. }
  482. static void switchProBuild(uint8_t slot, uint8_t out[63])
  483. {
  484. uint8_t bond =
  485. jcBondOf(slot); // IMU data comes from the mapped controller
  486. memset(out, 0, 63);
  487. jcInputPrefix(slot, out);
  488. // Gyro slot order follows hid-nintendo: raw+6 = ROLL, raw+8 = PITCH, raw+10 = YAW. Source routing (a proper
  489. // rotation, det +1): SwitchX<-+gy, SwitchY<--gx, SwitchZ<-+gz.
  490. //
  491. // The accel MUST use the SAME signed permutation as the gyro. Steam reads the gyro raw and ignores this, but the
  492. // Switch console FUSES accel (gravity) with gyro to anchor absolute orientation -- if the accel frame has the
  493. // opposite handedness, the gravity-correction term points the wrong way and the attitude estimate latches into a
  494. // rotated solution (intermittent ~45deg axis-mixing, per-unit roll bias, cleared only by a replug). So accel Y
  495. // must be -ax to match the gyro's -gx pitch axis; resting gravity then lands cleanly on Switch Z (the up axis).
  496. // The SC2 accelerometer is +/-2g full-scale (~16384 counts = 1g). The factory cal we present (sensitivity 0x4000)
  497. // tells the Switch it's a genuine +/-8g Pro Controller (4096 counts = 1g), so divide by SW_ACCEL_DIV=4 to report
  498. // ~1g. Without this the console reads gravity as ~4g, REJECTS the accel for drift correction (it must be linear
  499. // accel, not gravity), and gyro roll error accumulates into a slow ~45deg lean. Gyro is left at native scale.
  500. // accel X <- +ay, accel Y <- -ax, accel Z <- +az (signs match gyro)
  501. int16_t aX = (int16_t)(g_in[bond].ay / SW_ACCEL_DIV);
  502. int16_t aY = (int16_t)((-(int16_t)g_in[bond].ax) / SW_ACCEL_DIV);
  503. int16_t aZ = (int16_t)(g_in[bond].az / SW_ACCEL_DIV);
  504. int16_t groll = (int16_t)g_in[bond].gy;
  505. int16_t gpitch = (int16_t)(-(int16_t)g_in[bond].gx);
  506. int16_t gyaw = (int16_t)g_in[bond].gz;
  507. // Sensitivity trim (PR #189), skipped in legacy mode (g_swGyroLegacy, panel-selectable):
  508. // - the physical yaw of the SC made the roll (or yaw, if facing a wall like on the Switch) move too fast in
  509. // Switch mode -> 80%. int32 for the multiply so it cannot overflow.
  510. // - pitch/yaw were also a little off -> 90% matches a genuine Switch Pro Controller and Steam mode.
  511. if (!g_swGyroLegacy) {
  512. groll = (int16_t)(((int32_t)groll * 4) / 5);
  513. gpitch = (int16_t)(((int32_t)gpitch * 9) / 10);
  514. gyaw = (int16_t)(((int32_t)gyaw * 9) / 10);
  515. }
  516. for (int k = 0; k < 3; k++) {
  517. int o = 12 + k * 12;
  518. out[o + 0] = aX & 0xFF;
  519. out[o + 1] = (aX >> 8) & 0xFF;
  520. out[o + 2] = aY & 0xFF;
  521. out[o + 3] = (aY >> 8) & 0xFF;
  522. out[o + 4] = aZ & 0xFF;
  523. out[o + 5] = (aZ >> 8) & 0xFF;
  524. out[o + 6] = groll & 0xFF;
  525. out[o + 7] = (groll >> 8) & 0xFF;
  526. out[o + 8] = gpitch & 0xFF;
  527. out[o + 9] = (gpitch >> 8) & 0xFF;
  528. out[o + 10] = gyaw & 0xFF;
  529. out[o + 11] = (gyaw >> 8) & 0xFF;
  530. }
  531. }
  532. // --- Canonical factory SPI dumps the host reads for calibration. Neutral IMU + centered sticks so a fresh
  533. // "device" calibrates sane; user-cal regions (0x80xx) read 0xFF so the host falls back to these factory blocks.
  534. static const uint8_t SPI_IMU_CAL[24] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  535. 0x00, 0x40, 0x00, 0x40, 0x00, 0x40,
  536. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  537. 0x3B, 0x34, 0x3B, 0x34, 0x3B, 0x34 };
  538. static const uint8_t SPI_PARAMS1[24] = { 0x50, 0xFD, 0x00, 0x00, 0xC6, 0x0F,
  539. 0x0F, 0x30, 0x61, 0x96, 0x30, 0xF3,
  540. 0xD4, 0x14, 0x54, 0x41, 0x15, 0x54,
  541. 0xC7, 0x79, 0x9C, 0x33, 0x36, 0x63 };
  542. static const uint8_t SPI_PARAMS2[18] = { 0x0F, 0x30, 0x61, 0x96, 0x30, 0xF3,
  543. 0xD4, 0x14, 0x54, 0x41, 0x15, 0x54,
  544. 0xC7, 0x79, 0x9C, 0x33, 0x36, 0x63 };
  545. static const uint8_t SPI_COLOR[13] = { 0x32, 0x32, 0x32, 0xE6, 0xE6, 0xE6, 0x32,
  546. 0x32, 0x32, 0x32, 0x32, 0x32, 0xFF };
  547. // built at boot: left[9]+right[9] factory stick calibration (packed 12-bit). Shared across slots -- it's a
  548. // factory stub the host reads to seed its cal, and we present the same neutral stub for all controllers.
  549. static uint8_t g_spiStickCal[18];
  550. static void jcPack12(uint8_t *o9, const uint16_t v[6])
  551. { // pack 6 12-bit values into 9 bytes (Switch stick-cal format)
  552. o9[0] = v[0] & 0xFF;
  553. o9[1] = ((v[1] & 0x0F) << 4) | ((v[0] >> 8) & 0x0F);
  554. o9[2] = (v[1] >> 4) & 0xFF;
  555. o9[3] = v[2] & 0xFF;
  556. o9[4] = ((v[3] & 0x0F) << 4) | ((v[2] >> 8) & 0x0F);
  557. o9[5] = (v[3] >> 4) & 0xFF;
  558. o9[6] = v[4] & 0xFF;
  559. o9[7] = ((v[5] & 0x0F) << 4) | ((v[4] >> 8) & 0x0F);
  560. o9[8] = (v[5] >> 4) & 0xFF;
  561. }
  562. static void jcBuildStickCal()
  563. {
  564. const uint16_t C = 2048, R = 1800; // center, +/- range per axis
  565. uint16_t L[6] = {
  566. R, R, C, C, R, R
  567. }; // left order: max(x,y), center(x,y), min(x,y)
  568. uint16_t Rr[6] = {
  569. C, C, R, R, R, R
  570. }; // right order: center(x,y), min(x,y), max(x,y)
  571. jcPack12(&g_spiStickCal[0], L);
  572. jcPack12(&g_spiStickCal[9], Rr);
  573. }
  574. // Manual-pairing (subcommand 0x01) reply payloads. A real Switch runs this 3-stage BT key exchange over USB so it
  575. // can register the pad (Steam/hid-nintendo do not). The Switch only validates the shape, not the key contents. Each
  576. // is the 31-byte reply body following the 0x21 input prefix + ack(0x81) + echoed-subcommand(0x01).
  577. static const uint8_t BT_PAIR_2[31] = { // type 2: LTK exchange
  578. 0x02, 0xE5, 0xC8, 0xE4, 0x92, 0x05, 0xFF, 0xC9, 0x8A, 0x7D, 0xEA,
  579. 0x15, 0xF6, 0x19, 0xBA, 0x82, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00,
  580. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
  581. };
  582. static const uint8_t BT_PAIR_3[31] = {
  583. 0x03
  584. }; // type 3: save pairing (all zero body)
  585. // User-calibration SPI mirror (0x8000-0x80FF). A real Pro Controller stores the gyro/accel (and stick) calibration
  586. // the Switch writes during "Calibrate Motion Controls" here, then reads it back and applies it -- that's how a
  587. // resting IMU/neutral offset gets cancelled. Per-slot: each Pro Controller has its own SPI mirror, so each
  588. // gets its own persisted file (NSLOT files; the same 0x100 bytes as before, partitioned by slot index).
  589. #define SWCAL_FILE_BASE "/swimucal"
  590. static uint8_t g_userCal[NSLOT][0x100];
  591. static bool g_userCalLoaded[NSLOT] = { false, false, false, false };
  592. static void swCalFileName(uint8_t slot, char *out)
  593. {
  594. // "/swimucal0.bin" .. "/swimucal3.bin"
  595. const char *base = SWCAL_FILE_BASE;
  596. int i = 0;
  597. while (base[i]) {
  598. out[i] = base[i];
  599. i++;
  600. }
  601. out[i++] = (char)('0' + slot);
  602. out[i++] = '.';
  603. out[i++] = 'b';
  604. out[i++] = 'i';
  605. out[i++] = 'n';
  606. out[i] = '\0';
  607. }
  608. static void loadUserCal()
  609. {
  610. for (int s = 0; s < NSLOT; s++) {
  611. memset(g_userCal[s], 0xFF, sizeof g_userCal[s]);
  612. char fn[16];
  613. swCalFileName((uint8_t)s, fn);
  614. File f(InternalFS);
  615. if (f.open(fn, FILE_O_READ)) {
  616. f.read(g_userCal[s], sizeof g_userCal[s]);
  617. f.close();
  618. }
  619. g_userCalLoaded[s] = true;
  620. }
  621. }
  622. static void saveUserCal(uint8_t slot)
  623. {
  624. char fn[16];
  625. swCalFileName(slot, fn);
  626. InternalFS.remove(fn);
  627. File f(InternalFS);
  628. if (f.open(fn, FILE_O_WRITE)) {
  629. f.write(g_userCal[slot], sizeof g_userCal[slot]);
  630. f.close();
  631. }
  632. }
  633. // jcSpiWrite runs in the USB ISR (via jcSet). NEVER do flash I/O here -- a blocking LittleFS erase/write in the
  634. // interrupt wedges USB + the RF poll and corrupts state (device drops into a bad state needing a replug). Only
  635. // update the RAM mirror and flag it dirty; task() (main loop) does the actual save, debounced so a calibration
  636. // write-burst coalesces into one flash write. Per-slot: each Pro Controller's ISR write targets its own mirror.
  637. static volatile bool g_userCalDirty[NSLOT] = { false, false, false, false };
  638. static volatile unsigned long g_userCalDirtyMs[NSLOT] = { 0 };
  639. static void jcSpiWrite(uint8_t slot, uint32_t addr, uint8_t len,
  640. const uint8_t *data, uint16_t avail)
  641. {
  642. bool changed = false;
  643. for (uint8_t i = 0; i < len && i < avail; i++) {
  644. uint32_t a = addr + i;
  645. if (a >= 0x8000 && a < 0x8100) {
  646. if (g_userCal[slot][a - 0x8000] != data[i]) {
  647. g_userCal[slot][a - 0x8000] = data[i];
  648. changed = true;
  649. }
  650. }
  651. }
  652. if (changed) {
  653. g_userCalDirty[slot] = true;
  654. g_userCalDirtyMs[slot] = millis();
  655. } // defer the flash write to task()
  656. }
  657. static void spiRead(uint8_t slot, uint32_t addr, uint8_t len, uint8_t *dst)
  658. {
  659. for (uint8_t i = 0; i < len; i++) {
  660. uint32_t a = addr + i;
  661. uint8_t v = 0xFF;
  662. if (a >= 0x6020 && a < 0x6020 + 24)
  663. v = SPI_IMU_CAL[a - 0x6020];
  664. else if (a >= 0x603D && a < 0x603D + 18)
  665. v = g_spiStickCal[a - 0x603D];
  666. else if (a >= 0x6050 && a < 0x6050 + 13)
  667. v = SPI_COLOR[a - 0x6050];
  668. else if (a >= 0x6080 && a < 0x6080 + 24)
  669. v = SPI_PARAMS1[a - 0x6080];
  670. else if (a >= 0x6098 && a < 0x6098 + 18)
  671. v = SPI_PARAMS2[a - 0x6098];
  672. else if (a >= 0x8000 && a < 0x8100)
  673. // persisted user cal (0xFF blank -> factory fallback)
  674. v = g_userCal[slot][a - 0x8000];
  675. dst[i] = v;
  676. }
  677. }
  678. // Reply FIFO: the host's handshake/subcommand reports arrive in the USB ISR (jcSet); we enqueue the canonical
  679. // 0x81/0x21 reply and drain it from task() where sendReport is safe. SPSC ring, volatile indices. Per-slot: each
  680. // Pro Controller runs its own handshake state machine, so each has its own reply queue. Every reply is sent as
  681. // a full 63-byte report (64 with the id): macOS IOHIDManager silently DROPS any input report shorter than the
  682. // descriptor-declared length (0x3F), so short 0x81/0x21 replies never reached the host.
  683. #define JC_REPLEN 63
  684. struct JcRep {
  685. uint8_t rid;
  686. uint8_t data[JC_REPLEN];
  687. };
  688. #define JCQ_N 8
  689. static volatile uint8_t g_jcQh[NSLOT] = { 0, 0, 0, 0 };
  690. static volatile uint8_t g_jcQt[NSLOT] = { 0, 0, 0, 0 };
  691. static JcRep g_jcQ[NSLOT][JCQ_N];
  692. static void jcEnq(uint8_t slot, uint8_t rid, const uint8_t *d, uint8_t len)
  693. {
  694. uint8_t t = g_jcQt[slot];
  695. uint8_t nt = (uint8_t)((t + 1) % JCQ_N);
  696. if (nt == g_jcQh[slot])
  697. return; // full -> drop; the host re-requests on timeout
  698. if (len > JC_REPLEN)
  699. len = JC_REPLEN;
  700. g_jcQ[slot][t].rid = rid;
  701. memset(g_jcQ[slot][t].data, 0, JC_REPLEN);
  702. memcpy(g_jcQ[slot][t].data, d, len); // zero-pad to full report length
  703. g_jcQt[slot] = nt;
  704. }
  705. // Build the 0x21 reply (standard input prefix + ACK + echoed subcommand id + reply data) for a report-0x01 subcommand.
  706. static void jcSubcmd(uint8_t slot, uint8_t sub, const uint8_t *args,
  707. uint16_t alen)
  708. {
  709. uint8_t p[JC_REPLEN];
  710. memset(p, 0, sizeof p);
  711. jcInputPrefix(slot, p);
  712. p[13] = sub;
  713. switch (sub) {
  714. // manual BT pairing: 3-stage key exchange a real Switch runs over USB
  715. case 0x01: {
  716. uint8_t t = (alen >= 1) ? args[0] : 3;
  717. p[12] = 0x81; // pairing ACK
  718. if (t == 1) {
  719. // echo type + this slot's MAC + "Pro Controller" name
  720. p[14] = 0x01;
  721. memcpy(&p[15], g_jcMac[slot], 6);
  722. p[21] = 0x00;
  723. p[22] = 0x25;
  724. p[23] = 0x08;
  725. // "Pro Controller" = 50 72 6F 20 43 6F 6E 74 72 6F 6C 6C 65 72
  726. static const uint8_t NAME[14] = {
  727. 0x50, 0x72, 0x6F, 0x20, 0x43, 0x6F, 0x6E,
  728. 0x74, 0x72, 0x6F, 0x6C, 0x6C, 0x65, 0x72
  729. };
  730. memcpy(&p[24], NAME, 14);
  731. p[38] = 0x00;
  732. p[39] = 0x00;
  733. p[40] = 0x00;
  734. p[41] = 0x00;
  735. p[42] = 0x00;
  736. p[43] = 0x68;
  737. } else if (t == 2) {
  738. memcpy(&p[14], BT_PAIR_2, 31);
  739. } else {
  740. memcpy(&p[14], BT_PAIR_3, 31);
  741. }
  742. break;
  743. }
  744. case 0x02: // request device info
  745. p[12] = 0x82;
  746. p[14] = 0x03;
  747. // firmware version 3.72 (genuine Pro Controller value)
  748. p[15] = 0x48;
  749. p[16] = 0x03; // controller type: Pro Controller
  750. p[17] = 0x02; // fixed
  751. memcpy(&p[18], g_jcMac[slot], 6);
  752. p[24] = 0x01; // colors stored in SPI (0x6050)
  753. p[25] = 0x01; // fixed
  754. break;
  755. case 0x10: { // SPI flash read -> echo [addr][len] then the data
  756. if (alen < 5) {
  757. p[12] = 0x80;
  758. break;
  759. }
  760. uint32_t a = (uint32_t)args[0] | ((uint32_t)args[1] << 8) |
  761. ((uint32_t)args[2] << 16) |
  762. ((uint32_t)args[3] << 24);
  763. uint8_t rl = args[4];
  764. if (rl > 0x1D)
  765. rl = 0x1D;
  766. p[12] = 0x90;
  767. p[14] = args[0];
  768. p[15] = args[1];
  769. p[16] = args[2];
  770. p[17] = args[3];
  771. p[18] = rl;
  772. spiRead(slot, a, rl, &p[19]);
  773. break;
  774. }
  775. // SPI flash write -> persist user calibration so Switch motion-cal sticks
  776. case 0x11:
  777. if (alen >= 5) {
  778. uint32_t a = (uint32_t)args[0] |
  779. ((uint32_t)args[1] << 8) |
  780. ((uint32_t)args[2] << 16) |
  781. ((uint32_t)args[3] << 24);
  782. jcSpiWrite(slot, a, args[4], &args[5],
  783. (alen > 5) ? (uint16_t)(alen - 5) : 0);
  784. }
  785. p[12] = 0x80; // write ACK
  786. break;
  787. // set input report mode (0x30 = standard full) -> begin streaming
  788. case 0x03:
  789. if (alen >= 1 && args[0] == 0x30)
  790. g_swProReportMode[slot] = 0x30;
  791. p[12] = 0x80;
  792. break;
  793. // trigger buttons elapsed time -> canned reply (genuine pad returns data)
  794. case 0x04:
  795. p[12] = 0x83;
  796. p[14] = 0x00;
  797. p[15] = 0xCC;
  798. p[16] = 0x00;
  799. p[17] = 0xEE;
  800. p[18] = 0x00;
  801. p[19] = 0xFF;
  802. break;
  803. case 0x21:
  804. p[12] = 0xA0;
  805. break; // set NFC/IR config
  806. default:
  807. p[12] = 0x80;
  808. // 0x06/0x08/0x30/0x38/0x40/0x41/0x48/... generic positive ACK
  809. break;
  810. }
  811. jcEnq(slot, 0x21, p, JC_REPLEN);
  812. }
  813. // USB-ISR callback for the Pro Controller's OUT endpoint. Per-slot dispatch via per-instance callback
  814. // (jcSet##N closes over the slot index). Report 0x80 = USB handshake; 0x01 = subcommand.
  815. static void jcSetCommon(uint8_t slot, uint8_t rid, hid_report_type_t type,
  816. uint8_t const *b, uint16_t n)
  817. {
  818. if (type != HID_REPORT_TYPE_OUTPUT || n < 1)
  819. return;
  820. uint8_t id;
  821. const uint8_t *p;
  822. uint16_t pn;
  823. if (rid == 0) {
  824. id = b[0];
  825. p = b + 1;
  826. pn = (uint16_t)(n - 1);
  827. } // EP-OUT: id is the first payload byte
  828. else {
  829. id = rid;
  830. p = b;
  831. pn = n;
  832. } // control SET_REPORT: id already split out
  833. if (id == 0x80) {
  834. if (pn < 1)
  835. return;
  836. if (p[0] == 0x01) {
  837. uint8_t d[9] = { 0x01,
  838. 0x00,
  839. 0x03,
  840. g_jcMac[slot][0],
  841. g_jcMac[slot][1],
  842. g_jcMac[slot][2],
  843. g_jcMac[slot][3],
  844. g_jcMac[slot][4],
  845. g_jcMac[slot][5] };
  846. jcEnq(slot, 0x81, d, 9);
  847. } // device type + MAC
  848. else if (p[0] == 0x02) {
  849. uint8_t d[1] = { 0x02 };
  850. jcEnq(slot, 0x81, d, 1);
  851. } // handshake
  852. else if (p[0] == 0x03) {
  853. uint8_t d[1] = { 0x03 };
  854. jcEnq(slot, 0x81, d, 1);
  855. } // set baudrate
  856. // 0x04 force-USB / 0x05 enable-timeout / 0x06 reset: no reply expected
  857. return;
  858. }
  859. if (id == 0x01) { // [timer][rumble x8][subcmd][args...]
  860. if (pn < 10)
  861. return;
  862. jcRumble(slot, p, pn);
  863. jcSubcmd(slot, p[9], p + 10,
  864. (pn > 10) ? (uint16_t)(pn - 10) : 0);
  865. return;
  866. }
  867. if (id == 0x10) {
  868. jcRumble(slot, p, pn);
  869. return;
  870. }
  871. // report 0x82: ignored
  872. }
  873. #define JCCB(N) \
  874. static void jcSet##N(uint8_t r, hid_report_type_t t, uint8_t const *b, \
  875. uint16_t n) \
  876. { \
  877. jcSetCommon(N, r, t, b, n); \
  878. }
  879. // clang-format off
  880. JCCB(0)
  881. JCCB(1)
  882. JCCB(2)
  883. JCCB(3)
  884. // clang-format on
  885. typedef void (*jc_setcb_t)(uint8_t, hid_report_type_t, uint8_t const *,
  886. uint16_t);
  887. static jc_setcb_t const JC_SETCB[NSLOT] = { jcSet0, jcSet1, jcSet2, jcSet3 };
  888. // Dynamic-mount mode: begin() is unused (setup() calls beginPool()+usbReenumerate instead).
  889. void SwitchProController::begin()
  890. {
  891. }
  892. // Wake mouse (1 HID) is present in Switch mode, leaving CFG_TUD_HID-1 for the Pro Controller pool.
  893. uint8_t SwitchProController::maxSlots() const
  894. {
  895. uint8_t cap = (uint8_t)(CFG_TUD_HID - 1);
  896. return cap < NSLOT ? cap : (uint8_t)NSLOT;
  897. }
  898. void SwitchProController::usbIdentity()
  899. {
  900. USBDevice.setID(0x057E, 0x2009);
  901. USBDevice.setVersion(0x0200);
  902. USBDevice.setDeviceVersion(0x0220);
  903. USBDevice.setManufacturerDescriptor("Nintendo Co., Ltd.");
  904. USBDevice.setProductDescriptor("Pro Controller");
  905. }
  906. void SwitchProController::beginPool()
  907. {
  908. jcBuildStickCal();
  909. hdrBuildLevels();
  910. for (uint8_t s = 0; s < NSLOT; s++)
  911. hdrReset(s);
  912. loadUserCal();
  913. swProLoadCfg();
  914. initJcMacs();
  915. uint8_t pool = maxSlots();
  916. for (uint8_t s = 0; s < pool; s++) {
  917. g_swPro[s].enableOutEndpoint(true);
  918. g_swPro[s].setReportCallback(NULL, JC_SETCB[s]);
  919. g_swPro[s].setReportDescriptor(SWPRO_HID_DESC,
  920. sizeof SWPRO_HID_DESC);
  921. g_swPro[s].setPollInterval(1);
  922. g_swPro[s].begin();
  923. }
  924. }
  925. void SwitchProController::mountSlots(uint8_t k)
  926. {
  927. for (uint8_t u = 0; u < k; u++) {
  928. // Each re-enumeration restarts the host handshake -- clear this USB slot's gate + reply FIFO so we
  929. // don't stream 0x30 before the (new) host has re-selected report mode.
  930. g_swProReportMode[u] = 0;
  931. g_jcQh[u] = g_jcQt[u] = 0;
  932. // reset the rumble decoder + relay dedup so a stale amplitude from a
  933. // prior session can't carry across the reconnect
  934. hdrReset(u);
  935. g_jcLastLo[u] = g_jcLastHi[u] = 0;
  936. USBDevice.addInterface(g_swPro[u]);
  937. }
  938. }
  939. void SwitchProController::task()
  940. {
  941. for (uint8_t s = 0; s < g_usbMountCount; s++) {
  942. if (!g_swPro[s].ready())
  943. continue;
  944. // Deferred user-cal flash write (queued by the USB ISR; debounced so a calibration write-burst
  945. // is one save). Per-slot: each Pro Controller saves its own mirror.
  946. if (g_userCalDirty[s] &&
  947. (unsigned long)(millis() - g_userCalDirtyMs[s]) > 250u) {
  948. g_userCalDirty[s] = false;
  949. saveUserCal((uint8_t)s);
  950. }
  951. // drain handshake/subcommand replies first (ordered) -- one report per slot per call so a
  952. // bursty host-init doesn't starve the streamed 0x30
  953. if (g_jcQh[s] != g_jcQt[s]) {
  954. JcRep *r = &g_jcQ[s][g_jcQh[s]];
  955. usbTxHid(&g_swPro[s], r->rid, r->data, JC_REPLEN);
  956. g_jcQh[s] = (uint8_t)((g_jcQh[s] + 1) % JCQ_N);
  957. // one report per slot per call; the rest next loop
  958. continue;
  959. }
  960. // not until the host has finished init + selected 0x30
  961. if (g_swProReportMode[s] != 0x30)
  962. continue;
  963. if (millis() - g_swProLastMs[s] < USB_STREAM_MS)
  964. continue;
  965. g_swProLastMs[s] = millis();
  966. uint8_t p[63];
  967. switchProBuild((uint8_t)s, p);
  968. usbTxHid(&g_swPro[s], 0x30, p, sizeof p);
  969. }
  970. }