Skip to content
Permalink
Browse files
Merge pull request #11669 from LillyJadeKatrin/retroachievements-rche…
…evos-integration

Retroachievements rcheevos integration
  • Loading branch information
delroth committed Apr 4, 2023
2 parents 7be5fc5 + 84b3df0 commit b63b574
Show file tree
Hide file tree
Showing 25 changed files with 398 additions and 2 deletions.
@@ -51,3 +51,6 @@
[submodule "Externals/gtest"]
path = Externals/gtest
url = https://github.com/google/googletest.git
[submodule "Externals/rcheevos/rcheevos"]
path = Externals/rcheevos/rcheevos
url = https://github.com/RetroAchievements/rcheevos.git
@@ -72,6 +72,7 @@ option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence, show the current gam
option(USE_MGBA "Enables GBA controllers emulation using libmgba" ON)
option(ENABLE_AUTOUPDATE "Enables support for automatic updates" ON)
option(STEAM "Creates a build for Steam" OFF)
option(USE_RETRO_ACHIEVEMENTS "Enables integration with retroachievements.org" ON)

# Maintainers: if you consider blanket disabling this for your users, please
# consider the following points:
@@ -975,6 +976,10 @@ add_subdirectory(Externals/rangeset)

add_subdirectory(Externals/FatFs)

if (USE_RETRO_ACHIEVEMENTS)
add_subdirectory(Externals/rcheevos)
endif()

########################################
# Pre-build events: Define configuration variables and write SCM info header
#
@@ -0,0 +1,49 @@
add_library(rcheevos
rcheevos/include/rc_api_editor.h
rcheevos/include/rc_api_info.h
rcheevos/include/rc_api_request.h
rcheevos/include/rc_api_runtime.h
rcheevos/include/rc_api_user.h
rcheevos/include/rc_consoles.h
rcheevos/include/rc_error.h
rcheevos/include/rc_hash.h
rcheevos/include/rcheevos.h
rcheevos/include/rc_runtime.h
rcheevos/include/rc_runtime_types.h
rcheevos/include/rc_url.h
rcheevos/src/rapi/rc_api_common.c
rcheevos/src/rapi/rc_api_common.h
rcheevos/src/rapi/rc_api_editor.c
rcheevos/src/rapi/rc_api_info.c
rcheevos/src/rapi/rc_api_runtime.c
rcheevos/src/rapi/rc_api_user.c
rcheevos/src/rcheevos/alloc.c
rcheevos/src/rcheevos/compat.c
rcheevos/src/rcheevos/condition.c
rcheevos/src/rcheevos/condset.c
rcheevos/src/rcheevos/consoleinfo.c
rcheevos/src/rcheevos/format.c
rcheevos/src/rcheevos/lboard.c
rcheevos/src/rcheevos/memref.c
rcheevos/src/rcheevos/operand.c
rcheevos/src/rcheevos/rc_compat.h
rcheevos/src/rcheevos/rc_internal.h
rcheevos/src/rcheevos/rc_validate.c
rcheevos/src/rcheevos/rc_validate.h
rcheevos/src/rcheevos/richpresence.c
rcheevos/src/rcheevos/runtime.c
rcheevos/src/rcheevos/runtime_progress.c
rcheevos/src/rcheevos/trigger.c
rcheevos/src/rcheevos/value.c
rcheevos/src/rhash/hash.c
rcheevos/src/rhash/md5.c
rcheevos/src/rhash/md5.h
rcheevos/src/rurl/url.c
)

