Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/UI/CwCheatScreen.cpp
5656 views
1
// Copyright (c) 2012- PPSSPP Project.
2
3
// This program is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, version 2.0 or later versions.
6
7
// This program is distributed in the hope that it will be useful,
8
// but WITHOUT ANY WARRANTY; without even the implied warranty of
9
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
// GNU General Public License 2.0 for more details.
11
12
// A copy of the GPL 2.0 should have been included with the program.
13
// If not, see http://www.gnu.org/licenses/
14
15
// Official git repository and contact information can be found at
16
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
17
18
#include "ppsspp_config.h"
19
#include "ext/xxhash.h"
20
#include "Common/UI/UI.h"
21
22
#include "Common/Data/Text/I18n.h"
23
#include "Common/Data/Encoding/Utf8.h"
24
#include "Common/File/FileUtil.h"
25
#include "Common/StringUtils.h"
26
#include "Common/System/System.h"
27
#include "Common/System/Request.h"
28
#include "Common/UI/PopupScreens.h"
29
#include "Core/System.h"
30
#include "Core/Config.h"
31
#include "Core/CwCheat.h"
32
#include "Core/MIPS/JitCommon/JitCommon.h"
33
34
#include "UI/GameInfoCache.h"
35
#include "UI/CwCheatScreen.h"
36
37
static const int FILE_CHECK_FRAME_INTERVAL = 53;
38
39
static Path GetGlobalCheatFilePath() {
40
return GetSysDirectory(DIRECTORY_CHEATS) / "cheat.db";
41
}
42
43
CwCheatScreen::CwCheatScreen(const Path &gamePath)
44
: UITwoPaneBaseDialogScreen(gamePath, TwoPaneFlags::Default) {
45
}
46
47
CwCheatScreen::~CwCheatScreen() {
48
delete engine_;
49
}
50
51
bool CwCheatScreen::TryLoadCheatInfo() {
52
std::shared_ptr<GameInfo> info = g_gameInfoCache->GetInfo(nullptr, gamePath_, GameInfoFlags::PARAM_SFO);
53
std::string gameID;
54
if (!info->Ready(GameInfoFlags::PARAM_SFO)) {
55
return false;
56
}
57
gameID = info->GetParamSFO().GetValueString("DISC_ID");
58
if ((info->id.empty() || !info->disc_total)
59
&& gamePath_.FilePathContainsNoCase("PSP/GAME/")) {
60
gameID = g_paramSFO.GenerateFakeID(gamePath_);
61
}
62
63
if (!engine_ || gameID != gameID_) {
64
gameID_ = gameID;
65
delete engine_;
66
engine_ = new CWCheatEngine(gameID_);
67
engine_->CreateCheatFile();
68
}
69
70
// We won't parse this, just using it to detect changes to the file.
71
std::string str;
72
if (File::ReadTextFileToString(engine_->CheatFilename(), &str)) {
73
fileCheckHash_ = XXH3_64bits(str.c_str(), str.size());
74
}
75
fileCheckCounter_ = 0;
76
77
fileInfo_ = engine_->FileInfo();
78
79
// Let's also trigger a reload, in case it changed.
80
g_Config.bReloadCheats = true;
81
return true;
82
}
83
84
void CwCheatScreen::BeforeCreateViews() {
85
TryLoadCheatInfo(); // in case the info is already in cache.
86
}
87
88
void CwCheatScreen::CreateSettingsViews(UI::ViewGroup *leftColumn) {
89
using namespace UI;
90
auto cw = GetI18NCategory(I18NCat::CWCHEATS);
91
auto di = GetI18NCategory(I18NCat::DIALOG);
92
auto mm = GetI18NCategory(I18NCat::MAINMENU);
93
94
//leftColumn->Add(new Choice(cw->T("Add Cheat")))->OnClick.Handle(this, &CwCheatScreen::OnAddCheat);
95
leftColumn->Add(new ItemHeader(cw->T("Import Cheats")));
96
97
Path cheatPath = GetGlobalCheatFilePath();
98
99
std::string root = GetSysDirectory(DIRECTORY_MEMSTICK_ROOT).ToString();
100
101
std::string title = StringFromFormat(cw->T_cstr("Import from %s"), "PSP/Cheats/cheat.db");
102
103
leftColumn->Add(new Choice(title.c_str()))->OnClick.Handle(this, &CwCheatScreen::OnImportCheat);
104
leftColumn->Add(new Choice(mm->T("Browse"), ImageID("I_FOLDER_OPEN")))->OnClick.Handle(this, &CwCheatScreen::OnImportBrowse);
105
errorMessageView_ = leftColumn->Add(new TextView(di->T("LoadingFailed")));
106
errorMessageView_->SetVisibility(V_GONE);
107
108
leftColumn->Add(new ItemHeader(di->T("Options")));
109
#if !defined(MOBILE_DEVICE)
110
leftColumn->Add(new Choice(cw->T("Edit Cheat File")))->OnClick.Handle(this, &CwCheatScreen::OnEditCheatFile);
111
#endif
112
leftColumn->Add(new Choice(di->T("Disable All")))->OnClick.Handle(this, &CwCheatScreen::OnDisableAll);
113
leftColumn->Add(new PopupSliderChoice(&g_Config.iCwCheatRefreshIntervalMs, 1, 1000, 77, cw->T("Refresh interval"), 1, screenManager()))->SetFormat(di->T("%d ms"));
114
}
115
116
void CwCheatScreen::CreateContentViews(UI::ViewGroup *parent) {
117
using namespace UI;
118
auto cw = GetI18NCategory(I18NCat::CWCHEATS);
119
auto di = GetI18NCategory(I18NCat::DIALOG);
120
auto mm = GetI18NCategory(I18NCat::MAINMENU);
121
122
UI::ScrollView *rightScroll = parent->Add(new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT, 0.5f)));
123
rightScroll->SetTag("CwCheats");
124
rightScroll->RememberPosition(&g_Config.fCwCheatScrollPosition);
125
126
LinearLayout *rightColumn = new LinearLayoutList(ORIENT_VERTICAL, new LinearLayoutParams(200, FILL_PARENT));
127
rightScroll->Add(rightColumn);
128
rightColumn->Add(new ItemHeader(cw->T("Cheats")));
129
for (size_t i = 0; i < fileInfo_.size(); ++i) {
130
rightColumn->Add(new CheckBox(&fileInfo_[i].enabled, fileInfo_[i].name))->OnClick.Add([=](UI::EventParams &) {
131
OnCheckBox((int)i);
132
});
133
}
134
}
135
136
std::string_view CwCheatScreen::GetTitle() const {
137
auto cw = GetI18NCategory(I18NCat::CWCHEATS);
138
return cw->T("Cheats");
139
}
140
141
void CwCheatScreen::update() {
142
if (gameID_.empty()) {
143
if (TryLoadCheatInfo()) {
144
RecreateViews();
145
}
146
}
147
148
if (fileCheckCounter_++ >= FILE_CHECK_FRAME_INTERVAL && engine_) {
149
// Check if the file has changed. If it has, we'll reload.
150
std::string str;
151
if (File::ReadTextFileToString(engine_->CheatFilename(), &str)) {
152
uint64_t newHash = XXH3_64bits(str.c_str(), str.size());
153
if (newHash != fileCheckHash_) {
154
// This will update the hash.
155
RecreateViews();
156
}
157
}
158
fileCheckCounter_ = 0;
159
}
160
161
UIBaseDialogScreen::update();
162
}
163
164
void CwCheatScreen::onFinish(DialogResult result) {
165
if (result != DR_BACK) // This only works for BACK here.
166
return;
167
168
std::lock_guard<std::recursive_mutex> guard(MIPSComp::jitLock);
169
if (MIPSComp::jit) {
170
MIPSComp::jit->ClearCache();
171
}
172
}
173
174
void CwCheatScreen::OnDisableAll(UI::EventParams &params) {
175
// Disable all the switches.
176
for (auto &info : fileInfo_) {
177
info.enabled = false;
178
}
179
180
if (!RebuildCheatFile(INDEX_ALL)) {
181
// Probably the file was modified outside PPSSPP, refresh.
182
// TODO: Report error.
183
RecreateViews();
184
}
185
}
186
187
void CwCheatScreen::OnAddCheat(UI::EventParams &params) {
188
TriggerFinish(DR_OK);
189
g_Config.bReloadCheats = true;
190
}
191
192
void CwCheatScreen::OnEditCheatFile(UI::EventParams &params) {
193
g_Config.bReloadCheats = true;
194
std::lock_guard<std::recursive_mutex> guard(MIPSComp::jitLock);
195
if (MIPSComp::jit) {
196
MIPSComp::jit->ClearCache();
197
}
198
if (engine_) {
199
File::OpenFileInEditor(engine_->CheatFilename());
200
}
201
}
202
203
static char *GetLineNoNewline(char *temp, int sz, FILE *fp) {
204
char *line = fgets(temp, sz, fp);
205
if (!line)
206
return nullptr;
207
208
// If the last character is \n, just make it the terminator.
209
char *end = line + strlen(line) - 1;
210
if (*end == '\n')
211
*end = '\0';
212
return line;
213
}
214
215
void CwCheatScreen::OnImportBrowse(UI::EventParams &params) {
216
System_BrowseForFile(GetRequesterToken(), "Open cheat DB file", BrowseFileType::DB, [&](const std::string &value, int) {
217
Path path(value);
218
INFO_LOG(Log::System, "Attempting to load cheats from: '%s'", path.ToVisualString().c_str());
219
if (ImportCheats(path)) {
220
g_Config.bReloadCheats = true;
221
} else {
222
// Show an error message?
223
}
224
RecreateViews();
225
});
226
}
227
228
void CwCheatScreen::OnImportCheat(UI::EventParams &params) {
229
if (!ImportCheats(GetGlobalCheatFilePath())) {
230
// Show an error message?
231
errorMessageView_->SetVisibility(UI::V_VISIBLE);
232
}
233
234
g_Config.bReloadCheats = true;
235
RecreateViews();
236
}
237
238
bool CwCheatScreen::ImportCheats(const Path & cheatFile) {
239
if (gameID_.length() != 9 || !engine_) {
240
WARN_LOG(Log::Common, "CWCHEAT: Incorrect ID(%s) - can't import cheats.", gameID_.c_str());
241
return false;
242
}
243
244
std::string gameID = StringFromFormat("_S %s-%s", gameID_.substr(0, 4).c_str(), gameID_.substr(4).c_str());
245
246
FILE *in = File::OpenCFile(cheatFile, "rt");
247
if (!in) {
248
WARN_LOG(Log::Common, "Unable to open %s\n", cheatFile.c_str());
249
return false;
250
}
251
252
std::vector<std::string> title;
253
std::vector<std::string> newList;
254
255
char linebuf[2048]{};
256
bool parseGameEntry = false;
257
bool parseCheatEntry = false;
258
259
while (in && !feof(in)) {
260
char *line = GetLineNoNewline(linebuf, sizeof(linebuf), in);
261
262
if (!line) {
263
continue;
264
}
265
266
if (line[0] == '_' && line[1] == 'S') {
267
parseGameEntry = gameID == line;
268
parseCheatEntry = false;
269
} else if (parseGameEntry && line[0] == '_' && line[1] == 'C') {
270
// Test if cheat already exists.
271
parseCheatEntry = !HasCheatWithName(std::string(line).substr(4));
272
}
273
274
if (!parseGameEntry) {
275
if (newList.size() > 0) {
276
// Only parse the first matching game entry.
277
break;
278
} else {
279
// Haven't yet found a matching game entry, continue parsing.
280
continue;
281
}
282
}
283
284
if (line[0] == '_' && (line[1] == 'S' || line[1] == 'G') && title.size() < 2) {
285
title.push_back(line);
286
} else if (parseCheatEntry && ((line[0] == '_' && (line[1] == 'C' || line[1] == 'L')) || line[0] == '/' || line[0] == '#')) {
287
newList.push_back(line);
288
}
289
}
290
fclose(in);
291
292
std::string title2;
293
// Hmm, this probably gets confused about BOMs?
294
FILE *inTitle2 = File::OpenCFile(engine_->CheatFilename(), "rt");
295
if (inTitle2) {
296
char temp[2048];
297
char *line = GetLineNoNewline(temp, sizeof(temp), inTitle2);
298
if (line)
299
title2 = line;
300
fclose(inTitle2);
301
}
302
303
FILE *append = File::OpenCFile(engine_->CheatFilename(), "at");
304
if (!append)
305
return false;
306
307
if (title2.size() == 0 || title2[0] != '_' || title2[1] != 'S') {
308
for (int i = (int)title.size(); i > 0; i--) {
309
newList.insert(newList.begin(), title[i - 1]);
310
}
311
}
312
313
NOTICE_LOG(Log::Common, "Imported %u lines from %s.\n", (int)newList.size(), cheatFile.c_str());
314
if (newList.size() != 0) {
315
fputc('\n', append);
316
}
317
318
for (int i = 0; i < (int)newList.size(); i++) {
319
fprintf(append, "%s", newList[i].c_str());
320
if (i < (int)newList.size() - 1) {
321
fputc('\n', append);
322
}
323
}
324
fclose(append);
325
return true;
326
}
327
328
void CwCheatScreen::OnCheckBox(int index) {
329
if (!RebuildCheatFile(index)) {
330
// TODO: Report error. Let's reload the file, presumably it changed.
331
RecreateViews();
332
}
333
}
334
335
bool CwCheatScreen::HasCheatWithName(const std::string &name) {
336
for (const auto &existing : fileInfo_) {
337
if (name == existing.name) {
338
return true;
339
}
340
}
341
342
return false;
343
}
344
345
bool CwCheatScreen::RebuildCheatFile(int index) {
346
if (!engine_)
347
return false;
348
FILE *in = File::OpenCFile(engine_->CheatFilename(), "rt");
349
if (!in)
350
return false;
351
352
// In case lines were edited while we weren't looking, reload them.
353
std::vector<std::string> lines;
354
for (; !feof(in); ) {
355
char temp[2048];
356
char *line = GetLineNoNewline(temp, sizeof(temp), in);
357
if (!line)
358
break;
359
360
lines.push_back(line);
361
}
362
fclose(in);
363
364
auto updateLine = [&](const CheatFileInfo &info) {
365
// Line numbers start with one, not zero.
366
size_t lineIndex = info.lineNum - 1;
367
if (lines.size() > lineIndex) {
368
auto &line = lines[lineIndex];
369
// This is the one to change. Let's see if it matches - maybe the file changed.
370
bool isCheatDef = line.find("_C") != line.npos;
371
bool hasCheatName = !info.name.empty() && line.find(info.name) != line.npos;
372
if (!isCheatDef || !hasCheatName) {
373
return false;
374
}
375
376
line = (info.enabled ? "_C1 " : "_C0 ") + info.name;
377
return true;
378
}
379
return false;
380
};
381
382
if (index == INDEX_ALL) {
383
for (const auto &info : fileInfo_) {
384
// Bail out if any don't match with no changes.
385
if (!updateLine(info)) {
386
return false;
387
}
388
}
389
} else {
390
if (!updateLine(fileInfo_[index])) {
391
return false;
392
}
393
}
394
395
FILE *out = File::OpenCFile(engine_->CheatFilename(), "wt");
396
if (!out) {
397
return false;
398
}
399
400
for (size_t i = 0; i < lines.size(); ++i) {
401
fprintf(out, "%s", lines[i].c_str());
402
if (i != lines.size() - 1)
403
fputc('\n', out);
404
}
405
fclose(out);
406
407
// Cheats will need to be reparsed now.
408
g_Config.bReloadCheats = true;
409
return true;
410
}
411
412