GameShark cheat codes support
This commit is contained in:
parent
8b986dfab3
commit
163888f329
|
@ -1325,6 +1325,8 @@ if(BUILD_TESTING)
|
|||
core/deps/gtest/src/gtest_main.cc)
|
||||
|
||||
target_sources(${PROJECT_NAME} PRIVATE
|
||||
tests/src/CheatManagerTest.cpp
|
||||
tests/src/ConfigFileTest.cpp
|
||||
tests/src/div32_test.cpp
|
||||
tests/src/test_stubs.cpp
|
||||
tests/src/serialize_test.cpp
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
/*
|
||||
Command line parsing
|
||||
~yay~
|
||||
|
||||
Nothing too interesting here, really
|
||||
*/
|
||||
|
||||
#include <cstdio>
|
||||
|
@ -10,39 +7,9 @@
|
|||
#include <cstring>
|
||||
|
||||
#include "cfg/cfg.h"
|
||||
#include "stdclass.h"
|
||||
|
||||
char* trim_ws(char* str)
|
||||
{
|
||||
if (str==0 || strlen(str)==0)
|
||||
return 0;
|
||||
|
||||
while(*str)
|
||||
{
|
||||
if (!isspace(*str))
|
||||
break;
|
||||
str++;
|
||||
}
|
||||
|
||||
size_t l=strlen(str);
|
||||
|
||||
if (l==0)
|
||||
return 0;
|
||||
|
||||
while(l>0)
|
||||
{
|
||||
if (!isspace(str[l-1]))
|
||||
break;
|
||||
str[l-1]=0;
|
||||
l--;
|
||||
}
|
||||
|
||||
if (l==0)
|
||||
return 0;
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
int setconfig(char** arg,int cl)
|
||||
static int setconfig(char *arg[], int cl)
|
||||
{
|
||||
int rv=0;
|
||||
for(;;)
|
||||
|
@ -50,43 +17,38 @@ int setconfig(char** arg,int cl)
|
|||
if (cl<1)
|
||||
{
|
||||
WARN_LOG(COMMON, "-config : invalid number of parameters, format is section:key=value");
|
||||
return rv;
|
||||
break;
|
||||
}
|
||||
char* sep=strstr(arg[1],":");
|
||||
if (sep==0)
|
||||
std::string value(arg[1]);
|
||||
auto seppos = value.find(':');
|
||||
if (seppos == std::string::npos)
|
||||
{
|
||||
WARN_LOG(COMMON, "-config : invalid parameter %s, format is section:key=value", arg[1]);
|
||||
return rv;
|
||||
WARN_LOG(COMMON, "-config : invalid parameter %s, format is section:key=value", value.c_str());
|
||||
break;
|
||||
}
|
||||
char* value=strstr(sep+1,"=");
|
||||
if (value==0)
|
||||
auto eqpos = value.find('=', seppos);
|
||||
if (eqpos == std::string::npos)
|
||||
{
|
||||
WARN_LOG(COMMON, "-config : invalid parameter %s, format is section:key=value", arg[1]);
|
||||
return rv;
|
||||
WARN_LOG(COMMON, "-config : invalid parameter %s, format is section:key=value", value.c_str());
|
||||
break;
|
||||
}
|
||||
|
||||
*sep++=0;
|
||||
*value++=0;
|
||||
std::string sect = trim_ws(value.substr(0, seppos));
|
||||
std::string key = trim_ws(value.substr(seppos + 1, eqpos - seppos - 1));
|
||||
value = trim_ws(value.substr(eqpos + 1));
|
||||
|
||||
char* sect=trim_ws(arg[1]);
|
||||
char* key=trim_ws(sep);
|
||||
value=trim_ws(value);
|
||||
|
||||
if (sect==0 || key==0)
|
||||
if (sect.empty() || key.empty())
|
||||
{
|
||||
WARN_LOG(COMMON, "-config : invalid parameter, format is section:key=value");
|
||||
return rv;
|
||||
break;
|
||||
}
|
||||
|
||||
const char* constval = value;
|
||||
if (constval==0)
|
||||
constval="";
|
||||
INFO_LOG(COMMON, "Virtual cfg %s:%s=%s", sect, key, constval);
|
||||
INFO_LOG(COMMON, "Virtual cfg %s:%s=%s", sect.c_str(), key.c_str(), value.c_str());
|
||||
|
||||
cfgSetVirtual(sect, key, constval);
|
||||
cfgSetVirtual(sect, key, value);
|
||||
rv++;
|
||||
|
||||
if (cl>=3 && stricmp(arg[2],",")==0)
|
||||
if (cl>=3 && strcmp(arg[2],",")==0)
|
||||
{
|
||||
cl-=2;
|
||||
arg+=2;
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
#include "ini.h"
|
||||
#include "types.h"
|
||||
#include <sstream>
|
||||
|
||||
char* trim_ws(char* str);
|
||||
#include "stdclass.h"
|
||||
|
||||
namespace emucfg {
|
||||
|
||||
|
@ -197,74 +196,37 @@ void ConfigFile::set_bool(const std::string& section_name, const std::string& en
|
|||
|
||||
void ConfigFile::parse(FILE* file)
|
||||
{
|
||||
if(file == NULL)
|
||||
{
|
||||
if (file == nullptr)
|
||||
return;
|
||||
}
|
||||
|
||||
char line[512];
|
||||
char current_section[512] = { '\0' };
|
||||
std::string section;
|
||||
int cline = 0;
|
||||
while(file && !feof(file))
|
||||
while (true)
|
||||
{
|
||||
if (std::fgets(line, 512, file) == NULL || std::feof(file))
|
||||
{
|
||||
if (std::fgets(line, sizeof(line), file) == nullptr)
|
||||
break;
|
||||
}
|
||||
|
||||
cline++;
|
||||
|
||||
if (strlen(line) < 3)
|
||||
std::string s(line);
|
||||
s = trim_ws(s, " \r\n");
|
||||
if (s.empty())
|
||||
continue;
|
||||
if (s.length() >= 3 && s[0] == '[' && s[s.length() - 1] == ']')
|
||||
{
|
||||
section = s.substr(1, s.length() - 2);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line[strlen(line)-1] == '\r' ||
|
||||
line[strlen(line)-1] == '\n')
|
||||
auto eqpos = s.find('=');
|
||||
if (eqpos == std::string::npos)
|
||||
{
|
||||
line[strlen(line)-1] = '\0';
|
||||
}
|
||||
|
||||
char* tl = trim_ws(line);
|
||||
|
||||
if (tl[0] == '[' && tl[strlen(tl)-1] == ']')
|
||||
{
|
||||
tl[strlen(tl)-1] = '\0';
|
||||
|
||||
// FIXME: Data loss if buffer is too small
|
||||
strncpy(current_section, tl+1, sizeof(current_section));
|
||||
current_section[sizeof(current_section) - 1] = '\0';
|
||||
|
||||
trim_ws(current_section);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (strlen(current_section) == 0)
|
||||
{
|
||||
continue; //no open section
|
||||
}
|
||||
|
||||
char* separator = strstr(tl, "=");
|
||||
|
||||
if (!separator)
|
||||
{
|
||||
WARN_LOG(COMMON, "Malformed entry on config - ignoring @ %d(%s)", cline, tl);
|
||||
continue;
|
||||
}
|
||||
|
||||
*separator = '\0';
|
||||
|
||||
char* name = trim_ws(tl);
|
||||
char* value = trim_ws(separator + 1);
|
||||
if (name == NULL || value == NULL)
|
||||
{
|
||||
//printf("Malformed entry on config - ignoring @ %d(%s)\n",cline, tl);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
this->set(std::string(current_section), std::string(name), std::string(value));
|
||||
}
|
||||
WARN_LOG(COMMON, "Malformed entry on config - ignoring line %d: %s", cline, s.c_str());
|
||||
continue;
|
||||
}
|
||||
std::string property = trim_ws(s.substr(0, eqpos));
|
||||
std::string value = trim_ws(s.substr(eqpos + 1));
|
||||
if (value.length() >= 2 && value[0] == '"' && value[value.length() - 1] == '"')
|
||||
value = value.substr(1, value.length() - 2);
|
||||
set(section, property, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -275,7 +237,8 @@ void ConfigFile::save(FILE* file)
|
|||
const std::string& section_name = section_it.first;
|
||||
const ConfigSection& section = section_it.second;
|
||||
|
||||
std::fprintf(file, "[%s]\n", section_name.c_str());
|
||||
if (!section_name.empty())
|
||||
std::fprintf(file, "[%s]\n", section_name.c_str());
|
||||
|
||||
for (const auto& entry_it : section.entries)
|
||||
{
|
||||
|
@ -283,8 +246,6 @@ void ConfigFile::save(FILE* file)
|
|||
const ConfigEntry& entry = entry_it.second;
|
||||
std::fprintf(file, "%s = %s\n", entry_name.c_str(), entry.get_string().c_str());
|
||||
}
|
||||
|
||||
std::fputs("\n", file);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
370
core/cheats.cpp
370
core/cheats.cpp
|
@ -24,6 +24,7 @@
|
|||
#include "hw/sh4/sh4_mem.h"
|
||||
#include "reios/reios.h"
|
||||
#include "cfg/cfg.h"
|
||||
#include "cfg/ini.h"
|
||||
|
||||
const WidescreenCheat CheatManager::widescreen_cheats[] =
|
||||
{
|
||||
|
@ -312,60 +313,39 @@ void CheatManager::loadCheatFile(const std::string& filename)
|
|||
WARN_LOG(COMMON, "Cannot open cheat file '%s'", filename.c_str());
|
||||
return;
|
||||
}
|
||||
cheats.clear();
|
||||
Cheat cheat;
|
||||
int cheatNumber = 0;
|
||||
char buffer[512];
|
||||
while (fgets(buffer, sizeof(buffer), cheatfile) != nullptr)
|
||||
{
|
||||
std::string l = buffer;
|
||||
auto equalPos = l.find('=');
|
||||
if (equalPos == std::string::npos)
|
||||
continue;
|
||||
auto quotePos = l.find('"', equalPos);
|
||||
if (quotePos == std::string::npos)
|
||||
continue;
|
||||
auto quote2Pos = l.find('"', quotePos + 1);
|
||||
if (quote2Pos == std::string::npos)
|
||||
continue;
|
||||
if (l.substr(0, 5) != "cheat" || l[5] < '0' || l[5] > '9')
|
||||
continue;
|
||||
char *p;
|
||||
int number = strtol(&l[5], &p, 10);
|
||||
if (number != cheatNumber && cheat.type != Cheat::Type::disabled)
|
||||
{
|
||||
cheatNumber = number;
|
||||
cheats.push_back(cheat);
|
||||
cheat = Cheat();
|
||||
}
|
||||
std::string param = trim_trailing_ws(l.substr(p - &l[0], equalPos - (p - &l[0])));
|
||||
std::string value = l.substr(quotePos + 1, quote2Pos - quotePos - 1);
|
||||
emucfg::ConfigFile cfg;
|
||||
cfg.parse(cheatfile);
|
||||
fclose(cheatfile);
|
||||
|
||||
if (param == "_address")
|
||||
int count = cfg.get_int("", "cheats", 0);
|
||||
cheats.clear();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
std::string prefix = "cheat" + std::to_string(i) + "_";
|
||||
Cheat cheat{};
|
||||
cheat.description = cfg.get("", prefix + "desc", "Cheat " + std::to_string(i + 1));
|
||||
cheat.address = cfg.get_int("", prefix + "address", -1);
|
||||
if (cheat.address >= RAM_SIZE)
|
||||
{
|
||||
cheat.address = strtol(value.c_str(), nullptr, 10);
|
||||
verify(cheat.address < RAM_SIZE);
|
||||
WARN_LOG(COMMON, "Invalid address %x", cheat.address);
|
||||
continue;
|
||||
}
|
||||
else if (param == "_cheat_type")
|
||||
cheat.type = (Cheat::Type)strtol(value.c_str(), nullptr, 10);
|
||||
else if (param == "_desc")
|
||||
cheat.description = value;
|
||||
else if (param == "_memory_search_size")
|
||||
cheat.size = 1 << strtol(value.c_str(), nullptr, 10);
|
||||
else if (param == "_value")
|
||||
cheat.value = strtol(value.c_str(), nullptr, 10);
|
||||
else if (param == "_repeat_count")
|
||||
cheat.repeatCount = strtol(value.c_str(), nullptr, 10);
|
||||
else if (param == "_repeat_add_to_value")
|
||||
cheat.repeatValueIncrement = strtol(value.c_str(), nullptr, 10);
|
||||
else if (param == "_repeat_add_to_address")
|
||||
cheat.repeatAddressIncrement = strtol(value.c_str(), nullptr, 10);
|
||||
else if (param == "_enable")
|
||||
cheat.enabled = value == "true";
|
||||
cheat.type = (Cheat::Type)cfg.get_int("", prefix + "cheat_type", (int)Cheat::Type::disabled);
|
||||
cheat.size = 1 << cfg.get_int("", prefix + "memory_search_size", 0);
|
||||
cheat.value = cfg.get_int("", prefix + "value", cheat.value);
|
||||
cheat.repeatCount = cfg.get_int("", prefix + "repeat_count", cheat.repeatCount);
|
||||
cheat.repeatValueIncrement = cfg.get_int("", prefix + "repeat_add_to_value", cheat.repeatValueIncrement);
|
||||
cheat.repeatAddressIncrement = cfg.get_int("", prefix + "repeat_add_to_address", cheat.repeatAddressIncrement);
|
||||
cheat.enabled = cfg.get_bool("", prefix + "enable", false);
|
||||
cheat.destAddress = cfg.get_int("", prefix + "dest_address", 0);
|
||||
if (cheat.destAddress >= RAM_SIZE)
|
||||
{
|
||||
WARN_LOG(COMMON, "Invalid address %x", cheat.destAddress);
|
||||
continue;
|
||||
}
|
||||
if (cheat.type != Cheat::Type::disabled)
|
||||
cheats.push_back(cheat);
|
||||
}
|
||||
std::fclose(cheatfile);
|
||||
if (cheat.type != Cheat::Type::disabled)
|
||||
cheats.push_back(cheat);
|
||||
active = !cheats.empty();
|
||||
INFO_LOG(COMMON, "%d cheats loaded", (int)cheats.size());
|
||||
cfgSaveStr("cheats", gameId, filename);
|
||||
|
@ -506,6 +486,10 @@ void CheatManager::apply()
|
|||
case Cheat::Type::runNextIfLt:
|
||||
skipCheat = readRam(cheat.address, cheat.size) >= cheat.value;
|
||||
break;
|
||||
case Cheat::Type::copy:
|
||||
for (u32 i = 0; i < cheat.repeatCount; i++)
|
||||
writeRam(cheat.destAddress + i, readRam(cheat.address + i, cheat.size), cheat.size);
|
||||
break;
|
||||
}
|
||||
if (setValue)
|
||||
{
|
||||
|
@ -520,3 +504,289 @@ void CheatManager::apply()
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
static std::vector<u32> parseCodes(const std::string& s)
|
||||
{
|
||||
std::vector<u32> codes;
|
||||
std::string curCode;
|
||||
for (u8 c : s)
|
||||
{
|
||||
if (std::isxdigit(c))
|
||||
{
|
||||
curCode += c;
|
||||
if (curCode.length() == 8)
|
||||
{
|
||||
codes.push_back(strtol(curCode.c_str(), nullptr, 16));
|
||||
curCode.clear();
|
||||
}
|
||||
}
|
||||
else if (!curCode.empty())
|
||||
throw FlycastException("Invalid cheat code");
|
||||
}
|
||||
if (!curCode.empty())
|
||||
{
|
||||
if (curCode.length() != 8)
|
||||
throw FlycastException("Invalid cheat code");
|
||||
codes.push_back(strtol(curCode.c_str(), nullptr, 16));
|
||||
}
|
||||
|
||||
return codes;
|
||||
}
|
||||
|
||||
void CheatManager::addGameSharkCheat(const std::string& name, const std::string& s)
|
||||
{
|
||||
std::vector<u32> codes = parseCodes(s);
|
||||
Cheat conditionCheat;
|
||||
unsigned conditionLimit = 0;
|
||||
|
||||
for (unsigned i = 0; i < codes.size(); i++)
|
||||
{
|
||||
if (i < conditionLimit)
|
||||
cheats.push_back(conditionCheat);
|
||||
Cheat cheat{};
|
||||
cheat.description = name;
|
||||
u32 code = (codes[i] & 0xff000000) >> 24;
|
||||
switch (code)
|
||||
{
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
{
|
||||
// 8/16/32-bit write
|
||||
if (i + 1 >= codes.size())
|
||||
throw FlycastException("Missing value");
|
||||
cheat.type = Cheat::Type::setValue;
|
||||
cheat.size = code == 0 ? 8 : code == 1 ? 16 : 32;
|
||||
cheat.address = codes[i] & 0x00ffffff;
|
||||
cheat.value = codes[++i];
|
||||
cheats.push_back(cheat);
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
{
|
||||
u32 subcode = (codes[i] & 0x00ff0000) >> 16;
|
||||
switch (subcode)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
// Group write
|
||||
int count = codes[i] & 0xffff;
|
||||
if (i + count + 1 >= codes.size())
|
||||
throw FlycastException("Missing values");
|
||||
cheat.type = Cheat::Type::setValue;
|
||||
cheat.size = 32;
|
||||
cheat.address = codes[++i] & 0x00ffffff;
|
||||
for (int j = 0; j < count; j++)
|
||||
{
|
||||
if (j == 1)
|
||||
cheat.description += " (cont'd)";
|
||||
cheat.value = codes[++i];
|
||||
cheats.push_back(cheat);
|
||||
cheat.address += 4;
|
||||
if (j < count - 1 && i < conditionLimit)
|
||||
cheats.push_back(conditionCheat);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
case 2:
|
||||
{
|
||||
// 8-bit inc/decrement
|
||||
if (i + 1 >= codes.size())
|
||||
throw FlycastException("Missing value");
|
||||
cheat.type = subcode == 1 ? Cheat::Type::increase : Cheat::Type::decrease;
|
||||
cheat.size = 8;
|
||||
cheat.value = codes[i] & 0xff;
|
||||
cheat.address = codes[++i] & 0x00ffffff;
|
||||
cheats.push_back(cheat);
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
case 4:
|
||||
{
|
||||
// 16-bit inc/decrement
|
||||
if (i + 1 >= codes.size())
|
||||
throw FlycastException("Missing value");
|
||||
cheat.type = subcode == 3 ? Cheat::Type::increase : Cheat::Type::decrease;
|
||||
cheat.size = 16;
|
||||
cheat.value = codes[i] & 0xffff;
|
||||
cheat.address = codes[++i] & 0x00ffffff;
|
||||
cheats.push_back(cheat);
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
case 6:
|
||||
{
|
||||
// 32-bit inc/decrement
|
||||
if (i + 2 >= codes.size())
|
||||
throw FlycastException("Missing address or value");
|
||||
cheat.type = subcode == 5 ? Cheat::Type::increase : Cheat::Type::decrease;
|
||||
cheat.size = 32;
|
||||
cheat.address = codes[++i] & 0x00ffffff;
|
||||
cheat.value = codes[++i];
|
||||
cheats.push_back(cheat);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw FlycastException("Unsupported cheat type");
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
{
|
||||
// 32-bit repeat write
|
||||
if (i + 2 >= codes.size())
|
||||
throw FlycastException("Missing count or value");
|
||||
cheat.type = Cheat::Type::setValue;
|
||||
cheat.size = 32;
|
||||
cheat.address = codes[i] & 0x00ffffff;
|
||||
cheat.repeatCount = codes[++i] >> 16;
|
||||
cheat.repeatAddressIncrement = codes[i] & 0xffff;
|
||||
cheat.value = codes[++i];
|
||||
cheats.push_back(cheat);
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
{
|
||||
// copy bytes
|
||||
if (i + 2 >= codes.size())
|
||||
throw FlycastException("Missing count or destination address");
|
||||
cheat.type = Cheat::Type::copy;
|
||||
cheat.size = 8;
|
||||
cheat.address = codes[i] & 0x00ffffff;
|
||||
cheat.destAddress = codes[++i] & 0x00ffffff;
|
||||
cheat.repeatCount = codes[++i];
|
||||
cheats.push_back(cheat);
|
||||
}
|
||||
break;
|
||||
// TODO 7 change decryption type
|
||||
// TODO 0xb delay applying codes
|
||||
// TODO 0xc global enable test
|
||||
case 0xd:
|
||||
{
|
||||
// enable next code if eq/neq/lt/gt
|
||||
if (i + 1 >= codes.size())
|
||||
throw FlycastException("Missing count or destination address");
|
||||
cheat.size = 16;
|
||||
cheat.address = codes[i] & 0x00ffffff;
|
||||
switch (codes[++i] >> 16)
|
||||
{
|
||||
case 0:
|
||||
cheat.type = Cheat::Type::runNextIfEq;
|
||||
break;
|
||||
case 1:
|
||||
cheat.type = Cheat::Type::runNextIfNeq;
|
||||
break;
|
||||
case 2:
|
||||
cheat.type = Cheat::Type::runNextIfLt;
|
||||
break;
|
||||
case 3:
|
||||
cheat.type = Cheat::Type::runNextIfGt;
|
||||
break;
|
||||
default:
|
||||
throw FlycastException("Unsupported conditional code");
|
||||
}
|
||||
cheat.value = codes[i] & 0xffff;
|
||||
cheats.push_back(cheat);
|
||||
}
|
||||
break;
|
||||
case 0xe:
|
||||
{
|
||||
// multiline enable codes if eq/neq/lt/gt
|
||||
if (i + 1 >= codes.size())
|
||||
throw FlycastException("Missing test address");
|
||||
cheat.size = 16;
|
||||
cheat.value = codes[i] & 0xffff;
|
||||
conditionLimit = i + 1 + ((codes[i] >> 16) & 0xff);
|
||||
switch (codes[++i] >> 24)
|
||||
{
|
||||
case 0:
|
||||
cheat.type = Cheat::Type::runNextIfEq;
|
||||
break;
|
||||
case 1:
|
||||
cheat.type = Cheat::Type::runNextIfNeq;
|
||||
break;
|
||||
case 2:
|
||||
cheat.type = Cheat::Type::runNextIfLt;
|
||||
break;
|
||||
case 3:
|
||||
cheat.type = Cheat::Type::runNextIfGt;
|
||||
break;
|
||||
default:
|
||||
throw FlycastException("Unsupported conditional code");
|
||||
}
|
||||
cheat.address = codes[i] & 0x00ffffff;
|
||||
conditionCheat = cheat;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw FlycastException("Unsupported cheat type");
|
||||
}
|
||||
}
|
||||
active = !cheats.empty();
|
||||
#ifndef LIBRETRO
|
||||
std::string path = cfgLoadStr("cheats", gameId, "");
|
||||
if (path == "")
|
||||
{
|
||||
path = get_game_save_prefix() + ".cht";
|
||||
cfgSaveStr("cheats", gameId, path);
|
||||
}
|
||||
saveCheatFile(path);
|
||||
#endif
|
||||
}
|
||||
|
||||
void CheatManager::saveCheatFile(const std::string& filename)
|
||||
{
|
||||
#ifndef LIBRETRO
|
||||
emucfg::ConfigFile cfg;
|
||||
|
||||
cfg.set_int("", "cheats", cheats.size());
|
||||
int i = 0;
|
||||
for (const Cheat& cheat : cheats)
|
||||
{
|
||||
std::string prefix = "cheat" + std::to_string(i) + "_";
|
||||
cfg.set_int("", prefix + "address", cheat.address);
|
||||
cfg.set_int("", prefix + "address_bit_position", 0); // FIXME
|
||||
cfg.set_bool("", prefix + "big_endian", false);
|
||||
cfg.set_int("", prefix + "cheat_type", (int)cheat.type);
|
||||
cfg.set("", prefix + "code", "");
|
||||
cfg.set("", prefix + "desc", cheat.description);
|
||||
cfg.set_int("", prefix + "dest_address", cheat.destAddress);
|
||||
cfg.set_bool("", prefix + "enable", false); // force all cheats disabled at start
|
||||
cfg.set_int("", prefix + "handler", 1);
|
||||
int memSize;
|
||||
switch (cheat.size) {
|
||||
case 1:
|
||||
memSize = 0;
|
||||
break;
|
||||
case 2:
|
||||
memSize = 1;
|
||||
break;
|
||||
case 4:
|
||||
memSize = 2;
|
||||
break;
|
||||
case 8:
|
||||
memSize = 3;
|
||||
break;
|
||||
case 16:
|
||||
memSize = 4;
|
||||
break;
|
||||
case 32:
|
||||
default:
|
||||
memSize = 5;
|
||||
break;
|
||||
}
|
||||
cfg.set_int("", prefix + "memory_search_size", memSize);
|
||||
cfg.set_int("", prefix + "value", cheat.value);
|
||||
cfg.set_int("", prefix + "repeat_count", cheat.repeatCount);
|
||||
cfg.set_int("", prefix + "repeat_add_to_value", cheat.repeatValueIncrement);
|
||||
cfg.set_int("", prefix + "repeat_add_to_address", cheat.repeatAddressIncrement);
|
||||
i++;
|
||||
}
|
||||
FILE *fp = nowide::fopen(filename.c_str(), "w");
|
||||
if (fp == nullptr)
|
||||
throw FlycastException("Can't save cheat file");
|
||||
cfg.save(fp);
|
||||
fclose(fp);
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -39,7 +39,8 @@ struct Cheat
|
|||
runNextIfEq,
|
||||
runNextIfNeq,
|
||||
runNextIfGt,
|
||||
runNextIfLt
|
||||
runNextIfLt,
|
||||
copy
|
||||
};
|
||||
Type type = Type::disabled;
|
||||
std::string description;
|
||||
|
@ -50,6 +51,7 @@ struct Cheat
|
|||
u32 repeatCount = 1;
|
||||
u32 repeatValueIncrement = 0;
|
||||
u32 repeatAddressIncrement = 0;
|
||||
u32 destAddress = 0;
|
||||
};
|
||||
|
||||
class CheatManager
|
||||
|
@ -62,8 +64,10 @@ public:
|
|||
bool cheatEnabled(size_t index) const { return cheats[index].enabled; }
|
||||
void enableCheat(size_t index, bool enabled) { cheats[index].enabled = enabled; }
|
||||
void loadCheatFile(const std::string& filename);
|
||||
void saveCheatFile(const std::string& filename);
|
||||
// Returns true if using 16:9 anamorphic screen ratio
|
||||
bool isWidescreen() const { return widescreen_cheat != nullptr; }
|
||||
void addGameSharkCheat(const std::string& name, const std::string& s);
|
||||
|
||||
private:
|
||||
u32 readRam(u32 addr, u32 bits);
|
||||
|
@ -75,6 +79,10 @@ private:
|
|||
bool active = false;
|
||||
std::vector<Cheat> cheats;
|
||||
std::string gameId;
|
||||
|
||||
friend class CheatManagerTest_TestLoad_Test;
|
||||
friend class CheatManagerTest_TestGameShark_Test;
|
||||
friend class CheatManagerTest_TestSave_Test;
|
||||
};
|
||||
|
||||
extern CheatManager cheatManager;
|
||||
|
|
|
@ -442,7 +442,7 @@ void gui_stop_game(const std::string& message)
|
|||
game_started = false;
|
||||
reset_vmus();
|
||||
if (!message.empty())
|
||||
error_msg = "Flycast has stopped.\n\n" + message;
|
||||
gui_error("Flycast has stopped.\n\n" + message);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -2105,7 +2105,7 @@ static void gui_display_content()
|
|||
DiscSwap(game.path);
|
||||
gui_state = GuiState::Closed;
|
||||
} catch (const FlycastException& e) {
|
||||
error_msg = e.what();
|
||||
gui_error(e.what());
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -2131,7 +2131,6 @@ static void gui_display_content()
|
|||
ImGui::PopStyleVar();
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
error_popup();
|
||||
contentpath_warning_popup();
|
||||
}
|
||||
|
||||
|
@ -2206,7 +2205,7 @@ static void gui_network_start()
|
|||
NetworkHandshake::instance->stop();
|
||||
gui_state = GuiState::Main;
|
||||
emu.unloadGame();
|
||||
error_msg = e.what();
|
||||
gui_error(e.what());
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -2283,7 +2282,7 @@ static void gui_display_loadscreen()
|
|||
}
|
||||
} catch (const FlycastException& ex) {
|
||||
ERROR_LOG(BOOT, "%s", ex.what());
|
||||
error_msg = ex.what();
|
||||
gui_error(ex.what());
|
||||
#ifdef TEST_AUTOMATION
|
||||
die("Game load failed");
|
||||
#endif
|
||||
|
@ -2354,6 +2353,7 @@ void gui_display_ui()
|
|||
die("Unknown UI state");
|
||||
break;
|
||||
}
|
||||
error_popup();
|
||||
ImGui::Render();
|
||||
ImGui_impl_RenderDrawData(ImGui::GetDrawData());
|
||||
|
||||
|
@ -2615,3 +2615,8 @@ static void term_vmus()
|
|||
crosshairTexId = ImTextureID();
|
||||
}
|
||||
}
|
||||
|
||||
void gui_error(const std::string& what)
|
||||
{
|
||||
error_msg = what;
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ void gui_set_mouse_wheel(float delta);
|
|||
void gui_set_insets(int left, int right, int top, int bottom);
|
||||
void gui_stop_game(const std::string& message = "");
|
||||
void gui_start_game(const std::string& path);
|
||||
void gui_error(const std::string& what);
|
||||
|
||||
extern int screen_dpi;
|
||||
extern float scaling;
|
||||
|
|
|
@ -21,8 +21,60 @@
|
|||
#include "gui_util.h"
|
||||
#include "cheats.h"
|
||||
|
||||
static bool addingCheat;
|
||||
|
||||
static void addCheat()
|
||||
{
|
||||
static char cheatName[64];
|
||||
static char cheatCode[128];
|
||||
centerNextWindow();
|
||||
ImGui::SetNextWindowSize(ImVec2(std::min(ImGui::GetIO().DisplaySize.x, 600 * scaling), std::min(ImGui::GetIO().DisplaySize.y, 400 * scaling)));
|
||||
|
||||
ImGui::Begin("##main", nullptr, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar
|
||||
| ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize);
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(20 * scaling, 8 * scaling)); // from 8, 4
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::Indent(10 * scaling);
|
||||
ImGui::Text("ADD CHEAT");
|
||||
|
||||
ImGui::SameLine(ImGui::GetWindowContentRegionMax().x - ImGui::CalcTextSize("Cancel").x - ImGui::GetStyle().FramePadding.x * 4.f
|
||||
- ImGui::CalcTextSize("OK").x - ImGui::GetStyle().ItemSpacing.x);
|
||||
if (ImGui::Button("Cancel"))
|
||||
addingCheat = false;
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("OK"))
|
||||
{
|
||||
try {
|
||||
cheatManager.addGameSharkCheat(cheatName, cheatCode);
|
||||
addingCheat = false;
|
||||
cheatName[0] = 0;
|
||||
cheatCode[0] = 0;
|
||||
} catch (const FlycastException& e) {
|
||||
gui_error(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Unindent(10 * scaling);
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
ImGui::BeginChild(ImGui::GetID("input"), ImVec2(0, 0), true);
|
||||
{
|
||||
ImGui::InputText("Name", cheatName, sizeof(cheatName), 0, nullptr, nullptr);
|
||||
ImGui::Text("Code:");
|
||||
ImGui::InputTextMultiline("Code", cheatCode, sizeof(cheatCode), ImVec2(-FLT_MIN, ImGui::GetTextLineHeight() * 8), 0, nullptr, nullptr);
|
||||
}
|
||||
ImGui::EndChild();
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void gui_cheats()
|
||||
{
|
||||
if (addingCheat)
|
||||
{
|
||||
addCheat();
|
||||
return;
|
||||
}
|
||||
centerNextWindow();
|
||||
ImGui::SetNextWindowSize(ImVec2(std::min(ImGui::GetIO().DisplaySize.x, 600 * scaling), std::min(ImGui::GetIO().DisplaySize.y, 400 * scaling)));
|
||||
|
||||
|
@ -34,8 +86,11 @@ void gui_cheats()
|
|||
ImGui::Indent(10 * scaling);
|
||||
ImGui::Text("CHEATS");
|
||||
|
||||
ImGui::SameLine(ImGui::GetWindowContentRegionMax().x - ImGui::CalcTextSize("Close").x - ImGui::GetStyle().FramePadding.x * 4.f
|
||||
- ImGui::CalcTextSize("Load").x - ImGui::GetStyle().ItemSpacing.x);
|
||||
ImGui::SameLine(ImGui::GetWindowContentRegionMax().x - ImGui::CalcTextSize("Add").x - ImGui::CalcTextSize("Close").x - ImGui::GetStyle().FramePadding.x * 6.f
|
||||
- ImGui::CalcTextSize("Load").x - ImGui::GetStyle().ItemSpacing.x * 2);
|
||||
if (ImGui::Button("Add"))
|
||||
addingCheat = true;
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Load"))
|
||||
ImGui::OpenPopup("Select cheat file");
|
||||
select_file_popup("Select cheat file", [](bool cancelled, std::string selection)
|
||||
|
|
|
@ -128,3 +128,13 @@ static inline std::string trim_trailing_ws(const std::string& str,
|
|||
|
||||
return str.substr(0, strEnd + 1);
|
||||
}
|
||||
|
||||
static inline std::string trim_ws(const std::string& str,
|
||||
const std::string& whitespace = " ")
|
||||
{
|
||||
const auto strStart = str.find_first_not_of(whitespace);
|
||||
if (strStart == std::string::npos)
|
||||
return "";
|
||||
|
||||
return str.substr(strStart, str.find_last_not_of(whitespace) + 1 - strStart);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,255 @@
|
|||
#include "gtest/gtest.h"
|
||||
#include "types.h"
|
||||
#include "cfg/ini.h"
|
||||
#include "cfg/cfg.h"
|
||||
#include "cheats.h"
|
||||
#include "emulator.h"
|
||||
#include "log/LogManager.h"
|
||||
|
||||
class CheatManagerTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
emu.init();
|
||||
LogManager::Init();
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(CheatManagerTest, TestLoad)
|
||||
{
|
||||
FILE *fp = fopen("test.cht", "w");
|
||||
const char *s = R"(
|
||||
cheat0_address = "1234"
|
||||
cheat0_address_bit_position = "0"
|
||||
cheat0_big_endian = "false"
|
||||
cheat0_cheat_type = "1"
|
||||
cheat0_code = ""
|
||||
cheat0_desc = "widescreen"
|
||||
cheat0_dest_address = "7890"
|
||||
cheat0_enable = "false"
|
||||
cheat0_handler = "1"
|
||||
cheat0_memory_search_size = "5"
|
||||
cheat0_repeat_add_to_address = "4"
|
||||
cheat0_repeat_add_to_value = "16"
|
||||
cheat0_repeat_count = "3"
|
||||
cheat0_value = "5678"
|
||||
cheats = "1"
|
||||
)";
|
||||
fputs(s, fp);
|
||||
fclose(fp);
|
||||
|
||||
CheatManager mgr;
|
||||
mgr.reset("TESTGAME");
|
||||
mgr.loadCheatFile("test.cht");
|
||||
ASSERT_EQ(1, (int)mgr.cheatCount());
|
||||
ASSERT_EQ("widescreen", mgr.cheatDescription(0));
|
||||
ASSERT_FALSE(mgr.cheatEnabled(0));
|
||||
mgr.enableCheat(0, true);
|
||||
ASSERT_TRUE(mgr.cheatEnabled(0));
|
||||
mgr.enableCheat(0, false);
|
||||
ASSERT_FALSE(mgr.cheatEnabled(0));
|
||||
|
||||
ASSERT_EQ(1234u, mgr.cheats[0].address);
|
||||
ASSERT_EQ(5678u, mgr.cheats[0].value);
|
||||
ASSERT_EQ(Cheat::Type::setValue, mgr.cheats[0].type);
|
||||
ASSERT_EQ(32u, mgr.cheats[0].size);
|
||||
ASSERT_EQ(3u, mgr.cheats[0].repeatCount);
|
||||
ASSERT_EQ(4u, mgr.cheats[0].repeatAddressIncrement);
|
||||
ASSERT_EQ(16u, mgr.cheats[0].repeatValueIncrement);
|
||||
ASSERT_EQ(7890u, mgr.cheats[0].destAddress);
|
||||
}
|
||||
|
||||
TEST_F(CheatManagerTest, TestGameShark)
|
||||
{
|
||||
CheatManager mgr;
|
||||
mgr.reset("TESTGS1");
|
||||
mgr.addGameSharkCheat("cheat1", "00123456 deadc0d3");
|
||||
ASSERT_EQ("cheat1", mgr.cheats[0].description);
|
||||
ASSERT_EQ(Cheat::Type::setValue, mgr.cheats[0].type);
|
||||
ASSERT_EQ(0x123456u, mgr.cheats[0].address);
|
||||
ASSERT_EQ(0xdeadc0d3u, mgr.cheats[0].value);
|
||||
ASSERT_EQ(8u, mgr.cheats[0].size);
|
||||
ASSERT_EQ(1u, mgr.cheats[0].repeatCount);
|
||||
|
||||
mgr.reset("TESTGS2");
|
||||
mgr.addGameSharkCheat("cheat2", " 01222222\ndeadc0d3 ");
|
||||
ASSERT_EQ(Cheat::Type::setValue, mgr.cheats[0].type);
|
||||
ASSERT_EQ(0x222222u, mgr.cheats[0].address);
|
||||
ASSERT_EQ(0xdeadc0d3u, mgr.cheats[0].value);
|
||||
ASSERT_EQ(16u, mgr.cheats[0].size);
|
||||
ASSERT_EQ(1u, mgr.cheats[0].repeatCount);
|
||||
|
||||
mgr.reset("TESTGS3");
|
||||
mgr.addGameSharkCheat("cheat3", "\n02333333 \t baadf00d\n");
|
||||
ASSERT_EQ(Cheat::Type::setValue, mgr.cheats[0].type);
|
||||
ASSERT_EQ(0x333333u, mgr.cheats[0].address);
|
||||
ASSERT_EQ(0xbaadf00du, mgr.cheats[0].value);
|
||||
ASSERT_EQ(32u, mgr.cheats[0].size);
|
||||
ASSERT_EQ(1u, mgr.cheats[0].repeatCount);
|
||||
|
||||
mgr.reset("TESTGS4");
|
||||
mgr.addGameSharkCheat("cheat4", "\n03010042 \t 0c444444\n");
|
||||
ASSERT_EQ(Cheat::Type::increase, mgr.cheats[0].type);
|
||||
ASSERT_EQ(0x444444u, mgr.cheats[0].address);
|
||||
ASSERT_EQ(0x42u, mgr.cheats[0].value);
|
||||
ASSERT_EQ(8u, mgr.cheats[0].size);
|
||||
ASSERT_EQ(1u, mgr.cheats[0].repeatCount);
|
||||
|
||||
mgr.reset("TESTGS5");
|
||||
mgr.addGameSharkCheat("cheat5", "\n03041984 \t 0c555555\n");
|
||||
ASSERT_EQ(Cheat::Type::decrease, mgr.cheats[0].type);
|
||||
ASSERT_EQ(0x555555u, mgr.cheats[0].address);
|
||||
ASSERT_EQ(0x1984u, mgr.cheats[0].value);
|
||||
ASSERT_EQ(16u, mgr.cheats[0].size);
|
||||
ASSERT_EQ(1u, mgr.cheats[0].repeatCount);
|
||||
|
||||
mgr.reset("TESTGS6");
|
||||
mgr.addGameSharkCheat("cheat6", "03000003 0c666666 11111111 22222222 33333333");
|
||||
ASSERT_EQ(3u, mgr.cheats.size());
|
||||
ASSERT_EQ(Cheat::Type::setValue, mgr.cheats[0].type);
|
||||
ASSERT_EQ(0x666666u, mgr.cheats[0].address);
|
||||
ASSERT_EQ(0x11111111u, mgr.cheats[0].value);
|
||||
ASSERT_EQ(32u, mgr.cheats[0].size);
|
||||
ASSERT_EQ(1u, mgr.cheats[0].repeatCount);
|
||||
ASSERT_EQ(Cheat::Type::setValue, mgr.cheats[1].type);
|
||||
ASSERT_EQ(0x66666au, mgr.cheats[1].address);
|
||||
ASSERT_EQ(0x22222222u, mgr.cheats[1].value);
|
||||
ASSERT_EQ(32u, mgr.cheats[1].size);
|
||||
ASSERT_EQ(1u, mgr.cheats[1].repeatCount);
|
||||
ASSERT_EQ(Cheat::Type::setValue, mgr.cheats[2].type);
|
||||
ASSERT_EQ(0x66666eu, mgr.cheats[2].address);
|
||||
ASSERT_EQ(0x33333333u, mgr.cheats[2].value);
|
||||
ASSERT_EQ(32u, mgr.cheats[0].size);
|
||||
ASSERT_EQ(1u, mgr.cheats[0].repeatCount);
|
||||
|
||||
mgr.reset("TESTGS7");
|
||||
mgr.addGameSharkCheat("cheat7", "04aaaaaa 00020002 11111111");
|
||||
ASSERT_EQ(Cheat::Type::setValue, mgr.cheats[0].type);
|
||||
ASSERT_EQ(0xaaaaaau, mgr.cheats[0].address);
|
||||
ASSERT_EQ(0x11111111u, mgr.cheats[0].value);
|
||||
ASSERT_EQ(32u, mgr.cheats[0].size);
|
||||
ASSERT_EQ(2u, mgr.cheats[0].repeatCount);
|
||||
ASSERT_EQ(2u, mgr.cheats[0].repeatAddressIncrement);
|
||||
ASSERT_EQ(0u, mgr.cheats[0].repeatValueIncrement);
|
||||
|
||||
mgr.reset("TESTGS8");
|
||||
mgr.addGameSharkCheat("cheat8", "05bbbbbb 8ccccccc 00000010");
|
||||
ASSERT_EQ(Cheat::Type::copy, mgr.cheats[0].type);
|
||||
ASSERT_EQ(0xbbbbbbu, mgr.cheats[0].address);
|
||||
ASSERT_EQ(8u, mgr.cheats[0].size);
|
||||
ASSERT_EQ(0x10u, mgr.cheats[0].repeatCount);
|
||||
ASSERT_EQ(0xccccccu, mgr.cheats[0].destAddress);
|
||||
|
||||
mgr.reset("TESTGS9");
|
||||
mgr.addGameSharkCheat("cheat9", "0d123456 0000ffff");
|
||||
ASSERT_EQ(Cheat::Type::runNextIfEq, mgr.cheats[0].type);
|
||||
ASSERT_EQ(0x123456u, mgr.cheats[0].address);
|
||||
ASSERT_EQ(0xffffu, mgr.cheats[0].value);
|
||||
ASSERT_EQ(16u, mgr.cheats[0].size);
|
||||
ASSERT_EQ(1u, mgr.cheats[0].repeatCount);
|
||||
|
||||
mgr.reset("TESTGS10");
|
||||
mgr.addGameSharkCheat("cheat10", "0d654321" "00031984");
|
||||
ASSERT_EQ(Cheat::Type::runNextIfGt, mgr.cheats[0].type);
|
||||
ASSERT_EQ(0x654321u, mgr.cheats[0].address);
|
||||
ASSERT_EQ(0x1984u, mgr.cheats[0].value);
|
||||
ASSERT_EQ(16u, mgr.cheats[0].size);
|
||||
ASSERT_EQ(1u, mgr.cheats[0].repeatCount);
|
||||
|
||||
mgr.reset("TESTGS11");
|
||||
mgr.addGameSharkCheat("cheat11", "0e021111 00123456 02222222 22222222 01111111 00001111");
|
||||
ASSERT_EQ(Cheat::Type::runNextIfEq, mgr.cheats[0].type);
|
||||
ASSERT_EQ(0x123456u, mgr.cheats[0].address);
|
||||
ASSERT_EQ(0x1111u, mgr.cheats[0].value);
|
||||
ASSERT_EQ(16u, mgr.cheats[0].size);
|
||||
ASSERT_EQ(1u, mgr.cheats[0].repeatCount);
|
||||
ASSERT_EQ(Cheat::Type::setValue, mgr.cheats[1].type);
|
||||
ASSERT_EQ(0x222222u, mgr.cheats[1].address);
|
||||
ASSERT_EQ(0x22222222u, mgr.cheats[1].value);
|
||||
ASSERT_EQ(32u, mgr.cheats[1].size);
|
||||
ASSERT_EQ(1u, mgr.cheats[1].repeatCount);
|
||||
ASSERT_EQ(Cheat::Type::setValue, mgr.cheats[2].type);
|
||||
ASSERT_EQ(0x111111u, mgr.cheats[2].address);
|
||||
ASSERT_EQ(0x1111u, mgr.cheats[2].value);
|
||||
ASSERT_EQ(16u, mgr.cheats[2].size);
|
||||
ASSERT_EQ(1u, mgr.cheats[2].repeatCount);
|
||||
}
|
||||
|
||||
|
||||
TEST_F(CheatManagerTest, TestGameSharkError)
|
||||
{
|
||||
CheatManager mgr;
|
||||
EXPECT_THROW(mgr.addGameSharkCheat("error", "00"), FlycastException);
|
||||
EXPECT_THROW(mgr.addGameSharkCheat("error", "001234560000000"), FlycastException);
|
||||
EXPECT_THROW(mgr.addGameSharkCheat("error", "00 123456 00000000"), FlycastException);
|
||||
|
||||
EXPECT_THROW(mgr.addGameSharkCheat("error", "00123456 "), FlycastException);
|
||||
EXPECT_THROW(mgr.addGameSharkCheat("error", " 01123456 "), FlycastException);
|
||||
EXPECT_THROW(mgr.addGameSharkCheat("error", " 02123456 "), FlycastException);
|
||||
EXPECT_THROW(mgr.addGameSharkCheat("error", " 03000002 00123456 00000001"), FlycastException);
|
||||
|
||||
EXPECT_THROW(mgr.addGameSharkCheat("error", " 03013456"), FlycastException);
|
||||
EXPECT_THROW(mgr.addGameSharkCheat("error", " 03023456"), FlycastException);
|
||||
EXPECT_THROW(mgr.addGameSharkCheat("error", " 03033456"), FlycastException);
|
||||
EXPECT_THROW(mgr.addGameSharkCheat("error", " 03043456"), FlycastException);
|
||||
EXPECT_THROW(mgr.addGameSharkCheat("error", " 03050000 8c123456"), FlycastException);
|
||||
EXPECT_THROW(mgr.addGameSharkCheat("error", " 03060000 8c123456"), FlycastException);
|
||||
|
||||
EXPECT_THROW(mgr.addGameSharkCheat("error", " 04654321 00030004"), FlycastException);
|
||||
EXPECT_THROW(mgr.addGameSharkCheat("error", " 05654321 8c111111"), FlycastException);
|
||||
|
||||
EXPECT_THROW(mgr.addGameSharkCheat("error", " 0d0FFFFF"), FlycastException);
|
||||
EXPECT_THROW(mgr.addGameSharkCheat("error", " 0e0FFFFF"), FlycastException);
|
||||
}
|
||||
|
||||
TEST_F(CheatManagerTest, TestSave)
|
||||
{
|
||||
CheatManager mgr;
|
||||
mgr.reset("TESTSAVE");
|
||||
mgr.addGameSharkCheat("cheat1", "00010000 000000d3");
|
||||
mgr.addGameSharkCheat("cheat2", "01020000 0000c0d3");
|
||||
mgr.addGameSharkCheat("cheat3", "02030000 deadc0d3");
|
||||
mgr.addGameSharkCheat("cheat4", "03000004 8c040000 00000001 00000002 00000003 00000004");
|
||||
mgr.addGameSharkCheat("cheat5", "030100ff 8c050000");
|
||||
mgr.addGameSharkCheat("cheat6", "0304ffff 8c060000");
|
||||
|
||||
mgr.loadCheatFile(cfgLoadStr("cheats", "TESTSAVE", ""));
|
||||
ASSERT_EQ(Cheat::Type::setValue, mgr.cheats[0].type);
|
||||
ASSERT_EQ(0x010000u, mgr.cheats[0].address);
|
||||
ASSERT_EQ(0xd3u, mgr.cheats[0].value);
|
||||
ASSERT_EQ(8u, mgr.cheats[0].size);
|
||||
ASSERT_EQ(1u, mgr.cheats[0].repeatCount);
|
||||
ASSERT_EQ(Cheat::Type::setValue, mgr.cheats[1].type);
|
||||
ASSERT_EQ(0x020000u, mgr.cheats[1].address);
|
||||
ASSERT_EQ(0xc0d3u, mgr.cheats[1].value);
|
||||
ASSERT_EQ(16u, mgr.cheats[1].size);
|
||||
ASSERT_EQ(Cheat::Type::setValue, mgr.cheats[2].type);
|
||||
ASSERT_EQ(0x030000u, mgr.cheats[2].address);
|
||||
ASSERT_EQ(0xdeadc0d3u, mgr.cheats[2].value);
|
||||
ASSERT_EQ(32u, mgr.cheats[2].size);
|
||||
|
||||
ASSERT_EQ(Cheat::Type::setValue, mgr.cheats[3].type);
|
||||
ASSERT_EQ(0x040000u, mgr.cheats[3].address);
|
||||
ASSERT_EQ(1u, mgr.cheats[3].value);
|
||||
ASSERT_EQ(32u, mgr.cheats[3].size);
|
||||
ASSERT_EQ(Cheat::Type::setValue, mgr.cheats[4].type);
|
||||
ASSERT_EQ(0x040004u, mgr.cheats[4].address);
|
||||
ASSERT_EQ(2u, mgr.cheats[4].value);
|
||||
ASSERT_EQ(32u, mgr.cheats[4].size);
|
||||
ASSERT_EQ(Cheat::Type::setValue, mgr.cheats[5].type);
|
||||
ASSERT_EQ(0x040008u, mgr.cheats[5].address);
|
||||
ASSERT_EQ(3u, mgr.cheats[5].value);
|
||||
ASSERT_EQ(32u, mgr.cheats[5].size);
|
||||
ASSERT_EQ(Cheat::Type::setValue, mgr.cheats[6].type);
|
||||
ASSERT_EQ(0x04000cu, mgr.cheats[6].address);
|
||||
ASSERT_EQ(4u, mgr.cheats[6].value);
|
||||
ASSERT_EQ(32u, mgr.cheats[6].size);
|
||||
|
||||
ASSERT_EQ(Cheat::Type::increase, mgr.cheats[7].type);
|
||||
ASSERT_EQ(0x050000u, mgr.cheats[7].address);
|
||||
ASSERT_EQ(0xffu, mgr.cheats[7].value);
|
||||
ASSERT_EQ(8u, mgr.cheats[7].size);
|
||||
ASSERT_EQ(Cheat::Type::decrease, mgr.cheats[8].type);
|
||||
ASSERT_EQ(0x060000u, mgr.cheats[8].address);
|
||||
ASSERT_EQ(0xffffu, mgr.cheats[8].value);
|
||||
ASSERT_EQ(16u, mgr.cheats[8].size);
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
#include "gtest/gtest.h"
|
||||
#include "types.h"
|
||||
#include "cfg/ini.h"
|
||||
|
||||
class ConfigFileTest : public ::testing::Test {
|
||||
};
|
||||
|
||||
TEST_F(ConfigFileTest, TestLoadSave)
|
||||
{
|
||||
using namespace emucfg;
|
||||
ConfigFile file;
|
||||
file.set("", "prop1", "value1");
|
||||
file.set_int("", "prop2", 2);
|
||||
file.set_bool("", "prop3", true);
|
||||
ASSERT_EQ("value1", file.get("", "prop1", ""));
|
||||
ASSERT_EQ(2, file.get_int("", "prop2", 0));
|
||||
ASSERT_TRUE(file.get_bool("", "prop3", false));
|
||||
|
||||
FILE *fp = fopen("test.cfg", "w");
|
||||
file.save(fp);
|
||||
fclose(fp);
|
||||
fp = fopen("test.cfg", "r");
|
||||
char buf[1024];
|
||||
int l = fread(buf, 1, sizeof(buf) - 1, fp);
|
||||
buf[l] = '\0';
|
||||
fclose(fp);
|
||||
ASSERT_EQ("prop1 = value1\nprop2 = 2\nprop3 = yes\n", std::string(buf));
|
||||
|
||||
fp = fopen("test.cfg", "r");
|
||||
file = {};
|
||||
file.parse(fp);
|
||||
fclose(fp);
|
||||
ASSERT_EQ("value1", file.get("", "prop1", ""));
|
||||
ASSERT_EQ(2, file.get_int("", "prop2", 0));
|
||||
ASSERT_TRUE(file.get_bool("", "prop3", false));
|
||||
}
|
||||
|
||||
TEST_F(ConfigFileTest, TestQuotes)
|
||||
{
|
||||
using namespace emucfg;
|
||||
FILE *fp = fopen("test.cfg", "w");
|
||||
fprintf(fp, "propWithQuotes=\"value with quotes\"\n");
|
||||
fprintf(fp, "propWithQuotes2=\"42\"\n");
|
||||
fprintf(fp, "propWithQuotes3=\"true\"\n");
|
||||
fclose(fp);
|
||||
fp = fopen("test.cfg", "r");
|
||||
ConfigFile file;
|
||||
file.parse(fp);
|
||||
fclose(fp);
|
||||
ASSERT_EQ("value with quotes", file.get("", "propWithQuotes", ""));
|
||||
ASSERT_EQ(42, file.get_int("", "propWithQuotes2", 0));
|
||||
ASSERT_TRUE(file.get_bool("", "propWithQuotes3", false));
|
||||
}
|
||||
|
||||
TEST_F(ConfigFileTest, TestTrim)
|
||||
{
|
||||
using namespace emucfg;
|
||||
FILE *fp = fopen("test.cfg", "w");
|
||||
fprintf(fp, " prop = \"value 1 \" \n\n\n");
|
||||
fprintf(fp, " prop2 = 42 \n");
|
||||
fprintf(fp, " prop3 = yes \r\n\n");
|
||||
fclose(fp);
|
||||
fp = fopen("test.cfg", "r");
|
||||
ConfigFile file;
|
||||
file.parse(fp);
|
||||
fclose(fp);
|
||||
ASSERT_EQ("value 1 ", file.get("", "prop", ""));
|
||||
ASSERT_EQ(42, file.get_int("", "prop2", 0));
|
||||
ASSERT_TRUE(file.get_bool("", "prop3", false));
|
||||
}
|
||||
|
||||
TEST_F(ConfigFileTest, TestLoadSaveSection)
|
||||
{
|
||||
using namespace emucfg;
|
||||
ConfigFile file;
|
||||
file.set("sect1", "prop1", "value1");
|
||||
file.set_int("sect2", "prop2", 2);
|
||||
file.set_bool("sect2", "prop3", true);
|
||||
ASSERT_EQ("value1", file.get("sect1", "prop1", ""));
|
||||
ASSERT_EQ(2, file.get_int("sect2", "prop2", 0));
|
||||
ASSERT_TRUE(file.get_bool("sect2", "prop3", false));
|
||||
|
||||
FILE *fp = fopen("test.cfg", "w");
|
||||
file.save(fp);
|
||||
fclose(fp);
|
||||
fp = fopen("test.cfg", "r");
|
||||
char buf[1024];
|
||||
int l = fread(buf, 1, sizeof(buf) - 1, fp);
|
||||
buf[l] = '\0';
|
||||
fclose(fp);
|
||||
ASSERT_EQ("[sect1]\nprop1 = value1\n[sect2]\nprop2 = 2\nprop3 = yes\n", std::string(buf));
|
||||
|
||||
fp = fopen("test.cfg", "r");
|
||||
file = {};
|
||||
file.parse(fp);
|
||||
fclose(fp);
|
||||
ASSERT_EQ("value1", file.get("sect1", "prop1", ""));
|
||||
ASSERT_EQ(2, file.get_int("sect2", "prop2", 0));
|
||||
ASSERT_TRUE(file.get_bool("sect2", "prop3", false));
|
||||
}
|
||||
|
Loading…
Reference in New Issue