3rdparty: Update rcheevos to commit 0a7508d

This commit is contained in:
JordanTheToaster 2025-09-20 19:03:57 +01:00 committed by lightningterror
parent 535ebdbc8c
commit 24cb93d7c5
22 changed files with 335 additions and 141 deletions

View File

@ -11,7 +11,6 @@ add_library(rcheevos
include/rc_hash.h
include/rc_runtime.h
include/rc_runtime_types.h
include/rc_url.h
include/rc_util.h
src/rapi/rc_api_common.c
src/rapi/rc_api_common.h
@ -47,5 +46,5 @@ add_library(rcheevos
target_include_directories(rcheevos PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_include_directories(rcheevos INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_compile_definitions(rcheevos PRIVATE "RC_DISABLE_LUA=1" "RCHEEVOS_URL_SSL=1" "RC_NO_THREADS=1" "RC_HASH_NO_DISC" "RC_HASH_NO_ENCRYPTED" "RC_HASH_NO_ROM" "RC_HASH_NO_ZIP")
target_compile_definitions(rcheevos PRIVATE "RC_NO_THREADS=1" "RC_HASH_NO_DISC" "RC_HASH_NO_ENCRYPTED" "RC_HASH_NO_ROM" "RC_HASH_NO_ZIP")

View File

@ -186,6 +186,37 @@ RC_EXPORT int RC_CCONV rc_api_process_update_leaderboard_response(rc_api_update_
RC_EXPORT int RC_CCONV rc_api_process_update_leaderboard_server_response(rc_api_update_leaderboard_response_t* response, const rc_api_server_response_t* server_response);
RC_EXPORT void RC_CCONV rc_api_destroy_update_leaderboard_response(rc_api_update_leaderboard_response_t* response);
/* --- Update Rich Presence --- */
/**
* API parameters for an update rich presence request.
*/
typedef struct rc_api_update_rich_presence_request_t {
/* The username of the developer */
const char* username;
/* The API token from the login request */
const char* api_token;
/* The unique identifier of the game */
uint32_t game_id;
/* The script for the rich_presence */
const char* script;
}
rc_api_update_rich_presence_request_t;
/**
* Response data for an update rich presence request.
*/
typedef struct rc_api_update_rich_presence_response_t {
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_update_rich_presence_response_t;
RC_EXPORT int RC_CCONV rc_api_init_update_rich_presence_request(rc_api_request_t* request, const rc_api_update_rich_presence_request_t* api_params);
RC_EXPORT int RC_CCONV rc_api_init_update_rich_presence_request_hosted(rc_api_request_t* request, const rc_api_update_rich_presence_request_t* api_params, const rc_api_host_t* host);
RC_EXPORT int RC_CCONV rc_api_process_update_rich_presence_server_response(rc_api_update_rich_presence_response_t* response, const rc_api_server_response_t* server_response);
RC_EXPORT void RC_CCONV rc_api_destroy_update_rich_presence_response(rc_api_update_rich_presence_response_t* response);
/* --- Fetch Badge Range --- */
/**

View File

@ -211,6 +211,10 @@ typedef struct rc_client_user_game_summary_t {
uint32_t points_core;
uint32_t points_unlocked;
/* minimum version: 12.1 */
time_t beaten_time;
time_t completed_time;
} rc_client_user_game_summary_t;
/**
@ -358,6 +362,8 @@ typedef struct rc_client_subset_t {
RC_EXPORT const rc_client_subset_t* RC_CCONV rc_client_get_subset_info(rc_client_t* client, uint32_t subset_id);
RC_EXPORT void RC_CCONV rc_client_get_user_subset_summary(const rc_client_t* client, uint32_t subset_id, rc_client_user_game_summary_t* summary);
/*****************************************************************************\
| Fetch Game Hashes |
\*****************************************************************************/
@ -582,7 +588,7 @@ enum {
RC_EXPORT rc_client_leaderboard_list_t* RC_CCONV rc_client_create_leaderboard_list(rc_client_t* client, int grouping);
/**
* Destroys a list allocated by rc_client_get_leaderboard_list.
* Destroys a list allocated by rc_client_create_leaderboard_list.
*/
RC_EXPORT void RC_CCONV rc_client_destroy_leaderboard_list(rc_client_leaderboard_list_t* list);

View File

@ -37,7 +37,7 @@
* #endif
*/
#ifdef __cplusplus
#if defined(__cplusplus) && !defined(CXX_BUILD)
#define RC_BEGIN_C_DECLS extern "C" {
#define RC_END_C_DECLS }
#else

View File

@ -154,6 +154,7 @@ typedef struct rc_hash_iterator {
uint8_t consoles[12];
int index;
const char* path;
void* userdata;
rc_hash_callbacks_t callbacks;
} rc_hash_iterator_t;

View File

@ -1,36 +0,0 @@
#ifndef RC_URL_H
#define RC_URL_H
#include "rc_export.h"
#include <stddef.h>
RC_BEGIN_C_DECLS
RC_EXPORT int RC_CCONV rc_url_award_cheevo(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned cheevo_id, int hardcore, const char* game_hash);
RC_EXPORT int RC_CCONV rc_url_submit_lboard(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned lboard_id, int value);
RC_EXPORT int RC_CCONV rc_url_get_lboard_entries(char* buffer, size_t size, unsigned lboard_id, unsigned first_index, unsigned count);
RC_EXPORT int RC_CCONV rc_url_get_lboard_entries_near_user(char* buffer, size_t size, unsigned lboard_id, const char* user_name, unsigned count);
RC_EXPORT int RC_CCONV rc_url_get_gameid(char* buffer, size_t size, const char* hash);
RC_EXPORT int RC_CCONV rc_url_get_patch(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned gameid);
RC_EXPORT int RC_CCONV rc_url_get_badge_image(char* buffer, size_t size, const char* badge_name);
RC_EXPORT int RC_CCONV rc_url_login_with_password(char* buffer, size_t size, const char* user_name, const char* password);
RC_EXPORT int RC_CCONV rc_url_login_with_token(char* buffer, size_t size, const char* user_name, const char* login_token);
RC_EXPORT int RC_CCONV rc_url_get_unlock_list(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned gameid, int hardcore);
RC_EXPORT int RC_CCONV rc_url_post_playing(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned gameid);
RC_EXPORT int RC_CCONV rc_url_ping(char* url_buffer, size_t url_buffer_size, char* post_buffer, size_t post_buffer_size,
const char* user_name, const char* login_token, unsigned gameid, const char* rich_presence);
RC_END_C_DECLS
#endif /* RC_URL_H */

View File

@ -273,17 +273,17 @@ static int rc_json_extract_html_error(rc_api_response_t* response, const rc_api_
iterator.json = server_response->body;
iterator.end = server_response->body + server_response->body_length;
/* if the title contains an HTTP status code(i.e "404 Not Found"), return the title */
/* assume the title contains the most appropriate message to display to the user */
if (rc_json_find_substring(&iterator, "<title>")) {
const char* title_start = iterator.json + 7;
if (isdigit((int)*title_start) && rc_json_find_substring(&iterator, "</title>")) {
if (rc_json_find_substring(&iterator, "</title>")) {
response->error_message = rc_buffer_strncpy(&response->buffer, title_start, iterator.json - title_start);
response->succeeded = 0;
return RC_INVALID_JSON;
}
}
/* title not found, or did not start with an error code, return the first line of the response */
/* title not found, return the first line of the response (up to 200 characters) */
iterator.json = server_response->body;
while (iterator.json < iterator.end && *iterator.json != '\n' &&

View File

@ -100,7 +100,7 @@ int rc_api_process_fetch_code_notes_server_response(rc_api_fetch_code_notes_resp
if (!rc_json_get_required_string(&address_str, &response->response, &note_fields[0], "Address"))
return RC_MISSING_VALUE;
note->address = (unsigned)strtol(address_str, NULL, 16);
note->address = (uint32_t)strtoul(address_str, NULL, 16);
if (!rc_json_get_required_string(&note->note, &response->response, &note_fields[2], "Note"))
return RC_MISSING_VALUE;
@ -419,6 +419,60 @@ void rc_api_destroy_update_leaderboard_response(rc_api_update_leaderboard_respon
rc_buffer_destroy(&response->response.buffer);
}
/* --- Update Rich Presence --- */
int rc_api_init_update_rich_presence_request(rc_api_request_t* request, const rc_api_update_rich_presence_request_t* api_params) {
return rc_api_init_update_rich_presence_request_hosted(request, api_params, &g_host);
}
int rc_api_init_update_rich_presence_request_hosted(rc_api_request_t* request,
const rc_api_update_rich_presence_request_t* api_params,
const rc_api_host_t* host) {
rc_api_url_builder_t builder;
rc_api_url_build_dorequest_url(request, host);
if (api_params->game_id == 0)
return RC_INVALID_STATE;
if (!api_params->script)
return RC_INVALID_STATE;
rc_url_builder_init(&builder, &request->buffer, 128);
if (!rc_api_url_build_dorequest(&builder, "submitrichpresence", api_params->username, api_params->api_token))
return builder.result;
rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
rc_url_builder_append_str_param(&builder, "d", api_params->script);
request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
return builder.result;
}
int rc_api_process_update_rich_presence_server_response(rc_api_update_rich_presence_response_t* response, const rc_api_server_response_t* server_response) {
int result;
rc_json_field_t fields[] = {
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error"),
RC_JSON_NEW_FIELD("Code"),
};
memset(response, 0, sizeof(*response));
rc_buffer_init(&response->response.buffer);
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
if (result != RC_OK || !response->response.succeeded)
return result;
return RC_OK;
}
void rc_api_destroy_update_rich_presence_response(rc_api_update_rich_presence_response_t* response) {
rc_buffer_destroy(&response->response.buffer);
}
/* --- Fetch Badge Range --- */
int rc_api_init_fetch_badge_range_request(rc_api_request_t* request, const rc_api_fetch_badge_range_request_t* api_params) {

View File

@ -905,11 +905,22 @@ int rc_client_user_get_image_url(const rc_client_user_t* user, char buffer[], si
return rc_client_get_image_url(buffer, buffer_size, RC_IMAGE_TYPE_USER, user->display_name);
}
static void rc_client_subset_get_user_game_summary(const rc_client_subset_info_t* subset,
rc_client_user_game_summary_t* summary, const uint8_t unlock_bit)
static void rc_client_subset_get_user_game_summary(const rc_client_t* client,
const rc_client_subset_info_t* subset, rc_client_user_game_summary_t* summary)
{
rc_client_achievement_info_t* achievement = subset->achievements;
rc_client_achievement_info_t* stop = achievement + subset->public_.num_achievements;
time_t last_unlock_time = 0;
time_t last_progression_time = 0;
time_t first_win_time = 0;
int num_progression_achievements = 0;
int num_win_achievements = 0;
int num_unlocked_progression_achievements = 0;
const uint8_t unlock_bit = (client->state.hardcore) ?
RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE : RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE;
rc_mutex_lock((rc_mutex_t*)&client->state.mutex); /* remove const cast for mutex access */
for (; achievement < stop; ++achievement) {
switch (achievement->public_.category) {
case RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE:
@ -919,10 +930,28 @@ static void rc_client_subset_get_user_game_summary(const rc_client_subset_info_t
if (achievement->public_.unlocked & unlock_bit) {
++summary->num_unlocked_achievements;
summary->points_unlocked += achievement->public_.points;
if (achievement->public_.unlock_time > last_unlock_time)
last_unlock_time = achievement->public_.unlock_time;
if (achievement->public_.type == RC_CLIENT_ACHIEVEMENT_TYPE_PROGRESSION) {
++num_unlocked_progression_achievements;
if (achievement->public_.unlock_time > last_progression_time)
last_progression_time = achievement->public_.unlock_time;
}
else if (achievement->public_.type == RC_CLIENT_ACHIEVEMENT_TYPE_WIN) {
if (first_win_time == 0 || achievement->public_.unlock_time < first_win_time)
first_win_time = achievement->public_.unlock_time;
}
}
if (achievement->public_.bucket == RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED) {
if (achievement->public_.type == RC_CLIENT_ACHIEVEMENT_TYPE_PROGRESSION)
++num_progression_achievements;
else if (achievement->public_.type == RC_CLIENT_ACHIEVEMENT_TYPE_WIN)
++num_win_achievements;
if (achievement->public_.bucket == RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED)
++summary->num_unsupported_achievements;
}
break;
@ -934,13 +963,18 @@ static void rc_client_subset_get_user_game_summary(const rc_client_subset_info_t
continue;
}
}
rc_mutex_unlock((rc_mutex_t*)&client->state.mutex); /* remove const cast for mutex access */
if (summary->num_unlocked_achievements == summary->num_core_achievements)
summary->completed_time = last_unlock_time;
if ((first_win_time || num_win_achievements == 0) && num_unlocked_progression_achievements == num_progression_achievements)
summary->beaten_time = (first_win_time > last_progression_time) ? first_win_time : last_progression_time;
}
void rc_client_get_user_game_summary(const rc_client_t* client, rc_client_user_game_summary_t* summary)
{
const uint8_t unlock_bit = (client->state.hardcore) ?
RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE : RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE;
if (!summary)
return;
@ -949,20 +983,47 @@ void rc_client_get_user_game_summary(const rc_client_t* client, rc_client_user_g
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);
if (client->state.external_client) {
if (client->state.external_client->get_user_game_summary_v5) {
client->state.external_client->get_user_game_summary_v5(summary);
return;
}
if (client->state.external_client->get_user_game_summary) {
client->state.external_client->get_user_game_summary(summary);
return;
}
}
#endif
if (rc_client_is_game_loaded(client))
rc_client_subset_get_user_game_summary(client, client->game->subsets, summary);
}
void rc_client_get_user_subset_summary(const rc_client_t* client, uint32_t subset_id, rc_client_user_game_summary_t* summary)
{
if (!summary)
return;
memset(summary, 0, sizeof(*summary));
if (!client || !subset_id)
return;
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->get_user_subset_summary) {
client->state.external_client->get_user_subset_summary(subset_id, summary);
return;
}
#endif
if (!rc_client_is_game_loaded(client))
return;
rc_mutex_lock((rc_mutex_t*)&client->state.mutex); /* remove const cast for mutex access */
rc_client_subset_get_user_game_summary(client->game->subsets, summary, unlock_bit);
rc_mutex_unlock((rc_mutex_t*)&client->state.mutex); /* remove const cast for mutex access */
if (rc_client_is_game_loaded(client)) {
const rc_client_subset_info_t* subset = client->game->subsets;
for (; subset; subset = subset->next) {
if (subset->public_.id == subset_id) {
rc_client_subset_get_user_game_summary(client, subset, summary);
break;
}
}
}
}
typedef struct rc_client_fetch_all_user_progress_callback_data_t {
@ -1376,29 +1437,34 @@ static uint32_t rc_client_subset_toggle_hardcore_achievements(rc_client_subset_i
break;
}
}
else if (achievement->public_.state == RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE ||
achievement->public_.state == RC_CLIENT_ACHIEVEMENT_STATE_INACTIVE) {
/* if it's active despite being unlocked, and we're in encore mode, leave it active */
if (client->state.encore_mode) {
++active_count;
continue;
}
achievement->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED;
else {
achievement->public_.unlock_time = (active_bit == RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE) ?
achievement->unlock_time_hardcore : achievement->unlock_time_softcore;
achievement->unlock_time_hardcore : achievement->unlock_time_softcore;
if (achievement->trigger && achievement->trigger->state == RC_TRIGGER_STATE_PRIMED) {
rc_client_event_t client_event;
memset(&client_event, 0, sizeof(client_event));
client_event.type = RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_HIDE;
client_event.achievement = &achievement->public_;
client->callbacks.event_handler(&client_event, client);
if (achievement->public_.state == RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE ||
achievement->public_.state == RC_CLIENT_ACHIEVEMENT_STATE_INACTIVE) {
/* if it's active despite being unlocked, and we're in encore mode, leave it active */
if (client->state.encore_mode) {
++active_count;
continue;
}
/* switch to inactive */
achievement->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED;
if (achievement->trigger && rc_trigger_state_active(achievement->trigger->state)) {
/* hide any active challenge indicators */
if (achievement->trigger->state == RC_TRIGGER_STATE_PRIMED) {
rc_client_event_t client_event;
memset(&client_event, 0, sizeof(client_event));
client_event.type = RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_HIDE;
client_event.achievement = &achievement->public_;
client->callbacks.event_handler(&client_event, client);
}
achievement->trigger->state = RC_TRIGGER_STATE_TRIGGERED;
}
}
if (achievement->trigger && rc_trigger_state_active(achievement->trigger->state))
achievement->trigger->state = RC_TRIGGER_STATE_TRIGGERED;
}
}
@ -1728,7 +1794,9 @@ static void rc_client_activate_game(rc_client_load_state_t* load_state, rc_api_s
if (load_state->hash->hash[0] != '[') {
if (load_state->client->state.spectator_mode != RC_CLIENT_SPECTATOR_MODE_LOCKED) {
/* schedule the periodic ping */
rc_client_scheduled_callback_data_t* callback_data = rc_buffer_alloc(&load_state->game->buffer, sizeof(rc_client_scheduled_callback_data_t));
rc_client_scheduled_callback_data_t* callback_data = (rc_client_scheduled_callback_data_t*)
rc_buffer_alloc(&load_state->game->buffer, sizeof(rc_client_scheduled_callback_data_t));
memset(callback_data, 0, sizeof(*callback_data));
callback_data->callback = rc_client_ping;
callback_data->related_id = load_state->game->public_.id;
@ -1768,7 +1836,9 @@ static void rc_client_dispatch_activate_game(struct rc_client_scheduled_callback
static void rc_client_queue_activate_game(rc_client_load_state_t* load_state)
{
rc_client_scheduled_callback_data_t* scheduled_callback_data = calloc(1, sizeof(rc_client_scheduled_callback_data_t));
rc_client_scheduled_callback_data_t* scheduled_callback_data =
(rc_client_scheduled_callback_data_t*)calloc(1, sizeof(rc_client_scheduled_callback_data_t));
if (!scheduled_callback_data) {
rc_client_load_error(load_state, RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY));
return;
@ -1805,7 +1875,7 @@ static void rc_client_start_session_callback(const rc_api_server_response_t* ser
outstanding_requests = rc_client_end_load_state(load_state);
if (error_message) {
rc_client_load_error(callback_data, result, error_message);
rc_client_load_error(load_state, result, error_message);
}
else if (outstanding_requests < 0) {
/* previous load state was aborted, load_state was free'd */
@ -1818,7 +1888,7 @@ static void rc_client_start_session_callback(const rc_api_server_response_t* ser
(rc_api_start_session_response_t*)malloc(sizeof(rc_api_start_session_response_t));
if (!load_state->start_session_response) {
rc_client_load_error(callback_data, RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY));
rc_client_load_error(load_state, RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY));
}
else {
/* safer to parse the response again than to try to copy it */
@ -1911,7 +1981,7 @@ static void rc_client_copy_achievements(rc_client_load_state_t* load_state,
/* allocate the achievement array */
size = sizeof(rc_client_achievement_info_t) * num_achievements;
achievement = achievements = rc_buffer_alloc(buffer, size);
achievement = achievements = (rc_client_achievement_info_t*)rc_buffer_alloc(buffer, size);
memset(achievements, 0, size);
/* copy the achievement data */
@ -1960,7 +2030,7 @@ static void rc_client_copy_achievements(rc_client_load_state_t* load_state,
achievement->public_.bucket = RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED;
}
else {
rc_buffer_consume(buffer, preparse.parse.buffer, (uint8_t*)preparse.parse.buffer + preparse.parse.offset);
rc_buffer_consume(buffer, (const uint8_t*)preparse.parse.buffer, (uint8_t*)preparse.parse.buffer + preparse.parse.offset);
}
rc_destroy_preparse_state(&preparse);
@ -2053,7 +2123,7 @@ static void rc_client_copy_leaderboards(rc_client_load_state_t* load_state,
/* allocate the achievement array */
size = sizeof(rc_client_leaderboard_info_t) * num_leaderboards;
buffer = &load_state->game->buffer;
leaderboard = leaderboards = rc_buffer_alloc(buffer, size);
leaderboard = leaderboards = (rc_client_leaderboard_info_t*)rc_buffer_alloc(buffer, size);
memset(leaderboards, 0, size);
/* copy the achievement data */
@ -2103,7 +2173,7 @@ static void rc_client_copy_leaderboards(rc_client_load_state_t* load_state,
leaderboard->public_.state = RC_CLIENT_LEADERBOARD_STATE_DISABLED;
}
else {
rc_buffer_consume(buffer, preparse.parse.buffer, (uint8_t*)preparse.parse.buffer + preparse.parse.offset);
rc_buffer_consume(buffer, (const uint8_t*)preparse.parse.buffer, (uint8_t*)preparse.parse.buffer + preparse.parse.offset);
}
rc_destroy_preparse_state(&preparse);
@ -2618,7 +2688,7 @@ rc_client_game_hash_t* rc_client_find_game_hash(rc_client_t* client, const char*
}
if (!game_hash) {
game_hash = rc_buffer_alloc(&client->state.buffer, sizeof(rc_client_game_hash_t));
game_hash = (rc_client_game_hash_t*)rc_buffer_alloc(&client->state.buffer, sizeof(rc_client_game_hash_t));
memset(game_hash, 0, sizeof(*game_hash));
snprintf(game_hash->hash, sizeof(game_hash->hash), "%s", hash);
game_hash->game_id = RC_CLIENT_UNKNOWN_GAME_ID;
@ -2796,16 +2866,14 @@ rc_hash_iterator_t* rc_client_get_load_state_hash_iterator(rc_client_t* client)
static void rc_client_log_hash_message_verbose(const char* message, const rc_hash_iterator_t* iterator)
{
rc_client_load_state_t unused;
rc_client_load_state_t* load_state = (rc_client_load_state_t*)(((uint8_t*)iterator) - RC_OFFSETOF(unused, hash_iterator));
const rc_client_load_state_t* load_state = (const rc_client_load_state_t*)iterator->userdata;
if (load_state->client->state.log_level >= RC_CLIENT_LOG_LEVEL_INFO)
rc_client_log_message(load_state->client, message);
}
static void rc_client_log_hash_message_error(const char* message, const rc_hash_iterator_t* iterator)
{
rc_client_load_state_t unused;
rc_client_load_state_t* load_state = (rc_client_load_state_t*)(((uint8_t*)iterator) - RC_OFFSETOF(unused, hash_iterator));
const rc_client_load_state_t* load_state = (const rc_client_load_state_t*)iterator->userdata;
if (load_state->client->state.log_level >= RC_CLIENT_LOG_LEVEL_ERROR)
rc_client_log_message(load_state->client, message);
}
@ -2875,6 +2943,7 @@ rc_client_async_handle_t* rc_client_begin_identify_and_load_game(rc_client_t* cl
/* initialize the iterator */
rc_hash_initialize_iterator(&load_state->hash_iterator, file_path, data, data_size);
rc_hash_merge_callbacks(&load_state->hash_iterator, &client->callbacks.hash);
load_state->hash_iterator.userdata = load_state;
if (!load_state->hash_iterator.callbacks.verbose_message)
load_state->hash_iterator.callbacks.verbose_message = rc_client_log_hash_message_verbose;
@ -3114,7 +3183,8 @@ static rc_client_async_handle_t* rc_client_begin_change_media_internal(rc_client
rc_api_request_t request;
int result;
if (game_hash->game_id != RC_CLIENT_UNKNOWN_GAME_ID) {
if (game_hash->game_id != RC_CLIENT_UNKNOWN_GAME_ID || /* previously looked up */
game_hash->hash[0] == '[') { /* internal use - don't try to look up */
rc_client_change_media_internal(client, game_hash, callback, callback_userdata);
return NULL;
}
@ -3207,7 +3277,7 @@ static rc_client_game_info_t* rc_client_check_pending_media(rc_client_t* client,
}
/* still waiting for game data - don't call callback - it's queued */
if (pending_media)
if (pending_media)
return NULL;
return game;
@ -3364,7 +3434,7 @@ const rc_client_game_t* rc_client_get_game_info(const rc_client_t* client)
if (client->state.external_client->get_game_info)
return rc_client_external_convert_v1_game(client, client->state.external_client->get_game_info());
}
}
#endif
return client->game ? &client->game->public_ : NULL;
@ -4130,7 +4200,7 @@ static void rc_client_award_achievement_callback(const rc_api_server_response_t*
}
static void rc_client_award_achievement_server_call(rc_client_award_achievement_callback_data_t* ach_data)
{
{
rc_api_award_achievement_request_t api_params;
rc_api_request_t request;
int result;
@ -4284,7 +4354,7 @@ const rc_client_leaderboard_t* rc_client_get_leaderboard_info(const rc_client_t*
if (leaderboard != NULL)
return &leaderboard->public_;
}
return NULL;
}
@ -4368,7 +4438,7 @@ rc_client_leaderboard_list_t* rc_client_create_leaderboard_list(rc_client_t* cli
};
if (!client)
return calloc(1, sizeof(rc_client_leaderboard_list_t));
return (rc_client_leaderboard_list_t*)calloc(1, list_size);
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->create_leaderboard_list)
@ -4376,7 +4446,7 @@ rc_client_leaderboard_list_t* rc_client_create_leaderboard_list(rc_client_t* cli
#endif
if (!client->game)
return calloc(1, sizeof(rc_client_leaderboard_list_t));
return (rc_client_leaderboard_list_t*)calloc(1, list_size);
memset(&bucket_counts, 0, sizeof(bucket_counts));

View File

@ -35,6 +35,7 @@ typedef rc_client_async_handle_t* (RC_CCONV *rc_client_external_begin_load_subse
uint32_t subset_id, rc_client_callback_t callback, void* callback_userdata);
typedef const rc_client_game_t* (RC_CCONV *rc_client_external_get_game_info_func_t)(void);
typedef const rc_client_subset_t* (RC_CCONV *rc_client_external_get_subset_info_func_t)(uint32_t subset_id);
typedef void (RC_CCONV* rc_client_external_get_user_subset_summary_func_t)(uint32_t subset_id, rc_client_user_game_summary_t* summary);
typedef void (RC_CCONV *rc_client_external_get_user_game_summary_func_t)(rc_client_user_game_summary_t* summary);
typedef rc_client_async_handle_t* (RC_CCONV *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);
@ -139,9 +140,13 @@ typedef struct rc_client_external_t
/* VERSION 4 */
rc_client_external_set_int_func_t set_allow_background_memory_reads;
/* VERSION 5 */
rc_client_external_get_user_game_summary_func_t get_user_game_summary_v5;
rc_client_external_get_user_subset_summary_func_t get_user_subset_summary;
} rc_client_external_t;
#define RC_CLIENT_EXTERNAL_VERSION 4
#define RC_CLIENT_EXTERNAL_VERSION 5
void rc_client_add_game_hash(rc_client_t* client, const char* hash, uint32_t game_id);
void rc_client_load_unknown_game(rc_client_t* client, const char* hash);

View File

@ -144,6 +144,28 @@ typedef struct v3_rc_client_achievement_list_info_t {
rc_client_destroy_achievement_list_func_t destroy_func;
} v3_rc_client_achievement_list_info_t;
/* user_game_summary */
typedef struct v1_rc_client_user_game_summary_t {
uint32_t num_core_achievements;
uint32_t num_unofficial_achievements;
uint32_t num_unlocked_achievements;
uint32_t num_unsupported_achievements;
uint32_t points_core;
uint32_t points_unlocked;
} v1_rc_client_user_game_summary_t;
typedef struct v5_rc_client_user_game_summary_t {
uint32_t num_core_achievements;
uint32_t num_unofficial_achievements;
uint32_t num_unlocked_achievements;
uint32_t num_unsupported_achievements;
uint32_t points_core;
uint32_t points_unlocked;
time_t beaten_time;
time_t completed_time;
} v5_rc_client_user_game_summary_t;
RC_END_C_DECLS
#endif /* RC_CLIENT_EXTERNAL_CONVERSIONS_H */

View File

@ -78,6 +78,7 @@ static const rc_disallowed_setting_t _rc_disallowed_fbneo_settings[] = {
};
static const rc_disallowed_setting_t _rc_disallowed_fceumm_settings[] = {
{ "fceumm_game_genie", "!disabled" },
{ "fceumm_region", ",PAL,Dendy" },
{ NULL, NULL }
};

View File

@ -343,8 +343,12 @@ rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse) {
if (parse->buffer) {
classification = rc_classify_condition(&condition);
if (classification == RC_CONDITION_CLASSIFICATION_COMBINING) {
if (combining_classification == RC_CONDITION_CLASSIFICATION_COMBINING)
combining_classification = rc_find_next_classification(&(*memaddr)[1]); /* skip over '_' */
if (combining_classification == RC_CONDITION_CLASSIFICATION_COMBINING) {
if (**memaddr == '_')
combining_classification = rc_find_next_classification(&(*memaddr)[1]); /* skip over '_' */
else
combining_classification = RC_CONDITION_CLASSIFICATION_OTHER;
}
classification = combining_classification;
}

View File

@ -971,9 +971,10 @@ static const rc_memory_regions_t rc_memory_regions_wasm4 = { _rc_memory_regions_
/* https://wiibrew.org/wiki/Memory_map */
static const rc_memory_region_t _rc_memory_regions_wii[] = {
{ 0x00000000U, 0x017FFFFF, 0x80000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
{ 0x01800000U, 0x057FFFFF, 0x90000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }
{ 0x01800000U, 0x0FFFFFFF, 0x81800000U, RC_MEMORY_TYPE_UNUSED, "Unused" },
{ 0x10000000U, 0x13FFFFFF, 0x90000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }
};
static const rc_memory_regions_t rc_memory_regions_wii = { _rc_memory_regions_wii, 2 };
static const rc_memory_regions_t rc_memory_regions_wii = { _rc_memory_regions_wii, 3 };
/* ===== WonderSwan ===== */
/* http://daifukkat.su/docs/wsman/#ovr_memmap */

View File

@ -328,7 +328,7 @@ static float rc_build_float(uint32_t mantissa_bits, int32_t exponent, int sign)
if (mantissa_bits == 0) {
/* infinity */
#ifdef INFINITY /* INFINITY and NAN #defines require C99 */
dbl = INFINITY;
dbl = (double)INFINITY;
#else
dbl = -log(0.0);
#endif

View File

@ -380,9 +380,6 @@ int rc_operand_is_float_memref(const rc_operand_t* self) {
if (!rc_operand_is_memref(self))
return 0;
if (self->type == RC_OPERAND_RECALL)
return rc_memsize_is_float(self->memref_access_type);
if (self->value.memref->value.memref_type == RC_MEMREF_TYPE_MODIFIED_MEMREF) {
const rc_modified_memref_t* memref = (const rc_modified_memref_t*)self->value.memref;
if (memref->modifier_type != RC_OPERATOR_INDIRECT_READ)
@ -434,6 +431,9 @@ int rc_operand_is_float(const rc_operand_t* self) {
if (self->type == RC_OPERAND_FP)
return 1;
if (self->type == RC_OPERAND_RECALL)
return rc_memsize_is_float(self->size);
return rc_operand_is_float_memref(self);
}

View File

@ -174,8 +174,8 @@ static uint32_t rc_scale_value(uint32_t value, uint8_t oper, const rc_operand_t*
case RC_OPERATOR_SUB:
{
const uint32_t op_max = (operand->type == RC_OPERAND_CONST) ? operand->value.num : rc_max_value(operand);
if (value > op_max)
const uint32_t op_max = (operand->type == RC_OPERAND_CONST) ? operand->value.num : 0;
if (value >= op_max)
return value - op_max;
return 0xFFFFFFFF;
@ -351,11 +351,11 @@ static int rc_validate_condset_internal(const rc_condset_t* condset, char result
snprintf(result, result_size, "Condition %d: Using pointer from previous frame", index);
return 0;
}
if (rc_operand_is_float(&cond->operand1) || rc_operand_is_float(&cond->operand2)) {
if (rc_operand_is_float(operand1) || rc_operand_is_float(&cond->operand2)) {
snprintf(result, result_size, "Condition %d: Using non-integer value in AddAddress calcuation", index);
return 0;
}
if (rc_operand_type_is_transform(cond->operand1.type)) {
if (rc_operand_type_is_transform(operand1->type) && cond->oper != RC_OPERATOR_MULT) {
snprintf(result, result_size, "Condition %d: Using transformed value in AddAddress calcuation", index);
return 0;
}
@ -721,7 +721,7 @@ static int rc_validate_comparison_overlap(int comparison1, uint32_t value1, int
}
static int rc_validate_conflicting_conditions(const rc_condset_t* conditions, const rc_condset_t* compare_conditions,
const char* prefix, const char* compare_prefix, char result[], const size_t result_size)
int has_hits, const char* prefix, const char* compare_prefix, char result[], const size_t result_size)
{
int comparison1, comparison2;
uint32_t value1, value2;
@ -895,7 +895,15 @@ static int rc_validate_conflicting_conditions(const rc_condset_t* conditions, co
{
/* only ever report the redundancy on the non-ResetIf condition. The ResetIf is allowed to
* fire when the non-ResetIf condition is not true. */
continue;
if (has_hits)
continue;
}
else if (condition->type == RC_CONDITION_RESET_IF && compare_condition->type != RC_CONDITION_RESET_IF)
{
/* if the ResetIf condition is more restrictive than the non-ResetIf condition,
and there aren't any hits to clear, ignore it */
if (has_hits)
continue;
}
else if (compare_condition->type == RC_CONDITION_MEASURED_IF || condition->type == RC_CONDITION_MEASURED_IF)
{
@ -947,12 +955,21 @@ static int rc_validate_trigger_internal(const rc_trigger_t* trigger, char result
{
const rc_condset_t* alt;
int index;
int has_hits = (trigger->requirement && trigger->requirement->num_hittarget_conditions > 0);
if (!has_hits) {
for (alt = trigger->alternative; alt; alt = alt->next) {
if (alt->num_hittarget_conditions > 0) {
has_hits = 1;
break;
}
}
}
if (!trigger->alternative) {
if (!rc_validate_condset_internal(trigger->requirement, result, result_size, console_id, max_address))
return 0;
return rc_validate_conflicting_conditions(trigger->requirement, trigger->requirement, "", "", result, result_size);
return rc_validate_conflicting_conditions(trigger->requirement, trigger->requirement, has_hits, "", "", result, result_size);
}
snprintf(result, result_size, "Core ");
@ -960,7 +977,7 @@ static int rc_validate_trigger_internal(const rc_trigger_t* trigger, char result
return 0;
/* compare core to itself */
if (!rc_validate_conflicting_conditions(trigger->requirement, trigger->requirement, "Core", "Core", result, result_size))
if (!rc_validate_conflicting_conditions(trigger->requirement, trigger->requirement, has_hits, "Core", "Core", result, result_size))
return 0;
index = 1;
@ -972,15 +989,15 @@ static int rc_validate_trigger_internal(const rc_trigger_t* trigger, char result
/* compare alt to itself */
snprintf(altname, sizeof(altname), "Alt%d", index);
if (!rc_validate_conflicting_conditions(alt, alt, altname, altname, result, result_size))
if (!rc_validate_conflicting_conditions(alt, alt, has_hits, altname, altname, result, result_size))
return 0;
/* compare alt to core */
if (!rc_validate_conflicting_conditions(trigger->requirement, alt, "Core", altname, result, result_size))
if (!rc_validate_conflicting_conditions(trigger->requirement, alt, has_hits, "Core", altname, result, result_size))
return 0;
/* compare core to alt */
if (!rc_validate_conflicting_conditions(alt, trigger->requirement, altname, "Core", result, result_size))
if (!rc_validate_conflicting_conditions(alt, trigger->requirement, has_hits, altname, "Core", result, result_size))
return 0;
}

View File

@ -48,8 +48,9 @@ static void rc_alloc_helper_variable_memref_value(rc_richpresence_display_part_t
condset = value->conditions;
if (condset && !condset->next) {
/* single value - if it's only "measured" and "indirect" conditions, we can simplify to a memref */
if (condset->num_measured_conditions &&
/* single value - if it's a single Measured clause (including any AddSource/AddAddress helpers), we can
* simplify to a memref. If there are supporting clauses like MeasuredIf or ResetIf, we can't */
if (condset->num_measured_conditions == 1 &&
!condset->num_pause_conditions && !condset->num_reset_conditions &&
!condset->num_other_conditions && !condset->num_hittarget_conditions) {
rc_condition_t* condition = condset->conditions;

View File

@ -36,7 +36,7 @@ rc_runtime_t* rc_runtime_alloc(void) {
rc_runtime_event_handler_t unused = &rc_runtime_natvis_helper;
(void)unused;
self = malloc(sizeof(rc_runtime_t));
self = (rc_runtime_t*)malloc(sizeof(rc_runtime_t));
if (self) {
rc_runtime_init(self);
@ -307,7 +307,7 @@ int rc_runtime_activate_lboard(rc_runtime_t* self, uint32_t id, const char* mema
rc_lboard_t* lboard;
rc_preparse_state_t preparse;
rc_runtime_lboard_t* runtime_lboard;
int size;
int32_t size;
uint32_t i;
(void)unused_L;
@ -797,7 +797,7 @@ void rc_runtime_invalidate_address(rc_runtime_t* self, uint32_t address) {
} while (memref_list);
}
void rc_runtime_validate_addresses(rc_runtime_t* self, rc_runtime_event_handler_t event_handler,
void rc_runtime_validate_addresses(rc_runtime_t* self, rc_runtime_event_handler_t event_handler,
rc_runtime_validate_address_t validate_handler) {
int num_invalid = 0;
rc_memref_list_t* memref_list = &self->memrefs->memrefs;

View File

@ -920,7 +920,7 @@ uint32_t rc_runtime_progress_size(const rc_runtime_t* runtime, void* unused_L)
int rc_runtime_serialize_progress(void* buffer, const rc_runtime_t* runtime, void* unused_L)
{
return rc_runtime_serialize_progress_sized(buffer, 0xFFFFFFFF, runtime, unused_L);
return rc_runtime_serialize_progress_sized((uint8_t*)buffer, 0xFFFFFFFF, runtime, unused_L);
}
int rc_runtime_serialize_progress_sized(uint8_t* buffer, uint32_t buffer_size, const rc_runtime_t* runtime, void* unused_L)

View File

@ -61,27 +61,41 @@ static void rc_parse_legacy_value(rc_value_t* self, const char** memaddr, rc_par
char buffer[64] = "A:";
const char* buffer_ptr;
char* ptr;
char c;
int done;
/* convert legacy format into condset */
next_clause = &self->conditions;
do {
num_measured_conditions = 0;
/* count the number of joiners and add one to determine the number of clauses. */
done = 0;
num_measured_conditions = 1;
buffer_ptr = *memaddr;
while ((c = *buffer_ptr++) && c != '$') {
if (c == '_') {
++num_measured_conditions;
buffer[0] = 'A'; /* reset to AddSource */
do {
switch (*buffer_ptr++) {
case '_': /* add next */
++num_measured_conditions;
buffer[0] = 'A'; /* reset to AddSource */
break;
case '*': /* multiply */
if (*buffer_ptr == '-') {
/* multiplication by a negative number will convert to SubSource */
++buffer_ptr;
buffer[0] = 'B';
}
break;
case '\0': /* end of string */
case '$': /* maximum of */
case ':': /* end of leaderboard clause */
case ')': /* end of rich presence macro */
done = 1;
break;
default: /* assume everything else is valid - bad stuff will be filtered out later */
break;
}
else if (c == '*' && *buffer_ptr == '-') {
/* multiplication by a negative number will convert to SubSource */
++buffer_ptr;
buffer[0] = 'B';
}
}
} while (!done);
/* if last condition is SubSource, we'll need to add a dummy condition for the Measured */
if (buffer[0] == 'B')

View File

@ -480,6 +480,7 @@ static int rc_hash_file_from_buffer(char hash[33], uint32_t console_id, const rc
rc_hash_iterator_t buffered_file_iterator;
memset(&buffered_file_iterator, 0, sizeof(buffered_file_iterator));
memcpy(&buffered_file_iterator.callbacks, &iterator->callbacks, sizeof(iterator->callbacks));
buffered_file_iterator.userdata = iterator->userdata;
buffered_file_iterator.callbacks.filereader.open = rc_file_open_buffered_file;
buffered_file_iterator.callbacks.filereader.close = rc_file_close_buffered_file;
@ -650,6 +651,7 @@ int rc_hash_buffered_file(char hash[33], uint32_t console_id, const rc_hash_iter
rc_hash_iterator_t buffer_iterator;
memset(&buffer_iterator, 0, sizeof(buffer_iterator));
memcpy(&buffer_iterator.callbacks, &iterator->callbacks, sizeof(iterator->callbacks));
buffer_iterator.userdata = iterator->userdata;
buffer_iterator.path = iterator->path;
buffer_iterator.buffer = buffer;
buffer_iterator.buffer_size = (size_t)size;
@ -773,6 +775,7 @@ static int rc_hash_generate_from_playlist(char hash[33], uint32_t console_id, co
memset(&first_file_iterator, 0, sizeof(first_file_iterator));
memcpy(&first_file_iterator.callbacks, &iterator->callbacks, sizeof(iterator->callbacks));
first_file_iterator.userdata = iterator->userdata;
first_file_iterator.path = disc_path; /* rc_hash_destory_iterator will free */
result = rc_hash_from_file(hash, console_id, &first_file_iterator);
@ -1293,7 +1296,8 @@ static void rc_hash_initialize_iterator_from_path(rc_hash_iterator_t* iterator,
}
/* find the handler for the extension */
handler = bsearch(&search, handlers, num_handlers, sizeof(*handler), rc_hash_iterator_find_handler);
handler = (const rc_hash_iterator_ext_handler_entry_t*)
bsearch(&search, handlers, num_handlers, sizeof(*handler), rc_hash_iterator_find_handler);
if (handler) {
handler->handler(iterator, handler->data);
} else {