From afa69a587696f1ccb3a1106743d9d6850c5a47a5 Mon Sep 17 00:00:00 2001 From: spycrab Date: Mon, 1 Jan 2018 20:01:58 +0000 Subject: [PATCH] Qt: Implement "AR Codes" --- Source/Core/DolphinQt2/CMakeLists.txt | 2 + .../Core/DolphinQt2/Config/ARCodeEditor.cpp | 170 +++++++++++++++++ Source/Core/DolphinQt2/Config/ARCodeEditor.h | 29 +++ .../Core/DolphinQt2/Config/ARCodeWidget.cpp | 177 ++++++++++++++++++ Source/Core/DolphinQt2/Config/ARCodeWidget.h | 55 ++++++ .../DolphinQt2/Config/PropertiesDialog.cpp | 5 + Source/Core/DolphinQt2/DolphinQt2.vcxproj | 6 + 7 files changed, 444 insertions(+) create mode 100644 Source/Core/DolphinQt2/Config/ARCodeEditor.cpp create mode 100644 Source/Core/DolphinQt2/Config/ARCodeEditor.h create mode 100644 Source/Core/DolphinQt2/Config/ARCodeWidget.cpp create mode 100644 Source/Core/DolphinQt2/Config/ARCodeWidget.h diff --git a/Source/Core/DolphinQt2/CMakeLists.txt b/Source/Core/DolphinQt2/CMakeLists.txt index ea8b9be430..6a2bf7d19e 100644 --- a/Source/Core/DolphinQt2/CMakeLists.txt +++ b/Source/Core/DolphinQt2/CMakeLists.txt @@ -32,6 +32,8 @@ set(SRCS Translation.cpp WiiUpdate.cpp WiiUpdate.h + Config/ARCodeEditor.cpp + Config/ARCodeWidget.cpp Config/CheatWarningWidget.cpp Config/ControllersWindow.cpp Config/FilesystemWidget.cpp diff --git a/Source/Core/DolphinQt2/Config/ARCodeEditor.cpp b/Source/Core/DolphinQt2/Config/ARCodeEditor.cpp new file mode 100644 index 0000000000..147974ff99 --- /dev/null +++ b/Source/Core/DolphinQt2/Config/ARCodeEditor.cpp @@ -0,0 +1,170 @@ +// Copyright 2018 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt2/Config/ARCodeEditor.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Core/ARDecrypt.h" + +ARCodeEditor::ARCodeEditor(ActionReplay::ARCode& ar) : m_code(ar) +{ + CreateWidgets(); + ConnectWidgets(); + + m_name_edit->setText(QString::fromStdString(ar.name)); + + QString s; + + for (ActionReplay::AREntry& e : ar.ops) + { + s += QStringLiteral("%1 %2\n") + .arg(e.cmd_addr, 8, 16, QLatin1Char('0')) + .arg(e.value, 8, 16, QLatin1Char('0')); + } + + m_code_edit->setText(s); +} + +void ARCodeEditor::CreateWidgets() +{ + m_name_edit = new QLineEdit; + m_code_edit = new QTextEdit; + m_button_box = new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Save); + + 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(new QLabel(tr("Code:")), 1, 0); + grid_layout->addWidget(m_code_edit, 1, 1); + grid_layout->addWidget(m_button_box, 2, 1); + + QFont monospace(QFontDatabase::systemFont(QFontDatabase::FixedFont).family()); + + m_code_edit->setFont(monospace); + + setLayout(grid_layout); +} + +void ARCodeEditor::ConnectWidgets() +{ + connect(m_button_box, &QDialogButtonBox::accepted, this, &ARCodeEditor::accept); + connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject); +} + +void ARCodeEditor::accept() +{ + std::vector entries; + std::vector encrypted_lines; + + QStringList lines = m_code_edit->toPlainText().split(QStringLiteral("\n")); + + for (int i = 0; i < lines.size(); i++) + { + QString line = lines[i]; + + if (line.isEmpty()) + continue; + + QStringList values = line.split(QStringLiteral(" ")); + + bool good = true; + + u32 addr = 0; + u32 value = 0; + + if (values.size() == 2) + { + addr = values[0].toUInt(&good, 16); + + if (good) + value = values[1].toUInt(&good, 16); + + if (good) + entries.push_back(ActionReplay::AREntry(addr, value)); + } + else + { + QStringList blocks = line.split(QStringLiteral("-")); + + if (blocks.size() == 3 && blocks[0].size() == 4 && blocks[1].size() == 4 && + blocks[2].size() == 4) + { + encrypted_lines.emplace_back(blocks[0].toStdString() + blocks[1].toStdString() + + blocks[2].toStdString()); + } + else + { + good = false; + } + } + + if (!good) + { + auto result = QMessageBox::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; + } + } + + if (!encrypted_lines.empty()) + { + if (!entries.empty()) + { + auto result = QMessageBox::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; + default: + break; + } + } + ActionReplay::DecryptARCode(encrypted_lines, &entries); + } + + if (entries.empty()) + { + QMessageBox::critical(this, tr("Error"), + tr("The resulting decrypted AR code doesn't contain any lines.")); + return; + } + + m_code.name = m_name_edit->text().toStdString(); + m_code.ops = std::move(entries); + m_code.active = true; + m_code.user_defined = true; + + QDialog::accept(); +} diff --git a/Source/Core/DolphinQt2/Config/ARCodeEditor.h b/Source/Core/DolphinQt2/Config/ARCodeEditor.h new file mode 100644 index 0000000000..bc8e2faf48 --- /dev/null +++ b/Source/Core/DolphinQt2/Config/ARCodeEditor.h @@ -0,0 +1,29 @@ +// Copyright 2018 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "Core/ActionReplay.h" + +#include + +class QDialogButtonBox; +class QLineEdit; +class QTextEdit; + +class ARCodeEditor : public QDialog +{ +public: + explicit ARCodeEditor(ActionReplay::ARCode& ar); + +private: + void CreateWidgets(); + void ConnectWidgets(); + + void accept() override; + + QLineEdit* m_name_edit; + QTextEdit* m_code_edit; + QDialogButtonBox* m_button_box; + + ActionReplay::ARCode& m_code; +}; diff --git a/Source/Core/DolphinQt2/Config/ARCodeWidget.cpp b/Source/Core/DolphinQt2/Config/ARCodeWidget.cpp new file mode 100644 index 0000000000..e47cee4dc5 --- /dev/null +++ b/Source/Core/DolphinQt2/Config/ARCodeWidget.cpp @@ -0,0 +1,177 @@ +// Copyright 2018 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt2/Config/ARCodeWidget.h" + +#include +#include +#include +#include +#include + +#include "Common/FileUtil.h" +#include "Common/IniFile.h" +#include "Core/ActionReplay.h" +#include "Core/ConfigManager.h" +#include "DolphinQt2/Config/ARCodeEditor.h" +#include "DolphinQt2/Config/CheatWarningWidget.h" +#include "DolphinQt2/GameList/GameFile.h" + +ARCodeWidget::ARCodeWidget(const GameFile& game) + : m_game(game), m_game_id(game.GetGameID().toStdString()), m_game_revision(game.GetRevision()) +{ + CreateWidgets(); + ConnectWidgets(); + + IniFile game_ini_local; + + // We don't use LoadLocalGameIni() here because user cheat codes that are installed via the UI + // will always be stored in GS/${GAMEID}.ini + game_ini_local.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + m_game_id + ".ini"); + + IniFile game_ini_default = SConfig::GetInstance().LoadDefaultGameIni(m_game_id, m_game_revision); + m_ar_codes = ActionReplay::LoadCodes(game_ini_default, game_ini_local); + + UpdateList(); + OnSelectionChanged(); +} + +void ARCodeWidget::CreateWidgets() +{ + m_warning = new CheatWarningWidget(m_game_id); + m_code_list = new QListWidget; + m_code_add = new QPushButton(tr("Add New Code...")); + m_code_edit = new QPushButton(tr("Edit Code...")); + m_code_remove = new QPushButton(tr("Remove Code")); + + auto* button_layout = new QHBoxLayout; + + button_layout->addWidget(m_code_add); + button_layout->addWidget(m_code_edit); + button_layout->addWidget(m_code_remove); + + QVBoxLayout* layout = new QVBoxLayout; + + layout->addWidget(m_warning); + layout->addWidget(m_code_list); + layout->addLayout(button_layout); + + setLayout(layout); +} + +void ARCodeWidget::ConnectWidgets() +{ + connect(m_warning, &CheatWarningWidget::OpenCheatEnableSettings, this, + &ARCodeWidget::OpenGeneralSettings); + connect(m_code_list, &QListWidget::itemChanged, this, &ARCodeWidget::OnItemChanged); + connect(m_code_list, &QListWidget::itemSelectionChanged, this, &ARCodeWidget::OnSelectionChanged); + + connect(m_code_add, &QPushButton::pressed, this, &ARCodeWidget::OnCodeAddPressed); + connect(m_code_edit, &QPushButton::pressed, this, &ARCodeWidget::OnCodeEditPressed); + connect(m_code_remove, &QPushButton::pressed, this, &ARCodeWidget::OnCodeRemovePressed); +} + +void ARCodeWidget::OnItemChanged(QListWidgetItem* item) +{ + m_ar_codes[m_code_list->row(item)].active = (item->checkState() == Qt::Checked); + SaveCodes(); +} + +void ARCodeWidget::OnSelectionChanged() +{ + auto items = m_code_list->selectedItems(); + + if (items.empty()) + return; + + const auto* selected = items[0]; + + bool user_defined = m_ar_codes[m_code_list->row(selected)].user_defined; + + m_code_remove->setEnabled(user_defined); + m_code_edit->setText(user_defined ? tr("Edit Code...") : tr("Clone and Edit Code...")); +} + +void ARCodeWidget::UpdateList() +{ + m_code_list->clear(); + + for (const auto& ar : m_ar_codes) + { + auto* item = new QListWidgetItem(QString::fromStdString(ar.name) + .replace(QStringLiteral("<"), QStringLiteral("<")) + .replace(QStringLiteral(">"), QStringLiteral(">"))); + + item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable); + item->setCheckState(ar.active ? Qt::Checked : Qt::Unchecked); + + m_code_list->addItem(item); + } +} + +void ARCodeWidget::SaveCodes() +{ + IniFile game_ini_local; + game_ini_local.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + m_game_id + ".ini"); + ActionReplay::SaveCodes(&game_ini_local, m_ar_codes); + + game_ini_local.Save(File::GetUserPath(D_GAMESETTINGS_IDX) + m_game_id + ".ini"); +} + +void ARCodeWidget::OnCodeAddPressed() +{ + ActionReplay::ARCode ar; + + ARCodeEditor ed(ar); + + ed.exec(); + + m_ar_codes.push_back(std::move(ar)); + + UpdateList(); + SaveCodes(); +} + +void ARCodeWidget::OnCodeEditPressed() +{ + auto items = m_code_list->selectedItems(); + + if (items.empty()) + return; + + const auto* selected = items[0]; + + auto& current_ar = m_ar_codes[m_code_list->row(selected)]; + + bool user_defined = current_ar.user_defined; + + ActionReplay::ARCode ar = current_ar; + + ARCodeEditor ed(user_defined ? current_ar : ar); + + ed.exec(); + + if (!user_defined) + m_ar_codes.push_back(ar); + + SaveCodes(); + UpdateList(); +} + +void ARCodeWidget::OnCodeRemovePressed() +{ + auto items = m_code_list->selectedItems(); + + if (items.empty()) + return; + + const auto* selected = items[0]; + + m_ar_codes.erase(m_ar_codes.begin() + m_code_list->row(selected)); + + SaveCodes(); + UpdateList(); + + m_code_remove->setEnabled(false); +} diff --git a/Source/Core/DolphinQt2/Config/ARCodeWidget.h b/Source/Core/DolphinQt2/Config/ARCodeWidget.h new file mode 100644 index 0000000000..a1326648c8 --- /dev/null +++ b/Source/Core/DolphinQt2/Config/ARCodeWidget.h @@ -0,0 +1,55 @@ +// Copyright 2018 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +#include +#include + +#include "Common/CommonTypes.h" +#include "Core/ActionReplay.h" + +class CheatWarningWidget; +class GameFile; +class QLabel; +class QListWidget; +class QListWidgetItem; +class QPushButton; + +class ARCodeWidget : public QWidget +{ + Q_OBJECT +public: + explicit ARCodeWidget(const GameFile& game); + +signals: + void OpenGeneralSettings(); + +private: + void OnSelectionChanged(); + void OnItemChanged(QListWidgetItem* item); + + void CreateWidgets(); + void ConnectWidgets(); + void UpdateList(); + void SaveCodes(); + + void OnCodeAddPressed(); + void OnCodeEditPressed(); + void OnCodeRemovePressed(); + + const GameFile& m_game; + std::string m_game_id; + u8 m_game_revision; + + CheatWarningWidget* m_warning; + QListWidget* m_code_list; + QPushButton* m_code_add; + QPushButton* m_code_edit; + QPushButton* m_code_remove; + + std::vector m_ar_codes; +}; diff --git a/Source/Core/DolphinQt2/Config/PropertiesDialog.cpp b/Source/Core/DolphinQt2/Config/PropertiesDialog.cpp index d8c464f07c..72cf5a8ba8 100644 --- a/Source/Core/DolphinQt2/Config/PropertiesDialog.cpp +++ b/Source/Core/DolphinQt2/Config/PropertiesDialog.cpp @@ -6,6 +6,7 @@ #include #include +#include "DolphinQt2/Config/ARCodeWidget.h" #include "DolphinQt2/Config/FilesystemWidget.h" #include "DolphinQt2/Config/GeckoCodeWidget.h" #include "DolphinQt2/Config/InfoWidget.h" @@ -20,12 +21,16 @@ PropertiesDialog::PropertiesDialog(QWidget* parent, const GameFile& game) : QDia QTabWidget* tab_widget = new QTabWidget(this); InfoWidget* info = new InfoWidget(game); + ARCodeWidget* ar = new ARCodeWidget(game); GeckoCodeWidget* gecko = new GeckoCodeWidget(game); connect(gecko, &GeckoCodeWidget::OpenGeneralSettings, this, &PropertiesDialog::OpenGeneralSettings); + connect(ar, &ARCodeWidget::OpenGeneralSettings, this, &PropertiesDialog::OpenGeneralSettings); + tab_widget->addTab(info, tr("Info")); + tab_widget->addTab(ar, tr("AR Codes")); tab_widget->addTab(gecko, tr("Gecko Codes")); if (DiscIO::IsDisc(game.GetPlatformID())) diff --git a/Source/Core/DolphinQt2/DolphinQt2.vcxproj b/Source/Core/DolphinQt2/DolphinQt2.vcxproj index 82d1a653bb..68a3981630 100644 --- a/Source/Core/DolphinQt2/DolphinQt2.vcxproj +++ b/Source/Core/DolphinQt2/DolphinQt2.vcxproj @@ -60,6 +60,8 @@ + + @@ -112,6 +114,8 @@ + + @@ -162,6 +166,8 @@ + +