mode_dinput.cpp 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. #include "mode_dinput.h"
  2. #include "triton.h"
  3. #include "gamepad_util.h"
  4. #include "config.h"
  5. #include "usb_mount.h"
  6. #include "usb_tx.h"
  7. #include <Adafruit_TinyUSB.h>
  8. #include <Arduino.h>
  9. #include <string.h>
  10. DInputController g_dinputCtl;
  11. // Two top-level Application collections, one HID interface. DirectInput can only surface 8 axes per device
  12. // (DIJOYSTATE2: X/Y/Z/Rx/Ry/Rz + 2 sliders), and the SC2 has 13 analog inputs -- so the exotic ones live on a
  13. // second collection, which Windows exposes as a second joystick (one HIDClass PDO per top-level collection):
  14. //
  15. // report 1 "stick" device: X/Y = left stick, Rx/Ry = right stick, Z/Rz = LT/RT, hat = D-pad, buttons 1-26
  16. // report 2 "motion" device: X/Y = left pad, Rx/Ry = right pad, Z/Rz/Slider = gyro X/Y/Z, buttons 1-4 = pad
  17. // click/touch
  18. //
  19. // Both reports are 15 payload bytes. Nothing here needs an OUT endpoint: DirectInput force feedback is the PID
  20. // class (not the vendor rumble reports our other modes decode), so this mode is input-only.
  21. //
  22. // If a host ever refuses to split the collections, device #1 still works and the fallback is to mount the two
  23. // collections as two separate HID interfaces per slot (halves the controller count: 2 interfaces per slot
  24. // against the CFG_TUD_HID budget).
  25. static const uint8_t DI_HID_DESC[] = {
  26. // clang-format off
  27. 0x05, 0x01, // Usage Page (Generic Desktop)
  28. 0x09, 0x04, // Usage (Joystick)
  29. 0xA1, 0x01, // Collection (Application)
  30. 0x85, 0x01, // Report ID (1)
  31. 0x09, 0x30, // Usage (X) left stick X
  32. 0x09, 0x31, // Usage (Y) left stick Y
  33. 0x09, 0x33, // Usage (Rx) right stick X
  34. 0x09, 0x34, // Usage (Ry) right stick Y
  35. 0x16, 0x00, 0x80, // Logical Minimum (-32768)
  36. 0x26, 0xFF, 0x7F, // Logical Maximum (32767)
  37. 0x75, 0x10, // Report Size (16)
  38. 0x95, 0x04, // Report Count (4)
  39. 0x81, 0x02, // Input (Data,Var,Abs)
  40. 0x09, 0x32, // Usage (Z) left trigger
  41. 0x09, 0x35, // Usage (Rz) right trigger
  42. 0x15, 0x00, // Logical Minimum (0)
  43. 0x26, 0xFF, 0x00, // Logical Maximum (255)
  44. 0x75, 0x08, // Report Size (8)
  45. 0x95, 0x02, // Report Count (2)
  46. 0x81, 0x02, // Input (Data,Var,Abs)
  47. 0x09, 0x39, // Usage (Hat switch)
  48. 0x15, 0x00, // Logical Minimum (0)
  49. 0x25, 0x07, // Logical Maximum (7)
  50. 0x35, 0x00, // Physical Minimum (0)
  51. 0x46, 0x3B, 0x01, // Physical Maximum (315)
  52. 0x65, 0x14, // Unit (English rotation, degrees)
  53. 0x75, 0x04, // Report Size (4)
  54. 0x95, 0x01, // Report Count (1)
  55. // Null State: a 4-bit value outside 0..7 (we send 8) reads as "hat centred"
  56. 0x81, 0x42, // Input (Data,Var,Abs,Null State)
  57. 0x65, 0x00, // Unit (None)
  58. 0x75, 0x04, // Report Size (4)
  59. 0x95, 0x01, // Report Count (1)
  60. 0x81, 0x03, // Input (Const) -- pad the hat byte
  61. 0x05, 0x09, // Usage Page (Button)
  62. 0x19, 0x01, // Usage Minimum (Button 1)
  63. 0x29, 0x1A, // Usage Maximum (Button 26)
  64. 0x15, 0x00, // Logical Minimum (0)
  65. 0x25, 0x01, // Logical Maximum (1)
  66. 0x75, 0x01, // Report Size (1)
  67. 0x95, 0x1A, // Report Count (26)
  68. 0x81, 0x02, // Input (Data,Var,Abs)
  69. 0x75, 0x01, // Report Size (1)
  70. 0x95, 0x06, // Report Count (6)
  71. 0x81, 0x03, // Input (Const) -- pad to 4 button bytes
  72. 0xC0, // End Collection
  73. 0x05, 0x01, // Usage Page (Generic Desktop)
  74. 0x09, 0x04, // Usage (Joystick)
  75. 0xA1, 0x01, // Collection (Application)
  76. 0x85, 0x02, // Report ID (2)
  77. 0x09, 0x30, // Usage (X) left pad X
  78. 0x09, 0x31, // Usage (Y) left pad Y
  79. 0x09, 0x33, // Usage (Rx) right pad X
  80. 0x09, 0x34, // Usage (Ry) right pad Y
  81. 0x09, 0x32, // Usage (Z) gyro X
  82. 0x09, 0x35, // Usage (Rz) gyro Y
  83. 0x09, 0x36, // Usage (Slider) gyro Z
  84. 0x16, 0x00, 0x80, // Logical Minimum (-32768)
  85. 0x26, 0xFF, 0x7F, // Logical Maximum (32767)
  86. 0x75, 0x10, // Report Size (16)
  87. 0x95, 0x07, // Report Count (7)
  88. 0x81, 0x02, // Input (Data,Var,Abs)
  89. 0x05, 0x09, // Usage Page (Button)
  90. 0x19, 0x01, // Usage Minimum (Button 1)
  91. 0x29, 0x04, // Usage Maximum (Button 4)
  92. 0x15, 0x00, // Logical Minimum (0)
  93. 0x25, 0x01, // Logical Maximum (1)
  94. 0x75, 0x01, // Report Size (1)
  95. 0x95, 0x04, // Report Count (4)
  96. 0x81, 0x02, // Input (Data,Var,Abs)
  97. 0x75, 0x04, // Report Size (4)
  98. 0x95, 0x01, // Report Count (1)
  99. 0x81, 0x03, // Input (Const) -- pad to a whole byte
  100. 0xC0 // End Collection
  101. // clang-format on
  102. };
  103. #define DI_RID_STICK 0x01
  104. #define DI_RID_MOTION 0x02
  105. #define DI_REPLEN 15
  106. static Adafruit_USBD_HID g_di[NSLOT];
  107. static unsigned long g_diLastMs[NSLOT] = { 0 };
  108. // Latched trackpad axes. A pad only reports a position while it is touched, so a raw pass-through would snap
  109. // the axis to centre the moment the finger lifts -- useless for the throttle/trim bindings sim users want. The
  110. // axis therefore HOLDS the last touched position; a pad CLICK re-centres it (the deliberate reset gesture).
  111. struct PadAxis {
  112. int16_t lx, ly, rx, ry;
  113. };
  114. static PadAxis g_padAxis[NSLOT];
  115. // HID axes are down-positive; the SC2 sticks/pads are up-positive. Negating INT16_MIN overflows, so clamp.
  116. static inline int16_t diInv(int16_t v)
  117. {
  118. return (int16_t)(v == INT16_MIN ? INT16_MAX : -v);
  119. }
  120. // Buttons 1..26, raw SC2 bits -- no back-paddle/QAM remap: DirectInput bindings are done in the sim, and a
  121. // paddle that duplicates a face button would just cost the user two bindable inputs.
  122. static uint32_t diButtons(uint32_t b)
  123. {
  124. uint32_t o = 0;
  125. if (b & TB_A)
  126. o |= 1u << 0;
  127. if (b & TB_B)
  128. o |= 1u << 1;
  129. if (b & TB_X)
  130. o |= 1u << 2;
  131. if (b & TB_Y)
  132. o |= 1u << 3;
  133. if (b & TB_LB)
  134. o |= 1u << 4;
  135. if (b & TB_RB)
  136. o |= 1u << 5;
  137. if (b & TB_L2)
  138. o |= 1u << 6;
  139. if (b & TB_R2)
  140. o |= 1u << 7;
  141. // button 9 = Start-side, button 10 = Select-side (TB_VIEW/TB_MENU are named backwards -- see triton.h)
  142. if (b & TB_VIEW)
  143. o |= 1u << 8;
  144. if (b & TB_MENU)
  145. o |= 1u << 9;
  146. if (b & TB_L3)
  147. o |= 1u << 10;
  148. if (b & TB_R3)
  149. o |= 1u << 11;
  150. if (b & TB_DUP)
  151. o |= 1u << 12;
  152. if (b & TB_DDN)
  153. o |= 1u << 13;
  154. if (b & TB_DLF)
  155. o |= 1u << 14;
  156. if (b & TB_DRT)
  157. o |= 1u << 15;
  158. if (b & TB_L4)
  159. o |= 1u << 16;
  160. if (b & TB_R4)
  161. o |= 1u << 17;
  162. if (b & TB_L5)
  163. o |= 1u << 18;
  164. if (b & TB_R5)
  165. o |= 1u << 19;
  166. if (b & TB_LPADC)
  167. o |= 1u << 20;
  168. if (b & TB_RPADC)
  169. o |= 1u << 21;
  170. if (b & TB_LPADT)
  171. o |= 1u << 22;
  172. if (b & TB_RPADT)
  173. o |= 1u << 23;
  174. if (b & TB_STEAM)
  175. o |= 1u << 24;
  176. if (b & TB_QAM)
  177. o |= 1u << 25;
  178. return o;
  179. }
  180. static void diBuildStick(uint8_t bond, uint8_t out[DI_REPLEN])
  181. {
  182. const PuckInput &in = g_in[bond];
  183. memset(out, 0, DI_REPLEN);
  184. le16(out + 0, in.lx);
  185. le16(out + 2, diInv(in.ly));
  186. le16(out + 4, in.rx);
  187. le16(out + 6, diInv(in.ry));
  188. out[8] = in.lt;
  189. out[9] = in.rt;
  190. out[10] = psHatNibble(in.buttons);
  191. uint32_t btn = diButtons(in.buttons);
  192. out[11] = (uint8_t)(btn & 0xFF);
  193. out[12] = (uint8_t)((btn >> 8) & 0xFF);
  194. out[13] = (uint8_t)((btn >> 16) & 0xFF);
  195. out[14] = (uint8_t)((btn >> 24) & 0xFF);
  196. }
  197. static void diBuildMotion(uint8_t bond, uint8_t out[DI_REPLEN])
  198. {
  199. const PuckInput &in = g_in[bond];
  200. PadAxis &p = g_padAxis[bond];
  201. if (in.buttons & TB_LPADC) {
  202. p.lx = 0;
  203. p.ly = 0;
  204. } else if (in.buttons & TB_LPADT) {
  205. p.lx = in.lpx;
  206. p.ly = in.lpy;
  207. }
  208. if (in.buttons & TB_RPADC) {
  209. p.rx = 0;
  210. p.ry = 0;
  211. } else if (in.buttons & TB_RPADT) {
  212. p.rx = in.rpx;
  213. p.ry = in.rpy;
  214. }
  215. memset(out, 0, DI_REPLEN);
  216. le16(out + 0, p.lx);
  217. le16(out + 2, diInv(p.ly));
  218. le16(out + 4, p.rx);
  219. le16(out + 6, diInv(p.ry));
  220. le16(out + 8, in.gx);
  221. le16(out + 10, in.gy);
  222. le16(out + 12, in.gz);
  223. out[14] = (uint8_t)(((in.buttons & TB_LPADC) ? 0x01 : 0) |
  224. ((in.buttons & TB_RPADC) ? 0x02 : 0) |
  225. ((in.buttons & TB_LPADT) ? 0x04 : 0) |
  226. ((in.buttons & TB_RPADT) ? 0x08 : 0));
  227. }
  228. // Dynamic-mount mode: begin() is unused (setup() calls beginPool()+usbReenumerate instead).
  229. void DInputController::begin()
  230. {
  231. }
  232. uint8_t DInputController::maxSlots() const
  233. {
  234. uint8_t cap = (uint8_t)(CFG_TUD_HID - 1); // wake mouse holds HID 0
  235. return cap < NSLOT ? cap : (uint8_t)NSLOT;
  236. }
  237. void DInputController::usbIdentity()
  238. {
  239. // Deliberately NOT a cloned identity: nothing about DirectInput keys off VID/PID, and a generic ID keeps
  240. // Steam Input / the console whitelists from claiming the device out from under the sim.
  241. USBDevice.setID(0x1209, 0x4F50); // pid.codes community VID, "OP"
  242. USBDevice.setDeviceVersion(0x0100);
  243. USBDevice.setManufacturerDescriptor("OpenPuck");
  244. USBDevice.setProductDescriptor("OpenPuck Joystick");
  245. }
  246. void DInputController::beginPool()
  247. {
  248. uint8_t pool = maxSlots();
  249. for (uint8_t s = 0; s < pool; s++) {
  250. g_di[s].setReportDescriptor(DI_HID_DESC, sizeof DI_HID_DESC);
  251. g_di[s].setPollInterval(1);
  252. g_di[s].begin();
  253. }
  254. }
  255. void DInputController::mountSlots(uint8_t k)
  256. {
  257. for (uint8_t u = 0; u < k; u++)
  258. USBDevice.addInterface(g_di[u]);
  259. }
  260. void DInputController::task()
  261. {
  262. for (uint8_t u = 0; u < g_usbMountCount; u++) {
  263. if (!g_di[u].ready())
  264. continue;
  265. if (millis() - g_diLastMs[u] < USB_STREAM_MS)
  266. continue;
  267. int bond = g_usbToBond[u];
  268. if (bond < 0)
  269. continue;
  270. g_diLastMs[u] = millis();
  271. uint8_t p[DI_REPLEN];
  272. diBuildStick((uint8_t)bond, p);
  273. usbTxHid(&g_di[u], DI_RID_STICK, p, sizeof p);
  274. diBuildMotion((uint8_t)bond, p);
  275. usbTxHid(&g_di[u], DI_RID_MOTION, p, sizeof p);
  276. }
  277. }