diff --git a/Makefile.common b/Makefile.common index 43d9cf8c6f..981d5134d9 100644 --- a/Makefile.common +++ b/Makefile.common @@ -1851,18 +1851,21 @@ ifeq ($(HAVE_NETWORKING), 1) cheevos-new/parser.o \ cheevos-new/hash.o \ $(LIBRETRO_COMM_DIR)/formats/cdfs/cdfs.o \ - deps/rcheevos/src/rcheevos/trigger.o \ - deps/rcheevos/src/rcheevos/condset.o \ - deps/rcheevos/src/rcheevos/condition.o \ - deps/rcheevos/src/rcheevos/operand.o \ - deps/rcheevos/src/rcheevos/term.o \ - deps/rcheevos/src/rcheevos/expression.o \ - deps/rcheevos/src/rcheevos/value.o \ - deps/rcheevos/src/rcheevos/lboard.o \ deps/rcheevos/src/rcheevos/alloc.o \ + deps/rcheevos/src/rcheevos/compat.o \ + deps/rcheevos/src/rcheevos/condition.o \ + deps/rcheevos/src/rcheevos/condset.o \ + deps/rcheevos/src/rcheevos/consoleinfo.o \ deps/rcheevos/src/rcheevos/format.o \ + deps/rcheevos/src/rcheevos/lboard.o \ deps/rcheevos/src/rcheevos/memref.o \ + deps/rcheevos/src/rcheevos/operand.o \ deps/rcheevos/src/rcheevos/richpresence.o \ + deps/rcheevos/src/rcheevos/runtime.o \ + deps/rcheevos/src/rcheevos/runtime_progress.o \ + deps/rcheevos/src/rcheevos/trigger.o \ + deps/rcheevos/src/rcheevos/value.o \ + deps/rcheevos/src/rhash/hash.o \ deps/rcheevos/src/rurl/url.o ifeq ($(HAVE_LUA), 1) diff --git a/cheevos-new/cheevos.c b/cheevos-new/cheevos.c index 5ea558f276..5ce829c547 100644 --- a/cheevos-new/cheevos.c +++ b/cheevos-new/cheevos.c @@ -23,7 +23,7 @@ #include #include #include -#include +#include <../libretro-common/include/rhash.h> #include #include #include @@ -81,9 +81,6 @@ /* Define this macro to dump all cheevos' addresses. */ #undef CHEEVOS_DUMP_ADDRS -/* Define this macro to remove HTTP timeouts. */ -#undef CHEEVOS_NO_TIMEOUT - /* Define this macro to load a JSON file from disk instead of downloading * from retroachievements.org. */ #undef CHEEVOS_JSON_OVERRIDE @@ -92,7 +89,7 @@ * that name. */ #undef CHEEVOS_SAVE_JSON - /* Define this macro to log URLs. */ +/* Define this macro to log URLs. */ #undef CHEEVOS_LOG_URLS /* Define this macro to have the password and token logged. THIS WILL DISCLOSE @@ -311,69 +308,94 @@ static void rcheevos_get_user_agent(char* buffer) *ptr = '\0'; } -static void rcheevos_log_url(const char* format, const char* url) +#ifdef CHEEVOS_LOG_URLS +static void rcheevos_filter_url_param(char* url, char* param) +{ + char* start; + char* next; + size_t param_len = strlen(param); + + start = strchr(url, '?'); + if (!start) + start = url; + else + ++start; + + do + { + next = strchr(start, '&'); + + if (start[param_len] == '=' && memcmp(start, param, param_len) == 0) + { + if (next) + strcpy(start, next + 1); + else if (start > url) + start[-1] = '\0'; + else + *start = '\0'; + + return; + } + + if (!next) + return; + + start = next + 1; + } while (1); +} +#endif + +static void rcheevos_log_url(const char* api, const char* url) { #ifdef CHEEVOS_LOG_URLS -#ifdef CHEEVOS_LOG_PASSWORD - CHEEVOS_LOG(format, url); -#else + #ifdef CHEEVOS_LOG_PASSWORD + CHEEVOS_LOG(RCHEEVOS_TAG "%s: %s\n", api, url); + #else char copy[256]; - char* aux = NULL; - char* next = NULL; - - if (!string_is_empty(url)) - strlcpy(copy, url, sizeof(copy)); - - aux = strstr(copy, "?p="); - - if (!aux) - aux = strstr(copy, "&p="); - - if (aux) - { - aux += 3; - next = strchr(aux, '&'); - - if (next) - { - do - { - *aux++ = *next++; - } while (next[-1] != 0); - } - else - *aux = 0; - } - - aux = strstr(copy, "?t="); - - if (!aux) - aux = strstr(copy, "&t="); - - if (aux) - { - aux += 3; - next = strchr(aux, '&'); - - if (next) - { - do - { - *aux++ = *next++; - } while (next[-1] != 0); - } - else - *aux = 0; - } - - CHEEVOS_LOG(format, copy); -#endif + strlcpy(copy, url, sizeof(copy)); + rcheevos_filter_url_param(copy, "p"); + rcheevos_filter_url_param(copy, "t"); + CHEEVOS_LOG(RCHEEVOS_TAG "%s: %s\n", api, copy); + #endif #else - (void)format; + (void)api; (void)url; #endif } +static void rcheevos_log_post_url(const char* api, const char* url, const char* post) +{ +#ifdef CHEEVOS_LOG_URLS + #ifdef CHEEVOS_LOG_PASSWORD + if (post && post[0]) + CHEEVOS_LOG(RCHEEVOS_TAG "%s: %s&%s\n", api, url, post); + else + CHEEVOS_LOG(RCHEEVOS_TAG "%s: %s\n", api, url); + #else + if (post && post[0]) + { + char post_copy[2048]; + strlcpy(post_copy, post, sizeof(post_copy)); + rcheevos_filter_url_param(post_copy, "p"); + rcheevos_filter_url_param(post_copy, "t"); + + if (post_copy[0]) + CHEEVOS_LOG(RCHEEVOS_TAG "%s: %s&%s\n", api, url, post_copy); + else + CHEEVOS_LOG(RCHEEVOS_TAG "%s: %s\n", api, url); + } + else + { + CHEEVOS_LOG(RCHEEVOS_TAG "%s: %s\n", api, url); + } + #endif +#else + (void)api; + (void)url; + (void)post; +#endif +} + static retro_time_t rcheevos_async_send_rich_presence(rcheevos_async_io_request* request); static void rcheevos_async_award_achievement(rcheevos_async_io_request* request); static void rcheevos_async_submit_lboard(rcheevos_async_io_request* request); @@ -442,39 +464,6 @@ static void rcheevos_async_task_callback(retro_task_t* task, void* task_data, vo } } -static const char* rcheevos_rc_error(int ret) -{ - switch (ret) - { - case RC_OK: return "Ok"; - case RC_INVALID_LUA_OPERAND: return "Invalid Lua operand"; - case RC_INVALID_MEMORY_OPERAND: return "Invalid memory operand"; - case RC_INVALID_CONST_OPERAND: return "Invalid constant operand"; - case RC_INVALID_FP_OPERAND: return "Invalid floating-point operand"; - case RC_INVALID_CONDITION_TYPE: return "Invalid condition type"; - case RC_INVALID_OPERATOR: return "Invalid operator"; - case RC_INVALID_REQUIRED_HITS: return "Invalid required hits"; - case RC_DUPLICATED_START: return "Duplicated start condition"; - case RC_DUPLICATED_CANCEL: return "Duplicated cancel condition"; - case RC_DUPLICATED_SUBMIT: return "Duplicated submit condition"; - case RC_DUPLICATED_VALUE: return "Duplicated value expression"; - case RC_DUPLICATED_PROGRESS: return "Duplicated progress expression"; - case RC_MISSING_START: return "Missing start condition"; - case RC_MISSING_CANCEL: return "Missing cancel condition"; - case RC_MISSING_SUBMIT: return "Missing submit condition"; - case RC_MISSING_VALUE: return "Missing value expression"; - case RC_INVALID_LBOARD_FIELD: return "Invalid field in leaderboard"; - case RC_MISSING_DISPLAY_STRING: return "Missing display string"; - case RC_OUT_OF_MEMORY: return "Out of memory"; - case RC_INVALID_VALUE_FLAG: return "Invalid flag in value expression"; - case RC_MISSING_VALUE_MEASURED: return "Missing measured flag in value expression"; - case RC_MULTIPLE_MEASURED: return "Multiple measured targets"; - case RC_INVALID_MEASURED_TARGET: return "Invalid measured target"; - - default: return "Unknown error"; - } -} - static int rcheevos_parse(const char* json) { char buffer[256]; @@ -604,7 +593,7 @@ static int rcheevos_parse(const char* json) if (res < 0) { snprintf(buffer, sizeof(buffer), "Error in achievement %d \"%s\": %s", - cheevo->info->id, cheevo->info->title, rcheevos_rc_error(res)); + cheevo->info->id, cheevo->info->title, rc_error_str(res)); if (settings->bools.cheevos_verbose_enable) runloop_msg_queue_push(buffer, 0, 4 * 60, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); @@ -641,7 +630,7 @@ static int rcheevos_parse(const char* json) if (res < 0) { snprintf(buffer, sizeof(buffer), "Error in leaderboard %d \"%s\": %s", - lboard->info->id, lboard->info->title, rcheevos_rc_error(res)); + lboard->info->id, lboard->info->title, rc_error_str(res)); if (settings->bools.cheevos_verbose_enable) runloop_msg_queue_push(buffer, 0, 4 * 60, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); @@ -671,7 +660,7 @@ static int rcheevos_parse(const char* json) int buffer_size = rc_richpresence_size(rcheevos_locals.patchdata.richpresence_script); if (buffer_size <= 0) { - snprintf(buffer, sizeof(buffer), "Error in rich presence: %s", rcheevos_rc_error(buffer_size)); + snprintf(buffer, sizeof(buffer), "Error in rich presence: %s", rc_error_str(buffer_size)); if (settings->bools.cheevos_verbose_enable) runloop_msg_queue_push(buffer, 0, 4 * 60, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); @@ -721,7 +710,7 @@ static void rcheevos_async_award_achievement(rcheevos_async_io_request* request) { char buffer[256]; settings_t *settings = config_get_ptr(); - int ret = rc_url_award_cheevo(buffer, sizeof(buffer), settings->arrays.cheevos_username, rcheevos_locals.token, request->id, request->hardcore); + int ret = rc_url_award_cheevo(buffer, sizeof(buffer), settings->arrays.cheevos_username, rcheevos_locals.token, request->id, request->hardcore, rcheevos_locals.hash); if (ret != 0) { @@ -730,7 +719,7 @@ static void rcheevos_async_award_achievement(rcheevos_async_io_request* request) return; } - rcheevos_log_url(RCHEEVOS_TAG "rc_url_award_cheevo: %s\n", buffer); + rcheevos_log_url("rc_url_award_cheevo", buffer); task_push_http_transfer_with_user_agent(buffer, true, NULL, request->user_agent, rcheevos_async_task_callback, request); } @@ -907,7 +896,7 @@ static void rcheevos_async_submit_lboard(rcheevos_async_io_request* request) char buffer[256]; settings_t *settings = config_get_ptr(); int ret = rc_url_submit_lboard(buffer, sizeof(buffer), settings->arrays.cheevos_username, - rcheevos_locals.token, request->id, request->value, rcheevos_locals.hash); + rcheevos_locals.token, request->id, request->value); if (ret != 0) { @@ -916,7 +905,7 @@ static void rcheevos_async_submit_lboard(rcheevos_async_io_request* request) return; } - rcheevos_log_url(RCHEEVOS_TAG "rc_url_submit_lboard: %s\n", buffer); + rcheevos_log_url("rc_url_submit_lboard", buffer); task_push_http_transfer_with_user_agent(buffer, true, NULL, request->user_agent, rcheevos_async_task_callback, request); } @@ -971,39 +960,33 @@ static void rcheevos_test_leaderboards(void) switch (rc_evaluate_lboard(lboard->lboard, &lboard->last_value, rcheevos_peek, NULL, NULL)) { default: - case RC_LBOARD_INACTIVE: break; - case RC_LBOARD_ACTIVE: - /* this is where we would update the onscreen tracker */ - break; - - case RC_LBOARD_TRIGGERED: + case RC_LBOARD_STATE_TRIGGERED: rcheevos_lboard_submit(lboard); break; - case RC_LBOARD_CANCELED: - { + case RC_LBOARD_STATE_CANCELED: CHEEVOS_LOG(RCHEEVOS_TAG "Cancel leaderboard %s\n", lboard->info->title); lboard->active = 0; runloop_msg_queue_push("Leaderboard attempt cancelled!", 0, 2 * 60, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); break; - } - case RC_LBOARD_STARTED: - { - char buffer[256]; + case RC_LBOARD_STATE_STARTED: + if (!lboard->active) + { + char buffer[256]; - CHEEVOS_LOG(RCHEEVOS_TAG "Leaderboard started: %s\n", lboard->info->title); - lboard->active = 1; + CHEEVOS_LOG(RCHEEVOS_TAG "Leaderboard started: %s\n", lboard->info->title); + lboard->active = 1; - snprintf(buffer, sizeof(buffer), - "Leaderboard Active: %s", lboard->info->title); - runloop_msg_queue_push(buffer, 0, 2 * 60, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); - runloop_msg_queue_push(lboard->info->description, 0, 3 * 60, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + snprintf(buffer, sizeof(buffer), + "Leaderboard Active: %s", lboard->info->title); + runloop_msg_queue_push(buffer, 0, 2 * 60, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + runloop_msg_queue_push(lboard->info->description, 0, 3 * 60, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + } break; - } } if (rcheevos_locals.invalid_peek_address) @@ -1046,30 +1029,31 @@ static retro_time_t rcheevos_async_send_rich_presence(rcheevos_async_io_request* { char url[256], post_data[1024]; + int ret = rc_url_ping(url, sizeof(url), post_data, sizeof(post_data), + cheevos_username, rcheevos_locals.token, rcheevos_locals.patchdata.game_id, + rcheevos_locals.richpresence.evaluation); - snprintf(url, sizeof(url), - "http://retroachievements.org/dorequest.php?r=ping&u=%s&t=%s", - cheevos_username, rcheevos_locals.token); - - if (rcheevos_locals.richpresence.evaluation[0]) + if (ret < 0) { - char* tmp = NULL; - net_http_urlencode(&tmp, rcheevos_locals.richpresence.evaluation); - snprintf(post_data, sizeof(post_data), "g=%u&m=%s", rcheevos_locals.patchdata.game_id, tmp); - CHEEVOS_FREE(tmp); - -#ifdef HAVE_DISCORD - if (settings->bools.discord_enable) - discord_update(DISCORD_PRESENCE_RETROACHIEVEMENTS, false); -#endif + CHEEVOS_ERR(RCHEEVOS_TAG "buffer too small to create URL\n"); } else - snprintf(post_data, sizeof(post_data), "g=%u", rcheevos_locals.patchdata.game_id); + { + rcheevos_log_post_url("rc_url_ping", url, post_data); - rcheevos_get_user_agent(request->user_agent); - task_push_http_post_transfer_with_user_agent(url, post_data, true, "POST", request->user_agent, NULL, NULL); + rcheevos_get_user_agent(request->user_agent); + task_push_http_post_transfer_with_user_agent(url, post_data, true, "POST", request->user_agent, NULL, NULL); + } } +#ifdef HAVE_DISCORD + if (rcheevos_locals.richpresence.evaluation[0]) + { + if (settings->bools.discord_enable) + discord_update(DISCORD_PRESENCE_RETROACHIEVEMENTS, false); + } +#endif + /* Update rich presence every two minutes */ if (settings->bools.cheevos_richpresence_enable) return cpu_features_get_time_usec() + CHEEVOS_PING_FREQUENCY; @@ -1109,15 +1093,7 @@ void rcheevos_reset_game(void) rc_reset_lboard(lboard->lboard); if (lboard->active) - { lboard->active = 0; - - /* This ensures the leaderboard won't restart - * until the start trigger is false for at - * least one frame */ - if (lboard->lboard) - lboard->lboard->submitted = 1; - } } rcheevos_locals.richpresence.last_update = cpu_features_get_time_usec(); @@ -2463,14 +2439,6 @@ found: } memcpy(coro->last_hash, coro->hash, sizeof(coro->hash)); - size = rc_url_get_gameid(coro->url, sizeof(coro->url), coro->hash); - - if (size < 0) - { - CHEEVOS_ERR(RCHEEVOS_TAG "buffer too small to create URL\n"); - CORO_RET(); - } - sprintf(rcheevos_locals.hash, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", coro->hash[0], coro->hash[1], coro->hash[2], coro->hash[3], coro->hash[4], coro->hash[5], coro->hash[6], coro->hash[7], @@ -2478,7 +2446,16 @@ found: coro->hash[12], coro->hash[13], coro->hash[14], coro->hash[15]); CHEEVOS_LOG(RCHEEVOS_TAG "checking %s\n", rcheevos_locals.hash); - rcheevos_log_url(RCHEEVOS_TAG "rc_url_get_gameid: %s\n", coro->url); + + size = rc_url_get_gameid(coro->url, sizeof(coro->url), rcheevos_locals.hash); + + if (size < 0) + { + CHEEVOS_ERR(RCHEEVOS_TAG "buffer too small to create URL\n"); + CORO_RET(); + } + + rcheevos_log_url("rc_url_get_gameid", coro->url); CORO_GOSUB(RCHEEVOS_HTTP_GET); if (!coro->json) @@ -2511,7 +2488,7 @@ found: CORO_STOP(); } - rcheevos_log_url(RCHEEVOS_TAG "rc_url_get_patch: %s\n", coro->url); + rcheevos_log_url("rc_url_get_patch", coro->url); CORO_GOSUB(RCHEEVOS_HTTP_GET); if (!coro->json) @@ -2651,21 +2628,36 @@ found: } if (string_is_empty(coro->settings->arrays.cheevos_token)) + { ret = rc_url_login_with_password(coro->url, sizeof(coro->url), coro->settings->arrays.cheevos_username, coro->settings->arrays.cheevos_password); + + if (ret == RC_OK) + { + CHEEVOS_LOG(RCHEEVOS_TAG "attempting to login %s (with password)\n", coro->settings->arrays.cheevos_username); + rcheevos_log_url("rc_url_login_with_password", coro->url); + } + } else + { ret = rc_url_login_with_token(coro->url, sizeof(coro->url), coro->settings->arrays.cheevos_username, coro->settings->arrays.cheevos_token); + if (ret == RC_OK) + { + CHEEVOS_LOG(RCHEEVOS_TAG "attempting to login %s (with token)\n", coro->settings->arrays.cheevos_username); + rcheevos_log_url("rc_url_login_with_token", coro->url); + } + } + if (ret < 0) { CHEEVOS_ERR(RCHEEVOS_TAG "buffer too small to create URL\n"); CORO_STOP(); } - rcheevos_log_url(RCHEEVOS_TAG "rc_url_login_with_password: %s\n", coro->url); CORO_GOSUB(RCHEEVOS_HTTP_GET); if (!coro->json) @@ -2686,6 +2678,7 @@ found: tok); runloop_msg_queue_push(msg, 0, 5 * 60, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); *coro->settings->arrays.cheevos_token = 0; + CHEEVOS_ERR(RCHEEVOS_TAG "login error: %s\n", tok); CHEEVOS_FREE(coro->json); CORO_STOP(); @@ -2703,6 +2696,7 @@ found: runloop_msg_queue_push(msg, 0, 3 * 60, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); } + CHEEVOS_LOG(RCHEEVOS_TAG "logged in successfully\n"); strlcpy(rcheevos_locals.token, tok, sizeof(rcheevos_locals.token)); @@ -2838,7 +2832,7 @@ found: CORO_STOP(); } - rcheevos_log_url(RCHEEVOS_TAG "rc_url_get_unlock_list: %s\n", coro->url); + rcheevos_log_url("rc_url_get_unlock_list", coro->url); CORO_GOSUB(RCHEEVOS_HTTP_GET); if (coro->json) @@ -2862,15 +2856,19 @@ found: *************************************************************************/ CORO_SUB(RCHEEVOS_PLAYING) - snprintf( - coro->url, sizeof(coro->url), - "http://retroachievements.org/dorequest.php?r=postactivity&u=%s&t=%s&a=3&m=%u", + { + int ret = rc_url_post_playing(coro->url, sizeof(coro->url), coro->settings->arrays.cheevos_username, - rcheevos_locals.token, coro->gameid - ); + rcheevos_locals.token, coro->gameid); - coro->url[sizeof(coro->url) - 1] = 0; - rcheevos_log_url(RCHEEVOS_TAG "url to post the 'playing' activity: %s\n", coro->url); + if (ret < 0) + { + CHEEVOS_ERR(RCHEEVOS_TAG "buffer too small to create URL\n"); + CORO_STOP(); + } + } + + rcheevos_log_url("rc_url_post_playing", coro->url); CORO_GOSUB(RCHEEVOS_HTTP_GET); diff --git a/cheevos-new/fixup.c b/cheevos-new/fixup.c index 29783424f3..1a199caf2f 100644 --- a/cheevos-new/fixup.c +++ b/cheevos-new/fixup.c @@ -21,6 +21,7 @@ #include "../core.h" #include "../deps/rcheevos/include/rcheevos.h" +#include "../deps/rcheevos/include/rconsoles.h" static int rcheevos_cmpaddr(const void* e1, const void* e2) { diff --git a/deps/rcheevos/CHANGELOG.md b/deps/rcheevos/CHANGELOG.md index 8d5a48ea9e..2146f17700 100644 --- a/deps/rcheevos/CHANGELOG.md +++ b/deps/rcheevos/CHANGELOG.md @@ -1,3 +1,52 @@ +# v9.0.0 + +* new size: RC_MEMSIZE_BITCOUNT +* new flag: RC_CONDITION_OR_NEXT +* new flag: RC_CONDITION_TRIGGER +* new flag: RC_CONDITION_MEASURED_IF +* new operators: RC_OPERATOR_MULT / RC_OPERATOR_DIV +* is_bcd removed from memref - now part of RC_MEMSIZE +* add rc_runtime_t and associated functions +* add rc_hash_ functions +* add rc_error_str function +* add game_hash parameter to rc_url_award_cheevo +* remove hash parameter from rc_url_submit_lboard +* add rc_url_ping function +* add rc_console_ functions + +# v8.1.0 + +* new flag: RC_CONDITION_MEASURED +* new flag: RC_CONDITION_ADD_ADDRESS +* add rc_evaluate_trigger - extended version of rc_test_trigger with more granular return codes +* make rc_evaluate_value return a signed int (was unsigned int) +* new formats: RC_FORMAT_MINUTES and RC_FORMAT_SECONDS_AS_MINUTES +* removed " Points" text from RC_FORMAT_SCORE format +* removed RC_FORMAT_OTHER format. "OTHER" format now parses to RC_FORMAT_SCORE +* bugfix: AddHits will now honor AndNext on previous condition + +# v8.0.1 + +* bugfix: prevent null reference exception if rich presence contains condition without display string +* bugfix: 24-bit read from memory should only read 24-bits + +# v8.0.0 + +* support for prior operand type +* support for AndNext condition flag +* support for rich presence +* bugfix: update delta/prior memory values while group is paused +* bugfix: allow floating point number without leading 0 +* bugfix: support empty alt groups + +# v7.1.1 + +* Address signed/unsigned mismatch warnings + +# v7.1.0 + +* Added the RC_DISABLE_LUA macro to compile rcheevos without Lua support + # v7.0.2 * Make sure the code is C89-compliant diff --git a/deps/rcheevos/README.md b/deps/rcheevos/README.md index d9738e5312..f45f2f7c53 100644 --- a/deps/rcheevos/README.md +++ b/deps/rcheevos/README.md @@ -59,10 +59,16 @@ enum { RC_INVALID_VALUE_FLAG = -20, RC_MISSING_VALUE_MEASURED = -21, RC_MULTIPLE_MEASURED = -22, - RC_INVALID_MEASURED_TARGET = -23 + RC_INVALID_MEASURED_TARGET = -23, + RC_INVALID_COMPARISON = -24 }; ``` +To convert the return code into something human-readable, pass it to: +```c +const char* rc_error_str(int ret); +``` + ### Console identifiers This enumeration uniquely identifies each of the supported platforms in RetroAchievements. @@ -92,14 +98,38 @@ enum { RC_CONSOLE_PLAYSTATION_2 = 21, RC_CONSOLE_XBOX = 22, RC_CONSOLE_SKYNET = 23, - RC_CONSOLE_XBOX_ONE = 24, + RC_CONSOLE_POKEMON_MINI = 24, RC_CONSOLE_ATARI_2600 = 25, RC_CONSOLE_MS_DOS = 26, RC_CONSOLE_ARCADE = 27, RC_CONSOLE_VIRTUAL_BOY = 28, RC_CONSOLE_MSX = 29, RC_CONSOLE_COMMODORE_64 = 30, - RC_CONSOLE_ZX81 = 31 + RC_CONSOLE_ZX81 = 31, + RC_CONSOLE_ORIC = 32, + RC_CONSOLE_SG1000 = 33, + RC_CONSOLE_VIC20 = 34, + RC_CONSOLE_AMIGA = 35, + RC_CONSOLE_AMIGA_ST = 36, + RC_CONSOLE_AMSTRAD_PC = 37, + RC_CONSOLE_APPLE_II = 38, + RC_CONSOLE_SATURN = 39, + RC_CONSOLE_DREAMCAST = 40, + RC_CONSOLE_PSP = 41, + RC_CONSOLE_CDI = 42, + RC_CONSOLE_3DO = 43, + RC_CONSOLE_COLECOVISION = 44, + RC_CONSOLE_INTELLIVISION = 45, + RC_CONSOLE_VECTREX = 46, + RC_CONSOLE_PC8800 = 47, + RC_CONSOLE_PC9800 = 48, + RC_CONSOLE_PCFX = 49, + RC_CONSOLE_ATARI_5200 = 50, + RC_CONSOLE_ATARI_7800 = 51, + RC_CONSOLE_X68K = 52, + RC_CONSOLE_WONDERSWAN = 53, + RC_CONSOLE_CASSETTEVISION = 54, + RC_CONSOLE_SUPER_CASSETTEVISION = 55 }; ``` @@ -115,27 +145,23 @@ An operand is the leaf node of RetroAchievements expressions, and can hold one o typedef struct { union { /* A value read from memory. */ - struct { - /* The memory address or constant value of this variable. */ - unsigned value; - /* The previous memory contents if RC_OPERAND_DELTA. */ - unsigned previous; + rc_memref_value_t* memref; - /* The size of the variable. */ - char size; - /* True if the value is in BCD. */ - char is_bcd; - /* The type of the variable. */ - }; + /* An integer value. */ + unsigned num; /* A floating point value. */ - double fp_value; + double dbl; /* A reference to the Lua function that provides the value. */ - int function_ref; + int luafunc; }; + /* specifies which member of the value union is being used */ char type; + + /* the actual RC_MEMSIZE of the operand - memref.size may differ */ + char size; } rc_operand_t; ``` @@ -144,20 +170,21 @@ The `size` field, when applicable, holds one of these values: ```c enum { - RC_OPERAND_BIT_0, - RC_OPERAND_BIT_1, - RC_OPERAND_BIT_2, - RC_OPERAND_BIT_3, - RC_OPERAND_BIT_4, - RC_OPERAND_BIT_5, - RC_OPERAND_BIT_6, - RC_OPERAND_BIT_7, - RC_OPERAND_LOW, - RC_OPERAND_HIGH, - RC_OPERAND_8_BITS, - RC_OPERAND_16_BITS, - RC_OPERAND_24_BITS, - RC_OPERAND_32_BITS, + RC_MEMSIZE_8_BITS, + RC_MEMSIZE_16_BITS, + RC_MEMSIZE_24_BITS, + RC_MEMSIZE_32_BITS, + RC_MEMSIZE_LOW, + RC_MEMSIZE_HIGH, + RC_MEMSIZE_BIT_0, + RC_MEMSIZE_BIT_1, + RC_MEMSIZE_BIT_2, + RC_MEMSIZE_BIT_3, + RC_MEMSIZE_BIT_4, + RC_MEMSIZE_BIT_5, + RC_MEMSIZE_BIT_6, + RC_MEMSIZE_BIT_7, + RC_MEMSIZE_BITCOUNT }; ``` @@ -165,15 +192,18 @@ The `type` field is always valid, and holds one of these values: ```c enum { - RC_OPERAND_ADDRESS, /* Compare to the value of a live address in RAM. */ - RC_OPERAND_DELTA, /* The value last known at this address. */ - RC_OPERAND_CONST, /* A 32-bit unsigned integer. */ - RC_OPERAND_FP, /* A floating point value. */ - RC_OPERAND_LUA /* A Lua function that provides the value. */ + RC_OPERAND_ADDRESS, /* The value of a live address in RAM. */ + RC_OPERAND_DELTA, /* The value last known at this address. */ + RC_OPERAND_CONST, /* A 32-bit unsigned integer. */ + RC_OPERAND_FP, /* A floating point value. */ + RC_OPERAND_LUA, /* A Lua function that provides the value. */ + RC_OPERAND_PRIOR, /* The last differing value at this address. */ + RC_OPERAND_BCD, /* The BCD-decoded value of a live address in RAM */ + RC_OPERAND_INVERTED /* The twos-complement value of a live address in RAM */ }; ``` -`RC_OPERAND_ADDRESS`, `RC_OPERAND_DELTA` and `RC_OPERAND_CONST` mean that the anonymous structure in the union is active. `RC_OPERAND_FP` means that `fp_value` is active. `RC_OPERAND_LUA` means `function_ref` is active. +`RC_OPERAND_ADDRESS`, `RC_OPERAND_DELTA`, `RC_OPERAND_PRIOR`, `RC_OPERAND_BCD`, and `RC_OPERAND_INVERTED` mean that `memref` is active. `RC_OPERAND_CONST` means that `num` is active. `RC_OPERAND_FP` means that `dbl` is active. `RC_OPERAND_LUA` means `luafunc` is active. ### `rc_condition_t` @@ -184,9 +214,6 @@ A condition compares its two operands according to the defined operator. It also typedef struct rc_condition_t rc_condition_t; struct rc_condition_t { - /* The next condition in the chain. */ - rc_condition_t* next; - /* The condition's operands. */ rc_operand_t operand1; rc_operand_t operand2; @@ -196,6 +223,9 @@ struct rc_condition_t { /* Number of hits so far. */ unsigned current_hits; + /* The next condition in the chain. */ + rc_condition_t* next; + /* The type of the condition. */ char type; /* The comparison operator to use. */ @@ -219,7 +249,9 @@ enum { RC_CONDITION_ADD_HITS, RC_CONDITION_AND_NEXT, RC_CONDITION_MEASURED, - RC_CONDITION_ADD_ADDRESS + RC_CONDITION_ADD_ADDRESS, + RC_CONDITION_TRIGGER, + RC_CONDITION_MEASURED_IF }; ``` @@ -227,13 +259,16 @@ enum { ```c enum { - RC_CONDITION_EQ, - RC_CONDITION_LT, - RC_CONDITION_LE, - RC_CONDITION_GT, - RC_CONDITION_GE, - RC_CONDITION_NE, - RC_CONDITION_NONE + RC_OPERATOR_EQ, + RC_OPERATOR_LT, + RC_OPERATOR_LE, + RC_OPERATOR_GT, + RC_OPERATOR_GE, + RC_OPERATOR_NE, + RC_OPERATOR_NONE, + RC_OPERATOR_MULT, + RC_OPERATOR_DIV, + RC_OPERATOR_AND }; ``` @@ -267,6 +302,9 @@ typedef struct { /* The list of sub condition sets in this test. */ rc_condset_t* alternative; + + /* The memory references required by the trigger. */ + rc_memref_value_t* memrefs; } rc_trigger_t; ``` @@ -313,7 +351,8 @@ enum { RC_TRIGGER_STATE_ACTIVE, /* achievement is active and may trigger */ RC_TRIGGER_STATE_PAUSED, /* achievement is currently paused and will not trigger */ RC_TRIGGER_STATE_RESET, /* achievement hit counts were reset */ - RC_TRIGGER_STATE_TRIGGERED /* achievement has triggered */ + RC_TRIGGER_STATE_TRIGGERED, /* achievement has triggered */ + RC_TRIGGER_STATE_PRIMED /* all non-Trigger conditions are true */ }; ``` @@ -323,52 +362,12 @@ Finally, `rc_reset_trigger` can be used to reset the internal state of a trigger void rc_reset_trigger(rc_trigger_t* self); ``` -### `rc_term_t` - -A term is the leaf node of expressions used to compute values from operands. A term is evaluated by multiplying its two operands. `invert` is used to invert the bits of the second operand of the term, when the unary operator `~` is used. - -```c -typedef struct rc_term_t rc_term_t; - -struct rc_term_t { - /* The next term in this chain. */ - rc_term_t* next; - - /* The first operand. */ - rc_operand_t operand1; - /* The second operand. */ - rc_operand_t operand2; - - /* A value that is applied to the second variable to invert its bits. */ - unsigned invert; -}; -``` - -### `rc_expression_t` - -An expression is a collection of terms. All terms in the collection are added together to give the value of the expression. - -```c -typedef struct rc_expression_t rc_expression_t; - -struct rc_expression_t { - /* The next expression in this chain. */ - rc_expression_t* next; - - /* The list of terms in this expression. */ - rc_term_t* terms; -}; -``` - ### `rc_value_t` -A value is a collection of expressions. It's used to give the value for a leaderboard, and it evaluates to value of the expression with the greatest value in the collection. +A value is a collection of conditions that result in a single RC_CONDITION_MEASURED expression. It's used to calculate the value for a leaderboard and for lookups in rich presence. ```c typedef struct { - /* The list of expression to evaluate. */ - rc_expression_t* expressions; - /* The list of conditions to evaluate. */ rc_condset_t* conditions; @@ -413,9 +412,9 @@ typedef struct { rc_trigger_t cancel; rc_value_t value; rc_value_t* progress; + rc_memref_value_t* memrefs; - char started; - char submitted; + char state; } rc_lboard_t; ``` @@ -437,11 +436,12 @@ The function returns an action that must be performed by the caller, and `value` ```c enum { - RC_LBOARD_INACTIVE, - RC_LBOARD_ACTIVE, - RC_LBOARD_STARTED, - RC_LBOARD_CANCELED, - RC_LBOARD_TRIGGERED + RC_LBOARD_STATE_INACTIVE, /* leaderboard is not being processed */ + RC_LBOARD_STATE_WAITING, /* leaderboard cannot activate until the start condition has been false for at least one frame */ + RC_LBOARD_STATE_ACTIVE, /* leaderboard is active and may start */ + RC_LBOARD_STATE_STARTED, /* leaderboard attempt in progress */ + RC_LBOARD_STATE_CANCELED, /* leaderboard attempt canceled */ + RC_LBOARD_STATE_TRIGGERED /* leaderboard attempt complete, value should be submitted */ }; ``` @@ -458,6 +458,101 @@ The caller must keep track of these values and do the necessary actions: void rc_reset_lboard(rc_lboard_t* lboard); ``` +### `rc_runtime_t` + +The runtime encapsulates a set of achievements and leaderboards and manages processing them for each frame. When important things occur, events are raised for the caller via a callback. + +```c +typedef struct rc_runtime_t { + rc_runtime_trigger_t* triggers; + unsigned trigger_count; + unsigned trigger_capacity; + + rc_runtime_lboard_t* lboards; + unsigned lboard_count; + unsigned lboard_capacity; + + rc_runtime_richpresence_t* richpresence; + char* richpresence_display_buffer; + char richpresence_update_timer; + + rc_memref_value_t* memrefs; + rc_memref_value_t** next_memref; +} +rc_runtime_t; +``` + +The runtime must first be initialized. +```c +void rc_runtime_init(rc_runtime_t* runtime); +``` + +Then individual achievements, leaderboards, and even rich presence can be loaded into the runtime. These functions return RC_OK, or one of the negative value error codes listed above. +```c +int rc_runtime_activate_achievement(rc_runtime_t* runtime, unsigned id, const char* memaddr, lua_State* L, int funcs_idx); +int rc_runtime_activate_lboard(rc_runtime_t* runtime, unsigned id, const char* memaddr, lua_State* L, int funcs_idx); +int rc_runtime_activate_richpresence(rc_runtime_t* runtime, const char* script, lua_State* L, int funcs_idx); +``` + +The runtime should be called once per frame to evaluate the state of the active achievements/leaderboards: +```c +void rc_runtime_do_frame(rc_runtime_t* runtime, rc_runtime_event_handler_t event_handler, rc_peek_t peek, void* ud, lua_State* L); +``` + +The `event_handler` is a callback function that is called for each event that occurs when processing the frame. +```c +typedef struct rc_runtime_event_t { + unsigned id; + int value; + char type; +} +rc_runtime_event_t; + +typedef void (*rc_runtime_event_handler_t)(const rc_runtime_event_t* runtime_event); +``` + +The `event.type` field will be one of the following: +* RC_RUNTIME_EVENT_ACHIEVEMENT_ACTIVATED (id=achievement id) + An achievement starts in the RC_TRIGGER_STATE_WAITING state and cannot trigger until it has been false for at least one frame. This event indicates the achievement is no longer waiting and may trigger on a future frame. +* RC_RUNTIME_EVENT_ACHIEVEMENT_PAUSED (id=achievement id) + One or more conditions in the achievement have disabled the achievement. +* RC_RUNTIME_EVENT_ACHIEVEMENT_RESET (id=achievement id) + One or more conditions in the achievement have reset any progress captured in the achievement. +* RC_RUNTIME_EVENT_ACHIEVEMENT_TRIGGERED (id=achievement id) + All conditions for the achievement have been met and the user should be informed. + NOTE: If `rc_runtime_reset` is called without deactivating the achievement, it may trigger again. +* RC_RUNTIME_EVENT_ACHIEVEMENT_PRIMED (id=achievement id) + All non-trigger conditions for the achievement have been met. This typically indicates the achievement is a challenge achievement and the challenge is active. +* RC_RUNTIME_EVENT_LBOARD_STARTED (id=leaderboard id, value=leaderboard value) + The leaderboard's start condition has been met and the user should be informed that a leaderboard attempt has started. +* RC_RUNTIME_EVENT_LBOARD_CANCELED (id=leaderboard id, value=leaderboard value) + The leaderboard's cancel condition has been met and the user should be informed that a leaderboard attempt has failed. +* RC_RUNTIME_EVENT_LBOARD_UPDATED (id=leaderboard id, value=leaderboard value) + The leaderboard value has changed. +* RC_RUNTIME_EVENT_LBOARD_TRIGGERED (id=leaderboard id, value=leaderboard value) + The leaderboard's submit condition has been met and the user should be informed that a leaderboard attempt was successful. The value should be submitted. + +When an achievement triggers, it should be deactivated so it won't trigger again: +```c +void rc_runtime_deactivate_achievement(rc_runtime_t* runtime, unsigned id); +``` +Additionally, the unlock should be submitted to the server. + +When a leaderboard triggers, it should not be deactivated in case the player wants to try again for a better score. The value should be submitted to the server. + +`rc_runtime_do_frame` also periodically updates the rich presense string (every 60 frames). To get the current value, call +```c +const char* rc_runtime_get_richpresence(const rc_runtime_t* runtime); +``` + +When the game is reset, the runtime should also be reset: +```c +void rc_runtime_reset(rc_runtime_t* runtime); +``` + +This ensures any active achievements/leaderboards are set back to their initial states and prevents unexpected triggers when the memory changes in atypical way. + + ### Value Formatting **rcheevos** includes helper functions to parse formatting strings from RetroAchievements, and format values according to them. diff --git a/deps/rcheevos/include/rcheevos.h b/deps/rcheevos/include/rcheevos.h index 467771464b..2ff0289750 100644 --- a/deps/rcheevos/include/rcheevos.h +++ b/deps/rcheevos/include/rcheevos.h @@ -35,46 +35,12 @@ enum { RC_INVALID_VALUE_FLAG = -20, RC_MISSING_VALUE_MEASURED = -21, RC_MULTIPLE_MEASURED = -22, - RC_INVALID_MEASURED_TARGET = -23 + RC_INVALID_MEASURED_TARGET = -23, + RC_INVALID_COMPARISON = -24, + RC_INVALID_STATE = -25 }; -/*****************************************************************************\ -| Console identifiers | -\*****************************************************************************/ - -enum { - RC_CONSOLE_MEGA_DRIVE = 1, - RC_CONSOLE_NINTENDO_64 = 2, - RC_CONSOLE_SUPER_NINTENDO = 3, - RC_CONSOLE_GAMEBOY = 4, - RC_CONSOLE_GAMEBOY_ADVANCE = 5, - RC_CONSOLE_GAMEBOY_COLOR = 6, - RC_CONSOLE_NINTENDO = 7, - RC_CONSOLE_PC_ENGINE = 8, - RC_CONSOLE_SEGA_CD = 9, - RC_CONSOLE_SEGA_32X = 10, - RC_CONSOLE_MASTER_SYSTEM = 11, - RC_CONSOLE_PLAYSTATION = 12, - RC_CONSOLE_ATARI_LYNX = 13, - RC_CONSOLE_NEOGEO_POCKET = 14, - RC_CONSOLE_GAME_GEAR = 15, - RC_CONSOLE_GAMECUBE = 16, - RC_CONSOLE_ATARI_JAGUAR = 17, - RC_CONSOLE_NINTENDO_DS = 18, - RC_CONSOLE_WII = 19, - RC_CONSOLE_WII_U = 20, - RC_CONSOLE_PLAYSTATION_2 = 21, - RC_CONSOLE_XBOX = 22, - RC_CONSOLE_SKYNET = 23, - RC_CONSOLE_XBOX_ONE = 24, - RC_CONSOLE_ATARI_2600 = 25, - RC_CONSOLE_MS_DOS = 26, - RC_CONSOLE_ARCADE = 27, - RC_CONSOLE_VIRTUAL_BOY = 28, - RC_CONSOLE_MSX = 29, - RC_CONSOLE_COMMODORE_64 = 30, - RC_CONSOLE_ZX81 = 31 -}; +const char* rc_error_str(int ret); /*****************************************************************************\ | Callbacks | @@ -93,6 +59,12 @@ typedef unsigned (*rc_peek_t)(unsigned address, unsigned num_bytes, void* ud); /* Sizes. */ enum { + RC_MEMSIZE_8_BITS, + RC_MEMSIZE_16_BITS, + RC_MEMSIZE_24_BITS, + RC_MEMSIZE_32_BITS, + RC_MEMSIZE_LOW, + RC_MEMSIZE_HIGH, RC_MEMSIZE_BIT_0, RC_MEMSIZE_BIT_1, RC_MEMSIZE_BIT_2, @@ -101,12 +73,7 @@ enum { RC_MEMSIZE_BIT_5, RC_MEMSIZE_BIT_6, RC_MEMSIZE_BIT_7, - RC_MEMSIZE_LOW, - RC_MEMSIZE_HIGH, - RC_MEMSIZE_8_BITS, - RC_MEMSIZE_16_BITS, - RC_MEMSIZE_24_BITS, - RC_MEMSIZE_32_BITS + RC_MEMSIZE_BITCOUNT }; typedef struct { @@ -114,8 +81,6 @@ typedef struct { unsigned address; /* The size of the variable. */ char size; - /* True if the value is in BCD. */ - char is_bcd; /* True if the reference will be used in indirection */ char is_indirect; } rc_memref_t; @@ -143,12 +108,14 @@ struct rc_memref_value_t { /* types */ enum { - RC_OPERAND_ADDRESS, /* Compare to the value of a live address in RAM. */ - RC_OPERAND_DELTA, /* The value last known at this address. */ - RC_OPERAND_CONST, /* A 32-bit unsigned integer. */ - RC_OPERAND_FP, /* A floating point value. */ - RC_OPERAND_LUA, /* A Lua function that provides the value. */ - RC_OPERAND_PRIOR /* The last differing value at this address. */ + RC_OPERAND_ADDRESS, /* The value of a live address in RAM. */ + RC_OPERAND_DELTA, /* The value last known at this address. */ + RC_OPERAND_CONST, /* A 32-bit unsigned integer. */ + RC_OPERAND_FP, /* A floating point value. */ + RC_OPERAND_LUA, /* A Lua function that provides the value. */ + RC_OPERAND_PRIOR, /* The last differing value at this address. */ + RC_OPERAND_BCD, /* The BCD-decoded value of a live address in RAM */ + RC_OPERAND_INVERTED /* The twos-complement value of a live address in RAM */ }; typedef struct { @@ -166,7 +133,11 @@ typedef struct { int luafunc; } value; + /* specifies which member of the value union is being used */ char type; + + /* the actual RC_MEMSIZE of the operand - memref.size may differ */ + char size; } rc_operand_t; @@ -184,18 +155,24 @@ enum { RC_CONDITION_ADD_HITS, RC_CONDITION_AND_NEXT, RC_CONDITION_MEASURED, - RC_CONDITION_ADD_ADDRESS + RC_CONDITION_ADD_ADDRESS, + RC_CONDITION_OR_NEXT, + RC_CONDITION_TRIGGER, + RC_CONDITION_MEASURED_IF }; /* operators */ enum { - RC_CONDITION_EQ, - RC_CONDITION_LT, - RC_CONDITION_LE, - RC_CONDITION_GT, - RC_CONDITION_GE, - RC_CONDITION_NE, - RC_CONDITION_NONE + RC_OPERATOR_EQ, + RC_OPERATOR_LT, + RC_OPERATOR_LE, + RC_OPERATOR_GT, + RC_OPERATOR_GE, + RC_OPERATOR_NE, + RC_OPERATOR_NONE, + RC_OPERATOR_MULT, + RC_OPERATOR_DIV, + RC_OPERATOR_AND }; typedef struct rc_condition_t rc_condition_t; @@ -256,7 +233,8 @@ enum { RC_TRIGGER_STATE_ACTIVE, /* achievement is active and may trigger */ RC_TRIGGER_STATE_PAUSED, /* achievement is currently paused and will not trigger */ RC_TRIGGER_STATE_RESET, /* achievement hit counts were reset */ - RC_TRIGGER_STATE_TRIGGERED /* achievement has triggered */ + RC_TRIGGER_STATE_TRIGGERED, /* achievement has triggered */ + RC_TRIGGER_STATE_PRIMED /* all non-Trigger conditions are true */ }; typedef struct { @@ -290,38 +268,10 @@ int rc_test_trigger(rc_trigger_t* trigger, rc_peek_t peek, void* ud, lua_State* void rc_reset_trigger(rc_trigger_t* self); /*****************************************************************************\ -| Expressions and values | +| Values | \*****************************************************************************/ -typedef struct rc_term_t rc_term_t; - -struct rc_term_t { - /* The next term in this chain. */ - rc_term_t* next; - - /* The first operand. */ - rc_operand_t operand1; - /* The second operand. */ - rc_operand_t operand2; - - /* A value that is applied to the second variable to invert its bits. */ - unsigned invert; -}; - -typedef struct rc_expression_t rc_expression_t; - -struct rc_expression_t { - /* The next expression in this chain. */ - rc_expression_t* next; - - /* The list of terms in this expression. */ - rc_term_t* terms; -}; - typedef struct { - /* The list of expression to evaluate. */ - rc_expression_t* expressions; - /* The list of conditions to evaluate. */ rc_condset_t* conditions; @@ -340,11 +290,12 @@ int rc_evaluate_value(rc_value_t* value, rc_peek_t peek, void* ud, lua_State* L) /* Return values for rc_evaluate_lboard. */ enum { - RC_LBOARD_INACTIVE, - RC_LBOARD_ACTIVE, - RC_LBOARD_STARTED, - RC_LBOARD_CANCELED, - RC_LBOARD_TRIGGERED + RC_LBOARD_STATE_INACTIVE, /* leaderboard is not being processed */ + RC_LBOARD_STATE_WAITING, /* leaderboard cannot activate until the start condition has been false for at least one frame */ + RC_LBOARD_STATE_ACTIVE, /* leaderboard is active and may start */ + RC_LBOARD_STATE_STARTED, /* leaderboard attempt in progress */ + RC_LBOARD_STATE_CANCELED, /* leaderboard attempt canceled */ + RC_LBOARD_STATE_TRIGGERED /* leaderboard attempt complete, value should be submitted */ }; typedef struct { @@ -355,8 +306,7 @@ typedef struct { rc_value_t* progress; rc_memref_value_t* memrefs; - char started; - char submitted; + char state; } rc_lboard_t; @@ -433,6 +383,128 @@ int rc_richpresence_size(const char* script); rc_richpresence_t* rc_parse_richpresence(void* buffer, const char* script, lua_State* L, int funcs_ndx); int rc_evaluate_richpresence(rc_richpresence_t* richpresence, char* buffer, unsigned buffersize, rc_peek_t peek, void* peek_ud, lua_State* L); +/*****************************************************************************\ +| Runtime | +\*****************************************************************************/ + +typedef struct rc_runtime_trigger_t { + unsigned id; + rc_trigger_t* trigger; + void* buffer; + unsigned char md5[16]; + char owns_memrefs; +} +rc_runtime_trigger_t; + +typedef struct rc_runtime_lboard_t { + unsigned id; + int value; + rc_lboard_t* lboard; + void* buffer; + unsigned char md5[16]; + char owns_memrefs; +} +rc_runtime_lboard_t; + +typedef struct rc_runtime_richpresence_t { + rc_richpresence_t* richpresence; + void* buffer; + struct rc_runtime_richpresence_t* previous; + char owns_memrefs; +} +rc_runtime_richpresence_t; + +typedef struct rc_runtime_t { + rc_runtime_trigger_t* triggers; + unsigned trigger_count; + unsigned trigger_capacity; + + rc_runtime_lboard_t* lboards; + unsigned lboard_count; + unsigned lboard_capacity; + + rc_runtime_richpresence_t* richpresence; + char* richpresence_display_buffer; + char richpresence_update_timer; + + rc_memref_value_t* memrefs; + rc_memref_value_t** next_memref; +} +rc_runtime_t; + +void rc_runtime_init(rc_runtime_t* runtime); +void rc_runtime_destroy(rc_runtime_t* runtime); + +int rc_runtime_activate_achievement(rc_runtime_t* runtime, unsigned id, const char* memaddr, lua_State* L, int funcs_idx); +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_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); +rc_lboard_t* rc_runtime_get_lboard(const rc_runtime_t* runtime, unsigned id); + +int rc_runtime_activate_richpresence(rc_runtime_t* runtime, const char* script, lua_State* L, int funcs_idx); +const char* rc_runtime_get_richpresence(const rc_runtime_t* runtime); + +enum { + RC_RUNTIME_EVENT_ACHIEVEMENT_ACTIVATED, /* from WAITING, PAUSED, or PRIMED to ACTIVE */ + RC_RUNTIME_EVENT_ACHIEVEMENT_PAUSED, + RC_RUNTIME_EVENT_ACHIEVEMENT_RESET, + RC_RUNTIME_EVENT_ACHIEVEMENT_TRIGGERED, + RC_RUNTIME_EVENT_ACHIEVEMENT_PRIMED, + RC_RUNTIME_EVENT_LBOARD_STARTED, + RC_RUNTIME_EVENT_LBOARD_CANCELED, + RC_RUNTIME_EVENT_LBOARD_UPDATED, + RC_RUNTIME_EVENT_LBOARD_TRIGGERED +}; + +typedef struct rc_runtime_event_t { + unsigned id; + int value; + char type; +} +rc_runtime_event_t; + +typedef void (*rc_runtime_event_handler_t)(const rc_runtime_event_t* runtime_event); + +void rc_runtime_do_frame(rc_runtime_t* runtime, rc_runtime_event_handler_t event_handler, rc_peek_t peek, void* ud, lua_State* L); +void rc_runtime_reset(rc_runtime_t* runtime); + +int rc_runtime_progress_size(const rc_runtime_t* runtime, lua_State* L); +int rc_runtime_serialize_progress(void* buffer, const rc_runtime_t* runtime, lua_State* L); +int rc_runtime_deserialize_progress(rc_runtime_t* runtime, const unsigned char* serialized, lua_State* L); + +/*****************************************************************************\ +| Memory mapping | +\*****************************************************************************/ + +enum { + RC_MEMORY_TYPE_SYSTEM_RAM, /* normal system memory */ + RC_MEMORY_TYPE_SAVE_RAM, /* memory that persists between sessions */ + RC_MEMORY_TYPE_VIDEO_RAM, /* memory reserved for graphical processing */ + RC_MEMORY_TYPE_READONLY, /* memory that maps to read only data */ + RC_MEMORY_TYPE_HARDWARE_CONTROLLER, /* memory for interacting with system components */ + RC_MEMORY_TYPE_VIRTUAL_RAM, /* secondary address space that maps to real memory in system RAM */ + RC_MEMORY_TYPE_UNUSED /* these addresses don't really exist */ +}; + +typedef struct rc_memory_region_t { + unsigned start_address; /* first address of block as queried by RetroAchievements */ + unsigned end_address; /* last address of block as queried by RetroAchievements */ + unsigned real_address; /* real address for first address of block */ + char type; /* RC_MEMORY_TYPE_ for block */ + const char* description; /* short description of block */ +} +rc_memory_region_t; + +typedef struct rc_memory_regions_t { + const rc_memory_region_t* region; + unsigned num_regions; +} +rc_memory_regions_t; + +const rc_memory_regions_t* rc_console_memory_regions(int console_id); + #ifdef __cplusplus } #endif diff --git a/deps/rcheevos/include/rconsoles.h b/deps/rcheevos/include/rconsoles.h new file mode 100644 index 0000000000..d57c924791 --- /dev/null +++ b/deps/rcheevos/include/rconsoles.h @@ -0,0 +1,79 @@ +#ifndef RCONSOLES_H +#define RCONSOLES_H + +#ifdef __cplusplus +extern "C" { +#endif + +/*****************************************************************************\ +| Console identifiers | +\*****************************************************************************/ + +enum { + RC_CONSOLE_MEGA_DRIVE = 1, + RC_CONSOLE_NINTENDO_64 = 2, + RC_CONSOLE_SUPER_NINTENDO = 3, + RC_CONSOLE_GAMEBOY = 4, + RC_CONSOLE_GAMEBOY_ADVANCE = 5, + RC_CONSOLE_GAMEBOY_COLOR = 6, + RC_CONSOLE_NINTENDO = 7, + RC_CONSOLE_PC_ENGINE = 8, + RC_CONSOLE_SEGA_CD = 9, + RC_CONSOLE_SEGA_32X = 10, + RC_CONSOLE_MASTER_SYSTEM = 11, + RC_CONSOLE_PLAYSTATION = 12, + RC_CONSOLE_ATARI_LYNX = 13, + RC_CONSOLE_NEOGEO_POCKET = 14, + RC_CONSOLE_GAME_GEAR = 15, + RC_CONSOLE_GAMECUBE = 16, + RC_CONSOLE_ATARI_JAGUAR = 17, + RC_CONSOLE_NINTENDO_DS = 18, + RC_CONSOLE_WII = 19, + RC_CONSOLE_WII_U = 20, + RC_CONSOLE_PLAYSTATION_2 = 21, + RC_CONSOLE_XBOX = 22, + /* 23 used to be EVENTS */ + RC_CONSOLE_POKEMON_MINI = 24, + RC_CONSOLE_ATARI_2600 = 25, + RC_CONSOLE_MS_DOS = 26, + RC_CONSOLE_ARCADE = 27, + RC_CONSOLE_VIRTUAL_BOY = 28, + RC_CONSOLE_MSX = 29, + RC_CONSOLE_COMMODORE_64 = 30, + RC_CONSOLE_ZX81 = 31, + RC_CONSOLE_ORIC = 32, + RC_CONSOLE_SG1000 = 33, + RC_CONSOLE_VIC20 = 34, + RC_CONSOLE_AMIGA = 35, + RC_CONSOLE_AMIGA_ST = 36, + RC_CONSOLE_AMSTRAD_PC = 37, + RC_CONSOLE_APPLE_II = 38, + RC_CONSOLE_SATURN = 39, + RC_CONSOLE_DREAMCAST = 40, + RC_CONSOLE_PSP = 41, + RC_CONSOLE_CDI = 42, + RC_CONSOLE_3DO = 43, + RC_CONSOLE_COLECOVISION = 44, + RC_CONSOLE_INTELLIVISION = 45, + RC_CONSOLE_VECTREX = 46, + RC_CONSOLE_PC8800 = 47, + RC_CONSOLE_PC9800 = 48, + RC_CONSOLE_PCFX = 49, + RC_CONSOLE_ATARI_5200 = 50, + RC_CONSOLE_ATARI_7800 = 51, + RC_CONSOLE_X68K = 52, + RC_CONSOLE_WONDERSWAN = 53, + RC_CONSOLE_CASSETTEVISION = 54, + RC_CONSOLE_SUPER_CASSETTEVISION = 55, + + RC_CONSOLE_HUBS = 100, + RC_CONSOLE_EVENTS = 101 +}; + +const char* rc_console_name(int console_id); + +#ifdef __cplusplus +} +#endif + +#endif /* RCONSOLES_H */ diff --git a/deps/rcheevos/include/rhash.h b/deps/rcheevos/include/rhash.h new file mode 100644 index 0000000000..32e9655dfc --- /dev/null +++ b/deps/rcheevos/include/rhash.h @@ -0,0 +1,124 @@ +#ifndef RHASH_H +#define RHASH_H + +#include +#include +#include + +#include "rconsoles.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /* ===================================================== */ + + /* 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); + + /* generates a hash from a file. + * returns non-zero on success, or zero on failure. + */ + int rc_hash_generate_from_file(char hash[33], int console_id, const char* path); + + /* ===================================================== */ + + /* data for rc_hash_iterate + */ + struct rc_hash_iterator + { + uint8_t* buffer; + size_t buffer_size; + uint8_t consoles[12]; + int index; + const char* path; + }; + + /* initializes a rc_hash_iterator + * - path must be provided + * - if buffer and buffer_size are provided, path may be a filename (i.e. for something extracted from a zip file) + */ + void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* path, uint8_t* buffer, size_t buffer_size); + + /* releases resources associated to a rc_hash_iterator + */ + void rc_hash_destroy_iterator(struct rc_hash_iterator* iterator); + + /* generates the next hash for the data in the rc_hash_iterator. + * returns non-zero if a hash was generated, or zero if no more hashes can be generated for the data. + */ + int rc_hash_iterate(char hash[33], struct rc_hash_iterator* iterator); + + /* ===================================================== */ + + /* specifies a function to call when an error occurs to display the error message */ + typedef void (*rc_hash_message_callback)(const char*); + void rc_hash_init_error_message_callback(rc_hash_message_callback callback); + + /* specifies a function to call for verbose logging */ + void rc_hash_init_verbose_message_callback(rc_hash_message_callback callback); + + /* ===================================================== */ + + /* opens a file */ + typedef void* (*rc_hash_filereader_open_file_handler)(const char* path_utf8); + + /* moves the file pointer - standard fseek parameters */ + typedef void (*rc_hash_filereader_seek_handler)(void* file_handle, size_t offset, int origin); + + /* locates the file pointer */ + typedef size_t (*rc_hash_filereader_tell_handler)(void* file_handle); + + /* reads the specified number of bytes from the file starting at the read pointer. + * returns the number of bytes actually read. + */ + typedef size_t (*rc_hash_filereader_read_handler)(void* file_handle, void* buffer, size_t requested_bytes); + + /* closes the file */ + typedef void (*rc_hash_filereader_close_file_handler)(void* file_handle); + + struct rc_hash_filereader + { + rc_hash_filereader_open_file_handler open; + rc_hash_filereader_seek_handler seek; + rc_hash_filereader_tell_handler tell; + rc_hash_filereader_read_handler read; + rc_hash_filereader_close_file_handler close; + }; + + void rc_hash_init_custom_filereader(struct rc_hash_filereader* reader); + + /* ===================================================== */ + + /* opens a track from the specified file. track 0 indicates the first data track should be opened. + * returns a handle to be passed to the other functions, or NULL if the track could not be opened. + */ + typedef void* (*rc_hash_cdreader_open_track_handler)(const char* path, uint32_t track); + + /* attempts to read the specified number of bytes from the file starting at the read pointer. + * returns the number of bytes actually read. + */ + typedef size_t (*rc_hash_cdreader_read_sector_handler)(void* track_handle, uint32_t sector, void* buffer, size_t requested_bytes); + + /* closes the track handle */ + typedef void (*rc_hash_cdreader_close_track_handler)(void* track_handle); + + struct rc_hash_cdreader + { + rc_hash_cdreader_open_track_handler open_track; + rc_hash_cdreader_read_sector_handler read_sector; + rc_hash_cdreader_close_track_handler close_track; + }; + + void rc_hash_init_default_cdreader(); + void rc_hash_init_custom_cdreader(struct rc_hash_cdreader* reader); + + /* ===================================================== */ + +#ifdef __cplusplus +} +#endif + +#endif /* RHASH_H */ diff --git a/deps/rcheevos/include/rurl.h b/deps/rcheevos/include/rurl.h index 1c1a44cd0e..19bc675f87 100644 --- a/deps/rcheevos/include/rurl.h +++ b/deps/rcheevos/include/rurl.h @@ -7,11 +7,11 @@ extern "C" { #endif -int rc_url_award_cheevo(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned cheevo_id, int hardcore); +int rc_url_award_cheevo(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned cheevo_id, int hardcore, const char* game_hash); -int rc_url_submit_lboard(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned lboard_id, int value, const char* game_hash); +int rc_url_submit_lboard(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned lboard_id, int value); -int rc_url_get_gameid(char* buffer, size_t size, unsigned char hash[16]); +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); @@ -25,6 +25,9 @@ int rc_url_get_unlock_list(char* buffer, size_t size, const char* user_name, con int rc_url_post_playing(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned gameid); +int rc_url_ping(char* url_buffer, size_t url_buffer_size, char* post_buffer, size_t post_buffer_size, + const char* user_name, const char* login_token, unsigned gameid, const char* rich_presence); + #ifdef __cplusplus } #endif diff --git a/deps/rcheevos/src/rcheevos/alloc.c b/deps/rcheevos/src/rcheevos/alloc.c index 2730fa1ff1..69db981549 100644 --- a/deps/rcheevos/src/rcheevos/alloc.c +++ b/deps/rcheevos/src/rcheevos/alloc.c @@ -52,3 +52,37 @@ void rc_destroy_parse_state(rc_parse_state_t* parse) if (parse->scratch.memref != parse->scratch.memref_buffer) free(parse->scratch.memref); } + +const char* rc_error_str(int ret) +{ + switch (ret) { + case RC_OK: return "OK"; + case RC_INVALID_LUA_OPERAND: return "Invalid Lua operand"; + case RC_INVALID_MEMORY_OPERAND: return "Invalid memory operand"; + case RC_INVALID_CONST_OPERAND: return "Invalid constant operand"; + case RC_INVALID_FP_OPERAND: return "Invalid floating-point operand"; + case RC_INVALID_CONDITION_TYPE: return "Invalid condition type"; + case RC_INVALID_OPERATOR: return "Invalid operator"; + case RC_INVALID_REQUIRED_HITS: return "Invalid required hits"; + case RC_DUPLICATED_START: return "Duplicated start condition"; + case RC_DUPLICATED_CANCEL: return "Duplicated cancel condition"; + case RC_DUPLICATED_SUBMIT: return "Duplicated submit condition"; + case RC_DUPLICATED_VALUE: return "Duplicated value expression"; + case RC_DUPLICATED_PROGRESS: return "Duplicated progress expression"; + case RC_MISSING_START: return "Missing start condition"; + case RC_MISSING_CANCEL: return "Missing cancel condition"; + case RC_MISSING_SUBMIT: return "Missing submit condition"; + case RC_MISSING_VALUE: return "Missing value expression"; + case RC_INVALID_LBOARD_FIELD: return "Invalid field in leaderboard"; + case RC_MISSING_DISPLAY_STRING: return "Missing display string"; + case RC_OUT_OF_MEMORY: return "Out of memory"; + case RC_INVALID_VALUE_FLAG: return "Invalid flag in value expression"; + case RC_MISSING_VALUE_MEASURED: return "Missing measured flag in value expression"; + case RC_MULTIPLE_MEASURED: return "Multiple measured targets"; + case RC_INVALID_MEASURED_TARGET: return "Invalid measured target"; + case RC_INVALID_COMPARISON: return "Invalid comparison"; + case RC_INVALID_STATE: return "Invalid state"; + + default: return "Unknown error"; + } +} diff --git a/deps/rcheevos/src/rcheevos/compat.c b/deps/rcheevos/src/rcheevos/compat.c new file mode 100644 index 0000000000..6d6402503f --- /dev/null +++ b/deps/rcheevos/src/rcheevos/compat.c @@ -0,0 +1,44 @@ +#include "compat.h" + +#include +#include + +int rc_strncasecmp(const char* left, const char* right, size_t length) +{ + while (length) + { + if (*left != *right) + { + const int diff = tolower(*left) - tolower(*right); + if (diff != 0) + return diff; + } + + ++left; + ++right; + --length; + } + + return 0; +} + +char* rc_strdup(const char* str) +{ + const size_t length = strlen(str); + char* buffer = (char*)malloc(length + 1); + memcpy(buffer, str, length + 1); + return buffer; +} + +int rc_snprintf(char* buffer, size_t size, const char* format, ...) +{ + int result; + va_list args; + + va_start(args, format); + /* assume buffer is large enough and ignore size */ + result = vsprintf(buffer, format, args); + va_end(args); + + return result; +} diff --git a/deps/rcheevos/src/rcheevos/compat.h b/deps/rcheevos/src/rcheevos/compat.h new file mode 100644 index 0000000000..3bc648ab0f --- /dev/null +++ b/deps/rcheevos/src/rcheevos/compat.h @@ -0,0 +1,55 @@ +#ifndef RC_COMPAT_H +#define RC_COMPAT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#if defined(MINGW) || defined(__MINGW32__) || defined(__MINGW64__) + +/* MinGW redefinitions */ + +#elif defined(_MSC_VER) + +/* Visual Studio redefinitions */ + +#ifndef strcasecmp + #define strcasecmp _stricmp +#endif +#ifndef strncasecmp + #define strncasecmp _strnicmp +#endif +#ifndef strdup + #define strdup _strdup +#endif + +#elif __STDC_VERSION__ < 199901L + +/* C89 redefinitions */ + +#ifndef snprintf + extern int rc_snprintf(char* buffer, size_t size, const char* format, ...); + #define snprintf rc_snprintf +#endif + +#ifndef strncasecmp + extern int rc_strncasecmp(const char* left, const char* right, size_t length); + #define strncasecmp rc_strncasecmp +#endif + +#ifndef strdup + extern char* rc_strdup(const char* str); + #define strdup rc_strdup +#endif + +#endif /* __STDC_VERSION__ < 199901L */ + +#ifdef __cplusplus +} +#endif + +#endif /* RC_COMPAT_H */ diff --git a/deps/rcheevos/src/rcheevos/condition.c b/deps/rcheevos/src/rcheevos/condition.c index 7343e19564..1afb8bbbd2 100644 --- a/deps/rcheevos/src/rcheevos/condition.c +++ b/deps/rcheevos/src/rcheevos/condition.c @@ -6,6 +6,7 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse rc_condition_t* self; const char* aux; int ret2; + int can_modify = 0; aux = *memaddr; self = RC_ALLOC(rc_condition_t, parse); @@ -15,12 +16,15 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse switch (*aux) { case 'p': case 'P': self->type = RC_CONDITION_PAUSE_IF; break; case 'r': case 'R': self->type = RC_CONDITION_RESET_IF; break; - case 'a': case 'A': self->type = RC_CONDITION_ADD_SOURCE; break; - case 'b': case 'B': self->type = RC_CONDITION_SUB_SOURCE; break; + case 'a': case 'A': self->type = RC_CONDITION_ADD_SOURCE; can_modify = 1; break; + case 'b': case 'B': self->type = RC_CONDITION_SUB_SOURCE; can_modify = 1; break; case 'c': case 'C': self->type = RC_CONDITION_ADD_HITS; break; case 'n': case 'N': self->type = RC_CONDITION_AND_NEXT; break; + case 'o': case 'O': self->type = RC_CONDITION_OR_NEXT; break; case 'm': case 'M': self->type = RC_CONDITION_MEASURED; break; - case 'i': case 'I': self->type = RC_CONDITION_ADD_ADDRESS; break; + case 'q': case 'Q': self->type = RC_CONDITION_MEASURED_IF; break; + case 'i': case 'I': self->type = RC_CONDITION_ADD_ADDRESS; can_modify = 1; break; + case 't': case 'T': self->type = RC_CONDITION_TRIGGER; break; default: parse->offset = RC_INVALID_CONDITION_TYPE; return 0; } @@ -37,9 +41,14 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse return 0; } + if (self->operand1.type == RC_OPERAND_FP) { + parse->offset = can_modify ? RC_INVALID_FP_OPERAND : RC_INVALID_COMPARISON; + return 0; + } + switch (*aux++) { case '=': - self->oper = RC_CONDITION_EQ; + self->oper = RC_OPERATOR_EQ; aux += *aux == '='; break; @@ -51,33 +60,45 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse return 0; } - self->oper = RC_CONDITION_NE; + self->oper = RC_OPERATOR_NE; break; case '<': - self->oper = RC_CONDITION_LT; + self->oper = RC_OPERATOR_LT; if (*aux == '=') { - self->oper = RC_CONDITION_LE; + self->oper = RC_OPERATOR_LE; aux++; } break; case '>': - self->oper = RC_CONDITION_GT; + self->oper = RC_OPERATOR_GT; if (*aux == '=') { - self->oper = RC_CONDITION_GE; + self->oper = RC_OPERATOR_GE; aux++; } break; + case '*': + self->oper = RC_OPERATOR_MULT; + break; + + case '/': + self->oper = RC_OPERATOR_DIV; + break; + + case '&': + self->oper = RC_OPERATOR_AND; + break; + case '_': case ')': case '\0': - self->oper = RC_CONDITION_NONE; + self->oper = RC_OPERATOR_NONE; self->operand2.type = RC_OPERAND_CONST; self->operand2.value.num = 1; self->required_hits = 0; @@ -85,6 +106,36 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse return self; } + switch (self->oper) { + case RC_OPERATOR_MULT: + case RC_OPERATOR_DIV: + case RC_OPERATOR_AND: + /* modifying operators are only valid on modifying statements */ + if (!can_modify) { + parse->offset = RC_INVALID_OPERATOR; + return 0; + } + break; + + default: + /* comparison operators are not valid on modifying statements */ + if (can_modify) { + switch (self->type) { + case RC_CONDITION_ADD_SOURCE: + case RC_CONDITION_SUB_SOURCE: + case RC_CONDITION_ADD_ADDRESS: + /* prevent parse errors on legacy achievements where a condition was present before changing the type */ + self->oper = RC_OPERATOR_NONE; + break; + + default: + parse->offset = RC_INVALID_OPERATOR; + return 0; + } + } + break; + } + ret2 = rc_parse_operand(&self->operand2, &aux, 1, is_indirect, parse); if (ret2 < 0) { @@ -92,6 +143,17 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse return 0; } + if (self->oper == RC_OPERATOR_NONE) { + /* if operator is none, explicitly clear out the right side */ + self->operand2.type = RC_INVALID_CONST_OPERAND; + self->operand2.value.num = 0; + } + + if (!can_modify && self->operand2.type == RC_OPERAND_FP) { + parse->offset = RC_INVALID_COMPARISON; + return 0; + } + if (*aux == '(') { char* end; self->required_hits = (unsigned)strtoul(++aux, &end, 10); @@ -127,13 +189,39 @@ int rc_test_condition(rc_condition_t* self, rc_eval_state_t* eval_state) { unsigned value2 = rc_evaluate_operand(&self->operand2, eval_state); switch (self->oper) { - case RC_CONDITION_EQ: return value1 == value2; - case RC_CONDITION_NE: return value1 != value2; - case RC_CONDITION_LT: return value1 < value2; - case RC_CONDITION_LE: return value1 <= value2; - case RC_CONDITION_GT: return value1 > value2; - case RC_CONDITION_GE: return value1 >= value2; - case RC_CONDITION_NONE: return 1; + case RC_OPERATOR_EQ: return value1 == value2; + case RC_OPERATOR_NE: return value1 != value2; + case RC_OPERATOR_LT: return value1 < value2; + case RC_OPERATOR_LE: return value1 <= value2; + case RC_OPERATOR_GT: return value1 > value2; + case RC_OPERATOR_GE: return value1 >= value2; + case RC_OPERATOR_NONE: return 1; default: return 1; } } + +int rc_evaluate_condition_value(rc_condition_t* self, rc_eval_state_t* eval_state) { + unsigned value = rc_evaluate_operand(&self->operand1, eval_state); + + switch (self->oper) { + case RC_OPERATOR_MULT: + if (self->operand2.type == RC_OPERAND_FP) + value = (int)((double)value * self->operand2.value.dbl); + else + value *= rc_evaluate_operand(&self->operand2, eval_state); + break; + + case RC_OPERATOR_DIV: + if (self->operand2.type == RC_OPERAND_FP) + value = (int)((double)value / self->operand2.value.dbl); + else + value /= rc_evaluate_operand(&self->operand2, eval_state); + break; + + case RC_OPERATOR_AND: + value &= rc_evaluate_operand(&self->operand2, eval_state); + break; + } + + return value; +} diff --git a/deps/rcheevos/src/rcheevos/condset.c b/deps/rcheevos/src/rcheevos/condset.c index a9ed1513f9..9e142a61d3 100644 --- a/deps/rcheevos/src/rcheevos/condset.c +++ b/deps/rcheevos/src/rcheevos/condset.c @@ -9,15 +9,16 @@ static void rc_update_condition_pause(rc_condition_t* condition, int* in_pause) case RC_CONDITION_PAUSE_IF: *in_pause = condition->pause = 1; break; - + case RC_CONDITION_ADD_SOURCE: case RC_CONDITION_SUB_SOURCE: case RC_CONDITION_ADD_HITS: case RC_CONDITION_AND_NEXT: + case RC_CONDITION_OR_NEXT: case RC_CONDITION_ADD_ADDRESS: condition->pause = *in_pause; break; - + default: *in_pause = condition->pause = 0; break; @@ -48,14 +49,15 @@ rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse) { return 0; } - if ((*next)->oper == RC_CONDITION_NONE) { + if ((*next)->oper == RC_OPERATOR_NONE) { switch ((*next)->type) { case RC_CONDITION_ADD_ADDRESS: case RC_CONDITION_ADD_HITS: case RC_CONDITION_ADD_SOURCE: case RC_CONDITION_SUB_SOURCE: case RC_CONDITION_AND_NEXT: - break; + case RC_CONDITION_OR_NEXT: + break; default: parse->offset = RC_INVALID_OPERATOR; @@ -110,10 +112,14 @@ rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse) { static int rc_test_condset_internal(rc_condset_t* self, int processing_pause, rc_eval_state_t* eval_state) { rc_condition_t* condition; - int set_valid, cond_valid, prev_cond; + int set_valid, cond_valid, and_next, or_next; + unsigned measured_value = 0; + int can_measure = 1, measured_from_hits = 0; + eval_state->primed = 1; set_valid = 1; - prev_cond = 1; + and_next = 1; + or_next = 0; eval_state->add_value = eval_state->add_hits = eval_state->add_address = 0; for (condition = self->conditions; condition != 0; condition = condition->next) { @@ -121,93 +127,107 @@ static int rc_test_condset_internal(rc_condset_t* self, int processing_pause, rc continue; } + /* STEP 1: process modifier conditions */ switch (condition->type) { case RC_CONDITION_ADD_SOURCE: - eval_state->add_value += rc_evaluate_operand(&condition->operand1, eval_state); + eval_state->add_value += rc_evaluate_condition_value(condition, eval_state); eval_state->add_address = 0; continue; - + case RC_CONDITION_SUB_SOURCE: - eval_state->add_value -= rc_evaluate_operand(&condition->operand1, eval_state); - eval_state->add_address = 0; - continue; - - case RC_CONDITION_ADD_HITS: - /* always evaluate the condition to ensure everything is updated correctly */ - cond_valid = rc_test_condition(condition, eval_state); - - /* merge AndNext value and reset it for the next condition */ - cond_valid &= prev_cond; - prev_cond = 1; - - /* if the condition is true, tally it */ - if (cond_valid) { - if (condition->required_hits == 0 || condition->current_hits < condition->required_hits) { - condition->current_hits++; - } - - condition->is_true = (condition->required_hits == 0 || condition->current_hits >= condition->required_hits); - } - else { - condition->is_true = 0; - } - - eval_state->add_value = 0; - eval_state->add_address = 0; - eval_state->add_hits += condition->current_hits; - continue; - - case RC_CONDITION_AND_NEXT: - prev_cond &= rc_test_condition(condition, eval_state); - eval_state->add_value = 0; + eval_state->add_value -= rc_evaluate_condition_value(condition, eval_state); eval_state->add_address = 0; continue; case RC_CONDITION_ADD_ADDRESS: - eval_state->add_address = rc_evaluate_operand(&condition->operand1, eval_state); + eval_state->add_address = rc_evaluate_condition_value(condition, eval_state); continue; + + case RC_CONDITION_MEASURED: + if (condition->required_hits == 0) { + /* Measured condition without a hit target measures the value of the left operand */ + measured_value = rc_evaluate_condition_value(condition, eval_state) + eval_state->add_value; + } + break; + + default: + break; } - /* always evaluate the condition to ensure everything is updated correctly */ - cond_valid = rc_test_condition(condition, eval_state); + /* STEP 2: evaluate the current condition */ + condition->is_true = rc_test_condition(condition, eval_state); + eval_state->add_value = 0; + eval_state->add_address = 0; - /* merge AndNext value and reset it for the next condition */ - cond_valid &= prev_cond; - prev_cond = 1; + /* apply logic flags and reset them for the next condition */ + cond_valid = condition->is_true; + cond_valid &= and_next; + cond_valid |= or_next; + and_next = 1; + or_next = 0; - /* if the condition has a target hit count that has already been met, it's automatically true, even if not currently true. */ - if (condition->required_hits != 0 && (condition->current_hits + eval_state->add_hits) >= condition->required_hits) { - cond_valid = 1; - } - else if (cond_valid) { - condition->current_hits++; + /* true conditions should update hit count */ + if (cond_valid) { + eval_state->has_hits = 1; if (condition->required_hits == 0) { - /* not a hit-based requirement: ignore any additional logic! */ + /* no target hit count, just keep tallying */ + ++condition->current_hits; } - else if ((condition->current_hits + eval_state->add_hits) < condition->required_hits) { - /* HitCount target has not yet been met, condition is not yet valid */ - cond_valid = 0; + else if (condition->current_hits < condition->required_hits) { + /* target hit count hasn't been met, tally and revalidate - only true if hit count becomes met */ + ++condition->current_hits; + cond_valid = (condition->current_hits == condition->required_hits); + } + else { + /* target hit count has been met, do nothing */ } } - condition->is_true = cond_valid; - eval_state->has_hits |= (condition->current_hits || eval_state->add_hits); + else if (condition->current_hits > 0) { + /* target has been true in the past, if the hit target is met, consider it true now */ + eval_state->has_hits = 1; + cond_valid = (condition->current_hits == condition->required_hits); + } - /* capture measured state */ - if (condition->type == RC_CONDITION_MEASURED) { - unsigned int measured_value; - if (condition->required_hits > 0) + /* STEP 3: handle logic flags */ + switch (condition->type) { + case RC_CONDITION_ADD_HITS: + eval_state->add_hits += condition->current_hits; + continue; + + case RC_CONDITION_AND_NEXT: + and_next = cond_valid; + continue; + + case RC_CONDITION_OR_NEXT: + or_next = cond_valid; + continue; + + default: + break; + } + + if (eval_state->add_hits) { + if (condition->required_hits != 0) { + /* if the condition has a target hit count, we have to recalculate cond_valid including the AddHits counter */ + measured_from_hits = 1; measured_value = condition->current_hits + eval_state->add_hits; - else - measured_value = rc_evaluate_operand(&condition->operand1, eval_state) + eval_state->add_value; + cond_valid = (measured_value >= condition->required_hits); + } + else { + /* no target hit count. we can't tell if the add_hits value is from this frame or not, so ignore it. + complex condition will only be true if the current condition is true */ + } - if (measured_value > eval_state->measured_value) - eval_state->measured_value = measured_value; + eval_state->add_hits = 0; + } + else if (condition->required_hits != 0) { + /* if there's a hit target, capture the current hits for recording Measured value later */ + measured_from_hits = 1; + measured_value = condition->current_hits; } - /* reset AddHits and AddSource/SubSource values */ - eval_state->add_value = eval_state->add_hits = eval_state->add_address = 0; - + /* STEP 4: handle special flags */ switch (condition->type) { case RC_CONDITION_PAUSE_IF: /* as soon as we find a PauseIf that evaluates to true, stop processing the rest of the group */ @@ -227,20 +247,38 @@ static int rc_test_condset_internal(rc_condset_t* self, int processing_pause, rc /* PauseIf has a HitCount that hasn't been met, ignore it for now. */ } - break; - + continue; + case RC_CONDITION_RESET_IF: if (cond_valid) { eval_state->was_reset = 1; /* let caller know to reset all hit counts */ set_valid = 0; /* cannot be valid if we've hit a reset condition */ } + continue; + case RC_CONDITION_MEASURED_IF: + if (!cond_valid) + can_measure = 0; break; + case RC_CONDITION_TRIGGER: + /* update truthiness of set, but do not update truthiness of primed state */ + set_valid &= cond_valid; + continue; + default: - set_valid &= cond_valid; break; } + + /* STEP 5: update overall truthiness of set and primed state */ + eval_state->primed &= cond_valid; + set_valid &= cond_valid; + } + + /* 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; } return set_valid; @@ -255,6 +293,7 @@ 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 */ + eval_state->primed = 0; return 0; } } diff --git a/deps/rcheevos/src/rcheevos/consoleinfo.c b/deps/rcheevos/src/rcheevos/consoleinfo.c new file mode 100644 index 0000000000..a610b8ce95 --- /dev/null +++ b/deps/rcheevos/src/rcheevos/consoleinfo.c @@ -0,0 +1,573 @@ +#include "rcheevos.h" +#include "rconsoles.h" + +#include + +const char* rc_console_name(int console_id) +{ + switch (console_id) + { + case RC_CONSOLE_3DO: + return "3DO"; + + case RC_CONSOLE_AMIGA: + return "Amiga"; + + case RC_CONSOLE_AMIGA_ST: + return "Amiga ST"; + + case RC_CONSOLE_AMSTRAD_PC: + return "Amstrad CPC"; + + case RC_CONSOLE_APPLE_II: + return "Apple II"; + + case RC_CONSOLE_ARCADE: + return "Arcade"; + + case RC_CONSOLE_ATARI_2600: + return "Atari 2600"; + + case RC_CONSOLE_ATARI_5200: + return "Atari 5200"; + + case RC_CONSOLE_ATARI_7800: + return "Atari 7800"; + + case RC_CONSOLE_ATARI_JAGUAR: + return "Atari Jaguar"; + + case RC_CONSOLE_ATARI_LYNX: + return "Atari Lynx"; + + case RC_CONSOLE_CASSETTEVISION: + return "CassetteVision"; + + case RC_CONSOLE_CDI: + return "CD-I"; + + case RC_CONSOLE_COLECOVISION: + return "ColecoVision"; + + case RC_CONSOLE_COMMODORE_64: + return "Commodore 64"; + + case RC_CONSOLE_DREAMCAST: + return "Dreamcast"; + + case RC_CONSOLE_EVENTS: + return "Events"; + + case RC_CONSOLE_GAMEBOY: + return "GameBoy"; + + case RC_CONSOLE_GAMEBOY_ADVANCE: + return "GameBoy Advance"; + + case RC_CONSOLE_GAMEBOY_COLOR: + return "GameBoy Color"; + + case RC_CONSOLE_GAMECUBE: + return "GameCube"; + + case RC_CONSOLE_GAME_GEAR: + return "Game Gear"; + + case RC_CONSOLE_HUBS: + return "Hubs"; + + case RC_CONSOLE_INTELLIVISION: + return "Intellivision"; + + case RC_CONSOLE_MASTER_SYSTEM: + return "Master System"; + + case RC_CONSOLE_MEGA_DRIVE: + return "Sega Genesis"; + + case RC_CONSOLE_MS_DOS: + return "MS-DOS"; + + case RC_CONSOLE_MSX: + return "MSX"; + + case RC_CONSOLE_NINTENDO: + return "Nintendo Entertainment System"; + + case RC_CONSOLE_NINTENDO_64: + return "Nintendo 64"; + + case RC_CONSOLE_NINTENDO_DS: + return "Nintendo DS"; + + case RC_CONSOLE_NEOGEO_POCKET: + return "Neo Geo Pocket"; + + case RC_CONSOLE_ORIC: + return "Oric"; + + case RC_CONSOLE_PC8800: + return "PC-8000/8800"; + + case RC_CONSOLE_PC9800: + return "PC-9800"; + + case RC_CONSOLE_PCFX: + return "PCFX"; + + case RC_CONSOLE_PC_ENGINE: + return "PCEngine"; + + case RC_CONSOLE_PLAYSTATION: + return "PlayStation"; + + case RC_CONSOLE_PLAYSTATION_2: + return "PlayStation 2"; + + case RC_CONSOLE_PSP: + return "PlayStation Portable"; + + case RC_CONSOLE_POKEMON_MINI: + return "Pokemon Mini"; + + case RC_CONSOLE_SEGA_32X: + return "Sega 32X"; + + case RC_CONSOLE_SEGA_CD: + return "Sega CD"; + + case RC_CONSOLE_SATURN: + return "Sega Saturn"; + + case RC_CONSOLE_SG1000: + return "SG-1000"; + + case RC_CONSOLE_SUPER_NINTENDO: + return "Super Nintendo Entertainment System"; + + case RC_CONSOLE_SUPER_CASSETTEVISION: + return "Super CassetteVision"; + + case RC_CONSOLE_WONDERSWAN: + return "WonderSwan"; + + case RC_CONSOLE_VECTREX: + return "Vectrex"; + + case RC_CONSOLE_VIC20: + return "VIC-20"; + + case RC_CONSOLE_VIRTUAL_BOY: + return "Virtual Boy"; + + case RC_CONSOLE_WII: + return "Wii"; + + case RC_CONSOLE_WII_U: + return "Wii-U"; + + case RC_CONSOLE_X68K: + return "X68K"; + + case RC_CONSOLE_XBOX: + return "XBOX"; + + case RC_CONSOLE_ZX81: + return "ZX-81"; + + default: + return "Unknown"; + } +} + +/* ===== 3DO ===== */ +/* http://www.arcaderestoration.com/memorymap/48/3DO+Bios.aspx */ +/* NOTE: the Opera core attempts to expose the NVRAM as RETRO_SAVE_RAM, but the 3DO documentation + * says that applications should only access NVRAM through API calls as it's shared across mulitple + * games. This suggests that even if the core does expose it, it may change depending on which other + * games the user has played - so ignore it. + */ +static const rc_memory_region_t _rc_memory_regions_3do[] = { + { 0x000000U, 0x1FFFFFU, 0x000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Main RAM" }, +}; +static const rc_memory_regions_t rc_memory_regions_3do = { _rc_memory_regions_3do, 1 }; + +/* ===== Apple II ===== */ +static const rc_memory_region_t _rc_memory_regions_appleii[] = { + { 0x000000U, 0x00FFFFU, 0x000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Main RAM" }, + { 0x010000U, 0x01FFFFU, 0x010000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Auxillary RAM" } +}; +static const rc_memory_regions_t rc_memory_regions_appleii = { _rc_memory_regions_appleii, 2 }; + +/* ===== Atari 2600 ===== */ +static const rc_memory_region_t _rc_memory_regions_atari2600[] = { + { 0x000000U, 0x00007FU, 0x000080U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" } +}; +static const rc_memory_regions_t rc_memory_regions_atari2600 = { _rc_memory_regions_atari2600, 1 }; + +/* ===== Atari 7800 ===== */ +/* http://www.atarihq.com/danb/files/78map.txt */ +/* http://pdf.textfiles.com/technical/7800_devkit.pdf */ +static const rc_memory_region_t _rc_memory_regions_atari7800[] = { + { 0x000000U, 0x0017FFU, 0x000000U, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "Hardware Interface" }, + { 0x001800U, 0x0027FFU, 0x001800U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, + { 0x002800U, 0x002FFFU, 0x002800U, RC_MEMORY_TYPE_VIRTUAL_RAM, "Mirrored RAM" }, + { 0x003000U, 0x0037FFU, 0x003000U, RC_MEMORY_TYPE_VIRTUAL_RAM, "Mirrored RAM" }, + { 0x003800U, 0x003FFFU, 0x003800U, RC_MEMORY_TYPE_VIRTUAL_RAM, "Mirrored RAM" }, + { 0x004000U, 0x007FFFU, 0x004000U, RC_MEMORY_TYPE_SAVE_RAM, "Cartridge RAM" }, + { 0x008000U, 0x00FFFFU, 0x008000U, RC_MEMORY_TYPE_READONLY, "Cartridge ROM" } +}; +static const rc_memory_regions_t rc_memory_regions_atari7800 = { _rc_memory_regions_atari7800, 7 }; + +/* ===== Atari Jaguar ===== */ +/* https://www.mulle-kybernetik.com/jagdox/memorymap.html */ +static const rc_memory_region_t _rc_memory_regions_atari_jaguar[] = { + { 0x000000U, 0x1FFFFFU, 0x000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" } +}; +static const rc_memory_regions_t rc_memory_regions_atari_jaguar = { _rc_memory_regions_atari_jaguar, 1 }; + +/* ===== Atari Lynx ===== */ +/* http://www.retroisle.com/atari/lynx/Technical/Programming/lynxprgdumm.php */ +static const rc_memory_region_t _rc_memory_regions_atari_lynx[] = { + { 0x000000U, 0x0000FFU, 0x000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Zero Page" }, + { 0x000100U, 0x0001FFU, 0x000100U, RC_MEMORY_TYPE_SYSTEM_RAM, "Stack" }, + { 0x000200U, 0x00FBFFU, 0x000200U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, + { 0x00FC00U, 0x00FCFFU, 0x00FC00U, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "SUZY hardware access" }, + { 0x00FD00U, 0x00FDFFU, 0x00FD00U, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "MIKEY hardware access" }, + { 0x00FE00U, 0x00FFF7U, 0x00FE00U, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "Boot ROM" }, + { 0x00FFF8U, 0x00FFFFU, 0x00FFF8U, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "Hardware vectors" } +}; +static const rc_memory_regions_t rc_memory_regions_atari_lynx = { _rc_memory_regions_atari_lynx, 7 }; + +/* ===== ColecoVision ===== */ +static const rc_memory_region_t _rc_memory_regions_colecovision[] = { + { 0x000000U, 0x0003FFU, 0x006000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" } +}; +static const rc_memory_regions_t rc_memory_regions_colecovision = { _rc_memory_regions_colecovision, 1 }; + +/* ===== GameBoy / GameBoy Color ===== */ +static const rc_memory_region_t _rc_memory_regions_gameboy[] = { + { 0x000000U, 0x0000FFU, 0x000000U, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "Interrupt vector" }, + { 0x000100U, 0x00014FU, 0x000100U, RC_MEMORY_TYPE_READONLY, "Cartridge header" }, + { 0x000150U, 0x003FFFU, 0x000150U, RC_MEMORY_TYPE_READONLY, "Cartridge ROM (fixed)" }, /* bank 0 */ + { 0x004000U, 0x007FFFU, 0x004000U, RC_MEMORY_TYPE_READONLY, "Cartridge ROM (paged)" }, /* bank 1-XX (switchable) */ + { 0x008000U, 0x0097FFU, 0x008000U, RC_MEMORY_TYPE_VIDEO_RAM, "Tile RAM" }, + { 0x009800U, 0x009BFFU, 0x009800U, RC_MEMORY_TYPE_VIDEO_RAM, "BG1 map data" }, + { 0x009C00U, 0x009FFFU, 0x009C00U, RC_MEMORY_TYPE_VIDEO_RAM, "BG2 map data" }, + { 0x00A000U, 0x00BFFFU, 0x00A000U, RC_MEMORY_TYPE_SAVE_RAM, "Cartridge RAM"}, + { 0x00C000U, 0x00CFFFU, 0x00C000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM (fixed)" }, + { 0x00D000U, 0x00DFFFU, 0x00D000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM (bank 1)" }, + { 0x00E000U, 0x00FDFFU, 0x00C000U, RC_MEMORY_TYPE_VIRTUAL_RAM, "Echo RAM" }, + { 0x00FE00U, 0x00FE9FU, 0x00FE00U, RC_MEMORY_TYPE_VIDEO_RAM, "Sprite RAM"}, + { 0x00FEA0U, 0x00FEFFU, 0x00FEA0U, RC_MEMORY_TYPE_READONLY, "Unusable"}, + { 0x00FF00U, 0x00FF7FU, 0x00FF00U, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "Hardware I/O"}, + { 0x00FF80U, 0x00FFFEU, 0x00FF80U, RC_MEMORY_TYPE_SYSTEM_RAM, "Quick RAM"}, + { 0x00FFFFU, 0x00FFFFU, 0x00FFFFU, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "Interrupt enable"}, + + /* GameBoy Color provides six extra banks of memory that can be paged out through the $DXXX + * memory space, but the timing of that does not correspond with blanks, which is when achievements + * are processed. As such, it is desirable to always have access to these extra banks. We do this + * by expecting the extra banks to be addressable at addresses not supported by the native system. */ + { 0x010000U, 0x015FFFU, 0x010000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM (banks 2-7, GBC only)" } +}; +static const rc_memory_regions_t rc_memory_regions_gameboy = { _rc_memory_regions_gameboy, 16 }; +static const rc_memory_regions_t rc_memory_regions_gameboy_color = { _rc_memory_regions_gameboy, 17 }; + +/* ===== GameBoy Advance ===== */ +static const rc_memory_region_t _rc_memory_regions_gameboy_advance[] = { + { 0x000000U, 0x007FFFU, 0x03000000U, RC_MEMORY_TYPE_SAVE_RAM, "Cartridge RAM" }, + { 0x008000U, 0x047FFFU, 0x02000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" } +}; +static const rc_memory_regions_t rc_memory_regions_gameboy_advance = { _rc_memory_regions_gameboy_advance, 2 }; + +/* ===== Game Gear ===== */ +/* http://www.smspower.org/Development/MemoryMap */ +static const rc_memory_region_t _rc_memory_regions_game_gear[] = { + { 0x000000U, 0x001FFFU, 0x00C000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" } +}; +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 */ +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" }, +}; +static const rc_memory_regions_t rc_memory_regions_intellivision = { _rc_memory_regions_intellivision, 9 }; + +/* ===== Master System ===== */ +/* http://www.smspower.org/Development/MemoryMap */ +static const rc_memory_region_t _rc_memory_regions_master_system[] = { + { 0x000000U, 0x001FFFU, 0x00C000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" } +}; +static const rc_memory_regions_t rc_memory_regions_master_system = { _rc_memory_regions_master_system, 1 }; + +/* ===== MegaDrive (Genesis) ===== */ +/* http://www.smspower.org/Development/MemoryMap */ +static const rc_memory_region_t _rc_memory_regions_megadrive[] = { + { 0x000000U, 0x00FFFFU, 0xFF0000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, + { 0x010000U, 0x01FFFFU, 0x000000U, RC_MEMORY_TYPE_SAVE_RAM, "Cartridge RAM" } +}; +static const rc_memory_regions_t rc_memory_regions_megadrive = { _rc_memory_regions_megadrive, 2 }; + +/* ===== Neo Geo Pocket ===== */ +/* http://neopocott.emuunlim.com/docs/tech-11.txt */ +static const rc_memory_region_t _rc_memory_regions_neo_geo_pocket[] = { + /* MednafenNGP exposes 16KB, but the doc suggests there's 24-32KB */ + { 0x000000U, 0x003FFFU, 0x000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" } +}; +static const rc_memory_regions_t rc_memory_regions_neo_geo_pocket = { _rc_memory_regions_neo_geo_pocket, 1 }; + +/* ===== Nintendo Entertainment System ===== */ +/* https://wiki.nesdev.com/w/index.php/CPU_memory_map */ +static const rc_memory_region_t _rc_memory_regions_nes[] = { + { 0x0000U, 0x07FFU, 0x0000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, + { 0x0800U, 0x0FFFU, 0x0000U, RC_MEMORY_TYPE_VIRTUAL_RAM, "Mirror RAM" }, /* duplicates memory from $0000-$07FF */ + { 0x1000U, 0x17FFU, 0x0000U, RC_MEMORY_TYPE_VIRTUAL_RAM, "Mirror RAM" }, /* duplicates memory from $0000-$07FF */ + { 0x1800U, 0x1FFFU, 0x0000U, RC_MEMORY_TYPE_VIRTUAL_RAM, "Mirror RAM" }, /* duplicates memory from $0000-$07FF */ + { 0x2000U, 0x2007U, 0x2000U, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "PPU Register" }, + { 0x2008U, 0x3FFFU, 0x2008U, RC_MEMORY_TYPE_VIRTUAL_RAM, "Mirrored PPU Register" }, /* repeats every 8 bytes */ + { 0x4000U, 0x4017U, 0x4000U, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "APU and I/O register" }, + { 0x4018U, 0x401FU, 0x4018U, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "APU and I/O test register" }, + { 0x4020U, 0x5FFFU, 0x4020U, RC_MEMORY_TYPE_READONLY, "Cartridge data"}, /* varies by mapper */ + { 0x6000U, 0x7FFFU, 0x6000U, RC_MEMORY_TYPE_SAVE_RAM, "Cartridge RAM"}, + { 0x8000U, 0xFFFFU, 0x8000U, RC_MEMORY_TYPE_READONLY, "Cartridge ROM"}, +}; +static const rc_memory_regions_t rc_memory_regions_nes = { _rc_memory_regions_nes, 11 }; + +/* ===== Nintendo 64 ===== */ +/* https://raw.githubusercontent.com/mikeryan/n64dev/master/docs/n64ops/n64ops%23h.txt */ +static const rc_memory_region_t _rc_memory_regions_n64[] = { + { 0x000000U, 0x1FFFFFU, 0x00000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, /* RDRAM 1 */ + { 0x200000U, 0x3FFFFFU, 0x00020000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, /* RDRAM 2 */ + { 0x400000U, 0x7FFFFFU, 0x80000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" } /* expansion pak - cannot find any details for real address */ +}; +static const rc_memory_regions_t rc_memory_regions_n64 = { _rc_memory_regions_n64, 3 }; + +/* ===== Nintendo DS ===== */ +/* https://www.akkit.org/info/gbatek.htm#dsmemorymaps */ +static const rc_memory_region_t _rc_memory_regions_nintendo_ds[] = { + { 0x000000U, 0x3FFFFFU, 0x02000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" } +}; +static const rc_memory_regions_t rc_memory_regions_nintendo_ds = { _rc_memory_regions_nintendo_ds, 1 }; + +/* ===== Oric ===== */ +static const rc_memory_region_t _rc_memory_regions_oric[] = { + /* actual size depends on machine type - up to 64KB */ + { 0x000000U, 0x00FFFFU, 0x000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" } +}; +static const rc_memory_regions_t rc_memory_regions_oric = { _rc_memory_regions_oric, 1 }; + +/* ===== PC-8800 ===== */ +static const rc_memory_region_t _rc_memory_regions_pc8800[] = { + { 0x000000U, 0x00FFFFU, 0x000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Main RAM" }, + { 0x010000U, 0x010FFFU, 0x010000U, RC_MEMORY_TYPE_VIDEO_RAM, "Text VRAM" } /* technically VRAM, but often used as system RAM */ +}; +static const rc_memory_regions_t rc_memory_regions_pc8800 = { _rc_memory_regions_pc8800, 2 }; + +/* ===== PC Engine ===== */ +static const rc_memory_region_t _rc_memory_regions_pcengine[] = { + { 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 }; + +/* ===== PlayStation ===== */ +/* http://www.raphnet.net/electronique/psx_adaptor/Playstation.txt */ +static const rc_memory_region_t _rc_memory_regions_playstation[] = { + { 0x000000U, 0x00FFFFU, 0x000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Kernel RAM" }, + { 0x010000U, 0x1FFFFFU, 0x010000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" } +}; +static const rc_memory_regions_t rc_memory_regions_playstation = { _rc_memory_regions_playstation, 2 }; + +/* ===== Pokemon Mini ===== */ +/* https://www.pokemon-mini.net/documentation/memory-map/ */ +static const rc_memory_region_t _rc_memory_regions_pokemini[] = { + { 0x000000U, 0x000FFFU, 0x000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "BIOS RAM" }, + { 0x001000U, 0x001FFFU, 0x001000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" } +}; +static const rc_memory_regions_t rc_memory_regions_pokemini = { _rc_memory_regions_pokemini, 2 }; + +/* ===== Sega CD ===== */ +static const rc_memory_region_t _rc_memory_regions_segacd[] = { + { 0x000000U, 0x00FFFFU, 0x00FF0000U, RC_MEMORY_TYPE_SYSTEM_RAM, "68000 RAM" }, + { 0x010000U, 0x08FFFFU, 0x80000000U, RC_MEMORY_TYPE_SAVE_RAM, "CD PRG RAM" } /* normally banked into $020000-$03FFFF */ +}; +static const rc_memory_regions_t rc_memory_regions_segacd = { _rc_memory_regions_segacd, 2 }; + +/* ===== Sega Saturn ===== */ +/* https://segaretro.org/Sega_Saturn_hardware_notes_(2004-04-27) */ +static const rc_memory_region_t _rc_memory_regions_saturn[] = { + { 0x000000U, 0x0FFFFFU, 0x00200000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Work RAM Low" }, + { 0x100000U, 0x1FFFFFU, 0x06000000U, RC_MEMORY_TYPE_SAVE_RAM, "Work RAM High" } +}; +static const rc_memory_regions_t rc_memory_regions_saturn = { _rc_memory_regions_saturn, 2 }; + +/* ===== SG-1000 ===== */ +/* http://www.smspower.org/Development/MemoryMap */ +static const rc_memory_region_t _rc_memory_regions_sg1000[] = { + { 0x000000U, 0x0003FFU, 0xC000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" } + /* TODO: should cartridge memory be exposed ($0000-$BFFF)? it's usually just ROM data, but may contain on-cartridge RAM + * This not is also concerning: http://www.smspower.org/Development/MemoryMap + * Cartridges may disable the system RAM and thus take over the full 64KB address space. */ +}; +static const rc_memory_regions_t rc_memory_regions_sg1000 = { _rc_memory_regions_sg1000, 1 }; + +/* ===== Super Cassette Vision ===== */ +static const rc_memory_region_t _rc_memory_regions_scv[] = { + { 0x000000U, 0x000FFFU, 0x000000U, RC_MEMORY_TYPE_READONLY, "System ROM" }, + { 0x001000U, 0x001FFFU, 0x001000U, RC_MEMORY_TYPE_UNUSED, "" }, + { 0x002000U, 0x003FFFU, 0x002000U, RC_MEMORY_TYPE_VIDEO_RAM, "Video RAM" }, + { 0x004000U, 0x007FFFU, 0x004000U, RC_MEMORY_TYPE_UNUSED, "" }, + { 0x008000U, 0x00DFFFU, 0x008000U, RC_MEMORY_TYPE_READONLY, "Cartridge ROM" }, + { 0x00E000U, 0x00FF7FU, 0x00E000U, RC_MEMORY_TYPE_SAVE_RAM, "Cartridge RAM" }, + { 0x00FF80U, 0x00FFFFU, 0x00FF80U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" } +}; +static const rc_memory_regions_t rc_memory_regions_scv = { _rc_memory_regions_scv, 7 }; + +/* ===== Super Nintendo ===== */ +/* https://segaretro.org/Sega_Saturn_hardware_notes_(2004-04-27) */ +static const rc_memory_region_t _rc_memory_regions_snes[] = { + { 0x000000U, 0x01FFFFU, 0x7E0000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, + { 0x020000U, 0x03FFFFU, 0xFE0000U, RC_MEMORY_TYPE_SAVE_RAM, "Cartridge RAM" } +}; +static const rc_memory_regions_t rc_memory_regions_snes = { _rc_memory_regions_snes, 2 }; + +/* ===== WonderSwan ===== */ +/* http://daifukkat.su/docs/wsman/#ovr_memmap */ +static const rc_memory_region_t _rc_memory_regions_wonderswan[] = { + /* RAM ends at 0x3FFF for WonderSwan, WonderSwan color uses all 64KB */ + { 0x000000U, 0x00FFFFU, 0x000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" } +}; +static const rc_memory_regions_t rc_memory_regions_wonderswan = { _rc_memory_regions_wonderswan, 1 }; + +/* ===== Vectrex ===== */ +/* https://roadsidethoughts.com/vectrex/vectrex-memory-map.htm */ +static const rc_memory_region_t _rc_memory_regions_vectrex[] = { + { 0x000000U, 0x0003FFU, 0x00C800U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" } +}; +static const rc_memory_regions_t rc_memory_regions_vectrex = { _rc_memory_regions_vectrex, 1 }; + +/* ===== Virtual Boy ===== */ +static const rc_memory_region_t _rc_memory_regions_virtualboy[] = { + { 0x000000U, 0x00FFFFU, 0x05000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, + { 0x010000U, 0x01FFFFU, 0x06000000U, RC_MEMORY_TYPE_SAVE_RAM, "Cartridge RAM" } +}; +static const rc_memory_regions_t rc_memory_regions_virtualboy = { _rc_memory_regions_virtualboy, 2 }; + +/* ===== default ===== */ +static const rc_memory_regions_t rc_memory_regions_none = { 0, 0 }; + +const rc_memory_regions_t* rc_console_memory_regions(int console_id) +{ + switch (console_id) + { + case RC_CONSOLE_3DO: + return &rc_memory_regions_3do; + + case RC_CONSOLE_APPLE_II: + return &rc_memory_regions_appleii; + + case RC_CONSOLE_ATARI_2600: + return &rc_memory_regions_atari2600; + + case RC_CONSOLE_ATARI_7800: + return &rc_memory_regions_atari7800; + + case RC_CONSOLE_ATARI_JAGUAR: + return &rc_memory_regions_atari_jaguar; + + case RC_CONSOLE_ATARI_LYNX: + return &rc_memory_regions_atari_lynx; + + case RC_CONSOLE_COLECOVISION: + return &rc_memory_regions_colecovision; + + case RC_CONSOLE_GAMEBOY: + return &rc_memory_regions_gameboy; + + case RC_CONSOLE_GAMEBOY_COLOR: + return &rc_memory_regions_gameboy_color; + + case RC_CONSOLE_GAMEBOY_ADVANCE: + return &rc_memory_regions_gameboy_advance; + + case RC_CONSOLE_GAME_GEAR: + return &rc_memory_regions_game_gear; + + case RC_CONSOLE_INTELLIVISION: + return &rc_memory_regions_intellivision; + + case RC_CONSOLE_MASTER_SYSTEM: + return &rc_memory_regions_master_system; + + case RC_CONSOLE_MEGA_DRIVE: + case RC_CONSOLE_SEGA_32X: + /* NOTE: 32x adds an extra 512KB of memory (256KB RAM + 256KB VRAM) to the + * Genesis, but we currently don't support it. */ + return &rc_memory_regions_megadrive; + + case RC_CONSOLE_NEOGEO_POCKET: + return &rc_memory_regions_neo_geo_pocket; + + case RC_CONSOLE_NINTENDO: + return &rc_memory_regions_nes; + + case RC_CONSOLE_NINTENDO_64: + return &rc_memory_regions_n64; + + case RC_CONSOLE_NINTENDO_DS: + return &rc_memory_regions_nintendo_ds; + + case RC_CONSOLE_ORIC: + return &rc_memory_regions_oric; + + case RC_CONSOLE_PC8800: + return &rc_memory_regions_pc8800; + + case RC_CONSOLE_PC_ENGINE: + return &rc_memory_regions_pcengine; + + case RC_CONSOLE_PLAYSTATION: + return &rc_memory_regions_playstation; + + case RC_CONSOLE_POKEMON_MINI: + return &rc_memory_regions_pokemini; + + case RC_CONSOLE_SATURN: + return &rc_memory_regions_saturn; + + case RC_CONSOLE_SEGA_CD: + return &rc_memory_regions_segacd; + + case RC_CONSOLE_SG1000: + return &rc_memory_regions_sg1000; + + case RC_CONSOLE_SUPER_CASSETTEVISION: + return &rc_memory_regions_scv; + + case RC_CONSOLE_SUPER_NINTENDO: + return &rc_memory_regions_snes; + + case RC_CONSOLE_WONDERSWAN: + return &rc_memory_regions_wonderswan; + + case RC_CONSOLE_VECTREX: + return &rc_memory_regions_vectrex; + + case RC_CONSOLE_VIRTUAL_BOY: + return &rc_memory_regions_virtualboy; + + default: + return &rc_memory_regions_none; + } +} diff --git a/deps/rcheevos/src/rcheevos/expression.c b/deps/rcheevos/src/rcheevos/expression.c deleted file mode 100644 index 0adbc03a4f..0000000000 --- a/deps/rcheevos/src/rcheevos/expression.c +++ /dev/null @@ -1,41 +0,0 @@ -#include "internal.h" - -rc_expression_t* rc_parse_expression(const char** memaddr, rc_parse_state_t* parse) { - rc_expression_t* self; - rc_term_t** next; - - self = RC_ALLOC(rc_expression_t, parse); - next = &self->terms; - - for (;;) { - *next = rc_parse_term(memaddr, 0, parse); - - if (parse->offset < 0) { - return 0; - } - - next = &(*next)->next; - - if (**memaddr != '_') { - break; - } - - (*memaddr)++; - } - - *next = 0; - return self; -} - -int rc_evaluate_expression(rc_expression_t* self, rc_eval_state_t* eval_state) { - rc_term_t* term; - int value; - - value = 0; - - for (term = self->terms; term != 0; term = term->next) { - value += rc_evaluate_term(term, eval_state); - } - - return value; -} diff --git a/deps/rcheevos/src/rcheevos/format.c b/deps/rcheevos/src/rcheevos/format.c index 33dddda41d..f7556796cf 100644 --- a/deps/rcheevos/src/rcheevos/format.c +++ b/deps/rcheevos/src/rcheevos/format.c @@ -1,5 +1,7 @@ #include "internal.h" +#include "compat.h" + #include #include @@ -11,7 +13,7 @@ int rc_parse_format(const char* format_str) { } break; - + case 'T': if (!strcmp(format_str, "IME")) { return RC_FORMAT_FRAMES; @@ -21,7 +23,7 @@ int rc_parse_format(const char* format_str) { } break; - + case 'S': if (!strcmp(format_str, "ECS")) { return RC_FORMAT_SECONDS; @@ -34,7 +36,7 @@ int rc_parse_format(const char* format_str) { } break; - + case 'M': if (!strcmp(format_str, "ILLISECS")) { return RC_FORMAT_CENTISECS; diff --git a/deps/rcheevos/src/rcheevos/internal.h b/deps/rcheevos/src/rcheevos/internal.h index 72733183c3..612df0ac0d 100644 --- a/deps/rcheevos/src/rcheevos/internal.h +++ b/deps/rcheevos/src/rcheevos/internal.h @@ -6,7 +6,6 @@ #define RC_ALLOW_ALIGN(T) struct __align_ ## T { char ch; T t; }; RC_ALLOW_ALIGN(rc_condition_t) RC_ALLOW_ALIGN(rc_condset_t) -RC_ALLOW_ALIGN(rc_expression_t) RC_ALLOW_ALIGN(rc_lboard_t) RC_ALLOW_ALIGN(rc_memref_value_t) RC_ALLOW_ALIGN(rc_operand_t) @@ -15,7 +14,6 @@ RC_ALLOW_ALIGN(rc_richpresence_display_t) RC_ALLOW_ALIGN(rc_richpresence_display_part_t) RC_ALLOW_ALIGN(rc_richpresence_lookup_t) RC_ALLOW_ALIGN(rc_richpresence_lookup_item_t) -RC_ALLOW_ALIGN(rc_term_t) RC_ALLOW_ALIGN(rc_trigger_t) RC_ALLOW_ALIGN(rc_value_t) RC_ALLOW_ALIGN(char) @@ -36,8 +34,6 @@ typedef struct { rc_condition_t condition; rc_condset_t condset; rc_trigger_t trigger; - rc_term_t term; - rc_expression_t expression; rc_lboard_t lboard; rc_memref_value_t memref_value; rc_richpresence_t richpresence; @@ -61,6 +57,8 @@ typedef struct { unsigned measured_value; /* Measured */ char was_reset; /* ResetIf triggered */ char has_hits; /* one of more hit counts is non-zero */ + char primed; /* true if all non-Trigger conditions are true */ + char measured_from_hits; /* true if the measured_value came from a condition's hit count */ } rc_eval_state_t; @@ -86,7 +84,7 @@ void rc_destroy_parse_state(rc_parse_state_t* parse); void* rc_alloc(void* pointer, int* offset, int size, int alignment, rc_scratch_t* scratch); char* rc_alloc_str(rc_parse_state_t* parse, const char* text, int length); -rc_memref_value_t* rc_alloc_memref_value(rc_parse_state_t* parse, unsigned address, char size, char is_bcd, char is_indirect); +rc_memref_value_t* rc_alloc_memref_value(rc_parse_state_t* parse, unsigned address, char size, char is_indirect); void rc_update_memref_values(rc_memref_value_t* memref, rc_peek_t peek, void* ud); void rc_update_memref_value(rc_memref_value_t* memref, rc_peek_t peek, void* ud); rc_memref_value_t* rc_get_indirect_memref(rc_memref_value_t* memref, rc_eval_state_t* eval_state); @@ -99,16 +97,11 @@ void rc_reset_condset(rc_condset_t* self); rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse, int is_indirect); 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); unsigned rc_evaluate_operand(rc_operand_t* self, rc_eval_state_t* eval_state); -rc_term_t* rc_parse_term(const char** memaddr, int is_indirect, rc_parse_state_t* parse); -int rc_evaluate_term(rc_term_t* self, rc_eval_state_t* eval_state); - -rc_expression_t* rc_parse_expression(const char** memaddr, rc_parse_state_t* parse); -int rc_evaluate_expression(rc_expression_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); void rc_parse_lboard_internal(rc_lboard_t* self, const char* memaddr, rc_parse_state_t* parse); diff --git a/deps/rcheevos/src/rcheevos/lboard.c b/deps/rcheevos/src/rcheevos/lboard.c index 11628fff71..5eb107d52f 100644 --- a/deps/rcheevos/src/rcheevos/lboard.c +++ b/deps/rcheevos/src/rcheevos/lboard.c @@ -133,7 +133,7 @@ void rc_parse_lboard_internal(rc_lboard_t* self, const char* memaddr, rc_parse_s return; } - self->started = self->submitted = 0; + self->state = RC_LBOARD_STATE_WAITING; } int rc_lboard_size(const char* memaddr) { @@ -164,82 +164,89 @@ rc_lboard_t* rc_parse_lboard(void* buffer, const char* memaddr, lua_State* L, in int rc_evaluate_lboard(rc_lboard_t* self, int* value, rc_peek_t peek, void* peek_ud, lua_State* L) { int start_ok, cancel_ok, submit_ok; - int action = -1; rc_update_memref_values(self->memrefs, peek, peek_ud); - /* ASSERT: these are always tested once every frame, to ensure delta variables work properly */ + if (self->state == RC_LBOARD_STATE_INACTIVE) + return RC_LBOARD_STATE_INACTIVE; + + /* these are always tested once every frame, to ensure hit counts work properly */ start_ok = rc_test_trigger(&self->start, peek, peek_ud, L); cancel_ok = rc_test_trigger(&self->cancel, peek, peek_ud, L); submit_ok = rc_test_trigger(&self->submit, peek, peek_ud, L); - if (self->submitted) { - /* if we've already submitted or canceled the leaderboard, don't reactivate it until it becomes inactive. */ - if (!start_ok) { - self->submitted = 0; - } - } - else if (!self->started) { - /* leaderboard is not active, if the start condition is true, activate it */ - if (start_ok && !cancel_ok) { - if (submit_ok) { - /* start and submit both true in the same frame, just submit without announcing the leaderboard is available */ - action = RC_LBOARD_TRIGGERED; - /* prevent multiple submissions/notifications */ - self->submitted = 1; + switch (self->state) + { + case RC_LBOARD_STATE_WAITING: + case RC_LBOARD_STATE_TRIGGERED: + case RC_LBOARD_STATE_CANCELED: + /* don't activate/reactivate until the start condition becomes false */ + if (start_ok) { + *value = 0; + return RC_LBOARD_STATE_INACTIVE; /* just return inactive for all of these */ } - else if (self->start.requirement != 0 || self->start.alternative != 0) { - self->started = 1; - action = RC_LBOARD_STARTED; - } - } - } - else { - /* leaderboard is active */ - if (cancel_ok) { - /* cancel condition is true, deactivate the leaderboard */ - self->started = 0; - action = RC_LBOARD_CANCELED; - /* prevent multiple cancel notifications */ - self->submitted = 1; - } - else if (submit_ok) { - /* submit condition is true, submit the current value */ - self->started = 0; - action = RC_LBOARD_TRIGGERED; - self->submitted = 1; - } - } - if (action == -1) { - action = self->started ? RC_LBOARD_ACTIVE : RC_LBOARD_INACTIVE; + /* start condition is false, allow the leaderboard to start on future frames */ + self->state = RC_LBOARD_STATE_ACTIVE; + break; + + case RC_LBOARD_STATE_ACTIVE: + /* leaderboard attempt is not in progress. if the start condition is true and the cancel condition is not, start the attempt */ + if (start_ok && !cancel_ok) { + if (submit_ok) { + /* start and submit are both true in the same frame, just submit without announcing the leaderboard is available */ + self->state = RC_LBOARD_STATE_TRIGGERED; + } + else if (self->start.requirement == 0 && self->start.alternative == 0) { + /* start condition is empty - this leaderboard is submit-only with no measured progress */ + } + else { + /* start the leaderboard attempt */ + self->state = RC_LBOARD_STATE_STARTED; + + /* reset any hit counts in the value */ + if (self->value.conditions) + rc_reset_condset(self->value.conditions); + } + } + break; + + case RC_LBOARD_STATE_STARTED: + /* leaderboard attempt in progress */ + if (cancel_ok) { + /* cancel condition is true, abort the attempt */ + self->state = RC_LBOARD_STATE_CANCELED; + } + else if (submit_ok) { + /* submit condition is true, submit the current value */ + self->state = RC_LBOARD_STATE_TRIGGERED; + } + break; } /* Calculate the value */ - switch (action) { - case RC_LBOARD_STARTED: - if (self->value.conditions) - rc_reset_condset(self->value.conditions); - /* fall through */ - case RC_LBOARD_ACTIVE: - *value = rc_evaluate_value(self->progress != 0 ? self->progress : &self->value, peek, peek_ud, L); - break; + switch (self->state) { + case RC_LBOARD_STATE_STARTED: + if (self->progress) { + *value = rc_evaluate_value(self->progress, peek, peek_ud, L); + break; + } + /* fallthrough to RC_LBOARD_STATE_TRIGGERED */ - case RC_LBOARD_TRIGGERED: + case RC_LBOARD_STATE_TRIGGERED: *value = rc_evaluate_value(&self->value, peek, peek_ud, L); break; - case RC_LBOARD_INACTIVE: - case RC_LBOARD_CANCELED: + default: *value = 0; break; } - return action; + return self->state; } void rc_reset_lboard(rc_lboard_t* self) { - self->started = self->submitted = 0; + self->state = RC_LBOARD_STATE_WAITING; rc_reset_trigger(&self->start); rc_reset_trigger(&self->submit); diff --git a/deps/rcheevos/src/rcheevos/memref.c b/deps/rcheevos/src/rcheevos/memref.c index a7c24c7446..7ce1b3906a 100644 --- a/deps/rcheevos/src/rcheevos/memref.c +++ b/deps/rcheevos/src/rcheevos/memref.c @@ -5,7 +5,7 @@ #define MEMREF_PLACEHOLDER_ADDRESS 0xFFFFFFFF -static rc_memref_value_t* rc_alloc_memref_value_sizing_mode(rc_parse_state_t* parse, unsigned address, char size, char is_bcd, char is_indirect) { +static rc_memref_value_t* rc_alloc_memref_value_sizing_mode(rc_parse_state_t* parse, unsigned address, char size, char is_indirect) { rc_memref_t* memref; int i; @@ -20,7 +20,7 @@ static rc_memref_value_t* rc_alloc_memref_value_sizing_mode(rc_parse_state_t* pa /* have to track unique address/size/bcd combinations - use scratch.memref for sizing mode */ for (i = 0; i < parse->scratch.memref_count; ++i) { memref = &parse->scratch.memref[i]; - if (memref->address == address && memref->size == size && memref->is_bcd == is_bcd) { + if (memref->address == address && memref->size == size) { return &parse->scratch.obj.memref_value; } } @@ -57,7 +57,6 @@ static rc_memref_value_t* rc_alloc_memref_value_sizing_mode(rc_parse_state_t* pa memref = &parse->scratch.memref[parse->scratch.memref_count++]; memref->address = address; memref->size = size; - memref->is_bcd = is_bcd; memref->is_indirect = is_indirect; } @@ -65,7 +64,7 @@ static rc_memref_value_t* rc_alloc_memref_value_sizing_mode(rc_parse_state_t* pa return RC_ALLOC(rc_memref_value_t, parse); } -static rc_memref_value_t* rc_alloc_memref_value_constuct_mode(rc_parse_state_t* parse, unsigned address, char size, char is_bcd, char is_indirect) { +static rc_memref_value_t* rc_alloc_memref_value_constuct_mode(rc_parse_state_t* parse, unsigned address, char size, char is_indirect) { rc_memref_value_t** next_memref_value; rc_memref_value_t* memref_value; rc_memref_value_t* indirect_memref_value; @@ -75,8 +74,8 @@ static rc_memref_value_t* rc_alloc_memref_value_constuct_mode(rc_parse_state_t* next_memref_value = parse->first_memref; while (*next_memref_value) { memref_value = *next_memref_value; - if (!memref_value->memref.is_indirect && memref_value->memref.address == address && - memref_value->memref.size == size && memref_value->memref.is_bcd == is_bcd) { + if (!memref_value->memref.is_indirect && memref_value->memref.address == address && + memref_value->memref.size == size) { return memref_value; } @@ -96,7 +95,6 @@ static rc_memref_value_t* rc_alloc_memref_value_constuct_mode(rc_parse_state_t* memref_value = RC_ALLOC(rc_memref_value_t, parse); memref_value->memref.address = address; memref_value->memref.size = size; - memref_value->memref.is_bcd = is_bcd; memref_value->memref.is_indirect = is_indirect; memref_value->value = 0; memref_value->previous = 0; @@ -110,7 +108,6 @@ static rc_memref_value_t* rc_alloc_memref_value_constuct_mode(rc_parse_state_t* indirect_memref_value = RC_ALLOC(rc_memref_value_t, parse); indirect_memref_value->memref.address = MEMREF_PLACEHOLDER_ADDRESS; indirect_memref_value->memref.size = size; - indirect_memref_value->memref.is_bcd = is_bcd; indirect_memref_value->memref.is_indirect = 1; indirect_memref_value->value = 0; indirect_memref_value->previous = 0; @@ -123,16 +120,19 @@ static rc_memref_value_t* rc_alloc_memref_value_constuct_mode(rc_parse_state_t* return memref_value; } -rc_memref_value_t* rc_alloc_memref_value(rc_parse_state_t* parse, unsigned address, char size, char is_bcd, char is_indirect) { +rc_memref_value_t* rc_alloc_memref_value(rc_parse_state_t* parse, unsigned address, char size, char is_indirect) { if (!parse->first_memref) - return rc_alloc_memref_value_sizing_mode(parse, address, size, is_bcd, is_indirect); + return rc_alloc_memref_value_sizing_mode(parse, address, size, is_indirect); - return rc_alloc_memref_value_constuct_mode(parse, address, size, is_bcd, is_indirect); + return rc_alloc_memref_value_constuct_mode(parse, address, size, is_indirect); } static unsigned rc_memref_get_value(rc_memref_t* self, rc_peek_t peek, void* ud) { unsigned value; + if (!peek) + return 0; + switch (self->size) { case RC_MEMSIZE_BIT_0: @@ -177,56 +177,19 @@ static unsigned rc_memref_get_value(rc_memref_t* self, rc_peek_t peek, void* ud) case RC_MEMSIZE_8_BITS: value = peek(self->address, 1, ud); - - if (self->is_bcd) { - value = ((value >> 4) & 0x0f) * 10 + (value & 0x0f); - } - break; case RC_MEMSIZE_16_BITS: value = peek(self->address, 2, ud); - - if (self->is_bcd) { - value = ((value >> 12) & 0x0f) * 1000 - + ((value >> 8) & 0x0f) * 100 - + ((value >> 4) & 0x0f) * 10 - + ((value >> 0) & 0x0f) * 1; - } - break; case RC_MEMSIZE_24_BITS: /* peek 4 bytes - don't expect the caller to understand 24-bit numbers */ - value = peek(self->address, 4, ud); - - if (self->is_bcd) { - value = ((value >> 20) & 0x0f) * 100000 - + ((value >> 16) & 0x0f) * 10000 - + ((value >> 12) & 0x0f) * 1000 - + ((value >> 8) & 0x0f) * 100 - + ((value >> 4) & 0x0f) * 10 - + ((value >> 0) & 0x0f) * 1; - } else { - value &= 0x00FFFFFF; - } - + value = peek(self->address, 4, ud) & 0x00FFFFFF; break; case RC_MEMSIZE_32_BITS: value = peek(self->address, 4, ud); - - if (self->is_bcd) { - value = ((value >> 28) & 0x0f) * 10000000 - + ((value >> 24) & 0x0f) * 1000000 - + ((value >> 20) & 0x0f) * 100000 - + ((value >> 16) & 0x0f) * 10000 - + ((value >> 12) & 0x0f) * 1000 - + ((value >> 8) & 0x0f) * 100 - + ((value >> 4) & 0x0f) * 10 - + ((value >> 0) & 0x0f) * 1; - } - break; default: diff --git a/deps/rcheevos/src/rcheevos/operand.c b/deps/rcheevos/src/rcheevos/operand.c index 39f80c4f98..9bea21efe9 100644 --- a/deps/rcheevos/src/rcheevos/operand.c +++ b/deps/rcheevos/src/rcheevos/operand.c @@ -70,7 +70,6 @@ static int rc_parse_operand_memory(rc_operand_t* self, const char** memaddr, rc_ const char* aux = *memaddr; char* end; unsigned long address; - char is_bcd = 0; char size; switch (*aux++) { @@ -78,15 +77,18 @@ static int rc_parse_operand_memory(rc_operand_t* self, const char** memaddr, rc_ self->type = RC_OPERAND_DELTA; break; - case 'b': case 'B': - self->type = RC_OPERAND_ADDRESS; - is_bcd = 1; - break; - case 'p': case 'P': self->type = RC_OPERAND_PRIOR; break; + case 'b': case 'B': + self->type = RC_OPERAND_BCD; + break; + + case '~': + self->type = RC_OPERAND_INVERTED; + break; + default: self->type = RC_OPERAND_ADDRESS; aux--; @@ -104,25 +106,33 @@ static int rc_parse_operand_memory(rc_operand_t* self, const char** memaddr, rc_ aux++; switch (*aux++) { - 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; - case 'p': case 'P': size = RC_MEMSIZE_BIT_3; break; - case 'q': case 'Q': size = RC_MEMSIZE_BIT_4; break; - case 'r': case 'R': size = RC_MEMSIZE_BIT_5; break; - case 's': case 'S': size = RC_MEMSIZE_BIT_6; break; - case 't': case 'T': size = RC_MEMSIZE_BIT_7; break; - case 'l': case 'L': size = RC_MEMSIZE_LOW; break; - case 'u': case 'U': size = RC_MEMSIZE_HIGH; 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 'm': case 'M': self->size = RC_MEMSIZE_BIT_0; size = RC_MEMSIZE_8_BITS; break; + case 'n': case 'N': self->size = RC_MEMSIZE_BIT_1; size = RC_MEMSIZE_8_BITS; break; + case 'o': case 'O': self->size = RC_MEMSIZE_BIT_2; size = RC_MEMSIZE_8_BITS; break; + case 'p': case 'P': self->size = RC_MEMSIZE_BIT_3; size = RC_MEMSIZE_8_BITS; break; + case 'q': case 'Q': self->size = RC_MEMSIZE_BIT_4; size = RC_MEMSIZE_8_BITS; break; + case 'r': case 'R': self->size = RC_MEMSIZE_BIT_5; size = RC_MEMSIZE_8_BITS; break; + case 's': case 'S': self->size = RC_MEMSIZE_BIT_6; size = RC_MEMSIZE_8_BITS; break; + case 't': case 'T': self->size = RC_MEMSIZE_BIT_7; size = RC_MEMSIZE_8_BITS; break; + case 'l': case 'L': self->size = RC_MEMSIZE_LOW; size = RC_MEMSIZE_8_BITS; break; + case 'u': case 'U': self->size = RC_MEMSIZE_HIGH; size = RC_MEMSIZE_8_BITS; break; + case 'k': case 'K': self->size = RC_MEMSIZE_BITCOUNT; size = RC_MEMSIZE_8_BITS; break; + case 'h': case 'H': self->size = size = RC_MEMSIZE_8_BITS; break; + case 'w': case 'W': self->size = size = RC_MEMSIZE_24_BITS; break; + case 'x': case 'X': self->size = size = RC_MEMSIZE_32_BITS; break; - default: /* fall through */ + 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': aux--; + /* fallthrough */ case ' ': - size = RC_MEMSIZE_16_BITS; + self->size = size = RC_MEMSIZE_16_BITS; break; + + default: + return RC_INVALID_MEMORY_OPERAND; } address = strtoul(aux, &end, 16); @@ -135,7 +145,7 @@ static int rc_parse_operand_memory(rc_operand_t* self, const char** memaddr, rc_ address = 0xffffffffU; } - self->value.memref = rc_alloc_memref_value(parse, address, size, is_bcd, is_indirect); + self->value.memref = rc_alloc_memref_value(parse, address, size, is_indirect); if (parse->offset < 0) return parse->offset; @@ -143,14 +153,17 @@ static int rc_parse_operand_memory(rc_operand_t* self, const char** memaddr, rc_ return RC_OK; } -static int rc_parse_operand_trigger(rc_operand_t* self, const char** memaddr, int is_indirect, rc_parse_state_t* parse) { +int rc_parse_operand(rc_operand_t* self, const char** memaddr, int is_trigger, int is_indirect, rc_parse_state_t* parse) { const char* aux = *memaddr; char* end; int ret; unsigned long value; + int negative; + + self->size = RC_MEMSIZE_32_BITS; switch (*aux) { - case 'h': case 'H': + case 'h': case 'H': /* hex constant */ if (aux[2] == 'x' || aux[2] == 'X') { /* H0x1234 is a typo - either H1234 or 0xH1234 was probably meant */ return RC_INVALID_CONST_OPERAND; @@ -171,115 +184,9 @@ static int rc_parse_operand_trigger(rc_operand_t* self, const char** memaddr, in aux = end; break; - - case '0': - if (aux[1] == 'x' || aux[1] == 'X') { - /* fall through */ - default: - ret = rc_parse_operand_memory(self, &aux, parse, is_indirect); - if (ret < 0) { - return ret; - } - - break; - } - - /* fall through */ - case '+': case '-': - case '1': case '2': case '3': case '4': case '5': - case '6': case '7': case '8': case '9': - value = strtoul(aux, &end, 10); - - if (end == aux) { - return RC_INVALID_CONST_OPERAND; - } - - if (value > 0xffffffffU) { - value = 0xffffffffU; - } - - self->type = RC_OPERAND_CONST; - self->value.num = (unsigned)value; - - aux = end; - break; - - case '@': - ret = rc_parse_operand_lua(self, &aux, parse); - - if (ret < 0) { - return ret; - } - - break; - } - - *memaddr = aux; - return RC_OK; -} - -static int rc_parse_operand_term(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; - long svalue; - - switch (*aux) { - case 'h': case 'H': - value = strtoul(++aux, &end, 16); - - if (end == aux) { - return RC_INVALID_CONST_OPERAND; - } - - if (value > 0xffffffffU) { - value = 0xffffffffU; - } - - self->type = RC_OPERAND_CONST; - self->value.num = (unsigned)value; - - aux = end; - break; - - case 'v': case 'V': - svalue = strtol(++aux, &end, 10); - - if (end == aux) { - return RC_INVALID_CONST_OPERAND; - } - - if (svalue > 0xffffffffU) { - svalue = 0xffffffffU; - } - - self->type = RC_OPERAND_CONST; - self->value.num = (unsigned)svalue; - - aux = end; - break; - - case '0': - if (aux[1] == 'x' || aux[1] == 'X') { - /* fall through */ - default: - ret = rc_parse_operand_memory(self, &aux, parse, is_indirect); - - if (ret < 0) { - return ret; - } - - break; - } - - /* fall through */ - case '.': - case '+': case '-': - case '1': case '2': case '3': case '4': case '5': - case '6': case '7': case '8': case '9': - self->value.dbl = strtod(aux, &end); + case 'f': case 'F': /* floating point constant */ + self->value.dbl = strtod(++aux, &end); if (end == aux) { return RC_INVALID_FP_OPERAND; @@ -292,9 +199,77 @@ static int rc_parse_operand_term(rc_operand_t* self, const char** memaddr, int i else { self->type = RC_OPERAND_FP; } + aux = end; break; - + + case 'v': case 'V': /* signed integer constant */ + negative = 0; + ++aux; + + if (*aux == '-') + { + negative = 1; + ++aux; + } + else if (*aux == '+') + { + ++aux; + } + + value = strtoul(aux, &end, 10); + + if (end == aux) { + return RC_INVALID_CONST_OPERAND; + } + + if (value > 0x7fffffffU) { + value = 0x7fffffffU; + } + + self->type = RC_OPERAND_CONST; + + if (negative) + self->value.num = (unsigned)(-((long)value)); + else + self->value.num = (unsigned)value; + + aux = end; + break; + + case '0': + if (aux[1] == 'x' || aux[1] == 'X') { + /* fall through */ + default: + ret = rc_parse_operand_memory(self, &aux, parse, is_indirect); + + if (ret < 0) { + return ret; + } + + break; + } + + /* fall through for case '0' where not '0x' */ + case '+': case '-': + case '1': case '2': case '3': case '4': case '5': + case '6': case '7': case '8': case '9': + value = strtoul(aux, &end, 10); + + if (end == aux) { + return RC_INVALID_CONST_OPERAND; + } + + if (value > 0xffffffffU) { + value = 0xffffffffU; + } + + self->type = RC_OPERAND_CONST; + self->value.num = (unsigned)value; + + aux = end; + break; + case '@': ret = rc_parse_operand_lua(self, &aux, parse); @@ -309,15 +284,6 @@ static int rc_parse_operand_term(rc_operand_t* self, const char** memaddr, int i 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) { - if (is_trigger) { - return rc_parse_operand_trigger(self, memaddr, is_indirect, parse); - } - else { - return rc_parse_operand_term(self, memaddr, is_indirect, parse); - } -} - #ifndef RC_DISABLE_LUA typedef struct { @@ -339,6 +305,8 @@ static int rc_luapeek(lua_State* L) { #endif /* RC_DISABLE_LUA */ +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_evaluate_operand(rc_operand_t* self, rc_eval_state_t* eval_state) { #ifndef RC_DISABLE_LUA rc_luapeek_t luapeek; @@ -346,15 +314,15 @@ unsigned rc_evaluate_operand(rc_operand_t* self, rc_eval_state_t* eval_state) { unsigned value = 0; + /* step 1: read memory */ switch (self->type) { case RC_OPERAND_CONST: - value = self->value.num; - break; + return self->value.num; case RC_OPERAND_FP: - /* This is handled by rc_evaluate_expression. */ + /* This is handled by rc_evaluate_condition_value. */ return 0; - + case RC_OPERAND_LUA: #ifndef RC_DISABLE_LUA @@ -366,7 +334,7 @@ unsigned rc_evaluate_operand(rc_operand_t* self, rc_eval_state_t* eval_state) { luapeek.ud = eval_state->peek_userdata; lua_pushlightuserdata(eval_state->L, &luapeek); - + if (lua_pcall(eval_state->L, 2, 1, 0) == LUA_OK) { if (lua_isboolean(eval_state->L, -1)) { value = lua_toboolean(eval_state->L, -1); @@ -384,6 +352,8 @@ unsigned rc_evaluate_operand(rc_operand_t* self, rc_eval_state_t* eval_state) { break; case RC_OPERAND_ADDRESS: + case RC_OPERAND_BCD: + case RC_OPERAND_INVERTED: value = rc_get_indirect_memref(self->value.memref, eval_state)->value; break; @@ -396,5 +366,131 @@ unsigned rc_evaluate_operand(rc_operand_t* self, rc_eval_state_t* eval_state) { break; } + /* step 2: mask off appropriate bits */ + switch (self->size) + { + case RC_MEMSIZE_BIT_0: + value = (value >> 0) & 1; + break; + + case RC_MEMSIZE_BIT_1: + value = (value >> 1) & 1; + break; + + case RC_MEMSIZE_BIT_2: + value = (value >> 2) & 1; + break; + + case RC_MEMSIZE_BIT_3: + value = (value >> 3) & 1; + break; + + case RC_MEMSIZE_BIT_4: + value = (value >> 4) & 1; + break; + + case RC_MEMSIZE_BIT_5: + value = (value >> 5) & 1; + break; + + case RC_MEMSIZE_BIT_6: + value = (value >> 6) & 1; + break; + + case RC_MEMSIZE_BIT_7: + value = (value >> 7) & 1; + break; + + case RC_MEMSIZE_LOW: + value = value & 0x0f; + break; + + case RC_MEMSIZE_HIGH: + value = (value >> 4) & 0x0f; + break; + + case RC_MEMSIZE_BITCOUNT: + value = rc_bits_set[(value & 0x0F)] + + rc_bits_set[((value >> 4) & 0x0F)]; + break; + } + + /* step 3: apply logic */ + switch (self->type) + { + case RC_OPERAND_BCD: + switch (self->size) + { + case RC_MEMSIZE_8_BITS: + value = ((value >> 4) & 0x0f) * 10 + + ((value ) & 0x0f); + break; + + case RC_MEMSIZE_16_BITS: + value = ((value >> 12) & 0x0f) * 1000 + + ((value >> 8) & 0x0f) * 100 + + ((value >> 4) & 0x0f) * 10 + + ((value ) & 0x0f); + break; + + case RC_MEMSIZE_24_BITS: + value = ((value >> 20) & 0x0f) * 100000 + + ((value >> 16) & 0x0f) * 10000 + + ((value >> 12) & 0x0f) * 1000 + + ((value >> 8) & 0x0f) * 100 + + ((value >> 4) & 0x0f) * 10 + + ((value ) & 0x0f); + break; + + case RC_MEMSIZE_32_BITS: + value = ((value >> 28) & 0x0f) * 10000000 + + ((value >> 24) & 0x0f) * 1000000 + + ((value >> 20) & 0x0f) * 100000 + + ((value >> 16) & 0x0f) * 10000 + + ((value >> 12) & 0x0f) * 1000 + + ((value >> 8) & 0x0f) * 100 + + ((value >> 4) & 0x0f) * 10 + + ((value ) & 0x0f); + break; + + default: + break; + } + break; + + case RC_OPERAND_INVERTED: + switch (self->size) + { + case RC_MEMSIZE_LOW: + case RC_MEMSIZE_HIGH: + value ^= 0x0f; + break; + + case RC_MEMSIZE_8_BITS: + value ^= 0xff; + break; + + case RC_MEMSIZE_16_BITS: + value ^= 0xffff; + break; + + case RC_MEMSIZE_24_BITS: + value ^= 0xffffff; + break; + + case RC_MEMSIZE_32_BITS: + value ^= 0xffffffff; + break; + + default: + value ^= 0x01; + break; + } + break; + + default: + break; + } + return value; } diff --git a/deps/rcheevos/src/rcheevos/richpresence.c b/deps/rcheevos/src/rcheevos/richpresence.c index 1f9d7c3470..15fba8fec8 100644 --- a/deps/rcheevos/src/rcheevos/richpresence.c +++ b/deps/rcheevos/src/rcheevos/richpresence.c @@ -1,9 +1,8 @@ #include "internal.h" +#include "compat.h" + #include -#include -#include -#include /* special formats only used by rc_richpresence_display_part_t.display_type. must not overlap other RC_FORMAT values */ enum { @@ -116,6 +115,7 @@ static rc_richpresence_display_t* rc_parse_richpresence_display_internal(const c /* just calculating size, can't confirm lookup exists */ part = RC_ALLOC(rc_richpresence_display_part_t, parse); + in = line; line = ++ptr; while (ptr < endline && *ptr != ')') ++ptr; @@ -124,6 +124,10 @@ static rc_richpresence_display_t* rc_parse_richpresence_display_internal(const c if (parse->offset < 0) return 0; ++ptr; + } else { + /* no closing parenthesis - allocate space for the invalid string */ + --in; /* already skipped over @ */ + rc_alloc_str(parse, line, (int)(ptr - in)); } } else { @@ -139,6 +143,7 @@ static rc_richpresence_display_t* rc_parse_richpresence_display_internal(const c part->first_lookup_item = lookup->first_item; part->display_type = lookup->format; + in = line; line = ++ptr; while (ptr < endline && *ptr != ')') ++ptr; @@ -149,6 +154,12 @@ static rc_richpresence_display_t* rc_parse_richpresence_display_internal(const c return 0; ++ptr; } + else { + /* non-terminated macro, dump the macro and the remaining portion of the line */ + --in; /* already skipped over @ */ + part->display_type = RC_FORMAT_STRING; + part->text = rc_alloc_str(parse, in, (int)(ptr - in)); + } break; } @@ -170,7 +181,7 @@ static rc_richpresence_display_t* rc_parse_richpresence_display_internal(const c /* assert: the allocated string is going to be smaller than the memory used for the parameter of the macro */ part->display_type = RC_FORMAT_UNKNOWN_MACRO; - part->text = rc_alloc_str(parse, line, ptr - line); + part->text = rc_alloc_str(parse, line, (int)(ptr - line)); } } } @@ -194,7 +205,7 @@ static const char* rc_parse_richpresence_lookup(rc_richpresence_lookup_t* lookup const char* defaultlabel = 0; char* endptr = 0; unsigned key; - int chars; + unsigned chars; next = &lookup->first_item; @@ -372,7 +383,7 @@ int rc_richpresence_size(const char* script) { rc_richpresence_t* self; rc_parse_state_t parse; rc_init_parse_state(&parse, 0, 0, 0); - + self = RC_ALLOC(rc_richpresence_t, &parse); rc_parse_richpresence_internal(self, script, &parse); @@ -389,7 +400,7 @@ rc_richpresence_t* rc_parse_richpresence(void* buffer, const char* script, lua_S rc_init_parse_state_memrefs(&parse, &self->memrefs); rc_parse_richpresence_internal(self, script, &parse); - + rc_destroy_parse_state(&parse); return parse.offset >= 0 ? self : 0; } @@ -398,8 +409,10 @@ int rc_evaluate_richpresence(rc_richpresence_t* richpresence, char* buffer, unsi rc_richpresence_display_t* display; rc_richpresence_display_part_t* part; rc_richpresence_lookup_item_t* item; + char tmp[256]; char* ptr; - int chars; + const char* text; + size_t chars; unsigned value; rc_update_memref_values(richpresence->memrefs, peek, peek_ud); @@ -412,37 +425,52 @@ int rc_evaluate_richpresence(rc_richpresence_t* richpresence, char* buffer, unsi while (part) { switch (part->display_type) { case RC_FORMAT_STRING: - chars = snprintf(ptr, buffersize, "%s", part->text); + text = part->text; + chars = strlen(text); break; case RC_FORMAT_LOOKUP: value = rc_evaluate_value(&part->value, peek, peek_ud, L); item = part->first_lookup_item; if (!item) { + text = ""; chars = 0; } else { while (item->next_item && item->value != value) item = item->next_item; - chars = snprintf(ptr, buffersize, "%s", item->label); + text = item->label; + chars = strlen(text); } break; case RC_FORMAT_UNKNOWN_MACRO: - chars = snprintf(ptr, buffersize, "[Unknown macro]%s", part->text); + chars = snprintf(tmp, sizeof(tmp), "[Unknown macro]%s", part->text); + text = tmp; break; default: value = rc_evaluate_value(&part->value, peek, peek_ud, L); - chars = rc_format_value(ptr, buffersize, value, part->display_type); + chars = rc_format_value(tmp, sizeof(tmp), value, part->display_type); + text = tmp; break; } - if (chars > 0) { - ptr += chars; - buffersize -= chars; + if (chars > 0 && buffersize > 0) { + if ((unsigned)chars >= buffersize) { + /* prevent write past end of buffer */ + memcpy(ptr, text, buffersize - 1); + ptr[buffersize - 1] = '\0'; + buffersize = 0; + } + else { + memcpy(ptr, text, chars); + ptr[chars] = '\0'; + buffersize -= (unsigned)chars; + } } + ptr += chars; part = part->next; } diff --git a/deps/rcheevos/src/rcheevos/runtime.c b/deps/rcheevos/src/rcheevos/runtime.c new file mode 100644 index 0000000000..b9e9df4e76 --- /dev/null +++ b/deps/rcheevos/src/rcheevos/runtime.c @@ -0,0 +1,589 @@ +#include "internal.h" + +#include "../rhash/md5.h" + +#include +#include + +#define RC_RICHPRESENCE_DISPLAY_BUFFER_SIZE 256 + +void rc_runtime_init(rc_runtime_t* self) { + memset(self, 0, sizeof(rc_runtime_t)); + self->next_memref = &self->memrefs; +} + +void rc_runtime_destroy(rc_runtime_t* self) { + unsigned i; + + if (self->triggers) { + for (i = 0; i < self->trigger_count; ++i) + free(self->triggers[i].buffer); + + free(self->triggers); + self->triggers = NULL; + + self->trigger_count = self->trigger_capacity = 0; + } + + if (self->lboards) { + free(self->lboards); + self->lboards = NULL; + + self->lboard_count = self->lboard_capacity = 0; + } + + while (self->richpresence) { + rc_runtime_richpresence_t* previous = self->richpresence->previous; + + free(self->richpresence->buffer); + free(self->richpresence); + self->richpresence = previous; + } + + if (self->richpresence_display_buffer) { + free(self->richpresence_display_buffer); + self->richpresence_display_buffer = NULL; + } + + self->next_memref = 0; + self->memrefs = 0; +} + +static void rc_runtime_checksum(const char* memaddr, unsigned char* md5) { + md5_state_t state; + md5_init(&state); + md5_append(&state, (unsigned char*)memaddr, (int)strlen(memaddr)); + md5_finish(&state, md5); +} + +static void rc_runtime_deactivate_trigger_by_index(rc_runtime_t* self, unsigned index) { + if (self->triggers[index].owns_memrefs) { + /* if the trigger has one or more memrefs in its buffer, we can't free the buffer. + * just null out the trigger so the runtime processor will skip it + */ + rc_reset_trigger(self->triggers[index].trigger); + self->triggers[index].trigger = NULL; + } + else { + /* trigger doesn't own any memrefs, go ahead and free it, then replace it with the last trigger */ + free(self->triggers[index].buffer); + + if (--self->trigger_count > index) + memcpy(&self->triggers[index], &self->triggers[self->trigger_count], sizeof(rc_runtime_trigger_t)); + } +} + +void rc_runtime_deactivate_achievement(rc_runtime_t* self, unsigned id) { + unsigned i; + + for (i = 0; i < self->trigger_count; ++i) { + if (self->triggers[i].id == id && self->triggers[i].trigger != NULL) + rc_runtime_deactivate_trigger_by_index(self, i); + } +} + +int rc_runtime_activate_achievement(rc_runtime_t* self, unsigned id, const char* memaddr, lua_State* L, int funcs_idx) { + void* trigger_buffer; + rc_trigger_t* trigger; + rc_parse_state_t parse; + unsigned char md5[16]; + int size; + char owns_memref; + unsigned i; + + if (memaddr == NULL) + return RC_INVALID_MEMORY_OPERAND; + + rc_runtime_checksum(memaddr, md5); + + /* check to see if the id is already registered with an active trigger */ + for (i = 0; i < self->trigger_count; ++i) { + if (self->triggers[i].id == id && self->triggers[i].trigger != NULL) { + if (memcmp(self->triggers[i].md5, md5, 16) == 0) { + /* if the checksum hasn't changed, we can reuse the existing item */ + rc_reset_trigger(self->triggers[i].trigger); + return RC_OK; + } + + /* checksum has changed, deactivate the the item */ + rc_runtime_deactivate_trigger_by_index(self, i); + + /* deactivate may reorder the list so we should continue from the current index. however, we + * assume that only one trigger is active per id, so having found that, just stop scanning. + */ + break; + } + } + + /* check to see if a disabled trigger for the specific id matches the trigger being registered */ + for (i = 0; i < self->trigger_count; ++i) { + if (self->triggers[i].id == id && memcmp(self->triggers[i].md5, md5, 16) == 0) { + /* retrieve the trigger pointer from the buffer */ + size = 0; + trigger = (rc_trigger_t*)rc_alloc(self->triggers[i].buffer, &size, sizeof(rc_trigger_t), RC_ALIGNOF(rc_trigger_t), 0); + self->triggers[i].trigger = trigger; + + rc_reset_trigger(trigger); + return RC_OK; + } + } + + /* item has not been previously registered, determine how much space we need for it, and allocate it */ + size = rc_trigger_size(memaddr); + if (size < 0) + return size; + + trigger_buffer = malloc(size); + if (!trigger_buffer) + return RC_OUT_OF_MEMORY; + + /* populate the item, using the communal memrefs pool */ + rc_init_parse_state(&parse, trigger_buffer, L, funcs_idx); + parse.first_memref = &self->memrefs; + trigger = RC_ALLOC(rc_trigger_t, &parse); + rc_parse_trigger_internal(trigger, &memaddr, &parse); + rc_destroy_parse_state(&parse); + + if (parse.offset < 0) { + free(trigger_buffer); + *self->next_memref = NULL; /* disassociate any memrefs allocated by the failed parse */ + return parse.offset; + } + + /* if at least one memref was allocated within the trigger, we can't free the buffer when the trigger is deactivated */ + owns_memref = (*self->next_memref != NULL); + if (owns_memref) { + /* advance through the new memrefs so we're ready for the next allocation */ + do { + self->next_memref = &(*self->next_memref)->next; + } while (*self->next_memref != NULL); + } + + /* grow the trigger buffer if necessary */ + if (self->trigger_count == self->trigger_capacity) { + self->trigger_capacity += 32; + if (!self->triggers) + self->triggers = (rc_runtime_trigger_t*)malloc(self->trigger_capacity * sizeof(rc_runtime_trigger_t)); + else + self->triggers = (rc_runtime_trigger_t*)realloc(self->triggers, self->trigger_capacity * sizeof(rc_runtime_trigger_t)); + } + + /* assign the new trigger */ + self->triggers[self->trigger_count].id = id; + self->triggers[self->trigger_count].trigger = trigger; + self->triggers[self->trigger_count].buffer = trigger_buffer; + memcpy(self->triggers[self->trigger_count].md5, md5, 16); + self->triggers[self->trigger_count].owns_memrefs = owns_memref; + ++self->trigger_count; + + /* reset it, and return it */ + trigger->memrefs = NULL; + rc_reset_trigger(trigger); + return RC_OK; +} + +rc_trigger_t* rc_runtime_get_achievement(const rc_runtime_t* self, unsigned id) +{ + unsigned i; + + for (i = 0; i < self->trigger_count; ++i) { + if (self->triggers[i].id == id && self->triggers[i].trigger != NULL) + return self->triggers[i].trigger; + } + + return NULL; +} + +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. + * just null out the lboard so the runtime processor will skip it + */ + rc_reset_lboard(self->lboards[index].lboard); + self->lboards[index].lboard = NULL; + } + else { + /* lboard doesn't own any memrefs, go ahead and free it, then replace it with the last lboard */ + free(self->lboards[index].buffer); + + if (--self->lboard_count > index) + memcpy(&self->lboards[index], &self->lboards[self->lboard_count], sizeof(rc_runtime_lboard_t)); + } +} + +void rc_runtime_deactivate_lboard(rc_runtime_t* self, unsigned id) { + unsigned i; + + for (i = 0; i < self->lboard_count; ++i) { + if (self->lboards[i].id == id && self->lboards[i].lboard != NULL) + rc_runtime_deactivate_lboard_by_index(self, i); + } +} + +int rc_runtime_activate_lboard(rc_runtime_t* self, unsigned id, const char* memaddr, lua_State* L, int funcs_idx) { + void* lboard_buffer; + unsigned char md5[16]; + rc_lboard_t* lboard; + rc_parse_state_t parse; + int size; + char owns_memref; + unsigned i; + + if (memaddr == 0) + return RC_INVALID_MEMORY_OPERAND; + + rc_runtime_checksum(memaddr, md5); + + /* check to see if the id is already registered with an active lboard */ + for (i = 0; i < self->lboard_count; ++i) { + if (self->lboards[i].id == id && self->lboards[i].lboard != NULL) { + if (memcmp(self->lboards[i].md5, md5, 16) == 0) { + /* if the checksum hasn't changed, we can reuse the existing item */ + rc_reset_lboard(self->lboards[i].lboard); + return RC_OK; + } + + /* checksum has changed, deactivate the the item */ + rc_runtime_deactivate_lboard_by_index(self, i); + + /* deactivate may reorder the list so we should continue from the current index. however, we + * assume that only one trigger is active per id, so having found that, just stop scanning. + */ + break; + } + } + + /* check to see if a disabled lboard for the specific id matches the lboard being registered */ + for (i = 0; i < self->lboard_count; ++i) { + if (self->lboards[i].id == id && memcmp(self->lboards[i].md5, md5, 16) == 0) { + /* retrieve the lboard pointer from the buffer */ + size = 0; + lboard = (rc_lboard_t*)rc_alloc(self->lboards[i].buffer, &size, sizeof(rc_lboard_t), RC_ALIGNOF(rc_lboard_t), 0); + self->lboards[i].lboard = lboard; + + rc_reset_lboard(lboard); + return RC_OK; + } + } + + /* item has not been previously registered, determine how much space we need for it, and allocate it */ + size = rc_lboard_size(memaddr); + if (size < 0) + return size; + + lboard_buffer = malloc(size); + if (!lboard_buffer) + return RC_OUT_OF_MEMORY; + + /* populate the item, using the communal memrefs pool */ + rc_init_parse_state(&parse, lboard_buffer, L, funcs_idx); + lboard = RC_ALLOC(rc_lboard_t, &parse); + parse.first_memref = &self->memrefs; + rc_parse_lboard_internal(lboard, memaddr, &parse); + rc_destroy_parse_state(&parse); + + if (parse.offset < 0) { + free(lboard_buffer); + *self->next_memref = NULL; /* disassociate any memrefs allocated by the failed parse */ + return parse.offset; + } + + /* if at least one memref was allocated within the trigger, we can't free the buffer when the trigger is deactivated */ + owns_memref = (*self->next_memref != NULL); + if (owns_memref) { + /* advance through the new memrefs so we're ready for the next allocation */ + do { + self->next_memref = &(*self->next_memref)->next; + } while (*self->next_memref != NULL); + } + + /* grow the lboard buffer if necessary */ + if (self->lboard_count == self->lboard_capacity) { + self->lboard_capacity += 16; + if (!self->lboards) + self->lboards = (rc_runtime_lboard_t*)malloc(self->lboard_capacity * sizeof(rc_runtime_lboard_t)); + else + self->lboards = (rc_runtime_lboard_t*)realloc(self->lboards, self->lboard_capacity * sizeof(rc_runtime_lboard_t)); + } + + /* assign the new lboard */ + self->lboards[self->lboard_count].id = id; + self->lboards[self->lboard_count].value = 0; + self->lboards[self->lboard_count].lboard = lboard; + self->lboards[self->lboard_count].buffer = lboard_buffer; + memcpy(self->lboards[self->lboard_count].md5, md5, 16); + self->lboards[self->lboard_count].owns_memrefs = owns_memref; + ++self->lboard_count; + + /* reset it, and return it */ + lboard->memrefs = NULL; + rc_reset_lboard(lboard); + return RC_OK; +} + +rc_lboard_t* rc_runtime_get_lboard(const rc_runtime_t* self, unsigned id) +{ + unsigned i; + + for (i = 0; i < self->lboard_count; ++i) { + if (self->lboards[i].id == id && self->lboards[i].lboard != NULL) + return self->lboards[i].lboard; + } + + return NULL; +} + +int rc_runtime_activate_richpresence(rc_runtime_t* self, const char* script, lua_State* L, int funcs_idx) { + rc_richpresence_t* richpresence; + rc_runtime_richpresence_t* previous; + rc_richpresence_display_t* display; + rc_parse_state_t parse; + int size; + + if (script == NULL) + return RC_MISSING_DISPLAY_STRING; + + size = rc_richpresence_size(script); + if (size < 0) + return size; + + if (!self->richpresence_display_buffer) { + self->richpresence_display_buffer = (char*)malloc(RC_RICHPRESENCE_DISPLAY_BUFFER_SIZE * sizeof(char)); + if (!self->richpresence_display_buffer) + return RC_OUT_OF_MEMORY; + } + self->richpresence_display_buffer[0] = '\0'; + + previous = self->richpresence; + if (previous) { + if (!previous->owns_memrefs) { + free(previous->buffer); + previous = previous->previous; + } + } + + self->richpresence = (rc_runtime_richpresence_t*)malloc(sizeof(rc_runtime_richpresence_t)); + if (!self->richpresence) + return RC_OUT_OF_MEMORY; + + self->richpresence->previous = previous; + self->richpresence->owns_memrefs = 0; + self->richpresence->buffer = malloc(size); + + if (!self->richpresence->buffer) + return RC_OUT_OF_MEMORY; + + rc_init_parse_state(&parse, self->richpresence->buffer, L, funcs_idx); + self->richpresence->richpresence = richpresence = RC_ALLOC(rc_richpresence_t, &parse); + parse.first_memref = &self->memrefs; + rc_parse_richpresence_internal(richpresence, script, &parse); + rc_destroy_parse_state(&parse); + + if (parse.offset < 0) { + free(self->richpresence->buffer); + free(self->richpresence); + self->richpresence = previous; + *self->next_memref = NULL; /* disassociate any memrefs allocated by the failed parse */ + return parse.offset; + } + + /* if at least one memref was allocated within the rich presence, we can't free the buffer when the rich presence is deactivated */ + self->richpresence->owns_memrefs = (*self->next_memref != NULL); + if (self->richpresence->owns_memrefs) { + /* advance through the new memrefs so we're ready for the next allocation */ + do { + self->next_memref = &(*self->next_memref)->next; + } while (*self->next_memref != NULL); + } + + richpresence->memrefs = NULL; + self->richpresence_update_timer = 0; + + if (!richpresence->first_display || !richpresence->first_display->display) { + /* non-existant rich presence, treat like static empty string */ + *self->richpresence_display_buffer = '\0'; + self->richpresence->richpresence = NULL; + } + else if (richpresence->first_display->next || /* has conditional display strings */ + richpresence->first_display->display->next || /* has macros */ + richpresence->first_display->display->value.conditions) { /* is only a macro */ + /* dynamic rich presence - reset all of the conditions */ + display = richpresence->first_display; + while (display != NULL) { + rc_reset_trigger(&display->trigger); + display = display->next; + } + + rc_evaluate_richpresence(self->richpresence->richpresence, self->richpresence_display_buffer, RC_RICHPRESENCE_DISPLAY_BUFFER_SIZE - 1, NULL, NULL, L); + } + else { + /* static rich presence - copy the static string */ + const char* src = richpresence->first_display->display->text; + char* dst = self->richpresence_display_buffer; + const char* end = dst + RC_RICHPRESENCE_DISPLAY_BUFFER_SIZE - 1; + while (*src && dst < end) + *dst++ = *src++; + *dst = '\0'; + + /* by setting self->richpresence to null, it won't be evaluated in do_frame() */ + self->richpresence = NULL; + } + + return RC_OK; +} + +const char* rc_runtime_get_richpresence(const rc_runtime_t* self) +{ + if (self->richpresence_display_buffer) + return self->richpresence_display_buffer; + + return ""; +} + +void rc_runtime_do_frame(rc_runtime_t* self, rc_runtime_event_handler_t event_handler, rc_peek_t peek, void* ud, lua_State* L) { + rc_runtime_event_t runtime_event; + unsigned i; + + runtime_event.value = 0; + + rc_update_memref_values(self->memrefs, peek, ud); + + for (i = 0; i < self->trigger_count; ++i) { + rc_trigger_t* trigger = self->triggers[i].trigger; + int trigger_state; + + if (!trigger) + continue; + + trigger_state = trigger->state; + + switch (rc_evaluate_trigger(trigger, peek, ud, L)) + { + case RC_TRIGGER_STATE_RESET: + runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_RESET; + runtime_event.id = self->triggers[i].id; + event_handler(&runtime_event); + break; + + case RC_TRIGGER_STATE_TRIGGERED: + runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_TRIGGERED; + runtime_event.id = self->triggers[i].id; + event_handler(&runtime_event); + break; + + case RC_TRIGGER_STATE_PAUSED: + if (trigger_state != RC_TRIGGER_STATE_PAUSED) { + runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_PAUSED; + runtime_event.id = self->triggers[i].id; + event_handler(&runtime_event); + } + break; + + case RC_TRIGGER_STATE_PRIMED: + if (trigger_state != RC_TRIGGER_STATE_PRIMED) { + runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_PRIMED; + runtime_event.id = self->triggers[i].id; + event_handler(&runtime_event); + } + break; + + case RC_TRIGGER_STATE_ACTIVE: + if (trigger_state != RC_TRIGGER_STATE_ACTIVE) { + runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_ACTIVATED; + runtime_event.id = self->triggers[i].id; + event_handler(&runtime_event); + } + break; + } + } + + for (i = 0; i < self->lboard_count; ++i) { + rc_lboard_t* lboard = self->lboards[i].lboard; + int lboard_state; + + if (!lboard) + continue; + + lboard_state = lboard->state; + switch (rc_evaluate_lboard(lboard, &runtime_event.value, peek, ud, L)) + { + case RC_LBOARD_STATE_STARTED: /* leaderboard is running */ + if (lboard_state != RC_LBOARD_STATE_STARTED) { + self->lboards[i].value = runtime_event.value; + + runtime_event.type = RC_RUNTIME_EVENT_LBOARD_STARTED; + runtime_event.id = self->lboards[i].id; + event_handler(&runtime_event); + } + else if (runtime_event.value != self->lboards[i].value) { + self->lboards[i].value = runtime_event.value; + + runtime_event.type = RC_RUNTIME_EVENT_LBOARD_UPDATED; + runtime_event.id = self->lboards[i].id; + event_handler(&runtime_event); + } + break; + + case RC_LBOARD_STATE_CANCELED: + if (lboard_state != RC_LBOARD_STATE_CANCELED) { + runtime_event.type = RC_RUNTIME_EVENT_LBOARD_CANCELED; + runtime_event.id = self->lboards[i].id; + event_handler(&runtime_event); + } + break; + + case RC_LBOARD_STATE_TRIGGERED: + if (lboard_state != RC_RUNTIME_EVENT_LBOARD_TRIGGERED) { + runtime_event.type = RC_RUNTIME_EVENT_LBOARD_TRIGGERED; + runtime_event.id = self->lboards[i].id; + event_handler(&runtime_event); + } + break; + } + } + + if (self->richpresence && self->richpresence->richpresence) { + if (self->richpresence_update_timer == 0) { + /* generate into a temporary buffer so we don't get a partially updated string if it's read while its being updated */ + char buffer[RC_RICHPRESENCE_DISPLAY_BUFFER_SIZE]; + int len = rc_evaluate_richpresence(self->richpresence->richpresence, buffer, RC_RICHPRESENCE_DISPLAY_BUFFER_SIZE - 1, peek, ud, L); + + /* copy into the real buffer - write the 0 terminator first to ensure reads don't overflow the buffer */ + if (len > 0) { + buffer[RC_RICHPRESENCE_DISPLAY_BUFFER_SIZE - 1] = '\0'; + memcpy(self->richpresence_display_buffer, buffer, RC_RICHPRESENCE_DISPLAY_BUFFER_SIZE); + } + + /* schedule the next update for 60 frames later - most systems use a 60 fps framerate (some use more 50 or 75) + * since we're only sending to the server every two minutes, that's only every 7200 frames while active, which + * is evenly divisible by 50, 60, and 75. + */ + self->richpresence_update_timer = 59; + } + else { + self->richpresence_update_timer--; + } + } +} + +void rc_runtime_reset(rc_runtime_t* self) { + unsigned i; + + for (i = 0; i < self->trigger_count; ++i) { + if (self->triggers[i].trigger) + rc_reset_trigger(self->triggers[i].trigger); + } + + for (i = 0; i < self->lboard_count; ++i) { + if (self->lboards[i].lboard) + rc_reset_lboard(self->lboards[i].lboard); + } + + if (self->richpresence) { + rc_richpresence_display_t* display = self->richpresence->richpresence->first_display; + while (display != 0) { + rc_reset_trigger(&display->trigger); + display = display->next; + } + } +} diff --git a/deps/rcheevos/src/rcheevos/runtime_progress.c b/deps/rcheevos/src/rcheevos/runtime_progress.c new file mode 100644 index 0000000000..a57b7321b3 --- /dev/null +++ b/deps/rcheevos/src/rcheevos/runtime_progress.c @@ -0,0 +1,445 @@ +#include "internal.h" + +#include "../rhash/md5.h" + +#include + +#define RC_RUNTIME_MARKER 0x0A504152 /* RAP\n */ + +#define RC_RUNTIME_CHUNK_MEMREFS 0x4645524D /* MREF */ +#define RC_RUNTIME_CHUNK_ACHIEVEMENT 0x56484341 /* ACHV */ + +#define RC_RUNTIME_CHUNK_DONE 0x454E4F44 /* DONE */ + +typedef struct rc_runtime_progress_t { + rc_runtime_t* runtime; + + int offset; + unsigned char* buffer; + + int chunk_size_offset; + + lua_State* L; +} rc_runtime_progress_t; + +#define RC_TRIGGER_STATE_UNUPDATED 0x7F + +#define RC_MEMREF_FLAG_PREV_IS_PRIOR 0x00010000 + +static void rc_runtime_progress_write_uint(rc_runtime_progress_t* progress, unsigned value) +{ + if (progress->buffer) { + progress->buffer[progress->offset + 0] = value & 0xFF; value >>= 8; + progress->buffer[progress->offset + 1] = value & 0xFF; value >>= 8; + progress->buffer[progress->offset + 2] = value & 0xFF; value >>= 8; + progress->buffer[progress->offset + 3] = value & 0xFF; + } + + progress->offset += 4; +} + +static unsigned rc_runtime_progress_read_uint(rc_runtime_progress_t* progress) +{ + unsigned value = progress->buffer[progress->offset + 0] | + (progress->buffer[progress->offset + 1] << 8) | + (progress->buffer[progress->offset + 2] << 16) | + (progress->buffer[progress->offset + 3] << 24); + + progress->offset += 4; + return value; +} + +static void rc_runtime_progress_write_md5(rc_runtime_progress_t* progress, unsigned char* md5) +{ + if (progress->buffer) + memcpy(&progress->buffer[progress->offset], md5, 16); + + progress->offset += 16; +} + +static int rc_runtime_progress_match_md5(rc_runtime_progress_t* progress, unsigned char* md5) +{ + int result = 0; + if (progress->buffer) + result = (memcmp(&progress->buffer[progress->offset], md5, 16) == 0); + + progress->offset += 16; + + return result; +} + +static void rc_runtime_progress_start_chunk(rc_runtime_progress_t* progress, unsigned chunk_id) +{ + rc_runtime_progress_write_uint(progress, chunk_id); + + progress->chunk_size_offset = progress->offset; + + progress->offset += 4; +} + +static void rc_runtime_progress_end_chunk(rc_runtime_progress_t* progress) +{ + unsigned length; + int offset; + + progress->offset = (progress->offset + 3) & ~0x03; /* align to 4 byte boundary */ + + if (progress->buffer) { + /* ignore chunk size field when calculating chunk size */ + length = (unsigned)(progress->offset - progress->chunk_size_offset - 4); + + /* temporarily update the write pointer to write the chunk size field */ + offset = progress->offset; + progress->offset = progress->chunk_size_offset; + rc_runtime_progress_write_uint(progress, length); + progress->offset = offset; + } +} + +static void rc_runtime_progress_init(rc_runtime_progress_t* progress, rc_runtime_t* runtime, lua_State* L) +{ + memset(progress, 0, sizeof(rc_runtime_progress_t)); + progress->runtime = runtime; + progress->L = L; +} + +static int rc_runtime_progress_write_memrefs(rc_runtime_progress_t* progress) +{ + rc_memref_value_t* memref = progress->runtime->memrefs; + unsigned int flags = 0; + + rc_runtime_progress_start_chunk(progress, RC_RUNTIME_CHUNK_MEMREFS); + + while (memref) { + flags = memref->memref.size; + if (memref->previous == memref->prior) + flags |= RC_MEMREF_FLAG_PREV_IS_PRIOR; + + rc_runtime_progress_write_uint(progress, memref->memref.address); + rc_runtime_progress_write_uint(progress, flags); + rc_runtime_progress_write_uint(progress, memref->value); + rc_runtime_progress_write_uint(progress, memref->prior); + + memref = memref->next; + } + + rc_runtime_progress_end_chunk(progress); + return RC_OK; +} + +static int rc_runtime_progress_read_memrefs(rc_runtime_progress_t* progress) +{ + unsigned entries; + unsigned address, flags, value, prior; + char size; + rc_memref_value_t* memref; + + /* re-read the chunk size to determine how many memrefs are present */ + progress->offset -= 4; + entries = rc_runtime_progress_read_uint(progress) / 16; + + while (entries != 0) { + address = rc_runtime_progress_read_uint(progress); + flags = rc_runtime_progress_read_uint(progress); + value = rc_runtime_progress_read_uint(progress); + prior = rc_runtime_progress_read_uint(progress); + + size = flags & 0xFF; + + memref = progress->runtime->memrefs; + while (memref) { + if (memref->memref.address == address && memref->memref.size == size) { + memref->value = value; + memref->previous = (flags & RC_MEMREF_FLAG_PREV_IS_PRIOR) ? prior : value; + memref->prior = prior; + } + + memref = memref->next; + } + + --entries; + } + + return RC_OK; +} + +static int rc_runtime_progress_write_condset(rc_runtime_progress_t* progress, rc_condset_t* condset) +{ + rc_condition_t* cond; + + rc_runtime_progress_write_uint(progress, condset->is_paused); + + cond = condset->conditions; + while (cond) { + rc_runtime_progress_write_uint(progress, cond->current_hits); + rc_runtime_progress_write_uint(progress, cond->is_true); + + cond = cond->next; + } + + return RC_OK; +} + +static int rc_runtime_progress_read_condset(rc_runtime_progress_t* progress, rc_condset_t* condset) +{ + rc_condition_t* cond; + + condset->is_paused = rc_runtime_progress_read_uint(progress); + + cond = condset->conditions; + while (cond) { + cond->current_hits = rc_runtime_progress_read_uint(progress); + cond->is_true = rc_runtime_progress_read_uint(progress) & 0xFF; + + cond = cond->next; + } + + return RC_OK; +} + +static int rc_runtime_progress_write_trigger(rc_runtime_progress_t* progress, rc_trigger_t* trigger) +{ + rc_condset_t* condset; + int result; + + rc_runtime_progress_write_uint(progress, trigger->state); + rc_runtime_progress_write_uint(progress, trigger->measured_value); + + if (trigger->requirement) { + result = rc_runtime_progress_write_condset(progress, trigger->requirement); + if (result != RC_OK) + return result; + } + + condset = trigger->alternative; + while (condset) + { + result = rc_runtime_progress_write_condset(progress, condset); + if (result != RC_OK) + return result; + + condset = condset->next; + } + + return RC_OK; +} + +static int rc_runtime_progress_read_trigger(rc_runtime_progress_t* progress, rc_trigger_t* trigger) +{ + rc_condset_t* condset; + int result; + + trigger->state = rc_runtime_progress_read_uint(progress); + trigger->measured_value = rc_runtime_progress_read_uint(progress); + + if (trigger->requirement) { + result = rc_runtime_progress_read_condset(progress, trigger->requirement); + if (result != RC_OK) + return result; + } + + condset = trigger->alternative; + while (condset) + { + result = rc_runtime_progress_read_condset(progress, condset); + if (result != RC_OK) + return result; + + condset = condset->next; + } + + return RC_OK; +} + +static int rc_runtime_progress_write_achievements(rc_runtime_progress_t* progress) +{ + unsigned i; + int result; + + for (i = 0; i < progress->runtime->trigger_count; ++i) + { + rc_runtime_trigger_t* runtime_trigger = &progress->runtime->triggers[i]; + if (!runtime_trigger->trigger) + continue; + + switch (runtime_trigger->trigger->state) + { + case RC_TRIGGER_STATE_INACTIVE: + case RC_TRIGGER_STATE_TRIGGERED: + /* don't store state for inactive or triggered achievements */ + break; + + default: + break; + } + + rc_runtime_progress_start_chunk(progress, RC_RUNTIME_CHUNK_ACHIEVEMENT); + rc_runtime_progress_write_uint(progress, runtime_trigger->id); + rc_runtime_progress_write_md5(progress, runtime_trigger->md5); + + result = rc_runtime_progress_write_trigger(progress, runtime_trigger->trigger); + if (result != RC_OK) + return result; + + rc_runtime_progress_end_chunk(progress); + } + + return RC_OK; +} + +static int rc_runtime_progress_read_achievement(rc_runtime_progress_t* progress) +{ + unsigned id = rc_runtime_progress_read_uint(progress); + unsigned i; + + for (i = 0; i < progress->runtime->trigger_count; ++i) { + rc_runtime_trigger_t* runtime_trigger = &progress->runtime->triggers[i]; + if (runtime_trigger->id == id && runtime_trigger->trigger != NULL) { + /* ignore triggered and waiting achievements */ + if (runtime_trigger->trigger->state == RC_TRIGGER_STATE_UNUPDATED) { + /* only update state if definition hasn't changed (md5 matches) */ + if (rc_runtime_progress_match_md5(progress, runtime_trigger->md5)) + return rc_runtime_progress_read_trigger(progress, runtime_trigger->trigger); + break; + } + } + } + + return RC_OK; +} + +static int rc_runtime_progress_serialize_internal(rc_runtime_progress_t* progress) +{ + md5_state_t state; + unsigned char md5[16]; + int result; + + rc_runtime_progress_write_uint(progress, RC_RUNTIME_MARKER); + + if ((result = rc_runtime_progress_write_memrefs(progress)) != RC_OK) + return result; + + if ((result = rc_runtime_progress_write_achievements(progress)) != RC_OK) + return result; + + rc_runtime_progress_write_uint(progress, RC_RUNTIME_CHUNK_DONE); + rc_runtime_progress_write_uint(progress, 16); + + if (progress->buffer) { + md5_init(&state); + md5_append(&state, progress->buffer, progress->offset); + md5_finish(&state, md5); + } + + rc_runtime_progress_write_md5(progress, md5); + + return RC_OK; +} + +int rc_runtime_progress_size(const rc_runtime_t* runtime, lua_State* L) +{ + rc_runtime_progress_t progress; + int result; + + rc_runtime_progress_init(&progress, (rc_runtime_t*)runtime, L); + + result = rc_runtime_progress_serialize_internal(&progress); + if (result != RC_OK) + return result; + + return progress.offset; +} + +int rc_runtime_serialize_progress(void* buffer, const rc_runtime_t* runtime, lua_State* L) +{ + rc_runtime_progress_t progress; + + rc_runtime_progress_init(&progress, (rc_runtime_t*)runtime, L); + progress.buffer = (unsigned char*)buffer; + + return rc_runtime_progress_serialize_internal(&progress); +} + +int rc_runtime_deserialize_progress(rc_runtime_t* runtime, const unsigned char* serialized, lua_State* L) +{ + rc_runtime_progress_t progress; + md5_state_t state; + unsigned char md5[16]; + unsigned chunk_id; + unsigned chunk_size; + unsigned next_chunk_offset; + unsigned i; + int result = RC_OK; + + rc_runtime_progress_init(&progress, runtime, L); + progress.buffer = (unsigned char*)serialized; + + if (rc_runtime_progress_read_uint(&progress) != RC_RUNTIME_MARKER) { + rc_runtime_reset(runtime); + return RC_INVALID_STATE; + } + + for (i = 0; i < runtime->trigger_count; ++i) { + rc_runtime_trigger_t* runtime_trigger = &runtime->triggers[i]; + if (runtime_trigger->trigger) { + switch (runtime_trigger->trigger->state) + { + case RC_TRIGGER_STATE_INACTIVE: + case RC_TRIGGER_STATE_TRIGGERED: + /* don't update state for inactive or triggered achievements */ + break; + + default: + /* mark active achievements as unupdated. anything that's still unupdated + * after deserializing the progress will be reset to waiting */ + runtime_trigger->trigger->state = RC_TRIGGER_STATE_UNUPDATED; + break; + } + } + } + + do { + chunk_id = rc_runtime_progress_read_uint(&progress); + chunk_size = rc_runtime_progress_read_uint(&progress); + next_chunk_offset = progress.offset + chunk_size; + + switch (chunk_id) + { + case RC_RUNTIME_CHUNK_MEMREFS: + result = rc_runtime_progress_read_memrefs(&progress); + break; + + case RC_RUNTIME_CHUNK_ACHIEVEMENT: + result = rc_runtime_progress_read_achievement(&progress); + break; + + case RC_RUNTIME_CHUNK_DONE: + md5_init(&state); + md5_append(&state, progress.buffer, progress.offset); + md5_finish(&state, md5); + if (!rc_runtime_progress_match_md5(&progress, md5)) + result = RC_INVALID_STATE; + break; + + default: + if (chunk_size & 0xFFFF0000) + result = RC_INVALID_STATE; /* assume unknown chunk > 64KB is invalid */ + break; + } + + progress.offset = next_chunk_offset; + } while (result == RC_OK && chunk_id != RC_RUNTIME_CHUNK_DONE); + + if (result != RC_OK) { + rc_runtime_reset(runtime); + } + else { + for (i = 0; i < runtime->trigger_count; ++i) { + rc_trigger_t* trigger = runtime->triggers[i].trigger; + if (trigger && trigger->state == RC_TRIGGER_STATE_UNUPDATED) + rc_reset_trigger(trigger); + } + } + + return result; +} diff --git a/deps/rcheevos/src/rcheevos/term.c b/deps/rcheevos/src/rcheevos/term.c deleted file mode 100644 index 266dc0570c..0000000000 --- a/deps/rcheevos/src/rcheevos/term.c +++ /dev/null @@ -1,103 +0,0 @@ -#include "internal.h" - -rc_term_t* rc_parse_term(const char** memaddr, int is_indirect, rc_parse_state_t* parse) { - rc_term_t* self; - const char* aux; - char size; - int ret2; - - aux = *memaddr; - self = RC_ALLOC(rc_term_t, parse); - self->invert = 0; - - ret2 = rc_parse_operand(&self->operand1, &aux, 0, is_indirect, parse); - - if (ret2 < 0) { - parse->offset = ret2; - return 0; - } - - if (*aux == '*') { - aux++; - - if (*aux == '~') { - aux++; - self->invert = 1; - } - - ret2 = rc_parse_operand(&self->operand2, &aux, 0, is_indirect, parse); - - if (ret2 < 0) { - parse->offset = ret2; - return 0; - } - - if (self->invert) { - switch (self->operand2.type) { - case RC_OPERAND_ADDRESS: - case RC_OPERAND_DELTA: - case RC_OPERAND_PRIOR: - size = self->operand2.value.memref->memref.size; - break; - default: - size = RC_MEMSIZE_32_BITS; - break; - } - - 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: - /* invert is already 1 */ - break; - - case RC_MEMSIZE_LOW: - case RC_MEMSIZE_HIGH: - self->invert = 0xf; - break; - - case RC_MEMSIZE_8_BITS: - self->invert = 0xffU; - break; - - case RC_MEMSIZE_16_BITS: - self->invert = 0xffffU; - break; - - case RC_MEMSIZE_24_BITS: - self->invert = 0xffffffU; - break; - - case RC_MEMSIZE_32_BITS: - self->invert = 0xffffffffU; - break; - } - } - } - else { - self->operand2.type = RC_OPERAND_CONST; - self->operand2.value.num = 1; - } - - *memaddr = aux; - return self; -} - -int rc_evaluate_term(rc_term_t* self, rc_eval_state_t* eval_state) { - /* Operands are usually memory references and are always retrieved as unsigned. The floating - * point operand is signed, and will automatically make the result signed. Otherwise, multiply - * by the secondary operand (which is usually 1) and cast to signed. - */ - unsigned value = rc_evaluate_operand(&self->operand1, eval_state); - - if (self->operand2.type != RC_OPERAND_FP) { - return (int)(value * (rc_evaluate_operand(&self->operand2, eval_state) ^ self->invert)); - } - - return (int)((double)value * self->operand2.value.dbl); -} diff --git a/deps/rcheevos/src/rcheevos/trigger.c b/deps/rcheevos/src/rcheevos/trigger.c index fdb9002fb8..73f0e7839c 100644 --- a/deps/rcheevos/src/rcheevos/trigger.c +++ b/deps/rcheevos/src/rcheevos/trigger.c @@ -1,10 +1,7 @@ #include "internal.h" #include -#if !defined( __CELLOS_LV2__) && !defined(__MWERKS__) -#include -#endif -#include +#include /* memset */ void rc_parse_trigger_internal(rc_trigger_t* self, const char** memaddr, rc_parse_state_t* parse) { rc_condset_t** next; @@ -36,7 +33,7 @@ void rc_parse_trigger_internal(rc_trigger_t* self, const char** memaddr, rc_pars next = &(*next)->next; } - + *next = 0; *memaddr = aux; @@ -62,7 +59,7 @@ rc_trigger_t* rc_parse_trigger(void* buffer, const char* memaddr, lua_State* L, rc_trigger_t* self; rc_parse_state_t parse; rc_init_parse_state(&parse, buffer, L, funcs_ndx); - + self = RC_ALLOC(rc_trigger_t, &parse); rc_init_parse_state_memrefs(&parse, &self->memrefs); @@ -92,14 +89,15 @@ int rc_evaluate_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, lua_State* rc_condset_t* condset; int ret; char is_paused; + char is_primed; /* previously triggered, do nothing - return INACTIVE so caller doesn't report a repeated trigger */ if (self->state == RC_TRIGGER_STATE_TRIGGERED) - return RC_TRIGGER_STATE_INACTIVE; + return RC_TRIGGER_STATE_INACTIVE; rc_update_memref_values(self->memrefs, peek, ud); - /* not yet active, only update the memrefs - so deltas are corrent when it becomes active */ + /* not yet active, only update the memrefs - so deltas are correct when it becomes active */ if (self->state == RC_TRIGGER_STATE_INACTIVE) return RC_TRIGGER_STATE_INACTIVE; @@ -109,22 +107,41 @@ int rc_evaluate_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, lua_State* eval_state.peek_userdata = ud; eval_state.L = L; - ret = self->requirement != 0 ? rc_test_condset(self->requirement, &eval_state) : 1; - condset = self->alternative; + if (self->requirement != NULL) { + ret = rc_test_condset(self->requirement, &eval_state); + is_paused = self->requirement->is_paused; + is_primed = eval_state.primed; + } else { + ret = 1; + is_paused = 0; + is_primed = 1; + } + condset = self->alternative; if (condset) { int sub = 0; + char sub_paused = 1; + char sub_primed = 0; do { sub |= rc_test_condset(condset, &eval_state); - condset = condset->next; - } - while (condset != 0); + sub_paused &= condset->is_paused; + sub_primed |= eval_state.primed; + condset = condset->next; + } while (condset != 0); + + /* to trigger, the core must be true and at least one alt must be true */ ret &= sub; + is_primed &= sub_primed; + + /* if the core is not paused, all alts must be paused to count as a paused trigger */ + is_paused |= sub_paused; } - self->measured_value = eval_state.measured_value; + /* if paused, the measured value may not be captured, keep the old value */ + if (!is_paused) + self->measured_value = eval_state.measured_value; /* if the state is WAITING and the trigger is ready to fire, ignore it and reset the hit counts */ /* otherwise, if the state is WAITING, proceed to activating the trigger */ @@ -138,6 +155,10 @@ int rc_evaluate_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, lua_State* /* if any ResetIf condition was true, reset the hit counts */ rc_reset_trigger_hitcounts(self); + /* if the measured value came from a hit count, reset it too */ + if (eval_state.measured_from_hits) + self->measured_value = 0; + /* if there were hit counts to clear, return RESET, but don't change the state */ if (self->has_hits) { self->has_hits = 0; @@ -146,6 +167,7 @@ int rc_evaluate_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, lua_State* /* any hits that were tallied were just reset */ eval_state.has_hits = 0; + is_primed = 0; } else if (ret) { /* trigger was triggered */ @@ -156,20 +178,16 @@ int rc_evaluate_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, lua_State* /* did not trigger this frame - update the information we'll need for next time */ self->has_hits = eval_state.has_hits; - /* check to see if the trigger is paused */ - is_paused = (self->requirement != NULL) ? self->requirement->is_paused : 0; - if (!is_paused) { - /* if the core is not paused, all alts must be paused to count as a paused trigger */ - is_paused = (self->alternative != NULL); - for (condset = self->alternative; condset != NULL; condset = condset->next) { - if (!condset->is_paused) { - is_paused = 0; - break; - } - } + if (is_paused) { + self->state = RC_TRIGGER_STATE_PAUSED; + } + else if (is_primed) { + self->state = RC_TRIGGER_STATE_PRIMED; + } + else { + self->state = RC_TRIGGER_STATE_ACTIVE; } - self->state = is_paused ? RC_TRIGGER_STATE_PAUSED : RC_TRIGGER_STATE_ACTIVE; return self->state; } @@ -184,4 +202,6 @@ void rc_reset_trigger(rc_trigger_t* self) { rc_reset_trigger_hitcounts(self); self->state = RC_TRIGGER_STATE_WAITING; + self->measured_value = 0; + self->has_hits = 0; } diff --git a/deps/rcheevos/src/rcheevos/value.c b/deps/rcheevos/src/rcheevos/value.c index fcbc6c21e2..2414dada16 100644 --- a/deps/rcheevos/src/rcheevos/value.c +++ b/deps/rcheevos/src/rcheevos/value.c @@ -1,9 +1,7 @@ #include "internal.h" -#if !defined( __CELLOS_LV2__) && !defined(__MWERKS__) -#include -#endif -#include +#include /* memset */ +#include /* isdigit */ static void rc_parse_cond_value(rc_value_t* self, const char** memaddr, rc_parse_state_t* parse) { rc_condition_t** next; @@ -12,12 +10,10 @@ static void rc_parse_cond_value(rc_value_t* self, const char** memaddr, rc_parse has_measured = 0; in_add_address = 0; - self->expressions = 0; - /* this largely duplicates rc_parse_condset, but we cannot call it directly, as we need to check the + /* this largely duplicates rc_parse_condset, but we cannot call it directly, as we need to check the * type of each condition as we go */ self->conditions = RC_ALLOC(rc_condset_t, parse); - self->conditions->next = 0; self->conditions->has_pause = 0; next = &self->conditions->conditions; @@ -49,7 +45,7 @@ static void rc_parse_cond_value(rc_value_t* self, const char** memaddr, rc_parse return; } has_measured = 1; - if ((*next)->required_hits == 0 && (*next)->oper != RC_CONDITION_NONE) + if ((*next)->required_hits == 0 && (*next)->oper != RC_OPERATOR_NONE) (*next)->required_hits = (unsigned)-1; break; @@ -69,42 +65,125 @@ static void rc_parse_cond_value(rc_value_t* self, const char** memaddr, rc_parse (*memaddr)++; } - *next = 0; - if (!has_measured) { parse->offset = RC_MISSING_VALUE_MEASURED; } + + if (parse->buffer) { + *next = 0; + self->conditions->next = 0; + } } -void rc_parse_value_internal(rc_value_t* self, const char** memaddr, rc_parse_state_t* parse) { - rc_expression_t** next; +void rc_parse_legacy_value(rc_value_t* self, const char** memaddr, rc_parse_state_t* parse) { + rc_condition_t** next; + rc_condset_t** next_clause; + rc_condition_t* cond; + char buffer[64] = "A:"; + const char* buffer_ptr; + char* ptr; + int end_of_clause; - /* if it starts with a condition flag (M: A: B: C:), parse the conditions */ - if ((*memaddr)[1] == ':') { - rc_parse_cond_value(self, memaddr, parse); - return; - } + /* convert legacy format into condset */ + self->conditions = RC_ALLOC(rc_condset_t, parse); + self->conditions->has_pause = 0; - self->conditions = 0; - next = &self->expressions; + next = &self->conditions->conditions; + next_clause = &self->conditions->next; for (;;) { - *next = rc_parse_expression(memaddr, parse); + ptr = &buffer[2]; + end_of_clause = 0; + do { + switch (**memaddr) { + case '_': /* add next */ + case '$': /* maximum of */ + case '\0': /* end of string */ + case ':': /* end of leaderboard clause */ + case ')': /* end of rich presence macro */ + end_of_clause = 1; + *ptr = '\0'; + break; + + case '*': + *ptr++ = '*'; + + buffer_ptr = *memaddr + 1; + if (*buffer_ptr == '-') { + /* negative value automatically needs prefix, 'f' handles both float and digits, so use it */ + *ptr++ = 'f'; + } + else { + /* if it looks like a floating point number, add the 'f' prefix */ + while (isdigit(*buffer_ptr)) + ++buffer_ptr; + if (*buffer_ptr == '.') + *ptr++ = 'f'; + } + break; + + default: + *ptr++ = **memaddr; + break; + } + + ++(*memaddr); + } while (!end_of_clause); + + buffer_ptr = buffer; + cond = rc_parse_condition(&buffer_ptr, parse, 0); if (parse->offset < 0) { return; } - next = &(*next)->next; + switch (cond->oper) { + case RC_OPERATOR_MULT: + case RC_OPERATOR_DIV: + case RC_OPERATOR_AND: + case RC_OPERATOR_NONE: + break; - if (**memaddr != '$') { - break; + default: + parse->offset = RC_INVALID_OPERATOR; + return; } - (*memaddr)++; - } + cond->pause = 0; + *next = cond; - *next = 0; + switch ((*memaddr)[-1]) { + case '_': /* add next */ + next = &cond->next; + break; + + case '$': /* max of */ + cond->type = RC_CONDITION_MEASURED; + cond->next = 0; + *next_clause = RC_ALLOC(rc_condset_t, parse); + (*next_clause)->has_pause = 0; + next = &(*next_clause)->conditions; + next_clause = &(*next_clause)->next; + break; + + default: /* end of valid string */ + --(*memaddr); /* undo the increment we performed when copying the string */ + cond->type = RC_CONDITION_MEASURED; + cond->next = 0; + *next_clause = 0; + return; + } + } +} + +void rc_parse_value_internal(rc_value_t* self, const char** memaddr, rc_parse_state_t* parse) { + /* if it starts with a condition flag (M: A: B: C:), parse the conditions */ + if ((*memaddr)[1] == ':') { + rc_parse_cond_value(self, memaddr, parse); + } + else { + rc_parse_legacy_value(self, memaddr, parse); + } } int rc_value_size(const char* memaddr) { @@ -123,7 +202,7 @@ rc_value_t* rc_parse_value(void* buffer, const char* memaddr, lua_State* L, int rc_value_t* self; rc_parse_state_t parse; rc_init_parse_state(&parse, buffer, L, funcs_ndx); - + self = RC_ALLOC(rc_value_t, &parse); rc_init_parse_state_memrefs(&parse, &self->memrefs); @@ -133,26 +212,11 @@ rc_value_t* rc_parse_value(void* buffer, const char* memaddr, lua_State* L, int return parse.offset >= 0 ? self : 0; } -static int rc_evaluate_expr_value(rc_value_t* self, rc_eval_state_t* eval_state) { - rc_expression_t* exp; - int value, max; - - exp = self->expressions; - max = rc_evaluate_expression(exp, eval_state); - - for (exp = exp->next; exp != 0; exp = exp->next) { - value = rc_evaluate_expression(exp, eval_state); - - if (value > max) { - max = value; - } - } - - return max; -} - int rc_evaluate_value(rc_value_t* self, rc_peek_t peek, void* ud, lua_State* L) { rc_eval_state_t eval_state; + rc_condset_t* condset; + int result = 0; + memset(&eval_state, 0, sizeof(eval_state)); eval_state.peek = peek; eval_state.peek_userdata = ud; @@ -160,10 +224,17 @@ int rc_evaluate_value(rc_value_t* self, rc_peek_t peek, void* ud, lua_State* L) rc_update_memref_values(self->memrefs, peek, ud); - if (self->expressions) { - return rc_evaluate_expr_value(self, &eval_state); + rc_test_condset(self->conditions, &eval_state); + result = (int)eval_state.measured_value; + + condset = self->conditions->next; + while (condset != NULL) { + rc_test_condset(condset, &eval_state); + if ((int)eval_state.measured_value > result) + result = (int)eval_state.measured_value; + + condset = condset->next; } - rc_test_condset(self->conditions, &eval_state); - return (int)eval_state.measured_value; + return result; } diff --git a/deps/rcheevos/src/rhash/hash.c b/deps/rcheevos/src/rhash/hash.c new file mode 100644 index 0000000000..f69e696b5b --- /dev/null +++ b/deps/rcheevos/src/rhash/hash.c @@ -0,0 +1,1491 @@ +#ifdef RARCH_INTERNAL + /* explicit path to avoid conflict with libretro-common/include/rhash.h */ + #include "../../include/rhash.h" +#else + #include "rhash.h" +#endif + +#include "../rcheevos/compat.h" + +#include "md5.h" + +#include + +/* arbitrary limit to prevent allocating and hashing large files */ +#define MAX_BUFFER_SIZE 64 * 1024 * 1024 + +const char* rc_path_get_filename(const char* path); + +/* ===================================================== */ + +static rc_hash_message_callback error_message_callback = NULL; +rc_hash_message_callback verbose_message_callback = NULL; + +void rc_hash_init_error_message_callback(rc_hash_message_callback callback) +{ + error_message_callback = callback; +} + +int rc_hash_error(const char* message) +{ + if (error_message_callback) + error_message_callback(message); + + return 0; +} + +void rc_hash_init_verbose_message_callback(rc_hash_message_callback callback) +{ + verbose_message_callback = callback; +} + +static void rc_hash_verbose(const char* message) +{ + if (verbose_message_callback) + verbose_message_callback(message); +} + +/* ===================================================== */ + +static struct rc_hash_filereader filereader_funcs; +static struct rc_hash_filereader* filereader = NULL; + +void rc_hash_init_custom_filereader(struct rc_hash_filereader* reader) +{ + memcpy(&filereader_funcs, reader, sizeof(filereader_funcs)); + filereader = &filereader_funcs; +} + +static void* filereader_open(const char* path) +{ + return fopen(path, "rb"); +} + +static void filereader_seek(void* file_handle, size_t offset, int origin) +{ + fseek((FILE*)file_handle, (long)offset, origin); +} + +static size_t filereader_tell(void* file_handle) +{ + return ftell((FILE*)file_handle); +} + +static size_t filereader_read(void* file_handle, void* buffer, size_t requested_bytes) +{ + return fread(buffer, 1, requested_bytes, (FILE*)file_handle); +} + +static void filereader_close(void* file_handle) +{ + fclose((FILE*)file_handle); +} + +void* rc_file_open(const char* path) +{ + void* handle; + + if (!filereader) + { + filereader_funcs.open = filereader_open; + filereader_funcs.seek = filereader_seek; + filereader_funcs.tell = filereader_tell; + filereader_funcs.read = filereader_read; + filereader_funcs.close = filereader_close; + + filereader = &filereader_funcs; + } + + handle = filereader->open(path); + if (handle && verbose_message_callback) + { + char message[1024]; + snprintf(message, sizeof(message), "Opened %s", rc_path_get_filename(path)); + verbose_message_callback(message); + } + + return handle; +} + +void rc_file_seek(void* file_handle, size_t offset, int origin) +{ + if (filereader) + filereader->seek(file_handle, offset, origin); +} + +size_t rc_file_tell(void* file_handle) +{ + return (filereader) ? filereader->tell(file_handle) : 0; +} + +size_t rc_file_read(void* file_handle, void* buffer, int requested_bytes) +{ + return (filereader) ? filereader->read(file_handle, buffer, requested_bytes) : 0; +} + +void rc_file_close(void* file_handle) +{ + if (filereader) + filereader->close(file_handle); +} + +/* ===================================================== */ + +static struct rc_hash_cdreader cdreader_funcs; +static struct rc_hash_cdreader* cdreader = NULL; + +void rc_hash_init_custom_cdreader(struct rc_hash_cdreader* reader) +{ + memcpy(&cdreader_funcs, reader, sizeof(cdreader_funcs)); + cdreader = &cdreader_funcs; +} + +static void* rc_cd_open_track(const char* path, uint32_t track) +{ + if (cdreader && cdreader->open_track) + return cdreader->open_track(path, track); + + rc_hash_error("no hook registered for cdreader_open_track"); + return NULL; +} + +static size_t rc_cd_read_sector(void* track_handle, uint32_t sector, void* buffer, size_t requested_bytes) +{ + if (cdreader && cdreader->read_sector) + return cdreader->read_sector(track_handle, sector, buffer, requested_bytes); + + rc_hash_error("no hook registered for cdreader_read_sector"); + return 0; +} + +static void rc_cd_close_track(void* track_handle) +{ + if (cdreader && cdreader->close_track) + { + cdreader->close_track(track_handle); + return; + } + + rc_hash_error("no hook registered for cdreader_close_track"); +} + +static uint32_t rc_cd_find_file_sector(void* track_handle, const char* path, unsigned* size) +{ + uint8_t buffer[2048], *tmp; + int sector; + size_t filename_length; + const char* slash; + + if (!track_handle) + return 0; + + filename_length = strlen(path); + slash = strrchr(path, '\\'); + if (slash) + { + /* find the directory record for the first part of the path */ + memcpy(buffer, path, slash - path); + buffer[slash - path] = '\0'; + + sector = rc_cd_find_file_sector(track_handle, (const char *)buffer, NULL); + if (!sector) + return 0; + + ++slash; + filename_length -= (slash - path); + path = slash; + } + else + { + /* find the cd information (always 16 frames in) */ + if (!rc_cd_read_sector(track_handle, 16, buffer, 256)) + return 0; + + /* the directory_record starts at 156, the sector containing the table of contents is 2 bytes into that. + * https://www.cdroller.com/htm/readdata.html + */ + sector = buffer[156 + 2] | (buffer[156 + 3] << 8) | (buffer[156 + 4] << 16); + } + + /* fetch and process the directory record */ + if (!rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer))) + return 0; + + tmp = buffer; + while (tmp < buffer + sizeof(buffer)) + { + if (!*tmp) + return 0; + + /* filename is 33 bytes into the record and the format is "FILENAME;version" or "DIRECTORY" */ + if ((tmp[33 + filename_length] == ';' || tmp[33 + filename_length] == '\0') && + strncasecmp((const char*)(tmp + 33), path, filename_length) == 0) + { + sector = tmp[2] | (tmp[3] << 8) | (tmp[4] << 16); + + if (verbose_message_callback) + { + snprintf((char*)buffer, sizeof(buffer), "Found %s at sector %d", path, sector); + verbose_message_callback((const char*)buffer); + } + + if (size) + *size = tmp[10] | (tmp[11] << 8) | (tmp[12] << 16) | (tmp[13] << 24); + + return sector; + } + + /* the first byte of the record is the length of the record */ + tmp += *tmp; + } + + return 0; +} + +/* ===================================================== */ + +const char* rc_path_get_filename(const char* path) +{ + const char* ptr = path + strlen(path); + do + { + if (ptr[-1] == '/' || ptr[-1] == '\\') + break; + + --ptr; + } while (ptr > path); + + return ptr; +} + +static const char* rc_path_get_extension(const char* path) +{ + const char* ptr = path + strlen(path); + do + { + if (ptr[-1] == '.') + break; + + --ptr; + } while (ptr > path); + + return ptr; +} + +int rc_path_compare_extension(const char* path, const char* ext) +{ + size_t path_len = strlen(path); + size_t ext_len = strlen(ext); + const char* ptr = path + path_len - ext_len; + if (ptr[-1] != '.') + return 0; + + if (memcmp(ptr, ext, ext_len) == 0) + return 1; + + do + { + if (tolower(*ptr) != *ext) + return 0; + + ++ext; + ++ptr; + } while (*ptr); + + return 1; +} + +/* ===================================================== */ + +static int rc_hash_finalize(md5_state_t* md5, char hash[33]) +{ + md5_byte_t digest[16]; + + md5_finish(md5, digest); + + /* NOTE: sizeof(hash) is 4 because it's still treated like a pointer, despite specifying a size */ + snprintf(hash, 33, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + digest[0], digest[1], digest[2], digest[3], digest[4], digest[5], digest[6], digest[7], + digest[8], digest[9], digest[10], digest[11], digest[12], digest[13], digest[14], digest[15] + ); + + if (verbose_message_callback) + { + char message[128]; + snprintf(message, sizeof(message), "Generated hash %s", hash); + verbose_message_callback(message); + } + + return 1; +} + +static int rc_hash_buffer(char hash[33], uint8_t* buffer, size_t buffer_size) +{ + md5_state_t md5; + md5_init(&md5); + + if (buffer_size > MAX_BUFFER_SIZE) + buffer_size = MAX_BUFFER_SIZE; + + md5_append(&md5, buffer, (int)buffer_size); + + if (verbose_message_callback) + { + char message[128]; + snprintf(message, sizeof(message), "Hashing %u byte buffer", (unsigned)buffer_size); + verbose_message_callback(message); + } + + return rc_hash_finalize(&md5, hash); +} + +static int rc_hash_3do(char hash[33], const char* path) +{ + uint8_t buffer[2048]; + const uint8_t operafs_identifier[7] = { 0x01, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x01 }; + void* track_handle; + md5_state_t md5; + int sector; + int block_size, block_location; + int offset, stop; + size_t size = 0; + + track_handle = rc_cd_open_track(path, 1); + if (!track_handle) + return rc_hash_error("Could not open track"); + + /* the Opera filesystem stores the volume information in the first 132 bytes of sector 0 + * https://github.com/barbeque/3dodump/blob/master/OperaFS-Format.md + */ + rc_cd_read_sector(track_handle, 0, buffer, 132); + + if (memcmp(buffer, operafs_identifier, sizeof(operafs_identifier)) == 0) + { + if (verbose_message_callback) + { + char message[128]; + snprintf(message, sizeof(message), "Found 3DO CD, title=%s", &buffer[0x28]); + verbose_message_callback(message); + } + + /* include the volume header in the hash */ + md5_init(&md5); + md5_append(&md5, buffer, 132); + + /* the block size is at offset 0x4C (assume 0x4C is always 0) */ + block_size = buffer[0x4D] * 65536 + buffer[0x4E] * 256 + buffer[0x4F]; + + /* the root directory block location is at offset 0x64 (and duplicated several + * times, but we just look at the primary record) (assume 0x64 is always 0)*/ + block_location = buffer[0x65] * 65536 + buffer[0x66] * 256 + buffer[0x67]; + + /* multiply the block index by the block size to get the real address */ + block_location *= block_size; + + /* convert that to a sector and read it */ + sector = block_location / 2048; + + do + { + rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer)); + + /* offset to start of entries is at offset 0x10 (assume 0x10 and 0x11 are always 0) */ + offset = buffer[0x12] * 256 + buffer[0x13]; + + /* offset to end of entries is at offset 0x0C (assume 0x0C is always 0) */ + stop = buffer[0x0D] * 65536 + buffer[0x0E] * 256 + buffer[0x0F]; + + while (offset < stop) + { + if (buffer[offset + 0x03] == 0x02) /* file */ + { + if (strcasecmp((char*)&buffer[offset + 0x20], "LaunchMe") == 0) + { + /* the block size is at offset 0x0C (assume 0x0C is always 0) */ + block_size = buffer[offset + 0x0D] * 65536 + buffer[offset + 0x0E] * 256 + buffer[offset + 0x0F]; + + /* the block location is at offset 0x44 (assume 0x44 is always 0) */ + block_location = buffer[offset + 0x45] * 65536 + buffer[offset + 0x46] * 256 + buffer[offset + 0x47]; + 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]; + + if (verbose_message_callback) + { + char message[128]; + snprintf(message, sizeof(message), "Hashing header (%u bytes) and %s (%u bytes) ", 132, &buffer[offset + 0x20], (unsigned)size); + verbose_message_callback(message); + } + + break; + } + } + + /* the number of extra copies of the file is at offset 0x40 (assume 0x40-0x42 are always 0) */ + offset += 0x48 + buffer[offset + 0x43] * 4; + } + + if (size != 0) + break; + + /* did not find the file, see if the directory listing is continued in another sector */ + offset = buffer[0x02] * 256 + buffer[0x03]; + + /* no more sectors to search*/ + if (offset == 0xFFFF) + break; + + /* get next sector */ + offset *= block_size; + sector = (block_location + offset) / 2048; + } while (1); + + if (size == 0) + { + rc_cd_close_track(track_handle); + return rc_hash_error("Could not find LaunchMe"); + } + + sector = block_location / 2048; + + while (size > 2048) + { + rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer)); + md5_append(&md5, buffer, sizeof(buffer)); + + ++sector; + size -= 2048; + } + + rc_cd_read_sector(track_handle, sector, buffer, size); + md5_append(&md5, buffer, (int)size); + } + else + { + rc_cd_close_track(track_handle); + return rc_hash_error("Not a 3DO CD"); + } + + rc_cd_close_track(track_handle); + + return rc_hash_finalize(&md5, hash); +} + +static int rc_hash_arcade(char hash[33], const char* path) +{ + /* arcade hash is just the hash of the filename (no extension) - the cores are pretty stringent about having the right ROM data */ + const char* ptr = rc_path_get_filename(path); + const char* ext = rc_path_get_extension(ptr); + return rc_hash_buffer(hash, (uint8_t*)ptr, ext - ptr - 1); +} + +static int rc_hash_lynx(char hash[33], 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) + { + rc_hash_verbose("Ignoring LYNX header"); + + buffer += 64; + buffer_size -= 64; + } + + return rc_hash_buffer(hash, buffer, buffer_size); +} + +static int rc_hash_nes(char hash[33], 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) + { + rc_hash_verbose("Ignoring NES header"); + + buffer += 16; + buffer_size -= 16; + } + else if (buffer[0] == 'F' && buffer[1] == 'D' && buffer[2] == 'S' && buffer[3] == 0x1A) + { + rc_hash_verbose("Ignoring FDS header"); + + buffer += 16; + buffer_size -= 16; + } + + return rc_hash_buffer(hash, buffer, buffer_size); +} + +static int rc_hash_nintendo_ds(char hash[33], const char* path) +{ + uint8_t header[512]; + uint8_t* hash_buffer; + unsigned int hash_size, arm9_size, arm9_addr, arm7_size, arm7_addr, icon_addr; + size_t num_read; + int offset = 0; + md5_state_t md5; + void* file_handle; + + file_handle = rc_file_open(path); + if (!file_handle) + return rc_hash_error("Could not open file"); + + rc_file_seek(file_handle, 0, SEEK_SET); + if (rc_file_read(file_handle, header, sizeof(header)) != 512) + return rc_hash_error("Failed to read header"); + + if (header[0] == 0x2E && header[1] == 0x00 && header[2] == 0x00 && header[3] == 0xEA && + header[0xB0] == 0x44 && header[0xB1] == 0x46 && header[0xB2] == 0x96 && header[0xB3] == 0) + { + /* SuperCard header detected, ignore it */ + rc_hash_verbose("Ignoring SuperCard header"); + + offset = 512; + rc_file_seek(file_handle, offset, SEEK_SET); + rc_file_read(file_handle, header, sizeof(header)); + } + + arm9_addr = header[0x20] | (header[0x21] << 8) | (header[0x22] << 16) | (header[0x23] << 24); + arm9_size = header[0x2C] | (header[0x2D] << 8) | (header[0x2E] << 16) | (header[0x2F] << 24); + arm7_addr = header[0x30] | (header[0x31] << 8) | (header[0x32] << 16) | (header[0x33] << 24); + arm7_size = header[0x3C] | (header[0x3D] << 8) | (header[0x3E] << 16) | (header[0x3F] << 24); + icon_addr = header[0x68] | (header[0x69] << 8) | (header[0x6A] << 16) | (header[0x6B] << 24); + + if (arm9_size + arm7_size > 16 * 1024 * 1024) + { + /* sanity check - code blocks are typically less than 1MB each - assume not a DS ROM */ + snprintf((char*)header, sizeof(header), "arm9 code size (%u) + arm7 code size (%u) exceeds 16MB", arm9_size, arm7_size); + return rc_hash_error((const char*)header); + } + + hash_size = 0xA00; + if (arm9_size > hash_size) + hash_size = arm9_size; + if (arm7_size > hash_size) + hash_size = arm7_size; + + hash_buffer = (uint8_t*)malloc(hash_size); + if (!hash_buffer) + { + rc_file_close(file_handle); + + snprintf((char*)header, sizeof(header), "Failed to allocate %u bytes", hash_size); + return rc_hash_error((const char*)header); + } + + md5_init(&md5); + + rc_hash_verbose("Hashing 352 byte header"); + md5_append(&md5, header, 0x160); + + if (verbose_message_callback) + { + snprintf((char*)header, sizeof(header), "Hashing %u byte arm9 code (at %08X)", arm9_size, arm9_addr); + verbose_message_callback((const char*)header); + } + + rc_file_seek(file_handle, arm9_addr + offset, SEEK_SET); + rc_file_read(file_handle, hash_buffer, arm9_size); + md5_append(&md5, hash_buffer, arm9_size); + + if (verbose_message_callback) + { + snprintf((char*)header, sizeof(header), "Hashing %u byte arm7 code (at %08X)", arm7_size, arm7_addr); + verbose_message_callback((const char*)header); + } + + rc_file_seek(file_handle, arm7_addr + offset, SEEK_SET); + rc_file_read(file_handle, hash_buffer, arm7_size); + md5_append(&md5, hash_buffer, arm7_size); + + if (verbose_message_callback) + { + snprintf((char*)header, sizeof(header), "Hashing 2560 byte icon and labels data (at %08X)", icon_addr); + verbose_message_callback((const char*)header); + } + + rc_file_seek(file_handle, icon_addr + offset, SEEK_SET); + num_read = rc_file_read(file_handle, hash_buffer, 0xA00); + if (num_read < 0xA00) + { + /* some homebrew games don't provide a full icon block, and no data after the icon block. + * if we didn't get a full icon block, fill the remaining portion with 0s + */ + if (verbose_message_callback) + { + snprintf((char*)header, sizeof(header), "Warning: only got %u bytes for icon and labels data, 0-padding to 2560 bytes", (unsigned)num_read); + verbose_message_callback((const char*)header); + } + + memset(&hash_buffer[num_read], 0, 0xA00 - num_read); + } + md5_append(&md5, hash_buffer, 0xA00); + + free(hash_buffer); + rc_file_close(file_handle); + + return rc_hash_finalize(&md5, hash); +} + +static int rc_hash_pce_cd(char hash[33], const char* path) +{ + uint8_t buffer[2048]; + void* track_handle; + md5_state_t md5; + int sector, num_sectors; + unsigned size; + + track_handle = rc_cd_open_track(path, 0); + if (!track_handle) + return rc_hash_error("Could not open track"); + + /* the PC-Engine uses the second sector to specify boot information and program name. + * the string "PC Engine CD-ROM SYSTEM" should exist at 32 bytes into the sector + * http://shu.sheldows.com/shu/download/pcedocs/pce_cdrom.html + */ + rc_cd_read_sector(track_handle, 1, buffer, 128); + + /* normal PC Engine CD will have a header block in sector 1 */ + if (strncmp("PC Engine CD-ROM SYSTEM", (const char*)&buffer[32], 23) == 0) + { + /* the title of the disc is the last 22 bytes of the header */ + md5_init(&md5); + md5_append(&md5, &buffer[106], 22); + + if (verbose_message_callback) + { + char message[128]; + buffer[128] = '\0'; + snprintf(message, sizeof(message), "Found PC Engine CD, title=%s", &buffer[106]); + verbose_message_callback(message); + } + + /* the first three bytes specify the sector of the program data, and the fourth byte + * is the number of sectors. + */ + sector = buffer[0] * 65536 + buffer[1] * 256 + buffer[2]; + num_sectors = buffer[3]; + + if (verbose_message_callback) + { + char message[128]; + snprintf(message, sizeof(message), "Hashing %d sectors starting at sector %d", num_sectors, sector); + verbose_message_callback(message); + } + + while (num_sectors > 0) + { + rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer)); + md5_append(&md5, buffer, sizeof(buffer)); + + ++sector; + --num_sectors; + } + } + /* GameExpress CDs use a standard Joliet filesystem - locate and hash the BOOT.BIN */ + else if ((sector = rc_cd_find_file_sector(track_handle, "BOOT.BIN", &size)) != 0 && size < MAX_BUFFER_SIZE) + { + md5_init(&md5); + while (size > sizeof(buffer)) + { + rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer)); + md5_append(&md5, buffer, sizeof(buffer)); + + ++sector; + size -= sizeof(buffer); + } + + if (size > 0) + { + rc_cd_read_sector(track_handle, sector, buffer, size); + md5_append(&md5, buffer, size); + } + } + else + { + rc_cd_close_track(track_handle); + return rc_hash_error("Not a PC Engine CD"); + } + + rc_cd_close_track(track_handle); + + return rc_hash_finalize(&md5, hash); +} + +static int rc_hash_psx(char hash[33], const char* path) +{ + uint8_t buffer[2048]; + char exe_name[64] = ""; + char* ptr; + char* start; + void* track_handle; + uint32_t sector; + unsigned size; + size_t num_read; + int result = 0; + md5_state_t md5; + + track_handle = rc_cd_open_track(path, 1); + if (!track_handle) + return rc_hash_error("Could not open track"); + + sector = rc_cd_find_file_sector(track_handle, "SYSTEM.CNF", NULL); + if (!sector) + { + sector = rc_cd_find_file_sector(track_handle, "PSX.EXE", &size); + if (sector) + strcpy(exe_name, "PSX.EXE"); + } + else + { + size = (unsigned)rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer) - 1); + buffer[size] = '\0'; + + for (ptr = (char*)buffer; *ptr; ++ptr) + { + if (strncmp(ptr, "BOOT", 4) == 0) + { + ptr += 4; + while (isspace(*ptr)) + ++ptr; + + if (*ptr == '=') + { + ++ptr; + while (isspace(*ptr)) + ++ptr; + + if (strncmp(ptr, "cdrom:", 6) == 0) + ptr += 6; + if (*ptr == '\\') + ++ptr; + + start = ptr; + while (!isspace(*ptr) && *ptr != ';') + ++ptr; + + size = (unsigned)(ptr - start); + if (size >= sizeof(exe_name)) + size = sizeof(exe_name) - 1; + + memcpy(exe_name, start, size); + exe_name[size] = '\0'; + + if (verbose_message_callback) + { + snprintf((char*)buffer, sizeof(buffer), "Looking for boot executable: %s", exe_name); + verbose_message_callback((const char*)buffer); + } + + sector = rc_cd_find_file_sector(track_handle, exe_name, &size); + break; + } + } + + /* advance to end of line */ + while (*ptr && *ptr != '\n') + ++ptr; + } + } + + if (!sector) + { + rc_hash_error("Could not locate primary executable"); + } + else if ((num_read = rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer))) < sizeof(buffer)) + { + rc_hash_error("Could not read primary executable"); + } + else + { + if (memcmp(buffer, "PS-X EXE", 7) != 0) + { + if (verbose_message_callback) + { + char message[128]; + snprintf(message, sizeof(message), "%s did not contain PS-X EXE marker", exe_name); + verbose_message_callback(message); + } + } + else + { + /* the PS-X EXE header specifies the executable size as a 4-byte value 28 bytes into the header, which doesn't + * include the header itself. We want to include the header in the hash, so append another 2048 to that value. + */ + size = (((uint8_t)buffer[31] << 24) | ((uint8_t)buffer[30] << 16) | ((uint8_t)buffer[29] << 8) | (uint8_t)buffer[28]) + 2048; + } + + if (size > MAX_BUFFER_SIZE) + size = MAX_BUFFER_SIZE; + + if (verbose_message_callback) + { + char message[128]; + snprintf(message, sizeof(message), "Hashing %s title (%u bytes) and contents (%u bytes) ", exe_name, (unsigned)strlen(exe_name), size); + verbose_message_callback(message); + } + + /* there's also a few games that are use a singular engine and only differ via their data files. luckily, they have + * unique serial numbers, and use the serial number as the boot file in the standard way. include the boot file in the hash + */ + md5_init(&md5); + md5_append(&md5, (md5_byte_t*)exe_name, (int)strlen(exe_name)); + + do + { + md5_append(&md5, buffer, (int)num_read); + + size -= (unsigned)num_read; + if (size == 0) + break; + + ++sector; + if (size >= sizeof(buffer)) + num_read = rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer)); + else + num_read = rc_cd_read_sector(track_handle, sector, buffer, size); + } while (num_read > 0); + + result = rc_hash_finalize(&md5, hash); + } + + rc_cd_close_track(track_handle); + + return result; +} + +static int rc_hash_sega_cd(char hash[33], const char* path) +{ + uint8_t buffer[512]; + void* track_handle; + + track_handle = rc_cd_open_track(path, 1); + if (!track_handle) + return rc_hash_error("Could not open track"); + + /* the first 512 bytes of sector 0 are a volume header and ROM header that uniquely identify the game. + * After that is an arbitrary amount of code that ensures the game is being run in the correct region. + * Then more arbitrary code follows that actually starts the boot process. Somewhere in there, the + * primary executable is loaded. In many cases, a single game will have multiple executables, so even + * if we could determine the primary one, it's just the tip of the iceberg. As such, we've decided that + * hashing the volume and ROM headers is sufficient for identifying the game, and we'll have to trust + * that our players aren't modifying anything else on the disc. + */ + rc_cd_read_sector(track_handle, 0, buffer, sizeof(buffer)); + + return rc_hash_buffer(hash, buffer, sizeof(buffer)); +} + +static int rc_hash_snes(char hash[33], 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; + if (buffer_size - calc_size == 512) + { + rc_hash_verbose("Ignoring SNES header"); + + buffer += 512; + buffer_size -= 512; + } + + 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) +{ + switch (console_id) + { + default: + { + char message[128]; + snprintf(message, sizeof(message), "Unsupported console for buffer hash: %d", console_id); + return rc_hash_error(message); + } + + case RC_CONSOLE_APPLE_II: + case RC_CONSOLE_ATARI_2600: + case RC_CONSOLE_ATARI_7800: + case RC_CONSOLE_ATARI_JAGUAR: + case RC_CONSOLE_COLECOVISION: + case RC_CONSOLE_GAMEBOY: + case RC_CONSOLE_GAMEBOY_ADVANCE: + case RC_CONSOLE_GAMEBOY_COLOR: + case RC_CONSOLE_GAME_GEAR: + case RC_CONSOLE_INTELLIVISION: + 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_PC_ENGINE: /* NOTE: does not support PCEngine CD */ + case RC_CONSOLE_PC8800: + case RC_CONSOLE_POKEMON_MINI: + case RC_CONSOLE_SEGA_32X: + case RC_CONSOLE_SG1000: + case RC_CONSOLE_VECTREX: + case RC_CONSOLE_VIRTUAL_BOY: + case RC_CONSOLE_WONDERSWAN: + return rc_hash_buffer(hash, buffer, buffer_size); + + case RC_CONSOLE_ATARI_LYNX: + return rc_hash_lynx(hash, buffer, buffer_size); + + case RC_CONSOLE_NINTENDO: + return rc_hash_nes(hash, buffer, buffer_size); + + case RC_CONSOLE_SUPER_NINTENDO: + return rc_hash_snes(hash, buffer, buffer_size); + } +} + +static int rc_hash_whole_file(char hash[33], int console_id, const char* path) +{ + md5_state_t md5; + uint8_t* buffer; + size_t size; + const size_t buffer_size = 65536; + void* file_handle; + int result = 0; + + file_handle = rc_file_open(path); + if (!file_handle) + return rc_hash_error("Could not open file"); + + rc_file_seek(file_handle, 0, SEEK_END); + size = rc_file_tell(file_handle); + + if (verbose_message_callback) + { + char message[1024]; + if (size > MAX_BUFFER_SIZE) + snprintf(message, sizeof(message), "Hashing first %u bytes (of %u bytes) of %s", MAX_BUFFER_SIZE, (unsigned)size, rc_path_get_filename(path)); + else + snprintf(message, sizeof(message), "Hashing %s (%u bytes)", rc_path_get_filename(path), (unsigned)size); + verbose_message_callback(message); + } + + if (size > MAX_BUFFER_SIZE) + size = MAX_BUFFER_SIZE; + + md5_init(&md5); + + buffer = (uint8_t*)malloc(buffer_size); + if (buffer) + { + rc_file_seek(file_handle, 0, SEEK_SET); + while (size >= buffer_size) + { + rc_file_read(file_handle, buffer, (int)buffer_size); + md5_append(&md5, buffer, (int)buffer_size); + size -= buffer_size; + } + + if (size > 0) + { + rc_file_read(file_handle, buffer, (int)size); + md5_append(&md5, buffer, (int)size); + } + + free(buffer); + result = rc_hash_finalize(&md5, hash); + } + + rc_file_close(file_handle); + return result; +} + +static int rc_hash_buffered_file(char hash[33], int console_id, const char* path) +{ + uint8_t* buffer; + size_t size; + int result = 0; + void* file_handle; + + file_handle = rc_file_open(path); + if (!file_handle) + return rc_hash_error("Could not open file"); + + rc_file_seek(file_handle, 0, SEEK_END); + size = rc_file_tell(file_handle); + + if (verbose_message_callback) + { + char message[1024]; + if (size > MAX_BUFFER_SIZE) + snprintf(message, sizeof(message), "Buffering first %u bytes (of %d bytes) of %s", MAX_BUFFER_SIZE, (unsigned)size, rc_path_get_filename(path)); + else + snprintf(message, sizeof(message), "Buffering %s (%d bytes)", rc_path_get_filename(path), (unsigned)size); + verbose_message_callback(message); + } + + if (size > MAX_BUFFER_SIZE) + size = MAX_BUFFER_SIZE; + + buffer = (uint8_t*)malloc(size); + if (buffer) + { + rc_file_seek(file_handle, 0, SEEK_SET); + rc_file_read(file_handle, buffer, (int)size); + + result = rc_hash_generate_from_buffer(hash, console_id, buffer, size); + + free(buffer); + } + + rc_file_close(file_handle); + return result; +} + +static const char* rc_hash_get_first_item_from_playlist(const char* path) +{ + char buffer[1024]; + char* disc_path; + char* ptr, *start; + size_t num_read; + void* file_handle; + + file_handle = rc_file_open(path); + if (!file_handle) + { + rc_hash_error("Could not open playlist"); + return NULL; + } + + num_read = rc_file_read(file_handle, buffer, sizeof(buffer) - 1); + buffer[num_read] = '\0'; + + rc_file_close(file_handle); + + ptr = start = buffer; + /* ignore empty and commented lines */ + while (*ptr == '#' || *ptr == '\r' || *ptr == '\n') + { + while (*ptr && *ptr != '\n') + ++ptr; + if (*ptr) + ++ptr; + start = ptr; + } + + /* find and extract the current line */ + while (*ptr && *ptr != '\n') + ++ptr; + if (ptr > start && ptr[-1] == '\r') + --ptr; + *ptr = '\0'; + + if (verbose_message_callback) + { + char message[1024]; + snprintf(message, sizeof(message), "Extracted %s from playlist", buffer); + verbose_message_callback(message); + } + + ptr = (char*)rc_path_get_filename(path); + num_read = (ptr - path) + strlen(start) + 1; + + disc_path = (char*)malloc(num_read); + if (!disc_path) + return NULL; + + memcpy(disc_path, path, ptr - path); + strcpy(disc_path + (ptr - path), start); + return disc_path; +} + +static int rc_hash_generate_from_playlist(char hash[33], int console_id, const char* path) +{ + int result; + const char* disc_path; + + if (verbose_message_callback) + { + char message[1024]; + snprintf(message, sizeof(message), "Processing playlist: %s", rc_path_get_filename(path)); + verbose_message_callback(message); + } + + disc_path = rc_hash_get_first_item_from_playlist(path); + if (!disc_path) + return rc_hash_error("Failed to get first item from playlist"); + + result = rc_hash_generate_from_file(hash, console_id, disc_path); + + free((void*)disc_path); + return result; +} + +int rc_hash_generate_from_file(char hash[33], int console_id, const char* path) +{ + switch (console_id) + { + default: + { + char buffer[128]; + snprintf(buffer, sizeof(buffer), "Unsupported console for file hash: %d", console_id); + return rc_hash_error(buffer); + } + + case RC_CONSOLE_APPLE_II: + case RC_CONSOLE_ATARI_2600: + case RC_CONSOLE_ATARI_7800: + case RC_CONSOLE_ATARI_JAGUAR: + case RC_CONSOLE_COLECOVISION: + case RC_CONSOLE_GAMEBOY: + case RC_CONSOLE_GAMEBOY_ADVANCE: + case RC_CONSOLE_GAMEBOY_COLOR: + case RC_CONSOLE_GAME_GEAR: + case RC_CONSOLE_INTELLIVISION: + 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: + case RC_CONSOLE_SG1000: + case RC_CONSOLE_VECTREX: + 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); + + case RC_CONSOLE_ATARI_LYNX: + case RC_CONSOLE_NINTENDO: + case RC_CONSOLE_SUPER_NINTENDO: + /* additional logic whole-file hash - buffer then call rc_hash_generate_from_buffer */ + return rc_hash_buffered_file(hash, console_id, path); + + case RC_CONSOLE_3DO: + if (rc_path_compare_extension(path, "m3u")) + return rc_hash_generate_from_playlist(hash, console_id, path); + + return rc_hash_3do(hash, path); + + case RC_CONSOLE_ARCADE: + return rc_hash_arcade(hash, path); + + case RC_CONSOLE_NINTENDO_DS: + return rc_hash_nintendo_ds(hash, path); + + case RC_CONSOLE_PC_ENGINE: + if (rc_path_compare_extension(path, "cue") || rc_path_compare_extension(path, "chd")) + return rc_hash_pce_cd(hash, 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); + + case RC_CONSOLE_PC8800: + 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); + + case RC_CONSOLE_PLAYSTATION: + if (rc_path_compare_extension(path, "m3u")) + return rc_hash_generate_from_playlist(hash, console_id, path); + + return rc_hash_psx(hash, path); + + case RC_CONSOLE_SEGA_CD: + case RC_CONSOLE_SATURN: + if (rc_path_compare_extension(path, "m3u")) + return rc_hash_generate_from_playlist(hash, console_id, path); + + return rc_hash_sega_cd(hash, path); + } +} + +void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* path, uint8_t* buffer, size_t buffer_size) +{ + int need_path = !buffer; + + memset(iterator, 0, sizeof(*iterator)); + iterator->buffer = buffer; + iterator->buffer_size = buffer_size; + + iterator->consoles[0] = 0; + + do + { + const char* ext = rc_path_get_extension(path); + switch (tolower(*ext--)) + { + case 'a': + if (rc_path_compare_extension(ext, "a78")) + { + iterator->consoles[0] = RC_CONSOLE_ATARI_7800; + } + break; + + case 'b': + if (rc_path_compare_extension(ext, "bin")) + { + if (buffer_size == 0) + { + /* raw bin file may be a CD track. if it's more than 32MB, try a CD hash. */ + void* file = rc_file_open(path); + if (file) + { + size_t size; + + rc_file_seek(file, 0, SEEK_END); + size = rc_file_tell(file); + rc_file_close(file); + + if (size > 32 * 1024 * 1024) + { + /* 3DO and Sega CD are the only cores that supports directly opening the bin file. */ + iterator->consoles[0] = RC_CONSOLE_3DO; + iterator->consoles[1] = RC_CONSOLE_SEGA_CD; + + /* fallback to megadrive - see comment below */ + iterator->consoles[2] = RC_CONSOLE_MEGA_DRIVE; + break; + } + } + } + + /* bin is associated with MegaDrive, Sega32X and Atari 2600. Since they all use the same + * hashing algorithm, only specify one of them */ + iterator->consoles[0] = RC_CONSOLE_MEGA_DRIVE; + } + break; + + case 'c': + if (rc_path_compare_extension(ext, "cue") || rc_path_compare_extension(ext, "chd")) + { + iterator->consoles[0] = RC_CONSOLE_PLAYSTATION; + iterator->consoles[1] = RC_CONSOLE_PC_ENGINE; + iterator->consoles[2] = RC_CONSOLE_3DO; + /* SEGA CD hash doesn't have any logic to ensure it's being used against a SEGA CD, so it should always be last */ + iterator->consoles[3] = RC_CONSOLE_SEGA_CD; + need_path = 1; + } + else if (rc_path_compare_extension(ext, "col")) + { + iterator->consoles[0] = RC_CONSOLE_COLECOVISION; + } + break; + + case 'd': + if (rc_path_compare_extension(ext, "dsk")) + { + iterator->consoles[0] = RC_CONSOLE_APPLE_II; + } + else if (rc_path_compare_extension(ext, "d88")) + { + iterator->consoles[0] = RC_CONSOLE_PC8800; + } + break; + + case 'f': + if (rc_path_compare_extension(ext, "fig")) + { + iterator->consoles[0] = RC_CONSOLE_SUPER_NINTENDO; + } + else if (rc_path_compare_extension(ext, "fds")) + { + iterator->consoles[0] = RC_CONSOLE_NINTENDO; + } + break; + + case 'g': + if (rc_path_compare_extension(ext, "gba")) + { + iterator->consoles[0] = RC_CONSOLE_GAMEBOY_ADVANCE; + } + else if (rc_path_compare_extension(ext, "gbc")) + { + iterator->consoles[0] = RC_CONSOLE_GAMEBOY_COLOR; + } + else if (rc_path_compare_extension(ext, "gb")) + { + iterator->consoles[0] = RC_CONSOLE_GAMEBOY; + } + else if (rc_path_compare_extension(ext, "gg")) + { + iterator->consoles[0] = RC_CONSOLE_GAME_GEAR; + } + break; + + case 'i': + if (rc_path_compare_extension(ext, "iso")) + { + iterator->consoles[0] = RC_CONSOLE_3DO; + iterator->consoles[1] = RC_CONSOLE_SEGA_CD; + need_path = 1; + } + break; + + case 'j': + if (rc_path_compare_extension(ext, "jag")) + { + iterator->consoles[0] = RC_CONSOLE_ATARI_JAGUAR; + } + break; + + case 'l': + if (rc_path_compare_extension(ext, "lnx")) + { + iterator->consoles[0] = RC_CONSOLE_ATARI_LYNX; + } + break; + + case 'm': + if (rc_path_compare_extension(ext, "m3u")) + { + const char* disc_path = rc_hash_get_first_item_from_playlist(path); + if (disc_path) + { + path = iterator->path = disc_path; + continue; /* retry with disc_path */ + } + } + else if (rc_path_compare_extension(ext, "md")) + { + iterator->consoles[0] = RC_CONSOLE_MEGA_DRIVE; + } + else if (rc_path_compare_extension(ext, "min")) + { + iterator->consoles[0] = RC_CONSOLE_POKEMON_MINI; + } + break; + + case 'n': + if (rc_path_compare_extension(ext, "nes")) + { + iterator->consoles[0] = RC_CONSOLE_NINTENDO; + } + else if (rc_path_compare_extension(ext, "nds")) + { + iterator->consoles[0] = RC_CONSOLE_NINTENDO_DS; + } + else if (rc_path_compare_extension(ext, "n64")) + { + iterator->consoles[0] = RC_CONSOLE_NINTENDO_64; + } + else if (rc_path_compare_extension(ext, "ngc")) + { + iterator->consoles[0] = RC_CONSOLE_NEOGEO_POCKET; + } + break; + + case 'p': + if (rc_path_compare_extension(ext, "pce")) + { + iterator->consoles[0] = RC_CONSOLE_PC_ENGINE; + } + break; + + case 's': + if (rc_path_compare_extension(ext, "smc") || rc_path_compare_extension(ext, "sfc")) + { + iterator->consoles[0] = RC_CONSOLE_SUPER_NINTENDO; + } + else if (rc_path_compare_extension(ext, "sg")) + { + iterator->consoles[0] = RC_CONSOLE_SG1000; + } + else if (rc_path_compare_extension(ext, "sgx")) + { + iterator->consoles[0] = RC_CONSOLE_PC_ENGINE; + } + break; + + case 't': + if (rc_path_compare_extension(ext, "tap")) + { + iterator->consoles[0] = RC_CONSOLE_ORIC; + } + break; + + case 'v': + if (rc_path_compare_extension(ext, "vb")) + { + iterator->consoles[0] = RC_CONSOLE_VIRTUAL_BOY; + } + break; + + case 'w': + if (rc_path_compare_extension(ext, "wsc")) + { + iterator->consoles[0] = RC_CONSOLE_WONDERSWAN; + } + break; + + case 'z': + if (rc_path_compare_extension(ext, "zip")) + { + /* decompressing zip file not supported */ + iterator->consoles[0] = RC_CONSOLE_ARCADE; + need_path = 1; + } + break; + } + + if (verbose_message_callback) + { + char message[256]; + int count = 0; + while (iterator->consoles[count]) + ++count; + + snprintf(message, sizeof(message), "Found %d potential consoles for %s file extension", count, ext); + verbose_message_callback(message); + } + + /* loop is only for specific cases that redirect to another file - like m3u */ + break; + } while (1); + + if (need_path && !iterator->path) + iterator->path = strdup(path); + + /* if we didn't match the extension, default to something that does a whole file hash */ + if (!iterator->consoles[0]) + iterator->consoles[0] = RC_CONSOLE_GAMEBOY; +} + +void rc_hash_destroy_iterator(struct rc_hash_iterator* iterator) +{ + if (iterator->path) + { + free((void*)iterator->path); + iterator->path = NULL; + } +} + +int rc_hash_iterate(char hash[33], struct rc_hash_iterator* iterator) +{ + int next_console; + int result = 0; + + do + { + next_console = iterator->consoles[iterator->index]; + if (next_console == 0) + { + hash[0] = '\0'; + break; + } + + ++iterator->index; + + if (verbose_message_callback) + { + char message[128]; + snprintf(message, sizeof(message), "Trying console %d", next_console); + verbose_message_callback(message); + } + + if (iterator->buffer) + result = rc_hash_generate_from_buffer(hash, next_console, iterator->buffer, iterator->buffer_size); + else + result = rc_hash_generate_from_file(hash, next_console, iterator->path); + + } while (!result); + + return result; +} diff --git a/deps/rcheevos/src/rhash/md5.h b/deps/rcheevos/src/rhash/md5.h new file mode 100644 index 0000000000..8f100b176b --- /dev/null +++ b/deps/rcheevos/src/rhash/md5.h @@ -0,0 +1,18 @@ +#ifndef RC_MD5_H +#define RC_MD5_H + +/* NOTE: this is NOT the md5.h included in the rcheevos repository. It provides the same + * functionality using code already present in RetroArch */ + +/* android build has libretro-common/include in path, but not the base directory. + * other builds prioritize rcheevos/include over libretro-common/include. + * to ensure we get the correct include file, use a complicated relative path */ +#include <../../../libretro-common/include/rhash.h> + +#define md5_state_t MD5_CTX +#define md5_byte_t unsigned char +#define md5_init(state) MD5_Init(state) +#define md5_append(state, buffer, size) MD5_Update(state, buffer, size) +#define md5_finish(state, hash) MD5_Final(hash, state) + +#endif diff --git a/deps/rcheevos/src/rurl/url.c b/deps/rcheevos/src/rurl/url.c index 5ac67aa361..465d1bb2b4 100644 --- a/deps/rcheevos/src/rurl/url.c +++ b/deps/rcheevos/src/rurl/url.c @@ -1,15 +1,7 @@ #include "rurl.h" -#ifdef RARCH_INTERNAL -#include /* libretro-common/include/rhash.h */ -#define md5_state_t MD5_CTX -#define md5_byte_t unsigned char -#define md5_init(state) MD5_Init(state) -#define md5_append(state, buffer, size) MD5_Update(state, buffer, size) -#define md5_finish(state, hash) MD5_Final(hash, state) -#else -#include "..\rhash\md5.h" -#endif +#include "../rcheevos/compat.h" +#include "../rhash/md5.h" #include #include @@ -25,37 +17,41 @@ static int rc_url_encode(char* encoded, size_t len, const char* str) { case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '-': case '_': case '.': case '~': - if (len >= 2) { - *encoded++ = *str++; - len--; - } - else { + if (len < 2) return -1; - } + *encoded++ = *str++; + --len; break; - + + case ' ': + if (len < 2) + return -1; + + *encoded++ = '+'; + ++str; + --len; + break; + default: - if (len >= 4) { - snprintf(encoded, len, "%%%02x", (unsigned char)*str); - encoded += 3; - str++; - len -= 3; - } - else { + if (len < 4) return -1; - } + snprintf(encoded, len, "%%%02x", (unsigned char)*str); + encoded += 3; + ++str; + len -= 3; break; - - case 0: + + case '\0': *encoded = 0; return 0; } } } -int rc_url_award_cheevo(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned cheevo_id, int hardcore) { +int rc_url_award_cheevo(char* buffer, size_t size, const char* user_name, const char* login_token, + unsigned cheevo_id, int hardcore, const char* game_hash) { char urle_user_name[64]; char urle_login_token[64]; int written; @@ -63,11 +59,11 @@ int rc_url_award_cheevo(char* buffer, size_t size, const char* user_name, const if (rc_url_encode(urle_user_name, sizeof(urle_user_name), user_name) != 0) { return -1; } - + if (rc_url_encode(urle_login_token, sizeof(urle_login_token), login_token) != 0) { return -1; } - + written = snprintf( buffer, size, @@ -78,10 +74,14 @@ int rc_url_award_cheevo(char* buffer, size_t size, const char* user_name, const hardcore ? 1 : 0 ); + if (game_hash && strlen(game_hash) == 32 && (size - (size_t)written) >= 35) { + written += snprintf(buffer + written, size - (size_t)written, "&m=%s", game_hash); + } + return (size_t)written >= size ? -1 : 0; } -int rc_url_submit_lboard(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned lboard_id, int value, const char* game_hash) { +int rc_url_submit_lboard(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned lboard_id, int value) { char urle_user_name[64]; char urle_login_token[64]; char signature[64]; @@ -92,7 +92,7 @@ int rc_url_submit_lboard(char* buffer, size_t size, const char* user_name, const if (rc_url_encode(urle_user_name, sizeof(urle_user_name), user_name) != 0) { return -1; } - + if (rc_url_encode(urle_login_token, sizeof(urle_login_token), login_token) != 0) { return -1; } @@ -115,20 +115,15 @@ int rc_url_submit_lboard(char* buffer, size_t size, const char* user_name, const hash[ 8], hash[ 9], hash[10], hash[11],hash[12], hash[13], hash[14], hash[15] ); - if (game_hash && strlen(game_hash) == 32 && (size - (size_t)written) >= 35) { - written += snprintf(buffer + written, size - (size_t)written, "&m=%s", game_hash); - } - return (size_t)written >= size ? -1 : 0; } -int rc_url_get_gameid(char* buffer, size_t size, unsigned char hash[16]) { +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=%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", - hash[ 0], hash[ 1], hash[ 2], hash[ 3], hash[ 4], hash[ 5], hash[ 6], hash[ 7], - hash[ 8], hash[ 9], hash[10], hash[11],hash[12], hash[13], hash[14], hash[15] + "http://retroachievements.org/dorequest.php?r=gameid&m=%s", + hash ); return (size_t)written >= size ? -1 : 0; @@ -142,11 +137,11 @@ int rc_url_get_patch(char* buffer, size_t size, const char* user_name, const cha if (rc_url_encode(urle_user_name, sizeof(urle_user_name), user_name) != 0) { return -1; } - + if (rc_url_encode(urle_login_token, sizeof(urle_login_token), login_token) != 0) { return -1; } - + written = snprintf( buffer, size, @@ -178,11 +173,11 @@ int rc_url_login_with_password(char* buffer, size_t size, const char* user_name, if (rc_url_encode(urle_user_name, sizeof(urle_user_name), user_name) != 0) { return -1; } - + if (rc_url_encode(urle_password, sizeof(urle_password), password) != 0) { return -1; } - + written = snprintf( buffer, size, @@ -202,11 +197,11 @@ int rc_url_login_with_token(char* buffer, size_t size, const char* user_name, co if (rc_url_encode(urle_user_name, sizeof(urle_user_name), user_name) != 0) { return -1; } - + if (rc_url_encode(urle_login_token, sizeof(urle_login_token), login_token) != 0) { return -1; } - + written = snprintf( buffer, size, @@ -226,11 +221,11 @@ int rc_url_get_unlock_list(char* buffer, size_t size, const char* user_name, con if (rc_url_encode(urle_user_name, sizeof(urle_user_name), user_name) != 0) { return -1; } - + if (rc_url_encode(urle_login_token, sizeof(urle_login_token), login_token) != 0) { return -1; } - + written = snprintf( buffer, size, @@ -252,11 +247,11 @@ int rc_url_post_playing(char* buffer, size_t size, const char* user_name, const if (rc_url_encode(urle_user_name, sizeof(urle_user_name), user_name) != 0) { return -1; } - + if (rc_url_encode(urle_login_token, sizeof(urle_login_token), login_token) != 0) { return -1; } - + written = snprintf( buffer, size, @@ -268,3 +263,111 @@ int rc_url_post_playing(char* buffer, size_t size, const char* user_name, const return (size_t)written >= size ? -1 : 0; } + +static int rc_url_append_param_equals(char* buffer, size_t buffer_size, size_t buffer_offset, const char* param) +{ + int written = 0; + size_t param_len; + + if (buffer_offset >= buffer_size) + return -1; + + if (buffer_offset) { + buffer += buffer_offset; + buffer_size -= buffer_offset; + + if (buffer[-1] != '?') { + *buffer++ = '&'; + buffer_size--; + written = 1; + } + } + + param_len = strlen(param); + if (param_len + 1 >= buffer_size) + return -1; + memcpy(buffer, param, param_len); + buffer[param_len] = '='; + + written += (int)param_len + 1; + return written + (int)buffer_offset; +} + +static int rc_url_append_unum(char* buffer, size_t buffer_size, size_t* buffer_offset, const char* param, unsigned value) +{ + int written = rc_url_append_param_equals(buffer, buffer_size, *buffer_offset, param); + if (written > 0) { + char num[16]; + int chars = sprintf(num, "%u", value); + + if (chars + written < (int)buffer_size) + { + memcpy(&buffer[written], num, chars + 1); + *buffer_offset = written + chars; + return 0; + } + } + + return -1; +} + +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) + { + buffer += written; + buffer_size -= written; + + if (rc_url_encode(buffer, buffer_size, value) == 0) + { + written += (int)strlen(buffer); + *buffer_offset = written; + return 0; + } + } + + return -1; +} + +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"; + size_t written = strlen(base_url); + int failure = 0; + + if (url_buffer_size < written + 1) + return -1; + memcpy(url_buffer, base_url, written); + 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); + + *buffer_offset += written; + return failure; +} + +int rc_url_ping(char* url_buffer, size_t url_buffer_size, char* post_buffer, size_t post_buffer_size, + const char* user_name, const char* login_token, unsigned gameid, const char* rich_presence) +{ + size_t written = 0; + int failure = rc_url_build_dorequest(url_buffer, url_buffer_size, &written, "ping", user_name); + failure |= rc_url_append_unum(url_buffer, url_buffer_size, &written, "g", gameid); + + written = 0; + failure |= rc_url_append_str(post_buffer, post_buffer_size, &written, "t", login_token); + + if (rich_presence && *rich_presence) + failure |= rc_url_append_str(post_buffer, post_buffer_size, &written, "m", rich_presence); + + if (failure) { + if (url_buffer_size) + url_buffer[0] = '\0'; + if (post_buffer_size) + post_buffer[0] = '\0'; + } + + return failure; +} diff --git a/griffin/griffin.c b/griffin/griffin.c index 5a49927fda..2464aa23ad 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -181,17 +181,20 @@ ACHIEVEMENTS #include "../cheevos-new/parser.c" #include "../deps/rcheevos/src/rcheevos/alloc.c" +#include "../deps/rcheevos/src/rcheevos/compat.c" #include "../deps/rcheevos/src/rcheevos/condition.c" #include "../deps/rcheevos/src/rcheevos/condset.c" -#include "../deps/rcheevos/src/rcheevos/expression.c" +#include "../deps/rcheevos/src/rcheevos/consoleinfo.c" #include "../deps/rcheevos/src/rcheevos/format.c" #include "../deps/rcheevos/src/rcheevos/lboard.c" +#include "../deps/rcheevos/src/rcheevos/memref.c" #include "../deps/rcheevos/src/rcheevos/operand.c" -#include "../deps/rcheevos/src/rcheevos/term.c" +#include "../deps/rcheevos/src/rcheevos/richpresence.c" +#include "../deps/rcheevos/src/rcheevos/runtime.c" +#include "../deps/rcheevos/src/rcheevos/runtime_progress.c" #include "../deps/rcheevos/src/rcheevos/trigger.c" #include "../deps/rcheevos/src/rcheevos/value.c" -#include "../deps/rcheevos/src/rcheevos/memref.c" -#include "../deps/rcheevos/src/rcheevos/richpresence.c" +#include "../deps/rcheevos/src/rhash/hash.c" #include "../deps/rcheevos/src/rurl/url.c" #endif