Selaa lähdekoodia

Respond to 0x42 reports correctly

Jari 3 kuukautta sitten
vanhempi
commit
8a058df625
4 muutettua tiedostoa jossa 110 lisäystä ja 10 poistoa
  1. 5 0
      OpenPuck/mode_xinput.cpp
  2. 15 6
      OpenPuck/puck_hid.cpp
  3. 84 4
      OpenPuck/rf_link.cpp
  4. 6 0
      docs/index.html

+ 5 - 0
OpenPuck/mode_xinput.cpp

@@ -508,6 +508,11 @@ void XboxController::onReport45(int slot, const uint8_t *rep, bool fresh,
 	(void)bodyTlen;
 	if (slot < 0 || slot >= NSLOT)
 		return;
+	// rfXboxGamepad/rfXboxMouse read report 0x45 field offsets. A new-firmware controller sends its main
+	// input as report 0x42 (different front-section layout, not yet reversed) -- decoding it here as 0x45
+	// would emit garbage sticks/buttons. Skip until the 0x42 front layout lands; IMU-only modes are unaffected.
+	if (rep[0] != 0x45)
+		return;
 	rfXboxGamepad((uint8_t)slot, rep);
 	// Shared desktop mouse: slot 0's right-pad only. Other slots' right-pad is still part of the
 	// per-slot XInput (TB_RPADT in the gamepad report is unused today but kept for forward-compat).

+ 15 - 6
OpenPuck/puck_hid.cpp

@@ -449,22 +449,31 @@ void SteamPuckController::onReport45(int slot, const uint8_t *rep, bool fresh,
 		return;
 	if (!g_slot[slot].used)
 		return;
-	if (lizardActive()) {
+	// Report id: 0x45 = legacy main input; 0x42 = new-firmware (SC2 beta ~2026-07) main input (53B). Forward
+	// whichever the controller actually sent, verbatim, under its own id -- both are declared in PUCK_HID_DESC
+	// and this is exactly what the real puck does. rep[0] is the id byte (rep points at it in the F1 TLV).
+	const uint8_t rid = rep[0];
+	if (lizardActive() && rid == 0x45) {
 		// Every connected controller drives keyboard+mouse on ITS OWN slot interface; the OS merges the
 		// multiple HID mice/keyboards onto the one desktop. (Was hardcoded to slot 0, which went silent
 		// whenever the live controller bonded to a non-zero slot -- e.g. slot 0 holding a stale/phantom
 		// bond from a cloned backup.) rfLizard keeps its glide/edge state per-slot so they don't clobber.
+		// NOTE: rfLizard decodes 0x45 field offsets; the 0x42 front layout isn't reversed yet, so a
+		// new-firmware controller has no driverless lizard output until that decode lands (Steam still works).
 		rfLizard(slot, rep, &hid[slot], &hid[slot], 0x40, 0x41);
-	} else {
+	} else if (!lizardActive()) {
+		// body length after the id byte, clamped to the descriptor's declared size for this report id
+		// (0x42 = 53B vendor input, 0x45 = 45B input).
+		uint8_t maxb = (rid == 0x42) ? 53 : 45;
 		uint8_t blen = bodyTlen - 1;
-		if (blen > 45)
-			blen = 45; // body after the 0x45 id byte
+		if (blen > maxb)
+			blen = maxb;
 		// forward the puck's raw pad coords untouched (Steam does its own interpolation/smoothing). Forward
 		// only FRESH reports: the real puck dedupes, so stale repeats make Steam's velocity/smoothing
 		// stair-step. g_fwdNewOnly toggles for A/B.
 		if ((fresh || !g_fwdNewOnly) && hid[slot].ready())
-			usbTxHid(&hid[slot], 0x45, rep + 1,
-				 blen); // Steam/SDL Triton: input report 0x45
+			usbTxHid(&hid[slot], rid, rep + 1,
+				 blen); // Steam/SDL Triton: input report 0x45 (old) / 0x42 (new fw)
 	}
 }
 

+ 84 - 4
OpenPuck/rf_link.cpp

@@ -363,13 +363,20 @@ uint8_t rfConnTx(uint8_t ch, uint8_t s1, const uint8_t *payload, uint8_t plen,
 						ttype = rfrx[idx + 1];
 					if (tlen == 0)
 						break;
-					// Only a FULL 0x45 report that fits entirely in rfrx: a short or late/garbled TLV must not let the
-					// decode read past the RF buffer (corrupts rftx/RAM -> eventual crash).
+					// Only a FULL input report that fits entirely in rfrx: a short or late/garbled TLV must not let
+					// the decode read past the RF buffer (corrupts rftx/RAM -> eventual crash).
+					// Main input report id: 0x45 (legacy, 45B body) OR 0x42 (SC2 beta update ~2026-07, 53B body).
+					// VERIFIED from live captures of both: the 0x42 body [0..45] is byte-for-byte the SAME layout as
+					// 0x45 (buttons/triggers/sticks/pads/IMU at identical offsets) -- it just adds 8 trailing bytes
+					// ([46..47]=0x7FFF const, [48..53]=0) and sets two extra always-on status bits (28/29) that no
+					// mode reads. So both decode through this ONE path unchanged; rep[0] carries the id downstream
+					// (Steam forwards it verbatim under the right id/length in onReport45).
 					if (ttype == 6 && tlen >= 28 &&
 					    (size_t)(idx + 2) + tlen <=
 						    sizeof(rfrx) &&
-					    rfrx[idx + 2] == 0x45) {
-						// report 0x45: [0x45][seq][buttons u32]...
+					    (rfrx[idx + 2] == 0x45 ||
+					     rfrx[idx + 2] == 0x42)) {
+						// report 0x45/0x42: [id][seq][buttons u32]...
 						const uint8_t *rep =
 							&rfrx[idx + 2];
 						bool fresh =
@@ -582,6 +589,79 @@ uint8_t rfConnTx(uint8_t ch, uint8_t s1, const uint8_t *payload, uint8_t plen,
 								rep[0], rep + 1,
 								(uint8_t)(tlen -
 									  1));
+					} else if (ttype == 6 && tlen >= 46 &&
+						   (size_t)(idx + 2) + tlen <=
+							   sizeof(rfrx) &&
+						   rfrx[idx + 2] == 0x42) {
+						// NEW-FIRMWARE main input report (SC2 beta update ~2026-07): the controller moved
+						// its gamepad input from report 0x45 (45B body) to 0x42 (53B body). The IMU tail
+						// [34..45] is byte-identical to 0x45; the FRONT section [2..29] (buttons/sticks/
+						// triggers/pads) is a different layout still being reversed. Interim handling:
+						//  (a) forward 0x42 VERBATIM to Steam -- onReport45 reads rep[0] for the id, and
+						//      report 0x42 is already declared in PUCK_HID_DESC (53B), so this is exactly
+						//      what the real puck does -> restores Steam for new-fw controllers;
+						//  (b) decode the (identical) IMU into g_in so gyro-reading stream modes stay right.
+						// TODO(front-section): once the 0x42 [2..29] layout is captured, decode buttons/
+						// sticks/pads into g_in here (and drive the Steam-wake / Steam+Y-shutdown / mode
+						// chord like the 0x45 branch) so emulated modes (Switch/PS/Xbox/lizard) work too.
+						// The proven 0x45 branch above is left untouched so OLD controllers cannot regress.
+						const uint8_t *rep = &rfrx[idx + 2];
+						bool fresh =
+							(rep[1] != g_lastSeq[g_curSlot]);
+						if (fresh) {
+							g_stNew++;
+							g_lastSeq[g_curSlot] = rep[1];
+						}
+						g_connF1++;
+						imuFrom45(rep, &g_in[g_curSlot].ax,
+							  &g_in[g_curSlot].ay,
+							  &g_in[g_curSlot].az,
+							  &g_in[g_curSlot].gx,
+							  &g_in[g_curSlot].gy,
+							  &g_in[g_curSlot].gz);
+						if (g_active)
+							g_active->onReport45(g_curSlot,
+									     rep, fresh,
+									     tlen);
+						// RE capture aid: dump the full 0x42 report so the front-section [2..29] layout
+						// can be reversed by holding one control at a time. ~20/s, non-blocking. Remove
+						// once the layout is pinned.
+						{
+							static unsigned long lastI42 =
+								0;
+							if (Serial.availableForWrite() >
+								    120 &&
+							    millis() - lastI42 >= 50) {
+								lastI42 = millis();
+								Serial.print("I42 ");
+								for (uint8_t i = 0;
+								     i < tlen; i++)
+									Serial.printf(
+										"%02X",
+										rep[i]);
+								Serial.println();
+							}
+						}
+					} else if (ttype == 6 &&
+						   (size_t)(idx + 2) + tlen <=
+							   sizeof(rfrx) &&
+						   tlen >= 1) {
+						// DIAGNOSTIC (beta-update RE): a type-6 HID-report TLV whose report id we
+						// DON'T decode (not 0x45 input, not 0x43/0x44 status). If the new controller
+						// firmware moved input off 0x45, its record shows up here. Log rid + full body,
+						// rate-limited + non-blocking so it can't stall the loop. Remove once pinned.
+						static unsigned long lastUnk = 0;
+						if (Serial.availableForWrite() > 110 &&
+						    millis() - lastUnk >= 200) {
+							lastUnk = millis();
+							Serial.printf("UNK rid=%02X tlen=%u: ",
+								      rfrx[idx + 2], tlen);
+							for (uint8_t i = 0; i < tlen; i++)
+								Serial.printf(
+									"%02X",
+									rfrx[idx + 2 + i]);
+							Serial.println();
+						}
 					}
 					idx += tlen + 2;
 				}

+ 6 - 0
docs/index.html

@@ -65,6 +65,7 @@
   <span id="connState" class="pill dn">disconnected</span>
   <button id="connectBtn" class="primary">Connect</button>
   <span class="spacer"></span>
+  <button id="debugCdc" title="Reboot ONCE with the CDC serial console enabled (WebUSB goes away for that boot); auto-reverts on the next boot">Debug CDC</button>
   <button id="dfuSerial" title="Reboot into serial DFU (adafruit-nrfutil), then replug">Serial DFU</button>
   <button id="dfuUf2" title="Reboot into the UF2 mass-storage bootloader; drop a .uf2 on the drive">UF2 DFU</button>
   <button id="factoryErase" class="danger" title="Wipe ALL bonds + settings and reboot to defaults. You must re-pair. Cannot be undone.">⚠ Factory erase</button>
@@ -857,6 +858,11 @@ $("#hangCsv").onclick=()=>{
 };
 $("#hapClear").onclick=async()=>{ if(dev){ await send([0x07]); log("haptic re-init sent (clear stuck buzz)"); } };
 $("#ctlrOff").onclick=async()=>{ if(dev){ await send([0x08]); log("controller power-off attempt sent — watch link status"); } };
+$("#debugCdc").onclick=async()=>{
+  if(!dev){ log("not connected"); return; }
+  if(!confirm("Reboot with the CDC serial console enabled?\n\nThe puck reboots and comes back with a serial port (115200 baud) instead of WebUSB — this panel will disconnect and NOT reconnect until the next reboot.\n\nConnect a serial monitor to capture logs, then replug (or reboot) to return to normal WebUSB mode. The debug console auto-reverts after one boot.")) return;
+  await send([0x02,20,1]); log("debug-CDC reboot sent — device disconnecting; reconnect a serial monitor at 115200 baud");
+};
 $("#dfuSerial").onclick=async()=>{
   if(!dev){ log("not connected"); return; }
   if(!confirm("Reboot into serial DFU?\n\nThe puck will disconnect immediately. Flash with adafruit-nrfutil, then replug.")) return;