Debugger: Migrates Memory Search tab to it's own widget

Moves the Memory Search fucntionality to it's own widget so as to not pollute the CpuWidget with search related functionality, especially as it is intended to grow in scope. CpuWidget is fairly general and as such everything tends to get tossed together which makes it harder to navigate/understand/maintain.
This commit is contained in:
Dan McCarthy 2024-02-12 18:20:00 -06:00 committed by refractionpcsx2
parent 1f584736f1
commit ce5e66a2d5
9 changed files with 838 additions and 663 deletions

View File

@ -158,6 +158,9 @@ target_sources(pcsx2-qt PRIVATE
Debugger/DisassemblyWidget.cpp
Debugger/DisassemblyWidget.h
Debugger/DisassemblyWidget.ui
Debugger/MemorySearchWidget.cpp
Debugger/MemorySearchWidget.h
Debugger/MemorySearchWidget.ui
Debugger/MemoryViewWidget.cpp
Debugger/MemoryViewWidget.h
Debugger/MemoryViewWidget.ui

View File

@ -33,8 +33,6 @@
using namespace QtUtils;
using namespace MipsStackWalk;
using SearchComparison = CpuWidget::SearchComparison;
using SearchType = CpuWidget::SearchType;
CpuWidget::CpuWidget(QWidget* parent, DebugInterface& cpu)
: m_cpu(cpu)
@ -109,22 +107,6 @@ CpuWidget::CpuWidget(QWidget* parent, DebugInterface& cpu)
connect(m_ui.btnRefreshFunctions, &QPushButton::clicked, [this] { updateFunctionList(); });
connect(m_ui.txtFuncSearch, &QLineEdit::textChanged, [this] { updateFunctionList(); });
m_ui.listSearchResults->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_ui.btnSearch, &QPushButton::clicked, this, &CpuWidget::onSearchButtonClicked);
connect(m_ui.btnFilterSearch, &QPushButton::clicked, this, &CpuWidget::onSearchButtonClicked);
connect(m_ui.listSearchResults, &QListWidget::itemDoubleClicked, [this](QListWidgetItem* item)
{
m_ui.tabWidget->setCurrentWidget(m_ui.tab_memory);
m_ui.memoryviewWidget->gotoAddress(item->text().toUInt(nullptr, 16));
});
connect(m_ui.listSearchResults->verticalScrollBar(), &QScrollBar::valueChanged, this, &CpuWidget::onSearchResultsListScroll);
connect(m_ui.listSearchResults, &QListView::customContextMenuRequested, this, &CpuWidget::onListSearchResultsContextMenu);
connect(m_ui.cmbSearchType, &QComboBox::currentIndexChanged, [this](int i) {
if (i < 4)
m_ui.chkSearchHex->setEnabled(true);
else
m_ui.chkSearchHex->setEnabled(false);
});
m_ui.disassemblyWidget->SetCpu(&cpu);
m_ui.registerWidget->SetCpu(&cpu);
m_ui.memoryviewWidget->SetCpu(&cpu);
@ -140,11 +122,6 @@ CpuWidget::CpuWidget(QWidget* parent, DebugInterface& cpu)
}
this->repaint();
// Ensures we don't retrigger the load results function unintentionally
m_resultsLoadTimer.setInterval(100);
m_resultsLoadTimer.setSingleShot(true);
connect(&m_resultsLoadTimer, &QTimer::timeout, this, &CpuWidget::loadSearchResults);
m_ui.savedAddressesList->setModel(&m_savedAddressesModel);
m_ui.savedAddressesList->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_ui.savedAddressesList, &QTableView::customContextMenuRequested, this, &CpuWidget::onSavedAddressesListContextMenu);
@ -159,6 +136,12 @@ CpuWidget::CpuWidget(QWidget* parent, DebugInterface& cpu)
DebuggerSettingsManager::loadGameSettings(&m_bpModel);
DebuggerSettingsManager::loadGameSettings(&m_savedAddressesModel);
connect(m_ui.memorySearchWidget, &MemorySearchWidget::addAddressToSavedAddressesList, this, &CpuWidget::addAddressToSavedAddressesList);
connect(m_ui.memorySearchWidget, &MemorySearchWidget::goToAddressInDisassemblyView, [this](u32 address) { m_ui.disassemblyWidget->gotoAddress(address); });
connect(m_ui.memorySearchWidget, &MemorySearchWidget::goToAddressInMemoryView, m_ui.memoryviewWidget, &MemoryViewWidget::gotoAddress);
connect(m_ui.memorySearchWidget, &MemorySearchWidget::switchToMemoryViewTab, [this]() { m_ui.tabWidget->setCurrentWidget(m_ui.tab_memory); });
m_ui.memorySearchWidget->setCpu(&m_cpu);
}
CpuWidget::~CpuWidget() = default;
@ -168,6 +151,7 @@ void CpuWidget::paintEvent(QPaintEvent* event)
m_ui.registerWidget->update();
m_ui.disassemblyWidget->update();
m_ui.memoryviewWidget->update();
m_ui.memorySearchWidget->update();
}
// The cpu shouldn't be alive when these are called
@ -568,41 +552,6 @@ void CpuWidget::addAddressToSavedAddressesList(u32 address)
m_ui.savedAddressesList->edit(m_ui.savedAddressesList->model()->index(rowCount - 1, 1));
}
void CpuWidget::contextSearchResultGoToDisassembly()
{
const QItemSelectionModel* selModel = m_ui.listSearchResults->selectionModel();
if (!selModel->hasSelection())
return;
m_ui.disassemblyWidget->gotoAddress(m_ui.listSearchResults->selectedItems().first()->data(Qt::UserRole).toUInt());
}
void CpuWidget::contextRemoveSearchResult()
{
const QItemSelectionModel* selModel = m_ui.listSearchResults->selectionModel();
if (!selModel->hasSelection())
return;
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);
}
delete rowToRemove;
}
void CpuWidget::contextCopySearchResultAddress()
{
if (!m_ui.listSearchResults->selectionModel()->hasSelection())
return;
const u32 selectedResultIndex = m_ui.listSearchResults->row(m_ui.listSearchResults->selectedItems().first());
const u32 rowAddress = m_ui.listSearchResults->item(selectedResultIndex)->data(Qt::UserRole).toUInt();
const QString addressString = FilledQStringFromValue(rowAddress, 16);
QApplication::clipboard()->setText(addressString);
}
void CpuWidget::updateFunctionList(bool whenEmpty)
{
if (!m_cpu.isAlive())
@ -897,36 +846,6 @@ void CpuWidget::updateStackFrames()
m_stackModel.refreshData();
}
void CpuWidget::onListSearchResultsContextMenu(QPoint pos)
{
QMenu* contextMenu = new QMenu(tr("Search Results List Context Menu"), m_ui.listSearchResults);
const QItemSelectionModel* selModel = m_ui.listSearchResults->selectionModel();
const auto listSearchResults = m_ui.listSearchResults;
if (selModel->hasSelection())
{
QAction* copyAddressAction = new QAction(tr("Copy Address"), m_ui.listSearchResults);
connect(copyAddressAction, &QAction::triggered, this, &CpuWidget::contextCopySearchResultAddress);
contextMenu->addAction(copyAddressAction);
QAction* goToDisassemblyAction = new QAction(tr("Go to in Disassembly"), m_ui.listSearchResults);
connect(goToDisassemblyAction, &QAction::triggered, this, &CpuWidget::contextSearchResultGoToDisassembly);
contextMenu->addAction(goToDisassemblyAction);
QAction* addToSavedAddressesAction = new QAction(tr("Add to Saved Memory Addresses"), m_ui.listSearchResults);
connect(addToSavedAddressesAction, &QAction::triggered, this, [this, listSearchResults]() {
addAddressToSavedAddressesList(listSearchResults->selectedItems().first()->data(Qt::UserRole).toUInt());
});
contextMenu->addAction(addToSavedAddressesAction);
QAction* removeResultAction = new QAction(tr("Remove Result"), m_ui.listSearchResults);
connect(removeResultAction, &QAction::triggered, this, &CpuWidget::contextRemoveSearchResult);
contextMenu->addAction(removeResultAction);
}
contextMenu->popup(m_ui.listSearchResults->viewport()->mapToGlobal(pos));
}
void CpuWidget::onStackListContextMenu(QPoint pos)
{
if (!m_ui.stackList->selectionModel()->hasSelection())
@ -974,375 +893,6 @@ void CpuWidget::onStackListDoubleClick(const QModelIndex& index)
}
}
template <typename T>
static T readValueAtAddress(DebugInterface* cpu, u32 addr)
{
T val = 0;
switch (sizeof(T))
{
case sizeof(u8):
val = cpu->read8(addr);
break;
case sizeof(u16):
val = cpu->read16(addr);
break;
case sizeof(u32):
{
val = cpu->read32(addr);
break;
}
case sizeof(u64):
{
val = cpu->read64(addr);
break;
}
}
return val;
}
template <typename T>
static bool memoryValueComparator(SearchComparison searchComparison, T searchValue, T readValue)
{
const bool isNotOperator = searchComparison == SearchComparison::NotEquals;
switch (searchComparison)
{
case SearchComparison::Equals:
case SearchComparison::NotEquals:
{
bool areValuesEqual = false;
if constexpr (std::is_same_v<T, float>)
{
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);
}
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);
}
else
{
areValuesEqual = searchValue == readValue;
}
return isNotOperator ? !areValuesEqual : areValuesEqual;
break;
}
case SearchComparison::GreaterThan:
case SearchComparison::GreaterThanOrEqual:
case SearchComparison::LessThan:
case SearchComparison::LessThanOrEqual:
{
const bool hasEqualsCheck = searchComparison == SearchComparison::GreaterThanOrEqual || searchComparison == SearchComparison::LessThanOrEqual;
if (hasEqualsCheck && memoryValueComparator(SearchComparison::Equals, searchValue, readValue))
return true;
const bool isGreaterOperator = searchComparison == SearchComparison::GreaterThan || searchComparison == SearchComparison::GreaterThanOrEqual;
if (std::is_same_v<T, float>)
{
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;
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;
return isGreaterOperator ? isGreater : isLesser;
}
return isGreaterOperator ? (readValue > searchValue) : (readValue < searchValue);
}
default:
Console.Error("Debugger: Unknown type when doing memory search!");
return false;
}
}
template <typename T>
std::vector<u32> searchWorker(DebugInterface* cpu, std::vector<u32> searchAddresses, SearchComparison searchComparison, u32 start, u32 end, T searchValue)
{
std::vector<u32> hitAddresses;
const bool isSearchingRange = searchAddresses.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))
{
hitAddresses.push_back(addr);
}
}
}
else
{
for (const u32 addr : searchAddresses)
{
if (!cpu->isValidAddress(addr))
continue;
T readValue = readValueAtAddress<T>(cpu, addr);
if (memoryValueComparator(searchComparison, searchValue, readValue))
{
hitAddresses.push_back(addr);
}
}
}
return hitAddresses;
}
static bool compareByteArrayAtAddress(DebugInterface* cpu, SearchComparison searchComparison, u32 addr, QByteArray value)
{
const bool isNotOperator = searchComparison == SearchComparison::NotEquals;
for (qsizetype i = 0; i < value.length(); i++)
{
const char nextByte = cpu->read8(addr + i);
switch (searchComparison)
{
case SearchComparison::Equals:
{
if (nextByte != value[i])
return false;
break;
}
case SearchComparison::NotEquals:
{
if (nextByte != value[i])
return true;
break;
}
default:
{
Console.Error("Debugger: Unknown search comparison when doing memory search");
return false;
}
}
}
return !isNotOperator;
}
static std::vector<u32> searchWorkerByteArray(DebugInterface* cpu, SearchComparison searchComparison, std::vector<u32> searchAddresses, u32 start, u32 end, QByteArray value)
{
std::vector<u32> hitAddresses;
const bool isSearchingRange = searchAddresses.size() <= 0;
if (isSearchingRange)
{
for (u32 addr = start; addr < end; addr += 1)
{
if (compareByteArrayAtAddress(cpu, searchComparison, addr, value))
{
hitAddresses.emplace_back(addr);
addr += value.length() - 1;
}
}
}
else
{
for (u32 addr : searchAddresses)
{
if (compareByteArrayAtAddress(cpu, searchComparison, addr, value))
{
hitAddresses.emplace_back(addr);
}
}
}
return hitAddresses;
}
std::vector<u32> startWorker(DebugInterface* cpu, const SearchType type, const SearchComparison searchComparison, std::vector<u32> searchAddresses, 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));
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));
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));
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));
case SearchType::FloatType:
return searchWorker<float>(cpu, searchAddresses, searchComparison, start, end, value.toFloat());
case SearchType::DoubleType:
return searchWorker<double>(cpu, searchAddresses, searchComparison, start, end, value.toDouble());
case SearchType::StringType:
return searchWorkerByteArray(cpu, searchComparison, searchAddresses, start, end, value.toUtf8());
case SearchType::ArrayType:
return searchWorkerByteArray(cpu, searchComparison, searchAddresses, start, end, QByteArray::fromHex(value.toUtf8()));
default:
Console.Error("Debugger: Unknown type when doing memory search!");
break;
};
return {};
}
void CpuWidget::onSearchButtonClicked()
{
if (!m_cpu.isAlive())
return;
const SearchType searchType = static_cast<SearchType>(m_ui.cmbSearchType->currentIndex());
const bool searchHex = m_ui.chkSearchHex->isChecked();
bool ok;
const u32 searchStart = m_ui.txtSearchStart->text().toUInt(&ok, 16);
if (!ok)
{
QMessageBox::critical(this, tr("Debugger"), tr("Invalid start address"));
return;
}
const u32 searchEnd = m_ui.txtSearchEnd->text().toUInt(&ok, 16);
if (!ok)
{
QMessageBox::critical(this, tr("Debugger"), tr("Invalid end address"));
return;
}
if (searchStart >= searchEnd)
{
QMessageBox::critical(this, tr("Debugger"), tr("Start address can't be equal to or greater than the end address"));
return;
}
const QString searchValue = m_ui.txtSearchValue->text();
const SearchComparison searchComparison = static_cast<SearchComparison>(m_ui.cmbSearchComparison->currentIndex());
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:
case SearchType::Int16Type:
case SearchType::Int32Type:
case SearchType::Int64Type:
value = searchValue.toULongLong(&ok, searchHex ? 16 : 10);
break;
case SearchType::FloatType:
case SearchType::DoubleType:
searchValue.toDouble(&ok);
break;
case SearchType::StringType:
ok = !searchValue.isEmpty();
break;
case SearchType::ArrayType:
ok = !searchValue.trimmed().isEmpty();
break;
}
if (!ok)
{
QMessageBox::critical(this, tr("Debugger"), tr("Invalid search value"));
return;
}
switch (searchType)
{
case SearchType::ArrayType:
case SearchType::StringType:
case SearchType::DoubleType:
case SearchType::FloatType:
break;
case SearchType::Int64Type:
if (value <= std::numeric_limits<unsigned long long>::max())
break;
case SearchType::Int32Type:
if (value <= std::numeric_limits<unsigned long>::max())
break;
case SearchType::Int16Type:
if (value <= std::numeric_limits<unsigned short>::max())
break;
case SearchType::ByteType:
if (value <= std::numeric_limits<unsigned char>::max())
break;
default:
QMessageBox::critical(this, tr("Debugger"), tr("Value is larger than type"));
return;
}
QFutureWatcher<std::vector<u32>>* workerWatcher = new QFutureWatcher<std::vector<u32>>;
connect(workerWatcher, &QFutureWatcher<std::vector<u32>>::finished, [this, workerWatcher] {
m_ui.btnSearch->setDisabled(false);
m_ui.listSearchResults->clear();
const auto& results = workerWatcher->future().result();
m_searchResults = results;
loadSearchResults();
m_ui.btnFilterSearch->setDisabled(m_ui.listSearchResults->count() == 0);
});
m_ui.btnSearch->setDisabled(true);
std::vector<u32> addresses;
if (isFilterSearch)
{
addresses = m_searchResults;
}
QFuture<std::vector<u32>> workerFuture =
QtConcurrent::run(startWorker, &m_cpu, searchType, searchComparison, addresses, searchStart, searchEnd, searchValue, searchHex ? 16 : 10);
workerWatcher->setFuture(workerFuture);
}
void CpuWidget::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);
if (!m_resultsLoadTimer.isActive() && hasResultsToLoad && scrolledSufficiently)
{
// Load results once timer ends, allowing us to debounce repeated requests and only do one load.
m_resultsLoadTimer.start();
}
}
void CpuWidget::loadSearchResults()
{
const u32 numLoaded = m_ui.listSearchResults->count();
const u32 amountLeftToLoad = m_searchResults.size() - numLoaded;
if (amountLeftToLoad < 1)
return;
const bool isFirstLoad = numLoaded == 0;
const u32 maxLoadAmount = isFirstLoad ? m_initialResultsLoadLimit : m_numResultsAddedPerLoad;
const u32 numToLoad = amountLeftToLoad > maxLoadAmount ? maxLoadAmount : amountLeftToLoad;
for (u32 i = 0; i < numToLoad; i++)
{
u32 address = m_searchResults.at(numLoaded + i);
QListWidgetItem* item = new QListWidgetItem(QtUtils::FilledQStringFromValue(address, 16));
item->setData(Qt::UserRole, address);
m_ui.listSearchResults->addItem(item);
}
}
void CpuWidget::saveBreakpointsToDebuggerSettings()
{
DebuggerSettingsManager::saveGameSettings(&m_bpModel);

View File

@ -33,28 +33,8 @@ public:
CpuWidget(QWidget* parent, DebugInterface& cpu);
~CpuWidget();
enum class SearchType
{
ByteType,
Int16Type,
Int32Type,
Int64Type,
FloatType,
DoubleType,
StringType,
ArrayType
};
// Note: The order of these enum values must reflect the order in thee Search Comparison combobox.
enum class SearchComparison
{
Equals,
NotEquals,
GreaterThan,
GreaterThanOrEqual,
LessThan,
LessThanOrEqual
};
public slots:
void paintEvent(QPaintEvent* event);
@ -111,20 +91,12 @@ public slots:
m_ui.memoryviewWidget->update();
};
void onSearchButtonClicked();
void onSearchResultsListScroll(u32 value);
void loadSearchResults();
void contextSearchResultGoToDisassembly();
void contextRemoveSearchResult();
void contextCopySearchResultAddress();
void onListSearchResultsContextMenu(QPoint pos);
void saveBreakpointsToDebuggerSettings();
void saveSavedAddressesToDebuggerSettings();
private:
std::vector<QTableWidget*> m_registerTableViews;
std::vector<u32> m_searchResults;
QMenu* m_stacklistContextMenu = 0;
QMenu* m_funclistContextMenu = 0;
@ -139,10 +111,7 @@ private:
QSortFilterProxyModel m_threadProxyModel;
StackModel m_stackModel;
SavedAddressesModel m_savedAddressesModel;
QTimer m_resultsLoadTimer;
bool m_demangleFunctions = true;
bool m_moduleView = true;
u32 m_initialResultsLoadLimit = 20000;
u32 m_numResultsAddedPerLoad = 10000;
};

