dep: Add rcheevos

This commit is contained in:
Connor McLaughlin
2021-02-21 16:58:40 +10:00
parent e43773fbc8
commit 3ccaddc7e6
36 changed files with 11364 additions and 0 deletions

View File

@ -0,0 +1,192 @@
#include "rc_internal.h"
#include <stdlib.h>
#include <string.h>
void* rc_alloc_scratch(void* pointer, int* offset, int size, int alignment, rc_scratch_t* scratch, int scratch_object_pointer_offset)
{
rc_scratch_buffer_t* buffer;
/* if we have a real buffer, then allocate the data there */
if (pointer)
return rc_alloc(pointer, offset, size, alignment, NULL, scratch_object_pointer_offset);
/* update how much space will be required in the real buffer */
{
const int aligned_offset = (*offset + alignment - 1) & ~(alignment - 1);
*offset += (aligned_offset - *offset);
*offset += size;
}
/* find a scratch buffer to hold the temporary data */
buffer = &scratch->buffer;
do {
const int aligned_buffer_offset = (buffer->offset + alignment - 1) & ~(alignment - 1);
const int remaining = sizeof(buffer->buffer) - aligned_buffer_offset;
if (remaining >= size) {
/* claim the required space from an existing buffer */
return rc_alloc(buffer->buffer, &buffer->offset, size, alignment, NULL, -1);
}
if (!buffer->next)
break;
buffer = buffer->next;
} while (1);
/* not enough space in any existing buffer, allocate more */
if (size > (int)sizeof(buffer->buffer)) {
/* caller is asking for more than we can fit in a standard rc_scratch_buffer_t.
* leverage the fact that the buffer is the last field and extend its size.
* this chunk will be exactly large enough to hold the needed data, and since offset
* will exceed sizeof(buffer->buffer), it will never be eligible to hold anything else.
*/
const int needed = sizeof(rc_scratch_buffer_t) - sizeof(buffer->buffer) + size;
buffer->next = (rc_scratch_buffer_t*)malloc(needed);
}
else {
buffer->next = (rc_scratch_buffer_t*)malloc(sizeof(rc_scratch_buffer_t));
}
if (!buffer->next) {
*offset = RC_OUT_OF_MEMORY;
return NULL;
}
buffer = buffer->next;
buffer->offset = 0;
buffer->next = NULL;
/* claim the required space from the new buffer */
return rc_alloc(buffer->buffer, &buffer->offset, size, alignment, NULL, -1);
}
void* rc_alloc(void* pointer, int* offset, int size, int alignment, rc_scratch_t* scratch, int scratch_object_pointer_offset) {
void* ptr;
*offset = (*offset + alignment - 1) & ~(alignment - 1);
if (pointer != 0) {
/* valid buffer, grab the next chunk */
ptr = (void*)((char*)pointer + *offset);
}
else if (scratch != 0 && scratch_object_pointer_offset >= 0) {
/* only allocate one instance of each object type (indentified by scratch_object_pointer_offset) */
void** scratch_object_pointer = (void**)((char*)&scratch->objs + scratch_object_pointer_offset);
ptr = *scratch_object_pointer;
if (!ptr) {
int used;
ptr = *scratch_object_pointer = rc_alloc_scratch(NULL, &used, size, alignment, scratch, -1);
}
}
else {
/* nowhere to get memory from, return NULL */
ptr = NULL;
}
*offset += size;
return ptr;
}
char* rc_alloc_str(rc_parse_state_t* parse, const char* text, int length) {
int used = 0;
char* ptr;
rc_scratch_string_t** next = &parse->scratch.strings;
while (*next) {
int diff = strncmp(text, (*next)->value, length);
if (diff == 0) {
diff = (*next)->value[length];
if (diff == 0)
return (*next)->value;
}
if (diff < 0)
next = &(*next)->left;
else
next = &(*next)->right;
}
*next = rc_alloc_scratch(NULL, &used, sizeof(rc_scratch_string_t), RC_ALIGNOF(rc_scratch_string_t), &parse->scratch, RC_OFFSETOF(parse->scratch.objs, __rc_scratch_string_t));
ptr = (char*)rc_alloc_scratch(parse->buffer, &parse->offset, length + 1, RC_ALIGNOF(char), &parse->scratch, -1);
if (!ptr || !*next) {
if (parse->offset >= 0)
parse->offset = RC_OUT_OF_MEMORY;
return NULL;
}
memcpy(ptr, text, length);
ptr[length] = '\0';
(*next)->left = NULL;
(*next)->right = NULL;
(*next)->value = ptr;
return ptr;
}
void rc_init_parse_state(rc_parse_state_t* parse, void* buffer, lua_State* L, int funcs_ndx)
{
/* could use memset here, but rc_parse_state_t contains a 512 byte buffer that doesn't need to be initialized */
parse->offset = 0;
parse->L = L;
parse->funcs_ndx = funcs_ndx;
parse->buffer = buffer;
parse->scratch.buffer.offset = 0;
parse->scratch.buffer.next = NULL;
parse->scratch.strings = NULL;
memset(&parse->scratch.objs, 0, sizeof(parse->scratch.objs));
parse->first_memref = 0;
parse->variables = 0;
parse->measured_target = 0;
parse->has_required_hits = 0;
}
void rc_destroy_parse_state(rc_parse_state_t* parse)
{
rc_scratch_buffer_t* buffer = parse->scratch.buffer.next;
rc_scratch_buffer_t* next;
while (buffer) {
next = buffer->next;
free(buffer);
buffer = next;
}
}
const char* rc_error_str(int ret)
{
switch (ret) {
case RC_OK: return "OK";
case RC_INVALID_LUA_OPERAND: return "Invalid Lua operand";
case RC_INVALID_MEMORY_OPERAND: return "Invalid memory operand";
case RC_INVALID_CONST_OPERAND: return "Invalid constant operand";
case RC_INVALID_FP_OPERAND: return "Invalid floating-point operand";
case RC_INVALID_CONDITION_TYPE: return "Invalid condition type";
case RC_INVALID_OPERATOR: return "Invalid operator";
case RC_INVALID_REQUIRED_HITS: return "Invalid required hits";
case RC_DUPLICATED_START: return "Duplicated start condition";
case RC_DUPLICATED_CANCEL: return "Duplicated cancel condition";
case RC_DUPLICATED_SUBMIT: return "Duplicated submit condition";
case RC_DUPLICATED_VALUE: return "Duplicated value expression";
case RC_DUPLICATED_PROGRESS: return "Duplicated progress expression";
case RC_MISSING_START: return "Missing start condition";
case RC_MISSING_CANCEL: return "Missing cancel condition";
case RC_MISSING_SUBMIT: return "Missing submit condition";
case RC_MISSING_VALUE: return "Missing value expression";
case RC_INVALID_LBOARD_FIELD: return "Invalid field in leaderboard";
case RC_MISSING_DISPLAY_STRING: return "Missing display string";
case RC_OUT_OF_MEMORY: return "Out of memory";
case RC_INVALID_VALUE_FLAG: return "Invalid flag in value expression";
case RC_MISSING_VALUE_MEASURED: return "Missing measured flag in value expression";
case RC_MULTIPLE_MEASURED: return "Multiple measured targets";
case RC_INVALID_MEASURED_TARGET: return "Invalid measured target";
case RC_INVALID_COMPARISON: return "Invalid comparison";
case RC_INVALID_STATE: return "Invalid state";
default: return "Unknown error";
}
}

View File

@ -0,0 +1,62 @@
#include "rc_compat.h"
#include <ctype.h>
#include <stdarg.h>
int rc_strncasecmp(const char* left, const char* right, size_t length)
{
while (length)
{
if (*left != *right)
{
const int diff = tolower(*left) - tolower(*right);
if (diff != 0)
return diff;
}
++left;
++right;
--length;
}
return 0;
}
int rc_strcasecmp(const char* left, const char* right)
{
while (*left || *right)
{
if (*left != *right)
{
const int diff = tolower(*left) - tolower(*right);
if (diff != 0)
return diff;
}
++left;
++right;
}
return 0;
}
char* rc_strdup(const char* str)
{
const size_t length = strlen(str);
char* buffer = (char*)malloc(length + 1);
memcpy(buffer, str, length + 1);
return buffer;
}
int rc_snprintf(char* buffer, size_t size, const char* format, ...)
{
int result;
va_list args;
va_start(args, format);
/* assume buffer is large enough and ignore size */
result = vsprintf(buffer, format, args);
va_end(args);
return result;
}

View File

@ -0,0 +1,261 @@
#include "rc_internal.h"
#include <stdlib.h>
char rc_parse_operator(const char** memaddr) {
const char* oper = *memaddr;
switch (*oper) {
case '=':
++(*memaddr);
(*memaddr) += (**memaddr == '=');
return RC_OPERATOR_EQ;
case '!':
if (oper[1] == '=') {
(*memaddr) += 2;
return RC_OPERATOR_NE;
}
/* fall through */
default:
return RC_INVALID_OPERATOR;
case '<':
if (oper[1] == '=') {
(*memaddr) += 2;
return RC_OPERATOR_LE;
}
++(*memaddr);
return RC_OPERATOR_LT;
case '>':
if (oper[1] == '=') {
(*memaddr) += 2;
return RC_OPERATOR_GE;
}
++(*memaddr);
return RC_OPERATOR_GT;
case '*':
++(*memaddr);
return RC_OPERATOR_MULT;
case '/':
++(*memaddr);
return RC_OPERATOR_DIV;
case '&':
++(*memaddr);
return RC_OPERATOR_AND;
case '\0':/* end of string */
case '_': /* next condition */
case 'S': /* next condset */
case ')': /* end of macro */
case '$': /* maximum of values */
/* valid condition separator, condition may not have an operator */
return RC_OPERATOR_NONE;
}
}
rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse, int is_indirect) {
rc_condition_t* self;
const char* aux;
int ret2;
int can_modify = 0;
aux = *memaddr;
self = RC_ALLOC(rc_condition_t, parse);
self->current_hits = 0;
if (*aux != 0 && aux[1] == ':') {
switch (*aux) {
case 'p': case 'P': self->type = RC_CONDITION_PAUSE_IF; break;
case 'r': case 'R': self->type = RC_CONDITION_RESET_IF; break;
case 'a': case 'A': self->type = RC_CONDITION_ADD_SOURCE; can_modify = 1; break;
case 'b': case 'B': self->type = RC_CONDITION_SUB_SOURCE; can_modify = 1; break;
case 'c': case 'C': self->type = RC_CONDITION_ADD_HITS; break;
case 'd': case 'D': self->type = RC_CONDITION_SUB_HITS; break;
case 'n': case 'N': self->type = RC_CONDITION_AND_NEXT; break;
case 'o': case 'O': self->type = RC_CONDITION_OR_NEXT; break;
case 'm': case 'M': self->type = RC_CONDITION_MEASURED; break;
case 'q': case 'Q': self->type = RC_CONDITION_MEASURED_IF; break;
case 'i': case 'I': self->type = RC_CONDITION_ADD_ADDRESS; can_modify = 1; break;
case 't': case 'T': self->type = RC_CONDITION_TRIGGER; break;
case 'z': case 'Z': self->type = RC_CONDITION_RESET_NEXT_IF; break;
default: parse->offset = RC_INVALID_CONDITION_TYPE; return 0;
}
aux += 2;
}
else {
self->type = RC_CONDITION_STANDARD;
}
ret2 = rc_parse_operand(&self->operand1, &aux, 1, is_indirect, parse);
if (ret2 < 0) {
parse->offset = ret2;
return 0;
}
if (self->operand1.type == RC_OPERAND_FP) {
parse->offset = can_modify ? RC_INVALID_FP_OPERAND : RC_INVALID_COMPARISON;
return 0;
}
self->oper = rc_parse_operator(&aux);
switch (self->oper) {
case RC_OPERATOR_NONE:
/* non-modifying statements must have a second operand */
if (!can_modify) {
/* measured does not require a second operand when used in a value */
if (self->type != RC_CONDITION_MEASURED) {
parse->offset = RC_INVALID_OPERATOR;
return 0;
}
}
/* provide dummy operand of '1' and no required hits */
self->operand2.type = RC_OPERAND_CONST;
self->operand2.value.num = 1;
self->required_hits = 0;
*memaddr = aux;
return self;
case RC_OPERATOR_MULT:
case RC_OPERATOR_DIV:
case RC_OPERATOR_AND:
/* modifying operators are only valid on modifying statements */
if (can_modify)
break;
/* fallthrough */
case RC_INVALID_OPERATOR:
parse->offset = RC_INVALID_OPERATOR;
return 0;
default:
/* comparison operators are not valid on modifying statements */
if (can_modify) {
switch (self->type) {
case RC_CONDITION_ADD_SOURCE:
case RC_CONDITION_SUB_SOURCE:
case RC_CONDITION_ADD_ADDRESS:
/* prevent parse errors on legacy achievements where a condition was present before changing the type */
self->oper = RC_OPERATOR_NONE;
break;
default:
parse->offset = RC_INVALID_OPERATOR;
return 0;
}
}
break;
}
ret2 = rc_parse_operand(&self->operand2, &aux, 1, is_indirect, parse);
if (ret2 < 0) {
parse->offset = ret2;
return 0;
}
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;
}
if (!can_modify && self->operand2.type == RC_OPERAND_FP) {
parse->offset = RC_INVALID_COMPARISON;
return 0;
}
if (*aux == '(') {
char* end;
self->required_hits = (unsigned)strtoul(++aux, &end, 10);
if (end == aux || *end != ')') {
parse->offset = RC_INVALID_REQUIRED_HITS;
return 0;
}
parse->has_required_hits = 1;
aux = end + 1;
}
else if (*aux == '.') {
char* end;
self->required_hits = (unsigned)strtoul(++aux, &end, 10);
if (end == aux || *end != '.') {
parse->offset = RC_INVALID_REQUIRED_HITS;
return 0;
}
parse->has_required_hits = 1;
aux = end + 1;
}
else {
self->required_hits = 0;
}
*memaddr = aux;
return self;
}
int rc_test_condition(rc_condition_t* self, rc_eval_state_t* eval_state) {
unsigned value1 = rc_evaluate_operand(&self->operand1, eval_state) + eval_state->add_value;
unsigned value2 = rc_evaluate_operand(&self->operand2, eval_state);
switch (self->oper) {
case RC_OPERATOR_EQ: return value1 == value2;
case RC_OPERATOR_NE: return value1 != value2;
case RC_OPERATOR_LT: return value1 < value2;
case RC_OPERATOR_LE: return value1 <= value2;
case RC_OPERATOR_GT: return value1 > value2;
case RC_OPERATOR_GE: return value1 >= value2;
case RC_OPERATOR_NONE: return 1;
default: return 1;
}
}
int rc_evaluate_condition_value(rc_condition_t* self, rc_eval_state_t* eval_state) {
unsigned value = rc_evaluate_operand(&self->operand1, eval_state);
switch (self->oper) {
case RC_OPERATOR_MULT:
if (self->operand2.type == RC_OPERAND_FP)
value = (int)((double)value * self->operand2.value.dbl);
else
value *= rc_evaluate_operand(&self->operand2, eval_state);
break;
case RC_OPERATOR_DIV:
if (self->operand2.type == RC_OPERAND_FP)
{
if (self->operand2.value.dbl == 0.0)
value = 0;
else
value = (int)((double)value / self->operand2.value.dbl);
}
else
{
unsigned value2 = rc_evaluate_operand(&self->operand2, eval_state);
if (value2 == 0)
value = 0;
else
value /= value2;
}
break;
case RC_OPERATOR_AND:
value &= rc_evaluate_operand(&self->operand2, eval_state);
break;
}
return value;
}

View File

@ -0,0 +1,369 @@
#include "rc_internal.h"
static void rc_update_condition_pause(rc_condition_t* condition, int* in_pause) {
if (condition->next != 0) {
rc_update_condition_pause(condition->next, in_pause);
}
switch (condition->type) {
case RC_CONDITION_PAUSE_IF:
*in_pause = condition->pause = 1;
break;
case RC_CONDITION_ADD_SOURCE:
case RC_CONDITION_SUB_SOURCE:
case RC_CONDITION_ADD_HITS:
case RC_CONDITION_SUB_HITS:
case RC_CONDITION_AND_NEXT:
case RC_CONDITION_OR_NEXT:
case RC_CONDITION_ADD_ADDRESS:
condition->pause = *in_pause;
break;
default:
*in_pause = condition->pause = 0;
break;
}
}
rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse, int is_value) {
rc_condset_t* self;
rc_condition_t** next;
int in_pause;
int in_add_address;
unsigned measured_target = 0;
self = RC_ALLOC(rc_condset_t, parse);
self->has_pause = self->is_paused = 0;
next = &self->conditions;
if (**memaddr == 'S' || **memaddr == 's' || !**memaddr) {
/* empty group - editor allows it, so we have to support it */
*next = 0;
return self;
}
in_add_address = 0;
for (;;) {
*next = rc_parse_condition(memaddr, parse, in_add_address);
if (parse->offset < 0) {
return 0;
}
if ((*next)->oper == RC_OPERATOR_NONE) {
switch ((*next)->type) {
case RC_CONDITION_ADD_ADDRESS:
case RC_CONDITION_ADD_HITS:
case RC_CONDITION_SUB_HITS:
case RC_CONDITION_ADD_SOURCE:
case RC_CONDITION_SUB_SOURCE:
case RC_CONDITION_AND_NEXT:
case RC_CONDITION_OR_NEXT:
break;
case RC_CONDITION_MEASURED:
if (is_value)
break;
/* fallthrough to default */
default:
parse->offset = RC_INVALID_OPERATOR;
return 0;
}
}
self->has_pause |= (*next)->type == RC_CONDITION_PAUSE_IF;
in_add_address = (*next)->type == RC_CONDITION_ADD_ADDRESS;
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;
if ((*next)->oper != RC_OPERATOR_NONE)
(*next)->required_hits = measured_target;
}
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 {
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;
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;
default:
break;
}
next = &(*next)->next;
if (**memaddr != '_') {
break;
}
(*memaddr)++;
}
*next = 0;
if (parse->buffer != 0) {
in_pause = 0;
rc_update_condition_pause(self->conditions, &in_pause);
}
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;
int set_valid, cond_valid, and_next, or_next, reset_next;
unsigned measured_value = 0;
unsigned total_hits = 0;
int can_measure = 1, measured_from_hits = 0;
eval_state->primed = 1;
set_valid = 1;
and_next = 1;
or_next = 0;
reset_next = 0;
eval_state->add_value = eval_state->add_hits = eval_state->add_address = 0;
for (condition = self->conditions; condition != 0; condition = condition->next) {
if (condition->pause != processing_pause) {
continue;
}
/* STEP 1: process modifier conditions */
switch (condition->type) {
case RC_CONDITION_ADD_SOURCE:
eval_state->add_value += rc_evaluate_condition_value(condition, eval_state);
eval_state->add_address = 0;
continue;
case RC_CONDITION_SUB_SOURCE:
eval_state->add_value -= rc_evaluate_condition_value(condition, eval_state);
eval_state->add_address = 0;
continue;
case RC_CONDITION_ADD_ADDRESS:
eval_state->add_address = rc_evaluate_condition_value(condition, eval_state);
continue;
case RC_CONDITION_MEASURED:
if (condition->required_hits == 0) {
/* Measured condition without a hit target measures the value of the left operand */
measured_value = rc_evaluate_condition_value(condition, eval_state) + eval_state->add_value;
}
break;
default:
break;
}
/* STEP 2: evaluate the current condition */
condition->is_true = rc_test_condition(condition, eval_state);
eval_state->add_value = 0;
eval_state->add_address = 0;
/* apply logic flags and reset them for the next condition */
cond_valid = condition->is_true;
cond_valid &= and_next;
cond_valid |= or_next;
and_next = 1;
or_next = 0;
if (reset_next) {
/* previous ResetNextIf resets the hit count on this condition and prevents it from being true */
if (condition->current_hits)
eval_state->was_cond_reset = 1;
condition->current_hits = 0;
cond_valid = 0;
}
else if (cond_valid) {
/* true conditions should update hit count */
eval_state->has_hits = 1;
if (condition->required_hits == 0) {
/* no target hit count, just keep tallying */
++condition->current_hits;
}
else if (condition->current_hits < condition->required_hits) {
/* target hit count hasn't been met, tally and revalidate - only true if hit count becomes met */
++condition->current_hits;
cond_valid = (condition->current_hits == condition->required_hits);
}
else {
/* target hit count has been met, do nothing */
}
}
else if (condition->current_hits > 0) {
/* target has been true in the past, if the hit target is met, consider it true now */
eval_state->has_hits = 1;
cond_valid = (condition->current_hits == condition->required_hits);
}
/* STEP 3: handle logic flags */
switch (condition->type) {
case RC_CONDITION_ADD_HITS:
eval_state->add_hits += condition->current_hits;
reset_next = 0; /* ResetNextIf was applied to this AddHits condition; don't apply it to future conditions */
continue;
case RC_CONDITION_SUB_HITS:
eval_state->add_hits -= condition->current_hits;
reset_next = 0; /* ResetNextIf was applied to this AddHits condition; don't apply it to future conditions */
continue;
case RC_CONDITION_RESET_NEXT_IF:
reset_next = cond_valid;
continue;
case RC_CONDITION_AND_NEXT:
and_next = cond_valid;
continue;
case RC_CONDITION_OR_NEXT:
or_next = cond_valid;
continue;
default:
break;
}
/* reset logic flags for next condition */
reset_next = 0;
/* STEP 4: calculate total hits */
total_hits = condition->current_hits;
if (eval_state->add_hits) {
if (condition->required_hits != 0) {
/* if the condition has a target hit count, we have to recalculate cond_valid including the AddHits counter */
const int signed_hits = (int)condition->current_hits + eval_state->add_hits;
total_hits = (signed_hits >= 0) ? (unsigned)signed_hits : 0;
cond_valid = (total_hits >= condition->required_hits);
}
else {
/* no target hit count. we can't tell if the add_hits value is from this frame or not, so ignore it.
complex condition will only be true if the current condition is true */
}
eval_state->add_hits = 0;
}
/* STEP 5: handle special flags */
switch (condition->type) {
case RC_CONDITION_PAUSE_IF:
/* as soon as we find a PauseIf that evaluates to true, stop processing the rest of the group */
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. */
set_valid = 0;
if (condition->required_hits == 0) {
/* PauseIf didn't evaluate true, and doesn't have a HitCount, reset the HitCount to indicate the condition didn't match */
condition->current_hits = 0;
}
else {
/* PauseIf has a HitCount that hasn't been met, ignore it for now. */
}
continue;
case RC_CONDITION_RESET_IF:
if (cond_valid) {
eval_state->was_reset = 1; /* let caller know to reset all hit counts */
set_valid = 0; /* cannot be valid if we've hit a reset condition */
}
continue;
case RC_CONDITION_MEASURED:
if (condition->required_hits != 0) {
/* if there's a hit target, capture the current hits for recording Measured value later */
measured_from_hits = 1;
measured_value = total_hits;
}
break;
case RC_CONDITION_MEASURED_IF:
if (!cond_valid)
can_measure = 0;
break;
case RC_CONDITION_TRIGGER:
/* update truthiness of set, but do not update truthiness of primed state */
set_valid &= cond_valid;
continue;
default:
break;
}
/* STEP 5: update overall truthiness of set and primed state */
eval_state->primed &= cond_valid;
set_valid &= cond_valid;
}
/* if not suppressed, update the measured value */
if (measured_value > eval_state->measured_value && can_measure) {
eval_state->measured_value = measured_value;
eval_state->measured_from_hits = measured_from_hits;
}
return set_valid;
}
int rc_test_condset(rc_condset_t* self, rc_eval_state_t* eval_state) {
if (self->conditions == 0) {
/* important: empty group must evaluate true */
return 1;
}
if (self->has_pause) {
if ((self->is_paused = rc_test_condset_internal(self, 1, eval_state))) {
/* one or more Pause conditions exists, if any of them are true, stop processing this group */
eval_state->primed = 0;
return 0;
}
}
return rc_test_condset_internal(self, 0, eval_state);
}
void rc_reset_condset(rc_condset_t* self) {
rc_condition_t* condition;
for (condition = self->conditions; condition != 0; condition = condition->next) {
condition->current_hits = 0;
}
}

