Add MCP scanner tools, source.modules, reconnect, and constraint regions

Scanner engine:
- Add constrainRegions to ScanRequest — callers pass address ranges
  that are intersected with provider regions before scanning
- Merge overlapping/adjacent constraints to prevent duplicate results
- Fix final-chunk overlap: skip overlap advance on last chunk to avoid
  re-scanning the tail of a region

MCP tools:
- scanner.scan: value scans (int/float types) with optional region
  constraints, returns first 15 addresses
- scanner.scan_pattern: pattern/signature scans with wildcards
- source.modules: list loaded modules with base address and size
- mcp.reconnect: graceful client disconnect for IDE reconnection
- parseInteger() helper for hex string args (avoids JSON double
  precision loss on 64-bit addresses)
- Fix baseRelative semantics in hex.read/hex.write (was inverted)
- Auto-set tree.baseAddress from provider after process attach

Scanner panel:
- runValueScanAndWait() and runPatternScanAndWait() for blocking
  scan execution from MCP/automation code

Tests: 41 new test cases for constrainRegions covering gaps, partial
overlap, adjacent regions, writable filter, degenerate ranges,
overlapping constraints, boundary patterns, alignment, and value
types at region start/end positions.
This commit is contained in:
noita-player
2026-03-04 18:55:09 -08:00
parent 7b9b140823
commit 51de48a6ed
7 changed files with 1305 additions and 52 deletions

View File

@@ -10,6 +10,7 @@
#include <QApplication>
#include <QMenu>
#include <QPainter>
#include <QEventLoop>
namespace rcx {
@@ -418,6 +419,98 @@ ScanRequest ScannerPanel::buildRequest() {
return req;
}
QVector<ScanResult> ScannerPanel::runValueScanAndWait(ValueType valueType, const QString& value,
bool filterExecutable, bool filterWritable,
const QVector<AddressRange>& constrainRegions) {
QVector<ScanResult> results;
QString err;
ScanRequest req;
if (!serializeValue(valueType, value, req.pattern, req.mask, &err)) {
m_statusLabel->setText(QStringLiteral("Value error: %1").arg(err));
return results;
}
req.alignment = naturalAlignment(valueType);
req.filterExecutable = filterExecutable;
req.filterWritable = filterWritable;
req.constrainRegions = constrainRegions;
auto provider = m_providerGetter ? m_providerGetter() : nullptr;
if (!provider) {
m_statusLabel->setText(QStringLiteral("No provider (attach to a process or open a file first)"));
return results;
}
if (m_engine->isRunning()) {
m_statusLabel->setText(QStringLiteral("Scan already in progress"));
return results;
}
m_lastScanMode = 1;
m_lastValueType = valueType;
m_lastPattern = req.pattern;
m_progressBar->setValue(0);
m_progressBar->show();
m_statusLabel->setText(QStringLiteral("Scanning..."));
QEventLoop loop;
connect(m_engine, &ScanEngine::finished, this, [&results, &loop](const QVector<ScanResult>& r) {
results = r;
loop.quit();
}, Qt::SingleShotConnection);
m_engine->start(provider, req);
loop.exec();
return results;
}
QVector<ScanResult> ScannerPanel::runPatternScanAndWait(const QString& pattern,
bool filterExecutable, bool filterWritable,
const QVector<AddressRange>& constrainRegions) {
auto provider = m_providerGetter ? m_providerGetter() : nullptr;
return runPatternScanAndWait(provider, pattern, filterExecutable, filterWritable, constrainRegions);
}
QVector<ScanResult> ScannerPanel::runPatternScanAndWait(std::shared_ptr<Provider> provider,
const QString& pattern,
bool filterExecutable, bool filterWritable,
const QVector<AddressRange>& constrainRegions) {
QVector<ScanResult> results;
QString err;
ScanRequest req;
if (!parseSignature(pattern, req.pattern, req.mask, &err)) {
m_statusLabel->setText(QStringLiteral("Pattern error: %1").arg(err));
return results;
}
req.alignment = 1;
req.filterExecutable = filterExecutable;
req.filterWritable = filterWritable;
req.constrainRegions = constrainRegions;
if (!provider) {
m_statusLabel->setText(QStringLiteral("No provider (attach to a process or open a file first)"));
return results;
}
if (m_engine->isRunning()) {
m_statusLabel->setText(QStringLiteral("Scan already in progress"));
return results;
}
m_lastScanMode = 0;
m_lastPattern = req.pattern;
m_progressBar->setValue(0);
m_progressBar->show();
m_statusLabel->setText(QStringLiteral("Scanning..."));
QEventLoop loop;
connect(m_engine, &ScanEngine::finished, this, [&results, &loop](const QVector<ScanResult>& r) {
results = r;
loop.quit();
}, Qt::SingleShotConnection);
m_engine->start(provider, req);
loop.exec();
return results;
}
void ScannerPanel::onScanFinished(QVector<ScanResult> results) {
m_scanBtn->setText(QStringLiteral("Scan"));
m_progressBar->hide();