2018-03-26 02:17:47 +00:00
|
|
|
// Copyright 2018 Dolphin Emulator Project
|
|
|
|
// Licensed under GPLv2+
|
|
|
|
// Refer to the license.txt file included.
|
|
|
|
|
2018-07-06 22:40:15 +00:00
|
|
|
#include "DolphinQt/CheatsManager.h"
|
2018-03-26 02:17:47 +00:00
|
|
|
|
|
|
|
#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>
|
|
|
|
|
2019-07-31 13:44:16 +00:00
|
|
|
#include "Core/ActionReplay.h"
|
2018-03-26 02:17:47 +00:00
|
|
|
#include "Core/ConfigManager.h"
|
|
|
|
#include "Core/Core.h"
|
|
|
|
#include "Core/Debugger/PPCDebugInterface.h"
|
|
|
|
#include "Core/HW/Memmap.h"
|
2018-05-17 21:09:55 +00:00
|
|
|
#include "Core/PowerPC/MMU.h"
|
2018-03-26 02:17:47 +00:00
|
|
|
#include "Core/PowerPC/PowerPC.h"
|
2018-05-28 01:48:04 +00:00
|
|
|
|
2018-03-26 02:17:47 +00:00
|
|
|
#include "UICommon/GameFile.h"
|
|
|
|
|
2018-07-06 22:40:15 +00:00
|
|
|
#include "DolphinQt/Config/ARCodeWidget.h"
|
|
|
|
#include "DolphinQt/Config/GeckoCodeWidget.h"
|
|
|
|
#include "DolphinQt/Settings.h"
|
2018-03-26 02:17:47 +00:00
|
|
|
|
|
|
|
constexpr u32 MAX_RESULTS = 50;
|
|
|
|
|
|
|
|
constexpr int INDEX_ROLE = Qt::UserRole;
|
|
|
|
constexpr int COLUMN_ROLE = Qt::UserRole + 1;
|
|
|
|
|
2019-04-22 12:09:47 +00:00
|
|
|
constexpr int AR_SET_BYTE_CMD = 0x00;
|
|
|
|
constexpr int AR_SET_SHORT_CMD = 0x02;
|
|
|
|
constexpr int AR_SET_INT_CMD = 0x04;
|
|
|
|
|
2018-07-10 18:29:17 +00:00
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
2019-04-21 16:00:36 +00:00
|
|
|
static u32 GetResultValue(Result result)
|
|
|
|
{
|
|
|
|
switch (result.type)
|
|
|
|
{
|
|
|
|
case DataType::Byte:
|
|
|
|
return PowerPC::HostRead_U8(result.address);
|
|
|
|
case DataType::Short:
|
|
|
|
return PowerPC::HostRead_U16(result.address);
|
|
|
|
case DataType::Int:
|
|
|
|
return PowerPC::HostRead_U32(result.address);
|
|
|
|
default:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void UpdatePatch(Result result)
|
|
|
|
{
|
|
|
|
PowerPC::debug_interface.UnsetPatch(result.address);
|
|
|
|
if (result.locked)
|
|
|
|
{
|
|
|
|
switch (result.type)
|
|
|
|
{
|
|
|
|
case DataType::Byte:
|
|
|
|
PowerPC::debug_interface.SetPatch(result.address,
|
|
|
|
std::vector<u8>{static_cast<u8>(result.locked_value)});
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
PowerPC::debug_interface.SetPatch(result.address, result.locked_value);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static ActionReplay::AREntry ResultToAREntry(Result result)
|
|
|
|
{
|
|
|
|
u8 cmd;
|
|
|
|
|
|
|
|
switch (result.type)
|
|
|
|
{
|
|
|
|
case DataType::Byte:
|
2019-04-22 12:09:47 +00:00
|
|
|
cmd = AR_SET_BYTE_CMD;
|
2019-04-21 16:00:36 +00:00
|
|
|
break;
|
|
|
|
case DataType::Short:
|
2019-04-22 12:09:47 +00:00
|
|
|
cmd = AR_SET_SHORT_CMD;
|
2019-04-21 16:00:36 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
case DataType::Int:
|
2019-04-22 12:09:47 +00:00
|
|
|
cmd = AR_SET_INT_CMD;
|
2019-04-21 16:00:36 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
u32 address = result.address & 0xffffff;
|
|
|
|
|
|
|
|
return ActionReplay::AREntry(cmd << 24 | address, result.locked_value);
|
|
|
|
}
|
|
|
|
|
|
|
|
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 <= value;
|
|
|
|
case CompareType::More:
|
|
|
|
return mem_value > value;
|
|
|
|
case CompareType::MoreEqual:
|
|
|
|
return mem_value >= value;
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-17 22:28:07 +00:00
|
|
|
CheatsManager::CheatsManager(const GameListModel& game_list_model, QWidget* parent)
|
|
|
|
: QDialog(parent), m_game_list_model(game_list_model)
|
2018-03-26 02:17:47 +00:00
|
|
|
{
|
|
|
|
setWindowTitle(tr("Cheats Manager"));
|
2018-05-05 00:29:16 +00:00
|
|
|
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
2018-03-26 02:17:47 +00:00
|
|
|
|
|
|
|
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this,
|
|
|
|
&CheatsManager::OnStateChanged);
|
|
|
|
|
|
|
|
OnStateChanged(Core::GetState());
|
|
|
|
|
|
|
|
CreateWidgets();
|
|
|
|
ConnectWidgets();
|
|
|
|
Reset();
|
|
|
|
Update();
|
|
|
|
}
|
|
|
|
|
2018-07-10 18:29:17 +00:00
|
|
|
CheatsManager::~CheatsManager() = default;
|
|
|
|
|
2018-03-26 02:17:47 +00:00
|
|
|
void CheatsManager::OnStateChanged(Core::State state)
|
|
|
|
{
|
|
|
|
if (state != Core::State::Running && state != Core::State::Paused)
|
|
|
|
return;
|
|
|
|
|
2019-01-17 22:28:07 +00:00
|
|
|
for (int i = 0; i < m_game_list_model.rowCount(QModelIndex()); i++)
|
2018-03-26 02:17:47 +00:00
|
|
|
{
|
2019-01-17 22:28:07 +00:00
|
|
|
auto file = m_game_list_model.GetGameFile(i);
|
2018-03-26 02:17:47 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
|
2019-07-23 22:18:58 +00:00
|
|
|
connect(m_match_new, &QPushButton::clicked, this, &CheatsManager::NewSearch);
|
|
|
|
connect(m_match_next, &QPushButton::clicked, this, &CheatsManager::NextSearch);
|
|
|
|
connect(m_match_refresh, &QPushButton::clicked, this, &CheatsManager::Update);
|
|
|
|
connect(m_match_reset, &QPushButton::clicked, this, &CheatsManager::Reset);
|
2018-03-26 02:17:47 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
|
2018-07-09 08:02:10 +00:00
|
|
|
menu->addAction(tr("Remove from Watch"), this, [this] {
|
2018-09-25 04:01:24 +00:00
|
|
|
auto* item = m_watch_table->selectedItems()[0];
|
2018-03-26 02:17:47 +00:00
|
|
|
|
|
|
|
int index = item->data(INDEX_ROLE).toInt();
|
|
|
|
|
|
|
|
m_watch.erase(m_watch.begin() + index);
|
|
|
|
|
|
|
|
Update();
|
|
|
|
});
|
|
|
|
|
|
|
|
menu->addSeparator();
|
|
|
|
|
2018-07-09 08:02:10 +00:00
|
|
|
menu->addAction(tr("Generate Action Replay Code"), this, &CheatsManager::GenerateARCode);
|
2018-03-26 02:17:47 +00:00
|
|
|
|
|
|
|
menu->exec(QCursor::pos());
|
|
|
|
}
|
|
|
|
|
|
|
|
void CheatsManager::OnMatchContextMenu()
|
|
|
|
{
|
|
|
|
if (m_match_table->selectedItems().isEmpty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
QMenu* menu = new QMenu(this);
|
|
|
|
|
2018-07-09 08:02:10 +00:00
|
|
|
menu->addAction(tr("Add to Watch"), this, [this] {
|
2018-03-26 02:17:47 +00:00
|
|
|
auto* item = m_match_table->selectedItems()[0];
|
|
|
|
|
|
|
|
int index = item->data(INDEX_ROLE).toInt();
|
|
|
|
|
2019-04-21 16:00:36 +00:00
|
|
|
m_results[index].locked_value = GetResultValue(m_results[index]);
|
|
|
|
|
2018-03-26 02:17:47 +00:00
|
|
|
m_watch.push_back(m_results[index]);
|
|
|
|
|
|
|
|
Update();
|
|
|
|
});
|
|
|
|
|
|
|
|
menu->exec(QCursor::pos());
|
|
|
|
}
|
|
|
|
|
|
|
|
void CheatsManager::GenerateARCode()
|
|
|
|
{
|
|
|
|
if (!m_ar_code)
|
|
|
|
return;
|
|
|
|
|
2018-07-18 21:56:31 +00:00
|
|
|
auto* item = m_watch_table->selectedItems()[0];
|
2018-03-26 02:17:47 +00:00
|
|
|
|
|
|
|
int index = item->data(INDEX_ROLE).toInt();
|
|
|
|
ActionReplay::ARCode ar_code;
|
|
|
|
|
2020-12-10 11:58:27 +00:00
|
|
|
ar_code.enabled = true;
|
2018-03-26 02:17:47 +00:00
|
|
|
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;
|
2019-04-21 15:34:31 +00:00
|
|
|
case 2:
|
2018-03-26 02:17:47 +00:00
|
|
|
m_watch[index].locked = item->checkState() == Qt::Checked;
|
2019-04-21 15:34:31 +00:00
|
|
|
|
2019-04-21 16:00:36 +00:00
|
|
|
if (m_watch[index].locked)
|
|
|
|
m_watch[index].locked_value = GetResultValue(m_results[index]);
|
|
|
|
|
2019-04-21 15:34:31 +00:00
|
|
|
UpdatePatch(m_watch[index]);
|
2018-03-26 02:17:47 +00:00
|
|
|
break;
|
2019-04-21 15:34:31 +00:00
|
|
|
case 3:
|
2018-03-26 02:17:47 +00:00
|
|
|
{
|
|
|
|
const auto text = item->text();
|
|
|
|
u32 value = 0;
|
|
|
|
|
2019-04-21 15:34:31 +00:00
|
|
|
switch (m_watch[index].type)
|
2018-03-26 02:17:47 +00:00
|
|
|
{
|
|
|
|
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;
|
2019-04-21 15:34:31 +00:00
|
|
|
|
|
|
|
UpdatePatch(m_watch[index]);
|
|
|
|
|
2018-03-26 02:17:47 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Update();
|
|
|
|
}
|
|
|
|
|
|
|
|
QWidget* CheatsManager::CreateCheatSearch()
|
|
|
|
{
|
|
|
|
m_match_table = new QTableWidget;
|
|
|
|
m_watch_table = new QTableWidget;
|
|
|
|
|
2020-02-07 02:34:27 +00:00
|
|
|
m_match_table->setTabKeyNavigation(false);
|
|
|
|
m_watch_table->setTabKeyNavigation(false);
|
|
|
|
|
2018-03-26 02:17:47 +00:00
|
|
|
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);
|
|
|
|
|
2018-09-08 13:58:15 +00:00
|
|
|
// i18n: The base 10 numeral system. Not related to non-integer numbers
|
2018-03-26 02:17:47 +00:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-03 22:17:45 +00:00
|
|
|
std::function<bool(u32)> CheatsManager::CreateMatchFunction()
|
2018-03-26 02:17:47 +00:00
|
|
|
{
|
2018-10-03 22:17:45 +00:00
|
|
|
const QString text = m_match_value->text();
|
|
|
|
|
|
|
|
if (text.isEmpty())
|
|
|
|
{
|
|
|
|
m_result_label->setText(tr("No search value entered."));
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
const CompareType op = static_cast<CompareType>(m_match_operation->currentIndex());
|
2018-03-26 02:17:47 +00:00
|
|
|
|
|
|
|
const int base =
|
|
|
|
(m_match_decimal->isChecked() ? 10 : (m_match_hexadecimal->isChecked() ? 16 : 8));
|
|
|
|
|
2018-10-03 22:17:45 +00:00
|
|
|
bool conversion_succeeded = false;
|
|
|
|
std::function<bool(u32)> matches_func;
|
2018-03-26 02:17:47 +00:00
|
|
|
switch (static_cast<DataType>(m_match_length->currentIndex()))
|
|
|
|
{
|
|
|
|
case DataType::Byte:
|
2018-10-03 22:17:45 +00:00
|
|
|
{
|
|
|
|
u8 comparison_value = text.toUShort(&conversion_succeeded, base) & 0xFF;
|
|
|
|
matches_func = [=](u32 addr) {
|
|
|
|
return Compare<u8>(PowerPC::HostRead_U8(addr), comparison_value, op);
|
|
|
|
};
|
|
|
|
break;
|
|
|
|
}
|
2018-03-26 02:17:47 +00:00
|
|
|
case DataType::Short:
|
2018-10-03 22:17:45 +00:00
|
|
|
{
|
|
|
|
u16 comparison_value = text.toUShort(&conversion_succeeded, base);
|
|
|
|
matches_func = [=](u32 addr) {
|
|
|
|
return Compare(PowerPC::HostRead_U16(addr), comparison_value, op);
|
|
|
|
};
|
|
|
|
break;
|
|
|
|
}
|
2018-03-26 02:17:47 +00:00
|
|
|
case DataType::Int:
|
2018-10-03 22:17:45 +00:00
|
|
|
{
|
|
|
|
u32 comparison_value = text.toUInt(&conversion_succeeded, base);
|
|
|
|
matches_func = [=](u32 addr) {
|
|
|
|
return Compare(PowerPC::HostRead_U32(addr), comparison_value, op);
|
|
|
|
};
|
|
|
|
break;
|
|
|
|
}
|
2018-03-26 02:17:47 +00:00
|
|
|
case DataType::Float:
|
2018-10-03 22:17:45 +00:00
|
|
|
{
|
|
|
|
float comparison_value = text.toFloat(&conversion_succeeded);
|
|
|
|
matches_func = [=](u32 addr) {
|
|
|
|
return Compare(PowerPC::HostRead_F32(addr), comparison_value, op);
|
|
|
|
};
|
|
|
|
break;
|
|
|
|
}
|
2018-03-26 02:17:47 +00:00
|
|
|
case DataType::Double:
|
2018-10-03 22:17:45 +00:00
|
|
|
{
|
|
|
|
double comparison_value = text.toDouble(&conversion_succeeded);
|
|
|
|
matches_func = [=](u32 addr) {
|
|
|
|
return Compare(PowerPC::HostRead_F64(addr), comparison_value, op);
|
|
|
|
};
|
|
|
|
break;
|
|
|
|
}
|
2018-03-26 02:17:47 +00:00
|
|
|
case DataType::String:
|
|
|
|
{
|
2018-10-03 22:17:45 +00:00
|
|
|
if (op != CompareType::Equal && op != CompareType::NotEqual)
|
2018-03-26 02:17:47 +00:00
|
|
|
{
|
2018-10-03 22:17:45 +00:00
|
|
|
m_result_label->setText(tr("String values can only be compared using equality."));
|
|
|
|
return nullptr;
|
2018-03-26 02:17:47 +00:00
|
|
|
}
|
2018-10-03 22:17:45 +00:00
|
|
|
|
|
|
|
conversion_succeeded = true;
|
|
|
|
|
|
|
|
const QString lambda_text = m_match_value->text();
|
|
|
|
const QByteArray utf8_bytes = lambda_text.toUtf8();
|
|
|
|
|
|
|
|
matches_func = [op, utf8_bytes](u32 addr) {
|
|
|
|
bool is_equal = std::equal(utf8_bytes.cbegin(), utf8_bytes.cend(),
|
|
|
|
reinterpret_cast<char*>(Memory::m_pRAM + addr - 0x80000000));
|
|
|
|
switch (op)
|
|
|
|
{
|
|
|
|
case CompareType::Equal:
|
|
|
|
return is_equal;
|
|
|
|
case CompareType::NotEqual:
|
|
|
|
return !is_equal;
|
|
|
|
default:
|
|
|
|
// This should never occur since we've already checked the type of op
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
break;
|
2018-03-26 02:17:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-03 22:17:45 +00:00
|
|
|
if (conversion_succeeded)
|
|
|
|
return matches_func;
|
|
|
|
|
|
|
|
m_result_label->setText(tr("Cannot interpret the given value.\nHave you chosen the right type?"));
|
|
|
|
return nullptr;
|
2018-03-26 02:17:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CheatsManager::NewSearch()
|
|
|
|
{
|
|
|
|
m_results.clear();
|
|
|
|
const u32 base_address = 0x80000000;
|
|
|
|
|
|
|
|
if (!Memory::m_pRAM)
|
|
|
|
{
|
|
|
|
m_result_label->setText(tr("Memory Not Ready"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-10-03 22:17:45 +00:00
|
|
|
std::function<bool(u32)> matches_func = CreateMatchFunction();
|
|
|
|
if (matches_func == nullptr)
|
|
|
|
return;
|
|
|
|
|
2018-04-29 05:31:36 +00:00
|
|
|
Core::RunAsCPUThread([&] {
|
2020-04-28 17:10:50 +00:00
|
|
|
for (u32 i = 0; i < Memory::GetRamSizeReal() - GetTypeSize(); i++)
|
2018-04-29 05:31:36 +00:00
|
|
|
{
|
2018-10-03 22:17:45 +00:00
|
|
|
if (PowerPC::HostIsRAMAddress(base_address + i) && matches_func(base_address + i))
|
2018-04-29 05:31:36 +00:00
|
|
|
m_results.push_back(
|
|
|
|
{base_address + i, static_cast<DataType>(m_match_length->currentIndex())});
|
|
|
|
}
|
|
|
|
});
|
2018-03-26 02:17:47 +00:00
|
|
|
|
|
|
|
m_match_next->setEnabled(true);
|
|
|
|
|
|
|
|
Update();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CheatsManager::NextSearch()
|
|
|
|
{
|
|
|
|
if (!Memory::m_pRAM)
|
|
|
|
{
|
|
|
|
m_result_label->setText(tr("Memory Not Ready"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-10-03 22:17:45 +00:00
|
|
|
std::function<bool(u32)> matches_func = CreateMatchFunction();
|
|
|
|
if (matches_func == nullptr)
|
|
|
|
return;
|
|
|
|
|
|
|
|
Core::RunAsCPUThread([this, matches_func] {
|
2018-04-29 05:31:36 +00:00
|
|
|
m_results.erase(std::remove_if(m_results.begin(), m_results.end(),
|
2018-10-03 22:17:45 +00:00
|
|
|
[matches_func](Result r) {
|
2018-04-29 05:31:36 +00:00
|
|
|
return !PowerPC::HostIsRAMAddress(r.address) ||
|
2018-10-03 22:17:45 +00:00
|
|
|
!matches_func(r.address);
|
2018-04-29 05:31:36 +00:00
|
|
|
}),
|
|
|
|
m_results.end());
|
|
|
|
});
|
2018-03-26 02:17:47 +00:00
|
|
|
|
|
|
|
Update();
|
|
|
|
}
|
|
|
|
|
2019-04-21 16:00:36 +00:00
|
|
|
static QString GetResultString(const Result& result)
|
2019-04-21 13:38:59 +00:00
|
|
|
{
|
|
|
|
if (!PowerPC::HostIsRAMAddress(result.address))
|
|
|
|
{
|
|
|
|
return QStringLiteral("---");
|
|
|
|
}
|
|
|
|
switch (result.type)
|
|
|
|
{
|
|
|
|
case DataType::Byte:
|
|
|
|
return QStringLiteral("%1").arg(PowerPC::HostRead_U8(result.address), 2, 16, QLatin1Char('0'));
|
|
|
|
case DataType::Short:
|
|
|
|
return QStringLiteral("%1").arg(PowerPC::HostRead_U16(result.address), 4, 16, QLatin1Char('0'));
|
|
|
|
case DataType::Int:
|
|
|
|
return QStringLiteral("%1").arg(PowerPC::HostRead_U32(result.address), 8, 16, QLatin1Char('0'));
|
|
|
|
case DataType::Float:
|
|
|
|
return QString::number(PowerPC::HostRead_F32(result.address));
|
|
|
|
case DataType::Double:
|
|
|
|
return QString::number(PowerPC::HostRead_F64(result.address));
|
|
|
|
case DataType::String:
|
|
|
|
return QObject::tr("String Match");
|
2019-04-21 16:00:36 +00:00
|
|
|
default:
|
2019-07-30 11:57:06 +00:00
|
|
|
return {};
|
2019-04-21 13:38:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-26 02:17:47 +00:00
|
|
|
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_result_label->setText(tr("%1 Match(es)").arg(m_results.size()));
|
|
|
|
m_match_table->setRowCount(static_cast<int>(m_results.size()));
|
|
|
|
|
2018-04-29 05:31:36 +00:00
|
|
|
if (m_results.empty())
|
|
|
|
return;
|
2018-03-26 02:17:47 +00:00
|
|
|
|
2018-04-29 05:31:36 +00:00
|
|
|
m_updating = true;
|
2018-03-26 02:17:47 +00:00
|
|
|
|
2018-04-29 05:31:36 +00:00
|
|
|
Core::RunAsCPUThread([this] {
|
|
|
|
for (size_t i = 0; i < m_results.size(); i++)
|
2018-03-26 02:17:47 +00:00
|
|
|
{
|
2018-04-29 05:31:36 +00:00
|
|
|
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);
|
2018-03-26 02:17:47 +00:00
|
|
|
|
2019-04-21 16:00:36 +00:00
|
|
|
value_item->setText(GetResultString(m_results[i]));
|
2018-03-26 02:17:47 +00:00
|
|
|
|
2018-04-29 05:31:36 +00:00
|
|
|
address_item->setData(INDEX_ROLE, static_cast<int>(i));
|
|
|
|
value_item->setData(INDEX_ROLE, static_cast<int>(i));
|
2018-03-26 02:17:47 +00:00
|
|
|
|
2018-04-29 05:31:36 +00:00
|
|
|
m_match_table->setItem(static_cast<int>(i), 0, address_item);
|
|
|
|
m_match_table->setItem(static_cast<int>(i), 1, value_item);
|
|
|
|
}
|
2018-03-26 02:17:47 +00:00
|
|
|
|
2018-04-29 05:31:36 +00:00
|
|
|
m_watch_table->setRowCount(static_cast<int>(m_watch.size()));
|
2018-03-26 02:17:47 +00:00
|
|
|
|
2018-04-29 05:31:36 +00:00
|
|
|
for (size_t i = 0; i < m_watch.size(); i++)
|
2018-03-26 02:17:47 +00:00
|
|
|
{
|
2018-04-29 05:31:36 +00:00
|
|
|
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;
|
2018-03-26 02:17:47 +00:00
|
|
|
|
2019-04-21 16:00:36 +00:00
|
|
|
value_item->setText(GetResultString(m_results[i]));
|
2019-04-21 13:38:59 +00:00
|
|
|
|
2018-04-29 05:31:36 +00:00
|
|
|
name_item->setData(INDEX_ROLE, static_cast<int>(i));
|
|
|
|
name_item->setData(COLUMN_ROLE, 0);
|
2019-04-21 15:34:31 +00:00
|
|
|
|
2018-04-29 05:31:36 +00:00
|
|
|
address_item->setData(INDEX_ROLE, static_cast<int>(i));
|
|
|
|
address_item->setData(COLUMN_ROLE, 1);
|
2019-04-21 15:34:31 +00:00
|
|
|
|
2018-04-29 05:31:36 +00:00
|
|
|
lock_item->setData(INDEX_ROLE, static_cast<int>(i));
|
2019-04-21 15:34:31 +00:00
|
|
|
lock_item->setData(COLUMN_ROLE, 2);
|
|
|
|
|
2018-04-29 05:31:36 +00:00
|
|
|
value_item->setData(INDEX_ROLE, static_cast<int>(i));
|
2019-04-21 15:34:31 +00:00
|
|
|
value_item->setData(COLUMN_ROLE, 3);
|
2018-04-29 05:31:36 +00:00
|
|
|
|
|
|
|
name_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
|
|
|
|
address_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
|
2019-04-21 11:51:25 +00:00
|
|
|
lock_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsSelectable);
|
2019-04-21 15:34:31 +00:00
|
|
|
value_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
|
2018-04-29 05:31:36 +00:00
|
|
|
|
|
|
|
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);
|
2018-03-26 02:17:47 +00:00
|
|
|
}
|
2018-04-29 05:31:36 +00:00
|
|
|
});
|
2018-03-26 02:17:47 +00:00
|
|
|
|
|
|
|
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);
|
2019-07-30 11:57:06 +00:00
|
|
|
m_result_label->clear();
|
2018-03-26 02:17:47 +00:00
|
|
|
|
|
|
|
Update();
|
|
|
|
}
|