This commit is contained in:
Chase Harkcom 2026-03-29 16:17:08 -04:00 committed by GitHub
commit c6006e174b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 396 additions and 58 deletions

3
.gitmodules vendored
View File

@ -106,3 +106,6 @@
[submodule "externals/libretro-common"]
path = externals/libretro-common/libretro-common
url = https://github.com/libretro/libretro-common.git
[submodule "externals/rcheevos/rcheevos"]
path = externals/rcheevos/rcheevos
url = https://github.com/RetroAchievements/rcheevos.git

View File

@ -136,6 +136,8 @@ option(ENABLE_SSE42 "Enable SSE4.2 optimizations on x86_64" ON)
option(ENABLE_DEVELOPER_OPTIONS "Enable functionality targeted at emulator developers" OFF)
option(ENABLE_RETROACHIEVEMENTS "Build with RetroAchievements support" OFF)
# Compile options
CMAKE_DEPENDENT_OPTION(COMPILE_WITH_DWARF "Add DWARF debugging information" ${IS_DEBUG_BUILD} "MINGW" OFF)
option(ENABLE_LTO "Enable link time optimization" ${DEFAULT_ENABLE_LTO})

View File

@ -519,4 +519,9 @@ elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|ARM64|armv8")
else()
target_compile_definitions(xxhash PRIVATE XXH_VECTOR=XXH_SCALAR)
message(STATUS "Disabling SIMD for xxHash")
endif()
endif()
# rcheevos
if (ENABLE_RETROACHIEVEMENTS)
add_subdirectory(rcheevos)
endif()

30
externals/rcheevos/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1,30 @@
add_library(rcheevos STATIC
rcheevos/src/rc_compat.c
rcheevos/src/rc_client.c
rcheevos/src/rc_util.c
rcheevos/src/rc_version.c
rcheevos/src/rcheevos/alloc.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_validate.c
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/md5.c
rcheevos/src/rapi/rc_api_common.c
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
)
target_include_directories(rcheevos PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/rcheevos/include
)

1
externals/rcheevos/rcheevos vendored Submodule

@ -0,0 +1 @@
Subproject commit e9ca3694c862b61235595176dac4b22677848c93

View File

@ -184,6 +184,11 @@ if(ENABLE_DEVELOPER_OPTIONS)
add_compile_definitions(ENABLE_DEVELOPER_OPTIONS)
endif()
if (ENABLE_RETROACHIEVEMENTS)
add_compile_definitions(ENABLE_RETROACHIEVEMENTS)
add_subdirectory(retroachievements)
endif()
add_subdirectory(common)
add_subdirectory(core)
add_subdirectory(video_core)

View File

@ -334,3 +334,7 @@ endif()
if (CITRA_USE_PRECOMPILED_HEADERS)
target_precompile_headers(citra_qt PRIVATE precompiled_headers.h)
endif()
if (ENABLE_RETROACHIEVEMENTS)
target_link_libraries(citra_qt PRIVATE retroachievements)
endif()

View File

