diff --git a/Core/CwCheat.cpp b/Core/CwCheat.cpp index 0e5ba0cb5..b27741442 100644 --- a/Core/CwCheat.cpp +++ b/Core/CwCheat.cpp @@ -1,3 +1,7 @@ +#include +#include +#include +#include #include "i18n/i18n.h" #include "Common/StringUtils.h" #include "Common/ChunkFile.h" @@ -23,13 +27,191 @@ std::string activeCheatFile; static CWCheatEngine *cheatEngine; static bool cheatsEnabled; void hleCheat(u64 userdata, int cyclesLate); -void trim2(std::string& str); + +static inline std::string TrimString(const std::string &s) { + auto wsfront = std::find_if_not(s.begin(), s.end(), [](int c) { + return std::isspace(c); + }); + auto wsback = std::find_if_not(s.rbegin(), s.rend(), [](int c){ + return std::isspace(c); + }).base(); + return wsback > wsfront ? std::string(wsfront, wsback) : std::string(); +} + +class CheatFileParser { +public: + CheatFileParser(const std::string &filename, const std::string &gameID = "") { +#if defined(_WIN32) && !defined(__MINGW32__) + file_.open(ConvertUTF8ToWString(activeCheatFile)); +#else + file_.open(activeCheatFile.c_str()); +#endif + + validGameID_ = ReplaceAll(gameID, "-", ""); + } + + bool Parse(); + + std::vector GetErrors() const { + return errors_; + } + + std::vector GetCheats() const { + return cheats_; + } + +protected: + void Flush(); + void AddError(const std::string &msg); + void ParseLine(const std::string &line); + void ParseDataLine(const std::string &line, CheatCodeFormat format); + bool ValidateGameID(const std::string &gameID); + + std::ifstream file_; + std::string validGameID_; + + int line_ = 0; + int games_ = 0; + std::vector errors_; + std::vector cheats_; + std::vector pendingLines_; + CheatCodeFormat codeFormat_ = CheatCodeFormat::UNDEFINED; + bool gameEnabled_ = true; + bool gameRiskyEnabled_ = false; + bool cheatEnabled_ = false; +}; + +bool CheatFileParser::Parse() { + for (line_ = 1; file_ && !file_.eof(); ++line_) { + std::string line; + getline(file_, line, '\n'); + line = TrimString(line); + + if (line.length() > 2 && line[0] == '_') { + ParseLine(line); + } else if (line.length() >= 2 && line[0] == '/' && line[1] == '/') { + // Comment, ignore. + } else if (line.length() >= 1 && line[0] == '#') { + // Comment, ignore. + } else if (line.length() > 0) { + errors_.push_back(StringFromFormat("Unrecognized content on line %d: expecting _", line_)); + } + } + + Flush(); + + return errors_.empty(); +} + +void CheatFileParser::Flush() { + if (!pendingLines_.empty()) { + cheats_.push_back({ codeFormat_, pendingLines_ }); + pendingLines_.clear(); + } + codeFormat_ = CheatCodeFormat::UNDEFINED; +} + +void CheatFileParser::AddError(const std::string &err) { + errors_.push_back(StringFromFormat("Error on line %d: %s", line_, err.c_str())); +} + +void CheatFileParser::ParseLine(const std::string &line) { + switch (line[1]) { + case 'S': + // Disc ID, validate (for multi-disc cheat files)? + Flush(); + ++games_; + + if (ValidateGameID(line.substr(2))) { + if (gameRiskyEnabled_) { + // We found the right one, so let's not use this risky stuff. + cheats_.clear(); + gameRiskyEnabled_ = false; + } + gameEnabled_ = true; + } else if (games_ == 1) { + // Old behavior was to ignore. + // For BC, let's allow if the game id doesn't match, but there's only one line. + gameRiskyEnabled_ = true; + gameEnabled_ = true; + } else { + if (gameRiskyEnabled_) { + // There are multiple games here, kill the risky stuff. + cheats_.clear(); + gameRiskyEnabled_ = false; + } + gameEnabled_ = false; + } + return; + + case 'G': + // Game title. + return; + + case 'C': + // Cheat name and activation status. + if (line.length() >= 3 && line[2] >= '1' && line[2] <= '9') { + cheatEnabled_ = true; + } else if (line.length() >= 3 && line[2] == '0') { + cheatEnabled_ = false; + } else { + AddError("could not parse cheat name line"); + cheatEnabled_ = false; + return; + } + + Flush(); + return; + + case 'L': + // CwCheat data line. + ParseDataLine(line.substr(2), CheatCodeFormat::CWCHEAT); + return; + + case 'M': + // TempAR data line. + ParseDataLine(line.substr(2), CheatCodeFormat::TEMPAR); + return; + + default: + AddError("unknown line type"); + return; + } +} + +void CheatFileParser::ParseDataLine(const std::string &line, CheatCodeFormat format) { + if (codeFormat_ == CheatCodeFormat::UNDEFINED) { + codeFormat_ = format; + } else if (codeFormat_ != format) { + AddError("mixed code format (cwcheat/tempar)"); + pendingLines_.clear(); + cheatEnabled_ = false; + } + + if (!cheatEnabled_ || !gameEnabled_) { + return; + } + + CheatLine cheatLine; + int len = 0; + if (sscanf(line.c_str(), "%x %x %n", &cheatLine.part1, &cheatLine.part2, &len) == 2) { + if ((size_t)len < line.length()) { + AddError("junk after line data"); + } + pendingLines_.push_back(cheatLine); + } else { + AddError("expecting two values"); + } +} + +bool CheatFileParser::ValidateGameID(const std::string &gameID) { + return validGameID_.empty() || ReplaceAll(TrimString(gameID), "-", "") == validGameID_; +} static void __CheatStop() { - if (cheatEngine != 0) { - cheatEngine->Exit(); + if (cheatEngine) { delete cheatEngine; - cheatEngine = 0; + cheatEngine = nullptr; } cheatsEnabled = false; } @@ -44,7 +226,7 @@ static void __CheatStart() { } cheatEngine = new CWCheatEngine(); - cheatEngine->CreateCodeList(); + cheatEngine->ParseCheats(); g_Config.bReloadCheats = false; cheatsEnabled = true; } @@ -105,7 +287,7 @@ void hleCheat(u64 userdata, int cyclesLate) { return; if (g_Config.bReloadCheats) { //Checks if the "reload cheats" button has been pressed. - cheatEngine->CreateCodeList(); + cheatEngine->ParseCheats(); g_Config.bReloadCheats = false; } cheatEngine->Run(); @@ -114,33 +296,6 @@ void hleCheat(u64 userdata, int cyclesLate) { CWCheatEngine::CWCheatEngine() { } -void CWCheatEngine::Exit() { - exit2 = true; -} - -// Takes a single code line and creates a two-part vector for each code. Feeds to CreateCodeList -static inline std::vector makeCodeParts(const std::vector& CodesList) { - std::string currentcode; - std::vector finalList; - char split_char = '\n'; - char empty = ' '; - for (size_t i = 0; i < CodesList.size(); i++) { - currentcode = CodesList[i]; - for (size_t j = 0; j < currentcode.length(); j++) { - if (currentcode[j] == empty) { - currentcode[j] = '\n'; - } - } - trim2(currentcode); - std::istringstream iss(currentcode); - std::string each; - while (std::getline(iss, each, split_char)) { - finalList.push_back(each); - } - } - return finalList; -} - void CWCheatEngine::CreateCheatFile() { activeCheatFile = GetSysDirectory(DIRECTORY_CHEATS) + gameTitle + ".ini"; File::CreateFullPath(GetSysDirectory(DIRECTORY_CHEATS)); @@ -158,138 +313,37 @@ void CWCheatEngine::CreateCheatFile() { } } -void CWCheatEngine::CreateCodeList() { //Creates code list to be used in function GetNextCode - initialCodesList = GetCodesList(); - std::string currentcode, codename; - std::vector codelist; - for (size_t i = 0; i < initialCodesList.size(); i ++) { - if (initialCodesList[i].substr(0,2) == "_S") { - continue; //Line indicates Disc ID, not needed for cheats - } - if (initialCodesList[i].substr(0,2) == "_G") { - continue; //Line indicates game Title, also not needed for cheats - } - if (initialCodesList[i].substr(0,2) == "//") { - continue; //Line indicates comment, also not needed for cheats. - } - if (initialCodesList[i].substr(0,2) == "_C") { - if (initialCodesList[i].substr(2, 1) == "0") { - cheatEnabled = false; - } else { - cheatEnabled = true; - } - codename = initialCodesList[i]; - codename.erase (codename.begin(), codename.begin()+4); - codeNameList.push_back(codename); //Import names for GUI, will be implemented later. - continue; - } - if (initialCodesList[i].substr(0,2) == "_L") { - if (cheatEnabled == true) { - currentcode = initialCodesList[i]; - std::size_t comment = currentcode.find("//"); - if (comment != std::string::npos) { - currentcode.erase(currentcode.begin() + comment, currentcode.end()); - //Purge comments after code lines before adding them to codelist - } - currentcode.erase(currentcode.begin(), currentcode.begin() + 3); - codelist.push_back(currentcode); - } - continue; - } - } - parts = makeCodeParts(codelist); +void CWCheatEngine::ParseCheats() { + CheatFileParser parser(activeCheatFile, gameTitle); + + parser.Parse(); + // TODO: Report errors. + + cheats_ = parser.GetCheats(); } -std::vector CWCheatEngine::GetNextCode() { // Feeds a size-2 vector of ints to Run() which contains the address and value of one cheat. - std::string code1; - std::string code2; - std::vector splitCode; - std::vector finalCode; - std::string modifier2 = "0"; - while (true) { - // Shouldn't splitCode be cleared here? Otherwise var1 and var2 will always be set to the first two codes.. I don't get it - if (currentCode >= parts.size()) { - code1.clear(); - code2.clear(); - break; - } - code1 = parts[currentCode++]; - trim2(code1); - if (currentCode >= parts.size()) { - code1.clear(); - code2.clear(); - break; - } - code2 = parts[currentCode++]; - trim2(code2); - splitCode.push_back(code1); - splitCode.push_back(code2); - - int var1 = (int) parseHexLong(splitCode[0]); - int var2 = (int) parseHexLong(splitCode[1]); - finalCode.push_back(var1); - finalCode.push_back(var2); - if (splitCode[0].substr(0,1) == modifier2) { - break; - } - } - return finalCode; -} - -void CWCheatEngine::SkipCodes(int count) { - for (int i = 0; i < count; i ++) { - auto code = GetNextCode(); - if (code.empty()) - { - WARN_LOG(COMMON, "CWCHEAT: Tried to skip more codes than there are, the cheat is most likely wrong"); - break; - } - if (code[0] == 0) { - break; - } - } -} - -void CWCheatEngine::SkipAllCodes() { - currentCode = codes.size() - 1; -} - -int CWCheatEngine::GetAddress(int value) { //Returns static address used by ppsspp. Some games may not like this, and causes cheats to not work without offset - int address = (value + 0x08800000) & 0x3FFFFFFF; +u32 CWCheatEngine::GetAddress(u32 value) { + // Returns static address used by ppsspp. Some games may not like this, and causes cheats to not work without offset + u32 address = (value + 0x08800000) & 0x3FFFFFFF; return address; } - -inline void trim2(std::string& str) { - size_t pos = str.find_last_not_of(' '); - if(pos != std::string::npos) { - str.erase(pos + 1); - pos = str.find_first_not_of(' '); - if(pos != std::string::npos) str.erase(0, pos); - } - else str.erase(str.begin(), str.end()); -} - -std::vector CWCheatEngine::GetCodesList() { //Reads the entire cheat list from the appropriate .ini. - std::string line; - std::vector codesList; // Read from INI here +std::vector CWCheatEngine::GetCodesList() { + // Reads the entire cheat list from the appropriate .ini. + std::vector codesList; #if defined(_WIN32) && !defined(__MINGW32__) std::ifstream list(ConvertUTF8ToWString(activeCheatFile)); #else std::ifstream list(activeCheatFile.c_str()); #endif - if (!list) { - return codesList; - } - for (int i = 0; !list.eof(); i ++) { + while (list && !list.eof()) { + std::string line; getline(list, line, '\n'); - if (line.length() > 3 && (line.substr(0,1) == "_"||line.substr(0,2) == "//")){ - codesList.push_back(line); + + if (line.length() > 3 && (line.substr(0, 1) == "_" || line.substr(0, 2) == "//")) { + codesList.push_back(TrimString(line)); } } - for(size_t i = 0; i < codesList.size(); i++) { - trim2(codesList[i]); - } return codesList; } @@ -297,469 +351,671 @@ void CWCheatEngine::InvalidateICache(u32 addr, int size) { currentMIPS->InvalidateICache(addr & ~3, size); } -void CWCheatEngine::Run() { - exit2 = false; - while (!exit2) { - currentCode = 0; +enum class CheatOp { + Invalid, + Noop, - while (true) { - std::vector code = GetNextCode(); - if (code.size() < 2) { - Exit(); - break; + Write, + Add, + Subtract, + Or, + And, + Xor, + + MultiWrite, + + CopyBytesFrom, + Delay, + + Assert, + + IfEqual, + IfNotEqual, + IfLess, + IfGreater, + + IfAddrEqual, + IfAddrNotEqual, + IfAddrLess, + IfAddrGreater, + + IfPressed, + IfNotPressed, + + CwCheatPointerCommands, +}; + +struct CheatOperation { + CheatOp op; + uint32_t addr; + int sz; + uint32_t val; + + union { + struct { + uint32_t count; + uint32_t step; + uint32_t add; + } multiWrite; + struct { + uint32_t destAddr; + } copyBytesFrom; + struct { + uint32_t skip; + } ifTypes; + struct { + uint32_t skip; + uint32_t compareAddr; + } ifAddrTypes; + struct { + int offset; + int baseOffset; + int count; + int type; + } pointerCommands; + }; +}; + +CheatOperation CWCheatEngine::InterpretNextCwCheat(const CheatCode &cheat, size_t &i) { + const CheatLine &line1 = cheat.lines[i++]; + const uint32_t &arg = line1.part2; + + // Filled as needed. + u32 addr; + + int type = line1.part1 >> 28; + switch (type) { + case 0x0: // Write 8-bit data (up to 4 bytes.) + addr = GetAddress(line1.part1 & 0x0FFFFFFF); + if (arg & 0xFFFF0000) + return { CheatOp::Write, addr, 4, arg }; + else if (arg & 0x0000FF00) + return { CheatOp::Write, addr, 2, arg }; + else + return { CheatOp::Write, addr, 1, arg }; + + case 0x1: // Write 16-bit data. + addr = GetAddress(line1.part1 & 0x0FFFFFFF); + return { CheatOp::Write, addr, 2, arg }; + + case 0x2: // Write 32-bit data. + addr = GetAddress(line1.part1 & 0x0FFFFFFF); + return { CheatOp::Write, addr, 4, arg }; + + case 0x3: // Increment/decrement data. + addr = GetAddress(arg & 0x0FFFFFFF); + switch ((line1.part1 >> 20) & 0xF) { + case 1: + return { CheatOp::Add, addr, 1, line1.part1 & 0xFF }; + case 2: + return { CheatOp::Subtract, addr, 1, line1.part1 & 0xFF }; + case 3: + return { CheatOp::Add, addr, 2, line1.part1 & 0xFFFF }; + case 4: + return { CheatOp::Subtract, addr, 2, line1.part1 & 0xFFFF }; + case 5: + if (i < cheat.lines.size()) + return { CheatOp::Add, addr, 4, cheat.lines[i++].part1 }; + return { CheatOp::Invalid }; + case 6: + if (i < cheat.lines.size()) + return { CheatOp::Subtract, addr, 4, cheat.lines[i++].part1 }; + return { CheatOp::Invalid }; + + default: + return { CheatOp::Invalid }; + } + break; + + case 0x4: // 32-bit multi-write patch data. + addr = GetAddress(line1.part1 & 0x0FFFFFFF); + if (i < cheat.lines.size()) { + const CheatLine &line2 = cheat.lines[i++]; + + CheatOperation op = { CheatOp::MultiWrite, addr, 4, line2.part1 }; + op.multiWrite.count = arg >> 16; + op.multiWrite.step = (arg & 0xFFFF) * 4; + op.multiWrite.add = line2.part2; + return op; + } + return { CheatOp::Invalid }; + + case 0x5: // Memcpy command. + addr = GetAddress(line1.part1 & 0x0FFFFFFF); + if (i < cheat.lines.size()) { + const CheatLine &line2 = cheat.lines[i++]; + + CheatOperation op = { CheatOp::CopyBytesFrom, addr, 0, arg }; + op.copyBytesFrom.destAddr = GetAddress(line2.part1 & 0x0FFFFFFF); + return op; + } + return { CheatOp::Invalid }; + + case 0x6: // Pointer commands. + addr = GetAddress(line1.part1 & 0x0FFFFFFF); + if (i < cheat.lines.size()) { + const CheatLine &line2 = cheat.lines[i++]; + int count = (line2.part1 & 0xFFFF) - 1; + + // Validate lines to process - make sure we stay inside cheat.lines. + if (i + count > cheat.lines.size()) + return { CheatOp::Invalid }; + + CheatOperation op = { CheatOp::CwCheatPointerCommands, addr, 0, arg }; + op.pointerCommands.offset = (int)line2.part2; + // TODO: Verify sign handling. Is this really supposed to sign extend? + op.pointerCommands.baseOffset = ((int)line2.part1 >> 20) * 4; + op.pointerCommands.count = count; + op.pointerCommands.type = (line2.part1 >> 16) & 0xF; + return op; + } + return { CheatOp::Invalid }; + + case 0x7: // Boolean data operations. + addr = GetAddress(line1.part1 & 0x0FFFFFFF); + switch (arg >> 16) { + case 0x0000: // 8-bit OR. + return { CheatOp::Or, addr, 1, arg & 0xFF }; + case 0x0001: // 16-bit OR. + return { CheatOp::Or, addr, 2, arg & 0xFFFF }; + case 0x0002: // 8-bit AND. + return { CheatOp::And, addr, 1, arg & 0xFF }; + case 0x0003: // 16-bit AND. + return { CheatOp::And, addr, 2, arg & 0xFFFF }; + case 0x0004: // 8-bit XOR. + return { CheatOp::Xor, addr, 1, arg & 0xFF }; + case 0x0005: // 16-bit XOR. + return { CheatOp::Xor, addr, 2, arg & 0xFFFF }; + } + return { CheatOp::Invalid }; + + case 0x8: // 8-bit or 16-bit multi-write patch data. + addr = GetAddress(line1.part1 & 0x0FFFFFFF); + if (i < cheat.lines.size()) { + const CheatLine &line2 = cheat.lines[i++]; + const bool is8Bit = (line2.part1 & 0xFFFF0000) == 0; + const uint32_t val = is8Bit ? (line2.part1 & 0xFF) : (line2.part1 & 0xFFFF); + + CheatOperation op = { CheatOp::MultiWrite, addr, is8Bit ? 1 : 2, val }; + op.multiWrite.count = arg >> 16; + op.multiWrite.step = (arg & 0xFFFF) * (is8Bit ? 1 : 2); + op.multiWrite.add = line2.part2; + return op; + } + return { CheatOp::Invalid }; + + case 0xB: // Delay command. + return { CheatOp::Delay, 0, 0, arg }; + + case 0xC: // 32-bit equal check / code stopper. + addr = GetAddress(line1.part1 & 0x0FFFFFFF); + return { CheatOp::Assert, addr, 4, arg }; + + case 0xD: // Line skip tests & joker codes. + switch (arg >> 28) { + case 0x0: // 16-bit next line skip test. + case 0x2: // 8-bit next line skip test. + addr = GetAddress(line1.part1 & 0x0FFFFFFF); + { + const bool is8Bit = (arg >> 28) == 0x2; + const uint32_t val = is8Bit ? (arg & 0xFF) : (arg & 0xFFFF); + + CheatOp opcode; + switch ((arg >> 20) & 0xF) { + case 0x0: + opcode = CheatOp::IfEqual; + break; + case 0x1: + opcode = CheatOp::IfNotEqual; + break; + case 0x2: + opcode = CheatOp::IfLess; + break; + case 0x3: + opcode = CheatOp::IfGreater; + break; + default: + return { CheatOp::Invalid }; + } + + CheatOperation op = { opcode, addr, is8Bit ? 1 : 2, val }; + op.ifTypes.skip = 1; + return op; } - int value; - unsigned int comm = code[0]; - u32 arg = code[1]; - int addr = GetAddress(comm & 0x0FFFFFFF); + case 0x1: // Joker code - button pressed. + case 0x3: // Inverse joker code - button not pressed. + { + bool pressed = (arg >> 28) == 0x1; + CheatOperation op = { pressed ? CheatOp::IfPressed : CheatOp::IfNotPressed, 0, 0, arg & 0x0FFFFFFF }; + op.ifTypes.skip = (line1.part1 & 0xFF) + 1; + return op; + } - switch (comm >> 28) { - case 0: // 8-bit write.But need more check - if (Memory::IsValidAddress(addr)) { - InvalidateICache(addr & ~3, 4); - if (arg < 0x00000100) // 8-bit - Memory::Write_U8((u8) arg, addr); - else if (arg < 0x00010000) // 16-bit - Memory::Write_U16((u16) arg, addr); - else // 32-bit - Memory::Write_U32((u32) arg, addr); - } - break; - case 0x1: // 16-bit write - if (Memory::IsValidAddress(addr)) { - InvalidateICache(addr & ~3, 4); - Memory::Write_U16((u16) arg, addr); - } - break; - case 0x2: // 32-bit write - if (Memory::IsValidAddress(addr)){ - InvalidateICache(addr & ~3, 4); - Memory::Write_U32((u32) arg, addr); - } - break; - case 0x3: // Increment/Decrement - { - addr = GetAddress(arg & 0x0FFFFFFF); - InvalidateICache(addr & ~3, 4); - value = 0; - int increment = 0; - // Read value from memory - switch ((comm >> 20) & 0xF) { - case 1: - case 2: // 8-bit - value = Memory::Read_U8(addr); - increment = comm & 0xFF; - break; - case 3: - case 4: // 16-bit - value = Memory::Read_U16(addr); - increment = comm & 0xFFFF; - break; - case 5: - case 6: // 32-bit - value = Memory::Read_U32(addr); - code = GetNextCode(); - if (code[0] != 0) { - increment = code[0]; - } - break; - } - // Increment/Decrement value - switch ((comm >> 20) & 0xF) { - case 1: - case 3: - case 5: // increment - value += increment; - break; - case 2: - case 4: - case 6: // Decrement - value -= increment; - break; - } - // Write value back to memory - switch ((comm >> 20) & 0xF) { - case 1: - case 2: // 8-bit - Memory::Write_U8((u8) value, addr); - break; - case 3: - case 4: // 16-bit - Memory::Write_U16((u16) value, addr); - break; - case 5: - case 6: // 32-bit - Memory::Write_U32((u32) value, addr); - break; - } - break; - } - case 0x4: // 32-bit patch code - code = GetNextCode(); - if (true) { - int data = code[0]; - int dataAdd = code[1]; + case 0x4: // Adress equal test. + case 0x5: // Address not equal test. + case 0x6: // Address less than test. + case 0x7: // Address greater than test. + addr = GetAddress(line1.part1 & 0x0FFFFFFF); + if (i < cheat.lines.size()) { + const CheatLine &line2 = cheat.lines[i++]; + const int sz = 1 << (line2.part2 & 0xF); - int count = (arg >> 16) & 0xFFFF; - int stepAddr = (arg & 0xFFFF) * 4; + CheatOp opcode; + switch (arg >> 28) { + case 0x4: + opcode = CheatOp::IfAddrEqual; + break; + case 0x5: + opcode = CheatOp::IfAddrNotEqual; + break; + case 0x6: + opcode = CheatOp::IfAddrLess; + break; + case 0x7: + opcode = CheatOp::IfAddrGreater; + break; + default: + return { CheatOp::Invalid }; + } - InvalidateICache(addr, count * stepAddr); - for (int a = 0; a < count; a++) { - if (Memory::IsValidAddress(addr)) { - Memory::Write_U32((u32) data, addr); - } - addr += stepAddr; - data += dataAdd; - } - } - break; - case 0x5: // Memcpy command - code = GetNextCode(); - if (true) { - int destAddr = GetAddress(code[0]); - int len = arg; - InvalidateICache(destAddr, len); - if (Memory::IsValidAddress(addr) && Memory::IsValidAddress(destAddr)) { - Memory::MemcpyUnchecked(destAddr, addr, len); - } - } - break; - case 0x6: // Pointer commands - code = GetNextCode(); - if (code.size() >= 2) { - int arg2 = code[0]; - int offset = code[1]; - int baseOffset = (arg2 >> 20) * 4; - InvalidateICache(addr + baseOffset, 4); - int base = Memory::Read_U32(addr + baseOffset); - int count = arg2 & 0xFFFF; - int type = (arg2 >> 16) & 0xF; - for (int i = 1; i < count; i ++ ) { - if (i < count) { - code = GetNextCode(); - if (code.size() < 2) { - // User provided incomplete cheat. Should warn but would be very spammy... - break; - } - int arg3 = code[0]; - int arg4 = code[1]; - int comm3 = arg3 >> 28; - switch (comm3) { - case 0x1: // type copy byte - { - int srcAddr = Memory::Read_U32(addr) + offset; - int dstAddr = Memory::Read_U32(addr + baseOffset) + (arg3 & 0x0FFFFFFF); - if (Memory::IsValidAddress(dstAddr) && Memory::IsValidAddress(srcAddr) && Memory::IsValidAddress(srcAddr + arg) && Memory::IsValidAddress(dstAddr + arg)) { - Memory::MemcpyUnchecked(dstAddr, srcAddr, arg); - } - type = -1; //Done - break; } - case 0x2: - case 0x3: // type pointer walk - { - int walkOffset = arg3 & 0x0FFFFFFF; - if (comm3 == 0x3) { - walkOffset = -walkOffset; - } - base = Memory::Read_U32(base + walkOffset); - int comm4 = arg4 >> 28; - switch (comm4) { - case 0x2: - case 0x3: // type pointer walk - walkOffset = arg4 & 0x0FFFFFFF; - if (comm4 == 0x3) { - walkOffset = -walkOffset; - } - base = Memory::Read_U32(base + walkOffset); - break; - } - break; } - case 0x9: // type multi address write - base += arg3 & 0x0FFFFFFF; - arg += arg4; - break; - default: - break; + CheatOperation op = { opcode, addr, sz, 0 }; + op.ifAddrTypes.skip = line2.part1; + op.ifAddrTypes.compareAddr = GetAddress(arg & 0x0FFFFFFF); + return op; + } + return { CheatOp::Invalid }; - } - } - } + default: + return { CheatOp::Invalid }; + } - switch (type) { - case 0: // 8 bit write - Memory::Write_U8((u8) arg, base + offset); - break; - case 1: // 16-bit write - Memory::Write_U16((u16) arg, base + offset); - break; - case 2: // 32-bit write - Memory::Write_U32((u32) arg, base + offset); - break; - case 3: // 8 bit inverse write - Memory::Write_U8((u8) arg, base - offset); - break; - case 4: // 16-bit inverse write - Memory::Write_U16((u16) arg, base - offset); - break; - case 5: // 32-bit inverse write - Memory::Write_U32((u32) arg, base - offset); - break; - case -1: // Operation already performed, nothing to do - break; - } - } - break; - case 0x7: // Boolean commands. - switch (arg >> 16) { - case 0x0000: // 8-bit OR. - if (Memory::IsValidAddress(addr)) { - InvalidateICache(addr & ~3, 4); - int val1 = (int) (arg & 0xFF); - int val2 = (int) Memory::Read_U8(addr); - Memory::Write_U8((u8) (val1 | val2), addr); - } - break; - case 0x0002: // 8-bit AND. - if (Memory::IsValidAddress(addr)) { - InvalidateICache(addr & ~3, 4); - int val1 = (int) (arg & 0xFF); - int val2 = (int) Memory::Read_U8(addr); - Memory::Write_U8((u8) (val1 & val2), addr); - } - break; - case 0x0004: // 8-bit XOR. - if (Memory::IsValidAddress(addr)) { - InvalidateICache(addr & ~3, 4); - int val1 = (int) (arg & 0xFF); - int val2 = (int) Memory::Read_U8(addr); - Memory::Write_U8((u8) (val1 ^ val2), addr); - } - break; - case 0x0001: // 16-bit OR. - if (Memory::IsValidAddress(addr)) { - InvalidateICache(addr & ~3, 4); - short val1 = (short) (arg & 0xFFFF); - short val2 = (short) Memory::Read_U16(addr); - Memory::Write_U16((u16) (val1 | val2), addr); - } - break; - case 0x0003: // 16-bit AND. - if (Memory::IsValidAddress(addr)) { - InvalidateICache(addr & ~3, 4); - short val1 = (short) (arg & 0xFFFF); - short val2 = (short) Memory::Read_U16(addr); - Memory::Write_U16((u16) (val1 & val2), addr); - } - break; - case 0x0005: // 16-bit OR. - if (Memory::IsValidAddress(addr)) { - InvalidateICache(addr & ~3, 4); - short val1 = (short) (arg & 0xFFFF); - short val2 = (short) Memory::Read_U16(addr); - Memory::Write_U16((u16) (val1 ^ val2), addr); - } - break; - } - break; - case 0x8: // 8-bit and 16-bit patch code - code = GetNextCode(); - if (code.size() >= 2) { - int data = code[0]; - int dataAdd = code[1]; + case 0xE: // Multiple line skip tests. + addr = GetAddress(arg & 0x0FFFFFFF); + { + const bool is8Bit = (line1.part1 >> 24) == 0xE1; + const uint32_t val = is8Bit ? (line1.part1 & 0xFF) : (line1.part1 & 0xFFFF); - bool is8Bit = (data >> 16) == 0x0000; - int count = (arg >> 16) & 0xFFFF; - int stepAddr = (arg & 0xFFFF) * (is8Bit ? 1 : 2); - InvalidateICache(addr, count * stepAddr); - for (int a = 0; a < count; a++) { - if (Memory::IsValidAddress(addr)) { - if (is8Bit) { - Memory::Write_U8((u8) (data & 0xFF), addr); - } - else { - Memory::Write_U16((u16) (data & 0xFFFF), addr); - } - } - addr += stepAddr; - data += dataAdd; - } - } + CheatOp opcode; + switch (arg >> 28) { + case 0x0: + opcode = CheatOp::IfEqual; break; - case 0xB: // Time command (not sure what to do?) + case 0x1: + opcode = CheatOp::IfNotEqual; break; - case 0xC: // Code stopper - if (Memory::IsValidAddress(addr)) { - InvalidateICache(addr, 4); - value = Memory::Read_U32(addr); - if ((u32)value != arg) { - SkipAllCodes(); - } - } + case 0x2: + opcode = CheatOp::IfLess; + break; + case 0x3: + opcode = CheatOp::IfGreater; break; - case 0xD: // Test commands & Jocker codes - if ((arg >> 28) == 0x0 || (arg >> 28) == 0x2) { // 8Bit & 16Bit ignore next line cheat code - bool is8Bit = (arg >> 28) == 0x2; - addr = GetAddress(comm & 0x0FFFFFFF); - if (Memory::IsValidAddress(addr)) { - InvalidateICache(addr, 4); - int memoryValue = is8Bit ? Memory::Read_U8(addr) : Memory::Read_U16(addr); - int testValue = arg & (is8Bit ? 0xFF : 0xFFFF); - bool executeNextLines = false; - switch ((arg >> 20) & 0xF) { - case 0x0: // Equal - executeNextLines = memoryValue == testValue; - break; - case 0x1: // Not Equal - executeNextLines = memoryValue != testValue; - break; - case 0x2: // Less Than - executeNextLines = memoryValue < testValue; - break; - case 0x3: // Greater Than - executeNextLines = memoryValue > testValue; - break; - default: - break; - } - if (!executeNextLines) - SkipCodes(1); - } - break; - } - else if ((arg >> 28) == 0x1 || (arg >> 28) == 0x3) { // Buttons dependent ignore cheat code - // Button Code - // SELECT 0x00000001 - // START 0x00000008 - // DPAD UP 0x00000010 - // DPAD RIGHT 0x00000020 - // DPAD DOWN 0x00000040 - // DPAD LEFT 0x00000080 - // L TRIGGER 0x00000100 - // R TRIGGER 0x00000200 - // TRIANGLE 0x00001000 - // CIRCLE 0x00002000 - // CROSS 0x00004000 - // SQUARE 0x00008000 - // HOME 0x00010000 - // HOLD 0x00020000 - // WLAN 0x00040000 - // REMOTE HOLD 0x00080000 - // VOLUME UP 0x00100000 - // VOLUME DOWN 0x00200000 - // SCREEN 0x00400000 - // NOTE 0x00800000 - u32 buttonStatus = __CtrlPeekButtons(); - int skip = (comm & 0xFF) + 1; - u32 mask = arg & 0x0FFFFFFF; - if ((arg >> 28) == 0x1) - // Old, too specific check: if (buttonStatus == (arg & 0x0FFFFFFF)) // cheat code likes: 0xD00000nn 0x1bbbbbbb; - if ((buttonStatus & mask) == mask) // cheat code likes: 0xD00000nn 0x1bbbbbbb; - break; - else - SkipCodes(skip); - else // (arg >> 28) == 2? - // Old, too specific check: if (buttonStatus != (arg & 0x0FFFFFFF)) // cheat code likes: 0xD00000nn 0x3bbbbbbb; - if ((buttonStatus & mask) == mask) // cheat code likes: 0xD00000nn 0x3bbbbbbb; - SkipCodes(skip); - else - break; - break; - } - else if ((arg >> 28) == 0x4 || (arg >> 28) == 0x5 || (arg >> 28) == 0x6 || (arg >> 28) == 0x7) { - int addr1 = GetAddress(comm & 0x0FFFFFFF); - int addr2 = GetAddress(arg & 0x0FFFFFFF); - code = GetNextCode(); - if (code.size() >= 2) - if (Memory::IsValidAddress(addr1) && Memory::IsValidAddress(addr2)) { - int comm2 = code[0]; - int arg2 = code[1]; - int skip = (comm2 & 0xFFFFFFFF); - int memoryValue1 = 0; - int memoryValue2 = 0; - switch (arg2 & 0xF) { - case 0x0: // 8Bit - memoryValue1 = Memory::Read_U8(addr1); - memoryValue2 = Memory::Read_U8(addr2); - break; - case 0x1: // 16Bit - memoryValue1 = Memory::Read_U16(addr1); - memoryValue2 = Memory::Read_U16(addr2); - break; - case 0x2: // 32Bit - memoryValue1 = Memory::Read_U32(addr1); - memoryValue2 = Memory::Read_U32(addr2); - break; - default: - break; - } - switch (arg >> 28) { - case 0x4: // Equal - if (memoryValue1 != memoryValue2) - SkipCodes(skip); - break; - case 0x5: // Not Equal - if (memoryValue1 == memoryValue2) - SkipCodes(skip); - break; - case 0x6: // Less Than - if (memoryValue1 >= memoryValue2) - SkipCodes(skip); - break; - case 0x7: // Greater Than - if (memoryValue1 <= memoryValue2) - SkipCodes(skip); - break; - default: - break; - } - } - } - else - break; - case 0xE: // Test commands, multiple skip - { - bool is8Bit = (comm >> 24) == 0xE1; - addr = GetAddress(arg & 0x0FFFFFFF); - if (Memory::IsValidAddress(addr)) { - InvalidateICache(addr, 4); - int memoryValue = is8Bit ? Memory::Read_U8(addr) : Memory::Read_U16(addr); - int testValue = comm & (is8Bit ? 0xFF : 0xFFFF); - bool executeNextLines = false; - switch ( arg >> 28) { - case 0x0: // Equal - executeNextLines = memoryValue == testValue; - break; - case 0x1: // Not Equal - executeNextLines = memoryValue != testValue; - break; - case 0x2: // Less Than - executeNextLines = memoryValue < testValue; - break; - case 0x3: // Greater Than - executeNextLines = memoryValue > testValue; - break; - } - if (!executeNextLines) { - int skip = (comm >> 16) & (is8Bit ? 0xFF : 0xFFF); - SkipCodes(skip); - } - } - break; - } default: - { - break; + return { CheatOp::Invalid }; + } + + CheatOperation op = { opcode, addr, is8Bit ? 1 : 2, val }; + op.ifTypes.skip = (line1.part1 >> 16) & (is8Bit ? 0xFF : 0xFFF); + return op; + } + + default: + return { CheatOp::Invalid }; + } +} + +CheatOperation CWCheatEngine::InterpretNextTempAR(const CheatCode &cheat, size_t &i) { + // TODO + assert(false); + return { CheatOp::Invalid }; +} + +CheatOperation CWCheatEngine::InterpretNextOp(const CheatCode &cheat, size_t &i) { + if (cheat.fmt == CheatCodeFormat::CWCHEAT) + return InterpretNextCwCheat(cheat, i); + else if (cheat.fmt == CheatCodeFormat::TEMPAR) + return InterpretNextTempAR(cheat, i); + else + assert(false); + return { CheatOp::Invalid }; +} + +void CWCheatEngine::ApplyMemoryOperator(const CheatOperation &op, uint32_t(*oper)(uint32_t, uint32_t)) { + if (Memory::IsValidAddress(op.addr)) { + InvalidateICache(op.addr, 4); + if (op.sz == 1) + Memory::Write_U8((u8)oper(Memory::Read_U8(op.addr), op.val), op.addr); + else if (op.sz == 2) + Memory::Write_U16((u16)oper(Memory::Read_U16(op.addr), op.val),op. addr); + else if (op.sz == 4) + Memory::Write_U32((u32)oper(Memory::Read_U32(op.addr), op.val), op.addr); + } +} + +bool CWCheatEngine::TestIf(const CheatOperation &op, bool(*oper)(int, int)) { + if (Memory::IsValidAddress(op.addr)) { + InvalidateICache(op.addr, 4); + + int memoryValue = 0; + if (op.sz == 1) + memoryValue = (int)Memory::Read_U8(op.addr); + else if (op.sz == 2) + memoryValue = (int)Memory::Read_U16(op.addr); + else if (op.sz == 4) + memoryValue = (int)Memory::Read_U32(op.addr); + + return oper(memoryValue, (int)op.val); + } + return false; +} + +bool CWCheatEngine::TestIfAddr(const CheatOperation &op, bool(*oper)(int, int)) { + if (Memory::IsValidAddress(op.addr)) { + InvalidateICache(op.addr, 4); + + int memoryValue1 = 0; + int memoryValue2 = 0; + if (op.sz == 1) { + memoryValue1 = (int)Memory::Read_U8(op.addr); + memoryValue2 = (int)Memory::Read_U8(op.ifAddrTypes.compareAddr); + } else if (op.sz == 2) { + memoryValue1 = (int)Memory::Read_U16(op.addr); + memoryValue2 = (int)Memory::Read_U16(op.ifAddrTypes.compareAddr); + } else if (op.sz == 4) { + memoryValue1 = (int)Memory::Read_U32(op.addr); + memoryValue2 = (int)Memory::Read_U32(op.ifAddrTypes.compareAddr); + } + + return oper(memoryValue1, memoryValue2); + } + return false; +} + +void CWCheatEngine::ExecuteOp(const CheatOperation &op, const CheatCode &cheat, size_t &i) { + switch (op.op) { + case CheatOp::Invalid: + i = cheat.lines.size(); + break; + + case CheatOp::Noop: + break; + + case CheatOp::Write: + if (Memory::IsValidAddress(op.addr)) { + InvalidateICache(op.addr, 4); + if (op.sz == 1) + Memory::Write_U8((u8)op.val, op.addr); + else if (op.sz == 2) + Memory::Write_U16((u16)op.val, op.addr); + else if (op.sz == 4) + Memory::Write_U32((u32)op.val, op.addr); + } + break; + + case CheatOp::Add: + ApplyMemoryOperator(op, [](uint32_t a, uint32_t b) { + return a + b; + }); + break; + + case CheatOp::Subtract: + ApplyMemoryOperator(op, [](uint32_t a, uint32_t b) { + return a - b; + }); + break; + + case CheatOp::Or: + ApplyMemoryOperator(op, [](uint32_t a, uint32_t b) { + return a | b; + }); + break; + + case CheatOp::And: + ApplyMemoryOperator(op, [](uint32_t a, uint32_t b) { + return a & b; + }); + break; + + case CheatOp::Xor: + ApplyMemoryOperator(op, [](uint32_t a, uint32_t b) { + return a ^ b; + }); + break; + + case CheatOp::MultiWrite: + if (Memory::IsValidAddress(op.addr)) { + InvalidateICache(op.addr, op.multiWrite.count * op.multiWrite.step + op.sz); + + uint32_t data = op.val; + uint32_t addr = op.addr; + for (uint32_t a = 0; a < op.multiWrite.count; a++) { + if (Memory::IsValidAddress(addr)) { + if (op.sz == 1) + Memory::Write_U8((u8)data, addr); + else if (op.sz == 2) + Memory::Write_U16((u16)data, addr); + else if (op.sz == 4) + Memory::Write_U32((u32)data, addr); } + addr += op.multiWrite.step; + data += op.multiWrite.add; } } + break; + + case CheatOp::CopyBytesFrom: + if (Memory::IsValidRange(op.addr, op.val) && Memory::IsValidRange(op.copyBytesFrom.destAddr, op.val)) { + InvalidateICache(op.addr, op.val); + InvalidateICache(op.copyBytesFrom.destAddr, op.val); + + Memory::MemcpyUnchecked(op.copyBytesFrom.destAddr, op.addr, op.val); + } + break; + + case CheatOp::Delay: + // TODO: Not supported. + break; + + case CheatOp::Assert: + if (Memory::IsValidAddress(op.addr)) { + InvalidateICache(op.addr, 4); + if (Memory::Read_U32(op.addr) != op.val) { + i = cheat.lines.size(); + } + } + break; + + case CheatOp::IfEqual: + if (!TestIf(op, [](int a, int b) { return a == b; })) { + i += (size_t)op.ifTypes.skip; + } + break; + + case CheatOp::IfNotEqual: + if (!TestIf(op, [](int a, int b) { return a != b; })) { + i += (size_t)op.ifTypes.skip; + } + break; + + case CheatOp::IfLess: + if (!TestIf(op, [](int a, int b) { return a < b; })) { + i += (size_t)op.ifTypes.skip; + } + break; + + case CheatOp::IfGreater: + if (!TestIf(op, [](int a, int b) { return a > b; })) { + i += (size_t)op.ifTypes.skip; + } + break; + + case CheatOp::IfAddrEqual: + if (!TestIf(op, [](int a, int b) { return a == b; })) { + i += (size_t)op.ifAddrTypes.skip; + } + break; + + case CheatOp::IfAddrNotEqual: + if (!TestIf(op, [](int a, int b) { return a != b; })) { + i += (size_t)op.ifAddrTypes.skip; + } + break; + + case CheatOp::IfAddrLess: + if (!TestIf(op, [](int a, int b) { return a < b; })) { + i += (size_t)op.ifAddrTypes.skip; + } + break; + + case CheatOp::IfAddrGreater: + if (!TestIf(op, [](int a, int b) { return a > b; })) { + i += (size_t)op.ifAddrTypes.skip; + } + break; + + case CheatOp::IfPressed: + // Button Code + // SELECT 0x00000001 + // START 0x00000008 + // DPAD UP 0x00000010 + // DPAD RIGHT 0x00000020 + // DPAD DOWN 0x00000040 + // DPAD LEFT 0x00000080 + // L TRIGGER 0x00000100 + // R TRIGGER 0x00000200 + // TRIANGLE 0x00001000 + // CIRCLE 0x00002000 + // CROSS 0x00004000 + // SQUARE 0x00008000 + // HOME 0x00010000 + // HOLD 0x00020000 + // WLAN 0x00040000 + // REMOTE HOLD 0x00080000 + // VOLUME UP 0x00100000 + // VOLUME DOWN 0x00200000 + // SCREEN 0x00400000 + // NOTE 0x00800000 + if ((__CtrlPeekButtons() & op.val) != op.val) { + i += (size_t)op.ifTypes.skip; + } + break; + + case CheatOp::IfNotPressed: + if ((__CtrlPeekButtons() & op.val) == op.val) { + i += (size_t)op.ifTypes.skip; + } + break; + + case CheatOp::CwCheatPointerCommands: + { + InvalidateICache(op.addr + op.pointerCommands.baseOffset, 4); + u32 base = Memory::Read_U32(op.addr + op.pointerCommands.baseOffset); + u32 val = op.val; + int type = op.pointerCommands.type; + for (int a = 0; a < op.pointerCommands.count; ++a) { + const CheatLine &line = cheat.lines[i++]; + switch (line.part1 >> 28) { + case 0x1: // type copy byte + { + InvalidateICache(op.addr, 4); + u32 srcAddr = Memory::Read_U32(op.addr) + op.pointerCommands.offset; + u32 dstAddr = Memory::Read_U32(op.addr + op.pointerCommands.baseOffset) + (line.part1 & 0x0FFFFFFF); + if (Memory::IsValidRange(dstAddr, val) && Memory::IsValidRange(srcAddr, val)) { + InvalidateICache(dstAddr, val); + InvalidateICache(srcAddr, val); + Memory::MemcpyUnchecked(dstAddr, srcAddr, val); + } + // Don't perform any further action. + type = -1; + } + break; + + case 0x2: + case 0x3: // type pointer walk + { + int walkOffset = (int)line.part1 & 0x0FFFFFFF; + if ((line.part1 >> 28) == 0x3) { + walkOffset = -walkOffset; + } + InvalidateICache(base + walkOffset, 4); + base = Memory::Read_U32(base + walkOffset); + switch (line.part2 >> 28) { + case 0x2: + case 0x3: // type pointer walk + walkOffset = line.part2 & 0x0FFFFFFF; + if ((line.part2 >> 28) == 0x3) { + walkOffset = -walkOffset; + } + InvalidateICache(base + walkOffset, 4); + base = Memory::Read_U32(base + walkOffset); + break; + + default: + // Unexpected value in cheat line? + break; + } + } + break; + + case 0x9: // type multi address write + base += line.part1 & 0x0FFFFFFF; + val += line.part2; + break; + + default: + // Unexpected value in cheat line? + break; + } + } + + switch (type) { + case 0: // 8 bit write + InvalidateICache(base + op.pointerCommands.offset, 4); + Memory::Write_U8((u8)val, base + op.pointerCommands.offset); + break; + case 1: // 16-bit write + InvalidateICache(base + op.pointerCommands.offset, 4); + Memory::Write_U16((u16)val, base + op.pointerCommands.offset); + break; + case 2: // 32-bit write + InvalidateICache(base + op.pointerCommands.offset, 4); + Memory::Write_U32((u32)val, base + op.pointerCommands.offset); + break; + case 3: // 8 bit inverse write + InvalidateICache(base - op.pointerCommands.offset, 4); + Memory::Write_U8((u8)val, base - op.pointerCommands.offset); + break; + case 4: // 16-bit inverse write + InvalidateICache(base - op.pointerCommands.offset, 4); + Memory::Write_U16((u16)val, base - op.pointerCommands.offset); + break; + case 5: // 32-bit inverse write + InvalidateICache(base - op.pointerCommands.offset, 4); + Memory::Write_U32((u32)val, base - op.pointerCommands.offset); + break; + case -1: // Operation already performed, nothing to do + break; + } + } + break; + + default: + assert(false); + } +} + +void CWCheatEngine::Run() { + for (CheatCode cheat : cheats_) { + // InterpretNextOp and ExecuteOp move i. + for (size_t i = 0; i < cheat.lines.size(); ) { + CheatOperation op = InterpretNextOp(cheat, i); + ExecuteOp(op, cheat, i); + } } - // exiting... - Exit(); } bool CWCheatEngine::HasCheats() { - return !parts.empty(); + return !cheats_.empty(); } bool CheatsInEffect() { diff --git a/Core/CwCheat.h b/Core/CwCheat.h index be8b27be9..cef000228 100644 --- a/Core/CwCheat.h +++ b/Core/CwCheat.h @@ -18,25 +18,45 @@ void __CheatDoState(PointerWrap &p); // Return whether cheats are enabled and in effect. bool CheatsInEffect(); +struct CheatLine { + uint32_t part1; + uint32_t part2; +}; + +enum class CheatCodeFormat { + UNDEFINED, + CWCHEAT, + TEMPAR, +}; + +struct CheatCode { + CheatCodeFormat fmt; + std::vector lines; +}; + +struct CheatOperation; + class CWCheatEngine { public: CWCheatEngine(); std::vector GetCodesList(); - void CreateCodeList(); + void ParseCheats(); void CreateCheatFile(); - void Exit(); void Run(); - std::vector GetNextCode(); bool HasCheats(); private: void InvalidateICache(u32 addr, int size); - void SkipCodes(int count); - void SkipAllCodes(); - bool exit2, cheatEnabled; - int GetAddress(int value); - std::vector codeNameList; + u32 GetAddress(u32 value); - std::vector codes, initialCodesList, parts; - size_t currentCode; + CheatOperation InterpretNextOp(const CheatCode &cheat, size_t &i); + CheatOperation InterpretNextCwCheat(const CheatCode &cheat, size_t &i); + CheatOperation InterpretNextTempAR(const CheatCode &cheat, size_t &i); + + void ExecuteOp(const CheatOperation &op, const CheatCode &cheat, size_t &i); + void ApplyMemoryOperator(const CheatOperation &op, uint32_t(*oper)(uint32_t, uint32_t)); + bool TestIf(const CheatOperation &op, bool(*oper)(int a, int b)); + bool TestIfAddr(const CheatOperation &op, bool(*oper)(int a, int b)); + + std::vector cheats_; }; diff --git a/UI/CwCheatScreen.cpp b/UI/CwCheatScreen.cpp index a86c45d38..0fa131e4a 100644 --- a/UI/CwCheatScreen.cpp +++ b/UI/CwCheatScreen.cpp @@ -155,6 +155,7 @@ UI::EventReturn CwCheatScreen::OnEnableAll(UI::EventParams ¶ms) { } fs.close(); + g_Config.bReloadCheats = true; return UI::EVENT_DONE; }