diff --git a/src/Connection.c b/src/Connection.c index dfc849e..6efe357 100644 --- a/src/Connection.c +++ b/src/Connection.c @@ -11,7 +11,7 @@ static long terminationCallbackErrorCode; char* RemoteAddrString; struct sockaddr_storage RemoteAddr; SOCKADDR_LEN RemoteAddrLen; -int ServerMajorVersion; +int AppVersionQuad[4]; STREAM_CONFIGURATION StreamConfig; CONNECTION_LISTENER_CALLBACKS ListenerCallbacks; DECODER_RENDERER_CALLBACKS VideoCallbacks; @@ -206,15 +206,20 @@ static int resolveHostName(const char* host) } // Starts the connection to the streaming machine -int LiStartConnection(const char* host, PSTREAM_CONFIGURATION streamConfig, PCONNECTION_LISTENER_CALLBACKS clCallbacks, - PDECODER_RENDERER_CALLBACKS drCallbacks, PAUDIO_RENDERER_CALLBACKS arCallbacks, - void* renderContext, int drFlags, int _serverMajorVersion) { +int LiStartConnection(PSERVER_INFORMATION serverInfo, PSTREAM_CONFIGURATION streamConfig, PCONNECTION_LISTENER_CALLBACKS clCallbacks, + PDECODER_RENDERER_CALLBACKS drCallbacks, PAUDIO_RENDERER_CALLBACKS arCallbacks, void* renderContext, int drFlags) { int err; NegotiatedVideoFormat = 0; - ServerMajorVersion = _serverMajorVersion; memcpy(&StreamConfig, streamConfig, sizeof(StreamConfig)); - RemoteAddrString = strdup(host); + RemoteAddrString = strdup(serverInfo->address); + + // Extract the appversion from the supplied string + if (extractVersionQuadFromString(serverInfo->serverInfoAppVersion, + AppVersionQuad) < 0) { + Limelog("Invalid appversion string: %s\n", serverInfo->serverInfoAppVersion); + return -1; + } // Replace missing callbacks with placeholders fixupMissingCallbacks(&drCallbacks, &arCallbacks, &clCallbacks); @@ -244,7 +249,7 @@ int LiStartConnection(const char* host, PSTREAM_CONFIGURATION streamConfig, PCON Limelog("Resolving host name..."); ListenerCallbacks.stageStarting(STAGE_NAME_RESOLUTION); - err = resolveHostName(host); + err = resolveHostName(serverInfo->address); if (err != 0) { Limelog("failed: %d\n", err); ListenerCallbacks.stageFailed(STAGE_NAME_RESOLUTION, err); diff --git a/src/ControlStream.c b/src/ControlStream.c index 07f8abd..b11119b 100644 --- a/src/ControlStream.c +++ b/src/ControlStream.c @@ -151,17 +151,17 @@ int initializeControlStream(void) { PltCreateEvent(&invalidateRefFramesEvent); LbqInitializeLinkedBlockingQueue(&invalidReferenceFrameTuples, 20); - if (ServerMajorVersion == 3) { + if (AppVersionQuad[0] == 3) { packetTypes = (short*)packetTypesGen3; payloadLengths = (short*)payloadLengthsGen3; preconstructedPayloads = (char**)preconstructedPayloadsGen3; } - else if (ServerMajorVersion == 4) { + else if (AppVersionQuad[0] == 4) { packetTypes = (short*)packetTypesGen4; payloadLengths = (short*)payloadLengthsGen4; preconstructedPayloads = (char**)preconstructedPayloadsGen4; } - else if (ServerMajorVersion == 5) { + else if (AppVersionQuad[0] == 5) { packetTypes = (short*)packetTypesGen5; payloadLengths = (short*)payloadLengthsGen5; preconstructedPayloads = (char**)preconstructedPayloadsGen5; @@ -290,7 +290,7 @@ static int sendMessageEnet(short ptype, short paylen, const void* payload) { ENetEvent event; int err; - LC_ASSERT(ServerMajorVersion >= 5); + LC_ASSERT(AppVersionQuad[0] >= 5); // We may be trying to disconnect, so our peer could be gone. // This check is safe because we're guaranteed to be holding enetMutex. @@ -347,7 +347,7 @@ static int sendMessageTcp(short ptype, short paylen, const void* payload) { PNVCTL_TCP_PACKET_HEADER packet; SOCK_RET err; - LC_ASSERT(ServerMajorVersion < 5); + LC_ASSERT(AppVersionQuad[0] < 5); packet = malloc(sizeof(*packet) + paylen); if (packet == NULL) { @@ -373,7 +373,7 @@ static int sendMessageAndForget(short ptype, short paylen, const void* payload) // Unlike regular sockets, ENet sockets aren't safe to invoke from multiple // threads at once. We have to synchronize them with a lock. - if (ServerMajorVersion >= 5) { + if (AppVersionQuad[0] >= 5) { PltLockMutex(&enetMutex); ret = sendMessageEnet(ptype, paylen, payload); PltUnlockMutex(&enetMutex); @@ -387,7 +387,7 @@ static int sendMessageAndForget(short ptype, short paylen, const void* payload) static int sendMessageAndDiscardReply(short ptype, short paylen, const void* payload) { // Discard the response - if (ServerMajorVersion >= 5) { + if (AppVersionQuad[0] >= 5) { ENetEvent event; PltLockMutex(&enetMutex); @@ -469,7 +469,7 @@ static void lossStatsThreadFunc(void* context) { static void requestIdrFrame(void) { long long payload[3]; - if (ServerMajorVersion >= 5) { + if (AppVersionQuad[0] >= 5) { // Form the payload if (lastSeenFrame < 0x20) { payload[0] = 0; @@ -610,7 +610,7 @@ int stopControlStream(void) { // Called by the input stream to send a packet for Gen 5+ servers int sendInputPacketOnControlStream(unsigned char* data, int length) { - LC_ASSERT(ServerMajorVersion >= 5); + LC_ASSERT(AppVersionQuad[0] >= 5); // Send the input data (no reply expected) if (sendMessageAndForget(packetTypes[IDX_INPUT_DATA], length, data) == 0) { @@ -626,7 +626,7 @@ int startControlStream(void) { PltCreateMutex(&enetMutex); - if (ServerMajorVersion >= 5) { + if (AppVersionQuad[0] >= 5) { ENetAddress address; ENetEvent event; diff --git a/src/InputStream.c b/src/InputStream.c index b3ce360..f88e034 100644 --- a/src/InputStream.c +++ b/src/InputStream.c @@ -116,7 +116,7 @@ static int encryptData(const unsigned char* plaintext, int plaintextLen, int ret; int len; - if (ServerMajorVersion >= 7) { + if (AppVersionQuad[0] >= 7) { EVP_CIPHER_CTX_init(&cipherContext); // Gen 7 servers use 128-bit AES GCM @@ -332,7 +332,7 @@ static void inputSendThreadProc(void* context) { encryptedLengthPrefix = htonl((unsigned long)encryptedSize); memcpy(&encryptedBuffer[0], &encryptedLengthPrefix, 4); - if (ServerMajorVersion < 5) { + if (AppVersionQuad[0] < 5) { // Send the encrypted payload err = send(inputSock, (const char*) encryptedBuffer, (int) (encryptedSize + sizeof(encryptedLengthPrefix)), 0); @@ -347,7 +347,7 @@ static void inputSendThreadProc(void* context) { // bytes of ciphertext in the most recent game controller packet as the IV for // future encryption. I think it may be a buffer overrun on their end but we'll have // to mimic it to work correctly. - if (ServerMajorVersion >= 7 && encryptedSize >= 16 + sizeof(currentAesIv)) { + if (AppVersionQuad[0] >= 7 && encryptedSize >= 16 + sizeof(currentAesIv)) { memcpy(currentAesIv, &encryptedBuffer[4 + encryptedSize - sizeof(currentAesIv)], sizeof(currentAesIv)); @@ -369,7 +369,7 @@ int startInputStream(void) { int err; // After Gen 5, we send input on the control stream - if (ServerMajorVersion < 5) { + if (AppVersionQuad[0] < 5) { inputSock = connectTcpSocket(&RemoteAddr, RemoteAddrLen, 35043, INPUT_STREAM_TIMEOUT_SEC); if (inputSock == INVALID_SOCKET) { @@ -426,7 +426,7 @@ int LiSendMouseMoveEvent(short deltaX, short deltaY) { holder->packet.mouseMove.header.packetType = htonl(PACKET_TYPE_MOUSE_MOVE); holder->packet.mouseMove.magic = MOUSE_MOVE_MAGIC; // On Gen 5 servers, the header code is incremented by one - if (ServerMajorVersion >= 5) { + if (AppVersionQuad[0] >= 5) { holder->packet.mouseMove.magic++; } holder->packet.mouseMove.deltaX = htons(deltaX); @@ -457,7 +457,7 @@ int LiSendMouseButtonEvent(char action, int button) { holder->packetLength = sizeof(NV_MOUSE_BUTTON_PACKET); holder->packet.mouseButton.header.packetType = htonl(PACKET_TYPE_MOUSE_BUTTON); holder->packet.mouseButton.action = action; - if (ServerMajorVersion >= 5) { + if (AppVersionQuad[0] >= 5) { holder->packet.mouseButton.action++; } holder->packet.mouseButton.button = htonl(button); @@ -515,7 +515,7 @@ static int sendControllerEventInternal(short controllerNumber, short buttonFlags return -1; } - if (ServerMajorVersion == 3) { + if (AppVersionQuad[0] == 3) { // Generation 3 servers don't support multiple controllers so we send // the legacy packet holder->packetLength = sizeof(NV_CONTROLLER_PACKET); @@ -538,7 +538,7 @@ static int sendControllerEventInternal(short controllerNumber, short buttonFlags holder->packet.multiController.header.packetType = htonl(PACKET_TYPE_MULTI_CONTROLLER); holder->packet.multiController.headerA = MC_HEADER_A; // On Gen 5 servers, the header code is decremented by one - if (ServerMajorVersion >= 5) { + if (AppVersionQuad[0] >= 5) { holder->packet.multiController.headerA--; } holder->packet.multiController.headerB = MC_HEADER_B; @@ -598,7 +598,7 @@ int LiSendScrollEvent(signed char scrollClicks) { holder->packet.scroll.header.packetType = htonl(PACKET_TYPE_SCROLL); holder->packet.scroll.magicA = MAGIC_A; // On Gen 5 servers, the header code is incremented by one - if (ServerMajorVersion >= 5) { + if (AppVersionQuad[0] >= 5) { holder->packet.scroll.magicA++; } holder->packet.scroll.zero1 = 0; diff --git a/src/Limelight-internal.h b/src/Limelight-internal.h index f8712aa..efd5e96 100644 --- a/src/Limelight-internal.h +++ b/src/Limelight-internal.h @@ -12,7 +12,7 @@ extern char* RemoteAddrString; extern struct sockaddr_storage RemoteAddr; extern SOCKADDR_LEN RemoteAddrLen; -extern int ServerMajorVersion; +extern int AppVersionQuad[4]; extern STREAM_CONFIGURATION StreamConfig; extern CONNECTION_LISTENER_CALLBACKS ListenerCallbacks; extern DECODER_RENDERER_CALLBACKS VideoCallbacks; @@ -21,6 +21,7 @@ extern int NegotiatedVideoFormat; int isBeforeSignedInt(int numA, int numB, int ambiguousCase); int serviceEnetHost(ENetHost* client, ENetEvent* event, enet_uint32 timeoutMs); +int extractVersionQuadFromString(const char* string, int* quad); void fixupMissingCallbacks(PDECODER_RENDERER_CALLBACKS* drCallbacks, PAUDIO_RENDERER_CALLBACKS* arCallbacks, PCONNECTION_LISTENER_CALLBACKS* clCallbacks); diff --git a/src/Limelight.h b/src/Limelight.h index 52fdd97..f8579ae 100644 --- a/src/Limelight.h +++ b/src/Limelight.h @@ -214,15 +214,28 @@ typedef struct _CONNECTION_LISTENER_CALLBACKS { // Use this function to zero the connection callbacks when allocated on the stack or heap 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; +} SERVER_INFORMATION, *PSERVER_INFORMATION; + +// Use this function to zero the server information when allocated on the stack or heap +void LiInitializeServerInformation(PSERVER_INFORMATION serverInfo); + // This function begins streaming. // // Callbacks are all optional. Pass NULL for individual callbacks within each struct or pass NULL for the entire struct // to use the defaults for all callbacks. // -// _serverMajorVersion is the major version number of the 'appversion' tag in the /serverinfo request -// -int LiStartConnection(const char* host, PSTREAM_CONFIGURATION streamConfig, PCONNECTION_LISTENER_CALLBACKS clCallbacks, - PDECODER_RENDERER_CALLBACKS drCallbacks, PAUDIO_RENDERER_CALLBACKS arCallbacks, void* renderContext, int drFlags, int _serverMajorVersion); +int LiStartConnection(PSERVER_INFORMATION serverInfo, PSTREAM_CONFIGURATION streamConfig, PCONNECTION_LISTENER_CALLBACKS clCallbacks, + PDECODER_RENDERER_CALLBACKS drCallbacks, PAUDIO_RENDERER_CALLBACKS arCallbacks, void* renderContext, int drFlags); // This function stops streaming. void LiStopConnection(void); diff --git a/src/Misc.c b/src/Misc.c index 1e69468..5c26a6e 100644 --- a/src/Misc.c +++ b/src/Misc.c @@ -37,6 +37,38 @@ int isBeforeSignedInt(int numA, int numB, int ambiguousCase) { } } +int extractVersionQuadFromString(const char* string, int* quad) { + char versionString[128]; + char* nextDot; + char* nextNumber; + int i; + + strcpy(versionString, string); + nextNumber = versionString; + + for (i = 0; i < 4; i++) { + if (i == 3) { + nextDot = strchr(nextNumber, '\0'); + } + else { + nextDot = strchr(nextNumber, '.'); + } + if (nextDot == NULL) { + return -1; + } + + // Cut the string off at the next dot + *nextDot = '\0'; + + quad[i] = atoi(nextNumber); + + // Move on to the next segment + nextNumber = nextDot + 1; + } + + return 0; +} + void LiInitializeStreamConfiguration(PSTREAM_CONFIGURATION streamConfig) { memset(streamConfig, 0, sizeof(*streamConfig)); } @@ -51,4 +83,8 @@ void LiInitializeAudioCallbacks(PAUDIO_RENDERER_CALLBACKS arCallbacks) { void LiInitializeConnectionCallbacks(PCONNECTION_LISTENER_CALLBACKS clCallbacks) { memset(clCallbacks, 0, sizeof(*clCallbacks)); -} \ No newline at end of file +} + +void LiInitializeServerInformation(PSERVER_INFORMATION serverInfo) { + memset(serverInfo, 0, sizeof(*serverInfo)); +} diff --git a/src/RtspConnection.c b/src/RtspConnection.c index 38e1453..d1960c2 100644 --- a/src/RtspConnection.c +++ b/src/RtspConnection.c @@ -273,7 +273,7 @@ Exit: static int transactRtspMessage(PRTSP_MESSAGE request, PRTSP_MESSAGE response, int expectingPayload, int* error) { // Gen 5+ does RTSP over ENet not TCP - if (ServerMajorVersion >= 5) { + if (AppVersionQuad[0] >= 5) { return transactRtspMessageEnet(request, response, expectingPayload, error); } else { @@ -349,7 +349,7 @@ static int setupStream(PRTSP_MESSAGE response, char* target, int* error) { } } - if (ServerMajorVersion >= 6) { + if (AppVersionQuad[0] >= 6) { // It looks like GFE doesn't care what we say our port is but // we need to give it some port to successfully complete the // handshake process. @@ -446,7 +446,7 @@ int performRtspHandshake(void) { currentSeqNumber = 1; hasSessionId = 0; - switch (ServerMajorVersion) { + switch (AppVersionQuad[0]) { case 3: rtspClientVersion = 10; break; @@ -467,7 +467,7 @@ int performRtspHandshake(void) { } // Gen 5 servers use ENet to do the RTSP handshake - if (ServerMajorVersion >= 5) { + if (AppVersionQuad[0] >= 5) { ENetAddress address; ENetEvent event; @@ -562,7 +562,7 @@ int performRtspHandshake(void) { int error = -1; if (!setupStream(&response, - ServerMajorVersion >= 5 ? "streamid=audio/0/0" : "streamid=audio", + AppVersionQuad[0] >= 5 ? "streamid=audio/0/0" : "streamid=audio", &error)) { Limelog("RTSP SETUP streamid=audio request failed: %d\n", error); ret = error; @@ -594,7 +594,7 @@ int performRtspHandshake(void) { int error = -1; if (!setupStream(&response, - ServerMajorVersion >= 5 ? "streamid=video/0/0" : "streamid=video", + AppVersionQuad[0] >= 5 ? "streamid=video/0/0" : "streamid=video", &error)) { Limelog("RTSP SETUP streamid=video request failed: %d\n", error); ret = error; @@ -611,7 +611,7 @@ int performRtspHandshake(void) { freeMessage(&response); } - if (ServerMajorVersion >= 5) { + if (AppVersionQuad[0] >= 5) { RTSP_MESSAGE response; int error = -1; @@ -695,7 +695,7 @@ int performRtspHandshake(void) { Exit: // Cleanup the ENet stuff - if (ServerMajorVersion >= 5) { + if (AppVersionQuad[0] >= 5) { if (peer != NULL) { enet_peer_disconnect_now(peer, 0); peer = NULL; diff --git a/src/SdpGenerator.c b/src/SdpGenerator.c index 013df7e..f0376a8 100644 --- a/src/SdpGenerator.c +++ b/src/SdpGenerator.c @@ -185,7 +185,7 @@ static PSDP_OPTION getAttributesList(char*urlSafeAddr) { err |= addAttributeString(&optionHead, "x-nv-video[0].framesWithInvalidRefThreshold", "0"); sprintf(payloadStr, "%d", StreamConfig.bitrate); - if (ServerMajorVersion >= 5) { + if (AppVersionQuad[0] >= 5) { err |= addAttributeString(&optionHead, "x-nv-vqos[0].bw.minimumBitrateKbps", payloadStr); err |= addAttributeString(&optionHead, "x-nv-vqos[0].bw.maximumBitrateKbps", payloadStr); } @@ -219,17 +219,17 @@ static PSDP_OPTION getAttributesList(char*urlSafeAddr) { err |= addAttributeString(&optionHead, "x-nv-aqos.qosTrafficType", "4"); } - if (ServerMajorVersion == 3) { + if (AppVersionQuad[0] == 3) { err |= addGen3Options(&optionHead, urlSafeAddr); } - else if (ServerMajorVersion == 4) { + else if (AppVersionQuad[0] == 4) { err |= addGen4Options(&optionHead, urlSafeAddr); } else { err |= addGen5Options(&optionHead); } - if (ServerMajorVersion >= 4) { + if (AppVersionQuad[0] >= 4) { if (NegotiatedVideoFormat == VIDEO_FORMAT_H265) { err |= addAttributeString(&optionHead, "x-nv-clientSupportHevc", "1"); err |= addAttributeString(&optionHead, "x-nv-vqos[0].bitStreamFormat", "1"); @@ -298,7 +298,7 @@ static int fillSdpTail(char* buffer) { return sprintf(buffer, "t=0 0\r\n" "m=video %d \r\n", - ServerMajorVersion < 4 ? 47996 : 47998); + AppVersionQuad[0] < 4 ? 47996 : 47998); } // Get the SDP attributes for the stream config diff --git a/src/VideoDepacketizer.c b/src/VideoDepacketizer.c index 89122e3..77626cd 100644 --- a/src/VideoDepacketizer.c +++ b/src/VideoDepacketizer.c @@ -507,7 +507,7 @@ void processRtpPayload(PNV_VIDEO_PACKET videoPacket, int length) { lastPacketInStream = streamPacketIndex; // If this is the first packet, skip the frame header (if one exists) - if (firstPacket && ServerMajorVersion >= 5) { + if (firstPacket && AppVersionQuad[0] >= 5) { currentPos.offset += 8; currentPos.length -= 8; } diff --git a/src/VideoStream.c b/src/VideoStream.c index 4db3bab..7e6e5ec 100644 --- a/src/VideoStream.c +++ b/src/VideoStream.c @@ -218,7 +218,7 @@ int startVideoStream(void* rendererContext, int drFlags) { } } - if (ServerMajorVersion == 3) { + if (AppVersionQuad[0] == 3) { // Connect this socket to open port 47998 for our ping thread firstFrameSocket = connectTcpSocket(&RemoteAddr, RemoteAddrLen, FIRST_FRAME_PORT, FIRST_FRAME_TIMEOUT_SEC); @@ -234,7 +234,7 @@ int startVideoStream(void* rendererContext, int drFlags) { return err; } - if (ServerMajorVersion == 3) { + if (AppVersionQuad[0] == 3) { // Read the first frame to start the flow of video err = readFirstFrame(); if (err != 0) {