From cecfd19f7cee65941e2ccf978071372e4e822d60 Mon Sep 17 00:00:00 2001 From: TryTwo Date: Wed, 29 Jun 2022 11:27:44 -0700 Subject: [PATCH] Debugger MemoryViewWidget: Allow direct editing of memory cells. --- .../DolphinQt/Debugger/MemoryViewWidget.cpp | 160 +++++++++++++++++- .../DolphinQt/Debugger/MemoryViewWidget.h | 3 + 2 files changed, 154 insertions(+), 9 deletions(-) diff --git a/Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp b/Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp index 7e87d9720b2d..d7b044a7a5a5 100644 --- a/Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp @@ -14,6 +14,7 @@ #include #include +#include #include "Common/Align.h" #include "Common/FloatUtils.h" @@ -32,7 +33,7 @@ constexpr double SCROLL_FRACTION_DEGREES = 15.; constexpr auto USER_ROLE_IS_ROW_BREAKPOINT_CELL = Qt::UserRole; constexpr auto USER_ROLE_CELL_ADDRESS = Qt::UserRole + 1; -constexpr auto USER_ROLE_HAS_VALUE = Qt::UserRole + 2; +constexpr auto USER_ROLE_VALUE_TYPE = Qt::UserRole + 2; // Numbers for the scrollbar. These affect how much big the draggable part of the scrollbar is, how // smooth it scrolls, and how much memory it traverses while dragging. @@ -55,6 +56,7 @@ class MemoryViewTable final : public QTableWidget connect(this, &MemoryViewTable::customContextMenuRequested, m_view, &MemoryViewWidget::OnContextMenu); + connect(this, &MemoryViewTable::itemChanged, this, &MemoryViewTable::OnItemChanged); } void resizeEvent(QResizeEvent* event) override @@ -122,6 +124,21 @@ class MemoryViewTable final : public QTableWidget } } + void OnItemChanged(QTableWidgetItem* item) + { + if (m_view->m_updating == true) + return; + + m_view->m_updating = true; + QString text = item->text(); + u32 address = item->data(USER_ROLE_CELL_ADDRESS).toUInt(); + int type = item->data(USER_ROLE_VALUE_TYPE).toInt(); + + m_view->SetCellValue(address, type, text); + m_view->m_updating = false; + m_view->Update(); + } + private: MemoryViewWidget* m_view; }; @@ -235,6 +252,10 @@ constexpr int GetCharacterCount(MemoryViewWidget::Type type) void MemoryViewWidget::Update() { + if (m_updating == true) + return; + + m_updating = true; m_table->clearSelection(); u32 address = m_address; @@ -272,7 +293,7 @@ void MemoryViewWidget::Update() bp_item->setFlags(Qt::ItemIsEnabled); bp_item->setData(USER_ROLE_IS_ROW_BREAKPOINT_CELL, true); bp_item->setData(USER_ROLE_CELL_ADDRESS, row_address); - bp_item->setData(USER_ROLE_HAS_VALUE, false); + bp_item->setData(USER_ROLE_VALUE_TYPE, static_cast(Type::Null)); m_table->setItem(i, 0, bp_item); @@ -282,7 +303,7 @@ void MemoryViewWidget::Update() row_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); row_item->setData(USER_ROLE_IS_ROW_BREAKPOINT_CELL, false); row_item->setData(USER_ROLE_CELL_ADDRESS, row_address); - row_item->setData(USER_ROLE_HAS_VALUE, false); + row_item->setData(USER_ROLE_VALUE_TYPE, static_cast(Type::Null)); m_table->setItem(i, 1, row_item); @@ -297,7 +318,7 @@ void MemoryViewWidget::Update() item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); item->setData(USER_ROLE_IS_ROW_BREAKPOINT_CELL, false); item->setData(USER_ROLE_CELL_ADDRESS, row_address); - item->setData(USER_ROLE_HAS_VALUE, false); + item->setData(USER_ROLE_VALUE_TYPE, static_cast(Type::Null)); m_table->setItem(i, c, item); } @@ -345,6 +366,7 @@ void MemoryViewWidget::Update() m_table->viewport()->update(); m_table->update(); update(); + m_updating = false; } void MemoryViewWidget::UpdateColumns(Type type, int first_column) @@ -372,7 +394,7 @@ void MemoryViewWidget::UpdateColumns(Type type, int first_column) for (int c = 0; c < data_columns; c++) { auto* cell_item = new QTableWidgetItem; - cell_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); + cell_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable); cell_item->setTextAlignment(text_alignment); const u32 cell_address = row_address + c * GetTypeSize(type); @@ -384,14 +406,14 @@ void MemoryViewWidget::UpdateColumns(Type type, int first_column) cell_item->setText(value_to_string(cell_address)); cell_item->setData(USER_ROLE_IS_ROW_BREAKPOINT_CELL, false); cell_item->setData(USER_ROLE_CELL_ADDRESS, cell_address); - cell_item->setData(USER_ROLE_HAS_VALUE, true); + cell_item->setData(USER_ROLE_VALUE_TYPE, static_cast(type)); } else { cell_item->setText(QStringLiteral("-")); cell_item->setData(USER_ROLE_IS_ROW_BREAKPOINT_CELL, false); cell_item->setData(USER_ROLE_CELL_ADDRESS, cell_address); - cell_item->setData(USER_ROLE_HAS_VALUE, false); + cell_item->setData(USER_ROLE_VALUE_TYPE, static_cast(Type::Null)); } } }; @@ -537,6 +559,125 @@ AddressSpace::Type MemoryViewWidget::GetAddressSpace() const return m_address_space; } +void MemoryViewWidget::SetCellValue(u32 address, int type, QString input_text) +{ + const Type data_type = static_cast(type); + if (data_type == Type::Null) + return; + + bool good = false; + QString hex_string; + int radix = 0; + + switch (data_type) + { + case Type::ASCII: + { + good = true; + const QByteArray bytes = input_text.toLatin1(); + hex_string = QString::fromLatin1(bytes.toHex()); + break; + } + case Type::Float32: + { + const float value_float = input_text.toFloat(&good); + + if (good) + { + const u32 hex_out = Common::BitCast(value_float); + hex_string = QString::fromStdString(fmt::format("{:08X}", hex_out)); + } + break; + } + case Type::Double: + { + const double value_double = input_text.toDouble(&good); + + if (good) + { + const u64 hex_out = Common::BitCast(value_double); + hex_string = QString::fromStdString(fmt::format("{:016X}", hex_out)); + } + break; + } + case Type::Signed8: + { + const short value = input_text.toShort(&good, radix); + good &= std::numeric_limits::min() <= value && + value <= std::numeric_limits::max(); + if (good) + hex_string = QString::fromStdString(fmt::sprintf("%02hhX", value)); + break; + } + case Type::Signed16: + { + const short value = input_text.toShort(&good, radix); + if (good) + hex_string = QString::fromStdString(fmt::sprintf("%04hX", value)); + break; + } + case Type::Signed32: + { + const int value_int = input_text.toInt(&good, radix); + if (good) + hex_string = QString::fromStdString(fmt::sprintf("%08X", value_int)); + break; + } + case Type::Hex8: + radix = 16; + [[fallthrough]]; + case Type::Unsigned8: + { + const unsigned short value = input_text.toUShort(&good, radix); + good &= (value & 0xFF00) == 0; + if (good) + hex_string = QString::fromStdString(fmt::format("{:02X}", value)); + break; + } + case Type::Hex16: + radix = 16; + [[fallthrough]]; + case Type::Unsigned16: + { + const unsigned short value = input_text.toUShort(&good, radix); + if (good) + hex_string = QString::fromStdString(fmt::format("{:04X}", value)); + break; + } + case Type::Hex32: + radix = 16; + [[fallthrough]]; + case Type::Unsigned32: + { + const u32 value = input_text.toUInt(&good, radix); + if (good) + hex_string = QString::fromStdString(fmt::format("{:08X}", value)); + break; + } + case Type::Hex64: + { + const u64 value = input_text.toULongLong(&good, 16); + if (good) + hex_string = QString::fromStdString(fmt::format("{:016X}", value)); + break; + } + } + + if (!good) + return; + + QByteArray bytes = QByteArray::fromHex(hex_string.toUtf8()); + + // Cannot be used while update is running. + AddressSpace::Accessors* accessors = AddressSpace::GetAccessors(m_address_space); + + if (!accessors->IsValidAddress(address)) + return; + + for (const char c : bytes) + accessors->WriteU8(address++, static_cast(c)); +} + void MemoryViewWidget::SetDisplay(Type type, int bytes_per_row, int alignment, bool dual_view) { m_type = type; @@ -638,7 +779,8 @@ void MemoryViewWidget::OnContextMenu(const QPoint& pos) if (!item_selected || item_selected->data(USER_ROLE_IS_ROW_BREAKPOINT_CELL).toBool()) return; - const bool item_has_value = item_selected->data(USER_ROLE_HAS_VALUE).toBool(); + const bool item_has_value = + item_selected->data(USER_ROLE_VALUE_TYPE).toInt() != static_cast(Type::Null); const u32 addr = item_selected->data(USER_ROLE_CELL_ADDRESS).toUInt(); auto* menu = new QMenu(this); @@ -654,7 +796,7 @@ void MemoryViewWidget::OnContextMenu(const QPoint& pos) auto* copy_value = menu->addAction(tr("Copy Value"), this, [this, &pos] { // Re-fetch the item in case the underlying table has refreshed since the menu was opened. auto* item = m_table->itemAt(pos); - if (item && item->data(USER_ROLE_HAS_VALUE).toBool()) + if (item && item->data(USER_ROLE_VALUE_TYPE).toInt() != static_cast(Type::Null)) QApplication::clipboard()->setText(item->text()); }); copy_value->setEnabled(item_has_value); diff --git a/Source/Core/DolphinQt/Debugger/MemoryViewWidget.h b/Source/Core/DolphinQt/Debugger/MemoryViewWidget.h index 45266aad3948..a7b8fa8d4a58 100644 --- a/Source/Core/DolphinQt/Debugger/MemoryViewWidget.h +++ b/Source/Core/DolphinQt/Debugger/MemoryViewWidget.h @@ -23,6 +23,7 @@ class MemoryViewWidget final : public QWidget public: enum class Type : int { + Null = 0, Hex8 = 1, Hex16, Hex32, @@ -65,6 +66,7 @@ class MemoryViewWidget final : public QWidget void RequestWatch(QString name, u32 address); private: + void SetCellValue(u32 address, int type, QString input_text); void OnContextMenu(const QPoint& pos); void OnCopyAddress(u32 addr); void OnCopyHex(u32 addr); @@ -85,6 +87,7 @@ class MemoryViewWidget final : public QWidget int m_bytes_per_row = 16; int m_alignment = 16; bool m_dual_view = false; + bool m_updating = false; friend class MemoryViewTable; };