From e95feaf4951b8dc774671a5d6a1c31d76d78e3ac Mon Sep 17 00:00:00 2001 From: ABeltramo Date: Wed, 26 Mar 2025 00:16:11 +0100 Subject: [PATCH] Protocol extension: DualSense adaptive trigger support (#102) --- src/ControlStream.c | 43 ++++++++++++++++++++++++++++++++++++++++++- src/FakeCallbacks.c | 5 +++++ src/Limelight.h | 14 +++++++++++--- 3 files changed, 58 insertions(+), 4 deletions(-) diff --git a/src/ControlStream.c b/src/ControlStream.c index cf1285c..8f17dbd 100644 --- a/src/ControlStream.c +++ b/src/ControlStream.c @@ -66,6 +66,22 @@ typedef struct _QUEUED_ASYNC_CALLBACK { uint8_t g; uint8_t b; } setControllerLed; + struct { + uint16_t controllerNumber; + /** + * 0x04 - Right trigger + * 0x08 - Left trigger + */ + uint8_t eventFlags; + uint8_t typeLeft; + uint8_t typeRight; + // arrays of size DS_EFFECT_PAYLOAD_SIZE + // this is an opaque payload that will be read directly from the joypad and set as is to the client controller + // if you are curious about the actual data, there's some rationale in + // https://gist.github.com/Nielk1/6d54cc2c00d2201ccb8c2720ad7538db + uint8_t left[DS_EFFECT_PAYLOAD_SIZE]; + uint8_t right[DS_EFFECT_PAYLOAD_SIZE]; + } dsAdaptiveTrigger; } data; LINKED_BLOCKING_QUEUE_ENTRY entry; } QUEUED_ASYNC_CALLBACK, *PQUEUED_ASYNC_CALLBACK; @@ -122,6 +138,7 @@ static PPLT_CRYPTO_CONTEXT decryptionCtx; #define IDX_RUMBLE_TRIGGER_DATA 9 #define IDX_SET_MOTION_EVENT 10 #define IDX_SET_RGB_LED 11 +#define IDX_DS_ADAPTIVE_TRIGGERS 12 #define CONTROL_STREAM_TIMEOUT_SEC 10 #define CONTROL_STREAM_LINGER_TIMEOUT_SEC 2 @@ -195,6 +212,7 @@ static const short packetTypesGen7Enc[] = { 0x5500, // Rumble triggers (Sunshine protocol extension) 0x5501, // Set motion event (Sunshine protocol extension) 0x5502, // Set RGB LED (Sunshine protocol extension) + 0x5503, // Set Adaptive Triggers (Sunshine protocol extension) }; static const char requestIdrFrameGen3[] = { 0, 0 }; @@ -960,6 +978,14 @@ static void asyncCallbackThreadFunc(void* context) { queuedCb->data.setMotionEventState.motionType, queuedCb->data.setMotionEventState.reportRateHz); break; + case IDX_DS_ADAPTIVE_TRIGGERS: + ListenerCallbacks.setAdaptiveTriggers(queuedCb->data.dsAdaptiveTrigger.controllerNumber, + queuedCb->data.dsAdaptiveTrigger.eventFlags, + queuedCb->data.dsAdaptiveTrigger.typeLeft, + queuedCb->data.dsAdaptiveTrigger.typeRight, + queuedCb->data.dsAdaptiveTrigger.left, + queuedCb->data.dsAdaptiveTrigger.right); + break; default: // Unhandled packet type from queueAsyncCallback() LC_ASSERT(false); @@ -975,7 +1001,8 @@ static bool needsAsyncCallback(unsigned short packetType) { packetType == packetTypes[IDX_RUMBLE_TRIGGER_DATA] || packetType == packetTypes[IDX_SET_MOTION_EVENT] || packetType == packetTypes[IDX_SET_RGB_LED] || - packetType == packetTypes[IDX_HDR_INFO]; + packetType == packetTypes[IDX_HDR_INFO] || + packetType == packetTypes[IDX_DS_ADAPTIVE_TRIGGERS]; } static void queueAsyncCallback(PNVCTL_ENET_PACKET_HEADER_V1 ctlHdr, int packetLength) { @@ -1026,6 +1053,20 @@ static void queueAsyncCallback(PNVCTL_ENET_PACKET_HEADER_V1 ctlHdr, int packetLe else if (ctlHdr->type == packetTypes[IDX_HDR_INFO]) { queuedCb->typeIndex = IDX_HDR_INFO; } + else if (ctlHdr->type == packetTypes[IDX_DS_ADAPTIVE_TRIGGERS]){ + BbGet16(&bb, &queuedCb->data.dsAdaptiveTrigger.controllerNumber); + BbGet8(&bb, &queuedCb->data.dsAdaptiveTrigger.eventFlags); + BbGet8(&bb, &queuedCb->data.dsAdaptiveTrigger.typeLeft); + BbGet8(&bb, &queuedCb->data.dsAdaptiveTrigger.typeRight); + + for(int i = 0; i < DS_EFFECT_PAYLOAD_SIZE; i++) { + BbGet8(&bb, &queuedCb->data.dsAdaptiveTrigger.left[i]); + } + for(int i = 0; i < DS_EFFECT_PAYLOAD_SIZE; i++) { + BbGet8(&bb, &queuedCb->data.dsAdaptiveTrigger.right[i]); + } + queuedCb->typeIndex = IDX_DS_ADAPTIVE_TRIGGERS; + } else { // Unhandled packet type from needsAsyncCallback() LC_ASSERT(false); diff --git a/src/FakeCallbacks.c b/src/FakeCallbacks.c index 62260b8..29fdf18 100644 --- a/src/FakeCallbacks.c +++ b/src/FakeCallbacks.c @@ -39,6 +39,7 @@ static void fakeClConnectionStatusUpdate(int connectionStatus) {} static void fakeClSetHdrMode(bool enabled) {} static void fakeClRumbleTriggers(uint16_t controllerNumber, uint16_t leftTriggerMotor, uint16_t rightTriggerMotor) {} static void fakeClSetMotionEventState(uint16_t controllerNumber, uint8_t motionType, uint16_t reportRateHz) {} +static void fakeClSetAdaptiveTriggers(uint16_t controllerNumber, uint8_t eventFlags, uint8_t typeLeft, uint8_t typeRight, uint8_t *left, uint8_t *right) {}; static void fakeClSetControllerLED(uint16_t controllerNumber, uint8_t r, uint8_t g, uint8_t b) {} static CONNECTION_LISTENER_CALLBACKS fakeClCallbacks = { @@ -54,6 +55,7 @@ static CONNECTION_LISTENER_CALLBACKS fakeClCallbacks = { .rumbleTriggers = fakeClRumbleTriggers, .setMotionEventState = fakeClSetMotionEventState, .setControllerLED = fakeClSetControllerLED, + .setAdaptiveTriggers = fakeClSetAdaptiveTriggers, }; void fixupMissingCallbacks(PDECODER_RENDERER_CALLBACKS* drCallbacks, PAUDIO_RENDERER_CALLBACKS* arCallbacks, @@ -141,5 +143,8 @@ void fixupMissingCallbacks(PDECODER_RENDERER_CALLBACKS* drCallbacks, PAUDIO_REND if ((*clCallbacks)->setControllerLED == NULL) { (*clCallbacks)->setControllerLED = fakeClSetControllerLED; } + if ((*clCallbacks)->setAdaptiveTriggers == NULL) { + (*clCallbacks)->setAdaptiveTriggers = fakeClSetAdaptiveTriggers; + } } } diff --git a/src/Limelight.h b/src/Limelight.h index b2bb550..3d8aded 100644 --- a/src/Limelight.h +++ b/src/Limelight.h @@ -69,7 +69,7 @@ typedef struct _STREAM_CONFIGURATION { // Specifies the channel configuration of the audio stream. // See AUDIO_CONFIGURATION constants and MAKE_AUDIO_CONFIGURATION() below. int audioConfiguration; - + // Specifies the mask of supported video formats. // See VIDEO_FORMAT constants below. int supportedVideoFormats; @@ -469,6 +469,13 @@ typedef void(*ConnListenerRumbleTriggers)(uint16_t controllerNumber, uint16_t le // If reportRateHz is 0, the host is asking for motion event reporting to stop. typedef void(*ConnListenerSetMotionEventState)(uint16_t controllerNumber, uint8_t motionType, uint16_t reportRateHz); +// This callback is invoked to notify the client of a change in the dualsense +// adaptive trigger configuration. +#define DS_EFFECT_PAYLOAD_SIZE 10 +#define DS_EFFECT_RIGHT_TRIGGER 0x04 +#define DS_EFFECT_LEFT_TRIGGER 0x08 +typedef void(*ConnListenerSetAdaptiveTriggers)(uint16_t controllerNumber, uint8_t eventFlags, uint8_t typeLeft, uint8_t typeRight, uint8_t *left, uint8_t *right); + // This callback is invoked to set a controller's RGB LED (if present). typedef void(*ConnListenerSetControllerLED)(uint16_t controllerNumber, uint8_t r, uint8_t g, uint8_t b); @@ -485,6 +492,7 @@ typedef struct _CONNECTION_LISTENER_CALLBACKS { ConnListenerRumbleTriggers rumbleTriggers; ConnListenerSetMotionEventState setMotionEventState; ConnListenerSetControllerLED setControllerLED; + ConnListenerSetAdaptiveTriggers setAdaptiveTriggers; } CONNECTION_LISTENER_CALLBACKS, *PCONNECTION_LISTENER_CALLBACKS; // Use this function to zero the connection callbacks when allocated on the stack or heap @@ -512,10 +520,10 @@ void LiInitializeConnectionCallbacks(PCONNECTION_LISTENER_CALLBACKS clCallbacks) typedef struct _SERVER_INFORMATION { // Server host name or IP address in text form const char* address; - + // Text inside 'appversion' tag in /serverinfo const char* serverInfoAppVersion; - + // Text inside 'GfeVersion' tag in /serverinfo (if present) const char* serverInfoGfeVersion;