From 9c4b307dcbac1de1e1b02dbc707deaae03115d41 Mon Sep 17 00:00:00 2001 From: radius Date: Tue, 16 Oct 2018 18:16:01 -0500 Subject: [PATCH 01/12] Update Discord RPC Squashed 'deps/discord-rpc/' changes from ba9fe00c4d..7716eadca3 7716eadca3 Update D binding link (#234) e32d001809 readme nits 2cb9813eb6 Unity specific DLL setup af380116a0 Check C# strings against UTF8 bytes instead of clamping (#221) 3d3ae7129d Fix mismatched signs in comparison after b44defe (#209) b44defe60a [Linux] Null-terminate output from readlink in Discord_Register. (#208) dfad394be0 Added enum for response codes (#207) a3ad6afee2 Added Discord RPC C# implementation to list. (#205) 7c41a8ec19 Fixed issue with Discord RPC not updating presence during shutdown (#189) 5df1c5ae6d copy the whole folder UE4 c05c7148dd Updated README with UE plugin instructions (#183) git-subtree-dir: deps/discord-rpc git-subtree-split: 7716eadca31a0d556f9d71fb610943b203470533 --- .../button-clicker/Assets/DiscordRpc.cs | 58 ++++++------------- .../DiscordRpc/Public/DiscordRpcBlueprint.h | 10 ++++ .../src/discord_register_linux.cpp | 4 +- deps/discord-rpc/src/discord_rpc.cpp | 3 +- 4 files changed, 33 insertions(+), 42 deletions(-) diff --git a/deps/discord-rpc/examples/button-clicker/Assets/DiscordRpc.cs b/deps/discord-rpc/examples/button-clicker/Assets/DiscordRpc.cs index dec1ade0f5..af82c1c231 100644 --- a/deps/discord-rpc/examples/button-clicker/Assets/DiscordRpc.cs +++ b/deps/discord-rpc/examples/button-clicker/Assets/DiscordRpc.cs @@ -129,20 +129,20 @@ public class DiscordRpc FreeMem(); } - _presence.state = StrToPtr(state, 128); - _presence.details = StrToPtr(details, 128); + _presence.state = StrToPtr(state); + _presence.details = StrToPtr(details); _presence.startTimestamp = startTimestamp; _presence.endTimestamp = endTimestamp; - _presence.largeImageKey = StrToPtr(largeImageKey, 32); - _presence.largeImageText = StrToPtr(largeImageText, 128); - _presence.smallImageKey = StrToPtr(smallImageKey, 32); - _presence.smallImageText = StrToPtr(smallImageText, 128); - _presence.partyId = StrToPtr(partyId, 128); + _presence.largeImageKey = StrToPtr(largeImageKey); + _presence.largeImageText = StrToPtr(largeImageText); + _presence.smallImageKey = StrToPtr(smallImageKey); + _presence.smallImageText = StrToPtr(smallImageText); + _presence.partyId = StrToPtr(partyId); _presence.partySize = partySize; _presence.partyMax = partyMax; - _presence.matchSecret = StrToPtr(matchSecret, 128); - _presence.joinSecret = StrToPtr(joinSecret, 128); - _presence.spectateSecret = StrToPtr(spectateSecret, 128); + _presence.matchSecret = StrToPtr(matchSecret); + _presence.joinSecret = StrToPtr(joinSecret); + _presence.spectateSecret = StrToPtr(spectateSecret); _presence.instance = instance; return _presence; @@ -152,16 +152,18 @@ public class DiscordRpc /// Returns a pointer to a representation of the given string with a size of maxbytes /// /// String to convert - /// Max number of bytes to use /// Pointer to the UTF-8 representation of - private IntPtr StrToPtr(string input, int maxbytes) + private IntPtr StrToPtr(string input) { if (string.IsNullOrEmpty(input)) return IntPtr.Zero; - var convstr = StrClampBytes(input, maxbytes); - var convbytecnt = Encoding.UTF8.GetByteCount(convstr); - var buffer = Marshal.AllocHGlobal(convbytecnt); + var convbytecnt = Encoding.UTF8.GetByteCount(input); + var buffer = Marshal.AllocHGlobal(convbytecnt + 1); + for (int i = 0; i < convbytecnt + 1; i++) + { + Marshal.WriteByte(buffer, i , 0); + } _buffers.Add(buffer); - Marshal.Copy(Encoding.UTF8.GetBytes(convstr), 0, buffer, convbytecnt); + Marshal.Copy(Encoding.UTF8.GetBytes(input), 0, buffer, convbytecnt); return buffer; } @@ -181,30 +183,6 @@ public class DiscordRpc return Encoding.UTF8.GetString(Encoding.UTF8.GetBytes(str)); } - /// - /// Clamp the string to the given byte length preserving null termination - /// - /// string to clamp - /// max bytes the resulting string should have (including null termination) - /// null terminated string with a byte length less or equal to - private static string StrClampBytes(string toclamp, int maxbytes) - { - var str = StrToUtf8NullTerm(toclamp); - var strbytes = Encoding.UTF8.GetBytes(str); - - if (strbytes.Length <= maxbytes) - { - return str; - } - - var newstrbytes = new byte[] { }; - Array.Copy(strbytes, 0, newstrbytes, 0, maxbytes - 1); - newstrbytes[newstrbytes.Length - 1] = 0; - newstrbytes[newstrbytes.Length - 2] = 0; - - return Encoding.UTF8.GetString(newstrbytes); - } - /// /// Free the allocated memory for conversion to /// diff --git a/deps/discord-rpc/examples/unrealstatus/Plugins/discordrpc/Source/DiscordRpc/Public/DiscordRpcBlueprint.h b/deps/discord-rpc/examples/unrealstatus/Plugins/discordrpc/Source/DiscordRpc/Public/DiscordRpcBlueprint.h index 2d6521124b..17e2f9b29e 100644 --- a/deps/discord-rpc/examples/unrealstatus/Plugins/discordrpc/Source/DiscordRpc/Public/DiscordRpcBlueprint.h +++ b/deps/discord-rpc/examples/unrealstatus/Plugins/discordrpc/Source/DiscordRpc/Public/DiscordRpcBlueprint.h @@ -24,6 +24,16 @@ struct FDiscordUserData { FString avatar; }; +/** +* Valid response codes for Respond function +*/ +UENUM(BlueprintType) +enum class EDiscordJoinResponseCodes : uint8 +{ + DISCORD_REPLY_NO UMETA(DisplayName="No"), + DISCORD_REPLY_YES UMETA(DisplayName="Yes"), + DISCORD_REPLY_IGNORE UMETA(DisplayName="Ignore") +}; DECLARE_LOG_CATEGORY_EXTERN(Discord, Log, All); diff --git a/deps/discord-rpc/src/discord_register_linux.cpp b/deps/discord-rpc/src/discord_register_linux.cpp index b10f96db66..09911dcc6c 100644 --- a/deps/discord-rpc/src/discord_register_linux.cpp +++ b/deps/discord-rpc/src/discord_register_linux.cpp @@ -33,9 +33,11 @@ extern "C" DISCORD_EXPORT void Discord_Register(const char* applicationId, const char exePath[1024]; if (!command || !command[0]) { - if (readlink("/proc/self/exe", exePath, sizeof(exePath)) <= 0) { + ssize_t size = readlink("/proc/self/exe", exePath, sizeof(exePath)); + if (size <= 0 || size >= (ssize_t)sizeof(exePath)) { return; } + exePath[size] = '\0'; command = exePath; } diff --git a/deps/discord-rpc/src/discord_rpc.cpp b/deps/discord-rpc/src/discord_rpc.cpp index dedb3f1539..2e44c939ce 100644 --- a/deps/discord-rpc/src/discord_rpc.cpp +++ b/deps/discord-rpc/src/discord_rpc.cpp @@ -89,10 +89,11 @@ public: keepRunning.store(true); ioThread = std::thread([&]() { const std::chrono::duration maxWait{500LL}; + Discord_UpdateConnection(); while (keepRunning.load()) { - Discord_UpdateConnection(); std::unique_lock lock(waitForIOMutex); waitForIOActivity.wait_for(lock, maxWait); + Discord_UpdateConnection(); } }); } From d4b8e1c4fdbec42e02ea6017dd69c69abc595f63 Mon Sep 17 00:00:00 2001 From: radius Date: Tue, 16 Oct 2018 18:52:53 -0500 Subject: [PATCH 02/12] [discord] add more logging, fix partyId, for some reason this strdup is required for linux hosts --- discord/discord.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/discord/discord.c b/discord/discord.c index 0efe85e036..a51a77f1d4 100644 --- a/discord/discord.c +++ b/discord/discord.c @@ -192,10 +192,11 @@ void discord_update(enum discord_presence presence) room->gamename, room->gamecrc, room->corename); RARCH_LOG("%s\n", join_secret); discord_presence.joinSecret = strdup(join_secret); - discord_presence.spectateSecret = "SPECSPECSPEC"; - discord_presence.partyId = party_id; + /* discord_presence.spectateSecret = "SPECSPECSPEC"; */ + discord_presence.partyId = strdup(party_id); discord_presence.partyMax = 0; discord_presence.partySize = 0; + RARCH_LOG("[Discord] joining: \n Secret: %s\n Party: %s\n", discord_presence.joinSecret, discord_presence.partyId); break; case DISCORD_PRESENCE_NETPLAY_HOSTING_STOPPED: case DISCORD_PRESENCE_NETPLAY_CLIENT: From 325d85ed4bf5d4ec70e7f602a9c594323e6d6445 Mon Sep 17 00:00:00 2001 From: radius Date: Tue, 16 Oct 2018 22:53:42 -0500 Subject: [PATCH 03/12] [discord] nasty hack to register the application with the current runtime args basically saves the whole argv string to reuse it on game launch Not working due to: https://github.com/discordapp/discord-rpc/issues/240 https://github.com/discordapp/discord-rpc/issues/163 --- discord/discord.c | 9 +++++---- retroarch.c | 10 ++++++++++ retroarch.h | 3 +++ 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/discord/discord.c b/discord/discord.c index a51a77f1d4..0b00ec5bc9 100644 --- a/discord/discord.c +++ b/discord/discord.c @@ -227,12 +227,13 @@ void discord_init(void) handlers.spectateGame = handle_discord_spectate; handlers.joinRequest = handle_discord_join_request; - /* To-Do: Add the arguments RetroArch was started with to the register URI*/ - const char command[256] = "retroarch"; - Discord_Initialize(settings->arrays.discord_app_id, &handlers, 0, NULL); - Discord_Register(settings->arrays.discord_app_id, NULL); + char command[PATH_MAX_LENGTH]; + + strlcpy(command, _argv, sizeof(command)); + RARCH_LOG("[Discord] registering startup command: %s\n", command); + Discord_Register(settings->arrays.discord_app_id, command); discord_ready = true; } diff --git a/retroarch.c b/retroarch.c index 3e0dc5cb1a..3a3cf88daf 100644 --- a/retroarch.c +++ b/retroarch.c @@ -637,6 +637,16 @@ static void retroarch_parse_input_and_config(int argc, char *argv[]) bool explicit_menu = false; global_t *global = global_get_ptr(); + /* Nasty hack to copy the args into a global so discord can register the same arguments for launch */ + char buf[4096]; + for (unsigned i = 0; i < argc; i++) + { + snprintf(buf, sizeof(buf), "%s %s", _argv, argv[i]); + strlcpy(_argv, buf, sizeof(_argv)); + } + _argc = argc; + string_trim_whitespace_left(_argv); + const struct option opts[] = { #ifdef HAVE_DYNAMIC { "libretro", 1, NULL, 'L' }, diff --git a/retroarch.h b/retroarch.h index 50f481ea6c..ab4f62119d 100644 --- a/retroarch.h +++ b/retroarch.h @@ -305,6 +305,9 @@ typedef struct global #endif } global_t; +unsigned _argc; +char _argv[4096]; + bool rarch_ctl(enum rarch_ctl_state state, void *data); int retroarch_get_capabilities(enum rarch_capabilities type, From 583273d81bc085b0a660995801115c2b6c138207 Mon Sep 17 00:00:00 2001 From: radius Date: Tue, 16 Oct 2018 23:50:17 -0500 Subject: [PATCH 04/12] [discord] show connection confirmation dialog --- discord/discord.c | 41 ++++++++++++++++++++++++++++++++++++++--- intl/msg_hash_us.h | 4 ++++ msg_hash.h | 1 + 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/discord/discord.c b/discord/discord.c index 0b00ec5bc9..7fe4e3e41c 100644 --- a/discord/discord.c +++ b/discord/discord.c @@ -37,6 +37,11 @@ #include "../cheevos/cheevos.h" #endif +#ifdef HAVE_MENU +#include "../../menu/widgets/menu_input_dialog.h" +#endif + + static int FrustrationLevel = 0; static int64_t start_time = 0; @@ -48,6 +53,8 @@ static unsigned discord_status = 0; struct netplay_room *room; +static char user_id[128]; + DiscordRichPresence discord_presence; static void handle_discord_ready(const DiscordUser* connectedUser) @@ -93,14 +100,42 @@ static void handle_discord_spectate(const char* secret) RARCH_LOG("[Discord] spectate (%s)\n", secret); } +static void handle_discord_join_response(void *ignore, const char *line) +{ + if (strstr(line, "yes")) + Discord_Respond(user_id, DISCORD_REPLY_YES); + +#ifdef HAVE_MENU + menu_input_dialog_end(); + rarch_menu_running_finished(); +#endif +} + static void handle_discord_join_request(const DiscordUser* request) { - int response = -1; - char yn[4]; RARCH_LOG("[Discord] join request from %s#%s - %s\n", request->username, request->discriminator, request->userId); + + strlcpy(user_id, request->userId, sizeof(user_id)); + +#ifdef HAVE_MENU + char buf[PATH_MAX_LENGTH]; + menu_input_ctx_line_t line; + rarch_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; + + /* To-Do: bespoke dialog, should show while in-game and have a hotkey to accept */ + /* To-Do: show avatar of the user connecting */ + if (!menu_input_dialog_start(&line)) + return; +#endif } void discord_update(enum discord_presence presence) @@ -230,8 +265,8 @@ void discord_init(void) Discord_Initialize(settings->arrays.discord_app_id, &handlers, 0, NULL); char command[PATH_MAX_LENGTH]; - strlcpy(command, _argv, sizeof(command)); + RARCH_LOG("[Discord] registering startup command: %s\n", command); Discord_Register(settings->arrays.discord_app_id, command); discord_ready = true; diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index f49615f723..0a4f621720 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -129,6 +129,10 @@ MSG_HASH( MSG_NETPLAY_ENTER_PASSWORD, "Enter netplay server password:" ) +MSG_HASH( + MSG_DISCORD_CONNECTION_REQUEST, + "Do you want to allow connection from user:" + ) MSG_HASH( MSG_NETPLAY_INCORRECT_PASSWORD, "Incorrect password" diff --git a/msg_hash.h b/msg_hash.h index 643bca75db..ecb4c580f4 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -189,6 +189,7 @@ enum msg_hash_enums MSG_RESAMPLER_QUALITY_NORMAL, MSG_RESAMPLER_QUALITY_HIGHER, MSG_RESAMPLER_QUALITY_HIGHEST, + MSG_DISCORD_CONNECTION_REQUEST, MSG_ADDED_TO_FAVORITES, MSG_RESET_CORE_ASSOCIATION, MSG_CORE_ASSOCIATION_RESET, From a6e6ccc24497cbe739a48f172325844434ef1b7d Mon Sep 17 00:00:00 2001 From: radius Date: Wed, 17 Oct 2018 01:08:15 -0500 Subject: [PATCH 05/12] [discord] **not-working** download the connecting user avatar seems something special needs to be done for https transfers on windows at least see: https://github.com/libretro/RetroArch/issues/7451 --- discord/discord.c | 26 ++++++++++++++++++++++++-- file_path_special.c | 25 +++++++++++++++++++++++++ file_path_special.h | 3 ++- menu/cbs/menu_cbs_ok.c | 12 +++++++++++- menu/menu_cbs.h | 4 ++++ msg_hash.h | 1 + 6 files changed, 67 insertions(+), 4 deletions(-) diff --git a/discord/discord.c b/discord/discord.c index 7fe4e3e41c..39b76c0bce 100644 --- a/discord/discord.c +++ b/discord/discord.c @@ -39,8 +39,12 @@ #ifdef HAVE_MENU #include "../../menu/widgets/menu_input_dialog.h" +#include "../../menu/menu_cbs.h" #endif +#include +#include "../network/net_http_special.h" +#include "../tasks/tasks_internal.h" static int FrustrationLevel = 0; @@ -55,6 +59,8 @@ struct netplay_room *room; static char user_id[128]; +static char cdn_url[] = "https://cdn.discordapp.com/avatars"; + DiscordRichPresence discord_presence; static void handle_discord_ready(const DiscordUser* connectedUser) @@ -113,13 +119,29 @@ static void handle_discord_join_response(void *ignore, const char *line) static void handle_discord_join_request(const DiscordUser* request) { - RARCH_LOG("[Discord] join request from %s#%s - %s\n", + static char url[PATH_MAX_LENGTH]; + static char url_encoded[PATH_MAX_LENGTH]; + + RARCH_LOG("[Discord] join request from %s#%s - %s %s\n", request->username, request->discriminator, - request->userId); + request->userId, + request->avatar); strlcpy(user_id, request->userId, sizeof(user_id)); + snprintf(url, sizeof(url), "%s/%s/%s.png", cdn_url,request->userId, request->avatar); + net_http_urlencode_full(url_encoded, url, sizeof(url_encoded)); + + RARCH_LOG("[Discord] downloading avatar from: %s\n", url_encoded); + + file_transfer_t *transf = NULL; + transf = (file_transfer_t*)calloc(1, sizeof(*transf)); + transf->enum_idx = MENU_ENUM_LABEL_CB_DISCORD_AVATAR; + strlcpy(transf->path, request->avatar, sizeof(transf->path)); + + task_push_http_transfer(url_encoded, true, NULL, cb_generic_download, transf); + #ifdef HAVE_MENU char buf[PATH_MAX_LENGTH]; menu_input_ctx_line_t line; diff --git a/file_path_special.c b/file_path_special.c index fa6c3690a8..70769b97da 100644 --- a/file_path_special.c +++ b/file_path_special.c @@ -338,6 +338,31 @@ void fill_pathname_application_special(char *s, } #endif break; + case APPLICATION_SPECIAL_DIRECTORY_THUMBNAILS_DISCORD_AVATARS: + { + char *s1 = (char*)malloc(PATH_MAX_LENGTH * sizeof(char)); + char *s2 = (char*)malloc(PATH_MAX_LENGTH * sizeof(char)); + settings_t *settings = config_get_ptr(); + + s1[0] = s2[0] = '\0'; + + fill_pathname_join(s1, + settings->paths.directory_thumbnails, + "discord", + len); + fill_pathname_join(s2, + s1, "avatars", + PATH_MAX_LENGTH * sizeof(char) + ); + fill_pathname_slash(s2, + PATH_MAX_LENGTH * sizeof(char) + ); + strlcpy(s, s2, len); + free(s1); + free(s2); + } + break; + case APPLICATION_SPECIAL_DIRECTORY_THUMBNAILS_CHEEVOS_BADGES: { char *s1 = (char*)malloc(PATH_MAX_LENGTH * sizeof(char)); diff --git a/file_path_special.h b/file_path_special.h index 16455f2486..d98a6b3971 100644 --- a/file_path_special.h +++ b/file_path_special.h @@ -111,7 +111,8 @@ enum application_special_type APPLICATION_SPECIAL_DIRECTORY_ASSETS_ZARCH, APPLICATION_SPECIAL_DIRECTORY_ASSETS_ZARCH_FONT, APPLICATION_SPECIAL_DIRECTORY_ASSETS_ZARCH_ICONS, - APPLICATION_SPECIAL_DIRECTORY_THUMBNAILS_CHEEVOS_BADGES + APPLICATION_SPECIAL_DIRECTORY_THUMBNAILS_CHEEVOS_BADGES, + APPLICATION_SPECIAL_DIRECTORY_THUMBNAILS_DISCORD_AVATARS }; /** diff --git a/menu/cbs/menu_cbs_ok.c b/menu/cbs/menu_cbs_ok.c index 734408e440..1abbd27c42 100644 --- a/menu/cbs/menu_cbs_ok.c +++ b/menu/cbs/menu_cbs_ok.c @@ -3343,7 +3343,7 @@ static void cb_generic_dir_download(void *task_data, } /* expects http_transfer_t*, file_transfer_t* */ -static void cb_generic_download(void *task_data, +void cb_generic_download(void *task_data, void *user_data, const char *err) { char output_path[PATH_MAX_LENGTH]; @@ -3359,6 +3359,7 @@ static void cb_generic_download(void *task_data, goto finish; output_path[0] = '\0'; + char buf[PATH_MAX_LENGTH];; /* we have to determine dir_path at the time of writting or else * we'd run into races when the user changes the setting during an @@ -3423,6 +3424,15 @@ static void cb_generic_download(void *task_data, case MENU_ENUM_LABEL_CB_LAKKA_DOWNLOAD: dir_path = LAKKA_UPDATE_DIR; break; + case MENU_ENUM_LABEL_CB_DISCORD_AVATAR: + { + fill_pathname_application_special(buf, + PATH_MAX_LENGTH * sizeof(char), + APPLICATION_SPECIAL_DIRECTORY_THUMBNAILS_DISCORD_AVATARS); + dir_path = buf; + RARCH_LOG("Path: %s\n", dir_path); + break; + } default: RARCH_WARN("Unknown transfer type '%s' bailing out.\n", msg_hash_to_str(transf->enum_idx)); diff --git a/menu/menu_cbs.h b/menu/menu_cbs.h index 717ca7a302..e762038d44 100644 --- a/menu/menu_cbs.h +++ b/menu/menu_cbs.h @@ -286,6 +286,10 @@ void menu_cbs_init(void *data, int menu_cbs_exit(void); +void cb_generic_download(void *task_data, + void *user_data, const char *err); + + RETRO_END_DECLS #endif diff --git a/msg_hash.h b/msg_hash.h index ecb4c580f4..481ca725da 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -1932,6 +1932,7 @@ enum msg_hash_enums MENU_ENUM_LABEL_CB_UPDATE_SHADERS_CG, MENU_ENUM_LABEL_CB_UPDATE_SHADERS_GLSL, MENU_ENUM_LABEL_CB_UPDATE_SHADERS_SLANG, + MENU_ENUM_LABEL_CB_DISCORD_AVATAR, /* Sublabels */ MENU_ENUM_SUBLABEL_MIXER_ACTION_PLAY, From 87c99a491b71028f980fa8b354d19df4179a8f25 Mon Sep 17 00:00:00 2001 From: radius Date: Wed, 17 Oct 2018 10:28:36 -0500 Subject: [PATCH 06/12] [discord] make the avatar download code a function instead of this mess --- discord/discord.c | 52 +++++++++++++++++++++++++++++++----------- menu/cbs/menu_cbs_ok.c | 1 - 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/discord/discord.c b/discord/discord.c index 39b76c0bce..2a2e1f2782 100644 --- a/discord/discord.c +++ b/discord/discord.c @@ -45,6 +45,9 @@ #include #include "../network/net_http_special.h" #include "../tasks/tasks_internal.h" +#include +#include +#include "../file_path_special.h" static int FrustrationLevel = 0; @@ -117,10 +120,45 @@ static void handle_discord_join_response(void *ignore, const char *line) #endif } +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 fullpath[PATH_MAX_LENGTH]; + + static char buf[PATH_MAX_LENGTH]; + + file_transfer_t *transf = NULL; + + fill_pathname_application_special(buf, + sizeof(buf), + APPLICATION_SPECIAL_DIRECTORY_THUMBNAILS_DISCORD_AVATARS); + fill_pathname_join(fullpath, buf, avatar_id, sizeof(fullpath)); + + if(filestream_exists(fullpath)) + return true; + else + { + 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(url_encoded, true, NULL, cb_generic_download, transf); + + return false; + } +} + 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]; RARCH_LOG("[Discord] join request from %s#%s - %s %s\n", request->username, @@ -128,19 +166,7 @@ static void handle_discord_join_request(const DiscordUser* request) request->userId, request->avatar); - strlcpy(user_id, request->userId, sizeof(user_id)); - - snprintf(url, sizeof(url), "%s/%s/%s.png", cdn_url,request->userId, request->avatar); - net_http_urlencode_full(url_encoded, url, sizeof(url_encoded)); - - RARCH_LOG("[Discord] downloading avatar from: %s\n", url_encoded); - - file_transfer_t *transf = NULL; - transf = (file_transfer_t*)calloc(1, sizeof(*transf)); - transf->enum_idx = MENU_ENUM_LABEL_CB_DISCORD_AVATAR; - strlcpy(transf->path, request->avatar, sizeof(transf->path)); - - task_push_http_transfer(url_encoded, true, NULL, cb_generic_download, transf); + discord_download_avatar(request->userId, request->avatar); #ifdef HAVE_MENU char buf[PATH_MAX_LENGTH]; diff --git a/menu/cbs/menu_cbs_ok.c b/menu/cbs/menu_cbs_ok.c index 1abbd27c42..a077720d30 100644 --- a/menu/cbs/menu_cbs_ok.c +++ b/menu/cbs/menu_cbs_ok.c @@ -3430,7 +3430,6 @@ void cb_generic_download(void *task_data, PATH_MAX_LENGTH * sizeof(char), APPLICATION_SPECIAL_DIRECTORY_THUMBNAILS_DISCORD_AVATARS); dir_path = buf; - RARCH_LOG("Path: %s\n", dir_path); break; } default: From f909f98e1ab243cfe9c18f902a13ec6a8e3d4e2e Mon Sep 17 00:00:00 2001 From: radius Date: Wed, 17 Oct 2018 10:32:29 -0500 Subject: [PATCH 07/12] [discord] download our own avatar to use in-menu --- discord/discord.c | 69 ++++++++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/discord/discord.c b/discord/discord.c index 2a2e1f2782..4c2d482cfe 100644 --- a/discord/discord.c +++ b/discord/discord.c @@ -66,12 +66,47 @@ static char cdn_url[] = "https://cdn.discordapp.com/avatars"; DiscordRichPresence discord_presence; +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 fullpath[PATH_MAX_LENGTH]; + + static char buf[PATH_MAX_LENGTH]; + + file_transfer_t *transf = NULL; + + fill_pathname_application_special(buf, + sizeof(buf), + APPLICATION_SPECIAL_DIRECTORY_THUMBNAILS_DISCORD_AVATARS); + fill_pathname_join(fullpath, buf, avatar_id, sizeof(fullpath)); + + if(filestream_exists(fullpath)) + return true; + else + { + 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(url_encoded, true, NULL, cb_generic_download, transf); + + return false; + } +} + static void handle_discord_ready(const DiscordUser* connectedUser) { RARCH_LOG("[Discord] connected to user: %s#%s - avatar id: %s\n", connectedUser->username, connectedUser->discriminator, connectedUser->userId); + discord_download_avatar(connectedUser->userId, connectedUser->avatar); } static void handle_discord_disconnected(int errcode, const char* message) @@ -120,40 +155,6 @@ static void handle_discord_join_response(void *ignore, const char *line) #endif } -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 fullpath[PATH_MAX_LENGTH]; - - static char buf[PATH_MAX_LENGTH]; - - file_transfer_t *transf = NULL; - - fill_pathname_application_special(buf, - sizeof(buf), - APPLICATION_SPECIAL_DIRECTORY_THUMBNAILS_DISCORD_AVATARS); - fill_pathname_join(fullpath, buf, avatar_id, sizeof(fullpath)); - - if(filestream_exists(fullpath)) - return true; - else - { - 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(url_encoded, true, NULL, cb_generic_download, transf); - - return false; - } -} - static void handle_discord_join_request(const DiscordUser* request) { static char url[PATH_MAX_LENGTH]; From 69d020e16d6a7c523308a7b8b19be29f301e0f72 Mon Sep 17 00:00:00 2001 From: radius Date: Thu, 18 Oct 2018 15:55:18 -0500 Subject: [PATCH 08/12] [discord] disable confirmation dialog because in-game widgets will never happen see: https://github.com/libretro/RetroArch/issues/4572 --- discord/discord.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/discord/discord.c b/discord/discord.c index 4c2d482cfe..ad7c94cdb8 100644 --- a/discord/discord.c +++ b/discord/discord.c @@ -146,13 +146,17 @@ static void handle_discord_spectate(const char* secret) static void handle_discord_join_response(void *ignore, const char *line) { + /* To-Do: needs in-game widgets if (strstr(line, "yes")) Discord_Respond(user_id, DISCORD_REPLY_YES); + + #ifdef HAVE_MENU menu_input_dialog_end(); rarch_menu_running_finished(); #endif +*/ } static void handle_discord_join_request(const DiscordUser* request) @@ -180,10 +184,12 @@ static void handle_discord_join_request(const DiscordUser* request) line.label_setting = "no_setting"; line.cb = handle_discord_join_response; - /* To-Do: bespoke dialog, should show while in-game and have a hotkey to accept */ - /* To-Do: show avatar of the user connecting */ + /* To-Do: needs in-game widgets + To-Do: bespoke dialog, should show while in-game and have a hotkey to accept + To-Do: show avatar of the user connecting if (!menu_input_dialog_start(&line)) return; + */ #endif } From c2e3b5bfbf6c7d0f52a4159e97581b10040c48fa Mon Sep 17 00:00:00 2001 From: radius Date: Tue, 13 Nov 2018 21:59:39 -0500 Subject: [PATCH 09/12] [discord] do not toggle menu --- discord/discord.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/discord/discord.c b/discord/discord.c index ad7c94cdb8..7249380380 100644 --- a/discord/discord.c +++ b/discord/discord.c @@ -164,6 +164,7 @@ 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]; RARCH_LOG("[Discord] join request from %s#%s - %s %s\n", request->username, @@ -174,9 +175,10 @@ static void handle_discord_join_request(const DiscordUser* request) discord_download_avatar(request->userId, request->avatar); #ifdef HAVE_MENU - char buf[PATH_MAX_LENGTH]; menu_input_ctx_line_t line; + /* To-Do: needs in-game widgets rarch_menu_running(); + */ memset(&line, 0, sizeof(line)); snprintf(buf, sizeof(buf), "%s %s?", msg_hash_to_str(MSG_DISCORD_CONNECTION_REQUEST), request->username); From 3cedc83bfa25c05f61da1bfbb4676818eea57209 Mon Sep 17 00:00:00 2001 From: radius Date: Mon, 24 Dec 2018 14:28:36 -0500 Subject: [PATCH 10/12] [discord] cleanup, add helper functions --- command.c | 11 +++-------- discord/discord.c | 26 +++++++++++++++++++++----- discord/discord.h | 6 ++++++ 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/command.c b/command.c index 0f0ad1969d..166618703a 100644 --- a/command.c +++ b/command.c @@ -1834,9 +1834,6 @@ void command_playlist_update_write( **/ bool command_event(enum event_command cmd, void *data) { -#ifdef HAVE_DISCORD - static bool discord_inited = false; -#endif bool boolean = false; switch (cmd) @@ -2986,26 +2983,24 @@ TODO: Add a setting for these tweaks */ if (!settings->bools.discord_enable) return false; - if (discord_inited) + if (discord_is_ready()) return true; discord_init(); - discord_inited = true; } #endif break; case CMD_EVENT_DISCORD_DEINIT: #ifdef HAVE_DISCORD - if (!discord_inited) + if (!discord_is_ready()) return false; discord_shutdown(); - discord_inited = false; #endif break; case CMD_EVENT_DISCORD_UPDATE: #ifdef HAVE_DISCORD - if (!data || !discord_inited) + if (!data || !discord_is_ready()) return false; { diff --git a/discord/discord.c b/discord/discord.c index 7249380380..ed6929e479 100644 --- a/discord/discord.c +++ b/discord/discord.c @@ -49,8 +49,6 @@ #include #include "../file_path_special.h" -static int FrustrationLevel = 0; - static int64_t start_time = 0; static int64_t pause_time = 0; static int64_t ellapsed_time = 0; @@ -61,16 +59,33 @@ static unsigned discord_status = 0; struct netplay_room *room; static char user_id[128]; +static char user_name[128]; +static char avatar_path[PATH_MAX_LENGTH]; static char cdn_url[] = "https://cdn.discordapp.com/avatars"; DiscordRichPresence discord_presence; +char* discord_get_own_username(void) +{ + return user_name; +} + +char* discord_get_own_avatar(void) +{ + return avatar_path; +} + +bool discord_is_ready() +{ + return discord_ready; +} + 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 fullpath[PATH_MAX_LENGTH]; + static char avatar_path[PATH_MAX_LENGTH]; static char buf[PATH_MAX_LENGTH]; @@ -79,9 +94,9 @@ static bool discord_download_avatar(const char* user_id, const char* avatar_id) fill_pathname_application_special(buf, sizeof(buf), APPLICATION_SPECIAL_DIRECTORY_THUMBNAILS_DISCORD_AVATARS); - fill_pathname_join(fullpath, buf, avatar_id, sizeof(fullpath)); + fill_pathname_join(avatar_path, buf, avatar_id, sizeof(avatar_path)); - if(filestream_exists(fullpath)) + if(filestream_exists(avatar_path)) return true; else { @@ -102,6 +117,7 @@ static bool discord_download_avatar(const char* user_id, const char* avatar_id) static void handle_discord_ready(const DiscordUser* connectedUser) { + strlcpy(user_name, connectedUser->username, sizeof(user_name)); RARCH_LOG("[Discord] connected to user: %s#%s - avatar id: %s\n", connectedUser->username, connectedUser->discriminator, diff --git a/discord/discord.h b/discord/discord.h index 8148482d54..271fcec38c 100644 --- a/discord/discord.h +++ b/discord/discord.h @@ -55,4 +55,10 @@ void discord_update(enum discord_presence presence); void discord_run_callbacks(); +bool discord_is_ready(); + +char* discord_get_own_username(void); + +char* discord_get_own_avatar(void); + #endif /* __RARCH_DISCORD_H */ From 9aa7b2bd62670eca559d789725c69524d7981204 Mon Sep 17 00:00:00 2001 From: radius Date: Mon, 24 Dec 2018 15:06:21 -0500 Subject: [PATCH 11/12] [discord] add user avatar to ozone --- discord/discord.c | 24 ++++++++++---- discord/discord.h | 4 +++ menu/cbs/menu_cbs_ok.c | 8 +++++ menu/drivers/ozone/ozone.c | 52 +++++++++++++++++++++++++++--- menu/drivers/ozone/ozone_texture.h | 4 ++- 5 files changed, 80 insertions(+), 12 deletions(-) diff --git a/discord/discord.c b/discord/discord.c index ed6929e479..5ec7854752 100644 --- a/discord/discord.c +++ b/discord/discord.c @@ -54,13 +54,14 @@ static int64_t pause_time = 0; static int64_t ellapsed_time = 0; static bool discord_ready = false; +static bool discord_avatar_ready = false; static unsigned discord_status = 0; struct netplay_room *room; static char user_id[128]; static char user_name[128]; -static char avatar_path[PATH_MAX_LENGTH]; +static char user_avatar[PATH_MAX_LENGTH]; static char cdn_url[] = "https://cdn.discordapp.com/avatars"; @@ -73,7 +74,17 @@ char* discord_get_own_username(void) char* discord_get_own_avatar(void) { - return avatar_path; + return user_avatar; +} + +bool discord_avatar_is_ready() +{ + return discord_avatar_ready; +} + +void discord_avatar_set_ready(bool ready) +{ + discord_avatar_ready = ready; } bool discord_is_ready() @@ -85,7 +96,7 @@ 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 avatar_path[PATH_MAX_LENGTH]; + static char full_path[PATH_MAX_LENGTH]; static char buf[PATH_MAX_LENGTH]; @@ -94,9 +105,10 @@ static bool discord_download_avatar(const char* user_id, const char* avatar_id) fill_pathname_application_special(buf, sizeof(buf), APPLICATION_SPECIAL_DIRECTORY_THUMBNAILS_DISCORD_AVATARS); - fill_pathname_join(avatar_path, buf, avatar_id, sizeof(avatar_path)); + fill_pathname_join(full_path, buf, avatar_id, sizeof(full_path)); + strlcpy(user_avatar, avatar_id, sizeof(user_avatar)); - if(filestream_exists(avatar_path)) + if(filestream_exists(full_path)) return true; else { @@ -342,7 +354,7 @@ void discord_init(void) RARCH_LOG("[Discord] registering startup command: %s\n", command); Discord_Register(settings->arrays.discord_app_id, command); - discord_ready = true; + discord_ready = true; } void discord_shutdown(void) diff --git a/discord/discord.h b/discord/discord.h index 271fcec38c..34505dae59 100644 --- a/discord/discord.h +++ b/discord/discord.h @@ -57,6 +57,10 @@ void discord_run_callbacks(); bool discord_is_ready(); +void discord_avatar_set_ready(bool ready); + +bool discord_avatar_is_ready(); + char* discord_get_own_username(void); char* discord_get_own_avatar(void); diff --git a/menu/cbs/menu_cbs_ok.c b/menu/cbs/menu_cbs_ok.c index a077720d30..5966d286b3 100644 --- a/menu/cbs/menu_cbs_ok.c +++ b/menu/cbs/menu_cbs_ok.c @@ -26,6 +26,10 @@ #include "../../config.h" #endif +#ifdef HAVE_DISCORD +#include "../../discord/discord.h" +#endif + #include "../../config.def.h" #include "../../config.def.keybinds.h" #include "../../wifi/wifi_driver.h" @@ -3504,6 +3508,10 @@ finish: RARCH_ERR("Download of '%s' failed: %s\n", (transf ? transf->path: "unknown"), err); } +#ifdef HAVE_DISCORD + else if (transf->enum_idx == MENU_ENUM_LABEL_CB_DISCORD_AVATAR) + discord_avatar_set_ready(true); +#endif if (data) { diff --git a/menu/drivers/ozone/ozone.c b/menu/drivers/ozone/ozone.c index fa9fd08a87..8e062ecaa3 100644 --- a/menu/drivers/ozone/ozone.c +++ b/menu/drivers/ozone/ozone.c @@ -23,6 +23,10 @@ #include "ozone_texture.h" #include "ozone_sidebar.h" +#ifdef HAVE_DISCORD +#include "discord/discord.h" +#endif + #include #include #include @@ -348,14 +352,37 @@ static void ozone_context_reset(void *data, bool is_threaded) for (i = 0; i < OZONE_TEXTURE_LAST; i++) { char filename[PATH_MAX_LENGTH]; - strlcpy(filename, OZONE_TEXTURES_FILES[i], sizeof(filename)); +#ifdef HAVE_DISCORD + if (i == OZONE_TEXTURE_DISCORD_OWN_AVATAR) + strlcpy(filename, discord_get_own_avatar(), sizeof(filename)); + else +#endif + strlcpy(filename, OZONE_TEXTURES_FILES[i], sizeof(filename)); + strlcat(filename, ".png", sizeof(filename)); - if (!menu_display_reset_textures_list(filename, ozone->png_path, &ozone->textures[i], TEXTURE_FILTER_MIPMAP_LINEAR)) + +#ifdef HAVE_DISCORD + if (i == OZONE_TEXTURE_DISCORD_OWN_AVATAR && discord_avatar_is_ready()) { - ozone->has_all_assets = false; - RARCH_WARN("[OZONE] Asset missing: %s%s%s\n", ozone->png_path, path_default_slash(), filename); + char buf[PATH_MAX_LENGTH]; + fill_pathname_application_special(buf, + sizeof(buf), + APPLICATION_SPECIAL_DIRECTORY_THUMBNAILS_DISCORD_AVATARS); + if (!menu_display_reset_textures_list(filename, buf, &ozone->textures[i], TEXTURE_FILTER_MIPMAP_LINEAR)) + RARCH_WARN("[OZONE] Asset missing: %s%s%s\n", ozone->png_path, path_default_slash(), filename); } + else + { +#endif + if (!menu_display_reset_textures_list(filename, ozone->png_path, &ozone->textures[i], TEXTURE_FILTER_MIPMAP_LINEAR)) + { + ozone->has_all_assets = false; + RARCH_WARN("[OZONE] Asset missing: %s%s%s\n", ozone->png_path, path_default_slash(), filename); + } +#ifdef HAVE_DISCORD + } +#endif } /* Sidebar textures */ @@ -930,7 +957,12 @@ static void ozone_draw_header(ozone_handle_t *ozone, video_frame_info_t *video_i /* Icon */ menu_display_blend_begin(video_info); - ozone_draw_icon(video_info, 60, 60, ozone->textures[OZONE_TEXTURE_RETROARCH], 47, 14, video_info->width, video_info->height, 0, 1, ozone->theme->entries_icon); +#ifdef HAVE_DISCORD + if (discord_avatar_is_ready()) + ozone_draw_icon(video_info, 60, 60, ozone->textures[OZONE_TEXTURE_DISCORD_OWN_AVATAR], 47, 14, video_info->width, video_info->height, 0, 1, ozone->theme->entries_icon); + else +#endif + ozone_draw_icon(video_info, 60, 60, ozone->textures[OZONE_TEXTURE_RETROARCH], 47, 14, video_info->width, video_info->height, 0, 1, ozone->theme->entries_icon); menu_display_blend_end(video_info); /* Battery */ @@ -1117,6 +1149,16 @@ static void ozone_frame(void *data, video_frame_info_t *video_info) bool draw_osk = menu_input_dialog_get_display_kb(); static bool draw_osk_old = false; +#ifdef HAVE_DISCORD + static bool reset = false; + + if (discord_avatar_is_ready() && !reset) + { + ozone_context_reset(data, false); + reset = true; + } +#endif + menu_animation_ctx_entry_t entry; if (!ozone) diff --git a/menu/drivers/ozone/ozone_texture.h b/menu/drivers/ozone/ozone_texture.h index 5441732378..e049b0ef4a 100644 --- a/menu/drivers/ozone/ozone_texture.h +++ b/menu/drivers/ozone/ozone_texture.h @@ -28,7 +28,9 @@ enum OZONE_TEXTURE { OZONE_TEXTURE_RETROARCH = 0, OZONE_TEXTURE_CURSOR_BORDER, - +#ifdef HAVE_DISCORD + OZONE_TEXTURE_DISCORD_OWN_AVATAR, +#endif OZONE_TEXTURE_LAST }; From ec53480a660090465e11bc864836226b7c50e5f6 Mon Sep 17 00:00:00 2001 From: radius Date: Sat, 5 Jan 2019 15:11:58 -0500 Subject: [PATCH 12/12] [discord] let's rewrite this to do the same but different --- discord/discord.c | 2 +- retroarch.c | 14 ++++++++++---- retroarch.h | 5 ++--- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/discord/discord.c b/discord/discord.c index 5ec7854752..9d534dd1d9 100644 --- a/discord/discord.c +++ b/discord/discord.c @@ -350,7 +350,7 @@ void discord_init(void) Discord_Initialize(settings->arrays.discord_app_id, &handlers, 0, NULL); char command[PATH_MAX_LENGTH]; - strlcpy(command, _argv, sizeof(command)); + strlcpy(command, get_retroarch_launch_arguments(), sizeof(command)); RARCH_LOG("[Discord] registering startup command: %s\n", command); Discord_Register(settings->arrays.discord_app_id, command); diff --git a/retroarch.c b/retroarch.c index 3a3cf88daf..0092dac65d 100644 --- a/retroarch.c +++ b/retroarch.c @@ -276,6 +276,8 @@ static retro_time_t frame_limit_last_time = 0.0; extern bool input_driver_flushing_input; +static char launch_arguments[4096]; + #ifdef HAVE_DYNAMIC bool retroarch_core_set_on_cmdline(void) { @@ -641,11 +643,10 @@ static void retroarch_parse_input_and_config(int argc, char *argv[]) char buf[4096]; for (unsigned i = 0; i < argc; i++) { - snprintf(buf, sizeof(buf), "%s %s", _argv, argv[i]); - strlcpy(_argv, buf, sizeof(_argv)); + snprintf(buf, sizeof(buf), "%s %s", launch_arguments, argv[i]); + strlcpy(launch_arguments, buf, sizeof(launch_arguments)); } - _argc = argc; - string_trim_whitespace_left(_argv); + string_trim_whitespace_left(launch_arguments); const struct option opts[] = { #ifdef HAVE_DYNAMIC @@ -3663,3 +3664,8 @@ struct retro_system_info *runloop_get_libretro_system_info(void) struct retro_system_info *system = &runloop_system.info; return system; } + +char *get_retroarch_launch_arguments(void) +{ + return launch_arguments; +} \ No newline at end of file diff --git a/retroarch.h b/retroarch.h index ab4f62119d..86b5d24920 100644 --- a/retroarch.h +++ b/retroarch.h @@ -305,9 +305,6 @@ typedef struct global #endif } global_t; -unsigned _argc; -char _argv[4096]; - bool rarch_ctl(enum rarch_ctl_state state, void *data); int retroarch_get_capabilities(enum rarch_capabilities type, @@ -394,6 +391,8 @@ void rarch_menu_running_finished(void); bool retroarch_is_on_main_thread(void); +char *get_retroarch_launch_arguments(void); + rarch_system_info_t *runloop_get_system_info(void); struct retro_system_info *runloop_get_libretro_system_info(void);