config.cpp 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  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] = { MODE_LIZARD, MODE_XBOX, MODE_SW_PRO }; // back4+B/X/Y -> these modes (A always STEAM); Y defaults to Switch Pro
  9. bool g_persistMode = false;
  10. uint8_t g_bootMode = 0xFF;
  11. bool g_debugCdcThisBoot = false;
  12. static uint8_t g_debugCdc = 0; // persisted one-shot arm, stored in Cfg.rsvd0 (1 = keep CDC for the next boot)
  13. int g_mDiv = 64, g_mFric = 94;
  14. uint8_t g_abSwap = 0;
  15. uint8_t g_back[4] = {5,6,7,8}; // L4->LB R4->RB L5->L3 R5->R3 (0..11 buttons, 12..15 D-pad U/D/L/R)
  16. uint8_t g_qamMap = 0; // 0 = default (unmapped, uses hardcoded behavior per mode)
  17. uint8_t g_rumbleScale = 200; // rumble strength % (200 = double); adjustable from the WebUSB panel
  18. // poll rate is fixed. Faster than the controller can refresh wastes airtime; slower adds latency. Any rate
  19. // persisted by an older build is ignored and overwritten with the default on boot (see loadCfg).
  20. const uint32_t g_pollUs = POLL_US_DEFAULT;
  21. #define CFG_FILE "/cfg.bin"
  22. #define CFG_MAGIC 0xC9 // bumped (rumbleScale): old cfg ignored -> clean defaults on first boot
  23. struct Cfg { uint8_t magic, mode, mDiv, mFric, rsvd0, abSwap, back[4], pollU100, persistMode, bootMode, chordBtn[3], qamMap, rumbleScale; }; // rsvd0 = ex-padSmooth, now the one-shot debug-CDC arm
  24. void saveCfg(){
  25. Cfg c={CFG_MAGIC,g_usbMode,(uint8_t)g_mDiv,(uint8_t)g_mFric,g_debugCdc,g_abSwap,
  26. {g_back[0],g_back[1],g_back[2],g_back[3]},(uint8_t)(g_pollUs/100),(uint8_t)(g_persistMode?1:0),g_bootMode,
  27. {g_chordBtn[0],g_chordBtn[1],g_chordBtn[2]},g_qamMap,g_rumbleScale};
  28. InternalFS.remove(CFG_FILE); File f(InternalFS);
  29. if(f.open(CFG_FILE,FILE_O_WRITE)){ f.write((uint8_t*)&c,sizeof c); f.close(); }
  30. }
  31. void loadCfg(){
  32. Cfg c; File f(InternalFS); bool consume=false;
  33. if(f.open(CFG_FILE,FILE_O_READ)){
  34. if(f.read((uint8_t*)&c,sizeof c)==(int)sizeof c && c.magic==CFG_MAGIC){
  35. g_mDiv=c.mDiv?c.mDiv:64; g_mFric=c.mFric;
  36. g_abSwap=c.abSwap; for(int i=0;i<4;i++) g_back[i]=c.back[i];
  37. g_persistMode = c.persistMode?true:false;
  38. // one-shot debug-CDC (Cfg.rsvd0): honor it for THIS boot, then consume so the next boot reverts to normal.
  39. g_debugCdcThisBoot = c.rsvd0 ? true : false;
  40. if(c.rsvd0){ g_debugCdc = 0; consume = true; }
  41. // poll rate is fixed (g_pollUs const = POLL_US_DEFAULT). A stale rate from an older build is never
  42. // applied; rewrite cfg so the persisted byte also matches the new default.
  43. if(c.pollU100 != (uint8_t)(POLL_US_DEFAULT/100)) consume=true;
  44. // boot-mode policy: a one-shot bootMode (set by an explicit switch when !persist) wins once and is then
  45. // cleared; otherwise persist->last mode, else->Steam. (poll rate stays POLL_US_DEFAULT -- never restored from cfg.)
  46. if(c.bootMode!=0xFF){ g_usbMode=modeValid(c.bootMode)?c.bootMode:0; consume=true; }
  47. else g_usbMode = g_persistMode ? (modeValid(c.mode)?c.mode:0) : 0;
  48. static const uint8_t CHORD_DEF[3]={MODE_LIZARD,MODE_XBOX,MODE_SW_PRO};
  49. for(int i=0;i<3;i++) g_chordBtn[i]=modeValid(c.chordBtn[i])?c.chordBtn[i]:CHORD_DEF[i];
  50. g_qamMap = c.qamMap;
  51. g_rumbleScale = c.rumbleScale; // 0 is a valid setting (rumble off)
  52. }
  53. f.close();
  54. }
  55. if(consume){ g_bootMode=0xFF; saveCfg(); } // clear the one-shot so the NEXT cold boot reverts to the default/persist policy
  56. }
  57. void saveMode(uint8_t m){
  58. if(g_persistMode){ g_usbMode=m; g_bootMode=0xFF; }
  59. else { g_bootMode=m; }
  60. saveCfg();
  61. }
  62. void armDebugCdcNextBoot(){ g_debugCdc = 1; saveCfg(); } // next boot keeps CDC; loadCfg() consumes it after
  63. // FULL factory wipe: erase ALL persistent storage in one shot -- cfg.bin (modes/tunables/chords), bonds.bin
  64. // (the paired-controller record), and anything else living in the internal LittleFS. format() reformats the
  65. // whole filesystem, so there's nothing left to selectively miss. The caller is expected to reboot immediately
  66. // (NVIC_SystemReset): on the next boot loadCfg()/loadBonds() find no files and fall back to clean defaults,
  67. // and the controller must be re-paired. Destructive and irreversible -- gated behind explicit confirmation at
  68. // every call site (serial "ERASE-ALL", WebUSB op 0x0A magic, both with on-screen warnings).
  69. void factoryErase(){
  70. InternalFS.begin(); // ensure mounted before we reformat (no-op if already up)
  71. InternalFS.format(); // wipes cfg.bin + bonds.bin + the entire FS
  72. }
  73. // One-time factory reset for the -DOPK_FACTORY_RESET recovery build. The point is to clear a bad config/bond
  74. // ONCE (at the first boot after flashing) and then behave like a normal build that PERSISTS settings -- NOT to
  75. // wipe on every boot (which would make the board un-configurable). We track "already reset" with a tiny tag file
  76. // holding the build's git hash, written AFTER the wipe (so it survives in the freshly-formatted FS):
  77. // - tag missing or != this build's hash -> a reset is pending: wipe, then stamp the tag. Next boot persists.
  78. // - tag == this build's hash -> already reset for this build: skip, boot normally.
  79. // Because the tag is keyed to the git hash, flashing a DIFFERENT build re-triggers the one-time wipe; re-flashing
  80. // the identical image does not (use the WebUSB/serial erase for an on-demand wipe). buildTag is OPK_GIT_HASH.
  81. #define RESET_TAG_FILE "/rsttag"
  82. void factoryResetOnce(const char* buildTag){
  83. char tag[24] = {0};
  84. { File f(InternalFS);
  85. if (f.open(RESET_TAG_FILE, FILE_O_READ)) { int n=f.read((uint8_t*)tag, sizeof tag - 1); if(n>0) tag[n]=0; f.close(); } }
  86. if (strncmp(tag, buildTag, sizeof tag - 1) == 0) return; // this build already did its one-time reset -> persist
  87. factoryErase(); // wipe cfg.bin + bonds.bin + the old tag
  88. InternalFS.begin(); // remount the fresh FS
  89. File g(InternalFS); // stamp the tag so subsequent boots skip the wipe
  90. if (g.open(RESET_TAG_FILE, FILE_O_WRITE)) { g.write((const uint8_t*)buildTag, strlen(buildTag)); g.close(); }
  91. }