mirror of
https://github.com/WinampDesktop/winamp.git
synced 2025-06-18 06:25:46 -04:00
dep: Add rcheevos
This commit is contained in:
192
dep/rcheevos/src/rcheevos/alloc.c
Normal file
192
dep/rcheevos/src/rcheevos/alloc.c
Normal 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";
|
||||
}
|
||||
}
|
62
dep/rcheevos/src/rcheevos/compat.c
Normal file
62
dep/rcheevos/src/rcheevos/compat.c
Normal 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;
|
||||
}
|
261
dep/rcheevos/src/rcheevos/condition.c
Normal file
261
dep/rcheevos/src/rcheevos/condition.c
Normal 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;
|
||||
}
|
369
dep/rcheevos/src/rcheevos/condset.c
Normal file
369
dep/rcheevos/src/rcheevos/condset.c
Normal 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;
|
||||
}
|
||||
}
|
659
dep/rcheevos/src/rcheevos/consoleinfo.c
Normal file
659
dep/rcheevos/src/rcheevos/consoleinfo.c
Normal 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;
|
||||
}
|
||||
}
|
155
dep/rcheevos/src/rcheevos/format.c
Normal file
155
dep/rcheevos/src/rcheevos/format.c
Normal 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;
|
||||
}
|
263
dep/rcheevos/src/rcheevos/lboard.c
Normal file
263
dep/rcheevos/src/rcheevos/lboard.c
Normal 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);
|
||||
}
|
225
dep/rcheevos/src/rcheevos/memref.c
Normal file
225
dep/rcheevos/src/rcheevos/memref.c
Normal 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;
|
||||
}
|
||||
}
|
470
dep/rcheevos/src/rcheevos/operand.c
Normal file
470
dep/rcheevos/src/rcheevos/operand.c
Normal 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;
|
||||
}
|
60
dep/rcheevos/src/rcheevos/rc_compat.h
Normal file
60
dep/rcheevos/src/rcheevos/rc_compat.h
Normal 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 */
|
147
dep/rcheevos/src/rcheevos/rc_internal.h
Normal file
147
dep/rcheevos/src/rcheevos/rc_internal.h
Normal 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 */
|
684
dep/rcheevos/src/rcheevos/richpresence.c
Normal file
684
dep/rcheevos/src/rcheevos/richpresence.c
Normal 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);
|
||||
}
|
691
dep/rcheevos/src/rcheevos/runtime.c
Normal file
691
dep/rcheevos/src/rcheevos/runtime.c
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
533
dep/rcheevos/src/rcheevos/runtime_progress.c
Normal file
533
dep/rcheevos/src/rcheevos/runtime_progress.c
Normal 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;
|
||||
}
|
224
dep/rcheevos/src/rcheevos/trigger.c
Normal file
224
dep/rcheevos/src/rcheevos/trigger.c
Normal 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;
|
||||
}
|
310
dep/rcheevos/src/rcheevos/value.c
Normal file
310
dep/rcheevos/src/rcheevos/value.c
Normal 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;
|
||||
}
|
||||
}
|
782
dep/rcheevos/src/rhash/cdreader.c
Normal file
782
dep/rcheevos/src/rhash/cdreader.c
Normal 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);
|
||||
}
|
1993
dep/rcheevos/src/rhash/hash.c
Normal file
1993
dep/rcheevos/src/rhash/hash.c
Normal file
File diff suppressed because it is too large
Load Diff
381
dep/rcheevos/src/rhash/md5.c
Normal file
381
dep/rcheevos/src/rhash/md5.c
Normal 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));
|
||||
}
|
91
dep/rcheevos/src/rhash/md5.h
Normal file
91
dep/rcheevos/src/rhash/md5.h
Normal 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
373
dep/rcheevos/src/rurl/url.c
Normal 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;
|
||||
}
|
Reference in New Issue
Block a user