diff --git a/3rdparty/rcheevos/.editorconfig b/3rdparty/rcheevos/.editorconfig deleted file mode 100644 index d1591493ba..0000000000 --- a/3rdparty/rcheevos/.editorconfig +++ /dev/null @@ -1,15 +0,0 @@ -# More info: http://EditorConfig.org -root = true - -# * here means any file type -[*] -end_of_line = crlf -insert_final_newline = true - -# latin1 is a type of ASCII, should work with mbcs -[*.{h,c,cpp}] -charset = latin1 -indent_style = space -indent_size = 2 -trim_trailing_whitespace = true -curly_bracket_next_line = false diff --git a/3rdparty/rcheevos/.gitignore b/3rdparty/rcheevos/.gitignore deleted file mode 100644 index 23ead4b3a7..0000000000 --- a/3rdparty/rcheevos/.gitignore +++ /dev/null @@ -1,65 +0,0 @@ -# Prerequisites -*.d - -# Object files -*.o -*.ko -*.obj -*.elf - -# Linker output -*.ilk -*.map -*.exp - -# Precompiled Headers -*.gch -*.pch - -# Libraries -*.lib -*.a -*.la -*.lo - -# Shared objects (inc. Windows DLLs) -*.dll -*.so -*.so.* -*.dylib - -# Executables -*.exe -*.out -*.app -*.i*86 -*.x86_64 -*.hex - -# Debug files -*.dSYM/ -*.su -*.idb -*.pdb - -# Kernel Module Compile Results -*.mod* -*.cmd -.tmp_versions/ -modules.order -Module.symvers -Mkfile.old -dkms.conf - -# Visual Studio files -Debug/ -Release/ -*.user -.vs/ - -# Repository specific -test/test -test/galaga_nes.h -test/smw_snes.h -validator/validator -.vscode/* diff --git a/3rdparty/rcheevos/CHANGELOG.md b/3rdparty/rcheevos/CHANGELOG.md index fe237f0329..cb7111580b 100644 --- a/3rdparty/rcheevos/CHANGELOG.md +++ b/3rdparty/rcheevos/CHANGELOG.md @@ -1,3 +1,16 @@ +# v12.4.0 +* add avatar_last_updated field to rc_client_user_t and rc_api_login_response_t +* add more fields to fetch_games_list API +* add rc_client_begin_fetch_game_list +* add extended RAM to memory map for PSP +* add memory map for XBOX +* add hash generation for .neo files (geolith-specific NeoGeo ROMs) +* add validation warning if Mem and Delta conditions exist for same address and value +* ignore warning achievements when reporting achievement count in rc_client_get_user_game_summary +* fix crash when {recall} exists without a Remember +* fix crash when legacy value contains invalid syntax that overflows the conversion buffer +* fix handling of SubSource chain starting with a constant + # v12.3.0 * add rc_client_get_next_achievement_info * rc_client image functions will now return RC_INSUFFICENT_BUFFER instead of truncating if buffer is not large enough diff --git a/3rdparty/rcheevos/Package.swift b/3rdparty/rcheevos/Package.swift deleted file mode 100644 index 8205a679ec..0000000000 --- a/3rdparty/rcheevos/Package.swift +++ /dev/null @@ -1,32 +0,0 @@ -// swift-tools-version: 5.8 -// The swift-tools-version declares the minimum version of Swift required to build this package. - -import PackageDescription - -let package = Package( - name: "rcheevos", - products: [ - // Products define the executables and libraries a package produces, and make them visible to other packages. - .library( - name: "rcheevos", - targets: ["rcheevos"]), - ], - dependencies: [ - // Dependencies declare other packages that this package depends on. - // .package(url: /* package url */, from: "1.0.0"), - ], - targets: [ - // Targets are the basic building blocks of a package. A target can define a module or a test suite. - // Targets can depend on other targets in this package, and on products in packages this package depends on. - .target( - name: "rcheevos", - dependencies: [], - path: ".", - exclude: ["src/rcheevos/rc_libretro.c"], - sources: ["include", "src/rcheevos", "src/rapi", "src/rhash"], - publicHeadersPath: "include", - cSettings: [ - .define("RC_DISABLE_LUA") - ]), - ] -) diff --git a/3rdparty/rcheevos/include/rc_api_editor.h b/3rdparty/rcheevos/include/rc_api_editor.h index 61e2fd4edd..a0806932ef 100644 --- a/3rdparty/rcheevos/include/rc_api_editor.h +++ b/3rdparty/rcheevos/include/rc_api_editor.h @@ -84,6 +84,55 @@ RC_EXPORT int RC_CCONV rc_api_process_update_code_note_response(rc_api_update_co RC_EXPORT int RC_CCONV rc_api_process_update_code_note_server_response(rc_api_update_code_note_response_t* response, const rc_api_server_response_t* server_response); RC_EXPORT void RC_CCONV rc_api_destroy_update_code_note_response(rc_api_update_code_note_response_t* response); +/* --- Update Code Notes --- */ + +typedef struct rc_api_update_code_note_entry_t { + /* The address the note is associated to */ + uint32_t address; + /* The contents of the note (NULL or empty to delete a note) */ + const char* note; +} +rc_api_update_code_note_entry_t; + +/** + * API parameters for an update code notes request. + */ +typedef struct rc_api_update_code_notes_request_t { + /* The username of the developer */ + const char* username; + /* The API token from the login request */ + const char* api_token; + /* The unique identifier of the game */ + uint32_t game_id; + /* An array of notes to update */ + rc_api_update_code_note_entry_t* entries; + /* The number of items in the entries array */ + uint32_t num_entries; +} +rc_api_update_code_notes_request_t; + +/** + * Response data for an update code note request. + */ +typedef struct rc_api_update_code_notes_response_t { + /* The addresses that were successfully updated */ + uint32_t* updated_addresses; + /* The addresses that could not be updated due to permissions errors */ + uint32_t* access_denied_addresses; + /* The number of elements in the updated_addresses array */ + uint32_t num_updated_addresses; + /* The number of elements in the access_denied_addresses array */ + uint32_t num_access_denied_addresses; + + /* Common server-provided response information */ + rc_api_response_t response; +} +rc_api_update_code_notes_response_t; + +RC_EXPORT int RC_CCONV rc_api_init_update_code_notes_request_hosted(rc_api_request_t* request, const rc_api_update_code_notes_request_t* api_params, const rc_api_host_t* host); +RC_EXPORT int RC_CCONV rc_api_process_update_code_notes_server_response(rc_api_update_code_notes_response_t* response, const rc_api_server_response_t* server_response); +RC_EXPORT void RC_CCONV rc_api_destroy_update_code_notes_response(rc_api_update_code_notes_response_t* response); + /* --- Update Achievement --- */ /** diff --git a/3rdparty/rcheevos/include/rc_api_info.h b/3rdparty/rcheevos/include/rc_api_info.h index 0051fe23e5..1894cce123 100644 --- a/3rdparty/rcheevos/include/rc_api_info.h +++ b/3rdparty/rcheevos/include/rc_api_info.h @@ -164,8 +164,26 @@ rc_api_fetch_games_list_request_t; typedef struct rc_api_game_list_entry_t { /* The unique identifier of the game */ uint32_t id; + /* The number of achievements in the game */ + uint32_t num_achievements; + /* The number of leaderboards in the game */ + uint32_t num_leaderboards; + /* The number of points in the game */ + uint32_t points; /* The name of the game */ const char* name; + /* The image name for the game badge */ + const char* image_name; + /* The URL for the game badge image */ + const char* image_url; + /* An array of supported hashes */ + const char** supported_hashes; + /* An array of unsupported hashes */ + const char** unsupported_hashes; + /* The number of items in the supported_hashes array */ + uint32_t num_supported_hashes; + /* The number of items in the unsupported_hashes array */ + uint32_t num_unsupported_hashes; } rc_api_game_list_entry_t; diff --git a/3rdparty/rcheevos/include/rc_api_user.h b/3rdparty/rcheevos/include/rc_api_user.h index c977da9564..f604188aa8 100644 --- a/3rdparty/rcheevos/include/rc_api_user.h +++ b/3rdparty/rcheevos/include/rc_api_user.h @@ -42,6 +42,8 @@ typedef struct rc_api_login_response_t { const char* display_name; /* A URL to the user's avatar image */ const char* avatar_url; + /* When the avatar was last updated */ + time_t avatar_last_updated; /* Common server-provided response information */ rc_api_response_t response; @@ -187,6 +189,8 @@ typedef struct rc_api_followed_user_t { const char* display_name; /* A URL to the user's avatar image */ const char* avatar_url; + /* When the avatar was last updated */ + time_t avatar_last_updated; /* The player's last registered activity */ rc_api_followed_user_activity_t recent_activity; /* The current score of the player */ diff --git a/3rdparty/rcheevos/include/rc_client.h b/3rdparty/rcheevos/include/rc_client.h index 8dde30ab61..dd1538c077 100644 --- a/3rdparty/rcheevos/include/rc_client.h +++ b/3rdparty/rcheevos/include/rc_client.h @@ -190,6 +190,8 @@ typedef struct rc_client_user_t { uint32_t num_unread_messages; /* minimum version: 12.0 */ const char* avatar_url; + /* minimum version: 12.4 */ + time_t avatar_last_updated; } rc_client_user_t; /** @@ -450,6 +452,61 @@ RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_fetch_game_titles( */ RC_EXPORT void RC_CCONV rc_client_destroy_game_title_list(rc_client_game_title_list_t* list); +/*****************************************************************************\ +| Fetch Games List | +\*****************************************************************************/ + +typedef struct rc_client_game_list_entry_t { + /* The unique identifier of the game */ + uint32_t id; + /* The number of achievements in the game */ + uint32_t num_achievements; + /* The number of leaderboards in the game */ + uint32_t num_leaderboards; + /* The number of points in the game */ + uint32_t points; + /* The name of the game */ + const char* name; + /* The image name for the game badge */ + const char* image_name; + /* The URL for the game badge image */ + const char* image_url; + /* An array of supported hashes */ + const char** supported_hashes; + /* An array of unsupported hashes */ + const char** unsupported_hashes; + /* The number of items in the supported_hashes array */ + uint32_t num_supported_hashes; + /* The number of items in the unsupported_hashes array */ + uint32_t num_unsupported_hashes; +} rc_client_game_list_entry_t; + +typedef struct rc_client_game_list_t { + rc_client_game_list_entry_t* entries; + uint32_t num_entries; +} rc_client_game_list_t; + +/** + * Callback that is fired when a games list request completes. list may be null if the query failed. + */ +typedef void(RC_CCONV* rc_client_fetch_game_list_callback_t)(int result, const char* error_message, + rc_client_game_list_t* list, rc_client_t* client, + void* callback_userdata); + +/** + * Starts an asynchronous request for all games for the given console. + * This request returns the game metadata and supported/unsupported hashes for each game on the console, + * described by the rc_client_game_list_entry_t struct. After use, the list should be freed by calling + * the rc_client_destroy_game_list() function. + */ +RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_fetch_game_list( + rc_client_t* client, uint32_t console_id, rc_client_fetch_game_list_callback_t callback, void* callback_userdata); + +/** + * Destroys a previously-allocated result from the rc_client_begin_fetch_game_list() callback. + */ +RC_EXPORT void RC_CCONV rc_client_destroy_game_list(rc_client_game_list_t* list); + /*****************************************************************************\ | Achievements | \*****************************************************************************/ diff --git a/3rdparty/rcheevos/include/rc_error.h b/3rdparty/rcheevos/include/rc_error.h index 5d253442da..197bcea702 100644 --- a/3rdparty/rcheevos/include/rc_error.h +++ b/3rdparty/rcheevos/include/rc_error.h @@ -49,7 +49,8 @@ enum { RC_INSUFFICIENT_BUFFER = -36, RC_INVALID_VARIABLE_NAME = -37, RC_UNKNOWN_VARIABLE_NAME = -38, - RC_NOT_FOUND = -39 + RC_NOT_FOUND = -39, + RC_INVALID_VALUE = -40 }; RC_EXPORT const char* RC_CCONV rc_error_str(int ret); diff --git a/3rdparty/rcheevos/src/rapi/rc_api_common.c b/3rdparty/rcheevos/src/rapi/rc_api_common.c index 3a261fba46..75db8f41f0 100644 --- a/3rdparty/rcheevos/src/rapi/rc_api_common.c +++ b/3rdparty/rcheevos/src/rapi/rc_api_common.c @@ -360,7 +360,7 @@ int rc_json_parse_server_response(rc_api_response_t* response, const rc_api_serv if (server_response->http_status_code == RC_API_SERVER_RESPONSE_CLIENT_ERROR || server_response->http_status_code == RC_API_SERVER_RESPONSE_RETRYABLE_CLIENT_ERROR) { /* client provided error message is passed as the response body */ - response->error_message = server_response->body; + response->error_message = rc_buffer_strncpy(&response->buffer, server_response->body, server_response->body_length); response->succeeded = 0; return RC_NO_RESPONSE; } @@ -467,17 +467,12 @@ static int rc_json_get_array_entry_value(rc_json_field_t* field, rc_json_iterato return 1; } -int rc_json_get_required_unum_array(uint32_t** entries, uint32_t* num_entries, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name) { - rc_json_iterator_t iterator; - rc_json_field_t array; - rc_json_field_t value; - uint32_t* entry; - - memset(&array, 0, sizeof(array)); - if (!rc_json_get_required_array(num_entries, &array, response, field, field_name)) - return RC_MISSING_VALUE; - +static int rc_json_get_unum_array(uint32_t** entries, uint32_t* num_entries, rc_api_response_t* response, const rc_json_field_t* array, const char* field_name) { if (*num_entries) { + rc_json_iterator_t iterator; + rc_json_field_t value; + uint32_t* entry; + *entries = (uint32_t*)rc_buffer_alloc(&response->buffer, *num_entries * sizeof(uint32_t)); if (!*entries) return RC_OUT_OF_MEMORY; @@ -485,8 +480,8 @@ int rc_json_get_required_unum_array(uint32_t** entries, uint32_t* num_entries, r value.name = field_name; memset(&iterator, 0, sizeof(iterator)); - iterator.json = array.value_start; - iterator.end = array.value_end; + iterator.json = array->value_start; + iterator.end = array->value_end; entry = *entries; while (rc_json_get_array_entry_value(&value, &iterator)) { @@ -503,6 +498,77 @@ int rc_json_get_required_unum_array(uint32_t** entries, uint32_t* num_entries, r return RC_OK; } +int rc_json_get_required_unum_array(uint32_t** entries, uint32_t* num_entries, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name) { + rc_json_field_t array; + memset(&array, 0, sizeof(array)); + + if (!rc_json_get_required_array(num_entries, &array, response, field, field_name)) + return RC_MISSING_VALUE; + + return rc_json_get_unum_array(entries, num_entries, response, &array, field_name); +} + +int rc_json_get_optional_unum_array(uint32_t** entries, uint32_t* num_entries, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name) { + rc_json_field_t array; + memset(&array, 0, sizeof(array)); + + if (!rc_json_get_optional_array(num_entries, &array, field, field_name)) + *num_entries = 0; + + return rc_json_get_unum_array(entries, num_entries, response, &array, field_name); +} + +static int rc_json_get_string_array(const char*** entries, uint32_t* num_entries, rc_api_response_t* response, const rc_json_field_t* array, const char* field_name) { + if (*num_entries) { + rc_json_iterator_t iterator; + rc_json_field_t value; + const char** entry; + + *entries = (const char**)rc_buffer_alloc(&response->buffer, *num_entries * sizeof(const char*)); + if (!*entries) + return RC_OUT_OF_MEMORY; + + value.name = field_name; + + memset(&iterator, 0, sizeof(iterator)); + iterator.json = array->value_start; + iterator.end = array->value_end; + + entry = *entries; + while (rc_json_get_array_entry_value(&value, &iterator)) { + if (!rc_json_get_string(entry, &response->buffer, &value, field_name)) + return RC_MISSING_VALUE; + + ++entry; + } + } + else { + *entries = NULL; + } + + return RC_OK; +} + +int rc_json_get_required_string_array(const char*** entries, uint32_t* num_entries, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name) { + rc_json_field_t array; + + memset(&array, 0, sizeof(array)); + if (!rc_json_get_required_array(num_entries, &array, response, field, field_name)) + return RC_MISSING_VALUE; + + return rc_json_get_string_array(entries, num_entries, response, &array, field_name); +} + +int rc_json_get_optional_string_array(const char*** entries, uint32_t* num_entries, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name) { + rc_json_field_t array; + + memset(&array, 0, sizeof(array)); + if (!rc_json_get_optional_array(num_entries, &array, field, field_name)) + *num_entries = 0; + + return rc_json_get_string_array(entries, num_entries, response, &array, field_name); +} + int rc_json_get_required_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) { #ifndef NDEBUG if (strcmp(field->name, field_name) != 0) @@ -623,6 +689,12 @@ int rc_json_get_string(const char** out, rc_buffer_t* buffer, const rc_json_fiel return 0; } + if (len == 0) { + /* simple optimization for empty string - don't allocate space */ + *out = ""; + return 1; + } + if (len == 4 && memcmp(field->value_start, "null", 4) == 0) { *out = NULL; return 1; @@ -658,7 +730,11 @@ int rc_json_get_string(const char** out, rc_buffer_t* buffer, const rc_json_fiel if (*src == 'u') { /* unicode character */ - uint32_t ucs32_char = rc_json_decode_hex4(src + 1); + uint32_t ucs32_char; + if (src + 5 >= field->value_end) /* incomplete unicode character */ + return 0; + + ucs32_char = rc_json_decode_hex4(src + 1); src += 5; if (ucs32_char >= 0xD800 && ucs32_char < 0xE000) { @@ -710,7 +786,7 @@ int rc_json_get_string(const char** out, rc_buffer_t* buffer, const rc_json_fiel int rc_json_field_string_matches(const rc_json_field_t* field, const char* text) { int is_quoted = 0; const char* ptr = field->value_start; - if (!ptr) + if (!ptr || !text) return 0; if (*ptr == '"') { @@ -924,7 +1000,7 @@ int rc_json_get_datetime(time_t* out, const rc_json_field_t* field, const char* (void)field_name; #endif - if (*field->value_start == '\"') { + if (field->value_start && *field->value_start == '\"') { memset(&tm, 0, sizeof(tm)); if (sscanf_s(field->value_start + 1, "%d-%d-%d %d:%d:%d", /* DB format "2013-10-20 22:12:21" */ &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) == 6 || @@ -963,6 +1039,61 @@ int rc_json_get_required_datetime(time_t* out, rc_api_response_t* response, cons return rc_json_missing_field(response, field); } +int rc_json_get_timet(time_t* out, const rc_json_field_t* field, const char* field_name) +{ + const char* src = field->value_start; + int64_t value = 0; + int negative = 0; + +#ifndef NDEBUG + if (strcmp(field->name, field_name) != 0) + return 0; +#else + (void)field_name; +#endif + + if (!src) { + *out = 0; + return 0; + } + + /* assert: string contains only numerals and an optional sign per rc_json_parse_field */ + if (*src == '-') { + negative = 1; + ++src; + } else if (*src == '+') { + ++src; + } else if (*src < '0' || *src > '9') { + *out = 0; + return 0; + } + + while (src < field->value_end && *src != '.') { + value *= 10; + value += *src - '0'; + ++src; + } + + if (negative) + *out = (time_t)-value; + else + *out = (time_t)value; + + return 1; +} + +void rc_json_get_optional_timet(time_t* out, const rc_json_field_t* field, const char* field_name, time_t default_value) { + if (!rc_json_get_timet(out, field, field_name)) + *out = default_value; +} + +int rc_json_get_required_timet(time_t* out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name) { + if (rc_json_get_timet(out, field, field_name)) + return 1; + + return rc_json_missing_field(response, field); +} + int rc_json_get_bool(int* out, const rc_json_field_t* field, const char* field_name) { const char* src = field->value_start; @@ -1004,23 +1135,25 @@ int rc_json_get_required_bool(int* out, rc_api_response_t* response, const rc_js } void rc_json_extract_filename(rc_json_field_t* field) { - if (field->value_end) { + if (field->value_end && field->value_end > field->value_start) { const char* str = field->value_end; + if (str[-1] == '"') { + /* ignore trailing quote */ + field->value_end = --str; - /* remove the extension */ - while (str > field->value_start && str[-1] != '/') { - --str; - if (*str == '.') { - field->value_end = str; - break; + while (str > field->value_start) { + const char c = *(--str); + if (c == '.') { + /* found an extension. remove it */ + field->value_end = str; + } + else if (c == '/' || c == '"') { + /* found path separator or opening quote. stop */ + field->value_start = str + 1; + break; + } } } - - /* find the path separator */ - while (str > field->value_start && str[-1] != '/') - --str; - - field->value_start = str; } } @@ -1296,12 +1429,15 @@ int rc_api_init_fetch_image_request(rc_api_request_t* request, const rc_api_fetc int rc_api_init_fetch_image_request_hosted(rc_api_request_t* request, const rc_api_fetch_image_request_t* api_params, const rc_api_host_t* host) { rc_api_url_builder_t builder; + if (!api_params->image_name || !api_params->image_name[0]) + return RC_INVALID_STATE; + rc_buffer_init(&request->buffer); rc_url_builder_init(&builder, &request->buffer, 64); if (host && host->media_host) { /* custom media host provided */ - if (!strstr(host->host, "://")) + if (!strstr(host->media_host, "://")) rc_url_builder_append(&builder, "http://", 7); rc_url_builder_append(&builder, host->media_host, strlen(host->media_host)); } @@ -1358,6 +1494,7 @@ int rc_api_init_fetch_image_request_hosted(rc_api_request_t* request, const rc_a request->url = rc_url_builder_finalize(&builder); request->post_data = NULL; + request->content_type = NULL; return builder.result; } diff --git a/3rdparty/rcheevos/src/rapi/rc_api_common.h b/3rdparty/rcheevos/src/rapi/rc_api_common.h index 78e10d49b9..06a7485da3 100644 --- a/3rdparty/rcheevos/src/rapi/rc_api_common.h +++ b/3rdparty/rcheevos/src/rapi/rc_api_common.h @@ -49,21 +49,27 @@ int rc_json_get_num(int32_t* out, const rc_json_field_t* field, const char* fiel 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_timet(time_t* 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); +void rc_json_get_optional_timet(time_t* out, const rc_json_field_t* field, const char* field_name, time_t default_value); 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_timet(time_t* 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); int rc_json_get_required_unum_array(uint32_t** entries, uint32_t* num_entries, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name); +int rc_json_get_optional_unum_array(uint32_t** entries, uint32_t* num_entries, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name); +int rc_json_get_required_string_array(const char*** entries, uint32_t* num_entries, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name); +int rc_json_get_optional_string_array(const char*** entries, uint32_t* num_entries, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name); int rc_json_get_required_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_array_entry_object(rc_json_field_t* fields, size_t field_count, rc_json_iterator_t* iterator); int rc_json_get_next_object_field(rc_json_iterator_t* iterator, rc_json_field_t* field); diff --git a/3rdparty/rcheevos/src/rapi/rc_api_editor.c b/3rdparty/rcheevos/src/rapi/rc_api_editor.c index 775f73de8e..bb80586c2c 100644 --- a/3rdparty/rcheevos/src/rapi/rc_api_editor.c +++ b/3rdparty/rcheevos/src/rapi/rc_api_editor.c @@ -181,11 +181,6 @@ int rc_api_process_update_code_note_server_response(rc_api_update_code_note_resp rc_json_field_t fields[] = { RC_JSON_NEW_FIELD("Success"), RC_JSON_NEW_FIELD("Error") - /* unused fields - RC_JSON_NEW_FIELD("GameID"), - RC_JSON_NEW_FIELD("Address"), - RC_JSON_NEW_FIELD("Note") - */ }; memset(response, 0, sizeof(*response)); @@ -202,6 +197,111 @@ void rc_api_destroy_update_code_note_response(rc_api_update_code_note_response_t rc_buffer_destroy(&response->response.buffer); } +/* --- Update Code Notes --- */ + +int rc_api_init_update_code_notes_request_hosted(rc_api_request_t* request, + const rc_api_update_code_notes_request_t* api_params, + const rc_api_host_t* host) { + rc_api_update_code_note_entry_t* entry; + rc_api_update_code_note_entry_t* stop; + rc_api_url_builder_t builder; + char buffer[512]; + + rc_api_url_build_dorequest_url(request, host); + + if (api_params->game_id == 0 || api_params->num_entries == 0) + return RC_INVALID_STATE; + + rc_url_builder_init(&builder, &request->buffer, 128); + if (!rc_api_url_build_dorequest(&builder, "submitcodenotes", api_params->username, api_params->api_token)) + return builder.result; + + rc_url_builder_append_unum_param(&builder, "g", api_params->game_id); + rc_url_builder_append_str_param(&builder, "n", ""); + + entry = api_params->entries; + stop = entry + api_params->num_entries; + for (; entry < stop; ++entry) { + int chars = snprintf(buffer, sizeof(buffer), "%u", entry->address); + rc_url_builder_append(&builder, buffer, chars); + rc_url_builder_append(&builder, ":", 1); + + if (entry->note) { + const char* src = entry->note; + char* dst = buffer; + const char* dst_stop = buffer + sizeof(buffer) - 8; + do { + while (*src && dst < dst_stop) { + switch (*src) { + case '\n': /* escape newline as newlines separate notes. normalize to windows style line endings */ + *dst++ = '\\'; + *dst++ = 'r'; + *dst++ = '\\'; + *dst++ = 'n'; + ++src; + break; + case '\r': /* swallow line feed - one will be explicitly generated with the \n */ + ++src; + break; + case '\\': /* escape backslashes so raw backslashes don't grab the next character */ + *dst++ = '\\'; + *dst++ = '\\'; + ++src; + break; + default: + *dst++ = *src++; + break; + } + } + + /* terminate and reset output pointer to front of buffer */ + *dst = '\0'; + dst = buffer; + + /* url encode the normalized/escaped note */ + rc_url_builder_append_encoded_str(&builder, buffer); + } while (*src); + } + + rc_url_builder_append_encoded_str(&builder, "\n"); + } + + request->post_data = rc_url_builder_finalize(&builder); + request->content_type = RC_CONTENT_TYPE_URLENCODED; + + return builder.result; +} + +int rc_api_process_update_code_notes_server_response(rc_api_update_code_notes_response_t* response, const rc_api_server_response_t* server_response) { + int result; + + rc_json_field_t fields[] = { + RC_JSON_NEW_FIELD("Success"), + RC_JSON_NEW_FIELD("Error"), + RC_JSON_NEW_FIELD("Code"), + RC_JSON_NEW_FIELD("SuccessfulAddresses"), + RC_JSON_NEW_FIELD("AccessDeniedAddresses") + }; + + memset(response, 0, sizeof(*response)); + rc_buffer_init(&response->response.buffer); + + result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); + if (result == RC_OK || result == RC_ACCESS_DENIED) { + int result2 = rc_json_get_required_unum_array(&response->updated_addresses, &response->num_updated_addresses, &response->response, &fields[3], "SuccessfulAddresses"); + if (result2 != RC_OK) + return RC_MISSING_VALUE; + + rc_json_get_optional_unum_array(&response->access_denied_addresses, &response->num_access_denied_addresses, &response->response, &fields[4], "AccessDeniedAddresses"); + } + + return result; +} + +void rc_api_destroy_update_code_notes_response(rc_api_update_code_notes_response_t* response) { + rc_buffer_destroy(&response->response.buffer); +} + /* --- Update Achievement --- */ static const char* rc_type_string(uint32_t type) { @@ -456,7 +556,7 @@ int rc_api_process_update_rich_presence_server_response(rc_api_update_rich_prese rc_json_field_t fields[] = { RC_JSON_NEW_FIELD("Success"), RC_JSON_NEW_FIELD("Error"), - RC_JSON_NEW_FIELD("Code"), + RC_JSON_NEW_FIELD("Code") }; memset(response, 0, sizeof(*response)); diff --git a/3rdparty/rcheevos/src/rapi/rc_api_info.c b/3rdparty/rcheevos/src/rapi/rc_api_info.c index 5ab9709287..327a6dff3f 100644 --- a/3rdparty/rcheevos/src/rapi/rc_api_info.c +++ b/3rdparty/rcheevos/src/rapi/rc_api_info.c @@ -56,7 +56,6 @@ int rc_api_process_fetch_achievement_info_server_response(rc_api_fetch_achieveme rc_api_achievement_awarded_entry_t* entry; rc_json_field_t array_field; rc_json_iterator_t iterator; - uint32_t timet; int result; rc_json_field_t fields[] = { @@ -120,9 +119,8 @@ int rc_api_process_fetch_achievement_info_server_response(rc_api_fetch_achieveme if (!rc_json_get_required_string(&entry->username, &response->response, &entry_fields[0], "User")) return RC_MISSING_VALUE; - if (!rc_json_get_required_unum(&timet, &response->response, &entry_fields[1], "DateAwarded")) + if (!rc_json_get_required_timet(&entry->awarded, &response->response, &entry_fields[1], "DateAwarded")) return RC_MISSING_VALUE; - entry->awarded = (time_t)timet; rc_json_get_optional_string(&entry->avatar_url, &response->response, &entry_fields[2], "AvatarUrl", NULL); if (!entry->avatar_url) @@ -185,7 +183,6 @@ int rc_api_process_fetch_leaderboard_info_server_response(rc_api_fetch_leaderboa rc_api_lboard_info_entry_t* entry; rc_json_field_t array_field; rc_json_iterator_t iterator; - uint32_t timet; int result; size_t len; char format[16]; @@ -289,9 +286,8 @@ int rc_api_process_fetch_leaderboard_info_server_response(rc_api_fetch_leaderboa if (!rc_json_get_required_num(&entry->score, &response->response, &entry_fields[3], "Score")) return RC_MISSING_VALUE; - if (!rc_json_get_required_unum(&timet, &response->response, &entry_fields[4], "DateSubmitted")) + if (!rc_json_get_required_timet(&entry->submitted, &response->response, &entry_fields[4], "DateSubmitted")) return RC_MISSING_VALUE; - entry->submitted = (time_t)timet; rc_json_get_optional_string(&entry->avatar_url, &response->response, &entry_fields[5], "AvatarUrl", NULL); if (!entry->avatar_url) @@ -325,8 +321,8 @@ int rc_api_init_fetch_games_list_request_hosted(rc_api_request_t* request, return RC_INVALID_STATE; rc_url_builder_init(&builder, &request->buffer, 48); - rc_url_builder_append_str_param(&builder, "r", "gameslist"); - rc_url_builder_append_unum_param(&builder, "c", api_params->console_id); + rc_url_builder_append_str_param(&builder, "r", "systemgames"); + rc_url_builder_append_unum_param(&builder, "s", api_params->console_id); request->post_data = rc_url_builder_finalize(&builder); request->content_type = RC_CONTENT_TYPE_URLENCODED; @@ -347,9 +343,8 @@ int rc_api_process_fetch_games_list_response(rc_api_fetch_games_list_response_t* int rc_api_process_fetch_games_list_server_response(rc_api_fetch_games_list_response_t* response, const rc_api_server_response_t* server_response) { rc_api_game_list_entry_t* entry; rc_json_iterator_t iterator; - rc_json_field_t field; + rc_json_field_t array_field; int result; - char* end; rc_json_field_t fields[] = { RC_JSON_NEW_FIELD("Success"), @@ -357,6 +352,18 @@ int rc_api_process_fetch_games_list_server_response(rc_api_fetch_games_list_resp RC_JSON_NEW_FIELD("Response") }; + rc_json_field_t game_fields[] = { + RC_JSON_NEW_FIELD("ID"), + RC_JSON_NEW_FIELD("Title"), + RC_JSON_NEW_FIELD("ImageIcon"), + RC_JSON_NEW_FIELD("ImageUrl"), + RC_JSON_NEW_FIELD("NumAchievements"), + RC_JSON_NEW_FIELD("NumLeaderboards"), + RC_JSON_NEW_FIELD("Points"), + RC_JSON_NEW_FIELD("SupportedHashes"), /* array */ + RC_JSON_NEW_FIELD("UnsupportedHashes"), /* array */ + }; + memset(response, 0, sizeof(*response)); rc_buffer_init(&response->response.buffer); @@ -364,32 +371,50 @@ int rc_api_process_fetch_games_list_server_response(rc_api_fetch_games_list_resp if (result != RC_OK) return result; - if (!fields[2].value_start) { - /* call rc_json_get_required_object to generate the error message */ - rc_json_get_required_object(NULL, 0, &response->response, &fields[2], "Response"); + if (!rc_json_get_required_array(&response->num_entries, &array_field, &response->response, &fields[2], "Response")) return RC_MISSING_VALUE; - } - response->num_entries = fields[2].array_size; - rc_buffer_reserve(&response->response.buffer, response->num_entries * (32 + sizeof(rc_api_game_list_entry_t))); + if (response->num_entries) { + /* 8=image_name, 32=title, 64=image_url, 32=one hash */ + rc_buffer_reserve(&response->response.buffer, response->num_entries * (8 + 32 + 64 + 32 + sizeof(rc_api_game_list_entry_t))); - response->entries = (rc_api_game_list_entry_t*)rc_buffer_alloc(&response->response.buffer, response->num_entries * sizeof(rc_api_game_list_entry_t)); - if (!response->entries) - return RC_OUT_OF_MEMORY; + response->entries = (rc_api_game_list_entry_t*)rc_buffer_alloc(&response->response.buffer, response->num_entries * sizeof(rc_api_game_list_entry_t)); + if (!response->entries) + return RC_OUT_OF_MEMORY; - memset(&iterator, 0, sizeof(iterator)); - iterator.json = fields[2].value_start; - iterator.end = fields[2].value_end; + memset(&iterator, 0, sizeof(iterator)); + iterator.json = array_field.value_start; + iterator.end = array_field.value_end; - entry = response->entries; - while (rc_json_get_next_object_field(&iterator, &field)) { - entry->id = strtol(field.name, &end, 10); + entry = response->entries; + while (rc_json_get_array_entry_object(game_fields, sizeof(game_fields) / sizeof(game_fields[0]), &iterator)) { + if (!rc_json_get_required_unum(&entry->id, &response->response, &game_fields[0], "ID")) + return RC_MISSING_VALUE; + if (!rc_json_get_required_string(&entry->name, &response->response, &game_fields[1], "Title")) + return RC_MISSING_VALUE; + if (!rc_json_get_required_unum(&entry->num_achievements, &response->response, &game_fields[4], "NumAchievements")) + return RC_MISSING_VALUE; + if (!rc_json_get_required_unum(&entry->num_leaderboards, &response->response, &game_fields[5], "NumLeaderboards")) + return RC_MISSING_VALUE; + if (!rc_json_get_required_unum(&entry->points, &response->response, &game_fields[6], "Points")) + return RC_MISSING_VALUE; - field.name = ""; - if (!rc_json_get_string(&entry->name, &response->response.buffer, &field, "")) - return RC_MISSING_VALUE; + /* ImageIcon will be '/Images/0123456.png' - only return the '0123456' */ + rc_json_extract_filename(&game_fields[2]); + if (!rc_json_get_required_string(&entry->image_name, &response->response, &game_fields[2], "ImageIcon")) + return RC_MISSING_VALUE; + if (!rc_json_get_required_string(&entry->image_url, &response->response, &game_fields[3], "ImageUrl")) + return RC_MISSING_VALUE; - ++entry; + result = rc_json_get_required_string_array(&entry->supported_hashes, &entry->num_supported_hashes, &response->response, &game_fields[7], "SupportedHashes"); + if (result != RC_OK) + return result; + result = rc_json_get_optional_string_array(&entry->unsupported_hashes, &entry->num_unsupported_hashes, &response->response, &game_fields[8], "UnsupportedHashes"); + if (result != RC_OK) + return result; + + ++entry; + } } return RC_OK; @@ -484,7 +509,7 @@ int rc_api_process_fetch_game_titles_server_response(rc_api_fetch_game_titles_re return RC_MISSING_VALUE; rc_json_get_optional_string(&entry->image_url, &response->response, &entry_fields[3], "ImageUrl", ""); - if (!entry->image_url[0]) + if (!entry->image_url || !entry->image_url[0]) entry->image_url = rc_api_build_avatar_url(&response->response.buffer, RC_IMAGE_TYPE_GAME, entry->image_name); ++entry; diff --git a/3rdparty/rcheevos/src/rapi/rc_api_runtime.c b/3rdparty/rcheevos/src/rapi/rc_api_runtime.c index 99a001db2f..afecabbac9 100644 --- a/3rdparty/rcheevos/src/rapi/rc_api_runtime.c +++ b/3rdparty/rcheevos/src/rapi/rc_api_runtime.c @@ -113,7 +113,6 @@ static int rc_api_process_fetch_game_data_achievements(rc_api_response_t* respon const char* last_author = ""; const char* last_author_field = ""; size_t last_author_len = 0; - uint32_t timet; size_t len; rc_json_field_t achievement_fields[] = { @@ -155,11 +154,11 @@ static int rc_api_process_fetch_game_data_achievements(rc_api_response_t* respon return RC_MISSING_VALUE; rc_json_get_optional_string(&achievement->badge_url, response, &achievement_fields[13], "BadgeURL", ""); - if (!achievement->badge_url[0]) + if (!achievement->badge_url || !achievement->badge_url[0]) achievement->badge_url = rc_api_build_avatar_url(&response->buffer, RC_IMAGE_TYPE_ACHIEVEMENT, achievement->badge_name); rc_json_get_optional_string(&achievement->badge_locked_url, response, &achievement_fields[14], "BadgeLockedURL", ""); - if (!achievement->badge_locked_url[0]) + if (!achievement->badge_locked_url || !achievement->badge_locked_url[0]) achievement->badge_locked_url = rc_api_build_avatar_url(&response->buffer, RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED, achievement->badge_name); len = achievement_fields[6].value_end - achievement_fields[6].value_start; @@ -181,12 +180,10 @@ static int rc_api_process_fetch_game_data_achievements(rc_api_response_t* respon } } - if (!rc_json_get_required_unum(&timet, response, &achievement_fields[8], "Created")) + if (!rc_json_get_required_timet(&achievement->created, response, &achievement_fields[8], "Created")) return RC_MISSING_VALUE; - achievement->created = (time_t)timet; - if (!rc_json_get_required_unum(&timet, response, &achievement_fields[9], "Modified")) + if (!rc_json_get_required_timet(&achievement->updated, response, &achievement_fields[9], "Modified")) return RC_MISSING_VALUE; - achievement->updated = (time_t)timet; if (rc_json_field_string_matches(&achievement_fields[10], "")) achievement->type = RC_ACHIEVEMENT_TYPE_STANDARD; @@ -320,7 +317,7 @@ int rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_respon rc_json_extract_filename(&patchdata_fields[3]); rc_json_get_optional_string(&response->image_name, &response->response, &patchdata_fields[3], "ImageIcon", ""); rc_json_get_optional_string(&response->image_url, &response->response, &patchdata_fields[4], "ImageIconURL", ""); - if (!response->image_url[0]) + if (!response->image_url || !response->image_url[0]) response->image_url = rc_api_build_avatar_url(&response->response.buffer, RC_IMAGE_TYPE_GAME, response->image_name); /* estimate the amount of space necessary to store the rich presence script, achievements, and leaderboards. diff --git a/3rdparty/rcheevos/src/rapi/rc_api_user.c b/3rdparty/rcheevos/src/rapi/rc_api_user.c index 323756cfd1..9970e56c18 100644 --- a/3rdparty/rcheevos/src/rapi/rc_api_user.c +++ b/3rdparty/rcheevos/src/rapi/rc_api_user.c @@ -62,7 +62,8 @@ int rc_api_process_login_server_response(rc_api_login_response_t* response, cons RC_JSON_NEW_FIELD("Score"), RC_JSON_NEW_FIELD("SoftcoreScore"), RC_JSON_NEW_FIELD("Messages"), - RC_JSON_NEW_FIELD("AvatarUrl") + RC_JSON_NEW_FIELD("AvatarUrl"), + RC_JSON_NEW_FIELD("AvatarUpdatedAt") }; memset(response, 0, sizeof(*response)); @@ -90,6 +91,8 @@ int rc_api_process_login_server_response(rc_api_login_response_t* response, cons if (!response->avatar_url) response->avatar_url = rc_api_build_avatar_url(&response->response.buffer, RC_IMAGE_TYPE_USER, response->username); + rc_json_get_optional_timet(&response->avatar_last_updated, &fields[9], "AvatarUpdatedAt", 0); + return RC_OK; } @@ -145,7 +148,6 @@ int rc_api_process_start_session_server_response(rc_api_start_session_response_t rc_api_unlock_entry_t* unlock; rc_json_field_t array_field; rc_json_iterator_t iterator; - uint32_t timet; int result; rc_json_field_t fields[] = { @@ -181,9 +183,8 @@ int rc_api_process_start_session_server_response(rc_api_start_session_response_t while (rc_json_get_array_entry_object(unlock_entry_fields, sizeof(unlock_entry_fields) / sizeof(unlock_entry_fields[0]), &iterator)) { if (!rc_json_get_required_unum(&unlock->achievement_id, &response->response, &unlock_entry_fields[0], "ID")) return RC_MISSING_VALUE; - if (!rc_json_get_required_unum(&timet, &response->response, &unlock_entry_fields[1], "When")) + if (!rc_json_get_required_timet(&unlock->when, &response->response, &unlock_entry_fields[1], "When")) return RC_MISSING_VALUE; - unlock->when = (time_t)timet; ++unlock; } @@ -202,16 +203,14 @@ int rc_api_process_start_session_server_response(rc_api_start_session_response_t while (rc_json_get_array_entry_object(unlock_entry_fields, sizeof(unlock_entry_fields) / sizeof(unlock_entry_fields[0]), &iterator)) { if (!rc_json_get_required_unum(&unlock->achievement_id, &response->response, &unlock_entry_fields[0], "ID")) return RC_MISSING_VALUE; - if (!rc_json_get_required_unum(&timet, &response->response, &unlock_entry_fields[1], "When")) + if (!rc_json_get_required_timet(&unlock->when, &response->response, &unlock_entry_fields[1], "When")) return RC_MISSING_VALUE; - unlock->when = (time_t)timet; ++unlock; } } - rc_json_get_optional_unum(&timet, &fields[4], "ServerNow", 0); - response->server_now = (time_t)timet; + rc_json_get_optional_timet(&response->server_now, &fields[4], "ServerNow", 0); return RC_OK; } @@ -307,11 +306,11 @@ int rc_api_process_fetch_followed_users_server_response(rc_api_fetch_followed_us rc_json_field_t array_field; rc_json_iterator_t iterator; rc_api_followed_user_t* user; - uint32_t timet; int result; rc_json_field_t fields[] = { RC_JSON_NEW_FIELD("Success"), RC_JSON_NEW_FIELD("Error"), + RC_JSON_NEW_FIELD("Code"), RC_JSON_NEW_FIELD("Friends") }; @@ -324,6 +323,7 @@ int rc_api_process_fetch_followed_users_server_response(rc_api_fetch_followed_us RC_JSON_NEW_FIELD("LastGameId"), RC_JSON_NEW_FIELD("LastGameTitle"), RC_JSON_NEW_FIELD("LastGameIconUrl"), + RC_JSON_NEW_FIELD("AvatarUpdatedAt"), }; memset(response, 0, sizeof(*response)); @@ -333,7 +333,7 @@ int rc_api_process_fetch_followed_users_server_response(rc_api_fetch_followed_us if (result != RC_OK || !response->response.succeeded) return result; - if (!rc_json_get_required_array(&response->num_users, &array_field, &response->response, &fields[2], "Friends")) + if (!rc_json_get_required_array(&response->num_users, &array_field, &response->response, &fields[3], "Friends")) return RC_MISSING_VALUE; if (response->num_users) { @@ -354,11 +354,9 @@ int rc_api_process_fetch_followed_users_server_response(rc_api_fetch_followed_us if (!rc_json_get_required_unum(&user->score, &response->response, &followed_user_entry_fields[2], "RAPoints")) return RC_MISSING_VALUE; + rc_json_get_optional_timet(&user->avatar_last_updated, &followed_user_entry_fields[8], "AvatarUpdatedAt", 0); rc_json_get_optional_string(&user->recent_activity.description, &response->response, &followed_user_entry_fields[3], "LastSeen", NULL); - - rc_json_get_optional_unum(&timet, &followed_user_entry_fields[4], "LastSeenTime", 0); - user->recent_activity.when = (time_t)timet; - + rc_json_get_optional_timet(&user->recent_activity.when, &followed_user_entry_fields[4], "LastSeenTime", 0); rc_json_get_optional_unum(&user->recent_activity.context_id, &followed_user_entry_fields[5], "LastGameId", 0); rc_json_get_optional_string(&user->recent_activity.context, &response->response, &followed_user_entry_fields[6], "LastGameTitle", NULL); rc_json_get_optional_string(&user->recent_activity.context_image_url, &response->response, &followed_user_entry_fields[7], "LastGameIconUrl", NULL); diff --git a/3rdparty/rcheevos/src/rc_client.c b/3rdparty/rcheevos/src/rc_client.c index 08ea05340e..24de1bf14b 100644 --- a/3rdparty/rcheevos/src/rc_client.c +++ b/3rdparty/rcheevos/src/rc_client.c @@ -14,15 +14,15 @@ #include #ifdef _WIN32 -#define WIN32_LEAN_AND_MEAN -#include -#include + #define WIN32_LEAN_AND_MEAN + #include #else -#include + #include #endif #define RC_CLIENT_UNKNOWN_GAME_ID (uint32_t)-1 #define RC_CLIENT_RECENT_UNLOCK_DELAY_SECONDS (10 * 60) /* ten minutes */ +#define RC_CLIENT_ACHIEVEMENT_WARNING_ID 101000001 #define RC_MINIMUM_UNPAUSED_FRAMES 20 #define RC_PAUSE_DECAY_MULTIPLIER 4 @@ -240,7 +240,7 @@ static void rc_client_log_message_va(const rc_client_t* client, const char* form char buffer[2048]; #ifdef __STDC_SECURE_LIB__ - vsprintf_s(buffer, sizeof(buffer), format, args); + vsnprintf_s(buffer, sizeof(buffer), _TRUNCATE, format, args); #elif __STDC_VERSION__ >= 199901L /* vsnprintf requires c99 */ vsnprintf(buffer, sizeof(buffer), format, args); #else /* c89 doesn't have a size-limited vsprintf function - assume the buffer is large enough */ @@ -320,6 +320,22 @@ void rc_client_enable_logging(rc_client_t* client, int level, rc_client_message_ static rc_clock_t rc_client_clock_get_now_millisecs(const rc_client_t* client) { +#if defined(__APPLE__) && defined(__MACH__) + #ifdef CLOCK_MONOTONIC + /* clock_gettime() was added to Darwin in iOS 10.0 and macOS 10.12. + * On earlier deployment targets (like Leopard 10.5), the symbol doesn't exist + * in libSystem causing an "undefined reference to clock_gettime" linker error. + * To get the code to use the #else block below, forcibly undefine CLOCK_MONOTONIC + * when targeting earlier versions. */ + #include + #if (defined(MAC_OS_X_VERSION_MIN_REQUIRED) && MAC_OS_X_VERSION_MIN_REQUIRED < 101200) + #undef CLOCK_MONOTONIC + #elif (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < 100000) + #undef CLOCK_MONOTONIC + #endif + #endif +#endif + #if defined(CLOCK_MONOTONIC) struct timespec now; (void)client; @@ -703,6 +719,7 @@ static void rc_client_login_callback(const rc_api_server_response_t* server_resp client->user.display_name = rc_buffer_strcpy(&client->state.buffer, login_response.display_name); client->user.avatar_url = rc_buffer_strcpy(&client->state.buffer, login_response.avatar_url); + client->user.avatar_last_updated = login_response.avatar_last_updated; client->user.token = rc_buffer_strcpy(&client->state.buffer, login_response.api_token); client->user.score = login_response.score; client->user.score_softcore = login_response.score_softcore; @@ -934,6 +951,11 @@ static void rc_client_subset_get_user_game_summary(const rc_client_t* client, for (; achievement < stop; ++achievement) { switch (achievement->public_.category) { case RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE: + if (achievement->public_.id >= RC_CLIENT_ACHIEVEMENT_WARNING_ID) { + /* ignore warning achievements */ + continue; + } + ++summary->num_core_achievements; summary->points_core += achievement->public_.points; @@ -2109,10 +2131,10 @@ static void rc_client_copy_achievements(rc_client_load_state_t* load_state, else { rc_buffer_consume(buffer, (const uint8_t*)preparse.parse.buffer, (uint8_t*)preparse.parse.buffer + preparse.parse.offset); } - - rc_destroy_preparse_state(&preparse); } + rc_destroy_preparse_state(&preparse); + achievement->created_time = read->created; achievement->updated_time = read->updated; @@ -3881,6 +3903,193 @@ void rc_client_destroy_game_title_list(rc_client_game_title_list_t* list) free(list); } +/* ===== Fetch Games List ===== */ + +typedef struct rc_client_fetch_game_list_callback_data_t { + rc_client_t* client; + rc_client_fetch_game_list_callback_t callback; + void* callback_userdata; + rc_client_async_handle_t async_handle; +} rc_client_fetch_game_list_callback_data_t; + +static void rc_client_fetch_game_list_callback(const rc_api_server_response_t* server_response, void* callback_data) +{ + rc_client_fetch_game_list_callback_data_t* list_callback_data = + (rc_client_fetch_game_list_callback_data_t*)callback_data; + rc_client_t* client = list_callback_data->client; + rc_api_fetch_games_list_response_t list_response; + const char* error_message; + int result; + + result = rc_client_end_async(client, &list_callback_data->async_handle); + if (result) { + if (result != RC_CLIENT_ASYNC_DESTROYED) + RC_CLIENT_LOG_VERBOSE(client, "Fetch game list aborted"); + + free(list_callback_data); + return; + } + + result = rc_api_process_fetch_games_list_server_response(&list_response, server_response); + error_message = + rc_client_server_error_message(&result, server_response->http_status_code, &list_response.response); + if (error_message) { + RC_CLIENT_LOG_ERR_FORMATTED(client, "Fetch game list failed: %s", error_message); + list_callback_data->callback(result, error_message, NULL, client, list_callback_data->callback_userdata); + } else { + rc_client_game_list_t* list; + size_t strings_size = 0, hashes_size = 0; + const rc_api_game_list_entry_t* src; + const rc_api_game_list_entry_t* stop; + size_t list_size; + uint32_t i; + + /* calculate string buffer size */ + for (src = list_response.entries, stop = src + list_response.num_entries; src < stop; ++src) { + if (src->name) + strings_size += strlen(src->name) + 1; + if (src->image_name) + strings_size += strlen(src->image_name) + 1; + if (src->image_url) + strings_size += strlen(src->image_url) + 1; + hashes_size += src->num_supported_hashes * sizeof(const char*); + for (i = 0; i < src->num_supported_hashes; i++) + strings_size += strlen(src->supported_hashes[i]) + 1; + hashes_size += src->num_unsupported_hashes * sizeof(const char*); + for (i = 0; i < src->num_unsupported_hashes; i++) + strings_size += strlen(src->unsupported_hashes[i]) + 1; + } + + list_size = sizeof(*list) + sizeof(rc_client_game_list_entry_t) * list_response.num_entries + + sizeof(const char*) * hashes_size + strings_size; + list = (rc_client_game_list_t*)malloc(list_size); + if (!list) { + list_callback_data->callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), NULL, client, + list_callback_data->callback_userdata); + } else { + rc_client_game_list_entry_t* entry = list->entries = + (rc_client_game_list_entry_t*)((uint8_t*)list + sizeof(*list)); + const char** hash_list = + (const char**)((uint8_t*)entry + sizeof(rc_client_game_list_entry_t) * list_response.num_entries); + char* strings = (char*)((uint8_t*)hash_list + sizeof(const char*) * hashes_size); + size_t len; + + for (src = list_response.entries, stop = src + list_response.num_entries; src < stop; ++src, ++entry) { + entry->id = src->id; + entry->num_achievements = src->num_achievements; + entry->num_leaderboards = src->num_leaderboards; + entry->points = src->points; + + if (src->name) { + len = strlen(src->name) + 1; + entry->name = strings; + memcpy(strings, src->name, len); + strings += len; + } else { + entry->name = NULL; + } + + if (src->image_name) { + len = strlen(src->image_name) + 1; + entry->image_name = strings; + memcpy(strings, src->image_name, len); + strings += len; + } else { + entry->image_name = NULL; + } + + if (src->image_url) { + len = strlen(src->image_url) + 1; + entry->image_url = strings; + memcpy(strings, src->image_url, len); + strings += len; + } else { + entry->image_url = NULL; + } + + if ((entry->num_supported_hashes = src->num_supported_hashes) > 0) { + entry->supported_hashes = hash_list; + for (i = 0; i < src->num_supported_hashes; i++) { + len = strlen(src->supported_hashes[i]) + 1; + *(hash_list++) = strings; + memcpy(strings, src->supported_hashes[i], len); + strings += len; + } + } else { + entry->supported_hashes = NULL; + } + + if ((entry->num_unsupported_hashes = src->num_unsupported_hashes) > 0) { + entry->unsupported_hashes = hash_list; + for (i = 0; i < src->num_unsupported_hashes; i++) { + len = strlen(src->unsupported_hashes[i]) + 1; + *(hash_list++) = strings; + memcpy(strings, src->unsupported_hashes[i], len); + strings += len; + } + } else { + entry->unsupported_hashes = NULL; + } + } + + list->num_entries = list_response.num_entries; + + list_callback_data->callback(RC_OK, NULL, list, client, list_callback_data->callback_userdata); + } + } + + rc_api_destroy_fetch_games_list_response(&list_response); + free(list_callback_data); +} + +rc_client_async_handle_t* rc_client_begin_fetch_game_list(rc_client_t* client, uint32_t console_id, + rc_client_fetch_game_list_callback_t callback, + void* callback_userdata) +{ + rc_api_fetch_games_list_request_t api_params; + rc_client_fetch_game_list_callback_data_t* callback_data; + rc_client_async_handle_t* async_handle; + rc_api_request_t request; + int result; + const char* error_message; + + if (!client) { + callback(RC_INVALID_STATE, "client is required", NULL, client, callback_userdata); + return NULL; + } + + api_params.console_id = console_id; + result = rc_api_init_fetch_games_list_request_hosted(&request, &api_params, &client->state.host); + + if (result != RC_OK) { + error_message = rc_error_str(result); + callback(result, error_message, NULL, client, callback_userdata); + return NULL; + } + + callback_data = (rc_client_fetch_game_list_callback_data_t*)calloc(1, sizeof(*callback_data)); + if (!callback_data) { + callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), NULL, client, callback_userdata); + return NULL; + } + + callback_data->client = client; + callback_data->callback = callback; + callback_data->callback_userdata = callback_userdata; + + async_handle = &callback_data->async_handle; + rc_client_begin_async(client, async_handle); + client->callbacks.server_call(&request, rc_client_fetch_game_list_callback, callback_data, client); + rc_api_destroy_request(&request); + + return rc_client_async_handle_valid(client, async_handle) ? async_handle : NULL; +} + +void rc_client_destroy_game_list(rc_client_game_list_t* list) +{ + free(list); +} + /* ===== Achievements ===== */ static void rc_client_update_achievement_display_information(rc_client_t* client, rc_client_achievement_info_t* achievement, time_t recent_unlock_time) @@ -3928,7 +4137,7 @@ static void rc_client_update_achievement_display_information(rc_client_t* client if (!achievement->trigger->measured_as_percent) { char* ptr = achievement->public_.measured_progress; const int buffer_size = (int)sizeof(achievement->public_.measured_progress); - const int chars = rc_format_value(ptr, buffer_size, (int32_t)new_measured_value, RC_FORMAT_UNSIGNED_VALUE); + const int chars = rc_format_value(ptr, buffer_size - 1, (int32_t)new_measured_value, RC_FORMAT_UNSIGNED_VALUE); ptr[chars] = '/'; rc_format_value(ptr + chars + 1, buffer_size - chars - 1, (int32_t)achievement->trigger->measured_target, RC_FORMAT_UNSIGNED_VALUE); } @@ -4606,6 +4815,11 @@ static void rc_client_award_achievement(rc_client_t* client, rc_client_achieveme rc_mutex_unlock(&client->state.mutex); + if (achievement->public_.id >= RC_CLIENT_ACHIEVEMENT_WARNING_ID) { + RC_CLIENT_LOG_INFO_FORMATTED(client, "Unlocked warning achievement %u: %s", achievement->public_.id, achievement->public_.title); + return; + } + if (client->callbacks.can_submit_achievement_unlock && !client->callbacks.can_submit_achievement_unlock(achievement->public_.id, client)) { RC_CLIENT_LOG_INFO_FORMATTED(client, "Achievement %u unlock blocked by client", achievement->public_.id); @@ -4633,7 +4847,6 @@ static void rc_client_award_achievement(rc_client_t* client, rc_client_achieveme callback_data->client = client; callback_data->id = achievement->public_.id; callback_data->hardcore = client->state.hardcore; - callback_data->game_hash = client->game->public_.hash; callback_data->unlock_time = client->callbacks.get_time_millisecs(client); if (client->game) /* may be NULL if this gets called while unloading the game (from another thread - events are raised outside the lock) */ @@ -5810,9 +6023,6 @@ static void rc_client_update_memref_values(rc_client_t* client) { } while (modified_memref_list); } - if (client->game->runtime.richpresence && client->game->runtime.richpresence->richpresence) - rc_update_values(client->game->runtime.richpresence->richpresence->values, client->state.legacy_peek, client); - if (invalidated_memref) rc_client_update_active_achievements(client->game); } @@ -5842,6 +6052,7 @@ static void rc_client_do_frame_process_achievements(rc_client_t* client, rc_clie /* 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 && + trigger->measured_target != 0 && rc_trigger_state_active(new_state) && new_state != RC_TRIGGER_STATE_WAITING) { /* only show a popup for the achievement closest to triggering */ diff --git a/3rdparty/rcheevos/src/rc_client_external.c b/3rdparty/rcheevos/src/rc_client_external.c index c65371ddf2..88abcc9f01 100644 --- a/3rdparty/rcheevos/src/rc_client_external.c +++ b/3rdparty/rcheevos/src/rc_client_external.c @@ -43,6 +43,7 @@ static const char* rc_client_external_build_avatar_url(char buffer[], size_t buf return NULL; snprintf(buffer, buffer_size, "%s", request.url); + rc_api_destroy_request(&request); return buffer; } diff --git a/3rdparty/rcheevos/src/rc_util.c b/3rdparty/rcheevos/src/rc_util.c index cd750d2c3c..95de413dcb 100644 --- a/3rdparty/rcheevos/src/rc_util.c +++ b/3rdparty/rcheevos/src/rc_util.c @@ -121,7 +121,7 @@ char* rc_buffer_strncpy(rc_buffer_t* buffer, const char* src, size_t len) uint8_t* dst = rc_buffer_reserve(buffer, len + 1); memcpy(dst, src, len); dst[len] = '\0'; - rc_buffer_consume(buffer, dst, dst + len + 2); + rc_buffer_consume(buffer, dst, dst + len + 1); return (char*)dst; } @@ -194,6 +194,7 @@ const char* rc_error_str(int ret) case RC_INVALID_VARIABLE_NAME: return "Invalid variable name"; case RC_UNKNOWN_VARIABLE_NAME: return "Unknown variable name"; case RC_NOT_FOUND: return "Not found"; + case RC_INVALID_VALUE: return "Invalid value expression"; default: return "Unknown error"; } } diff --git a/3rdparty/rcheevos/src/rc_version.h b/3rdparty/rcheevos/src/rc_version.h index 60627ec694..7eaa32a862 100644 --- a/3rdparty/rcheevos/src/rc_version.h +++ b/3rdparty/rcheevos/src/rc_version.h @@ -8,7 +8,7 @@ RC_BEGIN_C_DECLS #define RCHEEVOS_VERSION_MAJOR 12 -#define RCHEEVOS_VERSION_MINOR 3 +#define RCHEEVOS_VERSION_MINOR 4 #define RCHEEVOS_VERSION_PATCH 0 #define RCHEEVOS_MAKE_VERSION(major, minor, patch) (major * 1000000 + minor * 1000 + patch) diff --git a/3rdparty/rcheevos/src/rcheevos/alloc.c b/3rdparty/rcheevos/src/rcheevos/alloc.c index 5dbf20cb0f..96fb8e3825 100644 --- a/3rdparty/rcheevos/src/rcheevos/alloc.c +++ b/3rdparty/rcheevos/src/rcheevos/alloc.c @@ -213,6 +213,10 @@ static void rc_preparse_sync_operand(rc_operand_t* operand, rc_parse_state_t* pa { if (rc_operand_is_memref(operand) || rc_operand_is_recall(operand)) { const rc_memref_t* src_memref = operand->value.memref; + if (!src_memref) { + parse->offset = RC_INVALID_MEMORY_OPERAND; + return; + } if (src_memref->value.memref_type == RC_MEMREF_TYPE_MODIFIED_MEMREF) { const rc_modified_memref_list_t* modified_memref_list = &memrefs->modified_memrefs; diff --git a/3rdparty/rcheevos/src/rcheevos/condition.c b/3rdparty/rcheevos/src/rcheevos/condition.c index ff6da02bf8..63986812e3 100644 --- a/3rdparty/rcheevos/src/rcheevos/condition.c +++ b/3rdparty/rcheevos/src/rcheevos/condition.c @@ -411,10 +411,13 @@ void rc_condition_update_parse_state(rc_condition_t* condition, rc_parse_state_t negate = rc_alloc_modified_memref(parse, new_size, &parse->addsource_parent, RC_OPERATOR_SUB_PARENT, &zero); parse->addsource_parent.value.memref = (rc_memref_t*)negate; parse->addsource_parent.size = zero.size; + + if (parse->addsource_parent.type == RC_OPERAND_CONST) + parse->addsource_parent.value.num = rc_get_modified_memref_value(negate, NULL, NULL); } /* subtract the condition from the chain */ - parse->addsource_oper = rc_operand_is_memref(&parse->addsource_parent) ? RC_OPERATOR_SUB_ACCUMULATOR : RC_OPERATOR_SUB_PARENT; + parse->addsource_oper = RC_OPERATOR_SUB_ACCUMULATOR; rc_condition_convert_to_operand(condition, &cond_operand, parse); rc_operand_addsource(&cond_operand, parse, new_size); memcpy(&parse->addsource_parent, &cond_operand, sizeof(cond_operand)); diff --git a/3rdparty/rcheevos/src/rcheevos/consoleinfo.c b/3rdparty/rcheevos/src/rcheevos/consoleinfo.c index aeecb38d2c..a7ad9afff4 100644 --- a/3rdparty/rcheevos/src/rcheevos/consoleinfo.c +++ b/3rdparty/rcheevos/src/rcheevos/consoleinfo.c @@ -818,12 +818,14 @@ static const rc_memory_region_t _rc_memory_regions_playstation2[] = { static const rc_memory_regions_t rc_memory_regions_playstation2 = { _rc_memory_regions_playstation2, 3 }; /* ===== PlayStation Portable ===== */ -/* https://github.com/uofw/upspd/wiki/Memory-map */ +/* https://github.com/uofw/upspd/wiki/Memory-map + * the RAM is extended at loadtime if the game has a MEMSIZE flag in its PARAM.SFO */ static const rc_memory_region_t _rc_memory_regions_psp[] = { { 0x00000000U, 0x007FFFFFU, 0x08000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Kernel RAM" }, { 0x00800000U, 0x01FFFFFFU, 0x08800000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, + { 0x02000000U, 0x03FFFFFFU, 0x0A000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Extended RAM" }, }; -static const rc_memory_regions_t rc_memory_regions_psp = { _rc_memory_regions_psp, 2 }; +static const rc_memory_regions_t rc_memory_regions_psp = { _rc_memory_regions_psp, 3 }; /* ===== Pokemon Mini ===== */ /* https://www.pokemon-mini.net/documentation/memory-map/ */ @@ -993,6 +995,13 @@ static const rc_memory_region_t _rc_memory_regions_wonderswan[] = { }; static const rc_memory_regions_t rc_memory_regions_wonderswan = { _rc_memory_regions_wonderswan, 2 }; +/* ===== Xbox ===== */ +static const rc_memory_region_t _rc_memory_regions_xbox[] = { + /* Retail Xbox memory map: https://xboxdevwiki.net/Memory */ + { 0x00000000U, 0x03FFFFFFU, 0x00000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" } +}; +static const rc_memory_regions_t rc_memory_regions_xbox = { _rc_memory_regions_xbox, 1 }; + /* ===== ZX Spectrum ===== */ /* https://github.com/TASEmulators/BizHawk/blob/3a3b22c/src/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum16K/ZX16.cs * https://github.com/TASEmulators/BizHawk/blob/3a3b22c/src/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Memory.cs @@ -1206,6 +1215,9 @@ const rc_memory_regions_t* rc_console_memory_regions(uint32_t console_id) case RC_CONSOLE_WONDERSWAN: return &rc_memory_regions_wonderswan; + case RC_CONSOLE_XBOX: + return &rc_memory_regions_xbox; + case RC_CONSOLE_ZX_SPECTRUM: return &rc_memory_regions_zx_spectrum; diff --git a/3rdparty/rcheevos/src/rcheevos/memref.c b/3rdparty/rcheevos/src/rcheevos/memref.c index 9c8f16066e..577c7e4833 100644 --- a/3rdparty/rcheevos/src/rcheevos/memref.c +++ b/3rdparty/rcheevos/src/rcheevos/memref.c @@ -742,6 +742,7 @@ uint32_t rc_get_modified_memref_value(const rc_modified_memref_t* memref, rc_pee break; case RC_OPERATOR_SUB_ACCUMULATOR: + /* sub accumulator is "parent - modifier" */ rc_typed_value_negate(&modifier); /* fallthrough */ /* to case RC_OPERATOR_SUB_ACCUMULATOR */ diff --git a/3rdparty/rcheevos/src/rcheevos/rc_validate.c b/3rdparty/rcheevos/src/rcheevos/rc_validate.c index 54537e3c4e..2d55c2f467 100644 --- a/3rdparty/rcheevos/src/rcheevos/rc_validate.c +++ b/3rdparty/rcheevos/src/rcheevos/rc_validate.c @@ -38,6 +38,7 @@ enum /* warnings about potential logic errors */ RC_VALIDATION_ERR_COMPARING_DIFFERENT_MEMORY_SIZES, RC_VALIDATION_ERR_MASK_RESULT_ALWAYS_ZERO, + RC_VALIDATION_ERR_SAME_COMPARISON_ACROSS_FRAMES, /* warnings that some areas of memory should be avoided */ RC_VALIDATION_ERR_KERNAL_RAM_REQUIRES_BIOS, @@ -244,6 +245,13 @@ static int rc_validate_format_error(char buffer[], size_t buffer_size, const rc_ snprintf(buffer, buffer_size, "Result of mask is always 0"); break; + case RC_VALIDATION_ERR_SAME_COMPARISON_ACROSS_FRAMES: + written = snprintf(buffer, buffer_size, "Same comparison across frames as "); + buffer += written; + buffer_size -= written; + rc_validate_format_cond_index(buffer, buffer_size, state, error->data1, error->data2); + break; + case RC_VALIDATION_ERR_MASK_TOO_LARGE: snprintf(buffer, buffer_size, "Mask has more bits than source"); break; @@ -1211,9 +1219,22 @@ static int rc_validate_conflicting_conditions(const rc_condset_t* conditions, co continue; operand2 = rc_validate_get_comparison(compare_condition, &comparison2, &value2); - if (!operand2 || !rc_operands_are_equal(operand1, operand2)) + if (!operand2) continue; + if (!rc_operands_are_equal(operand1, operand2)) { + if (compare_condition->type == condition->type && + compare_condition->oper == condition->oper && + operand1->type == RC_OPERAND_ADDRESS && + (operand2->type == RC_OPERAND_DELTA || operand2->type == RC_OPERAND_PRIOR) && + operand1->value.memref->address == operand2->value.memref->address && + rc_operands_are_equal(&compare_condition->operand2, &condition->operand2)) { + rc_validate_add_error(state, RC_VALIDATION_ERR_SAME_COMPARISON_ACROSS_FRAMES, + group_index, rc_validate_get_condition_index(conditions, condition)); + } + continue; + } + switch (compare_condition->type) { case RC_CONDITION_PAUSE_IF: if (conditions != compare_conditions) /* PauseIf only affects conditions in same group */ diff --git a/3rdparty/rcheevos/src/rcheevos/richpresence.c b/3rdparty/rcheevos/src/rcheevos/richpresence.c index b2f85c69e1..2292667301 100644 --- a/3rdparty/rcheevos/src/rcheevos/richpresence.c +++ b/3rdparty/rcheevos/src/rcheevos/richpresence.c @@ -38,6 +38,11 @@ static void rc_alloc_helper_variable_memref_value(rc_richpresence_display_part_t /* ensure new needed memrefs are allocated in the primary buffer */ rc_preparse_copy_memrefs(parse, &preparse.memrefs); + if (parse->offset < 0) { + rc_destroy_preparse_state(&preparse); + return; + } + /* parse the value into the scratch buffer so we can look at it */ rc_reset_parse_state(&preparse.parse, rc_buffer_alloc(&preparse.parse.scratch.buffer, (size_t)size)); preparse.parse.memrefs = parse->memrefs; @@ -725,14 +730,15 @@ rc_memrefs_t* rc_richpresence_get_memrefs(rc_richpresence_t* self) { void rc_update_richpresence(rc_richpresence_t* richpresence, rc_peek_t peek, void* peek_ud, void* unused_L) { (void)unused_L; - rc_update_richpresence_memrefs(richpresence, peek, peek_ud); - rc_update_values(richpresence->values, peek, peek_ud); rc_update_richpresence_internal(richpresence, peek, peek_ud); } void rc_update_richpresence_internal(rc_richpresence_t* richpresence, rc_peek_t peek, void* peek_ud) { rc_richpresence_display_t* display; + rc_update_richpresence_memrefs(richpresence, peek, peek_ud); + rc_update_values(richpresence->values, peek, peek_ud); + for (display = richpresence->first_display; display; display = display->next) { if (display->has_required_hits) rc_test_trigger(&display->trigger, peek, peek_ud, NULL); diff --git a/3rdparty/rcheevos/src/rcheevos/runtime.c b/3rdparty/rcheevos/src/rcheevos/runtime.c index 9c05985ee8..c1100b9af9 100644 --- a/3rdparty/rcheevos/src/rcheevos/runtime.c +++ b/3rdparty/rcheevos/src/rcheevos/runtime.c @@ -510,7 +510,7 @@ int rc_runtime_get_richpresence(const rc_runtime_t* self, char* buffer, size_t b void rc_runtime_do_frame(rc_runtime_t* self, rc_runtime_event_handler_t event_handler, rc_runtime_peek_t peek, void* ud, void* unused_L) { rc_runtime_event_t runtime_event; - int i; + int32_t i; runtime_event.value = 0; @@ -670,7 +670,7 @@ void rc_runtime_do_frame(rc_runtime_t* self, rc_runtime_event_handler_t event_ha break; case RC_LBOARD_STATE_TRIGGERED: - if (lboard_state != RC_RUNTIME_EVENT_LBOARD_TRIGGERED) { + if (lboard_state != RC_LBOARD_STATE_TRIGGERED) { runtime_event.type = RC_RUNTIME_EVENT_LBOARD_TRIGGERED; runtime_event.id = self->lboards[i].id; event_handler(&runtime_event); diff --git a/3rdparty/rcheevos/src/rcheevos/value.c b/3rdparty/rcheevos/src/rcheevos/value.c index 33faee5d5e..8876f5bced 100644 --- a/3rdparty/rcheevos/src/rcheevos/value.c +++ b/3rdparty/rcheevos/src/rcheevos/value.c @@ -120,6 +120,12 @@ static void rc_parse_legacy_value(rc_value_t* self, const char** memaddr, rc_par /* extract the next clause */ for (;; ++(*memaddr)) { + if (ptr == &buffer[sizeof(buffer)]) { + /* ran out of local buffer space for converting the condition */ + parse->offset = RC_INVALID_VALUE; + return; + } + switch (**memaddr) { case '_': /* add next */ *ptr = '\0'; @@ -176,7 +182,7 @@ static void rc_parse_legacy_value(rc_value_t* self, const char** memaddr, rc_par if (*buffer_ptr) { /* whatever we copied as a single condition was not fully consumed */ - parse->offset = RC_INVALID_COMPARISON; + parse->offset = RC_INVALID_VALUE; return; } @@ -429,7 +435,7 @@ rc_value_t* rc_alloc_variable(const char* memaddr, size_t memaddr_len, rc_parse_ /* no match found, create a new entry */ value = RC_ALLOC_SCRATCH(rc_value_t, parse); - memset(value, 0, sizeof(value->value)); + memset(value, 0, sizeof(*value)); value->value.size = RC_MEMSIZE_VARIABLE; value->next = NULL; diff --git a/3rdparty/rcheevos/src/rhash/cdreader.c b/3rdparty/rcheevos/src/rhash/cdreader.c index 9ffcc55bcb..cf4647b295 100644 --- a/3rdparty/rcheevos/src/rhash/cdreader.c +++ b/3rdparty/rcheevos/src/rhash/cdreader.c @@ -648,20 +648,45 @@ static void* cdreader_open_gdi_track(const char* path, uint32_t track, const rc_ while (isspace((unsigned char)*ptr)) ++ptr; - ptr2 = file; if (*ptr == '\"') { - ++ptr; + ptr2 = ++ptr; /* ignore leading quote */ while (*ptr != '\"') - *ptr2++ = *ptr++; - ++ptr; + { + ++ptr; + + if (ptr >= end) + { + if (iterator->callbacks.filereader.close) + iterator->callbacks.filereader.close(file_handle); + + rc_hash_iterator_error(iterator, "Quoted string without closing quote"); + return NULL; + } + } + + num_read = ptr - ptr2; + ++ptr; /* ignore trailing quote */ } else { - while (*ptr != ' ') - *ptr2++ = *ptr++; + ptr2 = ptr; + while (*ptr != ' ' && ptr < end) + ++ptr; + num_read = ptr - ptr2; } - *ptr2 = '\0'; + + if (num_read >= sizeof(file)) + { + if (iterator->callbacks.filereader.close) + iterator->callbacks.filereader.close(file_handle); + + rc_hash_iterator_error_formatted(iterator, "Cannot copy %u byte filename into %u byte buffer", (unsigned)num_read, (unsigned)sizeof(file)); + return NULL; + } + + memcpy(file, ptr2, num_read); + file[num_read] = '\0'; if (track == current_track) { diff --git a/3rdparty/rcheevos/src/rhash/hash.c b/3rdparty/rcheevos/src/rhash/hash.c index fe6f3ca9d0..451a9a8945 100644 --- a/3rdparty/rcheevos/src/rhash/hash.c +++ b/3rdparty/rcheevos/src/rhash/hash.c @@ -494,7 +494,7 @@ static int rc_hash_file_from_buffer(char hash[33], uint32_t console_id, const rc result = rc_hash_from_file(hash, console_id, &buffered_file_iterator); - buffered_file_iterator.path = NULL; + buffered_file_iterator.path = NULL; /* prevent attempt to free static "memory stream" string */ rc_hash_destroy_iterator(&buffered_file_iterator); return result; } @@ -543,6 +543,11 @@ static int rc_hash_from_buffer(char hash[33], uint32_t console_id, const rc_hash return rc_hash_buffer(hash, iterator->buffer, iterator->buffer_size, iterator); #ifndef RC_HASH_NO_ROM + case RC_CONSOLE_ARCADE: + /* .neo (Geolith Neo Geo cart) files carry the ROM data; other arcade + * formats are archives, which aren't hashed from a buffer. */ + return rc_hash_neogeo_cart(hash, iterator); + case RC_CONSOLE_ARDUBOY: return rc_hash_arduboy(hash, iterator); @@ -862,6 +867,11 @@ static int rc_hash_from_file(char hash[33], uint32_t console_id, const rc_hash_i #ifndef RC_HASH_NO_ROM case RC_CONSOLE_ARCADE: + /* .neo files (Geolith Neo Geo cart format) contain the actual ROM data, + * so are content-hashed. Everything else (.zip/.7z) hashes by filename. */ + if (rc_path_compare_extension(path, "neo")) + return rc_hash_neogeo_cart(hash, iterator); + return rc_hash_arcade(hash, iterator); case RC_CONSOLE_ARDUBOY: @@ -971,7 +981,7 @@ void rc_hash_merge_callbacks(rc_hash_iterator_t* iterator, const rc_hash_callbac if (callbacks->verbose_message) iterator->callbacks.verbose_message = callbacks->verbose_message; if (callbacks->error_message) - iterator->callbacks.verbose_message = callbacks->error_message; + iterator->callbacks.error_message = callbacks->error_message; if (callbacks->filereader.open) memcpy(&iterator->callbacks.filereader, &callbacks->filereader, sizeof(callbacks->filereader)); @@ -1230,6 +1240,7 @@ static const rc_hash_iterator_ext_handler_entry_t rc_hash_iterator_ext_handlers[ { "n64", rc_hash_initialize_iterator_single, RC_CONSOLE_NINTENDO_64 }, { "ndd", rc_hash_initialize_iterator_single, RC_CONSOLE_NINTENDO_64 }, { "nds", rc_hash_initialize_iterator_single, RC_CONSOLE_NINTENDO_DS }, /* handles both DS and DSi */ + { "neo", rc_hash_initialize_iterator_single, RC_CONSOLE_ARCADE }, /* Geolith Neo Geo cart format */ { "nes", rc_hash_initialize_iterator_single, RC_CONSOLE_NINTENDO }, { "ngc", rc_hash_initialize_iterator_single, RC_CONSOLE_NEOGEO_POCKET }, { "nib", rc_hash_initialize_iterator_nib, 0 }, @@ -1245,6 +1256,7 @@ static const rc_hash_iterator_ext_handler_entry_t rc_hash_iterator_ext_handlers[ { "sg", rc_hash_initialize_iterator_single, RC_CONSOLE_SG1000 }, { "sgx", rc_hash_initialize_iterator_single, RC_CONSOLE_PC_ENGINE }, { "smc", rc_hash_initialize_iterator_single, RC_CONSOLE_SUPER_NINTENDO }, + { "sms", rc_hash_initialize_iterator_single, RC_CONSOLE_MASTER_SYSTEM }, { "sv", rc_hash_initialize_iterator_single, RC_CONSOLE_SUPERVISION }, { "swc", rc_hash_initialize_iterator_single, RC_CONSOLE_SUPER_NINTENDO }, { "tap", rc_hash_initialize_iterator_tap, 0 }, diff --git a/3rdparty/rcheevos/src/rhash/rc_hash_internal.h b/3rdparty/rcheevos/src/rhash/rc_hash_internal.h index f50a9348d2..c1ea6d0c77 100644 --- a/3rdparty/rcheevos/src/rhash/rc_hash_internal.h +++ b/3rdparty/rcheevos/src/rhash/rc_hash_internal.h @@ -74,6 +74,7 @@ int rc_hash_buffered_file(char hash[33], uint32_t console_id, const rc_hash_iter int rc_hash_lynx(char hash[33], const rc_hash_iterator_t* iterator); int rc_hash_nes(char hash[33], const rc_hash_iterator_t* iterator); int rc_hash_n64(char hash[33], const rc_hash_iterator_t* iterator); + int rc_hash_neogeo_cart(char hash[33], const rc_hash_iterator_t* iterator); int rc_hash_nintendo_ds(char hash[33], const rc_hash_iterator_t* iterator); int rc_hash_pce(char hash[33], const rc_hash_iterator_t* iterator); int rc_hash_scv(char hash[33], const rc_hash_iterator_t* iterator);