소스 검색

Allow emulating a Steam Machine's receiver instead of a Puck (#242)

* Allow emulating Steam Machine internal receiver

* Fix

* Fix clang-format

* Fix comment
Florian Bach 1 개월 전
부모
커밋
17e89e2f49
7개의 변경된 파일과 78개의 추가작업 그리고 25개의 파일을 삭제
  1. 11 3
      OpenPuck/config.cpp
  2. 2 0
      OpenPuck/config.h
  3. 9 1
      OpenPuck/identity.cpp
  4. 3 2
      OpenPuck/identity.h
  5. 17 4
      OpenPuck/puck_hid.cpp
  6. 28 15
      OpenPuck/webusb_config.cpp
  7. 8 0
      docs/index.html

+ 11 - 3
OpenPuck/config.cpp

@@ -20,6 +20,8 @@ uint8_t g_chordDpad[4] = { MODE_PS3, MODE_DS4_GAME, MODE_PS5_GAME,
 bool g_persistMode = false;
 uint8_t g_bootMode = 0xFF;
 
+bool g_isMachineInternal = false;
+
 bool g_debugCdcThisBoot = false;
 
 // persisted one-shot arm, stored in Cfg.rsvd0 (1 = keep CDC for the next boot)
@@ -96,8 +98,8 @@ struct Cfg {
 	// RUMBLE_SCALE_PCT default. This revives the byte the removed rumble-strength slider used, so the
 	// on-flash layout is unchanged and an existing cfg.bin still loads.
 	// rxWin10: legacy RF tunable slot (window now fixed; ignored). lizKeep: the id9=0 hold enable (see
-	// haptics.h LIZKEEP_MS). rsvd2 used to be landAll87, this is now ignored.
-	uint8_t rxWin10, lizKeep, rsvd2;
+	// haptics.h LIZKEEP_MS).
+	uint8_t rxWin10, lizKeep, isMachineInternal;
 	TypeCfg type[ET_COUNT]; // per-emulated-type back/qam/abSwap/padHaptics
 	// TAIL (appended after CFG_MAGIC 0xCF shipped): back4+D-pad mode assignments. New tail fields go HERE, at
 	// the end, and loadCfg accepts a short file so an upgrade keeps every existing setting -- see CFG_LEN_MIN.
@@ -130,7 +132,7 @@ void saveCfg()
 		  (uint8_t)(g_rumbleScale / 2), // host-rumble strength, pct/2
 		  (uint8_t)(g_rxWin / 10),
 		  g_lizKeep,
-		  0, //rsvd2, used to be g_landAll87
+		  (uint8_t)(g_isMachineInternal ? 0xEE : 0),
 		  {},
 		  { g_chordDpad[0], g_chordDpad[1], g_chordDpad[2],
 		    g_chordDpad[3] },
@@ -215,6 +217,12 @@ void loadCfg()
 			// through -> keep the on default)
 			if (c.lizKeep <= 1)
 				g_lizKeep = c.lizKeep;
+
+			// This variable previously used to have values 0/1.
+			// Use a seperate trigger value 0xEE if we want to emulate a Steam Machine's
+			// internal receiver.
+			g_isMachineInternal = (c.isMachineInternal == 0xEE);
+
 			// host-rumble strength (pct/2; 0 = never set, or a cfg.bin from before this
 			// field was revived -> keep the RUMBLE_SCALE_PCT default)
 			if (c.rumbScale2) {

+ 2 - 0
OpenPuck/config.h

@@ -134,6 +134,8 @@ extern bool g_persistMode;
 // one-shot: boot into this mode once then clear (!persistMode + explicit switch)
 extern uint8_t g_bootMode;
 
+extern bool g_isMachineInternal;
+
 // One-shot debug CDC. Puck mode normally DROPS the CDC serial console to free the USB endpoint its wake-mouse
 // interface needs (to wake a sleeping Windows host). Arming this keeps CDC for the NEXT boot only -- dropping
 // the wake mouse that boot -- to attach the serial debugger; the boot after reverts automatically.

+ 9 - 1
OpenPuck/identity.cpp

@@ -8,7 +8,7 @@ char g_board[16];
 char g_usbSerial[18];
 
 // clang-format off
-const uint8_t ATTR83[25] = { // 0x83 attributes
+const uint8_t ATTR83_PUCK[25] = { // 0x83 attributes
 	// 1 byte tag, 4 byte little-endian value.
 	0x01,  0x04, 0x13, 0x00, 0x00, 		// product_id = 0x1304
 	0x02,  0x00, 0x00, 0x00, 0x00, 		// capabilities = 0 (bitfield)
@@ -16,6 +16,14 @@ const uint8_t ATTR83[25] = { // 0x83 attributes
 	0x04,  0x59, 0x83, 0x62, 0x6A,      // build_timestamp = 0x6A628359 todo: keep updated
 	0x09,  0x47, 0x00, 0x00, 0x00       // hw_id = 0x47
 };
+const uint8_t ATTR83_MACHINE[25] = { // 0x83 attributes
+	// 1 byte tag, 4 byte little-endian value.
+	0x01,  0x04, 0x13, 0x00, 0x00, 		// product_id = 0x1304. (same as the Puck, interestingly!)
+	0x02,  0x00, 0x00, 0x00, 0x00, 		// capabilities = 0 (bitfield)
+	0x0A,  0x01, 0x97, 0xCC, 0x68,      // boot_build_timestamp = 0x68CC9701
+	0x04,  0x59, 0x83, 0x62, 0x6A,      // build_timestamp = 0x6A628359 todo: keep updated
+	0x09,  0x44, 0x00, 0x00, 0x00       // hw_id = 0x44
+};
 // clang-format on
 
 void genSerial()

+ 3 - 2
OpenPuck/identity.h

@@ -10,7 +10,8 @@ extern char g_board[16]; // "MXB99602xxxxx"
 extern char g_usbSerial
 	[18]; // per-mode USB serial (Steam uses g_unit; others get a suffix)
 
-// 0x83 attributes, returned by the puck feature command channel.
-extern const uint8_t ATTR83[25];
+// 0x83 attributes, returned by the feature command channel.
+extern const uint8_t ATTR83_MACHINE[25];
+extern const uint8_t ATTR83_PUCK[25];
 
 void genSerial();

+ 17 - 4
OpenPuck/puck_hid.cpp

@@ -436,9 +436,16 @@ static void handleSet(int slot, uint8_t rid, hid_report_type_t type,
 	switch (cmd) {
 	case IBEX_CMD_GET_ATTRIBUTES_VALUES: // 0x83
 		S.resp[0] = IBEX_CMD_GET_ATTRIBUTES_VALUES;
-		S.resp[1] = sizeof ATTR83;
-		memcpy(S.resp + 2, ATTR83, sizeof ATTR83);
-		S.resp_len = 63;
+		if (g_isMachineInternal) {
+			S.resp[1] = sizeof ATTR83_MACHINE;
+			memcpy(S.resp + 2, ATTR83_MACHINE,
+			       sizeof ATTR83_MACHINE);
+			S.resp_len = 63;
+		} else {
+			S.resp[1] = sizeof ATTR83_PUCK;
+			memcpy(S.resp + 2, ATTR83_PUCK, sizeof ATTR83_PUCK);
+			S.resp_len = 63;
+		}
 		break;
 	case IBEX_CMD_GET_STRING_ATTRIBUTE: { // 0xAE
 		uint8_t idx = pln > 0 ? pl[0] : 1;
@@ -658,7 +665,13 @@ static setcb_t SETCB[NSLOT] = { setcb0, setcb1, setcb2, setcb3 };
 // ===================== IController =====================
 void SteamPuckController::begin()
 {
-	USBDevice.setID(0x28DE, 0x1304);
+	if (g_isMachineInternal) {
+		// Emulating a Steam Machine's internal receiver.
+		USBDevice.setID(0x28DE, 0x1305);
+	} else {
+		// Emulating a Steam Controller Puck.
+		USBDevice.setID(0x28DE, 0x1304);
+	}
 	USBDevice.setVersion(0x0201); // bcdUSB 2.01
 	USBDevice.setDeviceVersion(2);
 

+ 28 - 15
OpenPuck/webusb_config.cpp

@@ -125,16 +125,29 @@ static void webusbSendBlob()
 	p[0] = 0xA5;
 	p[1] = WB_PAYLEN;
 
-	// protocol version (21 = +rumble style (field 39, blob p[195]) and the REVIVED rumble-strength field 22
-	// at blob p[53], now carrying percent/2; 20 = +per-type trackpad->stick mapping (fields 80..87, blob
-	// p[187..194]); 19 = +Switch Pro legacy-gyro select (field 38, blob p[186]); the Switch report-rate and
-	// gyro-scale settings (fields 23/24, blob p[54..55]) are GONE -- those bytes read 0; 18 = +configurable back4+D-pad chords (fields 34..37, blob p[182..185]);
-	// 17 = per-type rumble field (TypeCfg k=8), per-type stride 8->9; 16 = +configurable
-	// lizard-map ops 0x11..0x15 / 0xAA frame, payload unchanged -- the panel MUST see >=16 before it dares
+	// clang-format off
+	// protocol version
+	// (21 = +rumble style (field 39, blob p[195]) and the REVIVED rumble-strength field 22 at blob p[53], 
+	// now carrying percent/2; 
+	// 20 = +per-type trackpad->stick mapping (fields 80..87, blob p[187..194]); 
+	// 19 = +Switch Pro legacy-gyro select (field 38, blob p[186]); the Switch report-rate and
+	// gyro-scale settings (fields 23/24, blob p[54..55]) are GONE -- those bytes read 0; 
+	// 18 = +configurable back4+D-pad chords (fields 34..37, blob p[182..185]);
+	// 17 = per-type rumble field (TypeCfg k=8), per-type stride 8->9; 
+	// 16 = +configurable lizard-map ops 0x11..0x15 / 0xAA frame, payload unchanged -- the panel MUST see >=16 before it dares
 	// send 0x11, or a blocking readLizard() would hang forever against a firmware that silently drops the
-	// unknown op; 15 = +staged firmware-update ops 0x20..0x24; 14 = +landAll87 toggle; 13 = +per-slot link
-	// stats; 12 = +relay rate + clock fingerprint; 11 = +reset cause; 10 = +ledBright per type; 9 = +per-type
-	// cfg; 8 = +per-slot link status; 7 = +raw accel; 6 = +swPro120/gyroScale)
+	// unknown op; 
+	// 15 = +staged firmware-update ops 0x20..0x24; 
+	// 14 = +landAll87 toggle; 
+	// 13 = +per-slot link stats; 
+	// 12 = +relay rate + clock fingerprint; 
+	// 11 = +reset cause; 
+	// 10 = +ledBright per type; 
+	// 9 = +per-type cfg; 
+	// 8 = +per-slot link status; 
+	// 7 = +raw accel; 
+	// 6 = +swPro120/gyroScale)
+	// clang-format on
 	p[2] = 21;
 	p[3] = g_usbMode;
 	p[4] = (uint8_t)g_mDiv;
@@ -301,8 +314,8 @@ static void webusbSendBlob()
 		q[7] = g_slotNoRxps[s];
 		q[8] = g_slotRelayps[s];
 	}
-	// v14/v17: verbatim-0x87-relay experiment toggle (panel reflects + toggles it)
-	p[181] = 0; // used to be g_landAll87
+	// v14/v17: verbatim-0x87-relay (g_landAll87), later repurposed for Machine toggle
+	p[181] = (uint8_t)(g_isMachineInternal ? 0xEE : 0);
 	// v18: back4+D-pad mode assignments (panel renders these as selects next to the B/X/Y ones)
 	p[182] = g_chordDpad[CHD_LEFT];
 	p[183] = g_chordDpad[CHD_UP];
@@ -310,13 +323,13 @@ static void webusbSendBlob()
 	p[185] = g_chordDpad[CHD_DOWN];
 	// v19: Switch Pro gyro mapping (0 = corrected/default, 1 = legacy pre-#189 raw axes)
 	p[186] = g_swGyroLegacy;
-	// v21: host-rumble style (RUMBLE_STYLE_* -- see haptics.h)
-	p[195] = g_rumbleStyle;
 	// v20: per-type trackpad->stick mapping, {left pad, right pad} per emulated type
 	for (int et = 0; et < ET_COUNT; et++) {
 		p[187 + et * 2] = g_padStickCfg[et][0];
 		p[188 + et * 2] = g_padStickCfg[et][1];
 	}
+	// v21: host-rumble style (RUMBLE_STYLE_* -- see haptics.h)
+	p[195] = g_rumbleStyle;
 	// CRITICAL: usb_web.write() SPINS (`while (remain && _connected) yield();`) until the IN FIFO drains or the
 	// panel disconnects. If the panel holds the WebUSB interface open but stops reading its IN endpoint -- a
 	// backgrounded tab, or the host briefly not servicing transferIn under load -- the FIFO never empties and
@@ -1086,9 +1099,9 @@ void webusbPoll()
 					// (field 25, poll RX window, removed -- g_rxWin is now FIXED/not configurable)
 					// (fields 27/28, post-connect haptic block, removed -- permanently disabled)
 
-				// Used to be experimental g_landAll87. Persisted; blob p[181] reflects state. Now ignored.
+				// Used to be g_landAll87, now g_isMachineInternal. Persisted to blob p[181].
 				case 29:
-					// g_landAll87 = v ? 1 : 0;
+					g_isMachineInternal = v ? 0xEE : 0;
 					break;
 				}
 				if (persist)

+ 8 - 0
docs/index.html

@@ -258,6 +258,9 @@
       <div class="row" style="margin:10px 0 0"><button id="ctlrOff">Turn off controller</button>
         <span class="note" style="flex:1">Fires the controller power-off (0x9F "off!", x3) — the same path Steam's "turn off controller" and host-suspend use. Watch the link status to see it disconnect.</span>
       </div>
+      <div class="row" style="margin:10px 0 0"><button id="isMachineInternal">off</button>
+        <span class="note" style="flex:1"><strong>Emulate Steam Machine.</strong> If this setting is enabled, OpenPuck will emulate a Steam Machine's internal receiver instead of a Steam Controller Puck.</span>
+      </div>
       <div class="row debugonly" style="margin:10px 0 0;border-top:1px solid #272d3d;padding-top:14px"><button id="stabBtn">Test stability</button>
         <span class="v" id="stabStatus" style="flex:0 0 auto;min-width:120px">—</span>
         <span class="note" style="flex:1">Buzzes the controllers every 10 s (keeps them awake) and times how long the puck stays up. On a reset it records the uptime and shows it by <em>Last reset</em>; it auto-resumes after the puck reconnects. The count is kept in this tab (a page refresh clears it).</span>
@@ -1118,6 +1121,7 @@ function applyBlob(p){
   checkUpdateNotice(); // needs BOTH the installed version (this blob) and relCache (fetched on connect)
   setSlider("mDiv",mDiv); setSlider("mFric",mFric);
   $("#persistMode").textContent = persistMode? "on":"off"; $("#persistMode").classList.toggle("active",!!persistMode);
+  { const la=(p.length>179?p[179] === 0xEE:false); $("#isMachineInternal").textContent = la?"on":"off"; $("#isMachineInternal").classList.toggle("active",!!la); }
   // per-type button config (blob v10/v17): 4 types x 9 bytes. readBlob() strips the 2-byte [0xA5][len] header, so
   // the firmware's buffer index 75 lands at JS index 73 here.
   if(p.length>=73+TYPE_DEFS.length*9){
@@ -1265,6 +1269,7 @@ function buildBackup(p, bp){
   // those settings existed carry rumble/swProRate/swGyro10 keys instead — now-removed settings, ignored.)
   if(p[0]>=18 && p.length>183) cfg.chordD=[p[180],p[181],p[182],p[183]];
   if(p[0]>=19 && p.length>184) cfg.swGyroLegacy=p[184];
+  if(p[0]>=21 && p.length>179) cfg.emulateSteamMachine=p[179];
   // version 2: include the lizard map so it survives a backup/restore cycle.
   // Guard on !lizardBusy to avoid a race with the one-shot lazy load (lizardExchange sets
   // lizardBusy while the initial 0x11 fetch is in flight); if the load is still pending the
@@ -1347,6 +1352,8 @@ async function importBackup(file){
     // Switch Pro gyro mapping (v19+ backups only). Backups also carry rumble/swProRate/swGyro10 from the
     // removed settings; those fields no longer exist on the puck, so they are not replayed.
     if(c.swGyroLegacy!==undefined) await sf(38, c.swGyroLegacy?1:0);
+    if(c.emulateSteamMachine!==undefined) await sf(29, c.emulateSteamMachine==0xEE?1:0);
+    
     // c.types is absent in some very old backups created before per-type paddle/haptic config
     // was added to the panel export; guard so a missing field never crashes the import mid-flight.
     if(typeN){
@@ -2297,6 +2304,7 @@ $("#wipeBoard").onclick=async()=>{
   log("FULL BOARD WIPE sent — the board is erasing firmware + all data (~15–20 s), then reboots as a blank UF2 drive. Flash OpenPuck (.uf2) to restore it.");
 };
 $("#persistMode").onclick=()=>{ const on=$("#persistMode").classList.contains("active"); setField(16, on?0:1); };
+$("#isMachineInternal").onclick=async()=>{ const on=$("#isMachineInternal").classList.contains("active"); await setField(29, on?0:1); };
 // Connection tuning: post-connect haptic block on (field 27) + seconds (field 28). (poll RX window is fixed.)
 for(const id of ["mDiv","mFric"]){
   const field={mDiv:1,mFric:2}[id];