#include "elf_memory_dumping_dialog.h" #include "Emu/Cell/SPUThread.h" #include "qt_utils.h" #include #include #include #include #include #include #include #include LOG_CHANNEL(gui_log, "GUI"); Q_DECLARE_METATYPE(spu_memory_segment_dump_data); elf_memory_dumping_dialog::elf_memory_dumping_dialog(u32 ppu_debugger_addr, std::shared_ptr _gui_settings, QWidget* parent) : QDialog(parent), m_gui_settings(std::move(_gui_settings)) { setWindowTitle(tr("SPU ELF Dumper")); setAttribute(Qt::WA_DeleteOnClose); m_seg_list = new QListWidget(); // Font const int pSize = 10; QFont mono = QFontDatabase::systemFont(QFontDatabase::FixedFont); mono.setPointSize(pSize); m_seg_list->setMinimumWidth(gui::utils::get_label_width(tr("PPU Address: 0x00000000, LS Address: 0x00000, Segment Size: 0x00000, Flags: 0x0"))); // Address expression input auto make_hex_edit = [this, mono](u32 max_digits) { QLineEdit* le = new QLineEdit(); le->setFont(mono); le->setMaxLength(max_digits + 2); le->setPlaceholderText("0x" + QStringLiteral("0").repeated(max_digits)); le->setValidator(new QRegularExpressionValidator(QRegularExpression(QStringLiteral("^(0[xX])?0*[a-fA-F0-9]{0,%1}$").arg(max_digits)), this)); return le; }; m_segment_size_input = make_hex_edit(5); m_ppu_address_input = make_hex_edit(8); m_ls_address_input = make_hex_edit(5); m_segment_flags_input = make_hex_edit(1); m_segment_flags_input->setText("0x7"); // READ WRITE EXEC m_ppu_address_input->setText(QStringLiteral("0x%1").arg(ppu_debugger_addr & -0x10000, 1, 16)); // SPU code segments are usually 128 bytes aligned, let's make it even 64k so the user would have to type himself the lower part to avoid human errors. QPushButton* add_segment_button = new QPushButton(QStringLiteral("+")); add_segment_button->setToolTip(tr("Add new segment")); add_segment_button->setFixedWidth(add_segment_button->sizeHint().height()); // Make button square connect(add_segment_button, &QAbstractButton::clicked, this, &elf_memory_dumping_dialog::add_new_segment); QPushButton* remove_segment_button = new QPushButton(QStringLiteral("-")); remove_segment_button->setToolTip(tr("Remove segment")); remove_segment_button->setFixedWidth(remove_segment_button->sizeHint().height()); // Make button square remove_segment_button->setEnabled(false); connect(remove_segment_button, &QAbstractButton::clicked, this, &elf_memory_dumping_dialog::remove_segment); QPushButton* save_to_file = new QPushButton(tr("Save To ELF")); save_to_file->setToolTip(tr("Save To An ELF file")); connect(save_to_file, &QAbstractButton::clicked, this, &elf_memory_dumping_dialog::save_to_file); QHBoxLayout* hbox_input = new QHBoxLayout; hbox_input->addWidget(new QLabel(tr("Segment Size:"))); hbox_input->addWidget(m_segment_size_input); hbox_input->addSpacing(5); hbox_input->addWidget(new QLabel(tr("PPU Address:"))); hbox_input->addWidget(m_ppu_address_input); hbox_input->addSpacing(5); hbox_input->addWidget(new QLabel(tr("LS Address:"))); hbox_input->addWidget(m_ls_address_input); hbox_input->addSpacing(5); hbox_input->addWidget(new QLabel(tr("Flags:"))); hbox_input->addWidget(m_segment_flags_input); QHBoxLayout* hbox_save_and_edit = new QHBoxLayout; hbox_save_and_edit->addStretch(2); hbox_save_and_edit->addWidget(add_segment_button); hbox_save_and_edit->addSpacing(4); hbox_save_and_edit->addWidget(remove_segment_button); hbox_save_and_edit->addSpacing(4); hbox_save_and_edit->addWidget(save_to_file); QVBoxLayout* vbox = new QVBoxLayout(); vbox->addLayout(hbox_input); vbox->addSpacing(5); vbox->addWidget(m_seg_list); vbox->addSpacing(5); vbox->addLayout(hbox_save_and_edit); setLayout(vbox); connect(m_seg_list, &QListWidget::currentRowChanged, this, [this, remove_segment_button](int row) { remove_segment_button->setEnabled(row >= 0 && m_seg_list->item(row)); }); show(); } void elf_memory_dumping_dialog::add_new_segment() { QStringList errors; auto interpret = [&](QString text, QString error_field) -> u32 { bool ok = false; // Parse expression (or at least used to, was nuked to remove the need for QtJsEngine) const QString fixed_expression = QRegularExpression(QRegularExpression::anchoredPattern("a .*|^[A-Fa-f0-9]+$")).match(text).hasMatch() ? "0x" + text : text; const u32 res = static_cast(fixed_expression.toULong(&ok, 16)); if (!ok) { errors << error_field; return umax; } return res; }; spu_memory_segment_dump_data data{}; data.segment_size = interpret(m_segment_size_input->text(), tr("Segment Size")); data.src_addr = vm::get_super_ptr(interpret(m_ppu_address_input->text(), tr("PPU Address"))); data.ls_addr = interpret(m_ls_address_input->text(), tr("LS Address")); data.flags = interpret(m_segment_flags_input->text(), tr("Segment Flags")); if (!errors.isEmpty()) { QMessageBox::warning(this, tr("Failed To Add Segment"), tr("Segment parameters are incorrect:\n%1").arg(errors.join('\n'))); return; } if (data.segment_size % 4) { QMessageBox::warning(this, tr("Failed To Add Segment"), tr("SPU segment size must be 4 bytes aligned.")); return; } if (data.segment_size + data.ls_addr > SPU_LS_SIZE || data.segment_size == 0 || data.segment_size % 4) { QMessageBox::warning(this, tr("Failed To Add Segment"), tr("SPU segment range is invalid.")); return; } if (!vm::check_addr(vm::try_get_addr(data.src_addr).first, vm::page_readable, data.segment_size)) { QMessageBox::warning(this, tr("Failed To Add Segment"), tr("PPU address range is not accessible.")); return; } for (int i = 0; i < m_seg_list->count(); ++i) { ensure(m_seg_list->item(i)->data(Qt::UserRole).canConvert()); const auto seg_stored = m_seg_list->item(i)->data(Qt::UserRole).value(); const auto stored_max = seg_stored.src_addr + seg_stored.segment_size; const auto data_max = data.src_addr + data.segment_size; if (seg_stored.src_addr < data_max && data.src_addr < stored_max) { QMessageBox::warning(this, tr("Failed To Add Segment"), tr("SPU segment overlaps with previous SPU segment(s)\n")); return; } } auto item = new QListWidgetItem(tr("PPU Address: 0x%0, LS Address: 0x%1, Segment Size: 0x%2, Flags: 0x%3").arg(+vm::try_get_addr(data.src_addr).first, 5, 16).arg(data.ls_addr, 2, 16).arg(data.segment_size, 2, 16).arg(data.flags, 2, 16), m_seg_list); item->setData(Qt::UserRole, QVariant::fromValue(data)); m_seg_list->setCurrentItem(item); } void elf_memory_dumping_dialog::remove_segment() { const int row = m_seg_list->currentRow(); if (row >= 0) { QListWidgetItem* item = m_seg_list->takeItem(row); delete item; } } void elf_memory_dumping_dialog::save_to_file() { std::vector segs; segs.reserve(m_seg_list->count()); for (int i = 0; i < m_seg_list->count(); ++i) { ensure(m_seg_list->item(i)->data(Qt::UserRole).canConvert()); const auto seg_stored = m_seg_list->item(i)->data(Qt::UserRole).value(); segs.emplace_back(seg_stored); } if (segs.empty()) { return; } const QString path_last_elf = m_gui_settings->GetValue(gui::fd_save_elf).toString(); const QString qpath = QFileDialog::getSaveFileName(this, tr("Capture"), path_last_elf, "SPU ELF (*.elf)"); const std::string path = qpath.toStdString(); if (!path.empty()) { const auto result = spu_thread::capture_memory_as_elf({segs.data(), segs.size()}).save(); if (!result.empty() && fs::write_file(path, fs::rewrite, result)) { gui_log.success("Saved ELF at %s", path); m_gui_settings->SetValue(gui::fd_save_elf, qpath); } else { QMessageBox::warning(this, tr("Save Failure"), tr("Failed to save SPU ELF.")); } } }