From 94657ae4ab890775b955095eae6d36c530505226 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sat, 24 Feb 2024 14:52:57 +1000 Subject: [PATCH] dep/rcheevos: Update to 3d01191 --- dep/rcheevos/CMakeLists.txt | 2 +- dep/rcheevos/include/rc_api_request.h | 2 +- dep/rcheevos/include/rc_client.h | 19 + .../include/rc_client_raintegration.h | 4 + dep/rcheevos/{src => include}/rc_util.h | 0 dep/rcheevos/rcheevos.vcxproj | 2 +- dep/rcheevos/rcheevos.vcxproj.filters | 4 +- dep/rcheevos/src/rapi/rc_api_common.c | 3 + dep/rcheevos/src/rc_client.c | 121 +++++-- dep/rcheevos/src/rc_client_external.h | 1 + dep/rcheevos/src/rc_client_internal.h | 15 +- dep/rcheevos/src/rc_client_raintegration.c | 23 +- .../src/rc_client_raintegration_internal.h | 4 + dep/rcheevos/src/rc_compat.c | 27 +- dep/rcheevos/src/rc_libretro.c | 88 ++--- dep/rcheevos/src/rc_version.h | 2 +- dep/rcheevos/src/rcheevos/consoleinfo.c | 26 ++ dep/rcheevos/src/rcheevos/rc_internal.h | 2 +- dep/rcheevos/src/rcheevos/runtime_progress.c | 2 +- dep/rcheevos/src/rhash/hash.c | 328 +++++++++++++++++- 20 files changed, 586 insertions(+), 89 deletions(-) rename dep/rcheevos/{src => include}/rc_util.h (100%) diff --git a/dep/rcheevos/CMakeLists.txt b/dep/rcheevos/CMakeLists.txt index e17d4c74e..ddcb543ce 100644 --- a/dep/rcheevos/CMakeLists.txt +++ b/dep/rcheevos/CMakeLists.txt @@ -12,6 +12,7 @@ add_library(rcheevos include/rc_runtime.h include/rc_runtime_types.h include/rc_url.h + include/rc_util.h src/rapi/rc_api_common.c src/rapi/rc_api_common.h src/rapi/rc_api_editor.c @@ -39,7 +40,6 @@ add_library(rcheevos src/rc_compat.c src/rc_compat.h src/rc_util.c - src/rc_util.h src/rc_version.h src/rhash/cdreader.c src/rhash/hash.c diff --git a/dep/rcheevos/include/rc_api_request.h b/dep/rcheevos/include/rc_api_request.h index 4eba7f0d7..dd72fb56d 100644 --- a/dep/rcheevos/include/rc_api_request.h +++ b/dep/rcheevos/include/rc_api_request.h @@ -2,7 +2,7 @@ #define RC_API_REQUEST_H #include "rc_error.h" -#include "../src/rc_util.h" +#include "rc_util.h" #include diff --git a/dep/rcheevos/include/rc_client.h b/dep/rcheevos/include/rc_client.h index 5659aa74f..9631b52dd 100644 --- a/dep/rcheevos/include/rc_client.h +++ b/dep/rcheevos/include/rc_client.h @@ -136,6 +136,11 @@ RC_EXPORT void RC_CCONV rc_client_set_get_time_millisecs_function(rc_client_t* c */ RC_EXPORT void RC_CCONV rc_client_abort_async(rc_client_t* client, rc_client_async_handle_t* async_handle); +/** + * Gets a clause that can be added to the User-Agent to identify the version of rcheevos being used. + */ +RC_EXPORT size_t RC_CCONV rc_client_get_user_agent_clause(rc_client_t* client, char buffer[], size_t buffer_size); + /*****************************************************************************\ | Logging | \*****************************************************************************/ @@ -230,6 +235,20 @@ RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_identify_and_load_g RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_load_game(rc_client_t* client, const char* hash, rc_client_callback_t callback, void* callback_userdata); +/** + * Gets the current progress of the asynchronous load game process. + */ +RC_EXPORT int RC_CCONV rc_client_get_load_game_state(const rc_client_t* client); +enum { + RC_CLIENT_LOAD_GAME_STATE_NONE, + RC_CLIENT_LOAD_GAME_STATE_IDENTIFYING_GAME, + RC_CLIENT_LOAD_GAME_STATE_AWAIT_LOGIN, + RC_CLIENT_LOAD_GAME_STATE_FETCHING_GAME_DATA, + RC_CLIENT_LOAD_GAME_STATE_STARTING_SESSION, + RC_CLIENT_LOAD_GAME_STATE_DONE, + RC_CLIENT_LOAD_GAME_STATE_ABORTED +}; + /** * Unloads the current game. */ diff --git a/dep/rcheevos/include/rc_client_raintegration.h b/dep/rcheevos/include/rc_client_raintegration.h index b2e77cdec..461129104 100644 --- a/dep/rcheevos/include/rc_client_raintegration.h +++ b/dep/rcheevos/include/rc_client_raintegration.h @@ -46,6 +46,8 @@ typedef void (RC_CCONV *rc_client_raintegration_event_handler_t)(const rc_client typedef void (RC_CCONV *rc_client_raintegration_write_memory_func_t)(uint32_t address, uint8_t* buffer, uint32_t num_bytes, rc_client_t* client); +typedef void (RC_CCONV* rc_client_raintegration_get_game_name_func_t)(char* buffer, uint32_t buffer_size, rc_client_t* client); + /* types needed to integrate raintegration */ #ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION @@ -74,6 +76,8 @@ RC_EXPORT void RC_CCONV rc_client_raintegration_update_menu_item(const rc_client RC_EXPORT int RC_CCONV rc_client_raintegration_activate_menu_item(const rc_client_t* client, uint32_t nMenuItemId); RC_EXPORT void RC_CCONV rc_client_raintegration_set_write_memory_function(rc_client_t* client, rc_client_raintegration_write_memory_func_t handler); +RC_EXPORT void RC_CCONV rc_client_raintegration_set_get_game_name_function(rc_client_t* client, rc_client_raintegration_get_game_name_func_t handler); +RC_EXPORT int RC_CCONV rc_client_raintegration_has_modifications(const rc_client_t* client); RC_EXPORT void RC_CCONV rc_client_raintegration_set_event_handler(rc_client_t* client, rc_client_raintegration_event_handler_t handler); diff --git a/dep/rcheevos/src/rc_util.h b/dep/rcheevos/include/rc_util.h similarity index 100% rename from dep/rcheevos/src/rc_util.h rename to dep/rcheevos/include/rc_util.h diff --git a/dep/rcheevos/rcheevos.vcxproj b/dep/rcheevos/rcheevos.vcxproj index 115f00d7f..774ff5d0b 100644 --- a/dep/rcheevos/rcheevos.vcxproj +++ b/dep/rcheevos/rcheevos.vcxproj @@ -43,12 +43,12 @@ + - diff --git a/dep/rcheevos/rcheevos.vcxproj.filters b/dep/rcheevos/rcheevos.vcxproj.filters index 2baa43b20..d262e0bee 100644 --- a/dep/rcheevos/rcheevos.vcxproj.filters +++ b/dep/rcheevos/rcheevos.vcxproj.filters @@ -144,8 +144,10 @@ rcheevos - + + include + \ No newline at end of file diff --git a/dep/rcheevos/src/rapi/rc_api_common.c b/dep/rcheevos/src/rapi/rc_api_common.c index 5835b2801..407efe55f 100644 --- a/dep/rcheevos/src/rapi/rc_api_common.c +++ b/dep/rcheevos/src/rapi/rc_api_common.c @@ -1150,6 +1150,9 @@ static void rc_api_update_host(char** host, const char* hostname) { } void rc_api_set_host(const char* hostname) { + if (hostname && strcmp(hostname, RETROACHIEVEMENTS_HOST) == 0) + hostname = NULL; + rc_api_update_host(&g_host, hostname); if (!hostname) { diff --git a/dep/rcheevos/src/rc_client.c b/dep/rcheevos/src/rc_client.c index 0028f4132..fe593392c 100644 --- a/dep/rcheevos/src/rc_client.c +++ b/dep/rcheevos/src/rc_client.c @@ -5,6 +5,7 @@ #include "rc_api_user.h" #include "rc_consoles.h" #include "rc_hash.h" +#include "rc_version.h" #include "rapi/rc_api_common.h" @@ -611,7 +612,7 @@ static void rc_client_login_callback(const rc_api_server_response_t* server_resp if (login_callback_data->callback) login_callback_data->callback(result, error_message, client, login_callback_data->callback_userdata); - if (load_state && load_state->progress == RC_CLIENT_LOAD_STATE_AWAIT_LOGIN) + if (load_state && load_state->progress == RC_CLIENT_LOAD_GAME_STATE_AWAIT_LOGIN) rc_client_begin_fetch_game_data(load_state); } else { @@ -634,7 +635,7 @@ static void rc_client_login_callback(const rc_api_server_response_t* server_resp RC_CLIENT_LOG_INFO_FORMATTED(client, "%s logged in successfully", login_response.display_name); - if (load_state && load_state->progress == RC_CLIENT_LOAD_STATE_AWAIT_LOGIN) + if (load_state && load_state->progress == RC_CLIENT_LOAD_GAME_STATE_AWAIT_LOGIN) rc_client_begin_fetch_game_data(load_state); if (login_callback_data->callback) @@ -795,7 +796,7 @@ void rc_client_logout(rc_client_t* client) rc_client_unload_game(client); - if (load_state && load_state->progress == RC_CLIENT_LOAD_STATE_AWAIT_LOGIN) + if (load_state && load_state->progress == RC_CLIENT_LOAD_GAME_STATE_AWAIT_LOGIN) rc_client_load_error(load_state, RC_ABORTED, "Login aborted"); } @@ -936,9 +937,9 @@ static int rc_client_end_load_state(rc_client_load_state_t* load_state) * the outstanding_requests count will reach zero and the memory will be free'd then. */ if (remaining_requests == 0) { /* if one of the callbacks called rc_client_load_error, progress will be set to - * RC_CLIENT_LOAD_STATE_UNKNOWN. There's no need to call the callback with RC_ABORTED + * RC_CLIENT_LOAD_STATE_ABORTED. There's no need to call the callback with RC_ABORTED * in that case, as it will have already been called with something more appropriate. */ - if (load_state->progress != RC_CLIENT_LOAD_STATE_UNKNOWN_GAME && load_state->callback) + if (load_state->progress != RC_CLIENT_LOAD_GAME_STATE_ABORTED && load_state->callback) load_state->callback(RC_ABORTED, "The requested game is no longer active", load_state->client, load_state->callback_userdata); rc_client_free_load_state(load_state); @@ -956,7 +957,7 @@ static void rc_client_load_error(rc_client_load_state_t* load_state, int result, rc_mutex_lock(&load_state->client->state.mutex); - load_state->progress = RC_CLIENT_LOAD_STATE_UNKNOWN_GAME; + load_state->progress = RC_CLIENT_LOAD_GAME_STATE_ABORTED; if (load_state->client->state.load == load_state) load_state->client->state.load = NULL; @@ -1020,6 +1021,8 @@ static void rc_client_invalidate_memref_leaderboards(rc_client_game_info_t* game for (; leaderboard < stop; ++leaderboard) { if (leaderboard->public_.state == RC_CLIENT_LEADERBOARD_STATE_DISABLED) continue; + if (!leaderboard->lboard) + continue; if (rc_trigger_contains_memref(&leaderboard->lboard->start, memref)) leaderboard->public_.state = RC_CLIENT_LEADERBOARD_STATE_DISABLED; @@ -1032,8 +1035,7 @@ static void rc_client_invalidate_memref_leaderboards(rc_client_game_info_t* game else continue; - if (leaderboard->lboard) - leaderboard->lboard->state = RC_LBOARD_STATE_DISABLED; + leaderboard->lboard->state = RC_LBOARD_STATE_DISABLED; RC_CLIENT_LOG_WARN_FORMATTED(client, "Disabled leaderboard %u. Invalid address %06X", leaderboard->public_.id, memref->address); } @@ -1301,6 +1303,8 @@ static void rc_client_activate_leaderboards(rc_client_game_info_t* game, rc_clie { rc_client_leaderboard_info_t* leaderboard; rc_client_leaderboard_info_t* stop; + const uint8_t leaderboards_allowed = + client->state.hardcore || client->state.allow_leaderboards_in_softcore; uint32_t active_count = 0; rc_client_subset_info_t* subset = game->subsets; @@ -1317,7 +1321,7 @@ static void rc_client_activate_leaderboards(rc_client_game_info_t* game, rc_clie continue; case RC_CLIENT_LEADERBOARD_STATE_INACTIVE: - if (client->state.hardcore) { + if (leaderboards_allowed) { rc_reset_lboard(leaderboard->lboard); leaderboard->public_.state = RC_CLIENT_LEADERBOARD_STATE_ACTIVE; ++active_count; @@ -1325,7 +1329,7 @@ static void rc_client_activate_leaderboards(rc_client_game_info_t* game, rc_clie break; default: - if (client->state.hardcore) + if (leaderboards_allowed) ++active_count; else leaderboard->public_.state = RC_CLIENT_LEADERBOARD_STATE_INACTIVE; @@ -1403,11 +1407,11 @@ static void rc_client_activate_game(rc_client_load_state_t* load_state, rc_api_s rc_mutex_lock(&client->state.mutex); load_state->progress = (client->state.load == load_state) ? - RC_CLIENT_LOAD_STATE_DONE : RC_CLIENT_LOAD_STATE_UNKNOWN_GAME; + RC_CLIENT_LOAD_GAME_STATE_DONE : RC_CLIENT_LOAD_GAME_STATE_ABORTED; client->state.load = NULL; rc_mutex_unlock(&client->state.mutex); - if (load_state->progress != RC_CLIENT_LOAD_STATE_DONE) { + if (load_state->progress != RC_CLIENT_LOAD_GAME_STATE_DONE) { /* previous load state was aborted */ if (load_state->callback) load_state->callback(RC_ABORTED, "The requested game is no longer active", client, load_state->callback_userdata); @@ -1557,7 +1561,7 @@ static void rc_client_begin_start_session(rc_client_load_state_t* load_state) rc_client_load_error(load_state, result, rc_error_str(result)); } else { - rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_STATE_STARTING_SESSION, 1); + rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_GAME_STATE_STARTING_SESSION, 1); RC_CLIENT_LOG_VERBOSE_FORMATTED(client, "Starting session for game %u", start_session_params.game_id); rc_client_begin_async(client, &load_state->async_handle); client->callbacks.server_call(&start_session_request, rc_client_start_session_callback, load_state, client); @@ -1872,7 +1876,7 @@ static void rc_client_fetch_game_data_callback(const rc_api_server_response_t* s } /* kick off the start session request while we process the game data */ - rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_STATE_STARTING_SESSION, 1); + rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_GAME_STATE_STARTING_SESSION, 1); if (load_state->client->state.spectator_mode != RC_CLIENT_SPECTATOR_MODE_OFF) { /* we can't unlock achievements without a session, lock spectator mode for the game */ load_state->client->state.spectator_mode = RC_CLIENT_SPECTATOR_MODE_LOCKED; @@ -2006,18 +2010,30 @@ static void rc_client_begin_fetch_game_data(rc_client_load_state_t* load_state) /* only a single hash was tried, capture it */ load_state->game->public_.console_id = load_state->hash_console_id; load_state->game->public_.hash = load_state->hash->hash; + + if (client->callbacks.identify_unknown_hash) { + load_state->hash->game_id = client->callbacks.identify_unknown_hash( + load_state->hash_console_id, load_state->hash->hash, client, load_state->callback_userdata); + + if (load_state->hash->game_id != 0) { + RC_CLIENT_LOG_INFO_FORMATTED(load_state->client, "Client says to load game %u for unidentified hash %s", + load_state->hash->game_id, load_state->hash->hash); + } + } } - load_state->game->public_.title = "Unknown Game"; - load_state->game->public_.badge_name = ""; - client->game = load_state->game; - load_state->game = NULL; + if (load_state->hash->game_id == 0) { + load_state->game->public_.title = "Unknown Game"; + load_state->game->public_.badge_name = ""; + client->game = load_state->game; + load_state->game = NULL; - rc_client_load_error(load_state, RC_NO_GAME_LOADED, "Unknown game"); - return; + rc_client_load_error(load_state, RC_NO_GAME_LOADED, "Unknown game"); + return; + } } - if (load_state->hash->hash[0] != '[') { + if (load_state->hash->hash[0] != '[') { /* not [NO HASH] or [SUBSETxx] */ load_state->game->public_.id = load_state->hash->game_id; load_state->game->public_.hash = load_state->hash->hash; } @@ -2028,7 +2044,7 @@ static void rc_client_begin_fetch_game_data(rc_client_load_state_t* load_state) rc_mutex_lock(&client->state.mutex); result = client->state.user; if (result == RC_CLIENT_USER_STATE_LOGIN_REQUESTED) - load_state->progress = RC_CLIENT_LOAD_STATE_AWAIT_LOGIN; + load_state->progress = RC_CLIENT_LOAD_GAME_STATE_AWAIT_LOGIN; rc_mutex_unlock(&client->state.mutex); switch (result) { @@ -2055,7 +2071,7 @@ static void rc_client_begin_fetch_game_data(rc_client_load_state_t* load_state) return; } - rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_STATE_FETCHING_GAME_DATA, 1); + rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_GAME_STATE_FETCHING_GAME_DATA, 1); RC_CLIENT_LOG_VERBOSE_FORMATTED(client, "Fetching data for game %u", fetch_game_data_request.game_id); rc_client_begin_async(client, &load_state->async_handle); @@ -2197,7 +2213,7 @@ static rc_client_async_handle_t* rc_client_load_game(rc_client_load_state_t* loa return NULL; } - rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_STATE_IDENTIFYING_GAME, 1); + rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_GAME_STATE_IDENTIFYING_GAME, 1); rc_client_begin_async(client, &load_state->async_handle); client->callbacks.server_call(&request, rc_client_identify_game_callback, load_state, client); @@ -2336,6 +2352,20 @@ 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); } +int rc_client_get_load_game_state(const rc_client_t* client) +{ + int state = RC_CLIENT_LOAD_GAME_STATE_NONE; + if (client) { + const rc_client_load_state_t* load_state = client->state.load; + if (load_state) + state = load_state->progress; + else if (client->game) + state = RC_CLIENT_LOAD_GAME_STATE_DONE; + } + + return state; +} + static void rc_client_game_mark_ui_to_be_hidden(rc_client_t* client, rc_client_game_info_t* game) { rc_client_achievement_info_t* achievement; @@ -3749,7 +3779,7 @@ int rc_client_has_leaderboards(rc_client_t* client) return result; } -static void rc_client_allocate_leaderboard_tracker(rc_client_game_info_t* game, rc_client_leaderboard_info_t* leaderboard) +void rc_client_allocate_leaderboard_tracker(rc_client_game_info_t* game, rc_client_leaderboard_info_t* leaderboard) { rc_client_leaderboard_tracker_info_t* tracker; rc_client_leaderboard_tracker_info_t* available_tracker = NULL; @@ -4007,6 +4037,11 @@ static void rc_client_submit_leaderboard_entry(rc_client_t* client, rc_client_le { rc_client_submit_leaderboard_entry_callback_data_t* callback_data; + if (!client->state.hardcore) { + RC_CLIENT_LOG_INFO_FORMATTED(client, "Leaderboard %u entry submission not allowed in softcore", leaderboard->public_.id); + return; + } + if (client->callbacks.can_submit_leaderboard_entry && !client->callbacks.can_submit_leaderboard_entry(leaderboard->public_.id, client)) { RC_CLIENT_LOG_INFO_FORMATTED(client, "Leaderboard %u entry submission blocked by client", leaderboard->public_.id); @@ -4545,6 +4580,11 @@ static void rc_client_do_frame_process_achievements(rc_client_t* client, rc_clie old_state = trigger->state; new_state = rc_evaluate_trigger(trigger, client->state.legacy_peek, client, NULL); + /* trigger->state doesn't actually change to RESET - RESET just serves as a notification. + * we don't care about that particular notification, so look at the actual state. */ + if (new_state == RC_TRIGGER_STATE_RESET) + new_state = trigger->state; + /* if the measured value changed and the achievement hasn't triggered, show a progress indicator */ if (trigger->measured_value != old_measured_value && old_measured_value != RC_MEASURED_UNKNOWN && trigger->measured_value <= trigger->measured_target && @@ -4940,7 +4980,7 @@ void rc_client_do_frame(rc_client_t* client) if (client->game->pending_events & RC_CLIENT_GAME_PENDING_EVENT_PROGRESS_TRACKER) rc_client_do_frame_update_progress_tracker(client, client->game); - if (client->state.hardcore) { + if (client->state.hardcore || client->state.allow_leaderboards_in_softcore) { for (subset = client->game->subsets; subset; subset = subset->next) { if (subset->active) rc_client_do_frame_process_leaderboards(client, subset); @@ -5406,7 +5446,9 @@ static void rc_client_disable_hardcore(rc_client_t* client) if (client->game) { rc_client_toggle_hardcore_achievements(client->game, client, RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE); - rc_client_deactivate_leaderboards(client->game, client); + + if (!client->state.allow_leaderboards_in_softcore) + rc_client_deactivate_leaderboards(client->game, client); } } @@ -5592,3 +5634,28 @@ void rc_client_set_host(const rc_client_t* client, const char* hostname) client->state.external_client->set_host(hostname); #endif } + +size_t rc_client_get_user_agent_clause(rc_client_t* client, char buffer[], size_t buffer_size) +{ + size_t result; + +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client && client->state.external_client && client->state.external_client->get_user_agent_clause) { + result = client->state.external_client->get_user_agent_clause(buffer, buffer_size); + if (result > 0) { + result += snprintf(buffer + result, buffer_size - result, " rc_client/" RCHEEVOS_VERSION_STRING); + buffer[buffer_size - 1] = '\0'; + return result; + } + } +#else + (void)client; +#endif + + result = snprintf(buffer, buffer_size, "rcheevos/" RCHEEVOS_VERSION_STRING); + + /* some implementations of snprintf will fill the buffer without null terminating. + * make sure the buffer is null terminated */ + buffer[buffer_size - 1] = '\0'; + return result; +} diff --git a/dep/rcheevos/src/rc_client_external.h b/dep/rcheevos/src/rc_client_external.h index 1d625421e..a519e428e 100644 --- a/dep/rcheevos/src/rc_client_external.h +++ b/dep/rcheevos/src/rc_client_external.h @@ -73,6 +73,7 @@ typedef struct rc_client_external_t rc_client_external_set_read_memory_func_t set_read_memory; rc_client_external_set_get_time_millisecs_func_t set_get_time_millisecs; rc_client_external_set_string_func_t set_host; + rc_client_external_copy_string_func_t get_user_agent_clause; rc_client_external_set_int_func_t set_hardcore_enabled; rc_client_external_get_int_func_t get_hardcore_enabled; diff --git a/dep/rcheevos/src/rc_client_internal.h b/dep/rcheevos/src/rc_client_internal.h index 509c56750..de8258630 100644 --- a/dep/rcheevos/src/rc_client_internal.h +++ b/dep/rcheevos/src/rc_client_internal.h @@ -26,6 +26,8 @@ typedef void (RC_CCONV *rc_client_post_process_game_data_response_t)(const rc_ap typedef int (RC_CCONV *rc_client_can_submit_achievement_unlock_t)(uint32_t achievement_id, rc_client_t* client); typedef int (RC_CCONV *rc_client_can_submit_leaderboard_entry_t)(uint32_t leaderboard_id, rc_client_t* client); typedef int (RC_CCONV *rc_client_rich_presence_override_t)(rc_client_t* client, char buffer[], size_t buffersize); +typedef uint32_t (RC_CCONV* rc_client_identify_hash_func_t)(uint32_t console_id, const char* hash, + rc_client_t* client, void* callback_userdata); typedef struct rc_client_callbacks_t { rc_client_read_memory_func_t read_memory; @@ -33,6 +35,7 @@ typedef struct rc_client_callbacks_t { rc_client_server_call_t server_call; rc_client_message_callback_t log_call; rc_get_time_millisecs_func_t get_time_millisecs; + rc_client_identify_hash_func_t identify_unknown_hash; rc_client_post_process_game_data_response_t post_process_game_data_response; rc_client_can_submit_achievement_unlock_t can_submit_achievement_unlock; rc_client_can_submit_leaderboard_entry_t can_submit_leaderboard_entry; @@ -265,16 +268,6 @@ void rc_client_update_active_leaderboards(rc_client_game_info_t* game); | Client | \*****************************************************************************/ -enum { - RC_CLIENT_LOAD_STATE_NONE, - RC_CLIENT_LOAD_STATE_IDENTIFYING_GAME, - RC_CLIENT_LOAD_STATE_AWAIT_LOGIN, - RC_CLIENT_LOAD_STATE_FETCHING_GAME_DATA, - RC_CLIENT_LOAD_STATE_STARTING_SESSION, - RC_CLIENT_LOAD_STATE_DONE, - RC_CLIENT_LOAD_STATE_UNKNOWN_GAME -}; - enum { RC_CLIENT_USER_STATE_NONE, RC_CLIENT_USER_STATE_LOGIN_REQUESTED, @@ -325,6 +318,7 @@ typedef struct rc_client_state_t { uint8_t log_level; uint8_t user; uint8_t disconnect; + uint8_t allow_leaderboards_in_softcore; struct rc_client_load_state_t* load; struct rc_client_async_handle_t* async_handles[4]; @@ -386,6 +380,7 @@ enum { void rc_client_set_legacy_peek(rc_client_t* client, int method); +void rc_client_allocate_leaderboard_tracker(rc_client_game_info_t* game, rc_client_leaderboard_info_t* leaderboard); void rc_client_release_leaderboard_tracker(rc_client_game_info_t* game, rc_client_leaderboard_info_t* leaderboard); RC_END_C_DECLS diff --git a/dep/rcheevos/src/rc_client_raintegration.c b/dep/rcheevos/src/rc_client_raintegration.c index efea7e449..227d03a84 100644 --- a/dep/rcheevos/src/rc_client_raintegration.c +++ b/dep/rcheevos/src/rc_client_raintegration.c @@ -77,7 +77,9 @@ static void rc_client_raintegration_load_dll(rc_client_t* client, raintegration->get_menu = (rc_client_raintegration_get_menu_func_t)GetProcAddress(hDLL, "_Rcheevos_RAIntegrationGetMenu"); raintegration->activate_menu_item = (rc_client_raintegration_activate_menuitem_func_t)GetProcAddress(hDLL, "_Rcheevos_ActivateRAIntegrationMenuItem"); raintegration->set_write_memory_function = (rc_client_raintegration_set_write_memory_func_t)GetProcAddress(hDLL, "_Rcheevos_SetRAIntegrationWriteMemoryFunction"); + raintegration->set_get_game_name_function = (rc_client_raintegration_set_get_game_name_func_t)GetProcAddress(hDLL, "_Rcheevos_SetRAIntegrationGetGameNameFunction"); raintegration->set_event_handler = (rc_client_raintegration_set_event_handler_func_t)GetProcAddress(hDLL, "_Rcheevos_SetRAIntegrationEventHandler"); + raintegration->has_modifications = (rc_client_raintegration_get_int_func_t)GetProcAddress(hDLL, "_Rcheevos_HasModifications"); if (!raintegration->get_version || !raintegration->init_client || @@ -147,7 +149,8 @@ static void rc_client_init_raintegration(rc_client_t* client, const char* host_url = client->state.raintegration->get_host_url(); if (host_url) { if (strcmp(host_url, "OFFLINE") != 0) { - rc_client_set_host(client, host_url); + if (strcmp(host_url, "https://retroachievements.org") != 0) + rc_client_set_host(client, host_url); } else if (client->state.raintegration->init_client_offline) { init_func = client->state.raintegration->init_client_offline; @@ -363,6 +366,12 @@ void rc_client_raintegration_set_write_memory_function(rc_client_t* client, rc_c client->state.raintegration->set_write_memory_function(client, handler); } +void rc_client_raintegration_set_get_game_name_function(rc_client_t* client, rc_client_raintegration_get_game_name_func_t handler) +{ + if (client && client->state.raintegration && client->state.raintegration->set_get_game_name_function) + client->state.raintegration->set_get_game_name_function(client, handler); +} + void rc_client_raintegration_set_event_handler(rc_client_t* client, rc_client_raintegration_event_handler_t handler) { @@ -382,6 +391,18 @@ const rc_client_raintegration_menu_t* rc_client_raintegration_get_menu(const rc_ return client->state.raintegration->get_menu(); } +int rc_client_raintegration_has_modifications(const rc_client_t* client) +{ + if (!client || !client->state.raintegration || + !client->state.raintegration->bIsInited || + !client->state.raintegration->has_modifications) + { + return 0; + } + + return client->state.raintegration->has_modifications(); +} + void rc_client_raintegration_rebuild_submenu(rc_client_t* client, HMENU hMenu) { HMENU hPopupMenu = NULL; diff --git a/dep/rcheevos/src/rc_client_raintegration_internal.h b/dep/rcheevos/src/rc_client_raintegration_internal.h index 530d98e1a..ce7c98b03 100644 --- a/dep/rcheevos/src/rc_client_raintegration_internal.h +++ b/dep/rcheevos/src/rc_client_raintegration_internal.h @@ -20,7 +20,9 @@ typedef void (RC_CCONV* rc_client_raintegration_hwnd_action_func_t)(HWND hWnd); typedef const rc_client_raintegration_menu_t* (RC_CCONV* rc_client_raintegration_get_menu_func_t)(void); typedef int (RC_CCONV* rc_client_raintegration_activate_menuitem_func_t)(uint32_t nMenuItemId); typedef void (RC_CCONV* rc_client_raintegration_set_write_memory_func_t)(rc_client_t* pClient, rc_client_raintegration_write_memory_func_t handler); +typedef void (RC_CCONV* rc_client_raintegration_set_get_game_name_func_t)(rc_client_t* pClient, rc_client_raintegration_get_game_name_func_t handler); typedef void (RC_CCONV* rc_client_raintegration_set_event_handler_func_t)(rc_client_t* pClient, rc_client_raintegration_event_handler_t handler); +typedef int (RC_CCONV* rc_client_raintegration_get_int_func_t)(void); typedef struct rc_client_raintegration_t { @@ -37,9 +39,11 @@ typedef struct rc_client_raintegration_t rc_client_raintegration_hwnd_action_func_t update_main_window_handle; rc_client_raintegration_set_write_memory_func_t set_write_memory_function; + rc_client_raintegration_set_get_game_name_func_t set_get_game_name_function; rc_client_raintegration_set_event_handler_func_t set_event_handler; rc_client_raintegration_get_menu_func_t get_menu; rc_client_raintegration_activate_menuitem_func_t activate_menu_item; + rc_client_raintegration_get_int_func_t has_modifications; rc_client_raintegration_get_external_client_func_t get_external_client; diff --git a/dep/rcheevos/src/rc_compat.c b/dep/rcheevos/src/rc_compat.c index 0ba7601ad..6a8a5de57 100644 --- a/dep/rcheevos/src/rc_compat.c +++ b/dep/rcheevos/src/rc_compat.c @@ -85,7 +85,8 @@ struct tm* rc_gmtime_s(struct tm* buf, const time_t* timer) #endif #ifndef RC_NO_THREADS -#ifdef _WIN32 + +#if defined(_WIN32) /* https://gist.github.com/roxlu/1c1af99f92bafff9d8d9 */ @@ -113,6 +114,30 @@ void rc_mutex_unlock(rc_mutex_t* mutex) ReleaseMutex(mutex->handle); } +#elif defined(GEKKO) + +/* https://github.com/libretro/RetroArch/pull/16116 */ + +void rc_mutex_init(rc_mutex_t* mutex) +{ + LWP_MutexInit(mutex, NULL); +} + +void rc_mutex_destroy(rc_mutex_t* mutex) +{ + LWP_MutexDestroy(mutex); +} + +void rc_mutex_lock(rc_mutex_t* mutex) +{ + LWP_MutexLock(mutex); +} + +void rc_mutex_unlock(rc_mutex_t* mutex) +{ + LWP_MutexUnlock(mutex); +} + #else void rc_mutex_init(rc_mutex_t* mutex) diff --git a/dep/rcheevos/src/rc_libretro.c b/dep/rcheevos/src/rc_libretro.c index 70bf06be1..d94d6d5b4 100644 --- a/dep/rcheevos/src/rc_libretro.c +++ b/dep/rcheevos/src/rc_libretro.c @@ -51,6 +51,11 @@ static const rc_disallowed_setting_t _rc_disallowed_dolphin_settings[] = { { NULL, NULL } }; +static const rc_disallowed_setting_t _rc_disallowed_dosbox_pure_settings[] = { + { "dosbox_pure_strict_mode", "false" }, + { NULL, NULL } +}; + static const rc_disallowed_setting_t _rc_disallowed_duckstation_settings[] = { { "duckstation_CDROM.LoadImagePatches", "true" }, { NULL, NULL } @@ -149,6 +154,7 @@ static const rc_disallowed_core_settings_t rc_disallowed_core_settings[] = { { "bsnes-mercury", _rc_disallowed_bsnes_settings }, { "cap32", _rc_disallowed_cap32_settings }, { "dolphin-emu", _rc_disallowed_dolphin_settings }, + { "DOSBox-pure", _rc_disallowed_dosbox_pure_settings }, { "DuckStation", _rc_disallowed_duckstation_settings }, { "ecwolf", _rc_disallowed_ecwolf_settings }, { "FCEUmm", _rc_disallowed_fceumm_settings }, @@ -324,27 +330,38 @@ uint8_t* rc_libretro_memory_find(const rc_libretro_memory_regions_t* regions, ui uint32_t rc_libretro_memory_read(const rc_libretro_memory_regions_t* regions, uint32_t address, uint8_t* buffer, uint32_t num_bytes) { - uint32_t i; + uint32_t bytes_read = 0; uint32_t avail; + uint32_t i; for (i = 0; i < regions->count; ++i) { const size_t size = regions->size[i]; - if (address < size) { - if (regions->data[i] == NULL) - break; - - avail = (unsigned)(size - address); - if (avail < num_bytes) - return avail; - - memcpy(buffer, ®ions->data[i][address], num_bytes); - return num_bytes; + if (address >= size) { + /* address is not in this block, adjust and look at next block */ + address -= (unsigned)size; + continue; } - address -= (unsigned)size; + if (regions->data[i] == NULL) /* no memory associated to this block. abort */ + break; + + avail = (unsigned)(size - address); + if (avail >= num_bytes) { + /* requested memory is fully within this block, copy and return it */ + memcpy(buffer, ®ions->data[i][address], num_bytes); + bytes_read += num_bytes; + return bytes_read; + } + + /* copy whatever is available in this block, and adjust for the next block */ + memcpy(buffer, ®ions->data[i][address], avail); + buffer += avail; + bytes_read += avail; + num_bytes -= avail; + address = 0; } - return 0; + return bytes_read; } void rc_libretro_init_verbose_message_callback(rc_libretro_message_callback callback) { @@ -671,8 +688,7 @@ void rc_libretro_hash_set_init(struct rc_libretro_hash_set_t* hash_set, return; file_handle = rc_file_open(m3u_path); - if (!file_handle) - { + if (!file_handle) { rc_hash_error("Could not open playlist"); return; } @@ -682,8 +698,7 @@ void rc_libretro_hash_set_init(struct rc_libretro_hash_set_t* hash_set, rc_file_seek(file_handle, 0, SEEK_SET); m3u_contents = (char*)malloc((size_t)file_len + 1); - if (m3u_contents) - { + if (m3u_contents) { rc_file_read(file_handle, m3u_contents, (int)file_len); m3u_contents[file_len] = '\0'; @@ -696,23 +711,19 @@ void rc_libretro_hash_set_init(struct rc_libretro_hash_set_t* hash_set, while (isspace((int)*ptr)) ++ptr; - if (*ptr == '#') - { + if (*ptr == '#') { /* ignore comment unless it's the special SAVEDISK extension */ - if (memcmp(ptr, "#SAVEDISK:", 10) == 0) - { + if (memcmp(ptr, "#SAVEDISK:", 10) == 0) { /* get the path to the save disk from the frontend, assign it a bogus hash so * it doesn't get hashed later */ - if (get_image_path(index, image_path, sizeof(image_path))) - { + if (get_image_path(index, image_path, sizeof(image_path))) { const char save_disk_hash[33] = "[SAVE DISK]"; rc_libretro_hash_set_add(hash_set, image_path, -1, save_disk_hash); ++index; } } } - else - { + else { /* non-empty line, tally a file */ ++index; } @@ -726,8 +737,7 @@ void rc_libretro_hash_set_init(struct rc_libretro_hash_set_t* hash_set, free(m3u_contents); } - if (hash_set->entries_count > 0) - { + if (hash_set->entries_count > 0) { /* at least one save disk was found. make sure the core supports the #SAVEDISK: extension by * asking for the last expected disk. if it's not found, assume no #SAVEDISK: support */ if (!get_image_path(index - 1, image_path, sizeof(image_path))) @@ -759,13 +769,10 @@ void rc_libretro_hash_set_add(struct rc_libretro_hash_set_t* hash_set, struct rc_libretro_hash_entry_t* scan; struct rc_libretro_hash_entry_t* stop = hash_set->entries + hash_set->entries_count;; - if (path_djb2) - { + if (path_djb2) { /* attempt to match the path */ - for (scan = hash_set->entries; scan < stop; ++scan) - { - if (scan->path_djb2 == path_djb2) - { + for (scan = hash_set->entries; scan < stop; ++scan) { + if (scan->path_djb2 == path_djb2) { entry = scan; break; } @@ -775,19 +782,20 @@ void rc_libretro_hash_set_add(struct rc_libretro_hash_set_t* hash_set, if (!entry) { /* entry not found, allocate a new one */ - if (hash_set->entries_size == 0) - { + if (hash_set->entries_size == 0) { hash_set->entries_size = 4; hash_set->entries = (struct rc_libretro_hash_entry_t*) malloc(hash_set->entries_size * sizeof(struct rc_libretro_hash_entry_t)); } - else if (hash_set->entries_count == hash_set->entries_size) - { + else if (hash_set->entries_count == hash_set->entries_size) { hash_set->entries_size += 4; hash_set->entries = (struct rc_libretro_hash_entry_t*)realloc(hash_set->entries, hash_set->entries_size * sizeof(struct rc_libretro_hash_entry_t)); } + if (hash_set->entries == NULL) /* unexpected, but better than crashing */ + return; + entry = hash_set->entries + hash_set->entries_count++; } @@ -802,8 +810,7 @@ const char* rc_libretro_hash_set_get_hash(const struct rc_libretro_hash_set_t* h const uint32_t path_djb2 = rc_libretro_djb2(path); struct rc_libretro_hash_entry_t* scan = hash_set->entries; struct rc_libretro_hash_entry_t* stop = scan + hash_set->entries_count; - for (; scan < stop; ++scan) - { + for (; scan < stop; ++scan) { if (scan->path_djb2 == path_djb2) return scan->hash; } @@ -815,8 +822,7 @@ int rc_libretro_hash_set_get_game_id(const struct rc_libretro_hash_set_t* hash_s { struct rc_libretro_hash_entry_t* scan = hash_set->entries; struct rc_libretro_hash_entry_t* stop = scan + hash_set->entries_count; - for (; scan < stop; ++scan) - { + for (; scan < stop; ++scan) { if (memcmp(scan->hash, hash, sizeof(scan->hash)) == 0) return scan->game_id; } diff --git a/dep/rcheevos/src/rc_version.h b/dep/rcheevos/src/rc_version.h index 6aa57af6c..daf57e1cb 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 0 +#define RCHEEVOS_VERSION_MINOR 1 #define RCHEEVOS_VERSION_PATCH 0 #define RCHEEVOS_MAKE_VERSION(major, minor, patch) (major * 1000000 + minor * 1000 + patch) diff --git a/dep/rcheevos/src/rcheevos/consoleinfo.c b/dep/rcheevos/src/rcheevos/consoleinfo.c index 95eb406cf..427db73b2 100644 --- a/dep/rcheevos/src/rcheevos/consoleinfo.c +++ b/dep/rcheevos/src/rcheevos/consoleinfo.c @@ -567,6 +567,29 @@ static const rc_memory_region_t _rc_memory_regions_msx[] = { }; static const rc_memory_regions_t rc_memory_regions_msx = { _rc_memory_regions_msx, 1 }; +/* ===== MS DOS ===== */ +static const rc_memory_region_t _rc_memory_regions_ms_dos[] = { + /* DOS emulators split the 640 KB conventional memory into two regions. + * First the part of the conventional memory given to the running game at $000000. + * The part of the conventional memory containing DOS and BIOS controlled memory + * is at $100000. The length of these can vary depending on the hardware + * and DOS version (or emulated DOS shell). + * These first two regions will only ever total to 640 KB but the regions map + * to 1 MB bounds to make resulting memory addresses more readable. + * When emulating a game not under DOS (so called 'PC Booter' games), the entirety + * of the 640 KB conventional memory block will be at $000000. + */ + { 0x00000000U, 0x0009FFFFU, 0x00000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Game Conventional Memory" }, + { 0x000A0000U, 0x000FFFFFU, 0x000A0000U, RC_MEMORY_TYPE_UNUSED, "Padding to align OS Conventional Memory" }, + { 0x00100000U, 0x0019FFFFU, 0x00100000U, RC_MEMORY_TYPE_SYSTEM_RAM, "OS Conventional Memory" }, + { 0x001A0000U, 0x001FFFFFU, 0x001A0000U, RC_MEMORY_TYPE_UNUSED, "Padding to align Expanded Memory" }, + /* Last is all the expanded memory which for now we map up to 64 MB which should be + * enough for the games we want to cover. An emulator might emulate more than that. + */ + { 0x00200000U, 0x041FFFFFU, 0x00200000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Expanded Memory" } +}; +static const rc_memory_regions_t rc_memory_regions_ms_dos = { _rc_memory_regions_ms_dos, 5 }; + /* ===== Neo Geo Pocket ===== */ /* http://neopocott.emuunlim.com/docs/tech-11.txt */ static const rc_memory_region_t _rc_memory_regions_neo_geo_pocket[] = { @@ -972,6 +995,9 @@ const rc_memory_regions_t* rc_console_memory_regions(uint32_t console_id) case RC_CONSOLE_MSX: return &rc_memory_regions_msx; + case RC_CONSOLE_MS_DOS: + return &rc_memory_regions_ms_dos; + case RC_CONSOLE_NEOGEO_POCKET: return &rc_memory_regions_neo_geo_pocket; diff --git a/dep/rcheevos/src/rcheevos/rc_internal.h b/dep/rcheevos/src/rcheevos/rc_internal.h index 8dac46146..135423980 100644 --- a/dep/rcheevos/src/rcheevos/rc_internal.h +++ b/dep/rcheevos/src/rcheevos/rc_internal.h @@ -2,7 +2,7 @@ #define RC_INTERNAL_H #include "rc_runtime_types.h" -#include "../rc_util.h" +#include "rc_util.h" RC_BEGIN_C_DECLS diff --git a/dep/rcheevos/src/rcheevos/runtime_progress.c b/dep/rcheevos/src/rcheevos/runtime_progress.c index d301377db..fd951dbd5 100644 --- a/dep/rcheevos/src/rcheevos/runtime_progress.c +++ b/dep/rcheevos/src/rcheevos/runtime_progress.c @@ -1,7 +1,7 @@ #include "rc_runtime.h" #include "rc_internal.h" -#include "../rc_util.h" +#include "rc_util.h" #include "../rhash/md5.h" #include diff --git a/dep/rcheevos/src/rhash/hash.c b/dep/rcheevos/src/rhash/hash.c index 382076c20..5d65512b6 100644 --- a/dep/rcheevos/src/rhash/hash.c +++ b/dep/rcheevos/src/rhash/hash.c @@ -16,6 +16,7 @@ #define MAX_BUFFER_SIZE 64 * 1024 * 1024 const char* rc_path_get_filename(const char* path); +static int rc_hash_whole_file(char hash[33], const char* path); /* ===================================================== */ @@ -511,8 +512,8 @@ static int rc_hash_cd_file(md5_state_t* md5, void* track_handle, uint32_t sector verbose_message_callback(message); } - if (size < (unsigned)num_read) - size = (unsigned)num_read; + if (size < (unsigned)num_read) /* we read a whole sector - only hash the part containing file data */ + num_read = (size_t)size; do { @@ -679,6 +680,293 @@ static int rc_hash_7800(char hash[33], const uint8_t* buffer, size_t buffer_size return rc_hash_buffer(hash, buffer, buffer_size); } +struct rc_hash_zip_idx +{ + size_t length; + uint8_t* data; +}; + +static int rc_hash_zip_idx_sort(const void* a, const void* b) +{ + struct rc_hash_zip_idx *A = (struct rc_hash_zip_idx*)a, *B = (struct rc_hash_zip_idx*)b; + size_t len = (A->length < B->length ? A->length : B->length); + return memcmp(A->data, B->data, len); +} + +static int rc_hash_zip_file(md5_state_t* md5, void* file_handle) +{ + uint8_t buf[2048], *alloc_buf, *cdir_start, *cdir_max, *cdir, *hashdata, eocdirhdr_size, cdirhdr_size; + uint32_t cdir_entry_len; + size_t sizeof_idx, indices_offset, alloc_size; + int64_t i_file, archive_size, ecdh_ofs, total_files, cdir_size, cdir_ofs; + struct rc_hash_zip_idx* hashindices, *hashindex; + + rc_file_seek(file_handle, 0, SEEK_END); + archive_size = rc_file_tell(file_handle); + + /* Basic sanity checks - reject files which are too small */ + eocdirhdr_size = 22; /* the 'end of central directory header' is 22 bytes */ + if (archive_size < eocdirhdr_size) + return rc_hash_error("ZIP is too small"); + + /* Macros used for reading ZIP and writing to a buffer for hashing (undefined again at the end of the function) */ + #define RC_ZIP_READ_LE16(p) ((uint16_t)(((const uint8_t*)(p))[0]) | ((uint16_t)(((const uint8_t*)(p))[1]) << 8U)) + #define RC_ZIP_READ_LE32(p) ((uint32_t)(((const uint8_t*)(p))[0]) | ((uint32_t)(((const uint8_t*)(p))[1]) << 8U) | ((uint32_t)(((const uint8_t*)(p))[2]) << 16U) | ((uint32_t)(((const uint8_t*)(p))[3]) << 24U)) + #define RC_ZIP_READ_LE64(p) ((uint64_t)(((const uint8_t*)(p))[0]) | ((uint64_t)(((const uint8_t*)(p))[1]) << 8U) | ((uint64_t)(((const uint8_t*)(p))[2]) << 16U) | ((uint64_t)(((const uint8_t*)(p))[3]) << 24U) | ((uint64_t)(((const uint8_t*)(p))[4]) << 32U) | ((uint64_t)(((const uint8_t*)(p))[5]) << 40U) | ((uint64_t)(((const uint8_t*)(p))[6]) << 48U) | ((uint64_t)(((const uint8_t*)(p))[7]) << 56U)) + #define RC_ZIP_WRITE_LE32(p,v) { ((uint8_t*)(p))[0] = (uint8_t)((uint32_t)(v) & 0xFF); ((uint8_t*)(p))[1] = (uint8_t)(((uint32_t)(v) >> 8) & 0xFF); ((uint8_t*)(p))[2] = (uint8_t)(((uint32_t)(v) >> 16) & 0xFF); ((uint8_t*)(p))[3] = (uint8_t)((uint32_t)(v) >> 24); } + #define RC_ZIP_WRITE_LE64(p,v) { ((uint8_t*)(p))[0] = (uint8_t)((uint64_t)(v) & 0xFF); ((uint8_t*)(p))[1] = (uint8_t)(((uint64_t)(v) >> 8) & 0xFF); ((uint8_t*)(p))[2] = (uint8_t)(((uint64_t)(v) >> 16) & 0xFF); ((uint8_t*)(p))[3] = (uint8_t)(((uint64_t)(v) >> 24) & 0xFF); ((uint8_t*)(p))[4] = (uint8_t)(((uint64_t)(v) >> 32) & 0xFF); ((uint8_t*)(p))[5] = (uint8_t)(((uint64_t)(v) >> 40) & 0xFF); ((uint8_t*)(p))[6] = (uint8_t)(((uint64_t)(v) >> 48) & 0xFF); ((uint8_t*)(p))[7] = (uint8_t)((uint64_t)(v) >> 56); } + + /* Find the end of central directory record by scanning the file from the end towards the beginning */ + for (ecdh_ofs = archive_size - sizeof(buf); ; ecdh_ofs -= (sizeof(buf) - 3)) + { + int i, n = sizeof(buf); + if (ecdh_ofs < 0) + ecdh_ofs = 0; + if (n > archive_size) + n = (int)archive_size; + rc_file_seek(file_handle, ecdh_ofs, SEEK_SET); + if (rc_file_read(file_handle, buf, n) != (size_t)n) + return rc_hash_error("ZIP read error"); + for (i = n - 4; i >= 0; --i) + if (RC_ZIP_READ_LE32(buf + i) == 0x06054b50) /* end of central directory header signature */ + break; + if (i >= 0) + { + ecdh_ofs += i; + break; + } + if (!ecdh_ofs || (archive_size - ecdh_ofs) >= (0xFFFF + eocdirhdr_size)) + return rc_hash_error("Failed to find ZIP central directory"); + } + + /* Read and verify the end of central directory record. */ + rc_file_seek(file_handle, ecdh_ofs, SEEK_SET); + if (rc_file_read(file_handle, buf, eocdirhdr_size) != eocdirhdr_size) + return rc_hash_error("Failed to read ZIP central directory"); + + /* Read central dir information from end of central directory header */ + total_files = RC_ZIP_READ_LE16(buf + 0x0A); + cdir_size = RC_ZIP_READ_LE32(buf + 0x0C); + cdir_ofs = RC_ZIP_READ_LE32(buf + 0x10); + + /* Check if this is a Zip64 file. In the block of code below: + * - 20 is the size of the ZIP64 end of central directory locator + * - 56 is the size of the ZIP64 end of central directory header + */ + if ((cdir_ofs == 0xFFFFFFFF || cdir_size == 0xFFFFFFFF || total_files == 0xFFFF) && ecdh_ofs >= (20 + 56)) + { + /* Read the ZIP64 end of central directory locator if it actually exists */ + rc_file_seek(file_handle, ecdh_ofs - 20, SEEK_SET); + if (rc_file_read(file_handle, buf, 20) == 20 && RC_ZIP_READ_LE32(buf) == 0x07064b50) /* locator signature */ + { + /* Found the locator, now read the actual ZIP64 end of central directory header */ + int64_t ecdh64_ofs = (int64_t)RC_ZIP_READ_LE64(buf + 0x08); + if (ecdh64_ofs <= (archive_size - 56)) + { + rc_file_seek(file_handle, ecdh64_ofs, SEEK_SET); + if (rc_file_read(file_handle, buf, 56) == 56 && RC_ZIP_READ_LE32(buf) == 0x06064b50) /* header signature */ + { + total_files = RC_ZIP_READ_LE64(buf + 0x20); + cdir_size = RC_ZIP_READ_LE64(buf + 0x28); + cdir_ofs = RC_ZIP_READ_LE64(buf + 0x30); + } + } + } + } + + /* Basic verificaton of central directory (limit to a 256MB content directory) */ + cdirhdr_size = 46; /* the 'central directory header' is 46 bytes */ + if ((cdir_size >= 0x10000000) || (cdir_size < total_files * cdirhdr_size) || ((cdir_ofs + cdir_size) > archive_size)) + return rc_hash_error("Central directory of ZIP file is invalid"); + + /* Allocate once for both directory and our temporary sort index (memory aligned to sizeof(rc_hash_zip_idx)) */ + sizeof_idx = sizeof(struct rc_hash_zip_idx); + indices_offset = (size_t)((cdir_size + sizeof_idx - 1) / sizeof_idx * sizeof_idx); + alloc_size = (size_t)(indices_offset + total_files * sizeof_idx); + alloc_buf = (uint8_t*)malloc(alloc_size); + + /* Read entire central directory to a buffer */ + if (!alloc_buf) + return rc_hash_error("Could not allocate temporary buffer"); + rc_file_seek(file_handle, cdir_ofs, SEEK_SET); + if ((int64_t)rc_file_read(file_handle, alloc_buf, (int)cdir_size) != cdir_size) + { + free(alloc_buf); + return rc_hash_error("Failed to read central directory of ZIP file"); + } + + cdir_start = alloc_buf; + cdir_max = cdir_start + cdir_size - cdirhdr_size; + cdir = cdir_start; + + /* Write our temporary hash data to the same buffer we read the central directory from. + * We can do that because the amount of data we keep for each file is guaranteed to be less than the file record. + */ + hashdata = alloc_buf; + hashindices = (struct rc_hash_zip_idx*)(alloc_buf + indices_offset); + hashindex = hashindices; + + /* Now process the central directory file records */ + for (i_file = 0, cdir = cdir_start; i_file < total_files && cdir >= cdir_start && cdir <= cdir_max; i_file++, cdir += cdir_entry_len) + { + const uint8_t *name, *name_end; + uint32_t signature = RC_ZIP_READ_LE32(cdir + 0x00); + uint32_t method = RC_ZIP_READ_LE16(cdir + 0x0A); + uint32_t crc32 = RC_ZIP_READ_LE32(cdir + 0x10); + uint64_t comp_size = RC_ZIP_READ_LE32(cdir + 0x14); + uint64_t decomp_size = RC_ZIP_READ_LE32(cdir + 0x18); + 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); + 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; + + /* Handle Zip64 fields */ + if (decomp_size == 0xFFFFFFFF || comp_size == 0xFFFFFFFF || local_hdr_ofs == 0xFFFFFFFF) + { + int invalid = 0; + const uint8_t *x = cdir + cdirhdr_size + filename_len, *xEnd, *field, *fieldEnd; + for (xEnd = x + extra_len; (x + (sizeof(uint16_t) * 2)) < xEnd; x = fieldEnd) + { + field = x + (sizeof(uint16_t) * 2); + fieldEnd = field + RC_ZIP_READ_LE16(x + 2); + if (RC_ZIP_READ_LE16(x) != 0x0001 || fieldEnd > xEnd) + continue; /* Not the Zip64 extended information extra field */ + + if (decomp_size == 0xFFFFFFFF) + { + if ((unsigned)(fieldEnd - field) < sizeof(uint64_t)) { invalid = 1; break; } + decomp_size = RC_ZIP_READ_LE64(field); + field += sizeof(uint64_t); + } + if (comp_size == 0xFFFFFFFF) + { + if ((unsigned)(fieldEnd - field) < sizeof(uint64_t)) { invalid = 1; break; } + comp_size = RC_ZIP_READ_LE64(field); + field += sizeof(uint64_t); + } + if (local_hdr_ofs == 0xFFFFFFFF) + { + if ((unsigned)(fieldEnd - field) < sizeof(uint64_t)) { invalid = 1; break; } + local_hdr_ofs = RC_ZIP_READ_LE64(field); + field += sizeof(uint64_t); + } + break; + } + if (invalid) + { + free(alloc_buf); + return rc_hash_error("Encountered invalid Zip64 file"); + } + } + + /* Basic sanity check on file record */ + /* 30 is the length of the local directory header preceeding the compressed data */ + if ((!method && decomp_size != comp_size) || (decomp_size && !comp_size) || ((local_hdr_ofs + 30 + comp_size) > (uint64_t)archive_size)) + { + free(alloc_buf); + return rc_hash_error("Encountered invalid entry in ZIP central directory"); + } + + /* Write the pointer and length of the data we record about this file */ + hashindex->data = hashdata; + hashindex->length = filename_len + 1 + 4 + 8; + 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++) + { + *(hashdata++) = + (*name == '\\' ? '/' : /* convert back-slashes to regular slashes */ + (*name >= 'A' && *name <= 'Z') ? (*name | 0x20) : /* convert upper case letters to lower case */ + *name); /* else use the byte as-is */ + } + + /* Add zero terminator, CRC32 and decompressed size to the hash data buffer */ + *(hashdata++) = '\0'; + RC_ZIP_WRITE_LE32(hashdata, crc32); + hashdata += 4; + RC_ZIP_WRITE_LE64(hashdata, decomp_size); + hashdata += 8; + + if (verbose_message_callback) + { + char message[1024]; + snprintf(message, sizeof(message), "File in ZIP: %.*s (%u bytes, CRC32 = %08X)", filename_len, (const char*)(cdir + cdirhdr_size), (unsigned)decomp_size, crc32); + verbose_message_callback(message); + } + } + + if (verbose_message_callback) + { + char message[1024]; + snprintf(message, sizeof(message), "Hashing %u files in ZIP archive", (unsigned)(hashindex - hashindices)); + verbose_message_callback(message); + } + + /* Sort the file list indices */ + qsort(hashindices, (hashindex - hashindices), sizeof(struct rc_hash_zip_idx), rc_hash_zip_idx_sort); + + /* Hash the data in the order of the now sorted indices */ + for (; hashindices != hashindex; hashindices++) + md5_append(md5, hashindices->data, (int)hashindices->length); + + free(alloc_buf); + return 1; + + #undef RC_ZIP_READ_LE16 + #undef RC_ZIP_READ_LE32 + #undef RC_ZIP_READ_LE64 + #undef RC_ZIP_WRITE_LE32 + #undef RC_ZIP_WRITE_LE64 +} + +static int rc_hash_ms_dos(char hash[33], const char* path) +{ + md5_state_t md5; + size_t path_len; + int res; + + void* file_handle = rc_file_open(path); + if (!file_handle) + return rc_hash_error("Could not open file"); + + /* hash the main content zip file first */ + md5_init(&md5); + res = rc_hash_zip_file(&md5, file_handle); + rc_file_close(file_handle); + + if (!res) + return 0; + + /* if this is a .dosz file, check if an associated .dosc file exists */ + path_len = strlen(path); + if (path[path_len-1] == 'z' || path[path_len-1] == 'Z') + { + char *dosc_path = strdup(path); + if (!dosc_path) + return rc_hash_error("Could not allocate temporary buffer"); + + /* swap the z to c and use the same capitalization, hash the file if it exists*/ + dosc_path[path_len-1] = (path[path_len-1] == 'z' ? 'c' : 'C'); + file_handle = rc_file_open(dosc_path); + free((void*)dosc_path); + if (file_handle) + { + res = rc_hash_zip_file(&md5, file_handle); + rc_file_close(file_handle); + + if (!res) + return 0; + } + } + + return rc_hash_finalize(&md5, hash); +} + 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 */ @@ -2083,6 +2371,7 @@ int rc_hash_generate_from_buffer(char hash[33], uint32_t console_id, const uint8 return rc_hash_snes(hash, buffer, buffer_size); case RC_CONSOLE_NINTENDO_64: + case RC_CONSOLE_NINTENDO_3DS: case RC_CONSOLE_NINTENDO_DS: case RC_CONSOLE_NINTENDO_DSI: return rc_hash_file_from_buffer(hash, console_id, buffer, buffer_size); @@ -2401,6 +2690,9 @@ int rc_hash_generate_from_file(char hash[33], uint32_t console_id, const char* p case RC_CONSOLE_GAMECUBE: return rc_hash_gamecube(hash, path); + case RC_CONSOLE_MS_DOS: + return rc_hash_ms_dos(hash, path); + case RC_CONSOLE_NEO_GEO_CD: return rc_hash_neogeo_cd(hash, path); @@ -2539,6 +2831,14 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* } break; + case '3': + if (rc_path_compare_extension(ext, "3ds") || + rc_path_compare_extension(ext, "3dsx")) + { + iterator->consoles[0] = RC_CONSOLE_NINTENDO_3DS; + } + break; + case '7': if (rc_path_compare_extension(ext, "7z")) { @@ -2562,6 +2862,11 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* { iterator->consoles[0] = RC_CONSOLE_ATARI_7800; } + else if (rc_path_compare_extension(ext, "app") || + rc_path_compare_extension(ext, "axf")) + { + iterator->consoles[0] = RC_CONSOLE_NINTENDO_3DS; + } break; case 'b': @@ -2647,6 +2952,12 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* { iterator->consoles[0] = RC_CONSOLE_SUPER_CASSETTEVISION; } + else if (rc_path_compare_extension(ext, "cci") || + rc_path_compare_extension(ext, "cia") || + rc_path_compare_extension(ext, "cxi")) + { + iterator->consoles[0] = RC_CONSOLE_NINTENDO_3DS; + } break; case 'd': @@ -2663,6 +2974,19 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* iterator->consoles[0] = RC_CONSOLE_PC8800; iterator->consoles[1] = RC_CONSOLE_SHARPX1; } + else if (rc_path_compare_extension(ext, "dosz")) + { + iterator->consoles[0] = RC_CONSOLE_MS_DOS; + } + break; + + case 'e': + if (rc_path_compare_extension(ext, "elf")) + { + /* This should probably apply to more consoles in the future */ + /* Although in any case this just hashes the entire file */ + iterator->consoles[0] = RC_CONSOLE_NINTENDO_3DS; + } break; case 'f':