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 "controller.h"
#include "addressparser.h" #include "addressparser.h"
#include "symbolstore.h"
#include "typeselectorpopup.h" #include "typeselectorpopup.h"
#include "providerregistry.h" #include "providerregistry.h"
#include "themes/thememanager.h" #include "themes/thememanager.h"
@@ -74,8 +75,10 @@ RcxDocument::RcxDocument(QObject* parent)
} }
ComposeResult RcxDocument::compose(uint64_t viewRootId, bool compactColumns, ComposeResult RcxDocument::compose(uint64_t viewRootId, bool compactColumns,
bool treeLines, bool braceWrap, bool typeHints) const { bool treeLines, bool braceWrap, bool typeHints,
return rcx::compose(tree, *provider, viewRootId, compactColumns, treeLines, braceWrap, typeHints); SymbolLookupFn symbolLookup) const {
return rcx::compose(tree, *provider, viewRootId, compactColumns, treeLines, braceWrap, typeHints,
std::move(symbolLookup));
} }
bool RcxDocument::save(const QString& path) { 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 // Footer "Trim" button — remove trailing hex nodes from end of struct
connect(editor, &RcxEditor::trimHexRequested, connect(editor, &RcxEditor::trimHexRequested,
this, [this](uint64_t structId) { 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); QVector<int> children = m_doc->tree.childrenOf(structId);
if (children.isEmpty()) return; if (children.isEmpty()) return;
@@ -304,7 +311,7 @@ void RcxController::connectEditor(RcxEditor* editor) {
int64_t nextVal = members.isEmpty() ? 0 : members.last().second + 1; int64_t nextVal = members.isEmpty() ? 0 : members.last().second + 1;
auto oldMembers = members; auto oldMembers = members;
for (int i = 0; i < count; i++) 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, m_doc->undoStack.push(new RcxCommand(this,
cmd::ChangeEnumMembers{enumId, oldMembers, members})); cmd::ChangeEnumMembers{enumId, oldMembers, members}));
}); });
@@ -442,6 +449,9 @@ void RcxController::connectEditor(RcxEditor* editor) {
*ok = prov->read(addr, &val, ptrSz); *ok = prov->read(addr, &val, ptrSz);
return val; 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 // Wire kernel paging callbacks if provider supports it
if (prov->hasKernelPaging()) { if (prov->hasKernelPaging()) {
cbs.vtop = [prov](uint32_t pid, uint64_t va, bool* ok) -> uint64_t { 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) { if (result.ok && result.value != m_doc->tree.baseAddress) {
uint64_t oldBase = m_doc->tree.baseAddress; uint64_t oldBase = m_doc->tree.baseAddress;
QString oldFormula = m_doc->tree.baseAddressFormula; 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( 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(); QString newFormula = formulaRx.match(s).hasMatch() ? s : QString();
m_doc->undoStack.push(new RcxCommand(this, m_doc->undoStack.push(new RcxCommand(this,
cmd::ChangeBase{oldBase, result.value, oldFormula, newFormula})); 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 // Bracket compose with thread-local doc pointer for type name resolution
s_composeDoc = m_doc; 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 // Compose against snapshot provider if active, otherwise real provider
if (m_snapshotProv) 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 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; s_composeDoc = nullptr;
@@ -836,7 +855,7 @@ void RcxController::changeNodeKind(int nodeIdx, NodeKind newKind) {
if (si == nodeIdx) continue; if (si == nodeIdx) continue;
auto& sib = m_doc->tree.nodes[si]; auto& sib = m_doc->tree.nodes[si];
if (sib.offset >= oldEnd) 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); bool needsRename = isHexNode(node.kind) && !isHexNode(newKind);
@@ -910,7 +929,7 @@ void RcxController::insertNodeAbove(int beforeIdx, NodeKind kind, const QString&
for (int si : siblings) { for (int si : siblings) {
auto& sib = m_doc->tree.nodes[si]; auto& sib = m_doc->tree.nodes[si];
if (sib.offset >= before.offset) 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})); m_doc->undoStack.push(new RcxCommand(this, cmd::Insert{n, adjs}));
@@ -935,7 +954,7 @@ void RcxController::removeNode(int nodeIdx) {
if (si == nodeIdx) continue; if (si == nodeIdx) continue;
auto& sib = m_doc->tree.nodes[si]; auto& sib = m_doc->tree.nodes[si];
if (sib.offset >= deletedEnd) { 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; if (si == nodeIdx) continue;
auto& sib = m_doc->tree.nodes[si]; auto& sib = m_doc->tree.nodes[si];
if (sib.offset >= copyOffset) 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 ── // ── 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"); auto* insertMenu = menu.addMenu(icon("diff-added.svg"), "Insert");
insertMenu->addAction("Insert 4", [this]() { insertMenu->addAction("Insert 4 Above", [this, firstIdx]() {
uint64_t target = m_viewRootId ? m_viewRootId : 0; if (firstIdx >= 0)
insertNode(target, -1, NodeKind::Hex32, QStringLiteral("field")); insertNodeAbove(firstIdx, NodeKind::Hex32, QStringLiteral("field"));
}); });
insertMenu->addAction("Insert 8", [this]() { insertMenu->addAction("Insert 8 Above", [this, firstIdx]() {
uint64_t target = m_viewRootId ? m_viewRootId : 0; if (firstIdx >= 0)
insertNode(target, -1, NodeKind::Hex64, QStringLiteral("field")); 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; auto members = m_doc->tree.nodes[ni].enumMembers;
int64_t nextVal = members.isEmpty() ? 0 : members.last().second + 1; int64_t nextVal = members.isEmpty() ? 0 : members.last().second + 1;
auto oldMembers = members; auto oldMembers = members;
members.append({QStringLiteral("NewMember"), nextVal}); members.emplaceBack(QStringLiteral("NewMember"), nextVal);
m_doc->undoStack.push(new RcxCommand(this, m_doc->undoStack.push(new RcxCommand(this,
cmd::ChangeEnumMembers{nodeId, oldMembers, members})); cmd::ChangeEnumMembers{nodeId, oldMembers, members}));
}); });
@@ -3328,6 +3357,9 @@ void RcxController::attachViaPlugin(const QString& providerIdentifier, const QSt
*ok = prov->read(addr, &val, ptrSz); *ok = prov->read(addr, &val, ptrSz);
return val; 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 // Wire kernel paging callbacks if provider supports it
if (prov->hasKernelPaging()) { if (prov->hasKernelPaging()) {
cbs.vtop = [prov](uint32_t pid, uint64_t va, bool* ok) -> uint64_t { 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); *ok = prov->read(addr, &val, ptrSz);
return val; 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 // Wire kernel paging callbacks if provider supports it
if (prov->hasKernelPaging()) { if (prov->hasKernelPaging()) {
cbs.vtop = [prov](uint32_t pid, uint64_t va, bool* ok) -> uint64_t { 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); int span = m_doc->tree.structSpan(structId);
if (span <= 0) return; if (span <= 0) return;
ranges.append({memBase, span}); ranges.emplaceBack(memBase, span);
if (!m_snapshotProv) return; if (!m_snapshotProv) return;
@@ -3664,7 +3699,7 @@ void RcxController::onRefreshTick() {
// Collect all needed ranges: main struct + pointer targets (absolute addresses) // Collect all needed ranges: main struct + pointer targets (absolute addresses)
QVector<QPair<uint64_t,int>> ranges; QVector<QPair<uint64_t,int>> ranges;
ranges.append({m_doc->tree.baseAddress, extent}); ranges.emplaceBack(m_doc->tree.baseAddress, extent);
if (m_snapshotProv) { if (m_snapshotProv) {
QSet<QPair<uint64_t,uint64_t>> visited; QSet<QPair<uint64_t,uint64_t>> visited;

View File

@@ -297,8 +297,8 @@ struct Node {
QJsonArray arr = o["enumMembers"].toArray(); QJsonArray arr = o["enumMembers"].toArray();
for (const auto& v : arr) { for (const auto& v : arr) {
QJsonObject em = v.toObject(); QJsonObject em = v.toObject();
n.enumMembers.append({em["name"].toString(), n.enumMembers.emplaceBack(em["name"].toString(),
em["value"].toString("0").toLongLong()}); em["value"].toString("0").toLongLong());
} }
} }
if (o.contains("bitfieldMembers")) { if (o.contains("bitfieldMembers")) {
@@ -1043,8 +1043,13 @@ namespace fmt {
// ── Compose function forward declaration ── // ── 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, ComposeResult compose(const NodeTree& tree, const Provider& prov, uint64_t viewRootId = 0,
bool compactColumns = false, bool treeLines = false, bool compactColumns = false, bool treeLines = false,
bool braceWrap = false, bool typeHints = false); bool braceWrap = false, bool typeHints = false,
SymbolLookupFn symbolLookup = {});
} // namespace rcx } // namespace rcx

View File

@@ -396,7 +396,7 @@ uint64_t PdbCtx::importEnum(uint32_t typeIndex) {
field->data.LF_ENUMERATE.value, field->data.LF_ENUMERATE.value,
field->data.LF_ENUMERATE.lfEasy.kind); field->data.LF_ENUMERATE.lfEasy.kind);
if (eName) 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 += static_cast<size_t>(eName - reinterpret_cast<const char*>(field));
i += strnlen(eName, maxSize - i - 1) + 1; 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.name = name;
n.parentId = parentId; n.parentId = parentId;
n.offset = offset; n.offset = offset;
n.bitfieldMembers.append({name, bitPos, bitLen}); n.bitfieldMembers.push_back(BitfieldMember{name, bitPos, bitLen});
tree.addNode(n); tree.addNode(n);
break; 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 ── // ── Public API: enumeratePdbTypes ──
QVector<PdbTypeInfo> enumeratePdbTypes(const QString& pdbPath, QString* errorMsg) { QVector<PdbTypeInfo> enumeratePdbTypes(const QString& pdbPath, QString* errorMsg) {
@@ -1126,6 +1238,11 @@ NodeTree importPdb(const QString& pdbPath, const QString& structFilter, QString*
namespace rcx { namespace rcx {
PdbSymbolResult extractPdbSymbols(const QString&, QString* errorMsg) {
if (errorMsg) *errorMsg = QStringLiteral("PDB import requires Windows");
return {};
}
QVector<PdbTypeInfo> enumeratePdbTypes(const QString&, QString* errorMsg) { QVector<PdbTypeInfo> enumeratePdbTypes(const QString&, QString* errorMsg) {
if (errorMsg) *errorMsg = QStringLiteral("PDB import requires Windows"); if (errorMsg) *errorMsg = QStringLiteral("PDB import requires Windows");
return {}; return {};

View File

@@ -294,7 +294,7 @@ NodeTree importReclassXml(const QString& filePath, QString* errorMsg, int pointe
// Defer ref resolution if array references a class // Defer ref resolution if array references a class
if (!arrayClassName.isEmpty()) { if (!arrayClassName.isEmpty()) {
pendingRefs.append({arrId, arrayClassName}); pendingRefs.push_back(PendingRef{arrId, arrayClassName});
} }
childOffset += nodeSize > 0 ? nodeSize : 0; 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 n.collapsed = true; // Start collapsed to avoid recursive expansion freeze
int nodeIdx = tree.addNode(n); int nodeIdx = tree.addNode(n);
uint64_t nodeId = tree.nodes[nodeIdx].id; uint64_t nodeId = tree.nodes[nodeIdx].id;
pendingRefs.append({nodeId, ptrClass}); pendingRefs.push_back(PendingRef{nodeId, ptrClass});
childOffset += nodeSize > 0 ? nodeSize : sizeForKind(kind); childOffset += nodeSize > 0 ? nodeSize : sizeForKind(kind);
continue; continue;
} }
@@ -335,7 +335,7 @@ NodeTree importReclassXml(const QString& filePath, QString* errorMsg, int pointe
if (!n.structTypeName.isEmpty()) { if (!n.structTypeName.isEmpty()) {
int nodeIdx = tree.addNode(n); int nodeIdx = tree.addNode(n);
uint64_t nodeId = tree.nodes[nodeIdx].id; uint64_t nodeId = tree.nodes[nodeIdx].id;
pendingRefs.append({nodeId, n.structTypeName}); pendingRefs.push_back(PendingRef{nodeId, n.structTypeName});
} else { } else {
tree.addNode(n); tree.addNode(n);
} }

View File

@@ -200,10 +200,10 @@ struct Tokenizer {
case '=': tk = TokKind::Equals; break; case '=': tk = TokKind::Equals; break;
default: tk = TokKind::Other; break; default: tk = TokKind::Other; break;
} }
tokens.append({tk, QString(c), line}); tokens.push_back(Token{tk, QString(c), line});
pos++; pos++;
} }
tokens.append({TokKind::Eof, {}, line}); tokens.push_back(Token{TokKind::Eof, {}, line});
} }
private: private:
@@ -241,7 +241,7 @@ private:
bool ok; bool ok;
int val = m.captured(1).toInt(&ok, 16); int val = m.captured(1).toInt(&ok, 16);
if (ok) { if (ok) {
offsets.append({commentLine, val}); offsets.push_back(LineOffset{commentLine, val});
} }
} }
} }
@@ -259,7 +259,7 @@ private:
void parseIdent() { void parseIdent() {
int start = pos; int start = pos;
while (pos < src.size() && (src[pos].isLetterOrNumber() || src[pos] == '_')) 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() { void parseNumber() {
@@ -276,7 +276,7 @@ private:
// Skip integer suffixes (U, L, LL, ULL, etc.) // Skip integer suffixes (U, L, LL, ULL, etc.)
while (pos < src.size() && (src[pos] == 'u' || src[pos] == 'U' || while (pos < src.size() && (src[pos] == 'u' || src[pos] == 'U' ||
src[pos] == 'l' || src[pos] == 'L')) pos++; 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; nextValue = memberValue + 1;
// Skip comma between members // Skip comma between members
@@ -1312,7 +1312,7 @@ static void buildFields(BuildContext& ctx, uint64_t parentId, int baseOffset,
if (!field.pointerTarget.isEmpty() && if (!field.pointerTarget.isEmpty() &&
field.pointerTarget != QStringLiteral("void")) { field.pointerTarget != QStringLiteral("void")) {
ctx.pendingRefs.append({nodeId, field.pointerTarget}); ctx.pendingRefs.push_back(PendingRef{nodeId, field.pointerTarget});
} }
computedOffset = fieldOffset + ctx.ptrSize; computedOffset = fieldOffset + ctx.ptrSize;
@@ -1342,7 +1342,7 @@ static void buildFields(BuildContext& ctx, uint64_t parentId, int baseOffset,
n.offset = fieldOffset; n.offset = fieldOffset;
int nodeIdx = ctx.tree.addNode(n); int nodeIdx = ctx.tree.addNode(n);
uint64_t nodeId = ctx.tree.nodes[nodeIdx].id; 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; computedOffset = fieldOffset + elemSize;
} }
continue; continue;
@@ -1461,7 +1461,7 @@ static void buildFields(BuildContext& ctx, uint64_t parentId, int baseOffset,
int nodeIdx = ctx.tree.addNode(n); int nodeIdx = ctx.tree.addNode(n);
uint64_t nodeId = ctx.tree.nodes[nodeIdx].id; 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) if (elemSize > 0)
computedOffset = fieldOffset + totalElements * elemSize; computedOffset = fieldOffset + totalElements * elemSize;
continue; continue;
@@ -1477,7 +1477,7 @@ static void buildFields(BuildContext& ctx, uint64_t parentId, int baseOffset,
int nodeIdx = ctx.tree.addNode(n); int nodeIdx = ctx.tree.addNode(n);
uint64_t nodeId = ctx.tree.nodes[nodeIdx].id; 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) if (elemSize > 0)
computedOffset = fieldOffset + elemSize; computedOffset = fieldOffset + elemSize;
continue; continue;

View File

@@ -570,6 +570,9 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) {
m_titleBar->applyTheme(ThemeManager::instance().current()); m_titleBar->applyTheme(ThemeManager::instance().current());
setMenuWidget(m_titleBar); setMenuWidget(m_titleBar);
m_menuBar = m_titleBar->menuBar(); m_menuBar = m_titleBar->menuBar();
#ifdef __linux__
m_menuBar->setNativeMenuBar(false);
#endif
#else #else
setWindowTitle(QStringLiteral("Reclass")); setWindowTitle(QStringLiteral("Reclass"));
setUnifiedTitleAndToolBarOnMac(true); setUnifiedTitleAndToolBarOnMac(true);
@@ -3640,7 +3643,7 @@ void MainWindow::importPdb() {
QVector<QPair<QString, uint32_t>> symPairs; QVector<QPair<QString, uint32_t>> symPairs;
symPairs.reserve(symResult.symbols.size()); symPairs.reserve(symResult.symbols.size());
for (const auto& s : symResult.symbols) for (const auto& s : symResult.symbols)
symPairs.append({s.name, s.rva}); symPairs.emplaceBack(s.name, s.rva);
int symCount = rcx::SymbolStore::instance().addModule( int symCount = rcx::SymbolStore::instance().addModule(
symResult.moduleName, pdbPath, symPairs); symResult.moduleName, pdbPath, symPairs);
if (symCount > 0) if (symCount > 0)
@@ -4207,7 +4210,7 @@ void MainWindow::createWorkspaceDock() {
const auto& nd = m_tabs[dk].doc->tree.nodes[ni]; const auto& nd = m_tabs[dk].doc->tree.nodes[ni];
QString tn = nd.structTypeName.isEmpty() ? nd.name : nd.structTypeName; QString tn = nd.structTypeName.isEmpty() ? nd.name : nd.structTypeName;
if (tn.isEmpty()) tn = QStringLiteral("(unnamed)"); 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; if (items.isEmpty()) return;
@@ -4888,7 +4891,7 @@ void MainWindow::createSymbolsDock() {
QVector<QPair<QString, uint32_t>> pairs; QVector<QPair<QString, uint32_t>> pairs;
pairs.reserve(result.symbols.size()); pairs.reserve(result.symbols.size());
for (const auto& s : result.symbols) for (const auto& s : result.symbols)
pairs.append({s.name, s.rva}); pairs.emplaceBack(s.name, s.rva);
int count = rcx::SymbolStore::instance().addModule( int count = rcx::SymbolStore::instance().addModule(
result.moduleName, pdbPath, pairs); result.moduleName, pdbPath, pairs);
setAppStatus(QStringLiteral("Loaded %1 symbols for %2").arg(count).arg(name)); setAppStatus(QStringLiteral("Loaded %1 symbols for %2").arg(count).arg(name));
@@ -4981,7 +4984,7 @@ void MainWindow::createSymbolsDock() {
QVector<QPair<QString, uint32_t>> pairs; QVector<QPair<QString, uint32_t>> pairs;
pairs.reserve(result.symbols.size()); pairs.reserve(result.symbols.size());
for (const auto& s : result.symbols) for (const auto& s : result.symbols)
pairs.append({s.name, s.rva}); pairs.emplaceBack(s.name, s.rva);
int count = rcx::SymbolStore::instance().addModule( int count = rcx::SymbolStore::instance().addModule(
result.moduleName, localPdb, pairs); result.moduleName, localPdb, pairs);
setAppStatus(QStringLiteral("Loaded %1 symbols for %2 (local)") setAppStatus(QStringLiteral("Loaded %1 symbols for %2 (local)")
@@ -5017,7 +5020,7 @@ void MainWindow::createSymbolsDock() {
QVector<QPair<QString, uint32_t>> pairs; QVector<QPair<QString, uint32_t>> pairs;
pairs.reserve(result.symbols.size()); pairs.reserve(result.symbols.size());
for (const auto& s : result.symbols) for (const auto& s : result.symbols)
pairs.append({s.name, s.rva}); pairs.emplaceBack(s.name, s.rva);
int count = rcx::SymbolStore::instance().addModule( int count = rcx::SymbolStore::instance().addModule(
result.moduleName, localPath, pairs); result.moduleName, localPath, pairs);
setAppStatus(QStringLiteral("Loaded %1 symbols for %2") setAppStatus(QStringLiteral("Loaded %1 symbols for %2")
@@ -5042,7 +5045,7 @@ void MainWindow::createSymbolsDock() {
QVector<QPair<QString, uint32_t>> pairs; QVector<QPair<QString, uint32_t>> pairs;
pairs.reserve(result.symbols.size()); pairs.reserve(result.symbols.size());
for (const auto& s : result.symbols) for (const auto& s : result.symbols)
pairs.append({s.name, s.rva}); pairs.emplaceBack(s.name, s.rva);
int count = rcx::SymbolStore::instance().addModule( int count = rcx::SymbolStore::instance().addModule(
result.moduleName, cached, pairs); result.moduleName, cached, pairs);
setAppStatus(QStringLiteral("Loaded %1 symbols for %2 (cached)") setAppStatus(QStringLiteral("Loaded %1 symbols for %2 (cached)")
@@ -5073,7 +5076,7 @@ void MainWindow::createSymbolsDock() {
QVector<QPair<QString, uint32_t>> pairs; QVector<QPair<QString, uint32_t>> pairs;
pairs.reserve(result.symbols.size()); pairs.reserve(result.symbols.size());
for (const auto& s : result.symbols) for (const auto& s : result.symbols)
pairs.append({s.name, s.rva}); pairs.emplaceBack(s.name, s.rva);
int count = rcx::SymbolStore::instance().addModule( int count = rcx::SymbolStore::instance().addModule(
result.moduleName, path, pairs); result.moduleName, path, pairs);
setAppStatus(QStringLiteral("Loaded %1 symbols for %2") setAppStatus(QStringLiteral("Loaded %1 symbols for %2")
@@ -5159,7 +5162,7 @@ void MainWindow::createSymbolsDock() {
m_symbolsProxy->setRecursiveFilteringEnabled(true); m_symbolsProxy->setRecursiveFilteringEnabled(true);
m_symbolsTree->setModel(m_symbolsProxy); m_symbolsTree->setModel(m_symbolsProxy);
m_symbolsTree->setExpandsOnDoubleClick(true); m_symbolsTree->setExpandsOnDoubleClick(false);
styleTree(m_symbolsTree); styleTree(m_symbolsTree);
// Debounced search // Debounced search
@@ -5199,12 +5202,57 @@ void MainWindow::createSymbolsDock() {
child->setData(moduleName, Qt::UserRole); child->setData(moduleName, Qt::UserRole);
child->setData(sym.first, Qt::UserRole + 1); child->setData(sym.first, Qt::UserRole + 1);
child->setData(sym.second, Qt::UserRole + 2); child->setData(sym.second, Qt::UserRole + 2);
static const QIcon symIcon(":/vsicons/symbol-method.svg");
child->setIcon(symIcon);
item->appendRow(child); 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 // Context menu for symbols
m_symbolsTree->setContextMenuPolicy(Qt::CustomContextMenu); m_symbolsTree->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_symbolsTree, &QWidget::customContextMenuRequested, this, [this](const QPoint& pos) { connect(m_symbolsTree, &QWidget::customContextMenuRequested, this, [this](const QPoint& pos) {
@@ -5288,7 +5336,8 @@ void MainWindow::rebuildSymbolsModel() {
if (!set) continue; if (!set) continue;
int count = set->nameToRva.size(); 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)); QStringLiteral("%1 (%2 symbols)").arg(moduleName).arg(count));
moduleItem->setData(moduleName, Qt::UserRole); moduleItem->setData(moduleName, Qt::UserRole);
moduleItem->setToolTip(set->pdbPath); moduleItem->setToolTip(set->pdbPath);
@@ -5310,8 +5359,9 @@ void MainWindow::rebuildModulesModel() {
auto modules = ctrl->document()->provider->enumerateModules(); auto modules = ctrl->document()->provider->enumerateModules();
if (modules.isEmpty()) return; if (modules.isEmpty()) return;
static const QIcon modIcon(":/vsicons/symbol-structure.svg");
for (const auto& mod : modules) { for (const auto& mod : modules) {
auto* item = new QStandardItem( auto* item = new QStandardItem(modIcon,
QStringLiteral("%1 [0x%2] (%3 KB)") QStringLiteral("%1 [0x%2] (%3 KB)")
.arg(mod.name) .arg(mod.name)
.arg(mod.base, 0, 16) .arg(mod.base, 0, 16)
@@ -5363,7 +5413,7 @@ void MainWindow::downloadSymbolsForProcess() {
QVector<QPair<QString, uint32_t>> pairs; QVector<QPair<QString, uint32_t>> pairs;
pairs.reserve(result.symbols.size()); pairs.reserve(result.symbols.size());
for (const auto& s : result.symbols) for (const auto& s : result.symbols)
pairs.append({s.name, s.rva}); pairs.emplaceBack(s.name, s.rva);
int count = rcx::SymbolStore::instance().addModule( int count = rcx::SymbolStore::instance().addModule(
result.moduleName, localPath, pairs); result.moduleName, localPath, pairs);
setAppStatus(QStringLiteral("Loaded %1 symbols for %2") setAppStatus(QStringLiteral("Loaded %1 symbols for %2")
@@ -5409,7 +5459,7 @@ void MainWindow::downloadSymbolsForProcess() {
QVector<QPair<QString, uint32_t>> pairs; QVector<QPair<QString, uint32_t>> pairs;
pairs.reserve(result.symbols.size()); pairs.reserve(result.symbols.size());
for (const auto& s : result.symbols) 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); int count = store.addModule(result.moduleName, localPdb, pairs);
setAppStatus(QStringLiteral("Loaded %1 symbols for %2 (local)") setAppStatus(QStringLiteral("Loaded %1 symbols for %2 (local)")
.arg(count).arg(mod.name)); .arg(count).arg(mod.name));
@@ -5433,7 +5483,7 @@ void MainWindow::downloadSymbolsForProcess() {
QVector<QPair<QString, uint32_t>> pairs; QVector<QPair<QString, uint32_t>> pairs;
pairs.reserve(result.symbols.size()); pairs.reserve(result.symbols.size());
for (const auto& s : result.symbols) 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); int count = store.addModule(result.moduleName, cached, pairs);
setAppStatus(QStringLiteral("Loaded %1 symbols for %2 (cached)") setAppStatus(QStringLiteral("Loaded %1 symbols for %2 (cached)")
.arg(count).arg(mod.name)); .arg(count).arg(mod.name));
@@ -5442,7 +5492,7 @@ void MainWindow::downloadSymbolsForProcess() {
continue; continue;
} }
pending.append({mod.name, mod.fullPath, mod.base, info}); pending.push_back(PendingModule{mod.name, mod.fullPath, mod.base, info});
} }
rebuildSymbolsModel(); rebuildSymbolsModel();
@@ -5515,7 +5565,7 @@ void MainWindow::rebuildWorkspaceModelNow() {
if (seenDocs.contains(tab.doc)) continue; if (seenDocs.contains(tab.doc)) continue;
seenDocs.insert(tab.doc); seenDocs.insert(tab.doc);
QString name = rootName(tab.doc->tree, tab.ctrl->viewRootId()); 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); rcx::syncProjectExplorer(m_workspaceModel, tabs, m_pinnedIds);
@@ -5843,6 +5893,9 @@ int main(int argc, char* argv[]) {
window.setWindowIcon(QIcon(":/icons/class.png")); window.setWindowIcon(QIcon(":/icons/class.png"));
window.show(); window.show();
#ifdef __linux__
window.showMaximized();
#endif
// --screenshot <path>: open default project, grab window, save, exit // --screenshot <path>: open default project, grab window, save, exit
{ {

View File

@@ -121,7 +121,7 @@ void McpBridge::onNewConnection() {
auto* pending = m_server->nextPendingConnection(); auto* pending = m_server->nextPendingConnection();
if (!pending) return; if (!pending) return;
m_clients.append({pending, {}, false}); m_clients.push_back(ClientState{pending, {}, false});
connect(pending, &QLocalSocket::readyRead, connect(pending, &QLocalSocket::readyRead,
this, &McpBridge::onReadyRead); this, &McpBridge::onReadyRead);
@@ -156,7 +156,7 @@ void McpBridge::onReadyRead() {
if (line.isEmpty()) continue; if (line.isEmpty()) continue;
if (m_processing) { if (m_processing) {
m_pendingRequests.append({sock, line}); m_pendingRequests.push_back(PendingRequest{sock, line});
continue; continue;
} }
m_processing = true; m_processing = true;
@@ -819,7 +819,7 @@ QJsonObject McpBridge::toolProjectState(const QJsonObject& args) {
QJsonArray nodeArr; QJsonArray nodeArr;
struct QueueEntry { uint64_t parentId; int depth; }; struct QueueEntry { uint64_t parentId; int depth; };
QVector<QueueEntry> queue; QVector<QueueEntry> queue;
queue.append({filterParentId, 0}); queue.push_back(QueueEntry{filterParentId, 0});
int totalCount = 0; // total nodes that match depth filter int totalCount = 0; // total nodes that match depth filter
int emitted = 0; int emitted = 0;
@@ -839,13 +839,13 @@ QJsonObject McpBridge::toolProjectState(const QJsonObject& args) {
if (totalCount <= offset) { if (totalCount <= offset) {
// Still skipping — but enqueue children for counting // Still skipping — but enqueue children for counting
if (entry.depth + 1 <= maxDepth) if (entry.depth + 1 <= maxDepth)
queue.append({n.id, entry.depth + 1}); queue.push_back(QueueEntry{n.id, entry.depth + 1});
continue; continue;
} }
if (emitted >= limit) { if (emitted >= limit) {
// Past limit — just keep counting total // Past limit — just keep counting total
if (entry.depth + 1 <= maxDepth) if (entry.depth + 1 <= maxDepth)
queue.append({n.id, entry.depth + 1}); queue.push_back(QueueEntry{n.id, entry.depth + 1});
continue; continue;
} }
@@ -875,7 +875,7 @@ QJsonObject McpBridge::toolProjectState(const QJsonObject& args) {
// Enqueue children if we haven't hit depth limit // Enqueue children if we haven't hit depth limit
if (entry.depth + 1 <= maxDepth) 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); if (errOut) *errOut = QStringLiteral("regions[%1]: end must be > start").arg(i);
return {}; return {};
} }
out.append({start, end}); out.push_back(AddressRange{start, end});
} }
return out; return out;
} }

View File

@@ -41,6 +41,11 @@ void PluginManager::LoadPlugins()
for (const QFileInfo& fileInfo : files) 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()); 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(); qDebug() << "PluginManager: Loaded plugin:" << plugin->Name().c_str() << plugin->Version().c_str() << "by" << plugin->Author().c_str();
// Store plugin entry // Store plugin entry
m_entries.append({library, plugin}); m_entries.push_back(PluginEntry{library, plugin});
m_plugins.append(plugin); m_plugins.append(plugin);
// Auto-register providers in global registry // Auto-register providers in global registry

View File

@@ -171,8 +171,8 @@ private:
for (const auto& path : s.value("recentFiles").toStringList()) { for (const auto& path : s.value("recentFiles").toStringList()) {
QFileInfo fi(path); QFileInfo fi(path);
if (!fi.exists()) continue; if (!fi.exists()) continue;
m_all.append({fi.absoluteFilePath(), fi.fileName(), fi.absolutePath(), m_all.push_back(Entry{fi.absoluteFilePath(), fi.fileName(), fi.absolutePath(),
fi.lastModified(), false}); fi.lastModified(), false});
} }
#ifdef __APPLE__ #ifdef __APPLE__
QDir exDir(QDir::cleanPath(QCoreApplication::applicationDirPath() + "/../Resources/examples")); QDir exDir(QDir::cleanPath(QCoreApplication::applicationDirPath() + "/../Resources/examples"));
@@ -180,8 +180,8 @@ private:
QDir exDir(QCoreApplication::applicationDirPath() + "/examples"); QDir exDir(QCoreApplication::applicationDirPath() + "/examples");
#endif #endif
for (const auto& fn : exDir.entryList({"*.rcx"}, QDir::Files, QDir::Name)) for (const auto& fn : exDir.entryList({"*.rcx"}, QDir::Files, QDir::Name))
m_all.append({exDir.absoluteFilePath(fn), fn, exDir.absolutePath(), m_all.push_back(Entry{exDir.absoluteFilePath(fn), fn, exDir.absolutePath(),
QFileInfo(exDir.filePath(fn)).lastModified(), true}); QFileInfo(exDir.filePath(fn)).lastModified(), true});
} }
void buildGroups() { void buildGroups() {
@@ -207,7 +207,7 @@ private:
static const char* names[] = {"Today","Yesterday","This week","This month","Older","Examples"}; static const char* names[] = {"Today","Yesterday","This week","This month","Older","Examples"};
m_groups.clear(); m_groups.clear();
for (int i = 0; i < 6; i++) 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; m_scrollY = 0;
} }
@@ -223,13 +223,11 @@ private:
{":/vsicons/debug.svg", "Import PDB", "Import types from a .pdb symbol file"} {":/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 // Sharp-cornered panel background
QPainterPath clip;
clip.addRoundedRect(QRectF(x, y, w, panelH), R, R);
p.save(); p.save();
p.setClipPath(clip); p.setClipRect(QRectF(x, y, w, panelH));
p.fillRect(x, y, w, panelH, m_t.background); p.fillRect(x, y, w, panelH, m_t.background);
for (int i = 0; i < N; i++) { for (int i = 0; i < N; i++) {
@@ -289,7 +287,7 @@ private:
if (gi > 0) fy += 15; if (gi > 0) fy += 15;
// Group header // 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); p.setPen(Qt::NoPen); p.setBrush(m_t.text);
int triX = x + 8, triY = fy + 11; int triX = x + 8, triY = fy + 11;
QPolygonF tri; QPolygonF tri;
@@ -307,7 +305,7 @@ private:
for (int ei : g.entries) { for (int ei : g.entries) {
auto& e = m_filtered[ei]; auto& e = m_filtered[ei];
QRectF er(x, fy, w, 52); 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); 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", 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)) if (set.nameToRva.contains(sym.first))
continue; continue;
set.nameToRva.insert(sym.first, sym.second); set.nameToRva.insert(sym.first, sym.second);
set.rvaToName.append({sym.second, sym.first}); set.rvaToName.emplaceBack(sym.second, sym.first);
} }
set.sortRvaIndex(); set.sortRvaIndex();

View File

@@ -191,23 +191,26 @@ inline FeatureResult countFlagFeatures(uint32_t val,
// ── Pointer feature checker ── // ── Pointer feature checker ──
inline FeatureResult countPtrFeatures64(uint64_t val) { 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) if (val == 0 || val == 0xFFFFFFFFFFFFFFFFULL || val == 0x00000000FFFFFFFFULL)
return {0, 6}; return {0, 5};
int passed = 0, checked = 6; // Hard reject: non-canonical address — impossible to dereference on x64
// Feature 1: canonical 48-bit address (sign-extended from bit 47) // User-mode: 0x0000000000000000 0x00007FFFFFFFFFFF
passed += (val <= 0x00007FFFFFFFFFFFULL // Kernel: 0xFFFF800000000000 0xFFFFFFFFFFFFFFFF
|| val >= 0xFFFF800000000000ULL) ? 1 : 0; if (val > 0x00007FFFFFFFFFFFULL && val < 0xFFFF800000000000ULL)
// Feature 2: aligned to 8 (heap/vtable allocations) return {0, 5};
int passed = 0, checked = 5;
// Feature 1: aligned to 8 (heap/vtable allocations)
passed += ((val & 7) == 0) ? 1 : 0; 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; 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; 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; 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; passed += (val < 0xFFFF800000000000ULL) ? 1 : 0;
return {passed, checked}; return {passed, checked};
} }
@@ -289,13 +292,13 @@ struct Candidate {
}; };
inline void addCandidate(QVector<Candidate>& out, NodeKind k, int score) { 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) { inline void addSplitCandidate(QVector<Candidate>& out, NodeKind k, int count, int score) {
if (score >= 25) { if (score >= 25) {
QVector<NodeKind> kinds(count, k); 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) if (h.ptrSize == 8)
addCandidate(out, NodeKind::Pointer64, featureScore(countPtrFeatures64(u64))); addCandidate(out, NodeKind::Pointer64, featureScore(countPtrFeatures64(u64)));
// Double // Double — rare in RE work; require strong evidence
{ {
double d; std::memcpy(&d, data, 8); 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); double ad = std::fabs(d);
passed += (d == 0.0 || (ad >= 1e-6 && ad <= 1e12)) ? 1 : 0; uint64_t mantissa = u64 & 0x000FFFFFFFFFFFFFull;
addCandidate(out, NodeKind::Double, featureScore({passed, checked})); // 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 // UTF8
addCandidate(out, NodeKind::UTF8, featureScore(countStringFeatures(data, 8))); addCandidate(out, NodeKind::UTF8, featureScore(countStringFeatures(data, 8)));
// UInt64 / Int64 // UInt64 / Int64 — only meaningful when value exceeds 32-bit range
{ if ((u64 >> 32) != 0) {
int passed = 0, checked = 4; int passed = 0, checked = 3;
// Feature 1: fits in 32 bits (small constant, not an address) // Feature 1: non-zero (always true after guard)
passed += (u64 <= 0xFFFFFFFFull) ? 1 : 0; passed += 1;
// Feature 2: upper 32 bits are zero (confirms it's a small value, not a pointer) // Feature 2: reasonable magnitude (below kernel range)
passed += ((u64 >> 32) == 0) ? 1 : 0; passed += (u64 < 0x0000FFFFFFFFFFFFULL) ? 1 : 0;
// Feature 3: non-zero // Feature 3: monotonic or page-aligned
passed += (u64 != 0) ? 1 : 0; passed += (h.monotonic || (u64 & 0xFFF) == 0) ? 1 : 0;
// Feature 4: monotonic or very small (< 0x10000)
passed += (h.monotonic || u64 < 0x10000) ? 1 : 0;
addCandidate(out, NodeKind::UInt64, featureScore({passed, checked})); 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) { for (const auto& c : deduped) {
int str = strengthFromScore(c.score); int str = strengthFromScore(c.score);
if (str > 0) if (str > 0)
result.append({c.kinds, c.score, str}); result.push_back(TypeSuggestion{c.kinds, c.score, str});
} }
return result; return result;
} }

View File

@@ -1030,7 +1030,7 @@ void TypeSelectorPopup::applyFilter(const QString& text) {
else if (t.category == TypeEntry::CatEnum) enumCount++; else if (t.category == TypeEntry::CatEnum) enumCount++;
else typeCount++; else typeCount++;
if (catAllowed(t)) 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(), std::sort(scored.begin(), scored.end(),
[](const Scored& a, const Scored& b) { return a.score > b.score; }); [](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]; const Node& n = tab.tree->nodes[idx];
if (n.kind != NodeKind::Struct) continue; if (n.kind != NodeKind::Struct) continue;
if (n.resolvedClassKeyword() == QStringLiteral("enum")) if (n.resolvedClassKeyword() == QStringLiteral("enum"))
enums.append({&n, tab.subPtr, tab.tree}); enums.push_back(Entry{&n, tab.subPtr, tab.tree});
else 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]; const Node& n = tab.tree->nodes[idx];
if (n.kind != NodeKind::Struct) continue; if (n.kind != NodeKind::Struct) continue;
bool ie = n.resolvedClassKeyword() == QStringLiteral("enum"); 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});
} }
} }