rpcs3/rpcs3/rpcs3qt/memory_viewer_panel.cpp
2023-07-31 20:03:24 +03:00

1240 lines
35 KiB
C++

#include "Utilities/mutex.h"
#include "Emu/Memory/vm_locking.h"
#include "Emu/Memory/vm.h"
#include "memory_viewer_panel.h"
#include "Emu/Cell/SPUThread.h"
#include "Emu/CPU/CPUDisAsm.h"
#include "Emu/Cell/SPUDisAsm.h"
#include "Emu/RSX/RSXThread.h"
#include "Emu/RSX/rsx_utils.h"
#include "Emu/IdManager.h"
#include <QVBoxLayout>
#include <QPushButton>
#include <QSpinBox>
#include <QGroupBox>
#include <QTextEdit>
#include <QComboBox>
#include <QCheckBox>
#include <QWheelEvent>
#include <QHoverEvent>
#include <QMouseEvent>
#include <QTimer>
#include <QThread>
#include <QKeyEvent>
#include "util/logs.hpp"
#include "util/asm.hpp"
#include "util/vm.hpp"
LOG_CHANNEL(gui_log, "GUI");
constexpr auto qstr = QString::fromStdString;
memory_viewer_panel::memory_viewer_panel(QWidget* parent, std::shared_ptr<CPUDisAsm> disasm, u32 addr, std::function<cpu_thread*()> func)
: QDialog(parent)
, m_addr(addr)
, m_get_cpu(std::move(func))
, m_type([&]()
{
const auto cpu = m_get_cpu();
if (!cpu) return thread_type::none;
if (cpu->id_type() == 1) return thread_type::ppu;
if (cpu->id_type() == 0x55) return thread_type::rsx;
if (cpu->id_type() == 2) return thread_type::spu;
fmt::throw_exception("Unknown CPU type (0x%x)", cpu->id_type());
}())
, m_rsx(m_type == thread_type::rsx ? static_cast<rsx::thread*>(m_get_cpu()) : nullptr)
, m_spu_shm([&]()
{
const auto cpu = m_get_cpu();
return cpu && m_type == thread_type::spu ? static_cast<spu_thread*>(cpu)->shm : nullptr;
}())
, m_addr_mask(m_type == thread_type::spu ? SPU_LS_SIZE - 1 : ~0)
, m_disasm(std::move(disasm))
{
const auto cpu = m_get_cpu();
setWindowTitle(
cpu && m_type == thread_type::spu ? tr("Memory Viewer Of %0").arg(qstr(cpu->get_name())) :
cpu && m_type == thread_type::rsx ? tr("Memory Viewer Of RSX[0x55555555]") :
tr("Memory Viewer"));
setObjectName("memory_viewer");
m_colcount = 4;
m_rowcount = 1;
const int pSize = 10;
// Font
QFont mono = QFontDatabase::systemFont(QFontDatabase::FixedFont);
mono.setPointSize(pSize);
m_fontMetrics = new QFontMetrics(mono);
// Layout:
QVBoxLayout* vbox_panel = new QVBoxLayout(this);
// Tools
QHBoxLayout* hbox_tools = new QHBoxLayout(this);
// Tools: Memory Viewer Options
QGroupBox* tools_mem = new QGroupBox(tr("Memory Viewer Options"), this);
QHBoxLayout* hbox_tools_mem = new QHBoxLayout(this);
// Tools: Memory Viewer Options: Address
QGroupBox* tools_mem_addr = new QGroupBox(tr("Address"), this);
QHBoxLayout* hbox_tools_mem_addr = new QHBoxLayout(this);
m_addr_line = new QLineEdit(this);
m_addr_line->setPlaceholderText("00000000");
m_addr_line->setFont(mono);
m_addr_line->setMaxLength(18);
m_addr_line->setFixedWidth(75);
m_addr_line->setFocus();
m_addr_line->setValidator(new QRegularExpressionValidator(QRegularExpression(m_type == thread_type::spu ? "^(0[xX])?0*[a-fA-F0-9]{0,5}$" : "^(0[xX])?0*[a-fA-F0-9]{0,8}$"), this));
hbox_tools_mem_addr->addWidget(m_addr_line);
tools_mem_addr->setLayout(hbox_tools_mem_addr);
// Tools: Memory Viewer Options: Words
QGroupBox* tools_mem_words = new QGroupBox(tr("Words"), this);
QHBoxLayout* hbox_tools_mem_words = new QHBoxLayout();
class words_spin_box : public QSpinBox
{
public:
words_spin_box(QWidget* parent = nullptr) : QSpinBox(parent) {}
~words_spin_box() override {}
private:
int valueFromText(const QString &text) const override
{
return std::countr_zero(text.toULong());
}
QString textFromValue(int value) const override
{
return tr("%0").arg(1 << value);
}
};
words_spin_box* sb_words = new words_spin_box(this);
sb_words->setRange(0, 2);
sb_words->setValue(2);
hbox_tools_mem_words->addWidget(sb_words);
tools_mem_words->setLayout(hbox_tools_mem_words);
// Tools: Memory Viewer Options: Control
QGroupBox* tools_mem_buttons = new QGroupBox(tr("Control"));
QHBoxLayout* hbox_tools_mem_buttons = new QHBoxLayout(this);
QPushButton* b_fprev = new QPushButton("<<", this);
QPushButton* b_prev = new QPushButton("<", this);
QPushButton* b_next = new QPushButton(">", this);
QPushButton* b_fnext = new QPushButton(">>", this);
b_fprev->setFixedWidth(20);
b_prev->setFixedWidth(20);
b_next->setFixedWidth(20);
b_fnext->setFixedWidth(20);
b_fprev->setAutoDefault(false);
b_prev->setAutoDefault(false);
b_next->setAutoDefault(false);
b_fnext->setAutoDefault(false);
hbox_tools_mem_buttons->addWidget(b_fprev);
hbox_tools_mem_buttons->addWidget(b_prev);
hbox_tools_mem_buttons->addWidget(b_next);
hbox_tools_mem_buttons->addWidget(b_fnext);
tools_mem_buttons->setLayout(hbox_tools_mem_buttons);
QGroupBox* tools_mem_refresh = new QGroupBox(tr("Refresh"));
QHBoxLayout* hbox_tools_mem_refresh = new QHBoxLayout(this);
QPushButton* button_auto_refresh = new QPushButton(QStringLiteral(" "), this);
button_auto_refresh->setFixedWidth(20);
button_auto_refresh->setAutoDefault(false);
hbox_tools_mem_refresh->addWidget(button_auto_refresh);
tools_mem_refresh->setLayout(hbox_tools_mem_refresh);
// Merge Tools: Memory Viewer
hbox_tools_mem->addWidget(tools_mem_addr);
hbox_tools_mem->addWidget(tools_mem_words);
hbox_tools_mem->addWidget(tools_mem_buttons);
hbox_tools_mem->addWidget(tools_mem_refresh);
tools_mem->setLayout(hbox_tools_mem);
// Tools: Raw Image Preview Options
QGroupBox* tools_img = new QGroupBox(tr("Raw Image Preview Options"), this);
QHBoxLayout* hbox_tools_img = new QHBoxLayout(this);
// Tools: Raw Image Preview Options : Size
QGroupBox* tools_img_size = new QGroupBox(tr("Size"), this);
QHBoxLayout* hbox_tools_img_size = new QHBoxLayout(this);
QLabel* l_x = new QLabel(" x ");
QSpinBox* sb_img_size_x = new QSpinBox(this);
QSpinBox* sb_img_size_y = new QSpinBox(this);
sb_img_size_x->setRange(1, m_type == thread_type::spu ? 256 : 4096);
sb_img_size_y->setRange(1, m_type == thread_type::spu ? 256 : 4096);
sb_img_size_x->setValue(256);
sb_img_size_y->setValue(256);
hbox_tools_img_size->addWidget(sb_img_size_x);
hbox_tools_img_size->addWidget(l_x);
hbox_tools_img_size->addWidget(sb_img_size_y);
tools_img_size->setLayout(hbox_tools_img_size);
// Tools: Raw Image Preview Options: Mode
QGroupBox* tools_img_mode = new QGroupBox(tr("Mode"), this);
QHBoxLayout* hbox_tools_img_mode = new QHBoxLayout(this);
QComboBox* cbox_img_mode = new QComboBox(this);
cbox_img_mode->addItem("RGB", QVariant::fromValue(color_format::RGB));
cbox_img_mode->addItem("ARGB", QVariant::fromValue(color_format::ARGB));
cbox_img_mode->addItem("RGBA", QVariant::fromValue(color_format::RGBA));
cbox_img_mode->addItem("ABGR", QVariant::fromValue(color_format::ABGR));
cbox_img_mode->addItem("G8", QVariant::fromValue(color_format::G8));
cbox_img_mode->addItem("G32MAX", QVariant::fromValue(color_format::G32MAX));
cbox_img_mode->setCurrentIndex(1); //ARGB
hbox_tools_img_mode->addWidget(cbox_img_mode);
tools_img_mode->setLayout(hbox_tools_img_mode);
// Merge Tools: Raw Image Preview Options
hbox_tools_img->addWidget(tools_img_size);
hbox_tools_img->addWidget(tools_img_mode);
tools_img->setLayout(hbox_tools_img);
// Tools: Tool Buttons
QGroupBox* tools_buttons = new QGroupBox(tr("Tools"), this);
QVBoxLayout* hbox_tools_buttons = new QVBoxLayout(this);
QPushButton* b_img = new QPushButton(tr("View\nimage"), this);
b_img->setAutoDefault(false);
hbox_tools_buttons->addWidget(b_img);
tools_buttons->setLayout(hbox_tools_buttons);
// Merge Tools = Memory Viewer Options + Raw Image Preview Options + Tool Buttons
hbox_tools->addSpacing(20);
hbox_tools->addWidget(tools_mem);
hbox_tools->addWidget(tools_img);
hbox_tools->addWidget(tools_buttons);
hbox_tools->addSpacing(20);
// Memory Panel:
m_hbox_mem_panel = new QHBoxLayout(this);
// Memory Panel: Address Panel
m_mem_addr = new QLabel("");
QSizePolicy sp_retain = m_mem_addr->sizePolicy();
sp_retain.setRetainSizeWhenHidden(false);
m_mem_addr->setSizePolicy(sp_retain);
m_mem_addr->setObjectName("memory_viewer_address_panel");
m_mem_addr->setFont(mono);
m_mem_addr->setAutoFillBackground(true);
m_mem_addr->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard);
m_mem_addr->ensurePolished();
// Memory Panel: Hex Panel
m_mem_hex = new QLabel("");
m_mem_hex->setSizePolicy(sp_retain);
m_mem_hex->setObjectName("memory_viewer_hex_panel");
m_mem_hex->setFont(mono);
m_mem_hex->setAutoFillBackground(true);
m_mem_hex->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard);
m_mem_hex->ensurePolished();
// Memory Panel: ASCII Panel
m_mem_ascii = new QLabel("");
m_mem_ascii->setSizePolicy(sp_retain);
m_mem_ascii->setObjectName("memory_viewer_ascii_panel");
m_mem_ascii->setFont(mono);
m_mem_ascii->setAutoFillBackground(true);
m_mem_ascii->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard);
m_mem_ascii->ensurePolished();
// Merge Memory Panel:
m_hbox_mem_panel->setAlignment(Qt::AlignTop | Qt::AlignHCenter);
m_hbox_mem_panel->addSpacing(20);
m_hbox_mem_panel->addWidget(m_mem_addr);
m_hbox_mem_panel->addSpacing(10);
m_hbox_mem_panel->addWidget(m_mem_hex);
m_hbox_mem_panel->addSpacing(10);
m_hbox_mem_panel->addWidget(m_mem_ascii);
m_hbox_mem_panel->addSpacing(20);
QHBoxLayout* hbox_memory_search = new QHBoxLayout(this);
// Set Margins to adjust WindowSize
vbox_panel->setContentsMargins(0, 0, 0, 0);
hbox_tools->setContentsMargins(0, 0, 0, 0);
tools_mem_addr->setContentsMargins(0, 5, 0, 0);
tools_mem_words->setContentsMargins(0, 5, 0, 0);
tools_mem_buttons->setContentsMargins(0, 5, 0, 0);
tools_img_mode->setContentsMargins(0, 5, 0, 0);
tools_img_size->setContentsMargins(0, 5, 0, 0);
tools_mem->setContentsMargins(0, 5, 0, 0);
tools_img->setContentsMargins(0, 5, 0, 0);
tools_buttons->setContentsMargins(0, 5, 0, 0);
m_hbox_mem_panel->setContentsMargins(0, 0, 0, 0);
hbox_memory_search->setContentsMargins(0, 0, 0, 0);
if (m_disasm)
{
// Extract memory view from the disassembler
std::tie(m_ptr, m_size) = m_disasm->get_memory_span();
}
QGroupBox* group_search = new QGroupBox(tr("Memory Search"), this);
QPushButton* button_collapse_viewer = new QPushButton(reinterpret_cast<const char*>(u8"Ʌ"), group_search);
button_collapse_viewer->setFixedWidth(QLabel(button_collapse_viewer->text()).sizeHint().width() * 3);
m_search_line = new QLineEdit(group_search);
m_search_line->setFixedWidth(QLabel(QString("This is the very length of the lineedit due to hidpi reasons.").chopped(4)).sizeHint().width());
m_search_line->setPlaceholderText(tr("Search..."));
m_search_line->setMaxLength(4096);
QPushButton* button_search = new QPushButton(tr("Search"), group_search);
button_search->setEnabled(false);
m_chkbox_case_insensitive = new QCheckBox(tr("Case Insensitive"), group_search);
m_chkbox_case_insensitive->setCheckable(true);
m_chkbox_case_insensitive->setToolTip(tr("When using string mode, the characters' case will not matter both in string and in memory."
"\nWarning: this may reduce performance of the search."));
m_cbox_input_mode = new QComboBox(group_search);
m_cbox_input_mode->addItem(tr("Select search mode(s).."), QVariant::fromValue(+no_mode));
m_cbox_input_mode->addItem(tr("Deselect All Modes"), QVariant::fromValue(+clear_modes));
m_cbox_input_mode->addItem(tr("String"), QVariant::fromValue(+as_string));
m_cbox_input_mode->addItem(tr("HEX bytes/integer"), QVariant::fromValue(+as_hex));
m_cbox_input_mode->addItem(tr("Double"), QVariant::fromValue(+as_f64));
m_cbox_input_mode->addItem(tr("Float"), QVariant::fromValue(+as_f32));
m_cbox_input_mode->addItem(tr("Instruction"), QVariant::fromValue(+as_inst));
m_cbox_input_mode->addItem(tr("RegEx Instruction"), QVariant::fromValue(+as_regex_inst));
QString tooltip = tr("String: search the memory for the specified string."
"\nHEX bytes/integer: search the memory for hexadecimal values. Spaces, commas, \"0x\", \"0X\", \"\\x\", \"h\", \"H\" ensure separation of bytes but they are not mandatory."
"\nDouble: reinterpret the string as 64-bit precision floating point value. Values are searched for exact representation, meaning -0 != 0."
"\nFloat: reinterpret the string as 32-bit precision floating point value. Values are searched for exact representation, meaning -0 != 0."
"\nInstruction: search an instruction contains the text of the string."
"\nRegEx: search an instruction containing text that matches the regular expression input.");
if (m_size != 0x40000/*SPU_LS_SIZE*/)
{
m_cbox_input_mode->addItem("SPU Instruction", QVariant::fromValue(+as_fake_spu_inst));
m_cbox_input_mode->addItem(tr("SPU RegEx-Instruction"), QVariant::fromValue(+as_regex_fake_spu_inst));
tooltip.append(tr("\nSPU Instruction: Search an SPU instruction contains the text of the string. For searching instructions within embedded SPU images.\nTip: SPU floats are commented along forming instructions."));
}
connect(m_cbox_input_mode, QOverload<int>::of(&QComboBox::currentIndexChanged), group_search, [this, button_search](int index)
{
if (index < 1 || m_rsx)
{
return;
}
if ((1u << index) == clear_modes)
{
m_modes = {};
}
else
{
m_modes = search_mode{m_modes | (1 << index)};
}
const s32 count = std::popcount(+m_modes);
if (count == 0)
{
button_search->setEnabled(false);
m_cbox_input_mode->setItemText(0, tr("Select search mode(s).."));
}
else
{
button_search->setEnabled(true);
m_cbox_input_mode->setItemText(0, tr("%0 mode(s) selected").arg(count));
}
for (u32 i = search_mode_last / 2; i > clear_modes; i /= 2)
{
if (i & m_modes && count > 1)
{
m_cbox_input_mode->setItemText(std::countr_zero<u32>(i), qstr(fmt::format("* %s", search_mode{i})));
}
else
{
m_cbox_input_mode->setItemText(std::countr_zero<u32>(i), qstr(fmt::format("%s", search_mode{i})));
}
}
if (count != 1)
{
m_cbox_input_mode->setCurrentIndex(0);
}
});
m_cbox_input_mode->setToolTip(tooltip);
QVBoxLayout* vbox_search_layout = new QVBoxLayout(group_search);
QHBoxLayout* hbox_search_panel = new QHBoxLayout(group_search);
QHBoxLayout* hbox_search_modes = new QHBoxLayout(group_search);
hbox_search_panel->addWidget(button_collapse_viewer);
hbox_search_panel->addWidget(m_search_line);
hbox_search_panel->addWidget(m_cbox_input_mode);
hbox_search_panel->addWidget(m_chkbox_case_insensitive);
hbox_search_panel->addWidget(button_search);
vbox_search_layout->addLayout(hbox_search_panel);
vbox_search_layout->addLayout(hbox_search_modes);
group_search->setLayout(vbox_search_layout);
hbox_memory_search->setAlignment(Qt::AlignHCenter | Qt::AlignBottom);
hbox_memory_search->addSpacing(20);
hbox_memory_search->addWidget(group_search);
hbox_memory_search->addSpacing(20);
// Merge and display everything
vbox_panel->addSpacing(10);
auto get_row = [row = 0]() mutable
{
return row++;
};
vbox_panel->addLayout(hbox_tools, get_row());
vbox_panel->addSpacing(5);
vbox_panel->addLayout(m_hbox_mem_panel, get_row());
// TODO: RSX memory searcher
if (!m_rsx)
{
vbox_panel->addLayout(hbox_memory_search, get_row());
vbox_panel->addSpacing(15);
}
else
{
group_search->deleteLater();
}
vbox_panel->setSizeConstraint(QLayout::SetNoConstraint);
setLayout(vbox_panel);
// Events
connect(m_addr_line, &QLineEdit::returnPressed, [this]()
{
bool ok = false;
const QString text = m_addr_line->text();
const u32 addr = (text.startsWith("0x", Qt::CaseInsensitive) ? text.right(text.size() - 2) : text).toULong(&ok, 16);
if (ok) m_addr = addr;
scroll(0); // Refresh
});
connect(sb_words, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [=, this]()
{
m_colcount = 1 << sb_words->value();
ShowMemory();
});
connect(b_prev, &QAbstractButton::clicked, [this]() { scroll(-1); });
connect(b_next, &QAbstractButton::clicked, [this]() { scroll(1); });
connect(b_fprev, &QAbstractButton::clicked, [this]() { scroll(m_rowcount * -1); });
connect(b_fnext, &QAbstractButton::clicked, [this]() { scroll(m_rowcount); });
connect(b_img, &QAbstractButton::clicked, [=, this]()
{
const color_format format = cbox_img_mode->currentData().value<color_format>();
const int sizex = sb_img_size_x->value();
const int sizey = sb_img_size_y->value();
ShowImage(this, m_addr, format, sizex, sizey, false);
});
QTimer* auto_refresh_timer = new QTimer(this);
connect(auto_refresh_timer, &QTimer::timeout, this, [this]()
{
ShowMemory();
});
connect(button_auto_refresh, &QAbstractButton::clicked, this, [=, this]()
{
const bool is_checked = button_auto_refresh->text() != " ";
if (auto_refresh_timer->isActive() != is_checked)
{
return;
}
if (is_checked)
{
button_auto_refresh->setText(QStringLiteral(" "));
auto_refresh_timer->stop();
}
else
{
button_auto_refresh->setText(reinterpret_cast<const char*>(u8""));
ShowMemory();
auto_refresh_timer->start(16);
}
});
if (!m_rsx)
{
connect(button_search, &QAbstractButton::clicked, this, [this]()
{
if (m_search_thread && m_search_thread->isRunning())
{
// Prevent spamming (search is costly on performance)
return;
}
if (m_search_thread)
{
m_search_thread->deleteLater();
m_search_thread = nullptr;
}
std::string wstr = m_search_line->text().toStdString();
if (wstr.empty() || wstr.size() >= 4096u)
{
gui_log.error("String is empty or too long (size=%u)", wstr.size());
return;
}
m_search_thread = QThread::create([this, wstr, m_modes = m_modes]()
{
gui_log.notice("Searching for %s (mode: %s)", wstr, m_modes);
u64 found = 0;
for (int modes = m_modes; modes; modes &= modes - 1)
{
found += OnSearch(wstr, modes & ~(modes - 1));
}
gui_log.success("Search completed (found %u matches)", +found);
});
m_search_thread->start();
});
connect(button_collapse_viewer, &QAbstractButton::clicked, this, [this, button_collapse_viewer, m_previous_row_count = -1]() mutable
{
const bool is_collapsing = button_collapse_viewer->text() == reinterpret_cast<const char*>(u8"Ʌ");
button_collapse_viewer->setText(is_collapsing ? "V" : reinterpret_cast<const char*>(u8"Ʌ"));
if (is_collapsing)
{
m_previous_row_count = std::exchange(m_rowcount, 0);
setMinimumHeight(0);
}
else
{
m_rowcount = std::exchange(m_previous_row_count, 0);
setMaximumHeight(16777215); // Default Qt value
}
ShowMemory();
QTimer::singleShot(0, this, [this, button_collapse_viewer]()
{
const bool is_collapsing = button_collapse_viewer->text() != reinterpret_cast<const char*>(u8"Ʌ");
// singleShot to evaluate properly after the event
const int height_hint = sizeHint().height();
resize(size().width(), height_hint);
if (is_collapsing)
{
setMinimumHeight(height_hint);
setMaximumHeight(height_hint + 1);
}
else
{
setMinimumHeight(m_min_height);
}
});
});
}
// Set the minimum height of one row
m_rowcount = 1;
ShowMemory();
m_min_height = sizeHint().height();
setMinimumHeight(m_min_height);
m_rowcount = 16;
ShowMemory();
setFixedWidth(sizeHint().width());
// Fill the QTextEdits
scroll(0);
// Show by default
show();
// Expected to be created by IDM, emulation stop will close it
const u32 id = idm::last_id();
auto handle_ptr = idm::get_unlocked<memory_viewer_handle>(id);
connect(this, &memory_viewer_panel::finished, [handle_ptr = std::move(handle_ptr), id, this](int)
{
if (m_search_thread)
{
m_search_thread->wait();
m_search_thread->deleteLater();
m_search_thread = nullptr;
}
idm::remove_verify<memory_viewer_handle>(id, handle_ptr);
});
}
memory_viewer_panel::~memory_viewer_panel()
{
}
void memory_viewer_panel::wheelEvent(QWheelEvent *event)
{
// Set some scrollspeed modifiers:
u32 step_size = 1;
if (event->modifiers().testFlag(Qt::ControlModifier))
step_size *= m_rowcount;
const QPoint num_steps = event->angleDelta() / 8 / 15; // http://doc.qt.io/qt-5/qwheelevent.html#pixelDelta
scroll(step_size * (0 - num_steps.y()));
}
void memory_viewer_panel::scroll(s32 steps)
{
m_addr += m_colcount * 4 * steps; // Add steps
m_addr &= m_addr_mask; // Mask it
m_addr -= m_addr % (m_colcount * 4); // Align by amount of bytes in a row
m_addr_line->setText(qstr(fmt::format("%08x", m_addr)));
ShowMemory();
}
void memory_viewer_panel::resizeEvent(QResizeEvent *event)
{
QDialog::resizeEvent(event);
const int font_height = m_fontMetrics->height();
const QMargins margins = layout()->contentsMargins();
int free_height = event->size().height()
- (layout()->count() * (margins.top() + margins.bottom())) - c_pad_memory_labels;
for (int i = 0; i < layout()->count(); i++)
{
const auto it = layout()->itemAt(i);
if (it != m_hbox_mem_panel) // Do not take our memory layout into account
free_height -= it->sizeHint().height();
}
const u32 new_row_count = std::max(0, free_height) / font_height;
if (m_rowcount != new_row_count)
{
m_rowcount = new_row_count;
QTimer::singleShot(0, [this]()
{
// Prevent recursion of events
ShowMemory();
});
}
}
std::string memory_viewer_panel::getHeaderAtAddr(u32 addr) const
{
if (m_type == thread_type::spu) return {};
// Check if its an SPU Local Storage beginning
const u32 spu_boundary = utils::align<u32>(addr, SPU_LS_SIZE);
if (spu_boundary <= addr + m_colcount * 4 - 1)
{
std::shared_ptr<named_thread<spu_thread>> spu;
if (const u32 raw_spu_index = (spu_boundary - RAW_SPU_BASE_ADDR) / SPU_LS_SIZE; raw_spu_index < 5)
{
spu = idm::get<named_thread<spu_thread>>(spu_thread::find_raw_spu(raw_spu_index));
if (spu && spu->get_type() == spu_type::threaded)
{
spu.reset();
}
}
else if (const u32 spu_index = (spu_boundary - SPU_FAKE_BASE_ADDR) / SPU_LS_SIZE; spu_index < spu_thread::id_count)
{
spu = idm::get<named_thread<spu_thread>>(spu_thread::id_base | spu_index);
if (spu && spu->get_type() != spu_type::threaded)
{
spu.reset();
}
}
if (spu)
{
return spu->get_name();
}
}
return {};
}
void* memory_viewer_panel::to_ptr(u32 addr, u32 size) const
{
if (m_type >= thread_type::spu && !m_get_cpu())
{
return nullptr;
}
if (!size)
{
return nullptr;
}
switch (m_type)
{
case thread_type::none:
case thread_type::ppu:
{
if (vm::check_addr(addr, 0, size))
{
return vm::get_super_ptr(addr);
}
break;
}
case thread_type::spu:
{
if (size <= SPU_LS_SIZE && SPU_LS_SIZE - size >= (addr % SPU_LS_SIZE))
{
return m_spu_shm->map_self() + (addr % SPU_LS_SIZE);
}
break;
}
case thread_type::rsx:
{
u32 final_addr = 0;
constexpr u32 local_mem = rsx::constants::local_mem_base;
if (size > 0x2000'0000 || local_mem + 0x1000'0000 - size < addr)
{
break;
}
for (u32 i = addr; i >> 20 <= (addr + size - 1) >> 20; i += 0x100000)
{
const u32 temp = rsx::get_address(i - (i >= local_mem ? local_mem : 0), i < local_mem ? CELL_GCM_LOCATION_MAIN : CELL_GCM_LOCATION_LOCAL, true);
if (!temp)
{
// Failure
final_addr = 0;
break;
}
if (!final_addr)
{
// First time, save starting address for later checks
final_addr = temp;
}
else if (final_addr != temp - (i - addr))
{
// TODO: Non-contiguous memory
final_addr = 0;
break;
}
}
if (vm::check_addr(final_addr, 0, size))
{
return vm::get_super_ptr(final_addr);
}
break;
}
}
return nullptr;
}
void memory_viewer_panel::ShowMemory()
{
QString t_mem_addr_str;
QString t_mem_hex_str;
QString t_mem_ascii_str;
for (u32 row = 0, spu_passed = 0; row < m_rowcount; row++)
{
if (row)
{
t_mem_addr_str += "\r\n";
t_mem_hex_str += "\r\n";
t_mem_ascii_str += "\r\n";
}
{
// Check if this address contains potential header
const u32 addr = (m_addr + (row - spu_passed) * m_colcount * 4) & m_addr_mask;
const std::string header = getHeaderAtAddr(addr);
if (!header.empty())
{
// Create an SPU header at the beginning of local storage
// Like so:
// =======================================
// SPU[0x0000100] CellSpursKernel0
// =======================================
bool _break = false;
for (u32 i = 0; i < 3; i++)
{
t_mem_addr_str += qstr(fmt::format("%08x", addr));
std::string str(i == 1 ? header : "");
const u32 expected_str_size = m_colcount * 13 - 2;
// Truncate or enlarge string to a fixed size
str.resize(expected_str_size);
std::replace(str.begin(), str.end(), '\0', i == 1 ? ' ' : '=');
t_mem_hex_str += qstr(str);
spu_passed++;
row++;
if (row >= m_rowcount)
{
_break = true;
break;
}
t_mem_addr_str += "\r\n";
t_mem_hex_str += "\r\n";
t_mem_ascii_str += "\r\n";
}
if (_break)
{
break;
}
}
t_mem_addr_str += qstr(fmt::format("%08x", (m_addr + (row - spu_passed) * m_colcount * 4) & m_addr_mask));
}
for (u32 col = 0; col < m_colcount; col++)
{
if (col)
{
t_mem_hex_str += " ";
}
const u32 addr = (m_addr + (row - spu_passed) * m_colcount * 4 + col * 4) & m_addr_mask;
if (const auto ptr = this->to_ptr(addr))
{
const be_t<u32> rmem = read_from_ptr<be_t<u32>>(static_cast<const u8*>(ptr));
t_mem_hex_str += qstr(fmt::format("%02x %02x %02x %02x",
static_cast<u8>(rmem >> 24),
static_cast<u8>(rmem >> 16),
static_cast<u8>(rmem >> 8),
static_cast<u8>(rmem >> 0)));
std::string str{reinterpret_cast<const char*>(&rmem), 4};
for (auto& ch : str)
{
if (!std::isprint(static_cast<u8>(ch))) ch = '.';
}
t_mem_ascii_str += qstr(std::move(str));
}
else
{
t_mem_hex_str += "?? ?? ?? ??";
t_mem_ascii_str += "????";
}
}
}
m_mem_addr->setVisible(m_rowcount != 0);
m_mem_hex->setVisible(m_rowcount != 0);
m_mem_ascii->setVisible(m_rowcount != 0);
if (t_mem_addr_str != m_mem_addr->text())
m_mem_addr->setText(t_mem_addr_str);
if (t_mem_hex_str != m_mem_hex->text())
m_mem_hex->setText(t_mem_hex_str);
if (t_mem_ascii_str != m_mem_ascii->text())
m_mem_ascii->setText(t_mem_ascii_str);
auto mask_height = [&](int height)
{
return m_rowcount != 0 ? height + c_pad_memory_labels : 0;
};
// Adjust Text Boxes (also helps with window resize)
QSize textSize = m_fontMetrics->size(0, m_mem_addr->text());
m_mem_addr->setFixedSize(textSize.width() + 10, mask_height(textSize.height()));
textSize = m_fontMetrics->size(0, m_mem_hex->text());
m_mem_hex->setFixedSize(textSize.width() + 10, mask_height(textSize.height()));
textSize = m_fontMetrics->size(0, m_mem_ascii->text());
m_mem_ascii->setFixedSize(textSize.width() + 10, mask_height(textSize.height()));
}
void memory_viewer_panel::SetPC(const uint pc)
{
m_addr = pc;
}
void memory_viewer_panel::keyPressEvent(QKeyEvent* event)
{
if (!isActiveWindow())
{
QDialog::keyPressEvent(event);
return;
}
if (event->modifiers() == Qt::ControlModifier)
{
switch (const auto key = event->key())
{
case Qt::Key_PageUp:
case Qt::Key_PageDown:
{
scroll(key == Qt::Key_PageDown ? m_rowcount : 0u - m_rowcount);
break;
}
case Qt::Key_F5:
{
if (event->isAutoRepeat())
{
break;
}
// Single refresh
ShowMemory();
break;
}
case Qt::Key_F:
{
m_addr_line->setFocus();
break;
}
default: break;
}
}
QDialog::keyPressEvent(event);
}
void memory_viewer_panel::ShowImage(QWidget* parent, u32 addr, color_format format, u32 width, u32 height, bool flipv) const
{
u32 texel_bytes = 4;
switch (format)
{
case color_format::RGB:
{
texel_bytes = 3;
break;
}
case color_format::G8:
{
texel_bytes = 1;
break;
}
default: break;
}
// If exceeds 32-bits it is invalid as well, UINT32_MAX always fails checks
const u32 memsize = utils::mul_saturate<u32>(utils::mul_saturate<u32>(texel_bytes, width), height);
if (memsize == 0)
{
return;
}
const auto originalBuffer = static_cast<u8*>(this->to_ptr(addr, memsize));
if (!originalBuffer)
{
return;
}
const auto convertedBuffer = new (std::nothrow) u8[memsize / texel_bytes * u64{4}];
if (!convertedBuffer)
{
// OOM or invalid memory address, give up
return;
}
switch (format)
{
case color_format::RGB:
{
const u32 pitch = width * 3;
const u32 pitch_new = width * 4;
for (u32 y = 0; y < height; y++)
{
const u32 offset = y * pitch;
const u32 offset_new = y * pitch_new;
for (u32 x = 0, x_new = 0; x < pitch; x += 3, x_new += 4)
{
convertedBuffer[offset_new + x_new + 0] = originalBuffer[offset + x + 2];
convertedBuffer[offset_new + x_new + 1] = originalBuffer[offset + x + 1];
convertedBuffer[offset_new + x_new + 2] = originalBuffer[offset + x + 0];
convertedBuffer[offset_new + x_new + 3] = 255;
}
}
break;
}
case color_format::ARGB:
{
const u32 pitch = width * 4;
for (u32 y = 0; y < height; y++)
{
const u32 offset = y * pitch;
for (u32 x = 0; x < pitch; x += 4)
{
convertedBuffer[offset + x + 0] = originalBuffer[offset + x + 3];
convertedBuffer[offset + x + 1] = originalBuffer[offset + x + 2];
convertedBuffer[offset + x + 2] = originalBuffer[offset + x + 1];
convertedBuffer[offset + x + 3] = originalBuffer[offset + x + 0];
}
}
break;
}
case color_format::RGBA:
{
const u32 pitch = width * 4;
for (u32 y = 0; y < height; y++)
{
const u32 offset = y * pitch;
for (u32 x = 0; x < pitch; x += 4)
{
convertedBuffer[offset + x + 0] = originalBuffer[offset + x + 2];
convertedBuffer[offset + x + 1] = originalBuffer[offset + x + 1];
convertedBuffer[offset + x + 2] = originalBuffer[offset + x + 0];
convertedBuffer[offset + x + 3] = originalBuffer[offset + x + 3];
}
}
break;
}
case color_format::ABGR:
{
const u32 pitch = width * 4;
for (u32 y = 0; y < height; y++)
{
const u32 offset = y * pitch;
for (u32 x = 0; x < pitch; x += 4)
{
convertedBuffer[offset + x + 0] = originalBuffer[offset + x + 1];
convertedBuffer[offset + x + 1] = originalBuffer[offset + x + 2];
convertedBuffer[offset + x + 2] = originalBuffer[offset + x + 3];
convertedBuffer[offset + x + 3] = originalBuffer[offset + x + 0];
}
}
break;
}
case color_format::G8:
{
const u32 pitch = width * 1;
const u32 pitch_new = width * 4;
for (u32 y = 0; y < height; y++)
{
const u32 offset = y * pitch;
const u32 offset_new = y * pitch_new;
for (u32 x = 0; x < pitch; x++)
{
const u8 color = originalBuffer[offset + x];
convertedBuffer[offset_new + x * 4 + 0] = color;
convertedBuffer[offset_new + x * 4 + 1] = color;
convertedBuffer[offset_new + x * 4 + 2] = color;
convertedBuffer[offset_new + x * 4 + 3] = 255;
}
}
break;
}
case color_format::G32MAX:
{
// Special: whitens as 4-byte groups tend to have a higher value, in order to perceive memory contents
// May be used to search for instructions or floats for example
const u32 pitch = width * 4;
for (u32 y = 0; y < height; y++)
{
const u32 offset = y * pitch;
for (u32 x = 0; x < pitch; x += 4)
{
const u8 color = std::max({originalBuffer[offset + x + 0], originalBuffer[offset + x + 1], originalBuffer[offset + x + 2], originalBuffer[offset + x + 3]});
convertedBuffer[offset + x + 0] = color;
convertedBuffer[offset + x + 1] = color;
convertedBuffer[offset + x + 2] = color;
convertedBuffer[offset + x + 3] = 255;
}
}
break;
}
}
// Flip vertically
if (flipv && height > 1 && memsize > 1)
{
const u32 pitch = width * 4;
for (u32 y = 0; y < height / 2; y++)
{
const u32 offset = y * pitch;
const u32 flip_offset = (height - y - 1) * pitch;
for (u32 x = 0; x < pitch; x++)
{
const u8 tmp = convertedBuffer[offset + x];
convertedBuffer[offset + x] = convertedBuffer[flip_offset + x];
convertedBuffer[flip_offset + x] = tmp;
}
}
}
std::unique_ptr<QImage> image = std::make_unique<QImage>(convertedBuffer, width, height, QImage::Format_ARGB32, [](void* buffer){ delete[] static_cast<u8*>(buffer); }, convertedBuffer);
if (image->isNull()) return;
QLabel* canvas = new QLabel();
canvas->setFixedSize(width, height);
canvas->setAttribute(Qt::WA_Hover);
canvas->setPixmap(QPixmap::fromImage(*image));
QLabel* image_title = new QLabel();
QVBoxLayout* layout = new QVBoxLayout();
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(image_title);
layout->addWidget(canvas);
struct image_viewer : public QDialog
{
QLabel* const m_canvas;
QLabel* const m_image_title;
const std::unique_ptr<QImage> m_image;
const u32 m_addr;
const int m_addr_scale = 1;
const u32 m_pitch;
const u32 m_width;
const u32 m_height;
int m_canvas_scale = 1;
image_viewer(QWidget* parent, QLabel* canvas, QLabel* image_title, std::unique_ptr<QImage> image, u32 addr, u32 addr_scale, u32 pitch, u32 width, u32 height) noexcept
: QDialog(parent)
, m_canvas(canvas)
, m_image_title(image_title)
, m_image(std::move(image))
, m_addr(addr)
, m_addr_scale(addr_scale)
, m_pitch(pitch)
, m_width(width)
, m_height(height)
{
}
bool eventFilter(QObject* object, QEvent* event) override
{
if (object == m_canvas && (event->type() == QEvent::HoverMove || event->type() == QEvent::HoverEnter || event->type() == QEvent::HoverLeave))
{
const QPointF xy = static_cast<QHoverEvent*>(event)->position() / m_canvas_scale;
set_window_name_by_coordinates(xy.x(), xy.y());
return false;
}
if (object == m_canvas && event->type() == QEvent::MouseButtonDblClick && static_cast<QMouseEvent*>(event)->button() == Qt::LeftButton)
{
QLineEdit* addr_line = static_cast<memory_viewer_panel*>(parent())->m_addr_line;
const QPointF xy = static_cast<QMouseEvent*>(event)->position() / m_canvas_scale;
addr_line->setText(qstr(fmt::format("%08x", get_pointed_addr(xy.x(), xy.y()))));
Q_EMIT addr_line->returnPressed();
close();
return false;
}
return QDialog::eventFilter(object, event);
}
u32 get_pointed_addr(u32 x, u32 y) const
{
return m_addr + m_addr_scale * (y * m_pitch + x) / m_canvas_scale;
}
void set_window_name_by_coordinates(int x, int y)
{
if (x < 0 || y < 0)
{
m_image_title->setText(qstr(fmt::format("[-, -]: NA")));
return;
}
m_image_title->setText(qstr(fmt::format("[x:%d, y:%d]: 0x%x", x, y, get_pointed_addr(x, y))));
}
void keyPressEvent(QKeyEvent* event) override
{
if (!isActiveWindow())
{
QDialog::keyPressEvent(event);
return;
}
if (event->modifiers() == Qt::ControlModifier)
{
switch (const auto key = event->key())
{
case Qt::Key_Equal: // Also plus
case Qt::Key_Plus:
case Qt::Key_Minus:
{
m_canvas_scale = std::clamp(m_canvas_scale + (key == Qt::Key_Minus ? -1 : 1), 1, 5);
const QSize fixed_size(m_width * m_canvas_scale, m_height * m_canvas_scale);
// Fast transformation makes it not blurry, does not use bilinear filtering
m_canvas->setPixmap(QPixmap::fromImage(m_image->scaled(fixed_size.width(), fixed_size.height(), Qt::KeepAspectRatio, Qt::FastTransformation)));
m_canvas->setFixedSize(fixed_size);
QTimer::singleShot(0, this, [this]()
{
// sizeHint() evaluates properly after events have been processed
setFixedSize(sizeHint());
});
break;
}
}
}
QDialog::keyPressEvent(event);
}
};
image_viewer* f_image_viewer = new image_viewer(parent, canvas, image_title, std::move(image), addr, texel_bytes, width, width, height);
canvas->installEventFilter(f_image_viewer);
f_image_viewer->setWindowTitle(qstr(fmt::format("Raw Image @ 0x%x", addr)));
f_image_viewer->setLayout(layout);
f_image_viewer->setAttribute(Qt::WA_DeleteOnClose);
f_image_viewer->show();
QTimer::singleShot(0, f_image_viewer, [f_image_viewer]()
{
// sizeHint() evaluates properly after events have been processed
f_image_viewer->setFixedSize(f_image_viewer->sizeHint());
});
}