From 95ae4b34d98facc3049749791e3f8cb318f5fa2b Mon Sep 17 00:00:00 2001 From: spycrab Date: Fri, 16 Mar 2018 12:39:53 +0100 Subject: [PATCH] Qt/Debugger: Implement "Memory" widget --- Source/Core/DolphinQt2/CMakeLists.txt | 2 + .../DolphinQt2/Debugger/BreakpointWidget.h | 3 + Source/Core/DolphinQt2/Debugger/CodeWidget.h | 2 +- .../DolphinQt2/Debugger/MemoryViewWidget.cpp | 411 +++++++++++++ .../DolphinQt2/Debugger/MemoryViewWidget.h | 62 ++ .../Core/DolphinQt2/Debugger/MemoryWidget.cpp | 581 ++++++++++++++++++ .../Core/DolphinQt2/Debugger/MemoryWidget.h | 88 +++ Source/Core/DolphinQt2/DolphinQt2.vcxproj | 6 + Source/Core/DolphinQt2/MainWindow.cpp | 12 + Source/Core/DolphinQt2/MainWindow.h | 2 + Source/Core/DolphinQt2/MenuBar.cpp | 9 + Source/Core/DolphinQt2/MenuBar.h | 1 + Source/Core/DolphinQt2/Settings.cpp | 14 + Source/Core/DolphinQt2/Settings.h | 3 + 14 files changed, 1195 insertions(+), 1 deletion(-) create mode 100644 Source/Core/DolphinQt2/Debugger/MemoryViewWidget.cpp create mode 100644 Source/Core/DolphinQt2/Debugger/MemoryViewWidget.h create mode 100644 Source/Core/DolphinQt2/Debugger/MemoryWidget.cpp create mode 100644 Source/Core/DolphinQt2/Debugger/MemoryWidget.h diff --git a/Source/Core/DolphinQt2/CMakeLists.txt b/Source/Core/DolphinQt2/CMakeLists.txt index 52af9c951d..46df6a0ce5 100644 --- a/Source/Core/DolphinQt2/CMakeLists.txt +++ b/Source/Core/DolphinQt2/CMakeLists.txt @@ -81,6 +81,8 @@ set(SRCS Debugger/BreakpointWidget.cpp Debugger/CodeViewWidget.cpp Debugger/CodeWidget.cpp + Debugger/MemoryViewWidget.cpp + Debugger/MemoryWidget.cpp Debugger/NewBreakpointDialog.cpp Debugger/RegisterColumn.cpp Debugger/RegisterWidget.cpp diff --git a/Source/Core/DolphinQt2/Debugger/BreakpointWidget.h b/Source/Core/DolphinQt2/Debugger/BreakpointWidget.h index b48814df19..5dea2d517a 100644 --- a/Source/Core/DolphinQt2/Debugger/BreakpointWidget.h +++ b/Source/Core/DolphinQt2/Debugger/BreakpointWidget.h @@ -27,6 +27,9 @@ public: bool do_break = true); void Update(); +signals: + void BreakpointsChanged(); + protected: void closeEvent(QCloseEvent*) override; diff --git a/Source/Core/DolphinQt2/Debugger/CodeWidget.h b/Source/Core/DolphinQt2/Debugger/CodeWidget.h index 67216c41b3..6739c5fa7a 100644 --- a/Source/Core/DolphinQt2/Debugger/CodeWidget.h +++ b/Source/Core/DolphinQt2/Debugger/CodeWidget.h @@ -32,13 +32,13 @@ public: void ToggleBreakpoint(); void AddBreakpoint(); + void Update(); signals: void BreakpointsChanged(); private: void CreateWidgets(); void ConnectWidgets(); - void Update(); void UpdateCallstack(); void UpdateSymbols(); void UpdateFunctionCalls(Symbol* symbol); diff --git a/Source/Core/DolphinQt2/Debugger/MemoryViewWidget.cpp b/Source/Core/DolphinQt2/Debugger/MemoryViewWidget.cpp new file mode 100644 index 0000000000..50fcc78cc7 --- /dev/null +++ b/Source/Core/DolphinQt2/Debugger/MemoryViewWidget.cpp @@ -0,0 +1,411 @@ +// Copyright 2018 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt2/Debugger/MemoryViewWidget.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "Core/Core.h" +#include "Core/PowerPC/BreakPoints.h" +#include "Core/PowerPC/PowerPC.h" +#include "DolphinQt2/QtUtils/ActionHelper.h" +#include "DolphinQt2/Settings.h" + +MemoryViewWidget::MemoryViewWidget(QWidget* parent) : QTableWidget(parent) +{ + horizontalHeader()->hide(); + verticalHeader()->hide(); + verticalScrollBar()->setHidden(true); + setShowGrid(false); + + setFont(Settings::Instance().GetDebugFont()); + + connect(&Settings::Instance(), &Settings::DebugFontChanged, this, &QWidget::setFont); + connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, [this] { Update(); }); + connect(this, &MemoryViewWidget::customContextMenuRequested, this, + &MemoryViewWidget::OnContextMenu); + + setContextMenuPolicy(Qt::CustomContextMenu); + + Update(); +} + +static int CalculateColumnCount(MemoryViewWidget::Type type) +{ + int column_count = 3; + + switch (type) + { + case MemoryViewWidget::Type::ASCII: + case MemoryViewWidget::Type::U8: + column_count += 16; + break; + case MemoryViewWidget::Type::U16: + column_count += 8; + break; + case MemoryViewWidget::Type::U32: + case MemoryViewWidget::Type::Float32: + column_count += 4; + break; + } + + return column_count; +} + +void MemoryViewWidget::Update() +{ + clearSelection(); + + setColumnCount(CalculateColumnCount(m_type)); + + setColumnWidth(0, 24); + + if (rowCount() == 0) + setRowCount(1); + + setRowHeight(0, 24); + + // Calculate (roughly) how many rows will fit in our table + int rows = std::round((height() / static_cast(rowHeight(0))) - 0.25); + + setRowCount(rows); + + for (int i = 0; i < rows; i++) + { + setRowHeight(i, 24); + + u32 addr = m_address - ((rowCount() / 2) * 16) + i * 16; + + auto* bp_item = new QTableWidgetItem; + bp_item->setFlags(Qt::ItemIsEnabled); + bp_item->setData(Qt::UserRole, addr); + + if (PowerPC::memchecks.OverlapsMemcheck(addr, 16)) + bp_item->setBackground(Qt::red); + + setItem(i, 0, bp_item); + + auto* addr_item = new QTableWidgetItem(QStringLiteral("%1").arg(addr, 8, 16, QLatin1Char('0'))); + + addr_item->setData(Qt::UserRole, addr); + addr_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); + + setItem(i, 1, addr_item); + + if (addr == m_address) + addr_item->setSelected(true); + + if (Core::GetState() != Core::State::Paused || !PowerPC::HostIsRAMAddress(addr)) + { + for (int c = 2; c < columnCount(); c++) + { + auto* item = new QTableWidgetItem(QStringLiteral("-")); + item->setFlags(Qt::ItemIsEnabled); + item->setData(Qt::UserRole, addr); + + setItem(i, c, item); + } + + continue; + } + + auto* description_item = + new QTableWidgetItem(QString::fromStdString(PowerPC::debug_interface.GetDescription(addr))); + + description_item->setForeground(Qt::blue); + description_item->setFlags(Qt::ItemIsEnabled); + + setItem(i, columnCount() - 1, description_item); + + switch (m_type) + { + case Type::U8: + for (int c = 0; c < 16; c++) + { + auto* hex_item = new QTableWidgetItem; + + if (PowerPC::HostIsRAMAddress(addr + c * sizeof(u8))) + { + u8 value = PowerPC::HostRead_U8(addr + c * sizeof(u8)); + hex_item->setText(QStringLiteral("%1").arg(value, 2, 16, QLatin1Char('0'))); + + hex_item->setFlags(Qt::ItemIsEnabled); + hex_item->setData(Qt::UserRole, addr); + } + else + { + hex_item->setFlags(0); + hex_item->setText(QStringLiteral("-")); + } + + setItem(i, 2 + c, hex_item); + } + break; + case Type::ASCII: + for (int c = 0; c < 16; c++) + { + auto* hex_item = new QTableWidgetItem; + + if (PowerPC::HostIsRAMAddress(addr + c * sizeof(u8))) + { + char value = PowerPC::HostRead_U8(addr + c * sizeof(u8)); + std::string s(1, std::isprint(value) ? value : '.'); + hex_item->setText(QString::fromStdString(s)); + + hex_item->setFlags(Qt::ItemIsEnabled); + hex_item->setData(Qt::UserRole, addr); + } + else + { + hex_item->setFlags(0); + hex_item->setText(QStringLiteral("-")); + } + + setItem(i, 2 + c, hex_item); + } + break; + case Type::U16: + for (int c = 0; c < 8; c++) + { + auto* hex_item = new QTableWidgetItem; + + if (PowerPC::HostIsRAMAddress(addr + c * sizeof(u16))) + { + u16 value = PowerPC::HostRead_U16(addr + c * sizeof(u16)); + hex_item->setText(QStringLiteral("%1").arg(value, 4, 16, QLatin1Char('0'))); + + hex_item->setFlags(Qt::ItemIsEnabled); + hex_item->setData(Qt::UserRole, addr); + } + else + { + hex_item->setFlags(0); + hex_item->setText(QStringLiteral("-")); + } + + setItem(i, 2 + c, hex_item); + } + break; + case Type::U32: + for (int c = 0; c < 4; c++) + { + auto* hex_item = new QTableWidgetItem; + + if (PowerPC::HostIsRAMAddress(addr + c * sizeof(u32))) + { + u32 value = PowerPC::HostRead_U32(addr + c * sizeof(u32)); + hex_item->setText(QStringLiteral("%1").arg(value, 8, 16, QLatin1Char('0'))); + + hex_item->setFlags(Qt::ItemIsEnabled); + hex_item->setData(Qt::UserRole, addr); + } + else + { + hex_item->setFlags(0); + hex_item->setText(QStringLiteral("-")); + } + + setItem(i, 2 + c, hex_item); + } + break; + case Type::Float32: + for (int c = 0; c < 4; c++) + { + auto* hex_item = new QTableWidgetItem; + + if (PowerPC::HostIsRAMAddress(addr + c * sizeof(u32))) + { + float value = PowerPC::Read_F32(addr + c * sizeof(u32)); + hex_item->setText(QString::number(value)); + + hex_item->setFlags(Qt::ItemIsEnabled); + hex_item->setData(Qt::UserRole, addr); + } + else + { + hex_item->setFlags(0); + hex_item->setText(QStringLiteral("-")); + } + + setItem(i, 2 + c, hex_item); + } + break; + } + } + + for (int i = 1; i < columnCount(); i++) + { + resizeColumnToContents(i); + // Add some extra spacing because the default width is too small in most cases + setColumnWidth(i, columnWidth(i) * 1.1); + } + + viewport()->update(); + update(); +} + +void MemoryViewWidget::SetType(Type type) +{ + if (m_type == type) + return; + + m_type = type; + Update(); +} + +void MemoryViewWidget::SetBPType(BPType type) +{ + m_bp_type = type; +} + +void MemoryViewWidget::SetAddress(u32 address) +{ + if (m_address == address) + return; + + m_address = address; + Update(); +} + +void MemoryViewWidget::SetBPLoggingEnabled(bool enabled) +{ + m_do_log = enabled; +} + +void MemoryViewWidget::resizeEvent(QResizeEvent*) +{ + Update(); +} + +void MemoryViewWidget::keyPressEvent(QKeyEvent* event) +{ + switch (event->key()) + { + case Qt::Key_Up: + m_address -= 3 * 16; + Update(); + return; + case Qt::Key_Down: + m_address += 3 * 16; + Update(); + return; + case Qt::Key_PageUp: + m_address -= rowCount() * 16; + Update(); + return; + case Qt::Key_PageDown: + m_address += rowCount() * 16; + Update(); + return; + default: + QWidget::keyPressEvent(event); + break; + } +} + +u32 MemoryViewWidget::GetContextAddress() const +{ + return m_context_address; +} + +void MemoryViewWidget::ToggleBreakpoint() +{ + u32 addr = GetContextAddress(); + + if (!PowerPC::memchecks.OverlapsMemcheck(addr, 16)) + { + TMemCheck check; + + check.start_address = addr; + check.end_address = check.start_address + 15; + check.is_ranged = true; + check.is_break_on_read = (m_bp_type == BPType::ReadOnly || m_bp_type == BPType::ReadWrite); + check.is_break_on_write = (m_bp_type == BPType::WriteOnly || m_bp_type == BPType::ReadWrite); + check.log_on_hit = m_do_log; + check.break_on_hit = true; + + PowerPC::memchecks.Add(check); + } + else + { + PowerPC::memchecks.Remove(addr); + } + + emit BreakpointsChanged(); + Update(); +} + +void MemoryViewWidget::wheelEvent(QWheelEvent* event) +{ + int delta = event->delta() > 0 ? -1 : 1; + + m_address += delta * 3 * 16; + Update(); +} + +void MemoryViewWidget::mousePressEvent(QMouseEvent* event) +{ + auto* item = itemAt(event->pos()); + if (item == nullptr) + return; + + const u32 addr = item->data(Qt::UserRole).toUInt(); + + m_context_address = addr; + + switch (event->button()) + { + case Qt::LeftButton: + if (column(item) == 0) + ToggleBreakpoint(); + else + SetAddress(addr); + + Update(); + break; + default: + break; + } +} + +void MemoryViewWidget::OnCopyAddress() +{ + u32 addr = GetContextAddress(); + QApplication::clipboard()->setText(QStringLiteral("%1").arg(addr, 8, 16, QLatin1Char('0'))); +} + +void MemoryViewWidget::OnCopyHex() +{ + u32 addr = GetContextAddress(); + + u64 a = PowerPC::HostRead_U64(addr); + u64 b = PowerPC::HostRead_U64(addr + sizeof(u64)); + + QApplication::clipboard()->setText( + QStringLiteral("%1%2").arg(a, 16, 16, QLatin1Char('0')).arg(b, 16, 16, QLatin1Char('0'))); +} + +void MemoryViewWidget::OnContextMenu() +{ + auto* menu = new QMenu(this); + + AddAction(menu, tr("Copy Address"), this, &MemoryViewWidget::OnCopyAddress); + + auto* copy_hex = AddAction(menu, tr("Copy Hex"), this, &MemoryViewWidget::OnCopyHex); + + copy_hex->setEnabled(Core::GetState() != Core::State::Uninitialized && + PowerPC::HostIsRAMAddress(GetContextAddress())); + + menu->addSeparator(); + + AddAction(menu, tr("Toggle Breakpoint"), this, &MemoryViewWidget::ToggleBreakpoint); + + menu->exec(QCursor::pos()); +} diff --git a/Source/Core/DolphinQt2/Debugger/MemoryViewWidget.h b/Source/Core/DolphinQt2/Debugger/MemoryViewWidget.h new file mode 100644 index 0000000000..e349a47914 --- /dev/null +++ b/Source/Core/DolphinQt2/Debugger/MemoryViewWidget.h @@ -0,0 +1,62 @@ +// Copyright 2018 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "Common/CommonTypes.h" + +class MemoryViewWidget : public QTableWidget +{ + Q_OBJECT +public: + enum class Type + { + U8, + U16, + U32, + ASCII, + Float32 + }; + + enum class BPType + { + ReadWrite, + ReadOnly, + WriteOnly + }; + + explicit MemoryViewWidget(QWidget* parent = nullptr); + + void Update(); + void ToggleBreakpoint(); + + void SetType(Type type); + void SetBPType(BPType type); + void SetAddress(u32 address); + + void SetBPLoggingEnabled(bool enabled); + + u32 GetContextAddress() const; + + void resizeEvent(QResizeEvent*) override; + void keyPressEvent(QKeyEvent* event) override; + void mousePressEvent(QMouseEvent* event) override; + void wheelEvent(QWheelEvent* event) override; + +signals: + void BreakpointsChanged(); + +private: + void OnContextMenu(); + void OnCopyAddress(); + void OnCopyHex(); + + Type m_type = Type::U8; + BPType m_bp_type = BPType::ReadWrite; + bool m_do_log = true; + u32 m_context_address; + u32 m_address = 0; +}; diff --git a/Source/Core/DolphinQt2/Debugger/MemoryWidget.cpp b/Source/Core/DolphinQt2/Debugger/MemoryWidget.cpp new file mode 100644 index 0000000000..9854de991a --- /dev/null +++ b/Source/Core/DolphinQt2/Debugger/MemoryWidget.cpp @@ -0,0 +1,581 @@ +// Copyright 2018 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt2/Debugger/MemoryWidget.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Common/File.h" +#include "Common/FileUtil.h" +#include "Core/ConfigManager.h" +#include "Core/HW/DSP.h" +#include "Core/HW/Memmap.h" +#include "Core/PowerPC/PowerPC.h" +#include "DolphinQt2/Debugger/MemoryViewWidget.h" +#include "DolphinQt2/Settings.h" + +MemoryWidget::MemoryWidget(QWidget* parent) : QDockWidget(parent) +{ + setWindowTitle(tr("Memory")); + setAllowedAreas(Qt::AllDockWidgetAreas); + + CreateWidgets(); + + QSettings& settings = Settings::GetQSettings(); + + restoreGeometry(settings.value(QStringLiteral("memorywidget/geometry")).toByteArray()); + setFloating(settings.value(QStringLiteral("memorywidget/floating")).toBool()); + m_splitter->restoreState(settings.value(QStringLiteral("codewidget/splitter")).toByteArray()); + + connect(&Settings::Instance(), &Settings::MemoryVisibilityChanged, + [this](bool visible) { setHidden(!visible); }); + + connect(&Settings::Instance(), &Settings::DebugModeToggled, + [this](bool enabled) { setHidden(!enabled || !Settings::Instance().IsMemoryVisible()); }); + + connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, &MemoryWidget::Update); + + setHidden(!Settings::Instance().IsCodeVisible() || !Settings::Instance().IsDebugModeEnabled()); + + LoadSettings(); + + ConnectWidgets(); + Update(); + OnTypeChanged(); +} + +MemoryWidget::~MemoryWidget() +{ + QSettings& settings = Settings::GetQSettings(); + + settings.setValue(QStringLiteral("memorywidget/geometry"), saveGeometry()); + settings.setValue(QStringLiteral("memorywidget/floating"), isFloating()); + settings.setValue(QStringLiteral("memorywidget/splitter"), m_splitter->saveState()); + + SaveSettings(); +} + +void MemoryWidget::CreateWidgets() +{ + auto* layout = new QHBoxLayout; + + //// Sidebar + + // Search + m_search_address = new QLineEdit; + m_data_edit = new QLineEdit; + m_set_value = new QPushButton(tr("Set &Value")); + + m_search_address->setPlaceholderText(tr("Search Address")); + m_data_edit->setPlaceholderText(tr("Value")); + + // Dump + m_dump_mram = new QPushButton(tr("Dump &MRAM")); + m_dump_exram = new QPushButton(tr("Dump &ExRAM")); + m_dump_fake_vmem = new QPushButton(tr("Dump &FakeVMEM")); + + // Search Options + auto* search_group = new QGroupBox(tr("Search")); + auto* search_layout = new QVBoxLayout; + search_group->setLayout(search_layout); + + m_find_next = new QPushButton(tr("Find &Next")); + m_find_previous = new QPushButton(tr("Find &Previous")); + m_find_ascii = new QRadioButton(tr("ASCII")); + m_find_hex = new QRadioButton(tr("Hex")); + m_result_label = new QLabel; + + search_layout->addWidget(m_find_next); + search_layout->addWidget(m_find_previous); + search_layout->addWidget(m_result_label); + + // Data Type + auto* datatype_group = new QGroupBox(tr("Data Type")); + auto* datatype_layout = new QVBoxLayout; + datatype_group->setLayout(datatype_layout); + + m_type_u8 = new QRadioButton(tr("U&8")); + m_type_u16 = new QRadioButton(tr("U&16")); + m_type_u32 = new QRadioButton(tr("U&32")); + m_type_ascii = new QRadioButton(tr("ASCII")); + m_type_float = new QRadioButton(tr("Float")); + + datatype_layout->addWidget(m_type_u8); + datatype_layout->addWidget(m_type_u16); + datatype_layout->addWidget(m_type_u32); + datatype_layout->addWidget(m_type_ascii); + datatype_layout->addWidget(m_type_float); + + // MBP options + auto* bp_group = new QGroupBox(tr("Memory breakpoint options")); + auto* bp_layout = new QVBoxLayout; + bp_group->setLayout(bp_layout); + + m_bp_read_write = new QRadioButton(tr("Read and Write")); + m_bp_read_only = new QRadioButton(tr("Read only")); + m_bp_write_only = new QRadioButton(tr("Write only")); + m_bp_log_check = new QCheckBox(tr("Log")); + + bp_layout->addWidget(m_bp_read_write); + bp_layout->addWidget(m_bp_read_only); + bp_layout->addWidget(m_bp_write_only); + bp_layout->addWidget(m_bp_log_check); + + // Sidebar + auto* sidebar = new QWidget; + auto* sidebar_layout = new QVBoxLayout; + sidebar->setLayout(sidebar_layout); + + sidebar_layout->addWidget(m_search_address); + sidebar_layout->addWidget(m_data_edit); + sidebar_layout->addWidget(m_find_ascii); + sidebar_layout->addWidget(m_find_hex); + sidebar_layout->addWidget(m_set_value); + sidebar_layout->addItem(new QSpacerItem(1, 32)); + sidebar_layout->addWidget(m_dump_mram); + sidebar_layout->addWidget(m_dump_exram); + sidebar_layout->addWidget(m_dump_fake_vmem); + sidebar_layout->addWidget(search_group); + sidebar_layout->addWidget(datatype_group); + sidebar_layout->addWidget(bp_group); + + // Splitter + m_splitter = new QSplitter(Qt::Horizontal); + + auto* sidebar_scroll = new QScrollArea; + sidebar_scroll->setWidget(sidebar); + sidebar_scroll->setWidgetResizable(true); + sidebar_scroll->setFixedWidth(250); + + m_memory_view = new MemoryViewWidget(this); + + m_splitter->addWidget(m_memory_view); + m_splitter->addWidget(sidebar_scroll); + + layout->addWidget(m_splitter); + + auto* widget = new QWidget; + widget->setLayout(layout); + setWidget(widget); +} + +void MemoryWidget::ConnectWidgets() +{ + connect(m_search_address, &QLineEdit::textChanged, this, &MemoryWidget::OnSearchAddress); + + connect(m_data_edit, &QLineEdit::textChanged, this, &MemoryWidget::ValidateSearchValue); + connect(m_find_ascii, &QRadioButton::toggled, this, &MemoryWidget::ValidateSearchValue); + connect(m_find_hex, &QRadioButton::toggled, this, &MemoryWidget::ValidateSearchValue); + + connect(m_set_value, &QPushButton::pressed, this, &MemoryWidget::OnSetValue); + + connect(m_dump_mram, &QPushButton::pressed, this, &MemoryWidget::OnDumpMRAM); + connect(m_dump_exram, &QPushButton::pressed, this, &MemoryWidget::OnDumpExRAM); + connect(m_dump_fake_vmem, &QPushButton::pressed, this, &MemoryWidget::OnDumpFakeVMEM); + + connect(m_find_next, &QPushButton::pressed, this, &MemoryWidget::OnFindNextValue); + connect(m_find_previous, &QPushButton::pressed, this, &MemoryWidget::OnFindPreviousValue); + + for (auto* radio : {m_type_u8, m_type_u16, m_type_u32, m_type_ascii, m_type_float}) + connect(radio, &QRadioButton::toggled, this, &MemoryWidget::OnTypeChanged); + + for (auto* radio : {m_bp_read_write, m_bp_read_only, m_bp_write_only}) + connect(radio, &QRadioButton::toggled, this, &MemoryWidget::OnBPTypeChanged); + + connect(m_bp_log_check, &QCheckBox::toggled, this, &MemoryWidget::OnBPLogChanged); + connect(m_memory_view, &MemoryViewWidget::BreakpointsChanged, this, + &MemoryWidget::BreakpointsChanged); +} + +void MemoryWidget::closeEvent(QCloseEvent*) +{ + Settings::Instance().SetMemoryVisible(false); +} + +void MemoryWidget::Update() +{ + m_memory_view->Update(); + update(); +} + +void MemoryWidget::LoadSettings() +{ + QSettings& settings = Settings::GetQSettings(); + + const bool search_ascii = + settings.value(QStringLiteral("memorywidget/searchascii"), true).toBool(); + const bool search_hex = settings.value(QStringLiteral("memorywidget/searchhex"), false).toBool(); + + m_find_ascii->setChecked(search_ascii); + m_find_hex->setChecked(search_hex); + + const bool type_u8 = settings.value(QStringLiteral("memorywidget/typeu8"), true).toBool(); + const bool type_u16 = settings.value(QStringLiteral("memorywidget/typeu16"), false).toBool(); + const bool type_u32 = settings.value(QStringLiteral("memorywidget/typeu32"), false).toBool(); + const bool type_float = settings.value(QStringLiteral("memorywidget/typefloat"), false).toBool(); + const bool type_ascii = settings.value(QStringLiteral("memorywidget/typeascii"), false).toBool(); + + m_type_u8->setChecked(type_u8); + m_type_u16->setChecked(type_u16); + m_type_u32->setChecked(type_u32); + m_type_float->setChecked(type_float); + m_type_ascii->setChecked(type_ascii); + + bool bp_rw = settings.value(QStringLiteral("memorywidget/bpreadwrite"), true).toBool(); + bool bp_r = settings.value(QStringLiteral("memorywidget/bpread"), false).toBool(); + bool bp_w = settings.value(QStringLiteral("memorywidget/bpwrite"), false).toBool(); + bool bp_log = settings.value(QStringLiteral("memorywidget/bplog"), true).toBool(); + + m_bp_read_write->setChecked(bp_rw); + m_bp_read_only->setChecked(bp_r); + m_bp_write_only->setChecked(bp_w); + m_bp_log_check->setChecked(bp_log); +} + +void MemoryWidget::SaveSettings() +{ + QSettings& settings = Settings::GetQSettings(); + + settings.setValue(QStringLiteral("memorywidget/searchascii"), m_find_ascii->isChecked()); + settings.setValue(QStringLiteral("memorywidget/searchhex"), m_find_hex->isChecked()); + + settings.setValue(QStringLiteral("memorywidget/typeu8"), m_type_u8->isChecked()); + settings.setValue(QStringLiteral("memorywidget/typeu16"), m_type_u16->isChecked()); + settings.setValue(QStringLiteral("memorywidget/typeu32"), m_type_u32->isChecked()); + settings.setValue(QStringLiteral("memorywidget/typeascii"), m_type_ascii->isChecked()); + settings.setValue(QStringLiteral("memorywidget/typefloat"), m_type_float->isChecked()); + + settings.setValue(QStringLiteral("memorywidget/bpreadwrite"), m_bp_read_write->isChecked()); + settings.setValue(QStringLiteral("memorywidget/bpread"), m_bp_read_only->isChecked()); + settings.setValue(QStringLiteral("memorywidget/bpwrite"), m_bp_write_only->isChecked()); + settings.setValue(QStringLiteral("memorywidget/bplog"), m_bp_log_check->isChecked()); +} + +void MemoryWidget::OnTypeChanged() +{ + MemoryViewWidget::Type type; + + if (m_type_u8->isChecked()) + type = MemoryViewWidget::Type::U8; + else if (m_type_u16->isChecked()) + type = MemoryViewWidget::Type::U16; + else if (m_type_u32->isChecked()) + type = MemoryViewWidget::Type::U32; + else if (m_type_ascii->isChecked()) + type = MemoryViewWidget::Type::ASCII; + else + type = MemoryViewWidget::Type::Float32; + + m_memory_view->SetType(type); + + SaveSettings(); +} + +void MemoryWidget::OnBPLogChanged() +{ + m_memory_view->SetBPLoggingEnabled(m_bp_log_check->isChecked()); + SaveSettings(); +} + +void MemoryWidget::OnBPTypeChanged() +{ + bool read_write = m_bp_read_write->isChecked(); + bool read_only = m_bp_read_only->isChecked(); + + MemoryViewWidget::BPType type; + + if (read_write) + type = MemoryViewWidget::BPType::ReadWrite; + else if (read_only) + type = MemoryViewWidget::BPType::ReadOnly; + else + type = MemoryViewWidget::BPType::WriteOnly; + + m_memory_view->SetBPType(type); + + SaveSettings(); +} + +void MemoryWidget::OnSearchAddress() +{ + bool good; + u32 addr = m_search_address->text().toUInt(&good, 16); + + QFont font; + QPalette palette; + + if (good || m_search_address->text().isEmpty()) + { + if (good) + m_memory_view->SetAddress(addr); + } + else + { + font.setBold(true); + palette.setColor(QPalette::Text, Qt::red); + } + + m_search_address->setFont(font); + m_search_address->setPalette(palette); +} + +void MemoryWidget::ValidateSearchValue() +{ + QFont font; + QPalette palette; + + if (m_find_hex->isChecked() && !m_data_edit->text().isEmpty()) + { + bool good; + m_data_edit->text().toULongLong(&good, 16); + + if (!good) + { + font.setBold(true); + palette.setColor(QPalette::Text, Qt::red); + } + } + + m_data_edit->setFont(font); + m_data_edit->setPalette(palette); +} + +void MemoryWidget::OnSetValue() +{ + bool good_address; + u32 addr = m_search_address->text().toUInt(&good_address, 16); + + if (!good_address) + { + QMessageBox::critical(this, tr("Error"), tr("Bad address provided.")); + return; + } + + if (m_data_edit->text().isEmpty()) + { + QMessageBox::critical(this, tr("Error"), tr("No value provided.")); + return; + } + + if (m_find_ascii->isChecked()) + { + std::string ascii = m_data_edit->text().toStdString(); + + for (char c : ascii) + PowerPC::HostWrite_U8(static_cast(c), addr++); + } + else + { + bool good_value; + u64 value = m_data_edit->text().toULongLong(&good_value, 16); + + if (!good_value) + { + QMessageBox::critical(this, tr("Error"), tr("Bad value provided.")); + return; + } + + if (value == static_cast(value)) + { + PowerPC::HostWrite_U8(static_cast(value), addr); + } + else if (value == static_cast(value)) + { + PowerPC::HostWrite_U16(static_cast(value), addr); + } + else if (value == static_cast(value)) + { + PowerPC::HostWrite_U32(static_cast(value), addr); + } + else + PowerPC::HostWrite_U64(value, addr); + } + + Update(); +} + +static void DumpArray(const std::string& filename, const u8* data, size_t length) +{ + if (!data) + return; + + File::IOFile f(filename, "wb"); + + if (!f) + { + QMessageBox::critical( + nullptr, QObject::tr("Error"), + QObject::tr("Failed to dump %1: Can't open file").arg(QString::fromStdString(filename))); + return; + } + + if (!f.WriteBytes(data, length)) + { + QMessageBox::critical(nullptr, QObject::tr("Error"), + QObject::tr("Failed to dump %1: Failed to write to file") + .arg(QString::fromStdString(filename))); + } +} + +void MemoryWidget::OnDumpMRAM() +{ + DumpArray(File::GetUserPath(F_RAMDUMP_IDX), Memory::m_pRAM, Memory::REALRAM_SIZE); +} + +void MemoryWidget::OnDumpExRAM() +{ + if (SConfig::GetInstance().bWii) + { + DumpArray(File::GetUserPath(F_ARAMDUMP_IDX), Memory::m_pEXRAM, Memory::EXRAM_SIZE); + } + else + { + DumpArray(File::GetUserPath(F_ARAMDUMP_IDX), DSP::GetARAMPtr(), DSP::ARAM_SIZE); + } +} + +void MemoryWidget::OnDumpFakeVMEM() +{ + DumpArray(File::GetUserPath(F_FAKEVMEMDUMP_IDX), Memory::m_pFakeVMEM, Memory::FAKEVMEM_SIZE); +} + +std::vector MemoryWidget::GetValueData() const +{ + std::vector search_for; // Series of bytes we want to look for + + if (m_find_ascii->isChecked()) + { + std::string s = m_data_edit->text().toStdString(); + + for (char c : s) + search_for.push_back(c); + } + else + { + bool good; + u64 value = m_data_edit->text().toULongLong(&good, 16); + + if (!good) + return {}; + + int size; + + if (value == static_cast(value)) + size = sizeof(u8); + else if (value == static_cast(value)) + size = sizeof(u16); + else if (value == static_cast(value)) + size = sizeof(u32); + else + size = sizeof(u64); + + for (int i = size - 1; i >= 0; i--) + search_for.push_back((value >> (i * 8)) & 0xFF); + } + + return search_for; +} + +void MemoryWidget::FindValue(bool next) +{ + std::vector search_for = GetValueData(); + + if (search_for.empty()) + return; + + const u8* ram_ptr = nullptr; + std::size_t ram_size = 0; + u32 base_address = 0; + + if (m_type_u16->isChecked()) + { + ram_ptr = DSP::GetARAMPtr(); + ram_size = DSP::ARAM_SIZE; + base_address = 0x0c005000; + } + else if (Memory::m_pRAM) + { + ram_ptr = Memory::m_pRAM; + ram_size = Memory::REALRAM_SIZE; + base_address = 0x80000000; + } + else + { + m_result_label->setText(tr("Memory Not Ready")); + return; + } + + if (search_for.empty()) + { + m_result_label->setText(tr("No Value Given")); + return; + } + + u32 addr = 0; + + if (!m_search_address->text().isEmpty()) + addr = m_search_address->text().toUInt(nullptr, 16) + 1; + + if (addr >= base_address) + addr -= base_address; + + if (addr >= ram_size - search_for.size()) + { + m_result_label->setText(tr("Address Out Of Range")); + return; + } + + const u8* ptr; + const u8* end; + + if (next) + { + end = &ram_ptr[ram_size - search_for.size() + 1]; + ptr = std::search(&ram_ptr[addr], end, search_for.begin(), search_for.end()); + } + else + { + end = &ram_ptr[addr + search_for.size() - 1]; + ptr = std::find_end(ram_ptr, end, search_for.begin(), search_for.end()); + } + + if (ptr != end) + { + m_result_label->setText(tr("Match Found")); + + u32 offset = static_cast(ptr - ram_ptr) + base_address; + + m_search_address->setText(QStringLiteral("%1").arg(offset, 8, 16, QLatin1Char('0'))); + + m_memory_view->SetAddress(offset); + + return; + } + + m_result_label->setText(tr("No Match")); +} + +void MemoryWidget::OnFindNextValue() +{ + FindValue(true); +} + +void MemoryWidget::OnFindPreviousValue() +{ + FindValue(false); +} diff --git a/Source/Core/DolphinQt2/Debugger/MemoryWidget.h b/Source/Core/DolphinQt2/Debugger/MemoryWidget.h new file mode 100644 index 0000000000..6b38b23ea2 --- /dev/null +++ b/Source/Core/DolphinQt2/Debugger/MemoryWidget.h @@ -0,0 +1,88 @@ +// Copyright 2018 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +#include + +#include "Common/CommonTypes.h" + +class MemoryViewWidget; +class QCheckBox; +class QLabel; +class QLineEdit; +class QPushButton; +class QRadioButton; +class QSplitter; + +class MemoryWidget : public QDockWidget +{ + Q_OBJECT +public: + explicit MemoryWidget(QWidget* parent = nullptr); + ~MemoryWidget(); + + void Update(); +signals: + void BreakpointsChanged(); + +private: + void CreateWidgets(); + void ConnectWidgets(); + + void LoadSettings(); + void SaveSettings(); + + void OnTypeChanged(); + void OnBPLogChanged(); + void OnBPTypeChanged(); + + void OnSearchAddress(); + void OnFindNextValue(); + void OnFindPreviousValue(); + void ValidateSearchValue(); + + void OnSetValue(); + + void OnDumpMRAM(); + void OnDumpExRAM(); + void OnDumpFakeVMEM(); + + std::vector GetValueData() const; + + void FindValue(bool next); + + void closeEvent(QCloseEvent*) override; + + MemoryViewWidget* m_memory_view; + QSplitter* m_splitter; + QLineEdit* m_search_address; + QLineEdit* m_data_edit; + QPushButton* m_set_value; + QPushButton* m_dump_mram; + QPushButton* m_dump_exram; + QPushButton* m_dump_fake_vmem; + + // Search + QPushButton* m_find_next; + QPushButton* m_find_previous; + QRadioButton* m_find_ascii; + QRadioButton* m_find_hex; + QLabel* m_result_label; + + // Datatypes + QRadioButton* m_type_u8; + QRadioButton* m_type_u16; + QRadioButton* m_type_u32; + QRadioButton* m_type_ascii; + QRadioButton* m_type_float; + + // Breakpoint options + QRadioButton* m_bp_read_write; + QRadioButton* m_bp_read_only; + QRadioButton* m_bp_write_only; + QCheckBox* m_bp_log_check; +}; diff --git a/Source/Core/DolphinQt2/DolphinQt2.vcxproj b/Source/Core/DolphinQt2/DolphinQt2.vcxproj index 3fe5e2613d..0690119e95 100644 --- a/Source/Core/DolphinQt2/DolphinQt2.vcxproj +++ b/Source/Core/DolphinQt2/DolphinQt2.vcxproj @@ -96,6 +96,8 @@ + + @@ -179,6 +181,8 @@ + + @@ -247,6 +251,8 @@ + + diff --git a/Source/Core/DolphinQt2/MainWindow.cpp b/Source/Core/DolphinQt2/MainWindow.cpp index f77e23a663..15d1cdac32 100644 --- a/Source/Core/DolphinQt2/MainWindow.cpp +++ b/Source/Core/DolphinQt2/MainWindow.cpp @@ -54,6 +54,7 @@ #include "DolphinQt2/Config/SettingsWindow.h" #include "DolphinQt2/Debugger/BreakpointWidget.h" #include "DolphinQt2/Debugger/CodeWidget.h" +#include "DolphinQt2/Debugger/MemoryWidget.h" #include "DolphinQt2/Debugger/RegisterWidget.h" #include "DolphinQt2/Debugger/WatchWidget.h" #include "DolphinQt2/FIFOPlayerWindow.h" @@ -195,6 +196,7 @@ void MainWindow::CreateComponents() m_log_widget = new LogWidget(this); m_log_config_widget = new LogConfigWidget(this); m_fifo_window = new FIFOPlayerWindow(this); + m_memory_widget = new MemoryWidget(this); connect(m_fifo_window, &FIFOPlayerWindow::LoadFIFORequested, this, [this](const QString& path) { StartGame(path); }); @@ -207,8 +209,16 @@ void MainWindow::CreateComponents() [this](u32 addr) { m_breakpoint_widget->AddAddressMBP(addr); }); connect(m_register_widget, &RegisterWidget::RequestMemoryBreakpoint, [this](u32 addr) { m_breakpoint_widget->AddAddressMBP(addr); }); + connect(m_code_widget, &CodeWidget::BreakpointsChanged, m_breakpoint_widget, &BreakpointWidget::Update); + connect(m_memory_widget, &MemoryWidget::BreakpointsChanged, m_breakpoint_widget, + &BreakpointWidget::Update); + + connect(m_breakpoint_widget, &BreakpointWidget::BreakpointsChanged, m_code_widget, + &CodeWidget::Update); + connect(m_breakpoint_widget, &BreakpointWidget::BreakpointsChanged, m_memory_widget, + &MemoryWidget::Update); #if defined(HAVE_XRANDR) && HAVE_XRANDR m_graphics_window = new GraphicsWindow( @@ -410,12 +420,14 @@ void MainWindow::ConnectStack() addDockWidget(Qt::RightDockWidgetArea, m_register_widget); addDockWidget(Qt::RightDockWidgetArea, m_watch_widget); addDockWidget(Qt::RightDockWidgetArea, m_breakpoint_widget); + addDockWidget(Qt::RightDockWidgetArea, m_memory_widget); tabifyDockWidget(m_log_widget, m_log_config_widget); tabifyDockWidget(m_log_widget, m_code_widget); tabifyDockWidget(m_log_widget, m_register_widget); tabifyDockWidget(m_log_widget, m_watch_widget); tabifyDockWidget(m_log_widget, m_breakpoint_widget); + tabifyDockWidget(m_log_widget, m_memory_widget); } QString MainWindow::PromptFileName() diff --git a/Source/Core/DolphinQt2/MainWindow.h b/Source/Core/DolphinQt2/MainWindow.h index 1cc8c2bc84..8dfa24af74 100644 --- a/Source/Core/DolphinQt2/MainWindow.h +++ b/Source/Core/DolphinQt2/MainWindow.h @@ -32,6 +32,7 @@ class HotkeyScheduler; class LogConfigWidget; class LogWidget; class MappingWindow; +class MemoryWidget; class NetPlayClient; class NetPlayDialog; class NetPlayServer; @@ -174,6 +175,7 @@ private: CodeWidget* m_code_widget; LogWidget* m_log_widget; LogConfigWidget* m_log_config_widget; + MemoryWidget* m_memory_widget; FIFOPlayerWindow* m_fifo_window; RegisterWidget* m_register_widget; WatchWidget* m_watch_widget; diff --git a/Source/Core/DolphinQt2/MenuBar.cpp b/Source/Core/DolphinQt2/MenuBar.cpp index c9f6641a70..9303644dd5 100644 --- a/Source/Core/DolphinQt2/MenuBar.cpp +++ b/Source/Core/DolphinQt2/MenuBar.cpp @@ -120,6 +120,7 @@ void MenuBar::OnDebugModeToggled(bool enabled) m_show_registers->setVisible(enabled); m_show_watch->setVisible(enabled); m_show_breakpoints->setVisible(enabled); + m_show_memory->setVisible(enabled); if (enabled) addMenu(m_symbols); @@ -377,6 +378,14 @@ void MenuBar::AddViewMenu() connect(&Settings::Instance(), &Settings::BreakpointsVisibilityChanged, m_show_breakpoints, &QAction::setChecked); + m_show_memory = view_menu->addAction(tr("&Memory")); + m_show_memory->setCheckable(true); + m_show_memory->setChecked(Settings::Instance().IsMemoryVisible()); + + connect(m_show_memory, &QAction::toggled, &Settings::Instance(), &Settings::SetMemoryVisible); + connect(&Settings::Instance(), &Settings::MemoryVisibilityChanged, m_show_memory, + &QAction::setChecked); + view_menu->addSeparator(); AddGameListTypeSection(view_menu); diff --git a/Source/Core/DolphinQt2/MenuBar.h b/Source/Core/DolphinQt2/MenuBar.h index be848b87fa..153e4f7281 100644 --- a/Source/Core/DolphinQt2/MenuBar.h +++ b/Source/Core/DolphinQt2/MenuBar.h @@ -201,6 +201,7 @@ private: QAction* m_show_registers; QAction* m_show_watch; QAction* m_show_breakpoints; + QAction* m_show_memory; // Symbols QMenu* m_symbols; diff --git a/Source/Core/DolphinQt2/Settings.cpp b/Source/Core/DolphinQt2/Settings.cpp index baa86879e8..e3e614a87e 100644 --- a/Source/Core/DolphinQt2/Settings.cpp +++ b/Source/Core/DolphinQt2/Settings.cpp @@ -303,6 +303,20 @@ bool Settings::IsCodeVisible() const return GetQSettings().value(QStringLiteral("debugger/showcode")).toBool(); } +void Settings::SetMemoryVisible(bool enabled) +{ + if (IsMemoryVisible() == enabled) + return; + QSettings().setValue(QStringLiteral("debugger/showmemory"), enabled); + + emit MemoryVisibilityChanged(enabled); +} + +bool Settings::IsMemoryVisible() const +{ + return QSettings().value(QStringLiteral("debugger/showmemory")).toBool(); +} + void Settings::SetDebugFont(QFont font) { if (GetDebugFont() != font) diff --git a/Source/Core/DolphinQt2/Settings.h b/Source/Core/DolphinQt2/Settings.h index b4e96b6e84..0f6f3a97e4 100644 --- a/Source/Core/DolphinQt2/Settings.h +++ b/Source/Core/DolphinQt2/Settings.h @@ -97,6 +97,8 @@ public: bool IsBreakpointsVisible() const; void SetCodeVisible(bool enabled); bool IsCodeVisible() const; + void SetMemoryVisible(bool enabled); + bool IsMemoryVisible() const; QFont GetDebugFont() const; void SetDebugFont(QFont font); @@ -127,6 +129,7 @@ signals: void WatchVisibilityChanged(bool visible); void BreakpointsVisibilityChanged(bool visible); void CodeVisibilityChanged(bool visible); + void MemoryVisibilityChanged(bool visible); void DebugModeToggled(bool enabled); void DebugFontChanged(QFont font); void AutoUpdateTrackChanged(const QString& mode);