View File

@ -176,185 +176,49 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="tab">
<widget class="QWidget" name="tabMemorySearch">
<attribute name="title">
<string>Memory Search</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<layout class="QHBoxLayout" name="horizontalLayout_6">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Value</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="txtSearchValue"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Type</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="cmbSearchType">
<item>
<property name="text">
<string>1 Byte (8 bits)</string>
</property>
</item>
<item>
<property name="text">
<string>2 Bytes (16 bits)</string>
</property>
</item>
<item>
<property name="text">
<string>4 Bytes (32 bits)</string>
</property>
</item>
<item>
<property name="text">
<string>8 Bytes (64 bits)</string>
</property>
</item>
<item>
<property name="text">
<string>Float</string>
</property>
</item>
<item>
<property name="text">
<string>Double</string>
</property>
</item>
<item>
<property name="text">
<string>String</string>
</property>
</item>
<item>
<property name="text">
<string>Array of byte</string>
</property>
</item>
</widget>
</item>
<item row="2" column="2">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Hex</string>
</property>
</widget>
</item>
<item row="2" column="3">
<widget class="QCheckBox" name="chkSearchHex">
<property name="text">
<string/>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="2" colspan="2">
<widget class="QPushButton" name="btnSearch">
<property name="text">
<string>Search</string>
</property>
</widget>
</item>
<item row="1" column="2" colspan="2">
<widget class="QPushButton" name="btnFilterSearch">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Filter Search</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="cmbSearchComparison">
<item>
<property name="text">
<string>Equals</string>
</property>
</item>
<item>
<property name="text">
<string>Not Equals</string>
</property>
</item>
<item>
<property name="text">
<string>Greater Than</string>
</property>
</item>
<item>
<property name="text">
<string>Greater Than Or Equal</string>
</property>
</item>
<item>
<property name="text">
<string>Less Than</string>
</property>
</item>
<item>
<property name="text">
<string>Less Than Or Equal</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="comparisonLabel">
<property name="text">
<string>Comparison</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0" alignment="Qt::AlignLeft">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Start</string>
</property>
</widget>
</item>
<item row="0" column="1" alignment="Qt::AlignLeft">
<widget class="QLineEdit" name="txtSearchStart">
<property name="text">
<string notr="true">0x00</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="label_5">
<property name="text">
<string>End</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QLineEdit" name="txtSearchEnd">
<property name="text">
<string notr="true">0x2000000</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QListWidget" name="listSearchResults"/>
<widget class="MemorySearchWidget" name="memorySearchWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<family>Monospace</family>
</font>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="autoFillBackground">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
@ -650,6 +514,12 @@
<header>pcsx2-qt/Debugger/MemoryViewWidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>MemorySearchWidget</class>
<extends>QWidget</extends>
<header>pcsx2-qt/Debugger/MemorySearchWidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>

