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