diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index 2b6015f382..9c8b3d1a4a 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -5,14 +5,16 @@ #include "Core/AchievementManager.h" -#include #include #include "Common/HttpRequest.h" #include "Common/WorkQueueThread.h" -#include "Config/AchievementSettings.h" +#include "Core/Config/AchievementSettings.h" #include "Core/Core.h" +#include "Core/PowerPC/MMU.h" +#include "Core/System.h" #include "DiscIO/Volume.h" +#include "VideoCommon/VideoEvents.h" static constexpr bool hardcore_mode_enabled = false; @@ -63,6 +65,7 @@ void AchievementManager::LoadGameByFilenameAsync(const std::string& iso_path, callback(AchievementManager::ResponseType::MANAGER_NOT_INITIALIZED); return; } + m_system = &Core::System::GetInstance(); struct FilereaderState { int64_t position = 0; @@ -113,11 +116,10 @@ void AchievementManager::LoadGameByFilenameAsync(const std::string& iso_path, }, .close = [](void* file_handle) { delete reinterpret_cast(file_handle); }}; rc_hash_init_custom_filereader(&volume_reader); - std::array game_hash; - if (!rc_hash_generate_from_file(game_hash.data(), RC_CONSOLE_GAMECUBE, iso_path.c_str())) + if (!rc_hash_generate_from_file(m_game_hash.data(), RC_CONSOLE_GAMECUBE, iso_path.c_str())) return; - m_queue.EmplaceItem([this, callback, game_hash] { - const auto resolve_hash_response = ResolveHash(game_hash); + 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) { callback(resolve_hash_response); @@ -204,12 +206,76 @@ void AchievementManager::ActivateDeactivateRichPresence() nullptr, 0); } +void AchievementManager::DoFrame() +{ + if (!m_is_game_loaded) + return; + Core::RunAsCPUThread([&] { + rc_runtime_do_frame( + &m_runtime, + [](const rc_runtime_event_t* runtime_event) { + AchievementManager::GetInstance()->AchievementEventHandler(runtime_event); + }, + [](unsigned address, unsigned num_bytes, void* ud) { + return static_cast(ud)->MemoryPeeker(address, num_bytes, ud); + }, + this, nullptr); + }); + if (!m_system) + return; + u64 current_time = m_system->GetCoreTiming().GetTicks(); + if (current_time - m_last_ping_time > SystemTimers::GetTicksPerSecond() * 120) + m_queue.EmplaceItem([this] { PingRichPresence(GenerateRichPresence()); }); +} + +u32 AchievementManager::MemoryPeeker(u32 address, u32 num_bytes, void* ud) +{ + if (!m_system) + return 0u; + Core::CPUThreadGuard threadguard(*m_system); + switch (num_bytes) + { + case 1: + return m_system->GetMMU() + .HostTryReadU8(threadguard, address) + .value_or(PowerPC::ReadResult(false, 0u)) + .value; + case 2: + return m_system->GetMMU() + .HostTryReadU16(threadguard, address) + .value_or(PowerPC::ReadResult(false, 0u)) + .value; + case 4: + return m_system->GetMMU() + .HostTryReadU32(threadguard, address) + .value_or(PowerPC::ReadResult(false, 0u)) + .value; + default: + ASSERT(false); + return 0u; + } +} + +void AchievementManager::AchievementEventHandler(const rc_runtime_event_t* runtime_event) +{ + switch (runtime_event->type) + { + case RC_RUNTIME_EVENT_ACHIEVEMENT_TRIGGERED: + HandleAchievementTriggeredEvent(runtime_event); + break; + case RC_RUNTIME_EVENT_LBOARD_TRIGGERED: + HandleLeaderboardTriggeredEvent(runtime_event); + break; + } +} + void AchievementManager::CloseGame() { m_is_game_loaded = false; m_game_id = 0; m_queue.Cancel(); m_unlock_map.clear(); + m_system = nullptr; ActivateDeactivateAchievements(); ActivateDeactivateLeaderboards(); ActivateDeactivateRichPresence(); @@ -366,6 +432,91 @@ void AchievementManager::ActivateDeactivateAchievement(AchievementId id, bool en rc_runtime_deactivate_achievement(&m_runtime, id); } +RichPresence AchievementManager::GenerateRichPresence() +{ + RichPresence rp_buffer; + Core::RunAsCPUThread([&] { + rc_runtime_get_richpresence( + &m_runtime, rp_buffer.data(), RP_SIZE, + [](unsigned address, unsigned num_bytes, void* ud) { + return static_cast(ud)->MemoryPeeker(address, num_bytes, ud); + }, + this, nullptr); + }); + return rp_buffer; +} + +AchievementManager::ResponseType AchievementManager::AwardAchievement(AchievementId achievement_id) +{ + std::string username = Config::Get(Config::RA_USERNAME); + std::string api_token = Config::Get(Config::RA_API_TOKEN); + rc_api_award_achievement_request_t award_request = {.username = username.c_str(), + .api_token = api_token.c_str(), + .achievement_id = achievement_id, + .hardcore = hardcore_mode_enabled, + .game_hash = m_game_hash.data()}; + rc_api_award_achievement_response_t award_response = {}; + ResponseType r_type = + Request( + award_request, &award_response, rc_api_init_award_achievement_request, + rc_api_process_award_achievement_response); + rc_api_destroy_award_achievement_response(&award_response); + return r_type; +} + +AchievementManager::ResponseType AchievementManager::SubmitLeaderboard(AchievementId leaderboard_id, + int value) +{ + std::string username = Config::Get(Config::RA_USERNAME); + std::string api_token = Config::Get(Config::RA_API_TOKEN); + rc_api_submit_lboard_entry_request_t submit_request = {.username = username.c_str(), + .api_token = api_token.c_str(), + .leaderboard_id = leaderboard_id, + .score = value, + .game_hash = m_game_hash.data()}; + rc_api_submit_lboard_entry_response_t submit_response = {}; + ResponseType r_type = + Request( + submit_request, &submit_response, rc_api_init_submit_lboard_entry_request, + rc_api_process_submit_lboard_entry_response); + rc_api_destroy_submit_lboard_entry_response(&submit_response); + return r_type; +} + +AchievementManager::ResponseType +AchievementManager::PingRichPresence(const RichPresence& rich_presence) +{ + std::string username = Config::Get(Config::RA_USERNAME); + std::string api_token = Config::Get(Config::RA_API_TOKEN); + rc_api_ping_request_t ping_request = {.username = username.c_str(), + .api_token = api_token.c_str(), + .game_id = m_game_id, + .rich_presence = rich_presence.data()}; + rc_api_ping_response_t ping_response = {}; + ResponseType r_type = Request( + ping_request, &ping_response, rc_api_init_ping_request, rc_api_process_ping_response); + rc_api_destroy_ping_response(&ping_response); + return r_type; +} + +void AchievementManager::HandleAchievementTriggeredEvent(const rc_runtime_event_t* runtime_event) +{ + auto it = m_unlock_map.find(runtime_event->id); + if (it == m_unlock_map.end()) + return; + it->second.session_unlock_count++; + m_queue.EmplaceItem([this, runtime_event] { AwardAchievement(runtime_event->id); }); + ActivateDeactivateAchievement(runtime_event->id, Config::Get(Config::RA_ACHIEVEMENTS_ENABLED), + Config::Get(Config::RA_UNOFFICIAL_ENABLED), + Config::Get(Config::RA_ENCORE_ENABLED)); +} + +void AchievementManager::HandleLeaderboardTriggeredEvent(const rc_runtime_event_t* runtime_event) +{ + m_queue.EmplaceItem( + [this, runtime_event] { SubmitLeaderboard(runtime_event->id, runtime_event->value); }); +} + // Every RetroAchievements API call, with only a partial exception for fetch_image, follows // the same design pattern (here, X is the name of the call): // Create a specific rc_api_X_request_t struct and populate with the necessary values diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index b5cf465915..b6cbcde732 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -4,6 +4,7 @@ #pragma once #ifdef USE_RETRO_ACHIEVEMENTS +#include #include #include #include @@ -18,6 +19,13 @@ #include "Common/WorkQueueThread.h" using AchievementId = u32; +constexpr size_t RP_SIZE = 256; +using RichPresence = std::array; + +namespace Core +{ +class System; +} class AchievementManager { @@ -45,6 +53,10 @@ public: void ActivateDeactivateLeaderboards(); void ActivateDeactivateRichPresence(); + void DoFrame(); + u32 MemoryPeeker(u32 address, u32 num_bytes, void* ud); + void AchievementEventHandler(const rc_runtime_event_t* runtime_event); + void CloseGame(); void Logout(); void Shutdown(); @@ -61,6 +73,14 @@ private: ResponseType FetchUnlockData(bool hardcore); void ActivateDeactivateAchievement(AchievementId id, bool enabled, bool unofficial, bool encore); + RichPresence GenerateRichPresence(); + + ResponseType AwardAchievement(AchievementId achievement_id); + ResponseType SubmitLeaderboard(AchievementId leaderboard_id, int value); + ResponseType PingRichPresence(const RichPresence& rich_presence); + + void HandleAchievementTriggeredEvent(const rc_runtime_event_t* runtime_event); + void HandleLeaderboardTriggeredEvent(const rc_runtime_event_t* runtime_event); template ResponseType Request(RcRequest rc_request, RcResponse* rc_response, @@ -68,10 +88,13 @@ private: const std::function& process_response); rc_runtime_t m_runtime{}; + Core::System* m_system{}; bool m_is_runtime_initialized = false; - unsigned int m_game_id = 0; + 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; + u64 m_last_ping_time = 0; struct UnlockStatus {