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