From 4fd456e1583a4c8686c8de87c2aeb1eb78125be1 Mon Sep 17 00:00:00 2001 From: David Guillen Fandos Date: Wed, 5 May 2021 02:20:00 +0200 Subject: Adding Code Breaker cheat support This works on both interpreter and dynarec. Tested in MIPS, ARM and x86, still needs some more testing, some edge cases can be buggy. --- arm/arm_emit.h | 8 + arm/arm_stub.S | 16 ++ cheats.c | 531 +++++++++++++++++++++----------------------------------- cheats.h | 29 +--- cpu.c | 8 + cpu_threaded.c | 10 ++ gba_memory.c | 4 - libretro.c | 10 +- main.c | 4 +- psp/mips_emit.h | 7 + psp/mips_stub.S | 13 ++ x86/x86_emit.h | 6 + 12 files changed, 283 insertions(+), 363 deletions(-) diff --git a/arm/arm_emit.h b/arm/arm_emit.h index 1432617..4368a80 100644 --- a/arm/arm_emit.h +++ b/arm/arm_emit.h @@ -31,6 +31,8 @@ u32 prepare_store_reg(u32 scratch_reg, u32 reg_index); void generate_load_reg(u32 ireg, u32 reg_index); void complete_store_reg(u32 scratch_reg, u32 reg_index); void complete_store_reg_pc_no_flags(u32 scratch_reg, u32 reg_index); +void thumb_cheat_hook(); +void arm_cheat_hook(); u32 arm_update_gba_arm(u32 pc); u32 arm_update_gba_thumb(u32 pc); @@ -1876,6 +1878,12 @@ u32 execute_store_cpsr_body(u32 _cpsr, u32 store_mask, u32 address) generate_indirect_branch_cycle_update(dual_thumb); \ } \ +#define thumb_process_cheats() \ + generate_function_call(thumb_cheat_hook); + +#define arm_process_cheats() \ + generate_function_call(arm_cheat_hook); + #define thumb_swi() \ generate_swi_hle_handler(opcode & 0xFF, thumb); \ generate_function_call(execute_swi_thumb); \ diff --git a/arm/arm_stub.S b/arm/arm_stub.S index 944d36a..222bb21 100644 --- a/arm/arm_stub.S +++ b/arm/arm_stub.S @@ -288,6 +288,22 @@ arm_update_gba_builder(idle_arm, arm, add) arm_update_gba_builder(idle_thumb, thumb, add) +@ Cheat hooks for master function +@ This is called whenever PC == cheats-master-function +@ Just calls the C function to process cheats + +#define cheat_hook_builder(mode) ;\ +defsymbl(mode##_cheat_hook) ;\ + save_flags() ;\ + store_registers_##mode() ;\ + call_c_function(process_cheats) ;\ + load_registers_##mode() ;\ + restore_flags() ;\ + bx lr ;\ + +cheat_hook_builder(arm) +cheat_hook_builder(thumb) + @ These are b stubs for performing indirect branches. They are not @ linked to and don't return, instead they link elsewhere. diff --git a/cheats.c b/cheats.c index f3e79e4..1a37081 100644 --- a/cheats.c +++ b/cheats.c @@ -19,373 +19,230 @@ #include "common.h" -cheat_type cheats[MAX_CHEATS]; -u32 num_cheats; - -void decrypt_gsa_code(u32 *address_ptr, u32 *value_ptr, cheat_variant_enum - cheat_variant) +typedef struct { - u32 i; - u32 address = *address_ptr; - u32 value = *value_ptr; - u32 r = 0xc6ef3720; - - u32 seeds_v1[4] = - { - 0x09f4fbbd, 0x9681884a, 0x352027e9, 0xf3dee5a7 - }; - u32 seeds_v3[4] = - { - 0x7aa9648f, 0x7fae6994, 0xc0efaad5, 0x42712c57 - }; - u32 *seeds; + bool cheat_active; + struct { + u32 address; + u32 value; + } codes[MAX_CHEAT_CODES]; + unsigned cheat_count; +} cheat_type; - if(cheat_variant == CHEAT_TYPE_GAMESHARK_V1) - seeds = seeds_v1; - else - seeds = seeds_v3; +cheat_type cheats[MAX_CHEATS]; +u32 max_cheat = 0; +u32 cheat_master_hook = 0xffffffff; - for(i = 0; i < 32; i++) +static void update_hook_codebreaker(cheat_type *cheat) +{ + int i; + for(i = 0; i < cheat->cheat_count; i++) { - value -= ((address << 4) + seeds[2]) ^ (address + r) ^ - ((address >> 5) + seeds[3]); - address -= ((value << 4) + seeds[0]) ^ (value + r) ^ - ((value >> 5) + seeds[1]); - r -= 0x9e3779b9; + u32 code = cheat->codes[i].address; + u32 address = code & 0xfffffff; + u32 opcode = code >> 28; + + if (opcode == 1) + { + u32 pcaddr = 0x08000000 | (address & 0x1ffffff); + #ifdef HAVE_DYNAREC + if (cheat_master_hook != pcaddr) + init_caches(); /* Flush caches to install hook */ + #endif + cheat_master_hook = pcaddr; + return; /* Only support for one hook */ + } } - - *address_ptr = address; - *value_ptr = value; } -void add_cheats(char *cheats_filename) +static void process_cheat_codebreaker(cheat_type *cheat, u16 pad) { - FILE *cheats_file; - char current_line[256]; - char *name_ptr; - u32 *cheat_code_ptr; - u32 address, value; - u32 num_cheat_lines; - u32 cheat_name_length; - cheat_variant_enum current_cheat_variant; - - num_cheats = 0; - - cheats_file = fopen(cheats_filename, "rb"); - - if(cheats_file) + int i; + unsigned j; + for(i = 0; i < cheat->cheat_count; i++) { - while(fgets(current_line, 256, cheats_file)) - { - // Get the header line first - name_ptr = strchr(current_line, ' '); - if(name_ptr) + u32 code = cheat->codes[i].address; + u16 value = cheat->codes[i].value; + u32 address = code & 0xfffffff; + u32 opcode = code >> 28; + + switch (opcode) { + case 0: /* Game CRC, ignored for now */ + break; + case 1: /* Master code function */ + break; + case 2: /* 16 bit OR */ + write_memory16(address, read_memory16(address) | value); + break; + case 3: /* 8 bit write */ + write_memory8(address, value); + break; + case 4: /* Slide code, writes a buffer with addr/value strides */ + if (i + 1 < cheat->cheat_count) { - *name_ptr = 0; - name_ptr++; + u16 count = cheat->codes[++i].address; + u16 vincr = cheat->codes[ i].address >> 16; + u16 aincr = cheat->codes[ i].value; + for (j = 0; j < count; j++) + { + write_memory16(address, value); + address += aincr; + value += vincr; + } } - - if(!strcasecmp(current_line, "gameshark_v1") || - !strcasecmp(current_line, "gameshark_v2") || - !strcasecmp(current_line, "PAR_v1") || - !strcasecmp(current_line, "PAR_v2")) + break; + case 5: /* Super code: copies bytes to a buffer addr */ + for (j = 0; j < value * 2 && i < cheat->cheat_count; j++) { - current_cheat_variant = CHEAT_TYPE_GAMESHARK_V1; + u8 bvalue, off = j % 6; + switch (off) { + case 0: + bvalue = cheat->codes[++i].address >> 24; + break; + case 1 ... 3: + bvalue = cheat->codes[i].address >> (24 - off*8); + break; + case 4 ... 5: + bvalue = cheat->codes[i].address >> (40 - off*8); + break; + }; + write_memory8(address, bvalue); + address++; } - else - - if(!strcasecmp(current_line, "gameshark_v3") || - !strcasecmp(current_line, "PAR_v3")) + break; + case 6: /* 16 bit AND */ + write_memory16(address, read_memory16(address) & value); + break; + case 7: /* Compare mem value and execute next cheat */ + if (read_memory16(address) != value) + i++; + break; + case 8: /* 16 bit write */ + write_memory16(address, value); + break; + case 10: /* Compare mem value and skip next cheat */ + if (read_memory16(address) == value) + i++; + break; + case 11: /* Compare mem value and skip next cheat */ + if (read_memory16(address) <= value) + i++; + break; + case 12: /* Compare mem value and skip next cheat */ + if (read_memory16(address) >= value) + i++; + break; + case 13: /* Check button state and execute next cheat */ + switch ((address >> 4) & 0xf) { + case 0: + if (((~pad) & 0x3ff) == value) + i++; + break; + case 1: + if ((pad & value) == value) + i++; + break; + case 2: + if ((pad & value) == 0) + i++; + break; + }; + break; + case 14: /* Increase 16/32 bit memory value */ + if (address & 1) { - current_cheat_variant = CHEAT_TYPE_GAMESHARK_V3; + u32 value32 = (u32)((s16)value); /* Sign extend to 32 bit */ + address &= ~1U; + write_memory32(address, read_memory32(address) + value32); } else { - current_cheat_variant = CHEAT_TYPE_INVALID; - } - - if(current_cheat_variant != CHEAT_TYPE_INVALID) - { - strncpy(cheats[num_cheats].cheat_name, name_ptr, CHEAT_NAME_LENGTH - 1); - cheats[num_cheats].cheat_name[CHEAT_NAME_LENGTH - 1] = 0; - cheat_name_length = strlen(cheats[num_cheats].cheat_name); - if(cheat_name_length && - ((cheats[num_cheats].cheat_name[cheat_name_length - 1] == '\n') || - (cheats[num_cheats].cheat_name[cheat_name_length - 1] == '\r'))) - { - cheats[num_cheats].cheat_name[cheat_name_length - 1] = 0; - cheat_name_length--; - } - - if(cheat_name_length && - cheats[num_cheats].cheat_name[cheat_name_length - 1] == '\r') - { - cheats[num_cheats].cheat_name[cheat_name_length - 1] = 0; - } - - cheats[num_cheats].cheat_variant = current_cheat_variant; - cheat_code_ptr = cheats[num_cheats].cheat_codes; - num_cheat_lines = 0; - - while(fgets(current_line, 256, cheats_file)) - { - if(strlen(current_line) < 3) - break; - - sscanf(current_line, "%08x %08x", &address, &value); - - decrypt_gsa_code(&address, &value, current_cheat_variant); - - cheat_code_ptr[0] = address; - cheat_code_ptr[1] = value; - - cheat_code_ptr += 2; - num_cheat_lines++; - } - - cheats[num_cheats].num_cheat_lines = num_cheat_lines; - - num_cheats++; + write_memory16(address, read_memory16(address) + value); } + break; + case 15: /* Immediate and check and skip */ + if ((read_memory16(address) & value) == 0) + i++; + break; } - - fclose(cheats_file); } } -void process_cheat_gs1(cheat_type *cheat) +void process_cheats(void) { - u32 cheat_opcode; - u32 *code_ptr = cheat->cheat_codes; - u32 address, value; - u32 i; - - for(i = 0; i < cheat->num_cheat_lines; i++) - { - address = code_ptr[0]; - value = code_ptr[1]; - - code_ptr += 2; + u32 i; - cheat_opcode = address >> 28; - address &= 0xFFFFFFF; - - switch(cheat_opcode) - { - case 0x0: - write_memory8(address, value); - break; + for(i = 0; i <= max_cheat; i++) + { + if(!cheats[i].cheat_active) + continue; - case 0x1: - write_memory16(address, value); - break; - - case 0x2: - write_memory32(address, value); - break; - - case 0x3: - { - u32 num_addresses = address & 0xFFFF; - u32 address1, address2; - u32 i2; - - for(i2 = 0; i2 < num_addresses; i2++) - { - address1 = code_ptr[0]; - address2 = code_ptr[1]; - code_ptr += 2; - i++; - - write_memory32(address1, value); - if(address2 != 0) - write_memory32(address2, value); - } - break; - } - - // ROM patch not supported yet - case 0x6: - break; - - // GS button down not supported yet - case 0x8: - break; - - // Reencryption (DEADFACE) not supported yet - case 0xD: - if(read_memory16(address) != (value & 0xFFFF)) - { - code_ptr += 2; - i++; - } - break; - - case 0xE: - if(read_memory16(value & 0xFFFFFFF) != (address & 0xFFFF)) - { - u32 skip = ((address >> 16) & 0x03); - code_ptr += skip * 2; - i += skip; - } - break; - - // Hook routine not supported yet (not important??) - case 0x0F: - break; - } - } + process_cheat_codebreaker(&cheats[i], 0x3ff ^ io_registers[REG_P1]); + } } -// These are especially incomplete. - -void process_cheat_gs3(cheat_type *cheat) +void cheat_clear() { - u32 cheat_opcode; - u32 *code_ptr = cheat->cheat_codes; - u32 address, value; - u32 i; - - for(i = 0; i < cheat->num_cheat_lines; i++) - { - address = code_ptr[0]; - value = code_ptr[1]; - - code_ptr += 2; - - cheat_opcode = address >> 28; - address &= 0xFFFFFFF; - - switch(cheat_opcode) - { - case 0x0: - cheat_opcode = address >> 24; - address = (address & 0xFFFFF) + ((address << 4) & 0xF000000); - - switch(cheat_opcode) - { - case 0x0: - { - u32 iterations = value >> 24; - u32 i2; - - value &= 0xFF; - - for(i2 = 0; i2 <= iterations; i2++, address++) - { - write_memory8(address, value); - } - break; - } - - case 0x2: - { - u32 iterations = value >> 16; - u32 i2; - - value &= 0xFFFF; - - for(i2 = 0; i2 <= iterations; i2++, address += 2) - { - write_memory16(address, value); - } - break; - } - - case 0x4: - write_memory32(address, value); - break; - } - break; - - case 0x4: - cheat_opcode = address >> 24; - address = (address & 0xFFFFF) + ((address << 4) & 0xF000000); - - switch(cheat_opcode) - { - case 0x0: - address = read_memory32(address) + (value >> 24); - write_memory8(address, value & 0xFF); - break; - - case 0x2: - address = read_memory32(address) + ((value >> 16) * 2); - write_memory16(address, value & 0xFFFF); - break; - - case 0x4: - address = read_memory32(address); - write_memory32(address, value); - break; - - } - break; - - case 0x8: - cheat_opcode = address >> 24; - address = (address & 0xFFFFF) + ((address << 4) & 0xF000000); - - switch(cheat_opcode) - { - case 0x0: - value = (value & 0xFF) + read_memory8(address); - write_memory8(address, value); - break; - - case 0x2: - value = (value & 0xFFFF) + read_memory16(address); - write_memory16(address, value); - break; - - case 0x4: - value = value + read_memory32(address); - write_memory32(address, value); - break; - } - break; - - case 0xC: - cheat_opcode = address >> 24; - address = (address & 0xFFFFFF) + 0x4000000; - - switch(cheat_opcode) - { - case 0x6: - write_memory16(address, value); - break; - - case 0x7: - write_memory32(address, value); - break; - } - break; - } - } + int i; + for (i = 0; i < MAX_CHEATS; i++) + { + cheats[i].cheat_count = 0; + cheats[i].cheat_active = false; + } + cheat_master_hook = 0xffffffff; } - -void process_cheats(void) +void cheat_parse(unsigned index, const char *code) { - u32 i; - - for(i = 0; i < num_cheats; i++) - { - if(cheats[i].cheat_active) - { - switch(cheats[i].cheat_variant) - { - case CHEAT_TYPE_GAMESHARK_V1: - process_cheat_gs1(cheats + i); - break; + int pos = 0; + int codelen = strlen(code); + cheat_type *ch = &cheats[index]; + char buf[1024]; + + if (index >= MAX_CHEATS) + return; + if (codelen >= sizeof(buf)) + return; + + memcpy(buf, code, codelen+1); + + /* Init to a known good state */ + ch->cheat_count = 0; + if (index > max_cheat) + max_cheat = index; + + /* Replace all the non-hex chars to spaces */ + for (pos = 0; pos < codelen; pos++) + if (!((buf[pos] >= '0' && buf[pos] <= '9') || + (buf[pos] >= 'a' && buf[pos] <= 'f') || + (buf[pos] >= 'A' && buf[pos] <= 'F'))) + buf[pos] = ' '; + + /* Try to parse as Code Breaker */ + pos = 0; + while (pos < codelen) + { + u32 op1; u16 op2; + if (2 != sscanf(&buf[pos], "%08x %04hx", &op1, &op2)) + break; + ch->codes[ch->cheat_count].address = op1; + ch->codes[ch->cheat_count++].value = op2; + pos += 13; + while (pos < codelen && buf[pos] == ' ') + pos++; + if (ch->cheat_count >= MAX_CHEAT_CODES) + break; + } + + if (pos >= codelen) + { + /* All codes were parsed! Process hook here */ + ch->cheat_active = true; + update_hook_codebreaker(ch); + return; + } + + /* TODO parse other types here */ +} - case CHEAT_TYPE_GAMESHARK_V3: - process_cheat_gs3(cheats + i); - break; - default: - break; - } - } - } -} diff --git a/cheats.h b/cheats.h index e25ad73..496df15 100644 --- a/cheats.h +++ b/cheats.h @@ -17,28 +17,17 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#define CHEAT_NAME_LENGTH 17 +#ifndef __GPSP_CHEATS_H__ +#define __GPSP_CHEATS_H__ -typedef enum -{ - CHEAT_TYPE_GAMESHARK_V1, - CHEAT_TYPE_GAMESHARK_V3, - CHEAT_TYPE_INVALID -} cheat_variant_enum; - -typedef struct -{ - char cheat_name[CHEAT_NAME_LENGTH]; - u32 cheat_active; - u32 cheat_codes[256]; - u32 num_cheat_lines; - cheat_variant_enum cheat_variant; -} cheat_type; +#define MAX_CHEATS 20 +#define MAX_CHEAT_CODES 64 void process_cheats(void); -void add_cheats(char *cheats_filename); +void cheat_parse(unsigned index, const char *code); +void cheat_clear(); + +extern u32 cheat_master_hook; -#define MAX_CHEATS 16 +#endif -extern cheat_type cheats[MAX_CHEATS]; -extern u32 num_cheats; diff --git a/cpu.c b/cpu.c index badb9c2..5df8bb8 100644 --- a/cpu.c +++ b/cpu.c @@ -1679,6 +1679,10 @@ arm_loop: collapse_flags(); cycles_per_instruction = global_cycles_per_instruction; + /* Process cheats if we are about to execute the cheat hook */ + if (pc == cheat_master_hook) + process_cheats(); + old_pc = pc; /* Execute ARM instruction */ @@ -3294,6 +3298,10 @@ thumb_loop: collapse_flags(); + /* Process cheats if we are about to execute the cheat hook */ + if (pc == cheat_master_hook) + process_cheats(); + old_pc = pc; /* Execute THUMB instruction */ diff --git a/cpu_threaded.c b/cpu_threaded.c index a32b1b8..6874ae0 100644 --- a/cpu_threaded.c +++ b/cpu_threaded.c @@ -3303,6 +3303,11 @@ s32 translate_block_arm(u32 pc, translation_region_type block_data[block_data_position].block_offset = translation_ptr; arm_base_cycles(); + if (pc == cheat_master_hook) + { + arm_process_cheats(); + } + translate_arm_instruction(); block_data_position++; @@ -3502,6 +3507,11 @@ s32 translate_block_thumb(u32 pc, translation_region_type block_data[block_data_position].block_offset = translation_ptr; thumb_base_cycles(); + if (pc == cheat_master_hook) + { + thumb_process_cheats(); + } + translate_thumb_instruction(); block_data_position++; diff --git a/gba_memory.c b/gba_memory.c index 8d3d39e..74d22c7 100644 --- a/gba_memory.c +++ b/gba_memory.c @@ -2380,7 +2380,6 @@ char gamepak_filename[512]; u32 load_gamepak(const struct retro_game_info* info, const char *name) { - char cheats_filename[256]; char *p; s32 file_size = load_gamepak_raw(name); @@ -2423,9 +2422,6 @@ u32 load_gamepak(const struct retro_game_info* info, const char *name) if ((load_game_config_over(gamepak_title, gamepak_code, gamepak_maker)) == -1) load_game_config(gamepak_title, gamepak_code, gamepak_maker); - change_ext(gamepak_filename, cheats_filename, ".cht"); - add_cheats(cheats_filename); - return 0; } diff --git a/libretro.c b/libretro.c index 21ca04f..40aec37 100644 --- a/libretro.c +++ b/libretro.c @@ -639,8 +639,16 @@ bool retro_unserialize(const void* data, size_t size) void retro_cheat_reset(void) { + cheat_clear(); +} + +void retro_cheat_set(unsigned index, bool enabled, const char* code) +{ + if (!enabled) + return; + + cheat_parse(index, code); } -void retro_cheat_set(unsigned index, bool enabled, const char* code) {} static void extract_directory(char* buf, const char* path, size_t size) { diff --git a/main.c b/main.c index 759aa94..260edd5 100644 --- a/main.c +++ b/main.c @@ -230,7 +230,9 @@ u32 update_gba(void) update_gbc_sound(cpu_ticks); gbc_sound_update = 0; - process_cheats(); + /* If there's no cheat hook, run on vblank! */ + if (cheat_master_hook == ~0U) + process_cheats(); vcount = 0; // We completed a frame, tell the dynarec to exit to the main thread diff --git a/psp/mips_emit.h b/psp/mips_emit.h index 12685e8..174fee5 100644 --- a/psp/mips_emit.h +++ b/psp/mips_emit.h @@ -44,6 +44,7 @@ void mips_indirect_branch_dual(u32 address); u32 execute_read_cpsr(); u32 execute_read_spsr(); void execute_swi(u32 pc); +void mips_cheat_hook(); u32 execute_spsr_restore(u32 address); void execute_store_cpsr(u32 new_cpsr, u32 store_mask); @@ -2422,6 +2423,12 @@ u32 execute_store_cpsr_body(u32 _cpsr, u32 store_mask, u32 address) generate_indirect_branch_cycle_update(dual); \ } \ +#define thumb_process_cheats() \ + generate_function_call(mips_cheat_hook); + +#define arm_process_cheats() \ + generate_function_call(mips_cheat_hook); + #ifdef TRACE_INSTRUCTIONS void trace_instruction(u32 pc) { diff --git a/psp/mips_stub.S b/psp/mips_stub.S index 08151db..786dc9e 100644 --- a/psp/mips_stub.S +++ b/psp/mips_stub.S @@ -44,6 +44,7 @@ .global init_emitter .global mips_lookup_pc .global smc_write +.global mips_cheat_hook .global write_io_epilogue .global memory_map_read @@ -256,6 +257,17 @@ mips_update_gba: nop +# Processes cheats whenever we hit the master PC +mips_cheat_hook: + sw $ra, REG_SAVE2($16) + save_registers + cfncall process_cheats, 8 + lw $ra, REG_SAVE2($16) + restore_registers + jr $ra + nop + + # Loads the main context and returns to it. # ARM regs must be saved before branching here return_to_main: @@ -649,6 +661,7 @@ fnptrs: .long set_cpu_mode # 5 .long execute_spsr_restore_body # 6 .long execute_store_cpsr_body # 7 + .long process_cheats # 8 #if !defined(HAVE_MMAP) diff --git a/x86/x86_emit.h b/x86/x86_emit.h index ef79110..45b663b 100644 --- a/x86/x86_emit.h +++ b/x86/x86_emit.h @@ -2236,6 +2236,12 @@ static void function_cc execute_swi(u32 pc) generate_indirect_branch_cycle_update(dual); \ } \ +#define thumb_process_cheats() \ + generate_function_call(process_cheats); + +#define arm_process_cheats() \ + generate_function_call(process_cheats); + #define thumb_swi() \ generate_swi_hle_handler(opcode & 0xFF); \ generate_update_pc((pc + 2)); \ -- cgit v1.2.3