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.
This commit is contained in:
Unknown W. Brackets 2020-04-11 12:43:55 -07:00
parent 4c061e9212
commit 29808ae53b
4 changed files with 143 additions and 161 deletions

View file

@ -61,8 +61,13 @@ public:
return cheats_;
}
std::vector<CheatFileInfo> 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<std::string> errors_;
std::vector<CheatFileInfo> cheatInfo_;
std::vector<CheatCode> cheats_;
std::vector<CheatLine> 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<std::string> CWCheatEngine::GetCodesList() {
// Reads the entire cheat list from the appropriate .ini.
std::vector<std::string> 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<CheatFileInfo> 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) {

View file

@ -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 <string>
#include <vector>
#include <iostream>
@ -34,12 +36,18 @@ struct CheatCode {
std::vector<CheatLine> lines;
};
struct CheatFileInfo {
int lineNum;
std::string name;
bool enabled;
};
struct CheatOperation;
class CWCheatEngine {
public:
CWCheatEngine();
std::vector<std::string> GetCodesList();
std::vector<CheatFileInfo> FileInfo();
void ParseCheats();
void CreateCheatFile();
void Run();

View file

@ -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 <deque>
#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<std::string> cheatList;
static CWCheatEngine *cheatEngine2;
static std::deque<bool> bEnableCheat;
static std::string gamePath_;
CwCheatScreen::CwCheatScreen(std::string gamePath)
: UIDialogScreenWithBackground() {
gamePath_ = gamePath;
}
void CwCheatScreen::CreateCodeList() {
std::shared_ptr<GameInfo> info = g_gameInfoCache->GetInfo(NULL, gamePath_, 0);
void CwCheatScreen::LoadCheatInfo() {
std::shared_ptr<GameInfo> 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 &params) {
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 &params) {
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 &params) {
}
}
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 &params) {
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<std::string> 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();
}

View file

@ -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 &params);
UI::EventReturn OnImportCheat(UI::EventParams &params);
@ -39,13 +39,20 @@ public:
UI::EventReturn OnEnableAll(UI::EventParams &params);
void onFinish(DialogResult result) override;
protected:
void CreateViews() override;
private:
UI::EventReturn OnCheckBox(UI::EventParams &params);
std::vector<std::string> formattedList_;
UI::ScrollView *rightScroll_;
UI::EventReturn OnCheckBox(int index);
enum { INDEX_ALL = -1 };
bool RebuildCheatFile(int index);
UI::ScrollView *rightScroll_ = nullptr;
std::vector<CheatFileInfo> fileInfo_;
std::string gamePath_;
bool enableAllFlag_ = false;
};
class CheatCheckBox : public UI::CheckBox {