From ce3d110ba1be6348a43ca2c4dc7e9410f68b0a51 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Fri, 6 Sep 2024 22:48:50 +1000 Subject: [PATCH] Qt: Add edit function to debugger memory view --- src/core/bus.cpp | 25 ++-- src/core/bus.h | 1 + src/duckstation-qt/debuggerwindow.cpp | 3 +- src/duckstation-qt/memoryviewwidget.cpp | 173 ++++++++++++++++++++++-- src/duckstation-qt/memoryviewwidget.h | 23 +++- 5 files changed, 197 insertions(+), 28 deletions(-) diff --git a/src/core/bus.cpp b/src/core/bus.cpp index 025de0fa6..47603e426 100644 --- a/src/core/bus.cpp +++ b/src/core/bus.cpp @@ -805,26 +805,31 @@ std::optional Bus::GetMemoryRegionForAddress(PhysicalMemoryAd return std::nullopt; } -static constexpr std::array, +static constexpr std::array, static_cast(Bus::MemoryRegion::Count)> s_code_region_ranges = {{ - {0, Bus::RAM_2MB_SIZE}, - {Bus::RAM_2MB_SIZE, Bus::RAM_2MB_SIZE * 2}, - {Bus::RAM_2MB_SIZE * 2, Bus::RAM_2MB_SIZE * 3}, - {Bus::RAM_2MB_SIZE * 3, Bus::RAM_MIRROR_END}, - {Bus::EXP1_BASE, Bus::EXP1_BASE + Bus::EXP1_SIZE}, - {CPU::SCRATCHPAD_ADDR, CPU::SCRATCHPAD_ADDR + CPU::SCRATCHPAD_SIZE}, - {Bus::BIOS_BASE, Bus::BIOS_BASE + Bus::BIOS_SIZE}, + {0, Bus::RAM_2MB_SIZE, true}, + {Bus::RAM_2MB_SIZE, Bus::RAM_2MB_SIZE * 2, true}, + {Bus::RAM_2MB_SIZE * 2, Bus::RAM_2MB_SIZE * 3, true}, + {Bus::RAM_2MB_SIZE * 3, Bus::RAM_MIRROR_END, true}, + {Bus::EXP1_BASE, Bus::EXP1_BASE + Bus::EXP1_SIZE, false}, + {CPU::SCRATCHPAD_ADDR, CPU::SCRATCHPAD_ADDR + CPU::SCRATCHPAD_SIZE, true}, + {Bus::BIOS_BASE, Bus::BIOS_BASE + Bus::BIOS_SIZE, false}, }}; PhysicalMemoryAddress Bus::GetMemoryRegionStart(MemoryRegion region) { - return s_code_region_ranges[static_cast(region)].first; + return std::get<0>(s_code_region_ranges[static_cast(region)]); } PhysicalMemoryAddress Bus::GetMemoryRegionEnd(MemoryRegion region) { - return s_code_region_ranges[static_cast(region)].second; + return std::get<1>(s_code_region_ranges[static_cast(region)]); +} + +bool Bus::IsMemoryRegionWritable(MemoryRegion region) +{ + return std::get<2>(s_code_region_ranges[static_cast(region)]); } u8* Bus::GetMemoryRegionPointer(MemoryRegion region) diff --git a/src/core/bus.h b/src/core/bus.h index b158580c6..b4b3812b3 100644 --- a/src/core/bus.h +++ b/src/core/bus.h @@ -221,6 +221,7 @@ enum class MemoryRegion std::optional GetMemoryRegionForAddress(PhysicalMemoryAddress address); PhysicalMemoryAddress GetMemoryRegionStart(MemoryRegion region); PhysicalMemoryAddress GetMemoryRegionEnd(MemoryRegion region); +bool IsMemoryRegionWritable(MemoryRegion region); u8* GetMemoryRegionPointer(MemoryRegion region); std::optional SearchMemory(PhysicalMemoryAddress start_address, const u8* pattern, const u8* mask, u32 pattern_length); diff --git a/src/duckstation-qt/debuggerwindow.cpp b/src/duckstation-qt/debuggerwindow.cpp index 9f10ce743..38d16f701 100644 --- a/src/duckstation-qt/debuggerwindow.cpp +++ b/src/duckstation-qt/debuggerwindow.cpp @@ -547,7 +547,8 @@ void DebuggerWindow::setMemoryViewRegion(Bus::MemoryRegion region) const PhysicalMemoryAddress start = Bus::GetMemoryRegionStart(region); const PhysicalMemoryAddress end = Bus::GetMemoryRegionEnd(region); - m_ui.memoryView->setData(start, Bus::GetMemoryRegionPointer(region), end - start); + m_ui.memoryView->setData(start, Bus::GetMemoryRegionPointer(region), end - start, + Bus::IsMemoryRegionWritable(region)); #define SET_REGION_RADIO_BUTTON(name, rb_region) \ do \ diff --git a/src/duckstation-qt/memoryviewwidget.cpp b/src/duckstation-qt/memoryviewwidget.cpp index 98d34b7fc..9d097c614 100644 --- a/src/duckstation-qt/memoryviewwidget.cpp +++ b/src/duckstation-qt/memoryviewwidget.cpp @@ -1,10 +1,14 @@ #include "memoryviewwidget.h" + +#include +#include #include #include #include MemoryViewWidget::MemoryViewWidget(QWidget* parent /* = nullptr */, size_t address_offset /* = 0 */, - const void* data_ptr /* = nullptr */, size_t data_size /* = 0 */) + void* data_ptr /* = nullptr */, size_t data_size /* = 0 */, + bool data_editable /* = false */) : QAbstractScrollArea(parent) { m_bytes_per_line = 16; @@ -15,7 +19,7 @@ MemoryViewWidget::MemoryViewWidget(QWidget* parent /* = nullptr */, size_t addre connect(horizontalScrollBar(), &QScrollBar::valueChanged, this, &MemoryViewWidget::adjustContent); if (data_ptr) - setData(address_offset, data_ptr, data_size); + setData(address_offset, data_ptr, data_size, data_editable); } MemoryViewWidget::~MemoryViewWidget() = default; @@ -42,11 +46,13 @@ void MemoryViewWidget::updateMetrics() m_char_height = fm.height(); } -void MemoryViewWidget::setData(size_t address_offset, const void* data_ptr, size_t data_size) +void MemoryViewWidget::setData(size_t address_offset, void* data_ptr, size_t data_size, bool data_editable) { m_data = data_ptr; m_data_size = data_size; + m_data_editable = data_editable; m_address_offset = address_offset; + m_selected_address = INVALID_SELECTED_ADDRESS; adjustContent(); } @@ -84,18 +90,92 @@ void MemoryViewWidget::setFont(const QFont& font) updateMetrics(); } -void MemoryViewWidget::resizeEvent(QResizeEvent*) +void MemoryViewWidget::resizeEvent(QResizeEvent* event) { adjustContent(); } +void MemoryViewWidget::mousePressEvent(QMouseEvent* event) +{ + if ((event->buttons() & Qt::LeftButton) != 0) + updateSelectedByte(event->pos()); + + QAbstractScrollArea::mousePressEvent(event); +} + +void MemoryViewWidget::mouseMoveEvent(QMouseEvent* event) +{ + if ((event->buttons() & Qt::LeftButton) != 0) + updateSelectedByte(event->pos()); + + QAbstractScrollArea::mouseMoveEvent(event); +} + +void MemoryViewWidget::keyPressEvent(QKeyEvent* event) +{ + if (m_selected_address < m_data_size && m_data_editable) + { + const int key = event->key(); + if (key == Qt::Key_Backspace) + { + if (m_selected_address > 0) + { + m_selected_address--; + m_editing_nibble = -1; + viewport()->update(); + } + } + else + { + const QString key_text = event->text(); + if (key_text.length() == 1) + { + const char ch = key_text[0].toLatin1(); + if (m_selection_was_ascii) + { + std::memcpy(static_cast(m_data) + m_selected_address, &ch, sizeof(unsigned char)); + m_selected_address = std::min(m_selected_address + 1, m_data_size - 1); + viewport()->update(); + } + else + { + int nibble = -1; + if (ch >= 'a' && ch <= 'f') + nibble = ch - 'a' + 0xa; + else if (ch >= 'A' && ch <= 'F') + nibble = ch - 'A' + 0xa; + else if (ch >= '0' && ch <= '9') + nibble = ch - '0'; + if (nibble >= 0) + { + m_editing_nibble++; + + unsigned char* pdata = static_cast(m_data) + m_selected_address; + *pdata = (*pdata & ~(0xf0 >> (m_editing_nibble * 4))) | (nibble << ((1 - m_editing_nibble) * 4)); + + if (m_editing_nibble == 1) + { + m_editing_nibble = -1; + m_selected_address = std::min(m_selected_address + 1, m_data_size - 1); + } + + viewport()->update(); + } + } + } + } + } + + QAbstractScrollArea::keyPressEvent(event); +} + template static bool RangesOverlap(T x1, T x2, T y1, T y2) { return (x2 >= y1 && x1 < y2); } -void MemoryViewWidget::paintEvent(QPaintEvent*) +void MemoryViewWidget::paintEvent(QPaintEvent* event) { QPainter painter(viewport()); painter.setFont(font()); @@ -103,12 +183,15 @@ void MemoryViewWidget::paintEvent(QPaintEvent*) return; const QColor highlight_color(100, 100, 0); + const QColor selected_color = viewport()->palette().color(QPalette::Highlight); + const QColor text_color = viewport()->palette().color(QPalette::WindowText); + const QColor edited_color(255, 0, 0); const int offsetX = horizontalScrollBar()->value(); int y = m_char_height; QString address; - painter.setPen(viewport()->palette().color(QPalette::WindowText)); + painter.setPen(text_color); y += m_char_height; @@ -162,10 +245,25 @@ void MemoryViewWidget::paintEvent(QPaintEvent*) { unsigned char value; std::memcpy(&value, static_cast(m_data) + offset, sizeof(value)); - if (offset >= m_highlight_start && offset < m_highlight_end) - painter.fillRect(x - m_char_width, y - m_char_height + 3, HEX_CHAR_WIDTH, m_char_height, highlight_color); + if (m_selected_address == offset) + painter.fillRect(x - m_char_width, y - m_char_height + 4, HEX_CHAR_WIDTH, m_char_height, selected_color); + else if (offset >= m_highlight_start && offset < m_highlight_end) + painter.fillRect(x - m_char_width, y - m_char_height + 4, HEX_CHAR_WIDTH, m_char_height, highlight_color); + + if (m_selected_address != offset || m_editing_nibble != 0 || m_selection_was_ascii) [[likely]] + { + painter.drawText(x, y, QString::asprintf("%02X", value)); + } + else + { + const QString high = QString::asprintf("%X", value >> 4); + const QRect low_rc = painter.boundingRect(x, y, HEX_CHAR_WIDTH, m_char_height, 0, high); + painter.setPen(edited_color); + painter.drawText(x, y, high); + painter.setPen(text_color); + painter.drawText(x + low_rc.width(), y, QString::asprintf("%X", (value & 0xF))); + } - painter.drawText(x, y, QString::asprintf("%02X", value)); x += HEX_CHAR_WIDTH; } y += m_char_height; @@ -195,8 +293,10 @@ void MemoryViewWidget::paintEvent(QPaintEvent*) { unsigned char value; std::memcpy(&value, static_cast(m_data) + offset, sizeof(value)); - if (offset >= m_highlight_start && offset < m_highlight_end) - painter.fillRect(x, y - m_char_height + 3, 2 * m_char_width, m_char_height, highlight_color); + if (m_selected_address == offset) + painter.fillRect(x, y - m_char_height + 4, 2 * m_char_width, m_char_height, selected_color); + else if (offset >= m_highlight_start && offset < m_highlight_end) + painter.fillRect(x, y - m_char_height + 4, 2 * m_char_width, m_char_height, highlight_color); if (!std::isprint(value)) value = '.'; @@ -207,6 +307,57 @@ void MemoryViewWidget::paintEvent(QPaintEvent*) } } +void MemoryViewWidget::updateSelectedByte(const QPoint& pos) +{ + const int xpos = pos.x() + horizontalScrollBar()->value(); + const int ypos = pos.y(); + + size_t new_selection = INVALID_SELECTED_ADDRESS; + bool new_ascii = false; + + // to left or above hex view + const int addr_width = addressWidth(); + if (xpos >= addr_width && ypos >= m_char_height) + { + const int row = (ypos - m_char_height) / m_char_height; + const size_t row_address = m_start_offset + (static_cast(row) * m_bytes_per_line); + + // out of Y range + if (row_address < m_end_offset) + { + // in hex view? + const int hex_end = addr_width + hexWidth() + m_char_width; + if (xpos < hex_end) + { + const int hex_char_width = 4 * m_char_width; + const int hex_offset = (xpos - addr_width) / hex_char_width; + new_selection = row_address + static_cast(hex_offset); + } + else + { + // in ascii view? + const int ascii_char_width = 2 * m_char_width; + const int ascii_end = hex_end + (m_bytes_per_line * ascii_char_width); + + // might be offscreen again + if (xpos < ascii_end) + { + const int ascii_offset = (xpos - hex_end) / ascii_char_width; + new_selection = row_address + static_cast(ascii_offset); + new_ascii = true; + } + } + } + } + + if (new_selection != m_selected_address || new_ascii != m_selection_was_ascii) + { + m_selected_address = new_selection; + m_selection_was_ascii = new_ascii; + viewport()->update(); + } +} + void MemoryViewWidget::adjustContent() { if (!m_data) diff --git a/src/duckstation-qt/memoryviewwidget.h b/src/duckstation-qt/memoryviewwidget.h index 760124989..f09f0e8f0 100644 --- a/src/duckstation-qt/memoryviewwidget.h +++ b/src/duckstation-qt/memoryviewwidget.h @@ -8,13 +8,13 @@ class MemoryViewWidget : public QAbstractScrollArea public: Q_OBJECT public: - MemoryViewWidget(QWidget* parent = nullptr, size_t address_offset = 0, const void* data_ptr = nullptr, - size_t data_size = 0); + MemoryViewWidget(QWidget* parent = nullptr, size_t address_offset = 0, void* data_ptr = nullptr, size_t data_size = 0, + bool data_editable = false); ~MemoryViewWidget(); size_t addressOffset() const { return m_address_offset; } - void setData(size_t address_offset, const void* data_ptr, size_t data_size); + void setData(size_t address_offset, void* data_ptr, size_t data_size, bool data_editable); void setHighlightRange(size_t start, size_t end); void clearHighlightRange(); void scrolltoOffset(size_t offset); @@ -22,19 +22,25 @@ public: void setFont(const QFont& font); protected: - void paintEvent(QPaintEvent*); - void resizeEvent(QResizeEvent*); + void paintEvent(QPaintEvent* event); + void resizeEvent(QResizeEvent* event); + void mousePressEvent(QMouseEvent* event); + void mouseMoveEvent(QMouseEvent* event); + void keyPressEvent(QKeyEvent* event); private Q_SLOTS: void adjustContent(); private: + static constexpr size_t INVALID_SELECTED_ADDRESS = ~static_cast(0); + int addressWidth() const; int hexWidth() const; int asciiWidth() const; void updateMetrics(); + void updateSelectedByte(const QPoint& pos); - const void* m_data; + void* m_data; size_t m_data_size; size_t m_address_offset; @@ -44,6 +50,11 @@ private: size_t m_highlight_start = 0; size_t m_highlight_end = 0; + size_t m_selected_address = INVALID_SELECTED_ADDRESS; + int m_editing_nibble = -1; + bool m_selection_was_ascii = false; + bool m_data_editable = false; + unsigned m_bytes_per_line; int m_char_width;