View File

@ -0,0 +1,659 @@
#include "rcheevos.h"
#include "rc_consoles.h"
#include <ctype.h>
const char* rc_console_name(int console_id)
{
switch (console_id)
{
case RC_CONSOLE_3DO:
return "3DO";
case RC_CONSOLE_AMIGA:
return "Amiga";
case RC_CONSOLE_AMSTRAD_PC:
return "Amstrad CPC";
case RC_CONSOLE_APPLE_II:
return "Apple II";
case RC_CONSOLE_ARCADE:
return "Arcade";
case RC_CONSOLE_ATARI_2600:
return "Atari 2600";
case RC_CONSOLE_ATARI_5200:
return "Atari 5200";
case RC_CONSOLE_ATARI_7800:
return "Atari 7800";
case RC_CONSOLE_ATARI_JAGUAR:
return "Atari Jaguar";
case RC_CONSOLE_ATARI_LYNX:
return "Atari Lynx";
case RC_CONSOLE_ATARI_ST:
return "Atari ST";
case RC_CONSOLE_CASSETTEVISION:
return "CassetteVision";
case RC_CONSOLE_CDI:
return "CD-I";
case RC_CONSOLE_COLECOVISION:
return "ColecoVision";
case RC_CONSOLE_COMMODORE_64:
return "Commodore 64";
case RC_CONSOLE_DREAMCAST:
return "Dreamcast";
case RC_CONSOLE_EVENTS:
return "Events";
case RC_CONSOLE_FAIRCHILD_CHANNEL_F:
return "Fairchild Channel F";
case RC_CONSOLE_FM_TOWNS:
return "FM Towns";
case RC_CONSOLE_GAME_AND_WATCH:
return "Game & Watch";
case RC_CONSOLE_GAMEBOY:
return "GameBoy";
case RC_CONSOLE_GAMEBOY_ADVANCE:
return "GameBoy Advance";
case RC_CONSOLE_GAMEBOY_COLOR:
return "GameBoy Color";
case RC_CONSOLE_GAMECUBE:
return "GameCube";
case RC_CONSOLE_GAME_GEAR:
return "Game Gear";
case RC_CONSOLE_HUBS:
return "Hubs";
case RC_CONSOLE_INTELLIVISION:
return "Intellivision";
case RC_CONSOLE_MAGNAVOX_ODYSSEY2:
return "Magnavox Odyssey 2";
case RC_CONSOLE_MASTER_SYSTEM:
return "Master System";
case RC_CONSOLE_MEGA_DRIVE:
return "Sega Genesis";
case RC_CONSOLE_MS_DOS:
return "MS-DOS";
case RC_CONSOLE_MSX:
return "MSX";
case RC_CONSOLE_NEO_GEO_CD:
return "Neo Geo CD";
case RC_CONSOLE_NEOGEO_POCKET:
return "Neo Geo Pocket";
case RC_CONSOLE_NINTENDO:
return "Nintendo Entertainment System";
case RC_CONSOLE_NINTENDO_64:
return "Nintendo 64";
case RC_CONSOLE_NINTENDO_DS:
return "Nintendo DS";
case RC_CONSOLE_NINTENDO_3DS:
return "Nintendo 3DS";
case RC_CONSOLE_NOKIA_NGAGE:
return "Nokia N-Gage";
case RC_CONSOLE_ORIC:
return "Oric";
case RC_CONSOLE_PC8800:
return "PC-8000/8800";
case RC_CONSOLE_PC9800:
return "PC-9800";
case RC_CONSOLE_PCFX:
return "PC-FX";
case RC_CONSOLE_PC_ENGINE:
return "PCEngine";
case RC_CONSOLE_PLAYSTATION:
return "PlayStation";
case RC_CONSOLE_PLAYSTATION_2:
return "PlayStation 2";
case RC_CONSOLE_PSP:
return "PlayStation Portable";
case RC_CONSOLE_POKEMON_MINI:
return "Pokemon Mini";
case RC_CONSOLE_SEGA_32X:
return "Sega 32X";
case RC_CONSOLE_SEGA_CD:
return "Sega CD";
case RC_CONSOLE_SATURN:
return "Sega Saturn";
case RC_CONSOLE_SG1000:
return "SG-1000";
case RC_CONSOLE_SUPER_NINTENDO:
return "Super Nintendo Entertainment System";
case RC_CONSOLE_SUPER_CASSETTEVISION:
return "Super CassetteVision";
case RC_CONSOLE_WONDERSWAN:
return "WonderSwan";
case RC_CONSOLE_VECTREX:
return "Vectrex";
case RC_CONSOLE_VIC20:
return "VIC-20";
case RC_CONSOLE_VIRTUAL_BOY:
return "Virtual Boy";
case RC_CONSOLE_WII:
return "Wii";
case RC_CONSOLE_WII_U:
return "Wii-U";
case RC_CONSOLE_X68K:
return "X68K";
case RC_CONSOLE_XBOX:
return "XBOX";
case RC_CONSOLE_ZX81:
return "ZX-81";
case RC_CONSOLE_ZX_SPECTRUM:
return "ZX Spectrum";
default:
return "Unknown";
}
}
/* ===== 3DO ===== */
/* http://www.arcaderestoration.com/memorymap/48/3DO+Bios.aspx */
/* NOTE: the Opera core attempts to expose the NVRAM as RETRO_SAVE_RAM, but the 3DO documentation
* says that applications should only access NVRAM through API calls as it's shared across mulitple
* games. This suggests that even if the core does expose it, it may change depending on which other
* games the user has played - so ignore it.
*/
static const rc_memory_region_t _rc_memory_regions_3do[] = {
{ 0x000000U, 0x1FFFFFU, 0x000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Main RAM" },
};
static const rc_memory_regions_t rc_memory_regions_3do = { _rc_memory_regions_3do, 1 };
/* ===== Apple II ===== */
static const rc_memory_region_t _rc_memory_regions_appleii[] = {
{ 0x000000U, 0x00FFFFU, 0x000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Main RAM" },
{ 0x010000U, 0x01FFFFU, 0x010000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Auxillary RAM" }
};
static const rc_memory_regions_t rc_memory_regions_appleii = { _rc_memory_regions_appleii, 2 };
/* ===== Atari 2600 ===== */
static const rc_memory_region_t _rc_memory_regions_atari2600[] = {
{ 0x000000U, 0x00007FU, 0x000080U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }
};
static const rc_memory_regions_t rc_memory_regions_atari2600 = { _rc_memory_regions_atari2600, 1 };
/* ===== Atari 7800 ===== */
/* http://www.atarihq.com/danb/files/78map.txt */
/* http://pdf.textfiles.com/technical/7800_devkit.pdf */
static const rc_memory_region_t _rc_memory_regions_atari7800[] = {
{ 0x000000U, 0x0017FFU, 0x000000U, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "Hardware Interface" },
{ 0x001800U, 0x0027FFU, 0x001800U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
{ 0x002800U, 0x002FFFU, 0x002800U, RC_MEMORY_TYPE_VIRTUAL_RAM, "Mirrored RAM" },
{ 0x003000U, 0x0037FFU, 0x003000U, RC_MEMORY_TYPE_VIRTUAL_RAM, "Mirrored RAM" },
{ 0x003800U, 0x003FFFU, 0x003800U, RC_MEMORY_TYPE_VIRTUAL_RAM, "Mirrored RAM" },
{ 0x004000U, 0x007FFFU, 0x004000U, RC_MEMORY_TYPE_SAVE_RAM, "Cartridge RAM" },
{ 0x008000U, 0x00FFFFU, 0x008000U, RC_MEMORY_TYPE_READONLY, "Cartridge ROM" }
};
static const rc_memory_regions_t rc_memory_regions_atari7800 = { _rc_memory_regions_atari7800, 7 };
/* ===== Atari Jaguar ===== */
/* https://www.mulle-kybernetik.com/jagdox/memorymap.html */
static const rc_memory_region_t _rc_memory_regions_atari_jaguar[] = {
{ 0x000000U, 0x1FFFFFU, 0x000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }
};
static const rc_memory_regions_t rc_memory_regions_atari_jaguar = { _rc_memory_regions_atari_jaguar, 1 };
/* ===== Atari Lynx ===== */
/* http://www.retroisle.com/atari/lynx/Technical/Programming/lynxprgdumm.php */
static const rc_memory_region_t _rc_memory_regions_atari_lynx[] = {
{ 0x000000U, 0x0000FFU, 0x000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Zero Page" },
{ 0x000100U, 0x0001FFU, 0x000100U, RC_MEMORY_TYPE_SYSTEM_RAM, "Stack" },
{ 0x000200U, 0x00FBFFU, 0x000200U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
{ 0x00FC00U, 0x00FCFFU, 0x00FC00U, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "SUZY hardware access" },
{ 0x00FD00U, 0x00FDFFU, 0x00FD00U, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "MIKEY hardware access" },
{ 0x00FE00U, 0x00FFF7U, 0x00FE00U, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "Boot ROM" },
{ 0x00FFF8U, 0x00FFFFU, 0x00FFF8U, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "Hardware vectors" }
};
static const rc_memory_regions_t rc_memory_regions_atari_lynx = { _rc_memory_regions_atari_lynx, 7 };
/* ===== ColecoVision ===== */
static const rc_memory_region_t _rc_memory_regions_colecovision[] = {
{ 0x000000U, 0x0003FFU, 0x006000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }
};
static const rc_memory_regions_t rc_memory_regions_colecovision = { _rc_memory_regions_colecovision, 1 };
/* ===== GameBoy / GameBoy Color ===== */
static const rc_memory_region_t _rc_memory_regions_gameboy[] = {
{ 0x000000U, 0x0000FFU, 0x000000U, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "Interrupt vector" },
{ 0x000100U, 0x00014FU, 0x000100U, RC_MEMORY_TYPE_READONLY, "Cartridge header" },
{ 0x000150U, 0x003FFFU, 0x000150U, RC_MEMORY_TYPE_READONLY, "Cartridge ROM (fixed)" }, /* bank 0 */
{ 0x004000U, 0x007FFFU, 0x004000U, RC_MEMORY_TYPE_READONLY, "Cartridge ROM (paged)" }, /* bank 1-XX (switchable) */
{ 0x008000U, 0x0097FFU, 0x008000U, RC_MEMORY_TYPE_VIDEO_RAM, "Tile RAM" },
{ 0x009800U, 0x009BFFU, 0x009800U, RC_MEMORY_TYPE_VIDEO_RAM, "BG1 map data" },
{ 0x009C00U, 0x009FFFU, 0x009C00U, RC_MEMORY_TYPE_VIDEO_RAM, "BG2 map data" },
{ 0x00A000U, 0x00BFFFU, 0x00A000U, RC_MEMORY_TYPE_SAVE_RAM, "Cartridge RAM"},
{ 0x00C000U, 0x00CFFFU, 0x00C000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM (fixed)" },
{ 0x00D000U, 0x00DFFFU, 0x00D000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM (bank 1)" },
{ 0x00E000U, 0x00FDFFU, 0x00C000U, RC_MEMORY_TYPE_VIRTUAL_RAM, "Echo RAM" },
{ 0x00FE00U, 0x00FE9FU, 0x00FE00U, RC_MEMORY_TYPE_VIDEO_RAM, "Sprite RAM"},
{ 0x00FEA0U, 0x00FEFFU, 0x00FEA0U, RC_MEMORY_TYPE_UNUSED, ""},
{ 0x00FF00U, 0x00FF7FU, 0x00FF00U, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "Hardware I/O"},
{ 0x00FF80U, 0x00FFFEU, 0x00FF80U, RC_MEMORY_TYPE_SYSTEM_RAM, "Quick RAM"},
{ 0x00FFFFU, 0x00FFFFU, 0x00FFFFU, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "Interrupt enable"},
/* GameBoy Color provides six extra banks of memory that can be paged out through the $DXXX
* memory space, but the timing of that does not correspond with blanks, which is when achievements
* are processed. As such, it is desirable to always have access to these extra banks. We do this
* by expecting the extra banks to be addressable at addresses not supported by the native system. */
{ 0x010000U, 0x015FFFU, 0x010000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM (banks 2-7, GBC only)" }
};
static const rc_memory_regions_t rc_memory_regions_gameboy = { _rc_memory_regions_gameboy, 16 };
static const rc_memory_regions_t rc_memory_regions_gameboy_color = { _rc_memory_regions_gameboy, 17 };
/* ===== GameBoy Advance ===== */
static const rc_memory_region_t _rc_memory_regions_gameboy_advance[] = {
{ 0x000000U, 0x007FFFU, 0x03000000U, RC_MEMORY_TYPE_SAVE_RAM, "Cartridge RAM" },
{ 0x008000U, 0x047FFFU, 0x02000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }
};
static const rc_memory_regions_t rc_memory_regions_gameboy_advance = { _rc_memory_regions_gameboy_advance, 2 };
/* ===== Game Gear ===== */
/* http://www.smspower.org/Development/MemoryMap */
static const rc_memory_region_t _rc_memory_regions_game_gear[] = {
{ 0x000000U, 0x001FFFU, 0x00C000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }
};
static const rc_memory_regions_t rc_memory_regions_game_gear = { _rc_memory_regions_game_gear, 1 };
/* ===== Intellivision ===== */
/* http://wiki.intellivision.us/index.php%3Ftitle%3DMemory_Map */
static const rc_memory_region_t _rc_memory_regions_intellivision[] = {
{ 0x000000U, 0x00007FU, 0x000000U, RC_MEMORY_TYPE_VIDEO_RAM, "STIC Registers" },
{ 0x000080U, 0x0000FFU, 0x000080U, RC_MEMORY_TYPE_UNUSED, "" },
{ 0x000100U, 0x00035FU, 0x000100U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
{ 0x000360U, 0x0003FFU, 0x000360U, RC_MEMORY_TYPE_UNUSED, "" },
{ 0x000400U, 0x000FFFU, 0x000400U, RC_MEMORY_TYPE_SYSTEM_RAM, "Cartridge RAM" },
{ 0x001000U, 0x001FFFU, 0x001000U, RC_MEMORY_TYPE_UNUSED, "" },
{ 0x002000U, 0x002FFFU, 0x002000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Cartridge RAM" },
{ 0x003000U, 0x003FFFU, 0x003000U, RC_MEMORY_TYPE_VIDEO_RAM, "Video RAM" },
{ 0x004000U, 0x00FFFFU, 0x004000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Cartridge RAM" },
};
static const rc_memory_regions_t rc_memory_regions_intellivision = { _rc_memory_regions_intellivision, 9 };
/* ===== Magnavox Odyssey 2 ===== */
static const rc_memory_region_t _rc_memory_regions_magnavox_odyssey_2[] = {
{ 0x000000U, 0x00003FU, 0x000040U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }
};
static const rc_memory_regions_t rc_memory_regions_magnavox_odyssey_2 = { _rc_memory_regions_magnavox_odyssey_2, 1 };
/* ===== Master System ===== */
/* http://www.smspower.org/Development/MemoryMap */
static const rc_memory_region_t _rc_memory_regions_master_system[] = {
{ 0x000000U, 0x001FFFU, 0x00C000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }
};
static const rc_memory_regions_t rc_memory_regions_master_system = { _rc_memory_regions_master_system, 1 };
/* ===== MegaDrive (Genesis) ===== */
/* http://www.smspower.org/Development/MemoryMap */
static const rc_memory_region_t _rc_memory_regions_megadrive[] = {
{ 0x000000U, 0x00FFFFU, 0xFF0000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
{ 0x010000U, 0x01FFFFU, 0x000000U, RC_MEMORY_TYPE_SAVE_RAM, "Cartridge RAM" }
};
static const rc_memory_regions_t rc_memory_regions_megadrive = { _rc_memory_regions_megadrive, 2 };
/* ===== MSX ===== */
/* https://www.msx.org/wiki/The_Memory */
/* MSX only has 64KB of addressable RAM, of which 32KB is reserved for the system/BIOS.
* However, the system has up to 512KB of RAM, which is paged into the addressable RAM
* We expect the raw RAM to be exposed, rather than force the devs to worry about the
* paging system. The entire RAM is expected to appear starting at $10000, which is not
* addressable by the system itself.
*/
static const rc_memory_region_t _rc_memory_regions_msx[] = {
{ 0x000000U, 0x07FFFFU, 0x010000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
};
static const rc_memory_regions_t rc_memory_regions_msx = { _rc_memory_regions_msx, 1 };
/* ===== Neo Geo Pocket ===== */
/* http://neopocott.emuunlim.com/docs/tech-11.txt */
static const rc_memory_region_t _rc_memory_regions_neo_geo_pocket[] = {
/* MednafenNGP exposes 16KB, but the doc suggests there's 24-32KB */
{ 0x000000U, 0x003FFFU, 0x000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }
};
static const rc_memory_regions_t rc_memory_regions_neo_geo_pocket = { _rc_memory_regions_neo_geo_pocket, 1 };
/* ===== Nintendo Entertainment System ===== */
/* https://wiki.nesdev.com/w/index.php/CPU_memory_map */
static const rc_memory_region_t _rc_memory_regions_nes[] = {
{ 0x0000U, 0x07FFU, 0x0000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
{ 0x0800U, 0x0FFFU, 0x0000U, RC_MEMORY_TYPE_VIRTUAL_RAM, "Mirror RAM" }, /* duplicates memory from $0000-$07FF */
{ 0x1000U, 0x17FFU, 0x0000U, RC_MEMORY_TYPE_VIRTUAL_RAM, "Mirror RAM" }, /* duplicates memory from $0000-$07FF */
{ 0x1800U, 0x1FFFU, 0x0000U, RC_MEMORY_TYPE_VIRTUAL_RAM, "Mirror RAM" }, /* duplicates memory from $0000-$07FF */
{ 0x2000U, 0x2007U, 0x2000U, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "PPU Register" },
{ 0x2008U, 0x3FFFU, 0x2008U, RC_MEMORY_TYPE_VIRTUAL_RAM, "Mirrored PPU Register" }, /* repeats every 8 bytes */
{ 0x4000U, 0x4017U, 0x4000U, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "APU and I/O register" },
{ 0x4018U, 0x401FU, 0x4018U, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "APU and I/O test register" },
/* NOTE: these are for the original NES/Famicom */
{ 0x4020U, 0x5FFFU, 0x4020U, RC_MEMORY_TYPE_READONLY, "Cartridge data"}, /* varies by mapper */
{ 0x6000U, 0x7FFFU, 0x6000U, RC_MEMORY_TYPE_SAVE_RAM, "Cartridge RAM"},
{ 0x8000U, 0xFFFFU, 0x8000U, RC_MEMORY_TYPE_READONLY, "Cartridge ROM"},
/* NOTE: these are the correct mappings for FDS: https://fms.komkon.org/EMUL8/NES.html
* 0x6000-0xDFFF is RAM on the FDS system and 0xE000-0xFFFF is FDS BIOS.
* If the core implements a memory map, we should still be able to translate the addresses
* correctly as we only use the classifications when a memory map is not provided
{ 0x4020U, 0x40FFU, 0x4020U, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "FDS I/O registers"},
{ 0x4100U, 0x5FFFU, 0x4100U, RC_MEMORY_TYPE_READONLY, "Cartridge data"}, // varies by mapper
{ 0x6000U, 0xDFFFU, 0x6000U, RC_MEMORY_TYPE_SYSTEM_RAM, "FDS RAM"},
{ 0xE000U, 0xFFFFU, 0xE000U, RC_MEMORY_TYPE_READONLY, "FDS BIOS ROM"},
*/
};
static const rc_memory_regions_t rc_memory_regions_nes = { _rc_memory_regions_nes, 11 };
/* ===== Nintendo 64 ===== */
/* https://raw.githubusercontent.com/mikeryan/n64dev/master/docs/n64ops/n64ops%23h.txt */
static const rc_memory_region_t _rc_memory_regions_n64[] = {
{ 0x000000U, 0x1FFFFFU, 0x00000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, /* RDRAM 1 */
{ 0x200000U, 0x3FFFFFU, 0x00200000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, /* RDRAM 2 */
{ 0x400000U, 0x7FFFFFU, 0x80000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" } /* expansion pak - cannot find any details for real address */
};
static const rc_memory_regions_t rc_memory_regions_n64 = { _rc_memory_regions_n64, 3 };
/* ===== Nintendo DS ===== */
/* https://www.akkit.org/info/gbatek.htm#dsmemorymaps */
static const rc_memory_region_t _rc_memory_regions_nintendo_ds[] = {
{ 0x000000U, 0x3FFFFFU, 0x02000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }
};
static const rc_memory_regions_t rc_memory_regions_nintendo_ds = { _rc_memory_regions_nintendo_ds, 1 };
/* ===== Oric ===== */
static const rc_memory_region_t _rc_memory_regions_oric[] = {
/* actual size depends on machine type - up to 64KB */
{ 0x000000U, 0x00FFFFU, 0x000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }
};
static const rc_memory_regions_t rc_memory_regions_oric = { _rc_memory_regions_oric, 1 };
/* ===== PC-8800 ===== */
static const rc_memory_region_t _rc_memory_regions_pc8800[] = {
{ 0x000000U, 0x00FFFFU, 0x000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Main RAM" },
{ 0x010000U, 0x010FFFU, 0x010000U, RC_MEMORY_TYPE_VIDEO_RAM, "Text VRAM" } /* technically VRAM, but often used as system RAM */
};
static const rc_memory_regions_t rc_memory_regions_pc8800 = { _rc_memory_regions_pc8800, 2 };
/* ===== PC Engine ===== */
/* http://www.archaicpixels.com/Memory_Map */
static const rc_memory_region_t _rc_memory_regions_pcengine[] = {
{ 0x000000U, 0x001FFFU, 0x1F0000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
{ 0x002000U, 0x011FFFU, 0x100000U, RC_MEMORY_TYPE_SYSTEM_RAM, "CD RAM" },
{ 0x012000U, 0x041FFFU, 0x0D0000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Super System Card RAM" },
{ 0x042000U, 0x0427FFU, 0x1EE000U, RC_MEMORY_TYPE_SAVE_RAM, "CD Battery-backed RAM" }
};
static const rc_memory_regions_t rc_memory_regions_pcengine = { _rc_memory_regions_pcengine, 4 };
/* ===== PC-FX ===== */
/* http://daifukkat.su/pcfx/data/memmap.html */
static const rc_memory_region_t _rc_memory_regions_pcfx[] = {
{ 0x000000U, 0x1FFFFFU, 0x00000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
{ 0x200000U, 0x207FFFU, 0xE0000000U, RC_MEMORY_TYPE_SAVE_RAM, "Internal Backup Memory" },
{ 0x208000U, 0x20FFFFU, 0xE8000000U, RC_MEMORY_TYPE_SAVE_RAM, "External Backup Memory" },
};
static const rc_memory_regions_t rc_memory_regions_pcfx = { _rc_memory_regions_pcfx, 3 };
/* ===== PlayStation ===== */
/* http://www.raphnet.net/electronique/psx_adaptor/Playstation.txt */
static const rc_memory_region_t _rc_memory_regions_playstation[] = {
{ 0x000000U, 0x00FFFFU, 0x000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Kernel RAM" },
{ 0x010000U, 0x1FFFFFU, 0x010000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }
};
static const rc_memory_regions_t rc_memory_regions_playstation = { _rc_memory_regions_playstation, 2 };
/* ===== Pokemon Mini ===== */
/* https://www.pokemon-mini.net/documentation/memory-map/ */
static const rc_memory_region_t _rc_memory_regions_pokemini[] = {
{ 0x000000U, 0x000FFFU, 0x000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "BIOS RAM" },
{ 0x001000U, 0x001FFFU, 0x001000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }
};
static const rc_memory_regions_t rc_memory_regions_pokemini = { _rc_memory_regions_pokemini, 2 };
/* ===== Sega CD ===== */
/* https://en.wikibooks.org/wiki/Genesis_Programming#MegaCD_Changes */
static const rc_memory_region_t _rc_memory_regions_segacd[] = {
{ 0x000000U, 0x00FFFFU, 0x00FF0000U, RC_MEMORY_TYPE_SYSTEM_RAM, "68000 RAM" },
{ 0x010000U, 0x08FFFFU, 0x80020000U, RC_MEMORY_TYPE_SAVE_RAM, "CD PRG RAM" } /* normally banked into $020000-$03FFFF */
};
static const rc_memory_regions_t rc_memory_regions_segacd = { _rc_memory_regions_segacd, 2 };
/* ===== Sega Saturn ===== */
/* https://segaretro.org/Sega_Saturn_hardware_notes_(2004-04-27) */
static const rc_memory_region_t _rc_memory_regions_saturn[] = {
{ 0x000000U, 0x0FFFFFU, 0x00200000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Work RAM Low" },
{ 0x100000U, 0x1FFFFFU, 0x06000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Work RAM High" }
};
static const rc_memory_regions_t rc_memory_regions_saturn = { _rc_memory_regions_saturn, 2 };
/* ===== SG-1000 ===== */
/* http://www.smspower.org/Development/MemoryMap */
static const rc_memory_region_t _rc_memory_regions_sg1000[] = {
{ 0x000000U, 0x0003FFU, 0xC000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }
/* TODO: should cartridge memory be exposed ($0000-$BFFF)? it's usually just ROM data, but may contain on-cartridge RAM
* This not is also concerning: http://www.smspower.org/Development/MemoryMap
* Cartridges may disable the system RAM and thus take over the full 64KB address space. */
};
static const rc_memory_regions_t rc_memory_regions_sg1000 = { _rc_memory_regions_sg1000, 1 };
/* ===== Super Cassette Vision ===== */
static const rc_memory_region_t _rc_memory_regions_scv[] = {
{ 0x000000U, 0x000FFFU, 0x000000U, RC_MEMORY_TYPE_READONLY, "System ROM" },
{ 0x001000U, 0x001FFFU, 0x001000U, RC_MEMORY_TYPE_UNUSED, "" },
{ 0x002000U, 0x003FFFU, 0x002000U, RC_MEMORY_TYPE_VIDEO_RAM, "Video RAM" },
{ 0x004000U, 0x007FFFU, 0x004000U, RC_MEMORY_TYPE_UNUSED, "" },
{ 0x008000U, 0x00DFFFU, 0x008000U, RC_MEMORY_TYPE_READONLY, "Cartridge ROM" },
{ 0x00E000U, 0x00FF7FU, 0x00E000U, RC_MEMORY_TYPE_SAVE_RAM, "Cartridge RAM" },
{ 0x00FF80U, 0x00FFFFU, 0x00FF80U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }
};
static const rc_memory_regions_t rc_memory_regions_scv = { _rc_memory_regions_scv, 7 };
/* ===== Super Nintendo ===== */
/* https://en.wikibooks.org/wiki/Super_NES_Programming/SNES_memory_map#LoROM */
static const rc_memory_region_t _rc_memory_regions_snes[] = {
{ 0x000000U, 0x01FFFFU, 0x7E0000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
{ 0x020000U, 0x03FFFFU, 0xFE0000U, RC_MEMORY_TYPE_SAVE_RAM, "Cartridge RAM" }
};
static const rc_memory_regions_t rc_memory_regions_snes = { _rc_memory_regions_snes, 2 };
/* ===== Vectrex ===== */
/* https://roadsidethoughts.com/vectrex/vectrex-memory-map.htm */
static const rc_memory_region_t _rc_memory_regions_vectrex[] = {
{ 0x000000U, 0x0003FFU, 0x00C800U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }
};
static const rc_memory_regions_t rc_memory_regions_vectrex = { _rc_memory_regions_vectrex, 1 };
/* ===== Virtual Boy ===== */
static const rc_memory_region_t _rc_memory_regions_virtualboy[] = {
{ 0x000000U, 0x00FFFFU, 0x05000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
{ 0x010000U, 0x01FFFFU, 0x06000000U, RC_MEMORY_TYPE_SAVE_RAM, "Cartridge RAM" }
};
static const rc_memory_regions_t rc_memory_regions_virtualboy = { _rc_memory_regions_virtualboy, 2 };
/* ===== WonderSwan ===== */
/* http://daifukkat.su/docs/wsman/#ovr_memmap */
static const rc_memory_region_t _rc_memory_regions_wonderswan[] = {
/* RAM ends at 0x3FFF for WonderSwan, WonderSwan color uses all 64KB */
{ 0x000000U, 0x00FFFFU, 0x000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
/* Only 64KB of SRAM is accessible via the addressing scheme, but the cartridge
* may have up to 512KB of SRAM. http://daifukkat.su/docs/wsman/#cart_meta
* Since beetle_wswan exposes it as a contiguous block, assume its contiguous
* even though the documentation says $20000-$FFFFF is ROM data. If this causes
* a conflict in the future, we can revisit. A new region with a virtual address
* could be added to pick up the additional SRAM data. As long as it immediately
* follows the 64KB at $10000, all existing achievements should be unaffected.
*/
{ 0x010000U, 0x08FFFFU, 0x010000U, RC_MEMORY_TYPE_SAVE_RAM, "Cartridge RAM" }
};
static const rc_memory_regions_t rc_memory_regions_wonderswan = { _rc_memory_regions_wonderswan, 2 };
/* ===== default ===== */
static const rc_memory_regions_t rc_memory_regions_none = { 0, 0 };
const rc_memory_regions_t* rc_console_memory_regions(int console_id)
{
switch (console_id)
{
case RC_CONSOLE_3DO:
return &rc_memory_regions_3do;
case RC_CONSOLE_APPLE_II:
return &rc_memory_regions_appleii;
case RC_CONSOLE_ATARI_2600:
return &rc_memory_regions_atari2600;
case RC_CONSOLE_ATARI_7800:
return &rc_memory_regions_atari7800;
case RC_CONSOLE_ATARI_JAGUAR:
return &rc_memory_regions_atari_jaguar;
case RC_CONSOLE_ATARI_LYNX:
return &rc_memory_regions_atari_lynx;
case RC_CONSOLE_COLECOVISION:
return &rc_memory_regions_colecovision;
case RC_CONSOLE_GAMEBOY:
return &rc_memory_regions_gameboy;
case RC_CONSOLE_GAMEBOY_COLOR:
return &rc_memory_regions_gameboy_color;
case RC_CONSOLE_GAMEBOY_ADVANCE:
return &rc_memory_regions_gameboy_advance;
case RC_CONSOLE_GAME_GEAR:
return &rc_memory_regions_game_gear;
case RC_CONSOLE_INTELLIVISION:
return &rc_memory_regions_intellivision;
case RC_CONSOLE_MAGNAVOX_ODYSSEY2:
return &rc_memory_regions_magnavox_odyssey_2;
case RC_CONSOLE_MASTER_SYSTEM:
return &rc_memory_regions_master_system;
case RC_CONSOLE_MEGA_DRIVE:
case RC_CONSOLE_SEGA_32X:
/* NOTE: 32x adds an extra 512KB of memory (256KB RAM + 256KB VRAM) to the
* Genesis, but we currently don't support it. */
return &rc_memory_regions_megadrive;
case RC_CONSOLE_MSX:
return &rc_memory_regions_msx;
case RC_CONSOLE_NEOGEO_POCKET:
return &rc_memory_regions_neo_geo_pocket;
case RC_CONSOLE_NINTENDO:
return &rc_memory_regions_nes;
case RC_CONSOLE_NINTENDO_64:
return &rc_memory_regions_n64;
case RC_CONSOLE_NINTENDO_DS:
return &rc_memory_regions_nintendo_ds;
case RC_CONSOLE_ORIC:
return &rc_memory_regions_oric;
case RC_CONSOLE_PC8800:
return &rc_memory_regions_pc8800;
case RC_CONSOLE_PC_ENGINE:
return &rc_memory_regions_pcengine;
case RC_CONSOLE_PCFX:
return &rc_memory_regions_pcfx;
case RC_CONSOLE_PLAYSTATION:
return &rc_memory_regions_playstation;
case RC_CONSOLE_POKEMON_MINI:
return &rc_memory_regions_pokemini;
case RC_CONSOLE_SATURN:
return &rc_memory_regions_saturn;
case RC_CONSOLE_SEGA_CD:
return &rc_memory_regions_segacd;
case RC_CONSOLE_SG1000:
return &rc_memory_regions_sg1000;
case RC_CONSOLE_SUPER_CASSETTEVISION:
return &rc_memory_regions_scv;
case RC_CONSOLE_SUPER_NINTENDO:
return &rc_memory_regions_snes;
case RC_CONSOLE_VECTREX:
return &rc_memory_regions_vectrex;
case RC_CONSOLE_VIRTUAL_BOY:
return &rc_memory_regions_virtualboy;
case RC_CONSOLE_WONDERSWAN:
return &rc_memory_regions_wonderswan;
default:
return &rc_memory_regions_none;
}
}

View File

@ -0,0 +1,155 @@
#include "rc_internal.h"
#include "rc_compat.h"
#include <string.h>
#include <stdio.h>
int rc_parse_format(const char* format_str) {
switch (*format_str++) {
case 'F':
if (!strcmp(format_str, "RAMES")) {
return RC_FORMAT_FRAMES;
}
break;
case 'T':
if (!strcmp(format_str, "IME")) {
return RC_FORMAT_FRAMES;
}
else if (!strcmp(format_str, "IMESECS")) {
return RC_FORMAT_SECONDS;
}
break;
case 'S':
if (!strcmp(format_str, "ECS")) {
return RC_FORMAT_SECONDS;
}
if (!strcmp(format_str, "CORE")) {
return RC_FORMAT_SCORE;
}
if (!strcmp(format_str, "ECS_AS_MINS")) {
return RC_FORMAT_SECONDS_AS_MINUTES;
}
break;
case 'M':
if (!strcmp(format_str, "ILLISECS")) {
return RC_FORMAT_CENTISECS;
}
if (!strcmp(format_str, "INUTES")) {
return RC_FORMAT_MINUTES;
}
break;
case 'P':
if (!strcmp(format_str, "OINTS")) {
return RC_FORMAT_SCORE;
}
break;
case 'V':
if (!strcmp(format_str, "ALUE")) {
return RC_FORMAT_VALUE;
}
break;
case 'O':
if (!strcmp(format_str, "THER")) {
return RC_FORMAT_SCORE;
}
break;
}
return RC_FORMAT_VALUE;
}
static int rc_format_value_minutes(char* buffer, int size, unsigned minutes) {
unsigned hours;
hours = minutes / 60;
minutes -= hours * 60;
return snprintf(buffer, size, "%uh%02u", hours, minutes);
}
static int rc_format_value_seconds(char* buffer, int size, unsigned seconds) {
unsigned hours, minutes;
/* apply modulus math to split the seconds into hours/minutes/seconds */
minutes = seconds / 60;
seconds -= minutes * 60;
if (minutes < 60) {
return snprintf(buffer, size, "%u:%02u", minutes, seconds);
}
hours = minutes / 60;
minutes -= hours * 60;
return snprintf(buffer, size, "%uh%02u:%02u", hours, minutes, seconds);
}
static int rc_format_value_centiseconds(char* buffer, int size, unsigned centiseconds) {
unsigned seconds;
int chars, chars2;
/* modulus off the centiseconds */
seconds = centiseconds / 100;
centiseconds -= seconds * 100;
chars = rc_format_value_seconds(buffer, size, seconds);
if (chars > 0) {
chars2 = snprintf(buffer + chars, size - chars, ".%02u", centiseconds);
if (chars2 > 0) {
chars += chars2;
} else {
chars = chars2;
}
}
return chars;
}
int rc_format_value(char* buffer, int size, int value, int format) {
int chars;
switch (format) {
case RC_FORMAT_FRAMES:
/* 60 frames per second = 100 centiseconds / 60 frames; multiply frames by 100 / 60 */
chars = rc_format_value_centiseconds(buffer, size, value * 10 / 6);
break;
case RC_FORMAT_SECONDS:
chars = rc_format_value_seconds(buffer, size, value);
break;
case RC_FORMAT_CENTISECS:
chars = rc_format_value_centiseconds(buffer, size, value);
break;
case RC_FORMAT_SECONDS_AS_MINUTES:
chars = rc_format_value_minutes(buffer, size, value / 60);
break;
case RC_FORMAT_MINUTES:
chars = rc_format_value_minutes(buffer, size, value);
break;
case RC_FORMAT_SCORE:
chars = snprintf(buffer, size, "%06d", value);
break;
default:
case RC_FORMAT_VALUE:
chars = snprintf(buffer, size, "%d", value);
break;
}
return chars;
}

View File

@ -0,0 +1,263 @@
#include "rc_internal.h"
enum {
RC_LBOARD_START = 1 << 0,
RC_LBOARD_CANCEL = 1 << 1,
RC_LBOARD_SUBMIT = 1 << 2,
RC_LBOARD_VALUE = 1 << 3,
RC_LBOARD_PROGRESS = 1 << 4,
RC_LBOARD_COMPLETE = RC_LBOARD_START | RC_LBOARD_CANCEL | RC_LBOARD_SUBMIT | RC_LBOARD_VALUE
};
void rc_parse_lboard_internal(rc_lboard_t* self, const char* memaddr, rc_parse_state_t* parse) {
int found;
self->progress = 0;
found = 0;
for (;;)
{
if ((memaddr[0] == 's' || memaddr[0] == 'S') &&
(memaddr[1] == 't' || memaddr[1] == 'T') &&
(memaddr[2] == 'a' || memaddr[2] == 'A') && memaddr[3] == ':') {
if ((found & RC_LBOARD_START) != 0) {
parse->offset = RC_DUPLICATED_START;
return;
}
found |= RC_LBOARD_START;
memaddr += 4;
rc_parse_trigger_internal(&self->start, &memaddr, parse);
self->start.memrefs = 0;
if (parse->offset < 0) {
return;
}
}
else if ((memaddr[0] == 'c' || memaddr[0] == 'C') &&
(memaddr[1] == 'a' || memaddr[1] == 'A') &&
(memaddr[2] == 'n' || memaddr[2] == 'N') && memaddr[3] == ':') {
if ((found & RC_LBOARD_CANCEL) != 0) {
parse->offset = RC_DUPLICATED_CANCEL;
return;
}
found |= RC_LBOARD_CANCEL;
memaddr += 4;
rc_parse_trigger_internal(&self->cancel, &memaddr, parse);
self->cancel.memrefs = 0;
if (parse->offset < 0) {
return;
}
}
else if ((memaddr[0] == 's' || memaddr[0] == 'S') &&
(memaddr[1] == 'u' || memaddr[1] == 'U') &&
(memaddr[2] == 'b' || memaddr[2] == 'B') && memaddr[3] == ':') {
if ((found & RC_LBOARD_SUBMIT) != 0) {
parse->offset = RC_DUPLICATED_SUBMIT;
return;
}
found |= RC_LBOARD_SUBMIT;
memaddr += 4;
rc_parse_trigger_internal(&self->submit, &memaddr, parse);
self->submit.memrefs = 0;
if (parse->offset < 0) {
return;
}
}
else if ((memaddr[0] == 'v' || memaddr[0] == 'V') &&
(memaddr[1] == 'a' || memaddr[1] == 'A') &&
(memaddr[2] == 'l' || memaddr[2] == 'L') && memaddr[3] == ':') {
if ((found & RC_LBOARD_VALUE) != 0) {
parse->offset = RC_DUPLICATED_VALUE;
return;
}
found |= RC_LBOARD_VALUE;
memaddr += 4;
rc_parse_value_internal(&self->value, &memaddr, parse);
self->value.memrefs = 0;
if (parse->offset < 0) {
return;
}
}
else if ((memaddr[0] == 'p' || memaddr[0] == 'P') &&
(memaddr[1] == 'r' || memaddr[1] == 'R') &&
(memaddr[2] == 'o' || memaddr[2] == 'O') && memaddr[3] == ':') {
if ((found & RC_LBOARD_PROGRESS) != 0) {
parse->offset = RC_DUPLICATED_PROGRESS;
return;
}
found |= RC_LBOARD_PROGRESS;
memaddr += 4;
self->progress = RC_ALLOC(rc_value_t, parse);
rc_parse_value_internal(self->progress, &memaddr, parse);
self->progress->memrefs = 0;
if (parse->offset < 0) {
return;
}
}
else {
parse->offset = RC_INVALID_LBOARD_FIELD;
return;
}
if (memaddr[0] != ':' || memaddr[1] != ':') {
break;
}
memaddr += 2;
}
if ((found & RC_LBOARD_COMPLETE) != RC_LBOARD_COMPLETE) {
if ((found & RC_LBOARD_START) == 0) {
parse->offset = RC_MISSING_START;
}
else if ((found & RC_LBOARD_CANCEL) == 0) {
parse->offset = RC_MISSING_CANCEL;
}
else if ((found & RC_LBOARD_SUBMIT) == 0) {
parse->offset = RC_MISSING_SUBMIT;
}
else if ((found & RC_LBOARD_VALUE) == 0) {
parse->offset = RC_MISSING_VALUE;
}
return;
}
self->state = RC_LBOARD_STATE_WAITING;
}
int rc_lboard_size(const char* memaddr) {
rc_lboard_t* self;
rc_parse_state_t parse;
rc_memref_t* first_memref;
rc_init_parse_state(&parse, 0, 0, 0);
rc_init_parse_state_memrefs(&parse, &first_memref);
self = RC_ALLOC(rc_lboard_t, &parse);
rc_parse_lboard_internal(self, memaddr, &parse);
rc_destroy_parse_state(&parse);
return parse.offset;
}
rc_lboard_t* rc_parse_lboard(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx) {
rc_lboard_t* self;
rc_parse_state_t parse;
rc_init_parse_state(&parse, buffer, L, funcs_ndx);
self = RC_ALLOC(rc_lboard_t, &parse);
rc_init_parse_state_memrefs(&parse, &self->memrefs);
rc_parse_lboard_internal(self, memaddr, &parse);
rc_destroy_parse_state(&parse);
return parse.offset >= 0 ? self : 0;
}
int rc_evaluate_lboard(rc_lboard_t* self, int* value, rc_peek_t peek, void* peek_ud, lua_State* L) {
int start_ok, cancel_ok, submit_ok;
rc_update_memref_values(self->memrefs, peek, peek_ud);
if (self->state == RC_LBOARD_STATE_INACTIVE || self->state == RC_LBOARD_STATE_DISABLED)
return RC_LBOARD_STATE_INACTIVE;
/* these are always tested once every frame, to ensure hit counts work properly */
start_ok = rc_test_trigger(&self->start, peek, peek_ud, L);
cancel_ok = rc_test_trigger(&self->cancel, peek, peek_ud, L);
submit_ok = rc_test_trigger(&self->submit, peek, peek_ud, L);
switch (self->state)
{
case RC_LBOARD_STATE_WAITING:
case RC_LBOARD_STATE_TRIGGERED:
case RC_LBOARD_STATE_CANCELED:
/* don't activate/reactivate until the start condition becomes false */
if (start_ok) {
*value = 0;
return RC_LBOARD_STATE_INACTIVE; /* just return inactive for all of these */
}
/* start condition is false, allow the leaderboard to start on future frames */
self->state = RC_LBOARD_STATE_ACTIVE;
break;
case RC_LBOARD_STATE_ACTIVE:
/* leaderboard attempt is not in progress. if the start condition is true and the cancel condition is not, start the attempt */
if (start_ok && !cancel_ok) {
if (submit_ok) {
/* start and submit are both true in the same frame, just submit without announcing the leaderboard is available */
self->state = RC_LBOARD_STATE_TRIGGERED;
}
else if (self->start.requirement == 0 && self->start.alternative == 0) {
/* start condition is empty - this leaderboard is submit-only with no measured progress */
}
else {
/* start the leaderboard attempt */
self->state = RC_LBOARD_STATE_STARTED;
/* reset any hit counts in the value */
if (self->progress)
rc_reset_value(self->progress);
rc_reset_value(&self->value);
}
}
break;
case RC_LBOARD_STATE_STARTED:
/* leaderboard attempt in progress */
if (cancel_ok) {
/* cancel condition is true, abort the attempt */
self->state = RC_LBOARD_STATE_CANCELED;
}
else if (submit_ok) {
/* submit condition is true, submit the current value */
self->state = RC_LBOARD_STATE_TRIGGERED;
}
break;
}
/* Calculate the value */
switch (self->state) {
case RC_LBOARD_STATE_STARTED:
if (self->progress) {
*value = rc_evaluate_value(self->progress, peek, peek_ud, L);
break;
}
/* fallthrough to RC_LBOARD_STATE_TRIGGERED */
case RC_LBOARD_STATE_TRIGGERED:
*value = rc_evaluate_value(&self->value, peek, peek_ud, L);
break;
default:
*value = 0;
break;
}
return self->state;
}
void rc_reset_lboard(rc_lboard_t* self) {
self->state = RC_LBOARD_STATE_WAITING;
rc_reset_trigger(&self->start);
rc_reset_trigger(&self->submit);
rc_reset_trigger(&self->cancel);
if (self->progress)
rc_reset_value(self->progress);
rc_reset_value(&self->value);
}

View File

@ -0,0 +1,225 @@
#include "rc_internal.h"
#include <stdlib.h> /* malloc/realloc */
#include <string.h> /* memcpy */
#define MEMREF_PLACEHOLDER_ADDRESS 0xFFFFFFFF
rc_memref_t* rc_alloc_memref(rc_parse_state_t* parse, unsigned address, char size, char is_indirect) {
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;
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);
}
memset(memref, 0, sizeof(*memref));
memref->address = address;
memref->value.size = size;
memref->value.is_indirect = is_indirect;
return memref;
}
int rc_parse_memref(const char** memaddr, char* size, unsigned* address) {
const char* aux = *memaddr;
char* end;
unsigned long value;
if (*aux++ != '0')
return RC_INVALID_MEMORY_OPERAND;
if (*aux != 'x' && *aux != 'X')
return RC_INVALID_MEMORY_OPERAND;
aux++;
switch (*aux++) {
case 'm': case 'M': *size = RC_MEMSIZE_BIT_0; break;
case 'n': case 'N': *size = RC_MEMSIZE_BIT_1; break;
case 'o': case 'O': *size = RC_MEMSIZE_BIT_2; break;
case 'p': case 'P': *size = RC_MEMSIZE_BIT_3; break;
case 'q': case 'Q': *size = RC_MEMSIZE_BIT_4; break;
case 'r': case 'R': *size = RC_MEMSIZE_BIT_5; break;
case 's': case 'S': *size = RC_MEMSIZE_BIT_6; break;
case 't': case 'T': *size = RC_MEMSIZE_BIT_7; break;
case 'l': case 'L': *size = RC_MEMSIZE_LOW; break;
case 'u': case 'U': *size = RC_MEMSIZE_HIGH; break;
case 'k': case 'K': *size = RC_MEMSIZE_BITCOUNT; break;
case 'h': case 'H': *size = RC_MEMSIZE_8_BITS; break;
case 'w': case 'W': *size = RC_MEMSIZE_24_BITS; break;
case 'x': case 'X': *size = RC_MEMSIZE_32_BITS; break;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
aux--;
/* fallthrough */
case ' ':
*size = RC_MEMSIZE_16_BITS;
break;
default:
return RC_INVALID_MEMORY_OPERAND;
}
value = strtoul(aux, &end, 16);
if (end == aux)
return RC_INVALID_MEMORY_OPERAND;
if (value > 0xffffffffU)
value = 0xffffffffU;
*address = (unsigned)value;
*memaddr = end;
return RC_OK;
}
static unsigned rc_peek_value(unsigned address, char size, rc_peek_t peek, void* ud) {
unsigned value;
if (!peek)
return 0;
switch (size)
{
case RC_MEMSIZE_BIT_0:
value = (peek(address, 1, ud) >> 0) & 1;
break;
case RC_MEMSIZE_BIT_1:
value = (peek(address, 1, ud) >> 1) & 1;
break;
case RC_MEMSIZE_BIT_2:
value = (peek(address, 1, ud) >> 2) & 1;
break;
case RC_MEMSIZE_BIT_3:
value = (peek(address, 1, ud) >> 3) & 1;
break;
case RC_MEMSIZE_BIT_4:
value = (peek(address, 1, ud) >> 4) & 1;
break;
case RC_MEMSIZE_BIT_5:
value = (peek(address, 1, ud) >> 5) & 1;
break;
case RC_MEMSIZE_BIT_6:
value = (peek(address, 1, ud) >> 6) & 1;
break;
case RC_MEMSIZE_BIT_7:
value = (peek(address, 1, ud) >> 7) & 1;
break;
case RC_MEMSIZE_LOW:
value = peek(address, 1, ud) & 0x0f;
break;
case RC_MEMSIZE_HIGH:
value = (peek(address, 1, ud) >> 4) & 0x0f;
break;
case RC_MEMSIZE_8_BITS:
value = peek(address, 1, ud);
break;
case RC_MEMSIZE_16_BITS:
value = peek(address, 2, ud);
break;
case RC_MEMSIZE_24_BITS:
/* peek 4 bytes - don't expect the caller to understand 24-bit numbers */
value = peek(address, 4, ud) & 0x00FFFFFF;
break;
case RC_MEMSIZE_32_BITS:
value = peek(address, 4, ud);
break;
default:
value = 0;
break;
}
return value;
}
void rc_update_memref_value(rc_memref_value_t* memref, unsigned new_value) {
if (memref->value == new_value) {
memref->changed = 0;
}
else {
memref->prior = memref->value;
memref->value = new_value;
memref->changed = 1;
}
}
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;
}
unsigned 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 unsigned 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));
}
return rc_get_memref_value_value(&memref->value, operand_type);
}
unsigned rc_get_memref_value_value(rc_memref_value_t* memref, int operand_type) {
switch (operand_type)
{
/* most common case explicitly first, even though it could be handled by default case.
* this helps the compiler to optimize if it turns the switch into a series of if/elses */
case RC_OPERAND_ADDRESS:
return memref->value;
case RC_OPERAND_DELTA:
if (!memref->changed) {
/* fallthrough */
default:
return memref->value;
}
/* fallthrough */
case RC_OPERAND_PRIOR:
return memref->prior;
}
}

View File

@ -0,0 +1,470 @@
#include "rc_internal.h"
#include <stdlib.h>
#include <ctype.h>
#include <math.h>
#ifndef RC_DISABLE_LUA
#ifdef __cplusplus
extern "C" {
#endif
#include <lua.h>
#include <lauxlib.h>
#ifdef __cplusplus
}
#endif
#endif /* RC_DISABLE_LUA */
static int rc_parse_operand_lua(rc_operand_t* self, const char** memaddr, rc_parse_state_t* parse) {
const char* aux = *memaddr;
#ifndef RC_DISABLE_LUA
const char* id;
#endif
if (*aux++ != '@') {
return RC_INVALID_LUA_OPERAND;
}
if (!isalpha(*aux)) {
return RC_INVALID_LUA_OPERAND;
}
#ifndef RC_DISABLE_LUA
id = aux;
#endif
while (isalnum(*aux) || *aux == '_') {
aux++;
}
#ifndef RC_DISABLE_LUA
if (parse->L != 0) {
if (!lua_istable(parse->L, parse->funcs_ndx)) {
return RC_INVALID_LUA_OPERAND;
}
lua_pushlstring(parse->L, id, aux - id);
lua_gettable(parse->L, parse->funcs_ndx);
if (!lua_isfunction(parse->L, -1)) {
lua_pop(parse->L, 1);
return RC_INVALID_LUA_OPERAND;
}
self->value.luafunc = luaL_ref(parse->L, LUA_REGISTRYINDEX);
}
#endif /* RC_DISABLE_LUA */
self->type = RC_OPERAND_LUA;
*memaddr = aux;
return RC_OK;
}
static int rc_parse_operand_memory(rc_operand_t* self, const char** memaddr, rc_parse_state_t* parse, int is_indirect) {
const char* aux = *memaddr;
unsigned address;
char size;
int ret;
switch (*aux) {
case 'd': case 'D':
self->type = RC_OPERAND_DELTA;
++aux;
break;
case 'p': case 'P':
self->type = RC_OPERAND_PRIOR;
++aux;
break;
case 'b': case 'B':
self->type = RC_OPERAND_BCD;
++aux;
break;
case '~':
self->type = RC_OPERAND_INVERTED;
++aux;
break;
default:
self->type = RC_OPERAND_ADDRESS;
break;
}
ret = rc_parse_memref(&aux, &self->size, &address);
if (ret != RC_OK)
return ret;
switch (self->size) {
case RC_MEMSIZE_BIT_0:
case RC_MEMSIZE_BIT_1:
case RC_MEMSIZE_BIT_2:
case RC_MEMSIZE_BIT_3:
case RC_MEMSIZE_BIT_4:
case RC_MEMSIZE_BIT_5:
case RC_MEMSIZE_BIT_6:
case RC_MEMSIZE_BIT_7:
case RC_MEMSIZE_LOW:
case RC_MEMSIZE_HIGH:
case RC_MEMSIZE_BITCOUNT:
/* these can all share an 8-bit memref and just mask off the appropriate data in rc_evaluate_operand */
size = RC_MEMSIZE_8_BITS;
break;
default:
size = self->size;
break;
}
self->value.memref = rc_alloc_memref(parse, address, size, is_indirect);
if (parse->offset < 0)
return parse->offset;
*memaddr = aux;
return RC_OK;
}
int rc_parse_operand(rc_operand_t* self, const char** memaddr, int is_trigger, int is_indirect, rc_parse_state_t* parse) {
const char* aux = *memaddr;
char* end;
int ret;
unsigned long value;
int negative;
self->size = RC_MEMSIZE_32_BITS;
switch (*aux) {
case 'h': case 'H': /* hex constant */
if (aux[2] == 'x' || aux[2] == 'X') {
/* H0x1234 is a typo - either H1234 or 0xH1234 was probably meant */
return RC_INVALID_CONST_OPERAND;
}
value = strtoul(++aux, &end, 16);
if (end == aux) {
return RC_INVALID_CONST_OPERAND;
}
if (value > 0xffffffffU) {
value = 0xffffffffU;
}
self->type = RC_OPERAND_CONST;
self->value.num = (unsigned)value;
aux = end;
break;
case 'f': case 'F': /* floating point constant */
self->value.dbl = strtod(++aux, &end);
if (end == aux) {
return RC_INVALID_FP_OPERAND;
}
if (floor(self->value.dbl) == self->value.dbl) {
self->type = RC_OPERAND_CONST;
self->value.num = (unsigned)floor(self->value.dbl);
}
else {
self->type = RC_OPERAND_FP;
}
aux = end;
break;
case 'v': case 'V': /* signed integer constant */
++aux;
/* fallthrough */
case '+': case '-': /* signed integer constant */
negative = 0;
if (*aux == '-')
{
negative = 1;
++aux;
}
else if (*aux == '+')
{
++aux;
}
value = strtoul(aux, &end, 10);
if (end == aux) {
return RC_INVALID_CONST_OPERAND;
}
if (value > 0x7fffffffU) {
value = 0x7fffffffU;
}
self->type = RC_OPERAND_CONST;
if (negative)
self->value.num = (unsigned)(-((long)value));
else
self->value.num = (unsigned)value;
aux = end;
break;
case '0':
if (aux[1] == 'x' || aux[1] == 'X') { /* hex integer constant */
/* fall through */
default:
ret = rc_parse_operand_memory(self, &aux, parse, is_indirect);
if (ret < 0) {
return ret;
}
break;
}
/* fall through for case '0' where not '0x' */
case '1': case '2': case '3': case '4': case '5': /* unsigned integer constant */
case '6': case '7': case '8': case '9':
value = strtoul(aux, &end, 10);
if (end == aux) {
return RC_INVALID_CONST_OPERAND;
}
if (value > 0xffffffffU) {
value = 0xffffffffU;
}
self->type = RC_OPERAND_CONST;
self->value.num = (unsigned)value;
aux = end;
break;
case '@':
ret = rc_parse_operand_lua(self, &aux, parse);
if (ret < 0) {
return ret;
}
break;
}
*memaddr = aux;
return RC_OK;
}
#ifndef RC_DISABLE_LUA
typedef struct {
rc_peek_t peek;
void* ud;
}
rc_luapeek_t;
static int rc_luapeek(lua_State* L) {
unsigned address = (unsigned)luaL_checkinteger(L, 1);
unsigned num_bytes = (unsigned)luaL_checkinteger(L, 2);
rc_luapeek_t* luapeek = (rc_luapeek_t*)lua_touserdata(L, 3);
unsigned value = luapeek->peek(address, num_bytes, luapeek->ud);
lua_pushinteger(L, value);
return 1;
}
#endif /* RC_DISABLE_LUA */
static const unsigned char rc_bits_set[16] = { 0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4 };
unsigned rc_evaluate_operand(rc_operand_t* self, rc_eval_state_t* eval_state) {
#ifndef RC_DISABLE_LUA
rc_luapeek_t luapeek;
#endif /* RC_DISABLE_LUA */
unsigned value;
/* step 1: read memory */
switch (self->type) {
case RC_OPERAND_CONST:
return self->value.num;
case RC_OPERAND_FP:
/* This is handled by rc_evaluate_condition_value. */
return 0;
case RC_OPERAND_LUA:
value = 0;
#ifndef RC_DISABLE_LUA
if (eval_state->L != 0) {
lua_rawgeti(eval_state->L, LUA_REGISTRYINDEX, self->value.luafunc);
lua_pushcfunction(eval_state->L, rc_luapeek);
luapeek.peek = eval_state->peek;
luapeek.ud = eval_state->peek_userdata;
lua_pushlightuserdata(eval_state->L, &luapeek);
if (lua_pcall(eval_state->L, 2, 1, 0) == LUA_OK) {
if (lua_isboolean(eval_state->L, -1)) {
value = lua_toboolean(eval_state->L, -1);
}
else {
value = (unsigned)lua_tonumber(eval_state->L, -1);
}
}
lua_pop(eval_state->L, 1);
}
#endif /* RC_DISABLE_LUA */
break;
default:
value = rc_get_memref_value(self->value.memref, self->type, eval_state);
break;
}
/* step 2: mask off appropriate bits */
switch (self->size)
{
case RC_MEMSIZE_BIT_0:
value = (value >> 0) & 1;
break;
case RC_MEMSIZE_BIT_1:
value = (value >> 1) & 1;
break;
case RC_MEMSIZE_BIT_2:
value = (value >> 2) & 1;
break;
case RC_MEMSIZE_BIT_3:
value = (value >> 3) & 1;
break;
case RC_MEMSIZE_BIT_4:
value = (value >> 4) & 1;
break;
case RC_MEMSIZE_BIT_5:
value = (value >> 5) & 1;
break;
case RC_MEMSIZE_BIT_6:
value = (value >> 6) & 1;
break;
case RC_MEMSIZE_BIT_7:
value = (value >> 7) & 1;
break;
case RC_MEMSIZE_LOW:
value = value & 0x0f;
break;
case RC_MEMSIZE_HIGH:
value = (value >> 4) & 0x0f;
break;
case RC_MEMSIZE_BITCOUNT:
value = rc_bits_set[(value & 0x0F)]
+ rc_bits_set[((value >> 4) & 0x0F)];
break;
default:
break;
}
/* step 3: apply logic */
switch (self->type)
{
case RC_OPERAND_BCD:
switch (self->size)
{
case RC_MEMSIZE_8_BITS:
value = ((value >> 4) & 0x0f) * 10
+ ((value ) & 0x0f);
break;
case RC_MEMSIZE_16_BITS:
value = ((value >> 12) & 0x0f) * 1000
+ ((value >> 8) & 0x0f) * 100
+ ((value >> 4) & 0x0f) * 10
+ ((value ) & 0x0f);
break;
case RC_MEMSIZE_24_BITS:
value = ((value >> 20) & 0x0f) * 100000
+ ((value >> 16) & 0x0f) * 10000
+ ((value >> 12) & 0x0f) * 1000
+ ((value >> 8) & 0x0f) * 100
+ ((value >> 4) & 0x0f) * 10
+ ((value ) & 0x0f);
break;
case RC_MEMSIZE_32_BITS:
case RC_MEMSIZE_VARIABLE:
value = ((value >> 28) & 0x0f) * 10000000
+ ((value >> 24) & 0x0f) * 1000000
+ ((value >> 20) & 0x0f) * 100000
+ ((value >> 16) & 0x0f) * 10000
+ ((value >> 12) & 0x0f) * 1000
+ ((value >> 8) & 0x0f) * 100
+ ((value >> 4) & 0x0f) * 10
+ ((value ) & 0x0f);
break;
default:
break;
}
break;
case RC_OPERAND_INVERTED:
switch (self->size)
{
case RC_MEMSIZE_LOW:
case RC_MEMSIZE_HIGH:
value ^= 0x0f;
break;
case RC_MEMSIZE_8_BITS:
value ^= 0xff;
break;
case RC_MEMSIZE_16_BITS:
value ^= 0xffff;
break;
case RC_MEMSIZE_24_BITS:
value ^= 0xffffff;
break;
case RC_MEMSIZE_32_BITS:
case RC_MEMSIZE_VARIABLE:
value ^= 0xffffffff;
break;
default:
value ^= 0x01;
break;
}
break;
default:
break;
}
return value;
}

View File

@ -0,0 +1,60 @@
#ifndef RC_COMPAT_H
#define RC_COMPAT_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if defined(MINGW) || defined(__MINGW32__) || defined(__MINGW64__)
/* MinGW redefinitions */
#elif defined(_MSC_VER)
/* Visual Studio redefinitions */
#ifndef strcasecmp
#define strcasecmp _stricmp
#endif
#ifndef strncasecmp
#define strncasecmp _strnicmp
#endif
#ifndef strdup
#define strdup _strdup
#endif
#elif __STDC_VERSION__ < 199901L
/* C89 redefinitions */
#ifndef snprintf
extern int rc_snprintf(char* buffer, size_t size, const char* format, ...);
#define snprintf rc_snprintf
#endif
#ifndef strncasecmp
extern int rc_strncasecmp(const char* left, const char* right, size_t length);
#define strncasecmp rc_strncasecmp
#endif
#ifndef strcasecmp
extern int rc_strcasecmp(const char* left, const char* right);
#define strcasecmp rc_strcasecmp
#endif
#ifndef strdup
extern char* rc_strdup(const char* str);
#define strdup rc_strdup
#endif
#endif /* __STDC_VERSION__ < 199901L */
#ifdef __cplusplus
}
#endif
#endif /* RC_COMPAT_H */

View File

@ -0,0 +1,147 @@
#ifndef INTERNAL_H
#define INTERNAL_H
#include "rcheevos.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct rc_scratch_string {
char* value;
struct rc_scratch_string* left;
struct rc_scratch_string* right;
}
rc_scratch_string_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_lboard_t)
RC_ALLOW_ALIGN(rc_memref_t)
RC_ALLOW_ALIGN(rc_operand_t)
RC_ALLOW_ALIGN(rc_richpresence_t)
RC_ALLOW_ALIGN(rc_richpresence_display_t)
RC_ALLOW_ALIGN(rc_richpresence_display_part_t)
RC_ALLOW_ALIGN(rc_richpresence_lookup_t)
RC_ALLOW_ALIGN(rc_richpresence_lookup_item_t)
RC_ALLOW_ALIGN(rc_scratch_string_t)
RC_ALLOW_ALIGN(rc_trigger_t)
RC_ALLOW_ALIGN(rc_value_t)
RC_ALLOW_ALIGN(char)
#define RC_ALIGNOF(T) (sizeof(struct __align_ ## T) - sizeof(T))
#define RC_OFFSETOF(o, t) (int)((char*)&(o.t) - (char*)&(o))
#define RC_ALLOC(t, p) ((t*)rc_alloc((p)->buffer, &(p)->offset, sizeof(t), RC_ALIGNOF(t), &(p)->scratch, RC_OFFSETOF((p)->scratch.objs, __ ## t)))
#define RC_ALLOC_SCRATCH(t, p) ((t*)rc_alloc_scratch((p)->buffer, &(p)->offset, sizeof(t), RC_ALIGNOF(t), &(p)->scratch, RC_OFFSETOF((p)->scratch.objs, __ ## t)))
typedef struct rc_scratch_buffer {
struct rc_scratch_buffer* next;
int offset;
unsigned char buffer[512 - 16];
}
rc_scratch_buffer_t;
typedef struct {
rc_scratch_buffer_t buffer;
rc_scratch_string_t* strings;
struct objs {
rc_condition_t* __rc_condition_t;
rc_condset_t* __rc_condset_t;
rc_lboard_t* __rc_lboard_t;
rc_memref_t* __rc_memref_t;
rc_operand_t* __rc_operand_t;
rc_richpresence_t* __rc_richpresence_t;
rc_richpresence_display_t* __rc_richpresence_display_t;
rc_richpresence_display_part_t* __rc_richpresence_display_part_t;
rc_richpresence_lookup_t* __rc_richpresence_lookup_t;
rc_richpresence_lookup_item_t* __rc_richpresence_lookup_item_t;
rc_scratch_string_t __rc_scratch_string_t;
rc_trigger_t* __rc_trigger_t;
rc_value_t* __rc_value_t;
} objs;
}
rc_scratch_t;
typedef struct {
unsigned add_value; /* AddSource/SubSource */
int add_hits; /* AddHits */
unsigned add_address; /* AddAddress */
rc_peek_t peek;
void* peek_userdata;
lua_State* L;
unsigned measured_value; /* Measured */
char was_reset; /* ResetIf triggered */
char has_hits; /* one of more hit counts is non-zero */
char primed; /* true if all non-Trigger conditions are true */
char measured_from_hits; /* true if the measured_value came from a condition's hit count */
char was_cond_reset; /* ResetNextIf triggered */
}
rc_eval_state_t;
typedef struct {
int offset;
lua_State* L;
int funcs_ndx;
void* buffer;
rc_scratch_t scratch;
rc_memref_t** first_memref;
rc_value_t** variables;
unsigned measured_target;
char has_required_hits;
}
rc_parse_state_t;
void rc_init_parse_state(rc_parse_state_t* parse, void* buffer, lua_State* L, int funcs_ndx);
void rc_init_parse_state_memrefs(rc_parse_state_t* parse, rc_memref_t** memrefs);
void rc_init_parse_state_variables(rc_parse_state_t* parse, rc_value_t** variables);
void rc_destroy_parse_state(rc_parse_state_t* parse);
void* rc_alloc(void* pointer, int* offset, int size, int alignment, rc_scratch_t* scratch, int scratch_object_pointer_offset);
void* rc_alloc_scratch(void* pointer, int* offset, int size, int alignment, rc_scratch_t* scratch, int scratch_object_pointer_offset);
char* rc_alloc_str(rc_parse_state_t* parse, const char* text, int length);
rc_memref_t* rc_alloc_memref(rc_parse_state_t* parse, unsigned address, char size, char is_indirect);
int rc_parse_memref(const char** memaddr, char* size, unsigned* 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, unsigned value);
unsigned rc_get_memref_value(rc_memref_t* memref, int operand_type, rc_eval_state_t* eval_state);
unsigned rc_get_memref_value_value(rc_memref_value_t* memref, int operand_type);
void rc_parse_trigger_internal(rc_trigger_t* self, const char** memaddr, rc_parse_state_t* parse);
rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse, int is_value);
int rc_test_condset(rc_condset_t* self, rc_eval_state_t* eval_state);
void rc_reset_condset(rc_condset_t* self);
rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse, int is_indirect);
int rc_test_condition(rc_condition_t* self, rc_eval_state_t* eval_state);
int rc_evaluate_condition_value(rc_condition_t* self, rc_eval_state_t* eval_state);
int rc_parse_operand(rc_operand_t* self, const char** memaddr, int is_trigger, int is_indirect, rc_parse_state_t* parse);
unsigned rc_evaluate_operand(rc_operand_t* self, rc_eval_state_t* eval_state);
char rc_parse_operator(const char** memaddr);
void rc_parse_value_internal(rc_value_t* self, const char** memaddr, rc_parse_state_t* parse);
void rc_reset_value(rc_value_t* self);
rc_value_t* rc_alloc_helper_variable(const char* memaddr, int memaddr_len, rc_parse_state_t* parse);
void rc_update_variables(rc_value_t* variable, rc_peek_t peek, void* ud, lua_State* L);
void rc_parse_lboard_internal(rc_lboard_t* self, const char* memaddr, rc_parse_state_t* parse);
void rc_parse_richpresence_internal(rc_richpresence_t* self, const char* script, rc_parse_state_t* parse);
#ifdef __cplusplus
}
#endif
#endif /* INTERNAL_H */

View File

@ -0,0 +1,684 @@
#include "rc_internal.h"
#include "rc_compat.h"
#include <ctype.h>
/* special formats only used by rc_richpresence_display_part_t.display_type. must not overlap other RC_FORMAT values */
enum {
RC_FORMAT_STRING = 101,
RC_FORMAT_LOOKUP = 102,
RC_FORMAT_UNKNOWN_MACRO = 103
};
static rc_memref_value_t* rc_alloc_helper_variable_memref_value(const char* memaddr, int memaddr_len, rc_parse_state_t* parse) {
const char* end;
rc_value_t* variable;
unsigned address;
char size;
/* single memory reference lookups without a modifier flag can be handled without a variable */
end = memaddr;
if (rc_parse_memref(&end, &size, &address) == RC_OK) {
/* make sure the entire memaddr was consumed. if not, there's an operator and it's a comparison, not a memory reference */
if (end == &memaddr[memaddr_len]) {
/* just a memory reference, allocate it */
return &rc_alloc_memref(parse, address, size, 0)->value;
}
}
/* not a simple memory reference, need to create a variable */
variable = rc_alloc_helper_variable(memaddr, memaddr_len, parse);
if (!variable)
return NULL;
return &variable->value;
}
static const char* rc_parse_line(const char* line, const char** end) {
const char* nextline;
const char* endline;
/* get a single line */
nextline = line;
while (*nextline && *nextline != '\n')
++nextline;
/* find a trailing comment marker (//) */
endline = line;
while (endline < nextline && (endline[0] != '/' || endline[1] != '/' || (endline > line && endline[-1] == '\\')))
++endline;
/* remove trailing whitespace */
if (endline == nextline) {
if (endline > line && endline[-1] == '\r')
--endline;
} else {
while (endline > line && isspace(endline[-1]))
--endline;
}
/* end is pointing at the first character to ignore - makes subtraction for length easier */
*end = endline;
if (*nextline == '\n')
++nextline;
return nextline;
}
static rc_richpresence_display_t* rc_parse_richpresence_display_internal(const char* line, const char* endline, rc_parse_state_t* parse, rc_richpresence_lookup_t* first_lookup) {
rc_richpresence_display_t* self;
rc_richpresence_display_part_t* part;
rc_richpresence_display_part_t** next;
rc_richpresence_lookup_t* lookup;
const char* ptr;
const char* in;
char* out;
if (endline - line < 1) {
parse->offset = RC_MISSING_DISPLAY_STRING;
return 0;
}
{
self = RC_ALLOC(rc_richpresence_display_t, parse);
memset(self, 0, sizeof(rc_richpresence_display_t));
next = &self->display;
}
/* break the string up on macros: text @macro() moretext */
do {
ptr = line;
while (ptr < endline) {
if (*ptr == '@' && (ptr == line || ptr[-1] != '\\')) /* ignore escaped @s */
break;
++ptr;
}
if (ptr > line) {
part = RC_ALLOC(rc_richpresence_display_part_t, parse);
memset(part, 0, sizeof(rc_richpresence_display_part_t));
*next = part;
next = &part->next;
/* handle string part */
part->display_type = RC_FORMAT_STRING;
part->text = rc_alloc_str(parse, line, (int)(ptr - line));
if (part->text) {
/* remove backslashes used for escaping */
in = part->text;
while (*in && *in != '\\')
++in;
if (*in == '\\') {
out = (char*)in++;
while (*in) {
*out++ = *in++;
if (*in == '\\')
++in;
}
*out = '\0';
}
}
}
if (*ptr == '@') {
/* handle macro part */
line = ++ptr;
while (ptr < endline && *ptr != '(')
++ptr;
if (ptr == endline) {
parse->offset = RC_MISSING_VALUE;
return 0;
}
if (ptr > line) {
/* find the lookup and hook it up */
lookup = first_lookup;
while (lookup) {
if (strncmp(lookup->name, line, ptr - line) == 0 && lookup->name[ptr - line] == '\0') {
part = RC_ALLOC(rc_richpresence_display_part_t, parse);
*next = part;
next = &part->next;
part->text = lookup->name;
part->lookup = lookup;
part->display_type = lookup->format;
in = line;
line = ++ptr;
while (ptr < endline && *ptr != ')')
++ptr;
if (*ptr == ')') {
part->value = rc_alloc_helper_variable_memref_value(line, (int)(ptr-line), parse);
if (parse->offset < 0)
return 0;
++ptr;
}
else {
/* non-terminated macro, dump the macro and the remaining portion of the line */
--in; /* already skipped over @ */
part->display_type = RC_FORMAT_STRING;
part->text = rc_alloc_str(parse, in, (int)(ptr - in));
}
break;
}
lookup = lookup->next;
}
if (!lookup) {
part = RC_ALLOC(rc_richpresence_display_part_t, parse);
memset(part, 0, sizeof(rc_richpresence_display_part_t));
*next = part;
next = &part->next;
/* find the closing parenthesis */
while (ptr < endline && *ptr != ')')
++ptr;
if (*ptr == ')')
++ptr;
/* assert: the allocated string is going to be smaller than the memory used for the parameter of the macro */
part->display_type = RC_FORMAT_UNKNOWN_MACRO;
part->text = rc_alloc_str(parse, line, (int)(ptr - line));
}
}
}
line = ptr;
} while (line < endline);
*next = 0;
return self;
}
static int rc_richpresence_lookup_item_count(rc_richpresence_lookup_item_t* item)
{
if (item == NULL)
return 0;
return (rc_richpresence_lookup_item_count(item->left) + rc_richpresence_lookup_item_count(item->right) + 1);
}
static void rc_rebalance_richpresence_lookup_get_items(rc_richpresence_lookup_item_t* root,
rc_richpresence_lookup_item_t** items, int* index)
{
if (root->left != NULL)
rc_rebalance_richpresence_lookup_get_items(root->left, items, index);
items[*index] = root;
++(*index);
if (root->right != NULL)
rc_rebalance_richpresence_lookup_get_items(root->right, items, index);
}
static void rc_rebalance_richpresence_lookup_rebuild(rc_richpresence_lookup_item_t** root,
rc_richpresence_lookup_item_t** items, int first, int last)
{
int mid = (first + last) / 2;
rc_richpresence_lookup_item_t* item = items[mid];
*root = item;
if (mid == first)
item->left = NULL;
else
rc_rebalance_richpresence_lookup_rebuild(&item->left, items, first, mid - 1);
if (mid == last)
item->right = NULL;
else
rc_rebalance_richpresence_lookup_rebuild(&item->right, items, mid + 1, last);
}
static void rc_rebalance_richpresence_lookup(rc_richpresence_lookup_item_t** root, rc_parse_state_t* parse)
{
rc_richpresence_lookup_item_t** items;
rc_scratch_buffer_t* buffer;
const int alignment = sizeof(rc_richpresence_lookup_item_t*);
int index;
int size;
/* don't bother rebalancing one or two items */
int count = rc_richpresence_lookup_item_count(*root);
if (count < 3)
return;
/* allocate space for the flattened list - prefer scratch memory if available */
size = count * sizeof(rc_richpresence_lookup_item_t*);
buffer = &parse->scratch.buffer;
do {
const int aligned_offset = (buffer->offset + alignment - 1) & ~(alignment - 1);
const int remaining = sizeof(buffer->buffer) - aligned_offset;
if (remaining >= size) {
items = (rc_richpresence_lookup_item_t**)&buffer->buffer[aligned_offset];
break;
}
buffer = buffer->next;
if (buffer == NULL) {
/* could not find large enough block of scratch memory; allocate. if allocation fails,
* we can still use the unbalanced tree, so just bail out */
items = (rc_richpresence_lookup_item_t**)malloc(size);
if (items == NULL)
return;
break;
}
} while (1);
/* flatten the list */
index = 0;
rc_rebalance_richpresence_lookup_get_items(*root, items, &index);
/* and rebuild it as a balanced tree */
rc_rebalance_richpresence_lookup_rebuild(root, items, 0, count - 1);
if (buffer == NULL)
free(items);
}
static void rc_insert_richpresence_lookup_item(rc_richpresence_lookup_t* lookup,
unsigned first, unsigned last, const char* label, int label_len, rc_parse_state_t* parse)
{
rc_richpresence_lookup_item_t** next;
rc_richpresence_lookup_item_t* item;
next = &lookup->root;
while ((item = *next) != NULL) {
if (first > item->last) {
if (first == item->last + 1 &&
strncmp(label, item->label, label_len) == 0 && item->label[label_len] == '\0') {
item->last = last;
return;
}
next = &item->right;
}
else if (last < item->first) {
if (last == item->first - 1 &&
strncmp(label, item->label, label_len) == 0 && item->label[label_len] == '\0') {
item->first = first;
return;
}
next = &item->left;
}
else {
parse->offset = RC_DUPLICATED_VALUE;
return;
}
}
item = RC_ALLOC_SCRATCH(rc_richpresence_lookup_item_t, parse);
item->first = first;
item->last = last;
item->label = rc_alloc_str(parse, label, label_len);
item->left = item->right = NULL;
*next = item;
}
static const char* rc_parse_richpresence_lookup(rc_richpresence_lookup_t* lookup, const char* nextline, rc_parse_state_t* parse)
{
const char* line;
const char* endline;
const char* label;
char* endptr = 0;
unsigned first, last;
int base;
do
{
line = nextline;
nextline = rc_parse_line(line, &endline);
if (endline - line < 2) {
/* ignore full line comments inside a lookup */
if (line[0] == '/' && line[1] == '/')
continue;
/* empty line indicates end of lookup */
if (lookup->root)
rc_rebalance_richpresence_lookup(&lookup->root, parse);
break;
}
/* "*=XXX" specifies default label if lookup does not provide a mapping for the value */
if (line[0] == '*' && line[1] == '=') {
line += 2;
lookup->default_label = rc_alloc_str(parse, line, (int)(endline - line));
continue;
}
label = line;
while (label < endline && *label != '=')
++label;
if (label == endline) {
parse->offset = RC_MISSING_VALUE;
break;
}
++label;
do {
/* get the value for the mapping */
if (line[0] == '0' && line[1] == 'x') {
line += 2;
base = 16;
} else {
base = 10;
}
first = strtoul(line, &endptr, base);
/* check for a range */
if (*endptr != '-') {
/* no range, just set last to first */
last = first;
}
else {
/* range, get last value */
line = endptr + 1;
if (line[0] == '0' && line[1] == 'x') {
line += 2;
base = 16;
} else {
base = 10;
}
last = strtoul(line, &endptr, base);
}
/* ignore spaces after the number - was previously ignored as string was split on equals */
while (*endptr == ' ')
++endptr;
/* if we've found the equal sign, this is the last item */
if (*endptr == '=') {
rc_insert_richpresence_lookup_item(lookup, first, last, label, (int)(endline - label), parse);
break;
}
/* otherwise, if it's not a comma, it's an error */
if (*endptr != ',') {
parse->offset = RC_INVALID_CONST_OPERAND;
break;
}
/* insert the current item and continue scanning the next one */
rc_insert_richpresence_lookup_item(lookup, first, last, label, (int)(endline - label), parse);
line = endptr + 1;
} while (line < endline);
} while (parse->offset > 0);
return nextline;
}
void rc_parse_richpresence_internal(rc_richpresence_t* self, const char* script, rc_parse_state_t* parse) {
rc_richpresence_display_t** nextdisplay;
rc_richpresence_lookup_t* firstlookup = NULL;
rc_richpresence_lookup_t** nextlookup = &firstlookup;
rc_richpresence_lookup_t* lookup;
rc_trigger_t* trigger;
char format[64];
const char* display = 0;
const char* line;
const char* nextline;
const char* endline;
const char* ptr;
int hasdisplay = 0;
int chars;
/* first pass: process macro initializers */
line = script;
while (*line)
{
nextline = rc_parse_line(line, &endline);
if (strncmp(line, "Lookup:", 7) == 0) {
line += 7;
lookup = RC_ALLOC_SCRATCH(rc_richpresence_lookup_t, parse);
lookup->name = rc_alloc_str(parse, line, (int)(endline - line));
lookup->format = RC_FORMAT_LOOKUP;
lookup->root = NULL;
lookup->default_label = "";
*nextlookup = lookup;
nextlookup = &lookup->next;
nextline = rc_parse_richpresence_lookup(lookup, nextline, parse);
if (parse->offset < 0)
return;
} else if (strncmp(line, "Format:", 7) == 0) {
line += 7;
lookup = RC_ALLOC_SCRATCH(rc_richpresence_lookup_t, parse);
lookup->name = rc_alloc_str(parse, line, (int)(endline - line));
lookup->root = NULL;
lookup->default_label = "";
*nextlookup = lookup;
nextlookup = &lookup->next;
line = nextline;
nextline = rc_parse_line(line, &endline);
if (parse->buffer && strncmp(line, "FormatType=", 11) == 0) {
line += 11;
chars = (int)(endline - line);
if (chars > 63)
chars = 63;
memcpy(format, line, chars);
format[chars] = '\0';
lookup->format = rc_parse_format(format);
} else {
lookup->format = RC_FORMAT_VALUE;
}
} else if (strncmp(line, "Display:", 8) == 0) {
display = nextline;
do {
line = nextline;
nextline = rc_parse_line(line, &endline);
} while (*line == '?');
}
line = nextline;
}
*nextlookup = 0;
self->first_lookup = firstlookup;
nextdisplay = &self->first_display;
/* second pass, process display string*/
if (display) {
line = display;
nextline = rc_parse_line(line, &endline);
while (*line == '?') {
/* conditional display: ?trigger?string */
ptr = ++line;
while (ptr < endline && *ptr != '?')
++ptr;
if (ptr < endline) {
*nextdisplay = rc_parse_richpresence_display_internal(ptr + 1, endline, parse, firstlookup);
if (parse->offset < 0)
return;
trigger = &((*nextdisplay)->trigger);
rc_parse_trigger_internal(trigger, &line, parse);
trigger->memrefs = 0;
if (parse->offset < 0)
return;
if (parse->buffer)
nextdisplay = &((*nextdisplay)->next);
}
line = nextline;
nextline = rc_parse_line(line, &endline);
}
/* non-conditional display: string */
*nextdisplay = rc_parse_richpresence_display_internal(line, endline, parse, firstlookup);
if (*nextdisplay) {
hasdisplay = 1;
nextdisplay = &((*nextdisplay)->next);
}
}
/* finalize */
*nextdisplay = 0;
if (!hasdisplay && parse->offset > 0) {
parse->offset = RC_MISSING_DISPLAY_STRING;
}
}
int rc_richpresence_size(const char* script) {
rc_richpresence_t* self;
rc_parse_state_t parse;
rc_memref_t* first_memref;
rc_value_t* variables;
rc_init_parse_state(&parse, 0, 0, 0);
rc_init_parse_state_memrefs(&parse, &first_memref);
rc_init_parse_state_variables(&parse, &variables);
self = RC_ALLOC(rc_richpresence_t, &parse);
rc_parse_richpresence_internal(self, script, &parse);
rc_destroy_parse_state(&parse);
return parse.offset;
}
rc_richpresence_t* rc_parse_richpresence(void* buffer, const char* script, lua_State* L, int funcs_ndx) {
rc_richpresence_t* self;
rc_parse_state_t parse;
rc_init_parse_state(&parse, buffer, L, funcs_ndx);
self = RC_ALLOC(rc_richpresence_t, &parse);
rc_init_parse_state_memrefs(&parse, &self->memrefs);
rc_init_parse_state_variables(&parse, &self->variables);
rc_parse_richpresence_internal(self, script, &parse);
rc_destroy_parse_state(&parse);
return parse.offset >= 0 ? self : 0;
}
void rc_update_richpresence(rc_richpresence_t* richpresence, rc_peek_t peek, void* peek_ud, lua_State* L) {
rc_richpresence_display_t* display;
rc_update_memref_values(richpresence->memrefs, peek, peek_ud);
rc_update_variables(richpresence->variables, peek, peek_ud, L);
for (display = richpresence->first_display; display; display = display->next) {
if (display->trigger.has_required_hits)
rc_test_trigger(&display->trigger, peek, peek_ud, L);
}
}
static int rc_evaluate_richpresence_display(rc_richpresence_display_part_t* part, char* buffer, unsigned buffersize)
{
rc_richpresence_lookup_item_t* item;
char tmp[256];
char* ptr = buffer;
const char* text;
size_t chars;
unsigned value;
*ptr = '\0';
while (part) {
switch (part->display_type) {
case RC_FORMAT_STRING:
text = part->text;
chars = strlen(text);
break;
case RC_FORMAT_LOOKUP:
value = part->value->value;
text = part->lookup->default_label;
item = part->lookup->root;
while (item) {
if (value > item->last) {
item = item->right;
}
else if (value < item->first) {
item = item->left;
}
else {
text = item->label;
break;
}
}
chars = strlen(text);
break;
case RC_FORMAT_UNKNOWN_MACRO:
chars = snprintf(tmp, sizeof(tmp), "[Unknown macro]%s", part->text);
text = tmp;
break;
default:
value = part->value->value;
chars = rc_format_value(tmp, sizeof(tmp), value, part->display_type);
text = tmp;
break;
}
if (chars > 0 && buffersize > 0) {
if ((unsigned)chars >= buffersize) {
/* prevent write past end of buffer */
memcpy(ptr, text, buffersize - 1);
ptr[buffersize - 1] = '\0';
buffersize = 0;
}
else {
memcpy(ptr, text, chars);
ptr[chars] = '\0';
buffersize -= (unsigned)chars;
}
}
ptr += chars;
part = part->next;
}
return (int)(ptr - buffer);
}
int rc_get_richpresence_display_string(rc_richpresence_t* richpresence, char* buffer, unsigned buffersize, rc_peek_t peek, void* peek_ud, lua_State* L) {
rc_richpresence_display_t* display;
for (display = richpresence->first_display; display; display = display->next) {
/* if we've reached the end of the condition list, process it */
if (!display->next)
return rc_evaluate_richpresence_display(display->display, buffer, buffersize);
/* triggers with required hits will be updated in rc_update_richpresence */
if (!display->trigger.has_required_hits)
rc_test_trigger(&display->trigger, peek, peek_ud, L);
/* if we've found a valid condition, process it */
if (display->trigger.state == RC_TRIGGER_STATE_TRIGGERED)
return rc_evaluate_richpresence_display(display->display, buffer, buffersize);
}
buffer[0] = '\0';
return 0;
}
int rc_evaluate_richpresence(rc_richpresence_t* richpresence, char* buffer, unsigned buffersize, rc_peek_t peek, void* peek_ud, lua_State* L) {
rc_update_richpresence(richpresence, peek, peek_ud, L);
return rc_get_richpresence_display_string(richpresence, buffer, buffersize, peek, peek_ud, L);
}

View File

@ -0,0 +1,691 @@
#include "rc_internal.h"
#include "../rhash/md5.h"
#include <stdlib.h>
#include <string.h>
#define RC_RICHPRESENCE_DISPLAY_BUFFER_SIZE 256
void rc_runtime_init(rc_runtime_t* self) {
memset(self, 0, sizeof(rc_runtime_t));
self->next_memref = &self->memrefs;
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;
}
static void rc_runtime_deactivate_lboard_by_index(rc_runtime_t* self, unsigned index) {
if (self->lboards[index].owns_memrefs) {
/* if the lboard has one or more memrefs in its buffer, we can't free the buffer.
* just null out the lboard so the runtime processor will skip it
*/
rc_reset_lboard(self->lboards[index].lboard);
self->lboards[index].lboard = NULL;
}
else {
/* lboard doesn't own any memrefs, go ahead and free it, then replace it with the last lboard */
free(self->lboards[index].buffer);
if (--self->lboard_count > index)
memcpy(&self->lboards[index], &self->lboards[self->lboard_count], sizeof(rc_runtime_lboard_t));
}
}
void rc_runtime_deactivate_lboard(rc_runtime_t* self, unsigned id) {
unsigned i;
for (i = 0; i < self->lboard_count; ++i) {
if (self->lboards[i].id == id && self->lboards[i].lboard != NULL)
rc_runtime_deactivate_lboard_by_index(self, i);
}
}
int rc_runtime_activate_lboard(rc_runtime_t* self, unsigned id, const char* memaddr, lua_State* L, int funcs_idx) {
void* lboard_buffer;
unsigned char md5[16];
rc_lboard_t* lboard;
rc_parse_state_t parse;
int size;
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 */
self->lboards[self->lboard_count].id = id;
self->lboards[self->lboard_count].value = 0;
self->lboards[self->lboard_count].lboard = lboard;
self->lboards[self->lboard_count].buffer = lboard_buffer;
self->lboards[self->lboard_count].invalid_memref = NULL;
memcpy(self->lboards[self->lboard_count].md5, md5, 16);
self->lboards[self->lboard_count].owns_memrefs = rc_runtime_allocated_memrefs(self);
++self->lboard_count;
/* reset it, and return it */
lboard->memrefs = NULL;
rc_reset_lboard(lboard);
return RC_OK;
}
rc_lboard_t* rc_runtime_get_lboard(const rc_runtime_t* self, unsigned id)
{
unsigned i;
for (i = 0; i < self->lboard_count; ++i) {
if (self->lboards[i].id == id && self->lboards[i].lboard != NULL)
return self->lboards[i].lboard;
}
return NULL;
}
int rc_runtime_activate_richpresence(rc_runtime_t* self, const char* script, lua_State* L, int funcs_idx) {
rc_richpresence_t* richpresence;
rc_runtime_richpresence_t* previous;
rc_richpresence_display_t* display;
rc_parse_state_t parse;
int size;
if (script == NULL)
return RC_MISSING_DISPLAY_STRING;
size = rc_richpresence_size(script);
if (size < 0)
return size;
previous = self->richpresence;
if (previous) {
if (!previous->owns_memrefs) {
free(previous->buffer);
previous = previous->previous;
}
}
self->richpresence = (rc_runtime_richpresence_t*)malloc(sizeof(rc_runtime_richpresence_t));
if (!self->richpresence)
return RC_OUT_OF_MEMORY;
self->richpresence->previous = previous;
self->richpresence->owns_memrefs = 0;
self->richpresence->buffer = malloc(size);
if (!self->richpresence->buffer)
return RC_OUT_OF_MEMORY;
rc_init_parse_state(&parse, self->richpresence->buffer, L, funcs_idx);
self->richpresence->richpresence = richpresence = RC_ALLOC(rc_richpresence_t, &parse);
parse.first_memref = &self->memrefs;
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 */
display = richpresence->first_display;
while (display != NULL) {
rc_reset_trigger(&display->trigger);
display = display->next;
}
}
return RC_OK;
}
int rc_runtime_get_richpresence(const rc_runtime_t* self, char* buffer, unsigned buffersize, rc_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_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 trigger_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;
}
trigger_state = trigger->state;
switch (rc_evaluate_trigger(trigger, peek, ud, L))
{
case RC_TRIGGER_STATE_RESET:
runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_RESET;
runtime_event.id = self->triggers[i].id;
event_handler(&runtime_event);
break;
case RC_TRIGGER_STATE_TRIGGERED:
runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_TRIGGERED;
runtime_event.id = self->triggers[i].id;
event_handler(&runtime_event);
break;
case RC_TRIGGER_STATE_PAUSED:
if (trigger_state != RC_TRIGGER_STATE_PAUSED) {
runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_PAUSED;
runtime_event.id = self->triggers[i].id;
event_handler(&runtime_event);
}
break;
case RC_TRIGGER_STATE_PRIMED:
if (trigger_state != RC_TRIGGER_STATE_PRIMED) {
runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_PRIMED;
runtime_event.id = self->triggers[i].id;
event_handler(&runtime_event);
}
break;
case RC_TRIGGER_STATE_ACTIVE:
if (trigger_state != RC_TRIGGER_STATE_ACTIVE) {
runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_ACTIVATED;
runtime_event.id = self->triggers[i].id;
event_handler(&runtime_event);
}
break;
}
}
for (i = 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) {
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 (cond->operand1.value.memref == memref || 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;
}
void rc_runtime_invalidate_address(rc_runtime_t* self, unsigned address) {
unsigned i;
rc_memref_t* memref;
rc_memref_t** last_memref;
if (!self->memrefs)
return;
/* 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 = &self->memrefs;
memref = *last_memref;
do {
if (memref->address == address && !memref->value.is_indirect) {
*last_memref = memref->next;
break;
}
last_memref = &memref->next;
memref = *last_memref;
} while (memref);
/* if the address is only used indirectly, don't disable anything dependent on it */
if (!memref)
return;
/* 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;
}
}
}
}

View File

@ -0,0 +1,533 @@
#include "rc_internal.h"
#include "../rhash/md5.h"
#include <string.h>
#define RC_RUNTIME_MARKER 0x0A504152 /* RAP\n */
#define RC_RUNTIME_CHUNK_MEMREFS 0x4645524D /* MREF */
#define RC_RUNTIME_CHUNK_ACHIEVEMENT 0x56484341 /* ACHV */
#define RC_RUNTIME_CHUNK_DONE 0x454E4F44 /* DONE */
typedef struct rc_runtime_progress_t {
rc_runtime_t* runtime;
int offset;
unsigned char* buffer;
int chunk_size_offset;
lua_State* L;
} rc_runtime_progress_t;
#define RC_TRIGGER_STATE_UNUPDATED 0x7F
#define RC_MEMREF_FLAG_CHANGED_THIS_FRAME 0x00010000
#define RC_COND_FLAG_IS_TRUE 0x00000001
#define RC_COND_FLAG_OPERAND1_IS_INDIRECT_MEMREF 0x00010000
#define RC_COND_FLAG_OPERAND1_MEMREF_CHANGED_THIS_FRAME 0x00020000
#define RC_COND_FLAG_OPERAND2_IS_INDIRECT_MEMREF 0x00100000
#define RC_COND_FLAG_OPERAND2_MEMREF_CHANGED_THIS_FRAME 0x00200000
static void rc_runtime_progress_write_uint(rc_runtime_progress_t* progress, unsigned value)
{
if (progress->buffer) {
progress->buffer[progress->offset + 0] = value & 0xFF; value >>= 8;
progress->buffer[progress->offset + 1] = value & 0xFF; value >>= 8;
progress->buffer[progress->offset + 2] = value & 0xFF; value >>= 8;
progress->buffer[progress->offset + 3] = value & 0xFF;
}
progress->offset += 4;
}
static unsigned rc_runtime_progress_read_uint(rc_runtime_progress_t* progress)
{
unsigned value = progress->buffer[progress->offset + 0] |
(progress->buffer[progress->offset + 1] << 8) |
(progress->buffer[progress->offset + 2] << 16) |
(progress->buffer[progress->offset + 3] << 24);
progress->offset += 4;
return value;
}
static void rc_runtime_progress_write_md5(rc_runtime_progress_t* progress, unsigned char* md5)
{
if (progress->buffer)
memcpy(&progress->buffer[progress->offset], md5, 16);
progress->offset += 16;
}
static int rc_runtime_progress_match_md5(rc_runtime_progress_t* progress, unsigned char* md5)
{
int result = 0;
if (progress->buffer)
result = (memcmp(&progress->buffer[progress->offset], md5, 16) == 0);
progress->offset += 16;
return result;
}
static void rc_runtime_progress_start_chunk(rc_runtime_progress_t* progress, unsigned chunk_id)
{
rc_runtime_progress_write_uint(progress, chunk_id);
progress->chunk_size_offset = progress->offset;
progress->offset += 4;
}
static void rc_runtime_progress_end_chunk(rc_runtime_progress_t* progress)
{
unsigned length;
int offset;
progress->offset = (progress->offset + 3) & ~0x03; /* align to 4 byte boundary */
if (progress->buffer) {
/* ignore chunk size field when calculating chunk size */
length = (unsigned)(progress->offset - progress->chunk_size_offset - 4);
/* temporarily update the write pointer to write the chunk size field */
offset = progress->offset;
progress->offset = progress->chunk_size_offset;
rc_runtime_progress_write_uint(progress, length);
progress->offset = offset;
}
}
static void rc_runtime_progress_init(rc_runtime_progress_t* progress, rc_runtime_t* runtime, lua_State* L)
{
memset(progress, 0, sizeof(rc_runtime_progress_t));
progress->runtime = runtime;
progress->L = L;
}
static int rc_runtime_progress_write_memrefs(rc_runtime_progress_t* progress)
{
rc_memref_t* memref = progress->runtime->memrefs;
unsigned int flags = 0;
rc_runtime_progress_start_chunk(progress, RC_RUNTIME_CHUNK_MEMREFS);
if (!progress->buffer) {
while (memref) {
progress->offset += 16;
memref = memref->next;
}
}
else {
while (memref) {
flags = memref->value.size;
if (memref->value.changed)
flags |= RC_MEMREF_FLAG_CHANGED_THIS_FRAME;
rc_runtime_progress_write_uint(progress, memref->address);
rc_runtime_progress_write_uint(progress, flags);
rc_runtime_progress_write_uint(progress, memref->value.value);
rc_runtime_progress_write_uint(progress, memref->value.prior);
memref = memref->next;
}
}
rc_runtime_progress_end_chunk(progress);
return RC_OK;
}
static int rc_runtime_progress_read_memrefs(rc_runtime_progress_t* progress)
{
unsigned entries;
unsigned address, flags, value, prior;
char size;
rc_memref_t* memref;
rc_memref_t* first_unmatched_memref = progress->runtime->memrefs;
/* re-read the chunk size to determine how many memrefs are present */
progress->offset -= 4;
entries = rc_runtime_progress_read_uint(progress) / 16;
while (entries != 0) {
address = rc_runtime_progress_read_uint(progress);
flags = rc_runtime_progress_read_uint(progress);
value = rc_runtime_progress_read_uint(progress);
prior = rc_runtime_progress_read_uint(progress);
size = flags & 0xFF;
memref = first_unmatched_memref;
while (memref) {
if (memref->address == address && memref->value.size == size) {
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)
first_unmatched_memref = memref->next;
break;
}
memref = memref->next;
}
--entries;
}
return RC_OK;
}
static int rc_runtime_progress_is_indirect_memref(rc_operand_t* oper)
{
switch (oper->type)
{
case RC_OPERAND_CONST:
case RC_OPERAND_FP:
case RC_OPERAND_LUA:
return 0;
default:
return oper->value.memref->value.is_indirect;
}
}
static int rc_runtime_progress_write_condset(rc_runtime_progress_t* progress, rc_condset_t* condset)
{
rc_condition_t* cond;
unsigned flags;
rc_runtime_progress_write_uint(progress, condset->is_paused);
cond = condset->conditions;
while (cond) {
flags = 0;
if (cond->is_true)
flags |= RC_COND_FLAG_IS_TRUE;
if (rc_runtime_progress_is_indirect_memref(&cond->operand1)) {
flags |= RC_COND_FLAG_OPERAND1_IS_INDIRECT_MEMREF;
if (cond->operand1.value.memref->value.changed)
flags |= RC_COND_FLAG_OPERAND1_MEMREF_CHANGED_THIS_FRAME;
}
if (rc_runtime_progress_is_indirect_memref(&cond->operand2)) {
flags |= RC_COND_FLAG_OPERAND2_IS_INDIRECT_MEMREF;
if (cond->operand2.value.memref->value.changed)
flags |= RC_COND_FLAG_OPERAND2_MEMREF_CHANGED_THIS_FRAME;
}
rc_runtime_progress_write_uint(progress, cond->current_hits);
rc_runtime_progress_write_uint(progress, flags);
if (flags & RC_COND_FLAG_OPERAND1_IS_INDIRECT_MEMREF) {
rc_runtime_progress_write_uint(progress, cond->operand1.value.memref->value.value);
rc_runtime_progress_write_uint(progress, cond->operand1.value.memref->value.prior);
}
if (flags & RC_COND_FLAG_OPERAND2_IS_INDIRECT_MEMREF) {
rc_runtime_progress_write_uint(progress, cond->operand2.value.memref->value.value);
rc_runtime_progress_write_uint(progress, cond->operand2.value.memref->value.prior);
}
cond = cond->next;
}
return RC_OK;
}
static int rc_runtime_progress_read_condset(rc_runtime_progress_t* progress, rc_condset_t* condset)
{
rc_condition_t* cond;
unsigned flags;
condset->is_paused = rc_runtime_progress_read_uint(progress);
cond = condset->conditions;
while (cond) {
cond->current_hits = rc_runtime_progress_read_uint(progress);
flags = rc_runtime_progress_read_uint(progress);
cond->is_true = (flags & RC_COND_FLAG_IS_TRUE) ? 1 : 0;
if (flags & RC_COND_FLAG_OPERAND1_IS_INDIRECT_MEMREF) {
cond->operand1.value.memref->value.value = rc_runtime_progress_read_uint(progress);
cond->operand1.value.memref->value.prior = rc_runtime_progress_read_uint(progress);
cond->operand1.value.memref->value.changed = (flags & RC_COND_FLAG_OPERAND1_MEMREF_CHANGED_THIS_FRAME) ? 1 : 0;
}
if (flags & RC_COND_FLAG_OPERAND2_IS_INDIRECT_MEMREF) {
cond->operand2.value.memref->value.value = rc_runtime_progress_read_uint(progress);
cond->operand2.value.memref->value.prior = rc_runtime_progress_read_uint(progress);
cond->operand2.value.memref->value.changed = (flags & RC_COND_FLAG_OPERAND2_MEMREF_CHANGED_THIS_FRAME) ? 1 : 0;
}
cond = cond->next;
}
return RC_OK;
}
static int rc_runtime_progress_write_trigger(rc_runtime_progress_t* progress, rc_trigger_t* trigger)
{
rc_condset_t* condset;
int result;
rc_runtime_progress_write_uint(progress, trigger->state);
rc_runtime_progress_write_uint(progress, trigger->measured_value);
if (trigger->requirement) {
result = rc_runtime_progress_write_condset(progress, trigger->requirement);
if (result != RC_OK)
return result;
}
condset = trigger->alternative;
while (condset) {
result = rc_runtime_progress_write_condset(progress, condset);
if (result != RC_OK)
return result;
condset = condset->next;
}
return RC_OK;
}
static int rc_runtime_progress_read_trigger(rc_runtime_progress_t* progress, rc_trigger_t* trigger)
{
rc_condset_t* condset;
int result;
trigger->state = rc_runtime_progress_read_uint(progress);
trigger->measured_value = rc_runtime_progress_read_uint(progress);
if (trigger->requirement) {
result = rc_runtime_progress_read_condset(progress, trigger->requirement);
if (result != RC_OK)
return result;
}
condset = trigger->alternative;
while (condset) {
result = rc_runtime_progress_read_condset(progress, condset);
if (result != RC_OK)
return result;
condset = condset->next;
}
return RC_OK;
}
static int rc_runtime_progress_write_achievements(rc_runtime_progress_t* progress)
{
unsigned i;
int offset = 0;
int result;
for (i = 0; i < progress->runtime->trigger_count; ++i) {
rc_runtime_trigger_t* runtime_trigger = &progress->runtime->triggers[i];
if (!runtime_trigger->trigger)
continue;
switch (runtime_trigger->trigger->state)
{
case RC_TRIGGER_STATE_DISABLED:
case RC_TRIGGER_STATE_INACTIVE:
case RC_TRIGGER_STATE_TRIGGERED:
/* don't store state for inactive or triggered achievements */
continue;
default:
break;
}
if (!progress->buffer) {
if (runtime_trigger->serialized_size) {
progress->offset += runtime_trigger->serialized_size;
continue;
}
offset = progress->offset;
}
rc_runtime_progress_start_chunk(progress, RC_RUNTIME_CHUNK_ACHIEVEMENT);
rc_runtime_progress_write_uint(progress, runtime_trigger->id);
rc_runtime_progress_write_md5(progress, runtime_trigger->md5);
result = rc_runtime_progress_write_trigger(progress, runtime_trigger->trigger);
if (result != RC_OK)
return result;
rc_runtime_progress_end_chunk(progress);
if (!progress->buffer)
runtime_trigger->serialized_size = progress->offset - offset;
}
return RC_OK;
}
static int rc_runtime_progress_read_achievement(rc_runtime_progress_t* progress)
{
unsigned id = rc_runtime_progress_read_uint(progress);
unsigned i;
for (i = 0; i < progress->runtime->trigger_count; ++i) {
rc_runtime_trigger_t* runtime_trigger = &progress->runtime->triggers[i];
if (runtime_trigger->id == id && runtime_trigger->trigger != NULL) {
/* ignore triggered and waiting achievements */
if (runtime_trigger->trigger->state == RC_TRIGGER_STATE_UNUPDATED) {
/* only update state if definition hasn't changed (md5 matches) */
if (rc_runtime_progress_match_md5(progress, runtime_trigger->md5))
return rc_runtime_progress_read_trigger(progress, runtime_trigger->trigger);
break;
}
}
}
return RC_OK;
}
static int rc_runtime_progress_serialize_internal(rc_runtime_progress_t* progress)
{
md5_state_t state;
unsigned char md5[16];
int result;
rc_runtime_progress_write_uint(progress, RC_RUNTIME_MARKER);
if ((result = rc_runtime_progress_write_memrefs(progress)) != RC_OK)
return result;
if ((result = rc_runtime_progress_write_achievements(progress)) != RC_OK)
return result;
rc_runtime_progress_write_uint(progress, RC_RUNTIME_CHUNK_DONE);
rc_runtime_progress_write_uint(progress, 16);
if (progress->buffer) {
md5_init(&state);
md5_append(&state, progress->buffer, progress->offset);
md5_finish(&state, md5);
}
rc_runtime_progress_write_md5(progress, md5);
return RC_OK;
}
int rc_runtime_progress_size(const rc_runtime_t* runtime, lua_State* L)
{
rc_runtime_progress_t progress;
int result;
rc_runtime_progress_init(&progress, (rc_runtime_t*)runtime, L);
result = rc_runtime_progress_serialize_internal(&progress);
if (result != RC_OK)
return result;
return progress.offset;
}
int rc_runtime_serialize_progress(void* buffer, const rc_runtime_t* runtime, lua_State* L)
{
rc_runtime_progress_t progress;
rc_runtime_progress_init(&progress, (rc_runtime_t*)runtime, L);
progress.buffer = (unsigned char*)buffer;
return rc_runtime_progress_serialize_internal(&progress);
}
int rc_runtime_deserialize_progress(rc_runtime_t* runtime, const unsigned char* serialized, lua_State* L)
{
rc_runtime_progress_t progress;
md5_state_t state;
unsigned char md5[16];
unsigned chunk_id;
unsigned chunk_size;
unsigned next_chunk_offset;
unsigned i;
int result = RC_OK;
rc_runtime_progress_init(&progress, runtime, L);
progress.buffer = (unsigned char*)serialized;
if (rc_runtime_progress_read_uint(&progress) != RC_RUNTIME_MARKER) {
rc_runtime_reset(runtime);
return RC_INVALID_STATE;
}
for (i = 0; i < runtime->trigger_count; ++i) {
rc_runtime_trigger_t* runtime_trigger = &runtime->triggers[i];
if (runtime_trigger->trigger) {
switch (runtime_trigger->trigger->state)
{
case RC_TRIGGER_STATE_DISABLED:
case RC_TRIGGER_STATE_INACTIVE:
case RC_TRIGGER_STATE_TRIGGERED:
/* don't update state for inactive or triggered achievements */
break;
default:
/* mark active achievements as unupdated. anything that's still unupdated
* after deserializing the progress will be reset to waiting */
runtime_trigger->trigger->state = RC_TRIGGER_STATE_UNUPDATED;
break;
}
}
}
do {
chunk_id = rc_runtime_progress_read_uint(&progress);
chunk_size = rc_runtime_progress_read_uint(&progress);
next_chunk_offset = progress.offset + chunk_size;
switch (chunk_id)
{
case RC_RUNTIME_CHUNK_MEMREFS:
result = rc_runtime_progress_read_memrefs(&progress);
break;
case RC_RUNTIME_CHUNK_ACHIEVEMENT:
result = rc_runtime_progress_read_achievement(&progress);
break;
case RC_RUNTIME_CHUNK_DONE:
md5_init(&state);
md5_append(&state, progress.buffer, progress.offset);
md5_finish(&state, md5);
if (!rc_runtime_progress_match_md5(&progress, md5))
result = RC_INVALID_STATE;
break;
default:
if (chunk_size & 0xFFFF0000)
result = RC_INVALID_STATE; /* assume unknown chunk > 64KB is invalid */
break;
}
progress.offset = next_chunk_offset;
} while (result == RC_OK && chunk_id != RC_RUNTIME_CHUNK_DONE);
if (result != RC_OK) {
rc_runtime_reset(runtime);
}
else {
for (i = 0; i < runtime->trigger_count; ++i) {
rc_trigger_t* trigger = runtime->triggers[i].trigger;
if (trigger && trigger->state == RC_TRIGGER_STATE_UNUPDATED)
rc_reset_trigger(trigger);
}
}
return result;
}

View File

@ -0,0 +1,224 @@
#include "rc_internal.h"
#include <stddef.h>
#include <string.h> /* memset */
void rc_parse_trigger_internal(rc_trigger_t* self, const char** memaddr, rc_parse_state_t* parse) {
rc_condset_t** next;
const char* aux;
aux = *memaddr;
next = &self->alternative;
/* reset in case multiple triggers are parsed by the same parse_state */
parse->measured_target = 0;
parse->has_required_hits = 0;
if (*aux == 's' || *aux == 'S') {
self->requirement = 0;
}
else {
self->requirement = rc_parse_condset(&aux, parse, 0);
if (parse->offset < 0) {
return;
}
self->requirement->next = 0;
}
while (*aux == 's' || *aux == 'S') {
aux++;
*next = rc_parse_condset(&aux, parse, 0);
if (parse->offset < 0) {
return;
}
next = &(*next)->next;
}
*next = 0;
*memaddr = aux;
self->measured_value = 0;
self->measured_target = parse->measured_target;
self->state = RC_TRIGGER_STATE_WAITING;
self->has_hits = 0;
self->has_required_hits = parse->has_required_hits;
}
int rc_trigger_size(const char* memaddr) {
rc_trigger_t* self;
rc_parse_state_t parse;
rc_memref_t* memrefs;
rc_init_parse_state(&parse, 0, 0, 0);
rc_init_parse_state_memrefs(&parse, &memrefs);
self = RC_ALLOC(rc_trigger_t, &parse);
rc_parse_trigger_internal(self, &memaddr, &parse);
rc_destroy_parse_state(&parse);
return parse.offset;
}
rc_trigger_t* rc_parse_trigger(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx) {
rc_trigger_t* self;
rc_parse_state_t parse;
rc_init_parse_state(&parse, buffer, L, funcs_ndx);
self = RC_ALLOC(rc_trigger_t, &parse);
rc_init_parse_state_memrefs(&parse, &self->memrefs);
rc_parse_trigger_internal(self, &memaddr, &parse);
rc_destroy_parse_state(&parse);
return parse.offset >= 0 ? self : 0;
}
static void rc_reset_trigger_hitcounts(rc_trigger_t* self) {
rc_condset_t* condset;
if (self->requirement != 0) {
rc_reset_condset(self->requirement);
}
condset = self->alternative;
while (condset != 0) {
rc_reset_condset(condset);
condset = condset->next;
}
}
int rc_evaluate_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, lua_State* L) {
rc_eval_state_t eval_state;
rc_condset_t* condset;
int ret;
char is_paused;
char is_primed;
/* previously triggered, do nothing - return INACTIVE so caller doesn't think it triggered again */
if (self->state == RC_TRIGGER_STATE_TRIGGERED)
return RC_TRIGGER_STATE_INACTIVE;
/* unsupported, do nothing - return INACTIVE */
if (self->state == RC_TRIGGER_STATE_DISABLED)
return RC_TRIGGER_STATE_INACTIVE;
/* update the memory references */
rc_update_memref_values(self->memrefs, peek, ud);
/* not yet active, only update the memrefs so deltas are correct when it becomes active */
if (self->state == RC_TRIGGER_STATE_INACTIVE)
return RC_TRIGGER_STATE_INACTIVE;
/* process the trigger */
memset(&eval_state, 0, sizeof(eval_state));
eval_state.peek = peek;
eval_state.peek_userdata = ud;
eval_state.L = L;
if (self->requirement != NULL) {
ret = rc_test_condset(self->requirement, &eval_state);
is_paused = self->requirement->is_paused;
is_primed = eval_state.primed;
} else {
ret = 1;
is_paused = 0;
is_primed = 1;
}
condset = self->alternative;
if (condset) {
int sub = 0;
char sub_paused = 1;
char sub_primed = 0;
do {
sub |= rc_test_condset(condset, &eval_state);
sub_paused &= condset->is_paused;
sub_primed |= eval_state.primed;
condset = condset->next;
} while (condset != 0);
/* to trigger, the core must be true and at least one alt must be true */
ret &= sub;
is_primed &= sub_primed;
/* if the core is not paused, all alts must be paused to count as a paused trigger */
is_paused |= sub_paused;
}
/* if paused, the measured value may not be captured, keep the old value */
if (!is_paused)
self->measured_value = eval_state.measured_value;
/* if the state is WAITING and the trigger is ready to fire, ignore it and reset the hit counts */
/* otherwise, if the state is WAITING, proceed to activating the trigger */
if (self->state == RC_TRIGGER_STATE_WAITING && ret) {
rc_reset_trigger(self);
self->has_hits = 0;
return RC_TRIGGER_STATE_WAITING;
}
if (eval_state.was_reset) {
/* if any ResetIf condition was true, reset the hit counts */
rc_reset_trigger_hitcounts(self);
/* if the measured value came from a hit count, reset it too */
if (eval_state.measured_from_hits)
self->measured_value = 0;
/* if there were hit counts to clear, return RESET, but don't change the state */
if (self->has_hits) {
self->has_hits = 0;
return RC_TRIGGER_STATE_RESET;
}
/* any hits that were tallied were just reset */
eval_state.has_hits = 0;
is_primed = 0;
}
else if (ret) {
/* trigger was triggered */
self->state = RC_TRIGGER_STATE_TRIGGERED;
return RC_TRIGGER_STATE_TRIGGERED;
}
/* did not trigger this frame - update the information we'll need for next time */
self->has_hits = eval_state.has_hits;
if (is_paused) {
self->state = RC_TRIGGER_STATE_PAUSED;
}
else if (is_primed) {
self->state = RC_TRIGGER_STATE_PRIMED;
}
else {
self->state = RC_TRIGGER_STATE_ACTIVE;
}
/* if an individual condition was reset, notify the caller */
if (eval_state.was_cond_reset)
return RC_TRIGGER_STATE_RESET;
/* otherwise, just return the current state */
return self->state;
}
int rc_test_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, lua_State* L) {
/* for backwards compatibilty, rc_test_trigger always assumes the achievement is active */
self->state = RC_TRIGGER_STATE_ACTIVE;
return (rc_evaluate_trigger(self, peek, ud, L) == RC_TRIGGER_STATE_TRIGGERED);
}
void rc_reset_trigger(rc_trigger_t* self) {
rc_reset_trigger_hitcounts(self);
self->state = RC_TRIGGER_STATE_WAITING;
self->measured_value = 0;
self->has_hits = 0;
}

View File

@ -0,0 +1,310 @@
#include "rc_internal.h"
#include <string.h> /* memset */
#include <ctype.h> /* isdigit */
static void rc_parse_cond_value(rc_value_t* self, const char** memaddr, rc_parse_state_t* parse) {
rc_condset_t** next_clause;
next_clause = &self->conditions;
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);
if (parse->offset < 0) {
return;
}
if (**memaddr == 'S' || **memaddr == 's') {
/* alt groups not supported */
parse->offset = RC_INVALID_VALUE_FLAG;
}
else if (parse->measured_target == 0) {
parse->offset = RC_MISSING_VALUE_MEASURED;
}
else if (**memaddr == '$') {
/* maximum of */
++(*memaddr);
next_clause = &(*next_clause)->next;
continue;
}
break;
} while (1);
(*next_clause)->next = 0;
}
void rc_parse_legacy_value(rc_value_t* self, const char** memaddr, rc_parse_state_t* parse) {
rc_condition_t** next;
rc_condset_t** next_clause;
rc_condition_t* cond;
char buffer[64] = "A:";
const char* buffer_ptr;
char* ptr;
int end_of_clause;
/* convert legacy format into condset */
self->conditions = RC_ALLOC(rc_condset_t, parse);
self->conditions->has_pause = 0;
self->conditions->is_paused = 0;
next = &self->conditions->conditions;
next_clause = &self->conditions->next;
for (;;) {
ptr = &buffer[2];
end_of_clause = 0;
do {
switch (**memaddr) {
case '_': /* add next */
case '$': /* maximum of */
case '\0': /* end of string */
case ':': /* end of leaderboard clause */
case ')': /* end of rich presence macro */
end_of_clause = 1;
*ptr = '\0';
break;
case '*':
*ptr++ = '*';
buffer_ptr = *memaddr + 1;
if (*buffer_ptr == '-') {
/* negative value automatically needs prefix, 'f' handles both float and digits, so use it */
*ptr++ = 'f';
}
else {
/* if it looks like a floating point number, add the 'f' prefix */
while (isdigit(*buffer_ptr))
++buffer_ptr;
if (*buffer_ptr == '.')
*ptr++ = 'f';
}
break;
default:
*ptr++ = **memaddr;
break;
}
++(*memaddr);
} while (!end_of_clause);
buffer_ptr = buffer;
cond = rc_parse_condition(&buffer_ptr, parse, 0);
if (parse->offset < 0) {
return;
}
switch (cond->oper) {
case RC_OPERATOR_MULT:
case RC_OPERATOR_DIV:
case RC_OPERATOR_AND:
case RC_OPERATOR_NONE:
break;
default:
parse->offset = RC_INVALID_OPERATOR;
return;
}
cond->pause = 0;
*next = cond;
switch ((*memaddr)[-1]) {
case '_': /* add next */
next = &cond->next;
break;
case '$': /* max of */
cond->type = RC_CONDITION_MEASURED;
cond->next = 0;
*next_clause = RC_ALLOC(rc_condset_t, parse);
(*next_clause)->has_pause = 0;
(*next_clause)->is_paused = 0;
next = &(*next_clause)->conditions;
next_clause = &(*next_clause)->next;
break;
default: /* end of valid string */
--(*memaddr); /* undo the increment we performed when copying the string */
cond->type = RC_CONDITION_MEASURED;
cond->next = 0;
*next_clause = 0;
return;
}
}
}
void rc_parse_value_internal(rc_value_t* self, const char** memaddr, rc_parse_state_t* parse) {
/* if it starts with a condition flag (M: A: B: C:), parse the conditions */
if ((*memaddr)[1] == ':') {
rc_parse_cond_value(self, memaddr, parse);
}
else {
rc_parse_legacy_value(self, memaddr, parse);
}
self->name = "(unnamed)";
self->value.value = self->value.prior = 0;
self->value.changed = 0;
self->next = 0;
}
int rc_value_size(const char* memaddr) {
rc_value_t* self;
rc_parse_state_t parse;
rc_memref_t* first_memref;
rc_init_parse_state(&parse, 0, 0, 0);
rc_init_parse_state_memrefs(&parse, &first_memref);
self = RC_ALLOC(rc_value_t, &parse);
rc_parse_value_internal(self, &memaddr, &parse);
rc_destroy_parse_state(&parse);
return parse.offset;
}
rc_value_t* rc_parse_value(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx) {
rc_value_t* self;
rc_parse_state_t parse;
rc_init_parse_state(&parse, buffer, L, funcs_ndx);
self = RC_ALLOC(rc_value_t, &parse);
rc_init_parse_state_memrefs(&parse, &self->memrefs);
rc_parse_value_internal(self, &memaddr, &parse);
rc_destroy_parse_state(&parse);
return parse.offset >= 0 ? self : 0;
}
int rc_evaluate_value(rc_value_t* self, rc_peek_t peek, void* ud, lua_State* L) {
rc_eval_state_t eval_state;
rc_condset_t* condset;
int result = 0;
int paused = 1;
rc_update_memref_values(self->memrefs, peek, ud);
for (condset = self->conditions; condset != NULL; condset = condset->next) {
memset(&eval_state, 0, sizeof(eval_state));
eval_state.peek = peek;
eval_state.peek_userdata = ud;
eval_state.L = L;
rc_test_condset(condset, &eval_state);
if (condset->is_paused)
continue;
if (eval_state.was_reset) {
/* if any ResetIf condition was true, reset the hit counts
* NOTE: ResetIf only affects the current condset when used in values!
*/
rc_reset_condset(condset);
/* if the measured value came from a hit count, reset it too */
if (eval_state.measured_from_hits)
eval_state.measured_value = 0;
}
if (paused) {
/* capture the first valid measurement */
result = (int)eval_state.measured_value;
paused = 0;
}
else {
/* multiple condsets are currently only used for the MAX_OF operation.
* only keep the condset's value if it's higher than the current highest value.
*/
if ((int)eval_state.measured_value > result)
result = (int)eval_state.measured_value;
}
}
if (!paused) {
/* if not paused, store the value so that it's available when paused. */
rc_update_memref_value(&self->value, result);
}
else {
/* when paused, the Measured value will not be captured, use the last captured value. */
result = self->value.value;
}
return result;
}
void rc_reset_value(rc_value_t* self) {
rc_condset_t* condset = self->conditions;
while (condset != NULL) {
rc_reset_condset(condset);
condset = condset->next;
}
self->value.value = self->value.prior = 0;
self->value.changed = 0;
}
void rc_init_parse_state_variables(rc_parse_state_t* parse, rc_value_t** variables) {
parse->variables = variables;
*variables = 0;
}
rc_value_t* rc_alloc_helper_variable(const char* memaddr, int memaddr_len, rc_parse_state_t* parse)
{
rc_value_t** variables = parse->variables;
rc_value_t* value;
const char* name;
unsigned measured_target;
while ((value = *variables) != NULL) {
if (strncmp(value->name, memaddr, memaddr_len) == 0 && value->name[memaddr_len] == 0)
return value;
variables = &value->next;
}
value = RC_ALLOC_SCRATCH(rc_value_t, parse);
memset(&value->value, 0, sizeof(value->value));
value->value.size = RC_MEMSIZE_VARIABLE;
value->memrefs = NULL;
/* capture name before calling parse as parse will update memaddr pointer */
name = rc_alloc_str(parse, memaddr, memaddr_len);
if (!name)
return NULL;
/* the helper variable likely has a Measured condition. capture the current measured_target so we can restore it
* after generating the variable so the variable's Measured target doesn't conflict with the rest of the trigger. */
measured_target = parse->measured_target;
/* disable variable resolution when defining a variable to prevent infinite recursion */
variables = parse->variables;
parse->variables = NULL;
rc_parse_value_internal(value, &memaddr, parse);
parse->variables = variables;
/* restore the measured target */
parse->measured_target = measured_target;
/* store name after calling parse as parse will set name to (unnamed) */
value->name = name;
/* append the new variable to the end of the list (have to re-evaluate in case any others were added) */
while (*variables != NULL)
variables = &(*variables)->next;
*variables = value;
return value;
}
void rc_update_variables(rc_value_t* variable, rc_peek_t peek, void* ud, lua_State* L) {
while (variable) {
rc_evaluate_value(variable, peek, ud, L);
variable = variable->next;
}
}

View File

@ -0,0 +1,782 @@
#include "rc_hash.h"
#include "../rcheevos/rc_compat.h"
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
/* internal helper functions in hash.c */
extern void* rc_file_open(const char* path);
extern void rc_file_seek(void* file_handle, size_t offset, int origin);
extern size_t rc_file_tell(void* file_handle);
extern size_t rc_file_read(void* file_handle, void* buffer, int requested_bytes);
extern void rc_file_close(void* file_handle);
extern int rc_hash_error(const char* message);
extern const char* rc_path_get_filename(const char* path);
extern int rc_path_compare_extension(const char* path, const char* ext);
extern rc_hash_message_callback verbose_message_callback;
struct cdrom_t
{
void* file_handle;
int sector_size;
int sector_header_size;
int first_sector_offset;
int first_sector;
};
static void cdreader_determine_sector_size(struct cdrom_t* cdrom)
{
/* Attempt to determine the sector and header sizes. The CUE file may be lying.
* Look for the sync pattern using each of the supported sector sizes.
* Then check for the presence of "CD001", which is gauranteed to be in either the
* boot record or primary volume descriptor, one of which is always at sector 16.
*/
const unsigned char sync_pattern[] = {
0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00
};
unsigned char header[32];
const int toc_sector = 16;
cdrom->sector_size = 0;
cdrom->sector_header_size = 0;
rc_file_seek(cdrom->file_handle, toc_sector * 2352 + cdrom->first_sector_offset, SEEK_SET);
rc_file_read(cdrom->file_handle, header, sizeof(header));
if (memcmp(header, sync_pattern, 12) == 0)
{
cdrom->sector_size = 2352;
if (memcmp(&header[25], "CD001", 5) == 0)
cdrom->sector_header_size = 24;
else
cdrom->sector_header_size = 16;
}
else
{
rc_file_seek(cdrom->file_handle, toc_sector * 2336 + cdrom->first_sector_offset, SEEK_SET);
rc_file_read(cdrom->file_handle, header, sizeof(header));
if (memcmp(header, sync_pattern, 12) == 0)
{
cdrom->sector_size = 2336;
if (memcmp(&header[25], "CD001", 5) == 0)
cdrom->sector_header_size = 24;
else
cdrom->sector_header_size = 16;
}
else
{
rc_file_seek(cdrom->file_handle, toc_sector * 2048 + cdrom->first_sector_offset, SEEK_SET);
rc_file_read(cdrom->file_handle, header, sizeof(header));
if (memcmp(&header[1], "CD001", 5) == 0)
{
cdrom->sector_size = 2048;
cdrom->sector_header_size = 0;
}
}
}
}
static void* cdreader_open_bin_track(const char* path, uint32_t track)
{
void* file_handle;
struct cdrom_t* cdrom;
if (track > 1)
{
if (verbose_message_callback)
verbose_message_callback("Cannot locate secondary tracks without a cue sheet");
return NULL;
}
file_handle = rc_file_open(path);
if (!file_handle)
return NULL;
cdrom = (struct cdrom_t*)calloc(1, sizeof(*cdrom));
cdrom->file_handle = file_handle;
cdreader_determine_sector_size(cdrom);
if (cdrom->sector_size == 0)
{
size_t size;
rc_file_seek(cdrom->file_handle, 0, SEEK_END);
size = ftell(cdrom->file_handle);
if ((size % 2352) == 0)
{
/* raw tracks use all 2352 bytes and have a 24 byte header */
cdrom->sector_size = 2352;
cdrom->sector_header_size = 24;
}
else if ((size % 2048) == 0)
{
/* cooked tracks eliminate all header/footer data */
cdrom->sector_size = 2048;
cdrom->sector_header_size = 0;
}
else if ((size % 2336) == 0)
{
/* MODE 2 format without 16-byte sync data */
cdrom->sector_size = 2336;
cdrom->sector_header_size = 8;
}
else
{
free(cdrom);
if (verbose_message_callback)
verbose_message_callback("Could not determine sector size");
return NULL;
}
}
return cdrom;
}
static int cdreader_open_bin(struct cdrom_t* cdrom, const char* path, const char* mode)
{
cdrom->file_handle = rc_file_open(path);
if (!cdrom->file_handle)
return 0;
/* determine sector size */
cdreader_determine_sector_size(cdrom);
/* could not determine, which means we'll probably have more issues later
* but use the CUE provided information anyway
*/
if (cdrom->sector_size == 0)
{
/* All of these modes have 2048 byte payloads. In MODE1/2352 and MODE2/2352
* modes, the mode can actually be specified per sector to change the payload
* size, but that reduces the ability to recover from errors when the disc
* is damaged, so it's seldomly used, and when it is, it's mostly for audio
* or video data where a blip or two probably won't be noticed by the user.
* So, while we techincally support all of the following modes, we only do
* so with 2048 byte payloads.
* http://totalsonicmastering.com/cuesheetsyntax.htm
* MODE1/2048 ? CDROM Mode1 Data (cooked) [no header, no footer]
* MODE1/2352 ? CDROM Mode1 Data (raw) [16 byte header, 288 byte footer]
* MODE2/2336 ? CDROM-XA Mode2 Data [8 byte header, 280 byte footer]
* MODE2/2352 ? CDROM-XA Mode2 Data [24 byte header, 280 byte footer]
*/
if (memcmp(mode, "MODE2/2352", 10) == 0)
{
cdrom->sector_size = 2352;
cdrom->sector_header_size = 24;
}
else if (memcmp(mode, "MODE1/2048", 10) == 0)
{
cdrom->sector_size = 2048;
cdrom->sector_header_size = 0;
}
else if (memcmp(mode, "MODE2/2336", 10) == 0)
{
cdrom->sector_size = 2336;
cdrom->sector_header_size = 8;
}
else if (memcmp(mode, "MODE1/2352", 10) == 0)
{
cdrom->sector_size = 2352;
cdrom->sector_header_size = 16;
}
}
return (cdrom->sector_size != 0);
}
static char* cdreader_get_bin_path(const char* cue_path, const char* bin_name)
{
const char* filename = rc_path_get_filename(cue_path);
const size_t bin_name_len = strlen(bin_name);
const size_t cue_path_len = filename - cue_path;
const size_t needed = cue_path_len + bin_name_len + 1;
char* bin_filename = (char*)malloc(needed);
if (!bin_filename)
{
char buffer[64];
snprintf(buffer, sizeof(buffer), "Failed to allocate %u bytes", (unsigned)needed);
rc_hash_error((const char*)buffer);
}
else
{
memcpy(bin_filename, cue_path, cue_path_len);
memcpy(bin_filename + cue_path_len, bin_name, bin_name_len + 1);
}
return bin_filename;
}
static size_t cdreader_get_bin_size(const char* cue_path, const char* bin_name)
{
size_t size = 0;
char* bin_filename = cdreader_get_bin_path(cue_path, bin_name);
if (bin_filename)
{
void* file_handle = rc_file_open(bin_filename);
if (file_handle)
{
rc_file_seek(file_handle, 0, SEEK_END);
size = rc_file_tell(file_handle);
rc_file_close(file_handle);
}
free(bin_filename);
}
return size;
}
static void* cdreader_open_cue_track(const char* path, uint32_t track)
{
void* file_handle;
size_t file_offset = 0;
char buffer[1024], mode[16];
char* bin_filename;
char file[256];
char *ptr, *ptr2, *end;
int current_track = 0;
int sector_size = 0;
int track_first_sector = 0;
int previous_sector_size = 0;
int previous_index_sector_offset = 0;
int previous_track_is_data = 0;
int previous_track_sector_offset = 0;
char previous_track_mode[16];
int largest_track = 0;
int largest_track_sector_count = 0;
int largest_track_offset = 0;
char largest_track_mode[16];
char largest_track_file[256];
int offset = 0;
int done = 0;
size_t num_read = 0;
struct cdrom_t* cdrom = NULL;
file_handle = rc_file_open(path);
if (!file_handle)
return NULL;
file[0] = '\0';
do
{
num_read = rc_file_read(file_handle, buffer, sizeof(buffer) - 1);
if (num_read == 0)
break;
buffer[num_read] = 0;
if (num_read == sizeof(buffer) - 1)
end = buffer + sizeof(buffer) * 3 / 4;
else
end = buffer + num_read;
for (ptr = buffer; ptr < end; ++ptr)
{
while (*ptr == ' ')
++ptr;
if (strncasecmp(ptr, "INDEX ", 6) == 0)
{
int m = 0, s = 0, f = 0;
int index, sector_offset;
ptr += 6;
index = atoi(ptr);
while (*ptr != ' ' && *ptr != '\n')
++ptr;
while (*ptr == ' ')
++ptr;
/* convert mm:ss:ff to sector count */
sscanf(ptr, "%d:%d:%d", &m, &s, &f);
sector_offset = ((m * 60) + s) * 75 + f;
sector_offset -= previous_index_sector_offset;
if (index == 1)
track_first_sector += sector_offset;
/* if looking for the largest data track, determine previous track size */
if (index == 1 && track == RC_HASH_CDTRACK_LARGEST && previous_track_is_data)
{
if (sector_offset > largest_track_sector_count)
{
largest_track_sector_count = sector_offset;
largest_track_offset = previous_track_sector_offset;
largest_track = current_track - 1;
memcpy(largest_track_mode, previous_track_mode, sizeof(largest_track_mode));
strcpy(largest_track_file, file);
}
}
/* calculate the true offset and update the counters for the next INDEX marker */
offset += sector_offset * previous_sector_size;
previous_sector_size = sector_size;
previous_index_sector_offset += sector_offset;
if (index == 1)
{
if (verbose_message_callback)
{
char message[128];
char* scan = mode;
while (*scan && !isspace(*scan))
++scan;
*scan = '\0';
snprintf(message, sizeof(message), "Found %s track %d (sector size %d, track starts at %d)", mode, current_track, sector_size, offset);
verbose_message_callback(message);
}
if (current_track == (int)track)
{
done = 1;
break;
}
memcpy(previous_track_mode, mode, sizeof(previous_track_mode));
previous_track_is_data = (memcmp(mode, "MODE", 4) == 0);
previous_track_sector_offset = offset;
if (previous_track_is_data && track == RC_HASH_CDTRACK_FIRST_DATA)
{
track = current_track;
done = 1;
break;
}
}
}
else if (strncasecmp(ptr, "TRACK ", 6) == 0)
{
ptr += 6;
current_track = atoi(ptr);
while (*ptr != ' ')
++ptr;
while (*ptr == ' ')
++ptr;
memcpy(mode, ptr, sizeof(mode));
previous_sector_size = sector_size;
if (memcmp(mode, "MODE", 4) == 0)
{
sector_size = atoi(ptr + 6);
}
else
{
/* assume AUDIO */
sector_size = 2352;
}
}
else if (strncasecmp(ptr, "FILE ", 5) == 0)
{
if (previous_sector_size > 0)
{
/* determine previous track size */
int sector_count = (int)cdreader_get_bin_size(path, file) / previous_sector_size;
track_first_sector += sector_count;
/* if looking for the largest data track, check to see if this one is larger */
if (track == RC_HASH_CDTRACK_LARGEST && previous_track_is_data)
{
if (sector_count > largest_track_sector_count)
{
largest_track_sector_count = sector_count;
largest_track_offset = previous_track_sector_offset;
largest_track = current_track;
memcpy(largest_track_mode, previous_track_mode, sizeof(largest_track_mode));
strcpy(largest_track_file, file);
}
}
}
ptr += 5;
ptr2 = ptr;
if (*ptr == '"')
{
++ptr;
do
{
++ptr2;
} while (*ptr2 && *ptr2 != '\n' && *ptr2 != '"');
}
else
{
do
{
++ptr2;
} while (*ptr2 && *ptr2 != '\n' && *ptr2 != ' ');
}
if (ptr2 - ptr < (int)sizeof(file))
{
memcpy(file, ptr, ptr2 - ptr);
file[ptr2 - ptr] = '\0';
}
else
{
file[0] = '\0';
}
current_track = 0;
previous_sector_size = 0;
previous_index_sector_offset = 0;
offset = 0;
}
while (*ptr && *ptr != '\n')
++ptr;
}
if (done)
break;
file_offset += (ptr - buffer);
rc_file_seek(file_handle, file_offset, SEEK_SET);
} while (1);
rc_file_close(file_handle);
if (track == RC_HASH_CDTRACK_LARGEST)
{
previous_track_is_data = (memcmp(mode, "MODE", 4) == 0);
if (previous_track_is_data)
{
int sector_count = (int)cdreader_get_bin_size(path, file) / previous_sector_size;
sector_count -= previous_index_sector_offset;
if (sector_count > largest_track_sector_count)
{
largest_track_sector_count = sector_count;
largest_track_offset = previous_track_sector_offset;
largest_track = current_track;
memcpy(largest_track_mode, previous_track_mode, sizeof(largest_track_mode));
strcpy(largest_track_file, file);
}
}
if (largest_track > 0)
{
current_track = largest_track;
track = (uint32_t)largest_track;
offset = largest_track_offset;
memcpy(mode, largest_track_mode, sizeof(mode));
strcpy(file, largest_track_file);
}
}
if (current_track == (int)track)
{
cdrom = (struct cdrom_t*)calloc(1, sizeof(*cdrom));
if (!cdrom)
{
snprintf((char*)buffer, sizeof(buffer), "Failed to allocate %u bytes", (unsigned)sizeof(*cdrom));
rc_hash_error((const char*)buffer);
return NULL;
}
cdrom->first_sector_offset = offset;
cdrom->first_sector = track_first_sector;
/* verify existance of bin file */
bin_filename = cdreader_get_bin_path(path, file);
if (bin_filename)
{
if (cdreader_open_bin(cdrom, bin_filename, mode))
{
if (verbose_message_callback)
{
if (cdrom->first_sector_offset)
snprintf((char*)buffer, sizeof(buffer), "Opened track %d (sector size %d, track starts at %d)", track, cdrom->sector_size, cdrom->first_sector_offset);
else
snprintf((char*)buffer, sizeof(buffer), "Opened track %d (sector size %d)", track, cdrom->sector_size);
verbose_message_callback((const char*)buffer);
}
}
else
{
snprintf((char*)buffer, sizeof(buffer), "Could not open %s", bin_filename);
rc_hash_error((const char*)buffer);
free(cdrom);
cdrom = NULL;
}
free(bin_filename);
}
}
return cdrom;
}
static void* cdreader_open_gdi_track(const char* path, uint32_t track)
{
void* file_handle;
char buffer[1024];
char mode[16] = "MODE1/";
char sector_size[16];
char file[256];
size_t track_size;
int track_type;
char* bin_path = "";
uint32_t current_track = 0;
char* ptr, *ptr2, *end;
int lba = 0;
uint32_t largest_track = 0;
size_t largest_track_size = 0;
char largest_track_file[256];
char largest_track_sector_size[16];
int largest_track_lba = 0;
int found = 0;
size_t num_read = 0;
size_t file_offset = 0;
struct cdrom_t* cdrom = NULL;
file_handle = rc_file_open(path);
if (!file_handle)
return NULL;
file[0] = '\0';
do
{
num_read = rc_file_read(file_handle, buffer, sizeof(buffer) - 1);
if (num_read == 0)
break;
buffer[num_read] = 0;
if (num_read == sizeof(buffer) - 1)
end = buffer + sizeof(buffer) * 3 / 4;
else
end = buffer + num_read;
ptr = buffer;
/* the first line contains the number of tracks, so we can get the last track index from it */
if (track == RC_HASH_CDTRACK_LAST)
track = atoi(ptr);
/* first line contains the number of tracks and will be skipped */
while (ptr < end)
{
/* skip until next newline */
while (*ptr != '\n' && ptr < end)
++ptr;
/* skip newlines */
while ((*ptr == '\n' || *ptr == '\r') && ptr < end)
++ptr;
/* line format: [trackid] [lba] [type] [sectorsize] [file] [?] */
current_track = (uint32_t)atoi(ptr);
if (track && current_track != track)
continue;
while (isdigit(*ptr))
++ptr;
++ptr;
lba = atoi(ptr);
while (isdigit(*ptr))
++ptr;
++ptr;
track_type = atoi(ptr);
while (isdigit(*ptr))
++ptr;
++ptr;
ptr2 = sector_size;
while (isdigit(*ptr))
*ptr2++ = *ptr++;
*ptr2 = '\0';
++ptr;
ptr2 = file;
if (*ptr == '\"')
{
++ptr;
while (*ptr != '\"')
*ptr2++ = *ptr++;
++ptr;
}
else
{
while (*ptr != ' ')
*ptr2++ = *ptr++;
}
*ptr2 = '\0';
if (track == current_track || (track == RC_HASH_CDTRACK_FIRST_DATA && track_type == 4))
{
found = 1;
break;
}
else if (track == RC_HASH_CDTRACK_LARGEST && track_type == 4)
{
track_size = cdreader_get_bin_size(path, file);
if (track_size > largest_track_size)
{
largest_track_size = track_size;
largest_track = current_track;
largest_track_lba = lba;
strcpy(largest_track_file, file);
strcpy(largest_track_sector_size, sector_size);
}
}
}
if (found)
break;
file_offset += (ptr - buffer);
rc_file_seek(file_handle, file_offset, SEEK_SET);
} while (1);
rc_file_close(file_handle);
cdrom = (struct cdrom_t*)calloc(1, sizeof(*cdrom));
if (!cdrom)
{
snprintf((char*)buffer, sizeof(buffer), "Failed to allocate %u bytes", (unsigned)sizeof(*cdrom));
rc_hash_error((const char*)buffer);
return NULL;
}
/* if we were tracking the largest track, make it the current track.
* otherwise, current_track will be the requested track, or last track. */
if (largest_track != 0 && largest_track != current_track)
{
current_track = largest_track;
strcpy(file, largest_track_file);
strcpy(sector_size, largest_track_sector_size);
lba = largest_track_lba;
}
/* open the bin file for the track - construct mode parameter from sector_size */
ptr = &mode[6];
ptr2 = sector_size;
while (*ptr2 && *ptr2 != '\"')
*ptr++ = *ptr2++;
*ptr = '\0';
bin_path = cdreader_get_bin_path(path, file);
if (cdreader_open_bin(cdrom, bin_path, mode))
{
cdrom->first_sector_offset = 0;
cdrom->first_sector = lba;
if (verbose_message_callback)
{
snprintf((char*)buffer, sizeof(buffer), "Opened track %d (sector size %d)", track, cdrom->sector_size);
verbose_message_callback((const char*)buffer);
}
}
else
{
snprintf((char*)buffer, sizeof(buffer), "Could not open %s", bin_path);
rc_hash_error((const char*)buffer);
free(cdrom);
cdrom = NULL;
}
return cdrom;
}
static void* cdreader_open_track(const char* path, uint32_t track)
{
/* backwards compatibility - 0 used to mean largest */
if (track == 0)
track = RC_HASH_CDTRACK_LARGEST;
if (rc_path_compare_extension(path, "cue"))
return cdreader_open_cue_track(path, track);
if (rc_path_compare_extension(path, "gdi"))
return cdreader_open_gdi_track(path, track);
return cdreader_open_bin_track(path, track);
}
static size_t cdreader_read_sector(void* track_handle, uint32_t sector, void* buffer, size_t requested_bytes)
{
size_t sector_start;
size_t num_read, total_read = 0;
uint8_t* buffer_ptr = (uint8_t*)buffer;
struct cdrom_t* cdrom = (struct cdrom_t*)track_handle;
if (!cdrom)
return 0;
sector_start = sector * cdrom->sector_size + cdrom->sector_header_size + cdrom->first_sector_offset;
while (requested_bytes > 2048)
{
rc_file_seek(cdrom->file_handle, sector_start, SEEK_SET);
num_read = rc_file_read(cdrom->file_handle, buffer_ptr, 2048);
total_read += num_read;
if (num_read < 2048)
return total_read;
buffer_ptr += 2048;
sector_start += cdrom->sector_size;
requested_bytes -= 2048;
}
rc_file_seek(cdrom->file_handle, sector_start, SEEK_SET);
num_read = rc_file_read(cdrom->file_handle, buffer_ptr, (int)requested_bytes);
total_read += num_read;
return total_read;
}
static void cdreader_close_track(void* track_handle)
{
struct cdrom_t* cdrom = (struct cdrom_t*)track_handle;
if (cdrom)
{
if (cdrom->file_handle)
rc_file_close(cdrom->file_handle);
free(track_handle);
}
}
static uint32_t cdreader_absolute_sector_to_track_sector(void* track_handle, uint32_t sector)
{
struct cdrom_t* cdrom = (struct cdrom_t*)track_handle;
if (cdrom)
return sector - cdrom->first_sector;
return 0;
}
void rc_hash_init_default_cdreader()
{
struct rc_hash_cdreader cdreader;
cdreader.open_track = cdreader_open_track;
cdreader.read_sector = cdreader_read_sector;
cdreader.close_track = cdreader_close_track;
cdreader.absolute_sector_to_track_sector = cdreader_absolute_sector_to_track_sector;
rc_hash_init_custom_cdreader(&cdreader);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,381 @@
/*
Copyright (C) 1999, 2000, 2002 Aladdin Enterprises. All rights reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
L. Peter Deutsch
ghost@aladdin.com
*/
/* $Id: md5.c,v 1.6 2002/04/13 19:20:28 lpd Exp $ */
/*
Independent implementation of MD5 (RFC 1321).
This code implements the MD5 Algorithm defined in RFC 1321, whose
text is available at
http://www.ietf.org/rfc/rfc1321.txt
The code is derived from the text of the RFC, including the test suite
(section A.5) but excluding the rest of Appendix A. It does not include
any code or documentation that is identified in the RFC as being
copyrighted.
The original and principal author of md5.c is L. Peter Deutsch
<ghost@aladdin.com>. Other authors are noted in the change history
that follows (in reverse chronological order):
2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order
either statically or dynamically; added missing #include <string.h>
in library.
2002-03-11 lpd Corrected argument list for main(), and added int return
type, in test program and T value program.
2002-02-21 lpd Added missing #include <stdio.h> in test program.
2000-07-03 lpd Patched to eliminate warnings about "constant is
unsigned in ANSI C, signed in traditional"; made test program
self-checking.
1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5).
1999-05-03 lpd Original version.
*/
#include "md5.h"
#include <string.h>
#undef BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */
#ifdef ARCH_IS_BIG_ENDIAN
# define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1)
#else
# define BYTE_ORDER 0
#endif
#define T_MASK ((md5_word_t)~0)
#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87)
#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9)
#define T3 0x242070db
#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111)
#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050)
#define T6 0x4787c62a
#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec)
#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe)
#define T9 0x698098d8
#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850)
#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e)
#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841)
#define T13 0x6b901122
#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c)
#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71)
#define T16 0x49b40821
#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d)
#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf)
#define T19 0x265e5a51
#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855)
#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2)
#define T22 0x02441453
#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e)
#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437)
#define T25 0x21e1cde6
#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829)
#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278)
#define T28 0x455a14ed
#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa)
#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07)
#define T31 0x676f02d9
#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375)
#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd)
#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e)
#define T35 0x6d9d6122
#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3)
#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb)
#define T38 0x4bdecfa9
#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f)
#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f)
#define T41 0x289b7ec6
#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805)
#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a)
#define T44 0x04881d05
#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6)
#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a)
#define T47 0x1fa27cf8
#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a)
#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb)
#define T50 0x432aff97
#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58)
#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6)
#define T53 0x655b59c3
#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d)
#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82)
#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e)
#define T57 0x6fa87e4f
#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f)
#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb)
#define T60 0x4e0811a1
#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d)
#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca)
#define T63 0x2ad7d2bb
#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e)
static void
md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/)
{
md5_word_t
a = pms->abcd[0], b = pms->abcd[1],
c = pms->abcd[2], d = pms->abcd[3];
md5_word_t t;
#if BYTE_ORDER > 0
/* Define storage only for big-endian CPUs. */
md5_word_t X[16];
#else
/* Define storage for little-endian or both types of CPUs. */
md5_word_t xbuf[16];
const md5_word_t *X;
#endif
{
#if BYTE_ORDER == 0
/*
* Determine dynamically whether this is a big-endian or
* little-endian machine, since we can use a more efficient
* algorithm on the latter.
*/
static const int w = 1;
if (*((const md5_byte_t *)&w)) /* dynamic little-endian */
#endif
#if BYTE_ORDER <= 0 /* little-endian */
{
/*
* On little-endian machines, we can process properly aligned
* data without copying it.
*/
if (!((data - (const md5_byte_t *)0) & 3)) {
/* data are properly aligned */
X = (const md5_word_t *)data;
} else {
/* not aligned */
memcpy(xbuf, data, 64);
X = xbuf;
}
}
#endif
#if BYTE_ORDER == 0
else /* dynamic big-endian */
#endif
#if BYTE_ORDER >= 0 /* big-endian */
{
/*
* On big-endian machines, we must arrange the bytes in the
* right order.
*/
const md5_byte_t *xp = data;
int i;
# if BYTE_ORDER == 0
X = xbuf; /* (dynamic only) */
# else
# define xbuf X /* (static only) */
# endif
for (i = 0; i < 16; ++i, xp += 4)
xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24);
}
#endif
}
#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
/* Round 1. */
/* Let [abcd k s i] denote the operation
a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */
#define F(x, y, z) (((x) & (y)) | (~(x) & (z)))
#define SET(a, b, c, d, k, s, Ti)\
t = a + F(b,c,d) + X[k] + Ti;\
a = ROTATE_LEFT(t, s) + b
/* Do the following 16 operations. */
SET(a, b, c, d, 0, 7, T1);
SET(d, a, b, c, 1, 12, T2);
SET(c, d, a, b, 2, 17, T3);
SET(b, c, d, a, 3, 22, T4);
SET(a, b, c, d, 4, 7, T5);
SET(d, a, b, c, 5, 12, T6);
SET(c, d, a, b, 6, 17, T7);
SET(b, c, d, a, 7, 22, T8);
SET(a, b, c, d, 8, 7, T9);
SET(d, a, b, c, 9, 12, T10);
SET(c, d, a, b, 10, 17, T11);
SET(b, c, d, a, 11, 22, T12);
SET(a, b, c, d, 12, 7, T13);
SET(d, a, b, c, 13, 12, T14);
SET(c, d, a, b, 14, 17, T15);
SET(b, c, d, a, 15, 22, T16);
#undef SET
/* Round 2. */
/* Let [abcd k s i] denote the operation
a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */
#define G(x, y, z) (((x) & (z)) | ((y) & ~(z)))
#define SET(a, b, c, d, k, s, Ti)\
t = a + G(b,c,d) + X[k] + Ti;\
a = ROTATE_LEFT(t, s) + b
/* Do the following 16 operations. */
SET(a, b, c, d, 1, 5, T17);
SET(d, a, b, c, 6, 9, T18);
SET(c, d, a, b, 11, 14, T19);
SET(b, c, d, a, 0, 20, T20);
SET(a, b, c, d, 5, 5, T21);
SET(d, a, b, c, 10, 9, T22);
SET(c, d, a, b, 15, 14, T23);
SET(b, c, d, a, 4, 20, T24);
SET(a, b, c, d, 9, 5, T25);
SET(d, a, b, c, 14, 9, T26);
SET(c, d, a, b, 3, 14, T27);
SET(b, c, d, a, 8, 20, T28);
SET(a, b, c, d, 13, 5, T29);
SET(d, a, b, c, 2, 9, T30);
SET(c, d, a, b, 7, 14, T31);
SET(b, c, d, a, 12, 20, T32);
#undef SET
/* Round 3. */
/* Let [abcd k s t] denote the operation
a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */
#define H(x, y, z) ((x) ^ (y) ^ (z))
#define SET(a, b, c, d, k, s, Ti)\
t = a + H(b,c,d) + X[k] + Ti;\
a = ROTATE_LEFT(t, s) + b
/* Do the following 16 operations. */
SET(a, b, c, d, 5, 4, T33);
SET(d, a, b, c, 8, 11, T34);
SET(c, d, a, b, 11, 16, T35);
SET(b, c, d, a, 14, 23, T36);
SET(a, b, c, d, 1, 4, T37);
SET(d, a, b, c, 4, 11, T38);
SET(c, d, a, b, 7, 16, T39);
SET(b, c, d, a, 10, 23, T40);
SET(a, b, c, d, 13, 4, T41);
SET(d, a, b, c, 0, 11, T42);
SET(c, d, a, b, 3, 16, T43);
SET(b, c, d, a, 6, 23, T44);
SET(a, b, c, d, 9, 4, T45);
SET(d, a, b, c, 12, 11, T46);
SET(c, d, a, b, 15, 16, T47);
SET(b, c, d, a, 2, 23, T48);
#undef SET
/* Round 4. */
/* Let [abcd k s t] denote the operation
a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */
#define I(x, y, z) ((y) ^ ((x) | ~(z)))
#define SET(a, b, c, d, k, s, Ti)\
t = a + I(b,c,d) + X[k] + Ti;\
a = ROTATE_LEFT(t, s) + b
/* Do the following 16 operations. */
SET(a, b, c, d, 0, 6, T49);
SET(d, a, b, c, 7, 10, T50);
SET(c, d, a, b, 14, 15, T51);
SET(b, c, d, a, 5, 21, T52);
SET(a, b, c, d, 12, 6, T53);
SET(d, a, b, c, 3, 10, T54);
SET(c, d, a, b, 10, 15, T55);
SET(b, c, d, a, 1, 21, T56);
SET(a, b, c, d, 8, 6, T57);
SET(d, a, b, c, 15, 10, T58);
SET(c, d, a, b, 6, 15, T59);
SET(b, c, d, a, 13, 21, T60);
SET(a, b, c, d, 4, 6, T61);
SET(d, a, b, c, 11, 10, T62);
SET(c, d, a, b, 2, 15, T63);
SET(b, c, d, a, 9, 21, T64);
#undef SET
/* Then perform the following additions. (That is increment each
of the four registers by the value it had before this block
was started.) */
pms->abcd[0] += a;
pms->abcd[1] += b;
pms->abcd[2] += c;
pms->abcd[3] += d;
}
void
md5_init(md5_state_t *pms)
{
pms->count[0] = pms->count[1] = 0;
pms->abcd[0] = 0x67452301;
pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476;
pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301;
pms->abcd[3] = 0x10325476;
}
void
md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes)
{
const md5_byte_t *p = data;
int left = nbytes;
int offset = (pms->count[0] >> 3) & 63;
md5_word_t nbits = (md5_word_t)(nbytes << 3);
if (nbytes <= 0)
return;
/* Update the message length. */
pms->count[1] += nbytes >> 29;
pms->count[0] += nbits;
if (pms->count[0] < nbits)
pms->count[1]++;
/* Process an initial partial block. */
if (offset) {
int copy = (offset + nbytes > 64 ? 64 - offset : nbytes);
memcpy(pms->buf + offset, p, copy);
if (offset + copy < 64)
return;
p += copy;
left -= copy;
md5_process(pms, pms->buf);
}
/* Process full blocks. */
for (; left >= 64; p += 64, left -= 64)
md5_process(pms, p);
/* Process a final partial block. */
if (left)
memcpy(pms->buf, p, left);
}
void
md5_finish(md5_state_t *pms, md5_byte_t digest[16])
{
static const md5_byte_t pad[64] = {
0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
md5_byte_t data[8];
int i;
/* Save the length before padding. */
for (i = 0; i < 8; ++i)
data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3));
/* Pad to 56 bytes mod 64. */
md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1);
/* Append the length. */
md5_append(pms, data, 8);
for (i = 0; i < 16; ++i)
digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3));
}

View File

@ -0,0 +1,91 @@
/*
Copyright (C) 1999, 2002 Aladdin Enterprises. All rights reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
L. Peter Deutsch
ghost@aladdin.com
*/
/* $Id: md5.h,v 1.4 2002/04/13 19:20:28 lpd Exp $ */
/*
Independent implementation of MD5 (RFC 1321).
This code implements the MD5 Algorithm defined in RFC 1321, whose
text is available at
http://www.ietf.org/rfc/rfc1321.txt
The code is derived from the text of the RFC, including the test suite
(section A.5) but excluding the rest of Appendix A. It does not include
any code or documentation that is identified in the RFC as being
copyrighted.
The original and principal author of md5.h is L. Peter Deutsch
<ghost@aladdin.com>. Other authors are noted in the change history
that follows (in reverse chronological order):
2002-04-13 lpd Removed support for non-ANSI compilers; removed
references to Ghostscript; clarified derivation from RFC 1321;
now handles byte order either statically or dynamically.
1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5);
added conditionalization for C++ compilation from Martin
Purschke <purschke@bnl.gov>.
1999-05-03 lpd Original version.
*/
#ifndef md5_INCLUDED
# define md5_INCLUDED
/*
* This package supports both compile-time and run-time determination of CPU
* byte order. If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be
* compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is
* defined as non-zero, the code will be compiled to run only on big-endian
* CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to
* run on either big- or little-endian CPUs, but will run slightly less
* efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined.
*/
typedef unsigned char md5_byte_t; /* 8-bit byte */
typedef unsigned int md5_word_t; /* 32-bit word */
/* Define the state of the MD5 Algorithm. */
typedef struct md5_state_s {
md5_word_t count[2]; /* message length in bits, lsw first */
md5_word_t abcd[4]; /* digest buffer */
md5_byte_t buf[64]; /* accumulate block */
} md5_state_t;
#ifdef __cplusplus
extern "C"
{
#endif
/* Initialize the algorithm. */
void md5_init(md5_state_t *pms);
/* Append a string to the message. */
void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes);
/* Finish the message and return the digest. */
void md5_finish(md5_state_t *pms, md5_byte_t digest[16]);
#ifdef __cplusplus
} /* end extern "C" */
#endif
#endif /* md5_INCLUDED */

373
dep/rcheevos/src/rurl/url.c Normal file
View File

@ -0,0 +1,373 @@
#include "rc_url.h"
#include "../rcheevos/rc_compat.h"
#include "../rhash/md5.h"
#include <stdio.h>
#include <string.h>
static int rc_url_encode(char* encoded, size_t len, const char* str) {
for (;;) {
switch (*str) {
case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j':
case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't':
case 'u': case 'v': case 'w': case 'x': case 'y': case 'z':
case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J':
case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T':
case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z':
case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
case '-': case '_': case '.': case '~':
if (len < 2)
return -1;
*encoded++ = *str++;
--len;
break;
case ' ':
if (len < 2)
return -1;
*encoded++ = '+';
++str;
--len;
break;
default:
if (len < 4)
return -1;
snprintf(encoded, len, "%%%02x", (unsigned char)*str);
encoded += 3;
++str;
len -= 3;
break;
case '\0':
*encoded = 0;
return 0;
}
}
}
int rc_url_award_cheevo(char* buffer, size_t size, const char* user_name, const char* login_token,
unsigned cheevo_id, int hardcore, const char* game_hash) {
char urle_user_name[64];
char urle_login_token[64];
int written;
if (rc_url_encode(urle_user_name, sizeof(urle_user_name), user_name) != 0) {
return -1;
}
if (rc_url_encode(urle_login_token, sizeof(urle_login_token), login_token) != 0) {
return -1;
}
written = snprintf(
buffer,
size,
"http://retroachievements.org/dorequest.php?r=awardachievement&u=%s&t=%s&a=%u&h=%d",
urle_user_name,
urle_login_token,
cheevo_id,
hardcore ? 1 : 0
);
if (game_hash && strlen(game_hash) == 32 && (size - (size_t)written) >= 35) {
written += snprintf(buffer + written, size - (size_t)written, "&m=%s", game_hash);
}
return (size_t)written >= size ? -1 : 0;
}
int rc_url_submit_lboard(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned lboard_id, int value) {
char urle_user_name[64];
char urle_login_token[64];
char signature[64];
unsigned char hash[16];
md5_state_t state;
int written;
if (rc_url_encode(urle_user_name, sizeof(urle_user_name), user_name) != 0) {
return -1;
}
if (rc_url_encode(urle_login_token, sizeof(urle_login_token), login_token) != 0) {
return -1;
}
/* Evaluate the signature. */
snprintf(signature, sizeof(signature), "%u%s%u", lboard_id, user_name, lboard_id);
md5_init(&state);
md5_append(&state, (unsigned char*)signature, (int)strlen(signature));
md5_finish(&state, hash);
written = snprintf(
buffer,
size,
"http://retroachievements.org/dorequest.php?r=submitlbentry&u=%s&t=%s&i=%u&s=%d&v=%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
urle_user_name,
urle_login_token,
lboard_id,
value,
hash[ 0], hash[ 1], hash[ 2], hash[ 3], hash[ 4], hash[ 5], hash[ 6], hash[ 7],
hash[ 8], hash[ 9], hash[10], hash[11],hash[12], hash[13], hash[14], hash[15]
);
return (size_t)written >= size ? -1 : 0;
}
int rc_url_get_gameid(char* buffer, size_t size, const char* hash) {
int written = snprintf(
buffer,
size,
"http://retroachievements.org/dorequest.php?r=gameid&m=%s",
hash
);
return (size_t)written >= size ? -1 : 0;
}
int rc_url_get_patch(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned gameid) {
char urle_user_name[64];
char urle_login_token[64];
int written;
if (rc_url_encode(urle_user_name, sizeof(urle_user_name), user_name) != 0) {
return -1;
}
if (rc_url_encode(urle_login_token, sizeof(urle_login_token), login_token) != 0) {
return -1;
}
written = snprintf(
buffer,
size,
"http://retroachievements.org/dorequest.php?r=patch&u=%s&t=%s&g=%u",
urle_user_name,
urle_login_token,
gameid
);
return (size_t)written >= size ? -1 : 0;
}
int rc_url_get_badge_image(char* buffer, size_t size, const char* badge_name) {
int written = snprintf(
buffer,
size,
"http://i.retroachievements.org/Badge/%s",
badge_name
);
return (size_t)written >= size ? -1 : 0;
}
int rc_url_login_with_password(char* buffer, size_t size, const char* user_name, const char* password) {
char urle_user_name[64];
char urle_password[64];
int written;
if (rc_url_encode(urle_user_name, sizeof(urle_user_name), user_name) != 0) {
return -1;
}
if (rc_url_encode(urle_password, sizeof(urle_password), password) != 0) {
return -1;
}
written = snprintf(
buffer,
size,
"http://retroachievements.org/dorequest.php?r=login&u=%s&p=%s",
urle_user_name,
urle_password
);
return (size_t)written >= size ? -1 : 0;
}
int rc_url_login_with_token(char* buffer, size_t size, const char* user_name, const char* login_token) {
char urle_user_name[64];
char urle_login_token[64];
int written;
if (rc_url_encode(urle_user_name, sizeof(urle_user_name), user_name) != 0) {
return -1;
}
if (rc_url_encode(urle_login_token, sizeof(urle_login_token), login_token) != 0) {
return -1;
}
written = snprintf(
buffer,
size,
"http://retroachievements.org/dorequest.php?r=login&u=%s&t=%s",
urle_user_name,
urle_login_token
);
return (size_t)written >= size ? -1 : 0;
}
int rc_url_get_unlock_list(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned gameid, int hardcore) {
char urle_user_name[64];
char urle_login_token[64];
int written;
if (rc_url_encode(urle_user_name, sizeof(urle_user_name), user_name) != 0) {
return -1;
}
if (rc_url_encode(urle_login_token, sizeof(urle_login_token), login_token) != 0) {
return -1;
}
written = snprintf(
buffer,
size,
"http://retroachievements.org/dorequest.php?r=unlocks&u=%s&t=%s&g=%u&h=%d",
urle_user_name,
urle_login_token,
gameid,
hardcore ? 1 : 0
);
return (size_t)written >= size ? -1 : 0;
}
int rc_url_post_playing(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned gameid) {
char urle_user_name[64];
char urle_login_token[64];
int written;
if (rc_url_encode(urle_user_name, sizeof(urle_user_name), user_name) != 0) {
return -1;
}
if (rc_url_encode(urle_login_token, sizeof(urle_login_token), login_token) != 0) {
return -1;
}
written = snprintf(
buffer,
size,
"http://retroachievements.org/dorequest.php?r=postactivity&u=%s&t=%s&a=3&m=%u",
urle_user_name,
urle_login_token,
gameid
);
return (size_t)written >= size ? -1 : 0;
}
static int rc_url_append_param_equals(char* buffer, size_t buffer_size, size_t buffer_offset, const char* param)
{
int written = 0;
size_t param_len;
if (buffer_offset >= buffer_size)
return -1;
if (buffer_offset) {
buffer += buffer_offset;
buffer_size -= buffer_offset;
if (buffer[-1] != '?') {
*buffer++ = '&';
buffer_size--;
written = 1;
}
}
param_len = strlen(param);
if (param_len + 1 >= buffer_size)
return -1;
memcpy(buffer, param, param_len);
buffer[param_len] = '=';
written += (int)param_len + 1;
return written + (int)buffer_offset;
}
static int rc_url_append_unum(char* buffer, size_t buffer_size, size_t* buffer_offset, const char* param, unsigned value)
{
int written = rc_url_append_param_equals(buffer, buffer_size, *buffer_offset, param);
if (written > 0) {
char num[16];
int chars = sprintf(num, "%u", value);
if (chars + written < (int)buffer_size)
{
memcpy(&buffer[written], num, chars + 1);
*buffer_offset = written + chars;
return 0;
}
}
return -1;
}
static int rc_url_append_str(char* buffer, size_t buffer_size, size_t* buffer_offset, const char* param, const char* value)
{
int written = rc_url_append_param_equals(buffer, buffer_size, *buffer_offset, param);
if (written > 0)
{
buffer += written;
buffer_size -= written;
if (rc_url_encode(buffer, buffer_size, value) == 0)
{
written += (int)strlen(buffer);
*buffer_offset = written;
return 0;
}
}
return -1;
}
static int rc_url_build_dorequest(char* url_buffer, size_t url_buffer_size, size_t* buffer_offset,
const char* api, const char* user_name)
{
const char* base_url = "http://retroachievements.org/dorequest.php";
size_t written = strlen(base_url);
int failure = 0;
if (url_buffer_size < written + 1)
return -1;
memcpy(url_buffer, base_url, written);
url_buffer[written++] = '?';
failure |= rc_url_append_str(url_buffer, url_buffer_size, &written, "r", api);
failure |= rc_url_append_str(url_buffer, url_buffer_size, &written, "u", user_name);
*buffer_offset += written;
return failure;
}
int rc_url_ping(char* url_buffer, size_t url_buffer_size, char* post_buffer, size_t post_buffer_size,
const char* user_name, const char* login_token, unsigned gameid, const char* rich_presence)
{
size_t written = 0;
int failure = rc_url_build_dorequest(url_buffer, url_buffer_size, &written, "ping", user_name);
failure |= rc_url_append_unum(url_buffer, url_buffer_size, &written, "g", gameid);
written = 0;
failure |= rc_url_append_str(post_buffer, post_buffer_size, &written, "t", login_token);
if (rich_presence && *rich_presence)
failure |= rc_url_append_str(post_buffer, post_buffer_size, &written, "m", rich_presence);
if (failure) {
if (url_buffer_size)
url_buffer[0] = '\0';
if (post_buffer_size)
post_buffer[0] = '\0';
}
return failure;
}