mirror of
https://github.com/mupen64plus/mupen64plus-oldsvn.git
synced 2025-04-02 10:52:35 -04:00
For example, before this fix, if you enabled the "Mario flies without wingcap" cheat during gameplay, it would work, but if you then disabled it during gameplay, Mario would still be able to fly. Now it correctly restores the old memory values so Mario can't fly anymore after you disable the cheat. Note, because cheats are basically writing to a random area of memory, this function may not always restore the previous state depending on the way that area of memory is used by the game.
634 lines
18 KiB
C
634 lines
18 KiB
C
/**
|
|
* Mupen64 - cheat.c
|
|
* Copyright (C) 2008 okaygo
|
|
*
|
|
* Mupen64Plus homepage: http://code.google.com/p/mupen64plus/
|
|
*
|
|
*
|
|
* This program is free software; you can redistribute it and/
|
|
* or modify it under the terms of the GNU General Public Li-
|
|
* cence as published by the Free Software Foundation; either
|
|
* version 2 of the Licence, or any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be use-
|
|
* ful, but WITHOUT ANY WARRANTY; without even the implied war-
|
|
* ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
* See the GNU General Public Licence for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public
|
|
* Licence along with this program; if not, write to the Free
|
|
* Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139,
|
|
* USA.
|
|
*
|
|
**/
|
|
|
|
// gameshark and xploder64 reference: http://doc.kodewerx.net/hacking_n64.html
|
|
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <zlib.h> // TODO: compress cfg file
|
|
|
|
#include "../memory/memory.h"
|
|
#include "cheat.h"
|
|
#include "main.h"
|
|
#include "rom.h"
|
|
#include "util.h" // list utilities
|
|
#include "config.h"
|
|
|
|
#define CHEAT_FILENAME "cheats.cfg"
|
|
|
|
// public globals
|
|
list_t g_Cheats = NULL; // list of all supported cheats
|
|
|
|
// static globals
|
|
static rom_cheats_t *g_Current = NULL; // current loaded rom
|
|
|
|
// private functions
|
|
static unsigned short read_address_16bit(unsigned int address)
|
|
{
|
|
return *(unsigned short *)((rdramb + ((address & 0xFFFFFF)^S16)));
|
|
}
|
|
|
|
static unsigned short read_address_8bit(unsigned int address)
|
|
{
|
|
*(unsigned short *)((rdramb + ((address & 0xFFFFFF)^S8)));
|
|
}
|
|
|
|
static void update_address_16bit(unsigned int address, unsigned short new_value)
|
|
{
|
|
*(unsigned short *)((rdramb + ((address & 0xFFFFFF)^S16))) = new_value;
|
|
}
|
|
|
|
static void update_address_8bit(unsigned int address, unsigned char new_value)
|
|
{
|
|
*(unsigned short *)((rdramb + ((address & 0xFFFFFF)^S8))) = new_value;
|
|
}
|
|
|
|
static int address_equal_to_8bit(unsigned int address, unsigned char value)
|
|
{
|
|
unsigned char value_read;
|
|
value_read = *(unsigned short *)((rdramb + ((address & 0xFFFFFF)^S8)));
|
|
return value_read == value;
|
|
}
|
|
|
|
static int address_equal_to_16bit(unsigned int address, unsigned short value)
|
|
{
|
|
unsigned short value_read;
|
|
value_read = *(unsigned short *)((rdramb + ((address & 0xFFFFFF)^S16)));
|
|
return value_read == value;
|
|
}
|
|
|
|
// individual application - returns 0 if we are supposed to skip the next cheat
|
|
// (only really used on conditional codes)
|
|
static int execute_cheat(unsigned int address, unsigned short value, unsigned short *old_value)
|
|
{
|
|
switch (address & 0xFF000000)
|
|
{
|
|
case 0x80000000:
|
|
case 0x88000000:
|
|
case 0xA0000000:
|
|
case 0xA8000000:
|
|
case 0xF0000000:
|
|
// if pointer to old value is valid and uninitialized, write current value to it
|
|
if(old_value && *old_value == CHEAT_CODE_MAGIC_VALUE)
|
|
*old_value = read_address_8bit(address);
|
|
update_address_8bit(address,value);
|
|
return 1;
|
|
break;
|
|
case 0x81000000:
|
|
case 0x89000000:
|
|
case 0xA1000000:
|
|
case 0xA9000000:
|
|
case 0xF1000000:
|
|
// if pointer to old value is valid and uninitialized, write current value to it
|
|
if(old_value && *old_value == CHEAT_CODE_MAGIC_VALUE)
|
|
*old_value = read_address_16bit(address);
|
|
update_address_16bit(address,value);
|
|
return 1;
|
|
break;
|
|
case 0xD0000000:
|
|
case 0xD8000000:
|
|
return address_equal_to_8bit(address,value);
|
|
break;
|
|
case 0xD1000000:
|
|
case 0xD9000000:
|
|
return address_equal_to_16bit(address,value);
|
|
break;
|
|
case 0xD2000000:
|
|
case 0xDB000000:
|
|
return !(address_equal_to_8bit(address,value));
|
|
break;
|
|
case 0xD3000000:
|
|
case 0xDA000000:
|
|
return !(address_equal_to_16bit(address,value));
|
|
break;
|
|
case 0xEE000000:
|
|
// most likely, this doesnt do anything.
|
|
execute_cheat(0xF1000318, 0x0040, NULL);
|
|
execute_cheat(0xF100031A, 0x0000, NULL);
|
|
return 1;
|
|
break;
|
|
default:
|
|
return 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int gs_button_pressed(void)
|
|
{
|
|
return key_pressed('g') ||
|
|
key_pressed('G') ||
|
|
event_active(config_get_string("Joy Mapping GS Button", ""));
|
|
}
|
|
|
|
// public functions
|
|
void cheat_apply_cheats(int entry)
|
|
{
|
|
list_node_t *node1, *node2;
|
|
cheat_t *cheat;
|
|
cheat_code_t *code;
|
|
|
|
// if no cheats for current rom, return
|
|
if(!g_Current) return;
|
|
|
|
list_foreach(g_Current->cheats, node1)
|
|
{
|
|
cheat = (cheat_t *)node1->data;
|
|
if(cheat->always_enabled || cheat->enabled)
|
|
{
|
|
cheat->was_enabled = 1;
|
|
switch(entry)
|
|
{
|
|
case ENTRY_BOOT:
|
|
list_foreach(cheat->cheat_codes, node2)
|
|
{
|
|
code = (cheat_code_t *)node2->data;
|
|
|
|
// code should only be written once at boot time
|
|
if((code->address & 0xF0000000) == 0xF0000000)
|
|
execute_cheat(code->address, code->value, &code->old_value);
|
|
}
|
|
break;
|
|
case ENTRY_VI:
|
|
list_foreach(cheat->cheat_codes, node2)
|
|
{
|
|
code = (cheat_code_t *)node2->data;
|
|
|
|
// conditional cheat codes
|
|
if((code->address & 0xF0000000) == 0xD0000000)
|
|
{
|
|
// if code needs GS button pressed and it's not, skip it
|
|
if(((code->address & 0xFF000000) == 0xD8000000 ||
|
|
(code->address & 0xFF000000) == 0xD9000000 ||
|
|
(code->address & 0xFF000000) == 0xDA000000 ||
|
|
(code->address & 0xFF000000) == 0xDB000000) &&
|
|
!gs_button_pressed())
|
|
{
|
|
// skip next code
|
|
if(node2->next != NULL)
|
|
node2 = node2->next;
|
|
continue;
|
|
}
|
|
|
|
// if condition true, execute next cheat code
|
|
if(execute_cheat(code->address, code->value, NULL))
|
|
{
|
|
node2 = node2->next;
|
|
code = (cheat_code_t *)node2->data;
|
|
|
|
// if code needs GS button pressed, don't save old value
|
|
if(((code->address & 0xFF000000) == 0xD8000000 ||
|
|
(code->address & 0xFF000000) == 0xD9000000 ||
|
|
(code->address & 0xFF000000) == 0xDA000000 ||
|
|
(code->address & 0xFF000000) == 0xDB000000))
|
|
execute_cheat(code->address, code->value, NULL);
|
|
else
|
|
execute_cheat(code->address, code->value, &code->old_value);
|
|
}
|
|
// if condition false, skip next code
|
|
else
|
|
{
|
|
if(node2->next != NULL)
|
|
node2 = node2->next;
|
|
continue;
|
|
}
|
|
}
|
|
// GS button triggers cheat code
|
|
else if((code->address & 0xFF000000) == 0x88000000 ||
|
|
(code->address & 0xFF000000) == 0x89000000 ||
|
|
(code->address & 0xFF000000) == 0xA8000000 ||
|
|
(code->address & 0xFF000000) == 0xA9000000)
|
|
{
|
|
if(gs_button_pressed())
|
|
execute_cheat(code->address, code->value, NULL);
|
|
}
|
|
// normal cheat code
|
|
else
|
|
{
|
|
// exclude boot-time cheat codes
|
|
if((code->address & 0xF0000000) != 0xF0000000)
|
|
execute_cheat(code->address, code->value, &code->old_value);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
// if cheat was enabled, but is now disabled, restore old memory values
|
|
else if(cheat->was_enabled)
|
|
{
|
|
cheat->was_enabled = 0;
|
|
switch(entry)
|
|
{
|
|
case ENTRY_VI:
|
|
list_foreach(cheat->cheat_codes, node2)
|
|
{
|
|
code = (cheat_code_t *)node2->data;
|
|
|
|
// set memory back to old value and clear saved copy of old value
|
|
if(code->old_value != CHEAT_CODE_MAGIC_VALUE)
|
|
{
|
|
execute_cheat(code->address, code->old_value, NULL);
|
|
code->old_value = CHEAT_CODE_MAGIC_VALUE;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** cheat_read_config
|
|
* Read config file and populate list of supported cheats. Format of cheat file is:
|
|
*
|
|
* {Some Game's CRC}
|
|
* name=Some Game
|
|
*
|
|
* [Cheat Name 1]
|
|
* enabled=1
|
|
* XXXXXXXX YYYY <-- cheat code (address, new value)
|
|
* XXXXXXXX YYYY
|
|
* XXXXXXXX YYYY
|
|
*
|
|
*
|
|
* [Cheat Name 2]
|
|
* enabled=0
|
|
* XXXXXXXX YYYY
|
|
* XXXXXXXX YYYY
|
|
* XXXXXXXX YYYY
|
|
*
|
|
* {Another Game's CRC}
|
|
* name=Another Game
|
|
* ...
|
|
*/
|
|
void cheat_read_config(void)
|
|
{
|
|
char path[PATH_MAX];
|
|
FILE *f = NULL;
|
|
char line[2048];
|
|
|
|
rom_cheats_t *romcheat = NULL;
|
|
cheat_t *cheat = NULL;
|
|
cheat_code_t *cheatcode = NULL;
|
|
|
|
snprintf(path, PATH_MAX, "%s%s", get_configpath(), CHEAT_FILENAME);
|
|
f = fopen(path, "r");
|
|
|
|
// if no cheat config file installed, exit quietly
|
|
if(!f) return;
|
|
|
|
// parse file lines
|
|
while(!feof(f))
|
|
{
|
|
if( !fgets( line, 2048, f ) )
|
|
break;
|
|
|
|
trim(line);
|
|
|
|
if(strlen(line) == 0 ||
|
|
line[0] == '#') // comment
|
|
continue;
|
|
|
|
// beginning of new rom section
|
|
if (line[0] == '{' && line[strlen(line)-1] == '}')
|
|
{
|
|
romcheat = cheat_new_rom();
|
|
sscanf(line, "{%x %x}", &romcheat->crc1, &romcheat->crc2);
|
|
continue;
|
|
}
|
|
|
|
// rom name (just informational)
|
|
if(strncasecmp(line, "name=", 5) == 0)
|
|
{
|
|
romcheat->rom_name = strdup(strstr(line, "=")+1);
|
|
continue;
|
|
}
|
|
|
|
// name of cheat
|
|
if(line[0] == '[' && line[strlen(line)-1] == ']')
|
|
{
|
|
cheat = cheat_new_cheat(romcheat);
|
|
line[strlen(line)-1] = '\0'; // get rid of trailing ']'
|
|
cheat->name = strdup(line+1);
|
|
continue;
|
|
}
|
|
|
|
// cheat always enabled?
|
|
if(strncasecmp(line, "enabled=", 8) == 0)
|
|
{
|
|
sscanf(line, "enabled=%d", &cheat->always_enabled);
|
|
continue;
|
|
}
|
|
|
|
// else, line must be a cheat code
|
|
cheatcode = cheat_new_cheat_code(cheat);
|
|
sscanf(line, "%x %hx", &cheatcode->address, &cheatcode->value);
|
|
}
|
|
fclose(f);
|
|
}
|
|
|
|
/** cheat_write_config
|
|
* Write out all cheats to file
|
|
*/
|
|
void cheat_write_config(void)
|
|
{
|
|
char path[PATH_MAX];
|
|
FILE *f = NULL;
|
|
|
|
list_node_t *node1, *node2, *node3;
|
|
rom_cheats_t *romcheat = NULL;
|
|
cheat_t *cheat = NULL;
|
|
cheat_code_t *cheatcode = NULL;
|
|
|
|
// if no cheats, don't bother writing out file
|
|
if(list_empty(g_Cheats)) return;
|
|
|
|
snprintf(path, PATH_MAX, "%s%s", get_configpath(), CHEAT_FILENAME);
|
|
f = fopen(path, "w");
|
|
if(!f)
|
|
return;
|
|
|
|
list_foreach(g_Cheats, node1)
|
|
{
|
|
romcheat = (rom_cheats_t *)node1->data;
|
|
|
|
fprintf(f, "{%.8x %.8x}\n"
|
|
"name=%s\n",
|
|
romcheat->crc1,
|
|
romcheat->crc2,
|
|
romcheat->rom_name);
|
|
|
|
list_foreach(romcheat->cheats, node2)
|
|
{
|
|
cheat = (cheat_t *)node2->data;
|
|
|
|
fprintf(f, "\n[%s]\n", cheat->name);
|
|
fprintf(f, "enabled=%d\n", cheat->always_enabled? 1 : 0);
|
|
|
|
list_foreach(cheat->cheat_codes, node3)
|
|
{
|
|
cheatcode = (cheat_code_t *)node3->data;
|
|
|
|
fprintf(f, "%.8x %.4hx\n", cheatcode->address, cheatcode->value);
|
|
}
|
|
}
|
|
fprintf(f, "\n");
|
|
}
|
|
|
|
fclose(f);
|
|
}
|
|
|
|
/** cheat_delete_all
|
|
* Delete all cheat-related structures
|
|
*/
|
|
void cheat_delete_all(void)
|
|
{
|
|
list_node_t *node1, *node2, *node3;
|
|
rom_cheats_t *romcheat = NULL;
|
|
cheat_t *cheat = NULL;
|
|
cheat_code_t *cheatcode = NULL;
|
|
|
|
list_foreach(g_Cheats, node1)
|
|
{
|
|
romcheat = (rom_cheats_t *)node1->data;
|
|
|
|
if(romcheat->rom_name)
|
|
free(romcheat->rom_name);
|
|
|
|
list_foreach(romcheat->cheats, node2)
|
|
{
|
|
cheat = (cheat_t *)node2->data;
|
|
|
|
if(cheat->name)
|
|
free(cheat->name);
|
|
list_foreach(cheat->cheat_codes, node3)
|
|
{
|
|
cheatcode = (cheat_code_t *)node3->data;
|
|
free(cheatcode);
|
|
}
|
|
list_delete(&cheat->cheat_codes);
|
|
free(cheat);
|
|
}
|
|
list_delete(&romcheat->cheats);
|
|
free(romcheat);
|
|
}
|
|
list_delete(&g_Cheats);
|
|
g_Current = NULL;
|
|
}
|
|
|
|
/** cheat_load_current_rom
|
|
* sets pointer to cheats for currently loaded rom.
|
|
*/
|
|
void cheat_load_current_rom(void)
|
|
{
|
|
list_node_t *node, *node2;
|
|
rom_cheats_t *rom_cheat = NULL;
|
|
cheat_t *cheat = NULL;
|
|
cheat_code_t *cheatcode = NULL;
|
|
unsigned int crc1, crc2;
|
|
|
|
g_Current = NULL;
|
|
|
|
if(!ROM_HEADER) return;
|
|
|
|
if(g_MemHasBeenBSwapped)
|
|
{
|
|
crc1 = sl(ROM_HEADER->CRC1);
|
|
crc2 = sl(ROM_HEADER->CRC2);
|
|
}
|
|
else
|
|
{
|
|
crc1 = ROM_HEADER->CRC1;
|
|
crc2 = ROM_HEADER->CRC2;
|
|
}
|
|
|
|
list_foreach(g_Cheats, node)
|
|
{
|
|
rom_cheat = (rom_cheats_t *)node->data;
|
|
|
|
if(crc1 == rom_cheat->crc1 &&
|
|
crc2 == rom_cheat->crc2)
|
|
{
|
|
g_Current = rom_cheat;
|
|
}
|
|
}
|
|
|
|
// if rom was found, clear any old saved values from cheat codes
|
|
if(g_Current)
|
|
{
|
|
list_foreach(g_Current->cheats, node)
|
|
{
|
|
cheat = (cheat_t *)node->data;
|
|
|
|
list_foreach(cheat->cheat_codes, node2)
|
|
{
|
|
cheatcode = (cheat_code_t *)node2->data;
|
|
cheatcode->old_value = CHEAT_CODE_MAGIC_VALUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** cheat_new_rom
|
|
* creates a new rom_cheats_t structure, appends it to the global list and returns it.
|
|
*/
|
|
rom_cheats_t *cheat_new_rom(void)
|
|
{
|
|
rom_cheats_t *romcheat = malloc(sizeof(rom_cheats_t));
|
|
|
|
if(!romcheat)
|
|
return NULL;
|
|
|
|
memset(romcheat, 0, sizeof(rom_cheats_t));
|
|
list_append(&g_Cheats, romcheat);
|
|
|
|
return romcheat;
|
|
}
|
|
|
|
/** cheat_new_cheat
|
|
* creates a new cheat_t structure, appends it to the given rom_cheats_t struct and returns it.
|
|
*/
|
|
cheat_t *cheat_new_cheat(rom_cheats_t *romcheat)
|
|
{
|
|
cheat_t *cheat = malloc(sizeof(cheat_t));
|
|
|
|
if(!cheat)
|
|
return NULL;
|
|
|
|
memset(cheat, 0, sizeof(cheat_t));
|
|
list_append(&romcheat->cheats, cheat);
|
|
|
|
return cheat;
|
|
}
|
|
|
|
/** cheat_new_cheat_code
|
|
* creates a new cheat_code_t structure, appends it to the given cheat_t struct and returns it.
|
|
*/
|
|
cheat_code_t *cheat_new_cheat_code(cheat_t *cheat)
|
|
{
|
|
cheat_code_t *code = malloc(sizeof(cheat_code_t));
|
|
|
|
if(!code)
|
|
return NULL;
|
|
|
|
memset(code, 0, sizeof(cheat_code_t));
|
|
code->old_value = CHEAT_CODE_MAGIC_VALUE; // initialize old_value
|
|
list_append(&cheat->cheat_codes, code);
|
|
|
|
return code;
|
|
}
|
|
|
|
/** cheat_delete_rom
|
|
* deletes given rom structure and removes it from the global list.
|
|
*/
|
|
void cheat_delete_rom(rom_cheats_t *romcheat)
|
|
{
|
|
list_node_t *romnode, *node1, *node2;
|
|
cheat_t *cheat;
|
|
cheat_code_t *cheatcode;
|
|
|
|
if(!romcheat)
|
|
return;
|
|
|
|
if(romcheat->rom_name)
|
|
free(romcheat->rom_name);
|
|
|
|
// remove any cheats associated with this rom
|
|
list_foreach(romcheat->cheats, node1)
|
|
{
|
|
cheat = (cheat_t *)node1->data;
|
|
|
|
if(cheat->name)
|
|
free(cheat->name);
|
|
|
|
// remove any codes associated with this cheat
|
|
list_foreach(cheat->cheat_codes, node2)
|
|
{
|
|
cheatcode = (cheat_code_t *)node2->data;
|
|
if(cheatcode)
|
|
free(cheatcode);
|
|
}
|
|
list_delete(&cheat->cheat_codes);
|
|
free(cheat);
|
|
}
|
|
list_delete(&romcheat->cheats);
|
|
|
|
// locate node associated with rom
|
|
romnode = list_find_node(g_Cheats, romcheat);
|
|
|
|
// free rom and remove it from the list
|
|
free(romcheat);
|
|
list_node_delete(&g_Cheats, romnode);
|
|
}
|
|
|
|
/** cheat_delete_cheat
|
|
* deletes given cheat structure and removes it from the given rom's cheat list.
|
|
*/
|
|
void cheat_delete_cheat(rom_cheats_t *romcheat, cheat_t *cheat)
|
|
{
|
|
list_node_t *cheatnode, *node;
|
|
cheat_code_t *cheatcode;
|
|
|
|
if(!cheat)
|
|
return;
|
|
|
|
if(cheat->name)
|
|
free(cheat->name);
|
|
|
|
// remove any codes associated with this cheat
|
|
list_foreach(cheat->cheat_codes, node)
|
|
{
|
|
cheatcode = (cheat_code_t *)node->data;
|
|
if(cheatcode)
|
|
free(cheatcode);
|
|
}
|
|
list_delete(&cheat->cheat_codes);
|
|
|
|
// locate node associated with cheat
|
|
cheatnode = list_find_node(romcheat->cheats, cheat);
|
|
|
|
// free cheat and remove it from the rom's cheat list
|
|
free(cheat);
|
|
list_node_delete(&romcheat->cheats, cheatnode);
|
|
}
|
|
|
|
/** cheat_delete_cheat_code
|
|
* deletes given cheat code structure and removes it from the given cheat's code list.
|
|
*/
|
|
void cheat_delete_cheat_code(cheat_t *cheat, cheat_code_t *cheatcode)
|
|
{
|
|
list_node_t *codenode;
|
|
|
|
if(!cheatcode)
|
|
return;
|
|
|
|
// locate node associated with cheat
|
|
codenode = list_find_node(cheat->cheat_codes, cheatcode);
|
|
|
|
// free cheat code and remove it from the cheat's code list
|
|
free(cheatcode);
|
|
list_node_delete(&cheat->cheat_codes, codenode);
|
|
}
|