config.cpp 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. #include "config.h"
  2. #include <Adafruit_LittleFS.h>
  3. #include <InternalFileSystem.h>
  4. #include <string.h>
  5. using namespace Adafruit_LittleFS_Namespace;
  6. uint8_t g_usbMode = 0;
  7. bool g_xbox = false;
  8. uint8_t g_chordBtn[3] = {
  9. MODE_LIZARD, MODE_XBOX, MODE_SW_PRO
  10. }; // back4+B/X/Y -> these modes (A always STEAM); Y defaults to Switch Pro
  11. bool g_persistMode = false;
  12. uint8_t g_bootMode = 0xFF;
  13. bool g_debugCdcThisBoot = false;
  14. // persisted one-shot arm, stored in Cfg.rsvd0 (1 = keep CDC for the next boot)
  15. static uint8_t g_debugCdc = 0;
  16. int g_mDiv = 64, g_mFric = 94;
  17. uint8_t g_abSwap = 0;
  18. uint8_t g_back[4] = {
  19. 5, 6, 7, 8
  20. }; // L4->LB R4->RB L5->L3 R5->R3 (0..11 buttons, 12..15 D-pad U/D/L/R)
  21. // 0 = default (unmapped, uses hardcoded behavior per mode)
  22. uint8_t g_qamMap = 0;
  23. // rumble strength % (200 = double); adjustable from the WebUSB panel
  24. uint8_t g_rumbleScale = 200;
  25. // poll rate is fixed. Faster than the controller can refresh wastes airtime; slower adds latency. Any rate
  26. // persisted by an older build is ignored and overwritten with the default on boot (see loadCfg).
  27. const uint32_t g_pollUs = POLL_US_DEFAULT;
  28. #define CFG_FILE "/cfg.bin"
  29. // bumped (rumbleScale): old cfg ignored -> clean defaults on first boot
  30. #define CFG_MAGIC 0xC9
  31. struct Cfg {
  32. uint8_t magic, mode, mDiv, mFric, rsvd0, abSwap, back[4], pollU100,
  33. persistMode, bootMode, chordBtn[3], qamMap, rumbleScale;
  34. }; // rsvd0 = ex-padSmooth, now the one-shot debug-CDC arm
  35. void saveCfg()
  36. {
  37. Cfg c = { CFG_MAGIC,
  38. g_usbMode,
  39. (uint8_t)g_mDiv,
  40. (uint8_t)g_mFric,
  41. g_debugCdc,
  42. g_abSwap,
  43. { g_back[0], g_back[1], g_back[2], g_back[3] },
  44. (uint8_t)(g_pollUs / 100),
  45. (uint8_t)(g_persistMode ? 1 : 0),
  46. g_bootMode,
  47. { g_chordBtn[0], g_chordBtn[1], g_chordBtn[2] },
  48. g_qamMap,
  49. g_rumbleScale };
  50. InternalFS.remove(CFG_FILE);
  51. File f(InternalFS);
  52. if (f.open(CFG_FILE, FILE_O_WRITE)) {
  53. f.write((uint8_t *)&c, sizeof c);
  54. f.close();
  55. }
  56. }
  57. void loadCfg()
  58. {
  59. Cfg c;
  60. File f(InternalFS);
  61. bool consume = false;
  62. if (f.open(CFG_FILE, FILE_O_READ)) {
  63. if (f.read((uint8_t *)&c, sizeof c) == (int)sizeof c &&
  64. c.magic == CFG_MAGIC) {
  65. g_mDiv = c.mDiv ? c.mDiv : 64;
  66. g_mFric = c.mFric;
  67. g_abSwap = c.abSwap;
  68. for (int i = 0; i < 4; i++)
  69. g_back[i] = c.back[i];
  70. g_persistMode = c.persistMode ? true : false;
  71. // one-shot debug-CDC (Cfg.rsvd0): honor for THIS boot, then consume so the next boot reverts to normal.
  72. g_debugCdcThisBoot = c.rsvd0 ? true : false;
  73. if (c.rsvd0) {
  74. g_debugCdc = 0;
  75. consume = true;
  76. }
  77. // poll rate is fixed; rewrite cfg so the persisted byte matches the new default.
  78. if (c.pollU100 != (uint8_t)(POLL_US_DEFAULT / 100))
  79. consume = true;
  80. // boot-mode policy: a one-shot bootMode (explicit switch when !persist) wins once then clears;
  81. // otherwise persist->last mode, else->Steam.
  82. if (c.bootMode != 0xFF) {
  83. g_usbMode = modeValid(c.bootMode) ? c.bootMode :
  84. 0;
  85. consume = true;
  86. } else
  87. g_usbMode = g_persistMode ? (modeValid(c.mode) ?
  88. c.mode :
  89. 0) :
  90. 0;
  91. static const uint8_t CHORD_DEF[3] = { MODE_LIZARD,
  92. MODE_XBOX,
  93. MODE_SW_PRO };
  94. for (int i = 0; i < 3; i++)
  95. g_chordBtn[i] = modeValid(c.chordBtn[i]) ?
  96. c.chordBtn[i] :
  97. CHORD_DEF[i];
  98. g_qamMap = c.qamMap;
  99. // 0 is a valid setting (rumble off)
  100. g_rumbleScale = c.rumbleScale;
  101. }
  102. f.close();
  103. }
  104. // clear the one-shot so the NEXT cold boot reverts to the default/persist policy
  105. if (consume) {
  106. g_bootMode = 0xFF;
  107. saveCfg();
  108. }
  109. }
  110. void saveMode(uint8_t m)
  111. {
  112. if (g_persistMode) {
  113. g_usbMode = m;
  114. g_bootMode = 0xFF;
  115. } else {
  116. g_bootMode = m;
  117. }
  118. saveCfg();
  119. }
  120. void armDebugCdcNextBoot()
  121. {
  122. g_debugCdc = 1;
  123. saveCfg();
  124. } // next boot keeps CDC; loadCfg() consumes it after
  125. // FULL factory wipe: reformat the internal LittleFS, erasing cfg.bin (modes/tunables/chords) AND bonds.bin
  126. // (paired-controller record). Caller reboots: next boot finds no files and falls back to clean defaults, and
  127. // the controller must be re-paired. Irreversible -- gated behind explicit confirmation at every call site.
  128. void factoryErase()
  129. {
  130. // ensure mounted before we reformat (no-op if already up)
  131. InternalFS.begin();
  132. InternalFS.format();
  133. }
  134. // One-time factory reset for the -DOPK_FACTORY_RESET recovery build: clear a bad config/bond ONCE (first boot
  135. // after flashing) then persist normally. "Already reset" is tracked by a tag file holding the build's git hash,
  136. // written AFTER the wipe (so it survives in the freshly-formatted FS):
  137. // - tag missing or != this build's hash -> wipe, then stamp the tag. Next boot persists.
  138. // - tag == this build's hash -> already reset for this build: skip, boot normally.
  139. // Keying the tag to the git hash means flashing a DIFFERENT build re-triggers the wipe. buildTag is OPK_GIT_HASH.
  140. #define RESET_TAG_FILE "/rsttag"
  141. void factoryResetOnce(const char *buildTag)
  142. {
  143. char tag[24] = { 0 };
  144. {
  145. File f(InternalFS);
  146. if (f.open(RESET_TAG_FILE, FILE_O_READ)) {
  147. int n = f.read((uint8_t *)tag, sizeof tag - 1);
  148. if (n > 0)
  149. tag[n] = 0;
  150. f.close();
  151. }
  152. }
  153. if (strncmp(tag, buildTag, sizeof tag - 1) == 0)
  154. return; // this build already did its one-time reset -> persist
  155. factoryErase(); // wipe cfg.bin + bonds.bin + the old tag
  156. InternalFS.begin(); // remount the fresh FS
  157. File g(InternalFS); // stamp the tag so subsequent boots skip the wipe
  158. if (g.open(RESET_TAG_FILE, FILE_O_WRITE)) {
  159. g.write((const uint8_t *)buildTag, strlen(buildTag));
  160. g.close();
  161. }
  162. }