Add cheats API, with GameShark and GameGenie import

This commit is contained in:
Lior Halphon 2020-04-09 20:11:55 +03:00
parent 1d80c185d8
commit 337e74352d
5 changed files with 218 additions and 3 deletions

173
Core/cheats.c Normal file
View File

@ -0,0 +1,173 @@
#include "gb.h"
#include "cheats.h"
#include <stdio.h>
static inline uint8_t hash_addr(uint16_t addr)
{
return addr;
}
static uint16_t bank_for_addr(GB_gameboy_t *gb, uint16_t addr)
{
if (addr < 0x4000) {
return gb->mbc_rom0_bank;
}
if (addr < 0x8000) {
return gb->mbc_rom_bank;
}
if (addr < 0xD000) {
return 0;
}
if (addr < 0xE000) {
return gb->cgb_ram_bank;
}
return 0;
}
void GB_apply_cheat(GB_gameboy_t *gb, uint16_t address, uint8_t *value)
{
const GB_cheat_hash_t *hash = gb->cheat_hash[hash_addr(address)];
if (hash) {
for (unsigned i = 0; i < hash->size; i++) {
GB_cheat_t *cheat = hash->cheats[i];
if (cheat->address == address && cheat->enabled && (!cheat->use_old_value || cheat->old_value == *value)) {
if (cheat->bank == GB_CHEAT_ANY_BANK || cheat->bank == bank_for_addr(gb, address)) {
*value = cheat->value;
break;
}
}
}
}
}
void GB_add_cheat(GB_gameboy_t *gb, const char *description, uint16_t address, uint16_t bank, uint8_t value, uint8_t old_value, bool use_old_value, bool enabled)
{
GB_cheat_t *cheat = malloc(sizeof(*cheat));
cheat->address = address;
cheat->bank = bank;
cheat->value = value;
cheat->old_value = old_value;
cheat->use_old_value = use_old_value;
cheat->enabled = enabled;
strncpy(cheat->description, description, sizeof(cheat->description));
cheat->description[sizeof(cheat->description) - 1] = 0;
gb->cheats = realloc(gb->cheats, (++gb->cheat_count) * sizeof(*cheat));
gb->cheats[gb->cheat_count - 1] = cheat;
GB_cheat_hash_t **hash = &gb->cheat_hash[hash_addr(address)];
if (!*hash) {
*hash = malloc(sizeof(GB_cheat_hash_t) + sizeof(cheat));
(*hash)->size = 1;
(*hash)->cheats[0] = cheat;
}
else {
(*hash)->size++;
*hash = malloc(sizeof(GB_cheat_hash_t) + sizeof(cheat) * (*hash)->size);
(*hash)->cheats[(*hash)->size - 1] = cheat;
}
}
const GB_cheat_t *const *GB_get_cheats(GB_gameboy_t *gb, size_t *size)
{
*size = gb->cheat_count;
return (void *)gb->cheats;
}
void GB_remove_cheat(GB_gameboy_t *gb, const GB_cheat_t *cheat)
{
for (unsigned i = 0; i < gb->cheat_count; i++) {
if (gb->cheats[i] == cheat) {
gb->cheats[i] = gb->cheats[--gb->cheat_count];
if (gb->cheat_count == 0) {
free(gb->cheats);
gb->cheats = NULL;
}
else {
gb->cheats = realloc(gb->cheats, gb->cheat_count * sizeof(*cheat));
}
break;
}
}
GB_cheat_hash_t **hash = &gb->cheat_hash[hash_addr(cheat->address)];
for (unsigned i = 0; i < (*hash)->size; i++) {
if ((*hash)->cheats[i] == cheat) {
(*hash)->cheats[i] = (*hash)->cheats[(*hash)->size--];
if ((*hash)->size == 0) {
free(*hash);
*hash = NULL;
}
else {
*hash = malloc(sizeof(GB_cheat_hash_t) + sizeof(cheat) * (*hash)->size);
}
break;
}
}
free((void *)cheat);
}
bool GB_import_cheat(GB_gameboy_t *gb, const char *cheat, const char *description, bool enabled)
{
uint8_t dummy;
/* GameShark */
{
uint8_t bank;
uint8_t value;
uint16_t address;
if (sscanf(cheat, "%02hhx%02hhx%04hx%c", &bank, &value, &address, &dummy) == 3) {
if (address > 0x7FFF) {
return false;
}
if (bank >= 0x80) {
bank &= 0xF;
}
GB_add_cheat(gb, description, address, bank, value, 0, false, enabled);
return true;
}
}
/* GameGnie */
{
char stripped_cheat[10] = {0,};
for (unsigned i = 0; i < 9 && *cheat; i++) {
stripped_cheat[i] = *(cheat++);
while (*cheat == '-') {
cheat++;
}
}
// Delete the 7th character;
stripped_cheat[7] = stripped_cheat[8];
stripped_cheat[8] = 0;
uint8_t old_value;
uint8_t value;
uint16_t address;
if (sscanf(stripped_cheat, "%02hhx%04hx%02hhx%c", &value, &address, &old_value, &dummy) == 3) {
address = (uint16_t)(address >> 4) | (uint16_t)(address << 12);
address ^= 0xF000;
if (address > 0x7FFF) {
return false;
}
old_value = (uint8_t)(old_value >> 2) | (uint8_t)(old_value << 6);
old_value ^= 0xBA;
GB_add_cheat(gb, description, address, GB_CHEAT_ANY_BANK, value, old_value, true, enabled);
return true;
}
if (sscanf(stripped_cheat, "%02hhx%04hx%c", &value, &address, &dummy) == 2) {
address = (uint16_t)(address >> 4) | (uint16_t)(address << 12);
address ^= 0xF000;
if (address > 0x7FFF) {
return false;
}
GB_add_cheat(gb, description, address, GB_CHEAT_ANY_BANK, value, false, true, enabled);
return true;
}
}
return false;
}

