mirror of
https://github.com/libretro/RetroArch
synced 2025-02-16 03:40:12 +00:00
generic memory mapping using rcheevos
This commit is contained in:
parent
d3b234f404
commit
b15411dad8
@ -1952,7 +1952,7 @@ ifeq ($(HAVE_NETWORKING), 1)
|
||||
|
||||
OBJ += cheevos/cheevos.o \
|
||||
cheevos/badges.o \
|
||||
cheevos/fixup.o \
|
||||
cheevos/memory.o \
|
||||
cheevos/parser.o \
|
||||
cheevos/hash.o \
|
||||
$(LIBRETRO_COMM_DIR)/formats/cdfs/cdfs.o \
|
||||
|
@ -52,7 +52,7 @@
|
||||
|
||||
#include "badges.h"
|
||||
#include "cheevos.h"
|
||||
#include "fixup.h"
|
||||
#include "memory.h"
|
||||
#include "parser.h"
|
||||
#include "hash.h"
|
||||
#include "util.h"
|
||||
@ -79,9 +79,6 @@
|
||||
/* Define this macro to prevent cheevos from being deactivated. */
|
||||
#undef CHEEVOS_DONT_DEACTIVATE
|
||||
|
||||
/* Define this macro to dump all cheevos' addresses. */
|
||||
#undef CHEEVOS_DUMP_ADDRS
|
||||
|
||||
/* Define this macro to load a JSON file from disk instead of downloading
|
||||
* from retroachievements.org. */
|
||||
#undef CHEEVOS_JSON_OVERRIDE
|
||||
@ -166,7 +163,7 @@ typedef struct
|
||||
rcheevos_lboard_t* lboards;
|
||||
rcheevos_richpresence_t richpresence;
|
||||
|
||||
rcheevos_fixups_t fixups;
|
||||
rcheevos_memory_regions_t memory;
|
||||
|
||||
char token[32];
|
||||
char hash[33];
|
||||
@ -185,7 +182,7 @@ static rcheevos_locals_t rcheevos_locals =
|
||||
NULL, /* unofficial */
|
||||
NULL, /* lboards */
|
||||
{0}, /* rich presence */
|
||||
{0}, /* fixups */
|
||||
{{0}},/* memory */
|
||||
{0}, /* token */
|
||||
"N/A",/* hash */
|
||||
};
|
||||
@ -377,31 +374,29 @@ static void rcheevos_log_post_url(
|
||||
#endif
|
||||
}
|
||||
|
||||
uint8_t* rcheevos_patch_address(unsigned address)
|
||||
{
|
||||
return rcheevos_memory_find(&rcheevos_locals.memory, address);
|
||||
}
|
||||
|
||||
static unsigned rcheevos_peek(unsigned address, unsigned num_bytes, void* ud)
|
||||
{
|
||||
const uint8_t* data = rcheevos_fixup_find(&rcheevos_locals.fixups,
|
||||
address, rcheevos_locals.patchdata.console_id);
|
||||
unsigned value = 0;
|
||||
|
||||
if (data)
|
||||
uint8_t* data = rcheevos_memory_find(&rcheevos_locals.memory, address);
|
||||
if (data != NULL)
|
||||
{
|
||||
switch (num_bytes)
|
||||
{
|
||||
case 4:
|
||||
value |= data[2] << 16 | data[3] << 24;
|
||||
case 2:
|
||||
value |= data[1] << 8;
|
||||
case 1:
|
||||
value |= data[0];
|
||||
case 4: return (data[3] << 24) | (data[2] << 16) | (data[1] << 8) | (data[0]);
|
||||
case 3: return (data[2] << 16) | (data[1] << 8) | (data[0]);
|
||||
case 2: return (data[1] << 8) | (data[0]);
|
||||
case 1: return data[0];
|
||||
}
|
||||
}
|
||||
else
|
||||
rcheevos_locals.invalid_peek_address = true;
|
||||
|
||||
return value;
|
||||
rcheevos_locals.invalid_peek_address = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void rcheevos_async_award_achievement(rcheevos_async_io_request* request);
|
||||
static void rcheevos_async_submit_lboard(rcheevos_async_io_request* request);
|
||||
|
||||
@ -564,8 +559,6 @@ static int rcheevos_parse(const char* json)
|
||||
rcheevos_lboard_t* lboard = NULL;
|
||||
rcheevos_racheevo_t* rac = NULL;
|
||||
|
||||
rcheevos_fixup_init(&rcheevos_locals.fixups);
|
||||
|
||||
res = rcheevos_get_patchdata(json, &rcheevos_locals.patchdata);
|
||||
|
||||
if (res != 0)
|
||||
@ -595,52 +588,18 @@ static int rcheevos_parse(const char* json)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Achievement memory accesses are 0-based, regardless of
|
||||
* where the memory is accessed by the
|
||||
* emulated code. As such, address 0 should always be
|
||||
* accessible and serves as an indicator that
|
||||
* other addresses will also be accessible.
|
||||
* Individual achievements will be "Unsupported" if
|
||||
* they contain addresses that cannot be resolved.
|
||||
* This check gives the user immediate feedback
|
||||
* if the core they're trying to use will disable all
|
||||
* achievements as "Unsupported".
|
||||
*/
|
||||
if (!rcheevos_patch_address(0, rcheevos_locals.patchdata.console_id))
|
||||
if (!rcheevos_memory_init(&rcheevos_locals.memory, rcheevos_locals.patchdata.console_id))
|
||||
{
|
||||
int delay_judgment = 0;
|
||||
rarch_system_info_t* system = runloop_get_system_info();
|
||||
|
||||
if (system->mmaps.num_descriptors == 0)
|
||||
/* some cores (like Mupen64-Plus) don't expose the memory until the first call to retro_run.
|
||||
* in that case, there will be a total_size of memory reported by the core, but init will return
|
||||
* false, as all of the pointers were null.
|
||||
*/
|
||||
if (rcheevos_locals.memory.total_size != 0)
|
||||
{
|
||||
/* Special case: the mupen64plus-nx core doesn't
|
||||
* initialize the RAM immediately. To avoid a race
|
||||
* condition - if the core says there's SYSTEM_RAM,
|
||||
* but the pointer is NULL, proceed. If the memory
|
||||
* isn't exposed when the achievements start processing,
|
||||
* they'll be marked "Unsupported" individually.
|
||||
*/
|
||||
retro_ctx_memory_info_t meminfo;
|
||||
meminfo.id = RETRO_MEMORY_SYSTEM_RAM;
|
||||
core_get_memory(&meminfo);
|
||||
|
||||
delay_judgment |= (meminfo.size > 0);
|
||||
/* reset the memory count and we'll re-evaluate in rcheevos_test() */
|
||||
rcheevos_locals.memory.count = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Special case: the sameboy core exposes the RAM
|
||||
* at $8000, but not the ROM at $0000. NES and
|
||||
* Gameboy achievements do attempt to map the
|
||||
* entire bus, and it's unlikely that an achievement
|
||||
* will reference the ROM data, so if the RAM is
|
||||
* still present, allow the core to load. If any
|
||||
* achievements do reference the ROM data, they'll
|
||||
* be marked "Unsupported" individually.
|
||||
*/
|
||||
delay_judgment |= (rcheevos_patch_address(0x8000, rcheevos_locals.patchdata.console_id) != NULL);
|
||||
}
|
||||
|
||||
if (!delay_judgment)
|
||||
{
|
||||
CHEEVOS_ERR(RCHEEVOS_TAG "No memory exposed by core\n");
|
||||
|
||||
@ -802,7 +761,7 @@ error:
|
||||
CHEEVOS_FREE(rcheevos_locals.unofficial);
|
||||
CHEEVOS_FREE(rcheevos_locals.lboards);
|
||||
rcheevos_free_patchdata(&rcheevos_locals.patchdata);
|
||||
rcheevos_fixup_destroy(&rcheevos_locals.fixups);
|
||||
rcheevos_memory_destroy(&rcheevos_locals.memory);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -1128,6 +1087,10 @@ void rcheevos_reset_game(void)
|
||||
}
|
||||
|
||||
rcheevos_locals.richpresence.last_update = cpu_features_get_time_usec();
|
||||
|
||||
/* some cores reallocate memory on reset, make sure we update our pointers */
|
||||
if (rcheevos_locals.memory.total_size > 0)
|
||||
rcheevos_memory_init(&rcheevos_locals.memory, rcheevos_locals.patchdata.console_id);
|
||||
}
|
||||
|
||||
#ifdef HAVE_MENU
|
||||
@ -1348,7 +1311,7 @@ bool rcheevos_unload(void)
|
||||
CHEEVOS_FREE(rcheevos_locals.lboards);
|
||||
CHEEVOS_FREE(rcheevos_locals.richpresence.richpresence);
|
||||
rcheevos_free_patchdata(&rcheevos_locals.patchdata);
|
||||
rcheevos_fixup_destroy(&rcheevos_locals.fixups);
|
||||
rcheevos_memory_destroy(&rcheevos_locals.memory);
|
||||
|
||||
rcheevos_locals.core = NULL;
|
||||
rcheevos_locals.unofficial = NULL;
|
||||
@ -1411,6 +1374,21 @@ void rcheevos_test(void)
|
||||
{
|
||||
settings_t *settings = config_get_ptr();
|
||||
|
||||
if (rcheevos_locals.memory.count == 0)
|
||||
{
|
||||
/* we were unable to initialize memory earlier, try now */
|
||||
if (!rcheevos_memory_init(&rcheevos_locals.memory, rcheevos_locals.patchdata.console_id))
|
||||
{
|
||||
CHEEVOS_ERR(RCHEEVOS_TAG "No memory exposed by core\n");
|
||||
|
||||
if (settings->bools.cheevos_verbose_enable)
|
||||
runloop_msg_queue_push("Cannot activate achievements using this core.", 0, 4 * 60, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_WARNING);
|
||||
|
||||
rcheevos_loaded = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
rcheevos_test_cheevo_set(true);
|
||||
|
||||
if (settings)
|
||||
@ -1435,11 +1413,6 @@ bool rcheevos_get_support_cheevos(void)
|
||||
return rcheevos_locals.core_supports;
|
||||
}
|
||||
|
||||
int rcheevos_get_console(void)
|
||||
{
|
||||
return rcheevos_locals.patchdata.console_id;
|
||||
}
|
||||
|
||||
const char* rcheevos_get_hash(void)
|
||||
{
|
||||
return rcheevos_locals.hash;
|
||||
|
@ -61,12 +61,12 @@ void rcheevos_set_support_cheevos(bool state);
|
||||
|
||||
bool rcheevos_get_support_cheevos(void);
|
||||
|
||||
int rcheevos_get_console(void);
|
||||
|
||||
const char* rcheevos_get_hash(void);
|
||||
|
||||
const char *rcheevos_get_richpresence(void);
|
||||
|
||||
uint8_t* rcheevos_patch_address(unsigned address);
|
||||
|
||||
extern bool rcheevos_loaded;
|
||||
extern bool rcheevos_hardcore_active;
|
||||
extern bool rcheevos_hardcore_paused;
|
||||
|
314
cheevos/fixup.c
314
cheevos/fixup.c
@ -1,314 +0,0 @@
|
||||
/* RetroArch - A frontend for libretro.
|
||||
* Copyright (C) 2015-2018 - 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 "fixup.h"
|
||||
#include "cheevos.h"
|
||||
#include "util.h"
|
||||
|
||||
#include "../retroarch.h"
|
||||
#include "../core.h"
|
||||
|
||||
#include "../deps/rcheevos/include/rcheevos.h"
|
||||
#include "../deps/rcheevos/include/rconsoles.h"
|
||||
|
||||
static int rcheevos_cmpaddr(const void* e1, const void* e2)
|
||||
{
|
||||
const rcheevos_fixup_t* f1 = (const rcheevos_fixup_t*)e1;
|
||||
const rcheevos_fixup_t* f2 = (const rcheevos_fixup_t*)e2;
|
||||
|
||||
if (f1->address < f2->address)
|
||||
return -1;
|
||||
else if (f1->address > f2->address)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static size_t rcheevos_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 rcheevos_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 rcheevos_fixup_init(rcheevos_fixups_t* fixups)
|
||||
{
|
||||
fixups->elements = NULL;
|
||||
fixups->capacity = fixups->count = 0;
|
||||
fixups->dirty = false;
|
||||
}
|
||||
|
||||
void rcheevos_fixup_destroy(rcheevos_fixups_t* fixups)
|
||||
{
|
||||
CHEEVOS_FREE(fixups->elements);
|
||||
rcheevos_fixup_init(fixups);
|
||||
}
|
||||
|
||||
const uint8_t* rcheevos_fixup_find(
|
||||
rcheevos_fixups_t* fixups, unsigned address, int console)
|
||||
{
|
||||
rcheevos_fixup_t key;
|
||||
rcheevos_fixup_t* found;
|
||||
const uint8_t* location;
|
||||
|
||||
if (fixups->dirty)
|
||||
{
|
||||
qsort(fixups->elements, fixups->count,
|
||||
sizeof(rcheevos_fixup_t), rcheevos_cmpaddr);
|
||||
fixups->dirty = false;
|
||||
}
|
||||
|
||||
key.address = address;
|
||||
found = (rcheevos_fixup_t*)bsearch(&key,
|
||||
fixups->elements, fixups->count,
|
||||
sizeof(rcheevos_fixup_t), rcheevos_cmpaddr);
|
||||
|
||||
if (found)
|
||||
return found->location;
|
||||
|
||||
if (fixups->count == fixups->capacity)
|
||||
{
|
||||
unsigned new_capacity = fixups->capacity == 0 ? 16 : fixups->capacity * 2;
|
||||
rcheevos_fixup_t* new_elements = (rcheevos_fixup_t*)
|
||||
realloc(fixups->elements, new_capacity * sizeof(rcheevos_fixup_t));
|
||||
|
||||
if (!new_elements)
|
||||
return NULL;
|
||||
|
||||
fixups->elements = new_elements;
|
||||
fixups->capacity = new_capacity;
|
||||
}
|
||||
|
||||
fixups->elements[fixups->count].address = address;
|
||||
fixups->elements[fixups->count++].location = location =
|
||||
rcheevos_patch_address(address, console);
|
||||
fixups->dirty = true;
|
||||
|
||||
return location;
|
||||
}
|
||||
|
||||
const uint8_t* rcheevos_patch_address(unsigned address, int console)
|
||||
{
|
||||
rarch_system_info_t* system = runloop_get_system_info();
|
||||
const void* pointer = NULL;
|
||||
unsigned original_address = address;
|
||||
|
||||
switch (console)
|
||||
{
|
||||
case RC_CONSOLE_NINTENDO:
|
||||
if (address >= 0x0800 && address < 0x2000)
|
||||
{
|
||||
/* Address in the mirrorred RAM,
|
||||
* adjust to real RAM. */
|
||||
CHEEVOS_LOG(RCHEEVOS_TAG "NES memory address in mirrorred RAM %X, adjusted to %X\n", address, address & 0x07ff);
|
||||
address &= 0x07ff;
|
||||
}
|
||||
break;
|
||||
case RC_CONSOLE_GAMEBOY_COLOR:
|
||||
if (address >= 0xe000 && address <= 0xfdff)
|
||||
{
|
||||
/* Address in the echo RAM, adjust to real RAM. */
|
||||
CHEEVOS_LOG(RCHEEVOS_TAG "GBC memory address in echo RAM %X, adjusted to %X\n", address, address - 0x2000);
|
||||
address -= 0x2000;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (system->mmaps.num_descriptors != 0)
|
||||
{
|
||||
/* We have memory descriptors, use it. */
|
||||
const rarch_memory_descriptor_t* desc = NULL;
|
||||
const rarch_memory_descriptor_t* end = NULL;
|
||||
|
||||
/* Patch the address to correctly map it to the mmaps. */
|
||||
switch (console)
|
||||
{
|
||||
case RC_CONSOLE_GAMEBOY_ADVANCE:
|
||||
if (address < 0x8000)
|
||||
{
|
||||
/* Internal RAM. */
|
||||
CHEEVOS_LOG(RCHEEVOS_TAG "GBA memory address %X adjusted to %X\n", address, address + 0x3000000);
|
||||
address += 0x3000000;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Work RAM. */
|
||||
CHEEVOS_LOG(RCHEEVOS_TAG "GBA memory address %X adjusted to %X\n", address, address + 0x2000000 - 0x8000);
|
||||
address += 0x2000000 - 0x8000;
|
||||
}
|
||||
break;
|
||||
case RC_CONSOLE_PC_ENGINE:
|
||||
if (address < 0x002000)
|
||||
{
|
||||
/* RAM. */
|
||||
CHEEVOS_LOG(RCHEEVOS_TAG "PCE memory address %X adjusted to %X\n", address, address + 0x1f0000);
|
||||
address += 0x1f0000;
|
||||
}
|
||||
else if (address < 0x012000)
|
||||
{
|
||||
/* CD-ROM RAM. */
|
||||
CHEEVOS_LOG(RCHEEVOS_TAG "PCE memory address %X adjusted to %X\n", address, address + 0x100000 - 0x002000);
|
||||
address += 0x100000 - 0x002000;
|
||||
}
|
||||
else if (address < 0x042000)
|
||||
{
|
||||
/* Super System Card RAM. */
|
||||
CHEEVOS_LOG(RCHEEVOS_TAG "PCE memory address %X adjusted to %X\n", address, address + 0x0d0000 - 0x012000);
|
||||
address += 0x0d0000 - 0x012000;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* CD-ROM battery backed RAM. */
|
||||
CHEEVOS_LOG(RCHEEVOS_TAG "PCE memory address %X adjusted to %X\n", address, address + 0x1ee000 - 0x042000);
|
||||
address += 0x1ee000 - 0x042000;
|
||||
}
|
||||
break;
|
||||
case RC_CONSOLE_SUPER_NINTENDO:
|
||||
if (address < 0x020000)
|
||||
{
|
||||
/* Work RAM. */
|
||||
CHEEVOS_LOG(RCHEEVOS_TAG "SNES memory address %X adjusted to %X\n", address, address + 0x7e0000);
|
||||
address += 0x7e0000;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Save RAM. */
|
||||
CHEEVOS_LOG(RCHEEVOS_TAG "SNES memory address %X adjusted to %X\n", address, address + 0x006000 - 0x020000);
|
||||
address += 0x006000 - 0x020000;
|
||||
}
|
||||
break;
|
||||
case RC_CONSOLE_SEGA_CD:
|
||||
if (address < 0x010000)
|
||||
{
|
||||
/* Work RAM. */
|
||||
address += 0xFF0000;
|
||||
CHEEVOS_LOG(RCHEEVOS_TAG "Sega CD memory address %X adjusted to %X\n", original_address, address);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* CD-ROM peripheral RAM - exposed at virtual address to avoid banking */
|
||||
address += 0x80020000 - 0x010000;
|
||||
CHEEVOS_LOG(RCHEEVOS_TAG "Sega CD memory address %X adjusted to %X\n", original_address, address);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
desc = system->mmaps.descriptors;
|
||||
end = desc + system->mmaps.num_descriptors;
|
||||
|
||||
for (; desc < end; desc++)
|
||||
{
|
||||
if (((desc->core.start ^ address) & desc->core.select) == 0)
|
||||
{
|
||||
pointer = desc->core.ptr;
|
||||
address -= desc->core.start;
|
||||
|
||||
if (desc->disconnect_mask)
|
||||
address = (unsigned)rcheevos_var_reduce(
|
||||
address & desc->disconnect_mask, desc->core.disconnect);
|
||||
|
||||
if (address >= desc->core.len)
|
||||
address -= rcheevos_var_highest_bit(address);
|
||||
|
||||
address += desc->core.offset;
|
||||
|
||||
CHEEVOS_LOG(RCHEEVOS_TAG "address %X set to descriptor %d at offset %X\n", original_address,
|
||||
(int)((desc - system->mmaps.descriptors) + 1), address);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (console == RC_CONSOLE_GAMEBOY_ADVANCE)
|
||||
{
|
||||
/* The RetroAchievements implementation of memory access
|
||||
* for GBA puts the save RAM first,
|
||||
* so the default looping behavior below is backwards.
|
||||
*
|
||||
* If the core doesn't expose a
|
||||
* memory map, say it isn't supported.
|
||||
*/
|
||||
pointer = NULL;
|
||||
}
|
||||
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 (address < meminfo.size)
|
||||
{
|
||||
pointer = meminfo.data;
|
||||
break;
|
||||
}
|
||||
|
||||
/**
|
||||
* HACK Subtract the correct amount of bytes to
|
||||
* reach the save RAM as its size is not always
|
||||
* set correctly in the core.
|
||||
*/
|
||||
if (i == 0 && console == RC_CONSOLE_NINTENDO)
|
||||
address -= 0x6000;
|
||||
else
|
||||
address -= meminfo.size;
|
||||
}
|
||||
}
|
||||
|
||||
if (!pointer)
|
||||
{
|
||||
CHEEVOS_LOG(RCHEEVOS_TAG "address %X not supported\n", original_address);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return (const uint8_t*)pointer + address;
|
||||
}
|
340
cheevos/memory.c
Normal file
340
cheevos/memory.c
Normal file
@ -0,0 +1,340 @@
|
||||
/* RetroArch - A frontend for libretro.
|
||||
* Copyright (C) 2015-2016 - 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 "memory.h"
|
||||
|
||||
#include "util.h"
|
||||
|
||||
#include "../retroarch.h"
|
||||
#include "../verbosity.h"
|
||||
|
||||
#include "../deps/rcheevos/include/rcheevos.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
uint8_t* rcheevos_memory_find(const rcheevos_memory_regions_t* regions, unsigned address)
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
for (i = 0; i < regions->count; ++i)
|
||||
{
|
||||
const size_t size = regions->size[i];
|
||||
if (address < size)
|
||||
{
|
||||
if (regions->data[i] == NULL)
|
||||
break;
|
||||
|
||||
return ®ions->data[i][address];
|
||||
}
|
||||
|
||||
address -= size;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char* rcheevos_memory_type(int type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case RC_MEMORY_TYPE_SAVE_RAM: return "SRAM";
|
||||
case RC_MEMORY_TYPE_VIDEO_RAM: return "VRAM";
|
||||
case RC_MEMORY_TYPE_UNUSED: return "UNUSED";
|
||||
default: return "SYSTEM RAM";
|
||||
}
|
||||
}
|
||||
|
||||
static void rcheevos_memory_register_region(rcheevos_memory_regions_t* regions,
|
||||
int type, uint8_t* data, size_t size, const char* description)
|
||||
{
|
||||
if (size == 0)
|
||||
return;
|
||||
|
||||
if (regions->count == MAX_MEMORY_REGIONS)
|
||||
{
|
||||
CHEEVOS_LOG(RCHEEVOS_TAG "Too many memory memory regions to register\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data && regions->count > 0 && !regions->data[regions->count - 1])
|
||||
{
|
||||
/* extend null region */
|
||||
regions->size[regions->count - 1] += size;
|
||||
}
|
||||
else if (data && regions->count > 0 &&
|
||||
data == (regions->data[regions->count - 1] + regions->size[regions->count - 1]))
|
||||
{
|
||||
/* extend non-null region */
|
||||
regions->size[regions->count - 1] += size;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* create new region */
|
||||
regions->data[regions->count] = data;
|
||||
regions->size[regions->count] = size;
|
||||
++regions->count;
|
||||
}
|
||||
|
||||
regions->total_size += size;
|
||||
|
||||
CHEEVOS_LOG(RCHEEVOS_TAG "Registered 0x%04X bytes of %s at $%06X (%s)\n", size,
|
||||
rcheevos_memory_type(type), regions->total_size - size, description);
|
||||
}
|
||||
|
||||
static void rcheevos_memory_init_without_regions(rcheevos_memory_regions_t* regions)
|
||||
{
|
||||
/* no regions specified, assume system RAM followed by save RAM */
|
||||
char description[64];
|
||||
retro_ctx_memory_info_t meminfo;
|
||||
sprintf(description, "offset 0x%06x", 0);
|
||||
|
||||
meminfo.id = RETRO_MEMORY_SYSTEM_RAM;
|
||||
core_get_memory(&meminfo);
|
||||
rcheevos_memory_register_region(regions, RC_MEMORY_TYPE_SYSTEM_RAM, meminfo.data, meminfo.size, description);
|
||||
|
||||
meminfo.id = RETRO_MEMORY_SAVE_RAM;
|
||||
core_get_memory(&meminfo);
|
||||
rcheevos_memory_register_region(regions, RC_MEMORY_TYPE_SAVE_RAM, meminfo.data, meminfo.size, description);
|
||||
}
|
||||
|
||||
static const rarch_memory_descriptor_t* rcheevos_memory_get_descriptor(const rarch_memory_map_t* mmap, unsigned real_address)
|
||||
{
|
||||
if (mmap->num_descriptors == 0)
|
||||
return NULL;
|
||||
|
||||
const rarch_memory_descriptor_t* desc = mmap->descriptors;
|
||||
const rarch_memory_descriptor_t* end = desc + mmap->num_descriptors;
|
||||
|
||||
for (; desc < end; desc++)
|
||||
{
|
||||
if (desc->core.select == 0)
|
||||
{
|
||||
/* if select is 0, attempt to explcitly match the address */
|
||||
if (real_address >= desc->core.start && real_address < desc->core.start + desc->core.len)
|
||||
return desc;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* otherwise, attempt to match the address by matching the select bits */
|
||||
if (((desc->core.start ^ real_address) & desc->core.select) == 0)
|
||||
{
|
||||
/* sanity check - make sure the descriptor is large enough to hold the target address */
|
||||
if (real_address - desc->core.start < desc->core.len)
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void rcheevos_memory_init_from_memory_map(rcheevos_memory_regions_t* regions, const rarch_memory_map_t* mmap, const rc_memory_regions_t* console_regions)
|
||||
{
|
||||
char description[64];
|
||||
unsigned i;
|
||||
uint8_t* region_start;
|
||||
uint8_t* desc_start;
|
||||
size_t desc_size;
|
||||
size_t offset;
|
||||
|
||||
for (i = 0; i < console_regions->num_regions; ++i)
|
||||
{
|
||||
const rc_memory_region_t* console_region = &console_regions->region[i];
|
||||
size_t console_region_size = console_region->end_address - console_region->start_address + 1;
|
||||
unsigned real_address = console_region->real_address;
|
||||
|
||||
while (console_region_size > 0)
|
||||
{
|
||||
const rarch_memory_descriptor_t* desc = rcheevos_memory_get_descriptor(mmap, real_address);
|
||||
if (!desc)
|
||||
{
|
||||
if (console_region->type != RC_MEMORY_TYPE_UNUSED)
|
||||
{
|
||||
CHEEVOS_LOG(RCHEEVOS_TAG "Could not map region starting at $%06X\n",
|
||||
real_address - console_region->real_address + console_region->start_address);
|
||||
}
|
||||
|
||||
rcheevos_memory_register_region(regions, console_region->type, NULL, console_region_size, "null filler");
|
||||
break;
|
||||
}
|
||||
|
||||
offset = real_address - desc->core.start;
|
||||
sprintf(description, "descriptor %u, offset 0x%06X", (int)(desc - mmap->descriptors) + 1, (int)offset);
|
||||
|
||||
if (desc->core.ptr)
|
||||
{
|
||||
desc_start = (uint8_t*)desc->core.ptr + desc->core.offset;
|
||||
region_start = desc_start + offset;
|
||||
}
|
||||
else
|
||||
{
|
||||
region_start = NULL;
|
||||
}
|
||||
|
||||
desc_size = desc->core.len - offset;
|
||||
|
||||
if (console_region_size > desc_size)
|
||||
{
|
||||
if (desc_size == 0)
|
||||
{
|
||||
if (console_region->type != RC_MEMORY_TYPE_UNUSED)
|
||||
{
|
||||
CHEEVOS_LOG(RCHEEVOS_TAG "Could not map region starting at $%06X\n",
|
||||
real_address - console_region->real_address + console_region->start_address);
|
||||
}
|
||||
|
||||
rcheevos_memory_register_region(regions, console_region->type, NULL, console_region_size, "null filler");
|
||||
console_region_size = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
rcheevos_memory_register_region(regions, console_region->type, region_start, desc_size, description);
|
||||
console_region_size -= desc_size;
|
||||
real_address += desc_size;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
rcheevos_memory_register_region(regions, console_region->type, region_start, console_region_size, description);
|
||||
console_region_size = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned rcheevos_memory_console_region_to_ram_type(int region_type)
|
||||
{
|
||||
switch (region_type)
|
||||
{
|
||||
case RC_MEMORY_TYPE_SAVE_RAM:
|
||||
return RETRO_MEMORY_SAVE_RAM;
|
||||
case RC_MEMORY_TYPE_VIDEO_RAM:
|
||||
return RETRO_MEMORY_VIDEO_RAM;
|
||||
default:
|
||||
return RETRO_MEMORY_SYSTEM_RAM;
|
||||
}
|
||||
}
|
||||
|
||||
static void rcheevos_memory_init_from_unmapped_memory(rcheevos_memory_regions_t* regions, const rc_memory_regions_t* console_regions, int console)
|
||||
{
|
||||
char description[64];
|
||||
unsigned i;
|
||||
|
||||
for (i = 0; i < console_regions->num_regions; ++i)
|
||||
{
|
||||
const rc_memory_region_t* console_region = &console_regions->region[i];
|
||||
const size_t console_region_size = console_region->end_address - console_region->start_address + 1;
|
||||
retro_ctx_memory_info_t meminfo;
|
||||
size_t offset;
|
||||
unsigned base_address = 0;
|
||||
unsigned j;
|
||||
|
||||
meminfo.id = rcheevos_memory_console_region_to_ram_type(console_region->type);
|
||||
|
||||
for (j = 0; j <= i; ++j)
|
||||
{
|
||||
const rc_memory_region_t* console_region2 = &console_regions->region[j];
|
||||
if (rcheevos_memory_console_region_to_ram_type(console_region2->type) == meminfo.id)
|
||||
{
|
||||
base_address = console_region2->start_address;
|
||||
break;
|
||||
}
|
||||
}
|
||||
offset = console_region->start_address - base_address;
|
||||
|
||||
core_get_memory(&meminfo);
|
||||
|
||||
if (offset < meminfo.size)
|
||||
{
|
||||
meminfo.size -= offset;
|
||||
|
||||
if (meminfo.data != NULL)
|
||||
{
|
||||
sprintf(description, "offset 0x%06X", (int)offset);
|
||||
meminfo.data = (uint8_t*)meminfo.data + offset;
|
||||
}
|
||||
else
|
||||
{
|
||||
sprintf(description, "null filler");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (console_region->type != RC_MEMORY_TYPE_UNUSED)
|
||||
{
|
||||
CHEEVOS_LOG(RCHEEVOS_TAG "Could not map region starting at $%06X\n", console_region->start_address);
|
||||
}
|
||||
|
||||
meminfo.data = NULL;
|
||||
meminfo.size = 0;
|
||||
}
|
||||
|
||||
if (console_region_size > meminfo.size)
|
||||
{
|
||||
/* want more than what is available, take what we can and null fill the rest */
|
||||
rcheevos_memory_register_region(regions, console_region->type, meminfo.data, meminfo.size, description);
|
||||
rcheevos_memory_register_region(regions, console_region->type, NULL, console_region_size - meminfo.size, "null filler");
|
||||
}
|
||||
else
|
||||
{
|
||||
/* only take as much as we need */
|
||||
rcheevos_memory_register_region(regions, console_region->type, meminfo.data, console_region_size, description);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void rcheevos_memory_destroy(rcheevos_memory_regions_t* regions)
|
||||
{
|
||||
memset(regions, 0, sizeof(*regions));
|
||||
}
|
||||
|
||||
bool rcheevos_memory_init(rcheevos_memory_regions_t* regions, int console)
|
||||
{
|
||||
const rc_memory_regions_t* console_regions = rc_console_memory_regions(console);
|
||||
rcheevos_memory_regions_t new_regions;
|
||||
bool has_valid_region = false;
|
||||
unsigned i;
|
||||
|
||||
if (regions == NULL)
|
||||
return false;
|
||||
|
||||
memset(&new_regions, 0, sizeof(new_regions));
|
||||
|
||||
if (console_regions == NULL || console_regions->num_regions == 0)
|
||||
{
|
||||
rcheevos_memory_init_without_regions(&new_regions);
|
||||
}
|
||||
else
|
||||
{
|
||||
rarch_system_info_t* system = runloop_get_system_info();
|
||||
if (system->mmaps.num_descriptors != 0)
|
||||
rcheevos_memory_init_from_memory_map(&new_regions, &system->mmaps, console_regions);
|
||||
else
|
||||
rcheevos_memory_init_from_unmapped_memory(&new_regions, console_regions, console);
|
||||
}
|
||||
|
||||
/* determine if any valid regions were found */
|
||||
for (i = 0; i < new_regions.count; i++)
|
||||
{
|
||||
if (new_regions.data[i] != NULL)
|
||||
{
|
||||
has_valid_region = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(regions, &new_regions, sizeof(*regions));
|
||||
return has_valid_region;
|
||||
}
|
@ -12,36 +12,31 @@
|
||||
* 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_FIXUP_H
|
||||
#define __RARCH_CHEEVOS_FIXUP_H
|
||||
#ifndef __RARCH_CHEEVOS_MEMORY_H
|
||||
#define __RARCH_CHEEVOS_MEMORY_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <boolean.h>
|
||||
|
||||
#include <retro_common_api.h>
|
||||
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned address;
|
||||
const uint8_t* location;
|
||||
} rcheevos_fixup_t;
|
||||
#define MAX_MEMORY_REGIONS 32
|
||||
|
||||
typedef struct
|
||||
{
|
||||
rcheevos_fixup_t* elements;
|
||||
unsigned capacity, count;
|
||||
bool dirty;
|
||||
} rcheevos_fixups_t;
|
||||
uint8_t* data[MAX_MEMORY_REGIONS];
|
||||
size_t size[MAX_MEMORY_REGIONS];
|
||||
unsigned count;
|
||||
size_t total_size;
|
||||
} rcheevos_memory_regions_t;
|
||||
|
||||
void rcheevos_fixup_init(rcheevos_fixups_t* fixups);
|
||||
void rcheevos_fixup_destroy(rcheevos_fixups_t* fixups);
|
||||
bool rcheevos_memory_init(rcheevos_memory_regions_t* regions, int console);
|
||||
void rcheevos_memory_destroy(rcheevos_memory_regions_t* regions);
|
||||
|
||||
const uint8_t* rcheevos_fixup_find(rcheevos_fixups_t* fixups, unsigned address, int console);
|
||||
|
||||
const uint8_t* rcheevos_patch_address(unsigned address, int console);
|
||||
uint8_t* rcheevos_memory_find(const rcheevos_memory_regions_t* regions, unsigned address);
|
||||
|
||||
RETRO_END_DECLS
|
||||
|
2
deps/rcheevos/src/rcheevos/consoleinfo.c
vendored
2
deps/rcheevos/src/rcheevos/consoleinfo.c
vendored
@ -259,7 +259,7 @@ static const rc_memory_region_t _rc_memory_regions_gameboy[] = {
|
||||
{ 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_READONLY, "Unusable"},
|
||||
{ 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"},
|
||||
|
@ -195,7 +195,7 @@ ACHIEVEMENTS
|
||||
|
||||
#include "../cheevos/cheevos.c"
|
||||
#include "../cheevos/badges.c"
|
||||
#include "../cheevos/fixup.c"
|
||||
#include "../cheevos/memory.c"
|
||||
#include "../cheevos/hash.c"
|
||||
#include "../cheevos/parser.c"
|
||||
|
||||
|
12
retroarch.c
12
retroarch.c
@ -170,7 +170,6 @@
|
||||
|
||||
#ifdef HAVE_CHEEVOS
|
||||
#include "cheevos/cheevos.h"
|
||||
#include "cheevos/fixup.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_TRANSLATE
|
||||
@ -12767,7 +12766,7 @@ static bool command_read_ram(const char *arg)
|
||||
reply_at = reply + snprintf(
|
||||
reply, alloc_size - 1, "READ_CORE_RAM" " %x", addr);
|
||||
|
||||
if ((data = rcheevos_patch_address(addr, rcheevos_get_console())))
|
||||
if ((data = rcheevos_patch_address(addr)))
|
||||
{
|
||||
for (i = 0; i < nbytes; i++)
|
||||
snprintf(reply_at + 3 * i, 4, " %.2X", data[i]);
|
||||
@ -12787,12 +12786,17 @@ static bool command_read_ram(const char *arg)
|
||||
static bool command_write_ram(const char *arg)
|
||||
{
|
||||
unsigned int addr = strtoul(arg, (char**)&arg, 16);
|
||||
uint8_t *data = (uint8_t *)rcheevos_patch_address(
|
||||
addr, rcheevos_get_console());
|
||||
uint8_t *data = (uint8_t *)rcheevos_patch_address(addr);
|
||||
|
||||
if (!data)
|
||||
return false;
|
||||
|
||||
if (rcheevos_hardcore_active && rcheevos_loaded && !rcheevos_hardcore_paused)
|
||||
{
|
||||
RARCH_LOG("Achievements hardcore mode disabled by WRITE_CORE_RAM\n");
|
||||
rcheevos_pause_hardcore();
|
||||
}
|
||||
|
||||
while (*arg)
|
||||
{
|
||||
*data = strtoul(arg, (char**)&arg, 16);
|
||||
|
Loading…
x
Reference in New Issue
Block a user