diff --git a/src/ControlStream.c b/src/ControlStream.c index bd05b53..5caee01 100644 --- a/src/ControlStream.c +++ b/src/ControlStream.c @@ -559,12 +559,17 @@ static bool isPacketSentWaitingForAck(ENetPacket* packet) { return false; } -static bool sendMessageEnet(short ptype, short paylen, const void* payload, bool reliable) { +static bool sendMessageEnet(short ptype, short paylen, const void* payload, uint8_t channelId, uint32_t flags) { ENetPacket* enetPacket; int err; LC_ASSERT(AppVersionQuad[0] >= 5); + // Only send reliable packets to GFE + if (!IS_SUNSHINE()) { + flags = ENET_PACKET_FLAG_RELIABLE; + } + if (encryptedControlStream) { PNVCTL_ENCRYPTED_PACKET_HEADER encPacket; PNVCTL_ENET_PACKET_HEADER_V2 packet; @@ -572,7 +577,7 @@ static bool sendMessageEnet(short ptype, short paylen, const void* payload, bool enetPacket = enet_packet_create(NULL, sizeof(*encPacket) + AES_GCM_TAG_LENGTH + sizeof(*packet) + paylen, - reliable ? ENET_PACKET_FLAG_RELIABLE : ENET_PACKET_FLAG_UNSEQUENCED); + flags); if (enetPacket == NULL) { return false; } @@ -606,7 +611,7 @@ static bool sendMessageEnet(short ptype, short paylen, const void* payload, bool else { PNVCTL_ENET_PACKET_HEADER_V1 packet; enetPacket = enet_packet_create(NULL, sizeof(*packet) + paylen, - reliable ? ENET_PACKET_FLAG_RELIABLE : ENET_PACKET_FLAG_UNSEQUENCED); + flags); if (enetPacket == NULL) { return false; } @@ -625,11 +630,24 @@ static bool sendMessageEnet(short ptype, short paylen, const void* payload, bool enetPacket->userData = (void*)&packetFreed; enetPacket->freeCallback = enetPacketFreeCb; + // channelCount == 0 is possible if the peer is disconnected, + // so we need to assign channel ID under the enetMutex to prevent + // racing updates the peer. + if (!IS_SUNSHINE() || peer->channelCount == 0) { + // We always use a single channel for GFE. + channelId = 0; + } + else if (channelId >= peer->channelCount) { + // If this peer doesn't support enough channels, distribute the remaining channels onto the ones + // the peer does support. We don't use the channel to distinguish traffic types, so this is safe. + channelId %= peer->channelCount; + } + // Queue the packet to be sent - err = enet_peer_send(peer, 0, enetPacket); + err = enet_peer_send(peer, channelId, enetPacket); // Wait until the packet is actually sent to provide backpressure on senders - if (err == 0 && reliable) { + if (err == 0 && (flags & ENET_PACKET_FLAG_RELIABLE)) { // Try to send the packet enet_host_service(client, NULL, 0); @@ -697,13 +715,13 @@ static bool sendMessageTcp(short ptype, short paylen, const void* payload) { return true; } -static bool sendMessageAndForget(short ptype, short paylen, const void* payload) { +static bool sendMessageAndForget(short ptype, short paylen, const void* payload, uint8_t channelId, uint32_t flags) { bool ret; // Unlike regular sockets, ENet sockets aren't safe to invoke from multiple // threads at once. We have to synchronize them with a lock. if (AppVersionQuad[0] >= 5) { - ret = sendMessageEnet(ptype, paylen, payload, true); + ret = sendMessageEnet(ptype, paylen, payload, channelId, flags); } else { ret = sendMessageTcp(ptype, paylen, payload); @@ -712,9 +730,9 @@ static bool sendMessageAndForget(short ptype, short paylen, const void* payload) return ret; } -static bool sendMessageAndDiscardReply(short ptype, short paylen, const void* payload) { +static bool sendMessageAndDiscardReply(short ptype, short paylen, const void* payload, uint8_t channelId, uint32_t flags) { if (AppVersionQuad[0] >= 5) { - if (!sendMessageEnet(ptype, paylen, payload, true)) { + if (!sendMessageEnet(ptype, paylen, payload, channelId, flags)) { return false; } } @@ -1099,7 +1117,11 @@ static void lossStatsThreadFunc(void* context) { while (LbqPollQueueElement(&frameFecStatusQueue, (void**)&queuedFrameStatus) == LBQ_SUCCESS) { // Send as an unreliable packet, since it's not a critical message - if (!sendMessageEnet(SS_FRAME_FEC_PTYPE, sizeof(queuedFrameStatus->fecStatus), &queuedFrameStatus->fecStatus, false)) { + if (!sendMessageEnet(SS_FRAME_FEC_PTYPE, + sizeof(queuedFrameStatus->fecStatus), + &queuedFrameStatus->fecStatus, + CTRL_CHANNEL_GENERIC, + ENET_PACKET_FLAG_UNSEQUENCED)) { Limelog("Loss Stats: Sending frame FEC status message failed: %d\n", (int)LastSocketError()); ListenerCallbacks.connectionTerminated(LastSocketFail()); free(queuedFrameStatus); @@ -1111,7 +1133,11 @@ static void lossStatsThreadFunc(void* context) { } // Send the message (and don't expect a response) - if (!sendMessageAndForget(0x0200, sizeof(periodicPingPayload), periodicPingPayload)) { + if (!sendMessageAndForget(0x0200, + sizeof(periodicPingPayload), + periodicPingPayload, + CTRL_CHANNEL_GENERIC, + ENET_PACKET_FLAG_RELIABLE)) { Limelog("Loss Stats: Transaction failed: %d\n", (int)LastSocketError()); ListenerCallbacks.connectionTerminated(LastSocketFail()); return; @@ -1147,7 +1173,10 @@ static void lossStatsThreadFunc(void* context) { // Send the message (and don't expect a response) if (!sendMessageAndForget(packetTypes[IDX_LOSS_STATS], - payloadLengths[IDX_LOSS_STATS], lossStatsPayload)) { + payloadLengths[IDX_LOSS_STATS], + lossStatsPayload, + CTRL_CHANNEL_GENERIC, + 0)) { free(lossStatsPayload); Limelog("Loss Stats: Transaction failed: %d\n", (int)LastSocketError()); ListenerCallbacks.connectionTerminated(LastSocketFail()); @@ -1186,7 +1215,10 @@ static void requestIdrFrame(void) { // Send the reference frame invalidation request and read the response if (!sendMessageAndDiscardReply(packetTypes[IDX_INVALIDATE_REF_FRAMES], - payloadLengths[IDX_INVALIDATE_REF_FRAMES], payload)) { + payloadLengths[IDX_INVALIDATE_REF_FRAMES], + payload, + CTRL_CHANNEL_URGENT, + ENET_PACKET_FLAG_RELIABLE)) { Limelog("Request IDR Frame: Transaction failed: %d\n", (int)LastSocketError()); ListenerCallbacks.connectionTerminated(LastSocketFail()); return; @@ -1195,7 +1227,10 @@ static void requestIdrFrame(void) { else { // Send IDR frame request and read the response if (!sendMessageAndDiscardReply(packetTypes[IDX_REQUEST_IDR_FRAME], - payloadLengths[IDX_REQUEST_IDR_FRAME], preconstructedPayloads[IDX_REQUEST_IDR_FRAME])) { + payloadLengths[IDX_REQUEST_IDR_FRAME], + preconstructedPayloads[IDX_REQUEST_IDR_FRAME], + CTRL_CHANNEL_URGENT, + ENET_PACKET_FLAG_RELIABLE)) { Limelog("Request IDR Frame: Transaction failed: %d\n", (int)LastSocketError()); ListenerCallbacks.connectionTerminated(LastSocketFail()); return; @@ -1217,7 +1252,9 @@ static void requestInvalidateReferenceFrames(int startFrame, int endFrame) { // Send the reference frame invalidation request and read the response if (!sendMessageAndDiscardReply(packetTypes[IDX_INVALIDATE_REF_FRAMES], - payloadLengths[IDX_INVALIDATE_REF_FRAMES], payload)) { + payloadLengths[IDX_INVALIDATE_REF_FRAMES], + payload, CTRL_CHANNEL_URGENT, + ENET_PACKET_FLAG_RELIABLE)) { Limelog("Request Invaldiate Reference Frames: Transaction failed: %d\n", (int)LastSocketError()); ListenerCallbacks.connectionTerminated(LastSocketFail()); return; @@ -1326,11 +1363,11 @@ int stopControlStream(void) { } // Called by the input stream to send a packet for Gen 5+ servers -int sendInputPacketOnControlStream(unsigned char* data, int length) { +int sendInputPacketOnControlStream(unsigned char* data, int length, uint8_t channelId, uint32_t flags) { LC_ASSERT(AppVersionQuad[0] >= 5); // Send the input data (no reply expected) - if (sendMessageAndForget(packetTypes[IDX_INPUT_DATA], length, data) == 0) { + if (sendMessageAndForget(packetTypes[IDX_INPUT_DATA], length, data, channelId, flags) == 0) { return -1; } @@ -1384,8 +1421,8 @@ int startControlStream(void) { enet_address_set_address(&address, (struct sockaddr *)&RemoteAddr, RemoteAddrLen); enet_address_set_port(&address, ControlPortNumber); - // Create a client that can use 1 outgoing connection and 1 channel - client = enet_host_create(address.address.ss_family, NULL, 1, 1, 0, 0); + // Create a client + client = enet_host_create(address.address.ss_family, NULL, 1, CTRL_CHANNEL_MAX + 1, 0, 0); if (client == NULL) { stopping = true; return -1; @@ -1394,7 +1431,7 @@ int startControlStream(void) { client->intercept = ignoreDisconnectIntercept; // Connect to the host - peer = enet_host_connect(client, &address, 1, 0); + peer = enet_host_connect(client, &address, CTRL_CHANNEL_MAX + 1, 0); if (peer == NULL) { stopping = true; enet_host_destroy(client); @@ -1471,8 +1508,10 @@ int startControlStream(void) { // Send START A if (!sendMessageAndDiscardReply(packetTypes[IDX_START_A], - payloadLengths[IDX_START_A], - preconstructedPayloads[IDX_START_A])) { + payloadLengths[IDX_START_A], + preconstructedPayloads[IDX_START_A], + CTRL_CHANNEL_GENERIC, + ENET_PACKET_FLAG_RELIABLE)) { Limelog("Start A failed: %d\n", (int)LastSocketError()); err = LastSocketFail(); stopping = true; @@ -1503,8 +1542,10 @@ int startControlStream(void) { // Send START B if (!sendMessageAndDiscardReply(packetTypes[IDX_START_B], - payloadLengths[IDX_START_B], - preconstructedPayloads[IDX_START_B])) { + payloadLengths[IDX_START_B], + preconstructedPayloads[IDX_START_B], + CTRL_CHANNEL_GENERIC, + ENET_PACKET_FLAG_RELIABLE)) { Limelog("Start B failed: %d\n", (int)LastSocketError()); err = LastSocketFail(); stopping = true; diff --git a/src/InputStream.c b/src/InputStream.c index aeec2c1..c24351c 100644 --- a/src/InputStream.c +++ b/src/InputStream.c @@ -18,6 +18,10 @@ static float absCurrentPosY; // Limited by number of bits in activeGamepadMask #define MAX_GAMEPADS 16 +static uint8_t currentPenButtonState; +static uint16_t currentActiveGamepadMask; +static int currentControllerButtonState[MAX_GAMEPADS]; + #define CLAMP(val, min, max) (((val) < (min)) ? (min) : (((val) > (max)) ? (max) : (val))) #define MAX_INPUT_PACKET_SIZE 128 @@ -46,6 +50,8 @@ static float absCurrentPosY; // Contains input stream packets typedef struct _PACKET_HOLDER { LINKED_BLOCKING_QUEUE_ENTRY entry; + uint32_t enetPacketFlags; + uint8_t channelId; // The union must be the last member since we abuse the NV_UNICODE_PACKET // text field to store variable length data which gets split before being @@ -92,6 +98,10 @@ int initializeInputStream(void) { needsBatchedScroll = APP_VERSION_AT_LEAST(7, 1, 409) && !IS_SUNSHINE(); batchedScrollDelta = 0; + currentPenButtonState = 0; + currentActiveGamepadMask = 0; + memset(currentControllerButtonState, 0, sizeof(currentControllerButtonState)); + // Start with the virtual mouse centered absCurrentPosX = absCurrentPosY = 0.5f; return 0; @@ -208,7 +218,10 @@ static bool sendInputPacket(PPACKET_HOLDER holder) { // has been removed. We send the plaintext packet through and the control stream code will do // the encryption. if (encryptedControlStream) { - err = (SOCK_RET)sendInputPacketOnControlStream((unsigned char*)&holder->packet, PACKET_SIZE(holder)); + err = (SOCK_RET)sendInputPacketOnControlStream((unsigned char*)&holder->packet, + PACKET_SIZE(holder), + holder->channelId, + holder->enetPacketFlags); if (err < 0) { Limelog("Input: sendInputPacketOnControlStream() failed: %d\n", (int) err); ListenerCallbacks.connectionTerminated(err); @@ -256,7 +269,9 @@ static bool sendInputPacket(PPACKET_HOLDER holder) { } err = (SOCK_RET)sendInputPacketOnControlStream((unsigned char*) encryptedBuffer, - (int) (encryptedSize + sizeof(encryptedLengthPrefix))); + (int)(encryptedSize + sizeof(encryptedLengthPrefix)), + holder->channelId, + holder->enetPacketFlags); if (err < 0) { Limelog("Input: sendInputPacketOnControlStream() failed: %d\n", (int) err); ListenerCallbacks.connectionTerminated(err); @@ -614,6 +629,8 @@ static int sendEnableHaptics(void) { return -1; } + holder->channelId = CTRL_CHANNEL_GENERIC; + holder->enetPacketFlags = ENET_PACKET_FLAG_RELIABLE; holder->packet.haptics.header.size = BE32(sizeof(NV_HAPTICS_PACKET) - sizeof(uint32_t)); holder->packet.haptics.header.magic = LE32(ENABLE_HAPTICS_MAGIC); holder->packet.haptics.enable = LE16(1); @@ -716,6 +733,11 @@ int LiSendMouseMoveEvent(short deltaX, short deltaY) { return -1; } + holder->channelId = CTRL_CHANNEL_MOUSE; + + // These deltas are cumulative, so allow them to be processed out of order + holder->enetPacketFlags = ENET_PACKET_FLAG_UNSEQUENCED; + holder->packet.mouseMoveRel.header.size = BE32(sizeof(NV_REL_MOUSE_MOVE_PACKET) - sizeof(uint32_t)); if (AppVersionQuad[0] >= 5) { holder->packet.mouseMoveRel.header.magic = LE32(MOUSE_MOVE_REL_MAGIC_GEN5); @@ -750,6 +772,11 @@ int LiSendMousePositionEvent(short x, short y, short referenceWidth, short refer return -1; } + holder->channelId = CTRL_CHANNEL_MOUSE; + + // The latest packet always contains the latest data, so discard older packets upon reordering + holder->enetPacketFlags = 0; + holder->packet.mouseMoveAbs.header.size = BE32(sizeof(NV_ABS_MOUSE_MOVE_PACKET) - sizeof(uint32_t)); holder->packet.mouseMoveAbs.header.magic = LE32(MOUSE_MOVE_ABS_MAGIC); holder->packet.mouseMoveAbs.x = BE16(x); @@ -806,6 +833,8 @@ int LiSendMouseButtonEvent(char action, int button) { return -1; } + holder->channelId = CTRL_CHANNEL_MOUSE; + holder->enetPacketFlags = ENET_PACKET_FLAG_RELIABLE; holder->packet.mouseButton.header.size = BE32(sizeof(NV_MOUSE_BUTTON_PACKET) - sizeof(uint32_t)); holder->packet.mouseButton.header.magic = (uint8_t)action; if (AppVersionQuad[0] >= 5) { @@ -838,6 +867,9 @@ int LiSendKeyboardEvent2(short keyCode, char keyAction, char modifiers, char fla return -1; } + holder->channelId = CTRL_CHANNEL_KEYBOARD; + holder->enetPacketFlags = ENET_PACKET_FLAG_RELIABLE; + // For proper behavior, the MODIFIER flag must not be set on the modifier key down event itself // for the extended modifiers on the right side of the keyboard. If the MODIFIER flag is set, // GFE will synthesize an errant key down event for the non-extended key, causing that key to be @@ -915,6 +947,10 @@ int LiSendUtf8TextEvent(const char *text, unsigned int length) { if (holder == NULL) { return -1; } + + holder->channelId = CTRL_CHANNEL_UTF8; + holder->enetPacketFlags = ENET_PACKET_FLAG_RELIABLE; + // Magic + string length holder->packet.unicode.header.size = BE32(sizeof(uint32_t) + length); holder->packet.unicode.header.magic = LE32(UTF8_TEXT_EVENT_MAGIC); @@ -956,6 +992,16 @@ static int sendControllerEventInternal(short controllerNumber, short activeGamep return -1; } + // Send each controller on a separate channel + holder->channelId = CTRL_CHANNEL_GAMEPAD_BASE + controllerNumber; + + // If the active gamepad mask or button state changes, send a reliable event to ensure delivery. + // For axis-only updates, we use sequenced unreliable packets to only process the latest updates. + holder->enetPacketFlags = (activeGamepadMask != currentActiveGamepadMask || buttonFlags != currentControllerButtonState[controllerNumber]) ? + ENET_PACKET_FLAG_RELIABLE : 0; + currentActiveGamepadMask = activeGamepadMask; + currentControllerButtonState[controllerNumber] = buttonFlags; + if (AppVersionQuad[0] == 3) { // Generation 3 servers don't support multiple controllers so we send // the legacy packet @@ -1065,6 +1111,11 @@ int LiSendHighResScrollEvent(short scrollAmount) { return -1; } + holder->channelId = CTRL_CHANNEL_MOUSE; + + // These deltas are cumulative, so allow them to be processed out of order + holder->enetPacketFlags = ENET_PACKET_FLAG_UNSEQUENCED; + holder->packet.scroll.header.size = BE32(sizeof(NV_SCROLL_PACKET) - sizeof(uint32_t)); if (AppVersionQuad[0] >= 5) { holder->packet.scroll.header.magic = LE32(SCROLL_MAGIC_GEN5); @@ -1095,6 +1146,11 @@ int LiSendHighResScrollEvent(short scrollAmount) { return -1; } + holder->channelId = CTRL_CHANNEL_MOUSE; + + // These deltas are cumulative, so allow them to be processed out of order + holder->enetPacketFlags = ENET_PACKET_FLAG_UNSEQUENCED; + holder->packet.scroll.header.size = BE32(sizeof(NV_SCROLL_PACKET) - sizeof(uint32_t)); if (AppVersionQuad[0] >= 5) { holder->packet.scroll.header.magic = LE32(SCROLL_MAGIC_GEN5); @@ -1145,6 +1201,11 @@ int LiSendHighResHScrollEvent(short scrollAmount) { return -1; } + holder->channelId = CTRL_CHANNEL_MOUSE; + + // These deltas are cumulative, so allow them to be processed out of order + holder->enetPacketFlags = ENET_PACKET_FLAG_UNSEQUENCED; + holder->packet.hscroll.header.size = BE32(sizeof(SS_HSCROLL_PACKET) - sizeof(uint32_t)); holder->packet.hscroll.header.magic = LE32(SS_HSCROLL_MAGIC); holder->packet.hscroll.scrollAmount = BE16(scrollAmount); @@ -1181,6 +1242,12 @@ int LiSendTouchEvent(uint8_t eventType, uint32_t pointerId, float x, float y, fl return -1; } + holder->channelId = CTRL_CHANNEL_TOUCH; + + // Allow move and hover events to be dropped if a newer one arrives, but don't allow + // state changing events like up/down/leave events to be dropped. + holder->enetPacketFlags = TOUCH_EVENT_IS_BATCHABLE(eventType) ? 0 : ENET_PACKET_FLAG_RELIABLE; + holder->packet.touch.header.size = BE32(sizeof(SS_TOUCH_PACKET) - sizeof(uint32_t)); holder->packet.touch.header.magic = LE32(SS_TOUCH_MAGIC); holder->packet.touch.eventType = eventType; @@ -1220,6 +1287,13 @@ int LiSendPenEvent(uint8_t eventType, uint8_t toolType, uint8_t penButtons, return -1; } + holder->channelId = CTRL_CHANNEL_PEN; + + // Allow move and hover events to be dropped if a newer one arrives (if no buttons changed), + // but don't allow state changing events like up/down/leave events to be dropped. + holder->enetPacketFlags = (TOUCH_EVENT_IS_BATCHABLE(eventType) && !(penButtons ^ currentPenButtonState)) ? 0 : ENET_PACKET_FLAG_RELIABLE; + currentPenButtonState = penButtons; + holder->packet.pen.header.size = BE32(sizeof(SS_PEN_PACKET) - sizeof(uint32_t)); holder->packet.pen.header.magic = LE32(SS_PEN_MAGIC); holder->packet.pen.eventType = eventType; @@ -1262,6 +1336,10 @@ int LiSendControllerArrivalEvent(uint8_t controllerNumber, uint16_t activeGamepa return -1; } + // Send each controller on a separate channel + holder->channelId = CTRL_CHANNEL_GAMEPAD_BASE + controllerNumber; + holder->enetPacketFlags = ENET_PACKET_FLAG_RELIABLE; + holder->packet.controllerArrival.header.size = BE32(sizeof(SS_CONTROLLER_ARRIVAL_PACKET) - sizeof(uint32_t)); holder->packet.controllerArrival.header.magic = LE32(SS_CONTROLLER_ARRIVAL_MAGIC); holder->packet.controllerArrival.controllerNumber = controllerNumber; @@ -1303,6 +1381,13 @@ int LiSendControllerTouchEvent(uint8_t controllerNumber, uint8_t eventType, uint return -1; } + // Send each controller on a separate channel + holder->channelId = CTRL_CHANNEL_GAMEPAD_BASE + controllerNumber; + + // Allow move and hover events to be dropped if a newer one arrives, but don't allow + // state changing events like up/down/leave events to be dropped. + holder->enetPacketFlags = TOUCH_EVENT_IS_BATCHABLE(eventType) ? 0 : ENET_PACKET_FLAG_RELIABLE; + holder->packet.controllerTouch.header.size = BE32(sizeof(SS_CONTROLLER_TOUCH_PACKET) - sizeof(uint32_t)); holder->packet.controllerTouch.header.magic = LE32(SS_CONTROLLER_TOUCH_MAGIC); holder->packet.controllerTouch.controllerNumber = controllerNumber; @@ -1344,6 +1429,12 @@ int LiSendControllerMotionEvent(uint8_t controllerNumber, uint8_t motionType, fl return -1; } + // Send each controller on a separate channel + holder->channelId = CTRL_CHANNEL_GAMEPAD_BASE + controllerNumber; + + // Motion events are so rapid that we can just drop any events that are lost in transit + holder->enetPacketFlags = 0; + holder->packet.controllerMotion.header.size = BE32(sizeof(SS_CONTROLLER_MOTION_PACKET) - sizeof(uint32_t)); holder->packet.controllerMotion.header.magic = LE32(SS_CONTROLLER_MOTION_MAGIC); holder->packet.controllerMotion.controllerNumber = controllerNumber; @@ -1384,6 +1475,10 @@ int LiSendControllerBatteryEvent(uint8_t controllerNumber, uint8_t batteryState, return -1; } + // Send each controller on a separate channel + holder->channelId = CTRL_CHANNEL_GAMEPAD_BASE + controllerNumber; + holder->enetPacketFlags = ENET_PACKET_FLAG_RELIABLE; + holder->packet.controllerBattery.header.size = BE32(sizeof(SS_CONTROLLER_BATTERY_PACKET) - sizeof(uint32_t)); holder->packet.controllerBattery.header.magic = LE32(SS_CONTROLLER_BATTERY_MAGIC); holder->packet.controllerBattery.controllerNumber = controllerNumber; diff --git a/src/Limelight-internal.h b/src/Limelight-internal.h index 33b59c6..e3f20b2 100644 --- a/src/Limelight-internal.h +++ b/src/Limelight-internal.h @@ -52,6 +52,17 @@ extern uint32_t SunshineFeatureFlags; #define SCM_AV1_MAIN8 0x10000 // Sunshine extension #define SCM_AV1_MAIN10 0x20000 // Sunshine extension +// ENet channel ID values +#define CTRL_CHANNEL_GENERIC 0x00 +#define CTRL_CHANNEL_URGENT 0x01 // IDR and reference frame invalidation requests +#define CTRL_CHANNEL_KEYBOARD 0x02 +#define CTRL_CHANNEL_MOUSE 0x03 +#define CTRL_CHANNEL_PEN 0x04 +#define CTRL_CHANNEL_TOUCH 0x05 +#define CTRL_CHANNEL_UTF8 0x06 +#define CTRL_CHANNEL_GAMEPAD_BASE 0x10 // 0x10 to 0x20 by controller index +#define CTRL_CHANNEL_MAX 0x20 + #ifndef UINT24_MAX #define UINT24_MAX 0xFFFFFF #endif @@ -106,7 +117,7 @@ void connectionReceivedCompleteFrame(int frameIndex); void connectionSawFrame(int frameIndex); void connectionLostPackets(int lastReceivedPacket, int nextReceivedPacket); void connectionSendFrameFecStatus(PSS_FRAME_FEC_STATUS fecStatus); -int sendInputPacketOnControlStream(unsigned char* data, int length); +int sendInputPacketOnControlStream(unsigned char* data, int length, uint8_t channelId, uint32_t flags); bool isControlDataInTransit(void); int performRtspHandshake(PSERVER_INFORMATION serverInfo);