View File

@ -0,0 +1,494 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#include "MemorySearchWidget.h"
#include "DebugTools/DebugInterface.h"
#include "QtUtils.h"
#include "common/Console.h"
#include <QtGui/QClipboard>
#include <QtWidgets/QMenu>
#include <QtWidgets/QScrollBar>
#include <QtWidgets/QMessageBox>
#include <QtConcurrent/QtConcurrent>
#include <QtCore/QFutureWatcher>
#include <QtGui/QPainter>
using SearchComparison = MemorySearchWidget::SearchComparison;
using SearchType = MemorySearchWidget::SearchType;
using namespace QtUtils;
MemorySearchWidget::MemorySearchWidget(QWidget* parent)
: QWidget(parent)
{
m_ui.setupUi(this);
this->repaint();
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
{
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);
});
// Ensures we don't retrigger the load results function unintentionally
m_resultsLoadTimer.setInterval(100);
m_resultsLoadTimer.setSingleShot(true);
connect(&m_resultsLoadTimer, &QTimer::timeout, this, &MemorySearchWidget::loadSearchResults);
}
void MemorySearchWidget::setCpu(DebugInterface* cpu)
{
m_cpu = cpu;
}
void MemorySearchWidget::contextSearchResultGoToDisassembly()
{
const QItemSelectionModel* selModel = m_ui.listSearchResults->selectionModel();
if (!selModel->hasSelection())
return;
u32 selectedAddress = m_ui.listSearchResults->selectedItems().first()->data(Qt::UserRole).toUInt();
emit goToAddressInDisassemblyView(selectedAddress);
}
void MemorySearchWidget::contextRemoveSearchResult()
{
const QItemSelectionModel* selModel = m_ui.listSearchResults->selectionModel();
if (!selModel->hasSelection())
return;
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);
}
delete rowToRemove;
}
void MemorySearchWidget::contextCopySearchResultAddress()
{
if (!m_ui.listSearchResults->selectionModel()->hasSelection())
return;
const u32 selectedResultIndex = m_ui.listSearchResults->row(m_ui.listSearchResults->selectedItems().first());
const u32 rowAddress = m_ui.listSearchResults->item(selectedResultIndex)->data(Qt::UserRole).toUInt();
const QString addressString = FilledQStringFromValue(rowAddress, 16);
QApplication::clipboard()->setText(addressString);
}
void MemorySearchWidget::onListSearchResultsContextMenu(QPoint pos)
{
QMenu* contextMenu = new QMenu(tr("Search Results List Context Menu"), m_ui.listSearchResults);
const QItemSelectionModel* selModel = m_ui.listSearchResults->selectionModel();
const auto listSearchResults = m_ui.listSearchResults;
if (selModel->hasSelection())
{
QAction* copyAddressAction = new QAction(tr("Copy Address"), m_ui.listSearchResults);
connect(copyAddressAction, &QAction::triggered, this, &MemorySearchWidget::contextCopySearchResultAddress);
contextMenu->addAction(copyAddressAction);
QAction* goToDisassemblyAction = new QAction(tr("Go to in Disassembly"), m_ui.listSearchResults);
connect(goToDisassemblyAction, &QAction::triggered, this, &MemorySearchWidget::contextSearchResultGoToDisassembly);
contextMenu->addAction(goToDisassemblyAction);
QAction* addToSavedAddressesAction = new QAction(tr("Add to Saved Memory Addresses"), m_ui.listSearchResults);
connect(addToSavedAddressesAction, &QAction::triggered, this, [this, listSearchResults]() {
u32 selectedAddress = listSearchResults->selectedItems().first()->data(Qt::UserRole).toUInt();
emit addAddressToSavedAddressesList(selectedAddress);
});
contextMenu->addAction(addToSavedAddressesAction);
QAction* removeResultAction = new QAction(tr("Remove Result"), m_ui.listSearchResults);
connect(removeResultAction, &QAction::triggered, this, &MemorySearchWidget::contextRemoveSearchResult);
contextMenu->addAction(removeResultAction);
}
contextMenu->popup(m_ui.listSearchResults->viewport()->mapToGlobal(pos));
}
template <typename T>
static T readValueAtAddress(DebugInterface* cpu, u32 addr)
{
T val = 0;
switch (sizeof(T))
{
case sizeof(u8):
val = cpu->read8(addr);
break;
case sizeof(u16):
val = cpu->read16(addr);
break;
case sizeof(u32):
{
val = cpu->read32(addr);
break;
}
case sizeof(u64):
{
val = cpu->read64(addr);
break;
}
}
return val;
}
template <typename T>
static bool memoryValueComparator(SearchComparison searchComparison, T searchValue, T readValue)
{
const bool isNotOperator = searchComparison == SearchComparison::NotEquals;
switch (searchComparison)
{
case SearchComparison::Equals:
case SearchComparison::NotEquals:
{
bool areValuesEqual = false;
if constexpr (std::is_same_v<T, float>)
{
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);
}
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);
}
else
{
areValuesEqual = searchValue == readValue;
}
return isNotOperator ? !areValuesEqual : areValuesEqual;
break;
}
case SearchComparison::GreaterThan:
case SearchComparison::GreaterThanOrEqual:
case SearchComparison::LessThan:
case SearchComparison::LessThanOrEqual:
{
const bool hasEqualsCheck = searchComparison == SearchComparison::GreaterThanOrEqual || searchComparison == SearchComparison::LessThanOrEqual;
if (hasEqualsCheck && memoryValueComparator(SearchComparison::Equals, searchValue, readValue))
return true;
const bool isGreaterOperator = searchComparison == SearchComparison::GreaterThan || searchComparison == SearchComparison::GreaterThanOrEqual;
if (std::is_same_v<T, float>)
{
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;
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;
return isGreaterOperator ? isGreater : isLesser;
}
return isGreaterOperator ? (readValue > searchValue) : (readValue < searchValue);
}
default:
Console.Error("Debugger: Unknown type when doing memory search!");
return false;
}
}
template <typename T>
std::vector<u32> searchWorker(DebugInterface* cpu, std::vector<u32> searchAddresses, SearchComparison searchComparison, u32 start, u32 end, T searchValue)
{
std::vector<u32> hitAddresses;
const bool isSearchingRange = searchAddresses.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))
{
hitAddresses.push_back(addr);
}
}
}
else
{
for (const u32 addr : searchAddresses)
{
if (!cpu->isValidAddress(addr))
continue;
T readValue = readValueAtAddress<T>(cpu, addr);
if (memoryValueComparator(searchComparison, searchValue, readValue))
{
hitAddresses.push_back(addr);
}
}
}
return hitAddresses;
}
static bool compareByteArrayAtAddress(DebugInterface* cpu, SearchComparison searchComparison, u32 addr, QByteArray value)
{
const bool isNotOperator = searchComparison == SearchComparison::NotEquals;
for (qsizetype i = 0; i < value.length(); i++)
{
const char nextByte = cpu->read8(addr + i);
switch (searchComparison)
{
case SearchComparison::Equals:
{
if (nextByte != value[i])
return false;
break;
}
case SearchComparison::NotEquals:
{
if (nextByte != value[i])
return true;
break;
}
default:
{
Console.Error("Debugger: Unknown search comparison when doing memory search");
return false;
}
}
}
return !isNotOperator;
}
static std::vector<u32> searchWorkerByteArray(DebugInterface* cpu, SearchComparison searchComparison, std::vector<u32> searchAddresses, u32 start, u32 end, QByteArray value)
{
std::vector<u32> hitAddresses;
const bool isSearchingRange = searchAddresses.size() <= 0;
if (isSearchingRange)
{
for (u32 addr = start; addr < end; addr += 1)
{
if (compareByteArrayAtAddress(cpu, searchComparison, addr, value))
{
hitAddresses.emplace_back(addr);
addr += value.length() - 1;
}
}
}
else
{
for (u32 addr : searchAddresses)
{
if (compareByteArrayAtAddress(cpu, searchComparison, addr, value))
{
hitAddresses.emplace_back(addr);
}
}
}
return hitAddresses;
}
std::vector<u32> startWorker(DebugInterface* cpu, const SearchType type, const SearchComparison searchComparison, std::vector<u32> searchAddresses, 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));
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));
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));
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));
case SearchType::FloatType:
return searchWorker<float>(cpu, searchAddresses, searchComparison, start, end, value.toFloat());
case SearchType::DoubleType:
return searchWorker<double>(cpu, searchAddresses, searchComparison, start, end, value.toDouble());
case SearchType::StringType:
return searchWorkerByteArray(cpu, searchComparison, searchAddresses, start, end, value.toUtf8());
case SearchType::ArrayType:
return searchWorkerByteArray(cpu, searchComparison, searchAddresses, start, end, QByteArray::fromHex(value.toUtf8()));
default:
Console.Error("Debugger: Unknown type when doing memory search!");
break;
};
return {};
}
void MemorySearchWidget::onSearchButtonClicked()
{
if (!m_cpu->isAlive())
return;
const SearchType searchType = static_cast<SearchType>(m_ui.cmbSearchType->currentIndex());
const bool searchHex = m_ui.chkSearchHex->isChecked();
bool ok;
const u32 searchStart = m_ui.txtSearchStart->text().toUInt(&ok, 16);
if (!ok)
{
QMessageBox::critical(this, tr("Debugger"), tr("Invalid start address"));
return;
}
const u32 searchEnd = m_ui.txtSearchEnd->text().toUInt(&ok, 16);
if (!ok)
{
QMessageBox::critical(this, tr("Debugger"), tr("Invalid end address"));
return;
}
if (searchStart >= searchEnd)
{
QMessageBox::critical(this, tr("Debugger"), tr("Start address can't be equal to or greater than the end address"));
return;
}
const QString searchValue = m_ui.txtSearchValue->text();
const SearchComparison searchComparison = static_cast<SearchComparison>(m_ui.cmbSearchComparison->currentIndex());
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:
case SearchType::Int16Type:
case SearchType::Int32Type:
case SearchType::Int64Type:
value = searchValue.toULongLong(&ok, searchHex ? 16 : 10);
break;
case SearchType::FloatType:
case SearchType::DoubleType:
searchValue.toDouble(&ok);
break;
case SearchType::StringType:
ok = !searchValue.isEmpty();
break;
case SearchType::ArrayType:
ok = !searchValue.trimmed().isEmpty();
break;
}
if (!ok)
{
QMessageBox::critical(this, tr("Debugger"), tr("Invalid search value"));
return;
}
switch (searchType)
{
case SearchType::ArrayType:
case SearchType::StringType:
case SearchType::DoubleType:
case SearchType::FloatType:
break;
case SearchType::Int64Type:
if (value <= std::numeric_limits<unsigned long long>::max())
break;
case SearchType::Int32Type:
if (value <= std::numeric_limits<unsigned long>::max())
break;
case SearchType::Int16Type:
if (value <= std::numeric_limits<unsigned short>::max())
break;
case SearchType::ByteType:
if (value <= std::numeric_limits<unsigned char>::max())
break;
default:
QMessageBox::critical(this, tr("Debugger"), tr("Value is larger than type"));
return;
}
QFutureWatcher<std::vector<u32>>* workerWatcher = new QFutureWatcher<std::vector<u32>>;
connect(workerWatcher, &QFutureWatcher<std::vector<u32>>::finished, [this, workerWatcher] {
m_ui.btnSearch->setDisabled(false);
m_ui.listSearchResults->clear();
const auto& results = workerWatcher->future().result();
m_searchResults = results;
loadSearchResults();
m_ui.btnFilterSearch->setDisabled(m_ui.listSearchResults->count() == 0);
});
m_ui.btnSearch->setDisabled(true);
std::vector<u32> addresses;
if (isFilterSearch)
{
addresses = m_searchResults;
}
QFuture<std::vector<u32>> workerFuture =
QtConcurrent::run(startWorker, m_cpu, searchType, searchComparison, addresses, searchStart, searchEnd, searchValue, searchHex ? 16 : 10);
workerWatcher->setFuture(workerFuture);
}
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);
if (!m_resultsLoadTimer.isActive() && hasResultsToLoad && scrolledSufficiently)
{
// Load results once timer ends, allowing us to debounce repeated requests and only do one load.
m_resultsLoadTimer.start();
}
}
void MemorySearchWidget::loadSearchResults()
{
const u32 numLoaded = m_ui.listSearchResults->count();
const u32 amountLeftToLoad = m_searchResults.size() - numLoaded;
if (amountLeftToLoad < 1)
return;
const bool isFirstLoad = numLoaded == 0;
const u32 maxLoadAmount = isFirstLoad ? m_initialResultsLoadLimit : m_numResultsAddedPerLoad;
const u32 numToLoad = amountLeftToLoad > maxLoadAmount ? maxLoadAmount : amountLeftToLoad;
for (u32 i = 0; i < numToLoad; i++)
{
u32 address = m_searchResults.at(numLoaded + i);
QListWidgetItem* item = new QListWidgetItem(QtUtils::FilledQStringFromValue(address, 16));
item->setData(Qt::UserRole, address);
m_ui.listSearchResults->addItem(item);
}
}

