mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
fix: rescan performance overhaul, background thread, WinDbg regions
Move rescan to background thread via ScanEngine::startRescan() to prevent UI freeze. Fix populateTable bottleneck caused by QHeaderView::ResizeToContents iterating all rows (6s -> 0ms for 512 results). Add chunked batch reads (256KB spans), enumerateRegions() for WinDbg/ProcessMemory providers, cancel support, and diagnostic logging throughout the scanner pipeline.
This commit is contained in:
111
src/main.cpp
111
src/main.cpp
@@ -440,6 +440,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) {
|
||||
setCentralWidget(m_mdiArea);
|
||||
|
||||
createWorkspaceDock();
|
||||
createScannerDock();
|
||||
createMenus();
|
||||
createStatusBar();
|
||||
|
||||
@@ -611,6 +612,11 @@ void MainWindow::createMenus() {
|
||||
|
||||
view->addSeparator();
|
||||
view->addAction(m_workspaceDock->toggleViewAction());
|
||||
{
|
||||
auto* scanAct = m_scannerDock->toggleViewAction();
|
||||
scanAct->setShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_S));
|
||||
view->addAction(scanAct);
|
||||
}
|
||||
|
||||
// Tools
|
||||
auto* tools = m_titleBar->menuBar()->addMenu("&Tools");
|
||||
@@ -1813,6 +1819,25 @@ void MainWindow::applyTheme(const Theme& theme) {
|
||||
"QToolButton:hover { color: %2; }")
|
||||
.arg(theme.textDim.name(), theme.indHoverSpan.name()));
|
||||
|
||||
// Scanner dock
|
||||
if (m_scannerPanel)
|
||||
m_scannerPanel->applyTheme(theme);
|
||||
if (m_scanDockTitle) {
|
||||
QPalette lp = m_scanDockTitle->palette();
|
||||
lp.setColor(QPalette::WindowText, theme.textDim);
|
||||
m_scanDockTitle->setPalette(lp);
|
||||
}
|
||||
if (auto* titleBar = m_scannerDock ? m_scannerDock->titleBarWidget() : nullptr) {
|
||||
QPalette tbPal = titleBar->palette();
|
||||
tbPal.setColor(QPalette::Window, theme.backgroundAlt);
|
||||
titleBar->setPalette(tbPal);
|
||||
}
|
||||
if (m_scanDockCloseBtn)
|
||||
m_scanDockCloseBtn->setStyleSheet(QStringLiteral(
|
||||
"QToolButton { color: %1; border: none; padding: 0px 4px 2px 4px; font-size: 12px; }"
|
||||
"QToolButton:hover { color: %2; }")
|
||||
.arg(theme.textDim.name(), theme.indHoverSpan.name()));
|
||||
|
||||
// Rendered C/C++ views: update lexer colors, paper, margins
|
||||
for (auto& tab : m_tabs) {
|
||||
for (auto& pane : tab.panes) {
|
||||
@@ -1933,6 +1958,11 @@ void MainWindow::setEditorFont(const QString& fontName) {
|
||||
// Sync dock titlebar font
|
||||
if (m_dockTitleLabel)
|
||||
m_dockTitleLabel->setFont(f);
|
||||
// Sync scanner panel font
|
||||
if (m_scannerPanel)
|
||||
m_scannerPanel->setEditorFont(f);
|
||||
if (m_scanDockTitle)
|
||||
m_scanDockTitle->setFont(f);
|
||||
}
|
||||
|
||||
RcxController* MainWindow::activeController() const {
|
||||
@@ -2814,6 +2844,87 @@ void MainWindow::createWorkspaceDock() {
|
||||
});
|
||||
}
|
||||
|
||||
// ── Scanner Dock ──
|
||||
|
||||
void MainWindow::createScannerDock() {
|
||||
m_scannerDock = new QDockWidget("Scanner", this);
|
||||
m_scannerDock->setObjectName("ScannerDock");
|
||||
m_scannerDock->setAllowedAreas(
|
||||
Qt::BottomDockWidgetArea | Qt::RightDockWidgetArea | Qt::LeftDockWidgetArea);
|
||||
m_scannerDock->setFeatures(
|
||||
QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable |
|
||||
QDockWidget::DockWidgetFloatable);
|
||||
|
||||
// Custom titlebar: label + close button (matches workspace dock)
|
||||
{
|
||||
const auto& t = ThemeManager::instance().current();
|
||||
|
||||
auto* titleBar = new QWidget(m_scannerDock);
|
||||
titleBar->setFixedHeight(24);
|
||||
titleBar->setAutoFillBackground(true);
|
||||
{
|
||||
QPalette tbPal = titleBar->palette();
|
||||
tbPal.setColor(QPalette::Window, t.backgroundAlt);
|
||||
titleBar->setPalette(tbPal);
|
||||
}
|
||||
auto* layout = new QHBoxLayout(titleBar);
|
||||
layout->setContentsMargins(6, 2, 2, 2);
|
||||
layout->setSpacing(0);
|
||||
|
||||
m_scanDockTitle = new QLabel("Scanner", titleBar);
|
||||
{
|
||||
QPalette lp = m_scanDockTitle->palette();
|
||||
lp.setColor(QPalette::WindowText, t.textDim);
|
||||
m_scanDockTitle->setPalette(lp);
|
||||
}
|
||||
layout->addWidget(m_scanDockTitle);
|
||||
|
||||
layout->addStretch();
|
||||
|
||||
m_scanDockCloseBtn = new QToolButton(titleBar);
|
||||
m_scanDockCloseBtn->setText(QStringLiteral("\u2715"));
|
||||
m_scanDockCloseBtn->setAutoRaise(true);
|
||||
m_scanDockCloseBtn->setCursor(Qt::PointingHandCursor);
|
||||
m_scanDockCloseBtn->setStyleSheet(QStringLiteral(
|
||||
"QToolButton { color: %1; border: none; padding: 0px 4px 2px 4px; font-size: 12px; }"
|
||||
"QToolButton:hover { color: %2; }")
|
||||
.arg(t.textDim.name(), t.indHoverSpan.name()));
|
||||
connect(m_scanDockCloseBtn, &QToolButton::clicked, m_scannerDock, &QDockWidget::close);
|
||||
layout->addWidget(m_scanDockCloseBtn);
|
||||
|
||||
m_scannerDock->setTitleBarWidget(titleBar);
|
||||
}
|
||||
|
||||
m_scannerPanel = new ScannerPanel(m_scannerDock);
|
||||
m_scannerPanel->applyTheme(ThemeManager::instance().current());
|
||||
{
|
||||
QSettings settings("Reclass", "Reclass");
|
||||
QString fontName = settings.value("font", "JetBrains Mono").toString();
|
||||
QFont f(fontName, 12);
|
||||
f.setFixedPitch(true);
|
||||
m_scannerPanel->setEditorFont(f);
|
||||
m_scanDockTitle->setFont(f);
|
||||
}
|
||||
m_scannerDock->setWidget(m_scannerPanel);
|
||||
addDockWidget(Qt::BottomDockWidgetArea, m_scannerDock);
|
||||
m_scannerDock->hide();
|
||||
|
||||
// Wire provider getter: lazily captures the active tab's provider at scan time
|
||||
m_scannerPanel->setProviderGetter([this]() -> std::shared_ptr<rcx::Provider> {
|
||||
auto* ctrl = activeController();
|
||||
return ctrl ? ctrl->document()->provider : nullptr;
|
||||
});
|
||||
|
||||
// Wire "Go to Address" to rebase the active tab
|
||||
connect(m_scannerPanel, &ScannerPanel::goToAddress, this, [this](uint64_t addr) {
|
||||
auto* ctrl = activeController();
|
||||
if (!ctrl) return;
|
||||
ctrl->document()->tree.baseAddress = addr;
|
||||
ctrl->document()->tree.baseAddressFormula.clear();
|
||||
ctrl->refresh();
|
||||
});
|
||||
}
|
||||
|
||||
void MainWindow::rebuildAllDocs() {
|
||||
m_allDocs.clear();
|
||||
for (auto it = m_tabs.begin(); it != m_tabs.end(); ++it)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "controller.h"
|
||||
#include "titlebar.h"
|
||||
#include "pluginmanager.h"
|
||||
#include "scannerpanel.h"
|
||||
#include <QMainWindow>
|
||||
#include <QMdiArea>
|
||||
#include <QMdiSubWindow>
|
||||
@@ -152,6 +153,13 @@ private:
|
||||
void rebuildWorkspaceModel();
|
||||
void updateBorderColor(const QColor& color);
|
||||
|
||||
// Scanner dock
|
||||
QDockWidget* m_scannerDock = nullptr;
|
||||
ScannerPanel* m_scannerPanel = nullptr;
|
||||
QLabel* m_scanDockTitle = nullptr;
|
||||
QToolButton* m_scanDockCloseBtn = nullptr;
|
||||
void createScannerDock();
|
||||
|
||||
protected:
|
||||
void changeEvent(QEvent* event) override;
|
||||
void resizeEvent(QResizeEvent* event) override;
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
#pragma once
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
namespace rcx {
|
||||
|
||||
struct MemoryRegion {
|
||||
uint64_t base = 0;
|
||||
uint64_t size = 0;
|
||||
bool readable = true;
|
||||
bool writable = false;
|
||||
bool executable = false;
|
||||
QString moduleName;
|
||||
};
|
||||
|
||||
class Provider {
|
||||
public:
|
||||
virtual ~Provider() = default;
|
||||
@@ -54,6 +64,11 @@ public:
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Enumerate committed/readable memory regions.
|
||||
// Used by the scan engine to know what address ranges to scan.
|
||||
// Default: returns empty (scan engine falls back to [0, size())).
|
||||
virtual QVector<MemoryRegion> enumerateRegions() const { return {}; }
|
||||
|
||||
// --- Derived convenience (non-virtual, never override) ---
|
||||
|
||||
bool isValid() const { return size() > 0; }
|
||||
|
||||
108
src/scanner.cpp
108
src/scanner.cpp
@@ -1,8 +1,11 @@
|
||||
#include "scanner.h"
|
||||
#include <QtConcurrent>
|
||||
#include <QMetaObject>
|
||||
#include <QElapsedTimer>
|
||||
#include <QDebug>
|
||||
#include <cstring>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
||||
namespace rcx {
|
||||
|
||||
@@ -393,12 +396,20 @@ void ScanEngine::start(std::shared_ptr<Provider> provider, const ScanRequest& re
|
||||
QVector<ScanResult> ScanEngine::runScan(std::shared_ptr<Provider> prov,
|
||||
const ScanRequest& req)
|
||||
{
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
|
||||
QVector<ScanResult> results;
|
||||
|
||||
if (!prov || req.pattern.isEmpty())
|
||||
return results;
|
||||
|
||||
auto regions = prov->enumerateRegions();
|
||||
qDebug() << "[scan] regions:" << regions.size()
|
||||
<< " pattern:" << req.pattern.size() << "bytes"
|
||||
<< " align:" << req.alignment
|
||||
<< " filterExec:" << req.filterExecutable
|
||||
<< " filterWrite:" << req.filterWritable;
|
||||
|
||||
// Fallback for providers that don't enumerate regions (file/buffer)
|
||||
if (regions.isEmpty()) {
|
||||
@@ -424,6 +435,8 @@ QVector<ScanResult> ScanEngine::runScan(std::shared_ptr<Provider> prov,
|
||||
totalBytes += r.size;
|
||||
}
|
||||
|
||||
qDebug() << "[scan] total scannable:" << (totalBytes / 1024) << "KB across filtered regions";
|
||||
|
||||
if (totalBytes == 0) return results;
|
||||
|
||||
uint64_t scannedBytes = 0;
|
||||
@@ -473,6 +486,7 @@ QVector<ScanResult> ScanEngine::runScan(std::shared_ptr<Provider> prov,
|
||||
ScanResult r;
|
||||
r.address = region.base + off + (uint64_t)i;
|
||||
r.regionModule = region.moduleName;
|
||||
r.scanValue = QByteArray(data + i, qMin(16, readLen - i));
|
||||
results.append(r);
|
||||
|
||||
if (results.size() >= req.maxResults)
|
||||
@@ -501,6 +515,100 @@ QVector<ScanResult> ScanEngine::runScan(std::shared_ptr<Provider> prov,
|
||||
}
|
||||
|
||||
done:
|
||||
qDebug() << "[scan] done:" << results.size() << "results in" << timer.elapsed() << "ms"
|
||||
<< " scanned:" << (scannedBytes / 1024) << "KB";
|
||||
return results;
|
||||
}
|
||||
|
||||
void ScanEngine::startRescan(std::shared_ptr<Provider> provider,
|
||||
QVector<ScanResult> results, int readSize) {
|
||||
if (isRunning()) return;
|
||||
|
||||
m_abort.store(false);
|
||||
|
||||
auto* watcher = new QFutureWatcher<QVector<ScanResult>>(this);
|
||||
m_watcher = watcher;
|
||||
|
||||
connect(watcher, &QFutureWatcher<QVector<ScanResult>>::finished, this, [this, watcher]() {
|
||||
auto results = watcher->result();
|
||||
watcher->deleteLater();
|
||||
if (m_watcher == watcher)
|
||||
m_watcher = nullptr;
|
||||
emit rescanFinished(results);
|
||||
});
|
||||
|
||||
watcher->setFuture(QtConcurrent::run(
|
||||
[this, provider, results = std::move(results), readSize]() mutable {
|
||||
return runRescan(provider, std::move(results), readSize);
|
||||
}));
|
||||
}
|
||||
|
||||
QVector<ScanResult> ScanEngine::runRescan(std::shared_ptr<Provider> prov,
|
||||
QVector<ScanResult> results, int readSize) {
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
|
||||
int total = results.size();
|
||||
if (total == 0 || !prov) return results;
|
||||
|
||||
qDebug() << "[rescan] start: " << total << "results, readSize:" << readSize;
|
||||
|
||||
// Save previous values
|
||||
for (auto& r : results)
|
||||
r.previousValue = r.scanValue;
|
||||
|
||||
// Sort indices by address for sequential chunked reads
|
||||
QVector<int> order(total);
|
||||
for (int i = 0; i < total; i++) order[i] = i;
|
||||
std::sort(order.begin(), order.end(), [&results](int a, int b) {
|
||||
return results[a].address < results[b].address;
|
||||
});
|
||||
|
||||
constexpr int kChunk = 256 * 1024;
|
||||
int updated = 0;
|
||||
int lastPct = -1;
|
||||
int chunks = 0;
|
||||
uint64_t totalBytesRead = 0;
|
||||
int i = 0;
|
||||
|
||||
while (i < total && !m_abort.load()) {
|
||||
uint64_t spanBase = results[order[i]].address;
|
||||
int spanEnd = i;
|
||||
|
||||
// Extend span while next result fits in the same chunk
|
||||
while (spanEnd + 1 < total) {
|
||||
uint64_t endAddr = results[order[spanEnd + 1]].address + readSize;
|
||||
if (endAddr - spanBase > (uint64_t)kChunk) break;
|
||||
spanEnd++;
|
||||
}
|
||||
|
||||
uint64_t spanLast = results[order[spanEnd]].address;
|
||||
int chunkLen = (int)(spanLast + readSize - spanBase);
|
||||
QByteArray chunk(chunkLen, '\0');
|
||||
prov->read(spanBase, chunk.data(), chunkLen);
|
||||
|
||||
for (int j = i; j <= spanEnd; j++) {
|
||||
auto& r = results[order[j]];
|
||||
int off = (int)(r.address - spanBase);
|
||||
r.scanValue = chunk.mid(off, readSize);
|
||||
}
|
||||
|
||||
chunks++;
|
||||
totalBytesRead += chunkLen;
|
||||
updated += (spanEnd - i + 1);
|
||||
i = spanEnd + 1;
|
||||
|
||||
int pct = updated * 100 / total;
|
||||
if (pct != lastPct) {
|
||||
lastPct = pct;
|
||||
QMetaObject::invokeMethod(this, "progress",
|
||||
Qt::QueuedConnection, Q_ARG(int, pct));
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "[rescan] done:" << updated << "/" << total << "results in"
|
||||
<< timer.elapsed() << "ms |" << chunks << "chunks,"
|
||||
<< (totalBytesRead / 1024) << "KB read";
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
@@ -65,16 +65,21 @@ public:
|
||||
explicit ScanEngine(QObject* parent = nullptr);
|
||||
|
||||
void start(std::shared_ptr<Provider> provider, const ScanRequest& req);
|
||||
void startRescan(std::shared_ptr<Provider> provider,
|
||||
QVector<ScanResult> results, int readSize);
|
||||
void abort();
|
||||
bool isRunning() const;
|
||||
|
||||
signals:
|
||||
void progress(int percent);
|
||||
void finished(QVector<ScanResult> results);
|
||||
void rescanFinished(QVector<ScanResult> results);
|
||||
void error(QString message);
|
||||
|
||||
private:
|
||||
QVector<ScanResult> runScan(std::shared_ptr<Provider> prov, const ScanRequest& req);
|
||||
QVector<ScanResult> runRescan(std::shared_ptr<Provider> prov,
|
||||
QVector<ScanResult> results, int readSize);
|
||||
|
||||
std::atomic<bool> m_abort{false};
|
||||
QFutureWatcher<QVector<ScanResult>>* m_watcher = nullptr;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "scannerpanel.h"
|
||||
#include "addressparser.h"
|
||||
#include <cstring>
|
||||
#include <QElapsedTimer>
|
||||
#include <QDebug>
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QHeaderView>
|
||||
@@ -135,8 +137,8 @@ ScannerPanel::ScannerPanel(QWidget* parent)
|
||||
m_resultTable->setColumnCount(2);
|
||||
m_resultTable->horizontalHeader()->hide();
|
||||
m_resultTable->verticalHeader()->hide();
|
||||
m_resultTable->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
m_resultTable->horizontalHeader()->setStretchLastSection(false);
|
||||
m_resultTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Interactive);
|
||||
m_resultTable->horizontalHeader()->setStretchLastSection(true);
|
||||
m_resultTable->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
m_resultTable->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
m_resultTable->setEditTriggers(QAbstractItemView::DoubleClicked);
|
||||
@@ -169,11 +171,12 @@ ScannerPanel::ScannerPanel(QWidget* parent)
|
||||
|
||||
mainLayout->addLayout(actionRow);
|
||||
|
||||
// ── Initial visibility: signature mode ──
|
||||
// ── Initial state: signature mode ──
|
||||
m_typeLabel->hide();
|
||||
m_typeCombo->hide();
|
||||
m_valueLabel->hide();
|
||||
m_valueEdit->hide();
|
||||
m_execCheck->setChecked(true);
|
||||
|
||||
// ── Connections ──
|
||||
connect(m_modeCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
|
||||
@@ -225,6 +228,8 @@ ScannerPanel::ScannerPanel(QWidget* parent)
|
||||
});
|
||||
connect(m_engine, &ScanEngine::finished,
|
||||
this, &ScannerPanel::onScanFinished);
|
||||
connect(m_engine, &ScanEngine::rescanFinished,
|
||||
this, &ScannerPanel::onRescanFinished);
|
||||
connect(m_engine, &ScanEngine::error, this, [this](const QString& msg) {
|
||||
m_statusLabel->setText(QStringLiteral("Error: %1").arg(msg));
|
||||
m_scanBtn->setText(QStringLiteral("Scan"));
|
||||
@@ -240,6 +245,8 @@ void ScannerPanel::setEditorFont(const QFont& font) {
|
||||
m_resultTable->setFont(font);
|
||||
QFontMetrics fm(font);
|
||||
m_resultTable->verticalHeader()->setDefaultSectionSize(fm.height() + 6);
|
||||
// Address column width: "00000000`00000000" + padding
|
||||
m_resultTable->setColumnWidth(0, fm.horizontalAdvance(QStringLiteral("00000000`00000000")) + 20);
|
||||
m_patternEdit->setFont(font);
|
||||
m_valueEdit->setFont(font);
|
||||
m_modeCombo->setFont(font);
|
||||
@@ -275,15 +282,16 @@ void ScannerPanel::onModeChanged(int index) {
|
||||
m_typeCombo->setVisible(!isSig);
|
||||
m_valueLabel->setVisible(!isSig);
|
||||
m_valueEdit->setVisible(!isSig);
|
||||
|
||||
// Auto-toggle filters: signatures → executable code, values → writable data
|
||||
m_execCheck->setChecked(isSig);
|
||||
m_writeCheck->setChecked(!isSig);
|
||||
}
|
||||
|
||||
void ScannerPanel::onScanClicked() {
|
||||
if (m_engine->isRunning()) {
|
||||
m_engine->abort();
|
||||
m_scanBtn->setText(QStringLiteral("Scan"));
|
||||
m_progressBar->hide();
|
||||
m_statusLabel->setText(QStringLiteral("Scan cancelled"));
|
||||
return;
|
||||
return; // finished/rescanFinished handler resets UI
|
||||
}
|
||||
|
||||
// Get provider
|
||||
@@ -344,41 +352,40 @@ ScanRequest ScannerPanel::buildRequest() {
|
||||
void ScannerPanel::onScanFinished(QVector<ScanResult> results) {
|
||||
m_scanBtn->setText(QStringLiteral("Scan"));
|
||||
m_progressBar->hide();
|
||||
m_results = results;
|
||||
m_results = std::move(results);
|
||||
|
||||
// Cache scan-time bytes
|
||||
if (m_lastScanMode == 1) {
|
||||
// Value mode — every result matched the same value, no re-read needed
|
||||
for (auto& r : m_results) {
|
||||
r.previousValue.clear();
|
||||
// Bytes are cached by the engine during scan.
|
||||
// Value mode: override with exact search pattern (engine caches raw chunk bytes).
|
||||
for (auto& r : m_results) {
|
||||
r.previousValue.clear();
|
||||
if (m_lastScanMode == 1)
|
||||
r.scanValue = m_lastPattern;
|
||||
}
|
||||
} else {
|
||||
// Signature mode — wildcards mean each match may differ, read actual bytes
|
||||
std::shared_ptr<Provider> prov;
|
||||
if (m_providerGetter)
|
||||
prov = m_providerGetter();
|
||||
for (auto& r : m_results) {
|
||||
r.previousValue.clear();
|
||||
r.scanValue = prov ? prov->readBytes(r.address, 16) : QByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
m_updateBtn->setEnabled(!m_results.isEmpty());
|
||||
populateTable(false);
|
||||
{
|
||||
QElapsedTimer pt;
|
||||
pt.start();
|
||||
populateTable(false);
|
||||
qDebug() << "[panel] populateTable(initial):" << m_results.size()
|
||||
<< "results," << pt.elapsed() << "ms";
|
||||
}
|
||||
|
||||
int n = m_results.size();
|
||||
m_statusLabel->setText(QStringLiteral("%1 result%2")
|
||||
.arg(m_results.size())
|
||||
.arg(m_results.size() == 1 ? "" : "s"));
|
||||
.arg(n).arg(n == 1 ? "" : "s"));
|
||||
}
|
||||
|
||||
void ScannerPanel::populateTable(bool showPrevious) {
|
||||
constexpr int kMaxRows = 10000;
|
||||
|
||||
m_resultTable->blockSignals(true);
|
||||
int cols = showPrevious ? 3 : 2;
|
||||
m_resultTable->setColumnCount(cols);
|
||||
m_resultTable->setRowCount(m_results.size());
|
||||
int displayCount = qMin(m_results.size(), kMaxRows);
|
||||
m_resultTable->setRowCount(displayCount);
|
||||
|
||||
for (int i = 0; i < m_results.size(); i++) {
|
||||
for (int i = 0; i < displayCount; i++) {
|
||||
const auto& r = m_results[i];
|
||||
|
||||
// Address column — WinDbg backtick format: 00000000`00000000
|
||||
@@ -402,13 +409,11 @@ void ScannerPanel::populateTable(bool showPrevious) {
|
||||
}
|
||||
}
|
||||
|
||||
m_resultTable->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
m_resultTable->horizontalHeader()->setStretchLastSection(false);
|
||||
m_resultTable->blockSignals(false);
|
||||
}
|
||||
|
||||
void ScannerPanel::onUpdateClicked() {
|
||||
if (m_results.isEmpty()) return;
|
||||
if (m_results.isEmpty() || m_engine->isRunning()) return;
|
||||
|
||||
std::shared_ptr<Provider> prov;
|
||||
if (m_providerGetter)
|
||||
@@ -418,55 +423,34 @@ void ScannerPanel::onUpdateClicked() {
|
||||
return;
|
||||
}
|
||||
|
||||
int readSize = (m_lastScanMode == 1) ? valueSize() : 16;
|
||||
|
||||
m_updateBtn->setEnabled(false);
|
||||
m_scanBtn->setEnabled(false);
|
||||
m_scanBtn->setText(QStringLiteral("Cancel"));
|
||||
m_statusLabel->setText(QStringLiteral("Re-scanning..."));
|
||||
m_progressBar->setValue(0);
|
||||
m_progressBar->show();
|
||||
|
||||
int readSize = (m_lastScanMode == 1) ? valueSize() : 16;
|
||||
int total = m_results.size();
|
||||
m_engine->startRescan(prov, m_results, readSize);
|
||||
}
|
||||
|
||||
// Single pass: read new values + build table rows
|
||||
m_resultTable->blockSignals(true);
|
||||
m_resultTable->setColumnCount(3);
|
||||
m_resultTable->setRowCount(total);
|
||||
void ScannerPanel::onRescanFinished(QVector<ScanResult> results) {
|
||||
m_scanBtn->setText(QStringLiteral("Scan"));
|
||||
m_progressBar->hide();
|
||||
m_results = std::move(results);
|
||||
m_updateBtn->setEnabled(!m_results.isEmpty());
|
||||
|
||||
for (int i = 0; i < total; i++) {
|
||||
auto& r = m_results[i];
|
||||
r.previousValue = r.scanValue;
|
||||
r.scanValue = prov->readBytes(r.address, readSize);
|
||||
|
||||
QString hexPart = QStringLiteral("%1").arg(r.address, 16, 16, QLatin1Char('0')).toUpper();
|
||||
hexPart.insert(8, '`');
|
||||
auto* addrItem = new QTableWidgetItem(hexPart);
|
||||
addrItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable);
|
||||
m_resultTable->setItem(i, 0, addrItem);
|
||||
|
||||
auto* valItem = new QTableWidgetItem(formatValue(r.scanValue));
|
||||
valItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable);
|
||||
m_resultTable->setItem(i, 1, valItem);
|
||||
|
||||
auto* prevItem = new QTableWidgetItem(formatValue(r.previousValue));
|
||||
prevItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
|
||||
m_resultTable->setItem(i, 2, prevItem);
|
||||
|
||||
if ((i & 0xFF) == 0) {
|
||||
m_progressBar->setValue(i * 100 / total);
|
||||
QApplication::processEvents();
|
||||
}
|
||||
{
|
||||
QElapsedTimer pt;
|
||||
pt.start();
|
||||
populateTable(true);
|
||||
qDebug() << "[panel] populateTable(rescan):" << m_results.size()
|
||||
<< "results," << pt.elapsed() << "ms";
|
||||
}
|
||||
|
||||
m_resultTable->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
m_resultTable->horizontalHeader()->setStretchLastSection(false);
|
||||
m_resultTable->blockSignals(false);
|
||||
|
||||
m_progressBar->setValue(100);
|
||||
m_progressBar->hide();
|
||||
m_scanBtn->setEnabled(true);
|
||||
m_updateBtn->setEnabled(true);
|
||||
int n = m_results.size();
|
||||
m_statusLabel->setText(QStringLiteral("Updated %1 result%2")
|
||||
.arg(total).arg(total == 1 ? "" : "s"));
|
||||
.arg(n).arg(n == 1 ? "" : "s"));
|
||||
}
|
||||
|
||||
void ScannerPanel::onGoToAddress() {
|
||||
|
||||
@@ -65,6 +65,7 @@ private slots:
|
||||
void onResultDoubleClicked(int row, int col);
|
||||
void onCellEdited(int row, int col);
|
||||
void onUpdateClicked();
|
||||
void onRescanFinished(QVector<ScanResult> results);
|
||||
|
||||
private:
|
||||
ScanRequest buildRequest();
|
||||
|
||||
Reference in New Issue
Block a user