From ed684fc222f324557180b059c88a6b1e174ee389 Mon Sep 17 00:00:00 2001 From: Jamiras <32680403+Jamiras@users.noreply.github.com> Date: Sat, 26 Oct 2024 08:28:23 -0600 Subject: [PATCH] add rc_modified_memref_t (#362) --- include/rc_runtime_types.h | 27 ++- src/rc_client.c | 31 ++- src/rcheevos/alloc.c | 8 +- src/rcheevos/condition.c | 273 +++++++++++++++------- src/rcheevos/condset.c | 287 +++++++++++------------ src/rcheevos/memref.c | 139 +++++++---- src/rcheevos/operand.c | 209 ++++++++++++++--- src/rcheevos/rc_internal.h | 85 ++++++- src/rcheevos/rc_runtime_types.natvis | 316 ++++++++++++++++++++++++++ src/rcheevos/rc_validate.c | 131 ++++------- src/rcheevos/richpresence.c | 2 +- src/rcheevos/runtime.c | 4 +- src/rcheevos/runtime_progress.c | 92 +++++++- src/rcheevos/trigger.c | 4 +- src/rcheevos/value.c | 66 +++++- test/rcheevos-test.vcxproj | 4 + test/rcheevos-test.vcxproj.filters | 8 + test/rcheevos/test_condition.c | 14 +- test/rcheevos/test_condset.c | 294 +++++++++++++++++++++++- test/rcheevos/test_memref.c | 126 +++++++--- test/rcheevos/test_operand.c | 14 +- test/rcheevos/test_rc_validate.c | 4 +- test/rcheevos/test_richpresence.c | 21 ++ test/rcheevos/test_runtime_progress.c | 54 +++++ test/rcheevos/test_trigger.c | 49 +++- test/rcheevos/test_value.c | 13 +- test/test_types.natvis | 9 + 27 files changed, 1790 insertions(+), 494 deletions(-) create mode 100644 src/rcheevos/rc_runtime_types.natvis create mode 100644 test/test_types.natvis diff --git a/include/rc_runtime_types.h b/include/rc_runtime_types.h index 4bf1b13b..0092013b 100644 --- a/include/rc_runtime_types.h +++ b/include/rc_runtime_types.h @@ -70,15 +70,14 @@ typedef struct rc_memref_value_t { /* The last differing value of this memory reference. */ uint32_t prior; - /* The size of the value. */ + /* The size of the value. (RC_MEMSIZE_*) */ uint8_t size; /* True if the value changed this frame. */ uint8_t changed; - /* The value type of the value (for variables) */ + /* The value type of the value. (RC_VALUE_TYPE_*) */ uint8_t type; - /* True if the reference will be used in indirection. - * NOTE: This is actually a property of the rc_memref_t, but we put it here to save space */ - uint8_t is_indirect; + /* The type of memref (RC_MEMREF_TYPE_*) */ + uint8_t memref_type; } rc_memref_value_t; @@ -93,6 +92,7 @@ struct rc_memref_t { rc_memref_t* next; }; + /*****************************************************************************\ | Operands | \*****************************************************************************/ @@ -125,11 +125,14 @@ typedef struct rc_operand_t { int luafunc; } value; - /* specifies which member of the value union is being used */ + /* specifies which member of the value union is being used (RC_OPERAND_*) */ uint8_t type; - /* the actual RC_MEMSIZE of the operand - memref.size may differ */ + /* the RC_MEMSIZE of the operand specified in the condition definition - memref.size may differ */ uint8_t size; + + /* specifies how to read the memref for some types (RC_OPERAND_*) */ + uint8_t memref_access_type; } rc_operand_t; @@ -180,7 +183,10 @@ enum { RC_OPERATOR_XOR, RC_OPERATOR_MOD, RC_OPERATOR_ADD, - RC_OPERATOR_SUB + RC_OPERATOR_SUB, + + RC_OPERATOR_SUB_PARENT, /* internal use */ + RC_OPERATOR_INDIRECT_READ /* internal use */ }; typedef struct rc_condition_t rc_condition_t; @@ -224,7 +230,7 @@ struct rc_condset_t { /* The next condition set in the chain. */ rc_condset_t* next; - /* The list of conditions in this condition set. */ + /* The first condition in this condition set. Then follow ->next chain. */ rc_condition_t* conditions; /* True if any condition in the set is a pause condition. */ @@ -232,9 +238,6 @@ struct rc_condset_t { /* True if the set is currently paused. */ uint8_t is_paused; - - /* True if the set has indirect memory references. */ - uint8_t has_indirect_memrefs; }; /*****************************************************************************\ diff --git a/src/rc_client.c b/src/rc_client.c index f050d513..dadd520a 100644 --- a/src/rc_client.c +++ b/src/rc_client.c @@ -1068,7 +1068,7 @@ static void rc_client_validate_addresses(rc_client_game_info_t* game, rc_client_ rc_memref_t** last_memref = &game->runtime.memrefs; rc_memref_t* memref = game->runtime.memrefs; for (; memref; memref = memref->next) { - if (!memref->value.is_indirect) { + if (memref->value.memref_type == RC_MEMREF_TYPE_MEMREF) { total_count++; if (memref->address > max_address || @@ -4913,19 +4913,28 @@ static void rc_client_update_memref_values(rc_client_t* client) int invalidated_memref = 0; for (; memref; memref = memref->next) { - if (memref->value.is_indirect) - continue; + switch (memref->value.memref_type) { + case RC_MEMREF_TYPE_MEMREF: + /* if processing_memref is set, and the memory read fails, all dependent achievements will be disabled */ + client->state.processing_memref = memref; - client->state.processing_memref = memref; + value = rc_peek_value(memref->address, memref->value.size, client->state.legacy_peek, client); - value = rc_peek_value(memref->address, memref->value.size, client->state.legacy_peek, client); + if (client->state.processing_memref) { + rc_update_memref_value(&memref->value, value); + } + else { + /* if the peek function cleared the processing_memref, the memref was invalidated */ + invalidated_memref = 1; + } + break; - if (client->state.processing_memref) { - rc_update_memref_value(&memref->value, value); - } - else { - /* if the peek function cleared the processing_memref, the memref was invalidated */ - invalidated_memref = 1; + case RC_MEMREF_TYPE_MODIFIED_MEMREF: + /* clear processing_memref so an invalid read doesn't disable anything */ + client->state.processing_memref = NULL; + rc_update_memref_value(&memref->value, + rc_get_modified_memref_value((rc_modified_memref_t*)memref, client->state.legacy_peek, client)); + break; } } diff --git a/src/rcheevos/alloc.c b/src/rcheevos/alloc.c index 0aa4e5cb..c71b478d 100644 --- a/src/rcheevos/alloc.c +++ b/src/rcheevos/alloc.c @@ -104,10 +104,14 @@ void rc_init_parse_state(rc_parse_state_t* parse, void* buffer, lua_State* L, in parse->scratch.strings = NULL; rc_buffer_init(&parse->scratch.buffer); memset(&parse->scratch.objs, 0, sizeof(parse->scratch.objs)); - parse->first_memref = 0; - parse->variables = 0; + parse->first_memref = NULL; + parse->variables = NULL; parse->measured_target = 0; parse->lines_read = 0; + parse->addsource_parent.type = RC_OPERAND_NONE; + parse->indirect_parent.type = RC_OPERAND_NONE; + parse->remember.type = RC_OPERAND_NONE; + parse->is_value = 0; parse->has_required_hits = 0; parse->measured_as_percent = 0; } diff --git a/src/rcheevos/condition.c b/src/rcheevos/condition.c index de8efd92..fbef1abe 100644 --- a/src/rcheevos/condition.c +++ b/src/rcheevos/condition.c @@ -1,6 +1,7 @@ #include "rc_internal.h" #include +#include #include static int rc_test_condition_compare(uint32_t value1, uint32_t value2, uint8_t oper) { @@ -15,7 +16,7 @@ static int rc_test_condition_compare(uint32_t value1, uint32_t value2, uint8_t o } } -static char rc_condition_determine_comparator(const rc_condition_t* self) { +static uint8_t rc_condition_determine_comparator(const rc_condition_t* self) { switch (self->oper) { case RC_OPERATOR_EQ: case RC_OPERATOR_NE: @@ -31,7 +32,8 @@ static char rc_condition_determine_comparator(const rc_condition_t* self) { } if ((self->operand1.type == RC_OPERAND_ADDRESS || self->operand1.type == RC_OPERAND_DELTA) && - !self->operand1.value.memref->value.is_indirect && !rc_operand_is_float(&self->operand1)) { + /* TODO: allow modified memref comparisons */ + self->operand1.value.memref->value.memref_type == RC_MEMREF_TYPE_MEMREF && !rc_operand_is_float(&self->operand1)) { /* left side is an integer memory reference */ int needs_translate = (self->operand1.size != self->operand1.value.memref->value.size); @@ -43,7 +45,7 @@ static char rc_condition_determine_comparator(const rc_condition_t* self) { return needs_translate ? RC_PROCESSING_COMPARE_DELTA_TO_CONST_TRANSFORMED : RC_PROCESSING_COMPARE_DELTA_TO_CONST; } else if ((self->operand2.type == RC_OPERAND_ADDRESS || self->operand2.type == RC_OPERAND_DELTA) && - !self->operand2.value.memref->value.is_indirect && !rc_operand_is_float(&self->operand2)) { + self->operand2.value.memref->value.memref_type == RC_MEMREF_TYPE_MEMREF && !rc_operand_is_float(&self->operand2)) { /* right side is an integer memory reference */ const int is_same_memref = (self->operand1.value.memref == self->operand2.value.memref); needs_translate |= (self->operand2.size != self->operand2.value.memref->value.size); @@ -161,7 +163,27 @@ static int rc_parse_operator(const char** memaddr) { } } -rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse, uint8_t is_indirect) { +void rc_condition_convert_to_operand(const rc_condition_t* condition, rc_operand_t* operand, rc_parse_state_t* parse) { + if (condition->oper == RC_OPERATOR_NONE) { + if (operand != &condition->operand1) + memcpy(operand, &condition->operand1, sizeof(*operand)); + } + else { + uint8_t new_size = RC_MEMSIZE_32_BITS; + if (rc_operand_is_float(&condition->operand1) || rc_operand_is_float(&condition->operand2)) + new_size = RC_MEMSIZE_FLOAT; + + operand->value.memref = (rc_memref_t*)rc_alloc_modified_memref(parse, + new_size, &condition->operand1, condition->oper, &condition->operand2); + + /* not actually an address, just a non-delta memref read */ + operand->type = operand->memref_access_type = RC_OPERAND_ADDRESS; + + operand->size = new_size; + } +} + +rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse) { rc_condition_t* self; const char* aux; int result; @@ -204,7 +226,7 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse self->type = RC_CONDITION_STANDARD; } - result = rc_parse_operand(&self->operand1, &aux, is_indirect, parse); + result = rc_parse_operand(&self->operand1, &aux, parse); if (result < 0) { parse->offset = result; return 0; @@ -229,8 +251,7 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse } /* provide dummy operand of '1' and no required hits */ - self->operand2.type = RC_OPERAND_CONST; - self->operand2.value.num = 1; + rc_operand_set_const(&self->operand2, 1); self->required_hits = 0; *memaddr = aux; return self; @@ -267,7 +288,7 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse break; } - result = rc_parse_operand(&self->operand2, &aux, is_indirect, parse); + result = rc_parse_operand(&self->operand2, &aux, parse); if (result < 0) { parse->offset = result; return 0; @@ -275,8 +296,7 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse if (self->oper == RC_OPERATOR_NONE) { /* if operator is none, explicitly clear out the right side */ - self->operand2.type = RC_OPERAND_CONST; - self->operand2.value.num = 0; + rc_operand_set_const(&self->operand2, 0); } if (*aux == '(') { @@ -324,6 +344,140 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse return self; } +void rc_condition_update_parse_state(rc_condition_t* condition, rc_parse_state_t* parse) { + /* type of values in the chain are determined by the parent. + * the last element of a chain is determined by the operand + * + * 1 + 1.5 + 1.75 + 1.0 => (int)1 + (int)1 + (int)1 + (float)1 = (float)4.0 + * 1.0 + 1.5 + 1.75 + 1.0 => (float)1.0 + (float)1.5 + (float)1.75 + (float)1.0 = (float)5.25 + * 1.0 + 1.5 + 1.75 + 1 => (float)1.0 + (float)1.5 + (float)1.75 + (int)1 = (int)5 + */ + + switch (condition->type) { + case RC_CONDITION_ADD_ADDRESS: + if (condition->oper != RC_OPERAND_NONE) + rc_condition_convert_to_operand(condition, &parse->indirect_parent, parse); + else + memcpy(&parse->indirect_parent, &condition->operand1, sizeof(parse->indirect_parent)); + + break; + + case RC_CONDITION_ADD_SOURCE: + if (parse->addsource_parent.type == RC_OPERAND_NONE) { + rc_condition_convert_to_operand(condition, &parse->addsource_parent, parse); + } + else { + rc_operand_t cond_operand; + /* type determined by parent */ + const uint8_t new_size = rc_operand_is_float(&parse->addsource_parent) ? RC_MEMSIZE_FLOAT : RC_MEMSIZE_32_BITS; + + rc_condition_convert_to_operand(condition, &cond_operand, parse); + rc_operand_addsource(&cond_operand, parse, new_size); + memcpy(&parse->addsource_parent, &cond_operand, sizeof(cond_operand)); + } + + parse->addsource_oper = RC_OPERATOR_ADD; + parse->indirect_parent.type = RC_OPERAND_NONE; + break; + + case RC_CONDITION_SUB_SOURCE: + if (parse->addsource_parent.type == RC_OPERAND_NONE) { + rc_condition_convert_to_operand(condition, &parse->addsource_parent, parse); + parse->addsource_oper = RC_OPERATOR_SUB_PARENT; + } + else { + rc_operand_t cond_operand; + /* type determined by parent */ + const uint8_t new_size = rc_operand_is_float(&parse->addsource_parent) ? RC_MEMSIZE_FLOAT : RC_MEMSIZE_32_BITS; + + if (parse->addsource_oper == RC_OPERATOR_ADD && !rc_operand_is_memref(&parse->addsource_parent)) { + /* if the previous element was a constant we have to turn it into a memref by adding zero */ + rc_modified_memref_t* memref; + rc_operand_t zero; + rc_operand_set_const(&zero, 0); + memref = rc_alloc_modified_memref(parse, + parse->addsource_parent.size, &parse->addsource_parent, RC_OPERATOR_ADD, &zero); + parse->addsource_parent.value.memref = (rc_memref_t*)memref; + parse->addsource_parent.type = RC_OPERAND_ADDRESS; + } + else if (parse->addsource_oper == RC_OPERATOR_SUB_PARENT) { + /* if the previous element was also a SubSource, we have to insert a 0 and start subtracting from there */ + rc_modified_memref_t* negate; + rc_operand_t zero; + + if (rc_operand_is_float(&parse->addsource_parent)) + rc_operand_set_float_const(&zero, 0.0); + else + rc_operand_set_const(&zero, 0); + + negate = rc_alloc_modified_memref(parse, new_size, &parse->addsource_parent, RC_OPERATOR_SUB_PARENT, &zero); + parse->addsource_parent.value.memref = (rc_memref_t*)negate; + parse->addsource_parent.size = zero.size; + } + + /* subtract the condition from the chain */ + parse->addsource_oper = rc_operand_is_memref(&parse->addsource_parent) ? RC_OPERATOR_SUB : RC_OPERATOR_SUB_PARENT; + rc_condition_convert_to_operand(condition, &cond_operand, parse); + rc_operand_addsource(&cond_operand, parse, new_size); + memcpy(&parse->addsource_parent, &cond_operand, sizeof(cond_operand)); + + /* indicate the next value can be added to the chain */ + parse->addsource_oper = RC_OPERATOR_ADD; + } + + parse->indirect_parent.type = RC_OPERAND_NONE; + break; + + case RC_CONDITION_REMEMBER: + rc_condition_convert_to_operand(condition, &condition->operand1, parse); + + if (parse->addsource_parent.type != RC_OPERAND_NONE) { + /* type determined by leaf */ + rc_operand_addsource(&condition->operand1, parse, condition->operand1.size); + } + + memcpy(&parse->remember, &condition->operand1, sizeof(parse->remember)); + + parse->addsource_parent.type = RC_OPERAND_NONE; + parse->indirect_parent.type = RC_OPERAND_NONE; + break; + + case RC_CONDITION_MEASURED: + /* Measured condition can have modifiers in values */ + if (parse->is_value) { + switch (condition->oper) { + case RC_OPERATOR_AND: + case RC_OPERATOR_XOR: + case RC_OPERATOR_DIV: + case RC_OPERATOR_MULT: + case RC_OPERATOR_MOD: + case RC_OPERATOR_ADD: + case RC_OPERATOR_SUB: + rc_condition_convert_to_operand(condition, &condition->operand1, parse); + break; + + default: + break; + } + } + + /* fallthrough */ /* to default */ + + default: + if (parse->addsource_parent.type != RC_OPERAND_NONE) { + /* type determined by leaf */ + rc_operand_addsource(&condition->operand1, parse, condition->operand1.size); + + if (parse->buffer) + condition->optimized_comparator = rc_condition_determine_comparator(condition); + } + + parse->addsource_parent.type = RC_OPERAND_NONE; + parse->indirect_parent.type = RC_OPERAND_NONE; + break; + } +} + int rc_condition_is_combining(const rc_condition_t* self) { switch (self->type) { case RC_CONDITION_STANDARD: @@ -500,41 +654,35 @@ static int rc_test_condition_compare_delta_to_memref_transformed(rc_condition_t* int rc_test_condition(rc_condition_t* self, rc_eval_state_t* eval_state) { rc_typed_value_t value1, value2; - if (eval_state->add_value.type != RC_VALUE_TYPE_NONE) { - /* if there's an accumulator, we can't use the optimized comparators */ - rc_evaluate_operand(&value1, &self->operand1, eval_state); - rc_typed_value_add(&value1, &eval_state->add_value); - } else { - /* use an optimized comparator whenever possible */ - switch (self->optimized_comparator) { - case RC_PROCESSING_COMPARE_MEMREF_TO_CONST: - return rc_test_condition_compare_memref_to_const(self); - case RC_PROCESSING_COMPARE_MEMREF_TO_DELTA: - return rc_test_condition_compare_memref_to_delta(self); - case RC_PROCESSING_COMPARE_MEMREF_TO_MEMREF: - return rc_test_condition_compare_memref_to_memref(self); - case RC_PROCESSING_COMPARE_DELTA_TO_CONST: - return rc_test_condition_compare_delta_to_const(self); - case RC_PROCESSING_COMPARE_DELTA_TO_MEMREF: - return rc_test_condition_compare_delta_to_memref(self); - case RC_PROCESSING_COMPARE_MEMREF_TO_CONST_TRANSFORMED: - return rc_test_condition_compare_memref_to_const_transformed(self); - case RC_PROCESSING_COMPARE_MEMREF_TO_DELTA_TRANSFORMED: - return rc_test_condition_compare_memref_to_delta_transformed(self); - case RC_PROCESSING_COMPARE_MEMREF_TO_MEMREF_TRANSFORMED: - return rc_test_condition_compare_memref_to_memref_transformed(self); - case RC_PROCESSING_COMPARE_DELTA_TO_CONST_TRANSFORMED: - return rc_test_condition_compare_delta_to_const_transformed(self); - case RC_PROCESSING_COMPARE_DELTA_TO_MEMREF_TRANSFORMED: - return rc_test_condition_compare_delta_to_memref_transformed(self); - case RC_PROCESSING_COMPARE_ALWAYS_TRUE: - return 1; - case RC_PROCESSING_COMPARE_ALWAYS_FALSE: - return 0; - default: - rc_evaluate_operand(&value1, &self->operand1, eval_state); - break; - } + /* use an optimized comparator whenever possible */ + switch (self->optimized_comparator) { + case RC_PROCESSING_COMPARE_MEMREF_TO_CONST: + return rc_test_condition_compare_memref_to_const(self); + case RC_PROCESSING_COMPARE_MEMREF_TO_DELTA: + return rc_test_condition_compare_memref_to_delta(self); + case RC_PROCESSING_COMPARE_MEMREF_TO_MEMREF: + return rc_test_condition_compare_memref_to_memref(self); + case RC_PROCESSING_COMPARE_DELTA_TO_CONST: + return rc_test_condition_compare_delta_to_const(self); + case RC_PROCESSING_COMPARE_DELTA_TO_MEMREF: + return rc_test_condition_compare_delta_to_memref(self); + case RC_PROCESSING_COMPARE_MEMREF_TO_CONST_TRANSFORMED: + return rc_test_condition_compare_memref_to_const_transformed(self); + case RC_PROCESSING_COMPARE_MEMREF_TO_DELTA_TRANSFORMED: + return rc_test_condition_compare_memref_to_delta_transformed(self); + case RC_PROCESSING_COMPARE_MEMREF_TO_MEMREF_TRANSFORMED: + return rc_test_condition_compare_memref_to_memref_transformed(self); + case RC_PROCESSING_COMPARE_DELTA_TO_CONST_TRANSFORMED: + return rc_test_condition_compare_delta_to_const_transformed(self); + case RC_PROCESSING_COMPARE_DELTA_TO_MEMREF_TRANSFORMED: + return rc_test_condition_compare_delta_to_memref_transformed(self); + case RC_PROCESSING_COMPARE_ALWAYS_TRUE: + return 1; + case RC_PROCESSING_COMPARE_ALWAYS_FALSE: + return 0; + default: + rc_evaluate_operand(&value1, &self->operand1, eval_state); + break; } rc_evaluate_operand(&value2, &self->operand2, eval_state); @@ -548,38 +696,5 @@ void rc_evaluate_condition_value(rc_typed_value_t* value, rc_condition_t* self, rc_evaluate_operand(value, &self->operand1, eval_state); rc_evaluate_operand(&amount, &self->operand2, eval_state); - switch (self->oper) { - case RC_OPERATOR_MULT: - rc_typed_value_multiply(value, &amount); - break; - - case RC_OPERATOR_DIV: - rc_typed_value_divide(value, &amount); - break; - - case RC_OPERATOR_AND: - rc_typed_value_convert(value, RC_VALUE_TYPE_UNSIGNED); - rc_typed_value_convert(&amount, RC_VALUE_TYPE_UNSIGNED); - value->value.u32 &= amount.value.u32; - break; - - case RC_OPERATOR_XOR: - rc_typed_value_convert(value, RC_VALUE_TYPE_UNSIGNED); - rc_typed_value_convert(&amount, RC_VALUE_TYPE_UNSIGNED); - value->value.u32 ^= amount.value.u32; - break; - - case RC_OPERATOR_MOD: - rc_typed_value_modulus(value, &amount); - break; - - case RC_OPERATOR_ADD: - rc_typed_value_add(value, &amount); - break; - - case RC_OPERATOR_SUB: - rc_typed_value_negate(&amount); - rc_typed_value_add(value, &amount); - break; - } + rc_typed_value_combine(value, &amount, self->oper); } diff --git a/src/rcheevos/condset.c b/src/rcheevos/condset.c index f03d47b4..250fa4b9 100644 --- a/src/rcheevos/condset.c +++ b/src/rcheevos/condset.c @@ -24,14 +24,70 @@ static void rc_update_condition_pause(rc_condition_t* condition) { } } -rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse, int is_value) { +static void rc_condition_update_recall_operand(rc_operand_t* operand, const rc_operand_t* remember) +{ + if (operand->type == RC_OPERAND_RECALL) { + if (rc_operand_type_is_memref(operand->memref_access_type) && operand->value.memref == NULL) { + memcpy(operand, remember, sizeof(*remember)); + operand->memref_access_type = operand->type; + operand->type = RC_OPERAND_RECALL; + } + } + else if (rc_operand_is_memref(operand) && operand->value.memref->value.memref_type == RC_MEMREF_TYPE_MODIFIED_MEMREF) { + rc_modified_memref_t* modified_memref = (rc_modified_memref_t*)operand->value.memref; + rc_condition_update_recall_operand(&modified_memref->parent, remember); + rc_condition_update_recall_operand(&modified_memref->modifier, remember); + } +} + +static void rc_update_condition_pause_remember(rc_condition_t* conditions) { + rc_operand_t* pause_remember = NULL; + rc_condition_t* condition; + + for (condition = conditions; condition; condition = condition->next) { + if (!condition->pause) + continue; + + if (condition->type == RC_CONDITION_REMEMBER) { + pause_remember = &condition->operand1; + } + else if (pause_remember == NULL) { + /* if we picked up a non-pause remember, discard it */ + if (condition->operand1.type == RC_OPERAND_RECALL && + rc_operand_type_is_memref(condition->operand1.memref_access_type)) { + condition->operand1.value.memref = NULL; + } + + if (condition->operand2.type == RC_OPERAND_RECALL && + rc_operand_type_is_memref(condition->operand2.memref_access_type)) { + condition->operand2.value.memref = NULL; + } + } + } + + if (pause_remember) { + for (condition = conditions; condition; condition = condition->next) { + if (!condition->pause) { + /* if we didn't find a remember for a non-pause condition, use the last pause remember */ + rc_condition_update_recall_operand(&condition->operand1, pause_remember); + rc_condition_update_recall_operand(&condition->operand2, pause_remember); + } + + /* Anything after this point will have already been handled */ + if (condition->type == RC_CONDITION_REMEMBER) + break; + } + } +} + +rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse) { rc_condset_t* self; rc_condition_t** next; - int in_add_address; + rc_condition_t* condition; uint32_t measured_target = 0; self = RC_ALLOC(rc_condset_t, parse); - self->has_pause = self->is_paused = self->has_indirect_memrefs = 0; + self->has_pause = self->is_paused = 0; next = &self->conditions; if (**memaddr == 'S' || **memaddr == 's' || !**memaddr) { @@ -40,16 +96,18 @@ rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse, in return self; } - in_add_address = 0; + /* each condition set has a functionally new recall accumulator */ + parse->remember.type = RC_OPERAND_NONE; + for (;;) { - *next = rc_parse_condition(memaddr, parse, in_add_address); + condition = rc_parse_condition(memaddr, parse); + *next = condition; - if (parse->offset < 0) { + if (parse->offset < 0) return 0; - } - if ((*next)->oper == RC_OPERATOR_NONE) { - switch ((*next)->type) { + if (condition->oper == RC_OPERATOR_NONE) { + switch (condition->type) { case RC_CONDITION_ADD_ADDRESS: case RC_CONDITION_ADD_SOURCE: case RC_CONDITION_SUB_SOURCE: @@ -59,7 +117,7 @@ rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse, in case RC_CONDITION_MEASURED: /* right hand side is not required when Measured is used in a value */ - if (is_value) + if (parse->is_value) break; /* fallthrough */ /* to default */ @@ -69,120 +127,92 @@ rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse, in } } - self->has_pause |= (*next)->type == RC_CONDITION_PAUSE_IF; - in_add_address = (*next)->type == RC_CONDITION_ADD_ADDRESS; - self->has_indirect_memrefs |= in_add_address; - - switch ((*next)->type) { - case RC_CONDITION_MEASURED: - if (measured_target != 0) { - /* multiple Measured flags cannot exist in the same group */ - parse->offset = RC_MULTIPLE_MEASURED; - return 0; - } - else if (is_value) { - measured_target = (unsigned)-1; - switch ((*next)->oper) - { - case RC_OPERATOR_AND: - case RC_OPERATOR_XOR: - case RC_OPERATOR_DIV: - case RC_OPERATOR_MULT: - case RC_OPERATOR_MOD: - case RC_OPERATOR_ADD: - case RC_OPERATOR_SUB: - case RC_OPERATOR_NONE: - /* measuring value. leave required_hits at 0 */ - break; + switch (condition->type) { + case RC_CONDITION_MEASURED: + if (measured_target != 0) { + /* multiple Measured flags cannot exist in the same group */ + parse->offset = RC_MULTIPLE_MEASURED; + return 0; + } + else if (parse->is_value) { + measured_target = (unsigned)-1; + switch (condition->oper) { + case RC_OPERATOR_AND: + case RC_OPERATOR_XOR: + case RC_OPERATOR_DIV: + case RC_OPERATOR_MULT: + case RC_OPERATOR_MOD: + case RC_OPERATOR_ADD: + case RC_OPERATOR_SUB: + case RC_OPERATOR_NONE: + /* measuring value. leave required_hits at 0 */ + break; + + default: + /* comparison operator, measuring hits. set required_hits to MAX_INT */ + condition->required_hits = measured_target; + break; + } + } + else if (condition->required_hits != 0) { + measured_target = condition->required_hits; + } + else if (condition->operand2.type == RC_OPERAND_CONST) { + measured_target = condition->operand2.value.num; + } + else if (condition->operand2.type == RC_OPERAND_FP) { + measured_target = (unsigned)condition->operand2.value.dbl; + } + else { + parse->offset = RC_INVALID_MEASURED_TARGET; + return 0; + } - default: - /* comparison operator, measuring hits. set required_hits to MAX_INT */ - (*next)->required_hits = measured_target; - break; + if (parse->measured_target && measured_target != parse->measured_target) { + /* multiple Measured flags in separate groups must have the same target */ + parse->offset = RC_MULTIPLE_MEASURED; + return 0; } - } - else if ((*next)->required_hits != 0) { - measured_target = (*next)->required_hits; - } - else if ((*next)->operand2.type == RC_OPERAND_CONST) { - measured_target = (*next)->operand2.value.num; - } - else if ((*next)->operand2.type == RC_OPERAND_FP) { - measured_target = (unsigned)(*next)->operand2.value.dbl; - } - else { - parse->offset = RC_INVALID_MEASURED_TARGET; - return 0; - } - if (parse->measured_target && measured_target != parse->measured_target) { - /* multiple Measured flags in separate groups must have the same target */ - parse->offset = RC_MULTIPLE_MEASURED; - return 0; - } + parse->measured_target = measured_target; + break; - parse->measured_target = measured_target; - break; + case RC_CONDITION_STANDARD: + case RC_CONDITION_TRIGGER: + /* these flags are not allowed in value expressions */ + if (parse->is_value) { + parse->offset = RC_INVALID_VALUE_FLAG; + return 0; + } + break; + } - case RC_CONDITION_STANDARD: - case RC_CONDITION_TRIGGER: - /* these flags are not allowed in value expressions */ - if (is_value) { - parse->offset = RC_INVALID_VALUE_FLAG; - return 0; - } - break; + rc_condition_update_parse_state(condition, parse); - default: - break; - } + self->has_pause |= condition->type == RC_CONDITION_PAUSE_IF; - next = &(*next)->next; + next = &condition->next; - if (**memaddr != '_') { + if (**memaddr != '_') break; - } (*memaddr)++; } *next = 0; - if (parse->buffer != 0) + if (parse->buffer && self->has_pause) { rc_update_condition_pause(self->conditions); - return self; -} - -static void rc_condset_update_indirect_memrefs(rc_condition_t* condition, int processing_pause, rc_eval_state_t* eval_state) { - for (; condition != 0; condition = condition->next) { - if (condition->pause != processing_pause) - continue; - - if (condition->type == RC_CONDITION_ADD_ADDRESS) { - rc_typed_value_t value; - rc_evaluate_condition_value(&value, condition, eval_state); - rc_typed_value_convert(&value, RC_VALUE_TYPE_UNSIGNED); - eval_state->add_address = value.value.u32; - continue; - } - - /* call rc_get_memref_value to update the indirect memrefs. it won't do anything with non-indirect - * memrefs and avoids a second check of is_indirect. also, we ignore the response, so it doesn't - * matter what operand type we pass. assume RC_OPERAND_ADDRESS is the quickest. */ - if (rc_operand_is_memref(&condition->operand1)) - rc_get_memref_value(condition->operand1.value.memref, RC_OPERAND_ADDRESS, eval_state); - - if (rc_operand_is_memref(&condition->operand2)) - rc_get_memref_value(condition->operand2.value.memref, RC_OPERAND_ADDRESS, eval_state); - - eval_state->add_address = 0; + if (parse->remember.type != RC_OPERATOR_NONE) + rc_update_condition_pause_remember(self->conditions); } + + return self; } static int rc_test_condset_internal(rc_condset_t* self, int processing_pause, rc_eval_state_t* eval_state) { rc_condition_t* condition; - rc_typed_value_t value; int set_valid, cond_valid, and_next, or_next, reset_next, measured_from_hits, can_measure; rc_typed_value_t measured_value; uint32_t total_hits; @@ -197,8 +227,7 @@ static int rc_test_condset_internal(rc_condset_t* self, int processing_pause, rc and_next = 1; or_next = 0; reset_next = 0; - eval_state->add_value.type = RC_VALUE_TYPE_NONE; - eval_state->add_hits = eval_state->add_address = 0; + eval_state->add_hits = 0; for (condition = self->conditions; condition != 0; condition = condition->next) { if (condition->pause != processing_pause) @@ -207,38 +236,16 @@ static int rc_test_condset_internal(rc_condset_t* self, int processing_pause, rc /* STEP 1: process modifier conditions */ switch (condition->type) { case RC_CONDITION_ADD_SOURCE: - rc_evaluate_condition_value(&value, condition, eval_state); - rc_typed_value_add(&eval_state->add_value, &value); - eval_state->add_address = 0; - continue; - case RC_CONDITION_SUB_SOURCE: - rc_evaluate_condition_value(&value, condition, eval_state); - rc_typed_value_negate(&value); - rc_typed_value_add(&eval_state->add_value, &value); - eval_state->add_address = 0; - continue; - case RC_CONDITION_ADD_ADDRESS: - rc_evaluate_condition_value(&value, condition, eval_state); - rc_typed_value_convert(&value, RC_VALUE_TYPE_UNSIGNED); - eval_state->add_address = value.value.u32; - continue; - case RC_CONDITION_REMEMBER: - rc_evaluate_condition_value(&value, condition, eval_state); - rc_typed_value_add(&value, &eval_state->add_value); - eval_state->recall_value.type = value.type; - eval_state->recall_value.value = value.value; - eval_state->add_value.type = RC_VALUE_TYPE_NONE; - eval_state->add_address = 0; + /* these are all managed by rc_modified_memref_t now */ continue; case RC_CONDITION_MEASURED: if (condition->required_hits == 0 && can_measure) { /* Measured condition without a hit target measures the value of the left operand */ - rc_evaluate_condition_value(&measured_value, condition, eval_state); - rc_typed_value_add(&measured_value, &eval_state->add_value); + rc_evaluate_operand(&measured_value, &condition->operand1, eval_state); } break; @@ -247,9 +254,7 @@ static int rc_test_condset_internal(rc_condset_t* self, int processing_pause, rc } /* STEP 2: evaluate the current condition */ - condition->is_true = (char)rc_test_condition(condition, eval_state); - eval_state->add_value.type = RC_VALUE_TYPE_NONE; - eval_state->add_address = 0; + condition->is_true = (uint8_t)rc_test_condition(condition, eval_state); /* apply logic flags and reset them for the next condition */ cond_valid = condition->is_true; @@ -342,20 +347,8 @@ static int rc_test_condset_internal(rc_condset_t* self, int processing_pause, rc 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 */ - if (cond_valid) { - /* indirect memrefs are not updated as part of the rc_update_memref_values call. - * an active pause aborts processing of the remaining part of the pause subset and the entire non-pause subset. - * if the set has any indirect memrefs, manually update them now so the deltas are correct */ - if (self->has_indirect_memrefs) { - /* first, update any indirect memrefs in the remaining part of the pause subset */ - rc_condset_update_indirect_memrefs(condition->next, 1, eval_state); - - /* then, update all indirect memrefs in the non-pause subset */ - rc_condset_update_indirect_memrefs(self->conditions, 0, eval_state); - } - + if (cond_valid) return 1; - } /* if we make it to the end of the function, make sure we indicate that nothing matched. if we do find a later PauseIf match, it'll automatically return true via the previous condition. */ @@ -416,7 +409,7 @@ static int rc_test_condset_internal(rc_condset_t* self, int processing_pause, rc if (eval_state->measured_value.type == RC_VALUE_TYPE_NONE || rc_typed_value_compare(&measured_value, &eval_state->measured_value, RC_OPERATOR_GT)) { memcpy(&eval_state->measured_value, &measured_value, sizeof(measured_value)); - eval_state->measured_from_hits = (char)measured_from_hits; + eval_state->measured_from_hits = (uint8_t)measured_from_hits; } } @@ -429,10 +422,6 @@ int rc_test_condset(rc_condset_t* self, rc_eval_state_t* eval_state) { return 1; } - /* initialize recall value so each condition set has a functionally new recall accumulator */ - eval_state->recall_value.type = RC_VALUE_TYPE_UNSIGNED; - eval_state->recall_value.value.u32 = 0; - if (self->has_pause) { /* one or more Pause conditions exists, if any of them are true, stop processing this group */ self->is_paused = (char)rc_test_condset_internal(self, 1, eval_state); diff --git a/src/rcheevos/memref.c b/src/rcheevos/memref.c index 0cbec67b..380ae0cf 100644 --- a/src/rcheevos/memref.c +++ b/src/rcheevos/memref.c @@ -6,41 +6,71 @@ #define MEMREF_PLACEHOLDER_ADDRESS 0xFFFFFFFF -rc_memref_t* rc_alloc_memref(rc_parse_state_t* parse, uint32_t address, uint8_t size, uint8_t is_indirect) { +rc_memref_t* rc_alloc_memref(rc_parse_state_t* parse, uint32_t address, uint8_t size) { rc_memref_t** next_memref; rc_memref_t* memref; - if (!is_indirect) { - /* attempt to find an existing memref that can be shared */ - next_memref = parse->first_memref; - while (*next_memref) { - memref = *next_memref; - if (!memref->value.is_indirect && memref->address == address && memref->value.size == size) - return memref; + /* attempt to find an existing memref that can be shared */ + next_memref = parse->first_memref; + while (*next_memref) { + memref = *next_memref; + if (memref->address == address && memref->value.memref_type == RC_MEMREF_TYPE_MEMREF && memref->value.size == size) + return memref; - next_memref = &memref->next; - } - - /* no match found, create a new entry */ - memref = RC_ALLOC_SCRATCH(rc_memref_t, parse); - *next_memref = memref; - } - else { - /* indirect references always create a new entry because we can't guarantee that the - * indirection amount will be the same between references. because they aren't shared, - * don't bother putting them in the chain. - */ - memref = RC_ALLOC(rc_memref_t, parse); + next_memref = &memref->next; } + /* no match found, create a new entry */ + memref = RC_ALLOC_SCRATCH(rc_memref_t, parse); + *next_memref = memref; + memset(memref, 0, sizeof(*memref)); - memref->address = address; + memref->value.memref_type = RC_MEMREF_TYPE_MEMREF; + memref->value.type = RC_VALUE_TYPE_UNSIGNED; memref->value.size = size; - memref->value.is_indirect = is_indirect; + memref->address = address; return memref; } +rc_modified_memref_t* rc_alloc_modified_memref(rc_parse_state_t* parse, uint8_t size, const rc_operand_t* parent, + uint8_t modifier_type, const rc_operand_t* modifier) { + rc_memref_t** next_memref; + rc_memref_t* memref; + rc_modified_memref_t* modified_memref; + + /* attempt to find an existing memref that can be shared */ + next_memref = parse->first_memref; + while (*next_memref) { + memref = *next_memref; + if (memref->value.memref_type == RC_MEMREF_TYPE_MODIFIED_MEMREF && memref->value.size == size) { + modified_memref = (rc_modified_memref_t*)memref; + if (modified_memref->modifier_type == modifier_type && + rc_operands_are_equal(&modified_memref->parent, parent) && + rc_operands_are_equal(&modified_memref->modifier, modifier)) { + return modified_memref; + } + } + + next_memref = &memref->next; + } + + /* no match found, create a new entry */ + modified_memref = RC_ALLOC_SCRATCH(rc_modified_memref_t, parse); + *next_memref = (rc_memref_t*)modified_memref; + + memset(modified_memref, 0, sizeof(*modified_memref)); + modified_memref->memref.value.memref_type = RC_MEMREF_TYPE_MODIFIED_MEMREF; + modified_memref->memref.value.size = size; + modified_memref->memref.value.type = (size == RC_MEMSIZE_FLOAT) ? RC_VALUE_TYPE_FLOAT : RC_VALUE_TYPE_UNSIGNED; + memcpy(&modified_memref->parent, parent, sizeof(modified_memref->parent)); + memcpy(&modified_memref->modifier, modifier, sizeof(modified_memref->modifier)); + modified_memref->modifier_type = modifier_type; + modified_memref->memref.address = rc_operand_is_memref(modifier) ? modifier->value.memref->address : modifier->value.num; + + return modified_memref; +} + int rc_parse_memref(const char** memaddr, uint8_t* size, uint32_t* address) { const char* aux = *memaddr; char* end; @@ -485,16 +515,6 @@ void rc_update_memref_value(rc_memref_value_t* memref, uint32_t new_value) { } } -void rc_update_memref_values(rc_memref_t* memref, rc_peek_t peek, void* ud) { - while (memref) { - /* indirect memory references are not shared and will be updated in rc_get_memref_value */ - if (!memref->value.is_indirect) - rc_update_memref_value(&memref->value, rc_peek_value(memref->address, memref->value.size, peek, ud)); - - memref = memref->next; - } -} - void rc_init_parse_state_memrefs(rc_parse_state_t* parse, rc_memref_t** memrefs) { parse->first_memref = memrefs; *memrefs = 0; @@ -520,12 +540,53 @@ static uint32_t rc_get_memref_value_value(const rc_memref_value_t* memref, int o } } -uint32_t rc_get_memref_value(rc_memref_t* memref, int operand_type, rc_eval_state_t* eval_state) { - /* if this is an indirect reference, handle the indirection. */ - if (memref->value.is_indirect) { - const uint32_t new_address = memref->address + eval_state->add_address; - rc_update_memref_value(&memref->value, rc_peek_value(new_address, memref->value.size, eval_state->peek, eval_state->peek_userdata)); +void rc_get_memref_value(rc_typed_value_t* value, rc_memref_t* memref, int operand_type) { + value->type = memref->value.type; + value->value.u32 = rc_get_memref_value_value(&memref->value, operand_type); +} + +uint32_t rc_get_modified_memref_value(const rc_modified_memref_t* memref, rc_peek_t peek, void* ud) { + rc_typed_value_t value, modifier; + + rc_evaluate_operand(&value, &memref->parent, NULL); + rc_evaluate_operand(&modifier, &memref->modifier, NULL); + + switch (memref->modifier_type) { + case RC_OPERATOR_INDIRECT_READ: + rc_typed_value_add(&value, &modifier); + rc_typed_value_convert(&value, RC_VALUE_TYPE_UNSIGNED); + value.value.u32 = rc_peek_value(value.value.u32, memref->memref.value.size, peek, ud); + value.type = memref->memref.value.type; + break; + + case RC_OPERATOR_SUB_PARENT: + rc_typed_value_negate(&value); + rc_typed_value_add(&value, &modifier); + rc_typed_value_convert(&value, memref->memref.value.type); + break; + + default: + rc_typed_value_combine(&value, &modifier, memref->modifier_type); + rc_typed_value_convert(&value, memref->memref.value.type); + break; } - return rc_get_memref_value_value(&memref->value, operand_type); + return value.value.u32; +} + +void rc_update_memref_values(rc_memref_t* memref, rc_peek_t peek, void* ud) { + while (memref) { + /* indirect memory references are not shared and will be updated in rc_get_memref_value */ + switch (memref->value.memref_type) { + case RC_MEMREF_TYPE_MEMREF: + rc_update_memref_value(&memref->value, rc_peek_value(memref->address, memref->value.size, peek, ud)); + break; + + case RC_MEMREF_TYPE_MODIFIED_MEMREF: + rc_update_memref_value(&memref->value, rc_get_modified_memref_value((rc_modified_memref_t*)memref, peek, ud)); + break; + } + + memref = memref->next; + } } diff --git a/src/rcheevos/operand.c b/src/rcheevos/operand.c index 09ac65ee..3a09a3b0 100644 --- a/src/rcheevos/operand.c +++ b/src/rcheevos/operand.c @@ -61,11 +61,13 @@ static int rc_parse_operand_lua(rc_operand_t* self, const char** memaddr, rc_par #endif /* RC_DISABLE_LUA */ self->type = RC_OPERAND_LUA; + self->size = RC_MEMSIZE_32_BITS; + self->memref_access_type = RC_OPERAND_ADDRESS; *memaddr = aux; return RC_OK; } -static int rc_parse_operand_variable(rc_operand_t* self, const char** memaddr) { +static int rc_parse_operand_variable(rc_operand_t* self, const char** memaddr, rc_parse_state_t* parse) { const char* aux = *memaddr; size_t i; char varName[RC_VALUE_MAX_NAME_LENGTH + 1] = { 0 }; @@ -86,6 +88,15 @@ static int rc_parse_operand_variable(rc_operand_t* self, const char** memaddr) { ++aux; if (strcmp(varName, "recall") == 0) { + if (parse->remember.type == RC_OPERAND_NONE) { + self->value.memref = NULL; + self->size = RC_MEMSIZE_32_BITS; + self->memref_access_type = RC_OPERAND_ADDRESS; + } + else { + memcpy(self, &parse->remember, sizeof(*self)); + self->memref_access_type = self->type; + } self->type = RC_OPERAND_RECALL; } else { /* process named variable when feature is available.*/ @@ -96,7 +107,7 @@ static int rc_parse_operand_variable(rc_operand_t* self, const char** memaddr) { return RC_OK; } -static int rc_parse_operand_memory(rc_operand_t* self, const char** memaddr, rc_parse_state_t* parse, uint8_t is_indirect) { +static int rc_parse_operand_memory(rc_operand_t* self, const char** memaddr, rc_parse_state_t* parse) { const char* aux = *memaddr; uint32_t address; uint8_t size; @@ -128,6 +139,8 @@ static int rc_parse_operand_memory(rc_operand_t* self, const char** memaddr, rc_ break; } + self->memref_access_type = self->type; + ret = rc_parse_memref(&aux, &self->size, &address); if (ret != RC_OK) return ret; @@ -137,13 +150,28 @@ static int rc_parse_operand_memory(rc_operand_t* self, const char** memaddr, rc_ /* if the shared size differs from the requested size and it's a prior operation, we * have to check to make sure both sizes use the same mask, or the prior value may be * updated when bits outside the mask are modified, which would make it look like the - * current value once the mask is applied. if the mask differs, create a new + * current value once the mask is applied. if the mask differs, create a new * non-shared record for tracking the prior data. */ if (rc_memref_mask(size) != rc_memref_mask(self->size)) size = self->size; } - self->value.memref = rc_alloc_memref(parse, address, size, is_indirect); + if (parse->indirect_parent.type != RC_OPERAND_NONE) { + if (parse->indirect_parent.type == RC_OPERAND_CONST) { + self->value.memref = rc_alloc_memref(parse, address + parse->indirect_parent.value.num, size); + } + else { + rc_operand_t offset; + rc_operand_set_const(&offset, address); + + self->value.memref = (rc_memref_t*)rc_alloc_modified_memref(parse, + size, &parse->indirect_parent, RC_OPERATOR_INDIRECT_READ, &offset); + } + } + else { + self->value.memref = rc_alloc_memref(parse, address, size); + } + if (parse->offset < 0) return parse->offset; @@ -151,7 +179,7 @@ static int rc_parse_operand_memory(rc_operand_t* self, const char** memaddr, rc_ return RC_OK; } -int rc_parse_operand(rc_operand_t* self, const char** memaddr, uint8_t is_indirect, rc_parse_state_t* parse) { +int rc_parse_operand(rc_operand_t* self, const char** memaddr, rc_parse_state_t* parse) { const char* aux = *memaddr; char* end; int ret; @@ -159,8 +187,6 @@ int rc_parse_operand(rc_operand_t* self, const char** memaddr, uint8_t is_indire int negative; int allow_decimal = 0; - self->size = RC_MEMSIZE_32_BITS; - switch (*aux) { case 'h': case 'H': /* hex constant */ if (aux[2] == 'x' || aux[2] == 'X') { @@ -175,15 +201,14 @@ int rc_parse_operand(rc_operand_t* self, const char** memaddr, uint8_t is_indire if (value > 0xffffffffU) value = 0xffffffffU; - self->type = RC_OPERAND_CONST; - self->value.num = (unsigned)value; + rc_operand_set_const(self, (unsigned)value); aux = end; break; case 'f': case 'F': /* floating point constant */ if (isalpha((unsigned char)aux[1])) { - ret = rc_parse_operand_memory(self, &aux, parse, is_indirect); + ret = rc_parse_operand_memory(self, &aux, parse); if (ret < 0) return ret; @@ -211,6 +236,7 @@ int rc_parse_operand(rc_operand_t* self, const char** memaddr, uint8_t is_indire /* custom parser for decimal values to ignore locale */ unsigned long shift = 1; unsigned long fraction = 0; + double dbl_val; aux = end + 1; if (*aux < '0' || *aux > '9') @@ -231,19 +257,19 @@ int rc_parse_operand(rc_operand_t* self, const char** memaddr, uint8_t is_indire /* non-zero fractional part, convert to double and merge in integer portion */ const double dbl_fraction = ((double)fraction) / ((double)shift); if (negative) - self->value.dbl = ((double)(-((long)value))) - dbl_fraction; + dbl_val = ((double)(-((long)value))) - dbl_fraction; else - self->value.dbl = (double)value + dbl_fraction; + dbl_val = (double)value + dbl_fraction; } else { /* fractional part is 0, just convert the integer portion */ if (negative) - self->value.dbl = (double)(-((long)value)); + dbl_val = (double)(-((long)value)); else - self->value.dbl = (double)value; + dbl_val = (double)value; } - self->type = RC_OPERAND_FP; + rc_operand_set_float_const(self, dbl_val); } else { /* not a floating point value, make sure something was read and advance the read pointer */ @@ -255,17 +281,15 @@ int rc_parse_operand(rc_operand_t* self, const char** memaddr, uint8_t is_indire if (value > 0x7fffffffU) value = 0x7fffffffU; - self->type = RC_OPERAND_CONST; - if (negative) - self->value.num = (unsigned)(-((long)value)); + rc_operand_set_const(self, (unsigned)(-((long)value))); else - self->value.num = (unsigned)value; + rc_operand_set_const(self, (unsigned)value); } break; case '{': /* variable */ ++aux; - ret = rc_parse_operand_variable(self, &aux); + ret = rc_parse_operand_variable(self, &aux, parse); if (ret < 0) return ret; @@ -275,7 +299,7 @@ int rc_parse_operand(rc_operand_t* self, const char** memaddr, uint8_t is_indire if (aux[1] == 'x' || aux[1] == 'X') { /* hex integer constant */ /* fallthrough */ /* to default */ default: - ret = rc_parse_operand_memory(self, &aux, parse, is_indirect); + ret = rc_parse_operand_memory(self, &aux, parse); if (ret < 0) return ret; @@ -292,8 +316,7 @@ int rc_parse_operand(rc_operand_t* self, const char** memaddr, uint8_t is_indire if (value > 0xffffffffU) value = 0xffffffffU; - self->type = RC_OPERAND_CONST; - self->value.num = (unsigned)value; + rc_operand_set_const(self, (unsigned)value); aux = end; break; @@ -332,8 +355,57 @@ static int rc_luapeek(lua_State* L) { #endif /* RC_DISABLE_LUA */ -int rc_operand_is_float_memref(const rc_operand_t* self) { - switch (self->size) { +void rc_operand_set_const(rc_operand_t* self, uint32_t value) { + self->size = RC_MEMSIZE_32_BITS; + self->type = RC_OPERAND_CONST; + self->memref_access_type = RC_OPERAND_NONE; + self->value.num = value; +} + +void rc_operand_set_float_const(rc_operand_t* self, double value) { + self->size = RC_MEMSIZE_FLOAT; + self->type = RC_OPERAND_FP; + self->memref_access_type = RC_OPERAND_NONE; + self->value.dbl = value; +} + +int rc_operands_are_equal(const rc_operand_t* left, const rc_operand_t* right) { + if (left->type != right->type) + return 0; + + switch (left->type) { + case RC_OPERAND_CONST: + return (left->value.num == right->value.num); + case RC_OPERAND_FP: + return (left->value.dbl == right->value.dbl); + case RC_OPERAND_RECALL: + return 1; + default: + break; + } + + /* comparing two memrefs - look for exact matches on type and size */ + if (left->size != right->size || left->value.memref->value.memref_type != right->value.memref->value.memref_type) + return 0; + + switch (left->value.memref->value.memref_type) { + case RC_MEMREF_TYPE_MODIFIED_MEMREF: + { + const rc_modified_memref_t* left_memref = (const rc_modified_memref_t*)left->value.memref; + const rc_modified_memref_t* right_memref = (const rc_modified_memref_t*)right->value.memref; + return (left_memref->modifier_type == right_memref->modifier_type && + rc_operands_are_equal(&left_memref->parent, &right_memref->parent) && + rc_operands_are_equal(&left_memref->modifier, &right_memref->modifier)); + } + + default: + return (left->value.memref->address == right->value.memref->address && + left->value.memref->value.size == right->value.memref->value.size); + } +} + +static int rc_memsize_is_float(uint8_t size) { + switch (size) { case RC_MEMSIZE_FLOAT: case RC_MEMSIZE_FLOAT_BE: case RC_MEMSIZE_DOUBLE32: @@ -347,8 +419,24 @@ int rc_operand_is_float_memref(const rc_operand_t* self) { } } -int rc_operand_is_memref(const rc_operand_t* self) { - switch (self->type) { +int rc_operand_is_float_memref(const rc_operand_t* self) { + if (!rc_operand_is_memref(self)) + return 0; + + if (self->type == RC_OPERAND_RECALL) + return rc_memsize_is_float(self->memref_access_type); + + if (self->value.memref->value.memref_type == RC_MEMREF_TYPE_MODIFIED_MEMREF) { + const rc_modified_memref_t* memref = (const rc_modified_memref_t*)self->value.memref; + if (memref->modifier_type != RC_OPERATOR_INDIRECT_READ) + return rc_memsize_is_float(self->value.memref->value.size); + } + + return rc_memsize_is_float(self->size); +} + +int rc_operand_type_is_memref(uint8_t type) { + switch (type) { case RC_OPERAND_CONST: case RC_OPERAND_FP: case RC_OPERAND_LUA: @@ -360,6 +448,10 @@ int rc_operand_is_memref(const rc_operand_t* self) { } } +int rc_operand_is_memref(const rc_operand_t* self) { + return rc_operand_type_is_memref(self->type); +} + int rc_operand_is_recall(const rc_operand_t* self) { switch (self->type) { case RC_OPERAND_RECALL: @@ -465,7 +557,44 @@ uint32_t rc_transform_operand_value(uint32_t value, const rc_operand_t* self) { return value; } -void rc_evaluate_operand(rc_typed_value_t* result, rc_operand_t* self, rc_eval_state_t* eval_state) { +void rc_operand_addsource(rc_operand_t* self, rc_parse_state_t* parse, uint8_t new_size) { + rc_modified_memref_t* modified_memref; + + if (rc_operand_is_memref(&parse->addsource_parent)) { + rc_operand_t modifier; + + if ((self->type == RC_OPERAND_DELTA || self->type == RC_OPERAND_PRIOR) && + self->type == parse->addsource_parent.type) { + /* if adding prev(x) and prev(y), just add x and y and take the prev of that */ + memcpy(&modifier, self, sizeof(modifier)); + modifier.type = parse->addsource_parent.type = RC_OPERAND_ADDRESS; + + modified_memref = rc_alloc_modified_memref(parse, + new_size, &parse->addsource_parent, parse->addsource_oper, &modifier); + } + else { + modified_memref = rc_alloc_modified_memref(parse, + new_size, &parse->addsource_parent, parse->addsource_oper, self); + } + } + else { + /* N + A => A + N */ + /* -N + A => A - N */ + modified_memref = rc_alloc_modified_memref(parse, + new_size, &parse->addsource_parent, parse->addsource_oper, self); + } + + self->value.memref = (rc_memref_t*)modified_memref; + + /* if adding a constant, change the type to be address (current value) */ + if (!rc_operand_is_memref(self)) + self->type = self->memref_access_type = RC_OPERAND_ADDRESS; + + /* result of an AddSource operation is always a 32-bit integer (even if parent or modifier is a float) */ + self->size = RC_MEMSIZE_32_BITS; +} + +void rc_evaluate_operand(rc_typed_value_t* result, const rc_operand_t* self, rc_eval_state_t* eval_state) { #ifndef RC_DISABLE_LUA rc_luapeek_t luapeek; #endif /* RC_DISABLE_LUA */ @@ -510,16 +639,28 @@ void rc_evaluate_operand(rc_typed_value_t* result, rc_operand_t* self, rc_eval_s #endif /* RC_DISABLE_LUA */ - break; + return; case RC_OPERAND_RECALL: - result->type = eval_state->recall_value.type; - result->value = eval_state->recall_value.value; - return; + if (!rc_operand_type_is_memref(self->memref_access_type)) { + rc_operand_t recall; + memcpy(&recall, self, sizeof(recall)); + recall.type = self->memref_access_type; + rc_evaluate_operand(result, &recall, eval_state); + return; + } + + if (!self->value.memref) { + result->type = RC_VALUE_TYPE_UNSIGNED; + result->value.u32 = 0; + return; + } + + rc_get_memref_value(result, self->value.memref, self->memref_access_type); + break; default: - result->type = RC_VALUE_TYPE_UNSIGNED; - result->value.u32 = rc_get_memref_value(self->value.memref, self->type, eval_state); + rc_get_memref_value(result, self->value.memref, self->type); break; } diff --git a/src/rcheevos/rc_internal.h b/src/rcheevos/rc_internal.h index fa913fec..1fc9125a 100644 --- a/src/rcheevos/rc_internal.h +++ b/src/rcheevos/rc_internal.h @@ -13,9 +13,34 @@ typedef struct rc_scratch_string { } rc_scratch_string_t; +typedef struct rc_modified_memref_t { + rc_memref_t memref; /* for compatibility with rc_operand_t.value.memref */ + rc_operand_t parent; /* The parent memref this memref is derived from (type will always be a memref type) */ + rc_operand_t modifier; /* The modifier to apply to the parent. */ + uint8_t modifier_type; /* How to apply the modifier to the parent. (RC_OPERATOR_*) */ +} +rc_modified_memref_t; + +/* enum helpers for natvis expansion. Have to use a struct to define the mapping, + * and a single field to allow the conditional logic to switch on the value */ +typedef struct __rc_bool_enum_t { uint8_t value; } __rc_bool_enum_t; +typedef struct __rc_memsize_enum_t { uint8_t value; } __rc_memsize_enum_t; +typedef struct __rc_memsize_enum_func_t { uint8_t value; } __rc_memsize_enum_func_t; +typedef struct __rc_operand_enum_t { uint8_t value; } __rc_operand_enum_t; +typedef struct __rc_value_type_enum_t { uint8_t value; } __rc_value_type_enum_t; +typedef struct __rc_memref_type_enum_t { uint8_t value; } __rc_memref_type_enum_t; +typedef struct __rc_condition_enum_t { uint8_t value; } __rc_condition_enum_t; +typedef struct __rc_condition_enum_str_t { uint8_t value; } __rc_condition_enum_str_t; +typedef struct __rc_operator_enum_t { uint8_t value; } __rc_operator_enum_t; +typedef struct __rc_operator_enum_str_t { uint8_t value; } __rc_operator_enum_str_t; +typedef struct __rc_operand_memref_t { rc_operand_t operand; } __rc_operand_memref_t; /* requires &rc_operand_t to be the same as &rc_operand_t.value.memref */ +typedef struct __rc_memref_list_t { rc_memref_t* first_memref; } __rc_memref_list_t; +typedef struct __rc_value_list_t { rc_value_t* first_value; } __rc_value_list_t; + #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_modified_memref_t) RC_ALLOW_ALIGN(rc_lboard_t) RC_ALLOW_ALIGN(rc_memref_t) RC_ALLOW_ALIGN(rc_operand_t) @@ -45,6 +70,7 @@ typedef struct { struct objs { rc_condition_t* __rc_condition_t; rc_condset_t* __rc_condset_t; + rc_modified_memref_t* __rc_modified_memref_t; rc_lboard_t* __rc_lboard_t; rc_memref_t* __rc_memref_t; rc_operand_t* __rc_operand_t; @@ -56,6 +82,24 @@ typedef struct { rc_scratch_string_t __rc_scratch_string_t; rc_trigger_t* __rc_trigger_t; rc_value_t* __rc_value_t; + + /* these fields aren't actually used by the code, but they force the + * virtual enum wrapper types to exist so natvis can use them */ + union { + __rc_bool_enum_t boolean; + __rc_memsize_enum_t memsize; + __rc_memsize_enum_func_t memsize_func; + __rc_operand_enum_t operand; + __rc_value_type_enum_t value_type; + __rc_memref_type_enum_t memref_type; + __rc_condition_enum_t condition; + __rc_condition_enum_str_t condition_str; + __rc_operator_enum_t oper; + __rc_operator_enum_str_t oper_str; + __rc_operand_memref_t operand_memref; + __rc_memref_list_t memref_list; + __rc_value_list_t value_list; + } natvis_extension; } objs; } rc_scratch_t; @@ -78,19 +122,23 @@ typedef struct { } rc_typed_value_t; +enum { + RC_MEMREF_TYPE_MEMREF, /* rc_memref_t */ + RC_MEMREF_TYPE_MODIFIED_MEMREF, /* rc_indirect_memref_t */ + RC_MEMREF_TYPE_VALUE /* rc_value_t */ +}; + #define RC_MEASURED_UNKNOWN 0xFFFFFFFF +#define RC_OPERAND_NONE 0xFF typedef struct { - rc_typed_value_t add_value;/* AddSource/SubSource */ - int32_t add_hits; /* AddHits */ - uint32_t add_address; /* AddAddress */ + int32_t add_hits; /* AddHits */ rc_peek_t peek; void* peek_userdata; lua_State* L; rc_typed_value_t measured_value; /* Measured */ - rc_typed_value_t recall_value; /* Set by RC_CONDITION_REMEMBER */ uint8_t was_reset; /* ResetIf triggered */ uint8_t has_hits; /* one of more hit counts is non-zero */ uint8_t primed; /* true if all non-Trigger conditions are true */ @@ -114,6 +162,12 @@ typedef struct { uint32_t measured_target; int lines_read; + rc_operand_t addsource_parent; + rc_operand_t indirect_parent; + rc_operand_t remember; + uint8_t addsource_oper; + + uint8_t is_value; uint8_t has_required_hits; uint8_t measured_as_percent; } @@ -128,11 +182,14 @@ void* rc_alloc(void* pointer, int32_t* offset, uint32_t size, uint32_t alignment void* rc_alloc_scratch(void* pointer, int32_t* offset, uint32_t size, uint32_t alignment, rc_scratch_t* scratch, uint32_t scratch_object_pointer_offset); char* rc_alloc_str(rc_parse_state_t* parse, const char* text, size_t length); -rc_memref_t* rc_alloc_memref(rc_parse_state_t* parse, uint32_t address, uint8_t size, uint8_t is_indirect); +rc_memref_t* rc_alloc_memref(rc_parse_state_t* parse, uint32_t address, uint8_t size); +rc_modified_memref_t* rc_alloc_modified_memref(rc_parse_state_t* parse, uint8_t size, const rc_operand_t* parent, + uint8_t modifier_type, const rc_operand_t* modifier); int rc_parse_memref(const char** memaddr, uint8_t* size, uint32_t* address); void rc_update_memref_values(rc_memref_t* memref, rc_peek_t peek, void* ud); void rc_update_memref_value(rc_memref_value_t* memref, uint32_t value); -uint32_t rc_get_memref_value(rc_memref_t* memref, int operand_type, rc_eval_state_t* eval_state); +void rc_get_memref_value(rc_typed_value_t* value, rc_memref_t* memref, int operand_type); +uint32_t rc_get_modified_memref_value(const rc_modified_memref_t* memref, rc_peek_t peek, void* ud); uint8_t rc_memref_shared_size(uint8_t size); uint32_t rc_memref_mask(uint8_t size); void rc_transform_memref_value(rc_typed_value_t* value, uint8_t size); @@ -141,7 +198,7 @@ uint32_t rc_peek_value(uint32_t address, uint8_t size, rc_peek_t peek, void* ud) void rc_parse_trigger_internal(rc_trigger_t* self, const char** memaddr, rc_parse_state_t* parse); int rc_trigger_state_active(int state); -rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse, int is_value); +rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse); int rc_test_condset(rc_condset_t* self, rc_eval_state_t* eval_state); void rc_reset_condset(rc_condset_t* self); @@ -161,16 +218,23 @@ enum { RC_PROCESSING_COMPARE_ALWAYS_FALSE }; -rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse, uint8_t is_indirect); +rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse); +void rc_condition_update_parse_state(rc_condition_t* condition, rc_parse_state_t* parse); int rc_test_condition(rc_condition_t* self, rc_eval_state_t* eval_state); void rc_evaluate_condition_value(rc_typed_value_t* value, rc_condition_t* self, rc_eval_state_t* eval_state); int rc_condition_is_combining(const rc_condition_t* self); +void rc_condition_convert_to_operand(const rc_condition_t* condition, rc_operand_t* operand, rc_parse_state_t* parse); -int rc_parse_operand(rc_operand_t* self, const char** memaddr, uint8_t is_indirect, rc_parse_state_t* parse); -void rc_evaluate_operand(rc_typed_value_t* value, rc_operand_t* self, rc_eval_state_t* eval_state); +int rc_parse_operand(rc_operand_t* self, const char** memaddr, rc_parse_state_t* parse); +void rc_evaluate_operand(rc_typed_value_t* value, const rc_operand_t* self, rc_eval_state_t* eval_state); int rc_operand_is_float_memref(const rc_operand_t* self); int rc_operand_is_float(const rc_operand_t* self); int rc_operand_is_recall(const rc_operand_t* self); +int rc_operand_type_is_memref(uint8_t type); +int rc_operands_are_equal(const rc_operand_t* left, const rc_operand_t* right); +void rc_operand_addsource(rc_operand_t* self, rc_parse_state_t* parse, uint8_t new_size); +void rc_operand_set_const(rc_operand_t* self, uint32_t value); +void rc_operand_set_float_const(rc_operand_t* self, double value); int rc_is_valid_variable_character(char ch, int is_first); void rc_parse_value_internal(rc_value_t* self, const char** memaddr, rc_parse_state_t* parse); @@ -187,6 +251,7 @@ void rc_typed_value_divide(rc_typed_value_t* value, const rc_typed_value_t* amou void rc_typed_value_modulus(rc_typed_value_t* value, const rc_typed_value_t* amount); void rc_typed_value_negate(rc_typed_value_t* value); int rc_typed_value_compare(const rc_typed_value_t* value1, const rc_typed_value_t* value2, char oper); +void rc_typed_value_combine(rc_typed_value_t* value, rc_typed_value_t* amount, uint8_t oper); void rc_typed_value_from_memref_value(rc_typed_value_t* value, const rc_memref_value_t* memref); int rc_format_typed_value(char* buffer, size_t size, const rc_typed_value_t* value, int format); diff --git a/src/rcheevos/rc_runtime_types.natvis b/src/rcheevos/rc_runtime_types.natvis new file mode 100644 index 00000000..6a9e512f --- /dev/null +++ b/src/rcheevos/rc_runtime_types.natvis @@ -0,0 +1,316 @@ + + + + + + {value.u32} (RC_VALUE_TYPE_UNSIGNED) + {value.f32} (RC_VALUE_TYPE_FLOAT) + {value.i32} (RC_VALUE_TYPE_SIGNED) + none (RC_VALUE_TYPE_NONE) + {value.i32} (unknown) + + + false + true + true ({value}) + + + {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} + {RC_MEMSIZE_16_BITS_BE} + {RC_MEMSIZE_24_BITS_BE} + {RC_MEMSIZE_32_BITS_BE} + {RC_MEMSIZE_FLOAT} + {RC_MEMSIZE_MBF32} + {RC_MEMSIZE_MBF32_LE} + {RC_MEMSIZE_FLOAT_BE} + {RC_MEMSIZE_DOUBLE32} + {RC_MEMSIZE_DOUBLE32_BE} + {RC_MEMSIZE_VARIABLE} + unknown ({value}) + + + {RC_VALUE_TYPE_NONE} + {RC_VALUE_TYPE_UNSIGNED} + {RC_VALUE_TYPE_SIGNED} + {RC_VALUE_TYPE_FLOAT} + unknown ({value}) + + + {RC_MEMREF_TYPE_MEMREF} + {RC_MEMREF_TYPE_MODIFIED_MEMREF} + + unknown ({value}) + + + byte + word + tbyte + dword + lower4 + upper4 + bit0 + bit1 + bit2 + bit3 + bit4 + bit5 + bit6 + bit7 + bitcount + word_be + tbyte_be + dword_be + float + mbf32 + mbf32_le + float_be + double32 + double32_be + var + unknown + + + {{value={(float)*((float*)&value)} prior={(float)*((float*)&prior)}}} + {{value={(int)value} prior={(int)prior}}} + {{value={value,x} prior={prior,x}}} + + value + prior + *((__rc_memsize_enum_t*)&size) + *((__rc_bool_enum_t*)&changed) + *((__rc_value_type_enum_t*)&type) + *((__rc_memref_type_enum_t*)&memref_type) + + + + {*(rc_modified_memref_t*)&value} + var + {*((__rc_memsize_enum_func_t*)&value.size)}({address,x}) + + (rc_modified_memref_t*)&value + value + address + next + + + + ... {*((__rc_memsize_enum_func_t*)&operand.size)} {operand.value.memref->address,x} + {*((__rc_memsize_enum_func_t*)&operand.size)} {operand.value.memref->address,x} + + (rc_modified_memref_t*)&operand.value + operand.value.memref->value + operand.value.memref->address + operand.value.memref->next + + + + {RC_OPERAND_ADDRESS} + {RC_OPERAND_DELTA} + {RC_OPERAND_CONST} + {RC_OPERAND_FP} + {RC_OPERAND_LUA} + {RC_OPERAND_PRIOR} + {RC_OPERAND_BCD} + {RC_OPERAND_INVERTED} + {RC_OPERAND_RECALL} + RC_OPERAND_NONE (255) + unknown ({value}) + + + {{{*(__rc_operand_memref_t*)&value.memref}}} + {{delta {*(__rc_operand_memref_t*)&value.memref}}} + {{prior {*(__rc_operand_memref_t*)&value.memref}}} + {{bcd {*(__rc_operand_memref_t*)&value.memref}}} + {{inverted {*(__rc_operand_memref_t*)&value.memref}}} + {{value {(int)value.num}}} + {{value {value.num,x}}} + {{value {value.num}}} + {{value {value.dbl}}} + {{recall}} + {{lua @{value.luafunc}}} + {{none}} + {{unknown}} + + value.num + value.dbl + value.num + value.dbl + value.memref + value.memref + (rc_modified_memref_t*)value.memref + *((__rc_operand_enum_t*)&type) + *((__rc_memsize_enum_t*)&size) + *((__rc_operand_enum_t*)&memref_access_type) + + + + {RC_CONDITION_STANDARD} + {RC_CONDITION_PAUSE_IF} + {RC_CONDITION_RESET_IF} + {RC_CONDITION_MEASURED_IF} + {RC_CONDITION_TRIGGER} + {RC_CONDITION_MEASURED} + {RC_CONDITION_ADD_SOURCE} + {RC_CONDITION_SUB_SOURCE} + {RC_CONDITION_ADD_ADDRESS} + {RC_CONDITION_REMEMBER} + {RC_CONDITION_ADD_HITS} + {RC_CONDITION_SUB_HITS} + {RC_CONDITION_RESET_NEXT_IF} + {RC_CONDITION_AND_NEXT} + {RC_CONDITION_OR_NEXT} + unknown ({value}) + + + + PauseIf + ResetIf + MeasuredIf + Trigger + Measured + AddSource + SubSource + AddAddress + Remember + AddHits + SubHits + ResetNextIf + AndNext + OrNext + {value} + + + {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} + {RC_OPERATOR_XOR} + {RC_OPERATOR_MOD} + {RC_OPERATOR_ADD} + {RC_OPERATOR_SUB} + {RC_OPERATOR_SUB_PARENT} + {RC_OPERATOR_INDIRECT_READ} + unknown ({value}) + + + == + < + <= + > + >= + != + + * + / + & + ^ + % + + + - + subtracted from + $ + unknown ({value}) + + + {*((__rc_condition_enum_str_t*)&type)} {operand1} + {*((__rc_condition_enum_str_t*)&type)} {operand1} ({required_hits}) + {*((__rc_condition_enum_str_t*)&type)} {operand1} {*((__rc_operator_enum_str_t*)&oper)} {operand2} + {*((__rc_condition_enum_str_t*)&type)} {operand1} {*((__rc_operator_enum_str_t*)&oper)} {operand2} ({required_hits}) + + *((__rc_condition_enum_t*)&type) + operand1 + *((__rc_operator_enum_t*)&oper) + operand2 + required_hits + current_hits + next + + + + + + conditions + next + this + + + + + $({parent} + {modifier}) + ({modifier} - {parent}) + ({parent} {*((__rc_operator_enum_str_t*)&modifier_type)} {modifier}) + + memref.value + parent + *((__rc_operator_enum_t*)&modifier_type) + modifier + + + + {value} {name,s} + + value + conditions + name + *((__rc_memref_list_t*)&memrefs) + next + + + + {{NULL}} + {(void**)&first_memref,na} + + + first_memref + next + this + + + + + {{NULL}} + {(void*)&first_value,na} + + + first_value + next + this + + + + + {{offset={offset} addsource_parent={addsource_parent} indirect_parent={indirect_parent}}} + + offset + ((__rc_memref_list_t*)first_memref) + ((__rc_value_list_t*)&variables) + addsource_parent + *((__rc_operator_enum_t*)&addsource_oper) + indirect_parent + remember + *((__rc_bool_enum_t*)&is_value) + *((__rc_bool_enum_t*)&has_required_hits) + *((__rc_bool_enum_t*)&measured_as_percent) + + + diff --git a/src/rcheevos/rc_validate.c b/src/rcheevos/rc_validate.c index 770f9dc9..98b24212 100644 --- a/src/rcheevos/rc_validate.c +++ b/src/rcheevos/rc_validate.c @@ -104,6 +104,7 @@ static uint32_t rc_max_value(const rc_operand_t* operand) return 8; case RC_MEMSIZE_8_BITS: + /* NOTE: BCD should max out at 0x99, but because each digit can be 15, it actually maxes at 15*10 + 15 */ return (operand->type == RC_OPERAND_BCD) ? 165 : 0xFF; case RC_MEMSIZE_16_BITS: @@ -160,10 +161,18 @@ static uint32_t rc_scale_value(uint32_t value, uint8_t oper, const rc_operand_t* case RC_OPERATOR_SUB: { - if (operand->type == RC_OPERAND_CONST) - return value - operand->value.num; - else if (value > rc_max_value(operand)) - return value - rc_max_value(operand); + const uint32_t op_max = (operand->type == RC_OPERAND_CONST) ? operand->value.num : rc_max_value(operand); + if (value > op_max) + return value - op_max; + + return 0xFFFFFFFF; + } + + case RC_OPERATOR_SUB_PARENT: + { + const uint32_t op_max = (operand->type == RC_OPERAND_CONST) ? operand->value.num : rc_max_value(operand); + if (op_max > value) + return op_max - value; return 0xFFFFFFFF; } @@ -173,6 +182,17 @@ static uint32_t rc_scale_value(uint32_t value, uint8_t oper, const rc_operand_t* } } +static uint32_t rc_max_chain_value(const rc_operand_t* operand) +{ + if (rc_operand_is_memref(operand) && operand->value.memref->value.memref_type == RC_MEMREF_TYPE_MODIFIED_MEMREF) { + const rc_modified_memref_t* modified_memref = (const rc_modified_memref_t*)operand->value.memref; + const uint32_t op_max = rc_max_chain_value(&modified_memref->parent); + return rc_scale_value(op_max, modified_memref->modifier_type, &modified_memref->modifier); + } + + return rc_max_value(operand); +} + static int rc_validate_get_condition_index(const rc_condset_t* condset, const rc_condition_t* condition) { int index = 1; @@ -256,18 +276,14 @@ static int rc_validate_range(uint32_t min_val, uint32_t max_val, char oper, uint return 1; } -int rc_validate_condset_internal(const rc_condset_t* condset, char result[], const size_t result_size, uint32_t console_id, uint32_t max_address) +static int rc_validate_condset_internal(const rc_condset_t* condset, char result[], const size_t result_size, uint32_t console_id, uint32_t max_address) { const rc_condition_t* cond; char buffer[128]; - uint32_t max_val; int index = 1; - unsigned long long add_source_max = 0; int in_add_hits = 0; int in_add_address = 0; int is_combining = 0; - int remember_used = 0; - int remember_used_in_pause = 0; if (!condset) { *result = '\0'; @@ -275,10 +291,8 @@ int rc_validate_condset_internal(const rc_condset_t* condset, char result[], con } for (cond = condset->conditions; cond; cond = cond->next, ++index) { - uint32_t max = rc_max_value(&cond->operand1); const int is_memref1 = rc_operand_is_memref(&cond->operand1); const int is_memref2 = rc_operand_is_memref(&cond->operand2); - const int uses_recall = rc_operand_is_recall(&cond->operand1) || rc_operand_is_recall(&cond->operand2); if (!in_add_address) { if (is_memref1 && !rc_validate_memref(cond->operand1.value.memref, buffer, sizeof(buffer), console_id, max_address)) { @@ -294,39 +308,24 @@ int rc_validate_condset_internal(const rc_condset_t* condset, char result[], con in_add_address = 0; } - if (!remember_used && uses_recall) { - if (!cond->pause && condset->has_pause) { - /* pause conditions will be processed before non-pause conditions. - * scan forward for any remembers in yet-to-be-processed pause conditions */ - const rc_condition_t* cond_rem_pause_check = cond->next; - for (; cond_rem_pause_check; cond_rem_pause_check = cond_rem_pause_check->next) { - if (cond_rem_pause_check->type == RC_CONDITION_REMEMBER && cond_rem_pause_check->pause) { - remember_used = 1; /* do not set remember_used_in_pause here because we don't know at which poing in the pause processing this remember is occurring. */ - break; - } - } - } - if (!remember_used) { + if (rc_operand_is_recall(&cond->operand1)) { + if (rc_operand_type_is_memref(cond->operand1.memref_access_type) && !cond->operand1.value.memref) { snprintf(result, result_size, "Condition %d: Recall used before Remember", index); return 0; } } - else if (cond->pause && uses_recall && !remember_used_in_pause) { - snprintf(result, result_size, "Condition %d: Recall used in Pause processing before Remember was used in Pause processing", index); - return 0; + + if (rc_operand_is_recall(&cond->operand2)) { + if (rc_operand_type_is_memref(cond->operand2.memref_access_type) && !cond->operand2.value.memref) { + snprintf(result, result_size, "Condition %d: Recall used before Remember", index); + return 0; + } } switch (cond->type) { case RC_CONDITION_ADD_SOURCE: - max = rc_scale_value(max, cond->oper, &cond->operand2); - add_source_max += max; - is_combining = 1; - continue; - case RC_CONDITION_SUB_SOURCE: - max = rc_scale_value(max, cond->oper, &cond->operand2); - if (add_source_max < max) /* potential underflow - may be expected */ - add_source_max = 0xFFFFFFFF; + case RC_CONDITION_REMEMBER: is_combining = 1; continue; @@ -339,12 +338,6 @@ int rc_validate_condset_internal(const rc_condset_t* condset, char result[], con is_combining = 1; continue; - case RC_CONDITION_REMEMBER: - is_combining = 1; - remember_used = 1; - remember_used_in_pause += cond->pause; - continue; - case RC_CONDITION_ADD_HITS: case RC_CONDITION_SUB_HITS: in_add_hits = 1; @@ -377,18 +370,11 @@ int rc_validate_condset_internal(const rc_condset_t* condset, char result[], con break; } - /* if we're in an add source chain, check for overflow */ - if (add_source_max) { - const unsigned long long overflow = add_source_max + max; - if (overflow > 0xFFFFFFFFUL) - max = 0xFFFFFFFF; - else - max += (unsigned)add_source_max; - } - /* check for comparing two differently sized memrefs */ - max_val = rc_max_value(&cond->operand2); - if (max_val != max && add_source_max == 0 && is_memref1 && is_memref2) { + if (is_memref1 && is_memref2 && + cond->operand1.value.memref->value.memref_type == RC_MEMREF_TYPE_MEMREF && + cond->operand2.value.memref->value.memref_type == RC_MEMREF_TYPE_MEMREF && + rc_max_value(&cond->operand1) != rc_max_value(&cond->operand2)) { snprintf(result, result_size, "Condition %d: Comparing different memory sizes", index); return 0; } @@ -396,19 +382,23 @@ int rc_validate_condset_internal(const rc_condset_t* condset, char result[], con if (is_memref1 && rc_operand_is_float(&cond->operand1)) { /* if left side is a float, right side will be converted to a float, so don't do range validation */ } - else if (is_memref1 || is_memref2 || add_source_max) { - /* if either side is a memref, or there's a running add source chain, check for impossible comparisons */ + else if (is_memref1 || is_memref2) { + /* if either side is a memref, check for impossible comparisons */ const size_t prefix_length = snprintf(result, result_size, "Condition %d: ", index); const rc_operand_t* operand1 = &cond->operand1; const rc_operand_t* operand2 = &cond->operand2; uint8_t oper = cond->oper; + uint32_t max = rc_max_chain_value(operand1); + uint32_t max_val = rc_max_value(operand2); uint32_t min_val; - if (!is_memref1 && !add_source_max) { + if (!is_memref1) { /* pretend constant was on right side */ operand1 = &cond->operand2; operand2 = &cond->operand1; - max = max_val; + max_val = max; + max = rc_max_value(&cond->operand2); + switch (oper) { case RC_OPERATOR_LT: oper = RC_OPERATOR_GT; break; case RC_OPERATOR_LE: oper = RC_OPERATOR_GE; break; @@ -426,8 +416,7 @@ int rc_validate_condset_internal(const rc_condset_t* condset, char result[], con min_val = (int)operand2->value.dbl; /* cannot compare an integer memory reference to a non-integral floating point value */ - if (!add_source_max && !rc_operand_is_float_memref(operand1) && - (float)min_val != operand2->value.dbl) { + if (!rc_operand_is_float_memref(operand1) && (float)min_val != operand2->value.dbl) { switch (oper) { case RC_OPERATOR_EQ: snprintf(result + prefix_length, result_size - prefix_length, "Comparison is never true"); @@ -455,8 +444,6 @@ int rc_validate_condset_internal(const rc_condset_t* condset, char result[], con if (!rc_validate_range(min_val, max_val, oper, max, result + prefix_length, result_size - prefix_length)) return 0; } - - add_source_max = 0; } if (is_combining) { @@ -697,24 +684,6 @@ static int rc_validate_comparison_overlap(int comparison1, uint32_t value1, int return RC_OVERLAP_NONE; } -static int rc_validate_are_operands_equal(const rc_operand_t* oper1, const rc_operand_t* oper2) -{ - if (oper1->type != oper2->type) - return 0; - - switch (oper1->type) - { - case RC_OPERAND_CONST: - return (oper1->value.num == oper2->value.num); - case RC_OPERAND_FP: - return (oper1->value.dbl == oper2->value.dbl); - case RC_OPERAND_RECALL: - return (oper2->type == RC_OPERAND_RECALL); - default: - return (oper1->value.memref->address == oper2->value.memref->address && oper1->size == oper2->size); - } -} - static int rc_validate_conflicting_conditions(const rc_condset_t* conditions, const rc_condset_t* compare_conditions, const char* prefix, const char* compare_prefix, char result[], const size_t result_size) { @@ -736,7 +705,7 @@ static int rc_validate_conflicting_conditions(const rc_condset_t* conditions, co for (condition = conditions->conditions; condition != NULL; condition = condition->next) { condition_chain_start = condition; - while (rc_condition_is_combining(condition)) + while (condition && rc_condition_is_combining(condition)) condition = condition->next; if (!condition) break; @@ -787,14 +756,14 @@ static int rc_validate_conflicting_conditions(const rc_condset_t* conditions, co if (compare_condition->type != condition_chain_iter->type || compare_condition->oper != condition_chain_iter->oper || compare_condition->required_hits != condition_chain_iter->required_hits || - !rc_validate_are_operands_equal(&compare_condition->operand1, &condition_chain_iter->operand1)) + !rc_operands_are_equal(&compare_condition->operand1, &condition_chain_iter->operand1)) { chain_matches = 0; break; } if (compare_condition->oper != RC_OPERATOR_NONE && - !rc_validate_are_operands_equal(&compare_condition->operand2, &condition_chain_iter->operand2)) + !rc_operands_are_equal(&compare_condition->operand2, &condition_chain_iter->operand2)) { chain_matches = 0; break; diff --git a/src/rcheevos/richpresence.c b/src/rcheevos/richpresence.c index 81fc56a1..abb925dc 100644 --- a/src/rcheevos/richpresence.c +++ b/src/rcheevos/richpresence.c @@ -26,7 +26,7 @@ static rc_memref_value_t* rc_alloc_helper_variable_memref_value(const char* mema if (end == &memaddr[memaddr_len]) { /* if it's not a derived size, we can reference the memref directly */ if (rc_memref_shared_size(size) == size) - return &rc_alloc_memref(parse, address, size, 0)->value; + return &rc_alloc_memref(parse, address, size)->value; } } diff --git a/src/rcheevos/runtime.c b/src/rcheevos/runtime.c index 25e9885d..6c065eda 100644 --- a/src/rcheevos/runtime.c +++ b/src/rcheevos/runtime.c @@ -819,7 +819,7 @@ void rc_runtime_invalidate_address(rc_runtime_t* self, uint32_t address) { rc_memref_t* memref = self->memrefs; while (memref) { - if (memref->address == address && !memref->value.is_indirect) { + if (memref->address == address && memref->value.memref_type == RC_MEMREF_TYPE_MEMREF) { /* remove the invalid memref from the chain so we don't try to evaluate it in the future. * it's still there, so anything referencing it will continue to fetch 0. */ @@ -841,7 +841,7 @@ void rc_runtime_validate_addresses(rc_runtime_t* self, rc_runtime_event_handler_ int num_invalid = 0; while (memref) { - if (!memref->value.is_indirect && !validate_handler(memref->address)) { + if (memref->value.memref_type == RC_MEMREF_TYPE_MEMREF && !validate_handler(memref->address)) { /* remove the invalid memref from the chain so we don't try to evaluate it in the future. * it's still there, so anything referencing it will continue to fetch 0. */ diff --git a/src/rcheevos/runtime_progress.c b/src/rcheevos/runtime_progress.c index 629f0e37..2f9ea96d 100644 --- a/src/rcheevos/runtime_progress.c +++ b/src/rcheevos/runtime_progress.c @@ -130,8 +130,10 @@ static int rc_runtime_progress_write_memrefs(rc_runtime_progress_t* progress) rc_memref_t* memref; uint32_t count = 0; - for (memref = progress->runtime->memrefs; memref; memref = memref->next) - ++count; + for (memref = progress->runtime->memrefs; memref; memref = memref->next) { + if (memref->value.memref_type == RC_MEMREF_TYPE_MEMREF) + ++count; + } if (count == 0) return RC_OK; @@ -146,6 +148,9 @@ static int rc_runtime_progress_write_memrefs(rc_runtime_progress_t* progress) else { uint32_t flags = 0; for (memref = progress->runtime->memrefs; memref; memref = memref->next) { + if (memref->value.memref_type != RC_MEMREF_TYPE_MEMREF) + continue; + flags = memref->value.size; if (memref->value.changed) flags |= RC_MEMREF_FLAG_CHANGED_THIS_FRAME; @@ -162,6 +167,67 @@ static int rc_runtime_progress_write_memrefs(rc_runtime_progress_t* progress) return RC_OK; } +static void rc_runtime_progress_update_modified_memrefs(rc_runtime_progress_t* progress) +{ + rc_typed_value_t value, prior_value, modifier, prior_modifier; + rc_modified_memref_t* modified_memref; + rc_operand_t prior_parent_operand, prior_modifier_operand; + rc_memref_t prior_parent_memref, prior_modifier_memref; + rc_memref_t* memref; + + for (memref = progress->runtime->memrefs; memref; memref = memref->next) { + if (memref->value.memref_type == RC_MEMREF_TYPE_MEMREF) + continue; + + memref->value.changed = 0; + modified_memref = (rc_modified_memref_t*)memref; + + /* indirect memref values are stored in conditions */ + if (modified_memref->modifier_type == RC_OPERATOR_INDIRECT_READ) + continue; + + /* non-indirect memref values can be reconstructed from the parents */ + memcpy(&prior_parent_operand, &modified_memref->parent, sizeof(prior_parent_operand)); + if (rc_operand_is_memref(&prior_parent_operand)) { + memcpy(&prior_parent_memref, modified_memref->parent.value.memref, sizeof(prior_parent_memref)); + prior_parent_memref.value.value = prior_parent_memref.value.prior; + memref->value.changed |= prior_parent_memref.value.changed; + prior_parent_operand.value.memref = &prior_parent_memref; + } + + memcpy(&prior_modifier_operand, &modified_memref->modifier, sizeof(prior_modifier_operand)); + if (rc_operand_is_memref(&prior_modifier_operand)) { + memcpy(&prior_modifier_memref, modified_memref->modifier.value.memref, sizeof(prior_modifier_memref)); + prior_modifier_memref.value.value = prior_modifier_memref.value.prior; + memref->value.changed |= prior_modifier_memref.value.changed; + prior_modifier_operand.value.memref = &prior_modifier_memref; + } + + rc_evaluate_operand(&value, &modified_memref->parent, NULL); + rc_evaluate_operand(&modifier, &modified_memref->modifier, NULL); + rc_evaluate_operand(&prior_value, &prior_parent_operand, NULL); + rc_evaluate_operand(&prior_modifier, &prior_modifier_operand, NULL); + + if (modified_memref->modifier_type == RC_OPERATOR_SUB_PARENT) { + rc_typed_value_negate(&value); + rc_typed_value_add(&value, &modifier); + + rc_typed_value_negate(&prior_value); + rc_typed_value_add(&prior_value, &prior_modifier); + } + else { + rc_typed_value_combine(&value, &modifier, modified_memref->modifier_type); + rc_typed_value_combine(&prior_value, &prior_modifier, modified_memref->modifier_type); + } + + rc_typed_value_convert(&value, modified_memref->memref.value.type); + memref->value.value = value.value.u32; + + rc_typed_value_convert(&prior_value, modified_memref->memref.value.type); + memref->value.prior = prior_value.value.u32; + } +} + static int rc_runtime_progress_read_memrefs(rc_runtime_progress_t* progress) { uint32_t entries; @@ -170,6 +236,9 @@ static int rc_runtime_progress_read_memrefs(rc_runtime_progress_t* progress) rc_memref_t* memref; rc_memref_t* first_unmatched_memref = progress->runtime->memrefs; + while (first_unmatched_memref && first_unmatched_memref->value.memref_type != RC_MEMREF_TYPE_MEMREF) + first_unmatched_memref = first_unmatched_memref->next; + /* re-read the chunk size to determine how many memrefs are present */ progress->offset -= 4; entries = rc_runtime_progress_read_uint(progress) / RC_RUNTIME_SERIALIZED_MEMREF_SIZE; @@ -184,14 +253,18 @@ static int rc_runtime_progress_read_memrefs(rc_runtime_progress_t* progress) memref = first_unmatched_memref; while (memref) { - if (memref->address == address && memref->value.size == size) { + if (memref->address == address && memref->value.size == size && memref->value.memref_type == RC_MEMREF_TYPE_MEMREF) { memref->value.value = value; memref->value.changed = (flags & RC_MEMREF_FLAG_CHANGED_THIS_FRAME) ? 1 : 0; memref->value.prior = prior; - if (memref == first_unmatched_memref) + if (memref == first_unmatched_memref) { first_unmatched_memref = memref->next; + while (first_unmatched_memref && first_unmatched_memref->value.memref_type != RC_MEMREF_TYPE_MEMREF) + first_unmatched_memref = first_unmatched_memref->next; + } + break; } @@ -201,6 +274,8 @@ static int rc_runtime_progress_read_memrefs(rc_runtime_progress_t* progress) --entries; } + rc_runtime_progress_update_modified_memrefs(progress); + return RC_OK; } @@ -215,7 +290,10 @@ static int rc_runtime_progress_is_indirect_memref(rc_operand_t* oper) return 0; default: - return oper->value.memref->value.is_indirect; + if (oper->value.memref->value.memref_type != RC_MEMREF_TYPE_MODIFIED_MEMREF) + return 0; + + return ((const rc_modified_memref_t*)oper->value.memref)->modifier_type == RC_OPERATOR_INDIRECT_READ; } } @@ -317,8 +395,8 @@ static uint32_t rc_runtime_progress_should_serialize_variable_condset(const rc_c { const rc_condition_t* condition; - /* predetermined presence of pause flag or indirect memrefs - must serialize */ - if (conditions->has_pause || conditions->has_indirect_memrefs) + /* predetermined presence of pause flag - must serialize */ + if (conditions->has_pause) return RC_VAR_FLAG_HAS_COND_DATA; /* if any conditions has required hits, must serialize */ diff --git a/src/rcheevos/trigger.c b/src/rcheevos/trigger.c index 71e186c0..f8c176f9 100644 --- a/src/rcheevos/trigger.c +++ b/src/rcheevos/trigger.c @@ -19,7 +19,7 @@ void rc_parse_trigger_internal(rc_trigger_t* self, const char** memaddr, rc_pars self->requirement = 0; } else { - self->requirement = rc_parse_condset(&aux, parse, 0); + self->requirement = rc_parse_condset(&aux, parse); if (parse->offset < 0) { return; @@ -30,7 +30,7 @@ void rc_parse_trigger_internal(rc_trigger_t* self, const char** memaddr, rc_pars while (*aux == 's' || *aux == 'S') { aux++; - *next = rc_parse_condset(&aux, parse, 0); + *next = rc_parse_condset(&aux, parse); if (parse->offset < 0) { return; diff --git a/src/rcheevos/value.c b/src/rcheevos/value.c index 25f2204e..13a2ef9a 100644 --- a/src/rcheevos/value.c +++ b/src/rcheevos/value.c @@ -27,7 +27,7 @@ static void rc_parse_cond_value(rc_value_t* self, const char** memaddr, rc_parse do { parse->measured_target = 0; /* passing is_value=1 should prevent any conflicts, but clear it out anyway */ - *next_clause = rc_parse_condset(memaddr, parse, 1); + *next_clause = rc_parse_condset(memaddr, parse); if (parse->offset < 0) { return; } @@ -112,7 +112,7 @@ void rc_parse_legacy_value(rc_value_t* self, const char** memaddr, rc_parse_stat /* process the clause */ buffer_ptr = buffer; - cond = rc_parse_condition(&buffer_ptr, parse, 0); + cond = rc_parse_condition(&buffer_ptr, parse); if (parse->offset < 0) return; @@ -142,21 +142,24 @@ void rc_parse_legacy_value(rc_value_t* self, const char** memaddr, rc_parse_stat if (**memaddr == '_') { /* add next */ + rc_condition_update_parse_state(cond, parse); next = &cond->next; continue; } if (cond->type == RC_CONDITION_SUB_SOURCE) { /* cannot change SubSource to Measured. add a dummy condition */ + rc_condition_update_parse_state(cond, parse); next = &cond->next; buffer_ptr = "A:0"; - cond = rc_parse_condition(&buffer_ptr, parse, 0); + cond = rc_parse_condition(&buffer_ptr, parse); *next = cond; } /* convert final AddSource condition to Measured */ cond->type = RC_CONDITION_MEASURED; cond->next = 0; + rc_condition_update_parse_state(cond, parse); if (**memaddr != '$') { /* end of valid string */ @@ -176,18 +179,21 @@ void rc_parse_legacy_value(rc_value_t* self, const char** memaddr, rc_parse_stat } void rc_parse_value_internal(rc_value_t* self, const char** memaddr, rc_parse_state_t* parse) { + const uint8_t was_value = parse->is_value; + parse->is_value = 1; + /* if it starts with a condition flag (M: A: B: C:), parse the conditions */ - if ((*memaddr)[1] == ':') { + if ((*memaddr)[1] == ':') rc_parse_cond_value(self, memaddr, parse); - } - else { + else rc_parse_legacy_value(self, memaddr, parse); - } self->name = "(unnamed)"; self->value.value = self->value.prior = 0; self->value.changed = 0; self->next = 0; + + parse->is_value = was_value; } int rc_value_size(const char* memaddr) { @@ -483,8 +489,12 @@ void rc_typed_value_negate(rc_typed_value_t* value) { void rc_typed_value_add(rc_typed_value_t* value, const rc_typed_value_t* amount) { rc_typed_value_t converted; - if (amount->type != value->type && value->type != RC_VALUE_TYPE_NONE) - amount = rc_typed_value_convert_into(&converted, amount, value->type); + if (amount->type != value->type && value->type != RC_VALUE_TYPE_NONE) { + if (amount->type == RC_VALUE_TYPE_FLOAT) + rc_typed_value_convert(value, RC_VALUE_TYPE_FLOAT); + else + amount = rc_typed_value_convert_into(&converted, amount, value->type); + } switch (value->type) { @@ -712,6 +722,44 @@ void rc_typed_value_modulus(rc_typed_value_t* value, const rc_typed_value_t* amo value->value.f32 = (float)fmod(value->value.f32, amount->value.f32); } +void rc_typed_value_combine(rc_typed_value_t* value, rc_typed_value_t* amount, uint8_t oper) { + switch (oper) { + case RC_OPERATOR_MULT: + rc_typed_value_multiply(value, amount); + break; + + case RC_OPERATOR_DIV: + rc_typed_value_divide(value, amount); + break; + + case RC_OPERATOR_AND: + rc_typed_value_convert(value, RC_VALUE_TYPE_UNSIGNED); + rc_typed_value_convert(amount, RC_VALUE_TYPE_UNSIGNED); + value->value.u32 &= amount->value.u32; + break; + + case RC_OPERATOR_XOR: + rc_typed_value_convert(value, RC_VALUE_TYPE_UNSIGNED); + rc_typed_value_convert(amount, RC_VALUE_TYPE_UNSIGNED); + value->value.u32 ^= amount->value.u32; + break; + + case RC_OPERATOR_MOD: + rc_typed_value_modulus(value, amount); + break; + + case RC_OPERATOR_ADD: + rc_typed_value_add(value, amount); + break; + + case RC_OPERATOR_SUB: + rc_typed_value_negate(amount); + rc_typed_value_add(value, amount); + break; + } +} + + static int rc_typed_value_compare_floats(float f1, float f2, char oper) { if (f1 == f2) { /* exactly equal */ diff --git a/test/rcheevos-test.vcxproj b/test/rcheevos-test.vcxproj index 69b991e2..9a59aeb5 100644 --- a/test/rcheevos-test.vcxproj +++ b/test/rcheevos-test.vcxproj @@ -253,6 +253,10 @@ + + + + diff --git a/test/rcheevos-test.vcxproj.filters b/test/rcheevos-test.vcxproj.filters index 9b29c9e7..fb0d9335 100644 --- a/test/rcheevos-test.vcxproj.filters +++ b/test/rcheevos-test.vcxproj.filters @@ -399,4 +399,12 @@ + + + src\rcheevos + + + tests\rcheevos + + \ No newline at end of file diff --git a/test/rcheevos/test_condition.c b/test/rcheevos/test_condition.c index 467e1a9f..04619765 100644 --- a/test/rcheevos/test_condition.c +++ b/test/rcheevos/test_condition.c @@ -35,7 +35,7 @@ static void _assert_parse_condition( rc_init_parse_state(&parse, buffer, 0, 0); rc_init_parse_state_memrefs(&parse, &memrefs); - self = rc_parse_condition(&memaddr, &parse, 0); + self = rc_parse_condition(&memaddr, &parse); rc_destroy_parse_state(&parse); ASSERT_NUM_EQUALS(self->type, expected_type); @@ -131,7 +131,7 @@ static void test_evaluate_condition(const char* memaddr, uint8_t expected_compar rc_init_parse_state(&parse, buffer, 0, 0); rc_init_parse_state_memrefs(&parse, &memrefs); - self = rc_parse_condition(&memaddr, &parse, 0); + self = rc_parse_condition(&memaddr, &parse); rc_destroy_parse_state(&parse); ASSERT_NUM_GREATER(parse.offset, 0); @@ -159,7 +159,7 @@ static void test_default_comparator(const char* memaddr) { rc_init_parse_state(&parse, buffer, 0, 0); rc_init_parse_state_memrefs(&parse, &memrefs); - condset = rc_parse_condset(&memaddr, &parse, 0); + condset = rc_parse_condset(&memaddr, &parse); rc_destroy_parse_state(&parse); ASSERT_NUM_GREATER(parse.offset, 0); @@ -187,7 +187,7 @@ static void test_evaluate_condition_float(const char* memaddr, int expected_resu rc_init_parse_state(&parse, buffer, 0, 0); rc_init_parse_state_memrefs(&parse, &memrefs); - self = rc_parse_condition(&memaddr, &parse, 0); + self = rc_parse_condition(&memaddr, &parse); rc_destroy_parse_state(&parse); ASSERT_NUM_GREATER(parse.offset, 0); @@ -213,7 +213,7 @@ static void test_condition_compare_delta() { const char* cond_str = "0xH0001>d0xH0001"; rc_init_parse_state(&parse, buffer, 0, 0); rc_init_parse_state_memrefs(&parse, &memrefs); - cond = rc_parse_condition(&cond_str, &parse, 0); + cond = rc_parse_condition(&cond_str, &parse); rc_destroy_parse_state(&parse); ASSERT_NUM_GREATER(parse.offset, 0); @@ -247,7 +247,7 @@ static void test_condition_delta_24bit() { const char* cond_str = "0xW0001>d0xW0001"; rc_init_parse_state(&parse, buffer, 0, 0); rc_init_parse_state_memrefs(&parse, &memrefs); - cond = rc_parse_condition(&cond_str, &parse, 0); + cond = rc_parse_condition(&cond_str, &parse); rc_destroy_parse_state(&parse); ASSERT_NUM_GREATER(parse.offset, 0); @@ -293,7 +293,7 @@ static void test_condition_prior_24bit() { const char* cond_str = "0xW0001>p0xW0001"; rc_init_parse_state(&parse, buffer, 0, 0); rc_init_parse_state_memrefs(&parse, &memrefs); - cond = rc_parse_condition(&cond_str, &parse, 0); + cond = rc_parse_condition(&cond_str, &parse); rc_destroy_parse_state(&parse); ASSERT_NUM_GREATER(parse.offset, 0); diff --git a/test/rcheevos/test_condset.c b/test/rcheevos/test_condset.c index 6720d367..fa143e01 100644 --- a/test/rcheevos/test_condset.c +++ b/test/rcheevos/test_condset.c @@ -18,7 +18,7 @@ static void _assert_parse_condset(rc_condset_t** condset, rc_condset_memrefs_t* rc_init_parse_state_memrefs(&parse, &memrefs->memrefs); rc_init_parse_state_variables(&parse, &memrefs->variables); - *condset = rc_parse_condset(&memaddr, &parse, 0); + *condset = rc_parse_condset(&memaddr, &parse); size = parse.offset; rc_destroy_parse_state(&parse); @@ -292,6 +292,64 @@ static void test_pauseif_hitcount_with_reset() { assert_hit_count(condset, 2, 0); } +static void test_pauseif_resetnextif() +{ + uint8_t ram[] = { 0x00, 0x12, 0x34, 0xAB, 0x56 }; + memory_t memory; + rc_condset_t* condset; + rc_condset_memrefs_t memrefs; + char buffer[2048]; + + memory.ram = ram; + memory.size = sizeof(ram); + + assert_parse_condset(&condset, &memrefs, buffer, "0xH0002=0_0xH0001=18_Z:0xH0003=1_N:0xH0000=0_P:0xH0002=25.1."); + + /* accumulate hit counts */ + assert_evaluate_condset(condset, memrefs, &memory, 0); + assert_hit_count(condset, 0, 0); + assert_hit_count(condset, 1, 1); + assert_hit_count(condset, 2, 0); + assert_hit_count(condset, 3, 1); + assert_hit_count(condset, 4, 0); + + /* pauseif triggered, non-pauseif conditions ignored */ + ram[2] = 25; + assert_evaluate_condset(condset, memrefs, &memory, 0); + assert_hit_count(condset, 0, 0); + assert_hit_count(condset, 1, 1); + assert_hit_count(condset, 2, 0); + assert_hit_count(condset, 3, 2); + assert_hit_count(condset, 4, 1); + + /* pause condition is no longer true, but its hitcount prevents the non-pauseif conditions from being processed */ + ram[2] = 10; + assert_evaluate_condset(condset, memrefs, &memory, 0); + assert_hit_count(condset, 0, 0); + assert_hit_count(condset, 1, 1); + assert_hit_count(condset, 2, 0); + assert_hit_count(condset, 3, 3); + assert_hit_count(condset, 4, 1); + + /* reset next if clears hit on pause, but not on non-pauseif conditions */ + ram[3] = 1; + assert_evaluate_condset(condset, memrefs, &memory, 0); + assert_hit_count(condset, 0, 0); + assert_hit_count(condset, 1, 2); + assert_hit_count(condset, 2, 1); /* resetnextif keeps its own hitcount */ + assert_hit_count(condset, 3, 0); + assert_hit_count(condset, 4, 0); + + /* trigger is true */ + ram[2] = 0; + assert_evaluate_condset(condset, memrefs, &memory, 1); + assert_hit_count(condset, 0, 1); + assert_hit_count(condset, 1, 3); + assert_hit_count(condset, 2, 2); /* resetnextif keeps its own hitcount */ + assert_hit_count(condset, 3, 0); + assert_hit_count(condset, 4, 0); +} + static void test_pauseif_does_not_increment_hits() { uint8_t ram[] = {0x00, 0x12, 0x34, 0xAB, 0x56}; memory_t memory; @@ -2558,6 +2616,55 @@ static void test_subsource_float() { assert_evaluate_condset(condset, memrefs, &memory, 0); } +static void test_addsource_bits_change() { + uint8_t ram[] = { 0x00, 0x12, 0x34, 0xAB, 0x56 }; + memory_t memory; + rc_condset_t* condset; + rc_condset_memrefs_t memrefs; + char buffer[2048]; + + memory.ram = ram; + memory.size = sizeof(ram); + + /* bit0(0x01) + bit1(0x01) + bit2(0x01) == 3 && delta(bit0(0x01)) + delta(bit1(0x01)) + delta(bit2(0x01)) == 2 */ + assert_parse_condset(&condset, &memrefs, buffer, "A:0xM0001_A:0xN0001_0xO0001=3_A:d0xM0001_A:d0xN0001_d0xO0001=2"); + + /* bit sum = 1 */ + assert_evaluate_condset(condset, memrefs, &memory, 0); + assert_hit_count(condset, 2, 0); + assert_hit_count(condset, 5, 0); + + /* bit sum = 3 (delta = 1) */ + ram[1] = 0x17; + assert_evaluate_condset(condset, memrefs, &memory, 0); + assert_hit_count(condset, 2, 1); + assert_hit_count(condset, 5, 0); + + /* bit sum = 2 (delta = 3) */ + ram[1] = 0x16; + assert_evaluate_condset(condset, memrefs, &memory, 0); + assert_hit_count(condset, 2, 1); + assert_hit_count(condset, 5, 0); + + /* bit sum = 1 (delta = 2) */ + ram[1] = 0x14; + assert_evaluate_condset(condset, memrefs, &memory, 0); + assert_hit_count(condset, 2, 1); + assert_hit_count(condset, 5, 1); + + /* bit sum = 2 (delta = 1) */ + ram[1] = 0x16; + assert_evaluate_condset(condset, memrefs, &memory, 0); + assert_hit_count(condset, 2, 1); + assert_hit_count(condset, 5, 1); + + /* bit sum = 3 (delta = 2) */ + ram[1] = 0x17; + assert_evaluate_condset(condset, memrefs, &memory, 1); + assert_hit_count(condset, 2, 2); + assert_hit_count(condset, 5, 2); +} + static void test_addhits() { uint8_t ram[] = {0x00, 0x12, 0x34, 0xAB, 0x56}; memory_t memory; @@ -3627,6 +3734,74 @@ static void test_addaddress_direct_pointer_prior() { assert_evaluate_condset(condset, memrefs, &memory, 1); } +static void test_addaddress_direct_pointer_change() { + uint8_t ram[] = { 0x01, 0x12, 0x34, 0xAB, 0x56 }; + memory_t memory; + rc_condset_t* condset; + rc_condset_memrefs_t memrefs; + char buffer[2048]; + + memory.ram = ram; + memory.size = sizeof(ram); + + /* byte(0x0000 + byte(0xh0000)) == 20 && delta(byte(0x0000 + byte(0xh0000))) == 22 */ + assert_parse_condset(&condset, &memrefs, buffer, "I:0xH0000=0_0xH0000=20_I:0xH0000=0_d0xH0000=22"); + + /* byte(0x0000 + 1); value=18, prev=0, prior=0 */ + assert_evaluate_condset(condset, memrefs, &memory, 0); + + /* byte(0x0000 + 1); value=18, prev=18, prior=0 */ + assert_evaluate_condset(condset, memrefs, &memory, 0); + + /* byte(0x0000 + 1); value=22, prev=18, prior=18 */ + assert_evaluate_condset(condset, memrefs, &memory, 0); + ram[1] = 22; + assert_evaluate_condset(condset, memrefs, &memory, 0); + + /* byte(0x0000 + 1); value=22, prev=22, prior=18 */ + assert_evaluate_condset(condset, memrefs, &memory, 0); + + /* byte(0x0000 + 1); value=20, prev=22, prior=22 */ + ram[1] = 20; + assert_evaluate_condset(condset, memrefs, &memory, 1); + + /* byte(0x0000 + 1); value=20, prev=20, prior=22 */ + assert_evaluate_condset(condset, memrefs, &memory, 0); + + /* point to new value */ + /* byte(0x0000 + 2); value=52, prev=20, prior=20 */ + ram[0] = 2; + assert_evaluate_condset(condset, memrefs, &memory, 0); + + /* byte(0x0000 + 2); value=52, prev=52, prior=20 */ + assert_evaluate_condset(condset, memrefs, &memory, 0); + + /* new pointed-at value is correct */ + /* byte(0x0000 + 2); value=22, prev=52, prior=52 */ + ram[2] = 22; + assert_evaluate_condset(condset, memrefs, &memory, 0); + + /* byte(0x0000 + 2); value=22, prev=22, prior=52 */ + assert_evaluate_condset(condset, memrefs, &memory, 0); + + /* point to original value. looks correct! */ + /* byte(0x0000 + 1); value=20, prev=22, prior=22 */ + ram[0] = 1; + assert_evaluate_condset(condset, memrefs, &memory, 1); + + /* point to secondary value, which is not correct */ + /* byte(0x0000 + 2); value=22, prev=20, prior=20 */ + ram[0] = 2; + assert_evaluate_condset(condset, memrefs, &memory, 0); + + /* byte(0x0000 + 2); value=22, prev=22, prior=20 */ + assert_evaluate_condset(condset, memrefs, &memory, 0); + + /* byte(0x0000 + 2); value=20, prev=22, prior=22 */ + ram[2] = 20; + assert_evaluate_condset(condset, memrefs, &memory, 1); +} + static void test_addaddress_indirect_pointer() { uint8_t ram[] = {0x01, 0x12, 0x34, 0xAB, 0x56}; memory_t memory; @@ -3780,6 +3955,86 @@ static void test_addaddress_indirect_pointer_multiple() { assert_hit_count(condset, 5, 1); } +static void test_addaddress_indirect_pointer_with_delta() +{ + uint8_t ram[] = { 0x01, 0x02, 0x03, 0x34, 0xAB, 0x56 }; + memory_t memory; + rc_condset_t* condset; + rc_condset_memrefs_t memrefs; + char buffer[2048]; + + memory.ram = ram; + memory.size = sizeof(ram); + + /* byte(byte(0)*2+2) == 22 && prev(byte(byte(0)*2+2) == 20 */ + assert_parse_condset(&condset, &memrefs, buffer, "I:0xH0000*2_0xH0002=22_I:0xH0000*2_d0xH0002=20"); + + /* byte(0) = 1, byte(1*2+2 [4]) = 0xAB, delta = 0 */ + assert_evaluate_condset(condset, memrefs, &memory, 0); + assert_hit_count(condset, 1, 0); + assert_hit_count(condset, 3, 0); + + /* byte(0) = 1, byte(1*2+2 [4]) = 22, delta = 0xAB */ + ram[4] = 22; + assert_evaluate_condset(condset, memrefs, &memory, 0); + assert_hit_count(condset, 1, 1); + assert_hit_count(condset, 3, 0); + + /* byte(0) = 1, byte(1*2+2 [4]) = 20, delta = 22 */ + ram[4] = 20; + assert_evaluate_condset(condset, memrefs, &memory, 0); + assert_hit_count(condset, 1, 1); + assert_hit_count(condset, 3, 0); + + /* byte(0) = 0, byte(0*2+2 [2]) = 2, delta = 20 */ + ram[0] = 0; + assert_evaluate_condset(condset, memrefs, &memory, 0); + assert_hit_count(condset, 1, 1); + assert_hit_count(condset, 3, 1); + + /* byte(0) = 0, byte(0*2+2 [2]) = 20, delta = 2 */ + ram[2] = 20; + ram[4] = 22; + assert_evaluate_condset(condset, memrefs, &memory, 0); + assert_hit_count(condset, 1, 1); + assert_hit_count(condset, 3, 1); + + /* byte(0) = 1, byte(1*2+2 [4]) = 22, delta = 20 */ + ram[0] = 1; + assert_evaluate_condset(condset, memrefs, &memory, 1); + assert_hit_count(condset, 1, 2); + assert_hit_count(condset, 3, 2); +} + +static void test_addaddress_indirect_constant() { + uint8_t ram[] = { 0x01, 0x12, 0x34, 0xAB, 0x56 }; + memory_t memory; + rc_condset_t* condset; + rc_condset_memrefs_t memrefs; + char buffer[2048]; + + memory.ram = ram; + memory.size = sizeof(ram); + + /* byte(0x0002 + 1) == 22 */ + assert_parse_condset(&condset, &memrefs, buffer, "I:1_0xH0002=22"); + + /* initially, byte(0x0003) == 22, false */ + assert_evaluate_condset(condset, memrefs, &memory, 0); + + /* memory at constant is correct */ + ram[1] = 22; + assert_evaluate_condset(condset, memrefs, &memory, 0); + + /* memory at address is correct */ + ram[2] = 22; + assert_evaluate_condset(condset, memrefs, &memory, 0); + + /* memory at offset address is correct */ + ram[3] = 22; + assert_evaluate_condset(condset, memrefs, &memory, 1); +} + static void test_addaddress_pointer_data_size_differs_from_pointer_size() { uint8_t ram[] = {0x01, 0x12, 0x34, 0xAB, 0x56}; memory_t memory; @@ -4044,6 +4299,37 @@ static void test_addaddress_scaled_negative() { assert_evaluate_condset(condset, memrefs, &memory, 0); } +static void test_addaddress_shared_size() +{ + uint8_t ram[] = { 0x01, 0x12, 0x34, 0xAB, 0x56 }; + memory_t memory; + rc_condset_t* condset; + rc_condset_memrefs_t memrefs; + char buffer[2048]; + + memory.ram = ram; + memory.size = sizeof(ram); + + /* bit2(byte(0) * 2) == 1 */ + assert_parse_condset(&condset, &memrefs, buffer, "I:0xH0000*2_0xO0000=1"); + + /* bit2( [0x34] ) = 1 */ + assert_evaluate_condset(condset, memrefs, &memory, 1); + + /* bit2( [0x01] ) = 0 */ + ram[2] = 1; + assert_evaluate_condset(condset, memrefs, &memory, 0); + + /* adjust pointer. bit2( [0x56] ) = 1 */ + ram[0] = 2; + assert_evaluate_condset(condset, memrefs, &memory, 1); + + /* new value is correct */ + ram[4] = 1; + assert_evaluate_condset(condset, memrefs, &memory, 0); +} + + static void test_prior_sequence() { uint8_t ram[] = {0x00}; memory_t memory; @@ -4144,6 +4430,7 @@ void test_condset(void) { TEST(test_pauseif_hitcount_one); TEST(test_pauseif_hitcount_two); TEST(test_pauseif_hitcount_with_reset); + TEST(test_pauseif_resetnextif); TEST(test_pauseif_does_not_increment_hits); TEST(test_pauseif_delta_updated); TEST(test_pauseif_indirect_delta_updated); @@ -4202,6 +4489,7 @@ void test_condset(void) { TEST(test_subsource_overflow_comparison_lesser); TEST(test_subsource_overflow_comparison_lesser_or_equal); TEST(test_subsource_float); + TEST(test_addsource_bits_change); /* addhits/subhits */ TEST(test_addhits); @@ -4230,10 +4518,13 @@ void test_condset(void) { TEST(test_addaddress_direct_pointer); TEST(test_addaddress_direct_pointer_delta); TEST(test_addaddress_direct_pointer_prior); + TEST(test_addaddress_direct_pointer_change); TEST(test_addaddress_indirect_pointer); TEST(test_addaddress_indirect_pointer_negative); TEST(test_addaddress_indirect_pointer_out_of_range); TEST(test_addaddress_indirect_pointer_multiple); + TEST(test_addaddress_indirect_pointer_with_delta); + TEST(test_addaddress_indirect_constant); TEST(test_addaddress_pointer_data_size_differs_from_pointer_size); TEST(test_addaddress_double_indirection); TEST(test_addaddress_double_indirection_with_delta); @@ -4242,6 +4533,7 @@ void test_condset(void) { TEST(test_addaddress_adjust_both_sides_different_bases); TEST(test_addaddress_scaled); TEST(test_addaddress_scaled_negative); + TEST(test_addaddress_shared_size); /* prior */ TEST(test_prior_sequence); diff --git a/test/rcheevos/test_memref.c b/test/rcheevos/test_memref.c index 50f3efd3..ba9ef5a5 100644 --- a/test/rcheevos/test_memref.c +++ b/test/rcheevos/test_memref.c @@ -274,31 +274,31 @@ static void test_allocate_shared_address() { rc_init_parse_state(&parse, NULL, 0, 0); rc_init_parse_state_memrefs(&parse, &memrefs); - rc_alloc_memref(&parse, 1, RC_MEMSIZE_8_BITS, 0); + rc_alloc_memref(&parse, 1, RC_MEMSIZE_8_BITS); ASSERT_NUM_EQUALS(get_memref_count(&parse), 1); - rc_alloc_memref(&parse, 1, RC_MEMSIZE_16_BITS, 0); /* differing size will not match */ + rc_alloc_memref(&parse, 1, RC_MEMSIZE_16_BITS); /* differing size will not match */ ASSERT_NUM_EQUALS(get_memref_count(&parse), 2); - rc_alloc_memref(&parse, 1, RC_MEMSIZE_LOW, 0); /* differing size will not match */ + rc_alloc_memref(&parse, 1, RC_MEMSIZE_LOW); /* differing size will not match */ ASSERT_NUM_EQUALS(get_memref_count(&parse), 3); - rc_alloc_memref(&parse, 1, RC_MEMSIZE_BIT_2, 0); /* differing size will not match */ + rc_alloc_memref(&parse, 1, RC_MEMSIZE_BIT_2); /* differing size will not match */ ASSERT_NUM_EQUALS(get_memref_count(&parse), 4); - rc_alloc_memref(&parse, 2, RC_MEMSIZE_8_BITS, 0); /* differing address will not match */ + rc_alloc_memref(&parse, 2, RC_MEMSIZE_8_BITS); /* differing address will not match */ ASSERT_NUM_EQUALS(get_memref_count(&parse), 5); - rc_alloc_memref(&parse, 1, RC_MEMSIZE_8_BITS, 0); /* match */ + rc_alloc_memref(&parse, 1, RC_MEMSIZE_8_BITS); /* match */ ASSERT_NUM_EQUALS(get_memref_count(&parse), 5); - rc_alloc_memref(&parse, 1, RC_MEMSIZE_16_BITS, 0); /* match */ + rc_alloc_memref(&parse, 1, RC_MEMSIZE_16_BITS); /* match */ ASSERT_NUM_EQUALS(get_memref_count(&parse), 5); - rc_alloc_memref(&parse, 1, RC_MEMSIZE_BIT_2, 0); /* match */ + rc_alloc_memref(&parse, 1, RC_MEMSIZE_BIT_2); /* match */ ASSERT_NUM_EQUALS(get_memref_count(&parse), 5); - rc_alloc_memref(&parse, 2, RC_MEMSIZE_8_BITS, 0); /* match */ + rc_alloc_memref(&parse, 2, RC_MEMSIZE_8_BITS); /* match */ ASSERT_NUM_EQUALS(get_memref_count(&parse), 5); rc_destroy_parse_state(&parse); @@ -316,38 +316,107 @@ static void test_allocate_shared_address2() { rc_init_parse_state(&parse, NULL, 0, 0); rc_init_parse_state_memrefs(&parse, &memrefs); - memref1 = rc_alloc_memref(&parse, 1, RC_MEMSIZE_8_BITS, 0); + memref1 = rc_alloc_memref(&parse, 1, RC_MEMSIZE_8_BITS); ASSERT_NUM_EQUALS(memref1->address, 1); ASSERT_NUM_EQUALS(memref1->value.size, RC_MEMSIZE_8_BITS); - ASSERT_NUM_EQUALS(memref1->value.is_indirect, 0); + ASSERT_NUM_EQUALS(memref1->value.memref_type, RC_MEMREF_TYPE_MEMREF); + ASSERT_NUM_EQUALS(memref1->value.type, RC_VALUE_TYPE_UNSIGNED); ASSERT_NUM_EQUALS(memref1->value.value, 0); ASSERT_NUM_EQUALS(memref1->value.changed, 0); ASSERT_NUM_EQUALS(memref1->value.prior, 0); ASSERT_PTR_EQUALS(memref1->next, 0); - memref2 = rc_alloc_memref(&parse, 1, RC_MEMSIZE_16_BITS, 0); /* differing size will not match */ - memref3 = rc_alloc_memref(&parse, 1, RC_MEMSIZE_LOW, 0); /* differing size will not match */ - memref4 = rc_alloc_memref(&parse, 1, RC_MEMSIZE_BIT_2, 0); /* differing size will not match */ - memref5 = rc_alloc_memref(&parse, 2, RC_MEMSIZE_8_BITS, 0); /* differing address will not match */ + memref2 = rc_alloc_memref(&parse, 1, RC_MEMSIZE_16_BITS); /* differing size will not match */ + memref3 = rc_alloc_memref(&parse, 1, RC_MEMSIZE_LOW); /* differing size will not match */ + memref4 = rc_alloc_memref(&parse, 1, RC_MEMSIZE_BIT_2); /* differing size will not match */ + memref5 = rc_alloc_memref(&parse, 2, RC_MEMSIZE_8_BITS); /* differing address will not match */ - memrefX = rc_alloc_memref(&parse, 1, RC_MEMSIZE_8_BITS, 0); /* match */ + memrefX = rc_alloc_memref(&parse, 1, RC_MEMSIZE_8_BITS); /* match */ ASSERT_PTR_EQUALS(memrefX, memref1); - memrefX = rc_alloc_memref(&parse, 1, RC_MEMSIZE_16_BITS, 0); /* match */ + memrefX = rc_alloc_memref(&parse, 1, RC_MEMSIZE_16_BITS); /* match */ ASSERT_PTR_EQUALS(memrefX, memref2); - memrefX = rc_alloc_memref(&parse, 1, RC_MEMSIZE_LOW, 0); /* match */ + memrefX = rc_alloc_memref(&parse, 1, RC_MEMSIZE_LOW); /* match */ ASSERT_PTR_EQUALS(memrefX, memref3); - memrefX = rc_alloc_memref(&parse, 1, RC_MEMSIZE_BIT_2, 0); /* match */ + memrefX = rc_alloc_memref(&parse, 1, RC_MEMSIZE_BIT_2); /* match */ ASSERT_PTR_EQUALS(memrefX, memref4); - memrefX = rc_alloc_memref(&parse, 2, RC_MEMSIZE_8_BITS, 0); /* match */ + memrefX = rc_alloc_memref(&parse, 2, RC_MEMSIZE_8_BITS); /* match */ ASSERT_PTR_EQUALS(memrefX, memref5); rc_destroy_parse_state(&parse); } +static void test_allocate_shared_indirect_address() { + rc_parse_state_t parse; + rc_memref_t* memrefs; + rc_memref_t* parent_memref1, *parent_memref2; + rc_operand_t parent1, parent2, delta1, intermediate2; + rc_modified_memref_t* child1, *child2, *child3, *child4; + rc_operand_t offset0, offset4; + rc_operand_set_const(&offset0, 0); + rc_operand_set_const(&offset4, 4); + + rc_init_parse_state(&parse, NULL, 0, 0); + rc_init_parse_state_memrefs(&parse, &memrefs); + + parent1.value.memref = parent_memref1 = rc_alloc_memref(&parse, 88, RC_MEMSIZE_16_BITS); + parent1.type = RC_OPERAND_ADDRESS; + parent1.size = RC_MEMSIZE_16_BITS; + parent2.value.memref = parent_memref2 = rc_alloc_memref(&parse, 99, RC_MEMSIZE_16_BITS); + parent2.type = RC_OPERAND_ADDRESS; + parent2.size = RC_MEMSIZE_16_BITS; + delta1.value.memref = parent_memref1; + delta1.type = RC_OPERAND_DELTA; + ASSERT_NUM_EQUALS(get_memref_count(&parse), 2); + + child1 = rc_alloc_modified_memref(&parse, RC_MEMSIZE_8_BITS, &parent1, RC_OPERATOR_INDIRECT_READ, &offset0); + ASSERT_NUM_EQUALS(get_memref_count(&parse), 3); + + /* differing size will not match */ + child2 = rc_alloc_modified_memref(&parse, RC_MEMSIZE_16_BITS, &parent1, RC_OPERATOR_INDIRECT_READ, &offset0); + ASSERT_NUM_EQUALS(get_memref_count(&parse), 4); + + /* differing parent will not match */ + child3 = rc_alloc_modified_memref(&parse, RC_MEMSIZE_8_BITS, &parent2, RC_OPERATOR_INDIRECT_READ, &offset0); + ASSERT_NUM_EQUALS(get_memref_count(&parse), 5); + + /* differing parent type will not match */ + child4 = rc_alloc_modified_memref(&parse, RC_MEMSIZE_8_BITS, &delta1, RC_OPERATOR_INDIRECT_READ, &offset0); + ASSERT_NUM_EQUALS(get_memref_count(&parse), 6); + + /* differing offset will not match */ + child4 = rc_alloc_modified_memref(&parse, RC_MEMSIZE_8_BITS, &parent1, RC_OPERATOR_INDIRECT_READ, &offset4); + ASSERT_NUM_EQUALS(get_memref_count(&parse), 7); + + /* exact match to first */ + ASSERT_PTR_EQUALS(rc_alloc_modified_memref(&parse, RC_MEMSIZE_8_BITS, &parent1, RC_OPERATOR_INDIRECT_READ, &offset0), child1); + ASSERT_NUM_EQUALS(get_memref_count(&parse), 7); + + /* exact match to differing parent */ + ASSERT_PTR_EQUALS(rc_alloc_modified_memref(&parse, RC_MEMSIZE_8_BITS, &parent2, RC_OPERATOR_INDIRECT_READ, &offset0), child3); + ASSERT_NUM_EQUALS(get_memref_count(&parse), 7); + + /* exact match to differing offset */ + ASSERT_PTR_EQUALS(rc_alloc_modified_memref(&parse, RC_MEMSIZE_8_BITS, &parent1, RC_OPERATOR_INDIRECT_READ, &offset4), child4); + ASSERT_NUM_EQUALS(get_memref_count(&parse), 7); + + /* intermediate parent */ + intermediate2.value.memref = &child2->memref; + intermediate2.type = RC_OPERAND_ADDRESS; + intermediate2.size = RC_MEMSIZE_32_BITS; + intermediate2.memref_access_type = RC_OPERAND_ADDRESS; + child4 = rc_alloc_modified_memref(&parse, RC_MEMSIZE_8_BITS, &intermediate2, RC_OPERATOR_INDIRECT_READ, &offset0); + ASSERT_NUM_EQUALS(get_memref_count(&parse), 8); + + ASSERT_PTR_EQUALS(rc_alloc_modified_memref(&parse, RC_MEMSIZE_8_BITS, &intermediate2, RC_OPERATOR_INDIRECT_READ, &offset0), child4); + ASSERT_NUM_EQUALS(get_memref_count(&parse), 8); + + rc_destroy_parse_state(&parse); +} + static void test_sizing_mode_grow_buffer() { int i; rc_parse_state_t parse; @@ -357,24 +426,24 @@ static void test_sizing_mode_grow_buffer() { /* memrefs are allocated 16 at a time */ for (i = 0; i < 100; i++) { - rc_alloc_memref(&parse, i, RC_MEMSIZE_8_BITS, 0); + rc_alloc_memref(&parse, i, RC_MEMSIZE_8_BITS); } ASSERT_NUM_EQUALS(get_memref_count(&parse), 100); /* 100 have been allocated, make sure we can still access items at various addresses without allocating more */ - rc_alloc_memref(&parse, 1, RC_MEMSIZE_8_BITS, 0); + rc_alloc_memref(&parse, 1, RC_MEMSIZE_8_BITS); ASSERT_NUM_EQUALS(get_memref_count(&parse), 100); - rc_alloc_memref(&parse, 25, RC_MEMSIZE_8_BITS, 0); + rc_alloc_memref(&parse, 25, RC_MEMSIZE_8_BITS); ASSERT_NUM_EQUALS(get_memref_count(&parse), 100); - rc_alloc_memref(&parse, 50, RC_MEMSIZE_8_BITS, 0); + rc_alloc_memref(&parse, 50, RC_MEMSIZE_8_BITS); ASSERT_NUM_EQUALS(get_memref_count(&parse), 100); - rc_alloc_memref(&parse, 75, RC_MEMSIZE_8_BITS, 0); + rc_alloc_memref(&parse, 75, RC_MEMSIZE_8_BITS); ASSERT_NUM_EQUALS(get_memref_count(&parse), 100); - rc_alloc_memref(&parse, 99, RC_MEMSIZE_8_BITS, 0); + rc_alloc_memref(&parse, 99, RC_MEMSIZE_8_BITS); ASSERT_NUM_EQUALS(get_memref_count(&parse), 100); rc_destroy_parse_state(&parse); @@ -394,8 +463,8 @@ static void test_update_memref_values() { rc_init_parse_state(&parse, NULL, 0, 0); rc_init_parse_state_memrefs(&parse, &memrefs); - memref1 = rc_alloc_memref(&parse, 1, RC_MEMSIZE_8_BITS, 0); - memref2 = rc_alloc_memref(&parse, 2, RC_MEMSIZE_8_BITS, 0); + memref1 = rc_alloc_memref(&parse, 1, RC_MEMSIZE_8_BITS); + memref2 = rc_alloc_memref(&parse, 2, RC_MEMSIZE_8_BITS); rc_update_memref_values(memrefs, peek, &memory); @@ -448,6 +517,7 @@ void test_memref(void) { TEST(test_allocate_shared_address); TEST(test_allocate_shared_address2); + TEST(test_allocate_shared_indirect_address); TEST(test_sizing_mode_grow_buffer); TEST(test_update_memref_values); diff --git a/test/rcheevos/test_operand.c b/test/rcheevos/test_operand.c index d6f0490e..60bd1d9e 100644 --- a/test/rcheevos/test_operand.c +++ b/test/rcheevos/test_operand.c @@ -10,7 +10,7 @@ static void _assert_parse_operand(rc_operand_t* self, char* buffer, const char** rc_init_parse_state(&parse, buffer, 0, 0); rc_init_parse_state_memrefs(&parse, &memrefs); - ret = rc_parse_operand(self, memaddr, 0, &parse); + ret = rc_parse_operand(self, memaddr, &parse); rc_destroy_parse_state(&parse); ASSERT_NUM_GREATER_EQUALS(ret, 0); @@ -67,7 +67,7 @@ static void test_parse_error_operand(const char* memaddr, int valid_chars, int e rc_init_parse_state(&parse, 0, 0, 0); rc_init_parse_state_memrefs(&parse, &memrefs); - ret = rc_parse_operand(&self, &memaddr, 0, &parse); + ret = rc_parse_operand(&self, &memaddr, &parse); rc_destroy_parse_state(&parse); ASSERT_NUM_EQUALS(expected_error, ret); @@ -97,7 +97,7 @@ static void test_evaluate_operand(const char* memaddr, memory_t* memory, uint32_ rc_init_parse_state(&parse, buffer, 0, 0); rc_init_parse_state_memrefs(&parse, &memrefs); - rc_parse_operand(&self, &memaddr, 0, &parse); + rc_parse_operand(&self, &memaddr, &parse); rc_destroy_parse_state(&parse); value = evaluate_operand(&self, memory, memrefs); @@ -126,7 +126,7 @@ static void test_evaluate_operand_float(const char* memaddr, memory_t* memory, d rc_init_parse_state(&parse, buffer, 0, 0); rc_init_parse_state_memrefs(&parse, &memrefs); - rc_parse_operand(&self, &memaddr, 0, &parse); + rc_parse_operand(&self, &memaddr, &parse); rc_destroy_parse_state(&parse); value = evaluate_operand_float(&self, memory, memrefs); @@ -530,7 +530,7 @@ static void test_evaluate_delta_memory_reference() { memaddr = "d0xh1"; rc_init_parse_state(&parse, buffer, 0, 0); rc_init_parse_state_memrefs(&parse, &memrefs); - rc_parse_operand(&op, &memaddr, 0, &parse); + rc_parse_operand(&op, &memaddr, &parse); rc_destroy_parse_state(&parse); ASSERT_UNUM_EQUALS(evaluate_operand(&op, &memory, memrefs), 0x00); /* first call gets uninitialized value */ @@ -568,7 +568,7 @@ void test_evaluate_prior_memory_reference() { memaddr = "p0xh1"; rc_init_parse_state(&parse, buffer, 0, 0); rc_init_parse_state_memrefs(&parse, &memrefs); - rc_parse_operand(&op, &memaddr, 0, &parse); + rc_parse_operand(&op, &memaddr, &parse); rc_destroy_parse_state(&parse); /* RC_OPERAND_PRIOR only updates when the memory value changes */ @@ -641,7 +641,7 @@ static void test_evaluate_delta_memory_reference_float() { memaddr = "dff0"; rc_init_parse_state(&parse, buffer, 0, 0); rc_init_parse_state_memrefs(&parse, &memrefs); - rc_parse_operand(&op, &memaddr, 0, &parse); + rc_parse_operand(&op, &memaddr, &parse); rc_destroy_parse_state(&parse); ASSERT_NUM_EQUALS(evaluate_operand_float(&op, &memory, memrefs), 0.0); /* first call gets uninitialized value */ diff --git a/test/rcheevos/test_rc_validate.c b/test/rcheevos/test_rc_validate.c index e39af161..85833494 100644 --- a/test/rcheevos/test_rc_validate.c +++ b/test/rcheevos/test_rc_validate.c @@ -288,7 +288,7 @@ void test_float_comparisons() { TEST_PARAMS2(test_validate_trigger, "f2.0=0xX1234", ""); TEST_PARAMS2(test_validate_trigger, "A:Ff2345_fF1234=f2.3", ""); TEST_PARAMS2(test_validate_trigger, "A:0xX2345_fF1234=f2.3", ""); - TEST_PARAMS2(test_validate_trigger, "A:Ff2345_0x1234=f2.3", ""); + TEST_PARAMS2(test_validate_trigger, "A:Ff2345_0x1234=f2.3", "Condition 2: Comparison is never true"); /* non integral comparison */ TEST_PARAMS2(test_validate_trigger, "fM1234>f2.3", ""); TEST_PARAMS2(test_validate_trigger, "fM1234>f-2.3", ""); TEST_PARAMS2(test_validate_trigger, "I:0xX2345_fM1234>f1.0", ""); @@ -413,7 +413,7 @@ void test_remember_recall_errors() { TEST_PARAMS2(test_validate_trigger, "{recall}=5_K:0xH1234&1023_K:{recall}*8_{recall}=100", "Condition 1: Recall used before Remember"); /* First remember is after first recall. */ TEST_PARAMS2(test_validate_trigger, "K:0xH1234&1023_K:{recall}*8_{recall}=100", ""); /* Recall used after Remember */ TEST_PARAMS2(test_validate_trigger, "{recall}=5_K:0xH1234*2_P:{recall}>6", ""); /* Remember sets recall in pause - no warning */ - TEST_PARAMS2(test_validate_trigger, "K:0xH1234*2_{recall}=5_P:{recall}>6", "Condition 3: Recall used in Pause processing before Remember was used in Pause processing"); /* Pause happens before remembered value. */ + TEST_PARAMS2(test_validate_trigger, "K:0xH1234*2_{recall}=5_P:{recall}>6", "Condition 3: Recall used before Remember"); /* Pause happens before remembered value. */ } void test_rc_validate(void) { diff --git a/test/rcheevos/test_richpresence.c b/test/rcheevos/test_richpresence.c index 809586ef..05947be9 100644 --- a/test/rcheevos/test_richpresence.c +++ b/test/rcheevos/test_richpresence.c @@ -225,6 +225,26 @@ static void test_conditional_display_invalid_condition_logic() { ASSERT_NUM_EQUALS(lines, 2); } +static void test_conditional_display_shared_lookup() { + uint8_t ram[] = { 0x00, 0x12, 0x34, 0xAB, 0x56 }; + memory_t memory; + rc_richpresence_t* richpresence; + char buffer[1024]; + + memory.ram = ram; + memory.size = sizeof(ram); + + assert_parse_richpresence(&richpresence, buffer, "Format:Points\nFormatType=VALUE\n\nDisplay:\n?0xH0001=1?One @Points(0xL0002)\n?0xH0001=0?Zero @Points(0xL0002)\nDefault @Points(0xL0002)"); + assert_richpresence_output(richpresence, &memory, "Default 4"); + + ram[1] = 1; + ram[2] = 24; + assert_richpresence_output(richpresence, &memory, "One 8"); + + ram[1] = 0; + assert_richpresence_output(richpresence, &memory, "Zero 8"); +} + static void test_conditional_display_whitespace_text() { uint8_t ram[] = { 0x00, 0x12, 0x34, 0xAB, 0x56 }; memory_t memory; @@ -1297,6 +1317,7 @@ void test_richpresence(void) { TEST(test_conditional_display_common_condition); TEST(test_conditional_display_duplicated_condition); TEST(test_conditional_display_invalid_condition_logic); + TEST(test_conditional_display_shared_lookup); TEST(test_conditional_display_whitespace_text); TEST(test_conditional_display_indirect); TEST(test_conditional_display_unnecessary_measured); diff --git a/test/rcheevos/test_runtime_progress.c b/test/rcheevos/test_runtime_progress.c index 38a2684e..2b2ab769 100644 --- a/test/rcheevos/test_runtime_progress.c +++ b/test/rcheevos/test_runtime_progress.c @@ -814,6 +814,59 @@ static void test_memref_shared_address() rc_runtime_destroy(&runtime); } +static void test_memref_addsource() { + uint8_t ram[] = { 1, 2, 3, 4, 5 }; + uint8_t buffer1[512]; + uint8_t buffer2[512]; + memory_t memory; + rc_runtime_t runtime; + + memory.ram = ram; + memory.size = sizeof(ram); + + rc_runtime_init(&runtime); + + /* byte(2) + byte(1) == 5 - third condition just prevents the achievement from triggering*/ + assert_activate_achievement(&runtime, 1, "A:0xH0002_0xH0001=3_0xH0004=99"); + assert_do_frame(&runtime, &memory); /* $2 = 3, $1 = 2, 3 + 2 = 5 */ + ram[1] = 3; + ram[2] = 0; + assert_do_frame(&runtime, &memory); /* $2 = 0, $1 = 3, 0 + 3 = 3 */ + assert_do_frame(&runtime, &memory); + assert_do_frame(&runtime, &memory); + assert_do_frame(&runtime, &memory); + + assert_hitcount(&runtime, 1, 0, 0, 0); + assert_hitcount(&runtime, 1, 0, 1, 4); + assert_cond_memref(&runtime, 1, 0, 1, 3, 5, 0); + + assert_serialize(&runtime, buffer1, sizeof(buffer1)); + + assert_do_frame(&runtime, &memory); + ram[1] = 6; + ram[2] = 1; + assert_do_frame(&runtime, &memory); /* $2 = 1, $1 = 6, 1 + 6 = 7 */ + assert_hitcount(&runtime, 1, 0, 0, 0); + assert_hitcount(&runtime, 1, 0, 1, 5); + assert_cond_memref(&runtime, 1, 0, 1, 7, 3, 1); + + assert_serialize(&runtime, buffer2, sizeof(buffer2)); + + reset_runtime(&runtime); + assert_deserialize(&runtime, buffer1); + assert_hitcount(&runtime, 1, 0, 0, 0); + assert_hitcount(&runtime, 1, 0, 1, 4); + assert_cond_memref(&runtime, 1, 0, 1, 3, 5, 0); + + reset_runtime(&runtime); + assert_deserialize(&runtime, buffer2); + assert_hitcount(&runtime, 1, 0, 0, 0); + assert_hitcount(&runtime, 1, 0, 1, 5); + assert_cond_memref(&runtime, 1, 0, 1, 7, 3, 1); + + rc_runtime_destroy(&runtime); +} + static void test_memref_indirect() { uint8_t ram[] = { 1, 2, 3, 4, 5 }; @@ -1687,6 +1740,7 @@ void test_runtime_progress(void) { TEST(test_no_core_group); TEST(test_memref_shared_address); + TEST(test_memref_addsource); TEST(test_memref_indirect); TEST(test_memref_double_indirect); diff --git a/test/rcheevos/test_trigger.c b/test/rcheevos/test_trigger.c index 3d303fea..286a5581 100644 --- a/test/rcheevos/test_trigger.c +++ b/test/rcheevos/test_trigger.c @@ -3,13 +3,14 @@ #include "../test_framework.h" #include "mock_memory.h" -static void _assert_parse_trigger(rc_trigger_t** trigger, void* buffer, const char* memaddr) +static void _assert_parse_trigger(rc_trigger_t** trigger, void* buffer, size_t buffer_size, const char* memaddr) { int size; unsigned* overflow; size = rc_trigger_size(memaddr); ASSERT_NUM_GREATER(size, 0); + ASSERT_NUM_LESS_EQUALS(size, buffer_size); overflow = (unsigned*)(((char*)buffer) + size); *overflow = 0xCDCDCDCD; @@ -21,7 +22,7 @@ static void _assert_parse_trigger(rc_trigger_t** trigger, void* buffer, const ch ASSERT_FAIL("write past end of buffer"); } } -#define assert_parse_trigger(trigger, buffer, memaddr) ASSERT_HELPER(_assert_parse_trigger(trigger, buffer, memaddr), "assert_parse_trigger") +#define assert_parse_trigger(trigger, buffer, memaddr) ASSERT_HELPER(_assert_parse_trigger(trigger, buffer, sizeof(buffer), memaddr), "assert_parse_trigger") static void _assert_evaluate_trigger(rc_trigger_t* trigger, memory_t* memory, int expected_result) { int result = rc_test_trigger(trigger, peek, memory, NULL); @@ -589,7 +590,7 @@ static void test_measured_indirect() { uint8_t ram[] = {0x00, 0x12, 0x34, 0xAB, 0x56}; memory_t memory; rc_trigger_t* trigger; - char buffer[256]; + char buffer[384]; memory.ram = ram; memory.size = sizeof(ram); @@ -1972,7 +1973,7 @@ static void test_remember_recall_separate_accumulator_per_group() { uint8_t ram[] = { 0x00, 0x12, 0x34, 0xAB, 0x56 }; memory_t memory; rc_trigger_t* trigger; - char buffer[512]; + char buffer[640]; memory.ram = ram; memory.size = sizeof(ram); @@ -2025,7 +2026,7 @@ static void test_remember_recall_use_same_value_multiple() { uint8_t ram[] = { 0x00, 0x12, 0x34, 0xAB, 0x56 }; memory_t memory; rc_trigger_t* trigger; - char buffer[512]; + char buffer[640]; memory.ram = ram; memory.size = sizeof(ram); @@ -2059,7 +2060,7 @@ static void test_remember_recall_in_pause_and_main() { uint8_t ram[] = { 0x00, 0x12, 0x34, 0xAB, 0x56 }; memory_t memory; rc_trigger_t* trigger; - char buffer[512]; + char buffer[640]; memory.ram = ram; memory.size = sizeof(ram); @@ -2106,6 +2107,41 @@ static void test_remember_recall_in_pause_and_main() { assert_hit_count(trigger, 0, 1, 4U); } +static void test_remember_recall_in_pause_with_chain() +{ + uint8_t ram[] = { 0x00, 0x12, 0x34, 0xAB, 0x56 }; + memory_t memory; + rc_trigger_t* trigger; + rc_condition_t* condition; + char buffer[640]; + + memory.ram = ram; + memory.size = sizeof(ram); + + assert_parse_trigger(&trigger, buffer, "I:{recall}_I:0xH02_0xH03=10_K:0xH00_P:0=1"); + + /* ensure recall memrefs point at the remember for the pause chain */ + condition = trigger->requirement->conditions; + ASSERT_NUM_EQUALS(condition->operand1.type, RC_OPERAND_RECALL); + ASSERT_PTR_NOT_NULL(condition->operand1.value.memref); + + condition = condition->next; + ASSERT_NUM_EQUALS(condition->operand1.value.memref->value.memref_type, RC_MEMREF_TYPE_MODIFIED_MEMREF); + ASSERT_NUM_EQUALS(((rc_modified_memref_t*)condition->operand1.value.memref)->parent.type, RC_OPERAND_RECALL); + ASSERT_PTR_NOT_NULL(((rc_modified_memref_t*)condition->operand1.value.memref)->parent.value.memref); + + /* byte(0)=0, remember. byte(0+2)=0x34, byte(0x34+3)=n/a */ + assert_evaluate_trigger(trigger, &memory, 0); + + ram[2] = 1; + /* byte(0)=0, remember. byte(0+2)=1, byte(1+3)=0x56 */ + assert_evaluate_trigger(trigger, &memory, 0); + + ram[4] = 10; + /* byte(0)=0, remember. byte(0+2)=1, byte(1+3)=10 */ + assert_evaluate_trigger(trigger, &memory, 1); +} + /* ======================================================== */ void test_trigger(void) { @@ -2175,6 +2211,7 @@ void test_trigger(void) { TEST(test_remember_recall_separate_accumulator_per_group); TEST(test_remember_recall_use_same_value_multiple); TEST(test_remember_recall_in_pause_and_main); + TEST(test_remember_recall_in_pause_with_chain); TEST_SUITE_END(); } diff --git a/test/rcheevos/test_value.c b/test/rcheevos/test_value.c index 07972180..b5067b65 100644 --- a/test/rcheevos/test_value.c +++ b/test/rcheevos/test_value.c @@ -284,7 +284,10 @@ static void test_typed_value_add(uint8_t type, uint32_t u32, double f32, rc_typed_value_add(&value, &amount); - if (type == RC_VALUE_TYPE_NONE) { + if (result_f32 != 0.0) { + assert_typed_value(&value, RC_VALUE_TYPE_FLOAT, result_u32, result_f32); + } + else if (type == RC_VALUE_TYPE_NONE) { assert_typed_value(&value, amount_type, result_u32, result_f32); } else { @@ -304,20 +307,20 @@ static void test_typed_value_addition() { /* unsigned source */ TEST_PARAMS8(test_typed_value_add, RC_VALUE_TYPE_UNSIGNED, 6, 0.0, RC_VALUE_TYPE_UNSIGNED, 8, 0.0, 14, 0.0); - TEST_PARAMS8(test_typed_value_add, RC_VALUE_TYPE_UNSIGNED, 6, 0.0, RC_VALUE_TYPE_FLOAT, 0, 8.0, 14, 0.0); + TEST_PARAMS8(test_typed_value_add, RC_VALUE_TYPE_UNSIGNED, 6, 0.0, RC_VALUE_TYPE_FLOAT, 0, 8.0, 0, 14.0); TEST_PARAMS8(test_typed_value_add, RC_VALUE_TYPE_UNSIGNED, 6, 0.0, RC_VALUE_TYPE_UNSIGNED, 0xFFFFFFFE, 0.0, 4, 0.0); TEST_PARAMS8(test_typed_value_add, RC_VALUE_TYPE_UNSIGNED, 6, 0.0, RC_VALUE_TYPE_SIGNED, (unsigned)-2, 0.0, 4, 0.0); - TEST_PARAMS8(test_typed_value_add, RC_VALUE_TYPE_UNSIGNED, 6, 0.0, RC_VALUE_TYPE_FLOAT, 0, -2.0, 4, 0.0); + TEST_PARAMS8(test_typed_value_add, RC_VALUE_TYPE_UNSIGNED, 6, 0.0, RC_VALUE_TYPE_FLOAT, 0, -2.0, 0, 4.0); TEST_PARAMS8(test_typed_value_add, RC_VALUE_TYPE_UNSIGNED, 6, 0.0, RC_VALUE_TYPE_NONE, 0, 0.0, 6, 0.0); /* signed source */ TEST_PARAMS8(test_typed_value_add, RC_VALUE_TYPE_SIGNED, (unsigned)-6, 0.0, RC_VALUE_TYPE_UNSIGNED, 2, 0.0, (unsigned)-4, 0.0); - TEST_PARAMS8(test_typed_value_add, RC_VALUE_TYPE_SIGNED, (unsigned)-6, 0.0, RC_VALUE_TYPE_FLOAT, 0, 2.0, (unsigned)-4, 0.0); + TEST_PARAMS8(test_typed_value_add, RC_VALUE_TYPE_SIGNED, (unsigned)-6, 0.0, RC_VALUE_TYPE_FLOAT, 0, 2.0, 0, -4.0); TEST_PARAMS8(test_typed_value_add, RC_VALUE_TYPE_SIGNED, (unsigned)-6, 0.0, RC_VALUE_TYPE_UNSIGNED, 8, 0.0, 2, 0.0); TEST_PARAMS8(test_typed_value_add, RC_VALUE_TYPE_SIGNED, (unsigned)-6, 0.0, RC_VALUE_TYPE_SIGNED, (unsigned)-2, 0.0, (unsigned)-8, 0.0); - TEST_PARAMS8(test_typed_value_add, RC_VALUE_TYPE_SIGNED, (unsigned)-6, 0.0, RC_VALUE_TYPE_FLOAT, 0, 2.0, (unsigned)-4, 0.0); + TEST_PARAMS8(test_typed_value_add, RC_VALUE_TYPE_SIGNED, (unsigned)-6, 0.0, RC_VALUE_TYPE_FLOAT, 0, 2.0, 0, -4.0); TEST_PARAMS8(test_typed_value_add, RC_VALUE_TYPE_SIGNED, (unsigned)-6, 0.0, RC_VALUE_TYPE_NONE, 0, 0.0, (unsigned)-6, 0.0); /* float source (whole numbers) */ diff --git a/test/test_types.natvis b/test/test_types.natvis new file mode 100644 index 00000000..2721f399 --- /dev/null +++ b/test/test_types.natvis @@ -0,0 +1,9 @@ + + + + + ((__rc_memref_list_t*)&memrefs) + ((__rc_value_list_t*)&variables) + + +