diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index 5ea4f253b9..35287d3560 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -35,6 +35,8 @@ void AchievementManager::Init() rc_runtime_init(&m_runtime); m_is_runtime_initialized = true; m_queue.Reset("AchievementManagerQueue", [](const std::function& func) { func(); }); + m_image_queue.Reset("AchievementManagerImageQueue", + [](const std::function& func) { func(); }); LoginAsync("", [](ResponseType r_type) {}); INFO_LOG_FMT(ACHIEVEMENTS, "Achievement Manager Initialized"); } @@ -55,6 +57,7 @@ AchievementManager::ResponseType AchievementManager::Login(const std::string& pa return AchievementManager::ResponseType::MANAGER_NOT_INITIALIZED; } AchievementManager::ResponseType r_type = VerifyCredentials(password); + FetchBadges(); if (m_update_callback) m_update_callback(); return r_type; @@ -71,6 +74,7 @@ void AchievementManager::LoginAsync(const std::string& password, const ResponseC } m_queue.EmplaceItem([this, password, callback] { callback(VerifyCredentials(password)); + FetchBadges(); if (m_update_callback) m_update_callback(); }); @@ -207,6 +211,7 @@ void AchievementManager::LoadGameByFilenameAsync(const std::string& iso_path, } 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); @@ -286,6 +291,256 @@ void AchievementManager::ActivateDeactivateRichPresence() INFO_LOG_FMT(ACHIEVEMENTS, "Rich presence (de)activated."); } +void AchievementManager::FetchBadges() +{ + if (!m_is_runtime_initialized || !IsLoggedIn() || !Config::Get(Config::RA_BADGES_ENABLED)) + { + if (m_update_callback) + m_update_callback(); + return; + } + m_image_queue.Cancel(); + + if (m_player_badge.name != m_display_name) + { + m_image_queue.EmplaceItem([this] { + std::string name_to_fetch; + { + std::lock_guard lg{m_lock}; + if (m_display_name == m_player_badge.name) + return; + name_to_fetch = m_display_name; + } + rc_api_fetch_image_request_t icon_request = {.image_name = name_to_fetch.c_str(), + .image_type = RC_IMAGE_TYPE_USER}; + Badge fetched_badge; + if (RequestImage(icon_request, &fetched_badge) == ResponseType::SUCCESS) + { + INFO_LOG_FMT(ACHIEVEMENTS, "Successfully downloaded player badge id {}.", name_to_fetch); + std::lock_guard lg{m_lock}; + if (name_to_fetch != m_display_name) + { + INFO_LOG_FMT(ACHIEVEMENTS, "Requested outdated badge id {} for player id {}.", + name_to_fetch, m_display_name); + return; + } + m_player_badge.badge = std::move(fetched_badge); + m_player_badge.name = std::move(name_to_fetch); + } + else + { + WARN_LOG_FMT(ACHIEVEMENTS, "Failed to download player badge id {}.", name_to_fetch); + } + if (m_update_callback) + m_update_callback(); + }); + } + + if (!IsGameLoaded()) + { + if (m_update_callback) + m_update_callback(); + return; + } + + int badgematch = 0; + { + std::lock_guard lg{m_lock}; + badgematch = m_game_badge.name.compare(m_game_data.image_name); + } + if (badgematch != 0) + { + m_image_queue.EmplaceItem([this] { + std::string name_to_fetch; + { + std::lock_guard lg{m_lock}; + if (m_game_badge.name.compare(m_game_data.image_name) == 0) + return; + name_to_fetch.assign(m_game_data.image_name); + } + rc_api_fetch_image_request_t icon_request = {.image_name = name_to_fetch.c_str(), + .image_type = RC_IMAGE_TYPE_GAME}; + Badge fetched_badge; + if (RequestImage(icon_request, &fetched_badge) == ResponseType::SUCCESS) + { + INFO_LOG_FMT(ACHIEVEMENTS, "Successfully downloaded game badge id {}.", name_to_fetch); + std::lock_guard lg{m_lock}; + if (name_to_fetch.compare(m_game_data.image_name) != 0) + { + INFO_LOG_FMT(ACHIEVEMENTS, "Requested outdated badge id {} for game id {}.", + name_to_fetch, m_game_data.image_name); + return; + } + m_game_badge.badge = std::move(fetched_badge); + m_game_badge.name = std::move(name_to_fetch); + } + else + { + WARN_LOG_FMT(ACHIEVEMENTS, "Failed to download game badge id {}.", name_to_fetch); + } + if (m_update_callback) + m_update_callback(); + }); + } + + unsigned num_achievements = m_game_data.num_achievements; + for (size_t index = 0; index < num_achievements; index++) + { + std::lock_guard lg{m_lock}; + // In case the number of achievements changes since the loop started; I just don't want + // to lock for the ENTIRE loop so instead I reclaim the lock each cycle + if (num_achievements != m_game_data.num_achievements) + break; + rc_api_achievement_definition_t& achievement = m_game_data.achievements[index]; + std::string name_to_fetch(achievement.badge_name); + const UnlockStatus& unlock_status = m_unlock_map[achievement.id]; + if (unlock_status.unlocked_badge.name != name_to_fetch) + { + m_image_queue.EmplaceItem([this, index] { + std::string current_name, name_to_fetch; + { + std::lock_guard lg{m_lock}; + if (m_game_data.num_achievements <= index) + { + INFO_LOG_FMT( + ACHIEVEMENTS, + "Attempted to fetch unlocked badge for index {} after achievement list cleared.", + index); + return; + } + rc_api_achievement_definition_t& achievement = m_game_data.achievements[index]; + auto unlock_itr = m_unlock_map.find(achievement.id); + if (unlock_itr == m_unlock_map.end()) + { + ERROR_LOG_FMT( + ACHIEVEMENTS, + "Attempted to fetch unlocked badge for achievement id {} not in unlock map.", + index); + return; + } + name_to_fetch.assign(achievement.badge_name); + current_name = unlock_itr->second.unlocked_badge.name; + } + if (current_name == name_to_fetch) + return; + rc_api_fetch_image_request_t icon_request = {.image_name = name_to_fetch.c_str(), + .image_type = RC_IMAGE_TYPE_ACHIEVEMENT}; + Badge fetched_badge; + if (RequestImage(icon_request, &fetched_badge) == ResponseType::SUCCESS) + { + INFO_LOG_FMT(ACHIEVEMENTS, "Successfully downloaded unlocked achievement badge id {}.", + name_to_fetch); + std::lock_guard lg{m_lock}; + if (m_game_data.num_achievements <= index) + { + INFO_LOG_FMT(ACHIEVEMENTS, + "Fetched unlocked badge for index {} after achievement list cleared.", + index); + return; + } + rc_api_achievement_definition_t& achievement = m_game_data.achievements[index]; + auto unlock_itr = m_unlock_map.find(achievement.id); + if (unlock_itr == m_unlock_map.end()) + { + ERROR_LOG_FMT(ACHIEVEMENTS, + "Fetched unlocked badge for achievement id {} not in unlock map.", index); + return; + } + if (name_to_fetch.compare(achievement.badge_name) != 0) + { + INFO_LOG_FMT( + ACHIEVEMENTS, + "Requested outdated unlocked achievement badge id {} for achievement id {}.", + name_to_fetch, current_name); + return; + } + unlock_itr->second.unlocked_badge.badge = std::move(fetched_badge); + unlock_itr->second.unlocked_badge.name = std::move(name_to_fetch); + } + else + { + WARN_LOG_FMT(ACHIEVEMENTS, "Failed to download unlocked achievement badge id {}.", + name_to_fetch); + } + if (m_update_callback) + m_update_callback(); + }); + } + if (unlock_status.locked_badge.name != name_to_fetch) + { + m_image_queue.EmplaceItem([this, index] { + std::string current_name, name_to_fetch; + { + std::lock_guard lg{m_lock}; + if (m_game_data.num_achievements <= index) + { + INFO_LOG_FMT( + ACHIEVEMENTS, + "Attempted to fetch locked badge for index {} after achievement list cleared.", + index); + return; + } + rc_api_achievement_definition_t& achievement = m_game_data.achievements[index]; + auto unlock_itr = m_unlock_map.find(achievement.id); + if (unlock_itr == m_unlock_map.end()) + { + ERROR_LOG_FMT( + ACHIEVEMENTS, + "Attempted to fetch locked badge for achievement id {} not in unlock map.", index); + return; + } + name_to_fetch.assign(achievement.badge_name); + current_name = unlock_itr->second.locked_badge.name; + } + if (current_name == name_to_fetch) + return; + rc_api_fetch_image_request_t icon_request = { + .image_name = name_to_fetch.c_str(), .image_type = RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED}; + Badge fetched_badge; + if (RequestImage(icon_request, &fetched_badge) == ResponseType::SUCCESS) + { + INFO_LOG_FMT(ACHIEVEMENTS, "Successfully downloaded locked achievement badge id {}.", + name_to_fetch); + std::lock_guard lg{m_lock}; + if (m_game_data.num_achievements <= index) + { + INFO_LOG_FMT(ACHIEVEMENTS, + "Fetched locked badge for index {} after achievement list cleared.", + index); + return; + } + rc_api_achievement_definition_t& achievement = m_game_data.achievements[index]; + auto unlock_itr = m_unlock_map.find(achievement.id); + if (unlock_itr == m_unlock_map.end()) + { + ERROR_LOG_FMT(ACHIEVEMENTS, + "Fetched locked badge for achievement id {} not in unlock map.", index); + return; + } + if (name_to_fetch.compare(achievement.badge_name) != 0) + { + INFO_LOG_FMT(ACHIEVEMENTS, + "Requested outdated locked achievement badge id {} for achievement id {}.", + name_to_fetch, current_name); + return; + } + unlock_itr->second.locked_badge.badge = std::move(fetched_badge); + unlock_itr->second.locked_badge.name = std::move(name_to_fetch); + } + else + { + WARN_LOG_FMT(ACHIEVEMENTS, "Failed to download locked achievement badge id {}.", + name_to_fetch); + } + if (m_update_callback) + m_update_callback(); + }); + } + } + if (m_update_callback) + m_update_callback(); +} + void AchievementManager::DoFrame() { if (!m_is_game_loaded) @@ -381,6 +636,11 @@ u32 AchievementManager::GetPlayerScore() const return IsLoggedIn() ? m_player_score : 0; } +const AchievementManager::BadgeStatus& AchievementManager::GetPlayerBadge() const +{ + return m_player_badge; +} + std::string AchievementManager::GetGameDisplayName() const { return IsGameLoaded() ? m_game_data.title : ""; @@ -417,7 +677,12 @@ rc_api_fetch_game_data_response_t* AchievementManager::GetGameData() return &m_game_data; } -AchievementManager::UnlockStatus +const AchievementManager::BadgeStatus& AchievementManager::GetGameBadge() const +{ + return m_game_badge; +} + +const AchievementManager::UnlockStatus& AchievementManager::GetUnlockStatus(AchievementId achievement_id) const { return m_unlock_map.at(achievement_id); @@ -447,10 +712,12 @@ void AchievementManager::CloseGame() ActivateDeactivateLeaderboards(); ActivateDeactivateRichPresence(); m_game_id = 0; + m_game_badge.name = ""; m_unlock_map.clear(); rc_api_destroy_fetch_game_data_response(&m_game_data); std::memset(&m_game_data, 0, sizeof(m_game_data)); m_queue.Cancel(); + m_image_queue.Cancel(); m_system = nullptr; } } @@ -461,8 +728,12 @@ void AchievementManager::CloseGame() void AchievementManager::Logout() { - CloseGame(); - Config::SetBaseOrCurrent(Config::RA_API_TOKEN, ""); + { + std::lock_guard lg{m_lock}; + CloseGame(); + m_player_badge.name = ""; + Config::SetBaseOrCurrent(Config::RA_API_TOKEN, ""); + } if (m_update_callback) m_update_callback(); INFO_LOG_FMT(ACHIEVEMENTS, "Logged out from server."); diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index 9b67c1e018..6379cee94a 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -58,6 +58,12 @@ public: using RichPresence = std::array; using Badge = std::vector; + struct BadgeStatus + { + std::string name = ""; + Badge badge{}; + }; + struct UnlockStatus { AchievementId game_data_index = 0; @@ -69,6 +75,8 @@ public: } remote_unlock_status = UnlockType::LOCKED; u32 session_unlock_count = 0; u32 points = 0; + BadgeStatus locked_badge; + BadgeStatus unlocked_badge; }; static AchievementManager* GetInstance(); @@ -84,6 +92,7 @@ public: void ActivateDeactivateAchievements(); void ActivateDeactivateLeaderboards(); void ActivateDeactivateRichPresence(); + void FetchBadges(); void DoFrame(); u32 MemoryPeeker(u32 address, u32 num_bytes, void* ud); @@ -92,10 +101,12 @@ public: std::recursive_mutex* GetLock(); std::string GetPlayerDisplayName() const; u32 GetPlayerScore() const; + const BadgeStatus& GetPlayerBadge() const; std::string GetGameDisplayName() const; PointSpread TallyScore() const; rc_api_fetch_game_data_response_t* GetGameData(); - UnlockStatus GetUnlockStatus(AchievementId achievement_id) const; + const BadgeStatus& GetGameBadge() const; + const UnlockStatus& GetUnlockStatus(AchievementId achievement_id) const; void GetAchievementProgress(AchievementId achievement_id, u32* value, u32* target); RichPresence GetRichPresence(); @@ -138,16 +149,19 @@ private: UpdateCallback m_update_callback; std::string m_display_name; u32 m_player_score = 0; + BadgeStatus m_player_badge; std::array m_game_hash{}; u32 m_game_id = 0; rc_api_fetch_game_data_response_t m_game_data{}; bool m_is_game_loaded = false; + BadgeStatus m_game_badge; RichPresence m_rich_presence; time_t m_last_ping_time = 0; std::unordered_map m_unlock_map; Common::WorkQueueThread> m_queue; + Common::WorkQueueThread> m_image_queue; std::recursive_mutex m_lock; }; // class AchievementManager diff --git a/Source/Core/Core/Config/AchievementSettings.cpp b/Source/Core/Core/Config/AchievementSettings.cpp index 3499fcc791..9211fb712e 100644 --- a/Source/Core/Core/Config/AchievementSettings.cpp +++ b/Source/Core/Core/Config/AchievementSettings.cpp @@ -19,6 +19,7 @@ const Info RA_LEADERBOARDS_ENABLED{ {System::Achievements, "Achievements", "LeaderboardsEnabled"}, false}; const Info RA_RICH_PRESENCE_ENABLED{ {System::Achievements, "Achievements", "RichPresenceEnabled"}, false}; +const Info RA_BADGES_ENABLED{{System::Achievements, "Achievements", "BadgesEnabled"}, false}; const Info RA_UNOFFICIAL_ENABLED{{System::Achievements, "Achievements", "UnofficialEnabled"}, false}; const Info RA_ENCORE_ENABLED{{System::Achievements, "Achievements", "EncoreEnabled"}, false}; diff --git a/Source/Core/Core/Config/AchievementSettings.h b/Source/Core/Core/Config/AchievementSettings.h index 33318f7400..c3dec06d01 100644 --- a/Source/Core/Core/Config/AchievementSettings.h +++ b/Source/Core/Core/Config/AchievementSettings.h @@ -14,6 +14,7 @@ extern const Info RA_API_TOKEN; extern const Info RA_ACHIEVEMENTS_ENABLED; extern const Info RA_LEADERBOARDS_ENABLED; extern const Info RA_RICH_PRESENCE_ENABLED; +extern const Info RA_BADGES_ENABLED; extern const Info RA_UNOFFICIAL_ENABLED; extern const Info RA_ENCORE_ENABLED; } // namespace Config diff --git a/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp b/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp index 8ec6278aae..7d0b63f9bb 100644 --- a/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp +++ b/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp @@ -47,6 +47,7 @@ bool IsSettingSaveable(const Config::Location& config_location) &Config::RA_ACHIEVEMENTS_ENABLED.GetLocation(), &Config::RA_LEADERBOARDS_ENABLED.GetLocation(), &Config::RA_RICH_PRESENCE_ENABLED.GetLocation(), + &Config::RA_BADGES_ENABLED.GetLocation(), &Config::RA_UNOFFICIAL_ENABLED.GetLocation(), &Config::RA_ENCORE_ENABLED.GetLocation(), };