config.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  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. #include <stddef.h> // offsetof (CFG_LEN_MIN, the short-file accept threshold)
  8. using namespace Adafruit_LittleFS_Namespace;
  9. uint8_t g_usbMode = 0;
  10. bool g_xbox = false;
  11. uint8_t g_chordBtn[3] = {
  12. MODE_LIZARD, MODE_XBOX, MODE_SW_PRO
  13. }; // back4+B/X/Y -> these modes (A always STEAM); Y defaults to Switch Pro
  14. // back4+D-pad (left/up/right/down). Defaults are the console personalities that ship WITHOUT a config
  15. // interface -- those modes can't be entered from the panel-less side any other way, and back4+A still returns
  16. // to Steam. Configurable like g_chordBtn (WebUSB fields 34..37).
  17. uint8_t g_chordDpad[4] = { MODE_PS3, MODE_DS4_GAME, MODE_PS5_GAME,
  18. MODE_SW_HORI };
  19. bool g_persistMode = false;
  20. uint8_t g_bootMode = 0xFF;
  21. bool g_debugCdcThisBoot = false;
  22. // persisted one-shot arm, stored in Cfg.rsvd0 (1 = keep CDC for the next boot)
  23. static uint8_t g_debugCdc = 0;
  24. int g_mDiv = 64, g_mFric = 94;
  25. // 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,
  26. // 16/17 PS touch/mute, 18 Switch Capture). Switch differs: QAM defaults to Capture(18), A/B swap on, and
  27. // trackpad haptics off. qamMap 0 = unmapped (hardcoded per-mode behavior). ledBright 0 = no override.
  28. // rumble 1 = enabled (default), 0 = host rumble silenced for that type.
  29. TypeCfg g_type[ET_COUNT] = {
  30. /* ET_XBOX */ { { 5, 6, 7, 8 }, 0, 0, 1, 0, 1 },
  31. /* ET_SWITCH */ { { 5, 6, 7, 8 }, 18, 1, 0, 0, 1 },
  32. /* ET_DS4 */ { { 5, 6, 7, 8 }, 0, 0, 1, 0, 1 },
  33. /* ET_DS5 */ { { 5, 6, 7, 8 }, 0, 0, 1, 0, 1 },
  34. };
  35. uint8_t g_etype = ET_NONE;
  36. // Trackpad -> stick mapping, off for every type by default (pads keep their touch/mouse behavior).
  37. uint8_t g_padStickCfg[ET_COUNT][2] = {};
  38. uint8_t g_padStick[2] = { PS_OFF, PS_OFF };
  39. // Live mirrors of the active type (puck modes use the harmless defaults below).
  40. uint8_t g_abSwap = 0;
  41. uint8_t g_back[4] = { 5, 6, 7, 8 };
  42. uint8_t g_qamMap = 0;
  43. uint8_t g_padHaptics = 1;
  44. uint8_t g_rumble = 1;
  45. uint8_t g_ledBright = 0;
  46. void applyActiveType()
  47. {
  48. g_etype = etypeForMode(g_usbMode);
  49. if (g_etype >=
  50. ET_COUNT) { // puck mode (Steam/Lizard): no remap, haptics on
  51. g_back[0] = 5;
  52. g_back[1] = 6;
  53. g_back[2] = 7;
  54. g_back[3] = 8;
  55. g_qamMap = 0;
  56. g_abSwap = 0;
  57. g_padHaptics = 1;
  58. g_rumble = 1;
  59. g_ledBright = 0;
  60. g_padStick[0] = g_padStick[1] = PS_OFF;
  61. return;
  62. }
  63. const TypeCfg &t = g_type[g_etype];
  64. for (int i = 0; i < 4; i++)
  65. g_back[i] = t.back[i];
  66. g_qamMap = t.qamMap;
  67. g_abSwap = t.abSwap;
  68. g_padHaptics = t.padHaptics;
  69. g_rumble = t.rumble;
  70. g_ledBright = t.ledBright;
  71. g_padStick[0] = g_padStickCfg[g_etype][0];
  72. g_padStick[1] = g_padStickCfg[g_etype][1];
  73. }
  74. // poll rate defaults to POLL_US_DEFAULT (250 Hz), matching the real Valve puck (see config.h). The
  75. // delivered report rate equals the poll rate (fresh IMU in every reply). Live-adjustable via console
  76. // "PR<hz>" for on-HW sweeps; session-only, so any rate persisted by an older build is ignored and boot
  77. // always starts at the default (see loadCfg).
  78. uint32_t g_pollUs = POLL_US_DEFAULT;
  79. #define CFG_FILE "/cfg.bin"
  80. // Struct layout/semantics changed (TypeCfg gained rumble byte); bump so old flash format is discarded ->
  81. // clean defaults once.
  82. #define CFG_MAGIC 0xCF
  83. struct Cfg {
  84. uint8_t magic, mode, mDiv, mFric, rsvd0, pollU100, persistMode,
  85. bootMode, chordBtn[3], rumbScale2;
  86. // rumbScale2: host-rumble strength as PERCENT/2 (so 500% fits a byte). 0 = never set -> keep the
  87. // RUMBLE_SCALE_PCT default. This revives the byte the removed rumble-strength slider used, so the
  88. // on-flash layout is unchanged and an existing cfg.bin still loads.
  89. // rxWin10: legacy RF tunable slot (window now fixed; ignored). lizKeep: the id9=0 hold enable (see
  90. // haptics.h LIZKEEP_MS). rsvd2 used to be landAll87, this is now ignored.
  91. uint8_t rxWin10, lizKeep, rsvd2;
  92. TypeCfg type[ET_COUNT]; // per-emulated-type back/qam/abSwap/padHaptics
  93. // TAIL (appended after CFG_MAGIC 0xCF shipped): back4+D-pad mode assignments. New tail fields go HERE, at
  94. // the end, and loadCfg accepts a short file so an upgrade keeps every existing setting -- see CFG_LEN_MIN.
  95. uint8_t chordDpad[4];
  96. // per-type trackpad->stick mapping: [et][0] = left pad, [et][1] = right pad (PS_*)
  97. uint8_t padStick[ET_COUNT][2];
  98. // RUMBLE_STYLE_*; 0xFF (short pre-tail file) -> compiled default
  99. uint8_t rumbleStyle;
  100. // autonomous controller power-off on host sleep (see haptics.h g_suspendOff). 0/1; 0xFF (short
  101. // pre-tail file) -> compiled default (on)
  102. uint8_t suspendOff;
  103. uint8_t ext[CFG_EXT_STORAGE_BYTES];
  104. }; // rsvd0 = ex-padSmooth, now the one-shot debug-CDC arm
  105. // Shortest cfg.bin we still accept: the layout as of CFG_MAGIC 0xCF, i.e. everything before the appended tail.
  106. // A file that stops anywhere in the tail leaves those bytes at the 0xFF prefill loadCfg() applies, which every
  107. // tail field treats as "unset" and replaces with its default.
  108. #define CFG_LEN_MIN (offsetof(struct Cfg, chordDpad))
  109. static uint8_t g_cfgExt[CFG_EXT_STORAGE_BYTES] = { 0xFF, 0xFF, 0xFF, 0xFF,
  110. 0xFF, 0xFF, 0xFF, 0xFF,
  111. 0xFF, 0xFF, 0xFF };
  112. uint8_t cfgExtRead(uint8_t index)
  113. {
  114. return index < CFG_EXT_STORAGE_BYTES ? g_cfgExt[index] : 0xFFu;
  115. }
  116. void cfgExtWrite(uint8_t index, uint8_t value)
  117. {
  118. if (index < CFG_EXT_STORAGE_BYTES)
  119. g_cfgExt[index] = value;
  120. }
  121. void saveCfg()
  122. {
  123. Cfg c = { CFG_MAGIC,
  124. g_usbMode,
  125. (uint8_t)g_mDiv,
  126. (uint8_t)g_mFric,
  127. g_debugCdc,
  128. (uint8_t)(g_pollUs / 100),
  129. (uint8_t)(g_persistMode ? 1 : 0),
  130. g_bootMode,
  131. { g_chordBtn[0], g_chordBtn[1], g_chordBtn[2] },
  132. (uint8_t)(g_rumbleScale / 2), // host-rumble strength, pct/2
  133. (uint8_t)(g_rxWin / 10),
  134. g_lizKeep,
  135. 0, //rsvd2, used to be g_landAll87
  136. {},
  137. { g_chordDpad[0], g_chordDpad[1], g_chordDpad[2],
  138. g_chordDpad[3] },
  139. {},
  140. g_rumbleStyle,
  141. g_suspendOff,
  142. {} };
  143. memcpy(c.ext, g_cfgExt, sizeof c.ext);
  144. for (int i = 0; i < ET_COUNT; i++) {
  145. c.type[i] = g_type[i];
  146. c.padStick[i][0] = g_padStickCfg[i][0];
  147. c.padStick[i][1] = g_padStickCfg[i][1];
  148. }
  149. InternalFS.remove(CFG_FILE);
  150. File f(InternalFS);
  151. if (f.open(CFG_FILE, FILE_O_WRITE)) {
  152. f.write((uint8_t *)&c, sizeof c);
  153. f.close();
  154. }
  155. }
  156. void loadCfg()
  157. {
  158. Cfg c;
  159. memset(g_cfgExt, 0xFF, sizeof g_cfgExt);
  160. // 0xFF prefill: bytes a SHORT (pre-tail) cfg.bin never wrote stay 0xFF, which is not a valid mode/flag, so
  161. // each tail field below falls back to its compiled default instead of reading whatever was on the stack.
  162. memset(&c, 0xFF, sizeof c);
  163. File f(InternalFS);
  164. bool consume = false;
  165. if (f.open(CFG_FILE, FILE_O_READ)) {
  166. int got = f.read((uint8_t *)&c, sizeof c);
  167. // Accept a file that is short only in the appended tail (>= CFG_LEN_MIN): an upgrade from a build
  168. // predating the tail keeps mode/paddles/chords instead of silently reverting to factory defaults.
  169. // Anything shorter, or a stale magic, is a real layout change -> discard and use defaults.
  170. if (got >= (int)CFG_LEN_MIN && c.magic == CFG_MAGIC) {
  171. const int extOff = (int)offsetof(struct Cfg, ext);
  172. if (got > extOff) {
  173. int n = got - extOff;
  174. if (n > (int)sizeof g_cfgExt)
  175. n = sizeof g_cfgExt;
  176. memcpy(g_cfgExt, c.ext, (size_t)n);
  177. }
  178. g_mDiv = c.mDiv ? c.mDiv : 64;
  179. g_mFric = c.mFric;
  180. for (int i = 0; i < ET_COUNT; i++)
  181. g_type[i] = c.type[i];
  182. g_persistMode = c.persistMode ? true : false;
  183. // one-shot debug-CDC (Cfg.rsvd0): honor for THIS boot, then consume so the next boot reverts to normal.
  184. g_debugCdcThisBoot = c.rsvd0 ? true : false;
  185. if (c.rsvd0) {
  186. g_debugCdc = 0;
  187. consume = true;
  188. }
  189. // poll rate is fixed; rewrite cfg so the persisted byte matches the new default.
  190. if (c.pollU100 != (uint8_t)(POLL_US_DEFAULT / 100))
  191. consume = true;
  192. // boot-mode policy: a one-shot bootMode (explicit switch when !persist) wins once then clears;
  193. // otherwise persist->last mode, else->Steam.
  194. if (c.bootMode != 0xFF) {
  195. g_usbMode = modeValid(c.bootMode) ? c.bootMode :
  196. 0;
  197. consume = true;
  198. } else
  199. g_usbMode = g_persistMode ? (modeValid(c.mode) ?
  200. c.mode :
  201. 0) :
  202. 0;
  203. static const uint8_t CHORD_DEF[3] = { MODE_LIZARD,
  204. MODE_XBOX,
  205. MODE_SW_PRO };
  206. for (int i = 0; i < 3; i++)
  207. g_chordBtn[i] = modeValid(c.chordBtn[i]) ?
  208. c.chordBtn[i] :
  209. CHORD_DEF[i];
  210. // D-pad chords: 0xFF (short pre-tail file) or any invalid mode keeps the compiled default.
  211. for (int i = 0; i < 4; i++)
  212. if (modeValid(c.chordDpad[i]))
  213. g_chordDpad[i] = c.chordDpad[i];
  214. // Pad->stick mapping: 0xFF (a file predating this tail field) or an out-of-range
  215. // value keeps the compiled default (off).
  216. for (int i = 0; i < ET_COUNT; i++)
  217. for (int k = 0; k < 2; k++)
  218. if (c.padStick[i][k] <= PS_MAX)
  219. g_padStickCfg[i][k] =
  220. c.padStick[i][k];
  221. // Missing extension bytes alone do not force a config rewrite.
  222. if (got < (int)offsetof(struct Cfg, ext))
  223. consume = true;
  224. // lizard-suppression keepalive enable (0/1; anything else = a pre-0xCE cfg leaked
  225. // through -> keep the on default)
  226. if (c.lizKeep <= 1)
  227. g_lizKeep = c.lizKeep;
  228. // host-rumble strength (pct/2; 0 = never set, or a cfg.bin from before this
  229. // field was revived -> keep the RUMBLE_SCALE_PCT default)
  230. if (c.rumbScale2) {
  231. uint16_t pct = (uint16_t)c.rumbScale2 * 2;
  232. if (pct < RUMBLE_SCALE_MIN)
  233. pct = RUMBLE_SCALE_MIN;
  234. else if (pct > RUMBLE_SCALE_MAX)
  235. pct = RUMBLE_SCALE_MAX;
  236. g_rumbleScale = pct;
  237. }
  238. // host-rumble style (0xFF = short pre-tail file -> keep the default)
  239. if (c.rumbleStyle <= RUMBLE_STYLE_MAX)
  240. g_rumbleStyle = c.rumbleStyle;
  241. // suspend power-off enable (0xFF = a cfg.bin predating this tail field -> keep the on default)
  242. if (c.suspendOff <= 1)
  243. g_suspendOff = c.suspendOff;
  244. // The poll RX window is now FIXED (g_rxWin is const) -- any persisted rxWin10 is ignored.
  245. }
  246. f.close();
  247. }
  248. // resolve the active emulated type's settings into the live mirrors the mode builders read
  249. applyActiveType();
  250. // clear the one-shot so the NEXT cold boot reverts to the default/persist policy
  251. if (consume) {
  252. g_bootMode = 0xFF;
  253. saveCfg();
  254. }
  255. }
  256. void saveMode(uint8_t m)
  257. {
  258. if (g_persistMode) {
  259. g_usbMode = m;
  260. g_bootMode = 0xFF;
  261. } else {
  262. g_bootMode = m;
  263. }
  264. saveCfg();
  265. }
  266. void armDebugCdcNextBoot()
  267. {
  268. g_debugCdc = 1;
  269. saveCfg();
  270. } // next boot keeps CDC; loadCfg() consumes it after
  271. // FULL factory wipe: reformat the internal LittleFS, erasing cfg.bin (modes/tunables/chords) AND bonds.bin
  272. // (paired-controller record). Caller reboots: next boot finds no files and falls back to clean defaults, and
  273. // the controller must be re-paired. Irreversible -- gated behind explicit confirmation at every call site.
  274. void factoryErase()
  275. {
  276. // ensure mounted before we reformat (no-op if already up)
  277. InternalFS.begin();
  278. InternalFS.format();
  279. }
  280. // One-time factory reset for the -DOPK_FACTORY_RESET recovery build: clear a bad config/bond ONCE (first boot
  281. // after flashing) then persist normally. "Already reset" is tracked by a tag file holding the build's git hash,
  282. // written AFTER the wipe (so it survives in the freshly-formatted FS):
  283. // - tag missing or != this build's hash -> wipe, then stamp the tag. Next boot persists.
  284. // - tag == this build's hash -> already reset for this build: skip, boot normally.
  285. // Keying the tag to the git hash means flashing a DIFFERENT build re-triggers the wipe. buildTag is OPK_GIT_HASH.
  286. #define RESET_TAG_FILE "/rsttag"
  287. void factoryResetOnce(const char *buildTag)
  288. {
  289. char tag[24] = { 0 };
  290. {
  291. File f(InternalFS);
  292. if (f.open(RESET_TAG_FILE, FILE_O_READ)) {
  293. int n = f.read((uint8_t *)tag, sizeof tag - 1);
  294. if (n > 0)
  295. tag[n] = 0;
  296. f.close();
  297. }
  298. }
  299. if (strncmp(tag, buildTag, sizeof tag - 1) == 0)
  300. return; // this build already did its one-time reset -> persist
  301. factoryErase(); // wipe cfg.bin + bonds.bin + the old tag
  302. InternalFS.begin(); // remount the fresh FS
  303. File g(InternalFS); // stamp the tag so subsequent boots skip the wipe
  304. if (g.open(RESET_TAG_FILE, FILE_O_WRITE)) {
  305. g.write((const uint8_t *)buildTag, strlen(buildTag));
  306. g.close();
  307. }
  308. }