mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
fix: replace remaining QList::append({}) in plugins and tests
Missed plugin and test directories in the previous Qt 6.8 compat fix.
This commit is contained in:
@@ -222,7 +222,7 @@ QVector<rcx::Provider::ThreadInfo> KernelProcessProvider::tebs() const
|
||||
auto* entries = reinterpret_cast<const RcxDrvTebEntry*>(outBuf.constData());
|
||||
|
||||
for (int i = 0; i < count; ++i)
|
||||
result.append({entries[i].tebAddress, entries[i].threadId});
|
||||
result.push_back(ThreadInfo{entries[i].tebAddress, entries[i].threadId});
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
@@ -253,7 +253,7 @@ void KernelProcessProvider::cacheModules()
|
||||
if (i == 0)
|
||||
m_base = entries[i].base;
|
||||
|
||||
m_modules.append({modName, entries[i].base, entries[i].size});
|
||||
m_modules.push_back(ModuleInfo{modName, entries[i].base, entries[i].size});
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ void ProcessMemoryProvider::cacheModules()
|
||||
if (GetModuleFileNameExW(m_handle, mods[i], modPath, MAX_PATH))
|
||||
fullPath = QString::fromWCharArray(modPath);
|
||||
|
||||
m_modules.append({
|
||||
m_modules.push_back(ModuleInfo{
|
||||
QString::fromWCharArray(modName),
|
||||
fullPath,
|
||||
(uint64_t)mi.lpBaseOfDll,
|
||||
@@ -205,7 +205,7 @@ QVector<rcx::Provider::ModuleEntry> ProcessMemoryProvider::enumerateModules() co
|
||||
QVector<ModuleEntry> result;
|
||||
result.reserve(m_modules.size());
|
||||
for (const auto& m : m_modules)
|
||||
result.append({m.name, m.fullPath, m.base, m.size});
|
||||
result.push_back(ModuleEntry{m.name, m.fullPath, m.base, m.size});
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -415,8 +415,9 @@ void ProcessMemoryProvider::cacheModules()
|
||||
for (auto it = moduleRanges.begin(); it != moduleRanges.end(); ++it)
|
||||
{
|
||||
QFileInfo fi(it.key());
|
||||
m_modules.append({
|
||||
m_modules.push_back(ModuleInfo{
|
||||
fi.fileName(),
|
||||
it.key(),
|
||||
it->base,
|
||||
it->end - it->base
|
||||
});
|
||||
@@ -545,7 +546,7 @@ QVector<rcx::Provider::ThreadInfo> ProcessMemoryProvider::tebs() const
|
||||
ULONG tbiLen = 0;
|
||||
NTSTATUS qitSt = pNtQIT(hThread, 0, &tbi, sizeof(tbi), &tbiLen);
|
||||
if (qitSt >= 0 && tbi.TebBaseAddress)
|
||||
result.append({(uint64_t)(uintptr_t)tbi.TebBaseAddress, tid});
|
||||
result.push_back(ThreadInfo{(uint64_t)(uintptr_t)tbi.TebBaseAddress, tid});
|
||||
CloseHandle(hThread);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -244,7 +244,7 @@ struct IpcClient {
|
||||
reinterpret_cast<const char*>(data + entry->nameOffset),
|
||||
(int)entry->nameLength);
|
||||
#endif
|
||||
result.append({modName, entry->base, entry->size});
|
||||
result.push_back(RemoteProcessProvider::ModuleInfo{modName, entry->base, entry->size});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -202,7 +202,7 @@ void BenchProject::benchBuildWorkspaceModel()
|
||||
// Build TabInfo array
|
||||
QVector<TabInfo> tabs;
|
||||
for (const auto& t : trees)
|
||||
tabs.append({ &t, QStringLiteral("test"), nullptr });
|
||||
tabs.push_back(TabInfo{ &t, QStringLiteral("test"), nullptr });
|
||||
|
||||
QStandardItemModel model;
|
||||
const int ITERS = 20;
|
||||
@@ -244,7 +244,7 @@ void BenchProject::benchWorkspaceSearch()
|
||||
|
||||
QVector<TabInfo> tabs;
|
||||
for (const auto& t : trees)
|
||||
tabs.append({ &t, QStringLiteral("test"), nullptr });
|
||||
tabs.push_back(TabInfo{ &t, QStringLiteral("test"), nullptr });
|
||||
|
||||
QStandardItemModel model;
|
||||
buildProjectExplorer(&model, tabs);
|
||||
|
||||
222
tests/grab_tabs.cpp
Normal file
222
tests/grab_tabs.cpp
Normal file
@@ -0,0 +1,222 @@
|
||||
#include <QtTest/QTest>
|
||||
#include <QApplication>
|
||||
#include <QMainWindow>
|
||||
#include <QDockWidget>
|
||||
#include <QTabBar>
|
||||
#include <QTextEdit>
|
||||
#include <QPixmap>
|
||||
#include <QToolButton>
|
||||
#include <QHBoxLayout>
|
||||
#include <QProxyStyle>
|
||||
#include <QStyleOptionTab>
|
||||
#include <QSettings>
|
||||
#include <QPainter>
|
||||
#include "../src/themes/thememanager.h"
|
||||
|
||||
// Minimal replica of the real app's MenuBarStyle for dock tab painting
|
||||
class TestTabStyle : public QProxyStyle {
|
||||
public:
|
||||
using QProxyStyle::QProxyStyle;
|
||||
|
||||
QSize sizeFromContents(ContentsType type, const QStyleOption* opt,
|
||||
const QSize& sz, const QWidget* w) const override {
|
||||
QSize s = QProxyStyle::sizeFromContents(type, opt, sz, w);
|
||||
if (type == CT_TabBarTab) {
|
||||
if (auto* tabBar = qobject_cast<const QTabBar*>(w)) {
|
||||
if (tabBar->parent() && qobject_cast<const QMainWindow*>(tabBar->parent()))
|
||||
s.setHeight(28);
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
void drawControl(ControlElement element, const QStyleOption* opt,
|
||||
QPainter* p, const QWidget* w) const override {
|
||||
// Tab shape — background, accent line, borders
|
||||
if (element == CE_TabBarTabShape) {
|
||||
if (auto* tab = qstyleoption_cast<const QStyleOptionTab*>(opt)) {
|
||||
auto* tabBar = qobject_cast<const QTabBar*>(w);
|
||||
if (tabBar && tabBar->parent() && qobject_cast<QMainWindow*>(tabBar->parent())) {
|
||||
bool selected = tab->state & State_Selected;
|
||||
bool hovered = tab->state & State_MouseOver;
|
||||
QColor bg = tab->palette.color(QPalette::Window);
|
||||
if (hovered && !selected)
|
||||
bg = tab->palette.color(QPalette::Mid);
|
||||
p->fillRect(tab->rect, bg);
|
||||
if (selected)
|
||||
p->fillRect(QRect(tab->rect.left(), tab->rect.top(),
|
||||
tab->rect.width(), 2),
|
||||
tab->palette.color(QPalette::Link));
|
||||
p->setPen(tab->palette.color(QPalette::Dark));
|
||||
p->drawLine(tab->rect.bottomLeft(), tab->rect.bottomRight());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Tab label — middle-elide long names, editor font
|
||||
if (element == CE_TabBarTabLabel) {
|
||||
if (auto* tab = qstyleoption_cast<const QStyleOptionTab*>(opt)) {
|
||||
auto* tabBar = qobject_cast<const QTabBar*>(w);
|
||||
if (tabBar && tabBar->parent() && qobject_cast<QMainWindow*>(tabBar->parent())) {
|
||||
int tabIdx = -1;
|
||||
for (int i = 0; i < tabBar->count(); ++i) {
|
||||
if (tabBar->tabRect(i).contains(tab->rect.center())) { tabIdx = i; break; }
|
||||
}
|
||||
int btnWidth = 0;
|
||||
if (tabIdx >= 0) {
|
||||
auto* btn = tabBar->tabButton(tabIdx, QTabBar::RightSide);
|
||||
if (btn) btnWidth = btn->sizeHint().width() + 4;
|
||||
}
|
||||
QRect textRect = tab->rect.adjusted(8, 0, -(8 + btnWidth), 0);
|
||||
QFont f("JetBrains Mono", 10);
|
||||
f.setFixedPitch(true);
|
||||
p->setFont(f);
|
||||
QFontMetrics fm(f);
|
||||
QString text = (tabIdx >= 0) ? tabBar->tabText(tabIdx) : tab->text;
|
||||
int maxW = textRect.width();
|
||||
if (fm.horizontalAdvance(text) > maxW) {
|
||||
int ellW = fm.horizontalAdvance(QStringLiteral("\u2026"));
|
||||
int avail = maxW - ellW;
|
||||
if (avail > 0) {
|
||||
int half = avail / 2;
|
||||
QString left, right;
|
||||
for (int i = 0; i < text.size(); ++i)
|
||||
if (fm.horizontalAdvance(text.left(i+1)) > half) { left = text.left(i); break; }
|
||||
if (left.isEmpty()) left = text.left(1);
|
||||
for (int i = text.size()-1; i >= 0; --i)
|
||||
if (fm.horizontalAdvance(text.mid(i)) > half) { right = text.mid(i+1); break; }
|
||||
if (right.isEmpty()) right = text.right(1);
|
||||
text = left + QStringLiteral("\u2026") + right;
|
||||
} else {
|
||||
text = QStringLiteral("\u2026");
|
||||
}
|
||||
}
|
||||
bool selected = tab->state & QStyle::State_Selected;
|
||||
p->setPen(selected ? tab->palette.color(QPalette::Text)
|
||||
: tab->palette.color(QPalette::WindowText));
|
||||
p->drawText(textRect, Qt::AlignVCenter | Qt::AlignLeft, text);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
QProxyStyle::drawControl(element, opt, p, w);
|
||||
}
|
||||
};
|
||||
|
||||
class TabBtns : public QWidget {
|
||||
public:
|
||||
explicit TabBtns(const QColor& hover, QWidget* parent = nullptr) : QWidget(parent) {
|
||||
auto* hl = new QHBoxLayout(this);
|
||||
hl->setContentsMargins(2, 0, 0, 0);
|
||||
hl->setSpacing(0);
|
||||
QString style = QStringLiteral(
|
||||
"QToolButton { border: none; padding: 1px; border-radius: 0px; }"
|
||||
"QToolButton:hover { background: %1; }").arg(hover.name());
|
||||
auto* pin = new QToolButton(this);
|
||||
pin->setFixedSize(16, 16);
|
||||
pin->setAutoRaise(true);
|
||||
pin->setIcon(QIcon(":/vsicons/pin.svg"));
|
||||
pin->setIconSize(QSize(12, 12));
|
||||
pin->setStyleSheet(style);
|
||||
hl->addWidget(pin);
|
||||
auto* close = new QToolButton(this);
|
||||
close->setFixedSize(16, 16);
|
||||
close->setAutoRaise(true);
|
||||
close->setIcon(QIcon(":/vsicons/close.svg"));
|
||||
close->setIconSize(QSize(12, 12));
|
||||
close->setStyleSheet(style);
|
||||
hl->addWidget(close);
|
||||
}
|
||||
};
|
||||
|
||||
class GrabTabs : public QObject {
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void grab() {
|
||||
const auto& t = rcx::ThemeManager::instance().current();
|
||||
|
||||
// Install custom style (no stylesheet — all painting via style)
|
||||
QApplication::setStyle(new TestTabStyle("Fusion"));
|
||||
|
||||
// Apply dark palette globally
|
||||
QPalette pal;
|
||||
pal.setColor(QPalette::Window, t.background);
|
||||
pal.setColor(QPalette::WindowText, t.textDim);
|
||||
pal.setColor(QPalette::Base, t.background);
|
||||
pal.setColor(QPalette::Text, t.text);
|
||||
pal.setColor(QPalette::Mid, t.hover);
|
||||
pal.setColor(QPalette::Dark, t.border);
|
||||
pal.setColor(QPalette::Link, t.indHoverSpan);
|
||||
QApplication::setPalette(pal);
|
||||
|
||||
auto* win = new QMainWindow;
|
||||
win->resize(700, 500);
|
||||
win->setDockNestingEnabled(true);
|
||||
win->setTabPosition(Qt::TopDockWidgetArea, QTabWidget::North);
|
||||
|
||||
auto* central = new QWidget(win);
|
||||
central->setMaximumSize(0, 0);
|
||||
central->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
|
||||
win->setCentralWidget(central);
|
||||
win->setStyleSheet(QStringLiteral(
|
||||
"QMainWindow::separator { width: 0px; height: 0px; background: transparent; }"));
|
||||
|
||||
QStringList names = {
|
||||
"shader_color_helper.hpp",
|
||||
"shader_crypt.cpp",
|
||||
"EPROCESS (class)",
|
||||
"very_long_struct_name_that_should_elide.h"
|
||||
};
|
||||
|
||||
QVector<QDockWidget*> docks;
|
||||
for (const auto& name : names) {
|
||||
auto* dock = new QDockWidget(name, win);
|
||||
dock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable);
|
||||
auto* emptyTitle = new QWidget(dock);
|
||||
emptyTitle->setFixedHeight(0);
|
||||
dock->setTitleBarWidget(emptyTitle);
|
||||
dock->setWidget(new QTextEdit(dock));
|
||||
if (!docks.isEmpty())
|
||||
win->tabifyDockWidget(docks.last(), dock);
|
||||
else
|
||||
win->addDockWidget(Qt::TopDockWidgetArea, dock);
|
||||
docks.append(dock);
|
||||
}
|
||||
// Select first tab
|
||||
docks.first()->raise();
|
||||
|
||||
win->show();
|
||||
QVERIFY(QTest::qWaitForWindowExposed(win));
|
||||
QApplication::processEvents();
|
||||
|
||||
// No stylesheet on dock tab bars — painting handled by TestTabStyle
|
||||
for (auto* tabBar : win->findChildren<QTabBar*>()) {
|
||||
if (tabBar->parent() != win) continue;
|
||||
tabBar->setStyleSheet(QString());
|
||||
tabBar->setElideMode(Qt::ElideNone);
|
||||
tabBar->setExpanding(false);
|
||||
|
||||
QPalette tp = tabBar->palette();
|
||||
tp.setColor(QPalette::WindowText, t.textDim);
|
||||
tp.setColor(QPalette::Text, t.text);
|
||||
tp.setColor(QPalette::Window, t.background);
|
||||
tp.setColor(QPalette::Mid, t.hover);
|
||||
tp.setColor(QPalette::Dark, t.border);
|
||||
tp.setColor(QPalette::Link, t.indHoverSpan);
|
||||
tabBar->setPalette(tp);
|
||||
|
||||
for (int i = 0; i < tabBar->count(); ++i)
|
||||
tabBar->setTabButton(i, QTabBar::RightSide, new TabBtns(t.hover, tabBar));
|
||||
}
|
||||
QApplication::processEvents();
|
||||
QApplication::processEvents();
|
||||
|
||||
QPixmap shot = win->grab(QRect(0, 0, win->width(), 50));
|
||||
shot.save(QStringLiteral("tab_screenshot.png"));
|
||||
qDebug() << "Saved" << shot.size();
|
||||
delete win;
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(GrabTabs)
|
||||
#include "grab_tabs.moc"
|
||||
@@ -382,6 +382,30 @@ private slots:
|
||||
QCOMPARE(r.value, 0x140000000ULL);
|
||||
}
|
||||
|
||||
// -- Bare module.dll identifier --
|
||||
|
||||
void bareModuleDll() {
|
||||
AddressParserCallbacks cbs;
|
||||
cbs.resolveIdentifier = [](const QString& name, bool* ok) -> uint64_t {
|
||||
*ok = (name == "client.dll");
|
||||
return *ok ? 0x7FF600000000ULL : 0;
|
||||
};
|
||||
auto r = AddressParser::evaluate("client.dll + 0xFF", 8, &cbs);
|
||||
QVERIFY(r.ok);
|
||||
QCOMPARE(r.value, 0x7FF6000000FFULL);
|
||||
}
|
||||
|
||||
void bareModuleExe() {
|
||||
AddressParserCallbacks cbs;
|
||||
cbs.resolveIdentifier = [](const QString& name, bool* ok) -> uint64_t {
|
||||
*ok = (name == "cs2.exe");
|
||||
return *ok ? 0x140000000ULL : 0;
|
||||
};
|
||||
auto r = AddressParser::evaluate("cs2.exe + 0xDE", 8, &cbs);
|
||||
QVERIFY(r.ok);
|
||||
QCOMPARE(r.value, 0x1400000DEULL);
|
||||
}
|
||||
|
||||
// -- Validate with new syntax --
|
||||
|
||||
void validateIdentifier() {
|
||||
|
||||
@@ -230,7 +230,7 @@ private slots:
|
||||
// Only include the pointer-expanded ones (near vtable at 0x100)
|
||||
if (lm.offsetAddr >= 0x100 && lm.offsetAddr < 0x200) {
|
||||
int nodeIdx = lm.nodeIdx;
|
||||
funcPtrs.append({i, lm.offsetAddr, lm.nodeKind,
|
||||
funcPtrs.push_back(FuncInfo{i, lm.offsetAddr, lm.nodeKind,
|
||||
nodeIdx >= 0 ? tree.nodes[nodeIdx].name : QString()});
|
||||
}
|
||||
}
|
||||
|
||||
397
tests/test_kernel_provider.cpp
Normal file
397
tests/test_kernel_provider.cpp
Normal file
@@ -0,0 +1,397 @@
|
||||
#include <QTest>
|
||||
#include <QSignalSpy>
|
||||
#include <QByteArray>
|
||||
#include <cstring>
|
||||
|
||||
#include "providers/provider.h"
|
||||
#include "scanner.h"
|
||||
#include "../plugins/KernelMemory/KernelMemoryPlugin.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <tlhelp32.h>
|
||||
#endif
|
||||
|
||||
using namespace rcx;
|
||||
|
||||
class TestKernelProvider : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
bool m_driverAvailable = false;
|
||||
KernelMemoryPlugin* m_plugin = nullptr;
|
||||
std::unique_ptr<Provider> m_provider;
|
||||
uint32_t m_selfPid = 0;
|
||||
|
||||
private slots:
|
||||
|
||||
// ── Setup: try to load driver, skip tests if unavailable ──
|
||||
|
||||
void initTestCase()
|
||||
{
|
||||
m_plugin = new KernelMemoryPlugin();
|
||||
|
||||
#ifdef _WIN32
|
||||
m_selfPid = GetCurrentProcessId();
|
||||
|
||||
// Try to open driver directly to see if it's available
|
||||
HANDLE h = CreateFileA(RCX_DRV_USERMODE_PATH,
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
0, nullptr, OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (h != INVALID_HANDLE_VALUE) {
|
||||
CloseHandle(h);
|
||||
m_driverAvailable = true;
|
||||
} else {
|
||||
// Try loading via plugin
|
||||
QString errorMsg;
|
||||
QString target = QStringLiteral("km:%1:self").arg(m_selfPid);
|
||||
m_provider = m_plugin->createProvider(target, &errorMsg);
|
||||
if (m_provider && m_provider->isValid()) {
|
||||
m_driverAvailable = true;
|
||||
} else {
|
||||
qWarning("Kernel driver not available: %s", qPrintable(errorMsg));
|
||||
qWarning("Tests requiring the driver will be skipped.");
|
||||
}
|
||||
}
|
||||
|
||||
if (m_driverAvailable && !m_provider) {
|
||||
QString target = QStringLiteral("km:%1:self").arg(m_selfPid);
|
||||
m_provider = m_plugin->createProvider(target, nullptr);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void cleanupTestCase()
|
||||
{
|
||||
m_provider.reset();
|
||||
delete m_plugin;
|
||||
m_plugin = nullptr;
|
||||
}
|
||||
|
||||
// ── 1. Plugin metadata (no driver needed) ──
|
||||
|
||||
void plugin_name()
|
||||
{
|
||||
QCOMPARE(QString::fromStdString(m_plugin->Name()), QStringLiteral("Kernel Memory"));
|
||||
}
|
||||
|
||||
void plugin_loadType()
|
||||
{
|
||||
QCOMPARE(m_plugin->LoadType(), IPlugin::k_ELoadTypeManual);
|
||||
}
|
||||
|
||||
void plugin_canHandle()
|
||||
{
|
||||
QVERIFY(m_plugin->canHandle(QStringLiteral("km:1234:test.exe")));
|
||||
QVERIFY(m_plugin->canHandle(QStringLiteral("phys:0")));
|
||||
QVERIFY(m_plugin->canHandle(QStringLiteral("msr:")));
|
||||
QVERIFY(!m_plugin->canHandle(QStringLiteral("1234:test.exe")));
|
||||
QVERIFY(!m_plugin->canHandle(QStringLiteral("file:test.bin")));
|
||||
}
|
||||
|
||||
void provider_noDriver_invalid()
|
||||
{
|
||||
// Creating provider with invalid target should fail gracefully
|
||||
QString err;
|
||||
auto prov = m_plugin->createProvider(QStringLiteral("km:0:invalid"), &err);
|
||||
// Either nullptr or invalid -- both are acceptable
|
||||
if (prov) QVERIFY(!prov->isValid() || prov->size() == 0);
|
||||
}
|
||||
|
||||
// ── 2. KUSER_SHARED_DATA validation (at 0x7FFE0000) ──
|
||||
|
||||
void kusd_ntMajorVersion()
|
||||
{
|
||||
if (!m_driverAvailable) QSKIP("Driver not loaded");
|
||||
QVERIFY(m_provider);
|
||||
|
||||
// KUSER_SHARED_DATA.NtMajorVersion at offset 0x26C
|
||||
uint32_t major = m_provider->readU32(0x7FFE0000 + 0x26C);
|
||||
QCOMPARE(major, (uint32_t)10); // Windows 10/11
|
||||
}
|
||||
|
||||
void kusd_ntMinorVersion()
|
||||
{
|
||||
if (!m_driverAvailable) QSKIP("Driver not loaded");
|
||||
|
||||
uint32_t minor = m_provider->readU32(0x7FFE0000 + 0x270);
|
||||
QCOMPARE(minor, (uint32_t)0); // Windows 10+ has minor = 0
|
||||
}
|
||||
|
||||
void kusd_ntBuildNumber()
|
||||
{
|
||||
if (!m_driverAvailable) QSKIP("Driver not loaded");
|
||||
|
||||
#ifdef _WIN32
|
||||
// Cross-validate with RtlGetVersion
|
||||
typedef NTSTATUS(NTAPI* RtlGetVersion_t)(PRTL_OSVERSIONINFOW);
|
||||
auto pRtlGetVersion = (RtlGetVersion_t)GetProcAddress(
|
||||
GetModuleHandleA("ntdll.dll"), "RtlGetVersion");
|
||||
QVERIFY(pRtlGetVersion);
|
||||
|
||||
RTL_OSVERSIONINFOW osvi{};
|
||||
osvi.dwOSVersionInfoSize = sizeof(osvi);
|
||||
QCOMPARE(pRtlGetVersion(&osvi), (NTSTATUS)0);
|
||||
|
||||
uint32_t buildFromDriver = m_provider->readU32(0x7FFE0000 + 0x260);
|
||||
QCOMPARE(buildFromDriver, (uint32_t)osvi.dwBuildNumber);
|
||||
#endif
|
||||
}
|
||||
|
||||
void kusd_systemTime_nonZero()
|
||||
{
|
||||
if (!m_driverAvailable) QSKIP("Driver not loaded");
|
||||
|
||||
uint64_t sysTime = m_provider->readU64(0x7FFE0000 + 0x14);
|
||||
QVERIFY(sysTime != 0);
|
||||
}
|
||||
|
||||
void kusd_tickCount_increasing()
|
||||
{
|
||||
if (!m_driverAvailable) QSKIP("Driver not loaded");
|
||||
|
||||
// TickCountMultiplier at 0x4, TickCount at 0x320
|
||||
uint64_t tick1 = m_provider->readU64(0x7FFE0000 + 0x320);
|
||||
QTest::qWait(120);
|
||||
uint64_t tick2 = m_provider->readU64(0x7FFE0000 + 0x320);
|
||||
QVERIFY2(tick2 > tick1,
|
||||
qPrintable(QStringLiteral("tick1=%1 tick2=%2").arg(tick1).arg(tick2)));
|
||||
}
|
||||
|
||||
void kusd_crossValidate_readProcessMemory()
|
||||
{
|
||||
if (!m_driverAvailable) QSKIP("Driver not loaded");
|
||||
|
||||
#ifdef _WIN32
|
||||
// Read same KUSD page through driver and ReadProcessMemory
|
||||
QByteArray driverBuf(256, 0);
|
||||
m_provider->read(0x7FFE0000, driverBuf.data(), 256);
|
||||
|
||||
QByteArray rpmBuf(256, 0);
|
||||
SIZE_T bytesRead = 0;
|
||||
HANDLE self = GetCurrentProcess();
|
||||
ReadProcessMemory(self, (LPCVOID)0x7FFE0000, rpmBuf.data(), 256, &bytesRead);
|
||||
|
||||
// NtMajorVersion (offset 0x26C relative = not in first 256 bytes, so compare what we have)
|
||||
// Compare first 256 bytes -- should be identical
|
||||
QCOMPARE(driverBuf, rpmBuf);
|
||||
#endif
|
||||
}
|
||||
|
||||
// ── 3. Self-read integration ──
|
||||
|
||||
void selfRead_mzHeader()
|
||||
{
|
||||
if (!m_driverAvailable) QSKIP("Driver not loaded");
|
||||
|
||||
#ifdef _WIN32
|
||||
uint64_t selfBase = (uint64_t)GetModuleHandleA(nullptr);
|
||||
QVERIFY(selfBase != 0);
|
||||
|
||||
uint8_t mz[2] = {};
|
||||
m_provider->read(selfBase, mz, 2);
|
||||
QCOMPARE(mz[0], (uint8_t)'M');
|
||||
QCOMPARE(mz[1], (uint8_t)'Z');
|
||||
#endif
|
||||
}
|
||||
|
||||
void selfRead_peSignature()
|
||||
{
|
||||
if (!m_driverAvailable) QSKIP("Driver not loaded");
|
||||
|
||||
#ifdef _WIN32
|
||||
uint64_t selfBase = (uint64_t)GetModuleHandleA(nullptr);
|
||||
|
||||
// PE offset at +0x3C
|
||||
uint32_t peOffset = m_provider->readU32(selfBase + 0x3C);
|
||||
QVERIFY(peOffset > 0 && peOffset < 0x1000);
|
||||
|
||||
// PE signature = "PE\0\0" = 0x00004550
|
||||
uint32_t peSig = m_provider->readU32(selfBase + peOffset);
|
||||
QCOMPARE(peSig, (uint32_t)0x00004550);
|
||||
#endif
|
||||
}
|
||||
|
||||
// ── 4. Scanner integration ──
|
||||
|
||||
void scanner_mzSigScan()
|
||||
{
|
||||
if (!m_driverAvailable) QSKIP("Driver not loaded");
|
||||
|
||||
#ifdef _WIN32
|
||||
auto shared = std::shared_ptr<Provider>(m_provider.get(), [](Provider*){});
|
||||
|
||||
ScanRequest req;
|
||||
req.pattern = QByteArray("\x4D\x5A", 2);
|
||||
req.mask = QByteArray("\xFF\xFF", 2);
|
||||
req.alignment = 1;
|
||||
req.maxResults = 10;
|
||||
|
||||
// Constrain to our own module for speed
|
||||
uint64_t selfBase = (uint64_t)GetModuleHandleA(nullptr);
|
||||
req.startAddress = selfBase;
|
||||
req.endAddress = selfBase + 0x1000;
|
||||
|
||||
ScanEngine engine;
|
||||
QSignalSpy spy(&engine, &ScanEngine::finished);
|
||||
engine.start(shared, req);
|
||||
QVERIFY(spy.wait(5000));
|
||||
|
||||
auto results = spy.at(0).at(0).value<QVector<ScanResult>>();
|
||||
QVERIFY(results.size() >= 1);
|
||||
QCOMPARE(results[0].address, selfBase);
|
||||
#endif
|
||||
}
|
||||
|
||||
// ── 5. Region enumeration ──
|
||||
|
||||
void regions_selfProcess()
|
||||
{
|
||||
if (!m_driverAvailable) QSKIP("Driver not loaded");
|
||||
|
||||
auto regions = m_provider->enumerateRegions();
|
||||
QVERIFY(regions.size() > 0);
|
||||
|
||||
// Should have at least one executable region (our code)
|
||||
bool hasExec = false;
|
||||
for (const auto& r : regions) {
|
||||
if (r.executable) { hasExec = true; break; }
|
||||
}
|
||||
QVERIFY(hasExec);
|
||||
}
|
||||
|
||||
// ── 6. PEB / modules ──
|
||||
|
||||
void peb_nonZero()
|
||||
{
|
||||
if (!m_driverAvailable) QSKIP("Driver not loaded");
|
||||
|
||||
QVERIFY(m_provider->peb() != 0);
|
||||
}
|
||||
|
||||
void symbol_selfModule()
|
||||
{
|
||||
if (!m_driverAvailable) QSKIP("Driver not loaded");
|
||||
|
||||
#ifdef _WIN32
|
||||
uint64_t selfBase = (uint64_t)GetModuleHandleA(nullptr);
|
||||
QString sym = m_provider->getSymbol(selfBase + 0x100);
|
||||
QVERIFY(!sym.isEmpty());
|
||||
QVERIFY(sym.contains(QStringLiteral("+0x")));
|
||||
#endif
|
||||
}
|
||||
|
||||
// ── 7. CR3 / address translation ──
|
||||
|
||||
void cr3_nonZero()
|
||||
{
|
||||
if (!m_driverAvailable) QSKIP("Driver not loaded");
|
||||
|
||||
auto* kprov = dynamic_cast<KernelProcessProvider*>(m_provider.get());
|
||||
QVERIFY(kprov);
|
||||
|
||||
uint64_t cr3 = kprov->getCr3();
|
||||
QVERIFY2(cr3 != 0, "CR3 should be non-zero for a running process");
|
||||
// CR3 should be page-aligned (low 12 bits cleared)
|
||||
QCOMPARE(cr3 & 0xFFF, (uint64_t)0);
|
||||
}
|
||||
|
||||
void vtop_kusd()
|
||||
{
|
||||
if (!m_driverAvailable) QSKIP("Driver not loaded");
|
||||
|
||||
auto* kprov = dynamic_cast<KernelProcessProvider*>(m_provider.get());
|
||||
QVERIFY(kprov);
|
||||
|
||||
// KUSER_SHARED_DATA is at VA 0x7FFE0000 in every process
|
||||
auto result = kprov->translateAddress(0x7FFE0000);
|
||||
QVERIFY2(result.valid, "KUSER_SHARED_DATA should be mapped");
|
||||
QVERIFY(result.physical != 0);
|
||||
// PML4E and PDPTE should be present
|
||||
QVERIFY(result.pml4e & 1); // Present bit
|
||||
QVERIFY(result.pdpte & 1); // Present bit
|
||||
}
|
||||
|
||||
void vtop_selfModule()
|
||||
{
|
||||
if (!m_driverAvailable) QSKIP("Driver not loaded");
|
||||
|
||||
#ifdef _WIN32
|
||||
auto* kprov = dynamic_cast<KernelProcessProvider*>(m_provider.get());
|
||||
QVERIFY(kprov);
|
||||
|
||||
uint64_t selfBase = (uint64_t)GetModuleHandleA(nullptr);
|
||||
auto result = kprov->translateAddress(selfBase);
|
||||
QVERIFY2(result.valid, "Own module base should be mapped");
|
||||
QVERIFY(result.physical != 0);
|
||||
|
||||
// Cross-validate: read MZ header via physical address
|
||||
// Read the first 2 bytes at the physical address using physical provider
|
||||
auto physEntries = kprov->readPageTable(kprov->getCr3(), 0, 16);
|
||||
QVERIFY(physEntries.size() > 0); // Should get at least some PML4 entries
|
||||
#endif
|
||||
}
|
||||
|
||||
void vtop_unmapped()
|
||||
{
|
||||
if (!m_driverAvailable) QSKIP("Driver not loaded");
|
||||
|
||||
auto* kprov = dynamic_cast<KernelProcessProvider*>(m_provider.get());
|
||||
QVERIFY(kprov);
|
||||
|
||||
// Address 0 should not be mapped in user mode
|
||||
auto result = kprov->translateAddress(0);
|
||||
QVERIFY2(!result.valid, "Address 0 should not be mapped");
|
||||
}
|
||||
|
||||
void readPageTable_cr3()
|
||||
{
|
||||
if (!m_driverAvailable) QSKIP("Driver not loaded");
|
||||
|
||||
auto* kprov = dynamic_cast<KernelProcessProvider*>(m_provider.get());
|
||||
QVERIFY(kprov);
|
||||
|
||||
uint64_t cr3 = kprov->getCr3();
|
||||
QVERIFY(cr3 != 0);
|
||||
|
||||
// Read the full PML4 table (512 entries)
|
||||
auto entries = kprov->readPageTable(cr3, 0, 512);
|
||||
QCOMPARE(entries.size(), 512);
|
||||
|
||||
// At least some entries should be present (kernel maps upper half)
|
||||
int presentCount = 0;
|
||||
for (const auto& e : entries) {
|
||||
if (e & 1) presentCount++;
|
||||
}
|
||||
QVERIFY2(presentCount > 0,
|
||||
qPrintable(QStringLiteral("Expected present PML4 entries, got 0")));
|
||||
}
|
||||
|
||||
// ── 8. Ping ──
|
||||
|
||||
void ping_version()
|
||||
{
|
||||
if (!m_driverAvailable) QSKIP("Driver not loaded");
|
||||
|
||||
#ifdef _WIN32
|
||||
HANDLE h = CreateFileA(RCX_DRV_USERMODE_PATH,
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
0, nullptr, OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (h == INVALID_HANDLE_VALUE) QSKIP("Cannot open driver handle");
|
||||
|
||||
RcxDrvPingResponse ping{};
|
||||
DWORD br = 0;
|
||||
BOOL ok = DeviceIoControl(h, IOCTL_RCX_PING, nullptr, 0,
|
||||
&ping, sizeof(ping), &br, nullptr);
|
||||
CloseHandle(h);
|
||||
|
||||
QVERIFY(ok);
|
||||
QCOMPARE(ping.version, (uint32_t)RCX_DRV_VERSION);
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(TestKernelProvider)
|
||||
#include "test_kernel_provider.moc"
|
||||
@@ -26,7 +26,7 @@ public:
|
||||
if (!m_server->listen(name)) return false;
|
||||
connect(m_server, &QLocalServer::newConnection, this, [this]() {
|
||||
while (auto* s = m_server->nextPendingConnection()) {
|
||||
m_clients.append({s, {}, false});
|
||||
m_clients.push_back(Client{s, {}, false});
|
||||
connect(s, &QLocalSocket::readyRead, this, [this, s]() { processSocket(s); });
|
||||
connect(s, &QLocalSocket::disconnected, this, [this, s]() {
|
||||
for (int i = 0; i < m_clients.size(); i++)
|
||||
|
||||
143
tests/test_project_dock.cpp
Normal file
143
tests/test_project_dock.cpp
Normal file
@@ -0,0 +1,143 @@
|
||||
#include <QtTest/QTest>
|
||||
#include <QApplication>
|
||||
#include <QMainWindow>
|
||||
#include <QDockWidget>
|
||||
#include <QTabWidget>
|
||||
#include <QTextEdit>
|
||||
|
||||
// Replicates the real app layout: QTabWidget central widget, project dock in LeftDockWidgetArea.
|
||||
|
||||
class TestProjectDock : public QObject {
|
||||
Q_OBJECT
|
||||
private:
|
||||
struct AppLayout {
|
||||
QMainWindow* win;
|
||||
QTabWidget* tabs;
|
||||
QDockWidget* project;
|
||||
};
|
||||
|
||||
AppLayout buildApp() {
|
||||
auto* win = new QMainWindow;
|
||||
win->resize(1280, 800);
|
||||
|
||||
// QTabWidget as central widget — same as real app
|
||||
auto* tabs = new QTabWidget(win);
|
||||
tabs->setTabsClosable(true);
|
||||
tabs->setMovable(true);
|
||||
tabs->setDocumentMode(true);
|
||||
tabs->addTab(new QTextEdit(tabs), "Untitled");
|
||||
win->setCentralWidget(tabs);
|
||||
|
||||
// Project dock — same as real app
|
||||
auto* project = new QDockWidget("Project", win);
|
||||
project->setObjectName("WorkspaceDock");
|
||||
project->setAllowedAreas(Qt::AllDockWidgetAreas);
|
||||
project->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable);
|
||||
project->setWidget(new QTextEdit(project));
|
||||
win->addDockWidget(Qt::LeftDockWidgetArea, project);
|
||||
project->hide();
|
||||
|
||||
return {win, tabs, project};
|
||||
}
|
||||
|
||||
void showProject(AppLayout& a) {
|
||||
if (a.project->isHidden() && !a.project->isFloating()) {
|
||||
a.win->addDockWidget(Qt::LeftDockWidgetArea, a.project);
|
||||
a.project->show();
|
||||
a.win->resizeDocks({a.project}, {qMax(200, a.win->width() / 5)}, Qt::Horizontal);
|
||||
} else {
|
||||
a.project->show();
|
||||
}
|
||||
}
|
||||
|
||||
private slots:
|
||||
void dockStartsLeft();
|
||||
void dockWidthIsReasonable();
|
||||
void dockStaysLeftAfterHideShow();
|
||||
void dockRespectsDragAfterShow();
|
||||
};
|
||||
|
||||
void TestProjectDock::dockStartsLeft()
|
||||
{
|
||||
auto app = buildApp();
|
||||
app.win->show();
|
||||
QTest::qWaitForWindowExposed(app.win);
|
||||
|
||||
showProject(app);
|
||||
QApplication::processEvents();
|
||||
|
||||
// Project should be to the left of the central tab widget
|
||||
QVERIFY2(app.project->x() < app.tabs->x(),
|
||||
qPrintable(QString("Project x=%1, Tabs x=%2")
|
||||
.arg(app.project->x()).arg(app.tabs->x())));
|
||||
delete app.win;
|
||||
}
|
||||
|
||||
void TestProjectDock::dockWidthIsReasonable()
|
||||
{
|
||||
auto app = buildApp();
|
||||
app.win->show();
|
||||
QTest::qWaitForWindowExposed(app.win);
|
||||
|
||||
showProject(app);
|
||||
QApplication::processEvents();
|
||||
|
||||
int dockWidth = app.project->width();
|
||||
int winWidth = app.win->width();
|
||||
double ratio = (double)dockWidth / winWidth;
|
||||
|
||||
qDebug() << "Dock width:" << dockWidth << "Window width:" << winWidth
|
||||
<< "Ratio:" << QString::number(ratio * 100, 'f', 1) + "%";
|
||||
|
||||
QVERIFY2(ratio < 0.40,
|
||||
qPrintable(QString("Dock too wide: %1% of window").arg(ratio * 100, 0, 'f', 1)));
|
||||
QVERIFY2(ratio > 0.10,
|
||||
qPrintable(QString("Dock too narrow: %1% of window").arg(ratio * 100, 0, 'f', 1)));
|
||||
delete app.win;
|
||||
}
|
||||
|
||||
void TestProjectDock::dockStaysLeftAfterHideShow()
|
||||
{
|
||||
auto app = buildApp();
|
||||
app.win->show();
|
||||
QTest::qWaitForWindowExposed(app.win);
|
||||
|
||||
showProject(app);
|
||||
QApplication::processEvents();
|
||||
QVERIFY(app.project->x() < app.tabs->x());
|
||||
|
||||
app.project->hide();
|
||||
QApplication::processEvents();
|
||||
|
||||
showProject(app);
|
||||
QApplication::processEvents();
|
||||
QVERIFY2(app.project->x() < app.tabs->x(),
|
||||
qPrintable(QString("After re-show: Project x=%1, Tabs x=%2")
|
||||
.arg(app.project->x()).arg(app.tabs->x())));
|
||||
delete app.win;
|
||||
}
|
||||
|
||||
void TestProjectDock::dockRespectsDragAfterShow()
|
||||
{
|
||||
auto app = buildApp();
|
||||
app.win->show();
|
||||
QTest::qWaitForWindowExposed(app.win);
|
||||
|
||||
showProject(app);
|
||||
QApplication::processEvents();
|
||||
QVERIFY(app.project->x() < app.tabs->x());
|
||||
|
||||
// Simulate user dragging to right
|
||||
app.win->addDockWidget(Qt::RightDockWidgetArea, app.project);
|
||||
QApplication::processEvents();
|
||||
QCOMPARE(app.win->dockWidgetArea(app.project), Qt::RightDockWidgetArea);
|
||||
|
||||
// Dock is visible — showProject should NOT force it back to left
|
||||
showProject(app);
|
||||
QApplication::processEvents();
|
||||
QCOMPARE(app.win->dockWidgetArea(app.project), Qt::RightDockWidgetArea);
|
||||
delete app.win;
|
||||
}
|
||||
|
||||
QTEST_MAIN(TestProjectDock)
|
||||
#include "test_project_dock.moc"
|
||||
307
tests/test_roundtrip_winsdk.cpp
Normal file
307
tests/test_roundtrip_winsdk.cpp
Normal file
@@ -0,0 +1,307 @@
|
||||
#include <QtTest/QTest>
|
||||
#include <QFile>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <algorithm>
|
||||
#include <random>
|
||||
#include "core.h"
|
||||
#include "imports/import_source.h"
|
||||
#include "generator.h"
|
||||
|
||||
using namespace rcx;
|
||||
|
||||
class TestRoundtripWinSdk : public QObject {
|
||||
Q_OBJECT
|
||||
private:
|
||||
NodeTree fullTree;
|
||||
QVector<int> rootIndices;
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void importCount();
|
||||
void pebOffsets();
|
||||
void roundTrip30();
|
||||
void generateRcx();
|
||||
};
|
||||
|
||||
void TestRoundtripWinSdk::initTestCase()
|
||||
{
|
||||
QString path = QStringLiteral(WINSDK_HEADER_PATH);
|
||||
QFile file(path);
|
||||
QVERIFY2(file.open(QIODevice::ReadOnly | QIODevice::Text),
|
||||
qPrintable("Cannot open " + path));
|
||||
QString source = QString::fromUtf8(file.readAll());
|
||||
QVERIFY(!source.isEmpty());
|
||||
|
||||
QString err;
|
||||
fullTree = importFromSource(source, &err, 8);
|
||||
|
||||
for (int i = 0; i < fullTree.nodes.size(); i++) {
|
||||
const auto& n = fullTree.nodes[i];
|
||||
if (n.parentId == 0 && n.kind == NodeKind::Struct)
|
||||
rootIndices.append(i);
|
||||
}
|
||||
qDebug() << "Imported" << fullTree.nodes.size() << "total nodes,"
|
||||
<< rootIndices.size() << "root structs";
|
||||
}
|
||||
|
||||
void TestRoundtripWinSdk::importCount()
|
||||
{
|
||||
QVERIFY2(rootIndices.size() >= 3000,
|
||||
qPrintable(QString("Expected >= 3000 roots, got %1").arg(rootIndices.size())));
|
||||
}
|
||||
|
||||
void TestRoundtripWinSdk::pebOffsets()
|
||||
{
|
||||
// Verify _PEB field offsets match WinDbg dt ntdll!_PEB
|
||||
int pebIdx = -1;
|
||||
for (int i = 0; i < fullTree.nodes.size(); i++) {
|
||||
if (fullTree.nodes[i].parentId == 0 &&
|
||||
fullTree.nodes[i].structTypeName == QStringLiteral("_PEB")) {
|
||||
pebIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
QVERIFY2(pebIdx >= 0, "Could not find _PEB root struct");
|
||||
|
||||
uint64_t pebId = fullTree.nodes[pebIdx].id;
|
||||
|
||||
// Collect direct children with offsets and sizes
|
||||
struct ChildInfo { QString name; int offset; int size; NodeKind kind; };
|
||||
QVector<ChildInfo> children;
|
||||
for (int i = 0; i < fullTree.nodes.size(); i++) {
|
||||
if (fullTree.nodes[i].parentId == pebId) {
|
||||
int sz = sizeForKind(fullTree.nodes[i].kind);
|
||||
if (sz == 0) sz = fullTree.structSpan(fullTree.nodes[i].id);
|
||||
if (sz == 0) sz = 1;
|
||||
children.push_back(ChildInfo{fullTree.nodes[i].name, fullTree.nodes[i].offset, sz, fullTree.nodes[i].kind});
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by offset
|
||||
std::sort(children.begin(), children.end(),
|
||||
[](const ChildInfo& a, const ChildInfo& b) { return a.offset < b.offset; });
|
||||
|
||||
// Dump all children for diagnostics
|
||||
for (const auto& c : children) {
|
||||
qDebug() << " " << Qt::hex << c.offset << c.name
|
||||
<< "kind=" << kindToString(c.kind) << "size=" << c.size;
|
||||
}
|
||||
|
||||
// Check for overlaps
|
||||
int overlapCount = 0;
|
||||
for (int i = 1; i < children.size(); i++) {
|
||||
int prevEnd = children[i-1].offset + children[i-1].size;
|
||||
if (children[i].offset < prevEnd && children[i-1].kind != NodeKind::Struct) {
|
||||
// Only flag overlaps where previous field has a known size (not struct references)
|
||||
overlapCount++;
|
||||
if (overlapCount <= 10)
|
||||
qDebug() << " OVERLAP:" << children[i].name << "at" << Qt::hex << children[i].offset
|
||||
<< "overlaps" << children[i-1].name << "(ends at" << Qt::hex << prevEnd << ")";
|
||||
}
|
||||
}
|
||||
|
||||
// Build name→offset map for field checks
|
||||
QHash<QString, int> offsets;
|
||||
QHash<QString, NodeKind> kinds;
|
||||
for (const auto& c : children) {
|
||||
offsets[c.name] = c.offset;
|
||||
kinds[c.name] = c.kind;
|
||||
}
|
||||
|
||||
int failCount = 0;
|
||||
auto checkField = [&](const QString& name, int expected, bool mustBePointer = false) {
|
||||
if (!offsets.contains(name)) {
|
||||
qDebug() << " MISSING:" << name;
|
||||
failCount++;
|
||||
return;
|
||||
}
|
||||
if (offsets[name] != expected) {
|
||||
qDebug() << " OFFSET MISMATCH:" << name << "got" << Qt::hex << offsets[name]
|
||||
<< "expected" << Qt::hex << expected;
|
||||
failCount++;
|
||||
return;
|
||||
}
|
||||
if (mustBePointer) {
|
||||
NodeKind k = kinds[name];
|
||||
if (k != NodeKind::Pointer64 && k != NodeKind::Pointer32) {
|
||||
qDebug() << " NOT POINTER:" << name << "kind=" << kindToString(k);
|
||||
failCount++;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Expected offsets computed from the source header layout (Vergilius-style)
|
||||
// Note: This header has union ALIGN(8) { KernelCallbackTable; UserSharedInfoPtr; }
|
||||
// after CrossProcessFlags, which shifts fields +0xC compared to some WinDbg versions.
|
||||
checkField(QStringLiteral("InheritedAddressSpace"), 0x000);
|
||||
checkField(QStringLiteral("ReadImageFileExecOptions"), 0x001);
|
||||
checkField(QStringLiteral("BeingDebugged"), 0x002);
|
||||
checkField(QStringLiteral("Mutant"), 0x008, true);
|
||||
checkField(QStringLiteral("ImageBaseAddress"), 0x010, true);
|
||||
checkField(QStringLiteral("Ldr"), 0x018, true);
|
||||
checkField(QStringLiteral("ProcessParameters"), 0x020, true);
|
||||
checkField(QStringLiteral("SubSystemData"), 0x028, true);
|
||||
checkField(QStringLiteral("ProcessHeap"), 0x030, true);
|
||||
checkField(QStringLiteral("FastPebLock"), 0x038, true);
|
||||
checkField(QStringLiteral("AtlThunkSListPtr"), 0x040, true);
|
||||
checkField(QStringLiteral("IFEOKey"), 0x048, true);
|
||||
checkField(QStringLiteral("SystemReserved"), 0x060);
|
||||
checkField(QStringLiteral("AtlThunkSListPtr32"), 0x064);
|
||||
checkField(QStringLiteral("ApiSetMap"), 0x068, true);
|
||||
checkField(QStringLiteral("TlsExpansionCounter"), 0x070);
|
||||
checkField(QStringLiteral("TlsBitmap"), 0x078, true);
|
||||
checkField(QStringLiteral("TlsBitmapBits"), 0x080);
|
||||
checkField(QStringLiteral("ReadOnlySharedMemoryBase"), 0x088, true);
|
||||
checkField(QStringLiteral("SharedData"), 0x090, true);
|
||||
checkField(QStringLiteral("ReadOnlyStaticServerData"), 0x098, true);
|
||||
checkField(QStringLiteral("AnsiCodePageData"), 0x0A0, true);
|
||||
checkField(QStringLiteral("OemCodePageData"), 0x0A8, true);
|
||||
checkField(QStringLiteral("UnicodeCaseTableData"), 0x0B0, true);
|
||||
checkField(QStringLiteral("NumberOfProcessors"), 0x0B8);
|
||||
checkField(QStringLiteral("NtGlobalFlag"), 0x0BC);
|
||||
checkField(QStringLiteral("HeapSegmentReserve"), 0x0C8);
|
||||
checkField(QStringLiteral("NumberOfHeaps"), 0x0E8);
|
||||
checkField(QStringLiteral("MaximumNumberOfHeaps"), 0x0EC);
|
||||
checkField(QStringLiteral("ProcessHeaps"), 0x0F0, true);
|
||||
checkField(QStringLiteral("OSMajorVersion"), 0x118);
|
||||
checkField(QStringLiteral("OSMinorVersion"), 0x11C);
|
||||
checkField(QStringLiteral("OSBuildNumber"), 0x120);
|
||||
checkField(QStringLiteral("SessionId"), 0x2C0);
|
||||
checkField(QStringLiteral("CsrServerReadOnlySharedMemoryBase"), 0x380);
|
||||
checkField(QStringLiteral("TppWorkerpListLock"), 0x388, true);
|
||||
checkField(QStringLiteral("WaitOnAddressHashTable"), 0x3A0);
|
||||
checkField(QStringLiteral("TelemetryCoverageHeader"), 0x7A0, true);
|
||||
checkField(QStringLiteral("CloudFileFlags"), 0x7A8);
|
||||
checkField(QStringLiteral("CloudFileDiagFlags"), 0x7AC);
|
||||
checkField(QStringLiteral("PlaceholderCompatibilityMode"), 0x7B0);
|
||||
checkField(QStringLiteral("LeapSecondData"), 0x7B8, true);
|
||||
checkField(QStringLiteral("NtGlobalFlag2"), 0x7C4);
|
||||
|
||||
QVERIFY2(failCount == 0,
|
||||
qPrintable(QString("%1 PEB field(s) have wrong offsets or are missing").arg(failCount)));
|
||||
}
|
||||
|
||||
void TestRoundtripWinSdk::roundTrip30()
|
||||
{
|
||||
const int kRequired = 30;
|
||||
|
||||
// Deterministic shuffle
|
||||
QVector<int> shuffled = rootIndices;
|
||||
std::mt19937 rng(42);
|
||||
std::shuffle(shuffled.begin(), shuffled.end(), rng);
|
||||
|
||||
int passCount = 0;
|
||||
int failCount = 0;
|
||||
int skipCount = 0;
|
||||
|
||||
for (int ri : shuffled) {
|
||||
uint64_t rootId = fullTree.nodes[ri].id;
|
||||
QString structName = fullTree.nodes[ri].structTypeName;
|
||||
|
||||
// Pass 1: export from full tree
|
||||
QString cpp1 = renderCpp(fullTree, rootId, nullptr, true);
|
||||
if (cpp1.isEmpty()) {
|
||||
skipCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Pass 2: re-import
|
||||
QString err;
|
||||
NodeTree tree2 = importFromSource(cpp1, &err);
|
||||
if (tree2.nodes.isEmpty()) {
|
||||
skipCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find the root in re-imported tree
|
||||
int rootIdx2 = -1;
|
||||
for (int i = 0; i < tree2.nodes.size(); i++) {
|
||||
if (tree2.nodes[i].parentId == 0 && tree2.nodes[i].kind == NodeKind::Struct) {
|
||||
if (tree2.nodes[i].structTypeName == structName) {
|
||||
rootIdx2 = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rootIdx2 < 0) {
|
||||
// Take first root
|
||||
for (int i = 0; i < tree2.nodes.size(); i++) {
|
||||
if (tree2.nodes[i].parentId == 0 && tree2.nodes[i].kind == NodeKind::Struct) {
|
||||
rootIdx2 = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rootIdx2 < 0) {
|
||||
skipCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Pass 3: re-export
|
||||
QString cpp2 = renderCpp(tree2, tree2.nodes[rootIdx2].id, nullptr, true);
|
||||
|
||||
if (cpp1 == cpp2) {
|
||||
passCount++;
|
||||
if (passCount <= kRequired)
|
||||
qDebug() << " PASS" << passCount << structName;
|
||||
} else {
|
||||
failCount++;
|
||||
if (failCount <= 5) {
|
||||
// Log first few failures for diagnostics
|
||||
QStringList lines1 = cpp1.split('\n');
|
||||
QStringList lines2 = cpp2.split('\n');
|
||||
int diffLine = -1;
|
||||
for (int i = 0; i < qMin(lines1.size(), lines2.size()); i++) {
|
||||
if (lines1[i] != lines2[i]) { diffLine = i; break; }
|
||||
}
|
||||
if (diffLine >= 0) {
|
||||
qDebug() << " FAIL" << structName << "first diff at line" << diffLine;
|
||||
qDebug() << " cpp1:" << lines1[diffLine].left(120);
|
||||
qDebug() << " cpp2:" << lines2[diffLine].left(120);
|
||||
} else {
|
||||
qDebug() << " FAIL" << structName << "line count differs:"
|
||||
<< lines1.size() << "vs" << lines2.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (passCount >= kRequired && failCount > 5)
|
||||
break; // found enough passes and logged enough failures
|
||||
}
|
||||
|
||||
qDebug() << "Round-trip results: pass=" << passCount
|
||||
<< "fail=" << failCount << "skip=" << skipCount;
|
||||
QVERIFY2(passCount >= kRequired,
|
||||
qPrintable(QString("Need %1 stable round-trips, got %2")
|
||||
.arg(kRequired).arg(passCount)));
|
||||
}
|
||||
|
||||
void TestRoundtripWinSdk::generateRcx()
|
||||
{
|
||||
// Set all root structs collapsed
|
||||
for (int ri : rootIndices)
|
||||
fullTree.nodes[ri].collapsed = true;
|
||||
|
||||
fullTree.baseAddress = 0xFFFFF80000000000ULL;
|
||||
|
||||
QJsonObject json = fullTree.toJson();
|
||||
QJsonDocument jdoc(json);
|
||||
QByteArray data = jdoc.toJson(QJsonDocument::Indented);
|
||||
|
||||
QVERIFY2(data.size() > 1000000,
|
||||
qPrintable(QString("RCX too small: %1 bytes").arg(data.size())));
|
||||
|
||||
QString outPath = QStringLiteral(WINSDK_RCX_OUTPUT);
|
||||
QFile file(outPath);
|
||||
QVERIFY2(file.open(QIODevice::WriteOnly | QIODevice::Truncate),
|
||||
qPrintable("Cannot write " + outPath));
|
||||
file.write(data);
|
||||
file.close();
|
||||
|
||||
qDebug() << "Wrote" << data.size() << "bytes to" << outPath;
|
||||
}
|
||||
|
||||
QTEST_MAIN(TestRoundtripWinSdk)
|
||||
#include "test_roundtrip_winsdk.moc"
|
||||
@@ -644,8 +644,8 @@ private slots:
|
||||
data[16] = 0xAA; // in region 1 (executable)
|
||||
|
||||
QVector<MemoryRegion> regions;
|
||||
regions.append({0, 16, true, true, false, "heap"});
|
||||
regions.append({16, 16, true, false, true, "code"});
|
||||
regions.push_back(MemoryRegion{0, 16, true, true, false, "heap"});
|
||||
regions.push_back(MemoryRegion{16, 16, true, false, true, "code"});
|
||||
|
||||
auto prov = std::make_shared<RegionProvider>(data, regions);
|
||||
ScanEngine engine;
|
||||
@@ -671,8 +671,8 @@ private slots:
|
||||
data[16] = 0xBB; // region 1 (not writable)
|
||||
|
||||
QVector<MemoryRegion> regions;
|
||||
regions.append({0, 16, true, true, false, "data"});
|
||||
regions.append({16, 16, true, false, true, "code"});
|
||||
regions.push_back(MemoryRegion{0, 16, true, true, false, "data"});
|
||||
regions.push_back(MemoryRegion{16, 16, true, false, true, "code"});
|
||||
|
||||
auto prov = std::make_shared<RegionProvider>(data, regions);
|
||||
ScanEngine engine;
|
||||
@@ -698,9 +698,9 @@ private slots:
|
||||
data[32] = 0xCC; // region 2: +w +x
|
||||
|
||||
QVector<MemoryRegion> regions;
|
||||
regions.append({0, 16, true, true, false, "data"});
|
||||
regions.append({16, 16, true, false, true, "code"});
|
||||
regions.append({32, 16, true, true, true, "rwx"});
|
||||
regions.push_back(MemoryRegion{0, 16, true, true, false, "data"});
|
||||
regions.push_back(MemoryRegion{16, 16, true, false, true, "code"});
|
||||
regions.push_back(MemoryRegion{32, 16, true, true, true, "rwx"});
|
||||
|
||||
auto prov = std::make_shared<RegionProvider>(data, regions);
|
||||
ScanEngine engine;
|
||||
@@ -726,7 +726,7 @@ private slots:
|
||||
data[0] = 0xDD;
|
||||
|
||||
QVector<MemoryRegion> regions;
|
||||
regions.append({0, 16, true, true, true, "Game.exe"});
|
||||
regions.push_back(MemoryRegion{0, 16, true, true, true, "Game.exe"});
|
||||
|
||||
auto prov = std::make_shared<RegionProvider>(data, regions);
|
||||
ScanEngine engine;
|
||||
@@ -943,8 +943,8 @@ private slots:
|
||||
|
||||
void provider_customRegions() {
|
||||
QVector<MemoryRegion> regs;
|
||||
regs.append({0x1000, 0x2000, true, true, false, "heap"});
|
||||
regs.append({0x3000, 0x1000, true, false, true, "code"});
|
||||
regs.push_back(MemoryRegion{0x1000, 0x2000, true, true, false, "heap"});
|
||||
regs.push_back(MemoryRegion{0x3000, 0x1000, true, false, true, "code"});
|
||||
|
||||
RegionProvider p(QByteArray(0x4000, '\0'), regs);
|
||||
auto result = p.enumerateRegions();
|
||||
@@ -982,9 +982,9 @@ private slots:
|
||||
data[36] = 0xEE; // region 2
|
||||
|
||||
QVector<MemoryRegion> regions;
|
||||
regions.append({0, 16, true, true, false, "region0"});
|
||||
regions.append({16, 16, true, true, false, "region1"});
|
||||
regions.append({32, 16, true, true, false, "region2"});
|
||||
regions.push_back(MemoryRegion{0, 16, true, true, false, "region0"});
|
||||
regions.push_back(MemoryRegion{16, 16, true, true, false, "region1"});
|
||||
regions.push_back(MemoryRegion{32, 16, true, true, false, "region2"});
|
||||
|
||||
auto prov = std::make_shared<RegionProvider>(data, regions);
|
||||
ScanEngine engine;
|
||||
@@ -1215,7 +1215,7 @@ private slots:
|
||||
data[160] = char(0xCC);
|
||||
data[210] = char(0xCC);
|
||||
QVector<MemoryRegion> regions;
|
||||
regions.append({100, 100, true, false, false, {}});
|
||||
regions.push_back(MemoryRegion{100, 100, true, false, false, {}});
|
||||
auto prov = std::make_shared<RegionProvider>(data, regions);
|
||||
ScanEngine engine;
|
||||
QSignalSpy finSpy(&engine, &ScanEngine::finished);
|
||||
@@ -1233,7 +1233,7 @@ private slots:
|
||||
void scan_constrainRegions_noOverlap() {
|
||||
QByteArray data(32, char(0xEE));
|
||||
QVector<MemoryRegion> regions;
|
||||
regions.append({0, 16, true, false, false, {}});
|
||||
regions.push_back(MemoryRegion{0, 16, true, false, false, {}});
|
||||
auto prov = std::make_shared<RegionProvider>(data, regions);
|
||||
ScanEngine engine;
|
||||
QSignalSpy finSpy(&engine, &ScanEngine::finished);
|
||||
@@ -1256,8 +1256,8 @@ private slots:
|
||||
data[10] = char(0xDD);
|
||||
data[35] = char(0xDD);
|
||||
QVector<MemoryRegion> regions;
|
||||
regions.append({0, 16, true, true, false, {}});
|
||||
regions.append({32, 16, true, true, false, {}});
|
||||
regions.push_back(MemoryRegion{0, 16, true, true, false, {}});
|
||||
regions.push_back(MemoryRegion{32, 16, true, true, false, {}});
|
||||
auto prov = std::make_shared<RegionProvider>(data, regions);
|
||||
ScanEngine engine;
|
||||
QSignalSpy finSpy(&engine, &ScanEngine::finished);
|
||||
@@ -1279,7 +1279,7 @@ private slots:
|
||||
data[120] = char(0xAB);
|
||||
data[160] = char(0xAB);
|
||||
QVector<MemoryRegion> regions;
|
||||
regions.append({100, 100, true, true, false, {}});
|
||||
regions.push_back(MemoryRegion{100, 100, true, true, false, {}});
|
||||
auto prov = std::make_shared<RegionProvider>(data, regions);
|
||||
ScanEngine engine;
|
||||
QSignalSpy finSpy(&engine, &ScanEngine::finished);
|
||||
@@ -1300,8 +1300,8 @@ private slots:
|
||||
data[0x1500] = char(0xCC);
|
||||
data[0x5500] = char(0xCC);
|
||||
QVector<MemoryRegion> regions;
|
||||
regions.append({0x1000, 0x1000, true, false, true, QString("game.exe")});
|
||||
regions.append({0x5000, 0x1000, true, true, false, {}});
|
||||
regions.push_back(MemoryRegion{0x1000, 0x1000, true, false, true, QString("game.exe")});
|
||||
regions.push_back(MemoryRegion{0x5000, 0x1000, true, true, false, {}});
|
||||
auto prov = std::make_shared<RegionProvider>(data, regions);
|
||||
ScanEngine engine;
|
||||
QSignalSpy finSpy(&engine, &ScanEngine::finished);
|
||||
@@ -1345,8 +1345,8 @@ private slots:
|
||||
data[12] = char(0xEF);
|
||||
data[20] = char(0xEF);
|
||||
QVector<MemoryRegion> regions;
|
||||
regions.append({0, 16, true, true, false, {}});
|
||||
regions.append({16, 16, true, true, false, {}});
|
||||
regions.push_back(MemoryRegion{0, 16, true, true, false, {}});
|
||||
regions.push_back(MemoryRegion{16, 16, true, true, false, {}});
|
||||
auto prov = std::make_shared<RegionProvider>(data, regions);
|
||||
ScanEngine engine;
|
||||
QSignalSpy finSpy(&engine, &ScanEngine::finished);
|
||||
@@ -1368,8 +1368,8 @@ private slots:
|
||||
data[0x1100] = char(0xBB);
|
||||
data[0x2100] = char(0xBB);
|
||||
QVector<MemoryRegion> regions;
|
||||
regions.append({0x1000, 0x1000, true, false, true, {}});
|
||||
regions.append({0x2000, 0x1000, true, true, false, {}});
|
||||
regions.push_back(MemoryRegion{0x1000, 0x1000, true, false, true, {}});
|
||||
regions.push_back(MemoryRegion{0x2000, 0x1000, true, true, false, {}});
|
||||
auto prov = std::make_shared<RegionProvider>(data, regions);
|
||||
ScanEngine engine;
|
||||
QSignalSpy finSpy(&engine, &ScanEngine::finished);
|
||||
@@ -1394,7 +1394,7 @@ private slots:
|
||||
data[15] = char(0xAA); // inside region, should be found
|
||||
data[25] = char(0xAA); // outside region, should NOT be found
|
||||
QVector<MemoryRegion> regions;
|
||||
regions.append({10, 10, true, true, false, {}});
|
||||
regions.push_back(MemoryRegion{10, 10, true, true, false, {}});
|
||||
auto prov = std::make_shared<RegionProvider>(data, regions);
|
||||
ScanEngine engine;
|
||||
QSignalSpy finSpy(&engine, &ScanEngine::finished);
|
||||
@@ -1415,7 +1415,7 @@ private slots:
|
||||
data[5] = char(0xBB);
|
||||
data[15] = char(0xBB);
|
||||
QVector<MemoryRegion> regions;
|
||||
regions.append({0, 32, true, true, false, {}});
|
||||
regions.push_back(MemoryRegion{0, 32, true, true, false, {}});
|
||||
auto prov = std::make_shared<RegionProvider>(data, regions);
|
||||
ScanEngine engine;
|
||||
QSignalSpy finSpy(&engine, &ScanEngine::finished);
|
||||
@@ -1506,7 +1506,7 @@ private slots:
|
||||
QByteArray data(0x10000, 0);
|
||||
data[0x8100] = char(0xFF);
|
||||
QVector<MemoryRegion> regions;
|
||||
regions.append({0x8000, 0x1000, true, true, false, {}});
|
||||
regions.push_back(MemoryRegion{0x8000, 0x1000, true, true, false, {}});
|
||||
auto prov = std::make_shared<RegionProvider>(data, regions);
|
||||
ScanEngine engine;
|
||||
QSignalSpy finSpy(&engine, &ScanEngine::finished);
|
||||
@@ -1581,7 +1581,7 @@ private slots:
|
||||
QByteArray data(64, 0);
|
||||
data[20] = char(0xFE);
|
||||
QVector<MemoryRegion> regions;
|
||||
regions.append({0, 64, true, true, false, {}});
|
||||
regions.push_back(MemoryRegion{0, 64, true, true, false, {}});
|
||||
auto prov = std::make_shared<RegionProvider>(data, regions);
|
||||
ScanEngine engine;
|
||||
QSignalSpy finSpy(&engine, &ScanEngine::finished);
|
||||
@@ -1602,7 +1602,7 @@ private slots:
|
||||
QByteArray data(64, 0);
|
||||
data[36] = char(0xDE); data[37] = char(0xAD); data[38] = char(0xBE); data[39] = char(0xEF);
|
||||
QVector<MemoryRegion> regions;
|
||||
regions.append({0, 64, true, true, false, {}});
|
||||
regions.push_back(MemoryRegion{0, 64, true, true, false, {}});
|
||||
auto prov = std::make_shared<RegionProvider>(data, regions);
|
||||
ScanEngine engine;
|
||||
QSignalSpy finSpy(&engine, &ScanEngine::finished);
|
||||
@@ -1624,7 +1624,7 @@ private slots:
|
||||
QByteArray data(64, 0);
|
||||
data[36] = char(0xDE); data[37] = char(0xAD); data[38] = char(0xBE); data[39] = char(0xEF);
|
||||
QVector<MemoryRegion> regions;
|
||||
regions.append({0, 64, true, true, false, {}});
|
||||
regions.push_back(MemoryRegion{0, 64, true, true, false, {}});
|
||||
auto prov = std::make_shared<RegionProvider>(data, regions);
|
||||
ScanEngine engine;
|
||||
QSignalSpy finSpy(&engine, &ScanEngine::finished);
|
||||
@@ -1643,7 +1643,7 @@ private slots:
|
||||
// Region [0, 64). Constraint [30, 32). 4-byte pattern can't fit in 2 bytes.
|
||||
QByteArray data(64, char(0xAA));
|
||||
QVector<MemoryRegion> regions;
|
||||
regions.append({0, 64, true, true, false, {}});
|
||||
regions.push_back(MemoryRegion{0, 64, true, true, false, {}});
|
||||
auto prov = std::make_shared<RegionProvider>(data, regions);
|
||||
ScanEngine engine;
|
||||
QSignalSpy finSpy(&engine, &ScanEngine::finished);
|
||||
@@ -1663,7 +1663,7 @@ private slots:
|
||||
QByteArray data(64, 0);
|
||||
data[30] = char(0x11); data[31] = char(0x22); data[32] = char(0x33); data[33] = char(0x44);
|
||||
QVector<MemoryRegion> regions;
|
||||
regions.append({0, 64, true, true, false, {}});
|
||||
regions.push_back(MemoryRegion{0, 64, true, true, false, {}});
|
||||
auto prov = std::make_shared<RegionProvider>(data, regions);
|
||||
ScanEngine engine;
|
||||
QSignalSpy finSpy(&engine, &ScanEngine::finished);
|
||||
@@ -1686,8 +1686,8 @@ private slots:
|
||||
data[15] = char(0x77); // last byte of first region
|
||||
data[16] = char(0x77); // first byte of second region
|
||||
QVector<MemoryRegion> regions;
|
||||
regions.append({0, 16, true, true, false, {}});
|
||||
regions.append({16, 16, true, true, false, {}});
|
||||
regions.push_back(MemoryRegion{0, 16, true, true, false, {}});
|
||||
regions.push_back(MemoryRegion{16, 16, true, true, false, {}});
|
||||
auto prov = std::make_shared<RegionProvider>(data, regions);
|
||||
ScanEngine engine;
|
||||
QSignalSpy finSpy(&engine, &ScanEngine::finished);
|
||||
@@ -1711,7 +1711,7 @@ private slots:
|
||||
QByteArray data(64, 0);
|
||||
data[10] = char(0xAA); data[11] = char(0xBB); data[12] = char(0xCC); data[13] = char(0xDD);
|
||||
QVector<MemoryRegion> regions;
|
||||
regions.append({0, 64, true, true, false, {}});
|
||||
regions.push_back(MemoryRegion{0, 64, true, true, false, {}});
|
||||
auto prov = std::make_shared<RegionProvider>(data, regions);
|
||||
ScanEngine engine;
|
||||
QSignalSpy finSpy(&engine, &ScanEngine::finished);
|
||||
|
||||
Reference in New Issue
Block a user