Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Debugger MemoryViewWidget: Allow direct editing of memory cells. #10794

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
196 changes: 187 additions & 9 deletions Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,17 @@
#include <QMenu>
#include <QMouseEvent>
#include <QScrollBar>
#include <QSignalBlocker>
#include <QTableWidget>
#include <QtGlobal>

#include <cmath>
#include <fmt/printf.h>

#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"
Expand All @@ -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.
Expand All @@ -55,6 +58,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
Expand Down Expand Up @@ -122,6 +126,27 @@ class MemoryViewTable final : public QTableWidget
}
}

void OnItemChanged(QTableWidgetItem* item)
{
QString text = item->text();
MemoryViewWidget::Type type =
static_cast<MemoryViewWidget::Type>(item->data(USER_ROLE_VALUE_TYPE).toInt());
std::vector<u8> bytes = m_view->ConvertTextToBytes(type, text);

u32 address = item->data(USER_ROLE_CELL_ADDRESS).toUInt();
u32 end_address = address + static_cast<u32>(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;
};
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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<int>(Type::Null));

m_table->setItem(i, 0, bp_item);

Expand All @@ -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<int>(Type::Null));

m_table->setItem(i, 1, row_item);

Expand All @@ -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<int>(Type::Null));

m_table->setItem(i, c, item);
}
Expand Down Expand Up @@ -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);
Expand All @@ -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<int>(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<int>(Type::Null));
}
}
};
Expand Down Expand Up @@ -537,6 +564,156 @@ AddressSpace::Type MemoryViewWidget::GetAddressSpace() const
return m_address_space;
}

std::vector<u8> 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<u8> bytes;

for (const char c : qbytes)
bytes.push_back(static_cast<u8>(c));

return bytes;
}
case Type::Float32:
{
const float float_value = input_text.toFloat(&good);

if (good)
{
const u32 value = Common::BitCast<u32>(float_value);
auto std_array = Common::BitCastToArray<u8>(Common::swap32(value));
return std::vector<u8>(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<u64>(double_value);
auto std_array = Common::BitCastToArray<u8>(Common::swap64(value));
return std::vector<u8>(std_array.begin(), std_array.end());
}
break;
}
case Type::Signed8:
{
const short value = input_text.toShort(&good, radix);
good &= std::numeric_limits<signed char>::min() <= value &&
value <= std::numeric_limits<signed char>::max();
if (good)
{
auto std_array = Common::BitCastToArray<u8>(Common::swap8(value));
return std::vector<u8>(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<u8>(Common::swap16(value));
return std::vector<u8>(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<u8>(Common::swap32(value));
return std::vector<u8>(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<u8>(Common::swap8(value));
return std::vector<u8>(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<u8>(Common::swap16(value));
return std::vector<u8>(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<u8>(Common::swap32(value));
return std::vector<u8>(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<u8>(Common::swap64(value));
return std::vector<u8>(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<u8> bytes;

for (const char c : qbytes)
bytes.push_back(static_cast<u8>(c));

return bytes;
}
break;
}
}

return {};
}

void MemoryViewWidget::SetDisplay(Type type, int bytes_per_row, int alignment, bool dual_view)
{
m_type = type;
Expand Down Expand Up @@ -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<int>(Type::Null);
const u32 addr = item_selected->data(USER_ROLE_CELL_ADDRESS).toUInt();

auto* menu = new QMenu(this);
Expand All @@ -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<int>(Type::Null))
QApplication::clipboard()->setText(item->text());
});
copy_value->setEnabled(item_has_value);
Expand Down
3 changes: 3 additions & 0 deletions Source/Core/DolphinQt/Debugger/MemoryViewWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -51,6 +53,7 @@ class MemoryViewWidget final : public QWidget
void UpdateFont();
void ToggleBreakpoint(u32 addr, bool row);

std::vector<u8> 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);
Expand Down