Added Achievement Manager with Login

Added AchievementManager class. Upon startup (currently only in DolphinQt), logs into RetroAchievements with the login credentials stored in achievements.ini.

Co-authored-by: AdmiralCurtiss <AdmiralCurtiss@users.noreply.github.com>
This commit is contained in:
LillyJadeKatrin 2023-03-18 12:38:53 -04:00
parent 07d2f3d305
commit 84b3df0af2
5 changed files with 182 additions and 0 deletions

View File

@ -0,0 +1,114 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#ifdef USE_RETRO_ACHIEVEMENTS
#include "Core/AchievementManager.h"
#include "Common/HttpRequest.h"
#include "Common/WorkQueueThread.h"
#include "Config/AchievementSettings.h"
#include "Core/Core.h"
AchievementManager* AchievementManager::GetInstance()
{
static AchievementManager s_instance;
return &s_instance;
}
void AchievementManager::Init()
{
if (!m_is_runtime_initialized && Config::Get(Config::RA_ENABLED))
{
rc_runtime_init(&m_runtime);
m_is_runtime_initialized = true;
m_queue.Reset("AchievementManagerQueue", [](const std::function<void()>& func) { func(); });
LoginAsync("", [](ResponseType r_type) {});
}
}
AchievementManager::ResponseType AchievementManager::Login(const std::string& password)
{
return VerifyCredentials(password);
}
void AchievementManager::LoginAsync(const std::string& password, const LoginCallback& callback)
{
m_queue.EmplaceItem([this, password, callback] { callback(VerifyCredentials(password)); });
}
bool AchievementManager::IsLoggedIn() const
{
return m_login_data.response.succeeded;
}
void AchievementManager::Logout()
{
Config::SetBaseOrCurrent(Config::RA_API_TOKEN, "");
rc_api_destroy_login_response(&m_login_data);
m_login_data.response.succeeded = 0;
}
void AchievementManager::Shutdown()
{
m_is_runtime_initialized = false;
m_queue.Shutdown();
// 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);
}
AchievementManager::ResponseType AchievementManager::VerifyCredentials(const std::string& password)
{
std::string username = Config::Get(Config::RA_USERNAME);
std::string api_token = Config::Get(Config::RA_API_TOKEN);
rc_api_login_request_t login_request = {
.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>(
login_request, &m_login_data, rc_api_init_login_request, rc_api_process_login_response);
if (r_type == ResponseType::SUCCESS)
Config::SetBaseOrCurrent(Config::RA_API_TOKEN, m_login_data.api_token);
return r_type;
}
// 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
// Call rc_api_init_X_request to convert this into a generic rc_api_request_t struct
// Perform the HTTP request using the url and post_data in the rc_api_request_t struct
// Call rc_api_process_X_response to convert the raw string HTTP response into a
// rc_api_X_response_t struct
// Use the data in the rc_api_X_response_t struct as needed
// Call rc_api_destroy_X_response when finished with the response struct to free memory
template <typename RcRequest, typename RcResponse>
AchievementManager::ResponseType AchievementManager::Request(
RcRequest rc_request, RcResponse* rc_response,
const std::function<int(rc_api_request_t*, const RcRequest*)>& init_request,
const std::function<int(RcResponse*, const char*)>& process_response)
{
rc_api_request_t api_request;
Common::HttpRequest http_request;
init_request(&api_request, &rc_request);
auto http_response = http_request.Post(api_request.url, api_request.post_data);
rc_api_destroy_request(&api_request);
if (http_response.has_value() && http_response->size() > 0)
{
const std::string response_str(http_response->begin(), http_response->end());
process_response(rc_response, response_str.c_str());
if (rc_response->response.succeeded)
{
return ResponseType::SUCCESS;
}
else
{
Logout();
return ResponseType::INVALID_CREDENTIALS;
}
}
else
{
return ResponseType::CONNECTION_FAILED;
}
}
#endif // USE_RETRO_ACHIEVEMENTS

View File

@ -0,0 +1,54 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#ifdef USE_RETRO_ACHIEVEMENTS
#include <functional>
#include <mutex>
#include <string>
#include <thread>
#include <rcheevos/include/rc_api_user.h>
#include <rcheevos/include/rc_runtime.h>
#include "Common/Event.h"
#include "Common/WorkQueueThread.h"
class AchievementManager
{
public:
enum class ResponseType
{
SUCCESS,
INVALID_CREDENTIALS,
CONNECTION_FAILED,
UNKNOWN_FAILURE
};
using LoginCallback = std::function<void(ResponseType)>;
static AchievementManager* GetInstance();
void Init();
ResponseType Login(const std::string& password);
void LoginAsync(const std::string& password, const LoginCallback& callback);
bool IsLoggedIn() const;
void Logout();
void Shutdown();
private:
AchievementManager() = default;
ResponseType VerifyCredentials(const std::string& password);
template <typename RcRequest, typename RcResponse>
ResponseType Request(RcRequest rc_request, RcResponse* rc_response,
const std::function<int(rc_api_request_t*, const RcRequest*)>& init_request,
const std::function<int(RcResponse*, const char*)>& process_response);
rc_runtime_t m_runtime{};
bool m_is_runtime_initialized = false;
rc_api_login_response_t m_login_data{};
Common::WorkQueueThread<std::function<void()>> m_queue;
}; // class AchievementManager
#endif // USE_RETRO_ACHIEVEMENTS

View File

@ -1,4 +1,6 @@
add_library(core
AchievementManager.cpp
AchievementManager.h
ActionReplay.cpp
ActionReplay.h
ARDecrypt.cpp

View File

@ -161,6 +161,7 @@
<ClInclude Include="Common\WindowsRegistry.h" />
<ClInclude Include="Common\WindowSystemInfo.h" />
<ClInclude Include="Common\WorkQueueThread.h" />
<ClInclude Include="Core\AchievementManager.h" />
<ClInclude Include="Core\ActionReplay.h" />
<ClInclude Include="Core\ARDecrypt.h" />
<ClInclude Include="Core\Boot\Boot.h" />
@ -796,6 +797,7 @@
<ClCompile Include="Common\UPnP.cpp" />
<ClCompile Include="Common\WindowsRegistry.cpp" />
<ClCompile Include="Common\Version.cpp" />
<ClCompile Include="Core\AchievementManager.cpp" />
<ClCompile Include="Core\ActionReplay.cpp" />
<ClCompile Include="Core\ARDecrypt.cpp" />
<ClCompile Include="Core\Boot\Boot_BS2Emu.cpp" />

View File

@ -37,6 +37,7 @@
#include "Common/Version.h"
#include "Common/WindowSystemInfo.h"
#include "Core/AchievementManager.h"
#include "Core/Boot/Boot.h"
#include "Core/BootManager.h"
#include "Core/CommonTitles.h"
@ -222,6 +223,11 @@ MainWindow::MainWindow(std::unique_ptr<BootParameters> boot_parameters,
InitControllers();
#ifdef USE_RETRO_ACHIEVEMENTS
// This has to be done before CreateComponents() so it's initialized.
AchievementManager::GetInstance()->Init();
#endif // USE_RETRO_ACHIEVEMENTS
CreateComponents();
ConnectGameList();
@ -301,6 +307,10 @@ MainWindow::~MainWindow()
Settings::Instance().ResetNetPlayClient();
Settings::Instance().ResetNetPlayServer();
#ifdef USE_RETRO_ACHIEVEMENTS
AchievementManager::GetInstance()->Shutdown();
#endif // USE_RETRO_ACHIEVEMENTS
delete m_render_widget;
delete m_netplay_dialog;