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.
This commit is contained in:
David Guillen Fandos 2021-05-05 02:20:00 +02:00 committed by David G. F
parent 52088a4d10
commit 4fd456e158
12 changed files with 289 additions and 369 deletions

View File

@ -31,6 +31,8 @@ u32 prepare_store_reg(u32 scratch_reg, u32 reg_index);
void generate_load_reg(u32 ireg, 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(u32 scratch_reg, u32 reg_index);
void complete_store_reg_pc_no_flags(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_arm(u32 pc);
u32 arm_update_gba_thumb(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); \ 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() \ #define thumb_swi() \
generate_swi_hle_handler(opcode & 0xFF, thumb); \ generate_swi_hle_handler(opcode & 0xFF, thumb); \
generate_function_call(execute_swi_thumb); \ generate_function_call(execute_swi_thumb); \

View File

@ -288,6 +288,22 @@ arm_update_gba_builder(idle_arm, arm, add)
arm_update_gba_builder(idle_thumb, thumb, 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 @ These are b stubs for performing indirect branches. They are not
@ linked to and don't return, instead they link elsewhere. @ linked to and don't return, instead they link elsewhere.

545
cheats.c
View File

@ -19,373 +19,230 @@
#include "common.h" #include "common.h"
typedef struct
{
bool cheat_active;
struct {
u32 address;
u32 value;
} codes[MAX_CHEAT_CODES];
unsigned cheat_count;
} cheat_type;
cheat_type cheats[MAX_CHEATS]; cheat_type cheats[MAX_CHEATS];
u32 num_cheats; u32 max_cheat = 0;
u32 cheat_master_hook = 0xffffffff;
void decrypt_gsa_code(u32 *address_ptr, u32 *value_ptr, cheat_variant_enum static void update_hook_codebreaker(cheat_type *cheat)
cheat_variant)
{ {
u32 i; int i;
u32 address = *address_ptr; for(i = 0; i < cheat->cheat_count; i++)
u32 value = *value_ptr;
u32 r = 0xc6ef3720;
u32 seeds_v1[4] =
{ {
0x09f4fbbd, 0x9681884a, 0x352027e9, 0xf3dee5a7 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 */
}
}
}
static void process_cheat_codebreaker(cheat_type *cheat, u16 pad)
{
int i;
unsigned j;
for(i = 0; i < cheat->cheat_count; i++)
{
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)
{
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;
}
}
break;
case 5: /* Super code: copies bytes to a buffer addr */
for (j = 0; j < value * 2 && i < cheat->cheat_count; j++)
{
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;
}; };
u32 seeds_v3[4] = write_memory8(address, bvalue);
{ address++;
0x7aa9648f, 0x7fae6994, 0xc0efaad5, 0x42712c57 }
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;
}; };
u32 *seeds; break;
case 14: /* Increase 16/32 bit memory value */
if(cheat_variant == CHEAT_TYPE_GAMESHARK_V1) if (address & 1)
seeds = seeds_v1;
else
seeds = seeds_v3;
for(i = 0; i < 32; i++)
{ {
value -= ((address << 4) + seeds[2]) ^ (address + r) ^ u32 value32 = (u32)((s16)value); /* Sign extend to 32 bit */
((address >> 5) + seeds[3]); address &= ~1U;
address -= ((value << 4) + seeds[0]) ^ (value + r) ^ write_memory32(address, read_memory32(address) + value32);
((value >> 5) + seeds[1]);
r -= 0x9e3779b9;
}
*address_ptr = address;
*value_ptr = value;
}
void add_cheats(char *cheats_filename)
{
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)
{
while(fgets(current_line, 256, cheats_file))
{
// Get the header line first
name_ptr = strchr(current_line, ' ');
if(name_ptr)
{
*name_ptr = 0;
name_ptr++;
}
if(!strcasecmp(current_line, "gameshark_v1") ||
!strcasecmp(current_line, "gameshark_v2") ||
!strcasecmp(current_line, "PAR_v1") ||
!strcasecmp(current_line, "PAR_v2"))
{
current_cheat_variant = CHEAT_TYPE_GAMESHARK_V1;
}
else
if(!strcasecmp(current_line, "gameshark_v3") ||
!strcasecmp(current_line, "PAR_v3"))
{
current_cheat_variant = CHEAT_TYPE_GAMESHARK_V3;
} }
else else
{ {
current_cheat_variant = CHEAT_TYPE_INVALID; write_memory16(address, read_memory16(address) + value);
} }
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; break;
case 15: /* Immediate and check and skip */
sscanf(current_line, "%08x %08x", &address, &value); if ((read_memory16(address) & value) == 0)
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++;
}
}
fclose(cheats_file);
}
}
void process_cheat_gs1(cheat_type *cheat)
{
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:
write_memory8(address, value);
break;
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++; 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; break;
} }
} }
} }
// These are especially incomplete.
void process_cheat_gs3(cheat_type *cheat)
{
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;
}
}
}
void process_cheats(void) void process_cheats(void)
{ {
u32 i; u32 i;
for(i = 0; i < num_cheats; i++) for(i = 0; i <= max_cheat; i++)
{ {
if(cheats[i].cheat_active) if(!cheats[i].cheat_active)
{ continue;
switch(cheats[i].cheat_variant)
{
case CHEAT_TYPE_GAMESHARK_V1:
process_cheat_gs1(cheats + i);
break;
case CHEAT_TYPE_GAMESHARK_V3: process_cheat_codebreaker(&cheats[i], 0x3ff ^ io_registers[REG_P1]);
process_cheat_gs3(cheats + i);
break;
default:
break;
}
}
} }
} }
void cheat_clear()
{
int i;
for (i = 0; i < MAX_CHEATS; i++)
{
cheats[i].cheat_count = 0;
cheats[i].cheat_active = false;
}
cheat_master_hook = 0xffffffff;
}
void cheat_parse(unsigned index, const char *code)
{
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 */
}

