commit 9e4063141b8c4905556df809b64b5ab28e523702 Author: ObserverOfTime Date: Fri Jan 29 11:19:21 2021 +0200 Initial commit diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..a0f7ec4 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,25 @@ + GLWT(Good Luck With That) Public License + Copyright (c) Everyone, except Author + +Everyone is permitted to copy, distribute, modify, merge, sell, publish, +sublicense or whatever they want with this software but at their OWN RISK. + + Preamble + +The author has absolutely no clue what the code in this project does. +It might just work or not, there is no third option. + + + GOOD LUCK WITH THAT PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION, AND MODIFICATION + + 0. You just DO WHATEVER YOU WANT TO as long as you NEVER LEAVE A +TRACE TO TRACK THE AUTHOR of the original product to blame for or hold +responsible. + +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +Good luck and Godspeed. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2d0faee --- /dev/null +++ b/Makefile @@ -0,0 +1,31 @@ +CC ?= gcc +CPPFLAGS ?= -D_FORTIFY_SOURCE=2 +CFLAGS ?= -std=gnu11 -Wall -Wextra -O2 + +-include Env.mak + +override LDFLAGS += -ljson-c -luuid + +ifdef DEBUG +CFLAGS += -g +LDFLAGS += -Wl,--strip-unneeded +else +CPPFLAGS += -DNDEBUG +LDFLAGS += -Wl,--strip-all +endif + +__d = $(if $(value $1),-D$1='"$($1)"',$(info [WARN] $1 is undefined)) + +all: rpc run + +rpc: CPPFLAGS += $(call __d,CLIENT_ID) +rpc: CPPFLAGS += $(call __d,DETAILS) +rpc: CPPFLAGS += $(call __d,STATE) +rpc: CPPFLAGS += $(call __d,LARGE_IMAGE) +rpc: CPPFLAGS += $(call __d,LARGE_TEXT) +rpc: CPPFLAGS += $(call __d,SMALL_IMAGE) +rpc: CPPFLAGS += $(call __d,SMALL_TEXT) +rpc: rpc.c; @$(CC) $(CPPFLAGS) $(CFLAGS) $< $(LDFLAGS) -o $@ + +.PHONY: run +run: rpc; @./$^ diff --git a/README.md b/README.md new file mode 100644 index 0000000..4f77e5d --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# discord-custom-rpc + +Custom Discord Rich Presence for Linux written in C. + +Depends on `json-c` & `uuid` and is configured during compilation. + +First, create an [application][] and add some Rich Presence assets. + +[application]: https://discord.com/developers/applications/ + +Then, create an `Env.mak` file to store the configuration. + +```make +# your client ID, required +CLIENT_ID = +# rich presence details, optional +DETAILS = +# rich presence state, optional +STATE = +# large image key, optional +LARGE_IMAGE = +# large image text, optional +LARGE_TEXT = +# small image key, optional +SMALL_IMAGE = +# small image text, optional +SMALL_TEXT = +# set to any value to enable debugging +DEBUG = +``` + +Now, you can compile and run the application with `make`. + +Alternatively, use `make rpc` to only compile it. +
You can run the application later with `./rpc` diff --git a/defs.h b/defs.h new file mode 100644 index 0000000..a5358e4 --- /dev/null +++ b/defs.h @@ -0,0 +1,33 @@ +#ifndef __DEFS__ +#define __DEFS__ + +#ifndef CLIENT_ID +#define CLIENT_ID NULL +#error "CLIENT_ID must be defined" +#endif // !CLIENT_ID + +#ifndef DETAILS +#define DETAILS NULL +#endif // !DETAILS + +#ifndef STATE +#define STATE NULL +#endif // !STATE + +#ifndef LARGE_IMAGE +#define LARGE_IMAGE NULL +#endif // !LARGE_IMAGE + +#ifndef LARGE_TEXT +#define LARGE_TEXT NULL +#endif // !LARGE_TEXT + +#ifndef SMALL_IMAGE +#define SMALL_IMAGE NULL +#endif // !SMALL_IMAGE + +#ifndef SMALL_TEXT +#define SMALL_TEXT NULL +#endif // !SMALL_TEXT + +#endif // !__DEFS__ diff --git a/rpc.c b/rpc.c new file mode 100644 index 0000000..80bd61d --- /dev/null +++ b/rpc.c @@ -0,0 +1,176 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "defs.h" +#include "utils.h" + +typedef enum { OP_AUTHENTICATE, OP_FRAME, OP_CLOSE } rpc_op; + +static int32_t sock = -1; + +void rpc_connect() { + struct sockaddr_un addr; + if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + perror("[ERROR] socket failed"); + exit(1); + } + memset(&addr, 0, sizeof addr); + addr.sun_family = AF_UNIX; + + const char *path = TMP_PATH; + strncpy(addr.sun_path, path, strlen(path)); + strncat(addr.sun_path, "/discord-ipc-0", 15); + printf("[INFO] connecting to %s\n", addr.sun_path); + + if (connect(sock, (struct sockaddr *) &addr, sizeof addr) == -1) { + perror("[ERROR] connect failed"); + exit(1); + } +} + +void rpc_send(rpc_op op, const char *data) { + int32_t len = strlen(data); + uint8_t op_b[4], len_b[4]; + INT_TO_BYTES(op, op_b); + INT_TO_BYTES(len, len_b); + + uint8_t *payload = (uint8_t *) malloc(8L + len); + memcpy(payload, op_b, 4L); + memcpy(payload + 4, len_b, 4L); + memcpy(payload + 8, data, len); + + LOG("[DEBUG] sending "); + LOG_B(op_b); + LOG_B(len_b); + LOG("%s\n", data); + if (send(sock, payload, 8L + len, 0) == -1) { + free(payload); + perror("[ERROR] send failed"); + exit(1); + } + free(payload); +} + +void rpc_recv(uint8_t *op, uint8_t *len, json_object **obj) { + if (recv(sock, op, 4L, 0) == -1) { + perror("[ERROR] recv failed"); + exit(1); + } + LOG("[DEBUG] received "); + LOG_B(op); + + if (recv(sock, len, 4L, 0) == -1) { + fflush(stderr); + perror("[ERROR] recv failed"); + exit(1); + } + uint32_t l = BYTES_TO_INT(len); + LOG_B(len); + + char *response = (char *) malloc(l); + if (recv(sock, response, l, 0) == -1) { + fflush(stderr); + perror("[ERROR] recv failed"); + exit(1); + } + LOG("%s\n", response); + + *obj = JSON_PARSE(response); + free(response); +} + +void rpc_handshake() { + json_object *r_obj, *s_obj = JSON_NEW(); + JSON_ADD(s_obj, v, 1, int); + JSON_ADD(s_obj, client_id, CLIENT_ID, string); + + rpc_send(OP_AUTHENTICATE, JSON_TO_STRING(s_obj)); + JSON_FREE(s_obj); + + uint8_t op[4], len[4]; + rpc_recv(op, len, &r_obj); + + const char *evt = JSON_GET(r_obj, evt, string); + if (!STR_EQ(evt, "READY")) { + const char *msg = JSON_GET(r_obj, message, string); + JSON_FREE(r_obj); + fprintf(stderr, "[ERROR] received evt: %s, message: %s\n", evt, msg); + exit(2); + } + JSON_FREE(r_obj); +} + +void rpc_set_activity() { + uint8_t op[4], len[4], uuid[16]; + + char *nonce = (char *) malloc(37L); + uuid_generate_random(uuid); + uuid_unparse_upper(uuid, nonce); + + json_object *r_obj, *s_obj = JSON_NEW(); + JSON_ADD(s_obj, cmd, "SET_ACTIVITY", string); + JSON_ADD(s_obj, nonce, nonce, string); + + json_object *activity = JSON_NEW(); + JSON_ADD_S(activity, details, DETAILS); + JSON_ADD_S(activity, state, STATE); + + json_object *timestamps = JSON_NEW(); + JSON_ADD(timestamps, start, TIME, int); + JSON_ADD_OBJ(activity, timestamps, timestamps); + + json_object *assets = JSON_NEW(); + JSON_ADD_S(assets, large_image, LARGE_IMAGE); + JSON_ADD_S(assets, large_text, LARGE_TEXT); + JSON_ADD_S(assets, small_image, SMALL_IMAGE); + JSON_ADD_S(assets, small_text, SMALL_TEXT); + JSON_ADD_OBJ(activity, assets, assets); + + json_object *args = JSON_NEW(); + JSON_ADD(args, pid, PID, int); + JSON_ADD_OBJ(args, activity, activity); + JSON_ADD_OBJ(s_obj, args, args); + + rpc_send(OP_FRAME, JSON_TO_STRING(s_obj)); + JSON_FREE(s_obj); + free(nonce); + + rpc_recv(op, len, &r_obj); + const char *evt = JSON_GET(r_obj, evt, string); + if (evt != NULL && STR_EQ(evt, "ERROR")) { + json_object *data = JSON_GET_OBJ(r_obj, data); + const char *msg = JSON_GET(data, message, string); + JSON_FREE(r_obj); + fprintf(stderr, "[ERROR] received message: %s\n", msg); + exit(3); + } + JSON_FREE(r_obj); +} + +static void rpc_disconnect() { + if (sock != -1) { + rpc_send(OP_CLOSE, ""); + close(sock); + } +} + +static void quit(__attribute__((unused)) int32_t sig) { + fflush(stderr); + exit(0); +} + +int main() { + atexit(rpc_disconnect); + signal(SIGINT, quit); + rpc_connect(); + rpc_handshake(); + rpc_set_activity(); + while (1) {} +} diff --git a/utils.h b/utils.h new file mode 100644 index 0000000..5462ec7 --- /dev/null +++ b/utils.h @@ -0,0 +1,61 @@ +#ifndef __UTILS__ +#define __UTILS__ + +#ifdef NDEBUG +#define LOG(msg, ...) +#define LOG_B(bytes) +#else +#define LOG(msg, ...) fprintf(stderr, msg, ##__VA_ARGS__) +#define LOG_B(bytes) \ + LOG("\\x%02x\\x%02x\\x%02x\\x%02x", /**/ \ + (bytes)[0], (bytes)[1], (bytes)[2], (bytes)[3]); +#endif // NDEBUG + +#define TIME ((int32_t) time(NULL)) + +#define PID ((int32_t) getpid()) + +#define TMP_PATH \ + ( \ + getenv("XDG_RUNTIME_DIR") ?: \ + getenv("TMPDIR") ?: \ + getenv("TMP") ?: \ + getenv("TEMP") ?: \ + "/tmp" \ + ) + +#define STR_EQ(s1, s2) (strncmp((s1), (s2), strlen((s2))) == 0) + +#define JSON_GET_OBJ(obj, key) json_object_object_get((obj), #key) + +#define JSON_GET(obj, key, type) json_object_get_##type(JSON_GET_OBJ(obj, key)) + +#define JSON_ADD_OBJ(obj, key, val) json_object_object_add((obj), #key, (val)) + +#define JSON_ADD(obj, key, val, type) \ + JSON_ADD_OBJ(obj, key, json_object_new_##type((val))) + +#define JSON_ADD_S(obj, key, val) \ + if ((val) != NULL) JSON_ADD(obj, key, val, string) + +#define JSON_TO_STRING(obj) \ + json_object_to_json_string_ext((obj), JSON_C_TO_STRING_PLAIN) + +#define JSON_PARSE(str) json_tokener_parse((str)) + +#define JSON_FREE(obj) json_object_put((obj)) + +#define JSON_NEW() json_object_new_object() + +#define INT_TO_BYTES(n, bytes) \ + { \ + (bytes)[0] = (n) & 0xFF; \ + (bytes)[1] = (n) >> 8 & 0xFF; \ + (bytes)[2] = (n) >> 16 & 0xFF; \ + (bytes)[3] = (n) >> 24 & 0xFF; \ + } + +#define BYTES_TO_INT(bytes) \ + ((bytes)[0] | (bytes)[1] << 8 | (bytes)[2] << 16 | (bytes)[3] << 24) + +#endif // !__UTILS__