2017-10-22 16:11:23 +00:00
|
|
|
/* 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
|
|
|
|
{
|
2017-10-22 17:02:15 +00:00
|
|
|
retro_ctx_memory_info_t meminfo = {NULL, 0, 0};
|
2017-10-22 16:11:23 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|