DolphinQt: Rewrite cheat search GUI.

This commit is contained in:
Admiral H. Curtiss 2021-08-22 07:13:00 +02:00
parent f3346262d2
commit 55397b6d52
10 changed files with 955 additions and 708 deletions

View File

@ -16,6 +16,10 @@ set(CMAKE_AUTOMOC ON)
add_executable(dolphin-emu
AboutDialog.cpp
AboutDialog.h
CheatSearchFactoryWidget.cpp
CheatSearchFactoryWidget.h
CheatSearchWidget.cpp
CheatSearchWidget.h
CheatsManager.cpp
CheatsManager.h
ConvertDialog.cpp
@ -269,6 +273,8 @@ add_executable(dolphin-emu
QtUtils/ModalMessageBox.cpp
QtUtils/ModalMessageBox.h
QtUtils/ParallelProgressDialog.h
QtUtils/PartiallyClosableTabWidget.cpp
QtUtils/PartiallyClosableTabWidget.h
QtUtils/ImageConverter.cpp
QtUtils/ImageConverter.h
QtUtils/UTF8CodePointCountValidator.cpp

View File

@ -0,0 +1,187 @@
// Copyright 2021 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "DolphinQt/CheatSearchFactoryWidget.h"
#include <string>
#include <vector>
#include <QButtonGroup>
#include <QCheckBox>
#include <QComboBox>
#include <QGroupBox>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QRadioButton>
#include <QVBoxLayout>
#include "Common/StringUtil.h"
#include "Core/CheatSearch.h"
#include "Core/Config/MainSettings.h"
#include "Core/ConfigManager.h"
#include "Core/HW/Memmap.h"
#include "Core/PowerPC/MMU.h"
CheatSearchFactoryWidget::CheatSearchFactoryWidget()
{
CreateWidgets();
ConnectWidgets();
RefreshGui();
}
CheatSearchFactoryWidget::~CheatSearchFactoryWidget() = default;
Q_DECLARE_METATYPE(Cheats::DataType);
void CheatSearchFactoryWidget::CreateWidgets()
{
auto* layout = new QVBoxLayout();
auto* address_space_group = new QGroupBox(tr("Address Space"));
auto* address_space_layout = new QVBoxLayout();
address_space_group->setLayout(address_space_layout);
m_standard_address_space = new QRadioButton(tr("Typical GameCube/Wii Address Space"));
m_standard_address_space->setChecked(true);
m_custom_address_space = new QRadioButton(tr("Custom Address Space"));
QLabel* label_standard_address_space =
new QLabel(tr("Sets up the search using standard MEM1 and (on Wii) MEM2 mappings in virtual "
"address space. This will work for the vast majority of games."));
label_standard_address_space->setWordWrap(true);
auto* custom_address_space_layout = new QVBoxLayout();
custom_address_space_layout->setMargin(6);
auto* custom_address_space_button_group = new QButtonGroup();
m_custom_virtual_address_space = new QRadioButton(tr("Use virtual addresses when possible"));
m_custom_virtual_address_space->setChecked(true);
m_custom_physical_address_space = new QRadioButton(tr("Use physical addresses"));
m_custom_effective_address_space =
new QRadioButton(tr("Use memory mapper configuration at time of scan"));
custom_address_space_button_group->addButton(m_custom_virtual_address_space);
custom_address_space_button_group->addButton(m_custom_physical_address_space);
custom_address_space_button_group->addButton(m_custom_effective_address_space);
custom_address_space_layout->addWidget(m_custom_virtual_address_space);
custom_address_space_layout->addWidget(m_custom_physical_address_space);
custom_address_space_layout->addWidget(m_custom_effective_address_space);
QLabel* label_range_start = new QLabel(tr("Range Start: "));
m_custom_address_start = new QLineEdit(QStringLiteral("0x80000000"));
QLabel* label_range_end = new QLabel(tr("Range End: "));
m_custom_address_end = new QLineEdit(QStringLiteral("0x81800000"));
custom_address_space_layout->addWidget(label_range_start);
custom_address_space_layout->addWidget(m_custom_address_start);
custom_address_space_layout->addWidget(label_range_end);
custom_address_space_layout->addWidget(m_custom_address_end);
address_space_layout->addWidget(m_standard_address_space);
address_space_layout->addWidget(label_standard_address_space);
address_space_layout->addWidget(m_custom_address_space);
address_space_layout->addLayout(custom_address_space_layout);
layout->addWidget(address_space_group);
auto* data_type_group = new QGroupBox(tr("Data Type"));
auto* data_type_layout = new QVBoxLayout();
data_type_group->setLayout(data_type_layout);
m_data_type_dropdown = new QComboBox();
m_data_type_dropdown->addItem(tr("8-bit Unsigned Integer"),
QVariant::fromValue(Cheats::DataType::U8));
m_data_type_dropdown->addItem(tr("16-bit Unsigned Integer"),
QVariant::fromValue(Cheats::DataType::U16));
m_data_type_dropdown->addItem(tr("32-bit Unsigned Integer"),
QVariant::fromValue(Cheats::DataType::U32));
m_data_type_dropdown->addItem(tr("64-bit Unsigned Integer"),
QVariant::fromValue(Cheats::DataType::U64));
m_data_type_dropdown->addItem(tr("8-bit Signed Integer"),
QVariant::fromValue(Cheats::DataType::S8));
m_data_type_dropdown->addItem(tr("16-bit Signed Integer"),
QVariant::fromValue(Cheats::DataType::S16));
m_data_type_dropdown->addItem(tr("32-bit Signed Integer"),
QVariant::fromValue(Cheats::DataType::S32));
m_data_type_dropdown->addItem(tr("64-bit Signed Integer"),
QVariant::fromValue(Cheats::DataType::S64));
m_data_type_dropdown->addItem(tr("32-bit Float"), QVariant::fromValue(Cheats::DataType::F32));
m_data_type_dropdown->addItem(tr("64-bit Float"), QVariant::fromValue(Cheats::DataType::F64));
m_data_type_dropdown->setCurrentIndex(6); // select 32bit signed int by default
data_type_layout->addWidget(m_data_type_dropdown);
m_data_type_aligned = new QCheckBox(tr("Aligned to data type length"));
m_data_type_aligned->setChecked(true);
data_type_layout->addWidget(m_data_type_aligned);
layout->addWidget(data_type_group);
m_new_search = new QPushButton(tr("New Search"));
layout->addWidget(m_new_search);
setLayout(layout);
}
void CheatSearchFactoryWidget::ConnectWidgets()
{
connect(m_new_search, &QPushButton::clicked, this, &CheatSearchFactoryWidget::OnNewSearchClicked);
connect(m_standard_address_space, &QPushButton::toggled, this,
&CheatSearchFactoryWidget::OnAddressSpaceRadioChanged);
connect(m_custom_address_space, &QRadioButton::toggled, this,
&CheatSearchFactoryWidget::OnAddressSpaceRadioChanged);
}
void CheatSearchFactoryWidget::RefreshGui()
{
bool enable_custom = m_custom_address_space->isChecked();
m_custom_virtual_address_space->setEnabled(enable_custom);
m_custom_physical_address_space->setEnabled(enable_custom);
m_custom_effective_address_space->setEnabled(enable_custom);
m_custom_address_start->setEnabled(enable_custom);
m_custom_address_end->setEnabled(enable_custom);
}
void CheatSearchFactoryWidget::OnAddressSpaceRadioChanged()
{
RefreshGui();
}
void CheatSearchFactoryWidget::OnNewSearchClicked()
{
std::vector<Cheats::MemoryRange> memory_ranges;
PowerPC::RequestedAddressSpace address_space;
if (m_standard_address_space->isChecked())
{
memory_ranges.emplace_back(0x80000000, Memory::GetRamSizeReal());
if (SConfig::GetInstance().bWii)
memory_ranges.emplace_back(0x90000000, Memory::GetExRamSizeReal());
address_space = PowerPC::RequestedAddressSpace::Virtual;
}
else
{
const std::string address_start_str = m_custom_address_start->text().toStdString();
const std::string address_end_str = m_custom_address_end->text().toStdString();
u64 address_start;
u64 address_end;
if (!TryParse(address_start_str, &address_start) || !TryParse(address_end_str, &address_end))
return;
if (address_end <= address_start || address_end > 0x1'0000'0000)
return;
memory_ranges.emplace_back(static_cast<u32>(address_start), address_end - address_start);
if (m_custom_virtual_address_space->isChecked())
address_space = PowerPC::RequestedAddressSpace::Virtual;
else if (m_custom_physical_address_space->isChecked())
address_space = PowerPC::RequestedAddressSpace::Physical;
else
address_space = PowerPC::RequestedAddressSpace::Effective;
}
bool aligned = m_data_type_aligned->isChecked();
auto data_type = m_data_type_dropdown->currentData().value<Cheats::DataType>();
auto session = Cheats::MakeSession(std::move(memory_ranges), address_space, aligned, data_type);
if (session)
emit NewSessionCreated(*session);
}

View File

@ -0,0 +1,51 @@
// Copyright 2021 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <vector>
#include <QWidget>
#include "Core/CheatSearch.h"
class QCheckBox;
class QComboBox;
class QLineEdit;
class QPushButton;
class QRadioButton;
class CheatSearchFactoryWidget : public QWidget
{
Q_OBJECT
public:
explicit CheatSearchFactoryWidget();
~CheatSearchFactoryWidget() override;
signals:
void NewSessionCreated(const Cheats::CheatSearchSessionBase& session);
private:
void CreateWidgets();
void ConnectWidgets();
void RefreshGui();
void OnAddressSpaceRadioChanged();
void OnNewSearchClicked();
QRadioButton* m_standard_address_space;
QRadioButton* m_custom_address_space;
QRadioButton* m_custom_virtual_address_space;
QRadioButton* m_custom_physical_address_space;
QRadioButton* m_custom_effective_address_space;
QLineEdit* m_custom_address_start;
QLineEdit* m_custom_address_end;
QComboBox* m_data_type_dropdown;
QCheckBox* m_data_type_aligned;
QPushButton* m_new_search;
};

View File

@ -0,0 +1,529 @@
// Copyright 2021 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "DolphinQt/CheatSearchWidget.h"
#include <cassert>
#include <functional>
#include <optional>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include <QCheckBox>
#include <QComboBox>
#include <QCursor>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QMenu>
#include <QPushButton>
#include <QSignalBlocker>
#include <QString>
#include <QTableWidget>
#include <QVBoxLayout>
#include <fmt/format.h>
#include "Common/Align.h"
#include "Common/BitUtils.h"
#include "Common/StringUtil.h"
#include "Common/Swap.h"
#include "Core/ActionReplay.h"
#include "Core/CheatGeneration.h"
#include "Core/CheatSearch.h"
#include "Core/ConfigManager.h"
#include "Core/PowerPC/PowerPC.h"
#include "DolphinQt/Config/CheatCodeEditor.h"
#include "DolphinQt/Config/CheatWarningWidget.h"
#include "UICommon/GameFile.h"
constexpr size_t TABLE_MAX_ROWS = 1000;
constexpr int ADDRESS_TABLE_ADDRESS_ROLE = Qt::UserRole;
constexpr int ADDRESS_TABLE_RESULT_INDEX_ROLE = Qt::UserRole + 1;
constexpr int ADDRESS_TABLE_COLUMN_INDEX_DESCRIPTION = 0;
constexpr int ADDRESS_TABLE_COLUMN_INDEX_ADDRESS = 1;
constexpr int ADDRESS_TABLE_COLUMN_INDEX_LAST_VALUE = 2;
constexpr int ADDRESS_TABLE_COLUMN_INDEX_CURRENT_VALUE = 3;
CheatSearchWidget::CheatSearchWidget(std::unique_ptr<Cheats::CheatSearchSessionBase> session)
: m_session(std::move(session))
{
setAttribute(Qt::WA_DeleteOnClose);
CreateWidgets();
ConnectWidgets();
UpdateGuiTable();
}
CheatSearchWidget::~CheatSearchWidget() = default;
Q_DECLARE_METATYPE(Cheats::CompareType);
Q_DECLARE_METATYPE(Cheats::FilterType);
void CheatSearchWidget::CreateWidgets()
{
QLabel* session_info_label = new QLabel();
{
QString ranges;
size_t range_count = m_session->GetMemoryRangeCount();
switch (range_count)
{
case 1:
{
auto m = m_session->GetMemoryRange(0);
ranges =
tr("[%1, %2]")
.arg(QString::fromStdString(fmt::format("0x{0:08x}", m.m_start)))
.arg(QString::fromStdString(fmt::format("0x{0:08x}", m.m_start + m.m_length - 1)));
break;
}
case 2:
{
auto m0 = m_session->GetMemoryRange(0);
auto m1 = m_session->GetMemoryRange(1);
ranges =
tr("[%1, %2] and [%3, %4]")
.arg(QString::fromStdString(fmt::format("0x{0:08x}", m0.m_start)))
.arg(QString::fromStdString(fmt::format("0x{0:08x}", m0.m_start + m0.m_length - 1)))
.arg(QString::fromStdString(fmt::format("0x{0:08x}", m1.m_start)))
.arg(QString::fromStdString(fmt::format("0x{0:08x}", m1.m_start + m1.m_length - 1)));
break;
}
default:
ranges = tr("%1 memory ranges").arg(range_count);
break;
}
QString space;
switch (m_session->GetAddressSpace())
{
case PowerPC::RequestedAddressSpace::Effective:
space = tr("Address space by CPU state");
break;
case PowerPC::RequestedAddressSpace::Physical:
space = tr("Physical address space");
break;
case PowerPC::RequestedAddressSpace::Virtual:
space = tr("Virtual address space");
break;
default:
space = tr("Unknown address space");
break;
}
QString type;
switch (m_session->GetDataType())
{
case Cheats::DataType::U8:
type = tr("8-bit Unsigned Integer");
break;
case Cheats::DataType::U16:
type = tr("16-bit Unsigned Integer");
break;
case Cheats::DataType::U32:
type = tr("32-bit Unsigned Integer");
break;
case Cheats::DataType::U64:
type = tr("64-bit Unsigned Integer");
break;
case Cheats::DataType::S8:
type = tr("8-bit Signed Integer");
break;
case Cheats::DataType::S16:
type = tr("16-bit Signed Integer");
break;
case Cheats::DataType::S32:
type = tr("32-bit Signed Integer");
break;
case Cheats::DataType::S64:
type = tr("64-bit Signed Integer");
break;
case Cheats::DataType::F32:
type = tr("32-bit Float");
break;
case Cheats::DataType::F64:
type = tr("64-bit Float");
break;
default:
type = tr("Unknown data type");
break;
}
QString aligned = m_session->GetAligned() ? tr("aligned") : tr("unaligned");
session_info_label->setText(tr("%1, %2, %3, %4").arg(ranges).arg(space).arg(type).arg(aligned));
}
auto* value_layout = new QHBoxLayout();
auto* instructions_label = new QLabel(tr("Keep addresses where value in memory"));
value_layout->addWidget(instructions_label);
m_compare_type_dropdown = new QComboBox();
m_compare_type_dropdown->addItem(tr("is equal to"),
QVariant::fromValue(Cheats::CompareType::Equal));
m_compare_type_dropdown->addItem(tr("is not equal to"),
QVariant::fromValue(Cheats::CompareType::NotEqual));
m_compare_type_dropdown->addItem(tr("is less than"),
QVariant::fromValue(Cheats::CompareType::Less));
m_compare_type_dropdown->addItem(tr("is less than or equal to"),
QVariant::fromValue(Cheats::CompareType::LessOrEqual));
m_compare_type_dropdown->addItem(tr("is greater than"),
QVariant::fromValue(Cheats::CompareType::Greater));
m_compare_type_dropdown->addItem(tr("is greater than or equal to"),
QVariant::fromValue(Cheats::CompareType::GreaterOrEqual));
value_layout->addWidget(m_compare_type_dropdown);
m_value_source_dropdown = new QComboBox();
m_value_source_dropdown->addItem(
tr("this value:"), QVariant::fromValue(Cheats::FilterType::CompareAgainstSpecificValue));
m_value_source_dropdown->addItem(
tr("last value"), QVariant::fromValue(Cheats::FilterType::CompareAgainstLastValue));
m_value_source_dropdown->addItem(tr("any value"),
QVariant::fromValue(Cheats::FilterType::DoNotFilter));
value_layout->addWidget(m_value_source_dropdown);
m_given_value_text = new QLineEdit();
value_layout->addWidget(m_given_value_text);
auto* button_layout = new QHBoxLayout();
m_next_scan_button = new QPushButton(tr("Search and Filter"));
button_layout->addWidget(m_next_scan_button);
m_refresh_values_button = new QPushButton(tr("Refresh Current Values"));
button_layout->addWidget(m_refresh_values_button);
m_reset_button = new QPushButton(tr("Reset Results"));
button_layout->addWidget(m_reset_button);
m_address_table = new QTableWidget();
m_address_table->setContextMenuPolicy(Qt::CustomContextMenu);
m_info_label_1 = new QLabel(tr("Waiting for first scan..."));
m_info_label_2 = new QLabel();
m_display_values_in_hex_checkbox = new QCheckBox(tr("Display values in Hex"));
QVBoxLayout* layout = new QVBoxLayout();
layout->addWidget(session_info_label);
layout->addLayout(value_layout);
layout->addLayout(button_layout);
layout->addWidget(m_display_values_in_hex_checkbox);
layout->addWidget(m_info_label_1);
layout->addWidget(m_info_label_2);
layout->addWidget(m_address_table);
setLayout(layout);
}
void CheatSearchWidget::ConnectWidgets()
{
connect(m_next_scan_button, &QPushButton::clicked, this, &CheatSearchWidget::OnNextScanClicked);
connect(m_refresh_values_button, &QPushButton::clicked, this,
&CheatSearchWidget::OnRefreshClicked);
connect(m_reset_button, &QPushButton::clicked, this, &CheatSearchWidget::OnResetClicked);
connect(m_address_table, &QTableWidget::itemChanged, this,
&CheatSearchWidget::OnAddressTableItemChanged);
connect(m_address_table, &QTableWidget::customContextMenuRequested, this,
&CheatSearchWidget::OnAddressTableContextMenu);
connect(m_value_source_dropdown, &QComboBox::currentTextChanged, this,
&CheatSearchWidget::OnValueSourceChanged);
connect(m_display_values_in_hex_checkbox, &QCheckBox::toggled, this,
&CheatSearchWidget::OnHexCheckboxStateChanged);
}
void CheatSearchWidget::OnNextScanClicked()
{
const bool had_old_results = m_session->WasFirstSearchDone();
const size_t old_count = m_session->GetResultCount();
const auto filter_type = m_value_source_dropdown->currentData().value<Cheats::FilterType>();
if (filter_type == Cheats::FilterType::CompareAgainstLastValue &&
!m_session->WasFirstSearchDone())
{
m_info_label_1->setText(tr("Cannot compare against last value on first search."));
return;
}
m_session->SetFilterType(filter_type);
m_session->SetCompareType(m_compare_type_dropdown->currentData().value<Cheats::CompareType>());
if (filter_type == Cheats::FilterType::CompareAgainstSpecificValue)
{
if (!m_session->SetValueFromString(m_given_value_text->text().toStdString()))
{
m_info_label_1->setText(tr("Failed to parse given value into target data type."));
return;
}
}
Cheats::SearchErrorCode error_code = m_session->RunSearch();
if (error_code == Cheats::SearchErrorCode::Success)
{
const size_t new_count = m_session->GetResultCount();
const size_t new_valid_count = m_session->GetValidValueCount();
m_info_label_1->setText(tr("Scan succeeded."));
if (had_old_results)
{
const QString removed_str =
tr("%n address(es) were removed.", "", static_cast<int>(old_count - new_count));
const QString remain_str = tr("%n address(es) remain.", "", static_cast<int>(new_count));
if (new_valid_count == new_count)
{
m_info_label_2->setText(tr("%1 %2").arg(removed_str).arg(remain_str));
}
else
{
const QString inaccessible_str =
tr("%n address(es) could not be accessed in emulated memory.", "",
static_cast<int>(new_count - new_valid_count));
m_info_label_2->setText(
tr("%1 %2 %3").arg(removed_str).arg(remain_str).arg(inaccessible_str));
}
}
else
{
const QString found_str = tr("Found %n address(es).", "", static_cast<int>(new_count));
if (new_valid_count == new_count)
{
m_info_label_2->setText(found_str);
}
else
{
const QString inaccessible_str =
tr("%n address(es) could not be accessed in emulated memory.", "",
static_cast<int>(new_count - new_valid_count));
m_info_label_2->setText(tr("%1 %2").arg(found_str).arg(inaccessible_str));
}
}
m_address_table_current_values.clear();
const bool show_in_hex = m_display_values_in_hex_checkbox->isChecked();
const bool too_many_results = new_count > TABLE_MAX_ROWS;
const size_t result_count_to_display = too_many_results ? TABLE_MAX_ROWS : new_count;
for (size_t i = 0; i < result_count_to_display; ++i)
{
m_address_table_current_values[m_session->GetResultAddress(i)] =
m_session->GetResultValueAsString(i, show_in_hex);
}
UpdateGuiTable();
}
else
{
switch (error_code)
{
case Cheats::SearchErrorCode::NoEmulationActive:
m_info_label_1->setText(tr("No game is running."));
break;
case Cheats::SearchErrorCode::InvalidParameters:
m_info_label_1->setText(tr("Invalid parameters given to search."));
break;
case Cheats::SearchErrorCode::VirtualAddressesCurrentlyNotAccessible:
m_info_label_1->setText(
tr("Search currently not possible in virtual address space. Please run "
"the game for a bit and try again."));
break;
default:
m_info_label_1->setText(tr("Unknown error occurred."));
break;
}
}
}
bool CheatSearchWidget::RefreshValues()
{
const size_t result_count = m_session->GetResultCount();
if (result_count == 0)
{
m_info_label_1->setText(tr("Cannot refresh without results."));
return false;
}
const bool too_many_results = result_count > TABLE_MAX_ROWS;
std::unique_ptr<Cheats::CheatSearchSessionBase> tmp;
if (too_many_results)
{
std::vector<size_t> value_indices;
value_indices.reserve(TABLE_MAX_ROWS);
for (size_t i = 0; i < TABLE_MAX_ROWS; ++i)
value_indices.push_back(i);
tmp = m_session->ClonePartial(value_indices);
}
else
{
tmp = m_session->Clone();
}
tmp->SetFilterType(Cheats::FilterType::DoNotFilter);
if (tmp->RunSearch() != Cheats::SearchErrorCode::Success)
{
m_info_label_1->setText(tr("Refresh failed. Please run the game for a bit and try again."));
return false;
}
m_address_table_current_values.clear();
const bool show_in_hex = m_display_values_in_hex_checkbox->isChecked();
const size_t result_count_to_display = tmp->GetResultCount();
for (size_t i = 0; i < result_count_to_display; ++i)
{
m_address_table_current_values[tmp->GetResultAddress(i)] =
tmp->GetResultValueAsString(i, show_in_hex);
}
UpdateGuiTable();
m_info_label_1->setText(tr("Refreshed current values."));
return true;
}
void CheatSearchWidget::OnRefreshClicked()
{
RefreshValues();
}
void CheatSearchWidget::OnResetClicked()
{
m_session->ResetResults();
m_address_table_current_values.clear();
UpdateGuiTable();
m_info_label_1->setText(tr("Waiting for first scan..."));
m_info_label_2->clear();
}
void CheatSearchWidget::OnAddressTableItemChanged(QTableWidgetItem* item)
{
const u32 address = item->data(ADDRESS_TABLE_ADDRESS_ROLE).toUInt();
const int column = item->column();
switch (column)
{
case ADDRESS_TABLE_COLUMN_INDEX_DESCRIPTION:
{
m_address_table_user_data[address].m_description = item->text().toStdString();
break;
}
default:
break;
}
}
void CheatSearchWidget::OnAddressTableContextMenu()
{
if (m_address_table->selectedItems().isEmpty())
return;
QMenu* menu = new QMenu(this);
menu->addAction(tr("Generate Action Replay Code"), this, &CheatSearchWidget::GenerateARCode);
menu->exec(QCursor::pos());
}
void CheatSearchWidget::OnValueSourceChanged()
{
const auto filter_type = m_value_source_dropdown->currentData().value<Cheats::FilterType>();
m_given_value_text->setEnabled(filter_type == Cheats::FilterType::CompareAgainstSpecificValue);
}
void CheatSearchWidget::OnHexCheckboxStateChanged()
{
if (!m_session->WasFirstSearchDone())
return;
if (!RefreshValues())
UpdateGuiTable();
}
void CheatSearchWidget::GenerateARCode()
{
if (m_address_table->selectedItems().isEmpty())
return;
auto* item = m_address_table->selectedItems()[0];
if (!item)
return;
const u32 index = item->data(ADDRESS_TABLE_RESULT_INDEX_ROLE).toUInt();
auto result = Cheats::GenerateActionReplayCode(*m_session, index);
if (result)
{
emit ActionReplayCodeGenerated(*result);
m_info_label_1->setText(tr("Generated AR code."));
}
else
{
switch (result.Error())
{
case Cheats::GenerateActionReplayCodeErrorCode::NotVirtualMemory:
m_info_label_1->setText(tr("Can only generate AR code for values in virtual memory."));
break;
case Cheats::GenerateActionReplayCodeErrorCode::InvalidAddress:
m_info_label_1->setText(tr("Cannot generate AR code for this address."));
break;
default:
m_info_label_1->setText(tr("Internal error while generating AR code."));
break;
}
}
}
void CheatSearchWidget::UpdateGuiTable()
{
const QSignalBlocker blocker(m_address_table);
m_address_table->clear();
m_address_table->setColumnCount(4);
m_address_table->setHorizontalHeaderLabels(
{tr("Description"), tr("Address"), tr("Last Value"), tr("Current Value")});
const size_t result_count = m_session->GetResultCount();
const bool too_many_results = result_count > TABLE_MAX_ROWS;
const int result_count_to_display = int(too_many_results ? TABLE_MAX_ROWS : result_count);
m_address_table->setRowCount(result_count_to_display);
for (int i = 0; i < result_count_to_display; ++i)
{
const u32 address = m_session->GetResultAddress(i);
const auto user_data_it = m_address_table_user_data.find(address);
const bool has_user_data = user_data_it != m_address_table_user_data.end();
auto* description_item = new QTableWidgetItem();
description_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
if (has_user_data)
description_item->setText(QString::fromStdString(user_data_it->second.m_description));
description_item->setData(ADDRESS_TABLE_ADDRESS_ROLE, address);
description_item->setData(ADDRESS_TABLE_RESULT_INDEX_ROLE, static_cast<u32>(i));
m_address_table->setItem(i, ADDRESS_TABLE_COLUMN_INDEX_DESCRIPTION, description_item);
auto* address_item = new QTableWidgetItem();
address_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
address_item->setText(QStringLiteral("0x%1").arg(address, 8, 16, QLatin1Char('0')));
address_item->setData(ADDRESS_TABLE_ADDRESS_ROLE, address);
address_item->setData(ADDRESS_TABLE_RESULT_INDEX_ROLE, static_cast<u32>(i));
m_address_table->setItem(i, ADDRESS_TABLE_COLUMN_INDEX_ADDRESS, address_item);
const bool show_in_hex = m_display_values_in_hex_checkbox->isChecked();
auto* last_value_item = new QTableWidgetItem();
last_value_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
last_value_item->setText(
QString::fromStdString(m_session->GetResultValueAsString(i, show_in_hex)));
last_value_item->setData(ADDRESS_TABLE_ADDRESS_ROLE, address);
last_value_item->setData(ADDRESS_TABLE_RESULT_INDEX_ROLE, static_cast<u32>(i));
m_address_table->setItem(i, ADDRESS_TABLE_COLUMN_INDEX_LAST_VALUE, last_value_item);
auto* current_value_item = new QTableWidgetItem();
current_value_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
const auto curr_val_it = m_address_table_current_values.find(address);
if (curr_val_it != m_address_table_current_values.end())
current_value_item->setText(QString::fromStdString(curr_val_it->second));
else
current_value_item->setText(QStringLiteral("---"));
current_value_item->setData(ADDRESS_TABLE_ADDRESS_ROLE, address);
current_value_item->setData(ADDRESS_TABLE_RESULT_INDEX_ROLE, static_cast<u32>(i));
m_address_table->setItem(i, ADDRESS_TABLE_COLUMN_INDEX_CURRENT_VALUE, current_value_item);
}
}

View File

@ -0,0 +1,80 @@
// Copyright 2021 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QWidget>
#include <functional>
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>
#include "Common/CommonTypes.h"
#include "Core/CheatSearch.h"
namespace ActionReplay
{
struct ARCode;
}
class QCheckBox;
class QComboBox;
class QLabel;
class QLineEdit;
class QPushButton;
class QTableWidget;
class QTableWidgetItem;
struct CheatSearchTableUserData
{
std::string m_description;
};
class CheatSearchWidget : public QWidget
{
Q_OBJECT
public:
explicit CheatSearchWidget(std::unique_ptr<Cheats::CheatSearchSessionBase> session);
~CheatSearchWidget() override;
signals:
void ActionReplayCodeGenerated(const ActionReplay::ARCode& ar_code);
private:
void CreateWidgets();
void ConnectWidgets();
void OnNextScanClicked();
void OnRefreshClicked();
void OnResetClicked();
void OnAddressTableItemChanged(QTableWidgetItem* item);
void OnAddressTableContextMenu();
void OnValueSourceChanged();
void OnHexCheckboxStateChanged();
bool RefreshValues();
void UpdateGuiTable();
void GenerateARCode();
std::unique_ptr<Cheats::CheatSearchSessionBase> m_session;
// storage for the 'Current Value' column's data
std::unordered_map<u32, std::string> m_address_table_current_values;
// storage for user-entered metadata such as text descriptions for memory addresses
// this is intentionally NOT cleared when updating values or resetting or similar
std::unordered_map<u32, CheatSearchTableUserData> m_address_table_user_data;
QComboBox* m_compare_type_dropdown;
QComboBox* m_value_source_dropdown;
QLineEdit* m_given_value_text;
QLabel* m_info_label_1;
QLabel* m_info_label_2;
QPushButton* m_next_scan_button;
QPushButton* m_refresh_values_button;
QPushButton* m_reset_button;
QCheckBox* m_display_values_in_hex_checkbox;
QTableWidget* m_address_table;
};

View File

@ -3,152 +3,24 @@
#include "DolphinQt/CheatsManager.h"
#include <algorithm>
#include <cstring>
#include <functional>
#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/ActionReplay.h"
#include "Core/CheatSearch.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/Debugger/PPCDebugInterface.h"
#include "Core/HW/Memmap.h"
#include "Core/PowerPC/MMU.h"
#include "Core/PowerPC/PowerPC.h"
#include "UICommon/GameFile.h"
#include "DolphinQt/CheatSearchFactoryWidget.h"
#include "DolphinQt/CheatSearchWidget.h"
#include "DolphinQt/Config/ARCodeWidget.h"
#include "DolphinQt/Config/GeckoCodeWidget.h"
#include "DolphinQt/Settings.h"
constexpr u32 MAX_RESULTS = 50;
constexpr int INDEX_ROLE = Qt::UserRole;
constexpr int COLUMN_ROLE = Qt::UserRole + 1;
constexpr int AR_SET_BYTE_CMD = 0x00;
constexpr int AR_SET_SHORT_CMD = 0x02;
constexpr int AR_SET_INT_CMD = 0x04;
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;
};
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:
cmd = AR_SET_BYTE_CMD;
break;
case DataType::Short:
cmd = AR_SET_SHORT_CMD;
break;
default:
case DataType::Int:
cmd = AR_SET_INT_CMD;
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;
}
}
#include "QtUtils/PartiallyClosableTabWidget.h"
CheatsManager::CheatsManager(QWidget* parent) : QDialog(parent)
{
@ -162,8 +34,6 @@ CheatsManager::CheatsManager(QWidget* parent) : QDialog(parent)
CreateWidgets();
ConnectWidgets();
Reset();
Update();
}
CheatsManager::~CheatsManager() = default;
@ -175,7 +45,7 @@ void CheatsManager::OnStateChanged(Core::State state)
const auto& game_id = SConfig::GetInstance().GetGameID();
const auto& game_tdb_id = SConfig::GetInstance().GetGameTDBID();
u16 revision = SConfig::GetInstance().GetRevision();
const u16 revision = SConfig::GetInstance().GetRevision();
if (m_game_id == game_id && m_game_tdb_id == game_tdb_id && m_revision == revision)
return;
@ -184,32 +54,40 @@ void CheatsManager::OnStateChanged(Core::State state)
m_game_tdb_id = game_tdb_id;
m_revision = revision;
if (m_tab_widget->count() == 3)
if (m_ar_code)
{
m_tab_widget->removeTab(0);
m_tab_widget->removeTab(0);
const int tab_index = m_tab_widget->indexOf(m_ar_code);
if (tab_index != -1)
m_tab_widget->removeTab(tab_index);
m_ar_code->deleteLater();
m_ar_code = nullptr;
}
if (m_tab_widget->count() == 1)
if (m_gecko_code)
{
if (m_ar_code)
m_ar_code->deleteLater();
m_ar_code = new ARCodeWidget(m_game_id, m_revision, false);
m_tab_widget->insertTab(0, m_ar_code, tr("AR Code"));
auto* gecko_code = new GeckoCodeWidget(m_game_id, m_game_tdb_id, m_revision, false);
m_tab_widget->insertTab(1, gecko_code, tr("Gecko Codes"));
const int tab_index = m_tab_widget->indexOf(m_gecko_code);
if (tab_index != -1)
m_tab_widget->removeTab(tab_index);
m_gecko_code->deleteLater();
m_gecko_code = nullptr;
}
m_ar_code = new ARCodeWidget(m_game_id, m_revision, false);
m_gecko_code = new GeckoCodeWidget(m_game_id, m_game_tdb_id, m_revision, false);
m_tab_widget->insertTab(0, m_ar_code, tr("AR Code"));
m_tab_widget->insertTab(1, m_gecko_code, tr("Gecko Codes"));
m_tab_widget->setTabUnclosable(0);
m_tab_widget->setTabUnclosable(1);
}
void CheatsManager::CreateWidgets()
{
m_tab_widget = new QTabWidget;
m_tab_widget = new PartiallyClosableTabWidget;
m_button_box = new QDialogButtonBox(QDialogButtonBox::Close);
m_cheat_search = CreateCheatSearch();
m_tab_widget->addTab(m_cheat_search, tr("Cheat Search"));
m_cheat_search_new = new CheatSearchFactoryWidget();
m_tab_widget->addTab(m_cheat_search_new, tr("Start New Cheat Search"));
m_tab_widget->setTabUnclosable(0);
auto* layout = new QVBoxLayout;
layout->addWidget(m_tab_widget);
@ -218,513 +96,27 @@ void CheatsManager::CreateWidgets()
setLayout(layout);
}
void CheatsManager::OnNewSessionCreated(const Cheats::CheatSearchSessionBase& session)
{
auto* w = new CheatSearchWidget(session.Clone());
const int tab_index = m_tab_widget->addTab(w, tr("Cheat Search"));
w->connect(w, &CheatSearchWidget::ActionReplayCodeGenerated, this,
[this](const ActionReplay::ARCode& ar_code) {
if (m_ar_code)
m_ar_code->AddCode(ar_code);
});
m_tab_widget->setCurrentIndex(tab_index);
}
void CheatsManager::OnTabCloseRequested(int index)
{
m_tab_widget->removeTab(index);
}
void CheatsManager::ConnectWidgets()
{
connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
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);
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);
menu->addAction(tr("Remove from Watch"), this, [this] {
auto* item = m_watch_table->selectedItems()[0];
int index = item->data(INDEX_ROLE).toInt();
m_watch.erase(m_watch.begin() + index);
Update();
});
menu->addSeparator();
menu->addAction(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);
menu->addAction(tr("Add to Watch"), this, [this] {
auto* item = m_match_table->selectedItems()[0];
int index = item->data(INDEX_ROLE).toInt();
m_results[index].locked_value = GetResultValue(m_results[index]);
m_watch.push_back(m_results[index]);
Update();
});
menu->exec(QCursor::pos());
}
void CheatsManager::GenerateARCode()
{
if (!m_ar_code)
return;
auto* item = m_watch_table->selectedItems()[0];
int index = item->data(INDEX_ROLE).toInt();
ActionReplay::ARCode ar_code;
ar_code.enabled = 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 2:
m_watch[index].locked = item->checkState() == Qt::Checked;
if (m_watch[index].locked)
m_watch[index].locked_value = GetResultValue(m_results[index]);
UpdatePatch(m_watch[index]);
break;
case 3:
{
const auto text = item->text();
u32 value = 0;
switch (m_watch[index].type)
{
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;
UpdatePatch(m_watch[index]);
break;
}
}
Update();
}
QWidget* CheatsManager::CreateCheatSearch()
{
m_match_table = new QTableWidget;
m_watch_table = new QTableWidget;
m_match_table->setTabKeyNavigation(false);
m_watch_table->setTabKeyNavigation(false);
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);
// i18n: The base 10 numeral system. Not related to non-integer numbers
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();
}
}
std::function<bool(u32)> CheatsManager::CreateMatchFunction()
{
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());
const int base =
(m_match_decimal->isChecked() ? 10 : (m_match_hexadecimal->isChecked() ? 16 : 8));
bool conversion_succeeded = false;
std::function<bool(u32)> matches_func;
switch (static_cast<DataType>(m_match_length->currentIndex()))
{
case DataType::Byte:
{
u8 comparison_value = text.toUShort(&conversion_succeeded, base) & 0xFF;
matches_func = [=](u32 addr) {
return Compare<u8>(PowerPC::HostRead_U8(addr), comparison_value, op);
};
break;
}
case DataType::Short:
{
u16 comparison_value = text.toUShort(&conversion_succeeded, base);
matches_func = [=](u32 addr) {
return Compare(PowerPC::HostRead_U16(addr), comparison_value, op);
};
break;
}
case DataType::Int:
{
u32 comparison_value = text.toUInt(&conversion_succeeded, base);
matches_func = [=](u32 addr) {
return Compare(PowerPC::HostRead_U32(addr), comparison_value, op);
};
break;
}
case DataType::Float:
{
float comparison_value = text.toFloat(&conversion_succeeded);
matches_func = [=](u32 addr) {
return Compare(PowerPC::HostRead_F32(addr), comparison_value, op);
};
break;
}
case DataType::Double:
{
double comparison_value = text.toDouble(&conversion_succeeded);
matches_func = [=](u32 addr) {
return Compare(PowerPC::HostRead_F64(addr), comparison_value, op);
};
break;
}
case DataType::String:
{
if (op != CompareType::Equal && op != CompareType::NotEqual)
{
m_result_label->setText(tr("String values can only be compared using equality."));
return nullptr;
}
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;
}
}
if (conversion_succeeded)
return matches_func;
m_result_label->setText(tr("Cannot interpret the given value.\nHave you chosen the right type?"));
return nullptr;
}
void CheatsManager::NewSearch()
{
m_results.clear();
const u32 base_address = 0x80000000;
if (!Memory::m_pRAM)
{
m_result_label->setText(tr("Memory Not Ready"));
return;
}
std::function<bool(u32)> matches_func = CreateMatchFunction();
if (matches_func == nullptr)
return;
Core::RunAsCPUThread([&] {
for (u32 i = 0; i < Memory::GetRamSizeReal() - GetTypeSize(); i++)
{
if (PowerPC::HostIsRAMAddress(base_address + i) && matches_func(base_address + i))
m_results.push_back(
{base_address + i, static_cast<DataType>(m_match_length->currentIndex())});
}
});
m_match_next->setEnabled(true);
Update();
}
void CheatsManager::NextSearch()
{
if (!Memory::m_pRAM)
{
m_result_label->setText(tr("Memory Not Ready"));
return;
}
std::function<bool(u32)> matches_func = CreateMatchFunction();
if (matches_func == nullptr)
return;
Core::RunAsCPUThread([this, matches_func] {
m_results.erase(std::remove_if(m_results.begin(), m_results.end(),
[matches_func](Result r) {
return !PowerPC::HostIsRAMAddress(r.address) ||
!matches_func(r.address);
}),
m_results.end());
});
Update();
}
static QString GetResultString(const Result& result)
{
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");
default:
return {};
}
}
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("%n Match(es)", "", static_cast<int>(m_results.size())));
m_match_table->setRowCount(static_cast<int>(m_results.size()));
if (m_results.empty())
return;
m_updating = true;
Core::RunAsCPUThread([this] {
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);
value_item->setText(GetResultString(m_results[i]));
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;
value_item->setText(GetResultString(m_results[i]));
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);
lock_item->setData(INDEX_ROLE, static_cast<int>(i));
lock_item->setData(COLUMN_ROLE, 2);
value_item->setData(INDEX_ROLE, static_cast<int>(i));
value_item->setData(COLUMN_ROLE, 3);
name_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
address_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
lock_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsSelectable);
value_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
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->clear();
Update();
connect(m_cheat_search_new, &CheatSearchFactoryWidget::NewSessionCreated, this,
&CheatsManager::OnNewSessionCreated);
connect(m_tab_widget, &QTabWidget::tabCloseRequested, this, &CheatsManager::OnTabCloseRequested);
}

