Create rcheevos integration stub

This commit is contained in:
Chase Harkcom 2026-02-17 20:53:33 -07:00
parent a5b7e9012f
commit f993fa3da4
10 changed files with 162 additions and 5 deletions

View File

@ -513,4 +513,7 @@ 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
add_subdirectory(rcheevos)

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
)

View File

@ -188,6 +188,7 @@ add_subdirectory(video_core)
add_subdirectory(audio_core)
add_subdirectory(network)
add_subdirectory(input_common)
add_subdirectory(rcheevos_integration)
if (ENABLE_TESTS)
add_subdirectory(tests)

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(Rcheevos)
// GetClassName is a macro defined by Windows.h, grrr...
const char* GetLogClassName(Class log_class) {

View File

@ -106,6 +106,7 @@ enum class Class : u8 {
WebService, ///< Interface to Citra Web Services
RPC_Server, ///< RPC server
Count, ///< Total number of logging classes
Rcheevos, ///< RetroAchievements
};
} // namespace Common::Log

View File

@ -500,7 +500,7 @@ add_library(citra_core STATIC
create_target_directory_groups(citra_core)
target_link_libraries(citra_core PUBLIC citra_common PRIVATE audio_core network video_core)
target_link_libraries(citra_core PUBLIC citra_common PRIVATE audio_core network video_core rcheevos_integration)
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)

View File

@ -50,6 +50,7 @@
#include "core/rpc/server.h"
#endif
#include "network/network.h"
#include "rcheevos_integration/rcheevos_integration.h"
#include "video_core/custom_textures/custom_tex_manager.h"
#include "video_core/gpu.h"
#include "video_core/renderer_base.h"
@ -73,9 +74,13 @@ Core::Timing& Global() {
return System::GetInstance().CoreTiming();
}
System::System() : movie{*this}, cheat_engine{*this} {}
System::System() : movie{*this}, cheat_engine{*this} {
initialize_retroachievements_client();
}
System::~System() = default;
System::~System() {
shutdown_retroachievements_client();
}
System::ResultStatus System::RunLoop(bool tight_loop) {
status = ResultStatus::Success;

View File

@ -0,0 +1,6 @@
add_library(rcheevos_integration STATIC
rcheevos_integration.cpp
rcheevos_integration.h
)
target_link_libraries(rcheevos_integration PUBLIC rcheevos PRIVATE citra_common)

View File

@ -0,0 +1,102 @@
#include "rcheevos_integration.h"
#include <rc_client.h>
#include "common/logging/log.h"
rc_client_t* g_client = NULL;
// 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(Rcheevos, "Attempting to read memory.");
return 0;
}
// // This is the callback function for the asynchronous HTTP call (which is not provided in this example)
// static void http_callback(int status_code, const char* content, size_t content_size, void* userdata, const char* error_message)
// {
// // Prepare a data object to pass the HTTP response to the callback
// rc_api_server_response_t server_response;
// memset(&server_response, 0, sizeof(server_response));
// server_response.body = content;
// server_response.body_length = content_size;
// server_response.http_status_code = status_code;
// // handle non-http errors (socket timeout, no internet available, etc)
// if (status_code == 0 && error_message) {
// // assume no server content and pass the error through instead
// server_response.body = error_message;
// server_response.body_length = strlen(error_message);
// // Let rc_client know the error was not catastrophic and could be retried. It may decide to retry or just
// // immediately pass the error to the callback. To prevent possible retries, use RC_API_SERVER_RESPONSE_CLIENT_ERROR.
// server_response.http_status_code = RC_API_SERVER_RESPONSE_RETRYABLE_CLIENT_ERROR;
// }
// // Get the rc_client callback and call it
// async_callback_data* async_data = (async_callback_data*)userdata;
// async_data->callback(&server_response, async_data->callback_data);
// // Release the captured rc_client callback data
// free(async_data);
// }
// This is the HTTP request dispatcher that is provided to the rc_client. Whenever the client
// needs to talk to the server, it will call this function.
static void server_call(const rc_api_request_t* request,
rc_client_server_callback_t callback, void* callback_data, rc_client_t* client)
{
LOG_DEBUG(Rcheevos, "Attempting to call server.");
// // RetroAchievements may not allow hardcore unlocks if we don't properly identify ourselves.
// const char* user_agent = "MyClient/1.2";
// // callback must be called with callback_data, regardless of the outcome of the HTTP call.
// // Since we're making the HTTP call asynchronously, we need to capture them and pass it
// // through the async HTTP code.
// async_callback_data* async_data = malloc(sizeof(async_callback_data));
// async_data->callback = callback;
// async_data->callback_data = callback_data;
// // If post data is provided, we need to make a POST request, otherwise, a GET request will suffice.
// if (request->post_data)
// async_http_post(request->url, request->post_data, user_agent, http_callback, async_data);
// else
// async_http_get(request->url, user_agent, http_callback, async_data);
}
// Write log messages to the console
static void log_message(const char* message, const rc_client_t* client)
{
LOG_DEBUG(Rcheevos, "Rcheevos internal message: \"{}\"", message);
}
void initialize_retroachievements_client()
{
LOG_DEBUG(Rcheevos, "Initializing RA client.");
// Create the client instance (using a global variable simplifies this example)
g_client = rc_client_create(read_memory, server_call);
// Provide a logging function to simplify debugging
rc_client_enable_logging(g_client, RC_CLIENT_LOG_LEVEL_VERBOSE, log_message);
// Disable hardcore - if we goof something up in the implementation, we don't want our
// account disabled for cheating.
rc_client_set_hardcore_enabled(g_client, 0);
}
void shutdown_retroachievements_client()
{
LOG_DEBUG(Rcheevos, "Shutting down RA client.");
if (g_client)
{
// Release resources associated to the client instance
rc_client_destroy(g_client);
g_client = NULL;
}
}

View File

@ -0,0 +1,8 @@
#pragma once
#include <rc_client.h>
extern rc_client_t* g_client;
void initialize_retroachievements_client();
void shutdown_retroachievements_client();