From 148c2f3c0d9d498dd07546226328d1ea67b300c0 Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Sun, 1 Oct 2023 13:35:00 -0400 Subject: [PATCH] Refactored AchievementManager hash code to take a volume. This change splits LoadGameAsync into three methods: two HashGame methods to accept either a string filepath or a volume, and a common LoadGameSync that both HashGames call to actually process the code. In the process, some minor cleanup, and the hash resolution now takes place on the work queue asynchronously. --- Source/Core/Core/AchievementManager.cpp | 296 +++++++++++++++-------- Source/Core/Core/AchievementManager.h | 28 ++- Source/Core/Core/BootManager.cpp | 10 - Source/Core/Core/HW/DVD/DVDInterface.cpp | 6 + 4 files changed, 225 insertions(+), 115 deletions(-) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index 6fab056c79..424446647f 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) { @@ -102,116 +101,149 @@ void AchievementManager::LoadGameByFilenameAsync(const std::string& iso_path, 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}; + 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; + } + { + std::lock_guard lg{m_lock}; + if (m_loading_volume.get() != nullptr) + { + return; + } + m_loading_volume = DiscIO::CreateVolume(volume->GetBlobReader().CopyReader()); + } + 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(); + 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) +{ + Hash current_hash; + { + std::lock_guard lg{m_lock}; + current_hash = m_game_hash; + } + const auto resolve_hash_response = ResolveHash(current_hash); + if (resolve_hash_response != ResponseType::SUCCESS || m_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); + callback(resolve_hash_response); + return; + } + + 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; @@ -790,6 +822,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 +921,7 @@ AchievementManager::ResponseType AchievementManager::VerifyCredentials(const std return r_type; } -AchievementManager::ResponseType -AchievementManager::ResolveHash(std::array game_hash) +AchievementManager::ResponseType AchievementManager::ResolveHash(Hash game_hash) { rc_api_resolve_hash_response_t hash_data{}; std::string username, api_token; diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index e790e0bc1c..afbe083cd1 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); @@ -140,15 +144,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(Hash game_hash); + 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 +192,11 @@ private: Core::System* m_system{}; bool m_is_runtime_initialized = false; UpdateCallback m_update_callback; + std::unique_ptr m_loading_volume; 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 +211,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/BootManager.cpp b/Source/Core/Core/BootManager.cpp index 1c9e2f03d4..3ddde2ce9c 100644 --- a/Source/Core/Core/BootManager.cpp +++ b/Source/Core/Core/BootManager.cpp @@ -164,16 +164,6 @@ 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) {}); -#endif // USE_RETRO_ACHIEVEMENTS - const bool load_ipl = !StartUp.bWii && !Config::Get(Config::MAIN_SKIP_IPL) && std::holds_alternative(boot->parameters); if (load_ipl) 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;