dep/rcheevos: Bump to 8afec6c
This commit is contained in:
parent
78ef9e1105
commit
8431d3be0c
|
@ -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 */
|
|
@ -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;
|
||||
|
||||
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;
|
||||
#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
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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 */
|
|
@ -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;
|
||||
|
|
|
@ -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 */
|
|
@ -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 */
|
|
@ -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 },
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
items = (rc_richpresence_lookup_item_t**)rc_buffer_alloc(&parse->scratch.buffer, size);
|
||||
|
||||
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;
|
||||
|
||||
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,
|
||||
|
|
|
@ -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"))
|
||||
|
|
Loading…
Reference in New Issue