diff --git a/Core/CwCheat.cpp b/Core/CwCheat.cpp index 886b0acbb1..a22eacdc94 100644 --- a/Core/CwCheat.cpp +++ b/Core/CwCheat.cpp @@ -61,8 +61,13 @@ public: return cheats_; } + std::vector GetFileInfo() const { + return cheatInfo_; + } + protected: void Flush(); + void FlushCheatInfo(); void AddError(const std::string &msg); void ParseLine(const std::string &line); void ParseDataLine(const std::string &line, CheatCodeFormat format); @@ -74,9 +79,11 @@ protected: int line_ = 0; int games_ = 0; std::vector errors_; + std::vector cheatInfo_; std::vector cheats_; std::vector pendingLines_; CheatCodeFormat codeFormat_ = CheatCodeFormat::UNDEFINED; + CheatFileInfo lastCheatInfo_; bool gameEnabled_ = true; bool gameRiskyEnabled_ = false; bool cheatEnabled_ = false; @@ -88,9 +95,7 @@ bool CheatFileParser::Parse() { getline(file_, line, '\n'); line = TrimString(line); - // Minimum length is set to 5 just to match GetCodesList() function - // which discards anything shorter when called anyway. - // It's decided from shortest possible _ lines name of the game "_G N+" + // Minimum length 5 is shortest possible _ lines name of the game "_G N+" // and a minimum of 1 displayable character in cheat name string "_C0 1" // which both equal to 5 characters. if (line.length() >= 5 && line[0] == '_') { @@ -111,12 +116,20 @@ bool CheatFileParser::Parse() { void CheatFileParser::Flush() { if (!pendingLines_.empty()) { + FlushCheatInfo(); cheats_.push_back({ codeFormat_, pendingLines_ }); pendingLines_.clear(); } codeFormat_ = CheatCodeFormat::UNDEFINED; } +void CheatFileParser::FlushCheatInfo() { + if (lastCheatInfo_.lineNum != 0) { + cheatInfo_.push_back(lastCheatInfo_); + lastCheatInfo_ = { 0 }; + } +} + void CheatFileParser::AddError(const std::string &err) { errors_.push_back(StringFromFormat("Error on line %d: %s", line_, err.c_str())); } @@ -132,6 +145,7 @@ void CheatFileParser::ParseLine(const std::string &line) { if (gameRiskyEnabled_) { // We found the right one, so let's not use this risky stuff. cheats_.clear(); + cheatInfo_.clear(); gameRiskyEnabled_ = false; } gameEnabled_ = true; @@ -144,6 +158,7 @@ void CheatFileParser::ParseLine(const std::string &line) { if (gameRiskyEnabled_) { // There are multiple games here, kill the risky stuff. cheats_.clear(); + cheatInfo_.clear(); gameRiskyEnabled_ = false; } gameEnabled_ = false; @@ -155,18 +170,20 @@ void CheatFileParser::ParseLine(const std::string &line) { return; case 'C': + Flush(); + // Cheat name and activation status. if (line.length() >= 3 && line[2] >= '1' && line[2] <= '9') { + lastCheatInfo_ = { line_, line.length() >= 5 ? line.substr(4) : "", true }; cheatEnabled_ = true; } else if (line.length() >= 3 && line[2] == '0') { + lastCheatInfo_ = { line_, line.length() >= 5 ? line.substr(4) : "", false }; cheatEnabled_ = false; } else { AddError("could not parse cheat name line"); cheatEnabled_ = false; return; } - - Flush(); return; case 'L': @@ -190,11 +207,16 @@ void CheatFileParser::ParseDataLine(const std::string &line, CheatCodeFormat for codeFormat_ = format; } else if (codeFormat_ != format) { AddError("mixed code format (cwcheat/tempar)"); + lastCheatInfo_ = { 0 }; pendingLines_.clear(); cheatEnabled_ = false; } - if (!cheatEnabled_ || !gameEnabled_) { + if (!gameEnabled_) { + return; + } + if (!cheatEnabled_) { + FlushCheatInfo(); return; } @@ -361,37 +383,11 @@ u32 CWCheatEngine::GetAddress(u32 value) { return address; } -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 - while (list && !list.eof()) { - std::string line; - getline(list, line, '\n'); +std::vector CWCheatEngine::FileInfo() { + CheatFileParser parser(activeCheatFile, gameTitle); - bool validCheatLine = false; - // This function is called by cheat menu(UI) which doesn't support empty names - // minimum 1 non space character is required starting from 5 position. - // It also goes through other "_" lines, but they all have to meet this requirement anyway - // so we don't have to specify any syntax checks here that are made by cheat engine. - if (line.length() >= 5 && line[0] == '_') { - for (size_t i = 4; i < line.length(); i++) { - if (line[i] != ' ') { - validCheatLine = true; - break; - } - } - } - // Any lines not passing this check are discarded when we save changes to the cheat ini file - if (validCheatLine || (line.length() >= 2 && line[0] == '/' && line[1] == '/') || (line.length() >= 1 && line[0] == '#')) { - codesList.push_back(TrimString(line)); - } - } - return codesList; + parser.Parse(); + return parser.GetFileInfo(); } void CWCheatEngine::InvalidateICache(u32 addr, int size) { diff --git a/Core/CwCheat.h b/Core/CwCheat.h index 882093db3d..c6e90d07b8 100644 --- a/Core/CwCheat.h +++ b/Core/CwCheat.h @@ -1,6 +1,8 @@ // Rough and ready CwCheats implementation, disabled by default. // Will not enable by default until the TOOD:s have been addressed. +#pragma once + #include #include #include @@ -34,12 +36,18 @@ struct CheatCode { std::vector lines; }; +struct CheatFileInfo { + int lineNum; + std::string name; + bool enabled; +}; + struct CheatOperation; class CWCheatEngine { public: CWCheatEngine(); - std::vector GetCodesList(); + std::vector FileInfo(); void ParseCheats(); void CreateCheatFile(); void Run(); diff --git a/UI/CwCheatScreen.cpp b/UI/CwCheatScreen.cpp index 7926d18911..acce1599d0 100644 --- a/UI/CwCheatScreen.cpp +++ b/UI/CwCheatScreen.cpp @@ -15,9 +15,6 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. -#include - -#include "input/input_state.h" #include "ui/ui.h" #include "util/text/utf8.h" #include "i18n/i18n.h" @@ -28,28 +25,16 @@ #include "Core/CwCheat.h" #include "Core/MIPS/JitCommon/JitCommon.h" -#include "UI/OnScreenDisplay.h" - -#include "UI/MainScreen.h" -#include "UI/EmuScreen.h" #include "UI/GameInfoCache.h" -#include "UI/MiscScreens.h" #include "UI/CwCheatScreen.h" -static bool enableAll = false; -static std::vector cheatList; -static CWCheatEngine *cheatEngine2; -static std::deque bEnableCheat; -static std::string gamePath_; - - CwCheatScreen::CwCheatScreen(std::string gamePath) : UIDialogScreenWithBackground() { gamePath_ = gamePath; } -void CwCheatScreen::CreateCodeList() { - std::shared_ptr info = g_gameInfoCache->GetInfo(NULL, gamePath_, 0); +void CwCheatScreen::LoadCheatInfo() { + std::shared_ptr info = g_gameInfoCache->GetInfo(nullptr, gamePath_, 0); if (info && info->paramSFOLoaded) { gameTitle = info->paramSFO.GetValueString("DISC_ID"); } @@ -58,22 +43,9 @@ void CwCheatScreen::CreateCodeList() { gameTitle = g_paramSFO.GenerateFakeID(gamePath_); } - cheatEngine2 = new CWCheatEngine(); + CWCheatEngine *cheatEngine2 = new CWCheatEngine(); cheatEngine2->CreateCheatFile(); - cheatList = cheatEngine2->GetCodesList(); - - bEnableCheat.clear(); - formattedList_.clear(); - for (size_t i = 0; i < cheatList.size(); i++) { - if (cheatList[i][0] == '_' && cheatList[i][1] == 'C') { - formattedList_.push_back(cheatList[i].substr(4)); - if (cheatList[i][2] == '0') { - bEnableCheat.push_back(false); - } else { - bEnableCheat.push_back(true); - } - } - } + fileInfo_ = cheatEngine2->FileInfo(); delete cheatEngine2; } @@ -84,8 +56,7 @@ void CwCheatScreen::CreateViews() { root_ = new AnchorLayout(new LayoutParams(FILL_PARENT, FILL_PARENT)); - CreateCodeList(); - g_Config.bReloadCheats = true; + LoadCheatInfo(); Margins actionMenuMargins(50, -15, 15, 0); LinearLayout *leftColumn = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(400, FILL_PARENT)); @@ -106,8 +77,10 @@ void CwCheatScreen::CreateViews() { rightScroll_->Add(rightColumn); rightColumn->Add(new ItemHeader(cw->T("Cheats"))); - for (size_t i = 0; i < formattedList_.size(); i++) { - rightColumn->Add(new CheatCheckBox(&bEnableCheat[i], formattedList_[i]))->OnClick.Handle(this, &CwCheatScreen::OnCheckBox); + for (size_t i = 0; i < fileInfo_.size(); ++i) { + rightColumn->Add(new CheatCheckBox(&fileInfo_[i].enabled, fileInfo_[i].name))->OnClick.Add([=](UI::EventParams &) { + return OnCheckBox(i); + }); } LinearLayout *layout = new LinearLayout(ORIENT_HORIZONTAL, new LayoutParams(FILL_PARENT, FILL_PARENT)); @@ -119,18 +92,9 @@ void CwCheatScreen::CreateViews() { } void CwCheatScreen::onFinish(DialogResult result) { - std::fstream fs; if (result != DR_BACK) // This only works for BACK here. return; - File::OpenCPPFile(fs, activeCheatFile, std::ios::out); - for (int j = 0; j < (int)cheatList.size(); j++) { - fs << cheatList[j]; - if (j < (int)cheatList.size() - 1) { - fs << "\n"; - } - } - fs.close(); - g_Config.bReloadCheats = true; + if (MIPSComp::jit) { MIPSComp::jit->ClearCache(); } @@ -138,30 +102,20 @@ void CwCheatScreen::onFinish(DialogResult result) { } UI::EventReturn CwCheatScreen::OnEnableAll(UI::EventParams ¶ms) { - std::fstream fs; - enableAll = !enableAll; - File::OpenCPPFile(fs, activeCheatFile, std::ios::out); - for (int j = 0; j < (int)cheatList.size(); j++) { - if (cheatList[j][0] == '_' && cheatList[j][1] == 'C') { - if (cheatList[j][2] == '0' && enableAll) { - cheatList[j][2] = '1'; - } else if (cheatList[j][2] != '0' && !enableAll) { - cheatList[j][2] = '0'; - } - } - } - for (size_t y = 0; y < bEnableCheat.size(); y++) { - bEnableCheat[y] = enableAll; - } - for (int i = 0; i < (int)cheatList.size(); i++) { - fs << cheatList[i]; - if (i < (int)cheatList.size() - 1) { - fs << "\n"; - } - } - fs.close(); + enableAllFlag_ = !enableAllFlag_; + + // Flip all the switches. + for (auto &info : fileInfo_) { + info.enabled = enableAllFlag_; + } + + if (!RebuildCheatFile(INDEX_ALL)) { + // Probably the file was modified outside PPSSPP, refresh. + // TODO: Report error. + RecreateViews(); + return UI::EVENT_SKIPPED; + } - g_Config.bReloadCheats = true; return UI::EVENT_DONE; } @@ -216,9 +170,9 @@ UI::EventReturn CwCheatScreen::OnImportCheat(UI::EventParams ¶ms) { getline(fs, line); } if (line[0] == '_' && line[1] == 'C') { - //Test if cheat already exists in cheatList - for (size_t j = 0; j < formattedList_.size(); j++) { - if (line.substr(4) == formattedList_[j]) { + // Test if cheat already exists. + for (const auto &existing : fileInfo_) { + if (line.substr(4) == existing.name) { finished = false; goto loop; } @@ -265,61 +219,78 @@ UI::EventReturn CwCheatScreen::OnImportCheat(UI::EventParams ¶ms) { } } fs.close(); + g_Config.bReloadCheats = true; - //Need a better way to refresh the screen, rather than exiting and having to re-enter. - TriggerFinish(DR_OK); + RecreateViews(); return UI::EVENT_DONE; } -UI::EventReturn CwCheatScreen::OnCheckBox(UI::EventParams ¶ms) { - CheatCheckBox *checkbox = (CheatCheckBox *)params.v; - if (checkbox->Toggled()) { - processFileOn(checkbox->Text()); +UI::EventReturn CwCheatScreen::OnCheckBox(int index) { + if (!RebuildCheatFile(index)) { + // TODO: Report error. Let's reload the file, presumably it changed. + RecreateViews(); + return UI::EVENT_SKIPPED; + } + + return UI::EVENT_DONE; +} + +bool CwCheatScreen::RebuildCheatFile(int index) { + std::fstream fs; + if (!File::OpenCPPFile(fs, activeCheatFile, std::ios::in)) { + return false; + } + + // In case lines were edited while we weren't looking, reload them. + std::vector lines; + for (; fs && !fs.eof(); ) { + std::string line; + std::getline(fs, line, '\n'); + lines.push_back(line); + } + fs.close(); + + auto updateLine = [&](const CheatFileInfo &info) { + // Line numbers start with one, not zero. + size_t lineIndex = info.lineNum - 1; + if (lines.size() > lineIndex) { + auto &line = lines[lineIndex]; + // This is the one to change. Let's see if it matches - maybe the file changed. + bool isCheatDef = line.find("_C") != line.npos; + bool hasCheatName = !info.name.empty() && line.find(info.name) != line.npos; + if (!isCheatDef || !hasCheatName) { + return false; + } + + line = (info.enabled ? "_C1 " : "_C0 ") + info.name; + } + return true; + }; + + if (index == INDEX_ALL) { + for (const auto &info : fileInfo_) { + // Bail out if any don't match with no changes. + if (!updateLine(info)) { + return false; + } + } } else { - processFileOff(checkbox->Text()); - } - return UI::EVENT_DONE; -} - -void CwCheatScreen::processFileOn(std::string activatedCheat) { - std::fstream fs; - for (size_t i = 0; i < cheatList.size(); i++) { - if (cheatList[i].length() >= 4) { - if (cheatList[i].substr(4) == activatedCheat) { - cheatList[i] = "_C1 " + activatedCheat; - } + if (!updateLine(fileInfo_[index])) { + return false; } } - File::OpenCPPFile(fs, activeCheatFile, std::ios::out); - for (size_t j = 0; j < cheatList.size(); j++) { - fs << cheatList[j]; - if (j < cheatList.size() - 1) { - fs << "\n"; - } + if (!File::OpenCPPFile(fs, activeCheatFile, std::ios::out | std::ios::trunc)) { + return false; + } + + for (const auto &line : lines) { + fs << line << '\n'; } fs.close(); + + // Cheats will need to be reparsed now. + g_Config.bReloadCheats = true; + return true; } - -void CwCheatScreen::processFileOff(std::string deactivatedCheat) { - std::fstream fs; - for (size_t i = 0; i < cheatList.size(); i++) { - if (cheatList[i].length() >= 4) { - if (cheatList[i].substr(4) == deactivatedCheat) { - cheatList[i] = "_C0 " + deactivatedCheat; - } - } - } - - File::OpenCPPFile(fs, activeCheatFile, std::ios::out); - - for (size_t j = 0; j < cheatList.size(); j++) { - fs << cheatList[j]; - if (j < cheatList.size() - 1) { - fs << "\n"; - } - } - fs.close(); -} - diff --git a/UI/CwCheatScreen.h b/UI/CwCheatScreen.h index 32ba42d022..2e244b0d5b 100644 --- a/UI/CwCheatScreen.h +++ b/UI/CwCheatScreen.h @@ -22,6 +22,8 @@ #include "ui/ui_context.h" #include "UI/MiscScreens.h" +struct CheatFileInfo; + extern std::string activeCheatFile; extern std::string gameTitle; @@ -29,9 +31,7 @@ class CwCheatScreen : public UIDialogScreenWithBackground { public: CwCheatScreen(std::string gamePath); - void CreateCodeList(); - void processFileOn(std::string activatedCheat); - void processFileOff(std::string deactivatedCheat); + void LoadCheatInfo(); UI::EventReturn OnAddCheat(UI::EventParams ¶ms); UI::EventReturn OnImportCheat(UI::EventParams ¶ms); @@ -39,13 +39,20 @@ public: UI::EventReturn OnEnableAll(UI::EventParams ¶ms); void onFinish(DialogResult result) override; + protected: void CreateViews() override; private: - UI::EventReturn OnCheckBox(UI::EventParams ¶ms); - std::vector formattedList_; - UI::ScrollView *rightScroll_; + UI::EventReturn OnCheckBox(int index); + + enum { INDEX_ALL = -1 }; + bool RebuildCheatFile(int index); + + UI::ScrollView *rightScroll_ = nullptr; + std::vector fileInfo_; + std::string gamePath_; + bool enableAllFlag_ = false; }; class CheatCheckBox : public UI::CheckBox {