mirror of
https://github.com/hrydgard/ppsspp.git
synced 2025-04-02 11:01:50 -04:00
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:
parent
4c061e9212
commit
29808ae53b
4 changed files with 143 additions and 161 deletions
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 ¶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<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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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<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 {
|
||||
|
|
Loading…
Add table
Reference in a new issue