target_include_directories(rcheevos PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/rcheevos/include")
target_include_directories(rcheevos INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}")
target_compile_definitions(rcheevos PRIVATE "RC_DISABLE_LUA=1" "RCHEEVOS_URL_SSL")
if(CMAKE_SYSTEM_NAME MATCHES "Windows")
target_compile_definitions(rcheevos PRIVATE "_CRT_SECURE_NO_WARNINGS")
endif()
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>$(ExternalsDir)rcheevos;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>
<ItemGroup>
<ProjectReference Include="$(ExternalsDir)rcheevos\rcheevos.vcxproj">
<Project>{CC99A910-3752-4465-95AA-7DC240D92A99}</Project>
</ProjectReference>
</ItemGroup>
</Project>
Submodule rcheevos added at c5304a
@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<Import Project="..\..\Source\VSProps\Base.Macros.props" />
<Import Project="$(VSPropsDir)Base.Targets.props" />
<PropertyGroup Label="Globals">
<ProjectGuid>{CC99A910-3752-4465-95AA-7DC240D92A99}</ProjectGuid>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<Import Project="$(VSPropsDir)Configuration.StaticLibrary.props" />
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings" />
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="$(VSPropsDir)Base.props" />
<Import Project="$(VSPropsDir)ClDisableAllWarnings.props" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<ItemGroup>
<ClCompile Include="rcheevos\src\rapi\rc_api_common.c" />
<ClCompile Include="rcheevos\src\rapi\rc_api_editor.c" />
<ClCompile Include="rcheevos\src\rapi\rc_api_info.c" />
<ClCompile Include="rcheevos\src\rapi\rc_api_runtime.c" />
<ClCompile Include="rcheevos\src\rapi\rc_api_user.c" />
<ClCompile Include="rcheevos\src\rcheevos\alloc.c" />
<ClCompile Include="rcheevos\src\rcheevos\compat.c" />
<ClCompile Include="rcheevos\src\rcheevos\condition.c" />
<ClCompile Include="rcheevos\src\rcheevos\condset.c" />
<ClCompile Include="rcheevos\src\rcheevos\consoleinfo.c" />
<ClCompile Include="rcheevos\src\rcheevos\format.c" />
<ClCompile Include="rcheevos\src\rcheevos\lboard.c" />
<ClCompile Include="rcheevos\src\rcheevos\memref.c" />
<ClCompile Include="rcheevos\src\rcheevos\operand.c" />
<ClCompile Include="rcheevos\src\rcheevos\rc_validate.c" />
<ClCompile Include="rcheevos\src\rcheevos\richpresence.c" />
<ClCompile Include="rcheevos\src\rcheevos\runtime.c" />
<ClCompile Include="rcheevos\src\rcheevos\runtime_progress.c" />
<ClCompile Include="rcheevos\src\rcheevos\trigger.c" />
<ClCompile Include="rcheevos\src\rcheevos\value.c" />
<ClCompile Include="rcheevos\src\rhash\hash.c" />
<ClCompile Include="rcheevos\src\rhash\md5.c" />
<ClCompile Include="rcheevos\src\rurl\url.c" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="rcheevos\include\rcheevos.h" />
<ClInclude Include="rcheevos\include\rc_api_editor.h" />
<ClInclude Include="rcheevos\include\rc_api_info.h" />
<ClInclude Include="rcheevos\include\rc_api_request.h" />
<ClInclude Include="rcheevos\include\rc_api_runtime.h" />
<ClInclude Include="rcheevos\include\rc_api_user.h" />
<ClInclude Include="rcheevos\include\rc_consoles.h" />
<ClInclude Include="rcheevos\include\rc_error.h" />
<ClInclude Include="rcheevos\include\rc_hash.h" />
<ClInclude Include="rcheevos\include\rc_runtime.h" />
<ClInclude Include="rcheevos\include\rc_runtime_types.h" />
<ClInclude Include="rcheevos\include\rc_url.h" />
<ClInclude Include="rcheevos\src\rapi\rc_api_common.h" />
<ClInclude Include="rcheevos\src\rcheevos\rc_compat.h" />
<ClInclude Include="rcheevos\src\rcheevos\rc_internal.h" />
<ClInclude Include="rcheevos\src\rcheevos\rc_validate.h" />
<ClInclude Include="rcheevos\src\rhash\md5.h" />
</ItemGroup>
<ItemDefinitionGroup>
<ClCompile>
<PreprocessorDefinitions>RC_DISABLE_LUA;RCHEEVOS_URL_SSL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(ProjectDir)rcheevos\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
@@ -112,6 +112,7 @@
#define LOGGER_CONFIG "Logger.ini"
#define DUALSHOCKUDPCLIENT_CONFIG "DSUClient.ini"
#define FREELOOK_CONFIG "FreeLook.ini"
#define RETROACHIEVEMENTS_CONFIG "RetroAchievements.ini"

// Files in the directory returned by GetUserPath(D_LOGS_IDX)
#define MAIN_LOG "dolphin.log"
@@ -160,7 +160,8 @@ static const std::map<System, std::string> system_to_name = {
{System::DualShockUDPClient, "DualShockUDPClient"},
{System::FreeLook, "FreeLook"},
{System::Session, "Session"},
{System::GameSettingsOnly, "GameSettingsOnly"}};
{System::GameSettingsOnly, "GameSettingsOnly"},
{System::Achievements, "Achievements"}};

const std::string& GetSystemName(System system)
{
@@ -34,6 +34,7 @@ enum class System
FreeLook,
Session,
GameSettingsOnly,
Achievements,
};

constexpr std::array<LayerType, 7> SEARCH_ORDER{{
@@ -847,6 +847,8 @@ static void RebuildUserDirectories(unsigned int dir_index)
s_user_paths[F_DUALSHOCKUDPCLIENTCONFIG_IDX] =
s_user_paths[D_CONFIG_IDX] + DUALSHOCKUDPCLIENT_CONFIG;
s_user_paths[F_FREELOOKCONFIG_IDX] = s_user_paths[D_CONFIG_IDX] + FREELOOK_CONFIG;
s_user_paths[F_RETROACHIEVEMENTSCONFIG_IDX] =
s_user_paths[D_CONFIG_IDX] + RETROACHIEVEMENTS_CONFIG;
s_user_paths[F_MAINLOG_IDX] = s_user_paths[D_LOGS_IDX] + MAIN_LOG;
s_user_paths[F_MEM1DUMP_IDX] = s_user_paths[D_DUMP_IDX] + MEM1_DUMP;
s_user_paths[F_MEM2DUMP_IDX] = s_user_paths[D_DUMP_IDX] + MEM2_DUMP;
@@ -85,6 +85,7 @@ enum
F_DUALSHOCKUDPCLIENTCONFIG_IDX,
F_FREELOOKCONFIG_IDX,
F_GBABIOS_IDX,
F_RETROACHIEVEMENTSCONFIG_IDX,
NUM_PATH_INDICES
};

@@ -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
@@ -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

0 comments on commit b63b574

Please sign in to comment.