dolphin/Source/Core/DolphinQt/Config/CheatCodeEditor.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

286 lines
7.8 KiB
C++
Raw Normal View History

// Copyright 2018 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
2018-07-07 00:40:15 +02:00
#include "DolphinQt/Config/CheatCodeEditor.h"
DolphinQt: Extract creator name from code name in Gecko codes. Gecko codes in Dolphin feature a dedicated field for the creator of the cheat code. When saved into the INI file, the code name and the creator name are concatenated, and then inserted in the `[Gecko]` section: ```ini [Gecko] $<cheat code name> [<creator>] <code line 1> <code line 2> <code line 3> <...> $<other cheat code name> [<creator>] <code line 1> <code line 2> <code line 3> <...> ``` On the other hand, enabled codes are listed under the `[Gecko_Enabled]` section, but in this case the creator name is omitted from the line: ```ini [Gecko_Enabled] $<cheat code name> $<other cheat code name> ``` Having the creator name in the `[Gecko]` section but not in the `[Gecko_Enabled]` section is arguably not ideal, but this is legacy behavior in Dolphin. The **Cheat Code Editor** dialog is not acknowledging this subtle behavior in Dolphin: the cheat code name and the creator name *can* be both inserted in the name field. This issue manifests as an inconsistent state where a Gecko code that *appears* to be enabled has no effect when the game is launched. As part of this fix, the creator name (if present) is now moved into the dedicated creator field before the code is stored internally. Test plan: - Right-click on any game and open the **Properties** dialog. - Switch to the **Gecko Codes** tab. - Press the **Add New Code...** button. - In the **Cheat Code Editor** dialog: - Enter `This is a test [Jane Doe]` in the **Name:** field. - Enter `01234567 00000000` in the **Code:** field. - Press **Save**. - Observe that the newly added code is now in the list, and *appears* to be enabled. - Close the **Properties** dialog. - Right-click on the same game and open the **Properties** dialog again. **Without** the fix, the newly added code, while still on the list, has been inadvertently disabled (it was never really enabled!). **With** the fix, the newly added code is the list and remains enabled. This fixes https://bugs.dolphin-emu.org/issues/13695.
2024-12-10 13:38:16 +00:00
#include <regex>
#include <QDialogButtonBox>
#include <QFontDatabase>
#include <QGridLayout>
#include <QLabel>
#include <QLineEdit>
#include <QStringList>
#include <QTextEdit>
#include "Common/StringUtil.h"
#include "Core/ARDecrypt.h"
#include "Core/ActionReplay.h"
#include "Core/GeckoCodeConfig.h"
2019-03-04 20:49:00 +01:00
#include "DolphinQt/QtUtils/ModalMessageBox.h"
2018-05-06 18:02:39 +02:00
CheatCodeEditor::CheatCodeEditor(QWidget* parent) : QDialog(parent)
{
2018-05-06 18:02:39 +02:00
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
setWindowTitle(tr("Cheat Code Editor"));
CreateWidgets();
ConnectWidgets();
}
void CheatCodeEditor::SetARCode(ActionReplay::ARCode* code)
{
m_name_edit->setText(QString::fromStdString(code->name));
m_code_edit->clear();
for (ActionReplay::AREntry& e : code->ops)
m_code_edit->append(QString::fromStdString(ActionReplay::SerializeLine(e)));
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));
m_code_edit->clear();
for (const auto& c : code->codes)
m_code_edit->append(QString::fromStdString(c.original_line));
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);
m_code_edit->setAcceptRichText(false);
m_notes_edit->setAcceptRichText(false);
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()
{
QString name = m_name_edit->text();
std::vector<ActionReplay::AREntry> entries;
std::vector<std::string> encrypted_lines;
QStringList lines = m_code_edit->toPlainText().split(QLatin1Char{'\n'});
for (int i = 0; i < lines.size(); i++)
{
QString line = lines[i];
if (line.isEmpty())
continue;
if (i == 0 && line[0] == u'$')
{
if (name.isEmpty())
name = line.right(line.size() - 1);
continue;
}
const auto parse_result = ActionReplay::DeserializeLine(line.toStdString());
if (std::holds_alternative<ActionReplay::AREntry>(parse_result))
{
entries.push_back(std::get<ActionReplay::AREntry>(parse_result));
}
else if (std::holds_alternative<ActionReplay::EncryptedLine>(parse_result))
{
encrypted_lines.emplace_back(std::get<ActionReplay::EncryptedLine>(parse_result));
}
else
{
2019-03-04 20:49:00 +01:00
auto result = ModalMessageBox::warning(
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 20:49:00 +01:00
auto result = ModalMessageBox::warning(
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 20:49:00 +01:00
ModalMessageBox::critical(this, tr("Error"),
tr("The resulting decrypted AR code doesn't contain any lines."));
return false;
}
m_ar_code->name = name.toStdString();
m_ar_code->ops = std::move(entries);
m_ar_code->user_defined = true;
return true;
}
bool CheatCodeEditor::AcceptGecko()
{
QString name = m_name_edit->text();
std::vector<Gecko::GeckoCode::Code> entries;
QStringList lines = m_code_edit->toPlainText().split(QLatin1Char{'\n'});
for (int i = 0; i < lines.size(); i++)
{
QString line = lines[i];
if (line.isEmpty())
continue;
if (i == 0 && line[0] == u'$')
{
if (name.isEmpty())
name = line.right(line.size() - 1);
continue;
}
if (std::optional<Gecko::GeckoCode::Code> c = Gecko::DeserializeLine(line.toStdString()))
{
entries.push_back(*c);
}
else
{
2019-03-04 20:49:00 +01: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);
if (result == QMessageBox::Abort)
return false;
}
}
if (entries.empty())
{
ModalMessageBox::critical(this, tr("Error"), tr("This Gecko code doesn't contain any lines."));
2018-04-27 22:45:40 +02:00
return false;
}
DolphinQt: Extract creator name from code name in Gecko codes. Gecko codes in Dolphin feature a dedicated field for the creator of the cheat code. When saved into the INI file, the code name and the creator name are concatenated, and then inserted in the `[Gecko]` section: ```ini [Gecko] $<cheat code name> [<creator>] <code line 1> <code line 2> <code line 3> <...> $<other cheat code name> [<creator>] <code line 1> <code line 2> <code line 3> <...> ``` On the other hand, enabled codes are listed under the `[Gecko_Enabled]` section, but in this case the creator name is omitted from the line: ```ini [Gecko_Enabled] $<cheat code name> $<other cheat code name> ``` Having the creator name in the `[Gecko]` section but not in the `[Gecko_Enabled]` section is arguably not ideal, but this is legacy behavior in Dolphin. The **Cheat Code Editor** dialog is not acknowledging this subtle behavior in Dolphin: the cheat code name and the creator name *can* be both inserted in the name field. This issue manifests as an inconsistent state where a Gecko code that *appears* to be enabled has no effect when the game is launched. As part of this fix, the creator name (if present) is now moved into the dedicated creator field before the code is stored internally. Test plan: - Right-click on any game and open the **Properties** dialog. - Switch to the **Gecko Codes** tab. - Press the **Add New Code...** button. - In the **Cheat Code Editor** dialog: - Enter `This is a test [Jane Doe]` in the **Name:** field. - Enter `01234567 00000000` in the **Code:** field. - Press **Save**. - Observe that the newly added code is now in the list, and *appears* to be enabled. - Close the **Properties** dialog. - Right-click on the same game and open the **Properties** dialog again. **Without** the fix, the newly added code, while still on the list, has been inadvertently disabled (it was never really enabled!). **With** the fix, the newly added code is the list and remains enabled. This fixes https://bugs.dolphin-emu.org/issues/13695.
2024-12-10 13:38:16 +00:00
m_gecko_code->name = name.trimmed().toStdString();
m_gecko_code->creator = m_creator_edit->text().trimmed().toStdString();
m_gecko_code->codes = std::move(entries);
m_gecko_code->notes = SplitString(m_notes_edit->toPlainText().toStdString(), '\n');
m_gecko_code->user_defined = true;
DolphinQt: Extract creator name from code name in Gecko codes. Gecko codes in Dolphin feature a dedicated field for the creator of the cheat code. When saved into the INI file, the code name and the creator name are concatenated, and then inserted in the `[Gecko]` section: ```ini [Gecko] $<cheat code name> [<creator>] <code line 1> <code line 2> <code line 3> <...> $<other cheat code name> [<creator>] <code line 1> <code line 2> <code line 3> <...> ``` On the other hand, enabled codes are listed under the `[Gecko_Enabled]` section, but in this case the creator name is omitted from the line: ```ini [Gecko_Enabled] $<cheat code name> $<other cheat code name> ``` Having the creator name in the `[Gecko]` section but not in the `[Gecko_Enabled]` section is arguably not ideal, but this is legacy behavior in Dolphin. The **Cheat Code Editor** dialog is not acknowledging this subtle behavior in Dolphin: the cheat code name and the creator name *can* be both inserted in the name field. This issue manifests as an inconsistent state where a Gecko code that *appears* to be enabled has no effect when the game is launched. As part of this fix, the creator name (if present) is now moved into the dedicated creator field before the code is stored internally. Test plan: - Right-click on any game and open the **Properties** dialog. - Switch to the **Gecko Codes** tab. - Press the **Add New Code...** button. - In the **Cheat Code Editor** dialog: - Enter `This is a test [Jane Doe]` in the **Name:** field. - Enter `01234567 00000000` in the **Code:** field. - Press **Save**. - Observe that the newly added code is now in the list, and *appears* to be enabled. - Close the **Properties** dialog. - Right-click on the same game and open the **Properties** dialog again. **Without** the fix, the newly added code, while still on the list, has been inadvertently disabled (it was never really enabled!). **With** the fix, the newly added code is the list and remains enabled. This fixes https://bugs.dolphin-emu.org/issues/13695.
2024-12-10 13:38:16 +00:00
{
// The creator name is not expected to be present in the cheat code name. It will be extracted
// and moved into its dedicated "creator" field.
std::smatch matches;
if (std::regex_match(m_gecko_code->name, matches, std::regex{R"(^(.*)\[(.*)\]$)"}))
{
m_gecko_code->name = StripWhitespace(matches[1].str());
if (m_gecko_code->creator.empty())
{
m_gecko_code->creator = StripWhitespace(matches[2].str());
}
}
}
return true;
}
void CheatCodeEditor::accept()
{
bool success = m_gecko_code != nullptr ? AcceptGecko() : AcceptAR();
if (success)
QDialog::accept();
}