From c09075209f15d3b48922c148fb8a6878239cd93f Mon Sep 17 00:00:00 2001 From: spycrab Date: Mon, 26 Mar 2018 04:17:47 +0200 Subject: [PATCH] Qt: Implement Cheats Manager --- Source/Core/DolphinQt2/CMakeLists.txt | 1 + Source/Core/DolphinQt2/CheatsManager.cpp | 633 ++++++++++++++++++ Source/Core/DolphinQt2/CheatsManager.h | 117 ++++ .../Core/DolphinQt2/Config/ARCodeWidget.cpp | 19 +- Source/Core/DolphinQt2/Config/ARCodeWidget.h | 5 +- .../DolphinQt2/Config/CheatWarningWidget.cpp | 9 +- .../DolphinQt2/Config/CheatWarningWidget.h | 3 +- .../DolphinQt2/Config/GeckoCodeWidget.cpp | 10 +- .../Core/DolphinQt2/Config/GeckoCodeWidget.h | 3 +- Source/Core/DolphinQt2/DolphinQt2.vcxproj | 3 + Source/Core/DolphinQt2/MainWindow.cpp | 8 + Source/Core/DolphinQt2/MainWindow.h | 3 + Source/Core/DolphinQt2/MenuBar.cpp | 10 + Source/Core/DolphinQt2/MenuBar.h | 2 + 14 files changed, 813 insertions(+), 13 deletions(-) create mode 100644 Source/Core/DolphinQt2/CheatsManager.cpp create mode 100644 Source/Core/DolphinQt2/CheatsManager.h diff --git a/Source/Core/DolphinQt2/CMakeLists.txt b/Source/Core/DolphinQt2/CMakeLists.txt index 7b885a1908ef..a59660babc34 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 000000000000..cb0956906f35 --- /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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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(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(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 +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(m_match_operation->currentIndex()); + + const int base = + (m_match_decimal->isChecked() ? 10 : (m_match_hexadecimal->isChecked() ? 16 : 8)); + + switch (static_cast(m_match_length->currentIndex())) + { + case DataType::Byte: + return Compare(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(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(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(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(i)); + value_item->setData(INDEX_ROLE, static_cast(i)); + + m_match_table->setItem(static_cast(i), 0, address_item); + m_match_table->setItem(static_cast(i), 1, value_item); + } + + m_watch_table->setRowCount(static_cast(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(i)); + name_item->setData(COLUMN_ROLE, 0); + address_item->setData(INDEX_ROLE, static_cast(i)); + address_item->setData(COLUMN_ROLE, 1); + value_item->setData(INDEX_ROLE, static_cast(i)); + value_item->setData(COLUMN_ROLE, 2); + lock_item->setData(INDEX_ROLE, static_cast(i)); + lock_item->setData(COLUMN_ROLE, 3); + value_item->setData(INDEX_ROLE, static_cast(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(i), 0, name_item); + m_watch_table->setItem(static_cast(i), 1, address_item); + m_watch_table->setItem(static_cast(i), 2, lock_item); + m_watch_table->setItem(static_cast(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 000000000000..8d4a5a12b47a --- /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 +#include + +#include +#include + +#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 m_results; + std::vector m_watch; + std::shared_ptr 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 9bb29338e253..76776c653986 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 8fda60976f2e..3f7617ff60bb 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 @@ class ARCodeWidget : public QWidget QPushButton* m_code_remove; std::vector m_ar_codes; + bool m_restart_required; }; diff --git a/Source/Core/DolphinQt2/Config/CheatWarningWidget.cpp b/Source/Core/DolphinQt2/Config/CheatWarningWidget.cpp index e2bd437a4c6b..590c0493e4c5 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 25e93e3b267e..bb9fd2f10a4f 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 @@ class CheatWarningWidget : public QWidget 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 1f93c22d3acc..4b65e46b4be8 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 cf885d0e25aa..8e0efcd698eb 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 @@ class GeckoCodeWidget : public QWidget QPushButton* m_remove_code; QPushButton* m_download_codes; std::vector m_gecko_codes; + bool m_restart_required; }; diff --git a/Source/Core/DolphinQt2/DolphinQt2.vcxproj b/Source/Core/DolphinQt2/DolphinQt2.vcxproj index 7a287effab5b..1d925a3950b1 100644 --- a/Source/Core/DolphinQt2/DolphinQt2.vcxproj +++ b/Source/Core/DolphinQt2/DolphinQt2.vcxproj @@ -60,6 +60,7 @@ + @@ -141,6 +142,7 @@ + @@ -205,6 +207,7 @@ + diff --git a/Source/Core/DolphinQt2/MainWindow.cpp b/Source/Core/DolphinQt2/MainWindow.cpp index e5f9806af9ce..f8590a747efb 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 aa660e2da95d..c654dfb538f4 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 @@ class MainWindow final : public QMainWindow void ShowNetPlaySetupDialog(); void ShowFIFOPlayer(); void ShowMemcardManager(); + void ShowCheatsManager(); void NetPlayInit(); bool NetPlayJoin(); @@ -188,4 +190,5 @@ class MainWindow final : public QMainWindow 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 c63e3cf4722f..96541345fc0b 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 153e4f7281b8..cb2678dca6d1 100644 --- a/Source/Core/DolphinQt2/MenuBar.h +++ b/Source/Core/DolphinQt2/MenuBar.h @@ -73,6 +73,7 @@ class MenuBar final : public QMenuBar void BootGameCubeIPL(DiscIO::Region region); void ShowFIFOPlayer(); void ShowAboutDialog(); + void ShowCheatsManager(); void ConnectWiiRemote(int id); // Options @@ -157,6 +158,7 @@ class MenuBar final : public QMenuBar 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;