View File

@ -5,6 +5,7 @@
#include <functional>
#include <memory>
#include <optional>
#include <vector>
#include <QDialog>
@ -12,29 +13,19 @@
#include "Common/CommonTypes.h"
#include "DolphinQt/GameList/GameListModel.h"
#include "Core/CheatSearch.h"
class ARCodeWidget;
class QComboBox;
class GeckoCodeWidget;
class CheatSearchFactoryWidget;
class QDialogButtonBox;
class QLabel;
class QLineEdit;
class QPushButton;
class QRadioButton;
class QSplitter;
class QTabWidget;
class QTableWidget;
class QTableWidgetItem;
struct Result;
class PartiallyClosableTabWidget;
namespace Core
{
enum class State;
}
namespace UICommon
{
class GameFile;
}
class CheatsManager : public QDialog
{
Q_OBJECT
@ -43,51 +34,20 @@ public:
~CheatsManager();
private:
QWidget* CreateCheatSearch();
void CreateWidgets();
void ConnectWidgets();
void OnStateChanged(Core::State state);
size_t GetTypeSize() const;
std::function<bool(u32)> CreateMatchFunction();
void Reset();
void NewSearch();
void NextSearch();
void Update();
void GenerateARCode();
void OnWatchContextMenu();
void OnMatchContextMenu();
void OnWatchItemChanged(QTableWidgetItem* item);
void OnNewSessionCreated(const Cheats::CheatSearchSessionBase& session);
void OnTabCloseRequested(int index);
std::string m_game_id;
std::string m_game_tdb_id;
u16 m_revision = 0;
std::vector<Result> m_results;
std::vector<Result> m_watch;
QDialogButtonBox* m_button_box;
QTabWidget* m_tab_widget = nullptr;
PartiallyClosableTabWidget* 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;
GeckoCodeWidget* m_gecko_code = nullptr;
CheatSearchFactoryWidget* m_cheat_search_new = nullptr;
};

