feat: enum support, workspace styling, EPROCESS/MMPFN test data

- Import enums from C/C++ source and PDB with name/value members
- Compose/format/generate enum definitions properly
- Workspace dock: rename to Project, theme-based titlebar and selection
- Add comprehensive EPROCESS.rcx (325 nodes) and MMPFN.rcx (65 nodes)
This commit is contained in:
IChooseYou
2026-02-23 16:01:35 -07:00
parent 078a6028f0
commit ff928df685
15 changed files with 2660 additions and 306 deletions

View File

@@ -234,6 +234,7 @@ struct PdbCtx {
QHash<uint32_t, uint64_t> typeCache; // typeIndex → nodeId
uint64_t importUDT(uint32_t typeIndex);
uint64_t importEnum(uint32_t typeIndex);
void importFieldList(uint32_t fieldListIndex, uint64_t parentId);
void importMemberType(uint32_t typeIndex, int offset, const QString& name, uint64_t parentId);
@@ -300,6 +301,59 @@ uint64_t PdbCtx::importUDT(uint32_t typeIndex) {
return nodeId;
}
uint64_t PdbCtx::importEnum(uint32_t typeIndex) {
if (typeIndex < tt->firstIndex()) return 0;
auto it = typeCache.find(typeIndex);
if (it != typeCache.end()) return it.value();
const auto* rec = tt->get(typeIndex);
if (!rec || rec->header.kind != TRK::LF_ENUM) return 0;
if (rec->data.LF_ENUM.property.fwdref) return 0;
QString qname = rec->data.LF_ENUM.name
? QString::fromUtf8(rec->data.LF_ENUM.name)
: QStringLiteral("<anon>");
Node s;
s.kind = NodeKind::Struct;
s.name = qname;
s.structTypeName = qname;
s.classKeyword = QStringLiteral("enum");
s.parentId = 0;
s.collapsed = true;
// Extract enum members from field list
uint32_t fieldListIndex = rec->data.LF_ENUM.field;
const auto* flRec = tt->get(fieldListIndex);
if (flRec && flRec->header.kind == TRK::LF_FIELDLIST) {
auto maxSize = flRec->header.size - sizeof(uint16_t);
for (size_t i = 0; i < maxSize; ) {
auto* field = reinterpret_cast<const PDB::CodeView::TPI::FieldList*>(
reinterpret_cast<const uint8_t*>(&flRec->data.LF_FIELD.list) + i);
if (field->kind != TRK::LF_ENUMERATE) break;
int64_t val = static_cast<int64_t>(leafValue(
field->data.LF_ENUMERATE.value,
field->data.LF_ENUMERATE.lfEasy.kind));
const char* eName = leafName(
field->data.LF_ENUMERATE.value,
field->data.LF_ENUMERATE.lfEasy.kind);
if (eName)
s.enumMembers.append({QString::fromUtf8(eName), val});
i += static_cast<size_t>(eName - reinterpret_cast<const char*>(field));
i += strnlen(eName, maxSize - i - 1) + 1;
i = (i + 3) & ~size_t(3);
}
}
int idx = tree.addNode(s);
uint64_t nodeId = tree.nodes[idx].id;
typeCache[typeIndex] = nodeId;
return nodeId;
}
void PdbCtx::importFieldList(uint32_t fieldListIndex, uint64_t parentId) {
const auto* rec = tt->get(fieldListIndex);
if (!rec || rec->header.kind != TRK::LF_FIELDLIST) return;
@@ -707,8 +761,9 @@ void PdbCtx::importMemberType(uint32_t typeIndex, int offset, const QString& nam
}
case TRK::LF_ENUM: {
// Map enum to its underlying integer type
// Map enum to its underlying integer type, link to enum definition
uint32_t utype = rec->data.LF_ENUM.utype;
uint64_t enumNodeId = importEnum(typeIndex);
Node n;
if (utype < tt->firstIndex()) {
n.kind = mapPrimitiveType(utype);
@@ -718,6 +773,7 @@ void PdbCtx::importMemberType(uint32_t typeIndex, int offset, const QString& nam
n.name = name;
n.parentId = parentId;
n.offset = offset;
n.refId = enumNodeId;
tree.addNode(n);
break;
}
@@ -823,14 +879,27 @@ QVector<PdbTypeInfo> enumeratePdbTypes(const QString& pdbPath, QString* errorMsg
bool isUDT = (rec->header.kind == TRK::LF_STRUCTURE ||
rec->header.kind == TRK::LF_CLASS ||
rec->header.kind == TRK::LF_UNION);
if (!isUDT) continue;
bool isEnum = (rec->header.kind == TRK::LF_ENUM);
if (!isUDT && !isEnum) continue;
const char* name = nullptr;
uint16_t fieldCount = 0;
bool isUnion = false;
uint64_t size = 0;
if (rec->header.kind == TRK::LF_UNION) {
if (isEnum) {
if (rec->data.LF_ENUM.property.fwdref) continue;
fieldCount = rec->data.LF_ENUM.count;
name = rec->data.LF_ENUM.name;
// Size from underlying type
uint32_t ut = rec->data.LF_ENUM.utype;
if (ut < tt.firstIndex()) {
NodeKind ek = mapPrimitiveType(ut);
size = sizeForKind(ek);
} else {
size = 4;
}
} else if (rec->header.kind == TRK::LF_UNION) {
if (rec->data.LF_UNION.property.fwdref) continue;
isUnion = true;
fieldCount = rec->data.LF_UNION.count;
@@ -856,6 +925,7 @@ QVector<PdbTypeInfo> enumeratePdbTypes(const QString& pdbPath, QString* errorMsg
info.size = size;
info.childCount = fieldCount;
info.isUnion = isUnion;
info.isEnum = isEnum;
result.append(info);
}
@@ -876,7 +946,12 @@ NodeTree importPdbSelected(const QString& pdbPath,
int total = typeIndices.size();
for (int i = 0; i < total; i++) {
ctx.importUDT(typeIndices[i]);
uint32_t ti = typeIndices[i];
const auto* rec = pdb.typeTable->get(ti);
if (rec && rec->header.kind == TRK::LF_ENUM)
ctx.importEnum(ti);
else
ctx.importUDT(ti);
if (progressCb && !progressCb(i + 1, total)) {
if (errorMsg) *errorMsg = QStringLiteral("Import cancelled");
return ctx.tree; // return partial result

View File

@@ -7,10 +7,11 @@ namespace rcx {
struct PdbTypeInfo {
uint32_t typeIndex; // TPI type index
QString name; // struct/class/union name
QString name; // struct/class/union/enum name
uint64_t size; // sizeof in bytes
int childCount; // direct member count
bool isUnion; // union vs struct/class
bool isEnum = false; // enum type
};
// Phase 1: Enumerate all UDT types in the PDB (fast scan, no recursive import).

View File

@@ -1,5 +1,6 @@
#include "import_source.h"
#include <QHash>
#include <QSet>
#include <QVector>
#include <QRegularExpression>
#include <QDebug>
@@ -285,13 +286,16 @@ struct ParsedField {
int commentOffset = -1; // from // 0xNN (-1 = none)
int bitfieldWidth = -1; // -1 = not a bitfield
QString pointerTarget; // for Type* -> the type name
bool isUnion = false; // union container
QVector<ParsedField> unionMembers; // children of union
};
struct ParsedStruct {
QString name;
QString keyword; // "struct" or "class"
QString keyword; // "struct", "class", or "enum"
QVector<ParsedField> fields;
int declaredSize = -1; // from static_assert
QVector<QPair<QString, int64_t>> enumValues; // for keyword="enum"
};
struct PendingRef {
@@ -378,8 +382,7 @@ struct Parser {
} else if (checkIdent("typedef")) {
parseTypedef();
} else if (checkIdent("enum")) {
skipToSemiOrBrace();
if (check(TokKind::RBrace)) { advance(); match(TokKind::Semi); }
parseEnumDef();
} else if (peek().kind == TokKind::Hash) {
// preprocessor (shouldn't reach here if tokenizer skipped them)
advance();
@@ -464,12 +467,18 @@ struct Parser {
// Might be "struct TypeName fieldName;" - fall through to field parsing
}
// Union: pick first member only
// Union: create container with all members
if (checkIdent("union")) {
parseUnion(ps);
continue;
}
// Enum definition inside struct
if (checkIdent("enum")) {
parseEnumDef();
continue;
}
// Static assert inside struct
if (checkIdent("static_assert")) {
parseStaticAssert();
@@ -489,33 +498,76 @@ struct Parser {
void parseUnion(ParsedStruct& ps) {
advance(); // skip "union"
// Optional union name
// Optional union tag name (before {)
if (check(TokKind::Ident) && peek(1).kind == TokKind::LBrace) {
advance(); // skip union name
advance(); // skip union tag name
}
if (!match(TokKind::LBrace)) { skipToSemiOrBrace(); return; }
// Parse first member of union
bool gotFirst = false;
// Parse ALL members of the union
ParsedField unionField;
unionField.isUnion = true;
while (peek().kind != TokKind::RBrace && peek().kind != TokKind::Eof) {
if (!gotFirst) {
ParsedField field;
if (parseField(field)) {
ps.fields.append(field);
gotFirst = true;
} else {
advance();
// Handle nested unions inside this union
if (checkIdent("union")) {
// Recurse: create a sub-union ParsedStruct temporarily,
// then steal its fields as a nested union member
ParsedStruct tmp;
parseUnion(tmp);
for (auto& f : tmp.fields)
unionField.unionMembers.append(f);
continue;
}
// Handle anonymous struct inside union: struct { ... };
if ((checkIdent("struct") || checkIdent("class")) && peek(1).kind == TokKind::LBrace) {
advance(); // skip "struct"
advance(); // skip "{"
int depth = 1;
while (peek().kind != TokKind::Eof && depth > 0) {
if (peek().kind == TokKind::LBrace) depth++;
else if (peek().kind == TokKind::RBrace) depth--;
if (depth > 0) advance();
}
if (check(TokKind::RBrace)) advance();
if (check(TokKind::Ident)) advance(); // optional field name
match(TokKind::Semi);
continue;
}
// Handle nested named struct definition inside union
if ((checkIdent("struct") || checkIdent("class")) &&
peek(1).kind == TokKind::Ident && peek(2).kind == TokKind::LBrace) {
parseStructOrForward();
continue;
}
ParsedField field;
if (parseField(field)) {
unionField.unionMembers.append(field);
} else {
// Skip remaining union members
skipToSemiOrBrace();
advance();
}
}
match(TokKind::RBrace);
// Optional field name after union close
if (check(TokKind::Ident)) advance();
// Optional field name after union close: union { ... } u3;
if (check(TokKind::Ident)) {
unionField.name = advance().text;
}
match(TokKind::Semi);
// Determine offset from first member with a known offset
for (const auto& m : unionField.unionMembers) {
if (m.commentOffset >= 0) {
unionField.commentOffset = m.commentOffset;
break;
}
}
ps.fields.append(unionField);
}
bool parseField(ParsedField& field) {
@@ -719,6 +771,90 @@ struct Parser {
}
match(TokKind::Semi);
}
void parseEnumDef() {
advance(); // skip "enum"
// Optional "class" or "struct" (enum class)
if (checkIdent("class") || checkIdent("struct"))
advance();
// Optional name
QString name;
if (check(TokKind::Ident) && peek(1).kind != TokKind::Semi) {
// Could be: enum Name { ... }; or enum Name : Type { ... };
// But NOT: enum Name; (forward decl) or enum Name field; (field usage)
if (peek(1).kind == TokKind::LBrace || peek(1).kind == TokKind::Colon) {
name = advance().text;
} else {
// Not an enum definition — revert. This might be a field like "enum Foo bar;"
return;
}
}
// Optional underlying type: enum Name : uint8_t { ... }
if (check(TokKind::Colon)) {
advance();
parseTypeName(); // skip underlying type
}
// Forward declaration: enum Name;
if (check(TokKind::Semi)) {
advance();
return;
}
if (!match(TokKind::LBrace)) { skipToSemiOrBrace(); return; }
ParsedStruct ps;
ps.name = name;
ps.keyword = QStringLiteral("enum");
// Parse enum members: Name [= Value], ...
int64_t nextValue = 0;
while (peek().kind != TokKind::RBrace && peek().kind != TokKind::Eof) {
if (!check(TokKind::Ident)) { advance(); continue; }
QString memberName = advance().text;
int64_t memberValue = nextValue;
if (check(TokKind::Equals)) {
advance();
// Parse value: could be number, negative number, or expression
bool negative = false;
if (peek().kind == TokKind::Other && peek().text == QStringLiteral("-")) {
negative = true;
advance();
}
if (check(TokKind::Number)) {
bool ok;
QString numText = peek().text;
if (numText.startsWith(QStringLiteral("0x"), Qt::CaseInsensitive))
memberValue = numText.mid(2).toLongLong(&ok, 16);
else
memberValue = numText.toLongLong(&ok);
if (negative) memberValue = -memberValue;
advance();
} else {
// Complex expression — skip to comma or brace
while (peek().kind != TokKind::Comma &&
peek().kind != TokKind::RBrace &&
peek().kind != TokKind::Eof)
advance();
}
}
ps.enumValues.append({memberName, memberValue});
nextValue = memberValue + 1;
// Skip comma between members
match(TokKind::Comma);
}
match(TokKind::RBrace);
match(TokKind::Semi);
if (!ps.name.isEmpty())
structs.append(ps);
}
};
// ── Padding field detection ──
@@ -758,6 +894,305 @@ static void emitHexPadding(NodeTree& tree, uint64_t parentId, int offset, int si
}
}
// ── Bitfield grouping: emit a single hex node covering consecutive bitfields ──
static void emitBitfieldGroup(NodeTree& tree, uint64_t parentId, int offset, int totalBits) {
int bytes = (totalBits + 7) / 8;
// Round up to nearest power-of-2 hex node
NodeKind hexKind;
if (bytes <= 1) hexKind = NodeKind::Hex8;
else if (bytes <= 2) hexKind = NodeKind::Hex16;
else if (bytes <= 4) hexKind = NodeKind::Hex32;
else hexKind = NodeKind::Hex64;
Node n;
n.kind = hexKind;
n.parentId = parentId;
n.offset = offset;
tree.addNode(n);
}
// ── NodeTree builder: recursive field emitter ──
struct BuildContext {
NodeTree& tree;
const QHash<QString, TypeInfo>& typeTable;
QHash<QString, uint64_t>& classIds;
QVector<PendingRef>& pendingRefs;
bool useCommentOffsets;
QSet<QString> enumNames; // enum type names (emit as UInt32 + refId)
};
static void buildFields(BuildContext& ctx, uint64_t parentId, int baseOffset,
const QVector<ParsedField>& fields) {
int computedOffset = 0;
for (int fi = 0; fi < fields.size(); fi++) {
const auto& field = fields[fi];
// Bitfield group: consume consecutive bitfields, emit single hex node
if (field.bitfieldWidth >= 0) {
int groupOffset;
if (ctx.useCommentOffsets && field.commentOffset >= 0)
groupOffset = field.commentOffset - baseOffset;
else
groupOffset = computedOffset;
int totalBits = 0;
while (fi < fields.size() && fields[fi].bitfieldWidth >= 0) {
totalBits += fields[fi].bitfieldWidth;
fi++;
}
fi--; // compensate for outer loop increment
if (totalBits > 0)
emitBitfieldGroup(ctx.tree, parentId, groupOffset, totalBits);
int bytes = (totalBits + 7) / 8;
int nodeSize = (bytes <= 1) ? 1 : (bytes <= 2) ? 2 : (bytes <= 4) ? 4 : 8;
computedOffset = groupOffset + nodeSize;
continue;
}
// Union container field
if (field.isUnion) {
int unionOffset;
if (ctx.useCommentOffsets && field.commentOffset >= 0)
unionOffset = field.commentOffset - baseOffset;
else
unionOffset = computedOffset;
Node unionNode;
unionNode.kind = NodeKind::Struct;
unionNode.classKeyword = QStringLiteral("union");
unionNode.name = field.name;
unionNode.parentId = parentId;
unionNode.offset = unionOffset;
unionNode.collapsed = true;
int unionIdx = ctx.tree.addNode(unionNode);
uint64_t unionId = ctx.tree.nodes[unionIdx].id;
// Build each union member independently so each starts at offset 0
int absUnionOffset = baseOffset + unionOffset;
for (const auto& member : field.unionMembers) {
QVector<ParsedField> single;
single.append(member);
buildFields(ctx, unionId, absUnionOffset, single);
}
// Advance computed offset past the union (max member size)
int unionSpan = ctx.tree.structSpan(unionId);
computedOffset = unionOffset + (unionSpan > 0 ? unionSpan : 0);
continue;
}
int fieldOffset;
if (ctx.useCommentOffsets && field.commentOffset >= 0)
fieldOffset = field.commentOffset - baseOffset;
else
fieldOffset = computedOffset;
// Resolve type
auto typeIt = ctx.typeTable.find(field.typeName);
bool knownType = typeIt != ctx.typeTable.end();
// Pointer field
if (field.isPointer) {
Node n;
n.kind = NodeKind::Pointer64;
n.name = field.name;
n.parentId = parentId;
n.offset = fieldOffset;
n.collapsed = true;
int nodeIdx = ctx.tree.addNode(n);
uint64_t nodeId = ctx.tree.nodes[nodeIdx].id;
if (!field.pointerTarget.isEmpty() &&
field.pointerTarget != QStringLiteral("void")) {
ctx.pendingRefs.append({nodeId, field.pointerTarget});
}
computedOffset = fieldOffset + 8;
continue;
}
// Enum-typed field: emit as UInt32 with refId to enum definition
if (!knownType && ctx.enumNames.contains(field.typeName)) {
int elemSize = 4;
NodeKind elemKind = NodeKind::UInt32;
if (!field.arraySizes.isEmpty()) {
int totalElements = 1;
for (int dim : field.arraySizes) totalElements *= (dim > 0 ? dim : 1);
Node n;
n.kind = NodeKind::Array;
n.name = field.name;
n.parentId = parentId;
n.offset = fieldOffset;
n.arrayLen = totalElements;
n.elementKind = elemKind;
ctx.tree.addNode(n);
computedOffset = fieldOffset + totalElements * elemSize;
} else {
Node n;
n.kind = elemKind;
n.name = field.name;
n.parentId = parentId;
n.offset = fieldOffset;
int nodeIdx = ctx.tree.addNode(n);
uint64_t nodeId = ctx.tree.nodes[nodeIdx].id;
ctx.pendingRefs.append({nodeId, field.typeName});
computedOffset = fieldOffset + elemSize;
}
continue;
}
// Determine base type info
NodeKind baseKind = NodeKind::Hex8;
int baseSize = 1;
bool isStructType = false;
if (knownType) {
baseKind = typeIt->kind;
baseSize = typeIt->size;
} else {
isStructType = true;
}
// Padding fields
if (isPaddingName(field.name) && !field.arraySizes.isEmpty()) {
int totalSize = baseSize;
for (int dim : field.arraySizes) totalSize *= (dim > 0 ? dim : 1);
emitHexPadding(ctx.tree, parentId, fieldOffset, totalSize);
computedOffset = fieldOffset + totalSize;
continue;
}
// Array fields
if (!field.arraySizes.isEmpty() && !isStructType) {
int firstDim = field.arraySizes.value(0, 1);
if (firstDim <= 0) firstDim = 1;
if (baseKind == NodeKind::Int8 && field.arraySizes.size() == 1 &&
field.typeName == QStringLiteral("char")) {
Node n;
n.kind = NodeKind::UTF8;
n.name = field.name;
n.parentId = parentId;
n.offset = fieldOffset;
n.strLen = firstDim;
ctx.tree.addNode(n);
computedOffset = fieldOffset + firstDim;
continue;
}
if (baseKind == NodeKind::UInt16 && field.arraySizes.size() == 1 &&
(field.typeName == QStringLiteral("wchar_t") || field.typeName == QStringLiteral("WCHAR"))) {
Node n;
n.kind = NodeKind::UTF16;
n.name = field.name;
n.parentId = parentId;
n.offset = fieldOffset;
n.strLen = firstDim;
ctx.tree.addNode(n);
computedOffset = fieldOffset + firstDim * 2;
continue;
}
if (baseKind == NodeKind::Float && field.arraySizes.size() == 1) {
if (firstDim == 2) {
Node n; n.kind = NodeKind::Vec2; n.name = field.name;
n.parentId = parentId; n.offset = fieldOffset;
ctx.tree.addNode(n); computedOffset = fieldOffset + 8; continue;
}
if (firstDim == 3) {
Node n; n.kind = NodeKind::Vec3; n.name = field.name;
n.parentId = parentId; n.offset = fieldOffset;
ctx.tree.addNode(n); computedOffset = fieldOffset + 12; continue;
}
if (firstDim == 4) {
Node n; n.kind = NodeKind::Vec4; n.name = field.name;
n.parentId = parentId; n.offset = fieldOffset;
ctx.tree.addNode(n); computedOffset = fieldOffset + 16; continue;
}
}
if (baseKind == NodeKind::Float && field.arraySizes.size() == 2 &&
field.arraySizes[0] == 4 && field.arraySizes[1] == 4) {
Node n; n.kind = NodeKind::Mat4x4; n.name = field.name;
n.parentId = parentId; n.offset = fieldOffset;
ctx.tree.addNode(n); computedOffset = fieldOffset + 64; continue;
}
int totalElements = 1;
for (int dim : field.arraySizes) totalElements *= (dim > 0 ? dim : 1);
Node n;
n.kind = NodeKind::Array;
n.name = field.name;
n.parentId = parentId;
n.offset = fieldOffset;
n.arrayLen = totalElements;
n.elementKind = baseKind;
ctx.tree.addNode(n);
computedOffset = fieldOffset + totalElements * baseSize;
continue;
}
// Struct-type field
if (isStructType) {
if (!field.arraySizes.isEmpty()) {
int totalElements = 1;
for (int dim : field.arraySizes) totalElements *= (dim > 0 ? dim : 1);
Node n;
n.kind = NodeKind::Array;
n.name = field.name;
n.parentId = parentId;
n.offset = fieldOffset;
n.arrayLen = totalElements;
n.elementKind = NodeKind::Struct;
n.structTypeName = field.typeName;
n.collapsed = true;
int nodeIdx = ctx.tree.addNode(n);
uint64_t nodeId = ctx.tree.nodes[nodeIdx].id;
ctx.pendingRefs.append({nodeId, field.typeName});
continue;
}
Node n;
n.kind = NodeKind::Struct;
n.name = field.name;
n.parentId = parentId;
n.offset = fieldOffset;
n.structTypeName = field.typeName;
n.collapsed = true;
int nodeIdx = ctx.tree.addNode(n);
uint64_t nodeId = ctx.tree.nodes[nodeIdx].id;
ctx.pendingRefs.append({nodeId, field.typeName});
continue;
}
// Simple primitive field
Node n;
n.kind = baseKind;
n.name = field.name;
n.parentId = parentId;
n.offset = fieldOffset;
ctx.tree.addNode(n);
computedOffset = fieldOffset + baseSize;
}
}
// ── Check if any field (or union member) has a comment offset ──
static bool hasAnyCommentOffset(const QVector<ParsedField>& fields) {
for (const auto& f : fields) {
if (f.commentOffset >= 0) return true;
if (f.isUnion && hasAnyCommentOffset(f.unionMembers)) return true;
}
return false;
}
// ── NodeTree builder ──
NodeTree importFromSource(const QString& sourceCode, QString* errorMsg) {
@@ -775,7 +1210,7 @@ NodeTree importFromSource(const QString& sourceCode, QString* errorMsg) {
parser.parse();
if (parser.structs.isEmpty()) {
if (errorMsg) *errorMsg = QStringLiteral("No struct definitions found");
if (errorMsg) *errorMsg = QStringLiteral("No struct or enum definitions found");
return {};
}
@@ -798,13 +1233,19 @@ NodeTree importFromSource(const QString& sourceCode, QString* errorMsg) {
// Determine offset mode: if ANY field in ANY struct has a comment offset, use comment mode
bool useCommentOffsets = false;
for (const auto& ps : parser.structs) {
for (const auto& f : ps.fields) {
if (f.commentOffset >= 0) { useCommentOffsets = true; break; }
}
if (useCommentOffsets) break;
if (hasAnyCommentOffset(ps.fields)) { useCommentOffsets = true; break; }
}
// Build nodes for each struct
// Collect enum type names for field-type detection
QSet<QString> enumNames;
for (const auto& ps : parser.structs) {
if (ps.keyword == QStringLiteral("enum") && !ps.name.isEmpty())
enumNames.insert(ps.name);
}
BuildContext ctx{tree, typeTable, classIds, pendingRefs, useCommentOffsets, enumNames};
// Build nodes for each struct/enum
for (const auto& ps : parser.structs) {
Node structNode;
structNode.kind = NodeKind::Struct;
@@ -815,222 +1256,21 @@ NodeTree importFromSource(const QString& sourceCode, QString* errorMsg) {
structNode.offset = 0;
structNode.collapsed = true;
// Enum: store members directly on the node, no child fields
if (ps.keyword == QStringLiteral("enum")) {
structNode.enumMembers = ps.enumValues;
int idx = tree.addNode(structNode);
uint64_t nodeId = tree.nodes[idx].id;
if (!ps.name.isEmpty())
classIds[ps.name] = nodeId;
continue;
}
int structIdx = tree.addNode(structNode);
uint64_t structId = tree.nodes[structIdx].id;
classIds[ps.name] = structId;
int computedOffset = 0;
for (const auto& field : ps.fields) {
// Skip bitfields
if (field.bitfieldWidth >= 0) continue;
int fieldOffset;
if (useCommentOffsets && field.commentOffset >= 0)
fieldOffset = field.commentOffset;
else
fieldOffset = computedOffset;
// Resolve type
auto typeIt = typeTable.find(field.typeName);
bool knownType = typeIt != typeTable.end();
// Pointer field
if (field.isPointer) {
Node n;
n.kind = NodeKind::Pointer64;
n.name = field.name;
n.parentId = structId;
n.offset = fieldOffset;
n.collapsed = true;
int nodeIdx = tree.addNode(n);
uint64_t nodeId = tree.nodes[nodeIdx].id;
// If target is not void and not a primitive, defer resolution
if (!field.pointerTarget.isEmpty() &&
field.pointerTarget != QStringLiteral("void")) {
pendingRefs.append({nodeId, field.pointerTarget});
}
computedOffset = fieldOffset + 8; // pointer size
continue;
}
// Determine base type info
NodeKind baseKind = NodeKind::Hex8;
int baseSize = 1;
bool isStructType = false;
if (knownType) {
baseKind = typeIt->kind;
baseSize = typeIt->size;
} else {
// Unknown type = assume struct reference
isStructType = true;
}
// Padding fields: name-based detection
if (isPaddingName(field.name) && !field.arraySizes.isEmpty()) {
int totalSize = baseSize;
for (int dim : field.arraySizes) totalSize *= (dim > 0 ? dim : 1);
emitHexPadding(tree, structId, fieldOffset, totalSize);
computedOffset = fieldOffset + totalSize;
continue;
}
// Array fields
if (!field.arraySizes.isEmpty() && !isStructType) {
int firstDim = field.arraySizes.value(0, 1);
if (firstDim <= 0) firstDim = 1;
// Special: char[N] -> UTF8
if (baseKind == NodeKind::Int8 && field.arraySizes.size() == 1 &&
field.typeName == QStringLiteral("char")) {
Node n;
n.kind = NodeKind::UTF8;
n.name = field.name;
n.parentId = structId;
n.offset = fieldOffset;
n.strLen = firstDim;
tree.addNode(n);
computedOffset = fieldOffset + firstDim;
continue;
}
// Special: wchar_t[N] -> UTF16
if (baseKind == NodeKind::UInt16 && field.arraySizes.size() == 1 &&
(field.typeName == QStringLiteral("wchar_t") || field.typeName == QStringLiteral("WCHAR"))) {
Node n;
n.kind = NodeKind::UTF16;
n.name = field.name;
n.parentId = structId;
n.offset = fieldOffset;
n.strLen = firstDim;
tree.addNode(n);
computedOffset = fieldOffset + firstDim * 2;
continue;
}
// Special: float[2] -> Vec2, float[3] -> Vec3, float[4] -> Vec4
if (baseKind == NodeKind::Float && field.arraySizes.size() == 1) {
if (firstDim == 2) {
Node n;
n.kind = NodeKind::Vec2;
n.name = field.name;
n.parentId = structId;
n.offset = fieldOffset;
tree.addNode(n);
computedOffset = fieldOffset + 8;
continue;
}
if (firstDim == 3) {
Node n;
n.kind = NodeKind::Vec3;
n.name = field.name;
n.parentId = structId;
n.offset = fieldOffset;
tree.addNode(n);
computedOffset = fieldOffset + 12;
continue;
}
if (firstDim == 4) {
Node n;
n.kind = NodeKind::Vec4;
n.name = field.name;
n.parentId = structId;
n.offset = fieldOffset;
tree.addNode(n);
computedOffset = fieldOffset + 16;
continue;
}
}
// Special: float[4][4] -> Mat4x4
if (baseKind == NodeKind::Float && field.arraySizes.size() == 2 &&
field.arraySizes[0] == 4 && field.arraySizes[1] == 4) {
Node n;
n.kind = NodeKind::Mat4x4;
n.name = field.name;
n.parentId = structId;
n.offset = fieldOffset;
tree.addNode(n);
computedOffset = fieldOffset + 64;
continue;
}
// Generic array
int totalElements = 1;
for (int dim : field.arraySizes) totalElements *= (dim > 0 ? dim : 1);
Node n;
n.kind = NodeKind::Array;
n.name = field.name;
n.parentId = structId;
n.offset = fieldOffset;
n.arrayLen = totalElements;
n.elementKind = baseKind;
tree.addNode(n);
computedOffset = fieldOffset + totalElements * baseSize;
continue;
}
// Struct-type field (embedded struct or array of structs)
if (isStructType) {
if (!field.arraySizes.isEmpty()) {
// Array of structs
int totalElements = 1;
for (int dim : field.arraySizes) totalElements *= (dim > 0 ? dim : 1);
Node n;
n.kind = NodeKind::Array;
n.name = field.name;
n.parentId = structId;
n.offset = fieldOffset;
n.arrayLen = totalElements;
n.elementKind = NodeKind::Struct;
n.structTypeName = field.typeName;
n.collapsed = true;
int nodeIdx = tree.addNode(n);
uint64_t nodeId = tree.nodes[nodeIdx].id;
pendingRefs.append({nodeId, field.typeName});
// For computed offsets: we don't know struct size yet, use 0
// The offset will be approximate for unknown struct sizes
if (!useCommentOffsets) {
// Try to estimate from same-file structs
// Can't know size yet since we may not have parsed it
// Just advance by 0 (will be corrected by comment offsets if present)
}
continue;
}
// Embedded struct
Node n;
n.kind = NodeKind::Struct;
n.name = field.name;
n.parentId = structId;
n.offset = fieldOffset;
n.structTypeName = field.typeName;
n.collapsed = true;
int nodeIdx = tree.addNode(n);
uint64_t nodeId = tree.nodes[nodeIdx].id;
pendingRefs.append({nodeId, field.typeName});
// Don't advance computed offset for unknown struct size
continue;
}
// Simple primitive field
Node n;
n.kind = baseKind;
n.name = field.name;
n.parentId = structId;
n.offset = fieldOffset;
tree.addNode(n);
computedOffset = fieldOffset + baseSize;
}
buildFields(ctx, structId, 0, ps.fields);
// Apply static_assert size: add tail padding if needed
auto sizeIt = parser.sizeAsserts.find(ps.name);