33
Core/cheats.h Normal file
View File

@ -0,0 +1,33 @@
#ifndef cheats_h
#define cheats_h
#include "gb_struct_def.h"
#define GB_CHEAT_ANY_BANK 0xFFFF
typedef struct GB_cheat_s GB_cheat_t;
void GB_add_cheat(GB_gameboy_t *gb, const char *description, uint16_t address, uint16_t bank, uint8_t value, uint8_t old_value, bool use_old_value, bool enabled);
bool GB_import_cheat(GB_gameboy_t *gb, const char *cheat, const char *description, bool enabled);
const GB_cheat_t *const *GB_get_cheats(GB_gameboy_t *gb, size_t *size);
void GB_remove_cheat(GB_gameboy_t *gb, const GB_cheat_t *cheat);
#ifdef GB_INTERNAL
void GB_apply_cheat(GB_gameboy_t *gb, uint16_t address, uint8_t *value);
#endif
typedef struct {
size_t size;
GB_cheat_t *cheats[];
} GB_cheat_hash_t;
struct GB_cheat_s {
uint16_t address;
uint16_t bank;
uint8_t value;
uint8_t old_value;
bool use_old_value;
bool enabled;
char description[128];
};
#endif

View File

@ -197,6 +197,9 @@ void GB_free(GB_gameboy_t *gb)
GB_debugger_clear_symbols(gb); GB_debugger_clear_symbols(gb);
#endif #endif
GB_rewind_free(gb); GB_rewind_free(gb);
while (gb->cheats) {
GB_remove_cheat(gb, gb->cheats[0]);
}
memset(gb, 0, sizeof(*gb)); memset(gb, 0, sizeof(*gb));
} }

View File

@ -21,6 +21,7 @@
#include "sm83_cpu.h" #include "sm83_cpu.h"
#include "symbol_hash.h" #include "symbol_hash.h"
#include "sgb.h" #include "sgb.h"
#include "cheats.h"
#define GB_STRUCT_VERSION 13 #define GB_STRUCT_VERSION 13
@ -645,6 +646,11 @@ struct GB_gameboy_internal_s {
double sgb_intro_sweep_phase; double sgb_intro_sweep_phase;
double sgb_intro_sweep_previous_sample; double sgb_intro_sweep_previous_sample;
/* Cheats */
size_t cheat_count;
GB_cheat_t **cheats;
GB_cheat_hash_t *cheat_hash[256];
/* Misc */ /* Misc */
bool turbo; bool turbo;
bool turbo_dont_skip; bool turbo_dont_skip;

View File

@ -435,12 +435,12 @@ uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr)
if (is_addr_in_dma_use(gb, addr)) { if (is_addr_in_dma_use(gb, addr)) {
addr = gb->dma_current_src; addr = gb->dma_current_src;
} }
if (gb->read_memory_callback) {
uint8_t data = read_map[addr >> 12](gb, addr); uint8_t data = read_map[addr >> 12](gb, addr);
GB_apply_cheat(gb, addr, &data);
if (gb->read_memory_callback) {
data = gb->read_memory_callback(gb, addr, data); data = gb->read_memory_callback(gb, addr, data);
return data;
} }
return read_map[addr >> 12](gb, addr); return data;
} }
static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value)