summaryrefslogtreecommitdiff
path: root/Source/Core/DolphinQt
diff options
context:
space:
mode:
authorTryTwo <taolas@gmail.com>2025-07-18 22:14:08 -0700
committerTryTwo <taolas@gmail.com>2025-07-30 17:40:24 -0700
commitf7e7b0f6b0e2b1aa895eaeec2ed37ada1bf4a21e (patch)
tree9cc3f8d57b0efb33ce20407e34d505479981d92f /Source/Core/DolphinQt
parentb2b2808d014fec0c8510ebbf6e91c850c7417925 (diff)
MemoryWidget: Add symbols and Notes.
Add option to hide them. Add box to search. Add ability to edit data symbols and notes in MemoryViewWidget.
Diffstat (limited to 'Source/Core/DolphinQt')
-rw-r--r--Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp138
-rw-r--r--Source/Core/DolphinQt/Debugger/MemoryViewWidget.h16
-rw-r--r--Source/Core/DolphinQt/Debugger/MemoryWidget.cpp141
-rw-r--r--Source/Core/DolphinQt/Debugger/MemoryWidget.h20
4 files changed, 309 insertions, 6 deletions
diff --git a/Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp b/Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp
index 2cd85627b2..9977f3f38b 100644
--- a/Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp
+++ b/Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp
@@ -29,8 +29,10 @@
#include "Core/Core.h"
#include "Core/HW/AddressSpace.h"
#include "Core/PowerPC/BreakPoints.h"
+#include "Core/PowerPC/PPCSymbolDB.h"
#include "Core/PowerPC/PowerPC.h"
#include "Core/System.h"
+#include "DolphinQt/Debugger/EditSymbolDialog.h"
#include "DolphinQt/Host.h"
#include "DolphinQt/Resources.h"
#include "DolphinQt/Settings.h"
@@ -196,7 +198,7 @@ private:
};
MemoryViewWidget::MemoryViewWidget(Core::System& system, QWidget* parent)
- : QWidget(parent), m_system(system)
+ : QWidget(parent), m_system(system), m_ppc_symbol_db(m_system.GetPPCSymbolDB())
{
auto* layout = new QHBoxLayout();
layout->setContentsMargins(0, 0, 0, 0);
@@ -220,6 +222,8 @@ MemoryViewWidget::MemoryViewWidget(Core::System& system, QWidget* parent)
this->setLayout(layout);
connect(&Settings::Instance(), &Settings::DebugFontChanged, this, &MemoryViewWidget::UpdateFont);
+ connect(Host::GetInstance(), &Host::PPCSymbolsChanged, this,
+ [this] { UpdateDispatcher(UpdateType::Symbols); });
connect(Host::GetInstance(), &Host::PPCBreakpointsChanged, this,
&MemoryViewWidget::UpdateBreakpointTags);
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, [this] {
@@ -347,6 +351,9 @@ void MemoryViewWidget::UpdateDispatcher(UpdateType type)
if (Core::GetState(m_system) == Core::State::Paused)
GetValues();
UpdateColumns();
+ [[fallthrough]];
+ case UpdateType::Symbols:
+ UpdateSymbols();
break;
case UpdateType::Auto:
// Values were captured on CPU thread while doing a callback.
@@ -371,7 +378,7 @@ void MemoryViewWidget::CreateTable()
// Span is the number of unique memory values covered in one row.
const int data_span = m_bytes_per_row / GetTypeSize(m_type);
m_data_columns = m_dual_view ? data_span * 2 : data_span;
- const int total_columns = MISC_COLUMNS + m_data_columns;
+ const int total_columns = MISC_COLUMNS + m_data_columns + (m_show_symbols ? 1 : 0);
const int rows =
std::round((m_table->height() / static_cast<float>(m_table->rowHeight(0))) - 0.25);
@@ -440,6 +447,15 @@ void MemoryViewWidget::CreateTable()
m_table->setItem(i, c + MISC_COLUMNS, item.clone());
}
+
+ if (!m_show_symbols)
+ continue;
+
+ // Symbols
+ auto* description_item = new QTableWidgetItem(QStringLiteral("-"));
+ description_item->setFlags(Qt::ItemIsEnabled);
+
+ m_table->setItem(i, m_table->columnCount() - 1, description_item);
}
// Update column width
@@ -500,6 +516,9 @@ void MemoryViewWidget::Update()
item->setBackground(Qt::transparent);
item->setData(USER_ROLE_VALID_ADDRESS, false);
}
+
+ if (m_show_symbols)
+ m_table->item(i, m_table->columnCount() - 1)->setData(USER_ROLE_CELL_ADDRESS, row_address);
}
UpdateBreakpointTags();
@@ -576,6 +595,34 @@ void MemoryViewWidget::UpdateColumns()
}
}
+void MemoryViewWidget::UpdateSymbols()
+{
+ if (!m_show_symbols)
+ return;
+
+ // Update symbols
+ for (int i = 0; i < m_table->rowCount(); i++)
+ {
+ auto* item = m_table->item(i, m_table->columnCount() - 1);
+ if (!item)
+ continue;
+
+ const u32 address = item->data(USER_ROLE_CELL_ADDRESS).toUInt();
+ const Common::Note* note = m_ppc_symbol_db.GetNoteFromAddr(address);
+
+ std::string desc;
+ if (note == nullptr)
+ desc = m_ppc_symbol_db.GetDescription(address);
+ else
+ desc = note->name;
+
+ item->setText(QString::fromStdString(" " + desc));
+ }
+
+ if (m_show_symbols)
+ m_table->resizeColumnToContents(m_table->columnCount() - 1);
+}
+
// Always runs on CPU thread from a callback.
void MemoryViewWidget::UpdateOnFrameEnd()
{
@@ -1059,6 +1106,78 @@ void MemoryViewWidget::OnCopyHex(u32 addr)
QStringLiteral("%1").arg(value, sizeof(u64) * 2, 16, QLatin1Char('0')).left(length * 2));
}
+void MemoryViewWidget::ShowSymbols(bool enable)
+{
+ m_show_symbols = enable;
+ UpdateDispatcher(UpdateType::Full);
+}
+
+void MemoryViewWidget::OnEditSymbol(EditSymbolType type, u32 addr)
+{
+ // Add Note and Add Region use these values.
+ std::string name = "";
+ std::string object_name = "";
+ u32 size = GetTypeSize(m_type);
+ u32 address = addr;
+ EditSymbolDialog::Type dialog_type = EditSymbolDialog::Type::Note;
+
+ // Add and edit region are tied to the same context menu action.
+ if (type == EditSymbolType::EditRegion)
+ {
+ // If symbol doesn't exist, it's safe to add a new region.
+ const Common::Symbol* const symbol = m_ppc_symbol_db.GetSymbolFromAddr(addr);
+ dialog_type = EditSymbolDialog::Type::Symbol;
+
+ if (symbol != nullptr)
+ {
+ // Leave the more specialized function editing to code widget.
+ if (symbol->type != Common::Symbol::Type::Data)
+ return;
+
+ // Edit data region.
+ name = symbol->name;
+ object_name = symbol->object_name;
+ size = symbol->size;
+ address = symbol->address;
+ }
+ }
+ else if (type == EditSymbolType::EditNote)
+ {
+ const Common::Note* note = m_ppc_symbol_db.GetNoteFromAddr(addr);
+ if (note == nullptr)
+ return;
+
+ name = note->name;
+ size = note->size;
+ address = note->address;
+ }
+
+ EditSymbolDialog dialog(this, address, &size, &name, dialog_type);
+
+ if (dialog.exec() != QDialog::Accepted)
+ return;
+
+ if (dialog.DeleteRequested())
+ {
+ if (type == EditSymbolType::EditRegion)
+ m_ppc_symbol_db.DeleteFunction(address);
+ else
+ m_ppc_symbol_db.DeleteNote(address);
+ }
+ else if (type == EditSymbolType::EditRegion)
+ {
+ m_ppc_symbol_db.AddKnownSymbol(Core::CPUThreadGuard{m_system}, address, size, name, object_name,
+ Common::Symbol::Type::Data);
+ }
+ else
+ {
+ m_ppc_symbol_db.AddKnownNote(address, size, name);
+ m_ppc_symbol_db.DetermineNoteLayers();
+ }
+
+ emit Host::GetInstance()->PPCSymbolsChanged();
+}
+
void MemoryViewWidget::OnContextMenu(const QPoint& pos)
{
auto* item_selected = m_table->itemAt(pos);
@@ -1089,6 +1208,21 @@ void MemoryViewWidget::OnContextMenu(const QPoint& pos)
menu->addSeparator();
+ auto* note_add_action = menu->addAction(
+ tr("Add Note"), this, [this, addr] { OnEditSymbol(EditSymbolType::AddNote, addr); });
+ auto* note_edit_action = menu->addAction(
+ tr("Edit Note"), this, [this, addr] { OnEditSymbol(EditSymbolType::EditNote, addr); });
+ menu->addAction(tr("Add or edit region label"), this,
+ [this, addr] { OnEditSymbol(EditSymbolType::EditRegion, addr); });
+
+ auto* note = m_ppc_symbol_db.GetNoteFromAddr(addr);
+ note_edit_action->setEnabled(note != nullptr);
+ // A note cannot be added ontop of the starting address of another note.
+ if (note != nullptr && note->address == addr)
+ note_add_action->setEnabled(false);
+
+ menu->addSeparator();
+
menu->addAction(tr("Show in code"), this, [this, addr] { emit ShowCode(addr); });
menu->addSeparator();
diff --git a/Source/Core/DolphinQt/Debugger/MemoryViewWidget.h b/Source/Core/DolphinQt/Debugger/MemoryViewWidget.h
index 46643e39a4..102d6e0aed 100644
--- a/Source/Core/DolphinQt/Debugger/MemoryViewWidget.h
+++ b/Source/Core/DolphinQt/Debugger/MemoryViewWidget.h
@@ -24,6 +24,8 @@ class CPUThreadGuard;
class System;
} // namespace Core
+class PPCSymbolDB;
+
// Captures direct editing of the table.
class TableEditDelegate : public QStyledItemDelegate
{
@@ -76,14 +78,23 @@ public:
Full,
Addresses,
Values,
+ Symbols,
Auto,
};
+ enum class EditSymbolType
+ {
+ AddNote,
+ EditNote,
+ EditRegion,
+ };
+
explicit MemoryViewWidget(Core::System& system, QWidget* parent = nullptr);
void CreateTable();
void UpdateDispatcher(UpdateType type = UpdateType::Addresses);
void Update();
+ void UpdateSymbols();
void UpdateOnFrameEnd();
void GetValues();
void UpdateFont(const QFont& font);
@@ -98,6 +109,7 @@ public:
void SetBPType(BPType type);
void SetAddress(u32 address);
void SetFocus() const;
+ void ShowSymbols(bool enable);
void SetBPLoggingEnabled(bool enabled);
@@ -108,6 +120,7 @@ signals:
void ActivateSearch();
private:
+ void OnEditSymbol(EditSymbolType type, u32 addr);
void OnContextMenu(const QPoint& pos);
void OnCopyAddress(u32 addr);
void OnCopyHex(u32 addr);
@@ -116,9 +129,11 @@ private:
void UpdateColumns();
void ScrollbarActionTriggered(int action);
void ScrollbarSliderReleased();
+
std::optional<QString> ValueToString(const Core::CPUThreadGuard& guard, u32 address, Type type);
Core::System& m_system;
+ PPCSymbolDB& m_ppc_symbol_db;
MemoryViewTable* m_table;
QScrollBar* m_scrollbar;
@@ -137,6 +152,7 @@ private:
int m_alignment = 16;
int m_data_columns;
bool m_dual_view = false;
+ bool m_show_symbols = true;
std::mutex m_updating;
QColor m_highlight_color = QColor(120, 255, 255, 100);
diff --git a/Source/Core/DolphinQt/Debugger/MemoryWidget.cpp b/Source/Core/DolphinQt/Debugger/MemoryWidget.cpp
index 50ecbf5dd9..7e68df9c7a 100644
--- a/Source/Core/DolphinQt/Debugger/MemoryWidget.cpp
+++ b/Source/Core/DolphinQt/Debugger/MemoryWidget.cpp
@@ -17,6 +17,7 @@
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
+#include <QListWidget>
#include <QMenuBar>
#include <QPushButton>
#include <QRegularExpression>
@@ -32,6 +33,7 @@
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/HW/AddressSpace.h"
+#include "Core/PowerPC/PPCSymbolDB.h"
#include "Core/System.h"
#include "DolphinQt/Debugger/MemoryViewWidget.h"
#include "DolphinQt/Host.h"
@@ -41,7 +43,7 @@
using Type = MemoryViewWidget::Type;
MemoryWidget::MemoryWidget(Core::System& system, QWidget* parent)
- : QDockWidget(parent), m_system(system)
+ : QDockWidget(parent), m_system(system), m_ppc_symbol_db(system.GetPPCSymbolDB())
{
setWindowTitle(tr("Memory"));
setObjectName(QStringLiteral("memory"));
@@ -247,6 +249,25 @@ void MemoryWidget::CreateWidgets()
bp_layout->addWidget(m_bp_log_check);
bp_layout->setSpacing(1);
+ // Notes
+ m_labels_group = new QGroupBox(tr("Labels"));
+ auto* symbols_box = new QTabWidget;
+ m_note_list = new QListWidget;
+ m_data_list = new QListWidget;
+ m_symbols_list = new QListWidget;
+ symbols_box->addTab(m_note_list, tr("Notes"));
+ symbols_box->addTab(m_data_list, tr("Data"));
+ symbols_box->addTab(m_symbols_list, tr("Symbols"));
+ m_symbols_list->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+
+ auto* labels_layout = new QVBoxLayout;
+ m_search_labels = new QLineEdit;
+ m_search_labels->setPlaceholderText(tr("Filter Label List"));
+
+ m_labels_group->setLayout(labels_layout);
+ labels_layout->addWidget(symbols_box);
+ labels_layout->addWidget(m_search_labels);
+
// Sidebar
auto* sidebar = new QWidget;
auto* sidebar_layout = new QVBoxLayout;
@@ -262,8 +283,9 @@ void MemoryWidget::CreateWidgets()
&MemoryWidget::OnSetValueFromFile);
menubar->addMenu(menu_import);
+ // View Menu
auto* auto_update_action =
- menu_views->addAction(tr("Auto update memory values"), this, [this](bool checked) {
+ menu_views->addAction(tr("&Auto update memory values"), this, [this](bool checked) {
m_auto_update_enabled = checked;
if (checked)
RegisterAfterFrameEventCallback();
@@ -274,14 +296,23 @@ void MemoryWidget::CreateWidgets()
auto_update_action->setChecked(true);
auto* highlight_update_action =
- menu_views->addAction(tr("Highlight recently changed values"), this,
+ menu_views->addAction(tr("&Highlight recently changed values"), this,
[this](bool checked) { m_memory_view->ToggleHighlights(checked); });
highlight_update_action->setCheckable(true);
highlight_update_action->setChecked(true);
- menu_views->addAction(tr("Highlight color"), this,
+ menu_views->addAction(tr("Highlight &color"), this,
[this] { m_memory_view->SetHighlightColor(); });
+ auto* show_notes =
+ menu_views->addAction(tr("&Show symbols and notes"), this, [this](bool checked) {
+ m_labels_visible = checked;
+ m_memory_view->ShowSymbols(checked);
+ UpdateNotes();
+ });
+ show_notes->setCheckable(true);
+ show_notes->setChecked(true);
+
QMenu* menu_export = new QMenu(tr("&Export"), menubar);
menu_export->addAction(tr("Dump &MRAM"), this, &MemoryWidget::OnDumpMRAM);
menu_export->addAction(tr("Dump &ExRAM"), this, &MemoryWidget::OnDumpExRAM);
@@ -306,6 +337,7 @@ void MemoryWidget::CreateWidgets()
sidebar_layout->addWidget(address_space_group);
sidebar_layout->addItem(new QSpacerItem(1, 10));
sidebar_layout->addWidget(bp_group);
+ sidebar_layout->addWidget(m_labels_group);
sidebar_layout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Expanding));
// Splitter
@@ -327,6 +359,7 @@ void MemoryWidget::CreateWidgets()
auto* widget = new QWidget;
widget->setLayout(layout);
setWidget(widget);
+ UpdateNotes();
}
void MemoryWidget::ConnectWidgets()
@@ -359,6 +392,12 @@ void MemoryWidget::ConnectWidgets()
connect(m_base_check, &QCheckBox::toggled, this, &MemoryWidget::ValidateAndPreviewInputValue);
connect(m_bp_log_check, &QCheckBox::toggled, this, &MemoryWidget::OnBPLogChanged);
+
+ for (auto* list : {m_symbols_list, m_data_list, m_note_list})
+ connect(list, &QListWidget::itemClicked, this, &MemoryWidget::OnSelectLabel);
+
+ connect(Host::GetInstance(), &Host::PPCSymbolsChanged, this, &MemoryWidget::RefreshLabelBox);
+ connect(m_search_labels, &QLineEdit::textChanged, this, &MemoryWidget::RefreshLabelBox);
connect(m_memory_view, &MemoryViewWidget::ShowCode, this, &MemoryWidget::ShowCode);
connect(m_memory_view, &MemoryViewWidget::RequestWatch, this, &MemoryWidget::RequestWatch);
connect(m_memory_view, &MemoryViewWidget::ActivateSearch, this,
@@ -795,6 +834,100 @@ void MemoryWidget::OnSetValueFromFile()
Update();
}
+void MemoryWidget::RefreshLabelBox()
+{
+ if (!m_labels_visible || (m_ppc_symbol_db.Notes().empty() && m_ppc_symbol_db.IsEmpty()))
+ {
+ m_labels_group->hide();
+ return;
+ }
+
+ m_labels_group->show();
+
+ UpdateSymbols();
+ UpdateNotes();
+}
+
+void MemoryWidget::OnSelectLabel()
+{
+ QList<QListWidgetItem*> items;
+ if (m_note_list->isVisible())
+ items = m_note_list->selectedItems();
+ else if (m_symbols_list->isVisible())
+ items = m_symbols_list->selectedItems();
+ else if (m_data_list->isVisible())
+ items = m_data_list->selectedItems();
+
+ if (items.isEmpty())
+ return;
+
+ const u32 address = items[0]->data(Qt::UserRole).toUInt();
+
+ SetAddress(address);
+}
+
+void MemoryWidget::UpdateSymbols()
+{
+ const QString selection = m_symbols_list->selectedItems().isEmpty() ?
+ QString{} :
+ m_symbols_list->selectedItems()[0]->text();
+ m_symbols_list->clear();
+ m_data_list->clear();
+
+ for (const auto& symbol : m_ppc_symbol_db.Symbols())
+ {
+ QString name = QString::fromStdString(symbol.second.name);
+
+ // If the symbol has an object name, add it to the entry name.
+ if (!symbol.second.object_name.empty())
+ {
+ name += QString::fromStdString(fmt::format(" ({})", symbol.second.object_name));
+ }
+
+ auto* item = new QListWidgetItem(name);
+ if (name == selection)
+ item->setSelected(true);
+
+ item->setData(Qt::UserRole, symbol.second.address);
+
+ if (!name.contains(m_search_labels->text(), Qt::CaseInsensitive))
+ continue;
+
+ if (symbol.second.type != Common::Symbol::Type::Function)
+ m_data_list->addItem(item);
+ else
+ m_symbols_list->addItem(item);
+ }
+
+ m_symbols_list->sortItems();
+}
+
+void MemoryWidget::UpdateNotes()
+{
+ // Save selection to re-apply.
+ const QString selection = m_note_list->selectedItems().isEmpty() ?
+ QStringLiteral("") :
+ m_note_list->selectedItems()[0]->text();
+ m_note_list->clear();
+
+ for (const auto& note : m_ppc_symbol_db.Notes())
+ {
+ const QString name = QString::fromStdString(note.second.name);
+
+ auto* item = new QListWidgetItem(name);
+ if (name == selection)
+ item->setSelected(true);
+
+ item->setData(Qt::UserRole, note.second.address);
+
+ // Filter notes based on the search text.
+ if (name.contains(m_search_labels->text(), Qt::CaseInsensitive))
+ m_note_list->addItem(item);
+ }
+
+ m_note_list->sortItems();
+}
+
static void DumpArray(const std::string& filename, const u8* data, size_t length)
{
if (!data)
diff --git a/Source/Core/DolphinQt/Debugger/MemoryWidget.h b/Source/Core/DolphinQt/Debugger/MemoryWidget.h
index 6aa1f01aa5..e1b0118844 100644
--- a/Source/Core/DolphinQt/Debugger/MemoryWidget.h
+++ b/Source/Core/DolphinQt/Debugger/MemoryWidget.h
@@ -14,8 +14,11 @@
class MemoryViewWidget;
class QCheckBox;
class QComboBox;
+class QGroupBox;
+class QHideEvent;
class QLabel;
class QLineEdit;
+class QListWidget;
class QPushButton;
class QRadioButton;
class QShowEvent;
@@ -27,6 +30,8 @@ class System;
class CPUThreadGuard;
} // namespace Core
+class PPCSymbolDB;
+
class MemoryWidget : public QDockWidget
{
Q_OBJECT
@@ -66,6 +71,11 @@ private:
void OnSetValue();
void OnSetValueFromFile();
+ void OnSelectLabel();
+ void RefreshLabelBox();
+ void UpdateSymbols();
+ void UpdateNotes();
+
void OnDumpMRAM();
void OnDumpExRAM();
void OnDumpARAM();
@@ -85,6 +95,7 @@ private:
void ActivateSearchAddress();
Core::System& m_system;
+ PPCSymbolDB& m_ppc_symbol_db;
MemoryViewWidget* m_memory_view;
QSplitter* m_splitter;
@@ -115,6 +126,15 @@ private:
QRadioButton* m_bp_read_only;
QRadioButton* m_bp_write_only;
QCheckBox* m_bp_log_check;
+
+ QGroupBox* m_labels_group;
+ QLineEdit* m_search_labels;
+ QListWidget* m_symbols_list;
+ QListWidget* m_data_list;
+ QListWidget* m_note_list;
+ QString m_note_filter;
+ bool m_labels_visible = true;
+
Common::EventHook m_vi_end_field_event;
bool m_auto_update_enabled = true;