mirror of
https://github.com/RPCSX/rpcsx.git
synced 2026-01-07 01:00:06 +01:00
* Added image scaling with Ctrl+Plus, Ctrl+Minus. * Added hover position to see which address is being pointed at, when double clicking on it the image viewer is closed and the memory viewer jumps to the clicked memory data.
1238 lines
35 KiB
C++
1238 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}$")));
|
|
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));
|
|
|
|
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.");
|
|
|
|
if (m_size != 0x40000/*SPU_LS_SIZE*/)
|
|
{
|
|
m_cbox_input_mode->addItem("SPU Instruction", QVariant::fromValue(+as_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 QPoint xy = static_cast<QHoverEvent*>(event)->pos() / 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 QPoint xy = static_cast<QMouseEvent*>(event)->pos() / 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());
|
|
});
|
|
}
|