mirror of
https://github.com/libretro/RetroArch
synced 2025-03-30 16:20:27 +00:00
Merge pull request #5586 from leiradel/master
Moved all cheevos_var_t related stuff to their own files
This commit is contained in:
commit
9279c33711
@ -1474,6 +1474,7 @@ ifeq ($(HAVE_NETWORKING), 1)
|
||||
ifeq ($(HAVE_CHEEVOS), 1)
|
||||
DEFINES += -DHAVE_CHEEVOS
|
||||
OBJ += cheevos/cheevos.o \
|
||||
cheevos/var.o \
|
||||
$(LIBRETRO_COMM_DIR)/utils/md5.o
|
||||
endif
|
||||
|
||||
|
@ -36,6 +36,7 @@
|
||||
#endif
|
||||
|
||||
#include "cheevos.h"
|
||||
#include "var.h"
|
||||
|
||||
#include "../command.h"
|
||||
#include "../dynamic.h"
|
||||
@ -62,9 +63,6 @@
|
||||
/* Define this macro to remove HTTP timeouts. */
|
||||
#undef CHEEVOS_NO_TIMEOUT
|
||||
|
||||
/* Define this macro to get extra-verbose log for cheevos. */
|
||||
#undef CHEEVOS_VERBOSE
|
||||
|
||||
/* Define this macro to load a JSON file from disk instead of downloading
|
||||
* from retroachievements.org. */
|
||||
#undef CHEEVOS_JSON_OVERRIDE
|
||||
@ -100,60 +98,6 @@
|
||||
#define CHEEVOS_EIGHT_MB ( 8 * 1024 * 1024)
|
||||
#define CHEEVOS_SIZE_LIMIT (64 * 1024 * 1024)
|
||||
|
||||
enum
|
||||
{
|
||||
/* Don't change those, the values match the console IDs
|
||||
* at retroachievements.org. */
|
||||
CHEEVOS_CONSOLE_MEGA_DRIVE = 1,
|
||||
CHEEVOS_CONSOLE_NINTENDO_64 = 2,
|
||||
CHEEVOS_CONSOLE_SUPER_NINTENDO = 3,
|
||||
CHEEVOS_CONSOLE_GAMEBOY = 4,
|
||||
CHEEVOS_CONSOLE_GAMEBOY_ADVANCE = 5,
|
||||
CHEEVOS_CONSOLE_GAMEBOY_COLOR = 6,
|
||||
CHEEVOS_CONSOLE_NINTENDO = 7,
|
||||
CHEEVOS_CONSOLE_PC_ENGINE = 8,
|
||||
CHEEVOS_CONSOLE_SEGA_CD = 9,
|
||||
CHEEVOS_CONSOLE_SEGA_32X = 10,
|
||||
CHEEVOS_CONSOLE_MASTER_SYSTEM = 11
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
CHEEVOS_VAR_SIZE_BIT_0 = 0,
|
||||
CHEEVOS_VAR_SIZE_BIT_1,
|
||||
CHEEVOS_VAR_SIZE_BIT_2,
|
||||
CHEEVOS_VAR_SIZE_BIT_3,
|
||||
CHEEVOS_VAR_SIZE_BIT_4,
|
||||
CHEEVOS_VAR_SIZE_BIT_5,
|
||||
CHEEVOS_VAR_SIZE_BIT_6,
|
||||
CHEEVOS_VAR_SIZE_BIT_7,
|
||||
CHEEVOS_VAR_SIZE_NIBBLE_LOWER,
|
||||
CHEEVOS_VAR_SIZE_NIBBLE_UPPER,
|
||||
/* Byte, */
|
||||
CHEEVOS_VAR_SIZE_EIGHT_BITS, /* =Byte, */
|
||||
CHEEVOS_VAR_SIZE_SIXTEEN_BITS,
|
||||
CHEEVOS_VAR_SIZE_THIRTYTWO_BITS,
|
||||
|
||||
CHEEVOS_VAR_SIZE_LAST
|
||||
}; /* cheevos_var_t.size */
|
||||
|
||||
enum
|
||||
{
|
||||
/* compare to the value of a live address in RAM */
|
||||
CHEEVOS_VAR_TYPE_ADDRESS = 0,
|
||||
|
||||
/* a number. assume 32 bit */
|
||||
CHEEVOS_VAR_TYPE_VALUE_COMP,
|
||||
|
||||
/* the value last known at this address. */
|
||||
CHEEVOS_VAR_TYPE_DELTA_MEM,
|
||||
|
||||
/* a custom user-set variable */
|
||||
CHEEVOS_VAR_TYPE_DYNAMIC_VAR,
|
||||
|
||||
CHEEVOS_VAR_TYPE_LAST
|
||||
}; /* cheevos_var_t.type */
|
||||
|
||||
enum
|
||||
{
|
||||
CHEEVOS_COND_OP_EQUALS = 0,
|
||||
@ -918,44 +862,6 @@ static int cheevos_count_cheevos(const char *json,
|
||||
Parse the MemAddr field.
|
||||
*****************************************************************************/
|
||||
|
||||
static unsigned cheevos_prefix_to_comp_size(char prefix)
|
||||
{
|
||||
/* Careful not to use ABCDEF here, this denotes part of an actual variable! */
|
||||
|
||||
switch( toupper( (unsigned char)prefix ) )
|
||||
{
|
||||
case 'M':
|
||||
return CHEEVOS_VAR_SIZE_BIT_0;
|
||||
case 'N':
|
||||
return CHEEVOS_VAR_SIZE_BIT_1;
|
||||
case 'O':
|
||||
return CHEEVOS_VAR_SIZE_BIT_2;
|
||||
case 'P':
|
||||
return CHEEVOS_VAR_SIZE_BIT_3;
|
||||
case 'Q':
|
||||
return CHEEVOS_VAR_SIZE_BIT_4;
|
||||
case 'R':
|
||||
return CHEEVOS_VAR_SIZE_BIT_5;
|
||||
case 'S':
|
||||
return CHEEVOS_VAR_SIZE_BIT_6;
|
||||
case 'T':
|
||||
return CHEEVOS_VAR_SIZE_BIT_7;
|
||||
case 'L':
|
||||
return CHEEVOS_VAR_SIZE_NIBBLE_LOWER;
|
||||
case 'U':
|
||||
return CHEEVOS_VAR_SIZE_NIBBLE_UPPER;
|
||||
case 'H':
|
||||
return CHEEVOS_VAR_SIZE_EIGHT_BITS;
|
||||
case 'X':
|
||||
return CHEEVOS_VAR_SIZE_THIRTYTWO_BITS;
|
||||
default:
|
||||
case ' ':
|
||||
break;
|
||||
}
|
||||
|
||||
return CHEEVOS_VAR_SIZE_SIXTEEN_BITS;
|
||||
}
|
||||
|
||||
static unsigned cheevos_read_hits(const char **memaddr)
|
||||
{
|
||||
char *end = NULL;
|
||||
@ -1022,203 +928,6 @@ static unsigned cheevos_parse_operator(const char **memaddr)
|
||||
return op;
|
||||
}
|
||||
|
||||
static size_t cheevos_reduce(size_t addr, size_t mask)
|
||||
{
|
||||
while (mask)
|
||||
{
|
||||
size_t tmp = (mask - 1) & ~mask;
|
||||
addr = (addr & tmp) | ((addr >> 1) & ~tmp);
|
||||
mask = (mask & (mask - 1)) >> 1;
|
||||
}
|
||||
|
||||
return addr;
|
||||
}
|
||||
|
||||
static size_t cheevos_highest_bit(size_t n)
|
||||
{
|
||||
n |= n >> 1;
|
||||
n |= n >> 2;
|
||||
n |= n >> 4;
|
||||
n |= n >> 8;
|
||||
n |= n >> 16;
|
||||
|
||||
return n ^ (n >> 1);
|
||||
}
|
||||
|
||||
void cheevos_parse_guest_addr(cheevos_var_t *var, unsigned value)
|
||||
{
|
||||
rarch_system_info_t *system = runloop_get_system_info();
|
||||
|
||||
var->bank_id = -1;
|
||||
var->value = value;
|
||||
|
||||
switch (cheevos_locals.console_id)
|
||||
{
|
||||
case CHEEVOS_CONSOLE_NINTENDO:
|
||||
if (var->value >= 0x0800 && var->value < 0x2000)
|
||||
{
|
||||
#ifdef CHEEVOS_VERBOSE
|
||||
RARCH_LOG("[CHEEVOS]: NES memory address in mirrorred RAM %X, adjusted to %X\n", var->value, var->value & 0x07ff);
|
||||
#endif
|
||||
var->value &= 0x07ff;
|
||||
}
|
||||
break;
|
||||
|
||||
case CHEEVOS_CONSOLE_GAMEBOY_COLOR:
|
||||
if (var->value >= 0xe000 && var->value <= 0xfdff)
|
||||
{
|
||||
#ifdef CHEEVOS_VERBOSE
|
||||
RARCH_LOG("[CHEEVOS]: GBC memory address in echo RAM %X, adjusted to %X\n", var->value, var->value - 0x2000);
|
||||
#endif
|
||||
var->value -= 0x2000;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (system->mmaps.num_descriptors != 0)
|
||||
{
|
||||
const rarch_memory_descriptor_t *desc = NULL;
|
||||
const rarch_memory_descriptor_t *end = NULL;
|
||||
|
||||
switch (cheevos_locals.console_id)
|
||||
{
|
||||
/* Patch the address to correctly map it to the mmaps */
|
||||
case CHEEVOS_CONSOLE_GAMEBOY_ADVANCE:
|
||||
if (var->value < 0x8000) /* Internal RAM */
|
||||
{
|
||||
#ifdef CHEEVOS_VERBOSE
|
||||
RARCH_LOG("[CHEEVOS]: GBA memory address %X adjusted to %X\n", var->value, var->value + 0x3000000);
|
||||
#endif
|
||||
var->value += 0x3000000;
|
||||
}
|
||||
else /* Work RAM */
|
||||
{
|
||||
#ifdef CHEEVOS_VERBOSE
|
||||
RARCH_LOG("[CHEEVOS]: GBA memory address %X adjusted to %X\n", var->value, var->value + 0x2000000 - 0x8000);
|
||||
#endif
|
||||
var->value += 0x2000000 - 0x8000;
|
||||
}
|
||||
break;
|
||||
|
||||
case CHEEVOS_CONSOLE_PC_ENGINE:
|
||||
#ifdef CHEEVOS_VERBOSE
|
||||
RARCH_LOG("[CHEEVOS]: PCE memory address %X adjusted to %X\n", var->value, var->value + 0x1f0000);
|
||||
#endif
|
||||
var->value += 0x1f0000;
|
||||
break;
|
||||
|
||||
case CHEEVOS_CONSOLE_SUPER_NINTENDO:
|
||||
if (var->value < 0x020000) /* Work RAM */
|
||||
{
|
||||
#ifdef CHEEVOS_VERBOSE
|
||||
RARCH_LOG("[CHEEVOS]: SNES memory address %X adjusted to %X\n", var->value, var->value + 0x7e0000);
|
||||
#endif
|
||||
var->value += 0x7e0000;
|
||||
}
|
||||
else /* Save RAM */
|
||||
{
|
||||
#ifdef CHEEVOS_VERBOSE
|
||||
RARCH_LOG("[CHEEVOS]: SNES memory address %X adjusted to %X\n", var->value, var->value + 0x006000 - 0x020000);
|
||||
#endif
|
||||
var->value += 0x006000 - 0x020000;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
desc = system->mmaps.descriptors;
|
||||
end = desc + system->mmaps.num_descriptors;
|
||||
|
||||
for (; desc < end; desc++)
|
||||
{
|
||||
if (((desc->core.start ^ var->value) & desc->core.select) == 0)
|
||||
{
|
||||
unsigned addr = var->value;
|
||||
var->bank_id = (int)(desc - system->mmaps.descriptors);
|
||||
var->value = (unsigned)cheevos_reduce(
|
||||
(var->value - desc->core.start) & desc->disconnect_mask,
|
||||
desc->core.disconnect);
|
||||
|
||||
if (var->value >= desc->core.len)
|
||||
var->value -= cheevos_highest_bit(var->value);
|
||||
|
||||
var->value += desc->core.offset;
|
||||
|
||||
#ifdef CHEEVOS_VERBOSE
|
||||
RARCH_LOG("[CHEEVOS]: Address %X set to descriptor %d at offset %X\n", addr, var->bank_id + 1, var->value);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(cheevos_locals.meminfo); i++)
|
||||
{
|
||||
if (var->value < cheevos_locals.meminfo[i].size)
|
||||
{
|
||||
var->bank_id = i;
|
||||
break;
|
||||
}
|
||||
|
||||
/* HACK subtract the correct amount of bytes to reach the save RAM */
|
||||
if (i == 0 && cheevos_locals.console_id == CHEEVOS_CONSOLE_NINTENDO)
|
||||
var->value -= 0x6000;
|
||||
else
|
||||
var->value -= cheevos_locals.meminfo[i].size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void cheevos_parse_var(cheevos_var_t *var, const char **memaddr)
|
||||
{
|
||||
char *end = NULL;
|
||||
const char *str = *memaddr;
|
||||
unsigned base = 16;
|
||||
|
||||
if (toupper((unsigned char)*str) == 'D' && str[1] == '0' && toupper((unsigned char)str[2]) == 'X')
|
||||
{
|
||||
/* d0x + 4 hex digits */
|
||||
str += 3;
|
||||
var->type = CHEEVOS_VAR_TYPE_DELTA_MEM;
|
||||
}
|
||||
else if (*str == '0' && toupper((unsigned char)str[1]) == 'X')
|
||||
{
|
||||
/* 0x + 4 hex digits */
|
||||
str += 2;
|
||||
var->type = CHEEVOS_VAR_TYPE_ADDRESS;
|
||||
}
|
||||
else
|
||||
{
|
||||
var->type = CHEEVOS_VAR_TYPE_VALUE_COMP;
|
||||
|
||||
if (toupper((unsigned char)*str) == 'H')
|
||||
str++;
|
||||
else
|
||||
{
|
||||
if (toupper((unsigned char)*str) == 'V')
|
||||
str++;
|
||||
|
||||
base = 10;
|
||||
}
|
||||
}
|
||||
|
||||
if (var->type != CHEEVOS_VAR_TYPE_VALUE_COMP)
|
||||
{
|
||||
var->size = cheevos_prefix_to_comp_size(*str);
|
||||
|
||||
if (var->size != CHEEVOS_VAR_SIZE_SIXTEEN_BITS)
|
||||
str++;
|
||||
}
|
||||
|
||||
var->value = (unsigned)strtol(str, &end, base);
|
||||
*memaddr = end;
|
||||
}
|
||||
|
||||
static void cheevos_parse_cond(cheevos_cond_t *cond, const char **memaddr)
|
||||
{
|
||||
const char* str = *memaddr;
|
||||
@ -1253,9 +962,9 @@ static void cheevos_parse_cond(cheevos_cond_t *cond, const char **memaddr)
|
||||
str += skip;
|
||||
}
|
||||
|
||||
cheevos_parse_var(&cond->source, &str);
|
||||
cheevos_var_parse(&cond->source, &str);
|
||||
cond->op = cheevos_parse_operator(&str);
|
||||
cheevos_parse_var(&cond->target, &str);
|
||||
cheevos_var_parse(&cond->target, &str);
|
||||
cond->curr_hits = 0;
|
||||
cond->req_hits = cheevos_read_hits(&str);
|
||||
|
||||
@ -1450,7 +1159,7 @@ static int cheevos_parse_expression(cheevos_expr_t *expr, const char* mem)
|
||||
|
||||
for (i = 0, aux = mem; i < expr->count; i++)
|
||||
{
|
||||
cheevos_parse_var(&expr->terms[i].var, &aux);
|
||||
cheevos_var_parse(&expr->terms[i].var, &aux);
|
||||
|
||||
if (*aux != '*')
|
||||
{
|
||||
@ -1816,104 +1525,10 @@ error:
|
||||
Test all the achievements (call once per frame).
|
||||
*****************************************************************************/
|
||||
|
||||
uint8_t *cheevos_get_memory(const cheevos_var_t *var)
|
||||
{
|
||||
uint8_t *memory = NULL;
|
||||
|
||||
if (var->bank_id >= 0)
|
||||
{
|
||||
rarch_system_info_t *system = runloop_get_system_info();
|
||||
|
||||
if (system->mmaps.num_descriptors != 0)
|
||||
memory = (uint8_t *)system->mmaps.descriptors[var->bank_id].core.ptr;
|
||||
else
|
||||
memory = (uint8_t *)cheevos_locals.meminfo[var->bank_id].data;
|
||||
|
||||
if (memory)
|
||||
memory += var->value;
|
||||
}
|
||||
|
||||
return memory;
|
||||
}
|
||||
|
||||
static unsigned cheevos_get_var_value(cheevos_var_t *var)
|
||||
{
|
||||
if (var->type == CHEEVOS_VAR_TYPE_VALUE_COMP)
|
||||
return var->value;
|
||||
|
||||
if ( var->type == CHEEVOS_VAR_TYPE_ADDRESS
|
||||
|| var->type == CHEEVOS_VAR_TYPE_DELTA_MEM)
|
||||
{
|
||||
const uint8_t *memory = cheevos_get_memory(var);
|
||||
unsigned live_val = 0;
|
||||
|
||||
if (memory)
|
||||
{
|
||||
live_val = memory[0];
|
||||
|
||||
switch (var->size)
|
||||
{
|
||||
case CHEEVOS_VAR_SIZE_BIT_0:
|
||||
live_val &= 1;
|
||||
break;
|
||||
case CHEEVOS_VAR_SIZE_BIT_1:
|
||||
live_val = (live_val >> 1) & 1;
|
||||
break;
|
||||
case CHEEVOS_VAR_SIZE_BIT_2:
|
||||
live_val = (live_val >> 2) & 1;
|
||||
break;
|
||||
case CHEEVOS_VAR_SIZE_BIT_3:
|
||||
live_val = (live_val >> 3) & 1;
|
||||
break;
|
||||
case CHEEVOS_VAR_SIZE_BIT_4:
|
||||
live_val = (live_val >> 4) & 1;
|
||||
break;
|
||||
case CHEEVOS_VAR_SIZE_BIT_5:
|
||||
live_val = (live_val >> 5) & 1;
|
||||
break;
|
||||
case CHEEVOS_VAR_SIZE_BIT_6:
|
||||
live_val = (live_val >> 6) & 1;
|
||||
break;
|
||||
case CHEEVOS_VAR_SIZE_BIT_7:
|
||||
live_val = (live_val >> 7) & 1;
|
||||
break;
|
||||
case CHEEVOS_VAR_SIZE_NIBBLE_LOWER:
|
||||
live_val &= 0x0f;
|
||||
break;
|
||||
case CHEEVOS_VAR_SIZE_NIBBLE_UPPER:
|
||||
live_val = (live_val >> 4) & 0x0f;
|
||||
break;
|
||||
case CHEEVOS_VAR_SIZE_EIGHT_BITS:
|
||||
break;
|
||||
case CHEEVOS_VAR_SIZE_SIXTEEN_BITS:
|
||||
live_val |= memory[1] << 8;
|
||||
break;
|
||||
case CHEEVOS_VAR_SIZE_THIRTYTWO_BITS:
|
||||
live_val |= memory[1] << 8;
|
||||
live_val |= memory[2] << 16;
|
||||
live_val |= memory[3] << 24;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (var->type == CHEEVOS_VAR_TYPE_DELTA_MEM)
|
||||
{
|
||||
unsigned previous = var->previous;
|
||||
var->previous = live_val;
|
||||
return previous;
|
||||
}
|
||||
|
||||
return live_val;
|
||||
}
|
||||
|
||||
/* We shouldn't get here... */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cheevos_test_condition(cheevos_cond_t *cond)
|
||||
{
|
||||
unsigned sval = cheevos_get_var_value(&cond->source) + cheevos_locals.add_buffer;
|
||||
unsigned tval = cheevos_get_var_value(&cond->target);
|
||||
unsigned sval = cheevos_var_get_value(&cond->source) + cheevos_locals.add_buffer;
|
||||
unsigned tval = cheevos_var_get_value(&cond->target);
|
||||
|
||||
switch (cond->op)
|
||||
{
|
||||
@ -1975,14 +1590,14 @@ static int cheevos_test_cond_set(const cheevos_condset_t *condset,
|
||||
|
||||
if (cond->type == CHEEVOS_COND_TYPE_ADD_SOURCE)
|
||||
{
|
||||
cheevos_locals.add_buffer += cheevos_get_var_value(&cond->source);
|
||||
cheevos_locals.add_buffer += cheevos_var_get_value(&cond->source);
|
||||
set_valid &= 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cond->type == CHEEVOS_COND_TYPE_SUB_SOURCE)
|
||||
{
|
||||
cheevos_locals.add_buffer -= cheevos_get_var_value(&cond->source);
|
||||
cheevos_locals.add_buffer -= cheevos_var_get_value(&cond->source);
|
||||
set_valid &= 1;
|
||||
continue;
|
||||
}
|
||||
@ -2276,7 +1891,7 @@ static int cheevos_expr_value(cheevos_expr_t* expr)
|
||||
|
||||
for (i = expr->count; i != 0; i--, term++)
|
||||
{
|
||||
value += cheevos_get_var_value(&term->var) * term->multiplier;
|
||||
value += cheevos_var_get_value(&term->var) * term->multiplier;
|
||||
}
|
||||
|
||||
return value;
|
||||
@ -2721,7 +2336,7 @@ static void cheevos_patch_addresses(cheevoset_t* set)
|
||||
{
|
||||
case CHEEVOS_VAR_TYPE_ADDRESS:
|
||||
case CHEEVOS_VAR_TYPE_DELTA_MEM:
|
||||
cheevos_parse_guest_addr(&cond->source, cond->source.value);
|
||||
cheevos_var_patch_addr(&cond->source, cheevos_locals.console_id);
|
||||
#ifdef CHEEVOS_DUMP_ADDRS
|
||||
RARCH_LOG("[CHEEVOS]: var %03d:%08X\n", cond->source.bank_id + 1, cond->source.value);
|
||||
#endif
|
||||
@ -2735,7 +2350,7 @@ static void cheevos_patch_addresses(cheevoset_t* set)
|
||||
{
|
||||
case CHEEVOS_VAR_TYPE_ADDRESS:
|
||||
case CHEEVOS_VAR_TYPE_DELTA_MEM:
|
||||
cheevos_parse_guest_addr(&cond->target, cond->target.value);
|
||||
cheevos_var_patch_addr(&cond->target, cheevos_locals.console_id);
|
||||
#ifdef CHEEVOS_DUMP_ADDRS
|
||||
RARCH_LOG("[CHEEVOS]: var %03d:%08X\n", cond->target.bank_id + 1, cond->target.value);
|
||||
#endif
|
||||
@ -2788,6 +2403,11 @@ bool cheevos_get_support_cheevos(void)
|
||||
return cheevos_locals.core_supports;
|
||||
}
|
||||
|
||||
cheevos_console_t cheevos_get_console(void)
|
||||
{
|
||||
return cheevos_locals.console_id;
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint8_t id[4]; /* NES^Z */
|
||||
@ -2861,7 +2481,7 @@ static int cheevos_iterate(coro_t* coro)
|
||||
size_t to_read = 4096;
|
||||
uint8_t *buffer = NULL;
|
||||
const char *end = NULL;
|
||||
|
||||
|
||||
enum
|
||||
{
|
||||
/* Negative values because CORO_SUB generates positive values */
|
||||
|
@ -23,6 +23,19 @@
|
||||
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
/*****************************************************************************
|
||||
Setup - mainly for debugging
|
||||
*****************************************************************************/
|
||||
|
||||
/* Define this macro to get extra-verbose log for cheevos. */
|
||||
#undef CHEEVOS_VERBOSE
|
||||
|
||||
/*****************************************************************************
|
||||
End of setup
|
||||
*****************************************************************************/
|
||||
|
||||
#define CHEEVOS_TAG "[CHEEVOS]: "
|
||||
|
||||
typedef struct cheevos_ctx_desc
|
||||
{
|
||||
unsigned idx;
|
||||
@ -30,14 +43,24 @@ typedef struct cheevos_ctx_desc
|
||||
size_t len;
|
||||
} cheevos_ctx_desc_t;
|
||||
|
||||
typedef struct
|
||||
typedef enum
|
||||
{
|
||||
unsigned size;
|
||||
unsigned type;
|
||||
int bank_id;
|
||||
unsigned value;
|
||||
unsigned previous;
|
||||
} cheevos_var_t;
|
||||
/* Don't change those, the values match the console IDs
|
||||
* at retroachievements.org. */
|
||||
CHEEVOS_CONSOLE_MEGA_DRIVE = 1,
|
||||
CHEEVOS_CONSOLE_NINTENDO_64 = 2,
|
||||
CHEEVOS_CONSOLE_SUPER_NINTENDO = 3,
|
||||
CHEEVOS_CONSOLE_GAMEBOY = 4,
|
||||
CHEEVOS_CONSOLE_GAMEBOY_ADVANCE = 5,
|
||||
CHEEVOS_CONSOLE_GAMEBOY_COLOR = 6,
|
||||
CHEEVOS_CONSOLE_NINTENDO = 7,
|
||||
CHEEVOS_CONSOLE_PC_ENGINE = 8,
|
||||
CHEEVOS_CONSOLE_SEGA_CD = 9,
|
||||
CHEEVOS_CONSOLE_SEGA_32X = 10,
|
||||
CHEEVOS_CONSOLE_MASTER_SYSTEM = 11,
|
||||
CHEEVOS_CONSOLE_XBOX_360 = 12,
|
||||
CHEEVOS_CONSOLE_ATARI_LYNX = 13
|
||||
} cheevos_console_t;
|
||||
|
||||
bool cheevos_load(const void *data);
|
||||
|
||||
@ -61,9 +84,7 @@ void cheevos_set_support_cheevos(bool state);
|
||||
|
||||
bool cheevos_get_support_cheevos(void);
|
||||
|
||||
void cheevos_parse_guest_addr(cheevos_var_t *var, unsigned value);
|
||||
|
||||
uint8_t *cheevos_get_memory(const cheevos_var_t *var);
|
||||
cheevos_console_t cheevos_get_console(void);
|
||||
|
||||
extern bool cheevos_loaded;
|
||||
extern int cheats_are_enabled;
|
||||
|
410
cheevos/var.c
Normal file
410
cheevos/var.c
Normal file
@ -0,0 +1,410 @@
|
||||
/* RetroArch - A frontend for libretro.
|
||||
* Copyright (C) 2015-2017 - Andre Leiradella
|
||||
*
|
||||
* RetroArch is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with RetroArch.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <libretro.h>
|
||||
|
||||
#include "var.h"
|
||||
|
||||
#include "../retroarch.h"
|
||||
#include "../core.h"
|
||||
#include "../verbosity.h"
|
||||
|
||||
#ifdef CHEEVOS_VERBOSE
|
||||
#define CHEEVOS_LOG RARCH_LOG
|
||||
#else
|
||||
#define CHEEVOS_LOG(...)
|
||||
#endif
|
||||
|
||||
/*****************************************************************************
|
||||
Parsing
|
||||
*****************************************************************************/
|
||||
|
||||
static cheevos_var_size_t cheevos_var_parse_prefix(const char** memaddr)
|
||||
{
|
||||
/* Careful not to use ABCDEF here, this denotes part of an actual variable! */
|
||||
const char* str = *memaddr;
|
||||
cheevos_var_size_t size;
|
||||
|
||||
switch (toupper((unsigned char)*str++))
|
||||
{
|
||||
case 'M':
|
||||
size = CHEEVOS_VAR_SIZE_BIT_0;
|
||||
break;
|
||||
case 'N':
|
||||
size = CHEEVOS_VAR_SIZE_BIT_1;
|
||||
break;
|
||||
case 'O':
|
||||
size = CHEEVOS_VAR_SIZE_BIT_2;
|
||||
break;
|
||||
case 'P':
|
||||
size = CHEEVOS_VAR_SIZE_BIT_3;
|
||||
break;
|
||||
case 'Q':
|
||||
size = CHEEVOS_VAR_SIZE_BIT_4;
|
||||
break;
|
||||
case 'R':
|
||||
size = CHEEVOS_VAR_SIZE_BIT_5;
|
||||
break;
|
||||
case 'S':
|
||||
size = CHEEVOS_VAR_SIZE_BIT_6;
|
||||
break;
|
||||
case 'T':
|
||||
size = CHEEVOS_VAR_SIZE_BIT_7;
|
||||
break;
|
||||
case 'L':
|
||||
size = CHEEVOS_VAR_SIZE_NIBBLE_LOWER;
|
||||
break;
|
||||
case 'U':
|
||||
size = CHEEVOS_VAR_SIZE_NIBBLE_UPPER;
|
||||
break;
|
||||
case 'H':
|
||||
size = CHEEVOS_VAR_SIZE_EIGHT_BITS;
|
||||
break;
|
||||
case 'X':
|
||||
size = CHEEVOS_VAR_SIZE_THIRTYTWO_BITS;
|
||||
break;
|
||||
default:
|
||||
str--;
|
||||
/* fall through */
|
||||
case ' ':
|
||||
size = CHEEVOS_VAR_SIZE_SIXTEEN_BITS;
|
||||
break;
|
||||
}
|
||||
|
||||
*memaddr = str;
|
||||
return size;
|
||||
}
|
||||
|
||||
static size_t cheevos_var_reduce(size_t addr, size_t mask)
|
||||
{
|
||||
while (mask)
|
||||
{
|
||||
size_t tmp = (mask - 1) & ~mask;
|
||||
addr = (addr & tmp) | ((addr >> 1) & ~tmp);
|
||||
mask = (mask & (mask - 1)) >> 1;
|
||||
}
|
||||
|
||||
return addr;
|
||||
}
|
||||
|
||||
static size_t cheevos_var_highest_bit(size_t n)
|
||||
{
|
||||
n |= n >> 1;
|
||||
n |= n >> 2;
|
||||
n |= n >> 4;
|
||||
n |= n >> 8;
|
||||
n |= n >> 16;
|
||||
|
||||
return n ^ (n >> 1);
|
||||
}
|
||||
|
||||
void cheevos_var_parse(cheevos_var_t* var, const char** memaddr)
|
||||
{
|
||||
char *end = NULL;
|
||||
const char *str = *memaddr;
|
||||
unsigned base = 16;
|
||||
|
||||
if (toupper((unsigned char)*str) == 'D' && str[1] == '0' && toupper((unsigned char)str[2]) == 'X')
|
||||
{
|
||||
/* d0x + 4 hex digits */
|
||||
str += 3;
|
||||
var->type = CHEEVOS_VAR_TYPE_DELTA_MEM;
|
||||
}
|
||||
else if (*str == '0' && toupper((unsigned char)str[1]) == 'X')
|
||||
{
|
||||
/* 0x + 4 hex digits */
|
||||
str += 2;
|
||||
var->type = CHEEVOS_VAR_TYPE_ADDRESS;
|
||||
}
|
||||
else
|
||||
{
|
||||
var->type = CHEEVOS_VAR_TYPE_VALUE_COMP;
|
||||
|
||||
if (toupper((unsigned char)*str) == 'H')
|
||||
str++;
|
||||
else
|
||||
{
|
||||
if (toupper((unsigned char)*str) == 'V')
|
||||
str++;
|
||||
|
||||
base = 10;
|
||||
}
|
||||
}
|
||||
|
||||
if (var->type != CHEEVOS_VAR_TYPE_VALUE_COMP)
|
||||
{
|
||||
var->size = cheevos_var_parse_prefix(&str);
|
||||
}
|
||||
|
||||
var->value = (unsigned)strtol(str, &end, base);
|
||||
*memaddr = end;
|
||||
}
|
||||
|
||||
void cheevos_var_patch_addr(cheevos_var_t* var, cheevos_console_t console)
|
||||
{
|
||||
rarch_system_info_t *system = runloop_get_system_info();
|
||||
|
||||
var->bank_id = -1;
|
||||
|
||||
if (console == CHEEVOS_CONSOLE_NINTENDO)
|
||||
{
|
||||
if (var->value >= 0x0800 && var->value < 0x2000)
|
||||
{
|
||||
CHEEVOS_LOG(CHEEVOS_TAG "NES memory address in mirrorred RAM %X, adjusted to %X\n", var->value, var->value & 0x07ff);
|
||||
var->value &= 0x07ff;
|
||||
}
|
||||
}
|
||||
else if (console == CHEEVOS_CONSOLE_GAMEBOY_COLOR)
|
||||
{
|
||||
if (var->value >= 0xe000 && var->value <= 0xfdff)
|
||||
{
|
||||
CHEEVOS_LOG(CHEEVOS_TAG "GBC memory address in echo RAM %X, adjusted to %X\n", var->value, var->value - 0x2000);
|
||||
var->value -= 0x2000;
|
||||
}
|
||||
}
|
||||
|
||||
if (system->mmaps.num_descriptors != 0)
|
||||
{
|
||||
const rarch_memory_descriptor_t *desc = NULL;
|
||||
const rarch_memory_descriptor_t *end = NULL;
|
||||
|
||||
/* Patch the address to correctly map it to the mmaps */
|
||||
if (console == CHEEVOS_CONSOLE_GAMEBOY_ADVANCE)
|
||||
{
|
||||
if (var->value < 0x8000) /* Internal RAM */
|
||||
{
|
||||
CHEEVOS_LOG(CHEEVOS_TAG "GBA memory address %X adjusted to %X\n", var->value, var->value + 0x3000000);
|
||||
var->value += 0x3000000;
|
||||
}
|
||||
else /* Work RAM */
|
||||
{
|
||||
CHEEVOS_LOG(CHEEVOS_TAG "GBA memory address %X adjusted to %X\n", var->value, var->value + 0x2000000 - 0x8000);
|
||||
var->value += 0x2000000 - 0x8000;
|
||||
}
|
||||
}
|
||||
else if (console == CHEEVOS_CONSOLE_PC_ENGINE)
|
||||
{
|
||||
CHEEVOS_LOG(CHEEVOS_TAG "PCE memory address %X adjusted to %X\n", var->value, var->value + 0x1f0000);
|
||||
var->value += 0x1f0000;
|
||||
}
|
||||
else if (console == CHEEVOS_CONSOLE_SUPER_NINTENDO)
|
||||
{
|
||||
if (var->value < 0x020000) /* Work RAM */
|
||||
{
|
||||
CHEEVOS_LOG(CHEEVOS_TAG "SNES memory address %X adjusted to %X\n", var->value, var->value + 0x7e0000);
|
||||
var->value += 0x7e0000;
|
||||
}
|
||||
else /* Save RAM */
|
||||
{
|
||||
CHEEVOS_LOG(CHEEVOS_TAG "SNES memory address %X adjusted to %X\n", var->value, var->value + 0x006000 - 0x020000);
|
||||
var->value += 0x006000 - 0x020000;
|
||||
}
|
||||
}
|
||||
|
||||
desc = system->mmaps.descriptors;
|
||||
end = desc + system->mmaps.num_descriptors;
|
||||
|
||||
for (; desc < end; desc++)
|
||||
{
|
||||
if (((desc->core.start ^ var->value) & desc->core.select) == 0)
|
||||
{
|
||||
unsigned addr = var->value;
|
||||
var->bank_id = (int)(desc - system->mmaps.descriptors);
|
||||
var->value = (unsigned)cheevos_var_reduce(
|
||||
(var->value - desc->core.start) & desc->disconnect_mask,
|
||||
desc->core.disconnect);
|
||||
|
||||
if (var->value >= desc->core.len)
|
||||
var->value -= cheevos_var_highest_bit(var->value);
|
||||
|
||||
var->value += desc->core.offset;
|
||||
|
||||
CHEEVOS_LOG(CHEEVOS_TAG "address %X set to descriptor %d at offset %X\n", addr, var->bank_id + 1, var->value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
for (i = 0; i < 4; i++)
|
||||
{
|
||||
retro_ctx_memory_info_t meminfo;
|
||||
|
||||
switch (i)
|
||||
{
|
||||
case 0:
|
||||
meminfo.id = RETRO_MEMORY_SYSTEM_RAM;
|
||||
break;
|
||||
case 1:
|
||||
meminfo.id = RETRO_MEMORY_SAVE_RAM;
|
||||
break;
|
||||
case 2:
|
||||
meminfo.id = RETRO_MEMORY_VIDEO_RAM;
|
||||
break;
|
||||
case 3:
|
||||
meminfo.id = RETRO_MEMORY_RTC;
|
||||
break;
|
||||
}
|
||||
|
||||
core_get_memory(&meminfo);
|
||||
|
||||
if (var->value < meminfo.size)
|
||||
{
|
||||
var->bank_id = i;
|
||||
break;
|
||||
}
|
||||
|
||||
/* HACK subtract the correct amount of bytes to reach the save RAM */
|
||||
if (i == 0 && console == CHEEVOS_CONSOLE_NINTENDO)
|
||||
var->value -= 0x6000;
|
||||
else
|
||||
var->value -= meminfo.size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
Testing
|
||||
*****************************************************************************/
|
||||
|
||||
uint8_t* cheevos_var_get_memory(const cheevos_var_t* var)
|
||||
{
|
||||
uint8_t* memory = NULL;
|
||||
|
||||
if (var->bank_id >= 0)
|
||||
{
|
||||
rarch_system_info_t* system = runloop_get_system_info();
|
||||
|
||||
if (system->mmaps.num_descriptors != 0)
|
||||
memory = (uint8_t*)system->mmaps.descriptors[var->bank_id].core.ptr;
|
||||
else
|
||||
{
|
||||
retro_ctx_memory_info_t meminfo;
|
||||
|
||||
switch (var->bank_id)
|
||||
{
|
||||
case 0:
|
||||
meminfo.id = RETRO_MEMORY_SYSTEM_RAM;
|
||||
break;
|
||||
case 1:
|
||||
meminfo.id = RETRO_MEMORY_SAVE_RAM;
|
||||
break;
|
||||
case 2:
|
||||
meminfo.id = RETRO_MEMORY_VIDEO_RAM;
|
||||
break;
|
||||
case 3:
|
||||
meminfo.id = RETRO_MEMORY_RTC;
|
||||
break;
|
||||
default:
|
||||
RARCH_ERR(CHEEVOS_TAG "invalid bank id: %s\n", var->bank_id);
|
||||
break;
|
||||
}
|
||||
|
||||
core_get_memory(&meminfo);
|
||||
memory = (uint8_t*)meminfo.data;
|
||||
}
|
||||
|
||||
if (memory)
|
||||
memory += var->value;
|
||||
}
|
||||
|
||||
return memory;
|
||||
}
|
||||
|
||||
unsigned cheevos_var_get_value(cheevos_var_t* var)
|
||||
{
|
||||
const uint8_t* memory = NULL;
|
||||
unsigned value = 0;
|
||||
|
||||
switch (var->type)
|
||||
{
|
||||
case CHEEVOS_VAR_TYPE_VALUE_COMP:
|
||||
value = var->value;
|
||||
break;
|
||||
|
||||
case CHEEVOS_VAR_TYPE_ADDRESS:
|
||||
case CHEEVOS_VAR_TYPE_DELTA_MEM:
|
||||
memory = cheevos_var_get_memory(var);
|
||||
|
||||
if (memory)
|
||||
{
|
||||
value = memory[0];
|
||||
|
||||
switch (var->size)
|
||||
{
|
||||
case CHEEVOS_VAR_SIZE_BIT_0:
|
||||
value &= 1;
|
||||
break;
|
||||
case CHEEVOS_VAR_SIZE_BIT_1:
|
||||
value = (value >> 1) & 1;
|
||||
break;
|
||||
case CHEEVOS_VAR_SIZE_BIT_2:
|
||||
value = (value >> 2) & 1;
|
||||
break;
|
||||
case CHEEVOS_VAR_SIZE_BIT_3:
|
||||
value = (value >> 3) & 1;
|
||||
break;
|
||||
case CHEEVOS_VAR_SIZE_BIT_4:
|
||||
value = (value >> 4) & 1;
|
||||
break;
|
||||
case CHEEVOS_VAR_SIZE_BIT_5:
|
||||
value = (value >> 5) & 1;
|
||||
break;
|
||||
case CHEEVOS_VAR_SIZE_BIT_6:
|
||||
value = (value >> 6) & 1;
|
||||
break;
|
||||
case CHEEVOS_VAR_SIZE_BIT_7:
|
||||
value = (value >> 7) & 1;
|
||||
break;
|
||||
case CHEEVOS_VAR_SIZE_NIBBLE_LOWER:
|
||||
value &= 0x0f;
|
||||
break;
|
||||
case CHEEVOS_VAR_SIZE_NIBBLE_UPPER:
|
||||
value = (value >> 4) & 0x0f;
|
||||
break;
|
||||
case CHEEVOS_VAR_SIZE_EIGHT_BITS:
|
||||
break;
|
||||
case CHEEVOS_VAR_SIZE_SIXTEEN_BITS:
|
||||
value |= memory[1] << 8;
|
||||
break;
|
||||
case CHEEVOS_VAR_SIZE_THIRTYTWO_BITS:
|
||||
value |= memory[1] << 8;
|
||||
value |= memory[2] << 16;
|
||||
value |= memory[3] << 24;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (var->type == CHEEVOS_VAR_TYPE_DELTA_MEM)
|
||||
{
|
||||
unsigned previous = var->previous;
|
||||
var->previous = value;
|
||||
value = previous;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case CHEEVOS_VAR_TYPE_DYNAMIC_VAR:
|
||||
/* We shouldn't get here... */
|
||||
break;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
77
cheevos/var.h
Normal file
77
cheevos/var.h
Normal file
@ -0,0 +1,77 @@
|
||||
/* RetroArch - A frontend for libretro.
|
||||
* Copyright (C) 2015-2017 - Andre Leiradella
|
||||
*
|
||||
* RetroArch is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with RetroArch.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __RARCH_CHEEVOS_VAR_H
|
||||
#define __RARCH_CHEEVOS_VAR_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "cheevos.h"
|
||||
|
||||
#include <retro_common_api.h>
|
||||
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
typedef enum
|
||||
{
|
||||
CHEEVOS_VAR_SIZE_BIT_0 = 0,
|
||||
CHEEVOS_VAR_SIZE_BIT_1,
|
||||
CHEEVOS_VAR_SIZE_BIT_2,
|
||||
CHEEVOS_VAR_SIZE_BIT_3,
|
||||
CHEEVOS_VAR_SIZE_BIT_4,
|
||||
CHEEVOS_VAR_SIZE_BIT_5,
|
||||
CHEEVOS_VAR_SIZE_BIT_6,
|
||||
CHEEVOS_VAR_SIZE_BIT_7,
|
||||
CHEEVOS_VAR_SIZE_NIBBLE_LOWER,
|
||||
CHEEVOS_VAR_SIZE_NIBBLE_UPPER,
|
||||
/* Byte, */
|
||||
CHEEVOS_VAR_SIZE_EIGHT_BITS, /* =Byte, */
|
||||
CHEEVOS_VAR_SIZE_SIXTEEN_BITS,
|
||||
CHEEVOS_VAR_SIZE_THIRTYTWO_BITS
|
||||
} cheevos_var_size_t;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
/* compare to the value of a live address in RAM */
|
||||
CHEEVOS_VAR_TYPE_ADDRESS = 0,
|
||||
|
||||
/* a number. assume 32 bit */
|
||||
CHEEVOS_VAR_TYPE_VALUE_COMP,
|
||||
|
||||
/* the value last known at this address. */
|
||||
CHEEVOS_VAR_TYPE_DELTA_MEM,
|
||||
|
||||
/* a custom user-set variable */
|
||||
CHEEVOS_VAR_TYPE_DYNAMIC_VAR
|
||||
} cheevos_var_type_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
cheevos_var_size_t size;
|
||||
cheevos_var_type_t type;
|
||||
int bank_id;
|
||||
unsigned value;
|
||||
unsigned previous;
|
||||
} cheevos_var_t;
|
||||
|
||||
void cheevos_var_parse(cheevos_var_t* var, const char** memaddr);
|
||||
void cheevos_var_patch_addr(cheevos_var_t* var, cheevos_console_t console);
|
||||
|
||||
uint8_t* cheevos_var_get_memory(const cheevos_var_t* var);
|
||||
unsigned cheevos_var_get_value(cheevos_var_t* var);
|
||||
|
||||
RETRO_END_DECLS
|
||||
|
||||
#endif /* __RARCH_CHEEVOS_VAR_H */
|
11
command.c
11
command.c
@ -40,6 +40,7 @@
|
||||
|
||||
#ifdef HAVE_CHEEVOS
|
||||
#include "cheevos/cheevos.h"
|
||||
#include "cheevos/var.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_MENU
|
||||
@ -261,8 +262,9 @@ static bool command_read_ram(const char *arg)
|
||||
reply_at = reply + strlen("READ_CORE_RAM ");
|
||||
strlcpy(reply_at, arg, sizeof(reply)-strlen(reply));
|
||||
|
||||
cheevos_parse_guest_addr(&var, strtoul(reply_at, (char**)&reply_at, 16));
|
||||
data = cheevos_get_memory(&var);
|
||||
var.value = strtoul(reply_at, (char**)&reply_at, 16);
|
||||
cheevos_var_patch_addr(&var, cheevos_get_console());
|
||||
data = cheevos_var_get_memory(&var);
|
||||
|
||||
if (data)
|
||||
{
|
||||
@ -293,9 +295,10 @@ static bool command_write_ram(const char *arg)
|
||||
unsigned nbytes = 0;
|
||||
uint8_t *data = NULL;
|
||||
|
||||
cheevos_parse_guest_addr(&var, strtoul(arg, (char**)&arg, 16));
|
||||
var.value = strtoul(arg, (char**)&arg, 16);
|
||||
cheevos_var_patch_addr(&var, cheevos_get_console());
|
||||
|
||||
data = cheevos_get_memory(&var);
|
||||
data = cheevos_var_get_memory(&var);
|
||||
|
||||
if (data)
|
||||
{
|
||||
|
@ -151,6 +151,7 @@ ACHIEVEMENTS
|
||||
#include "../libretro-common/formats/json/jsonsax.c"
|
||||
#include "../network/net_http_special.c"
|
||||
#include "../cheevos/cheevos.c"
|
||||
#include "../cheevos/var.c"
|
||||
#endif
|
||||
|
||||
/*============================================================
|
||||
|
Loading…
x
Reference in New Issue
Block a user