rpc and shi

This commit is contained in:
kai
2026-10-26 00:20:22 +00:00
parent f7ceb38f6b
commit 43930a66de
13 changed files with 559 additions and 0 deletions
+8
View File
@@ -297,6 +297,14 @@
</Unit>
<Unit filename="../src/crypto.cpp" />
<Unit filename="../src/crypto_tools.h" />
<Unit filename="../src/discordrpc.cpp">
<Option target="default" />
<Option target="debug" />
</Unit>
<Unit filename="../src/discordrpc.h">
<Option target="default" />
<Option target="debug" />
</Unit>
<Unit filename="../src/cube.h">
<Option compile="1" />
<Option weight="0" />
+2
View File
@@ -259,6 +259,8 @@
<Unit filename="../src/soundscheduler.cpp" />
<Unit filename="../src/renderhud.cpp" />
<Unit filename="../src/console.cpp" />
<Unit filename="../src/discordrpc.cpp" />
<Unit filename="../src/discordrpc.h" />
<Unit filename="../src/editing.cpp" />
<Unit filename="../src/bot/ac_bot_ai.cpp" />
<Unit filename="../src/bot/ac_bot.cpp" />
+1
View File
@@ -73,6 +73,7 @@ CLIENT_OBJS= \
clients2c.o \
command.o \
console.o \
discordrpc.o \
docs.o \
editing.o \
entities.o \
+2
View File
@@ -2,6 +2,7 @@
#include "cube.h"
#include "bot/bot.h"
#include "discordrpc.h"
VAR(connected, 1, 0, 0);
@@ -293,6 +294,7 @@ COMMAND(current_version, "s");
void cleanupclient()
{
discordrpccleanup();
abortconnect();
disconnect(1);
if(clienthost)
+35
View File
@@ -2,6 +2,7 @@
#include "cube.h"
#include "bot/bot.h"
#include "discordrpc.h"
int nextmode = 0; // nextmode becomes gamemode after next map load
VAR(gamemode, 1, 0, 0);
@@ -1898,6 +1899,40 @@ void spectatecn()
}
COMMAND(spectatecn, "");
// Discord Rich Presence: shows what we're doing on the user's discord profile
VARP(discordrpc, 0, 1, 1);
void updategamepresence(int millis)
{
if(!discordrpc)
{
discordrpctoggle(false);
return;
}
discordrpctoggle(true);
static int lastbuild = 0;
if(!lastbuild || millis - lastbuild >= 1000) // the details don't change every frame
{
lastbuild = millis;
string details, state;
serverinfo *si = multiplayer(NULL) ? getconnectedserverinfo() : NULL;
const char *map = clientmap[0] ? clientmap : "unknown map";
if(multiplayer(NULL) && si && si->name[0]) copystring(details, si->name);
else if(multiplayer(NULL)) copystring(details, "Multiplayer game");
else copystring(details, "Singleplayer");
if(si && si->numplayers > 0) formatstring(state)("%s on %s (%d/%d players)", modestr(gamemode, true), map, si->numplayers, si->maxclients);
else formatstring(state)("%s on %s", modestr(gamemode, true), map);
discordrpcpresence(details, state);
}
discordrpcpoll(millis);
}
void serverextension(char *ext, char *args)
{
if(!ext || !ext[0]) return;
+471
View File
@@ -0,0 +1,471 @@
// discordrpc.cpp: minimal Discord Rich Presence client.
//
// Talks the Discord desktop client's local IPC protocol directly over its unix
// socket / windows named pipe (the same thing the official SDK does), so there's
// no library to link and no helper process to ship. Everything is non-blocking
// and non-fatal: if Discord isn't running we simply retry now and then.
#include "discordrpc.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <time.h>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#else
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#endif
static const char *RPC_CLIENT_ID = "1564063539117826158";
// IPC opcodes and frame layout: uint32 opcode, uint32 length, then the JSON payload
enum { RPC_OP_HANDSHAKE = 0, RPC_OP_FRAME, RPC_OP_CLOSE, RPC_OP_PING, RPC_OP_PONG };
enum
{
RPC_RETRY_INTERVAL = 5000, // how often to look for Discord while it isn't there
RPC_SEND_INTERVAL = 5000, // Discord allows 5 activity updates per 20 seconds
RPC_MAX_PAYLOAD = 4096,
RPC_MAX_FIELD = 128, // Discord's limit for details/state
RPC_INBUF_SIZE = 8192
};
#ifdef _WIN32
typedef HANDLE rpcsocket;
#define RPC_NOSOCKET (INVALID_HANDLE_VALUE)
#else
typedef int rpcsocket;
#define RPC_NOSOCKET (-1)
#endif
static rpcsocket rpcsock = RPC_NOSOCKET;
static bool rpcwanted = false, rpcready = false;
static int rpclasttry = -1, rpclastsend = -1;
static int rpcinlen = 0, rpcnonce = 0;
static time_t rpcstart = 0;
static char rpcinbuf[RPC_INBUF_SIZE];
static char rpcdetails[RPC_MAX_FIELD], rpcstate[RPC_MAX_FIELD];
static char rpcsentdetails[RPC_MAX_FIELD], rpcsentstate[RPC_MAX_FIELD];
static int rpcpid()
{
#ifdef _WIN32
return (int)GetCurrentProcessId();
#else
return (int)getpid();
#endif
}
#ifndef MSG_NOSIGNAL
#define MSG_NOSIGNAL 0
#endif
static void rpcdisconnect()
{
if(rpcsock == RPC_NOSOCKET) return;
#ifdef _WIN32
CloseHandle(rpcsock);
#else
close(rpcsock);
#endif
rpcsock = RPC_NOSOCKET;
rpcready = false;
rpcinlen = 0;
// forget what Discord has, so the activity is pushed again after a reconnect
rpcsentdetails[0] = rpcsentstate[0] = 0;
}
static bool rpcwrite(int op, const char *payload, int len);
// tells Discord to drop our activity instead of leaving the last one stuck there
static void rpcclearactivity()
{
static const char *clearmessage = "{\"cmd\":\"SET_ACTIVITY\",\"args\":{\"pid\":0,\"activity\":null}}";
if(rpcready) rpcwrite(RPC_OP_FRAME, clearmessage, (int)strlen(clearmessage));
}
static bool rpcwrite(int op, const char *payload, int len)
{
if(rpcsock == RPC_NOSOCKET) return false;
if(len < 0 || len > RPC_MAX_PAYLOAD) return false;
char buf[8 + RPC_MAX_PAYLOAD];
buf[0] = (char)(op&0xff);
buf[1] = (char)((op>>8)&0xff);
buf[2] = buf[3] = 0;
buf[4] = (char)(len&0xff);
buf[5] = (char)((len>>8)&0xff);
buf[6] = (char)((len>>16)&0xff);
buf[7] = (char)((len>>24)&0xff);
memcpy(buf + 8, payload, len);
int total = 8 + len, sent = 0;
while(sent < total)
{
#ifdef _WIN32
DWORD written = 0;
if(!WriteFile(rpcsock, buf + sent, (DWORD)(total - sent), &written, NULL) || !written)
{
rpcdisconnect();
return false;
}
sent += (int)written;
#else
int n = (int)send(rpcsock, buf + sent, total - sent, MSG_NOSIGNAL);
if(n < 0)
{
if(errno == EINTR) continue;
// full pipe buffer: drop this update instead of stalling the game
if(errno == EAGAIN || errno == EWOULDBLOCK) return false;
rpcdisconnect();
return false;
}
if(!n)
{
rpcdisconnect();
return false;
}
sent += n;
#endif
}
return true;
}
// reads everything Discord has sent us so far, false if the pipe died
static bool rpcreceive()
{
for(;;)
{
if(rpcinlen >= RPC_INBUF_SIZE)
{
rpcdisconnect();
return false;
}
#ifdef _WIN32
DWORD available = 0;
if(!PeekNamedPipe(rpcsock, NULL, 0, NULL, &available, NULL))
{
rpcdisconnect();
return false;
}
if(!available) return true;
DWORD got = 0;
if(!ReadFile(rpcsock, rpcinbuf + rpcinlen, (DWORD)(RPC_INBUF_SIZE - rpcinlen), &got, NULL))
{
rpcdisconnect();
return false;
}
if(!got)
{
rpcdisconnect();
return false;
}
rpcinlen += (int)got;
#else
int got = (int)recv(rpcsock, rpcinbuf + rpcinlen, RPC_INBUF_SIZE - rpcinlen, 0);
if(got < 0)
{
if(errno == EINTR) continue;
if(errno == EAGAIN || errno == EWOULDBLOCK) return true;
rpcdisconnect();
return false;
}
if(!got)
{
// Discord closed the pipe (client quit or restarted)
rpcdisconnect();
return false;
}
rpcinlen += got;
#endif
}
}
static void rpchandlemessage(int op, const char *payload, int len)
{
if(op == RPC_OP_PING) { rpcwrite(RPC_OP_PONG, payload, len); return; }
if(op == RPC_OP_CLOSE) { rpcdisconnect(); return; }
if(op != RPC_OP_FRAME) return;
char msg[RPC_MAX_PAYLOAD + 1];
if(len > RPC_MAX_PAYLOAD) len = RPC_MAX_PAYLOAD;
memcpy(msg, payload, len);
msg[len] = 0;
if(!rpcready)
{
if(strstr(msg, "\"evt\":\"READY\"") || strstr(msg, "\"evt\": \"READY\""))
{
rpcready = true;
rpclastsend = -1; // push the current activity straight away
}
// a bad/expired client id or a rejected handshake: stop retrying this pipe
else if(strstr(msg, "\"evt\":\"ERROR\"") || strstr(msg, "\"evt\": \"ERROR\"")) rpcdisconnect();
}
}
static void rpcprocess()
{
int pos = 0;
while(rpcinlen - pos >= 8)
{
const unsigned char *h = (const unsigned char *)rpcinbuf + pos;
int op = h[0] | (h[1]<<8) | (h[2]<<16) | (h[3]<<24);
int len = h[4] | (h[5]<<8) | (h[6]<<16) | (h[7]<<24);
if(len < 0 || len > RPC_INBUF_SIZE - 8)
{
rpcdisconnect();
return;
}
if(rpcinlen - pos - 8 < len) break; // partial frame, wait for more
rpchandlemessage(op, rpcinbuf + pos + 8, len);
if(rpcsock == RPC_NOSOCKET) return;
pos += 8 + len;
}
if(pos)
{
memmove(rpcinbuf, rpcinbuf + pos, rpcinlen - pos);
rpcinlen -= pos;
}
}
static void rpcappend(char *buf, int max, int &len, const char *s)
{
int l = (int)strlen(s);
if(len + l > max - 1) l = max - 1 - len;
if(l <= 0) return;
memcpy(buf + len, s, l);
len += l;
buf[len] = 0;
}
static void rpcappendstring(char *buf, int max, int &len, const char *s)
{
rpcappend(buf, max, len, "\"");
for(const char *p = s; *p; p++)
{
unsigned char c = (unsigned char)*p;
if(c == '"' || c == '\\')
{
char esc[3] = { '\\', (char)c, 0 };
rpcappend(buf, max, len, esc);
}
else if(c < 0x20)
{
char esc[8];
snprintf(esc, sizeof(esc), "\\u%04x", c);
rpcappend(buf, max, len, esc);
}
else
{
char chr[2] = { (char)c, 0 };
rpcappend(buf, max, len, chr);
}
}
rpcappend(buf, max, len, "\"");
}
static void rpcsendactivity()
{
if(!rpcready) return;
if(!rpcstart) rpcstart = time(NULL);
char activity[768];
int len = 0;
activity[0] = 0;
if(rpcdetails[0])
{
rpcappend(activity, sizeof(activity), len, "\"details\":");
rpcappendstring(activity, sizeof(activity), len, rpcdetails);
}
if(rpcstate[0])
{
if(len) rpcappend(activity, sizeof(activity), len, ",");
rpcappend(activity, sizeof(activity), len, "\"state\":");
rpcappendstring(activity, sizeof(activity), len, rpcstate);
}
if(len) rpcappend(activity, sizeof(activity), len, ",");
char timestamps[64];
snprintf(timestamps, sizeof(timestamps), "\"timestamps\":{\"start\":%ld}", (long)rpcstart);
rpcappend(activity, sizeof(activity), len, timestamps);
char nonce[64];
rpcnonce++;
snprintf(nonce, sizeof(nonce), "%lx-%x-%x-%lx",
(unsigned long)time(NULL), (unsigned)(rpcpid()&0xffff),
(unsigned)(rpcnonce&0xffff),
(unsigned long)((rpcnonce * 2654435761UL)&0xffffffffUL));
char msg[1024];
snprintf(msg, sizeof(msg),
"{\"cmd\":\"SET_ACTIVITY\",\"nonce\":\"%s\",\"args\":{\"pid\":%d,\"activity\":{%s}}}",
nonce, rpcpid(), activity);
if(rpcwrite(RPC_OP_FRAME, msg, (int)strlen(msg)))
{
memcpy(rpcsentdetails, rpcdetails, RPC_MAX_FIELD);
memcpy(rpcsentstate, rpcstate, RPC_MAX_FIELD);
}
}
#ifndef _WIN32
static bool rpcconnectsocket(const char *path)
{
struct sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
if(strlen(path) >= sizeof(addr.sun_path)) return false;
memcpy(addr.sun_path, path, strlen(path) + 1);
int s = socket(AF_UNIX, SOCK_STREAM, 0);
if(s < 0) return false;
if(connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0)
{
close(s);
return false;
}
// never let a wedged Discord stall the game
fcntl(s, F_SETFL, fcntl(s, F_GETFL, 0) | O_NONBLOCK);
#ifdef SO_NOSIGPIPE
int on = 1;
setsockopt(s, SOL_SOCKET, SO_NOSIGPIPE, &on, sizeof(on));
#endif
rpcsock = s;
return true;
}
static bool rpcconnectdir(const char *dir)
{
if(!dir || !*dir) return false;
char path[256];
for(int i = 0; i < 10; i++)
{
snprintf(path, sizeof(path), "%s/discord-ipc-%d", dir, i);
if(rpcconnectsocket(path)) return true;
}
return false;
}
static void rpcconnect()
{
rpcdisconnect();
// the usual places for the discord-ipc-N socket: plain, flatpak and snap installs
const char *runtime = getenv("XDG_RUNTIME_DIR");
char sub[256];
if(runtime && *runtime)
{
if(rpcconnectdir(runtime)) return;
snprintf(sub, sizeof(sub), "%s/app/com.discordapp.Discord", runtime);
if(rpcconnectdir(sub)) return;
snprintf(sub, sizeof(sub), "%s/app/com.discordapp.DiscordCanary", runtime);
if(rpcconnectdir(sub)) return;
snprintf(sub, sizeof(sub), "%s/app/com.discordapp.DiscordPTB", runtime);
if(rpcconnectdir(sub)) return;
snprintf(sub, sizeof(sub), "%s/.flatpak/com.discordapp.Discord/xdg-run", runtime);
if(rpcconnectdir(sub)) return;
snprintf(sub, sizeof(sub), "%s/snap.discord", runtime);
if(rpcconnectdir(sub)) return;
}
const char *tmp = getenv("TMPDIR");
if(rpcconnectdir(tmp)) return;
if(rpcconnectdir("/tmp")) return;
if(rpcconnectdir("/tmp/snap.discord")) return;
return;
}
#else
static void rpcconnect()
{
rpcdisconnect();
char path[64];
for(int i = 0; i < 10; i++)
{
snprintf(path, sizeof(path), "\\\\?\\pipe\\discord-ipc-%d", i);
HANDLE h = CreateFileA(path, GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
if(h != INVALID_HANDLE_VALUE)
{
rpcsock = h;
return;
}
}
}
#endif
static void rpchandshake()
{
rpcready = false;
rpcinlen = 0;
char msg[128];
snprintf(msg, sizeof(msg), "{\"v\":1,\"client_id\":\"%s\"}", RPC_CLIENT_ID);
if(!rpcwrite(RPC_OP_HANDSHAKE, msg, (int)strlen(msg))) rpcdisconnect();
}
void discordrpcpresence(const char *details, const char *state)
{
const char *d = details ? details : "", *s = state ? state : "";
if(!strcmp(d, rpcdetails) && !strcmp(s, rpcstate)) return;
snprintf(rpcdetails, RPC_MAX_FIELD, "%s", d);
snprintf(rpcstate, RPC_MAX_FIELD, "%s", s);
}
void discordrpctoggle(bool enabled)
{
if(enabled == rpcwanted) return;
rpcwanted = enabled;
if(!enabled)
{
rpcclearactivity();
rpcdisconnect();
rpcstart = 0;
rpclastsend = rpclasttry = -1;
}
else rpclasttry = -1; // try to connect on the next poll
}
void discordrpcpoll(int millis)
{
if(!rpcwanted)
{
rpcdisconnect();
return;
}
if(rpcsock == RPC_NOSOCKET)
{
if(rpclasttry >= 0 && millis - rpclasttry >= 0 && millis - rpclasttry < RPC_RETRY_INTERVAL) return;
rpclasttry = millis;
rpcconnect();
if(rpcsock == RPC_NOSOCKET) return;
rpchandshake();
if(rpcsock == RPC_NOSOCKET) return;
}
if(!rpcreceive()) return;
rpcprocess();
if(!rpcready || rpcsock == RPC_NOSOCKET) return;
if(rpclastsend >= 0 && millis - rpclastsend >= 0 && millis - rpclastsend < RPC_SEND_INTERVAL) return;
if(!strcmp(rpcdetails, rpcsentdetails) && !strcmp(rpcstate, rpcsentstate)) return;
rpclastsend = millis;
rpcsendactivity();
}
void discordrpccleanup()
{
rpcclearactivity();
rpcdisconnect();
rpcwanted = false;
}
+20
View File
@@ -0,0 +1,20 @@
// discordrpc.h: minimal Discord Rich Presence client (no external dependencies)
#ifndef DISCORDRPC_H
#define DISCORDRPC_H
// sets the activity shown on Discord, NULL or "" clears it. Cheap to call every
// frame: nothing is sent to Discord unless the text actually changed.
extern void discordrpcpresence(const char *details, const char *state);
// enables/disables the connection (disabling also clears the activity)
extern void discordrpctoggle(bool enabled);
// drive from the main loop: (re)connects, answers pings, pushes changed activity.
// millis is a monotonic millisecond clock (the game's totalmillis).
extern void discordrpcpoll(int millis);
// closes the connection and clears the activity (call on exit)
extern void discordrpccleanup();
#endif
+2
View File
@@ -1552,6 +1552,8 @@ int main(int argc, char **argv)
if(needsautoscreenshot) showscores(true);
updategamepresence(totalmillis);
serverslice(0);
if(elapsed) fps = (1000.0f/elapsed+fps*10)/11; // avoid DIV-by-0
+1
View File
@@ -521,6 +521,7 @@ extern void resetwater();
extern void abortconnect();
extern void disconnect(int onlyclean = 0, int async = 0);
extern void cleanupclient();
extern void updategamepresence(int millis);
extern void toserver(char *text);
extern void addmsg(int type, const char *fmt = NULL, ...);
extern bool multiplayer(const char *op = "");
+7
View File
@@ -644,6 +644,13 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Standalone|Win32'">true</ExcludedFromBuild>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Standalone|Win32'">Use</PrecompiledHeader>
</ClCompile>
<ClCompile Include="..\src\discordrpc.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Standalone Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Standalone|Win32'">true</ExcludedFromBuild>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Profile|Win32'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="..\src\crypto.cpp" />
<ClCompile Include="..\src\docs.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Standalone Debug|Win32'">true</ExcludedFromBuild>
+3
View File
@@ -314,6 +314,9 @@
<ClCompile Include="..\src\console.cpp">
<Filter>support</Filter>
</ClCompile>
<ClCompile Include="..\src\discordrpc.cpp">
<Filter>support</Filter>
</ClCompile>
<ClCompile Include="..\src\crypto.cpp">
<Filter>support</Filter>
</ClCompile>
@@ -60,6 +60,7 @@
B914CEAC11CAC42F009BA307 /* packetqueue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B914CE5D11CAC42F009BA307 /* packetqueue.cpp */; };
B914CEAD11CAC42F009BA307 /* pch.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B914CE5E11CAC42F009BA307 /* pch.cpp */; };
B914CEAE11CAC42F009BA307 /* physics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B914CE5F11CAC42F009BA307 /* physics.cpp */; };
B9D15C0211CAC42F009BA307 /* discordrpc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B9D15C0111CAC42F009BA307 /* discordrpc.cpp */; };
B914CEB011CAC42F009BA307 /* protocol.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B914CE6111CAC42F009BA307 /* protocol.cpp */; };
B914CEB311CAC42F009BA307 /* rendercubes.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B914CE6411CAC42F009BA307 /* rendercubes.cpp */; };
B914CEB411CAC42F009BA307 /* rendergl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B914CE6511CAC42F009BA307 /* rendergl.cpp */; };
@@ -193,6 +194,8 @@
B914CE5D11CAC42F009BA307 /* packetqueue.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = packetqueue.cpp; sourceTree = "<group>"; };
B914CE5E11CAC42F009BA307 /* pch.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = pch.cpp; sourceTree = "<group>"; };
B914CE5F11CAC42F009BA307 /* physics.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = physics.cpp; sourceTree = "<group>"; };
B9D15C0111CAC42F009BA307 /* discordrpc.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = discordrpc.cpp; sourceTree = "<group>"; };
B9D15C0311CAC42F009BA307 /* discordrpc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = discordrpc.h; sourceTree = "<group>"; };
B914CE6011CAC42F009BA307 /* platform.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = platform.h; sourceTree = "<group>"; };
B914CE6111CAC42F009BA307 /* protocol.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = protocol.cpp; sourceTree = "<group>"; };
B914CE6211CAC42F009BA307 /* protocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = protocol.h; sourceTree = "<group>"; };
@@ -418,6 +421,8 @@
B914CE5D11CAC42F009BA307 /* packetqueue.cpp */,
B914CE5E11CAC42F009BA307 /* pch.cpp */,
B914CE5F11CAC42F009BA307 /* physics.cpp */,
B9D15C0111CAC42F009BA307 /* discordrpc.cpp */,
B9D15C0311CAC42F009BA307 /* discordrpc.h */,
B914CE6011CAC42F009BA307 /* platform.h */,
B914CE6111CAC42F009BA307 /* protocol.cpp */,
B914CE6211CAC42F009BA307 /* protocol.h */,
@@ -640,6 +645,7 @@
B914CEAC11CAC42F009BA307 /* packetqueue.cpp in Sources */,
B914CEAD11CAC42F009BA307 /* pch.cpp in Sources */,
B914CEAE11CAC42F009BA307 /* physics.cpp in Sources */,
B9D15C0211CAC42F009BA307 /* discordrpc.cpp in Sources */,
B914CEB011CAC42F009BA307 /* protocol.cpp in Sources */,
B914CEB311CAC42F009BA307 /* rendercubes.cpp in Sources */,
B914CEB411CAC42F009BA307 /* rendergl.cpp in Sources */,