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.
This commit is contained in:
IChooseYou
2026-03-14 09:21:14 -06:00
committed by IChooseYou
parent 5ded192990
commit 5921af2b4f
13 changed files with 337 additions and 110 deletions

View File

@@ -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<int> 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<QPair<uint64_t,int>> ranges;
ranges.append({m_doc->tree.baseAddress, extent});
ranges.emplaceBack(m_doc->tree.baseAddress, extent);
if (m_snapshotProv) {
QSet<QPair<uint64_t,uint64_t>> visited;

View File

@@ -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<QString(uint64_t addr)>;
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

View File

@@ -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<size_t>(eName - reinterpret_cast<const char*>(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<PDB::HashRecord> hashRecords = publicSymbolStream.GetRecords();
const size_t count = hashRecords.GetLength();
result.symbols.reserve(static_cast<int>(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<PDB::HashRecord> hashRecords = globalSymbolStream.GetRecords();
result.symbols.reserve(result.symbols.size() + static_cast<int>(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<PdbTypeInfo> 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<PdbTypeInfo> enumeratePdbTypes(const QString&, QString* errorMsg) {
if (errorMsg) *errorMsg = QStringLiteral("PDB import requires Windows");
return {};

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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<QPair<QString, uint32_t>> 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<QPair<QString, uint32_t>> 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<QPair<QString, uint32_t>> 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<QPair<QString, uint32_t>> 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<QPair<QString, uint32_t>> 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<QPair<QString, uint32_t>> 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<QPair<QString, uint32_t>> 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<QPair<QString, uint32_t>> 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<QPair<QString, uint32_t>> 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<void*>(it.key()) });
tabs.push_back(rcx::TabInfo{ &tab.doc->tree, name, static_cast<void*>(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 <path>: open default project, grab window, save, exit
{

View File

@@ -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<QueueEntry> 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<AddressRange> 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;
}

View File

@@ -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

View File

@@ -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",

View File

@@ -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();

View File

@@ -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<Candidate>& 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<Candidate>& out, NodeKind k, int count, int score) {
if (score >= 25) {
QVector<NodeKind> 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<Candidat
if (h.ptrSize == 8)
addCandidate(out, NodeKind::Pointer64, featureScore(countPtrFeatures64(u64)));
// Double
// Double — rare in RE work; require strong evidence
{
double d; std::memcpy(&d, data, 8);
uint64_t exp = (u64 >> 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<TypeSuggestion> pruneAndRank(QVector<Candidate>& 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;
}

View File

@@ -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; });

View File

@@ -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});
}
}