CVE-2023-42801 (published 2023-12-14T17:15Z)

Moonlight-common-c contains the core GameStream client code shared between Moonlight clients. Moonlight-common-c is vulnerable to buffer overflow starting in commit f57bd745b4cbed577ea654fad4701bea4d38b44c. A malicious game streaming server could exploit a buffer overflow vulnerability to crash a moonlight client. Achieving RCE is possible but unlikely, due to stack canaries in use by modern compiler toolchains. The published binaries for official clients Qt, Android, iOS/tvOS, and Embedded are built with stack canaries, but some unofficial clients may not use stack canaries. This vulnerability takes place after the pairing process, so it requires the client to be tricked into pairing to a malicious host. It is not possible to perform using a man-in-the-middle due to public key pinning that takes place during the pairing process. The bug was addressed in commit b2497a3918a6d79808d9fd0c04734786e70d5954.

CVSS v3 base score: 7.6, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:H)

Weakness information:
cwe_id: CWE-120
cwe_name: Buffer Copy without Checking Size of Input ('Classic Buffer Overflow')
description: The product copies an input buffer to an output buffer without verifying that the size of the input buffer is less than the size of the output buffer, leading to a buffer overflow.
extended_description: A buffer overflow condition exists when a product attempts to put more data in a buffer than it can hold, or when it attempts to put data in a memory area outside of the boundaries of a buffer. The simplest type of error, and the most common cause of buffer overflows, is the "classic" case in which the product copies the buffer without restricting how much is copied. Other variants exist, but the existence of a classic overflow strongly suggests that the programmer is not considering even the most basic of security protections.
         

Fix information:
repo_url: https://github.com/moonlight-stream/moonlight-common-c
commit_details: repo_url: https://github.com/moonlight-stream/moonlight-common-c
author: Cameron Gutman
author_date: 2023-10-06 17:43:56-05:00
msg: Rewrite extractVersionQuadFromString() to avoid copying the input string
repository: repo_url: https://github.com/moonlight-stream/moonlight-common-c
repo_name: moonlight-stream/moonlight-common-c
description: Core implementation of Nvidia's GameStream protocol
repo_language: C
owner: moonlight-stream
file_changes: filename: Misc.c
change_type: ModificationType.MODIFY
diff: @@ -80,32 +80,19 @@ int gracefullyDisconnectEnetPeer(ENetHost* host, ENetPeer* peer, enet_uint32 lin
 }
 
 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;
+    const char* nextNumber = string;
+    for (int i = 0; i < 4; i++) {
+        // Parse the next component
+        quad[i] = (int)strtol(nextNumber, (char**)&nextNumber, 10);
+
+        // Skip the dot if we still have version components left.
+        //
+        // We continue looping even when we're at the end of the
+        // input string to ensure all subsequent version components
+        // are zeroed.
+        if (*nextNumber != 0) {
+            nextNumber++;
         }
-        
-        // 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;

programming_language: C++
repo_url: https://github.com/moonlight-stream/moonlight-common-c
commit_details: repo_url: https://github.com/moonlight-stream/moonlight-common-c
author: Cameron Gutman
author_date: 2016-10-05 17:23:59-07:00
msg: API update to provide enough information to support GFE 3.0.7
repository: repo_url: https://github.com/moonlight-stream/moonlight-common-c
repo_name: moonlight-stream/moonlight-common-c
description: Core implementation of Nvidia's GameStream protocol
repo_language: C
owner: moonlight-stream
file_changes: filename: Connection.c
change_type: ModificationType.MODIFY
diff: @@ -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);

programming_language: C
filename: ControlStream.c
change_type: ModificationType.MODIFY
diff: @@ -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;
         

programming_language: C++
filename: InputStream.c
change_type: ModificationType.MODIFY
diff: @@ -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;

programming_language: C
filename: Limelight-internal.h
change_type: ModificationType.MODIFY
diff: @@ -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);

programming_language: C
filename: Limelight.h
change_type: ModificationType.MODIFY
diff: @@ -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);

programming_language: C++
filename: Misc.c
change_type: ModificationType.MODIFY
diff: @@ -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));
+}

programming_language: C++
filename: RtspConnection.c
change_type: ModificationType.MODIFY
diff: @@ -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;

programming_language: C
filename: SdpGenerator.c
change_type: ModificationType.MODIFY
diff: @@ -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

programming_language: C++
filename: VideoDepacketizer.c
change_type: ModificationType.MODIFY
diff: @@ -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;
     }

programming_language: C
filename: VideoStream.c
change_type: ModificationType.MODIFY
diff: @@ -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) {

programming_language: C++