From cb6dfeddf098f80a2e7d96b6fad665327b2f62b7 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 11 Apr 2020 10:46:06 -0700 Subject: [PATCH 1/7] UI: Prevent changing memstick path in game. It's going to work in confusing ways if the game has files open, etc. --- UI/GameSettingsScreen.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 2ab1e6beb5..aa8fd250a6 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -261,8 +261,7 @@ void GameSettingsScreen::CreateViews() { return UI::EVENT_CONTINUE; }); softwareGPU->OnClick.Handle(this, &GameSettingsScreen::OnSoftwareRendering); - if (PSP_IsInited()) - softwareGPU->SetEnabled(false); + softwareGPU->SetEnabled(!PSP_IsInited()); } graphicsSettings->Add(new ItemHeader(gr->T("Frame Rate Control"))); @@ -828,6 +827,7 @@ void GameSettingsScreen::CreateViews() { memstickPath->OnClick.Handle(this, &GameSettingsScreen::OnChangeMemStickDir); #elif defined(_WIN32) && !PPSSPP_PLATFORM(UWP) SavePathInMyDocumentChoice = systemSettings->Add(new CheckBox(&installed_, sy->T("Save path in My Documents", "Save path in My Documents"))); + SavePathInMyDocumentChoice->SetEnabled(!PSP_IsInited()); SavePathInMyDocumentChoice->OnClick.Handle(this, &GameSettingsScreen::OnSavePathMydoc); SavePathInOtherChoice = systemSettings->Add(new CheckBox(&otherinstalled_, sy->T("Save path in installed.txt", "Save path in installed.txt"))); SavePathInOtherChoice->SetEnabled(false); @@ -843,7 +843,7 @@ void GameSettingsScreen::CreateViews() { if (!(File::Delete(PPSSPPpath + "installedTEMP.txt"))) SavePathInMyDocumentChoice->SetEnabled(false); else - SavePathInOtherChoice->SetEnabled(true); + SavePathInOtherChoice->SetEnabled(!PSP_IsInited()); } else SavePathInMyDocumentChoice->SetEnabled(false); } else { @@ -860,7 +860,7 @@ void GameSettingsScreen::CreateViews() { // Skip UTF-8 encoding bytes if there are any. There are 3 of them. if (tempString.substr(0, 3) == "\xEF\xBB\xBF") tempString = tempString.substr(3); - SavePathInOtherChoice->SetEnabled(true); + SavePathInOtherChoice->SetEnabled(!PSP_IsInited()); if (!(tempString == "")) { installed_ = false; otherinstalled_ = true; @@ -874,7 +874,7 @@ void GameSettingsScreen::CreateViews() { #endif #if defined(_M_X64) - systemSettings->Add(new CheckBox(&g_Config.bCacheFullIsoInRam, sy->T("Cache ISO in RAM", "Cache full ISO in RAM"))); + systemSettings->Add(new CheckBox(&g_Config.bCacheFullIsoInRam, sy->T("Cache ISO in RAM", "Cache full ISO in RAM")))->SetEnabled(!PSP_IsInited()); #endif systemSettings->Add(new ItemHeader(sy->T("Cheats", "Cheats (experimental, see forums)"))); From 4c061e9212bfdede71812021312995fb466503ca Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 11 Apr 2020 11:22:11 -0700 Subject: [PATCH 2/7] UI: Use standard back and checkbox in cheats UI. This makes cheat names wrap and grow smartly automatically, since checkboxes normally do that. --- UI/CwCheatScreen.cpp | 42 ++++++++++++++++++------------------------ UI/CwCheatScreen.h | 33 +++++++-------------------------- 2 files changed, 25 insertions(+), 50 deletions(-) diff --git a/UI/CwCheatScreen.cpp b/UI/CwCheatScreen.cpp index 2ab0a47a0a..7926d18911 100644 --- a/UI/CwCheatScreen.cpp +++ b/UI/CwCheatScreen.cpp @@ -81,14 +81,15 @@ void CwCheatScreen::CreateViews() { using namespace UI; auto cw = GetI18NCategory("CwCheats"); auto di = GetI18NCategory("Dialog"); + + root_ = new AnchorLayout(new LayoutParams(FILL_PARENT, FILL_PARENT)); + CreateCodeList(); g_Config.bReloadCheats = true; - root_ = new LinearLayout(ORIENT_HORIZONTAL); Margins actionMenuMargins(50, -15, 15, 0); LinearLayout *leftColumn = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(400, FILL_PARENT)); leftColumn->Add(new ItemHeader(cw->T("Options"))); - leftColumn->Add(new Choice(di->T("Back")))->OnClick.Handle(this, &UIScreen::OnBack); //leftColumn->Add(new Choice(cw->T("Add Cheat")))->OnClick.Handle(this, &CwCheatScreen::OnAddCheat); leftColumn->Add(new Choice(cw->T("Import Cheats")))->OnClick.Handle(this, &CwCheatScreen::OnImportCheat); #if !defined(MOBILE_DEVICE) @@ -97,21 +98,24 @@ void CwCheatScreen::CreateViews() { leftColumn->Add(new Choice(cw->T("Enable/Disable All")))->OnClick.Handle(this, &CwCheatScreen::OnEnableAll); leftColumn->Add(new PopupSliderChoice(&g_Config.iCwCheatRefreshRate, 1, 1000, cw->T("Refresh Rate"), 1, screenManager())); - rightScroll_ = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(0.5f)); + rightScroll_ = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT, 0.5f)); rightScroll_->SetTag("CwCheats"); rightScroll_->SetScrollToTop(false); rightScroll_->ScrollTo(g_Config.fCwCheatScrollPosition); LinearLayout *rightColumn = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(200, FILL_PARENT, actionMenuMargins)); - LayoutParams *layout = new LayoutParams(500, 50, LP_PLAIN); rightScroll_->Add(rightColumn); - root_->Add(leftColumn); - root_->Add(rightScroll_); rightColumn->Add(new ItemHeader(cw->T("Cheats"))); for (size_t i = 0; i < formattedList_.size(); i++) { - name = formattedList_[i].c_str(); - rightColumn->Add(new CheatCheckBox(&bEnableCheat[i], cw->T(name), ""))->OnClick.Handle(this, &CwCheatScreen::OnCheckBox); + rightColumn->Add(new CheatCheckBox(&bEnableCheat[i], formattedList_[i]))->OnClick.Handle(this, &CwCheatScreen::OnCheckBox); } + + LinearLayout *layout = new LinearLayout(ORIENT_HORIZONTAL, new LayoutParams(FILL_PARENT, FILL_PARENT)); + layout->Add(leftColumn); + layout->Add(rightScroll_); + root_->Add(layout); + + AddStandardBack(root_); } void CwCheatScreen::onFinish(DialogResult result) { @@ -268,6 +272,12 @@ UI::EventReturn CwCheatScreen::OnImportCheat(UI::EventParams ¶ms) { } UI::EventReturn CwCheatScreen::OnCheckBox(UI::EventParams ¶ms) { + CheatCheckBox *checkbox = (CheatCheckBox *)params.v; + if (checkbox->Toggled()) { + processFileOn(checkbox->Text()); + } else { + processFileOff(checkbox->Text()); + } return UI::EVENT_DONE; } @@ -313,19 +323,3 @@ void CwCheatScreen::processFileOff(std::string deactivatedCheat) { fs.close(); } -void CheatCheckBox::Draw(UIContext &dc) { - ClickableItem::Draw(dc); - int paddingX = 16; - int paddingY = 12; - - ImageID image = *toggle_ ? dc.theme->checkOn : dc.theme->checkOff; - - UI::Style style = dc.theme->itemStyle; - if (!IsEnabled()) - style = dc.theme->itemDisabledStyle; - - dc.SetFontStyle(dc.theme->uiFont); - dc.DrawText(text_.c_str(), bounds_.x + paddingX, bounds_.centerY(), style.fgColor, ALIGN_VCENTER); - dc.Draw()->DrawImage(image, bounds_.x2() - paddingX, bounds_.centerY(), 1.0f, style.fgColor, ALIGN_RIGHT | ALIGN_VCENTER); -} - diff --git a/UI/CwCheatScreen.h b/UI/CwCheatScreen.h index a51c94afbe..32ba42d022 100644 --- a/UI/CwCheatScreen.h +++ b/UI/CwCheatScreen.h @@ -28,12 +28,11 @@ extern std::string gameTitle; class CwCheatScreen : public UIDialogScreenWithBackground { public: CwCheatScreen(std::string gamePath); - CwCheatScreen() {} + void CreateCodeList(); void processFileOn(std::string activatedCheat); void processFileOff(std::string deactivatedCheat); - const char * name; - std::string activatedCheat, deactivatedCheat; + UI::EventReturn OnAddCheat(UI::EventParams ¶ms); UI::EventReturn OnImportCheat(UI::EventParams ¶ms); UI::EventReturn OnEditCheatFile(UI::EventParams ¶ms); @@ -49,34 +48,16 @@ private: UI::ScrollView *rightScroll_; }; -// TODO: Instead just hook the OnClick event on a regular checkbox. -class CheatCheckBox : public UI::ClickableItem, public CwCheatScreen { +class CheatCheckBox : public UI::CheckBox { public: - CheatCheckBox(bool *toggle, const std::string &text, const std::string &smallText = "", UI::LayoutParams *layoutParams = 0) - : UI::ClickableItem(layoutParams), toggle_(toggle), text_(text) { - OnClick.Handle(this, &CheatCheckBox::OnClicked); + CheatCheckBox(bool *toggle, const std::string &text, UI::LayoutParams *layoutParams = nullptr) + : UI::CheckBox(toggle, text, "", layoutParams), text_(text) { } - virtual void Draw(UIContext &dc); - - UI::EventReturn OnClicked(UI::EventParams &e) { - bool temp = false; - if (toggle_) { - *toggle_ = !(*toggle_); - temp = *toggle_; - } - if (temp) { - activatedCheat = text_; - processFileOn(activatedCheat); - } else { - deactivatedCheat = text_; - processFileOff(deactivatedCheat); - } - return UI::EVENT_DONE; + std::string Text() { + return text_; } private: - bool *toggle_; std::string text_; - std::string smallText_; }; From 29808ae53b2b23f1df30f9957689508e566172e7 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 11 Apr 2020 12:43:55 -0700 Subject: [PATCH 3/7] UI: Update cheat file more safely. This uses the common parsing logic (which supports multiple games in a single cheat file), and prevents reverting the file if edited outside. --- Core/CwCheat.cpp | 68 +++++++------- Core/CwCheat.h | 10 ++- UI/CwCheatScreen.cpp | 207 +++++++++++++++++++------------------------ UI/CwCheatScreen.h | 19 ++-- 4 files changed, 143 insertions(+), 161 deletions(-) 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 { From 698dfa74b4076358ed9111b5717a46ed6484dd3f Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 11 Apr 2020 12:57:35 -0700 Subject: [PATCH 4/7] UI: Monitor cheat file changes on cheat editor. This way if they switch apps or whatever, it'll update when they come back. This also means constant disk access, but it should be cached anyway. --- UI/CwCheatScreen.cpp | 35 ++++++++++++++++++++++++++++++++--- UI/CwCheatScreen.h | 4 ++++ 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/UI/CwCheatScreen.cpp b/UI/CwCheatScreen.cpp index acce1599d0..fb6608b3b2 100644 --- a/UI/CwCheatScreen.cpp +++ b/UI/CwCheatScreen.cpp @@ -15,9 +15,10 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. +#include "ext/cityhash/city.h" +#include "i18n/i18n.h" #include "ui/ui.h" #include "util/text/utf8.h" -#include "i18n/i18n.h" #include "Common/FileUtil.h" #include "Core/Core.h" @@ -28,6 +29,8 @@ #include "UI/GameInfoCache.h" #include "UI/CwCheatScreen.h" +static const int FILE_CHECK_FRAME_INTERVAL = 53; + CwCheatScreen::CwCheatScreen(std::string gamePath) : UIDialogScreenWithBackground() { gamePath_ = gamePath; @@ -43,10 +46,20 @@ void CwCheatScreen::LoadCheatInfo() { gameTitle = g_paramSFO.GenerateFakeID(gamePath_); } + // We won't parse this, just using it to detect changes to the file. + std::string str; + if (readFileToString(true, activeCheatFile.c_str(), str)) { + fileCheckHash_ = CityHash64(str.c_str(), str.size()); + } + fileCheckCounter_ = 0; + CWCheatEngine *cheatEngine2 = new CWCheatEngine(); cheatEngine2->CreateCheatFile(); fileInfo_ = cheatEngine2->FileInfo(); delete cheatEngine2; + + // Let's also trigger a reload, in case it changed. + g_Config.bReloadCheats = true; } void CwCheatScreen::CreateViews() { @@ -79,7 +92,7 @@ void CwCheatScreen::CreateViews() { rightColumn->Add(new ItemHeader(cw->T("Cheats"))); 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); + return OnCheckBox((int)i); }); } @@ -91,6 +104,23 @@ void CwCheatScreen::CreateViews() { AddStandardBack(root_); } +void CwCheatScreen::update() { + if (fileCheckCounter_++ >= FILE_CHECK_FRAME_INTERVAL) { + // Check if the file has changed. If it has, we'll reload. + std::string str; + if (readFileToString(true, activeCheatFile.c_str(), str)) { + uint64_t newHash = CityHash64(str.c_str(), str.size()); + if (newHash != fileCheckHash_) { + // This will update the hash. + RecreateViews(); + } + } + fileCheckCounter_ = 0; + } + + UIDialogScreenWithBackground::update(); +} + void CwCheatScreen::onFinish(DialogResult result) { if (result != DR_BACK) // This only works for BACK here. return; @@ -130,7 +160,6 @@ UI::EventReturn CwCheatScreen::OnEditCheatFile(UI::EventParams ¶ms) { if (MIPSComp::jit) { MIPSComp::jit->ClearCache(); } - TriggerFinish(DR_OK); #if PPSSPP_PLATFORM(UWP) LaunchBrowser(activeCheatFile.c_str()); #else diff --git a/UI/CwCheatScreen.h b/UI/CwCheatScreen.h index 2e244b0d5b..3417204218 100644 --- a/UI/CwCheatScreen.h +++ b/UI/CwCheatScreen.h @@ -15,6 +15,7 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. +#include #include #include "ui/view.h" @@ -38,6 +39,7 @@ public: UI::EventReturn OnEditCheatFile(UI::EventParams ¶ms); UI::EventReturn OnEnableAll(UI::EventParams ¶ms); + void update() override; void onFinish(DialogResult result) override; protected: @@ -52,6 +54,8 @@ private: UI::ScrollView *rightScroll_ = nullptr; std::vector fileInfo_; std::string gamePath_; + int fileCheckCounter_ = 0; + uint64_t fileCheckHash_; bool enableAllFlag_ = false; }; From dbc838565cd70ff4702991b407f54387dc84e3e1 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 11 Apr 2020 12:59:06 -0700 Subject: [PATCH 5/7] UI: Cleanup old specialized checkbox. --- UI/CwCheatScreen.cpp | 2 +- UI/CwCheatScreen.h | 14 -------------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/UI/CwCheatScreen.cpp b/UI/CwCheatScreen.cpp index fb6608b3b2..a1559d24af 100644 --- a/UI/CwCheatScreen.cpp +++ b/UI/CwCheatScreen.cpp @@ -91,7 +91,7 @@ void CwCheatScreen::CreateViews() { rightColumn->Add(new ItemHeader(cw->T("Cheats"))); for (size_t i = 0; i < fileInfo_.size(); ++i) { - rightColumn->Add(new CheatCheckBox(&fileInfo_[i].enabled, fileInfo_[i].name))->OnClick.Add([=](UI::EventParams &) { + rightColumn->Add(new CheckBox(&fileInfo_[i].enabled, fileInfo_[i].name))->OnClick.Add([=](UI::EventParams &) { return OnCheckBox((int)i); }); } diff --git a/UI/CwCheatScreen.h b/UI/CwCheatScreen.h index 3417204218..860fb8f864 100644 --- a/UI/CwCheatScreen.h +++ b/UI/CwCheatScreen.h @@ -58,17 +58,3 @@ private: uint64_t fileCheckHash_; bool enableAllFlag_ = false; }; - -class CheatCheckBox : public UI::CheckBox { -public: - CheatCheckBox(bool *toggle, const std::string &text, UI::LayoutParams *layoutParams = nullptr) - : UI::CheckBox(toggle, text, "", layoutParams), text_(text) { - } - - std::string Text() { - return text_; - } - -private: - std::string text_; -}; From 5ba7cca5f3dd3ade904274c9509479fac28059ad Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 11 Apr 2020 13:52:25 -0700 Subject: [PATCH 6/7] Cheats: Cleanup global usage. --- Core/CwCheat.cpp | 32 ++++++++++++++------------- Core/CwCheat.h | 5 ++++- UI/CwCheatScreen.cpp | 52 +++++++++++++++++++++++++++----------------- UI/CwCheatScreen.h | 9 ++++---- 4 files changed, 58 insertions(+), 40 deletions(-) diff --git a/Core/CwCheat.cpp b/Core/CwCheat.cpp index a22eacdc94..3bf9078c61 100644 --- a/Core/CwCheat.cpp +++ b/Core/CwCheat.cpp @@ -22,8 +22,6 @@ #endif static int CheatEvent = -1; -std::string gameTitle; -std::string activeCheatFile; static CWCheatEngine *cheatEngine; static bool cheatsEnabled; void hleCheat(u64 userdata, int cyclesLate); @@ -43,9 +41,9 @@ class CheatFileParser { public: CheatFileParser(const std::string &filename, const std::string &gameID = "") { #if defined(_WIN32) && !defined(__MINGW32__) - file_.open(ConvertUTF8ToWString(activeCheatFile)); + file_.open(ConvertUTF8ToWString(filename)); #else - file_.open(activeCheatFile.c_str()); + file_.open(filename.c_str()); #endif validGameID_ = ReplaceAll(gameID, "-", ""); @@ -247,13 +245,13 @@ static void __CheatStop() { static void __CheatStart() { __CheatStop(); - gameTitle = g_paramSFO.GetValueString("DISC_ID"); - - if (gameTitle != "") { //this only generates ini files on boot, let's leave homebrew ini file for UI + std::string gameID = g_paramSFO.GetValueString("DISC_ID"); + cheatEngine = new CWCheatEngine(gameID); + // This only generates ini files on boot, let's leave homebrew ini file for UI. + if (!gameID.empty()) { cheatEngine->CreateCheatFile(); } - cheatEngine = new CWCheatEngine(); cheatEngine->ParseCheats(); g_Config.bReloadCheats = false; cheatsEnabled = true; @@ -348,28 +346,32 @@ void hleCheat(u64 userdata, int cyclesLate) { cheatEngine->Run(); } -CWCheatEngine::CWCheatEngine() { +CWCheatEngine::CWCheatEngine(const std::string &gameID) : gameID_(gameID) { } void CWCheatEngine::CreateCheatFile() { - activeCheatFile = GetSysDirectory(DIRECTORY_CHEATS) + gameTitle + ".ini"; + filename_ = GetSysDirectory(DIRECTORY_CHEATS) + gameID_ + ".ini"; File::CreateFullPath(GetSysDirectory(DIRECTORY_CHEATS)); - if (!File::Exists(activeCheatFile)) { - FILE *f = File::OpenCFile(activeCheatFile, "wb"); + if (!File::Exists(filename_)) { + FILE *f = File::OpenCFile(filename_, "wb"); if (f) { fwrite("\xEF\xBB\xBF\n", 1, 4, f); fclose(f); } - if (!File::Exists(activeCheatFile)) { + if (!File::Exists(filename_)) { auto err = GetI18NCategory("Error"); host->NotifyUserMessage(err->T("Unable to create cheat file, disk may be full")); } } } +std::string CWCheatEngine::CheatFilename() { + return filename_; +} + void CWCheatEngine::ParseCheats() { - CheatFileParser parser(activeCheatFile, gameTitle); + CheatFileParser parser(filename_, gameID_); parser.Parse(); // TODO: Report errors. @@ -384,7 +386,7 @@ u32 CWCheatEngine::GetAddress(u32 value) { } std::vector CWCheatEngine::FileInfo() { - CheatFileParser parser(activeCheatFile, gameTitle); + CheatFileParser parser(filename_, gameID_); parser.Parse(); return parser.GetFileInfo(); diff --git a/Core/CwCheat.h b/Core/CwCheat.h index c6e90d07b8..00c6134fd9 100644 --- a/Core/CwCheat.h +++ b/Core/CwCheat.h @@ -46,10 +46,11 @@ struct CheatOperation; class CWCheatEngine { public: - CWCheatEngine(); + CWCheatEngine(const std::string &gameID); std::vector FileInfo(); void ParseCheats(); void CreateCheatFile(); + std::string CheatFilename(); void Run(); bool HasCheats(); @@ -67,4 +68,6 @@ private: bool TestIfAddr(const CheatOperation &op, bool(*oper)(int a, int b)); std::vector cheats_; + std::string gameID_; + std::string filename_; }; diff --git a/UI/CwCheatScreen.cpp b/UI/CwCheatScreen.cpp index a1559d24af..c36056b273 100644 --- a/UI/CwCheatScreen.cpp +++ b/UI/CwCheatScreen.cpp @@ -31,32 +31,41 @@ static const int FILE_CHECK_FRAME_INTERVAL = 53; -CwCheatScreen::CwCheatScreen(std::string gamePath) +CwCheatScreen::CwCheatScreen(const std::string &gamePath) : UIDialogScreenWithBackground() { gamePath_ = gamePath; } +CwCheatScreen::~CwCheatScreen() { + delete engine_; +} + void CwCheatScreen::LoadCheatInfo() { std::shared_ptr info = g_gameInfoCache->GetInfo(nullptr, gamePath_, 0); + std::string gameID; if (info && info->paramSFOLoaded) { - gameTitle = info->paramSFO.GetValueString("DISC_ID"); + gameID = info->paramSFO.GetValueString("DISC_ID"); } if ((info->id.empty() || !info->disc_total) && gamePath_.find("/PSP/GAME/") != std::string::npos) { - gameTitle = g_paramSFO.GenerateFakeID(gamePath_); + gameID = g_paramSFO.GenerateFakeID(gamePath_); + } + + if (engine_ == nullptr || gameID != gameID_) { + gameID_ = gameID; + delete engine_; + engine_ = new CWCheatEngine(gameID_); + engine_->CreateCheatFile(); } // We won't parse this, just using it to detect changes to the file. std::string str; - if (readFileToString(true, activeCheatFile.c_str(), str)) { + if (readFileToString(true, engine_->CheatFilename().c_str(), str)) { fileCheckHash_ = CityHash64(str.c_str(), str.size()); } fileCheckCounter_ = 0; - CWCheatEngine *cheatEngine2 = new CWCheatEngine(); - cheatEngine2->CreateCheatFile(); - fileInfo_ = cheatEngine2->FileInfo(); - delete cheatEngine2; + fileInfo_ = engine_->FileInfo(); // Let's also trigger a reload, in case it changed. g_Config.bReloadCheats = true; @@ -105,10 +114,10 @@ void CwCheatScreen::CreateViews() { } void CwCheatScreen::update() { - if (fileCheckCounter_++ >= FILE_CHECK_FRAME_INTERVAL) { + if (fileCheckCounter_++ >= FILE_CHECK_FRAME_INTERVAL && engine_) { // Check if the file has changed. If it has, we'll reload. std::string str; - if (readFileToString(true, activeCheatFile.c_str(), str)) { + if (readFileToString(true, engine_->CheatFilename().c_str(), str)) { uint64_t newHash = CityHash64(str.c_str(), str.size()); if (newHash != fileCheckHash_) { // This will update the hash. @@ -160,17 +169,19 @@ UI::EventReturn CwCheatScreen::OnEditCheatFile(UI::EventParams ¶ms) { if (MIPSComp::jit) { MIPSComp::jit->ClearCache(); } + if (engine_) { #if PPSSPP_PLATFORM(UWP) - LaunchBrowser(activeCheatFile.c_str()); + LaunchBrowser(engine_->CheatFilename().c_str()); #else - File::openIniFile(activeCheatFile); + File::openIniFile(engine_->CheatFilename()); #endif + } return UI::EVENT_DONE; } UI::EventReturn CwCheatScreen::OnImportCheat(UI::EventParams ¶ms) { - if (gameTitle.length() != 9) { - WARN_LOG(COMMON, "CWCHEAT: Incorrect ID(%s) - can't import cheats.", gameTitle.c_str()); + if (gameID_.length() != 9 || !engine_) { + WARN_LOG(COMMON, "CWCHEAT: Incorrect ID(%s) - can't import cheats.", gameID_.c_str()); return UI::EVENT_DONE; } std::string line; @@ -179,7 +190,7 @@ UI::EventReturn CwCheatScreen::OnImportCheat(UI::EventParams ¶ms) { std::vector newList; std::string cheatFile = GetSysDirectory(DIRECTORY_CHEATS) + "cheat.db"; - std::string gameID = StringFromFormat("_S %s-%s", gameTitle.substr(0, 4).c_str(), gameTitle.substr(4).c_str()); + std::string gameID = StringFromFormat("_S %s-%s", gameID_.substr(0, 4).c_str(), gameID_.substr(4).c_str()); std::fstream fs; File::OpenCPPFile(fs, cheatFile, std::ios::in); @@ -226,10 +237,10 @@ UI::EventReturn CwCheatScreen::OnImportCheat(UI::EventParams ¶ms) { } fs.close(); std::string title2; - File::OpenCPPFile(fs, activeCheatFile, std::ios::in); + File::OpenCPPFile(fs, engine_->CheatFilename(), std::ios::in); getline(fs, title2); fs.close(); - File::OpenCPPFile(fs, activeCheatFile, std::ios::out | std::ios::app); + File::OpenCPPFile(fs, engine_->CheatFilename(), std::ios::out | std::ios::app); auto it = title.begin(); if (((title2[0] == '_' && title2[1] != 'S') || title2[0] == '/' || title2[0] == '#') && it != title.end() && (++it) != title.end()) { @@ -266,7 +277,7 @@ UI::EventReturn CwCheatScreen::OnCheckBox(int index) { bool CwCheatScreen::RebuildCheatFile(int index) { std::fstream fs; - if (!File::OpenCPPFile(fs, activeCheatFile, std::ios::in)) { + if (!engine_ || !File::OpenCPPFile(fs, engine_->CheatFilename(), std::ios::in)) { return false; } @@ -292,8 +303,9 @@ bool CwCheatScreen::RebuildCheatFile(int index) { } line = (info.enabled ? "_C1 " : "_C0 ") + info.name; + return true; } - return true; + return false; }; if (index == INDEX_ALL) { @@ -310,7 +322,7 @@ bool CwCheatScreen::RebuildCheatFile(int index) { } - if (!File::OpenCPPFile(fs, activeCheatFile, std::ios::out | std::ios::trunc)) { + if (!File::OpenCPPFile(fs, engine_->CheatFilename(), std::ios::out | std::ios::trunc)) { return false; } diff --git a/UI/CwCheatScreen.h b/UI/CwCheatScreen.h index 860fb8f864..c1bc827c68 100644 --- a/UI/CwCheatScreen.h +++ b/UI/CwCheatScreen.h @@ -24,13 +24,12 @@ #include "UI/MiscScreens.h" struct CheatFileInfo; - -extern std::string activeCheatFile; -extern std::string gameTitle; +class CWCheatEngine; class CwCheatScreen : public UIDialogScreenWithBackground { public: - CwCheatScreen(std::string gamePath); + CwCheatScreen(const std::string &gamePath); + ~CwCheatScreen(); void LoadCheatInfo(); @@ -52,8 +51,10 @@ private: bool RebuildCheatFile(int index); UI::ScrollView *rightScroll_ = nullptr; + CWCheatEngine *engine_ = nullptr; std::vector fileInfo_; std::string gamePath_; + std::string gameID_; int fileCheckCounter_ = 0; uint64_t fileCheckHash_; bool enableAllFlag_ = false; From e60623498fcda9b31694f521d449d820f29cc463 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 11 Apr 2020 14:01:32 -0700 Subject: [PATCH 7/7] Cheats: Fix use in homebrew on start. --- Core/CwCheat.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Core/CwCheat.cpp b/Core/CwCheat.cpp index 3bf9078c61..b50ed2ce8d 100644 --- a/Core/CwCheat.cpp +++ b/Core/CwCheat.cpp @@ -245,10 +245,17 @@ static void __CheatStop() { static void __CheatStart() { __CheatStop(); - std::string gameID = g_paramSFO.GetValueString("DISC_ID"); + std::string realGameID = g_paramSFO.GetValueString("DISC_ID"); + std::string gameID = realGameID; + const std::string gamePath = PSP_CoreParameter().fileToStart; + const bool badGameSFO = realGameID.empty() || !g_paramSFO.GetValueInt("DISC_TOTAL"); + if (badGameSFO && gamePath.find("/PSP/GAME/") != std::string::npos) { + gameID = g_paramSFO.GenerateFakeID(gamePath); + } + cheatEngine = new CWCheatEngine(gameID); // This only generates ini files on boot, let's leave homebrew ini file for UI. - if (!gameID.empty()) { + if (!realGameID.empty()) { cheatEngine->CreateCheatFile(); } @@ -347,10 +354,10 @@ void hleCheat(u64 userdata, int cyclesLate) { } CWCheatEngine::CWCheatEngine(const std::string &gameID) : gameID_(gameID) { + filename_ = GetSysDirectory(DIRECTORY_CHEATS) + gameID_ + ".ini"; } void CWCheatEngine::CreateCheatFile() { - filename_ = GetSysDirectory(DIRECTORY_CHEATS) + gameID_ + ".ini"; File::CreateFullPath(GetSysDirectory(DIRECTORY_CHEATS)); if (!File::Exists(filename_)) {