mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-07-02 21:11:25 +12:00
1240 lines
35 KiB
C++
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());
|
|
});
|
|
}
|