From 1a2ad89a1731307d0676b45b5ce1785f790dc957 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Mon, 24 Jun 2024 11:27:43 +1000 Subject: [PATCH] dep/rcheevos: Bump to d54cf8f --- dep/rcheevos/include/rc_api_editor.h | 6 + dep/rcheevos/include/rc_api_info.h | 45 +++ dep/rcheevos/include/rc_api_user.h | 3 + dep/rcheevos/include/rc_client.h | 30 ++ .../include/rc_client_raintegration.h | 13 +- dep/rcheevos/include/rc_error.h | 5 +- dep/rcheevos/include/rc_runtime.h | 8 +- dep/rcheevos/include/rc_runtime_types.h | 13 +- dep/rcheevos/src/rapi/rc_api_common.c | 88 +++-- dep/rcheevos/src/rapi/rc_api_common.h | 2 + dep/rcheevos/src/rapi/rc_api_info.c | 90 ++++++ dep/rcheevos/src/rapi/rc_api_runtime.c | 25 +- dep/rcheevos/src/rc_client.c | 303 +++++++++++++----- dep/rcheevos/src/rc_client_external.h | 5 +- dep/rcheevos/src/rc_client_internal.h | 2 + dep/rcheevos/src/rc_client_raintegration.c | 57 +++- .../src/rc_client_raintegration_internal.h | 5 + dep/rcheevos/src/rc_libretro.c | 3 +- dep/rcheevos/src/rc_util.c | 3 + dep/rcheevos/src/rc_version.h | 2 +- dep/rcheevos/src/rcheevos/condition.c | 32 +- dep/rcheevos/src/rcheevos/condset.c | 17 + dep/rcheevos/src/rcheevos/consoleinfo.c | 101 ++++-- dep/rcheevos/src/rcheevos/memref.c | 37 +++ dep/rcheevos/src/rcheevos/operand.c | 57 ++++ dep/rcheevos/src/rcheevos/rc_internal.h | 16 +- dep/rcheevos/src/rcheevos/rc_validate.c | 230 ++++++++++--- dep/rcheevos/src/rcheevos/runtime_progress.c | 132 ++++++-- dep/rcheevos/src/rcheevos/value.c | 95 +++++- dep/rcheevos/src/rhash/hash.c | 123 +++++-- 30 files changed, 1275 insertions(+), 273 deletions(-) diff --git a/dep/rcheevos/include/rc_api_editor.h b/dep/rcheevos/include/rc_api_editor.h index 2a6df339e..657fa3f0e 100644 --- a/dep/rcheevos/include/rc_api_editor.h +++ b/dep/rcheevos/include/rc_api_editor.h @@ -43,6 +43,7 @@ typedef struct rc_api_fetch_code_notes_response_t { rc_api_fetch_code_notes_response_t; RC_EXPORT int RC_CCONV rc_api_init_fetch_code_notes_request(rc_api_request_t* request, const rc_api_fetch_code_notes_request_t* api_params); +/* [deprecated] use rc_api_process_fetch_code_notes_server_response instead */ RC_EXPORT int RC_CCONV rc_api_process_fetch_code_notes_response(rc_api_fetch_code_notes_response_t* response, const char* server_response); RC_EXPORT int RC_CCONV rc_api_process_fetch_code_notes_server_response(rc_api_fetch_code_notes_response_t* response, const rc_api_server_response_t* server_response); RC_EXPORT void RC_CCONV rc_api_destroy_fetch_code_notes_response(rc_api_fetch_code_notes_response_t* response); @@ -76,6 +77,7 @@ typedef struct rc_api_update_code_note_response_t { rc_api_update_code_note_response_t; RC_EXPORT int RC_CCONV rc_api_init_update_code_note_request(rc_api_request_t* request, const rc_api_update_code_note_request_t* api_params); +/* [deprecated] use rc_api_process_update_code_note_server_response instead */ RC_EXPORT int RC_CCONV rc_api_process_update_code_note_response(rc_api_update_code_note_response_t* response, const char* server_response); RC_EXPORT int RC_CCONV rc_api_process_update_code_note_server_response(rc_api_update_code_note_response_t* response, const rc_api_server_response_t* server_response); RC_EXPORT void RC_CCONV rc_api_destroy_update_code_note_response(rc_api_update_code_note_response_t* response); @@ -124,6 +126,7 @@ typedef struct rc_api_update_achievement_response_t { rc_api_update_achievement_response_t; RC_EXPORT int RC_CCONV rc_api_init_update_achievement_request(rc_api_request_t* request, const rc_api_update_achievement_request_t* api_params); +/* [deprecated] use rc_api_process_update_achievement_server_response instead */ RC_EXPORT int RC_CCONV rc_api_process_update_achievement_response(rc_api_update_achievement_response_t* response, const char* server_response); RC_EXPORT int RC_CCONV rc_api_process_update_achievement_server_response(rc_api_update_achievement_response_t* response, const rc_api_server_response_t* server_response); RC_EXPORT void RC_CCONV rc_api_destroy_update_achievement_response(rc_api_update_achievement_response_t* response); @@ -174,6 +177,7 @@ typedef struct rc_api_update_leaderboard_response_t { rc_api_update_leaderboard_response_t; RC_EXPORT int RC_CCONV rc_api_init_update_leaderboard_request(rc_api_request_t* request, const rc_api_update_leaderboard_request_t* api_params); +/* [deprecated] use rc_api_process_update_leaderboard_server_response instead */ RC_EXPORT int RC_CCONV rc_api_process_update_leaderboard_response(rc_api_update_leaderboard_response_t* response, const char* server_response); RC_EXPORT int RC_CCONV rc_api_process_update_leaderboard_server_response(rc_api_update_leaderboard_response_t* response, const rc_api_server_response_t* server_response); RC_EXPORT void RC_CCONV rc_api_destroy_update_leaderboard_response(rc_api_update_leaderboard_response_t* response); @@ -204,6 +208,7 @@ typedef struct rc_api_fetch_badge_range_response_t { rc_api_fetch_badge_range_response_t; RC_EXPORT int RC_CCONV rc_api_init_fetch_badge_range_request(rc_api_request_t* request, const rc_api_fetch_badge_range_request_t* api_params); +/* [deprecated] use rc_api_process_fetch_badge_range_server_response instead */ RC_EXPORT int RC_CCONV rc_api_process_fetch_badge_range_response(rc_api_fetch_badge_range_response_t* response, const char* server_response); RC_EXPORT int RC_CCONV rc_api_process_fetch_badge_range_server_response(rc_api_fetch_badge_range_response_t* response, const rc_api_server_response_t* server_response); RC_EXPORT void RC_CCONV rc_api_destroy_fetch_badge_range_response(rc_api_fetch_badge_range_response_t* response); @@ -244,6 +249,7 @@ typedef struct rc_api_add_game_hash_response_t { rc_api_add_game_hash_response_t; RC_EXPORT int RC_CCONV rc_api_init_add_game_hash_request(rc_api_request_t* request, const rc_api_add_game_hash_request_t* api_params); +/* [deprecated] use rc_api_process_add_game_hash_server_response instead */ RC_EXPORT int RC_CCONV rc_api_process_add_game_hash_response(rc_api_add_game_hash_response_t* response, const char* server_response); RC_EXPORT int RC_CCONV rc_api_process_add_game_hash_server_response(rc_api_add_game_hash_response_t* response, const rc_api_server_response_t* server_response); RC_EXPORT void RC_CCONV rc_api_destroy_add_game_hash_response(rc_api_add_game_hash_response_t* response); diff --git a/dep/rcheevos/include/rc_api_info.h b/dep/rcheevos/include/rc_api_info.h index 93b652bf7..b947f256c 100644 --- a/dep/rcheevos/include/rc_api_info.h +++ b/dep/rcheevos/include/rc_api_info.h @@ -62,6 +62,7 @@ typedef struct rc_api_fetch_achievement_info_response_t { rc_api_fetch_achievement_info_response_t; 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); @@ -134,6 +135,7 @@ typedef struct rc_api_fetch_leaderboard_info_response_t { rc_api_fetch_leaderboard_info_response_t; 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); @@ -173,10 +175,53 @@ typedef struct rc_api_fetch_games_list_response_t { rc_api_fetch_games_list_response_t; 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); +/* --- Fetch Game Titles --- */ + +/** + * API parameters for a fetch games list request. + */ +typedef struct rc_api_fetch_game_titles_request_t { + /* An array of game ids to fetch titles for */ + const uint32_t* game_ids; + /* The number of items in the game_ids array */ + uint32_t num_game_ids; +} +rc_api_fetch_game_titles_request_t; + +/* A game title entry */ +typedef struct rc_api_game_title_entry_t { + /* The unique identifier of the game */ + uint32_t id; + /* The title of the game */ + const char* title; + /* The image name for the game badge */ + const char* image_name; +} +rc_api_game_title_entry_t; + +/** + * Response data for a fetch games title request. + */ +typedef struct rc_api_fetch_game_titles_response_t { + /* An array of requested entries */ + rc_api_game_title_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_game_titles_response_t; + +RC_EXPORT int RC_CCONV rc_api_init_fetch_game_titles_request(rc_api_request_t* request, const rc_api_fetch_game_titles_request_t* api_params); +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); + 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 c06cec445..f8e4dedde 100644 --- a/dep/rcheevos/include/rc_api_user.h +++ b/dep/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/dep/rcheevos/include/rc_client.h b/dep/rcheevos/include/rc_client.h index 9631b52dd..1fa50357c 100644 --- a/dep/rcheevos/include/rc_client.h +++ b/dep/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. @@ -249,6 +251,11 @@ enum { RC_CLIENT_LOAD_GAME_STATE_ABORTED }; +/** + * Determines if a game was successfully identified and loaded. + */ +RC_EXPORT int RC_CCONV rc_client_is_game_loaded(const rc_client_t* client); + /** * Unloads the current game. */ @@ -264,6 +271,7 @@ typedef struct rc_client_game_t { /** * Get information about the current game. Returns NULL if no game is loaded. + * NOTE: returns a dummy game record if an unidentified game is loaded. */ RC_EXPORT const rc_client_game_t* RC_CCONV rc_client_get_game_info(const rc_client_t* client); @@ -273,11 +281,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 | @@ -677,15 +693,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); +/** + * 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/dep/rcheevos/include/rc_client_raintegration.h b/dep/rcheevos/include/rc_client_raintegration.h index 461129104..2aa33dfdb 100644 --- a/dep/rcheevos/include/rc_client_raintegration.h +++ b/dep/rcheevos/include/rc_client_raintegration.h @@ -27,6 +27,14 @@ typedef struct rc_client_raintegration_menu_t { uint32_t num_items; } rc_client_raintegration_menu_t; +enum { + RC_CLIENT_RAINTEGRATION_ACHIEVEMENT_STATE_NONE = 0, + RC_CLIENT_RAINTEGRATION_ACHIEVEMENT_STATE_PUBLISHED = 1, + RC_CLIENT_RAINTEGRATION_ACHIEVEMENT_STATE_LOCAL = 2, + RC_CLIENT_RAINTEGRATION_ACHIEVEMENT_STATE_MODIFIED = 3, + RC_CLIENT_RAINTEGRATION_ACHIEVEMENT_STATE_INSECURE = 4, +}; + enum { RC_CLIENT_RAINTEGRATION_EVENT_TYPE_NONE = 0, RC_CLIENT_RAINTEGRATION_EVENT_MENUITEM_CHECKED_CHANGED = 1, /* [menu_item] checked changed */ @@ -73,15 +81,18 @@ 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 void RC_CCONV rc_client_raintegration_set_console_id(rc_client_t* client, uint32_t console_id); 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); +RC_EXPORT int RC_CCONV rc_client_raintegration_get_achievement_state(const rc_client_t* client, uint32_t achievement_id); + #endif /* RC_CLIENT_SUPPORTS_RAINTEGRATION */ RC_END_C_DECLS diff --git a/dep/rcheevos/include/rc_error.h b/dep/rcheevos/include/rc_error.h index d0aca2fd7..171bbf8e9 100644 --- a/dep/rcheevos/include/rc_error.h +++ b/dep/rcheevos/include/rc_error.h @@ -45,7 +45,10 @@ 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_INVALID_VARIABLE_NAME = -37, + RC_UNKNOWN_VARIABLE_NAME = -38 }; RC_EXPORT const char* RC_CCONV rc_error_str(int ret); diff --git a/dep/rcheevos/include/rc_runtime.h b/dep/rcheevos/include/rc_runtime.h index c5780c47a..d778fde5c 100644 --- a/dep/rcheevos/include/rc_runtime.h +++ b/dep/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/dep/rcheevos/include/rc_runtime_types.h b/dep/rcheevos/include/rc_runtime_types.h index d8a7db65d..4bf1b13bf 100644 --- a/dep/rcheevos/include/rc_runtime_types.h +++ b/dep/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 }; @@ -104,7 +106,8 @@ enum { RC_OPERAND_LUA, /* A Lua function that provides the value. */ RC_OPERAND_PRIOR, /* The last differing value at this address. */ RC_OPERAND_BCD, /* The BCD-decoded value of a live address in RAM. */ - RC_OPERAND_INVERTED /* The twos-complement value of a live address in RAM. */ + RC_OPERAND_INVERTED, /* The twos-complement value of a live address in RAM. */ + RC_OPERAND_RECALL /* The value captured by the last RC_CONDITION_REMEMBER condition */ }; typedef struct rc_operand_t { @@ -152,6 +155,7 @@ enum { RC_CONDITION_ADD_SOURCE, /* everything from this point on affects the condition after it */ RC_CONDITION_SUB_SOURCE, RC_CONDITION_ADD_ADDRESS, + RC_CONDITION_REMEMBER, /* logic flags (second switch) */ RC_CONDITION_ADD_HITS, @@ -173,7 +177,10 @@ enum { RC_OPERATOR_MULT, RC_OPERATOR_DIV, RC_OPERATOR_AND, - RC_OPERATOR_XOR + RC_OPERATOR_XOR, + RC_OPERATOR_MOD, + RC_OPERATOR_ADD, + RC_OPERATOR_SUB }; typedef struct rc_condition_t rc_condition_t; @@ -284,6 +291,8 @@ RC_EXPORT void RC_CCONV rc_reset_trigger(rc_trigger_t* self); | Values | \*****************************************************************************/ +#define RC_VALUE_MAX_NAME_LENGTH 15 + struct rc_value_t { /* The current value of the variable. */ rc_memref_value_t value; diff --git a/dep/rcheevos/src/rapi/rc_api_common.c b/dep/rcheevos/src/rapi/rc_api_common.c index 407efe55f..f96daedce 100644 --- a/dep/rcheevos/src/rapi/rc_api_common.c +++ b/dep/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; - - 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; - } + rc_json_iterator_t iterator; + memset(&iterator, 0, sizeof(iterator)); + iterator.json = server_response->body; + iterator.end = server_response->body + server_response->body_length; + + /* if the title contains an HTTP status code(i.e "404 Not Found"), return the title */ + 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; + + while (iterator.json < iterator.end && *iterator.json != '\n' && + iterator.json - server_response->body < 200) { + ++iterator.json; + } - if (end > json && end[-1] == '\r') - --end; + if (iterator.json > server_response->body && iterator.json[-1] == '\r') + --iterator.json; - if (end > json) - response->error_message = rc_buffer_strncpy(&response->buffer, json, end - 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; @@ -915,6 +938,27 @@ int rc_json_get_required_bool(int* out, rc_api_response_t* response, const rc_js return rc_json_missing_field(response, field); } +void rc_json_extract_filename(rc_json_field_t* field) { + if (field->value_end) { + const char* str = field->value_end; + + /* remove the extension */ + while (str > field->value_start && str[-1] != '/') { + --str; + if (*str == '.') { + field->value_end = str; + break; + } + } + + /* find the path separator */ + while (str > field->value_start && str[-1] != '/') + --str; + + field->value_start = str; + } +} + /* --- rc_api_request --- */ void rc_api_destroy_request(rc_api_request_t* request) diff --git a/dep/rcheevos/src/rapi/rc_api_common.h b/dep/rcheevos/src/rapi/rc_api_common.h index 7311cfff5..538fdbba1 100644 --- a/dep/rcheevos/src/rapi/rc_api_common.h +++ b/dep/rcheevos/src/rapi/rc_api_common.h @@ -67,6 +67,8 @@ int rc_json_get_array_entry_object(rc_json_field_t* fields, size_t field_count, int rc_json_get_next_object_field(rc_json_iterator_t* iterator, rc_json_field_t* field); int rc_json_get_object_string_length(const char* json); +void rc_json_extract_filename(rc_json_field_t* field); + void rc_url_builder_append_encoded_str(rc_api_url_builder_t* builder, const char* str); void rc_url_builder_append_num_param(rc_api_url_builder_t* builder, const char* param, int32_t value); void rc_url_builder_append_unum_param(rc_api_url_builder_t* builder, const char* param, uint32_t value); diff --git a/dep/rcheevos/src/rapi/rc_api_info.c b/dep/rcheevos/src/rapi/rc_api_info.c index 2b9f88262..6f3822a2d 100644 --- a/dep/rcheevos/src/rapi/rc_api_info.c +++ b/dep/rcheevos/src/rapi/rc_api_info.c @@ -3,6 +3,8 @@ #include "rc_runtime_types.h" +#include "../rc_compat.h" + #include #include @@ -371,3 +373,91 @@ int rc_api_process_fetch_games_list_server_response(rc_api_fetch_games_list_resp void rc_api_destroy_fetch_games_list_response(rc_api_fetch_games_list_response_t* response) { rc_buffer_destroy(&response->response.buffer); } + +/* --- Fetch Game Titles --- */ + +int rc_api_init_fetch_game_titles_request(rc_api_request_t* request, const rc_api_fetch_game_titles_request_t* api_params) { + rc_api_url_builder_t builder; + char num[16]; + uint32_t i; + + rc_api_url_build_dorequest_url(request); + + if (api_params->num_game_ids == 0) + return RC_INVALID_STATE; + + rc_url_builder_init(&builder, &request->buffer, 48); + rc_url_builder_append_str_param(&builder, "r", "gameinfolist"); + rc_url_builder_append_unum_param(&builder, "g", api_params->game_ids[0]); + + for (i = 1; i < api_params->num_game_ids; i++) { + int chars = snprintf(num, sizeof(num), "%u", api_params->game_ids[i]); + rc_url_builder_append(&builder, ",", 1); + rc_url_builder_append(&builder, num, chars); + } + + request->post_data = rc_url_builder_finalize(&builder); + request->content_type = RC_CONTENT_TYPE_URLENCODED; + + return builder.result; +} + +int 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_api_game_title_entry_t* entry; + rc_json_iterator_t iterator; + rc_json_field_t array_field; + int result; + + 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("ID"), + RC_JSON_NEW_FIELD("Title"), + RC_JSON_NEW_FIELD("ImageIcon") + }; + + 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 (!rc_json_get_required_array(&response->num_entries, &array_field, &response->response, &fields[2], "Response")) + return RC_MISSING_VALUE; + + if (response->num_entries) { + response->entries = (rc_api_game_title_entry_t*)rc_buffer_alloc(&response->response.buffer, response->num_entries * sizeof(rc_api_game_title_entry_t)); + if (!response->entries) + return RC_OUT_OF_MEMORY; + + memset(&iterator, 0, sizeof(iterator)); + iterator.json = array_field.value_start; + iterator.end = array_field.value_end; + + entry = response->entries; + while (rc_json_get_array_entry_object(entry_fields, sizeof(entry_fields) / sizeof(entry_fields[0]), &iterator)) { + if (!rc_json_get_required_unum(&entry->id, &response->response, &entry_fields[0], "ID")) + return RC_MISSING_VALUE; + if (!rc_json_get_required_string(&entry->title, &response->response, &entry_fields[1], "Title")) + return RC_MISSING_VALUE; + + /* ImageIcon will be '/Images/0123456.png' - only return the '0123456' */ + rc_json_extract_filename(&entry_fields[2]); + if (!rc_json_get_required_string(&entry->image_name, &response->response, &entry_fields[2], "ImageIcon")) + return RC_MISSING_VALUE; + + ++entry; + } + } + + return RC_OK; +} + +void rc_api_destroy_fetch_game_titles_response(rc_api_fetch_game_titles_response_t* response) { + rc_buffer_destroy(&response->response.buffer); +} diff --git a/dep/rcheevos/src/rapi/rc_api_runtime.c b/dep/rcheevos/src/rapi/rc_api_runtime.c index 4f3bc5b57..1a183f214 100644 --- a/dep/rcheevos/src/rapi/rc_api_runtime.c +++ b/dep/rcheevos/src/rapi/rc_api_runtime.c @@ -111,7 +111,6 @@ int rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_respon rc_api_leaderboard_definition_t* leaderboard; rc_json_field_t array_field; rc_json_iterator_t iterator; - const char* str; const char* last_author = ""; const char* last_author_field = ""; size_t last_author_len = 0; @@ -180,17 +179,7 @@ int rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_respon return RC_MISSING_VALUE; /* ImageIcon will be '/Images/0123456.png' - only return the '0123456' */ - if (patchdata_fields[3].value_end) { - str = patchdata_fields[3].value_end - 5; - if (memcmp(str, ".png\"", 5) == 0) { - patchdata_fields[3].value_end -= 5; - - while (str > patchdata_fields[3].value_start && str[-1] != '/') - --str; - - patchdata_fields[3].value_start = str; - } - } + rc_json_extract_filename(&patchdata_fields[3]); rc_json_get_optional_string(&response->image_name, &response->response, &patchdata_fields[3], "ImageIcon", ""); /* estimate the amount of space necessary to store the rich presence script, achievements, and leaderboards. @@ -248,9 +237,15 @@ int rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_respon if (!rc_json_get_required_string(&achievement->author, &response->response, &achievement_fields[6], "Author")) return RC_MISSING_VALUE; - last_author = achievement->author; - last_author_field = achievement_fields[6].value_start; - last_author_len = len; + if (achievement->author == NULL) { + /* ensure we don't pass NULL out to client */ + last_author = achievement->author = ""; + last_author_len = 0; + } else { + last_author = achievement->author; + last_author_field = achievement_fields[6].value_start; + last_author_len = len; + } } if (!rc_json_get_required_unum(&timet, &response->response, &achievement_fields[8], "Created")) diff --git a/dep/rcheevos/src/rc_client.c b/dep/rcheevos/src/rc_client.c index fe593392c..17f66a850 100644 --- a/dep/rcheevos/src/rc_client.c +++ b/dep/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) { @@ -871,7 +884,7 @@ void rc_client_get_user_game_summary(const rc_client_t* client, rc_client_user_g } #endif - if (!client->game) + if (!rc_client_is_game_loaded(client)) return; rc_mutex_lock((rc_mutex_t*)&client->state.mutex); /* remove const cast for mutex access */ @@ -1401,6 +1414,18 @@ 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; @@ -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 */ @@ -1917,13 +1947,13 @@ static void rc_client_fetch_game_data_callback(const rc_api_server_response_t* s if (!subset->public_.title) { const char* core_subset_title = rc_client_subset_extract_title(load_state->game, load_state->game->public_.title); if (core_subset_title) { - rc_client_subset_info_t* scan = load_state->game->subsets; - for (; scan; scan = scan->next) { - if (scan->public_.title == load_state->game->public_.title) { - scan->public_.title = core_subset_title; - break; - } - } + scan = load_state->game->subsets; + for (; scan; scan = scan->next) { + if (scan->public_.title == load_state->game->public_.title) { + scan->public_.title = core_subset_title; + break; + } + } } subset->public_.title = rc_buffer_strcpy(&load_state->game->buffer, fetch_game_data_response.title); @@ -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)) { @@ -2021,10 +2052,21 @@ static void rc_client_begin_fetch_game_data(rc_client_load_state_t* load_state) } } } +#else + load_state->game->public_.console_id = RC_CONSOLE_UNKNOWN; + load_state->game->public_.hash = load_state->hash->hash; +#endif /* RC_CLIENT_SUPPORTS_HASH */ if (load_state->hash->game_id == 0) { + rc_client_subset_info_t* subset; + + subset = (rc_client_subset_info_t*)rc_buffer_alloc(&load_state->game->buffer, sizeof(rc_client_subset_info_t)); + memset(subset, 0, sizeof(*subset)); + subset->public_.title = ""; + load_state->game->public_.title = "Unknown Game"; load_state->game->public_.badge_name = ""; + load_state->game->subsets = subset; client->game = load_state->game; load_state->game = NULL; @@ -2229,14 +2271,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; @@ -2269,6 +2303,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, @@ -2352,6 +2396,8 @@ 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; @@ -2366,6 +2412,23 @@ int rc_client_get_load_game_state(const rc_client_t* client) return state; } +int rc_client_is_game_loaded(const rc_client_t* client) +{ + const rc_client_game_t* game; + + if (!client) + return 0; + +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->get_game_info) + game = client->state.external_client->get_game_info(); + else +#endif + game = client->game ? &client->game->public_ : NULL; + + return (game && game->id != 0); +} + 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; @@ -2454,8 +2517,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); } @@ -2463,13 +2528,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); } @@ -2501,22 +2572,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) { @@ -2547,12 +2661,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) { @@ -2639,53 +2749,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; + if (!hash || !hash[0]) { + callback(RC_INVALID_STATE, "hash is required", client, callback_userdata); + return NULL; + } - 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; - } +#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 - 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; - } + 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); - callback_data->callback = callback; - callback_data->callback_userdata = callback_userdata; - callback_data->client = client; - callback_data->hash = game_hash; - callback_data->game = game; + 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; + } - 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); + pending_media->hash = strdup(hash); + pending_media->callback = callback; + pending_media->callback_userdata = callback_userdata; - rc_api_destroy_request(&request); + client->state.load->pending_media = pending_media; + } + } else { + game = client->game; + } + rc_mutex_unlock(&client->state.mutex); - /* if handle is no longer valid, the async operation completed synchronously */ - return rc_client_async_handle_valid(client, async_handle) ? async_handle : NULL; + 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) @@ -2726,7 +2861,7 @@ rc_client_async_handle_t* rc_client_begin_load_subset(rc_client_t* client, uint3 return client->state.external_client->begin_load_subset(client, subset_id, callback, callback_userdata); #endif - if (!client->game) { + if (!rc_client_is_game_loaded(client)) { callback(RC_NO_GAME_LOADED, rc_error_str(RC_NO_GAME_LOADED), client, callback_userdata); return NULL; } @@ -5249,7 +5384,7 @@ size_t rc_client_progress_size(rc_client_t* client) return client->state.external_client->progress_size(); #endif - if (!client->game) + if (!rc_client_is_game_loaded(client)) return 0; rc_mutex_lock(&client->state.mutex); @@ -5260,6 +5395,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; @@ -5268,17 +5408,17 @@ 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) + if (!rc_client_is_game_loaded(client)) return RC_NO_GAME_LOADED; if (!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; @@ -5378,6 +5518,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; @@ -5387,10 +5532,10 @@ 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) + if (!rc_client_is_game_loaded(client)) return RC_NO_GAME_LOADED; rc_mutex_lock(&client->state.mutex); @@ -5407,7 +5552,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/dep/rcheevos/src/rc_client_external.h b/dep/rcheevos/src/rc_client_external.h index a519e428e..82ec1d49d 100644 --- a/dep/rcheevos/src/rc_client_external.h +++ b/dep/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/dep/rcheevos/src/rc_client_internal.h b/dep/rcheevos/src/rc_client_internal.h index de8258630..a27da6588 100644 --- a/dep/rcheevos/src/rc_client_internal.h +++ b/dep/rcheevos/src/rc_client_internal.h @@ -368,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 { diff --git a/dep/rcheevos/src/rc_client_raintegration.c b/dep/rcheevos/src/rc_client_raintegration.c index 227d03a84..a686f7fd3 100644 --- a/dep/rcheevos/src/rc_client_raintegration.c +++ b/dep/rcheevos/src/rc_client_raintegration.c @@ -69,6 +69,7 @@ static void rc_client_raintegration_load_dll(rc_client_t* client, raintegration->get_host_url = (rc_client_raintegration_get_string_func_t)GetProcAddress(hDLL, "_RA_HostUrl"); raintegration->init_client = (rc_client_raintegration_init_client_func_t)GetProcAddress(hDLL, "_RA_InitClient"); raintegration->init_client_offline = (rc_client_raintegration_init_client_func_t)GetProcAddress(hDLL, "_RA_InitOffline"); + raintegration->set_console_id = (rc_client_raintegration_set_int_func_t)GetProcAddress(hDLL, "_RA_SetConsoleID"); raintegration->shutdown = (rc_client_raintegration_action_func_t)GetProcAddress(hDLL, "_RA_Shutdown"); raintegration->update_main_window_handle = (rc_client_raintegration_hwnd_action_func_t)GetProcAddress(hDLL, "_RA_UpdateHWnd"); @@ -80,6 +81,7 @@ static void rc_client_raintegration_load_dll(rc_client_t* client, 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"); + raintegration->get_achievement_state = (rc_client_raintegration_get_achievement_state_func_t)GetProcAddress(hDLL, "_Rcheevos_GetAchievementState"); if (!raintegration->get_version || !raintegration->init_client || @@ -204,6 +206,7 @@ static void rc_client_init_raintegration(rc_client_t* client, /* attach the external client and call the callback */ client->state.external_client = external_client; + client->state.raintegration->hMainWindow = version_validation_callback_data->main_window_handle; client->state.raintegration->bIsInited = 1; version_validation_callback_data->callback(RC_OK, NULL, @@ -352,12 +355,14 @@ rc_client_async_handle_t* rc_client_begin_load_raintegration(rc_client_t* client void rc_client_raintegration_update_main_window_handle(rc_client_t* client, HWND main_window_handle) { - if (client && client->state.raintegration && - client->state.raintegration->bIsInited && - client->state.raintegration->update_main_window_handle) - { + if (client && client->state.raintegration) { + client->state.raintegration->hMainWindow = main_window_handle; + + if (client->state.raintegration->bIsInited && + client->state.raintegration->update_main_window_handle) { client->state.raintegration->update_main_window_handle(main_window_handle); - } + } + } } void rc_client_raintegration_set_write_memory_function(rc_client_t* client, rc_client_raintegration_write_memory_func_t handler) @@ -383,26 +388,41 @@ const rc_client_raintegration_menu_t* rc_client_raintegration_get_menu(const rc_ { if (!client || !client->state.raintegration || !client->state.raintegration->bIsInited || - !client->state.raintegration->get_menu) - { + !client->state.raintegration->get_menu) { return NULL; } return client->state.raintegration->get_menu(); } +void rc_client_raintegration_set_console_id(rc_client_t* client, uint32_t console_id) +{ + if (client && client->state.raintegration && client->state.raintegration->set_console_id) + client->state.raintegration->set_console_id(console_id); +} + 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) - { + !client->state.raintegration->bIsInited || + !client->state.raintegration->has_modifications) { return 0; } return client->state.raintegration->has_modifications(); } +int rc_client_raintegration_get_achievement_state(const rc_client_t* client, uint32_t achievement_id) +{ + if (!client || !client->state.raintegration || + !client->state.raintegration->bIsInited || + !client->state.raintegration->get_achievement_state) { + return RC_CLIENT_RAINTEGRATION_ACHIEVEMENT_STATE_NONE; + } + + return client->state.raintegration->get_achievement_state(achievement_id); +} + void rc_client_raintegration_rebuild_submenu(rc_client_t* client, HMENU hMenu) { HMENU hPopupMenu = NULL; @@ -434,7 +454,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); } @@ -449,7 +469,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) { @@ -464,6 +484,9 @@ void rc_client_raintegration_rebuild_submenu(rc_client_t* client, HMENU hMenu) AppendMenuA(hMenu, flags, (UINT_PTR)hPopupMenu, menuText); else ModifyMenuA(hMenu, nIndex, flags | MF_BYPOSITION, (UINT_PTR)hPopupMenu, menuText); + + if (client->state.raintegration->hMainWindow && GetMenu(client->state.raintegration->hMainWindow) == hMenu) + DrawMenuBar(client->state.raintegration->hMainWindow); } client->state.raintegration->hPopupMenu = hPopupMenu; @@ -478,15 +501,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) @@ -498,6 +524,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/dep/rcheevos/src/rc_client_raintegration_internal.h b/dep/rcheevos/src/rc_client_raintegration_internal.h index ce7c98b03..ce3a99dda 100644 --- a/dep/rcheevos/src/rc_client_raintegration_internal.h +++ b/dep/rcheevos/src/rc_client_raintegration_internal.h @@ -17,16 +17,19 @@ typedef const char* (RC_CCONV* rc_client_raintegration_get_string_func_t)(void); typedef int (RC_CCONV* rc_client_raintegration_init_client_func_t)(HWND hMainWnd, const char* sClientName, const char* sClientVersion); typedef int (RC_CCONV* rc_client_raintegration_get_external_client_func_t)(rc_client_external_t* pClient, int nVersion); typedef void (RC_CCONV* rc_client_raintegration_hwnd_action_func_t)(HWND hWnd); +typedef int (RC_CCONV* rc_client_raintegration_get_achievement_state_func_t)(uint32_t nMenuItemId); 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 void (RC_CCONV* rc_client_raintegration_set_int_func_t)(int); typedef int (RC_CCONV* rc_client_raintegration_get_int_func_t)(void); typedef struct rc_client_raintegration_t { HINSTANCE hDLL; + HWND hMainWindow; HMENU hPopupMenu; uint8_t bIsInited; @@ -34,6 +37,7 @@ typedef struct rc_client_raintegration_t rc_client_raintegration_get_string_func_t get_host_url; rc_client_raintegration_init_client_func_t init_client; rc_client_raintegration_init_client_func_t init_client_offline; + rc_client_raintegration_set_int_func_t set_console_id; rc_client_raintegration_action_func_t shutdown; rc_client_raintegration_hwnd_action_func_t update_main_window_handle; @@ -44,6 +48,7 @@ typedef struct rc_client_raintegration_t 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_achievement_state_func_t get_achievement_state; rc_client_raintegration_get_external_client_func_t get_external_client; diff --git a/dep/rcheevos/src/rc_libretro.c b/dep/rcheevos/src/rc_libretro.c index d94d6d5b4..14398ef1e 100644 --- a/dep/rcheevos/src/rc_libretro.c +++ b/dep/rcheevos/src/rc_libretro.c @@ -69,6 +69,7 @@ static const rc_disallowed_setting_t _rc_disallowed_ecwolf_settings[] = { static const rc_disallowed_setting_t _rc_disallowed_fbneo_settings[] = { { "fbneo-allow-patched-romsets", "enabled" }, { "fbneo-cheat-*", "!,Disabled,0 - Disabled" }, + { "fbneo-cpu-speed-adjust", "??%" }, /* disallow speeds under 100% */ { "fbneo-dipswitch-*", "Universe BIOS*" }, { "fbneo-neogeo-mode", "UNIBIOS" }, { NULL, NULL } @@ -178,7 +179,7 @@ static const rc_disallowed_core_settings_t rc_disallowed_core_settings[] = { static int rc_libretro_string_equal_nocase_wildcard(const char* test, const char* value) { char c1, c2; while ((c1 = *test++)) { - if (tolower(c1) != tolower(c2 = *value++)) + if (tolower(c1) != tolower(c2 = *value++) && c2 != '?') return (c2 == '*'); } diff --git a/dep/rcheevos/src/rc_util.c b/dep/rcheevos/src/rc_util.c index fa369a3b0..b6aa5bf6d 100644 --- a/dep/rcheevos/src/rc_util.c +++ b/dep/rcheevos/src/rc_util.c @@ -183,6 +183,9 @@ 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"; + case RC_INVALID_VARIABLE_NAME: return "Invalid variable name"; + case RC_UNKNOWN_VARIABLE_NAME: return "Unknown variable name"; default: return "Unknown error"; } } diff --git a/dep/rcheevos/src/rc_version.h b/dep/rcheevos/src/rc_version.h index daf57e1cb..3e337b9a2 100644 --- a/dep/rcheevos/src/rc_version.h +++ b/dep/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 4 #define RCHEEVOS_VERSION_PATCH 0 #define RCHEEVOS_MAKE_VERSION(major, minor, patch) (major * 1000000 + minor * 1000 + patch) diff --git a/dep/rcheevos/src/rcheevos/condition.c b/dep/rcheevos/src/rcheevos/condition.c index 236dff373..de8efd924 100644 --- a/dep/rcheevos/src/rcheevos/condition.c +++ b/dep/rcheevos/src/rcheevos/condition.c @@ -139,6 +139,18 @@ static int rc_parse_operator(const char** memaddr) { ++(*memaddr); return RC_OPERATOR_XOR; + case '%': + ++(*memaddr); + return RC_OPERATOR_MOD; + + case '+': + ++(*memaddr); + return RC_OPERATOR_ADD; + + case '-': + ++(*memaddr); + return RC_OPERATOR_SUB; + case '\0':/* end of string */ case '_': /* next condition */ case 'S': /* next condset */ @@ -176,12 +188,13 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse case 'q': case 'Q': self->type = RC_CONDITION_MEASURED_IF; break; case 'i': case 'I': self->type = RC_CONDITION_ADD_ADDRESS; can_modify = 1; break; case 't': case 'T': self->type = RC_CONDITION_TRIGGER; break; + case 'k': case 'K': self->type = RC_CONDITION_REMEMBER; can_modify = 1; break; case 'z': case 'Z': self->type = RC_CONDITION_RESET_NEXT_IF; break; case 'g': case 'G': parse->measured_as_percent = 1; self->type = RC_CONDITION_MEASURED; break; - /* e f h j k l s u v w x y */ + /* e f h j l s u v w x y */ default: parse->offset = RC_INVALID_CONDITION_TYPE; return 0; } @@ -226,6 +239,9 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse case RC_OPERATOR_DIV: case RC_OPERATOR_AND: case RC_OPERATOR_XOR: + case RC_OPERATOR_MOD: + case RC_OPERATOR_ADD: + case RC_OPERATOR_SUB: /* modifying operators are only valid on modifying statements */ if (can_modify) break; @@ -238,6 +254,7 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse case RC_CONDITION_ADD_SOURCE: case RC_CONDITION_SUB_SOURCE: case RC_CONDITION_ADD_ADDRESS: + case RC_CONDITION_REMEMBER: /* prevent parse errors on legacy achievements where a condition was present before changing the type */ self->oper = RC_OPERATOR_NONE; break; @@ -551,5 +568,18 @@ void rc_evaluate_condition_value(rc_typed_value_t* value, rc_condition_t* self, rc_typed_value_convert(&amount, RC_VALUE_TYPE_UNSIGNED); value->value.u32 ^= amount.value.u32; break; + + case RC_OPERATOR_MOD: + rc_typed_value_modulus(value, &amount); + break; + + case RC_OPERATOR_ADD: + rc_typed_value_add(value, &amount); + break; + + case RC_OPERATOR_SUB: + rc_typed_value_negate(&amount); + rc_typed_value_add(value, &amount); + break; } } diff --git a/dep/rcheevos/src/rcheevos/condset.c b/dep/rcheevos/src/rcheevos/condset.c index 23a0e30e1..f03d47b45 100644 --- a/dep/rcheevos/src/rcheevos/condset.c +++ b/dep/rcheevos/src/rcheevos/condset.c @@ -53,6 +53,7 @@ rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse, in case RC_CONDITION_ADD_ADDRESS: case RC_CONDITION_ADD_SOURCE: case RC_CONDITION_SUB_SOURCE: + case RC_CONDITION_REMEMBER: /* these conditions don't require a right hand size (implied *1) */ break; @@ -87,6 +88,9 @@ rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse, in case RC_OPERATOR_XOR: case RC_OPERATOR_DIV: case RC_OPERATOR_MULT: + case RC_OPERATOR_MOD: + case RC_OPERATOR_ADD: + case RC_OPERATOR_SUB: case RC_OPERATOR_NONE: /* measuring value. leave required_hits at 0 */ break; @@ -221,6 +225,15 @@ static int rc_test_condset_internal(rc_condset_t* self, int processing_pause, rc eval_state->add_address = value.value.u32; continue; + case RC_CONDITION_REMEMBER: + rc_evaluate_condition_value(&value, condition, eval_state); + rc_typed_value_add(&value, &eval_state->add_value); + eval_state->recall_value.type = value.type; + eval_state->recall_value.value = value.value; + eval_state->add_value.type = RC_VALUE_TYPE_NONE; + eval_state->add_address = 0; + continue; + case RC_CONDITION_MEASURED: if (condition->required_hits == 0 && can_measure) { /* Measured condition without a hit target measures the value of the left operand */ @@ -416,6 +429,10 @@ int rc_test_condset(rc_condset_t* self, rc_eval_state_t* eval_state) { return 1; } + /* initialize recall value so each condition set has a functionally new recall accumulator */ + eval_state->recall_value.type = RC_VALUE_TYPE_UNSIGNED; + eval_state->recall_value.value.u32 = 0; + if (self->has_pause) { /* one or more Pause conditions exists, if any of them are true, stop processing this group */ self->is_paused = (char)rc_test_condset_internal(self, 1, eval_state); diff --git a/dep/rcheevos/src/rcheevos/consoleinfo.c b/dep/rcheevos/src/rcheevos/consoleinfo.c index 427db73b2..b8bee226d 100644 --- a/dep/rcheevos/src/rcheevos/consoleinfo.c +++ b/dep/rcheevos/src/rcheevos/consoleinfo.c @@ -368,9 +368,14 @@ static const rc_memory_regions_t rc_memory_regions_atari_lynx = { _rc_memory_reg /* ===== ColecoVision ===== */ static const rc_memory_region_t _rc_memory_regions_colecovision[] = { - { 0x000000U, 0x0003FFU, 0x006000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" } + /* "System RAM" refers to the main RAM at 0x6000-0x63FF. However, this RAM might not always be visible. + * If the Super Game Module (SGM) is active, then it might overlay its own RAM at 0x0000-0x1FFF and 0x2000-0x7FFF. + * These positions overlap the BIOS and System RAM, therefore we use virtual addresses for these memory spaces. */ + { 0x000000U, 0x0003FFU, 0x006000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, + { 0x000400U, 0x0023FFU, 0x010000U, RC_MEMORY_TYPE_SYSTEM_RAM, "SGM Low RAM" }, /* Normally situated at 0x0000-0x1FFF, which overlaps the BIOS */ + { 0x002400U, 0x0083FFU, 0x012000U, RC_MEMORY_TYPE_SYSTEM_RAM, "SGM High RAM" } /* Normally situated at 0x2000-0x7FFF, which overlaps System RAM */ }; -static const rc_memory_regions_t rc_memory_regions_colecovision = { _rc_memory_regions_colecovision, 1 }; +static const rc_memory_regions_t rc_memory_regions_colecovision = { _rc_memory_regions_colecovision, 3 }; /* ===== Commodore 64 ===== */ /* https://www.c64-wiki.com/wiki/Memory_Map */ @@ -418,7 +423,7 @@ static const rc_memory_region_t _rc_memory_regions_fairchild_channel_f[] = { }; static const rc_memory_regions_t rc_memory_regions_fairchild_channel_f = { _rc_memory_regions_fairchild_channel_f, 4 }; -/* ===== GameBoy / GameBoy Color ===== */ +/* ===== GameBoy / MegaDuck ===== */ static const rc_memory_region_t _rc_memory_regions_gameboy[] = { { 0x000000U, 0x0000FFU, 0x000000U, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "Interrupt vector" }, { 0x000100U, 0x00014FU, 0x000100U, RC_MEMORY_TYPE_READONLY, "Cartridge header" }, @@ -427,8 +432,37 @@ static const rc_memory_region_t _rc_memory_regions_gameboy[] = { { 0x008000U, 0x0097FFU, 0x008000U, RC_MEMORY_TYPE_VIDEO_RAM, "Tile RAM" }, { 0x009800U, 0x009BFFU, 0x009800U, RC_MEMORY_TYPE_VIDEO_RAM, "BG1 map data" }, { 0x009C00U, 0x009FFFU, 0x009C00U, RC_MEMORY_TYPE_VIDEO_RAM, "BG2 map data" }, - { 0x00A000U, 0x00BFFFU, 0x00A000U, RC_MEMORY_TYPE_SAVE_RAM, "Cartridge RAM"}, + { 0x00A000U, 0x00BFFFU, 0x00A000U, RC_MEMORY_TYPE_SAVE_RAM, "Cartridge RAM (bank 0)"}, { 0x00C000U, 0x00CFFFU, 0x00C000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM (fixed)" }, + { 0x00D000U, 0x00DFFFU, 0x00D000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM (fixed)" }, + { 0x00E000U, 0x00FDFFU, 0x00C000U, RC_MEMORY_TYPE_VIRTUAL_RAM, "Echo RAM" }, + { 0x00FE00U, 0x00FE9FU, 0x00FE00U, RC_MEMORY_TYPE_VIDEO_RAM, "Sprite RAM"}, + { 0x00FEA0U, 0x00FEFFU, 0x00FEA0U, RC_MEMORY_TYPE_UNUSED, ""}, + { 0x00FF00U, 0x00FF7FU, 0x00FF00U, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "Hardware I/O"}, + { 0x00FF80U, 0x00FFFEU, 0x00FF80U, RC_MEMORY_TYPE_SYSTEM_RAM, "Quick RAM"}, + { 0x00FFFFU, 0x00FFFFU, 0x00FFFFU, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "Interrupt enable"}, + + /* GameBoy's cartridge RAM may have a total of up to 16 banks that can be paged through $A000-$BFFF. + * It is desirable to always have access to these extra banks. We do this by expecting the extra banks + * to be addressable at addresses not supported by the native system. 0x10000-0x16000 is reserved + * for the extra banks of system memory that are exclusive to the GameBoy Color. */ + { 0x010000U, 0x015FFFU, 0x010000U, RC_MEMORY_TYPE_UNUSED, "Unused (GameBoy Color exclusive)" }, + { 0x016000U, 0x033FFFU, 0x016000U, RC_MEMORY_TYPE_SAVE_RAM, "Cartridge RAM (banks 1-15)" }, +}; +static const rc_memory_regions_t rc_memory_regions_megaduck = { _rc_memory_regions_gameboy, 16 }; +static const rc_memory_regions_t rc_memory_regions_gameboy = { _rc_memory_regions_gameboy, 18 }; + +/* ===== GameBoy Color ===== */ +static const rc_memory_region_t _rc_memory_regions_gameboy_color[] = { + { 0x000000U, 0x0000FFU, 0x000000U, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "Interrupt vector" }, + { 0x000100U, 0x00014FU, 0x000100U, RC_MEMORY_TYPE_READONLY, "Cartridge header" }, + { 0x000150U, 0x003FFFU, 0x000150U, RC_MEMORY_TYPE_READONLY, "Cartridge ROM (fixed)" }, /* bank 0 */ + { 0x004000U, 0x007FFFU, 0x004000U, RC_MEMORY_TYPE_READONLY, "Cartridge ROM (paged)" }, /* bank 1-XX (switchable) */ + { 0x008000U, 0x0097FFU, 0x008000U, RC_MEMORY_TYPE_VIDEO_RAM, "Tile RAM" }, + { 0x009800U, 0x009BFFU, 0x009800U, RC_MEMORY_TYPE_VIDEO_RAM, "BG1 map data" }, + { 0x009C00U, 0x009FFFU, 0x009C00U, RC_MEMORY_TYPE_VIDEO_RAM, "BG2 map data" }, + { 0x00A000U, 0x00BFFFU, 0x00A000U, RC_MEMORY_TYPE_SAVE_RAM, "Cartridge RAM (bank 0)"}, + { 0x00C000U, 0x00CFFFU, 0x00C000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM (bank 0)" }, { 0x00D000U, 0x00DFFFU, 0x00D000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM (bank 1)" }, { 0x00E000U, 0x00FDFFU, 0x00C000U, RC_MEMORY_TYPE_VIRTUAL_RAM, "Echo RAM" }, { 0x00FE00U, 0x00FE9FU, 0x00FE00U, RC_MEMORY_TYPE_VIDEO_RAM, "Sprite RAM"}, @@ -437,14 +471,14 @@ static const rc_memory_region_t _rc_memory_regions_gameboy[] = { { 0x00FF80U, 0x00FFFEU, 0x00FF80U, RC_MEMORY_TYPE_SYSTEM_RAM, "Quick RAM"}, { 0x00FFFFU, 0x00FFFFU, 0x00FFFFU, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "Interrupt enable"}, - /* GameBoy Color provides six extra banks of memory that can be paged out through the $DXXX - * memory space, but the timing of that does not correspond with blanks, which is when achievements - * are processed. As such, it is desirable to always have access to these extra banks. We do this - * by expecting the extra banks to be addressable at addresses not supported by the native system. */ - { 0x010000U, 0x015FFFU, 0x010000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM (banks 2-7, GBC only)" } + /* GameBoy Color provides 6 extra banks of system memory that can be paged out through the $D000-$DFFF, + * and the cartridge RAM may have a total of up to 16 banks page through $A000-$BFFF. + * It is desirable to always have access to these extra banks. We do this by expecting the extra banks + * to be addressable at addresses not supported by the native system. */ + { 0x010000U, 0x015FFFU, 0x010000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM (banks 2-7)" }, + { 0x016000U, 0x033FFFU, 0x016000U, RC_MEMORY_TYPE_SAVE_RAM, "Cartridge RAM (banks 1-15)" }, }; -static const rc_memory_regions_t rc_memory_regions_gameboy = { _rc_memory_regions_gameboy, 16 }; -static const rc_memory_regions_t rc_memory_regions_gameboy_color = { _rc_memory_regions_gameboy, 17 }; +static const rc_memory_regions_t rc_memory_regions_gameboy_color = { _rc_memory_regions_gameboy_color, 18 }; /* ===== GameBoy Advance ===== */ /* http://problemkaputt.de/gbatek-gba-memory-map.htm */ @@ -463,11 +497,19 @@ static const rc_memory_region_t _rc_memory_regions_gamecube[] = { static const rc_memory_regions_t rc_memory_regions_gamecube = { _rc_memory_regions_gamecube, 1 }; /* ===== Game Gear ===== */ -/* http://www.smspower.org/Development/MemoryMap */ +/* https://www.smspower.org/Development/MemoryMap */ +/* https://www.smspower.org/Development/Mappers */ static const rc_memory_region_t _rc_memory_regions_game_gear[] = { - { 0x000000U, 0x001FFFU, 0x00C000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" } + { 0x000000U, 0x001FFFU, 0x00C000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, + /* GG/SMS have various possible mappings for cartridge memory depending on the mapper used. + * However, these ultimately do not map all of their memory at once, typically requiring banking. + * Thus, the "real address" used is just a virtual address mapping all cartridge memory in one contiguous block. + * Note that this may possibly refer to non-battery backed "extended RAM" so this isn't strictly RC_MEMORY_TYPE_SAVE_RAM. + * libretro cores expose "extended RAM" as RETRO_MEMORY_SAVE_RAM regardless however. + */ + { 0x002000U, 0x009FFFU, 0x010000U, RC_MEMORY_TYPE_SAVE_RAM, "Cartridge RAM" } }; -static const rc_memory_regions_t rc_memory_regions_game_gear = { _rc_memory_regions_game_gear, 1 }; +static const rc_memory_regions_t rc_memory_regions_game_gear = { _rc_memory_regions_game_gear, 2 }; /* ===== Intellivision ===== */ /* http://wiki.intellivision.us/index.php/Memory_Map */ @@ -531,14 +573,22 @@ static const rc_memory_region_t _rc_memory_regions_magnavox_odyssey_2[] = { static const rc_memory_regions_t rc_memory_regions_magnavox_odyssey_2 = { _rc_memory_regions_magnavox_odyssey_2, 2 }; /* ===== Master System ===== */ -/* http://www.smspower.org/Development/MemoryMap */ +/* https://www.smspower.org/Development/MemoryMap */ +/* https://www.smspower.org/Development/Mappers */ static const rc_memory_region_t _rc_memory_regions_master_system[] = { - { 0x000000U, 0x001FFFU, 0x00C000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" } + { 0x000000U, 0x001FFFU, 0x00C000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, + /* GG/SMS have various possible mappings for cartridge memory depending on the mapper used. + * However, these ultimately do not map all of their memory at once, typically requiring banking. + * Thus, the "real address" used is just a virtual address mapping all cartridge memory in one contiguous block. + * Note that this may possibly refer to non-battery backed "extended RAM" so this isn't strictly RC_MEMORY_TYPE_SAVE_RAM. + * libretro cores expose "extended RAM" as RETRO_MEMORY_SAVE_RAM regardless however. + */ + { 0x002000U, 0x009FFFU, 0x010000U, RC_MEMORY_TYPE_SAVE_RAM, "Cartridge RAM" } }; -static const rc_memory_regions_t rc_memory_regions_master_system = { _rc_memory_regions_master_system, 1 }; +static const rc_memory_regions_t rc_memory_regions_master_system = { _rc_memory_regions_master_system, 2 }; /* ===== MegaDrive (Genesis) ===== */ -/* http://www.smspower.org/Development/MemoryMap */ +/* https://www.smspower.org/Development/MemoryMap */ static const rc_memory_region_t _rc_memory_regions_megadrive[] = { { 0x000000U, 0x00FFFFU, 0xFF0000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, { 0x010000U, 0x01FFFFU, 0x000000U, RC_MEMORY_TYPE_SAVE_RAM, "Cartridge RAM" } @@ -725,10 +775,11 @@ static const rc_memory_regions_t rc_memory_regions_pcfx = { _rc_memory_regions_p /* ===== PlayStation ===== */ /* http://www.raphnet.net/electronique/psx_adaptor/Playstation.txt */ static const rc_memory_region_t _rc_memory_regions_playstation[] = { - { 0x000000U, 0x00FFFFU, 0x000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Kernel RAM" }, - { 0x010000U, 0x1FFFFFU, 0x010000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" } + { 0x000000U, 0x00FFFFU, 0x00000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Kernel RAM" }, + { 0x010000U, 0x1FFFFFU, 0x00010000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, + { 0x200000U, 0x2003FFU, 0x1F800000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Scratchpad RAM" } }; -static const rc_memory_regions_t rc_memory_regions_playstation = { _rc_memory_regions_playstation, 2 }; +static const rc_memory_regions_t rc_memory_regions_playstation = { _rc_memory_regions_playstation, 3 }; /* ===== PlayStation 2 ===== */ /* https://psi-rockin.github.io/ps2tek/ */ @@ -772,7 +823,7 @@ static const rc_memory_region_t _rc_memory_regions_saturn[] = { static const rc_memory_regions_t rc_memory_regions_saturn = { _rc_memory_regions_saturn, 2 }; /* ===== SG-1000 ===== */ -/* http://www.smspower.org/Development/MemoryMap */ +/* https://www.smspower.org/Development/MemoryMap */ static const rc_memory_region_t _rc_memory_regions_sg1000[] = { { 0x000000U, 0x0003FFU, 0xC000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, /* https://github.com/libretro/FBNeo/blob/697801c6262be6ca91615cf905444d3e039bc06f/src/burn/drv/sg1000/d_sg1000.cpp#L210-L237 */ @@ -957,8 +1008,7 @@ const rc_memory_regions_t* rc_console_memory_regions(uint32_t console_id) case RC_CONSOLE_FAIRCHILD_CHANNEL_F: return &rc_memory_regions_fairchild_channel_f; - - case RC_CONSOLE_MEGADUCK: + case RC_CONSOLE_GAMEBOY: return &rc_memory_regions_gameboy; @@ -989,6 +1039,9 @@ const rc_memory_regions_t* rc_console_memory_regions(uint32_t console_id) case RC_CONSOLE_MEGA_DRIVE: return &rc_memory_regions_megadrive; + case RC_CONSOLE_MEGADUCK: + return &rc_memory_regions_megaduck; + case RC_CONSOLE_SEGA_32X: return &rc_memory_regions_megadrive_32x; diff --git a/dep/rcheevos/src/rcheevos/memref.c b/dep/rcheevos/src/rcheevos/memref.c index 87f6ec0bf..0cbec67b8 100644 --- a/dep/rcheevos/src/rcheevos/memref.c +++ b/dep/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/dep/rcheevos/src/rcheevos/operand.c b/dep/rcheevos/src/rcheevos/operand.c index 252258275..09ac65ee1 100644 --- a/dep/rcheevos/src/rcheevos/operand.c +++ b/dep/rcheevos/src/rcheevos/operand.c @@ -3,6 +3,7 @@ #include #include #include +#include #ifndef RC_DISABLE_LUA @@ -64,6 +65,37 @@ static int rc_parse_operand_lua(rc_operand_t* self, const char** memaddr, rc_par return RC_OK; } +static int rc_parse_operand_variable(rc_operand_t* self, const char** memaddr) { + const char* aux = *memaddr; + size_t i; + char varName[RC_VALUE_MAX_NAME_LENGTH + 1] = { 0 }; + + for (i = 0; i < RC_VALUE_MAX_NAME_LENGTH && *aux != '}'; i++) { + if (!rc_is_valid_variable_character(*aux, i == 0)) + return RC_INVALID_VARIABLE_NAME; + + varName[i] = *aux++; + } + + if (i == 0) + return RC_INVALID_VARIABLE_NAME; + + if (*aux != '}') + return RC_INVALID_VARIABLE_NAME; + + ++aux; + + if (strcmp(varName, "recall") == 0) { + self->type = RC_OPERAND_RECALL; + } + else { /* process named variable when feature is available.*/ + return RC_UNKNOWN_VARIABLE_NAME; + } + + *memaddr = aux; + return RC_OK; +} + static int rc_parse_operand_memory(rc_operand_t* self, const char** memaddr, rc_parse_state_t* parse, uint8_t is_indirect) { const char* aux = *memaddr; uint32_t address; @@ -231,6 +263,13 @@ int rc_parse_operand(rc_operand_t* self, const char** memaddr, uint8_t is_indire self->value.num = (unsigned)value; } break; + case '{': /* variable */ + ++aux; + ret = rc_parse_operand_variable(self, &aux); + if (ret < 0) + return ret; + + break; case '0': if (aux[1] == 'x' || aux[1] == 'X') { /* hex integer constant */ @@ -297,6 +336,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; @@ -311,6 +352,7 @@ int rc_operand_is_memref(const rc_operand_t* self) { case RC_OPERAND_CONST: case RC_OPERAND_FP: case RC_OPERAND_LUA: + case RC_OPERAND_RECALL: return 0; default: @@ -318,6 +360,16 @@ int rc_operand_is_memref(const rc_operand_t* self) { } } +int rc_operand_is_recall(const rc_operand_t* self) { + switch (self->type) { + case RC_OPERAND_RECALL: + return 1; + + default: + return 0; + } +} + int rc_operand_is_float(const rc_operand_t* self) { if (self->type == RC_OPERAND_FP) return 1; @@ -460,6 +512,11 @@ void rc_evaluate_operand(rc_typed_value_t* result, rc_operand_t* self, rc_eval_s break; + case RC_OPERAND_RECALL: + result->type = eval_state->recall_value.type; + result->value = eval_state->recall_value.value; + return; + default: result->type = RC_VALUE_TYPE_UNSIGNED; result->value.u32 = rc_get_memref_value(self->value.memref, self->type, eval_state); diff --git a/dep/rcheevos/src/rcheevos/rc_internal.h b/dep/rcheevos/src/rcheevos/rc_internal.h index 135423980..fa913fec8 100644 --- a/dep/rcheevos/src/rcheevos/rc_internal.h +++ b/dep/rcheevos/src/rcheevos/rc_internal.h @@ -89,12 +89,13 @@ typedef struct { void* peek_userdata; lua_State* L; - rc_typed_value_t measured_value; /* Measured */ - uint8_t was_reset; /* ResetIf triggered */ - uint8_t has_hits; /* one of more hit counts is non-zero */ - uint8_t primed; /* true if all non-Trigger conditions are true */ - uint8_t measured_from_hits; /* true if the measured_value came from a condition's hit count */ - uint8_t was_cond_reset; /* ResetNextIf triggered */ + rc_typed_value_t measured_value; /* Measured */ + rc_typed_value_t recall_value; /* Set by RC_CONDITION_REMEMBER */ + uint8_t was_reset; /* ResetIf triggered */ + uint8_t has_hits; /* one of more hit counts is non-zero */ + uint8_t primed; /* true if all non-Trigger conditions are true */ + uint8_t measured_from_hits; /* true if the measured_value came from a condition's hit count */ + uint8_t was_cond_reset; /* ResetNextIf triggered */ } rc_eval_state_t; @@ -169,7 +170,9 @@ int rc_parse_operand(rc_operand_t* self, const char** memaddr, uint8_t is_indire void rc_evaluate_operand(rc_typed_value_t* value, rc_operand_t* self, rc_eval_state_t* eval_state); 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_is_valid_variable_character(char ch, int is_first); void rc_parse_value_internal(rc_value_t* self, const char** memaddr, rc_parse_state_t* parse); int rc_evaluate_value_typed(rc_value_t* self, rc_typed_value_t* value, rc_peek_t peek, void* ud, lua_State* L); void rc_reset_value(rc_value_t* self); @@ -181,6 +184,7 @@ void rc_typed_value_convert(rc_typed_value_t* value, char new_type); void rc_typed_value_add(rc_typed_value_t* value, const rc_typed_value_t* amount); void rc_typed_value_multiply(rc_typed_value_t* value, const rc_typed_value_t* amount); void rc_typed_value_divide(rc_typed_value_t* value, const rc_typed_value_t* amount); +void rc_typed_value_modulus(rc_typed_value_t* value, const rc_typed_value_t* amount); void rc_typed_value_negate(rc_typed_value_t* value); int rc_typed_value_compare(const rc_typed_value_t* value1, const rc_typed_value_t* value2, char oper); void rc_typed_value_from_memref_value(rc_typed_value_t* value, const rc_memref_value_t* memref); diff --git a/dep/rcheevos/src/rcheevos/rc_validate.c b/dep/rcheevos/src/rcheevos/rc_validate.c index 1dc6a8742..bd3b305a4 100644 --- a/dep/rcheevos/src/rcheevos/rc_validate.c +++ b/dep/rcheevos/src/rcheevos/rc_validate.c @@ -143,6 +143,31 @@ static uint32_t rc_scale_value(uint32_t value, uint8_t oper, const rc_operand_t* case RC_OPERATOR_XOR: return value | rc_max_value(operand); + case RC_OPERATOR_MOD: + { + const uint32_t divisor = (operand->type == RC_OPERAND_CONST) ? operand->value.num : 1; + return (divisor >= value) ? (divisor - 1) : value; + } + + case RC_OPERATOR_ADD: + { + unsigned long scaled = ((unsigned long)value) + rc_max_value(operand); + if (scaled > 0xFFFFFFFF) + return 0xFFFFFFFF; + + return (uint32_t)scaled; + } + + case RC_OPERATOR_SUB: + { + if (operand->type == RC_OPERAND_CONST) + return value - operand->value.num; + else if (value > rc_max_value(operand)) + return value - rc_max_value(operand); + + return 0xFFFFFFFF; + } + default: return value; } @@ -241,6 +266,8 @@ int rc_validate_condset_internal(const rc_condset_t* condset, char result[], con int in_add_hits = 0; int in_add_address = 0; int is_combining = 0; + int remember_used = 0; + int remember_used_in_pause = 0; if (!condset) { *result = '\0'; @@ -251,6 +278,7 @@ int rc_validate_condset_internal(const rc_condset_t* condset, char result[], con uint32_t max = rc_max_value(&cond->operand1); const int is_memref1 = rc_operand_is_memref(&cond->operand1); const int is_memref2 = rc_operand_is_memref(&cond->operand2); + const int uses_recall = rc_operand_is_recall(&cond->operand1) || rc_operand_is_recall(&cond->operand2); if (!in_add_address) { if (is_memref1 && !rc_validate_memref(cond->operand1.value.memref, buffer, sizeof(buffer), console_id, max_address)) { @@ -266,6 +294,28 @@ int rc_validate_condset_internal(const rc_condset_t* condset, char result[], con in_add_address = 0; } + if (!remember_used && uses_recall) { + if (!cond->pause && condset->has_pause) { + /* pause conditions will be processed before non-pause conditions. + * scan forward for any remembers in yet-to-be-processed pause conditions */ + const rc_condition_t* cond_rem_pause_check = cond->next; + for (; cond_rem_pause_check; cond_rem_pause_check = cond_rem_pause_check->next) { + if (cond_rem_pause_check->type == RC_CONDITION_REMEMBER && cond_rem_pause_check->pause) { + remember_used = 1; /* do not set remember_used_in_pause here because we don't know at which poing in the pause processing this remember is occurring. */ + break; + } + } + } + if (!remember_used) { + snprintf(result, result_size, "Condition %d: Recall used before Remember", index); + return 0; + } + } + else if (cond->pause && uses_recall && !remember_used_in_pause) { + snprintf(result, result_size, "Condition %d: Recall used in Pause processing before Remember was used in Pause processing", index); + return 0; + } + switch (cond->type) { case RC_CONDITION_ADD_SOURCE: max = rc_scale_value(max, cond->oper, &cond->operand2); @@ -289,6 +339,12 @@ int rc_validate_condset_internal(const rc_condset_t* condset, char result[], con is_combining = 1; continue; + case RC_CONDITION_REMEMBER: + is_combining = 1; + remember_used = 1; + remember_used_in_pause += cond->pause; + continue; + case RC_CONDITION_ADD_HITS: case RC_CONDITION_SUB_HITS: in_add_hits = 1; @@ -337,48 +393,67 @@ int rc_validate_condset_internal(const rc_condset_t* condset, char result[], con return 0; } - /* if either side is a memref, or there's a running add source chain, check for impossible comparisons */ - if (is_memref1 || is_memref2 || add_source_max) { + if (is_memref1 && rc_operand_is_float(&cond->operand1)) { + /* if left side is a float, right side will be converted to a float, so don't do range validation */ + } + else if (is_memref1 || is_memref2 || add_source_max) { + /* if either side is a memref, or there's a running add source chain, check for impossible comparisons */ const size_t prefix_length = snprintf(result, result_size, "Condition %d: ", index); - + const rc_operand_t* operand1 = &cond->operand1; + const rc_operand_t* operand2 = &cond->operand2; + uint8_t oper = cond->oper; uint32_t min_val; - switch (cond->operand2.type) { + + if (!is_memref1 && !add_source_max) { + /* pretend constant was on right side */ + operand1 = &cond->operand2; + operand2 = &cond->operand1; + max = max_val; + switch (oper) { + case RC_OPERATOR_LT: oper = RC_OPERATOR_GT; break; + case RC_OPERATOR_LE: oper = RC_OPERATOR_GE; break; + case RC_OPERATOR_GT: oper = RC_OPERATOR_LT; break; + case RC_OPERATOR_GE: oper = RC_OPERATOR_LE; break; + } + } + + switch (operand2->type) { case RC_OPERAND_CONST: - min_val = cond->operand2.value.num; + min_val = operand2->value.num; break; case RC_OPERAND_FP: - min_val = (int)cond->operand2.value.dbl; + min_val = (int)operand2->value.dbl; /* cannot compare an integer memory reference to a non-integral floating point value */ - /* assert: is_memref1 (because operand2==FP means !is_memref2) */ - if (!add_source_max && !rc_operand_is_float_memref(&cond->operand1) && - (float)min_val != cond->operand2.value.dbl) { - snprintf(result + prefix_length, result_size - prefix_length, "Comparison is never true"); - return 0; + if (!add_source_max && !rc_operand_is_float_memref(operand1) && + (float)min_val != operand2->value.dbl) { + switch (oper) { + case RC_OPERATOR_EQ: + snprintf(result + prefix_length, result_size - prefix_length, "Comparison is never true"); + return 0; + case RC_OPERATOR_NE: + snprintf(result + prefix_length, result_size - prefix_length, "Comparison is always true"); + return 0; + case RC_OPERATOR_GT: /* value could be greater than floor(float) */ + case RC_OPERATOR_LE: /* value could be less than or equal to floor(float) */ + break; + case RC_OPERATOR_GE: /* value could be greater than or equal to ceil(float) */ + case RC_OPERATOR_LT: /* value could be less than ceil(float) */ + ++min_val; + break; + } } break; - default: + default: /* right side is memref or add source chain */ min_val = 0; - - /* cannot compare an integer memory reference to a non-integral floating point value */ - /* assert: is_memref2 (because operand1==FP means !is_memref1) */ - if (cond->operand1.type == RC_OPERAND_FP && !add_source_max && !rc_operand_is_float_memref(&cond->operand2) && - (float)((int)cond->operand1.value.dbl) != cond->operand1.value.dbl) { - snprintf(result + prefix_length, result_size - prefix_length, "Comparison is never true"); - return 0; - } - break; } - if (rc_operand_is_float(&cond->operand2) && rc_operand_is_float(&cond->operand1)) { - /* both sides are floats, don't validate range*/ - } else if (!rc_validate_range(min_val, max_val, cond->oper, max, result + prefix_length, result_size - prefix_length)) { + if (!rc_validate_range(min_val, max_val, oper, max, result + prefix_length, result_size - prefix_length)) return 0; - } } add_source_max = 0; @@ -416,6 +491,7 @@ static int rc_validate_is_combining_condition(const rc_condition_t* condition) case RC_CONDITION_RESET_NEXT_IF: case RC_CONDITION_SUB_HITS: case RC_CONDITION_SUB_SOURCE: + case RC_CONDITION_REMEMBER: return 1; default: @@ -423,22 +499,6 @@ static int rc_validate_is_combining_condition(const rc_condition_t* condition) } } -static const rc_condition_t* rc_validate_next_non_combining_condition(const rc_condition_t* condition) -{ - int is_combining = rc_validate_is_combining_condition(condition); - for (condition = condition->next; condition != NULL; condition = condition->next) - { - if (rc_validate_is_combining_condition(condition)) - is_combining = 1; - else if (is_combining) - is_combining = 0; - else - return condition; - } - - return NULL; -} - static int rc_validate_get_opposite_comparison(int oper) { switch (oper) @@ -637,6 +697,24 @@ static int rc_validate_comparison_overlap(int comparison1, uint32_t value1, int return RC_OVERLAP_NONE; } +static int rc_validate_are_operands_equal(const rc_operand_t* oper1, const rc_operand_t* oper2) +{ + if (oper1->type != oper2->type) + return 0; + + switch (oper1->type) + { + case RC_OPERAND_CONST: + return (oper1->value.num == oper2->value.num); + case RC_OPERAND_FP: + return (oper1->value.dbl == oper2->value.dbl); + case RC_OPERAND_RECALL: + return (oper2->type == RC_OPERAND_RECALL); + default: + return (oper1->value.memref->address == oper2->value.memref->address && oper1->size == oper2->size); + } +} + static int rc_validate_conflicting_conditions(const rc_condset_t* conditions, const rc_condset_t* compare_conditions, const char* prefix, const char* compare_prefix, char result[], const size_t result_size) { @@ -646,6 +724,7 @@ static int rc_validate_conflicting_conditions(const rc_condset_t* conditions, co const rc_operand_t* operand2; const rc_condition_t* compare_condition; const rc_condition_t* condition; + const rc_condition_t* condition_chain_start; int overlap; /* empty group */ @@ -653,9 +732,14 @@ static int rc_validate_conflicting_conditions(const rc_condset_t* conditions, co return 1; /* outer loop is the source conditions */ - for (condition = conditions->conditions; condition != NULL; - condition = rc_validate_next_non_combining_condition(condition)) + for (condition = conditions->conditions; condition != NULL; condition = condition->next) { + condition_chain_start = condition; + while (rc_condition_is_combining(condition)) + condition = condition->next; + if (!condition) + break; + /* hits can be captured at any time, so any potential conflict will not be conflicting at another time */ if (condition->required_hits) continue; @@ -680,11 +764,62 @@ static int rc_validate_conflicting_conditions(const rc_condset_t* conditions, co } /* inner loop is the potentially conflicting conditions */ - for (compare_condition = compare_conditions->conditions; compare_condition != NULL; - compare_condition = rc_validate_next_non_combining_condition(compare_condition)) + for (compare_condition = compare_conditions->conditions; compare_condition != NULL; compare_condition = compare_condition->next) { - if (compare_condition == condition) + if (compare_condition == condition_chain_start) + { + /* skip condition we're already looking at */ + while (compare_condition != condition) + compare_condition = compare_condition->next; + continue; + } + + /* if combining conditions exist, make sure the same combining conditions exist in the + * compare logic. conflicts can only occur if the combinining conditions match. */ + if (condition_chain_start != condition) + { + int chain_matches = 1; + const rc_condition_t* condition_chain_iter = condition_chain_start; + while (condition_chain_iter != condition) + { + if (compare_condition->type != condition_chain_iter->type || + compare_condition->oper != condition_chain_iter->oper || + compare_condition->required_hits != condition_chain_iter->required_hits || + !rc_validate_are_operands_equal(&compare_condition->operand1, &condition_chain_iter->operand1)) + { + chain_matches = 0; + break; + } + + if (compare_condition->oper != RC_OPERATOR_NONE && + !rc_validate_are_operands_equal(&compare_condition->operand2, &condition_chain_iter->operand2)) + { + if (compare_condition->operand2.type != condition_chain_iter->operand2.type) + { + chain_matches = 0; + break; + } + } + + if (!compare_condition->next) + { + chain_matches = 0; + break; + } + + compare_condition = compare_condition->next; + condition_chain_iter = condition_chain_iter->next; + } + + /* combining field didn't match, or there's more unmatched combining fields. ignore this condition */ + if (!chain_matches || rc_validate_is_combining_condition(compare_condition)) + { + while (compare_condition->next && rc_validate_is_combining_condition(compare_condition)) + compare_condition = compare_condition->next; + continue; + } + } if (compare_condition->required_hits) continue; @@ -801,8 +936,7 @@ static int rc_validate_trigger_internal(const rc_trigger_t* trigger, char result const rc_condset_t* alt; int index; - if (!trigger->alternative) - { + if (!trigger->alternative) { if (!rc_validate_condset_internal(trigger->requirement, result, result_size, console_id, max_address)) return 0; diff --git a/dep/rcheevos/src/rcheevos/runtime_progress.c b/dep/rcheevos/src/rcheevos/runtime_progress.c index fd951dbd5..629f0e376 100644 --- a/dep/rcheevos/src/rcheevos/runtime_progress.c +++ b/dep/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); @@ -197,6 +210,7 @@ static int rc_runtime_progress_is_indirect_memref(rc_operand_t* oper) { case RC_OPERAND_CONST: case RC_OPERAND_FP: + case RC_OPERAND_RECALL: case RC_OPERAND_LUA: return 0; @@ -210,6 +224,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 +247,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 +336,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 +360,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 +531,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 +549,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 +563,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 +602,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 +620,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 +649,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 +717,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 +762,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 +782,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 +799,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 +815,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 +828,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 +850,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 +888,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/dep/rcheevos/src/rcheevos/value.c b/dep/rcheevos/src/rcheevos/value.c index 3662fc364..25f2204e3 100644 --- a/dep/rcheevos/src/rcheevos/value.c +++ b/dep/rcheevos/src/rcheevos/value.c @@ -3,6 +3,21 @@ #include /* memset */ #include /* isdigit */ #include /* FLT_EPSILON */ +#include /* fmod */ + + + +int rc_is_valid_variable_character(char ch, int is_first) { + if (is_first) { + if (!isalpha((unsigned char)ch)) + return 0; + } + else { + if (!isalnum((unsigned char)ch)) + return 0; + } + return 1; +} static void rc_parse_cond_value(rc_value_t* self, const char** memaddr, rc_parse_state_t* parse) { rc_condset_t** next_clause; @@ -112,6 +127,9 @@ void rc_parse_legacy_value(rc_value_t* self, const char** memaddr, rc_parse_stat case RC_OPERATOR_DIV: case RC_OPERATOR_AND: case RC_OPERATOR_XOR: + case RC_OPERATOR_MOD: + case RC_OPERATOR_ADD: + case RC_OPERATOR_SUB: case RC_OPERATOR_NONE: break; @@ -628,6 +646,72 @@ void rc_typed_value_divide(rc_typed_value_t* value, const rc_typed_value_t* amou value->value.f32 /= amount->value.f32; } +void rc_typed_value_modulus(rc_typed_value_t* value, const rc_typed_value_t* amount) { + rc_typed_value_t converted; + + switch (amount->type) + { + case RC_VALUE_TYPE_UNSIGNED: + if (amount->value.u32 == 0) { /* divide by zero */ + value->type = RC_VALUE_TYPE_NONE; + return; + } + + switch (value->type) { + case RC_VALUE_TYPE_UNSIGNED: /* integer math */ + value->value.u32 %= amount->value.u32; + return; + case RC_VALUE_TYPE_SIGNED: /* integer math */ + value->value.i32 %= (int)amount->value.u32; + return; + case RC_VALUE_TYPE_FLOAT: + amount = rc_typed_value_convert_into(&converted, amount, RC_VALUE_TYPE_FLOAT); + break; + default: + value->type = RC_VALUE_TYPE_NONE; + return; + } + break; + + case RC_VALUE_TYPE_SIGNED: + if (amount->value.i32 == 0) { /* divide by zero */ + value->type = RC_VALUE_TYPE_NONE; + return; + } + + switch (value->type) { + case RC_VALUE_TYPE_SIGNED: /* integer math */ + value->value.i32 %= amount->value.i32; + return; + case RC_VALUE_TYPE_UNSIGNED: /* integer math */ + value->value.u32 %= (unsigned)amount->value.i32; + return; + case RC_VALUE_TYPE_FLOAT: + amount = rc_typed_value_convert_into(&converted, amount, RC_VALUE_TYPE_FLOAT); + break; + default: + value->type = RC_VALUE_TYPE_NONE; + return; + } + break; + + case RC_VALUE_TYPE_FLOAT: + break; + + default: + value->type = RC_VALUE_TYPE_NONE; + return; + } + + if (amount->value.f32 == 0.0) { /* divide by zero */ + value->type = RC_VALUE_TYPE_NONE; + return; + } + + rc_typed_value_convert(value, RC_VALUE_TYPE_FLOAT); + value->value.f32 = (float)fmod(value->value.f32, amount->value.f32); +} + static int rc_typed_value_compare_floats(float f1, float f2, char oper) { if (f1 == f2) { /* exactly equal */ @@ -683,9 +767,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/dep/rcheevos/src/rhash/hash.c b/dep/rcheevos/src/rhash/hash.c index 5d65512b6..5e27d973f 100644 --- a/dep/rcheevos/src/rhash/hash.c +++ b/dep/rcheevos/src/rhash/hash.c @@ -10,6 +10,7 @@ #if defined(_WIN32) #define WIN32_LEAN_AND_MEAN #include +#include #endif /* arbitrary limit to prevent allocating and hashing large files */ @@ -52,9 +53,9 @@ static void rc_hash_verbose(const char* message) static struct rc_hash_filereader filereader_funcs; static struct rc_hash_filereader* filereader = NULL; +#if defined(WINVER) && WINVER >= 0x0500 static void* filereader_open(const char* path) { -#if defined(WINVER) && WINVER >= 0x0500 /* Windows requires using wchar APIs for Unicode paths */ /* Note that MultiByteToWideChar will only be defined for >= Windows 2000 */ wchar_t* wpath; @@ -75,21 +76,34 @@ static void* filereader_open(const char* path) free(wpath); return NULL; } -#if defined(__STDC_WANT_SECURE_LIB__) - _wfopen_s(&fp, wpath, L"rb"); -#else + + #if defined(__STDC_WANT_SECURE_LIB__) + /* have to use _SH_DENYNO because some cores lock the file while its loaded */ + fp = _wfsopen(wpath, L"rb", _SH_DENYNO); + #else fp = _wfopen(wpath, L"rb"); -#endif + #endif + free(wpath); return fp; -#elif defined(__STDC_WANT_SECURE_LIB__) - FILE* fp; - fopen_s(&fp, path, "rb"); - return fp; -#else +} +#else /* !WINVER >= 0x0500 */ +static void* filereader_open(const char* path) +{ + #if defined(__STDC_WANT_SECURE_LIB__) + #if defined(WINVER) + /* have to use _SH_DENYNO because some cores lock the file while its loaded */ + return _fsopen(path, "rb", _SH_DENYNO); + #else /* !WINVER */ + FILE *fp; + fopen_s(&fp, path, "rb"); + return fp; + #endif + #else /* !__STDC_WANT_SECURE_LIB__ */ return fopen(path, "rb"); -#endif + #endif } +#endif /* WINVER >= 0x0500 */ static void filereader_seek(void* file_handle, int64_t offset, int origin) { @@ -818,12 +832,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) { @@ -877,7 +897,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 */ @@ -972,11 +992,12 @@ static int rc_hash_arcade(char hash[33], const char* path) /* arcade hash is just the hash of the filename (no extension) - the cores are pretty stringent about having the right ROM data */ const char* filename = rc_path_get_filename(path); const char* ext = rc_path_get_extension(filename); + char buffer[128]; /* realistically, this should never need more than ~32 characters */ size_t filename_length = ext - filename - 1; /* fbneo supports loading subsystems by using specific folder names. * if one is found, include it in the hash. - * https://github.com/libretro/FBNeo/blob/master/src/burner/libretro/README.md#emulating-consoles + * https://github.com/libretro/FBNeo/blob/master/src/burner/libretro/README.md#emulating-consoles-and-computers */ if (filename > path + 1) { @@ -993,31 +1014,67 @@ static int rc_hash_arcade(char hash[33], const char* path) } while (folder > path); parent_folder_length = filename - folder - 1; + if (parent_folder_length < 16) + { + char* ptr = buffer; + while (folder < filename - 1) + *ptr++ = tolower(*folder++); + *ptr = '\0'; + + folder = buffer; + } + switch (parent_folder_length) { case 3: - if (memcmp(folder, "nes", 3) == 0 || - memcmp(folder, "fds", 3) == 0 || - memcmp(folder, "sms", 3) == 0 || - memcmp(folder, "msx", 3) == 0 || - memcmp(folder, "ngp", 3) == 0 || - memcmp(folder, "pce", 3) == 0 || - memcmp(folder, "sgx", 3) == 0) + if (memcmp(folder, "nes", 3) == 0 || /* NES */ + memcmp(folder, "fds", 3) == 0 || /* FDS */ + memcmp(folder, "sms", 3) == 0 || /* Master System */ + memcmp(folder, "msx", 3) == 0 || /* MSX */ + memcmp(folder, "ngp", 3) == 0 || /* NeoGeo Pocket */ + memcmp(folder, "pce", 3) == 0 || /* PCEngine */ + memcmp(folder, "chf", 3) == 0 || /* ChannelF */ + memcmp(folder, "sgx", 3) == 0) /* SuperGrafX */ include_folder = 1; break; case 4: - if (memcmp(folder, "tg16", 4) == 0) + if (memcmp(folder, "tg16", 4) == 0 || /* TurboGrafx-16 */ + memcmp(folder, "msx1", 4) == 0) /* MSX */ + include_folder = 1; + break; + case 5: + if (memcmp(folder, "neocd", 5) == 0) /* NeoGeo CD */ include_folder = 1; break; case 6: - if (memcmp(folder, "coleco", 6) == 0 || - memcmp(folder, "sg1000", 6) == 0) + if (memcmp(folder, "coleco", 6) == 0 || /* Colecovision */ + memcmp(folder, "sg1000", 6) == 0) /* SG-1000 */ + include_folder = 1; + break; + case 7: + if (memcmp(folder, "genesis", 7) == 0) /* Megadrive (Genesis) */ include_folder = 1; break; case 8: - if (memcmp(folder, "gamegear", 8) == 0 || - memcmp(folder, "megadriv", 8) == 0 || - memcmp(folder, "spectrum", 8) == 0) + if (memcmp(folder, "gamegear", 8) == 0 || /* Game Gear */ + memcmp(folder, "megadriv", 8) == 0 || /* Megadrive */ + memcmp(folder, "pcengine", 8) == 0 || /* PCEngine */ + memcmp(folder, "channelf", 8) == 0 || /* ChannelF */ + memcmp(folder, "spectrum", 8) == 0) /* ZX Spectrum */ + include_folder = 1; + break; + case 9: + if (memcmp(folder, "megadrive", 9) == 0) /* Megadrive */ + include_folder = 1; + break; + case 10: + if (memcmp(folder, "supergrafx", 10) == 0 || /* SuperGrafX */ + memcmp(folder, "zxspectrum", 10) == 0) /* ZX Spectrum */ + include_folder = 1; + break; + case 12: + if (memcmp(folder, "mastersystem", 12) == 0 || /* Master System */ + memcmp(folder, "colecovision", 12) == 0) /* Colecovision */ include_folder = 1; break; default: @@ -1026,10 +1083,8 @@ static int rc_hash_arcade(char hash[33], const char* path) if (include_folder) { - char buffer[128]; /* realistically, this should never need more than ~20 characters */ if (parent_folder_length + filename_length + 1 < sizeof(buffer)) { - memcpy(&buffer[0], folder, parent_folder_length); buffer[parent_folder_length] = '_'; memcpy(&buffer[parent_folder_length + 1], filename, filename_length); return rc_hash_buffer(hash, (uint8_t*)&buffer[0], parent_folder_length + filename_length + 1); @@ -2121,6 +2176,14 @@ static int rc_hash_psp(char hash[33], const char* path) uint32_t size; md5_state_t md5; + /* https://www.psdevwiki.com/psp/PBP + * A PBP file is an archive containing the PARAM.SFO, primary executable, and a bunch of metadata. + * While we could extract the PARAM.SFO and primary executable to mimic the normal PSP hashing logic, + * it's easier to just hash the entire file. This also helps alleviate issues where the primary + * executable is just a game engine and the only differentiating data would be the metadata. */ + if (rc_path_compare_extension(path, "pbp")) + return rc_hash_whole_file(hash, path); + track_handle = rc_cd_open_track(path, 1); if (!track_handle) return rc_hash_error("Could not open track"); @@ -3134,6 +3197,10 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* { iterator->consoles[0] = RC_CONSOLE_PC_ENGINE; } + else if (rc_path_compare_extension(ext, "pbp")) + { + iterator->consoles[0] = RC_CONSOLE_PSP; + } else if (rc_path_compare_extension(ext, "pgm")) { iterator->consoles[0] = RC_CONSOLE_ELEKTOR_TV_GAMES_COMPUTER;