diff --git a/rpcs3/Emu/CPU/CPUDisAsm.h b/rpcs3/Emu/CPU/CPUDisAsm.h index c24a1cab4d..bea390f1a6 100644 --- a/rpcs3/Emu/CPU/CPUDisAsm.h +++ b/rpcs3/Emu/CPU/CPUDisAsm.h @@ -18,7 +18,7 @@ class CPUDisAsm { protected: cpu_disasm_mode m_mode{}; - const std::add_pointer_t m_offset{}; + const u8* m_offset{}; const u32 m_start_pc; const std::add_pointer_t m_cpu{}; u32 m_op = 0; @@ -64,10 +64,14 @@ public: std::string last_opcode{}; u32 dump_pc{}; - CPUDisAsm& change_mode(cpu_disasm_mode mode) + cpu_disasm_mode change_mode(cpu_disasm_mode mode) { - m_mode = mode; - return *this; + return std::exchange(m_mode, mode); + } + + const u8* change_ptr(const u8* ptr) + { + return std::exchange(m_offset, ptr); } protected: diff --git a/rpcs3/rpcs3qt/memory_string_searcher.cpp b/rpcs3/rpcs3qt/memory_string_searcher.cpp index e36bb624aa..12a41637e6 100644 --- a/rpcs3/rpcs3qt/memory_string_searcher.cpp +++ b/rpcs3/rpcs3qt/memory_string_searcher.cpp @@ -2,6 +2,7 @@ #include "Emu/Memory/vm.h" #include "Emu/Memory/vm_reservation.h" #include "Emu/CPU/CPUDisAsm.h" +#include "Emu/Cell/SPUDisAsm.h" #include "Emu/IdManager.h" #include "Utilities/Thread.h" @@ -23,15 +24,59 @@ LOG_CHANNEL(gui_log, "GUI"); -enum : int +constexpr auto qstr = QString::fromStdString; + +enum search_mode : int { - as_string, - as_hex, - as_f64, - as_f32, - as_inst, + no_mode = 1, + as_string = 2, + as_hex = 4, + as_f64 = 8, + as_f32 = 16, + as_inst = 32, + as_fake_spu_inst = 64, }; +template <> +void fmt_class_string::format(std::string& out, u64 arg) +{ + if (!arg) + { + out += "No search modes have been selected"; + } + + for (int modes = static_cast(arg); modes; modes &= modes - 1) + { + const int mode = modes & ~(modes - 1); + + auto mode_s = [&]() -> std::string_view + { + switch (mode) + { + case as_string: return "String"; + case as_hex: return "HEX bytes/integer"; + case as_f64: return "Double"; + case as_f32: return "Float"; + case as_inst: return "Instruction"; + case as_fake_spu_inst: return "SPU Instruction"; + default: return ""; + } + }(); + + if (mode_s.empty()) + { + break; + } + + if (modes != static_cast(arg)) + { + out += ", "; + } + + out += mode_s; + } +} + memory_string_searcher::memory_string_searcher(QWidget* parent, std::shared_ptr disasm, std::string_view title) : QDialog(parent) , m_disasm(std::move(disasm)) @@ -64,16 +109,42 @@ memory_string_searcher::memory_string_searcher(QWidget* parent, std::shared_ptr< "\nWarning: this may reduce performance of the search.")); m_cbox_input_mode = new QComboBox(this); - m_cbox_input_mode->addItem("String", QVariant::fromValue(+as_string)); - m_cbox_input_mode->addItem("HEX bytes/integer", QVariant::fromValue(+as_hex)); - m_cbox_input_mode->addItem("Double", QVariant::fromValue(+as_f64)); - m_cbox_input_mode->addItem("Float", QVariant::fromValue(+as_f32)); - m_cbox_input_mode->addItem("Instruction", QVariant::fromValue(+as_inst)); - m_cbox_input_mode->setToolTip(tr("String: search the memory for the specified string." - "\nHEX bytes/integer: search the memory for hexadecimal values. Spaces, commas, \"0x\", \"0X\", \"\\x\" ensure separation of bytes but they are not mandatory." + m_cbox_input_mode->addItem(tr("Select search mode(s).."), QVariant::fromValue(+no_mode)); + 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.")); + "\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::of(&QComboBox::currentIndexChanged), this, [this](int index) + { + if ((1 << index) == no_mode) + { + m_modes = {}; + } + else + { + m_modes = search_mode{m_modes | (1 << index)}; + } + + m_modes_label->setText(qstr(fmt::format("%s.", m_modes))); + }); + + m_cbox_input_mode->setToolTip(tooltip); + + m_modes_label = new QLabel(qstr(fmt::format("%s.", m_modes))); QHBoxLayout* hbox_panel = new QHBoxLayout(); hbox_panel->addWidget(m_addr_line); @@ -81,9 +152,33 @@ memory_string_searcher::memory_string_searcher(QWidget* parent, std::shared_ptr< hbox_panel->addWidget(m_chkbox_case_insensitive); hbox_panel->addWidget(button_search); - setLayout(hbox_panel); + QVBoxLayout* vbox_panel = new QVBoxLayout(); + vbox_panel->addLayout(hbox_panel); + vbox_panel->addWidget(m_modes_label); - connect(button_search, &QAbstractButton::clicked, this, &memory_string_searcher::OnSearch); + setLayout(vbox_panel); + + connect(button_search, &QAbstractButton::clicked, this, [this]() + { + std::string wstr = m_addr_line->text().toStdString(); + + if (wstr.empty() || wstr.size() >= 4096u) + { + gui_log.error("String is empty or too long (size=%u)", wstr.size()); + return; + } + + 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); + }); layout()->setSizeConstraint(QLayout::SetFixedSize); @@ -97,19 +192,8 @@ memory_string_searcher::memory_string_searcher(QWidget* parent, std::shared_ptr< }); } -void memory_string_searcher::OnSearch() +u64 memory_string_searcher::OnSearch(std::string wstr, int mode) { - std::string wstr = m_addr_line->text().toStdString(); - - if (wstr.empty() || wstr.size() >= 4096u) - { - gui_log.error("String is empty or too long (size=%u)", wstr.size()); - return; - } - - gui_log.notice("Searching for %s", wstr); - - const int mode = std::max(m_cbox_input_mode->currentIndex(), 0); bool case_insensitive = false; // First characters for case insensitive search @@ -126,6 +210,7 @@ void memory_string_searcher::OnSearch() { case as_inst: case as_string: + case as_fake_spu_inst: { case_insensitive = m_chkbox_case_insensitive->isChecked(); @@ -141,7 +226,7 @@ void memory_string_searcher::OnSearch() constexpr std::string_view hex_chars = "0123456789ABCDEFabcdef"; // Split - std::vector parts = fmt::split(wstr, {" ", ",", "0x", "0X", "\\x"}); + std::vector parts = fmt::split(wstr, {" ", ",", "0x", "0X", "\\x", "h", "H"}); // Pad zeroes for (std::string& part : parts) @@ -164,7 +249,7 @@ void memory_string_searcher::OnSearch() { gui_log.error("String '%s' cannot be interpreted as hexadecimal byte string due to unknown character '%c'.", m_addr_line->text().toStdString(), wstr[pos]); - return; + return 0; } std::string dst; @@ -182,13 +267,16 @@ void memory_string_searcher::OnSearch() } case as_f64: { + // Remove trailing 'f' letters + wstr = wstr.substr(0, wstr.find_last_not_of("Ff") + 1); + char* end{}; be_t value = std::strtod(wstr.data(), &end); - if (end != wstr.data() + wstr.size()) + if (wstr.empty() || end != wstr.data() + wstr.size()) { gui_log.error("String '%s' cannot be interpreted as double.", wstr); - return; + return 0; } wstr.resize(sizeof(value)); @@ -197,13 +285,15 @@ void memory_string_searcher::OnSearch() } case as_f32: { + wstr = wstr.substr(0, wstr.find_last_not_of("Ff") + 1); + char* end{}; be_t value = std::strtof(wstr.data(), &end); - if (end != wstr.data() + wstr.size()) + if (wstr.empty() || end != wstr.data() + wstr.size()) { gui_log.error("String '%s' cannot be interpreted as float.", wstr); - return; + return 0; } wstr.resize(sizeof(value)); @@ -218,7 +308,7 @@ void memory_string_searcher::OnSearch() atomic_t avail_addr = 0; // There's no need for so many threads (except for instructions searching) - const u32 max_threads = utils::aligned_div(utils::get_thread_count(), mode != as_inst ? 2 : 1); + const u32 max_threads = utils::aligned_div(utils::get_thread_count(), mode < as_inst ? 2 : 1); static constexpr u32 block_size = 0x2000000; @@ -226,12 +316,14 @@ void memory_string_searcher::OnSearch() const named_thread_group workers("String Searcher "sv, max_threads, [&]() { - if (mode == as_inst) + if (mode == as_inst || mode == as_fake_spu_inst) { auto disasm = m_disasm->copy_type_erased(); disasm->change_mode(cpu_disasm_mode::normal); - const usz limit = std::min(m_size, m_ptr == vm::g_sudo_addr ? 0x4000'0000 : m_size); + SPUDisAsm spu_dis(cpu_disasm_mode::normal, static_cast(m_ptr)); + + const usz limit = std::min(m_size, m_ptr == vm::g_sudo_addr ? 0xFFFF'0000 : m_size); while (true) { @@ -241,7 +333,7 @@ void memory_string_searcher::OnSearch() { if (val < limit && val != umax) { - while (m_ptr == vm::g_sudo_addr && !vm::check_addr(val, vm::page_executable)) + while (m_ptr == vm::g_sudo_addr && !vm::check_addr(val, mode == as_inst ? vm::page_executable : 0)) { // Skip unmapped memory val = utils::align(val + 1, 0x10000); @@ -274,11 +366,23 @@ void memory_string_searcher::OnSearch() return; } + u32 spu_base_pc = 0; + + if (mode == as_fake_spu_inst) + { + // Check if we can extend the limits of SPU decoder so it can use the previous 64k block + // For SPU instruction patterns + spu_base_pc = (addr >= 0x10000 && (m_ptr != vm::g_sudo_addr || vm::check_addr(addr - 0x10000, 0))) ? 0x10000 : 0; + + // Set base for SPU decoder + spu_dis.change_ptr(static_cast(m_ptr) + addr - spu_base_pc); + } + for (u32 i = 0; i < 0x10000; i += 4) { - if (disasm->disasm(addr + i)) + if (mode == as_fake_spu_inst ? spu_dis.disasm(spu_base_pc + i) : disasm->disasm(addr + i)) { - auto& last = disasm->last_opcode; + auto& last = mode == as_fake_spu_inst ? spu_dis.last_opcode : disasm->last_opcode; if (case_insensitive) { @@ -287,7 +391,7 @@ void memory_string_searcher::OnSearch() if (last.find(wstr) != umax) { - gui_log.success("Found instruction at 0x%08x: '%s'", addr + i, disasm->last_opcode); + gui_log.success("Found instruction at 0x%08x: '%s'", addr + i, last); found++; } } @@ -437,5 +541,5 @@ void memory_string_searcher::OnSearch() workers.join(); - gui_log.success("Search completed (found %u matches)", +found); + return found; } diff --git a/rpcs3/rpcs3qt/memory_string_searcher.h b/rpcs3/rpcs3qt/memory_string_searcher.h index 34aa5464b5..e499d0119f 100644 --- a/rpcs3/rpcs3qt/memory_string_searcher.h +++ b/rpcs3/rpcs3qt/memory_string_searcher.h @@ -7,9 +7,12 @@ class QLineEdit; class QCheckBox; class QComboBox; +class QLabel; class CPUDisAsm; +enum search_mode : int; + class memory_string_searcher : public QDialog { Q_OBJECT @@ -17,17 +20,20 @@ class memory_string_searcher : public QDialog QLineEdit* m_addr_line = nullptr; QCheckBox* m_chkbox_case_insensitive = nullptr; QComboBox* m_cbox_input_mode = nullptr; + QLabel* m_modes_label = nullptr; std::shared_ptr m_disasm; const void* m_ptr; usz m_size; + search_mode m_modes{}; + public: explicit memory_string_searcher(QWidget* parent, std::shared_ptr disasm, std::string_view title = {}); -private Q_SLOTS: - void OnSearch(); +private: + u64 OnSearch(std::string wstr, int mode); }; // Lifetime management with IDM