Merge pull request #6650 from spycrab/qt_cheatmanager
Qt: Implement Cheats Manager
This commit is contained in:
commit
71c659ea29
|
@ -7,6 +7,7 @@ set(CMAKE_AUTOMOC ON)
|
|||
|
||||
add_executable(dolphin-emu-qt2
|
||||
AboutDialog.cpp
|
||||
CheatsManager.cpp
|
||||
FIFOPlayerWindow.cpp
|
||||
HotkeyScheduler.cpp
|
||||
Host.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();
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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."));
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue