From 9983976ddb0b61390a2206f883ec4ae118fc7f58 Mon Sep 17 00:00:00 2001 From: Jamiras Date: Sun, 10 Nov 2019 15:28:43 -0700 Subject: [PATCH] update rcheevos to v8.1 --- cheevos-new/cheevos.c | 22 ++- deps/rcheevos/README.md | 59 ++++-- deps/rcheevos/include/rcheevos.h | 65 +++++-- deps/rcheevos/include/rurl.h | 2 +- deps/rcheevos/src/rcheevos/alloc.c | 1 + deps/rcheevos/src/rcheevos/condition.c | 25 ++- deps/rcheevos/src/rcheevos/condset.c | 124 ++++++++++--- deps/rcheevos/src/rcheevos/expression.c | 8 +- deps/rcheevos/src/rcheevos/format.c | 91 +++++++--- deps/rcheevos/src/rcheevos/internal.h | 42 +++-- deps/rcheevos/src/rcheevos/lboard.c | 7 +- deps/rcheevos/src/rcheevos/memref.c | 209 +++++++++++++++------- deps/rcheevos/src/rcheevos/operand.c | 62 ++++--- deps/rcheevos/src/rcheevos/richpresence.c | 23 ++- deps/rcheevos/src/rcheevos/term.c | 18 +- deps/rcheevos/src/rcheevos/trigger.c | 136 ++++++++++---- deps/rcheevos/src/rcheevos/value.c | 107 ++++++++++- deps/rcheevos/src/rurl/url.c | 4 +- 18 files changed, 758 insertions(+), 247 deletions(-) diff --git a/cheevos-new/cheevos.c b/cheevos-new/cheevos.c index c141e4e6bf..44666f187f 100644 --- a/cheevos-new/cheevos.c +++ b/cheevos-new/cheevos.c @@ -110,7 +110,7 @@ typedef struct rc_lboard_t* lboard; const rcheevos_ralboard_t* info; bool active; - unsigned last_value; + int last_value; int format; } rcheevos_lboard_t; @@ -355,6 +355,13 @@ static const char* rcheevos_rc_error(int ret) 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 value (measured)"; + case RC_MULTIPLE_MEASURED: return "Multiple measured targets"; + case RC_INVALID_MEASURED_TARGET: return "Invalid measured target"; + default: return "Unknown error"; } } @@ -803,7 +810,6 @@ static void rcheevos_test_leaderboards(void) CHEEVOS_LOG(RCHEEVOS_TAG "Leaderboard started: %s\n", lboard->info->title); lboard->active = 1; - lboard->last_value = 0; snprintf(buffer, sizeof(buffer), "Leaderboard Active: %s", lboard->info->title); @@ -824,19 +830,29 @@ void rcheevos_reset_game(void) cheevo = rcheevos_locals.core; for (i = 0; i < rcheevos_locals.patchdata.core_count; i++, cheevo++) { + rc_reset_trigger(cheevo->trigger); cheevo->last = 1; } cheevo = rcheevos_locals.unofficial; for (i = 0; i < rcheevos_locals.patchdata.unofficial_count; i++, cheevo++) { + rc_reset_trigger(cheevo->trigger); cheevo->last = 1; } lboard = rcheevos_locals.lboards; for (i = 0; i < rcheevos_locals.patchdata.lboard_count; i++, lboard++) { - lboard->active = 0; + 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 */ + lboard->lboard->submitted = 1; + } } } diff --git a/deps/rcheevos/README.md b/deps/rcheevos/README.md index 3453251e28..d9738e5312 100644 --- a/deps/rcheevos/README.md +++ b/deps/rcheevos/README.md @@ -53,7 +53,13 @@ enum { RC_MISSING_CANCEL = -14, RC_MISSING_SUBMIT = -15, RC_MISSING_VALUE = -16, - RC_INVALID_LBOARD_FIELD = -17 + RC_INVALID_LBOARD_FIELD = -17, + RC_MISSING_DISPLAY_STRING = -18, + RC_OUT_OF_MEMORY = -19, + RC_INVALID_VALUE_FLAG = -20, + RC_MISSING_VALUE_MEASURED = -21, + RC_MULTIPLE_MEASURED = -22, + RC_INVALID_MEASURED_TARGET = -23 }; ``` @@ -190,16 +196,14 @@ struct rc_condition_t { /* Number of hits so far. */ unsigned current_hits; - /** - * Set if the condition needs to processed as part of the "check if paused" - * pass - */ - char pause; - /* The type of the condition. */ char type; /* The comparison operator to use. */ char oper; /* operator is a reserved word in C++. */ + /* Set if the condition needs to processed as part of the "check if paused" pass. */ + char pause; + /* Whether or not the condition evaluated as true on the last check. */ + char is_true; }; ``` @@ -212,7 +216,10 @@ enum { RC_CONDITION_RESET_IF, RC_CONDITION_ADD_SOURCE, RC_CONDITION_SUB_SOURCE, - RC_CONDITION_ADD_HITS + RC_CONDITION_ADD_HITS, + RC_CONDITION_AND_NEXT, + RC_CONDITION_MEASURED, + RC_CONDITION_ADD_ADDRESS }; ``` @@ -225,7 +232,8 @@ enum { RC_CONDITION_LE, RC_CONDITION_GT, RC_CONDITION_GE, - RC_CONDITION_NE + RC_CONDITION_NE, + RC_CONDITION_NONE }; ``` @@ -279,10 +287,10 @@ rc_trigger_t* rc_parse_trigger(void* buffer, const char* memaddr, lua_State* L, `buffer` is the caller-allocated buffer, which must have enough space for the trigger. `memaddr` describes the trigger, and must be the same one used to compute the trigger's size with `rc_trigger_size`. `L` must be a valid Lua state, and `funcs_ndx` must be an index to the current Lua stack which contains a table which is a map of names to functions. This map is used to look for operands which are Lua functions. -Once the trigger is created, `rc_test_trigger` can be called to test whether the trigger fires or not. +Once the trigger is created, `rc_evaluate_trigger` can be called to test whether the trigger fires or not. ```c -int rc_test_trigger(rc_trigger_t* trigger, rc_peek_t peek, void* ud, lua_State* L); +int rc_evaluate_trigger(rc_trigger_t* trigger, rc_peek_t peek, void* ud, lua_State* L); ``` `trigger` is the trigger to test. `peek` is a callback used to read bytes from the emulated memory. `ud` is an user-provided opaque value that is passed to `peek`. `L` is the Lua state in which context the Lua functions are looked for and called, if necessary. @@ -297,6 +305,18 @@ where `address` is the starting address to read from, `num_bytes` the number of > Addresses passed to `peek` do *not* map 1:1 to the emulated memory. (**TODO**: document the mapping from `peek` addresses to emulated memory for each supported system.) +The return value of `rc_evaluate_trigger` is one of the following: +```c +enum { + RC_TRIGGER_STATE_INACTIVE, /* achievement is not being processed */ + RC_TRIGGER_STATE_WAITING, /* achievement cannot trigger until it has been false for at least one frame */ + 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 */ +}; +``` + Finally, `rc_reset_trigger` can be used to reset the internal state of a trigger. ```c @@ -348,6 +368,12 @@ A value is a collection of expressions. It's used to give the value for a leader typedef struct { /* The list of expression to evaluate. */ rc_expression_t* expressions; + + /* The list of conditions to evaluate. */ + rc_condset_t* conditions; + + /* The memory references required by the value. */ + rc_memref_value_t* memrefs; } rc_value_t; ``` @@ -369,7 +395,7 @@ rc_value_t* rc_parse_value(void* buffer, const char* memaddr, lua_State* L, int To compute the value, use `rc_evaluate_value`: ```c -unsigned rc_evaluate_value(rc_value_t* value, rc_peek_t peek, void* ud, lua_State* L); +int rc_evaluate_value(rc_value_t* value, rc_peek_t peek, void* ud, lua_State* L); ``` `value` is the value to compute the value of, and `peek`, `ud`, and `L`, are as in [`rc_test_trigger`](#rc_test_trigger). @@ -404,7 +430,7 @@ rc_lboard_t* rc_parse_lboard(void* buffer, const char* memaddr, lua_State* L, in A leaderboard can be evaluated with the `rc_evaluate_lboard` function: ```c -int rc_evaluate_lboard(rc_lboard_t* lboard, unsigned* value, rc_peek_t peek, void* peek_ud, lua_State* L); +int rc_evaluate_lboard(rc_lboard_t* lboard, int* value, rc_peek_t peek, void* peek_ud, lua_State* L); ``` The function returns an action that must be performed by the caller, and `value` contains the value to be used for that action when the function returns. The action can be one of: @@ -451,7 +477,8 @@ enum { RC_FORMAT_CENTISECS, RC_FORMAT_SCORE, RC_FORMAT_VALUE, - RC_FORMAT_OTHER, + RC_FORMAT_MINUTES, + RC_FORMAT_SECONDS_AS_MINUTES }; ``` @@ -460,10 +487,10 @@ enum { `rc_format_value` can be used to format the given value into the provided buffer: ```c -void rc_format_value(char* buffer, int size, unsigned value, int format); +int rc_format_value(char* buffer, int size, int value, int format); ``` -`buffer` receives `value` formatted according to `format`. No more than `size` characters will be written to `buffer`. 32 characters are enough to hold any valid value with any format. +`buffer` receives `value` formatted according to `format`. No more than `size` characters will be written to `buffer`. 32 characters are enough to hold any valid value with any format. The returned value is the number of characters written. # **rurl** diff --git a/deps/rcheevos/include/rcheevos.h b/deps/rcheevos/include/rcheevos.h index 423be5e6db..467771464b 100644 --- a/deps/rcheevos/include/rcheevos.h +++ b/deps/rcheevos/include/rcheevos.h @@ -31,7 +31,11 @@ enum { RC_MISSING_VALUE = -16, RC_INVALID_LBOARD_FIELD = -17, RC_MISSING_DISPLAY_STRING = -18, - RC_OUT_OF_MEMORY = -19 + RC_OUT_OF_MEMORY = -19, + RC_INVALID_VALUE_FLAG = -20, + RC_MISSING_VALUE_MEASURED = -21, + RC_MULTIPLE_MEASURED = -22, + RC_INVALID_MEASURED_TARGET = -23 }; /*****************************************************************************\ @@ -112,6 +116,8 @@ typedef struct { 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; typedef struct rc_memref_value_t rc_memref_value_t; @@ -176,7 +182,9 @@ enum { RC_CONDITION_ADD_SOURCE, RC_CONDITION_SUB_SOURCE, RC_CONDITION_ADD_HITS, - RC_CONDITION_AND_NEXT + RC_CONDITION_AND_NEXT, + RC_CONDITION_MEASURED, + RC_CONDITION_ADD_ADDRESS }; /* operators */ @@ -186,7 +194,8 @@ enum { RC_CONDITION_LE, RC_CONDITION_GT, RC_CONDITION_GE, - RC_CONDITION_NE + RC_CONDITION_NE, + RC_CONDITION_NONE }; typedef struct rc_condition_t rc_condition_t; @@ -204,16 +213,17 @@ struct rc_condition_t { /* The next condition in the chain. */ rc_condition_t* next; - /** - * Set if the condition needs to processed as part of the "check if paused" - * pass - */ - char pause; - /* The type of the condition. */ char type; + /* The comparison operator to use. */ char oper; /* operator is a reserved word in C++. */ + + /* Set if the condition needs to processed as part of the "check if paused" pass. */ + char pause; + + /* Whether or not the condition evaluated true on the last check */ + char is_true; }; /*****************************************************************************\ @@ -231,12 +241,24 @@ struct rc_condset_t { /* True if any condition in the set is a pause condition. */ char has_pause; + + /* True if the set is currently paused. */ + char is_paused; }; /*****************************************************************************\ | Trigger | \*****************************************************************************/ +enum { + RC_TRIGGER_STATE_INACTIVE, /* achievement is not being processed */ + RC_TRIGGER_STATE_WAITING, /* achievement cannot trigger until it has been false for at least one frame */ + 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 */ +}; + typedef struct { /* The main condition set. */ rc_condset_t* requirement; @@ -246,11 +268,24 @@ typedef struct { /* The memory references required by the trigger. */ rc_memref_value_t* memrefs; + + /* The current state of the MEASURED condition. */ + unsigned measured_value; + + /* The target state of the MEASURED condition */ + unsigned measured_target; + + /* The current state of the trigger */ + char state; + + /* True if at least one condition has a non-zero hit count */ + char has_hits; } rc_trigger_t; int rc_trigger_size(const char* memaddr); rc_trigger_t* rc_parse_trigger(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx); +int rc_evaluate_trigger(rc_trigger_t* trigger, rc_peek_t peek, void* ud, lua_State* L); int rc_test_trigger(rc_trigger_t* trigger, rc_peek_t peek, void* ud, lua_State* L); void rc_reset_trigger(rc_trigger_t* self); @@ -287,6 +322,9 @@ typedef struct { /* The list of expression to evaluate. */ rc_expression_t* expressions; + /* The list of conditions to evaluate. */ + rc_condset_t* conditions; + /* The memory references required by the value. */ rc_memref_value_t* memrefs; } @@ -294,7 +332,7 @@ rc_value_t; int rc_value_size(const char* memaddr); rc_value_t* rc_parse_value(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx); -unsigned rc_evaluate_value(rc_value_t* value, rc_peek_t peek, void* ud, lua_State* L); +int rc_evaluate_value(rc_value_t* value, rc_peek_t peek, void* ud, lua_State* L); /*****************************************************************************\ | Leaderboards | @@ -324,7 +362,7 @@ rc_lboard_t; int rc_lboard_size(const char* memaddr); rc_lboard_t* rc_parse_lboard(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx); -int rc_evaluate_lboard(rc_lboard_t* lboard, unsigned* value, rc_peek_t peek, void* peek_ud, lua_State* L); +int rc_evaluate_lboard(rc_lboard_t* lboard, int* value, rc_peek_t peek, void* peek_ud, lua_State* L); void rc_reset_lboard(rc_lboard_t* lboard); /*****************************************************************************\ @@ -338,11 +376,12 @@ enum { RC_FORMAT_CENTISECS, RC_FORMAT_SCORE, RC_FORMAT_VALUE, - RC_FORMAT_OTHER + RC_FORMAT_MINUTES, + RC_FORMAT_SECONDS_AS_MINUTES }; int rc_parse_format(const char* format_str); -int rc_format_value(char* buffer, int size, unsigned value, int format); +int rc_format_value(char* buffer, int size, int value, int format); /*****************************************************************************\ | Rich Presence | diff --git a/deps/rcheevos/include/rurl.h b/deps/rcheevos/include/rurl.h index 8c81050cc5..f21ecf2fe9 100644 --- a/deps/rcheevos/include/rurl.h +++ b/deps/rcheevos/include/rurl.h @@ -9,7 +9,7 @@ extern "C" { 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_submit_lboard(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned lboard_id, unsigned value, unsigned char hash[16]); +int rc_url_submit_lboard(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned lboard_id, int value, unsigned char hash[16]); int rc_url_get_gameid(char* buffer, size_t size, unsigned char hash[16]); diff --git a/deps/rcheevos/src/rcheevos/alloc.c b/deps/rcheevos/src/rcheevos/alloc.c index 56d8f8ac37..2730fa1ff1 100644 --- a/deps/rcheevos/src/rcheevos/alloc.c +++ b/deps/rcheevos/src/rcheevos/alloc.c @@ -44,6 +44,7 @@ void rc_init_parse_state(rc_parse_state_t* parse, void* buffer, lua_State* L, in parse->scratch.memref_size = sizeof(parse->scratch.memref_buffer) / sizeof(parse->scratch.memref_buffer[0]); parse->scratch.memref_count = 0; parse->first_memref = 0; + parse->measured_target = 0; } void rc_destroy_parse_state(rc_parse_state_t* parse) diff --git a/deps/rcheevos/src/rcheevos/condition.c b/deps/rcheevos/src/rcheevos/condition.c index efd172a6d2..7343e19564 100644 --- a/deps/rcheevos/src/rcheevos/condition.c +++ b/deps/rcheevos/src/rcheevos/condition.c @@ -2,7 +2,7 @@ #include -rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse) { +rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse, int is_indirect) { rc_condition_t* self; const char* aux; int ret2; @@ -19,6 +19,8 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse case 'b': case 'B': self->type = RC_CONDITION_SUB_SOURCE; break; case 'c': case 'C': self->type = RC_CONDITION_ADD_HITS; break; case 'n': case 'N': self->type = RC_CONDITION_AND_NEXT; break; + case 'm': case 'M': self->type = RC_CONDITION_MEASURED; break; + case 'i': case 'I': self->type = RC_CONDITION_ADD_ADDRESS; break; default: parse->offset = RC_INVALID_CONDITION_TYPE; return 0; } @@ -28,7 +30,7 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse self->type = RC_CONDITION_STANDARD; } - ret2 = rc_parse_operand(&self->operand1, &aux, 1, parse); + ret2 = rc_parse_operand(&self->operand1, &aux, 1, is_indirect, parse); if (ret2 < 0) { parse->offset = ret2; @@ -71,9 +73,19 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse } break; + + case '_': + case ')': + case '\0': + self->oper = RC_CONDITION_NONE; + self->operand2.type = RC_OPERAND_CONST; + self->operand2.value.num = 1; + self->required_hits = 0; + *memaddr = aux - 1; + return self; } - ret2 = rc_parse_operand(&self->operand2, &aux, 1, parse); + ret2 = rc_parse_operand(&self->operand2, &aux, 1, is_indirect, parse); if (ret2 < 0) { parse->offset = ret2; @@ -110,9 +122,9 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse return self; } -int rc_test_condition(rc_condition_t* self, unsigned add_buffer, rc_peek_t peek, void* ud, lua_State* L) { - unsigned value1 = rc_evaluate_operand(&self->operand1, peek, ud, L) + add_buffer; - unsigned value2 = rc_evaluate_operand(&self->operand2, peek, ud, L); +int rc_test_condition(rc_condition_t* self, rc_eval_state_t* eval_state) { + unsigned value1 = rc_evaluate_operand(&self->operand1, eval_state) + eval_state->add_value; + unsigned value2 = rc_evaluate_operand(&self->operand2, eval_state); switch (self->oper) { case RC_CONDITION_EQ: return value1 == value2; @@ -121,6 +133,7 @@ int rc_test_condition(rc_condition_t* self, unsigned add_buffer, rc_peek_t peek, 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; default: return 1; } } diff --git a/deps/rcheevos/src/rcheevos/condset.c b/deps/rcheevos/src/rcheevos/condset.c index c2d59a2691..a9ed1513f9 100644 --- a/deps/rcheevos/src/rcheevos/condset.c +++ b/deps/rcheevos/src/rcheevos/condset.c @@ -14,6 +14,7 @@ static void rc_update_condition_pause(rc_condition_t* condition, int* in_pause) case RC_CONDITION_SUB_SOURCE: case RC_CONDITION_ADD_HITS: case RC_CONDITION_AND_NEXT: + case RC_CONDITION_ADD_ADDRESS: condition->pause = *in_pause; break; @@ -27,9 +28,10 @@ rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse) { rc_condset_t* self; rc_condition_t** next; int in_pause; + int in_add_address; self = RC_ALLOC(rc_condset_t, parse); - self->has_pause = 0; + self->has_pause = self->is_paused = 0; next = &self->conditions; if (**memaddr == 'S' || **memaddr == 's' || !**memaddr) { @@ -38,14 +40,54 @@ rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse) { return self; } + in_add_address = 0; for (;;) { - *next = rc_parse_condition(memaddr, parse); + *next = rc_parse_condition(memaddr, parse, in_add_address); if (parse->offset < 0) { return 0; } + if ((*next)->oper == RC_CONDITION_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; + + default: + parse->offset = RC_INVALID_OPERATOR; + return 0; + } + } + self->has_pause |= (*next)->type == RC_CONDITION_PAUSE_IF; + in_add_address = (*next)->type == RC_CONDITION_ADD_ADDRESS; + + if ((*next)->type == RC_CONDITION_MEASURED) { + unsigned measured_target = 0; + if ((*next)->required_hits == 0) { + if ((*next)->operand2.type != RC_OPERAND_CONST) { + parse->offset = RC_INVALID_MEASURED_TARGET; + return 0; + } + + measured_target = (*next)->operand2.value.num; + } + else { + measured_target = (*next)->required_hits; + } + + if (parse->measured_target && measured_target != parse->measured_target) { + parse->offset = RC_MULTIPLE_MEASURED; + return 0; + } + + parse->measured_target = measured_target; + } + next = &(*next)->next; if (**memaddr != '_') { @@ -66,14 +108,13 @@ rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse) { return self; } -static int rc_test_condset_internal(rc_condset_t* self, int processing_pause, int* reset, rc_peek_t peek, void* ud, lua_State* L) { +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; - unsigned add_buffer, add_hits; set_valid = 1; prev_cond = 1; - add_buffer = add_hits = 0; + eval_state->add_value = eval_state->add_hits = eval_state->add_address = 0; for (condition = self->conditions; condition != 0; condition = condition->next) { if (condition->pause != processing_pause) { @@ -82,39 +123,60 @@ static int rc_test_condset_internal(rc_condset_t* self, int processing_pause, in switch (condition->type) { case RC_CONDITION_ADD_SOURCE: - add_buffer += rc_evaluate_operand(&condition->operand1, peek, ud, L); + eval_state->add_value += rc_evaluate_operand(&condition->operand1, eval_state); + eval_state->add_address = 0; continue; case RC_CONDITION_SUB_SOURCE: - add_buffer -= rc_evaluate_operand(&condition->operand1, peek, ud, L); + eval_state->add_value -= rc_evaluate_operand(&condition->operand1, eval_state); + eval_state->add_address = 0; continue; case RC_CONDITION_ADD_HITS: - if (rc_test_condition(condition, add_buffer, peek, ud, L)) { + /* 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; } - add_buffer = 0; - add_hits += condition->current_hits; + 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, add_buffer, peek, ud, L); - add_buffer = 0; + prev_cond &= rc_test_condition(condition, eval_state); + eval_state->add_value = 0; + eval_state->add_address = 0; + continue; + + case RC_CONDITION_ADD_ADDRESS: + eval_state->add_address = rc_evaluate_operand(&condition->operand1, eval_state); continue; } - /* always evaluate the condition to ensure delta values get tracked correctly */ - cond_valid = rc_test_condition(condition, add_buffer, peek, ud, L); + /* 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 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 + add_hits) >= condition->required_hits) { + if (condition->required_hits != 0 && (condition->current_hits + eval_state->add_hits) >= condition->required_hits) { cond_valid = 1; } else if (cond_valid) { @@ -123,14 +185,28 @@ static int rc_test_condset_internal(rc_condset_t* self, int processing_pause, in if (condition->required_hits == 0) { /* not a hit-based requirement: ignore any additional logic! */ } - else if ((condition->current_hits + add_hits) < condition->required_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; } } + condition->is_true = cond_valid; + eval_state->has_hits |= (condition->current_hits || eval_state->add_hits); + + /* capture measured state */ + if (condition->type == RC_CONDITION_MEASURED) { + unsigned int measured_value; + if (condition->required_hits > 0) + measured_value = condition->current_hits + eval_state->add_hits; + else + measured_value = rc_evaluate_operand(&condition->operand1, eval_state) + eval_state->add_value; + + if (measured_value > eval_state->measured_value) + eval_state->measured_value = measured_value; + } /* reset AddHits and AddSource/SubSource values */ - add_buffer = add_hits = 0; + eval_state->add_value = eval_state->add_hits = eval_state->add_address = 0; switch (condition->type) { case RC_CONDITION_PAUSE_IF: @@ -155,7 +231,7 @@ static int rc_test_condset_internal(rc_condset_t* self, int processing_pause, in case RC_CONDITION_RESET_IF: if (cond_valid) { - *reset = 1; /* let caller know to reset all hit counts */ + 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 */ } @@ -170,18 +246,20 @@ static int rc_test_condset_internal(rc_condset_t* self, int processing_pause, in return set_valid; } -int rc_test_condset(rc_condset_t* self, int* reset, rc_peek_t peek, void* ud, lua_State* L) { +int rc_test_condset(rc_condset_t* self, rc_eval_state_t* eval_state) { if (self->conditions == 0) { /* important: empty group must evaluate true */ return 1; } - if (self->has_pause && rc_test_condset_internal(self, 1, reset, peek, ud, L)) { - /* one or more Pause conditions exists, if any of them are true, stop processing this group */ - return 0; + 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 */ + return 0; + } } - return rc_test_condset_internal(self, 0, reset, peek, ud, L); + return rc_test_condset_internal(self, 0, eval_state); } void rc_reset_condset(rc_condset_t* self) { diff --git a/deps/rcheevos/src/rcheevos/expression.c b/deps/rcheevos/src/rcheevos/expression.c index f2fca1e863..0adbc03a4f 100644 --- a/deps/rcheevos/src/rcheevos/expression.c +++ b/deps/rcheevos/src/rcheevos/expression.c @@ -8,7 +8,7 @@ rc_expression_t* rc_parse_expression(const char** memaddr, rc_parse_state_t* par next = &self->terms; for (;;) { - *next = rc_parse_term(memaddr, parse); + *next = rc_parse_term(memaddr, 0, parse); if (parse->offset < 0) { return 0; @@ -27,14 +27,14 @@ rc_expression_t* rc_parse_expression(const char** memaddr, rc_parse_state_t* par return self; } -unsigned rc_evaluate_expression(rc_expression_t* self, rc_peek_t peek, void* ud, lua_State* L) { +int rc_evaluate_expression(rc_expression_t* self, rc_eval_state_t* eval_state) { rc_term_t* term; - unsigned value; + int value; value = 0; for (term = self->terms; term != 0; term = term->next) { - value += rc_evaluate_term(term, peek, ud, L); + value += rc_evaluate_term(term, eval_state); } return value; diff --git a/deps/rcheevos/src/rcheevos/format.c b/deps/rcheevos/src/rcheevos/format.c index e03ced8070..33dddda41d 100644 --- a/deps/rcheevos/src/rcheevos/format.c +++ b/deps/rcheevos/src/rcheevos/format.c @@ -29,6 +29,9 @@ int rc_parse_format(const char* format_str) { if (!strcmp(format_str, "CORE")) { return RC_FORMAT_SCORE; } + if (!strcmp(format_str, "ECS_AS_MINS")) { + return RC_FORMAT_SECONDS_AS_MINUTES; + } break; @@ -36,6 +39,9 @@ int rc_parse_format(const char* format_str) { if (!strcmp(format_str, "ILLISECS")) { return RC_FORMAT_CENTISECS; } + if (!strcmp(format_str, "INUTES")) { + return RC_FORMAT_MINUTES; + } break; @@ -55,7 +61,7 @@ int rc_parse_format(const char* format_str) { case 'O': if (!strcmp(format_str, "THER")) { - return RC_FORMAT_OTHER; + return RC_FORMAT_SCORE; } break; @@ -64,45 +70,82 @@ int rc_parse_format(const char* format_str) { return RC_FORMAT_VALUE; } -int rc_format_value(char* buffer, int size, unsigned value, int format) { - unsigned a, b, c; +static int rc_format_value_minutes(char* buffer, int size, unsigned minutes) { + unsigned hours; + + hours = minutes / 60; + minutes -= hours * 60; + return snprintf(buffer, size, "%uh%02u", hours, minutes); +} + +static int rc_format_value_seconds(char* buffer, int size, unsigned seconds) { + unsigned hours, minutes; + + /* apply modulus math to split the seconds into hours/minutes/seconds */ + minutes = seconds / 60; + seconds -= minutes * 60; + if (minutes < 60) { + return snprintf(buffer, size, "%u:%02u", minutes, seconds); + } + + hours = minutes / 60; + minutes -= hours * 60; + return snprintf(buffer, size, "%uh%02u:%02u", hours, minutes, seconds); +} + +static int rc_format_value_centiseconds(char* buffer, int size, unsigned centiseconds) { + unsigned seconds; + int chars, chars2; + + /* modulus off the centiseconds */ + seconds = centiseconds / 100; + centiseconds -= seconds * 100; + + chars = rc_format_value_seconds(buffer, size, seconds); + if (chars > 0) { + chars2 = snprintf(buffer + chars, size - chars, ".%02u", centiseconds); + if (chars2 > 0) { + chars += chars2; + } else { + chars = chars2; + } + } + + return chars; +} + +int rc_format_value(char* buffer, int size, int value, int format) { int chars; switch (format) { case RC_FORMAT_FRAMES: - a = value * 10 / 6; /* centisecs */ - b = a / 100; /* seconds */ - a -= b * 100; - c = b / 60; /* minutes */ - b -= c * 60; - chars = snprintf(buffer, size, "%02u:%02u.%02u", c, b, a); + /* 60 frames per second = 100 centiseconds / 60 frames; multiply frames by 100 / 60 */ + chars = rc_format_value_centiseconds(buffer, size, value * 10 / 6); break; case RC_FORMAT_SECONDS: - a = value / 60; /* minutes */ - value -= a * 60; - chars = snprintf(buffer, size, "%02u:%02u", a, value); + chars = rc_format_value_seconds(buffer, size, value); break; case RC_FORMAT_CENTISECS: - a = value / 100; /* seconds */ - value -= a * 100; - b = a / 60; /* minutes */ - a -= b * 60; - chars = snprintf(buffer, size, "%02u:%02u.%02u", b, a, value); + chars = rc_format_value_centiseconds(buffer, size, value); + break; + + case RC_FORMAT_SECONDS_AS_MINUTES: + chars = rc_format_value_minutes(buffer, size, value / 60); + break; + + case RC_FORMAT_MINUTES: + chars = rc_format_value_minutes(buffer, size, value); break; case RC_FORMAT_SCORE: - chars = snprintf(buffer, size, "%06u Points", value); + chars = snprintf(buffer, size, "%06d", value); break; - case RC_FORMAT_VALUE: - chars = snprintf(buffer, size, "%01u", value); - break; - - case RC_FORMAT_OTHER: default: - chars = snprintf(buffer, size, "%06u", value); + case RC_FORMAT_VALUE: + chars = snprintf(buffer, size, "%d", value); break; } diff --git a/deps/rcheevos/src/rcheevos/internal.h b/deps/rcheevos/src/rcheevos/internal.h index c770984d78..72733183c3 100644 --- a/deps/rcheevos/src/rcheevos/internal.h +++ b/deps/rcheevos/src/rcheevos/internal.h @@ -1,8 +1,7 @@ #ifndef INTERNAL_H #define INTERNAL_H -#include -#include +#include "rcheevos.h" #define RC_ALLOW_ALIGN(T) struct __align_ ## T { char ch; T t; }; RC_ALLOW_ALIGN(rc_condition_t) @@ -21,8 +20,6 @@ RC_ALLOW_ALIGN(rc_trigger_t) RC_ALLOW_ALIGN(rc_value_t) RC_ALLOW_ALIGN(char) -#define RC_TAG2(x,y) x ## y -#define RC_TAG(x,y) RC_TAG2(x,y) #define RC_ALIGNOF(T) (sizeof(struct __align_ ## T) - sizeof(T)) #define RC_ALLOC(t, p) ((t*)rc_alloc((p)->buffer, &(p)->offset, sizeof(t), RC_ALIGNOF(t), &(p)->scratch)) @@ -52,6 +49,21 @@ typedef struct { } rc_scratch_t; +typedef struct { + unsigned add_value; /* AddSource/SubSource */ + unsigned add_hits; /* AddHits */ + unsigned add_address; /* AddAddress */ + + rc_peek_t peek; + void* peek_userdata; + lua_State* L; + + unsigned measured_value; /* Measured */ + char was_reset; /* ResetIf triggered */ + char has_hits; /* one of more hit counts is non-zero */ +} +rc_eval_state_t; + typedef struct { int offset; @@ -62,6 +74,8 @@ typedef struct { rc_scratch_t scratch; rc_memref_value_t** first_memref; + + unsigned measured_target; } rc_parse_state_t; @@ -72,26 +86,28 @@ 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); +rc_memref_value_t* rc_alloc_memref_value(rc_parse_state_t* parse, unsigned address, char size, char is_bcd, 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); void rc_parse_trigger_internal(rc_trigger_t* self, const char** memaddr, rc_parse_state_t* parse); rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse); -int rc_test_condset(rc_condset_t* self, int* reset, rc_peek_t peek, void* ud, lua_State* L); +int rc_test_condset(rc_condset_t* self, rc_eval_state_t* eval_state); void rc_reset_condset(rc_condset_t* self); -rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse); -int rc_test_condition(rc_condition_t* self, unsigned add_buffer, rc_peek_t peek, void* ud, lua_State* L); +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_parse_operand(rc_operand_t* self, const char** memaddr, int is_trigger, rc_parse_state_t* parse); -unsigned rc_evaluate_operand(rc_operand_t* self, rc_peek_t peek, void* ud, lua_State* L); +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, rc_parse_state_t* parse); -unsigned rc_evaluate_term(rc_term_t* self, rc_peek_t peek, void* ud, lua_State* L); +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); -unsigned rc_evaluate_expression(rc_expression_t* self, rc_peek_t peek, void* ud, lua_State* L); +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); diff --git a/deps/rcheevos/src/rcheevos/lboard.c b/deps/rcheevos/src/rcheevos/lboard.c index a0e8334d38..11628fff71 100644 --- a/deps/rcheevos/src/rcheevos/lboard.c +++ b/deps/rcheevos/src/rcheevos/lboard.c @@ -162,7 +162,7 @@ rc_lboard_t* rc_parse_lboard(void* buffer, const char* memaddr, lua_State* L, in return parse.offset >= 0 ? self : 0; } -int rc_evaluate_lboard(rc_lboard_t* self, unsigned* value, rc_peek_t peek, void* peek_ud, lua_State* L) { +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; @@ -217,8 +217,11 @@ int rc_evaluate_lboard(rc_lboard_t* self, unsigned* value, rc_peek_t peek, void* /* Calculate the value */ switch (action) { - case RC_LBOARD_ACTIVE: /* fall through */ 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; diff --git a/deps/rcheevos/src/rcheevos/memref.c b/deps/rcheevos/src/rcheevos/memref.c index 01ab2692f8..a7c24c7446 100644 --- a/deps/rcheevos/src/rcheevos/memref.c +++ b/deps/rcheevos/src/rcheevos/memref.c @@ -3,75 +3,101 @@ #include /* malloc/realloc */ #include /* memcpy */ -rc_memref_value_t* rc_alloc_memref_value(rc_parse_state_t* parse, unsigned address, char size, char is_bcd) { - rc_memref_value_t** next_memref_value; - rc_memref_value_t* memref_value; +#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) { rc_memref_t* memref; int i; - if (!parse->first_memref) { - /* sizing mode - have to track unique address/size/bcd combinations */ - 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) { - return &parse->scratch.obj.memref_value; - } - } - - /* resize unique tracking buffer if necessary */ - if (parse->scratch.memref_count == parse->scratch.memref_size) { - if (parse->scratch.memref == parse->scratch.memref_buffer) { - parse->scratch.memref_size += 16; - memref = (rc_memref_t*)malloc(parse->scratch.memref_size * sizeof(parse->scratch.memref_buffer[0])); - if (memref) { - parse->scratch.memref = memref; - memcpy(memref, parse->scratch.memref_buffer, parse->scratch.memref_count * sizeof(parse->scratch.memref_buffer[0])); - } - else { - parse->offset = RC_OUT_OF_MEMORY; - return 0; - } - } - else { - parse->scratch.memref_size += 32; - memref = (rc_memref_t*)realloc(parse->scratch.memref, parse->scratch.memref_size * sizeof(parse->scratch.memref_buffer[0])); - if (memref) { - parse->scratch.memref = memref; - } - else { - parse->offset = RC_OUT_OF_MEMORY; - return 0; - } - } - } - - /* add new unique tracking entry */ - if (parse->scratch.memref) { - memref = &parse->scratch.memref[parse->scratch.memref_count++]; - memref->address = address; - memref->size = size; - memref->is_bcd = is_bcd; - } - - /* allocate memory but don't actually populate, as it might overwrite the self object referencing the rc_memref_value_t */ + /* indirect address always creates two new entries; don't bother tracking them */ + if (is_indirect) { + RC_ALLOC(rc_memref_value_t, parse); return RC_ALLOC(rc_memref_value_t, parse); - } - - /* construction mode - find or create the appropriate rc_memref_value_t */ - next_memref_value = parse->first_memref; - while (*next_memref_value) { - memref_value = *next_memref_value; - if (memref_value->memref.address == address && memref_value->memref.size == size && memref_value->memref.is_bcd == is_bcd) { - return memref_value; - } - - next_memref_value = &memref_value->next; } + memref = NULL; + + /* 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) { + return &parse->scratch.obj.memref_value; + } + } + + /* no match found - resize unique tracking buffer if necessary */ + if (parse->scratch.memref_count == parse->scratch.memref_size) { + if (parse->scratch.memref == parse->scratch.memref_buffer) { + parse->scratch.memref_size += 16; + memref = (rc_memref_t*)malloc(parse->scratch.memref_size * sizeof(parse->scratch.memref_buffer[0])); + if (memref) { + parse->scratch.memref = memref; + memcpy(memref, parse->scratch.memref_buffer, parse->scratch.memref_count * sizeof(parse->scratch.memref_buffer[0])); + } + else { + parse->offset = RC_OUT_OF_MEMORY; + return 0; + } + } + else { + parse->scratch.memref_size += 32; + memref = (rc_memref_t*)realloc(parse->scratch.memref, parse->scratch.memref_size * sizeof(parse->scratch.memref_buffer[0])); + if (memref) { + parse->scratch.memref = memref; + } + else { + parse->offset = RC_OUT_OF_MEMORY; + return 0; + } + } + } + + /* add new unique tracking entry */ + if (parse->scratch.memref) { + memref = &parse->scratch.memref[parse->scratch.memref_count++]; + memref->address = address; + memref->size = size; + memref->is_bcd = is_bcd; + memref->is_indirect = is_indirect; + } + + /* allocate memory but don't actually populate, as it might overwrite the self object referencing the rc_memref_value_t */ + 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) { + rc_memref_value_t** next_memref_value; + rc_memref_value_t* memref_value; + rc_memref_value_t* indirect_memref_value; + + if (!is_indirect) { + /* attempt to find an existing rc_memref_value_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) { + return memref_value; + } + + next_memref_value = &memref_value->next; + } + } + else { + /* indirect address always creates two new entries - one for the original address, and one for + the indirect dereference - just skip ahead to the end of the list */ + next_memref_value = parse->first_memref; + while (*next_memref_value) { + next_memref_value = &(*next_memref_value)->next; + } + } + + /* no match found, create a new entry */ 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; memref_value->prior = 0; @@ -79,9 +105,31 @@ rc_memref_value_t* rc_alloc_memref_value(rc_parse_state_t* parse, unsigned addre *next_memref_value = memref_value; + /* also create the indirect deference entry for indirect references */ + if (is_indirect) { + 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; + indirect_memref_value->prior = 0; + indirect_memref_value->next = 0; + + memref_value->next = indirect_memref_value; + } + 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) { + if (!parse->first_memref) + return rc_alloc_memref_value_sizing_mode(parse, address, size, is_bcd, is_indirect); + + return rc_alloc_memref_value_constuct_mode(parse, address, size, is_bcd, is_indirect); +} + static unsigned rc_memref_get_value(rc_memref_t* self, rc_peek_t peek, void* ud) { unsigned value; @@ -149,6 +197,7 @@ static unsigned rc_memref_get_value(rc_memref_t* self, rc_peek_t peek, void* ud) 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) { @@ -158,6 +207,8 @@ static unsigned rc_memref_get_value(rc_memref_t* self, rc_peek_t peek, void* ud) + ((value >> 8) & 0x0f) * 100 + ((value >> 4) & 0x0f) * 10 + ((value >> 0) & 0x0f) * 1; + } else { + value &= 0x00FFFFFF; } break; @@ -186,19 +237,45 @@ static unsigned rc_memref_get_value(rc_memref_t* self, rc_peek_t peek, void* ud) return value; } +void rc_update_memref_value(rc_memref_value_t* memref, rc_peek_t peek, void* ud) { + memref->previous = memref->value; + memref->value = rc_memref_get_value(&memref->memref, peek, ud); + if (memref->value != memref->previous) + memref->prior = memref->previous; +} + void rc_update_memref_values(rc_memref_value_t* memref, rc_peek_t peek, void* ud) { while (memref) { - memref->previous = memref->value; - memref->value = rc_memref_get_value(&memref->memref, peek, ud); - if (memref->value != memref->previous) - memref->prior = memref->previous; - + if (memref->memref.address != MEMREF_PLACEHOLDER_ADDRESS) + rc_update_memref_value(memref, peek, ud); memref = memref->next; } } -void rc_init_parse_state_memrefs(rc_parse_state_t* parse, rc_memref_value_t** memrefs) -{ +void rc_init_parse_state_memrefs(rc_parse_state_t* parse, rc_memref_value_t** memrefs) { parse->first_memref = memrefs; *memrefs = 0; } + +rc_memref_value_t* rc_get_indirect_memref(rc_memref_value_t* memref, rc_eval_state_t* eval_state) { + unsigned new_address; + + if (eval_state->add_address == 0) + return memref; + + if (!memref->memref.is_indirect) + return memref; + + new_address = memref->memref.address + eval_state->add_address; + + /* an extra rc_memref_value_t is allocated for offset calculations */ + memref = memref->next; + + /* if the adjusted address has changed, update the record */ + if (memref->memref.address != new_address) { + memref->memref.address = new_address; + rc_update_memref_value(memref, eval_state->peek, eval_state->peek_userdata); + } + + return memref; +} diff --git a/deps/rcheevos/src/rcheevos/operand.c b/deps/rcheevos/src/rcheevos/operand.c index d62081a094..39f80c4f98 100644 --- a/deps/rcheevos/src/rcheevos/operand.c +++ b/deps/rcheevos/src/rcheevos/operand.c @@ -66,7 +66,7 @@ static int rc_parse_operand_lua(rc_operand_t* self, const char** memaddr, rc_par return RC_OK; } -static int rc_parse_operand_memory(rc_operand_t* self, const char** memaddr, rc_parse_state_t* parse) { +static int rc_parse_operand_memory(rc_operand_t* self, const char** memaddr, rc_parse_state_t* parse, int is_indirect) { const char* aux = *memaddr; char* end; unsigned long address; @@ -135,7 +135,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, (unsigned)address, size, is_bcd); + self->value.memref = rc_alloc_memref_value(parse, address, size, is_bcd, is_indirect); if (parse->offset < 0) return parse->offset; @@ -143,7 +143,7 @@ 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, rc_parse_state_t* parse) { +static int rc_parse_operand_trigger(rc_operand_t* self, const char** memaddr, int is_indirect, rc_parse_state_t* parse) { const char* aux = *memaddr; char* end; int ret; @@ -151,6 +151,11 @@ static int rc_parse_operand_trigger(rc_operand_t* self, const char** memaddr, rc switch (*aux) { case 'h': case 'H': + if (aux[2] == 'x' || aux[2] == 'X') { + /* H0x1234 is a typo - either H1234 or 0xH1234 was probably meant */ + return RC_INVALID_CONST_OPERAND; + } + value = strtoul(++aux, &end, 16); if (end == aux) { @@ -171,7 +176,7 @@ static int rc_parse_operand_trigger(rc_operand_t* self, const char** memaddr, rc if (aux[1] == 'x' || aux[1] == 'X') { /* fall through */ default: - ret = rc_parse_operand_memory(self, &aux, parse); + ret = rc_parse_operand_memory(self, &aux, parse, is_indirect); if (ret < 0) { return ret; @@ -214,11 +219,12 @@ static int rc_parse_operand_trigger(rc_operand_t* self, const char** memaddr, rc return RC_OK; } -static int rc_parse_operand_term(rc_operand_t* self, const char** memaddr, rc_parse_state_t* parse) { +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': @@ -239,18 +245,18 @@ static int rc_parse_operand_term(rc_operand_t* self, const char** memaddr, rc_pa break; case 'v': case 'V': - value = strtoul(++aux, &end, 10); + svalue = strtol(++aux, &end, 10); if (end == aux) { return RC_INVALID_CONST_OPERAND; } - if (value > 0xffffffffU) { - value = 0xffffffffU; + if (svalue > 0xffffffffU) { + svalue = 0xffffffffU; } self->type = RC_OPERAND_CONST; - self->value.num = (unsigned)value; + self->value.num = (unsigned)svalue; aux = end; break; @@ -259,7 +265,7 @@ static int rc_parse_operand_term(rc_operand_t* self, const char** memaddr, rc_pa if (aux[1] == 'x' || aux[1] == 'X') { /* fall through */ default: - ret = rc_parse_operand_memory(self, &aux, parse); + ret = rc_parse_operand_memory(self, &aux, parse, is_indirect); if (ret < 0) { return ret; @@ -303,12 +309,12 @@ static int rc_parse_operand_term(rc_operand_t* self, const char** memaddr, rc_pa return RC_OK; } -int rc_parse_operand(rc_operand_t* self, const char** memaddr, int is_trigger, 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) { if (is_trigger) { - return rc_parse_operand_trigger(self, memaddr, parse); + return rc_parse_operand_trigger(self, memaddr, is_indirect, parse); } else { - return rc_parse_operand_term(self, memaddr, parse); + return rc_parse_operand_term(self, memaddr, is_indirect, parse); } } @@ -333,7 +339,7 @@ static int rc_luapeek(lua_State* L) { #endif /* RC_DISABLE_LUA */ -unsigned rc_evaluate_operand(rc_operand_t* self, rc_peek_t peek, void* ud, lua_State* L) { +unsigned rc_evaluate_operand(rc_operand_t* self, rc_eval_state_t* eval_state) { #ifndef RC_DISABLE_LUA rc_luapeek_t luapeek; #endif /* RC_DISABLE_LUA */ @@ -352,25 +358,25 @@ unsigned rc_evaluate_operand(rc_operand_t* self, rc_peek_t peek, void* ud, lua_S case RC_OPERAND_LUA: #ifndef RC_DISABLE_LUA - if (L != 0) { - lua_rawgeti(L, LUA_REGISTRYINDEX, self->value.luafunc); - lua_pushcfunction(L, rc_luapeek); + if (eval_state->L != 0) { + lua_rawgeti(eval_state->L, LUA_REGISTRYINDEX, self->value.luafunc); + lua_pushcfunction(eval_state->L, rc_luapeek); - luapeek.peek = peek; - luapeek.ud = ud; + luapeek.peek = eval_state->peek; + luapeek.ud = eval_state->peek_userdata; - lua_pushlightuserdata(L, &luapeek); + lua_pushlightuserdata(eval_state->L, &luapeek); - if (lua_pcall(L, 2, 1, 0) == LUA_OK) { - if (lua_isboolean(L, -1)) { - value = lua_toboolean(L, -1); + 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); } else { - value = (unsigned)lua_tonumber(L, -1); + value = (unsigned)lua_tonumber(eval_state->L, -1); } } - lua_pop(L, 1); + lua_pop(eval_state->L, 1); } #endif /* RC_DISABLE_LUA */ @@ -378,15 +384,15 @@ unsigned rc_evaluate_operand(rc_operand_t* self, rc_peek_t peek, void* ud, lua_S break; case RC_OPERAND_ADDRESS: - value = self->value.memref->value; + value = rc_get_indirect_memref(self->value.memref, eval_state)->value; break; case RC_OPERAND_DELTA: - value = self->value.memref->previous; + value = rc_get_indirect_memref(self->value.memref, eval_state)->previous; break; case RC_OPERAND_PRIOR: - value = self->value.memref->prior; + value = rc_get_indirect_memref(self->value.memref, eval_state)->prior; break; } diff --git a/deps/rcheevos/src/rcheevos/richpresence.c b/deps/rcheevos/src/rcheevos/richpresence.c index 1ac64a56c4..0e8b4e70df 100644 --- a/deps/rcheevos/src/rcheevos/richpresence.c +++ b/deps/rcheevos/src/rcheevos/richpresence.c @@ -51,8 +51,10 @@ static rc_richpresence_display_t* rc_parse_richpresence_display_internal(const c const char* in; char* out; - if (endline - line < 1) + if (endline - line < 1) { + parse->offset = RC_MISSING_DISPLAY_STRING; return 0; + } { self = RC_ALLOC(rc_richpresence_display_t, parse); @@ -103,6 +105,11 @@ static rc_richpresence_display_t* rc_parse_richpresence_display_internal(const c while (ptr < endline && *ptr != '(') ++ptr; + if (ptr == endline) { + parse->offset = RC_MISSING_VALUE; + return 0; + } + if (ptr > line) { if (!parse->buffer) { /* just calculating size, can't confirm lookup exists */ @@ -179,6 +186,7 @@ static const char* rc_parse_richpresence_lookup(rc_richpresence_lookup_t* lookup const char* line; const char* endline; const char* defaultlabel = 0; + char* endptr = 0; unsigned key; int chars; @@ -208,9 +216,14 @@ static const char* rc_parse_richpresence_lookup(rc_richpresence_lookup_t* lookup } if (number[0] == '0' && number[1] == 'x') - key = (unsigned)strtoul(&number[2], 0, 16); + key = strtoul(&number[2], &endptr, 16); else - key = (unsigned)strtoul(&number[0], 0, 10); + key = strtoul(&number[0], &endptr, 10); + + if (*endptr && !isspace(*endptr)) { + parse->offset = RC_INVALID_CONST_OPERAND; + return nextline; + } item = RC_ALLOC(rc_richpresence_lookup_item_t, parse); item->value = key; @@ -263,6 +276,8 @@ void rc_parse_richpresence_internal(rc_richpresence_t* self, const char* script, nextlookup = &lookup->next; nextline = rc_parse_richpresence_lookup(lookup, nextline, parse); + if (parse->offset < 0) + return; } else if (strncmp(line, "Format:", 7) == 0) { line += 7; @@ -316,6 +331,8 @@ void rc_parse_richpresence_internal(rc_richpresence_t* self, const char* script, if (ptr < endline) { *nextdisplay = rc_parse_richpresence_display_internal(ptr + 1, endline, parse, self); + if (parse->offset < 0) + return; trigger = &((*nextdisplay)->trigger); rc_parse_trigger_internal(trigger, &line, parse); trigger->memrefs = 0; diff --git a/deps/rcheevos/src/rcheevos/term.c b/deps/rcheevos/src/rcheevos/term.c index 3356a1ee43..266dc0570c 100644 --- a/deps/rcheevos/src/rcheevos/term.c +++ b/deps/rcheevos/src/rcheevos/term.c @@ -1,6 +1,6 @@ #include "internal.h" -rc_term_t* rc_parse_term(const char** memaddr, rc_parse_state_t* parse) { +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; @@ -10,7 +10,7 @@ rc_term_t* rc_parse_term(const char** memaddr, rc_parse_state_t* parse) { self = RC_ALLOC(rc_term_t, parse); self->invert = 0; - ret2 = rc_parse_operand(&self->operand1, &aux, 0, parse); + ret2 = rc_parse_operand(&self->operand1, &aux, 0, is_indirect, parse); if (ret2 < 0) { parse->offset = ret2; @@ -25,7 +25,7 @@ rc_term_t* rc_parse_term(const char** memaddr, rc_parse_state_t* parse) { self->invert = 1; } - ret2 = rc_parse_operand(&self->operand2, &aux, 0, parse); + ret2 = rc_parse_operand(&self->operand2, &aux, 0, is_indirect, parse); if (ret2 < 0) { parse->offset = ret2; @@ -88,12 +88,16 @@ rc_term_t* rc_parse_term(const char** memaddr, rc_parse_state_t* parse) { return self; } -unsigned rc_evaluate_term(rc_term_t* self, rc_peek_t peek, void* ud, lua_State* L) { - unsigned value = rc_evaluate_operand(&self->operand1, peek, ud, L); +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 value * (rc_evaluate_operand(&self->operand2, peek, ud, L) ^ self->invert); + return (int)(value * (rc_evaluate_operand(&self->operand2, eval_state) ^ self->invert)); } - return (unsigned)((double)value * self->operand2.value.dbl); + return (int)((double)value * self->operand2.value.dbl); } diff --git a/deps/rcheevos/src/rcheevos/trigger.c b/deps/rcheevos/src/rcheevos/trigger.c index 90c3388164..0e76e031bb 100644 --- a/deps/rcheevos/src/rcheevos/trigger.c +++ b/deps/rcheevos/src/rcheevos/trigger.c @@ -1,6 +1,7 @@ #include "internal.h" #include +#include void rc_parse_trigger_internal(rc_trigger_t* self, const char** memaddr, rc_parse_state_t* parse) { rc_condset_t** next; @@ -35,6 +36,11 @@ void rc_parse_trigger_internal(rc_trigger_t* self, const char** memaddr, rc_pars *next = 0; *memaddr = aux; + + self->measured_value = 0; + self->measured_target = parse->measured_target; + self->state = RC_TRIGGER_STATE_WAITING; + self->has_hits = 0; } int rc_trigger_size(const char* memaddr) { @@ -63,36 +69,7 @@ rc_trigger_t* rc_parse_trigger(void* buffer, const char* memaddr, lua_State* L, return parse.offset >= 0 ? self : 0; } -int rc_test_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, lua_State* L) { - int ret, reset; - rc_condset_t* condset; - - rc_update_memref_values(self->memrefs, peek, ud); - - reset = 0; - ret = self->requirement != 0 ? rc_test_condset(self->requirement, &reset, peek, ud, L) : 1; - condset = self->alternative; - - if (condset) { - int sub = 0; - - do { - sub |= rc_test_condset(condset, &reset, peek, ud, L); - condset = condset->next; - } - while (condset != 0); - - ret &= sub && !reset; - } - - if (reset) { - rc_reset_trigger(self); - } - - return ret; -} - -void rc_reset_trigger(rc_trigger_t* self) { +static void rc_reset_trigger_hitcounts(rc_trigger_t* self) { rc_condset_t* condset; if (self->requirement != 0) { @@ -106,3 +83,102 @@ void rc_reset_trigger(rc_trigger_t* self) { condset = condset->next; } } + +int rc_evaluate_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, lua_State* L) { + rc_eval_state_t eval_state; + rc_condset_t* condset; + int ret; + char is_paused; + + /* 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; + + rc_update_memref_values(self->memrefs, peek, ud); + + /* not yet active, only update the memrefs - so deltas are corrent when it becomes active */ + if (self->state == RC_TRIGGER_STATE_INACTIVE) + return RC_TRIGGER_STATE_INACTIVE; + + /* process the trigger */ + memset(&eval_state, 0, sizeof(eval_state)); + eval_state.peek = peek; + 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 (condset) { + int sub = 0; + + do { + sub |= rc_test_condset(condset, &eval_state); + condset = condset->next; + } + while (condset != 0); + + ret &= sub; + } + + 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 */ + if (self->state == RC_TRIGGER_STATE_WAITING && ret) { + rc_reset_trigger(self); + self->has_hits = 0; + return RC_TRIGGER_STATE_WAITING; + } + + if (eval_state.was_reset) { + /* if any ResetIf condition was true, reset the hit counts */ + rc_reset_trigger_hitcounts(self); + + /* if there were hit counts to clear, return RESET, but don't change the state */ + if (self->has_hits) { + self->has_hits = 0; + return RC_TRIGGER_STATE_RESET; + } + + /* any hits that were tallied were just reset */ + eval_state.has_hits = 0; + } + else if (ret) { + /* trigger was triggered */ + self->state = RC_TRIGGER_STATE_TRIGGERED; + return RC_TRIGGER_STATE_TRIGGERED; + } + + /* 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; + } + } + } + + self->state = is_paused ? RC_TRIGGER_STATE_PAUSED : RC_TRIGGER_STATE_ACTIVE; + return self->state; +} + +int rc_test_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, lua_State* L) { + /* for backwards compatibilty, rc_test_trigger always assumes the achievement is active */ + self->state = RC_TRIGGER_STATE_ACTIVE; + + return (rc_evaluate_trigger(self, peek, ud, L) == RC_TRIGGER_STATE_TRIGGERED); +} + +void rc_reset_trigger(rc_trigger_t* self) { + rc_reset_trigger_hitcounts(self); + + self->state = RC_TRIGGER_STATE_WAITING; +} diff --git a/deps/rcheevos/src/rcheevos/value.c b/deps/rcheevos/src/rcheevos/value.c index 7320414a3e..b9a7db49a5 100644 --- a/deps/rcheevos/src/rcheevos/value.c +++ b/deps/rcheevos/src/rcheevos/value.c @@ -1,8 +1,88 @@ #include "internal.h" +#include + +static void rc_parse_cond_value(rc_value_t* self, const char** memaddr, rc_parse_state_t* parse) { + rc_condition_t** next; + int has_measured; + int in_add_address; + + 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 + * 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; + for (;;) { + *next = rc_parse_condition(memaddr, parse, in_add_address); + + if (parse->offset < 0) { + return; + } + + in_add_address = (*next)->type == RC_CONDITION_ADD_ADDRESS; + + switch ((*next)->type) { + case RC_CONDITION_ADD_HITS: + case RC_CONDITION_ADD_SOURCE: + case RC_CONDITION_SUB_SOURCE: + case RC_CONDITION_AND_NEXT: + case RC_CONDITION_ADD_ADDRESS: + /* combining flags are allowed */ + break; + + case RC_CONDITION_RESET_IF: + /* ResetIf is allowed (primarily for rich presense - leaderboard will typically cancel instead of resetting) */ + break; + + case RC_CONDITION_MEASURED: + if (has_measured) { + parse->offset = RC_MULTIPLE_MEASURED; + return; + } + has_measured = 1; + if ((*next)->required_hits == 0 && (*next)->oper != RC_CONDITION_NONE) + (*next)->required_hits = (unsigned)-1; + break; + + default: + /* non-combinding flags and PauseIf are not allowed */ + parse->offset = RC_INVALID_VALUE_FLAG; + return; + } + + (*next)->pause = 0; + next = &(*next)->next; + + if (**memaddr != '_') { + break; + } + + (*memaddr)++; + } + + *next = 0; + + if (!has_measured) { + parse->offset = RC_MISSING_VALUE_MEASURED; + } +} + void rc_parse_value_internal(rc_value_t* self, const char** memaddr, rc_parse_state_t* parse) { rc_expression_t** next; + /* 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; + } + + self->conditions = 0; next = &self->expressions; for (;;) { @@ -50,17 +130,15 @@ rc_value_t* rc_parse_value(void* buffer, const char* memaddr, lua_State* L, int return parse.offset >= 0 ? self : 0; } -unsigned rc_evaluate_value(rc_value_t* self, rc_peek_t peek, void* ud, lua_State* L) { +static int rc_evaluate_expr_value(rc_value_t* self, rc_eval_state_t* eval_state) { rc_expression_t* exp; - unsigned value, max; - - rc_update_memref_values(self->memrefs, peek, ud); + int value, max; exp = self->expressions; - max = rc_evaluate_expression(exp, peek, ud, L); + max = rc_evaluate_expression(exp, eval_state); for (exp = exp->next; exp != 0; exp = exp->next) { - value = rc_evaluate_expression(exp, peek, ud, L); + value = rc_evaluate_expression(exp, eval_state); if (value > max) { max = value; @@ -69,3 +147,20 @@ unsigned rc_evaluate_value(rc_value_t* self, rc_peek_t peek, void* ud, lua_State 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; + memset(&eval_state, 0, sizeof(eval_state)); + eval_state.peek = peek; + eval_state.peek_userdata = ud; + eval_state.L = 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); + return (int)eval_state.measured_value; +} diff --git a/deps/rcheevos/src/rurl/url.c b/deps/rcheevos/src/rurl/url.c index 46b186babb..e7b7a67aab 100644 --- a/deps/rcheevos/src/rurl/url.c +++ b/deps/rcheevos/src/rurl/url.c @@ -69,7 +69,7 @@ int rc_url_award_cheevo(char* buffer, size_t size, const char* user_name, const 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, unsigned value, unsigned char hash[16]) { +int rc_url_submit_lboard(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned lboard_id, int value, unsigned char hash[16]) { char urle_user_name[64]; char urle_login_token[64]; int written; @@ -85,7 +85,7 @@ int rc_url_submit_lboard(char* buffer, size_t size, const char* user_name, const written = snprintf( buffer, size, - "http://retroachievements.org/dorequest.php?r=submitlbentry&u=%s&t=%s&i=%u&s=%u&v=%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + "http://retroachievements.org/dorequest.php?r=submitlbentry&u=%s&t=%s&i=%u&s=%d&v=%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", urle_user_name, urle_login_token, lboard_id,