From 543e4bca16dfd1afdc1274459a4805f4c0d1b9c1 Mon Sep 17 00:00:00 2001 From: twinaphex Date: Sat, 6 Jun 2020 20:50:25 +0200 Subject: [PATCH] Merge Discord code into retroarch.c --- Makefile.common | 3 +- cheevos/cheevos.c | 3 +- griffin/griffin.c | 4 - network/discord.c | 571 ---------------------------------------------- network/discord.h | 8 +- retroarch.c | 553 ++++++++++++++++++++++++++++++++++++++++++-- 6 files changed, 546 insertions(+), 596 deletions(-) delete mode 100644 network/discord.c diff --git a/Makefile.common b/Makefile.common index 612759c295..5b8146a94c 100644 --- a/Makefile.common +++ b/Makefile.common @@ -1926,8 +1926,7 @@ ifeq ($(HAVE_NETWORKING), 1) OBJ += deps/discord-rpc/src/discord_rpc.o \ deps/discord-rpc/src/rpc_connection.o \ - deps/discord-rpc/src/serialization.o \ - network/discord.o + deps/discord-rpc/src/serialization.o ifneq ($(findstring Win32,$(OS)),) OBJ += deps/discord-rpc/src/discord_register_win.o \ diff --git a/cheevos/cheevos.c b/cheevos/cheevos.c index e876720e68..4485109892 100644 --- a/cheevos/cheevos.c +++ b/cheevos/cheevos.c @@ -1078,7 +1078,8 @@ static retro_time_t rcheevos_async_send_rich_presence(rcheevos_async_io_request* #ifdef HAVE_DISCORD if (rcheevos_locals.richpresence.evaluation[0]) { - if (settings->bools.discord_enable) + if (settings->bools.discord_enable + && discord_is_ready()) discord_update(DISCORD_PRESENCE_RETROACHIEVEMENTS, false); } #endif diff --git a/griffin/griffin.c b/griffin/griffin.c index 1e78fa2715..b7bf376f73 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -1496,16 +1496,12 @@ XML HTTP SERVER ============================================================ */ #if defined(HAVE_DISCORD) -#include "../network/discord.c" - #if defined(_WIN32) #include "../deps/discord-rpc/src/discord_register_win.c" #endif - #if defined(__linux__) #include "../deps/discord-rpc/src/discord_register_linux.c" #endif - #endif /*============================================================ diff --git a/network/discord.c b/network/discord.c deleted file mode 100644 index 75be94405d..0000000000 --- a/network/discord.c +++ /dev/null @@ -1,571 +0,0 @@ -/* RetroArch - A frontend for libretro. - * Copyright (C) 2018-2019 - Andrés Suárez - * - * RetroArch is free software: you can redistribute it and/or modify it under the terms - * of the GNU General Public License as published by the Free Software Found- - * ation, either version 3 of the License, or (at your option) any later version. - * - * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with RetroArch. - * If not, see . - */ - -#include - -#include -#include -#include - -#include -#include -#include -#include - -#include - -#include "discord.h" - -#include "../retroarch.h" -#include "../core.h" -#include "../core_info.h" -#include "../paths.h" -#include "../verbosity.h" - -#include "../msg_hash.h" -#include "../tasks/task_file_transfer.h" - -#ifdef HAVE_NETWORKING -#include "netplay/netplay.h" -#include "netplay/netplay_discovery.h" -#include "../tasks/tasks_internal.h" -#endif - -#ifdef HAVE_CHEEVOS -#include "../cheevos/cheevos.h" -#endif - -#ifdef HAVE_MENU -#include "../menu/menu_cbs.h" -#endif - -#include "net_http_special.h" -#include "../tasks/tasks_internal.h" -#include "../file_path_special.h" - -#define CDN_URL "https://cdn.discordapp.com/avatars" - -/* The discord API specifies these variables: -- userId --------- char[24] - the userId of the player asking to join -- username ------- char[344] - the username of the player asking to join -- discriminator -- char[8] - the discriminator of the player asking to join -- spectateSecret - char[128] - secret used for spectatin matches -- joinSecret - char[128] - secret used to join matches -- partyId - char[128] - the party you would be joining -*/ - -struct discord_state -{ - bool ready; - bool avatar_ready; - bool connecting; - - unsigned status; - - int64_t start_time; - int64_t pause_time; - int64_t elapsed_time; - - char user_name[344]; - char self_party_id[128]; - char peer_party_id[128]; - char user_avatar[PATH_MAX_LENGTH]; - - DiscordRichPresence presence; -}; - -typedef struct discord_state discord_state_t; - -/* TODO/FIXME - static public global variables */ -static discord_state_t discord_st; - -/* Forward declarations */ -#if defined(__cplusplus) && !defined(CXX_BUILD) -extern "C" { -#endif - -void Discord_Register(const char *a, const char *b); - -#if defined(__cplusplus) && !defined(CXX_BUILD) -} -#endif - -static discord_state_t *discord_get_ptr(void) -{ - return &discord_st; -} - -bool discord_is_ready(void) -{ - discord_state_t *discord_st = discord_get_ptr(); - return discord_st->ready; -} - -char* discord_get_own_username(void) -{ - discord_state_t *discord_st = discord_get_ptr(); - - if (discord_st->ready) - return discord_st->user_name; - return NULL; -} - -char *discord_get_own_avatar(void) -{ - discord_state_t *discord_st = discord_get_ptr(); - if (discord_st->ready) - return discord_st->user_avatar; - return NULL; -} - -bool discord_avatar_is_ready(void) -{ - return false; -} - -void discord_avatar_set_ready(bool ready) -{ - discord_state_t *discord_st = discord_get_ptr(); - discord_st->avatar_ready = ready; -} - -#ifdef HAVE_MENU -static bool discord_download_avatar( - const char* user_id, const char* avatar_id) -{ - static char url[PATH_MAX_LENGTH]; - static char url_encoded[PATH_MAX_LENGTH]; - static char full_path[PATH_MAX_LENGTH]; - static char buf[PATH_MAX_LENGTH]; - file_transfer_t *transf = NULL; - discord_state_t *discord_st = discord_get_ptr(); - - RARCH_LOG("[DISCORD] user avatar id: %s\n", user_id); - - fill_pathname_application_special(buf, - sizeof(buf), - APPLICATION_SPECIAL_DIRECTORY_THUMBNAILS_DISCORD_AVATARS); - fill_pathname_join(full_path, buf, avatar_id, sizeof(full_path)); - strlcpy(discord_st->user_avatar, - avatar_id, sizeof(discord_st->user_avatar)); - - if (path_is_valid(full_path)) - return true; - - if (string_is_empty(avatar_id)) - return false; - - snprintf(url, sizeof(url), "%s/%s/%s.png", CDN_URL, user_id, avatar_id); - net_http_urlencode_full(url_encoded, url, sizeof(url_encoded)); - snprintf(buf, sizeof(buf), "%s.png", avatar_id); - - transf = (file_transfer_t*)calloc(1, sizeof(*transf)); - transf->enum_idx = MENU_ENUM_LABEL_CB_DISCORD_AVATAR; - strlcpy(transf->path, buf, sizeof(transf->path)); - - RARCH_LOG("[DISCORD] downloading avatar from: %s\n", url_encoded); - task_push_http_transfer_file(url_encoded, true, NULL, cb_generic_download, transf); - - return false; -} -#endif - -static void handle_discord_ready(const DiscordUser* connectedUser) -{ - discord_state_t *discord_st = discord_get_ptr(); - - strlcpy(discord_st->user_name, - connectedUser->username, sizeof(discord_st->user_name)); - - RARCH_LOG("[DISCORD] connected to user: %s#%s\n", - connectedUser->username, - connectedUser->discriminator); - -#ifdef HAVE_MENU - discord_download_avatar(connectedUser->userId, connectedUser->avatar); -#endif -} - -static void handle_discord_disconnected(int errcode, const char* message) -{ - RARCH_LOG("[DISCORD] disconnected (%d: %s)\n", errcode, message); -} - -static void handle_discord_error(int errcode, const char* message) -{ - RARCH_LOG("[DISCORD] error (%d: %s)\n", errcode, message); -} - -static void handle_discord_join_cb(retro_task_t *task, - void *task_data, void *user_data, const char *err) -{ - char join_hostname[PATH_MAX_LENGTH]; - struct netplay_room *room = NULL; - http_transfer_data_t *data = (http_transfer_data_t*)task_data; - discord_state_t *discord_st = discord_get_ptr(); - - if (!data || err) - goto finish; - - data->data = (char*)realloc(data->data, data->len + 1); - data->data[data->len] = '\0'; - - netplay_rooms_parse(data->data); - room = netplay_room_get(0); - - if (room) - { - bool host_method_is_mitm = room->host_method == NETPLAY_HOST_METHOD_MITM; - const char *srv_address = host_method_is_mitm ? room->mitm_address : room->address; - unsigned srv_port = host_method_is_mitm ? room->mitm_port : room->port; - - if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_DATA_INITED, NULL)) - deinit_netplay(); - netplay_driver_ctl(RARCH_NETPLAY_CTL_ENABLE_CLIENT, NULL); - - snprintf(join_hostname, sizeof(join_hostname), "%s|%d", - srv_address, srv_port); - - RARCH_LOG("[DISCORD] joining lobby at: %s\n", join_hostname); - task_push_netplay_crc_scan(room->gamecrc, - room->gamename, join_hostname, room->corename, room->subsystem_name); - discord_st->connecting = true; - discord_update(DISCORD_PRESENCE_NETPLAY_CLIENT, false); - } - -finish: - - if (err) - RARCH_ERR("%s: %s\n", msg_hash_to_str(MSG_DOWNLOAD_FAILED), err); - - if (data) - { - if (data->data) - free(data->data); - free(data); - } - - if (user_data) - free(user_data); -} - -static void handle_discord_join(const char* secret) -{ - char url[2048] = "http://lobby.libretro.com/"; - struct string_list *list = string_split(secret, "|"); - discord_state_t *discord_st = discord_get_ptr(); - - RARCH_LOG("[DISCORD] join secret: (%s)\n", secret); - - strlcpy(discord_st->peer_party_id, - list->elems[0].data, sizeof(discord_st->peer_party_id)); - strlcat(url, discord_st->peer_party_id, sizeof(url)); - strlcat(url, "/", sizeof(url)); - - RARCH_LOG("[DISCORD] querying lobby id: %s at %s\n", - discord_st->peer_party_id, url); - task_push_http_transfer(url, true, NULL, handle_discord_join_cb, NULL); -} - -static void handle_discord_spectate(const char* secret) -{ - RARCH_LOG("[DISCORD] spectate (%s)\n", secret); -} - -#ifdef HAVE_MENU -#if 0 -static void handle_discord_join_response(void *ignore, const char *line) -{ - /* TODO/FIXME: needs in-game widgets */ - if (strstr(line, "yes")) - Discord_Respond(user_id, DISCORD_REPLY_YES); - -#ifdef HAVE_MENU - menu_input_dialog_end(); - retroarch_menu_running_finished(false); -#endif -} -#endif -#endif - -static void handle_discord_join_request(const DiscordUser* request) -{ - static char url[PATH_MAX_LENGTH]; - static char url_encoded[PATH_MAX_LENGTH]; - static char filename[PATH_MAX_LENGTH]; - char buf[PATH_MAX_LENGTH]; -#ifdef HAVE_MENU - menu_input_ctx_line_t line; -#endif - - RARCH_LOG("[DISCORD] join request from %s#%s - %s %s\n", - request->username, - request->discriminator, - request->userId, - request->avatar); - -#ifdef HAVE_MENU - discord_download_avatar(request->userId, request->avatar); - -#if 0 - /* TODO/FIXME: Needs in-game widgets */ - retroarch_menu_running(); - - memset(&line, 0, sizeof(line)); - snprintf(buf, sizeof(buf), "%s %s?", - msg_hash_to_str(MSG_DISCORD_CONNECTION_REQUEST), request->username); - line.label = buf; - line.label_setting = "no_setting"; - line.cb = handle_discord_join_response; - - /* TODO/FIXME: needs in-game widgets - * TODO/FIXME: bespoke dialog, should show while in-game - * and have a hotkey to accept - * TODO/FIXME: show avatar of the user connecting - */ - if (!menu_input_dialog_start(&line)) - return; -#endif -#endif -} - -/* TODO/FIXME - replace last parameter with struct type to allow for more - * arguments to be passed later */ -void discord_update(enum discord_presence presence, bool fuzzy_archive_match) -{ - core_info_t *core_info = NULL; - discord_state_t *discord_st = discord_get_ptr(); - - core_info_get_current_core(&core_info); - - if (!discord_st->ready) - return; - - if (presence == discord_st->status) - return; - - if (!discord_st->connecting - && - ( presence == DISCORD_PRESENCE_NONE - || presence == DISCORD_PRESENCE_MENU)) - { - memset(&discord_st->presence, - 0, sizeof(discord_st->presence)); - discord_st->peer_party_id[0] = '\0'; - } - - switch (presence) - { - case DISCORD_PRESENCE_MENU: - discord_st->presence.details = msg_hash_to_str( - MENU_ENUM_LABEL_VALUE_DISCORD_IN_MENU); - discord_st->presence.largeImageKey = "base"; - discord_st->presence.largeImageText = msg_hash_to_str( - MENU_ENUM_LABEL_VALUE_NO_CORE); - discord_st->presence.instance = 0; - break; - case DISCORD_PRESENCE_GAME_PAUSED: - discord_st->presence.smallImageKey = "paused"; - discord_st->presence.smallImageText = msg_hash_to_str( - MENU_ENUM_LABEL_VALUE_DISCORD_STATUS_PAUSED); - discord_st->presence.details = msg_hash_to_str( - MENU_ENUM_LABEL_VALUE_DISCORD_IN_GAME_PAUSED); - discord_st->pause_time = time(0); - discord_st->elapsed_time = difftime(time(0), - discord_st->start_time); - discord_st->presence.startTimestamp = discord_st->pause_time; - break; - case DISCORD_PRESENCE_GAME: - if (core_info) - { - const char *system_id = core_info->system_id - ? core_info->system_id : "core"; - const char *label = NULL; - const struct playlist_entry *entry = NULL; - playlist_t *current_playlist = playlist_get_cached(); - - if (current_playlist) - { - playlist_get_index_by_path( - current_playlist, path_get(RARCH_PATH_CONTENT), &entry, - fuzzy_archive_match); - - if (entry && !string_is_empty(entry->label)) - label = entry->label; - } - - if (!label) - label = path_basename(path_get(RARCH_PATH_BASENAME)); -#if 0 - RARCH_LOG("[DISCORD] current core: %s\n", system_id); - RARCH_LOG("[DISCORD] current content: %s\n", label); -#endif - discord_st->presence.largeImageKey = system_id; - - if (core_info->display_name) - discord_st->presence.largeImageText = - core_info->display_name; - - discord_st->start_time = time(0); - if (discord_st->pause_time != 0) - discord_st->start_time = time(0) - - discord_st->elapsed_time; - - discord_st->pause_time = 0; - discord_st->elapsed_time = 0; - - discord_st->presence.smallImageKey = "playing"; - discord_st->presence.smallImageText = msg_hash_to_str( - MENU_ENUM_LABEL_VALUE_DISCORD_STATUS_PLAYING); - discord_st->presence.startTimestamp = discord_st->start_time; - discord_st->presence.details = msg_hash_to_str( - MENU_ENUM_LABEL_VALUE_DISCORD_IN_GAME); - - discord_st->presence.state = label; - discord_st->presence.instance = 0; - - if (!netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL)) - { - discord_st->peer_party_id[0] = '\0'; - discord_st->connecting = false; - discord_st->presence.partyId = NULL; - discord_st->presence.partyMax = 0; - discord_st->presence.partySize = 0; - discord_st->presence.joinSecret = (const char*)'\0'; - } - } - break; - case DISCORD_PRESENCE_NETPLAY_HOSTING: - { - char join_secret[128]; - struct netplay_room *room = netplay_get_host_room(); - bool host_method_is_mitm = room->host_method == NETPLAY_HOST_METHOD_MITM; - const char *srv_address = host_method_is_mitm ? room->mitm_address : room->address; - unsigned srv_port = host_method_is_mitm ? room->mitm_port : room->port; - if (room->id == 0) - return; - - RARCH_LOG("[DISCORD] netplay room details: id=%d" - ", nick=%s IP=%s port=%d\n", - room->id, room->nickname, - srv_address, srv_port); - - snprintf(discord_st->self_party_id, - sizeof(discord_st->self_party_id), "%d", room->id); - snprintf(join_secret, - sizeof(join_secret), "%d|%" PRId64, - room->id, cpu_features_get_time_usec()); - - discord_st->presence.joinSecret = strdup(join_secret); -#if 0 - discord_st->presence.spectateSecret = "SPECSPECSPEC"; -#endif - discord_st->presence.partyId = strdup(discord_st->self_party_id); - discord_st->presence.partyMax = 2; - discord_st->presence.partySize = 1; - - RARCH_LOG("[DISCORD] join secret: %s\n", join_secret); - RARCH_LOG("[DISCORD] party id: %s\n", discord_st->self_party_id); - } - break; - case DISCORD_PRESENCE_NETPLAY_CLIENT: - RARCH_LOG("[DISCORD] party id: %s\n", discord_st->peer_party_id); - discord_st->presence.partyId = strdup(discord_st->peer_party_id); - break; - case DISCORD_PRESENCE_NETPLAY_NETPLAY_STOPPED: - { - if (!netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL) && - !netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_CONNECTED, NULL)) - { - discord_st->peer_party_id[0] = '\0'; - discord_st->connecting = false; - discord_st->presence.partyId = NULL; - discord_st->presence.partyMax = 0; - discord_st->presence.partySize = 0; - discord_st->presence.joinSecret = (const char*)'\0'; - } - } - break; -#ifdef HAVE_CHEEVOS - case DISCORD_PRESENCE_RETROACHIEVEMENTS: - discord_st->presence.details = rcheevos_get_richpresence(); - presence = DISCORD_PRESENCE_GAME; - break; -#endif - case DISCORD_PRESENCE_SHUTDOWN: - discord_st->presence.partyId = NULL; - discord_st->presence.partyMax = 0; - discord_st->presence.partySize = 0; - discord_st->presence.joinSecret = (const char*)'\0'; - discord_st->connecting = false; - default: - break; - } - - RARCH_LOG("[DISCORD] updating (%d)\n", presence); - - Discord_UpdatePresence(&discord_st->presence); - discord_st->status = presence; -} - -void discord_init(const char *discord_app_id, char *args) -{ - DiscordEventHandlers handlers; - char full_path[PATH_MAX_LENGTH]; - char command[PATH_MAX_LENGTH]; - discord_state_t *discord_st = discord_get_ptr(); - - discord_st->start_time = time(0); - - memset(&handlers, 0, sizeof(handlers)); - - handlers.ready = handle_discord_ready; - handlers.disconnected = handle_discord_disconnected; - handlers.errored = handle_discord_error; - handlers.joinGame = handle_discord_join; - handlers.spectateGame = handle_discord_spectate; - handlers.joinRequest = handle_discord_join_request; - - RARCH_LOG("[DISCORD] initializing ..\n"); - - Discord_Initialize(discord_app_id, &handlers, 0, NULL); - -#ifdef _WIN32 - fill_pathname_application_path(full_path, sizeof(full_path)); - if (strstr(args, full_path)) - strlcpy(command, args, sizeof(command)); - else - { - path_basedir(full_path); - snprintf(command, sizeof(command), "%s%s", full_path, args); - } -#else - snprintf(command, sizeof(command), "sh -c %s", args); -#endif - RARCH_LOG("[DISCORD] registering startup command: %s\n", command); - Discord_Register(discord_app_id, command); - discord_st->ready = true; -} - -void discord_shutdown(void) -{ - discord_state_t *discord_st = discord_get_ptr(); - - RARCH_LOG("[DISCORD] shutting down ..\n"); - - Discord_ClearPresence(); - Discord_Shutdown(); - discord_st->ready = false; -} diff --git a/network/discord.h b/network/discord.h index 1839df58d7..045b8b416a 100644 --- a/network/discord.h +++ b/network/discord.h @@ -42,11 +42,9 @@ typedef struct discord_userdata enum discord_presence status; } discord_userdata_t; -void discord_init(const char *discord_app_id, char *args); - -void discord_shutdown(void); - -void discord_update(enum discord_presence presence, bool fuzzy_archive_match); +void discord_update( + enum discord_presence presence, + bool fuzzy_archive_match); bool discord_is_ready(void); diff --git a/retroarch.c b/retroarch.c index 3430d9b3f8..f643dfed9e 100644 --- a/retroarch.c +++ b/retroarch.c @@ -4,7 +4,7 @@ * Copyright (C) 2012-2015 - Michael Lelli * Copyright (C) 2014-2017 - Jean-André Santoni * Copyright (C) 2016-2019 - Brad Parker - * Copyright (C) 2016-2019 - Andrés Suárez (input mapper code) + * Copyright (C) 2016-2019 - Andrés Suárez (input mapper/Discord code) * * RetroArch is free software: you can redistribute it and/or modify it under the terms * of the GNU General Public License as published by the Free Software Found- @@ -114,7 +114,9 @@ #endif #ifdef HAVE_DISCORD +#include #include "deps/discord-rpc/include/discord_rpc.h" +#include "network/discord.h" #endif #include "config.def.h" @@ -223,6 +225,7 @@ #include "tasks/task_audio_mixer.h" #endif #include "tasks/task_content.h" +#include "tasks/task_file_transfer.h" #include "tasks/task_powerstate.h" #include "tasks/tasks_internal.h" #include "performance_counters.h" @@ -1297,6 +1300,8 @@ static const camera_driver_t *camera_drivers[] = { #define MENU_ENTRIES_GET_SELECTION_BUF_PTR_INTERNAL(idx) ((menu_st->entries.list) ? MENU_LIST_GET_SELECTION(menu_st->entries.list, (unsigned)idx) : NULL) #endif +#define CDN_URL "https://cdn.discordapp.com/avatars" + #ifdef HAVE_DYNAMIC #define SYMBOL(x) do { \ function_t func = dylib_proc(lib_handle_local, #x); \ @@ -1759,6 +1764,37 @@ typedef struct input_mapper input_bits_t buttons[MAX_USERS]; } input_mapper_t; +/* The Discord API specifies these variables: +- userId --------- char[24] - the userId of the player asking to join +- username ------- char[344] - the username of the player asking to join +- discriminator -- char[8] - the discriminator of the player asking to join +- spectateSecret - char[128] - secret used for spectatin matches +- joinSecret - char[128] - secret used to join matches +- partyId - char[128] - the party you would be joining +*/ + +struct discord_state +{ + bool ready; + bool avatar_ready; + bool connecting; + + unsigned status; + + int64_t start_time; + int64_t pause_time; + int64_t elapsed_time; + + char user_name[344]; + char self_party_id[128]; + char peer_party_id[128]; + char user_avatar[PATH_MAX_LENGTH]; + + DiscordRichPresence presence; +}; + +typedef struct discord_state discord_state_t; + struct rarch_state { enum rarch_core_type current_core_type; @@ -2206,6 +2242,7 @@ struct rarch_state #ifdef HAVE_MENU struct menu_state menu_driver_state; #endif + discord_state_t discord_st; struct retro_callbacks retro_ctx; struct retro_core_t current_core; @@ -2493,6 +2530,17 @@ struct retro_keybind input_autoconf_binds[MAX_USERS][RARCH_BIND_LIST_END]; struct retro_subsystem_info subsystem_data[SUBSYSTEM_MAX_SUBSYSTEMS]; /* Forward declarations */ +#ifdef HAVE_DISCORD +#if defined(__cplusplus) && !defined(CXX_BUILD) +extern "C" +{ +#endif + void Discord_Register(const char *a, const char *b); +#if defined(__cplusplus) && !defined(CXX_BUILD) +} +#endif +#endif + static void retroarch_fail(int error_code, const char *error); static void retroarch_core_options_intl_init( struct rarch_state *p_rarch, @@ -6265,6 +6313,477 @@ bool menu_driver_ctl(enum rarch_menu_ctl_state state, void *data) } #endif +#ifdef HAVE_DISCORD +bool discord_is_ready(void) +{ + struct rarch_state *p_rarch = &rarch_st; + discord_state_t *discord_st = &p_rarch->discord_st; + return discord_st->ready; +} + +char *discord_get_own_username(void) +{ + struct rarch_state *p_rarch = &rarch_st; + discord_state_t *discord_st = &p_rarch->discord_st; + + if (discord_st->ready) + return discord_st->user_name; + return NULL; +} + +char *discord_get_own_avatar(void) +{ + struct rarch_state *p_rarch = &rarch_st; + discord_state_t *discord_st = &p_rarch->discord_st; + if (discord_st->ready) + return discord_st->user_avatar; + return NULL; +} + +bool discord_avatar_is_ready(void) +{ + return false; +} + +void discord_avatar_set_ready(bool ready) +{ + struct rarch_state *p_rarch = &rarch_st; + discord_state_t *discord_st = &p_rarch->discord_st; + discord_st->avatar_ready = ready; +} + +#ifdef HAVE_MENU +static bool discord_download_avatar( + const char* user_id, const char* avatar_id) +{ + static char url[PATH_MAX_LENGTH]; + static char url_encoded[PATH_MAX_LENGTH]; + static char full_path[PATH_MAX_LENGTH]; + static char buf[PATH_MAX_LENGTH]; + file_transfer_t *transf = NULL; + struct rarch_state *p_rarch = &rarch_st; + discord_state_t *discord_st = &p_rarch->discord_st; + + RARCH_LOG("[DISCORD] user avatar id: %s\n", user_id); + + fill_pathname_application_special(buf, + sizeof(buf), + APPLICATION_SPECIAL_DIRECTORY_THUMBNAILS_DISCORD_AVATARS); + fill_pathname_join(full_path, buf, avatar_id, sizeof(full_path)); + strlcpy(discord_st->user_avatar, + avatar_id, sizeof(discord_st->user_avatar)); + + if (path_is_valid(full_path)) + return true; + + if (string_is_empty(avatar_id)) + return false; + + snprintf(url, sizeof(url), "%s/%s/%s.png", CDN_URL, user_id, avatar_id); + net_http_urlencode_full(url_encoded, url, sizeof(url_encoded)); + snprintf(buf, sizeof(buf), "%s.png", avatar_id); + + transf = (file_transfer_t*)calloc(1, sizeof(*transf)); + transf->enum_idx = MENU_ENUM_LABEL_CB_DISCORD_AVATAR; + strlcpy(transf->path, buf, sizeof(transf->path)); + + RARCH_LOG("[DISCORD] downloading avatar from: %s\n", url_encoded); + task_push_http_transfer_file(url_encoded, true, NULL, cb_generic_download, transf); + + return false; +} +#endif + +static void handle_discord_ready(const DiscordUser* connectedUser) +{ + struct rarch_state *p_rarch = &rarch_st; + discord_state_t *discord_st = &p_rarch->discord_st; + + strlcpy(discord_st->user_name, + connectedUser->username, sizeof(discord_st->user_name)); + + RARCH_LOG("[DISCORD] connected to user: %s#%s\n", + connectedUser->username, + connectedUser->discriminator); + +#ifdef HAVE_MENU + discord_download_avatar(connectedUser->userId, connectedUser->avatar); +#endif +} + +static void handle_discord_disconnected(int errcode, const char* message) +{ + RARCH_LOG("[DISCORD] disconnected (%d: %s)\n", errcode, message); +} + +static void handle_discord_error(int errcode, const char* message) +{ + RARCH_LOG("[DISCORD] error (%d: %s)\n", errcode, message); +} + +static void handle_discord_join_cb(retro_task_t *task, + void *task_data, void *user_data, const char *err) +{ + char join_hostname[PATH_MAX_LENGTH]; + struct netplay_room *room = NULL; + http_transfer_data_t *data = (http_transfer_data_t*)task_data; + struct rarch_state *p_rarch = &rarch_st; + discord_state_t *discord_st = &p_rarch->discord_st; + + if (!data || err) + goto finish; + + data->data = (char*)realloc(data->data, data->len + 1); + data->data[data->len] = '\0'; + + netplay_rooms_parse(data->data); + room = netplay_room_get(0); + + if (room) + { + bool host_method_is_mitm = room->host_method == NETPLAY_HOST_METHOD_MITM; + const char *srv_address = host_method_is_mitm ? room->mitm_address : room->address; + unsigned srv_port = host_method_is_mitm ? room->mitm_port : room->port; + + if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_DATA_INITED, NULL)) + deinit_netplay(); + netplay_driver_ctl(RARCH_NETPLAY_CTL_ENABLE_CLIENT, NULL); + + snprintf(join_hostname, sizeof(join_hostname), "%s|%d", + srv_address, srv_port); + + RARCH_LOG("[DISCORD] Joining lobby at: %s\n", join_hostname); + task_push_netplay_crc_scan(room->gamecrc, + room->gamename, join_hostname, room->corename, room->subsystem_name); + discord_st->connecting = true; + if (discord_st->ready) + discord_update(DISCORD_PRESENCE_NETPLAY_CLIENT, false); + } + +finish: + + if (err) + RARCH_ERR("%s: %s\n", msg_hash_to_str(MSG_DOWNLOAD_FAILED), err); + + if (data) + { + if (data->data) + free(data->data); + free(data); + } + + if (user_data) + free(user_data); +} + +static void handle_discord_join(const char* secret) +{ + char url[2048] = "http://lobby.libretro.com/"; + struct string_list *list = string_split(secret, "|"); + struct rarch_state *p_rarch = &rarch_st; + discord_state_t *discord_st = &p_rarch->discord_st; + + strlcpy(discord_st->peer_party_id, + list->elems[0].data, sizeof(discord_st->peer_party_id)); + strlcat(url, discord_st->peer_party_id, sizeof(url)); + strlcat(url, "/", sizeof(url)); + + RARCH_LOG("[DISCORD] Querying lobby id: %s at %s\n", + discord_st->peer_party_id, url); + task_push_http_transfer(url, true, NULL, handle_discord_join_cb, NULL); +} + +static void handle_discord_spectate(const char* secret) +{ + RARCH_LOG("[DISCORD] spectate (%s)\n", secret); +} + +#ifdef HAVE_MENU +#if 0 +static void handle_discord_join_response(void *ignore, const char *line) +{ + /* TODO/FIXME: needs in-game widgets */ + if (strstr(line, "yes")) + Discord_Respond(user_id, DISCORD_REPLY_YES); + +#ifdef HAVE_MENU + menu_input_dialog_end(); + retroarch_menu_running_finished(false); +#endif +} +#endif +#endif + +static void handle_discord_join_request(const DiscordUser* request) +{ + static char url[PATH_MAX_LENGTH]; + static char url_encoded[PATH_MAX_LENGTH]; + static char filename[PATH_MAX_LENGTH]; + char buf[PATH_MAX_LENGTH]; +#ifdef HAVE_MENU + menu_input_ctx_line_t line; +#endif + + RARCH_LOG("[DISCORD] join request from %s#%s - %s %s\n", + request->username, + request->discriminator, + request->userId, + request->avatar); + +#ifdef HAVE_MENU + discord_download_avatar(request->userId, request->avatar); + +#if 0 + /* TODO/FIXME: Needs in-game widgets */ + retroarch_menu_running(); + + memset(&line, 0, sizeof(line)); + snprintf(buf, sizeof(buf), "%s %s?", + msg_hash_to_str(MSG_DISCORD_CONNECTION_REQUEST), request->username); + line.label = buf; + line.label_setting = "no_setting"; + line.cb = handle_discord_join_response; + + /* TODO/FIXME: needs in-game widgets + * TODO/FIXME: bespoke dialog, should show while in-game + * and have a hotkey to accept + * TODO/FIXME: show avatar of the user connecting + */ + if (!menu_input_dialog_start(&line)) + return; +#endif +#endif +} + +void discord_update(enum discord_presence presence, bool fuzzy_archive_match) +{ + struct rarch_state *p_rarch = &rarch_st; + discord_state_t *discord_st = &p_rarch->discord_st; + + if (presence == discord_st->status) + return; + + if (!discord_st->connecting + && + ( presence == DISCORD_PRESENCE_NONE + || presence == DISCORD_PRESENCE_MENU)) + { + memset(&discord_st->presence, + 0, sizeof(discord_st->presence)); + discord_st->peer_party_id[0] = '\0'; + } + + switch (presence) + { + case DISCORD_PRESENCE_MENU: + discord_st->presence.details = msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_DISCORD_IN_MENU); + discord_st->presence.largeImageKey = "base"; + discord_st->presence.largeImageText = msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_NO_CORE); + discord_st->presence.instance = 0; + break; + case DISCORD_PRESENCE_GAME_PAUSED: + discord_st->presence.smallImageKey = "paused"; + discord_st->presence.smallImageText = msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_DISCORD_STATUS_PAUSED); + discord_st->presence.details = msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_DISCORD_IN_GAME_PAUSED); + discord_st->pause_time = time(0); + discord_st->elapsed_time = difftime(time(0), + discord_st->start_time); + discord_st->presence.startTimestamp = discord_st->pause_time; + break; + case DISCORD_PRESENCE_GAME: + { + core_info_t *core_info = NULL; + core_info_get_current_core(&core_info); + + if (core_info) + { + const char *system_id = + core_info->system_id + ? core_info->system_id + : "core"; + const char *label = NULL; + const struct playlist_entry *entry = NULL; + playlist_t *current_playlist = playlist_get_cached(); + + if (current_playlist) + { + playlist_get_index_by_path( + current_playlist, + path_get(RARCH_PATH_CONTENT), + &entry, + fuzzy_archive_match); + + if (entry && !string_is_empty(entry->label)) + label = entry->label; + } + + if (!label) + label = path_basename(path_get(RARCH_PATH_BASENAME)); + discord_st->presence.largeImageKey = system_id; + + if (core_info->display_name) + discord_st->presence.largeImageText = + core_info->display_name; + + discord_st->start_time = time(0); + if (discord_st->pause_time != 0) + discord_st->start_time = time(0) - + discord_st->elapsed_time; + + discord_st->pause_time = 0; + discord_st->elapsed_time = 0; + + discord_st->presence.smallImageKey = "playing"; + discord_st->presence.smallImageText = msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_DISCORD_STATUS_PLAYING); + discord_st->presence.startTimestamp = discord_st->start_time; + discord_st->presence.details = msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_DISCORD_IN_GAME); + + discord_st->presence.state = label; + discord_st->presence.instance = 0; + + if (!netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL)) + { + discord_st->peer_party_id[0] = '\0'; + discord_st->connecting = false; + discord_st->presence.partyId = NULL; + discord_st->presence.partyMax = 0; + discord_st->presence.partySize = 0; + discord_st->presence.joinSecret = (const char*)'\0'; + } + } + } + break; + case DISCORD_PRESENCE_NETPLAY_HOSTING: + { + char join_secret[128]; + struct netplay_room *room = netplay_get_host_room(); + bool host_method_is_mitm = room->host_method == NETPLAY_HOST_METHOD_MITM; + const char *srv_address = host_method_is_mitm ? room->mitm_address : room->address; + unsigned srv_port = host_method_is_mitm ? room->mitm_port : room->port; + if (room->id == 0) + return; + + RARCH_LOG("[DISCORD] netplay room details: id=%d" + ", nick=%s IP=%s port=%d\n", + room->id, room->nickname, + srv_address, srv_port); + + snprintf(discord_st->self_party_id, + sizeof(discord_st->self_party_id), "%d", room->id); + snprintf(join_secret, + sizeof(join_secret), "%d|%" PRId64, + room->id, cpu_features_get_time_usec()); + + discord_st->presence.joinSecret = strdup(join_secret); +#if 0 + discord_st->presence.spectateSecret = "SPECSPECSPEC"; +#endif + discord_st->presence.partyId = strdup(discord_st->self_party_id); + discord_st->presence.partyMax = 2; + discord_st->presence.partySize = 1; + + RARCH_LOG("[DISCORD] join secret: %s\n", join_secret); + RARCH_LOG("[DISCORD] party id: %s\n", discord_st->self_party_id); + } + break; + case DISCORD_PRESENCE_NETPLAY_CLIENT: + RARCH_LOG("[DISCORD] party id: %s\n", discord_st->peer_party_id); + discord_st->presence.partyId = strdup(discord_st->peer_party_id); + break; + case DISCORD_PRESENCE_NETPLAY_NETPLAY_STOPPED: + { + if (!netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL) && + !netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_CONNECTED, NULL)) + { + discord_st->peer_party_id[0] = '\0'; + discord_st->connecting = false; + discord_st->presence.partyId = NULL; + discord_st->presence.partyMax = 0; + discord_st->presence.partySize = 0; + discord_st->presence.joinSecret = (const char*)'\0'; + } + } + break; +#ifdef HAVE_CHEEVOS + case DISCORD_PRESENCE_RETROACHIEVEMENTS: + discord_st->presence.details = rcheevos_get_richpresence(); + presence = DISCORD_PRESENCE_GAME; + break; +#endif + case DISCORD_PRESENCE_SHUTDOWN: + discord_st->presence.partyId = NULL; + discord_st->presence.partyMax = 0; + discord_st->presence.partySize = 0; + discord_st->presence.joinSecret = (const char*)'\0'; + discord_st->connecting = false; + default: + break; + } + +#ifdef DEBUG + RARCH_LOG("[DISCORD] updating (%d)\n", presence); +#endif + + Discord_UpdatePresence(&discord_st->presence); + discord_st->status = presence; +} + +static void discord_init( + discord_state_t *discord_st, + const char *discord_app_id, char *args) +{ + DiscordEventHandlers handlers; + char full_path[PATH_MAX_LENGTH]; + char command[PATH_MAX_LENGTH]; + + discord_st->start_time = time(0); + + memset(&handlers, 0, sizeof(handlers)); + + handlers.ready = handle_discord_ready; + handlers.disconnected = handle_discord_disconnected; + handlers.errored = handle_discord_error; + handlers.joinGame = handle_discord_join; + handlers.spectateGame = handle_discord_spectate; + handlers.joinRequest = handle_discord_join_request; + + RARCH_LOG("[DISCORD] initializing ..\n"); + + Discord_Initialize(discord_app_id, &handlers, 0, NULL); + +#ifdef _WIN32 + fill_pathname_application_path(full_path, sizeof(full_path)); + if (strstr(args, full_path)) + strlcpy(command, args, sizeof(command)); + else + { + path_basedir(full_path); + snprintf(command, sizeof(command), "%s%s", full_path, args); + } +#else + snprintf(command, sizeof(command), "sh -c %s", args); +#endif + RARCH_LOG("[DISCORD] registering startup command: %s\n", command); + Discord_Register(discord_app_id, command); + discord_st->ready = true; +} + +static void discord_shutdown(discord_state_t *discord_st) +{ + RARCH_LOG("[DISCORD] shutting down ..\n"); + + Discord_ClearPresence(); + Discord_Shutdown(); + discord_st->ready = false; +} +#endif + static void log_counters( struct retro_perf_counter **counters, unsigned num) { @@ -12617,30 +13136,34 @@ bool command_event(enum event_command cmd, void *data) { bool discord_enable = settings ? settings->bools.discord_enable : false; const char *discord_app_id = settings ? settings->arrays.discord_app_id : NULL; + discord_state_t *discord_st = &p_rarch->discord_st; if (!settings) return false; if (!discord_enable) return false; - if (discord_is_ready()) + if (discord_st->ready) return true; - discord_init(discord_app_id, + discord_init(discord_st, + discord_app_id, p_rarch->launch_arguments); } #endif break; case CMD_EVENT_DISCORD_UPDATE: -#ifdef HAVE_DISCORD - if (!data || !discord_is_ready()) - return false; - { +#ifdef HAVE_DISCORD + discord_state_t *discord_st = &p_rarch->discord_st; + if (!data || !discord_st->ready) + return false; + bool playlist_fuzzy_archive_match = settings->bools.playlist_fuzzy_archive_match; discord_userdata_t *userdata = (discord_userdata_t*)data; - discord_update(userdata->status, playlist_fuzzy_archive_match); - } + if (discord_st->ready) + discord_update(userdata->status, playlist_fuzzy_archive_match); #endif + } break; case CMD_EVENT_AI_SERVICE_CALL: @@ -32947,14 +33470,15 @@ bool retroarch_main_quit(void) { struct rarch_state *p_rarch = &rarch_st; #ifdef HAVE_DISCORD + discord_state_t *discord_st = &p_rarch->discord_st; if (discord_is_inited) { discord_userdata_t userdata; userdata.status = DISCORD_PRESENCE_SHUTDOWN; command_event(CMD_EVENT_DISCORD_UPDATE, &userdata); } - if (discord_is_ready()) - discord_shutdown(); + if (discord_st->ready) + discord_shutdown(discord_st); discord_is_inited = false; #endif @@ -34197,6 +34721,8 @@ int runloop_iterate(void) retro_time_t current_time = cpu_features_get_time_usec(); #ifdef HAVE_DISCORD + discord_state_t *discord_st = &p_rarch->discord_st; + if (discord_is_inited) Discord_RunCallbacks(); #endif @@ -34334,8 +34860,9 @@ int runloop_iterate(void) cheat_manager_apply_retro_cheats(); #ifdef HAVE_DISCORD - if (discord_is_inited && discord_is_ready()) - discord_update(DISCORD_PRESENCE_GAME, settings->bools.playlist_fuzzy_archive_match); + if (discord_is_inited && discord_st->ready) + discord_update(DISCORD_PRESENCE_GAME, + settings->bools.playlist_fuzzy_archive_match); #endif for (i = 0; i < max_users; i++)