View File

@ -17,28 +17,17 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * 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 #define MAX_CHEATS 20
{ #define MAX_CHEAT_CODES 64
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;
void process_cheats(void); void process_cheats(void);
void add_cheats(char *cheats_filename); void cheat_parse(unsigned index, const char *code);
void cheat_clear();
#define MAX_CHEATS 16 extern u32 cheat_master_hook;
#endif
extern cheat_type cheats[MAX_CHEATS];
extern u32 num_cheats;

8
cpu.c
View File

@ -1679,6 +1679,10 @@ arm_loop:
collapse_flags(); collapse_flags();
cycles_per_instruction = global_cycles_per_instruction; 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; old_pc = pc;
/* Execute ARM instruction */ /* Execute ARM instruction */
@ -3294,6 +3298,10 @@ thumb_loop:
collapse_flags(); collapse_flags();
/* Process cheats if we are about to execute the cheat hook */
if (pc == cheat_master_hook)
process_cheats();
old_pc = pc; old_pc = pc;
/* Execute THUMB instruction */ /* Execute THUMB instruction */

View File

@ -3303,6 +3303,11 @@ s32 translate_block_arm(u32 pc, translation_region_type
block_data[block_data_position].block_offset = translation_ptr; block_data[block_data_position].block_offset = translation_ptr;
arm_base_cycles(); arm_base_cycles();
if (pc == cheat_master_hook)
{
arm_process_cheats();
}
translate_arm_instruction(); translate_arm_instruction();
block_data_position++; 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; block_data[block_data_position].block_offset = translation_ptr;
thumb_base_cycles(); thumb_base_cycles();
if (pc == cheat_master_hook)
{
thumb_process_cheats();
}
translate_thumb_instruction(); translate_thumb_instruction();
block_data_position++; block_data_position++;

View File

@ -2380,7 +2380,6 @@ char gamepak_filename[512];
u32 load_gamepak(const struct retro_game_info* info, const char *name) u32 load_gamepak(const struct retro_game_info* info, const char *name)
{ {
char cheats_filename[256];
char *p; char *p;
s32 file_size = load_gamepak_raw(name); 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) if ((load_game_config_over(gamepak_title, gamepak_code, gamepak_maker)) == -1)
load_game_config(gamepak_title, gamepak_code, gamepak_maker); load_game_config(gamepak_title, gamepak_code, gamepak_maker);
change_ext(gamepak_filename, cheats_filename, ".cht");
add_cheats(cheats_filename);
return 0; return 0;
} }

View File

@ -639,8 +639,16 @@ bool retro_unserialize(const void* data, size_t size)
void retro_cheat_reset(void) 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) static void extract_directory(char* buf, const char* path, size_t size)
{ {

2
main.c
View File

@ -230,6 +230,8 @@ u32 update_gba(void)
update_gbc_sound(cpu_ticks); update_gbc_sound(cpu_ticks);
gbc_sound_update = 0; gbc_sound_update = 0;
/* If there's no cheat hook, run on vblank! */
if (cheat_master_hook == ~0U)
process_cheats(); process_cheats();
vcount = 0; vcount = 0;

View File

@ -44,6 +44,7 @@ void mips_indirect_branch_dual(u32 address);
u32 execute_read_cpsr(); u32 execute_read_cpsr();
u32 execute_read_spsr(); u32 execute_read_spsr();
void execute_swi(u32 pc); void execute_swi(u32 pc);
void mips_cheat_hook();
u32 execute_spsr_restore(u32 address); u32 execute_spsr_restore(u32 address);
void execute_store_cpsr(u32 new_cpsr, u32 store_mask); 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); \ 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 #ifdef TRACE_INSTRUCTIONS
void trace_instruction(u32 pc) void trace_instruction(u32 pc)
{ {

View File

@ -44,6 +44,7 @@
.global init_emitter .global init_emitter
.global mips_lookup_pc .global mips_lookup_pc
.global smc_write .global smc_write
.global mips_cheat_hook
.global write_io_epilogue .global write_io_epilogue
.global memory_map_read .global memory_map_read
@ -256,6 +257,17 @@ mips_update_gba:
nop 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. # Loads the main context and returns to it.
# ARM regs must be saved before branching here # ARM regs must be saved before branching here
return_to_main: return_to_main:
@ -649,6 +661,7 @@ fnptrs:
.long set_cpu_mode # 5 .long set_cpu_mode # 5
.long execute_spsr_restore_body # 6 .long execute_spsr_restore_body # 6
.long execute_store_cpsr_body # 7 .long execute_store_cpsr_body # 7
.long process_cheats # 8
#if !defined(HAVE_MMAP) #if !defined(HAVE_MMAP)

View File

@ -2236,6 +2236,12 @@ static void function_cc execute_swi(u32 pc)
generate_indirect_branch_cycle_update(dual); \ 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() \ #define thumb_swi() \
generate_swi_hle_handler(opcode & 0xFF); \ generate_swi_hle_handler(opcode & 0xFF); \
generate_update_pc((pc + 2)); \ generate_update_pc((pc + 2)); \