From bd032bbcb800ab8db1fb21b1a9d233c62d9ebc69 Mon Sep 17 00:00:00 2001 From: Dan McCarthy <dan.mccarthy.software@gmail.com> Date: Sun, 11 Feb 2024 17:53:12 -0600 Subject: [PATCH] Debugger: Memory search expansions + results count Adds memory search comparisons for Increased, Increased By, Decreased, Decreased By, Changed, Not Changed, Changed By. For arrays, adds not equals, changed, not changed for filter searches. Now only shows the comparison types that are currently valid for the given search type and if there's prior search results. Also refactors to allow holding the prior set of search results rather than just the addresses, needed for these search comparisons to work. Also adds a ui label to show that the debugger is searching after clicking the search button which then gets replaced with the results count when the search completes. --- pcsx2-qt/Debugger/CpuWidget.cpp | 1 - pcsx2-qt/Debugger/CpuWidget.h | 4 - pcsx2-qt/Debugger/CpuWidget.ui | 2 +- pcsx2-qt/Debugger/DisassemblyWidget.cpp | 7 - pcsx2-qt/Debugger/MemorySearchWidget.cpp | 351 ++++++++++++++++++----- pcsx2-qt/Debugger/MemorySearchWidget.h | 95 +++++- pcsx2-qt/Debugger/MemorySearchWidget.ui | 20 +- 7 files changed, 375 insertions(+), 105 deletions(-) diff --git a/pcsx2-qt/Debugger/CpuWidget.cpp b/pcsx2-qt/Debugger/CpuWidget.cpp index e703fffffeaf7..9d9e1ec8a7e95 100644 --- a/pcsx2-qt/Debugger/CpuWidget.cpp +++ b/pcsx2-qt/Debugger/CpuWidget.cpp @@ -33,7 +33,6 @@ using namespace QtUtils; using namespace MipsStackWalk; - CpuWidget::CpuWidget(QWidget* parent, DebugInterface& cpu) : m_cpu(cpu) , m_bpModel(cpu) diff --git a/pcsx2-qt/Debugger/CpuWidget.h b/pcsx2-qt/Debugger/CpuWidget.h index 828e4d9a92d5d..7f09a19eebb97 100644 --- a/pcsx2-qt/Debugger/CpuWidget.h +++ b/pcsx2-qt/Debugger/CpuWidget.h @@ -33,9 +33,6 @@ class CpuWidget final : public QWidget CpuWidget(QWidget* parent, DebugInterface& cpu); ~CpuWidget(); - - // Note: The order of these enum values must reflect the order in thee Search Comparison combobox. - public slots: void paintEvent(QPaintEvent* event); @@ -92,7 +89,6 @@ public slots: m_ui.memoryviewWidget->update(); }; - void saveBreakpointsToDebuggerSettings(); void saveSavedAddressesToDebuggerSettings(); diff --git a/pcsx2-qt/Debugger/CpuWidget.ui b/pcsx2-qt/Debugger/CpuWidget.ui index 5389105ad14ee..c5ad30f83c4c1 100644 --- a/pcsx2-qt/Debugger/CpuWidget.ui +++ b/pcsx2-qt/Debugger/CpuWidget.ui @@ -180,7 +180,7 @@ <attribute name="title"> <string>Memory Search</string> </attribute> - <layout class="QHBoxLayout" name="horizontalLayout_6"> + <layout class="QHBoxLayout" name="horizontalLayout_10"> <property name="spacing"> <number>0</number> </property> diff --git a/pcsx2-qt/Debugger/DisassemblyWidget.cpp b/pcsx2-qt/Debugger/DisassemblyWidget.cpp index cb2d6c9fabfaa..485a1fdc4da9f 100644 --- a/pcsx2-qt/Debugger/DisassemblyWidget.cpp +++ b/pcsx2-qt/Debugger/DisassemblyWidget.cpp @@ -410,7 +410,6 @@ void DisassemblyWidget::paintEvent(QPaintEvent* event) std::vector<BranchLine> branchLines = m_disassemblyManager.getBranchLines(m_visibleStart, visibleEnd - m_visibleStart); s32 branchCount = 0; - s32 skippedBranches = 0; for (const auto& branchLine : branchLines) { if (branchCount == (m_showInstructionOpcode ? 3 : 5)) @@ -453,12 +452,6 @@ void DisassemblyWidget::paintEvent(QPaintEvent* event) bottom = (((branchLine.second - m_visibleStart) / 4) * m_rowHeight) + (m_rowHeight / 2); } - if ((top < 0 && bottom < 0) || (top > winBottom && bottom > winBottom) || (top < 0 && bottom > winBottom) || (top > winBottom && bottom < 0)) - { - skippedBranches++; - continue; - } - branchCount++; if (branchLine.first == m_selectedAddressStart || branchLine.second == m_selectedAddressStart) diff --git a/pcsx2-qt/Debugger/MemorySearchWidget.cpp b/pcsx2-qt/Debugger/MemorySearchWidget.cpp index d3b0b71c4f4cb..324204e7b0cd5 100644 --- a/pcsx2-qt/Debugger/MemorySearchWidget.cpp +++ b/pcsx2-qt/Debugger/MemorySearchWidget.cpp @@ -19,6 +19,8 @@ using SearchComparison = MemorySearchWidget::SearchComparison; using SearchType = MemorySearchWidget::SearchType; +using SearchResult = MemorySearchWidget::SearchResult; +using SearchResults = QMap<u32, MemorySearchWidget::SearchResult>; using namespace QtUtils; @@ -31,19 +33,14 @@ MemorySearchWidget::MemorySearchWidget(QWidget* parent) m_ui.listSearchResults->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_ui.btnSearch, &QPushButton::clicked, this, &MemorySearchWidget::onSearchButtonClicked); connect(m_ui.btnFilterSearch, &QPushButton::clicked, this, &MemorySearchWidget::onSearchButtonClicked); - connect(m_ui.listSearchResults, &QListWidget::itemDoubleClicked, [this](QListWidgetItem* item) // move back to cpu widget + connect(m_ui.listSearchResults, &QListWidget::itemDoubleClicked, [this](QListWidgetItem* item) { emit switchToMemoryViewTab(); emit goToAddressInMemoryView(item->text().toUInt(nullptr, 16)); }); connect(m_ui.listSearchResults->verticalScrollBar(), &QScrollBar::valueChanged, this, &MemorySearchWidget::onSearchResultsListScroll); connect(m_ui.listSearchResults, &QListView::customContextMenuRequested, this, &MemorySearchWidget::onListSearchResultsContextMenu); - connect(m_ui.cmbSearchType, &QComboBox::currentIndexChanged, [this](int i) { - if (i < 4) - m_ui.chkSearchHex->setEnabled(true); - else - m_ui.chkSearchHex->setEnabled(false); - }); + connect(m_ui.cmbSearchType, &QComboBox::currentIndexChanged, this, &MemorySearchWidget::onSearchTypeChanged); // Ensures we don't retrigger the load results function unintentionally m_resultsLoadTimer.setInterval(100); @@ -74,10 +71,8 @@ void MemorySearchWidget::contextRemoveSearchResult() const int selectedResultIndex = m_ui.listSearchResults->row(m_ui.listSearchResults->selectedItems().first()); const auto* rowToRemove = m_ui.listSearchResults->takeItem(selectedResultIndex); - if (m_searchResults.size() > static_cast<size_t>(selectedResultIndex) && m_searchResults.at(selectedResultIndex) == rowToRemove->data(Qt::UserRole).toUInt()) - { - m_searchResults.erase(m_searchResults.begin() + selectedResultIndex); - } + u32 address = rowToRemove->data(Qt::UserRole).toUInt(); + m_searchResultsMap.remove(address); delete rowToRemove; } @@ -123,9 +118,22 @@ void MemorySearchWidget::onListSearchResultsContextMenu(QPoint pos) contextMenu->popup(m_ui.listSearchResults->viewport()->mapToGlobal(pos)); } +template<typename T> +T readValueAtAddress(DebugInterface* cpu, u32 addr); +template<> +float readValueAtAddress<float>(DebugInterface* cpu, u32 addr) +{ + return std::bit_cast<float>(cpu->read32(addr)); +} + +template<> +double readValueAtAddress<double>(DebugInterface* cpu, u32 addr) +{ + return std::bit_cast<double>(cpu->read64(addr)); +} template <typename T> -static T readValueAtAddress(DebugInterface* cpu, u32 addr) +T readValueAtAddress(DebugInterface* cpu, u32 addr) { T val = 0; switch (sizeof(T)) @@ -164,15 +172,13 @@ static bool memoryValueComparator(SearchComparison searchComparison, T searchVal { const T fTop = searchValue + 0.00001f; const T fBottom = searchValue - 0.00001f; - const T memValue = std::bit_cast<float, u32>(readValue); - areValuesEqual = (fBottom < memValue && memValue < fTop); + areValuesEqual = (fBottom < readValue && readValue < fTop); } else if constexpr (std::is_same_v<T, double>) { const double dTop = searchValue + 0.00001f; const double dBottom = searchValue - 0.00001f; - const double memValue = std::bit_cast<double, u64>(readValue); - areValuesEqual = (dBottom < memValue && memValue < dTop); + areValuesEqual = (dBottom < readValue && readValue < dTop); } else { @@ -195,18 +201,16 @@ static bool memoryValueComparator(SearchComparison searchComparison, T searchVal { const T fTop = searchValue + 0.00001f; const T fBottom = searchValue - 0.00001f; - const T memValue = std::bit_cast<float, u32>(readValue); - const bool isGreater = memValue > fTop; - const bool isLesser = memValue < fBottom; + const bool isGreater = readValue > fTop; + const bool isLesser = readValue < fBottom; return isGreaterOperator ? isGreater : isLesser; } else if (std::is_same_v<T, double>) { const double dTop = searchValue + 0.00001f; const double dBottom = searchValue - 0.00001f; - const double memValue = std::bit_cast<double, u64>(readValue); - const bool isGreater = memValue > dTop; - const bool isLesser = memValue < dBottom; + const bool isGreater = readValue > dTop; + const bool isLesser = readValue < dBottom; return isGreaterOperator ? isGreater : isLesser; } @@ -218,38 +222,104 @@ static bool memoryValueComparator(SearchComparison searchComparison, T searchVal } } +// Handles the comparison of the read value against either the search value, or if existing searchResults are available, the value at the same address in the searchResultsMap template <typename T> -std::vector<u32> searchWorker(DebugInterface* cpu, std::vector<u32> searchAddresses, SearchComparison searchComparison, u32 start, u32 end, T searchValue) +bool handleSearchComparison(SearchComparison searchComparison, u32 searchAddress, SearchResults searchResults, T searchValue, T readValue) { - std::vector<u32> hitAddresses; - const bool isSearchingRange = searchAddresses.size() <= 0; + const bool isNotOperator = searchComparison == SearchComparison::NotEquals || searchComparison == SearchComparison::NotChanged; + switch (searchComparison) + { + case SearchComparison::Equals: + case SearchComparison::NotEquals: + case SearchComparison::GreaterThan: + case SearchComparison::GreaterThanOrEqual: + case SearchComparison::LessThan: + case SearchComparison::LessThanOrEqual: + { + return memoryValueComparator(searchComparison, searchValue, readValue); + break; + } + case SearchComparison::Increased: + { + const T priorValue = searchResults.value(searchAddress).getValue<T>(); + return memoryValueComparator(SearchComparison::GreaterThan, priorValue, readValue); + break; + } + case SearchComparison::IncreasedBy: + { + + const T priorValue = searchResults.value(searchAddress).getValue<T>(); + const T expectedIncrease = searchValue + priorValue; + return memoryValueComparator(SearchComparison::Equals, readValue, expectedIncrease); + break; + } + case SearchComparison::Decreased: + { + const T priorValue = searchResults.value(searchAddress).getValue<T>(); + return memoryValueComparator(SearchComparison::LessThan, priorValue, readValue); + break; + } + case SearchComparison::DecreasedBy: + { + const T priorValue = searchResults.value(searchAddress).getValue<T>(); + const T expectedDecrease = priorValue - searchValue; + return memoryValueComparator(SearchComparison::Equals, readValue, expectedDecrease); + break; + } + case SearchComparison::Changed: + case SearchComparison::NotChanged: + { + const T priorValue = searchResults.value(searchAddress).getValue<T>(); + return memoryValueComparator(isNotOperator ? SearchComparison::Equals : SearchComparison::NotEquals, priorValue, readValue); + break; + } + case SearchComparison::ChangedBy: + { + const T priorValue = searchResults.value(searchAddress).getValue<T>(); + const T expectedIncrease = searchValue + priorValue; + const T expectedDecrease = priorValue - searchValue; + return memoryValueComparator(SearchComparison::Equals, readValue, expectedIncrease) || memoryValueComparator(SearchComparison::Equals, readValue, expectedDecrease); + } + default: + Console.Error("Debugger: Unknown type when doing memory search!"); + return false; + } +} + +template <typename T> +SearchResults searchWorker(DebugInterface* cpu, SearchResults searchResults, SearchType searchType, SearchComparison searchComparison, u32 start, u32 end, T searchValue) +{ + SearchResults newSearchResults; + const bool isSearchingRange = searchResults.size() <= 0; if (isSearchingRange) { for (u32 addr = start; addr < end; addr += sizeof(T)) { if (!cpu->isValidAddress(addr)) continue; + T readValue = readValueAtAddress<T>(cpu, addr); - if (memoryValueComparator(searchComparison, searchValue, readValue)) + if (handleSearchComparison(searchComparison, addr, searchResults, searchValue, readValue)) { - hitAddresses.push_back(addr); + newSearchResults.insert(addr, MemorySearchWidget::SearchResult(addr, QVariant::fromValue(readValue), searchType)); } } } else { - for (const u32 addr : searchAddresses) + for (const MemorySearchWidget::SearchResult& searchResult : searchResults) { + const u32 addr = searchResult.getAddress(); if (!cpu->isValidAddress(addr)) continue; T readValue = readValueAtAddress<T>(cpu, addr); - if (memoryValueComparator(searchComparison, searchValue, readValue)) + if (handleSearchComparison(searchComparison, addr, searchResults, searchValue, readValue)) { - hitAddresses.push_back(addr); + newSearchResults.insert(addr, MemorySearchWidget::SearchResult(addr, QVariant::fromValue(readValue), searchType)); } } } - return hitAddresses; + return newSearchResults; } static bool compareByteArrayAtAddress(DebugInterface* cpu, SearchComparison searchComparison, u32 addr, QByteArray value) @@ -282,55 +352,105 @@ static bool compareByteArrayAtAddress(DebugInterface* cpu, SearchComparison sear return !isNotOperator; } -static std::vector<u32> searchWorkerByteArray(DebugInterface* cpu, SearchComparison searchComparison, std::vector<u32> searchAddresses, u32 start, u32 end, QByteArray value) +bool handleArraySearchComparison(DebugInterface* cpu, SearchComparison searchComparison, u32 searchAddress, SearchResults searchResults, QByteArray searchValue) { - std::vector<u32> hitAddresses; - const bool isSearchingRange = searchAddresses.size() <= 0; + const bool isNotOperator = searchComparison == SearchComparison::NotEquals || searchComparison == SearchComparison::NotChanged; + switch (searchComparison) + { + case SearchComparison::Equals: + case SearchComparison::NotEquals: + { + return compareByteArrayAtAddress(cpu, searchComparison, searchAddress, searchValue); + break; + } + case SearchComparison::Changed: + case SearchComparison::NotChanged: + { + QByteArray priorValue = searchResults.value(searchAddress).getArrayValue(); + return compareByteArrayAtAddress(cpu, isNotOperator ? SearchComparison::Equals : SearchComparison::NotEquals, searchAddress, priorValue); + break; + } + default: + { + Console.Error("Debugger: Unknown search comparison when doing memory search"); + return false; + } + } + // Default to no match found unless the comparison is a NotEquals + return isNotOperator; +} + +static QByteArray readArrayAtAddress(DebugInterface* cpu, u32 address, u32 length) +{ + QByteArray readArray; + for (u32 i = address; i < address + length; i++) + { + readArray.append(cpu->read8(i)); + } + return readArray; +} + +static SearchResults searchWorkerByteArray(DebugInterface* cpu, SearchType searchType, SearchComparison searchComparison, SearchResults searchResults, u32 start, u32 end, QByteArray searchValue) +{ + SearchResults newResults; + const bool isSearchingRange = searchResults.size() <= 0; if (isSearchingRange) { for (u32 addr = start; addr < end; addr += 1) { - if (compareByteArrayAtAddress(cpu, searchComparison, addr, value)) + if (!cpu->isValidAddress(addr)) + continue; + if (handleArraySearchComparison(cpu, searchComparison, addr, searchResults, searchValue)) { - hitAddresses.emplace_back(addr); - addr += value.length() - 1; + newResults.insert(addr, MemorySearchWidget::SearchResult(addr, searchValue, searchType)); + addr += searchValue.length() - 1; } } } else { - for (u32 addr : searchAddresses) + for (MemorySearchWidget::SearchResult searchResult : searchResults) { - if (compareByteArrayAtAddress(cpu, searchComparison, addr, value)) + const u32 addr = searchResult.getAddress(); + if (!cpu->isValidAddress(addr)) + continue; + if (handleArraySearchComparison(cpu, searchComparison, addr, searchResults, searchValue)) { - hitAddresses.emplace_back(addr); + QByteArray matchValue; + if (searchComparison == SearchComparison::Equals) + matchValue = searchValue; + else if (searchComparison == SearchComparison::NotChanged) + matchValue = searchResult.getArrayValue(); + else + matchValue = readArrayAtAddress(cpu, addr, searchValue.length() - 1); + newResults.insert(addr, MemorySearchWidget::SearchResult(addr, matchValue, searchType)); } } } - return hitAddresses; + return newResults; } -std::vector<u32> startWorker(DebugInterface* cpu, const SearchType type, const SearchComparison searchComparison, std::vector<u32> searchAddresses, u32 start, u32 end, QString value, int base) +SearchResults startWorker(DebugInterface* cpu, const SearchType type, const SearchComparison comparison, SearchResults searchResults, u32 start, u32 end, QString value, int base) { const bool isSigned = value.startsWith("-"); switch (type) { case SearchType::ByteType: - return isSigned ? searchWorker<s8>(cpu, searchAddresses, searchComparison, start, end, value.toShort(nullptr, base)) : searchWorker<u8>(cpu, searchAddresses, searchComparison, start, end, value.toUShort(nullptr, base)); + return isSigned ? searchWorker<s8>(cpu, searchResults, type, comparison, start, end, value.toShort(nullptr, base)) : searchWorker<u8>(cpu, searchResults, type, comparison, start, end, value.toUShort(nullptr, base)); case SearchType::Int16Type: - return isSigned ? searchWorker<s16>(cpu, searchAddresses, searchComparison, start, end, value.toShort(nullptr, base)) : searchWorker<u16>(cpu, searchAddresses, searchComparison, start, end, value.toUShort(nullptr, base)); + return isSigned ? searchWorker<s16>(cpu, searchResults, type, comparison, start, end, value.toShort(nullptr, base)) : searchWorker<u16>(cpu, searchResults, type, comparison, start, end, value.toUShort(nullptr, base)); case SearchType::Int32Type: - return isSigned ? searchWorker<s32>(cpu, searchAddresses, searchComparison, start, end, value.toInt(nullptr, base)) : searchWorker<u32>(cpu, searchAddresses, searchComparison, start, end, value.toUInt(nullptr, base)); + return isSigned ? searchWorker<s32>(cpu, searchResults, type, comparison, start, end, value.toInt(nullptr, base)) : searchWorker<u32>(cpu, searchResults, type, comparison, start, end, value.toUInt(nullptr, base)); case SearchType::Int64Type: - return isSigned ? searchWorker<s64>(cpu, searchAddresses, searchComparison, start, end, value.toLong(nullptr, base)) : searchWorker<s64>(cpu, searchAddresses, searchComparison, start, end, value.toULongLong(nullptr, base)); + return isSigned ? searchWorker<s64>(cpu, searchResults, type, comparison, start, end, value.toLong(nullptr, base)) : searchWorker<s64>(cpu, searchResults, type, comparison, start, end, value.toULongLong(nullptr, base)); case SearchType::FloatType: - return searchWorker<float>(cpu, searchAddresses, searchComparison, start, end, value.toFloat()); + return searchWorker<float>(cpu, searchResults, type, comparison, start, end, value.toFloat()); case SearchType::DoubleType: - return searchWorker<double>(cpu, searchAddresses, searchComparison, start, end, value.toDouble()); + return searchWorker<double>(cpu, searchResults, type, comparison, start, end, value.toDouble()); case SearchType::StringType: - return searchWorkerByteArray(cpu, searchComparison, searchAddresses, start, end, value.toUtf8()); + return searchWorkerByteArray(cpu, type, comparison, searchResults, start, end, value.toUtf8()); case SearchType::ArrayType: - return searchWorkerByteArray(cpu, searchComparison, searchAddresses, start, end, QByteArray::fromHex(value.toUtf8())); + return searchWorkerByteArray(cpu, type, comparison, searchResults, start, end, QByteArray::fromHex(value.toUtf8())); default: Console.Error("Debugger: Unknown type when doing memory search!"); break; @@ -343,7 +463,7 @@ void MemorySearchWidget::onSearchButtonClicked() if (!m_cpu->isAlive()) return; - const SearchType searchType = static_cast<SearchType>(m_ui.cmbSearchType->currentIndex()); + const SearchType searchType = getCurrentSearchType(); const bool searchHex = m_ui.chkSearchHex->isChecked(); bool ok; @@ -370,23 +490,10 @@ void MemorySearchWidget::onSearchButtonClicked() } const QString searchValue = m_ui.txtSearchValue->text(); - const SearchComparison searchComparison = static_cast<SearchComparison>(m_ui.cmbSearchComparison->currentIndex()); + const SearchComparison searchComparison = getCurrentSearchComparison(); const bool isFilterSearch = sender() == m_ui.btnFilterSearch; unsigned long long value; - const bool isVariableSize = searchType == SearchType::ArrayType || searchType == SearchType::StringType; - if (isVariableSize && !isFilterSearch && searchComparison == SearchComparison::NotEquals) - { - QMessageBox::critical(this, tr("Debugger"), tr("Search types Array and String can use the Not Equals search comparison type with new searches.")); - return; - } - - if (isVariableSize && searchComparison != SearchComparison::Equals && searchComparison != SearchComparison::NotEquals) - { - QMessageBox::critical(this, tr("Debugger"), tr("Search types Array and String can only be used with Equals search comparisons.")); - return; - } - switch (searchType) { case SearchType::ByteType: @@ -437,35 +544,39 @@ void MemorySearchWidget::onSearchButtonClicked() return; } - QFutureWatcher<std::vector<u32>>* workerWatcher = new QFutureWatcher<std::vector<u32>>; - - connect(workerWatcher, &QFutureWatcher<std::vector<u32>>::finished, [this, workerWatcher] { + QFutureWatcher<SearchResults>* workerWatcher = new QFutureWatcher<SearchResults>(); + auto onSearchFinished = [this, workerWatcher] { m_ui.btnSearch->setDisabled(false); m_ui.listSearchResults->clear(); const auto& results = workerWatcher->future().result(); - m_searchResults = results; + m_searchResultsMap = results; loadSearchResults(); + m_ui.resultsCountLabel->setText(QString(tr("%0 results found")).arg(results.size())); m_ui.btnFilterSearch->setDisabled(m_ui.listSearchResults->count() == 0); - }); + updateSearchComparisonSelections(); + }; + connect(workerWatcher, &QFutureWatcher<std::vector<u32>>::finished, onSearchFinished); m_ui.btnSearch->setDisabled(true); - std::vector<u32> addresses; + SearchResults searchResultsMap; if (isFilterSearch) { - addresses = m_searchResults; + searchResultsMap = m_searchResultsMap; } - QFuture<std::vector<u32>> workerFuture = - QtConcurrent::run(startWorker, m_cpu, searchType, searchComparison, addresses, searchStart, searchEnd, searchValue, searchHex ? 16 : 10); + + QFuture<SearchResults> workerFuture = QtConcurrent::run(startWorker, m_cpu, searchType, searchComparison, searchResultsMap, searchStart, searchEnd, searchValue, searchHex ? 16 : 10); workerWatcher->setFuture(workerFuture); + connect(workerWatcher, &QFutureWatcher<SearchResults>::finished, onSearchFinished); + m_ui.resultsCountLabel->setText(tr("Searching...")); + m_ui.resultsCountLabel->setVisible(true); } void MemorySearchWidget::onSearchResultsListScroll(u32 value) { - bool hasResultsToLoad = static_cast<size_t>(m_ui.listSearchResults->count()) < m_searchResults.size(); - bool scrolledSufficiently = value > (m_ui.listSearchResults->verticalScrollBar()->maximum() * 0.95); - + const bool hasResultsToLoad = static_cast<qsizetype>(m_ui.listSearchResults->count()) < m_searchResultsMap.size(); + const bool scrolledSufficiently = value > (m_ui.listSearchResults->verticalScrollBar()->maximum() * 0.95); if (!m_resultsLoadTimer.isActive() && hasResultsToLoad && scrolledSufficiently) { // Load results once timer ends, allowing us to debounce repeated requests and only do one load. @@ -476,7 +587,7 @@ void MemorySearchWidget::onSearchResultsListScroll(u32 value) void MemorySearchWidget::loadSearchResults() { const u32 numLoaded = m_ui.listSearchResults->count(); - const u32 amountLeftToLoad = m_searchResults.size() - numLoaded; + const u32 amountLeftToLoad = m_searchResultsMap.size() - numLoaded; if (amountLeftToLoad < 1) return; @@ -484,11 +595,93 @@ void MemorySearchWidget::loadSearchResults() const u32 maxLoadAmount = isFirstLoad ? m_initialResultsLoadLimit : m_numResultsAddedPerLoad; const u32 numToLoad = amountLeftToLoad > maxLoadAmount ? maxLoadAmount : amountLeftToLoad; + const auto addresses = m_searchResultsMap.keys(); for (u32 i = 0; i < numToLoad; i++) { - u32 address = m_searchResults.at(numLoaded + i); + const u32 address = addresses.at(numLoaded + i); QListWidgetItem* item = new QListWidgetItem(QtUtils::FilledQStringFromValue(address, 16)); item->setData(Qt::UserRole, address); m_ui.listSearchResults->addItem(item); } } + +SearchType MemorySearchWidget::getCurrentSearchType() +{ + return static_cast<SearchType>(m_ui.cmbSearchType->currentIndex()); +} + +SearchComparison MemorySearchWidget::getCurrentSearchComparison() +{ + // Note: The index can't be converted directly to the enum value since we change what comparisons are shown. + return m_searchComparisonLabelMap.labelToEnum(m_ui.cmbSearchComparison->currentText()); +} + +void MemorySearchWidget::onSearchTypeChanged(int newIndex) +{ + if (newIndex < 4) + m_ui.chkSearchHex->setEnabled(true); + else + m_ui.chkSearchHex->setEnabled(false); + + // Clear existing search results when the comparison type changes + if (m_searchResultsMap.size() > 0 && (int)(m_searchResultsMap.first().getType()) != newIndex) + { + m_searchResultsMap.clear(); + m_ui.btnSearch->setDisabled(false); + m_ui.btnFilterSearch->setDisabled(true); + } + updateSearchComparisonSelections(); +} + +void MemorySearchWidget::updateSearchComparisonSelections() +{ + const QString selectedComparisonLabel = m_ui.cmbSearchComparison->currentText(); + const SearchComparison selectedComparison = m_searchComparisonLabelMap.labelToEnum(selectedComparisonLabel); + + const std::vector<SearchComparison> comparisons = getValidSearchComparisonsForState(getCurrentSearchType(), m_searchResultsMap); + m_ui.cmbSearchComparison->clear(); + for (const SearchComparison comparison : comparisons) + { + m_ui.cmbSearchComparison->addItem(m_searchComparisonLabelMap.enumToLabel(comparison)); + } + + // Preserve selection if applicable + if (selectedComparison == SearchComparison::Invalid) + return; + if (std::find(comparisons.begin(), comparisons.end(), selectedComparison) != comparisons.end()) + m_ui.cmbSearchComparison->setCurrentText(selectedComparisonLabel); +} + +std::vector<SearchComparison> MemorySearchWidget::getValidSearchComparisonsForState(SearchType type, SearchResults existingResults) +{ + const bool hasResults = existingResults.size() > 0; + std::vector<SearchComparison> comparisons = { SearchComparison::Equals }; + + if (type == SearchType::ArrayType || type == SearchType::StringType) + { + if (hasResults && existingResults.first().isArrayValue()) + { + comparisons.push_back(SearchComparison::NotEquals); + comparisons.push_back(SearchComparison::Changed); + comparisons.push_back(SearchComparison::NotChanged); + } + return comparisons; + } + comparisons.push_back(SearchComparison::NotEquals); + comparisons.push_back(SearchComparison::GreaterThan); + comparisons.push_back(SearchComparison::GreaterThanOrEqual); + comparisons.push_back(SearchComparison::LessThan); + comparisons.push_back(SearchComparison::LessThanOrEqual); + + if (hasResults && existingResults.first().getType() == type) + { + comparisons.push_back(SearchComparison::Increased); + comparisons.push_back(SearchComparison::IncreasedBy); + comparisons.push_back(SearchComparison::Decreased); + comparisons.push_back(SearchComparison::DecreasedBy); + comparisons.push_back(SearchComparison::Changed); + comparisons.push_back(SearchComparison::ChangedBy); + comparisons.push_back(SearchComparison::NotChanged); + } + return comparisons; +} diff --git a/pcsx2-qt/Debugger/MemorySearchWidget.h b/pcsx2-qt/Debugger/MemorySearchWidget.h index 5cd3833dea339..28ff0a372cfaf 100644 --- a/pcsx2-qt/Debugger/MemorySearchWidget.h +++ b/pcsx2-qt/Debugger/MemorySearchWidget.h @@ -9,6 +9,7 @@ #include <QtWidgets/QWidget> #include <QtCore/QTimer> +#include <QtCore/QMap> class MemorySearchWidget final : public QWidget { @@ -39,12 +40,86 @@ class MemorySearchWidget final : public QWidget GreaterThan, GreaterThanOrEqual, LessThan, - LessThanOrEqual + LessThanOrEqual, + Increased, + IncreasedBy, + Decreased, + DecreasedBy, + Changed, + ChangedBy, + NotChanged, + Invalid + }; + + class SearchComparisonLabelMap + { + public: + SearchComparisonLabelMap() + { + insert(SearchComparison::Equals, tr("Equals")); + insert(SearchComparison::NotEquals, tr("Not Equals")); + insert(SearchComparison::GreaterThan, tr("Greater Than")); + insert(SearchComparison::GreaterThanOrEqual, tr("Greater Than Or Equal")); + insert(SearchComparison::LessThan, tr("Less Than")); + insert(SearchComparison::LessThanOrEqual, tr("Less Than Or Equal")); + insert(SearchComparison::Increased, tr("Increased")); + insert(SearchComparison::IncreasedBy, tr("Increased By")); + insert(SearchComparison::Decreased, tr("Decreased")); + insert(SearchComparison::DecreasedBy, tr("Decreased By")); + insert(SearchComparison::Changed, tr("Changed")); + insert(SearchComparison::ChangedBy, tr("Changed By")); + insert(SearchComparison::NotChanged, tr("Not Changed")); + insert(SearchComparison::Invalid, ""); + } + SearchComparison labelToEnum(QString comparisonLabel) + { + return labelToEnumMap.value(comparisonLabel, SearchComparison::Invalid); + } + QString enumToLabel(SearchComparison comparison) { + return enumToLabelMap.value(comparison, ""); + } + private: + QMap<SearchComparison, QString> enumToLabelMap; + QMap<QString, SearchComparison> labelToEnumMap; + void insert(SearchComparison comparison, QString comparisonLabel) + { + enumToLabelMap.insert(comparison, comparisonLabel); + labelToEnumMap.insert(comparisonLabel, comparison); + }; + }; + + class SearchResult + { + private: + u32 address; + QVariant value; + SearchType type; + + public: + SearchResult() {} + SearchResult(u32 address, const QVariant& value, SearchType type) + : address(address), value(value), type(type) + { + } + bool isIntegerValue() const { return type == SearchType::ByteType || type == SearchType::Int16Type || type == SearchType::Int32Type || type == SearchType::Int64Type; } + bool isFloatValue() const { return type == SearchType::FloatType; } + bool isDoubleValue() const { return type == SearchType::DoubleType; } + bool isArrayValue() const { return type == SearchType::ArrayType || type == SearchType::StringType; } + u32 getAddress() const { return address; } + SearchType getType() const { return type; } + QByteArray getArrayValue() const { return isArrayValue() ? value.toByteArray() : QByteArray(); } + + template<typename T> + T getValue() const + { + return value.value<T>(); + } }; public slots: void onSearchButtonClicked(); void onSearchResultsListScroll(u32 value); + void onSearchTypeChanged(int newIndex); void loadSearchResults(); void contextSearchResultGoToDisassembly(); void contextRemoveSearchResult(); @@ -58,13 +133,17 @@ public slots: void switchToMemoryViewTab(); private: - std::vector<u32> m_searchResults; - - Ui::MemorySearchWidget m_ui; + QMap<u32, SearchResult> m_searchResultsMap; + SearchComparisonLabelMap m_searchComparisonLabelMap; + Ui::MemorySearchWidget m_ui; + DebugInterface* m_cpu; + QTimer m_resultsLoadTimer; - DebugInterface* m_cpu; - QTimer m_resultsLoadTimer; - - u32 m_initialResultsLoadLimit = 20000; + u32 m_initialResultsLoadLimit = 20000; u32 m_numResultsAddedPerLoad = 10000; + + void updateSearchComparisonSelections(); + std::vector<SearchComparison> getValidSearchComparisonsForState(SearchType type, QMap<u32, MemorySearchWidget::SearchResult> existingResults); + SearchType getCurrentSearchType(); + SearchComparison getCurrentSearchComparison(); }; diff --git a/pcsx2-qt/Debugger/MemorySearchWidget.ui b/pcsx2-qt/Debugger/MemorySearchWidget.ui index 789724a035f52..8e397067bf81e 100644 --- a/pcsx2-qt/Debugger/MemorySearchWidget.ui +++ b/pcsx2-qt/Debugger/MemorySearchWidget.ui @@ -22,7 +22,7 @@ <item> <layout class="QGridLayout" name="gridLayout_2"> <item row="0" column="0"> - <widget class="QLabel" name="label"> + <widget class="QLabel" name="valueLabel"> <property name="text"> <string>Value</string> </property> @@ -32,7 +32,7 @@ <widget class="QLineEdit" name="txtSearchValue"/> </item> <item row="2" column="0"> - <widget class="QLabel" name="label_2"> + <widget class="QLabel" name="typeLabel"> <property name="text"> <string>Type</string> </property> @@ -83,7 +83,7 @@ </widget> </item> <item row="2" column="2"> - <widget class="QLabel" name="label_3"> + <widget class="QLabel" name="hexLabel"> <property name="text"> <string>Hex</string> </property> @@ -162,7 +162,7 @@ <item> <layout class="QGridLayout" name="gridLayout_3"> <item row="0" column="0" alignment="Qt::AlignLeft"> - <widget class="QLabel" name="label_4"> + <widget class="QLabel" name="startLabel"> <property name="text"> <string>Start</string> </property> @@ -176,7 +176,7 @@ </widget> </item> <item row="0" column="2"> - <widget class="QLabel" name="label_5"> + <widget class="QLabel" name="endLabel"> <property name="text"> <string>End</string> </property> @@ -194,6 +194,16 @@ <item> <widget class="QListWidget" name="listSearchResults"/> </item> + <item> + <widget class="QLabel" name="resultsCountLabel"> + <property name="visible"> + <bool>false</bool> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> </layout> </widget> <resources/>