392 lines
11 KiB
C++
Executable File
392 lines
11 KiB
C++
Executable File
#include <../base.hpp>
|
|
|
|
#define CHEAT_CPP
|
|
namespace SNES {
|
|
|
|
Cheat cheat;
|
|
|
|
Cheat::cheat_t& Cheat::cheat_t::operator=(const Cheat::cheat_t& source) {
|
|
enabled = source.enabled;
|
|
code = source.code;
|
|
desc = source.desc;
|
|
count = source.count;
|
|
|
|
addr.reset();
|
|
data.reset();
|
|
for(unsigned n = 0; n < count; n++) {
|
|
addr[n] = source.addr[n];
|
|
data[n] = source.data[n];
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
//used to sort cheat code list by description
|
|
bool Cheat::cheat_t::operator<(const Cheat::cheat_t& source) {
|
|
return strcmp(desc, source.desc) < 0;
|
|
}
|
|
|
|
//parse item ("0123-4567+89AB-CDEF"), return cheat_t item
|
|
//return true if code is valid, false otherwise
|
|
bool Cheat::decode(const char *s, Cheat::cheat_t &item) const {
|
|
item.enabled = false;
|
|
item.count = 0;
|
|
|
|
lstring list;
|
|
list.split("+", s);
|
|
|
|
for(unsigned n = 0; n < list.size(); n++) {
|
|
unsigned addr;
|
|
uint8_t data;
|
|
type_t type;
|
|
if(decode(list[n], addr, data, type) == false) return false;
|
|
|
|
item.addr[item.count] = addr;
|
|
item.data[item.count] = data;
|
|
item.count++;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//read() is used by MemBus::read() if Cheat::enabled(addr) returns true to look up cheat code.
|
|
//returns true if cheat code was found, false if it was not.
|
|
//when true, cheat code substitution value is stored in data.
|
|
bool Cheat::read(unsigned addr, uint8_t &data) const {
|
|
addr = mirror_address(addr);
|
|
for(unsigned i = 0; i < code.size(); i++) {
|
|
if(enabled(i) == false) continue;
|
|
|
|
for(unsigned n = 0; n < code[i].count; n++) {
|
|
if(addr == mirror_address(code[i].addr[n])) {
|
|
data = code[i].data[n];
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
//code not found, or code is disabled
|
|
return false;
|
|
}
|
|
|
|
//==============
|
|
//master control
|
|
//==============
|
|
|
|
//global cheat system enable/disable:
|
|
//if disabled, *all* cheat codes are disabled;
|
|
//otherwise only individually disabled codes are.
|
|
|
|
bool Cheat::enabled() const {
|
|
return cheat_system_enabled;
|
|
}
|
|
|
|
void Cheat::enable() {
|
|
cheat_system_enabled = true;
|
|
cheat_enabled = (cheat_system_enabled && cheat_enabled_code_exists);
|
|
}
|
|
|
|
void Cheat::disable() {
|
|
cheat_system_enabled = false;
|
|
cheat_enabled = false;
|
|
}
|
|
|
|
//================================
|
|
//cheat list manipulation routines
|
|
//================================
|
|
|
|
bool Cheat::add(bool enable, const char *code_, const char *desc_) {
|
|
cheat_t item;
|
|
if(decode(code_, item) == false) return false;
|
|
|
|
unsigned i = code.size();
|
|
code[i] = item;
|
|
code[i].enabled = enable;
|
|
code[i].desc = desc_;
|
|
code[i].code = code_;
|
|
encode_description(code[i].desc);
|
|
update(code[i]);
|
|
|
|
update_cheat_status();
|
|
return true;
|
|
}
|
|
|
|
bool Cheat::edit(unsigned i, bool enable, const char *code_, const char *desc_) {
|
|
cheat_t item;
|
|
if(decode(code_, item) == false) return false;
|
|
|
|
//disable current code and clear from code lookup table
|
|
code[i].enabled = false;
|
|
update(code[i]);
|
|
|
|
code[i] = item;
|
|
code[i].enabled = enable;
|
|
code[i].desc = desc_;
|
|
code[i].code = code_;
|
|
encode_description(code[i].desc);
|
|
update(code[i]);
|
|
|
|
update_cheat_status();
|
|
return true;
|
|
}
|
|
|
|
bool Cheat::remove(unsigned i) {
|
|
unsigned size = code.size();
|
|
if(i >= size) return false; //also verifies size cannot be < 1
|
|
|
|
for(unsigned n = i; n < size - 1; n++) code[n] = code[n + 1];
|
|
code.resize(size - 1);
|
|
|
|
update_cheat_status();
|
|
return true;
|
|
}
|
|
|
|
bool Cheat::get(unsigned i, cheat_t &item) const {
|
|
if(i >= code.size()) return false;
|
|
|
|
item = code[i];
|
|
decode_description(item.desc);
|
|
return true;
|
|
}
|
|
|
|
//==============================
|
|
//cheat status modifier routines
|
|
//==============================
|
|
|
|
bool Cheat::enabled(unsigned i) const {
|
|
return (i < code.size() ? code[i].enabled : false);
|
|
}
|
|
|
|
void Cheat::enable(unsigned i) {
|
|
if(i >= code.size()) return;
|
|
|
|
code[i].enabled = true;
|
|
update(code[i]);
|
|
update_cheat_status();
|
|
}
|
|
|
|
void Cheat::disable(unsigned i) {
|
|
if(i >= code.size()) return;
|
|
|
|
code[i].enabled = false;
|
|
update(code[i]);
|
|
update_cheat_status();
|
|
}
|
|
|
|
//===============================
|
|
//cheat file load / save routines
|
|
//
|
|
//file format:
|
|
//"description", status, nnnn-nnnn[+nnnn-nnnn...]\r\n
|
|
//...
|
|
//===============================
|
|
|
|
void Cheat::load(string data) {
|
|
data.replace("\r\n", "\n");
|
|
data.qreplace(" ", "");
|
|
|
|
lstring line;
|
|
line.split("\n", data);
|
|
for(unsigned i = 0; i < line.size(); i++) {
|
|
lstring part;
|
|
part.qsplit(",", line[i]);
|
|
if(part.size() != 3) continue;
|
|
trim(part[0], "\"");
|
|
add(part[1] == "enabled", /* code = */ part[2], /* desc = */ part[0]);
|
|
}
|
|
}
|
|
|
|
string Cheat::save() const {
|
|
string data;
|
|
for(unsigned i = 0; i < code.size(); i++) {
|
|
data << "\"" << code[i].desc << "\", "
|
|
<< (code[i].enabled ? "enabled, " : "disabled, ")
|
|
<< code[i].code << "\r\n";
|
|
}
|
|
return data;
|
|
}
|
|
|
|
void Cheat::clear() {
|
|
cheat_enabled_code_exists = false;
|
|
memset(mask, 0, 0x200000);
|
|
code.reset();
|
|
}
|
|
|
|
Cheat::Cheat() : cheat_system_enabled(true) {
|
|
clear();
|
|
}
|
|
|
|
//==================
|
|
//internal functions
|
|
//==================
|
|
|
|
//string <> binary code translation routines
|
|
//decode() "7e123456" -> 0x7e123456
|
|
//encode() 0x7e123456 -> "7e123456"
|
|
|
|
bool Cheat::decode(const char *s, unsigned &addr, uint8_t &data, type_t &type) const {
|
|
string t = s;
|
|
strlower(t);
|
|
|
|
#define ischr(n) ((n >= '0' && n <= '9') || (n >= 'a' && n <= 'f'))
|
|
|
|
if(strlen(t) == 8 || (strlen(t) == 9 && t[6] == ':')) {
|
|
//strip ':'
|
|
if(strlen(t) == 9 && t[6] == ':') t = string() << substr(t, 0, 6) << substr(t, 7);
|
|
//validate input
|
|
for(unsigned i = 0; i < 8; i++) if(!ischr(t[i])) return false;
|
|
|
|
type = ProActionReplay;
|
|
unsigned r = strhex((const char*)t);
|
|
addr = r >> 8;
|
|
data = r & 0xff;
|
|
return true;
|
|
} else if(strlen(t) == 9 && t[4] == '-') {
|
|
//strip '-'
|
|
t = string() << substr(t, 0, 4) << substr(t, 5);
|
|
//validate input
|
|
for(unsigned i = 0; i < 8; i++) if(!ischr(t[i])) return false;
|
|
|
|
type = GameGenie;
|
|
strtr(t, "df4709156bc8a23e", "0123456789abcdef");
|
|
unsigned r = strhex((const char*)t);
|
|
//8421 8421 8421 8421 8421 8421
|
|
//abcd efgh ijkl mnop qrst uvwx
|
|
//ijkl qrst opab cduv wxef ghmn
|
|
addr = (!!(r & 0x002000) << 23) | (!!(r & 0x001000) << 22)
|
|
| (!!(r & 0x000800) << 21) | (!!(r & 0x000400) << 20)
|
|
| (!!(r & 0x000020) << 19) | (!!(r & 0x000010) << 18)
|
|
| (!!(r & 0x000008) << 17) | (!!(r & 0x000004) << 16)
|
|
| (!!(r & 0x800000) << 15) | (!!(r & 0x400000) << 14)
|
|
| (!!(r & 0x200000) << 13) | (!!(r & 0x100000) << 12)
|
|
| (!!(r & 0x000002) << 11) | (!!(r & 0x000001) << 10)
|
|
| (!!(r & 0x008000) << 9) | (!!(r & 0x004000) << 8)
|
|
| (!!(r & 0x080000) << 7) | (!!(r & 0x040000) << 6)
|
|
| (!!(r & 0x020000) << 5) | (!!(r & 0x010000) << 4)
|
|
| (!!(r & 0x000200) << 3) | (!!(r & 0x000100) << 2)
|
|
| (!!(r & 0x000080) << 1) | (!!(r & 0x000040) << 0);
|
|
data = r >> 24;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool Cheat::encode(string &s, unsigned addr, uint8_t data, type_t type) const {
|
|
char t[16];
|
|
|
|
if(type == ProActionReplay) {
|
|
sprintf(t, "%.6x%.2x", addr, data);
|
|
s = t;
|
|
return true;
|
|
} else if(type == GameGenie) {
|
|
unsigned r = addr;
|
|
addr = (!!(r & 0x008000) << 23) | (!!(r & 0x004000) << 22)
|
|
| (!!(r & 0x002000) << 21) | (!!(r & 0x001000) << 20)
|
|
| (!!(r & 0x000080) << 19) | (!!(r & 0x000040) << 18)
|
|
| (!!(r & 0x000020) << 17) | (!!(r & 0x000010) << 16)
|
|
| (!!(r & 0x000200) << 15) | (!!(r & 0x000100) << 14)
|
|
| (!!(r & 0x800000) << 13) | (!!(r & 0x400000) << 12)
|
|
| (!!(r & 0x200000) << 11) | (!!(r & 0x100000) << 10)
|
|
| (!!(r & 0x000008) << 9) | (!!(r & 0x000004) << 8)
|
|
| (!!(r & 0x000002) << 7) | (!!(r & 0x000001) << 6)
|
|
| (!!(r & 0x080000) << 5) | (!!(r & 0x040000) << 4)
|
|
| (!!(r & 0x020000) << 3) | (!!(r & 0x010000) << 2)
|
|
| (!!(r & 0x000800) << 1) | (!!(r & 0x000400) << 0);
|
|
sprintf(t, "%.2x%.2x-%.4x", data, addr >> 16, addr & 0xffff);
|
|
strtr(t, "0123456789abcdef", "df4709156bc8a23e");
|
|
s = t;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//speed up S-CPU memory reads by disabling cheat code lookup when either:
|
|
//a) cheat system is disabled by user, or b) no enabled cheat codes exist
|
|
void Cheat::update_cheat_status() {
|
|
for(unsigned i = 0; i < code.size(); i++) {
|
|
if(code[i].enabled) {
|
|
cheat_enabled_code_exists = true;
|
|
cheat_enabled = (cheat_system_enabled && cheat_enabled_code_exists);
|
|
return;
|
|
}
|
|
}
|
|
cheat_enabled_code_exists = false;
|
|
cheat_enabled = false;
|
|
}
|
|
|
|
//address lookup table manipulation and mirroring
|
|
//mirror_address() 0x000000 -> 0x7e0000
|
|
//set() enable specified address, mirror accordingly
|
|
//clear() disable specified address, mirror accordingly
|
|
unsigned Cheat::mirror_address(unsigned addr) const {
|
|
if((addr & 0x40e000) != 0x0000) return addr;
|
|
//8k WRAM mirror
|
|
//$[00-3f|80-bf]:[0000-1fff] -> $7e:[0000-1fff]
|
|
return (0x7e0000 + (addr & 0x1fff));
|
|
}
|
|
|
|
//updates mask[] table enabled bits;
|
|
//must be called after modifying item.enabled state.
|
|
void Cheat::update(const cheat_t &item) {
|
|
for(unsigned n = 0; n < item.count; n++) {
|
|
(item.enabled) ? set(item.addr[n]) : clear(item.addr[n]);
|
|
}
|
|
}
|
|
|
|
void Cheat::set(unsigned addr) {
|
|
addr = mirror_address(addr);
|
|
|
|
mask[addr >> 3] |= 1 << (addr & 7);
|
|
if((addr & 0xffe000) == 0x7e0000) {
|
|
//mirror $7e:[0000-1fff] to $[00-3f|80-bf]:[0000-1fff]
|
|
unsigned mirror;
|
|
for(unsigned x = 0; x <= 0x3f; x++) {
|
|
mirror = ((0x00 + x) << 16) + (addr & 0x1fff);
|
|
mask[mirror >> 3] |= 1 << (mirror & 7);
|
|
mirror = ((0x80 + x) << 16) + (addr & 0x1fff);
|
|
mask[mirror >> 3] |= 1 << (mirror & 7);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Cheat::clear(unsigned addr) {
|
|
addr = mirror_address(addr);
|
|
|
|
//if there is more than one cheat code using the same address,
|
|
//(eg with a different override value) then do not clear code
|
|
//lookup table entry.
|
|
uint8_t r;
|
|
if(read(addr, r) == true) return;
|
|
|
|
mask[addr >> 3] &= ~(1 << (addr & 7));
|
|
if((addr & 0xffe000) == 0x7e0000) {
|
|
//mirror $7e:[0000-1fff] to $[00-3f|80-bf]:[0000-1fff]
|
|
unsigned mirror;
|
|
for(unsigned x = 0; x <= 0x3f; x++) {
|
|
mirror = ((0x00 + x) << 16) + (addr & 0x1fff);
|
|
mask[mirror >> 3] &= ~(1 << (mirror & 7));
|
|
mirror = ((0x80 + x) << 16) + (addr & 0x1fff);
|
|
mask[mirror >> 3] &= ~(1 << (mirror & 7));
|
|
}
|
|
}
|
|
}
|
|
|
|
//these two functions are used to safely store description text inside .cfg file format.
|
|
|
|
string& Cheat::encode_description(string &desc) const {
|
|
desc.replace("\"", "\\q");
|
|
desc.replace("\n", "\\n");
|
|
return desc;
|
|
}
|
|
|
|
string& Cheat::decode_description(string &desc) const {
|
|
desc.replace("\\q", "\"");
|
|
desc.replace("\\n", "\n");
|
|
return desc;
|
|
}
|
|
|
|
};
|
|
|