From 84b3df0af276e47a2d08885001448ee454357b61 Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Sat, 18 Mar 2023 12:38:53 -0400 Subject: [PATCH] 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 --- Source/Core/Core/AchievementManager.cpp | 114 ++++++++++++++++++++++++ Source/Core/Core/AchievementManager.h | 54 +++++++++++ Source/Core/Core/CMakeLists.txt | 2 + Source/Core/DolphinLib.props | 2 + Source/Core/DolphinQt/MainWindow.cpp | 10 +++ 5 files changed, 182 insertions(+) create mode 100644 Source/Core/Core/AchievementManager.cpp create mode 100644 Source/Core/Core/AchievementManager.h diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp new file mode 100644 index 0000000000..7e0fa5a9d9 --- /dev/null +++ b/Source/Core/Core/AchievementManager.cpp @@ -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& 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( + 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 +AchievementManager::ResponseType AchievementManager::Request( + RcRequest rc_request, RcResponse* rc_response, + const std::function& init_request, + const std::function& 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 diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h new file mode 100644 index 0000000000..91fd9339ef --- /dev/null +++ b/Source/Core/Core/AchievementManager.h @@ -0,0 +1,54 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#ifdef USE_RETRO_ACHIEVEMENTS +#include +#include +#include +#include + +#include +#include + +#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; + + 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 + ResponseType Request(RcRequest rc_request, RcResponse* rc_response, + const std::function& init_request, + const std::function& process_response); + + rc_runtime_t m_runtime{}; + bool m_is_runtime_initialized = false; + rc_api_login_response_t m_login_data{}; + Common::WorkQueueThread> m_queue; +}; // class AchievementManager + +#endif // USE_RETRO_ACHIEVEMENTS diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index a53eb20705..fcba3cba14 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -1,4 +1,6 @@ add_library(core + AchievementManager.cpp + AchievementManager.h ActionReplay.cpp ActionReplay.h ARDecrypt.cpp diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 4cada3066e..44048669a6 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -161,6 +161,7 @@ + @@ -796,6 +797,7 @@ + diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index ac34999c4d..c1566fb795 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -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 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;