Преглед изворни кода

partway through code reunification

Jari пре 3 месеци
родитељ
комит
f07f8dc473

+ 1 - 12
OpenPuck/mode_hidgyro.cpp

@@ -9,21 +9,10 @@
 #include <Adafruit_TinyUSB.h>
 #include <Arduino.h>
 #include <string.h>
+#include "src/common/hid_reports.h"
 
 HidGyroController g_hidGyroCtl;
 
-static const uint8_t GYRO_HID_DESC[] = {
-	0x05, 0x01, 0x09, 0x05, 0xA1, 0x01, 0x85, 0x01, 0x09, 0x30, 0x09,
-	0x31, 0x09, 0x32, 0x09, 0x35, 0x15, 0x00, 0x25, 0x07, 0x75, 0x04,
-	0x95, 0x01, 0x81, 0x42, 0x65, 0x00, 0x05, 0x09, 0x19, 0x01, 0x29,
-	0x0E, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x0E, 0x81, 0x02,
-	0x06, 0x00, 0xFF, 0x09, 0x20, 0x75, 0x06, 0x95, 0x01, 0x15, 0x00,
-	0x25, 0x7F, 0x81, 0x02, 0x05, 0x01, 0x09, 0x33, 0x09, 0x34, 0x15,
-	0x00, 0x26, 0xFF, 0x00, 0x75, 0x08, 0x95, 0x02, 0x81, 0x02, 0x06,
-	0x00, 0xFF, 0x09, 0x21, 0x95, 0x36, 0x81, 0x02, 0x85, 0x05, 0x09,
-	0x22, 0x95, 0x1F, 0x91, 0x02, 0x85, 0x04, 0x09, 0x23, 0x95, 0x24,
-	0xB1, 0x02, 0x85, 0x02, 0x09, 0x24, 0x95, 0x24, 0xB1, 0x02, 0xC0
-};
 #define DS4_TOUCH_H 942
 #define DS4_STATUS_USB 0x1B // cable + level 11 (full)
 static unsigned long g_gyroLastMs[NSLOT] = { 0 };

+ 1 - 63
OpenPuck/mode_ps3.cpp

@@ -8,6 +8,7 @@
 #include <Adafruit_TinyUSB.h>
 #include <Arduino.h>
 #include <string.h>
+#include "src/common/hid_reports.h"
 
 Ps3Controller g_ps3Ctl;
 
@@ -18,21 +19,6 @@ Ps3Controller g_ps3Ctl;
 // descriptor (declaring input 0x01, output 0x01, and feature reports 0x01/0x02/0xEE/0xEF) keeps both the
 // console and PC HID stacks happy. Do not "tidy" it: this is the real firmware's (famously odd) descriptor.
 // ---------------------------------------------------------------------------------------------------------
-static const uint8_t DS3_HID_DESC[148] = {
-	0x05, 0x01, 0x09, 0x04, 0xA1, 0x01, 0xA1, 0x02, 0x85, 0x01, 0x75, 0x08,
-	0x95, 0x01, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x81, 0x03, 0x75, 0x01, 0x95,
-	0x13, 0x15, 0x00, 0x25, 0x01, 0x35, 0x00, 0x45, 0x01, 0x05, 0x09, 0x19,
-	0x01, 0x29, 0x13, 0x81, 0x02, 0x75, 0x01, 0x95, 0x0D, 0x06, 0x00, 0xFF,
-	0x81, 0x03, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x05, 0x01, 0x09, 0x01, 0xA1,
-	0x00, 0x75, 0x08, 0x95, 0x04, 0x35, 0x00, 0x46, 0xFF, 0x00, 0x09, 0x30,
-	0x09, 0x31, 0x09, 0x32, 0x09, 0x35, 0x81, 0x02, 0xC0, 0x05, 0x01, 0x75,
-	0x08, 0x95, 0x27, 0x09, 0x01, 0x81, 0x02, 0x75, 0x08, 0x95, 0x30, 0x09,
-	0x01, 0x91, 0x02, 0x75, 0x08, 0x95, 0x30, 0x09, 0x01, 0xB1, 0x02, 0xC0,
-	0xA1, 0x02, 0x85, 0x02, 0x75, 0x08, 0x95, 0x30, 0x09, 0x01, 0xB1, 0x02,
-	0xC0, 0xA1, 0x02, 0x85, 0xEE, 0x75, 0x08, 0x95, 0x30, 0x09, 0x01, 0xB1,
-	0x02, 0xC0, 0xA1, 0x02, 0x85, 0xEF, 0x75, 0x08, 0x95, 0x30, 0x09, 0x01,
-	0xB1, 0x02, 0xC0, 0xC0
-};
 
 // ---------------------------------------------------------------------------------------------------------
 // Magic GET_REPORT(Feature) responses. Lifted verbatim from GIMX-firmwares EMUPS3 (matlo) -- a LUFA-based DS3
@@ -43,58 +29,10 @@ static const uint8_t DS3_HID_DESC[148] = {
 //   GET 0x01 -> GET 0xF2 -> {SET 0xEF / GET 0xEF}x2 -> GET 0xF8 -> SET output 0x01 -> SET 0xF4(enable)
 // then input report 0x01 streams. (GIMX does not even handle 0xF4 and streams unconditionally; so do we.)
 // ---------------------------------------------------------------------------------------------------------
-static const uint8_t report_01[64] = {
-	0x00, 0x01, 0x04, 0x00, 0x07, 0x0c, 0x01, 0x02, 0x18, 0x18, 0x18,
-	0x18, 0x09, 0x0a, 0x10, 0x11, 0x12, 0x13, 0x00, 0x00, 0x00, 0x00,
-	0x04, 0x00, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x04, 0x04,
-	0x04, 0x04, 0x00, 0x00, 0x04, 0x00, 0x01, 0x02, 0x07, 0x00, 0x17,
-	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
-};
 // 0xF2 device info; bytes [4..9] = the controller's Bluetooth MAC (overlaid from g_ds3Mac at runtime).
-static const uint8_t report_f2[64] = {
-	0xf2, 0xff, 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x00,
-	0x03, 0x50, 0x81, 0xd8, 0x01, 0x8a, 0x13, 0x00, 0x00, 0x00, 0x00,
-	0x04, 0x00, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x04, 0x04,
-	0x04, 0x04, 0x00, 0x00, 0x04, 0x00, 0x01, 0x02, 0x07, 0x00, 0x17,
-	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
-};
 // 0xF5 host-pairing info; bytes [2..7] = master (host) BT MAC, overlaid once learned from a SET 0xF5.
-static const uint8_t report_f5[64] = {
-	0x01, 0x00, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xff, 0xf7, 0x00,
-	0x03, 0x50, 0x81, 0xd8, 0x01, 0x8a, 0x13, 0x00, 0x00, 0x00, 0x00,
-	0x04, 0x00, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x04, 0x04,
-	0x04, 0x04, 0x00, 0x00, 0x04, 0x00, 0x01, 0x02, 0x07, 0x00, 0x17,
-	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
-};
 // 0xEF / 0xF8 calibration; byte [7] echoes whatever the PS3 last wrote in SET 0xEF byte[6] (the "do you
 // remember" check the console performs during the 0xEF dance).
-static const uint8_t report_ef[64] = {
-	0x00, 0xef, 0x04, 0x00, 0x07, 0x03, 0x01, 0xb0, 0x00, 0x00, 0x00,
-	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x6b, 0x02, 0x68, 0x00,
-	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
-};
-static const uint8_t report_f8[64] = {
-	0x00, 0x01, 0x00, 0x00, 0x07, 0x03, 0x01, 0xb0, 0x00, 0x00, 0x00,
-	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x6b, 0x02, 0x68, 0x00,
-	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
-};
-static const uint8_t report_f7[64] = {
-	0x01, 0x04, 0xc4, 0x02, 0xd6, 0x01, 0xee, 0xff, 0x14, 0x13, 0x01,
-	0x02, 0xc4, 0x01, 0xd6, 0x00, 0x00, 0x02, 0x02, 0x02, 0x00, 0x03,
-	0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x62, 0x01, 0x02, 0x01, 0x5e,
-	0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
-};
 
 // One HID slot -- a PS3 expects exactly one Sixaxis per USB port.
 static Adafruit_USBD_HID g_ds3;

+ 1 - 25
OpenPuck/mode_ps5.cpp

@@ -9,34 +9,10 @@
 #include <Adafruit_TinyUSB.h>
 #include <Arduino.h>
 #include <string.h>
+#include "src/common/hid_reports.h"
 
 Ps5Controller g_ps5Ctl;
 
-static const uint8_t PS5_HID_DESC[] = {
-	0x05, 0x01, 0x09, 0x05, 0xA1, 0x01, 0x85, 0x01, 0x09, 0x30, 0x09, 0x31,
-	0x09, 0x32, 0x09, 0x35, 0x09, 0x33, 0x09, 0x34, 0x15, 0x00, 0x26, 0xFF,
-	0x00, 0x75, 0x08, 0x95, 0x06, 0x81, 0x02, 0x06, 0x00, 0xFF, 0x09, 0x20,
-	0x95, 0x01, 0x81, 0x02, 0x05, 0x01, 0x09, 0x39, 0x15, 0x00, 0x25, 0x07,
-	0x35, 0x00, 0x46, 0x3B, 0x01, 0x65, 0x14, 0x75, 0x04, 0x95, 0x01, 0x81,
-	0x42, 0x65, 0x00, 0x05, 0x09, 0x19, 0x01, 0x29, 0x0F, 0x15, 0x00, 0x25,
-	0x01, 0x75, 0x01, 0x95, 0x0F, 0x81, 0x02, 0x06, 0x00, 0xFF, 0x09, 0x21,
-	0x95, 0x0D, 0x81, 0x02, 0x06, 0x00, 0xFF, 0x09, 0x22, 0x15, 0x00, 0x26,
-	0xFF, 0x00, 0x75, 0x08, 0x95, 0x34, 0x81, 0x02, 0x85, 0x02, 0x09, 0x23,
-	0x95, 0x2F, 0x91, 0x02, 0x85, 0x05, 0x09, 0x33, 0x95, 0x28, 0xB1, 0x02,
-	0x85, 0x08, 0x09, 0x34, 0x95, 0x2F, 0xB1, 0x02, 0x85, 0x09, 0x09, 0x24,
-	0x95, 0x13, 0xB1, 0x02, 0x85, 0x0A, 0x09, 0x25, 0x95, 0x1A, 0xB1, 0x02,
-	0x85, 0x20, 0x09, 0x26, 0x95, 0x3F, 0xB1, 0x02, 0x85, 0x21, 0x09, 0x27,
-	0x95, 0x04, 0xB1, 0x02, 0x85, 0x22, 0x09, 0x40, 0x95, 0x3F, 0xB1, 0x02,
-	0x85, 0x80, 0x09, 0x28, 0x95, 0x3F, 0xB1, 0x02, 0x85, 0x81, 0x09, 0x29,
-	0x95, 0x3F, 0xB1, 0x02, 0x85, 0x82, 0x09, 0x2A, 0x95, 0x09, 0xB1, 0x02,
-	0x85, 0x83, 0x09, 0x2B, 0x95, 0x3F, 0xB1, 0x02, 0x85, 0x84, 0x09, 0x2C,
-	0x95, 0x3F, 0xB1, 0x02, 0x85, 0x85, 0x09, 0x2D, 0x95, 0x02, 0xB1, 0x02,
-	0x85, 0xA0, 0x09, 0x2E, 0x95, 0x01, 0xB1, 0x02, 0x85, 0xE0, 0x09, 0x2F,
-	0x95, 0x3F, 0xB1, 0x02, 0x85, 0xF0, 0x09, 0x30, 0x95, 0x3F, 0xB1, 0x02,
-	0x85, 0xF1, 0x09, 0x31, 0x95, 0x3F, 0xB1, 0x02, 0x85, 0xF2, 0x09, 0x32,
-	0x95, 0x0F, 0xB1, 0x02, 0x85, 0xF4, 0x09, 0x35, 0x95, 0x3F, 0xB1, 0x02,
-	0x85, 0xF5, 0x09, 0x36, 0x95, 0x03, 0xB1, 0x02, 0xC0
-};
 #define PS5_TOUCH_H 1080
 #define PS5_STATUS_USB 0x1A // charging + level 10 (~100%)
 static unsigned long g_ps5LastMs[NSLOT] = { 0 };

+ 1 - 18
OpenPuck/mode_switch_hori.cpp

@@ -7,28 +7,11 @@
 #include "usb_tx.h"
 #include <Adafruit_TinyUSB.h>
 #include <Arduino.h>
+#include "src/common/hid_reports.h"
 
 SwitchHoriController g_switchHori;
 
 // 8-byte input report: [btn_lo][btn_hi][hat][LX][LY][RX][RY][vendor], sticks uint8 center 0x80.
-static const uint8_t SWITCH_HID_DESC[] = {
-
-	// Usage Page (Generic Desktop), Usage (Game Pad), Collection (App)
-	0x05, 0x01, 0x09, 0x05, 0xA1, 0x01, 0x15, 0x00, 0x25, 0x01, 0x35, 0x00,
-	0x45, 0x01, 0x75, 0x01, 0x95, 0x10, // 16 x 1-bit buttons
-	0x05, 0x09, 0x19, 0x01, 0x29, 0x10, 0x81, 0x02, 0x05, 0x01, 0x25, 0x07,
-	0x46, 0x3B, 0x01, 0x75, 0x04, 0x95, 0x01, 0x65, 0x14, 0x09, 0x39, 0x81,
-	0x42, // hat switch (4 bits)
-	0x65, 0x00, 0x95, 0x01, 0x81, 0x01, // 4-bit padding to a byte
-	0x26, 0xFF, 0x00, 0x46, 0xFF, 0x00, 0x09, 0x30, 0x09, 0x31, 0x09, 0x32,
-	0x09, 0x35, 0x75, 0x08, 0x95, 0x04, 0x81, 0x02, // X,Y,Z,Rz
-
-	// vendor byte (input)
-	0x06, 0x00, 0xFF, 0x09, 0x20, 0x95, 0x01, 0x81, 0x02,
-
-	// vendor (output, 8 bytes; unused)
-	0x0A, 0x21, 0x26, 0x95, 0x08, 0x91, 0x02, 0xC0
-};
 // NSLOT HORIPAD HID instances -- one per bond slot. The Switch console binds each as a separate gamepad.
 static Adafruit_USBD_HID g_switch[NSLOT];
 static unsigned long g_swLastMs[NSLOT] = { 0 };

+ 1 - 37
OpenPuck/mode_switch_pro.cpp

@@ -13,6 +13,7 @@
 #include <Arduino.h>
 #include <string.h>
 #include <math.h>
+#include "src/common/hid_reports.h"
 using namespace Adafruit_LittleFS_Namespace;
 
 SwitchProController g_switchPro;
@@ -71,25 +72,6 @@ static int16_t gscale(int16_t v)
 	return (int16_t)s;
 }
 
