Compare commits

...

10 commits

Author SHA1 Message Date
611a2e7f8f Add session input policy control stream support 2026-02-12 00:37:54 -07:00
Cameron Gutman
6250fa29ee Update ENet for FreeBSD compatibility 2026-02-06 23:24:42 -06:00
Cameron Gutman
07c32c80f9 Use a valid port when calling connect() for local address detection
BSDs (including Darwin) don't like using port 0.
2026-02-06 22:53:54 -06:00
7mile
305993b013
fix: use UDP connect to probe local address (#121) 2026-01-29 18:52:06 -06:00
ns6089
2a5a1f3e8a
Add support for LTR ACK control messages (#122)
* Add support for LTR ACK control messages
2026-01-20 21:47:38 -06:00
Cameron Gutman
435bc6a5a4 Fix pthread_attr leak 2026-01-11 01:46:30 -06:00
Cameron Gutman
3a377e7d7b Only set an explicit pthread stack size on Vita 2026-01-05 00:23:31 -06:00
Cameron Gutman
0586f3d65f Fix thread context leak on non-Vita platforms 2026-01-05 00:21:35 -06:00
Cameron Gutman
b126e481a1 Improve locking for batched mouse and gamepad sensor events
By unlocking the mutex before we enqueue the new entry, we can avoid the input thread immediately contending on the mutex after the new item wakes it up.
2025-11-25 17:30:01 -06:00
Cameron Gutman
20c05eda6a Don't lock the ENet mutex when querying for RTT information 2025-11-25 17:26:01 -06:00
14 changed files with 281 additions and 84 deletions

2
enet

@ -1 +1 @@
Subproject commit dea6fb5414b180908b58c0293c831105b5d124dd
Subproject commit 78cc9b4b03bdd4f95c277391cac6e0951407072b

View file

@ -377,6 +377,19 @@ int LiStartConnection(PSERVER_INFORMATION serverInfo, PSTREAM_CONFIGURATION stre
ListenerCallbacks.stageFailed(STAGE_NAME_RESOLUTION, err);
goto Cleanup;
}
// Resolve LocalAddr by RemoteAddr.
{
SOCKADDR_LEN localAddrLen;
err = getLocalAddressByUdpConnect(&RemoteAddr, AddrLen, RtspPortNumber, &LocalAddr, &localAddrLen);
if (err != 0) {
Limelog("failed to resolve local addr: %d\n", err);
ListenerCallbacks.stageFailed(STAGE_NAME_RESOLUTION, err);
goto Cleanup;
}
LC_ASSERT(localAddrLen == AddrLen);
}
stage++;
LC_ASSERT(stage == STAGE_NAME_RESOLUTION);
ListenerCallbacks.stageComplete(STAGE_NAME_RESOLUTION);

View file

@ -31,11 +31,12 @@ typedef struct _NVCTL_ENCRYPTED_PACKET_HEADER {
// encrypted NVCTL_ENET_PACKET_HEADER_V2 and payload data follow
} NVCTL_ENCRYPTED_PACKET_HEADER, *PNVCTL_ENCRYPTED_PACKET_HEADER;
typedef struct _QUEUED_FRAME_INVALIDATION_TUPLE {
typedef struct _QUEUED_REFERENCE_FRAME_CONTROL {
uint32_t startFrame;
uint32_t endFrame;
bool invalidate; // true: RFI(startFrame, endFrame); false: LTR_ACK(startFrame)
LINKED_BLOCKING_QUEUE_ENTRY entry;
} QUEUED_FRAME_INVALIDATION_TUPLE, *PQUEUED_FRAME_INVALIDATION_TUPLE;
} QUEUED_REFERENCE_FRAME_CONTROL, *PQUEUED_REFERENCE_FRAME_CONTROL;
typedef struct _QUEUED_FRAME_FEC_STATUS {
SS_FRAME_FEC_STATUS fecStatus;
@ -82,6 +83,12 @@ typedef struct _QUEUED_ASYNC_CALLBACK {
uint8_t left[DS_EFFECT_PAYLOAD_SIZE];
uint8_t right[DS_EFFECT_PAYLOAD_SIZE];
} dsAdaptiveTrigger;
struct {
uint8_t allowKeyboard;
uint8_t allowMouse;
uint8_t allowGamepad;
uint8_t reason;
} setInputPolicy;
} data;
LINKED_BLOCKING_QUEUE_ENTRY entry;
} QUEUED_ASYNC_CALLBACK, *PQUEUED_ASYNC_CALLBACK;
@ -113,7 +120,7 @@ static int lastConnectionStatusUpdate;
static uint32_t currentEnetSequenceNumber;
static uint64_t firstFrameTimeMs;
static LINKED_BLOCKING_QUEUE invalidReferenceFrameTuples;
static LINKED_BLOCKING_QUEUE referenceFrameControlQueue;
static LINKED_BLOCKING_QUEUE frameFecStatusQueue;
static LINKED_BLOCKING_QUEUE asyncCallbackQueue;
static PLT_EVENT idrFrameRequiredEvent;
@ -139,6 +146,7 @@ static PPLT_CRYPTO_CONTEXT decryptionCtx;
#define IDX_SET_MOTION_EVENT 10
#define IDX_SET_RGB_LED 11
#define IDX_DS_ADAPTIVE_TRIGGERS 12
#define IDX_SET_INPUT_POLICY 13
#define CONTROL_STREAM_TIMEOUT_SEC 10
#define CONTROL_STREAM_LINGER_TIMEOUT_SEC 2
@ -156,6 +164,7 @@ static const short packetTypesGen3[] = {
-1, // Rumble triggers (unused)
-1, // Set motion event (unused)
-1, // Set RGB LED (unused)
-1,
};
static const short packetTypesGen4[] = {
0x0606, // Request IDR frame
@ -170,6 +179,7 @@ static const short packetTypesGen4[] = {
-1, // Rumble triggers (unused)
-1, // Set motion event (unused)
-1, // Set RGB LED (unused)
-1,
};
static const short packetTypesGen5[] = {
0x0305, // Start A
@ -184,6 +194,7 @@ static const short packetTypesGen5[] = {
-1, // Rumble triggers (unused)
-1, // Set motion event (unused)
-1, // Set RGB LED (unused)
-1,
};
static const short packetTypesGen7[] = {
0x0305, // Start A
@ -198,6 +209,7 @@ static const short packetTypesGen7[] = {
-1, // Rumble triggers (unused)
-1, // Set motion event (unused)
-1, // Set RGB LED (unused)
-1,
};
static const short packetTypesGen7Enc[] = {
0x0302, // Request IDR frame
@ -213,6 +225,7 @@ static const short packetTypesGen7Enc[] = {
0x5501, // Set motion event (Sunshine protocol extension)
0x5502, // Set RGB LED (Sunshine protocol extension)
0x5503, // Set Adaptive Triggers (Sunshine protocol extension)
0x5504,
};
static const char requestIdrFrameGen3[] = { 0, 0 };
@ -300,7 +313,7 @@ static bool supportsIdrFrameRequest;
int initializeControlStream(void) {
stopping = false;
PltCreateEvent(&idrFrameRequiredEvent);
LbqInitializeLinkedBlockingQueue(&invalidReferenceFrameTuples, 20);
LbqInitializeLinkedBlockingQueue(&referenceFrameControlQueue, 20);
LbqInitializeLinkedBlockingQueue(&frameFecStatusQueue, 8); // Limits number of frame status reports per periodic ping interval
LbqInitializeLinkedBlockingQueue(&asyncCallbackQueue, 30);
PltCreateMutex(&enetMutex);
@ -375,7 +388,7 @@ void destroyControlStream(void) {
PltDestroyCryptoContext(encryptionCtx);
PltDestroyCryptoContext(decryptionCtx);
PltCloseEvent(&idrFrameRequiredEvent);
freeBasicLbqList(LbqDestroyLinkedBlockingQueue(&invalidReferenceFrameTuples));
freeBasicLbqList(LbqDestroyLinkedBlockingQueue(&referenceFrameControlQueue));
freeBasicLbqList(LbqDestroyLinkedBlockingQueue(&frameFecStatusQueue));
freeBasicLbqList(LbqDestroyLinkedBlockingQueue(&asyncCallbackQueue));
@ -386,12 +399,15 @@ static void queueFrameInvalidationTuple(uint32_t startFrame, uint32_t endFrame)
LC_ASSERT(startFrame <= endFrame);
if (isReferenceFrameInvalidationEnabled()) {
PQUEUED_FRAME_INVALIDATION_TUPLE qfit;
PQUEUED_REFERENCE_FRAME_CONTROL qfit;
qfit = malloc(sizeof(*qfit));
if (qfit != NULL) {
qfit->startFrame = startFrame;
qfit->endFrame = endFrame;
if (LbqOfferQueueItem(&invalidReferenceFrameTuples, qfit, &qfit->entry) == LBQ_BOUND_EXCEEDED) {
*qfit = (QUEUED_REFERENCE_FRAME_CONTROL){
.startFrame = startFrame,
.endFrame = endFrame,
.invalidate = true,
};
if (LbqOfferQueueItem(&referenceFrameControlQueue, qfit, &qfit->entry) == LBQ_BOUND_EXCEEDED) {
// Too many invalidation tuples, so we need an IDR frame now
Limelog("RFI range list reached maximum size limit\n");
free(qfit);
@ -411,7 +427,7 @@ static void queueFrameInvalidationTuple(uint32_t startFrame, uint32_t endFrame)
void LiRequestIdrFrame(void) {
// Any reference frame invalidation requests should be dropped now.
// We require a full IDR frame to recover.
freeBasicLbqList(LbqFlushQueueItems(&invalidReferenceFrameTuples));
freeBasicLbqList(LbqFlushQueueItems(&referenceFrameControlQueue));
// Request the IDR frame
PltSetEvent(&idrFrameRequiredEvent);
@ -423,9 +439,29 @@ void connectionDetectedFrameLoss(uint32_t startFrame, uint32_t endFrame) {
}
// When we receive a frame, update the number of our current frame
void connectionReceivedCompleteFrame(uint32_t frameIndex) {
// and send ACK control message if the frame is LTR
void connectionReceivedCompleteFrame(uint32_t frameIndex, bool frameIsLTR) {
lastGoodFrame = frameIndex;
intervalGoodFrameCount++;
if (frameIsLTR && IS_SUNSHINE() && isReferenceFrameInvalidationEnabled()) {
// Queue LTR frame ACK control message
PQUEUED_REFERENCE_FRAME_CONTROL qfit;
qfit = malloc(sizeof(*qfit));
if (qfit != NULL) {
*qfit = (QUEUED_REFERENCE_FRAME_CONTROL){
.startFrame = frameIndex,
.invalidate = false,
};
if (LbqOfferQueueItem(&referenceFrameControlQueue, qfit, &qfit->entry) == LBQ_BOUND_EXCEEDED) {
// This shouldn't happen and indicates that something has gone wrong with the queue
LC_ASSERT(false);
Limelog("Couldn't queue LTR ACK because the list has reached maximum size limit\n");
free(qfit);
LiRequestIdrFrame();
}
}
}
}
void connectionSendFrameFecStatus(PSS_FRAME_FEC_STATUS fecStatus) {
@ -986,6 +1022,12 @@ static void asyncCallbackThreadFunc(void* context) {
queuedCb->data.dsAdaptiveTrigger.left,
queuedCb->data.dsAdaptiveTrigger.right);
break;
case IDX_SET_INPUT_POLICY:
ListenerCallbacks.setInputPolicy(queuedCb->data.setInputPolicy.allowKeyboard,
queuedCb->data.setInputPolicy.allowMouse,
queuedCb->data.setInputPolicy.allowGamepad,
queuedCb->data.setInputPolicy.reason);
break;
default:
// Unhandled packet type from queueAsyncCallback()
LC_ASSERT(false);
@ -1002,7 +1044,8 @@ static bool needsAsyncCallback(unsigned short packetType) {
packetType == packetTypes[IDX_SET_MOTION_EVENT] ||
packetType == packetTypes[IDX_SET_RGB_LED] ||
packetType == packetTypes[IDX_HDR_INFO] ||
packetType == packetTypes[IDX_DS_ADAPTIVE_TRIGGERS];
packetType == packetTypes[IDX_DS_ADAPTIVE_TRIGGERS] ||
packetType == packetTypes[IDX_SET_INPUT_POLICY];
}
static void queueAsyncCallback(PNVCTL_ENET_PACKET_HEADER_V1 ctlHdr, int packetLength) {
@ -1063,6 +1106,13 @@ static void queueAsyncCallback(PNVCTL_ENET_PACKET_HEADER_V1 ctlHdr, int packetLe
BbGetBytes(&bb, queuedCb->data.dsAdaptiveTrigger.right, DS_EFFECT_PAYLOAD_SIZE);
queuedCb->typeIndex = IDX_DS_ADAPTIVE_TRIGGERS;
}
else if (ctlHdr->type == packetTypes[IDX_SET_INPUT_POLICY]) {
BbGet8(&bb, &queuedCb->data.setInputPolicy.allowKeyboard);
BbGet8(&bb, &queuedCb->data.setInputPolicy.allowMouse);
BbGet8(&bb, &queuedCb->data.setInputPolicy.allowGamepad);
BbGet8(&bb, &queuedCb->data.setInputPolicy.reason);
queuedCb->typeIndex = IDX_SET_INPUT_POLICY;
}
else {
// Unhandled packet type from needsAsyncCallback()
LC_ASSERT(false);
@ -1512,22 +1562,22 @@ static void requestIdrFrame(void) {
}
static void requestInvalidateReferenceFrames(uint32_t startFrame, uint32_t endFrame) {
int64_t payload[3];
LC_ASSERT(startFrame <= endFrame);
LC_ASSERT(isReferenceFrameInvalidationEnabled());
payload[0] = LE64(startFrame);
payload[1] = LE64(endFrame);
payload[2] = 0;
SS_RFI_REQUEST payload = {
.firstFrameIndex = LE32(startFrame),
.lastFrameIndex = LE32(endFrame),
};
// Send the reference frame invalidation request and read the response
if (!sendMessageAndDiscardReply(packetTypes[IDX_INVALIDATE_REF_FRAMES],
sizeof(payload),
payload, CTRL_CHANNEL_URGENT,
&payload,
CTRL_CHANNEL_URGENT,
ENET_PACKET_FLAG_RELIABLE,
false)) {
Limelog("Request Invaldiate Reference Frames: Transaction failed: %d\n", (int)LastSocketError());
Limelog("Request Invalidate Reference Frames: Transaction failed: %d\n", (int)LastSocketError());
ListenerCallbacks.connectionTerminated(LastSocketFail());
return;
}
@ -1535,32 +1585,65 @@ static void requestInvalidateReferenceFrames(uint32_t startFrame, uint32_t endFr
Limelog("Invalidate reference frame request sent (%d to %d)\n", startFrame, endFrame);
}
static void invalidateRefFramesFunc(void* context) {
static void confirmLongtermReferenceFrame(uint32_t frameIndex) {
LC_ASSERT(isReferenceFrameInvalidationEnabled());
SS_LTR_FRAME_ACK payload = {
.frameIndex = LE32(frameIndex),
};
// Send LTR frame ACK and don't wait for response
if (!sendMessageAndForget(SS_LTR_FRAME_ACK_PTYPE,
sizeof(payload),
&payload,
CTRL_CHANNEL_URGENT,
ENET_PACKET_FLAG_RELIABLE,
false)) {
Limelog("LTR frame ACK: Transaction failed: %d\n", (int)LastSocketError());
ListenerCallbacks.connectionTerminated(LastSocketFail());
return;
}
}
static void referenceFrameControlFunc(void* context) {
LC_ASSERT(isReferenceFrameInvalidationEnabled());
while (!PltIsThreadInterrupted(&invalidateRefFramesThread)) {
PQUEUED_FRAME_INVALIDATION_TUPLE qfit;
uint32_t startFrame;
uint32_t endFrame;
PQUEUED_REFERENCE_FRAME_CONTROL qfit;
uint32_t invalidateStartFrame;
uint32_t invalidateEndFrame;
bool invalidate = false;
// Wait for a reference frame invalidation request or a request to shutdown
if (LbqWaitForQueueElement(&invalidReferenceFrameTuples, (void**)&qfit) != LBQ_SUCCESS) {
// Wait for a reference frame control message or a request to shutdown
if (LbqWaitForQueueElement(&referenceFrameControlQueue, (void**)&qfit) != LBQ_SUCCESS) {
// Bail if we're stopping
return;
}
startFrame = qfit->startFrame;
endFrame = qfit->endFrame;
// Aggregate all lost frames into one range
do {
LC_ASSERT(qfit->endFrame >= endFrame);
endFrame = qfit->endFrame;
if (qfit->invalidate) {
if (!invalidate) {
invalidateStartFrame = qfit->startFrame;
invalidateEndFrame = qfit->endFrame;
invalidate = true;
}
else {
// Aggregate all lost frames into one range
LC_ASSERT(qfit->endFrame >= invalidateEndFrame);
invalidateEndFrame = qfit->endFrame;
}
}
else {
// Send LTR frame ACK
confirmLongtermReferenceFrame(qfit->startFrame);
}
free(qfit);
} while (LbqPollQueueElement(&invalidReferenceFrameTuples, (void**)&qfit) == LBQ_SUCCESS);
} while (LbqPollQueueElement(&referenceFrameControlQueue, (void**)&qfit) == LBQ_SUCCESS);
if (invalidate) {
// Send the reference frame invalidation request
requestInvalidateReferenceFrames(startFrame, endFrame);
requestInvalidateReferenceFrames(invalidateStartFrame, invalidateEndFrame);
}
}
}
@ -1574,8 +1657,8 @@ static void requestIdrFrameFunc(void* context) {
return;
}
// Any pending reference frame invalidation requests are now redundant
freeBasicLbqList(LbqFlushQueueItems(&invalidReferenceFrameTuples));
// Any pending RFI requests and LTR frame ACK messages are now redundant
freeBasicLbqList(LbqFlushQueueItems(&referenceFrameControlQueue));
// Request the IDR frame
requestIdrFrame();
@ -1585,7 +1668,7 @@ static void requestIdrFrameFunc(void* context) {
// Stops the control stream
int stopControlStream(void) {
stopping = true;
LbqSignalQueueShutdown(&invalidReferenceFrameTuples);
LbqSignalQueueShutdown(&referenceFrameControlQueue);
LbqSignalQueueShutdown(&frameFecStatusQueue);
LbqSignalQueueDrain(&asyncCallbackQueue);
PltSetEvent(&idrFrameRequiredEvent);
@ -1644,6 +1727,34 @@ int sendInputPacketOnControlStream(unsigned char* data, int length, uint8_t chan
return 0;
}
int LiSendSessionInputPolicy(bool allowKeyboard, bool allowMouse, bool allowGamepad, uint8_t reason) {
struct {
uint8_t allowKeyboard;
uint8_t allowMouse;
uint8_t allowGamepad;
uint8_t reason;
} payload;
if (!IS_SUNSHINE() || AppVersionQuad[0] < 7) {
return LI_ERR_UNSUPPORTED;
}
if (client == NULL || peer == NULL || stopping) {
return -2;
}
payload.allowKeyboard = allowKeyboard ? 1 : 0;
payload.allowMouse = allowMouse ? 1 : 0;
payload.allowGamepad = allowGamepad ? 1 : 0;
payload.reason = reason;
if (sendMessageAndForget(0x5504, sizeof(payload), &payload, CTRL_CHANNEL_GENERIC, ENET_PACKET_FLAG_RELIABLE, false) == 0) {
return -1;
}
return 0;
}
// Called by the input stream to flush queued packets before a batching wait
void flushInputOnControlStream(void) {
if (AppVersionQuad[0] >= 5) {
@ -1670,7 +1781,10 @@ bool isControlDataInTransit(void) {
bool LiGetEstimatedRttInfo(uint32_t* estimatedRtt, uint32_t* estimatedRttVariance) {
bool ret = false;
PltLockMutex(&enetMutex);
// We do not acquire enetMutex here because we're just reading metrics
// and observing a torn write every once in a while is totally fine.
// The peer pointer points to memory reserved inside the client object,
// so it's guaranteed that it will never go away underneath us.
if (peer != NULL && peer->state == ENET_PEER_STATE_CONNECTED) {
if (estimatedRtt != NULL) {
*estimatedRtt = peer->roundTripTime;
@ -1682,7 +1796,6 @@ bool LiGetEstimatedRttInfo(uint32_t* estimatedRtt, uint32_t* estimatedRttVarianc
ret = true;
}
PltUnlockMutex(&enetMutex);
return ret;
}
@ -1970,7 +2083,7 @@ int startControlStream(void) {
// Only create the reference frame invalidation thread if RFI is enabled
if (isReferenceFrameInvalidationEnabled()) {
err = PltCreateThread("InvRefFrames", invalidateRefFramesFunc, NULL, &invalidateRefFramesThread);
err = PltCreateThread("InvRefFrames", referenceFrameControlFunc, NULL, &invalidateRefFramesThread);
if (err != 0) {
stopping = true;
PltSetEvent(&idrFrameRequiredEvent);

View file

@ -41,6 +41,7 @@ static void fakeClRumbleTriggers(uint16_t controllerNumber, uint16_t leftTrigger
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 void fakeClSetInputPolicy(uint8_t allowKeyboard, uint8_t allowMouse, uint8_t allowGamepad, uint8_t reason) {}
static CONNECTION_LISTENER_CALLBACKS fakeClCallbacks = {
.stageStarting = fakeClStageStarting,
@ -56,6 +57,7 @@ static CONNECTION_LISTENER_CALLBACKS fakeClCallbacks = {
.setMotionEventState = fakeClSetMotionEventState,
.setControllerLED = fakeClSetControllerLED,
.setAdaptiveTriggers = fakeClSetAdaptiveTriggers,
.setInputPolicy = fakeClSetInputPolicy,
};
void fixupMissingCallbacks(PDECODER_RENDERER_CALLBACKS* drCallbacks, PAUDIO_RENDERER_CALLBACKS* arCallbacks,
@ -146,5 +148,8 @@ void fixupMissingCallbacks(PDECODER_RENDERER_CALLBACKS* drCallbacks, PAUDIO_REND
if ((*clCallbacks)->setAdaptiveTriggers == NULL) {
(*clCallbacks)->setAdaptiveTriggers = fakeClSetAdaptiveTriggers;
}
if ((*clCallbacks)->setInputPolicy == NULL) {
(*clCallbacks)->setInputPolicy = fakeClSetInputPolicy;
}
}
}

View file

@ -724,9 +724,14 @@ int LiSendMouseMoveEvent(short deltaX, short deltaY) {
// Queue a packet holder if this is the only pending relative mouse event
if (!currentRelativeMouseState.dirty) {
// Set the dirty flag to claim ownership of inserting the packet holder
// and unlock to allow other threads to enqueue or process input.
currentRelativeMouseState.dirty = true;
PltUnlockMutex(&batchedInputMutex);
holder = allocatePacketHolder(0);
if (holder == NULL) {
PltUnlockMutex(&batchedInputMutex);
currentRelativeMouseState.dirty = false;
return -1;
}
@ -747,22 +752,21 @@ int LiSendMouseMoveEvent(short deltaX, short deltaY) {
// Remaining fields are set in the input thread based on the latest currentRelativeMouseState values
err = LbqOfferQueueItem(&packetQueue, holder, &holder->entry);
if (err == LBQ_SUCCESS) {
currentRelativeMouseState.dirty = true;
}
else {
if (err != LBQ_SUCCESS) {
LC_ASSERT(err == LBQ_BOUND_EXCEEDED);
Limelog("Input queue reached maximum size limit\n");
freePacketHolder(holder);
// We weren't able to insert the entry, so let the next call try again
currentRelativeMouseState.dirty = false;
}
}
else {
// There's already a packet holder queued to send this event
PltUnlockMutex(&batchedInputMutex);
err = 0;
}
PltUnlockMutex(&batchedInputMutex);
return err;
}
@ -785,9 +789,14 @@ int LiSendMousePositionEvent(short x, short y, short referenceWidth, short refer
// Queue a packet holder if this is the only pending absolute mouse event
if (!currentAbsoluteMouseState.dirty) {
// Set the dirty flag to claim ownership of inserting the packet holder
// and unlock to allow other threads to enqueue or process input.
currentAbsoluteMouseState.dirty = true;
PltUnlockMutex(&batchedInputMutex);
holder = allocatePacketHolder(0);
if (holder == NULL) {
PltUnlockMutex(&batchedInputMutex);
currentAbsoluteMouseState.dirty = false;
return -1;
}
@ -803,22 +812,21 @@ int LiSendMousePositionEvent(short x, short y, short referenceWidth, short refer
// Remaining fields are set in the input thread based on the latest currentAbsoluteMouseState values
err = LbqOfferQueueItem(&packetQueue, holder, &holder->entry);
if (err == LBQ_SUCCESS) {
currentAbsoluteMouseState.dirty = true;
}
else {
if (err != LBQ_SUCCESS) {
LC_ASSERT(err == LBQ_BOUND_EXCEEDED);
Limelog("Input queue reached maximum size limit\n");
freePacketHolder(holder);
// We weren't able to insert the entry, so let the next call try again
currentAbsoluteMouseState.dirty = false;
}
}
else {
// There's already a packet holder queued to send this event
PltUnlockMutex(&batchedInputMutex);
err = 0;
}
PltUnlockMutex(&batchedInputMutex);
// This is not thread safe, but it's not a big deal because callers that want to
// use LiSendRelativeMotionAsMousePositionEvent() must not mix these function
// without synchronization (otherwise the state of the cursor on the host is
@ -1536,9 +1544,14 @@ int LiSendControllerMotionEvent(uint8_t controllerNumber, uint8_t motionType, fl
// Queue a packet holder if this is the only pending sensor event
if (!currentGamepadSensorState[controllerNumber][motionType - 1].dirty) {
// Set the dirty flag to claim ownership of inserting the packet holder
// and unlock to allow other threads to enqueue or process input.
currentGamepadSensorState[controllerNumber][motionType - 1].dirty = true;
PltUnlockMutex(&batchedInputMutex);
holder = allocatePacketHolder(0);
if (holder == NULL) {
PltUnlockMutex(&batchedInputMutex);
currentGamepadSensorState[controllerNumber][motionType - 1].dirty = false;
return -1;
}
@ -1554,22 +1567,21 @@ int LiSendControllerMotionEvent(uint8_t controllerNumber, uint8_t motionType, fl
// Remaining fields are set in the input thread based on the latest currentGamepadSensorState values
err = LbqOfferQueueItem(&packetQueue, holder, &holder->entry);
if (err == LBQ_SUCCESS) {
currentGamepadSensorState[controllerNumber][motionType - 1].dirty = true;
}
else {
if (err != LBQ_SUCCESS) {
LC_ASSERT(err == LBQ_BOUND_EXCEEDED);
Limelog("Input queue reached maximum size limit\n");
freePacketHolder(holder);
// We weren't able to insert the entry, so let the next call try again
currentGamepadSensorState[controllerNumber][motionType - 1].dirty = false;
}
}
else {
// There's already a packet holder queued to send this event
PltUnlockMutex(&batchedInputMutex);
err = 0;
}
PltUnlockMutex(&batchedInputMutex);
return err;
}

View file

@ -55,7 +55,7 @@ extern uint32_t EncryptionFeaturesEnabled;
// ENet channel ID values
#define CTRL_CHANNEL_GENERIC 0x00
#define CTRL_CHANNEL_URGENT 0x01 // IDR and reference frame invalidation requests
#define CTRL_CHANNEL_URGENT 0x01 // IDR, LTR ACK and RFI
#define CTRL_CHANNEL_KEYBOARD 0x02
#define CTRL_CHANNEL_MOUSE 0x03
#define CTRL_CHANNEL_PEN 0x04
@ -119,7 +119,7 @@ int startControlStream(void);
int stopControlStream(void);
void destroyControlStream(void);
void connectionDetectedFrameLoss(uint32_t startFrame, uint32_t endFrame);
void connectionReceivedCompleteFrame(uint32_t frameIndex);
void connectionReceivedCompleteFrame(uint32_t frameIndex, bool frameIsLTR);
void connectionSawFrame(uint32_t frameIndex);
void connectionSendFrameFecStatus(PSS_FRAME_FEC_STATUS fecStatus);
int sendInputPacketOnControlStream(unsigned char* data, int length, uint8_t channelId, uint32_t flags, bool moreData);

View file

@ -483,6 +483,8 @@ typedef void(*ConnListenerSetAdaptiveTriggers)(uint16_t controllerNumber, uint8_
// 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);
typedef void(*ConnListenerSetInputPolicy)(uint8_t allowKeyboard, uint8_t allowMouse, uint8_t allowGamepad, uint8_t reason);
typedef struct _CONNECTION_LISTENER_CALLBACKS {
ConnListenerStageStarting stageStarting;
ConnListenerStageComplete stageComplete;
@ -497,6 +499,7 @@ typedef struct _CONNECTION_LISTENER_CALLBACKS {
ConnListenerSetMotionEventState setMotionEventState;
ConnListenerSetControllerLED setControllerLED;
ConnListenerSetAdaptiveTriggers setAdaptiveTriggers;
ConnListenerSetInputPolicy setInputPolicy;
} CONNECTION_LISTENER_CALLBACKS, *PCONNECTION_LISTENER_CALLBACKS;
// Use this function to zero the connection callbacks when allocated on the stack or heap
@ -837,6 +840,12 @@ int LiSendHighResScrollEvent(short scrollAmount);
int LiSendHScrollEvent(signed char scrollClicks);
int LiSendHighResHScrollEvent(short scrollAmount);
#define LI_SESSION_INPUT_POLICY_REASON_STREAM_START 0x00
#define LI_SESSION_INPUT_POLICY_REASON_USER_TOGGLE 0x01
#define LI_SESSION_INPUT_POLICY_REASON_HOST_ACK 0x02
#define LI_SESSION_INPUT_POLICY_REASON_HOST_OVERRIDE 0x03
int LiSendSessionInputPolicy(bool allowKeyboard, bool allowMouse, bool allowGamepad, uint8_t reason);
// This function returns a time in microseconds with an implementation-defined epoch.
// It should only ever be compared with the return value from a previous call to itself.
uint64_t LiGetMicroseconds(void);

View file

@ -86,9 +86,7 @@ void* ThreadProc(void* context) {
ctx->entry(ctx->context);
#if defined(__vita__)
free(ctx);
#endif
#if defined(LC_WINDOWS) || defined(__vita__) || defined(__WIIU__) || defined(__3DS__)
return 0;
@ -288,11 +286,14 @@ int PltCreateThread(const char* name, ThreadEntry entry, void* context, PLT_THRE
pthread_attr_init(&attr);
#ifdef __vita__
pthread_attr_setstacksize(&attr, 0x100000);
#endif
ctx->name = name;
int err = pthread_create(&thread->thread, &attr, ThreadProc, ctx);
pthread_attr_destroy(&attr);
if (err != 0) {
free(ctx);
return err;

View file

@ -568,6 +568,40 @@ Exit:
return s;
}
int getLocalAddressByUdpConnect(const struct sockaddr_storage* targetAddr, SOCKADDR_LEN targetAddrLen, unsigned short targetPort,
struct sockaddr_storage* localAddr, SOCKADDR_LEN* localAddrLen) {
SOCKET udpSocket;
LC_SOCKADDR connAddr;
LC_ASSERT(targetPort != 0);
udpSocket = createSocket(targetAddr->ss_family, SOCK_DGRAM, IPPROTO_UDP, false);
if (udpSocket == INVALID_SOCKET) {
return LastSocketError();
}
memcpy(&connAddr, targetAddr, targetAddrLen);
SET_PORT(&connAddr, RtspPortNumber);
if (connect(udpSocket, (struct sockaddr*)&connAddr, targetAddrLen) < 0) {
int err = LastSocketError();
Limelog("UDP connect() failed: %d\n", err);
closeSocket(udpSocket);
return err;
}
*localAddrLen = sizeof(*localAddr);
if (getsockname(udpSocket, (struct sockaddr*)localAddr, localAddrLen) < 0) {
int err = LastSocketError();
Limelog("getsockname() failed: %d\n", err);
closeSocket(udpSocket);
return err;
}
closeSocket(udpSocket);
return 0;
}
// See TCP_MAXSEG note in connectTcpSocket() above for more information.
// TCP_NODELAY must be enabled on the socket for this function to work!
int sendMtuSafe(SOCKET s, char* buffer, int size) {

View file

@ -107,6 +107,8 @@ void addrToUrlSafeString(struct sockaddr_storage* addr, char* string, size_t str
SOCKET createSocket(int addressFamily, int socketType, int protocol, bool nonBlocking);
SOCKET connectTcpSocket(struct sockaddr_storage* dstaddr, SOCKADDR_LEN addrlen, unsigned short port, int timeoutSec);
int getLocalAddressByUdpConnect(const struct sockaddr_storage* targetAddr, SOCKADDR_LEN targetAddrLen, unsigned short targetPort,
struct sockaddr_storage* localAddr, SOCKADDR_LEN* localAddrLen);
int sendMtuSafe(SOCKET s, char* buffer, int size);
SOCKET bindUdpSocket(int addressFamily, struct sockaddr_storage* localAddr, SOCKADDR_LEN addrLen, int bufferSize, int socketQosType);
int enableNoDelay(SOCKET s);

View file

@ -382,9 +382,9 @@ cleanup_packets:
// Check all NV_VIDEO_PACKET fields except FEC stuff which differs in the recovered packet
LC_ASSERT_VT(nvPacket->flags == droppedNvPacket->flags);
LC_ASSERT_VT(nvPacket->extraFlags == droppedNvPacket->extraFlags);
LC_ASSERT_VT(nvPacket->frameIndex == droppedNvPacket->frameIndex);
LC_ASSERT_VT(nvPacket->streamPacketIndex == droppedNvPacket->streamPacketIndex);
LC_ASSERT_VT(nvPacket->reserved == droppedNvPacket->reserved);
LC_ASSERT_VT(!queue->multiFecCapable || nvPacket->multiFecBlocks == droppedNvPacket->multiFecBlocks);
// Check the data itself - use memcmp() and only loop if an error is detected

View file

@ -477,18 +477,6 @@ static bool transactRtspMessageTcp(PRTSP_MESSAGE request, PRTSP_MESSAGE response
// Decrypt (if necessary) and deserialize the RTSP response
ret = unsealRtspMessage(responseBuffer, offset, response);
// Fetch the local address for this socket if it's not populated yet
if (LocalAddr.ss_family == 0) {
SOCKADDR_LEN addrLen = (SOCKADDR_LEN)sizeof(LocalAddr);
if (getsockname(sock, (struct sockaddr*)&LocalAddr, &addrLen) < 0) {
Limelog("Failed to get local address: %d\n", LastSocketError());
memset(&LocalAddr, 0, sizeof(LocalAddr));
}
else {
LC_ASSERT(addrLen == AddrLen);
}
}
Exit:
if (serializedMessage != NULL) {
free(serializedMessage);

View file

@ -22,11 +22,13 @@ typedef struct _ENC_VIDEO_HEADER {
#define FLAG_EOF 0x2
#define FLAG_SOF 0x4
#define NV_VIDEO_PACKET_EXTRA_FLAG_LTR_FRAME 0x1
typedef struct _NV_VIDEO_PACKET {
uint32_t streamPacketIndex;
uint32_t frameIndex;
uint8_t flags;
uint8_t reserved;
uint8_t extraFlags;
uint8_t multiFecFlags;
uint8_t multiFecBlocks;
uint32_t fecInfo;
@ -67,4 +69,20 @@ typedef struct _SS_FRAME_FEC_STATUS {
uint8_t multiFecBlockCount;
} SS_FRAME_FEC_STATUS, *PSS_FRAME_FEC_STATUS;
// Fields are little-endian
#define SS_LTR_FRAME_ACK_PTYPE 0x0350
typedef struct _SS_LTR_FRAME_ACK {
uint32_t frameIndex;
uint32_t reserved;
} SS_LTR_FRAME_ACK, *PSS_LTR_FRAME_ACK;
// Fields are little-endian
#define SS_RFI_REQUEST_PTYPE 0x0301
typedef struct _SS_RFI_REQUEST {
uint32_t firstFrameIndex;
uint32_t reserved1;
uint32_t lastFrameIndex;
uint32_t reserved2[3];
} SS_RFI_REQUEST, *PSS_RFI_REQUEST;
#pragma pack(pop)

View file

@ -466,7 +466,7 @@ static bool isIdrFrameStart(PBUFFER_DESC buffer) {
}
// Reassemble the frame with the given frame number
static void reassembleFrame(int frameNumber) {
static void reassembleFrame(int frameNumber, bool frameIsLTR) {
if (nalChainHead != NULL) {
QUEUED_DECODE_UNIT qduDS;
PQUEUED_DECODE_UNIT qdu;
@ -539,7 +539,7 @@ static void reassembleFrame(int frameNumber) {
}
// Notify the control connection
connectionReceivedCompleteFrame(frameNumber);
connectionReceivedCompleteFrame(frameNumber, frameIsLTR);
// Clear frame drops
consecutiveFrameDrops = 0;
@ -748,6 +748,7 @@ static void processRtpPayload(PNV_VIDEO_PACKET videoPacket, int length,
BUFFER_DESC currentPos;
uint32_t frameIndex;
uint8_t flags;
uint8_t extraFlags;
bool firstPacket, lastPacket;
uint32_t streamPacketIndex;
uint8_t fecCurrentBlockNumber;
@ -765,6 +766,7 @@ static void processRtpPayload(PNV_VIDEO_PACKET videoPacket, int length,
fecLastBlockNumber = (videoPacket->multiFecBlocks >> 6) & 0x3;
frameIndex = videoPacket->frameIndex;
flags = videoPacket->flags;
extraFlags = videoPacket->extraFlags;
firstPacket = isFirstPacket(flags, fecCurrentBlockNumber);
lastPacket = (flags & FLAG_EOF) && fecCurrentBlockNumber == fecLastBlockNumber;
@ -1119,7 +1121,7 @@ static void processRtpPayload(PNV_VIDEO_PACKET videoPacket, int length,
}
}
reassembleFrame(frameIndex);
reassembleFrame(frameIndex, extraFlags & NV_VIDEO_PACKET_EXTRA_FLAG_LTR_FRAME);
}
}