2017-09-27 06:53:05 +00:00
|
|
|
// Copyright 2017 Dolphin Emulator Project
|
2021-07-05 01:22:19 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
2017-09-27 06:53:05 +00:00
|
|
|
|
2018-07-06 22:40:15 +00:00
|
|
|
#include "DolphinQt/Debugger/WatchWidget.h"
|
2017-09-27 06:53:05 +00:00
|
|
|
|
2019-03-04 19:49:00 +00:00
|
|
|
#include <QHeaderView>
|
2021-02-20 14:03:42 +00:00
|
|
|
#include <QInputDialog>
|
2019-03-04 19:49:00 +00:00
|
|
|
#include <QMenu>
|
|
|
|
#include <QTableWidget>
|
|
|
|
#include <QToolBar>
|
|
|
|
#include <QVBoxLayout>
|
|
|
|
|
2017-09-27 06:53:05 +00:00
|
|
|
#include "Common/FileUtil.h"
|
|
|
|
#include "Common/IniFile.h"
|
|
|
|
#include "Core/ConfigManager.h"
|
|
|
|
#include "Core/Core.h"
|
2018-05-17 21:09:55 +00:00
|
|
|
#include "Core/PowerPC/MMU.h"
|
2017-09-27 06:53:05 +00:00
|
|
|
#include "Core/PowerPC/PowerPC.h"
|
2018-04-11 21:43:47 +00:00
|
|
|
|
2020-04-14 22:12:35 +00:00
|
|
|
#include "DolphinQt/Host.h"
|
2019-03-04 19:49:00 +00:00
|
|
|
#include "DolphinQt/QtUtils/ModalMessageBox.h"
|
2018-07-06 22:40:15 +00:00
|
|
|
#include "DolphinQt/Resources.h"
|
|
|
|
#include "DolphinQt/Settings.h"
|
2017-09-27 06:53:05 +00:00
|
|
|
|
|
|
|
WatchWidget::WatchWidget(QWidget* parent) : QDockWidget(parent)
|
|
|
|
{
|
2018-01-21 15:25:58 +00:00
|
|
|
// i18n: This kind of "watch" is used for watching emulated memory.
|
|
|
|
// It's not related to timekeeping devices.
|
2017-09-27 06:53:05 +00:00
|
|
|
setWindowTitle(tr("Watch"));
|
2018-04-19 09:32:00 +00:00
|
|
|
setObjectName(QStringLiteral("watch"));
|
|
|
|
|
2019-02-28 20:19:04 +00:00
|
|
|
setHidden(!Settings::Instance().IsWatchVisible() || !Settings::Instance().IsDebugModeEnabled());
|
|
|
|
|
2017-09-27 06:53:05 +00:00
|
|
|
setAllowedAreas(Qt::AllDockWidgetAreas);
|
|
|
|
|
2020-02-09 07:32:04 +00:00
|
|
|
CreateWidgets();
|
|
|
|
|
2018-03-23 11:10:53 +00:00
|
|
|
auto& settings = Settings::GetQSettings();
|
2017-09-27 06:53:05 +00:00
|
|
|
|
|
|
|
restoreGeometry(settings.value(QStringLiteral("watchwidget/geometry")).toByteArray());
|
2019-02-28 20:19:04 +00:00
|
|
|
// macOS: setHidden() needs to be evaluated before setFloating() for proper window presentation
|
|
|
|
// according to Settings
|
2017-09-27 06:53:05 +00:00
|
|
|
setFloating(settings.value(QStringLiteral("watchwidget/floating")).toBool());
|
|
|
|
|
|
|
|
ConnectWidgets();
|
|
|
|
|
2020-09-12 22:53:17 +00:00
|
|
|
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, [this](Core::State state) {
|
2019-07-06 08:50:11 +00:00
|
|
|
UpdateButtonsEnabled();
|
2017-09-27 06:53:05 +00:00
|
|
|
if (state != Core::State::Starting)
|
|
|
|
Update();
|
|
|
|
});
|
|
|
|
|
2020-04-14 22:12:35 +00:00
|
|
|
connect(Host::GetInstance(), &Host::UpdateDisasmDialog, this, &WatchWidget::Update);
|
|
|
|
|
2020-09-12 22:53:17 +00:00
|
|
|
connect(&Settings::Instance(), &Settings::WatchVisibilityChanged, this,
|
2017-09-27 06:53:05 +00:00
|
|
|
[this](bool visible) { setHidden(!visible); });
|
|
|
|
|
2020-09-12 22:53:17 +00:00
|
|
|
connect(&Settings::Instance(), &Settings::DebugModeToggled, this,
|
2017-09-27 06:53:05 +00:00
|
|
|
[this](bool enabled) { setHidden(!enabled || !Settings::Instance().IsWatchVisible()); });
|
|
|
|
|
2018-04-11 21:43:47 +00:00
|
|
|
connect(&Settings::Instance(), &Settings::ThemeChanged, this, &WatchWidget::UpdateIcons);
|
|
|
|
UpdateIcons();
|
2017-09-27 06:53:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
WatchWidget::~WatchWidget()
|
|
|
|
{
|
2018-03-23 11:10:53 +00:00
|
|
|
auto& settings = Settings::GetQSettings();
|
2017-09-27 06:53:05 +00:00
|
|
|
|
|
|
|
settings.setValue(QStringLiteral("watchwidget/geometry"), saveGeometry());
|
|
|
|
settings.setValue(QStringLiteral("watchwidget/floating"), isFloating());
|
|
|
|
}
|
|
|
|
|
|
|
|
void WatchWidget::CreateWidgets()
|
|
|
|
{
|
|
|
|
m_toolbar = new QToolBar;
|
2018-08-02 19:50:23 +00:00
|
|
|
m_toolbar->setContentsMargins(0, 0, 0, 0);
|
2018-04-11 21:43:47 +00:00
|
|
|
m_toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
|
|
|
|
|
2017-09-27 06:53:05 +00:00
|
|
|
m_table = new QTableWidget;
|
2020-02-07 02:34:27 +00:00
|
|
|
m_table->setTabKeyNavigation(false);
|
2017-09-27 06:53:05 +00:00
|
|
|
|
2018-08-02 19:50:23 +00:00
|
|
|
m_table->setContentsMargins(0, 0, 0, 0);
|
2019-06-28 21:04:23 +00:00
|
|
|
m_table->setColumnCount(NUM_COLUMNS);
|
2017-09-27 06:53:05 +00:00
|
|
|
m_table->verticalHeader()->setHidden(true);
|
|
|
|
m_table->setContextMenuPolicy(Qt::CustomContextMenu);
|
2022-12-23 09:11:17 +00:00
|
|
|
m_table->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
|
|
|
m_table->setSelectionBehavior(QAbstractItemView::SelectRows);
|
2017-09-27 06:53:05 +00:00
|
|
|
|
2021-02-20 14:03:42 +00:00
|
|
|
m_new = m_toolbar->addAction(tr("New"), this, &WatchWidget::OnNewWatch);
|
|
|
|
m_delete = m_toolbar->addAction(tr("Delete"), this, &WatchWidget::OnDelete);
|
|
|
|
m_clear = m_toolbar->addAction(tr("Clear"), this, &WatchWidget::OnClear);
|
2018-07-09 08:02:10 +00:00
|
|
|
m_load = m_toolbar->addAction(tr("Load"), this, &WatchWidget::OnLoad);
|
|
|
|
m_save = m_toolbar->addAction(tr("Save"), this, &WatchWidget::OnSave);
|
2017-09-27 06:53:05 +00:00
|
|
|
|
2021-02-20 14:03:42 +00:00
|
|
|
m_new->setEnabled(false);
|
|
|
|
m_delete->setEnabled(false);
|
|
|
|
m_clear->setEnabled(false);
|
2017-09-27 06:53:05 +00:00
|
|
|
m_load->setEnabled(false);
|
|
|
|
m_save->setEnabled(false);
|
|
|
|
|
|
|
|
auto* layout = new QVBoxLayout;
|
2018-08-02 19:50:23 +00:00
|
|
|
layout->setContentsMargins(2, 2, 2, 2);
|
|
|
|
layout->setSpacing(0);
|
2017-09-27 06:53:05 +00:00
|
|
|
layout->addWidget(m_toolbar);
|
|
|
|
layout->addWidget(m_table);
|
|
|
|
|
|
|
|
QWidget* widget = new QWidget;
|
|
|
|
widget->setLayout(layout);
|
|
|
|
|
|
|
|
setWidget(widget);
|
|
|
|
}
|
|
|
|
|
|
|
|
void WatchWidget::ConnectWidgets()
|
|
|
|
{
|
|
|
|
connect(m_table, &QTableWidget::customContextMenuRequested, this, &WatchWidget::ShowContextMenu);
|
|
|
|
connect(m_table, &QTableWidget::itemChanged, this, &WatchWidget::OnItemChanged);
|
|
|
|
}
|
|
|
|
|
2018-04-11 21:43:47 +00:00
|
|
|
void WatchWidget::UpdateIcons()
|
|
|
|
{
|
2021-02-20 14:03:42 +00:00
|
|
|
// TODO: Create a "debugger_add_watch" icon
|
|
|
|
m_new->setIcon(Resources::GetScaledThemeIcon("debugger_add_breakpoint"));
|
|
|
|
m_delete->setIcon(Resources::GetScaledThemeIcon("debugger_delete"));
|
|
|
|
m_clear->setIcon(Resources::GetScaledThemeIcon("debugger_clear"));
|
2018-04-11 21:43:47 +00:00
|
|
|
m_load->setIcon(Resources::GetScaledThemeIcon("debugger_load"));
|
|
|
|
m_save->setIcon(Resources::GetScaledThemeIcon("debugger_save"));
|
|
|
|
}
|
|
|
|
|
2019-07-06 08:50:11 +00:00
|
|
|
void WatchWidget::UpdateButtonsEnabled()
|
|
|
|
{
|
|
|
|
if (!isVisible())
|
|
|
|
return;
|
|
|
|
|
2021-02-20 14:03:42 +00:00
|
|
|
const bool is_enabled = Core::IsRunning();
|
|
|
|
m_new->setEnabled(is_enabled);
|
|
|
|
m_delete->setEnabled(is_enabled);
|
|
|
|
m_clear->setEnabled(is_enabled);
|
|
|
|
m_load->setEnabled(is_enabled);
|
|
|
|
m_save->setEnabled(is_enabled);
|
2019-07-06 08:50:11 +00:00
|
|
|
}
|
|
|
|
|
2017-09-27 06:53:05 +00:00
|
|
|
void WatchWidget::Update()
|
|
|
|
{
|
2019-07-06 08:50:11 +00:00
|
|
|
if (!isVisible())
|
|
|
|
return;
|
|
|
|
|
2017-09-27 06:53:05 +00:00
|
|
|
m_updating = true;
|
|
|
|
|
|
|
|
m_table->clear();
|
|
|
|
|
2018-01-29 16:58:02 +00:00
|
|
|
int size = static_cast<int>(PowerPC::debug_interface.GetWatches().size());
|
2017-09-27 06:53:05 +00:00
|
|
|
|
|
|
|
m_table->setRowCount(size + 1);
|
|
|
|
|
2018-09-08 13:58:15 +00:00
|
|
|
m_table->setHorizontalHeaderLabels(
|
|
|
|
{tr("Label"), tr("Address"), tr("Hexadecimal"),
|
|
|
|
// i18n: The base 10 numeral system. Not related to non-integer numbers
|
|
|
|
tr("Decimal"),
|
|
|
|
// i18n: Data type used in computing
|
2019-06-28 21:04:23 +00:00
|
|
|
tr("String"),
|
|
|
|
// i18n: Floating-point (non-integer) number
|
2022-12-23 05:17:40 +00:00
|
|
|
tr("Float"), tr("Locked")});
|
2017-09-27 06:53:05 +00:00
|
|
|
|
2023-02-12 10:07:11 +00:00
|
|
|
Core::CPUThreadGuard guard;
|
|
|
|
|
2017-09-27 06:53:05 +00:00
|
|
|
for (int i = 0; i < size; i++)
|
|
|
|
{
|
2022-12-23 05:17:40 +00:00
|
|
|
const auto& entry = PowerPC::debug_interface.GetWatch(i);
|
2017-09-27 06:53:05 +00:00
|
|
|
|
|
|
|
auto* label = new QTableWidgetItem(QString::fromStdString(entry.name));
|
|
|
|
auto* address =
|
|
|
|
new QTableWidgetItem(QStringLiteral("%1").arg(entry.address, 8, 16, QLatin1Char('0')));
|
|
|
|
auto* hex = new QTableWidgetItem;
|
|
|
|
auto* decimal = new QTableWidgetItem;
|
|
|
|
auto* string = new QTableWidgetItem;
|
2019-06-28 21:04:23 +00:00
|
|
|
auto* floatValue = new QTableWidgetItem;
|
|
|
|
|
2022-12-23 05:17:40 +00:00
|
|
|
auto* lockValue = new QTableWidgetItem;
|
|
|
|
lockValue->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable);
|
|
|
|
|
|
|
|
std::array<QTableWidgetItem*, NUM_COLUMNS> items = {label, address, hex, decimal,
|
|
|
|
string, floatValue, lockValue};
|
2017-09-27 06:53:05 +00:00
|
|
|
|
|
|
|
QBrush brush = QPalette().brush(QPalette::Text);
|
|
|
|
|
2023-02-12 10:07:11 +00:00
|
|
|
if (!Core::IsRunning() || !PowerPC::HostIsRAMAddress(guard, entry.address))
|
2017-09-27 06:53:05 +00:00
|
|
|
brush.setColor(Qt::red);
|
|
|
|
|
|
|
|
if (Core::IsRunning())
|
|
|
|
{
|
2023-02-12 10:07:11 +00:00
|
|
|
if (PowerPC::HostIsRAMAddress(guard, entry.address))
|
2017-09-27 06:53:05 +00:00
|
|
|
{
|
2023-02-12 10:07:11 +00:00
|
|
|
hex->setText(QStringLiteral("%1").arg(PowerPC::HostRead_U32(guard, entry.address), 8, 16,
|
2017-09-27 06:53:05 +00:00
|
|
|
QLatin1Char('0')));
|
2023-02-12 10:07:11 +00:00
|
|
|
decimal->setText(QString::number(PowerPC::HostRead_U32(guard, entry.address)));
|
|
|
|
string->setText(QString::fromStdString(PowerPC::HostGetString(guard, entry.address, 32)));
|
|
|
|
floatValue->setText(QString::number(PowerPC::HostRead_F32(guard, entry.address)));
|
2022-12-23 05:17:40 +00:00
|
|
|
lockValue->setCheckState(entry.locked ? Qt::Checked : Qt::Unchecked);
|
2017-09-27 06:53:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
address->setForeground(brush);
|
2019-06-28 21:04:23 +00:00
|
|
|
string->setFlags(Qt::ItemIsEnabled);
|
2017-09-27 06:53:05 +00:00
|
|
|
|
2019-06-28 21:04:23 +00:00
|
|
|
for (int column = 0; column < NUM_COLUMNS; column++)
|
2017-09-27 06:53:05 +00:00
|
|
|
{
|
2019-06-28 21:04:23 +00:00
|
|
|
auto* item = items[column];
|
2017-09-27 06:53:05 +00:00
|
|
|
item->setData(Qt::UserRole, i);
|
2019-06-28 21:04:23 +00:00
|
|
|
item->setData(Qt::UserRole + 1, column);
|
|
|
|
m_table->setItem(i, column, item);
|
2017-09-27 06:53:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
auto* label = new QTableWidgetItem;
|
|
|
|
label->setData(Qt::UserRole, -1);
|
|
|
|
|
|
|
|
m_table->setItem(size, 0, label);
|
|
|
|
|
2019-06-28 21:04:23 +00:00
|
|
|
for (int i = 1; i < NUM_COLUMNS; i++)
|
2017-09-27 06:53:05 +00:00
|
|
|
{
|
|
|
|
auto* no_edit = new QTableWidgetItem;
|
|
|
|
no_edit->setFlags(Qt::ItemIsEnabled);
|
|
|
|
m_table->setItem(size, i, no_edit);
|
|
|
|
}
|
|
|
|
|
|
|
|
m_updating = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void WatchWidget::closeEvent(QCloseEvent*)
|
|
|
|
{
|
|
|
|
Settings::Instance().SetWatchVisible(false);
|
|
|
|
}
|
2017-10-03 16:43:44 +00:00
|
|
|
|
2019-07-06 08:50:11 +00:00
|
|
|
void WatchWidget::showEvent(QShowEvent* event)
|
|
|
|
{
|
|
|
|
UpdateButtonsEnabled();
|
|
|
|
Update();
|
|
|
|
}
|
|
|
|
|
2021-02-20 14:03:42 +00:00
|
|
|
void WatchWidget::OnDelete()
|
|
|
|
{
|
|
|
|
if (m_table->selectedItems().empty())
|
|
|
|
return;
|
|
|
|
|
2022-12-23 09:11:17 +00:00
|
|
|
DeleteSelectedWatches();
|
2021-02-20 14:03:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void WatchWidget::OnClear()
|
|
|
|
{
|
|
|
|
PowerPC::debug_interface.ClearWatches();
|
|
|
|
Update();
|
|
|
|
}
|
|
|
|
|
|
|
|
void WatchWidget::OnNewWatch()
|
|
|
|
{
|
2021-08-21 17:35:21 +00:00
|
|
|
const QString text =
|
|
|
|
QInputDialog::getText(this, tr("Input"), tr("Enter address to watch:"), QLineEdit::Normal,
|
|
|
|
QString{}, nullptr, Qt::WindowCloseButtonHint);
|
2021-02-20 14:03:42 +00:00
|
|
|
bool good;
|
2021-08-21 17:35:21 +00:00
|
|
|
const uint address = text.toUInt(&good, 16);
|
2021-02-20 14:03:42 +00:00
|
|
|
|
|
|
|
if (!good)
|
|
|
|
{
|
|
|
|
ModalMessageBox::warning(this, tr("Error"), tr("Invalid watch address: %1").arg(text));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const QString name = QStringLiteral("mem_%1").arg(address, 8, 16, QLatin1Char('0'));
|
|
|
|
AddWatch(name, address);
|
|
|
|
}
|
|
|
|
|
2017-09-27 06:53:05 +00:00
|
|
|
void WatchWidget::OnLoad()
|
|
|
|
{
|
|
|
|
IniFile ini;
|
|
|
|
|
2018-01-29 16:58:02 +00:00
|
|
|
std::vector<std::string> watches;
|
2017-09-27 06:53:05 +00:00
|
|
|
|
|
|
|
if (!ini.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + SConfig::GetInstance().GetGameID() + ".ini",
|
|
|
|
false))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-02-12 10:07:11 +00:00
|
|
|
Core::CPUThreadGuard guard;
|
|
|
|
|
2017-09-27 06:53:05 +00:00
|
|
|
if (ini.GetLines("Watches", &watches, false))
|
|
|
|
{
|
2022-12-23 05:17:40 +00:00
|
|
|
for (const auto& watch : PowerPC::debug_interface.GetWatches())
|
|
|
|
{
|
2023-02-12 10:07:11 +00:00
|
|
|
PowerPC::debug_interface.UnsetPatch(guard, watch.address);
|
2022-12-23 05:17:40 +00:00
|
|
|
}
|
2018-01-29 16:58:02 +00:00
|
|
|
PowerPC::debug_interface.ClearWatches();
|
|
|
|
PowerPC::debug_interface.LoadWatchesFromStrings(watches);
|
2017-09-27 06:53:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Update();
|
|
|
|
}
|
|
|
|
|
|
|
|
void WatchWidget::OnSave()
|
|
|
|
{
|
|
|
|
IniFile ini;
|
|
|
|
ini.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + SConfig::GetInstance().GetGameID() + ".ini",
|
|
|
|
false);
|
2018-01-29 16:58:02 +00:00
|
|
|
ini.SetLines("Watches", PowerPC::debug_interface.SaveWatchesToStrings());
|
2017-09-27 06:53:05 +00:00
|
|
|
ini.Save(File::GetUserPath(D_GAMESETTINGS_IDX) + SConfig::GetInstance().GetGameID() + ".ini");
|
|
|
|
}
|
|
|
|
|
|
|
|
void WatchWidget::ShowContextMenu()
|
|
|
|
{
|
|
|
|
QMenu* menu = new QMenu(this);
|
|
|
|
|
2019-02-12 22:47:17 +00:00
|
|
|
if (!m_table->selectedItems().empty())
|
2017-09-27 06:53:05 +00:00
|
|
|
{
|
2022-12-23 09:11:17 +00:00
|
|
|
const std::size_t count = m_table->selectionModel()->selectedRows().count();
|
|
|
|
if (count > 1)
|
|
|
|
{
|
2023-01-23 19:49:22 +00:00
|
|
|
// i18n: This kind of "watch" is used for watching emulated memory.
|
|
|
|
// It's not related to timekeeping devices.
|
2022-12-23 09:11:17 +00:00
|
|
|
menu->addAction(tr("&Delete Watches"), this, [this] { DeleteSelectedWatches(); });
|
2023-01-23 19:49:22 +00:00
|
|
|
// i18n: This kind of "watch" is used for watching emulated memory.
|
|
|
|
// It's not related to timekeeping devices.
|
2022-12-23 09:11:17 +00:00
|
|
|
menu->addAction(tr("&Lock Watches"), this, [this] { LockSelectedWatches(); });
|
2023-01-23 19:49:22 +00:00
|
|
|
// i18n: This kind of "watch" is used for watching emulated memory.
|
|
|
|
// It's not related to timekeeping devices.
|
2022-12-23 09:11:17 +00:00
|
|
|
menu->addAction(tr("&Unlock Watches"), this, [this] { UnlockSelectedWatches(); });
|
|
|
|
}
|
|
|
|
else if (count == 1)
|
2017-09-27 06:53:05 +00:00
|
|
|
{
|
2022-12-23 09:11:17 +00:00
|
|
|
auto row_variant = m_table->selectedItems()[0]->data(Qt::UserRole);
|
2017-09-27 06:53:05 +00:00
|
|
|
|
2022-12-23 09:11:17 +00:00
|
|
|
if (!row_variant.isNull())
|
2017-09-27 06:53:05 +00:00
|
|
|
{
|
2022-12-23 09:11:17 +00:00
|
|
|
int row = row_variant.toInt();
|
|
|
|
|
|
|
|
if (row >= 0)
|
|
|
|
{
|
|
|
|
menu->addAction(tr("Show in Memory"), this, [this, row] { ShowInMemory(row); });
|
|
|
|
// i18n: This kind of "watch" is used for watching emulated memory.
|
|
|
|
// It's not related to timekeeping devices.
|
|
|
|
menu->addAction(tr("&Delete Watch"), this, [this, row] { DeleteWatchAndUpdate(row); });
|
|
|
|
menu->addAction(tr("&Add Memory Breakpoint"), this,
|
|
|
|
[this, row] { AddWatchBreakpoint(row); });
|
|
|
|
}
|
2017-09-27 06:53:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
menu->addSeparator();
|
|
|
|
|
2018-07-09 08:02:10 +00:00
|
|
|
menu->addAction(tr("Update"), this, &WatchWidget::Update);
|
2017-09-27 06:53:05 +00:00
|
|
|
|
|
|
|
menu->exec(QCursor::pos());
|
|
|
|
}
|
|
|
|
|
|
|
|
void WatchWidget::OnItemChanged(QTableWidgetItem* item)
|
|
|
|
{
|
|
|
|
if (m_updating || item->data(Qt::UserRole).isNull())
|
|
|
|
return;
|
|
|
|
|
|
|
|
int row = item->data(Qt::UserRole).toInt();
|
|
|
|
int column = item->data(Qt::UserRole + 1).toInt();
|
|
|
|
|
|
|
|
if (row == -1)
|
|
|
|
{
|
|
|
|
if (!item->text().isEmpty())
|
|
|
|
{
|
|
|
|
AddWatch(item->text(), 0);
|
|
|
|
|
|
|
|
Update();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
switch (column)
|
|
|
|
{
|
2022-12-23 02:50:16 +00:00
|
|
|
case COLUMN_INDEX_LABEL:
|
2017-09-27 06:53:05 +00:00
|
|
|
if (item->text().isEmpty())
|
2022-12-23 09:11:17 +00:00
|
|
|
DeleteWatchAndUpdate(row);
|
2017-09-27 06:53:05 +00:00
|
|
|
else
|
2018-01-29 16:58:02 +00:00
|
|
|
PowerPC::debug_interface.UpdateWatchName(row, item->text().toStdString());
|
2017-09-27 06:53:05 +00:00
|
|
|
break;
|
2022-12-23 02:50:16 +00:00
|
|
|
case COLUMN_INDEX_ADDRESS:
|
|
|
|
case COLUMN_INDEX_HEX:
|
|
|
|
case COLUMN_INDEX_DECIMAL:
|
2017-09-27 06:53:05 +00:00
|
|
|
{
|
|
|
|
bool good;
|
2022-12-23 02:50:16 +00:00
|
|
|
const bool column_uses_hex_formatting =
|
|
|
|
column == COLUMN_INDEX_ADDRESS || column == COLUMN_INDEX_HEX;
|
|
|
|
quint32 value = item->text().toUInt(&good, column_uses_hex_formatting ? 16 : 10);
|
2017-09-27 06:53:05 +00:00
|
|
|
|
|
|
|
if (good)
|
|
|
|
{
|
2023-02-12 10:07:11 +00:00
|
|
|
Core::CPUThreadGuard guard;
|
|
|
|
|
2022-12-23 02:50:16 +00:00
|
|
|
if (column == COLUMN_INDEX_ADDRESS)
|
2022-12-23 05:17:40 +00:00
|
|
|
{
|
|
|
|
const auto& watch = PowerPC::debug_interface.GetWatch(row);
|
2023-02-12 10:07:11 +00:00
|
|
|
PowerPC::debug_interface.UnsetPatch(guard, watch.address);
|
2018-01-29 16:58:02 +00:00
|
|
|
PowerPC::debug_interface.UpdateWatchAddress(row, value);
|
2022-12-23 05:17:40 +00:00
|
|
|
if (watch.locked)
|
2023-02-12 10:07:11 +00:00
|
|
|
LockWatchAddress(guard, value);
|
2022-12-23 05:17:40 +00:00
|
|
|
}
|
2017-09-27 06:53:05 +00:00
|
|
|
else
|
2022-12-23 05:17:40 +00:00
|
|
|
{
|
2023-02-12 10:07:11 +00:00
|
|
|
PowerPC::HostWrite_U32(guard, value, PowerPC::debug_interface.GetWatch(row).address);
|
2022-12-23 05:17:40 +00:00
|
|
|
}
|
2017-09-27 06:53:05 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2019-03-04 19:49:00 +00:00
|
|
|
ModalMessageBox::critical(this, tr("Error"), tr("Invalid input provided"));
|
2017-09-27 06:53:05 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2022-12-23 05:17:40 +00:00
|
|
|
case COLUMN_INDEX_LOCK:
|
|
|
|
{
|
|
|
|
PowerPC::debug_interface.UpdateWatchLockedState(row, item->checkState() == Qt::Checked);
|
|
|
|
const auto& watch = PowerPC::debug_interface.GetWatch(row);
|
2023-02-12 10:07:11 +00:00
|
|
|
Core::CPUThreadGuard guard;
|
2022-12-23 05:17:40 +00:00
|
|
|
if (watch.locked)
|
2023-02-12 10:07:11 +00:00
|
|
|
LockWatchAddress(guard, watch.address);
|
2022-12-23 05:17:40 +00:00
|
|
|
else
|
2023-02-12 10:07:11 +00:00
|
|
|
PowerPC::debug_interface.UnsetPatch(guard, watch.address);
|
2022-12-23 05:17:40 +00:00
|
|
|
break;
|
|
|
|
}
|
2017-09-27 06:53:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Update();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-12 10:07:11 +00:00
|
|
|
void WatchWidget::LockWatchAddress(const Core::CPUThreadGuard& guard, u32 address)
|
2022-12-23 05:17:40 +00:00
|
|
|
{
|
2023-02-12 10:07:11 +00:00
|
|
|
const std::string memory_data_as_string = PowerPC::HostGetString(guard, address, 4);
|
2022-12-23 05:17:40 +00:00
|
|
|
|
|
|
|
std::vector<u8> bytes;
|
|
|
|
for (const char c : memory_data_as_string)
|
|
|
|
{
|
|
|
|
bytes.push_back(static_cast<u8>(c));
|
|
|
|
}
|
|
|
|
|
2023-02-12 10:07:11 +00:00
|
|
|
PowerPC::debug_interface.SetFramePatch(guard, address, bytes);
|
2022-12-23 05:17:40 +00:00
|
|
|
}
|
|
|
|
|
2022-12-23 09:11:17 +00:00
|
|
|
void WatchWidget::DeleteSelectedWatches()
|
|
|
|
{
|
|
|
|
{
|
2023-02-12 10:07:11 +00:00
|
|
|
Core::CPUThreadGuard guard;
|
|
|
|
std::vector<int> row_indices;
|
|
|
|
for (const auto& index : m_table->selectionModel()->selectedRows())
|
|
|
|
{
|
|
|
|
const auto* item = m_table->item(index.row(), index.column());
|
|
|
|
const auto row_variant = item->data(Qt::UserRole);
|
|
|
|
if (row_variant.isNull())
|
|
|
|
continue;
|
2022-12-23 09:11:17 +00:00
|
|
|
|
2023-02-12 10:07:11 +00:00
|
|
|
row_indices.push_back(row_variant.toInt());
|
|
|
|
}
|
2022-12-23 09:11:17 +00:00
|
|
|
|
2023-02-12 10:07:11 +00:00
|
|
|
// Sort greatest to smallest, so we don't stomp on existing indices
|
|
|
|
std::sort(row_indices.begin(), row_indices.end(), std::greater{});
|
|
|
|
for (const int row : row_indices)
|
|
|
|
{
|
|
|
|
DeleteWatch(guard, row);
|
|
|
|
}
|
2022-12-23 09:11:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Update();
|
|
|
|
}
|
|
|
|
|
2023-02-12 10:07:11 +00:00
|
|
|
void WatchWidget::DeleteWatch(const Core::CPUThreadGuard& guard, int row)
|
2017-09-27 06:53:05 +00:00
|
|
|
{
|
2023-02-12 10:07:11 +00:00
|
|
|
PowerPC::debug_interface.UnsetPatch(guard, PowerPC::debug_interface.GetWatch(row).address);
|
2018-01-29 16:58:02 +00:00
|
|
|
PowerPC::debug_interface.RemoveWatch(row);
|
2022-12-23 09:11:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void WatchWidget::DeleteWatchAndUpdate(int row)
|
|
|
|
{
|
2023-02-12 10:07:11 +00:00
|
|
|
{
|
|
|
|
Core::CPUThreadGuard guard;
|
|
|
|
DeleteWatch(guard, row);
|
|
|
|
}
|
|
|
|
|
2017-09-27 06:53:05 +00:00
|
|
|
Update();
|
|
|
|
}
|
|
|
|
|
|
|
|
void WatchWidget::AddWatchBreakpoint(int row)
|
|
|
|
{
|
2018-01-29 16:58:02 +00:00
|
|
|
emit RequestMemoryBreakpoint(PowerPC::debug_interface.GetWatch(row).address);
|
2017-09-27 06:53:05 +00:00
|
|
|
}
|
|
|
|
|
2022-05-28 00:34:47 +00:00
|
|
|
void WatchWidget::ShowInMemory(int row)
|
|
|
|
{
|
|
|
|
emit ShowMemory(PowerPC::debug_interface.GetWatch(row).address);
|
|
|
|
}
|
|
|
|
|
2017-09-27 06:53:05 +00:00
|
|
|
void WatchWidget::AddWatch(QString name, u32 addr)
|
|
|
|
{
|
2018-01-29 16:58:02 +00:00
|
|
|
PowerPC::debug_interface.SetWatch(addr, name.toStdString());
|
2020-03-21 07:48:49 +00:00
|
|
|
Update();
|
2017-09-27 06:53:05 +00:00
|
|
|
}
|
2022-12-23 09:11:17 +00:00
|
|
|
|
|
|
|
void WatchWidget::LockSelectedWatches()
|
|
|
|
{
|
|
|
|
{
|
2023-02-12 10:07:11 +00:00
|
|
|
Core::CPUThreadGuard guard;
|
|
|
|
for (const auto& index : m_table->selectionModel()->selectedRows())
|
|
|
|
{
|
|
|
|
const auto* item = m_table->item(index.row(), index.column());
|
|
|
|
const auto row_variant = item->data(Qt::UserRole);
|
|
|
|
if (row_variant.isNull())
|
|
|
|
continue;
|
|
|
|
const int row = row_variant.toInt();
|
|
|
|
const auto& watch = PowerPC::debug_interface.GetWatch(row);
|
|
|
|
if (watch.locked)
|
|
|
|
continue;
|
|
|
|
PowerPC::debug_interface.UpdateWatchLockedState(row, true);
|
|
|
|
LockWatchAddress(guard, watch.address);
|
|
|
|
}
|
2022-12-23 09:11:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Update();
|
|
|
|
}
|
|
|
|
|
|
|
|
void WatchWidget::UnlockSelectedWatches()
|
|
|
|
{
|
|
|
|
{
|
2023-02-12 10:07:11 +00:00
|
|
|
Core::CPUThreadGuard guard;
|
|
|
|
for (const auto& index : m_table->selectionModel()->selectedRows())
|
|
|
|
{
|
|
|
|
const auto* item = m_table->item(index.row(), index.column());
|
|
|
|
const auto row_variant = item->data(Qt::UserRole);
|
|
|
|
if (row_variant.isNull())
|
|
|
|
continue;
|
|
|
|
const int row = row_variant.toInt();
|
|
|
|
const auto& watch = PowerPC::debug_interface.GetWatch(row);
|
|
|
|
if (!watch.locked)
|
|
|
|
continue;
|
|
|
|
PowerPC::debug_interface.UpdateWatchLockedState(row, false);
|
|
|
|
PowerPC::debug_interface.UnsetPatch(guard, watch.address);
|
|
|
|
}
|
2022-12-23 09:11:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Update();
|
|
|
|
}
|