diff --git a/rpcs3/Emu/Cell/SPUThread.cpp b/rpcs3/Emu/Cell/SPUThread.cpp index f5152dd7d0..6cdabdf64c 100644 --- a/rpcs3/Emu/Cell/SPUThread.cpp +++ b/rpcs3/Emu/Cell/SPUThread.cpp @@ -1105,7 +1105,7 @@ void spu_thread::dump_regs(std::string& ret) const fmt::append(ret, "%08x %08x %08x %08x", r.u32r[0], r.u32r[1], r.u32r[2], r.u32r[3]); } - if (i3 >= 0x80 && is_exec_code(i3)) + if (i3 >= 0x80 && is_exec_code(i3, ls)) { dis_asm.disasm(i3); fmt::append(ret, " -> %s", dis_asm.last_opcode); @@ -1197,7 +1197,7 @@ std::vector> spu_thread::dump_callstack_list() const return true; } - return !addr || !is_exec_code(addr); + return !addr || !is_exec_code(addr, ls); }; if (is_invalid(lr)) @@ -3912,7 +3912,7 @@ bool spu_thread::check_mfc_interrupts(u32 next_pc) return false; } -bool spu_thread::is_exec_code(u32 addr) const +bool spu_thread::is_exec_code(u32 addr, const u8* ls_ptr) { if (addr & ~0x3FFFC) { @@ -3922,7 +3922,7 @@ bool spu_thread::is_exec_code(u32 addr) const for (u32 i = 0; i < 30; i++) { const u32 addr0 = addr + (i * 4); - const u32 op = _ref(addr0); + const u32 op = read_from_ptr>(ls_ptr + addr0); const auto type = s_spu_itype.decode(op); if (type == spu_itype::UNK || !op) @@ -5925,20 +5925,84 @@ void spu_thread::fast_call(u32 ls_addr) gpr[1]._u32[3] = old_stack; } +spu_exec_object spu_thread::capture_memory_as_elf(std::span segs, u32 pc_hint) +{ + spu_exec_object spu_exec; + spu_exec.set_error(elf_error::ok); + + std::vector all_data(SPU_LS_SIZE); + + for (auto& seg : segs) + { + std::vector data(seg.segment_size); + + if (auto [vm_addr, ok] = vm::try_get_addr(seg.src_addr); ok) + { + if (!vm::try_access(vm_addr, data.data(), data.size(), false)) + { + spu_log.error("capture_memory_as_elf(): Failed to read {0x%x..0x%x}, aborting capture.", +vm_addr, vm_addr + seg.segment_size - 1); + spu_exec.set_error(elf_error::stream_data); + return spu_exec; + } + } + else + { + std::memcpy(data.data(), seg.src_addr, data.size()); + } + + std::memcpy(all_data.data() + seg.ls_addr, data.data(), data.size()); + + auto& prog = spu_exec.progs.emplace_back(SYS_SPU_SEGMENT_TYPE_COPY, seg.flags & 0x7, seg.ls_addr, seg.segment_size, 8, std::move(data)); + + prog.p_paddr = prog.p_vaddr; + + spu_log.success("Segment: p_type=0x%x, p_vaddr=0x%x, p_filesz=0x%x, p_memsz=0x%x", prog.p_type, prog.p_vaddr, prog.p_filesz, prog.p_memsz); + } + + + u32 pc0 = pc_hint; + + if (pc_hint != umax) + { + for (pc0 = pc_hint; pc0; pc0 -= 4) + { + const u32 op = read_from_ptr>(all_data.data(), pc0 - 4); + + // Try to find function entry (if they are placed sequentially search for BI $LR of previous function) + if (!op || op == 0x35000000u || s_spu_itype.decode(op) == spu_itype::UNK) + { + if (is_exec_code(pc0, all_data.data())) + break; + } + } + } + else + { + for (pc0 = 0; pc0 < SPU_LS_SIZE; pc0 += 4) + { + const spu_opcode_t op{read_from_ptr>(all_data.data(), pc0)}; + + // Try to find a function entry (very basic) + if (is_exec_code(pc0, all_data.data())) + break; + } + } + + spu_exec.header.e_entry = pc0; + + return spu_exec; +} + bool spu_thread::capture_state() { ensure(state & cpu_flag::wait); - spu_exec_object spu_exec; - // Save data as an executable segment, even the SPU stack // In the past, an optimization was made here to save only non-zero chunks of data // But Ghidra didn't like accessing memory out of chunks (pretty common) // So it has been reverted - auto& prog = spu_exec.progs.emplace_back(SYS_SPU_SEGMENT_TYPE_COPY, 0x7, 0, SPU_LS_SIZE, 8, std::vector(ls, ls + SPU_LS_SIZE)); - - prog.p_paddr = prog.p_vaddr; - spu_log.success("Segment: p_type=0x%x, p_vaddr=0x%x, p_filesz=0x%x, p_memsz=0x%x", prog.p_type, prog.p_vaddr, prog.p_filesz, prog.p_memsz); + spu_memory_segment_dump_data single_seg{.ls_addr = 0, .src_addr = ls, .segment_size = SPU_LS_SIZE}; + spu_exec_object spu_exec = capture_memory_as_elf({&single_seg, 1}, pc); std::string name; @@ -5957,22 +6021,6 @@ bool spu_thread::capture_state() fmt::append(name, "RawSPU.%u", lv2_id); } - u32 pc0 = pc; - - for (; pc0; pc0 -= 4) - { - be_t op; - std::memcpy(&op, prog.bin.data() + pc0 - 4, 4); - - // Try to find function entry (if they are placed sequentially search for BI $LR of previous function) - if (!op || op == 0x35000000u || s_spu_itype.decode(op) == spu_itype::UNK) - { - break; - } - } - - spu_exec.header.e_entry = pc0; - name = vfs::escape(name, true); std::replace(name.begin(), name.end(), ' ', '_'); @@ -6027,7 +6075,7 @@ bool spu_thread::capture_state() } rewind = std::make_shared(); - (*rewind)(std::span(prog.bin.data(), prog.bin.size())); // span serialization doesn't remember size which is what we need + (*rewind)(std::span(spu_exec.progs[0].bin.data(), spu_exec.progs[0].bin.size())); // span serialization doesn't remember size which is what we need serialize_common(*rewind); // TODO: Save and restore decrementer state properly diff --git a/rpcs3/Emu/Cell/SPUThread.h b/rpcs3/Emu/Cell/SPUThread.h index f59f9186c5..a2f24a9836 100644 --- a/rpcs3/Emu/Cell/SPUThread.h +++ b/rpcs3/Emu/Cell/SPUThread.h @@ -9,6 +9,10 @@ #include "util/logs.hpp" #include "util/to_endian.hpp" +#include "Loader/ELF.h" + +#include + LOG_CHANNEL(spu_log, "SPU"); struct lv2_event_queue; @@ -596,6 +600,14 @@ enum class spu_type : u32 isolated, }; +struct spu_memory_segment_dump_data +{ + u32 ls_addr; + const u8* src_addr; + u32 segment_size; + u32 flags = umax; +}; + class spu_thread : public cpu_thread { public: @@ -804,7 +816,7 @@ public: void set_events(u32 bits); void set_interrupt_status(bool enable); bool check_mfc_interrupts(u32 next_pc); - bool is_exec_code(u32 addr) const; // Only a hint, do not rely on it other than debugging purposes + static bool is_exec_code(u32 addr, const u8* ls_ptr); // Only a hint, do not rely on it other than debugging purposes u32 get_ch_count(u32 ch); s64 get_ch_value(u32 ch); bool set_ch_value(u32 ch, u32 value); @@ -816,6 +828,7 @@ public: std::array, 32> rewind_captures; // shared_ptr to avoid header inclusion u8 current_rewind_capture_idx = 0; + static spu_exec_object capture_memory_as_elf(std::span segs, u32 pc_hint = umax); bool capture_state(); bool try_load_debug_capture(); void wakeup_delay(u32 div = 1) const; diff --git a/rpcs3/Loader/ELF.h b/rpcs3/Loader/ELF.h index 69defb3e5f..c7340b7115 100644 --- a/rpcs3/Loader/ELF.h +++ b/rpcs3/Loader/ELF.h @@ -354,6 +354,11 @@ public: std::vector save(std::vector&& init = std::vector{}) const { + if (get_error() != elf_error::ok) + { + return std::move(init); + } + fs::file stream = fs::make_stream>(std::move(init)); const bool fixup_shdrs = shdrs.empty() || shdrs[0].sh_type != sec_type::sht_null; @@ -437,15 +442,10 @@ public: elf_object& set_error(elf_error error) { // Setting an error causes the state to clear if there was no error before - // Trying to set elf_error::ok is ignored - if (error != elf_error::ok) - { - if (m_error == elf_error::ok) - clear(); - - m_error = error; - } + if (m_error == elf_error::ok && error != elf_error::ok) + clear(); + m_error = error; return *this; } diff --git a/rpcs3/rpcs3.vcxproj b/rpcs3/rpcs3.vcxproj index eb20fe5be9..7bcfff6287 100644 --- a/rpcs3/rpcs3.vcxproj +++ b/rpcs3/rpcs3.vcxproj @@ -225,6 +225,9 @@ true + + true + true @@ -477,6 +480,9 @@ true + + true + true @@ -694,6 +700,7 @@ + @@ -1170,6 +1177,16 @@ .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" + + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWIN32_LEAN_AND_MEAN -DHAVE_VULKAN -DMINIUPNP_STATICLIB -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -DQT_MULTIMEDIAWIDGETS_LIB -DQT_SVG_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\SoundTouch\soundtouch\include" "-I.\..\3rdparty\cubeb\extra" "-I.\..\3rdparty\cubeb\cubeb\include" "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWIN32_LEAN_AND_MEAN -DHAVE_VULKAN -DMINIUPNP_STATICLIB -DHAVE_SDL2 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -DQT_MULTIMEDIAWIDGETS_LIB -DQT_SVG_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\SoundTouch\soundtouch\include" "-I.\..\3rdparty\cubeb\extra" "-I.\..\3rdparty\cubeb\cubeb\include" "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\libsdl-org\SDL\include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg" + Moc%27ing %(Identity)... diff --git a/rpcs3/rpcs3.vcxproj.filters b/rpcs3/rpcs3.vcxproj.filters index 5369594056..2fa1de1297 100644 --- a/rpcs3/rpcs3.vcxproj.filters +++ b/rpcs3/rpcs3.vcxproj.filters @@ -1002,6 +1002,15 @@ Generated Files\Release + + Gui\dev tools + + + Generated Files\Debug + + + Generated Files\Release + @@ -1471,6 +1480,9 @@ Gui\flow_layout + + Gui\dev tools + diff --git a/rpcs3/rpcs3qt/CMakeLists.txt b/rpcs3/rpcs3qt/CMakeLists.txt index fab0863724..4f88c00b16 100644 --- a/rpcs3/rpcs3qt/CMakeLists.txt +++ b/rpcs3/rpcs3qt/CMakeLists.txt @@ -20,6 +20,7 @@ set(SRC_FILES downloader.cpp _discord_utils.cpp emu_settings.cpp + elf_memory_dumping_dialog.cpp fatal_error_dialog.cpp find_dialog.cpp flow_layout.cpp diff --git a/rpcs3/rpcs3qt/debugger_frame.cpp b/rpcs3/rpcs3qt/debugger_frame.cpp index 59d3aec7ed..193883e8ad 100644 --- a/rpcs3/rpcs3qt/debugger_frame.cpp +++ b/rpcs3/rpcs3qt/debugger_frame.cpp @@ -2,6 +2,7 @@ #include "register_editor_dialog.h" #include "instruction_editor_dialog.h" #include "memory_viewer_panel.h" +#include "elf_memory_dumping_dialog.h" #include "gui_settings.h" #include "debugger_list.h" #include "breakpoint_list.h" @@ -289,7 +290,7 @@ void debugger_frame::keyPressEvent(QKeyEvent* event) QLabel* l = new QLabel(tr( "Keys Ctrl+G: Go to typed address." "\nKeys Ctrl+B: Open breakpoints settings." - "\nKeys Alt+S: Capture SPU images of selected SPU." + "\nKeys Alt+S: Capture SPU images of selected SPU or generalized form when used from PPU." "\nKeys Alt+R: Load last saved SPU state capture." "\nKey D: SPU MFC commands logger, MFC debug setting must be enabled." "\nKey D: Also PPU calling history logger, interpreter and non-zero call history size must be used." @@ -569,6 +570,12 @@ void debugger_frame::keyPressEvent(QKeyEvent* event) if (modifiers & Qt::AltModifier) { + if (cpu->id_type() == 1) + { + new elf_memory_dumping_dialog(pc, m_gui_settings, this); + return; + } + if (cpu->id_type() != 2) { return; diff --git a/rpcs3/rpcs3qt/elf_memory_dumping_dialog.cpp b/rpcs3/rpcs3qt/elf_memory_dumping_dialog.cpp new file mode 100644 index 0000000000..7bbcc53689 --- /dev/null +++ b/rpcs3/rpcs3qt/elf_memory_dumping_dialog.cpp @@ -0,0 +1,224 @@ +#include "elf_memory_dumping_dialog.h" +#include "Utilities/Config.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 = [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)))); + 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%x").arg(ppu_debugger_addr & -0x10000, 2, 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, 2, 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.")); + } + } +} diff --git a/rpcs3/rpcs3qt/elf_memory_dumping_dialog.h b/rpcs3/rpcs3qt/elf_memory_dumping_dialog.h new file mode 100644 index 0000000000..3c6d7fe59f --- /dev/null +++ b/rpcs3/rpcs3qt/elf_memory_dumping_dialog.h @@ -0,0 +1,33 @@ +#pragma once + +#include "util/types.hpp" +#include "gui_settings.h" + +#include +#include +#include + +#include + +class elf_memory_dumping_dialog : public QDialog +{ + Q_OBJECT + +public: + explicit elf_memory_dumping_dialog(u32 ppu_debugger_pc, std::shared_ptr _gui_settings, QWidget* parent = nullptr); + +protected: + void add_new_segment(); + void remove_segment(); + void save_to_file(); + + std::shared_ptr m_gui_settings; + + // UI variables needed in higher scope + QListWidget* m_seg_list = nullptr; + + QLineEdit* m_ls_address_input = nullptr; + QLineEdit* m_segment_size_input = nullptr; + QLineEdit* m_ppu_address_input = nullptr; + QLineEdit* m_segment_flags_input = nullptr; +}; diff --git a/rpcs3/rpcs3qt/gui_settings.h b/rpcs3/rpcs3qt/gui_settings.h index 9f1b1c7038..bd83bf4623 100644 --- a/rpcs3/rpcs3qt/gui_settings.h +++ b/rpcs3/rpcs3qt/gui_settings.h @@ -148,6 +148,7 @@ namespace gui const gui_save fd_ext_tar = gui_save(main_window, "lastExplorePathExTAR", ""); const gui_save fd_insert_disc = gui_save(main_window, "lastExplorePathDISC", ""); const gui_save fd_cfg_check = gui_save(main_window, "lastExplorePathCfgChk", ""); + const gui_save fd_save_elf = gui_save(main_window, "lastExplorePathSaveElf", ""); const gui_save mw_debugger = gui_save(main_window, "debuggerVisible", false); const gui_save mw_logger = gui_save(main_window, "loggerVisible", true); diff --git a/rpcs3/rpcs3qt/vfs_dialog_path_widget.cpp b/rpcs3/rpcs3qt/vfs_dialog_path_widget.cpp index 41d0cfe291..878fb0476d 100644 --- a/rpcs3/rpcs3qt/vfs_dialog_path_widget.cpp +++ b/rpcs3/rpcs3qt/vfs_dialog_path_widget.cpp @@ -87,7 +87,7 @@ void vfs_dialog_path_widget::remove_directory() const const int row = m_dir_list->currentRow(); if (row > 0) { - QListWidgetItem* item = m_dir_list->item(row); + QListWidgetItem* item = m_dir_list->takeItem(row); delete item; } }