config.cpp 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. #include "config.h"
  2. #include "rf_link.h" // g_rxWin (poll RX window persisted here)
  3. #include "haptics.h" // g_hapticBlockOn, g_hapticBlockMs
  4. #include <Adafruit_LittleFS.h>
  5. #include <InternalFileSystem.h>
  6. #include <string.h>
  7. using namespace Adafruit_LittleFS_Namespace;
  8. uint8_t g_usbMode = 0;
  9. bool g_xbox = false;
  10. uint8_t g_chordBtn[3] = {
  11. MODE_LIZARD, MODE_XBOX, MODE_SW_PRO
  12. }; // back4+B/X/Y -> these modes (A always STEAM); Y defaults to Switch Pro
  13. bool g_persistMode = false;
  14. uint8_t g_bootMode = 0xFF;
  15. bool g_debugCdcThisBoot = false;
  16. // persisted one-shot arm, stored in Cfg.rsvd0 (1 = keep CDC for the next boot)
  17. static uint8_t g_debugCdc = 0;
  18. int g_mDiv = 64, g_mFric = 94;
  19. // Per-type button config. back default {5,6,7,8} = L4->LB R4->RB L5->L3 R5->R3 (0..11 buttons, 12..15 D-pad,
  20. // 16/17 PS touch/mute, 18 Switch Capture). Switch differs: QAM defaults to Capture(18), A/B swap on, and
  21. // trackpad haptics off. qamMap 0 = unmapped (hardcoded per-mode behavior). ledBright 0 = no override.
  22. TypeCfg g_type[ET_COUNT] = {
  23. /* ET_XBOX */ { { 5, 6, 7, 8 }, 0, 0, 1, 0 },
  24. /* ET_SWITCH */ { { 5, 6, 7, 8 }, 18, 1, 0, 0 },
  25. /* ET_DS4 */ { { 5, 6, 7, 8 }, 0, 0, 1, 0 },
  26. /* ET_DS5 */ { { 5, 6, 7, 8 }, 0, 0, 1, 0 },
  27. };
  28. uint8_t g_etype = ET_NONE;
  29. // Live mirrors of the active type (puck modes use the harmless defaults below).
  30. uint8_t g_abSwap = 0;
  31. uint8_t g_back[4] = { 5, 6, 7, 8 };
  32. uint8_t g_qamMap = 0;
  33. uint8_t g_padHaptics = 1;
  34. uint8_t g_ledBright = 0;
  35. void applyActiveType()
  36. {
  37. g_etype = etypeForMode(g_usbMode);
  38. if (g_etype >=
  39. ET_COUNT) { // puck mode (Steam/Lizard): no remap, haptics on
  40. g_back[0] = 5;
  41. g_back[1] = 6;
  42. g_back[2] = 7;
  43. g_back[3] = 8;
  44. g_qamMap = 0;
  45. g_abSwap = 0;
  46. g_padHaptics = 1;
  47. g_ledBright = 0;
  48. return;
  49. }
  50. const TypeCfg &t = g_type[g_etype];
  51. for (int i = 0; i < 4; i++)
  52. g_back[i] = t.back[i];
  53. g_qamMap = t.qamMap;
  54. g_abSwap = t.abSwap;
  55. g_padHaptics = t.padHaptics;
  56. g_ledBright = t.ledBright;
  57. }
  58. // rumble strength % (200 = double); adjustable from the WebUSB panel
  59. uint8_t g_rumbleScale = 200;
  60. // poll rate is fixed. Faster than the controller can refresh wastes airtime; slower adds latency. Any rate
  61. // persisted by an older build is ignored and overwritten with the default on boot (see loadCfg).
  62. const uint32_t g_pollUs = POLL_US_DEFAULT;
  63. #define CFG_FILE "/cfg.bin"
  64. // Struct layout/semantics changed (RF tunables); bump so old flash format is discarded -> clean defaults once.
  65. #define CFG_MAGIC 0xCD
  66. struct Cfg {
  67. uint8_t magic, mode, mDiv, mFric, rsvd0, pollU100, persistMode,
  68. bootMode, chordBtn[3], rumbleScale;
  69. // Connection tunables: the connected-poll RX window in 10us units (g_rxWin/10), the post-connect haptic
  70. // block enable, and its duration in seconds. 0 in rxWin10 => use the boot default.
  71. uint8_t rxWin10, hapticBlockOn, hapticBlockS;
  72. TypeCfg type[ET_COUNT]; // per-emulated-type back/qam/abSwap/padHaptics
  73. }; // rsvd0 = ex-padSmooth, now the one-shot debug-CDC arm
  74. void saveCfg()
  75. {
  76. Cfg c = { CFG_MAGIC,
  77. g_usbMode,
  78. (uint8_t)g_mDiv,
  79. (uint8_t)g_mFric,
  80. g_debugCdc,
  81. (uint8_t)(g_pollUs / 100),
  82. (uint8_t)(g_persistMode ? 1 : 0),
  83. g_bootMode,
  84. { g_chordBtn[0], g_chordBtn[1], g_chordBtn[2] },
  85. g_rumbleScale,
  86. (uint8_t)(g_rxWin / 10),
  87. g_hapticBlockOn,
  88. (uint8_t)(g_hapticBlockMs / 1000),
  89. {} };
  90. for (int i = 0; i < ET_COUNT; i++)
  91. c.type[i] = g_type[i];
  92. InternalFS.remove(CFG_FILE);
  93. File f(InternalFS);
  94. if (f.open(CFG_FILE, FILE_O_WRITE)) {
  95. f.write((uint8_t *)&c, sizeof c);
  96. f.close();
  97. }
  98. }
  99. void loadCfg()
  100. {
  101. Cfg c;
  102. File f(InternalFS);
  103. bool consume = false;
  104. if (f.open(CFG_FILE, FILE_O_READ)) {
  105. if (f.read((uint8_t *)&c, sizeof c) == (int)sizeof c &&
  106. c.magic == CFG_MAGIC) {
  107. g_mDiv = c.mDiv ? c.mDiv : 64;
  108. g_mFric = c.mFric;
  109. for (int i = 0; i < ET_COUNT; i++)
  110. g_type[i] = c.type[i];
  111. g_persistMode = c.persistMode ? true : false;
  112. // one-shot debug-CDC (Cfg.rsvd0): honor for THIS boot, then consume so the next boot reverts to normal.
  113. g_debugCdcThisBoot = c.rsvd0 ? true : false;
  114. if (c.rsvd0) {
  115. g_debugCdc = 0;
  116. consume = true;
  117. }
  118. // poll rate is fixed; rewrite cfg so the persisted byte matches the new default.
  119. if (c.pollU100 != (uint8_t)(POLL_US_DEFAULT / 100))
  120. consume = true;
  121. // boot-mode policy: a one-shot bootMode (explicit switch when !persist) wins once then clears;
  122. // otherwise persist->last mode, else->Steam.
  123. if (c.bootMode != 0xFF) {
  124. g_usbMode = modeValid(c.bootMode) ? c.bootMode :
  125. 0;
  126. consume = true;
  127. } else
  128. g_usbMode = g_persistMode ? (modeValid(c.mode) ?
  129. c.mode :
  130. 0) :
  131. 0;
  132. static const uint8_t CHORD_DEF[3] = { MODE_LIZARD,
  133. MODE_XBOX,
  134. MODE_SW_PRO };
  135. for (int i = 0; i < 3; i++)
  136. g_chordBtn[i] = modeValid(c.chordBtn[i]) ?
  137. c.chordBtn[i] :
  138. CHORD_DEF[i];
  139. // 0 is a valid setting (rumble off)
  140. g_rumbleScale = c.rumbleScale;
  141. // Connection tunables: the poll RX window is now FIXED (g_rxWin is const) -- any persisted
  142. // rxWin10 is ignored. Restore the post-connect haptic block (enable + duration in seconds).
  143. g_hapticBlockOn = c.hapticBlockOn ? 1 : 0;
  144. // clamp seconds so s*1000 fits the uint16 ms field (max 60s)
  145. g_hapticBlockMs = (uint16_t)((c.hapticBlockS > 60 ?
  146. 60 :
  147. c.hapticBlockS) *
  148. 1000u);
  149. }
  150. f.close();
  151. }
  152. // resolve the active emulated type's settings into the live mirrors the mode builders read
  153. applyActiveType();
  154. // clear the one-shot so the NEXT cold boot reverts to the default/persist policy
  155. if (consume) {
  156. g_bootMode = 0xFF;
  157. saveCfg();
  158. }
  159. }
  160. void saveMode(uint8_t m)
  161. {
  162. if (g_persistMode) {
  163. g_usbMode = m;
  164. g_bootMode = 0xFF;
  165. } else {
  166. g_bootMode = m;
  167. }
  168. saveCfg();
  169. }
  170. void armDebugCdcNextBoot()
  171. {
  172. g_debugCdc = 1;
  173. saveCfg();
  174. } // next boot keeps CDC; loadCfg() consumes it after
  175. // FULL factory wipe: reformat the internal LittleFS, erasing cfg.bin (modes/tunables/chords) AND bonds.bin
  176. // (paired-controller record). Caller reboots: next boot finds no files and falls back to clean defaults, and
  177. // the controller must be re-paired. Irreversible -- gated behind explicit confirmation at every call site.
  178. void factoryErase()
  179. {
  180. // ensure mounted before we reformat (no-op if already up)
  181. InternalFS.begin();
  182. InternalFS.format();
  183. }
  184. // One-time factory reset for the -DOPK_FACTORY_RESET recovery build: clear a bad config/bond ONCE (first boot
  185. // after flashing) then persist normally. "Already reset" is tracked by a tag file holding the build's git hash,
  186. // written AFTER the wipe (so it survives in the freshly-formatted FS):
  187. // - tag missing or != this build's hash -> wipe, then stamp the tag. Next boot persists.
  188. // - tag == this build's hash -> already reset for this build: skip, boot normally.
  189. // Keying the tag to the git hash means flashing a DIFFERENT build re-triggers the wipe. buildTag is OPK_GIT_HASH.
  190. #define RESET_TAG_FILE "/rsttag"
  191. void factoryResetOnce(const char *buildTag)
  192. {
  193. char tag[24] = { 0 };
  194. {
  195. File f(InternalFS);
  196. if (f.open(RESET_TAG_FILE, FILE_O_READ)) {
  197. int n = f.read((uint8_t *)tag, sizeof tag - 1);
  198. if (n > 0)
  199. tag[n] = 0;
  200. f.close();
  201. }
  202. }
  203. if (strncmp(tag, buildTag, sizeof tag - 1) == 0)
  204. return; // this build already did its one-time reset -> persist
  205. factoryErase(); // wipe cfg.bin + bonds.bin + the old tag
  206. InternalFS.begin(); // remount the fresh FS
  207. File g(InternalFS); // stamp the tag so subsequent boots skip the wipe
  208. if (g.open(RESET_TAG_FILE, FILE_O_WRITE)) {
  209. g.write((const uint8_t *)buildTag, strlen(buildTag));
  210. g.close();
  211. }
  212. }