diff --git a/Source/Core/DolphinQt/Debugger/MemoryWidget.cpp b/Source/Core/DolphinQt/Debugger/MemoryWidget.cpp index 4739cf5474..a2f9c9bc03 100644 --- a/Source/Core/DolphinQt/Debugger/MemoryWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/MemoryWidget.cpp @@ -7,8 +7,11 @@ #include #include +#include + #include #include +#include #include #include #include @@ -22,6 +25,7 @@ #include #include +#include "Common/BitUtils.h" #include "Common/FileUtil.h" #include "Common/IOFile.h" #include "Core/ConfigManager.h" @@ -92,12 +96,11 @@ void MemoryWidget::CreateWidgets() m_search_address = new QLineEdit; m_search_offset = new QLineEdit; - m_data_edit = new QLineEdit; - m_set_value = new QPushButton(tr("Set &Value")); + m_search_address->setMaxLength(8); + m_search_offset->setMaxLength(9); m_search_address->setPlaceholderText(tr("Search Address")); m_search_offset->setPlaceholderText(tr("Offset")); - m_data_edit->setPlaceholderText(tr("Value")); m_address_splitter->addWidget(m_search_address); m_address_splitter->addWidget(m_search_offset); @@ -105,11 +108,34 @@ void MemoryWidget::CreateWidgets() m_address_splitter->setCollapsible(0, false); m_address_splitter->setStretchFactor(1, 2); - auto* search_type_group = new QButtonGroup(this); - m_find_ascii = new QRadioButton(tr("ASCII")); - m_find_hex = new QRadioButton(tr("Hex string")); - search_type_group->addButton(m_find_ascii); - search_type_group->addButton(m_find_hex); + auto* input_layout = new QHBoxLayout; + m_data_edit = new QLineEdit; + m_base_check = new QCheckBox(tr("Hex")); + m_set_value = new QPushButton(tr("Set &Value")); + m_data_preview = new QLabel; + + m_base_check->setLayoutDirection(Qt::RightToLeft); + m_data_edit->setPlaceholderText(tr("Value")); + m_data_preview->setBackgroundRole(QPalette::AlternateBase); + m_data_preview->setAutoFillBackground(true); + + input_layout->addWidget(m_data_edit); + input_layout->addWidget(m_base_check); + + // Input types + m_input_combo = new QComboBox; + m_input_combo->setMaxVisibleItems(20); + // Order here determines combo list order. + m_input_combo->addItem(tr("Hex Byte String"), int(InputID::HEXSTR)); + m_input_combo->addItem(tr("ASCII"), int(InputID::ASCII)); + m_input_combo->addItem(tr("Float"), int(InputID::FLOAT)); + m_input_combo->addItem(tr("Double"), int(InputID::DOUBLE)); + m_input_combo->addItem(tr("Unsigned 8"), int(InputID::U8)); + m_input_combo->addItem(tr("Unsigned 16"), int(InputID::U16)); + m_input_combo->addItem(tr("Unsigned 32"), int(InputID::U32)); + m_input_combo->addItem(tr("Signed 8"), int(InputID::S8)); + m_input_combo->addItem(tr("Signed 16"), int(InputID::S16)); + m_input_combo->addItem(tr("Signed 32"), int(InputID::S32)); // Dump m_dump_mram = new QPushButton(tr("Dump &MRAM")); @@ -197,19 +223,11 @@ void MemoryWidget::CreateWidgets() auto* sidebar = new QWidget; auto* sidebar_layout = new QVBoxLayout; sidebar_layout->setSpacing(1); - sidebar->setLayout(sidebar_layout); - sidebar_layout->addWidget(m_address_splitter); - sidebar_layout->addWidget(m_data_edit); - - auto* types_layout = new QHBoxLayout; - types_layout->addWidget(m_find_ascii); - types_layout->addItem(new QSpacerItem(20, 1)); - types_layout->addWidget(m_find_hex); - types_layout->setAlignment(Qt::AlignCenter); - sidebar_layout->addLayout(types_layout); - + sidebar_layout->addLayout(input_layout); + sidebar_layout->addWidget(m_input_combo); + sidebar_layout->addWidget(m_data_preview); sidebar_layout->addWidget(m_set_value); sidebar_layout->addItem(new QSpacerItem(1, 20)); sidebar_layout->addWidget(m_dump_mram); @@ -247,10 +265,10 @@ void MemoryWidget::ConnectWidgets() { connect(m_search_address, &QLineEdit::textChanged, this, &MemoryWidget::OnSearchAddress); connect(m_search_offset, &QLineEdit::textChanged, this, &MemoryWidget::OnSearchAddress); + connect(m_data_edit, &QLineEdit::textChanged, this, &MemoryWidget::ValidateAndPreviewInputValue); - connect(m_data_edit, &QLineEdit::textChanged, this, &MemoryWidget::ValidateSearchValue); - connect(m_find_ascii, &QRadioButton::toggled, this, &MemoryWidget::ValidateSearchValue); - connect(m_find_hex, &QRadioButton::toggled, this, &MemoryWidget::ValidateSearchValue); + connect(m_input_combo, qOverload(&QComboBox::currentIndexChanged), this, + &MemoryWidget::ValidateAndPreviewInputValue); connect(m_set_value, &QPushButton::clicked, this, &MemoryWidget::OnSetValue); @@ -274,6 +292,7 @@ void MemoryWidget::ConnectWidgets() for (auto* radio : {m_bp_read_write, m_bp_read_only, m_bp_write_only}) connect(radio, &QRadioButton::toggled, this, &MemoryWidget::OnBPTypeChanged); + connect(m_base_check, &QCheckBox::toggled, this, &MemoryWidget::ValidateAndPreviewInputValue); connect(m_bp_log_check, &QCheckBox::toggled, this, &MemoryWidget::OnBPLogChanged); connect(m_memory_view, &MemoryViewWidget::BreakpointsChanged, this, &MemoryWidget::BreakpointsChanged); @@ -304,12 +323,9 @@ void MemoryWidget::LoadSettings() { QSettings& settings = Settings::GetQSettings(); - const bool search_ascii = - settings.value(QStringLiteral("memorywidget/searchascii"), true).toBool(); - const bool search_hex = settings.value(QStringLiteral("memorywidget/searchhex"), false).toBool(); + const int combo_index = settings.value(QStringLiteral("memorywidget/inputcombo"), 1).toInt(); - m_find_ascii->setChecked(search_ascii); - m_find_hex->setChecked(search_hex); + m_input_combo->setCurrentIndex(combo_index); const bool address_space_effective = settings.value(QStringLiteral("memorywidget/addrspace_effective"), true).toBool(); @@ -356,8 +372,7 @@ void MemoryWidget::SaveSettings() { QSettings& settings = Settings::GetQSettings(); - settings.setValue(QStringLiteral("memorywidget/searchascii"), m_find_ascii->isChecked()); - settings.setValue(QStringLiteral("memorywidget/searchhex"), m_find_hex->isChecked()); + settings.setValue(QStringLiteral("memorywidget/inputcombo"), m_input_combo->currentIndex()); settings.setValue(QStringLiteral("memorywidget/addrspace_effective"), m_address_space_effective->isChecked()); @@ -409,6 +424,7 @@ void MemoryWidget::OnTypeChanged() else type = MemoryViewWidget::Type::Float32; + ValidateAndPreviewInputValue(); m_memory_view->SetType(type); SaveSettings(); @@ -476,12 +492,138 @@ void MemoryWidget::OnSearchAddress() m_search_offset->setPalette(offset_palette); } -void MemoryWidget::ValidateSearchValue() +void MemoryWidget::ValidateAndPreviewInputValue() { + m_data_preview->clear(); + QString input_text = m_data_edit->text(); + const auto combo_id = static_cast(m_input_combo->currentData().toInt()); + + m_base_check->setEnabled(combo_id == InputID::U32 || combo_id == InputID::S32 || + combo_id == InputID::U16 || combo_id == InputID::S16 || + combo_id == InputID::U8 || combo_id == InputID::S8); + + if (input_text.isEmpty()) + return; + + // Remove any spaces + if (combo_id != InputID::ASCII) + input_text.remove(QLatin1Char(' ')); + QFont font; QPalette palette; + QString hex_string; + bool good = false; + const int radix = (m_base_check->isChecked() && m_base_check->isEnabled()) ? 16 : 0; - if (!IsValueValid()) + switch (combo_id) + { + case InputID::ASCII: + { + good = true; + const QByteArray bytes = input_text.toLatin1(); + hex_string = QString::fromLatin1(bytes.toHex()); + break; + } + case InputID::FLOAT: + { + const float value_float = input_text.toFloat(&good); + + if (good) + { + const u32 hex_out = Common::BitCast(value_float); + hex_string = QString::fromStdString(fmt::format("{:X}", hex_out)); + } + break; + } + case InputID::DOUBLE: + { + const double value_double = input_text.toDouble(&good); + + if (good) + { + const u64 hex_out = Common::BitCast(value_double); + hex_string = QString::fromStdString(fmt::format("{:X}", hex_out)); + } + break; + } + case InputID::S8: + { + const short value = input_text.toShort(&good, radix); + good &= std::numeric_limits::min() <= value && + value <= std::numeric_limits::max(); + if (good) + hex_string = QString::fromStdString(fmt::sprintf("%02hhX", value)); + break; + } + case InputID::S16: + { + const short value = input_text.toShort(&good, radix); + if (good) + hex_string = QString::fromStdString(fmt::sprintf("%04hX", value)); + break; + } + case InputID::S32: + { + const int value_int = input_text.toInt(&good, radix); + if (good) + hex_string = QString::fromStdString(fmt::sprintf("%08X", value_int)); + break; + } + case InputID::U8: + { + const unsigned short value = input_text.toUShort(&good, radix); + good &= (value & 0xFF00) == 0; + if (good) + hex_string = QString::fromStdString(fmt::format("{:02X}", value)); + break; + } + case InputID::U16: + { + const unsigned short value = input_text.toUShort(&good, radix); + if (good) + hex_string = QString::fromStdString(fmt::format("{:04X}", value)); + break; + } + case InputID::U32: + { + const u32 value = input_text.toUInt(&good, radix); + if (good) + hex_string = QString::fromStdString(fmt::format("{:08X}", value)); + break; + } + case InputID::HEXSTR: + { + // Confirm it is only hex bytes + const QRegularExpression is_hex(QStringLiteral("^([0-9A-F]{2})*$"), + QRegularExpression::CaseInsensitiveOption); + const QRegularExpressionMatch match = is_hex.match(input_text); + good = match.hasMatch(); + if (good) + { + const QByteArray hbytes = QByteArray::fromHex(input_text.toUtf8()); + hex_string = QString::fromLatin1(hbytes.toHex()); + } + break; + } + } + + if (good) + { + int output_length = hex_string.length(); + + if (output_length > 16) + { + hex_string.truncate(16); + output_length = hex_string.length(); + hex_string.append(QStringLiteral("...")); + } + + for (int i = 2; i < output_length; i += 2) + hex_string.insert(output_length - i, QLatin1Char{' '}); + + m_data_preview->setText(hex_string); + } + else { font.setBold(true); palette.setColor(QPalette::Text, Qt::red); @@ -491,6 +633,26 @@ void MemoryWidget::ValidateSearchValue() m_data_edit->setPalette(palette); } +QByteArray MemoryWidget::GetInputData() const +{ + // Empty or invalid input data returns an empty array. + if (m_data_preview->text().isEmpty()) + return QByteArray(); + + const auto combo_id = static_cast(m_input_combo->currentData().toInt()); + + // Ascii might be truncated, pull from data edit box. + if (combo_id == InputID::ASCII) + return QByteArray(m_data_edit->text().toUtf8()); + + // If we are doing a large aray of hex bytes + if (combo_id == InputID::HEXSTR) + return QByteArray::fromHex(m_data_edit->text().toUtf8()); + + // Data preview has exactly what we want to input, so pull it from there. + return QByteArray::fromHex(m_data_preview->text().toUtf8()); +} + void MemoryWidget::OnSetValue() { if (!Core::IsRunning()) @@ -510,13 +672,10 @@ void MemoryWidget::OnSetValue() return; } - if (m_data_edit->text().isEmpty()) - { - ModalMessageBox::critical(this, tr("Error"), tr("No value provided.")); - return; - } + const QByteArray bytes = GetInputData(); - if (!IsValueValid()) + // Invalid input will give an empty array. + if (bytes.isEmpty()) { ModalMessageBox::critical(this, tr("Error"), tr("Bad value provided.")); return; @@ -524,7 +683,6 @@ void MemoryWidget::OnSetValue() AddressSpace::Accessors* accessors = AddressSpace::GetAccessors(m_memory_view->GetAddressSpace()); - const QByteArray bytes = GetValueData(); for (const char c : bytes) accessors->WriteU8(target_addr.address++, static_cast(c)); @@ -582,29 +740,6 @@ void MemoryWidget::OnDumpFakeVMEM() std::distance(accessors->begin(), accessors->end())); } -bool MemoryWidget::IsValueValid() const -{ - if (m_find_ascii->isChecked()) - return true; - const QRegularExpression is_hex(QStringLiteral("^([0-9A-F]{2})*$"), - QRegularExpression::CaseInsensitiveOption); - const QRegularExpressionMatch match = is_hex.match(m_data_edit->text()); - return match.hasMatch(); -} - -QByteArray MemoryWidget::GetValueData() const -{ - if (!IsValueValid()) - return QByteArray(); - - const QByteArray value = m_data_edit->text().toUtf8(); - - if (m_find_ascii->isChecked()) - return value; - - return QByteArray::fromHex(value); -} - MemoryWidget::TargetAddress MemoryWidget::GetTargetAddress() const { TargetAddress target; @@ -646,20 +781,20 @@ void MemoryWidget::FindValue(bool next) return; } + const QByteArray search_for = GetInputData(); + + if (search_for.isEmpty()) + { + m_result_label->setText(tr("Bad Value Given")); + return; + } + if (!m_search_address->text().isEmpty()) { // skip the quoted address so we don't potentially refind the last result target_addr.address += next ? 1 : -1; } - const QByteArray search_for = GetValueData(); - - if (search_for.isEmpty()) - { - m_result_label->setText(tr("No value provided.")); - return; - } - AddressSpace::Accessors* accessors = AddressSpace::GetAccessors(m_memory_view->GetAddressSpace()); const auto found_addr = diff --git a/Source/Core/DolphinQt/Debugger/MemoryWidget.h b/Source/Core/DolphinQt/Debugger/MemoryWidget.h index 084499af68..6ae3b85543 100644 --- a/Source/Core/DolphinQt/Debugger/MemoryWidget.h +++ b/Source/Core/DolphinQt/Debugger/MemoryWidget.h @@ -12,6 +12,7 @@ class MemoryViewWidget; class QCheckBox; +class QComboBox; class QLabel; class QLineEdit; class QPushButton; @@ -19,6 +20,21 @@ class QRadioButton; class QShowEvent; class QSplitter; +enum class InputID : int +{ + // Order does not matter here. + S8 = 1, + S16, + S32, + U8, + U16, + U32, + HEXSTR, + FLOAT, + DOUBLE, + ASCII +}; + class MemoryWidget : public QDockWidget { Q_OBJECT @@ -55,7 +71,6 @@ private: void OnSearchAddress(); void OnFindNextValue(); void OnFindPreviousValue(); - void ValidateSearchValue(); void OnSetValue(); @@ -64,10 +79,9 @@ private: void OnDumpARAM(); void OnDumpFakeVMEM(); - bool IsValueValid() const; - QByteArray GetValueData() const; + void ValidateAndPreviewInputValue(); + QByteArray GetInputData() const; TargetAddress GetTargetAddress() const; - void FindValue(bool next); void closeEvent(QCloseEvent*) override; @@ -78,6 +92,8 @@ private: QLineEdit* m_search_address; QLineEdit* m_search_offset; QLineEdit* m_data_edit; + QCheckBox* m_base_check; + QLabel* m_data_preview; QPushButton* m_set_value; QPushButton* m_dump_mram; QPushButton* m_dump_exram; @@ -87,8 +103,7 @@ private: // Search QPushButton* m_find_next; QPushButton* m_find_previous; - QRadioButton* m_find_ascii; - QRadioButton* m_find_hex; + QComboBox* m_input_combo; QLabel* m_result_label; // Address Spaces