From bd59b0a4902718aed235e2f22ba7dd9bf15789f6 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 | 196 +++++++++++++++++- .../DolphinQt/Debugger/MemoryViewWidget.h | 3 + .../Core/DolphinQt/Debugger/MemoryWidget.cpp | 184 +++++----------- Source/Core/DolphinQt/Debugger/MemoryWidget.h | 15 -- 4 files changed, 247 insertions(+), 151 deletions(-) diff --git a/Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp b/Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp index 7e87d9720b..c12fbf0d3b 100644 --- a/Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp @@ -10,14 +10,17 @@ #include #include #include +#include #include #include #include +#include #include "Common/Align.h" #include "Common/FloatUtils.h" #include "Common/StringUtil.h" +#include "Common/Swap.h" #include "Core/Core.h" #include "Core/HW/AddressSpace.h" #include "Core/PowerPC/BreakPoints.h" @@ -32,7 +35,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 +58,7 @@ public: connect(this, &MemoryViewTable::customContextMenuRequested, m_view, &MemoryViewWidget::OnContextMenu); + connect(this, &MemoryViewTable::itemChanged, this, &MemoryViewTable::OnItemChanged); } void resizeEvent(QResizeEvent* event) override @@ -122,6 +126,27 @@ public: } } + void OnItemChanged(QTableWidgetItem* item) + { + QString text = item->text(); + MemoryViewWidget::Type type = + static_cast(item->data(USER_ROLE_VALUE_TYPE).toInt()); + std::vector bytes = m_view->ConvertTextToBytes(type, text); + + u32 address = item->data(USER_ROLE_CELL_ADDRESS).toUInt(); + u32 end_address = address + static_cast(bytes.size()) - 1; + AddressSpace::Accessors* accessors = AddressSpace::GetAccessors(m_view->GetAddressSpace()); + + if (!bytes.empty() && accessors->IsValidAddress(address) && + accessors->IsValidAddress(end_address)) + { + for (const u8 c : bytes) + accessors->WriteU8(address++, c); + } + + m_view->Update(); + } + private: MemoryViewWidget* m_view; }; @@ -235,6 +260,8 @@ constexpr int GetCharacterCount(MemoryViewWidget::Type type) void MemoryViewWidget::Update() { + const QSignalBlocker blocker(m_table); + m_table->clearSelection(); u32 address = m_address; @@ -272,7 +299,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 +309,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 +324,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); } @@ -372,7 +399,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 +411,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 +564,156 @@ AddressSpace::Type MemoryViewWidget::GetAddressSpace() const return m_address_space; } +std::vector MemoryViewWidget::ConvertTextToBytes(Type type, QString input_text) +{ + if (type == Type::Null) + return {}; + + bool good = false; + int radix = 0; + + switch (type) + { + case Type::ASCII: + { + const QByteArray qbytes = input_text.toUtf8(); + std::vector bytes; + + for (const char c : qbytes) + bytes.push_back(static_cast(c)); + + return bytes; + } + case Type::Float32: + { + const float float_value = input_text.toFloat(&good); + + if (good) + { + const u32 value = Common::BitCast(float_value); + auto std_array = Common::BitCastToArray(Common::swap32(value)); + return std::vector(std_array.begin(), std_array.end()); + } + break; + } + case Type::Double: + { + const double double_value = input_text.toDouble(&good); + + if (good) + { + const u64 value = Common::BitCast(double_value); + auto std_array = Common::BitCastToArray(Common::swap64(value)); + return std::vector(std_array.begin(), std_array.end()); + } + 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) + { + auto std_array = Common::BitCastToArray(Common::swap8(value)); + return std::vector(std_array.begin(), std_array.end()); + } + break; + } + case Type::Signed16: + { + const short value = input_text.toShort(&good, radix); + if (good) + { + auto std_array = Common::BitCastToArray(Common::swap16(value)); + return std::vector(std_array.begin(), std_array.end()); + } + break; + } + case Type::Signed32: + { + const int value = input_text.toInt(&good, radix); + if (good) + { + auto std_array = Common::BitCastToArray(Common::swap32(value)); + return std::vector(std_array.begin(), std_array.end()); + } + 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) + { + auto std_array = Common::BitCastToArray(Common::swap8(value)); + return std::vector(std_array.begin(), std_array.end()); + } + break; + } + case Type::Hex16: + radix = 16; + [[fallthrough]]; + case Type::Unsigned16: + { + const unsigned short value = input_text.toUShort(&good, radix); + if (good) + { + auto std_array = Common::BitCastToArray(Common::swap16(value)); + return std::vector(std_array.begin(), std_array.end()); + } + break; + } + case Type::Hex32: + radix = 16; + [[fallthrough]]; + case Type::Unsigned32: + { + const u32 value = input_text.toUInt(&good, radix); + if (good) + { + auto std_array = Common::BitCastToArray(Common::swap32(value)); + return std::vector(std_array.begin(), std_array.end()); + } + break; + } + case Type::Hex64: + { + const u64 value = input_text.toULongLong(&good, 16); + if (good) + { + auto std_array = Common::BitCastToArray(Common::swap64(value)); + return std::vector(std_array.begin(), std_array.end()); + } + break; + } + case Type::HexString: + { + // Confirm it is only hex bytes + const QRegularExpression is_hex(QStringLiteral("^([0-9A-F]{2})*$"), + QRegularExpression::CaseInsensitiveOption); + const QRegularExpressionMatch match = is_hex.match(input_text); + good = match.hasMatch(); + if (good) + { + const QByteArray qbytes = QByteArray::fromHex(input_text.toUtf8()); + std::vector bytes; + + for (const char c : qbytes) + bytes.push_back(static_cast(c)); + + return bytes; + } + break; + } + } + + return {}; +} + void MemoryViewWidget::SetDisplay(Type type, int bytes_per_row, int alignment, bool dual_view) { m_type = type; @@ -638,7 +815,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 +832,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 45266aad39..16e3650068 100644 --- a/Source/Core/DolphinQt/Debugger/MemoryViewWidget.h +++ b/Source/Core/DolphinQt/Debugger/MemoryViewWidget.h @@ -23,10 +23,12 @@ class MemoryViewWidget final : public QWidget public: enum class Type : int { + Null = 0, Hex8 = 1, Hex16, Hex32, Hex64, + HexString, Unsigned8, Unsigned16, Unsigned32, @@ -51,6 +53,7 @@ public: void UpdateFont(); void ToggleBreakpoint(u32 addr, bool row); + std::vector ConvertTextToBytes(Type type, QString input_text); void SetAddressSpace(AddressSpace::Type address_space); AddressSpace::Type GetAddressSpace() const; void SetDisplay(Type type, int bytes_per_row, int alignment, bool dual_view); diff --git a/Source/Core/DolphinQt/Debugger/MemoryWidget.cpp b/Source/Core/DolphinQt/Debugger/MemoryWidget.cpp index 93bb6561ba..55af3dbc80 100644 --- a/Source/Core/DolphinQt/Debugger/MemoryWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/MemoryWidget.cpp @@ -36,6 +36,8 @@ #include "DolphinQt/QtUtils/ModalMessageBox.h" #include "DolphinQt/Settings.h" +using Type = MemoryViewWidget::Type; + MemoryWidget::MemoryWidget(QWidget* parent) : QDockWidget(parent) { setWindowTitle(tr("Memory")); @@ -127,16 +129,16 @@ void MemoryWidget::CreateWidgets() m_input_combo = new QComboBox; m_input_combo->setMaxVisibleItems(20); // Order here determines combo list order. - m_input_combo->addItem(tr("Hex Byte String"), int(InputID::HEXSTR)); - m_input_combo->addItem(tr("ASCII"), int(InputID::ASCII)); - m_input_combo->addItem(tr("Float"), int(InputID::FLOAT)); - m_input_combo->addItem(tr("Double"), int(InputID::DOUBLE)); - m_input_combo->addItem(tr("Unsigned 8"), int(InputID::U8)); - m_input_combo->addItem(tr("Unsigned 16"), int(InputID::U16)); - m_input_combo->addItem(tr("Unsigned 32"), int(InputID::U32)); - m_input_combo->addItem(tr("Signed 8"), int(InputID::S8)); - m_input_combo->addItem(tr("Signed 16"), int(InputID::S16)); - m_input_combo->addItem(tr("Signed 32"), int(InputID::S32)); + m_input_combo->addItem(tr("Hex Byte String"), int(Type::HexString)); + m_input_combo->addItem(tr("ASCII"), int(Type::ASCII)); + m_input_combo->addItem(tr("Float"), int(Type::Float32)); + m_input_combo->addItem(tr("Double"), int(Type::Double)); + m_input_combo->addItem(tr("Unsigned 8"), int(Type::Unsigned8)); + m_input_combo->addItem(tr("Unsigned 16"), int(Type::Unsigned16)); + m_input_combo->addItem(tr("Unsigned 32"), int(Type::Unsigned32)); + m_input_combo->addItem(tr("Signed 8"), int(Type::Signed8)); + m_input_combo->addItem(tr("Signed 16"), int(Type::Signed16)); + m_input_combo->addItem(tr("Signed 32"), int(Type::Signed32)); // Dump auto* dump_group = new QGroupBox(tr("Dump")); @@ -192,18 +194,18 @@ void MemoryWidget::CreateWidgets() m_display_combo = new QComboBox; m_display_combo->setMaxVisibleItems(20); - m_display_combo->addItem(tr("Hex 8"), int(MemoryViewWidget::Type::Hex8)); - m_display_combo->addItem(tr("Hex 16"), int(MemoryViewWidget::Type::Hex16)); - m_display_combo->addItem(tr("Hex 32"), int(MemoryViewWidget::Type::Hex32)); - m_display_combo->addItem(tr("Unsigned 8"), int(MemoryViewWidget::Type::Unsigned8)); - m_display_combo->addItem(tr("Unsigned 16"), int(MemoryViewWidget::Type::Unsigned16)); - m_display_combo->addItem(tr("Unsigned 32"), int(MemoryViewWidget::Type::Unsigned32)); - m_display_combo->addItem(tr("Signed 8"), int(MemoryViewWidget::Type::Signed8)); - m_display_combo->addItem(tr("Signed 16"), int(MemoryViewWidget::Type::Signed16)); - m_display_combo->addItem(tr("Signed 32"), int(MemoryViewWidget::Type::Signed32)); - m_display_combo->addItem(tr("ASCII"), int(MemoryViewWidget::Type::ASCII)); - m_display_combo->addItem(tr("Float"), int(MemoryViewWidget::Type::Float32)); - m_display_combo->addItem(tr("Double"), int(MemoryViewWidget::Type::Double)); + m_display_combo->addItem(tr("Hex 8"), int(Type::Hex8)); + m_display_combo->addItem(tr("Hex 16"), int(Type::Hex16)); + m_display_combo->addItem(tr("Hex 32"), int(Type::Hex32)); + m_display_combo->addItem(tr("Unsigned 8"), int(Type::Unsigned8)); + m_display_combo->addItem(tr("Unsigned 16"), int(Type::Unsigned16)); + m_display_combo->addItem(tr("Unsigned 32"), int(Type::Unsigned32)); + m_display_combo->addItem(tr("Signed 8"), int(Type::Signed8)); + m_display_combo->addItem(tr("Signed 16"), int(Type::Signed16)); + m_display_combo->addItem(tr("Signed 32"), int(Type::Signed32)); + m_display_combo->addItem(tr("ASCII"), int(Type::ASCII)); + m_display_combo->addItem(tr("Float"), int(Type::Float32)); + m_display_combo->addItem(tr("Double"), int(Type::Double)); m_align_combo = new QComboBox; m_align_combo->addItem(tr("Fixed Alignment")); @@ -435,12 +437,12 @@ void MemoryWidget::OnAddressSpaceChanged() void MemoryWidget::OnDisplayChanged() { - const auto type = static_cast(m_display_combo->currentData().toInt()); + const auto type = static_cast(m_display_combo->currentData().toInt()); int bytes_per_row = m_row_length_combo->currentData().toInt(); int alignment; bool dual_view = m_dual_check->isChecked(); - if (type == MemoryViewWidget::Type::Double && bytes_per_row == 4) + if (type == Type::Double && bytes_per_row == 4) bytes_per_row = 8; // Alignment: First (fixed) option equals bytes per row. 'currentData' is correct for other @@ -522,119 +524,40 @@ void MemoryWidget::ValidateAndPreviewInputValue() { m_data_preview->clear(); QString input_text = m_data_edit->text(); - const auto combo_id = static_cast(m_input_combo->currentData().toInt()); + const auto input_type = static_cast(m_input_combo->currentData().toInt()); - m_base_check->setEnabled(combo_id == InputID::U32 || combo_id == InputID::S32 || - combo_id == InputID::U16 || combo_id == InputID::S16 || - combo_id == InputID::U8 || combo_id == InputID::S8); + m_base_check->setEnabled(input_type == Type::Unsigned32 || input_type == Type::Signed32 || + input_type == Type::Unsigned16 || input_type == Type::Signed16 || + input_type == Type::Unsigned8 || input_type == Type::Signed8); if (input_text.isEmpty()) return; // Remove any spaces - if (combo_id != InputID::ASCII) + if (input_type != Type::ASCII) input_text.remove(QLatin1Char(' ')); + if (m_base_check->isChecked()) + { + if (input_text.startsWith(QLatin1Char('-'))) + input_text.insert(1, QStringLiteral("0x")); + else + input_text.prepend(QStringLiteral("0x")); + } + QFont font; QPalette palette; - QString hex_string; - bool good = false; - const int radix = (m_base_check->isChecked() && m_base_check->isEnabled()) ? 16 : 0; + std::vector bytes = m_memory_view->ConvertTextToBytes(input_type, input_text); - switch (combo_id) + if (!bytes.empty()) { - case InputID::ASCII: - { - good = true; - const QByteArray bytes = input_text.toLatin1(); - hex_string = QString::fromLatin1(bytes.toHex()); - break; - } - case InputID::FLOAT: - { - const float value_float = input_text.toFloat(&good); + QString hex_string; + std::string s; - if (good) - { - const u32 hex_out = Common::BitCast(value_float); - hex_string = QString::fromStdString(fmt::format("{:08X}", hex_out)); - } - break; - } - case InputID::DOUBLE: - { - const double value_double = input_text.toDouble(&good); + for (const u8 c : bytes) + s.append(fmt::format("{:02x}", c)); - if (good) - { - const u64 hex_out = Common::BitCast(value_double); - hex_string = QString::fromStdString(fmt::format("{:016X}", hex_out)); - } - break; - } - case InputID::S8: - { - 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 InputID::S16: - { - const short value = input_text.toShort(&good, radix); - if (good) - hex_string = QString::fromStdString(fmt::sprintf("%04hX", value)); - break; - } - case InputID::S32: - { - const int value_int = input_text.toInt(&good, radix); - if (good) - hex_string = QString::fromStdString(fmt::sprintf("%08X", value_int)); - break; - } - case InputID::U8: - { - 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 InputID::U16: - { - const unsigned short value = input_text.toUShort(&good, radix); - if (good) - hex_string = QString::fromStdString(fmt::format("{:04X}", value)); - break; - } - case InputID::U32: - { - const u32 value = input_text.toUInt(&good, radix); - if (good) - hex_string = QString::fromStdString(fmt::format("{:08X}", value)); - break; - } - case InputID::HEXSTR: - { - // Confirm it is only hex bytes - const QRegularExpression is_hex(QStringLiteral("^([0-9A-F]{2})*$"), - QRegularExpression::CaseInsensitiveOption); - const QRegularExpressionMatch match = is_hex.match(input_text); - good = match.hasMatch(); - if (good) - { - const QByteArray hbytes = QByteArray::fromHex(input_text.toUtf8()); - hex_string = QString::fromLatin1(hbytes.toHex()); - } - break; - } - } - - if (good) - { + hex_string = QString::fromStdString(s); int output_length = hex_string.length(); if (output_length > 16) @@ -665,14 +588,14 @@ QByteArray MemoryWidget::GetInputData() const if (m_data_preview->text().isEmpty()) return QByteArray(); - const auto combo_id = static_cast(m_input_combo->currentData().toInt()); + const auto input_type = static_cast(m_input_combo->currentData().toInt()); // Ascii might be truncated, pull from data edit box. - if (combo_id == InputID::ASCII) + if (input_type == Type::ASCII) return QByteArray(m_data_edit->text().toUtf8()); - // If we are doing a large aray of hex bytes - if (combo_id == InputID::HEXSTR) + // If we are doing a large array of hex bytes + if (input_type == Type::HexString) return QByteArray::fromHex(m_data_edit->text().toUtf8()); // Data preview has exactly what we want to input, so pull it from there. @@ -708,6 +631,13 @@ void MemoryWidget::OnSetValue() } AddressSpace::Accessors* accessors = AddressSpace::GetAccessors(m_memory_view->GetAddressSpace()); + u32 end_address = target_addr.address + static_cast(bytes.size()) - 1; + + if (!accessors->IsValidAddress(target_addr.address) || !accessors->IsValidAddress(end_address)) + { + ModalMessageBox::critical(this, tr("Error"), tr("Target address range is invalid.")); + return; + } for (const char c : bytes) accessors->WriteU8(target_addr.address++, static_cast(c)); diff --git a/Source/Core/DolphinQt/Debugger/MemoryWidget.h b/Source/Core/DolphinQt/Debugger/MemoryWidget.h index ff0070ddb5..4e344af37b 100644 --- a/Source/Core/DolphinQt/Debugger/MemoryWidget.h +++ b/Source/Core/DolphinQt/Debugger/MemoryWidget.h @@ -20,21 +20,6 @@ class QRadioButton; class QShowEvent; class QSplitter; -enum class InputID : int -{ - // Order does not matter here. - S8 = 1, - S16, - S32, - U8, - U16, - U32, - HEXSTR, - FLOAT, - DOUBLE, - ASCII -}; - class MemoryWidget : public QDockWidget { Q_OBJECT