@ -11,6 +11,10 @@
#include "citra_qt/uisettings.h"
#include "common/file_util.h"
#include "common/settings.h"
#include "core/core.h"
#ifdef ENABLE_RETROACHIEVEMENTS
#include "retroachievements/client.h"
#endif
#include "ui_configure_general.h"
// The QSlider doesn't have an easy way to set a custom step amount,
@ -47,6 +51,10 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)
ui->updates_group->setVisible(false);
#endif
#ifndef ENABLE_RETROACHIEVEMENTS
ui->retroachievements_group->setVisible(false);
#endif
SetupPerGameUI();
SetConfiguration();
@ -74,6 +82,9 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)
}
ui->change_screenshot_dir->setEnabled(true);
});
connect(ui->retroachievements_log_in_button, &QPushButton::clicked, this,
&ConfigureGeneral::RetroAchievementsLogIn);
}
ConfigureGeneral::~ConfigureGeneral() = default;
@ -196,6 +207,16 @@ void ConfigureGeneral::RetranslateUI() {
ui->retranslateUi(this);
}
void ConfigureGeneral::RetroAchievementsLogIn() {
#ifdef ENABLE_RETROACHIEVEMENTS
std::string username = ui->retroachievements_username_input->text().toStdString(),
password = ui->retroachievements_password_input->text().toStdString();
Core::System::GetInstance().RetroAchievementsClient().LogInUser(username.c_str(),
password.c_str());
#endif
}
void ConfigureGeneral::SetupPerGameUI() {
if (Settings::IsConfiguringGlobal()) {
ui->frame_limit->setEnabled(Settings::values.frame_limit.UsingGlobal());

View File

@ -24,6 +24,7 @@ public:
void ApplyConfiguration();
void RetranslateUI();
void SetConfiguration();
void RetroAchievementsLogIn();
void SetupPerGameUI();

View File

@ -295,7 +295,7 @@
</widget>
</item>
<item>
<widget class="QLabel" name="screenshot_dir_label">
<widget class="QLabel">
<property name="text">
<string>Save Screenshots To</string>
</property>
@ -317,6 +317,82 @@
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="retroachievements_group">
<property name="title">
<string>RetroAchievements</string>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QWidget" name="widget_retroachievements_username" native="true">
<layout class="QHBoxLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel">
<property name="text">
<string>Username</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="retroachievements_username_input"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="widget_retroachievements_password" native="true">
<layout class="QHBoxLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel">
<property name="text">
<string>Password</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="retroachievements_password_input">
<property name="echoMode">
<enum>QLineEdit::PasswordEchoOnEdit</enum>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QPushButton" name="retroachievements_log_in_button">
<property name="text">
<string>Log In</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item alignment="Qt::AlignRight">
<widget class="QPushButton" name="button_reset_defaults">
<property name="text">

View File

@ -137,7 +137,8 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
CLS(Movie) \
CLS(Loader) \
CLS(WebService) \
CLS(RPC_Server)
CLS(RPC_Server) \
CLS(RetroAchievements)
// GetClassName is a macro defined by Windows.h, grrr...
const char* GetLogClassName(Class log_class) {

View File

@ -52,60 +52,61 @@ enum class Class : u8 {
Applet_SWKBD, ///< The Software Keyboard applet
Service, ///< HLE implementation of system services. Each major service
///< should have its own subclass.
Service_SRV, ///< The SRV (Service Directory) implementation
Service_FRD, ///< The FRD (Friends) service
Service_FS, ///< The FS (Filesystem) service implementation
Service_ERR, ///< The ERR (Error) port implementation
Service_ACT, ///< The ACT (Account) service
Service_APT, ///< The APT (Applets) service
Service_BOSS, ///< The BOSS (SpotPass) service
Service_GSP, ///< The GSP (GPU control) service
Service_AC, ///< The AC (WiFi status) service
Service_AM, ///< The AM (Application manager) service
Service_PTM, ///< The PTM (Power status & misc.) service
Service_LDR, ///< The LDR (3ds dll loader) service
Service_MIC, ///< The MIC (Microphone) service
Service_NDM, ///< The NDM (Network daemon manager) service
Service_NFC, ///< The NFC service
Service_NIM, ///< The NIM (Network interface manager) service
Service_NS, ///< The NS (Nintendo User Interface Shell) service
Service_NWM, ///< The NWM (Network wlan manager) service
Service_CAM, ///< The CAM (Camera) service
Service_CECD, ///< The CECD (StreetPass) service
Service_CFG, ///< The CFG (Configuration) service
Service_CSND, ///< The CSND (CWAV format process) service
Service_DSP, ///< The DSP (DSP control) service
Service_DLP, ///< The DLP (Download Play) service
Service_HID, ///< The HID (Human interface device) service
Service_HTTP, ///< The HTTP service
Service_SOC, ///< The SOC (Socket) service
Service_IR, ///< The IR service
Service_Y2R, ///< The Y2R (YUV to RGB conversion) service
Service_PS, ///< The PS (Process) service
Service_PLGLDR, ///< The PLGLDR (plugin loader) service
Service_NEWS, ///< The NEWS (Notifications) service
HW, ///< Low-level hardware emulation
HW_Memory, ///< Memory-map and address translation
HW_LCD, ///< LCD register emulation
HW_GPU, ///< GPU control emulation
HW_AES, ///< AES engine emulation
HW_RSA, ///< RSA engine emulation
HW_ECC, ///< ECC engine emulation
Frontend, ///< Emulator UI
Render, ///< Emulator video output and hardware acceleration
Render_Software, ///< Software renderer backend
Render_OpenGL, ///< OpenGL backend
Render_Vulkan, ///< Vulkan backend
Audio, ///< Audio emulation
Audio_DSP, ///< The HLE and LLE implementations of the DSP
Audio_Sink, ///< Emulator audio output backend
Loader, ///< ROM loader
Input, ///< Input emulation
Network, ///< Network emulation
Movie, ///< Movie (Input Recording) Playback
WebService, ///< Interface to Citra Web Services
RPC_Server, ///< RPC server
Count, ///< Total number of logging classes
Service_SRV, ///< The SRV (Service Directory) implementation
Service_FRD, ///< The FRD (Friends) service
Service_FS, ///< The FS (Filesystem) service implementation
Service_ERR, ///< The ERR (Error) port implementation
Service_ACT, ///< The ACT (Account) service
Service_APT, ///< The APT (Applets) service
Service_BOSS, ///< The BOSS (SpotPass) service
Service_GSP, ///< The GSP (GPU control) service
Service_AC, ///< The AC (WiFi status) service
Service_AM, ///< The AM (Application manager) service
Service_PTM, ///< The PTM (Power status & misc.) service
Service_LDR, ///< The LDR (3ds dll loader) service
Service_MIC, ///< The MIC (Microphone) service
Service_NDM, ///< The NDM (Network daemon manager) service
Service_NFC, ///< The NFC service
Service_NIM, ///< The NIM (Network interface manager) service
Service_NS, ///< The NS (Nintendo User Interface Shell) service
Service_NWM, ///< The NWM (Network wlan manager) service
Service_CAM, ///< The CAM (Camera) service
Service_CECD, ///< The CECD (StreetPass) service
Service_CFG, ///< The CFG (Configuration) service
Service_CSND, ///< The CSND (CWAV format process) service
Service_DSP, ///< The DSP (DSP control) service
Service_DLP, ///< The DLP (Download Play) service
Service_HID, ///< The HID (Human interface device) service
Service_HTTP, ///< The HTTP service
Service_SOC, ///< The SOC (Socket) service
Service_IR, ///< The IR service
Service_Y2R, ///< The Y2R (YUV to RGB conversion) service
Service_PS, ///< The PS (Process) service
Service_PLGLDR, ///< The PLGLDR (plugin loader) service
Service_NEWS, ///< The NEWS (Notifications) service
HW, ///< Low-level hardware emulation
HW_Memory, ///< Memory-map and address translation
HW_LCD, ///< LCD register emulation
HW_GPU, ///< GPU control emulation
HW_AES, ///< AES engine emulation
HW_RSA, ///< RSA engine emulation
HW_ECC, ///< ECC engine emulation
Frontend, ///< Emulator UI
Render, ///< Emulator video output and hardware acceleration
Render_Software, ///< Software renderer backend
Render_OpenGL, ///< OpenGL backend
Render_Vulkan, ///< Vulkan backend
Audio, ///< Audio emulation
Audio_DSP, ///< The HLE and LLE implementations of the DSP
Audio_Sink, ///< Emulator audio output backend
Loader, ///< ROM loader
Input, ///< Input emulation
Network, ///< Network emulation
Movie, ///< Movie (Input Recording) Playback
WebService, ///< Interface to Citra Web Services
RPC_Server, ///< RPC server
RetroAchievements, ///< RetroAchievements
Count, ///< Total number of logging classes
};
} // namespace Common::Log

View File

@ -506,6 +506,11 @@ target_link_libraries(citra_core PUBLIC citra_common PRIVATE audio_core network
target_link_libraries(citra_core PRIVATE Boost::boost Boost::serialization Boost::iostreams httplib)
target_link_libraries(citra_core PUBLIC dds-ktx PRIVATE cryptopp fmt lodepng open_source_archives)
if (ENABLE_RETROACHIEVEMENTS)
target_link_libraries(citra_core PUBLIC retroachievements)
target_compile_definitions(citra_core PUBLIC ENABLE_RETROACHIEVEMENTS)
endif()
if (ENABLE_WEB_SERVICE)
target_link_libraries(citra_core PRIVATE web_service)
endif()

View File

@ -50,6 +50,9 @@
#include "core/rpc/server.h"
#endif
#include "network/network.h"
#ifdef ENABLE_RETROACHIEVEMENTS
#include "retroachievements/client.h"
#endif
#include "video_core/custom_textures/custom_tex_manager.h"
#include "video_core/gpu.h"
#include "video_core/renderer_base.h"
@ -73,7 +76,12 @@ Core::Timing& Global() {
return System::GetInstance().CoreTiming();
}
System::System() : movie{*this}, cheat_engine{*this} {}
System::System() : movie{*this}, cheat_engine{*this} {
#ifdef ENABLE_RETROACHIEVEMENTS
retroachievements_client = std::make_unique<RetroAchievements::Client>(*this);
retroachievements_client->Initialize();
#endif
}
System::~System() = default;
@ -653,6 +661,16 @@ const Cheats::CheatEngine& System::CheatEngine() const {
return cheat_engine;
}
#ifdef ENABLE_RETROACHIEVEMENTS
RetroAchievements::Client& System::RetroAchievementsClient() {
return *retroachievements_client;
}
const RetroAchievements::Client& System::RetroAchievementsClient() const {
return *retroachievements_client;
}
#endif
void System::RegisterVideoDumper(std::shared_ptr<VideoDumper::Backend> dumper) {
video_dumper = std::move(dumper);
}

View File

@ -70,6 +70,12 @@ namespace Loader {
class AppLoader;
}
#ifdef ENABLE_RETROACHIEVEMENTS
namespace RetroAchievements {
class Client;
} // namespace RetroAchievements
#endif
namespace Core {
class ARM_Interface;
@ -277,6 +283,14 @@ public:
/// Gets a const reference to the cheat engine
[[nodiscard]] const Cheats::CheatEngine& CheatEngine() const;
#ifdef ENABLE_RETROACHIEVEMENTS
// Gets a reference to the RetroAchievements client
[[nodiscard]] RetroAchievements::Client& RetroAchievementsClient();
// Gets a const reference to the RetroAchievements client
[[nodiscard]] const RetroAchievements::Client& RetroAchievementsClient() const;
#endif
/// Gets a reference to the custom texture cache system
[[nodiscard]] VideoCore::CustomTexManager& CustomTexManager();
@ -430,6 +444,11 @@ private:
/// Cheats manager
Cheats::CheatEngine cheat_engine;
#ifdef ENABLE_RETROACHIEVEMENTS
/// RetroAchievements
std::unique_ptr<RetroAchievements::Client> retroachievements_client;
#endif
/// Video dumper backend
std::shared_ptr<VideoDumper::Backend> video_dumper;

View File

@ -0,0 +1,6 @@
add_library(retroachievements STATIC
client.cpp
client.h
)
target_link_libraries(retroachievements PRIVATE citra_common httplib rcheevos)

View File

@ -0,0 +1,113 @@
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "client.h"
#include <cstring>
#include <string>
#include <httplib.h>
#include <rc_client.h>
#include <rc_error.h>
#include "common/logging/log.h"
#include "common/scm_rev.h"
namespace RetroAchievements {
static const char base_url[] = "https://retroachievements.org";
// TODO: Make this use a numeric version as per
// https://github.com/RetroAchievements/rcheevos/wiki/rc_client-integration#user-agent-header
static const std::string user_agent = std::string("Azahar/") + Common::g_build_fullname;
static const httplib::Headers headers = httplib::Headers({{"User-Agent", user_agent}});
namespace Callbacks {
// This is the function the rc_client will use to read memory for the emulator. we don't need it
// yet, so just provide a dummy function that returns "no memory read".
static uint32_t read_memory(uint32_t address, uint8_t* buffer, uint32_t num_bytes,
rc_client_t* client) {
// TODO: implement later
LOG_DEBUG(RetroAchievements, "Attempting to read memory.");
return 0;
}
static void server_call(const rc_api_request_t* request, rc_client_server_callback_t callback,
void* callback_data, rc_client_t* rc_client) {
LOG_DEBUG(RetroAchievements, "Attempting to call server.");
if (std::strstr(request->url, base_url) == nullptr) {
LOG_ERROR(RetroAchievements, "Invalid URL: \"{}\"", request->url);
return;
}
httplib::Client client(base_url);
const char* path = &request->url[sizeof(base_url) - 1];
// TODO: Should make this async?
httplib::Result result;
if (request->post_data) {
result = client.Post(path, headers, request->post_data, std::strlen(request->post_data),
request->content_type);
} else {
result = client.Get(path, headers);
}
if (result) {
LOG_DEBUG(RetroAchievements, "Status: {}", result->status);
LOG_DEBUG(RetroAchievements, "Body: {}", result->body);
rc_api_server_response_t server_response = {.body = result->body.c_str(),
.body_length = result->body.length(),
.http_status_code = result->status};
callback(&server_response, callback_data);
} else {
LOG_DEBUG(RetroAchievements, "HTTP error {}", result.error());
}
}
// Write log messages to the console
static void log_message(const char* message, const rc_client_t* client) {
LOG_DEBUG(RetroAchievements, "RetroAchievements internal message: \"{}\"", message);
}
} // namespace Callbacks
Client::Client(const Core::System& _system) : system{_system} {}
Client::~Client() {
if (rc_client) {
rc_client_destroy(rc_client);
rc_client = NULL;
}
}
void Client::Initialize() {
LOG_DEBUG(RetroAchievements, "Initializing RetroAchievements client.");
rc_client = rc_client_create(Callbacks::read_memory, Callbacks::server_call);
rc_client_enable_logging(rc_client, RC_CLIENT_LOG_LEVEL_VERBOSE, Callbacks::log_message);
rc_client_set_hardcore_enabled(rc_client, 0);
}
static void login_callback(int result, const char* error_message, rc_client_t* client,
void* userdata) {
// If not successful, just report the error and bail.
if (result != RC_OK) {
LOG_ERROR(RetroAchievements, "Login failed.");
return;
}
// Login was successful. Capture the token for future logins so we don't have to store the
// password anywhere.
const rc_client_user_t* user = rc_client_get_user_info(client);
// store_retroachievements_credentials(user->username, user->token);
// Inform user of successful login
LOG_INFO(RetroAchievements, "Logged in as {} ({} points)", user->display_name, user->score);
}
void Client::LogInUser(const char* username, const char* password) {
rc_client_begin_login_with_password(rc_client, username, password, login_callback, NULL);
}
} // namespace RetroAchievements

View File

@ -0,0 +1,27 @@
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
namespace Core {
class System;
}
struct rc_client_t;
namespace RetroAchievements {
class Client {
public:
explicit Client(const Core::System& system);
~Client();
void Initialize();
void LogInUser(const char* username, const char* password);
private:
const Core::System& system;
rc_client_t* rc_client = nullptr;
};
} // namespace RetroAchievements