From 5921af2b4f92cbe17260bf04bf1074d9ca806752 Mon Sep 17 00:00:00 2001 From: IChooseYou Date: Sat, 14 Mar 2026 09:21:14 -0600 Subject: [PATCH] fix: replace QList::append({}) with push_back/emplaceBack for Qt 6.8 Qt 6.8's stricter QList rejects brace-enclosed initializer lists in append(). Fixed 43 call sites across 13 files. --- src/controller.cpp | 75 +++++++++++++----- src/core.h | 11 ++- src/imports/import_pdb.cpp | 121 ++++++++++++++++++++++++++++- src/imports/import_reclass_xml.cpp | 6 +- src/imports/import_source.cpp | 20 ++--- src/main.cpp | 83 ++++++++++++++++---- src/mcp/mcp_bridge.cpp | 14 ++-- src/pluginmanager.cpp | 7 +- src/startpage.h | 22 +++--- src/symbolstore.cpp | 2 +- src/typeinfer.h | 78 +++++++++++-------- src/typeselectorpopup.cpp | 2 +- src/workspace_model.h | 6 +- 13 files changed, 337 insertions(+), 110 deletions(-) diff --git a/src/controller.cpp b/src/controller.cpp index 5a2c231..236eb57 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -1,5 +1,6 @@ #include "controller.h" #include "addressparser.h" +#include "symbolstore.h" #include "typeselectorpopup.h" #include "providerregistry.h" #include "themes/thememanager.h" @@ -74,8 +75,10 @@ RcxDocument::RcxDocument(QObject* parent) } ComposeResult RcxDocument::compose(uint64_t viewRootId, bool compactColumns, - bool treeLines, bool braceWrap, bool typeHints) const { - return rcx::compose(tree, *provider, viewRootId, compactColumns, treeLines, braceWrap, typeHints); + bool treeLines, bool braceWrap, bool typeHints, + SymbolLookupFn symbolLookup) const { + return rcx::compose(tree, *provider, viewRootId, compactColumns, treeLines, braceWrap, typeHints, + std::move(symbolLookup)); } bool RcxDocument::save(const QString& path) { @@ -269,6 +272,10 @@ void RcxController::connectEditor(RcxEditor* editor) { // Footer "Trim" button — remove trailing hex nodes from end of struct connect(editor, &RcxEditor::trimHexRequested, this, [this](uint64_t structId) { + // Unions don't have trailing padding — all members overlap at offset 0 + int si = m_doc->tree.indexOfId(structId); + if (si >= 0 && m_doc->tree.nodes[si].classKeyword == QStringLiteral("union")) + return; QVector children = m_doc->tree.childrenOf(structId); if (children.isEmpty()) return; @@ -304,7 +311,7 @@ void RcxController::connectEditor(RcxEditor* editor) { int64_t nextVal = members.isEmpty() ? 0 : members.last().second + 1; auto oldMembers = members; for (int i = 0; i < count; i++) - members.append({QStringLiteral("Member%1").arg(nextVal + i), nextVal + i}); + members.emplaceBack(QStringLiteral("Member%1").arg(nextVal + i), nextVal + i); m_doc->undoStack.push(new RcxCommand(this, cmd::ChangeEnumMembers{enumId, oldMembers, members})); }); @@ -442,6 +449,9 @@ void RcxController::connectEditor(RcxEditor* editor) { *ok = prov->read(addr, &val, ptrSz); return val; }; + cbs.resolveIdentifier = [prov](const QString& name, bool* ok) -> uint64_t { + return SymbolStore::instance().resolve(name, prov, ok); + }; // Wire kernel paging callbacks if provider supports it if (prov->hasKernelPaging()) { cbs.vtop = [prov](uint32_t pid, uint64_t va, bool* ok) -> uint64_t { @@ -467,9 +477,9 @@ void RcxController::connectEditor(RcxEditor* editor) { if (result.ok && result.value != m_doc->tree.baseAddress) { uint64_t oldBase = m_doc->tree.baseAddress; QString oldFormula = m_doc->tree.baseAddressFormula; - // Store formula if input uses module/deref/kernel-function syntax + // Store formula if input uses module/deref/kernel-function/symbol syntax static const QRegularExpression formulaRx( - QStringLiteral("[<\\[]|\\b(?:vtop|cr3|phys)\\s*\\(")); + QStringLiteral("[<\\[]|\\b(?:vtop|cr3|phys)\\s*\\(|\\w+!\\w+")); QString newFormula = formulaRx.match(s).hasMatch() ? s : QString(); m_doc->undoStack.push(new RcxCommand(this, cmd::ChangeBase{oldBase, result.value, oldFormula, newFormula})); @@ -631,11 +641,20 @@ void RcxController::refresh() { // Bracket compose with thread-local doc pointer for type name resolution s_composeDoc = m_doc; + // Build symbol lookup callback if PDB symbols are loaded + SymbolLookupFn symLookup; + if (SymbolStore::instance().hasSymbols() && m_doc->provider) { + auto* prov = m_doc->provider.get(); + symLookup = [prov](uint64_t addr) -> QString { + return SymbolStore::instance().getSymbolForAddress(addr, prov); + }; + } + // Compose against snapshot provider if active, otherwise real provider if (m_snapshotProv) - m_lastResult = rcx::compose(m_doc->tree, *m_snapshotProv, m_viewRootId, m_compactColumns, m_treeLines, m_braceWrap, m_typeHints); + m_lastResult = rcx::compose(m_doc->tree, *m_snapshotProv, m_viewRootId, m_compactColumns, m_treeLines, m_braceWrap, m_typeHints, symLookup); else - m_lastResult = m_doc->compose(m_viewRootId, m_compactColumns, m_treeLines, m_braceWrap, m_typeHints); + m_lastResult = m_doc->compose(m_viewRootId, m_compactColumns, m_treeLines, m_braceWrap, m_typeHints, symLookup); s_composeDoc = nullptr; @@ -836,7 +855,7 @@ void RcxController::changeNodeKind(int nodeIdx, NodeKind newKind) { if (si == nodeIdx) continue; auto& sib = m_doc->tree.nodes[si]; if (sib.offset >= oldEnd) - adjs.append({sib.id, sib.offset, sib.offset + delta}); + adjs.push_back(cmd::OffsetAdj{sib.id, sib.offset, sib.offset + delta}); } } bool needsRename = isHexNode(node.kind) && !isHexNode(newKind); @@ -910,7 +929,7 @@ void RcxController::insertNodeAbove(int beforeIdx, NodeKind kind, const QString& for (int si : siblings) { auto& sib = m_doc->tree.nodes[si]; if (sib.offset >= before.offset) - adjs.append({sib.id, sib.offset, sib.offset + insertSize}); + adjs.push_back(cmd::OffsetAdj{sib.id, sib.offset, sib.offset + insertSize}); } m_doc->undoStack.push(new RcxCommand(this, cmd::Insert{n, adjs})); @@ -935,7 +954,7 @@ void RcxController::removeNode(int nodeIdx) { if (si == nodeIdx) continue; auto& sib = m_doc->tree.nodes[si]; if (sib.offset >= deletedEnd) { - adjs.append({sib.id, sib.offset, sib.offset - deletedSize}); + adjs.push_back(cmd::OffsetAdj{sib.id, sib.offset, sib.offset - deletedSize}); } } } @@ -1442,7 +1461,7 @@ void RcxController::duplicateNode(int nodeIdx) { if (si == nodeIdx) continue; auto& sib = m_doc->tree.nodes[si]; if (sib.offset >= copyOffset) - adjs.append({sib.id, sib.offset, sib.offset + copySize}); + adjs.push_back(cmd::OffsetAdj{sib.id, sib.offset, sib.offset + copySize}); } } @@ -1762,14 +1781,24 @@ void RcxController::showContextMenu(RcxEditor* editor, int line, int nodeIdx, // ── Insert ► submenu ── { + // Find earliest selected node (lowest offset) for insert-above + int firstIdx = -1; + int lowestOff = INT_MAX; + for (uint64_t id : ids) { + int idx = m_doc->tree.indexOfId(id); + if (idx >= 0 && m_doc->tree.nodes[idx].offset < lowestOff) { + lowestOff = m_doc->tree.nodes[idx].offset; + firstIdx = idx; + } + } auto* insertMenu = menu.addMenu(icon("diff-added.svg"), "Insert"); - insertMenu->addAction("Insert 4", [this]() { - uint64_t target = m_viewRootId ? m_viewRootId : 0; - insertNode(target, -1, NodeKind::Hex32, QStringLiteral("field")); + insertMenu->addAction("Insert 4 Above", [this, firstIdx]() { + if (firstIdx >= 0) + insertNodeAbove(firstIdx, NodeKind::Hex32, QStringLiteral("field")); }); - insertMenu->addAction("Insert 8", [this]() { - uint64_t target = m_viewRootId ? m_viewRootId : 0; - insertNode(target, -1, NodeKind::Hex64, QStringLiteral("field")); + insertMenu->addAction("Insert 8 Above", [this, firstIdx]() { + if (firstIdx >= 0) + insertNodeAbove(firstIdx, NodeKind::Hex64, QStringLiteral("field")); }); } @@ -1919,7 +1948,7 @@ void RcxController::showContextMenu(RcxEditor* editor, int line, int nodeIdx, auto members = m_doc->tree.nodes[ni].enumMembers; int64_t nextVal = members.isEmpty() ? 0 : members.last().second + 1; auto oldMembers = members; - members.append({QStringLiteral("NewMember"), nextVal}); + members.emplaceBack(QStringLiteral("NewMember"), nextVal); m_doc->undoStack.push(new RcxCommand(this, cmd::ChangeEnumMembers{nodeId, oldMembers, members})); }); @@ -3328,6 +3357,9 @@ void RcxController::attachViaPlugin(const QString& providerIdentifier, const QSt *ok = prov->read(addr, &val, ptrSz); return val; }; + cbs.resolveIdentifier = [prov](const QString& name, bool* ok) -> uint64_t { + return SymbolStore::instance().resolve(name, prov, ok); + }; // Wire kernel paging callbacks if provider supports it if (prov->hasKernelPaging()) { cbs.vtop = [prov](uint32_t pid, uint64_t va, bool* ok) -> uint64_t { @@ -3470,6 +3502,9 @@ void RcxController::selectSource(const QString& text) { *ok = prov->read(addr, &val, ptrSz); return val; }; + cbs.resolveIdentifier = [prov](const QString& name, bool* ok) -> uint64_t { + return SymbolStore::instance().resolve(name, prov, ok); + }; // Wire kernel paging callbacks if provider supports it if (prov->hasKernelPaging()) { cbs.vtop = [prov](uint32_t pid, uint64_t va, bool* ok) -> uint64_t { @@ -3616,7 +3651,7 @@ void RcxController::collectPointerRanges( int span = m_doc->tree.structSpan(structId); if (span <= 0) return; - ranges.append({memBase, span}); + ranges.emplaceBack(memBase, span); if (!m_snapshotProv) return; @@ -3664,7 +3699,7 @@ void RcxController::onRefreshTick() { // Collect all needed ranges: main struct + pointer targets (absolute addresses) QVector> ranges; - ranges.append({m_doc->tree.baseAddress, extent}); + ranges.emplaceBack(m_doc->tree.baseAddress, extent); if (m_snapshotProv) { QSet> visited; diff --git a/src/core.h b/src/core.h index 9794d2f..387f4cc 100644 --- a/src/core.h +++ b/src/core.h @@ -297,8 +297,8 @@ struct Node { QJsonArray arr = o["enumMembers"].toArray(); for (const auto& v : arr) { QJsonObject em = v.toObject(); - n.enumMembers.append({em["name"].toString(), - em["value"].toString("0").toLongLong()}); + n.enumMembers.emplaceBack(em["name"].toString(), + em["value"].toString("0").toLongLong()); } } if (o.contains("bitfieldMembers")) { @@ -1043,8 +1043,13 @@ namespace fmt { // ── Compose function forward declaration ── +// Optional callback: given an absolute address, return a symbol name (e.g. "nt!PsActiveProcessHead") +// or empty string if no symbol matches. Used for PDB symbol annotations on rows. +using SymbolLookupFn = std::function; + ComposeResult compose(const NodeTree& tree, const Provider& prov, uint64_t viewRootId = 0, bool compactColumns = false, bool treeLines = false, - bool braceWrap = false, bool typeHints = false); + bool braceWrap = false, bool typeHints = false, + SymbolLookupFn symbolLookup = {}); } // namespace rcx diff --git a/src/imports/import_pdb.cpp b/src/imports/import_pdb.cpp index 0bb8672..c4db6eb 100644 --- a/src/imports/import_pdb.cpp +++ b/src/imports/import_pdb.cpp @@ -396,7 +396,7 @@ uint64_t PdbCtx::importEnum(uint32_t typeIndex) { field->data.LF_ENUMERATE.value, field->data.LF_ENUMERATE.lfEasy.kind); if (eName) - s.enumMembers.append({QString::fromUtf8(eName), val}); + s.enumMembers.emplaceBack(QString::fromUtf8(eName), val); i += static_cast(eName - reinterpret_cast(field)); i += strnlen(eName, maxSize - i - 1) + 1; @@ -880,7 +880,7 @@ void PdbCtx::importMemberType(uint32_t typeIndex, int offset, const QString& nam n.name = name; n.parentId = parentId; n.offset = offset; - n.bitfieldMembers.append({name, bitPos, bitLen}); + n.bitfieldMembers.push_back(BitfieldMember{name, bitPos, bitLen}); tree.addNode(n); break; } @@ -943,6 +943,118 @@ struct PdbFile { } }; +// ── Public API: extractPdbSymbols ── + +PdbSymbolResult extractPdbSymbols(const QString& pdbPath, QString* errorMsg) { + auto setErr = [&](const QString& msg) { if (errorMsg) *errorMsg = msg; }; + + MappedFile mapped; + if (!QFile::exists(pdbPath)) { + setErr(QStringLiteral("PDB file not found: ") + pdbPath); + return {}; + } + if (!mapped.open(pdbPath)) { + setErr(QStringLiteral("Failed to memory-map PDB file: ") + pdbPath); + return {}; + } + if (PDB::ValidateFile(mapped.base, mapped.size) != PDB::ErrorCode::Success) { + setErr(QStringLiteral("Invalid PDB file: ") + pdbPath); + return {}; + } + + PDB::RawFile rawFile = PDB::CreateRawFile(mapped.base); + if (PDB::HasValidDBIStream(rawFile) != PDB::ErrorCode::Success) { + setErr(QStringLiteral("PDB has no valid DBI stream: ") + pdbPath); + return {}; + } + + const PDB::DBIStream dbiStream = PDB::CreateDBIStream(rawFile); + + // Validate required sub-streams + if (dbiStream.HasValidSymbolRecordStream(rawFile) != PDB::ErrorCode::Success || + dbiStream.HasValidPublicSymbolStream(rawFile) != PDB::ErrorCode::Success || + dbiStream.HasValidImageSectionStream(rawFile) != PDB::ErrorCode::Success) { + setErr(QStringLiteral("PDB DBI stream missing required sub-streams")); + return {}; + } + + const PDB::ImageSectionStream imageSectionStream = dbiStream.CreateImageSectionStream(rawFile); + const PDB::CoalescedMSFStream symbolRecordStream = dbiStream.CreateSymbolRecordStream(rawFile); + + PdbSymbolResult result; + + // Derive module name from PDB filename (e.g. "ntoskrnl.pdb" → "ntoskrnl") + QFileInfo fi(pdbPath); + result.moduleName = fi.completeBaseName(); + + // Read public symbols (S_PUB32) + const PDB::PublicSymbolStream publicSymbolStream = dbiStream.CreatePublicSymbolStream(rawFile); + { + const PDB::ArrayView hashRecords = publicSymbolStream.GetRecords(); + const size_t count = hashRecords.GetLength(); + result.symbols.reserve(static_cast(count)); + + for (const PDB::HashRecord& hashRecord : hashRecords) { + const PDB::CodeView::DBI::Record* record = + publicSymbolStream.GetRecord(symbolRecordStream, hashRecord); + if (record->header.kind != PDB::CodeView::DBI::SymbolRecordKind::S_PUB32) + continue; + + const uint32_t rva = imageSectionStream.ConvertSectionOffsetToRVA( + record->data.S_PUB32.section, record->data.S_PUB32.offset); + if (rva == 0u) + continue; + + result.symbols.push_back(PdbSymbol{QString::fromUtf8(record->data.S_PUB32.name), rva}); + } + } + + // Read global symbols (S_GDATA32, S_GTHREAD32, S_LDATA32, S_LTHREAD32, S_GPROC32, S_LPROC32) + if (dbiStream.HasValidGlobalSymbolStream(rawFile) == PDB::ErrorCode::Success) { + const PDB::GlobalSymbolStream globalSymbolStream = dbiStream.CreateGlobalSymbolStream(rawFile); + const PDB::ArrayView hashRecords = globalSymbolStream.GetRecords(); + + result.symbols.reserve(result.symbols.size() + static_cast(hashRecords.GetLength())); + + for (const PDB::HashRecord& hashRecord : hashRecords) { + const PDB::CodeView::DBI::Record* record = + globalSymbolStream.GetRecord(symbolRecordStream, hashRecord); + + const char* name = nullptr; + uint32_t rva = 0u; + + if (record->header.kind == PDB::CodeView::DBI::SymbolRecordKind::S_GDATA32) { + name = record->data.S_GDATA32.name; + rva = imageSectionStream.ConvertSectionOffsetToRVA( + record->data.S_GDATA32.section, record->data.S_GDATA32.offset); + } else if (record->header.kind == PDB::CodeView::DBI::SymbolRecordKind::S_GTHREAD32) { + name = record->data.S_GTHREAD32.name; + rva = imageSectionStream.ConvertSectionOffsetToRVA( + record->data.S_GTHREAD32.section, record->data.S_GTHREAD32.offset); + } else if (record->header.kind == PDB::CodeView::DBI::SymbolRecordKind::S_LDATA32) { + name = record->data.S_LDATA32.name; + rva = imageSectionStream.ConvertSectionOffsetToRVA( + record->data.S_LDATA32.section, record->data.S_LDATA32.offset); + } else if (record->header.kind == PDB::CodeView::DBI::SymbolRecordKind::S_LTHREAD32) { + name = record->data.S_LTHREAD32.name; + rva = imageSectionStream.ConvertSectionOffsetToRVA( + record->data.S_LTHREAD32.section, record->data.S_LTHREAD32.offset); + } + + if (rva == 0u) + continue; + if (!name || name[0] == '\0') + continue; + + result.symbols.push_back(PdbSymbol{QString::fromUtf8(name), rva}); + } + } + + qDebug() << "[PDB] extractPdbSymbols:" << result.symbols.size() << "symbols from" + << result.moduleName; + return result; +} + // ── Public API: enumeratePdbTypes ── QVector enumeratePdbTypes(const QString& pdbPath, QString* errorMsg) { @@ -1126,6 +1238,11 @@ NodeTree importPdb(const QString& pdbPath, const QString& structFilter, QString* namespace rcx { +PdbSymbolResult extractPdbSymbols(const QString&, QString* errorMsg) { + if (errorMsg) *errorMsg = QStringLiteral("PDB import requires Windows"); + return {}; +} + QVector enumeratePdbTypes(const QString&, QString* errorMsg) { if (errorMsg) *errorMsg = QStringLiteral("PDB import requires Windows"); return {}; diff --git a/src/imports/import_reclass_xml.cpp b/src/imports/import_reclass_xml.cpp index e26255d..5da5275 100644 --- a/src/imports/import_reclass_xml.cpp +++ b/src/imports/import_reclass_xml.cpp @@ -294,7 +294,7 @@ NodeTree importReclassXml(const QString& filePath, QString* errorMsg, int pointe // Defer ref resolution if array references a class if (!arrayClassName.isEmpty()) { - pendingRefs.append({arrId, arrayClassName}); + pendingRefs.push_back(PendingRef{arrId, arrayClassName}); } childOffset += nodeSize > 0 ? nodeSize : 0; @@ -321,7 +321,7 @@ NodeTree importReclassXml(const QString& filePath, QString* errorMsg, int pointe n.collapsed = true; // Start collapsed to avoid recursive expansion freeze int nodeIdx = tree.addNode(n); uint64_t nodeId = tree.nodes[nodeIdx].id; - pendingRefs.append({nodeId, ptrClass}); + pendingRefs.push_back(PendingRef{nodeId, ptrClass}); childOffset += nodeSize > 0 ? nodeSize : sizeForKind(kind); continue; } @@ -335,7 +335,7 @@ NodeTree importReclassXml(const QString& filePath, QString* errorMsg, int pointe if (!n.structTypeName.isEmpty()) { int nodeIdx = tree.addNode(n); uint64_t nodeId = tree.nodes[nodeIdx].id; - pendingRefs.append({nodeId, n.structTypeName}); + pendingRefs.push_back(PendingRef{nodeId, n.structTypeName}); } else { tree.addNode(n); } diff --git a/src/imports/import_source.cpp b/src/imports/import_source.cpp index 7c3f30a..ff67902 100644 --- a/src/imports/import_source.cpp +++ b/src/imports/import_source.cpp @@ -200,10 +200,10 @@ struct Tokenizer { case '=': tk = TokKind::Equals; break; default: tk = TokKind::Other; break; } - tokens.append({tk, QString(c), line}); + tokens.push_back(Token{tk, QString(c), line}); pos++; } - tokens.append({TokKind::Eof, {}, line}); + tokens.push_back(Token{TokKind::Eof, {}, line}); } private: @@ -241,7 +241,7 @@ private: bool ok; int val = m.captured(1).toInt(&ok, 16); if (ok) { - offsets.append({commentLine, val}); + offsets.push_back(LineOffset{commentLine, val}); } } } @@ -259,7 +259,7 @@ private: void parseIdent() { int start = pos; while (pos < src.size() && (src[pos].isLetterOrNumber() || src[pos] == '_')) pos++; - tokens.append({TokKind::Ident, src.mid(start, pos - start), line}); + tokens.push_back(Token{TokKind::Ident, src.mid(start, pos - start), line}); } void parseNumber() { @@ -276,7 +276,7 @@ private: // Skip integer suffixes (U, L, LL, ULL, etc.) while (pos < src.size() && (src[pos] == 'u' || src[pos] == 'U' || src[pos] == 'l' || src[pos] == 'L')) pos++; - tokens.append({TokKind::Number, src.mid(start, pos - start), line}); + tokens.push_back(Token{TokKind::Number, src.mid(start, pos - start), line}); } }; @@ -1034,7 +1034,7 @@ struct Parser { } } - ps.enumValues.append({memberName, memberValue}); + ps.enumValues.emplaceBack(memberName, memberValue); nextValue = memberValue + 1; // Skip comma between members @@ -1312,7 +1312,7 @@ static void buildFields(BuildContext& ctx, uint64_t parentId, int baseOffset, if (!field.pointerTarget.isEmpty() && field.pointerTarget != QStringLiteral("void")) { - ctx.pendingRefs.append({nodeId, field.pointerTarget}); + ctx.pendingRefs.push_back(PendingRef{nodeId, field.pointerTarget}); } computedOffset = fieldOffset + ctx.ptrSize; @@ -1342,7 +1342,7 @@ static void buildFields(BuildContext& ctx, uint64_t parentId, int baseOffset, n.offset = fieldOffset; int nodeIdx = ctx.tree.addNode(n); uint64_t nodeId = ctx.tree.nodes[nodeIdx].id; - ctx.pendingRefs.append({nodeId, field.typeName}); + ctx.pendingRefs.push_back(PendingRef{nodeId, field.typeName}); computedOffset = fieldOffset + elemSize; } continue; @@ -1461,7 +1461,7 @@ static void buildFields(BuildContext& ctx, uint64_t parentId, int baseOffset, int nodeIdx = ctx.tree.addNode(n); uint64_t nodeId = ctx.tree.nodes[nodeIdx].id; - ctx.pendingRefs.append({nodeId, field.typeName}); + ctx.pendingRefs.push_back(PendingRef{nodeId, field.typeName}); if (elemSize > 0) computedOffset = fieldOffset + totalElements * elemSize; continue; @@ -1477,7 +1477,7 @@ static void buildFields(BuildContext& ctx, uint64_t parentId, int baseOffset, int nodeIdx = ctx.tree.addNode(n); uint64_t nodeId = ctx.tree.nodes[nodeIdx].id; - ctx.pendingRefs.append({nodeId, field.typeName}); + ctx.pendingRefs.push_back(PendingRef{nodeId, field.typeName}); if (elemSize > 0) computedOffset = fieldOffset + elemSize; continue; diff --git a/src/main.cpp b/src/main.cpp index c2e2cde..9ebd38c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -570,6 +570,9 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) { m_titleBar->applyTheme(ThemeManager::instance().current()); setMenuWidget(m_titleBar); m_menuBar = m_titleBar->menuBar(); +#ifdef __linux__ + m_menuBar->setNativeMenuBar(false); +#endif #else setWindowTitle(QStringLiteral("Reclass")); setUnifiedTitleAndToolBarOnMac(true); @@ -3640,7 +3643,7 @@ void MainWindow::importPdb() { QVector> symPairs; symPairs.reserve(symResult.symbols.size()); for (const auto& s : symResult.symbols) - symPairs.append({s.name, s.rva}); + symPairs.emplaceBack(s.name, s.rva); int symCount = rcx::SymbolStore::instance().addModule( symResult.moduleName, pdbPath, symPairs); if (symCount > 0) @@ -4207,7 +4210,7 @@ void MainWindow::createWorkspaceDock() { const auto& nd = m_tabs[dk].doc->tree.nodes[ni]; QString tn = nd.structTypeName.isEmpty() ? nd.name : nd.structTypeName; if (tn.isEmpty()) tn = QStringLiteral("(unnamed)"); - items.append({sid, dk, ni, nd.resolvedClassKeyword(), tn}); + items.push_back(SelItem{sid, dk, ni, nd.resolvedClassKeyword(), tn}); } if (items.isEmpty()) return; @@ -4888,7 +4891,7 @@ void MainWindow::createSymbolsDock() { QVector> pairs; pairs.reserve(result.symbols.size()); for (const auto& s : result.symbols) - pairs.append({s.name, s.rva}); + pairs.emplaceBack(s.name, s.rva); int count = rcx::SymbolStore::instance().addModule( result.moduleName, pdbPath, pairs); setAppStatus(QStringLiteral("Loaded %1 symbols for %2").arg(count).arg(name)); @@ -4981,7 +4984,7 @@ void MainWindow::createSymbolsDock() { QVector> pairs; pairs.reserve(result.symbols.size()); for (const auto& s : result.symbols) - pairs.append({s.name, s.rva}); + pairs.emplaceBack(s.name, s.rva); int count = rcx::SymbolStore::instance().addModule( result.moduleName, localPdb, pairs); setAppStatus(QStringLiteral("Loaded %1 symbols for %2 (local)") @@ -5017,7 +5020,7 @@ void MainWindow::createSymbolsDock() { QVector> pairs; pairs.reserve(result.symbols.size()); for (const auto& s : result.symbols) - pairs.append({s.name, s.rva}); + pairs.emplaceBack(s.name, s.rva); int count = rcx::SymbolStore::instance().addModule( result.moduleName, localPath, pairs); setAppStatus(QStringLiteral("Loaded %1 symbols for %2") @@ -5042,7 +5045,7 @@ void MainWindow::createSymbolsDock() { QVector> pairs; pairs.reserve(result.symbols.size()); for (const auto& s : result.symbols) - pairs.append({s.name, s.rva}); + pairs.emplaceBack(s.name, s.rva); int count = rcx::SymbolStore::instance().addModule( result.moduleName, cached, pairs); setAppStatus(QStringLiteral("Loaded %1 symbols for %2 (cached)") @@ -5073,7 +5076,7 @@ void MainWindow::createSymbolsDock() { QVector> pairs; pairs.reserve(result.symbols.size()); for (const auto& s : result.symbols) - pairs.append({s.name, s.rva}); + pairs.emplaceBack(s.name, s.rva); int count = rcx::SymbolStore::instance().addModule( result.moduleName, path, pairs); setAppStatus(QStringLiteral("Loaded %1 symbols for %2") @@ -5159,7 +5162,7 @@ void MainWindow::createSymbolsDock() { m_symbolsProxy->setRecursiveFilteringEnabled(true); m_symbolsTree->setModel(m_symbolsProxy); - m_symbolsTree->setExpandsOnDoubleClick(true); + m_symbolsTree->setExpandsOnDoubleClick(false); styleTree(m_symbolsTree); // Debounced search @@ -5199,12 +5202,57 @@ void MainWindow::createSymbolsDock() { child->setData(moduleName, Qt::UserRole); child->setData(sym.first, Qt::UserRole + 1); child->setData(sym.second, Qt::UserRole + 2); + static const QIcon symIcon(":/vsicons/symbol-method.svg"); + child->setIcon(symIcon); item->appendRow(child); } } } }); + // Double-click symbol → navigate to moduleBase + RVA + connect(m_symbolsTree, &QTreeView::doubleClicked, this, [this](const QModelIndex& proxyIdx) { + QModelIndex srcIdx = m_symbolsProxy->mapToSource(proxyIdx); + auto* item = m_symbolsModel->itemFromIndex(srcIdx); + if (!item) return; + + // Module-level: toggle expand + if (!item->parent()) { + if (m_symbolsTree->isExpanded(proxyIdx)) + m_symbolsTree->collapse(proxyIdx); + else + m_symbolsTree->expand(proxyIdx); + return; + } + + // Symbol-level: navigate to moduleBase + RVA + QString moduleName = item->data(Qt::UserRole).toString(); + uint32_t rva = item->data(Qt::UserRole + 1).toUInt(); + + auto* ctrl = activeController(); + if (!ctrl || !ctrl->document()->provider) return; + + uint64_t moduleBase = ctrl->document()->provider->symbolToAddress(moduleName); + if (moduleBase == 0) + moduleBase = ctrl->document()->provider->symbolToAddress(moduleName + QStringLiteral(".dll")); + if (moduleBase == 0) + moduleBase = ctrl->document()->provider->symbolToAddress(moduleName + QStringLiteral(".exe")); + if (moduleBase == 0) + moduleBase = ctrl->document()->provider->symbolToAddress(moduleName + QStringLiteral(".sys")); + if (moduleBase == 0) return; + + uint64_t addr = moduleBase + rva; + ctrl->document()->tree.baseAddress = addr; + ctrl->document()->tree.baseAddressFormula.clear(); + ctrl->resetChangeTracking(); + ctrl->refresh(); + + QString symName = item->data(Qt::UserRole + 2).toString(); + setAppStatus(QStringLiteral("Navigated to %1!%2 (0x%3)") + .arg(moduleName, symName) + .arg(addr, 0, 16)); + }); + // Context menu for symbols m_symbolsTree->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_symbolsTree, &QWidget::customContextMenuRequested, this, [this](const QPoint& pos) { @@ -5288,7 +5336,8 @@ void MainWindow::rebuildSymbolsModel() { if (!set) continue; int count = set->nameToRva.size(); - auto* moduleItem = new QStandardItem( + static const QIcon modIcon(":/vsicons/symbol-structure.svg"); + auto* moduleItem = new QStandardItem(modIcon, QStringLiteral("%1 (%2 symbols)").arg(moduleName).arg(count)); moduleItem->setData(moduleName, Qt::UserRole); moduleItem->setToolTip(set->pdbPath); @@ -5310,8 +5359,9 @@ void MainWindow::rebuildModulesModel() { auto modules = ctrl->document()->provider->enumerateModules(); if (modules.isEmpty()) return; + static const QIcon modIcon(":/vsicons/symbol-structure.svg"); for (const auto& mod : modules) { - auto* item = new QStandardItem( + auto* item = new QStandardItem(modIcon, QStringLiteral("%1 [0x%2] (%3 KB)") .arg(mod.name) .arg(mod.base, 0, 16) @@ -5363,7 +5413,7 @@ void MainWindow::downloadSymbolsForProcess() { QVector> pairs; pairs.reserve(result.symbols.size()); for (const auto& s : result.symbols) - pairs.append({s.name, s.rva}); + pairs.emplaceBack(s.name, s.rva); int count = rcx::SymbolStore::instance().addModule( result.moduleName, localPath, pairs); setAppStatus(QStringLiteral("Loaded %1 symbols for %2") @@ -5409,7 +5459,7 @@ void MainWindow::downloadSymbolsForProcess() { QVector> pairs; pairs.reserve(result.symbols.size()); for (const auto& s : result.symbols) - pairs.append({s.name, s.rva}); + pairs.emplaceBack(s.name, s.rva); int count = store.addModule(result.moduleName, localPdb, pairs); setAppStatus(QStringLiteral("Loaded %1 symbols for %2 (local)") .arg(count).arg(mod.name)); @@ -5433,7 +5483,7 @@ void MainWindow::downloadSymbolsForProcess() { QVector> pairs; pairs.reserve(result.symbols.size()); for (const auto& s : result.symbols) - pairs.append({s.name, s.rva}); + pairs.emplaceBack(s.name, s.rva); int count = store.addModule(result.moduleName, cached, pairs); setAppStatus(QStringLiteral("Loaded %1 symbols for %2 (cached)") .arg(count).arg(mod.name)); @@ -5442,7 +5492,7 @@ void MainWindow::downloadSymbolsForProcess() { continue; } - pending.append({mod.name, mod.fullPath, mod.base, info}); + pending.push_back(PendingModule{mod.name, mod.fullPath, mod.base, info}); } rebuildSymbolsModel(); @@ -5515,7 +5565,7 @@ void MainWindow::rebuildWorkspaceModelNow() { if (seenDocs.contains(tab.doc)) continue; seenDocs.insert(tab.doc); QString name = rootName(tab.doc->tree, tab.ctrl->viewRootId()); - tabs.append({ &tab.doc->tree, name, static_cast(it.key()) }); + tabs.push_back(rcx::TabInfo{ &tab.doc->tree, name, static_cast(it.key()) }); } rcx::syncProjectExplorer(m_workspaceModel, tabs, m_pinnedIds); @@ -5843,6 +5893,9 @@ int main(int argc, char* argv[]) { window.setWindowIcon(QIcon(":/icons/class.png")); window.show(); +#ifdef __linux__ + window.showMaximized(); +#endif // --screenshot : open default project, grab window, save, exit { diff --git a/src/mcp/mcp_bridge.cpp b/src/mcp/mcp_bridge.cpp index 8837934..34385bd 100644 --- a/src/mcp/mcp_bridge.cpp +++ b/src/mcp/mcp_bridge.cpp @@ -121,7 +121,7 @@ void McpBridge::onNewConnection() { auto* pending = m_server->nextPendingConnection(); if (!pending) return; - m_clients.append({pending, {}, false}); + m_clients.push_back(ClientState{pending, {}, false}); connect(pending, &QLocalSocket::readyRead, this, &McpBridge::onReadyRead); @@ -156,7 +156,7 @@ void McpBridge::onReadyRead() { if (line.isEmpty()) continue; if (m_processing) { - m_pendingRequests.append({sock, line}); + m_pendingRequests.push_back(PendingRequest{sock, line}); continue; } m_processing = true; @@ -819,7 +819,7 @@ QJsonObject McpBridge::toolProjectState(const QJsonObject& args) { QJsonArray nodeArr; struct QueueEntry { uint64_t parentId; int depth; }; QVector queue; - queue.append({filterParentId, 0}); + queue.push_back(QueueEntry{filterParentId, 0}); int totalCount = 0; // total nodes that match depth filter int emitted = 0; @@ -839,13 +839,13 @@ QJsonObject McpBridge::toolProjectState(const QJsonObject& args) { if (totalCount <= offset) { // Still skipping — but enqueue children for counting if (entry.depth + 1 <= maxDepth) - queue.append({n.id, entry.depth + 1}); + queue.push_back(QueueEntry{n.id, entry.depth + 1}); continue; } if (emitted >= limit) { // Past limit — just keep counting total if (entry.depth + 1 <= maxDepth) - queue.append({n.id, entry.depth + 1}); + queue.push_back(QueueEntry{n.id, entry.depth + 1}); continue; } @@ -875,7 +875,7 @@ QJsonObject McpBridge::toolProjectState(const QJsonObject& args) { // Enqueue children if we haven't hit depth limit if (entry.depth + 1 <= maxDepth) - queue.append({n.id, entry.depth + 1}); + queue.push_back(QueueEntry{n.id, entry.depth + 1}); } } @@ -1665,7 +1665,7 @@ static QVector parseRegionsArg(const QJsonObject& args, QString* e if (errOut) *errOut = QStringLiteral("regions[%1]: end must be > start").arg(i); return {}; } - out.append({start, end}); + out.push_back(AddressRange{start, end}); } return out; } diff --git a/src/pluginmanager.cpp b/src/pluginmanager.cpp index 8aa42e8..400942e 100644 --- a/src/pluginmanager.cpp +++ b/src/pluginmanager.cpp @@ -41,6 +41,11 @@ void PluginManager::LoadPlugins() for (const QFileInfo& fileInfo : files) { + // Skip the remote-inject payload binary — it's not a plugin and + // loading it (especially on Linux) spawns a rogue thread. + if (fileInfo.baseName().startsWith("rcx_payload")) + continue; + LoadPlugin(fileInfo.absoluteFilePath()); } @@ -83,7 +88,7 @@ bool PluginManager::LoadPlugin(const QString& path) qDebug() << "PluginManager: Loaded plugin:" << plugin->Name().c_str() << plugin->Version().c_str() << "by" << plugin->Author().c_str(); // Store plugin entry - m_entries.append({library, plugin}); + m_entries.push_back(PluginEntry{library, plugin}); m_plugins.append(plugin); // Auto-register providers in global registry diff --git a/src/startpage.h b/src/startpage.h index b3ad860..37f6345 100644 --- a/src/startpage.h +++ b/src/startpage.h @@ -171,8 +171,8 @@ private: for (const auto& path : s.value("recentFiles").toStringList()) { QFileInfo fi(path); if (!fi.exists()) continue; - m_all.append({fi.absoluteFilePath(), fi.fileName(), fi.absolutePath(), - fi.lastModified(), false}); + m_all.push_back(Entry{fi.absoluteFilePath(), fi.fileName(), fi.absolutePath(), + fi.lastModified(), false}); } #ifdef __APPLE__ QDir exDir(QDir::cleanPath(QCoreApplication::applicationDirPath() + "/../Resources/examples")); @@ -180,8 +180,8 @@ private: QDir exDir(QCoreApplication::applicationDirPath() + "/examples"); #endif for (const auto& fn : exDir.entryList({"*.rcx"}, QDir::Files, QDir::Name)) - m_all.append({exDir.absoluteFilePath(fn), fn, exDir.absolutePath(), - QFileInfo(exDir.filePath(fn)).lastModified(), true}); + m_all.push_back(Entry{exDir.absoluteFilePath(fn), fn, exDir.absolutePath(), + QFileInfo(exDir.filePath(fn)).lastModified(), true}); } void buildGroups() { @@ -207,7 +207,7 @@ private: static const char* names[] = {"Today","Yesterday","This week","This month","Older","Examples"}; m_groups.clear(); for (int i = 0; i < 6; i++) - if (!bk[i].isEmpty()) m_groups.append({names[i], true, bk[i]}); + if (!bk[i].isEmpty()) m_groups.push_back(Group{names[i], true, bk[i]}); m_scrollY = 0; } @@ -223,13 +223,11 @@ private: {":/vsicons/debug.svg", "Import PDB", "Import types from a .pdb symbol file"} }; - const int N = 5, CH = 84, R = 6, panelH = N * CH; + const int N = 5, CH = 84, panelH = N * CH; - // Rounded panel background - QPainterPath clip; - clip.addRoundedRect(QRectF(x, y, w, panelH), R, R); + // Sharp-cornered panel background p.save(); - p.setClipPath(clip); + p.setClipRect(QRectF(x, y, w, panelH)); p.fillRect(x, y, w, panelH, m_t.background); for (int i = 0; i < N; i++) { @@ -289,7 +287,7 @@ private: if (gi > 0) fy += 15; // Group header - m_grpRects.append({gi, QRectF(x, fy, w, 28)}); + m_grpRects.emplaceBack(gi, QRectF(x, fy, w, 28)); p.setPen(Qt::NoPen); p.setBrush(m_t.text); int triX = x + 8, triY = fy + 11; QPolygonF tri; @@ -307,7 +305,7 @@ private: for (int ei : g.entries) { auto& e = m_filtered[ei]; QRectF er(x, fy, w, 52); - m_entRects.append({ei, er}); + m_entRects.emplaceBack(ei, er); if (m_hz == HZ_Entry && m_hi == ei) p.fillRect(er, m_t.hover); drawIcon(p, e.isExample ? ":/vsicons/book.svg" : ":/vsicons/symbol-structure.svg", diff --git a/src/symbolstore.cpp b/src/symbolstore.cpp index 0ba3316..f7ad971 100644 --- a/src/symbolstore.cpp +++ b/src/symbolstore.cpp @@ -31,7 +31,7 @@ int SymbolStore::addModule(const QString& moduleName, const QString& pdbPath, if (set.nameToRva.contains(sym.first)) continue; set.nameToRva.insert(sym.first, sym.second); - set.rvaToName.append({sym.second, sym.first}); + set.rvaToName.emplaceBack(sym.second, sym.first); } set.sortRvaIndex(); diff --git a/src/typeinfer.h b/src/typeinfer.h index 5b0bced..56cdf11 100644 --- a/src/typeinfer.h +++ b/src/typeinfer.h @@ -191,23 +191,26 @@ inline FeatureResult countFlagFeatures(uint32_t val, // ── Pointer feature checker ── inline FeatureResult countPtrFeatures64(uint64_t val) { - // Hard reject: common sentinel values are never pointers + // Hard reject: common sentinel values if (val == 0 || val == 0xFFFFFFFFFFFFFFFFULL || val == 0x00000000FFFFFFFFULL) - return {0, 6}; + return {0, 5}; - int passed = 0, checked = 6; - // Feature 1: canonical 48-bit address (sign-extended from bit 47) - passed += (val <= 0x00007FFFFFFFFFFFULL - || val >= 0xFFFF800000000000ULL) ? 1 : 0; - // Feature 2: aligned to 8 (heap/vtable allocations) + // Hard reject: non-canonical address — impossible to dereference on x64 + // User-mode: 0x0000000000000000 – 0x00007FFFFFFFFFFF + // Kernel: 0xFFFF800000000000 – 0xFFFFFFFFFFFFFFFF + if (val > 0x00007FFFFFFFFFFFULL && val < 0xFFFF800000000000ULL) + return {0, 5}; + + int passed = 0, checked = 5; + // Feature 1: aligned to 8 (heap/vtable allocations) passed += ((val & 7) == 0) ? 1 : 0; - // Feature 3: above null guard pages (real addresses >= 64KB) + // Feature 2: above null guard pages (real addresses >= 64KB) passed += (val >= 0x10000) ? 1 : 0; - // Feature 4: has upper 32 bits (real 64-bit address, not a small constant) + // Feature 3: has upper 32 bits (real 64-bit address, not a small constant) passed += ((val >> 32) != 0) ? 1 : 0; - // Feature 5: above 4GB (in real 64-bit address space, not a 32-bit value) + // Feature 4: above 4GB (in real 64-bit address space) passed += (val > 0x100000000ULL) ? 1 : 0; - // Feature 6: user-mode address range (not kernel 0xFFFF800000000000+) + // Feature 5: user-mode address range (not kernel) passed += (val < 0xFFFF800000000000ULL) ? 1 : 0; return {passed, checked}; } @@ -289,13 +292,13 @@ struct Candidate { }; inline void addCandidate(QVector& out, NodeKind k, int score) { - if (score >= 25) out.append({{k}, score}); + if (score >= 25) out.push_back(Candidate{{k}, score}); } inline void addSplitCandidate(QVector& out, NodeKind k, int count, int score) { if (score >= 25) { QVector kinds(count, k); - out.append({std::move(kinds), score}); + out.push_back(Candidate{std::move(kinds), score}); } } @@ -308,32 +311,43 @@ inline void tryWhole8(const uint8_t* data, const InferHints& h, QVector> 52) & 0x7FF; - int passed = 0, checked = 3; - passed += std::isfinite(d) ? 1 : 0; - passed += (exp > 0 || (u64 & 0x7FFFFFFFFFFFFFFFull) == 0) ? 1 : 0; double ad = std::fabs(d); - passed += (d == 0.0 || (ad >= 1e-6 && ad <= 1e12)) ? 1 : 0; - addCandidate(out, NodeKind::Double, featureScore({passed, checked})); + uint64_t mantissa = u64 & 0x000FFFFFFFFFFFFFull; + // Hard reject: outside plausible range [1e-6, 1e7] (matches float checker) + bool inRange = (d == 0.0 || (ad >= 1e-6 && ad <= 1e7)); + // Hard reject: lower 32 zero with non-zero mantissa (two 32-bit fields) + bool splitField = ((u64 & 0xFFFFFFFF) == 0 && mantissa != 0); + if (inRange && !splitField) { + uint64_t exp = (u64 >> 52) & 0x7FF; + int passed = 0, checked = 4; + // Feature 1: finite + passed += std::isfinite(d) ? 1 : 0; + // Feature 2: non-denormal + passed += (exp > 0 || (u64 & 0x7FFFFFFFFFFFFFFFull) == 0) ? 1 : 0; + // Feature 3: has fractional part or is a small special value + double ip; double frac = std::fabs(std::modf(d, &ip)); + passed += (frac > 0.001 || ad <= 1.0) ? 1 : 0; + // Feature 4: not a large exact integer (likely reinterpreted binary data) + passed += !(ad > 1000.0 && frac < 0.001) ? 1 : 0; + addCandidate(out, NodeKind::Double, featureScore({passed, checked})); + } } // UTF8 addCandidate(out, NodeKind::UTF8, featureScore(countStringFeatures(data, 8))); - // UInt64 / Int64 - { - int passed = 0, checked = 4; - // Feature 1: fits in 32 bits (small constant, not an address) - passed += (u64 <= 0xFFFFFFFFull) ? 1 : 0; - // Feature 2: upper 32 bits are zero (confirms it's a small value, not a pointer) - passed += ((u64 >> 32) == 0) ? 1 : 0; - // Feature 3: non-zero - passed += (u64 != 0) ? 1 : 0; - // Feature 4: monotonic or very small (< 0x10000) - passed += (h.monotonic || u64 < 0x10000) ? 1 : 0; + // UInt64 / Int64 — only meaningful when value exceeds 32-bit range + if ((u64 >> 32) != 0) { + int passed = 0, checked = 3; + // Feature 1: non-zero (always true after guard) + passed += 1; + // Feature 2: reasonable magnitude (below kernel range) + passed += (u64 < 0x0000FFFFFFFFFFFFULL) ? 1 : 0; + // Feature 3: monotonic or page-aligned + passed += (h.monotonic || (u64 & 0xFFF) == 0) ? 1 : 0; addCandidate(out, NodeKind::UInt64, featureScore({passed, checked})); } } @@ -467,7 +481,7 @@ inline QVector pruneAndRank(QVector& cands, int maxRe for (const auto& c : deduped) { int str = strengthFromScore(c.score); if (str > 0) - result.append({c.kinds, c.score, str}); + result.push_back(TypeSuggestion{c.kinds, c.score, str}); } return result; } diff --git a/src/typeselectorpopup.cpp b/src/typeselectorpopup.cpp index fa629c7..c8fef85 100644 --- a/src/typeselectorpopup.cpp +++ b/src/typeselectorpopup.cpp @@ -1030,7 +1030,7 @@ void TypeSelectorPopup::applyFilter(const QString& text) { else if (t.category == TypeEntry::CatEnum) enumCount++; else typeCount++; if (catAllowed(t)) - scored.append({i, sc, std::move(pos)}); + scored.push_back(Scored{i, sc, std::move(pos)}); } std::sort(scored.begin(), scored.end(), [](const Scored& a, const Scored& b) { return a.score > b.score; }); diff --git a/src/workspace_model.h b/src/workspace_model.h index 3bbb13a..f560b66 100644 --- a/src/workspace_model.h +++ b/src/workspace_model.h @@ -108,9 +108,9 @@ inline void buildProjectExplorer(QStandardItemModel* model, const Node& n = tab.tree->nodes[idx]; if (n.kind != NodeKind::Struct) continue; if (n.resolvedClassKeyword() == QStringLiteral("enum")) - enums.append({&n, tab.subPtr, tab.tree}); + enums.push_back(Entry{&n, tab.subPtr, tab.tree}); else - types.append({&n, tab.subPtr, tab.tree}); + types.push_back(Entry{&n, tab.subPtr, tab.tree}); } } @@ -152,7 +152,7 @@ inline void syncProjectExplorer(QStandardItemModel* model, const Node& n = tab.tree->nodes[idx]; if (n.kind != NodeKind::Struct) continue; bool ie = n.resolvedClassKeyword() == QStringLiteral("enum"); - desired.append({n.id, &n, tab.subPtr, tab.tree, ie}); + desired.push_back(Entry{n.id, &n, tab.subPtr, tab.tree, ie}); } }