dep/rcheevos: Bump to 8afec6c

This commit is contained in:
Stenzek 2023-11-30 14:06:00 +10:00
parent 78ef9e1105
commit 8431d3be0c
No known key found for this signature in database
11 changed files with 1095 additions and 157 deletions

View file

@ -0,0 +1,35 @@
#ifndef RC_CLIENT_RAINTEGRATION_H
#define RC_CLIENT_RAINTEGRATION_H
#ifdef __cplusplus
extern "C" {
#endif
#ifndef _WIN32
#undef RC_CLIENT_SUPPORTS_RAINTEGRATION /* Windows required for RAIntegration */
#endif
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
#ifndef RC_CLIENT_SUPPORTS_EXTERNAL
#define RC_CLIENT_SUPPORTS_EXTERNAL /* external rc_client required for RAIntegration */
#endif
#include "rc_client.h"
#include <wtypes.h> /* HWND */
rc_client_async_handle_t* rc_client_begin_load_raintegration(rc_client_t* client,
const wchar_t* search_directory, HWND main_window_handle,
const char* client_name, const char* client_version,
rc_client_callback_t callback, void* callback_userdata);
void rc_client_unload_raintegration(rc_client_t* client);
#endif /* RC_CLIENT_SUPPORTS_RAINTEGRATION */
#ifdef __cplusplus
}
#endif
#endif /* RC_CLIENT_RAINTEGRATION_H */

View file

