summaryrefslogtreecommitdiff
path: root/Source/Core
diff options
context:
space:
mode:
authorAnthony <Helios747@users.noreply.github.com>2018-04-19 21:47:54 +0100
committerGitHub <noreply@github.com>2018-04-19 21:47:54 +0100
commit71c659ea29ea3f054b1188dc2662373e44e389c7 (patch)
treeeeefe6d5af5548c44982a09bc91e18c90c0f7af2 /Source/Core
parent396204248daa3946e7db8f5a733b82f64d4a160d (diff)
parentc09075209f15d3b48922c148fb8a6878239cd93f (diff)
Merge pull request #6650 from spycrab/qt_cheatmanager
Qt: Implement Cheats Manager
Diffstat (limited to 'Source/Core')
-rw-r--r--Source/Core/DolphinQt2/CMakeLists.txt1
-rw-r--r--Source/Core/DolphinQt2/CheatsManager.cpp633
-rw-r--r--Source/Core/DolphinQt2/CheatsManager.h117
-rw-r--r--Source/Core/DolphinQt2/Config/ARCodeWidget.cpp19
-rw-r--r--Source/Core/DolphinQt2/Config/ARCodeWidget.h5
-rw-r--r--Source/Core/DolphinQt2/Config/CheatWarningWidget.cpp9
-rw-r--r--Source/Core/DolphinQt2/Config/CheatWarningWidget.h3
-rw-r--r--Source/Core/DolphinQt2/Config/GeckoCodeWidget.cpp10
-rw-r--r--Source/Core/DolphinQt2/Config/GeckoCodeWidget.h3
-rw-r--r--Source/Core/DolphinQt2/DolphinQt2.vcxproj3
-rw-r--r--Source/Core/DolphinQt2/MainWindow.cpp8
-rw-r--r--Source/Core/DolphinQt2/MainWindow.h3
-rw-r--r--Source/Core/DolphinQt2/MenuBar.cpp10
-rw-r--r--Source/Core/DolphinQt2/MenuBar.h2
14 files changed, 813 insertions, 13 deletions
diff --git a/Source/Core/DolphinQt2/CMakeLists.txt b/Source/Core/DolphinQt2/CMakeLists.txt
index 7b885a1908..a59660babc 100644
--- a/Source/Core/DolphinQt2/CMakeLists.txt
+++ b/Source/Core/DolphinQt2/CMakeLists.txt
@@ -7,6 +7,7 @@ set(CMAKE_AUTOMOC ON)
add_executable(dolphin-emu-qt2
AboutDialog.cpp
+ CheatsManager.cpp
FIFOPlayerWindow.cpp
HotkeyScheduler.cpp
Host.cpp
diff --git a/Source/Core/DolphinQt2/CheatsManager.cpp b/Source/Core/DolphinQt2/CheatsManager.cpp
new file mode 100644
index 0000000000..cb0956906f
--- /dev/null
+++ b/Source/Core/DolphinQt2/CheatsManager.cpp
@@ -0,0 +1,633 @@
+// Copyright 2018 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include "DolphinQt2/CheatsManager.h"
+
+#include <algorithm>
+#include <cstring>
+
+#include <QComboBox>
+#include <QDialogButtonBox>
+#include <QGroupBox>
+#include <QHeaderView>
+#include <QLabel>
+#include <QLineEdit>
+#include <QMenu>
+#include <QPushButton>
+#include <QRadioButton>
+#include <QSplitter>
+#include <QTabWidget>
+#include <QTableWidget>
+#include <QVBoxLayout>
+
+#include "Core/ConfigManager.h"
+#include "Core/Core.h"
+#include "Core/Debugger/PPCDebugInterface.h"
+#include "Core/HW/Memmap.h"
+#include "Core/PowerPC/PowerPC.h"
+#include "UICommon/GameFile.h"
+
+#include "DolphinQt2/Config/ARCodeWidget.h"
+#include "DolphinQt2/Config/GeckoCodeWidget.h"
+#include "DolphinQt2/GameList/GameListModel.h"
+#include "DolphinQt2/QtUtils/ActionHelper.h"
+#include "DolphinQt2/Settings.h"
+
+constexpr u32 MAX_RESULTS = 50;
+
+constexpr int INDEX_ROLE = Qt::UserRole;
+constexpr int COLUMN_ROLE = Qt::UserRole + 1;
+
+CheatsManager::CheatsManager(QWidget* parent) : QDialog(parent)
+{
+ setWindowTitle(tr("Cheats Manager"));
+
+ connect(&Settings::Instance(), &Settings::EmulationStateChanged, this,
+ &CheatsManager::OnStateChanged);
+
+ OnStateChanged(Core::GetState());
+
+ CreateWidgets();
+ ConnectWidgets();
+ Reset();
+ Update();
+}
+
+void CheatsManager::OnStateChanged(Core::State state)
+{
+ if (state != Core::State::Running && state != Core::State::Paused)
+ return;
+
+ auto* model = Settings::Instance().GetGameListModel();
+
+ for (int i = 0; i < model->rowCount(QModelIndex()); i++)
+ {
+ auto file = model->GetGameFile(i);
+
+ if (file->GetGameID() == SConfig::GetInstance().GetGameID())
+ {
+ m_game_file = file;
+ if (m_tab_widget->count() == 3)
+ {
+ m_tab_widget->removeTab(0);
+ m_tab_widget->removeTab(0);
+ }
+
+ if (m_tab_widget->count() == 1)
+ {
+ if (m_ar_code)
+ m_ar_code->deleteLater();
+
+ m_ar_code = new ARCodeWidget(*m_game_file, false);
+ m_tab_widget->insertTab(0, m_ar_code, tr("AR Code"));
+ m_tab_widget->insertTab(1, new GeckoCodeWidget(*m_game_file, false), tr("Gecko Codes"));
+ }
+ }
+ }
+}
+
+void CheatsManager::CreateWidgets()
+{
+ m_tab_widget = new QTabWidget;
+ m_button_box = new QDialogButtonBox(QDialogButtonBox::Close);
+
+ m_cheat_search = CreateCheatSearch();
+
+ m_tab_widget->addTab(m_cheat_search, tr("Cheat Search"));
+
+ auto* layout = new QVBoxLayout;
+ layout->addWidget(m_tab_widget);
+ layout->addWidget(m_button_box);
+
+ setLayout(layout);
+}
+
+void CheatsManager::ConnectWidgets()
+{
+ connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
+
+ connect(m_match_new, &QPushButton::pressed, this, &CheatsManager::NewSearch);
+ connect(m_match_next, &QPushButton::pressed, this, &CheatsManager::NextSearch);
+ connect(m_match_refresh, &QPushButton::pressed, this, &CheatsManager::Update);
+ connect(m_match_reset, &QPushButton::pressed, this, &CheatsManager::Reset);
+
+ m_match_table->setContextMenuPolicy(Qt::CustomContextMenu);
+ m_watch_table->setContextMenuPolicy(Qt::CustomContextMenu);
+
+ connect(m_match_table, &QTableWidget::customContextMenuRequested, this,
+ &CheatsManager::OnMatchContextMenu);
+ connect(m_watch_table, &QTableWidget::customContextMenuRequested, this,
+ &CheatsManager::OnWatchContextMenu);
+ connect(m_watch_table, &QTableWidget::itemChanged, this, &CheatsManager::OnWatchItemChanged);
+}
+
+void CheatsManager::OnWatchContextMenu()
+{
+ if (m_watch_table->selectedItems().isEmpty())
+ return;
+
+ QMenu* menu = new QMenu(this);
+
+ AddAction(menu, tr("Remove from Watch"), this, [this] {
+ auto* item = m_match_table->selectedItems()[0];
+
+ int index = item->data(INDEX_ROLE).toInt();
+
+ m_watch.erase(m_watch.begin() + index);
+
+ Update();
+ });
+
+ menu->addSeparator();
+
+ AddAction(menu, tr("Generate Action Replay Code"), this, &CheatsManager::GenerateARCode);
+
+ menu->exec(QCursor::pos());
+}
+
+void CheatsManager::OnMatchContextMenu()
+{
+ if (m_match_table->selectedItems().isEmpty())
+ return;
+
+ QMenu* menu = new QMenu(this);
+
+ AddAction(menu, tr("Add to Watch"), this, [this] {
+ auto* item = m_match_table->selectedItems()[0];
+
+ int index = item->data(INDEX_ROLE).toInt();
+
+ m_watch.push_back(m_results[index]);
+
+ Update();
+ });
+
+ menu->exec(QCursor::pos());
+}
+
+static ActionReplay::AREntry ResultToAREntry(Result result)
+{
+ u8 cmd;
+
+ switch (result.type)
+ {
+ case DataType::Byte:
+ cmd = 0x00;
+ break;
+ case DataType::Short:
+ cmd = 0x02;
+ break;
+ default:
+ case DataType::Int:
+ cmd = 0x04;
+ break;
+ }
+
+ u32 address = result.address & 0xffffff;
+
+ return ActionReplay::AREntry(cmd << 24 | address, result.locked_value);
+}
+
+void CheatsManager::GenerateARCode()
+{
+ if (!m_ar_code)
+ return;
+
+ auto* item = m_match_table->selectedItems()[0];
+
+ int index = item->data(INDEX_ROLE).toInt();
+ ActionReplay::ARCode ar_code;
+
+ ar_code.active = true;
+ ar_code.user_defined = true;
+ ar_code.name = tr("Generated by search (Address %1)")
+ .arg(m_watch[index].address, 8, 16, QLatin1Char('0'))
+ .toStdString();
+
+ ar_code.ops.push_back(ResultToAREntry(m_watch[index]));
+
+ m_ar_code->AddCode(ar_code);
+}
+
+void CheatsManager::OnWatchItemChanged(QTableWidgetItem* item)
+{
+ if (m_updating)
+ return;
+
+ int index = item->data(INDEX_ROLE).toInt();
+ int column = item->data(COLUMN_ROLE).toInt();
+
+ switch (column)
+ {
+ case 0:
+ m_watch[index].name = item->text();
+ break;
+ case 3:
+ m_watch[index].locked = item->checkState() == Qt::Checked;
+ break;
+ case 4:
+ {
+ const auto text = item->text();
+ u32 value = 0;
+
+ switch (static_cast<DataType>(m_match_length->currentIndex()))
+ {
+ case DataType::Byte:
+ value = text.toUShort(nullptr, 16) & 0xFF;
+ break;
+ case DataType::Short:
+ value = text.toUShort(nullptr, 16);
+ break;
+ case DataType::Int:
+ value = text.toUInt(nullptr, 16);
+ break;
+ case DataType::Float:
+ {
+ float f = text.toFloat();
+ std::memcpy(&value, &f, sizeof(float));
+ break;
+ }
+ default:
+ break;
+ }
+
+ m_watch[index].locked_value = value;
+ break;
+ }
+ }
+
+ Update();
+}
+
+QWidget* CheatsManager::CreateCheatSearch()
+{
+ m_match_table = new QTableWidget;
+ m_watch_table = new QTableWidget;
+
+ m_match_table->verticalHeader()->hide();
+ m_watch_table->verticalHeader()->hide();
+
+ m_match_table->setSelectionBehavior(QAbstractItemView::SelectRows);
+ m_watch_table->setSelectionBehavior(QAbstractItemView::SelectRows);
+
+ // Options
+ m_result_label = new QLabel;
+ m_match_length = new QComboBox;
+ m_match_operation = new QComboBox;
+ m_match_value = new QLineEdit;
+ m_match_new = new QPushButton(tr("New Search"));
+ m_match_next = new QPushButton(tr("Next Search"));
+ m_match_refresh = new QPushButton(tr("Refresh"));
+ m_match_reset = new QPushButton(tr("Reset"));
+
+ auto* options = new QWidget;
+ auto* layout = new QVBoxLayout;
+ options->setLayout(layout);
+
+ for (const auto& option : {tr("8-bit Integer"), tr("16-bit Integer"), tr("32-bit Integer"),
+ tr("Float"), tr("Double"), tr("String")})
+ {
+ m_match_length->addItem(option);
+ }
+
+ for (const auto& option : {tr("Equals to"), tr("Not equals to"), tr("Less than"),
+ tr("Less or equal to"), tr("More than"), tr("More or equal to")})
+ {
+ m_match_operation->addItem(option);
+ }
+
+ auto* group_box = new QGroupBox(tr("Type"));
+ auto* group_layout = new QHBoxLayout;
+ group_box->setLayout(group_layout);
+
+ m_match_decimal = new QRadioButton(tr("Decimal"));
+ m_match_hexadecimal = new QRadioButton(tr("Hexadecimal"));
+ m_match_octal = new QRadioButton(tr("Octal"));
+
+ group_layout->addWidget(m_match_decimal);
+ group_layout->addWidget(m_match_hexadecimal);
+ group_layout->addWidget(m_match_octal);
+
+ layout->addWidget(m_result_label);
+ layout->addWidget(m_match_length);
+ layout->addWidget(m_match_operation);
+ layout->addWidget(m_match_value);
+ layout->addWidget(group_box);
+ layout->addWidget(m_match_new);
+ layout->addWidget(m_match_next);
+ layout->addWidget(m_match_refresh);
+ layout->addWidget(m_match_reset);
+
+ // Splitters
+ m_option_splitter = new QSplitter(Qt::Horizontal);
+ m_table_splitter = new QSplitter(Qt::Vertical);
+
+ m_table_splitter->addWidget(m_match_table);
+ m_table_splitter->addWidget(m_watch_table);
+
+ m_option_splitter->addWidget(m_table_splitter);
+ m_option_splitter->addWidget(options);
+
+ return m_option_splitter;
+}
+
+size_t CheatsManager::GetTypeSize() const
+{
+ switch (static_cast<DataType>(m_match_length->currentIndex()))
+ {
+ case DataType::Byte:
+ return sizeof(u8);
+ case DataType::Short:
+ return sizeof(u16);
+ case DataType::Int:
+ return sizeof(u32);
+ case DataType::Float:
+ return sizeof(float);
+ case DataType::Double:
+ return sizeof(double);
+ default:
+ return m_match_value->text().toStdString().size();
+ }
+}
+
+template <typename T>
+static bool Compare(T mem_value, T value, CompareType op)
+{
+ switch (op)
+ {
+ case CompareType::Equal:
+ return mem_value == value;
+ case CompareType::NotEqual:
+ return mem_value != value;
+ case CompareType::Less:
+ return mem_value < value;
+ case CompareType::LessEqual:
+ return mem_value <= mem_value;
+ case CompareType::More:
+ return value > mem_value;
+ case CompareType::MoreEqual:
+ return value >= mem_value;
+ default:
+ return false;
+ }
+}
+
+bool CheatsManager::MatchesSearch(u32 addr) const
+{
+ const auto text = m_match_value->text();
+ const auto op = static_cast<CompareType>(m_match_operation->currentIndex());
+
+ const int base =
+ (m_match_decimal->isChecked() ? 10 : (m_match_hexadecimal->isChecked() ? 16 : 8));
+
+ switch (static_cast<DataType>(m_match_length->currentIndex()))
+ {
+ case DataType::Byte:
+ return Compare<u8>(PowerPC::HostRead_U8(addr), text.toUShort(nullptr, base) & 0xFF, op);
+ case DataType::Short:
+ return Compare(PowerPC::HostRead_U16(addr), text.toUShort(nullptr, base), op);
+ case DataType::Int:
+ return Compare(PowerPC::HostRead_U32(addr), text.toUInt(nullptr, base), op);
+ case DataType::Float:
+ return Compare(PowerPC::Read_F32(addr), text.toFloat(), op);
+ case DataType::Double:
+ return Compare(PowerPC::Read_F64(addr), text.toDouble(), op);
+ case DataType::String:
+ {
+ bool is_equal = std::equal(text.toUtf8().cbegin(), text.toUtf8().cend(),
+ reinterpret_cast<char*>(Memory::m_pRAM + addr - 0x80000000));
+
+ // String only supports equals and not equals comparisons because the other ones frankly don't
+ // make any sense here
+ switch (op)
+ {
+ case CompareType::Equal:
+ return is_equal;
+ case CompareType::NotEqual:
+ return !is_equal;
+ default:
+ return false;
+ }
+ }
+ }
+
+ return false;
+}
+
+void CheatsManager::NewSearch()
+{
+ m_results.clear();
+ const u32 base_address = 0x80000000;
+
+ if (!Memory::m_pRAM)
+ {
+ m_result_label->setText(tr("Memory Not Ready"));
+ return;
+ }
+
+ const auto prev_state = Core::GetState();
+ Core::SetState(Core::State::Paused);
+
+ for (u32 i = 0; i < Memory::REALRAM_SIZE - GetTypeSize(); i++)
+ {
+ if (PowerPC::HostIsRAMAddress(base_address + i) && MatchesSearch(base_address + i))
+ m_results.push_back(
+ {base_address + i, static_cast<DataType>(m_match_length->currentIndex())});
+ }
+
+ Core::SetState(prev_state);
+
+ m_match_next->setEnabled(true);
+
+ Update();
+}
+
+void CheatsManager::NextSearch()
+{
+ if (!Memory::m_pRAM)
+ {
+ m_result_label->setText(tr("Memory Not Ready"));
+ return;
+ }
+
+ const auto prev_state = Core::GetState();
+ Core::SetState(Core::State::Paused);
+
+ m_results.erase(std::remove_if(m_results.begin(), m_results.end(),
+ [this](Result r) {
+ return !PowerPC::HostIsRAMAddress(r.address) ||
+ !MatchesSearch(r.address);
+ }),
+ m_results.end());
+
+ Core::SetState(prev_state);
+
+ Update();
+}
+
+void CheatsManager::Update()
+{
+ m_match_table->clear();
+ m_watch_table->clear();
+ m_match_table->setColumnCount(2);
+ m_watch_table->setColumnCount(4);
+
+ m_match_table->setHorizontalHeaderLabels({tr("Address"), tr("Value")});
+ m_watch_table->setHorizontalHeaderLabels({tr("Name"), tr("Address"), tr("Lock"), tr("Value")});
+
+ if (m_results.size() > MAX_RESULTS)
+ {
+ m_result_label->setText(tr("Too many matches to display (%1)").arg(m_results.size()));
+ return;
+ }
+
+ m_updating = true;
+
+ m_result_label->setText(tr("%1 Match(es)").arg(m_results.size()));
+ m_match_table->setRowCount(static_cast<int>(m_results.size()));
+
+ for (size_t i = 0; i < m_results.size(); i++)
+ {
+ auto* address_item = new QTableWidgetItem(
+ QStringLiteral("%1").arg(m_results[i].address, 8, 16, QLatin1Char('0')));
+ auto* value_item = new QTableWidgetItem;
+
+ address_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
+ value_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
+
+ if (PowerPC::HostIsRAMAddress(m_results[i].address))
+ {
+ const auto prev_state = Core::GetState();
+ Core::SetState(Core::State::Paused);
+
+ switch (m_results[i].type)
+ {
+ case DataType::Byte:
+ value_item->setText(QStringLiteral("%1").arg(PowerPC::HostRead_U8(m_results[i].address), 2,
+ 16, QLatin1Char('0')));
+ break;
+ case DataType::Short:
+ value_item->setText(QStringLiteral("%1").arg(PowerPC::HostRead_U16(m_results[i].address), 4,
+ 16, QLatin1Char('0')));
+ break;
+ case DataType::Int:
+ value_item->setText(QStringLiteral("%1").arg(PowerPC::HostRead_U32(m_results[i].address), 8,
+ 16, QLatin1Char('0')));
+ break;
+ case DataType::Float:
+ value_item->setText(QString::number(PowerPC::Read_F32(m_results[i].address)));
+ break;
+ case DataType::Double:
+ value_item->setText(QString::number(PowerPC::Read_F64(m_results[i].address)));
+ break;
+ case DataType::String:
+ value_item->setText(tr("String Match"));
+ break;
+ }
+
+ Core::SetState(prev_state);
+ }
+ else
+ {
+ value_item->setText(QStringLiteral("---"));
+ }
+
+ address_item->setData(INDEX_ROLE, static_cast<int>(i));
+ value_item->setData(INDEX_ROLE, static_cast<int>(i));
+
+ m_match_table->setItem(static_cast<int>(i), 0, address_item);
+ m_match_table->setItem(static_cast<int>(i), 1, value_item);
+ }
+
+ m_watch_table->setRowCount(static_cast<int>(m_watch.size()));
+
+ for (size_t i = 0; i < m_watch.size(); i++)
+ {
+ auto* name_item = new QTableWidgetItem(m_watch[i].name);
+ auto* address_item =
+ new QTableWidgetItem(QStringLiteral("%1").arg(m_watch[i].address, 8, 16, QLatin1Char('0')));
+ auto* lock_item = new QTableWidgetItem;
+ auto* value_item = new QTableWidgetItem;
+
+ if (PowerPC::HostIsRAMAddress(m_watch[i].address))
+ {
+ const auto prev_state = Core::GetState();
+ Core::SetState(Core::State::Paused);
+
+ if (m_watch[i].locked)
+ {
+ PowerPC::debug_interface.Patch(m_watch[i].address, m_watch[i].locked_value);
+ }
+
+ switch (m_watch[i].type)
+ {
+ case DataType::Byte:
+ value_item->setText(QStringLiteral("%1").arg(PowerPC::HostRead_U8(m_watch[i].address), 2,
+ 16, QLatin1Char('0')));
+ break;
+ case DataType::Short:
+ value_item->setText(QStringLiteral("%1").arg(PowerPC::HostRead_U16(m_watch[i].address), 4,
+ 16, QLatin1Char('0')));
+ break;
+ case DataType::Int:
+ value_item->setText(QStringLiteral("%1").arg(PowerPC::HostRead_U32(m_watch[i].address), 8,
+ 16, QLatin1Char('0')));
+ break;
+ case DataType::Float:
+ value_item->setText(QString::number(PowerPC::Read_F32(m_watch[i].address)));
+ break;
+ case DataType::Double:
+ value_item->setText(QString::number(PowerPC::Read_F64(m_watch[i].address)));
+ break;
+ case DataType::String:
+ value_item->setText(tr("String Match"));
+ break;
+ }
+
+ Core::SetState(prev_state);
+ }
+ else
+ {
+ value_item->setText(QStringLiteral("---"));
+ }
+
+ name_item->setData(INDEX_ROLE, static_cast<int>(i));
+ name_item->setData(COLUMN_ROLE, 0);
+ address_item->setData(INDEX_ROLE, static_cast<int>(i));
+ address_item->setData(COLUMN_ROLE, 1);
+ value_item->setData(INDEX_ROLE, static_cast<int>(i));
+ value_item->setData(COLUMN_ROLE, 2);
+ lock_item->setData(INDEX_ROLE, static_cast<int>(i));
+ lock_item->setData(COLUMN_ROLE, 3);
+ value_item->setData(INDEX_ROLE, static_cast<int>(i));
+ value_item->setData(COLUMN_ROLE, 4);
+
+ name_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
+ address_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
+ lock_item->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsSelectable);
+ value_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
+
+ lock_item->setCheckState(m_watch[i].locked ? Qt::Checked : Qt::Unchecked);
+
+ m_watch_table->setItem(static_cast<int>(i), 0, name_item);
+ m_watch_table->setItem(static_cast<int>(i), 1, address_item);
+ m_watch_table->setItem(static_cast<int>(i), 2, lock_item);
+ m_watch_table->setItem(static_cast<int>(i), 3, value_item);
+ }
+
+ m_updating = false;
+}
+
+void CheatsManager::Reset()
+{
+ m_results.clear();
+ m_watch.clear();
+ m_match_next->setEnabled(false);
+ m_match_table->clear();
+ m_watch_table->clear();
+ m_match_decimal->setChecked(true);
+ m_result_label->setText(QStringLiteral(""));
+
+ Update();
+}
diff --git a/Source/Core/DolphinQt2/CheatsManager.h b/Source/Core/DolphinQt2/CheatsManager.h
new file mode 100644
index 0000000000..8d4a5a12b4
--- /dev/null
+++ b/Source/Core/DolphinQt2/CheatsManager.h
@@ -0,0 +1,117 @@
+// Copyright 2018 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <vector>
+
+#include <QDialog>
+#include <QString>
+
+#include "Common/CommonTypes.h"
+
+class ARCodeWidget;
+class QComboBox;
+class QDialogButtonBox;
+class QLineEdit;
+class QPushButton;
+class QRadioButton;
+class QSplitter;
+class QTabWidget;
+class QTableWidget;
+class QTableWidgetItem;
+class QLabel;
+
+namespace UICommon
+{
+class GameFile;
+}
+
+namespace Core
+{
+enum class State;
+}
+
+enum class CompareType : int
+{
+ Equal = 0,
+ NotEqual = 1,
+ Less = 2,
+ LessEqual = 3,
+ More = 4,
+ MoreEqual = 5
+};
+
+enum class DataType : int
+{
+ Byte = 0,
+ Short = 1,
+ Int = 2,
+ Float = 3,
+ Double = 4,
+ String = 5
+};
+
+struct Result
+{
+ u32 address;
+ DataType type;
+ QString name;
+ bool locked = false;
+ u32 locked_value;
+};
+
+class CheatsManager : public QDialog
+{
+ Q_OBJECT
+public:
+ explicit CheatsManager(QWidget* parent = nullptr);
+
+private:
+ QWidget* CreateCheatSearch();
+ void CreateWidgets();
+ void ConnectWidgets();
+ void OnStateChanged(Core::State state);
+
+ size_t GetTypeSize() const;
+ bool MatchesSearch(u32 addr) const;
+
+ void Reset();
+ void NewSearch();
+ void NextSearch();
+ void Update();
+ void GenerateARCode();
+
+ void OnWatchContextMenu();
+ void OnMatchContextMenu();
+ void OnWatchItemChanged(QTableWidgetItem* item);
+
+ std::vector<Result> m_results;
+ std::vector<Result> m_watch;
+ std::shared_ptr<const UICommon::GameFile> m_game_file;
+ QDialogButtonBox* m_button_box;
+ QTabWidget* m_tab_widget = nullptr;
+
+ QWidget* m_cheat_search;
+ ARCodeWidget* m_ar_code = nullptr;
+
+ QLabel* m_result_label;
+ QTableWidget* m_match_table;
+ QTableWidget* m_watch_table;
+ QSplitter* m_option_splitter;
+ QSplitter* m_table_splitter;
+ QComboBox* m_match_length;
+ QComboBox* m_match_operation;
+ QLineEdit* m_match_value;
+ QPushButton* m_match_new;
+ QPushButton* m_match_next;
+ QPushButton* m_match_refresh;
+ QPushButton* m_match_reset;
+
+ QRadioButton* m_match_decimal;
+ QRadioButton* m_match_hexadecimal;
+ QRadioButton* m_match_octal;
+ bool m_updating = false;
+};
diff --git a/Source/Core/DolphinQt2/Config/ARCodeWidget.cpp b/Source/Core/DolphinQt2/Config/ARCodeWidget.cpp
index 9bb29338e2..76776c6539 100644
--- a/Source/Core/DolphinQt2/Config/ARCodeWidget.cpp
+++ b/Source/Core/DolphinQt2/Config/ARCodeWidget.cpp
@@ -18,8 +18,9 @@
#include "DolphinQt2/Config/CheatWarningWidget.h"
#include "UICommon/GameFile.h"
-ARCodeWidget::ARCodeWidget(const UICommon::GameFile& game)
- : m_game(game), m_game_id(game.GetGameID()), m_game_revision(game.GetRevision())
+ARCodeWidget::ARCodeWidget(const UICommon::GameFile& game, bool restart_required)
+ : m_game(game), m_game_id(game.GetGameID()), m_game_revision(game.GetRevision()),
+ m_restart_required(restart_required)
{
CreateWidgets();
ConnectWidgets();
@@ -39,7 +40,7 @@ ARCodeWidget::ARCodeWidget(const UICommon::GameFile& game)
void ARCodeWidget::CreateWidgets()
{
- m_warning = new CheatWarningWidget(m_game_id);
+ m_warning = new CheatWarningWidget(m_game_id, m_restart_required);
m_code_list = new QListWidget;
m_code_add = new QPushButton(tr("&Add New Code..."));
m_code_edit = new QPushButton(tr("&Edit Code..."));
@@ -75,6 +76,10 @@ void ARCodeWidget::ConnectWidgets()
void ARCodeWidget::OnItemChanged(QListWidgetItem* item)
{
m_ar_codes[m_code_list->row(item)].active = (item->checkState() == Qt::Checked);
+
+ if (!m_restart_required)
+ ActionReplay::ApplyCodes(m_ar_codes);
+
SaveCodes();
}
@@ -119,6 +124,14 @@ void ARCodeWidget::SaveCodes()
game_ini_local.Save(File::GetUserPath(D_GAMESETTINGS_IDX) + m_game_id + ".ini");
}
+void ARCodeWidget::AddCode(ActionReplay::ARCode code)
+{
+ m_ar_codes.push_back(std::move(code));
+
+ UpdateList();
+ SaveCodes();
+}
+
void ARCodeWidget::OnCodeAddPressed()
{
ActionReplay::ARCode ar;
diff --git a/Source/Core/DolphinQt2/Config/ARCodeWidget.h b/Source/Core/DolphinQt2/Config/ARCodeWidget.h
index 8fda60976f..3f7617ff60 100644
--- a/Source/Core/DolphinQt2/Config/ARCodeWidget.h
+++ b/Source/Core/DolphinQt2/Config/ARCodeWidget.h
@@ -27,7 +27,9 @@ class ARCodeWidget : public QWidget
{
Q_OBJECT
public:
- explicit ARCodeWidget(const UICommon::GameFile& game);
+ explicit ARCodeWidget(const UICommon::GameFile& game, bool restart_required = true);
+
+ void AddCode(ActionReplay::ARCode code);
signals:
void OpenGeneralSettings();
@@ -56,4 +58,5 @@ private:
QPushButton* m_code_remove;
std::vector<ActionReplay::ARCode> m_ar_codes;
+ bool m_restart_required;
};
diff --git a/Source/Core/DolphinQt2/Config/CheatWarningWidget.cpp b/Source/Core/DolphinQt2/Config/CheatWarningWidget.cpp
index e2bd437a4c..590c0493e4 100644
--- a/Source/Core/DolphinQt2/Config/CheatWarningWidget.cpp
+++ b/Source/Core/DolphinQt2/Config/CheatWarningWidget.cpp
@@ -14,14 +14,15 @@
#include "Core/Core.h"
#include "DolphinQt2/Settings.h"
-CheatWarningWidget::CheatWarningWidget(const std::string& game_id) : m_game_id(game_id)
+CheatWarningWidget::CheatWarningWidget(const std::string& game_id, bool restart_required)
+ : m_game_id(game_id), m_restart_required(restart_required)
{
CreateWidgets();
ConnectWidgets();
- connect(&Settings::Instance(), &Settings::EnableCheatsChanged,
+ connect(&Settings::Instance(), &Settings::EnableCheatsChanged, this,
[this] { Update(Core::IsRunning()); });
- connect(&Settings::Instance(), &Settings::EmulationStateChanged,
+ connect(&Settings::Instance(), &Settings::EmulationStateChanged, this,
[this](Core::State state) { Update(state == Core::State::Running); });
Update(Core::IsRunning());
@@ -58,7 +59,7 @@ void CheatWarningWidget::Update(bool running)
bool hide_widget = true;
bool hide_config_button = true;
- if (running && SConfig::GetInstance().GetGameID() == m_game_id)
+ if (running && SConfig::GetInstance().GetGameID() == m_game_id && m_restart_required)
{
hide_widget = false;
m_text->setText(tr("Changing cheats will only take effect when the game is restarted."));
diff --git a/Source/Core/DolphinQt2/Config/CheatWarningWidget.h b/Source/Core/DolphinQt2/Config/CheatWarningWidget.h
index 25e93e3b26..bb9fd2f10a 100644
--- a/Source/Core/DolphinQt2/Config/CheatWarningWidget.h
+++ b/Source/Core/DolphinQt2/Config/CheatWarningWidget.h
@@ -15,7 +15,7 @@ class CheatWarningWidget : public QWidget
{
Q_OBJECT
public:
- explicit CheatWarningWidget(const std::string& game_id);
+ explicit CheatWarningWidget(const std::string& game_id, bool restart_required);
signals:
void OpenCheatEnableSettings();
@@ -29,4 +29,5 @@ private:
QLabel* m_text;
QPushButton* m_config_button;
const std::string m_game_id;
+ bool m_restart_required;
};
diff --git a/Source/Core/DolphinQt2/Config/GeckoCodeWidget.cpp b/Source/Core/DolphinQt2/Config/GeckoCodeWidget.cpp
index 1f93c22d3a..4b65e46b4b 100644
--- a/Source/Core/DolphinQt2/Config/GeckoCodeWidget.cpp
+++ b/Source/Core/DolphinQt2/Config/GeckoCodeWidget.cpp
@@ -22,8 +22,9 @@
#include "DolphinQt2/Config/CheatWarningWidget.h"
#include "UICommon/GameFile.h"
-GeckoCodeWidget::GeckoCodeWidget(const UICommon::GameFile& game)
- : m_game(game), m_game_id(game.GetGameID()), m_game_revision(game.GetRevision())
+GeckoCodeWidget::GeckoCodeWidget(const UICommon::GameFile& game, bool restart_required)
+ : m_game(game), m_game_id(game.GetGameID()), m_game_revision(game.GetRevision()),
+ m_restart_required(restart_required)
{
CreateWidgets();
ConnectWidgets();
@@ -42,7 +43,7 @@ GeckoCodeWidget::GeckoCodeWidget(const UICommon::GameFile& game)
void GeckoCodeWidget::CreateWidgets()
{
- m_warning = new CheatWarningWidget(m_game_id);
+ m_warning = new CheatWarningWidget(m_game_id, m_restart_required);
m_code_list = new QListWidget;
m_name_label = new QLabel;
m_creator_label = new QLabel;
@@ -155,6 +156,9 @@ void GeckoCodeWidget::OnItemChanged(QListWidgetItem* item)
{
m_gecko_codes[m_code_list->row(item)].enabled = (item->checkState() == Qt::Checked);
+ if (!m_restart_required)
+ Gecko::SetActiveCodes(m_gecko_codes);
+
SaveCodes();
}
diff --git a/Source/Core/DolphinQt2/Config/GeckoCodeWidget.h b/Source/Core/DolphinQt2/Config/GeckoCodeWidget.h
index cf885d0e25..8e0efcd698 100644
--- a/Source/Core/DolphinQt2/Config/GeckoCodeWidget.h
+++ b/Source/Core/DolphinQt2/Config/GeckoCodeWidget.h
@@ -28,7 +28,7 @@ class GeckoCodeWidget : public QWidget
{
Q_OBJECT
public:
- explicit GeckoCodeWidget(const UICommon::GameFile& game);
+ explicit GeckoCodeWidget(const UICommon::GameFile& game, bool restart_required = true);
signals:
void OpenGeneralSettings();
@@ -62,4 +62,5 @@ private:
QPushButton* m_remove_code;
QPushButton* m_download_codes;
std::vector<Gecko::GeckoCode> m_gecko_codes;
+ bool m_restart_required;
};
diff --git a/Source/Core/DolphinQt2/DolphinQt2.vcxproj b/Source/Core/DolphinQt2/DolphinQt2.vcxproj
index 7a287effab..1d925a3950 100644
--- a/Source/Core/DolphinQt2/DolphinQt2.vcxproj
+++ b/Source/Core/DolphinQt2/DolphinQt2.vcxproj
@@ -60,6 +60,7 @@
<!--NOTE: When adding moc'd files, you must list outputs in the ClCompile ItemGroup too!-->
<ItemGroup>
<QtMoc Include="AboutDialog.h" />
+ <QtMoc Include="CheatsManager.h" />
<QtMoc Include="Config\CheatCodeEditor.h" />
<QtMoc Include="Config\ARCodeWidget.h" />
<QtMoc Include="Config\CheatWarningWidget.h" />
@@ -141,6 +142,7 @@
<ClCompile Include="$(QtMocOutPrefix)AudioPane.cpp" />
<ClCompile Include="$(QtMocOutPrefix)AdvancedWidget.cpp" />
<ClCompile Include="$(QtMocOutPrefix)BreakpointWidget.cpp" />
+ <ClCompile Include="$(QtMocOutPrefix)CheatsManager.cpp" />
<ClCompile Include="$(QtMocOutPrefix)CheatWarningWidget.cpp" />
<ClCompile Include="$(QtMocOutPrefix)CodeViewWidget.cpp" />
<ClCompile Include="$(QtMocOutPrefix)CodeWidget.cpp" />
@@ -205,6 +207,7 @@
<ClCompile Include="$(QtMocOutPrefix)USBDeviceAddToWhitelistDialog.cpp" />
<ClCompile Include="$(QtMocOutPrefix)Updater.cpp" />
<ClCompile Include="AboutDialog.cpp" />
+ <ClCompile Include="CheatsManager.cpp" />
<ClCompile Include="Config\CheatCodeEditor.cpp" />
<ClCompile Include="Config\ARCodeWidget.cpp" />
<ClCompile Include="Config\CheatWarningWidget.cpp" />
diff --git a/Source/Core/DolphinQt2/MainWindow.cpp b/Source/Core/DolphinQt2/MainWindow.cpp
index e5f9806af9..f8590a747e 100644
--- a/Source/Core/DolphinQt2/MainWindow.cpp
+++ b/Source/Core/DolphinQt2/MainWindow.cpp
@@ -46,6 +46,7 @@
#include "DiscIO/NANDImporter.h"
#include "DolphinQt2/AboutDialog.h"
+#include "DolphinQt2/CheatsManager.h"
#include "DolphinQt2/Config/ControllersWindow.h"
#include "DolphinQt2/Config/Graphics/GraphicsWindow.h"
#include "DolphinQt2/Config/LogConfigWidget.h"
@@ -204,6 +205,7 @@ void MainWindow::CreateComponents()
m_watch_widget = new WatchWidget(this);
m_breakpoint_widget = new BreakpointWidget(this);
m_code_widget = new CodeWidget(this);
+ m_cheats_manager = new CheatsManager(this);
connect(m_watch_widget, &WatchWidget::RequestMemoryBreakpoint,
[this](u32 addr) { m_breakpoint_widget->AddAddressMBP(addr); });
@@ -275,6 +277,7 @@ void MainWindow::ConnectMenuBar()
// Tools
connect(m_menu_bar, &MenuBar::ShowMemcardManager, this, &MainWindow::ShowMemcardManager);
+ connect(m_menu_bar, &MenuBar::ShowCheatsManager, this, &MainWindow::ShowCheatsManager);
connect(m_menu_bar, &MenuBar::BootGameCubeIPL, this, &MainWindow::OnBootGameCubeIPL);
connect(m_menu_bar, &MenuBar::ImportNANDBackup, this, &MainWindow::OnImportNANDBackup);
connect(m_menu_bar, &MenuBar::PerformOnlineUpdate, this, &MainWindow::PerformOnlineUpdate);
@@ -1233,6 +1236,11 @@ void MainWindow::ShowMemcardManager()
manager.exec();
}
+void MainWindow::ShowCheatsManager()
+{
+ m_cheats_manager->show();
+}
+
void MainWindow::OnUpdateProgressDialog(QString title, int progress, int total)
{
if (!m_progress_dialog)
diff --git a/Source/Core/DolphinQt2/MainWindow.h b/Source/Core/DolphinQt2/MainWindow.h
index aa660e2da9..c654dfb538 100644
--- a/Source/Core/DolphinQt2/MainWindow.h
+++ b/Source/Core/DolphinQt2/MainWindow.h
@@ -22,6 +22,7 @@ class QProgressDialog;
class BreakpointWidget;
struct BootParameters;
+class CheatsManager;
class CodeWidget;
class ControllersWindow;
class DragEnterEvent;
@@ -121,6 +122,7 @@ private:
void ShowNetPlaySetupDialog();
void ShowFIFOPlayer();
void ShowMemcardManager();
+ void ShowCheatsManager();
void NetPlayInit();
bool NetPlayJoin();
@@ -188,4 +190,5 @@ private:
FIFOPlayerWindow* m_fifo_window;
RegisterWidget* m_register_widget;
WatchWidget* m_watch_widget;
+ CheatsManager* m_cheats_manager;
};
diff --git a/Source/Core/DolphinQt2/MenuBar.cpp b/Source/Core/DolphinQt2/MenuBar.cpp
index c63e3cf472..96541345fc 100644
--- a/Source/Core/DolphinQt2/MenuBar.cpp
+++ b/Source/Core/DolphinQt2/MenuBar.cpp
@@ -99,6 +99,9 @@ void MenuBar::OnEmulationStateChanged(Core::State state)
m_recording_stop->setEnabled(false);
m_recording_play->setEnabled(!running);
+ // Tools
+ m_show_cheat_manager->setEnabled(Settings::Instance().GetCheatsEnabled());
+
// Symbols
m_symbols->setEnabled(running);
@@ -167,6 +170,13 @@ void MenuBar::AddToolsMenu()
AddAction(tools_menu, tr("&Memory Card Manager (GC)"), this,
[this] { emit ShowMemcardManager(); });
+ m_show_cheat_manager =
+ AddAction(tools_menu, tr("&Cheats Manager"), this, [this] { emit ShowCheatsManager(); });
+
+ connect(&Settings::Instance(), &Settings::EnableCheatsChanged, [this](bool enabled) {
+ m_show_cheat_manager->setEnabled(Core::GetState() != Core::State::Uninitialized && enabled);
+ });
+
tools_menu->addSeparator();
AddAction(tools_menu, tr("Import Wii Save..."), this, &MenuBar::ImportWiiSave);
diff --git a/Source/Core/DolphinQt2/MenuBar.h b/Source/Core/DolphinQt2/MenuBar.h
index 153e4f7281..cb2678dca6 100644
--- a/Source/Core/DolphinQt2/MenuBar.h
+++ b/Source/Core/DolphinQt2/MenuBar.h
@@ -73,6 +73,7 @@ signals:
void BootGameCubeIPL(DiscIO::Region region);
void ShowFIFOPlayer();
void ShowAboutDialog();
+ void ShowCheatsManager();
void ConnectWiiRemote(int id);
// Options
@@ -157,6 +158,7 @@ private:
QMenu* m_backup_menu;
// Tools
+ QAction* m_show_cheat_manager;
QAction* m_wad_install_action;
QMenu* m_perform_online_update_menu;
QAction* m_perform_online_update_for_current_region;