View File

@ -0,0 +1,70 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#pragma once
#include "ui_MemorySearchWidget.h"
#include "DebugTools/DebugInterface.h"
#include <QtWidgets/QWidget>
#include <QtCore/QTimer>
class MemorySearchWidget final : public QWidget
{
Q_OBJECT
public:
MemorySearchWidget(QWidget* parent);
~MemorySearchWidget() = default;
void setCpu(DebugInterface* cpu);
enum class SearchType
{
ByteType,
Int16Type,
Int32Type,
Int64Type,
FloatType,
DoubleType,
StringType,
ArrayType
};
// Note: The order of these enum values must reflect the order in thee Search Comparison combobox.
enum class SearchComparison
{
Equals,
NotEquals,
GreaterThan,
GreaterThanOrEqual,
LessThan,
LessThanOrEqual
};
public slots:
void onSearchButtonClicked();
void onSearchResultsListScroll(u32 value);
void loadSearchResults();
void contextSearchResultGoToDisassembly();
void contextRemoveSearchResult();
void contextCopySearchResultAddress();
void onListSearchResultsContextMenu(QPoint pos);
signals:
void addAddressToSavedAddressesList(u32 address);
void goToAddressInDisassemblyView(u32 address);
void goToAddressInMemoryView(u32 address);
void switchToMemoryViewTab();
private:
std::vector<u32> m_searchResults;
Ui::MemorySearchWidget m_ui;
DebugInterface* m_cpu;
QTimer m_resultsLoadTimer;
u32 m_initialResultsLoadLimit = 20000;
u32 m_numResultsAddedPerLoad = 10000;
};

