From c8f56590902662c66454fc24e1a05603ab97b335 Mon Sep 17 00:00:00 2001 From: trigger <5994581+cipherxof@users.noreply.github.com> Date: Tue, 29 Apr 2025 05:42:16 -0700 Subject: [PATCH] Qt: Hex validator for address/instruction inputs (#17113) --- rpcs3/rpcs3.vcxproj | 1 + rpcs3/rpcs3.vcxproj.filters | 3 + rpcs3/rpcs3qt/debugger_frame.cpp | 8 +-- rpcs3/rpcs3qt/hex_validator.h | 71 +++++++++++++++++++++ rpcs3/rpcs3qt/instruction_editor_dialog.cpp | 9 +-- rpcs3/rpcs3qt/memory_viewer_panel.cpp | 13 ++-- 6 files changed, 91 insertions(+), 14 deletions(-) create mode 100644 rpcs3/rpcs3qt/hex_validator.h diff --git a/rpcs3/rpcs3.vcxproj b/rpcs3/rpcs3.vcxproj index 95bcae1b89..8fd974ac5b 100644 --- a/rpcs3/rpcs3.vcxproj +++ b/rpcs3/rpcs3.vcxproj @@ -1458,6 +1458,7 @@ .\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 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -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$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg" + diff --git a/rpcs3/rpcs3.vcxproj.filters b/rpcs3/rpcs3.vcxproj.filters index 6394ef671d..1b79187af3 100644 --- a/rpcs3/rpcs3.vcxproj.filters +++ b/rpcs3/rpcs3.vcxproj.filters @@ -1415,6 +1415,9 @@ Gui\widgets + + Gui\utils + diff --git a/rpcs3/rpcs3qt/debugger_frame.cpp b/rpcs3/rpcs3qt/debugger_frame.cpp index f98c7fbdd5..2136c6c9bd 100644 --- a/rpcs3/rpcs3qt/debugger_frame.cpp +++ b/rpcs3/rpcs3qt/debugger_frame.cpp @@ -10,6 +10,7 @@ #include "call_stack_list.h" #include "input_dialog.h" #include "qt_utils.h" +#include "hex_validator.h" #include "Emu/System.h" #include "Emu/IdManager.h" @@ -1404,15 +1405,14 @@ void debugger_frame::ShowGotoAddressDialog() // Address expression input QLineEdit* expression_input(new QLineEdit(m_goto_dialog)); expression_input->setFont(m_mono); - expression_input->setMaxLength(18); if (const auto thread = get_cpu(); !thread || thread->get_class() != thread_class::spu) { - expression_input->setValidator(new QRegularExpressionValidator(QRegularExpression("^(0[xX])?0*[a-fA-F0-9]{0,8}$"), this)); + expression_input->setValidator(new HexValidator(expression_input)); } else { - expression_input->setValidator(new QRegularExpressionValidator(QRegularExpression("^(0[xX])?0*[a-fA-F0-9]{0,5}$"), this)); + expression_input->setValidator(new HexValidator(expression_input)); } // Ok/Cancel @@ -1451,7 +1451,7 @@ void debugger_frame::ShowGotoAddressDialog() // This also works if no thread is selected and has been selected before if (result == QDialog::Accepted && cpu == get_cpu() && cpu == cpu_check()) { - PerformGoToRequest(expression_input->text()); + PerformGoToRequest(normalize_hex_qstring(expression_input->text())); } m_goto_dialog = nullptr; diff --git a/rpcs3/rpcs3qt/hex_validator.h b/rpcs3/rpcs3qt/hex_validator.h new file mode 100644 index 0000000000..8f2b2fc248 --- /dev/null +++ b/rpcs3/rpcs3qt/hex_validator.h @@ -0,0 +1,71 @@ +#pragma once + +#include +#include + +class HexValidator : public QValidator +{ +public: + explicit HexValidator(QObject* parent = nullptr, int max_bits = 32) + : QValidator(parent) + , m_max_bits(max_bits) + {} + + State validate(QString& input, int& pos) const override + { + Q_UNUSED(pos); + + QString stripped = input.toLower().remove(' '); + + if (stripped.startsWith("0x")) + stripped = stripped.mid(2); + + if (stripped.endsWith("h")) + stripped.chop(1); + + if (stripped.isEmpty()) + return QValidator::Intermediate; + + if (stripped.length() > 16) + return QValidator::Invalid; + + static const QRegularExpression hex_re("^[0-9a-f]+$"); + if (!hex_re.match(stripped).hasMatch()) + return QValidator::Invalid; + + QString sig = stripped; + sig.remove(QRegularExpression("^0+")); + const int sig_nibbles = sig.isEmpty() ? 1 : sig.length(); + if (sig_nibbles > (m_max_bits + 3) / 4) + return QValidator::Invalid; + + bool ok = false; + const qulonglong value = stripped.toULongLong(&ok, 16); + if (!ok) + return QValidator::Invalid; + + if (m_max_bits < 64) + { + const qulonglong max_val = (qulonglong(1) << m_max_bits) - 1; + if (value > max_val) + return QValidator::Invalid; + } + + return QValidator::Acceptable; + } + +private: + const int m_max_bits; +}; + +inline QString normalize_hex_qstring(const QString& input) +{ + QString s = input; + s.remove(' '); + s = s.toLower(); + if (s.startsWith("0x")) + s = s.mid(2); + if (s.endsWith('h')) + s.chop(1); + return s; +} diff --git a/rpcs3/rpcs3qt/instruction_editor_dialog.cpp b/rpcs3/rpcs3qt/instruction_editor_dialog.cpp index db491f8e14..26205d2a14 100644 --- a/rpcs3/rpcs3qt/instruction_editor_dialog.cpp +++ b/rpcs3/rpcs3qt/instruction_editor_dialog.cpp @@ -1,4 +1,5 @@ #include "instruction_editor_dialog.h" +#include "hex_validator.h" #include "Emu/Cell/SPUThread.h" #include "Emu/CPU/CPUThread.h" @@ -44,8 +45,8 @@ instruction_editor_dialog::instruction_editor_dialog(QWidget *parent, u32 _pc, C m_instr = new QLineEdit(this); m_instr->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); - m_instr->setMaxLength(8); - m_instr->setMaximumWidth(65); + m_instr->setValidator(new HexValidator(m_instr)); + m_instr->setMaximumWidth(130); m_disasm->change_mode(cpu_disasm_mode::normal); m_disasm->disasm(m_pc); @@ -109,7 +110,7 @@ instruction_editor_dialog::instruction_editor_dialog(QWidget *parent, u32 _pc, C } bool ok; - const ulong opcode = m_instr->text().toULong(&ok, 16); + const ulong opcode = normalize_hex_qstring(m_instr->text()).toULong(&ok, 16); if (!ok || opcode > u32{umax}) { QMessageBox::critical(this, tr("Error"), tr("Failed to parse PPU instruction.")); @@ -152,7 +153,7 @@ instruction_editor_dialog::instruction_editor_dialog(QWidget *parent, u32 _pc, C void instruction_editor_dialog::updatePreview() const { bool ok; - const be_t opcode{static_cast(m_instr->text().toULong(&ok, 16))}; + const be_t opcode{static_cast(normalize_hex_qstring(m_instr->text()).toULong(&ok, 16))}; m_disasm->change_ptr(reinterpret_cast(&opcode) - std::intptr_t{m_pc}); if (ok && m_disasm->disasm(m_pc)) diff --git a/rpcs3/rpcs3qt/memory_viewer_panel.cpp b/rpcs3/rpcs3qt/memory_viewer_panel.cpp index bd8fc91727..8905744e77 100644 --- a/rpcs3/rpcs3qt/memory_viewer_panel.cpp +++ b/rpcs3/rpcs3qt/memory_viewer_panel.cpp @@ -2,6 +2,7 @@ #include "Emu/Memory/vm.h" #include "memory_viewer_panel.h" +#include "hex_validator.h" #include "Emu/Cell/SPUThread.h" #include "Emu/CPU/CPUDisAsm.h" @@ -94,10 +95,10 @@ memory_viewer_panel::memory_viewer_panel(QWidget* parent, std::shared_ptrsetPlaceholderText("00000000"); m_addr_line->setFont(mono); - m_addr_line->setMaxLength(18); - m_addr_line->setFixedWidth(75); + m_addr_line->setValidator(new HexValidator(m_addr_line)); + m_addr_line->setFixedWidth(100); m_addr_line->setFocus(); - m_addr_line->setValidator(new QRegularExpressionValidator(QRegularExpression(m_type == thread_class::spu ? "^(0[xX])?0*[a-fA-F0-9]{0,5}$" : "^(0[xX])?0*[a-fA-F0-9]{0,8}$"), this)); + m_addr_line->setAlignment(Qt::AlignCenter); hbox_tools_mem_addr->addWidget(m_addr_line); tools_mem_addr->setLayout(hbox_tools_mem_addr); @@ -287,7 +288,8 @@ memory_viewer_panel::memory_viewer_panel(QWidget* parent, std::shared_ptr(u8"Ʌ"), group_search); button_collapse_viewer->setFixedWidth(QLabel(button_collapse_viewer->text()).sizeHint().width() * 3); - + button_collapse_viewer->setAutoDefault(false); + 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...")); @@ -424,8 +426,7 @@ memory_viewer_panel::memory_viewer_panel(QWidget* parent, std::shared_ptrtext(); - const u32 addr = (text.startsWith("0x", Qt::CaseInsensitive) ? text.right(text.size() - 2) : text).toULong(&ok, 16); + const u32 addr = normalize_hex_qstring(m_addr_line->text()).toULong(&ok, 16); if (ok) m_addr = addr; scroll(0); // Refresh