2018-01-24 12:35:13 +00:00
|
|
|
// Copyright 2018 Dolphin Emulator Project
|
2021-07-05 01:22:19 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
2018-01-24 12:35:13 +00:00
|
|
|
|
2018-07-06 22:40:15 +00:00
|
|
|
#include "DolphinQt/Config/CheatCodeEditor.h"
|
2018-01-24 12:35:13 +00:00
|
|
|
|
|
|
|
#include <QDialogButtonBox>
|
|
|
|
#include <QFontDatabase>
|
|
|
|
#include <QGridLayout>
|
|
|
|
#include <QLabel>
|
|
|
|
#include <QLineEdit>
|
|
|
|
#include <QStringList>
|
|
|
|
#include <QTextEdit>
|
|
|
|
|
2021-08-10 16:29:54 +00:00
|
|
|
#include "Common/StringUtil.h"
|
|
|
|
|
2018-01-24 12:35:13 +00:00
|
|
|
#include "Core/ARDecrypt.h"
|
|
|
|
#include "Core/ActionReplay.h"
|
|
|
|
#include "Core/GeckoCodeConfig.h"
|
|
|
|
|
2019-03-04 19:49:00 +00:00
|
|
|
#include "DolphinQt/QtUtils/ModalMessageBox.h"
|
|
|
|
|
2018-05-06 16:02:39 +00:00
|
|
|
CheatCodeEditor::CheatCodeEditor(QWidget* parent) : QDialog(parent)
|
2018-01-24 12:35:13 +00:00
|
|
|
{
|
2018-05-06 16:02:39 +00:00
|
|
|
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
|
|
|
setWindowTitle(tr("Cheat Code Editor"));
|
|
|
|
|
2018-01-24 12:35:13 +00:00
|
|
|
CreateWidgets();
|
|
|
|
ConnectWidgets();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CheatCodeEditor::SetARCode(ActionReplay::ARCode* code)
|
|
|
|
{
|
|
|
|
m_name_edit->setText(QString::fromStdString(code->name));
|
|
|
|
|
2021-08-05 17:05:12 +00:00
|
|
|
m_code_edit->clear();
|
2018-01-24 12:35:13 +00:00
|
|
|
|
|
|
|
for (ActionReplay::AREntry& e : code->ops)
|
2021-08-05 17:05:12 +00:00
|
|
|
m_code_edit->append(QString::fromStdString(ActionReplay::SerializeLine(e)));
|
2018-01-24 12:35:13 +00:00
|
|
|
|
|
|
|
m_creator_label->setHidden(true);
|
|
|
|
m_creator_edit->setHidden(true);
|
|
|
|
m_notes_label->setHidden(true);
|
|
|
|
m_notes_edit->setHidden(true);
|
|
|
|
|
|
|
|
m_ar_code = code;
|
|
|
|
m_gecko_code = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CheatCodeEditor::SetGeckoCode(Gecko::GeckoCode* code)
|
|
|
|
{
|
|
|
|
m_name_edit->setText(QString::fromStdString(code->name));
|
|
|
|
m_creator_edit->setText(QString::fromStdString(code->creator));
|
|
|
|
|
2021-08-05 17:05:12 +00:00
|
|
|
m_code_edit->clear();
|
2018-01-24 12:35:13 +00:00
|
|
|
|
|
|
|
for (const auto& c : code->codes)
|
2021-08-05 17:05:12 +00:00
|
|
|
m_code_edit->append(QString::fromStdString(c.original_line));
|
2018-01-24 12:35:13 +00:00
|
|
|
|
|
|
|
QString notes_string;
|
|
|
|
for (const auto& line : code->notes)
|
|
|
|
notes_string += QStringLiteral("%1\n").arg(QString::fromStdString(line));
|
|
|
|
|
|
|
|
m_notes_edit->setText(notes_string);
|
|
|
|
|
|
|
|
m_creator_label->setHidden(false);
|
|
|
|
m_creator_edit->setHidden(false);
|
|
|
|
m_notes_label->setHidden(false);
|
|
|
|
m_notes_edit->setHidden(false);
|
|
|
|
|
|
|
|
m_gecko_code = code;
|
|
|
|
m_ar_code = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CheatCodeEditor::CreateWidgets()
|
|
|
|
{
|
|
|
|
m_name_edit = new QLineEdit;
|
|
|
|
m_creator_edit = new QLineEdit;
|
|
|
|
m_notes_edit = new QTextEdit;
|
|
|
|
m_code_edit = new QTextEdit;
|
|
|
|
m_button_box = new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Save);
|
|
|
|
|
|
|
|
m_creator_label = new QLabel(tr("Creator:"));
|
|
|
|
m_notes_label = new QLabel(tr("Notes:"));
|
|
|
|
|
|
|
|
QGridLayout* grid_layout = new QGridLayout;
|
|
|
|
|
|
|
|
grid_layout->addWidget(new QLabel(tr("Name:")), 0, 0);
|
|
|
|
grid_layout->addWidget(m_name_edit, 0, 1);
|
|
|
|
grid_layout->addWidget(m_creator_label, 1, 0);
|
|
|
|
grid_layout->addWidget(m_creator_edit, 1, 1);
|
|
|
|
grid_layout->addWidget(m_notes_label, 2, 0);
|
|
|
|
grid_layout->addWidget(m_notes_edit, 2, 1);
|
|
|
|
grid_layout->addWidget(new QLabel(tr("Code:")), 3, 0);
|
|
|
|
grid_layout->addWidget(m_code_edit, 3, 1);
|
|
|
|
grid_layout->addWidget(m_button_box, 4, 1);
|
|
|
|
|
|
|
|
QFont monospace(QFontDatabase::systemFont(QFontDatabase::FixedFont).family());
|
|
|
|
|
|
|
|
m_code_edit->setFont(monospace);
|
|
|
|
|
2018-06-05 10:45:08 +00:00
|
|
|
m_code_edit->setAcceptRichText(false);
|
|
|
|
m_notes_edit->setAcceptRichText(false);
|
|
|
|
|
2018-01-24 12:35:13 +00:00
|
|
|
setLayout(grid_layout);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CheatCodeEditor::ConnectWidgets()
|
|
|
|
{
|
|
|
|
connect(m_button_box, &QDialogButtonBox::accepted, this, &CheatCodeEditor::accept);
|
|
|
|
connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CheatCodeEditor::AcceptAR()
|
|
|
|
{
|
2021-08-10 17:19:34 +00:00
|
|
|
QString name = m_name_edit->text();
|
|
|
|
|
2018-01-24 12:35:13 +00:00
|
|
|
std::vector<ActionReplay::AREntry> entries;
|
|
|
|
std::vector<std::string> encrypted_lines;
|
|
|
|
|
2019-07-30 11:57:06 +00:00
|
|
|
QStringList lines = m_code_edit->toPlainText().split(QLatin1Char{'\n'});
|
2018-01-24 12:35:13 +00:00
|
|
|
|
|
|
|
for (int i = 0; i < lines.size(); i++)
|
|
|
|
{
|
|
|
|
QString line = lines[i];
|
|
|
|
|
|
|
|
if (line.isEmpty())
|
|
|
|
continue;
|
2021-08-10 17:19:34 +00:00
|
|
|
if (i == 0 && line[0] == u'$')
|
|
|
|
{
|
|
|
|
if (name.isEmpty())
|
|
|
|
name = line.right(line.size() - 1);
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-08-05 17:05:12 +00:00
|
|
|
const auto parse_result = ActionReplay::DeserializeLine(line.toStdString());
|
2018-01-24 12:35:13 +00:00
|
|
|
|
2021-08-05 17:05:12 +00:00
|
|
|
if (std::holds_alternative<ActionReplay::AREntry>(parse_result))
|
2018-01-24 12:35:13 +00:00
|
|
|
{
|
2021-08-05 17:05:12 +00:00
|
|
|
entries.push_back(std::get<ActionReplay::AREntry>(parse_result));
|
2018-01-24 12:35:13 +00:00
|
|
|
}
|
2021-08-05 17:05:12 +00:00
|
|
|
else if (std::holds_alternative<ActionReplay::EncryptedLine>(parse_result))
|
2018-01-24 12:35:13 +00:00
|
|
|
{
|
2021-08-05 17:05:12 +00:00
|
|
|
encrypted_lines.emplace_back(std::get<ActionReplay::EncryptedLine>(parse_result));
|
2018-01-24 12:35:13 +00:00
|
|
|
}
|
2021-08-05 17:05:12 +00:00
|
|
|
else
|
2018-01-24 12:35:13 +00:00
|
|
|
{
|
2019-03-04 19:49:00 +00:00
|
|
|
auto result = ModalMessageBox::warning(
|
2018-01-24 12:35:13 +00:00
|
|
|
this, tr("Parsing Error"),
|
|
|
|
tr("Unable to parse line %1 of the entered AR code as a valid "
|
|
|
|
"encrypted or decrypted code. Make sure you typed it correctly.\n\n"
|
|
|
|
"Would you like to ignore this line and continue parsing?")
|
|
|
|
.arg(i + 1),
|
|
|
|
QMessageBox::Ok | QMessageBox::Abort);
|
|
|
|
|
|
|
|
if (result == QMessageBox::Abort)
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!encrypted_lines.empty())
|
|
|
|
{
|
|
|
|
if (!entries.empty())
|
|
|
|
{
|
2019-03-04 19:49:00 +00:00
|
|
|
auto result = ModalMessageBox::warning(
|
2018-01-24 12:35:13 +00:00
|
|
|
this, tr("Invalid Mixed Code"),
|
|
|
|
tr("This Action Replay code contains both encrypted and unencrypted lines; "
|
|
|
|
"you should check that you have entered it correctly.\n\n"
|
|
|
|
"Do you want to discard all unencrypted lines?"),
|
|
|
|
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
|
|
|
|
|
|
|
|
// YES = Discard the unencrypted lines then decrypt the encrypted ones instead.
|
|
|
|
// NO = Discard the encrypted lines, keep the unencrypted ones
|
|
|
|
// CANCEL = Stop and let the user go back to editing
|
|
|
|
switch (result)
|
|
|
|
{
|
|
|
|
case QMessageBox::Yes:
|
|
|
|
entries.clear();
|
|
|
|
break;
|
|
|
|
case QMessageBox::No:
|
|
|
|
encrypted_lines.clear();
|
|
|
|
break;
|
|
|
|
case QMessageBox::Cancel:
|
|
|
|
return false;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ActionReplay::DecryptARCode(encrypted_lines, &entries);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (entries.empty())
|
|
|
|
{
|
2019-03-04 19:49:00 +00:00
|
|
|
ModalMessageBox::critical(this, tr("Error"),
|
|
|
|
tr("The resulting decrypted AR code doesn't contain any lines."));
|
2018-01-24 12:35:13 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-08-10 17:19:34 +00:00
|
|
|
m_ar_code->name = name.toStdString();
|
2018-01-24 12:35:13 +00:00
|
|
|
m_ar_code->ops = std::move(entries);
|
|
|
|
m_ar_code->user_defined = true;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CheatCodeEditor::AcceptGecko()
|
|
|
|
{
|
2021-08-10 17:19:34 +00:00
|
|
|
QString name = m_name_edit->text();
|
|
|
|
|
2018-01-24 12:35:13 +00:00
|
|
|
std::vector<Gecko::GeckoCode::Code> entries;
|
|
|
|
|
2019-07-30 11:57:06 +00:00
|
|
|
QStringList lines = m_code_edit->toPlainText().split(QLatin1Char{'\n'});
|
2018-01-24 12:35:13 +00:00
|
|
|
|
|
|
|
for (int i = 0; i < lines.size(); i++)
|
|
|
|
{
|
|
|
|
QString line = lines[i];
|
|
|
|
|
|
|
|
if (line.isEmpty())
|
|
|
|
continue;
|
|
|
|
|
2021-08-10 17:19:34 +00:00
|
|
|
if (i == 0 && line[0] == u'$')
|
|
|
|
{
|
|
|
|
if (name.isEmpty())
|
|
|
|
name = line.right(line.size() - 1);
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-08-05 17:05:12 +00:00
|
|
|
if (std::optional<Gecko::GeckoCode::Code> c = Gecko::DeserializeLine(line.toStdString()))
|
|
|
|
{
|
|
|
|
entries.push_back(*c);
|
|
|
|
}
|
|
|
|
else
|
2018-01-24 12:35:13 +00:00
|
|
|
{
|
2019-03-04 19:49:00 +00:00
|
|
|
auto result = ModalMessageBox::warning(
|
|
|
|
this, tr("Parsing Error"),
|
|
|
|
tr("Unable to parse line %1 of the entered Gecko code as a valid "
|
|
|
|
"code. Make sure you typed it correctly.\n\n"
|
|
|
|
"Would you like to ignore this line and continue parsing?")
|
|
|
|
.arg(i + 1),
|
|
|
|
QMessageBox::Ok | QMessageBox::Abort);
|
2018-01-24 12:35:13 +00:00
|
|
|
|
|
|
|
if (result == QMessageBox::Abort)
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (entries.empty())
|
|
|
|
{
|
2021-08-10 16:29:54 +00:00
|
|
|
ModalMessageBox::critical(this, tr("Error"), tr("This Gecko code doesn't contain any lines."));
|
2018-04-27 20:45:40 +00:00
|
|
|
return false;
|
2018-01-24 12:35:13 +00:00
|
|
|
}
|
|
|
|
|
2021-08-10 17:19:34 +00:00
|
|
|
m_gecko_code->name = name.toStdString();
|
2018-01-24 12:35:13 +00:00
|
|
|
m_gecko_code->creator = m_creator_edit->text().toStdString();
|
|
|
|
m_gecko_code->codes = std::move(entries);
|
2021-08-10 16:29:54 +00:00
|
|
|
m_gecko_code->notes = SplitString(m_notes_edit->toPlainText().toStdString(), '\n');
|
2018-01-24 12:35:13 +00:00
|
|
|
m_gecko_code->user_defined = true;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CheatCodeEditor::accept()
|
|
|
|
{
|
|
|
|
bool success = m_gecko_code != nullptr ? AcceptGecko() : AcceptAR();
|
|
|
|
|
|
|
|
if (success)
|
|
|
|
QDialog::accept();
|
|
|
|
}
|