diff --git a/libretro/libretro.c b/libretro/libretro.c index 7903552..d1737f3 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -81,6 +81,32 @@ static retro_input_state_t input_state_cb; static retro_environment_t environ_cb; static retro_audio_sample_batch_t audio_cb; +/* Cheat Support */ +#define MAX_CHEATS (150) +#define MAX_DESC_LENGTH (63) + +typedef struct +{ + char code[12]; + char text[MAX_DESC_LENGTH]; + uint8_t enable; + uint16_t data; + uint16_t old; + uint32_t address; + uint8_t *prev; +} CHEATENTRY; + +static int maxcheats = 0; +static int maxROMcheats = 0; +static int maxRAMcheats = 0; + +static CHEATENTRY cheatlist[MAX_CHEATS]; +static uint8_t cheatIndexes[MAX_CHEATS]; + +static char ggvalidchars[] = "ABCDEFGHJKLMNPRSTVWXYZ0123456789"; + +static char arvalidchars[] = "0123456789ABCDEF"; + /************************************ * Genesis Plus GX implementation ************************************/ @@ -169,6 +195,8 @@ int load_archive(char *filename, unsigned char *buffer, int maxsize, char *exten return size; } +void RAMCheatUpdate(void); + void osd_input_update(void) { int i, player = 0; @@ -176,6 +204,9 @@ void osd_input_update(void) input_poll_cb(); + /* Update RAM patches */ + RAMCheatUpdate(); + for (i = 0; i < MAX_INPUTS; i++) { temp = 0; @@ -941,6 +972,314 @@ static void check_variables(void) } } +/* Cheat Support */ +static uint32_t decode_cheat(char *string, int index) +{ + char *p; + int i,n; + uint32_t len = 0; + uint32_t address = 0; + uint16_t data = 0; + uint8_t ref = 0; + /* 16-bit Game Genie code (ABCD-EFGH) */ + if ((strlen(string) >= 9) && (string[4] == '-')) + { + /* 16-bit system only */ + if ((system_hw & SYSTEM_PBC) != SYSTEM_MD) + { + return 0; + } + for (i = 0; i < 8; i++) + { + if (i == 4) string++; + p = strchr (ggvalidchars, *string++); + if (p == NULL) return 0; + n = p - ggvalidchars; + switch (i) + { + case 0: + data |= n << 3; + break; + case 1: + data |= n >> 2; + address |= (n & 3) << 14; + break; + case 2: + address |= n << 9; + break; + case 3: + address |= (n & 0xF) << 20 | (n >> 4) << 8; + break; + case 4: + data |= (n & 1) << 12; + address |= (n >> 1) << 16; + break; + case 5: + data |= (n & 1) << 15 | (n >> 1) << 8; + break; + case 6: + data |= (n >> 3) << 13; + address |= (n & 7) << 5; + break; + case 7: + address |= n; + break; + } + } + /* code length */ + len = 9; + } + /* 8-bit Game Genie code (DDA-AAA-XXX) */ + else if ((strlen(string) >= 11) && (string[3] == '-') && (string[7] == '-')) + { + /* 8-bit system only */ + if ((system_hw & SYSTEM_PBC) == SYSTEM_MD) + { + return 0; + } + /* decode 8-bit data */ + for (i=0; i<2; i++) + { + p = strchr (arvalidchars, *string++); + if (p == NULL) return 0; + n = (p - arvalidchars) & 0xF; + data |= (n << ((1 - i) * 4)); + } + /* decode 16-bit address (low 12-bits) */ + for (i=0; i<3; i++) + { + if (i==1) string++; /* skip separator */ + p = strchr (arvalidchars, *string++); + if (p == NULL) return 0; + n = (p - arvalidchars) & 0xF; + address |= (n << ((2 - i) * 4)); + } + /* decode 16-bit address (high 4-bits) */ + p = strchr (arvalidchars, *string++); + if (p == NULL) return 0; + n = (p - arvalidchars) & 0xF; + n ^= 0xF; /* bits inversion */ + address |= (n << 12); + /* RAM address are also supported */ + if (address >= 0xC000) + { + /* convert to 24-bit Work RAM address */ + address = 0xFF0000 | (address & 0x1FFF); + } + /* decode reference 8-bit data */ + for (i=0; i<2; i++) + { + string++; /* skip separator and 2nd digit */ + p = strchr (arvalidchars, *string++); + if (p == NULL) return 0; + n = (p - arvalidchars) & 0xF; + ref |= (n << ((1 - i) * 4)); + } + ref = (ref >> 2) | ((ref & 0x03) << 6); /* 2-bit right rotation */ + ref ^= 0xBA; /* XOR */ + /* update old data value */ + cheatlist[index].old = ref; + /* code length */ + len = 11; + } + /* Action Replay code */ + else if (string[6] == ':') + { + if ((system_hw & SYSTEM_PBC) == SYSTEM_MD) + { + /* 16-bit code (AAAAAA:DDDD) */ + if (strlen(string) < 11) return 0; + /* decode 24-bit address */ + for (i=0; i<6; i++) + { + p = strchr (arvalidchars, *string++); + if (p == NULL) return 0; + n = (p - arvalidchars) & 0xF; + address |= (n << ((5 - i) * 4)); + } + /* decode 16-bit data */ + string++; + for (i=0; i<4; i++) + { + p = strchr (arvalidchars, *string++); + if (p == NULL) return 0; + n = (p - arvalidchars) & 0xF; + data |= (n << ((3 - i) * 4)); + } + /* code length */ + len = 11; + } + else + { + /* 8-bit code (xxAAAA:DD) */ + if (strlen(string) < 9) return 0; + /* decode 16-bit address */ + string+=2; + for (i=0; i<4; i++) + { + p = strchr (arvalidchars, *string++); + if (p == NULL) return 0; + n = (p - arvalidchars) & 0xF; + address |= (n << ((3 - i) * 4)); + } + /* ROM addresses are not supported */ + if (address < 0xC000) return 0; + /* convert to 24-bit Work RAM address */ + address = 0xFF0000 | (address & 0x1FFF); + /* decode 8-bit data */ + string++; + for (i=0; i<2; i++) + { + p = strchr (arvalidchars, *string++); + if (p == NULL) return 0; + n = (p - arvalidchars) & 0xF; + data |= (n << ((1 - i) * 4)); + } + /* code length */ + len = 9; + } + } + /* Valid code found ? */ + if (len) + { + /* update cheat address & data values */ + cheatlist[index].address = address; + cheatlist[index].data = data; + cheatlist[index].enable = 1; // Enable the cheat by default + } + /* return code length (0 = invalid) */ + return len; +} + +static void apply_cheats(void) +{ + uint8_t *ptr; + /* clear ROM&RAM patches counter */ + maxROMcheats = maxRAMcheats = 0; + int i; + for (i = 0; i < maxcheats; i++) + { + if (cheatlist[i].enable) + { + if (cheatlist[i].address < cart.romsize) + { + if ((system_hw & SYSTEM_PBC) == SYSTEM_MD) + { + /* patch ROM data */ + cheatlist[i].old = *(uint16_t *)(cart.rom + (cheatlist[i].address & 0xFFFFFE)); + *(uint16_t *)(cart.rom + (cheatlist[i].address & 0xFFFFFE)) = cheatlist[i].data; + } + else + { + /* add ROM patch */ + maxROMcheats++; + cheatIndexes[MAX_CHEATS - maxROMcheats] = i; + /* get current banked ROM address */ + ptr = &z80_readmap[(cheatlist[i].address) >> 10][cheatlist[i].address & 0x03FF]; + /* check if reference matches original ROM data */ + if (((uint8_t)cheatlist[i].old) == *ptr) + { + /* patch data */ + *ptr = cheatlist[i].data; + /* save patched ROM address */ + cheatlist[i].prev = ptr; + } + else + { + /* no patched ROM address yet */ + cheatlist[i].prev = NULL; + } + } + } + else if (cheatlist[i].address >= 0xFF0000) + { + /* add RAM patch */ + cheatIndexes[maxRAMcheats++] = i; + } + } + } +} + +static void clear_cheats(void) +{ + int i = maxcheats; + /* disable cheats in reversed order in case the same address is used by multiple patches */ + while (i > 0) + { + if (cheatlist[i-1].enable) + { + if (cheatlist[i-1].address < cart.romsize) + { + if ((system_hw & SYSTEM_PBC) == SYSTEM_MD) + { + /* restore original ROM data */ + *(uint16_t *)(cart.rom + (cheatlist[i-1].address & 0xFFFFFE)) = cheatlist[i-1].old; + } + else + { + /* check if previous banked ROM address has been patched */ + if (cheatlist[i-1].prev != NULL) + { + /* restore original data */ + *cheatlist[i-1].prev = cheatlist[i-1].old; + /* no more patched ROM address */ + cheatlist[i-1].prev = NULL; + } + } + } + } + i--; + } +} + +static void remove_cheats(void) +{ + int i = maxcheats; + while (i > 0) + { + if (cheatlist[i-1].enable) + { + cheatlist[i-1].text[0] = 0; + cheatlist[i-1].code[0] = 0; + cheatlist[i-1].address = 0; + cheatlist[i-1].data = 0; + cheatlist[i-1].enable = 0; + + maxcheats--; + } + + i--; + } +} + +/**************************************************************************** +* RAMCheatUpdate +* +* Apply RAM patches (this should be called once per frame) +* +****************************************************************************/ +void RAMCheatUpdate(void) +{ + int index, cnt = maxRAMcheats; + while (cnt) + { + /* get cheat index */ + index = cheatIndexes[--cnt]; + /* apply RAM patch */ + //if (cheatlist[index].data & 0xFF00) + if (cheatlist[index].data & 0x00FF) // For LSB? + { + /* word patch */ + *(uint16_t *)(work_ram + (cheatlist[index].address & 0xFFFE)) = cheatlist[index].data; + } + else + { + /* byte patch */ + work_ram[cheatlist[index].address & 0xFFFF] = cheatlist[index].data; + } + } +} + /************************************ * libretro implementation ************************************/ @@ -1173,12 +1512,24 @@ bool retro_unserialize(const void *data, size_t size) return TRUE; } -void retro_cheat_reset(void) {} +void retro_cheat_reset(void) +{ + /* clear existing ROM patches */ + clear_cheats(); + /* remove cheats from the list */ + remove_cheats(); +} + void retro_cheat_set(unsigned index, bool enabled, const char *code) { - (void)index; - (void)enabled; - (void)code; + /* clear existing ROM patches */ + clear_cheats(); + /* interpret code and give it an index */ + decode_cheat((char *)code, maxcheats); + /* increment cheat count */ + maxcheats++; + /* apply ROM patches */ + apply_cheats(); } bool retro_load_game(const struct retro_game_info *info)