upgrade to rcheevos 9.0

This commit is contained in:
Jamiras 2020-05-04 09:11:16 -06:00
parent 4a9e1e6c7e
commit 1534e81bed
31 changed files with 4950 additions and 1008 deletions

View File

@ -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)

View File

@ -23,7 +23,7 @@
#include <features/features_cpu.h>
#include <formats/cdfs.h>
#include <compat/strl.h>
#include <rhash.h>
#include <../libretro-common/include/rhash.h>
#include <retro_miscellaneous.h>
#include <retro_math.h>
#include <net/net_http.h>
@ -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);

View File

@ -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)
{

View File

@ -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

View File

@ -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.

View File

@ -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

79
deps/rcheevos/include/rconsoles.h vendored Normal file
View File

@ -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 */

124
deps/rcheevos/include/rhash.h vendored Normal file
View File

@ -0,0 +1,124 @@
#ifndef RHASH_H
#define RHASH_H
#include <stddef.h>
#include <stdio.h>
#include <stdint.h>
#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 */

View File

@ -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

View File

@ -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";
}
}

44
deps/rcheevos/src/rcheevos/compat.c vendored Normal file
View File

@ -0,0 +1,44 @@
#include "compat.h"
#include <ctype.h>
#include <stdarg.h>
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;
}

55
deps/rcheevos/src/rcheevos/compat.h vendored Normal file
View File

@ -0,0 +1,55 @@
#ifndef RC_COMPAT_H
#define RC_COMPAT_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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 */

View File

@ -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;
}

View File

@ -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;
}
}

573
deps/rcheevos/src/rcheevos/consoleinfo.c vendored Normal file
View File

@ -0,0 +1,573 @@
#include "rcheevos.h"
#include "rconsoles.h"
#include <ctype.h>
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;
}
}

View File

@ -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;
}

View File

@ -1,5 +1,7 @@
#include "internal.h"
#include "compat.h"
#include <string.h>
#include <stdio.h>
@ -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;

View File

@ -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);

View File

@ -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);

View File

@ -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:

View File

@ -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;
}

View File

@ -1,9 +1,8 @@
#include "internal.h"
#include "compat.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* 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;
}

589
deps/rcheevos/src/rcheevos/runtime.c vendored Normal file
View File

@ -0,0 +1,589 @@
#include "internal.h"
#include "../rhash/md5.h"
#include <stdlib.h>
#include <string.h>
#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;
}
}
}

View File

@ -0,0 +1,445 @@
#include "internal.h"
#include "../rhash/md5.h"
#include <string.h>
#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;
}

View File

@ -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);
}

View File

@ -1,10 +1,7 @@
#include "internal.h"
#include <stddef.h>
#if !defined( __CELLOS_LV2__) && !defined(__MWERKS__)
#include <memory.h>
#endif
#include <string.h>
#include <string.h> /* 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;
}

View File

@ -1,9 +1,7 @@
#include "internal.h"
#if !defined( __CELLOS_LV2__) && !defined(__MWERKS__)
#include <memory.h>
#endif
#include <string.h>
#include <string.h> /* memset */
#include <ctype.h> /* 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;
}

1491
deps/rcheevos/src/rhash/hash.c vendored Normal file

File diff suppressed because it is too large Load Diff

18
deps/rcheevos/src/rhash/md5.h vendored Normal file
View File

@ -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

View File

@ -1,15 +1,7 @@
#include "rurl.h"
#ifdef RARCH_INTERNAL
#include <rhash.h> /* 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 <stdio.h>
#include <string.h>
@ -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;
}

View File

@ -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