@ -23,10 +23,6 @@
#define RC_CLIENT_UNKNOWN_GAME_ID (uint32_t)-1
#define RC_CLIENT_RECENT_UNLOCK_DELAY_SECONDS (10 * 60) /* ten minutes */
struct rc_client_async_handle_t {
uint8_t aborted;
};
enum {
RC_CLIENT_ASYNC_NOT_ABORTED = 0,
RC_CLIENT_ASYNC_ABORTED = 1,
@ -131,6 +127,11 @@ void rc_client_destroy(rc_client_t* client)
rc_client_unload_game(client);
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->destroy)
client->state.external_client->destroy();
#endif
rc_buffer_destroy(&client->state.buffer);
rc_mutex_destroy(&client->state.mutex);
@ -227,6 +228,11 @@ void rc_client_enable_logging(rc_client_t* client, int level, rc_client_message_
{
client->callbacks.log_call = callback;
client->state.log_level = callback ? level : RC_CLIENT_LOG_LEVEL_NONE;
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->enable_logging)
client->state.external_client->enable_logging(client, level, callback);
#endif
}
/* ===== Common ===== */
@ -291,6 +297,22 @@ static rc_clock_t rc_client_clock_get_now_millisecs(const rc_client_t* client)
void rc_client_set_get_time_millisecs_function(rc_client_t* client, rc_get_time_millisecs_func_t handler)
{
client->callbacks.get_time_millisecs = handler ? handler : rc_client_clock_get_now_millisecs;
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->set_get_time_millisecs)
client->state.external_client->set_get_time_millisecs(client, handler);
#endif
}
int rc_client_async_handle_aborted(rc_client_t* client, rc_client_async_handle_t* async_handle)
{
int aborted;
rc_mutex_lock(&client->state.mutex);
aborted = async_handle->aborted;
rc_mutex_unlock(&client->state.mutex);
return aborted;
}
static void rc_client_begin_async(rc_client_t* client, rc_client_async_handle_t* async_handle)
@ -333,12 +355,41 @@ static int rc_client_end_async(rc_client_t* client, rc_client_async_handle_t* as
void rc_client_abort_async(rc_client_t* client, rc_client_async_handle_t* async_handle)
{
if (async_handle && client) {
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->abort_async) {
client->state.external_client->abort_async(async_handle);
return;
}
#endif
rc_mutex_lock(&client->state.mutex);
async_handle->aborted = RC_CLIENT_ASYNC_ABORTED;
rc_mutex_unlock(&client->state.mutex);
}
}
static int rc_client_async_handle_valid(rc_client_t* client, rc_client_async_handle_t* async_handle)
{
int valid = 0;
size_t i;
/* there is a small window of opportunity where the client could have been destroyed before calling
* this function, but this function assumes the possibility that the handle has been destroyed, so
* we can't check it for RC_CLIENT_ASYNC_DESTROYED before attempting to scan the client data */
rc_mutex_lock(&client->state.mutex);
for (i = 0; i < sizeof(client->state.async_handles) / sizeof(client->state.async_handles[0]); ++i) {
if (client->state.async_handles[i] == async_handle) {
valid = 1;
break;
}
}
rc_mutex_unlock(&client->state.mutex);
return valid;
}
static const char* rc_client_server_error_message(int* result, int http_status_code, const rc_api_response_t* response)
{
if (!response->succeeded) {
@ -461,6 +512,10 @@ static int rc_client_should_retry(const rc_api_server_response_t* server_respons
/* connection to server from cloudfare was dropped before request was completed */
return 1;
case 525: /* 525 SSL Handshake Failed */
/* web server worker connection pool is exhausted */
return 1;
case RC_API_SERVER_RESPONSE_RETRYABLE_CLIENT_ERROR:
/* client provided non-HTTP error (explicitly retryable) */
return 1;
@ -614,7 +669,13 @@ static rc_client_async_handle_t* rc_client_begin_login(rc_client_t* client,
rc_api_destroy_request(&request);
return &callback_data->async_handle;
/* if the user state has changed, the async operation completed synchronously */
rc_mutex_lock(&client->state.mutex);
if (client->state.user != RC_CLIENT_USER_STATE_LOGIN_REQUESTED)
callback_data = NULL;
rc_mutex_unlock(&client->state.mutex);
return callback_data ? &callback_data->async_handle : NULL;
}
rc_client_async_handle_t* rc_client_begin_login_with_password(rc_client_t* client,
@ -637,6 +698,11 @@ rc_client_async_handle_t* rc_client_begin_login_with_password(rc_client_t* clien
return NULL;
}
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->begin_login_with_password)
return client->state.external_client->begin_login_with_password(client, username, password, callback, callback_userdata);
#endif
memset(&login_request, 0, sizeof(login_request));
login_request.username = username;
login_request.password = password;
@ -665,6 +731,11 @@ rc_client_async_handle_t* rc_client_begin_login_with_token(rc_client_t* client,
return NULL;
}
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->begin_login_with_token)
return client->state.external_client->begin_login_with_token(client, username, token, callback, callback_userdata);
#endif
memset(&login_request, 0, sizeof(login_request));
login_request.username = username;
login_request.api_token = token;
@ -680,6 +751,13 @@ void rc_client_logout(rc_client_t* client)
if (!client)
return;
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->logout) {
client->state.external_client->logout();
return;
}
#endif
switch (client->state.user) {
case RC_CLIENT_USER_STATE_LOGGED_IN:
RC_CLIENT_LOG_INFO_FORMATTED(client, "Logging %s out", client->user.display_name);
@ -707,7 +785,15 @@ void rc_client_logout(rc_client_t* client)
const rc_client_user_t* rc_client_get_user_info(const rc_client_t* client)
{
return (client && client->state.user == RC_CLIENT_USER_STATE_LOGGED_IN) ? &client->user : NULL;
if (!client)
return NULL;
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->get_user_info)
return client->state.external_client->get_user_info();
#endif
return (client->state.user == RC_CLIENT_USER_STATE_LOGGED_IN) ? &client->user : NULL;
}
int rc_client_user_get_image_url(const rc_client_user_t* user, char buffer[], size_t buffer_size)
@ -758,7 +844,17 @@ void rc_client_get_user_game_summary(const rc_client_t* client, rc_client_user_g
return;
memset(summary, 0, sizeof(*summary));
if (!client || !client->game)
if (!client)
return;
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->get_user_game_summary) {
client->state.external_client->get_user_game_summary(summary);
return;
}
#endif
if (!client->game)
return;
rc_mutex_lock((rc_mutex_t*)&client->state.mutex); /* remove const cast for mutex access */
@ -2111,6 +2207,11 @@ rc_client_async_handle_t* rc_client_begin_load_game(rc_client_t* client, const c
return NULL;
}
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->begin_load_game)
return client->state.external_client->begin_load_game(client, hash, callback, callback_userdata);
#endif
load_state = (rc_client_load_state_t*)calloc(1, sizeof(*load_state));
if (!load_state) {
callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), client, callback_userdata);
@ -2137,6 +2238,11 @@ rc_client_async_handle_t* rc_client_begin_identify_and_load_game(rc_client_t* cl
return NULL;
}
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->begin_identify_and_load_game)
return client->state.external_client->begin_identify_and_load_game(client, console_id, file_path, data, data_size, callback, callback_userdata);
#endif
if (data) {
if (file_path) {
RC_CLIENT_LOG_INFO_FORMATTED(client, "Identifying game: %zu bytes at %p (%s)", data_size, data, file_path);
@ -2241,6 +2347,13 @@ void rc_client_unload_game(rc_client_t* client)
if (!client)
return;
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->unload_game) {
client->state.external_client->unload_game();
return;
}
#endif
rc_mutex_lock(&client->state.mutex);
game = client->game;
@ -2365,6 +2478,11 @@ rc_client_async_handle_t* rc_client_begin_change_media(rc_client_t* client, cons
return NULL;
}
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->begin_change_media)
return client->state.external_client->begin_change_media(client, file_path, data, data_size, callback, callback_userdata);
#endif
rc_mutex_lock(&client->state.mutex);
if (client->state.load) {
game = client->state.load->game;
@ -2475,6 +2593,7 @@ rc_client_async_handle_t* rc_client_begin_change_media(rc_client_t* client, cons
else {
/* call the server to make sure the hash is valid for the loaded game */
rc_client_load_state_t* callback_data;
rc_client_async_handle_t* async_handle;
rc_api_resolve_hash_request_t resolve_hash_request;
rc_api_request_t request;
int result;
@ -2500,18 +2619,28 @@ rc_client_async_handle_t* rc_client_begin_change_media(rc_client_t* client, cons
callback_data->hash = game_hash;
callback_data->game = game;
rc_client_begin_async(client, &callback_data->async_handle);
async_handle = &callback_data->async_handle;
rc_client_begin_async(client, async_handle);
client->callbacks.server_call(&request, rc_client_identify_changed_media_callback, callback_data, client);
rc_api_destroy_request(&request);
return &callback_data->async_handle;
/* if handle is no longer valid, the async operation completed synchronously */
return rc_client_async_handle_valid(client, async_handle) ? async_handle : NULL;
}
}
const rc_client_game_t* rc_client_get_game_info(const rc_client_t* client)
{
return (client && client->game) ? &client->game->public_ : NULL;
if (!client)
return NULL;
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->get_game_info)
return client->state.external_client->get_game_info();
#endif
return client->game ? &client->game->public_ : NULL;
}
int rc_client_game_get_image_url(const rc_client_game_t* game, char buffer[], size_t buffer_size)
@ -2524,19 +2653,24 @@ int rc_client_game_get_image_url(const rc_client_game_t* game, char buffer[], si
/* ===== Subsets ===== */
void rc_client_begin_load_subset(rc_client_t* client, uint32_t subset_id, rc_client_callback_t callback, void* callback_userdata)
rc_client_async_handle_t* rc_client_begin_load_subset(rc_client_t* client, uint32_t subset_id, rc_client_callback_t callback, void* callback_userdata)
{
char buffer[32];
rc_client_load_state_t* load_state;
if (!client) {
callback(RC_INVALID_STATE, "client is required", client, callback_userdata);
return;
return NULL;
}
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->begin_load_subset)
return client->state.external_client->begin_load_subset(client, subset_id, callback, callback_userdata);
#endif
if (!client->game) {
callback(RC_NO_GAME_LOADED, rc_error_str(RC_NO_GAME_LOADED), client, callback_userdata);
return;
return NULL;
}
snprintf(buffer, sizeof(buffer), "[SUBSET%lu]", (unsigned long)subset_id);
@ -2544,7 +2678,7 @@ void rc_client_begin_load_subset(rc_client_t* client, uint32_t subset_id, rc_cli
load_state = (rc_client_load_state_t*)calloc(1, sizeof(*load_state));
if (!load_state) {
callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), client, callback_userdata);
return;
return NULL;
}
load_state->client = client;
@ -2556,13 +2690,23 @@ void rc_client_begin_load_subset(rc_client_t* client, uint32_t subset_id, rc_cli
client->state.load = load_state;
rc_client_begin_fetch_game_data(load_state);
return (client->state.load == load_state) ? &load_state->async_handle : NULL;
}
const rc_client_subset_t* rc_client_get_subset_info(rc_client_t* client, uint32_t subset_id)
{
rc_client_subset_info_t* subset;
if (!client || !client->game)
if (!client)
return NULL;
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->get_subset_info)
return client->state.external_client->get_subset_info(subset_id);
#endif
if (!client->game)
return NULL;
for (subset = client->game->subsets; subset; subset = subset->next) {
@ -2722,7 +2866,7 @@ rc_client_achievement_list_t* rc_client_create_achievement_list(rc_client_t* cli
rc_client_achievement_t** bucket_achievements;
rc_client_achievement_t** achievement_ptr;
rc_client_achievement_bucket_t* bucket_ptr;
rc_client_achievement_list_t* list;
rc_client_achievement_list_info_t* list;
rc_client_subset_info_t* subset;
const uint32_t list_size = RC_ALIGN(sizeof(*list));
uint32_t bucket_counts[16];
@ -2745,8 +2889,16 @@ rc_client_achievement_list_t* rc_client_create_achievement_list(rc_client_t* cli
};
const time_t recent_unlock_time = time(NULL) - RC_CLIENT_RECENT_UNLOCK_DELAY_SECONDS;
if (!client || !client->game)
return calloc(1, sizeof(rc_client_achievement_list_t));
if (!client)
return (rc_client_achievement_list_t*)calloc(1, sizeof(rc_client_achievement_list_info_t));
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->create_achievement_list)
return (rc_client_achievement_list_t*)client->state.external_client->create_achievement_list(category, grouping);
#endif
if (!client->game)
return (rc_client_achievement_list_t*)calloc(1, sizeof(rc_client_achievement_list_info_t));
memset(&bucket_counts, 0, sizeof(bucket_counts));
@ -2811,8 +2963,8 @@ rc_client_achievement_list_t* rc_client_create_achievement_list(rc_client_t* cli
buckets_size = RC_ALIGN(num_buckets * sizeof(rc_client_achievement_bucket_t));
list = (rc_client_achievement_list_t*)malloc(list_size + buckets_size + num_achievements * sizeof(rc_client_achievement_t*));
bucket_ptr = list->buckets = (rc_client_achievement_bucket_t*)((uint8_t*)list + list_size);
list = (rc_client_achievement_list_info_t*)malloc(list_size + buckets_size + num_achievements * sizeof(rc_client_achievement_t*));
bucket_ptr = list->public_.buckets = (rc_client_achievement_bucket_t*)((uint8_t*)list + list_size);
achievement_ptr = (rc_client_achievement_t**)((uint8_t*)bucket_ptr + buckets_size);
if (grouping == RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_PROGRESS) {
@ -2891,13 +3043,17 @@ rc_client_achievement_list_t* rc_client_create_achievement_list(rc_client_t* cli
rc_mutex_unlock(&client->state.mutex);
list->num_buckets = (uint32_t)(bucket_ptr - list->buckets);
return list;
list->destroy_func = NULL;
list->public_.num_buckets = (uint32_t)(bucket_ptr - list->public_.buckets);
return &list->public_;
}
void rc_client_destroy_achievement_list(rc_client_achievement_list_t* list)
{
if (list)
rc_client_achievement_list_info_t* info = (rc_client_achievement_list_info_t*)list;
if (info->destroy_func)
info->destroy_func(info);
else
free(list);
}
@ -2906,7 +3062,15 @@ int rc_client_has_achievements(rc_client_t* client)
rc_client_subset_info_t* subset;
int result;
if (!client || !client->game)
if (!client)
return 0;
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->has_achievements)
return client->state.external_client->has_achievements();
#endif
if (!client->game)
return 0;
rc_mutex_lock(&client->state.mutex);
@ -2952,7 +3116,15 @@ const rc_client_achievement_t* rc_client_get_achievement_info(rc_client_t* clien
{
rc_client_subset_info_t* subset;
if (!client || !client->game)
if (!client)
return NULL;
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->get_achievement_info)
return client->state.external_client->get_achievement_info(id);
#endif
if (!client->game)
return NULL;
for (subset = client->game->subsets; subset; subset = subset->next) {
@ -3229,7 +3401,15 @@ const rc_client_leaderboard_t* rc_client_get_leaderboard_info(const rc_client_t*
{
rc_client_subset_info_t* subset;
if (!client || !client->game)
if (!client)
return NULL;
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->get_leaderboard_info)
return client->state.external_client->get_leaderboard_info(id);
#endif
if (!client->game)
return NULL;
for (subset = client->game->subsets; subset; subset = subset->next) {
@ -3301,7 +3481,7 @@ rc_client_leaderboard_list_t* rc_client_create_leaderboard_list(rc_client_t* cli
rc_client_leaderboard_t** bucket_leaderboards;
rc_client_leaderboard_t** leaderboard_ptr;
rc_client_leaderboard_bucket_t* bucket_ptr;
rc_client_leaderboard_list_t* list;
rc_client_leaderboard_list_info_t* list;
rc_client_subset_info_t* subset;
const uint32_t list_size = RC_ALIGN(sizeof(*list));
uint32_t bucket_counts[8];
@ -3320,7 +3500,15 @@ rc_client_leaderboard_list_t* rc_client_create_leaderboard_list(rc_client_t* cli
RC_CLIENT_LEADERBOARD_BUCKET_UNSUPPORTED
};
if (!client || !client->game)
if (!client)
return calloc(1, sizeof(rc_client_leaderboard_list_t));
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->create_leaderboard_list)
return (rc_client_leaderboard_list_t*)client->state.external_client->create_leaderboard_list(grouping);
#endif
if (!client->game)
return calloc(1, sizeof(rc_client_leaderboard_list_t));
memset(&bucket_counts, 0, sizeof(bucket_counts));
@ -3385,8 +3573,8 @@ rc_client_leaderboard_list_t* rc_client_create_leaderboard_list(rc_client_t* cli
buckets_size = RC_ALIGN(num_buckets * sizeof(rc_client_leaderboard_bucket_t));
list = (rc_client_leaderboard_list_t*)malloc(list_size + buckets_size + num_leaderboards * sizeof(rc_client_leaderboard_t*));
bucket_ptr = list->buckets = (rc_client_leaderboard_bucket_t*)((uint8_t*)list + list_size);
list = (rc_client_leaderboard_list_info_t*)malloc(list_size + buckets_size + num_leaderboards * sizeof(rc_client_leaderboard_t*));
bucket_ptr = list->public_.buckets = (rc_client_leaderboard_bucket_t*)((uint8_t*)list + list_size);
leaderboard_ptr = (rc_client_leaderboard_t**)((uint8_t*)bucket_ptr + buckets_size);
if (grouping == RC_CLIENT_LEADERBOARD_LIST_GROUPING_TRACKING) {
@ -3455,13 +3643,17 @@ rc_client_leaderboard_list_t* rc_client_create_leaderboard_list(rc_client_t* cli
rc_mutex_unlock(&client->state.mutex);
list->num_buckets = (uint32_t)(bucket_ptr - list->buckets);
return list;
list->destroy_func = NULL;
list->public_.num_buckets = (uint32_t)(bucket_ptr - list->public_.buckets);
return &list->public_;
}
void rc_client_destroy_leaderboard_list(rc_client_leaderboard_list_t* list)
{
if (list)
rc_client_leaderboard_list_info_t* info = (rc_client_leaderboard_list_info_t*)list;
if (info->destroy_func)
info->destroy_func(info);
else
free(list);
}
@ -3470,7 +3662,15 @@ int rc_client_has_leaderboards(rc_client_t* client)
rc_client_subset_info_t* subset;
int result;
if (!client || !client->game)
if (!client)
return 0;
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->has_leaderboards)
return client->state.external_client->has_leaderboards();
#endif
if (!client->game)
return 0;
rc_mutex_lock(&client->state.mutex);
@ -3843,24 +4043,26 @@ static void rc_client_fetch_leaderboard_entries_callback(const rc_api_server_res
lbinfo_callback_data->callback(result, error_message, NULL, client, lbinfo_callback_data->callback_userdata);
}
else {
rc_client_leaderboard_entry_list_t* list;
const size_t list_size = sizeof(*list) + sizeof(rc_client_leaderboard_entry_t) * lbinfo_response.num_entries;
rc_client_leaderboard_entry_list_info_t* info;
const size_t list_size = sizeof(*info) + sizeof(rc_client_leaderboard_entry_t) * lbinfo_response.num_entries;
size_t needed_size = list_size;
uint32_t i;
for (i = 0; i < lbinfo_response.num_entries; i++)
needed_size += strlen(lbinfo_response.entries[i].username) + 1;
list = (rc_client_leaderboard_entry_list_t*)malloc(needed_size);
if (!list) {
info = (rc_client_leaderboard_entry_list_info_t*)malloc(needed_size);
if (!info) {
lbinfo_callback_data->callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), NULL, client, lbinfo_callback_data->callback_userdata);
}
else {
rc_client_leaderboard_entry_t* entry = list->entries = (rc_client_leaderboard_entry_t*)((uint8_t*)list + sizeof(*list));
rc_client_leaderboard_entry_list_t* list = &info->public_;
rc_client_leaderboard_entry_t* entry = list->entries = (rc_client_leaderboard_entry_t*)((uint8_t*)info + sizeof(*info));
char* user = (char*)((uint8_t*)list + list_size);
const rc_api_lboard_info_entry_t* lbentry = lbinfo_response.entries;
const rc_api_lboard_info_entry_t* stop = lbentry + lbinfo_response.num_entries;
const size_t logged_in_user_len = strlen(client->user.display_name) + 1;
info->destroy_func = NULL;
list->user_index = -1;
for (; lbentry < stop; ++lbentry, ++entry) {
@ -3894,6 +4096,7 @@ static rc_client_async_handle_t* rc_client_begin_fetch_leaderboard_info(rc_clien
rc_client_fetch_leaderboard_entries_callback_t callback, void* callback_userdata)
{
rc_client_fetch_leaderboard_entries_callback_data_t* callback_data;
rc_client_async_handle_t* async_handle;
rc_api_request_t request;
int result;
const char* error_message;
@ -3917,11 +4120,12 @@ static rc_client_async_handle_t* rc_client_begin_fetch_leaderboard_info(rc_clien
callback_data->callback_userdata = callback_userdata;
callback_data->leaderboard_id = lbinfo_request->leaderboard_id;
rc_client_begin_async(client, &callback_data->async_handle);
async_handle = &callback_data->async_handle;
rc_client_begin_async(client, async_handle);
client->callbacks.server_call(&request, rc_client_fetch_leaderboard_entries_callback, callback_data, client);
rc_api_destroy_request(&request);
return &callback_data->async_handle;
return rc_client_async_handle_valid(client, async_handle) ? async_handle : NULL;
}
rc_client_async_handle_t* rc_client_begin_fetch_leaderboard_entries(rc_client_t* client, uint32_t leaderboard_id,
@ -3929,6 +4133,11 @@ rc_client_async_handle_t* rc_client_begin_fetch_leaderboard_entries(rc_client_t*
{
rc_api_fetch_leaderboard_info_request_t lbinfo_request;
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->begin_fetch_leaderboard_entries)
return client->state.external_client->begin_fetch_leaderboard_entries(client, leaderboard_id, first_entry, count, callback, callback_userdata);
#endif
memset(&lbinfo_request, 0, sizeof(lbinfo_request));
lbinfo_request.leaderboard_id = leaderboard_id;
lbinfo_request.first_entry = first_entry;
@ -3942,6 +4151,11 @@ rc_client_async_handle_t* rc_client_begin_fetch_leaderboard_entries_around_user(
{
rc_api_fetch_leaderboard_info_request_t lbinfo_request;
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->begin_fetch_leaderboard_entries_around_user)
return client->state.external_client->begin_fetch_leaderboard_entries_around_user(client, leaderboard_id, count, callback, callback_userdata);
#endif
memset(&lbinfo_request, 0, sizeof(lbinfo_request));
lbinfo_request.leaderboard_id = leaderboard_id;
lbinfo_request.username = client->user.username;
@ -3957,7 +4171,10 @@ rc_client_async_handle_t* rc_client_begin_fetch_leaderboard_entries_around_user(
void rc_client_destroy_leaderboard_entry_list(rc_client_leaderboard_entry_list_t* list)
{
if (list)
rc_client_leaderboard_entry_list_info_t* info = (rc_client_leaderboard_entry_list_info_t*)list;
if (info->destroy_func)
info->destroy_func(info);
else
free(list);
}
@ -4022,10 +4239,15 @@ static void rc_client_ping(rc_client_scheduled_callback_data_t* callback_data, r
int rc_client_has_rich_presence(rc_client_t* client)
{
if (!client || !client->game)
if (!client)
return 0;
if (!client->game->runtime.richpresence || !client->game->runtime.richpresence->richpresence)
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->has_rich_presence)
return client->state.external_client->has_rich_presence();
#endif
if (!client->game || !client->game->runtime.richpresence || !client->game->runtime.richpresence->richpresence)
return 0;
return 1;
@ -4035,7 +4257,15 @@ size_t rc_client_get_rich_presence_message(rc_client_t* client, char buffer[], s
{
int result;
if (!client || !client->game || !buffer)
if (!client || !buffer)
return 0;
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->get_rich_presence_message)
return client->state.external_client->get_rich_presence_message(buffer, buffer_size);
#endif
if (!client->game)
return 0;
rc_mutex_lock(&client->state.mutex);
@ -4045,8 +4275,12 @@ size_t rc_client_get_rich_presence_message(rc_client_t* client, char buffer[], s
rc_mutex_unlock(&client->state.mutex);
if (result == 0)
if (result == 0) {
result = snprintf(buffer, buffer_size, "Playing %s", client->game->public_.title);
/* snprintf will return the amount of space needed, we want to return the number of chars written */
if ((size_t)result >= buffer_size)
return (buffer_size - 1);
}
return result;
}
@ -4055,14 +4289,28 @@ size_t rc_client_get_rich_presence_message(rc_client_t* client, char buffer[], s
void rc_client_set_event_handler(rc_client_t* client, rc_client_event_handler_t handler)
{
if (client)
client->callbacks.event_handler = handler;
if (!client)
return;
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->set_event_handler)
client->state.external_client->set_event_handler(client, handler);
#endif
client->callbacks.event_handler = handler;
}
void rc_client_set_read_memory_function(rc_client_t* client, rc_client_read_memory_func_t handler)
{
if (client)
client->callbacks.read_memory = handler;
if (!client)
return;
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->set_read_memory)
client->state.external_client->set_read_memory(client, handler);
#endif
client->callbacks.read_memory = handler;
}
static void rc_client_invalidate_processing_memref(rc_client_t* client)
@ -4173,7 +4421,15 @@ void rc_client_set_legacy_peek(rc_client_t* client, int method)
int rc_client_is_processing_required(rc_client_t* client)
{
if (!client || !client->game)
if (!client)
return 0;
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->is_processing_required)
return client->state.external_client->is_processing_required();
#endif
if (!client->game)
return 0;
if (client->game->runtime.trigger_count || client->game->runtime.lboard_count)
@ -4594,6 +4850,13 @@ void rc_client_do_frame(rc_client_t* client)
if (!client)
return;
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->do_frame) {
client->state.external_client->do_frame();
return;
}
#endif
if (client->game && !client->game->waiting_for_reset) {
rc_runtime_richpresence_t* richpresence;
rc_client_subset_info_t* subset;
@ -4639,6 +4902,13 @@ void rc_client_idle(rc_client_t* client)
if (!client)
return;
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->idle) {
client->state.external_client->idle();
return;
}
#endif
scheduled_callback = client->state.scheduled_callbacks;
if (scheduled_callback) {
const rc_clock_t now = client->callbacks.get_time_millisecs(client);
@ -4765,7 +5035,17 @@ static void rc_client_reset_all(rc_client_t* client)
void rc_client_reset(rc_client_t* client)
{
rc_client_game_hash_t* game_hash;
if (!client || !client->game)
if (!client)
return;
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->reset) {
client->state.external_client->reset();
return;
}
#endif
if (!client->game)
return;
game_hash = rc_client_find_game_hash(client, client->game->public_.hash);
@ -4796,7 +5076,15 @@ size_t rc_client_progress_size(rc_client_t* client)
{
size_t result;
if (!client || !client->game)
if (!client)
return 0;
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->progress_size)
return client->state.external_client->progress_size();
#endif
if (!client->game)
return 0;
rc_mutex_lock(&client->state.mutex);
@ -4810,7 +5098,15 @@ int rc_client_serialize_progress(rc_client_t* client, uint8_t* buffer)
{
int result;
if (!client || !client->game)
if (!client)
return RC_NO_GAME_LOADED;
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->serialize_progress)
return client->state.external_client->serialize_progress(buffer);
#endif
if (!client->game)
return RC_NO_GAME_LOADED;
if (!buffer)
@ -4921,7 +5217,15 @@ int rc_client_deserialize_progress(rc_client_t* client, const uint8_t* serialize
rc_client_subset_info_t* subset;
int result;
if (!client || !client->game)
if (!client)
return RC_NO_GAME_LOADED;
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->deserialize_progress)
return client->state.external_client->deserialize_progress(serialized);
#endif
if (!client->game)
return RC_NO_GAME_LOADED;
rc_mutex_lock(&client->state.mutex);
@ -4988,6 +5292,13 @@ void rc_client_set_hardcore_enabled(rc_client_t* client, int enabled)
if (!client)
return;
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->get_hardcore_enabled) {
client->state.external_client->set_hardcore_enabled(enabled);
return;
}
#endif
rc_mutex_lock(&client->state.mutex);
enabled = enabled ? 1 : 0;
@ -5022,51 +5333,107 @@ void rc_client_set_hardcore_enabled(rc_client_t* client, int enabled)
int rc_client_get_hardcore_enabled(const rc_client_t* client)
{
return client && client->state.hardcore;
if (!client)
return 0;
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->get_hardcore_enabled)
return client->state.external_client->get_hardcore_enabled();
#endif
return client->state.hardcore;
}
void rc_client_set_unofficial_enabled(rc_client_t* client, int enabled)
{
if (client) {
RC_CLIENT_LOG_INFO_FORMATTED(client, "Unofficial %s", enabled ? "enabled" : "disabled");
client->state.unofficial_enabled = enabled ? 1 : 0;
if (!client)
return;
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->set_unofficial_enabled) {
client->state.external_client->set_unofficial_enabled(enabled);
return;
}
#endif
RC_CLIENT_LOG_INFO_FORMATTED(client, "Unofficial %s", enabled ? "enabled" : "disabled");
client->state.unofficial_enabled = enabled ? 1 : 0;
}
int rc_client_get_unofficial_enabled(const rc_client_t* client)
{
return client && client->state.unofficial_enabled;
if (!client)
return 0;
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->get_unofficial_enabled)
return client->state.external_client->get_unofficial_enabled();
#endif
return client->state.unofficial_enabled;
}
void rc_client_set_encore_mode_enabled(rc_client_t* client, int enabled)
{
if (client) {
RC_CLIENT_LOG_INFO_FORMATTED(client, "Encore mode %s", enabled ? "enabled" : "disabled");
client->state.encore_mode = enabled ? 1 : 0;
if (!client)
return;
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->set_encore_mode_enabled) {
client->state.external_client->set_encore_mode_enabled(enabled);
return;
}
#endif
RC_CLIENT_LOG_INFO_FORMATTED(client, "Encore mode %s", enabled ? "enabled" : "disabled");
client->state.encore_mode = enabled ? 1 : 0;
}
int rc_client_get_encore_mode_enabled(const rc_client_t* client)
{
return client && client->state.encore_mode;
if (!client)
return 0;
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->get_encore_mode_enabled)
return client->state.external_client->get_encore_mode_enabled();
#endif
return client->state.encore_mode;
}
void rc_client_set_spectator_mode_enabled(rc_client_t* client, int enabled)
{
if (client) {
if (!enabled && client->state.spectator_mode == RC_CLIENT_SPECTATOR_MODE_LOCKED) {
RC_CLIENT_LOG_WARN(client, "Spectator mode cannot be disabled if it was enabled prior to loading game.");
return;
}
if (!client)
return;
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->set_spectator_mode_enabled) {
client->state.external_client->set_spectator_mode_enabled(enabled);
return;
}
#endif
RC_CLIENT_LOG_INFO_FORMATTED(client, "Spectator mode %s", enabled ? "enabled" : "disabled");
client->state.spectator_mode = enabled ? RC_CLIENT_SPECTATOR_MODE_ON : RC_CLIENT_SPECTATOR_MODE_OFF;
if (!enabled && client->state.spectator_mode == RC_CLIENT_SPECTATOR_MODE_LOCKED) {
RC_CLIENT_LOG_WARN(client, "Spectator mode cannot be disabled if it was enabled prior to loading game.");
return;
}
RC_CLIENT_LOG_INFO_FORMATTED(client, "Spectator mode %s", enabled ? "enabled" : "disabled");
client->state.spectator_mode = enabled ? RC_CLIENT_SPECTATOR_MODE_ON : RC_CLIENT_SPECTATOR_MODE_OFF;
}
int rc_client_get_spectator_mode_enabled(const rc_client_t* client)
{
return client && (client->state.spectator_mode == RC_CLIENT_SPECTATOR_MODE_OFF) ? 0 : 1;
if (!client)
return 0;
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->get_spectator_mode_enabled)
return client->state.external_client->get_spectator_mode_enabled();
#endif
return (client->state.spectator_mode == RC_CLIENT_SPECTATOR_MODE_OFF) ? 0 : 1;
}
void rc_client_set_userdata(rc_client_t* client, void* userdata)
@ -5094,4 +5461,9 @@ void rc_client_set_host(const rc_client_t* client, const char* hostname)
RC_CLIENT_LOG_VERBOSE_FORMATTED(client, "Using host: %s", hostname);
}
rc_api_set_host(hostname);
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->set_host)
client->state.external_client->set_host(hostname);
#endif
}

View file

@ -0,0 +1,133 @@
#ifndef RC_CLIENT_EXTERNAL_H
#define RC_CLIENT_EXTERNAL_H
#ifdef __cplusplus
extern "C" {
#endif
#include "rc_client.h"
/* NOTE: any function that is passed a callback also needs to be passed a client instance to pass
* to the callback, and the external interface has to capture both. */
typedef void (*rc_client_external_enable_logging_func_t)(rc_client_t* client, int level, rc_client_message_callback_t callback);
typedef void (*rc_client_external_set_event_handler_func_t)(rc_client_t* client, rc_client_event_handler_t handler);
typedef void (*rc_client_external_set_read_memory_func_t)(rc_client_t* client, rc_client_read_memory_func_t handler);
typedef void (*rc_client_external_set_get_time_millisecs_func_t)(rc_client_t* client, rc_get_time_millisecs_func_t handler);
typedef void (*rc_client_external_set_int_func_t)(int value);
typedef int (*rc_client_external_get_int_func_t)(void);
typedef void (*rc_client_external_set_string_func_t)(const char* value);
typedef size_t (*rc_client_external_copy_string_func_t)(char buffer[], size_t buffer_size);
typedef void (*rc_client_external_action_func_t)(void);
typedef void (*rc_client_external_async_handle_func_t)(rc_client_async_handle_t* handle);
typedef rc_client_async_handle_t* (*rc_client_external_begin_login_func_t)(rc_client_t* client,
const char* username, const char* pass_token, rc_client_callback_t callback, void* callback_userdata);
typedef const rc_client_user_t* (*rc_client_external_get_user_info_func_t)(void);
typedef rc_client_async_handle_t* (*rc_client_external_begin_identify_and_load_game_func_t)(
rc_client_t* client, uint32_t console_id, const char* file_path,
const uint8_t* data, size_t data_size, rc_client_callback_t callback, void* callback_userdata);
typedef rc_client_async_handle_t* (*rc_client_external_begin_load_game_func_t)(rc_client_t* client,
const char* hash, rc_client_callback_t callback, void* callback_userdata);
typedef rc_client_async_handle_t* (*rc_client_external_begin_load_subset_t)(rc_client_t* client,
uint32_t subset_id, rc_client_callback_t callback, void* callback_userdata);
typedef const rc_client_game_t* (*rc_client_external_get_game_info_func_t)(void);
typedef const rc_client_subset_t* (*rc_client_external_get_subset_info_func_t)(uint32_t subset_id);
typedef void (*rc_client_external_get_user_game_summary_func_t)(rc_client_user_game_summary_t* summary);
typedef rc_client_async_handle_t* (*rc_client_external_begin_change_media_func_t)(rc_client_t* client, const char* file_path,
const uint8_t* data, size_t data_size, rc_client_callback_t callback, void* callback_userdata);
/* NOTE: rc_client_external_create_achievement_list_func_t returns an internal wrapper structure which contains the public list
* and a destructor function. */
struct rc_client_achievement_list_info_t;
typedef struct rc_client_achievement_list_info_t* (*rc_client_external_create_achievement_list_func_t)(int category, int grouping);
typedef const rc_client_achievement_t* (*rc_client_external_get_achievement_info_func_t)(uint32_t id);
/* NOTE: rc_client_external_create_leaderboard_list_func_t returns an internal wrapper structure which contains the public list
* and a destructor function. */
struct rc_client_leaderboard_list_info_t;
typedef struct rc_client_leaderboard_list_info_t* (*rc_client_external_create_leaderboard_list_func_t)(int grouping);
typedef const rc_client_leaderboard_t* (*rc_client_external_get_leaderboard_info_func_t)(uint32_t id);
/* NOTE: rc_client_external_begin_fetch_leaderboard_entries_func_t and rc_client_external_begin_fetch_leaderboard_entries_around_user_func_t
* pass an internal wrapper structure around the list, which contains the public list and a destructor function. */
typedef rc_client_async_handle_t* (*rc_client_external_begin_fetch_leaderboard_entries_func_t)(rc_client_t* client,
uint32_t leaderboard_id, uint32_t first_entry, uint32_t count,
rc_client_fetch_leaderboard_entries_callback_t callback, void* callback_userdata);
typedef rc_client_async_handle_t* (*rc_client_external_begin_fetch_leaderboard_entries_around_user_func_t)(rc_client_t* client,
uint32_t leaderboard_id, uint32_t count, rc_client_fetch_leaderboard_entries_callback_t callback, void* callback_userdata);
typedef size_t (*rc_client_external_progress_size_func_t)(void);
typedef int (*rc_client_external_serialize_progress_func_t)(uint8_t* buffer);
typedef int (*rc_client_external_deserialize_progress_func_t)(const uint8_t* buffer);
typedef struct rc_client_external_t
{
rc_client_external_action_func_t destroy;
rc_client_external_enable_logging_func_t enable_logging;
rc_client_external_set_event_handler_func_t set_event_handler;
rc_client_external_set_read_memory_func_t set_read_memory;
rc_client_external_set_get_time_millisecs_func_t set_get_time_millisecs;
rc_client_external_set_string_func_t set_host;
rc_client_external_set_int_func_t set_hardcore_enabled;
rc_client_external_get_int_func_t get_hardcore_enabled;
rc_client_external_set_int_func_t set_unofficial_enabled;
rc_client_external_get_int_func_t get_unofficial_enabled;
rc_client_external_set_int_func_t set_encore_mode_enabled;
rc_client_external_get_int_func_t get_encore_mode_enabled;
rc_client_external_set_int_func_t set_spectator_mode_enabled;
rc_client_external_get_int_func_t get_spectator_mode_enabled;
rc_client_external_async_handle_func_t abort_async;
rc_client_external_begin_login_func_t begin_login_with_password;
rc_client_external_begin_login_func_t begin_login_with_token;
rc_client_external_action_func_t logout;
rc_client_external_get_user_info_func_t get_user_info;
rc_client_external_begin_identify_and_load_game_func_t begin_identify_and_load_game;
rc_client_external_begin_load_game_func_t begin_load_game;
rc_client_external_get_game_info_func_t get_game_info;
rc_client_external_begin_load_subset_t begin_load_subset;
rc_client_external_get_subset_info_func_t get_subset_info;
rc_client_external_action_func_t unload_game;
rc_client_external_get_user_game_summary_func_t get_user_game_summary;
rc_client_external_begin_change_media_func_t begin_change_media;
rc_client_external_create_achievement_list_func_t create_achievement_list;
rc_client_external_get_int_func_t has_achievements;
rc_client_external_get_achievement_info_func_t get_achievement_info;
rc_client_external_create_leaderboard_list_func_t create_leaderboard_list;
rc_client_external_get_int_func_t has_leaderboards;
rc_client_external_get_leaderboard_info_func_t get_leaderboard_info;
rc_client_external_begin_fetch_leaderboard_entries_func_t begin_fetch_leaderboard_entries;
rc_client_external_begin_fetch_leaderboard_entries_around_user_func_t begin_fetch_leaderboard_entries_around_user;
rc_client_external_copy_string_func_t get_rich_presence_message;
rc_client_external_get_int_func_t has_rich_presence;
rc_client_external_action_func_t do_frame;
rc_client_external_action_func_t idle;
rc_client_external_get_int_func_t is_processing_required;
rc_client_external_action_func_t reset;
rc_client_external_progress_size_func_t progress_size;
rc_client_external_serialize_progress_func_t serialize_progress;
rc_client_external_deserialize_progress_func_t deserialize_progress;
} rc_client_external_t;
#define RC_CLIENT_EXTERNAL_VERSION 1
#ifdef __cplusplus
}
#endif
#endif /* RC_CLIENT_EXTERNAL_H */

View file

@ -7,6 +7,13 @@ extern "C" {
#include "rc_client.h"
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
#include "rc_client_raintegration_internal.h"
#endif
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
#include "rc_client_external.h"
#endif
#include "rc_compat.h"
#include "rc_runtime.h"
#include "rc_runtime_types.h"
@ -50,6 +57,12 @@ typedef struct rc_client_scheduled_callback_data_t
void rc_client_schedule_callback(rc_client_t* client, rc_client_scheduled_callback_data_t* scheduled_callback);
struct rc_client_async_handle_t {
uint8_t aborted;
};
int rc_client_async_handle_aborted(rc_client_t* client, rc_client_async_handle_t* async_handle);
/*****************************************************************************\
| Achievements |
\*****************************************************************************/
@ -78,6 +91,14 @@ typedef struct rc_client_achievement_info_t {
time_t updated_time;
} rc_client_achievement_info_t;
struct rc_client_achievement_list_info_t;
typedef void (*rc_client_destroy_achievement_list_func_t)(struct rc_client_achievement_list_info_t* list);
typedef struct rc_client_achievement_list_info_t {
rc_client_achievement_list_t public_;
rc_client_destroy_achievement_list_func_t destroy_func;
} rc_client_achievement_list_info_t;
enum {
RC_CLIENT_PROGRESS_TRACKER_ACTION_NONE,
RC_CLIENT_PROGRESS_TRACKER_ACTION_SHOW,
@ -145,6 +166,22 @@ typedef struct rc_client_leaderboard_info_t {
uint8_t hidden;
} rc_client_leaderboard_info_t;
struct rc_client_leaderboard_list_info_t;
typedef void (*rc_client_destroy_leaderboard_list_func_t)(struct rc_client_leaderboard_list_info_t* list);
typedef struct rc_client_leaderboard_list_info_t {
rc_client_leaderboard_list_t public_;
rc_client_destroy_leaderboard_list_func_t destroy_func;
} rc_client_leaderboard_list_info_t;
struct rc_client_leaderboard_entry_list_info_t;
typedef void (*rc_client_destroy_leaderboard_entry_list_func_t)(struct rc_client_leaderboard_entry_list_info_t* list);
typedef struct rc_client_leaderboard_entry_list_info_t {
rc_client_leaderboard_entry_list_t public_;
rc_client_destroy_leaderboard_entry_list_func_t destroy_func;
} rc_client_leaderboard_entry_list_info_t;
uint8_t rc_client_map_leaderboard_format(int format);
/*****************************************************************************\
@ -177,7 +214,7 @@ typedef struct rc_client_subset_info_t {
uint8_t pending_events;
} rc_client_subset_info_t;
void rc_client_begin_load_subset(rc_client_t* client, uint32_t subset_id, rc_client_callback_t callback, void* callback_userdata);
rc_client_async_handle_t* rc_client_begin_load_subset(rc_client_t* client, uint32_t subset_id, rc_client_callback_t callback, void* callback_userdata);
/*****************************************************************************\
| Game |
@ -273,6 +310,13 @@ typedef struct rc_client_state_t {
rc_client_scheduled_callback_data_t* scheduled_callbacks;
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
rc_client_external_t* external_client;
#endif
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
rc_client_raintegration_t* raintegration;
#endif
uint8_t hardcore;
uint8_t encore_mode;
uint8_t spectator_mode;

View file

@ -0,0 +1,373 @@
#include "rc_client_raintegration_internal.h"
#include "rc_client_internal.h"
#include "rapi/rc_api_common.h"
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
static void rc_client_raintegration_load_dll(rc_client_t* client,
const wchar_t* search_directory, rc_client_callback_t callback, void* callback_userdata)
{
wchar_t sPath[_MAX_PATH];
const int nPathSize = sizeof(sPath) / sizeof(sPath[0]);
rc_client_raintegration_t* raintegration;
int sPathIndex = 0;
DWORD dwAttrib;
HINSTANCE hDLL;
if (search_directory) {
sPathIndex = swprintf_s(sPath, nPathSize, L"%s\\", search_directory);
if (sPathIndex > nPathSize - 22) {
callback(RC_INVALID_STATE, "search_directory too long", client, callback_userdata);
return;
}
}
#if defined(_M_X64) || defined(__amd64__)
wcscpy_s(&sPath[sPathIndex], nPathSize - sPathIndex, L"RA_Integration-x64.dll");
dwAttrib = GetFileAttributesW(sPath);
if (dwAttrib == INVALID_FILE_ATTRIBUTES) {
wcscpy_s(&sPath[sPathIndex], nPathSize - sPathIndex, L"RA_Integration.dll");
dwAttrib = GetFileAttributesW(sPath);
}
#else
wcscpy_s(&sPath[sPathIndex], nPathSize - sPathIndex, L"RA_Integration.dll");
dwAttrib = GetFileAttributesW(sPath);
#endif
if (dwAttrib == INVALID_FILE_ATTRIBUTES) {
callback(RC_MISSING_VALUE, "RA_Integration.dll not found in search directory", client, callback_userdata);
return;
}
hDLL = LoadLibraryW(sPath);
if (hDLL == NULL) {
char error_message[512];
const DWORD last_error = GetLastError();
int offset = snprintf(error_message, sizeof(error_message), "Failed to load RA_Integration.dll (%u)", last_error);
if (last_error != 0) {
LPSTR messageBuffer = NULL;
const DWORD size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, last_error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL);
snprintf(&error_message[offset], sizeof(error_message) - offset, ": %.*s", size, messageBuffer);
LocalFree(messageBuffer);
}
callback(RC_ABORTED, error_message, client, callback_userdata);
return;
}
raintegration = (rc_client_raintegration_t*)rc_buffer_alloc(&client->state.buffer, sizeof(rc_client_raintegration_t));
memset(raintegration, 0, sizeof(*raintegration));
raintegration->hDLL = hDLL;
raintegration->get_version = (rc_client_raintegration_get_string_func)GetProcAddress(hDLL, "_RA_IntegrationVersion");
raintegration->get_host_url = (rc_client_raintegration_get_string_func)GetProcAddress(hDLL, "_RA_HostUrl");
raintegration->init_client = (rc_client_raintegration_init_client_func)GetProcAddress(hDLL, "_RA_InitClient");
raintegration->init_client_offline = (rc_client_raintegration_init_client_func)GetProcAddress(hDLL, "_RA_InitOffline");
raintegration->shutdown = (rc_client_raintegration_action_func)GetProcAddress(hDLL, "_RA_Shutdown");
raintegration->get_external_client = (rc_client_raintegration_get_external_client)GetProcAddress(hDLL, "_Rcheevos_GetExternalClient");
if (!raintegration->get_version ||
!raintegration->init_client ||
!raintegration->get_external_client) {
FreeLibrary(hDLL);
callback(RC_ABORTED, "One or more required exports was not found in RA_Integration.dll", client, callback_userdata);
}
else {
rc_mutex_lock(&client->state.mutex);
client->state.raintegration = raintegration;
rc_mutex_unlock(&client->state.mutex);
RC_CLIENT_LOG_INFO_FORMATTED(client, "RA_Integration.dll %s loaded", client->state.raintegration->get_version());
}
}
typedef struct rc_client_version_validation_callback_data_t {
rc_client_t* client;
rc_client_callback_t callback;
void* callback_userdata;
HWND main_window_handle;
char* client_name;
char* client_version;
rc_client_async_handle_t async_handle;
} rc_client_version_validation_callback_data_t;
int rc_client_version_less(const char* left, const char* right)
{
do {
int left_len = 0;
int right_len = 0;
while (*left && *left == '0')
++left;
while (left[left_len] && left[left_len] != '.')
++left_len;
while (*right && *right == '0')
++right;
while (right[right_len] && right[right_len] != '.')
++right_len;
if (left_len != right_len)
return (left_len < right_len);
while (left_len--) {
if (*left != *right)
return (*left < *right);
++left;
++right;
}
if (*left == '.')
++left;
if (*right == '.')
++right;
} while (*left || *right);
return 0;
}
static void rc_client_init_raintegration(rc_client_t* client,
rc_client_version_validation_callback_data_t* version_validation_callback_data)
{
rc_client_raintegration_init_client_func init_func = client->state.raintegration->init_client;
if (client->state.raintegration->get_host_url) {
const char* host_url = client->state.raintegration->get_host_url();
if (host_url) {
if (strcmp(host_url, "OFFLINE") != 0) {
rc_client_set_host(client, host_url);
}
else if (client->state.raintegration->init_client_offline) {
init_func = client->state.raintegration->init_client_offline;
RC_CLIENT_LOG_INFO(client, "Initializing in offline mode");
}
}
}
if (!init_func || !init_func(version_validation_callback_data->main_window_handle,
version_validation_callback_data->client_name,
version_validation_callback_data->client_version)) {
const char* error_message = "RA_Integration initialization failed";
rc_client_unload_raintegration(client);
RC_CLIENT_LOG_ERR(client, error_message);
version_validation_callback_data->callback(RC_ABORTED, error_message, client, version_validation_callback_data->callback_userdata);
}
else {
rc_client_external_t* external_client = (rc_client_external_t*)
rc_buffer_alloc(&client->state.buffer, sizeof(*external_client));
memset(external_client, 0, sizeof(*external_client));
if (!client->state.raintegration->get_external_client(external_client, RC_CLIENT_EXTERNAL_VERSION)) {
const char* error_message = "RA_Integration external client export failed";
rc_client_unload_raintegration(client);
RC_CLIENT_LOG_ERR(client, error_message);
version_validation_callback_data->callback(RC_ABORTED, error_message, client, version_validation_callback_data->callback_userdata);
}
else {
/* copy state to the external client */
if (external_client->enable_logging)
external_client->enable_logging(client, client->state.log_level, client->callbacks.log_call);
if (external_client->set_event_handler)
external_client->set_event_handler(client, client->callbacks.event_handler);
if (external_client->set_read_memory)
external_client->set_read_memory(client, client->callbacks.read_memory);
if (external_client->set_hardcore_enabled)
external_client->set_hardcore_enabled(rc_client_get_hardcore_enabled(client));
if (external_client->set_unofficial_enabled)
external_client->set_unofficial_enabled(rc_client_get_unofficial_enabled(client));
if (external_client->set_encore_mode_enabled)
external_client->set_encore_mode_enabled(rc_client_get_encore_mode_enabled(client));
if (external_client->set_spectator_mode_enabled)
external_client->set_spectator_mode_enabled(rc_client_get_spectator_mode_enabled(client));
/* attach the external client and call the callback */
client->state.external_client = external_client;
version_validation_callback_data->callback(RC_OK, NULL,
client, version_validation_callback_data->callback_userdata);
}
}
}
static void rc_client_version_validation_callback(const rc_api_server_response_t* server_response, void* callback_data)
{
rc_client_version_validation_callback_data_t* version_validation_callback_data =
(rc_client_version_validation_callback_data_t*)callback_data;
rc_client_t* client = version_validation_callback_data->client;
if (rc_client_async_handle_aborted(client, &version_validation_callback_data->async_handle)) {
RC_CLIENT_LOG_VERBOSE(client, "Version validation aborted");
}
else {
rc_api_response_t response;
int result;
const char* current_version;
const char* minimum_version = "";
rc_json_field_t fields[] = {
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error"),
RC_JSON_NEW_FIELD("MinimumVersion"),
};
memset(&response, 0, sizeof(response));
rc_buffer_init(&response.buffer);
result = rc_json_parse_server_response(&response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
if (result == RC_OK) {
if (!rc_json_get_required_string(&minimum_version, &response, &fields[2], "MinimumVersion"))
result = RC_MISSING_VALUE;
}
if (result != RC_OK) {
RC_CLIENT_LOG_ERR_FORMATTED(client, "Failed to fetch latest integration version: %.*s", server_response->body_length, server_response->body);
rc_client_unload_raintegration(client);
version_validation_callback_data->callback(result, rc_error_str(result),
client, version_validation_callback_data->callback_userdata);
}
else {
current_version = client->state.raintegration->get_version();
if (rc_client_version_less(current_version, minimum_version)) {
char error_message[256];
rc_client_unload_raintegration(client);
snprintf(error_message, sizeof(error_message),
"RA_Integration version %s is lower than minimum version %s", current_version, minimum_version);
RC_CLIENT_LOG_WARN(client, error_message);
version_validation_callback_data->callback(RC_ABORTED, error_message, client, version_validation_callback_data->callback_userdata);
}
else {
RC_CLIENT_LOG_INFO_FORMATTED(client, "Validated RA_Integration version %s (minimum %s)", current_version, minimum_version);
rc_client_init_raintegration(client, version_validation_callback_data);
}
}
rc_buffer_destroy(&response.buffer);
}
free(version_validation_callback_data->client_name);
free(version_validation_callback_data->client_version);
free(version_validation_callback_data);
}
rc_client_async_handle_t* rc_client_begin_load_raintegration(rc_client_t* client,
const wchar_t* search_directory, HWND main_window_handle,
const char* client_name, const char* client_version,
rc_client_callback_t callback, void* callback_userdata)
{
rc_client_version_validation_callback_data_t* callback_data;
rc_api_url_builder_t builder;
rc_api_request_t request;
if (!client) {
callback(RC_INVALID_STATE, "client is required", client, callback_userdata);
return NULL;
}
if (!client_name) {
callback(RC_INVALID_STATE, "client_name is required", client, callback_userdata);
return NULL;
}
if (!client_version) {
callback(RC_INVALID_STATE, "client_version is required", client, callback_userdata);
return NULL;
}
if (client->state.user != RC_CLIENT_USER_STATE_NONE) {
callback(RC_INVALID_STATE, "Cannot initialize RAIntegration after login", client, callback_userdata);
return NULL;
}
if (!client->state.raintegration) {
if (!main_window_handle) {
callback(RC_INVALID_STATE, "main_window_handle is required", client, callback_userdata);
return NULL;
}
rc_client_raintegration_load_dll(client, search_directory, callback, callback_userdata);
if (!client->state.raintegration)
return NULL;
}
if (client->state.raintegration->get_host_url) {
const char* host_url = client->state.raintegration->get_host_url();
if (host_url && strcmp(host_url, "https://retroachievements.org") != 0 &&
strcmp(host_url, "OFFLINE") != 0) {
/* if the DLL specifies a custom host, use it */
rc_client_set_host(client, host_url);
}
}
memset(&request, 0, sizeof(request));
rc_api_url_build_dorequest_url(&request);
rc_url_builder_init(&builder, &request.buffer, 48);
rc_url_builder_append_str_param(&builder, "r", "latestintegration");
request.post_data = rc_url_builder_finalize(&builder);
callback_data = calloc(1, sizeof(*callback_data));
if (!callback_data) {
callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), client, callback_userdata);
return NULL;
}
callback_data->client = client;
callback_data->callback = callback;
callback_data->callback_userdata = callback_userdata;
callback_data->client_name = strdup(client_name);
callback_data->client_version = strdup(client_version);
callback_data->main_window_handle = main_window_handle;
client->callbacks.server_call(&request, rc_client_version_validation_callback, callback_data, client);
return &callback_data->async_handle;
}
void rc_client_unload_raintegration(rc_client_t* client)
{
HINSTANCE hDLL;
if (!client || !client->state.raintegration)
return;
RC_CLIENT_LOG_INFO(client, "Unloading RA_Integration")
if (client->state.raintegration->shutdown) {
#ifdef __cplusplus
try {
#endif
client->state.raintegration->shutdown();
#ifdef __cplusplus
}
catch (std::runtime_error&) {
}
#endif
}
rc_mutex_lock(&client->state.mutex);
hDLL = client->state.raintegration->hDLL;
client->state.raintegration = NULL;
client->state.external_client = NULL;
rc_mutex_unlock(&client->state.mutex);
if (hDLL)
FreeLibrary(hDLL);
}
#endif /* RC_CLIENT_SUPPORTS_RAINTEGRATION */

View file

@ -0,0 +1,44 @@
#ifndef RC_CLIENT_RAINTEGRATION_INTERNAL_H
#define RC_CLIENT_RAINTEGRATION_INTERNAL_H
#ifdef __cplusplus
extern "C" {
#endif
#include "rc_client_raintegration.h"
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
#include "rc_client_external.h"
#include "rc_compat.h"
#ifndef CCONV
#define CCONV __cdecl
#endif
typedef void (CCONV* rc_client_raintegration_action_func)(void);
typedef const char* (CCONV* rc_client_raintegration_get_string_func)(void);
typedef int (CCONV* rc_client_raintegration_init_client_func)(HWND hMainWnd, const char* sClientName, const char* sClientVersion);
typedef int (CCONV* rc_client_raintegration_get_external_client)(rc_client_external_t* pClient, int nVersion);
typedef struct rc_client_raintegration_t
{
HINSTANCE hDLL;
rc_client_raintegration_get_string_func get_version;
rc_client_raintegration_get_string_func get_host_url;
rc_client_raintegration_init_client_func init_client;
rc_client_raintegration_init_client_func init_client_offline;
rc_client_raintegration_action_func shutdown;
rc_client_raintegration_get_external_client get_external_client;
} rc_client_raintegration_t;
#endif /* RC_CLIENT_SUPPORTS_RAINTEGRATION */
#ifdef __cplusplus
}
#endif
#endif /* RC_CLIENT_RAINTEGRATION_INTERNAL_H */

View file

@ -96,6 +96,11 @@ static const rc_disallowed_setting_t _rc_disallowed_mesen_s_settings[] = {
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_neocd_settings[] = {
{ "neocd_bios", "uni-bios*" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_pcsx_rearmed_settings[] = {
{ "pcsx_rearmed_region", "pal" },
{ NULL, NULL }
@ -152,6 +157,7 @@ static const rc_disallowed_core_settings_t rc_disallowed_core_settings[] = {
{ "Genesis Plus GX Wide", _rc_disallowed_gpgx_wide_settings },
{ "Mesen", _rc_disallowed_mesen_settings },
{ "Mesen-S", _rc_disallowed_mesen_s_settings },
{ "NeoCD", _rc_disallowed_neocd_settings },
{ "PPSSPP", _rc_disallowed_ppsspp_settings },
{ "PCSX-ReARMed", _rc_disallowed_pcsx_rearmed_settings },
{ "PicoDrive", _rc_disallowed_picodrive_settings },

View file

@ -5,7 +5,7 @@
void* rc_alloc_scratch(void* pointer, int32_t* offset, uint32_t size, uint32_t alignment, rc_scratch_t* scratch, uint32_t scratch_object_pointer_offset)
{
rc_scratch_buffer_t* buffer;
void* data;
/* if we have a real buffer, then allocate the data there */
if (pointer)
@ -19,49 +19,13 @@ void* rc_alloc_scratch(void* pointer, int32_t* offset, uint32_t size, uint32_t a
}
/* find a scratch buffer to hold the temporary data */
buffer = &scratch->buffer;
do {
const uint32_t aligned_buffer_offset = (buffer->offset + alignment - 1) & ~(alignment - 1);
if (aligned_buffer_offset < sizeof(buffer->buffer)) {
const uint32_t remaining = sizeof(buffer->buffer) - aligned_buffer_offset;
if (remaining >= size) {
/* claim the required space from an existing buffer */
return rc_alloc(buffer->buffer, &buffer->offset, size, alignment, NULL, -1);
}
}
if (!buffer->next)
break;
buffer = buffer->next;
} while (1);
/* not enough space in any existing buffer, allocate more */
if (size > (uint32_t)sizeof(buffer->buffer)) {
/* caller is asking for more than we can fit in a standard rc_scratch_buffer_t.
* leverage the fact that the buffer is the last field and extend its size.
* this chunk will be exactly large enough to hold the needed data, and since offset
* will exceed sizeof(buffer->buffer), it will never be eligible to hold anything else.
*/
const size_t needed = sizeof(rc_scratch_buffer_t) - sizeof(buffer->buffer) + size;
buffer->next = (rc_scratch_buffer_t*)malloc(needed);
}
else {
buffer->next = (rc_scratch_buffer_t*)malloc(sizeof(rc_scratch_buffer_t));
}
if (!buffer->next) {
data = rc_buffer_alloc(&scratch->buffer, size);
if (!data) {
*offset = RC_OUT_OF_MEMORY;
return NULL;
}
buffer = buffer->next;
buffer->offset = 0;
buffer->next = NULL;
/* claim the required space from the new buffer */
return rc_alloc(buffer->buffer, &buffer->offset, size, alignment, NULL, -1);
return data;
}
void* rc_alloc(void* pointer, int32_t* offset, uint32_t size, uint32_t alignment, rc_scratch_t* scratch, uint32_t scratch_object_pointer_offset) {
@ -137,9 +101,8 @@ void rc_init_parse_state(rc_parse_state_t* parse, void* buffer, lua_State* L, in
parse->L = L;
parse->funcs_ndx = funcs_ndx;
parse->buffer = buffer;
parse->scratch.buffer.offset = 0;
parse->scratch.buffer.next = NULL;
parse->scratch.strings = NULL;
rc_buffer_init(&parse->scratch.buffer);
memset(&parse->scratch.objs, 0, sizeof(parse->scratch.objs));
parse->first_memref = 0;
parse->variables = 0;
@ -151,12 +114,5 @@ void rc_init_parse_state(rc_parse_state_t* parse, void* buffer, lua_State* L, in
void rc_destroy_parse_state(rc_parse_state_t* parse)
{
rc_scratch_buffer_t* buffer = parse->scratch.buffer.next;
rc_scratch_buffer_t* next;
while (buffer) {
next = buffer->next;
free(buffer);
buffer = next;
}
rc_buffer_destroy(&parse->scratch.buffer);
}

View file

@ -2,6 +2,7 @@
#define RC_INTERNAL_H
#include "rc_runtime_types.h"
#include "../rc_util.h"
#ifdef __cplusplus
extern "C" {
@ -39,15 +40,8 @@ RC_ALLOW_ALIGN(char)
/* force alignment to 4 bytes on 32-bit systems, or 8 bytes on 64-bit systems */
#define RC_ALIGN(n) (((n) + (sizeof(void*)-1)) & ~(sizeof(void*)-1))
typedef struct rc_scratch_buffer {
struct rc_scratch_buffer* next;
int32_t offset;
uint8_t buffer[512 - 16];
}
rc_scratch_buffer_t;
typedef struct {
rc_scratch_buffer_t buffer;
rc_buffer_t buffer;
rc_scratch_string_t* strings;
struct objs {

View file

@ -279,7 +279,6 @@ static void rc_rebalance_richpresence_lookup_rebuild(rc_richpresence_lookup_item
static void rc_rebalance_richpresence_lookup(rc_richpresence_lookup_item_t** root, rc_parse_state_t* parse)
{
rc_richpresence_lookup_item_t** items;
rc_scratch_buffer_t* buffer;
int index;
int size;
@ -288,29 +287,13 @@ static void rc_rebalance_richpresence_lookup(rc_richpresence_lookup_item_t** roo
if (count < 3)
return;
/* allocate space for the flattened list - prefer scratch memory if available */
/* allocate space for the flattened list in scratch memory */
size = count * sizeof(rc_richpresence_lookup_item_t*);
buffer = &parse->scratch.buffer;
do {
const int aligned_offset = RC_ALIGN(buffer->offset);
const int remaining = sizeof(buffer->buffer) - aligned_offset;
if (remaining >= size) {
items = (rc_richpresence_lookup_item_t**)&buffer->buffer[aligned_offset];
break;
}
buffer = buffer->next;
if (buffer == NULL) {
/* could not find large enough block of scratch memory; allocate. if allocation fails,
* we can still use the unbalanced tree, so just bail out */
items = (rc_richpresence_lookup_item_t**)malloc(size);
if (items == NULL)
return;
items = (rc_richpresence_lookup_item_t**)rc_buffer_alloc(&parse->scratch.buffer, size);
break;
}
} while (1);
/* if allocation fails, we can still use the unbalanced tree, so just bail out */
if (items == NULL)
return;
/* flatten the list */
index = 0;
@ -318,9 +301,6 @@ static void rc_rebalance_richpresence_lookup(rc_richpresence_lookup_item_t** roo
/* and rebuild it as a balanced tree */
rc_rebalance_richpresence_lookup_rebuild(root, items, 0, count - 1);
if (buffer == NULL)
free(items);
}
static void rc_insert_richpresence_lookup_item(rc_richpresence_lookup_t* lookup,

View file

@ -2594,7 +2594,8 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
iterator->consoles[4] = RC_CONSOLE_PSP;
iterator->consoles[5] = RC_CONSOLE_PC_ENGINE_CD;
iterator->consoles[6] = RC_CONSOLE_3DO;
iterator->consoles[7] = RC_CONSOLE_PCFX;
iterator->consoles[7] = RC_CONSOLE_NEO_GEO_CD;
iterator->consoles[8] = RC_CONSOLE_PCFX;
need_path = 1;
}
else if (rc_path_compare_extension(ext, "col"))