-static const uint8_t SWPRO_HID_DESC[] = {
-	0x05, 0x01, 0x15, 0x00, 0x09, 0x04, 0xA1, 0x01, 0x85, 0x30, 0x05, 0x01,
-	0x05, 0x09, 0x19, 0x01, 0x29, 0x0A, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01,
-	0x95, 0x0A, 0x55, 0x00, 0x65, 0x00, 0x81, 0x02, 0x05, 0x09, 0x19, 0x0B,
-	0x29, 0x0E, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x04, 0x81, 0x02,
-	0x75, 0x01, 0x95, 0x02, 0x81, 0x03, 0x0B, 0x01, 0x00, 0x01, 0x00, 0xA1,
-	0x00, 0x0B, 0x30, 0x00, 0x01, 0x00, 0x0B, 0x31, 0x00, 0x01, 0x00, 0x0B,
-	0x32, 0x00, 0x01, 0x00, 0x0B, 0x35, 0x00, 0x01, 0x00, 0x15, 0x00, 0x27,
-	0xFF, 0xFF, 0x00, 0x00, 0x75, 0x10, 0x95, 0x04, 0x81, 0x02, 0xC0, 0x0B,
-	0x39, 0x00, 0x01, 0x00, 0x15, 0x00, 0x25, 0x07, 0x35, 0x00, 0x46, 0x3B,
-	0x01, 0x65, 0x14, 0x75, 0x04, 0x95, 0x01, 0x81, 0x42, 0x05, 0x09, 0x19,
-	0x0F, 0x29, 0x12, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x04, 0x81,
-	0x02, 0x75, 0x08, 0x95, 0x34, 0x81, 0x03, 0x06, 0x00, 0xFF, 0x85, 0x21,
-	0x09, 0x01, 0x75, 0x08, 0x95, 0x3F, 0x81, 0x03, 0x85, 0x81, 0x09, 0x02,
-	0x75, 0x08, 0x95, 0x3F, 0x81, 0x03, 0x85, 0x01, 0x09, 0x03, 0x75, 0x08,
-	0x95, 0x3F, 0x91, 0x83, 0x85, 0x10, 0x09, 0x04, 0x75, 0x08, 0x95, 0x3F,
-	0x91, 0x83, 0x85, 0x80, 0x09, 0x05, 0x75, 0x08, 0x95, 0x3F, 0x91, 0x83,
-	0x85, 0x82, 0x09, 0x06, 0x75, 0x08, 0x95, 0x3F, 0x91, 0x83, 0xC0
-};
 #define JC_BTN_Y (1u << 0)
 #define JC_BTN_X (1u << 1)
 #define JC_BTN_B (1u << 2)
@@ -549,19 +531,6 @@ static void switchProBuild(uint8_t slot, uint8_t out[63])
 }
 // --- Canonical factory SPI dumps the host reads for calibration. Neutral IMU + centered sticks so a fresh
 // "device" calibrates sane; user-cal regions (0x80xx) read 0xFF so the host falls back to these factory blocks.
-static const uint8_t SPI_IMU_CAL[24] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-					 0x00, 0x40, 0x00, 0x40, 0x00, 0x40,
-					 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-					 0x3B, 0x34, 0x3B, 0x34, 0x3B, 0x34 };
-static const uint8_t SPI_PARAMS1[24] = { 0x50, 0xFD, 0x00, 0x00, 0xC6, 0x0F,
-					 0x0F, 0x30, 0x61, 0x96, 0x30, 0xF3,
-					 0xD4, 0x14, 0x54, 0x41, 0x15, 0x54,
-					 0xC7, 0x79, 0x9C, 0x33, 0x36, 0x63 };
-static const uint8_t SPI_PARAMS2[18] = { 0x0F, 0x30, 0x61, 0x96, 0x30, 0xF3,
-					 0xD4, 0x14, 0x54, 0x41, 0x15, 0x54,
-					 0xC7, 0x79, 0x9C, 0x33, 0x36, 0x63 };
-static const uint8_t SPI_COLOR[13] = { 0x32, 0x32, 0x32, 0xE6, 0xE6, 0xE6, 0x32,
-				       0x32, 0x32, 0x32, 0x32, 0x32, 0xFF };
 // built at boot: left[9]+right[9] factory stick calibration (packed 12-bit). Shared across slots -- it's a
 // factory stub the host reads to seed its cal, and we present the same neutral stub for all controllers.
 static uint8_t g_spiStickCal[18];
@@ -592,11 +561,6 @@ static void jcBuildStickCal()
 // Manual-pairing (subcommand 0x01) reply payloads. A real Switch runs this 3-stage BT key exchange over USB so it
 // can register the pad (Steam/hid-nintendo do not). The Switch only validates the shape, not the key contents. Each
 // is the 31-byte reply body following the 0x21 input prefix + ack(0x81) + echoed-subcommand(0x01).
-static const uint8_t BT_PAIR_2[31] = { // type 2: LTK exchange
-	0x02, 0xE5, 0xC8, 0xE4, 0x92, 0x05, 0xFF, 0xC9, 0x8A, 0x7D, 0xEA,
-	0x15, 0xF6, 0x19, 0xBA, 0x82, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00,
-	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
-};
 static const uint8_t BT_PAIR_3[31] = {
 	0x03
 }; // type 3: save pairing (all zero body)

+ 1 - 33
OpenPuck/puck_hid.cpp

@@ -12,6 +12,7 @@
 #include <Adafruit_TinyUSB.h>
 #include <Arduino.h>
 #include <string.h>
+#include "src/common/hid_reports.h"
 
 uint8_t g_fwdNewOnly = 1;
 // Content dedup for the Steam input forward: only forward a 0x45/0x42 report when its body EXCLUDING the
@@ -48,39 +49,6 @@ static inline bool slotPoweringOff(int slot)
 
 // Cloned puck HID report descriptor: mouse(0x40)+keyboard(0x41)+vendor(FF00) with the 63-byte FEATURE
 // command reports on report id 1/2. Each of the 4 interfaces uses this.
-static const uint8_t PUCK_HID_DESC[] = {
-	0x05, 0x01, 0x09, 0x02, 0xA1, 0x01, 0x85, 0x40, 0x09, 0x01, 0xA1, 0x00,
-	0x05, 0x09, 0x19, 0x01, 0x29, 0x02, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01,
-	0x95, 0x02, 0x81, 0x02, 0x75, 0x06, 0x95, 0x01, 0x81, 0x01, 0x05, 0x01,
-	0x09, 0x30, 0x09, 0x31, 0x15, 0x81, 0x25, 0x7F, 0x75, 0x08, 0x95, 0x02,
-	0x81, 0x06, 0x95, 0x01, 0x09, 0x38, 0x81, 0x06, 0x05, 0x0C, 0x0A, 0x38,
-	0x02, 0x95, 0x01, 0x81, 0x06, 0xC0, 0xC0, 0x05, 0x01, 0x09, 0x06, 0xA1,
-	0x01, 0x85, 0x41, 0x05, 0x07, 0x19, 0xE0, 0x29, 0xE7, 0x15, 0x00, 0x25,
-	0x01, 0x75, 0x01, 0x95, 0x08, 0x81, 0x02, 0x81, 0x01, 0x19, 0x00, 0x29,
-	0x65, 0x15, 0x00, 0x25, 0x65, 0x75, 0x08, 0x95, 0x06, 0x81, 0x00, 0xC0,
-	0x06, 0x00, 0xFF, 0x09, 0x01, 0xA1, 0x01, 0x85, 0x42, 0x15, 0x00, 0x26,
-	0xFF, 0x00, 0x75, 0x08, 0x95, 0x35, 0x09, 0x42, 0x81, 0x02, 0x85, 0x44,
-	0x15, 0x00, 0x26, 0xFF, 0x00, 0x75, 0x08, 0x95, 0x05, 0x09, 0x44, 0x81,
-	0x02, 0x85, 0x79, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x75, 0x08, 0x95, 0x01,
-	0x09, 0x79, 0x81, 0x02, 0x85, 0x43, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x75,
-	0x08, 0x95, 0x0E, 0x09, 0x43, 0x81, 0x02, 0x85, 0x7B, 0x15, 0x00, 0x26,
-	0xFF, 0x00, 0x75, 0x08, 0x95, 0x0C, 0x09, 0x7B, 0x81, 0x02, 0x85, 0x45,
-	0x15, 0x00, 0x26, 0xFF, 0x00, 0x75, 0x08, 0x95, 0x2D, 0x09, 0x45, 0x81,
-	0x02, 0x85, 0x80, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x75, 0x08, 0x95, 0x09,
-	0x09, 0x80, 0x91, 0x02, 0x85, 0x81, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x75,
-	0x08, 0x95, 0x07, 0x09, 0x81, 0x91, 0x02, 0x85, 0x82, 0x15, 0x00, 0x26,
-	0xFF, 0x00, 0x75, 0x08, 0x95, 0x03, 0x09, 0x82, 0x91, 0x02, 0x85, 0x83,
-	0x15, 0x00, 0x26, 0xFF, 0x00, 0x75, 0x08, 0x95, 0x09, 0x09, 0x83, 0x91,
-	0x02, 0x85, 0x84, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x75, 0x08, 0x95, 0x08,
-	0x09, 0x84, 0x91, 0x02, 0x85, 0x85, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x75,
-	0x08, 0x95, 0x03, 0x09, 0x85, 0x91, 0x02, 0x85, 0x86, 0x15, 0x00, 0x26,
-	0xFF, 0x00, 0x75, 0x08, 0x95, 0x03, 0x09, 0x86, 0x91, 0x02, 0x85, 0x87,
-	0x15, 0x00, 0x26, 0xFF, 0x00, 0x75, 0x08, 0x95, 0x3F, 0x09, 0x87, 0x91,
-	0x02, 0x85, 0x89, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x75, 0x08, 0x95, 0x3F,
-	0x09, 0x89, 0x91, 0x02, 0x85, 0x88, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x75,
-	0x08, 0x95, 0x3F, 0x09, 0x88, 0x91, 0x02, 0x85, 0x01, 0x95, 0x3F, 0x09,
-	0x01, 0xB1, 0x02, 0x85, 0x02, 0x95, 0x3F, 0x09, 0x01, 0xB1, 0x02, 0xC0
-};
 
 // Pure MODE_LIZARD only: same puck/lizard descriptor plus a tiny Consumer Control report for media keys.
 // MODE_STEAM must keep PUCK_HID_DESC verbatim so Steam's controller/haptics path sees the normal puck shape.

+ 209 - 0
OpenPuck/src/common/hid_reports.c

