1 // Copyright 2018 Citra Emulator Project
2 // Licensed under GPLv2 or any later version
3 // Refer to the license.txt file included.
4 
5 #include <QCheckBox>
6 #include <QMessageBox>
7 #include <QTableWidgetItem>
8 #include "citra_qt/cheats.h"
9 #include "core/cheats/cheat_base.h"
10 #include "core/cheats/cheats.h"
11 #include "core/cheats/gateway_cheat.h"
12 #include "core/core.h"
13 #include "core/hle/kernel/process.h"
14 #include "ui_cheats.h"
15 
CheatDialog(QWidget * parent)16 CheatDialog::CheatDialog(QWidget* parent)
17     : QDialog(parent), ui(std::make_unique<Ui::CheatDialog>()) {
18     // Setup gui control settings
19     ui->setupUi(this);
20     setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
21     ui->tableCheats->setColumnWidth(0, 30);
22     ui->tableCheats->setColumnWidth(2, 85);
23     ui->tableCheats->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed);
24     ui->tableCheats->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);
25     ui->tableCheats->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Fixed);
26     ui->lineName->setEnabled(false);
27     ui->textCode->setEnabled(false);
28     ui->textNotes->setEnabled(false);
29     const auto game_id = fmt::format(
30         "{:016X}", Core::System::GetInstance().Kernel().GetCurrentProcess()->codeset->program_id);
31     ui->labelTitle->setText(tr("Title ID: %1").arg(QString::fromStdString(game_id)));
32 
33     connect(ui->buttonClose, &QPushButton::clicked, this, &CheatDialog::OnCancel);
34     connect(ui->buttonAddCheat, &QPushButton::clicked, this, &CheatDialog::OnAddCheat);
35     connect(ui->tableCheats, &QTableWidget::cellClicked, this, &CheatDialog::OnRowSelected);
36     connect(ui->lineName, &QLineEdit::textEdited, this, &CheatDialog::OnTextEdited);
37     connect(ui->textNotes, &QPlainTextEdit::textChanged, this, &CheatDialog::OnTextEdited);
38     connect(ui->textCode, &QPlainTextEdit::textChanged, this, &CheatDialog::OnTextEdited);
39 
40     connect(ui->buttonSave, &QPushButton::clicked,
41             [this] { SaveCheat(ui->tableCheats->currentRow()); });
42     connect(ui->buttonDelete, &QPushButton::clicked, this, &CheatDialog::OnDeleteCheat);
43 
44     LoadCheats();
45 }
46 
47 CheatDialog::~CheatDialog() = default;
48 
LoadCheats()49 void CheatDialog::LoadCheats() {
50     cheats = Core::System::GetInstance().CheatEngine().GetCheats();
51 
52     ui->tableCheats->setRowCount(cheats.size());
53 
54     for (size_t i = 0; i < cheats.size(); i++) {
55         QCheckBox* enabled = new QCheckBox();
56         enabled->setChecked(cheats[i]->IsEnabled());
57         enabled->setStyleSheet(QStringLiteral("margin-left:7px;"));
58         ui->tableCheats->setItem(i, 0, new QTableWidgetItem());
59         ui->tableCheats->setCellWidget(i, 0, enabled);
60         ui->tableCheats->setItem(
61             i, 1, new QTableWidgetItem(QString::fromStdString(cheats[i]->GetName())));
62         ui->tableCheats->setItem(
63             i, 2, new QTableWidgetItem(QString::fromStdString(cheats[i]->GetType())));
64         enabled->setProperty("row", static_cast<int>(i));
65 
66         connect(enabled, &QCheckBox::stateChanged, this, &CheatDialog::OnCheckChanged);
67     }
68 }
69 
CheckSaveCheat()70 bool CheatDialog::CheckSaveCheat() {
71     auto answer = QMessageBox::warning(
72         this, tr("Cheats"), tr("Would you like to save the current cheat?"),
73         QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Cancel);
74 
75     if (answer == QMessageBox::Yes) {
76         return SaveCheat(last_row);
77     } else {
78         return answer != QMessageBox::Cancel;
79     }
80 }
81 
SaveCheat(int row)82 bool CheatDialog::SaveCheat(int row) {
83     if (ui->lineName->text().isEmpty()) {
84         QMessageBox::critical(this, tr("Save Cheat"), tr("Please enter a cheat name."));
85         return false;
86     }
87     if (ui->textCode->toPlainText().isEmpty()) {
88         QMessageBox::critical(this, tr("Save Cheat"), tr("Please enter the cheat code."));
89         return false;
90     }
91 
92     // Check if the cheat lines are valid
93     auto code_lines = ui->textCode->toPlainText().split(QLatin1Char{'\n'}, QString::SkipEmptyParts);
94     for (int i = 0; i < code_lines.size(); ++i) {
95         Cheats::GatewayCheat::CheatLine cheat_line(code_lines[i].toStdString());
96         if (cheat_line.valid)
97             continue;
98 
99         auto answer = QMessageBox::warning(
100             this, tr("Save Cheat"),
101             tr("Cheat code line %1 is not valid.\nWould you like to ignore the error and continue?")
102                 .arg(i + 1),
103             QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
104         if (answer == QMessageBox::No)
105             return false;
106     }
107 
108     auto cheat = std::make_shared<Cheats::GatewayCheat>(ui->lineName->text().toStdString(),
109                                                         ui->textCode->toPlainText().toStdString(),
110                                                         ui->textNotes->toPlainText().toStdString());
111 
112     if (newly_created) {
113         Core::System::GetInstance().CheatEngine().AddCheat(cheat);
114         newly_created = false;
115     } else {
116         Core::System::GetInstance().CheatEngine().UpdateCheat(row, cheat);
117     }
118     Core::System::GetInstance().CheatEngine().SaveCheatFile();
119 
120     int previous_row = ui->tableCheats->currentRow();
121     int previous_col = ui->tableCheats->currentColumn();
122     LoadCheats();
123     ui->tableCheats->setCurrentCell(previous_row, previous_col);
124 
125     edited = false;
126     ui->buttonSave->setEnabled(false);
127     ui->buttonAddCheat->setEnabled(true);
128     return true;
129 }
130 
closeEvent(QCloseEvent * event)131 void CheatDialog::closeEvent(QCloseEvent* event) {
132     if (edited && !CheckSaveCheat()) {
133         event->ignore();
134         return;
135     }
136     event->accept();
137 }
138 
OnCancel()139 void CheatDialog::OnCancel() {
140     close();
141 }
142 
OnRowSelected(int row,int column)143 void CheatDialog::OnRowSelected(int row, int column) {
144     if (row == last_row) {
145         return;
146     }
147     if (edited && !CheckSaveCheat()) {
148         ui->tableCheats->setCurrentCell(last_row, last_col);
149         return;
150     }
151     if (static_cast<std::size_t>(row) < cheats.size()) {
152         if (newly_created) {
153             // Remove the newly created dummy item
154             newly_created = false;
155             ui->tableCheats->setRowCount(ui->tableCheats->rowCount() - 1);
156         }
157 
158         const auto& current_cheat = cheats[row];
159         ui->lineName->setText(QString::fromStdString(current_cheat->GetName()));
160         ui->textNotes->setPlainText(QString::fromStdString(current_cheat->GetComments()));
161         ui->textCode->setPlainText(QString::fromStdString(current_cheat->GetCode()));
162     }
163 
164     edited = false;
165     ui->buttonSave->setEnabled(false);
166     ui->buttonDelete->setEnabled(true);
167     ui->buttonAddCheat->setEnabled(true);
168     ui->lineName->setEnabled(true);
169     ui->textCode->setEnabled(true);
170     ui->textNotes->setEnabled(true);
171 
172     last_row = row;
173     last_col = column;
174 }
175 
OnCheckChanged(int state)176 void CheatDialog::OnCheckChanged(int state) {
177     const QCheckBox* checkbox = qobject_cast<QCheckBox*>(sender());
178     int row = static_cast<int>(checkbox->property("row").toInt());
179     cheats[row]->SetEnabled(state);
180     Core::System::GetInstance().CheatEngine().SaveCheatFile();
181 }
182 
OnTextEdited()183 void CheatDialog::OnTextEdited() {
184     edited = true;
185     ui->buttonSave->setEnabled(true);
186 }
187 
OnDeleteCheat()188 void CheatDialog::OnDeleteCheat() {
189     if (newly_created) {
190         newly_created = false;
191     } else {
192         Core::System::GetInstance().CheatEngine().RemoveCheat(ui->tableCheats->currentRow());
193         Core::System::GetInstance().CheatEngine().SaveCheatFile();
194     }
195 
196     LoadCheats();
197     if (cheats.empty()) {
198         ui->lineName->clear();
199         ui->textCode->clear();
200         ui->textNotes->clear();
201         ui->lineName->setEnabled(false);
202         ui->textCode->setEnabled(false);
203         ui->textNotes->setEnabled(false);
204         ui->buttonDelete->setEnabled(false);
205         last_row = last_col = -1;
206     } else {
207         if (last_row >= ui->tableCheats->rowCount()) {
208             last_row = ui->tableCheats->rowCount() - 1;
209         }
210         ui->tableCheats->setCurrentCell(last_row, last_col);
211 
212         const auto& current_cheat = cheats[last_row];
213         ui->lineName->setText(QString::fromStdString(current_cheat->GetName()));
214         ui->textNotes->setPlainText(QString::fromStdString(current_cheat->GetComments()));
215         ui->textCode->setPlainText(QString::fromStdString(current_cheat->GetCode()));
216     }
217 
218     edited = false;
219     ui->buttonSave->setEnabled(false);
220     ui->buttonAddCheat->setEnabled(true);
221 }
222 
OnAddCheat()223 void CheatDialog::OnAddCheat() {
224     if (edited && !CheckSaveCheat()) {
225         return;
226     }
227 
228     int row = ui->tableCheats->rowCount();
229     ui->tableCheats->setRowCount(row + 1);
230     ui->tableCheats->setCurrentCell(row, 1);
231 
232     // create a dummy item
233     ui->tableCheats->setItem(row, 1, new QTableWidgetItem(tr("[new cheat]")));
234     ui->tableCheats->setItem(row, 2, new QTableWidgetItem(QString{}));
235     ui->lineName->clear();
236     ui->lineName->setPlaceholderText(tr("[new cheat]"));
237     ui->textCode->clear();
238     ui->textNotes->clear();
239     ui->lineName->setEnabled(true);
240     ui->textCode->setEnabled(true);
241     ui->textNotes->setEnabled(true);
242     ui->buttonSave->setEnabled(true);
243     ui->buttonDelete->setEnabled(true);
244     ui->buttonAddCheat->setEnabled(false);
245 
246     edited = false;
247     newly_created = true;
248     last_row = row;
249     last_col = 1;
250 }
251