666 lines
24 KiB
C++
666 lines
24 KiB
C++
// Copyright 2021 Dolphin Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#include "DolphinQt/CheatSearchWidget.h"
|
|
|
|
#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 <QSettings>
|
|
#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/Core.h"
|
|
#include "Core/PowerPC/PowerPC.h"
|
|
#include "Core/System.h"
|
|
|
|
#include "DolphinQt/Config/CheatCodeEditor.h"
|
|
#include "DolphinQt/Config/CheatWarningWidget.h"
|
|
#include "DolphinQt/QtUtils/WrapInScrollArea.h"
|
|
#include "DolphinQt/Settings.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(Core::System& system,
|
|
std::unique_ptr<Cheats::CheatSearchSessionBase> session,
|
|
QWidget* parent)
|
|
: QWidget(parent), m_system(system), m_session(std::move(session))
|
|
{
|
|
setAttribute(Qt::WA_DeleteOnClose);
|
|
CreateWidgets();
|
|
ConnectWidgets();
|
|
OnValueSourceChanged();
|
|
RecreateGUITable();
|
|
}
|
|
|
|
CheatSearchWidget::~CheatSearchWidget()
|
|
{
|
|
auto& settings = Settings::GetQSettings();
|
|
settings.setValue(QStringLiteral("cheatsearchwidget/displayhex"),
|
|
m_display_values_in_hex_checkbox->isChecked());
|
|
settings.setValue(QStringLiteral("cheatsearchwidget/autoupdatecurrentvalues"),
|
|
m_autoupdate_current_values->isChecked());
|
|
|
|
if (m_session->IsIntegerType())
|
|
{
|
|
settings.setValue(QStringLiteral("cheatsearchwidget/parsehex"),
|
|
m_parse_values_as_hex_checkbox->isChecked());
|
|
}
|
|
}
|
|
|
|
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));
|
|
session_info_label->setWordWrap(true);
|
|
}
|
|
|
|
// i18n: This label is followed by a dropdown where the user can select things like "is equal to"
|
|
// or "is less than or equal to", followed by another dropdown where the user can select "any
|
|
// value", "last value", or "this value:". These three UI elements are intended to form a sentence
|
|
// together. Because the UI elements can't be reordered by a translation, you may have to give
|
|
// up on the idea of having them form a sentence depending on the grammar of your target language.
|
|
auto* instructions_label = new QLabel(tr("Keep addresses where value in memory"));
|
|
|
|
auto* value_layout = new QHBoxLayout();
|
|
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& settings = Settings::GetQSettings();
|
|
m_parse_values_as_hex_checkbox = new QCheckBox(tr("Parse as Hex"));
|
|
if (m_session->IsIntegerType())
|
|
{
|
|
m_parse_values_as_hex_checkbox->setChecked(
|
|
settings.value(QStringLiteral("cheatsearchwidget/parsehex")).toBool());
|
|
value_layout->addWidget(m_parse_values_as_hex_checkbox);
|
|
}
|
|
|
|
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();
|
|
|
|
auto* const checkboxes_layout = new QHBoxLayout();
|
|
m_display_values_in_hex_checkbox = new QCheckBox(tr("Display values in Hex"));
|
|
m_display_values_in_hex_checkbox->setChecked(
|
|
settings.value(QStringLiteral("cheatsearchwidget/displayhex")).toBool());
|
|
checkboxes_layout->addWidget(m_display_values_in_hex_checkbox);
|
|
checkboxes_layout->setStretchFactor(m_display_values_in_hex_checkbox, 1);
|
|
|
|
m_autoupdate_current_values = new QCheckBox(tr("Automatically update Current Values"));
|
|
m_autoupdate_current_values->setChecked(
|
|
settings.value(QStringLiteral("cheatsearchwidget/autoupdatecurrentvalues"), true).toBool());
|
|
checkboxes_layout->addWidget(m_autoupdate_current_values);
|
|
checkboxes_layout->setStretchFactor(m_autoupdate_current_values, 2);
|
|
|
|
QVBoxLayout* layout = new QVBoxLayout();
|
|
layout->addWidget(session_info_label);
|
|
layout->addWidget(instructions_label);
|
|
layout->addLayout(value_layout);
|
|
layout->addLayout(button_layout);
|
|
layout->addLayout(checkboxes_layout);
|
|
layout->addWidget(m_info_label_1);
|
|
layout->addWidget(m_info_label_2);
|
|
layout->addWidget(m_address_table);
|
|
|
|
WrapInScrollArea(this, 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::OnDisplayHexCheckboxStateChanged);
|
|
}
|
|
|
|
void CheatSearchWidget::OnNextScanClicked()
|
|
{
|
|
Core::CPUThreadGuard guard{m_system};
|
|
|
|
const bool had_old_results = m_session->WasFirstSearchDone();
|
|
|
|
const auto filter_type = m_value_source_dropdown->currentData().value<Cheats::FilterType>();
|
|
if (filter_type == Cheats::FilterType::CompareAgainstLastValue && !had_old_results)
|
|
{
|
|
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)
|
|
{
|
|
QString search_value = m_given_value_text->text();
|
|
if (m_session->IsIntegerType() || m_session->IsFloatingType())
|
|
search_value = search_value.simplified().remove(QLatin1Char(' '));
|
|
if (!m_session->SetValueFromString(search_value.toStdString(),
|
|
m_parse_values_as_hex_checkbox->isChecked()))
|
|
{
|
|
m_info_label_1->setText(tr("Failed to parse given value into target data type."));
|
|
return;
|
|
}
|
|
}
|
|
|
|
const size_t old_count = m_session->GetResultCount();
|
|
const Cheats::SearchErrorCode error_code = m_session->RunSearch(guard);
|
|
|
|
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);
|
|
}
|
|
|
|
RecreateGUITable();
|
|
}
|
|
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::UpdateTableRows(const Core::CPUThreadGuard& guard, const size_t begin_index,
|
|
const size_t end_index, const UpdateSource source)
|
|
{
|
|
const bool update_status_text = source == UpdateSource::User;
|
|
|
|
auto tmp = m_session->ClonePartial(begin_index, end_index);
|
|
tmp->SetFilterType(Cheats::FilterType::DoNotFilter);
|
|
|
|
const Cheats::SearchErrorCode error_code = tmp->RunSearch(guard);
|
|
if (error_code != Cheats::SearchErrorCode::Success)
|
|
{
|
|
if (update_status_text)
|
|
m_info_label_1->setText(tr("Refresh failed. Please run the game for a bit and try again."));
|
|
return false;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
RefreshGUICurrentValues(begin_index, end_index);
|
|
if (update_status_text)
|
|
m_info_label_1->setText(tr("Refreshed current values."));
|
|
return true;
|
|
}
|
|
|
|
void CheatSearchWidget::UpdateTableVisibleCurrentValues(const UpdateSource source)
|
|
{
|
|
if (source == UpdateSource::Auto && !m_autoupdate_current_values->isChecked())
|
|
return;
|
|
|
|
if (m_address_table->rowCount() == 0)
|
|
return;
|
|
|
|
UpdateTableRows(Core::CPUThreadGuard{m_system}, GetVisibleRowsBeginIndex(),
|
|
GetVisibleRowsEndIndex(), source);
|
|
}
|
|
|
|
bool CheatSearchWidget::UpdateTableAllCurrentValues(const UpdateSource source)
|
|
{
|
|
if (source == UpdateSource::Auto && !m_autoupdate_current_values->isChecked())
|
|
return false;
|
|
|
|
const size_t result_count = m_address_table->rowCount();
|
|
if (result_count == 0)
|
|
{
|
|
if (source == UpdateSource::User)
|
|
m_info_label_1->setText(tr("Cannot refresh without results."));
|
|
return false;
|
|
}
|
|
|
|
return UpdateTableRows(Core::CPUThreadGuard{m_system}, 0, result_count, source);
|
|
}
|
|
|
|
void CheatSearchWidget::OnRefreshClicked()
|
|
{
|
|
UpdateTableAllCurrentValues(UpdateSource::User);
|
|
}
|
|
|
|
void CheatSearchWidget::OnResetClicked()
|
|
{
|
|
m_session->ResetResults();
|
|
m_address_table_current_values.clear();
|
|
|
|
RecreateGUITable();
|
|
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;
|
|
}
|
|
}
|
|
|
|
int CheatSearchWidget::GetVisibleRowsBeginIndex() const
|
|
{
|
|
return m_address_table->rowAt(0);
|
|
}
|
|
|
|
int CheatSearchWidget::GetVisibleRowsEndIndex() const
|
|
{
|
|
const int row_at_bottom_of_viewport = m_address_table->rowAt(m_address_table->height());
|
|
const int end_index = m_address_table->rowCount();
|
|
return row_at_bottom_of_viewport == -1 ? end_index : row_at_bottom_of_viewport + 1;
|
|
}
|
|
|
|
void CheatSearchWidget::OnAddressTableContextMenu()
|
|
{
|
|
if (m_address_table->selectedItems().isEmpty())
|
|
return;
|
|
|
|
auto* item = m_address_table->selectedItems()[0];
|
|
const u32 address = item->data(ADDRESS_TABLE_ADDRESS_ROLE).toUInt();
|
|
|
|
QMenu* menu = new QMenu(this);
|
|
menu->setAttribute(Qt::WA_DeleteOnClose, true);
|
|
|
|
menu->addAction(tr("Show in memory"), [this, address] { emit ShowMemory(address); });
|
|
menu->addAction(tr("Add to watch"), this, [this, address] {
|
|
const QString name = QStringLiteral("mem_%1").arg(address, 8, 16, QLatin1Char('0'));
|
|
emit RequestWatch(name, address);
|
|
});
|
|
menu->addAction(tr("Generate Action Replay Code(s)"), this, &CheatSearchWidget::GenerateARCodes);
|
|
|
|
menu->exec(QCursor::pos());
|
|
}
|
|
|
|
void CheatSearchWidget::OnValueSourceChanged()
|
|
{
|
|
const auto filter_type = m_value_source_dropdown->currentData().value<Cheats::FilterType>();
|
|
const bool is_value_search = filter_type == Cheats::FilterType::CompareAgainstSpecificValue;
|
|
m_given_value_text->setEnabled(is_value_search);
|
|
m_parse_values_as_hex_checkbox->setEnabled(is_value_search && m_session->IsIntegerType());
|
|
}
|
|
|
|
void CheatSearchWidget::OnDisplayHexCheckboxStateChanged()
|
|
{
|
|
if (!m_session->WasFirstSearchDone())
|
|
return;
|
|
|
|
// If the game is running CheatsManager::OnFrameEnd will update values automatically.
|
|
if (Core::GetState(m_system) != Core::State::Running)
|
|
UpdateTableAllCurrentValues(UpdateSource::User);
|
|
}
|
|
|
|
void CheatSearchWidget::GenerateARCodes()
|
|
{
|
|
if (m_address_table->selectedItems().isEmpty())
|
|
return;
|
|
|
|
bool had_success = false;
|
|
bool had_error = false;
|
|
std::optional<Cheats::GenerateActionReplayCodeErrorCode> error_code;
|
|
|
|
for (auto* const item : m_address_table->selectedItems())
|
|
{
|
|
const u32 index = item->data(ADDRESS_TABLE_RESULT_INDEX_ROLE).toUInt();
|
|
auto result = Cheats::GenerateActionReplayCode(*m_session, index);
|
|
if (result)
|
|
{
|
|
emit ActionReplayCodeGenerated(*result);
|
|
had_success = true;
|
|
}
|
|
else
|
|
{
|
|
const auto new_error_code = result.Error();
|
|
if (!had_error)
|
|
{
|
|
error_code = new_error_code;
|
|
}
|
|
else if (error_code != new_error_code)
|
|
{
|
|
// If we have a different error code signify multiple errors with an empty optional<>.
|
|
error_code.reset();
|
|
}
|
|
|
|
had_error = true;
|
|
}
|
|
}
|
|
|
|
if (had_error)
|
|
{
|
|
if (error_code.has_value())
|
|
{
|
|
switch (*error_code)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_info_label_1->setText(tr("Multiple errors while generating AR codes."));
|
|
}
|
|
}
|
|
else if (had_success)
|
|
{
|
|
m_info_label_1->setText(tr("Generated AR code(s)."));
|
|
}
|
|
}
|
|
|
|
void CheatSearchWidget::RefreshCurrentValueTableItem(
|
|
QTableWidgetItem* const current_value_table_item)
|
|
{
|
|
const auto address = current_value_table_item->data(ADDRESS_TABLE_ADDRESS_ROLE).toUInt();
|
|
const auto curr_val_iter = m_address_table_current_values.find(address);
|
|
if (curr_val_iter != m_address_table_current_values.end())
|
|
current_value_table_item->setText(QString::fromStdString(curr_val_iter->second));
|
|
else
|
|
current_value_table_item->setText(QStringLiteral("---"));
|
|
}
|
|
|
|
void CheatSearchWidget::RefreshGUICurrentValues(const size_t begin_index, const size_t end_index)
|
|
{
|
|
for (size_t i = begin_index; i < end_index; ++i)
|
|
{
|
|
QTableWidgetItem* const current_value_table_item =
|
|
m_address_table->item(static_cast<int>(i), ADDRESS_TABLE_COLUMN_INDEX_CURRENT_VALUE);
|
|
RefreshCurrentValueTableItem(current_value_table_item);
|
|
}
|
|
}
|
|
|
|
void CheatSearchWidget::RecreateGUITable()
|
|
{
|
|
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);
|
|
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);
|
|
RefreshCurrentValueTableItem(current_value_item);
|
|
}
|
|
}
|