2018-05-27 04:24:13 +00:00
|
|
|
// Copyright 2018 Dolphin Emulator Project
|
2021-07-05 01:22:19 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
2018-05-27 04:24:13 +00:00
|
|
|
|
2018-08-06 21:56:40 +00:00
|
|
|
#include "UICommon/DiscordPresence.h"
|
|
|
|
|
2018-07-03 21:50:08 +00:00
|
|
|
#include "Core/Config/NetplaySettings.h"
|
2018-06-08 20:56:11 +00:00
|
|
|
#include "Core/Config/UISettings.h"
|
|
|
|
#include "Core/ConfigManager.h"
|
|
|
|
|
|
|
|
#ifdef USE_DISCORD_PRESENCE
|
2018-05-27 04:24:13 +00:00
|
|
|
|
2018-08-19 12:43:39 +00:00
|
|
|
#include <algorithm>
|
2018-05-27 04:24:13 +00:00
|
|
|
#include <ctime>
|
2019-11-24 00:15:52 +00:00
|
|
|
#include <set>
|
2018-08-19 12:43:39 +00:00
|
|
|
#include <string>
|
2018-05-27 04:24:13 +00:00
|
|
|
|
2020-04-29 09:45:59 +00:00
|
|
|
#include <discord_rpc.h>
|
2019-11-24 00:15:52 +00:00
|
|
|
#include <fmt/format.h>
|
|
|
|
|
|
|
|
#include "Common/Hash.h"
|
2023-04-12 02:08:14 +00:00
|
|
|
#include "Common/HttpRequest.h"
|
2022-01-17 00:38:11 +00:00
|
|
|
#include "Common/StringUtil.h"
|
2019-11-24 00:15:52 +00:00
|
|
|
|
2024-02-24 20:57:06 +00:00
|
|
|
#include "Core/AchievementManager.h"
|
|
|
|
#include "Core/Config/AchievementSettings.h"
|
2024-01-31 01:56:56 +00:00
|
|
|
#include "Core/System.h"
|
|
|
|
|
2018-05-27 04:24:13 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
namespace Discord
|
|
|
|
{
|
2023-05-23 04:07:06 +00:00
|
|
|
#ifdef USE_DISCORD_PRESENCE
|
2023-02-02 22:48:14 +00:00
|
|
|
static bool s_using_custom_client = false;
|
|
|
|
|
2018-08-19 12:43:39 +00:00
|
|
|
namespace
|
|
|
|
{
|
|
|
|
Handler* event_handler = nullptr;
|
|
|
|
const char* username = "";
|
2024-02-24 20:57:06 +00:00
|
|
|
static int64_t s_start_timestamp = std::chrono::duration_cast<std::chrono::seconds>(
|
|
|
|
std::chrono::system_clock::now().time_since_epoch())
|
|
|
|
.count();
|
2018-07-03 21:50:08 +00:00
|
|
|
|
2018-08-19 12:43:39 +00:00
|
|
|
void HandleDiscordReady(const DiscordUser* user)
|
2018-07-03 21:50:08 +00:00
|
|
|
{
|
|
|
|
username = user->username;
|
|
|
|
}
|
|
|
|
|
2018-08-19 12:43:39 +00:00
|
|
|
void HandleDiscordJoinRequest(const DiscordUser* user)
|
2018-07-20 22:27:43 +00:00
|
|
|
{
|
|
|
|
if (event_handler == nullptr)
|
|
|
|
return;
|
|
|
|
|
2019-11-24 00:15:52 +00:00
|
|
|
const std::string discord_tag = fmt::format("{}#{}", user->username, user->discriminator);
|
2018-07-20 22:27:43 +00:00
|
|
|
event_handler->DiscordJoinRequest(user->userId, discord_tag, user->avatar);
|
|
|
|
}
|
|
|
|
|
2018-08-19 12:43:39 +00:00
|
|
|
void HandleDiscordJoin(const char* join_secret)
|
2018-07-03 21:50:08 +00:00
|
|
|
{
|
2018-07-20 22:27:43 +00:00
|
|
|
if (event_handler == nullptr)
|
2018-07-03 21:50:08 +00:00
|
|
|
return;
|
|
|
|
|
2020-09-20 11:58:17 +00:00
|
|
|
if (Config::Get(Config::NETPLAY_NICKNAME) == Config::NETPLAY_NICKNAME.GetDefaultValue())
|
2018-08-06 21:56:40 +00:00
|
|
|
Config::SetCurrent(Config::NETPLAY_NICKNAME, username);
|
2018-07-03 21:50:08 +00:00
|
|
|
|
|
|
|
std::string secret(join_secret);
|
|
|
|
|
2018-07-20 22:27:43 +00:00
|
|
|
std::string type = secret.substr(0, secret.find('\n'));
|
|
|
|
size_t offset = type.length() + 1;
|
2018-07-03 21:50:08 +00:00
|
|
|
|
|
|
|
switch (static_cast<SecretType>(std::stol(type)))
|
|
|
|
{
|
|
|
|
default:
|
|
|
|
case SecretType::Empty:
|
|
|
|
return;
|
|
|
|
|
|
|
|
case SecretType::IPAddress:
|
|
|
|
{
|
2018-08-06 21:56:40 +00:00
|
|
|
// SetBaseOrCurrent will save the ip address, which isn't what's wanted in this situation
|
|
|
|
Config::SetCurrent(Config::NETPLAY_TRAVERSAL_CHOICE, "direct");
|
2018-07-03 21:50:08 +00:00
|
|
|
|
|
|
|
std::string host = secret.substr(offset, secret.find_last_of(':') - offset);
|
2018-08-06 21:56:40 +00:00
|
|
|
Config::SetCurrent(Config::NETPLAY_ADDRESS, host);
|
2018-07-03 21:50:08 +00:00
|
|
|
|
|
|
|
offset += host.length();
|
|
|
|
if (secret[offset] == ':')
|
2018-08-06 21:56:40 +00:00
|
|
|
Config::SetCurrent(Config::NETPLAY_CONNECT_PORT, std::stoul(secret.substr(offset + 1)));
|
2018-07-03 21:50:08 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SecretType::RoomID:
|
|
|
|
{
|
2018-08-06 21:56:40 +00:00
|
|
|
Config::SetCurrent(Config::NETPLAY_TRAVERSAL_CHOICE, "traversal");
|
2018-07-03 21:50:08 +00:00
|
|
|
|
2018-08-06 21:56:40 +00:00
|
|
|
Config::SetCurrent(Config::NETPLAY_HOST_CODE, secret.substr(offset));
|
2018-07-03 21:50:08 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2018-07-20 22:27:43 +00:00
|
|
|
event_handler->DiscordJoin();
|
2018-07-03 21:50:08 +00:00
|
|
|
}
|
2018-08-19 12:43:39 +00:00
|
|
|
|
2023-04-12 02:08:14 +00:00
|
|
|
std::string ArtworkForGameId()
|
2018-08-19 12:43:39 +00:00
|
|
|
{
|
2023-04-12 02:08:14 +00:00
|
|
|
const DiscIO::Region region = SConfig::GetInstance().m_region;
|
2024-01-31 01:56:56 +00:00
|
|
|
const bool is_wii = Core::System::GetInstance().IsWii();
|
2023-04-12 02:08:14 +00:00
|
|
|
const std::string region_code = SConfig::GetInstance().GetGameTDBImageRegionCode(is_wii, region);
|
|
|
|
|
|
|
|
static constexpr char cover_url[] = "https://discord.dolphin-emu.org/cover-art/{}/{}.png";
|
|
|
|
return fmt::format(cover_url, region_code, SConfig::GetInstance().GetGameTDBID());
|
2018-08-19 12:43:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace
|
2018-07-03 21:50:08 +00:00
|
|
|
#endif
|
|
|
|
|
2018-07-20 22:27:43 +00:00
|
|
|
Discord::Handler::~Handler() = default;
|
|
|
|
|
2018-05-27 04:24:13 +00:00
|
|
|
void Init()
|
|
|
|
{
|
|
|
|
#ifdef USE_DISCORD_PRESENCE
|
2018-06-08 20:56:11 +00:00
|
|
|
if (!Config::Get(Config::MAIN_USE_DISCORD_PRESENCE))
|
2018-05-30 03:44:20 +00:00
|
|
|
return;
|
|
|
|
|
2018-05-27 04:24:13 +00:00
|
|
|
DiscordEventHandlers handlers = {};
|
2018-07-03 21:50:08 +00:00
|
|
|
|
|
|
|
handlers.ready = HandleDiscordReady;
|
2018-07-20 22:27:43 +00:00
|
|
|
handlers.joinRequest = HandleDiscordJoinRequest;
|
2018-07-03 21:50:08 +00:00
|
|
|
handlers.joinGame = HandleDiscordJoin;
|
2022-08-03 04:46:11 +00:00
|
|
|
Discord_Initialize(DEFAULT_CLIENT_ID.c_str(), &handlers, 1, nullptr);
|
2018-05-27 04:24:13 +00:00
|
|
|
UpdateDiscordPresence();
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2022-08-03 04:46:11 +00:00
|
|
|
void UpdateClientID(const std::string& new_client)
|
|
|
|
{
|
|
|
|
#ifdef USE_DISCORD_PRESENCE
|
|
|
|
if (!Config::Get(Config::MAIN_USE_DISCORD_PRESENCE))
|
|
|
|
return;
|
|
|
|
|
|
|
|
s_using_custom_client = new_client.empty() || new_client.compare(DEFAULT_CLIENT_ID) != 0;
|
|
|
|
|
|
|
|
Shutdown();
|
|
|
|
if (s_using_custom_client)
|
|
|
|
Discord_Initialize(new_client.c_str(), nullptr, 0, nullptr);
|
|
|
|
else // if initialising dolphin's client ID, make sure to restore event handlers
|
|
|
|
Init();
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2018-07-03 21:50:08 +00:00
|
|
|
void CallPendingCallbacks()
|
|
|
|
{
|
|
|
|
#ifdef USE_DISCORD_PRESENCE
|
|
|
|
if (!Config::Get(Config::MAIN_USE_DISCORD_PRESENCE))
|
|
|
|
return;
|
|
|
|
|
|
|
|
Discord_RunCallbacks();
|
|
|
|
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2018-07-20 22:27:43 +00:00
|
|
|
void InitNetPlayFunctionality(Handler& handler)
|
2018-07-03 21:50:08 +00:00
|
|
|
{
|
|
|
|
#ifdef USE_DISCORD_PRESENCE
|
2018-07-20 22:27:43 +00:00
|
|
|
event_handler = &handler;
|
2018-07-03 21:50:08 +00:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2022-08-03 04:46:11 +00:00
|
|
|
bool UpdateDiscordPresenceRaw(const std::string& details, const std::string& state,
|
|
|
|
const std::string& large_image_key,
|
|
|
|
const std::string& large_image_text,
|
|
|
|
const std::string& small_image_key,
|
|
|
|
const std::string& small_image_text, const int64_t start_timestamp,
|
|
|
|
const int64_t end_timestamp, const int party_size,
|
|
|
|
const int party_max)
|
|
|
|
{
|
|
|
|
#ifdef USE_DISCORD_PRESENCE
|
|
|
|
if (!Config::Get(Config::MAIN_USE_DISCORD_PRESENCE))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// only /dev/dolphin sets this, don't let homebrew change official client ID raw presence
|
|
|
|
if (!s_using_custom_client)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
DiscordRichPresence discord_presence = {};
|
|
|
|
discord_presence.details = details.c_str();
|
|
|
|
discord_presence.state = state.c_str();
|
|
|
|
discord_presence.largeImageKey = large_image_key.c_str();
|
|
|
|
discord_presence.largeImageText = large_image_text.c_str();
|
|
|
|
discord_presence.smallImageKey = small_image_key.c_str();
|
|
|
|
discord_presence.smallImageText = small_image_text.c_str();
|
|
|
|
discord_presence.startTimestamp = start_timestamp;
|
|
|
|
discord_presence.endTimestamp = end_timestamp;
|
|
|
|
discord_presence.partySize = party_size;
|
|
|
|
discord_presence.partyMax = party_max;
|
|
|
|
Discord_UpdatePresence(&discord_presence);
|
|
|
|
|
|
|
|
return true;
|
2023-01-23 14:39:41 +00:00
|
|
|
#else
|
|
|
|
return false;
|
2022-08-03 04:46:11 +00:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2018-08-06 21:56:40 +00:00
|
|
|
void UpdateDiscordPresence(int party_size, SecretType type, const std::string& secret,
|
2024-02-24 20:57:06 +00:00
|
|
|
const std::string& current_game, bool reset_timer)
|
2018-05-27 04:24:13 +00:00
|
|
|
{
|
|
|
|
#ifdef USE_DISCORD_PRESENCE
|
2018-06-08 20:56:11 +00:00
|
|
|
if (!Config::Get(Config::MAIN_USE_DISCORD_PRESENCE))
|
2018-05-30 03:44:20 +00:00
|
|
|
return;
|
|
|
|
|
2022-08-03 04:46:11 +00:00
|
|
|
// reset the client ID if running homebrew has changed it
|
|
|
|
if (s_using_custom_client)
|
|
|
|
UpdateClientID(DEFAULT_CLIENT_ID);
|
|
|
|
|
2018-07-20 22:27:43 +00:00
|
|
|
const std::string& title =
|
|
|
|
current_game.empty() ? SConfig::GetInstance().GetTitleDescription() : current_game;
|
2023-04-12 02:08:14 +00:00
|
|
|
std::string game_artwork =
|
|
|
|
SConfig::GetInstance().GetGameTDBID().empty() ? "" : ArtworkForGameId();
|
2018-05-27 04:24:13 +00:00
|
|
|
|
|
|
|
DiscordRichPresence discord_presence = {};
|
2018-08-19 12:43:39 +00:00
|
|
|
if (game_artwork.empty())
|
|
|
|
{
|
|
|
|
discord_presence.largeImageKey = "dolphin_logo";
|
|
|
|
discord_presence.largeImageText = "Dolphin is an emulator for the GameCube and the Wii.";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
discord_presence.largeImageKey = game_artwork.c_str();
|
|
|
|
discord_presence.largeImageText = title.c_str();
|
|
|
|
discord_presence.smallImageKey = "dolphin_logo";
|
|
|
|
discord_presence.smallImageText = "Dolphin is an emulator for the GameCube and the Wii.";
|
|
|
|
}
|
2018-05-27 04:24:13 +00:00
|
|
|
discord_presence.details = title.empty() ? "Not in-game" : title.c_str();
|
2024-02-24 20:57:06 +00:00
|
|
|
if (reset_timer)
|
|
|
|
{
|
|
|
|
s_start_timestamp = std::chrono::duration_cast<std::chrono::seconds>(
|
|
|
|
std::chrono::system_clock::now().time_since_epoch())
|
|
|
|
.count();
|
|
|
|
}
|
|
|
|
discord_presence.startTimestamp = s_start_timestamp;
|
2018-07-03 21:50:08 +00:00
|
|
|
|
2024-02-24 20:57:06 +00:00
|
|
|
#ifdef USE_RETRO_ACHIEVEMENTS
|
|
|
|
std::string state_string;
|
|
|
|
#endif // USE_RETRO_ACHIEVEMENTS
|
2018-08-06 21:56:40 +00:00
|
|
|
if (party_size > 0)
|
2018-07-03 21:50:08 +00:00
|
|
|
{
|
|
|
|
if (party_size < 4)
|
|
|
|
{
|
|
|
|
discord_presence.state = "In a party";
|
|
|
|
discord_presence.partySize = party_size;
|
|
|
|
discord_presence.partyMax = 4;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// others can still join to spectate
|
|
|
|
discord_presence.state = "In a full party";
|
|
|
|
discord_presence.partySize = party_size;
|
|
|
|
// Note: joining still works without partyMax
|
|
|
|
}
|
|
|
|
}
|
2024-02-24 20:57:06 +00:00
|
|
|
#ifdef USE_RETRO_ACHIEVEMENTS
|
|
|
|
else if (Config::Get(Config::RA_ENABLED) && Config::Get(Config::RA_DISCORD_PRESENCE_ENABLED))
|
|
|
|
{
|
|
|
|
state_string = AchievementManager::GetInstance().GetRichPresence().data();
|
|
|
|
if (state_string.length() >= 128)
|
|
|
|
{
|
|
|
|
// 124 characters + 3 dots + null terminator - thanks to Stenzek for format
|
|
|
|
state_string.resize(124);
|
|
|
|
state_string += "...";
|
|
|
|
}
|
|
|
|
discord_presence.state = state_string.c_str();
|
|
|
|
}
|
|
|
|
#endif // USE_RETRO_ACHIEVEMENTS
|
2018-07-03 21:50:08 +00:00
|
|
|
|
2018-07-20 22:27:43 +00:00
|
|
|
std::string party_id;
|
2018-07-03 21:50:08 +00:00
|
|
|
std::string secret_final;
|
|
|
|
if (type != SecretType::Empty)
|
|
|
|
{
|
2018-07-20 22:27:43 +00:00
|
|
|
// Declearing party_id or secret_final here will deallocate the variable before passing the
|
2018-07-03 21:50:08 +00:00
|
|
|
// values over to Discord_UpdatePresence.
|
|
|
|
|
|
|
|
const size_t secret_length = secret.length();
|
2018-07-20 22:27:43 +00:00
|
|
|
party_id = std::to_string(
|
2018-07-03 21:50:08 +00:00
|
|
|
Common::HashAdler32(reinterpret_cast<const u8*>(secret.c_str()), secret_length));
|
|
|
|
|
|
|
|
const std::string secret_type = std::to_string(static_cast<int>(type));
|
|
|
|
secret_final.reserve(secret_type.length() + 1 + secret_length);
|
|
|
|
secret_final += secret_type;
|
|
|
|
secret_final += '\n';
|
|
|
|
secret_final += secret;
|
|
|
|
}
|
2018-07-20 22:27:43 +00:00
|
|
|
discord_presence.partyId = party_id.c_str();
|
2018-07-03 21:50:08 +00:00
|
|
|
discord_presence.joinSecret = secret_final.c_str();
|
|
|
|
|
2018-05-27 04:24:13 +00:00
|
|
|
Discord_UpdatePresence(&discord_presence);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2018-07-20 22:27:43 +00:00
|
|
|
std::string CreateSecretFromIPAddress(const std::string& ip_address, int port)
|
|
|
|
{
|
|
|
|
const std::string port_string = std::to_string(port);
|
|
|
|
std::string secret;
|
|
|
|
secret.reserve(ip_address.length() + 1 + port_string.length());
|
|
|
|
secret += ip_address;
|
|
|
|
secret += ':';
|
|
|
|
secret += port_string;
|
|
|
|
return secret;
|
|
|
|
}
|
|
|
|
|
2018-05-27 04:24:13 +00:00
|
|
|
void Shutdown()
|
|
|
|
{
|
|
|
|
#ifdef USE_DISCORD_PRESENCE
|
2018-06-08 20:56:11 +00:00
|
|
|
if (!Config::Get(Config::MAIN_USE_DISCORD_PRESENCE))
|
2018-05-30 03:44:20 +00:00
|
|
|
return;
|
|
|
|
|
2018-06-01 02:54:15 +00:00
|
|
|
Discord_ClearPresence();
|
2018-05-27 04:24:13 +00:00
|
|
|
Discord_Shutdown();
|
|
|
|
#endif
|
|
|
|
}
|
2018-06-06 04:16:42 +00:00
|
|
|
|
|
|
|
void SetDiscordPresenceEnabled(bool enabled)
|
|
|
|
{
|
2018-06-08 20:56:11 +00:00
|
|
|
if (Config::Get(Config::MAIN_USE_DISCORD_PRESENCE) == enabled)
|
2018-06-06 04:16:42 +00:00
|
|
|
return;
|
|
|
|
|
2018-06-08 20:56:11 +00:00
|
|
|
if (Config::Get(Config::MAIN_USE_DISCORD_PRESENCE))
|
2018-06-06 04:16:42 +00:00
|
|
|
Discord::Shutdown();
|
|
|
|
|
2018-06-08 20:56:11 +00:00
|
|
|
Config::SetBase(Config::MAIN_USE_DISCORD_PRESENCE, enabled);
|
2018-06-06 04:16:42 +00:00
|
|
|
|
2018-06-08 20:56:11 +00:00
|
|
|
if (Config::Get(Config::MAIN_USE_DISCORD_PRESENCE))
|
2018-06-06 04:16:42 +00:00
|
|
|
Discord::Init();
|
|
|
|
}
|
|
|
|
|
2018-05-27 04:24:13 +00:00
|
|
|
} // namespace Discord
|