mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-03-12 22:56:52 +01:00
Merge pull request #11763 from LillyJadeKatrin/retroachievements-memory-events
RetroAchievements Memory, Events, and Awarding
This commit is contained in:
commit
ef95d942e1
@ -5,14 +5,16 @@
|
|||||||
|
|
||||||
#include "Core/AchievementManager.h"
|
#include "Core/AchievementManager.h"
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <rcheevos/include/rc_hash.h>
|
#include <rcheevos/include/rc_hash.h>
|
||||||
|
|
||||||
#include "Common/HttpRequest.h"
|
#include "Common/HttpRequest.h"
|
||||||
#include "Common/WorkQueueThread.h"
|
#include "Common/WorkQueueThread.h"
|
||||||
#include "Config/AchievementSettings.h"
|
#include "Core/Config/AchievementSettings.h"
|
||||||
#include "Core/Core.h"
|
#include "Core/Core.h"
|
||||||
|
#include "Core/PowerPC/MMU.h"
|
||||||
|
#include "Core/System.h"
|
||||||
#include "DiscIO/Volume.h"
|
#include "DiscIO/Volume.h"
|
||||||
|
#include "VideoCommon/VideoEvents.h"
|
||||||
|
|
||||||
static constexpr bool hardcore_mode_enabled = false;
|
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);
|
callback(AchievementManager::ResponseType::MANAGER_NOT_INITIALIZED);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
m_system = &Core::System::GetInstance();
|
||||||
struct FilereaderState
|
struct FilereaderState
|
||||||
{
|
{
|
||||||
int64_t position = 0;
|
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); }};
|
.close = [](void* file_handle) { delete reinterpret_cast<FilereaderState*>(file_handle); }};
|
||||||
rc_hash_init_custom_filereader(&volume_reader);
|
rc_hash_init_custom_filereader(&volume_reader);
|
||||||
std::array<char, HASH_LENGTH> game_hash;
|
if (!rc_hash_generate_from_file(m_game_hash.data(), RC_CONSOLE_GAMECUBE, iso_path.c_str()))
|
||||||
if (!rc_hash_generate_from_file(game_hash.data(), RC_CONSOLE_GAMECUBE, iso_path.c_str()))
|
|
||||||
return;
|
return;
|
||||||
m_queue.EmplaceItem([this, callback, game_hash] {
|
m_queue.EmplaceItem([this, callback] {
|
||||||
const auto resolve_hash_response = ResolveHash(game_hash);
|
const auto resolve_hash_response = ResolveHash(this->m_game_hash);
|
||||||
if (resolve_hash_response != ResponseType::SUCCESS || m_game_id == 0)
|
if (resolve_hash_response != ResponseType::SUCCESS || m_game_id == 0)
|
||||||
{
|
{
|
||||||
callback(resolve_hash_response);
|
callback(resolve_hash_response);
|
||||||
@ -204,12 +206,76 @@ void AchievementManager::ActivateDeactivateRichPresence()
|
|||||||
nullptr, 0);
|
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()
|
void AchievementManager::CloseGame()
|
||||||
{
|
{
|
||||||
m_is_game_loaded = false;
|
m_is_game_loaded = false;
|
||||||
m_game_id = 0;
|
m_game_id = 0;
|
||||||
m_queue.Cancel();
|
m_queue.Cancel();
|
||||||
m_unlock_map.clear();
|
m_unlock_map.clear();
|
||||||
|
m_system = nullptr;
|
||||||
ActivateDeactivateAchievements();
|
ActivateDeactivateAchievements();
|
||||||
ActivateDeactivateLeaderboards();
|
ActivateDeactivateLeaderboards();
|
||||||
ActivateDeactivateRichPresence();
|
ActivateDeactivateRichPresence();
|
||||||
@ -366,6 +432,91 @@ void AchievementManager::ActivateDeactivateAchievement(AchievementId id, bool en
|
|||||||
rc_runtime_deactivate_achievement(&m_runtime, id);
|
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
|
// 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):
|
// 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
|
// Create a specific rc_api_X_request_t struct and populate with the necessary values
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#ifdef USE_RETRO_ACHIEVEMENTS
|
#ifdef USE_RETRO_ACHIEVEMENTS
|
||||||
|
#include <array>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
@ -18,6 +19,13 @@
|
|||||||
#include "Common/WorkQueueThread.h"
|
#include "Common/WorkQueueThread.h"
|
||||||
|
|
||||||
using AchievementId = u32;
|
using AchievementId = u32;
|
||||||
|
constexpr size_t RP_SIZE = 256;
|
||||||
|
using RichPresence = std::array<char, RP_SIZE>;
|
||||||
|
|
||||||
|
namespace Core
|
||||||
|
{
|
||||||
|
class System;
|
||||||
|
}
|
||||||
|
|
||||||
class AchievementManager
|
class AchievementManager
|
||||||
{
|
{
|
||||||
@ -45,6 +53,10 @@ public:
|
|||||||
void ActivateDeactivateLeaderboards();
|
void ActivateDeactivateLeaderboards();
|
||||||
void ActivateDeactivateRichPresence();
|
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 CloseGame();
|
||||||
void Logout();
|
void Logout();
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
@ -61,6 +73,14 @@ private:
|
|||||||
ResponseType FetchUnlockData(bool hardcore);
|
ResponseType FetchUnlockData(bool hardcore);
|
||||||
|
|
||||||
void ActivateDeactivateAchievement(AchievementId id, bool enabled, bool unofficial, bool encore);
|
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>
|
template <typename RcRequest, typename RcResponse>
|
||||||
ResponseType Request(RcRequest rc_request, RcResponse* rc_response,
|
ResponseType Request(RcRequest rc_request, RcResponse* rc_response,
|
||||||
@ -68,10 +88,13 @@ private:
|
|||||||
const std::function<int(RcResponse*, const char*)>& process_response);
|
const std::function<int(RcResponse*, const char*)>& process_response);
|
||||||
|
|
||||||
rc_runtime_t m_runtime{};
|
rc_runtime_t m_runtime{};
|
||||||
|
Core::System* m_system{};
|
||||||
bool m_is_runtime_initialized = false;
|
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{};
|
rc_api_fetch_game_data_response_t m_game_data{};
|
||||||
bool m_is_game_loaded = false;
|
bool m_is_game_loaded = false;
|
||||||
|
u64 m_last_ping_time = 0;
|
||||||
|
|
||||||
struct UnlockStatus
|
struct UnlockStatus
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user