diff --git a/.github/workflows/release-binaries.yml b/.github/workflows/release-binaries.yml index 2a4e092f75..5d8aeb4c34 100644 --- a/.github/workflows/release-binaries.yml +++ b/.github/workflows/release-binaries.yml @@ -279,7 +279,7 @@ jobs: -DYUZU_USE_BUNDLED_QT=OFF -DYUZU_USE_BUNDLED_SDL3=ON -DENABLE_UPDATE_CHECKER=OFF - -DUSE_DISCORD_PRESENCE=OFF + -DUSE_DISCORD_PRESENCE=ON -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp index 4b1168a59b..6695d2e58b 100644 --- a/src/core/loader/deconstructed_rom_directory.cpp +++ b/src/core/loader/deconstructed_rom_directory.cpp @@ -15,6 +15,7 @@ #include "common/logging.h" #include "common/random.h" #include "common/settings.h" +#include "common/string_util.h" #include "core/core.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/control_metadata.h" @@ -264,6 +265,9 @@ AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys if (nacp_file != nullptr) { control.emplace(nacp_file); name = control->GetApplicationName(); + } else if (sidecar_dirs.size() > 1 && Common::ToLower(file_dir->GetName()) == "exefs") { + // Without control data, name the game after its folder like the game list does + name = sidecar_dirs.back()->GetName(); } romfs_exefs_dir = file_dir; @@ -522,3 +526,4 @@ ResultStatus AppLoader_DeconstructedRomDirectory::ReadNSOModules(Modules& out_mo } } // namespace Loader + diff --git a/src/qt_common/discord/discord_impl.cpp b/src/qt_common/discord/discord_impl.cpp index 7cf1216d88..c4d75c6dd2 100644 --- a/src/qt_common/discord/discord_impl.cpp +++ b/src/qt_common/discord/discord_impl.cpp @@ -5,6 +5,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include #include @@ -17,6 +18,8 @@ #include "common/common_types.h" #include "common/string_util.h" #include "core/core.h" +#include "core/file_sys/control_metadata.h" +#include "core/file_sys/patch_manager.h" #include "core/loader/loader.h" #include "discord_impl.h" @@ -27,11 +30,37 @@ namespace DiscordRPC { +namespace { + +// Community-maintained names and covers, keyed by title ID: .txt and .png +constexpr char COVERS_HOST[] = "raw.githubusercontent.com"; +constexpr char COVERS_PATH[] = "/mushymeow/eglantine-covers/main/"; + +std::optional FetchFromCovers(const std::string& file_name, bool want_body) { + httplib::SSLClient client(COVERS_HOST); + client.set_connection_timeout(3); + client.set_read_timeout(3); + client.set_follow_location(true); + +#ifdef YUZU_BUNDLED_OPENSSL + client.load_ca_cert_store(kCert, sizeof(kCert)); +#endif + + const std::string path = std::string{COVERS_PATH} + file_name; + const auto res = want_body ? client.Get(path) : client.Head(path); + if (!res || res->status != 200) { + return std::nullopt; + } + return res->body; +} + +} // namespace + DiscordImpl::DiscordImpl(Core::System& system_) : system{system_} { DiscordEventHandlers handlers{}; - // The number is the client ID for Eden, it's used for images and the + // The number is the client ID for Eglantine, it's used for images and the // application name - Discord_Initialize("1397286652128264252", &handlers, 1, nullptr); + Discord_Initialize("1566479696022081606", &handlers, 1, nullptr); } DiscordImpl::~DiscordImpl() { @@ -71,8 +100,7 @@ std::string DiscordImpl::GetGameString(const std::string& title) { static constexpr char DEFAULT_DISCORD_TEXT[] = "Eglantine is an emulator for the Nintendo Switch"; static constexpr char DEFAULT_DISCORD_IMAGE[] = - "https://git.eden-emu.dev/eden-emu/eden/raw/branch/master/dist/qt_themes/default/icons/256x256/" - "eden.png"; + "https://raw.githubusercontent.com/Djihads80/Eglantine/refs/heads/nx2/dist/Eglantine.png"; void DiscordImpl::UpdateGameStatus(std::string_view game_url, bool has_boxart) { const std::string url = std::string{has_boxart ? game_url : DEFAULT_DISCORD_IMAGE}; @@ -87,15 +115,41 @@ void DiscordImpl::UpdateGameStatus(std::string_view game_url, bool has_boxart) { presence.smallImageText = DEFAULT_DISCORD_TEXT; presence.state = game_title.c_str(); presence.status_display_type = DiscordStatusDisplayType_State; - presence.details = "Currently in game"; + const std::string details = + game_version.empty() ? "Currently in game" : fmt::format("Version {}", game_version); + presence.details = details.c_str(); presence.startTimestamp = start_time; Discord_UpdatePresence(&presence); } void DiscordImpl::Update() { if (system.IsPoweredOn()) { + game_title.clear(); system.GetAppLoader().ReadTitle(game_title); + // Read the version from the patched metadata so installed updates are reflected + const FileSys::PatchManager pm(system.GetApplicationProcessProgramID(), + system.GetFileSystemController(), + system.GetContentProvider()); + const auto metadata = pm.GetControlMetadata(); + game_version = metadata.first != nullptr ? metadata.first->GetVersionString() : ""; + if (game_version.empty()) { + // Loose dumps without control data can still report a version from their RomFS + system.GetAppLoader().ReadDisplayVersion(game_version); + } + + const auto title_id = fmt::format("{:016X}", system.GetApplicationProcessProgramID()); + if (const auto name = FetchFromCovers(title_id + ".txt", true)) { + if (auto stripped = Common::StripSpaces(*name); !stripped.empty()) { + game_title = std::move(stripped); + } + } + if (FetchFromCovers(title_id + ".png", false)) { + UpdateGameStatus(fmt::format("https://{}{}{}.png", COVERS_HOST, COVERS_PATH, title_id), + true); + return; + } + // Used to format Icon URL for yuzu website game compatibility page std::string icon_name = GetGameString(game_title); auto const game_url = fmt::format( @@ -134,3 +188,4 @@ void DiscordImpl::Update() { Discord_UpdatePresence(&presence); } } // namespace DiscordRPC + diff --git a/src/qt_common/discord/discord_impl.h b/src/qt_common/discord/discord_impl.h index 602e431482..3968c76844 100644 --- a/src/qt_common/discord/discord_impl.h +++ b/src/qt_common/discord/discord_impl.h @@ -29,7 +29,9 @@ class DiscordImpl : public DiscordInterface { void UpdateGameStatus(std::string_view game_url, bool use_default); std::string game_title{}; + std::string game_version{}; Core::System& system; }; } // namespace DiscordRPC +