1 // Copyright 2018 Dolphin Emulator Project
2 // Licensed under GPLv2+
3 // Refer to the license.txt file included.
4
5 #include "DolphinQt/Config/CheatCodeEditor.h"
6
7 #include <QDialogButtonBox>
8 #include <QFontDatabase>
9 #include <QGridLayout>
10 #include <QLabel>
11 #include <QLineEdit>
12 #include <QStringList>
13 #include <QTextEdit>
14
15 #include "Core/ARDecrypt.h"
16 #include "Core/ActionReplay.h"
17 #include "Core/GeckoCodeConfig.h"
18
19 #include "DolphinQt/QtUtils/ModalMessageBox.h"
20
CheatCodeEditor(QWidget * parent)21 CheatCodeEditor::CheatCodeEditor(QWidget* parent) : QDialog(parent)
22 {
23 setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
24 setWindowTitle(tr("Cheat Code Editor"));
25
26 CreateWidgets();
27 ConnectWidgets();
28 }
29
SetARCode(ActionReplay::ARCode * code)30 void CheatCodeEditor::SetARCode(ActionReplay::ARCode* code)
31 {
32 m_name_edit->setText(QString::fromStdString(code->name));
33
34 QString s;
35
36 for (ActionReplay::AREntry& e : code->ops)
37 {
38 s += QStringLiteral("%1 %2\n")
39 .arg(e.cmd_addr, 8, 16, QLatin1Char('0'))
40 .arg(e.value, 8, 16, QLatin1Char('0'));
41 }
42
43 m_code_edit->setText(s);
44
45 m_creator_label->setHidden(true);
46 m_creator_edit->setHidden(true);
47 m_notes_label->setHidden(true);
48 m_notes_edit->setHidden(true);
49
50 m_ar_code = code;
51 m_gecko_code = nullptr;
52 }
53
SetGeckoCode(Gecko::GeckoCode * code)54 void CheatCodeEditor::SetGeckoCode(Gecko::GeckoCode* code)
55 {
56 m_name_edit->setText(QString::fromStdString(code->name));
57 m_creator_edit->setText(QString::fromStdString(code->creator));
58
59 QString code_string;
60
61 for (const auto& c : code->codes)
62 code_string += QStringLiteral("%1 %2\n")
63 .arg(c.address, 8, 16, QLatin1Char('0'))
64 .arg(c.data, 8, 16, QLatin1Char('0'));
65
66 m_code_edit->setText(code_string);
67
68 QString notes_string;
69 for (const auto& line : code->notes)
70 notes_string += QStringLiteral("%1\n").arg(QString::fromStdString(line));
71
72 m_notes_edit->setText(notes_string);
73
74 m_creator_label->setHidden(false);
75 m_creator_edit->setHidden(false);
76 m_notes_label->setHidden(false);
77 m_notes_edit->setHidden(false);
78
79 m_gecko_code = code;
80 m_ar_code = nullptr;
81 }
82
CreateWidgets()83 void CheatCodeEditor::CreateWidgets()
84 {
85 m_name_edit = new QLineEdit;
86 m_creator_edit = new QLineEdit;
87 m_notes_edit = new QTextEdit;
88 m_code_edit = new QTextEdit;
89 m_button_box = new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Save);
90
91 m_creator_label = new QLabel(tr("Creator:"));
92 m_notes_label = new QLabel(tr("Notes:"));
93
94 QGridLayout* grid_layout = new QGridLayout;
95
96 grid_layout->addWidget(new QLabel(tr("Name:")), 0, 0);
97 grid_layout->addWidget(m_name_edit, 0, 1);
98 grid_layout->addWidget(m_creator_label, 1, 0);
99 grid_layout->addWidget(m_creator_edit, 1, 1);
100 grid_layout->addWidget(m_notes_label, 2, 0);
101 grid_layout->addWidget(m_notes_edit, 2, 1);
102 grid_layout->addWidget(new QLabel(tr("Code:")), 3, 0);
103 grid_layout->addWidget(m_code_edit, 3, 1);
104 grid_layout->addWidget(m_button_box, 4, 1);
105
106 QFont monospace(QFontDatabase::systemFont(QFontDatabase::FixedFont).family());
107
108 m_code_edit->setFont(monospace);
109
110 m_code_edit->setAcceptRichText(false);
111 m_notes_edit->setAcceptRichText(false);
112
113 setLayout(grid_layout);
114 }
115
ConnectWidgets()116 void CheatCodeEditor::ConnectWidgets()
117 {
118 connect(m_button_box, &QDialogButtonBox::accepted, this, &CheatCodeEditor::accept);
119 connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
120 }
121
AcceptAR()122 bool CheatCodeEditor::AcceptAR()
123 {
124 std::vector<ActionReplay::AREntry> entries;
125 std::vector<std::string> encrypted_lines;
126
127 QStringList lines = m_code_edit->toPlainText().split(QLatin1Char{'\n'});
128
129 for (int i = 0; i < lines.size(); i++)
130 {
131 QString line = lines[i];
132
133 if (line.isEmpty())
134 continue;
135
136 QStringList values = line.split(QLatin1Char{' '});
137
138 bool good = true;
139
140 u32 addr = 0;
141 u32 value = 0;
142
143 if (values.size() == 2)
144 {
145 addr = values[0].toUInt(&good, 16);
146
147 if (good)
148 value = values[1].toUInt(&good, 16);
149
150 if (good)
151 entries.push_back(ActionReplay::AREntry(addr, value));
152 }
153 else
154 {
155 QStringList blocks = line.split(QLatin1Char{'-'});
156
157 if (blocks.size() == 3 && blocks[0].size() == 4 && blocks[1].size() == 4 &&
158 blocks[2].size() == 5)
159 {
160 encrypted_lines.emplace_back(blocks[0].toStdString() + blocks[1].toStdString() +
161 blocks[2].toStdString());
162 }
163 else
164 {
165 good = false;
166 }
167 }
168
169 if (!good)
170 {
171 auto result = ModalMessageBox::warning(
172 this, tr("Parsing Error"),
173 tr("Unable to parse line %1 of the entered AR code as a valid "
174 "encrypted or decrypted code. Make sure you typed it correctly.\n\n"
175 "Would you like to ignore this line and continue parsing?")
176 .arg(i + 1),
177 QMessageBox::Ok | QMessageBox::Abort);
178
179 if (result == QMessageBox::Abort)
180 return false;
181 }
182 }
183
184 if (!encrypted_lines.empty())
185 {
186 if (!entries.empty())
187 {
188 auto result = ModalMessageBox::warning(
189 this, tr("Invalid Mixed Code"),
190 tr("This Action Replay code contains both encrypted and unencrypted lines; "
191 "you should check that you have entered it correctly.\n\n"
192 "Do you want to discard all unencrypted lines?"),
193 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
194
195 // YES = Discard the unencrypted lines then decrypt the encrypted ones instead.
196 // NO = Discard the encrypted lines, keep the unencrypted ones
197 // CANCEL = Stop and let the user go back to editing
198 switch (result)
199 {
200 case QMessageBox::Yes:
201 entries.clear();
202 break;
203 case QMessageBox::No:
204 encrypted_lines.clear();
205 break;
206 case QMessageBox::Cancel:
207 return false;
208 default:
209 break;
210 }
211 }
212 ActionReplay::DecryptARCode(encrypted_lines, &entries);
213 }
214
215 if (entries.empty())
216 {
217 ModalMessageBox::critical(this, tr("Error"),
218 tr("The resulting decrypted AR code doesn't contain any lines."));
219 return false;
220 }
221
222 m_ar_code->name = m_name_edit->text().toStdString();
223 m_ar_code->ops = std::move(entries);
224 m_ar_code->user_defined = true;
225
226 return true;
227 }
228
AcceptGecko()229 bool CheatCodeEditor::AcceptGecko()
230 {
231 std::vector<Gecko::GeckoCode::Code> entries;
232
233 QStringList lines = m_code_edit->toPlainText().split(QLatin1Char{'\n'});
234
235 for (int i = 0; i < lines.size(); i++)
236 {
237 QString line = lines[i];
238
239 if (line.isEmpty())
240 continue;
241
242 QStringList values = line.split(QLatin1Char{' '});
243
244 bool good = values.size() == 2;
245
246 u32 addr = 0;
247 u32 value = 0;
248
249 if (good)
250 addr = values[0].toUInt(&good, 16);
251
252 if (good)
253 value = values[1].toUInt(&good, 16);
254
255 if (!good)
256 {
257 auto result = ModalMessageBox::warning(
258 this, tr("Parsing Error"),
259 tr("Unable to parse line %1 of the entered Gecko code as a valid "
260 "code. Make sure you typed it correctly.\n\n"
261 "Would you like to ignore this line and continue parsing?")
262 .arg(i + 1),
263 QMessageBox::Ok | QMessageBox::Abort);
264
265 if (result == QMessageBox::Abort)
266 return false;
267 }
268 else
269 {
270 Gecko::GeckoCode::Code c;
271 c.address = addr;
272 c.data = value;
273 c.original_line = line.toStdString();
274
275 entries.push_back(c);
276 }
277 }
278
279 if (entries.empty())
280 {
281 ModalMessageBox::critical(this, tr("Error"),
282 tr("The resulting decrypted AR code doesn't contain any lines."));
283 return false;
284 }
285
286 m_gecko_code->name = m_name_edit->text().toStdString();
287 m_gecko_code->creator = m_creator_edit->text().toStdString();
288 m_gecko_code->codes = std::move(entries);
289 m_gecko_code->user_defined = true;
290
291 std::vector<std::string> note_lines;
292 for (const QString& line : m_notes_edit->toPlainText().split(QLatin1Char{'\n'}))
293 note_lines.push_back(line.toStdString());
294
295 m_gecko_code->notes = std::move(note_lines);
296
297 return true;
298 }
299
accept()300 void CheatCodeEditor::accept()
301 {
302 bool success = m_gecko_code != nullptr ? AcceptGecko() : AcceptAR();
303
304 if (success)
305 QDialog::accept();
306 }
307