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);
/**
- * Deserializes the runtime state from a buffer.
+ * Serializes the runtime state into a buffer.
* Returns RC_OK on success, or an error indicator.
*/
+RC_EXPORT int RC_CCONV rc_client_serialize_progress_sized(rc_client_t* client, uint8_t* buffer, size_t buffer_size);
+
+/**
+ * Deserializes the runtime state from a buffer.
+ * Returns RC_OK on success, or an error indicator.
+ * [deprecated] use rc_client_deserialize_progress_sized instead
+ */
RC_EXPORT int RC_CCONV rc_client_deserialize_progress(rc_client_t* client, const uint8_t* serialized);
+/**
+ * Serializes the runtime state into a buffer.
+ * Returns RC_OK on success, or an error indicator.
+ */
+RC_EXPORT int RC_CCONV rc_client_deserialize_progress_sized(rc_client_t* client, const uint8_t* serialized, size_t serialized_size);
+
RC_END_C_DECLS
#endif /* RC_RUNTIME_H */
diff --git a/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;
+ rc_json_iterator_t iterator;
+ memset(&iterator, 0, sizeof(iterator));
+ iterator.json = server_response->body;
+ iterator.end = server_response->body + server_response->body_length;
- const char* title_start = strstr(json, "
");
- if (title_start) {
- title_start += 7;
- if (isdigit((int)*title_start)) {
- const char* title_end = strstr(title_start + 7, "");
- if (title_end) {
- response->error_message = rc_buffer_strncpy(&response->buffer, title_start, title_end - title_start);
- response->succeeded = 0;
- return RC_INVALID_JSON;
- }
+ /* if the title contains an HTTP status code(i.e "404 Not Found"), return the title */
+ if (rc_json_find_substring(&iterator, "")) {
+ const char* title_start = iterator.json + 7;
+ if (isdigit((int)*title_start) && rc_json_find_substring(&iterator, "")) {
+ response->error_message = rc_buffer_strncpy(&response->buffer, title_start, iterator.json - title_start);
+ response->succeeded = 0;
+ return RC_INVALID_JSON;
}
}
- while (*end && *end != '\n' && end - json < 200)
- ++end;
+ /* title not found, or did not start with an error code, return the first line of the response */
+ iterator.json = server_response->body;
- if (end > json && end[-1] == '\r')
- --end;
+ while (iterator.json < iterator.end && *iterator.json != '\n' &&
+ iterator.json - server_response->body < 200) {
+ ++iterator.json;
+ }
- if (end > json)
- response->error_message = rc_buffer_strncpy(&response->buffer, json, end - json);
+ if (iterator.json > server_response->body && iterator.json[-1] == '\r')
+ --iterator.json;
+
+ if (iterator.json > server_response->body)
+ response->error_message = rc_buffer_strncpy(&response->buffer, server_response->body, iterator.json - server_response->body);
response->succeeded = 0;
return RC_INVALID_JSON;
@@ -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;
-
- result = rc_api_init_resolve_hash_request(&request, &resolve_hash_request);
- if (result != RC_OK) {
- callback(result, rc_error_str(result), client, callback_userdata);
- return NULL;
- }
-
- callback_data = (rc_client_load_state_t*)calloc(1, sizeof(rc_client_load_state_t));
- if (!callback_data) {
- callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), client, callback_userdata);
- return NULL;
- }
-
- callback_data->callback = callback;
- callback_data->callback_userdata = callback_userdata;
- callback_data->client = client;
- callback_data->hash = game_hash;
- callback_data->game = game;
-
- async_handle = &callback_data->async_handle;
- rc_client_begin_async(client, async_handle);
- client->callbacks.server_call(&request, rc_client_identify_changed_media_callback, callback_data, client);
-
- rc_api_destroy_request(&request);
-
- /* if handle is no longer valid, the async operation completed synchronously */
- return rc_client_async_handle_valid(client, async_handle) ? async_handle : NULL;
+ if (!hash || !hash[0]) {
+ callback(RC_INVALID_STATE, "hash is required", client, callback_userdata);
+ return NULL;
}
+
+#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
+ if (client->state.external_client && client->state.external_client->begin_change_media_from_hash) {
+ return client->state.external_client->begin_change_media_from_hash(client, hash, callback, callback_userdata);
+ }
+#endif
+
+ rc_mutex_lock(&client->state.mutex);
+ if (client->state.load) {
+ game = client->state.load->game;
+ if (game->public_.console_id == 0) {
+ /* still waiting for game data */
+ pending_media = client->state.load->pending_media;
+ if (pending_media)
+ rc_client_free_pending_media(pending_media);
+
+ pending_media = (rc_client_pending_media_t*)calloc(1, sizeof(*pending_media));
+ if (!pending_media) {
+ rc_mutex_unlock(&client->state.mutex);
+ callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), client, callback_userdata);
+ return NULL;
+ }
+
+ pending_media->hash = strdup(hash);
+ pending_media->callback = callback;
+ pending_media->callback_userdata = callback_userdata;
+
+ client->state.load->pending_media = pending_media;
+ }
+ } else {
+ game = client->game;
+ }
+ rc_mutex_unlock(&client->state.mutex);
+
+ if (!game) {
+ callback(RC_NO_GAME_LOADED, rc_error_str(RC_NO_GAME_LOADED), client, callback_userdata);
+ return NULL;
+ }
+
+ /* still waiting for game data */
+ if (pending_media)
+ return NULL;
+
+ /* check to see if we've already hashed this file. */
+ game_hash = rc_client_find_game_hash(client, hash);
+ return rc_client_begin_change_media_internal(client, game, game_hash, callback, callback_userdata);
}
const rc_client_game_t* rc_client_get_game_info(const rc_client_t* client)
@@ -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
- return fopen(path, "rb");
-#endif
}
+#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 /* 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;