diff --git a/deps/rcheevos/CHANGELOG.md b/deps/rcheevos/CHANGELOG.md index a76f5fe754..99af21020f 100644 --- a/deps/rcheevos/CHANGELOG.md +++ b/deps/rcheevos/CHANGELOG.md @@ -1,3 +1,24 @@ +# v10.2.0 + +* add RC_MEMSIZE_16_BITS_BE, RC_MEMSIZE_24_BITS_BE, and RC_MEMSIZE_32_BITS_BE +* add secondary flag for RC_CONDITION_MEASURED that tells the UI when to show progress as raw vs. as a percentage +* add rapi calls for fetch_leaderboard_info, fetch_achievement_info and fetch_game_list +* add hash support for RC_CONSOLE_PSP +* add RCHEEVOS_URL_SSL compile flag to use https in rurl functions +* add space to "PC Engine" label +* update RC_CONSOLE_INTELLIVISION memory map to acknowledge non-8-bit addresses +* standardize to z64 format when hashing RC_CONSOLE_N64 +* prevent generating hash for PSX disc when requesting RC_CONSOLE_PLAYSTATION2 +* fix wrong error message being returned when a leaderboard was only slightly malformed + +# v10.1.0 + +* add RC_RUNTIME_EVENT_ACHIEVEMENT_UNPRIMED +* add rc_runtime_validate_addresses +* add external memory to memory map for Magnavox Odyssey 2 +* fix memory map base address for NeoGeo Pocket +* fix bitcount always returning 0 when used in rich presence + # v10.0.0 * add rapi sublibrary for communicating with server (eliminates need for client-side JSON parsing; client must still diff --git a/deps/rcheevos/include/rc_api_info.h b/deps/rcheevos/include/rc_api_info.h new file mode 100644 index 0000000000..7979cc391f --- /dev/null +++ b/deps/rcheevos/include/rc_api_info.h @@ -0,0 +1,182 @@ +#ifndef RC_API_INFO_H +#define RC_API_INFO_H + +#include "rc_api_request.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* --- Fetch Achievement Info --- */ + +/** + * API parameters for a fetch achievement info request. + */ +typedef struct rc_api_fetch_achievement_info_request_t { + /* The username of the player */ + const char* username; + /* The API token from the login request */ + const char* api_token; + /* The unique identifier of the achievement */ + unsigned achievement_id; + /* The 1-based index of the first entry to retrieve */ + unsigned first_entry; + /* The number of entries to retrieve */ + unsigned count; + /* Non-zero to only return unlocks earned by the user's friends */ + unsigned friends_only; +} +rc_api_fetch_achievement_info_request_t; + +/* An achievement awarded entry */ +typedef struct rc_api_achievement_awarded_entry_t { + /* The user associated to the entry */ + const char* username; + /* When the achievement was awarded */ + time_t awarded; +} +rc_api_achievement_awarded_entry_t; + +/** + * Response data for a fetch achievement info request. + */ +typedef struct rc_api_fetch_achievement_info_response_t { + /* The unique identifier of the achievement */ + unsigned id; + /* The unique identifier of the game to which the leaderboard is associated */ + unsigned game_id; + /* The number of times the achievement has been awarded */ + unsigned num_awarded; + /* The number of players that have earned at least one achievement for the game */ + unsigned num_players; + + /* An array of recently rewarded entries */ + rc_api_achievement_awarded_entry_t* recently_awarded; + /* The number of items in the recently_awarded array */ + unsigned num_recently_awarded; + + /* Common server-provided response information */ + rc_api_response_t response; +} +rc_api_fetch_achievement_info_response_t; + +int rc_api_init_fetch_achievement_info_request(rc_api_request_t* request, const rc_api_fetch_achievement_info_request_t* api_params); +int rc_api_process_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response, const char* server_response); +void rc_api_destroy_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response); + +/* --- Fetch Leaderboard Info --- */ + +/** + * API parameters for a fetch leaderboard info request. + */ +typedef struct rc_api_fetch_leaderboard_info_request_t { + /* The unique identifier of the leaderboard */ + unsigned leaderboard_id; + /* The number of entries to retrieve */ + unsigned count; + /* The 1-based index of the first entry to retrieve */ + unsigned first_entry; + /* The username of the player around whom the entries should be returned */ + const char* username; +} +rc_api_fetch_leaderboard_info_request_t; + +/* A leaderboard info entry */ +typedef struct rc_api_lboard_info_entry_t { + /* The user associated to the entry */ + const char* username; + /* The rank of the entry */ + unsigned rank; + /* The index of the entry */ + unsigned index; + /* The value of the entry */ + int score; + /* When the entry was submitted */ + time_t submitted; +} +rc_api_lboard_info_entry_t; + +/** + * Response data for a fetch leaderboard info request. + */ +typedef struct rc_api_fetch_leaderboard_info_response_t { + /* The unique identifier of the leaderboard */ + unsigned id; + /* The format to pass to rc_format_value to format the leaderboard value */ + int format; + /* If non-zero, indicates that lower scores appear first */ + int lower_is_better; + /* The title of the leaderboard */ + const char* title; + /* The description of the leaderboard */ + const char* description; + /* The definition of the leaderboard to be passed to rc_runtime_activate_lboard */ + const char* definition; + /* The unique identifier of the game to which the leaderboard is associated */ + unsigned game_id; + /* The author of the leaderboard */ + const char* author; + /* When the leaderboard was first uploaded to the server */ + time_t created; + /* When the leaderboard was last modified on the server */ + time_t updated; + + /* An array of requested entries */ + rc_api_lboard_info_entry_t* entries; + /* The number of items in the entries array */ + unsigned num_entries; + + /* Common server-provided response information */ + rc_api_response_t response; +} +rc_api_fetch_leaderboard_info_response_t; + +int rc_api_init_fetch_leaderboard_info_request(rc_api_request_t* request, const rc_api_fetch_leaderboard_info_request_t* api_params); +int rc_api_process_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response, const char* server_response); +void rc_api_destroy_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response); + +/* --- Fetch Games List --- */ + +/** + * API parameters for a fetch games list request. + */ +typedef struct rc_api_fetch_games_list_request_t { + /* The unique identifier of the console to query */ + unsigned console_id; +} +rc_api_fetch_games_list_request_t; + +/* A game list entry */ +typedef struct rc_api_game_list_entry_t { + /* The unique identifier of the game */ + unsigned id; + /* The name of the game */ + const char* name; +} +rc_api_game_list_entry_t; + +/** + * Response data for a fetch games list request. + */ +typedef struct rc_api_fetch_games_list_response_t { + /* An array of requested entries */ + rc_api_game_list_entry_t* entries; + /* The number of items in the entries array */ + unsigned num_entries; + + /* Common server-provided response information */ + rc_api_response_t response; +} +rc_api_fetch_games_list_response_t; + +int rc_api_init_fetch_games_list_request(rc_api_request_t* request, const rc_api_fetch_games_list_request_t* api_params); +int rc_api_process_fetch_games_list_response(rc_api_fetch_games_list_response_t* response, const char* server_response); +void rc_api_destroy_fetch_games_list_response(rc_api_fetch_games_list_response_t* response); + +#ifdef __cplusplus +} +#endif + +#endif /* RC_API_INFO_H */ diff --git a/deps/rcheevos/include/rc_api_runtime.h b/deps/rcheevos/include/rc_api_runtime.h index 85c632fe18..26bbac9e56 100644 --- a/deps/rcheevos/include/rc_api_runtime.h +++ b/deps/rcheevos/include/rc_api_runtime.h @@ -36,9 +36,9 @@ int rc_api_init_fetch_image_request(rc_api_request_t* request, const rc_api_fetc * API parameters for a resolve hash request. */ typedef struct rc_api_resolve_hash_request_t { - /* The username of the player */ + /* Unused - hash lookup does not require credentials */ const char* username; - /* The API token from the login request */ + /* Unused - hash lookup does not require credentials */ const char* api_token; /* The generated hash of the game to be identified */ const char* game_hash; diff --git a/deps/rcheevos/include/rc_hash.h b/deps/rcheevos/include/rc_hash.h index 1e274e6236..e11ea89e91 100644 --- a/deps/rcheevos/include/rc_hash.h +++ b/deps/rcheevos/include/rc_hash.h @@ -16,7 +16,7 @@ extern "C" { /* 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, uint8_t* buffer, size_t buffer_size); + int rc_hash_generate_from_buffer(char hash[33], int 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. diff --git a/deps/rcheevos/include/rc_runtime.h b/deps/rcheevos/include/rc_runtime.h index d6f913ca2f..7407dbae30 100644 --- a/deps/rcheevos/include/rc_runtime.h +++ b/deps/rcheevos/include/rc_runtime.h @@ -7,6 +7,8 @@ extern "C" { #include "rc_error.h" +#include + /*****************************************************************************\ | Forward Declarations (defined in rc_runtime_types.h) | \*****************************************************************************/ @@ -94,6 +96,7 @@ int rc_runtime_activate_achievement(rc_runtime_t* runtime, unsigned id, const ch void rc_runtime_deactivate_achievement(rc_runtime_t* runtime, unsigned id); rc_trigger_t* rc_runtime_get_achievement(const rc_runtime_t* runtime, unsigned id); int rc_runtime_get_achievement_measured(const rc_runtime_t* runtime, unsigned id, unsigned* measured_value, unsigned* measured_target); +int rc_runtime_format_achievement_measured(const rc_runtime_t* runtime, unsigned id, char *buffer, size_t buffer_size); int rc_runtime_activate_lboard(rc_runtime_t* runtime, unsigned id, const char* memaddr, lua_State* L, int funcs_idx); void rc_runtime_deactivate_lboard(rc_runtime_t* runtime, unsigned id); diff --git a/deps/rcheevos/include/rc_runtime_types.h b/deps/rcheevos/include/rc_runtime_types.h index 25511a2ee7..be85b3a46d 100644 --- a/deps/rcheevos/include/rc_runtime_types.h +++ b/deps/rcheevos/include/rc_runtime_types.h @@ -51,6 +51,9 @@ enum { RC_MEMSIZE_BIT_6, RC_MEMSIZE_BIT_7, RC_MEMSIZE_BITCOUNT, + RC_MEMSIZE_16_BITS_BE, + RC_MEMSIZE_24_BITS_BE, + RC_MEMSIZE_32_BITS_BE, RC_MEMSIZE_VARIABLE }; @@ -255,6 +258,9 @@ struct rc_trigger_t { /* True if at least one condition has a non-zero required hit count */ char has_required_hits; + + /* True if the measured value should be displayed as a percentage */ + char measured_as_percent; }; int rc_trigger_size(const char* memaddr); diff --git a/deps/rcheevos/include/rc_url.h b/deps/rcheevos/include/rc_url.h index a0e7a11764..b61cd808c9 100644 --- a/deps/rcheevos/include/rc_url.h +++ b/deps/rcheevos/include/rc_url.h @@ -11,6 +11,9 @@ int rc_url_award_cheevo(char* buffer, size_t size, const char* user_name, const int 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); + int 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); diff --git a/deps/rcheevos/src/rapi/rc_api_common.c b/deps/rcheevos/src/rapi/rc_api_common.c index b449cb954e..a8ca8f7e61 100644 --- a/deps/rcheevos/src/rapi/rc_api_common.c +++ b/deps/rcheevos/src/rapi/rc_api_common.c @@ -20,7 +20,7 @@ static char* g_imagehost = NULL; /* --- rc_json --- */ -static int rc_json_parse_object(const char** json_ptr, rc_json_field_t* fields, size_t field_count); +static int rc_json_parse_object(const char** json_ptr, rc_json_field_t* fields, size_t field_count, unsigned* fields_seen); static int rc_json_parse_array(const char** json_ptr, rc_json_field_t* field); static int rc_json_parse_field(const char** json_ptr, rc_json_field_t* field) { @@ -68,7 +68,7 @@ static int rc_json_parse_field(const char** json_ptr, rc_json_field_t* field) { break; case '{': /* object */ - result = rc_json_parse_object(json_ptr, NULL, 0); + result = rc_json_parse_object(json_ptr, NULL, 0, &field->array_size); if (result != RC_OK) return result; @@ -127,13 +127,54 @@ static int rc_json_parse_array(const char** json_ptr, rc_json_field_t* field) { return RC_OK; } -static int rc_json_parse_object(const char** json_ptr, rc_json_field_t* fields, size_t field_count) { - rc_json_field_t non_matching_field; - rc_json_field_t* field; +static int rc_json_get_next_field(rc_json_object_field_iterator_t* iterator) { + const char* json = iterator->json; + + while (isspace((unsigned char)*json)) + ++json; + + if (*json != '"') + return RC_INVALID_JSON; + + iterator->field.name = ++json; + while (*json != '"') { + if (!*json) + return RC_INVALID_JSON; + ++json; + } + iterator->name_len = json - iterator->field.name; + ++json; + + while (isspace((unsigned char)*json)) + ++json; + + if (*json != ':') + return RC_INVALID_JSON; + + ++json; + + while (isspace((unsigned char)*json)) + ++json; + + if (rc_json_parse_field(&json, &iterator->field) < 0) + return RC_INVALID_JSON; + + while (isspace((unsigned char)*json)) + ++json; + + iterator->json = json; + return RC_OK; +} + +static int rc_json_parse_object(const char** json_ptr, rc_json_field_t* fields, size_t field_count, unsigned* fields_seen) { + rc_json_object_field_iterator_t iterator; const char* json = *json_ptr; - const char* key_start; - size_t key_len; size_t i; + unsigned num_fields = 0; + int result; + + if (fields_seen) + *fields_seen = 0; for (i = 0; i < field_count; ++i) fields[i].value_start = fields[i].value_end = NULL; @@ -147,61 +188,50 @@ static int rc_json_parse_object(const char** json_ptr, rc_json_field_t* fields, return RC_OK; } + memset(&iterator, 0, sizeof(iterator)); + iterator.json = json; + do { - while (isspace((unsigned char)*json)) - ++json; + result = rc_json_get_next_field(&iterator); + if (result != RC_OK) + return result; - if (*json != '"') - return RC_INVALID_JSON; - - key_start = ++json; - while (*json != '"') { - if (!*json) - return RC_INVALID_JSON; - ++json; - } - key_len = json - key_start; - ++json; - - while (isspace((unsigned char)*json)) - ++json; - - if (*json != ':') - return RC_INVALID_JSON; - - ++json; - - while (isspace((unsigned char)*json)) - ++json; - - field = &non_matching_field; for (i = 0; i < field_count; ++i) { - if (!fields[i].value_start && strncmp(fields[i].name, key_start, key_len) == 0 && fields[i].name[key_len] == '\0') { - field = &fields[i]; + if (!fields[i].value_start && strncmp(fields[i].name, iterator.field.name, iterator.name_len) == 0 && + fields[i].name[iterator.name_len] == '\0') { + fields[i].value_start = iterator.field.value_start; + fields[i].value_end = iterator.field.value_end; + fields[i].array_size = iterator.field.array_size; break; } } - if (rc_json_parse_field(&json, field) < 0) - return RC_INVALID_JSON; - - while (isspace((unsigned char)*json)) - ++json; - - if (*json != ',') + ++num_fields; + if (*iterator.json != ',') break; - ++json; + ++iterator.json; } while (1); - if (*json != '}') + if (*iterator.json != '}') return RC_INVALID_JSON; - *json_ptr = ++json; + if (fields_seen) + *fields_seen = num_fields; + + *json_ptr = ++iterator.json; return RC_OK; } +int rc_json_get_next_object_field(rc_json_object_field_iterator_t* iterator) { + if (*iterator->json != ',' && *iterator->json != '{') + return 0; + + ++iterator->json; + return (rc_json_get_next_field(iterator) == RC_OK); +} + int rc_json_parse_response(rc_api_response_t* response, const char* json, rc_json_field_t* fields, size_t field_count) { #ifndef NDEBUG if (field_count < 2) @@ -213,7 +243,7 @@ int rc_json_parse_response(rc_api_response_t* response, const char* json, rc_jso #endif if (*json == '{') { - int result = rc_json_parse_object(&json, fields, field_count); + int result = rc_json_parse_object(&json, fields, field_count, NULL); rc_json_get_optional_string(&response->error_message, response, &fields[1], "Error", NULL); rc_json_get_optional_bool(&response->succeeded, &fields[0], "Success", 1); @@ -266,11 +296,15 @@ static int rc_json_missing_field(rc_api_response_t* response, const rc_json_fiel 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) { const char* json = field->value_start; +#ifndef NDEBUG + if (strcmp(field->name, field_name) != 0) + return 0; +#endif if (!json) return rc_json_missing_field(response, field); - return (rc_json_parse_object(&json, fields, field_count) == RC_OK); + return (rc_json_parse_object(&json, fields, field_count, &field->array_size) == RC_OK); } static int rc_json_get_array_entry_value(rc_json_field_t* field, rc_json_field_t* iterator) { @@ -322,6 +356,11 @@ int rc_json_get_required_unum_array(unsigned** entries, unsigned* num_entries, r } int rc_json_get_required_array(unsigned* num_entries, rc_json_field_t* iterator, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name) { +#ifndef NDEBUG + if (strcmp(field->name, field_name) != 0) + return 0; +#endif + if (!field->value_start || *field->value_start != '[') { *num_entries = 0; return rc_json_missing_field(response, field); @@ -341,7 +380,7 @@ int rc_json_get_array_entry_object(rc_json_field_t* fields, size_t field_count, while (isspace((unsigned char)*iterator->value_start)) ++iterator->value_start; - rc_json_parse_object(&iterator->value_start, fields, field_count); + rc_json_parse_object(&iterator->value_start, fields, field_count, NULL); while (isspace((unsigned char)*iterator->value_start)) ++iterator->value_start; @@ -358,7 +397,7 @@ static unsigned rc_json_decode_hex4(const char* input) { memcpy(hex, input, 4); hex[4] = '\0'; - return strtol(hex, NULL, 16); + return (unsigned)strtoul(hex, NULL, 16); } static int rc_json_ucs32_to_utf8(unsigned char* dst, unsigned ucs32_char) { @@ -604,6 +643,48 @@ int rc_json_get_required_unum(unsigned* out, rc_api_response_t* response, const 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; + +#ifndef NDEBUG + if (strcmp(field->name, field_name) != 0) + return 0; +#endif + + if (*field->value_start == '\"') { + memset(&tm, 0, sizeof(tm)); + if (sscanf(field->value_start + 1, "%d-%d-%d %d:%d:%d", + &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) == 6) { + tm.tm_mon--; /* 0-based */ + tm.tm_year -= 1900; /* 1900 based */ + + /* mktime converts a struct tm to a time_t using the local timezone. + * the input string is UTC. since timegm is not universally cross-platform, + * figure out the offset between UTC and local time by applying the + * timezone conversion twice and manually removing the difference */ + { + time_t local_timet = mktime(&tm); + struct tm* gmt_tm = gmtime(&local_timet); + time_t skewed_timet = mktime(gmt_tm); /* applies local time adjustment second time */ + time_t tz_offset = skewed_timet - local_timet; + *out = local_timet - tz_offset; + } + + return 1; + } + } + + *out = 0; + return 0; +} + +int rc_json_get_required_datetime(time_t* out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name) { + if (rc_json_get_datetime(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; diff --git a/deps/rcheevos/src/rapi/rc_api_common.h b/deps/rcheevos/src/rapi/rc_api_common.h index d79c2aeaec..ad26ca00a7 100644 --- a/deps/rcheevos/src/rapi/rc_api_common.h +++ b/deps/rcheevos/src/rapi/rc_api_common.h @@ -4,6 +4,7 @@ #include "rc_api_request.h" #include +#include #ifdef __cplusplus extern "C" { @@ -30,11 +31,19 @@ typedef struct rc_json_field_t { } rc_json_field_t; +typedef struct rc_json_object_field_iterator_t { + rc_json_field_t field; + const char* json; + size_t name_len; +} +rc_json_object_field_iterator_t; + int rc_json_parse_response(rc_api_response_t* response, const char* json, rc_json_field_t* fields, size_t field_count); int rc_json_get_string(const char** out, rc_api_buffer_t* buffer, const rc_json_field_t* field, const char* field_name); int rc_json_get_num(int* out, const rc_json_field_t* field, const char* field_name); int rc_json_get_unum(unsigned* 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(int* out, const rc_json_field_t* field, const char* field_name, int default_value); void rc_json_get_optional_unum(unsigned* out, const rc_json_field_t* field, const char* field_name, int default_value); @@ -43,10 +52,12 @@ int rc_json_get_required_string(const char** out, rc_api_response_t* response, c int rc_json_get_required_num(int* out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name); int rc_json_get_required_unum(unsigned* 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); int rc_json_get_required_unum_array(unsigned** entries, unsigned* num_entries, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name); int rc_json_get_required_array(unsigned* 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_array_entry_object(rc_json_field_t* fields, size_t field_count, rc_json_field_t* iterator); +int rc_json_get_next_object_field(rc_json_object_field_iterator_t* iterator); void rc_buf_init(rc_api_buffer_t* buffer); void rc_buf_destroy(rc_api_buffer_t* buffer); diff --git a/deps/rcheevos/src/rapi/rc_api_info.c b/deps/rcheevos/src/rapi/rc_api_info.c new file mode 100644 index 0000000000..7dc758a1c2 --- /dev/null +++ b/deps/rcheevos/src/rapi/rc_api_info.c @@ -0,0 +1,328 @@ +#include "rc_api_info.h" +#include "rc_api_common.h" + +#include "rc_runtime_types.h" + +#include +#include + +/* --- Fetch Achievement Info --- */ + +int rc_api_init_fetch_achievement_info_request(rc_api_request_t* request, const rc_api_fetch_achievement_info_request_t* api_params) { + rc_api_url_builder_t builder; + + rc_api_url_build_dorequest_url(request); + + if (api_params->achievement_id == 0) + return RC_INVALID_STATE; + + rc_url_builder_init(&builder, &request->buffer, 48); + if (rc_api_url_build_dorequest(&builder, "achievementwondata", api_params->username, api_params->api_token)) { + rc_url_builder_append_unum_param(&builder, "a", api_params->achievement_id); + + if (api_params->friends_only) + rc_url_builder_append_unum_param(&builder, "f", 1); + if (api_params->first_entry > 1) + rc_url_builder_append_unum_param(&builder, "o", api_params->first_entry - 1); /* number of entries to skip */ + rc_url_builder_append_unum_param(&builder, "c", api_params->count); + + request->post_data = rc_url_builder_finalize(&builder); + } + + return builder.result; +} + +int rc_api_process_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response, const char* server_response) { + rc_api_achievement_awarded_entry_t* entry; + rc_json_field_t iterator; + unsigned timet; + int result; + + rc_json_field_t fields[] = { + {"Success"}, + {"Error"}, + {"AchievementID"}, + {"Response"} + /* unused fields + {"Offset"}, + {"Count"}, + {"FriendsOnly"}, + * unused fields */ + }; + + rc_json_field_t response_fields[] = { + {"NumEarned"}, + {"TotalPlayers"}, + {"GameID"}, + {"RecentWinner"} /* array */ + }; + + rc_json_field_t entry_fields[] = { + {"User"}, + {"DateAwarded"} + }; + + memset(response, 0, sizeof(*response)); + rc_buf_init(&response->response.buffer); + + result = rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); + if (result != RC_OK) + return result; + + if (!rc_json_get_required_unum(&response->id, &response->response, &fields[2], "AchievementID")) + return RC_MISSING_VALUE; + if (!rc_json_get_required_object(response_fields, sizeof(response_fields) / sizeof(response_fields[0]), &response->response, &fields[3], "Response")) + return RC_MISSING_VALUE; + + if (!rc_json_get_required_unum(&response->num_awarded, &response->response, &response_fields[0], "NumEarned")) + return RC_MISSING_VALUE; + if (!rc_json_get_required_unum(&response->num_players, &response->response, &response_fields[1], "TotalPlayers")) + return RC_MISSING_VALUE; + if (!rc_json_get_required_unum(&response->game_id, &response->response, &response_fields[2], "GameID")) + return RC_MISSING_VALUE; + + if (!rc_json_get_required_array(&response->num_recently_awarded, &iterator, &response->response, &response_fields[3], "RecentWinner")) + return RC_MISSING_VALUE; + + if (response->num_recently_awarded) { + response->recently_awarded = (rc_api_achievement_awarded_entry_t*)rc_buf_alloc(&response->response.buffer, response->num_recently_awarded * sizeof(rc_api_achievement_awarded_entry_t)); + if (!response->recently_awarded) + return RC_OUT_OF_MEMORY; + + entry = response->recently_awarded; + while (rc_json_get_array_entry_object(entry_fields, sizeof(entry_fields) / sizeof(entry_fields[0]), &iterator)) { + 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")) + return RC_MISSING_VALUE; + entry->awarded = (time_t)timet; + + ++entry; + } + } + + return RC_OK; +} + +void rc_api_destroy_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response) { + rc_buf_destroy(&response->response.buffer); +} + +/* --- Fetch Leaderboard Info --- */ + +int rc_api_init_fetch_leaderboard_info_request(rc_api_request_t* request, const rc_api_fetch_leaderboard_info_request_t* api_params) { + rc_api_url_builder_t builder; + + rc_api_url_build_dorequest_url(request); + + if (api_params->leaderboard_id == 0) + return RC_INVALID_STATE; + + rc_url_builder_init(&builder, &request->buffer, 48); + rc_url_builder_append_str_param(&builder, "r", "lbinfo"); + rc_url_builder_append_unum_param(&builder, "i", api_params->leaderboard_id); + + if (api_params->username) + rc_url_builder_append_str_param(&builder, "u", api_params->username); + else if (api_params->first_entry > 1) + rc_url_builder_append_unum_param(&builder, "o", api_params->first_entry - 1); /* number of entries to skip */ + + rc_url_builder_append_unum_param(&builder, "c", api_params->count); + request->post_data = rc_url_builder_finalize(&builder); + + return builder.result; +} + +int rc_api_process_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response, const char* server_response) { + rc_api_lboard_info_entry_t* entry; + rc_json_field_t iterator; + unsigned timet; + int result; + size_t len; + char format[16]; + + rc_json_field_t fields[] = { + {"Success"}, + {"Error"}, + {"LeaderboardData"} + }; + + rc_json_field_t leaderboarddata_fields[] = { + {"LBID"}, + {"LBFormat"}, + {"LowerIsBetter"}, + {"LBTitle"}, + {"LBDesc"}, + {"LBMem"}, + {"GameID"}, + {"LBAuthor"}, + {"LBCreated"}, + {"LBUpdated"}, + {"Entries"} /* array */ + /* unused fields + {"GameTitle"}, + {"ConsoleID"}, + {"ConsoleName"}, + {"ForumTopicID"}, + {"GameIcon"}, + * unused fields */ + }; + + rc_json_field_t entry_fields[] = { + {"User"}, + {"Rank"}, + {"Index"}, + {"Score"}, + {"DateSubmitted"} + }; + + memset(response, 0, sizeof(*response)); + rc_buf_init(&response->response.buffer); + + result = rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); + if (result != RC_OK) + return result; + + if (!rc_json_get_required_object(leaderboarddata_fields, sizeof(leaderboarddata_fields) / sizeof(leaderboarddata_fields[0]), &response->response, &fields[2], "LeaderboardData")) + return RC_MISSING_VALUE; + + if (!rc_json_get_required_unum(&response->id, &response->response, &leaderboarddata_fields[0], "LBID")) + return RC_MISSING_VALUE; + if (!rc_json_get_required_num(&response->lower_is_better, &response->response, &leaderboarddata_fields[2], "LowerIsBetter")) + return RC_MISSING_VALUE; + if (!rc_json_get_required_string(&response->title, &response->response, &leaderboarddata_fields[3], "LBTitle")) + return RC_MISSING_VALUE; + if (!rc_json_get_required_string(&response->description, &response->response, &leaderboarddata_fields[4], "LBDesc")) + return RC_MISSING_VALUE; + if (!rc_json_get_required_string(&response->definition, &response->response, &leaderboarddata_fields[5], "LBMem")) + return RC_MISSING_VALUE; + if (!rc_json_get_required_unum(&response->game_id, &response->response, &leaderboarddata_fields[6], "GameID")) + return RC_MISSING_VALUE; + if (!rc_json_get_required_string(&response->author, &response->response, &leaderboarddata_fields[7], "LBAuthor")) + return RC_MISSING_VALUE; + if (!rc_json_get_required_datetime(&response->created, &response->response, &leaderboarddata_fields[8], "LBCreated")) + return RC_MISSING_VALUE; + if (!rc_json_get_required_datetime(&response->updated, &response->response, &leaderboarddata_fields[9], "LBUpdated")) + return RC_MISSING_VALUE; + + if (!leaderboarddata_fields[1].value_end) + return RC_MISSING_VALUE; + len = leaderboarddata_fields[1].value_end - leaderboarddata_fields[1].value_start - 2; + if (len < sizeof(format) - 1) { + memcpy(format, leaderboarddata_fields[1].value_start + 1, len); + format[len] = '\0'; + response->format = rc_parse_format(format); + } + else { + response->format = RC_FORMAT_VALUE; + } + + if (!rc_json_get_required_array(&response->num_entries, &iterator, &response->response, &leaderboarddata_fields[10], "Entries")) + return RC_MISSING_VALUE; + + if (response->num_entries) { + response->entries = (rc_api_lboard_info_entry_t*)rc_buf_alloc(&response->response.buffer, response->num_entries * sizeof(rc_api_lboard_info_entry_t)); + if (!response->entries) + return RC_OUT_OF_MEMORY; + + entry = response->entries; + while (rc_json_get_array_entry_object(entry_fields, sizeof(entry_fields) / sizeof(entry_fields[0]), &iterator)) { + if (!rc_json_get_required_string(&entry->username, &response->response, &entry_fields[0], "User")) + return RC_MISSING_VALUE; + + if (!rc_json_get_required_unum(&entry->rank, &response->response, &entry_fields[1], "Rank")) + return RC_MISSING_VALUE; + + if (!rc_json_get_required_unum(&entry->index, &response->response, &entry_fields[2], "Index")) + return RC_MISSING_VALUE; + + 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")) + return RC_MISSING_VALUE; + entry->submitted = (time_t)timet; + + ++entry; + } + } + + return RC_OK; +} + +void rc_api_destroy_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response) { + rc_buf_destroy(&response->response.buffer); +} + +/* --- Fetch Games List --- */ + +int rc_api_init_fetch_games_list_request(rc_api_request_t* request, const rc_api_fetch_games_list_request_t* api_params) { + rc_api_url_builder_t builder; + + rc_api_url_build_dorequest_url(request); + + if (api_params->console_id == 0) + 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); + + request->post_data = rc_url_builder_finalize(&builder); + + return builder.result; +} + +int rc_api_process_fetch_games_list_response(rc_api_fetch_games_list_response_t* response, const char* server_response) { + rc_api_game_list_entry_t* entry; + rc_json_object_field_iterator_t iterator; + int result; + char* end; + + rc_json_field_t fields[] = { + {"Success"}, + {"Error"}, + {"Response"} + }; + + memset(response, 0, sizeof(*response)); + rc_buf_init(&response->response.buffer); + + result = rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); + 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"); + return RC_MISSING_VALUE; + } + + response->num_entries = fields[2].array_size; + rc_buf_reserve(&response->response.buffer, response->num_entries * (32 + sizeof(rc_api_game_list_entry_t))); + + response->entries = (rc_api_game_list_entry_t*)rc_buf_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; + + entry = response->entries; + while (rc_json_get_next_object_field(&iterator)) { + entry->id = strtol(iterator.field.name, &end, 10); + + iterator.field.name = ""; + if (!rc_json_get_string(&entry->name, &response->response.buffer, &iterator.field, "")) + return RC_MISSING_VALUE; + + ++entry; + } + + return RC_OK; +} + +void rc_api_destroy_fetch_games_list_response(rc_api_fetch_games_list_response_t* response) { + rc_buf_destroy(&response->response.buffer); +} diff --git a/deps/rcheevos/src/rapi/rc_api_runtime.c b/deps/rcheevos/src/rapi/rc_api_runtime.c index e7df18cf06..c072f01be1 100644 --- a/deps/rcheevos/src/rapi/rc_api_runtime.c +++ b/deps/rcheevos/src/rapi/rc_api_runtime.c @@ -20,10 +20,9 @@ int rc_api_init_resolve_hash_request(rc_api_request_t* request, const rc_api_res return RC_INVALID_STATE; rc_url_builder_init(&builder, &request->buffer, 48); - if (rc_api_url_build_dorequest(&builder, "gameid", api_params->username, api_params->api_token)) { - rc_url_builder_append_str_param(&builder, "m", api_params->game_hash); - request->post_data = rc_url_builder_finalize(&builder); - } + rc_url_builder_append_str_param(&builder, "r", "gameid"); + rc_url_builder_append_str_param(&builder, "m", api_params->game_hash); + request->post_data = rc_url_builder_finalize(&builder); return builder.result; } @@ -130,7 +129,7 @@ int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* r if (result != RC_OK) return result; - if (!rc_json_get_required_object(patchdata_fields, sizeof(patchdata_fields) / sizeof(patchdata_fields[0]), &response->response, &fields[2], "Response")) + if (!rc_json_get_required_object(patchdata_fields, sizeof(patchdata_fields) / sizeof(patchdata_fields[0]), &response->response, &fields[2], "PatchData")) return RC_MISSING_VALUE; if (!rc_json_get_required_unum(&response->id, &response->response, &patchdata_fields[0], "ID")) @@ -440,8 +439,8 @@ int rc_api_process_submit_lboard_entry_response(rc_api_submit_lboard_entry_respo {"Rank"}, {"Score"} /* unused fields - { "DateSumitted" }, - * unused fields */ + {"DateSubmitted"}, + * unused fields */ }; rc_json_field_t rank_info_fields[] = { diff --git a/deps/rcheevos/src/rcheevos/alloc.c b/deps/rcheevos/src/rcheevos/alloc.c index a387650e99..c361eecee6 100644 --- a/deps/rcheevos/src/rcheevos/alloc.c +++ b/deps/rcheevos/src/rcheevos/alloc.c @@ -144,6 +144,7 @@ void rc_init_parse_state(rc_parse_state_t* parse, void* buffer, lua_State* L, in parse->measured_target = 0; parse->lines_read = 0; parse->has_required_hits = 0; + parse->measured_as_percent = 0; } void rc_destroy_parse_state(rc_parse_state_t* parse) diff --git a/deps/rcheevos/src/rcheevos/compat.c b/deps/rcheevos/src/rcheevos/compat.c index df75b9ed5c..877c64ddb2 100644 --- a/deps/rcheevos/src/rcheevos/compat.c +++ b/deps/rcheevos/src/rcheevos/compat.c @@ -55,6 +55,7 @@ int rc_snprintf(char* buffer, size_t size, const char* format, ...) va_start(args, format); /* assume buffer is large enough and ignore size */ + (void)size; result = vsprintf(buffer, format, args); va_end(args); diff --git a/deps/rcheevos/src/rcheevos/condition.c b/deps/rcheevos/src/rcheevos/condition.c index 7cf81d9b2e..38f0f38e66 100644 --- a/deps/rcheevos/src/rcheevos/condition.c +++ b/deps/rcheevos/src/rcheevos/condition.c @@ -86,6 +86,11 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse case 'i': case 'I': self->type = RC_CONDITION_ADD_ADDRESS; can_modify = 1; break; case 't': case 'T': self->type = RC_CONDITION_TRIGGER; break; case 'z': case 'Z': self->type = RC_CONDITION_RESET_NEXT_IF; break; + case 'g': case 'G': + parse->measured_as_percent = 1; + self->type = RC_CONDITION_MEASURED; + break; + /* e f h j k l s u v w x y */ default: parse->offset = RC_INVALID_CONDITION_TYPE; return 0; } @@ -95,7 +100,7 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse self->type = RC_CONDITION_STANDARD; } - result = rc_parse_operand(&self->operand1, &aux, 1, is_indirect, parse); + result = rc_parse_operand(&self->operand1, &aux, is_indirect, parse); if (result < 0) { parse->offset = result; return 0; @@ -158,7 +163,7 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse break; } - result = rc_parse_operand(&self->operand2, &aux, 1, is_indirect, parse); + result = rc_parse_operand(&self->operand2, &aux, is_indirect, parse); if (result < 0) { parse->offset = result; return 0; diff --git a/deps/rcheevos/src/rcheevos/condset.c b/deps/rcheevos/src/rcheevos/condset.c index caa2771b92..7f73e44647 100644 --- a/deps/rcheevos/src/rcheevos/condset.c +++ b/deps/rcheevos/src/rcheevos/condset.c @@ -18,7 +18,7 @@ static void rc_update_condition_pause(rc_condition_t* condition, int* in_pause) case RC_CONDITION_OR_NEXT: case RC_CONDITION_ADD_ADDRESS: case RC_CONDITION_RESET_NEXT_IF: - condition->pause = *in_pause; + condition->pause = (char)*in_pause; break; default: @@ -140,7 +140,7 @@ rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse, in return self; } -static void rc_condset_update_indirect_memrefs(rc_condset_t* self, rc_condition_t* condition, int processing_pause, rc_eval_state_t* eval_state) { +static void rc_condset_update_indirect_memrefs(rc_condition_t* condition, int processing_pause, rc_eval_state_t* eval_state) { for (; condition != 0; condition = condition->next) { if (condition->pause != processing_pause) continue; @@ -210,7 +210,7 @@ static int rc_test_condset_internal(rc_condset_t* self, int processing_pause, rc } /* STEP 2: evaluate the current condition */ - condition->is_true = rc_test_condition(condition, eval_state); + condition->is_true = (char)rc_test_condition(condition, eval_state); eval_state->add_value = 0; eval_state->add_address = 0; @@ -311,10 +311,10 @@ static int rc_test_condset_internal(rc_condset_t* self, int processing_pause, rc * if the set has any indirect memrefs, manually update them now so the deltas are correct */ if (self->has_indirect_memrefs) { /* first, update any indirect memrefs in the remaining part of the pause subset */ - rc_condset_update_indirect_memrefs(self, condition->next, 1, eval_state); + rc_condset_update_indirect_memrefs(condition->next, 1, eval_state); /* then, update all indirect memrefs in the non-pause subset */ - rc_condset_update_indirect_memrefs(self, self->conditions, 0, eval_state); + rc_condset_update_indirect_memrefs(self->conditions, 0, eval_state); } return 1; @@ -371,7 +371,7 @@ static int rc_test_condset_internal(rc_condset_t* self, int processing_pause, rc /* if not suppressed, update the measured value */ if (measured_value > eval_state->measured_value && can_measure) { eval_state->measured_value = measured_value; - eval_state->measured_from_hits = measured_from_hits; + eval_state->measured_from_hits = (char)measured_from_hits; } return set_valid; @@ -384,8 +384,9 @@ int rc_test_condset(rc_condset_t* self, rc_eval_state_t* eval_state) { } if (self->has_pause) { - if ((self->is_paused = rc_test_condset_internal(self, 1, eval_state))) { - /* one or more Pause conditions exists, if any of them are true, stop processing this group */ + /* one or more Pause conditions exists, if any of them are true, stop processing this group */ + self->is_paused = (char)rc_test_condset_internal(self, 1, eval_state); + if (self->is_paused) { eval_state->primed = 0; return 0; } diff --git a/deps/rcheevos/src/rcheevos/consoleinfo.c b/deps/rcheevos/src/rcheevos/consoleinfo.c index ad6a655a0d..04b049d1c3 100644 --- a/deps/rcheevos/src/rcheevos/consoleinfo.c +++ b/deps/rcheevos/src/rcheevos/consoleinfo.c @@ -136,7 +136,7 @@ const char* rc_console_name(int console_id) return "PC-FX"; case RC_CONSOLE_PC_ENGINE: - return "PCEngine"; + return "PC Engine"; case RC_CONSOLE_PLAYSTATION: return "PlayStation"; @@ -323,19 +323,45 @@ static const rc_memory_region_t _rc_memory_regions_game_gear[] = { static const rc_memory_regions_t rc_memory_regions_game_gear = { _rc_memory_regions_game_gear, 1 }; /* ===== Intellivision ===== */ -/* http://wiki.intellivision.us/index.php%3Ftitle%3DMemory_Map */ +/* http://wiki.intellivision.us/index.php/Memory_Map */ +/* NOTE: Intellivision memory addresses point at 16-bit values. FreeIntv exposes them as little-endian + * 32-bit values. As such, the addresses are off by a factor of 4 _and_ the data is only where we + * expect it on little-endian systems. + */ static const rc_memory_region_t _rc_memory_regions_intellivision[] = { - { 0x000000U, 0x00007FU, 0x000000U, RC_MEMORY_TYPE_VIDEO_RAM, "STIC Registers" }, - { 0x000080U, 0x0000FFU, 0x000080U, RC_MEMORY_TYPE_UNUSED, "" }, - { 0x000100U, 0x00035FU, 0x000100U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, - { 0x000360U, 0x0003FFU, 0x000360U, RC_MEMORY_TYPE_UNUSED, "" }, - { 0x000400U, 0x000FFFU, 0x000400U, RC_MEMORY_TYPE_SYSTEM_RAM, "Cartridge RAM" }, - { 0x001000U, 0x001FFFU, 0x001000U, RC_MEMORY_TYPE_UNUSED, "" }, - { 0x002000U, 0x002FFFU, 0x002000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Cartridge RAM" }, - { 0x003000U, 0x003FFFU, 0x003000U, RC_MEMORY_TYPE_VIDEO_RAM, "Video RAM" }, - { 0x004000U, 0x00FFFFU, 0x004000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Cartridge RAM" }, + /* For backwards compatibility, register a 128-byte chunk of video RAM so the system memory + * will start at $0080. $0000-$007F previously tried to map to the STIC video registers as + * RETRO_MEMORY_VIDEO_RAM, and FreeIntv didn't expose any RETRO_MEMORY_VIDEO_RAM, so the first + * byte of RETRO_MEMORY_SYSTEM_RAM was registered at $0080. The data at $0080 is actually the + * STIC registers (4 bytes each), so we need to provide an arbitrary 128-byte padding that + * claims to be video RAM to ensure the system RAM ends up at the right address. + */ + { 0x000000U, 0x00007FU, 0xFFFFFFU, RC_MEMORY_TYPE_VIDEO_RAM, "" }, + + /* RetroAchievements address = real address x4 + 0x80. + * These all have to map to RETRO_MEMORY_SYSTEM_RAM (even the video-related fields) as the + * entire block is exposed as a single entity by FreeIntv */ + + /* $0000-$007F: STIC registers, $0040-$007F are readonly */ + { 0x000080U, 0x00027FU, 0x000000U, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "STIC Registers" }, + /* $0080-$00FF: unused */ + { 0x000280U, 0x00047FU, 0x000080U, RC_MEMORY_TYPE_UNUSED, "" }, + /* $0100-$035F: system RAM, $0100-$01EF is scratch memory and only 8-bits per address */ + { 0x000480U, 0x000DFFU, 0x000100U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, + /* $0360-$03FF: unused */ + { 0x000E00U, 0x00107FU, 0x000360U, RC_MEMORY_TYPE_UNUSED, "" }, + /* $0400-$0FFF: cartridge RAM */ + { 0x001080U, 0x00407FU, 0x000400U, RC_MEMORY_TYPE_SYSTEM_RAM, "Cartridge RAM" }, + /* $1000-$1FFF: unused */ + { 0x004080U, 0x00807FU, 0x001000U, RC_MEMORY_TYPE_UNUSED, "" }, + /* $2000-$2FFF: cartridge RAM */ + { 0x008080U, 0x00C07FU, 0x002000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Cartridge RAM" }, + /* $3000-$3FFF: video RAM */ + { 0x00C080U, 0x01007FU, 0x003000U, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "Video RAM" }, + /* $4000-$FFFF: cartridge RAM */ + { 0x010080U, 0x04007FU, 0x004000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Cartridge RAM" }, }; -static const rc_memory_regions_t rc_memory_regions_intellivision = { _rc_memory_regions_intellivision, 9 }; +static const rc_memory_regions_t rc_memory_regions_intellivision = { _rc_memory_regions_intellivision, 10 }; /* ===== Magnavox Odyssey 2 ===== */ /* https://sudonull.com/post/76885-Architecture-and-programming-Philips-Videopac-Magnavox-Odyssey-2 */ @@ -447,13 +473,13 @@ static const rc_memory_regions_t rc_memory_regions_pc8800 = { _rc_memory_regions /* ===== PC Engine ===== */ /* http://www.archaicpixels.com/Memory_Map */ -static const rc_memory_region_t _rc_memory_regions_pcengine[] = { +static const rc_memory_region_t _rc_memory_regions_pc_engine[] = { { 0x000000U, 0x001FFFU, 0x1F0000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, { 0x002000U, 0x011FFFU, 0x100000U, RC_MEMORY_TYPE_SYSTEM_RAM, "CD RAM" }, { 0x012000U, 0x041FFFU, 0x0D0000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Super System Card RAM" }, { 0x042000U, 0x0427FFU, 0x1EE000U, RC_MEMORY_TYPE_SAVE_RAM, "CD Battery-backed RAM" } }; -static const rc_memory_regions_t rc_memory_regions_pcengine = { _rc_memory_regions_pcengine, 4 }; +static const rc_memory_regions_t rc_memory_regions_pc_engine = { _rc_memory_regions_pc_engine, 4 }; /* ===== PC-FX ===== */ /* http://daifukkat.su/pcfx/data/memmap.html */ @@ -679,7 +705,7 @@ const rc_memory_regions_t* rc_console_memory_regions(int console_id) return &rc_memory_regions_pc8800; case RC_CONSOLE_PC_ENGINE: - return &rc_memory_regions_pcengine; + return &rc_memory_regions_pc_engine; case RC_CONSOLE_PCFX: return &rc_memory_regions_pcfx; diff --git a/deps/rcheevos/src/rcheevos/lboard.c b/deps/rcheevos/src/rcheevos/lboard.c index 5cc907d3f1..ab454c3b8c 100644 --- a/deps/rcheevos/src/rcheevos/lboard.c +++ b/deps/rcheevos/src/rcheevos/lboard.c @@ -29,10 +29,6 @@ void rc_parse_lboard_internal(rc_lboard_t* self, const char* memaddr, rc_parse_s memaddr += 4; rc_parse_trigger_internal(&self->start, &memaddr, parse); self->start.memrefs = 0; - - if (parse->offset < 0) { - return; - } } else if ((memaddr[0] == 'c' || memaddr[0] == 'C') && (memaddr[1] == 'a' || memaddr[1] == 'A') && @@ -46,10 +42,6 @@ void rc_parse_lboard_internal(rc_lboard_t* self, const char* memaddr, rc_parse_s memaddr += 4; rc_parse_trigger_internal(&self->cancel, &memaddr, parse); self->cancel.memrefs = 0; - - if (parse->offset < 0) { - return; - } } else if ((memaddr[0] == 's' || memaddr[0] == 'S') && (memaddr[1] == 'u' || memaddr[1] == 'U') && @@ -63,10 +55,6 @@ void rc_parse_lboard_internal(rc_lboard_t* self, const char* memaddr, rc_parse_s memaddr += 4; rc_parse_trigger_internal(&self->submit, &memaddr, parse); self->submit.memrefs = 0; - - if (parse->offset < 0) { - return; - } } else if ((memaddr[0] == 'v' || memaddr[0] == 'V') && (memaddr[1] == 'a' || memaddr[1] == 'A') && @@ -80,10 +68,6 @@ void rc_parse_lboard_internal(rc_lboard_t* self, const char* memaddr, rc_parse_s memaddr += 4; rc_parse_value_internal(&self->value, &memaddr, parse); self->value.memrefs = 0; - - if (parse->offset < 0) { - return; - } } else if ((memaddr[0] == 'p' || memaddr[0] == 'P') && (memaddr[1] == 'r' || memaddr[1] == 'R') && @@ -99,20 +83,22 @@ void rc_parse_lboard_internal(rc_lboard_t* self, const char* memaddr, rc_parse_s self->progress = RC_ALLOC(rc_value_t, parse); rc_parse_value_internal(self->progress, &memaddr, parse); self->progress->memrefs = 0; - - if (parse->offset < 0) { - return; - } } - else { + + /* encountered an error parsing one of the parts */ + if (parse->offset < 0) + return; + + /* end of string, or end of quoted string - stop processing */ + if (memaddr[0] == '\0' || memaddr[0] == '\"') + break; + + /* expect two colons between fields */ + if (memaddr[0] != ':' || memaddr[1] != ':') { parse->offset = RC_INVALID_LBOARD_FIELD; return; } - if (memaddr[0] != ':' || memaddr[1] != ':') { - break; - } - memaddr += 2; } diff --git a/deps/rcheevos/src/rcheevos/memref.c b/deps/rcheevos/src/rcheevos/memref.c index 398ef474d7..865abb8f6e 100644 --- a/deps/rcheevos/src/rcheevos/memref.c +++ b/deps/rcheevos/src/rcheevos/memref.c @@ -53,6 +53,11 @@ int rc_parse_memref(const char** memaddr, char* size, unsigned* address) { aux++; switch (*aux++) { + /* ordered by estimated frequency in case compiler doesn't build a jump table */ + case 'h': case 'H': *size = RC_MEMSIZE_8_BITS; break; + case ' ': *size = RC_MEMSIZE_16_BITS; break; + case 'x': case 'X': *size = RC_MEMSIZE_32_BITS; break; + case 'm': case 'M': *size = RC_MEMSIZE_BIT_0; break; case 'n': case 'N': *size = RC_MEMSIZE_BIT_1; break; case 'o': case 'O': *size = RC_MEMSIZE_BIT_2; break; @@ -64,17 +69,21 @@ int rc_parse_memref(const char** memaddr, char* size, unsigned* address) { case 'l': case 'L': *size = RC_MEMSIZE_LOW; break; case 'u': case 'U': *size = RC_MEMSIZE_HIGH; break; case 'k': case 'K': *size = RC_MEMSIZE_BITCOUNT; break; - case 'h': case 'H': *size = RC_MEMSIZE_8_BITS; break; case 'w': case 'W': *size = RC_MEMSIZE_24_BITS; break; - case 'x': case 'X': *size = RC_MEMSIZE_32_BITS; break; + case 'g': case 'G': *size = RC_MEMSIZE_32_BITS_BE; break; + case 'i': case 'I': *size = RC_MEMSIZE_16_BITS_BE; break; + case 'j': case 'J': *size = RC_MEMSIZE_24_BITS_BE; break; + + /* case 'v': case 'V': */ + /* case 'y': case 'Y': 64 bit? */ + /* case 'z': case 'Z': 128 bit? */ case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + /* legacy support - addresses without a size prefix are assumed to be 16-bit */ aux--; - /* fallthrough */ - case ' ': *size = RC_MEMSIZE_16_BITS; break; @@ -97,10 +106,24 @@ int rc_parse_memref(const char** memaddr, char* size, unsigned* address) { static const unsigned char rc_bits_set[16] = { 0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4 }; -unsigned rc_transform_memref_value(unsigned value, char size) -{ +unsigned rc_transform_memref_value(unsigned value, char size) { switch (size) { + case RC_MEMSIZE_8_BITS: + value = (value & 0x000000ff); + break; + + case RC_MEMSIZE_16_BITS: + value = (value & 0x0000ffff); + break; + + case RC_MEMSIZE_24_BITS: + value = (value & 0x00ffffff); + break; + + case RC_MEMSIZE_32_BITS: + break; + case RC_MEMSIZE_BIT_0: value = (value >> 0) & 1; break; @@ -146,6 +169,24 @@ unsigned rc_transform_memref_value(unsigned value, char size) + rc_bits_set[((value >> 4) & 0x0F)]; break; + case RC_MEMSIZE_16_BITS_BE: + value = ((value & 0xFF00) >> 8) | + ((value & 0x00FF) << 8); + break; + + case RC_MEMSIZE_24_BITS_BE: + value = ((value & 0xFF0000) >> 16) | + (value & 0x00FF00) | + ((value & 0x0000FF) << 16); + break; + + case RC_MEMSIZE_32_BITS_BE: + value = ((value & 0xFF000000) >> 24) | + ((value & 0x00FF0000) >> 8) | + ((value & 0x0000FF00) << 8) | + ((value & 0x000000FF) << 24); + break; + default: break; } @@ -153,35 +194,48 @@ unsigned rc_transform_memref_value(unsigned value, char size) return value; } -char rc_memref_shared_size(char size) -{ - switch (size) { - case RC_MEMSIZE_BIT_0: - case RC_MEMSIZE_BIT_1: - case RC_MEMSIZE_BIT_2: - case RC_MEMSIZE_BIT_3: - case RC_MEMSIZE_BIT_4: - case RC_MEMSIZE_BIT_5: - case RC_MEMSIZE_BIT_6: - case RC_MEMSIZE_BIT_7: - case RC_MEMSIZE_LOW: - case RC_MEMSIZE_HIGH: - case RC_MEMSIZE_BITCOUNT: - /* these can all share an 8-bit memref and just mask off the appropriate data in rc_transform_memref_value */ - return RC_MEMSIZE_8_BITS; +/* all sizes less than 8-bits (1 byte) are mapped to 8-bits. 24-bit is mapped to 32-bit + * as we don't expect the client to understand a request for 3 bytes. all other reads are + * mapped to the little-endian read of the same size. */ +static const char rc_memref_shared_sizes[] = { + RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_8_BITS */ + RC_MEMSIZE_16_BITS, /* RC_MEMSIZE_16_BITS */ + RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_24_BITS */ + RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_32_BITS */ + RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_LOW */ + RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_HIGH */ + RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_BIT_0 */ + RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_BIT_1 */ + RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_BIT_2 */ + RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_BIT_3 */ + RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_BIT_4 */ + RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_BIT_5 */ + RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_BIT_6 */ + RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_BIT_7 */ + RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_BITCOUNT */ + RC_MEMSIZE_16_BITS, /* RC_MEMSIZE_16_BITS_BE */ + RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_24_BITS_BE */ + RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_32_BITS_BE */ + RC_MEMSIZE_32_BITS /* RC_MEMSIZE_VARIABLE */ +}; - default: - return size; - } +char rc_memref_shared_size(char size) { + const size_t index = (size_t)size; + if (index >= sizeof(rc_memref_shared_sizes) / sizeof(rc_memref_shared_sizes[0])) + return size; + + return rc_memref_shared_sizes[index]; } static unsigned rc_peek_value(unsigned address, char size, rc_peek_t peek, void* ud) { unsigned value; + char shared_size; if (!peek) return 0; - switch (size) + shared_size = rc_memref_shared_size(size); + switch (shared_size) { case RC_MEMSIZE_8_BITS: value = peek(address, 1, ud); @@ -191,28 +245,17 @@ static unsigned rc_peek_value(unsigned address, char size, rc_peek_t peek, void* value = peek(address, 2, ud); break; - case RC_MEMSIZE_24_BITS: - /* peek 4 bytes - don't expect the caller to understand 24-bit numbers */ - value = peek(address, 4, ud) & 0x00FFFFFF; - break; - case RC_MEMSIZE_32_BITS: value = peek(address, 4, ud); break; default: - if (rc_memref_shared_size(size) == RC_MEMSIZE_8_BITS) - { - value = peek(address, 1, ud); - value = rc_transform_memref_value(value, size); - } - else - { - value = 0; - } - break; + return 0; } + if (shared_size != size) + value = rc_transform_memref_value(value, size); + return value; } @@ -242,17 +285,7 @@ void rc_init_parse_state_memrefs(rc_parse_state_t* parse, rc_memref_t** memrefs) *memrefs = 0; } -unsigned rc_get_memref_value(rc_memref_t* memref, int operand_type, rc_eval_state_t* eval_state) { - /* if this is an indirect reference, handle the indirection. */ - if (memref->value.is_indirect) { - const unsigned new_address = memref->address + eval_state->add_address; - rc_update_memref_value(&memref->value, rc_peek_value(new_address, memref->value.size, eval_state->peek, eval_state->peek_userdata)); - } - - return rc_get_memref_value_value(&memref->value, operand_type); -} - -unsigned rc_get_memref_value_value(rc_memref_value_t* memref, int operand_type) { +static unsigned rc_get_memref_value_value(rc_memref_value_t* memref, int operand_type) { switch (operand_type) { /* most common case explicitly first, even though it could be handled by default case. @@ -271,3 +304,13 @@ unsigned rc_get_memref_value_value(rc_memref_value_t* memref, int operand_type) return memref->prior; } } + +unsigned rc_get_memref_value(rc_memref_t* memref, int operand_type, rc_eval_state_t* eval_state) { + /* if this is an indirect reference, handle the indirection. */ + if (memref->value.is_indirect) { + const unsigned new_address = memref->address + eval_state->add_address; + rc_update_memref_value(&memref->value, rc_peek_value(new_address, memref->value.size, eval_state->peek, eval_state->peek_userdata)); + } + + return rc_get_memref_value_value(&memref->value, operand_type); +} diff --git a/deps/rcheevos/src/rcheevos/operand.c b/deps/rcheevos/src/rcheevos/operand.c index a260b931a8..2f4cbcf4b3 100644 --- a/deps/rcheevos/src/rcheevos/operand.c +++ b/deps/rcheevos/src/rcheevos/operand.c @@ -103,7 +103,7 @@ static int rc_parse_operand_memory(rc_operand_t* self, const char** memaddr, rc_ return ret; size = rc_memref_shared_size(self->size); - self->value.memref = rc_alloc_memref(parse, address, size, is_indirect); + self->value.memref = rc_alloc_memref(parse, address, size, (char)is_indirect); if (parse->offset < 0) return parse->offset; @@ -111,12 +111,13 @@ static int rc_parse_operand_memory(rc_operand_t* self, const char** memaddr, rc_ return RC_OK; } -int rc_parse_operand(rc_operand_t* self, const char** memaddr, int is_trigger, int is_indirect, rc_parse_state_t* parse) { +int rc_parse_operand(rc_operand_t* self, const char** memaddr, int is_indirect, rc_parse_state_t* parse) { const char* aux = *memaddr; char* end; int ret; unsigned long value; int negative; + int allow_decimal = 0; self->size = RC_MEMSIZE_32_BITS; @@ -128,14 +129,11 @@ int rc_parse_operand(rc_operand_t* self, const char** memaddr, int is_trigger, i } value = strtoul(++aux, &end, 16); - - if (end == aux) { + if (end == aux) return RC_INVALID_CONST_OPERAND; - } - if (value > 0xffffffffU) { + if (value > 0xffffffffU) value = 0xffffffffU; - } self->type = RC_OPERAND_CONST; self->value.num = (unsigned)value; @@ -144,47 +142,61 @@ int rc_parse_operand(rc_operand_t* self, const char** memaddr, int is_trigger, i break; case 'f': case 'F': /* floating point constant */ - self->value.dbl = strtod(++aux, &end); - - if (end == aux) { - return RC_INVALID_FP_OPERAND; - } - - if (floor(self->value.dbl) == self->value.dbl) { - self->type = RC_OPERAND_CONST; - self->value.num = (unsigned)floor(self->value.dbl); - } - else { - self->type = RC_OPERAND_FP; - } - - aux = end; - break; - + allow_decimal = 1; + /* fall through */ case 'v': case 'V': /* signed integer constant */ ++aux; - /* fallthrough */ + /* fall through */ case '+': case '-': /* signed integer constant */ negative = 0; - if (*aux == '-') - { + if (*aux == '-') { negative = 1; ++aux; } - else if (*aux == '+') - { + else if (*aux == '+') { ++aux; } value = strtoul(aux, &end, 10); - if (end == aux) { - return RC_INVALID_CONST_OPERAND; + if (*end == '.' && allow_decimal) { + /* custom parser for decimal values to ignore locale */ + unsigned long shift = 1; + unsigned long fraction = 0; + + aux = end + 1; + if (*aux < '0' || *aux > '9') + return RC_INVALID_FP_OPERAND; + + do { + fraction *= 10; + fraction += (*aux - '0'); + shift *= 10; + ++aux; + } while (*aux >= '0' && *aux <= '9'); + + /* if fractional part is 0, convert to an integer constant */ + if (fraction != 0) { + /* non-zero fractional part, convert to double and merge in integer portion */ + const double dbl_fraction = ((double)fraction) / ((double)shift); + if (negative) + self->value.dbl = ((double)(-((long)value))) - dbl_fraction; + else + self->value.dbl = (double)value + dbl_fraction; + + self->type = RC_OPERAND_FP; + break; + } + } else { + /* not a floating point value, make sure something was read and advance the read pointer */ + if (end == aux) + return allow_decimal ? RC_INVALID_FP_OPERAND : RC_INVALID_CONST_OPERAND; + + aux = end; } - if (value > 0x7fffffffU) { + if (value > 0x7fffffffU) value = 0x7fffffffU; - } self->type = RC_OPERAND_CONST; @@ -193,7 +205,6 @@ int rc_parse_operand(rc_operand_t* self, const char** memaddr, int is_trigger, i else self->value.num = (unsigned)value; - aux = end; break; case '0': @@ -202,9 +213,8 @@ int rc_parse_operand(rc_operand_t* self, const char** memaddr, int is_trigger, i default: ret = rc_parse_operand_memory(self, &aux, parse, is_indirect); - if (ret < 0) { + if (ret < 0) return ret; - } break; } @@ -213,14 +223,11 @@ int rc_parse_operand(rc_operand_t* self, const char** memaddr, int is_trigger, i 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); - - if (end == aux) { + if (end == aux) return RC_INVALID_CONST_OPERAND; - } - if (value > 0xffffffffU) { + if (value > 0xffffffffU) value = 0xffffffffU; - } self->type = RC_OPERAND_CONST; self->value.num = (unsigned)value; @@ -231,9 +238,8 @@ int rc_parse_operand(rc_operand_t* self, const char** memaddr, int is_trigger, i case '@': ret = rc_parse_operand_lua(self, &aux, parse); - if (ret < 0) { + if (ret < 0) return ret; - } break; } @@ -340,6 +346,7 @@ unsigned rc_evaluate_operand(rc_operand_t* self, rc_eval_state_t* eval_state) { break; case RC_MEMSIZE_16_BITS: + case RC_MEMSIZE_16_BITS_BE: value = ((value >> 12) & 0x0f) * 1000 + ((value >> 8) & 0x0f) * 100 + ((value >> 4) & 0x0f) * 10 @@ -347,6 +354,7 @@ unsigned rc_evaluate_operand(rc_operand_t* self, rc_eval_state_t* eval_state) { break; case RC_MEMSIZE_24_BITS: + case RC_MEMSIZE_24_BITS_BE: value = ((value >> 20) & 0x0f) * 100000 + ((value >> 16) & 0x0f) * 10000 + ((value >> 12) & 0x0f) * 1000 @@ -356,6 +364,7 @@ unsigned rc_evaluate_operand(rc_operand_t* self, rc_eval_state_t* eval_state) { break; case RC_MEMSIZE_32_BITS: + case RC_MEMSIZE_32_BITS_BE: case RC_MEMSIZE_VARIABLE: value = ((value >> 28) & 0x0f) * 10000000 + ((value >> 24) & 0x0f) * 1000000 @@ -385,14 +394,17 @@ unsigned rc_evaluate_operand(rc_operand_t* self, rc_eval_state_t* eval_state) { break; case RC_MEMSIZE_16_BITS: + case RC_MEMSIZE_16_BITS_BE: value ^= 0xffff; break; case RC_MEMSIZE_24_BITS: + case RC_MEMSIZE_24_BITS_BE: value ^= 0xffffff; break; case RC_MEMSIZE_32_BITS: + case RC_MEMSIZE_32_BITS_BE: case RC_MEMSIZE_VARIABLE: value ^= 0xffffffff; break; diff --git a/deps/rcheevos/src/rcheevos/rc_internal.h b/deps/rcheevos/src/rcheevos/rc_internal.h index 1e11732f69..f4fb3bef27 100644 --- a/deps/rcheevos/src/rcheevos/rc_internal.h +++ b/deps/rcheevos/src/rcheevos/rc_internal.h @@ -99,6 +99,7 @@ typedef struct { int lines_read; char has_required_hits; + char measured_as_percent; } rc_parse_state_t; @@ -116,7 +117,6 @@ int rc_parse_memref(const char** memaddr, char* size, unsigned* address); void rc_update_memref_values(rc_memref_t* memref, rc_peek_t peek, void* ud); void rc_update_memref_value(rc_memref_value_t* memref, unsigned value); unsigned rc_get_memref_value(rc_memref_t* memref, int operand_type, rc_eval_state_t* eval_state); -unsigned rc_get_memref_value_value(rc_memref_value_t* memref, int operand_type); char rc_memref_shared_size(char size); unsigned rc_transform_memref_value(unsigned value, char size); @@ -131,7 +131,7 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse int rc_test_condition(rc_condition_t* self, rc_eval_state_t* eval_state); int rc_evaluate_condition_value(rc_condition_t* self, rc_eval_state_t* eval_state); -int rc_parse_operand(rc_operand_t* self, const char** memaddr, int is_trigger, int is_indirect, rc_parse_state_t* parse); +int rc_parse_operand(rc_operand_t* self, const char** memaddr, int is_indirect, rc_parse_state_t* parse); unsigned rc_evaluate_operand(rc_operand_t* self, rc_eval_state_t* eval_state); void rc_parse_value_internal(rc_value_t* self, const char** memaddr, rc_parse_state_t* parse); diff --git a/deps/rcheevos/src/rcheevos/rc_libretro.c b/deps/rcheevos/src/rcheevos/rc_libretro.c index 4a76e3c2a4..8cb9262af9 100644 --- a/deps/rcheevos/src/rcheevos/rc_libretro.c +++ b/deps/rcheevos/src/rcheevos/rc_libretro.c @@ -89,6 +89,11 @@ static const rc_disallowed_setting_t _rc_disallowed_ppsspp_settings[] = { { NULL, NULL } }; +static const rc_disallowed_setting_t _rc_disallowed_smsplus_settings[] = { + { "smsplus_region", "pal" }, + { NULL, NULL } +}; + static const rc_disallowed_setting_t _rc_disallowed_snes9x_settings[] = { { "snes9x_region", "pal" }, { NULL, NULL } @@ -112,6 +117,7 @@ static const rc_disallowed_core_settings_t rc_disallowed_core_settings[] = { { "PPSSPP", _rc_disallowed_ppsspp_settings }, { "PCSX-ReARMed", _rc_disallowed_pcsx_rearmed_settings }, { "PicoDrive", _rc_disallowed_picodrive_settings }, + { "SMS Plus GX", _rc_disallowed_smsplus_settings }, { "Snes9x", _rc_disallowed_snes9x_settings }, { "Virtual Jaguar", _rc_disallowed_virtual_jaguar_settings }, { NULL, NULL } diff --git a/deps/rcheevos/src/rcheevos/richpresence.c b/deps/rcheevos/src/rcheevos/richpresence.c index fc1c8baf02..87c4fd6302 100644 --- a/deps/rcheevos/src/rcheevos/richpresence.c +++ b/deps/rcheevos/src/rcheevos/richpresence.c @@ -383,7 +383,7 @@ static const char* rc_parse_richpresence_lookup(rc_richpresence_lookup_t* lookup base = 10; } - first = strtoul(line, &endptr, base); + first = (unsigned)strtoul(line, &endptr, base); /* check for a range */ if (*endptr != '-') { @@ -401,7 +401,7 @@ static const char* rc_parse_richpresence_lookup(rc_richpresence_lookup_t* lookup base = 10; } - last = strtoul(line, &endptr, base); + last = (unsigned)strtoul(line, &endptr, base); } /* ignore spaces after the number - was previously ignored as string was split on equals */ @@ -493,7 +493,7 @@ void rc_parse_richpresence_internal(rc_richpresence_t* self, const char* script, memcpy(format, line, chars); format[chars] = '\0'; - lookup->format = rc_parse_format(format); + lookup->format = (unsigned short)rc_parse_format(format); } else { lookup->format = RC_FORMAT_VALUE; } diff --git a/deps/rcheevos/src/rcheevos/runtime.c b/deps/rcheevos/src/rcheevos/runtime.c index 539d646086..39aba71e04 100644 --- a/deps/rcheevos/src/rcheevos/runtime.c +++ b/deps/rcheevos/src/rcheevos/runtime.c @@ -1,5 +1,6 @@ #include "rc_runtime.h" #include "rc_internal.h" +#include "rc_compat.h" #include "../rhash/md5.h" @@ -241,6 +242,33 @@ int rc_runtime_get_achievement_measured(const rc_runtime_t* runtime, unsigned id return 1; } +int rc_runtime_format_achievement_measured(const rc_runtime_t* runtime, unsigned id, char* buffer, size_t buffer_size) +{ + const rc_trigger_t* trigger = rc_runtime_get_achievement(runtime, id); + unsigned value; + if (!buffer || !buffer_size) + return 0; + + if (!trigger || /* no trigger */ + trigger->measured_target == 0 || /* not measured */ + !rc_trigger_state_active(trigger->state)) { /* don't report measured value for inactive triggers */ + *buffer = '\0'; + return 0; + } + + /* cap the value at the target so we can count past the target: "107 >= 100" */ + value = trigger->measured_value; + if (value > trigger->measured_target) + value = trigger->measured_target; + + if (trigger->measured_as_percent) { + unsigned percent = (unsigned)(((unsigned long long)value * 100) / trigger->measured_target); + return snprintf(buffer, buffer_size, "%u%%", percent); + } + + return snprintf(buffer, buffer_size, "%u/%u", value, trigger->measured_target); +} + static void rc_runtime_deactivate_lboard_by_index(rc_runtime_t* self, unsigned index) { if (self->lboards[index].owns_memrefs) { /* if the lboard has one or more memrefs in its buffer, we can't free the buffer. @@ -507,7 +535,7 @@ void rc_runtime_do_frame(rc_runtime_t* self, rc_runtime_event_handler_t event_ha if (new_state == old_state) continue; - /* raise an UNPRIMED event when changing from UNPRIMED to anything else */ + /* raise an UNPRIMED event when changing from PRIMED to anything else */ if (old_state == RC_TRIGGER_STATE_PRIMED) { runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_UNPRIMED; runtime_event.id = self->triggers[i].id; diff --git a/deps/rcheevos/src/rcheevos/runtime_progress.c b/deps/rcheevos/src/rcheevos/runtime_progress.c index 32081d65d9..31c1e25a77 100644 --- a/deps/rcheevos/src/rcheevos/runtime_progress.c +++ b/deps/rcheevos/src/rcheevos/runtime_progress.c @@ -247,7 +247,7 @@ static int rc_runtime_progress_read_condset(rc_runtime_progress_t* progress, rc_ rc_condition_t* cond; unsigned flags; - condset->is_paused = rc_runtime_progress_read_uint(progress); + condset->is_paused = (char)rc_runtime_progress_read_uint(progress); cond = condset->conditions; while (cond) { @@ -257,12 +257,18 @@ static int rc_runtime_progress_read_condset(rc_runtime_progress_t* progress, rc_ cond->is_true = (flags & RC_COND_FLAG_IS_TRUE) ? 1 : 0; if (flags & RC_COND_FLAG_OPERAND1_IS_INDIRECT_MEMREF) { + if (!rc_operand_is_memref(&cond->operand1)) /* this should never happen, but better safe than sorry */ + return RC_INVALID_STATE; + cond->operand1.value.memref->value.value = rc_runtime_progress_read_uint(progress); cond->operand1.value.memref->value.prior = rc_runtime_progress_read_uint(progress); cond->operand1.value.memref->value.changed = (flags & RC_COND_FLAG_OPERAND1_MEMREF_CHANGED_THIS_FRAME) ? 1 : 0; } if (flags & RC_COND_FLAG_OPERAND2_IS_INDIRECT_MEMREF) { + if (!rc_operand_is_memref(&cond->operand2)) /* this should never happen, but better safe than sorry */ + return RC_INVALID_STATE; + cond->operand2.value.memref->value.value = rc_runtime_progress_read_uint(progress); cond->operand2.value.memref->value.prior = rc_runtime_progress_read_uint(progress); cond->operand2.value.memref->value.changed = (flags & RC_COND_FLAG_OPERAND2_MEMREF_CHANGED_THIS_FRAME) ? 1 : 0; @@ -305,7 +311,7 @@ static int rc_runtime_progress_read_trigger(rc_runtime_progress_t* progress, rc_ rc_condset_t* condset; int result; - trigger->state = rc_runtime_progress_read_uint(progress); + trigger->state = (char)rc_runtime_progress_read_uint(progress); trigger->measured_value = rc_runtime_progress_read_uint(progress); if (trigger->requirement) { diff --git a/deps/rcheevos/src/rcheevos/trigger.c b/deps/rcheevos/src/rcheevos/trigger.c index 368b5410cc..56cbc19b0f 100644 --- a/deps/rcheevos/src/rcheevos/trigger.c +++ b/deps/rcheevos/src/rcheevos/trigger.c @@ -13,6 +13,7 @@ void rc_parse_trigger_internal(rc_trigger_t* self, const char** memaddr, rc_pars /* reset in case multiple triggers are parsed by the same parse_state */ parse->measured_target = 0; parse->has_required_hits = 0; + parse->measured_as_percent = 0; if (*aux == 's' || *aux == 'S') { self->requirement = 0; @@ -43,6 +44,7 @@ void rc_parse_trigger_internal(rc_trigger_t* self, const char** memaddr, rc_pars self->measured_value = 0; self->measured_target = parse->measured_target; + self->measured_as_percent = parse->measured_as_percent; self->state = RC_TRIGGER_STATE_WAITING; self->has_hits = 0; self->has_required_hits = parse->has_required_hits; diff --git a/deps/rcheevos/src/rcheevos/value.c b/deps/rcheevos/src/rcheevos/value.c index 8cdc8f937c..f5190d6ed9 100644 --- a/deps/rcheevos/src/rcheevos/value.c +++ b/deps/rcheevos/src/rcheevos/value.c @@ -72,17 +72,14 @@ void rc_parse_legacy_value(rc_value_t* self, const char** memaddr, rc_parse_stat *ptr++ = '*'; buffer_ptr = *memaddr + 1; - if (*buffer_ptr == '-') { - /* negative value automatically needs prefix, 'f' handles both float and digits, so use it */ + if (*buffer_ptr == '-' || *buffer_ptr == '+') + ++buffer_ptr; /* ignore sign */ + + /* if it looks like a floating point number, add the 'f' prefix */ + while (isdigit((unsigned char)*buffer_ptr)) + ++buffer_ptr; + if (*buffer_ptr == '.') *ptr++ = 'f'; - } - else { - /* if it looks like a floating point number, add the 'f' prefix */ - while (isdigit((unsigned char)*buffer_ptr)) - ++buffer_ptr; - if (*buffer_ptr == '.') - *ptr++ = 'f'; - } break; default: @@ -95,7 +92,12 @@ void rc_parse_legacy_value(rc_value_t* self, const char** memaddr, rc_parse_stat buffer_ptr = buffer; cond = rc_parse_condition(&buffer_ptr, parse, 0); - if (parse->offset < 0) { + if (parse->offset < 0) + return; + + if (*buffer_ptr) { + /* whatever we copied as a single condition was not fully consumed */ + parse->offset = RC_INVALID_COMPARISON; return; } diff --git a/deps/rcheevos/src/rhash/hash.c b/deps/rcheevos/src/rhash/hash.c index 81dc08fa67..0f254057f9 100644 --- a/deps/rcheevos/src/rhash/hash.c +++ b/deps/rcheevos/src/rhash/hash.c @@ -118,7 +118,11 @@ void* rc_file_open(const char* path) void* handle; if (!filereader) + { rc_hash_init_custom_filereader(NULL); + if (!filereader) + return NULL; + } handle = filereader->open(path); if (handle && verbose_message_callback) @@ -360,7 +364,7 @@ static int rc_hash_finalize(md5_state_t* md5, char hash[33]) return 1; } -static int rc_hash_buffer(char hash[33], uint8_t* buffer, size_t buffer_size) +static int rc_hash_buffer(char hash[33], const uint8_t* buffer, size_t buffer_size) { md5_state_t md5; md5_init(&md5); @@ -410,9 +414,9 @@ static int rc_hash_cd_file(md5_state_t* md5, void* track_handle, uint32_t sector { md5_append(md5, buffer, (int)num_read); - size -= (unsigned)num_read; - if (size == 0) + if (size <= (unsigned)num_read) break; + size -= (unsigned)num_read; ++sector; if (size >= sizeof(buffer)) @@ -494,7 +498,7 @@ static int rc_hash_3do(char hash[33], const char* path) block_location *= block_size; /* the file size is at offset 0x10 (assume 0x10 is always 0) */ - size = buffer[offset + 0x11] * 65536 + buffer[offset + 0x12] * 256 + buffer[offset + 0x13]; + size = (size_t)buffer[offset + 0x11] * 65536 + buffer[offset + 0x12] * 256 + buffer[offset + 0x13]; if (verbose_message_callback) { @@ -557,7 +561,7 @@ static int rc_hash_3do(char hash[33], const char* path) return rc_hash_finalize(&md5, hash); } -static int rc_hash_7800(char hash[33], uint8_t* buffer, size_t buffer_size) +static int rc_hash_7800(char hash[33], const uint8_t* buffer, size_t buffer_size) { /* if the file contains a header, ignore it */ if (memcmp(&buffer[1], "ATARI7800", 9) == 0) @@ -644,7 +648,7 @@ static int rc_hash_arcade(char hash[33], const char* path) return rc_hash_buffer(hash, (uint8_t*)filename, filename_length); } -static int rc_hash_lynx(char hash[33], uint8_t* buffer, size_t buffer_size) +static int rc_hash_lynx(char hash[33], const uint8_t* buffer, size_t buffer_size) { /* if the file contains a header, ignore it */ if (buffer[0] == 'L' && buffer[1] == 'Y' && buffer[2] == 'N' && buffer[3] == 'X' && buffer[4] == 0) @@ -658,7 +662,7 @@ static int rc_hash_lynx(char hash[33], uint8_t* buffer, size_t buffer_size) return rc_hash_buffer(hash, buffer, buffer_size); } -static int rc_hash_nes(char hash[33], uint8_t* buffer, size_t buffer_size) +static int rc_hash_nes(char hash[33], const uint8_t* buffer, size_t buffer_size) { /* if the file contains a header, ignore it */ if (buffer[0] == 'N' && buffer[1] == 'E' && buffer[2] == 'S' && buffer[3] == 0x1A) @@ -679,6 +683,212 @@ static int rc_hash_nes(char hash[33], uint8_t* buffer, size_t buffer_size) return rc_hash_buffer(hash, buffer, buffer_size); } +static void rc_hash_v64_to_z64(uint8_t* buffer, const uint8_t* stop) +{ + uint32_t* ptr = (uint32_t*)buffer; + const uint32_t* stop32 = (const uint32_t*)stop; + while (ptr < stop32) + { + uint32_t temp = *ptr; + temp = (temp & 0xFF00FF00) >> 8 | + (temp & 0x00FF00FF) << 8; + *ptr++ = temp; + } +} + +static void rc_hash_n64_to_z64(uint8_t* buffer, const uint8_t* stop) +{ + uint32_t* ptr = (uint32_t*)buffer; + const uint32_t* stop32 = (const uint32_t*)stop; + while (ptr < stop32) + { + uint32_t temp = *ptr; + temp = (temp & 0xFF000000) >> 24 | + (temp & 0x00FF0000) >> 8 | + (temp & 0x0000FF00) << 8 | + (temp & 0x000000FF) << 24; + *ptr++ = temp; + } +} + +static int rc_hash_n64(char hash[33], const uint8_t* buffer, size_t buffer_size) +{ + uint8_t* swapbuffer; + uint8_t* stop; + const size_t swapbuffer_size = 65536; + md5_state_t md5; + size_t remaining; + int is_v64; + + if (buffer[0] == 0x80) /* z64 format (big endian [native]) */ + { + return rc_hash_buffer(hash, buffer, buffer_size); + } + else if (buffer[0] == 0x37) /* v64 format (byteswapped) */ + { + rc_hash_verbose("converting v64 to z64"); + is_v64 = 1; + } + else if (buffer[0] == 0x40) /* n64 format (little endian) */ + { + rc_hash_verbose("converting n64 to z64"); + is_v64 = 0; + } + else + { + rc_hash_verbose("Not a Nintendo 64 ROM"); + return 0; + } + + swapbuffer = (uint8_t*)malloc(swapbuffer_size); + if (!swapbuffer) + return rc_hash_error("Could not allocate temporary buffer"); + stop = swapbuffer + swapbuffer_size; + + md5_init(&md5); + + if (buffer_size > MAX_BUFFER_SIZE) + remaining = MAX_BUFFER_SIZE; + else + remaining = (size_t)buffer_size; + + if (verbose_message_callback) + { + char message[64]; + snprintf(message, sizeof(message), "Hashing %u bytes", (unsigned)remaining); + verbose_message_callback(message); + } + + while (remaining >= swapbuffer_size) + { + memcpy(swapbuffer, buffer, swapbuffer_size); + + if (is_v64) + rc_hash_v64_to_z64(swapbuffer, stop); + else + rc_hash_n64_to_z64(swapbuffer, stop); + + md5_append(&md5, swapbuffer, (int)swapbuffer_size); + buffer += swapbuffer_size; + remaining -= swapbuffer_size; + } + + if (remaining > 0) + { + memcpy(swapbuffer, buffer, remaining); + + stop = swapbuffer + remaining; + if (is_v64) + rc_hash_v64_to_z64(swapbuffer, stop); + else + rc_hash_n64_to_z64(swapbuffer, stop); + + md5_append(&md5, swapbuffer, (int)remaining); + } + + free(swapbuffer); + return rc_hash_finalize(&md5, hash); +} + +static int rc_hash_n64_file(char hash[33], const char* path) +{ + uint8_t* buffer; + uint8_t* stop; + const size_t buffer_size = 65536; + md5_state_t md5; + size_t remaining; + void* file_handle; + int is_v64 = 0; + int is_n64 = 0; + + file_handle = rc_file_open(path); + if (!file_handle) + return rc_hash_error("Could not open file"); + + buffer = (uint8_t*)malloc(buffer_size); + if (!buffer) + { + rc_file_close(file_handle); + return rc_hash_error("Could not allocate temporary buffer"); + } + stop = buffer + buffer_size; + + /* read first byte so we can detect endianness */ + rc_file_seek(file_handle, 0, SEEK_SET); + rc_file_read(file_handle, buffer, 1); + + if (buffer[0] == 0x80) /* z64 format (big endian [native]) */ + { + } + else if (buffer[0] == 0x37) /* v64 format (byteswapped) */ + { + rc_hash_verbose("converting v64 to z64"); + is_v64 = 1; + } + else if (buffer[0] == 0x40) /* n64 format (little endian) */ + { + rc_hash_verbose("converting n64 to z64"); + is_n64 = 1; + } + else + { + free(buffer); + rc_file_close(file_handle); + + rc_hash_verbose("Not a Nintendo 64 ROM"); + return 0; + } + + /* calculate total file size */ + rc_file_seek(file_handle, 0, SEEK_END); + remaining = (size_t)rc_file_tell(file_handle); + if (remaining > MAX_BUFFER_SIZE) + remaining = MAX_BUFFER_SIZE; + + if (verbose_message_callback) + { + char message[64]; + snprintf(message, sizeof(message), "Hashing %u bytes", (unsigned)remaining); + verbose_message_callback(message); + } + + /* begin hashing */ + md5_init(&md5); + + rc_file_seek(file_handle, 0, SEEK_SET); + while (remaining >= buffer_size) + { + rc_file_read(file_handle, buffer, (int)buffer_size); + + if (is_v64) + rc_hash_v64_to_z64(buffer, stop); + else if (is_n64) + rc_hash_n64_to_z64(buffer, stop); + + md5_append(&md5, buffer, (int)buffer_size); + remaining -= buffer_size; + } + + if (remaining > 0) + { + rc_file_read(file_handle, buffer, (int)remaining); + + stop = buffer + remaining; + if (is_v64) + rc_hash_v64_to_z64(buffer, stop); + else if (is_n64) + rc_hash_n64_to_z64(buffer, stop); + + md5_append(&md5, buffer, (int)remaining); + } + + /* cleanup */ + rc_file_close(file_handle); + free(buffer); + + return rc_hash_finalize(&md5, hash); +} + static int rc_hash_nintendo_ds(char hash[33], const char* path) { uint8_t header[512]; @@ -790,7 +1000,7 @@ static int rc_hash_nintendo_ds(char hash[33], const char* path) return rc_hash_finalize(&md5, hash); } -static int rc_hash_pce(char hash[33], uint8_t* buffer, size_t buffer_size) +static int rc_hash_pce(char hash[33], const uint8_t* buffer, size_t buffer_size) { /* if the file contains a header, ignore it (expect ROM data to be multiple of 128KB) */ uint32_t calc_size = ((uint32_t)buffer_size / 0x20000) * 0x20000; @@ -1095,6 +1305,7 @@ static int rc_hash_find_playstation_executable(void* track_handle, const char* b size = (unsigned)rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer) - 1); buffer[size] = '\0'; + sector = 0; for (ptr = (char*)buffer; *ptr; ++ptr) { if (strncmp(ptr, boot_key, boot_key_len) == 0) @@ -1170,7 +1381,7 @@ static int rc_hash_psx(char hash[33], const char* path) { rc_hash_error("Could not locate primary executable"); } - else if ((rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer))) < sizeof(buffer)) + else if (rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer)) < sizeof(buffer)) { rc_hash_error("Could not read primary executable"); } @@ -1227,7 +1438,7 @@ static int rc_hash_ps2(char hash[33], const char* path) { rc_hash_error("Could not locate primary executable"); } - else if ((rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer))) < sizeof(buffer)) + else if (rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer)) < sizeof(buffer)) { rc_hash_error("Could not read primary executable"); } @@ -1258,6 +1469,40 @@ static int rc_hash_ps2(char hash[33], const char* path) return result; } +static int rc_hash_psp(char hash[33], const char* path) +{ + void* track_handle; + uint32_t sector; + unsigned size; + md5_state_t md5; + + track_handle = rc_cd_open_track(path, 1); + if (!track_handle) + return rc_hash_error("Could not open track"); + + /* http://www.romhacking.net/forum/index.php?topic=30899.0 + * PSP_GAME/PARAM.SFO contains key/value pairs identifying the game for the system (i.e. serial number, + * name, version). PSP_GAME/SYSDIR/EBOOT.BIN is the encrypted primary executable. + */ + sector = rc_cd_find_file_sector(track_handle, "PSP_GAME\\PARAM.SFO", &size); + if (!sector) + return rc_hash_error("Not a PSP game disc"); + + md5_init(&md5); + if (!rc_hash_cd_file(&md5, track_handle, sector, NULL, size, "PSP_GAME\\PARAM.SFO")) + return 0; + + sector = rc_cd_find_file_sector(track_handle, "PSP_GAME\\SYSDIR\\EBOOT.BIN", &size); + if (!sector) + return rc_hash_error("Could not find primary executable"); + + if (!rc_hash_cd_file(&md5, track_handle, sector, NULL, size, "PSP_GAME\\SYSDIR\\EBOOT.BIN")) + return 0; + + rc_cd_close_track(track_handle); + return rc_hash_finalize(&md5, hash); +} + static int rc_hash_sega_cd(char hash[33], const char* path) { uint8_t buffer[512]; @@ -1287,7 +1532,7 @@ static int rc_hash_sega_cd(char hash[33], const char* path) return rc_hash_buffer(hash, buffer, sizeof(buffer)); } -static int rc_hash_snes(char hash[33], uint8_t* buffer, size_t buffer_size) +static int rc_hash_snes(char hash[33], const uint8_t* buffer, size_t buffer_size) { /* if the file contains a header, ignore it */ uint32_t calc_size = ((uint32_t)buffer_size / 0x2000) * 0x2000; @@ -1302,7 +1547,7 @@ static int rc_hash_snes(char hash[33], uint8_t* buffer, size_t buffer_size) return rc_hash_buffer(hash, buffer, buffer_size); } -int rc_hash_generate_from_buffer(char hash[33], int console_id, uint8_t* buffer, size_t buffer_size) +int rc_hash_generate_from_buffer(char hash[33], int console_id, const uint8_t* buffer, size_t buffer_size) { switch (console_id) { @@ -1327,7 +1572,6 @@ int rc_hash_generate_from_buffer(char hash[33], int console_id, uint8_t* buffer, case RC_CONSOLE_MEGA_DRIVE: case RC_CONSOLE_MSX: case RC_CONSOLE_NEOGEO_POCKET: - case RC_CONSOLE_NINTENDO_64: case RC_CONSOLE_ORIC: case RC_CONSOLE_PC8800: case RC_CONSOLE_POKEMON_MINI: @@ -1349,6 +1593,9 @@ int rc_hash_generate_from_buffer(char hash[33], int console_id, uint8_t* buffer, case RC_CONSOLE_NINTENDO: return rc_hash_nes(hash, buffer, buffer_size); + case RC_CONSOLE_NINTENDO_64: + return rc_hash_n64(hash, buffer, buffer_size); + case RC_CONSOLE_PC_ENGINE: /* NOTE: does not support PCEngine CD */ return rc_hash_pce(hash, buffer, buffer_size); @@ -1357,7 +1604,7 @@ int rc_hash_generate_from_buffer(char hash[33], int console_id, uint8_t* buffer, } } -static int rc_hash_whole_file(char hash[33], int console_id, const char* path) +static int rc_hash_whole_file(char hash[33], const char* path) { md5_state_t md5; uint8_t* buffer; @@ -1608,7 +1855,6 @@ int rc_hash_generate_from_file(char hash[33], int console_id, const char* path) case RC_CONSOLE_MASTER_SYSTEM: case RC_CONSOLE_MEGA_DRIVE: case RC_CONSOLE_NEOGEO_POCKET: - case RC_CONSOLE_NINTENDO_64: case RC_CONSOLE_ORIC: case RC_CONSOLE_POKEMON_MINI: case RC_CONSOLE_SEGA_32X: @@ -1619,7 +1865,7 @@ int rc_hash_generate_from_file(char hash[33], int console_id, const char* path) case RC_CONSOLE_VIRTUAL_BOY: case RC_CONSOLE_WONDERSWAN: /* generic whole-file hash - don't buffer */ - return rc_hash_whole_file(hash, console_id, path); + return rc_hash_whole_file(hash, path); case RC_CONSOLE_MSX: case RC_CONSOLE_PC8800: @@ -1627,7 +1873,7 @@ int rc_hash_generate_from_file(char hash[33], int console_id, const char* path) if (rc_path_compare_extension(path, "m3u")) return rc_hash_generate_from_playlist(hash, console_id, path); - return rc_hash_whole_file(hash, console_id, path); + return rc_hash_whole_file(hash, path); case RC_CONSOLE_ATARI_7800: case RC_CONSOLE_ATARI_LYNX: @@ -1645,6 +1891,9 @@ int rc_hash_generate_from_file(char hash[33], int console_id, const char* path) case RC_CONSOLE_ARCADE: return rc_hash_arcade(hash, path); + case RC_CONSOLE_NINTENDO_64: + return rc_hash_n64_file(hash, path); + case RC_CONSOLE_NINTENDO_DS: return rc_hash_nintendo_ds(hash, path); @@ -1675,6 +1924,9 @@ int rc_hash_generate_from_file(char hash[33], int console_id, const char* path) return rc_hash_ps2(hash, path); + case RC_CONSOLE_PSP: + return rc_hash_psp(hash, path); + case RC_CONSOLE_DREAMCAST: if (rc_path_compare_extension(path, "m3u")) return rc_hash_generate_from_playlist(hash, console_id, path); @@ -1690,7 +1942,7 @@ int rc_hash_generate_from_file(char hash[33], int console_id, const char* path) } } -static void rc_hash_iterator_append_console(struct rc_hash_iterator* iterator, int console_id) +static void rc_hash_iterator_append_console(struct rc_hash_iterator* iterator, uint8_t console_id) { int i = 0; while (iterator->consoles[i] != 0) @@ -1916,8 +2168,9 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* if (rc_path_compare_extension(ext, "iso")) { iterator->consoles[0] = RC_CONSOLE_PLAYSTATION_2; - iterator->consoles[1] = RC_CONSOLE_3DO; - iterator->consoles[2] = RC_CONSOLE_SEGA_CD; /* ASSERT: handles both Sega CD and Saturn */ + iterator->consoles[1] = RC_CONSOLE_PSP; + iterator->consoles[2] = RC_CONSOLE_3DO; + iterator->consoles[3] = RC_CONSOLE_SEGA_CD; /* ASSERT: handles both Sega CD and Saturn */ need_path = 1; } break; @@ -2061,6 +2314,10 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* { iterator->consoles[0] = RC_CONSOLE_VIRTUAL_BOY; } + else if (rc_path_compare_extension(ext, "v64")) + { + iterator->consoles[0] = RC_CONSOLE_NINTENDO_64; + } break; case 'w': @@ -2081,6 +2338,10 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* iterator->consoles[0] = RC_CONSOLE_ARCADE; need_path = 1; } + else if (rc_path_compare_extension(ext, "z64")) + { + iterator->consoles[0] = RC_CONSOLE_NINTENDO_64; + } break; } diff --git a/deps/rcheevos/src/rurl/url.c b/deps/rcheevos/src/rurl/url.c index 1deb479484..a4a25ec734 100644 --- a/deps/rcheevos/src/rurl/url.c +++ b/deps/rcheevos/src/rurl/url.c @@ -6,6 +6,12 @@ #include #include +#if RCHEEVOS_URL_SSL +#define RCHEEVOS_URL_PROTOCOL "https" +#else +#define RCHEEVOS_URL_PROTOCOL "http" +#endif + static int rc_url_encode(char* encoded, size_t len, const char* str) { for (;;) { switch (*str) { @@ -67,7 +73,7 @@ int rc_url_award_cheevo(char* buffer, size_t size, const char* user_name, const written = snprintf( buffer, size, - "http://retroachievements.org/dorequest.php?r=awardachievement&u=%s&t=%s&a=%u&h=%d", + RCHEEVOS_URL_PROTOCOL"://retroachievements.org/dorequest.php?r=awardachievement&u=%s&t=%s&a=%u&h=%d", urle_user_name, urle_login_token, cheevo_id, @@ -106,7 +112,7 @@ int rc_url_submit_lboard(char* buffer, size_t size, const char* user_name, const written = snprintf( buffer, size, - "http://retroachievements.org/dorequest.php?r=submitlbentry&u=%s&t=%s&i=%u&s=%d&v=%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + RCHEEVOS_URL_PROTOCOL"://retroachievements.org/dorequest.php?r=submitlbentry&u=%s&t=%s&i=%u&s=%d&v=%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", urle_user_name, urle_login_token, lboard_id, @@ -122,7 +128,7 @@ int rc_url_get_gameid(char* buffer, size_t size, const char* hash) { int written = snprintf( buffer, size, - "http://retroachievements.org/dorequest.php?r=gameid&m=%s", + RCHEEVOS_URL_PROTOCOL"://retroachievements.org/dorequest.php?r=gameid&m=%s", hash ); @@ -145,7 +151,7 @@ int rc_url_get_patch(char* buffer, size_t size, const char* user_name, const cha written = snprintf( buffer, size, - "http://retroachievements.org/dorequest.php?r=patch&u=%s&t=%s&g=%u", + RCHEEVOS_URL_PROTOCOL"://retroachievements.org/dorequest.php?r=patch&u=%s&t=%s&g=%u", urle_user_name, urle_login_token, gameid @@ -181,7 +187,7 @@ int rc_url_login_with_password(char* buffer, size_t size, const char* user_name, written = snprintf( buffer, size, - "http://retroachievements.org/dorequest.php?r=login&u=%s&p=%s", + RCHEEVOS_URL_PROTOCOL"://retroachievements.org/dorequest.php?r=login&u=%s&p=%s", urle_user_name, urle_password ); @@ -205,7 +211,7 @@ int rc_url_login_with_token(char* buffer, size_t size, const char* user_name, co written = snprintf( buffer, size, - "http://retroachievements.org/dorequest.php?r=login&u=%s&t=%s", + RCHEEVOS_URL_PROTOCOL"://retroachievements.org/dorequest.php?r=login&u=%s&t=%s", urle_user_name, urle_login_token ); @@ -229,7 +235,7 @@ int rc_url_get_unlock_list(char* buffer, size_t size, const char* user_name, con written = snprintf( buffer, size, - "http://retroachievements.org/dorequest.php?r=unlocks&u=%s&t=%s&g=%u&h=%d", + RCHEEVOS_URL_PROTOCOL"://retroachievements.org/dorequest.php?r=unlocks&u=%s&t=%s&g=%u&h=%d", urle_user_name, urle_login_token, gameid, @@ -255,7 +261,7 @@ int rc_url_post_playing(char* buffer, size_t size, const char* user_name, const written = snprintf( buffer, size, - "http://retroachievements.org/dorequest.php?r=postactivity&u=%s&t=%s&a=3&m=%u", + RCHEEVOS_URL_PROTOCOL"://retroachievements.org/dorequest.php?r=postactivity&u=%s&t=%s&a=3&m=%u", urle_user_name, urle_login_token, gameid @@ -300,8 +306,7 @@ static int rc_url_append_unum(char* buffer, size_t buffer_size, size_t* buffer_o char num[16]; int chars = snprintf(num, sizeof(num), "%u", value); - if (chars + written < (int)buffer_size) - { + if (chars + written < (int)buffer_size) { memcpy(&buffer[written], num, chars + 1); *buffer_offset = written + chars; return 0; @@ -314,13 +319,11 @@ static int rc_url_append_unum(char* buffer, size_t buffer_size, size_t* buffer_o static int rc_url_append_str(char* buffer, size_t buffer_size, size_t* buffer_offset, const char* param, const char* value) { int written = rc_url_append_param_equals(buffer, buffer_size, *buffer_offset, param); - if (written > 0) - { + if (written > 0) { buffer += written; buffer_size -= written; - if (rc_url_encode(buffer, buffer_size, value) == 0) - { + if (rc_url_encode(buffer, buffer_size, value) == 0) { written += (int)strlen(buffer); *buffer_offset = written; return 0; @@ -333,7 +336,7 @@ static int rc_url_append_str(char* buffer, size_t buffer_size, size_t* buffer_of static int rc_url_build_dorequest(char* url_buffer, size_t url_buffer_size, size_t* buffer_offset, const char* api, const char* user_name) { - const char* base_url = "http://retroachievements.org/dorequest.php"; + const char* base_url = RCHEEVOS_URL_PROTOCOL"://retroachievements.org/dorequest.php"; size_t written = strlen(base_url); int failure = 0; @@ -343,7 +346,8 @@ static int rc_url_build_dorequest(char* url_buffer, size_t url_buffer_size, size url_buffer[written++] = '?'; failure |= rc_url_append_str(url_buffer, url_buffer_size, &written, "r", api); - failure |= rc_url_append_str(url_buffer, url_buffer_size, &written, "u", user_name); + if (user_name) + failure |= rc_url_append_str(url_buffer, url_buffer_size, &written, "u", user_name); *buffer_offset += written; return failure; @@ -371,3 +375,28 @@ int rc_url_ping(char* url_buffer, size_t url_buffer_size, char* post_buffer, siz return failure; } + +int rc_url_get_lboard_entries(char* buffer, size_t size, unsigned lboard_id, unsigned first_index, unsigned count) +{ + size_t written = 0; + int failure = rc_url_build_dorequest(buffer, size, &written, "lbinfo", NULL); + failure |= rc_url_append_unum(buffer, size, &written, "i", lboard_id); + if (first_index > 1) + failure |= rc_url_append_unum(buffer, size, &written, "o", first_index - 1); + failure |= rc_url_append_unum(buffer, size, &written, "c", count); + + return failure; +} + +int rc_url_get_lboard_entries_near_user(char* buffer, size_t size, unsigned lboard_id, const char* user_name, unsigned count) +{ + size_t written = 0; + int failure = rc_url_build_dorequest(buffer, size, &written, "lbinfo", NULL); + failure |= rc_url_append_unum(buffer, size, &written, "i", lboard_id); + failure |= rc_url_append_str(buffer, size, &written, "u", user_name); + failure |= rc_url_append_unum(buffer, size, &written, "c", count); + + return failure; +} + +#undef RCHEEVOS_URL_PROTOCOL