From 1bfc4b6e6c9025b521a0a3975b964c82d4da1423 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sat, 25 Jan 2025 15:17:11 +1000 Subject: [PATCH] dep/rcheevos: Update to 08999e0 --- dep/rcheevos/include/rc_api_info.h | 36 ++++ dep/rcheevos/include/rc_api_user.h | 43 ++++ dep/rcheevos/include/rc_client.h | 82 ++++++++ dep/rcheevos/src/rapi/rc_api_info.c | 85 ++++++++ dep/rcheevos/src/rapi/rc_api_user.c | 100 +++++++++ dep/rcheevos/src/rc_client.c | 266 ++++++++++++++++++++++++ dep/rcheevos/src/rcheevos/operand.c | 21 +- dep/rcheevos/src/rcheevos/rc_internal.h | 1 + 8 files changed, 632 insertions(+), 2 deletions(-) diff --git a/dep/rcheevos/include/rc_api_info.h b/dep/rcheevos/include/rc_api_info.h index 7d6cfa2be..f52267469 100644 --- a/dep/rcheevos/include/rc_api_info.h +++ b/dep/rcheevos/include/rc_api_info.h @@ -225,6 +225,42 @@ RC_EXPORT int RC_CCONV rc_api_init_fetch_game_titles_request(rc_api_request_t* r RC_EXPORT int RC_CCONV rc_api_process_fetch_game_titles_server_response(rc_api_fetch_game_titles_response_t* response, const rc_api_server_response_t* server_response); RC_EXPORT void RC_CCONV rc_api_destroy_fetch_game_titles_response(rc_api_fetch_game_titles_response_t* response); +/* --- Fetch Game Hashes --- */ + +/** + * API parameters for a fetch games list request. + */ +typedef struct rc_api_fetch_hash_library_request_t { + /* The unique identifier of the console to query */ + uint32_t console_id; +} rc_api_fetch_hash_library_request_t; + +/* A hash library entry */ +typedef struct rc_api_hash_library_entry_t { + /* The hash for the game */ + const char* hash; + /* The unique identifier of the game */ + uint32_t game_id; +} rc_api_hash_library_entry_t; + +/** + * Response data for a fetch hash library request. + */ +typedef struct rc_api_fetch_hash_library_response_t { + /* An array of entries, one per game */ + rc_api_hash_library_entry_t* entries; + /* The number of items in the entries array */ + uint32_t num_entries; + + /* Common server-provided response information */ + rc_api_response_t response; +} +rc_api_fetch_hash_library_response_t; + +RC_EXPORT int RC_CCONV rc_api_init_fetch_hash_library_request(rc_api_request_t* request, const rc_api_fetch_hash_library_request_t* api_params); +RC_EXPORT int RC_CCONV rc_api_process_fetch_hash_library_server_response(rc_api_fetch_hash_library_response_t* response, const rc_api_server_response_t* server_response); +RC_EXPORT void RC_CCONV rc_api_destroy_fetch_hash_library_response(rc_api_fetch_hash_library_response_t* response); + RC_END_C_DECLS #endif /* RC_API_INFO_H */ diff --git a/dep/rcheevos/include/rc_api_user.h b/dep/rcheevos/include/rc_api_user.h index f8e4dedde..005c73ecf 100644 --- a/dep/rcheevos/include/rc_api_user.h +++ b/dep/rcheevos/include/rc_api_user.h @@ -147,6 +147,49 @@ RC_EXPORT int RC_CCONV rc_api_process_fetch_user_unlocks_response(rc_api_fetch_u RC_EXPORT int RC_CCONV rc_api_process_fetch_user_unlocks_server_response(rc_api_fetch_user_unlocks_response_t* response, const rc_api_server_response_t* server_response); RC_EXPORT void RC_CCONV rc_api_destroy_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response); +/* --- Fetch All Progress --- */ + +/** + * API parameters for a fetch all progress request. + */ +typedef struct rc_api_fetch_all_progress_request_t { + /* The username of the player */ + const char* username; + /* The API token from the login request */ + const char* api_token; + /* The unique identifier of the console to query */ + uint32_t console_id; +} rc_api_fetch_all_progress_request_t; + +/* An all-progress entry */ +typedef struct rc_api_all_progress_entry_t { + /* The unique identifier of the game */ + uint32_t game_id; + /* The total number of achievements for this game */ + uint32_t num_achievements; + /* The total number of unlocked achievements for this game in softcore mode */ + uint32_t num_unlocked_achievements; + /* The total number of unlocked achievements for this game in hardcore mode */ + uint32_t num_unlocked_achievements_hardcore; +} rc_api_all_progress_entry_t; + +/** + * Response data for a fetch all progress request. + */ +typedef struct rc_api_fetch_all_progress_response_t { + /* An array of entries, one per game */ + rc_api_all_progress_entry_t* entries; + /* The number of items in the entries array */ + uint32_t num_entries; + + /* Common server-provided response information */ + rc_api_response_t response; +} rc_api_fetch_all_progress_response_t; + +RC_EXPORT int RC_CCONV rc_api_init_fetch_all_progress_request(rc_api_request_t* request, const rc_api_fetch_all_progress_request_t* api_params); +RC_EXPORT int RC_CCONV rc_api_process_fetch_all_progress_server_response(rc_api_fetch_all_progress_response_t* response, const rc_api_server_response_t* server_response); +RC_EXPORT void RC_CCONV rc_api_destroy_fetch_all_progress_response(rc_api_fetch_all_progress_response_t* response); + RC_END_C_DECLS #endif /* RC_API_H */ diff --git a/dep/rcheevos/include/rc_client.h b/dep/rcheevos/include/rc_client.h index dcbd35430..2f03528c3 100644 --- a/dep/rcheevos/include/rc_client.h +++ b/dep/rcheevos/include/rc_client.h @@ -310,6 +310,88 @@ 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); +/*****************************************************************************\ +| Game Info | +\*****************************************************************************/ + +typedef struct rc_client_hash_library_entry_t +{ + /* The hash for the game */ + const char* hash; + /* The unique identifier of the game */ + uint32_t game_id; +} rc_client_hash_library_entry_t; + +typedef struct rc_client_hash_library_t +{ + /* An array of entries, one per game */ + rc_client_hash_library_entry_t* entries; + /* The number of items in the entries array */ + uint32_t num_entries; +} rc_client_hash_library_t; + +/** + * Callback that is fired when a hash library request completes. list may be null if the query failed. + */ +typedef void(RC_CCONV* rc_client_fetch_hash_library_callback_t)(int result, const char* error_message, + rc_client_hash_library_t* list, rc_client_t* client, + void* callback_userdata); + +/** + * Starts an asynchronous request for all hashes for the given console. + * This request returns a mapping from hashes to the game's unique identifier. A single game may have multiple + * hashes in the case of multi-disc games, or variants that are still compatible with the same achievement set. + */ +RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_fetch_hash_library( + rc_client_t* client, uint32_t console_id, rc_client_fetch_hash_library_callback_t callback, void* callback_userdata); + + +/** + * Destroys a previously-allocated result from the rc_client_destroy_hash_library() callback. + */ +RC_EXPORT void RC_CCONV rc_client_destroy_hash_library(rc_client_hash_library_t* list); + +typedef struct rc_client_all_progress_list_entry_t +{ + /* The unique identifier of the game */ + uint32_t game_id; + /* The total number of achievements for this game */ + uint32_t num_achievements; + /* The total number of unlocked achievements for this game in softcore mode */ + uint32_t num_unlocked_achievements; + /* The total number of unlocked achievements for this game in hardcore mode */ + uint32_t num_unlocked_achievements_hardcore; +} rc_client_all_progress_list_entry_t; + +typedef struct rc_client_all_progress_list_t +{ + /* An array of entries, one per game */ + rc_client_all_progress_list_entry_t* entries; + /* The number of items in the entries array */ + uint32_t num_entries; +} rc_client_all_progress_list_t; + +/** + * Callback that is fired when an all progress query completes. list may be null if the query failed. + */ +typedef void(RC_CCONV* rc_client_fetch_all_progress_list_callback_t)(int result, const char* error_message, + rc_client_all_progress_list_t* list, + rc_client_t* client, void* callback_userdata); + +/** + * Starts an asynchronous request for all progress for the given console. + * This query returns the total number of achievements for all games tracked by this console, as well as + * the user's achievement unlock count for both softcore and hardcore modes. + */ +RC_EXPORT rc_client_async_handle_t* RC_CCONV +rc_client_begin_fetch_all_progress_list(rc_client_t* client, uint32_t console_id, + rc_client_fetch_all_progress_list_callback_t callback, void* callback_userdata); + +/** + * Destroys a previously-allocated result from the rc_client_begin_fetch_all_progress_list() callback. + */ +RC_EXPORT void RC_CCONV rc_client_destroy_all_progress_list(rc_client_all_progress_list_t* list); + /*****************************************************************************\ | Achievements | \*****************************************************************************/ diff --git a/dep/rcheevos/src/rapi/rc_api_info.c b/dep/rcheevos/src/rapi/rc_api_info.c index 0339f256d..615169f8d 100644 --- a/dep/rcheevos/src/rapi/rc_api_info.c +++ b/dep/rcheevos/src/rapi/rc_api_info.c @@ -1,6 +1,7 @@ #include "rc_api_info.h" #include "rc_api_common.h" +#include "rc_consoles.h" #include "rc_runtime_types.h" #include "../rc_compat.h" @@ -464,3 +465,87 @@ int rc_api_process_fetch_game_titles_server_response(rc_api_fetch_game_titles_re void rc_api_destroy_fetch_game_titles_response(rc_api_fetch_game_titles_response_t* response) { rc_buffer_destroy(&response->response.buffer); } + +/* --- Fetch Game Hashes --- */ + +int rc_api_init_fetch_hash_library_request(rc_api_request_t* request, + const rc_api_fetch_hash_library_request_t* api_params) +{ + rc_api_url_builder_t builder; + rc_api_url_build_dorequest_url(request); + + if (api_params->console_id == RC_CONSOLE_UNKNOWN) + return RC_INVALID_STATE; + + rc_url_builder_init(&builder, &request->buffer, 48); + rc_url_builder_append_str_param(&builder, "r", "hashlibrary"); + rc_url_builder_append_unum_param(&builder, "c", api_params->console_id); + + request->post_data = rc_url_builder_finalize(&builder); + request->content_type = RC_CONTENT_TYPE_URLENCODED; + + return builder.result; +} + +int rc_api_process_fetch_hash_library_server_response(rc_api_fetch_hash_library_response_t* response, + const rc_api_server_response_t* server_response) +{ + rc_api_hash_library_entry_t* entry; + rc_json_iterator_t iterator; + rc_json_field_t field; + int result; + char* end; + + rc_json_field_t fields[] = { + RC_JSON_NEW_FIELD("Success"), + RC_JSON_NEW_FIELD("Error"), + RC_JSON_NEW_FIELD("MD5List"), + }; + + 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) + return result; + + if (!fields[2].value_start) + { + /* call rc_json_get_required_object to generate the error message */ + rc_json_get_required_object(NULL, 0, &response->response, &fields[2], "MD5List"); + return RC_MISSING_VALUE; + } + + response->num_entries = fields[2].array_size; + rc_buffer_reserve(&response->response.buffer, response->num_entries * (32 + sizeof(rc_api_hash_library_entry_t))); + + response->entries = (rc_api_hash_library_entry_t*)rc_buffer_alloc( + &response->response.buffer, response->num_entries * sizeof(rc_api_hash_library_entry_t)); + if (!response->entries) + return RC_OUT_OF_MEMORY; + + memset(&iterator, 0, sizeof(iterator)); + iterator.json = fields[2].value_start; + iterator.end = fields[2].value_end; + + entry = response->entries; + while (rc_json_get_next_object_field(&iterator, &field)) + { + /* TODO: This isn't handling escape characters in the key, the RC JSON parsing functions have no method for it. */ + entry->hash = rc_buffer_strncpy(&response->response.buffer, field.name, field.name_len); + + field.name = ""; + if (!rc_json_get_unum(&entry->game_id, &field, "")) + return RC_MISSING_VALUE; + + ++entry; + } + + return RC_OK; +} + +void rc_api_destroy_fetch_hash_library_response(rc_api_fetch_hash_library_response_t* response) +{ + rc_buffer_destroy(&response->response.buffer); +} diff --git a/dep/rcheevos/src/rapi/rc_api_user.c b/dep/rcheevos/src/rapi/rc_api_user.c index 0349f4a58..fa3352374 100644 --- a/dep/rcheevos/src/rapi/rc_api_user.c +++ b/dep/rcheevos/src/rapi/rc_api_user.c @@ -1,8 +1,10 @@ #include "rc_api_user.h" #include "rc_api_common.h" +#include "rc_consoles.h" #include "../rc_version.h" +#include #include /* --- Login --- */ @@ -252,3 +254,101 @@ int rc_api_process_fetch_user_unlocks_server_response(rc_api_fetch_user_unlocks_ void rc_api_destroy_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response) { rc_buffer_destroy(&response->response.buffer); } + +/* --- Fetch All Progress --- */ + +int rc_api_init_fetch_all_progress_request(rc_api_request_t* request, + const rc_api_fetch_all_progress_request_t* api_params) +{ + rc_api_url_builder_t builder; + + rc_api_url_build_dorequest_url(request); + + if (api_params->console_id == RC_CONSOLE_UNKNOWN) + return RC_INVALID_STATE; + + rc_url_builder_init(&builder, &request->buffer, 48); + if (rc_api_url_build_dorequest(&builder, "allprogress", api_params->username, api_params->api_token)) + { + rc_url_builder_append_unum_param(&builder, "c", api_params->console_id); + request->post_data = rc_url_builder_finalize(&builder); + request->content_type = RC_CONTENT_TYPE_URLENCODED; + } + + return builder.result; +} + +int rc_api_process_fetch_all_progress_server_response(rc_api_fetch_all_progress_response_t* response, + const rc_api_server_response_t* server_response) +{ + rc_api_all_progress_entry_t* entry; + rc_json_iterator_t iterator; + rc_json_field_t field; + int result; + char* end; + + rc_json_field_t fields[] = { + RC_JSON_NEW_FIELD("Success"), + RC_JSON_NEW_FIELD("Error"), + RC_JSON_NEW_FIELD("Response"), + }; + + rc_json_field_t entry_fields[] = { + RC_JSON_NEW_FIELD("Achievements"), + RC_JSON_NEW_FIELD("Unlocked"), + RC_JSON_NEW_FIELD("UnlockedHardcore"), + }; + + 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) + return result; + + if (!fields[2].value_start) + { + /* call rc_json_get_required_object to generate the error message */ + rc_json_get_required_object(NULL, 0, &response->response, &fields[2], "Response"); + return RC_MISSING_VALUE; + } + + response->num_entries = fields[2].array_size; + rc_buffer_reserve(&response->response.buffer, response->num_entries * sizeof(rc_api_all_progress_entry_t)); + + response->entries = (rc_api_all_progress_entry_t*)rc_buffer_alloc( + &response->response.buffer, response->num_entries * sizeof(rc_api_all_progress_entry_t)); + if (!response->entries) + return RC_OUT_OF_MEMORY; + + memset(&iterator, 0, sizeof(iterator)); + iterator.json = fields[2].value_start; + iterator.end = fields[2].value_end; + + entry = response->entries; + while (rc_json_get_next_object_field(&iterator, &field)) + { + entry->game_id = strtol(field.name, &end, 10); + + field.name = ""; + if (!rc_json_get_required_object(entry_fields, sizeof(entry_fields) / sizeof(entry_fields[0]), response, &field, + "")) + { + return RC_MISSING_VALUE; + } + + rc_json_get_optional_unum(&entry->num_achievements, &entry_fields[0], "Achievements", 0); + rc_json_get_optional_unum(&entry->num_unlocked_achievements, &entry_fields[1], "Unlocked", 0); + rc_json_get_optional_unum(&entry->num_unlocked_achievements_hardcore, &entry_fields[2], "UnlockedHardcore", 0); + + ++entry; + } + + return RC_OK; +} + +void rc_api_destroy_fetch_all_progress_response(rc_api_fetch_all_progress_response_t* response) +{ + rc_buffer_destroy(&response->response.buffer); +} diff --git a/dep/rcheevos/src/rc_client.c b/dep/rcheevos/src/rc_client.c index 0df41fdf9..a01aa696e 100644 --- a/dep/rcheevos/src/rc_client.c +++ b/dep/rcheevos/src/rc_client.c @@ -3209,6 +3209,272 @@ const rc_client_subset_t* rc_client_get_subset_info(rc_client_t* client, uint32_ return NULL; } +/* ===== Game Info ===== */ + +typedef struct rc_client_fetch_hash_library_callback_data_t +{ + rc_client_t* client; + rc_client_fetch_hash_library_callback_t callback; + void* callback_userdata; + uint32_t console_id; + rc_client_async_handle_t async_handle; +} rc_client_fetch_hash_library_callback_data_t; + +static void rc_client_fetch_hash_library_callback(const rc_api_server_response_t* server_response, void* callback_data) +{ + rc_client_fetch_hash_library_callback_data_t* hashlib_callback_data = + (rc_client_fetch_hash_library_callback_data_t*)callback_data; + rc_client_t* client = hashlib_callback_data->client; + rc_api_fetch_hash_library_response_t hashlib_response; + const char* error_message; + int result; + + result = rc_client_end_async(client, &hashlib_callback_data->async_handle); + if (result) + { + if (result != RC_CLIENT_ASYNC_DESTROYED) + RC_CLIENT_LOG_VERBOSE(client, "Fetch hash library aborted"); + + free(hashlib_callback_data); + return; + } + + result = rc_api_process_fetch_hash_library_server_response(&hashlib_response, server_response); + error_message = + rc_client_server_error_message(&result, server_response->http_status_code, &hashlib_response.response); + if (error_message) + { + RC_CLIENT_LOG_ERR_FORMATTED(client, "Fetch hash library for console %u failed: %s", + hashlib_callback_data->console_id, error_message); + hashlib_callback_data->callback(result, error_message, NULL, client, hashlib_callback_data->callback_userdata); + } + else + { + rc_client_hash_library_t* list; + const size_t list_size = sizeof(*list) + sizeof(rc_client_leaderboard_entry_t) * hashlib_response.num_entries; + size_t needed_size = list_size; + uint32_t i; + + for (i = 0; i < hashlib_response.num_entries; i++) + needed_size += strlen(hashlib_response.entries[i].hash) + 1; + + list = (rc_client_hash_library_t*)malloc(needed_size); + if (!list) + { + hashlib_callback_data->callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), NULL, client, + hashlib_callback_data->callback_userdata); + } + else + { + rc_client_hash_library_entry_t* entry = list->entries = + (rc_client_hash_library_entry_t*)((uint8_t*)list + sizeof(*list)); + char* hash = (char*)((uint8_t*)list + list_size); + const rc_api_hash_library_entry_t* hlentry = hashlib_response.entries; + const rc_api_hash_library_entry_t* stop = hlentry + hashlib_response.num_entries; + + for (; hlentry < stop; ++hlentry, ++entry) + { + const size_t len = strlen(hlentry->hash) + 1; + entry->hash = hash; + entry->game_id = hlentry->game_id; + memcpy(hash, hlentry->hash, len); + hash += len; + } + + list->num_entries = hashlib_response.num_entries; + + hashlib_callback_data->callback(RC_OK, NULL, list, client, hashlib_callback_data->callback_userdata); + } + } + + rc_api_destroy_fetch_hash_library_response(&hashlib_response); + free(hashlib_callback_data); +} + +rc_client_async_handle_t* rc_client_begin_fetch_hash_library(rc_client_t* client, uint32_t console_id, + rc_client_fetch_hash_library_callback_t callback, + void* callback_userdata) +{ + rc_api_fetch_hash_library_request_t api_params; + rc_client_fetch_hash_library_callback_data_t* callback_data; + rc_client_async_handle_t* async_handle; + rc_api_request_t request; + int result; + const char* error_message; + + if (!client) + { + callback(RC_INVALID_STATE, "client is required", NULL, client, callback_userdata); + return NULL; + } + + api_params.console_id = console_id; + result = rc_api_init_fetch_hash_library_request(&request, &api_params); + + if (result != RC_OK) + { + error_message = rc_error_str(result); + callback(result, error_message, NULL, client, callback_userdata); + return NULL; + } + + callback_data = (rc_client_fetch_hash_library_callback_data_t*)calloc(1, sizeof(*callback_data)); + if (!callback_data) + { + callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), NULL, client, callback_userdata); + return NULL; + } + + callback_data->client = client; + callback_data->callback = callback; + callback_data->callback_userdata = callback_userdata; + callback_data->console_id = console_id; + + async_handle = &callback_data->async_handle; + rc_client_begin_async(client, async_handle); + client->callbacks.server_call(&request, rc_client_fetch_hash_library_callback, callback_data, client); + rc_api_destroy_request(&request); + + return rc_client_async_handle_valid(client, async_handle) ? async_handle : NULL; +} + +void rc_client_destroy_hash_library(rc_client_hash_library_t* list) +{ + free(list); +} + +typedef struct rc_client_fetch_all_progress_callback_data_t +{ + rc_client_t* client; + rc_client_fetch_all_progress_list_callback_t callback; + void* callback_userdata; + uint32_t console_id; + rc_client_async_handle_t async_handle; +} rc_client_fetch_all_progress_callback_data_t; + +static void rc_client_fetch_all_progress_callback(const rc_api_server_response_t* server_response, void* callback_data) +{ + rc_client_fetch_all_progress_callback_data_t* ap_callback_data = + (rc_client_fetch_all_progress_callback_data_t*)callback_data; + rc_client_t* client = ap_callback_data->client; + rc_api_fetch_all_progress_response_t ap_response; + const char* error_message; + int result; + + result = rc_client_end_async(client, &ap_callback_data->async_handle); + if (result) + { + if (result != RC_CLIENT_ASYNC_DESTROYED) + RC_CLIENT_LOG_VERBOSE(client, "Fetch all progress aborted"); + + free(ap_callback_data); + return; + } + + result = rc_api_process_fetch_all_progress_server_response(&ap_response, server_response); + error_message = rc_client_server_error_message(&result, server_response->http_status_code, &ap_response.response); + if (error_message) + { + RC_CLIENT_LOG_ERR_FORMATTED(client, "Fetch all progress for console %u failed: %s", ap_callback_data->console_id, + error_message); + ap_callback_data->callback(result, error_message, NULL, client, ap_callback_data->callback_userdata); + } + else + { + rc_client_all_progress_list_t* list; + const size_t list_size = sizeof(*list) + sizeof(rc_client_all_progress_list_entry_t) * ap_response.num_entries; + + list = (rc_client_all_progress_list_t*)malloc(list_size); + if (!list) + { + ap_callback_data->callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), NULL, client, + ap_callback_data->callback_userdata); + } + else + { + rc_client_all_progress_list_entry_t* entry = list->entries = + (rc_client_all_progress_list_entry_t*)((uint8_t*)list + sizeof(*list)); + const rc_api_all_progress_entry_t* hlentry = ap_response.entries; + const rc_api_all_progress_entry_t* stop = hlentry + ap_response.num_entries; + + for (; hlentry < stop; ++hlentry, ++entry) + { + entry->game_id = hlentry->game_id; + entry->num_achievements = hlentry->num_achievements; + entry->num_unlocked_achievements = hlentry->num_unlocked_achievements; + entry->num_unlocked_achievements_hardcore = hlentry->num_unlocked_achievements_hardcore; + } + + list->num_entries = ap_response.num_entries; + + ap_callback_data->callback(RC_OK, NULL, list, client, ap_callback_data->callback_userdata); + } + } + + rc_api_destroy_fetch_all_progress_response(&ap_response); + free(ap_callback_data); +} + +rc_client_async_handle_t* rc_client_begin_fetch_all_progress_list(rc_client_t* client, uint32_t console_id, + rc_client_fetch_all_progress_list_callback_t callback, + void* callback_userdata) +{ + rc_api_fetch_all_progress_request_t api_params; + rc_client_fetch_all_progress_callback_data_t* callback_data; + rc_client_async_handle_t* async_handle; + rc_api_request_t request; + int result; + const char* error_message; + + if (!client) + { + callback(RC_INVALID_STATE, "client is required", NULL, client, callback_userdata); + return NULL; + } + else if (client->state.user != RC_CLIENT_USER_STATE_LOGGED_IN) + { + callback(RC_INVALID_STATE, "client must be logged in", NULL, client, callback_userdata); + return NULL; + } + + api_params.username = client->user.username; + api_params.api_token = client->user.token; + api_params.console_id = console_id; + + result = rc_api_init_fetch_all_progress_request(&request, &api_params); + + if (result != RC_OK) + { + error_message = rc_error_str(result); + callback(result, error_message, NULL, client, callback_userdata); + return NULL; + } + + callback_data = (rc_client_fetch_all_progress_callback_data_t*)calloc(1, sizeof(*callback_data)); + if (!callback_data) + { + callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), NULL, client, callback_userdata); + return NULL; + } + + callback_data->client = client; + callback_data->callback = callback; + callback_data->callback_userdata = callback_userdata; + callback_data->console_id = console_id; + + async_handle = &callback_data->async_handle; + rc_client_begin_async(client, async_handle); + client->callbacks.server_call(&request, rc_client_fetch_all_progress_callback, callback_data, client); + rc_api_destroy_request(&request); + + return rc_client_async_handle_valid(client, async_handle) ? async_handle : NULL; +} + +void rc_client_destroy_all_progress_list(rc_client_all_progress_list_t* list) +{ + free(list); +} + /* ===== Achievements ===== */ static void rc_client_update_achievement_display_information(rc_client_t* client, rc_client_achievement_info_t* achievement, time_t recent_unlock_time) diff --git a/dep/rcheevos/src/rcheevos/operand.c b/dep/rcheevos/src/rcheevos/operand.c index 4325df07c..437b043d2 100644 --- a/dep/rcheevos/src/rcheevos/operand.c +++ b/dep/rcheevos/src/rcheevos/operand.c @@ -465,6 +465,17 @@ int rc_operand_type_is_memref(uint8_t type) { } } +int rc_operand_type_is_transform(uint8_t type) { + switch (type) { + case RC_OPERAND_BCD: + case RC_OPERAND_INVERTED: + return 1; + + default: + return 0; + } +} + int rc_operand_is_memref(const rc_operand_t* self) { return rc_operand_type_is_memref(self->type); } @@ -603,9 +614,15 @@ void rc_operand_addsource(rc_operand_t* self, rc_parse_state_t* parse, uint8_t n self->value.memref = (rc_memref_t*)modified_memref; - /* if adding a constant, change the type to be address (current value) */ - if (!rc_operand_is_memref(self)) + if (!rc_operand_is_memref(self)) { + /* if adding a constant, change the type to be address (current value) */ self->type = self->memref_access_type = RC_OPERAND_ADDRESS; + } + else if (rc_operand_type_is_transform(self->type)) { + /* transform is applied in the modified_memref. change the type to be + * address (current value) to avoid applying the transform again */ + self->type = self->memref_access_type = RC_OPERAND_ADDRESS; + } /* result of an AddSource operation is always a 32-bit integer (even if parent or modifier is a float) */ self->size = RC_MEMSIZE_32_BITS; diff --git a/dep/rcheevos/src/rcheevos/rc_internal.h b/dep/rcheevos/src/rcheevos/rc_internal.h index d412be831..ff59cf49d 100644 --- a/dep/rcheevos/src/rcheevos/rc_internal.h +++ b/dep/rcheevos/src/rcheevos/rc_internal.h @@ -352,6 +352,7 @@ int rc_operand_is_float_memref(const rc_operand_t* self); int rc_operand_is_float(const rc_operand_t* self); int rc_operand_is_recall(const rc_operand_t* self); int rc_operand_type_is_memref(uint8_t type); +int rc_operand_type_is_transform(uint8_t type); int rc_operands_are_equal(const rc_operand_t* left, const rc_operand_t* right); void rc_operand_addsource(rc_operand_t* self, rc_parse_state_t* parse, uint8_t new_size); void rc_operand_set_const(rc_operand_t* self, uint32_t value);