View File

@ -45,6 +45,8 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="AboutDialog.cpp" />
<ClCompile Include="CheatSearchFactoryWidget.cpp" />
<ClCompile Include="CheatSearchWidget.cpp" />
<ClCompile Include="CheatsManager.cpp" />
<ClCompile Include="Config\ARCodeWidget.cpp" />
<ClCompile Include="Config\CheatCodeEditor.cpp" />
@ -168,6 +170,7 @@
<ClCompile Include="QtUtils\FlowLayout.cpp" />
<ClCompile Include="QtUtils\ImageConverter.cpp" />
<ClCompile Include="QtUtils\ModalMessageBox.cpp" />
<ClCompile Include="QtUtils\PartiallyClosableTabWidget.cpp" />
<ClCompile Include="QtUtils\UTF8CodePointCountValidator.cpp" />
<ClCompile Include="QtUtils\WindowActivationEventFilter.cpp" />
<ClCompile Include="QtUtils\WinIconHelper.cpp" />
@ -227,6 +230,8 @@
<ClInclude Include="Translation.h" />
<ClInclude Include="WiiUpdate.h" />
<QtMoc Include="AboutDialog.h" />
<QtMoc Include="CheatSearchFactoryWidget.h" />
<QtMoc Include="CheatSearchWidget.h" />
<QtMoc Include="CheatsManager.h" />
<QtMoc Include="Config\ARCodeWidget.h" />
<QtMoc Include="Config\CheatWarningWidget.h" />
@ -340,6 +345,7 @@
<QtMoc Include="QtUtils\ElidedButton.h" />
<QtMoc Include="QtUtils\FileOpenEventFilter.h" />
<QtMoc Include="QtUtils\ParallelProgressDialog.h" />
<QtMoc Include="QtUtils\PartiallyClosableTabWidget.h" />
<QtMoc Include="QtUtils\UTF8CodePointCountValidator.h" />
<QtMoc Include="QtUtils\WindowActivationEventFilter.h" />
<QtMoc Include="RenderWidget.h" />

View File

@ -0,0 +1,19 @@
// Copyright 2021 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "DolphinQt/QtUtils/PartiallyClosableTabWidget.h"
#include <QStyle>
#include <QTabBar>
PartiallyClosableTabWidget::PartiallyClosableTabWidget(QWidget* parent) : QTabWidget(parent)
{
setTabsClosable(true);
}
void PartiallyClosableTabWidget::setTabUnclosable(int index)
{
QTabBar::ButtonPosition closeSide = (QTabBar::ButtonPosition)style()->styleHint(
QStyle::SH_TabBar_CloseButtonPosition, nullptr, this);
tabBar()->setTabButton(index, closeSide, nullptr);
}

View File

@ -0,0 +1,17 @@
// Copyright 2021 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QTabWidget>
class QEvent;
class PartiallyClosableTabWidget : public QTabWidget
{
Q_OBJECT
public:
PartiallyClosableTabWidget(QWidget* parent = nullptr);
void setTabUnclosable(int index);
};