Skip to content

Commit

Permalink
Debugger MemoryViewWidget: Allow direct editing of memory cells.
Browse files Browse the repository at this point in the history
  • Loading branch information
TryTwo committed Jun 29, 2022
1 parent ab52c9d commit cecfd19
Show file tree
Hide file tree
Showing 2 changed files with 154 additions and 9 deletions.
160 changes: 151 additions & 9 deletions Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp
Expand Up @@ -14,6 +14,7 @@
#include <QtGlobal>

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

#include "Common/Align.h"
#include "Common/FloatUtils.h"
Expand All @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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;
};
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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<int>(Type::Null));

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

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

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

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

m_table->setItem(i, c, item);
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand All @@ -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<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 +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>(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<u32>(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<u64>(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<signed char>::min() <= value &&
value <= std::numeric_limits<signed char>::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<u8>(c));
}

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

auto* menu = new QMenu(this);
Expand All @@ -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<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
Expand Up @@ -23,6 +23,7 @@ class MemoryViewWidget final : public QWidget
public:
enum class Type : int
{
Null = 0,
Hex8 = 1,
Hex16,
Hex32,
Expand Down Expand Up @@ -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);
Expand All @@ -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;
};

0 comments on commit cecfd19

Please sign in to comment.