diff --git a/Makefile.common b/Makefile.common index 3277ff0a3e..ba4135de05 100644 --- a/Makefile.common +++ b/Makefile.common @@ -2115,7 +2115,7 @@ ifeq ($(HAVE_NETWORKING), 1) # RetroAchievements ifeq ($(HAVE_CHEEVOS), 1) - DEFINES += -DHAVE_CHEEVOS + DEFINES += -DHAVE_CHEEVOS -DRC_CLIENT_SUPPORTS_HASH INCLUDE_DIRS += -Ideps/rcheevos/include ifneq ($(HAVE_THREADS), 1) diff --git a/cheevos/cheevos.c b/cheevos/cheevos.c index 6261ed7ea5..f55175fd1d 100644 --- a/cheevos/cheevos.c +++ b/cheevos/cheevos.c @@ -107,7 +107,7 @@ static rcheevos_locals_t rcheevos_locals = 0, /* menuitem_count */ #endif #ifdef HAVE_RC_CLIENT - true,/* hardcore_allowed */ + true, /* hardcore_allowed */ #else #ifdef HAVE_GFX_WIDGETS 0, /* active_lboard_trackers */ @@ -1221,6 +1221,7 @@ bool rcheevos_unload(void) #ifdef HAVE_GFX_WIDGETS rcheevos_hide_widgets(gfx_widgets_ready()); + gfx_widget_set_cheevos_set_loading(false); #endif #ifdef HAVE_RC_CLIENT @@ -2441,6 +2442,10 @@ static void rcheevos_client_load_game_callback(int result, const rc_client_game_t* game = rc_client_get_game_info(client); char msg[256]; +#if defined(HAVE_GFX_WIDGETS) + gfx_widget_set_cheevos_set_loading(false); +#endif + if (result != RC_OK || !game) { if (result == RC_NO_GAME_LOADED) @@ -3257,6 +3262,11 @@ bool rcheevos_load(const void *data) /* provide hooks for reading files */ rc_hash_reset_cdreader_hooks(); +#if defined(HAVE_GFX_WIDGETS) + if (settings->bools.cheevos_verbose_enable) + gfx_widget_set_cheevos_set_loading(true); +#endif + rc_client_begin_identify_and_load_game(rcheevos_locals.client, RC_CONSOLE_UNKNOWN, info->path, info->data, info->size, rcheevos_client_load_game_callback, NULL); diff --git a/cheevos/cheevos_client.c b/cheevos/cheevos_client.c index 62e9fd01b2..1a4a34f5e1 100644 --- a/cheevos/cheevos_client.c +++ b/cheevos/cheevos_client.c @@ -338,6 +338,12 @@ static void rcheevos_client_http_task_callback(retro_task_t* task, if (!http_data) { + CHEEVOS_LOG(RCHEEVOS_TAG "http_task returned null"); + callback_data->callback(&server_response, callback_data->callback_data); + } + else if (http_data->status < 0) + { + CHEEVOS_LOG(RCHEEVOS_TAG "http_task returned %d", http_data->status); callback_data->callback(&server_response, callback_data->callback_data); } else diff --git a/cheevos/cheevos_menu.c b/cheevos/cheevos_menu.c index 9b59c0635b..90e904fffe 100644 --- a/cheevos/cheevos_menu.c +++ b/cheevos/cheevos_menu.c @@ -447,29 +447,71 @@ void rcheevos_menu_populate(void* data) { /* no achievements found */ if (!rcheevos_locals->core_supports) + { menu_entries_append(info->list, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CANNOT_ACTIVATE_ACHIEVEMENTS_WITH_THIS_CORE), msg_hash_to_str(MENU_ENUM_LABEL_CANNOT_ACTIVATE_ACHIEVEMENTS_WITH_THIS_CORE), MENU_ENUM_LABEL_CANNOT_ACTIVATE_ACHIEVEMENTS_WITH_THIS_CORE, FILE_TYPE_NONE, 0, 0, NULL); - else if (!rc_client_get_game_info(rcheevos_locals->client)) + } + else if (!game) + { + int state = rc_client_get_load_game_state(rcheevos_locals->client); + enum msg_hash_enums msg = MENU_ENUM_LABEL_VALUE_UNKNOWN_GAME; + switch (state) + { + case RC_CLIENT_LOAD_GAME_STATE_IDENTIFYING_GAME: + msg = MENU_ENUM_LABEL_VALUE_CHEEVOS_IDENTIFYING_GAME; + break; + case RC_CLIENT_LOAD_GAME_STATE_AWAIT_LOGIN: + msg = MENU_ENUM_LABEL_VALUE_NOT_LOGGED_IN; + break; + case RC_CLIENT_LOAD_GAME_STATE_FETCHING_GAME_DATA: + msg = MENU_ENUM_LABEL_VALUE_CHEEVOS_FETCHING_GAME_DATA; + break; + case RC_CLIENT_LOAD_GAME_STATE_STARTING_SESSION: + msg = MENU_ENUM_LABEL_VALUE_CHEEVOS_STARTING_SESSION; + break; + case RC_CLIENT_LOAD_GAME_STATE_NONE: + if (!rc_client_get_user_info(rcheevos_locals->client)) + msg = MENU_ENUM_LABEL_VALUE_NOT_LOGGED_IN; + break; + } + menu_entries_append(info->list, - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_UNKNOWN_GAME), - msg_hash_to_str(MENU_ENUM_LABEL_UNKNOWN_GAME), - MENU_ENUM_LABEL_UNKNOWN_GAME, + msg_hash_to_str(msg), + msg_hash_to_str(MENU_ENUM_LABEL_NO_ACHIEVEMENTS_TO_DISPLAY), + MENU_ENUM_LABEL_NO_ACHIEVEMENTS_TO_DISPLAY, FILE_TYPE_NONE, 0, 0, NULL); + } + else if (!game->id) + { + char buffer[128]; + snprintf(buffer, sizeof(buffer), "%s (%s)", + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_UNKNOWN_GAME), game->hash); + + menu_entries_append(info->list, + buffer, + msg_hash_to_str(MENU_ENUM_LABEL_NO_ACHIEVEMENTS_TO_DISPLAY), + MENU_ENUM_LABEL_NO_ACHIEVEMENTS_TO_DISPLAY, + FILE_TYPE_NONE, 0, 0, NULL); + } else if (!rc_client_get_user_info(rcheevos_locals->client)) + { menu_entries_append(info->list, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NOT_LOGGED_IN), msg_hash_to_str(MENU_ENUM_LABEL_NOT_LOGGED_IN), MENU_ENUM_LABEL_NOT_LOGGED_IN, FILE_TYPE_NONE, 0, 0, NULL); + } else + { menu_entries_append(info->list, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NO_ACHIEVEMENTS_TO_DISPLAY), msg_hash_to_str(MENU_ENUM_LABEL_NO_ACHIEVEMENTS_TO_DISPLAY), MENU_ENUM_LABEL_NO_ACHIEVEMENTS_TO_DISPLAY, FILE_TYPE_NONE, 0, 0, NULL); + } } } diff --git a/deps/rcheevos/CHANGELOG.md b/deps/rcheevos/CHANGELOG.md index 2649f7c5f5..8f6a1db16c 100644 --- a/deps/rcheevos/CHANGELOG.md +++ b/deps/rcheevos/CHANGELOG.md @@ -1,3 +1,15 @@ +# v11.2.0 +* add alternate methods for state serialization/deserialization that accept a buffer_size parameter +* add RC_CLIENT_SUPPORTS_HASH compile flag + - allows rc_client code to build without the rhash files (except md5.c) + - must be explicitly defined to use rc_client_begin_identify_and_load_game +* add rc_client_get_load_game_state +* add rc_client_raintegration_set_get_game_name_function +* add RC_MEMSIZE_DOUBLE32 and RC_MEMSIZE_DOUBLE32_BE +* exclude directory records from ZIP hash algorithm +* fix media host when explicitly setting host to production server +* fix potential out-of-bounds read looking for error message in non-JSON response + # v11.1.0 * add rc_client_get_user_agent_clause to generate substring to include in client User-Agents * add rc_client_can_pause function to control pause spam diff --git a/deps/rcheevos/include/rc_api_info.h b/deps/rcheevos/include/rc_api_info.h index a9586eeb9a..918bb6bc04 100644 --- a/deps/rcheevos/include/rc_api_info.h +++ b/deps/rcheevos/include/rc_api_info.h @@ -6,9 +6,7 @@ #include #include -#ifdef __cplusplus -extern "C" { -#endif +RC_BEGIN_C_DECLS /* --- Fetch Achievement Info --- */ @@ -63,10 +61,11 @@ typedef struct rc_api_fetch_achievement_info_response_t { } rc_api_fetch_achievement_info_response_t; -int rc_api_init_fetch_achievement_info_request(rc_api_request_t* request, const rc_api_fetch_achievement_info_request_t* api_params); -int rc_api_process_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response, const char* server_response); -int rc_api_process_fetch_achievement_info_server_response(rc_api_fetch_achievement_info_response_t* response, const rc_api_server_response_t* server_response); -void rc_api_destroy_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response); +RC_EXPORT int RC_CCONV rc_api_init_fetch_achievement_info_request(rc_api_request_t* request, const rc_api_fetch_achievement_info_request_t* api_params); +/* [deprecated] use rc_api_process_fetch_achievement_info_server_response instead */ +RC_EXPORT int RC_CCONV rc_api_process_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response, const char* server_response); +RC_EXPORT int RC_CCONV rc_api_process_fetch_achievement_info_server_response(rc_api_fetch_achievement_info_response_t* response, const rc_api_server_response_t* server_response); +RC_EXPORT void RC_CCONV rc_api_destroy_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response); /* --- Fetch Leaderboard Info --- */ @@ -135,10 +134,11 @@ typedef struct rc_api_fetch_leaderboard_info_response_t { } rc_api_fetch_leaderboard_info_response_t; -int rc_api_init_fetch_leaderboard_info_request(rc_api_request_t* request, const rc_api_fetch_leaderboard_info_request_t* api_params); -int rc_api_process_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response, const char* server_response); -int rc_api_process_fetch_leaderboard_info_server_response(rc_api_fetch_leaderboard_info_response_t* response, const rc_api_server_response_t* server_response); -void rc_api_destroy_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response); +RC_EXPORT int RC_CCONV rc_api_init_fetch_leaderboard_info_request(rc_api_request_t* request, const rc_api_fetch_leaderboard_info_request_t* api_params); +/* [deprecated] use rc_api_process_fetch_leaderboard_info_server_response instead */ +RC_EXPORT int RC_CCONV rc_api_process_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response, const char* server_response); +RC_EXPORT int RC_CCONV rc_api_process_fetch_leaderboard_info_server_response(rc_api_fetch_leaderboard_info_response_t* response, const rc_api_server_response_t* server_response); +RC_EXPORT void RC_CCONV rc_api_destroy_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response); /* --- Fetch Games List --- */ @@ -174,13 +174,12 @@ typedef struct rc_api_fetch_games_list_response_t { } rc_api_fetch_games_list_response_t; -int rc_api_init_fetch_games_list_request(rc_api_request_t* request, const rc_api_fetch_games_list_request_t* api_params); -int rc_api_process_fetch_games_list_response(rc_api_fetch_games_list_response_t* response, const char* server_response); -int rc_api_process_fetch_games_list_server_response(rc_api_fetch_games_list_response_t* response, const rc_api_server_response_t* server_response); -void rc_api_destroy_fetch_games_list_response(rc_api_fetch_games_list_response_t* response); +RC_EXPORT int RC_CCONV rc_api_init_fetch_games_list_request(rc_api_request_t* request, const rc_api_fetch_games_list_request_t* api_params); +/* [deprecated] use rc_api_process_fetch_games_list_server_response instead */ +RC_EXPORT int RC_CCONV rc_api_process_fetch_games_list_response(rc_api_fetch_games_list_response_t* response, const char* server_response); +RC_EXPORT int RC_CCONV rc_api_process_fetch_games_list_server_response(rc_api_fetch_games_list_response_t* response, const rc_api_server_response_t* server_response); +RC_EXPORT void RC_CCONV rc_api_destroy_fetch_games_list_response(rc_api_fetch_games_list_response_t* response); -#ifdef __cplusplus -} -#endif +RC_END_C_DECLS #endif /* RC_API_INFO_H */ diff --git a/deps/rcheevos/include/rc_api_user.h b/deps/rcheevos/include/rc_api_user.h index c06cec4450..f8e4dedde2 100644 --- a/deps/rcheevos/include/rc_api_user.h +++ b/deps/rcheevos/include/rc_api_user.h @@ -47,6 +47,7 @@ typedef struct rc_api_login_response_t { rc_api_login_response_t; RC_EXPORT int RC_CCONV rc_api_init_login_request(rc_api_request_t* request, const rc_api_login_request_t* api_params); +/* [deprecated] use rc_api_process_login_server_response instead */ RC_EXPORT int RC_CCONV rc_api_process_login_response(rc_api_login_response_t* response, const char* server_response); RC_EXPORT int RC_CCONV rc_api_process_login_server_response(rc_api_login_response_t* response, const rc_api_server_response_t* server_response); RC_EXPORT void RC_CCONV rc_api_destroy_login_response(rc_api_login_response_t* response); @@ -104,6 +105,7 @@ typedef struct rc_api_start_session_response_t { rc_api_start_session_response_t; RC_EXPORT int RC_CCONV rc_api_init_start_session_request(rc_api_request_t* request, const rc_api_start_session_request_t* api_params); +/* [deprecated] use rc_api_process_start_session_server_response instead */ RC_EXPORT int RC_CCONV rc_api_process_start_session_response(rc_api_start_session_response_t* response, const char* server_response); RC_EXPORT int RC_CCONV rc_api_process_start_session_server_response(rc_api_start_session_response_t* response, const rc_api_server_response_t* server_response); RC_EXPORT void RC_CCONV rc_api_destroy_start_session_response(rc_api_start_session_response_t* response); @@ -140,6 +142,7 @@ typedef struct rc_api_fetch_user_unlocks_response_t { rc_api_fetch_user_unlocks_response_t; RC_EXPORT int RC_CCONV rc_api_init_fetch_user_unlocks_request(rc_api_request_t* request, const rc_api_fetch_user_unlocks_request_t* api_params); +/* [deprecated] use rc_api_process_fetch_user_unlocks_server_response instead */ RC_EXPORT int RC_CCONV rc_api_process_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response, const char* server_response); 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); diff --git a/deps/rcheevos/include/rc_client.h b/deps/rcheevos/include/rc_client.h index 48bef373ee..1f977c189f 100644 --- a/deps/rcheevos/include/rc_client.h +++ b/deps/rcheevos/include/rc_client.h @@ -221,6 +221,7 @@ RC_EXPORT void RC_CCONV rc_client_get_user_game_summary(const rc_client_t* clien | Game | \*****************************************************************************/ +#ifdef RC_CLIENT_SUPPORTS_HASH /** * Start loading an unidentified game. */ @@ -228,6 +229,7 @@ RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_identify_and_load_g uint32_t console_id, const char* file_path, const uint8_t* data, size_t data_size, rc_client_callback_t callback, void* callback_userdata); +#endif /** * Start loading a game. @@ -235,6 +237,20 @@ RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_identify_and_load_g RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_load_game(rc_client_t* client, const char* hash, rc_client_callback_t callback, void* callback_userdata); +/** + * Gets the current progress of the asynchronous load game process. + */ +RC_EXPORT int RC_CCONV rc_client_get_load_game_state(const rc_client_t* client); +enum { + RC_CLIENT_LOAD_GAME_STATE_NONE, + RC_CLIENT_LOAD_GAME_STATE_IDENTIFYING_GAME, + RC_CLIENT_LOAD_GAME_STATE_AWAIT_LOGIN, + RC_CLIENT_LOAD_GAME_STATE_FETCHING_GAME_DATA, + RC_CLIENT_LOAD_GAME_STATE_STARTING_SESSION, + RC_CLIENT_LOAD_GAME_STATE_DONE, + RC_CLIENT_LOAD_GAME_STATE_ABORTED +}; + /** * Unloads the current game. */ @@ -259,11 +275,19 @@ RC_EXPORT const rc_client_game_t* RC_CCONV rc_client_get_game_info(const rc_clie */ RC_EXPORT int RC_CCONV rc_client_game_get_image_url(const rc_client_game_t* game, char buffer[], size_t buffer_size); +#ifdef RC_CLIENT_SUPPORTS_HASH /** * Changes the active disc in a multi-disc game. */ RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_change_media(rc_client_t* client, const char* file_path, const uint8_t* data, size_t data_size, rc_client_callback_t callback, void* callback_userdata); +#endif + +/** + * Changes the active disc in a multi-disc game. + */ +RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_change_media_from_hash(rc_client_t* client, const char* hash, + rc_client_callback_t callback, void* callback_userdata); /*****************************************************************************\ | Subsets | @@ -663,15 +687,29 @@ RC_EXPORT size_t RC_CCONV rc_client_progress_size(rc_client_t* client); /** * Serializes the runtime state into a buffer. * Returns RC_OK on success, or an error indicator. + * [deprecated] use rc_client_serialize_progress_sized instead */ RC_EXPORT int RC_CCONV rc_client_serialize_progress(rc_client_t* client, uint8_t* buffer); /** - * Deserializes the runtime state from a buffer. + * Serializes the runtime state into a buffer. * Returns RC_OK on success, or an error indicator. */ +RC_EXPORT int RC_CCONV rc_client_serialize_progress_sized(rc_client_t* client, uint8_t* buffer, size_t buffer_size); + +/** + * Deserializes the runtime state from a buffer. + * Returns RC_OK on success, or an error indicator. + * [deprecated] use rc_client_deserialize_progress_sized instead + */ RC_EXPORT int RC_CCONV rc_client_deserialize_progress(rc_client_t* client, const uint8_t* serialized); +/** + * Serializes the runtime state into a buffer. + * Returns RC_OK on success, or an error indicator. + */ +RC_EXPORT int RC_CCONV rc_client_deserialize_progress_sized(rc_client_t* client, const uint8_t* serialized, size_t serialized_size); + RC_END_C_DECLS #endif /* RC_RUNTIME_H */ diff --git a/deps/rcheevos/include/rc_client_raintegration.h b/deps/rcheevos/include/rc_client_raintegration.h index b2e77cdec0..676f0fb600 100644 --- a/deps/rcheevos/include/rc_client_raintegration.h +++ b/deps/rcheevos/include/rc_client_raintegration.h @@ -46,6 +46,8 @@ typedef void (RC_CCONV *rc_client_raintegration_event_handler_t)(const rc_client typedef void (RC_CCONV *rc_client_raintegration_write_memory_func_t)(uint32_t address, uint8_t* buffer, uint32_t num_bytes, rc_client_t* client); +typedef void (RC_CCONV* rc_client_raintegration_get_game_name_func_t)(char* buffer, uint32_t buffer_size, rc_client_t* client); + /* types needed to integrate raintegration */ #ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION @@ -71,9 +73,11 @@ RC_EXPORT const rc_client_raintegration_menu_t* RC_CCONV rc_client_raintegration RC_EXPORT void RC_CCONV rc_client_raintegration_rebuild_submenu(rc_client_t* client, HMENU hMenu); RC_EXPORT void RC_CCONV rc_client_raintegration_update_menu_item(const rc_client_t* client, const rc_client_raintegration_menu_item_t* menu_item); -RC_EXPORT int RC_CCONV rc_client_raintegration_activate_menu_item(const rc_client_t* client, uint32_t nMenuItemId); +RC_EXPORT int RC_CCONV rc_client_raintegration_activate_menu_item(const rc_client_t* client, uint32_t menu_item_id); RC_EXPORT void RC_CCONV rc_client_raintegration_set_write_memory_function(rc_client_t* client, rc_client_raintegration_write_memory_func_t handler); +RC_EXPORT void RC_CCONV rc_client_raintegration_set_get_game_name_function(rc_client_t* client, rc_client_raintegration_get_game_name_func_t handler); +RC_EXPORT int RC_CCONV rc_client_raintegration_has_modifications(const rc_client_t* client); RC_EXPORT void RC_CCONV rc_client_raintegration_set_event_handler(rc_client_t* client, rc_client_raintegration_event_handler_t handler); diff --git a/deps/rcheevos/include/rc_error.h b/deps/rcheevos/include/rc_error.h index d0aca2fd70..403f55446f 100644 --- a/deps/rcheevos/include/rc_error.h +++ b/deps/rcheevos/include/rc_error.h @@ -45,7 +45,8 @@ enum { RC_NO_RESPONSE = -32, RC_ACCESS_DENIED = -33, RC_INVALID_CREDENTIALS = -34, - RC_EXPIRED_TOKEN = -35 + RC_EXPIRED_TOKEN = -35, + RC_INSUFFICIENT_BUFFER = -36 }; RC_EXPORT const char* RC_CCONV rc_error_str(int ret); diff --git a/deps/rcheevos/include/rc_runtime.h b/deps/rcheevos/include/rc_runtime.h index c5780c47a2..d778fde5ce 100644 --- a/deps/rcheevos/include/rc_runtime.h +++ b/deps/rcheevos/include/rc_runtime.h @@ -143,9 +143,15 @@ typedef int (RC_CCONV *rc_runtime_validate_address_t)(uint32_t address); RC_EXPORT void RC_CCONV rc_runtime_validate_addresses(rc_runtime_t* runtime, rc_runtime_event_handler_t event_handler, rc_runtime_validate_address_t validate_handler); RC_EXPORT void RC_CCONV rc_runtime_invalidate_address(rc_runtime_t* runtime, uint32_t address); -RC_EXPORT int RC_CCONV rc_runtime_progress_size(const rc_runtime_t* runtime, lua_State* L); +RC_EXPORT uint32_t RC_CCONV rc_runtime_progress_size(const rc_runtime_t* runtime, lua_State* L); + +/* [deprecated] use rc_runtime_serialize_progress_sized instead */ RC_EXPORT int RC_CCONV rc_runtime_serialize_progress(void* buffer, const rc_runtime_t* runtime, lua_State* L); +RC_EXPORT int RC_CCONV rc_runtime_serialize_progress_sized(uint8_t* buffer, uint32_t buffer_size, const rc_runtime_t* runtime, lua_State* L); + +/* [deprecated] use rc_runtime_deserialize_progress_sized instead */ RC_EXPORT int RC_CCONV rc_runtime_deserialize_progress(rc_runtime_t* runtime, const uint8_t* serialized, lua_State* L); +RC_EXPORT int RC_CCONV rc_runtime_deserialize_progress_sized(rc_runtime_t* runtime, const uint8_t* serialized, uint32_t serialized_size, lua_State* L); RC_END_C_DECLS diff --git a/deps/rcheevos/include/rc_runtime_types.h b/deps/rcheevos/include/rc_runtime_types.h index d8a7db65d0..2bda461040 100644 --- a/deps/rcheevos/include/rc_runtime_types.h +++ b/deps/rcheevos/include/rc_runtime_types.h @@ -59,6 +59,8 @@ enum { RC_MEMSIZE_MBF32, RC_MEMSIZE_MBF32_LE, RC_MEMSIZE_FLOAT_BE, + RC_MEMSIZE_DOUBLE32, + RC_MEMSIZE_DOUBLE32_BE, RC_MEMSIZE_VARIABLE }; diff --git a/deps/rcheevos/src/rapi/rc_api_common.c b/deps/rcheevos/src/rapi/rc_api_common.c index 5835b28014..ea29c2383c 100644 --- a/deps/rcheevos/src/rapi/rc_api_common.c +++ b/deps/rcheevos/src/rapi/rc_api_common.c @@ -37,6 +37,24 @@ static void rc_json_skip_whitespace(rc_json_iterator_t* iterator) ++iterator->json; } +static int rc_json_find_substring(rc_json_iterator_t* iterator, const char* substring) +{ + const char first = *substring; + const size_t substring_len = strlen(substring); + const char* end = iterator->end - substring_len; + + while (iterator->json <= end) { + if (*iterator->json == first) { + if (memcmp(iterator->json, substring, substring_len) == 0) + return 1; + } + + ++iterator->json; + } + + return 0; +} + static int rc_json_find_closing_quote(rc_json_iterator_t* iterator) { while (iterator->json < iterator->end) { @@ -237,8 +255,6 @@ int rc_json_get_next_object_field(rc_json_iterator_t* iterator, rc_json_field_t* } int rc_json_get_object_string_length(const char* json) { - const char* json_start = json; - rc_json_iterator_t iterator; memset(&iterator, 0, sizeof(iterator)); iterator.json = json; @@ -246,34 +262,41 @@ int rc_json_get_object_string_length(const char* json) { rc_json_parse_object(&iterator, NULL, 0, NULL); - return (int)(iterator.json - json_start); + if (iterator.json == json) /* not JSON */ + return (int)strlen(json); + + return (int)(iterator.json - json); } static int rc_json_extract_html_error(rc_api_response_t* response, const rc_api_server_response_t* server_response) { - const char* json = server_response->body; - const char* end = json; + rc_json_iterator_t iterator; + memset(&iterator, 0, sizeof(iterator)); + iterator.json = server_response->body; + iterator.end = server_response->body + server_response->body_length; - const char* title_start = strstr(json, ""); - if (title_start) { - title_start += 7; - if (isdigit((int)*title_start)) { - const char* title_end = strstr(title_start + 7, ""); - if (title_end) { - response->error_message = rc_buffer_strncpy(&response->buffer, title_start, title_end - title_start); - response->succeeded = 0; - return RC_INVALID_JSON; - } + /* if the title contains an HTTP status code(i.e "404 Not Found"), return the title */ + if (rc_json_find_substring(&iterator, "")) { + const char* title_start = iterator.json + 7; + if (isdigit((int)*title_start) && rc_json_find_substring(&iterator, "")) { + response->error_message = rc_buffer_strncpy(&response->buffer, title_start, iterator.json - title_start); + response->succeeded = 0; + return RC_INVALID_JSON; } } - while (*end && *end != '\n' && end - json < 200) - ++end; + /* title not found, or did not start with an error code, return the first line of the response */ + iterator.json = server_response->body; - if (end > json && end[-1] == '\r') - --end; + while (iterator.json < iterator.end && *iterator.json != '\n' && + iterator.json - server_response->body < 200) { + ++iterator.json; + } - if (end > json) - response->error_message = rc_buffer_strncpy(&response->buffer, json, end - json); + if (iterator.json > server_response->body && iterator.json[-1] == '\r') + --iterator.json; + + if (iterator.json > server_response->body) + response->error_message = rc_buffer_strncpy(&response->buffer, server_response->body, iterator.json - server_response->body); response->succeeded = 0; return RC_INVALID_JSON; @@ -1150,6 +1173,9 @@ static void rc_api_update_host(char** host, const char* hostname) { } void rc_api_set_host(const char* hostname) { + if (hostname && strcmp(hostname, RETROACHIEVEMENTS_HOST) == 0) + hostname = NULL; + rc_api_update_host(&g_host, hostname); if (!hostname) { diff --git a/deps/rcheevos/src/rc_client.c b/deps/rcheevos/src/rc_client.c index 6790e1768d..8b90e2ed68 100644 --- a/deps/rcheevos/src/rc_client.c +++ b/deps/rcheevos/src/rc_client.c @@ -42,9 +42,12 @@ typedef struct rc_client_generic_callback_data_t { typedef struct rc_client_pending_media_t { +#ifdef RC_CLIENT_SUPPORTS_HASH const char* file_path; uint8_t* data; size_t data_size; +#endif + const char* hash; rc_client_callback_t callback; void* callback_userdata; } rc_client_pending_media_t; @@ -59,7 +62,9 @@ typedef struct rc_client_load_state_t rc_client_subset_info_t* subset; rc_client_game_hash_t* hash; +#ifdef RC_CLIENT_SUPPORTS_HASH rc_hash_iterator_t hash_iterator; +#endif rc_client_pending_media_t* pending_media; rc_api_start_session_response_t *start_session_response; @@ -68,7 +73,9 @@ typedef struct rc_client_load_state_t uint8_t progress; uint8_t outstanding_requests; +#ifdef RC_CLIENT_SUPPORTS_HASH uint8_t hash_console_id; +#endif } rc_client_load_state_t; static void rc_client_begin_fetch_game_data(rc_client_load_state_t* callback_data); @@ -135,6 +142,10 @@ void rc_client_destroy(rc_client_t* client) rc_client_unload_game(client); +#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION + rc_client_unload_raintegration(client); +#endif + #ifdef RC_CLIENT_SUPPORTS_EXTERNAL if (client->state.external_client && client->state.external_client->destroy) client->state.external_client->destroy(); @@ -151,9 +162,11 @@ void rc_client_destroy(rc_client_t* client) static rc_client_t* g_hash_client = NULL; +#ifdef RC_CLIENT_SUPPORTS_HASH static void rc_client_log_hash_message(const char* message) { rc_client_log_message(g_hash_client, message); } +#endif void rc_client_log_message(const rc_client_t* client, const char* message) { @@ -612,7 +625,7 @@ static void rc_client_login_callback(const rc_api_server_response_t* server_resp if (login_callback_data->callback) login_callback_data->callback(result, error_message, client, login_callback_data->callback_userdata); - if (load_state && load_state->progress == RC_CLIENT_LOAD_STATE_AWAIT_LOGIN) + if (load_state && load_state->progress == RC_CLIENT_LOAD_GAME_STATE_AWAIT_LOGIN) rc_client_begin_fetch_game_data(load_state); } else { @@ -635,7 +648,7 @@ static void rc_client_login_callback(const rc_api_server_response_t* server_resp RC_CLIENT_LOG_INFO_FORMATTED(client, "%s logged in successfully", login_response.display_name); - if (load_state && load_state->progress == RC_CLIENT_LOAD_STATE_AWAIT_LOGIN) + if (load_state && load_state->progress == RC_CLIENT_LOAD_GAME_STATE_AWAIT_LOGIN) rc_client_begin_fetch_game_data(load_state); if (login_callback_data->callback) @@ -796,7 +809,7 @@ void rc_client_logout(rc_client_t* client) rc_client_unload_game(client); - if (load_state && load_state->progress == RC_CLIENT_LOAD_STATE_AWAIT_LOGIN) + if (load_state && load_state->progress == RC_CLIENT_LOAD_GAME_STATE_AWAIT_LOGIN) rc_client_load_error(load_state, RC_ABORTED, "Login aborted"); } @@ -937,9 +950,9 @@ static int rc_client_end_load_state(rc_client_load_state_t* load_state) * the outstanding_requests count will reach zero and the memory will be free'd then. */ if (remaining_requests == 0) { /* if one of the callbacks called rc_client_load_error, progress will be set to - * RC_CLIENT_LOAD_STATE_UNKNOWN. There's no need to call the callback with RC_ABORTED + * RC_CLIENT_LOAD_STATE_ABORTED. There's no need to call the callback with RC_ABORTED * in that case, as it will have already been called with something more appropriate. */ - if (load_state->progress != RC_CLIENT_LOAD_STATE_UNKNOWN_GAME && load_state->callback) + if (load_state->progress != RC_CLIENT_LOAD_GAME_STATE_ABORTED && load_state->callback) load_state->callback(RC_ABORTED, "The requested game is no longer active", load_state->client, load_state->callback_userdata); rc_client_free_load_state(load_state); @@ -957,7 +970,7 @@ static void rc_client_load_error(rc_client_load_state_t* load_state, int result, rc_mutex_lock(&load_state->client->state.mutex); - load_state->progress = RC_CLIENT_LOAD_STATE_UNKNOWN_GAME; + load_state->progress = RC_CLIENT_LOAD_GAME_STATE_ABORTED; if (load_state->client->state.load == load_state) load_state->client->state.load = NULL; @@ -1401,17 +1414,29 @@ static void rc_client_apply_unlocks(rc_client_subset_info_t* subset, rc_api_unlo } } +static void rc_client_free_pending_media(rc_client_pending_media_t* pending_media) +{ + if (pending_media->hash) + free((void*)pending_media->hash); +#ifdef RC_CLIENT_SUPPORTS_HASH + if (pending_media->data) + free(pending_media->data); + free((void*)pending_media->file_path); +#endif + free(pending_media); +} + static void rc_client_activate_game(rc_client_load_state_t* load_state, rc_api_start_session_response_t *start_session_response) { rc_client_t* client = load_state->client; rc_mutex_lock(&client->state.mutex); load_state->progress = (client->state.load == load_state) ? - RC_CLIENT_LOAD_STATE_DONE : RC_CLIENT_LOAD_STATE_UNKNOWN_GAME; + RC_CLIENT_LOAD_GAME_STATE_DONE : RC_CLIENT_LOAD_GAME_STATE_ABORTED; client->state.load = NULL; rc_mutex_unlock(&client->state.mutex); - if (load_state->progress != RC_CLIENT_LOAD_STATE_DONE) { + if (load_state->progress != RC_CLIENT_LOAD_GAME_STATE_DONE) { /* previous load state was aborted */ if (load_state->callback) load_state->callback(RC_ABORTED, "The requested game is no longer active", client, load_state->callback_userdata); @@ -1449,12 +1474,17 @@ static void rc_client_activate_game(rc_client_load_state_t* load_state, rc_api_s rc_mutex_unlock(&load_state->client->state.mutex); if (pending_media) { - rc_client_begin_change_media(client, pending_media->file_path, - pending_media->data, pending_media->data_size, pending_media->callback, pending_media->callback_userdata); - if (pending_media->data) - free(pending_media->data); - free((void*)pending_media->file_path); - free(pending_media); + if (pending_media->hash) { + rc_client_begin_change_media_from_hash(client, pending_media->hash, + pending_media->callback, pending_media->callback_userdata); + } else { +#ifdef RC_CLIENT_SUPPORTS_HASH + rc_client_begin_change_media(client, pending_media->file_path, + pending_media->data, pending_media->data_size, + pending_media->callback, pending_media->callback_userdata); +#endif + } + rc_client_free_pending_media(pending_media); } /* client->game must be set before calling this function so it can query the console_id */ @@ -1561,7 +1591,7 @@ static void rc_client_begin_start_session(rc_client_load_state_t* load_state) rc_client_load_error(load_state, result, rc_error_str(result)); } else { - rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_STATE_STARTING_SESSION, 1); + rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_GAME_STATE_STARTING_SESSION, 1); RC_CLIENT_LOG_VERBOSE_FORMATTED(client, "Starting session for game %u", start_session_params.game_id); rc_client_begin_async(client, &load_state->async_handle); client->callbacks.server_call(&start_session_request, rc_client_start_session_callback, load_state, client); @@ -1876,7 +1906,7 @@ static void rc_client_fetch_game_data_callback(const rc_api_server_response_t* s } /* kick off the start session request while we process the game data */ - rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_STATE_STARTING_SESSION, 1); + rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_GAME_STATE_STARTING_SESSION, 1); if (load_state->client->state.spectator_mode != RC_CLIENT_SPECTATOR_MODE_OFF) { /* we can't unlock achievements without a session, lock spectator mode for the game */ load_state->client->state.spectator_mode = RC_CLIENT_SPECTATOR_MODE_LOCKED; @@ -1962,6 +1992,7 @@ static void rc_client_begin_fetch_game_data(rc_client_load_state_t* load_state) int result; if (load_state->hash->game_id == 0) { +#ifdef RC_CLIENT_SUPPORTS_HASH char hash[33]; if (rc_hash_iterate(hash, &load_state->hash_iterator)) { @@ -2010,18 +2041,34 @@ static void rc_client_begin_fetch_game_data(rc_client_load_state_t* load_state) /* only a single hash was tried, capture it */ load_state->game->public_.console_id = load_state->hash_console_id; load_state->game->public_.hash = load_state->hash->hash; + + if (client->callbacks.identify_unknown_hash) { + load_state->hash->game_id = client->callbacks.identify_unknown_hash( + load_state->hash_console_id, load_state->hash->hash, client, load_state->callback_userdata); + + if (load_state->hash->game_id != 0) { + RC_CLIENT_LOG_INFO_FORMATTED(load_state->client, "Client says to load game %u for unidentified hash %s", + load_state->hash->game_id, load_state->hash->hash); + } + } } +#else + load_state->game->public_.console_id = RC_CONSOLE_UNKNOWN; + load_state->game->public_.hash = load_state->hash->hash; +#endif /* RC_CLIENT_SUPPORTS_HASH */ - load_state->game->public_.title = "Unknown Game"; - load_state->game->public_.badge_name = ""; - client->game = load_state->game; - load_state->game = NULL; + if (load_state->hash->game_id == 0) { + load_state->game->public_.title = "Unknown Game"; + load_state->game->public_.badge_name = ""; + client->game = load_state->game; + load_state->game = NULL; - rc_client_load_error(load_state, RC_NO_GAME_LOADED, "Unknown game"); - return; + rc_client_load_error(load_state, RC_NO_GAME_LOADED, "Unknown game"); + return; + } } - if (load_state->hash->hash[0] != '[') { + if (load_state->hash->hash[0] != '[') { /* not [NO HASH] or [SUBSETxx] */ load_state->game->public_.id = load_state->hash->game_id; load_state->game->public_.hash = load_state->hash->hash; } @@ -2032,7 +2079,7 @@ static void rc_client_begin_fetch_game_data(rc_client_load_state_t* load_state) rc_mutex_lock(&client->state.mutex); result = client->state.user; if (result == RC_CLIENT_USER_STATE_LOGIN_REQUESTED) - load_state->progress = RC_CLIENT_LOAD_STATE_AWAIT_LOGIN; + load_state->progress = RC_CLIENT_LOAD_GAME_STATE_AWAIT_LOGIN; rc_mutex_unlock(&client->state.mutex); switch (result) { @@ -2059,7 +2106,7 @@ static void rc_client_begin_fetch_game_data(rc_client_load_state_t* load_state) return; } - rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_STATE_FETCHING_GAME_DATA, 1); + rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_GAME_STATE_FETCHING_GAME_DATA, 1); RC_CLIENT_LOG_VERBOSE_FORMATTED(client, "Fetching data for game %u", fetch_game_data_request.game_id); rc_client_begin_async(client, &load_state->async_handle); @@ -2201,7 +2248,7 @@ static rc_client_async_handle_t* rc_client_load_game(rc_client_load_state_t* loa return NULL; } - rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_STATE_IDENTIFYING_GAME, 1); + rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_GAME_STATE_IDENTIFYING_GAME, 1); rc_client_begin_async(client, &load_state->async_handle); client->callbacks.server_call(&request, rc_client_identify_game_callback, load_state, client); @@ -2217,14 +2264,6 @@ static rc_client_async_handle_t* rc_client_load_game(rc_client_load_state_t* loa return (client->state.load == load_state) ? &load_state->async_handle : NULL; } -rc_hash_iterator_t* rc_client_get_load_state_hash_iterator(rc_client_t* client) -{ - if (client && client->state.load) - return &client->state.load->hash_iterator; - - return NULL; -} - rc_client_async_handle_t* rc_client_begin_load_game(rc_client_t* client, const char* hash, rc_client_callback_t callback, void* callback_userdata) { rc_client_load_state_t* load_state; @@ -2257,6 +2296,16 @@ rc_client_async_handle_t* rc_client_begin_load_game(rc_client_t* client, const c return rc_client_load_game(load_state, hash, NULL); } +#ifdef RC_CLIENT_SUPPORTS_HASH + +rc_hash_iterator_t* rc_client_get_load_state_hash_iterator(rc_client_t* client) +{ + if (client && client->state.load) + return &client->state.load->hash_iterator; + + return NULL; +} + rc_client_async_handle_t* rc_client_begin_identify_and_load_game(rc_client_t* client, uint32_t console_id, const char* file_path, const uint8_t* data, size_t data_size, @@ -2340,6 +2389,22 @@ rc_client_async_handle_t* rc_client_begin_identify_and_load_game(rc_client_t* cl return rc_client_load_game(load_state, hash, file_path); } +#endif /* RC_CLIENT_SUPPORTS_HASH */ + +int rc_client_get_load_game_state(const rc_client_t* client) +{ + int state = RC_CLIENT_LOAD_GAME_STATE_NONE; + if (client) { + const rc_client_load_state_t* load_state = client->state.load; + if (load_state) + state = load_state->progress; + else if (client->game) + state = RC_CLIENT_LOAD_GAME_STATE_DONE; + } + + return state; +} + static void rc_client_game_mark_ui_to_be_hidden(rc_client_t* client, rc_client_game_info_t* game) { rc_client_achievement_info_t* achievement; @@ -2428,8 +2493,10 @@ void rc_client_unload_game(rc_client_t* client) } } -static void rc_client_change_media(rc_client_t* client, const rc_client_game_hash_t* game_hash, rc_client_callback_t callback, void* callback_userdata) +static void rc_client_change_media_internal(rc_client_t* client, const rc_client_game_hash_t* game_hash, rc_client_callback_t callback, void* callback_userdata) { + client->game->public_.hash = game_hash->hash; + if (game_hash->game_id == client->game->public_.id) { RC_CLIENT_LOG_INFO_FORMATTED(client, "Switching to valid media for game %u: %s", game_hash->game_id, game_hash->hash); } @@ -2437,13 +2504,19 @@ static void rc_client_change_media(rc_client_t* client, const rc_client_game_has RC_CLIENT_LOG_INFO(client, "Switching to unknown media"); } else if (game_hash->game_id == 0) { + if (client->state.hardcore) { + RC_CLIENT_LOG_WARN_FORMATTED(client, "Disabling hardcore for unidentified media: %s", game_hash->hash); + rc_client_set_hardcore_enabled(client, 0); + callback(RC_HARDCORE_DISABLED, "Hardcore disabled. Unidentified media inserted.", client, callback_userdata); + return; + } + RC_CLIENT_LOG_INFO_FORMATTED(client, "Switching to unrecognized media: %s", game_hash->hash); } else { RC_CLIENT_LOG_INFO_FORMATTED(client, "Switching to known media for game %u: %s", game_hash->game_id, game_hash->hash); } - client->game->public_.hash = game_hash->hash; callback(RC_OK, NULL, client, callback_userdata); } @@ -2475,22 +2548,65 @@ static void rc_client_identify_changed_media_callback(const rc_api_server_respon else { load_state->hash->game_id = resolve_hash_response.game_id; - if (resolve_hash_response.game_id == 0 && client->state.hardcore) { - RC_CLIENT_LOG_WARN_FORMATTED(client, "Disabling hardcore for unidentified media: %s", load_state->hash->hash); - rc_client_set_hardcore_enabled(client, 0); - client->game->public_.hash = load_state->hash->hash; /* do still update the loaded hash */ - load_state->callback(RC_HARDCORE_DISABLED, "Hardcore disabled. Unidentified media inserted.", client, load_state->callback_userdata); - } - else { + if (resolve_hash_response.game_id != 0) { RC_CLIENT_LOG_INFO_FORMATTED(client, "Identified game: %u (%s)", load_state->hash->game_id, load_state->hash->hash); - rc_client_change_media(client, load_state->hash, load_state->callback, load_state->callback_userdata); } + + rc_client_change_media_internal(client, load_state->hash, load_state->callback, load_state->callback_userdata); } free(load_state); rc_api_destroy_resolve_hash_response(&resolve_hash_response); } +static rc_client_async_handle_t* rc_client_begin_change_media_internal(rc_client_t* client, + rc_client_game_info_t* game, rc_client_game_hash_t* game_hash, rc_client_callback_t callback, void* callback_userdata) +{ + 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; + + if (game_hash->game_id != RC_CLIENT_UNKNOWN_GAME_ID) { + rc_client_change_media_internal(client, game_hash, callback, callback_userdata); + return NULL; + } + + /* call the server to make sure the hash is valid for the loaded game */ + memset(&resolve_hash_request, 0, sizeof(resolve_hash_request)); + resolve_hash_request.game_hash = game_hash->hash; + + result = rc_api_init_resolve_hash_request(&request, &resolve_hash_request); + if (result != RC_OK) { + callback(result, rc_error_str(result), client, callback_userdata); + return NULL; + } + + callback_data = (rc_client_load_state_t*)calloc(1, sizeof(rc_client_load_state_t)); + if (!callback_data) { + callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), client, callback_userdata); + return NULL; + } + + callback_data->callback = callback; + callback_data->callback_userdata = callback_userdata; + callback_data->client = client; + callback_data->hash = game_hash; + callback_data->game = game; + + 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); + + /* if handle is no longer valid, the async operation completed synchronously */ + return rc_client_async_handle_valid(client, async_handle) ? async_handle : NULL; +} + +#ifdef RC_CLIENT_SUPPORTS_HASH + rc_client_async_handle_t* rc_client_begin_change_media(rc_client_t* client, const char* file_path, const uint8_t* data, size_t data_size, rc_client_callback_t callback, void* callback_userdata) { @@ -2521,12 +2637,8 @@ rc_client_async_handle_t* rc_client_begin_change_media(rc_client_t* client, cons if (game->public_.console_id == 0) { /* still waiting for game data */ pending_media = client->state.load->pending_media; - if (pending_media) { - if (pending_media->data) - free(pending_media->data); - free((void*)pending_media->file_path); - free(pending_media); - } + if (pending_media) + rc_client_free_pending_media(pending_media); pending_media = (rc_client_pending_media_t*)calloc(1, sizeof(*pending_media)); if (!pending_media) { @@ -2613,53 +2725,78 @@ rc_client_async_handle_t* rc_client_begin_change_media(rc_client_t* client, cons rc_mutex_unlock(&client->state.mutex); if (!result) { - rc_client_change_media(client, game_hash, callback, callback_userdata); + rc_client_change_media_internal(client, game_hash, callback, callback_userdata); return NULL; } } - if (game_hash->game_id != RC_CLIENT_UNKNOWN_GAME_ID) { - rc_client_change_media(client, game_hash, callback, callback_userdata); + return rc_client_begin_change_media_internal(client, game, game_hash, callback, callback_userdata); +} + +#endif /* RC_CLIENT_SUPPORTS_HASH */ + +rc_client_async_handle_t* rc_client_begin_change_media_from_hash(rc_client_t* client, const char* hash, + rc_client_callback_t callback, void* callback_userdata) +{ + rc_client_game_hash_t* game_hash; + rc_client_game_info_t* game; + rc_client_pending_media_t* pending_media = NULL; + + if (!client) { + callback(RC_INVALID_STATE, "client is required", client, callback_userdata); return NULL; } - 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; - memset(&resolve_hash_request, 0, sizeof(resolve_hash_request)); - resolve_hash_request.game_hash = game_hash->hash; - - result = rc_api_init_resolve_hash_request(&request, &resolve_hash_request); - if (result != RC_OK) { - callback(result, rc_error_str(result), client, callback_userdata); - return NULL; - } - - callback_data = (rc_client_load_state_t*)calloc(1, sizeof(rc_client_load_state_t)); - if (!callback_data) { - callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), client, callback_userdata); - return NULL; - } - - callback_data->callback = callback; - callback_data->callback_userdata = callback_userdata; - callback_data->client = client; - callback_data->hash = game_hash; - callback_data->game = game; - - 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); - - /* if handle is no longer valid, the async operation completed synchronously */ - return rc_client_async_handle_valid(client, async_handle) ? async_handle : NULL; + if (!hash || !hash[0]) { + callback(RC_INVALID_STATE, "hash is required", client, callback_userdata); + return NULL; } + +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->begin_change_media_from_hash) { + return client->state.external_client->begin_change_media_from_hash(client, hash, callback, callback_userdata); + } +#endif + + rc_mutex_lock(&client->state.mutex); + if (client->state.load) { + game = client->state.load->game; + if (game->public_.console_id == 0) { + /* still waiting for game data */ + pending_media = client->state.load->pending_media; + if (pending_media) + rc_client_free_pending_media(pending_media); + + pending_media = (rc_client_pending_media_t*)calloc(1, sizeof(*pending_media)); + if (!pending_media) { + rc_mutex_unlock(&client->state.mutex); + callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), client, callback_userdata); + return NULL; + } + + pending_media->hash = strdup(hash); + pending_media->callback = callback; + pending_media->callback_userdata = callback_userdata; + + client->state.load->pending_media = pending_media; + } + } else { + game = client->game; + } + rc_mutex_unlock(&client->state.mutex); + + if (!game) { + callback(RC_NO_GAME_LOADED, rc_error_str(RC_NO_GAME_LOADED), client, callback_userdata); + return NULL; + } + + /* still waiting for game data */ + if (pending_media) + return NULL; + + /* check to see if we've already hashed this file. */ + game_hash = rc_client_find_game_hash(client, hash); + return rc_client_begin_change_media_internal(client, game, game_hash, callback, callback_userdata); } const rc_client_game_t* rc_client_get_game_info(const rc_client_t* client) @@ -3753,7 +3890,7 @@ int rc_client_has_leaderboards(rc_client_t* client) return result; } -static void rc_client_allocate_leaderboard_tracker(rc_client_game_info_t* game, rc_client_leaderboard_info_t* leaderboard) +void rc_client_allocate_leaderboard_tracker(rc_client_game_info_t* game, rc_client_leaderboard_info_t* leaderboard) { rc_client_leaderboard_tracker_info_t* tracker; rc_client_leaderboard_tracker_info_t* available_tracker = NULL; @@ -5234,6 +5371,11 @@ size_t rc_client_progress_size(rc_client_t* client) } int rc_client_serialize_progress(rc_client_t* client, uint8_t* buffer) +{ + return rc_client_serialize_progress_sized(client, buffer, 0xFFFFFFFF); +} + +int rc_client_serialize_progress_sized(rc_client_t* client, uint8_t* buffer, size_t buffer_size) { int result; @@ -5242,7 +5384,7 @@ int rc_client_serialize_progress(rc_client_t* client, uint8_t* buffer) #ifdef RC_CLIENT_SUPPORTS_EXTERNAL if (client->state.external_client && client->state.external_client->serialize_progress) - return client->state.external_client->serialize_progress(buffer); + return client->state.external_client->serialize_progress(buffer, buffer_size); #endif if (!client->game) @@ -5252,7 +5394,7 @@ int rc_client_serialize_progress(rc_client_t* client, uint8_t* buffer) return RC_INVALID_STATE; rc_mutex_lock(&client->state.mutex); - result = rc_runtime_serialize_progress(buffer, &client->game->runtime, NULL); + result = rc_runtime_serialize_progress_sized(buffer, (uint32_t)buffer_size, &client->game->runtime, NULL); rc_mutex_unlock(&client->state.mutex); return result; @@ -5352,6 +5494,11 @@ static void rc_client_subset_after_deserialize_progress(rc_client_game_info_t* g } int rc_client_deserialize_progress(rc_client_t* client, const uint8_t* serialized) +{ + return rc_client_deserialize_progress_sized(client, serialized, 0xFFFFFFFF); +} + +int rc_client_deserialize_progress_sized(rc_client_t* client, const uint8_t* serialized, size_t serialized_size) { rc_client_subset_info_t* subset; int result; @@ -5361,7 +5508,7 @@ int rc_client_deserialize_progress(rc_client_t* client, const uint8_t* serialize #ifdef RC_CLIENT_SUPPORTS_EXTERNAL if (client->state.external_client && client->state.external_client->deserialize_progress) - return client->state.external_client->deserialize_progress(serialized); + return client->state.external_client->deserialize_progress(serialized, serialized_size); #endif if (!client->game) @@ -5381,7 +5528,7 @@ int rc_client_deserialize_progress(rc_client_t* client, const uint8_t* serialize result = RC_OK; } else { - result = rc_runtime_deserialize_progress(&client->game->runtime, serialized, NULL); + result = rc_runtime_deserialize_progress_sized(&client->game->runtime, serialized, (uint32_t)serialized_size, NULL); } for (subset = client->game->subsets; subset; subset = subset->next) diff --git a/deps/rcheevos/src/rc_client_external.h b/deps/rcheevos/src/rc_client_external.h index a519e428e4..82ec1d49d7 100644 --- a/deps/rcheevos/src/rc_client_external.h +++ b/deps/rcheevos/src/rc_client_external.h @@ -61,8 +61,8 @@ typedef rc_client_async_handle_t* (RC_CCONV *rc_client_external_begin_fetch_lead typedef size_t (RC_CCONV *rc_client_external_progress_size_func_t)(void); -typedef int (RC_CCONV *rc_client_external_serialize_progress_func_t)(uint8_t* buffer); -typedef int (RC_CCONV *rc_client_external_deserialize_progress_func_t)(const uint8_t* buffer); +typedef int (RC_CCONV *rc_client_external_serialize_progress_func_t)(uint8_t* buffer, size_t buffer_size); +typedef int (RC_CCONV *rc_client_external_deserialize_progress_func_t)(const uint8_t* buffer, size_t buffer_size); typedef struct rc_client_external_t { @@ -99,6 +99,7 @@ typedef struct rc_client_external_t 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_begin_load_game_func_t begin_change_media_from_hash; rc_client_external_create_achievement_list_func_t create_achievement_list; rc_client_external_get_int_func_t has_achievements; diff --git a/deps/rcheevos/src/rc_client_internal.h b/deps/rcheevos/src/rc_client_internal.h index 5e57091516..a27da6588d 100644 --- a/deps/rcheevos/src/rc_client_internal.h +++ b/deps/rcheevos/src/rc_client_internal.h @@ -26,6 +26,8 @@ typedef void (RC_CCONV *rc_client_post_process_game_data_response_t)(const rc_ap typedef int (RC_CCONV *rc_client_can_submit_achievement_unlock_t)(uint32_t achievement_id, rc_client_t* client); typedef int (RC_CCONV *rc_client_can_submit_leaderboard_entry_t)(uint32_t leaderboard_id, rc_client_t* client); typedef int (RC_CCONV *rc_client_rich_presence_override_t)(rc_client_t* client, char buffer[], size_t buffersize); +typedef uint32_t (RC_CCONV* rc_client_identify_hash_func_t)(uint32_t console_id, const char* hash, + rc_client_t* client, void* callback_userdata); typedef struct rc_client_callbacks_t { rc_client_read_memory_func_t read_memory; @@ -33,6 +35,7 @@ typedef struct rc_client_callbacks_t { rc_client_server_call_t server_call; rc_client_message_callback_t log_call; rc_get_time_millisecs_func_t get_time_millisecs; + rc_client_identify_hash_func_t identify_unknown_hash; rc_client_post_process_game_data_response_t post_process_game_data_response; rc_client_can_submit_achievement_unlock_t can_submit_achievement_unlock; rc_client_can_submit_leaderboard_entry_t can_submit_leaderboard_entry; @@ -265,16 +268,6 @@ void rc_client_update_active_leaderboards(rc_client_game_info_t* game); | Client | \*****************************************************************************/ -enum { - RC_CLIENT_LOAD_STATE_NONE, - RC_CLIENT_LOAD_STATE_IDENTIFYING_GAME, - RC_CLIENT_LOAD_STATE_AWAIT_LOGIN, - RC_CLIENT_LOAD_STATE_FETCHING_GAME_DATA, - RC_CLIENT_LOAD_STATE_STARTING_SESSION, - RC_CLIENT_LOAD_STATE_DONE, - RC_CLIENT_LOAD_STATE_UNKNOWN_GAME -}; - enum { RC_CLIENT_USER_STATE_NONE, RC_CLIENT_USER_STATE_LOGIN_REQUESTED, @@ -375,8 +368,10 @@ int rc_value_contains_memref(const rc_value_t* value, const rc_memref_t* memref) /* end runtime.c internals */ /* helper functions for unit tests */ +#ifdef RC_CLIENT_SUPPORTS_HASH struct rc_hash_iterator; struct rc_hash_iterator* rc_client_get_load_state_hash_iterator(rc_client_t* client); +#endif /* end helper functions for unit tests */ enum { @@ -387,6 +382,7 @@ enum { void rc_client_set_legacy_peek(rc_client_t* client, int method); +void rc_client_allocate_leaderboard_tracker(rc_client_game_info_t* game, rc_client_leaderboard_info_t* leaderboard); void rc_client_release_leaderboard_tracker(rc_client_game_info_t* game, rc_client_leaderboard_info_t* leaderboard); RC_END_C_DECLS diff --git a/deps/rcheevos/src/rc_client_raintegration.c b/deps/rcheevos/src/rc_client_raintegration.c index efea7e449d..4ad9defa7c 100644 --- a/deps/rcheevos/src/rc_client_raintegration.c +++ b/deps/rcheevos/src/rc_client_raintegration.c @@ -77,7 +77,9 @@ static void rc_client_raintegration_load_dll(rc_client_t* client, raintegration->get_menu = (rc_client_raintegration_get_menu_func_t)GetProcAddress(hDLL, "_Rcheevos_RAIntegrationGetMenu"); raintegration->activate_menu_item = (rc_client_raintegration_activate_menuitem_func_t)GetProcAddress(hDLL, "_Rcheevos_ActivateRAIntegrationMenuItem"); raintegration->set_write_memory_function = (rc_client_raintegration_set_write_memory_func_t)GetProcAddress(hDLL, "_Rcheevos_SetRAIntegrationWriteMemoryFunction"); + raintegration->set_get_game_name_function = (rc_client_raintegration_set_get_game_name_func_t)GetProcAddress(hDLL, "_Rcheevos_SetRAIntegrationGetGameNameFunction"); raintegration->set_event_handler = (rc_client_raintegration_set_event_handler_func_t)GetProcAddress(hDLL, "_Rcheevos_SetRAIntegrationEventHandler"); + raintegration->has_modifications = (rc_client_raintegration_get_int_func_t)GetProcAddress(hDLL, "_Rcheevos_HasModifications"); if (!raintegration->get_version || !raintegration->init_client || @@ -147,7 +149,8 @@ static void rc_client_init_raintegration(rc_client_t* client, 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); + if (strcmp(host_url, "https://retroachievements.org") != 0) + rc_client_set_host(client, host_url); } else if (client->state.raintegration->init_client_offline) { init_func = client->state.raintegration->init_client_offline; @@ -363,6 +366,12 @@ void rc_client_raintegration_set_write_memory_function(rc_client_t* client, rc_c client->state.raintegration->set_write_memory_function(client, handler); } +void rc_client_raintegration_set_get_game_name_function(rc_client_t* client, rc_client_raintegration_get_game_name_func_t handler) +{ + if (client && client->state.raintegration && client->state.raintegration->set_get_game_name_function) + client->state.raintegration->set_get_game_name_function(client, handler); +} + void rc_client_raintegration_set_event_handler(rc_client_t* client, rc_client_raintegration_event_handler_t handler) { @@ -382,6 +391,18 @@ const rc_client_raintegration_menu_t* rc_client_raintegration_get_menu(const rc_ return client->state.raintegration->get_menu(); } +int rc_client_raintegration_has_modifications(const rc_client_t* client) +{ + if (!client || !client->state.raintegration || + !client->state.raintegration->bIsInited || + !client->state.raintegration->has_modifications) + { + return 0; + } + + return client->state.raintegration->has_modifications(); +} + void rc_client_raintegration_rebuild_submenu(rc_client_t* client, HMENU hMenu) { HMENU hPopupMenu = NULL; @@ -413,7 +434,7 @@ void rc_client_raintegration_rebuild_submenu(rc_client_t* client, HMENU hMenu) if (menuitem->checked) flags |= MF_CHECKED; if (!menuitem->enabled) - flags |= MF_DISABLED | MF_GRAYED; + flags |= MF_GRAYED; AppendMenuA(hPopupMenu, flags, menuitem->id, menuitem->label); } @@ -428,7 +449,7 @@ void rc_client_raintegration_rebuild_submenu(rc_client_t* client, HMENU hMenu) UINT flags = MF_POPUP | MF_STRING; if (!menu || !menu->num_items) - flags |= MF_DISABLED | MF_GRAYED; + flags |= MF_GRAYED; while (--nIndex >= 0) { @@ -457,15 +478,18 @@ void rc_client_raintegration_update_menu_item(const rc_client_t* client, const r flags |= MF_CHECKED; CheckMenuItem(client->state.raintegration->hPopupMenu, menuitem->id, flags | MF_BYCOMMAND); + + flags = (menuitem->enabled) ? MF_ENABLED : MF_GRAYED; + EnableMenuItem(client->state.raintegration->hPopupMenu, menuitem->id, flags | MF_BYCOMMAND); } } -int rc_client_raintegration_activate_menu_item(const rc_client_t* client, uint32_t nMenuItemId) +int rc_client_raintegration_activate_menu_item(const rc_client_t* client, uint32_t menu_item_id) { if (!client || !client->state.raintegration || !client->state.raintegration->activate_menu_item) return 0; - return client->state.raintegration->activate_menu_item(nMenuItemId); + return client->state.raintegration->activate_menu_item(menu_item_id); } void rc_client_unload_raintegration(rc_client_t* client) @@ -477,6 +501,9 @@ void rc_client_unload_raintegration(rc_client_t* client) RC_CLIENT_LOG_INFO(client, "Unloading RA_Integration") + if (client->state.external_client && client->state.external_client->destroy) + client->state.external_client->destroy(); + if (client->state.raintegration->shutdown) client->state.raintegration->shutdown(); diff --git a/deps/rcheevos/src/rc_client_raintegration_internal.h b/deps/rcheevos/src/rc_client_raintegration_internal.h index 530d98e1a8..ce7c98b03b 100644 --- a/deps/rcheevos/src/rc_client_raintegration_internal.h +++ b/deps/rcheevos/src/rc_client_raintegration_internal.h @@ -20,7 +20,9 @@ typedef void (RC_CCONV* rc_client_raintegration_hwnd_action_func_t)(HWND hWnd); typedef const rc_client_raintegration_menu_t* (RC_CCONV* rc_client_raintegration_get_menu_func_t)(void); typedef int (RC_CCONV* rc_client_raintegration_activate_menuitem_func_t)(uint32_t nMenuItemId); typedef void (RC_CCONV* rc_client_raintegration_set_write_memory_func_t)(rc_client_t* pClient, rc_client_raintegration_write_memory_func_t handler); +typedef void (RC_CCONV* rc_client_raintegration_set_get_game_name_func_t)(rc_client_t* pClient, rc_client_raintegration_get_game_name_func_t handler); typedef void (RC_CCONV* rc_client_raintegration_set_event_handler_func_t)(rc_client_t* pClient, rc_client_raintegration_event_handler_t handler); +typedef int (RC_CCONV* rc_client_raintegration_get_int_func_t)(void); typedef struct rc_client_raintegration_t { @@ -37,9 +39,11 @@ typedef struct rc_client_raintegration_t rc_client_raintegration_hwnd_action_func_t update_main_window_handle; rc_client_raintegration_set_write_memory_func_t set_write_memory_function; + rc_client_raintegration_set_get_game_name_func_t set_get_game_name_function; rc_client_raintegration_set_event_handler_func_t set_event_handler; rc_client_raintegration_get_menu_func_t get_menu; rc_client_raintegration_activate_menuitem_func_t activate_menu_item; + rc_client_raintegration_get_int_func_t has_modifications; rc_client_raintegration_get_external_client_func_t get_external_client; diff --git a/deps/rcheevos/src/rc_util.c b/deps/rcheevos/src/rc_util.c index fa369a3b0d..edc6a5a398 100644 --- a/deps/rcheevos/src/rc_util.c +++ b/deps/rcheevos/src/rc_util.c @@ -183,6 +183,7 @@ const char* rc_error_str(int ret) case RC_ACCESS_DENIED: return "Access denied"; case RC_INVALID_CREDENTIALS: return "Invalid credentials"; case RC_EXPIRED_TOKEN: return "Expired token"; + case RC_INSUFFICIENT_BUFFER: return "Buffer not large enough"; default: return "Unknown error"; } } diff --git a/deps/rcheevos/src/rc_version.h b/deps/rcheevos/src/rc_version.h index daf57e1cb5..6d9614faf7 100644 --- a/deps/rcheevos/src/rc_version.h +++ b/deps/rcheevos/src/rc_version.h @@ -8,7 +8,7 @@ RC_BEGIN_C_DECLS #define RCHEEVOS_VERSION_MAJOR 11 -#define RCHEEVOS_VERSION_MINOR 1 +#define RCHEEVOS_VERSION_MINOR 2 #define RCHEEVOS_VERSION_PATCH 0 #define RCHEEVOS_MAKE_VERSION(major, minor, patch) (major * 1000000 + minor * 1000 + patch) diff --git a/deps/rcheevos/src/rcheevos/memref.c b/deps/rcheevos/src/rcheevos/memref.c index 87f6ec0bf1..0cbec67b85 100644 --- a/deps/rcheevos/src/rcheevos/memref.c +++ b/deps/rcheevos/src/rcheevos/memref.c @@ -95,6 +95,8 @@ int rc_parse_memref(const char** memaddr, uint8_t* size, uint32_t* address) { switch (*aux++) { case 'f': case 'F': *size = RC_MEMSIZE_FLOAT; break; case 'b': case 'B': *size = RC_MEMSIZE_FLOAT_BE; break; + case 'h': case 'H': *size = RC_MEMSIZE_DOUBLE32; break; + case 'i': case 'I': *size = RC_MEMSIZE_DOUBLE32_BE; break; case 'm': case 'M': *size = RC_MEMSIZE_MBF32; break; case 'l': case 'L': *size = RC_MEMSIZE_MBF32_LE; break; @@ -198,6 +200,29 @@ static void rc_transform_memref_float_be(rc_typed_value_t* value) { value->type = RC_VALUE_TYPE_FLOAT; } +static void rc_transform_memref_double32(rc_typed_value_t* value) +{ + /* decodes the four most significant bytes of an IEEE 754 double into a float */ + const uint32_t mantissa = (value->value.u32 & 0x000FFFFF) << 3; + const int32_t exponent = (int32_t)((value->value.u32 >> 20) & 0x7FF) - 1023; + const int sign = (value->value.u32 & 0x80000000); + value->value.f32 = rc_build_float(mantissa, exponent, sign); + value->type = RC_VALUE_TYPE_FLOAT; +} + +static void rc_transform_memref_double32_be(rc_typed_value_t* value) +{ + /* decodes the four most significant bytes of an IEEE 754 double in big endian format into a float */ + const uint32_t mantissa = (((value->value.u32 & 0xFF000000) >> 24) | + ((value->value.u32 & 0x00FF0000) >> 8) | + ((value->value.u32 & 0x00000F00) << 8)) << 3; + const int32_t exponent = (int32_t)(((value->value.u32 & 0x0000007F) << 4) | + ((value->value.u32 & 0x0000F000) >> 12)) - 1023; + const int sign = (value->value.u32 & 0x00000080); + value->value.f32 = rc_build_float(mantissa, exponent, sign); + value->type = RC_VALUE_TYPE_FLOAT; +} + static void rc_transform_memref_mbf32(rc_typed_value_t* value) { /* decodes a Microsoft Binary Format float */ /* NOTE: 32-bit MBF is stored in memory as big endian (at least for Apple II) */ @@ -322,6 +347,14 @@ void rc_transform_memref_value(rc_typed_value_t* value, uint8_t size) { rc_transform_memref_float_be(value); break; + case RC_MEMSIZE_DOUBLE32: + rc_transform_memref_double32(value); + break; + + case RC_MEMSIZE_DOUBLE32_BE: + rc_transform_memref_double32_be(value); + break; + case RC_MEMSIZE_MBF32: rc_transform_memref_mbf32(value); break; @@ -358,6 +391,8 @@ static const uint32_t rc_memref_masks[] = { 0xffffffff, /* RC_MEMSIZE_MBF32 */ 0xffffffff, /* RC_MEMSIZE_MBF32_LE */ 0xffffffff, /* RC_MEMSIZE_FLOAT_BE */ + 0xffffffff, /* RC_MEMSIZE_DOUBLE32 */ + 0xffffffff, /* RC_MEMSIZE_DOUBLE32_BE*/ 0xffffffff /* RC_MEMSIZE_VARIABLE */ }; @@ -395,6 +430,8 @@ static const uint8_t rc_memref_shared_sizes[] = { RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_MBF32 */ RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_MBF32_LE */ RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_FLOAT_BE */ + RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_DOUBLE32 */ + RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_DOUBLE32_BE*/ RC_MEMSIZE_32_BITS /* RC_MEMSIZE_VARIABLE */ }; diff --git a/deps/rcheevos/src/rcheevos/operand.c b/deps/rcheevos/src/rcheevos/operand.c index 2522582752..65d41dc5e0 100644 --- a/deps/rcheevos/src/rcheevos/operand.c +++ b/deps/rcheevos/src/rcheevos/operand.c @@ -297,6 +297,8 @@ int rc_operand_is_float_memref(const rc_operand_t* self) { switch (self->size) { case RC_MEMSIZE_FLOAT: case RC_MEMSIZE_FLOAT_BE: + case RC_MEMSIZE_DOUBLE32: + case RC_MEMSIZE_DOUBLE32_BE: case RC_MEMSIZE_MBF32: case RC_MEMSIZE_MBF32_LE: return 1; diff --git a/deps/rcheevos/src/rcheevos/runtime_progress.c b/deps/rcheevos/src/rcheevos/runtime_progress.c index fd951dbd5b..07e1dbe1b6 100644 --- a/deps/rcheevos/src/rcheevos/runtime_progress.c +++ b/deps/rcheevos/src/rcheevos/runtime_progress.c @@ -4,6 +4,7 @@ #include "rc_util.h" #include "../rhash/md5.h" +#include #include #include @@ -17,17 +18,22 @@ #define RC_RUNTIME_CHUNK_DONE 0x454E4F44 /* DONE */ +#define RC_RUNTIME_MIN_BUFFER_SIZE 4 + 8 + 16 /* RUNTIME_MARKER, CHUNK_DONE, MD5 */ + typedef struct rc_runtime_progress_t { const rc_runtime_t* runtime; uint32_t offset; uint8_t* buffer; + uint32_t buffer_size; uint32_t chunk_size_offset; lua_State* L; } rc_runtime_progress_t; +#define assert_chunk_size(expected_size) assert((uint32_t)(progress->offset - progress->chunk_size_offset - 4) == (uint32_t)(expected_size)) + #define RC_TRIGGER_STATE_UNUPDATED 0x7F #define RC_MEMREF_FLAG_CHANGED_THIS_FRAME 0x00010000 @@ -117,21 +123,29 @@ static void rc_runtime_progress_init(rc_runtime_progress_t* progress, const rc_r progress->L = L; } +#define RC_RUNTIME_SERIALIZED_MEMREF_SIZE 16 /* 4x uint: address, flags, value, prior */ + static int rc_runtime_progress_write_memrefs(rc_runtime_progress_t* progress) { - rc_memref_t* memref = progress->runtime->memrefs; - uint32_t flags = 0; + rc_memref_t* memref; + uint32_t count = 0; + + for (memref = progress->runtime->memrefs; memref; memref = memref->next) + ++count; + if (count == 0) + return RC_OK; + + if (progress->offset + 8 + count * RC_RUNTIME_SERIALIZED_MEMREF_SIZE > progress->buffer_size) + return RC_INSUFFICIENT_BUFFER; rc_runtime_progress_start_chunk(progress, RC_RUNTIME_CHUNK_MEMREFS); if (!progress->buffer) { - while (memref) { - progress->offset += 16; - memref = memref->next; - } + progress->offset += count * RC_RUNTIME_SERIALIZED_MEMREF_SIZE; } else { - while (memref) { + uint32_t flags = 0; + for (memref = progress->runtime->memrefs; memref; memref = memref->next) { flags = memref->value.size; if (memref->value.changed) flags |= RC_MEMREF_FLAG_CHANGED_THIS_FRAME; @@ -140,11 +154,10 @@ static int rc_runtime_progress_write_memrefs(rc_runtime_progress_t* progress) rc_runtime_progress_write_uint(progress, flags); rc_runtime_progress_write_uint(progress, memref->value.value); rc_runtime_progress_write_uint(progress, memref->value.prior); - - memref = memref->next; } } + assert_chunk_size(count * RC_RUNTIME_SERIALIZED_MEMREF_SIZE); rc_runtime_progress_end_chunk(progress); return RC_OK; } @@ -159,7 +172,7 @@ static int rc_runtime_progress_read_memrefs(rc_runtime_progress_t* progress) /* re-read the chunk size to determine how many memrefs are present */ progress->offset -= 4; - entries = rc_runtime_progress_read_uint(progress) / 16; + entries = rc_runtime_progress_read_uint(progress) / RC_RUNTIME_SERIALIZED_MEMREF_SIZE; while (entries != 0) { address = rc_runtime_progress_read_uint(progress); @@ -210,6 +223,9 @@ static int rc_runtime_progress_write_condset(rc_runtime_progress_t* progress, rc rc_condition_t* cond; uint32_t flags; + if (progress->offset + 4 > progress->buffer_size) + return RC_INSUFFICIENT_BUFFER; + rc_runtime_progress_write_uint(progress, condset->is_paused); cond = condset->conditions; @@ -230,15 +246,24 @@ static int rc_runtime_progress_write_condset(rc_runtime_progress_t* progress, rc flags |= RC_COND_FLAG_OPERAND2_MEMREF_CHANGED_THIS_FRAME; } + if (progress->offset + 8 > progress->buffer_size) + return RC_INSUFFICIENT_BUFFER; + rc_runtime_progress_write_uint(progress, cond->current_hits); rc_runtime_progress_write_uint(progress, flags); if (flags & RC_COND_FLAG_OPERAND1_IS_INDIRECT_MEMREF) { + if (progress->offset + 8 > progress->buffer_size) + return RC_INSUFFICIENT_BUFFER; + rc_runtime_progress_write_uint(progress, cond->operand1.value.memref->value.value); rc_runtime_progress_write_uint(progress, cond->operand1.value.memref->value.prior); } if (flags & RC_COND_FLAG_OPERAND2_IS_INDIRECT_MEMREF) { + if (progress->offset + 8 > progress->buffer_size) + return RC_INSUFFICIENT_BUFFER; + rc_runtime_progress_write_uint(progress, cond->operand2.value.memref->value.value); rc_runtime_progress_write_uint(progress, cond->operand2.value.memref->value.prior); } @@ -310,6 +335,9 @@ static int rc_runtime_progress_write_variable(rc_runtime_progress_t* progress, c { uint32_t flags; + if (progress->offset + 12 > progress->buffer_size) + return RC_INSUFFICIENT_BUFFER; + flags = rc_runtime_progress_should_serialize_variable_condset(variable->conditions); if (variable->value.changed) flags |= RC_MEMREF_FLAG_CHANGED_THIS_FRAME; @@ -331,21 +359,30 @@ static int rc_runtime_progress_write_variables(rc_runtime_progress_t* progress) { uint32_t count = 0; const rc_value_t* variable; + int result; for (variable = progress->runtime->variables; variable; variable = variable->next) ++count; if (count == 0) return RC_OK; + /* header + count + count(djb2,flags,value,prior,?cond) */ + if (progress->offset + 8 + 4 + count * 16 > progress->buffer_size) + return RC_INSUFFICIENT_BUFFER; + rc_runtime_progress_start_chunk(progress, RC_RUNTIME_CHUNK_VARIABLES); rc_runtime_progress_write_uint(progress, count); - for (variable = progress->runtime->variables; variable; variable = variable->next) - { + for (variable = progress->runtime->variables; variable; variable = variable->next) { uint32_t djb2 = rc_djb2(variable->name); + if (progress->offset + 16 > progress->buffer_size) + return RC_INSUFFICIENT_BUFFER; + rc_runtime_progress_write_uint(progress, djb2); - rc_runtime_progress_write_variable(progress, variable); + result = rc_runtime_progress_write_variable(progress, variable); + if (result != RC_OK) + return result; } rc_runtime_progress_end_chunk(progress); @@ -493,7 +530,7 @@ static int rc_runtime_progress_read_trigger(rc_runtime_progress_t* progress, rc_ static int rc_runtime_progress_write_achievements(rc_runtime_progress_t* progress) { uint32_t i; - int offset = 0; + int initial_offset = 0; int result; for (i = 0; i < progress->runtime->trigger_count; ++i) { @@ -511,7 +548,10 @@ static int rc_runtime_progress_write_achievements(rc_runtime_progress_t* progres continue; } - offset = progress->offset; + initial_offset = progress->offset; + } else { + if (progress->offset + runtime_trigger->serialized_size > progress->buffer_size) + return RC_INSUFFICIENT_BUFFER; } rc_runtime_progress_start_chunk(progress, RC_RUNTIME_CHUNK_ACHIEVEMENT); @@ -522,10 +562,15 @@ static int rc_runtime_progress_write_achievements(rc_runtime_progress_t* progres if (result != RC_OK) return result; + if (runtime_trigger->serialized_size) { + /* runtime_trigger->serialized_size includes the header */ + assert_chunk_size(runtime_trigger->serialized_size - 8); + } + rc_runtime_progress_end_chunk(progress); if (!progress->buffer) - runtime_trigger->serialized_size = progress->offset - offset; + runtime_trigger->serialized_size = progress->offset - initial_offset; } return RC_OK; @@ -556,7 +601,7 @@ static int rc_runtime_progress_write_leaderboards(rc_runtime_progress_t* progres { uint32_t i; uint32_t flags; - int offset = 0; + int initial_offset = 0; int result; for (i = 0; i < progress->runtime->lboard_count; ++i) { @@ -574,7 +619,10 @@ static int rc_runtime_progress_write_leaderboards(rc_runtime_progress_t* progres continue; } - offset = progress->offset; + initial_offset = progress->offset; + } else { + if (progress->offset + runtime_lboard->serialized_size > progress->buffer_size) + return RC_INSUFFICIENT_BUFFER; } rc_runtime_progress_start_chunk(progress, RC_RUNTIME_CHUNK_LEADERBOARD); @@ -600,10 +648,15 @@ static int rc_runtime_progress_write_leaderboards(rc_runtime_progress_t* progres if (result != RC_OK) return result; + if (runtime_lboard->serialized_size) { + /* runtime_lboard->serialized_size includes the header */ + assert_chunk_size(runtime_lboard->serialized_size - 8); + } + rc_runtime_progress_end_chunk(progress); if (!progress->buffer) - runtime_lboard->serialized_size = progress->offset - offset; + runtime_lboard->serialized_size = progress->offset - initial_offset; } return RC_OK; @@ -663,6 +716,9 @@ static int rc_runtime_progress_write_rich_presence(rc_runtime_progress_t* progre if (!display->next) return RC_OK; + if (progress->offset + 8 + 16 > progress->buffer_size) + return RC_INSUFFICIENT_BUFFER; + rc_runtime_progress_start_chunk(progress, RC_RUNTIME_CHUNK_RICHPRESENCE); rc_runtime_progress_write_md5(progress, progress->runtime->richpresence->md5); @@ -705,6 +761,9 @@ static int rc_runtime_progress_serialize_internal(rc_runtime_progress_t* progres uint8_t md5[16]; int result; + if (progress->buffer_size < RC_RUNTIME_MIN_BUFFER_SIZE) + return RC_INSUFFICIENT_BUFFER; + rc_runtime_progress_write_uint(progress, RC_RUNTIME_MARKER); if ((result = rc_runtime_progress_write_memrefs(progress)) != RC_OK) @@ -722,6 +781,9 @@ static int rc_runtime_progress_serialize_internal(rc_runtime_progress_t* progres if ((result = rc_runtime_progress_write_rich_presence(progress)) != RC_OK) return result; + if (progress->offset + 8 + 16 > progress->buffer_size) + return RC_INSUFFICIENT_BUFFER; + rc_runtime_progress_write_uint(progress, RC_RUNTIME_CHUNK_DONE); rc_runtime_progress_write_uint(progress, 16); @@ -736,12 +798,13 @@ static int rc_runtime_progress_serialize_internal(rc_runtime_progress_t* progres return RC_OK; } -int rc_runtime_progress_size(const rc_runtime_t* runtime, lua_State* L) +uint32_t rc_runtime_progress_size(const rc_runtime_t* runtime, lua_State* L) { rc_runtime_progress_t progress; int result; rc_runtime_progress_init(&progress, runtime, L); + progress.buffer_size = 0xFFFFFFFF; result = rc_runtime_progress_serialize_internal(&progress); if (result != RC_OK) @@ -751,6 +814,11 @@ int rc_runtime_progress_size(const rc_runtime_t* runtime, lua_State* L) } int rc_runtime_serialize_progress(void* buffer, const rc_runtime_t* runtime, lua_State* L) +{ + return rc_runtime_serialize_progress_sized(buffer, 0xFFFFFFFF, runtime, L); +} + +int rc_runtime_serialize_progress_sized(uint8_t* buffer, uint32_t buffer_size, const rc_runtime_t* runtime, lua_State* L) { rc_runtime_progress_t progress; @@ -759,11 +827,17 @@ int rc_runtime_serialize_progress(void* buffer, const rc_runtime_t* runtime, lua rc_runtime_progress_init(&progress, runtime, L); progress.buffer = (uint8_t*)buffer; + progress.buffer_size = buffer_size; return rc_runtime_progress_serialize_internal(&progress); } int rc_runtime_deserialize_progress(rc_runtime_t* runtime, const uint8_t* serialized, lua_State* L) +{ + return rc_runtime_deserialize_progress_sized(runtime, serialized, 0xFFFFFFFF, L); +} + +int rc_runtime_deserialize_progress_sized(rc_runtime_t* runtime, const uint8_t* serialized, uint32_t serialized_size, lua_State* L) { rc_runtime_progress_t progress; md5_state_t state; @@ -775,9 +849,9 @@ int rc_runtime_deserialize_progress(rc_runtime_t* runtime, const uint8_t* serial int seen_rich_presence = 0; int result = RC_OK; - if (!serialized) { + if (!serialized || serialized_size < RC_RUNTIME_MIN_BUFFER_SIZE) { rc_runtime_reset(runtime); - return RC_INVALID_STATE; + return RC_INSUFFICIENT_BUFFER; } rc_runtime_progress_init(&progress, runtime, L); @@ -813,12 +887,21 @@ int rc_runtime_deserialize_progress(rc_runtime_t* runtime, const uint8_t* serial } do { + if (progress.offset + 8 >= serialized_size) { + result = RC_INSUFFICIENT_BUFFER; + break; + } + chunk_id = rc_runtime_progress_read_uint(&progress); chunk_size = rc_runtime_progress_read_uint(&progress); next_chunk_offset = progress.offset + chunk_size; - switch (chunk_id) - { + if (next_chunk_offset > serialized_size) { + result = RC_INSUFFICIENT_BUFFER; + break; + } + + switch (chunk_id) { case RC_RUNTIME_CHUNK_MEMREFS: result = rc_runtime_progress_read_memrefs(&progress); break; diff --git a/deps/rcheevos/src/rcheevos/value.c b/deps/rcheevos/src/rcheevos/value.c index 3662fc3649..dc91df6b7d 100644 --- a/deps/rcheevos/src/rcheevos/value.c +++ b/deps/rcheevos/src/rcheevos/value.c @@ -683,9 +683,14 @@ static int rc_typed_value_compare_floats(float f1, float f2, char oper) { } int rc_typed_value_compare(const rc_typed_value_t* value1, const rc_typed_value_t* value2, char oper) { - rc_typed_value_t converted_value2; - if (value2->type != value1->type) - value2 = rc_typed_value_convert_into(&converted_value2, value2, value1->type); + rc_typed_value_t converted_value; + if (value2->type != value1->type) { + /* if either side is a float, convert both sides to float. otherwise, assume the signed-ness of the left side. */ + if (value2->type == RC_VALUE_TYPE_FLOAT) + value1 = rc_typed_value_convert_into(&converted_value, value1, value2->type); + else + value2 = rc_typed_value_convert_into(&converted_value, value2, value1->type); + } switch (value1->type) { case RC_VALUE_TYPE_UNSIGNED: diff --git a/deps/rcheevos/src/rhash/hash.c b/deps/rcheevos/src/rhash/hash.c index 5fd1a97257..8f7bc958a1 100644 --- a/deps/rcheevos/src/rhash/hash.c +++ b/deps/rcheevos/src/rhash/hash.c @@ -528,7 +528,7 @@ static int rc_hash_cd_file(md5_state_t* md5, void* track_handle, uint32_t sector verbose_message_callback(message); } - if (size < (unsigned)num_read) + if (size < (unsigned)num_read) /* we read a whole sector - only hash the part containing file data */ num_read = (size_t)size; do @@ -834,12 +834,18 @@ static int rc_hash_zip_file(md5_state_t* md5, void* file_handle) uint32_t filename_len = RC_ZIP_READ_LE16(cdir + 0x1C); int32_t extra_len = RC_ZIP_READ_LE16(cdir + 0x1E); int32_t comment_len = RC_ZIP_READ_LE16(cdir + 0x20); + int32_t external_attr = RC_ZIP_READ_LE16(cdir + 0x26); uint64_t local_hdr_ofs = RC_ZIP_READ_LE32(cdir + 0x2A); cdir_entry_len = cdirhdr_size + filename_len + extra_len + comment_len; if (signature != 0x02014b50) /* expected central directory entry signature */ break; + /* Ignore records describing a directory (we only hash file records) */ + name = (cdir + cdirhdr_size); + if (name[filename_len - 1] == '/' || name[filename_len - 1] == '\\' || (external_attr & 0x10)) + continue; + /* Handle Zip64 fields */ if (decomp_size == 0xFFFFFFFF || comp_size == 0xFFFFFFFF || local_hdr_ofs == 0xFFFFFFFF) { @@ -893,7 +899,7 @@ static int rc_hash_zip_file(md5_state_t* md5, void* file_handle) hashindex++; /* Convert and store the file name in the hash data buffer */ - for (name = (cdir + cdirhdr_size), name_end = name + filename_len; name != name_end; name++) + for (name_end = name + filename_len; name != name_end; name++) { *(hashdata++) = (*name == '\\' ? '/' : /* convert back-slashes to regular slashes */ @@ -1717,7 +1723,7 @@ static int rc_hash_nintendo_3ds_ncch(md5_state_t* md5, void* file_handle, uint8_ } AES_init_ctx(&ncch_aes, primary_key); - AES_CTR_xcrypt_buffer(&ncch_aes, &hash_buffer[exefs_section_offset], exefs_section_size); + AES_CTR_xcrypt_buffer(&ncch_aes, &hash_buffer[exefs_section_offset], (size_t)exefs_section_size); } } } diff --git a/gfx/gfx_widgets.h b/gfx/gfx_widgets.h index b86391fb5c..52dec19db5 100644 --- a/gfx/gfx_widgets.h +++ b/gfx/gfx_widgets.h @@ -391,6 +391,7 @@ void gfx_widgets_set_challenge_display(unsigned id, const char* badge); void gfx_widgets_clear_challenge_displays(void); void gfx_widget_set_achievement_progress(const char* badge, const char* progress); void gfx_widget_set_cheevos_disconnect(bool visible); +void gfx_widget_set_cheevos_set_loading(bool visible); #endif /* TODO/FIXME/WARNING: Not thread safe! */ diff --git a/gfx/widgets/gfx_widget_leaderboard_display.c b/gfx/widgets/gfx_widget_leaderboard_display.c index 6383a30108..388080c6f5 100644 --- a/gfx/widgets/gfx_widget_leaderboard_display.c +++ b/gfx/widgets/gfx_widget_leaderboard_display.c @@ -66,6 +66,7 @@ struct gfx_widget_leaderboard_display_state unsigned challenge_count; uint16_t char_width[CHEEVO_LBOARD_LAST_FIXED_CHAR - CHEEVO_LBOARD_FIRST_FIXED_CHAR + 1]; uint16_t fixed_char_width; + uint16_t loading; bool disconnected; }; @@ -115,6 +116,7 @@ static void gfx_widget_leaderboard_display_frame(void* data, void* userdata) if (state->tracker_count == 0 && state->challenge_count == 0 && state->progress_tracker.show_until == 0 && + !state->loading && !state->disconnected) return; @@ -342,9 +344,10 @@ static void gfx_widget_leaderboard_display_frame(void* data, void* userdata) } } - if (state->disconnected) + if (state->disconnected || state->loading) { - const char* disconnected_text = "! RA !"; + char loading_buffer[8] = "RA ..."; + const char* disconnected_text = state->disconnected ? "! RA !" : loading_buffer; const unsigned disconnect_widget_width = font_driver_get_message_width( state->dispwidget_ptr->gfx_widget_fonts.msg_queue.font, disconnected_text, 0, 1) + CHEEVO_LBOARD_DISPLAY_PADDING * 2; @@ -353,6 +356,13 @@ static void gfx_widget_leaderboard_display_frame(void* data, void* userdata) x = video_width - disconnect_widget_width - spacing; y -= disconnect_widget_height + spacing; + if (state->loading) { + const uint16_t loading_shift = 5; + loading_buffer[((state->loading - 1) >> loading_shift) + 3] = '\0'; + state->loading &= (1 << (loading_shift + 2)) - 1; + ++state->loading; + } + /* Backdrop */ gfx_display_draw_quad( p_disp, @@ -586,6 +596,12 @@ void gfx_widget_set_cheevos_disconnect(bool value) state->disconnected = value; } +void gfx_widget_set_cheevos_set_loading(bool value) +{ + gfx_widget_leaderboard_display_state_t* state = &p_w_leaderboard_display_st; + state->loading = value ? 1 : 0; +} + const gfx_widget_t gfx_widget_leaderboard_display = { &gfx_widget_leaderboard_display_init, diff --git a/griffin/griffin.c b/griffin/griffin.c index 426e249e93..e1bb908f58 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -194,6 +194,7 @@ ACHIEVEMENTS /* Gekko (Wii) and 3DS use custom pthread wrappers (see rthreads.c) */ #define RC_NO_THREADS 1 #endif +#define RC_CLIENT_SUPPORTS_HASH 1 #include "../libretro-common/formats/cdfs/cdfs.c" #include "../network/net_http_special.c" diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index 9790c7de38..7f9ad8ec18 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -10152,6 +10152,18 @@ MSG_HASH( MENU_ENUM_LABEL_CHEEVOS_SERVER_RECONNECTED, "All pending requests have succesfully been synced to the RetroAchievements server." ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_CHEEVOS_IDENTIFYING_GAME, + "Identifying game" +) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_CHEEVOS_FETCHING_GAME_DATA, + "Fetching game data" +) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_CHEEVOS_STARTING_SESSION, + "Starting session" +) MSG_HASH( MENU_ENUM_LABEL_VALUE_NOT_LOGGED_IN, "Not logged in" diff --git a/msg_hash.h b/msg_hash.h index d0695ea789..3690c9fc63 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -3062,6 +3062,9 @@ enum msg_hash_enums MENU_LABEL(ACHIEVEMENT_SERVER_UNREACHABLE), MENU_LABEL(CHEEVOS_SERVER_DISCONNECTED), MENU_LABEL(CHEEVOS_SERVER_RECONNECTED), + MENU_LABEL(CHEEVOS_IDENTIFYING_GAME), + MENU_LABEL(CHEEVOS_FETCHING_GAME_DATA), + MENU_LABEL(CHEEVOS_STARTING_SESSION), MENU_LABEL(CORE_INFORMATION), MENU_LABEL(DISC_INFORMATION), MENU_LABEL(CORE_LOCK), diff --git a/runloop.c b/runloop.c index 86e28f67e8..09c616fec2 100644 --- a/runloop.c +++ b/runloop.c @@ -6983,6 +6983,10 @@ int runloop_iterate(void) netplay_driver_ctl(RARCH_NETPLAY_CTL_PAUSE, NULL); #endif #endif +#ifdef HAVE_CHEEVOS + if (cheevos_enable) + rcheevos_idle(); +#endif #ifdef HAVE_MENU /* Rely on vsync throttling unless VRR is enabled and menu throttle is disabled. */ if (vrr_runloop_enable && !settings->bools.menu_throttle_framerate) @@ -6998,10 +7002,6 @@ int runloop_iterate(void) : settings->floats.video_refresh_rate)); else runloop_set_frame_limit(&video_st->av_info, settings->floats.fastforward_ratio); -#endif -#ifdef HAVE_CHEEVOS - if (cheevos_enable) - rcheevos_idle(); #endif goto end; case RUNLOOP_STATE_ITERATE: