System: Move Discord presence into its own file

pull/3732/head
Stenzek 5 months ago
parent 71dafa7198
commit cc1775f0be
No known key found for this signature in database

@ -16,6 +16,7 @@
X(Controller) \
X(Core) \
X(CueParser) \
X(DiscordPresence) \
X(DInputSource) \
X(DMA) \
X(DynamicLibrary) \

@ -38,6 +38,8 @@ add_library(core
ddgo_controller.h
digital_controller.cpp
digital_controller.h
discord_presence.cpp
discord_presence.h
dma.cpp
dma.h
fullscreenui.cpp

@ -10,6 +10,7 @@
#include "cheats.h"
#include "core.h"
#include "cpu_core.h"
#include "discord_presence.h"
#include "fullscreenui.h"
#include "fullscreenui_private.h"
#include "game_list.h"
@ -1098,9 +1099,9 @@ void Achievements::UpdateRichPresence(std::unique_lock<std::recursive_mutex>& lo
INFO_LOG("Rich presence updated: {}", s_state.rich_presence_string);
lock.unlock();
System::UpdateRichPresence(false);
lock.lock();
#ifdef ENABLE_DISCORD_PRESENCE
DiscordPresence::UpdateDetails(GetCurrentGameBadgeURL(), s_state.rich_presence_string);
#endif
}
void Achievements::OnSystemStarting(CDImage* image, bool disable_hardcore_mode)
@ -1337,6 +1338,10 @@ void Achievements::ClientLoadGameCallback(int result, const char* error_message,
// update progress database on first load, in case it was played on another PC
UpdateGameSummary(true);
#ifdef ENABLE_DISCORD_PRESENCE
DiscordPresence::UpdateDetails(s_state.game_badge_url, s_state.rich_presence_string);
#endif
// Defer starting the prefetch, because otherwise when loading state we'll block until it's all downloaded.
// TODO: This can be removed once we're counting requests.
if (g_settings.achievements_prefetch_badges)
@ -1355,6 +1360,10 @@ void Achievements::ClientLoadGameCallback(int result, const char* error_message,
void Achievements::ClearGameInfo()
{
#ifdef ENABLE_DISCORD_PRESENCE
DiscordPresence::UpdateDetails({}, {});
#endif
FullscreenUI::ClearAchievementsState();
s_state.active_leaderboard_trackers = {};

@ -31,6 +31,7 @@
<ClCompile Include="cpu_types.cpp" />
<ClCompile Include="ddgo_controller.cpp" />
<ClCompile Include="digital_controller.cpp" />
<ClCompile Include="discord_presence.cpp" />
<ClCompile Include="fullscreenui_achievements.cpp" />
<ClCompile Include="fullscreenui_game_list.cpp" />
<ClCompile Include="fullscreenui_settings.cpp" />
@ -118,6 +119,7 @@
</ClInclude>
<ClInclude Include="ddgo_controller.h" />
<ClInclude Include="digital_controller.h" />
<ClInclude Include="discord_presence.h" />
<ClInclude Include="fullscreenui_private.h" />
<ClInclude Include="fullscreenui_strings.h" />
<ClInclude Include="fullscreenui_widgets.h" />

@ -73,6 +73,7 @@
<ClCompile Include="fullscreenui_achievements.cpp" />
<ClCompile Include="core.cpp" />
<ClCompile Include="sound_effect_manager.cpp" />
<ClCompile Include="discord_presence.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="types.h" />
@ -158,6 +159,7 @@
<ClInclude Include="core_private.h" />
<ClInclude Include="sound_effect_manager.h" />
<ClInclude Include="gpu_helpers.h" />
<ClInclude Include="discord_presence.h" />
</ItemGroup>
<ItemGroup>
<None Include="gpu_sw_rasterizer.inl" />

@ -0,0 +1,196 @@
// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "discord_presence.h"
#ifdef ENABLE_DISCORD_PRESENCE
#include "achievements.h"
#include "game_database.h"
#include "system.h"
#include "common/dynamic_library.h"
#include "common/error.h"
#include "common/log.h"
#include "common/string_util.h"
#include "discord_rpc.h"
#include <ctime>
LOG_CHANNEL(DiscordPresence);
#define DISCORD_RPC_FUNCTIONS(X) \
X(Discord_Initialize) \
X(Discord_Shutdown) \
X(Discord_RunCallbacks) \
X(Discord_UpdatePresence) \
X(Discord_ClearPresence)
namespace DiscordPresence {
static bool OpenDiscordRPC(Error* error);
static void CloseDiscordRPC();
namespace {
struct Locals
{
bool discord_presence_active;
std::time_t session_start_time;
std::string current_state;
std::string current_image_url;
DynamicLibrary rpc_library;
#define ADD_FUNC(F) decltype(&::F) rpc_##F;
DISCORD_RPC_FUNCTIONS(ADD_FUNC)
#undef ADD_FUNC
};
} // namespace
static Locals s_locals;
} // namespace DiscordPresence
bool DiscordPresence::OpenDiscordRPC(Error* error)
{
if (s_locals.rpc_library.IsOpen())
return true;
const std::string libname = DynamicLibrary::GetVersionedFilename("discord-rpc");
if (!s_locals.rpc_library.Open(libname.c_str(), error))
{
Error::AddPrefix(error, "Failed to load discord-rpc: ");
return false;
}
#define LOAD_FUNC(F) \
if (!s_locals.rpc_library.GetSymbol(#F, &s_locals.rpc_##F)) \
{ \
Error::SetStringFmt(error, "Failed to find function {}", #F); \
CloseDiscordRPC(); \
return false; \
}
DISCORD_RPC_FUNCTIONS(LOAD_FUNC)
#undef LOAD_FUNC
return true;
}
void DiscordPresence::CloseDiscordRPC()
{
if (!s_locals.rpc_library.IsOpen())
return;
#define UNLOAD_FUNC(F) s_locals.rpc_##F = nullptr;
DISCORD_RPC_FUNCTIONS(UNLOAD_FUNC)
#undef UNLOAD_FUNC
s_locals.rpc_library.Close();
}
bool DiscordPresence::Initialize()
{
if (s_locals.discord_presence_active)
return true;
Error error;
if (!OpenDiscordRPC(&error))
{
ERROR_LOG("Failed to open discord-rpc: {}", error.GetDescription());
return false;
}
DiscordEventHandlers handlers = {};
s_locals.rpc_Discord_Initialize("705325712680288296", &handlers, 0, nullptr);
s_locals.discord_presence_active = true;
s_locals.session_start_time = std::time(nullptr);
INFO_LOG("Discord Rich Presence initialized successfully");
if (const auto lock = Achievements::GetLock(); Achievements::HasActiveGame())
UpdateDetails(Achievements::GetCurrentGameBadgeURL(), Achievements::GetRichPresenceString());
Update(true);
return true;
}
void DiscordPresence::Shutdown()
{
if (!s_locals.discord_presence_active)
return;
INFO_LOG("Shutting down Discord Rich Presence...");
s_locals.discord_presence_active = false;
s_locals.rpc_Discord_ClearPresence();
s_locals.rpc_Discord_Shutdown();
CloseDiscordRPC();
}
void DiscordPresence::Poll()
{
if (!s_locals.discord_presence_active)
return;
s_locals.rpc_Discord_RunCallbacks();
}
void DiscordPresence::Update(bool is_new_session)
{
if (!s_locals.discord_presence_active)
return;
if (is_new_session)
s_locals.session_start_time = std::time(nullptr);
// https://discord.com/developers/docs/rich-presence/how-to#updating-presence-update-presence-payload-fields
DiscordRichPresence rp = {};
rp.largeImageKey = s_locals.current_image_url.empty() ? "duckstation_logo" : s_locals.current_image_url.c_str();
rp.largeImageText = "DuckStation PS1/PSX Emulator";
rp.startTimestamp = s_locals.session_start_time;
TinyString game_details("No Game Running");
if (System::IsValidOrInitializing())
{
// Use disc set name if it's not a custom title.
const bool custom_title = System::IsRunningGameCustomTitle();
const GameDatabase::Entry* game_entry = System::GetGameDatabaseEntry();
if (!custom_title && game_entry && game_entry->disc_set)
{
game_details = game_entry->disc_set->GetDisplayTitle(true);
}
else
{
if (const std::string& game_title = System::GetGameTitle(); !game_title.empty())
game_details = game_title;
else
game_details = "Unknown Game";
}
}
rp.details = game_details.c_str();
rp.state = s_locals.current_state.empty() ? nullptr : s_locals.current_state.c_str();
INFO_LOG("Updating: '{}' state='{}', badge='{}'", game_details, s_locals.current_state, rp.largeImageKey);
s_locals.rpc_Discord_UpdatePresence(&rp);
}
void DiscordPresence::UpdateDetails(std::string_view badge_url, std::string_view state)
{
if (badge_url.empty())
s_locals.current_image_url = {};
else if (s_locals.current_image_url != badge_url)
s_locals.current_image_url = badge_url;
if (state.empty())
s_locals.current_state = {};
else
s_locals.current_state = StringUtil::Ellipsise(state, 128);
Update(false);
}
#endif // ENABLE_DISCORD_PRESENCE

@ -0,0 +1,30 @@
// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#pragma once
#include "common/types.h"
#include <string_view>
// Discord presence is available on all platforms except Android.
#ifndef __ANDROID__
#define ENABLE_DISCORD_PRESENCE
#endif
#ifdef ENABLE_DISCORD_PRESENCE
namespace DiscordPresence {
bool Initialize();
void Shutdown();
void Poll();
/// Called when rich presence changes.
void Update(bool is_new_session);
void UpdateDetails(std::string_view badge_url, std::string_view state);
} // namespace DiscordPresence
#endif // ENABLE_DISCORD_PRESENCE

@ -13,6 +13,7 @@
#include "cpu_code_cache.h"
#include "cpu_core.h"
#include "cpu_pgxp.h"
#include "discord_presence.h"
#include "dma.h"
#include "fullscreenui.h"
#include "game_database.h"
@ -61,7 +62,6 @@
#include "common/align.h"
#include "common/binary_reader_writer.h"
#include "common/dynamic_library.h"
#include "common/error.h"
#include "common/file_system.h"
#include "common/layered_settings_interface.h"
@ -99,10 +99,6 @@ LOG_CHANNEL(System);
#include <Objbase.h>
#endif
#ifndef __ANDROID__
#define ENABLE_DISCORD_PRESENCE 1
#endif
// #define PROFILE_MEMORY_SAVE_STATES 1
SystemBootParameters::SystemBootParameters() = default;
@ -235,12 +231,6 @@ static bool DoRunahead();
static void UpdateSessionTime(const std::string& prev_serial);
#ifdef ENABLE_DISCORD_PRESENCE
static void InitializeDiscordPresence();
static void ShutdownDiscordPresence();
static void PollDiscordPresence();
#endif
namespace {
struct ALIGN_TO_CACHE_LINE StateVars
@ -334,11 +324,6 @@ struct ALIGN_TO_CACHE_LINE StateVars
// process start time
Timer::Value process_start_time = 0;
#ifdef ENABLE_DISCORD_PRESENCE
bool discord_presence_active = false;
std::time_t discord_presence_time_epoch;
#endif
};
} // namespace
@ -537,7 +522,7 @@ bool System::CoreThreadInitialize(Error* error)
#ifdef ENABLE_DISCORD_PRESENCE
if (g_settings.enable_discord_presence)
InitializeDiscordPresence();
DiscordPresence::Initialize();
#endif
return true;
@ -546,7 +531,7 @@ bool System::CoreThreadInitialize(Error* error)
void System::CoreThreadShutdown()
{
#ifdef ENABLE_DISCORD_PRESENCE
ShutdownDiscordPresence();
DiscordPresence::Shutdown();
#endif
Achievements::Shutdown();
@ -587,7 +572,7 @@ void System::IdlePollUpdate()
InputManager::PollSources();
#ifdef ENABLE_DISCORD_PRESENCE
PollDiscordPresence();
DiscordPresence::Poll();
#endif
HTTPCache::PollRequests();
@ -802,6 +787,11 @@ GameHash System::GetGameHash()
return s_state.running_game_hash;
}
bool System::IsRunningGameCustomTitle()
{
return s_state.running_game_custom_title;
}
bool System::IsRunningUnknownGame()
{
return !s_state.running_game_entry;
@ -2203,7 +2193,9 @@ void System::ClearRunningGame()
Host::OnSystemGameChanged(s_state.running_game_path, s_state.running_game_serial, s_state.running_game_title,
s_state.running_game_hash);
UpdateRichPresence(true);
#ifdef ENABLE_DISCORD_PRESENCE
DiscordPresence::Update(true);
#endif
}
void System::Execute()
@ -2261,7 +2253,7 @@ void System::FrameDone()
}
#ifdef ENABLE_DISCORD_PRESENCE
PollDiscordPresence();
DiscordPresence::Poll();
#endif
#ifdef ENABLE_GDB_SERVER
@ -4382,7 +4374,9 @@ void System::UpdateRunningGame(const std::string& path, CDImage* image, bool boo
if (s_state.running_game_serial != prev_serial)
UpdateSessionTime(prev_serial);
UpdateRichPresence(booting);
#ifdef ENABLE_DISCORD_PRESENCE
DiscordPresence::Update(booting);
#endif
if (!s_state.running_game_title.empty())
{
@ -4985,9 +4979,9 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
if (g_settings.enable_discord_presence != old_settings.enable_discord_presence)
{
if (g_settings.enable_discord_presence)
InitializeDiscordPresence();
DiscordPresence::Initialize();
else
ShutdownDiscordPresence();
DiscordPresence::Shutdown();
}
#endif
@ -6427,153 +6421,3 @@ u64 System::GetSessionPlayedTime()
const Timer::Value ctime = Timer::GetCurrentValue();
return static_cast<u64>(std::round(Timer::ConvertValueToSeconds(ctime - s_state.session_start_time)));
}
#ifdef ENABLE_DISCORD_PRESENCE
#include "discord_rpc.h"
#define DISCORD_RPC_FUNCTIONS(X) \
X(Discord_Initialize) \
X(Discord_Shutdown) \
X(Discord_RunCallbacks) \
X(Discord_UpdatePresence) \
X(Discord_ClearPresence)
namespace dyn_libs {
static bool OpenDiscordRPC(Error* error);
static void CloseDiscordRPC();
static DynamicLibrary s_discord_rpc_library;
#define ADD_FUNC(F) static decltype(&::F) F;
DISCORD_RPC_FUNCTIONS(ADD_FUNC)
#undef ADD_FUNC
} // namespace dyn_libs
bool dyn_libs::OpenDiscordRPC(Error* error)
{
if (s_discord_rpc_library.IsOpen())
return true;
const std::string libname = DynamicLibrary::GetVersionedFilename("discord-rpc");
if (!s_discord_rpc_library.Open(libname.c_str(), error))
{
Error::AddPrefix(error, "Failed to load discord-rpc: ");
return false;
}
#define LOAD_FUNC(F) \
if (!s_discord_rpc_library.GetSymbol(#F, &F)) \
{ \
Error::SetStringFmt(error, "Failed to find function {}", #F); \
CloseDiscordRPC(); \
return false; \
}
DISCORD_RPC_FUNCTIONS(LOAD_FUNC)
#undef LOAD_FUNC
return true;
}
void dyn_libs::CloseDiscordRPC()
{
if (!s_discord_rpc_library.IsOpen())
return;
#define UNLOAD_FUNC(F) F = nullptr;
DISCORD_RPC_FUNCTIONS(UNLOAD_FUNC)
#undef UNLOAD_FUNC
s_discord_rpc_library.Close();
}
void System::InitializeDiscordPresence()
{
if (s_state.discord_presence_active)
return;
Error error;
if (!dyn_libs::OpenDiscordRPC(&error))
{
ERROR_LOG("Failed to open discord-rpc: {}", error.GetDescription());
return;
}
DiscordEventHandlers handlers = {};
dyn_libs::Discord_Initialize("705325712680288296", &handlers, 0, nullptr);
s_state.discord_presence_active = true;
UpdateRichPresence(true);
}
void System::ShutdownDiscordPresence()
{
if (!s_state.discord_presence_active)
return;
dyn_libs::Discord_ClearPresence();
dyn_libs::Discord_Shutdown();
dyn_libs::CloseDiscordRPC();
s_state.discord_presence_active = false;
}
void System::UpdateRichPresence(bool update_session_time)
{
if (!s_state.discord_presence_active)
return;
if (update_session_time)
s_state.discord_presence_time_epoch = std::time(nullptr);
// https://discord.com/developers/docs/rich-presence/how-to#updating-presence-update-presence-payload-fields
DiscordRichPresence rp = {};
rp.largeImageKey = "duckstation_logo";
rp.largeImageText = "DuckStation PS1/PSX Emulator";
rp.startTimestamp = s_state.discord_presence_time_epoch;
TinyString game_details("No Game Running");
if (IsValidOrInitializing())
{
// Use disc set name if it's not a custom title.
if (!s_state.running_game_custom_title && s_state.running_game_entry && s_state.running_game_entry->disc_set)
{
game_details = s_state.running_game_entry->disc_set->GetDisplayTitle(true);
}
else
{
if (s_state.running_game_title.empty())
game_details = "Unknown Game";
else
game_details = std::string_view(s_state.running_game_title);
}
}
rp.details = game_details.c_str();
const auto lock = Achievements::GetLock();
std::string state_string;
if (Achievements::HasRichPresence())
rp.state = (state_string = StringUtil::Ellipsise(Achievements::GetRichPresenceString(), 128)).c_str();
if (const std::string& badge_url = Achievements::GetCurrentGameBadgeURL(); !badge_url.empty())
rp.largeImageKey = badge_url.c_str();
dyn_libs::Discord_UpdatePresence(&rp);
}
void System::PollDiscordPresence()
{
if (!s_state.discord_presence_active)
return;
dyn_libs::Discord_RunCallbacks();
}
#else
void System::UpdateRichPresence(bool update_session_time)
{
}
#endif

@ -234,6 +234,7 @@ const std::string& GetGamePath();
const std::string& GetExeOverride();
const GameDatabase::Entry* GetGameDatabaseEntry();
GameHash GetGameHash();
bool IsRunningGameCustomTitle();
bool IsRunningUnknownGame();
bool IsUsingKnownPS1BIOS();
BootMode GetBootMode();
@ -462,9 +463,6 @@ void CalculateRewindMemoryUsage(u32 num_saves, u32 resolution_scale, u32 multisa
void ClearMemorySaveStates(bool reallocate_resources, bool recycle_textures);
void SetRunaheadReplayFlag(bool is_analog_input);
/// Called when rich presence changes.
void UpdateRichPresence(bool update_session_time);
} // namespace System
namespace Host {

Loading…
Cancel
Save