View File

@ -0,0 +1,201 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MemorySearchWidget</class>
<widget class="QWidget" name="MemorySearchWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="font">
<font>
<family>Monospace</family>
</font>
</property>
<property name="windowTitle">
<string/>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Value</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="txtSearchValue"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Type</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="cmbSearchType">
<item>
<property name="text">
<string>1 Byte (8 bits)</string>
</property>
</item>
<item>
<property name="text">
<string>2 Bytes (16 bits)</string>
</property>
</item>
<item>
<property name="text">
<string>4 Bytes (32 bits)</string>
</property>
</item>
<item>
<property name="text">
<string>8 Bytes (64 bits)</string>
</property>
</item>
<item>
<property name="text">
<string>Float</string>
</property>
</item>
<item>
<property name="text">
<string>Double</string>
</property>
</item>
<item>
<property name="text">
<string>String</string>
</property>
</item>
<item>
<property name="text">
<string>Array of byte</string>
</property>
</item>
</widget>
</item>
<item row="2" column="2">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Hex</string>
</property>
</widget>
</item>
<item row="2" column="3">
<widget class="QCheckBox" name="chkSearchHex">
<property name="text">
<string/>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="2" colspan="2">
<widget class="QPushButton" name="btnSearch">
<property name="text">
<string>Search</string>
</property>
</widget>
</item>
<item row="1" column="2" colspan="2">
<widget class="QPushButton" name="btnFilterSearch">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Filter Search</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="cmbSearchComparison">
<item>
<property name="text">
<string>Equals</string>
</property>
</item>
<item>
<property name="text">
<string>Not Equals</string>
</property>
</item>
<item>
<property name="text">
<string>Greater Than</string>
</property>
</item>
<item>
<property name="text">
<string>Greater Than Or Equal</string>
</property>
</item>
<item>
<property name="text">
<string>Less Than</string>
</property>
</item>
<item>
<property name="text">
<string>Less Than Or Equal</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="comparisonLabel">
<property name="text">
<string>Comparison</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0" alignment="Qt::AlignLeft">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Start</string>
</property>
</widget>
</item>
<item row="0" column="1" alignment="Qt::AlignLeft">
<widget class="QLineEdit" name="txtSearchStart">
<property name="text">
<string notr="true">0x00</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="label_5">
<property name="text">
<string>End</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QLineEdit" name="txtSearchEnd">
<property name="text">
<string notr="true">0x2000000</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QListWidget" name="listSearchResults"/>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -103,6 +103,7 @@
<ClCompile Include="Debugger\CpuWidget.cpp" />
<ClCompile Include="Debugger\DebuggerWindow.cpp" />
<ClCompile Include="Debugger\DisassemblyWidget.cpp" />
<ClCompile Include="Debugger\MemorySearchWidget.cpp" />
<ClCompile Include="Debugger\MemoryViewWidget.cpp" />
<ClCompile Include="Debugger\RegisterWidget.cpp" />
<ClCompile Include="Debugger\BreakpointDialog.cpp" />
@ -194,6 +195,7 @@
<QtMoc Include="Debugger\CpuWidget.h" />
<QtMoc Include="Debugger\DebuggerWindow.h" />
<QtMoc Include="Debugger\DisassemblyWidget.h" />
<QtMoc Include="Debugger\MemorySearchWidget.h" />
<QtMoc Include="Debugger\MemoryViewWidget.h" />
<QtMoc Include="Debugger\RegisterWidget.h" />
<QtMoc Include="Debugger\BreakpointDialog.h" />
@ -252,6 +254,7 @@
<ClCompile Include="$(IntDir)Debugger\moc_CpuWidget.cpp" />
<ClCompile Include="$(IntDir)Debugger\moc_DisassemblyWidget.cpp" />
<ClCompile Include="$(IntDir)Debugger\moc_RegisterWidget.cpp" />
<ClCompile Include="$(IntDir)Debugger\moc_MemorySearchWidget.cpp" />
<ClCompile Include="$(IntDir)Debugger\moc_MemoryViewWidget.cpp" />
<ClCompile Include="$(IntDir)Debugger\moc_BreakpointDialog.cpp" />
<ClCompile Include="$(IntDir)Debugger\Models\moc_BreakpointModel.cpp" />
@ -380,6 +383,9 @@
<QtUi Include="Debugger\RegisterWidget.ui">
<FileType>Document</FileType>
</QtUi>
<QtUi Include="Debugger\MemorySearchWidget.ui">
<FileType>Document</FileType>
</QtUi>
<QtUi Include="Debugger\MemoryViewWidget.ui">
<FileType>Document</FileType>
</QtUi>

