CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/UI/CwCheatScreen.cpp
Views: 1401
// Copyright (c) 2012- PPSSPP Project.12// This program is free software: you can redistribute it and/or modify3// it under the terms of the GNU General Public License as published by4// the Free Software Foundation, version 2.0 or later versions.56// This program is distributed in the hope that it will be useful,7// but WITHOUT ANY WARRANTY; without even the implied warranty of8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9// GNU General Public License 2.0 for more details.1011// A copy of the GPL 2.0 should have been included with the program.12// If not, see http://www.gnu.org/licenses/1314// Official git repository and contact information can be found at15// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.1617#include "ppsspp_config.h"18#include "ext/xxhash.h"19#include "Common/UI/UI.h"2021#include "Common/Data/Text/I18n.h"22#include "Common/Data/Encoding/Utf8.h"23#include "Common/File/FileUtil.h"24#include "Common/StringUtils.h"25#include "Common/System/System.h"26#include "Common/System/Request.h"27#include "Core/Core.h"28#include "Core/Config.h"29#include "Core/CwCheat.h"30#include "Core/MIPS/JitCommon/JitCommon.h"3132#include "UI/GameInfoCache.h"33#include "UI/CwCheatScreen.h"3435static const int FILE_CHECK_FRAME_INTERVAL = 53;3637static Path GetGlobalCheatFilePath() {38return GetSysDirectory(DIRECTORY_CHEATS) / "cheat.db";39}4041CwCheatScreen::CwCheatScreen(const Path &gamePath)42: UIDialogScreenWithGameBackground(gamePath) {43}4445CwCheatScreen::~CwCheatScreen() {46delete engine_;47}4849bool CwCheatScreen::TryLoadCheatInfo() {50std::shared_ptr<GameInfo> info = g_gameInfoCache->GetInfo(nullptr, gamePath_, GameInfoFlags::PARAM_SFO);51std::string gameID;52if (!info->Ready(GameInfoFlags::PARAM_SFO)) {53return false;54}55gameID = info->GetParamSFO().GetValueString("DISC_ID");56if ((info->id.empty() || !info->disc_total)57&& gamePath_.FilePathContainsNoCase("PSP/GAME/")) {58gameID = g_paramSFO.GenerateFakeID(gamePath_);59}6061if (!engine_ || gameID != gameID_) {62gameID_ = gameID;63delete engine_;64engine_ = new CWCheatEngine(gameID_);65engine_->CreateCheatFile();66}6768// We won't parse this, just using it to detect changes to the file.69std::string str;70if (File::ReadTextFileToString(engine_->CheatFilename(), &str)) {71fileCheckHash_ = XXH3_64bits(str.c_str(), str.size());72}73fileCheckCounter_ = 0;7475fileInfo_ = engine_->FileInfo();7677// Let's also trigger a reload, in case it changed.78g_Config.bReloadCheats = true;79return true;80}8182void CwCheatScreen::CreateViews() {83using namespace UI;84auto cw = GetI18NCategory(I18NCat::CWCHEATS);85auto di = GetI18NCategory(I18NCat::DIALOG);86auto mm = GetI18NCategory(I18NCat::MAINMENU);8788root_ = new AnchorLayout(new LayoutParams(FILL_PARENT, FILL_PARENT));8990TryLoadCheatInfo(); // in case the info is already in cache.91Margins actionMenuMargins(50, -15, 15, 0);9293LinearLayout *leftColumn = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(400, FILL_PARENT));94//leftColumn->Add(new Choice(cw->T("Add Cheat")))->OnClick.Handle(this, &CwCheatScreen::OnAddCheat);95leftColumn->Add(new ItemHeader(cw->T("Import Cheats")));9697Path cheatPath = GetGlobalCheatFilePath();9899std::string root = GetSysDirectory(DIRECTORY_MEMSTICK_ROOT).ToString();100101std::string title = StringFromFormat(cw->T_cstr("Import from %s"), "PSP/Cheats/cheat.db");102103leftColumn->Add(new Choice(title.c_str()))->OnClick.Handle(this, &CwCheatScreen::OnImportCheat);104leftColumn->Add(new Choice(mm->T("Browse"), ImageID("I_FOLDER_OPEN")))->OnClick.Handle(this, &CwCheatScreen::OnImportBrowse);105errorMessageView_ = leftColumn->Add(new TextView(di->T("LoadingFailed")));106errorMessageView_->SetVisibility(V_GONE);107108leftColumn->Add(new ItemHeader(di->T("Options")));109#if !defined(MOBILE_DEVICE)110leftColumn->Add(new Choice(cw->T("Edit Cheat File")))->OnClick.Handle(this, &CwCheatScreen::OnEditCheatFile);111#endif112leftColumn->Add(new Choice(di->T("Disable All")))->OnClick.Handle(this, &CwCheatScreen::OnDisableAll);113leftColumn->Add(new PopupSliderChoice(&g_Config.iCwCheatRefreshIntervalMs, 1, 1000, 77, cw->T("Refresh interval"), 1, screenManager()))->SetFormat(di->T("%d ms"));114115116rightScroll_ = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT, 0.5f));117rightScroll_->SetTag("CwCheats");118rightScroll_->RememberPosition(&g_Config.fCwCheatScrollPosition);119LinearLayout *rightColumn = new LinearLayoutList(ORIENT_VERTICAL, new LinearLayoutParams(200, FILL_PARENT, actionMenuMargins));120rightScroll_->Add(rightColumn);121122rightColumn->Add(new ItemHeader(cw->T("Cheats")));123for (size_t i = 0; i < fileInfo_.size(); ++i) {124rightColumn->Add(new CheckBox(&fileInfo_[i].enabled, fileInfo_[i].name))->OnClick.Add([=](UI::EventParams &) {125return OnCheckBox((int)i);126});127}128129LinearLayout *layout = new LinearLayout(ORIENT_HORIZONTAL, new LayoutParams(FILL_PARENT, FILL_PARENT));130layout->Add(leftColumn);131layout->Add(rightScroll_);132root_->Add(layout);133134AddStandardBack(root_);135}136137void CwCheatScreen::update() {138if (gameID_.empty()) {139if (TryLoadCheatInfo()) {140RecreateViews();141}142}143144if (fileCheckCounter_++ >= FILE_CHECK_FRAME_INTERVAL && engine_) {145// Check if the file has changed. If it has, we'll reload.146std::string str;147if (File::ReadTextFileToString(engine_->CheatFilename(), &str)) {148uint64_t newHash = XXH3_64bits(str.c_str(), str.size());149if (newHash != fileCheckHash_) {150// This will update the hash.151RecreateViews();152}153}154fileCheckCounter_ = 0;155}156157UIDialogScreenWithGameBackground::update();158}159160void CwCheatScreen::onFinish(DialogResult result) {161if (result != DR_BACK) // This only works for BACK here.162return;163164std::lock_guard<std::recursive_mutex> guard(MIPSComp::jitLock);165if (MIPSComp::jit) {166MIPSComp::jit->ClearCache();167}168}169170UI::EventReturn CwCheatScreen::OnDisableAll(UI::EventParams ¶ms) {171// Disable all the switches.172for (auto &info : fileInfo_) {173info.enabled = false;174}175176if (!RebuildCheatFile(INDEX_ALL)) {177// Probably the file was modified outside PPSSPP, refresh.178// TODO: Report error.179RecreateViews();180return UI::EVENT_SKIPPED;181}182183return UI::EVENT_DONE;184}185186UI::EventReturn CwCheatScreen::OnAddCheat(UI::EventParams ¶ms) {187TriggerFinish(DR_OK);188g_Config.bReloadCheats = true;189return UI::EVENT_DONE;190}191192UI::EventReturn CwCheatScreen::OnEditCheatFile(UI::EventParams ¶ms) {193g_Config.bReloadCheats = true;194std::lock_guard<std::recursive_mutex> guard(MIPSComp::jitLock);195if (MIPSComp::jit) {196MIPSComp::jit->ClearCache();197}198if (engine_) {199File::OpenFileInEditor(engine_->CheatFilename());200}201return UI::EVENT_DONE;202}203204static char *GetLineNoNewline(char *temp, int sz, FILE *fp) {205char *line = fgets(temp, sz, fp);206if (!line)207return nullptr;208209// If the last character is \n, just make it the terminator.210char *end = line + strlen(line) - 1;211if (*end == '\n')212*end = '\0';213return line;214}215216UI::EventReturn CwCheatScreen::OnImportBrowse(UI::EventParams ¶ms) {217System_BrowseForFile(GetRequesterToken(), "Open cheat DB file", BrowseFileType::DB, [&](const std::string &value, int) {218Path path(value);219INFO_LOG(Log::System, "Attempting to load cheats from: '%s'", path.ToVisualString().c_str());220if (ImportCheats(path)) {221g_Config.bReloadCheats = true;222} else {223// Show an error message?224}225RecreateViews();226});227return UI::EVENT_DONE;228}229230UI::EventReturn CwCheatScreen::OnImportCheat(UI::EventParams ¶ms) {231if (!ImportCheats(GetGlobalCheatFilePath())) {232// Show an error message?233errorMessageView_->SetVisibility(UI::V_VISIBLE);234return UI::EVENT_DONE;235}236237g_Config.bReloadCheats = true;238RecreateViews();239return UI::EVENT_DONE;240}241242bool CwCheatScreen::ImportCheats(const Path & cheatFile) {243if (gameID_.length() != 9 || !engine_) {244WARN_LOG(Log::Common, "CWCHEAT: Incorrect ID(%s) - can't import cheats.", gameID_.c_str());245return false;246}247248std::string gameID = StringFromFormat("_S %s-%s", gameID_.substr(0, 4).c_str(), gameID_.substr(4).c_str());249250FILE *in = File::OpenCFile(cheatFile, "rt");251if (!in) {252WARN_LOG(Log::Common, "Unable to open %s\n", cheatFile.c_str());253return false;254}255256std::vector<std::string> title;257std::vector<std::string> newList;258259char linebuf[2048]{};260bool parseGameEntry = false;261bool parseCheatEntry = false;262263while (in && !feof(in)) {264char *line = GetLineNoNewline(linebuf, sizeof(linebuf), in);265266if (!line) {267continue;268}269270if (line[0] == '_' && line[1] == 'S') {271parseGameEntry = gameID == line;272parseCheatEntry = false;273} else if (parseGameEntry && line[0] == '_' && line[1] == 'C') {274// Test if cheat already exists.275parseCheatEntry = !HasCheatWithName(std::string(line).substr(4));276}277278if (!parseGameEntry) {279if (newList.size() > 0) {280// Only parse the first matching game entry.281break;282} else {283// Haven't yet found a matching game entry, continue parsing.284continue;285}286}287288if (line[0] == '_' && (line[1] == 'S' || line[1] == 'G') && title.size() < 2) {289title.push_back(line);290} else if (parseCheatEntry && ((line[0] == '_' && (line[1] == 'C' || line[1] == 'L')) || line[0] == '/' || line[0] == '#')) {291newList.push_back(line);292}293}294fclose(in);295296std::string title2;297// Hmm, this probably gets confused about BOMs?298FILE *inTitle2 = File::OpenCFile(engine_->CheatFilename(), "rt");299if (inTitle2) {300char temp[2048];301char *line = GetLineNoNewline(temp, sizeof(temp), inTitle2);302if (line)303title2 = line;304fclose(inTitle2);305}306307FILE *append = File::OpenCFile(engine_->CheatFilename(), "at");308if (!append)309return UI::EVENT_SKIPPED;310311if (title2.size() == 0 || title2[0] != '_' || title2[1] != 'S') {312for (int i = (int)title.size(); i > 0; i--) {313newList.insert(newList.begin(), title[i - 1]);314}315}316317NOTICE_LOG(Log::Common, "Imported %u lines from %s.\n", (int)newList.size(), cheatFile.c_str());318if (newList.size() != 0) {319fputc('\n', append);320}321322for (int i = 0; i < (int)newList.size(); i++) {323fprintf(append, "%s", newList[i].c_str());324if (i < (int)newList.size() - 1) {325fputc('\n', append);326}327}328fclose(append);329return true;330}331332UI::EventReturn CwCheatScreen::OnCheckBox(int index) {333if (!RebuildCheatFile(index)) {334// TODO: Report error. Let's reload the file, presumably it changed.335RecreateViews();336return UI::EVENT_SKIPPED;337}338339return UI::EVENT_DONE;340}341342bool CwCheatScreen::HasCheatWithName(const std::string &name) {343for (const auto &existing : fileInfo_) {344if (name == existing.name) {345return true;346}347}348349return false;350}351352bool CwCheatScreen::RebuildCheatFile(int index) {353if (!engine_)354return false;355FILE *in = File::OpenCFile(engine_->CheatFilename(), "rt");356if (!in)357return false;358359// In case lines were edited while we weren't looking, reload them.360std::vector<std::string> lines;361for (; !feof(in); ) {362char temp[2048];363char *line = GetLineNoNewline(temp, sizeof(temp), in);364if (!line)365break;366367lines.push_back(line);368}369fclose(in);370371auto updateLine = [&](const CheatFileInfo &info) {372// Line numbers start with one, not zero.373size_t lineIndex = info.lineNum - 1;374if (lines.size() > lineIndex) {375auto &line = lines[lineIndex];376// This is the one to change. Let's see if it matches - maybe the file changed.377bool isCheatDef = line.find("_C") != line.npos;378bool hasCheatName = !info.name.empty() && line.find(info.name) != line.npos;379if (!isCheatDef || !hasCheatName) {380return false;381}382383line = (info.enabled ? "_C1 " : "_C0 ") + info.name;384return true;385}386return false;387};388389if (index == INDEX_ALL) {390for (const auto &info : fileInfo_) {391// Bail out if any don't match with no changes.392if (!updateLine(info)) {393return false;394}395}396} else {397if (!updateLine(fileInfo_[index])) {398return false;399}400}401402FILE *out = File::OpenCFile(engine_->CheatFilename(), "wt");403if (!out) {404return false;405}406407for (size_t i = 0; i < lines.size(); ++i) {408fprintf(out, "%s", lines[i].c_str());409if (i != lines.size() - 1)410fputc('\n', out);411}412fclose(out);413414// Cheats will need to be reparsed now.415g_Config.bReloadCheats = true;416return true;417}418419420