summaryrefslogtreecommitdiff
path: root/Source
diff options
context:
space:
mode:
authorAdmiral H. Curtiss <pikachu025@gmail.com>2023-05-21 03:20:34 +0200
committerGitHub <noreply@github.com>2023-05-21 03:20:34 +0200
commitef95d942e19608777690baaca0b0eee5313221b2 (patch)
tree3f6eefb5d4064b99331178e8ee3ef9ff415823a5 /Source
parentd59b61c87e036dfb0146d611a5ac8b5bcce4ba78 (diff)
parentc9c2e17e5a8e245475d34865da4fd2d1fb0e100a (diff)
Merge pull request #11763 from LillyJadeKatrin/retroachievements-memory-events
RetroAchievements Memory, Events, and Awarding
Diffstat (limited to 'Source')
-rw-r--r--Source/Core/Core/AchievementManager.cpp163
-rw-r--r--Source/Core/Core/AchievementManager.h25
2 files changed, 181 insertions, 7 deletions
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 <array>
#include <rcheevos/include/rc_hash.h>
#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<FilereaderState*>(file_handle); }};
rc_hash_init_custom_filereader(&volume_reader);
- std::array<char, HASH_LENGTH> 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<AchievementManager*>(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<u8>(false, 0u))
+ .value;
+ case 2:
+ return m_system->GetMMU()
+ .HostTryReadU16(threadguard, address)
+ .value_or(PowerPC::ReadResult<u16>(false, 0u))
+ .value;
+ case 4:
+ return m_system->GetMMU()
+ .HostTryReadU32(threadguard, address)
+ .value_or(PowerPC::ReadResult<u32>(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<AchievementManager*>(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<rc_api_award_achievement_request_t, rc_api_award_achievement_response_t>(
+ 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<rc_api_submit_lboard_entry_request_t, rc_api_submit_lboard_entry_response_t>(
+ 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<rc_api_ping_request_t, rc_api_ping_response_t>(
+ 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 <array>
#include <functional>
#include <mutex>
#include <string>
@@ -18,6 +19,13 @@
#include "Common/WorkQueueThread.h"
using AchievementId = u32;
+constexpr size_t RP_SIZE = 256;
+using RichPresence = std::array<char, RP_SIZE>;
+
+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 <typename RcRequest, typename RcResponse>
ResponseType Request(RcRequest rc_request, RcResponse* rc_response,
@@ -68,10 +88,13 @@ private:
const std::function<int(RcResponse*, const char*)>& 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<char, HASH_LENGTH> 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
{