diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index 6fab056c79..ebb83ac801 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -20,7 +20,7 @@ #include "Core/Core.h" #include "Core/PowerPC/MMU.h" #include "Core/System.h" -#include "DiscIO/Volume.h" +#include "DiscIO/Blob.h" #include "VideoCommon/OnScreenDisplay.h" #include "VideoCommon/VideoEvents.h" @@ -91,8 +91,7 @@ bool AchievementManager::IsLoggedIn() const return !Config::Get(Config::RA_API_TOKEN).empty(); } -void AchievementManager::LoadGameByFilenameAsync(const std::string& iso_path, - const ResponseCallback& callback) +void AchievementManager::HashGame(const std::string& file_path, const ResponseCallback& callback) { if (!m_is_runtime_initialized) { @@ -101,117 +100,215 @@ void AchievementManager::LoadGameByFilenameAsync(const std::string& iso_path, callback(AchievementManager::ResponseType::MANAGER_NOT_INITIALIZED); return; } + if (m_disabled) + { + INFO_LOG_FMT(ACHIEVEMENTS, "Achievement Manager is disabled until core is rebooted."); + OSD::AddMessage("Achievements are disabled until you restart emulation.", + OSD::Duration::VERY_LONG, OSD::Color::RED); + return; + } + m_system = &Core::System::GetInstance(); + m_queue.EmplaceItem([this, callback, file_path] { + Hash new_hash; + { + std::lock_guard lg{m_filereader_lock}; + rc_hash_filereader volume_reader{ + .open = &AchievementManager::FilereaderOpenByFilepath, + .seek = &AchievementManager::FilereaderSeek, + .tell = &AchievementManager::FilereaderTell, + .read = &AchievementManager::FilereaderRead, + .close = &AchievementManager::FilereaderClose, + }; + rc_hash_init_custom_filereader(&volume_reader); + if (!rc_hash_generate_from_file(new_hash.data(), RC_CONSOLE_GAMECUBE, file_path.c_str())) + { + ERROR_LOG_FMT(ACHIEVEMENTS, "Unable to generate achievement hash from game file {}.", + file_path); + callback(AchievementManager::ResponseType::MALFORMED_OBJECT); + } + } + { + std::lock_guard lg{m_lock}; + if (m_disabled) + { + INFO_LOG_FMT(ACHIEVEMENTS, "Achievements disabled while hash was resolving."); + callback(AchievementManager::ResponseType::EXPIRED_CONTEXT); + return; + } + m_game_hash = std::move(new_hash); + } + LoadGameSync(callback); + }); +} + +void AchievementManager::HashGame(const DiscIO::Volume* volume, const ResponseCallback& callback) +{ + if (!m_is_runtime_initialized) + { + ERROR_LOG_FMT(ACHIEVEMENTS, + "Attempted to load game achievements without Achievement Manager initialized."); + callback(AchievementManager::ResponseType::MANAGER_NOT_INITIALIZED); + return; + } + if (volume == nullptr) + { + INFO_LOG_FMT(ACHIEVEMENTS, "New volume is empty."); + return; + } + if (m_disabled) + { + INFO_LOG_FMT(ACHIEVEMENTS, "Achievement Manager is disabled until core is rebooted."); + OSD::AddMessage("Achievements are disabled until core is rebooted.", OSD::Duration::VERY_LONG, + OSD::Color::RED); + return; + } + // Need to SetDisabled outside a lock because it uses m_lock internally. + bool disable = false; + { + std::lock_guard lg{m_lock}; + if (m_loading_volume.get() != nullptr) + { + disable = true; + } + else + { + m_loading_volume = DiscIO::CreateVolume(volume->GetBlobReader().CopyReader()); + } + } + if (disable) + { + INFO_LOG_FMT(ACHIEVEMENTS, "Disabling Achievement Manager due to hash spam."); + SetDisabled(true); + callback(AchievementManager::ResponseType::EXPIRED_CONTEXT); + return; + } m_system = &Core::System::GetInstance(); struct FilereaderState { int64_t position = 0; std::unique_ptr volume; }; - rc_hash_filereader volume_reader{ - .open = [](const char* path_utf8) -> void* { - auto state = std::make_unique(); - state->volume = DiscIO::CreateVolume(path_utf8); - if (!state->volume) - return nullptr; - return state.release(); - }, - .seek = - [](void* file_handle, int64_t offset, int origin) { - switch (origin) - { - case SEEK_SET: - reinterpret_cast(file_handle)->position = offset; - break; - case SEEK_CUR: - reinterpret_cast(file_handle)->position += offset; - break; - case SEEK_END: - // Unused - break; - } - }, - .tell = - [](void* file_handle) { - return reinterpret_cast(file_handle)->position; - }, - .read = - [](void* file_handle, void* buffer, size_t requested_bytes) { - FilereaderState* filereader_state = reinterpret_cast(file_handle); - bool success = (filereader_state->volume->Read( - filereader_state->position, requested_bytes, reinterpret_cast(buffer), - DiscIO::PARTITION_NONE)); - if (success) - { - filereader_state->position += requested_bytes; - return requested_bytes; - } - else - { - return static_cast(0); - } - }, - .close = [](void* file_handle) { delete reinterpret_cast(file_handle); }}; - rc_hash_init_custom_filereader(&volume_reader); - if (!rc_hash_generate_from_file(m_game_hash.data(), RC_CONSOLE_GAMECUBE, iso_path.c_str())) - { - ERROR_LOG_FMT(ACHIEVEMENTS, "Unable to generate achievement hash from game file."); - return; - } m_queue.EmplaceItem([this, callback] { - const auto resolve_hash_response = ResolveHash(this->m_game_hash); - if (resolve_hash_response != ResponseType::SUCCESS || m_game_id == 0) + Hash new_hash; { - callback(resolve_hash_response); - INFO_LOG_FMT(ACHIEVEMENTS, "No RetroAchievements data found for this game."); - OSD::AddMessage("No RetroAchievements data found for this game.", OSD::Duration::VERY_LONG, - OSD::Color::RED); - return; + std::lock_guard lg{m_filereader_lock}; + rc_hash_filereader volume_reader{ + .open = &AchievementManager::FilereaderOpenByVolume, + .seek = &AchievementManager::FilereaderSeek, + .tell = &AchievementManager::FilereaderTell, + .read = &AchievementManager::FilereaderRead, + .close = &AchievementManager::FilereaderClose, + }; + rc_hash_init_custom_filereader(&volume_reader); + if (!rc_hash_generate_from_file(new_hash.data(), RC_CONSOLE_GAMECUBE, "")) + { + ERROR_LOG_FMT(ACHIEVEMENTS, "Unable to generate achievement hash from volume."); + callback(AchievementManager::ResponseType::MALFORMED_OBJECT); + return; + } } - - const auto start_session_response = StartRASession(); - if (start_session_response != ResponseType::SUCCESS) - { - callback(start_session_response); - WARN_LOG_FMT(ACHIEVEMENTS, "Failed to connect to RetroAchievements server."); - OSD::AddMessage("Failed to connect to RetroAchievements server.", OSD::Duration::VERY_LONG, - OSD::Color::RED); - return; - } - - const auto fetch_game_data_response = FetchGameData(); - if (fetch_game_data_response != ResponseType::SUCCESS) - { - ERROR_LOG_FMT(ACHIEVEMENTS, "Unable to retrieve data from RetroAchievements server."); - OSD::AddMessage("Unable to retrieve data from RetroAchievements server.", - OSD::Duration::VERY_LONG, OSD::Color::RED); - return; - } - INFO_LOG_FMT(ACHIEVEMENTS, "Loading achievements for {}.", m_game_data.title); - - // Claim the lock, then queue the fetch unlock data calls, then initialize the unlock map in - // ActivateDeactiveAchievements. This allows the calls to process while initializing the - // unlock map but then forces them to wait until it's initialized before making modifications to - // it. { std::lock_guard lg{m_lock}; - m_is_game_loaded = true; - m_framecount = 0; - LoadUnlockData([](ResponseType r_type) {}); - ActivateDeactivateAchievements(); - ActivateDeactivateLeaderboards(); - ActivateDeactivateRichPresence(); + if (m_disabled) + { + INFO_LOG_FMT(ACHIEVEMENTS, "Achievements disabled while hash was resolving."); + callback(AchievementManager::ResponseType::EXPIRED_CONTEXT); + return; + } + m_game_hash = std::move(new_hash); + m_loading_volume.reset(); } - FetchBadges(); - // Reset this to zero so that RP immediately triggers on the first frame - m_last_ping_time = 0; - INFO_LOG_FMT(ACHIEVEMENTS, "RetroAchievements successfully loaded for {}.", m_game_data.title); - - if (m_update_callback) - m_update_callback(); - callback(fetch_game_data_response); + LoadGameSync(callback); }); } +void AchievementManager::LoadGameSync(const ResponseCallback& callback) +{ + u32 new_game_id = 0; + Hash current_hash; + { + std::lock_guard lg{m_lock}; + current_hash = m_game_hash; + } + const auto resolve_hash_response = ResolveHash(current_hash, &new_game_id); + if (resolve_hash_response != ResponseType::SUCCESS || new_game_id == 0) + { + INFO_LOG_FMT(ACHIEVEMENTS, "No RetroAchievements data found for this game."); + OSD::AddMessage("No RetroAchievements data found for this game.", OSD::Duration::VERY_LONG, + OSD::Color::RED); + SetDisabled(true); + callback(resolve_hash_response); + return; + } + u32 old_game_id; + { + std::lock_guard lg{m_lock}; + old_game_id = m_game_id; + } + if (new_game_id == old_game_id) + { + INFO_LOG_FMT(ACHIEVEMENTS, "Alternate hash resolved for current game {}.", old_game_id); + callback(ResponseType::SUCCESS); + return; + } + else if (old_game_id != 0) + { + INFO_LOG_FMT(ACHIEVEMENTS, "Swapping game {} for game {}; achievements disabled.", old_game_id, + new_game_id); + OSD::AddMessage("Achievements are now disabled. Please close emulation to re-enable.", + OSD::Duration::VERY_LONG, OSD::Color::RED); + SetDisabled(true); + callback(ResponseType::EXPIRED_CONTEXT); + return; + } + { + std::lock_guard lg{m_lock}; + m_game_id = new_game_id; + } + + const auto start_session_response = StartRASession(); + if (start_session_response != ResponseType::SUCCESS) + { + WARN_LOG_FMT(ACHIEVEMENTS, "Failed to connect to RetroAchievements server."); + OSD::AddMessage("Failed to connect to RetroAchievements server.", OSD::Duration::VERY_LONG, + OSD::Color::RED); + callback(start_session_response); + return; + } + + const auto fetch_game_data_response = FetchGameData(); + if (fetch_game_data_response != ResponseType::SUCCESS) + { + ERROR_LOG_FMT(ACHIEVEMENTS, "Unable to retrieve data from RetroAchievements server."); + OSD::AddMessage("Unable to retrieve data from RetroAchievements server.", + OSD::Duration::VERY_LONG, OSD::Color::RED); + return; + } + INFO_LOG_FMT(ACHIEVEMENTS, "Loading achievements for {}.", m_game_data.title); + + // Claim the lock, then queue the fetch unlock data calls, then initialize the unlock map in + // ActivateDeactiveAchievements. This allows the calls to process while initializing the + // unlock map but then forces them to wait until it's initialized before making modifications to + // it. + { + std::lock_guard lg{m_lock}; + m_is_game_loaded = true; + m_framecount = 0; + LoadUnlockData([](ResponseType r_type) {}); + ActivateDeactivateAchievements(); + ActivateDeactivateLeaderboards(); + ActivateDeactivateRichPresence(); + } + FetchBadges(); + // Reset this to zero so that RP immediately triggers on the first frame + m_last_ping_time = 0; + INFO_LOG_FMT(ACHIEVEMENTS, "RetroAchievements successfully loaded for {}.", m_game_data.title); + + if (m_update_callback) + m_update_callback(); + callback(fetch_game_data_response); +} + bool AchievementManager::IsGameLoaded() const { return m_is_game_loaded; @@ -741,6 +838,25 @@ AchievementManager::RichPresence AchievementManager::GetRichPresence() return rich_presence; } +void AchievementManager::SetDisabled(bool disable) +{ + { + std::lock_guard lg{m_lock}; + m_disabled = disable; + } + if (disable) + { + INFO_LOG_FMT(ACHIEVEMENTS, "Achievement Manager has been disabled."); + OSD::AddMessage("Please close all games to re-enable achievements.", OSD::Duration::VERY_LONG, + OSD::Color::RED); + CloseGame(); + } + else + { + INFO_LOG_FMT(ACHIEVEMENTS, "Achievement Manager has been re-enabled."); + } +}; + void AchievementManager::CloseGame() { { @@ -790,6 +906,69 @@ void AchievementManager::Shutdown() INFO_LOG_FMT(ACHIEVEMENTS, "Achievement Manager shut down."); } +void* AchievementManager::FilereaderOpenByFilepath(const char* path_utf8) +{ + auto state = std::make_unique(); + state->volume = DiscIO::CreateVolume(path_utf8); + if (!state->volume) + return nullptr; + return state.release(); +} + +void* AchievementManager::FilereaderOpenByVolume(const char* path_utf8) +{ + auto state = std::make_unique(); + { + std::lock_guard lg{*AchievementManager::GetInstance()->GetLock()}; + state->volume = std::move(AchievementManager::GetInstance()->GetLoadingVolume()); + } + if (!state->volume) + return nullptr; + return state.release(); +} + +void AchievementManager::FilereaderSeek(void* file_handle, int64_t offset, int origin) +{ + switch (origin) + { + case SEEK_SET: + static_cast(file_handle)->position = offset; + break; + case SEEK_CUR: + static_cast(file_handle)->position += offset; + break; + case SEEK_END: + // Unused + break; + } +} + +int64_t AchievementManager::FilereaderTell(void* file_handle) +{ + return static_cast(file_handle)->position; +} + +size_t AchievementManager::FilereaderRead(void* file_handle, void* buffer, size_t requested_bytes) +{ + FilereaderState* filereader_state = static_cast(file_handle); + bool success = (filereader_state->volume->Read(filereader_state->position, requested_bytes, + static_cast(buffer), DiscIO::PARTITION_NONE)); + if (success) + { + filereader_state->position += requested_bytes; + return requested_bytes; + } + else + { + return 0; + } +} + +void AchievementManager::FilereaderClose(void* file_handle) +{ + delete static_cast(file_handle); +} + AchievementManager::ResponseType AchievementManager::VerifyCredentials(const std::string& password) { rc_api_login_response_t login_data{}; @@ -826,8 +1005,8 @@ AchievementManager::ResponseType AchievementManager::VerifyCredentials(const std return r_type; } -AchievementManager::ResponseType -AchievementManager::ResolveHash(std::array game_hash) +AchievementManager::ResponseType AchievementManager::ResolveHash(const Hash& game_hash, + u32* game_id) { rc_api_resolve_hash_response_t hash_data{}; std::string username, api_token; @@ -843,9 +1022,8 @@ AchievementManager::ResolveHash(std::array game_hash) rc_api_process_resolve_hash_response); if (r_type == ResponseType::SUCCESS) { - std::lock_guard lg{m_lock}; - m_game_id = hash_data.game_id; - INFO_LOG_FMT(ACHIEVEMENTS, "Hashed game ID {} for RetroAchievements.", m_game_id); + *game_id = hash_data.game_id; + INFO_LOG_FMT(ACHIEVEMENTS, "Hashed game ID {} for RetroAchievements.", *game_id); } else { diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index e790e0bc1c..7d93b7d781 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -18,6 +18,7 @@ #include "Common/Event.h" #include "Common/WorkQueueThread.h" +#include "DiscIO/Volume.h" namespace Core { @@ -51,6 +52,8 @@ public: u32 soft_points; }; + static constexpr size_t HASH_SIZE = 33; + using Hash = std::array; using AchievementId = u32; static constexpr size_t FORMAT_SIZE = 24; using FormattedValue = std::array; @@ -105,7 +108,8 @@ public: ResponseType Login(const std::string& password); void LoginAsync(const std::string& password, const ResponseCallback& callback); bool IsLoggedIn() const; - void LoadGameByFilenameAsync(const std::string& iso_path, const ResponseCallback& callback); + void HashGame(const std::string& file_path, const ResponseCallback& callback); + void HashGame(const DiscIO::Volume* volume, const ResponseCallback& callback); bool IsGameLoaded() const; void LoadUnlockData(const ResponseCallback& callback); @@ -132,6 +136,8 @@ public: u32* target); const std::unordered_map& GetLeaderboardsInfo() const; RichPresence GetRichPresence(); + bool IsDisabled() const { return m_disabled; }; + void SetDisabled(bool disabled); void CloseGame(); void Logout(); @@ -140,15 +146,29 @@ public: private: AchievementManager() = default; - static constexpr int HASH_LENGTH = 33; + struct FilereaderState + { + int64_t position = 0; + std::unique_ptr volume; + }; + + static void* FilereaderOpenByFilepath(const char* path_utf8); + static void* FilereaderOpenByVolume(const char* path_utf8); + static void FilereaderSeek(void* file_handle, int64_t offset, int origin); + static int64_t FilereaderTell(void* file_handle); + static size_t FilereaderRead(void* file_handle, void* buffer, size_t requested_bytes); + static void FilereaderClose(void* file_handle); ResponseType VerifyCredentials(const std::string& password); - ResponseType ResolveHash(std::array game_hash); + ResponseType ResolveHash(const Hash& game_hash, u32* game_id); + void LoadGameSync(const ResponseCallback& callback); ResponseType StartRASession(); ResponseType FetchGameData(); ResponseType FetchUnlockData(bool hardcore); ResponseType FetchBoardInfo(AchievementId leaderboard_id); + std::unique_ptr& GetLoadingVolume() { return m_loading_volume; }; + void ActivateDeactivateAchievement(AchievementId id, bool enabled, bool unofficial, bool encore); void GenerateRichPresence(); @@ -174,10 +194,12 @@ private: Core::System* m_system{}; bool m_is_runtime_initialized = false; UpdateCallback m_update_callback; + std::unique_ptr m_loading_volume; + bool m_disabled = false; std::string m_display_name; u32 m_player_score = 0; BadgeStatus m_player_badge; - std::array m_game_hash{}; + Hash m_game_hash{}; u32 m_game_id = 0; rc_api_fetch_game_data_response_t m_game_data{}; bool m_is_game_loaded = false; @@ -192,6 +214,7 @@ private: Common::WorkQueueThread> m_queue; Common::WorkQueueThread> m_image_queue; mutable std::recursive_mutex m_lock; + std::recursive_mutex m_filereader_lock; }; // class AchievementManager #endif // USE_RETRO_ACHIEVEMENTS diff --git a/Source/Core/Core/Boot/Boot.cpp b/Source/Core/Core/Boot/Boot.cpp index 5fc52585e0..725057ea52 100644 --- a/Source/Core/Core/Boot/Boot.cpp +++ b/Source/Core/Core/Boot/Boot.cpp @@ -25,6 +25,7 @@ #include "Common/MsgHandler.h" #include "Common/StringUtil.h" +#include "Core/AchievementManager.h" #include "Core/Boot/DolReader.h" #include "Core/Boot/ElfReader.h" #include "Core/CommonTitles.h" @@ -558,6 +559,11 @@ bool CBoot::BootUp(Core::System& system, const Core::CPUThreadGuard& guard, SetupGCMemory(system, guard); } +#ifdef USE_RETRO_ACHIEVEMENTS + AchievementManager::GetInstance()->HashGame(executable.path, + [](AchievementManager::ResponseType r_type) {}); +#endif // USE_RETRO_ACHIEVEMENTS + if (!executable.reader->LoadIntoMemory(system)) { PanicAlertFmtT("Failed to load the executable to memory."); diff --git a/Source/Core/Core/BootManager.cpp b/Source/Core/Core/BootManager.cpp index 1c9e2f03d4..b45ffb7254 100644 --- a/Source/Core/Core/BootManager.cpp +++ b/Source/Core/Core/BootManager.cpp @@ -165,13 +165,7 @@ bool BootCore(std::unique_ptr boot, const WindowSystemInfo& wsi) } #ifdef USE_RETRO_ACHIEVEMENTS - std::string path = ""; - if (std::holds_alternative(boot->parameters)) - { - path = std::get(boot->parameters).path; - } - AchievementManager::GetInstance()->LoadGameByFilenameAsync( - path, [](AchievementManager::ResponseType r_type) {}); + AchievementManager::GetInstance()->SetDisabled(false); #endif // USE_RETRO_ACHIEVEMENTS const bool load_ipl = !StartUp.bWii && !Config::Get(Config::MAIN_SKIP_IPL) && diff --git a/Source/Core/Core/ConfigManager.cpp b/Source/Core/Core/ConfigManager.cpp index 27b7b2a51b..fa1fa2a7a0 100644 --- a/Source/Core/Core/ConfigManager.cpp +++ b/Source/Core/Core/ConfigManager.cpp @@ -30,6 +30,7 @@ #include "Common/StringUtil.h" #include "Common/Version.h" +#include "Core/AchievementManager.h" #include "Core/Boot/Boot.h" #include "Core/CommonTitles.h" #include "Core/Config/DefaultLocale.h" @@ -168,6 +169,10 @@ void SConfig::SetRunningGameMetadata(const std::string& game_id, const std::stri if (!was_changed) return; +#ifdef USE_RETRO_ACHIEVEMENTS + AchievementManager::GetInstance()->SetDisabled(true); +#endif // USE_RETRO_ACHIEVEMENTS + if (game_id == "00000000") { m_title_name.clear(); diff --git a/Source/Core/Core/HW/DVD/DVDInterface.cpp b/Source/Core/Core/HW/DVD/DVDInterface.cpp index a17ee5bd37..b8a57fe0ea 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.cpp +++ b/Source/Core/Core/HW/DVD/DVDInterface.cpp @@ -20,6 +20,7 @@ #include "Common/Config/Config.h" #include "Common/Logging/Log.h" +#include "Core/AchievementManager.h" #include "Core/Config/MainSettings.h" #include "Core/Config/SessionSettings.h" #include "Core/CoreTiming.h" @@ -396,6 +397,11 @@ void DVDInterface::SetDisc(std::unique_ptr disc, m_auto_disc_change_index = 0; } +#ifdef USE_RETRO_ACHIEVEMENTS + AchievementManager::GetInstance()->HashGame(disc.get(), + [](AchievementManager::ResponseType r_type) {}); +#endif // USE_RETRO_ACHIEVEMENTS + // Assume that inserting a disc requires having an empty disc before if (had_disc != has_disc) ExpansionInterface::g_rtc_flags[ExpansionInterface::RTCFlag::DiscChanged] = true; diff --git a/Source/Core/Core/IOS/ES/ES.cpp b/Source/Core/Core/IOS/ES/ES.cpp index af30cd6765..4771115ce1 100644 --- a/Source/Core/Core/IOS/ES/ES.cpp +++ b/Source/Core/Core/IOS/ES/ES.cpp @@ -16,6 +16,7 @@ #include "Common/NandPaths.h" #include "Common/ScopeGuard.h" #include "Common/StringUtil.h" +#include "Core/AchievementManager.h" #include "Core/CommonTitles.h" #include "Core/ConfigManager.h" #include "Core/Core.h" @@ -476,6 +477,12 @@ bool ESDevice::LaunchPPCTitle(u64 title_id) if (!Core::IsRunningAndStarted()) return BootstrapPPC(); +#ifdef USE_RETRO_ACHIEVEMENTS + INFO_LOG_FMT(ACHIEVEMENTS, + "WAD and NAND formats not currently supported by Achievement Manager."); + AchievementManager::GetInstance()->SetDisabled(true); +#endif // USE_RETRO_ACHIEVEMENTS + core_timing.RemoveEvent(s_bootstrap_ppc_for_launch_event); core_timing.ScheduleEvent(ticks, s_bootstrap_ppc_for_launch_event); return true; diff --git a/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp b/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp index c984fdda0f..3cd42b6e3b 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp +++ b/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp @@ -37,6 +37,11 @@ AchievementHeaderWidget::AchievementHeaderWidget(QWidget* parent) : QWidget(pare m_game_progress_hard = new QProgressBar(); m_game_progress_soft = new QProgressBar(); m_rich_presence = new QLabel(); + m_locked_warning = new QLabel(); + + m_locked_warning->setText(tr("Achievements have been disabled.
Please close all running " + "games to re-enable achievements.")); + m_locked_warning->setStyleSheet(QStringLiteral("QLabel { color : red; }")); QSizePolicy sp_retain = m_game_progress_hard->sizePolicy(); sp_retain.setRetainSizeWhenHidden(true); @@ -54,6 +59,7 @@ AchievementHeaderWidget::AchievementHeaderWidget(QWidget* parent) : QWidget(pare text_col->addWidget(m_game_progress_hard); text_col->addWidget(m_game_progress_soft); text_col->addWidget(m_rich_presence); + text_col->addWidget(m_locked_warning); QHBoxLayout* header_layout = new QHBoxLayout(); header_layout->addLayout(icon_col); header_layout->addLayout(text_col); @@ -148,6 +154,7 @@ void AchievementHeaderWidget::UpdateData() QString::fromUtf8(AchievementManager::GetInstance()->GetRichPresence().data())); if (!m_rich_presence->isVisible()) m_rich_presence->setVisible(Config::Get(Config::RA_RICH_PRESENCE_ENABLED)); + m_locked_warning->setVisible(false); } else { @@ -157,6 +164,10 @@ void AchievementHeaderWidget::UpdateData() m_game_progress_hard->setVisible(false); m_game_progress_soft->setVisible(false); m_rich_presence->setVisible(false); + if (AchievementManager::GetInstance()->IsDisabled()) + { + m_locked_warning->setVisible(true); + } } } diff --git a/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.h b/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.h index 2c4f939ceb..7a644bd763 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.h +++ b/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.h @@ -30,6 +30,7 @@ private: QProgressBar* m_game_progress_hard; QProgressBar* m_game_progress_soft; QLabel* m_rich_presence; + QLabel* m_locked_warning; QGroupBox* m_header_box; };