summaryrefslogtreecommitdiff
path: root/Source
diff options
context:
space:
mode:
authorAnthony <Helios747@users.noreply.github.com>2018-01-23 12:23:54 -0800
committerGitHub <noreply@github.com>2018-01-23 12:23:54 -0800
commit440101a6bb8f95d1b6a3dcd42a188e2affe3e9e1 (patch)
tree76fe11feee333c1d5744a3d34f4542557c53c768 /Source
parentfca56d532a4da844c324d7a4ea05252e72d89fcf (diff)
parentafa69a587696f1ccb3a1106743d9d6850c5a47a5 (diff)
Merge pull request #6279 from spycrab/qt_arcodes
Qt: Implement "AR Codes"
Diffstat (limited to 'Source')
-rw-r--r--Source/Core/DolphinQt2/CMakeLists.txt2
-rw-r--r--Source/Core/DolphinQt2/Config/ARCodeEditor.cpp170
-rw-r--r--Source/Core/DolphinQt2/Config/ARCodeEditor.h29
-rw-r--r--Source/Core/DolphinQt2/Config/ARCodeWidget.cpp177
-rw-r--r--Source/Core/DolphinQt2/Config/ARCodeWidget.h55
-rw-r--r--Source/Core/DolphinQt2/Config/PropertiesDialog.cpp5
-rw-r--r--Source/Core/DolphinQt2/DolphinQt2.vcxproj6
7 files changed, 444 insertions, 0 deletions
diff --git a/Source/Core/DolphinQt2/CMakeLists.txt b/Source/Core/DolphinQt2/CMakeLists.txt
index 3ed0ffb1c8..13ebb17417 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 <QDialogButtonBox>
+#include <QFontDatabase>
+#include <QGridLayout>
+#include <QLabel>
+#include <QLineEdit>
+#include <QMessageBox>
+#include <QStringList>
+#include <QTextEdit>
+
+#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<ActionReplay::AREntry> entries;
+ std::vector<std::string> 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 <QDialog>
+
+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 <QButtonGroup>
+#include <QHBoxLayout>
+#include <QListWidget>
+#include <QPushButton>
+#include <QVBoxLayout>
+
+#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("&lt;"), QStringLiteral("<"))
+ .replace(QStringLiteral("&gt;"), 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 <QWidget>
+
+#include <string>
+#include <vector>
+
+#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<ActionReplay::ARCode> 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 <QTabWidget>
#include <QVBoxLayout>
+#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 dcdbf78281..3600aaf51d 100644
--- a/Source/Core/DolphinQt2/DolphinQt2.vcxproj
+++ b/Source/Core/DolphinQt2/DolphinQt2.vcxproj
@@ -60,6 +60,8 @@
<!--NOTE: When adding moc'd files, you must list outputs in the ClCompile ItemGroup too!-->
<ItemGroup>
<QtMoc Include="AboutDialog.h" />
+ <QtMoc Include="Config\ARCodeEditor.h" />
+ <QtMoc Include="Config\ARCodeWidget.h" />
<QtMoc Include="Config\CheatWarningWidget.h" />
<QtMoc Include="Config\ControllersWindow.h" />
<QtMoc Include="Config\FilesystemWidget.h" />
@@ -116,6 +118,8 @@
</ItemGroup>
<!--TODO figure out how to get QtMoc to add outputs to ClCompile's inputs...-->
<ItemGroup>
+ <ClCompile Include="$(QtMocOutPrefix)ARCodeEditor.cpp" />
+ <ClCompile Include="$(QtMocOutPrefix)ARCodeWidget.cpp" />
<ClCompile Include="$(QtMocOutPrefix)AboutDialog.cpp" />
<ClCompile Include="$(QtMocOutPrefix)AdvancedPane.cpp" />
<ClCompile Include="$(QtMocOutPrefix)AudioPane.cpp" />
@@ -170,6 +174,8 @@
<ClCompile Include="$(QtMocOutPrefix)ToolBar.cpp" />
<ClCompile Include="$(QtMocOutPrefix)WatchWidget.cpp" />
<ClCompile Include="AboutDialog.cpp" />
+ <ClCompile Include="Config\ARCodeEditor.cpp" />
+ <ClCompile Include="Config\ARCodeWidget.cpp" />
<ClCompile Include="Config\CheatWarningWidget.cpp" />
<ClCompile Include="Config\ControllersWindow.cpp" />
<ClCompile Include="Config\FilesystemWidget.cpp" />