#include "rc_runtime.h" #include "rc_internal.h" #include "rc_compat.h" #include "../rhash/md5.h" #include #include #define RC_RICHPRESENCE_DISPLAY_BUFFER_SIZE 256 void rc_runtime_init(rc_runtime_t* self) { memset(self, 0, sizeof(rc_runtime_t)); self->next_memref = &self->memrefs; self->next_variable = &self->variables; } void rc_runtime_destroy(rc_runtime_t* self) { unsigned i; if (self->triggers) { for (i = 0; i < self->trigger_count; ++i) free(self->triggers[i].buffer); free(self->triggers); self->triggers = NULL; self->trigger_count = self->trigger_capacity = 0; } if (self->lboards) { for (i = 0; i < self->lboard_count; ++i) free(self->lboards[i].buffer); free(self->lboards); self->lboards = NULL; self->lboard_count = self->lboard_capacity = 0; } while (self->richpresence) { rc_runtime_richpresence_t* previous = self->richpresence->previous; free(self->richpresence->buffer); free(self->richpresence); self->richpresence = previous; } self->next_memref = 0; self->memrefs = 0; } static void rc_runtime_checksum(const char* memaddr, unsigned char* md5) { md5_state_t state; md5_init(&state); md5_append(&state, (unsigned char*)memaddr, (int)strlen(memaddr)); md5_finish(&state, md5); } static char rc_runtime_allocated_memrefs(rc_runtime_t* self) { char owns_memref = 0; /* if at least one memref was allocated within the object, we can't free the buffer when the object is deactivated */ if (*self->next_memref != NULL) { owns_memref = 1; /* advance through the new memrefs so we're ready for the next allocation */ do { self->next_memref = &(*self->next_memref)->next; } while (*self->next_memref != NULL); } /* if at least one variable was allocated within the object, we can't free the buffer when the object is deactivated */ if (*self->next_variable != NULL) { owns_memref = 1; /* advance through the new variables so we're ready for the next allocation */ do { self->next_variable = &(*self->next_variable)->next; } while (*self->next_variable != NULL); } return owns_memref; } static void rc_runtime_deactivate_trigger_by_index(rc_runtime_t* self, unsigned index) { if (self->triggers[index].owns_memrefs) { /* if the trigger has one or more memrefs in its buffer, we can't free the buffer. * just null out the trigger so the runtime processor will skip it */ rc_reset_trigger(self->triggers[index].trigger); self->triggers[index].trigger = NULL; } else { /* trigger doesn't own any memrefs, go ahead and free it, then replace it with the last trigger */ free(self->triggers[index].buffer); if (--self->trigger_count > index) memcpy(&self->triggers[index], &self->triggers[self->trigger_count], sizeof(rc_runtime_trigger_t)); } } void rc_runtime_deactivate_achievement(rc_runtime_t* self, unsigned id) { unsigned i; for (i = 0; i < self->trigger_count; ++i) { if (self->triggers[i].id == id && self->triggers[i].trigger != NULL) rc_runtime_deactivate_trigger_by_index(self, i); } } int rc_runtime_activate_achievement(rc_runtime_t* self, unsigned id, const char* memaddr, lua_State* L, int funcs_idx) { void* trigger_buffer; rc_trigger_t* trigger; rc_runtime_trigger_t* runtime_trigger; rc_parse_state_t parse; unsigned char md5[16]; int size; unsigned i; if (memaddr == NULL) return RC_INVALID_MEMORY_OPERAND; rc_runtime_checksum(memaddr, md5); /* check to see if the id is already registered with an active trigger */ for (i = 0; i < self->trigger_count; ++i) { if (self->triggers[i].id == id && self->triggers[i].trigger != NULL) { if (memcmp(self->triggers[i].md5, md5, 16) == 0) { /* if the checksum hasn't changed, we can reuse the existing item */ rc_reset_trigger(self->triggers[i].trigger); return RC_OK; } /* checksum has changed, deactivate the the item */ rc_runtime_deactivate_trigger_by_index(self, i); /* deactivate may reorder the list so we should continue from the current index. however, we * assume that only one trigger is active per id, so having found that, just stop scanning. */ break; } } /* check to see if a disabled trigger for the specific id matches the trigger being registered */ for (i = 0; i < self->trigger_count; ++i) { if (self->triggers[i].id == id && memcmp(self->triggers[i].md5, md5, 16) == 0) { /* retrieve the trigger pointer from the buffer */ size = 0; trigger = (rc_trigger_t*)rc_alloc(self->triggers[i].buffer, &size, sizeof(rc_trigger_t), RC_ALIGNOF(rc_trigger_t), NULL, -1); self->triggers[i].trigger = trigger; rc_reset_trigger(trigger); return RC_OK; } } /* item has not been previously registered, determine how much space we need for it, and allocate it */ size = rc_trigger_size(memaddr); if (size < 0) return size; trigger_buffer = malloc(size); if (!trigger_buffer) return RC_OUT_OF_MEMORY; /* populate the item, using the communal memrefs pool */ rc_init_parse_state(&parse, trigger_buffer, L, funcs_idx); parse.first_memref = &self->memrefs; trigger = RC_ALLOC(rc_trigger_t, &parse); rc_parse_trigger_internal(trigger, &memaddr, &parse); rc_destroy_parse_state(&parse); if (parse.offset < 0) { free(trigger_buffer); *self->next_memref = NULL; /* disassociate any memrefs allocated by the failed parse */ return parse.offset; } /* grow the trigger buffer if necessary */ if (self->trigger_count == self->trigger_capacity) { self->trigger_capacity += 32; if (!self->triggers) self->triggers = (rc_runtime_trigger_t*)malloc(self->trigger_capacity * sizeof(rc_runtime_trigger_t)); else self->triggers = (rc_runtime_trigger_t*)realloc(self->triggers, self->trigger_capacity * sizeof(rc_runtime_trigger_t)); if (!self->triggers) { free(trigger_buffer); *self->next_memref = NULL; /* disassociate any memrefs allocated by the failed parse */ return RC_OUT_OF_MEMORY; } } /* assign the new trigger */ runtime_trigger = &self->triggers[self->trigger_count]; runtime_trigger->id = id; runtime_trigger->trigger = trigger; runtime_trigger->buffer = trigger_buffer; runtime_trigger->invalid_memref = NULL; memcpy(runtime_trigger->md5, md5, 16); runtime_trigger->serialized_size = 0; runtime_trigger->owns_memrefs = rc_runtime_allocated_memrefs(self); ++self->trigger_count; /* reset it, and return it */ trigger->memrefs = NULL; rc_reset_trigger(trigger); return RC_OK; } rc_trigger_t* rc_runtime_get_achievement(const rc_runtime_t* self, unsigned id) { unsigned i; for (i = 0; i < self->trigger_count; ++i) { if (self->triggers[i].id == id && self->triggers[i].trigger != NULL) return self->triggers[i].trigger; } return NULL; } int rc_runtime_get_achievement_measured(const rc_runtime_t* runtime, unsigned id, unsigned* measured_value, unsigned* measured_target) { const rc_trigger_t* trigger = rc_runtime_get_achievement(runtime, id); if (!measured_value || !measured_target) return 0; if (!trigger) { *measured_value = *measured_target = 0; return 0; } if (rc_trigger_state_active(trigger->state)) { *measured_value = trigger->measured_value; *measured_target = trigger->measured_target; } else { /* don't report measured information for inactive triggers */ *measured_value = *measured_target = 0; } return 1; } int rc_runtime_format_achievement_measured(const rc_runtime_t* runtime, unsigned id, char* buffer, size_t buffer_size) { const rc_trigger_t* trigger = rc_runtime_get_achievement(runtime, id); unsigned value; if (!buffer || !buffer_size) return 0; if (!trigger || /* no trigger */ trigger->measured_target == 0 || /* not measured */ !rc_trigger_state_active(trigger->state)) { /* don't report measured value for inactive triggers */ *buffer = '\0'; return 0; } /* cap the value at the target so we can count past the target: "107 >= 100" */ value = trigger->measured_value; if (value > trigger->measured_target) value = trigger->measured_target; if (trigger->measured_as_percent) { unsigned percent = (unsigned)(((unsigned long long)value * 100) / trigger->measured_target); return snprintf(buffer, buffer_size, "%u%%", percent); } return snprintf(buffer, buffer_size, "%u/%u", value, trigger->measured_target); } static void rc_runtime_deactivate_lboard_by_index(rc_runtime_t* self, unsigned index) { if (self->lboards[index].owns_memrefs) { /* if the lboard has one or more memrefs in its buffer, we can't free the buffer. * just null out the lboard so the runtime processor will skip it */ rc_reset_lboard(self->lboards[index].lboard); self->lboards[index].lboard = NULL; } else { /* lboard doesn't own any memrefs, go ahead and free it, then replace it with the last lboard */ free(self->lboards[index].buffer); if (--self->lboard_count > index) memcpy(&self->lboards[index], &self->lboards[self->lboard_count], sizeof(rc_runtime_lboard_t)); } } void rc_runtime_deactivate_lboard(rc_runtime_t* self, unsigned id) { unsigned i; for (i = 0; i < self->lboard_count; ++i) { if (self->lboards[i].id == id && self->lboards[i].lboard != NULL) rc_runtime_deactivate_lboard_by_index(self, i); } } int rc_runtime_activate_lboard(rc_runtime_t* self, unsigned id, const char* memaddr, lua_State* L, int funcs_idx) { void* lboard_buffer; unsigned char md5[16]; rc_lboard_t* lboard; rc_parse_state_t parse; rc_runtime_lboard_t* runtime_lboard; int size; unsigned i; if (memaddr == 0) return RC_INVALID_MEMORY_OPERAND; rc_runtime_checksum(memaddr, md5); /* check to see if the id is already registered with an active lboard */ for (i = 0; i < self->lboard_count; ++i) { if (self->lboards[i].id == id && self->lboards[i].lboard != NULL) { if (memcmp(self->lboards[i].md5, md5, 16) == 0) { /* if the checksum hasn't changed, we can reuse the existing item */ rc_reset_lboard(self->lboards[i].lboard); return RC_OK; } /* checksum has changed, deactivate the the item */ rc_runtime_deactivate_lboard_by_index(self, i); /* deactivate may reorder the list so we should continue from the current index. however, we * assume that only one trigger is active per id, so having found that, just stop scanning. */ break; } } /* check to see if a disabled lboard for the specific id matches the lboard being registered */ for (i = 0; i < self->lboard_count; ++i) { if (self->lboards[i].id == id && memcmp(self->lboards[i].md5, md5, 16) == 0) { /* retrieve the lboard pointer from the buffer */ size = 0; lboard = (rc_lboard_t*)rc_alloc(self->lboards[i].buffer, &size, sizeof(rc_lboard_t), RC_ALIGNOF(rc_lboard_t), NULL, -1); self->lboards[i].lboard = lboard; rc_reset_lboard(lboard); return RC_OK; } } /* item has not been previously registered, determine how much space we need for it, and allocate it */ size = rc_lboard_size(memaddr); if (size < 0) return size; lboard_buffer = malloc(size); if (!lboard_buffer) return RC_OUT_OF_MEMORY; /* populate the item, using the communal memrefs pool */ rc_init_parse_state(&parse, lboard_buffer, L, funcs_idx); lboard = RC_ALLOC(rc_lboard_t, &parse); parse.first_memref = &self->memrefs; rc_parse_lboard_internal(lboard, memaddr, &parse); rc_destroy_parse_state(&parse); if (parse.offset < 0) { free(lboard_buffer); *self->next_memref = NULL; /* disassociate any memrefs allocated by the failed parse */ return parse.offset; } /* grow the lboard buffer if necessary */ if (self->lboard_count == self->lboard_capacity) { self->lboard_capacity += 16; if (!self->lboards) self->lboards = (rc_runtime_lboard_t*)malloc(self->lboard_capacity * sizeof(rc_runtime_lboard_t)); else self->lboards = (rc_runtime_lboard_t*)realloc(self->lboards, self->lboard_capacity * sizeof(rc_runtime_lboard_t)); if (!self->lboards) { free(lboard_buffer); *self->next_memref = NULL; /* disassociate any memrefs allocated by the failed parse */ return RC_OUT_OF_MEMORY; } } /* assign the new lboard */ runtime_lboard = &self->lboards[self->lboard_count++]; runtime_lboard->id = id; runtime_lboard->value = 0; runtime_lboard->lboard = lboard; runtime_lboard->buffer = lboard_buffer; runtime_lboard->invalid_memref = NULL; memcpy(runtime_lboard->md5, md5, 16); runtime_lboard->serialized_size = 0; runtime_lboard->owns_memrefs = rc_runtime_allocated_memrefs(self); /* reset it, and return it */ lboard->memrefs = NULL; rc_reset_lboard(lboard); return RC_OK; } rc_lboard_t* rc_runtime_get_lboard(const rc_runtime_t* self, unsigned id) { unsigned i; for (i = 0; i < self->lboard_count; ++i) { if (self->lboards[i].id == id && self->lboards[i].lboard != NULL) return self->lboards[i].lboard; } return NULL; } int rc_runtime_format_lboard_value(char* buffer, int size, int value, int format) { return rc_format_value(buffer, size, value, format); } int rc_runtime_activate_richpresence(rc_runtime_t* self, const char* script, lua_State* L, int funcs_idx) { rc_richpresence_t* richpresence; rc_runtime_richpresence_t* previous; rc_runtime_richpresence_t** previous_ptr; rc_parse_state_t parse; unsigned char md5[16]; int size; if (script == NULL) return RC_MISSING_DISPLAY_STRING; rc_runtime_checksum(script, md5); /* look for existing match */ previous_ptr = NULL; previous = self->richpresence; while (previous) { if (previous && self->richpresence->richpresence && memcmp(self->richpresence->md5, md5, 16) == 0) { /* unchanged. reset all of the conditions */ rc_reset_richpresence(self->richpresence->richpresence); /* move to front of linked list*/ if (previous_ptr) { *previous_ptr = previous->previous; if (!self->richpresence->owns_memrefs) { free(self->richpresence->buffer); previous->previous = self->richpresence->previous; } else { previous->previous = self->richpresence; } self->richpresence = previous; } /* return success*/ return RC_OK; } previous_ptr = &previous->previous; previous = previous->previous; } /* no existing match found, parse script */ size = rc_richpresence_size(script); if (size < 0) return size; /* if the previous script doesn't have any memrefs, free it */ previous = self->richpresence; if (previous) { if (!previous->owns_memrefs) { free(previous->buffer); previous = previous->previous; } } /* allocate and process the new script */ self->richpresence = (rc_runtime_richpresence_t*)malloc(sizeof(rc_runtime_richpresence_t)); if (!self->richpresence) return RC_OUT_OF_MEMORY; self->richpresence->previous = previous; self->richpresence->owns_memrefs = 0; memcpy(self->richpresence->md5, md5, sizeof(md5)); self->richpresence->buffer = malloc(size); if (!self->richpresence->buffer) return RC_OUT_OF_MEMORY; rc_init_parse_state(&parse, self->richpresence->buffer, L, funcs_idx); self->richpresence->richpresence = richpresence = RC_ALLOC(rc_richpresence_t, &parse); parse.first_memref = &self->memrefs; parse.variables = &self->variables; rc_parse_richpresence_internal(richpresence, script, &parse); rc_destroy_parse_state(&parse); if (parse.offset < 0) { free(self->richpresence->buffer); free(self->richpresence); self->richpresence = previous; *self->next_memref = NULL; /* disassociate any memrefs allocated by the failed parse */ return parse.offset; } self->richpresence->owns_memrefs = rc_runtime_allocated_memrefs(self); richpresence->memrefs = NULL; richpresence->variables = NULL; if (!richpresence->first_display || !richpresence->first_display->display) { /* non-existant rich presence */ self->richpresence->richpresence = NULL; } else { /* reset all of the conditions */ rc_reset_richpresence(richpresence); } return RC_OK; } int rc_runtime_get_richpresence(const rc_runtime_t* self, char* buffer, unsigned buffersize, rc_runtime_peek_t peek, void* peek_ud, lua_State* L) { if (self->richpresence && self->richpresence->richpresence) return rc_get_richpresence_display_string(self->richpresence->richpresence, buffer, buffersize, peek, peek_ud, L); *buffer = '\0'; return 0; } void rc_runtime_do_frame(rc_runtime_t* self, rc_runtime_event_handler_t event_handler, rc_runtime_peek_t peek, void* ud, lua_State* L) { rc_runtime_event_t runtime_event; int i; runtime_event.value = 0; rc_update_memref_values(self->memrefs, peek, ud); rc_update_variables(self->variables, peek, ud, L); for (i = self->trigger_count - 1; i >= 0; --i) { rc_trigger_t* trigger = self->triggers[i].trigger; int old_state, new_state; if (!trigger) continue; if (self->triggers[i].invalid_memref) { runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_DISABLED; runtime_event.id = self->triggers[i].id; runtime_event.value = self->triggers[i].invalid_memref->address; trigger->state = RC_TRIGGER_STATE_DISABLED; self->triggers[i].invalid_memref = NULL; event_handler(&runtime_event); runtime_event.value = 0; /* achievement loop expects this to stay at 0 */ continue; } old_state = trigger->state; new_state = rc_evaluate_trigger(trigger, peek, ud, L); /* the trigger state doesn't actually change to RESET, RESET just serves as a notification. * handle the notification, then look at the actual state */ if (new_state == RC_TRIGGER_STATE_RESET) { runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_RESET; runtime_event.id = self->triggers[i].id; event_handler(&runtime_event); new_state = trigger->state; } /* if the state hasn't changed, there won't be any events raised */ if (new_state == old_state) continue; /* raise an UNPRIMED event when changing from PRIMED to anything else */ if (old_state == RC_TRIGGER_STATE_PRIMED) { runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_UNPRIMED; runtime_event.id = self->triggers[i].id; event_handler(&runtime_event); } /* raise events for each of the possible new states */ switch (new_state) { case RC_TRIGGER_STATE_TRIGGERED: runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_TRIGGERED; runtime_event.id = self->triggers[i].id; event_handler(&runtime_event); break; case RC_TRIGGER_STATE_PAUSED: runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_PAUSED; runtime_event.id = self->triggers[i].id; event_handler(&runtime_event); break; case RC_TRIGGER_STATE_PRIMED: runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_PRIMED; runtime_event.id = self->triggers[i].id; event_handler(&runtime_event); break; case RC_TRIGGER_STATE_ACTIVE: /* only raise ACTIVATED event when transitioning from an inactive state. * note that inactive in this case means active but cannot trigger. */ if (old_state == RC_TRIGGER_STATE_WAITING || old_state == RC_TRIGGER_STATE_PAUSED) { runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_ACTIVATED; runtime_event.id = self->triggers[i].id; event_handler(&runtime_event); } break; } } for (i = self->lboard_count - 1; i >= 0; --i) { rc_lboard_t* lboard = self->lboards[i].lboard; int lboard_state; if (!lboard) continue; if (self->lboards[i].invalid_memref) { runtime_event.type = RC_RUNTIME_EVENT_LBOARD_DISABLED; runtime_event.id = self->lboards[i].id; runtime_event.value = self->lboards[i].invalid_memref->address; lboard->state = RC_LBOARD_STATE_DISABLED; self->lboards[i].invalid_memref = NULL; event_handler(&runtime_event); continue; } lboard_state = lboard->state; switch (rc_evaluate_lboard(lboard, &runtime_event.value, peek, ud, L)) { case RC_LBOARD_STATE_STARTED: /* leaderboard is running */ if (lboard_state != RC_LBOARD_STATE_STARTED) { self->lboards[i].value = runtime_event.value; runtime_event.type = RC_RUNTIME_EVENT_LBOARD_STARTED; runtime_event.id = self->lboards[i].id; event_handler(&runtime_event); } else if (runtime_event.value != self->lboards[i].value) { self->lboards[i].value = runtime_event.value; runtime_event.type = RC_RUNTIME_EVENT_LBOARD_UPDATED; runtime_event.id = self->lboards[i].id; event_handler(&runtime_event); } break; case RC_LBOARD_STATE_CANCELED: if (lboard_state != RC_LBOARD_STATE_CANCELED) { runtime_event.type = RC_RUNTIME_EVENT_LBOARD_CANCELED; runtime_event.id = self->lboards[i].id; event_handler(&runtime_event); } break; case RC_LBOARD_STATE_TRIGGERED: if (lboard_state != RC_RUNTIME_EVENT_LBOARD_TRIGGERED) { runtime_event.type = RC_RUNTIME_EVENT_LBOARD_TRIGGERED; runtime_event.id = self->lboards[i].id; event_handler(&runtime_event); } break; } } if (self->richpresence && self->richpresence->richpresence) rc_update_richpresence(self->richpresence->richpresence, peek, ud, L); } void rc_runtime_reset(rc_runtime_t* self) { rc_value_t* variable; unsigned i; for (i = 0; i < self->trigger_count; ++i) { if (self->triggers[i].trigger) rc_reset_trigger(self->triggers[i].trigger); } for (i = 0; i < self->lboard_count; ++i) { if (self->lboards[i].lboard) rc_reset_lboard(self->lboards[i].lboard); } if (self->richpresence && self->richpresence->richpresence) { rc_richpresence_display_t* display = self->richpresence->richpresence->first_display; while (display != 0) { rc_reset_trigger(&display->trigger); display = display->next; } } for (variable = self->variables; variable; variable = variable->next) rc_reset_value(variable); } static int rc_condset_contains_memref(const rc_condset_t* condset, const rc_memref_t* memref) { rc_condition_t* cond; if (!condset) return 0; for (cond = condset->conditions; cond; cond = cond->next) { if (rc_operand_is_memref(&cond->operand1) && cond->operand1.value.memref == memref) return 1; if (rc_operand_is_memref(&cond->operand2) && cond->operand2.value.memref == memref) return 1; } return 0; } static int rc_value_contains_memref(const rc_value_t* value, const rc_memref_t* memref) { rc_condset_t* condset; if (!value) return 0; for (condset = value->conditions; condset; condset = condset->next) { if (rc_condset_contains_memref(condset, memref)) return 1; } return 0; } static int rc_trigger_contains_memref(const rc_trigger_t* trigger, const rc_memref_t* memref) { rc_condset_t* condset; if (!trigger) return 0; if (rc_condset_contains_memref(trigger->requirement, memref)) return 1; for (condset = trigger->alternative; condset; condset = condset->next) { if (rc_condset_contains_memref(condset, memref)) return 1; } return 0; } static void rc_runtime_invalidate_memref(rc_runtime_t* self, rc_memref_t* memref) { unsigned i; /* disable any achievements dependent on the address */ for (i = 0; i < self->trigger_count; ++i) { if (!self->triggers[i].invalid_memref && rc_trigger_contains_memref(self->triggers[i].trigger, memref)) self->triggers[i].invalid_memref = memref; } /* disable any leaderboards dependent on the address */ for (i = 0; i < self->lboard_count; ++i) { if (!self->lboards[i].invalid_memref) { rc_lboard_t* lboard = self->lboards[i].lboard; if (lboard) { if (rc_trigger_contains_memref(&lboard->start, memref)) { lboard->start.state = RC_TRIGGER_STATE_DISABLED; self->lboards[i].invalid_memref = memref; } if (rc_trigger_contains_memref(&lboard->cancel, memref)) { lboard->cancel.state = RC_TRIGGER_STATE_DISABLED; self->lboards[i].invalid_memref = memref; } if (rc_trigger_contains_memref(&lboard->submit, memref)) { lboard->submit.state = RC_TRIGGER_STATE_DISABLED; self->lboards[i].invalid_memref = memref; } if (rc_value_contains_memref(&lboard->value, memref)) self->lboards[i].invalid_memref = memref; } } } } void rc_runtime_invalidate_address(rc_runtime_t* self, unsigned address) { rc_memref_t** last_memref = &self->memrefs; rc_memref_t* memref = self->memrefs; while (memref) { if (memref->address == address && !memref->value.is_indirect) { /* 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. */ *last_memref = memref->next; rc_runtime_invalidate_memref(self, memref); break; } last_memref = &memref->next; memref = *last_memref; } } void rc_runtime_validate_addresses(rc_runtime_t* self, rc_runtime_event_handler_t event_handler, rc_runtime_validate_address_t validate_handler) { rc_memref_t** last_memref = &self->memrefs; rc_memref_t* memref = self->memrefs; int num_invalid = 0; while (memref) { if (!memref->value.is_indirect && !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. */ *last_memref = memref->next; rc_runtime_invalidate_memref(self, memref); ++num_invalid; } else { last_memref = &memref->next; } memref = *last_memref; } if (num_invalid) { rc_runtime_event_t runtime_event; int i; for (i = self->trigger_count - 1; i >= 0; --i) { rc_trigger_t* trigger = self->triggers[i].trigger; if (trigger && self->triggers[i].invalid_memref) { runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_DISABLED; runtime_event.id = self->triggers[i].id; runtime_event.value = self->triggers[i].invalid_memref->address; trigger->state = RC_TRIGGER_STATE_DISABLED; self->triggers[i].invalid_memref = NULL; event_handler(&runtime_event); } } for (i = self->lboard_count - 1; i >= 0; --i) { rc_lboard_t* lboard = self->lboards[i].lboard; if (lboard && self->lboards[i].invalid_memref) { runtime_event.type = RC_RUNTIME_EVENT_LBOARD_DISABLED; runtime_event.id = self->lboards[i].id; runtime_event.value = self->lboards[i].invalid_memref->address; lboard->state = RC_LBOARD_STATE_DISABLED; self->lboards[i].invalid_memref = NULL; event_handler(&runtime_event); } } } }