diff --git a/config/menus_settings.cfg b/config/menus_settings.cfg
index 00fb3f00..4b2be42a 100644
--- a/config/menus_settings.cfg
+++ b/config/menus_settings.cfg
@@ -527,6 +527,7 @@ menuitem "Configure auto media download \i\1" [ showmenu
menuitemradio "Show gamemode names " 0 1 "$modeacronyms" [ verbal abbreviated ] [ modeacronyms $arg1 ]
menuitemcheckbox "Show main menu when starting AC: " "$showmenuonstartup" [ showmenuonstartup = $arg1 ]
menuitemcheckbox "Auto-screenshot: " "$autoscreenshot" [ autoscreenshot $arg1 ]
+menuitemcheckbox "Discord Rich Presence: " "$discordrpc" [ discordrpc $arg1 ]
menuitemcheckbox "Allow HUD echos: " "$allowhudechos" [ allowhudechos $arg1 ]
menuitemslider "Servers to ping at once: " 0 50 "$maxservpings" 5 [ maxservpings $arg1 ]
menuitemslider "Milliseconds between pinging servers: " 1000 60000 "$servpingrate" 1000 [ servpingrate $arg1 ]
diff --git a/source/codeblocks/AssaultCube.cbp b/source/codeblocks/AssaultCube.cbp
index b13a7836..09140c22 100755
--- a/source/codeblocks/AssaultCube.cbp
+++ b/source/codeblocks/AssaultCube.cbp
@@ -297,6 +297,14 @@
+
+
+
+
+
+
+
+
diff --git a/source/codeblocks/AssaultCube_Linux.cbp b/source/codeblocks/AssaultCube_Linux.cbp
index ae04f309..bec93509 100644
--- a/source/codeblocks/AssaultCube_Linux.cbp
+++ b/source/codeblocks/AssaultCube_Linux.cbp
@@ -259,6 +259,8 @@
+
+
diff --git a/source/src/Makefile b/source/src/Makefile
index bf061958..bb558a69 100644
--- a/source/src/Makefile
+++ b/source/src/Makefile
@@ -73,6 +73,7 @@ CLIENT_OBJS= \
clients2c.o \
command.o \
console.o \
+ discordrpc.o \
docs.o \
editing.o \
entities.o \
diff --git a/source/src/client.cpp b/source/src/client.cpp
index 04501116..d4711a0a 100644
--- a/source/src/client.cpp
+++ b/source/src/client.cpp
@@ -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)
diff --git a/source/src/clientgame.cpp b/source/src/clientgame.cpp
index edc81f82..7307e972 100644
--- a/source/src/clientgame.cpp
+++ b/source/src/clientgame.cpp
@@ -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;
diff --git a/source/src/discordrpc.cpp b/source/src/discordrpc.cpp
new file mode 100644
index 00000000..2a1c6ef1
--- /dev/null
+++ b/source/src/discordrpc.cpp
@@ -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
+#include
+#include
+#include
+#include
+
+#ifdef _WIN32
+ #define WIN32_LEAN_AND_MEAN
+ #include
+#else
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+#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;
+}
diff --git a/source/src/discordrpc.h b/source/src/discordrpc.h
new file mode 100644
index 00000000..213969d0
--- /dev/null
+++ b/source/src/discordrpc.h
@@ -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
diff --git a/source/src/main.cpp b/source/src/main.cpp
index 540d892a..46e03e79 100644
--- a/source/src/main.cpp
+++ b/source/src/main.cpp
@@ -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
diff --git a/source/src/protos.h b/source/src/protos.h
index 2da83608..f00682ac 100644
--- a/source/src/protos.h
+++ b/source/src/protos.h
@@ -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 = "");
diff --git a/source/vcpp/assaultcube.vcxproj b/source/vcpp/assaultcube.vcxproj
index da1bf793..0bdca39a 100644
--- a/source/vcpp/assaultcube.vcxproj
+++ b/source/vcpp/assaultcube.vcxproj
@@ -644,6 +644,13 @@
true
Use
+
+ true
+ true
+ NotUsing
+ NotUsing
+ NotUsing
+
true
diff --git a/source/vcpp/assaultcube.vcxproj.filters b/source/vcpp/assaultcube.vcxproj.filters
index 4af0af45..ab8f8bb6 100644
--- a/source/vcpp/assaultcube.vcxproj.filters
+++ b/source/vcpp/assaultcube.vcxproj.filters
@@ -314,6 +314,9 @@
support
+
+ support
+
support
diff --git a/source/xcode/assaultcube.xcodeproj/project.pbxproj b/source/xcode/assaultcube.xcodeproj/project.pbxproj
index a1b47e07..b50c3909 100755
--- a/source/xcode/assaultcube.xcodeproj/project.pbxproj
+++ b/source/xcode/assaultcube.xcodeproj/project.pbxproj
@@ -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 = ""; };
B914CE5E11CAC42F009BA307 /* pch.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = pch.cpp; sourceTree = ""; };
B914CE5F11CAC42F009BA307 /* physics.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = physics.cpp; sourceTree = ""; };
+ B9D15C0111CAC42F009BA307 /* discordrpc.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = discordrpc.cpp; sourceTree = ""; };
+ B9D15C0311CAC42F009BA307 /* discordrpc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = discordrpc.h; sourceTree = ""; };
B914CE6011CAC42F009BA307 /* platform.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = platform.h; sourceTree = ""; };
B914CE6111CAC42F009BA307 /* protocol.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = protocol.cpp; sourceTree = ""; };
B914CE6211CAC42F009BA307 /* protocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = protocol.h; sourceTree = ""; };
@@ -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 */,