ソースを参照

update defaults and add "clean" ps4 and 5 modes

Jariullah Safi 4 ヶ月 前
親
コミット
8336180428

+ 16 - 6
OpenPuck/OpenPuck.ino

@@ -59,6 +59,14 @@ void setup() {
   // for that boot and skips the wake mouse (no endpoint room for both). Clean modes always rebuild bare.
   const bool puckMode = g_active->isPuck();
   const bool keepCdc  = puckMode && g_debugCdcThisBoot;
+  // The "game" PlayStation modes enumerate as a CLEAN single HID gamepad so games that classify off the raw HID
+  // device (Fortnite/UE GameInput, Windows.Gaming.Input) recognise them as PlayStation. A real Sony pad is just
+  // the gamepad HID (+ a USB-audio interface); our extra wake-mouse HID and WebUSB vendor interface make those
+  // classifiers refuse the PS glyph path even with the correct VID/PID (SDL/Steam tolerate the extras, which is
+  // why Steam still sees the normal PS modes). In clean-PS modes we skip BOTH the wake mouse and WebUSB -- the
+  // cost is no config panel / host-wake; chord back to Steam (back-paddle 4 + A) to reach the panel. The normal
+  // MODE_PS5 / MODE_HIDGYRO keep wake + panel (use those when you need host-wake; the *_GAME ones for games).
+  const bool psClean  = modeIsCleanPS(g_usbMode);
   USBDevice.detach(); delay(30);
   if (keepCdc) {
     USBDevice.setConfigurationBuffer(g_usbCfgDesc, sizeof g_usbCfgDesc);   // keep boot CDC composite (debug boot)
@@ -70,7 +78,7 @@ void setup() {
   // Distinct USB serial PER MODE (must be set AFTER clearConfiguration, which nulls it). Hosts cache USB
   // identity by VID:PID:serial; reusing one serial under a changing VID:PID can make a host refuse the new
   // identity. Steam keeps the exact unit serial (its pairing identity); the others get a 1-char suffix.
-  static const char MODE_SUFFIX[] = {'X','N','L','P','S','G'};   // modes 1..6
+  static const char MODE_SUFFIX[] = {'X','N','L','P','S','G','Q','D'};   // modes 1..8 (Q=PS5 game, D=DS4 game)
   if (puckMode) { USBDevice.setSerialDescriptor(g_unit); }
   else { snprintf(g_usbSerial, sizeof g_usbSerial, "%s%c", g_unit, MODE_SUFFIX[g_usbMode-1]); USBDevice.setSerialDescriptor(g_usbSerial); }
 
@@ -82,11 +90,13 @@ void setup() {
   g_active->begin();   // register this mode's USB interface(s) + set VID/PID/strings
 
   // Boot-mouse wake interface for clean (non-puck) modes, and for puck on the one-shot debug boot (CDC on,
-  // no endpoint room for wake mouse on a normal puck boot -- wake is registered above instead).
-  if (!puckMode && !keepCdc) wakeHidBegin();
+  // no endpoint room for wake mouse on a normal puck boot -- wake is registered above instead). Skipped for PS
+  // modes so the device stays a single clean HID gamepad (see psClean above).
+  if (!puckMode && !keepCdc && !psClean) wakeHidBegin();
 
-  // WebUSB config panel -- every mode. Puck: registered above (IF 0) before wake + slots; clean modes after controller.
-  if (!puckMode) usb_web.begin();
+  // WebUSB config panel -- every mode EXCEPT the PlayStation modes. Puck: registered above (IF 0) before wake +
+  // slots; other clean modes after controller. PS modes omit it to present a genuine single-HID PS controller.
+  if (!puckMode && !psClean) usb_web.begin();
   // Enable USB Remote Wakeup (bit 5) so the host lets us signal wake-from-sleep. Bit 7 is always required.
   USBDevice.setConfigurationAttribute(0x80 | 0x20);  // bmAttributes: required(0x80) | remote_wakeup(0x20)
   USBDevice.attach();   // re-connect with the final descriptor (host re-reads it fresh -> deterministic enumeration)
@@ -95,7 +105,7 @@ void setup() {
   if (USBDevice.suspended()){ USBDevice.remoteWakeup(); ledWakePulse(); }   // wake host if bus was sleeping when we (re-)attached
   loadBonds();
   hapticInit();   // clear relay/active flags + arm the reconnect block & initial stop burst
-  static const char* MODE_NAME[]={"STEAM(puck)","XBOX(xinput+mouse)","SWITCH(horipad)","LIZARD(puck kb/mouse)","SWITCH(pro+gyro)","PS5(dualsense)","HIDGYRO(ds4+motion)"};
+  static const char* MODE_NAME[]={"STEAM(puck)","XBOX(xinput+mouse)","SWITCH(horipad)","LIZARD(puck kb/mouse)","SWITCH(pro+gyro)","PS5(dualsense)","HIDGYRO(ds4+motion)","PS5(dualsense,game/clean)","DS4(ds4,game/clean)"};
   Serial.printf("# copycat up: unit=%s board=%s, mode=%s\n", g_unit, g_board, MODE_NAME[g_usbMode<=MODE_MAX?g_usbMode:0]);
   if (puckMode) Serial.printf("# puck USB: %s\n", keepCdc ? "DEBUG boot (CDC console on, wake mouse off; reverts next boot)" : "normal (CDC off, wake mouse on)");
   Serial.printf("# session addr %02X%02X%02X%02X/%02X ch%u (discovery on ibex/ch2)\n",

+ 2 - 2
OpenPuck/config.cpp

@@ -6,7 +6,7 @@ using namespace Adafruit_LittleFS_Namespace;
 
 uint8_t g_usbMode = 0;
 bool    g_xbox = false;
-uint8_t g_chordBtn[3] = { MODE_LIZARD, MODE_XBOX, MODE_SW_HORI };   // back4+B/X/Y -> these modes (A always STEAM)
+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
 bool    g_persistMode = false;
 uint8_t g_bootMode = 0xFF;
 
@@ -52,7 +52,7 @@ void loadCfg(){
       // cleared; otherwise persist->last mode, else->Steam. (poll rate stays POLL_US_DEFAULT -- never restored from cfg.)
       if(c.bootMode!=0xFF){ g_usbMode=modeValid(c.bootMode)?c.bootMode:0; consume=true; }
       else                 g_usbMode = g_persistMode ? (modeValid(c.mode)?c.mode:0) : 0;
-      static const uint8_t CHORD_DEF[3]={MODE_LIZARD,MODE_XBOX,MODE_SW_HORI};
+      static const uint8_t CHORD_DEF[3]={MODE_LIZARD,MODE_XBOX,MODE_SW_PRO};
       for(int i=0;i<3;i++) g_chordBtn[i]=modeValid(c.chordBtn[i])?c.chordBtn[i]:CHORD_DEF[i];
       g_qamMap = c.qamMap;
       g_rumbleScale = c.rumbleScale;   // 0 is a valid setting (rumble off)

+ 10 - 3
OpenPuck/config.h

@@ -34,9 +34,16 @@
 #define MODE_SW_HORI 2   // HORIPAD (Switch console whitelist)
 #define MODE_LIZARD  3   // Puck HID; always keyboard+mouse (ignores Steam heartbeat)
 #define MODE_SW_PRO  4   // Nintendo Switch Pro Controller (057E:2009) + gyro
-#define MODE_PS5     5   // Sony DualSense (054C:0CE6) + gyro + split trackpad
-#define MODE_HIDGYRO 6   // DS4-layout generic HID gamepad + gyro (Fortnite-friendly)
-#define MODE_MAX     6
+#define MODE_PS5     5   // Sony DualSense (054C:0CE6) + gyro + split trackpad (+ wake mouse + WebUSB panel)
+#define MODE_HIDGYRO 6   // DS4-layout generic HID gamepad + gyro (+ wake mouse + WebUSB panel)
+#define MODE_PS5_GAME 7  // DualSense, CLEAN single-HID (no wake/WebUSB) so PC games classify it as PlayStation (Fortnite)
+#define MODE_DS4_GAME 8  // DS4, CLEAN single-HID (no wake/WebUSB) for game PlayStation classification
+#define MODE_MAX     8
+
+// The two "game" personalities drop the wake-mouse + WebUSB interfaces so the device is a genuine single-HID PS
+// controller (some PC games -- e.g. Fortnite/UE GameInput -- refuse PS classification when extra interfaces are
+// present). Cost: no config panel / host-wake while in these modes; chord back to Steam (back4 + A) for the panel.
+static inline bool modeIsCleanPS(uint8_t m){ return m==MODE_PS5_GAME || m==MODE_DS4_GAME; }
 
 static inline bool modeIsPuck(uint8_t m){ return m==MODE_STEAM || m==MODE_LIZARD; }
 static inline bool modeValid(uint8_t m){ return m<=MODE_MAX; }

+ 2 - 0
OpenPuck/controllers.cpp

@@ -21,6 +21,8 @@ IController* controllerFor(uint8_t mode){
     case MODE_SW_PRO:  return &g_switchPro;
     case MODE_PS5:     return &g_ps5Ctl;
     case MODE_HIDGYRO: return &g_hidGyroCtl;
+    case MODE_PS5_GAME:return &g_ps5Ctl;     // same DualSense controller; setup() drops wake/WebUSB for clean enum
+    case MODE_DS4_GAME:return &g_hidGyroCtl; // same DS4 controller; setup() drops wake/WebUSB for clean enum
     default:           return &g_steamPuck;
   }
 }

+ 1 - 1
OpenPuck/mode_hidgyro.cpp

@@ -66,7 +66,7 @@ static void hidGyroBuild(uint8_t out[63]){
 
 void HidGyroController::begin(){
   USBDevice.setID(0x054C, 0x05C4);
-  USBDevice.setDeviceVersion(0x0102);   // bumped from 0x0101 for 1ms poll interval (Windows caches config by VID:PID:bcdDevice)
+  USBDevice.setDeviceVersion(0x0104);   // bumped: clean-DS4 is now a separate mode (DS4_GAME); normal HIDGYRO keeps wake+WebUSB. Host re-reads config by VID:PID:serial (per-mode suffix) -- bump invalidates any cached 0x0103.
   USBDevice.setManufacturerDescriptor("Sony Computer Entertainment");
   USBDevice.setProductDescriptor("Wireless Controller");
   g_hidGyro.enableOutEndpoint(true);

+ 1 - 1
OpenPuck/mode_ps5.cpp

@@ -97,7 +97,7 @@ static void ps5Build(uint8_t out[63]){
 
 void Ps5Controller::begin(){
   USBDevice.setID(0x054C, 0x0CE6);
-  USBDevice.setDeviceVersion(0x0102);   // bumped from 0x0101 for 1ms poll interval (Windows caches config by VID:PID:bcdDevice)
+  USBDevice.setDeviceVersion(0x0104);   // bumped: clean-PS is now a separate mode (PS5_GAME); normal PS5 keeps wake+WebUSB. Host re-reads config by VID:PID:serial (per-mode suffix) -- bump invalidates any cached 0x0103.
   USBDevice.setManufacturerDescriptor("Sony Interactive Entertainment");
   USBDevice.setProductDescriptor("DualSense Wireless Controller");
   g_ps5.enableOutEndpoint(true);

+ 1 - 1
OpenPuck/serial_console.cpp

@@ -38,7 +38,7 @@ void serialConsolePoll(){
       else if (line[0]=='x'){ uint8_t m=strtoul(line+1,0,10); if(modeValid(m)){
         if(USBDevice.suspended()){ Serial.println("# mode change blocked: host suspended"); }
         else { Serial.printf("# switch mode %u (reboot)\n",m); delay(20); saveMode(m); delay(40); NVIC_SystemReset(); }
-      } }   // switch USB mode: 0=steam 1=xbox 2=hori 3=lizard 4=swpro 5=ps5 6=hidgyro
+      } }   // switch USB mode: 0=steam 1=xbox 2=hori 3=lizard 4=swpro 5=ps5 6=hidgyro 7=ps5-game/clean 8=ds4-game/clean
       else if (line[0]=='c'){ g_rfCh=atoi(line+1); Serial.printf("# ch=%u\n",g_rfCh); if(g_rfListen) rfListenStart(); }
       else if (line[0]=='p'){ g_rfPrefix=strtol(line+1,0,16); Serial.printf("# prefix=%02X\n",g_rfPrefix); if(g_rfListen) rfListenStart(); }
       else if (line[0]=='i'){ g_crcinit=strtoul(line+1,0,16); Serial.printf("# crcinit=%06lX\n",(unsigned long)g_crcinit); }

+ 8 - 3
docs/index.html

@@ -59,6 +59,8 @@
         <button data-mode="4" class="modebtn">Switch Pro + gyro</button>
         <button data-mode="5" class="modebtn">PS5 DualSense</button>
         <button data-mode="6" class="modebtn">HID gyro (DS4)</button>
+        <button data-mode="7" class="modebtn">PS5 (game/clean)</button>
+        <button data-mode="8" class="modebtn">DS4 (game/clean)</button>
       </div>
       <p class="note">Switching reboots the copycat and re-enumerates USB (~2 s). WebUSB works in every mode, including Steam/Lizard with Steam running (Chrome claims the vendor interface; Steam keeps the HID slots). Switch HORIPAD + WebUSB is PC-only.</p>
       <div class="row"><label>Persist last mode</label>
@@ -148,7 +150,7 @@
 </div>
 
 <script>
-const MODE_NAMES = ["Steam (puck)","Xbox 360","Switch (HORIPAD)","Lizard (always)","Switch Pro + gyro","PS5 DualSense","HID gyro (DS4)"];
+const MODE_NAMES = ["Steam (puck)","Xbox 360","Switch (HORIPAD)","Lizard (always)","Switch Pro + gyro","PS5 DualSense","HID gyro (DS4)","PS5 (game/clean)","DS4 (game/clean)"];
 const CHORD_FIELD = [17, 18, 19];
 const BTN_CODES = [[0,"— none —"],[1,"A"],[2,"B"],[3,"X"],[4,"Y"],[5,"LB"],[6,"RB"],[7,"L3"],[8,"R3"],[9,"Back"],[10,"Quick Access Menu"],[11,"Guide"],[12,"D-pad Up"],[13,"D-pad Down"],[14,"D-pad Left"],[15,"D-pad Right"],[16,"PS Touch Click"],[17,"PS5 Mute"]];
 let dev=null, epIn=0, epOut=0, ifNum=0, polling=false, capturing=false;
@@ -225,7 +227,7 @@ function applyBlob(p){
   const mode=p[1], mDiv=p[2], mFric=p[3], qamMap=p[4], abSwap=p[5];   // p[4] = QAM remap code (0 = default)
   const back=[p[6],p[7],p[8],p[9]], slot=p[10], up=p[11], f1=(p[12]|(p[13]<<8));
   const newps=(p.length>16?(p[15]|(p[16]<<8)):0), persistMode=(p.length>22?p[22]:0);
-  const chord=(p.length>25)?[p[23],p[24],p[25]]:[3,1,2];   // v2 blob: chordBtn after qos/persist
+  const chord=(p.length>25)?[p[23],p[24],p[25]]:[3,1,4];   // v2 blob: chordBtn after qos/persist (Y defaults to Switch Pro=4)
   const polls=(p.length>27?(p[26]|(p[27]<<8)):0);          // poll TX rate
   const loopUs=(p.length>29?(p[28]|(p[29]<<8)):0);         // loop diag
   const worstIdx=(p.length>30?p[30]:0), worstUs=(p.length>32?(p[31]|(p[32]<<8)):0);
@@ -382,7 +384,10 @@ for(const sel of document.querySelectorAll("select.chord")){
   sel.addEventListener("change", ()=>setField(CHORD_FIELD[+sel.dataset.i], +sel.value));
 }
 for(const b of document.querySelectorAll(".modebtn")){
-  b.onclick=()=>{ if(confirm("Switch to "+MODE_NAMES[+b.dataset.mode]+"? The copycat will reboot.")){ send([0x03, +b.dataset.mode]); log("mode switch requested — device will reboot"); } };
+  b.onclick=()=>{ const m=+b.dataset.mode;
+    const clean=(m===7||m===8);
+    const msg="Switch to "+MODE_NAMES[m]+"? The copycat will reboot."+(clean?"\n\nNOTE: the game/clean PlayStation modes drop WebUSB + host-wake, so THIS PANEL WILL DISCONNECT and can't reach the device while it's in this mode. To get back, chord on the controller: hold back-paddle 4 + A to return to Steam mode.":"");
+    if(confirm(msg)){ send([0x03, m]); log("mode switch requested — device will reboot"); } };
 }
 if(!("usb" in navigator)) $("#connectBtn").outerHTML='<b style="color:var(--bad)">WebUSB not supported — use Chrome or Edge.</b>';
 </script>