Merge pull request #11730 from LillyJadeKatrin/retroachievements-load-game-data

RetroAchievements Load Game Data
This commit is contained in:
Admiral H. Curtiss 2023-04-12 11:50:50 +02:00 committed by GitHub
commit c5bbe0af02
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 182 additions and 12 deletions

@ -1 +1 @@
Subproject commit c5304a61bcf256ae80fcd1c8f64ad9646aaea757 Subproject commit d9e990e6d13527532b7e2bb23164a1f3b7f33bb5

View File

@ -4,10 +4,15 @@
#ifdef USE_RETRO_ACHIEVEMENTS #ifdef USE_RETRO_ACHIEVEMENTS
#include "Core/AchievementManager.h" #include "Core/AchievementManager.h"
#include <array>
#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 "Config/AchievementSettings.h"
#include "Core/Core.h" #include "Core/Core.h"
#include "DiscIO/Volume.h"
AchievementManager* AchievementManager::GetInstance() AchievementManager* AchievementManager::GetInstance()
{ {
@ -28,49 +33,185 @@ void AchievementManager::Init()
AchievementManager::ResponseType AchievementManager::Login(const std::string& password) AchievementManager::ResponseType AchievementManager::Login(const std::string& password)
{ {
if (!m_is_runtime_initialized)
return AchievementManager::ResponseType::MANAGER_NOT_INITIALIZED;
return VerifyCredentials(password); return VerifyCredentials(password);
} }
void AchievementManager::LoginAsync(const std::string& password, const LoginCallback& callback) void AchievementManager::LoginAsync(const std::string& password, const ResponseCallback& callback)
{ {
if (!m_is_runtime_initialized)
{
callback(AchievementManager::ResponseType::MANAGER_NOT_INITIALIZED);
return;
}
m_queue.EmplaceItem([this, password, callback] { callback(VerifyCredentials(password)); }); m_queue.EmplaceItem([this, password, callback] { callback(VerifyCredentials(password)); });
} }
bool AchievementManager::IsLoggedIn() const bool AchievementManager::IsLoggedIn() const
{ {
return m_login_data.response.succeeded; return !Config::Get(Config::RA_API_TOKEN).empty();
}
void AchievementManager::LoadGameByFilenameAsync(const std::string& iso_path,
const ResponseCallback& callback)
{
if (!m_is_runtime_initialized)
{
callback(AchievementManager::ResponseType::MANAGER_NOT_INITIALIZED);
return;
}
struct FilereaderState
{
int64_t position = 0;
std::unique_ptr<DiscIO::Volume> volume;
};
rc_hash_filereader volume_reader{
.open =
[](const char* path_utf8) {
auto state = std::make_unique<FilereaderState>();
state->volume = DiscIO::CreateVolume(path_utf8);
return reinterpret_cast<void*>(state.release());
},
.seek =
[](void* file_handle, int64_t offset, int origin) {
switch (origin)
{
case SEEK_SET:
reinterpret_cast<FilereaderState*>(file_handle)->position = offset;
break;
case SEEK_CUR:
reinterpret_cast<FilereaderState*>(file_handle)->position += offset;
break;
case SEEK_END:
// Unused
break;
}
},
.tell =
[](void* file_handle) {
return reinterpret_cast<FilereaderState*>(file_handle)->position;
},
.read =
[](void* file_handle, void* buffer, size_t requested_bytes) {
FilereaderState* filereader_state = reinterpret_cast<FilereaderState*>(file_handle);
bool success = (filereader_state->volume->Read(
filereader_state->position, requested_bytes, reinterpret_cast<u8*>(buffer),
DiscIO::PARTITION_NONE));
if (success)
{
filereader_state->position += requested_bytes;
return requested_bytes;
}
else
{
return static_cast<size_t>(0);
}
},
.close = [](void* file_handle) { delete reinterpret_cast<FilereaderState*>(file_handle); }};
rc_hash_init_custom_filereader(&volume_reader);
std::array<char, HASH_LENGTH> game_hash;
rc_hash_generate_from_file(game_hash.data(), RC_CONSOLE_GAMECUBE, iso_path.c_str());
m_queue.EmplaceItem([this, callback, game_hash] {
const auto resolve_hash_response = ResolveHash(game_hash);
if (resolve_hash_response != ResponseType::SUCCESS || m_game_id == 0)
{
callback(resolve_hash_response);
return;
}
const auto start_session_response = StartRASession();
if (start_session_response != ResponseType::SUCCESS)
{
callback(start_session_response);
return;
}
const auto fetch_game_data_response = FetchGameData();
m_is_game_loaded = fetch_game_data_response == ResponseType::SUCCESS;
callback(fetch_game_data_response);
});
}
void AchievementManager::CloseGame()
{
m_is_game_loaded = false;
m_game_id = 0;
m_queue.Cancel();
} }
void AchievementManager::Logout() void AchievementManager::Logout()
{ {
CloseGame();
Config::SetBaseOrCurrent(Config::RA_API_TOKEN, ""); Config::SetBaseOrCurrent(Config::RA_API_TOKEN, "");
rc_api_destroy_login_response(&m_login_data);
m_login_data.response.succeeded = 0;
} }
void AchievementManager::Shutdown() void AchievementManager::Shutdown()
{ {
CloseGame();
m_is_runtime_initialized = false; m_is_runtime_initialized = false;
m_queue.Shutdown(); m_queue.Shutdown();
// DON'T log out - keep those credentials for next run. // DON'T log out - keep those credentials for next run.
rc_api_destroy_login_response(&m_login_data);
m_login_data.response.succeeded = 0;
rc_runtime_destroy(&m_runtime); rc_runtime_destroy(&m_runtime);
} }
AchievementManager::ResponseType AchievementManager::VerifyCredentials(const std::string& password) AchievementManager::ResponseType AchievementManager::VerifyCredentials(const std::string& password)
{ {
rc_api_login_response_t login_data{};
std::string username = Config::Get(Config::RA_USERNAME); std::string username = Config::Get(Config::RA_USERNAME);
std::string api_token = Config::Get(Config::RA_API_TOKEN); std::string api_token = Config::Get(Config::RA_API_TOKEN);
rc_api_login_request_t login_request = { rc_api_login_request_t login_request = {
.username = username.c_str(), .api_token = api_token.c_str(), .password = password.c_str()}; .username = username.c_str(), .api_token = api_token.c_str(), .password = password.c_str()};
ResponseType r_type = Request<rc_api_login_request_t, rc_api_login_response_t>( ResponseType r_type = Request<rc_api_login_request_t, rc_api_login_response_t>(
login_request, &m_login_data, rc_api_init_login_request, rc_api_process_login_response); login_request, &login_data, rc_api_init_login_request, rc_api_process_login_response);
if (r_type == ResponseType::SUCCESS) if (r_type == ResponseType::SUCCESS)
Config::SetBaseOrCurrent(Config::RA_API_TOKEN, m_login_data.api_token); Config::SetBaseOrCurrent(Config::RA_API_TOKEN, login_data.api_token);
rc_api_destroy_login_response(&login_data);
return r_type; return r_type;
} }
AchievementManager::ResponseType
AchievementManager::ResolveHash(std::array<char, HASH_LENGTH> game_hash)
{
rc_api_resolve_hash_response_t hash_data{};
std::string username = Config::Get(Config::RA_USERNAME);
std::string api_token = Config::Get(Config::RA_API_TOKEN);
rc_api_resolve_hash_request_t resolve_hash_request = {
.username = username.c_str(), .api_token = api_token.c_str(), .game_hash = game_hash.data()};
ResponseType r_type = Request<rc_api_resolve_hash_request_t, rc_api_resolve_hash_response_t>(
resolve_hash_request, &hash_data, rc_api_init_resolve_hash_request,
rc_api_process_resolve_hash_response);
if (r_type == ResponseType::SUCCESS)
m_game_id = hash_data.game_id;
rc_api_destroy_resolve_hash_response(&hash_data);
return r_type;
}
AchievementManager::ResponseType AchievementManager::StartRASession()
{
rc_api_start_session_response_t session_data{};
std::string username = Config::Get(Config::RA_USERNAME);
std::string api_token = Config::Get(Config::RA_API_TOKEN);
rc_api_start_session_request_t start_session_request = {
.username = username.c_str(), .api_token = api_token.c_str(), .game_id = m_game_id};
ResponseType r_type = Request<rc_api_start_session_request_t, rc_api_start_session_response_t>(
start_session_request, &session_data, rc_api_init_start_session_request,
rc_api_process_start_session_response);
rc_api_destroy_start_session_response(&session_data);
return r_type;
}
AchievementManager::ResponseType AchievementManager::FetchGameData()
{
std::string username = Config::Get(Config::RA_USERNAME);
std::string api_token = Config::Get(Config::RA_API_TOKEN);
rc_api_fetch_game_data_request_t fetch_data_request = {
.username = username.c_str(), .api_token = api_token.c_str(), .game_id = m_game_id};
return Request<rc_api_fetch_game_data_request_t, rc_api_fetch_game_data_response_t>(
fetch_data_request, &m_game_data, rc_api_init_fetch_game_data_request,
rc_api_process_fetch_game_data_response);
}
// 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

View File

@ -9,6 +9,7 @@
#include <string> #include <string>
#include <thread> #include <thread>
#include <rcheevos/include/rc_api_runtime.h>
#include <rcheevos/include/rc_api_user.h> #include <rcheevos/include/rc_api_user.h>
#include <rcheevos/include/rc_runtime.h> #include <rcheevos/include/rc_runtime.h>
@ -21,24 +22,33 @@ public:
enum class ResponseType enum class ResponseType
{ {
SUCCESS, SUCCESS,
MANAGER_NOT_INITIALIZED,
INVALID_CREDENTIALS, INVALID_CREDENTIALS,
CONNECTION_FAILED, CONNECTION_FAILED,
UNKNOWN_FAILURE UNKNOWN_FAILURE
}; };
using LoginCallback = std::function<void(ResponseType)>; using ResponseCallback = std::function<void(ResponseType)>;
static AchievementManager* GetInstance(); static AchievementManager* GetInstance();
void Init(); void Init();
ResponseType Login(const std::string& password); ResponseType Login(const std::string& password);
void LoginAsync(const std::string& password, const LoginCallback& callback); void LoginAsync(const std::string& password, const ResponseCallback& callback);
bool IsLoggedIn() const; bool IsLoggedIn() const;
void LoadGameByFilenameAsync(const std::string& iso_path, const ResponseCallback& callback);
void CloseGame();
void Logout(); void Logout();
void Shutdown(); void Shutdown();
private: private:
AchievementManager() = default; AchievementManager() = default;
static constexpr int HASH_LENGTH = 33;
ResponseType VerifyCredentials(const std::string& password); ResponseType VerifyCredentials(const std::string& password);
ResponseType ResolveHash(std::array<char, HASH_LENGTH> game_hash);
ResponseType StartRASession();
ResponseType FetchGameData();
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,
@ -47,7 +57,10 @@ private:
rc_runtime_t m_runtime{}; rc_runtime_t m_runtime{};
bool m_is_runtime_initialized = false; bool m_is_runtime_initialized = false;
rc_api_login_response_t m_login_data{}; unsigned int m_game_id = 0;
rc_api_fetch_game_data_response_t m_game_data{};
bool m_is_game_loaded = false;
Common::WorkQueueThread<std::function<void()>> m_queue; Common::WorkQueueThread<std::function<void()>> m_queue;
}; // class AchievementManager }; // class AchievementManager

View File

@ -29,6 +29,7 @@
#include "Common/IniFile.h" #include "Common/IniFile.h"
#include "Common/Logging/Log.h" #include "Common/Logging/Log.h"
#include "Core/AchievementManager.h"
#include "Core/Boot/Boot.h" #include "Core/Boot/Boot.h"
#include "Core/Config/MainSettings.h" #include "Core/Config/MainSettings.h"
#include "Core/Config/SYSCONFSettings.h" #include "Core/Config/SYSCONFSettings.h"
@ -163,6 +164,16 @@ bool BootCore(std::unique_ptr<BootParameters> boot, const WindowSystemInfo& wsi)
} }
} }
#ifdef USE_RETRO_ACHIEVEMENTS
std::string path = "";
if (std::holds_alternative<BootParameters::Disc>(boot->parameters))
{
path = std::get<BootParameters::Disc>(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) && const bool load_ipl = !StartUp.bWii && !Config::Get(Config::MAIN_SKIP_IPL) &&
std::holds_alternative<BootParameters::Disc>(boot->parameters); std::holds_alternative<BootParameters::Disc>(boot->parameters);
if (load_ipl) if (load_ipl)

View File

@ -38,6 +38,7 @@
#include "Common/Timer.h" #include "Common/Timer.h"
#include "Common/Version.h" #include "Common/Version.h"
#include "Core/AchievementManager.h"
#include "Core/Boot/Boot.h" #include "Core/Boot/Boot.h"
#include "Core/BootManager.h" #include "Core/BootManager.h"
#include "Core/Config/MainSettings.h" #include "Core/Config/MainSettings.h"
@ -283,6 +284,10 @@ void Stop() // - Hammertime!
if (GetState() == State::Stopping || GetState() == State::Uninitialized) if (GetState() == State::Stopping || GetState() == State::Uninitialized)
return; return;
#ifdef USE_RETRO_ACHIEVEMENTS
AchievementManager::GetInstance()->CloseGame();
#endif // USE_RETRO_ACHIEVEMENTS
s_is_stopping = true; s_is_stopping = true;
CallOnStateChangedCallbacks(State::Stopping); CallOnStateChangedCallbacks(State::Stopping);