diff --git a/Core/cheats.c b/Core/cheats.c new file mode 100644 index 0000000..36b4afb --- /dev/null +++ b/Core/cheats.c @@ -0,0 +1,173 @@ +#include "gb.h" +#include "cheats.h" +#include + +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; +} diff --git a/Core/cheats.h b/Core/cheats.h new file mode 100644 index 0000000..c461f22 --- /dev/null +++ b/Core/cheats.h @@ -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 diff --git a/Core/gb.c b/Core/gb.c index ba3f20a..75538e1 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -197,6 +197,9 @@ void GB_free(GB_gameboy_t *gb) GB_debugger_clear_symbols(gb); #endif GB_rewind_free(gb); + while (gb->cheats) { + GB_remove_cheat(gb, gb->cheats[0]); + } memset(gb, 0, sizeof(*gb)); } diff --git a/Core/gb.h b/Core/gb.h index 1f3bacf..8a60770 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -21,6 +21,7 @@ #include "sm83_cpu.h" #include "symbol_hash.h" #include "sgb.h" +#include "cheats.h" #define GB_STRUCT_VERSION 13 @@ -644,6 +645,11 @@ struct GB_gameboy_internal_s { double sgb_intro_jingle_phases[7]; double sgb_intro_sweep_phase; double sgb_intro_sweep_previous_sample; + + /* Cheats */ + size_t cheat_count; + GB_cheat_t **cheats; + GB_cheat_hash_t *cheat_hash[256]; /* Misc */ bool turbo; diff --git a/Core/memory.c b/Core/memory.c index 27f58e1..58b96bb 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -435,12 +435,12 @@ uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr) if (is_addr_in_dma_use(gb, addr)) { addr = gb->dma_current_src; } + uint8_t data = read_map[addr >> 12](gb, addr); + GB_apply_cheat(gb, addr, &data); if (gb->read_memory_callback) { - uint8_t data = read_map[addr >> 12](gb, addr); 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)