From 4ba5fc033377cc2d2a446a844e2665a3712fab02 Mon Sep 17 00:00:00 2001 From: Jamiras <32680403+Jamiras@users.noreply.github.com> Date: Sun, 21 Jan 2024 19:12:30 -0700 Subject: [PATCH] (cheevos) upgrade to rcheevos 11.1 (#16151) * move pause spam management into rc_client * update to rcheevos 11.1 * show [m] on missables * update makefile --- Makefile.common | 1 + cheevos/cheevos.c | 15 + cheevos/cheevos.h | 1 + cheevos/cheevos_locals.h | 2 + cheevos/cheevos_menu.c | 18 +- deps/rcheevos/CHANGELOG.md | 23 + deps/rcheevos/include/rc_api_request.h | 16 +- deps/rcheevos/include/rc_api_runtime.h | 65 +- deps/rcheevos/include/rc_api_user.h | 36 +- deps/rcheevos/include/rc_client.h | 168 +-- .../include/rc_client_raintegration.h | 85 ++ deps/rcheevos/include/rc_consoles.h | 18 +- deps/rcheevos/include/rc_error.h | 12 +- deps/rcheevos/include/rc_export.h | 100 ++ deps/rcheevos/include/rc_hash.h | 72 +- deps/rcheevos/include/rc_runtime.h | 58 +- deps/rcheevos/include/rc_runtime_types.h | 65 +- deps/rcheevos/include/rc_url.h | 36 +- deps/rcheevos/{src => include}/rc_util.h | 10 +- deps/rcheevos/src/rapi/rc_api_common.c | 50 +- deps/rcheevos/src/rapi/rc_api_common.h | 13 +- deps/rcheevos/src/rapi/rc_api_runtime.c | 57 +- deps/rcheevos/src/rapi/rc_api_user.c | 11 +- deps/rcheevos/src/rc_client.c | 708 ++++++++++-- deps/rcheevos/src/rc_client_external.h | 132 +++ deps/rcheevos/src/rc_client_internal.h | 70 +- deps/rcheevos/src/rc_client_raintegration.c | 493 ++++++++ .../src/rc_client_raintegration_internal.h | 52 + deps/rcheevos/src/rc_compat.c | 4 +- deps/rcheevos/src/rc_compat.h | 10 +- deps/rcheevos/src/rc_libretro.c | 96 +- deps/rcheevos/src/rc_libretro.h | 48 +- deps/rcheevos/src/rc_util.c | 2 +- deps/rcheevos/src/rc_version.c | 11 + deps/rcheevos/src/rc_version.h | 17 +- deps/rcheevos/src/rcheevos/alloc.c | 58 +- deps/rcheevos/src/rcheevos/condset.c | 2 +- deps/rcheevos/src/rcheevos/consoleinfo.c | 31 +- deps/rcheevos/src/rcheevos/format.c | 74 ++ deps/rcheevos/src/rcheevos/lboard.c | 2 +- deps/rcheevos/src/rcheevos/operand.c | 13 +- deps/rcheevos/src/rcheevos/rc_internal.h | 18 +- deps/rcheevos/src/rcheevos/richpresence.c | 34 +- deps/rcheevos/src/rcheevos/runtime_progress.c | 2 +- deps/rcheevos/src/rcheevos/value.c | 2 +- deps/rcheevos/src/rhash/aes.c | 480 ++++++++ deps/rcheevos/src/rhash/aes.h | 49 + deps/rcheevos/src/rhash/hash.c | 1006 ++++++++++++++++- griffin/griffin.c | 1 + runloop.c | 23 +- 50 files changed, 3785 insertions(+), 585 deletions(-) create mode 100644 deps/rcheevos/include/rc_client_raintegration.h create mode 100644 deps/rcheevos/include/rc_export.h rename deps/rcheevos/{src => include}/rc_util.h (95%) create mode 100644 deps/rcheevos/src/rc_client_external.h create mode 100644 deps/rcheevos/src/rc_client_raintegration.c create mode 100644 deps/rcheevos/src/rc_client_raintegration_internal.h create mode 100644 deps/rcheevos/src/rc_version.c create mode 100644 deps/rcheevos/src/rhash/aes.c create mode 100644 deps/rcheevos/src/rhash/aes.h diff --git a/Makefile.common b/Makefile.common index c10890968a..4a872356bd 100644 --- a/Makefile.common +++ b/Makefile.common @@ -2148,6 +2148,7 @@ ifeq ($(HAVE_NETWORKING), 1) deps/rcheevos/src/rcheevos/runtime_progress.o \ deps/rcheevos/src/rcheevos/trigger.o \ deps/rcheevos/src/rcheevos/value.o \ + deps/rcheevos/src/rhash/aes.o \ deps/rcheevos/src/rhash/cdreader.o \ deps/rcheevos/src/rhash/hash.o \ deps/rcheevos/src/rapi/rc_api_common.o \ diff --git a/cheevos/cheevos.c b/cheevos/cheevos.c index fd7e45e1cb..896d8fa3d0 100644 --- a/cheevos/cheevos.c +++ b/cheevos/cheevos.c @@ -115,6 +115,7 @@ static rcheevos_locals_t rcheevos_locals = 0.0, /* tracker_progress */ #endif {RCHEEVOS_LOAD_STATE_NONE, 0, 0 }, /* load_info */ + 0, /* unpaused_frames */ false,/* hardcore_active */ false,/* loaded */ #ifdef HAVE_GFX_WIDGETS @@ -370,6 +371,15 @@ void rcheevos_spectating_changed(void) #endif } +bool rcheevos_is_pause_allowed(void) +{ +#ifdef HAVE_RC_CLIENT + return rc_client_can_pause(rcheevos_locals.client, NULL); +#else + return (rcheevos_locals.unpaused_frames == 0); +#endif +} + #ifdef HAVE_RC_CLIENT static void rcheevos_show_mastery_placard(void) @@ -1964,6 +1974,11 @@ void rcheevos_test(void) rcheevos_locals.tracker_progress = 0.0; } #endif + + /* We processed a frame - if there's a pause delay in effect, process it */ + if (rcheevos_locals.unpaused_frames > 0) + rcheevos_locals.unpaused_frames--; + #endif /* HAVE_RC_CLIENT */ } diff --git a/cheevos/cheevos.h b/cheevos/cheevos.h index 2d6b6ff4dc..2fbbbeaaf5 100644 --- a/cheevos/cheevos.h +++ b/cheevos/cheevos.h @@ -45,6 +45,7 @@ void rcheevos_hardcore_enabled_changed(void); void rcheevos_toggle_hardcore_paused(void); bool rcheevos_hardcore_active(void); +bool rcheevos_is_pause_allowed(void); void rcheevos_spectating_changed(void); void rcheevos_validate_config_settings(void); diff --git a/cheevos/cheevos_locals.h b/cheevos/cheevos_locals.h index 708f166502..aa57027780 100644 --- a/cheevos/cheevos_locals.h +++ b/cheevos/cheevos_locals.h @@ -230,6 +230,8 @@ typedef struct rcheevos_locals_t rcheevos_load_info_t load_info; /* load info */ + uint32_t unpaused_frames; /* number of unpaused frames before next pause is allowed */ + bool hardcore_active; /* hardcore functionality is active */ bool loaded; /* load task has completed */ #ifdef HAVE_GFX_WIDGETS diff --git a/cheevos/cheevos_menu.c b/cheevos/cheevos_menu.c index 42a8d8e7a1..9b59c0635b 100644 --- a/cheevos/cheevos_menu.c +++ b/cheevos/cheevos_menu.c @@ -52,14 +52,20 @@ bool rcheevos_menu_get_state(unsigned menu_offset, char* buffer, size_t buffer_s const rc_client_achievement_t* cheevo = menuitem->achievement; if (cheevo) { - if (cheevo->measured_progress[0]) + if (cheevo->state != RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE) { - snprintf(buffer, buffer_size, "%s - %s", - msg_hash_to_str(menuitem->state_label_idx), cheevo->measured_progress); + strlcpy(buffer, msg_hash_to_str(menuitem->state_label_idx), buffer_size); } else - strlcpy(buffer, msg_hash_to_str(menuitem->state_label_idx), buffer_size); - + { + const char* missable = cheevo->type == RC_CLIENT_ACHIEVEMENT_TYPE_MISSABLE ? "[m] " : ""; + if (cheevo->measured_progress[0]) + snprintf(buffer, buffer_size, "%s%s - %s", missable, + msg_hash_to_str(menuitem->state_label_idx), cheevo->measured_progress); + else + snprintf(buffer, buffer_size, "%s%s", missable, + msg_hash_to_str(menuitem->state_label_idx)); + } return true; } } @@ -405,10 +411,12 @@ void rcheevos_menu_populate(void* data) do { if (menuitem->achievement) + { menu_entries_append(info->list, menuitem->achievement->title, menuitem->achievement->description, MENU_ENUM_LABEL_CHEEVOS_LOCKED_ENTRY, MENU_SETTINGS_CHEEVOS_START + idx, 0, 0, NULL); + } else { if (menuitem->subset_id) diff --git a/deps/rcheevos/CHANGELOG.md b/deps/rcheevos/CHANGELOG.md index 9cced4c5d2..2649f7c5f5 100644 --- a/deps/rcheevos/CHANGELOG.md +++ b/deps/rcheevos/CHANGELOG.md @@ -1,3 +1,26 @@ +# v11.1.0 +* add rc_client_get_user_agent_clause to generate substring to include in client User-Agents +* add rc_client_can_pause function to control pause spam +* add achievement type and rarity to rc_api_fetch_game_data_response_t and rc_client_achievement_t +* add RC_CLIENT_ACHIEVEMENT_BUCKET_UNSYNCED for achievements that have been unlocked locally but not synced to the server +* add RC_CONSOLE_NEO_GEO_CD to supported consoles for chd file extension +* add hash logic for RC_CONSOLE_NINTENDO_3DS (note: added new file rhash/aes.c to support this) +* add hash logic for RC_CONSOLE_MS_DOS +* add game_hash and hardcore fields to rc_api_start_session_request_t and rc_api_ping_request_t +* add RC_FORMAT_FIXED1/2/3, RC_FORMAT_TENS, RC_FORMAT_HUNDREDS, RC_FORMAT_THOUSANDS, and RC_FORMAT_UNSIGNED_VALUE +* add RC_CONSOLE_STANDALONE +* add extern "C" and __cdecl attributes to public functions +* add __declspec(dllexport/dllimport) attributes to public functions via #define enablement +* add rc_version and rc_version_string functions for accessing version from external linkage +* add unicode path support to default filereader (Windows builds) +* add rc_mutex support for GEKKO (libogc) +* fix async_handle being returned when rc_client_begin_login is aborted synchronously +* fix logic error hashing CD files smaller than one sector +* fix read across region boundary in rc_libretro_memory_read +* fix RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_SHOW event not being raised if achievement is reset in the same frame that it's primed +* moved rc_util.h from src/ to include/ +* initial (incomplete) support for rc_client_external_t and rc_client_raintegration_t + # v11.0.0 * add rc_client_t and related functions * add RC_MEMSIZE_FLOAT_BE diff --git a/deps/rcheevos/include/rc_api_request.h b/deps/rcheevos/include/rc_api_request.h index 578488f3db..dd72fb56de 100644 --- a/deps/rcheevos/include/rc_api_request.h +++ b/deps/rcheevos/include/rc_api_request.h @@ -2,13 +2,11 @@ #define RC_API_REQUEST_H #include "rc_error.h" -#include "../src/rc_util.h" +#include "rc_util.h" #include -#ifdef __cplusplus -extern "C" { -#endif +RC_BEGIN_C_DECLS /** * A constructed request to send to the retroachievements server. @@ -42,10 +40,10 @@ typedef struct rc_api_response_t { } rc_api_response_t; -void rc_api_destroy_request(rc_api_request_t* request); +RC_EXPORT void RC_CCONV rc_api_destroy_request(rc_api_request_t* request); -void rc_api_set_host(const char* hostname); -void rc_api_set_image_host(const char* hostname); +RC_EXPORT void RC_CCONV rc_api_set_host(const char* hostname); +RC_EXPORT void RC_CCONV rc_api_set_image_host(const char* hostname); typedef struct rc_api_server_response_t { /* Pointer to the data returned from the server */ @@ -61,8 +59,6 @@ enum { RC_API_SERVER_RESPONSE_RETRYABLE_CLIENT_ERROR = -2 }; -#ifdef __cplusplus -} -#endif +RC_END_C_DECLS #endif /* RC_API_REQUEST_H */ diff --git a/deps/rcheevos/include/rc_api_runtime.h b/deps/rcheevos/include/rc_api_runtime.h index e6d72a20d6..5b5552ca54 100644 --- a/deps/rcheevos/include/rc_api_runtime.h +++ b/deps/rcheevos/include/rc_api_runtime.h @@ -6,9 +6,7 @@ #include #include -#ifdef __cplusplus -extern "C" { -#endif +RC_BEGIN_C_DECLS /* --- Fetch Image --- */ @@ -29,7 +27,7 @@ rc_api_fetch_image_request_t; #define RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED 3 #define RC_IMAGE_TYPE_USER 4 -int rc_api_init_fetch_image_request(rc_api_request_t* request, const rc_api_fetch_image_request_t* api_params); +RC_EXPORT int RC_CCONV rc_api_init_fetch_image_request(rc_api_request_t* request, const rc_api_fetch_image_request_t* api_params); /* --- Resolve Hash --- */ @@ -58,10 +56,10 @@ typedef struct rc_api_resolve_hash_response_t { } rc_api_resolve_hash_response_t; -int rc_api_init_resolve_hash_request(rc_api_request_t* request, const rc_api_resolve_hash_request_t* api_params); -int rc_api_process_resolve_hash_response(rc_api_resolve_hash_response_t* response, const char* server_response); -int rc_api_process_resolve_hash_server_response(rc_api_resolve_hash_response_t* response, const rc_api_server_response_t* server_response); -void rc_api_destroy_resolve_hash_response(rc_api_resolve_hash_response_t* response); +RC_EXPORT int RC_CCONV rc_api_init_resolve_hash_request(rc_api_request_t* request, const rc_api_resolve_hash_request_t* api_params); +RC_EXPORT int RC_CCONV rc_api_process_resolve_hash_response(rc_api_resolve_hash_response_t* response, const char* server_response); +RC_EXPORT int RC_CCONV rc_api_process_resolve_hash_server_response(rc_api_resolve_hash_response_t* response, const rc_api_server_response_t* server_response); +RC_EXPORT void RC_CCONV rc_api_destroy_resolve_hash_response(rc_api_resolve_hash_response_t* response); /* --- Fetch Game Data --- */ @@ -119,12 +117,23 @@ typedef struct rc_api_achievement_definition_t { time_t created; /* When the achievement was last modified on the server */ time_t updated; + /* The achievement type (win/progression/missable) */ + uint32_t type; + /* The approximate rarity of the achievement (X% of users have earned the achievement) */ + float rarity; + /* The approximate rarity of the achievement in hardcore (X% of users have earned the achievement in hardcore) */ + float rarity_hardcore; } rc_api_achievement_definition_t; #define RC_ACHIEVEMENT_CATEGORY_CORE 3 #define RC_ACHIEVEMENT_CATEGORY_UNOFFICIAL 5 +#define RC_ACHIEVEMENT_TYPE_STANDARD 0 +#define RC_ACHIEVEMENT_TYPE_MISSABLE 1 +#define RC_ACHIEVEMENT_TYPE_PROGRESSION 2 +#define RC_ACHIEVEMENT_TYPE_WIN 3 + /** * Response data for a fetch game data request. */ @@ -155,10 +164,10 @@ typedef struct rc_api_fetch_game_data_response_t { } rc_api_fetch_game_data_response_t; -int rc_api_init_fetch_game_data_request(rc_api_request_t* request, const rc_api_fetch_game_data_request_t* api_params); -int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* response, const char* server_response); -int rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_response_t* response, const rc_api_server_response_t* server_response); -void rc_api_destroy_fetch_game_data_response(rc_api_fetch_game_data_response_t* response); +RC_EXPORT int RC_CCONV rc_api_init_fetch_game_data_request(rc_api_request_t* request, const rc_api_fetch_game_data_request_t* api_params); +RC_EXPORT int RC_CCONV rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* response, const char* server_response); +RC_EXPORT int RC_CCONV rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_response_t* response, const rc_api_server_response_t* server_response); +RC_EXPORT void RC_CCONV rc_api_destroy_fetch_game_data_response(rc_api_fetch_game_data_response_t* response); /* --- Ping --- */ @@ -174,6 +183,10 @@ typedef struct rc_api_ping_request_t { uint32_t game_id; /* (optional) The current rich presence evaluation for the user */ const char* rich_presence; + /* (recommended) The hash associated to the game being played */ + const char* game_hash; + /* Non-zero if hardcore is currently enabled (ignored if game_hash is null) */ + uint32_t hardcore; } rc_api_ping_request_t; @@ -186,10 +199,10 @@ typedef struct rc_api_ping_response_t { } rc_api_ping_response_t; -int rc_api_init_ping_request(rc_api_request_t* request, const rc_api_ping_request_t* api_params); -int rc_api_process_ping_response(rc_api_ping_response_t* response, const char* server_response); -int rc_api_process_ping_server_response(rc_api_ping_response_t* response, const rc_api_server_response_t* server_response); -void rc_api_destroy_ping_response(rc_api_ping_response_t* response); +RC_EXPORT int RC_CCONV rc_api_init_ping_request(rc_api_request_t* request, const rc_api_ping_request_t* api_params); +RC_EXPORT int RC_CCONV rc_api_process_ping_response(rc_api_ping_response_t* response, const char* server_response); +RC_EXPORT int RC_CCONV rc_api_process_ping_server_response(rc_api_ping_response_t* response, const rc_api_server_response_t* server_response); +RC_EXPORT void RC_CCONV rc_api_destroy_ping_response(rc_api_ping_response_t* response); /* --- Award Achievement --- */ @@ -229,10 +242,10 @@ typedef struct rc_api_award_achievement_response_t { } rc_api_award_achievement_response_t; -int rc_api_init_award_achievement_request(rc_api_request_t* request, const rc_api_award_achievement_request_t* api_params); -int rc_api_process_award_achievement_response(rc_api_award_achievement_response_t* response, const char* server_response); -int rc_api_process_award_achievement_server_response(rc_api_award_achievement_response_t* response, const rc_api_server_response_t* server_response); -void rc_api_destroy_award_achievement_response(rc_api_award_achievement_response_t* response); +RC_EXPORT int RC_CCONV rc_api_init_award_achievement_request(rc_api_request_t* request, const rc_api_award_achievement_request_t* api_params); +RC_EXPORT int RC_CCONV rc_api_process_award_achievement_response(rc_api_award_achievement_response_t* response, const char* server_response); +RC_EXPORT int RC_CCONV rc_api_process_award_achievement_server_response(rc_api_award_achievement_response_t* response, const rc_api_server_response_t* server_response); +RC_EXPORT void RC_CCONV rc_api_destroy_award_achievement_response(rc_api_award_achievement_response_t* response); /* --- Submit Leaderboard Entry --- */ @@ -287,13 +300,11 @@ typedef struct rc_api_submit_lboard_entry_response_t { } rc_api_submit_lboard_entry_response_t; -int rc_api_init_submit_lboard_entry_request(rc_api_request_t* request, const rc_api_submit_lboard_entry_request_t* api_params); -int rc_api_process_submit_lboard_entry_response(rc_api_submit_lboard_entry_response_t* response, const char* server_response); -int rc_api_process_submit_lboard_entry_server_response(rc_api_submit_lboard_entry_response_t* response, const rc_api_server_response_t* server_response); -void rc_api_destroy_submit_lboard_entry_response(rc_api_submit_lboard_entry_response_t* response); +RC_EXPORT int RC_CCONV rc_api_init_submit_lboard_entry_request(rc_api_request_t* request, const rc_api_submit_lboard_entry_request_t* api_params); +RC_EXPORT int RC_CCONV rc_api_process_submit_lboard_entry_response(rc_api_submit_lboard_entry_response_t* response, const char* server_response); +RC_EXPORT int RC_CCONV rc_api_process_submit_lboard_entry_server_response(rc_api_submit_lboard_entry_response_t* response, const rc_api_server_response_t* server_response); +RC_EXPORT void RC_CCONV rc_api_destroy_submit_lboard_entry_response(rc_api_submit_lboard_entry_response_t* response); -#ifdef __cplusplus -} -#endif +RC_END_C_DECLS #endif /* RC_API_RUNTIME_H */ diff --git a/deps/rcheevos/include/rc_api_user.h b/deps/rcheevos/include/rc_api_user.h index 9fb348315c..c06cec4450 100644 --- a/deps/rcheevos/include/rc_api_user.h +++ b/deps/rcheevos/include/rc_api_user.h @@ -6,9 +6,7 @@ #include #include -#ifdef __cplusplus -extern "C" { -#endif +RC_BEGIN_C_DECLS /* --- Login --- */ @@ -48,10 +46,10 @@ typedef struct rc_api_login_response_t { } rc_api_login_response_t; -int rc_api_init_login_request(rc_api_request_t* request, const rc_api_login_request_t* api_params); -int rc_api_process_login_response(rc_api_login_response_t* response, const char* server_response); -int rc_api_process_login_server_response(rc_api_login_response_t* response, const rc_api_server_response_t* server_response); -void rc_api_destroy_login_response(rc_api_login_response_t* response); +RC_EXPORT int RC_CCONV rc_api_init_login_request(rc_api_request_t* request, const rc_api_login_request_t* api_params); +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); /* --- Start Session --- */ @@ -65,6 +63,10 @@ typedef struct rc_api_start_session_request_t { const char* api_token; /* The unique identifier of the game */ uint32_t game_id; + /* (recommended) The hash associated to the game being played */ + const char* game_hash; + /* Non-zero if hardcore is currently enabled (ignored if game_hash is null) */ + uint32_t hardcore; } rc_api_start_session_request_t; @@ -101,10 +103,10 @@ typedef struct rc_api_start_session_response_t { } rc_api_start_session_response_t; -int rc_api_init_start_session_request(rc_api_request_t* request, const rc_api_start_session_request_t* api_params); -int rc_api_process_start_session_response(rc_api_start_session_response_t* response, const char* server_response); -int rc_api_process_start_session_server_response(rc_api_start_session_response_t* response, const rc_api_server_response_t* server_response); -void rc_api_destroy_start_session_response(rc_api_start_session_response_t* response); +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); +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); /* --- Fetch User Unlocks --- */ @@ -137,13 +139,11 @@ typedef struct rc_api_fetch_user_unlocks_response_t { } rc_api_fetch_user_unlocks_response_t; -int rc_api_init_fetch_user_unlocks_request(rc_api_request_t* request, const rc_api_fetch_user_unlocks_request_t* api_params); -int rc_api_process_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response, const char* server_response); -int rc_api_process_fetch_user_unlocks_server_response(rc_api_fetch_user_unlocks_response_t* response, const rc_api_server_response_t* server_response); -void rc_api_destroy_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response); +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); +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); -#ifdef __cplusplus -} -#endif +RC_END_C_DECLS #endif /* RC_API_H */ diff --git a/deps/rcheevos/include/rc_client.h b/deps/rcheevos/include/rc_client.h index da0c40bdf1..48bef373ee 100644 --- a/deps/rcheevos/include/rc_client.h +++ b/deps/rcheevos/include/rc_client.h @@ -1,10 +1,6 @@ #ifndef RC_CLIENT_H #define RC_CLIENT_H -#ifdef __cplusplus -extern "C" { -#endif - #include "rc_api_request.h" #include "rc_error.h" @@ -12,6 +8,8 @@ extern "C" { #include #include +RC_BEGIN_C_DECLS + /* implementation abstracted in rc_client_internal.h */ typedef struct rc_client_t rc_client_t; typedef struct rc_client_async_handle_t rc_client_async_handle_t; @@ -24,27 +22,27 @@ typedef struct rc_client_async_handle_t rc_client_async_handle_t; * Callback used to read num_bytes bytes from memory starting at address into buffer. * Returns the number of bytes read. A return value of 0 indicates the address was invalid. */ -typedef uint32_t (*rc_client_read_memory_func_t)(uint32_t address, uint8_t* buffer, uint32_t num_bytes, rc_client_t* client); +typedef uint32_t (RC_CCONV *rc_client_read_memory_func_t)(uint32_t address, uint8_t* buffer, uint32_t num_bytes, rc_client_t* client); /** * Internal method passed to rc_client_server_call_t to process the server response. */ -typedef void (*rc_client_server_callback_t)(const rc_api_server_response_t* server_response, void* callback_data); +typedef void (RC_CCONV *rc_client_server_callback_t)(const rc_api_server_response_t* server_response, void* callback_data); /** * Callback used to issue a request to the server. */ -typedef void (*rc_client_server_call_t)(const rc_api_request_t* request, rc_client_server_callback_t callback, void* callback_data, rc_client_t* client); +typedef void (RC_CCONV *rc_client_server_call_t)(const rc_api_request_t* request, rc_client_server_callback_t callback, void* callback_data, rc_client_t* client); /** * Generic callback for asynchronous eventing. */ -typedef void (*rc_client_callback_t)(int result, const char* error_message, rc_client_t* client, void* userdata); +typedef void (RC_CCONV *rc_client_callback_t)(int result, const char* error_message, rc_client_t* client, void* userdata); /** * Callback for logging or displaying a message. */ -typedef void (*rc_client_message_callback_t)(const char* message, const rc_client_t* client); +typedef void (RC_CCONV *rc_client_message_callback_t)(const char* message, const rc_client_t* client); /*****************************************************************************\ | Runtime | @@ -53,13 +51,13 @@ typedef void (*rc_client_message_callback_t)(const char* message, const rc_clien /** * Creates a new rc_client_t object. */ -rc_client_t* rc_client_create(rc_client_read_memory_func_t read_memory_function, rc_client_server_call_t server_call_function); +RC_EXPORT rc_client_t* RC_CCONV rc_client_create(rc_client_read_memory_func_t read_memory_function, rc_client_server_call_t server_call_function); /** * Releases resources associated to a rc_client_t object. * Pointer will no longer be valid after making this call. */ -void rc_client_destroy(rc_client_t* client); +RC_EXPORT void RC_CCONV rc_client_destroy(rc_client_t* client); /** * Sets whether hardcore is enabled (on by default). @@ -67,34 +65,34 @@ void rc_client_destroy(rc_client_t* client); * Enabling hardcore with a game loaded will raise an RC_CLIENT_EVENT_RESET * event. Processing will be disabled until rc_client_reset is called. */ -void rc_client_set_hardcore_enabled(rc_client_t* client, int enabled); +RC_EXPORT void RC_CCONV rc_client_set_hardcore_enabled(rc_client_t* client, int enabled); /** * Gets whether hardcore is enabled (on by default). */ -int rc_client_get_hardcore_enabled(const rc_client_t* client); +RC_EXPORT int RC_CCONV rc_client_get_hardcore_enabled(const rc_client_t* client); /** * Sets whether encore mode is enabled (off by default). * Evaluated when loading a game. Has no effect while a game is loaded. */ -void rc_client_set_encore_mode_enabled(rc_client_t* client, int enabled); +RC_EXPORT void RC_CCONV rc_client_set_encore_mode_enabled(rc_client_t* client, int enabled); /** * Gets whether encore mode is enabled (off by default). */ -int rc_client_get_encore_mode_enabled(const rc_client_t* client); +RC_EXPORT int RC_CCONV rc_client_get_encore_mode_enabled(const rc_client_t* client); /** * Sets whether unofficial achievements should be loaded. * Evaluated when loading a game. Has no effect while a game is loaded. */ -void rc_client_set_unofficial_enabled(rc_client_t* client, int enabled); +RC_EXPORT void RC_CCONV rc_client_set_unofficial_enabled(rc_client_t* client, int enabled); /** * Gets whether unofficial achievements should be loaded. */ -int rc_client_get_unofficial_enabled(const rc_client_t* client); +RC_EXPORT int RC_CCONV rc_client_get_unofficial_enabled(const rc_client_t* client); /** * Sets whether spectator mode is enabled (off by default). @@ -103,40 +101,45 @@ int rc_client_get_unofficial_enabled(const rc_client_t* client); * Can be modified while a game is loaded. Evaluated at unlock/submit time. * Cannot be modified if disabled before a game is loaded. */ -void rc_client_set_spectator_mode_enabled(rc_client_t* client, int enabled); +RC_EXPORT void RC_CCONV rc_client_set_spectator_mode_enabled(rc_client_t* client, int enabled); /** * Gets whether spectator mode is enabled (off by default). */ -int rc_client_get_spectator_mode_enabled(const rc_client_t* client); +RC_EXPORT int RC_CCONV rc_client_get_spectator_mode_enabled(const rc_client_t* client); /** * Attaches client-specific data to the runtime. */ -void rc_client_set_userdata(rc_client_t* client, void* userdata); +RC_EXPORT void RC_CCONV rc_client_set_userdata(rc_client_t* client, void* userdata); /** * Gets the client-specific data attached to the runtime. */ -void* rc_client_get_userdata(const rc_client_t* client); +RC_EXPORT void* RC_CCONV rc_client_get_userdata(const rc_client_t* client); /** * Sets the name of the server to use. */ -void rc_client_set_host(const rc_client_t* client, const char* hostname); +RC_EXPORT void RC_CCONV rc_client_set_host(const rc_client_t* client, const char* hostname); typedef uint64_t rc_clock_t; -typedef rc_clock_t (*rc_get_time_millisecs_func_t)(const rc_client_t* client); +typedef rc_clock_t (RC_CCONV *rc_get_time_millisecs_func_t)(const rc_client_t* client); /** * Specifies a function that returns a value that increases once per millisecond. */ -void rc_client_set_get_time_millisecs_function(rc_client_t* client, rc_get_time_millisecs_func_t handler); +RC_EXPORT void RC_CCONV rc_client_set_get_time_millisecs_function(rc_client_t* client, rc_get_time_millisecs_func_t handler); /** * Marks an async process as aborted. The associated callback will not be called. */ -void rc_client_abort_async(rc_client_t* client, rc_client_async_handle_t* async_handle); +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 | @@ -145,7 +148,7 @@ void rc_client_abort_async(rc_client_t* client, rc_client_async_handle_t* async_ /** * Sets the logging level and provides a callback to be called to do the logging. */ -void rc_client_enable_logging(rc_client_t* client, int level, rc_client_message_callback_t callback); +RC_EXPORT void RC_CCONV rc_client_enable_logging(rc_client_t* client, int level, rc_client_message_callback_t callback); enum { RC_CLIENT_LOG_LEVEL_NONE = 0, RC_CLIENT_LOG_LEVEL_ERROR = 1, @@ -162,21 +165,21 @@ enum { /** * Attempt to login a user. */ -rc_client_async_handle_t* rc_client_begin_login_with_password(rc_client_t* client, +RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_login_with_password(rc_client_t* client, const char* username, const char* password, rc_client_callback_t callback, void* callback_userdata); /** * Attempt to login a user. */ -rc_client_async_handle_t* rc_client_begin_login_with_token(rc_client_t* client, +RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_login_with_token(rc_client_t* client, const char* username, const char* token, rc_client_callback_t callback, void* callback_userdata); /** * Logout the user. */ -void rc_client_logout(rc_client_t* client); +RC_EXPORT void RC_CCONV rc_client_logout(rc_client_t* client); typedef struct rc_client_user_t { const char* display_name; @@ -190,16 +193,15 @@ typedef struct rc_client_user_t { /** * Gets information about the logged in user. Will return NULL if the user is not logged in. */ -const rc_client_user_t* rc_client_get_user_info(const rc_client_t* client); +RC_EXPORT const rc_client_user_t* RC_CCONV rc_client_get_user_info(const rc_client_t* client); /** * Gets the URL for the user's profile picture. * Returns RC_OK on success. */ -int rc_client_user_get_image_url(const rc_client_user_t* user, char buffer[], size_t buffer_size); +RC_EXPORT int RC_CCONV rc_client_user_get_image_url(const rc_client_user_t* user, char buffer[], size_t buffer_size); -typedef struct rc_client_user_game_summary_t -{ +typedef struct rc_client_user_game_summary_t { uint32_t num_core_achievements; uint32_t num_unofficial_achievements; uint32_t num_unlocked_achievements; @@ -213,7 +215,7 @@ typedef struct rc_client_user_game_summary_t * Gets a breakdown of the number of achievements in the game, and how many the user has unlocked. * Used for the "You have unlocked X of Y achievements" message shown when the game starts. */ -void rc_client_get_user_game_summary(const rc_client_t* client, rc_client_user_game_summary_t* summary); +RC_EXPORT void RC_CCONV rc_client_get_user_game_summary(const rc_client_t* client, rc_client_user_game_summary_t* summary); /*****************************************************************************\ | Game | @@ -222,7 +224,7 @@ void rc_client_get_user_game_summary(const rc_client_t* client, rc_client_user_g /** * Start loading an unidentified game. */ -rc_client_async_handle_t* rc_client_begin_identify_and_load_game(rc_client_t* client, +RC_EXPORT rc_client_async_handle_t* RC_CCONV 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, rc_client_callback_t callback, void* callback_userdata); @@ -230,13 +232,13 @@ rc_client_async_handle_t* rc_client_begin_identify_and_load_game(rc_client_t* cl /** * Start loading a game. */ -rc_client_async_handle_t* rc_client_begin_load_game(rc_client_t* client, const char* hash, +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); /** * Unloads the current game. */ -void rc_client_unload_game(rc_client_t* client); +RC_EXPORT void RC_CCONV rc_client_unload_game(rc_client_t* client); typedef struct rc_client_game_t { uint32_t id; @@ -249,18 +251,18 @@ typedef struct rc_client_game_t { /** * Get information about the current game. Returns NULL if no game is loaded. */ -const rc_client_game_t* rc_client_get_game_info(const rc_client_t* client); +RC_EXPORT const rc_client_game_t* RC_CCONV rc_client_get_game_info(const rc_client_t* client); /** * Gets the URL for the game image. * Returns RC_OK on success. */ -int rc_client_game_get_image_url(const rc_client_game_t* game, char buffer[], size_t buffer_size); +RC_EXPORT int RC_CCONV rc_client_game_get_image_url(const rc_client_game_t* game, char buffer[], size_t buffer_size); /** * Changes the active disc in a multi-disc game. */ -rc_client_async_handle_t* rc_client_begin_change_media(rc_client_t* client, const char* file_path, +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); /*****************************************************************************\ @@ -276,7 +278,7 @@ typedef struct rc_client_subset_t { uint32_t num_leaderboards; } rc_client_subset_t; -const rc_client_subset_t* rc_client_get_subset_info(rc_client_t* client, uint32_t subset_id); +RC_EXPORT const rc_client_subset_t* RC_CCONV rc_client_get_subset_info(rc_client_t* client, uint32_t subset_id); /*****************************************************************************\ | Achievements | @@ -297,6 +299,13 @@ enum { RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE_AND_UNOFFICIAL = RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE | RC_CLIENT_ACHIEVEMENT_CATEGORY_UNOFFICIAL }; +enum { + RC_CLIENT_ACHIEVEMENT_TYPE_STANDARD = 0, + RC_CLIENT_ACHIEVEMENT_TYPE_MISSABLE = 1, + RC_CLIENT_ACHIEVEMENT_TYPE_PROGRESSION = 2, + RC_CLIENT_ACHIEVEMENT_TYPE_WIN = 3 +}; + enum { RC_CLIENT_ACHIEVEMENT_BUCKET_UNKNOWN = 0, RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED = 1, @@ -306,7 +315,8 @@ enum { RC_CLIENT_ACHIEVEMENT_BUCKET_RECENTLY_UNLOCKED = 5, RC_CLIENT_ACHIEVEMENT_BUCKET_ACTIVE_CHALLENGE = 6, RC_CLIENT_ACHIEVEMENT_BUCKET_ALMOST_THERE = 7, - NUM_RC_CLIENT_ACHIEVEMENT_BUCKETS = 8 + RC_CLIENT_ACHIEVEMENT_BUCKET_UNSYNCED = 8, + NUM_RC_CLIENT_ACHIEVEMENT_BUCKETS = 9 }; enum { @@ -329,18 +339,22 @@ typedef struct rc_client_achievement_t { uint8_t category; uint8_t bucket; uint8_t unlocked; + /* minimum version: 11.1 */ + float rarity; + float rarity_hardcore; + uint8_t type; } rc_client_achievement_t; /** * Get information about an achievement. Returns NULL if not found. */ -const rc_client_achievement_t* rc_client_get_achievement_info(rc_client_t* client, uint32_t id); +RC_EXPORT const rc_client_achievement_t* RC_CCONV rc_client_get_achievement_info(rc_client_t* client, uint32_t id); /** * Gets the URL for the achievement image. * Returns RC_OK on success. */ -int rc_client_achievement_get_image_url(const rc_client_achievement_t* achievement, int state, char buffer[], size_t buffer_size); +RC_EXPORT int RC_CCONV rc_client_achievement_get_image_url(const rc_client_achievement_t* achievement, int state, char buffer[], size_t buffer_size); typedef struct rc_client_achievement_bucket_t { rc_client_achievement_t** achievements; @@ -365,17 +379,17 @@ enum { * Creates a list of achievements matching the specified category and grouping. * Returns an allocated list that must be free'd by calling rc_client_destroy_achievement_list. */ -rc_client_achievement_list_t* rc_client_create_achievement_list(rc_client_t* client, int category, int grouping); +RC_EXPORT rc_client_achievement_list_t* RC_CCONV rc_client_create_achievement_list(rc_client_t* client, int category, int grouping); /** * Destroys a list allocated by rc_client_get_achievement_list. */ -void rc_client_destroy_achievement_list(rc_client_achievement_list_t* list); +RC_EXPORT void RC_CCONV rc_client_destroy_achievement_list(rc_client_achievement_list_t* list); /** * Returns non-zero if there are any achievements that can be queried through rc_client_create_achievement_list(). */ -int rc_client_has_achievements(rc_client_t* client); +RC_EXPORT int RC_CCONV rc_client_has_achievements(rc_client_t* client); /*****************************************************************************\ | Leaderboards | @@ -411,7 +425,7 @@ typedef struct rc_client_leaderboard_t { /** * Get information about a leaderboard. Returns NULL if not found. */ -const rc_client_leaderboard_t* rc_client_get_leaderboard_info(const rc_client_t* client, uint32_t id); +RC_EXPORT const rc_client_leaderboard_t* RC_CCONV rc_client_get_leaderboard_info(const rc_client_t* client, uint32_t id); typedef struct rc_client_leaderboard_tracker_t { char display[RC_CLIENT_LEADERBOARD_DISPLAY_SIZE]; @@ -450,17 +464,17 @@ enum { * Creates a list of leaderboards matching the specified grouping. * Returns an allocated list that must be free'd by calling rc_client_destroy_leaderboard_list. */ -rc_client_leaderboard_list_t* rc_client_create_leaderboard_list(rc_client_t* client, int grouping); +RC_EXPORT rc_client_leaderboard_list_t* RC_CCONV rc_client_create_leaderboard_list(rc_client_t* client, int grouping); /** * Destroys a list allocated by rc_client_get_leaderboard_list. */ -void rc_client_destroy_leaderboard_list(rc_client_leaderboard_list_t* list); +RC_EXPORT void RC_CCONV rc_client_destroy_leaderboard_list(rc_client_leaderboard_list_t* list); /** * Returns non-zero if the current game has any leaderboards. */ -int rc_client_has_leaderboards(rc_client_t* client); +RC_EXPORT int RC_CCONV rc_client_has_leaderboards(rc_client_t* client); typedef struct rc_client_leaderboard_entry_t { const char* user; @@ -476,33 +490,33 @@ typedef struct rc_client_leaderboard_entry_list_t { int32_t user_index; } rc_client_leaderboard_entry_list_t; -typedef void (*rc_client_fetch_leaderboard_entries_callback_t)(int result, const char* error_message, +typedef void (RC_CCONV *rc_client_fetch_leaderboard_entries_callback_t)(int result, const char* error_message, rc_client_leaderboard_entry_list_t* list, rc_client_t* client, void* callback_userdata); /** * Fetches a list of leaderboard entries from the server. * Callback receives an allocated list that must be free'd by calling rc_client_destroy_leaderboard_entry_list. */ -rc_client_async_handle_t* rc_client_begin_fetch_leaderboard_entries(rc_client_t* client, uint32_t leaderboard_id, +RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_fetch_leaderboard_entries(rc_client_t* client, uint32_t leaderboard_id, uint32_t first_entry, uint32_t count, rc_client_fetch_leaderboard_entries_callback_t callback, void* callback_userdata); /** * Fetches a list of leaderboard entries from the server containing the logged-in user. * Callback receives an allocated list that must be free'd by calling rc_client_destroy_leaderboard_entry_list. */ -rc_client_async_handle_t* rc_client_begin_fetch_leaderboard_entries_around_user(rc_client_t* client, uint32_t leaderboard_id, +RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_fetch_leaderboard_entries_around_user(rc_client_t* client, uint32_t leaderboard_id, uint32_t count, rc_client_fetch_leaderboard_entries_callback_t callback, void* callback_userdata); /** * Gets the URL for the profile picture of the user associated to a leaderboard entry. * Returns RC_OK on success. */ -int rc_client_leaderboard_entry_get_user_image_url(const rc_client_leaderboard_entry_t* entry, char buffer[], size_t buffer_size); +RC_EXPORT int RC_CCONV rc_client_leaderboard_entry_get_user_image_url(const rc_client_leaderboard_entry_t* entry, char buffer[], size_t buffer_size); /** * Destroys a list allocated by rc_client_begin_fetch_leaderboard_entries or rc_client_begin_fetch_leaderboard_entries_around_user. */ -void rc_client_destroy_leaderboard_entry_list(rc_client_leaderboard_entry_list_t* list); +RC_EXPORT void RC_CCONV rc_client_destroy_leaderboard_entry_list(rc_client_leaderboard_entry_list_t* list); /** * Used for scoreboard events. Contains the response from the server when a leaderboard entry is submitted. @@ -543,13 +557,13 @@ typedef struct rc_client_leaderboard_scoreboard_t { /** * Returns non-zero if the current game supports rich presence. */ -int rc_client_has_rich_presence(rc_client_t* client); +RC_EXPORT int RC_CCONV rc_client_has_rich_presence(rc_client_t* client); /** * Gets the current rich presence message. * Returns the number of characters written to buffer. */ -size_t rc_client_get_rich_presence_message(rc_client_t* client, char buffer[], size_t buffer_size); +RC_EXPORT size_t RC_CCONV rc_client_get_rich_presence_message(rc_client_t* client, char buffer[], size_t buffer_size); /*****************************************************************************\ | Processing | @@ -577,16 +591,14 @@ enum { RC_CLIENT_EVENT_RECONNECTED = 18 /* all pending unlocks have been completed */ }; -typedef struct rc_client_server_error_t -{ +typedef struct rc_client_server_error_t { const char* error_message; const char* api; int result; uint32_t related_id; } rc_client_server_error_t; -typedef struct rc_client_event_t -{ +typedef struct rc_client_event_t { uint32_t type; rc_client_achievement_t* achievement; @@ -600,60 +612,66 @@ typedef struct rc_client_event_t /** * Callback used to notify the client when certain events occur. */ -typedef void (*rc_client_event_handler_t)(const rc_client_event_t* event, rc_client_t* client); +typedef void (RC_CCONV *rc_client_event_handler_t)(const rc_client_event_t* event, rc_client_t* client); /** * Provides a callback for event handling. */ -void rc_client_set_event_handler(rc_client_t* client, rc_client_event_handler_t handler); +RC_EXPORT void RC_CCONV rc_client_set_event_handler(rc_client_t* client, rc_client_event_handler_t handler); /** * Provides a callback for reading memory. */ -void rc_client_set_read_memory_function(rc_client_t* client, rc_client_read_memory_func_t handler); +RC_EXPORT void RC_CCONV rc_client_set_read_memory_function(rc_client_t* client, rc_client_read_memory_func_t handler); /** * Determines if there are any active achievements/leaderboards/rich presence that need processing. */ -int rc_client_is_processing_required(rc_client_t* client); +RC_EXPORT int RC_CCONV rc_client_is_processing_required(rc_client_t* client); /** * Processes achievements for the current frame. */ -void rc_client_do_frame(rc_client_t* client); +RC_EXPORT void RC_CCONV rc_client_do_frame(rc_client_t* client); /** * Processes the periodic queue. * Called internally by rc_client_do_frame. * Should be explicitly called if rc_client_do_frame is not being called because emulation is paused. */ -void rc_client_idle(rc_client_t* client); +RC_EXPORT void RC_CCONV rc_client_idle(rc_client_t* client); + +/** + * Determines if a sufficient amount of frames have been processed since the last call to rc_client_can_pause. + * Should not be called unless the client is trying to pause. + * If false is returned, and frames_remaining is not NULL, frames_remaining will be set to the number of frames + * still required before pause is allowed, which can be converted to a time in seconds for displaying to the user. + */ +RC_EXPORT int RC_CCONV rc_client_can_pause(rc_client_t* client, uint32_t* frames_remaining); /** * Informs the runtime that the emulator has been reset. Will reset all achievements and leaderboards * to their initial state (includes hiding indicators/trackers). */ -void rc_client_reset(rc_client_t* client); +RC_EXPORT void RC_CCONV rc_client_reset(rc_client_t* client); /** * Gets the number of bytes needed to serialized the runtime state. */ -size_t rc_client_progress_size(rc_client_t* client); +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. */ -int rc_client_serialize_progress(rc_client_t* client, uint8_t* buffer); +RC_EXPORT int RC_CCONV rc_client_serialize_progress(rc_client_t* client, uint8_t* buffer); /** * Deserializes the runtime state from a buffer. * Returns RC_OK on success, or an error indicator. */ -int rc_client_deserialize_progress(rc_client_t* client, const uint8_t* serialized); +RC_EXPORT int RC_CCONV rc_client_deserialize_progress(rc_client_t* client, const uint8_t* serialized); -#ifdef __cplusplus -} -#endif +RC_END_C_DECLS #endif /* RC_RUNTIME_H */ diff --git a/deps/rcheevos/include/rc_client_raintegration.h b/deps/rcheevos/include/rc_client_raintegration.h new file mode 100644 index 0000000000..b2e77cdec0 --- /dev/null +++ b/deps/rcheevos/include/rc_client_raintegration.h @@ -0,0 +1,85 @@ +#ifndef RC_CLIENT_RAINTEGRATION_H +#define RC_CLIENT_RAINTEGRATION_H + +#ifndef _WIN32 + #undef RC_CLIENT_SUPPORTS_RAINTEGRATION /* Windows required for RAIntegration */ +#endif + +#include + +#include "rc_export.h" + +RC_BEGIN_C_DECLS + +typedef struct rc_client_t rc_client_t; /* forward reference; in rc_client.h */ + +/* types needed to implement raintegration */ + +typedef struct rc_client_raintegration_menu_item_t { + const char* label; + uint32_t id; + uint8_t checked; + uint8_t enabled; +} rc_client_raintegration_menu_item_t; + +typedef struct rc_client_raintegration_menu_t { + rc_client_raintegration_menu_item_t* items; + uint32_t num_items; +} rc_client_raintegration_menu_t; + +enum { + RC_CLIENT_RAINTEGRATION_EVENT_TYPE_NONE = 0, + RC_CLIENT_RAINTEGRATION_EVENT_MENUITEM_CHECKED_CHANGED = 1, /* [menu_item] checked changed */ + RC_CLIENT_RAINTEGRATION_EVENT_HARDCORE_CHANGED = 2, /* hardcore was enabled or disabled */ + RC_CLIENT_RAINTEGRATION_EVENT_PAUSE = 3 /* emulated system should be paused */ +}; + +typedef struct rc_client_raintegration_event_t { + uint32_t type; + + const rc_client_raintegration_menu_item_t* menu_item; +} rc_client_raintegration_event_t; + +typedef void (RC_CCONV *rc_client_raintegration_event_handler_t)(const rc_client_raintegration_event_t* event, + rc_client_t* 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); + +/* types needed to integrate raintegration */ + +#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION + +#ifndef RC_CLIENT_SUPPORTS_EXTERNAL + #define RC_CLIENT_SUPPORTS_EXTERNAL /* external rc_client required for RAIntegration */ +#endif + +#include /* HWND */ + +#include "rc_client.h" + +RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_load_raintegration(rc_client_t* client, + const wchar_t* search_directory, HWND main_window_handle, + const char* client_name, const char* client_version, + rc_client_callback_t callback, void* callback_userdata); + +RC_EXPORT void RC_CCONV rc_client_unload_raintegration(rc_client_t* client); + +RC_EXPORT void RC_CCONV rc_client_raintegration_update_main_window_handle(rc_client_t* client, HWND main_window_handle); + +RC_EXPORT const rc_client_raintegration_menu_t* RC_CCONV rc_client_raintegration_get_menu(const rc_client_t* client); + +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 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_event_handler(rc_client_t* client, + rc_client_raintegration_event_handler_t handler); + +#endif /* RC_CLIENT_SUPPORTS_RAINTEGRATION */ + +RC_END_C_DECLS + +#endif /* RC_CLIENT_RAINTEGRATION_H */ diff --git a/deps/rcheevos/include/rc_consoles.h b/deps/rcheevos/include/rc_consoles.h index e416bc9e08..269879202b 100644 --- a/deps/rcheevos/include/rc_consoles.h +++ b/deps/rcheevos/include/rc_consoles.h @@ -1,12 +1,12 @@ #ifndef RC_CONSOLES_H #define RC_CONSOLES_H -#ifdef __cplusplus -extern "C" { -#endif +#include "rc_export.h" #include +RC_BEGIN_C_DECLS + /*****************************************************************************\ | Console identifiers | \*****************************************************************************/ @@ -95,10 +95,11 @@ enum { RC_CONSOLE_UZEBOX = 80, RC_CONSOLE_HUBS = 100, - RC_CONSOLE_EVENTS = 101 + RC_CONSOLE_EVENTS = 101, + RC_CONSOLE_STANDALONE = 102 }; -const char* rc_console_name(int console_id); +RC_EXPORT const char* RC_CCONV rc_console_name(uint32_t console_id); /*****************************************************************************\ | Memory mapping | @@ -129,11 +130,8 @@ typedef struct rc_memory_regions_t { } rc_memory_regions_t; -const rc_memory_regions_t* rc_console_memory_regions(uint32_t console_id); +RC_EXPORT const rc_memory_regions_t* RC_CCONV rc_console_memory_regions(uint32_t console_id); - -#ifdef __cplusplus -} -#endif +RC_END_C_DECLS #endif /* RC_CONSOLES_H */ diff --git a/deps/rcheevos/include/rc_error.h b/deps/rcheevos/include/rc_error.h index 89d7627088..d0aca2fd70 100644 --- a/deps/rcheevos/include/rc_error.h +++ b/deps/rcheevos/include/rc_error.h @@ -1,9 +1,9 @@ #ifndef RC_ERROR_H #define RC_ERROR_H -#ifdef __cplusplus -extern "C" { -#endif +#include "rc_export.h" + +RC_BEGIN_C_DECLS /*****************************************************************************\ | Return values | @@ -48,10 +48,8 @@ enum { RC_EXPIRED_TOKEN = -35 }; -const char* rc_error_str(int ret); +RC_EXPORT const char* RC_CCONV rc_error_str(int ret); -#ifdef __cplusplus -} -#endif +RC_END_C_DECLS #endif /* RC_ERROR_H */ diff --git a/deps/rcheevos/include/rc_export.h b/deps/rcheevos/include/rc_export.h new file mode 100644 index 0000000000..da111056d3 --- /dev/null +++ b/deps/rcheevos/include/rc_export.h @@ -0,0 +1,100 @@ +#ifndef RC_EXPORT_H +#define RC_EXPORT_H + +/* These macros control how callbacks and public functions are defined */ + +/* RC_SHARED should be defined when building rcheevos as a shared library (e.g. dll/dylib/so). External code should not define this macro. */ +/* RC_STATIC should be defined when building rcheevos as a static library. External code should also define this macro. */ +/* RC_IMPORT should be defined for external code using rcheevos as a shared library. */ + +/* For compatibility, if none of these three macros are defined, then the build is assumed to be RC_STATIC */ + +#if !defined(RC_SHARED) && !defined(RC_STATIC) && !defined(RC_IMPORT) + #define RC_STATIC +#endif + +#if (defined(RC_SHARED) && defined(RC_STATIC)) || (defined(RC_SHARED) && defined(RC_IMPORT)) || (defined(RC_STATIC) && defined(RC_IMPORT)) + #error RC_SHARED, RC_STATIC, and RC_IMPORT are mutually exclusive +#endif + +/* RC_BEGIN_C_DECLS and RC_END_C_DECLS should be used for all headers, to enforce C linkage and the C calling convention */ +/* RC_BEGIN_C_DECLS should be placed after #include's and before header declarations */ +/* RC_END_C_DECLS should be placed after header declarations */ + +/* example usage + * + * #ifndef RC_HEADER_H + * #define RC_HEADER_H + * + * #include + * + * RC_BEGIN_C_DECLS + * + * uint8_t rc_function(void); + * + * RC_END_C_DECLS + * + * #endif + */ + +#ifdef __cplusplus + #define RC_BEGIN_C_DECLS extern "C" { + #define RC_END_C_DECLS } +#else + #define RC_BEGIN_C_DECLS + #define RC_END_C_DECLS +#endif + +/* RC_CCONV should be used for public functions and callbacks, to enforce the cdecl calling convention, if applicable */ +/* RC_CCONV should be placed after the return type, and between the ( and * for callbacks */ + +/* example usage */ +/* void RC_CCONV rc_function(void) */ +/* void (RC_CCONV *rc_callback)(void) */ + +#if defined(_WIN32) + /* Windows compilers will ignore __cdecl when not applicable */ + #define RC_CCONV __cdecl +#elif defined(__GNUC__) && defined(__i386__) + /* GNU C compilers will warn if cdecl is defined on an unsupported platform */ + #define RC_CCONV __attribute__((cdecl)) +#else + #define RC_CCONV +#endif + +/* RC_EXPORT should be used for public functions */ +/* RC_EXPORT will provide necessary hints for shared library usage, if applicable */ +/* RC_EXPORT should be placed before the return type */ + +/* example usage */ +/* RC_EXPORT void rc_function(void) */ + +#ifdef RC_SHARED + #if defined(_WIN32) + #define RC_EXPORT __declspec(dllexport) + #elif defined(__GNUC__) && __GNUC__ >= 4 + #define RC_EXPORT __attribute__((visibility("default"))) + #else + #define RC_EXPORT + #endif +#endif + +#ifdef RC_IMPORT + #if defined(_WIN32) + #define RC_EXPORT __declspec(dllimport) + #elif defined(__GNUC__) && __GNUC__ >= 4 + #define RC_EXPORT __attribute__((visibility("default"))) + #else + #define RC_EXPORT + #endif +#endif + +#ifdef RC_STATIC + #if defined(__GNUC__) && __GNUC__ >= 4 + #define RC_EXPORT __attribute__((visibility("default"))) + #else + #define RC_EXPORT + #endif +#endif + +#endif /* RC_EXPORT_H */ diff --git a/deps/rcheevos/include/rc_hash.h b/deps/rcheevos/include/rc_hash.h index ba9ea1c027..3a71bcebf8 100644 --- a/deps/rcheevos/include/rc_hash.h +++ b/deps/rcheevos/include/rc_hash.h @@ -7,21 +7,19 @@ #include "rc_consoles.h" -#ifdef __cplusplus -extern "C" { -#endif +RC_BEGIN_C_DECLS /* ===================================================== */ /* generates a hash from a block of memory. * returns non-zero on success, or zero on failure. */ - int rc_hash_generate_from_buffer(char hash[33], int console_id, const uint8_t* buffer, size_t buffer_size); + RC_EXPORT int RC_CCONV rc_hash_generate_from_buffer(char hash[33], uint32_t console_id, const uint8_t* buffer, size_t buffer_size); /* generates a hash from a file. * returns non-zero on success, or zero on failure. */ - int rc_hash_generate_from_file(char hash[33], int console_id, const char* path); + RC_EXPORT int RC_CCONV rc_hash_generate_from_file(char hash[33], uint32_t console_id, const char* path); /* ===================================================== */ @@ -40,44 +38,44 @@ extern "C" { * - path must be provided * - if buffer and buffer_size are provided, path may be a filename (i.e. for something extracted from a zip file) */ - void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* path, const uint8_t* buffer, size_t buffer_size); + RC_EXPORT void RC_CCONV rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* path, const uint8_t* buffer, size_t buffer_size); /* releases resources associated to a rc_hash_iterator */ - void rc_hash_destroy_iterator(struct rc_hash_iterator* iterator); + RC_EXPORT void RC_CCONV rc_hash_destroy_iterator(struct rc_hash_iterator* iterator); /* generates the next hash for the data in the rc_hash_iterator. * returns non-zero if a hash was generated, or zero if no more hashes can be generated for the data. */ - int rc_hash_iterate(char hash[33], struct rc_hash_iterator* iterator); + RC_EXPORT int RC_CCONV rc_hash_iterate(char hash[33], struct rc_hash_iterator* iterator); /* ===================================================== */ /* specifies a function to call when an error occurs to display the error message */ - typedef void (*rc_hash_message_callback)(const char*); - void rc_hash_init_error_message_callback(rc_hash_message_callback callback); + typedef void (RC_CCONV *rc_hash_message_callback)(const char*); + RC_EXPORT void RC_CCONV rc_hash_init_error_message_callback(rc_hash_message_callback callback); /* specifies a function to call for verbose logging */ - void rc_hash_init_verbose_message_callback(rc_hash_message_callback callback); + RC_EXPORT void rc_hash_init_verbose_message_callback(rc_hash_message_callback callback); /* ===================================================== */ /* opens a file */ - typedef void* (*rc_hash_filereader_open_file_handler)(const char* path_utf8); + typedef void* (RC_CCONV *rc_hash_filereader_open_file_handler)(const char* path_utf8); /* moves the file pointer - standard fseek parameters */ - typedef void (*rc_hash_filereader_seek_handler)(void* file_handle, int64_t offset, int origin); + typedef void (RC_CCONV *rc_hash_filereader_seek_handler)(void* file_handle, int64_t offset, int origin); /* locates the file pointer */ - typedef int64_t (*rc_hash_filereader_tell_handler)(void* file_handle); + typedef int64_t (RC_CCONV *rc_hash_filereader_tell_handler)(void* file_handle); /* reads the specified number of bytes from the file starting at the read pointer. * returns the number of bytes actually read. */ - typedef size_t (*rc_hash_filereader_read_handler)(void* file_handle, void* buffer, size_t requested_bytes); + typedef size_t (RC_CCONV *rc_hash_filereader_read_handler)(void* file_handle, void* buffer, size_t requested_bytes); /* closes the file */ - typedef void (*rc_hash_filereader_close_file_handler)(void* file_handle); + typedef void (RC_CCONV *rc_hash_filereader_close_file_handler)(void* file_handle); struct rc_hash_filereader { @@ -88,7 +86,7 @@ extern "C" { rc_hash_filereader_close_file_handler close; }; - void rc_hash_init_custom_filereader(struct rc_hash_filereader* reader); + RC_EXPORT void RC_CCONV rc_hash_init_custom_filereader(struct rc_hash_filereader* reader); /* ===================================================== */ @@ -100,18 +98,18 @@ extern "C" { /* opens a track from the specified file. see the RC_HASH_CDTRACK_ defines for special tracks. * returns a handle to be passed to the other functions, or NULL if the track could not be opened. */ - typedef void* (*rc_hash_cdreader_open_track_handler)(const char* path, uint32_t track); + typedef void* (RC_CCONV *rc_hash_cdreader_open_track_handler)(const char* path, uint32_t track); /* attempts to read the specified number of bytes from the file starting at the specified absolute sector. * returns the number of bytes actually read. */ - typedef size_t (*rc_hash_cdreader_read_sector_handler)(void* track_handle, uint32_t sector, void* buffer, size_t requested_bytes); + typedef size_t (RC_CCONV *rc_hash_cdreader_read_sector_handler)(void* track_handle, uint32_t sector, void* buffer, size_t requested_bytes); /* closes the track handle */ - typedef void (*rc_hash_cdreader_close_track_handler)(void* track_handle); + typedef void (RC_CCONV *rc_hash_cdreader_close_track_handler)(void* track_handle); /* gets the absolute sector index for the first sector of a track */ - typedef uint32_t(*rc_hash_cdreader_first_track_sector_handler)(void* track_handle); + typedef uint32_t(RC_CCONV *rc_hash_cdreader_first_track_sector_handler)(void* track_handle); struct rc_hash_cdreader { @@ -121,14 +119,34 @@ extern "C" { rc_hash_cdreader_first_track_sector_handler first_track_sector; }; - void rc_hash_get_default_cdreader(struct rc_hash_cdreader* cdreader); - void rc_hash_init_default_cdreader(void); - void rc_hash_init_custom_cdreader(struct rc_hash_cdreader* reader); + RC_EXPORT void RC_CCONV rc_hash_get_default_cdreader(struct rc_hash_cdreader* cdreader); + RC_EXPORT void RC_CCONV rc_hash_init_default_cdreader(void); + RC_EXPORT void RC_CCONV rc_hash_init_custom_cdreader(struct rc_hash_cdreader* reader); + + /* specifies a function called to obtain a 3DS CIA decryption normal key. + * this key would be derived from slot0x3DKeyX and the common key specified by the passed index. + * the normal key should be written in big endian format + * returns non-zero on success, or zero on failure. + */ + typedef int (RC_CCONV *rc_hash_3ds_get_cia_normal_key_func)(uint8_t common_key_index, uint8_t out_normal_key[16]); + RC_EXPORT void RC_CCONV rc_hash_init_3ds_get_cia_normal_key_func(rc_hash_3ds_get_cia_normal_key_func func); + + /* specifies a function called to obtain 3DS NCCH decryption normal keys. + * the primary key will always use slot0x2CKeyX and the passed primary KeyY. + * the secondary key will use the KeyX slot passed + * the secondary KeyY will be identical to the primary keyY if the passed program id is NULL + * if the program id is not null, then the secondary KeyY will be obtained with "seed crypto" + * with "seed crypto" the 8 byte program id can be used to obtain a 16 byte "seed" within the seeddb.bin firmware file + * the primary KeyY then the seed will then be hashed with SHA256, and the upper 16 bytes of the digest will be the secondary KeyY used + * the normal keys should be written in big endian format + * returns non-zero on success, or zero on failure. + */ + typedef int (RC_CCONV *rc_hash_3ds_get_ncch_normal_keys_func)(uint8_t primary_key_y[16], uint8_t secondary_key_x_slot, uint8_t* optional_program_id, + uint8_t out_primary_key[16], uint8_t out_secondary_key[16]); + RC_EXPORT void RC_CCONV rc_hash_init_3ds_get_ncch_normal_keys_func(rc_hash_3ds_get_ncch_normal_keys_func func); /* ===================================================== */ -#ifdef __cplusplus -} -#endif +RC_END_C_DECLS #endif /* RC_HASH_H */ diff --git a/deps/rcheevos/include/rc_runtime.h b/deps/rcheevos/include/rc_runtime.h index 2804cc97d9..c5780c47a2 100644 --- a/deps/rcheevos/include/rc_runtime.h +++ b/deps/rcheevos/include/rc_runtime.h @@ -1,15 +1,13 @@ #ifndef RC_RUNTIME_H #define RC_RUNTIME_H -#ifdef __cplusplus -extern "C" { -#endif - #include "rc_error.h" #include #include +RC_BEGIN_C_DECLS + /*****************************************************************************\ | Forward Declarations (defined in rc_runtime_types.h) | \*****************************************************************************/ @@ -35,7 +33,7 @@ typedef struct rc_value_t rc_value_t; * num_bytes is greater than 1, the value is read in little-endian from * memory. */ -typedef uint32_t(*rc_runtime_peek_t)(uint32_t address, uint32_t num_bytes, void* ud); +typedef uint32_t(RC_CCONV *rc_runtime_peek_t)(uint32_t address, uint32_t num_bytes, void* ud); /*****************************************************************************\ | Runtime | @@ -94,24 +92,24 @@ typedef struct rc_runtime_t { } rc_runtime_t; -rc_runtime_t* rc_runtime_alloc(void); -void rc_runtime_init(rc_runtime_t* runtime); -void rc_runtime_destroy(rc_runtime_t* runtime); +RC_EXPORT rc_runtime_t* RC_CCONV rc_runtime_alloc(void); +RC_EXPORT void RC_CCONV rc_runtime_init(rc_runtime_t* runtime); +RC_EXPORT void RC_CCONV rc_runtime_destroy(rc_runtime_t* runtime); -int rc_runtime_activate_achievement(rc_runtime_t* runtime, uint32_t id, const char* memaddr, lua_State* L, int funcs_idx); -void rc_runtime_deactivate_achievement(rc_runtime_t* runtime, uint32_t id); -rc_trigger_t* rc_runtime_get_achievement(const rc_runtime_t* runtime, uint32_t id); -int rc_runtime_get_achievement_measured(const rc_runtime_t* runtime, uint32_t id, unsigned* measured_value, unsigned* measured_target); -int rc_runtime_format_achievement_measured(const rc_runtime_t* runtime, uint32_t id, char *buffer, size_t buffer_size); +RC_EXPORT int RC_CCONV rc_runtime_activate_achievement(rc_runtime_t* runtime, uint32_t id, const char* memaddr, lua_State* L, int funcs_idx); +RC_EXPORT void RC_CCONV rc_runtime_deactivate_achievement(rc_runtime_t* runtime, uint32_t id); +RC_EXPORT rc_trigger_t* RC_CCONV rc_runtime_get_achievement(const rc_runtime_t* runtime, uint32_t id); +RC_EXPORT int RC_CCONV rc_runtime_get_achievement_measured(const rc_runtime_t* runtime, uint32_t id, unsigned* measured_value, unsigned* measured_target); +RC_EXPORT int RC_CCONV rc_runtime_format_achievement_measured(const rc_runtime_t* runtime, uint32_t id, char *buffer, size_t buffer_size); -int rc_runtime_activate_lboard(rc_runtime_t* runtime, uint32_t id, const char* memaddr, lua_State* L, int funcs_idx); -void rc_runtime_deactivate_lboard(rc_runtime_t* runtime, uint32_t id); -rc_lboard_t* rc_runtime_get_lboard(const rc_runtime_t* runtime, uint32_t id); -int rc_runtime_format_lboard_value(char* buffer, int size, int32_t value, int format); +RC_EXPORT int RC_CCONV rc_runtime_activate_lboard(rc_runtime_t* runtime, uint32_t id, const char* memaddr, lua_State* L, int funcs_idx); +RC_EXPORT void RC_CCONV rc_runtime_deactivate_lboard(rc_runtime_t* runtime, uint32_t id); +RC_EXPORT rc_lboard_t* RC_CCONV rc_runtime_get_lboard(const rc_runtime_t* runtime, uint32_t id); +RC_EXPORT int RC_CCONV rc_runtime_format_lboard_value(char* buffer, int size, int32_t value, int format); -int rc_runtime_activate_richpresence(rc_runtime_t* runtime, const char* script, lua_State* L, int funcs_idx); -int rc_runtime_get_richpresence(const rc_runtime_t* runtime, char* buffer, size_t buffersize, rc_runtime_peek_t peek, void* peek_ud, lua_State* L); +RC_EXPORT int RC_CCONV rc_runtime_activate_richpresence(rc_runtime_t* runtime, const char* script, lua_State* L, int funcs_idx); +RC_EXPORT int RC_CCONV rc_runtime_get_richpresence(const rc_runtime_t* runtime, char* buffer, size_t buffersize, rc_runtime_peek_t peek, void* peek_ud, lua_State* L); enum { RC_RUNTIME_EVENT_ACHIEVEMENT_ACTIVATED, /* from WAITING, PAUSED, or PRIMED to ACTIVE */ @@ -136,21 +134,19 @@ typedef struct rc_runtime_event_t { } rc_runtime_event_t; -typedef void (*rc_runtime_event_handler_t)(const rc_runtime_event_t* runtime_event); +typedef void (RC_CCONV *rc_runtime_event_handler_t)(const rc_runtime_event_t* runtime_event); -void rc_runtime_do_frame(rc_runtime_t* runtime, rc_runtime_event_handler_t event_handler, rc_runtime_peek_t peek, void* ud, lua_State* L); -void rc_runtime_reset(rc_runtime_t* runtime); +RC_EXPORT void RC_CCONV rc_runtime_do_frame(rc_runtime_t* runtime, rc_runtime_event_handler_t event_handler, rc_runtime_peek_t peek, void* ud, lua_State* L); +RC_EXPORT void RC_CCONV rc_runtime_reset(rc_runtime_t* runtime); -typedef int (*rc_runtime_validate_address_t)(uint32_t address); -void rc_runtime_validate_addresses(rc_runtime_t* runtime, rc_runtime_event_handler_t event_handler, rc_runtime_validate_address_t validate_handler); -void rc_runtime_invalidate_address(rc_runtime_t* runtime, uint32_t address); +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); -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); -int rc_runtime_deserialize_progress(rc_runtime_t* runtime, const uint8_t* serialized, lua_State* L); +RC_EXPORT int RC_CCONV rc_runtime_progress_size(const rc_runtime_t* runtime, lua_State* L); +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_deserialize_progress(rc_runtime_t* runtime, const uint8_t* serialized, lua_State* L); -#ifdef __cplusplus -} -#endif +RC_END_C_DECLS #endif /* RC_RUNTIME_H */ diff --git a/deps/rcheevos/include/rc_runtime_types.h b/deps/rcheevos/include/rc_runtime_types.h index 04a0c931ca..d8a7db65d0 100644 --- a/deps/rcheevos/include/rc_runtime_types.h +++ b/deps/rcheevos/include/rc_runtime_types.h @@ -1,15 +1,13 @@ #ifndef RC_RUNTIME_TYPES_H #define RC_RUNTIME_TYPES_H -#ifdef __cplusplus -extern "C" { -#endif - #include "rc_error.h" #include #include +RC_BEGIN_C_DECLS + #ifndef RC_RUNTIME_H /* prevents pedantic redefiniton error */ typedef struct lua_State lua_State; @@ -31,7 +29,7 @@ typedef struct rc_value_t rc_value_t; * num_bytes is greater than 1, the value is read in little-endian from * memory. */ -typedef uint32_t(*rc_peek_t)(uint32_t address, uint32_t num_bytes, void* ud); +typedef uint32_t(RC_CCONV *rc_peek_t)(uint32_t address, uint32_t num_bytes, void* ud); /*****************************************************************************\ | Memory References | @@ -132,7 +130,7 @@ typedef struct rc_operand_t { } rc_operand_t; -int rc_operand_is_memref(const rc_operand_t* operand); +RC_EXPORT int RC_CCONV rc_operand_is_memref(const rc_operand_t* operand); /*****************************************************************************\ | Conditions | @@ -276,11 +274,11 @@ struct rc_trigger_t { uint8_t measured_as_percent; }; -int rc_trigger_size(const char* memaddr); -rc_trigger_t* rc_parse_trigger(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx); -int rc_evaluate_trigger(rc_trigger_t* trigger, rc_peek_t peek, void* ud, lua_State* L); -int rc_test_trigger(rc_trigger_t* trigger, rc_peek_t peek, void* ud, lua_State* L); -void rc_reset_trigger(rc_trigger_t* self); +RC_EXPORT int RC_CCONV rc_trigger_size(const char* memaddr); +RC_EXPORT rc_trigger_t* RC_CCONV rc_parse_trigger(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx); +RC_EXPORT int RC_CCONV rc_evaluate_trigger(rc_trigger_t* trigger, rc_peek_t peek, void* ud, lua_State* L); +RC_EXPORT int RC_CCONV rc_test_trigger(rc_trigger_t* trigger, rc_peek_t peek, void* ud, lua_State* L); +RC_EXPORT void RC_CCONV rc_reset_trigger(rc_trigger_t* self); /*****************************************************************************\ | Values | @@ -303,9 +301,9 @@ struct rc_value_t { rc_value_t* next; }; -int rc_value_size(const char* memaddr); -rc_value_t* rc_parse_value(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx); -int32_t rc_evaluate_value(rc_value_t* value, rc_peek_t peek, void* ud, lua_State* L); +RC_EXPORT int RC_CCONV rc_value_size(const char* memaddr); +RC_EXPORT rc_value_t* RC_CCONV rc_parse_value(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx); +RC_EXPORT int32_t RC_CCONV rc_evaluate_value(rc_value_t* value, rc_peek_t peek, void* ud, lua_State* L); /*****************************************************************************\ | Leaderboards | @@ -333,10 +331,10 @@ struct rc_lboard_t { uint8_t state; }; -int rc_lboard_size(const char* memaddr); -rc_lboard_t* rc_parse_lboard(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx); -int rc_evaluate_lboard(rc_lboard_t* lboard, int32_t* value, rc_peek_t peek, void* peek_ud, lua_State* L); -void rc_reset_lboard(rc_lboard_t* lboard); +RC_EXPORT int RC_CCONV rc_lboard_size(const char* memaddr); +RC_EXPORT rc_lboard_t* RC_CCONV rc_parse_lboard(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx); +RC_EXPORT int RC_CCONV rc_evaluate_lboard(rc_lboard_t* lboard, int32_t* value, rc_peek_t peek, void* peek_ud, lua_State* L); +RC_EXPORT void RC_CCONV rc_reset_lboard(rc_lboard_t* lboard); /*****************************************************************************\ | Value formatting | @@ -356,11 +354,18 @@ enum { RC_FORMAT_FLOAT3, RC_FORMAT_FLOAT4, RC_FORMAT_FLOAT5, - RC_FORMAT_FLOAT6 + RC_FORMAT_FLOAT6, + RC_FORMAT_FIXED1, + RC_FORMAT_FIXED2, + RC_FORMAT_FIXED3, + RC_FORMAT_TENS, + RC_FORMAT_HUNDREDS, + RC_FORMAT_THOUSANDS, + RC_FORMAT_UNSIGNED_VALUE }; -int rc_parse_format(const char* format_str); -int rc_format_value(char* buffer, int size, int32_t value, int format); +RC_EXPORT int RC_CCONV rc_parse_format(const char* format_str); +RC_EXPORT int RC_CCONV rc_format_value(char* buffer, int size, int32_t value, int format); /*****************************************************************************\ | Rich Presence | @@ -411,16 +416,14 @@ struct rc_richpresence_t { rc_value_t* variables; }; -int rc_richpresence_size(const char* script); -int rc_richpresence_size_lines(const char* script, int* lines_read); -rc_richpresence_t* rc_parse_richpresence(void* buffer, const char* script, lua_State* L, int funcs_ndx); -int rc_evaluate_richpresence(rc_richpresence_t* richpresence, char* buffer, size_t buffersize, rc_peek_t peek, void* peek_ud, lua_State* L); -void rc_update_richpresence(rc_richpresence_t* richpresence, rc_peek_t peek, void* peek_ud, lua_State* L); -int rc_get_richpresence_display_string(rc_richpresence_t* richpresence, char* buffer, size_t buffersize, rc_peek_t peek, void* peek_ud, lua_State* L); -void rc_reset_richpresence(rc_richpresence_t* self); +RC_EXPORT int RC_CCONV rc_richpresence_size(const char* script); +RC_EXPORT int RC_CCONV rc_richpresence_size_lines(const char* script, int* lines_read); +RC_EXPORT rc_richpresence_t* RC_CCONV rc_parse_richpresence(void* buffer, const char* script, lua_State* L, int funcs_ndx); +RC_EXPORT int RC_CCONV rc_evaluate_richpresence(rc_richpresence_t* richpresence, char* buffer, size_t buffersize, rc_peek_t peek, void* peek_ud, lua_State* L); +RC_EXPORT void RC_CCONV rc_update_richpresence(rc_richpresence_t* richpresence, rc_peek_t peek, void* peek_ud, lua_State* L); +RC_EXPORT int RC_CCONV rc_get_richpresence_display_string(rc_richpresence_t* richpresence, char* buffer, size_t buffersize, rc_peek_t peek, void* peek_ud, lua_State* L); +RC_EXPORT void RC_CCONV rc_reset_richpresence(rc_richpresence_t* self); -#ifdef __cplusplus -} -#endif +RC_END_C_DECLS #endif /* RC_RUNTIME_TYPES_H */ diff --git a/deps/rcheevos/include/rc_url.h b/deps/rcheevos/include/rc_url.h index b61cd808c9..e3f06d698d 100644 --- a/deps/rcheevos/include/rc_url.h +++ b/deps/rcheevos/include/rc_url.h @@ -1,38 +1,36 @@ #ifndef RC_URL_H #define RC_URL_H +#include "rc_export.h" + #include -#ifdef __cplusplus -extern "C" { -#endif +RC_BEGIN_C_DECLS -int rc_url_award_cheevo(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned cheevo_id, int hardcore, const char* game_hash); +RC_EXPORT int RC_CCONV rc_url_award_cheevo(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned cheevo_id, int hardcore, const char* game_hash); -int rc_url_submit_lboard(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned lboard_id, int value); +RC_EXPORT int RC_CCONV rc_url_submit_lboard(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned lboard_id, int value); -int rc_url_get_lboard_entries(char* buffer, size_t size, unsigned lboard_id, unsigned first_index, unsigned count); -int rc_url_get_lboard_entries_near_user(char* buffer, size_t size, unsigned lboard_id, const char* user_name, unsigned count); +RC_EXPORT int RC_CCONV rc_url_get_lboard_entries(char* buffer, size_t size, unsigned lboard_id, unsigned first_index, unsigned count); +RC_EXPORT int RC_CCONV rc_url_get_lboard_entries_near_user(char* buffer, size_t size, unsigned lboard_id, const char* user_name, unsigned count); -int rc_url_get_gameid(char* buffer, size_t size, const char* hash); +RC_EXPORT int RC_CCONV rc_url_get_gameid(char* buffer, size_t size, const char* hash); -int rc_url_get_patch(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned gameid); +RC_EXPORT int RC_CCONV rc_url_get_patch(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned gameid); -int rc_url_get_badge_image(char* buffer, size_t size, const char* badge_name); +RC_EXPORT int RC_CCONV rc_url_get_badge_image(char* buffer, size_t size, const char* badge_name); -int rc_url_login_with_password(char* buffer, size_t size, const char* user_name, const char* password); +RC_EXPORT int RC_CCONV rc_url_login_with_password(char* buffer, size_t size, const char* user_name, const char* password); -int rc_url_login_with_token(char* buffer, size_t size, const char* user_name, const char* login_token); +RC_EXPORT int RC_CCONV rc_url_login_with_token(char* buffer, size_t size, const char* user_name, const char* login_token); -int rc_url_get_unlock_list(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned gameid, int hardcore); +RC_EXPORT int RC_CCONV rc_url_get_unlock_list(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned gameid, int hardcore); -int rc_url_post_playing(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned gameid); +RC_EXPORT int RC_CCONV rc_url_post_playing(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned gameid); -int rc_url_ping(char* url_buffer, size_t url_buffer_size, char* post_buffer, size_t post_buffer_size, - const char* user_name, const char* login_token, unsigned gameid, const char* rich_presence); +RC_EXPORT int RC_CCONV rc_url_ping(char* url_buffer, size_t url_buffer_size, char* post_buffer, size_t post_buffer_size, + const char* user_name, const char* login_token, unsigned gameid, const char* rich_presence); -#ifdef __cplusplus -} -#endif +RC_END_C_DECLS #endif /* RC_URL_H */ diff --git a/deps/rcheevos/src/rc_util.h b/deps/rcheevos/include/rc_util.h similarity index 95% rename from deps/rcheevos/src/rc_util.h rename to deps/rcheevos/include/rc_util.h index 34f75da912..18d27e0fc7 100644 --- a/deps/rcheevos/src/rc_util.h +++ b/deps/rcheevos/include/rc_util.h @@ -1,12 +1,12 @@ #ifndef RC_UTIL_H #define RC_UTIL_H +#include "rc_export.h" + #include #include -#ifdef __cplusplus -extern "C" { -#endif +RC_BEGIN_C_DECLS /** * A block of memory for variable length data (like strings and arrays). @@ -46,8 +46,6 @@ uint32_t rc_djb2(const char* input); void rc_format_md5(char checksum[33], const uint8_t digest[16]); -#ifdef __cplusplus -} -#endif +RC_END_C_DECLS #endif /* RC_UTIL_H */ diff --git a/deps/rcheevos/src/rapi/rc_api_common.c b/deps/rcheevos/src/rapi/rc_api_common.c index 96c32b32e3..5835b28014 100644 --- a/deps/rcheevos/src/rapi/rc_api_common.c +++ b/deps/rcheevos/src/rapi/rc_api_common.c @@ -475,13 +475,13 @@ int rc_json_get_required_array(uint32_t* num_entries, rc_json_field_t* array_fie return 0; #endif - if (!rc_json_get_optional_array(num_entries, array_field, response, field, field_name)) + if (!rc_json_get_optional_array(num_entries, array_field, field, field_name)) return rc_json_missing_field(response, field); return 1; } -int rc_json_get_optional_array(uint32_t* num_entries, rc_json_field_t* array_field, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name) { +int rc_json_get_optional_array(uint32_t* num_entries, rc_json_field_t* array_field, const rc_json_field_t* field, const char* field_name) { #ifndef NDEBUG if (strcmp(field->name, field_name) != 0) return 0; @@ -783,6 +783,52 @@ int rc_json_get_required_unum(uint32_t* out, rc_api_response_t* response, const return rc_json_missing_field(response, field); } +int rc_json_get_float(float* out, const rc_json_field_t* field, const char* field_name) { + int32_t whole, fraction, fraction_denominator; + const char* decimal = field->value_start; + + if (!decimal) { + *out = 0.0f; + return 0; + } + + if (!rc_json_get_num(&whole, field, field_name)) + return 0; + + while (decimal < field->value_end && *decimal != '.') + ++decimal; + + fraction = 0; + fraction_denominator = 1; + if (decimal) { + ++decimal; + while (decimal < field->value_end && *decimal >= '0' && *decimal <= '9') { + fraction *= 10; + fraction += *decimal - '0'; + fraction_denominator *= 10; + ++decimal; + } + } + + if (whole < 0) + fraction = -fraction; + + *out = (float)whole + ((float)fraction / (float)fraction_denominator); + return 1; +} + +void rc_json_get_optional_float(float* out, const rc_json_field_t* field, const char* field_name, float default_value) { + if (!rc_json_get_float(out, field, field_name)) + *out = default_value; +} + +int rc_json_get_required_float(float* out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name) { + if (rc_json_get_float(out, field, field_name)) + return 1; + + return rc_json_missing_field(response, field); +} + int rc_json_get_datetime(time_t* out, const rc_json_field_t* field, const char* field_name) { struct tm tm; diff --git a/deps/rcheevos/src/rapi/rc_api_common.h b/deps/rcheevos/src/rapi/rc_api_common.h index 9fbb8a23d0..7311cfff55 100644 --- a/deps/rcheevos/src/rapi/rc_api_common.h +++ b/deps/rcheevos/src/rapi/rc_api_common.h @@ -6,9 +6,7 @@ #include #include -#ifdef __cplusplus -extern "C" { -#endif +RC_BEGIN_C_DECLS #define RC_CONTENT_TYPE_URLENCODED "application/x-www-form-urlencoded" @@ -47,16 +45,19 @@ int rc_json_parse_server_response(rc_api_response_t* response, const rc_api_serv int rc_json_get_string(const char** out, rc_buffer_t* buffer, const rc_json_field_t* field, const char* field_name); int rc_json_get_num(int32_t* out, const rc_json_field_t* field, const char* field_name); int rc_json_get_unum(uint32_t* out, const rc_json_field_t* field, const char* field_name); +int rc_json_get_float(float* out, const rc_json_field_t* field, const char* field_name); int rc_json_get_bool(int* out, const rc_json_field_t* field, const char* field_name); int rc_json_get_datetime(time_t* out, const rc_json_field_t* field, const char* field_name); void rc_json_get_optional_string(const char** out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name, const char* default_value); void rc_json_get_optional_num(int32_t* out, const rc_json_field_t* field, const char* field_name, int default_value); void rc_json_get_optional_unum(uint32_t* out, const rc_json_field_t* field, const char* field_name, uint32_t default_value); +void rc_json_get_optional_float(float* out, const rc_json_field_t* field, const char* field_name, float default_value); void rc_json_get_optional_bool(int* out, const rc_json_field_t* field, const char* field_name, int default_value); -int rc_json_get_optional_array(uint32_t* num_entries, rc_json_field_t* iterator, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name); +int rc_json_get_optional_array(uint32_t* num_entries, rc_json_field_t* iterator, const rc_json_field_t* field, const char* field_name); int rc_json_get_required_string(const char** out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name); int rc_json_get_required_num(int32_t* out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name); int rc_json_get_required_unum(uint32_t* out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name); +int rc_json_get_required_float(float* out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name); int rc_json_get_required_bool(int* out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name); int rc_json_get_required_datetime(time_t* out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name); int rc_json_get_required_object(rc_json_field_t* fields, size_t field_count, rc_api_response_t* response, rc_json_field_t* field, const char* field_name); @@ -74,8 +75,6 @@ void rc_url_builder_append_str_param(rc_api_url_builder_t* builder, const char* void rc_api_url_build_dorequest_url(rc_api_request_t* request); int rc_api_url_build_dorequest(rc_api_url_builder_t* builder, const char* api, const char* username, const char* api_token); -#ifdef __cplusplus -} -#endif +RC_END_C_DECLS #endif /* RC_API_COMMON_H */ diff --git a/deps/rcheevos/src/rapi/rc_api_runtime.c b/deps/rcheevos/src/rapi/rc_api_runtime.c index 27aeac13de..4f3bc5b57c 100644 --- a/deps/rcheevos/src/rapi/rc_api_runtime.c +++ b/deps/rcheevos/src/rapi/rc_api_runtime.c @@ -92,6 +92,20 @@ int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* r return rc_api_process_fetch_game_data_server_response(response, &response_obj); } +static int rc_parse_achievement_type(const char* type) +{ + if (strcmp(type, "missable") == 0) + return RC_ACHIEVEMENT_TYPE_MISSABLE; + + if (strcmp(type, "win_condition") == 0) + return RC_ACHIEVEMENT_TYPE_WIN; + + if (strcmp(type, "progression") == 0) + return RC_ACHIEVEMENT_TYPE_PROGRESSION; + + return RC_ACHIEVEMENT_TYPE_STANDARD; +} + int rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_response_t* response, const rc_api_server_response_t* server_response) { rc_api_achievement_definition_t* achievement; rc_api_leaderboard_definition_t* leaderboard; @@ -120,10 +134,6 @@ int rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_respon RC_JSON_NEW_FIELD("RichPresencePatch"), RC_JSON_NEW_FIELD("Achievements"), /* array */ RC_JSON_NEW_FIELD("Leaderboards") /* array */ - /* unused fields - RC_JSON_NEW_FIELD("ForumTopicID"), - RC_JSON_NEW_FIELD("Flags") - * unused fields */ }; rc_json_field_t achievement_fields[] = { @@ -136,7 +146,10 @@ int rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_respon RC_JSON_NEW_FIELD("Author"), RC_JSON_NEW_FIELD("BadgeName"), RC_JSON_NEW_FIELD("Created"), - RC_JSON_NEW_FIELD("Modified") + RC_JSON_NEW_FIELD("Modified"), + RC_JSON_NEW_FIELD("Type"), + RC_JSON_NEW_FIELD("Rarity"), + RC_JSON_NEW_FIELD("RarityHardcore") }; rc_json_field_t leaderboard_fields[] = { @@ -247,6 +260,35 @@ int rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_respon return RC_MISSING_VALUE; achievement->updated = (time_t)timet; + achievement->type = RC_ACHIEVEMENT_TYPE_STANDARD; + if (achievement_fields[10].value_end) { + len = achievement_fields[10].value_end - achievement_fields[10].value_start - 2; + if (len < sizeof(format) - 1) { + memcpy(format, achievement_fields[10].value_start + 1, len); + format[len] = '\0'; + achievement->type = rc_parse_achievement_type(format); + } + } + + /* legacy support : if title contains[m], change type to missable and remove[m] from title */ + if (memcmp(achievement->title, "[m]", 3) == 0) { + len = 3; + while (achievement->title[len] == ' ') + ++len; + achievement->title += len; + achievement->type = RC_ACHIEVEMENT_TYPE_MISSABLE; + } + else if (achievement_fields[1].value_end && memcmp(achievement_fields[1].value_end - 4, "[m]", 3) == 0) { + len = strlen(achievement->title) - 3; + while (achievement->title[len - 1] == ' ') + --len; + ((char*)achievement->title)[len] = '\0'; + achievement->type = RC_ACHIEVEMENT_TYPE_MISSABLE; + } + + rc_json_get_optional_float(&achievement->rarity, &achievement_fields[11], "Rarity", 100.0); + rc_json_get_optional_float(&achievement->rarity_hardcore, &achievement_fields[12], "RarityHardcore", 100.0); + ++achievement; } } @@ -318,6 +360,11 @@ int rc_api_init_ping_request(rc_api_request_t* request, const rc_api_ping_reques if (api_params->rich_presence && *api_params->rich_presence) rc_url_builder_append_str_param(&builder, "m", api_params->rich_presence); + if (api_params->game_hash && *api_params->game_hash) { + rc_url_builder_append_unum_param(&builder, "h", api_params->hardcore); + rc_url_builder_append_str_param(&builder, "x", api_params->game_hash); + } + request->post_data = rc_url_builder_finalize(&builder); request->content_type = RC_CONTENT_TYPE_URLENCODED; } diff --git a/deps/rcheevos/src/rapi/rc_api_user.c b/deps/rcheevos/src/rapi/rc_api_user.c index 0fa279c48e..0349f4a584 100644 --- a/deps/rcheevos/src/rapi/rc_api_user.c +++ b/deps/rcheevos/src/rapi/rc_api_user.c @@ -94,7 +94,14 @@ int rc_api_init_start_session_request(rc_api_request_t* request, const rc_api_st rc_url_builder_init(&builder, &request->buffer, 48); if (rc_api_url_build_dorequest(&builder, "startsession", api_params->username, api_params->api_token)) { rc_url_builder_append_unum_param(&builder, "g", api_params->game_id); + + if (api_params->game_hash && *api_params->game_hash) { + rc_url_builder_append_unum_param(&builder, "h", api_params->hardcore); + rc_url_builder_append_str_param(&builder, "m", api_params->game_hash); + } + rc_url_builder_append_str_param(&builder, "l", RCHEEVOS_VERSION_STRING); + request->post_data = rc_url_builder_finalize(&builder); request->content_type = RC_CONTENT_TYPE_URLENCODED; } @@ -139,7 +146,7 @@ int rc_api_process_start_session_server_response(rc_api_start_session_response_t if (result != RC_OK || !response->response.succeeded) return result; - if (rc_json_get_optional_array(&response->num_unlocks, &array_field, &response->response, &fields[2], "Unlocks") && response->num_unlocks) { + if (rc_json_get_optional_array(&response->num_unlocks, &array_field, &fields[2], "Unlocks") && response->num_unlocks) { response->unlocks = (rc_api_unlock_entry_t*)rc_buffer_alloc(&response->response.buffer, response->num_unlocks * sizeof(rc_api_unlock_entry_t)); if (!response->unlocks) return RC_OUT_OF_MEMORY; @@ -160,7 +167,7 @@ int rc_api_process_start_session_server_response(rc_api_start_session_response_t } } - if (rc_json_get_optional_array(&response->num_hardcore_unlocks, &array_field, &response->response, &fields[3], "HardcoreUnlocks") && response->num_hardcore_unlocks) { + if (rc_json_get_optional_array(&response->num_hardcore_unlocks, &array_field, &fields[3], "HardcoreUnlocks") && response->num_hardcore_unlocks) { response->hardcore_unlocks = (rc_api_unlock_entry_t*)rc_buffer_alloc(&response->response.buffer, response->num_hardcore_unlocks * sizeof(rc_api_unlock_entry_t)); if (!response->hardcore_unlocks) return RC_OUT_OF_MEMORY; diff --git a/deps/rcheevos/src/rc_client.c b/deps/rcheevos/src/rc_client.c index 98d8c1fe85..6790e1768d 100644 --- a/deps/rcheevos/src/rc_client.c +++ b/deps/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" @@ -23,9 +24,8 @@ #define RC_CLIENT_UNKNOWN_GAME_ID (uint32_t)-1 #define RC_CLIENT_RECENT_UNLOCK_DELAY_SECONDS (10 * 60) /* ten minutes */ -struct rc_client_async_handle_t { - uint8_t aborted; -}; +#define RC_MINIMUM_UNPAUSED_FRAMES 20 +#define RC_PAUSE_DECAY_MULTIPLIER 4 enum { RC_CLIENT_ASYNC_NOT_ABORTED = 0, @@ -80,12 +80,15 @@ static void rc_client_raise_leaderboard_events(rc_client_t* client, rc_client_su static void rc_client_raise_pending_events(rc_client_t* client, rc_client_game_info_t* game); static void rc_client_reschedule_callback(rc_client_t* client, rc_client_scheduled_callback_data_t* callback, rc_clock_t when); static void rc_client_award_achievement_retry(rc_client_scheduled_callback_data_t* callback_data, rc_client_t* client, rc_clock_t now); +static int rc_client_is_award_achievement_pending(const rc_client_t* client, uint32_t achievement_id); static void rc_client_submit_leaderboard_entry_retry(rc_client_scheduled_callback_data_t* callback_data, rc_client_t* client, rc_clock_t now); /* ===== Construction/Destruction ===== */ static void rc_client_dummy_event_handler(const rc_client_event_t* event, rc_client_t* client) { + (void)event; + (void)client; } rc_client_t* rc_client_create(rc_client_read_memory_func_t read_memory_function, rc_client_server_call_t server_call_function) @@ -95,6 +98,7 @@ rc_client_t* rc_client_create(rc_client_read_memory_func_t read_memory_function, return NULL; client->state.hardcore = 1; + client->state.required_unpaused_frames = RC_MINIMUM_UNPAUSED_FRAMES; client->callbacks.read_memory = read_memory_function; client->callbacks.server_call = server_call_function; @@ -131,6 +135,11 @@ void rc_client_destroy(rc_client_t* client) rc_client_unload_game(client); +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->destroy) + client->state.external_client->destroy(); +#endif + rc_buffer_destroy(&client->state.buffer); rc_mutex_destroy(&client->state.mutex); @@ -227,6 +236,11 @@ void rc_client_enable_logging(rc_client_t* client, int level, rc_client_message_ { client->callbacks.log_call = callback; client->state.log_level = callback ? level : RC_CLIENT_LOG_LEVEL_NONE; + +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->enable_logging) + client->state.external_client->enable_logging(client, level, callback); +#endif } /* ===== Common ===== */ @@ -235,6 +249,8 @@ static rc_clock_t rc_client_clock_get_now_millisecs(const rc_client_t* client) { #if defined(CLOCK_MONOTONIC) struct timespec now; + (void)client; + if (clock_gettime(CLOCK_MONOTONIC, &now) < 0) return 0; @@ -244,6 +260,8 @@ static rc_clock_t rc_client_clock_get_now_millisecs(const rc_client_t* client) static LARGE_INTEGER freq; LARGE_INTEGER ticks; + (void)client; + /* Frequency is the number of ticks per second and is guaranteed to not change. */ if (!freq.QuadPart) { if (!QueryPerformanceFrequency(&freq)) @@ -259,6 +277,9 @@ static rc_clock_t rc_client_clock_get_now_millisecs(const rc_client_t* client) return (rc_clock_t)(ticks.QuadPart / freq.QuadPart); #else const clock_t clock_now = clock(); + + (void)client; + if (sizeof(clock_t) == 4) { static uint32_t clock_wraps = 0; static clock_t last_clock = 0; @@ -291,6 +312,22 @@ static rc_clock_t rc_client_clock_get_now_millisecs(const rc_client_t* client) void rc_client_set_get_time_millisecs_function(rc_client_t* client, rc_get_time_millisecs_func_t handler) { client->callbacks.get_time_millisecs = handler ? handler : rc_client_clock_get_now_millisecs; + +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->set_get_time_millisecs) + client->state.external_client->set_get_time_millisecs(client, handler); +#endif +} + +int rc_client_async_handle_aborted(rc_client_t* client, rc_client_async_handle_t* async_handle) +{ + int aborted; + + rc_mutex_lock(&client->state.mutex); + aborted = async_handle->aborted; + rc_mutex_unlock(&client->state.mutex); + + return aborted; } static void rc_client_begin_async(rc_client_t* client, rc_client_async_handle_t* async_handle) @@ -333,12 +370,41 @@ static int rc_client_end_async(rc_client_t* client, rc_client_async_handle_t* as void rc_client_abort_async(rc_client_t* client, rc_client_async_handle_t* async_handle) { if (async_handle && client) { +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->abort_async) { + client->state.external_client->abort_async(async_handle); + return; + } +#endif + rc_mutex_lock(&client->state.mutex); async_handle->aborted = RC_CLIENT_ASYNC_ABORTED; rc_mutex_unlock(&client->state.mutex); } } +static int rc_client_async_handle_valid(rc_client_t* client, rc_client_async_handle_t* async_handle) +{ + int valid = 0; + size_t i; + + /* there is a small window of opportunity where the client could have been destroyed before calling + * this function, but this function assumes the possibility that the handle has been destroyed, so + * we can't check it for RC_CLIENT_ASYNC_DESTROYED before attempting to scan the client data */ + rc_mutex_lock(&client->state.mutex); + + for (i = 0; i < sizeof(client->state.async_handles) / sizeof(client->state.async_handles[0]); ++i) { + if (client->state.async_handles[i] == async_handle) { + valid = 1; + break; + } + } + + rc_mutex_unlock(&client->state.mutex); + + return valid; +} + static const char* rc_client_server_error_message(int* result, int http_status_code, const rc_api_response_t* response) { if (!response->succeeded) { @@ -352,6 +418,8 @@ static const char* rc_client_server_error_message(int* result, int http_status_c return response->error_message; } + (void)http_status_code; + if (*result != RC_OK) return rc_error_str(*result); @@ -461,6 +529,10 @@ static int rc_client_should_retry(const rc_api_server_response_t* server_respons /* connection to server from cloudfare was dropped before request was completed */ return 1; + case 525: /* 525 SSL Handshake Failed */ + /* web server worker connection pool is exhausted */ + return 1; + case RC_API_SERVER_RESPONSE_RETRYABLE_CLIENT_ERROR: /* client provided non-HTTP error (explicitly retryable) */ return 1; @@ -614,7 +686,13 @@ static rc_client_async_handle_t* rc_client_begin_login(rc_client_t* client, rc_api_destroy_request(&request); - return &callback_data->async_handle; + /* if the user state has changed, the async operation completed synchronously */ + rc_mutex_lock(&client->state.mutex); + if (client->state.user != RC_CLIENT_USER_STATE_LOGIN_REQUESTED) + callback_data = NULL; + rc_mutex_unlock(&client->state.mutex); + + return callback_data ? &callback_data->async_handle : NULL; } rc_client_async_handle_t* rc_client_begin_login_with_password(rc_client_t* client, @@ -637,6 +715,11 @@ rc_client_async_handle_t* rc_client_begin_login_with_password(rc_client_t* clien return NULL; } +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->begin_login_with_password) + return client->state.external_client->begin_login_with_password(client, username, password, callback, callback_userdata); +#endif + memset(&login_request, 0, sizeof(login_request)); login_request.username = username; login_request.password = password; @@ -665,6 +748,11 @@ rc_client_async_handle_t* rc_client_begin_login_with_token(rc_client_t* client, return NULL; } +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->begin_login_with_token) + return client->state.external_client->begin_login_with_token(client, username, token, callback, callback_userdata); +#endif + memset(&login_request, 0, sizeof(login_request)); login_request.username = username; login_request.api_token = token; @@ -680,6 +768,13 @@ void rc_client_logout(rc_client_t* client) if (!client) return; +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->logout) { + client->state.external_client->logout(); + return; + } +#endif + switch (client->state.user) { case RC_CLIENT_USER_STATE_LOGGED_IN: RC_CLIENT_LOG_INFO_FORMATTED(client, "Logging %s out", client->user.display_name); @@ -707,7 +802,15 @@ void rc_client_logout(rc_client_t* client) const rc_client_user_t* rc_client_get_user_info(const rc_client_t* client) { - return (client && client->state.user == RC_CLIENT_USER_STATE_LOGGED_IN) ? &client->user : NULL; + if (!client) + return NULL; + +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->get_user_info) + return client->state.external_client->get_user_info(); +#endif + + return (client->state.user == RC_CLIENT_USER_STATE_LOGGED_IN) ? &client->user : NULL; } int rc_client_user_get_image_url(const rc_client_user_t* user, char buffer[], size_t buffer_size) @@ -758,7 +861,17 @@ void rc_client_get_user_game_summary(const rc_client_t* client, rc_client_user_g return; memset(summary, 0, sizeof(*summary)); - if (!client || !client->game) + if (!client) + return; + +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->get_user_game_summary) { + client->state.external_client->get_user_game_summary(summary); + return; + } +#endif + + if (!client->game) return; rc_mutex_lock((rc_mutex_t*)&client->state.mutex); /* remove const cast for mutex access */ @@ -908,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; @@ -920,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); } @@ -1189,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; @@ -1205,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; @@ -1213,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; @@ -1246,7 +1362,7 @@ static void rc_client_deactivate_leaderboards(rc_client_game_info_t* game, rc_cl case RC_CLIENT_LEADERBOARD_STATE_TRACKING: rc_client_release_leaderboard_tracker(client->game, leaderboard); - /* fallthrough to default */ + /* fallthrough */ /* to default */ default: leaderboard->public_.state = RC_CLIENT_LEADERBOARD_STATE_INACTIVE; break; @@ -1437,6 +1553,8 @@ static void rc_client_begin_start_session(rc_client_load_state_t* load_state) start_session_params.username = client->user.username; start_session_params.api_token = client->user.token; start_session_params.game_id = load_state->hash->game_id; + start_session_params.game_hash = load_state->hash->hash; + start_session_params.hardcore = client->state.hardcore; result = rc_api_init_start_session_request(&start_session_request, &start_session_params); if (result != RC_OK) { @@ -1513,6 +1631,9 @@ static void rc_client_copy_achievements(rc_client_load_state_t* load_state, achievement->public_.points = read->points; achievement->public_.category = (read->category != RC_ACHIEVEMENT_CATEGORY_CORE) ? RC_CLIENT_ACHIEVEMENT_CATEGORY_UNOFFICIAL : RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE; + achievement->public_.rarity = read->rarity; + achievement->public_.rarity_hardcore = read->rarity_hardcore; + achievement->public_.type = read->type; /* assert: mapping is 1:1 */ memaddr = read->definition; rc_runtime_checksum(memaddr, achievement->md5); @@ -1584,6 +1705,13 @@ uint8_t rc_client_map_leaderboard_format(int format) case RC_FORMAT_FLOAT4: case RC_FORMAT_FLOAT5: case RC_FORMAT_FLOAT6: + case RC_FORMAT_FIXED1: + case RC_FORMAT_FIXED2: + case RC_FORMAT_FIXED3: + case RC_FORMAT_TENS: + case RC_FORMAT_HUNDREDS: + case RC_FORMAT_THOUSANDS: + case RC_FORMAT_UNSIGNED_VALUE: default: return RC_CLIENT_LEADERBOARD_FORMAT_VALUE; } @@ -2111,6 +2239,11 @@ rc_client_async_handle_t* rc_client_begin_load_game(rc_client_t* client, const c return NULL; } +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->begin_load_game) + return client->state.external_client->begin_load_game(client, hash, callback, callback_userdata); +#endif + load_state = (rc_client_load_state_t*)calloc(1, sizeof(*load_state)); if (!load_state) { callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), client, callback_userdata); @@ -2137,6 +2270,11 @@ rc_client_async_handle_t* rc_client_begin_identify_and_load_game(rc_client_t* cl return NULL; } +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->begin_identify_and_load_game) + return client->state.external_client->begin_identify_and_load_game(client, console_id, file_path, data, data_size, callback, callback_userdata); +#endif + if (data) { if (file_path) { RC_CLIENT_LOG_INFO_FORMATTED(client, "Identifying game: %zu bytes at %p (%s)", data_size, data, file_path); @@ -2241,6 +2379,13 @@ void rc_client_unload_game(rc_client_t* client) if (!client) return; +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->unload_game) { + client->state.external_client->unload_game(); + return; + } +#endif + rc_mutex_lock(&client->state.mutex); game = client->game; @@ -2365,6 +2510,11 @@ rc_client_async_handle_t* rc_client_begin_change_media(rc_client_t* client, cons return NULL; } +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->begin_change_media) + return client->state.external_client->begin_change_media(client, file_path, data, data_size, callback, callback_userdata); +#endif + rc_mutex_lock(&client->state.mutex); if (client->state.load) { game = client->state.load->game; @@ -2475,6 +2625,7 @@ rc_client_async_handle_t* rc_client_begin_change_media(rc_client_t* client, cons 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; @@ -2500,18 +2651,28 @@ rc_client_async_handle_t* rc_client_begin_change_media(rc_client_t* client, cons callback_data->hash = game_hash; callback_data->game = game; - rc_client_begin_async(client, &callback_data->async_handle); + 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); - return &callback_data->async_handle; + /* if handle is no longer valid, the async operation completed synchronously */ + return rc_client_async_handle_valid(client, async_handle) ? async_handle : NULL; } } const rc_client_game_t* rc_client_get_game_info(const rc_client_t* client) { - return (client && client->game) ? &client->game->public_ : NULL; + if (!client) + return NULL; + +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->get_game_info) + return client->state.external_client->get_game_info(); +#endif + + return client->game ? &client->game->public_ : NULL; } int rc_client_game_get_image_url(const rc_client_game_t* game, char buffer[], size_t buffer_size) @@ -2524,19 +2685,24 @@ int rc_client_game_get_image_url(const rc_client_game_t* game, char buffer[], si /* ===== Subsets ===== */ -void rc_client_begin_load_subset(rc_client_t* client, uint32_t subset_id, rc_client_callback_t callback, void* callback_userdata) +rc_client_async_handle_t* rc_client_begin_load_subset(rc_client_t* client, uint32_t subset_id, rc_client_callback_t callback, void* callback_userdata) { char buffer[32]; rc_client_load_state_t* load_state; if (!client) { callback(RC_INVALID_STATE, "client is required", client, callback_userdata); - return; + return NULL; } +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->begin_load_subset) + return client->state.external_client->begin_load_subset(client, subset_id, callback, callback_userdata); +#endif + if (!client->game) { callback(RC_NO_GAME_LOADED, rc_error_str(RC_NO_GAME_LOADED), client, callback_userdata); - return; + return NULL; } snprintf(buffer, sizeof(buffer), "[SUBSET%lu]", (unsigned long)subset_id); @@ -2544,7 +2710,7 @@ void rc_client_begin_load_subset(rc_client_t* client, uint32_t subset_id, rc_cli load_state = (rc_client_load_state_t*)calloc(1, sizeof(*load_state)); if (!load_state) { callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), client, callback_userdata); - return; + return NULL; } load_state->client = client; @@ -2556,13 +2722,23 @@ void rc_client_begin_load_subset(rc_client_t* client, uint32_t subset_id, rc_cli client->state.load = load_state; rc_client_begin_fetch_game_data(load_state); + + return (client->state.load == load_state) ? &load_state->async_handle : NULL; } const rc_client_subset_t* rc_client_get_subset_info(rc_client_t* client, uint32_t subset_id) { rc_client_subset_info_t* subset; - if (!client || !client->game) + if (!client) + return NULL; + +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->get_subset_info) + return client->state.external_client->get_subset_info(subset_id); +#endif + + if (!client->game) return NULL; for (subset = client->game->subsets; subset; subset = subset->next) { @@ -2587,7 +2763,14 @@ static void rc_client_update_achievement_display_information(rc_client_t* client if (achievement->public_.state == RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED) { /* achievement unlocked */ - new_bucket = RC_CLIENT_ACHIEVEMENT_BUCKET_UNLOCKED; + if (achievement->public_.unlock_time >= recent_unlock_time) { + new_bucket = RC_CLIENT_ACHIEVEMENT_BUCKET_RECENTLY_UNLOCKED; + } else { + new_bucket = RC_CLIENT_ACHIEVEMENT_BUCKET_UNLOCKED; + + if (client->state.disconnect && rc_client_is_award_achievement_pending(client, achievement->public_.id)) + new_bucket = RC_CLIENT_ACHIEVEMENT_BUCKET_UNSYNCED; + } } else { /* active achievement */ @@ -2628,9 +2811,6 @@ static void rc_client_update_achievement_display_information(rc_client_t* client } } - if (new_bucket == RC_CLIENT_ACHIEVEMENT_BUCKET_UNLOCKED && achievement->public_.unlock_time >= recent_unlock_time) - new_bucket = RC_CLIENT_ACHIEVEMENT_BUCKET_RECENTLY_UNLOCKED; - achievement->public_.bucket = new_bucket; } @@ -2644,6 +2824,7 @@ static const char* rc_client_get_achievement_bucket_label(uint8_t bucket_type) case RC_CLIENT_ACHIEVEMENT_BUCKET_RECENTLY_UNLOCKED: return "Recently Unlocked"; case RC_CLIENT_ACHIEVEMENT_BUCKET_ACTIVE_CHALLENGE: return "Active Challenges"; case RC_CLIENT_ACHIEVEMENT_BUCKET_ALMOST_THERE: return "Almost There"; + case RC_CLIENT_ACHIEVEMENT_BUCKET_UNSYNCED: return "Unlocks Not Synced to Server"; default: return "Unknown"; } } @@ -2701,6 +2882,7 @@ static uint8_t rc_client_map_bucket(uint8_t bucket, int grouping) if (grouping == RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_LOCK_STATE) { switch (bucket) { case RC_CLIENT_ACHIEVEMENT_BUCKET_RECENTLY_UNLOCKED: + case RC_CLIENT_ACHIEVEMENT_BUCKET_UNSYNCED: return RC_CLIENT_ACHIEVEMENT_BUCKET_UNLOCKED; case RC_CLIENT_ACHIEVEMENT_BUCKET_ACTIVE_CHALLENGE: @@ -2722,10 +2904,10 @@ rc_client_achievement_list_t* rc_client_create_achievement_list(rc_client_t* cli rc_client_achievement_t** bucket_achievements; rc_client_achievement_t** achievement_ptr; rc_client_achievement_bucket_t* bucket_ptr; - rc_client_achievement_list_t* list; + rc_client_achievement_list_info_t* list; rc_client_subset_info_t* subset; const uint32_t list_size = RC_ALIGN(sizeof(*list)); - uint32_t bucket_counts[16]; + uint32_t bucket_counts[NUM_RC_CLIENT_ACHIEVEMENT_BUCKETS]; uint32_t num_buckets; uint32_t num_achievements; size_t buckets_size; @@ -2735,7 +2917,8 @@ rc_client_achievement_list_t* rc_client_create_achievement_list(rc_client_t* cli const uint8_t shared_bucket_order[] = { RC_CLIENT_ACHIEVEMENT_BUCKET_ACTIVE_CHALLENGE, RC_CLIENT_ACHIEVEMENT_BUCKET_RECENTLY_UNLOCKED, - RC_CLIENT_ACHIEVEMENT_BUCKET_ALMOST_THERE + RC_CLIENT_ACHIEVEMENT_BUCKET_ALMOST_THERE, + RC_CLIENT_ACHIEVEMENT_BUCKET_UNSYNCED, }; const uint8_t subset_bucket_order[] = { RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED, @@ -2745,8 +2928,16 @@ rc_client_achievement_list_t* rc_client_create_achievement_list(rc_client_t* cli }; const time_t recent_unlock_time = time(NULL) - RC_CLIENT_RECENT_UNLOCK_DELAY_SECONDS; - if (!client || !client->game) - return calloc(1, sizeof(rc_client_achievement_list_t)); + if (!client) + return (rc_client_achievement_list_t*)calloc(1, sizeof(rc_client_achievement_list_info_t)); + +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->create_achievement_list) + return (rc_client_achievement_list_t*)client->state.external_client->create_achievement_list(category, grouping); +#endif + + if (!client->game) + return (rc_client_achievement_list_t*)calloc(1, sizeof(rc_client_achievement_list_info_t)); memset(&bucket_counts, 0, sizeof(bucket_counts)); @@ -2811,8 +3002,8 @@ rc_client_achievement_list_t* rc_client_create_achievement_list(rc_client_t* cli buckets_size = RC_ALIGN(num_buckets * sizeof(rc_client_achievement_bucket_t)); - list = (rc_client_achievement_list_t*)malloc(list_size + buckets_size + num_achievements * sizeof(rc_client_achievement_t*)); - bucket_ptr = list->buckets = (rc_client_achievement_bucket_t*)((uint8_t*)list + list_size); + list = (rc_client_achievement_list_info_t*)malloc(list_size + buckets_size + num_achievements * sizeof(rc_client_achievement_t*)); + bucket_ptr = list->public_.buckets = (rc_client_achievement_bucket_t*)((uint8_t*)list + list_size); achievement_ptr = (rc_client_achievement_t**)((uint8_t*)bucket_ptr + buckets_size); if (grouping == RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_PROGRESS) { @@ -2891,13 +3082,17 @@ rc_client_achievement_list_t* rc_client_create_achievement_list(rc_client_t* cli rc_mutex_unlock(&client->state.mutex); - list->num_buckets = (uint32_t)(bucket_ptr - list->buckets); - return list; + list->destroy_func = NULL; + list->public_.num_buckets = (uint32_t)(bucket_ptr - list->public_.buckets); + return &list->public_; } void rc_client_destroy_achievement_list(rc_client_achievement_list_t* list) { - if (list) + rc_client_achievement_list_info_t* info = (rc_client_achievement_list_info_t*)list; + if (info->destroy_func) + info->destroy_func(info); + else free(list); } @@ -2906,7 +3101,15 @@ int rc_client_has_achievements(rc_client_t* client) rc_client_subset_info_t* subset; int result; - if (!client || !client->game) + if (!client) + return 0; + +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->has_achievements) + return client->state.external_client->has_achievements(); +#endif + + if (!client->game) return 0; rc_mutex_lock(&client->state.mutex); @@ -2952,7 +3155,15 @@ const rc_client_achievement_t* rc_client_get_achievement_info(rc_client_t* clien { rc_client_subset_info_t* subset; - if (!client || !client->game) + if (!client) + return NULL; + +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->get_achievement_info) + return client->state.external_client->get_achievement_info(id); +#endif + + if (!client->game) return NULL; for (subset = client->game->subsets; subset; subset = subset->next) { @@ -2986,6 +3197,24 @@ typedef struct rc_client_award_achievement_callback_data_t rc_client_scheduled_callback_data_t* scheduled_callback_data; } rc_client_award_achievement_callback_data_t; +static int rc_client_is_award_achievement_pending(const rc_client_t* client, uint32_t achievement_id) +{ + /* assume lock already held */ + rc_client_scheduled_callback_data_t* scheduled_callback = client->state.scheduled_callbacks; + for (; scheduled_callback; scheduled_callback = scheduled_callback->next) + { + if (scheduled_callback->callback == rc_client_award_achievement_retry) + { + rc_client_award_achievement_callback_data_t* ach_data = + (rc_client_award_achievement_callback_data_t*)scheduled_callback->data; + if (ach_data->id == achievement_id) + return 1; + } + } + + return 0; +} + static void rc_client_award_achievement_server_call(rc_client_award_achievement_callback_data_t* ach_data); static void rc_client_award_achievement_retry(rc_client_scheduled_callback_data_t* callback_data, rc_client_t* client, rc_clock_t now) @@ -2993,6 +3222,9 @@ static void rc_client_award_achievement_retry(rc_client_scheduled_callback_data_ rc_client_award_achievement_callback_data_t* ach_data = (rc_client_award_achievement_callback_data_t*)callback_data->data; + (void)client; + (void)now; + rc_client_award_achievement_server_call(ach_data); } @@ -3229,7 +3461,15 @@ const rc_client_leaderboard_t* rc_client_get_leaderboard_info(const rc_client_t* { rc_client_subset_info_t* subset; - if (!client || !client->game) + if (!client) + return NULL; + +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->get_leaderboard_info) + return client->state.external_client->get_leaderboard_info(id); +#endif + + if (!client->game) return NULL; for (subset = client->game->subsets; subset; subset = subset->next) { @@ -3301,7 +3541,7 @@ rc_client_leaderboard_list_t* rc_client_create_leaderboard_list(rc_client_t* cli rc_client_leaderboard_t** bucket_leaderboards; rc_client_leaderboard_t** leaderboard_ptr; rc_client_leaderboard_bucket_t* bucket_ptr; - rc_client_leaderboard_list_t* list; + rc_client_leaderboard_list_info_t* list; rc_client_subset_info_t* subset; const uint32_t list_size = RC_ALIGN(sizeof(*list)); uint32_t bucket_counts[8]; @@ -3320,7 +3560,15 @@ rc_client_leaderboard_list_t* rc_client_create_leaderboard_list(rc_client_t* cli RC_CLIENT_LEADERBOARD_BUCKET_UNSUPPORTED }; - if (!client || !client->game) + if (!client) + return calloc(1, sizeof(rc_client_leaderboard_list_t)); + +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->create_leaderboard_list) + return (rc_client_leaderboard_list_t*)client->state.external_client->create_leaderboard_list(grouping); +#endif + + if (!client->game) return calloc(1, sizeof(rc_client_leaderboard_list_t)); memset(&bucket_counts, 0, sizeof(bucket_counts)); @@ -3385,8 +3633,8 @@ rc_client_leaderboard_list_t* rc_client_create_leaderboard_list(rc_client_t* cli buckets_size = RC_ALIGN(num_buckets * sizeof(rc_client_leaderboard_bucket_t)); - list = (rc_client_leaderboard_list_t*)malloc(list_size + buckets_size + num_leaderboards * sizeof(rc_client_leaderboard_t*)); - bucket_ptr = list->buckets = (rc_client_leaderboard_bucket_t*)((uint8_t*)list + list_size); + list = (rc_client_leaderboard_list_info_t*)malloc(list_size + buckets_size + num_leaderboards * sizeof(rc_client_leaderboard_t*)); + bucket_ptr = list->public_.buckets = (rc_client_leaderboard_bucket_t*)((uint8_t*)list + list_size); leaderboard_ptr = (rc_client_leaderboard_t**)((uint8_t*)bucket_ptr + buckets_size); if (grouping == RC_CLIENT_LEADERBOARD_LIST_GROUPING_TRACKING) { @@ -3455,13 +3703,17 @@ rc_client_leaderboard_list_t* rc_client_create_leaderboard_list(rc_client_t* cli rc_mutex_unlock(&client->state.mutex); - list->num_buckets = (uint32_t)(bucket_ptr - list->buckets); - return list; + list->destroy_func = NULL; + list->public_.num_buckets = (uint32_t)(bucket_ptr - list->public_.buckets); + return &list->public_; } void rc_client_destroy_leaderboard_list(rc_client_leaderboard_list_t* list) { - if (list) + rc_client_leaderboard_list_info_t* info = (rc_client_leaderboard_list_info_t*)list; + if (info->destroy_func) + info->destroy_func(info); + else free(list); } @@ -3470,7 +3722,15 @@ int rc_client_has_leaderboards(rc_client_t* client) rc_client_subset_info_t* subset; int result; - if (!client || !client->game) + if (!client) + return 0; + +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->has_leaderboards) + return client->state.external_client->has_leaderboards(); +#endif + + if (!client->game) return 0; rc_mutex_lock(&client->state.mutex); @@ -3593,6 +3853,9 @@ static void rc_client_submit_leaderboard_entry_retry(rc_client_scheduled_callbac rc_client_submit_leaderboard_entry_callback_data_t* lboard_data = (rc_client_submit_leaderboard_entry_callback_data_t*)callback_data->data; + (void)client; + (void)now; + rc_client_submit_leaderboard_entry_server_call(lboard_data); } @@ -3748,6 +4011,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); @@ -3795,7 +4063,7 @@ static void rc_client_subset_reset_leaderboards(rc_client_game_info_t* game, rc_ case RC_CLIENT_LEADERBOARD_STATE_TRACKING: rc_client_release_leaderboard_tracker(game, leaderboard); - /* fallthrough to default */ + /* fallthrough */ /* to default */ default: leaderboard->public_.state = RC_CLIENT_LEADERBOARD_STATE_ACTIVE; rc_reset_lboard(lboard); @@ -3843,24 +4111,26 @@ static void rc_client_fetch_leaderboard_entries_callback(const rc_api_server_res lbinfo_callback_data->callback(result, error_message, NULL, client, lbinfo_callback_data->callback_userdata); } else { - rc_client_leaderboard_entry_list_t* list; - const size_t list_size = sizeof(*list) + sizeof(rc_client_leaderboard_entry_t) * lbinfo_response.num_entries; + rc_client_leaderboard_entry_list_info_t* info; + const size_t list_size = sizeof(*info) + sizeof(rc_client_leaderboard_entry_t) * lbinfo_response.num_entries; size_t needed_size = list_size; uint32_t i; for (i = 0; i < lbinfo_response.num_entries; i++) needed_size += strlen(lbinfo_response.entries[i].username) + 1; - list = (rc_client_leaderboard_entry_list_t*)malloc(needed_size); - if (!list) { + info = (rc_client_leaderboard_entry_list_info_t*)malloc(needed_size); + if (!info) { lbinfo_callback_data->callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), NULL, client, lbinfo_callback_data->callback_userdata); } else { - rc_client_leaderboard_entry_t* entry = list->entries = (rc_client_leaderboard_entry_t*)((uint8_t*)list + sizeof(*list)); + rc_client_leaderboard_entry_list_t* list = &info->public_; + rc_client_leaderboard_entry_t* entry = list->entries = (rc_client_leaderboard_entry_t*)((uint8_t*)info + sizeof(*info)); char* user = (char*)((uint8_t*)list + list_size); const rc_api_lboard_info_entry_t* lbentry = lbinfo_response.entries; const rc_api_lboard_info_entry_t* stop = lbentry + lbinfo_response.num_entries; const size_t logged_in_user_len = strlen(client->user.display_name) + 1; + info->destroy_func = NULL; list->user_index = -1; for (; lbentry < stop; ++lbentry, ++entry) { @@ -3894,6 +4164,7 @@ static rc_client_async_handle_t* rc_client_begin_fetch_leaderboard_info(rc_clien rc_client_fetch_leaderboard_entries_callback_t callback, void* callback_userdata) { rc_client_fetch_leaderboard_entries_callback_data_t* callback_data; + rc_client_async_handle_t* async_handle; rc_api_request_t request; int result; const char* error_message; @@ -3917,11 +4188,12 @@ static rc_client_async_handle_t* rc_client_begin_fetch_leaderboard_info(rc_clien callback_data->callback_userdata = callback_userdata; callback_data->leaderboard_id = lbinfo_request->leaderboard_id; - rc_client_begin_async(client, &callback_data->async_handle); + async_handle = &callback_data->async_handle; + rc_client_begin_async(client, async_handle); client->callbacks.server_call(&request, rc_client_fetch_leaderboard_entries_callback, callback_data, client); rc_api_destroy_request(&request); - return &callback_data->async_handle; + return rc_client_async_handle_valid(client, async_handle) ? async_handle : NULL; } rc_client_async_handle_t* rc_client_begin_fetch_leaderboard_entries(rc_client_t* client, uint32_t leaderboard_id, @@ -3929,6 +4201,11 @@ rc_client_async_handle_t* rc_client_begin_fetch_leaderboard_entries(rc_client_t* { rc_api_fetch_leaderboard_info_request_t lbinfo_request; +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->begin_fetch_leaderboard_entries) + return client->state.external_client->begin_fetch_leaderboard_entries(client, leaderboard_id, first_entry, count, callback, callback_userdata); +#endif + memset(&lbinfo_request, 0, sizeof(lbinfo_request)); lbinfo_request.leaderboard_id = leaderboard_id; lbinfo_request.first_entry = first_entry; @@ -3942,6 +4219,11 @@ rc_client_async_handle_t* rc_client_begin_fetch_leaderboard_entries_around_user( { rc_api_fetch_leaderboard_info_request_t lbinfo_request; +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->begin_fetch_leaderboard_entries_around_user) + return client->state.external_client->begin_fetch_leaderboard_entries_around_user(client, leaderboard_id, count, callback, callback_userdata); +#endif + memset(&lbinfo_request, 0, sizeof(lbinfo_request)); lbinfo_request.leaderboard_id = leaderboard_id; lbinfo_request.username = client->user.username; @@ -3957,7 +4239,10 @@ rc_client_async_handle_t* rc_client_begin_fetch_leaderboard_entries_around_user( void rc_client_destroy_leaderboard_entry_list(rc_client_leaderboard_entry_list_t* list) { - if (list) + rc_client_leaderboard_entry_list_info_t* info = (rc_client_leaderboard_entry_list_info_t*)list; + if (info->destroy_func) + info->destroy_func(info); + else free(list); } @@ -4007,6 +4292,8 @@ static void rc_client_ping(rc_client_scheduled_callback_data_t* callback_data, r api_params.api_token = client->user.token; api_params.game_id = client->game->public_.id; api_params.rich_presence = buffer; + api_params.game_hash = client->game->public_.hash; + api_params.hardcore = client->state.hardcore; result = rc_api_init_ping_request(&request, &api_params); if (result != RC_OK) { @@ -4022,10 +4309,15 @@ static void rc_client_ping(rc_client_scheduled_callback_data_t* callback_data, r int rc_client_has_rich_presence(rc_client_t* client) { - if (!client || !client->game) + if (!client) return 0; - if (!client->game->runtime.richpresence || !client->game->runtime.richpresence->richpresence) +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->has_rich_presence) + return client->state.external_client->has_rich_presence(); +#endif + + if (!client->game || !client->game->runtime.richpresence || !client->game->runtime.richpresence->richpresence) return 0; return 1; @@ -4035,7 +4327,15 @@ size_t rc_client_get_rich_presence_message(rc_client_t* client, char buffer[], s { int result; - if (!client || !client->game || !buffer) + if (!client || !buffer) + return 0; + +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->get_rich_presence_message) + return client->state.external_client->get_rich_presence_message(buffer, buffer_size); +#endif + + if (!client->game) return 0; rc_mutex_lock(&client->state.mutex); @@ -4045,8 +4345,12 @@ size_t rc_client_get_rich_presence_message(rc_client_t* client, char buffer[], s rc_mutex_unlock(&client->state.mutex); - if (result == 0) + if (result == 0) { result = snprintf(buffer, buffer_size, "Playing %s", client->game->public_.title); + /* snprintf will return the amount of space needed, we want to return the number of chars written */ + if ((size_t)result >= buffer_size) + return (buffer_size - 1); + } return result; } @@ -4055,14 +4359,28 @@ size_t rc_client_get_rich_presence_message(rc_client_t* client, char buffer[], s void rc_client_set_event_handler(rc_client_t* client, rc_client_event_handler_t handler) { - if (client) - client->callbacks.event_handler = handler; + if (!client) + return; + +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->set_event_handler) + client->state.external_client->set_event_handler(client, handler); +#endif + + client->callbacks.event_handler = handler; } void rc_client_set_read_memory_function(rc_client_t* client, rc_client_read_memory_func_t handler) { - if (client) - client->callbacks.read_memory = handler; + if (!client) + return; + +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->set_read_memory) + client->state.external_client->set_read_memory(client, handler); +#endif + + client->callbacks.read_memory = handler; } static void rc_client_invalidate_processing_memref(rc_client_t* client) @@ -4173,7 +4491,15 @@ void rc_client_set_legacy_peek(rc_client_t* client, int method) int rc_client_is_processing_required(rc_client_t* client) { - if (!client || !client->game) + if (!client) + return 0; + +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->is_processing_required) + return client->state.external_client->is_processing_required(); +#endif + + if (!client->game) return 0; if (client->game->runtime.trigger_count || client->game->runtime.lboard_count) @@ -4228,6 +4554,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 && @@ -4289,6 +4620,9 @@ static void rc_client_progress_tracker_timer_elapsed(rc_client_scheduled_callbac rc_client_event_t client_event; memset(&client_event, 0, sizeof(client_event)); + (void)callback_data; + (void)now; + rc_mutex_lock(&client->state.mutex); if (client->game->progress_tracker.action == RC_CLIENT_PROGRESS_TRACKER_ACTION_NONE) { client->game->progress_tracker.hide_callback->when = 0; @@ -4594,6 +4928,13 @@ void rc_client_do_frame(rc_client_t* client) if (!client) return; +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->do_frame) { + client->state.external_client->do_frame(); + return; + } +#endif + if (client->game && !client->game->waiting_for_reset) { rc_runtime_richpresence_t* richpresence; rc_client_subset_info_t* subset; @@ -4613,7 +4954,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); @@ -4629,6 +4970,24 @@ void rc_client_do_frame(rc_client_t* client) rc_client_raise_pending_events(client, client->game); } + /* we've processed a frame. if there's a pause delay in effect, process it */ + if (client->state.unpaused_frame_decay > 0) { + client->state.unpaused_frame_decay--; + + if (client->state.unpaused_frame_decay == 0 && + client->state.required_unpaused_frames > RC_MINIMUM_UNPAUSED_FRAMES) { + /* the full decay has elapsed and a penalty still exists. + * lower the penalty and reset the decay counter */ + client->state.required_unpaused_frames >>= 1; + + if (client->state.required_unpaused_frames <= RC_MINIMUM_UNPAUSED_FRAMES) + client->state.required_unpaused_frames = RC_MINIMUM_UNPAUSED_FRAMES; + + client->state.unpaused_frame_decay = + client->state.required_unpaused_frames * (RC_PAUSE_DECAY_MULTIPLIER - 1) - 1; + } + } + rc_client_idle(client); } @@ -4639,6 +4998,13 @@ void rc_client_idle(rc_client_t* client) if (!client) return; +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->idle) { + client->state.external_client->idle(); + return; + } +#endif + scheduled_callback = client->state.scheduled_callbacks; if (scheduled_callback) { const rc_clock_t now = client->callbacks.get_time_millisecs(client); @@ -4765,7 +5131,17 @@ static void rc_client_reset_all(rc_client_t* client) void rc_client_reset(rc_client_t* client) { rc_client_game_hash_t* game_hash; - if (!client || !client->game) + if (!client) + return; + +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->reset) { + client->state.external_client->reset(); + return; + } +#endif + + if (!client->game) return; game_hash = rc_client_find_game_hash(client, client->game->public_.hash); @@ -4792,11 +5168,62 @@ void rc_client_reset(rc_client_t* client) rc_client_raise_pending_events(client, client->game); } +int rc_client_can_pause(rc_client_t* client, uint32_t* frames_remaining) +{ +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->can_pause) + return client->state.external_client->can_pause(frames_remaining); +#endif + + if (frames_remaining) + *frames_remaining = 0; + + /* pause is always allowed in softcore */ + if (!rc_client_get_hardcore_enabled(client)) + return 1; + + /* a full decay means we haven't processed any frames since the last time this was called. */ + if (client->state.unpaused_frame_decay == client->state.required_unpaused_frames * RC_PAUSE_DECAY_MULTIPLIER) + return 1; + + /* if less than RC_MINIMUM_UNPAUSED_FRAMES have been processed, don't allow the pause */ + if (client->state.unpaused_frame_decay > client->state.required_unpaused_frames * (RC_PAUSE_DECAY_MULTIPLIER - 1)) { + if (frames_remaining) { + *frames_remaining = client->state.unpaused_frame_decay - + client->state.required_unpaused_frames * (RC_PAUSE_DECAY_MULTIPLIER - 1); + } + return 0; + } + + /* we're going to allow the emulator to pause. calculate how many frames are needed before the next + * pause will be allowed. */ + + if (client->state.unpaused_frame_decay > 0) { + /* The user has paused within the decay window. Require a longer + * run of unpaused frames before allowing the next pause */ + if (client->state.required_unpaused_frames < 5 * 60) /* don't make delay longer then 5 seconds */ + client->state.required_unpaused_frames += RC_MINIMUM_UNPAUSED_FRAMES; + } + + /* require multiple unpaused_frames windows to decay the penalty */ + client->state.unpaused_frame_decay = client->state.required_unpaused_frames * RC_PAUSE_DECAY_MULTIPLIER; + + return 1; +} + size_t rc_client_progress_size(rc_client_t* client) { size_t result; - if (!client || !client->game) + if (!client) + return 0; + +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->progress_size) + return client->state.external_client->progress_size(); +#endif + + if (!client->game) return 0; rc_mutex_lock(&client->state.mutex); @@ -4810,7 +5237,15 @@ int rc_client_serialize_progress(rc_client_t* client, uint8_t* buffer) { int result; - if (!client || !client->game) + if (!client) + return RC_NO_GAME_LOADED; + +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->serialize_progress) + return client->state.external_client->serialize_progress(buffer); +#endif + + if (!client->game) return RC_NO_GAME_LOADED; if (!buffer) @@ -4921,7 +5356,15 @@ int rc_client_deserialize_progress(rc_client_t* client, const uint8_t* serialize rc_client_subset_info_t* subset; int result; - if (!client || !client->game) + if (!client) + return RC_NO_GAME_LOADED; + +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->deserialize_progress) + return client->state.external_client->deserialize_progress(serialized); +#endif + + if (!client->game) return RC_NO_GAME_LOADED; rc_mutex_lock(&client->state.mutex); @@ -4977,7 +5420,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); } } @@ -4988,6 +5433,13 @@ void rc_client_set_hardcore_enabled(rc_client_t* client, int enabled) if (!client) return; +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->get_hardcore_enabled) { + client->state.external_client->set_hardcore_enabled(enabled); + return; + } +#endif + rc_mutex_lock(&client->state.mutex); enabled = enabled ? 1 : 0; @@ -5022,51 +5474,107 @@ void rc_client_set_hardcore_enabled(rc_client_t* client, int enabled) int rc_client_get_hardcore_enabled(const rc_client_t* client) { - return client && client->state.hardcore; + if (!client) + return 0; + +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->get_hardcore_enabled) + return client->state.external_client->get_hardcore_enabled(); +#endif + + return client->state.hardcore; } void rc_client_set_unofficial_enabled(rc_client_t* client, int enabled) { - if (client) { - RC_CLIENT_LOG_INFO_FORMATTED(client, "Unofficial %s", enabled ? "enabled" : "disabled"); - client->state.unofficial_enabled = enabled ? 1 : 0; + if (!client) + return; + +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->set_unofficial_enabled) { + client->state.external_client->set_unofficial_enabled(enabled); + return; } +#endif + + RC_CLIENT_LOG_INFO_FORMATTED(client, "Unofficial %s", enabled ? "enabled" : "disabled"); + client->state.unofficial_enabled = enabled ? 1 : 0; } int rc_client_get_unofficial_enabled(const rc_client_t* client) { - return client && client->state.unofficial_enabled; + if (!client) + return 0; + +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->get_unofficial_enabled) + return client->state.external_client->get_unofficial_enabled(); +#endif + + return client->state.unofficial_enabled; } void rc_client_set_encore_mode_enabled(rc_client_t* client, int enabled) { - if (client) { - RC_CLIENT_LOG_INFO_FORMATTED(client, "Encore mode %s", enabled ? "enabled" : "disabled"); - client->state.encore_mode = enabled ? 1 : 0; + if (!client) + return; + +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->set_encore_mode_enabled) { + client->state.external_client->set_encore_mode_enabled(enabled); + return; } +#endif + + RC_CLIENT_LOG_INFO_FORMATTED(client, "Encore mode %s", enabled ? "enabled" : "disabled"); + client->state.encore_mode = enabled ? 1 : 0; } int rc_client_get_encore_mode_enabled(const rc_client_t* client) { - return client && client->state.encore_mode; + if (!client) + return 0; + +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->get_encore_mode_enabled) + return client->state.external_client->get_encore_mode_enabled(); +#endif + + return client->state.encore_mode; } void rc_client_set_spectator_mode_enabled(rc_client_t* client, int enabled) { - if (client) { - if (!enabled && client->state.spectator_mode == RC_CLIENT_SPECTATOR_MODE_LOCKED) { - RC_CLIENT_LOG_WARN(client, "Spectator mode cannot be disabled if it was enabled prior to loading game."); - return; - } + if (!client) + return; - RC_CLIENT_LOG_INFO_FORMATTED(client, "Spectator mode %s", enabled ? "enabled" : "disabled"); - client->state.spectator_mode = enabled ? RC_CLIENT_SPECTATOR_MODE_ON : RC_CLIENT_SPECTATOR_MODE_OFF; +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->set_spectator_mode_enabled) { + client->state.external_client->set_spectator_mode_enabled(enabled); + return; } +#endif + + if (!enabled && client->state.spectator_mode == RC_CLIENT_SPECTATOR_MODE_LOCKED) { + RC_CLIENT_LOG_WARN(client, "Spectator mode cannot be disabled if it was enabled prior to loading game."); + return; + } + + RC_CLIENT_LOG_INFO_FORMATTED(client, "Spectator mode %s", enabled ? "enabled" : "disabled"); + client->state.spectator_mode = enabled ? RC_CLIENT_SPECTATOR_MODE_ON : RC_CLIENT_SPECTATOR_MODE_OFF; } int rc_client_get_spectator_mode_enabled(const rc_client_t* client) { - return client && (client->state.spectator_mode == RC_CLIENT_SPECTATOR_MODE_OFF) ? 0 : 1; + if (!client) + return 0; + +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client->state.external_client && client->state.external_client->get_spectator_mode_enabled) + return client->state.external_client->get_spectator_mode_enabled(); +#endif + + return (client->state.spectator_mode == RC_CLIENT_SPECTATOR_MODE_OFF) ? 0 : 1; } void rc_client_set_userdata(rc_client_t* client, void* userdata) @@ -5094,4 +5602,34 @@ void rc_client_set_host(const rc_client_t* client, const char* hostname) RC_CLIENT_LOG_VERBOSE_FORMATTED(client, "Using host: %s", hostname); } rc_api_set_host(hostname); + +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + if (client && client->state.external_client && client->state.external_client->set_host) + 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/deps/rcheevos/src/rc_client_external.h b/deps/rcheevos/src/rc_client_external.h new file mode 100644 index 0000000000..a519e428e4 --- /dev/null +++ b/deps/rcheevos/src/rc_client_external.h @@ -0,0 +1,132 @@ +#ifndef RC_CLIENT_EXTERNAL_H +#define RC_CLIENT_EXTERNAL_H + +#include "rc_client.h" + +RC_BEGIN_C_DECLS + +/* NOTE: any function that is passed a callback also needs to be passed a client instance to pass + * to the callback, and the external interface has to capture both. */ + +typedef void (RC_CCONV *rc_client_external_enable_logging_func_t)(rc_client_t* client, int level, rc_client_message_callback_t callback); +typedef void (RC_CCONV *rc_client_external_set_event_handler_func_t)(rc_client_t* client, rc_client_event_handler_t handler); +typedef void (RC_CCONV *rc_client_external_set_read_memory_func_t)(rc_client_t* client, rc_client_read_memory_func_t handler); +typedef void (RC_CCONV *rc_client_external_set_get_time_millisecs_func_t)(rc_client_t* client, rc_get_time_millisecs_func_t handler); +typedef int (RC_CCONV *rc_client_external_can_pause_func_t)(uint32_t* frames_remaining); + +typedef void (RC_CCONV *rc_client_external_set_int_func_t)(int value); +typedef int (RC_CCONV *rc_client_external_get_int_func_t)(void); +typedef void (RC_CCONV *rc_client_external_set_string_func_t)(const char* value); +typedef size_t (RC_CCONV *rc_client_external_copy_string_func_t)(char buffer[], size_t buffer_size); +typedef void (RC_CCONV *rc_client_external_action_func_t)(void); + +typedef void (RC_CCONV *rc_client_external_async_handle_func_t)(rc_client_async_handle_t* handle); + +typedef rc_client_async_handle_t* (RC_CCONV *rc_client_external_begin_login_func_t)(rc_client_t* client, + const char* username, const char* pass_token, rc_client_callback_t callback, void* callback_userdata); +typedef const rc_client_user_t* (RC_CCONV *rc_client_external_get_user_info_func_t)(void); + +typedef rc_client_async_handle_t* (RC_CCONV *rc_client_external_begin_identify_and_load_game_func_t)( + rc_client_t* client, uint32_t console_id, const char* file_path, + const uint8_t* data, size_t data_size, rc_client_callback_t callback, void* callback_userdata); +typedef rc_client_async_handle_t* (RC_CCONV *rc_client_external_begin_load_game_func_t)(rc_client_t* client, + const char* hash, rc_client_callback_t callback, void* callback_userdata); +typedef rc_client_async_handle_t* (RC_CCONV *rc_client_external_begin_load_subset_t)(rc_client_t* client, + uint32_t subset_id, rc_client_callback_t callback, void* callback_userdata); +typedef const rc_client_game_t* (RC_CCONV *rc_client_external_get_game_info_func_t)(void); +typedef const rc_client_subset_t* (RC_CCONV *rc_client_external_get_subset_info_func_t)(uint32_t subset_id); +typedef void (RC_CCONV *rc_client_external_get_user_game_summary_func_t)(rc_client_user_game_summary_t* summary); +typedef rc_client_async_handle_t* (RC_CCONV *rc_client_external_begin_change_media_func_t)(rc_client_t* client, const char* file_path, + const uint8_t* data, size_t data_size, rc_client_callback_t callback, void* callback_userdata); + +/* NOTE: rc_client_external_create_achievement_list_func_t returns an internal wrapper structure which contains the public list + * and a destructor function. */ +struct rc_client_achievement_list_info_t; +typedef struct rc_client_achievement_list_info_t* (RC_CCONV *rc_client_external_create_achievement_list_func_t)(int category, int grouping); +typedef const rc_client_achievement_t* (RC_CCONV *rc_client_external_get_achievement_info_func_t)(uint32_t id); + +/* NOTE: rc_client_external_create_leaderboard_list_func_t returns an internal wrapper structure which contains the public list + * and a destructor function. */ +struct rc_client_leaderboard_list_info_t; +typedef struct rc_client_leaderboard_list_info_t* (RC_CCONV *rc_client_external_create_leaderboard_list_func_t)(int grouping); +typedef const rc_client_leaderboard_t* (RC_CCONV *rc_client_external_get_leaderboard_info_func_t)(uint32_t id); + +/* NOTE: rc_client_external_begin_fetch_leaderboard_entries_func_t and rc_client_external_begin_fetch_leaderboard_entries_around_user_func_t + * pass an internal wrapper structure around the list, which contains the public list and a destructor function. */ +typedef rc_client_async_handle_t* (RC_CCONV *rc_client_external_begin_fetch_leaderboard_entries_func_t)(rc_client_t* client, + uint32_t leaderboard_id, uint32_t first_entry, uint32_t count, + rc_client_fetch_leaderboard_entries_callback_t callback, void* callback_userdata); +typedef rc_client_async_handle_t* (RC_CCONV *rc_client_external_begin_fetch_leaderboard_entries_around_user_func_t)(rc_client_t* client, + uint32_t leaderboard_id, uint32_t count, rc_client_fetch_leaderboard_entries_callback_t callback, void* callback_userdata); + + +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 struct rc_client_external_t +{ + rc_client_external_action_func_t destroy; + + rc_client_external_enable_logging_func_t enable_logging; + rc_client_external_set_event_handler_func_t set_event_handler; + 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; + rc_client_external_set_int_func_t set_unofficial_enabled; + rc_client_external_get_int_func_t get_unofficial_enabled; + rc_client_external_set_int_func_t set_encore_mode_enabled; + rc_client_external_get_int_func_t get_encore_mode_enabled; + rc_client_external_set_int_func_t set_spectator_mode_enabled; + rc_client_external_get_int_func_t get_spectator_mode_enabled; + + rc_client_external_async_handle_func_t abort_async; + + rc_client_external_begin_login_func_t begin_login_with_password; + rc_client_external_begin_login_func_t begin_login_with_token; + rc_client_external_action_func_t logout; + rc_client_external_get_user_info_func_t get_user_info; + + rc_client_external_begin_identify_and_load_game_func_t begin_identify_and_load_game; + rc_client_external_begin_load_game_func_t begin_load_game; + rc_client_external_get_game_info_func_t get_game_info; + rc_client_external_begin_load_subset_t begin_load_subset; + rc_client_external_get_subset_info_func_t get_subset_info; + 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_create_achievement_list_func_t create_achievement_list; + rc_client_external_get_int_func_t has_achievements; + rc_client_external_get_achievement_info_func_t get_achievement_info; + + rc_client_external_create_leaderboard_list_func_t create_leaderboard_list; + rc_client_external_get_int_func_t has_leaderboards; + rc_client_external_get_leaderboard_info_func_t get_leaderboard_info; + rc_client_external_begin_fetch_leaderboard_entries_func_t begin_fetch_leaderboard_entries; + rc_client_external_begin_fetch_leaderboard_entries_around_user_func_t begin_fetch_leaderboard_entries_around_user; + + rc_client_external_copy_string_func_t get_rich_presence_message; + rc_client_external_get_int_func_t has_rich_presence; + + rc_client_external_action_func_t do_frame; + rc_client_external_action_func_t idle; + rc_client_external_get_int_func_t is_processing_required; + rc_client_external_can_pause_func_t can_pause; + rc_client_external_action_func_t reset; + + rc_client_external_progress_size_func_t progress_size; + rc_client_external_serialize_progress_func_t serialize_progress; + rc_client_external_deserialize_progress_func_t deserialize_progress; + +} rc_client_external_t; + +#define RC_CLIENT_EXTERNAL_VERSION 1 + +RC_END_C_DECLS + +#endif /* RC_CLIENT_EXTERNAL_H */ diff --git a/deps/rcheevos/src/rc_client_internal.h b/deps/rcheevos/src/rc_client_internal.h index c3ff588215..5e57091516 100644 --- a/deps/rcheevos/src/rc_client_internal.h +++ b/deps/rcheevos/src/rc_client_internal.h @@ -1,26 +1,31 @@ #ifndef RC_CLIENT_INTERNAL_H #define RC_CLIENT_INTERNAL_H -#ifdef __cplusplus -extern "C" { -#endif - #include "rc_client.h" +#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION + #include "rc_client_raintegration_internal.h" +#endif +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + #include "rc_client_external.h" +#endif + #include "rc_compat.h" #include "rc_runtime.h" #include "rc_runtime_types.h" +RC_BEGIN_C_DECLS + /*****************************************************************************\ | Callbacks | \*****************************************************************************/ struct rc_api_fetch_game_data_response_t; -typedef void (*rc_client_post_process_game_data_response_t)(const rc_api_server_response_t* server_response, +typedef void (RC_CCONV *rc_client_post_process_game_data_response_t)(const rc_api_server_response_t* server_response, struct rc_api_fetch_game_data_response_t* game_data_response, rc_client_t* client, void* userdata); -typedef int (*rc_client_can_submit_achievement_unlock_t)(uint32_t achievement_id, rc_client_t* client); -typedef int (*rc_client_can_submit_leaderboard_entry_t)(uint32_t leaderboard_id, rc_client_t* client); -typedef int (*rc_client_rich_presence_override_t)(rc_client_t* client, char buffer[], size_t buffersize); +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 struct rc_client_callbacks_t { rc_client_read_memory_func_t read_memory; @@ -37,7 +42,7 @@ typedef struct rc_client_callbacks_t { } rc_client_callbacks_t; struct rc_client_scheduled_callback_data_t; -typedef void (*rc_client_scheduled_callback_t)(struct rc_client_scheduled_callback_data_t* callback_data, rc_client_t* client, rc_clock_t now); +typedef void (RC_CCONV *rc_client_scheduled_callback_t)(struct rc_client_scheduled_callback_data_t* callback_data, rc_client_t* client, rc_clock_t now); typedef struct rc_client_scheduled_callback_data_t { @@ -50,6 +55,12 @@ typedef struct rc_client_scheduled_callback_data_t void rc_client_schedule_callback(rc_client_t* client, rc_client_scheduled_callback_data_t* scheduled_callback); +struct rc_client_async_handle_t { + uint8_t aborted; +}; + +int rc_client_async_handle_aborted(rc_client_t* client, rc_client_async_handle_t* async_handle); + /*****************************************************************************\ | Achievements | \*****************************************************************************/ @@ -78,6 +89,14 @@ typedef struct rc_client_achievement_info_t { time_t updated_time; } rc_client_achievement_info_t; +struct rc_client_achievement_list_info_t; +typedef void (RC_CCONV *rc_client_destroy_achievement_list_func_t)(struct rc_client_achievement_list_info_t* list); + +typedef struct rc_client_achievement_list_info_t { + rc_client_achievement_list_t public_; + rc_client_destroy_achievement_list_func_t destroy_func; +} rc_client_achievement_list_info_t; + enum { RC_CLIENT_PROGRESS_TRACKER_ACTION_NONE, RC_CLIENT_PROGRESS_TRACKER_ACTION_SHOW, @@ -145,6 +164,22 @@ typedef struct rc_client_leaderboard_info_t { uint8_t hidden; } rc_client_leaderboard_info_t; +struct rc_client_leaderboard_list_info_t; +typedef void (RC_CCONV *rc_client_destroy_leaderboard_list_func_t)(struct rc_client_leaderboard_list_info_t* list); + +typedef struct rc_client_leaderboard_list_info_t { + rc_client_leaderboard_list_t public_; + rc_client_destroy_leaderboard_list_func_t destroy_func; +} rc_client_leaderboard_list_info_t; + +struct rc_client_leaderboard_entry_list_info_t; +typedef void (RC_CCONV *rc_client_destroy_leaderboard_entry_list_func_t)(struct rc_client_leaderboard_entry_list_info_t* list); + +typedef struct rc_client_leaderboard_entry_list_info_t { + rc_client_leaderboard_entry_list_t public_; + rc_client_destroy_leaderboard_entry_list_func_t destroy_func; +} rc_client_leaderboard_entry_list_info_t; + uint8_t rc_client_map_leaderboard_format(int format); /*****************************************************************************\ @@ -177,7 +212,7 @@ typedef struct rc_client_subset_info_t { uint8_t pending_events; } rc_client_subset_info_t; -void rc_client_begin_load_subset(rc_client_t* client, uint32_t subset_id, rc_client_callback_t callback, void* callback_userdata); +rc_client_async_handle_t* rc_client_begin_load_subset(rc_client_t* client, uint32_t subset_id, rc_client_callback_t callback, void* callback_userdata); /*****************************************************************************\ | Game | @@ -273,6 +308,16 @@ typedef struct rc_client_state_t { rc_client_scheduled_callback_data_t* scheduled_callbacks; +#ifdef RC_CLIENT_SUPPORTS_EXTERNAL + rc_client_external_t* external_client; +#endif +#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION + rc_client_raintegration_t* raintegration; +#endif + + uint16_t unpaused_frame_decay; + uint16_t required_unpaused_frames; + uint8_t hardcore; uint8_t encore_mode; uint8_t spectator_mode; @@ -280,6 +325,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]; @@ -343,8 +389,6 @@ void rc_client_set_legacy_peek(rc_client_t* client, int method); void rc_client_release_leaderboard_tracker(rc_client_game_info_t* game, rc_client_leaderboard_info_t* leaderboard); -#ifdef __cplusplus -} -#endif +RC_END_C_DECLS #endif /* RC_CLIENT_INTERNAL_H */ diff --git a/deps/rcheevos/src/rc_client_raintegration.c b/deps/rcheevos/src/rc_client_raintegration.c new file mode 100644 index 0000000000..efea7e449d --- /dev/null +++ b/deps/rcheevos/src/rc_client_raintegration.c @@ -0,0 +1,493 @@ +#include "rc_client_raintegration_internal.h" + +#include "rc_client_internal.h" + +#include "rapi/rc_api_common.h" + +#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION + +static void rc_client_raintegration_load_dll(rc_client_t* client, + const wchar_t* search_directory, rc_client_callback_t callback, void* callback_userdata) +{ + wchar_t sPath[_MAX_PATH]; + const int nPathSize = sizeof(sPath) / sizeof(sPath[0]); + rc_client_raintegration_t* raintegration; + int sPathIndex = 0; + DWORD dwAttrib; + HINSTANCE hDLL; + + if (search_directory) { + sPathIndex = swprintf_s(sPath, nPathSize, L"%s\\", search_directory); + if (sPathIndex > nPathSize - 22) { + callback(RC_INVALID_STATE, "search_directory too long", client, callback_userdata); + return; + } + } + +#if defined(_M_X64) || defined(__amd64__) + wcscpy_s(&sPath[sPathIndex], nPathSize - sPathIndex, L"RA_Integration-x64.dll"); + dwAttrib = GetFileAttributesW(sPath); + if (dwAttrib == INVALID_FILE_ATTRIBUTES) { + wcscpy_s(&sPath[sPathIndex], nPathSize - sPathIndex, L"RA_Integration.dll"); + dwAttrib = GetFileAttributesW(sPath); + } +#else + wcscpy_s(&sPath[sPathIndex], nPathSize - sPathIndex, L"RA_Integration.dll"); + dwAttrib = GetFileAttributesW(sPath); +#endif + + if (dwAttrib == INVALID_FILE_ATTRIBUTES) { + callback(RC_MISSING_VALUE, "RA_Integration.dll not found in search directory", client, callback_userdata); + return; + } + + hDLL = LoadLibraryW(sPath); + if (hDLL == NULL) { + char error_message[512]; + const DWORD last_error = GetLastError(); + int offset = snprintf(error_message, sizeof(error_message), "Failed to load RA_Integration.dll (%u)", last_error); + + if (last_error != 0) { + LPSTR messageBuffer = NULL; + const DWORD size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, last_error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL); + + snprintf(&error_message[offset], sizeof(error_message) - offset, ": %.*s", size, messageBuffer); + + LocalFree(messageBuffer); + } + + callback(RC_ABORTED, error_message, client, callback_userdata); + return; + } + + raintegration = (rc_client_raintegration_t*)rc_buffer_alloc(&client->state.buffer, sizeof(rc_client_raintegration_t)); + memset(raintegration, 0, sizeof(*raintegration)); + raintegration->hDLL = hDLL; + + raintegration->get_version = (rc_client_raintegration_get_string_func_t)GetProcAddress(hDLL, "_RA_IntegrationVersion"); + 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->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"); + + raintegration->get_external_client = (rc_client_raintegration_get_external_client_func_t)GetProcAddress(hDLL, "_Rcheevos_GetExternalClient"); + 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_event_handler = (rc_client_raintegration_set_event_handler_func_t)GetProcAddress(hDLL, "_Rcheevos_SetRAIntegrationEventHandler"); + + if (!raintegration->get_version || + !raintegration->init_client || + !raintegration->get_external_client) { + FreeLibrary(hDLL); + + callback(RC_ABORTED, "One or more required exports was not found in RA_Integration.dll", client, callback_userdata); + } + else { + rc_mutex_lock(&client->state.mutex); + client->state.raintegration = raintegration; + rc_mutex_unlock(&client->state.mutex); + + RC_CLIENT_LOG_INFO_FORMATTED(client, "RA_Integration.dll %s loaded", client->state.raintegration->get_version()); + } +} + +typedef struct rc_client_version_validation_callback_data_t { + rc_client_t* client; + rc_client_callback_t callback; + void* callback_userdata; + HWND main_window_handle; + char* client_name; + char* client_version; + rc_client_async_handle_t async_handle; +} rc_client_version_validation_callback_data_t; + +int rc_client_version_less(const char* left, const char* right) +{ + do { + int left_len = 0; + int right_len = 0; + while (*left && *left == '0') + ++left; + while (left[left_len] && left[left_len] != '.') + ++left_len; + while (*right && *right == '0') + ++right; + while (right[right_len] && right[right_len] != '.') + ++right_len; + + if (left_len != right_len) + return (left_len < right_len); + + while (left_len--) { + if (*left != *right) + return (*left < *right); + ++left; + ++right; + } + + if (*left == '.') + ++left; + if (*right == '.') + ++right; + } while (*left || *right); + + return 0; +} + +static void rc_client_init_raintegration(rc_client_t* client, + rc_client_version_validation_callback_data_t* version_validation_callback_data) +{ + rc_client_raintegration_init_client_func_t init_func = client->state.raintegration->init_client; + + if (client->state.raintegration->get_host_url) { + 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); + } + else if (client->state.raintegration->init_client_offline) { + init_func = client->state.raintegration->init_client_offline; + RC_CLIENT_LOG_INFO(client, "Initializing in offline mode"); + } + } + } + + if (!init_func || !init_func(version_validation_callback_data->main_window_handle, + version_validation_callback_data->client_name, + version_validation_callback_data->client_version)) { + const char* error_message = "RA_Integration initialization failed"; + + rc_client_unload_raintegration(client); + + RC_CLIENT_LOG_ERR(client, error_message); + version_validation_callback_data->callback(RC_ABORTED, error_message, client, version_validation_callback_data->callback_userdata); + } + else { + rc_client_external_t* external_client = (rc_client_external_t*) + rc_buffer_alloc(&client->state.buffer, sizeof(*external_client)); + memset(external_client, 0, sizeof(*external_client)); + + if (!client->state.raintegration->get_external_client(external_client, RC_CLIENT_EXTERNAL_VERSION)) { + const char* error_message = "RA_Integration external client export failed"; + + rc_client_unload_raintegration(client); + + RC_CLIENT_LOG_ERR(client, error_message); + version_validation_callback_data->callback(RC_ABORTED, error_message, client, version_validation_callback_data->callback_userdata); + } + else { + /* copy state to the external client */ + if (external_client->enable_logging) + external_client->enable_logging(client, client->state.log_level, client->callbacks.log_call); + + if (external_client->set_event_handler) + external_client->set_event_handler(client, client->callbacks.event_handler); + if (external_client->set_read_memory) + external_client->set_read_memory(client, client->callbacks.read_memory); + + if (external_client->set_hardcore_enabled) + external_client->set_hardcore_enabled(rc_client_get_hardcore_enabled(client)); + if (external_client->set_unofficial_enabled) + external_client->set_unofficial_enabled(rc_client_get_unofficial_enabled(client)); + if (external_client->set_encore_mode_enabled) + external_client->set_encore_mode_enabled(rc_client_get_encore_mode_enabled(client)); + if (external_client->set_spectator_mode_enabled) + external_client->set_spectator_mode_enabled(rc_client_get_spectator_mode_enabled(client)); + + /* attach the external client and call the callback */ + client->state.external_client = external_client; + + client->state.raintegration->bIsInited = 1; + + version_validation_callback_data->callback(RC_OK, NULL, + client, version_validation_callback_data->callback_userdata); + } + } +} + +static void rc_client_version_validation_callback(const rc_api_server_response_t* server_response, void* callback_data) +{ + rc_client_version_validation_callback_data_t* version_validation_callback_data = + (rc_client_version_validation_callback_data_t*)callback_data; + rc_client_t* client = version_validation_callback_data->client; + + if (rc_client_async_handle_aborted(client, &version_validation_callback_data->async_handle)) { + RC_CLIENT_LOG_VERBOSE(client, "Version validation aborted"); + } + else { + rc_api_response_t response; + int result; + const char* current_version; + const char* minimum_version = ""; + + rc_json_field_t fields[] = { + RC_JSON_NEW_FIELD("Success"), + RC_JSON_NEW_FIELD("Error"), + RC_JSON_NEW_FIELD("MinimumVersion"), + }; + + memset(&response, 0, sizeof(response)); + rc_buffer_init(&response.buffer); + + result = rc_json_parse_server_response(&response, server_response, fields, sizeof(fields) / sizeof(fields[0])); + if (result == RC_OK) { + if (!rc_json_get_required_string(&minimum_version, &response, &fields[2], "MinimumVersion")) + result = RC_MISSING_VALUE; + } + + if (result != RC_OK) { + RC_CLIENT_LOG_ERR_FORMATTED(client, "Failed to fetch latest integration version: %.*s", server_response->body_length, server_response->body); + + rc_client_unload_raintegration(client); + + version_validation_callback_data->callback(result, rc_error_str(result), + client, version_validation_callback_data->callback_userdata); + } + else { + current_version = client->state.raintegration->get_version(); + + if (rc_client_version_less(current_version, minimum_version)) { + char error_message[256]; + + rc_client_unload_raintegration(client); + + snprintf(error_message, sizeof(error_message), + "RA_Integration version %s is lower than minimum version %s", current_version, minimum_version); + RC_CLIENT_LOG_WARN(client, error_message); + version_validation_callback_data->callback(RC_ABORTED, error_message, client, version_validation_callback_data->callback_userdata); + } + else { + RC_CLIENT_LOG_INFO_FORMATTED(client, "Validated RA_Integration version %s (minimum %s)", current_version, minimum_version); + + rc_client_init_raintegration(client, version_validation_callback_data); + } + } + + rc_buffer_destroy(&response.buffer); + } + + free(version_validation_callback_data->client_name); + free(version_validation_callback_data->client_version); + free(version_validation_callback_data); +} + +rc_client_async_handle_t* rc_client_begin_load_raintegration(rc_client_t* client, + const wchar_t* search_directory, HWND main_window_handle, + const char* client_name, const char* client_version, + rc_client_callback_t callback, void* callback_userdata) +{ + rc_client_version_validation_callback_data_t* callback_data; + rc_api_url_builder_t builder; + rc_api_request_t request; + + if (!client) { + callback(RC_INVALID_STATE, "client is required", client, callback_userdata); + return NULL; + } + + if (!client_name) { + callback(RC_INVALID_STATE, "client_name is required", client, callback_userdata); + return NULL; + } + + if (!client_version) { + callback(RC_INVALID_STATE, "client_version is required", client, callback_userdata); + return NULL; + } + + if (client->state.user != RC_CLIENT_USER_STATE_NONE) { + callback(RC_INVALID_STATE, "Cannot initialize RAIntegration after login", client, callback_userdata); + return NULL; + } + + if (!client->state.raintegration) { + if (!main_window_handle) { + callback(RC_INVALID_STATE, "main_window_handle is required", client, callback_userdata); + return NULL; + } + + rc_client_raintegration_load_dll(client, search_directory, callback, callback_userdata); + if (!client->state.raintegration) + return NULL; + } + + if (client->state.raintegration->get_host_url) { + const char* host_url = client->state.raintegration->get_host_url(); + if (host_url && strcmp(host_url, "https://retroachievements.org") != 0 && + strcmp(host_url, "OFFLINE") != 0) { + /* if the DLL specifies a custom host, use it */ + rc_client_set_host(client, host_url); + } + } + + memset(&request, 0, sizeof(request)); + rc_api_url_build_dorequest_url(&request); + rc_url_builder_init(&builder, &request.buffer, 48); + rc_url_builder_append_str_param(&builder, "r", "latestintegration"); + request.post_data = rc_url_builder_finalize(&builder); + + callback_data = calloc(1, sizeof(*callback_data)); + if (!callback_data) { + callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), client, callback_userdata); + return NULL; + } + + callback_data->client = client; + callback_data->callback = callback; + callback_data->callback_userdata = callback_userdata; + callback_data->client_name = strdup(client_name); + callback_data->client_version = strdup(client_version); + callback_data->main_window_handle = main_window_handle; + + client->callbacks.server_call(&request, rc_client_version_validation_callback, callback_data, client); + return &callback_data->async_handle; +} + +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) + { + 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) +{ + if (client && client->state.raintegration && client->state.raintegration->set_write_memory_function) + client->state.raintegration->set_write_memory_function(client, handler); +} + +void rc_client_raintegration_set_event_handler(rc_client_t* client, + rc_client_raintegration_event_handler_t handler) +{ + if (client && client->state.raintegration && client->state.raintegration->set_event_handler) + client->state.raintegration->set_event_handler(client, handler); +} + +const rc_client_raintegration_menu_t* rc_client_raintegration_get_menu(const rc_client_t* client) +{ + if (!client || !client->state.raintegration || + !client->state.raintegration->bIsInited || + !client->state.raintegration->get_menu) + { + return NULL; + } + + return client->state.raintegration->get_menu(); +} + +void rc_client_raintegration_rebuild_submenu(rc_client_t* client, HMENU hMenu) +{ + HMENU hPopupMenu = NULL; + const rc_client_raintegration_menu_t* menu; + + if (!client || !client->state.raintegration) + return; + + /* destroy the existing menu */ + if (client->state.raintegration->hPopupMenu) + DestroyMenu(client->state.raintegration->hPopupMenu); + + /* create the popup menu */ + hPopupMenu = CreatePopupMenu(); + + menu = rc_client_raintegration_get_menu(client); + if (menu && menu->num_items) + { + const rc_client_raintegration_menu_item_t* menuitem = menu->items; + const rc_client_raintegration_menu_item_t* stop = menu->items + menu->num_items; + + for (; menuitem < stop; ++menuitem) + { + if (menuitem->id == 0) + AppendMenuA(hPopupMenu, MF_SEPARATOR, 0U, NULL); + else + { + UINT flags = MF_STRING; + if (menuitem->checked) + flags |= MF_CHECKED; + if (!menuitem->enabled) + flags |= MF_DISABLED | MF_GRAYED; + + AppendMenuA(hPopupMenu, flags, menuitem->id, menuitem->label); + } + } + } + + /* add/update the item containing the popup menu */ + { + int nIndex = GetMenuItemCount(hMenu); + const char* menuText = "&RetroAchievements"; + char buffer[64]; + + UINT flags = MF_POPUP | MF_STRING; + if (!menu || !menu->num_items) + flags |= MF_DISABLED | MF_GRAYED; + + while (--nIndex >= 0) + { + if (GetMenuStringA(hMenu, nIndex, buffer, sizeof(buffer) - 1, MF_BYPOSITION)) + { + if (strcmp(buffer, menuText) == 0) + break; + } + } + + if (nIndex == -1) + AppendMenuA(hMenu, flags, (UINT_PTR)hPopupMenu, menuText); + else + ModifyMenuA(hMenu, nIndex, flags | MF_BYPOSITION, (UINT_PTR)hPopupMenu, menuText); + } + + client->state.raintegration->hPopupMenu = hPopupMenu; +} + +void rc_client_raintegration_update_menu_item(const rc_client_t* client, const rc_client_raintegration_menu_item_t* menuitem) +{ + if (client && client->state.raintegration && client->state.raintegration->hPopupMenu) + { + UINT flags = MF_STRING; + if (menuitem->checked) + flags |= MF_CHECKED; + + CheckMenuItem(client->state.raintegration->hPopupMenu, menuitem->id, flags | MF_BYCOMMAND); + } +} + +int rc_client_raintegration_activate_menu_item(const rc_client_t* client, uint32_t nMenuItemId) +{ + if (!client || !client->state.raintegration || !client->state.raintegration->activate_menu_item) + return 0; + + return client->state.raintegration->activate_menu_item(nMenuItemId); +} + +void rc_client_unload_raintegration(rc_client_t* client) +{ + HINSTANCE hDLL; + + if (!client || !client->state.raintegration) + return; + + RC_CLIENT_LOG_INFO(client, "Unloading RA_Integration") + + if (client->state.raintegration->shutdown) + client->state.raintegration->shutdown(); + + rc_mutex_lock(&client->state.mutex); + hDLL = client->state.raintegration->hDLL; + client->state.raintegration = NULL; + client->state.external_client = NULL; + rc_mutex_unlock(&client->state.mutex); + + if (hDLL) + FreeLibrary(hDLL); +} + +#endif /* RC_CLIENT_SUPPORTS_RAINTEGRATION */ diff --git a/deps/rcheevos/src/rc_client_raintegration_internal.h b/deps/rcheevos/src/rc_client_raintegration_internal.h new file mode 100644 index 0000000000..530d98e1a8 --- /dev/null +++ b/deps/rcheevos/src/rc_client_raintegration_internal.h @@ -0,0 +1,52 @@ +#ifndef RC_CLIENT_RAINTEGRATION_INTERNAL_H +#define RC_CLIENT_RAINTEGRATION_INTERNAL_H + +#include "rc_client_raintegration.h" + +#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION + +#include "rc_client_external.h" +#include "rc_compat.h" + +RC_BEGIN_C_DECLS + +/* RAIntegration follows the same calling convention as rcheevos */ + +typedef void (RC_CCONV* rc_client_raintegration_action_func_t)(void); +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 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_event_handler_func_t)(rc_client_t* pClient, rc_client_raintegration_event_handler_t handler); + +typedef struct rc_client_raintegration_t +{ + HINSTANCE hDLL; + HMENU hPopupMenu; + uint8_t bIsInited; + + rc_client_raintegration_get_string_func_t get_version; + 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_action_func_t shutdown; + + 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_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_external_client_func_t get_external_client; + +} rc_client_raintegration_t; + +RC_END_C_DECLS + +#endif /* RC_CLIENT_SUPPORTS_RAINTEGRATION */ + +#endif /* RC_CLIENT_RAINTEGRATION_INTERNAL_H */ diff --git a/deps/rcheevos/src/rc_compat.c b/deps/rcheevos/src/rc_compat.c index 72819cf214..6a8a5de57a 100644 --- a/deps/rcheevos/src/rc_compat.c +++ b/deps/rcheevos/src/rc_compat.c @@ -116,7 +116,9 @@ void rc_mutex_unlock(rc_mutex_t* mutex) #elif defined(GEKKO) -void rc_mutex_init(rc_mutex_t *mutex) +/* https://github.com/libretro/RetroArch/pull/16116 */ + +void rc_mutex_init(rc_mutex_t* mutex) { LWP_MutexInit(mutex, NULL); } diff --git a/deps/rcheevos/src/rc_compat.h b/deps/rcheevos/src/rc_compat.h index e22f9b84e5..2ef9ae0c5b 100644 --- a/deps/rcheevos/src/rc_compat.h +++ b/deps/rcheevos/src/rc_compat.h @@ -1,14 +1,14 @@ #ifndef RC_COMPAT_H #define RC_COMPAT_H -#ifdef __cplusplus -extern "C" { -#endif +#include "rc_export.h" #include #include #include +RC_BEGIN_C_DECLS + #if defined(MINGW) || defined(__MINGW32__) || defined(__MINGW64__) /* MinGW redefinitions */ @@ -92,8 +92,6 @@ extern "C" { void rc_mutex_unlock(rc_mutex_t* mutex); #endif -#ifdef __cplusplus -} -#endif +RC_END_C_DECLS #endif /* RC_COMPAT_H */ diff --git a/deps/rcheevos/src/rc_libretro.c b/deps/rcheevos/src/rc_libretro.c index 4facc14af4..d94d6d5b4c 100644 --- a/deps/rcheevos/src/rc_libretro.c +++ b/deps/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 } @@ -96,6 +101,11 @@ static const rc_disallowed_setting_t _rc_disallowed_mesen_s_settings[] = { { NULL, NULL } }; +static const rc_disallowed_setting_t _rc_disallowed_neocd_settings[] = { + { "neocd_bios", "uni-bios*" }, + { NULL, NULL } +}; + static const rc_disallowed_setting_t _rc_disallowed_pcsx_rearmed_settings[] = { { "pcsx_rearmed_region", "pal" }, { NULL, NULL } @@ -144,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 }, @@ -152,6 +163,7 @@ static const rc_disallowed_core_settings_t rc_disallowed_core_settings[] = { { "Genesis Plus GX Wide", _rc_disallowed_gpgx_wide_settings }, { "Mesen", _rc_disallowed_mesen_settings }, { "Mesen-S", _rc_disallowed_mesen_s_settings }, + { "NeoCD", _rc_disallowed_neocd_settings }, { "PPSSPP", _rc_disallowed_ppsspp_settings }, { "PCSX-ReARMed", _rc_disallowed_pcsx_rearmed_settings }, { "PicoDrive", _rc_disallowed_picodrive_settings }, @@ -318,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) { @@ -616,7 +639,7 @@ static void rc_libretro_memory_init_from_unmapped_memory(rc_libretro_memory_regi } int rc_libretro_memory_init(rc_libretro_memory_regions_t* regions, const struct retro_memory_map* mmap, - rc_libretro_get_core_memory_info_func get_core_memory_info, int console_id) { + rc_libretro_get_core_memory_info_func get_core_memory_info, uint32_t console_id) { const rc_memory_regions_t* console_regions = rc_console_memory_regions(console_id); rc_libretro_memory_regions_t new_regions; int has_valid_region = 0; @@ -665,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; } @@ -676,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'; @@ -690,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; } @@ -720,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))) @@ -753,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; } @@ -769,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++; } @@ -796,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; } @@ -809,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/deps/rcheevos/src/rc_libretro.h b/deps/rcheevos/src/rc_libretro.h index ceb92983f8..66ab9a3528 100644 --- a/deps/rcheevos/src/rc_libretro.h +++ b/deps/rcheevos/src/rc_libretro.h @@ -1,9 +1,7 @@ #ifndef RC_LIBRETRO_H #define RC_LIBRETRO_H -#ifdef __cplusplus -extern "C" { -#endif +#include "rc_export.h" /* this file comes from the libretro repository, which is not an explicit submodule. * the integration must set up paths appropriately to find it. */ @@ -12,6 +10,8 @@ extern "C" { #include #include +RC_BEGIN_C_DECLS + /*****************************************************************************\ | Disallowed Settings | \*****************************************************************************/ @@ -22,17 +22,17 @@ typedef struct rc_disallowed_setting_t const char* value; } rc_disallowed_setting_t; -const rc_disallowed_setting_t* rc_libretro_get_disallowed_settings(const char* library_name); -int rc_libretro_is_setting_allowed(const rc_disallowed_setting_t* disallowed_settings, const char* setting, const char* value); -int rc_libretro_is_system_allowed(const char* library_name, uint32_t console_id); +RC_EXPORT const rc_disallowed_setting_t* RC_CCONV rc_libretro_get_disallowed_settings(const char* library_name); +RC_EXPORT int RC_CCONV rc_libretro_is_setting_allowed(const rc_disallowed_setting_t* disallowed_settings, const char* setting, const char* value); +RC_EXPORT int RC_CCONV rc_libretro_is_system_allowed(const char* library_name, uint32_t console_id); /*****************************************************************************\ | Memory Mapping | \*****************************************************************************/ /* specifies a function to call for verbose logging */ -typedef void (*rc_libretro_message_callback)(const char*); -void rc_libretro_init_verbose_message_callback(rc_libretro_message_callback callback); +typedef void (RC_CCONV *rc_libretro_message_callback)(const char*); +RC_EXPORT void RC_CCONV rc_libretro_init_verbose_message_callback(rc_libretro_message_callback callback); #define RC_LIBRETRO_MAX_MEMORY_REGIONS 32 typedef struct rc_libretro_memory_regions_t @@ -49,15 +49,15 @@ typedef struct rc_libretro_core_memory_info_t size_t size; } rc_libretro_core_memory_info_t; -typedef void (*rc_libretro_get_core_memory_info_func)(uint32_t id, rc_libretro_core_memory_info_t* info); +typedef void (RC_CCONV *rc_libretro_get_core_memory_info_func)(uint32_t id, rc_libretro_core_memory_info_t* info); -int rc_libretro_memory_init(rc_libretro_memory_regions_t* regions, const struct retro_memory_map* mmap, - rc_libretro_get_core_memory_info_func get_core_memory_info, int console_id); -void rc_libretro_memory_destroy(rc_libretro_memory_regions_t* regions); +RC_EXPORT int RC_CCONV rc_libretro_memory_init(rc_libretro_memory_regions_t* regions, const struct retro_memory_map* mmap, + rc_libretro_get_core_memory_info_func get_core_memory_info, uint32_t console_id); +RC_EXPORT void RC_CCONV rc_libretro_memory_destroy(rc_libretro_memory_regions_t* regions); -uint8_t* rc_libretro_memory_find(const rc_libretro_memory_regions_t* regions, uint32_t address); -uint8_t* rc_libretro_memory_find_avail(const rc_libretro_memory_regions_t* regions, uint32_t address, uint32_t* avail); -uint32_t rc_libretro_memory_read(const rc_libretro_memory_regions_t* regions, uint32_t address, uint8_t* buffer, uint32_t num_bytes); +RC_EXPORT uint8_t* RC_CCONV rc_libretro_memory_find(const rc_libretro_memory_regions_t* regions, uint32_t address); +RC_EXPORT uint8_t* RC_CCONV rc_libretro_memory_find_avail(const rc_libretro_memory_regions_t* regions, uint32_t address, uint32_t* avail); +RC_EXPORT uint32_t RC_CCONV rc_libretro_memory_read(const rc_libretro_memory_regions_t* regions, uint32_t address, uint8_t* buffer, uint32_t num_bytes); /*****************************************************************************\ | Disk Identification | @@ -77,19 +77,17 @@ typedef struct rc_libretro_hash_set_t uint16_t entries_size; } rc_libretro_hash_set_t; -typedef int (*rc_libretro_get_image_path_func)(uint32_t index, char* buffer, size_t buffer_size); +typedef int (RC_CCONV *rc_libretro_get_image_path_func)(uint32_t index, char* buffer, size_t buffer_size); -void rc_libretro_hash_set_init(struct rc_libretro_hash_set_t* hash_set, +RC_EXPORT void RC_CCONV rc_libretro_hash_set_init(struct rc_libretro_hash_set_t* hash_set, const char* m3u_path, rc_libretro_get_image_path_func get_image_path); -void rc_libretro_hash_set_destroy(struct rc_libretro_hash_set_t* hash_set); +RC_EXPORT void RC_CCONV rc_libretro_hash_set_destroy(struct rc_libretro_hash_set_t* hash_set); -void rc_libretro_hash_set_add(struct rc_libretro_hash_set_t* hash_set, - const char* path, uint32_t game_id, const char hash[33]); -const char* rc_libretro_hash_set_get_hash(const struct rc_libretro_hash_set_t* hash_set, const char* path); -int rc_libretro_hash_set_get_game_id(const struct rc_libretro_hash_set_t* hash_set, const char* hash); +RC_EXPORT void RC_CCONV rc_libretro_hash_set_add(struct rc_libretro_hash_set_t* hash_set, + const char* path, uint32_t game_id, const char hash[33]); +RC_EXPORT const char* RC_CCONV rc_libretro_hash_set_get_hash(const struct rc_libretro_hash_set_t* hash_set, const char* path); +RC_EXPORT int RC_CCONV rc_libretro_hash_set_get_game_id(const struct rc_libretro_hash_set_t* hash_set, const char* hash); -#ifdef __cplusplus -} -#endif +RC_END_C_DECLS #endif /* RC_LIBRETRO_H */ diff --git a/deps/rcheevos/src/rc_util.c b/deps/rcheevos/src/rc_util.c index 9deee91b45..fa369a3b0d 100644 --- a/deps/rcheevos/src/rc_util.c +++ b/deps/rcheevos/src/rc_util.c @@ -35,7 +35,7 @@ void rc_buffer_destroy(rc_buffer_t* buffer) { rc_buffer_chunk_t* next = chunk->next; #ifdef DEBUG_BUFFERS - total += (int)(chunk->end - chunk->data); + total += (int)(chunk->end - chunk->start); wasted += (int)(chunk->end - chunk->write); ++count; #endif diff --git a/deps/rcheevos/src/rc_version.c b/deps/rcheevos/src/rc_version.c new file mode 100644 index 0000000000..a29d0a5f52 --- /dev/null +++ b/deps/rcheevos/src/rc_version.c @@ -0,0 +1,11 @@ +#include "rc_version.h" + +uint32_t rc_version(void) +{ + return RCHEEVOS_VERSION; +} + +const char* rc_version_string(void) +{ + return RCHEEVOS_VERSION_STRING; +} diff --git a/deps/rcheevos/src/rc_version.h b/deps/rcheevos/src/rc_version.h index 19be5671aa..daf57e1cb5 100644 --- a/deps/rcheevos/src/rc_version.h +++ b/deps/rcheevos/src/rc_version.h @@ -1,12 +1,14 @@ #ifndef RC_VERSION_H #define RC_VERSION_H -#ifdef __cplusplus -extern "C" { -#endif +#include "rc_export.h" + +#include + +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) @@ -22,8 +24,9 @@ extern "C" { #define RCHEEVOS_VERSION_STRING RCHEEVOS_MAKE_VERSION_STRING_SHORT(RCHEEVOS_VERSION_MAJOR, RCHEEVOS_VERSION_MINOR) #endif -#ifdef __cplusplus -} -#endif +RC_EXPORT uint32_t rc_version(void); +RC_EXPORT const char* rc_version_string(void); + +RC_END_C_DECLS #endif /* RC_VERSION_H */ diff --git a/deps/rcheevos/src/rcheevos/alloc.c b/deps/rcheevos/src/rcheevos/alloc.c index 7b43a2c759..0aa4e5cb5f 100644 --- a/deps/rcheevos/src/rcheevos/alloc.c +++ b/deps/rcheevos/src/rcheevos/alloc.c @@ -5,7 +5,7 @@ void* rc_alloc_scratch(void* pointer, int32_t* offset, uint32_t size, uint32_t alignment, rc_scratch_t* scratch, uint32_t scratch_object_pointer_offset) { - rc_scratch_buffer_t* buffer; + void* data; /* if we have a real buffer, then allocate the data there */ if (pointer) @@ -19,49 +19,13 @@ void* rc_alloc_scratch(void* pointer, int32_t* offset, uint32_t size, uint32_t a } /* find a scratch buffer to hold the temporary data */ - buffer = &scratch->buffer; - do { - const uint32_t aligned_buffer_offset = (buffer->offset + alignment - 1) & ~(alignment - 1); - if (aligned_buffer_offset < sizeof(buffer->buffer)) { - const uint32_t remaining = sizeof(buffer->buffer) - aligned_buffer_offset; - - if (remaining >= size) { - /* claim the required space from an existing buffer */ - return rc_alloc(buffer->buffer, &buffer->offset, size, alignment, NULL, -1); - } - } - - if (!buffer->next) - break; - - buffer = buffer->next; - } while (1); - - /* not enough space in any existing buffer, allocate more */ - if (size > (uint32_t)sizeof(buffer->buffer)) { - /* caller is asking for more than we can fit in a standard rc_scratch_buffer_t. - * leverage the fact that the buffer is the last field and extend its size. - * this chunk will be exactly large enough to hold the needed data, and since offset - * will exceed sizeof(buffer->buffer), it will never be eligible to hold anything else. - */ - const size_t needed = sizeof(rc_scratch_buffer_t) - sizeof(buffer->buffer) + size; - buffer->next = (rc_scratch_buffer_t*)malloc(needed); - } - else { - buffer->next = (rc_scratch_buffer_t*)malloc(sizeof(rc_scratch_buffer_t)); - } - - if (!buffer->next) { + data = rc_buffer_alloc(&scratch->buffer, size); + if (!data) { *offset = RC_OUT_OF_MEMORY; return NULL; } - buffer = buffer->next; - buffer->offset = 0; - buffer->next = NULL; - - /* claim the required space from the new buffer */ - return rc_alloc(buffer->buffer, &buffer->offset, size, alignment, NULL, -1); + return data; } void* rc_alloc(void* pointer, int32_t* offset, uint32_t size, uint32_t alignment, rc_scratch_t* scratch, uint32_t scratch_object_pointer_offset) { @@ -73,7 +37,7 @@ void* rc_alloc(void* pointer, int32_t* offset, uint32_t size, uint32_t alignment /* valid buffer, grab the next chunk */ ptr = (void*)((char*)pointer + *offset); } - else if (scratch != 0 && scratch_object_pointer_offset >= 0) { + else if (scratch != 0 && scratch_object_pointer_offset < sizeof(scratch->objs)) { /* only allocate one instance of each object type (indentified by scratch_object_pointer_offset) */ void** scratch_object_pointer = (void**)((char*)&scratch->objs + scratch_object_pointer_offset); ptr = *scratch_object_pointer; @@ -137,9 +101,8 @@ void rc_init_parse_state(rc_parse_state_t* parse, void* buffer, lua_State* L, in parse->L = L; parse->funcs_ndx = funcs_ndx; parse->buffer = buffer; - parse->scratch.buffer.offset = 0; - parse->scratch.buffer.next = NULL; parse->scratch.strings = NULL; + rc_buffer_init(&parse->scratch.buffer); memset(&parse->scratch.objs, 0, sizeof(parse->scratch.objs)); parse->first_memref = 0; parse->variables = 0; @@ -151,12 +114,5 @@ void rc_init_parse_state(rc_parse_state_t* parse, void* buffer, lua_State* L, in void rc_destroy_parse_state(rc_parse_state_t* parse) { - rc_scratch_buffer_t* buffer = parse->scratch.buffer.next; - rc_scratch_buffer_t* next; - - while (buffer) { - next = buffer->next; - free(buffer); - buffer = next; - } + rc_buffer_destroy(&parse->scratch.buffer); } diff --git a/deps/rcheevos/src/rcheevos/condset.c b/deps/rcheevos/src/rcheevos/condset.c index 4c2e7ea37e..23a0e30e1f 100644 --- a/deps/rcheevos/src/rcheevos/condset.c +++ b/deps/rcheevos/src/rcheevos/condset.c @@ -60,7 +60,7 @@ rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse, in /* right hand side is not required when Measured is used in a value */ if (is_value) break; - /* fallthrough to default */ + /* fallthrough */ /* to default */ default: parse->offset = RC_INVALID_OPERATOR; diff --git a/deps/rcheevos/src/rcheevos/consoleinfo.c b/deps/rcheevos/src/rcheevos/consoleinfo.c index 985c429220..427db73b22 100644 --- a/deps/rcheevos/src/rcheevos/consoleinfo.c +++ b/deps/rcheevos/src/rcheevos/consoleinfo.c @@ -2,7 +2,7 @@ #include -const char* rc_console_name(int console_id) +const char* rc_console_name(uint32_t console_id) { switch (console_id) { @@ -195,6 +195,9 @@ const char* rc_console_name(int console_id) case RC_CONSOLE_SHARPX1: return "Sharp X1"; + case RC_CONSOLE_STANDALONE: + return "Standalone"; + case RC_CONSOLE_SUPER_NINTENDO: return "Super Nintendo Entertainment System"; @@ -564,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[] = { @@ -969,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/deps/rcheevos/src/rcheevos/format.c b/deps/rcheevos/src/rcheevos/format.c index c797faeb42..0aa44ee6a1 100644 --- a/deps/rcheevos/src/rcheevos/format.c +++ b/deps/rcheevos/src/rcheevos/format.c @@ -14,6 +14,9 @@ int rc_parse_format(const char* format_str) { if (!strncmp(format_str, "LOAT", 4) && format_str[4] >= '1' && format_str[4] <= '6' && format_str[5] == '\0') { return RC_FORMAT_FLOAT1 + (format_str[4] - '1'); } + if (!strncmp(format_str, "IXED", 4) && format_str[4] >= '1' && format_str[4] <= '3' && format_str[5] == '\0') { + return RC_FORMAT_FIXED1 + (format_str[4] - '1'); + } break; @@ -24,6 +27,12 @@ int rc_parse_format(const char* format_str) { if (!strcmp(format_str, "IMESECS")) { return RC_FORMAT_SECONDS; } + if (!strcmp(format_str, "HOUSANDS")) { + return RC_FORMAT_THOUSANDS; + } + if (!strcmp(format_str, "ENS")) { + return RC_FORMAT_TENS; + } break; @@ -64,11 +73,25 @@ int rc_parse_format(const char* format_str) { break; + case 'U': + if (!strcmp(format_str, "NSIGNED")) { + return RC_FORMAT_UNSIGNED_VALUE; + } + + break; + case 'O': if (!strcmp(format_str, "THER")) { return RC_FORMAT_SCORE; } + break; + + case 'H': + if (!strcmp(format_str, "UNDREDS")) { + return RC_FORMAT_HUNDREDS; + } + break; } @@ -119,6 +142,22 @@ static int rc_format_value_centiseconds(char* buffer, size_t size, uint32_t cent return chars; } +static int rc_format_value_fixed(char* buffer, size_t size, const char* format, int32_t value, int32_t factor) +{ + if (value >= 0) + return snprintf(buffer, size, format, value / factor, value % factor); + + return snprintf(buffer, size, format, value / factor, (-value) % factor); +} + +static int rc_format_value_padded(char* buffer, size_t size, const char* format, int32_t value) +{ + if (value == 0) + return snprintf(buffer, size, "0"); + + return snprintf(buffer, size, format, value); +} + int rc_format_typed_value(char* buffer, size_t size, const rc_typed_value_t* value, int format) { int chars; rc_typed_value_t converted_value; @@ -192,6 +231,41 @@ int rc_format_typed_value(char* buffer, size_t size, const rc_typed_value_t* val rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_FLOAT); chars = snprintf(buffer, size, "%.6f", converted_value.value.f32); break; + + case RC_FORMAT_FIXED1: + rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_SIGNED); + chars = rc_format_value_fixed(buffer, size, "%d.%u", converted_value.value.i32, 10); + break; + + case RC_FORMAT_FIXED2: + rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_SIGNED); + chars = rc_format_value_fixed(buffer, size, "%d.%02u", converted_value.value.i32, 100); + break; + + case RC_FORMAT_FIXED3: + rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_SIGNED); + chars = rc_format_value_fixed(buffer, size, "%d.%03u", converted_value.value.i32, 1000); + break; + + case RC_FORMAT_TENS: + rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_SIGNED); + chars = rc_format_value_padded(buffer, size, "%d0", converted_value.value.i32); + break; + + case RC_FORMAT_HUNDREDS: + rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_SIGNED); + chars = rc_format_value_padded(buffer, size, "%d00", converted_value.value.i32); + break; + + case RC_FORMAT_THOUSANDS: + rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_SIGNED); + chars = rc_format_value_padded(buffer, size, "%d000", converted_value.value.i32); + break; + + case RC_FORMAT_UNSIGNED_VALUE: + rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_UNSIGNED); + chars = snprintf(buffer, size, "%u", converted_value.value.u32); + break; } return chars; diff --git a/deps/rcheevos/src/rcheevos/lboard.c b/deps/rcheevos/src/rcheevos/lboard.c index 0191336a21..98b4ec2f17 100644 --- a/deps/rcheevos/src/rcheevos/lboard.c +++ b/deps/rcheevos/src/rcheevos/lboard.c @@ -235,7 +235,7 @@ int rc_evaluate_lboard(rc_lboard_t* self, int32_t* value, rc_peek_t peek, void* *value = rc_evaluate_value(self->progress, peek, peek_ud, L); break; } - /* fallthrough to RC_LBOARD_STATE_TRIGGERED */ + /* fallthrough */ /* to RC_LBOARD_STATE_TRIGGERED */ case RC_LBOARD_STATE_TRIGGERED: *value = rc_evaluate_value(&self->value, peek, peek_ud, L); diff --git a/deps/rcheevos/src/rcheevos/operand.c b/deps/rcheevos/src/rcheevos/operand.c index d7e20908f8..2522582752 100644 --- a/deps/rcheevos/src/rcheevos/operand.c +++ b/deps/rcheevos/src/rcheevos/operand.c @@ -6,16 +6,12 @@ #ifndef RC_DISABLE_LUA -#ifdef __cplusplus -extern "C" { -#endif +RC_BEGIN_C_DECLS #include #include -#ifdef __cplusplus -} -#endif +RC_END_C_DECLS #endif /* RC_DISABLE_LUA */ @@ -238,7 +234,7 @@ int rc_parse_operand(rc_operand_t* self, const char** memaddr, uint8_t is_indire case '0': if (aux[1] == 'x' || aux[1] == 'X') { /* hex integer constant */ - /* fall through */ + /* fallthrough */ /* to default */ default: ret = rc_parse_operand_memory(self, &aux, parse, is_indirect); @@ -247,8 +243,7 @@ int rc_parse_operand(rc_operand_t* self, const char** memaddr, uint8_t is_indire break; } - - /* fall through for case '0' where not '0x' */ + /* fallthrough */ /* to case '1' for case '0' where not '0x' */ case '1': case '2': case '3': case '4': case '5': /* unsigned integer constant */ case '6': case '7': case '8': case '9': value = strtoul(aux, &end, 10); diff --git a/deps/rcheevos/src/rcheevos/rc_internal.h b/deps/rcheevos/src/rcheevos/rc_internal.h index 4a2f306a89..1354239809 100644 --- a/deps/rcheevos/src/rcheevos/rc_internal.h +++ b/deps/rcheevos/src/rcheevos/rc_internal.h @@ -2,10 +2,9 @@ #define RC_INTERNAL_H #include "rc_runtime_types.h" +#include "rc_util.h" -#ifdef __cplusplus -extern "C" { -#endif +RC_BEGIN_C_DECLS typedef struct rc_scratch_string { char* value; @@ -39,15 +38,8 @@ RC_ALLOW_ALIGN(char) /* force alignment to 4 bytes on 32-bit systems, or 8 bytes on 64-bit systems */ #define RC_ALIGN(n) (((n) + (sizeof(void*)-1)) & ~(sizeof(void*)-1)) -typedef struct rc_scratch_buffer { - struct rc_scratch_buffer* next; - int32_t offset; - uint8_t buffer[512 - 16]; -} -rc_scratch_buffer_t; - typedef struct { - rc_scratch_buffer_t buffer; + rc_buffer_t buffer; rc_scratch_string_t* strings; struct objs { @@ -200,8 +192,6 @@ int rc_lboard_state_active(int state); void rc_parse_richpresence_internal(rc_richpresence_t* self, const char* script, rc_parse_state_t* parse); -#ifdef __cplusplus -} -#endif +RC_END_C_DECLS #endif /* RC_INTERNAL_H */ diff --git a/deps/rcheevos/src/rcheevos/richpresence.c b/deps/rcheevos/src/rcheevos/richpresence.c index db58c76c00..0dd660e686 100644 --- a/deps/rcheevos/src/rcheevos/richpresence.c +++ b/deps/rcheevos/src/rcheevos/richpresence.c @@ -189,6 +189,10 @@ static rc_richpresence_display_t* rc_parse_richpresence_display_internal(const c {"Float4", 6, RC_FORMAT_FLOAT4}, {"Float5", 6, RC_FORMAT_FLOAT5}, {"Float6", 6, RC_FORMAT_FLOAT6}, + {"Fixed1", 6, RC_FORMAT_FIXED1}, + {"Fixed2", 6, RC_FORMAT_FIXED2}, + {"Fixed3", 6, RC_FORMAT_FIXED3}, + {"Unsigned", 8, RC_FORMAT_UNSIGNED_VALUE} }; size_t i; @@ -279,7 +283,6 @@ static void rc_rebalance_richpresence_lookup_rebuild(rc_richpresence_lookup_item static void rc_rebalance_richpresence_lookup(rc_richpresence_lookup_item_t** root, rc_parse_state_t* parse) { rc_richpresence_lookup_item_t** items; - rc_scratch_buffer_t* buffer; int index; int size; @@ -288,29 +291,13 @@ static void rc_rebalance_richpresence_lookup(rc_richpresence_lookup_item_t** roo if (count < 3) return; - /* allocate space for the flattened list - prefer scratch memory if available */ + /* allocate space for the flattened list in scratch memory */ size = count * sizeof(rc_richpresence_lookup_item_t*); - buffer = &parse->scratch.buffer; - do { - const int aligned_offset = RC_ALIGN(buffer->offset); - const int remaining = sizeof(buffer->buffer) - aligned_offset; + items = (rc_richpresence_lookup_item_t**)rc_buffer_alloc(&parse->scratch.buffer, size); - if (remaining >= size) { - items = (rc_richpresence_lookup_item_t**)&buffer->buffer[aligned_offset]; - break; - } - - buffer = buffer->next; - if (buffer == NULL) { - /* could not find large enough block of scratch memory; allocate. if allocation fails, - * we can still use the unbalanced tree, so just bail out */ - items = (rc_richpresence_lookup_item_t**)malloc(size); - if (items == NULL) - return; - - break; - } - } while (1); + /* if allocation fails, we can still use the unbalanced tree, so just bail out */ + if (items == NULL) + return; /* flatten the list */ index = 0; @@ -318,9 +305,6 @@ static void rc_rebalance_richpresence_lookup(rc_richpresence_lookup_item_t** roo /* and rebuild it as a balanced tree */ rc_rebalance_richpresence_lookup_rebuild(root, items, 0, count - 1); - - if (buffer == NULL) - free(items); } static void rc_insert_richpresence_lookup_item(rc_richpresence_lookup_t* lookup, diff --git a/deps/rcheevos/src/rcheevos/runtime_progress.c b/deps/rcheevos/src/rcheevos/runtime_progress.c index d301377db8..fd951dbd5b 100644 --- a/deps/rcheevos/src/rcheevos/runtime_progress.c +++ b/deps/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/deps/rcheevos/src/rcheevos/value.c b/deps/rcheevos/src/rcheevos/value.c index e7df82df90..3662fc3649 100644 --- a/deps/rcheevos/src/rcheevos/value.c +++ b/deps/rcheevos/src/rcheevos/value.c @@ -447,7 +447,7 @@ void rc_typed_value_negate(rc_typed_value_t* value) { { case RC_VALUE_TYPE_UNSIGNED: rc_typed_value_convert(value, RC_VALUE_TYPE_SIGNED); - /* fallthrough to RC_VALUE_TYPE_SIGNED */ + /* fallthrough */ /* to RC_VALUE_TYPE_SIGNED */ case RC_VALUE_TYPE_SIGNED: value->value.i32 = -(value->value.i32); diff --git a/deps/rcheevos/src/rhash/aes.c b/deps/rcheevos/src/rhash/aes.c new file mode 100644 index 0000000000..54ed10bce9 --- /dev/null +++ b/deps/rcheevos/src/rhash/aes.c @@ -0,0 +1,480 @@ +/* This file is sourced from https://github.com/kokke/tiny-AES-c, with unused code excised. + * This code is licensed under the Unlicense license, effectively public domain. + * https://github.com/kokke/tiny-AES-c/blob/f06ac37/unlicense.txt + */ + +/* + +This is an implementation of the AES algorithm, specifically ECB, CTR and CBC mode. +Block size can be chosen in aes.h - available choices are AES128, AES192, AES256. + +The implementation is verified against the test vectors in: + National Institute of Standards and Technology Special Publication 800-38A 2001 ED + +ECB-AES128 +---------- + + plain-text: + 6bc1bee22e409f96e93d7e117393172a + ae2d8a571e03ac9c9eb76fac45af8e51 + 30c81c46a35ce411e5fbc1191a0a52ef + f69f2445df4f9b17ad2b417be66c3710 + + key: + 2b7e151628aed2a6abf7158809cf4f3c + + resulting cipher + 3ad77bb40d7a3660a89ecaf32466ef97 + f5d3d58503b9699de785895a96fdbaaf + 43b1cd7f598ece23881b00e3ed030688 + 7b0c785e27e8ad3f8223207104725dd4 + + +NOTE: String length must be evenly divisible by 16byte (str_len % 16 == 0) + You should pad the end of the string with zeros if this is not the case. + For AES192/256 the key size is proportionally larger. + +*/ + + +/*****************************************************************************/ +/* Includes: */ +/*****************************************************************************/ +#include /* CBC mode, for memset */ +#include "aes.h" + +/*****************************************************************************/ +/* Defines: */ +/*****************************************************************************/ + +/* The number of columns comprising a state in AES. This is a constant in AES. Value=4 */ +#define Nb 4 + +#define Nk 4 /* The number of 32 bit words in a key. */ +#define Nr 10 /* The number of rounds in AES Cipher. */ + +/*****************************************************************************/ +/* Private variables: */ +/*****************************************************************************/ + +/* state - array holding the intermediate results during decryption. */ +typedef uint8_t state_t[4][4]; + +/* The lookup-tables are marked const so they can be placed in read-only storage instead of RAM + * The numbers below can be computed dynamically trading ROM for RAM - + * This can be useful in (embedded) bootloader applications, where ROM is often limited. + */ +static const uint8_t sbox[256] = { + /*0 1 2 3 4 5 6 7 8 9 A B C D E F */ + 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, + 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, + 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, + 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, + 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, + 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, + 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, + 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, + 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, + 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, + 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, + 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, + 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, + 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, + 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, + 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 }; + +static const uint8_t rsbox[256] = { + 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, + 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, + 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, + 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, + 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, + 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, + 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, + 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, + 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, + 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, + 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, + 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, + 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, + 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, + 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d }; + +/* The round constant word array, Rcon[i], contains the values given by + * x to the power (i-1) being powers of x (x is denoted as {02}) in the field GF(2^8) + */ +static const uint8_t Rcon[11] = { + 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36 }; + +/* + * Jordan Goulder points out in PR #12 (https://github.com/kokke/tiny-AES-C/pull/12), + * that you can remove most of the elements in the Rcon array, because they are unused. + * + * From Wikipedia's article on the Rijndael key schedule @ https://en.wikipedia.org/wiki/Rijndael_key_schedule#Rcon + * + * "Only the first some of these constants are actually used – up to rcon[10] for AES-128 (as 11 round keys are needed), + * up to rcon[8] for AES-192, up to rcon[7] for AES-256. rcon[0] is not used in AES algorithm." + */ + + +/*****************************************************************************/ +/* Private functions: */ +/*****************************************************************************/ + +#define getSBoxValue(num) (sbox[(num)]) + +/* This function produces Nb(Nr+1) round keys. The round keys are used in each round to decrypt the states. */ +static void KeyExpansion(uint8_t RoundKey[AES_keyExpSize], const uint8_t Key[AES_KEYLEN]) +{ + unsigned i, j, k; + uint8_t tempa[4]; /* Used for the column/row operations */ + + /* The first round key is the key itself. */ + for (i = 0; i < Nk; ++i) + { + RoundKey[(i * 4) + 0] = Key[(i * 4) + 0]; + RoundKey[(i * 4) + 1] = Key[(i * 4) + 1]; + RoundKey[(i * 4) + 2] = Key[(i * 4) + 2]; + RoundKey[(i * 4) + 3] = Key[(i * 4) + 3]; + } + + /* All other round keys are found from the previous round keys. */ + for (i = Nk; i < Nb * (Nr + 1); ++i) + { + { + k = (i - 1) * 4; + tempa[0]=RoundKey[k + 0]; + tempa[1]=RoundKey[k + 1]; + tempa[2]=RoundKey[k + 2]; + tempa[3]=RoundKey[k + 3]; + + } + + if (i % Nk == 0) + { + /* This function shifts the 4 bytes in a word to the left once. */ + /* [a0,a1,a2,a3] becomes [a1,a2,a3,a0] */ + + /* Function RotWord() */ + { + const uint8_t u8tmp = tempa[0]; + tempa[0] = tempa[1]; + tempa[1] = tempa[2]; + tempa[2] = tempa[3]; + tempa[3] = u8tmp; + } + + /* SubWord() is a function that takes a four-byte input word and + * applies the S-box to each of the four bytes to produce an output word. + */ + + /* Function Subword() */ + { + tempa[0] = getSBoxValue(tempa[0]); + tempa[1] = getSBoxValue(tempa[1]); + tempa[2] = getSBoxValue(tempa[2]); + tempa[3] = getSBoxValue(tempa[3]); + } + + tempa[0] = tempa[0] ^ Rcon[i/Nk]; + } + + j = i * 4; k=(i - Nk) * 4; + RoundKey[j + 0] = RoundKey[k + 0] ^ tempa[0]; + RoundKey[j + 1] = RoundKey[k + 1] ^ tempa[1]; + RoundKey[j + 2] = RoundKey[k + 2] ^ tempa[2]; + RoundKey[j + 3] = RoundKey[k + 3] ^ tempa[3]; + } +} + +void AES_init_ctx(struct AES_ctx* ctx, const uint8_t key[AES_KEYLEN]) +{ + KeyExpansion(ctx->RoundKey, key); +} + +void AES_init_ctx_iv(struct AES_ctx* ctx, const uint8_t key[AES_KEYLEN], const uint8_t iv[AES_BLOCKLEN]) +{ + KeyExpansion(ctx->RoundKey, key); + memcpy (ctx->Iv, iv, AES_BLOCKLEN); +} + +void AES_ctx_set_iv(struct AES_ctx* ctx, const uint8_t iv[AES_BLOCKLEN]) +{ + memcpy (ctx->Iv, iv, AES_BLOCKLEN); +} + +/* This function adds the round key to state. + * The round key is added to the state by an XOR function. + */ +static void AddRoundKey(uint8_t round, state_t* state, const uint8_t RoundKey[AES_keyExpSize]) +{ + uint8_t i,j; + for (i = 0; i < 4; ++i) + { + for (j = 0; j < 4; ++j) + { + (*state)[i][j] ^= RoundKey[(round * Nb * 4) + (i * Nb) + j]; + } + } +} + +/* The SubBytes Function Substitutes the values in the + * state matrix with values in an S-box. + */ +static void SubBytes(state_t* state) +{ + uint8_t i, j; + for (i = 0; i < 4; ++i) + { + for (j = 0; j < 4; ++j) + { + (*state)[j][i] = getSBoxValue((*state)[j][i]); + } + } +} + +/* The ShiftRows() function shifts the rows in the state to the left. + * Each row is shifted with different offset. + * Offset = Row number. So the first row is not shifted. + */ +static void ShiftRows(state_t* state) +{ + uint8_t temp; + + /* Rotate first row 1 columns to left */ + temp = (*state)[0][1]; + (*state)[0][1] = (*state)[1][1]; + (*state)[1][1] = (*state)[2][1]; + (*state)[2][1] = (*state)[3][1]; + (*state)[3][1] = temp; + + /* Rotate second row 2 columns to left */ + temp = (*state)[0][2]; + (*state)[0][2] = (*state)[2][2]; + (*state)[2][2] = temp; + + temp = (*state)[1][2]; + (*state)[1][2] = (*state)[3][2]; + (*state)[3][2] = temp; + + /* Rotate third row 3 columns to left */ + temp = (*state)[0][3]; + (*state)[0][3] = (*state)[3][3]; + (*state)[3][3] = (*state)[2][3]; + (*state)[2][3] = (*state)[1][3]; + (*state)[1][3] = temp; +} + +static uint8_t xtime(uint8_t x) +{ + return ((x<<1) ^ (((x>>7) & 1) * 0x1b)); +} + +/* MixColumns function mixes the columns of the state matrix */ +static void MixColumns(state_t* state) +{ + uint8_t i; + uint8_t Tmp, Tm, t; + for (i = 0; i < 4; ++i) + { + t = (*state)[i][0]; + Tmp = (*state)[i][0] ^ (*state)[i][1] ^ (*state)[i][2] ^ (*state)[i][3] ; + Tm = (*state)[i][0] ^ (*state)[i][1] ; Tm = xtime(Tm); (*state)[i][0] ^= Tm ^ Tmp ; + Tm = (*state)[i][1] ^ (*state)[i][2] ; Tm = xtime(Tm); (*state)[i][1] ^= Tm ^ Tmp ; + Tm = (*state)[i][2] ^ (*state)[i][3] ; Tm = xtime(Tm); (*state)[i][2] ^= Tm ^ Tmp ; + Tm = (*state)[i][3] ^ t ; Tm = xtime(Tm); (*state)[i][3] ^= Tm ^ Tmp ; + } +} + +/* Multiply is used to multiply numbers in the field GF(2^8) + * Note: The last call to xtime() is unneeded, but often ends up generating a smaller binary + * The compiler seems to be able to vectorize the operation better this way. + * See https://github.com/kokke/tiny-AES-c/pull/34 + */ + +#define Multiply(x, y) \ + ( ((y & 1) * x) ^ \ + ((y>>1 & 1) * xtime(x)) ^ \ + ((y>>2 & 1) * xtime(xtime(x))) ^ \ + ((y>>3 & 1) * xtime(xtime(xtime(x)))) ^ \ + ((y>>4 & 1) * xtime(xtime(xtime(xtime(x)))))) \ + +#define getSBoxInvert(num) (rsbox[(num)]) + +/* MixColumns function mixes the columns of the state matrix. + * The method used to multiply may be difficult to understand for the inexperienced. + * Please use the references to gain more information. + */ +static void InvMixColumns(state_t* state) +{ + int i; + uint8_t a, b, c, d; + for (i = 0; i < 4; ++i) + { + a = (*state)[i][0]; + b = (*state)[i][1]; + c = (*state)[i][2]; + d = (*state)[i][3]; + + (*state)[i][0] = Multiply(a, 0x0e) ^ Multiply(b, 0x0b) ^ Multiply(c, 0x0d) ^ Multiply(d, 0x09); + (*state)[i][1] = Multiply(a, 0x09) ^ Multiply(b, 0x0e) ^ Multiply(c, 0x0b) ^ Multiply(d, 0x0d); + (*state)[i][2] = Multiply(a, 0x0d) ^ Multiply(b, 0x09) ^ Multiply(c, 0x0e) ^ Multiply(d, 0x0b); + (*state)[i][3] = Multiply(a, 0x0b) ^ Multiply(b, 0x0d) ^ Multiply(c, 0x09) ^ Multiply(d, 0x0e); + } +} + + +/* The SubBytes Function Substitutes the values in the + * state matrix with values in an S-box. + */ +static void InvSubBytes(state_t* state) +{ + uint8_t i, j; + for (i = 0; i < 4; ++i) + { + for (j = 0; j < 4; ++j) + { + (*state)[j][i] = getSBoxInvert((*state)[j][i]); + } + } +} + +static void InvShiftRows(state_t* state) +{ + uint8_t temp; + + /* Rotate first row 1 columns to right */ + temp = (*state)[3][1]; + (*state)[3][1] = (*state)[2][1]; + (*state)[2][1] = (*state)[1][1]; + (*state)[1][1] = (*state)[0][1]; + (*state)[0][1] = temp; + + /* Rotate second row 2 columns to right */ + temp = (*state)[0][2]; + (*state)[0][2] = (*state)[2][2]; + (*state)[2][2] = temp; + + temp = (*state)[1][2]; + (*state)[1][2] = (*state)[3][2]; + (*state)[3][2] = temp; + + /* Rotate third row 3 columns to right */ + temp = (*state)[0][3]; + (*state)[0][3] = (*state)[1][3]; + (*state)[1][3] = (*state)[2][3]; + (*state)[2][3] = (*state)[3][3]; + (*state)[3][3] = temp; +} + +/* Cipher is the main function that encrypts the PlainText. */ +static void Cipher(state_t* state, const uint8_t RoundKey[AES_keyExpSize]) +{ + uint8_t round = 0; + + /* Add the First round key to the state before starting the rounds. */ + AddRoundKey(0, state, RoundKey); + + /* There will be Nr rounds. + * The first Nr-1 rounds are identical. + * These Nr rounds are executed in the loop below. + * Last one without MixColumns() + */ + for (round = 1; ; ++round) + { + SubBytes(state); + ShiftRows(state); + if (round == Nr) { + break; + } + MixColumns(state); + AddRoundKey(round, state, RoundKey); + } + /* Add round key to last round */ + AddRoundKey(Nr, state, RoundKey); +} + +static void InvCipher(state_t* state, const uint8_t RoundKey[AES_keyExpSize]) +{ + uint8_t round = 0; + + /* Add the First round key to the state before starting the rounds. */ + AddRoundKey(Nr, state, RoundKey); + + /* There will be Nr rounds. + * The first Nr-1 rounds are identical. + * These Nr rounds are executed in the loop below. + * Last one without InvMixColumn() + */ + for (round = (Nr - 1); ; --round) + { + InvShiftRows(state); + InvSubBytes(state); + AddRoundKey(round, state, RoundKey); + if (round == 0) { + break; + } + InvMixColumns(state); + } +} + +/*****************************************************************************/ +/* Public functions: */ +/*****************************************************************************/ + +static void XorWithIv(uint8_t* buf, const uint8_t Iv[AES_BLOCKLEN]) +{ + uint8_t i; + for (i = 0; i < AES_BLOCKLEN; ++i) /* The block in AES is always 128bit no matter the key size */ + { + buf[i] ^= Iv[i]; + } +} + +void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) +{ + size_t i; + uint8_t storeNextIv[AES_BLOCKLEN]; + for (i = 0; i < length; i += AES_BLOCKLEN) + { + memcpy(storeNextIv, buf, AES_BLOCKLEN); + InvCipher((state_t*)buf, ctx->RoundKey); + XorWithIv(buf, ctx->Iv); + memcpy(ctx->Iv, storeNextIv, AES_BLOCKLEN); + buf += AES_BLOCKLEN; + } +} + +/* Symmetrical operation: same function for encrypting as for decrypting. Note any IV/nonce should never be reused with the same key */ +void AES_CTR_xcrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) +{ + uint8_t buffer[AES_BLOCKLEN]; + + size_t i; + int bi; + for (i = 0, bi = AES_BLOCKLEN; i < length; ++i, ++bi) + { + if (bi == AES_BLOCKLEN) /* we need to regen xor compliment in buffer */ + { + memcpy(buffer, ctx->Iv, AES_BLOCKLEN); + Cipher((state_t*)buffer, ctx->RoundKey); + + /* Increment Iv and handle overflow */ + for (bi = (AES_BLOCKLEN - 1); bi >= 0; --bi) + { + /* inc will overflow */ + if (ctx->Iv[bi] == 255) + { + ctx->Iv[bi] = 0; + continue; + } + ctx->Iv[bi] += 1; + break; + } + bi = 0; + } + + buf[i] = (buf[i] ^ buffer[bi]); + } +} diff --git a/deps/rcheevos/src/rhash/aes.h b/deps/rcheevos/src/rhash/aes.h new file mode 100644 index 0000000000..13e679ee14 --- /dev/null +++ b/deps/rcheevos/src/rhash/aes.h @@ -0,0 +1,49 @@ +#ifndef AES_H +#define AES_H + +/* This file is sourced from https://github.com/kokke/tiny-AES-c, with unused code excised. + * This code is licensed under the Unlicense license, effectively public domain. + * https://github.com/kokke/tiny-AES-c/blob/f06ac37/unlicense.txt + */ + +#include +#include + +#define AES_BLOCKLEN 16 /* Block length in bytes - AES is 128b block only */ +#define AES_KEYLEN 16 /* Key length in bytes */ +#define AES_keyExpSize 176 + +struct AES_ctx +{ + uint8_t RoundKey[AES_keyExpSize]; + uint8_t Iv[AES_BLOCKLEN]; +}; + +#ifdef __cplusplus +extern "C" { +#endif + +void AES_init_ctx(struct AES_ctx* ctx, const uint8_t key[AES_KEYLEN]); +void AES_init_ctx_iv(struct AES_ctx* ctx, const uint8_t key[AES_KEYLEN], const uint8_t iv[AES_BLOCKLEN]); +void AES_ctx_set_iv(struct AES_ctx* ctx, const uint8_t iv[AES_BLOCKLEN]); + +/* buffer size MUST be mutile of AES_BLOCKLEN; + * Suggest https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7 for padding scheme + * NOTES: you need to set IV in ctx via AES_init_ctx_iv() or AES_ctx_set_iv() + * no IV should ever be reused with the same key + */ +void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length); + +/* Same function for encrypting as for decrypting. + * IV is incremented for every block, and used after encryption as XOR-compliment for output + * Suggesting https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7 for padding scheme + * NOTES: you need to set IV in ctx with AES_init_ctx_iv() or AES_ctx_set_iv() + * no IV should ever be reused with the same key + */ +void AES_CTR_xcrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length); + +#ifdef __cplusplus +} +#endif + +#endif /* AES_H */ diff --git a/deps/rcheevos/src/rhash/hash.c b/deps/rcheevos/src/rhash/hash.c index 06fc03f225..5fd1a97257 100644 --- a/deps/rcheevos/src/rhash/hash.c +++ b/deps/rcheevos/src/rhash/hash.c @@ -2,15 +2,22 @@ #include "../rc_compat.h" +#include "aes.h" #include "md5.h" #include #include +#if defined(_WIN32) +#define WIN32_LEAN_AND_MEAN +#include +#endif + /* arbitrary limit to prevent allocating and hashing large files */ #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); /* ===================================================== */ @@ -48,7 +55,35 @@ static struct rc_hash_filereader* filereader = NULL; 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; + int wpath_length; + FILE* fp; + + /* Calculate wpath length from path */ + wpath_length = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, path, -1, NULL, 0); + if (wpath_length == 0) /* 0 indicates error (this is likely from invalid UTF-8) */ + return NULL; + + wpath = (wchar_t*)malloc(wpath_length * sizeof(wchar_t)); + if (!wpath) + return NULL; + + if (MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, wpath_length) == 0) + { + free(wpath); + return NULL; + } #if defined(__STDC_WANT_SECURE_LIB__) + _wfopen_s(&fp, wpath, L"rb"); +#else + fp = _wfopen(wpath, L"rb"); +#endif + free(wpath); + return fp; +#elif defined(__STDC_WANT_SECURE_LIB__) FILE* fp; fopen_s(&fp, path, "rb"); return fp; @@ -329,6 +364,21 @@ static uint32_t rc_cd_find_file_sector(void* track_handle, const char* path, uin /* ===================================================== */ +static rc_hash_3ds_get_cia_normal_key_func _3ds_get_cia_normal_key_func = NULL; +static rc_hash_3ds_get_ncch_normal_keys_func _3ds_get_ncch_normal_keys_func = NULL; + +void rc_hash_init_3ds_get_cia_normal_key_func(rc_hash_3ds_get_cia_normal_key_func func) +{ + _3ds_get_cia_normal_key_func = func; +} + +void rc_hash_init_3ds_get_ncch_normal_keys_func(rc_hash_3ds_get_ncch_normal_keys_func func) +{ + _3ds_get_ncch_normal_keys_func = func; +} + +/* ===================================================== */ + const char* rc_path_get_filename(const char* path) { const char* ptr = path + strlen(path); @@ -479,7 +529,7 @@ static int rc_hash_cd_file(md5_state_t* md5, void* track_handle, uint32_t sector } if (size < (unsigned)num_read) - size = (unsigned)num_read; + num_read = (size_t)size; do { @@ -646,6 +696,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 */ @@ -1087,6 +1424,619 @@ static int rc_hash_n64(char hash[33], const char* path) return rc_hash_finalize(&md5, hash); } +static int rc_hash_nintendo_3ds_ncch(md5_state_t* md5, void* file_handle, uint8_t header[0x200], struct AES_ctx* cia_aes) +{ + struct AES_ctx ncch_aes; + uint8_t* hash_buffer; + uint64_t exefs_offset, exefs_real_size; + uint32_t exefs_buffer_size; + uint8_t primary_key[AES_KEYLEN], secondary_key[AES_KEYLEN]; + uint8_t fixed_key_flag, no_crypto_flag, seed_crypto_flag; + uint8_t crypto_method, secondary_key_x_slot; + uint16_t ncch_version; + uint32_t i; + uint8_t primary_key_y[AES_KEYLEN], program_id[sizeof(uint64_t)]; + uint8_t iv[AES_BLOCKLEN]; + uint8_t exefs_section_name[8]; + uint64_t exefs_section_offset, exefs_section_size; + + exefs_offset = ((uint32_t)header[0x1A3] << 24) | (header[0x1A2] << 16) | (header[0x1A1] << 8) | header[0x1A0]; + exefs_real_size = ((uint32_t)header[0x1A7] << 24) | (header[0x1A6] << 16) | (header[0x1A5] << 8) | header[0x1A4]; + + /* Offset and size are in "media units" (1 media unit = 0x200 bytes) */ + exefs_offset *= 0x200; + exefs_real_size *= 0x200; + + if (exefs_real_size > MAX_BUFFER_SIZE) + exefs_buffer_size = MAX_BUFFER_SIZE; + else + exefs_buffer_size = (uint32_t)exefs_real_size; + + /* This region is technically optional, but it should always be present for executable content (i.e. games) */ + if (exefs_offset == 0 || exefs_real_size == 0) + return rc_hash_error("ExeFS was not available"); + + /* NCCH flag 7 is a bitfield of various crypto related flags */ + fixed_key_flag = header[0x188 + 7] & 0x01; + no_crypto_flag = header[0x188 + 7] & 0x04; + seed_crypto_flag = header[0x188 + 7] & 0x20; + + ncch_version = (header[0x113] << 8) | header[0x112]; + + if (no_crypto_flag == 0) + { + rc_hash_verbose("Encrypted NCCH detected"); + + if (fixed_key_flag != 0) + { + /* Fixed crypto key means all 0s for both keys */ + memset(primary_key, 0, sizeof(primary_key)); + memset(secondary_key, 0, sizeof(secondary_key)); + rc_hash_verbose("Using fixed key crypto"); + } + else + { + if (_3ds_get_ncch_normal_keys_func == NULL) + return rc_hash_error("An encrypted NCCH was detected, but the NCCH normal keys callback was not set"); + + /* Primary key y is just the first 16 bytes of the header */ + memcpy(primary_key_y, header, sizeof(primary_key_y)); + + /* NCCH flag 3 indicates which secondary key x slot is used */ + crypto_method = header[0x188 + 3]; + + switch (crypto_method) + { + case 0x00: + rc_hash_verbose("Using NCCH crypto method v1"); + secondary_key_x_slot = 0x2C; + break; + case 0x01: + rc_hash_verbose("Using NCCH crypto method v2"); + secondary_key_x_slot = 0x25; + break; + case 0x0A: + rc_hash_verbose("Using NCCH crypto method v3"); + secondary_key_x_slot = 0x18; + break; + case 0x0B: + rc_hash_verbose("Using NCCH crypto method v4"); + secondary_key_x_slot = 0x1B; + break; + default: + snprintf((char*)header, 0x200, "Invalid crypto method %02X", (unsigned)crypto_method); + return rc_hash_error((const char*)header); + } + + /* We only need the program id if we're doing seed crypto */ + if (seed_crypto_flag != 0) + { + rc_hash_verbose("Using seed crypto"); + memcpy(program_id, &header[0x118], sizeof(program_id)); + } + + if (_3ds_get_ncch_normal_keys_func(primary_key_y, secondary_key_x_slot, seed_crypto_flag != 0 ? program_id : NULL, primary_key, secondary_key) == 0) + return rc_hash_error("Could not obtain NCCH normal keys"); + } + + switch (ncch_version) + { + case 0: + case 2: + rc_hash_verbose("Detected NCCH version 0/2"); + for (i = 0; i < 8; i++) + { + /* First 8 bytes is the partition id in reverse byte order */ + iv[7 - i] = header[0x108 + i]; + } + + /* Magic number for ExeFS */ + iv[8] = 2; + + /* Rest of the bytes are 0 */ + memset(&iv[9], 0, sizeof(iv) - 9); + break; + case 1: + rc_hash_verbose("Detected NCCH version 1"); + for (i = 0; i < 8; i++) + { + /* First 8 bytes is the partition id in normal byte order */ + iv[i] = header[0x108 + i]; + } + + /* Next 4 bytes are 0 */ + memset(&iv[8], 0, 4); + + /* Last 4 bytes is the ExeFS byte offset in big endian */ + iv[12] = (exefs_offset >> 24) & 0xFF; + iv[13] = (exefs_offset >> 16) & 0xFF; + iv[14] = (exefs_offset >> 8) & 0xFF; + iv[15] = exefs_offset & 0xFF; + break; + default: + snprintf((char*)header, 0x200, "Invalid NCCH version %04X", (unsigned)ncch_version); + return rc_hash_error((const char*)header); + } + } + + /* ASSERT: file position must be +0x200 from start of NCCH (i.e. end of header) */ + exefs_offset -= 0x200; + + if (cia_aes) + { + /* We have to decrypt the data between the header and the ExeFS so the CIA AES state is correct + * when we reach the ExeFS. This decrypted data is not included in the RetroAchievements hash */ + + /* This should never happen in practice, but just in case */ + if (exefs_offset > MAX_BUFFER_SIZE) + return rc_hash_error("Too much data required to decrypt in order to hash"); + + hash_buffer = (uint8_t*)malloc((uint32_t)exefs_offset); + if (!hash_buffer) + { + snprintf((char*)header, 0x200, "Failed to allocate %u bytes", (unsigned)exefs_offset); + return rc_hash_error((const char*)header); + } + + if (rc_file_read(file_handle, hash_buffer, (uint32_t)exefs_offset) != (uint32_t)exefs_offset) + { + free(hash_buffer); + return rc_hash_error("Could not read NCCH data"); + } + + AES_CBC_decrypt_buffer(cia_aes, hash_buffer, (uint32_t)exefs_offset); + free(hash_buffer); + } + else + { + /* No decryption needed, just skip over the in-between data */ + rc_file_seek(file_handle, (int64_t)exefs_offset, SEEK_CUR); + } + + hash_buffer = (uint8_t*)malloc(exefs_buffer_size); + if (!hash_buffer) + { + snprintf((char*)header, 0x200, "Failed to allocate %u bytes", (unsigned)exefs_buffer_size); + return rc_hash_error((const char*)header); + } + + /* Clear out crypto flags to ensure we get the same hash for decrypted and encrypted ROMs */ + memset(&header[0x114], 0, 4); + header[0x188 + 3] = 0; + header[0x188 + 7] &= ~(0x20 | 0x04 | 0x01); + + rc_hash_verbose("Hashing 512 byte NCCH header"); + md5_append(md5, header, 0x200); + + if (verbose_message_callback) + { + snprintf((char*)header, 0x200, "Hashing %u bytes for ExeFS (at NCCH offset %08X%08X)", (unsigned)exefs_buffer_size, (unsigned)(exefs_offset >> 32), (unsigned)exefs_offset); + verbose_message_callback((const char*)header); + } + + if (rc_file_read(file_handle, hash_buffer, exefs_buffer_size) != exefs_buffer_size) + { + free(hash_buffer); + return rc_hash_error("Could not read ExeFS data"); + } + + if (cia_aes) + { + rc_hash_verbose("Performing CIA decryption for ExeFS"); + AES_CBC_decrypt_buffer(cia_aes, hash_buffer, exefs_buffer_size); + } + + if (no_crypto_flag == 0) + { + rc_hash_verbose("Performing NCCH decryption for ExeFS"); + + AES_init_ctx_iv(&ncch_aes, primary_key, iv); + AES_CTR_xcrypt_buffer(&ncch_aes, hash_buffer, 0x200); + + for (i = 0; i < 8; i++) + { + memcpy(exefs_section_name, &hash_buffer[i * 16], sizeof(exefs_section_name)); + exefs_section_offset = ((uint32_t)hash_buffer[i * 16 + 11] << 24) | (hash_buffer[i * 16 + 10] << 16) | (hash_buffer[i * 16 + 9] << 8) | hash_buffer[i * 16 + 8]; + exefs_section_size = ((uint32_t)hash_buffer[i * 16 + 15] << 24) | (hash_buffer[i * 16 + 14] << 16) | (hash_buffer[i * 16 + 13] << 8) | hash_buffer[i * 16 + 12]; + + /* 0 size indicates an unused section */ + if (exefs_section_size == 0) + continue; + + /* Offsets must be aligned by a media unit */ + if (exefs_section_offset & 0x1FF) + return rc_hash_error("ExeFS section offset is misaligned"); + + /* Offset is relative to the end of the header */ + exefs_section_offset += 0x200; + + /* Check against malformed sections */ + if (exefs_section_offset + ((exefs_section_size + 0x1FF) & ~(uint64_t)0x1FF) > (uint64_t)exefs_real_size) + return rc_hash_error("ExeFS section would overflow"); + + if (memcmp(exefs_section_name, "icon", 4) == 0 || memcmp(exefs_section_name, "banner", 6) == 0) + { + /* Align size up by a media unit */ + exefs_section_size = (exefs_section_size + 0x1FF) & ~(uint64_t)0x1FF; + AES_init_ctx(&ncch_aes, primary_key); + } + else + { + /* We don't align size up here, as the padding bytes will use the primary key rather than the secondary key */ + AES_init_ctx(&ncch_aes, secondary_key); + } + + /* In theory, the section offset + size could be greater than the buffer size */ + /* In practice, this likely never occurs, but just in case it does, ignore the section or constrict the size */ + if (exefs_section_offset + exefs_section_size > exefs_buffer_size) + { + if (exefs_section_offset >= exefs_buffer_size) + continue; + + exefs_section_size = exefs_buffer_size - exefs_section_offset; + } + + if (verbose_message_callback) + { + exefs_section_name[7] = '\0'; + snprintf((char*)header, 0x200, "Decrypting ExeFS file %s at ExeFS offset %08X with size %08X", (const char*)exefs_section_name, (unsigned)exefs_section_offset, (unsigned)exefs_section_size); + verbose_message_callback((const char*)header); + } + + AES_CTR_xcrypt_buffer(&ncch_aes, &hash_buffer[exefs_section_offset], exefs_section_size & ~(uint64_t)0xF); + + if (exefs_section_size & 0x1FF) + { + /* Handle padding bytes, these always use the primary key */ + exefs_section_offset += exefs_section_size; + exefs_section_size = 0x200 - (exefs_section_size & 0x1FF); + + if (verbose_message_callback) + { + snprintf((char*)header, 0x200, "Decrypting ExeFS padding at ExeFS offset %08X with size %08X", (unsigned)exefs_section_offset, (unsigned)exefs_section_size); + verbose_message_callback((const char*)header); + } + + /* Align our decryption start to an AES block boundary */ + if (exefs_section_size & 0xF) + { + /* We're a little evil here re-using the IV like this, but this seems to be the best way to deal with this... */ + memcpy(iv, ncch_aes.Iv, sizeof(iv)); + exefs_section_offset &= ~(uint64_t)0xF; + + /* First decrypt these last bytes using the secondary key */ + AES_CTR_xcrypt_buffer(&ncch_aes, &hash_buffer[exefs_section_offset], 0x10 - (exefs_section_size & 0xF)); + + /* Now re-encrypt these bytes using the primary key */ + AES_init_ctx_iv(&ncch_aes, primary_key, iv); + AES_CTR_xcrypt_buffer(&ncch_aes, &hash_buffer[exefs_section_offset], 0x10 - (exefs_section_size & 0xF)); + + /* All of the padding can now be decrypted using the primary key */ + AES_ctx_set_iv(&ncch_aes, iv); + exefs_section_size += 0x10 - (exefs_section_size & 0xF); + } + + AES_init_ctx(&ncch_aes, primary_key); + AES_CTR_xcrypt_buffer(&ncch_aes, &hash_buffer[exefs_section_offset], exefs_section_size); + } + } + } + + md5_append(md5, hash_buffer, exefs_buffer_size); + + free(hash_buffer); + return 1; +} + +static uint32_t rc_hash_nintendo_3ds_cia_signature_size(uint8_t header[0x200]) +{ + uint32_t signature_type; + + signature_type = ((uint32_t)header[0] << 24) | (header[1] << 16) | (header[2] << 8) | header[3]; + switch (signature_type) + { + case 0x010000: + case 0x010003: + return 0x200 + 0x3C; + case 0x010001: + case 0x010004: + return 0x100 + 0x3C; + case 0x010002: + case 0x010005: + return 0x3C + 0x40; + default: + snprintf((char*)header, 0x200, "Invalid signature type %08X", (unsigned)signature_type); + return rc_hash_error((const char*)header); + } +} + +static int rc_hash_nintendo_3ds_cia(md5_state_t* md5, void* file_handle, uint8_t header[0x200]) +{ + const uint32_t CIA_HEADER_SIZE = 0x2020; /* Yes, this is larger than the header[0x200], but we only use the beginning of the header */ + const uint64_t CIA_ALIGNMENT_MASK = 64 - 1; /* sizes are aligned by 64 bytes */ + struct AES_ctx aes; + uint8_t iv[AES_BLOCKLEN], normal_key[AES_KEYLEN], title_key[AES_KEYLEN], title_id[sizeof(uint64_t)]; + uint32_t cert_size, tik_size, tmd_size; + int64_t cert_offset, tik_offset, tmd_offset, content_offset; + uint32_t signature_size, i; + uint16_t content_count; + uint8_t common_key_index; + + cert_size = ((uint32_t)header[0x0B] << 24) | (header[0x0A] << 16) | (header[0x09] << 8) | header[0x08]; + tik_size = ((uint32_t)header[0x0F] << 24) | (header[0x0E] << 16) | (header[0x0D] << 8) | header[0x0C]; + tmd_size = ((uint32_t)header[0x13] << 24) | (header[0x12] << 16) | (header[0x11] << 8) | header[0x10]; + + cert_offset = (CIA_HEADER_SIZE + CIA_ALIGNMENT_MASK) & ~CIA_ALIGNMENT_MASK; + tik_offset = (cert_offset + cert_size + CIA_ALIGNMENT_MASK) & ~CIA_ALIGNMENT_MASK; + tmd_offset = (tik_offset + tik_size + CIA_ALIGNMENT_MASK) & ~CIA_ALIGNMENT_MASK; + content_offset = (tmd_offset + tmd_size + CIA_ALIGNMENT_MASK) & ~CIA_ALIGNMENT_MASK; + + /* Check if this CIA is encrypted, if it isn't, we can hash it right away */ + + rc_file_seek(file_handle, tmd_offset, SEEK_SET); + if (rc_file_read(file_handle, header, 4) != 4) + return rc_hash_error("Could not read TMD signature type"); + + signature_size = rc_hash_nintendo_3ds_cia_signature_size(header); + if (signature_size == 0) + return 0; /* rc_hash_nintendo_3ds_cia_signature_size will call rc_hash_error, so we don't need to do so here */ + + rc_file_seek(file_handle, signature_size + 0x9E, SEEK_CUR); + if (rc_file_read(file_handle, header, 2) != 2) + return rc_hash_error("Could not read TMD content count"); + + content_count = (header[0] << 8) | header[1]; + + rc_file_seek(file_handle, 0x9C4 - 0x9E - 2, SEEK_CUR); + for (i = 0; i < content_count; i++) + { + if (rc_file_read(file_handle, header, 0x30) != 0x30) + return rc_hash_error("Could not read TMD content chunk"); + + /* Content index 0 is the main content (i.e. the 3DS executable) */ + if (((header[4] << 8) | header[5]) == 0) + break; + + content_offset += ((uint32_t)header[0xC] << 24) | (header[0xD] << 16) | (header[0xE] << 8) | header[0xF]; + } + + if (i == content_count) + return rc_hash_error("Could not find main content chunk in TMD"); + + if ((header[7] & 1) == 0) + { + /* Not encrypted, we can hash the NCCH immediately */ + rc_file_seek(file_handle, content_offset, SEEK_SET); + if (rc_file_read(file_handle, header, 0x200) != 0x200) + return rc_hash_error("Could not read NCCH header"); + + if (memcmp(&header[0x100], "NCCH", 4) != 0) + { + snprintf((char*)header, 0x200, "NCCH header was not at %08X%08X", (unsigned)(content_offset >> 32), (unsigned)content_offset); + return rc_hash_error((const char*)header); + } + + return rc_hash_nintendo_3ds_ncch(md5, file_handle, header, NULL); + } + + if (_3ds_get_cia_normal_key_func == NULL) + return rc_hash_error("An encrypted CIA was detected, but the CIA normal key callback was not set"); + + /* Acquire the encrypted title key, title id, and common key index from the ticket */ + /* These will be needed to decrypt the title key, and that will be needed to decrypt the CIA */ + + rc_file_seek(file_handle, tik_offset, SEEK_SET); + if (rc_file_read(file_handle, header, 4) != 4) + return rc_hash_error("Could not read ticket signature type"); + + signature_size = rc_hash_nintendo_3ds_cia_signature_size(header); + if (signature_size == 0) + return 0; + + rc_file_seek(file_handle, signature_size, SEEK_CUR); + if (rc_file_read(file_handle, header, 0xB2) != 0xB2) + return rc_hash_error("Could not read ticket data"); + + memcpy(title_key, &header[0x7F], sizeof(title_key)); + memcpy(title_id, &header[0x9C], sizeof(title_id)); + common_key_index = header[0xB1]; + + if (common_key_index > 5) + { + snprintf((char*)header, 0x200, "Invalid common key index %02X", (unsigned)common_key_index); + return rc_hash_error((const char*)header); + } + + if (_3ds_get_cia_normal_key_func(common_key_index, normal_key) == 0) + { + snprintf((char*)header, 0x200, "Could not obtain common key %02X", (unsigned)common_key_index); + return rc_hash_error((const char*)header); + } + + memset(iv, 0, sizeof(iv)); + memcpy(iv, title_id, sizeof(title_id)); + AES_init_ctx_iv(&aes, normal_key, iv); + + /* Finally, decrypt the title key */ + AES_CBC_decrypt_buffer(&aes, title_key, sizeof(title_key)); + + /* Now we can hash the NCCH */ + + rc_file_seek(file_handle, content_offset, SEEK_SET); + if (rc_file_read(file_handle, header, 0x200) != 0x200) + return rc_hash_error("Could not read NCCH header"); + + memset(iv, 0, sizeof(iv)); /* Content index is iv (which is always 0 for main content) */ + AES_init_ctx_iv(&aes, title_key, iv); + AES_CBC_decrypt_buffer(&aes, header, 0x200); + + if (memcmp(&header[0x100], "NCCH", 4) != 0) + { + snprintf((char*)header, 0x200, "NCCH header was not at %08X%08X", (unsigned)(content_offset >> 32), (unsigned)content_offset); + return rc_hash_error((const char*)header); + } + + return rc_hash_nintendo_3ds_ncch(md5, file_handle, header, &aes); +} + +static int rc_hash_nintendo_3ds_3dsx(md5_state_t* md5, void* file_handle, uint8_t header[0x200]) +{ + uint8_t* hash_buffer; + uint32_t header_size, reloc_header_size, code_size; + int64_t code_offset; + + header_size = (header[5] << 8) | header[4]; + reloc_header_size = (header[7] << 8) | header[6]; + code_size = ((uint32_t)header[0x13] << 24) | (header[0x12] << 16) | (header[0x11] << 8) | header[0x10]; + + /* 3 relocation headers are in-between the 3DSX header and code segment */ + code_offset = header_size + reloc_header_size * 3; + + if (code_size > MAX_BUFFER_SIZE) + code_size = MAX_BUFFER_SIZE; + + hash_buffer = (uint8_t*)malloc(code_size); + if (!hash_buffer) + { + snprintf((char*)header, 0x200, "Failed to allocate %u bytes", (unsigned)code_size); + return rc_hash_error((const char*)header); + } + + rc_file_seek(file_handle, code_offset, SEEK_SET); + + if (verbose_message_callback) + { + snprintf((char*)header, 0x200, "Hashing %u bytes for 3DSX (at %08X)", (unsigned)code_size, (unsigned)code_offset); + verbose_message_callback((const char*)header); + } + + if (rc_file_read(file_handle, hash_buffer, code_size) != code_size) + { + free(hash_buffer); + return rc_hash_error("Could not read 3DSX code segment"); + } + + md5_append(md5, hash_buffer, code_size); + + free(hash_buffer); + return 1; +} + +static int rc_hash_nintendo_3ds(char hash[33], const char* path) +{ + md5_state_t md5; + void* file_handle; + uint8_t header[0x200]; /* NCCH and NCSD headers are both 0x200 bytes */ + int64_t header_offset; + + file_handle = rc_file_open(path); + if (!file_handle) + return rc_hash_error("Could not open file"); + + rc_file_seek(file_handle, 0, SEEK_SET); + + /* If we don't have a full header, this is probably not a 3DS ROM */ + if (rc_file_read(file_handle, header, sizeof(header)) != sizeof(header)) + { + rc_file_close(file_handle); + return rc_hash_error("Could not read 3DS ROM header"); + } + + md5_init(&md5); + + if (memcmp(&header[0x100], "NCSD", 4) == 0) + { + /* A NCSD container contains 1-8 NCCH partitions */ + /* The first partition (index 0) is reserved for executable content */ + header_offset = ((uint32_t)header[0x123] << 24) | (header[0x122] << 16) | (header[0x121] << 8) | header[0x120]; + /* Offset is in "media units" (1 media unit = 0x200 bytes) */ + header_offset *= 0x200; + + /* We include the NCSD header in the hash, as that will ensure different versions of a game result in a different hash + * This is due to some revisions / languages only ever changing other NCCH paritions (e.g. the game manual) + */ + rc_hash_verbose("Hashing 512 byte NCSD header"); + md5_append(&md5, header, sizeof(header)); + + if (verbose_message_callback) + { + snprintf((char*)header, sizeof(header), "Detected NCSD header, seeking to NCCH partition at %08X%08X", (unsigned)(header_offset >> 32), (unsigned)header_offset); + verbose_message_callback((const char*)header); + } + + rc_file_seek(file_handle, header_offset, SEEK_SET); + if (rc_file_read(file_handle, header, sizeof(header)) != sizeof(header)) + { + rc_file_close(file_handle); + return rc_hash_error("Could not read 3DS NCCH header"); + } + + if (memcmp(&header[0x100], "NCCH", 4) != 0) + { + rc_file_close(file_handle); + snprintf((char*)header, sizeof(header), "3DS NCCH header was not at %08X%08X", (unsigned)(header_offset >> 32), (unsigned)header_offset); + return rc_hash_error((const char*)header); + } + } + + if (memcmp(&header[0x100], "NCCH", 4) == 0) + { + if (rc_hash_nintendo_3ds_ncch(&md5, file_handle, header, NULL)) + { + rc_file_close(file_handle); + return rc_hash_finalize(&md5, hash); + } + + rc_file_close(file_handle); + return rc_hash_error("Failed to hash 3DS NCCH container"); + } + + /* Couldn't identify either an NCSD or NCCH */ + + /* Try to identify this as a CIA */ + if (header[0] == 0x20 && header[1] == 0x20 && header[2] == 0x00 && header[3] == 0x00) + { + rc_hash_verbose("Detected CIA, attempting to find executable NCCH"); + + if (rc_hash_nintendo_3ds_cia(&md5, file_handle, header)) + { + rc_file_close(file_handle); + return rc_hash_finalize(&md5, hash); + } + + rc_file_close(file_handle); + return rc_hash_error("Failed to hash 3DS CIA container"); + } + + /* This might be a homebrew game, try to detect that */ + if (memcmp(&header[0], "3DSX", 4) == 0) + { + rc_hash_verbose("Detected 3DSX"); + + if (rc_hash_nintendo_3ds_3dsx(&md5, file_handle, header)) + { + rc_file_close(file_handle); + return rc_hash_finalize(&md5, hash); + } + + rc_file_close(file_handle); + return rc_hash_error("Failed to hash 3DS 3DSX container"); + } + + /* Raw ELF marker (AXF/ELF files) */ + if (memcmp(&header[0], "\x7f\x45\x4c\x46", 4) == 0) + { + rc_hash_verbose("Detected AXF/ELF file, hashing entire file"); + + /* Don't bother doing anything fancy here, just hash entire file */ + rc_file_close(file_handle); + return rc_hash_whole_file(hash, path); + } + + rc_file_close(file_handle); + return rc_hash_error("Not a 3DS ROM"); +} + static int rc_hash_nintendo_ds(char hash[33], const char* path) { uint8_t header[512]; @@ -1925,7 +2875,7 @@ void rc_file_seek_buffered_file(void* file_handle, int64_t offset, int origin) { case SEEK_SET: buffered_file->read_ptr = buffered_file->data + offset; break; case SEEK_CUR: buffered_file->read_ptr += offset; break; - case SEEK_END: buffered_file->read_ptr = buffered_file->data + buffered_file->data_size - offset; break; + case SEEK_END: buffered_file->read_ptr = buffered_file->data + buffered_file->data_size + offset; break; } if (buffered_file->read_ptr < buffered_file->data) @@ -1957,7 +2907,7 @@ void rc_file_close_buffered_file(void* file_handle) free(file_handle); } -static int rc_hash_file_from_buffer(char hash[33], int console_id, const uint8_t* buffer, size_t buffer_size) +static int rc_hash_file_from_buffer(char hash[33], uint32_t console_id, const uint8_t* buffer, size_t buffer_size) { struct rc_hash_filereader buffered_filereader_funcs; struct rc_hash_filereader* old_filereader = filereader; @@ -1980,7 +2930,7 @@ static int rc_hash_file_from_buffer(char hash[33], int console_id, const uint8_t return result; } -int rc_hash_generate_from_buffer(char hash[33], int console_id, const uint8_t* buffer, size_t buffer_size) +int rc_hash_generate_from_buffer(char hash[33], uint32_t console_id, const uint8_t* buffer, size_t buffer_size) { switch (console_id) { @@ -2050,6 +3000,7 @@ int rc_hash_generate_from_buffer(char hash[33], int console_id, const uint8_t* b 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); @@ -2115,7 +3066,7 @@ static int rc_hash_whole_file(char hash[33], const char* path) return result; } -static int rc_hash_buffered_file(char hash[33], int console_id, const char* path) +static int rc_hash_buffered_file(char hash[33], uint32_t console_id, const char* path) { uint8_t* buffer; int64_t size; @@ -2261,7 +3212,7 @@ static const char* rc_hash_get_first_item_from_playlist(const char* path) return disc_path; } -static int rc_hash_generate_from_playlist(char hash[33], int console_id, const char* path) +static int rc_hash_generate_from_playlist(char hash[33], uint32_t console_id, const char* path) { int result; const char* disc_path; @@ -2283,7 +3234,7 @@ static int rc_hash_generate_from_playlist(char hash[33], int console_id, const c return result; } -int rc_hash_generate_from_file(char hash[33], int console_id, const char* path) +int rc_hash_generate_from_file(char hash[33], uint32_t console_id, const char* path) { switch (console_id) { @@ -2368,12 +3319,18 @@ int rc_hash_generate_from_file(char hash[33], int console_id, const char* path) 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); case RC_CONSOLE_NINTENDO_64: return rc_hash_n64(hash, path); + case RC_CONSOLE_NINTENDO_3DS: + return rc_hash_nintendo_3ds(hash, path); + case RC_CONSOLE_NINTENDO_DS: case RC_CONSOLE_NINTENDO_DSI: return rc_hash_nintendo_ds(hash, path); @@ -2506,6 +3463,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")) { @@ -2529,6 +3494,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': @@ -2594,7 +3564,8 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* iterator->consoles[4] = RC_CONSOLE_PSP; iterator->consoles[5] = RC_CONSOLE_PC_ENGINE_CD; iterator->consoles[6] = RC_CONSOLE_3DO; - iterator->consoles[7] = RC_CONSOLE_PCFX; + iterator->consoles[7] = RC_CONSOLE_NEO_GEO_CD; + iterator->consoles[8] = RC_CONSOLE_PCFX; need_path = 1; } else if (rc_path_compare_extension(ext, "col")) @@ -2613,6 +3584,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': @@ -2629,6 +3606,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': diff --git a/griffin/griffin.c b/griffin/griffin.c index 044820c698..c735128e3d 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -223,6 +223,7 @@ ACHIEVEMENTS #include "../deps/rcheevos/src/rcheevos/runtime_progress.c" #include "../deps/rcheevos/src/rcheevos/trigger.c" #include "../deps/rcheevos/src/rcheevos/value.c" +#include "../deps/rcheevos/src/rhash/aes.c" #include "../deps/rcheevos/src/rhash/cdreader.c" #include "../deps/rcheevos/src/rhash/hash.c" diff --git a/runloop.c b/runloop.c index 9279b9f7f0..5d781aca91 100644 --- a/runloop.c +++ b/runloop.c @@ -6247,22 +6247,23 @@ static enum runloop_state_enum runloop_check_state( #ifdef HAVE_CHEEVOS if (cheevos_hardcore_active) { - static int unpaused_frames = 0; - - if (runloop_st->flags & RUNLOOP_FLAG_PAUSED) - unpaused_frames = 0; - else - /* Frame advance is not allowed when achievement hardcore is active */ + if (!(runloop_st->flags & RUNLOOP_FLAG_PAUSED)) { - /* Limit pause to approximately three times per second (depending on core framerate) */ - if (unpaused_frames < 20) + /* In hardcore mode, the user is only allowed to pause infrequently. */ + if ((pause_pressed && !old_pause_pressed) || + (!focused && old_focus && pause_nonactive)) { - ++unpaused_frames; - pause_pressed = false; + /* If the user is trying to pause, check to see if it's allowed. */ + if (!rcheevos_is_pause_allowed()) + { + pause_pressed = false; + if (pause_nonactive) + focused = true; + } } } } - else + else /* frame advance not allowed in hardcore */ #endif { frameadvance_pressed = BIT256_GET(current_bits, RARCH_FRAMEADVANCE);