Initial commit

This commit is contained in:
Observer of Time 2021-01-29 11:19:21 +02:00
commit 9e4063141b
Signed by: chronobserver
GPG Key ID: 8A2DEA1DBAEBCA9E
6 changed files with 361 additions and 0 deletions

25
LICENSE.txt Normal file
View File

@ -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.

31
Makefile Normal file
View File

@ -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; @./$^

35
README.md Normal file
View File

@ -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.
<br/>You can run the application later with `./rpc`

33
defs.h Normal file
View File

@ -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__

176
rpc.c Normal file
View File

@ -0,0 +1,176 @@
#include <json-c/json.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <time.h>
#include <unistd.h>
#include <uuid/uuid.h>
#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) {}
}

61
utils.h Normal file
View File

@ -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__