dolphin/Source/Core/DolphinQt2/Debugger/MemoryWidget.cpp
Lioncash 6d0cab3743 DolphinQt2/MemoryWidget: Use QString's toUtf8() where applicable instead of toStdString()
Avoids needing to iterate and append the characters in one case. This also
alters the function to not need to construct a temporary std::string
(QString's toUtf8() is sufficient, as QByteArray exposes iterators).

toStdString() is equivalent to retrieving the QString's underlying
QByteArray via calling QString's .toUtf8 member function and then
calling .toStdString() on the result of it (discarding the QByteArray
afterwords), so this just trims off an unnecessary step in the process.

This is also somewhat more indicative of the conversions going on:
toStdString() converts the underlying character sequence of a
QString to UTF-8, not strict ASCII, so we're really using a superset of
ASCII.
2018-05-10 22:10:45 -04:00

587 lines
17 KiB
C++

// Copyright 2018 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "DolphinQt2/Debugger/MemoryWidget.h"
#include <string>
#include <QCheckBox>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QMessageBox>
#include <QPushButton>
#include <QRadioButton>
#include <QScrollArea>
#include <QSpacerItem>
#include <QSplitter>
#include <QTableWidget>
#include <QVBoxLayout>
#include "Common/File.h"
#include "Common/FileUtil.h"
#include "Core/ConfigManager.h"
#include "Core/HW/DSP.h"
#include "Core/HW/Memmap.h"
#include "Core/PowerPC/PowerPC.h"
#include "DolphinQt2/Debugger/MemoryViewWidget.h"
#include "DolphinQt2/Settings.h"
MemoryWidget::MemoryWidget(QWidget* parent) : QDockWidget(parent)
{
setWindowTitle(tr("Memory"));
setObjectName(QStringLiteral("memory"));
setAllowedAreas(Qt::AllDockWidgetAreas);
CreateWidgets();
QSettings& settings = Settings::GetQSettings();
restoreGeometry(settings.value(QStringLiteral("memorywidget/geometry")).toByteArray());
setFloating(settings.value(QStringLiteral("memorywidget/floating")).toBool());
m_splitter->restoreState(settings.value(QStringLiteral("codewidget/splitter")).toByteArray());
connect(&Settings::Instance(), &Settings::MemoryVisibilityChanged,
[this](bool visible) { setHidden(!visible); });
connect(&Settings::Instance(), &Settings::DebugModeToggled,
[this](bool enabled) { setHidden(!enabled || !Settings::Instance().IsMemoryVisible()); });
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, &MemoryWidget::Update);
setHidden(!Settings::Instance().IsCodeVisible() || !Settings::Instance().IsDebugModeEnabled());
LoadSettings();
ConnectWidgets();
Update();
OnTypeChanged();
}
MemoryWidget::~MemoryWidget()
{
QSettings& settings = Settings::GetQSettings();
settings.setValue(QStringLiteral("memorywidget/geometry"), saveGeometry());
settings.setValue(QStringLiteral("memorywidget/floating"), isFloating());
settings.setValue(QStringLiteral("memorywidget/splitter"), m_splitter->saveState());
SaveSettings();
}
void MemoryWidget::CreateWidgets()
{
auto* layout = new QHBoxLayout;
//// Sidebar
// Search
m_search_address = new QLineEdit;
m_data_edit = new QLineEdit;
m_set_value = new QPushButton(tr("Set &Value"));
m_search_address->setPlaceholderText(tr("Search Address"));
m_data_edit->setPlaceholderText(tr("Value"));
// Dump
m_dump_mram = new QPushButton(tr("Dump &MRAM"));
m_dump_exram = new QPushButton(tr("Dump &ExRAM"));
m_dump_fake_vmem = new QPushButton(tr("Dump &FakeVMEM"));
// Search Options
auto* search_group = new QGroupBox(tr("Search"));
auto* search_layout = new QVBoxLayout;
search_group->setLayout(search_layout);
m_find_next = new QPushButton(tr("Find &Next"));
m_find_previous = new QPushButton(tr("Find &Previous"));
m_find_ascii = new QRadioButton(tr("ASCII"));
m_find_hex = new QRadioButton(tr("Hex"));
m_result_label = new QLabel;
search_layout->addWidget(m_find_next);
search_layout->addWidget(m_find_previous);
search_layout->addWidget(m_result_label);
// Data Type
auto* datatype_group = new QGroupBox(tr("Data Type"));
auto* datatype_layout = new QVBoxLayout;
datatype_group->setLayout(datatype_layout);
m_type_u8 = new QRadioButton(tr("U&8"));
m_type_u16 = new QRadioButton(tr("U&16"));
m_type_u32 = new QRadioButton(tr("U&32"));
m_type_ascii = new QRadioButton(tr("ASCII"));
m_type_float = new QRadioButton(tr("Float"));
datatype_layout->addWidget(m_type_u8);
datatype_layout->addWidget(m_type_u16);
datatype_layout->addWidget(m_type_u32);
datatype_layout->addWidget(m_type_ascii);
datatype_layout->addWidget(m_type_float);
// MBP options
auto* bp_group = new QGroupBox(tr("Memory breakpoint options"));
auto* bp_layout = new QVBoxLayout;
bp_group->setLayout(bp_layout);
// i18n: This string is used for a radio button that represents the type of
// memory breakpoint that gets triggered when a read operation or write operation occurs.
// The string is not a command to read and write something or to allow reading and writing.
m_bp_read_write = new QRadioButton(tr("Read and write"));
// i18n: This string is used for a radio button that represents the type of
// memory breakpoint that gets triggered when a read operation occurs.
// The string does not mean "read-only" in the sense that something cannot be written to.
m_bp_read_only = new QRadioButton(tr("Read only"));
// i18n: This string is used for a radio button that represents the type of
// memory breakpoint that gets triggered when a write operation occurs.
// The string does not mean "write-only" in the sense that something cannot be read from.
m_bp_write_only = new QRadioButton(tr("Write only"));
m_bp_log_check = new QCheckBox(tr("Log"));
bp_layout->addWidget(m_bp_read_write);
bp_layout->addWidget(m_bp_read_only);
bp_layout->addWidget(m_bp_write_only);
bp_layout->addWidget(m_bp_log_check);
// Sidebar
auto* sidebar = new QWidget;
auto* sidebar_layout = new QVBoxLayout;
sidebar->setLayout(sidebar_layout);
sidebar_layout->addWidget(m_search_address);
sidebar_layout->addWidget(m_data_edit);
sidebar_layout->addWidget(m_find_ascii);
sidebar_layout->addWidget(m_find_hex);
sidebar_layout->addWidget(m_set_value);
sidebar_layout->addItem(new QSpacerItem(1, 32));
sidebar_layout->addWidget(m_dump_mram);
sidebar_layout->addWidget(m_dump_exram);
sidebar_layout->addWidget(m_dump_fake_vmem);
sidebar_layout->addWidget(search_group);
sidebar_layout->addWidget(datatype_group);
sidebar_layout->addWidget(bp_group);
// Splitter
m_splitter = new QSplitter(Qt::Horizontal);
auto* sidebar_scroll = new QScrollArea;
sidebar_scroll->setWidget(sidebar);
sidebar_scroll->setWidgetResizable(true);
sidebar_scroll->setFixedWidth(250);
m_memory_view = new MemoryViewWidget(this);
m_splitter->addWidget(m_memory_view);
m_splitter->addWidget(sidebar_scroll);
layout->addWidget(m_splitter);
auto* widget = new QWidget;
widget->setLayout(layout);
setWidget(widget);
}
void MemoryWidget::ConnectWidgets()
{
connect(m_search_address, &QLineEdit::textChanged, this, &MemoryWidget::OnSearchAddress);
connect(m_data_edit, &QLineEdit::textChanged, this, &MemoryWidget::ValidateSearchValue);
connect(m_find_ascii, &QRadioButton::toggled, this, &MemoryWidget::ValidateSearchValue);
connect(m_find_hex, &QRadioButton::toggled, this, &MemoryWidget::ValidateSearchValue);
connect(m_set_value, &QPushButton::pressed, this, &MemoryWidget::OnSetValue);
connect(m_dump_mram, &QPushButton::pressed, this, &MemoryWidget::OnDumpMRAM);
connect(m_dump_exram, &QPushButton::pressed, this, &MemoryWidget::OnDumpExRAM);
connect(m_dump_fake_vmem, &QPushButton::pressed, this, &MemoryWidget::OnDumpFakeVMEM);
connect(m_find_next, &QPushButton::pressed, this, &MemoryWidget::OnFindNextValue);
connect(m_find_previous, &QPushButton::pressed, this, &MemoryWidget::OnFindPreviousValue);
for (auto* radio : {m_type_u8, m_type_u16, m_type_u32, m_type_ascii, m_type_float})
connect(radio, &QRadioButton::toggled, this, &MemoryWidget::OnTypeChanged);
for (auto* radio : {m_bp_read_write, m_bp_read_only, m_bp_write_only})
connect(radio, &QRadioButton::toggled, this, &MemoryWidget::OnBPTypeChanged);
connect(m_bp_log_check, &QCheckBox::toggled, this, &MemoryWidget::OnBPLogChanged);
connect(m_memory_view, &MemoryViewWidget::BreakpointsChanged, this,
&MemoryWidget::BreakpointsChanged);
}
void MemoryWidget::closeEvent(QCloseEvent*)
{
Settings::Instance().SetMemoryVisible(false);
}
void MemoryWidget::Update()
{
m_memory_view->Update();
update();
}
void MemoryWidget::LoadSettings()
{
QSettings& settings = Settings::GetQSettings();
const bool search_ascii =
settings.value(QStringLiteral("memorywidget/searchascii"), true).toBool();
const bool search_hex = settings.value(QStringLiteral("memorywidget/searchhex"), false).toBool();
m_find_ascii->setChecked(search_ascii);
m_find_hex->setChecked(search_hex);
const bool type_u8 = settings.value(QStringLiteral("memorywidget/typeu8"), true).toBool();
const bool type_u16 = settings.value(QStringLiteral("memorywidget/typeu16"), false).toBool();
const bool type_u32 = settings.value(QStringLiteral("memorywidget/typeu32"), false).toBool();
const bool type_float = settings.value(QStringLiteral("memorywidget/typefloat"), false).toBool();
const bool type_ascii = settings.value(QStringLiteral("memorywidget/typeascii"), false).toBool();
m_type_u8->setChecked(type_u8);
m_type_u16->setChecked(type_u16);
m_type_u32->setChecked(type_u32);
m_type_float->setChecked(type_float);
m_type_ascii->setChecked(type_ascii);
bool bp_rw = settings.value(QStringLiteral("memorywidget/bpreadwrite"), true).toBool();
bool bp_r = settings.value(QStringLiteral("memorywidget/bpread"), false).toBool();
bool bp_w = settings.value(QStringLiteral("memorywidget/bpwrite"), false).toBool();
bool bp_log = settings.value(QStringLiteral("memorywidget/bplog"), true).toBool();
m_bp_read_write->setChecked(bp_rw);
m_bp_read_only->setChecked(bp_r);
m_bp_write_only->setChecked(bp_w);
m_bp_log_check->setChecked(bp_log);
}
void MemoryWidget::SaveSettings()
{
QSettings& settings = Settings::GetQSettings();
settings.setValue(QStringLiteral("memorywidget/searchascii"), m_find_ascii->isChecked());
settings.setValue(QStringLiteral("memorywidget/searchhex"), m_find_hex->isChecked());
settings.setValue(QStringLiteral("memorywidget/typeu8"), m_type_u8->isChecked());
settings.setValue(QStringLiteral("memorywidget/typeu16"), m_type_u16->isChecked());
settings.setValue(QStringLiteral("memorywidget/typeu32"), m_type_u32->isChecked());
settings.setValue(QStringLiteral("memorywidget/typeascii"), m_type_ascii->isChecked());
settings.setValue(QStringLiteral("memorywidget/typefloat"), m_type_float->isChecked());
settings.setValue(QStringLiteral("memorywidget/bpreadwrite"), m_bp_read_write->isChecked());
settings.setValue(QStringLiteral("memorywidget/bpread"), m_bp_read_only->isChecked());
settings.setValue(QStringLiteral("memorywidget/bpwrite"), m_bp_write_only->isChecked());
settings.setValue(QStringLiteral("memorywidget/bplog"), m_bp_log_check->isChecked());
}
void MemoryWidget::OnTypeChanged()
{
MemoryViewWidget::Type type;
if (m_type_u8->isChecked())
type = MemoryViewWidget::Type::U8;
else if (m_type_u16->isChecked())
type = MemoryViewWidget::Type::U16;
else if (m_type_u32->isChecked())
type = MemoryViewWidget::Type::U32;
else if (m_type_ascii->isChecked())
type = MemoryViewWidget::Type::ASCII;
else
type = MemoryViewWidget::Type::Float32;
m_memory_view->SetType(type);
SaveSettings();
}
void MemoryWidget::OnBPLogChanged()
{
m_memory_view->SetBPLoggingEnabled(m_bp_log_check->isChecked());
SaveSettings();
}
void MemoryWidget::OnBPTypeChanged()
{
bool read_write = m_bp_read_write->isChecked();
bool read_only = m_bp_read_only->isChecked();
MemoryViewWidget::BPType type;
if (read_write)
type = MemoryViewWidget::BPType::ReadWrite;
else if (read_only)
type = MemoryViewWidget::BPType::ReadOnly;
else
type = MemoryViewWidget::BPType::WriteOnly;
m_memory_view->SetBPType(type);
SaveSettings();
}
void MemoryWidget::OnSearchAddress()
{
bool good;
u32 addr = m_search_address->text().toUInt(&good, 16);
QFont font;
QPalette palette;
if (good || m_search_address->text().isEmpty())
{
if (good)
m_memory_view->SetAddress(addr);
}
else
{
font.setBold(true);
palette.setColor(QPalette::Text, Qt::red);
}
m_search_address->setFont(font);
m_search_address->setPalette(palette);
}
void MemoryWidget::ValidateSearchValue()
{
QFont font;
QPalette palette;
if (m_find_hex->isChecked() && !m_data_edit->text().isEmpty())
{
bool good;
m_data_edit->text().toULongLong(&good, 16);
if (!good)
{
font.setBold(true);
palette.setColor(QPalette::Text, Qt::red);
}
}
m_data_edit->setFont(font);
m_data_edit->setPalette(palette);
}
void MemoryWidget::OnSetValue()
{
bool good_address;
u32 addr = m_search_address->text().toUInt(&good_address, 16);
if (!good_address)
{
QMessageBox::critical(this, tr("Error"), tr("Bad address provided."));
return;
}
if (m_data_edit->text().isEmpty())
{
QMessageBox::critical(this, tr("Error"), tr("No value provided."));
return;
}
if (m_find_ascii->isChecked())
{
const QByteArray bytes = m_data_edit->text().toUtf8();
for (char c : bytes)
PowerPC::HostWrite_U8(static_cast<u8>(c), addr++);
}
else
{
bool good_value;
u64 value = m_data_edit->text().toULongLong(&good_value, 16);
if (!good_value)
{
QMessageBox::critical(this, tr("Error"), tr("Bad value provided."));
return;
}
if (value == static_cast<u8>(value))
{
PowerPC::HostWrite_U8(static_cast<u8>(value), addr);
}
else if (value == static_cast<u16>(value))
{
PowerPC::HostWrite_U16(static_cast<u16>(value), addr);
}
else if (value == static_cast<u32>(value))
{
PowerPC::HostWrite_U32(static_cast<u32>(value), addr);
}
else
PowerPC::HostWrite_U64(value, addr);
}
Update();
}
static void DumpArray(const std::string& filename, const u8* data, size_t length)
{
if (!data)
return;
File::IOFile f(filename, "wb");
if (!f)
{
QMessageBox::critical(
nullptr, QObject::tr("Error"),
QObject::tr("Failed to dump %1: Can't open file").arg(QString::fromStdString(filename)));
return;
}
if (!f.WriteBytes(data, length))
{
QMessageBox::critical(nullptr, QObject::tr("Error"),
QObject::tr("Failed to dump %1: Failed to write to file")
.arg(QString::fromStdString(filename)));
}
}
void MemoryWidget::OnDumpMRAM()
{
DumpArray(File::GetUserPath(F_RAMDUMP_IDX), Memory::m_pRAM, Memory::REALRAM_SIZE);
}
void MemoryWidget::OnDumpExRAM()
{
if (SConfig::GetInstance().bWii)
{
DumpArray(File::GetUserPath(F_ARAMDUMP_IDX), Memory::m_pEXRAM, Memory::EXRAM_SIZE);
}
else
{
DumpArray(File::GetUserPath(F_ARAMDUMP_IDX), DSP::GetARAMPtr(), DSP::ARAM_SIZE);
}
}
void MemoryWidget::OnDumpFakeVMEM()
{
DumpArray(File::GetUserPath(F_FAKEVMEMDUMP_IDX), Memory::m_pFakeVMEM, Memory::FAKEVMEM_SIZE);
}
std::vector<u8> MemoryWidget::GetValueData() const
{
std::vector<u8> search_for; // Series of bytes we want to look for
if (m_find_ascii->isChecked())
{
const QByteArray bytes = m_data_edit->text().toUtf8();
search_for.assign(bytes.begin(), bytes.end());
}
else
{
bool good;
u64 value = m_data_edit->text().toULongLong(&good, 16);
if (!good)
return {};
int size;
if (value == static_cast<u8>(value))
size = sizeof(u8);
else if (value == static_cast<u16>(value))
size = sizeof(u16);
else if (value == static_cast<u32>(value))
size = sizeof(u32);
else
size = sizeof(u64);
for (int i = size - 1; i >= 0; i--)
search_for.push_back((value >> (i * 8)) & 0xFF);
}
return search_for;
}
void MemoryWidget::FindValue(bool next)
{
std::vector<u8> search_for = GetValueData();
if (search_for.empty())
{
m_result_label->setText(tr("No Value Given"));
return;
}
const u8* ram_ptr = nullptr;
std::size_t ram_size = 0;
u32 base_address = 0;
if (m_type_u16->isChecked())
{
ram_ptr = DSP::GetARAMPtr();
ram_size = DSP::ARAM_SIZE;
base_address = 0x0c005000;
}
else if (Memory::m_pRAM)
{
ram_ptr = Memory::m_pRAM;
ram_size = Memory::REALRAM_SIZE;
base_address = 0x80000000;
}
else
{
m_result_label->setText(tr("Memory Not Ready"));
return;
}
u32 addr = 0;
if (!m_search_address->text().isEmpty())
addr = m_search_address->text().toUInt(nullptr, 16) + 1;
if (addr >= base_address)
addr -= base_address;
if (addr >= ram_size - search_for.size())
{
m_result_label->setText(tr("Address Out of Range"));
return;
}
const u8* ptr;
const u8* end;
if (next)
{
end = &ram_ptr[ram_size - search_for.size() + 1];
ptr = std::search(&ram_ptr[addr], end, search_for.begin(), search_for.end());
}
else
{
end = &ram_ptr[addr + search_for.size() - 1];
ptr = std::find_end(ram_ptr, end, search_for.begin(), search_for.end());
}
if (ptr != end)
{
m_result_label->setText(tr("Match Found"));
u32 offset = static_cast<u32>(ptr - ram_ptr) + base_address;
m_search_address->setText(QStringLiteral("%1").arg(offset, 8, 16, QLatin1Char('0')));
m_memory_view->SetAddress(offset);
return;
}
m_result_label->setText(tr("No Match"));
}
void MemoryWidget::OnFindNextValue()
{
FindValue(true);
}
void MemoryWidget::OnFindPreviousValue()
{
FindValue(false);
}