summaryrefslogtreecommitdiff
path: root/Source/Core
diff options
context:
space:
mode:
authorspycrab <spycrab@users.noreply.github.com>2018-02-16 14:53:52 +0100
committerspycrab <spycrab@users.noreply.github.com>2018-02-28 21:16:57 +0100
commit4b54f6b1c76c4ee2079a9307075dbc7ead5fa8ea (patch)
tree5f4076ac7c5f7f5660360da2440066c4a07d7c35 /Source/Core
parent6a609e6e3c5ac4f832f4f03082185cb46fd682e4 (diff)
Qt/PropertiesDialog: Implement "Patches" tab
Diffstat (limited to 'Source/Core')
-rw-r--r--Source/Core/DolphinQt2/CMakeLists.txt2
-rw-r--r--Source/Core/DolphinQt2/Config/NewPatchDialog.cpp217
-rw-r--r--Source/Core/DolphinQt2/Config/NewPatchDialog.h45
-rw-r--r--Source/Core/DolphinQt2/Config/PatchesWidget.cpp175
-rw-r--r--Source/Core/DolphinQt2/Config/PatchesWidget.h45
-rw-r--r--Source/Core/DolphinQt2/Config/PropertiesDialog.cpp3
-rw-r--r--Source/Core/DolphinQt2/DolphinQt2.vcxproj6
7 files changed, 493 insertions, 0 deletions
diff --git a/Source/Core/DolphinQt2/CMakeLists.txt b/Source/Core/DolphinQt2/CMakeLists.txt
index 6dc9dd409e..24484b2b6a 100644
--- a/Source/Core/DolphinQt2/CMakeLists.txt
+++ b/Source/Core/DolphinQt2/CMakeLists.txt
@@ -71,6 +71,8 @@ set(SRCS
Config/Mapping/WiimoteEmuExtension.cpp
Config/Mapping/WiimoteEmuGeneral.cpp
Config/Mapping/WiimoteEmuMotionControl.cpp
+ Config/NewPatchDialog.cpp
+ Config/PatchesWidget.cpp
Config/PropertiesDialog.cpp
Config/SettingsWindow.cpp
Debugger/BreakpointWidget.cpp
diff --git a/Source/Core/DolphinQt2/Config/NewPatchDialog.cpp b/Source/Core/DolphinQt2/Config/NewPatchDialog.cpp
new file mode 100644
index 0000000000..f575366306
--- /dev/null
+++ b/Source/Core/DolphinQt2/Config/NewPatchDialog.cpp
@@ -0,0 +1,217 @@
+// Copyright 2018 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include "DolphinQt2/Config/NewPatchDialog.h"
+
+#include <QDialogButtonBox>
+#include <QGridLayout>
+#include <QGroupBox>
+#include <QLabel>
+#include <QLineEdit>
+#include <QMessageBox>
+#include <QPushButton>
+#include <QRadioButton>
+#include <QScrollArea>
+#include <QVBoxLayout>
+
+#include <iostream>
+
+NewPatchDialog::NewPatchDialog(PatchEngine::Patch& patch) : m_patch(patch)
+{
+ setWindowTitle(tr("Patch Editor"));
+ CreateWidgets();
+ ConnectWidgets();
+
+ for (size_t i = 0; i < m_patch.entries.size(); i++)
+ {
+ m_entry_layout->addWidget(CreateEntry(static_cast<int>(i)));
+ }
+
+ if (m_patch.entries.empty())
+ {
+ AddEntry();
+ m_patch.active = true;
+ }
+}
+
+void NewPatchDialog::CreateWidgets()
+{
+ m_name_edit = new QLineEdit;
+ m_name_edit->setPlaceholderText(tr("Patch name"));
+ m_name_edit->setText(QString::fromStdString(m_patch.name));
+
+ m_entry_widget = new QWidget;
+ m_entry_layout = new QVBoxLayout;
+
+ auto* scroll_area = new QScrollArea;
+ m_entry_widget->setLayout(m_entry_layout);
+ scroll_area->setWidget(m_entry_widget);
+ scroll_area->setWidgetResizable(true);
+
+ m_add_button = new QPushButton(tr("Add"));
+
+ m_button_box = new QDialogButtonBox(QDialogButtonBox::Save | QDialogButtonBox::Cancel);
+
+ auto* layout = new QGridLayout;
+
+ layout->addWidget(m_name_edit, 0, 0);
+ layout->addWidget(scroll_area, 1, 0);
+ layout->addWidget(m_add_button, 2, 0);
+ layout->addWidget(m_button_box, 3, 0);
+
+ setLayout(layout);
+}
+
+void NewPatchDialog::ConnectWidgets()
+{
+ connect(m_name_edit, static_cast<void (QLineEdit::*)(const QString&)>(&QLineEdit::textEdited),
+ [this](const QString& name) { m_patch.name = name.toStdString(); });
+
+ connect(m_add_button, &QPushButton::pressed, this, &NewPatchDialog::AddEntry);
+
+ connect(m_button_box, &QDialogButtonBox::accepted, this, &NewPatchDialog::accept);
+ connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
+}
+
+void NewPatchDialog::AddEntry()
+{
+ PatchEngine::PatchEntry entry;
+ entry.type = PatchEngine::PATCH_8BIT;
+ entry.address = entry.value = 0;
+
+ m_patch.entries.push_back(entry);
+
+ m_entry_layout->addWidget(CreateEntry(static_cast<int>(m_patch.entries.size() - 1)));
+}
+
+QGroupBox* NewPatchDialog::CreateEntry(int index)
+{
+ QGroupBox* box = new QGroupBox();
+
+ auto* type = new QGroupBox(tr("Type"));
+ auto* type_layout = new QHBoxLayout;
+ auto* remove = new QPushButton(tr("Remove"));
+
+ auto* byte = new QRadioButton(tr("byte"));
+ auto* word = new QRadioButton(tr("word"));
+ auto* dword = new QRadioButton(tr("dword"));
+
+ type_layout->addWidget(byte);
+ type_layout->addWidget(word);
+ type_layout->addWidget(dword);
+ type->setLayout(type_layout);
+
+ auto* offset = new QLineEdit;
+ auto* value = new QLineEdit;
+
+ m_edits.push_back(offset);
+ m_edits.push_back(value);
+
+ auto* layout = new QGridLayout;
+ layout->addWidget(type, 0, 0, 1, -1);
+ layout->addWidget(new QLabel(tr("Offset:")), 1, 0);
+ layout->addWidget(offset, 1, 1);
+ layout->addWidget(new QLabel(tr("Value:")), 2, 0);
+ layout->addWidget(value, 2, 1);
+ layout->addWidget(remove, 3, 0, 1, -1);
+ box->setLayout(layout);
+
+ connect(offset, static_cast<void (QLineEdit::*)(const QString&)>(&QLineEdit::textEdited),
+ [this, index, offset](const QString& text) {
+ bool okay = true;
+ m_patch.entries[index].address = text.toUInt(&okay, 16);
+
+ QFont font;
+ QPalette palette;
+
+ font.setBold(!okay);
+
+ if (!okay)
+ palette.setColor(QPalette::Text, Qt::red);
+
+ offset->setFont(font);
+ offset->setPalette(palette);
+ });
+
+ connect(value, static_cast<void (QLineEdit::*)(const QString&)>(&QLineEdit::textEdited),
+ [this, index, value](const QString& text) {
+ bool okay;
+ m_patch.entries[index].value = text.toUInt(&okay, 16);
+
+ QFont font;
+ QPalette palette;
+
+ font.setBold(!okay);
+
+ if (!okay)
+ palette.setColor(QPalette::Text, Qt::red);
+
+ value->setFont(font);
+ value->setPalette(palette);
+ });
+
+ connect(remove, &QPushButton::pressed, [this, box, index] {
+ if (m_patch.entries.size() > 1)
+ {
+ m_entry_layout->removeWidget(box);
+ m_patch.entries.erase(m_patch.entries.begin() + index);
+ m_id--;
+ }
+ });
+
+ connect(byte, &QRadioButton::toggled, [this, index](bool checked) {
+ if (checked)
+ m_patch.entries[index].type = PatchEngine::PATCH_8BIT;
+ });
+
+ connect(word, &QRadioButton::toggled, [this, index](bool checked) {
+ if (checked)
+ m_patch.entries[index].type = PatchEngine::PATCH_16BIT;
+ });
+
+ connect(dword, &QRadioButton::toggled, [this, index](bool checked) {
+ if (checked)
+ m_patch.entries[index].type = PatchEngine::PATCH_32BIT;
+ });
+
+ auto entry_type = m_patch.entries[index].type;
+
+ byte->setChecked(entry_type == PatchEngine::PATCH_8BIT);
+ word->setChecked(entry_type == PatchEngine::PATCH_16BIT);
+ dword->setChecked(entry_type == PatchEngine::PATCH_32BIT);
+
+ offset->setText(
+ QStringLiteral("%1").arg(m_patch.entries[index].address, 10, 16, QLatin1Char('0')));
+ value->setText(QStringLiteral("%1").arg(m_patch.entries[index].value, 10, 16, QLatin1Char('0')));
+
+ return box;
+}
+
+void NewPatchDialog::accept()
+{
+ if (m_name_edit->text().isEmpty())
+ {
+ QMessageBox::critical(this, tr("Error"), tr("You have to enter a name."));
+ return;
+ }
+
+ bool valid = true;
+
+ for (const auto* edit : m_edits)
+ {
+ edit->text().toUInt(&valid, 16);
+ if (!valid)
+ break;
+ }
+
+ if (!valid)
+ {
+ QMessageBox::critical(
+ this, tr("Error"),
+ tr("Some values you provided are invalid.\nPlease check the highlighted values."));
+ return;
+ }
+
+ QDialog::accept();
+}
diff --git a/Source/Core/DolphinQt2/Config/NewPatchDialog.h b/Source/Core/DolphinQt2/Config/NewPatchDialog.h
new file mode 100644
index 0000000000..6419545daf
--- /dev/null
+++ b/Source/Core/DolphinQt2/Config/NewPatchDialog.h
@@ -0,0 +1,45 @@
+// Copyright 2018 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <vector>
+
+#include <QDialog>
+#include <QWidget>
+
+#include "Core/PatchEngine.h"
+#include "DolphinQt2/GameList/GameFile.h"
+
+class QDialogButtonBox;
+class QGroupBox;
+class QLineEdit;
+class QVBoxLayout;
+class QPushButton;
+
+class NewPatchDialog : public QDialog
+{
+public:
+ explicit NewPatchDialog(PatchEngine::Patch& patch);
+
+private:
+ void CreateWidgets();
+ void ConnectWidgets();
+ void AddEntry();
+
+ void accept() override;
+
+ QGroupBox* CreateEntry(int index);
+
+ QLineEdit* m_name_edit;
+ QWidget* m_entry_widget;
+ QVBoxLayout* m_entry_layout;
+ QPushButton* m_add_button;
+ QDialogButtonBox* m_button_box;
+
+ std::vector<QLineEdit*> m_edits;
+
+ PatchEngine::Patch& m_patch;
+ int m_id = 0;
+};
diff --git a/Source/Core/DolphinQt2/Config/PatchesWidget.cpp b/Source/Core/DolphinQt2/Config/PatchesWidget.cpp
new file mode 100644
index 0000000000..b2e1c2c1b6
--- /dev/null
+++ b/Source/Core/DolphinQt2/Config/PatchesWidget.cpp
@@ -0,0 +1,175 @@
+// Copyright 2018 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include "DolphinQt2/Config/PatchesWidget.h"
+
+#include <QGridLayout>
+#include <QListWidget>
+#include <QPushButton>
+
+#include "Common/FileUtil.h"
+#include "Common/IniFile.h"
+#include "Common/StringUtil.h"
+#include "Core/ConfigManager.h"
+#include "DolphinQt2/Config/NewPatchDialog.h"
+
+PatchesWidget::PatchesWidget(const GameFile& game)
+ : m_game(game), m_game_id(game.GetGameID().toStdString()), m_game_revision(game.GetRevision())
+{
+ IniFile game_ini_local;
+ 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);
+
+ PatchEngine::LoadPatchSection("OnFrame", m_patches, game_ini_default, game_ini_local);
+
+ CreateWidgets();
+ ConnectWidgets();
+
+ Update();
+
+ UpdateActions();
+}
+
+void PatchesWidget::CreateWidgets()
+{
+ m_list = new QListWidget;
+ m_add_button = new QPushButton(tr("&Add..."));
+ m_edit_button = new QPushButton();
+ m_remove_button = new QPushButton(tr("&Remove"));
+
+ auto* layout = new QGridLayout;
+
+ layout->addWidget(m_list, 0, 0, 1, -1);
+ layout->addWidget(m_add_button, 1, 0);
+ layout->addWidget(m_edit_button, 1, 2);
+ layout->addWidget(m_remove_button, 1, 1);
+
+ setLayout(layout);
+}
+
+void PatchesWidget::ConnectWidgets()
+{
+ connect(m_list, &QListWidget::itemSelectionChanged, this, &PatchesWidget::UpdateActions);
+ connect(m_list, &QListWidget::itemChanged, this, &PatchesWidget::OnItemChanged);
+ connect(m_remove_button, &QPushButton::pressed, this, &PatchesWidget::OnRemove);
+ connect(m_add_button, &QPushButton::pressed, this, &PatchesWidget::OnAdd);
+ connect(m_edit_button, &QPushButton::pressed, this, &PatchesWidget::OnEdit);
+}
+
+void PatchesWidget::OnItemChanged(QListWidgetItem* item)
+{
+ m_patches[m_list->row(item)].active = (item->checkState() == Qt::Checked);
+ SavePatches();
+}
+
+void PatchesWidget::OnAdd()
+{
+ PatchEngine::Patch patch;
+ patch.user_defined = true;
+
+ if (NewPatchDialog(patch).exec())
+ {
+ m_patches.push_back(patch);
+ SavePatches();
+ Update();
+ }
+}
+
+void PatchesWidget::OnEdit()
+{
+ if (m_list->selectedItems().isEmpty())
+ return;
+
+ auto* item = m_list->selectedItems()[0];
+
+ auto patch = m_patches[m_list->row(item)];
+
+ if (!patch.user_defined)
+ patch.name += tr(" (Copy)").toStdString();
+
+ if (NewPatchDialog(patch).exec())
+ {
+ if (patch.user_defined)
+ {
+ m_patches[m_list->row(item)] = patch;
+ }
+ else
+ {
+ patch.user_defined = true;
+ m_patches.push_back(patch);
+ }
+
+ SavePatches();
+ Update();
+ }
+}
+
+void PatchesWidget::OnRemove()
+{
+ if (m_list->selectedItems().isEmpty())
+ return;
+
+ m_patches.erase(m_patches.begin() + m_list->row(m_list->selectedItems()[0]));
+ SavePatches();
+ Update();
+}
+
+void PatchesWidget::SavePatches()
+{
+ std::vector<std::string> lines;
+ std::vector<std::string> lines_enabled;
+
+ for (const auto& patch : m_patches)
+ {
+ if (patch.active)
+ lines_enabled.push_back("$" + patch.name);
+
+ if (!patch.user_defined)
+ continue;
+
+ lines.push_back("$" + patch.name);
+
+ for (const auto& entry : patch.entries)
+ {
+ lines.push_back(StringFromFormat("0x%08X:%s:0x%08X", entry.address,
+ PatchEngine::PatchTypeStrings[entry.type], entry.value));
+ }
+ }
+
+ IniFile game_ini_local;
+ game_ini_local.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + m_game_id + ".ini");
+
+ game_ini_local.SetLines("OnFrame_Enabled", lines_enabled);
+ game_ini_local.SetLines("OnFrame", lines);
+
+ game_ini_local.Save(File::GetUserPath(D_GAMESETTINGS_IDX) + m_game_id + ".ini");
+}
+
+void PatchesWidget::Update()
+{
+ m_list->clear();
+
+ for (const auto& patch : m_patches)
+ {
+ auto* item = new QListWidgetItem(QString::fromStdString(patch.name));
+ item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
+ item->setCheckState(patch.active ? Qt::Checked : Qt::Unchecked);
+ item->setData(Qt::UserRole, patch.user_defined);
+
+ m_list->addItem(item);
+ }
+}
+
+void PatchesWidget::UpdateActions()
+{
+ bool selected = !m_list->selectedItems().isEmpty();
+ auto* item = m_list->selectedItems()[0];
+
+ bool user_defined = selected ? item->data(Qt::UserRole).toBool() : true;
+
+ m_edit_button->setEnabled(selected);
+ m_edit_button->setText(user_defined ? tr("&Edit...") : tr("&Clone..."));
+ m_remove_button->setEnabled(selected && user_defined);
+}
diff --git a/Source/Core/DolphinQt2/Config/PatchesWidget.h b/Source/Core/DolphinQt2/Config/PatchesWidget.h
new file mode 100644
index 0000000000..86a0174607
--- /dev/null
+++ b/Source/Core/DolphinQt2/Config/PatchesWidget.h
@@ -0,0 +1,45 @@
+// Copyright 2018 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include <QWidget>
+
+#include "Core/PatchEngine.h"
+#include "DolphinQt2/GameList/GameFile.h"
+
+class QListWidget;
+class QListWidgetItem;
+class QPushButton;
+
+class PatchesWidget : public QWidget
+{
+public:
+ explicit PatchesWidget(const GameFile& game);
+
+private:
+ void CreateWidgets();
+ void ConnectWidgets();
+ void SavePatches();
+ void Update();
+ void UpdateActions();
+
+ void OnItemChanged(QListWidgetItem*);
+ void OnAdd();
+ void OnRemove();
+ void OnEdit();
+
+ QListWidget* m_list;
+ QPushButton* m_add_button;
+ QPushButton* m_edit_button;
+ QPushButton* m_remove_button;
+
+ std::vector<PatchEngine::Patch> m_patches;
+ const GameFile& m_game;
+ std::string m_game_id;
+ u16 m_game_revision;
+};
diff --git a/Source/Core/DolphinQt2/Config/PropertiesDialog.cpp b/Source/Core/DolphinQt2/Config/PropertiesDialog.cpp
index 72cf5a8ba8..36f2aef707 100644
--- a/Source/Core/DolphinQt2/Config/PropertiesDialog.cpp
+++ b/Source/Core/DolphinQt2/Config/PropertiesDialog.cpp
@@ -10,6 +10,7 @@
#include "DolphinQt2/Config/FilesystemWidget.h"
#include "DolphinQt2/Config/GeckoCodeWidget.h"
#include "DolphinQt2/Config/InfoWidget.h"
+#include "DolphinQt2/Config/PatchesWidget.h"
#include "DolphinQt2/Config/PropertiesDialog.h"
PropertiesDialog::PropertiesDialog(QWidget* parent, const GameFile& game) : QDialog(parent)
@@ -23,6 +24,7 @@ PropertiesDialog::PropertiesDialog(QWidget* parent, const GameFile& game) : QDia
ARCodeWidget* ar = new ARCodeWidget(game);
GeckoCodeWidget* gecko = new GeckoCodeWidget(game);
+ PatchesWidget* patches = new PatchesWidget(game);
connect(gecko, &GeckoCodeWidget::OpenGeneralSettings, this,
&PropertiesDialog::OpenGeneralSettings);
@@ -30,6 +32,7 @@ PropertiesDialog::PropertiesDialog(QWidget* parent, const GameFile& game) : QDia
connect(ar, &ARCodeWidget::OpenGeneralSettings, this, &PropertiesDialog::OpenGeneralSettings);
tab_widget->addTab(info, tr("Info"));
+ tab_widget->addTab(patches, tr("Patches"));
tab_widget->addTab(ar, tr("AR Codes"));
tab_widget->addTab(gecko, tr("Gecko Codes"));
diff --git a/Source/Core/DolphinQt2/DolphinQt2.vcxproj b/Source/Core/DolphinQt2/DolphinQt2.vcxproj
index cb8410c289..81a50e7d58 100644
--- a/Source/Core/DolphinQt2/DolphinQt2.vcxproj
+++ b/Source/Core/DolphinQt2/DolphinQt2.vcxproj
@@ -72,6 +72,7 @@
<QtMoc Include="Config\Mapping\MappingWindow.h" />
<QtMoc Include="Config\LogConfigWidget.h" />
<QtMoc Include="Config\LogWidget.h" />
+ <QtMoc Include="Config\NewPatchDialog.h" />
<QtMoc Include="Config\Graphics\AdvancedWidget.h" />
<QtMoc Include="Config\Graphics\EnhancementsWidget.h" />
<QtMoc Include="Config\Graphics\GeneralWidget.h" />
@@ -83,6 +84,7 @@
<QtMoc Include="Config\Graphics\HacksWidget.h" />
<QtMoc Include="Config\Graphics\SoftwareRendererWidget.h" />
<QtMoc Include="Config\InfoWidget.h" />
+ <QtMoc Include="Config\PatchesWidget.h" />
<QtMoc Include="Config\PropertiesDialog.h" />
<QtMoc Include="Config\SettingsWindow.h" />
<QtMoc Include="FIFOPlayerWindow.h" />
@@ -175,7 +177,9 @@
<ClCompile Include="$(QtMocOutPrefix)NetPlayDialog.cpp" />
<ClCompile Include="$(QtMocOutPrefix)NetPlaySetupDialog.cpp" />
<ClCompile Include="$(QtMocOutPrefix)NewBreakpointDialog.cpp" />
+ <ClCompile Include="$(QtMocOutPrefix)NewPatchDialog.cpp" />
<ClCompile Include="$(QtMocOutPrefix)PadMappingDialog.cpp" />
+ <ClCompile Include="$(QtMocOutPrefix)PatchesWidget.cpp" />
<ClCompile Include="$(QtMocOutPrefix)PropertiesDialog.cpp" />
<ClCompile Include="$(QtMocOutPrefix)DoubleClickEventFilter.cpp" />
<ClCompile Include="$(QtMocOutPrefix)RegisterWidget.cpp" />
@@ -226,6 +230,8 @@
<ClCompile Include="Config\Mapping\WiimoteEmuMotionControl.cpp" />
<ClCompile Include="Config\LogConfigWidget.cpp" />
<ClCompile Include="Config\LogWidget.cpp" />
+ <ClCompile Include="Config\NewPatchDialog.cpp" />
+ <ClCompile Include="Config\PatchesWidget.cpp" />
<ClCompile Include="Config\PropertiesDialog.cpp" />
<ClCompile Include="Config\SettingsWindow.cpp" />
<ClCompile Include="FIFOPlayerWindow.cpp" />