mirror of
https://github.com/libretro/RetroArch.git
synced 2024-11-27 18:20:27 +00:00
315 lines
9.4 KiB
C
315 lines
9.4 KiB
C
/* 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;
|
|
}
|