View File

@ -272,6 +272,9 @@
<ClCompile Include="Debugger\RegisterWidget.cpp">
<Filter>Debugger</Filter>
</ClCompile>
<ClCompile Include="Debugger\MemorySearchWidget.cpp">
<Filter>Debugger</Filter>
</ClCompile>
<ClCompile Include="Debugger\MemoryViewWidget.cpp">
<Filter>Debugger</Filter>
</ClCompile>
@ -302,6 +305,9 @@
<ClCompile Include="$(IntDir)Debugger\moc_RegisterWidget.cpp">
<Filter>moc</Filter>
</ClCompile>
<ClCompile Include="$(IntDir)Debugger\moc_MemorySearchWidget.cpp">
<Filter>moc</Filter>
</ClCompile>
<ClCompile Include="$(IntDir)Debugger\moc_MemoryViewWidget.cpp">
<Filter>moc</Filter>
</ClCompile>
@ -483,6 +489,9 @@
<QtMoc Include="Debugger\RegisterWidget.h">
<Filter>Debugger</Filter>
</QtMoc>
<QtMoc Include="Debugger\MemorySearchWidget.h">
<Filter>Debugger</Filter>
</QtMoc>
<QtMoc Include="Debugger\MemoryViewWidget.h">
<Filter>Debugger</Filter>
</QtMoc>
@ -625,6 +634,9 @@
<QtUi Include="Debugger\RegisterWidget.ui">
<Filter>Debugger</Filter>
</QtUi>
<QtUi Include="Debugger\MemorySearchWidget.ui">
<Filter>Debugger</Filter>
</QtUi>
<QtUi Include="Debugger\MemoryViewWidget.ui">
<Filter>Debugger</Filter>
</QtUi>