From b28460353ff3f227ed1b2faf01a064bcf3aacb11 Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Mon, 13 May 2024 22:31:22 +0200 Subject: [PATCH] use SpinBox in `DialogEditSimpleValue`, build custom SpinBox to limit `uint32` and 64bit types to the appropriate ranges --- CMakeLists.txt | 1 + include/QtHelpers/DialogEditSimpleValue.h | 5 +- include/QtHelpers/LongLongSpinBox.h | 272 ++++++++++++++++++++++ src/QtHelpers/DialogEditSimpleValue.cpp | 198 +++++++++------- 4 files changed, 392 insertions(+), 84 deletions(-) create mode 100644 include/QtHelpers/LongLongSpinBox.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b4d9562..3346f0c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -101,6 +101,7 @@ x64dbg_plugin(${PROJECT_NAME} include/QtHelpers/ItemModelLoggerSamples.h include/QtHelpers/WidgetDatabaseView.h include/QtHelpers/WidgetAutorefresh.h + include/QtHelpers/LongLongSpinBox.h src/Spelunky2.cpp src/Configuration.cpp src/Data/EntityDB.cpp diff --git a/include/QtHelpers/DialogEditSimpleValue.h b/include/QtHelpers/DialogEditSimpleValue.h index b33304c..ff7d92b 100644 --- a/include/QtHelpers/DialogEditSimpleValue.h +++ b/include/QtHelpers/DialogEditSimpleValue.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -25,13 +26,13 @@ namespace S2Plugin private slots: void cancelBtnClicked(); void changeBtnClicked(); - void decValueChanged(); + void decValueChanged(const QString& text); private: uintptr_t mMemoryAddress; MemoryFieldType mFieldType; - QLineEdit* mLineEditDecValue; + QAbstractSpinBox* mSpinBox; QLineEdit* mLineEditHexValue; }; } // namespace S2Plugin diff --git a/include/QtHelpers/LongLongSpinBox.h b/include/QtHelpers/LongLongSpinBox.h new file mode 100644 index 0000000..8c1e42f --- /dev/null +++ b/include/QtHelpers/LongLongSpinBox.h @@ -0,0 +1,272 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +namespace S2Plugin +{ + class LongLongSpinBox : public QAbstractSpinBox + { + Q_OBJECT + + using type = long long; + + type m_minimum; + type m_maximum; + type m_value; + + public: + explicit LongLongSpinBox(QWidget* parent = nullptr) : QAbstractSpinBox(parent) + { + setRange(std::numeric_limits::min(), std::numeric_limits::max()); + QObject::connect(lineEdit(), &QLineEdit::textEdited, this, &LongLongSpinBox::onEditFinished); + }; + ~LongLongSpinBox(){}; + + type value() const + { + return m_value; + }; + + type minimum() const + { + return m_minimum; + }; + + void setMinimum(type min) + { + m_minimum = min; + } + + type maximum() const + { + return m_maximum; + }; + + void setMaximum(type max) + { + m_maximum = max; + } + + void setRange(type min, type max) + { + setMinimum(min); + setMaximum(max); + } + + void stepBy(int steps) override + { + type new_value; + + if (steps > 0 && m_value > std::numeric_limits::max() - steps) // overflow + new_value = m_maximum; + else if (steps < 0 && m_value < std::numeric_limits::min() - steps) // underflow + new_value = m_minimum; + else if ((m_value + steps) < m_minimum) + new_value = m_minimum; + else if ((m_value + steps) > m_maximum) + new_value = m_maximum; + else + new_value = m_value + steps; + + setValue(new_value); + } + + protected: + // bool event(QEvent *event); + QValidator::State validate(QString& input, int&) const override + { + if (input.isEmpty()) + return QValidator::Acceptable; + + // technically should check if the range allows positive / negative values + if (input.length() == 1 && (input[0] == '+' || input[0] == '-')) + return QValidator::Acceptable; + + bool ok; + type val = input.toLongLong(&ok); + if (!ok) + return QValidator::Invalid; + + if (val < m_minimum || val > m_maximum) + return QValidator::Invalid; + + return QValidator::Acceptable; + } + + virtual type valueFromText(const QString& text) const + { + return text.toLongLong(); + } + + virtual QString textFromValue(type val) const + { + return QString::number(val); + } + // virtual void fixup(QString &str) const; + + virtual QAbstractSpinBox::StepEnabled stepEnabled() const + { + return StepUpEnabled | StepDownEnabled; + } + + public Q_SLOTS: + void setValue(type val) + { + if (m_value != val) + { + auto new_text = textFromValue(val); + lineEdit()->setText(new_text); + m_value = val; + // emit valueChanged(val); + emit valueChanged(new_text); + } + } + + void onEditFinished() + { + auto new_text = text(); + m_value = valueFromText(new_text); + // emit valueChanged(m_value); + emit valueChanged(new_text); + } + + Q_SIGNALS: + // void valueChanged(type v); + void valueChanged(const QString& v); + }; + + class ULongLongSpinBox : public QAbstractSpinBox + { + Q_OBJECT + + using type = unsigned long long; + + type m_minimum; + type m_maximum; + type m_value; + + public: + explicit ULongLongSpinBox(QWidget* parent = nullptr) : QAbstractSpinBox(parent) + { + setRange(std::numeric_limits::min(), std::numeric_limits::max()); + QObject::connect(lineEdit(), &QLineEdit::textEdited, this, &ULongLongSpinBox::onEditFinished); + }; + ~ULongLongSpinBox(){}; + + type value() const + { + return m_value; + }; + + type minimum() const + { + return m_minimum; + }; + + void setMinimum(type min) + { + m_minimum = min; + } + + type maximum() const + { + return m_maximum; + }; + + void setMaximum(type max) + { + m_maximum = max; + } + + void setRange(type min, type max) + { + setMinimum(min); + setMaximum(max); + } + + void stepBy(int steps) override + { + type new_value; + + if (steps > 0 && m_value > std::numeric_limits::max() - steps) // overflow + new_value = m_maximum; + else if (steps < 0 && m_value < std::numeric_limits::min() - steps) // underflow + new_value = m_minimum; + else if ((m_value + steps) < m_minimum) + new_value = m_minimum; + else if ((m_value + steps) > m_maximum) + new_value = m_maximum; + else + new_value = m_value + steps; + + setValue(new_value); + } + + protected: + // bool event(QEvent *event); + QValidator::State validate(QString& input, int&) const override + { + if (input.isEmpty()) + return QValidator::Acceptable; + + // technically should to be more complicated, chacking if the range allows positive / negative values + if (input.length() == 1 && input[0] == '+') + return QValidator::Acceptable; + + bool ok; + type val = input.toULongLong(&ok); + if (!ok) + return QValidator::Invalid; + + if (val < m_minimum || val > m_maximum) + return QValidator::Invalid; + + return QValidator::Acceptable; + } + + virtual type valueFromText(const QString& text) const + { + return text.toULongLong(); + } + + virtual QString textFromValue(type val) const + { + return QString::number(val); + } + // virtual void fixup(QString &str) const; + + virtual QAbstractSpinBox::StepEnabled stepEnabled() const + { + return StepUpEnabled | StepDownEnabled; + } + + public Q_SLOTS: + void setValue(type val) + { + if (m_value != val) + { + auto new_text = textFromValue(val); + lineEdit()->setText(new_text); + m_value = val; + // emit valueChanged(val); + emit valueChanged(new_text); + } + } + + void onEditFinished() + { + auto new_text = text(); + m_value = valueFromText(new_text); + // emit valueChanged(m_value); + emit valueChanged(new_text); + } + + Q_SIGNALS: + // void valueChanged(type v); + void valueChanged(const QString& v); + }; +} // namespace S2Plugin diff --git a/src/QtHelpers/DialogEditSimpleValue.cpp b/src/QtHelpers/DialogEditSimpleValue.cpp index cd1399e..444d07e 100644 --- a/src/QtHelpers/DialogEditSimpleValue.cpp +++ b/src/QtHelpers/DialogEditSimpleValue.cpp @@ -1,12 +1,10 @@ #include "QtHelpers/DialogEditSimpleValue.h" #include "Configuration.h" +#include "QtHelpers/LongLongSpinBox.h" #include "QtPlugin.h" -#include "pluginmain.h" -#include +#include "read_helpers.h" #include #include -#include -#include #include #include #include @@ -29,90 +27,108 @@ S2Plugin::DialogEditSimpleValue::DialogEditSimpleValue(const QString& fieldName, gridLayout->addWidget(new QLabel("New value (dec):", this), 1, 0); gridLayout->addWidget(new QLabel("New value (hex):", this), 2, 0); - mLineEditDecValue = new QLineEdit(this); mLineEditHexValue = new QLineEdit(this); mLineEditHexValue->setDisabled(true); - QObject::connect(mLineEditDecValue, &QLineEdit::textChanged, this, &DialogEditSimpleValue::decValueChanged); switch (mFieldType) { case MemoryFieldType::Byte: { - mLineEditDecValue->setValidator(new QIntValidator((std::numeric_limits::min)(), (std::numeric_limits::max)(), this)); - int8_t v = Script::Memory::ReadByte(mMemoryAddress); - mLineEditDecValue->setText(QString("%1").arg(v)); + auto spinBox = new QSpinBox(this); + QObject::connect(spinBox, static_cast(&QSpinBox::valueChanged), this, &DialogEditSimpleValue::decValueChanged); + spinBox->setRange(std::numeric_limits::min(), std::numeric_limits::max()); + auto v = Read(mMemoryAddress); + spinBox->setValue(v); + mSpinBox = spinBox; break; } case MemoryFieldType::UnsignedByte: { - mLineEditDecValue->setValidator(new QIntValidator((std::numeric_limits::min)(), (std::numeric_limits::max)(), this)); - uint8_t v = Script::Memory::ReadByte(mMemoryAddress); - mLineEditDecValue->setText(QString("%1").arg(v)); + auto spinBox = new QSpinBox(this); + QObject::connect(spinBox, static_cast(&QSpinBox::valueChanged), this, &DialogEditSimpleValue::decValueChanged); + spinBox->setRange(std::numeric_limits::min(), std::numeric_limits::max()); + auto v = Read(mMemoryAddress); + spinBox->setValue(v); + mSpinBox = spinBox; break; } case MemoryFieldType::Word: { - mLineEditDecValue->setValidator(new QIntValidator((std::numeric_limits::min)(), (std::numeric_limits::max)(), this)); - int16_t v = Script::Memory::ReadWord(mMemoryAddress); - mLineEditDecValue->setText(QString("%1").arg(v)); + auto spinBox = new QSpinBox(this); + QObject::connect(spinBox, static_cast(&QSpinBox::valueChanged), this, &DialogEditSimpleValue::decValueChanged); + spinBox->setRange(std::numeric_limits::min(), std::numeric_limits::max()); + auto v = Read(mMemoryAddress); + spinBox->setValue(v); + mSpinBox = spinBox; break; } - case MemoryFieldType::UTF16Char: case MemoryFieldType::UnsignedWord: { - mLineEditDecValue->setValidator(new QIntValidator((std::numeric_limits::min)(), (std::numeric_limits::max)(), this)); - uint16_t v = Script::Memory::ReadWord(mMemoryAddress); - mLineEditDecValue->setText(QString("%1").arg(v)); + auto spinBox = new QSpinBox(this); + QObject::connect(spinBox, static_cast(&QSpinBox::valueChanged), this, &DialogEditSimpleValue::decValueChanged); + spinBox->setRange(std::numeric_limits::min(), std::numeric_limits::max()); + auto v = Read(mMemoryAddress); + spinBox->setValue(v); + mSpinBox = spinBox; break; } case MemoryFieldType::Dword: { - mLineEditDecValue->setValidator(new QIntValidator((std::numeric_limits::min)(), (std::numeric_limits::max)(), this)); - int32_t v = Script::Memory::ReadDword(mMemoryAddress); - mLineEditDecValue->setText(QString("%1").arg(v)); + auto spinBox = new QSpinBox(this); + QObject::connect(spinBox, static_cast(&QSpinBox::valueChanged), this, &DialogEditSimpleValue::decValueChanged); + spinBox->setRange(std::numeric_limits::min(), std::numeric_limits::max()); + auto v = Read(mMemoryAddress); + spinBox->setValue(v); + mSpinBox = spinBox; break; } case MemoryFieldType::UnsignedDword: case MemoryFieldType::StringsTableID: { - // TODO: test why is this commented out? - // mLineEditDecValue->setValidator(new QIntValidator((std::numeric_limits::min)(), (std::numeric_limits::max)(), this)); - uint32_t v = Script::Memory::ReadDword(mMemoryAddress); - mLineEditDecValue->setText(QString("%1").arg(v)); + // no point of making SpinBox for uint32_t type, we can just use bigger type + auto spinBox = new ULongLongSpinBox(this); + QObject::connect(spinBox, &ULongLongSpinBox::valueChanged, this, &DialogEditSimpleValue::decValueChanged); + spinBox->setRange(std::numeric_limits::min(), std::numeric_limits::max()); + auto v = Read(mMemoryAddress); + spinBox->setValue(v); + mSpinBox = spinBox; break; } case MemoryFieldType::Qword: { - // mLineEditDecValue->setValidator(new QIntValidator((std::numeric_limits::min)(), (std::numeric_limits::max)(), this)); - int64_t v = Script::Memory::ReadQword(mMemoryAddress); - mLineEditDecValue->setText(QString("%1").arg(v)); + auto spinBox = new LongLongSpinBox(this); + QObject::connect(spinBox, &LongLongSpinBox::valueChanged, this, &DialogEditSimpleValue::decValueChanged); + // spinBox->setRange(std::numeric_limits::min(), std::numeric_limits::max()); + auto v = Read(mMemoryAddress); + spinBox->setValue(v); + mSpinBox = spinBox; break; } case MemoryFieldType::UnsignedQword: { - // mLineEditDecValue->setValidator(new QIntValidator((std::numeric_limits::min)(), (std::numeric_limits::max)(), this)); - uint64_t v = Script::Memory::ReadQword(mMemoryAddress); - mLineEditDecValue->setText(QString("%1").arg(v)); + auto spinBox = new ULongLongSpinBox(this); + QObject::connect(spinBox, &ULongLongSpinBox::valueChanged, this, &DialogEditSimpleValue::decValueChanged); + // spinBox->setRange(std::numeric_limits::min(), std::numeric_limits::max()); + auto v = Read(mMemoryAddress); + spinBox->setValue(v); + mSpinBox = spinBox; break; } case MemoryFieldType::Float: - { - mLineEditDecValue->setValidator(new QDoubleValidator((std::numeric_limits::max)() * -1, (std::numeric_limits::max)(), 1000, this)); - uint32_t tmp = Script::Memory::ReadDword(mMemoryAddress); - float v = reinterpret_cast(tmp); - mLineEditDecValue->setText(QString("%1").arg(v)); - break; - } case MemoryFieldType::Double: { - mLineEditDecValue->setValidator(new QDoubleValidator((std::numeric_limits::max)() * -1, (std::numeric_limits::max)(), 1000, this)); + auto spinbox = new QDoubleSpinBox(this); + mSpinBox = spinbox; + QObject::connect(spinbox, static_cast(&QDoubleSpinBox::valueChanged), this, &DialogEditSimpleValue::decValueChanged); size_t tmp = Script::Memory::ReadQword(mMemoryAddress); double v = reinterpret_cast(tmp); - mLineEditDecValue->setText(QString("%1").arg(v)); + spinbox->setValue(v); break; } } - gridLayout->addWidget(mLineEditDecValue, 1, 1); + decValueChanged(mSpinBox->text()); + + gridLayout->addWidget(mSpinBox, 1, 1); gridLayout->addWidget(mLineEditHexValue, 2, 1); // BUTTONS @@ -133,8 +149,7 @@ S2Plugin::DialogEditSimpleValue::DialogEditSimpleValue(const QString& fieldName, layout->addStretch(); layout->addLayout(buttonLayout); - mLineEditDecValue->setFocus(); - mLineEditDecValue->selectAll(); + mSpinBox->setFocus(); } QSize S2Plugin::DialogEditSimpleValue::minimumSizeHint() const @@ -155,141 +170,160 @@ void S2Plugin::DialogEditSimpleValue::cancelBtnClicked() void S2Plugin::DialogEditSimpleValue::changeBtnClicked() { + switch (mFieldType) { case MemoryFieldType::Byte: - { - int8_t v = mLineEditDecValue->text().toInt(); - Script::Memory::WriteByte(mMemoryAddress, v); - break; - } case MemoryFieldType::UnsignedByte: { - uint8_t v = mLineEditDecValue->text().toInt(); - Script::Memory::WriteByte(mMemoryAddress, v); + int v = 0; + auto obj = qobject_cast(mSpinBox); + if (obj) // probably not needed but just in case + v = obj->value(); + + Script::Memory::WriteByte(mMemoryAddress, static_cast(v)); break; } case MemoryFieldType::Word: - { - int16_t v = mLineEditDecValue->text().toShort(); - Script::Memory::WriteWord(mMemoryAddress, v); - break; - } case MemoryFieldType::UnsignedWord: - case MemoryFieldType::UTF16Char: { - uint16_t v = mLineEditDecValue->text().toUShort(); - Script::Memory::WriteWord(mMemoryAddress, v); + int v = 0; + auto obj = qobject_cast(mSpinBox); + if (obj) // probably not needed but just in case + v = obj->value(); + + Script::Memory::WriteByte(mMemoryAddress, static_cast(v)); break; } case MemoryFieldType::Dword: { - int32_t v = mLineEditDecValue->text().toLong(); - Script::Memory::WriteDword(mMemoryAddress, v); + int v = 0; + auto obj = qobject_cast(mSpinBox); + if (obj) // probably not needed but just in case + v = obj->value(); + + Script::Memory::WriteByte(mMemoryAddress, static_cast(v)); break; } case MemoryFieldType::UnsignedDword: case MemoryFieldType::StringsTableID: { - uint32_t v = mLineEditDecValue->text().toULong(); + int v = 0; + auto obj = qobject_cast(mSpinBox); + if (obj) // probably not needed but just in case + v = obj->value(); + Script::Memory::WriteDword(mMemoryAddress, v); break; } case MemoryFieldType::Qword: { - int64_t v = mLineEditDecValue->text().toLongLong(); + int64_t v = 0; + auto obj = qobject_cast(mSpinBox); + if (obj) // probably not needed but just in case + v = obj->value(); + Script::Memory::WriteQword(mMemoryAddress, v); break; } case MemoryFieldType::UnsignedQword: { - uint64_t v = mLineEditDecValue->text().toULongLong(); + uint64_t v = 0; + auto obj = qobject_cast(mSpinBox); + if (obj) // probably not needed but just in case + v = obj->value(); + Script::Memory::WriteQword(mMemoryAddress, v); break; } case MemoryFieldType::Float: { - float v = mLineEditDecValue->text().toFloat(); - uint32_t tmp = reinterpret_cast(v); - Script::Memory::WriteDword(mMemoryAddress, tmp); + float v = 0; + auto obj = qobject_cast(mSpinBox); + if (obj) // probably not needed but just in case + v = obj->value(); + + Script::Memory::WriteDword(mMemoryAddress, reinterpret_cast(v)); break; } case MemoryFieldType::Double: { - double v = mLineEditDecValue->text().toDouble(); - size_t tmp = reinterpret_cast(v); - Script::Memory::WriteQword(mMemoryAddress, tmp); + double v = 0; + auto obj = qobject_cast(mSpinBox); + if (obj) // probably not needed but just in case + v = obj->value(); + + Script::Memory::WriteQword(mMemoryAddress, reinterpret_cast(v)); break; } } accept(); } -void S2Plugin::DialogEditSimpleValue::decValueChanged() +void S2Plugin::DialogEditSimpleValue::decValueChanged(const QString& text) { std::stringstream ss; switch (mFieldType) { case MemoryFieldType::Byte: { - int8_t v = mLineEditDecValue->text().toInt(); + int8_t v = text.toInt(); ss << QString::asprintf("0x%02x", static_cast(v)).toStdString(); break; } case MemoryFieldType::UnsignedByte: { - uint8_t v = mLineEditDecValue->text().toInt(); + uint8_t v = text.toInt(); ss << "0x" << std::hex << std::setw(2) << std::setfill('0') << static_cast(v); break; } case MemoryFieldType::Word: { - int16_t v = mLineEditDecValue->text().toShort(); + int16_t v = text.toShort(); ss << "0x" << std::hex << std::setw(4) << std::setfill('0') << v; break; } case MemoryFieldType::UnsignedWord: - case MemoryFieldType::UTF16Char: { - uint16_t v = mLineEditDecValue->text().toUShort(); + uint16_t v = text.toUShort(); ss << "0x" << std::hex << std::setw(4) << std::setfill('0') << v; break; } case MemoryFieldType::Dword: { - int32_t v = mLineEditDecValue->text().toLong(); + int32_t v = text.toLong(); ss << "0x" << std::hex << std::setw(8) << std::setfill('0') << v; break; } case MemoryFieldType::UnsignedDword: case MemoryFieldType::StringsTableID: { - uint32_t v = mLineEditDecValue->text().toULong(); + uint32_t v = text.toULong(); ss << "0x" << std::hex << std::setw(8) << std::setfill('0') << v; break; } case MemoryFieldType::Qword: { - int64_t v = mLineEditDecValue->text().toLongLong(); + int64_t v = text.toLongLong(); ss << "0x" << std::hex << std::setw(16) << std::setfill('0') << v; break; } case MemoryFieldType::UnsignedQword: { - uint64_t v = mLineEditDecValue->text().toULongLong(); + uint64_t v = text.toULongLong(); ss << "0x" << std::hex << std::setw(16) << std::setfill('0') << v; break; } case MemoryFieldType::Float: { - float v = mLineEditDecValue->text().toFloat(); + float v = text.toFloat(); uint32_t tmp = reinterpret_cast(v); ss << "0x" << std::hex << std::setw(8) << std::setfill('0') << tmp; break; } case MemoryFieldType::Double: { - double v = mLineEditDecValue->text().toDouble(); + double v = text.toDouble(); size_t tmp = reinterpret_cast(v); ss << "0x" << std::hex << std::setw(16) << std::setfill('0') << tmp; break;