mirror of
https://github.com/Rosalie241/RMG.git
synced 2025-06-25 14:07:02 -04:00
1162 lines
32 KiB
C++
1162 lines
32 KiB
C++
/*
|
|
* Rosalie's Mupen GUI - https://github.com/Rosalie241/RMG
|
|
* Copyright (C) 2020-2025 Rosalie Wanders <rosalie@mailbox.org>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 3.
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
#define CORE_INTERNAL
|
|
#include "CachedRomHeaderAndSettings.hpp"
|
|
#include "Directories.hpp"
|
|
#include "RomSettings.hpp"
|
|
#include "RomHeader.hpp"
|
|
#include "Settings.hpp"
|
|
#include "Library.hpp"
|
|
#include "Cheats.hpp"
|
|
#include "Error.hpp"
|
|
|
|
#include "m64p/Api.hpp"
|
|
|
|
#ifdef USE_LIBFMT
|
|
#include "../3rdParty/fmt/include/fmt/core.h"
|
|
#include "../3rdParty/fmt/include/fmt/format.h"
|
|
#include "../3rdParty/fmt/include/fmt/format-inl.h"
|
|
#include "../3rdParty/fmt/src/format.cc"
|
|
|
|
#define fmt_string(...) fmt::format(__VA_ARGS__)
|
|
#else // USE_LIBFMT
|
|
#include <format>
|
|
|
|
#define fmt_string(...) std::format(__VA_ARGS__)
|
|
#endif // USE_LIBFMT
|
|
|
|
#include <filesystem>
|
|
#include <algorithm>
|
|
#include <fstream>
|
|
#include <sstream>
|
|
|
|
//
|
|
// Local Structs
|
|
//
|
|
|
|
struct l_LoadedCheat
|
|
{
|
|
CoreCheat cheat;
|
|
CoreCheatOption cheatOption;
|
|
};
|
|
|
|
//
|
|
// Local Variables
|
|
//
|
|
|
|
static CoreCheatFile l_SharedCheatFile;
|
|
static CoreCheatFile l_UserCheatFile;
|
|
static std::vector<l_LoadedCheat> l_LoadedCheats;
|
|
static std::vector<CoreCheat> l_NetplayCheats;
|
|
|
|
//
|
|
// Local Functions
|
|
//
|
|
|
|
static bool read_file_lines(const std::filesystem::path& file, std::vector<std::string>& lines)
|
|
{
|
|
std::string error;
|
|
std::ifstream inputStream(file);
|
|
std::string line;
|
|
|
|
if (!inputStream.is_open())
|
|
{
|
|
error = "read_file_lines Failed:";
|
|
error += "failed to open \"";
|
|
error += file.string();
|
|
error += "\"";
|
|
CoreSetError(error);
|
|
return false;
|
|
}
|
|
|
|
// read file line by line
|
|
while (std::getline(inputStream, line))
|
|
{
|
|
// strip '\r' to support CRLF (for windows users)
|
|
if (line.ends_with("\r"))
|
|
{
|
|
line.erase((line.size() - 1), 1);
|
|
}
|
|
lines.push_back(line);
|
|
}
|
|
|
|
inputStream.close();
|
|
return true;
|
|
}
|
|
|
|
static std::filesystem::path get_cheat_file_name(const CoreRomHeader& romHeader, const CoreRomSettings& romSettings)
|
|
{
|
|
std::filesystem::path cheatFileName;
|
|
|
|
// fallback to using MD5 as file name when CRC1 & CRC2 & CountryCode are 0
|
|
if (romHeader.CRC1 == 0 && romHeader.CRC2 == 0 && romHeader.CountryCode == 0)
|
|
{
|
|
// ensure MD5 is a valid length
|
|
if (romSettings.MD5.size() != 32)
|
|
{ // if it's invalid, return an empty path
|
|
return std::filesystem::path();
|
|
}
|
|
|
|
cheatFileName = fmt_string("{}.cht", romSettings.MD5);
|
|
}
|
|
else
|
|
{ // else use CRC1 & CRC2 & CountryCode
|
|
cheatFileName = fmt_string("{:08X}-{:08X}-{:02X}.cht", romHeader.CRC1, romHeader.CRC2, romHeader.CountryCode);
|
|
}
|
|
|
|
return cheatFileName;
|
|
}
|
|
|
|
static std::filesystem::path get_shared_cheat_file_path(const CoreRomHeader& romHeader, const CoreRomSettings& romSettings)
|
|
{
|
|
std::filesystem::path cheatFilePath;
|
|
|
|
cheatFilePath = CoreGetSharedDataDirectory();
|
|
cheatFilePath += CORE_DIR_SEPERATOR_STR;
|
|
cheatFilePath += "Cheats";
|
|
cheatFilePath += CORE_DIR_SEPERATOR_STR;
|
|
cheatFilePath += get_cheat_file_name(romHeader, romSettings);
|
|
|
|
return cheatFilePath;
|
|
}
|
|
|
|
static std::filesystem::path get_user_cheat_file_path(const CoreRomHeader& romHeader, const CoreRomSettings& romSettings)
|
|
{
|
|
std::filesystem::path oldCheatFilePath;
|
|
std::filesystem::path cheatFilePath;
|
|
std::error_code errorCode;
|
|
|
|
oldCheatFilePath = CoreGetUserDataDirectory();
|
|
oldCheatFilePath += CORE_DIR_SEPERATOR_STR;
|
|
oldCheatFilePath += "Cheats-User";
|
|
oldCheatFilePath += CORE_DIR_SEPERATOR_STR;
|
|
oldCheatFilePath += get_cheat_file_name(romHeader, romSettings);
|
|
|
|
cheatFilePath = CoreGetUserConfigDirectory();
|
|
cheatFilePath += CORE_DIR_SEPERATOR_STR;
|
|
cheatFilePath += "Cheats-User";
|
|
cheatFilePath += CORE_DIR_SEPERATOR_STR;
|
|
cheatFilePath += get_cheat_file_name(romHeader, romSettings);
|
|
|
|
// try to make the user cheats directory
|
|
// if it doesn't exist yet
|
|
if (!std::filesystem::is_directory(cheatFilePath.parent_path(), errorCode))
|
|
{
|
|
std::filesystem::create_directory(cheatFilePath.parent_path(), errorCode);
|
|
}
|
|
|
|
// keep compatability with <v0.4.1
|
|
if (std::filesystem::is_regular_file(oldCheatFilePath))
|
|
{
|
|
return oldCheatFilePath;
|
|
}
|
|
|
|
return cheatFilePath;
|
|
}
|
|
|
|
static std::vector<std::string> split_string(std::string str, char delim)
|
|
{
|
|
std::vector<std::string> splitString;
|
|
std::stringstream stringStream(str);
|
|
std::string element;
|
|
|
|
while (std::getline(stringStream, element, delim))
|
|
{
|
|
splitString.push_back(element);
|
|
}
|
|
|
|
return splitString;
|
|
}
|
|
|
|
static std::string join_split_string(const std::vector<std::string>& splitStr, char seperator, int skip = 0)
|
|
{
|
|
std::string joinedString;
|
|
std::string element;
|
|
int skippedElements = 0;
|
|
for (size_t i = 0; i < splitStr.size(); i++)
|
|
{
|
|
// allow for skipping elements
|
|
if (skippedElements++ < skip)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
element = splitStr[i];
|
|
|
|
joinedString += element;
|
|
// when not at the last element,
|
|
// insert seperator
|
|
if (i != (splitStr.size() - 1))
|
|
{
|
|
joinedString += seperator;
|
|
}
|
|
}
|
|
|
|
return joinedString;
|
|
}
|
|
|
|
static bool parse_cheat(const std::vector<std::string>& lines, int startIndex, CoreCheat& cheat, int& endIndex)
|
|
{
|
|
std::string error;
|
|
std::string line;
|
|
for (size_t i = startIndex; i < lines.size(); i++)
|
|
{
|
|
line = lines[i];
|
|
|
|
// Parse metadata
|
|
// $Cheat Name
|
|
// Author=Cheat Author
|
|
// Note=Cheat Note
|
|
if (line.starts_with("$"))
|
|
{
|
|
line.erase(0, 1);
|
|
cheat.Name = line;
|
|
continue;
|
|
}
|
|
else if (line.starts_with("Author="))
|
|
{
|
|
line.erase(0, 7);
|
|
cheat.Author = line;
|
|
continue;
|
|
}
|
|
else if (line.starts_with("Note="))
|
|
{
|
|
line.erase(0, 5);
|
|
cheat.Note = line;
|
|
continue;
|
|
}
|
|
|
|
// newline = new cheat
|
|
if (line.empty())
|
|
{
|
|
endIndex = i;
|
|
break;
|
|
}
|
|
|
|
const std::vector<std::string> splitLine = split_string(line, ' ');
|
|
|
|
// skip invalid lines
|
|
if (splitLine.size() < 2)
|
|
{
|
|
error = "parse_cheat Failed: ";
|
|
error += "invalid line: \"";
|
|
error += line;
|
|
error += "\"";
|
|
CoreSetError(error);
|
|
return false;
|
|
}
|
|
|
|
std::string address = splitLine[0];
|
|
std::string value = splitLine[1];
|
|
|
|
if (splitLine.size() == 2 && address.size() == 8 && (value.size() == 4 || value.size() == 9))
|
|
{ // cheat code
|
|
CoreCheatCode cheatCode;
|
|
cheatCode.Address = std::strtoll(address.c_str(), nullptr, 16);
|
|
|
|
// we don't support 'value:old value',
|
|
// so strip the old value
|
|
if (value.size() == 9)
|
|
{
|
|
if (value[4] == ':')
|
|
{
|
|
value.erase(4, 5);
|
|
}
|
|
else
|
|
{ // invalid line
|
|
error = "parse_cheat Failed: ";
|
|
error += "invalid line: \"";
|
|
error += line;
|
|
error += "\"";
|
|
CoreSetError(error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (value.find("?") != std::string::npos)
|
|
{ // value uses options
|
|
cheatCode.UseOptions = true;
|
|
cheatCode.OptionIndex = value.find('?');
|
|
cheatCode.OptionSize = std::count(value.begin(), value.end(), '?');
|
|
std::replace(value.begin(), value.end(), '?', '0');
|
|
if (value.empty())
|
|
{
|
|
cheatCode.Value = 0;
|
|
}
|
|
else
|
|
{
|
|
cheatCode.Value = std::strtoll(value.c_str(), nullptr, 16);
|
|
}
|
|
}
|
|
else
|
|
{ // value doesn't use options
|
|
cheatCode.UseOptions = false;
|
|
cheatCode.Value = std::strtoll(value.c_str(), nullptr, 16);
|
|
}
|
|
|
|
// add code to cheat
|
|
cheat.CheatCodes.push_back(cheatCode);
|
|
}
|
|
else if (address.size() <= 4 && value.size() > 0)
|
|
{ // cheat option
|
|
CoreCheatOption option;
|
|
|
|
option.Name = join_split_string(splitLine, ' ', 1);
|
|
option.Value = std::strtoll(address.c_str(), nullptr, 16);
|
|
option.Size = address.size();
|
|
|
|
// add option to cheat
|
|
cheat.HasOptions = true;
|
|
cheat.CheatOptions.push_back(option);
|
|
}
|
|
else
|
|
{ // invalid line?
|
|
error = "parse_cheat Failed: ";
|
|
error += "invalid line: \"";
|
|
error += line;
|
|
error += "\"";
|
|
CoreSetError(error);
|
|
return false;
|
|
}
|
|
|
|
// eof = stop reading
|
|
if (i == (lines.size() - 1))
|
|
{
|
|
endIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return !cheat.Name.empty() && !cheat.CheatCodes.empty();
|
|
}
|
|
|
|
static bool parse_cheat_file(const std::vector<std::string>& lines, CoreCheatFile& cheatFile)
|
|
{
|
|
int endIndex = -1;
|
|
std::string line;
|
|
std::string error;
|
|
bool readHeader = false;
|
|
bool readHeaderName = false;
|
|
|
|
for (size_t index = 0; index < lines.size(); index++)
|
|
{
|
|
line = lines[index];
|
|
|
|
if (!readHeader && line.starts_with("[") && line.ends_with("]"))
|
|
{
|
|
line.erase(0, 1); // strip '['
|
|
line.erase((line.size() - 1), 1); // strip ']'
|
|
|
|
if (line.size() == 32)
|
|
{ // MD5
|
|
cheatFile.CRC1 = 0;
|
|
cheatFile.CRC2 = 0;
|
|
cheatFile.CountryCode = 0;
|
|
cheatFile.MD5 = line;
|
|
}
|
|
else
|
|
{ // CRC1 & CRC2 & CountryCode
|
|
// validate header
|
|
if (line.size() != 22 || line[8] != '-' || line[17] != '-' || line[18] != 'C' || line[19] != ':')
|
|
{
|
|
error = "parse_cheat_file Failed: ";
|
|
error += "invalid header: \"";
|
|
error += line;
|
|
error += "\"";
|
|
CoreSetError(error);
|
|
return false;
|
|
}
|
|
|
|
const std::vector<std::string> splitCrcString = split_string(line, '-');
|
|
const std::string crc1 = splitCrcString[0];
|
|
const std::string crc2 = splitCrcString[1];
|
|
const std::string countryCode = split_string(line, ':')[1];
|
|
|
|
cheatFile.CRC1 = std::strtoll(crc1.c_str(), nullptr, 16);
|
|
cheatFile.CRC2 = std::strtoll(crc2.c_str(), nullptr, 16);
|
|
cheatFile.CountryCode = std::strtoll(countryCode.c_str(), nullptr, 16);
|
|
}
|
|
readHeader = true;
|
|
}
|
|
else if (readHeader && !readHeaderName && line.starts_with("Name="))
|
|
{
|
|
line.erase(0, 5);
|
|
cheatFile.Name = line;
|
|
readHeaderName = true;
|
|
}
|
|
else if (readHeader && readHeaderName && line.starts_with('$'))
|
|
{
|
|
CoreCheat cheat;
|
|
endIndex = 0;
|
|
|
|
if (!parse_cheat(lines, index, cheat, endIndex))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
cheatFile.Cheats.push_back(cheat);
|
|
index = endIndex;
|
|
}
|
|
else if (!line.empty())
|
|
{
|
|
error = "parse_cheat_file Failed: ";
|
|
error += "unknown line: \"";
|
|
error += line;
|
|
error += "\"";
|
|
CoreSetError(error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!readHeader)
|
|
{
|
|
error = "parse_cheat_file Failed: ";
|
|
error += "no header found!";
|
|
CoreSetError(error);
|
|
return false;
|
|
}
|
|
|
|
if (!readHeaderName)
|
|
{
|
|
error = "parse_cheat_file Failed: ";
|
|
error += "no header name found!";
|
|
CoreSetError(error);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool write_cheat_file(const CoreCheatFile& cheatFile, const std::filesystem::path& path)
|
|
{
|
|
std::string lines;
|
|
std::ofstream outputStream(path);
|
|
std::string error;
|
|
|
|
if (!outputStream.is_open())
|
|
{
|
|
error = "write_cheat_file Failed: ";
|
|
error += "Failed to open \"";
|
|
error += path.string();
|
|
error += "\'";
|
|
CoreSetError(error);
|
|
return false;
|
|
}
|
|
|
|
// fallback to using MD5 when CRC1 & CRC2 & CountryCode are 0
|
|
if (cheatFile.CRC1 == 0 && cheatFile.CRC2 == 0 && cheatFile.CountryCode == 0)
|
|
{
|
|
lines += fmt_string("[{}]\n", cheatFile.MD5);
|
|
}
|
|
else
|
|
{ // else use CRC1 & CRC2 & CountryCode
|
|
lines += fmt_string("[{:08X}-{:08X}-C:{:02X}]\n", cheatFile.CRC1, cheatFile.CRC2, cheatFile.CountryCode);
|
|
}
|
|
|
|
lines += fmt_string("Name={}\n\n", cheatFile.Name);
|
|
|
|
for (const CoreCheat& cheat : cheatFile.Cheats)
|
|
{
|
|
lines += fmt_string("${}\n", cheat.Name);
|
|
|
|
if (!cheat.Author.empty())
|
|
{
|
|
lines += fmt_string("Author={}\n", cheat.Author);
|
|
}
|
|
|
|
if (!cheat.Note.empty())
|
|
{
|
|
lines += fmt_string("Note={}\n", cheat.Note);
|
|
}
|
|
|
|
for (const CoreCheatCode& code : cheat.CheatCodes)
|
|
{
|
|
if (code.UseOptions)
|
|
{
|
|
std::string value = fmt_string("{:04X}", code.Value);
|
|
|
|
// insert question mark string
|
|
value.replace(code.OptionIndex, code.OptionSize, code.OptionSize, '?');
|
|
|
|
lines += fmt_string("{:04X} {}\n", code.Address, value);
|
|
}
|
|
else
|
|
{
|
|
lines += fmt_string("{:08X} {:04X}\n", code.Address, code.Value);
|
|
}
|
|
}
|
|
|
|
if (cheat.HasOptions)
|
|
{
|
|
for (const CoreCheatOption& option : cheat.CheatOptions)
|
|
{
|
|
lines += fmt_string("{:0{}X} {}\n", option.Value, option.Size, option.Name);
|
|
}
|
|
}
|
|
|
|
// extra newline
|
|
lines += "\n";
|
|
}
|
|
|
|
outputStream << lines;
|
|
outputStream.close();
|
|
return true;
|
|
}
|
|
|
|
bool combine_cheat_code_and_option(const CoreCheatCode& code, const CoreCheatOption& option, int32_t& combinedValue)
|
|
{
|
|
std::string codeValueString;
|
|
std::string optionValueString;
|
|
|
|
// make sure the code needs an option
|
|
if (!code.UseOptions)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// make sure the size is the same
|
|
if (code.OptionSize != option.Size)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
codeValueString = fmt_string("{:04X}", code.Value);
|
|
optionValueString = fmt_string("{:0{}X}", option.Value, option.Size);
|
|
|
|
// insert option
|
|
codeValueString.replace(code.OptionIndex, code.OptionSize, optionValueString);
|
|
|
|
// convert to int32_t
|
|
combinedValue = std::strtoll(codeValueString.c_str(), nullptr, 16);
|
|
return true;
|
|
}
|
|
|
|
static std::vector<CoreCheat>::iterator find_user_cheat_using_name(std::string name)
|
|
{
|
|
auto predicate = [name](const CoreCheat& other)
|
|
{
|
|
return name == other.Name;
|
|
};
|
|
|
|
return std::find_if(l_UserCheatFile.Cheats.begin(), l_UserCheatFile.Cheats.end(), predicate);
|
|
}
|
|
|
|
static bool get_romheader_and_romsettings(const std::filesystem::path& file, CoreRomHeader& romHeader, CoreRomSettings& romSettings)
|
|
{
|
|
if (file.empty())
|
|
{
|
|
if (!CoreGetCurrentRomHeader(romHeader) ||
|
|
!CoreGetCurrentRomSettings(romSettings))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return CoreGetCachedRomHeaderAndSettings(file, nullptr, &romHeader, nullptr, &romSettings);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//
|
|
// Exported Functions
|
|
//
|
|
|
|
CORE_EXPORT bool CoreGetCurrentCheats(std::filesystem::path file, std::vector<CoreCheat>& cheats)
|
|
{
|
|
CoreRomHeader romHeader;
|
|
CoreRomSettings romSettings;
|
|
CoreCheatFile sharedCheatFile;
|
|
CoreCheatFile userCheatFile;
|
|
std::filesystem::path sharedCheatFilePath;
|
|
std::filesystem::path userCheatFilePath;
|
|
bool hasSharedCheatFile = false;
|
|
bool hasUserCheatFile = false;
|
|
std::vector<std::string> sharedCheatFilelines;
|
|
std::vector<std::string> userCheatFileLines;
|
|
|
|
if (!get_romheader_and_romsettings(file, romHeader, romSettings))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
sharedCheatFilePath = get_shared_cheat_file_path(romHeader, romSettings);
|
|
userCheatFilePath = get_user_cheat_file_path(romHeader, romSettings);
|
|
|
|
// do nothing if neither the shared or user cheat file exists
|
|
hasSharedCheatFile = std::filesystem::is_regular_file(sharedCheatFilePath);
|
|
hasUserCheatFile = std::filesystem::is_regular_file(userCheatFilePath);
|
|
if (!hasSharedCheatFile && !hasUserCheatFile)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// fail when we fail to the shared or user cheat file
|
|
if ((hasSharedCheatFile && !read_file_lines(sharedCheatFilePath, sharedCheatFilelines)) ||
|
|
(hasUserCheatFile && !read_file_lines(userCheatFilePath, userCheatFileLines)))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// fail when we fail to parse the shared or user cheat file
|
|
if ((hasSharedCheatFile && !parse_cheat_file(sharedCheatFilelines, sharedCheatFile)) ||
|
|
(hasUserCheatFile && !parse_cheat_file(userCheatFileLines, userCheatFile)))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
l_SharedCheatFile = sharedCheatFile;
|
|
l_UserCheatFile = userCheatFile;
|
|
|
|
// add shared & user cheats
|
|
// add user cheats first
|
|
for (const CoreCheat& cheat : userCheatFile.Cheats)
|
|
{
|
|
cheats.push_back(cheat);
|
|
}
|
|
// add shared cheats
|
|
// and check if any cheats with the same name
|
|
// already exist, if it does, then just skip them
|
|
for (const CoreCheat& cheat : sharedCheatFile.Cheats)
|
|
{
|
|
auto iter = find_user_cheat_using_name(cheat.Name);
|
|
|
|
// skip cheat with the same name
|
|
if (iter != l_UserCheatFile.Cheats.end())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
cheats.push_back(cheat);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
CORE_EXPORT bool CoreParseCheat(const std::vector<std::string>& lines, CoreCheat& cheat)
|
|
{
|
|
int endIndex = 0;
|
|
return parse_cheat(lines, 0, cheat, endIndex);
|
|
}
|
|
|
|
CORE_EXPORT bool CoreGetCheatLines(CoreCheat cheat, std::vector<std::string>& codeLines, std::vector<std::string>& optionLines)
|
|
{
|
|
for (const CoreCheatCode& code : cheat.CheatCodes)
|
|
{
|
|
if (code.UseOptions)
|
|
{
|
|
std::string value = fmt_string("{:04X}", code.Value);
|
|
|
|
// insert question mark string
|
|
value.replace(code.OptionIndex, code.OptionSize, code.OptionSize, '?');
|
|
|
|
codeLines.push_back(fmt_string("{:04X} {}", code.Address, value));
|
|
}
|
|
else
|
|
{
|
|
codeLines.push_back(fmt_string("{:08X} {:04X}", code.Address, code.Value));
|
|
}
|
|
}
|
|
|
|
if (cheat.HasOptions)
|
|
{
|
|
for (const CoreCheatOption& option : cheat.CheatOptions)
|
|
{
|
|
optionLines.push_back(fmt_string("{:0{}X} {}", option.Value, option.Size, option.Name));
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
CORE_EXPORT bool CoreAddCheat(std::filesystem::path file, CoreCheat cheat)
|
|
{
|
|
std::string error;
|
|
CoreRomHeader romHeader;
|
|
CoreRomSettings romSettings;
|
|
std::filesystem::path cheatFilePath;
|
|
|
|
if (!get_romheader_and_romsettings(file, romHeader, romSettings))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
cheatFilePath = get_user_cheat_file_path(romHeader, romSettings);
|
|
|
|
// try to find another cheat with the same name,
|
|
// if it exists, fail because we don't allow that
|
|
auto iter = find_user_cheat_using_name(cheat.Name);
|
|
if (iter != l_UserCheatFile.Cheats.end())
|
|
{
|
|
error = "CoreAddCheat Failed: ";
|
|
error += "cheat with name already exists!";
|
|
CoreSetError(error);
|
|
return false;
|
|
}
|
|
|
|
// copy info to cheat file
|
|
l_UserCheatFile.CRC1 = romHeader.CRC1;
|
|
l_UserCheatFile.CRC2 = romHeader.CRC2;
|
|
l_UserCheatFile.CountryCode = romHeader.CountryCode;
|
|
l_UserCheatFile.MD5 = romSettings.MD5;
|
|
l_UserCheatFile.Name = romSettings.GoodName;
|
|
l_UserCheatFile.Cheats.push_back(cheat);
|
|
|
|
return write_cheat_file(l_UserCheatFile, cheatFilePath);
|
|
}
|
|
|
|
CORE_EXPORT bool CoreUpdateCheat(std::filesystem::path file, CoreCheat oldCheat, CoreCheat newCheat)
|
|
{
|
|
CoreRomHeader romHeader;
|
|
CoreRomSettings romSettings;
|
|
std::filesystem::path cheatFilePath;
|
|
CoreCheatOption cheatOption;
|
|
|
|
if (!get_romheader_and_romsettings(file, romHeader, romSettings))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
cheatFilePath = get_user_cheat_file_path(romHeader, romSettings);
|
|
|
|
// copy over cheat settings when name has changed
|
|
if (oldCheat.Name != newCheat.Name)
|
|
{
|
|
CoreEnableCheat(file, newCheat, CoreIsCheatEnabled(file, oldCheat));
|
|
|
|
// only set option to new cheat when
|
|
// retrieving from old one succeeds
|
|
if (CoreGetCheatOption(file, oldCheat, cheatOption))
|
|
{
|
|
CoreSetCheatOption(file, newCheat, cheatOption);
|
|
}
|
|
|
|
// reset old cheat settings
|
|
CoreEnableCheat(file, oldCheat, false);
|
|
CoreResetCheatOption(file, oldCheat);
|
|
}
|
|
|
|
// try to find old cheat in user cheats,
|
|
// when it isnt found, it's most likely a system cheat
|
|
auto iter = find_user_cheat_using_name(oldCheat.Name);
|
|
if (iter != l_UserCheatFile.Cheats.end())
|
|
{ // when found, erase it
|
|
l_UserCheatFile.Cheats.erase(iter);
|
|
}
|
|
|
|
// copy info to cheat file & add cheat
|
|
l_UserCheatFile.CRC1 = romHeader.CRC1;
|
|
l_UserCheatFile.CRC2 = romHeader.CRC2;
|
|
l_UserCheatFile.CountryCode = romHeader.CountryCode;
|
|
l_UserCheatFile.MD5 = romSettings.MD5;
|
|
l_UserCheatFile.Name = romSettings.GoodName;
|
|
l_UserCheatFile.Cheats.push_back(newCheat);
|
|
return write_cheat_file(l_UserCheatFile, cheatFilePath);
|
|
}
|
|
|
|
CORE_EXPORT bool CoreCanRemoveCheat(CoreCheat cheat)
|
|
{
|
|
return std::find(l_UserCheatFile.Cheats.begin(), l_UserCheatFile.Cheats.end(), cheat) != l_UserCheatFile.Cheats.end();
|
|
}
|
|
|
|
CORE_EXPORT bool CoreRemoveCheat(std::filesystem::path file, CoreCheat cheat)
|
|
{
|
|
CoreRomHeader romHeader;
|
|
CoreRomSettings romSettings;
|
|
std::filesystem::path cheatFilePath;
|
|
|
|
if (!CoreCanRemoveCheat(cheat))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!get_romheader_and_romsettings(file, romHeader, romSettings))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
cheatFilePath = get_user_cheat_file_path(romHeader, romSettings);
|
|
|
|
// try to find the cheat
|
|
auto iter = std::find(l_UserCheatFile.Cheats.begin(), l_UserCheatFile.Cheats.end(), cheat);
|
|
if (iter != l_UserCheatFile.Cheats.end())
|
|
{
|
|
l_UserCheatFile.Cheats.erase(iter);
|
|
}
|
|
|
|
return write_cheat_file(l_UserCheatFile, cheatFilePath);
|
|
}
|
|
|
|
CORE_EXPORT bool CoreEnableCheat(std::filesystem::path file, CoreCheat cheat, bool enabled)
|
|
{
|
|
CoreRomHeader romHeader;
|
|
CoreRomSettings romSettings;
|
|
std::string settingSection;
|
|
std::string settingKey;
|
|
|
|
if (!get_romheader_and_romsettings(file, romHeader, romSettings))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
settingSection = romSettings.MD5 + " Cheats";
|
|
settingKey = "Cheat \"" + cheat.Name + "\" Enabled";
|
|
|
|
// if the cheat is disabled and the settings key doesn't exist, do nothing
|
|
if (!enabled && !CoreSettingsKeyExists(settingSection, settingKey))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return CoreSettingsSetValue(settingSection, settingKey, enabled);
|
|
}
|
|
|
|
CORE_EXPORT bool CoreIsCheatEnabled(std::filesystem::path file, CoreCheat cheat)
|
|
{
|
|
CoreRomHeader romHeader;
|
|
CoreRomSettings romSettings;
|
|
std::string settingSection;
|
|
std::string settingKey;
|
|
|
|
if (!get_romheader_and_romsettings(file, romHeader, romSettings))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
settingSection = romSettings.MD5 + " Cheats";
|
|
settingKey = "Cheat \"" + cheat.Name + "\" Enabled";
|
|
|
|
return CoreSettingsGetBoolValue(settingSection, settingKey, false);
|
|
}
|
|
|
|
CORE_EXPORT bool CoreHasCheatOptionSet(std::filesystem::path file, CoreCheat cheat)
|
|
{
|
|
CoreRomHeader romHeader;
|
|
CoreRomSettings romSettings;
|
|
std::string settingSection;
|
|
std::string settingKey;
|
|
|
|
if (!get_romheader_and_romsettings(file, romHeader, romSettings))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
settingSection = romSettings.MD5 + " Cheats";
|
|
settingKey = "Cheat \"" + cheat.Name + "\" Option";
|
|
|
|
return CoreSettingsGetIntValue(settingSection, settingKey, -1) != -1;
|
|
}
|
|
|
|
CORE_EXPORT bool CoreSetCheatOption(std::filesystem::path file, CoreCheat cheat, CoreCheatOption option)
|
|
{
|
|
CoreRomHeader romHeader;
|
|
CoreRomSettings romSettings;
|
|
std::string settingSection;
|
|
std::string settingKey;
|
|
|
|
if (!get_romheader_and_romsettings(file, romHeader, romSettings))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
settingSection = romSettings.MD5 + " Cheats";
|
|
settingKey = "Cheat \"" + cheat.Name + "\" Option";
|
|
|
|
return CoreSettingsSetValue(settingSection, settingKey, static_cast<int>(option.Value));
|
|
}
|
|
|
|
CORE_EXPORT bool CoreGetCheatOption(std::filesystem::path file, CoreCheat cheat, CoreCheatOption& option)
|
|
{
|
|
CoreRomHeader romHeader;
|
|
CoreRomSettings romSettings;
|
|
std::string settingSection;
|
|
std::string settingKey;
|
|
int value = 0;
|
|
|
|
if (!cheat.HasOptions)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!get_romheader_and_romsettings(file, romHeader, romSettings))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
settingSection = romSettings.MD5 + " Cheats";
|
|
settingKey = "Cheat \"" + cheat.Name + "\" Option";
|
|
|
|
value = CoreSettingsGetIntValue(settingSection, settingKey, -1);
|
|
if (value == -1)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (const CoreCheatOption& cheatOption : cheat.CheatOptions)
|
|
{
|
|
if (cheatOption.Value == (uint32_t)value)
|
|
{
|
|
option = cheatOption;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// when nothing was found, reset option
|
|
CoreSettingsSetValue(settingSection, settingKey, -1);
|
|
return false;
|
|
}
|
|
|
|
CORE_EXPORT bool CoreResetCheatOption(std::filesystem::path file, CoreCheat cheat)
|
|
{
|
|
CoreRomHeader romHeader;
|
|
CoreRomSettings romSettings;
|
|
std::string settingSection;
|
|
std::string settingKey;
|
|
|
|
if (!cheat.HasOptions)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!get_romheader_and_romsettings(file, romHeader, romSettings))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
settingSection = romSettings.MD5 + " Cheats";
|
|
settingKey = "Cheat \"" + cheat.Name + "\" Option";
|
|
|
|
CoreSettingsSetValue(settingSection, settingKey, -1);
|
|
return true;
|
|
}
|
|
|
|
CORE_EXPORT bool CoreApplyCheats(void)
|
|
{
|
|
std::string error;
|
|
m64p_error ret;
|
|
std::vector<m64p_cheat_code> m64p_cheatCodes;
|
|
std::vector<CoreCheat> cheats;
|
|
CoreCheatOption cheatOption;
|
|
bool skipCheat = false;
|
|
int32_t combinedValue;
|
|
|
|
if (!m64p::Core.IsHooked())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// fail when parsing cheats fails
|
|
if (!CoreGetCurrentCheats("", cheats))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// fail when clearing cheats fails
|
|
if (!CoreClearCheats())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (const CoreCheat& cheat : cheats)
|
|
{
|
|
skipCheat = false;
|
|
|
|
if (!CoreIsCheatEnabled("", cheat))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (const CoreCheatCode& code : cheat.CheatCodes)
|
|
{
|
|
if (code.UseOptions)
|
|
{
|
|
// make sure an option has been set
|
|
if (!CoreHasCheatOptionSet("", cheat))
|
|
{
|
|
skipCheat = true;
|
|
break;
|
|
}
|
|
|
|
// make sure retrieving it succeeds
|
|
if (!CoreGetCheatOption("", cheat, cheatOption))
|
|
{
|
|
skipCheat = true;
|
|
break;
|
|
}
|
|
|
|
// make sure combining the cheat code & option succeeds
|
|
if (!combine_cheat_code_and_option(code, cheatOption, combinedValue))
|
|
{
|
|
skipCheat = true;
|
|
break;
|
|
}
|
|
|
|
m64p_cheatCodes.push_back({code.Address, combinedValue});
|
|
}
|
|
else
|
|
{
|
|
m64p_cheatCodes.push_back({code.Address, code.Value});
|
|
}
|
|
}
|
|
|
|
if (skipCheat)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ret = m64p::Core.AddCheat(cheat.Name.c_str(), m64p_cheatCodes.data(), m64p_cheatCodes.size());
|
|
if (ret != M64ERR_SUCCESS)
|
|
{
|
|
error = "CoreApplyCheats m64p::Core.AddCheat(";
|
|
error += cheat.Name.c_str();
|
|
error += ") Failed:";
|
|
error += m64p::Core.ErrorMessage(ret);
|
|
CoreSetError(error);
|
|
return false;
|
|
}
|
|
|
|
// add cheat to loaded cheats
|
|
l_LoadedCheats.push_back({cheat, cheatOption});
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
CORE_EXPORT bool CoreClearCheats(void)
|
|
{
|
|
std::string error;
|
|
m64p_error ret;
|
|
|
|
if (!m64p::Core.IsHooked())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (const l_LoadedCheat& loadedCheat : l_LoadedCheats)
|
|
{
|
|
ret = m64p::Core.CheatEnabled(loadedCheat.cheat.Name.c_str(), 0);
|
|
if (ret != M64ERR_SUCCESS)
|
|
{
|
|
error = "CoreClearCheats m64p::Core.CheatEnabled(";
|
|
error += loadedCheat.cheat.Name.c_str();
|
|
error += ") Failed:";
|
|
error += m64p::Core.ErrorMessage(ret);
|
|
CoreSetError(error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
l_SharedCheatFile = {};
|
|
l_UserCheatFile = {};
|
|
l_LoadedCheats.clear();
|
|
l_NetplayCheats.clear();
|
|
return true;
|
|
}
|
|
|
|
CORE_EXPORT bool CoreSetNetplayCheats(const std::vector<CoreCheat>& cheats)
|
|
{
|
|
l_NetplayCheats = cheats;
|
|
return true;
|
|
}
|
|
|
|
CORE_EXPORT bool CoreApplyNetplayCheats(void)
|
|
{
|
|
std::string error;
|
|
m64p_error ret;
|
|
std::vector<m64p_cheat_code> m64p_cheatCodes;
|
|
std::vector<CoreCheat> cheats;
|
|
CoreCheatOption cheatOption;
|
|
bool skipCheat = false;
|
|
int32_t combinedValue;
|
|
|
|
if (!m64p::Core.IsHooked())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (const CoreCheat& cheat : l_NetplayCheats)
|
|
{
|
|
skipCheat = false;
|
|
|
|
for (const CoreCheatCode& code : cheat.CheatCodes)
|
|
{
|
|
if (code.UseOptions)
|
|
{
|
|
// make sure an option has been set and is valid
|
|
if (cheat.CheatOptions.size() != 1)
|
|
{
|
|
skipCheat = true;
|
|
break;
|
|
}
|
|
|
|
// make sure retrieving it succeeds
|
|
cheatOption = cheat.CheatOptions[0];
|
|
|
|
// make sure combining the cheat code & option succeeds
|
|
if (!combine_cheat_code_and_option(code, cheatOption, combinedValue))
|
|
{
|
|
skipCheat = true;
|
|
break;
|
|
}
|
|
|
|
m64p_cheatCodes.push_back({code.Address, combinedValue});
|
|
}
|
|
else
|
|
{
|
|
m64p_cheatCodes.push_back({code.Address, code.Value});
|
|
}
|
|
}
|
|
|
|
if (skipCheat)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ret = m64p::Core.AddCheat(cheat.Name.c_str(), m64p_cheatCodes.data(), m64p_cheatCodes.size());
|
|
if (ret != M64ERR_SUCCESS)
|
|
{
|
|
error = "CoreApplyNetplayCheats m64p::Core.AddCheat(";
|
|
error += cheat.Name.c_str();
|
|
error += ") Failed:";
|
|
error += m64p::Core.ErrorMessage(ret);
|
|
CoreSetError(error);
|
|
return false;
|
|
}
|
|
|
|
// add cheat to loaded cheats
|
|
l_LoadedCheats.push_back({cheat, cheatOption});
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
CORE_EXPORT bool CorePressGamesharkButton(bool enabled)
|
|
{
|
|
std::string error;
|
|
m64p_error ret;
|
|
int tmpValue = enabled ? 1 : 0;
|
|
|
|
if (!m64p::Core.IsHooked())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ret = m64p::Core.DoCommand(M64CMD_CORE_STATE_SET, M64CORE_INPUT_GAMESHARK, &tmpValue);
|
|
if (ret != M64ERR_SUCCESS)
|
|
{
|
|
error = "CorePressGamesharkButton m64p::Core.DoCommand(M64CMD_CORE_STATE_SET) Failed: ";
|
|
error += m64p::Core.ErrorMessage(ret);
|
|
CoreSetError(error);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|