git.y1.nz

SameBoy

Accurate GB/GBC emulator
download: https://git.y1.nz/archives/sameboy.tar.gz
README | Files | Log | Refs | LICENSE

Core/cheats.c

      1 #include "gb.h"
      2 #include "cheats.h"
      3 #include <stdio.h>
      4 #include <assert.h>
      5 #include <errno.h>
      6 #include <string.h>
      7 #include <stdlib.h>
      8 
      9 static inline uint8_t hash_addr(uint16_t addr)
     10 {
     11     return addr;
     12 }
     13 
     14 static uint16_t bank_for_addr(GB_gameboy_t *gb, uint16_t addr)
     15 {
     16     if (addr < 0x4000) {
     17         return gb->mbc_rom0_bank;
     18     }
     19     
     20     if (addr < 0x8000) {
     21         return gb->mbc_rom_bank;
     22     }
     23     
     24     if (addr < 0xD000) {
     25         return 0;
     26     }
     27     
     28     if (addr < 0xE000) {
     29         return gb->cgb_ram_bank;
     30     }
     31     
     32     return 0;
     33 }
     34 
     35 static noinline void apply_cheat(GB_gameboy_t *gb, uint16_t address, uint8_t *value)
     36 {
     37     if (unlikely(!gb->boot_rom_finished)) return;
     38     const GB_cheat_hash_t *hash = gb->cheat_hash[hash_addr(address)];
     39     if (likely(!hash)) return;
     40     
     41     for (unsigned i = 0; i < hash->size; i++) {
     42         GB_cheat_t *cheat = hash->cheats[i];
     43         if (cheat->address == address && cheat->enabled && (!cheat->use_old_value || cheat->old_value == *value)) {
     44             if (cheat->bank == GB_CHEAT_ANY_BANK || cheat->bank == bank_for_addr(gb, address)) {
     45                 *value = cheat->value;
     46                 break;
     47             }
     48         }
     49     }
     50 }
     51 
     52 void GB_apply_cheat(GB_gameboy_t *gb, uint16_t address, uint8_t *value)
     53 {
     54     if (likely(!gb->cheat_enabled)) return;
     55     if (likely(gb->cheat_count == 0)) return; // Optimization
     56     apply_cheat(gb, address, value);
     57 }
     58 
     59 bool GB_cheats_enabled(GB_gameboy_t *gb)
     60 {
     61     return gb->cheat_enabled;
     62 }
     63 
     64 void GB_set_cheats_enabled(GB_gameboy_t *gb, bool enabled)
     65 {
     66     gb->cheat_enabled = enabled;
     67 }
     68 
     69 const GB_cheat_t *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)
     70 {
     71     GB_ASSERT_NOT_RUNNING_OTHER_THREAD(gb)
     72     
     73     GB_cheat_t *cheat = malloc(sizeof(*cheat));
     74     cheat->address = address;
     75     cheat->bank = bank;
     76     cheat->value = value;
     77     cheat->old_value = old_value;
     78     cheat->use_old_value = use_old_value;
     79     cheat->enabled = enabled;
     80     strncpy(cheat->description, description, sizeof(cheat->description));
     81     cheat->description[sizeof(cheat->description) - 1] = 0;
     82     gb->cheats = realloc(gb->cheats, (++gb->cheat_count) * sizeof(gb->cheats[0]));
     83     gb->cheats[gb->cheat_count - 1] = cheat;
     84     
     85     GB_cheat_hash_t **hash = &gb->cheat_hash[hash_addr(address)];
     86     if (!*hash) {
     87         *hash = malloc(sizeof(GB_cheat_hash_t) + sizeof(cheat));
     88         (*hash)->size = 1;
     89         (*hash)->cheats[0] = cheat;
     90     }
     91     else {
     92         (*hash)->size++;
     93         *hash = realloc(*hash, sizeof(GB_cheat_hash_t) + sizeof(cheat) * (*hash)->size);
     94         (*hash)->cheats[(*hash)->size - 1] = cheat;
     95     }
     96     
     97     return cheat;
     98 }
     99 
    100 const GB_cheat_t *const *GB_get_cheats(GB_gameboy_t *gb, size_t *size)
    101 {
    102     if (size) {
    103         *size = gb->cheat_count;
    104     }
    105     return (void *)gb->cheats;
    106 }
    107 
    108 void GB_remove_cheat(GB_gameboy_t *gb, const GB_cheat_t *cheat)
    109 {
    110     GB_ASSERT_NOT_RUNNING_OTHER_THREAD(gb)
    111     
    112     for (unsigned i = 0; i < gb->cheat_count; i++) {
    113         if (gb->cheats[i] == cheat) {
    114             gb->cheats[i] = gb->cheats[--gb->cheat_count];
    115             if (gb->cheat_count == 0) {
    116                 free(gb->cheats);
    117                 gb->cheats = NULL;
    118             }
    119             else {
    120                 gb->cheats = realloc(gb->cheats, gb->cheat_count * sizeof(gb->cheats[0]));
    121             }
    122             break;
    123         }
    124     }
    125     
    126     GB_cheat_hash_t **hash = &gb->cheat_hash[hash_addr(cheat->address)];
    127     for (unsigned i = 0; i < (*hash)->size; i++) {
    128         if ((*hash)->cheats[i] == cheat) {
    129             (*hash)->cheats[i] = (*hash)->cheats[--(*hash)->size];
    130             if ((*hash)->size == 0) {
    131                 free(*hash);
    132                 *hash = NULL;
    133             }
    134             else {
    135                 *hash = realloc(*hash, sizeof(GB_cheat_hash_t) + sizeof(cheat) * (*hash)->size);
    136             }
    137             break;
    138         }
    139     }
    140     
    141     free((void *)cheat);
    142 }
    143 
    144 void GB_remove_all_cheats(GB_gameboy_t *gb)
    145 {
    146     while (gb->cheat_count) {
    147         GB_remove_cheat(gb, gb->cheats[0]);
    148     }
    149 }
    150 
    151 const GB_cheat_t *GB_import_cheat(GB_gameboy_t *gb, const char *cheat, const char *description, bool enabled)
    152 {
    153     GB_ASSERT_NOT_RUNNING_OTHER_THREAD(gb)
    154     
    155     uint8_t dummy;
    156     /* GameShark */
    157     if (strlen(cheat) == 8) {
    158 #ifdef _WIN32
    159         // The hh modifier is not supported on old MSVCRT, it's completely ignored
    160         uint32_t bank = 0;
    161         uint32_t value = 0;
    162 #pragma GCC diagnostic ignored "-Wformat"
    163 #else
    164         uint8_t bank;
    165         uint8_t value;
    166 #endif
    167         uint16_t address;
    168         if (sscanf(cheat, "%02hhx%02hhx%04hx%c", &bank, &value, &address, &dummy) == 3) {
    169             address = __builtin_bswap16(address);
    170             return GB_add_cheat(gb, description, address, bank == 1? GB_CHEAT_ANY_BANK : (bank & 0xF), value, 0, false, enabled);
    171         }
    172     }
    173     
    174     /* Game Genie */
    175     {
    176         char stripped_cheat[10] = {0,};
    177         for (unsigned i = 0; i < 9 && *cheat; i++) {
    178             stripped_cheat[i] = *(cheat++);
    179             while (*cheat == '-') {
    180                 cheat++;
    181             }
    182         }
    183         
    184         // Delete the 7th character;
    185         stripped_cheat[7] = stripped_cheat[8];
    186         stripped_cheat[8] = 0;
    187         
    188 #ifdef _WIN32
    189         uint32_t old_value = 0;
    190         uint32_t value = 0;
    191 #else
    192         uint8_t old_value;
    193         uint8_t value;
    194 #endif
    195         uint16_t address;
    196         if (strlen(stripped_cheat) != 8 && strlen(stripped_cheat) != 6) {
    197             return NULL;
    198         }
    199         if (sscanf(stripped_cheat, "%02hhx%04hx%02hhx%c", &value, &address, &old_value, &dummy) == 3) {
    200             address = (uint16_t)(address >> 4) | (uint16_t)(address << 12);
    201             address ^= 0xF000;
    202             if (address > 0x7FFF) {
    203                 return false;
    204             }
    205             old_value = (uint8_t)(old_value >> 2) | (uint8_t)(old_value << 6);
    206             old_value ^= 0xBA;
    207             return GB_add_cheat(gb, description, address, GB_CHEAT_ANY_BANK, value, old_value, true, enabled);
    208         }
    209         
    210         if (sscanf(stripped_cheat, "%02hhx%04hx%c", &value, &address, &dummy) == 2) {
    211             address = (uint16_t)(address >> 4) | (uint16_t)(address << 12);
    212             address ^= 0xF000;
    213             if (address > 0x7FFF) {
    214                 return false;
    215             }
    216             return GB_add_cheat(gb, description, address, GB_CHEAT_ANY_BANK, value, false, true, enabled);
    217         }
    218     }
    219     return NULL;
    220 }
    221 
    222 void GB_update_cheat(GB_gameboy_t *gb, const GB_cheat_t *_cheat, const char *description, uint16_t address, uint16_t bank, uint8_t value, uint8_t old_value, bool use_old_value, bool enabled)
    223 {
    224     GB_ASSERT_NOT_RUNNING_OTHER_THREAD(gb)
    225     
    226     GB_cheat_t *cheat = NULL;
    227     for (unsigned i = 0; i < gb->cheat_count; i++) {
    228         if (gb->cheats[i] == _cheat) {
    229             cheat = gb->cheats[i];
    230             break;
    231         }
    232     }
    233     
    234     assert(cheat);
    235     if (!cheat) return;
    236     
    237     if (cheat->address != address) {
    238         /* Remove from old bucket */
    239         GB_cheat_hash_t **hash = &gb->cheat_hash[hash_addr(cheat->address)];
    240         for (unsigned i = 0; i < (*hash)->size; i++) {
    241             if ((*hash)->cheats[i] == cheat) {
    242                 (*hash)->cheats[i] = (*hash)->cheats[--(*hash)->size];
    243                 if ((*hash)->size == 0) {
    244                     free(*hash);
    245                     *hash = NULL;
    246                 }
    247                 else {
    248                     *hash = realloc(*hash, sizeof(GB_cheat_hash_t) + sizeof(cheat) * (*hash)->size);
    249                 }
    250                 break;
    251             }
    252         }
    253         cheat->address = address;
    254         
    255         /* Add to new bucket */
    256         hash = &gb->cheat_hash[hash_addr(address)];
    257         if (!*hash) {
    258             *hash = malloc(sizeof(GB_cheat_hash_t) + sizeof(cheat));
    259             (*hash)->size = 1;
    260             (*hash)->cheats[0] = cheat;
    261         }
    262         else {
    263             (*hash)->size++;
    264             *hash = realloc(*hash, sizeof(GB_cheat_hash_t) + sizeof(cheat) * (*hash)->size);
    265             (*hash)->cheats[(*hash)->size - 1] = cheat;
    266         }
    267     }
    268     cheat->bank = bank;
    269     cheat->value = value;
    270     cheat->old_value = old_value;
    271     cheat->use_old_value = use_old_value;
    272     cheat->enabled = enabled;
    273     if (description != cheat->description) {
    274         strncpy(cheat->description, description, sizeof(cheat->description));
    275         cheat->description[sizeof(cheat->description) - 1] = 0;
    276     }
    277 }
    278 
    279 #define CHEAT_MAGIC 'SBCh'
    280 
    281 int GB_load_cheats(GB_gameboy_t *gb, const char *path, bool replace_existing)
    282 {
    283     GB_ASSERT_NOT_RUNNING_OTHER_THREAD(gb)
    284     
    285     FILE *f = fopen(path, "rb");
    286     if (!f) {
    287         return errno;
    288     }
    289     
    290     uint32_t magic = 0;
    291     uint32_t struct_size = 0;
    292     if (fread(&magic, sizeof(magic), 1, f) != 1) {
    293         goto error;
    294     }
    295     if (fread(&struct_size, sizeof(struct_size), 1, f) != 1) {
    296         goto error;
    297     }
    298     if (magic != LE32(CHEAT_MAGIC) && magic != BE32(CHEAT_MAGIC)) {
    299         GB_log(gb, "The file is not a SameBoy cheat database");
    300         goto error;
    301     }
    302     
    303     if (struct_size != sizeof(GB_cheat_t)) {
    304         GB_log(gb, "This cheat database is not compatible with this version of SameBoy");
    305         goto error;
    306     }
    307     
    308     // Remove all cheats first
    309     if (replace_existing) {
    310         GB_remove_all_cheats(gb);
    311     }
    312     
    313     GB_cheat_t cheat;
    314     while (fread(&cheat, sizeof(cheat), 1, f) == 1) {
    315         if (magic != CHEAT_MAGIC) {
    316             cheat.address = __builtin_bswap16(cheat.address);
    317             cheat.bank = __builtin_bswap16(cheat.bank);
    318         }
    319         cheat.description[sizeof(cheat.description) - 1] = 0;
    320         GB_add_cheat(gb, cheat.description, cheat.address, cheat.bank, cheat.value, cheat.old_value, cheat.use_old_value, cheat.enabled);
    321     }
    322     
    323     fclose(f);
    324     return 0;
    325 
    326 error: 
    327     fclose(f);
    328     return -1;
    329 }
    330 
    331 int GB_save_cheats(GB_gameboy_t *gb, const char *path)
    332 {
    333     if (!gb->cheat_count) return 0; // Nothing to save.
    334     FILE *f = fopen(path, "wb");
    335     if (!f) {
    336         GB_log(gb, "Could not dump cheat database: %s.\n", strerror(errno));
    337         return errno;
    338     }
    339     
    340     uint32_t magic = CHEAT_MAGIC;
    341     uint32_t struct_size = sizeof(GB_cheat_t);
    342     
    343     if (fwrite(&magic, sizeof(magic), 1, f) != 1) {
    344         fclose(f);
    345         return EIO;
    346     }
    347     
    348     if (fwrite(&struct_size, sizeof(struct_size), 1, f) != 1) {
    349         fclose(f);
    350         return EIO;
    351     }
    352     
    353     for (size_t i = 0; i <gb->cheat_count; i++) {
    354         if (fwrite(gb->cheats[i], sizeof(*gb->cheats[i]), 1, f) != 1) {
    355             fclose(f);
    356             return EIO;
    357         }
    358     }
    359     
    360     fclose(f);
    361     return 0;
    362 }

This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.