Ver código fonte

even more code reuse

Jari 2 meses atrás
pai
commit
46a6504243

+ 8 - 44
OpenPuck/mode_hidgyro.cpp

@@ -1,6 +1,7 @@
 #include "mode_hidgyro.h"
 #include "triton.h"
 #include "gamepad_util.h"
+#include "src/common/report_build.h"
 #include "config.h"
 #include "haptics.h"
 #include "bonds.h"
@@ -131,50 +132,13 @@ static ds4_setcb_t const DS4_SETCB[NSLOT] = { hidGyroSet0, hidGyroSet1,
 // usbSlot drives the per-HID counters; slot (bond) is the controller whose decoded input feeds the report.
 static void hidGyroBuild(uint8_t usbSlot, uint8_t slot, uint8_t out[63])
 {
-	uint32_t b = psButtonsFromSteam(g_in[slot].buttons);
-	bool lTouch = (b & TB_LPADT) || (b & TB_LPADC),
-	     rTouch = (b & TB_RPADT) || (b & TB_RPADC);
-	memset(out, 0, 63);
-	out[0] = swStick(g_in[slot].lx, false);
-	out[1] = swStick(g_in[slot].ly, true);
-	out[2] = swStick(g_in[slot].rx, false);
-	out[3] = swStick(g_in[slot].ry, true);
-	out[4] = psHatNibble(b) | psFaceNibble(b);
-	out[5] = psShouldersByte(b, g_in[slot].lt, g_in[slot].rt);
-	static uint8_t ctr[NSLOT] = { 0 };
-	out[6] = ((ctr[usbSlot]++ & 0x0F) << 4) |
-		 ((b & TB_TOUCH || b & TB_LPADC || b & TB_RPADC) ? 0x02 : 0) |
-		 ((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[29] = DS4_STATUS_USB;
-	if (lTouch || rTouch) {
-		uint16_t lx, ly, rx, ry;
-		steamPadsToTouch(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[NSLOT] = { 0 };
-		out[32] = 1;
-		out[33] = tstamp[usbSlot]++;
-		touchPackPads(out + 34, lTouch, rTouch, lx, ly, rx, ry);
-	} else {
-		out[32] = 0;
-		touchPackPads(
-			out + 34, false, false, 0, 0, 0,
-			0); // contact 0x80 -- memset(0) reads as touch @0,0
-	}
+	// Report layout is in the shared builder (src/common/report_build.c); fill
+	// the remap config from OpenPuck's active-type globals + per-usbSlot seq.
+	report_cfg_t cfg = { g_abSwap != 0,
+			     { g_back[0], g_back[1], g_back[2], g_back[3] },
+			     g_qamMap };
+	static report_seq_t seq[NSLOT];
+	build_ds4(&g_in[slot], &cfg, &seq[usbSlot], out);
 }
 
 // Dynamic-mount mode: begin() is unused (setup() calls beginPool()+usbReenumerate instead).

+ 11 - 81
OpenPuck/mode_ps3.cpp

@@ -1,6 +1,7 @@
 #include "mode_ps3.h"
 #include "triton.h"
 #include "gamepad_util.h"
+#include "src/common/report_build.h"
 #include "config.h"
 #include "haptics.h"
 #include "bonds.h"
@@ -172,98 +173,27 @@ static void ds3SetReport(uint8_t rid, hid_report_type_t type, uint8_t const *b,
 // SC2 IMU int16 (center 0) -> DS3 10-bit unsigned (center 511), little-endian on the wire (low byte first).
 // >>6 maps the full int16 swing onto roughly +/-512 around center; the PS3 applies its own calibration so
 // exact scale is not critical. Writes 2 bytes at out[0..1].
-static void ds3Imu(uint8_t *out, int16_t v)
-{
-	int32_t e = 511 + ((int32_t)v >> 6);
-	if (e < 0)
-		e = 0;
-	if (e > 1023)
-		e = 1023;
-	out[0] = (uint8_t)(e & 0xFF);
-	out[1] = (uint8_t)((e >> 8) & 0xFF);
-}
 
 // Build the 48-byte input-report PAYLOAD (the stack prepends report id 0x01 -> 49-byte Sixaxis report).
 // Offsets are the genuine report's rd[] minus one (rd[0] is the prepended id).
 static void ds3Build(uint8_t slot, uint8_t out[48])
 {
-	uint32_t b = psButtonsFromSteam(g_in[slot].buttons);
-	bool l2 = (g_in[slot].lt > SW_TRIG_ON) || (b & TB_L2);
-	bool r2 = (g_in[slot].rt > SW_TRIG_ON) || (b & TB_R2);
-	memset(out, 0, 48);
-
-	// out[0] reserved (rd[1]); if it were 0xFF the kernel/console would drop the report -- keep 0.
-
-	// out[1] (rd[2]): Select(bit0) L3 R3 Start(bit3) Up Right Down Left.
-	// SC2 View -> Start, Menu -> Select: matches the physical button positions on the puck/controller (the
-	// opposite of the PS5/DS4 Create/Options convention, which read reversed on the DS3 in testing).
-	out[1] = ((b & TB_MENU) ? 0x01 : 0) | ((b & TB_L3) ? 0x02 : 0) |
-		 ((b & TB_R3) ? 0x04 : 0) | ((b & TB_VIEW) ? 0x08 : 0) |
-		 ((b & TB_DUP) ? 0x10 : 0) | ((b & TB_DRT) ? 0x20 : 0) |
-		 ((b & TB_DDN) ? 0x40 : 0) | ((b & TB_DLF) ? 0x80 : 0);
-
-	// out[2] (rd[3]): L2 R2 L1 R1 Triangle Circle Cross Square. g_abSwap swaps the A/B + X/Y face pair.
-	uint8_t tri, cir, crs, sqr;
-	if (g_abSwap) {
-		tri = (b & TB_A) ? 0x10 : 0;
-		cir = (b & TB_B) ? 0x20 : 0;
-		crs = (b & TB_X) ? 0x40 : 0;
-		sqr = (b & TB_Y) ? 0x80 : 0;
-	} else {
-		tri = (b & TB_Y) ? 0x10 : 0;
-		cir = (b & TB_B) ? 0x20 : 0;
-		crs = (b & TB_A) ? 0x40 : 0;
-		sqr = (b & TB_X) ? 0x80 : 0;
-	}
-	out[2] = (l2 ? 0x01 : 0) | (r2 ? 0x02 : 0) | ((b & TB_LB) ? 0x04 : 0) |
-		 ((b & TB_RB) ? 0x08 : 0) | tri | cir | crs | sqr;
-
-	// out[3] (rd[4]): PS button
-	out[3] = (b & TB_STEAM) ? 0x01 : 0;
-
-	// out[5..8] (rd[6..9]): LX LY RX RY, center 0x80
-	out[5] = swStick(g_in[slot].lx, false);
-	out[6] = swStick(g_in[slot].ly, true);
-	out[7] = swStick(g_in[slot].rx, false);
-	out[8] = swStick(g_in[slot].ry, true);
-
-	// out[13..24] (rd[14..25]): analog pressures, order Up Right Down Left L2 R2 L1 R1 Tri Cir Cross Square
-	out[13] = (b & TB_DUP) ? 0xFF : 0;
-	out[14] = (b & TB_DRT) ? 0xFF : 0;
-	out[15] = (b & TB_DDN) ? 0xFF : 0;
-	out[16] = (b & TB_DLF) ? 0xFF : 0;
-	out[17] = g_in[slot].lt;
-	out[18] = g_in[slot].rt;
-	out[19] = (b & TB_LB) ? 0xFF : 0;
-	out[20] = (b & TB_RB) ? 0xFF : 0;
-	out[21] = tri ? 0xFF : 0;
-	out[22] = cir ? 0xFF : 0;
-	out[23] = crs ? 0xFF : 0;
-	out[24] = sqr ? 0xFF : 0;
-
-	// out[28..29] (rd[29..30]): connection/charge status. Battery "full"; cosmetic on the PS3 over USB
-	// (the console knows it's wired from enumeration, not this byte).
-	out[28] = 0x00;
-	out[29] = 0x05; // battery level: full
-
-	// out[40..47] (rd[41..48]): accel X, accel Z, accel Y, gyro Z -- each 10-bit LE, center 511.
-	ds3Imu(out + 40, g_in[slot].ax);
-	ds3Imu(out + 42, g_in[slot].az);
-	ds3Imu(out + 44, g_in[slot].ay);
-	ds3Imu(out + 46, g_in[slot].gz);
+	// Report layout is in the shared builder (src/common/report_build.c).
+	report_cfg_t cfg = { g_abSwap != 0,
+			     { g_back[0], g_back[1], g_back[2], g_back[3] },
+			     g_qamMap };
+	build_ds3(&g_in[slot], &cfg, out);
 }
 
 // Neutral input report: centered sticks + IMU, no buttons. Streamed when no controller is linked so the PS3
 // keeps the Sixaxis "alive" (and a games/XMB controller slot) instead of seeing a silent endpoint.
 static void ds3Neutral(uint8_t out[48])
 {
-	memset(out, 0, 48);
-	out[5] = out[6] = out[7] = out[8] = 0x80; // sticks centered
-	out[29] = 0x05; // battery full
-	ds3Imu(out + 40, 0);
-	ds3Imu(out + 42, 0);
-	ds3Imu(out + 44, 0);
-	ds3Imu(out + 46, 0);
+	// A zeroed input through the shared builder IS the neutral report (centered
+	// sticks + IMU, no buttons, battery full).
+	puck_input_t neutral = { 0 };
+	report_cfg_t cfg = { false, { 0, 0, 0, 0 }, 0 };
+	build_ds3(&neutral, &cfg, out);
 }
 
 void Ps3Controller::usbIdentity()

+ 7 - 34
OpenPuck/mode_ps5.cpp

@@ -1,6 +1,7 @@
 #include "mode_ps5.h"
 #include "triton.h"
 #include "gamepad_util.h"
+#include "src/common/report_build.h"
 #include "config.h"
 #include "haptics.h"
 #include "bonds.h"
@@ -139,40 +140,12 @@ static ps5_setcb_t const PS5_SETCB[NSLOT] = { ps5Set0, ps5Set1, ps5Set2,
 // usbSlot drives the per-HID sequence counter; bond is the controller whose decoded input feeds the report.
 static void ps5Build(uint8_t usbSlot, uint8_t slot, uint8_t out[63])
 {
-	uint32_t b = psButtonsFromSteam(g_in[slot].buttons);
-	bool lTouch = (b & TB_LPADT) || (b & TB_LPADC),
-	     rTouch = (b & TB_RPADT) || (b & TB_RPADC);
-	memset(out, 0, 63);
-	out[0] = swStick(g_in[slot].lx, false);
-	out[1] = swStick(g_in[slot].ly, true);
-	out[2] = swStick(g_in[slot].rx, false);
-	out[3] = swStick(g_in[slot].ry, true);
-	out[4] = g_in[slot].lt;
-	out[5] = g_in[slot].rt;
-	static uint8_t seq[NSLOT] = { 0 };
-	out[6] = seq[usbSlot]++;
-	out[7] = psHatNibble(b) | psFaceNibble(b);
-	out[8] = psShouldersByte(b, g_in[slot].lt, g_in[slot].rt);
-	out[9] = ((b & TB_STEAM) ? 0x01 : 0) |
-		 ((b & TB_TOUCH || b & TB_LPADC || b & 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;
-	uint16_t lx, ly, rx, ry;
-	steamPadsToTouch(b, PS5_TOUCH_H, g_in[slot].lpx, g_in[slot].lpy,
-			 g_in[slot].rpx, g_in[slot].rpy, &lx, &ly, &rx, &ry);
-	touchPackPads(out + 32, lTouch, rTouch, lx, ly, rx, ry);
-	out[52] = PS5_STATUS_USB;
+	// Report layout is in the shared builder (src/common/report_build.c).
+	report_cfg_t cfg = { g_abSwap != 0,
+			     { g_back[0], g_back[1], g_back[2], g_back[3] },
+			     g_qamMap };
+	static report_seq_t seq[NSLOT];
+	build_ps5(&g_in[slot], &cfg, &seq[usbSlot], out);
 }
 
 // Dynamic-mount mode: begin() is unused (setup() calls beginPool()+usbReenumerate instead).

+ 188 - 0
OpenPuck/src/common/report_build.c

@@ -0,0 +1,188 @@
+// report_build.c — see report_build.h.
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+#include "report_build.h"
+#include <string.h>
+
+#define DS4_TOUCH_H 942
+#define DS4_STATUS_USB 0x1B
+#define PS5_TOUCH_H 1080
+#define PS5_STATUS_USB 0x1A
+
+uint32_t ps_buttons_remap(uint32_t raw, const report_cfg_t *cfg)
+{
+	uint32_t b = raw;
+	if (cfg->qam && (b & TB_QAM)) {
+		b &= ~(uint32_t)TB_QAM;
+		b |= triton_from_code(cfg->qam);
+	}
+	if ((b & CHORD_BACK4) == CHORD_BACK4)
+		b &= ~(uint32_t)(TB_A | TB_B | TB_X | TB_Y);
+	if (b & TB_L4)
+		b |= triton_from_code(cfg->back[0]);
+	if (b & TB_R4)
+		b |= triton_from_code(cfg->back[1]);
+	if (b & TB_L5)
+		b |= triton_from_code(cfg->back[2]);
+	if (b & TB_R5)
+		b |= triton_from_code(cfg->back[3]);
+	return b;
+}
+
+uint16_t build_ds4(const puck_input_t *in, const report_cfg_t *cfg,
+		   report_seq_t *seq, uint8_t out[63])
+{
+	uint32_t b = ps_buttons_remap(in->buttons, cfg);
+	bool l_touch = (b & TB_LPADT) || (b & TB_LPADC);
+	bool r_touch = (b & TB_RPADT) || (b & TB_RPADC);
+	memset(out, 0, 63);
+	out[0] = sw_stick(in->lx, false);
+	out[1] = sw_stick(in->ly, true);
+	out[2] = sw_stick(in->rx, false);
+	out[3] = sw_stick(in->ry, true);
+	out[4] = ps_hat_nibble(b) | ps_face_nibble(b, cfg->ab_swap);
+	out[5] = ps_shoulders_byte(b, in->lt, in->rt);
+	out[6] = ((seq->ctr++ & 0x0F) << 4) |
+		 ((b & (TB_TOUCH | TB_LPADC | TB_RPADC)) ? 0x02 : 0) |
+		 ((b & TB_STEAM) ? 0x01 : 0);
+	out[7] = in->lt;
+	out[8] = in->rt;
+	out[12] = in->gx & 0xFF;
+	out[13] = in->gx >> 8;
+	out[14] = in->gz & 0xFF;
+	out[15] = in->gz >> 8;
+	out[16] = (-in->gy) & 0xFF;
+	out[17] = (-in->gy) >> 8;
+	out[18] = in->ax & 0xFF;
+	out[19] = in->ax >> 8;
+	out[20] = in->ay & 0xFF;
+	out[21] = in->ay >> 8;
+	out[22] = in->az & 0xFF;
+	out[23] = in->az >> 8;
+	out[29] = DS4_STATUS_USB;
+	if (l_touch || r_touch) {
+		uint16_t lx, ly, rx, ry;
+		steam_pads_to_touch(b, DS4_TOUCH_H, in->lpx, in->lpy, in->rpx,
+				    in->rpy, &lx, &ly, &rx, &ry);
+		out[32] = 1;
+		out[33] = seq->tstamp++;
+		touch_pack_pads(out + 34, l_touch, r_touch, lx, ly, rx, ry);
+	} else {
+		out[32] = 0;
+		touch_pack_pads(out + 34, false, false, 0, 0, 0, 0);
+	}
+	return 63;
+}
+
+uint16_t build_ps5(const puck_input_t *in, const report_cfg_t *cfg,
+		   report_seq_t *seq, uint8_t out[63])
+{
+	uint32_t b = ps_buttons_remap(in->buttons, cfg);
+	bool l_touch = (b & TB_LPADT) || (b & TB_LPADC);
+	bool r_touch = (b & TB_RPADT) || (b & TB_RPADC);
+	memset(out, 0, 63);
+	out[0] = sw_stick(in->lx, false);
+	out[1] = sw_stick(in->ly, true);
+	out[2] = sw_stick(in->rx, false);
+	out[3] = sw_stick(in->ry, true);
+	out[4] = in->lt;
+	out[5] = in->rt;
+	out[6] = seq->ctr++;
+	out[7] = ps_hat_nibble(b) | ps_face_nibble(b, cfg->ab_swap);
+	out[8] = ps_shoulders_byte(b, in->lt, in->rt);
+	out[9] = ((b & TB_STEAM) ? 0x01 : 0) |
+		 ((b & (TB_TOUCH | TB_LPADC | TB_RPADC)) ? 0x02 : 0) |
+		 ((b & TB_MUTE) ? 0x04 : 0);
+	out[15] = in->gx & 0xFF;
+	out[16] = in->gx >> 8;
+	out[17] = in->gz & 0xFF;
+	out[18] = in->gz >> 8;
+	out[19] = (-in->gy) & 0xFF;
+	out[20] = (-in->gy) >> 8;
+	out[21] = in->ax & 0xFF;
+	out[22] = in->ax >> 8;
+	out[23] = in->ay & 0xFF;
+	out[24] = in->ay >> 8;
+	out[25] = in->az & 0xFF;
+	out[26] = in->az >> 8;
+	uint16_t lx, ly, rx, ry;
+	steam_pads_to_touch(b, PS5_TOUCH_H, in->lpx, in->lpy, in->rpx, in->rpy,
+			    &lx, &ly, &rx, &ry);
+	touch_pack_pads(out + 32, l_touch, r_touch, lx, ly, rx, ry);
+	out[52] = PS5_STATUS_USB;
+	return 63;
+}
+
+// SC2 IMU int16 (center 0) → DS3 10-bit unsigned (center 511), little-endian.
+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;
+	out[0] = (uint8_t)(e & 0xFF);
+	out[1] = (uint8_t)((e >> 8) & 0xFF);
+}
+
+uint16_t build_ds3(const puck_input_t *in, const report_cfg_t *cfg,
+		   uint8_t out[48])
+{
+	uint32_t b = ps_buttons_remap(in->buttons, cfg);
+	bool l2 = (in->lt > SW_TRIG_ON) || (b & TB_L2);
+	bool r2 = (in->rt > SW_TRIG_ON) || (b & TB_R2);
+	memset(out, 0, 48);
+
+	// out[1]: Select L3 R3 Start Up Right Down Left. SC2 View→Start, Menu→Select.
+	out[1] = ((b & TB_MENU) ? 0x01 : 0) | ((b & TB_L3) ? 0x02 : 0) |
+		 ((b & TB_R3) ? 0x04 : 0) | ((b & TB_VIEW) ? 0x08 : 0) |
+		 ((b & TB_DUP) ? 0x10 : 0) | ((b & TB_DRT) ? 0x20 : 0) |
+		 ((b & TB_DDN) ? 0x40 : 0) | ((b & TB_DLF) ? 0x80 : 0);
+
+	// out[2]: L2 R2 L1 R1 Triangle Circle Cross Square (ab_swap swaps A/B + X/Y).
+	uint8_t tri, cir, crs, sqr;
+	if (cfg->ab_swap) {
+		tri = (b & TB_A) ? 0x10 : 0;
+		cir = (b & TB_B) ? 0x20 : 0;
+		crs = (b & TB_X) ? 0x40 : 0;
+		sqr = (b & TB_Y) ? 0x80 : 0;
+	} else {
+		tri = (b & TB_Y) ? 0x10 : 0;
+		cir = (b & TB_B) ? 0x20 : 0;
+		crs = (b & TB_A) ? 0x40 : 0;
+		sqr = (b & TB_X) ? 0x80 : 0;
+	}
+	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[5] = sw_stick(in->lx, false);
+	out[6] = sw_stick(in->ly, true);
+	out[7] = sw_stick(in->rx, false);
+	out[8] = sw_stick(in->ry, true);
+
+	// out[13..24]: analog pressures Up Right Down Left L2 R2 L1 R1 Tri Cir Crs Sqr.
+	out[13] = (b & TB_DUP) ? 0xFF : 0;
+	out[14] = (b & TB_DRT) ? 0xFF : 0;
+	out[15] = (b & TB_DDN) ? 0xFF : 0;
+	out[16] = (b & TB_DLF) ? 0xFF : 0;
+	out[17] = in->lt;
+	out[18] = in->rt;
+	out[19] = (b & TB_LB) ? 0xFF : 0;
+	out[20] = (b & TB_RB) ? 0xFF : 0;
+	out[21] = tri ? 0xFF : 0;
+	out[22] = cir ? 0xFF : 0;
+	out[23] = crs ? 0xFF : 0;
+	out[24] = sqr ? 0xFF : 0;
+
+	out[28] = 0x00;
+	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, in->ax);
+	ds3_imu(out + 42, in->az);
+	ds3_imu(out + 44, in->ay);
+	ds3_imu(out + 46, in->gz);
+	return 48;
+}

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

@@ -0,0 +1,51 @@
+// report_build.h — shared PlayStation-family USB report builders (DS4 / DualSense
+// / DS3). The byte layout (g_in → report bytes) is identical across OpenPuck and
+// PicoPuck; only the CONFIG SOURCE differs (OpenPuck globals vs PicoPuck
+// settings), so it's passed in via report_cfg_t. Each firmware just fills the cfg
+// and calls these.
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+#ifndef OPK_COMMON_REPORT_BUILD_H
+#define OPK_COMMON_REPORT_BUILD_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include "triton_input.h"
+#include "gamepad_util.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Per-mode remap config (the pieces the builders need that live in each
+// firmware's settings): A/B+X/Y swap, the four back-paddle codes, the QAM code.
+typedef struct {
+	bool ab_swap;
+	uint8_t back[4]; // L4/R4/L5/R5 → button code (0 = none)
+	uint8_t qam; // QAM (…) → button code (0 = none)
+} report_cfg_t;
+
+// Free-running per-interface sequence state (report counter + touch timestamp).
+typedef struct {
+	uint8_t ctr;
+	uint8_t tstamp;
+} report_seq_t;
+
+// Apply the QAM remap, the all-four-back-paddles chord guard, and the four
+// back-paddle remaps to a raw Triton button field (uses the shared code→TB_ map).
+uint32_t ps_buttons_remap(uint32_t raw, const report_cfg_t *cfg);
+
+// Build a report body into out[]. Return the body length. rid is fixed per mode
+// (0x01 for all three) and set by the caller.
+uint16_t build_ds4(const puck_input_t *in, const report_cfg_t *cfg,
+		   report_seq_t *seq, uint8_t out[63]); // DS4 / HID-gyro
+uint16_t build_ps5(const puck_input_t *in, const report_cfg_t *cfg,
+		   report_seq_t *seq, uint8_t out[63]); // DualSense
+uint16_t build_ds3(const puck_input_t *in, const report_cfg_t *cfg,
+		   uint8_t out[48]); // DualShock 3 / Sixaxis
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // OPK_COMMON_REPORT_BUILD_H

+ 21 - 0
OpenPuck/src/common/triton_input.h

@@ -0,0 +1,21 @@
+// triton_input.h — the canonical decoded controller input (one per slot), shared
+// by OpenPuck and PicoPuck. Every controller decodes into this and every output
+// personality reads it, so it must be one definition. (The decoders that FILL it
+// differ per transport — RF report 0x45 vs BT — and stay in each triton.{c,cpp}.)
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+#ifndef OPK_COMMON_TRITON_INPUT_H
+#define OPK_COMMON_TRITON_INPUT_H
+
+#include <stdint.h>
+
+typedef struct {
+	uint32_t buttons; // TB_* bits (see triton_masks.h)
+	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;
+
+#endif // OPK_COMMON_TRITON_INPUT_H

+ 4 - 10
OpenPuck/triton.h

@@ -39,15 +39,9 @@ void imuFrom45(const uint8_t *r, int16_t *ax, int16_t *ay, int16_t *az,
 // ---- shared decoded input (filled by rf_link.cpp once per fresh report 0x45, read by every mode) ----
 // One PuckInput per bond slot: each controller in a multi-slot puck has its own decoded input. The stream
 // modes (Switch, PS5, DS4) read g_in[s] for each active slot in their task() loop; the puck/lizard mode reads
-// g_in[slot] inside onReport45/onAuxReport to forward to hid[slot].
-struct PuckInput {
-	// raw Triton buttons (TB_*); per-mode builders apply their own chord masking
-	uint32_t buttons;
-	int16_t lx, ly, rx, ry; // sticks (int16, center 0)
-	uint8_t lt, rt; // triggers scaled 0..255 (trigU8)
-	int16_t lpx, lpy, rpx, rpy; // left / right trackpad coords (int16)
-	int16_t ax, ay, az; // accelerometer
-	int16_t gx, gy, gz; // gyroscope
-};
+// g_in[slot] inside onReport45/onAuxReport to forward to hid[slot]. The struct itself is the shared canonical
+// input (src/common/triton_input.h); PuckInput is OpenPuck's historical alias for it.
+#include "src/common/triton_input.h"
+typedef puck_input_t PuckInput;
 #include "bonds.h" // NSLOT
 extern PuckInput g_in[NSLOT];

+ 1 - 0
PicoPuck/CMakeLists.txt

@@ -69,6 +69,7 @@ target_sources(picopuck PRIVATE
     ${CMAKE_CURRENT_SOURCE_DIR}/../OpenPuck/src/common/hd_rumble.c
     ${CMAKE_CURRENT_SOURCE_DIR}/../OpenPuck/src/common/gamepad_util.c
     ${CMAKE_CURRENT_SOURCE_DIR}/../OpenPuck/src/common/usb_mount.c
+    ${CMAKE_CURRENT_SOURCE_DIR}/../OpenPuck/src/common/report_build.c
 )
 
 target_include_directories(picopuck PRIVATE

+ 16 - 47
PicoPuck/src/puck/mode_ds4.c

@@ -4,63 +4,32 @@
 //
 // SPDX-License-Identifier: AGPL-3.0-or-later
 #include "puck/emu.h"
-#include "gamepad_util.h"
 #include "puck/relay.h"
 #include "sys/settings.h"
 #include <string.h>
 #include "hid_reports.h"
+#include "report_build.h"
 
-#define DS4_TOUCH_H 942
-#define DS4_STATUS_USB 0x1B
 #define ET_DS4 2 // per-type config index (matches OpenPuck ET_DS4)
 
+// The DS4 report layout lives in the shared builder (common/report_build.c); here
+// we only fill the remap config from settings and hold the per-slot seq state.
+static report_cfg_t ds4_cfg(void)
+{
+	const pp_type_cfg_t *t = &settings()->type[ET_DS4];
+	report_cfg_t c = { .ab_swap = t->ab_swap,
+			   .back = { t->back[0], t->back[1], t->back[2],
+				     t->back[3] },
+			   .qam = t->qam };
+	return c;
+}
+
 static uint16_t ds4_build(int slot, uint8_t *out, uint8_t *rid)
 {
+	static report_seq_t seq[PP_NSLOT];
 	*rid = 0x01;
-	uint32_t b = g_in[slot].buttons;
-	bool lt_touch = (b & TB_LPADT) || (b & TB_LPADC);
-	bool rt_touch = (b & TB_RPADT) || (b & TB_RPADC);
-	memset(out, 0, 63);
-	out[0] = sw_stick(g_in[slot].lx, false);
-	out[1] = sw_stick(g_in[slot].ly, true);
-	out[2] = sw_stick(g_in[slot].rx, false);
-	out[3] = sw_stick(g_in[slot].ry, true);
-	out[4] = ps_hat_nibble(b) |
-		 ps_face_nibble(b, settings()->type[ET_DS4].ab_swap);
-	out[5] = ps_shoulders_byte(b, g_in[slot].lt, g_in[slot].rt);
-	static uint8_t ctr;
-	out[6] = ((ctr++ & 0x0F) << 4) |
-		 ((b & (TB_TOUCH | TB_LPADC | TB_RPADC)) ? 0x02 : 0) |
-		 ((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[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);
-		static uint8_t tstamp;
-		out[32] = 1;
-		out[33] = tstamp++;
-		touch_pack_pads(out + 34, lt_touch, rt_touch, lx, ly, rx, ry);
-	} else {
-		out[32] = 0;
-		touch_pack_pads(out + 34, false, false, 0, 0, 0, 0);
-	}
-	return 63;
+	report_cfg_t cfg = ds4_cfg();
+	return build_ds4(&g_in[slot], &cfg, &seq[slot], out);
 }
 
 static uint16_t ds4_get(int slot, uint8_t rid, uint8_t type, uint8_t *buf,

+ 12 - 65
PicoPuck/src/puck/mode_ps3.c

@@ -10,10 +10,11 @@
 //
 // SPDX-License-Identifier: AGPL-3.0-or-later
 #include "puck/emu.h"
-#include "gamepad_util.h"
 #include "puck/relay.h"
+#include "sys/settings.h"
 #include <string.h>
 #include "hid_reports.h"
+#include "report_build.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
@@ -116,73 +117,19 @@ static void ds3_set(int slot, uint8_t rid, uint8_t type, const uint8_t *b,
 	puck_rumble(slot, (uint16_t)p[4] * 257u, p[2] ? 0xFFFFu : 0u);
 }
 
-// SC2 IMU int16 (center 0) → DS3 10-bit unsigned (center 511), little-endian.
-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;
-	out[0] = (uint8_t)(e & 0xFF);
-	out[1] = (uint8_t)((e >> 8) & 0xFF);
-}
-
-// Build the 48-byte input payload (id 0x01 prepended by the stack → 49-byte
-// Sixaxis report). Offsets are the genuine report's rd[] minus one.
+// Input-report layout is in the shared builder (common/report_build.c). PS3 has
+// no dedicated per-type config; it reuses the DS4 DualShock config (back paddles
+// / QAM / A-B swap).
+#define ET_DS4 2
 static uint16_t ds3_build(int slot, uint8_t *out, uint8_t *rid)
 {
 	*rid = 0x01;
-	uint32_t b = g_in[slot].buttons;
-	bool l2 = (g_in[slot].lt > SW_TRIG_ON) || (b & TB_L2);
-	bool r2 = (g_in[slot].rt > SW_TRIG_ON) || (b & TB_R2);
-	memset(out, 0, 48);
-
-	// out[1]: Select L3 R3 Start Up Right Down Left. SC2 View→Start, Menu→Select
-	// (matches the physical button positions on the puck).
-	out[1] = ((b & TB_MENU) ? 0x01 : 0) | ((b & TB_L3) ? 0x02 : 0) |
-		 ((b & TB_R3) ? 0x04 : 0) | ((b & TB_VIEW) ? 0x08 : 0) |
-		 ((b & TB_DUP) ? 0x10 : 0) | ((b & TB_DRT) ? 0x20 : 0) |
-		 ((b & TB_DDN) ? 0x40 : 0) | ((b & TB_DLF) ? 0x80 : 0);
-
-	// out[2]: L2 R2 L1 R1 Triangle Circle Cross Square.
-	uint8_t tri = (b & TB_Y) ? 0x10 : 0;
-	uint8_t cir = (b & TB_B) ? 0x20 : 0;
-	uint8_t crs = (b & TB_A) ? 0x40 : 0;
-	uint8_t sqr = (b & TB_X) ? 0x80 : 0;
-	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[5] = sw_stick(g_in[slot].lx, false);
-	out[6] = sw_stick(g_in[slot].ly, true);
-	out[7] = sw_stick(g_in[slot].rx, false);
-	out[8] = sw_stick(g_in[slot].ry, true);
-
-	// out[13..24]: analog pressures Up Right Down Left L2 R2 L1 R1 Tri Cir Crs Sqr.
-	out[13] = (b & TB_DUP) ? 0xFF : 0;
-	out[14] = (b & TB_DRT) ? 0xFF : 0;
-	out[15] = (b & TB_DDN) ? 0xFF : 0;
-	out[16] = (b & TB_DLF) ? 0xFF : 0;
-	out[17] = g_in[slot].lt;
-	out[18] = g_in[slot].rt;
-	out[19] = (b & TB_LB) ? 0xFF : 0;
-	out[20] = (b & TB_RB) ? 0xFF : 0;
-	out[21] = tri ? 0xFF : 0;
-	out[22] = cir ? 0xFF : 0;
-	out[23] = crs ? 0xFF : 0;
-	out[24] = sqr ? 0xFF : 0;
-
-	out[28] = 0x00;
-	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);
-	ds3_imu(out + 42, g_in[slot].az);
-	ds3_imu(out + 44, g_in[slot].ay);
-	ds3_imu(out + 46, g_in[slot].gz);
-	return 48;
+	const pp_type_cfg_t *t = &settings()->type[ET_DS4];
+	report_cfg_t cfg = { .ab_swap = t->ab_swap,
+			     .back = { t->back[0], t->back[1], t->back[2],
+				       t->back[3] },
+			     .qam = t->qam };
+	return build_ds3(&g_in[slot], &cfg, out);
 }
 
 const emu_mode_t emu_ps3 = {

+ 9 - 39
PicoPuck/src/puck/mode_ps5.c

@@ -4,55 +4,25 @@
 //
 // SPDX-License-Identifier: AGPL-3.0-or-later
 #include "puck/emu.h"
-#include "gamepad_util.h"
 #include "puck/relay.h"
 #include "sys/settings.h"
 #include <string.h>
 #include "hid_reports.h"
+#include "report_build.h"
 
-#define PS5_TOUCH_H 1080
-#define PS5_STATUS_USB 0x1A
 #define ET_DS5 3 // per-type config index (matches OpenPuck ET_DS5)
 
+// Report layout is in the shared builder (common/report_build.c).
 static uint16_t ps5_build(int slot, uint8_t *out, uint8_t *rid)
 {
+	static report_seq_t seq[PP_NSLOT];
 	*rid = 0x01;
-	uint32_t b = g_in[slot].buttons;
-	bool lt_touch = (b & TB_LPADT) || (b & TB_LPADC);
-	bool rt_touch = (b & TB_RPADT) || (b & TB_RPADC);
-	memset(out, 0, 63);
-	out[0] = sw_stick(g_in[slot].lx, false);
-	out[1] = sw_stick(g_in[slot].ly, true);
-	out[2] = sw_stick(g_in[slot].rx, false);
-	out[3] = sw_stick(g_in[slot].ry, true);
-	out[4] = g_in[slot].lt;
-	out[5] = g_in[slot].rt;
-	static uint8_t seq;
-	out[6] = seq++;
-	out[7] = ps_hat_nibble(b) |
-		 ps_face_nibble(b, settings()->type[ET_DS5].ab_swap);
-	out[8] = ps_shoulders_byte(b, g_in[slot].lt, g_in[slot].rt);
-	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;
-	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);
-	touch_pack_pads(out + 32, lt_touch, rt_touch, lx, ly, rx, ry);
-	out[52] = PS5_STATUS_USB;
-	return 63;
+	const pp_type_cfg_t *t = &settings()->type[ET_DS5];
+	report_cfg_t cfg = { .ab_swap = t->ab_swap,
+			     .back = { t->back[0], t->back[1], t->back[2],
+				       t->back[3] },
+			     .qam = t->qam };
+	return build_ps5(&g_in[slot], &cfg, &seq[slot], out);
 }
 
 static uint16_t ps5_get(int slot, uint8_t rid, uint8_t type, uint8_t *buf,

+ 1 - 10
PicoPuck/src/puck/triton.h

@@ -12,16 +12,7 @@
 #include <stdint.h>
 #include "config/picopuck_config.h"
 #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)
-} puck_input_t;
+#include "triton_input.h" // shared canonical input struct (puck_input_t)
 
 extern puck_input_t g_in[PP_NSLOT];