@@ -0,0 +1,209 @@
+// hid_reports.c — shared HID report descriptors + magic feature/handshake
+// byte arrays, IDENTICAL across OpenPuck and PicoPuck. Single source of truth;
+// both firmwares link this exact file (OpenPuck via its arduino src/ tree,
+// PicoPuck via CMake). Do not fork — edit here and both stay in lockstep.
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+#include "hid_reports.h"
+
+const uint8_t DS3_HID_DESC[148] = {
+	0x05, 0x01, 0x09, 0x04, 0xa1, 0x01, 0xa1, 0x02, 0x85, 0x01, 0x75, 0x08,
+	0x95, 0x01, 0x15, 0x00, 0x26, 0xff, 0x00, 0x81, 0x03, 0x75, 0x01, 0x95,
+	0x13, 0x15, 0x00, 0x25, 0x01, 0x35, 0x00, 0x45, 0x01, 0x05, 0x09, 0x19,
+	0x01, 0x29, 0x13, 0x81, 0x02, 0x75, 0x01, 0x95, 0x0d, 0x06, 0x00, 0xff,
+	0x81, 0x03, 0x15, 0x00, 0x26, 0xff, 0x00, 0x05, 0x01, 0x09, 0x01, 0xa1,
+	0x00, 0x75, 0x08, 0x95, 0x04, 0x35, 0x00, 0x46, 0xff, 0x00, 0x09, 0x30,
+	0x09, 0x31, 0x09, 0x32, 0x09, 0x35, 0x81, 0x02, 0xc0, 0x05, 0x01, 0x75,
+	0x08, 0x95, 0x27, 0x09, 0x01, 0x81, 0x02, 0x75, 0x08, 0x95, 0x30, 0x09,
+	0x01, 0x91, 0x02, 0x75, 0x08, 0x95, 0x30, 0x09, 0x01, 0xb1, 0x02, 0xc0,
+	0xa1, 0x02, 0x85, 0x02, 0x75, 0x08, 0x95, 0x30, 0x09, 0x01, 0xb1, 0x02,
+	0xc0, 0xa1, 0x02, 0x85, 0xee, 0x75, 0x08, 0x95, 0x30, 0x09, 0x01, 0xb1,
+	0x02, 0xc0, 0xa1, 0x02, 0x85, 0xef, 0x75, 0x08, 0x95, 0x30, 0x09, 0x01,
+	0xb1, 0x02, 0xc0, 0xc0,
+};
+
+const uint8_t report_01[64] = {
+	0x00, 0x01, 0x04, 0x00, 0x07, 0x0c, 0x01, 0x02, 0x18, 0x18, 0x18,
+	0x18, 0x09, 0x0a, 0x10, 0x11, 0x12, 0x13, 0x00, 0x00, 0x00, 0x00,
+	0x04, 0x00, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x04, 0x04,
+	0x04, 0x04, 0x00, 0x00, 0x04, 0x00, 0x01, 0x02, 0x07, 0x00, 0x17,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+const uint8_t report_f2[64] = {
+	0xf2, 0xff, 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x00,
+	0x03, 0x50, 0x81, 0xd8, 0x01, 0x8a, 0x13, 0x00, 0x00, 0x00, 0x00,
+	0x04, 0x00, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x04, 0x04,
+	0x04, 0x04, 0x00, 0x00, 0x04, 0x00, 0x01, 0x02, 0x07, 0x00, 0x17,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+const uint8_t report_f5[64] = {
+	0x01, 0x00, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xff, 0xf7, 0x00,
+	0x03, 0x50, 0x81, 0xd8, 0x01, 0x8a, 0x13, 0x00, 0x00, 0x00, 0x00,
+	0x04, 0x00, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x04, 0x04,
+	0x04, 0x04, 0x00, 0x00, 0x04, 0x00, 0x01, 0x02, 0x07, 0x00, 0x17,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+const uint8_t report_ef[64] = {
+	0x00, 0xef, 0x04, 0x00, 0x07, 0x03, 0x01, 0xb0, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x6b, 0x02, 0x68, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+const uint8_t report_f8[64] = {
+	0x00, 0x01, 0x00, 0x00, 0x07, 0x03, 0x01, 0xb0, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x6b, 0x02, 0x68, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+const uint8_t report_f7[64] = {
+	0x01, 0x04, 0xc4, 0x02, 0xd6, 0x01, 0xee, 0xff, 0x14, 0x13, 0x01,
+	0x02, 0xc4, 0x01, 0xd6, 0x00, 0x00, 0x02, 0x02, 0x02, 0x00, 0x03,
+	0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x62, 0x01, 0x02, 0x01, 0x5e,
+	0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+const uint8_t GYRO_HID_DESC[110] = {
+	0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0x85, 0x01, 0x09, 0x30, 0x09,
+	0x31, 0x09, 0x32, 0x09, 0x35, 0x15, 0x00, 0x25, 0x07, 0x75, 0x04,
+	0x95, 0x01, 0x81, 0x42, 0x65, 0x00, 0x05, 0x09, 0x19, 0x01, 0x29,
+	0x0e, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x0e, 0x81, 0x02,
+	0x06, 0x00, 0xff, 0x09, 0x20, 0x75, 0x06, 0x95, 0x01, 0x15, 0x00,
+	0x25, 0x7f, 0x81, 0x02, 0x05, 0x01, 0x09, 0x33, 0x09, 0x34, 0x15,
+	0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x02, 0x81, 0x02, 0x06,
+	0x00, 0xff, 0x09, 0x21, 0x95, 0x36, 0x81, 0x02, 0x85, 0x05, 0x09,
+	0x22, 0x95, 0x1f, 0x91, 0x02, 0x85, 0x04, 0x09, 0x23, 0x95, 0x24,
+	0xb1, 0x02, 0x85, 0x02, 0x09, 0x24, 0x95, 0x24, 0xb1, 0x02, 0xc0,
+};
+
+const uint8_t PS5_HID_DESC[273] = {
+	0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0x85, 0x01, 0x09, 0x30, 0x09, 0x31,
+	0x09, 0x32, 0x09, 0x35, 0x09, 0x33, 0x09, 0x34, 0x15, 0x00, 0x26, 0xff,
+	0x00, 0x75, 0x08, 0x95, 0x06, 0x81, 0x02, 0x06, 0x00, 0xff, 0x09, 0x20,
+	0x95, 0x01, 0x81, 0x02, 0x05, 0x01, 0x09, 0x39, 0x15, 0x00, 0x25, 0x07,
+	0x35, 0x00, 0x46, 0x3b, 0x01, 0x65, 0x14, 0x75, 0x04, 0x95, 0x01, 0x81,
+	0x42, 0x65, 0x00, 0x05, 0x09, 0x19, 0x01, 0x29, 0x0f, 0x15, 0x00, 0x25,
+	0x01, 0x75, 0x01, 0x95, 0x0f, 0x81, 0x02, 0x06, 0x00, 0xff, 0x09, 0x21,
+	0x95, 0x0d, 0x81, 0x02, 0x06, 0x00, 0xff, 0x09, 0x22, 0x15, 0x00, 0x26,
+	0xff, 0x00, 0x75, 0x08, 0x95, 0x34, 0x81, 0x02, 0x85, 0x02, 0x09, 0x23,
+	0x95, 0x2f, 0x91, 0x02, 0x85, 0x05, 0x09, 0x33, 0x95, 0x28, 0xb1, 0x02,
+	0x85, 0x08, 0x09, 0x34, 0x95, 0x2f, 0xb1, 0x02, 0x85, 0x09, 0x09, 0x24,
+	0x95, 0x13, 0xb1, 0x02, 0x85, 0x0a, 0x09, 0x25, 0x95, 0x1a, 0xb1, 0x02,
+	0x85, 0x20, 0x09, 0x26, 0x95, 0x3f, 0xb1, 0x02, 0x85, 0x21, 0x09, 0x27,
+	0x95, 0x04, 0xb1, 0x02, 0x85, 0x22, 0x09, 0x40, 0x95, 0x3f, 0xb1, 0x02,
+	0x85, 0x80, 0x09, 0x28, 0x95, 0x3f, 0xb1, 0x02, 0x85, 0x81, 0x09, 0x29,
+	0x95, 0x3f, 0xb1, 0x02, 0x85, 0x82, 0x09, 0x2a, 0x95, 0x09, 0xb1, 0x02,
+	0x85, 0x83, 0x09, 0x2b, 0x95, 0x3f, 0xb1, 0x02, 0x85, 0x84, 0x09, 0x2c,
+	0x95, 0x3f, 0xb1, 0x02, 0x85, 0x85, 0x09, 0x2d, 0x95, 0x02, 0xb1, 0x02,
+	0x85, 0xa0, 0x09, 0x2e, 0x95, 0x01, 0xb1, 0x02, 0x85, 0xe0, 0x09, 0x2f,
+	0x95, 0x3f, 0xb1, 0x02, 0x85, 0xf0, 0x09, 0x30, 0x95, 0x3f, 0xb1, 0x02,
+	0x85, 0xf1, 0x09, 0x31, 0x95, 0x3f, 0xb1, 0x02, 0x85, 0xf2, 0x09, 0x32,
+	0x95, 0x0f, 0xb1, 0x02, 0x85, 0xf4, 0x09, 0x35, 0x95, 0x3f, 0xb1, 0x02,
+	0x85, 0xf5, 0x09, 0x36, 0x95, 0x03, 0xb1, 0x02, 0xc0,
+};
+
+const uint8_t SWITCH_HID_DESC[86] = {
+	0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0x15, 0x00, 0x25, 0x01, 0x35,
+	0x00, 0x45, 0x01, 0x75, 0x01, 0x95, 0x10, 0x05, 0x09, 0x19, 0x01,
+	0x29, 0x10, 0x81, 0x02, 0x05, 0x01, 0x25, 0x07, 0x46, 0x3b, 0x01,
+	0x75, 0x04, 0x95, 0x01, 0x65, 0x14, 0x09, 0x39, 0x81, 0x42, 0x65,
+	0x00, 0x95, 0x01, 0x81, 0x01, 0x26, 0xff, 0x00, 0x46, 0xff, 0x00,
+	0x09, 0x30, 0x09, 0x31, 0x09, 0x32, 0x09, 0x35, 0x75, 0x08, 0x95,
+	0x04, 0x81, 0x02, 0x06, 0x00, 0xff, 0x09, 0x20, 0x95, 0x01, 0x81,
+	0x02, 0x0a, 0x21, 0x26, 0x95, 0x08, 0x91, 0x02, 0xc0,
+};
+
+const uint8_t SWPRO_HID_DESC[203] = {
+	0x05, 0x01, 0x15, 0x00, 0x09, 0x04, 0xa1, 0x01, 0x85, 0x30, 0x05, 0x01,
+	0x05, 0x09, 0x19, 0x01, 0x29, 0x0a, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01,
+	0x95, 0x0a, 0x55, 0x00, 0x65, 0x00, 0x81, 0x02, 0x05, 0x09, 0x19, 0x0b,
+	0x29, 0x0e, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x04, 0x81, 0x02,
+	0x75, 0x01, 0x95, 0x02, 0x81, 0x03, 0x0b, 0x01, 0x00, 0x01, 0x00, 0xa1,
+	0x00, 0x0b, 0x30, 0x00, 0x01, 0x00, 0x0b, 0x31, 0x00, 0x01, 0x00, 0x0b,
+	0x32, 0x00, 0x01, 0x00, 0x0b, 0x35, 0x00, 0x01, 0x00, 0x15, 0x00, 0x27,
+	0xff, 0xff, 0x00, 0x00, 0x75, 0x10, 0x95, 0x04, 0x81, 0x02, 0xc0, 0x0b,
+	0x39, 0x00, 0x01, 0x00, 0x15, 0x00, 0x25, 0x07, 0x35, 0x00, 0x46, 0x3b,
+	0x01, 0x65, 0x14, 0x75, 0x04, 0x95, 0x01, 0x81, 0x42, 0x05, 0x09, 0x19,
+	0x0f, 0x29, 0x12, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x04, 0x81,
+	0x02, 0x75, 0x08, 0x95, 0x34, 0x81, 0x03, 0x06, 0x00, 0xff, 0x85, 0x21,
+	0x09, 0x01, 0x75, 0x08, 0x95, 0x3f, 0x81, 0x03, 0x85, 0x81, 0x09, 0x02,
+	0x75, 0x08, 0x95, 0x3f, 0x81, 0x03, 0x85, 0x01, 0x09, 0x03, 0x75, 0x08,
+	0x95, 0x3f, 0x91, 0x83, 0x85, 0x10, 0x09, 0x04, 0x75, 0x08, 0x95, 0x3f,
+	0x91, 0x83, 0x85, 0x80, 0x09, 0x05, 0x75, 0x08, 0x95, 0x3f, 0x91, 0x83,
+	0x85, 0x82, 0x09, 0x06, 0x75, 0x08, 0x95, 0x3f, 0x91, 0x83, 0xc0,
+};
+
+const uint8_t SPI_IMU_CAL[24] = {
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3b, 0x34, 0x3b, 0x34, 0x3b, 0x34,
+};
+
+const uint8_t SPI_PARAMS1[24] = {
+	0x50, 0xfd, 0x00, 0x00, 0xc6, 0x0f, 0x0f, 0x30, 0x61, 0x96, 0x30, 0xf3,
+	0xd4, 0x14, 0x54, 0x41, 0x15, 0x54, 0xc7, 0x79, 0x9c, 0x33, 0x36, 0x63,
+};
+
+const uint8_t SPI_PARAMS2[18] = {
+	0x0f, 0x30, 0x61, 0x96, 0x30, 0xf3, 0xd4, 0x14, 0x54,
+	0x41, 0x15, 0x54, 0xc7, 0x79, 0x9c, 0x33, 0x36, 0x63,
+};
+
+const uint8_t SPI_COLOR[13] = {
+	0x32, 0x32, 0x32, 0xe6, 0xe6, 0xe6, 0x32,
+	0x32, 0x32, 0x32, 0x32, 0x32, 0xff,
+};
+
+const uint8_t BT_PAIR_2[31] = {
+	0x02, 0xe5, 0xc8, 0xe4, 0x92, 0x05, 0xff, 0xc9, 0x8a, 0x7d, 0xea,
+	0x15, 0xf6, 0x19, 0xba, 0x82, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+// Valve puck HID report descriptor (mouse 0x40 + keyboard 0x41 + vendor FF00);
+// the device a real puck presents on each of its four control interfaces.
+const uint8_t PUCK_HID_DESC[372] = {
+	0x05, 0x01, 0x09, 0x02, 0xa1, 0x01, 0x85, 0x40, 0x09, 0x01, 0xa1, 0x00,
+	0x05, 0x09, 0x19, 0x01, 0x29, 0x02, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01,
+	0x95, 0x02, 0x81, 0x02, 0x75, 0x06, 0x95, 0x01, 0x81, 0x01, 0x05, 0x01,
+	0x09, 0x30, 0x09, 0x31, 0x15, 0x81, 0x25, 0x7f, 0x75, 0x08, 0x95, 0x02,
+	0x81, 0x06, 0x95, 0x01, 0x09, 0x38, 0x81, 0x06, 0x05, 0x0c, 0x0a, 0x38,
+	0x02, 0x95, 0x01, 0x81, 0x06, 0xc0, 0xc0, 0x05, 0x01, 0x09, 0x06, 0xa1,
+	0x01, 0x85, 0x41, 0x05, 0x07, 0x19, 0xe0, 0x29, 0xe7, 0x15, 0x00, 0x25,
+	0x01, 0x75, 0x01, 0x95, 0x08, 0x81, 0x02, 0x81, 0x01, 0x19, 0x00, 0x29,
+	0x65, 0x15, 0x00, 0x25, 0x65, 0x75, 0x08, 0x95, 0x06, 0x81, 0x00, 0xc0,
+	0x06, 0x00, 0xff, 0x09, 0x01, 0xa1, 0x01, 0x85, 0x42, 0x15, 0x00, 0x26,
+	0xff, 0x00, 0x75, 0x08, 0x95, 0x35, 0x09, 0x42, 0x81, 0x02, 0x85, 0x44,
+	0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x05, 0x09, 0x44, 0x81,
+	0x02, 0x85, 0x79, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x01,
+	0x09, 0x79, 0x81, 0x02, 0x85, 0x43, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75,
+	0x08, 0x95, 0x0e, 0x09, 0x43, 0x81, 0x02, 0x85, 0x7b, 0x15, 0x00, 0x26,
+	0xff, 0x00, 0x75, 0x08, 0x95, 0x0c, 0x09, 0x7b, 0x81, 0x02, 0x85, 0x45,
+	0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x2d, 0x09, 0x45, 0x81,
+	0x02, 0x85, 0x80, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x09,
+	0x09, 0x80, 0x91, 0x02, 0x85, 0x81, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75,
+	0x08, 0x95, 0x07, 0x09, 0x81, 0x91, 0x02, 0x85, 0x82, 0x15, 0x00, 0x26,
+	0xff, 0x00, 0x75, 0x08, 0x95, 0x03, 0x09, 0x82, 0x91, 0x02, 0x85, 0x83,
+	0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x09, 0x09, 0x83, 0x91,
+	0x02, 0x85, 0x84, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x08,
+	0x09, 0x84, 0x91, 0x02, 0x85, 0x85, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75,
+	0x08, 0x95, 0x03, 0x09, 0x85, 0x91, 0x02, 0x85, 0x86, 0x15, 0x00, 0x26,
+	0xff, 0x00, 0x75, 0x08, 0x95, 0x03, 0x09, 0x86, 0x91, 0x02, 0x85, 0x87,
+	0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x3f, 0x09, 0x87, 0x91,
+	0x02, 0x85, 0x89, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x3f,
+	0x09, 0x89, 0x91, 0x02, 0x85, 0x88, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75,
+	0x08, 0x95, 0x3f, 0x09, 0x88, 0x91, 0x02, 0x85, 0x01, 0x95, 0x3f, 0x09,
+	0x01, 0xb1, 0x02, 0x85, 0x02, 0x95, 0x3f, 0x09, 0x01, 0xb1, 0x02, 0xc0,
+};

+ 37 - 0
OpenPuck/src/common/hid_reports.h

@@ -0,0 +1,37 @@
+// hid_reports.h — see hid_reports.c. Sized extern declarations so callers keep
+// using sizeof(NAME) unchanged.
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+#ifndef OPK_COMMON_HID_REPORTS_H
+#define OPK_COMMON_HID_REPORTS_H
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern const uint8_t DS3_HID_DESC[148];
+extern const uint8_t report_01[64];
+extern const uint8_t report_f2[64];
+extern const uint8_t report_f5[64];
+extern const uint8_t report_ef[64];
+extern const uint8_t report_f8[64];
+extern const uint8_t report_f7[64];
+extern const uint8_t GYRO_HID_DESC[110];
+extern const uint8_t PS5_HID_DESC[273];
+extern const uint8_t SWITCH_HID_DESC[86];
+extern const uint8_t SWPRO_HID_DESC[203];
+extern const uint8_t SPI_IMU_CAL[24];
+extern const uint8_t SPI_PARAMS1[24];
+extern const uint8_t SPI_PARAMS2[18];
+extern const uint8_t SPI_COLOR[13];
+extern const uint8_t BT_PAIR_2[31];
+
+extern const uint8_t PUCK_HID_DESC[372];
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // OPK_COMMON_HID_REPORTS_H

+ 51 - 0
OpenPuck/src/common/triton_masks.h

@@ -0,0 +1,51 @@
+// triton_masks.h — the Steam Controller 2 canonical button field (TB_* masks)
+// and the analog-trigger digital threshold. This is the canonicalization
+// contract every controller decodes into and every output personality reads,
+// so it MUST be identical across OpenPuck and PicoPuck — hence one shared file
+// both firmwares include. (The g_in struct + decoders differ per firmware and
+// stay in each project's triton.h.)
+//
+// The 32-bit field is report 0x45 bytes [2..5]. Bits 0..29 are real SC2 bits;
+// bits 30/31 are unused by the controller and reused as virtual PS remap targets
+// (TB_TOUCH/TB_MUTE) settable only via a back-paddle/QAM remap.
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+#ifndef OPK_COMMON_TRITON_MASKS_H
+#define OPK_COMMON_TRITON_MASKS_H
+
+#define TB_A 0x1u
+#define TB_B 0x2u
+#define TB_X 0x4u
+#define TB_Y 0x8u
+#define TB_QAM 0x10u
+#define TB_R3 0x20u
+#define TB_VIEW 0x40u
+#define TB_R4 0x80u
+#define TB_R5 0x100u
+#define TB_RB 0x200u
+#define TB_DDN 0x400u
+#define TB_DRT 0x800u
+#define TB_DLF 0x1000u
+#define TB_DUP 0x2000u
+#define TB_MENU 0x4000u
+#define TB_L3 0x8000u
+#define TB_STEAM 0x10000u
+#define TB_L4 0x20000u
+#define TB_L5 0x40000u
+#define TB_LB 0x80000u
+#define TB_RPADT 0x200000u
+#define TB_RPADC 0x400000u
+#define TB_LPADT 0x2000000u
+#define TB_LPADC 0x4000000u
+#define TB_R2 0x800000u
+#define TB_L2 0x8000000u
+#define TB_TOUCH 0x40000000u // PS touchpad click (remap-only virtual target)
+#define TB_MUTE 0x80000000u // PS5 mute (remap-only virtual target)
+
+// All four back paddles held → mode-switch chord guard.
+#define CHORD_BACK4 (TB_R4 | TB_L4 | TB_R5 | TB_L5)
+
+// Analog-trigger fraction (of 0xFF) at which digital ZL/ZR / L2/R2 trips.
+#define SW_TRIG_ON 40
+
+#endif // OPK_COMMON_TRITON_MASKS_H

+ 3 - 43
OpenPuck/triton.h

@@ -6,49 +6,9 @@
 //   [0]=0x45 [1]=seq [2..5]=buttons u32; analog offsets below are from the buttons low byte (rep[2]).
 #pragma once
 #include <stdint.h>
-
-// ---- Triton button masks (the 32-bit field at rep[2..5]) ----
-#define TB_A 0x1u
-#define TB_B 0x2u
-#define TB_X 0x4u
-#define TB_Y 0x8u
-#define TB_QAM 0x10u
-#define TB_R3 0x20u
-#define TB_VIEW 0x40u
-#define TB_R4 0x80u
-#define TB_R5 0x100u
-#define TB_RB 0x200u
-#define TB_DDN 0x400u
-#define TB_DRT 0x800u
-#define TB_DLF 0x1000u
-#define TB_DUP 0x2000u
-#define TB_MENU 0x4000u
-#define TB_L3 0x8000u
-#define TB_STEAM 0x10000u
-#define TB_L4 0x20000u
-#define TB_L5 0x40000u
-#define TB_LB 0x80000u
-#define TB_RPADT 0x200000u
-#define TB_RPADC 0x400000u
-#define TB_LPADT 0x2000000u
-#define TB_LPADC 0x4000000u
-// Trigger full-pull / click bits (the controller sets these at a full ZL/ZR pull; also the target of a back-
-// paddle / QAM remap to LT/RT). psShouldersByte / the Switch builders already read these as L2/R2 / ZL/ZR.
-#define TB_R2 0x800000u
-#define TB_L2 0x8000000u
-
-// Virtual PS targets, settable ONLY via a back-paddle/QAM remap (psOrBackCode / tritonFromCode), never by the
-// controller itself. They live on bits 30/31 -- the only bits the SC2 0x45 report does NOT use (see PROTOCOL.md
-// §8.1; bits 0..29 are all real). They originally aliased 0x00100000 (right stick touch) and 0x01000000 (left
-// stick touch), so merely RESTING a thumb on a capacitive stick fired the DualSense trackpad-click / mute.
-#define TB_TOUCH 0x40000000u // PS touchpad click
-#define TB_MUTE 0x80000000u // PS5 mute
-
-// all four back paddles held -> mode-switch chord guard
-#define CHORD_BACK4 (TB_R4 | TB_L4 | TB_R5 | TB_L5)
-
-// analog-trigger fraction (of 0xFF) at which digital ZL/ZR (Switch) etc. trip
-#define SW_TRIG_ON 40
+// Triton button masks (TB_*), SW_TRIG_ON and CHORD_BACK4 are the canonicalization
+// contract shared byte-for-byte with PicoPuck — one source of truth.
+#include "src/common/triton_masks.h"
 
 // ---- report 0x45 field decoders (offsets relative to rep[2], the buttons low byte) ----
 static inline int s16off(const uint8_t *r, int off)

+ 3 - 0
PicoPuck/CMakeLists.txt

@@ -65,11 +65,14 @@ target_sources(picopuck PRIVATE
     ${CMAKE_CURRENT_SOURCE_DIR}/src/bt/drv_xinput.c
     ${CMAKE_CURRENT_SOURCE_DIR}/src/bt/drv_sc2.c
     ${CMAKE_CURRENT_SOURCE_DIR}/src/bt/drv_ds4.c
+    # Shared source of truth, compiled by BOTH firmwares (see ../OpenPuck/src/common).
+    ${CMAKE_CURRENT_SOURCE_DIR}/../OpenPuck/src/common/hid_reports.c
 )
 
 target_include_directories(picopuck PRIVATE
     ${CMAKE_CURRENT_SOURCE_DIR}/src
     ${CMAKE_CURRENT_SOURCE_DIR}/src/config
+    ${CMAKE_CURRENT_SOURCE_DIR}/../OpenPuck/src/common
 )
 
 target_compile_definitions(picopuck PRIVATE

+ 5 - 6
PicoPuck/src/puck/gamepad_util.h

@@ -6,16 +6,15 @@
 
 #include <stdint.h>
 #include <stdbool.h>
-#include "puck/triton.h"
+#include "puck/triton.h" // brings in SW_TRIG_ON via the shared triton_masks.h
 
-#define SW_TRIG_ON 40  // analog-trigger fraction (/255) that trips digital ZL/ZR/L2/R2
 #define TOUCH_PAD_W 1920u
 
-uint8_t sw_stick(int16_t v, bool invert);      // int16 center0 → u8 center0x80
-void ps_neutral_calib(uint8_t *buf);           // DS4/DualSense motion calib payload
+uint8_t sw_stick(int16_t v, bool invert); // int16 center0 → u8 center0x80
+void ps_neutral_calib(uint8_t *buf); // DS4/DualSense motion calib payload
 
-uint8_t ps_hat_nibble(uint32_t b);             // d-pad → 8-way hat (8 = neutral)
-uint8_t ps_face_nibble(uint32_t b);            // PlayStation face nibble
+uint8_t ps_hat_nibble(uint32_t b); // d-pad → 8-way hat (8 = neutral)
+uint8_t ps_face_nibble(uint32_t b); // PlayStation face nibble
 uint8_t ps_shoulders_byte(uint32_t b, uint8_t lt, uint8_t rt);
 
 void steam_pads_to_touch(uint32_t b, uint16_t touch_h, int16_t lpx, int16_t lpy,

+ 36 - 30
PicoPuck/src/puck/mode_ds4.c

@@ -7,23 +7,11 @@
 #include "puck/gamepad_util.h"
 #include "puck/relay.h"
 #include <string.h>
+#include "hid_reports.h"
 
 #define DS4_TOUCH_H 942
 #define DS4_STATUS_USB 0x1B
 
-static const uint8_t GYRO_HID_DESC[] = {
-	0x05, 0x01, 0x09, 0x05, 0xA1, 0x01, 0x85, 0x01, 0x09, 0x30, 0x09, 0x31,
-	0x09, 0x32, 0x09, 0x35, 0x15, 0x00, 0x25, 0x07, 0x75, 0x04, 0x95, 0x01,
-	0x81, 0x42, 0x65, 0x00, 0x05, 0x09, 0x19, 0x01, 0x29, 0x0E, 0x15, 0x00,
-	0x25, 0x01, 0x75, 0x01, 0x95, 0x0E, 0x81, 0x02, 0x06, 0x00, 0xFF, 0x09,
-	0x20, 0x75, 0x06, 0x95, 0x01, 0x15, 0x00, 0x25, 0x7F, 0x81, 0x02, 0x05,
-	0x01, 0x09, 0x33, 0x09, 0x34, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x75, 0x08,
-	0x95, 0x02, 0x81, 0x02, 0x06, 0x00, 0xFF, 0x09, 0x21, 0x95, 0x36, 0x81,
-	0x02, 0x85, 0x05, 0x09, 0x22, 0x95, 0x1F, 0x91, 0x02, 0x85, 0x04, 0x09,
-	0x23, 0x95, 0x24, 0xB1, 0x02, 0x85, 0x02, 0x09, 0x24, 0x95, 0x24, 0xB1,
-	0x02, 0xC0
-};
-
 static uint16_t ds4_build(int slot, uint8_t *out, uint8_t *rid)
 {
 	*rid = 0x01;
@@ -43,17 +31,24 @@ static uint16_t ds4_build(int slot, uint8_t *out, uint8_t *rid)
 		 ((b & TB_STEAM) ? 0x01 : 0);
 	out[7] = g_in[slot].lt;
 	out[8] = g_in[slot].rt;
-	out[12] = g_in[slot].gx & 0xFF;  out[13] = g_in[slot].gx >> 8;
-	out[14] = g_in[slot].gz & 0xFF;  out[15] = g_in[slot].gz >> 8;
-	out[16] = (-g_in[slot].gy) & 0xFF; out[17] = (-g_in[slot].gy) >> 8;
-	out[18] = g_in[slot].ax & 0xFF;  out[19] = g_in[slot].ax >> 8;
-	out[20] = g_in[slot].ay & 0xFF;  out[21] = g_in[slot].ay >> 8;
-	out[22] = g_in[slot].az & 0xFF;  out[23] = g_in[slot].az >> 8;
+	out[12] = g_in[slot].gx & 0xFF;
+	out[13] = g_in[slot].gx >> 8;
+	out[14] = g_in[slot].gz & 0xFF;
+	out[15] = g_in[slot].gz >> 8;
+	out[16] = (-g_in[slot].gy) & 0xFF;
+	out[17] = (-g_in[slot].gy) >> 8;
+	out[18] = g_in[slot].ax & 0xFF;
+	out[19] = g_in[slot].ax >> 8;
+	out[20] = g_in[slot].ay & 0xFF;
+	out[21] = g_in[slot].ay >> 8;
+	out[22] = g_in[slot].az & 0xFF;
+	out[23] = g_in[slot].az >> 8;
 	out[29] = DS4_STATUS_USB;
 	if (lt_touch || rt_touch) {
 		uint16_t lx, ly, rx, ry;
-		steam_pads_to_touch(b, DS4_TOUCH_H, g_in[slot].lpx, g_in[slot].lpy,
-				    g_in[slot].rpx, g_in[slot].rpy, &lx, &ly, &rx, &ry);
+		steam_pads_to_touch(b, DS4_TOUCH_H, g_in[slot].lpx,
+				    g_in[slot].lpy, g_in[slot].rpx,
+				    g_in[slot].rpy, &lx, &ly, &rx, &ry);
 		static uint8_t tstamp;
 		out[32] = 1;
 		out[33] = tstamp++;
@@ -71,22 +66,28 @@ static uint16_t ds4_get(int slot, uint8_t rid, uint8_t type, uint8_t *buf,
 	if (type != PP_HID_FEATURE || reqlen == 0)
 		return 0;
 	memset(buf, 0, reqlen);
-	uint8_t mac[6] = { 0x00, 0x1B, 0xDC, 0x4F, 0x55, (uint8_t)(0x50 + slot) };
+	uint8_t mac[6] = {
+		0x00, 0x1B, 0xDC, 0x4F, 0x55, (uint8_t)(0x50 + slot)
+	};
 	switch (rid) {
 	case 0x02:
-		if (reqlen < 36) return 0;
+		if (reqlen < 36)
+			return 0;
 		ps_neutral_calib(buf);
 		return 36;
 	case 0x12:
-		if (reqlen < 15) return 0;
+		if (reqlen < 15)
+			return 0;
 		memcpy(buf, mac, 6);
 		return 15;
 	case 0x81:
-		if (reqlen < 6) return 0;
+		if (reqlen < 6)
+			return 0;
 		memcpy(buf, mac, 6);
 		return 6;
 	case 0xA3:
-		if (reqlen < 48) return 0;
+		if (reqlen < 48)
+			return 0;
 		buf[0] = 0x01;
 		return 48;
 	default:
@@ -104,10 +105,13 @@ static void ds4_set(int slot, uint8_t rid, uint8_t type, const uint8_t *b,
 	const uint8_t *p;
 	uint16_t pn;
 	if (rid == 0) {
-		if (n < 1 || b[0] != 0x05) return;
-		p = b + 1; pn = (uint16_t)(n - 1);
+		if (n < 1 || b[0] != 0x05)
+			return;
+		p = b + 1;
+		pn = (uint16_t)(n - 1);
 	} else if (rid == 0x05) {
-		p = b; pn = n;
+		p = b;
+		pn = n;
 	} else {
 		return;
 	}
@@ -117,7 +121,9 @@ static void ds4_set(int slot, uint8_t rid, uint8_t type, const uint8_t *b,
 }
 
 const emu_mode_t emu_ds4 = {
-	.vid = 0x054C, .pid = 0x05C4, .bcd = 0x0120,
+	.vid = 0x054C,
+	.pid = 0x05C4,
+	.bcd = 0x0120,
 	.product = "Wireless Controller",
 	.report_desc = GYRO_HID_DESC,
 	.report_desc_len = sizeof(GYRO_HID_DESC),

+ 24 - 77
PicoPuck/src/puck/mode_ps3.c

@@ -13,82 +13,21 @@
 #include "puck/gamepad_util.h"
 #include "puck/relay.h"
 #include <string.h>
+#include "hid_reports.h"
 
 // Genuine Sixaxis / DualShock 3 HID report descriptor (148 bytes, verbatim from
 // real hardware). The console does not parse this to drive input — it matches by
 // VID/PID + handshake — but a faithful descriptor keeps host HID stacks happy.
 // Do not "tidy" it: this is the real firmware's (famously odd) descriptor.
-static const uint8_t DS3_HID_DESC[148] = {
-	0x05, 0x01, 0x09, 0x04, 0xA1, 0x01, 0xA1, 0x02, 0x85, 0x01, 0x75, 0x08,
-	0x95, 0x01, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x81, 0x03, 0x75, 0x01, 0x95,
-	0x13, 0x15, 0x00, 0x25, 0x01, 0x35, 0x00, 0x45, 0x01, 0x05, 0x09, 0x19,
-	0x01, 0x29, 0x13, 0x81, 0x02, 0x75, 0x01, 0x95, 0x0D, 0x06, 0x00, 0xFF,
-	0x81, 0x03, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x05, 0x01, 0x09, 0x01, 0xA1,
-	0x00, 0x75, 0x08, 0x95, 0x04, 0x35, 0x00, 0x46, 0xFF, 0x00, 0x09, 0x30,
-	0x09, 0x31, 0x09, 0x32, 0x09, 0x35, 0x81, 0x02, 0xC0, 0x05, 0x01, 0x75,
-	0x08, 0x95, 0x27, 0x09, 0x01, 0x81, 0x02, 0x75, 0x08, 0x95, 0x30, 0x09,
-	0x01, 0x91, 0x02, 0x75, 0x08, 0x95, 0x30, 0x09, 0x01, 0xB1, 0x02, 0xC0,
-	0xA1, 0x02, 0x85, 0x02, 0x75, 0x08, 0x95, 0x30, 0x09, 0x01, 0xB1, 0x02,
-	0xC0, 0xA1, 0x02, 0x85, 0xEE, 0x75, 0x08, 0x95, 0x30, 0x09, 0x01, 0xB1,
-	0x02, 0xC0, 0xA1, 0x02, 0x85, 0xEF, 0x75, 0x08, 0x95, 0x30, 0x09, 0x01,
-	0xB1, 0x02, 0xC0, 0xC0
-};
 
 // Magic GET_REPORT(Feature) responses, verbatim from GIMX-firmwares EMUPS3 — a
 // LUFA DS3 emulator proven to enumerate on a real PS3. The enable handshake is:
 //   GET 0x01 -> GET 0xF2 -> {SET 0xEF / GET 0xEF}x2 -> GET 0xF8 -> SET out 0x01
 //   -> SET 0xF4(enable); then input report 0x01 streams (we stream regardless).
-static const uint8_t report_01[64] = {
-	0x00, 0x01, 0x04, 0x00, 0x07, 0x0c, 0x01, 0x02, 0x18, 0x18, 0x18,
-	0x18, 0x09, 0x0a, 0x10, 0x11, 0x12, 0x13, 0x00, 0x00, 0x00, 0x00,
-	0x04, 0x00, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x04, 0x04,
-	0x04, 0x04, 0x00, 0x00, 0x04, 0x00, 0x01, 0x02, 0x07, 0x00, 0x17,
-	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
-};
-static const uint8_t report_f2[64] = {
-	0xf2, 0xff, 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x00,
-	0x03, 0x50, 0x81, 0xd8, 0x01, 0x8a, 0x13, 0x00, 0x00, 0x00, 0x00,
-	0x04, 0x00, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x04, 0x04,
-	0x04, 0x04, 0x00, 0x00, 0x04, 0x00, 0x01, 0x02, 0x07, 0x00, 0x17,
-	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
-};
-static const uint8_t report_f5[64] = {
-	0x01, 0x00, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xff, 0xf7, 0x00,
-	0x03, 0x50, 0x81, 0xd8, 0x01, 0x8a, 0x13, 0x00, 0x00, 0x00, 0x00,
-	0x04, 0x00, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x04, 0x04,
-	0x04, 0x04, 0x00, 0x00, 0x04, 0x00, 0x01, 0x02, 0x07, 0x00, 0x17,
-	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
-};
-static const uint8_t report_ef[64] = {
-	0x00, 0xef, 0x04, 0x00, 0x07, 0x03, 0x01, 0xb0, 0x00, 0x00, 0x00,
-	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x6b, 0x02, 0x68, 0x00,
-	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
-};
-static const uint8_t report_f8[64] = {
-	0x00, 0x01, 0x00, 0x00, 0x07, 0x03, 0x01, 0xb0, 0x00, 0x00, 0x00,
-	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x6b, 0x02, 0x68, 0x00,
-	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
-};
-static const uint8_t report_f7[64] = {
-	0x01, 0x04, 0xc4, 0x02, 0xd6, 0x01, 0xee, 0xff, 0x14, 0x13, 0x01,
-	0x02, 0xc4, 0x01, 0xd6, 0x00, 0x00, 0x02, 0x02, 0x02, 0x00, 0x03,
-	0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x62, 0x01, 0x02, 0x01, 0x5e,
-	0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
-};
 
-static uint8_t g_byte6ef = 0xb0;                 // last SET 0xEF byte[6]; echoed in GET 0xEF/0xF8 byte[7]
-static uint8_t g_masterBd[6];                    // host MAC learned via SET 0xF5
+static uint8_t g_byte6ef =
+	0xb0; // last SET 0xEF byte[6]; echoed in GET 0xEF/0xF8 byte[7]
+static uint8_t g_masterBd[6]; // host MAC learned via SET 0xF5
 static bool g_haveMaster;
 // Controller BT MAC (OUI 00:1B:DC like the other PS modes; last byte 0x80 keeps
 // it distinct). Only relevant to BT pairing identity; read via GET 0xF2.
@@ -96,7 +35,8 @@ static const uint8_t g_ds3Mac[6] = { 0x00, 0x1B, 0xDC, 0x4F, 0x55, 0x80 };
 
 // Copy a 64-byte Sixaxis feature report verbatim (raw TinyUSB does not prepend
 // the id). Returns min(64, reqlen).
-static uint16_t emit_feature(uint8_t *buf, uint16_t reqlen, const uint8_t arr[64])
+static uint16_t emit_feature(uint8_t *buf, uint16_t reqlen,
+			     const uint8_t arr[64])
 {
 	uint16_t n = reqlen < 64 ? reqlen : 64;
 	memcpy(buf, arr, n);
@@ -113,16 +53,16 @@ static uint16_t ds3_get(int slot, uint8_t rid, uint8_t type, uint8_t *buf,
 	switch (rid) {
 	case 0x01:
 		return emit_feature(buf, reqlen, report_01);
-	case 0xf2:  // overlay our controller MAC at [4..9]
+	case 0xf2: // overlay our controller MAC at [4..9]
 		memcpy(r, report_f2, 64);
 		memcpy(r + 4, g_ds3Mac, 6);
 		return emit_feature(buf, reqlen, r);
-	case 0xf5:  // overlay learned host MAC at [2..7]
+	case 0xf5: // overlay learned host MAC at [2..7]
 		memcpy(r, report_f5, 64);
 		if (g_haveMaster)
 			memcpy(r + 2, g_masterBd, 6);
 		return emit_feature(buf, reqlen, r);
-	case 0xef:  // echo the PS3's remembered byte at [7]
+	case 0xef: // echo the PS3's remembered byte at [7]
 		memcpy(r, report_ef, 64);
 		r[7] = g_byte6ef;
 		return emit_feature(buf, reqlen, r);
@@ -158,10 +98,13 @@ static void ds3_set(int slot, uint8_t rid, uint8_t type, const uint8_t *b,
 	const uint8_t *p;
 	uint16_t pn;
 	if (rid == 0) {
-		if (n < 1 || b[0] != 0x01) return;
-		p = b + 1; pn = (uint16_t)(n - 1);
+		if (n < 1 || b[0] != 0x01)
+			return;
+		p = b + 1;
+		pn = (uint16_t)(n - 1);
 	} else if (rid == 0x01) {
-		p = b; pn = n;
+		p = b;
+		pn = n;
 	} else {
 		return;
 	}
@@ -177,8 +120,10 @@ static void ds3_set(int slot, uint8_t rid, uint8_t type, const uint8_t *b,
 static void ds3_imu(uint8_t *out, int16_t v)
 {
 	int32_t e = 511 + ((int32_t)v >> 6);
-	if (e < 0) e = 0;
-	if (e > 1023) e = 1023;
+	if (e < 0)
+		e = 0;
+	if (e > 1023)
+		e = 1023;
 	out[0] = (uint8_t)(e & 0xFF);
 	out[1] = (uint8_t)((e >> 8) & 0xFF);
 }
@@ -208,7 +153,7 @@ static uint16_t ds3_build(int slot, uint8_t *out, uint8_t *rid)
 	out[2] = (l2 ? 0x01 : 0) | (r2 ? 0x02 : 0) | ((b & TB_LB) ? 0x04 : 0) |
 		 ((b & TB_RB) ? 0x08 : 0) | tri | cir | crs | sqr;
 
-	out[3] = (b & TB_STEAM) ? 0x01 : 0;  // PS button
+	out[3] = (b & TB_STEAM) ? 0x01 : 0; // PS button
 
 	out[5] = sw_stick(g_in[slot].lx, false);
 	out[6] = sw_stick(g_in[slot].ly, true);
@@ -230,7 +175,7 @@ static uint16_t ds3_build(int slot, uint8_t *out, uint8_t *rid)
 	out[24] = sqr ? 0xFF : 0;
 
 	out[28] = 0x00;
-	out[29] = 0x05;  // battery level: full (cosmetic over USB)
+	out[29] = 0x05; // battery level: full (cosmetic over USB)
 
 	// out[40..47]: accel X, accel Z, accel Y, gyro Z — each 10-bit LE, center 511.
 	ds3_imu(out + 40, g_in[slot].ax);
@@ -241,7 +186,9 @@ static uint16_t ds3_build(int slot, uint8_t *out, uint8_t *rid)
 }
 
 const emu_mode_t emu_ps3 = {
-	.vid = 0x054C, .pid = 0x0268, .bcd = 0x0100,
+	.vid = 0x054C,
+	.pid = 0x0268,
+	.bcd = 0x0100,
 	.product = "PLAYSTATION(R)3 Controller",
 	.report_desc = DS3_HID_DESC,
 	.report_desc_len = sizeof(DS3_HID_DESC),

+ 37 - 42
PicoPuck/src/puck/mode_ps5.c

@@ -7,36 +7,11 @@
 #include "puck/gamepad_util.h"
 #include "puck/relay.h"
 #include <string.h>
+#include "hid_reports.h"
 
 #define PS5_TOUCH_H 1080
 #define PS5_STATUS_USB 0x1A
 
-static const uint8_t PS5_HID_DESC[] = {
-	0x05, 0x01, 0x09, 0x05, 0xA1, 0x01, 0x85, 0x01, 0x09, 0x30, 0x09, 0x31,
-	0x09, 0x32, 0x09, 0x35, 0x09, 0x33, 0x09, 0x34, 0x15, 0x00, 0x26, 0xFF,
-	0x00, 0x75, 0x08, 0x95, 0x06, 0x81, 0x02, 0x06, 0x00, 0xFF, 0x09, 0x20,
-	0x95, 0x01, 0x81, 0x02, 0x05, 0x01, 0x09, 0x39, 0x15, 0x00, 0x25, 0x07,
-	0x35, 0x00, 0x46, 0x3B, 0x01, 0x65, 0x14, 0x75, 0x04, 0x95, 0x01, 0x81,
-	0x42, 0x65, 0x00, 0x05, 0x09, 0x19, 0x01, 0x29, 0x0F, 0x15, 0x00, 0x25,
-	0x01, 0x75, 0x01, 0x95, 0x0F, 0x81, 0x02, 0x06, 0x00, 0xFF, 0x09, 0x21,
-	0x95, 0x0D, 0x81, 0x02, 0x06, 0x00, 0xFF, 0x09, 0x22, 0x15, 0x00, 0x26,
-	0xFF, 0x00, 0x75, 0x08, 0x95, 0x34, 0x81, 0x02, 0x85, 0x02, 0x09, 0x23,
-	0x95, 0x2F, 0x91, 0x02, 0x85, 0x05, 0x09, 0x33, 0x95, 0x28, 0xB1, 0x02,
-	0x85, 0x08, 0x09, 0x34, 0x95, 0x2F, 0xB1, 0x02, 0x85, 0x09, 0x09, 0x24,
-	0x95, 0x13, 0xB1, 0x02, 0x85, 0x0A, 0x09, 0x25, 0x95, 0x1A, 0xB1, 0x02,
-	0x85, 0x20, 0x09, 0x26, 0x95, 0x3F, 0xB1, 0x02, 0x85, 0x21, 0x09, 0x27,
-	0x95, 0x04, 0xB1, 0x02, 0x85, 0x22, 0x09, 0x40, 0x95, 0x3F, 0xB1, 0x02,
-	0x85, 0x80, 0x09, 0x28, 0x95, 0x3F, 0xB1, 0x02, 0x85, 0x81, 0x09, 0x29,
-	0x95, 0x3F, 0xB1, 0x02, 0x85, 0x82, 0x09, 0x2A, 0x95, 0x09, 0xB1, 0x02,
-	0x85, 0x83, 0x09, 0x2B, 0x95, 0x3F, 0xB1, 0x02, 0x85, 0x84, 0x09, 0x2C,
-	0x95, 0x3F, 0xB1, 0x02, 0x85, 0x85, 0x09, 0x2D, 0x95, 0x02, 0xB1, 0x02,
-	0x85, 0xA0, 0x09, 0x2E, 0x95, 0x01, 0xB1, 0x02, 0x85, 0xE0, 0x09, 0x2F,
-	0x95, 0x3F, 0xB1, 0x02, 0x85, 0xF0, 0x09, 0x30, 0x95, 0x3F, 0xB1, 0x02,
-	0x85, 0xF1, 0x09, 0x31, 0x95, 0x3F, 0xB1, 0x02, 0x85, 0xF2, 0x09, 0x32,
-	0x95, 0x0F, 0xB1, 0x02, 0x85, 0xF4, 0x09, 0x35, 0x95, 0x3F, 0xB1, 0x02,
-	0x85, 0xF5, 0x09, 0x36, 0x95, 0x03, 0xB1, 0x02, 0xC0
-};
-
 static uint16_t ps5_build(int slot, uint8_t *out, uint8_t *rid)
 {
 	*rid = 0x01;
@@ -57,12 +32,18 @@ static uint16_t ps5_build(int slot, uint8_t *out, uint8_t *rid)
 	out[9] = ((b & TB_STEAM) ? 0x01 : 0) |
 		 ((b & (TB_TOUCH | TB_LPADC | TB_RPADC)) ? 0x02 : 0) |
 		 ((b & TB_MUTE) ? 0x04 : 0);
-	out[15] = g_in[slot].gx & 0xFF;  out[16] = g_in[slot].gx >> 8;
-	out[17] = g_in[slot].gz & 0xFF;  out[18] = g_in[slot].gz >> 8;
-	out[19] = (-g_in[slot].gy) & 0xFF; out[20] = (-g_in[slot].gy) >> 8;
-	out[21] = g_in[slot].ax & 0xFF;  out[22] = g_in[slot].ax >> 8;
-	out[23] = g_in[slot].ay & 0xFF;  out[24] = g_in[slot].ay >> 8;
-	out[25] = g_in[slot].az & 0xFF;  out[26] = g_in[slot].az >> 8;
+	out[15] = g_in[slot].gx & 0xFF;
+	out[16] = g_in[slot].gx >> 8;
+	out[17] = g_in[slot].gz & 0xFF;
+	out[18] = g_in[slot].gz >> 8;
+	out[19] = (-g_in[slot].gy) & 0xFF;
+	out[20] = (-g_in[slot].gy) >> 8;
+	out[21] = g_in[slot].ax & 0xFF;
+	out[22] = g_in[slot].ax >> 8;
+	out[23] = g_in[slot].ay & 0xFF;
+	out[24] = g_in[slot].ay >> 8;
+	out[25] = g_in[slot].az & 0xFF;
+	out[26] = g_in[slot].az >> 8;
 	uint16_t lx, ly, rx, ry;
 	steam_pads_to_touch(b, PS5_TOUCH_H, g_in[slot].lpx, g_in[slot].lpy,
 			    g_in[slot].rpx, g_in[slot].rpy, &lx, &ly, &rx, &ry);
@@ -77,23 +58,32 @@ static uint16_t ps5_get(int slot, uint8_t rid, uint8_t type, uint8_t *buf,
 	if (type != PP_HID_FEATURE || reqlen == 0)
 		return 0;
 	memset(buf, 0, reqlen);
-	uint8_t mac[6] = { 0x00, 0x1B, 0xDC, 0x4F, 0x55, (uint8_t)(0x60 + slot) };
+	uint8_t mac[6] = {
+		0x00, 0x1B, 0xDC, 0x4F, 0x55, (uint8_t)(0x60 + slot)
+	};
 	switch (rid) {
 	case 0x03:
-		if (reqlen < 47) return 0;
-		buf[0] = 0x00; buf[1] = 0x28; buf[2] = 0x01; buf[3] = 0x00;
+		if (reqlen < 47)
+			return 0;
+		buf[0] = 0x00;
+		buf[1] = 0x28;
+		buf[2] = 0x01;
+		buf[3] = 0x00;
 		buf[4] = 0x0E;
 		return 47;
 	case 0x05:
-		if (reqlen < 40) return 0;
+		if (reqlen < 40)
+			return 0;
 		ps_neutral_calib(buf);
 		return 40;
 	case 0x09:
-		if (reqlen < 19) return 0;
+		if (reqlen < 19)
+			return 0;
 		memcpy(buf, mac, 6);
 		return 19;
 	case 0x20:
-		if (reqlen < 63) return 0;
+		if (reqlen < 63)
+			return 0;
 		buf[23] = 0x01;
 		buf[27] = 0x01;
 		return 63;
@@ -112,10 +102,13 @@ static void ps5_set(int slot, uint8_t rid, uint8_t type, const uint8_t *b,
 	const uint8_t *p;
 	uint16_t pn;
 	if (rid == 0) {
-		if (n < 1 || b[0] != 0x02) return;
-		p = b + 1; pn = (uint16_t)(n - 1);
+		if (n < 1 || b[0] != 0x02)
+			return;
+		p = b + 1;
+		pn = (uint16_t)(n - 1);
 	} else if (rid == 0x02) {
-		p = b; pn = n;
+		p = b;
+		pn = n;
 	} else {
 		return;
 	}
@@ -126,7 +119,9 @@ static void ps5_set(int slot, uint8_t rid, uint8_t type, const uint8_t *b,
 }
 
 const emu_mode_t emu_ps5 = {
-	.vid = 0x054C, .pid = 0x0CE6, .bcd = 0x0110,
+	.vid = 0x054C,
+	.pid = 0x0CE6,
+	.bcd = 0x0110,
 	.product = "DualSense Wireless Controller",
 	.report_desc = PS5_HID_DESC,
 	.report_desc_len = sizeof(PS5_HID_DESC),

+ 48 - 32
PicoPuck/src/puck/mode_switch_hori.c

@@ -6,19 +6,7 @@
 // SPDX-License-Identifier: AGPL-3.0-or-later
 #include "puck/emu.h"
 #include "puck/gamepad_util.h"
-
-static const uint8_t SWITCH_HID_DESC[] = {
-	0x05, 0x01, 0x09, 0x05, 0xA1, 0x01, 0x15, 0x00, 0x25, 0x01, 0x35, 0x00,
-	0x45, 0x01, 0x75, 0x01, 0x95, 0x10,
-	0x05, 0x09, 0x19, 0x01, 0x29, 0x10, 0x81, 0x02, 0x05, 0x01, 0x25, 0x07,
-	0x46, 0x3B, 0x01, 0x75, 0x04, 0x95, 0x01, 0x65, 0x14, 0x09, 0x39, 0x81,
-	0x42,
-	0x65, 0x00, 0x95, 0x01, 0x81, 0x01,
-	0x26, 0xFF, 0x00, 0x46, 0xFF, 0x00, 0x09, 0x30, 0x09, 0x31, 0x09, 0x32,
-	0x09, 0x35, 0x75, 0x08, 0x95, 0x04, 0x81, 0x02,
-	0x06, 0x00, 0xFF, 0x09, 0x20, 0x95, 0x01, 0x81, 0x02,
-	0x0A, 0x21, 0x26, 0x95, 0x08, 0x91, 0x02, 0xC0
-};
+#include "hid_reports.h"
 
 static uint16_t hori_build(int slot, uint8_t *out, uint8_t *rid)
 {
@@ -26,27 +14,53 @@ static uint16_t hori_build(int slot, uint8_t *out, uint8_t *rid)
 	uint32_t b = g_in[slot].buttons;
 	uint16_t btn = 0;
 	// Nintendo layout (abSwap): A→B(0x02) B→A(0x04) X→Y(0x01) Y→X(0x08).
-	if (b & TB_A) btn |= 0x02;
-	if (b & TB_B) btn |= 0x04;
-	if (b & TB_X) btn |= 0x01;
-	if (b & TB_Y) btn |= 0x08;
-	if (b & TB_LB) btn |= 0x10;
-	if (b & TB_RB) btn |= 0x20;
-	if (g_in[slot].lt >= SW_TRIG_ON || (b & TB_L2)) btn |= 0x40;  // ZL
-	if (g_in[slot].rt >= SW_TRIG_ON || (b & TB_R2)) btn |= 0x80;  // ZR
-	if (b & TB_MENU) btn |= 0x100;   // Minus
-	if (b & TB_VIEW) btn |= 0x200;   // Plus
-	if (b & TB_L3) btn |= 0x400;
-	if (b & TB_R3) btn |= 0x800;
-	if (b & TB_STEAM) btn |= 0x1000; // Home
-	if (b & TB_QAM) btn |= 0x2000;   // Capture
+	if (b & TB_A)
+		btn |= 0x02;
+	if (b & TB_B)
+		btn |= 0x04;
+	if (b & TB_X)
+		btn |= 0x01;
+	if (b & TB_Y)
+		btn |= 0x08;
+	if (b & TB_LB)
+		btn |= 0x10;
+	if (b & TB_RB)
+		btn |= 0x20;
+	if (g_in[slot].lt >= SW_TRIG_ON || (b & TB_L2))
+		btn |= 0x40; // ZL
+	if (g_in[slot].rt >= SW_TRIG_ON || (b & TB_R2))
+		btn |= 0x80; // ZR
+	if (b & TB_MENU)
+		btn |= 0x100; // Minus
+	if (b & TB_VIEW)
+		btn |= 0x200; // Plus
+	if (b & TB_L3)
+		btn |= 0x400;
+	if (b & TB_R3)
+		btn |= 0x800;
+	if (b & TB_STEAM)
+		btn |= 0x1000; // Home
+	if (b & TB_QAM)
+		btn |= 0x2000; // Capture
 
 	bool u = b & TB_DUP, d = b & TB_DDN, l = b & TB_DLF, r = b & TB_DRT;
 	uint8_t hat = 8;
-	if (u && r) hat = 1; else if (r && d) hat = 3;
-	else if (d && l) hat = 5; else if (l && u) hat = 7;
-	else if (u) hat = 0; else if (r) hat = 2;
-	else if (d) hat = 4; else if (l) hat = 6;
+	if (u && r)
+		hat = 1;
+	else if (r && d)
+		hat = 3;
+	else if (d && l)
+		hat = 5;
+	else if (l && u)
+		hat = 7;
+	else if (u)
+		hat = 0;
+	else if (r)
+		hat = 2;
+	else if (d)
+		hat = 4;
+	else if (l)
+		hat = 6;
 
 	out[0] = (uint8_t)(btn & 0xFF);
 	out[1] = (uint8_t)(btn >> 8);
@@ -60,7 +74,9 @@ static uint16_t hori_build(int slot, uint8_t *out, uint8_t *rid)
 }
 
 const emu_mode_t emu_switch_hori = {
-	.vid = 0x0F0D, .pid = 0x0092, .bcd = 0x0210,
+	.vid = 0x0F0D,
+	.pid = 0x0092,
+	.bcd = 0x0210,
 	.product = "POKKEN CONTROLLER",
 	.report_desc = SWITCH_HID_DESC,
 	.report_desc_len = sizeof(SWITCH_HID_DESC),

+ 243 - 164
PicoPuck/src/puck/mode_switch_pro.c

@@ -22,33 +22,14 @@
 #include <string.h>
 #include <math.h>
 #include "pico/time.h"
+#include "hid_reports.h"
 
-#define ET_SWITCH 1              // per-type config index (matches OpenPuck ET_SWITCH)
-#define SW_PRO_REPORT_MS 15u     // 66 Hz
-#define SW_PRO_REPORT_MS_120 8u  // ~120 Hz
-#define SW_STREAM_MS 4u          // "full" (~250 Hz), matches emu_present cadence
-#define SW_ACCEL_DIV 4           // SC2 ±2g (16384/g) → Pro ±8g (4096/g)
-#define CHORD_BACK4 (TB_L4 | TB_R4 | TB_L5 | TB_R5)
-
-static const uint8_t SWPRO_HID_DESC[] = {
-	0x05, 0x01, 0x15, 0x00, 0x09, 0x04, 0xA1, 0x01, 0x85, 0x30, 0x05, 0x01,
-	0x05, 0x09, 0x19, 0x01, 0x29, 0x0A, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01,
-	0x95, 0x0A, 0x55, 0x00, 0x65, 0x00, 0x81, 0x02, 0x05, 0x09, 0x19, 0x0B,
-	0x29, 0x0E, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x04, 0x81, 0x02,
-	0x75, 0x01, 0x95, 0x02, 0x81, 0x03, 0x0B, 0x01, 0x00, 0x01, 0x00, 0xA1,
-	0x00, 0x0B, 0x30, 0x00, 0x01, 0x00, 0x0B, 0x31, 0x00, 0x01, 0x00, 0x0B,
-	0x32, 0x00, 0x01, 0x00, 0x0B, 0x35, 0x00, 0x01, 0x00, 0x15, 0x00, 0x27,
-	0xFF, 0xFF, 0x00, 0x00, 0x75, 0x10, 0x95, 0x04, 0x81, 0x02, 0xC0, 0x0B,
-	0x39, 0x00, 0x01, 0x00, 0x15, 0x00, 0x25, 0x07, 0x35, 0x00, 0x46, 0x3B,
-	0x01, 0x65, 0x14, 0x75, 0x04, 0x95, 0x01, 0x81, 0x42, 0x05, 0x09, 0x19,
-	0x0F, 0x29, 0x12, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x04, 0x81,
-	0x02, 0x75, 0x08, 0x95, 0x34, 0x81, 0x03, 0x06, 0x00, 0xFF, 0x85, 0x21,
-	0x09, 0x01, 0x75, 0x08, 0x95, 0x3F, 0x81, 0x03, 0x85, 0x81, 0x09, 0x02,
-	0x75, 0x08, 0x95, 0x3F, 0x81, 0x03, 0x85, 0x01, 0x09, 0x03, 0x75, 0x08,
-	0x95, 0x3F, 0x91, 0x83, 0x85, 0x10, 0x09, 0x04, 0x75, 0x08, 0x95, 0x3F,
-	0x91, 0x83, 0x85, 0x80, 0x09, 0x05, 0x75, 0x08, 0x95, 0x3F, 0x91, 0x83,
-	0x85, 0x82, 0x09, 0x06, 0x75, 0x08, 0x95, 0x3F, 0x91, 0x83, 0xC0
-};
+#define ET_SWITCH 1 // per-type config index (matches OpenPuck ET_SWITCH)
+#define SW_PRO_REPORT_MS 15u // 66 Hz
+#define SW_PRO_REPORT_MS_120 8u // ~120 Hz
+#define SW_STREAM_MS 4u // "full" (~250 Hz), matches emu_present cadence
+#define SW_ACCEL_DIV 4 // SC2 ±2g (16384/g) → Pro ±8g (4096/g)
+// CHORD_BACK4 comes from the shared triton_masks.h (via emu.h → triton.h).
 
 #define JC_BTN_Y (1u << 0)
 #define JC_BTN_X (1u << 1)
@@ -73,25 +54,44 @@ static uint32_t code_to_jc(uint8_t c, uint32_t fA, uint32_t fB, uint32_t fX,
 			   uint32_t fY)
 {
 	switch (c) {
-	case 1: return fA;
-	case 2: return fB;
-	case 3: return fX;
-	case 4: return fY;
-	case 5: return JC_BTN_L;
-	case 6: return JC_BTN_R;
-	case 7: return JC_BTN_LSTICK;
-	case 8: return JC_BTN_RSTICK;
-	case 9: return JC_BTN_MINUS;
-	case 10: return JC_BTN_PLUS;
-	case 11: return JC_BTN_HOME;
-	case 18: return JC_BTN_CAPTURE;
-	case 19: return JC_BTN_ZL;
-	case 20: return JC_BTN_ZR;
-	case 12: return JC_BTN_UP;
-	case 13: return JC_BTN_DOWN;
-	case 14: return JC_BTN_LEFT;
-	case 15: return JC_BTN_RIGHT;
-	default: return 0;
+	case 1:
+		return fA;
+	case 2:
+		return fB;
+	case 3:
+		return fX;
+	case 4:
+		return fY;
+	case 5:
+		return JC_BTN_L;
+	case 6:
+		return JC_BTN_R;
+	case 7:
+		return JC_BTN_LSTICK;
+	case 8:
+		return JC_BTN_RSTICK;
+	case 9:
+		return JC_BTN_MINUS;
+	case 10:
+		return JC_BTN_PLUS;
+	case 11:
+		return JC_BTN_HOME;
+	case 18:
+		return JC_BTN_CAPTURE;
+	case 19:
+		return JC_BTN_ZL;
+	case 20:
+		return JC_BTN_ZR;
+	case 12:
+		return JC_BTN_UP;
+	case 13:
+		return JC_BTN_DOWN;
+	case 14:
+		return JC_BTN_LEFT;
+	case 15:
+		return JC_BTN_RIGHT;
+	default:
+		return 0;
 	}
 }
 
@@ -105,26 +105,35 @@ static void jc_mac(int slot, uint8_t out[6])
 	out[5] = (uint8_t)(0x10 + slot);
 }
 static uint8_t g_jcTimer[PP_NSLOT];
-static uint8_t g_reportMode[PP_NSLOT];  // 0 until subcommand 0x03 selects 0x30
+static uint8_t g_reportMode[PP_NSLOT]; // 0 until subcommand 0x03 selects 0x30
 
 // ---- HD-rumble amplitude decoder (see OpenPuck for the full protocol notes).
 enum { HDR_AMP_MIN = -256, HDR_AMP_OFF = -256 };
 static int16_t hdr_amp7(uint8_t code)
 {
-	if (code == 0) return HDR_AMP_MIN;
-	if (code < 16) return (int16_t)(8 * (int)code - 248);
-	if (code < 32) return (int16_t)(2 * (int)code - 158);
+	if (code == 0)
+		return HDR_AMP_MIN;
+	if (code < 16)
+		return (int16_t)(8 * (int)code - 248);
+	if (code < 32)
+		return (int16_t)(2 * (int)code - 158);
 	return (int16_t)((int)code - 127);
 }
 static int16_t hdr_amp5(uint8_t code, int16_t cur)
 {
-	if (code == 0) return HDR_AMP_OFF;
-	if (code <= 11) return (int16_t)(-16 * (int)(code - 1));
+	if (code == 0)
+		return HDR_AMP_OFF;
+	if (code <= 11)
+		return (int16_t)(-16 * (int)(code - 1));
 	int step = 0;
-	if (code >= 17 && code <= 19) step = 4;
-	else if (code >= 20 && code <= 22) step = 1;
-	else if (code >= 26 && code <= 28) step = -1;
-	else if (code >= 29 && code <= 31) step = -4;
+	if (code >= 17 && code <= 19)
+		step = 4;
+	else if (code >= 20 && code <= 22)
+		step = 1;
+	else if (code >= 26 && code <= 28)
+		step = -1;
+	else if (code >= 29 && code <= 31)
+		step = -4;
 	int v = (int)cur + step;
 	return v < HDR_AMP_MIN ? HDR_AMP_MIN : (v > 0 ? 0 : (int16_t)v);
 }
@@ -132,13 +141,16 @@ static uint16_t g_hdr_level[257];
 static bool g_hdr_built;
 static void hdr_build_levels(void)
 {
-	if (g_hdr_built) return;
+	if (g_hdr_built)
+		return;
 	for (int u = HDR_AMP_MIN; u <= 0; u++) {
 		float lin = (float)u / 32.0f;
 		float amp = (lin >= -7.9375f) ? exp2f(lin) : 0.0f;
-		if (amp > 1.0f) amp = 1.0f;
+		if (amp > 1.0f)
+			amp = 1.0f;
 		uint32_t v = (uint32_t)(amp * 65535.0f + 0.5f);
-		g_hdr_level[u - HDR_AMP_MIN] = (v > 0xFFFF) ? 0xFFFF : (uint16_t)v;
+		g_hdr_level[u - HDR_AMP_MIN] = (v > 0xFFFF) ? 0xFFFF :
+							      (uint16_t)v;
 	}
 	g_hdr_built = true;
 }
@@ -160,7 +172,8 @@ static uint16_t hdr_decode(int slot, uint8_t motor, const uint8_t b[4])
 		uint16_t la = g_hdr_level[*lo - HDR_AMP_MIN]; \
 		uint16_t ha = g_hdr_level[*hi - HDR_AMP_MIN]; \
 		uint16_t lv = la > ha ? la : ha;              \
-		if (lv > peak) peak = lv;                     \
+		if (lv > peak)                                \
+			peak = lv;                            \
 	} while (0)
 
 	switch (hdr_field(w, 30, 2)) {
@@ -180,8 +193,10 @@ static uint16_t hdr_decode(int slot, uint8_t motor, const uint8_t b[4])
 			bool want_hi = (w & 1) != 0;
 			bool is_freq = ((w >> 2) & 1) != 0;
 			if (!is_freq) {
-				if (want_hi) *hi = hdr_amp7(hdr_field(w, 23, 7));
-				else *lo = hdr_amp7(hdr_field(w, 23, 7));
+				if (want_hi)
+					*hi = hdr_amp7(hdr_field(w, 23, 7));
+				else
+					*lo = hdr_amp7(hdr_field(w, 23, 7));
 			}
 			HDR_SAMPLE();
 			*lo = hdr_amp5(hdr_field(w, 18, 5), *lo);
@@ -232,11 +247,13 @@ static uint16_t hdr_decode(int slot, uint8_t motor, const uint8_t b[4])
 static uint16_t g_jc_last_lo[PP_NSLOT], g_jc_last_hi[PP_NSLOT];
 static void jc_rumble(int slot, const uint8_t *p, uint16_t pn)
 {
-	if (pn < 9) return;  // [timer][left x4][right x4]
+	if (pn < 9)
+		return; // [timer][left x4][right x4]
 	hdr_build_levels();
-	uint16_t lo = hdr_decode(slot, 0, p + 1), hi = hdr_decode(slot, 1, p + 5);
+	uint16_t lo = hdr_decode(slot, 0, p + 1),
+		 hi = hdr_decode(slot, 1, p + 5);
 	if (lo == g_jc_last_lo[slot] && hi == g_jc_last_hi[slot])
-		return;  // Switch streams rumble every frame; relay only on change
+		return; // Switch streams rumble every frame; relay only on change
 	g_jc_last_lo[slot] = lo;
 	g_jc_last_hi[slot] = hi;
 	puck_rumble(slot, lo, hi);
@@ -258,22 +275,31 @@ static uint8_t jc_battery_nibble(int slot)
 {
 	uint8_t pct = g_battery[slot];
 	uint8_t cap;
-	if (pct >= 70) cap = 4;
-	else if (pct >= 50) cap = 3;
-	else if (pct >= 30) cap = 2;
-	else if (pct >= 10) cap = 1;
-	else cap = 0;
-	return (uint8_t)(cap << 1);  // even nibble {0,2,4,6,8}; leaves bit4 (charging) clear
+	if (pct >= 70)
+		cap = 4;
+	else if (pct >= 50)
+		cap = 3;
+	else if (pct >= 30)
+		cap = 2;
+	else if (pct >= 10)
+		cap = 1;
+	else
+		cap = 0;
+	return (uint8_t)(cap
+			 << 1); // even nibble {0,2,4,6,8}; leaves bit4 (charging) clear
 }
 
 // Scale a gyro axis by sw_gyro10/10, clamped to int16.
 static int16_t gscale(int16_t v)
 {
 	uint8_t g10 = settings()->sw_gyro10;
-	if (g10 == 10) return v;
+	if (g10 == 10)
+		return v;
 	int32_t s = (int32_t)v * (int32_t)g10 / 10;
-	if (s > 32767) s = 32767;
-	else if (s < -32768) s = -32768;
+	if (s > 32767)
+		s = 32767;
+	else if (s < -32768)
+		s = -32768;
 	return (int16_t)s;
 }
 static void jc_input_prefix(int slot, uint8_t *out)
@@ -288,28 +314,50 @@ static void jc_input_prefix(int slot, uint8_t *out)
 	uint32_t fX = t->ab_swap ? JC_BTN_Y : JC_BTN_X;
 	uint32_t fY = t->ab_swap ? JC_BTN_X : JC_BTN_Y;
 	uint32_t jc = 0;
-	if (b & TB_Y) jc |= fY;
-	if (b & TB_B) jc |= fB;
-	if (b & TB_A) jc |= fA;
-	if (b & TB_X) jc |= fX;
-	if (b & TB_LB) jc |= JC_BTN_L;
-	if (b & TB_RB) jc |= JC_BTN_R;
-	if ((g_in[slot].lt >= SW_TRIG_ON) || (b & TB_L2)) jc |= JC_BTN_ZL;
-	if ((g_in[slot].rt >= SW_TRIG_ON) || (b & TB_R2)) jc |= JC_BTN_ZR;
-	if (b & TB_VIEW) jc |= JC_BTN_PLUS;
-	if (b & TB_MENU) jc |= JC_BTN_MINUS;
-	if (b & TB_L3) jc |= JC_BTN_LSTICK;
-	if (b & TB_R3) jc |= JC_BTN_RSTICK;
-	if (b & TB_STEAM) jc |= JC_BTN_HOME;
-	if (b & TB_DDN) jc |= JC_BTN_DOWN;
-	if (b & TB_DUP) jc |= JC_BTN_UP;
-	if (b & TB_DRT) jc |= JC_BTN_RIGHT;
-	if (b & TB_DLF) jc |= JC_BTN_LEFT;
-	if (b & TB_L4) jc |= code_to_jc(t->back[0], fA, fB, fX, fY);
-	if (b & TB_R4) jc |= code_to_jc(t->back[1], fA, fB, fX, fY);
-	if (b & TB_L5) jc |= code_to_jc(t->back[2], fA, fB, fX, fY);
-	if (b & TB_R5) jc |= code_to_jc(t->back[3], fA, fB, fX, fY);
-	if (qam) jc |= code_to_jc(t->qam, fA, fB, fX, fY);
+	if (b & TB_Y)
+		jc |= fY;
+	if (b & TB_B)
+		jc |= fB;
+	if (b & TB_A)
+		jc |= fA;
+	if (b & TB_X)
+		jc |= fX;
+	if (b & TB_LB)
+		jc |= JC_BTN_L;
+	if (b & TB_RB)
+		jc |= JC_BTN_R;
+	if ((g_in[slot].lt >= SW_TRIG_ON) || (b & TB_L2))
+		jc |= JC_BTN_ZL;
+	if ((g_in[slot].rt >= SW_TRIG_ON) || (b & TB_R2))
+		jc |= JC_BTN_ZR;
+	if (b & TB_VIEW)
+		jc |= JC_BTN_PLUS;
+	if (b & TB_MENU)
+		jc |= JC_BTN_MINUS;
+	if (b & TB_L3)
+		jc |= JC_BTN_LSTICK;
+	if (b & TB_R3)
+		jc |= JC_BTN_RSTICK;
+	if (b & TB_STEAM)
+		jc |= JC_BTN_HOME;
+	if (b & TB_DDN)
+		jc |= JC_BTN_DOWN;
+	if (b & TB_DUP)
+		jc |= JC_BTN_UP;
+	if (b & TB_DRT)
+		jc |= JC_BTN_RIGHT;
+	if (b & TB_DLF)
+		jc |= JC_BTN_LEFT;
+	if (b & TB_L4)
+		jc |= code_to_jc(t->back[0], fA, fB, fX, fY);
+	if (b & TB_R4)
+		jc |= code_to_jc(t->back[1], fA, fB, fX, fY);
+	if (b & TB_L5)
+		jc |= code_to_jc(t->back[2], fA, fB, fX, fY);
+	if (b & TB_R5)
+		jc |= code_to_jc(t->back[3], fA, fB, fX, fY);
+	if (qam)
+		jc |= code_to_jc(t->qam, fA, fB, fX, fY);
 	out[0] = g_jcTimer[slot]++;
 	uint8_t chg = (g_battery_state[slot] == 1) ? 0x10 : 0x00;
 	out[1] = (uint8_t)((jc_battery_nibble(slot) << 4) | chg);
@@ -318,23 +366,11 @@ static void jc_input_prefix(int slot, uint8_t *out)
 	out[4] = (uint8_t)(jc >> 16);
 	jc_pack_stick(out + 5, g_in[slot].lx, g_in[slot].ly);
 	jc_pack_stick(out + 8, g_in[slot].rx, g_in[slot].ry);
-	out[11] = 0x09;  // rumble_input_report echo (some Switch fw expects nonzero)
+	out[11] =
+		0x09; // rumble_input_report echo (some Switch fw expects nonzero)
 }
 
 // ---- factory SPI dumps the host reads for calibration ----------------------
-static const uint8_t SPI_IMU_CAL[24] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-					 0x00, 0x40, 0x00, 0x40, 0x00, 0x40,
-					 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-					 0x3B, 0x34, 0x3B, 0x34, 0x3B, 0x34 };
-static const uint8_t SPI_PARAMS1[24] = { 0x50, 0xFD, 0x00, 0x00, 0xC6, 0x0F,
-					 0x0F, 0x30, 0x61, 0x96, 0x30, 0xF3,
-					 0xD4, 0x14, 0x54, 0x41, 0x15, 0x54,
-					 0xC7, 0x79, 0x9C, 0x33, 0x36, 0x63 };
-static const uint8_t SPI_PARAMS2[18] = { 0x0F, 0x30, 0x61, 0x96, 0x30, 0xF3,
-					 0xD4, 0x14, 0x54, 0x41, 0x15, 0x54,
-					 0xC7, 0x79, 0x9C, 0x33, 0x36, 0x63 };
-static const uint8_t SPI_COLOR[13] = { 0x32, 0x32, 0x32, 0xE6, 0xE6, 0xE6, 0x32,
-				       0x32, 0x32, 0x32, 0x32, 0x32, 0xFF };
 static uint8_t g_spi_stick_cal[18];
 static bool g_stick_cal_built;
 static void jc_pack12(uint8_t *o9, const uint16_t v[6])
@@ -351,7 +387,8 @@ static void jc_pack12(uint8_t *o9, const uint16_t v[6])
 }
 static void jc_build_stick_cal(void)
 {
-	if (g_stick_cal_built) return;
+	if (g_stick_cal_built)
+		return;
 	const uint16_t C = 2048, R = 1800;
 	uint16_t L[6] = { R, R, C, C, R, R };
 	uint16_t Rr[6] = { C, C, R, R, R, R };
@@ -364,7 +401,8 @@ static void jc_build_stick_cal(void)
 // fallback. Each Pro Controller has its own motion-cal region.
 static uint8_t g_user_cal[PP_NSLOT][0x100];
 static bool g_user_cal_init[PP_NSLOT];
-static void jc_spi_write(int slot, uint32_t addr, uint8_t len, const uint8_t *data, uint16_t avail)
+static void jc_spi_write(int slot, uint32_t addr, uint8_t len,
+			 const uint8_t *data, uint16_t avail)
 {
 	for (uint8_t i = 0; i < len && i < avail; i++) {
 		uint32_t a = addr + i;
@@ -381,33 +419,38 @@ static void spi_read(int slot, uint32_t addr, uint8_t len, uint8_t *dst)
 	for (uint8_t i = 0; i < len; i++) {
 		uint32_t a = addr + i;
 		uint8_t v = 0xFF;
-		if (a >= 0x6020 && a < 0x6020 + 24) v = SPI_IMU_CAL[a - 0x6020];
-		else if (a >= 0x603D && a < 0x603D + 18) v = g_spi_stick_cal[a - 0x603D];
-		else if (a >= 0x6050 && a < 0x6050 + 13) v = SPI_COLOR[a - 0x6050];
-		else if (a >= 0x6080 && a < 0x6080 + 24) v = SPI_PARAMS1[a - 0x6080];
-		else if (a >= 0x6098 && a < 0x6098 + 18) v = SPI_PARAMS2[a - 0x6098];
-		else if (a >= 0x8000 && a < 0x8100) v = g_user_cal[slot][a - 0x8000];
+		if (a >= 0x6020 && a < 0x6020 + 24)
+			v = SPI_IMU_CAL[a - 0x6020];
+		else if (a >= 0x603D && a < 0x603D + 18)
+			v = g_spi_stick_cal[a - 0x603D];
+		else if (a >= 0x6050 && a < 0x6050 + 13)
+			v = SPI_COLOR[a - 0x6050];
+		else if (a >= 0x6080 && a < 0x6080 + 24)
+			v = SPI_PARAMS1[a - 0x6080];
+		else if (a >= 0x6098 && a < 0x6098 + 18)
+			v = SPI_PARAMS2[a - 0x6098];
+		else if (a >= 0x8000 && a < 0x8100)
+			v = g_user_cal[slot][a - 0x8000];
 		dst[i] = v;
 	}
 }
 
-static const uint8_t BT_PAIR_2[31] = {
-	0x02, 0xE5, 0xC8, 0xE4, 0x92, 0x05, 0xFF, 0xC9, 0x8A, 0x7D, 0xEA,
-	0x15, 0xF6, 0x19, 0xBA, 0x82, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00,
-	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
-};
-
 // ---- per-slot reply FIFO ----------------------------------------------------
 #define JC_REPLEN 63
 #define JCQ_N 8
-typedef struct { uint8_t rid; uint8_t data[JC_REPLEN]; } jc_rep_t;
+typedef struct {
+	uint8_t rid;
+	uint8_t data[JC_REPLEN];
+} jc_rep_t;
 static jc_rep_t g_jcq[PP_NSLOT][JCQ_N];
 static uint8_t g_jcq_h[PP_NSLOT], g_jcq_t[PP_NSLOT];
 static void jc_enq(int slot, uint8_t rid, const uint8_t *d, uint8_t len)
 {
 	uint8_t nt = (uint8_t)((g_jcq_t[slot] + 1) % JCQ_N);
-	if (nt == g_jcq_h[slot]) return;  // full → drop; host re-requests on timeout
-	if (len > JC_REPLEN) len = JC_REPLEN;
+	if (nt == g_jcq_h[slot])
+		return; // full → drop; host re-requests on timeout
+	if (len > JC_REPLEN)
+		len = JC_REPLEN;
 	g_jcq[slot][g_jcq_t[slot]].rid = rid;
 	memset(g_jcq[slot][g_jcq_t[slot]].data, 0, JC_REPLEN);
 	memcpy(g_jcq[slot][g_jcq_t[slot]].data, d, len);
@@ -424,16 +467,19 @@ static void jc_subcmd(int slot, uint8_t sub, const uint8_t *args, uint16_t alen)
 	jc_input_prefix(slot, p);
 	p[13] = sub;
 	switch (sub) {
-	case 0x01: {  // manual BT pairing: 3-stage key exchange
+	case 0x01: { // manual BT pairing: 3-stage key exchange
 		uint8_t ty = (alen >= 1) ? args[0] : 3;
 		p[12] = 0x81;
 		if (ty == 1) {
 			p[14] = 0x01;
 			memcpy(&p[15], mac, 6);
-			p[21] = 0x00; p[22] = 0x25; p[23] = 0x08;
+			p[21] = 0x00;
+			p[22] = 0x25;
+			p[23] = 0x08;
 			static const uint8_t NAME[14] = {
 				0x50, 0x72, 0x6F, 0x20, 0x43, 0x6F, 0x6E,
-				0x74, 0x72, 0x6F, 0x6C, 0x6C, 0x65, 0x72 };
+				0x74, 0x72, 0x6F, 0x6C, 0x6C, 0x65, 0x72
+			};
 			memcpy(&p[24], NAME, 14);
 			p[43] = 0x68;
 		} else if (ty == 2) {
@@ -443,58 +489,71 @@ static void jc_subcmd(int slot, uint8_t sub, const uint8_t *args, uint16_t alen)
 		}
 		break;
 	}
-	case 0x02:  // device info
+	case 0x02: // device info
 		p[12] = 0x82;
 		p[14] = 0x03;
 		p[15] = 0x48;
-		p[16] = 0x03;  // Pro Controller
+		p[16] = 0x03; // Pro Controller
 		p[17] = 0x02;
 		memcpy(&p[18], mac, 6);
 		p[24] = 0x01;
 		p[25] = 0x01;
 		break;
-	case 0x10: {  // SPI flash read → echo [addr][len] then data
-		if (alen < 5) { p[12] = 0x80; break; }
+	case 0x10: { // SPI flash read → echo [addr][len] then data
+		if (alen < 5) {
+			p[12] = 0x80;
+			break;
+		}
 		uint32_t a = (uint32_t)args[0] | ((uint32_t)args[1] << 8) |
-			     ((uint32_t)args[2] << 16) | ((uint32_t)args[3] << 24);
+			     ((uint32_t)args[2] << 16) |
+			     ((uint32_t)args[3] << 24);
 		uint8_t rl = args[4];
-		if (rl > 0x1D) rl = 0x1D;
+		if (rl > 0x1D)
+			rl = 0x1D;
 		p[12] = 0x90;
-		p[14] = args[0]; p[15] = args[1]; p[16] = args[2]; p[17] = args[3];
+		p[14] = args[0];
+		p[15] = args[1];
+		p[16] = args[2];
+		p[17] = args[3];
 		p[18] = rl;
 		spi_read(slot, a, rl, &p[19]);
 		break;
 	}
-	case 0x11:  // SPI flash write → mirror user cal
+	case 0x11: // SPI flash write → mirror user cal
 		if (alen >= 5) {
-			uint32_t a = (uint32_t)args[0] | ((uint32_t)args[1] << 8) |
-				     ((uint32_t)args[2] << 16) | ((uint32_t)args[3] << 24);
+			uint32_t a = (uint32_t)args[0] |
+				     ((uint32_t)args[1] << 8) |
+				     ((uint32_t)args[2] << 16) |
+				     ((uint32_t)args[3] << 24);
 			jc_spi_write(slot, a, args[4], &args[5],
 				     (alen > 5) ? (uint16_t)(alen - 5) : 0);
 		}
 		p[12] = 0x80;
 		break;
-	case 0x03:  // set input report mode
+	case 0x03: // set input report mode
 		if (alen >= 1 && args[0] == 0x30)
 			g_reportMode[slot] = 0x30;
 		p[12] = 0x80;
 		break;
-	case 0x04:  // trigger elapsed time
+	case 0x04: // trigger elapsed time
 		p[12] = 0x83;
-		p[15] = 0xCC; p[17] = 0xEE; p[19] = 0xFF;
+		p[15] = 0xCC;
+		p[17] = 0xEE;
+		p[19] = 0xFF;
 		break;
-	case 0x21:  // NFC/IR config
+	case 0x21: // NFC/IR config
 		p[12] = 0xA0;
 		break;
 	default:
-		p[12] = 0x80;  // generic ACK
+		p[12] = 0x80; // generic ACK
 		break;
 	}
 	jc_enq(slot, 0x21, p, JC_REPLEN);
 }
 
 // ---- emu interface ---------------------------------------------------------
-static void swpro_set(int slot, uint8_t rid, uint8_t type, const uint8_t *b, uint16_t n)
+static void swpro_set(int slot, uint8_t rid, uint8_t type, const uint8_t *b,
+		      uint16_t n)
 {
 	if (type != PP_HID_OUTPUT || n < 1)
 		return;
@@ -503,13 +562,22 @@ static void swpro_set(int slot, uint8_t rid, uint8_t type, const uint8_t *b, uin
 	uint8_t id;
 	const uint8_t *p;
 	uint16_t pn;
-	if (rid == 0) { id = b[0]; p = b + 1; pn = (uint16_t)(n - 1); }
-	else { id = rid; p = b; pn = n; }
-	if (id == 0x80) {  // USB handshake
-		if (pn < 1) return;
+	if (rid == 0) {
+		id = b[0];
+		p = b + 1;
+		pn = (uint16_t)(n - 1);
+	} else {
+		id = rid;
+		p = b;
+		pn = n;
+	}
+	if (id == 0x80) { // USB handshake
+		if (pn < 1)
+			return;
 		if (p[0] == 0x01) {
-			uint8_t mac[6]; jc_mac(slot, mac);
-			uint8_t d[9] = { 0x01, 0x00, 0x03, mac[0], mac[1],
+			uint8_t mac[6];
+			jc_mac(slot, mac);
+			uint8_t d[9] = { 0x01,	 0x00,	 0x03,	 mac[0], mac[1],
 					 mac[2], mac[3], mac[4], mac[5] };
 			jc_enq(slot, 0x81, d, 9);
 		} else if (p[0] == 0x02) {
@@ -521,13 +589,15 @@ static void swpro_set(int slot, uint8_t rid, uint8_t type, const uint8_t *b, uin
 		}
 		return;
 	}
-	if (id == 0x01) {  // [timer][rumble x8][subcmd][args...]
-		if (pn < 10) return;
+	if (id == 0x01) { // [timer][rumble x8][subcmd][args...]
+		if (pn < 10)
+			return;
 		jc_rumble(slot, p, pn);
-		jc_subcmd(slot, p[9], p + 10, (pn > 10) ? (uint16_t)(pn - 10) : 0);
+		jc_subcmd(slot, p[9], p + 10,
+			  (pn > 10) ? (uint16_t)(pn - 10) : 0);
 		return;
 	}
-	if (id == 0x10) {  // rumble-only
+	if (id == 0x10) { // rumble-only
 		jc_rumble(slot, p, pn);
 		return;
 	}
@@ -548,12 +618,18 @@ static void swpro_build_30(int slot, uint8_t out[63])
 	int16_t gyaw = gscale((int16_t)g_in[slot].gz);
 	for (int k = 0; k < 3; k++) {
 		int o = 12 + k * 12;
-		out[o + 0] = aX & 0xFF;    out[o + 1] = (aX >> 8) & 0xFF;
-		out[o + 2] = aY & 0xFF;    out[o + 3] = (aY >> 8) & 0xFF;
-		out[o + 4] = aZ & 0xFF;    out[o + 5] = (aZ >> 8) & 0xFF;
-		out[o + 6] = groll & 0xFF; out[o + 7] = (groll >> 8) & 0xFF;
-		out[o + 8] = gpitch & 0xFF; out[o + 9] = (gpitch >> 8) & 0xFF;
-		out[o + 10] = gyaw & 0xFF; out[o + 11] = (gyaw >> 8) & 0xFF;
+		out[o + 0] = aX & 0xFF;
+		out[o + 1] = (aX >> 8) & 0xFF;
+		out[o + 2] = aY & 0xFF;
+		out[o + 3] = (aY >> 8) & 0xFF;
+		out[o + 4] = aZ & 0xFF;
+		out[o + 5] = (aZ >> 8) & 0xFF;
+		out[o + 6] = groll & 0xFF;
+		out[o + 7] = (groll >> 8) & 0xFF;
+		out[o + 8] = gpitch & 0xFF;
+		out[o + 9] = (gpitch >> 8) & 0xFF;
+		out[o + 10] = gyaw & 0xFF;
+		out[o + 11] = (gyaw >> 8) & 0xFF;
 	}
 }
 
@@ -570,13 +646,14 @@ static uint16_t swpro_build(int slot, uint8_t *out, uint8_t *rid)
 		return JC_REPLEN;
 	}
 	if (g_reportMode[slot] != 0x30)
-		return 0;  // not until the host selects report mode 0x30
+		return 0; // not until the host selects report mode 0x30
 	// Rate-limit the 0x30 stream by the configured Switch cadence (the console
 	// integrates 3 IMU samples/report at a fixed ~5 ms/sample). Per-slot timer.
 	static uint32_t last_ms[PP_NSLOT];
 	uint8_t rate = settings()->sw_pro_rate;
 	uint32_t interval = (rate == 2) ? SW_STREAM_MS :
-			    (rate == 1) ? SW_PRO_REPORT_MS_120 : SW_PRO_REPORT_MS;
+			    (rate == 1) ? SW_PRO_REPORT_MS_120 :
+					  SW_PRO_REPORT_MS;
 	uint32_t t = to_ms_since_boot(get_absolute_time());
 	if (t - last_ms[slot] < interval)
 		return 0;
@@ -587,7 +664,9 @@ static uint16_t swpro_build(int slot, uint8_t *out, uint8_t *rid)
 }
 
 const emu_mode_t emu_switch_pro = {
-	.vid = 0x057E, .pid = 0x2009, .bcd = 0x0220,
+	.vid = 0x057E,
+	.pid = 0x2009,
+	.bcd = 0x0220,
 	.product = "Pro Controller",
 	.report_desc = SWPRO_HID_DESC,
 	.report_desc_len = sizeof(SWPRO_HID_DESC),

+ 7 - 37
PicoPuck/src/puck/triton.h

@@ -11,46 +11,16 @@
 
 #include <stdint.h>
 #include "config/picopuck_config.h"
-
-// Triton button masks (the 32-bit field at report 0x45 bytes [2..5]).
-#define TB_A 0x1u
-#define TB_B 0x2u
-#define TB_X 0x4u
-#define TB_Y 0x8u
-#define TB_QAM 0x10u
-#define TB_R3 0x20u
-#define TB_VIEW 0x40u
-#define TB_R4 0x80u
-#define TB_R5 0x100u
-#define TB_RB 0x200u
-#define TB_DDN 0x400u
-#define TB_DRT 0x800u
-#define TB_DLF 0x1000u
-#define TB_DUP 0x2000u
-#define TB_MENU 0x4000u
-#define TB_L3 0x8000u
-#define TB_STEAM 0x10000u
-#define TB_L4 0x20000u
-#define TB_L5 0x40000u
-#define TB_LB 0x80000u
-#define TB_RPADT 0x200000u
-#define TB_RPADC 0x400000u
-#define TB_LPADT 0x2000000u
-#define TB_LPADC 0x4000000u
-#define TB_R2 0x800000u
-#define TB_L2 0x8000000u
-// Virtual PS targets (remap-only; bits the SC2 0x45 report never uses).
-#define TB_TOUCH 0x40000000u  // PS touchpad click
-#define TB_MUTE 0x80000000u   // PS5 mute
+#include "triton_masks.h" // shared TB_* button masks + SW_TRIG_ON + CHORD_BACK4
 
 // Decoded controller input, one per slot.
 typedef struct {
-	uint32_t buttons;             // TB_* bits
-	int16_t lx, ly, rx, ry;       // sticks, center 0
-	uint8_t lt, rt;               // triggers 0..255
-	int16_t lpx, lpy, rpx, rpy;   // trackpads (SC2 only; 0 for generic pads)
-	int16_t ax, ay, az;           // accelerometer (0 if none)
-	int16_t gx, gy, gz;           // gyroscope (0 if none)
+	uint32_t buttons; // TB_* bits
+	int16_t lx, ly, rx, ry; // sticks, center 0
+	uint8_t lt, rt; // triggers 0..255
+	int16_t lpx, lpy, rpx, rpy; // trackpads (SC2 only; 0 for generic pads)
+	int16_t ax, ay, az; // accelerometer (0 if none)
+	int16_t gx, gy, gz; // gyroscope (0 if none)
 } puck_input_t;
 
 extern puck_input_t g_in[PP_NSLOT];

+ 4 - 35
PicoPuck/src/usb/puck_desc.c

@@ -1,43 +1,12 @@
-// puck_desc.c — the cloned Valve puck HID report descriptor (see puck_desc.h).
+// puck_desc.c — PUCK_HID_DESC now lives in the shared source of truth
+// (OpenPuck/src/common/hid_reports.c), linked by both firmwares. This file only
+// provides PicoPuck's convenience length + the size static-assert (see .h).
 //
 // SPDX-License-Identifier: AGPL-3.0-or-later
 #include "usb/puck_desc.h"
+#include "hid_reports.h"
 #include "tusb.h"
 
-const uint8_t PUCK_HID_DESC[] = {
-	0x05, 0x01, 0x09, 0x02, 0xA1, 0x01, 0x85, 0x40, 0x09, 0x01, 0xA1, 0x00,
-	0x05, 0x09, 0x19, 0x01, 0x29, 0x02, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01,
-	0x95, 0x02, 0x81, 0x02, 0x75, 0x06, 0x95, 0x01, 0x81, 0x01, 0x05, 0x01,
-	0x09, 0x30, 0x09, 0x31, 0x15, 0x81, 0x25, 0x7F, 0x75, 0x08, 0x95, 0x02,
-	0x81, 0x06, 0x95, 0x01, 0x09, 0x38, 0x81, 0x06, 0x05, 0x0C, 0x0A, 0x38,
-	0x02, 0x95, 0x01, 0x81, 0x06, 0xC0, 0xC0, 0x05, 0x01, 0x09, 0x06, 0xA1,
-	0x01, 0x85, 0x41, 0x05, 0x07, 0x19, 0xE0, 0x29, 0xE7, 0x15, 0x00, 0x25,
-	0x01, 0x75, 0x01, 0x95, 0x08, 0x81, 0x02, 0x81, 0x01, 0x19, 0x00, 0x29,
-	0x65, 0x15, 0x00, 0x25, 0x65, 0x75, 0x08, 0x95, 0x06, 0x81, 0x00, 0xC0,
-	0x06, 0x00, 0xFF, 0x09, 0x01, 0xA1, 0x01, 0x85, 0x42, 0x15, 0x00, 0x26,
-	0xFF, 0x00, 0x75, 0x08, 0x95, 0x35, 0x09, 0x42, 0x81, 0x02, 0x85, 0x44,
-	0x15, 0x00, 0x26, 0xFF, 0x00, 0x75, 0x08, 0x95, 0x05, 0x09, 0x44, 0x81,
-	0x02, 0x85, 0x79, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x75, 0x08, 0x95, 0x01,
-	0x09, 0x79, 0x81, 0x02, 0x85, 0x43, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x75,
-	0x08, 0x95, 0x0E, 0x09, 0x43, 0x81, 0x02, 0x85, 0x7B, 0x15, 0x00, 0x26,
-	0xFF, 0x00, 0x75, 0x08, 0x95, 0x0C, 0x09, 0x7B, 0x81, 0x02, 0x85, 0x45,
-	0x15, 0x00, 0x26, 0xFF, 0x00, 0x75, 0x08, 0x95, 0x2D, 0x09, 0x45, 0x81,
-	0x02, 0x85, 0x80, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x75, 0x08, 0x95, 0x09,
-	0x09, 0x80, 0x91, 0x02, 0x85, 0x81, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x75,
-	0x08, 0x95, 0x07, 0x09, 0x81, 0x91, 0x02, 0x85, 0x82, 0x15, 0x00, 0x26,
-	0xFF, 0x00, 0x75, 0x08, 0x95, 0x03, 0x09, 0x82, 0x91, 0x02, 0x85, 0x83,
-	0x15, 0x00, 0x26, 0xFF, 0x00, 0x75, 0x08, 0x95, 0x09, 0x09, 0x83, 0x91,
-	0x02, 0x85, 0x84, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x75, 0x08, 0x95, 0x08,
-	0x09, 0x84, 0x91, 0x02, 0x85, 0x85, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x75,
-	0x08, 0x95, 0x03, 0x09, 0x85, 0x91, 0x02, 0x85, 0x86, 0x15, 0x00, 0x26,
-	0xFF, 0x00, 0x75, 0x08, 0x95, 0x03, 0x09, 0x86, 0x91, 0x02, 0x85, 0x87,
-	0x15, 0x00, 0x26, 0xFF, 0x00, 0x75, 0x08, 0x95, 0x3F, 0x09, 0x87, 0x91,
-	0x02, 0x85, 0x89, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x75, 0x08, 0x95, 0x3F,
-	0x09, 0x89, 0x91, 0x02, 0x85, 0x88, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x75,
-	0x08, 0x95, 0x3F, 0x09, 0x88, 0x91, 0x02, 0x85, 0x01, 0x95, 0x3F, 0x09,
-	0x01, 0xB1, 0x02, 0x85, 0x02, 0x95, 0x3F, 0x09, 0x01, 0xB1, 0x02, 0xC0
-};
-
 const size_t PUCK_HID_DESC_LEN = sizeof(PUCK_HID_DESC);
 
 TU_VERIFY_STATIC(sizeof(PUCK_HID_DESC) == PUCK_HID_DESC_SIZE,

+ 5 - 8
PicoPuck/src/usb/puck_desc.h

@@ -6,17 +6,14 @@
 
 #include <stdint.h>
 #include <stddef.h>
+#include "hid_reports.h" // PUCK_HID_DESC lives in the shared source of truth
 
-// Byte-for-byte the descriptor a real Valve puck presents on each of its four
-// control interfaces: mouse(0x40) + keyboard(0x41) + vendor(FF00) with input
-// reports 0x42/0x43/0x44/0x45/0x79/0x7B, output reports 0x80-0x89, and 63-byte
-// FEATURE command reports on report ids 0x01/0x02. Copied verbatim from
-// OpenPuck/puck_hid.cpp so Steam's SDL driver sees an identical device.
-// Compile-time size, needed by the config-descriptor macros. Verified against
-// the actual array with a static assert in puck_desc.c.
+// PUCK_HID_DESC (the byte-for-byte Valve puck descriptor) is defined in
+// OpenPuck/src/common/hid_reports.c and declared in hid_reports.h — shared by
+// both firmwares. Compile-time size, needed by the config-descriptor macros;
+// verified against the actual array with a static assert in puck_desc.c.
 #define PUCK_HID_DESC_SIZE 372
 
-extern const uint8_t PUCK_HID_DESC[];
 extern const size_t PUCK_HID_DESC_LEN;
 
 #endif // PICOPUCK_PUCK_DESC_H