Files
archived-Reclass/src/imports/import_source.cpp
IChooseYou ed8a44917b feat: 32-bit process support, scanner rescan filtering, suppress flash on navigate
- Add pointerSize() to Provider base; WoW64/ELF detection in ProcessMemory,
  WinDbg, and RemoteProcessMemory plugins
- Wire pointer size through NodeTree, source/XML imports, C++ generator,
  controller, compose, address parser, and RPC protocol header
- Add is32Bit to PluginProcessInfo and ProcessInfo; show (32-bit) in picker
- Scanner rescan now filters results against the current input value
- Go-to-address from scanner resets change tracking to prevent false flashing
2026-03-01 07:42:40 -07:00

1334 lines
47 KiB
C++

#include "import_source.h"
#include <QHash>
#include <QSet>
#include <QVector>
#include <QRegularExpression>
#include <QDebug>
namespace rcx {
// ── Built-in type alias table ──
struct TypeInfo {
NodeKind kind;
int size; // bytes (0 = dynamic/pointer)
};
static QHash<QString, TypeInfo> buildTypeTable(int ptrSize = 8) {
QHash<QString, TypeInfo> t;
// Pointer/size_t kinds depend on target architecture
NodeKind ptrKind = (ptrSize >= 8) ? NodeKind::Pointer64 : NodeKind::Pointer32;
NodeKind uintpKind = (ptrSize >= 8) ? NodeKind::UInt64 : NodeKind::UInt32;
NodeKind intpKind = (ptrSize >= 8) ? NodeKind::Int64 : NodeKind::Int32;
// stdint.h
t[QStringLiteral("uint8_t")] = {NodeKind::UInt8, 1};
t[QStringLiteral("int8_t")] = {NodeKind::Int8, 1};
t[QStringLiteral("uint16_t")] = {NodeKind::UInt16, 2};
t[QStringLiteral("int16_t")] = {NodeKind::Int16, 2};
t[QStringLiteral("uint32_t")] = {NodeKind::UInt32, 4};
t[QStringLiteral("int32_t")] = {NodeKind::Int32, 4};
t[QStringLiteral("uint64_t")] = {NodeKind::UInt64, 8};
t[QStringLiteral("int64_t")] = {NodeKind::Int64, 8};
// Standard C
t[QStringLiteral("char")] = {NodeKind::Int8, 1};
t[QStringLiteral("short")] = {NodeKind::Int16, 2};
t[QStringLiteral("int")] = {NodeKind::Int32, 4};
t[QStringLiteral("long")] = {NodeKind::Int32, 4};
t[QStringLiteral("float")] = {NodeKind::Float, 4};
t[QStringLiteral("double")] = {NodeKind::Double, 8};
t[QStringLiteral("bool")] = {NodeKind::Bool, 1};
t[QStringLiteral("_Bool")] = {NodeKind::Bool, 1};
t[QStringLiteral("void")] = {NodeKind::Hex8, 1};
t[QStringLiteral("wchar_t")] = {NodeKind::UInt16, 2};
// Multi-word C types (pre-merged by parser)
t[QStringLiteral("unsigned char")] = {NodeKind::UInt8, 1};
t[QStringLiteral("signed char")] = {NodeKind::Int8, 1};
t[QStringLiteral("unsigned short")] = {NodeKind::UInt16, 2};
t[QStringLiteral("signed short")] = {NodeKind::Int16, 2};
t[QStringLiteral("unsigned int")] = {NodeKind::UInt32, 4};
t[QStringLiteral("signed int")] = {NodeKind::Int32, 4};
t[QStringLiteral("unsigned")] = {NodeKind::UInt32, 4};
t[QStringLiteral("long long")] = {NodeKind::Int64, 8};
t[QStringLiteral("unsigned long")] = {NodeKind::UInt32, 4};
t[QStringLiteral("signed long")] = {NodeKind::Int32, 4};
t[QStringLiteral("unsigned long long")] = {NodeKind::UInt64, 8};
t[QStringLiteral("signed long long")] = {NodeKind::Int64, 8};
t[QStringLiteral("long int")] = {NodeKind::Int32, 4};
t[QStringLiteral("long long int")] = {NodeKind::Int64, 8};
t[QStringLiteral("unsigned long int")] = {NodeKind::UInt32, 4};
t[QStringLiteral("unsigned long long int")] = {NodeKind::UInt64, 8};
t[QStringLiteral("short int")] = {NodeKind::Int16, 2};
t[QStringLiteral("unsigned short int")] = {NodeKind::UInt16, 2};
// Windows types
t[QStringLiteral("BYTE")] = {NodeKind::UInt8, 1};
t[QStringLiteral("UCHAR")] = {NodeKind::UInt8, 1};
t[QStringLiteral("BOOLEAN")] = {NodeKind::UInt8, 1};
t[QStringLiteral("CHAR")] = {NodeKind::Int8, 1};
t[QStringLiteral("WORD")] = {NodeKind::UInt16, 2};
t[QStringLiteral("USHORT")] = {NodeKind::UInt16, 2};
t[QStringLiteral("SHORT")] = {NodeKind::Int16, 2};
t[QStringLiteral("WCHAR")] = {NodeKind::UInt16, 2};
t[QStringLiteral("DWORD")] = {NodeKind::UInt32, 4};
t[QStringLiteral("ULONG")] = {NodeKind::UInt32, 4};
t[QStringLiteral("UINT")] = {NodeKind::UInt32, 4};
t[QStringLiteral("LONG")] = {NodeKind::Int32, 4};
t[QStringLiteral("LONG32")] = {NodeKind::Int32, 4};
t[QStringLiteral("INT")] = {NodeKind::Int32, 4};
t[QStringLiteral("BOOL")] = {NodeKind::Int32, 4};
t[QStringLiteral("FLOAT")] = {NodeKind::Float, 4};
t[QStringLiteral("QWORD")] = {NodeKind::UInt64, 8};
t[QStringLiteral("ULONGLONG")] = {NodeKind::UInt64, 8};
t[QStringLiteral("DWORD64")] = {NodeKind::UInt64, 8};
t[QStringLiteral("ULONG64")] = {NodeKind::UInt64, 8};
t[QStringLiteral("UINT64")] = {NodeKind::UInt64, 8};
t[QStringLiteral("LONGLONG")] = {NodeKind::Int64, 8};
t[QStringLiteral("LONG64")] = {NodeKind::Int64, 8};
t[QStringLiteral("INT64")] = {NodeKind::Int64, 8};
// Platform pointer-size types (depend on target architecture)
t[QStringLiteral("PVOID")] = {ptrKind, ptrSize};
t[QStringLiteral("LPVOID")] = {ptrKind, ptrSize};
t[QStringLiteral("HANDLE")] = {ptrKind, ptrSize};
t[QStringLiteral("HMODULE")] = {ptrKind, ptrSize};
t[QStringLiteral("HWND")] = {ptrKind, ptrSize};
t[QStringLiteral("HINSTANCE")] = {ptrKind, ptrSize};
t[QStringLiteral("SIZE_T")] = {uintpKind, ptrSize};
t[QStringLiteral("ULONG_PTR")] = {uintpKind, ptrSize};
t[QStringLiteral("UINT_PTR")] = {uintpKind, ptrSize};
t[QStringLiteral("DWORD_PTR")] = {uintpKind, ptrSize};
t[QStringLiteral("LONG_PTR")] = {intpKind, ptrSize};
t[QStringLiteral("INT_PTR")] = {intpKind, ptrSize};
t[QStringLiteral("SSIZE_T")] = {intpKind, ptrSize};
t[QStringLiteral("uintptr_t")] = {uintpKind, ptrSize};
t[QStringLiteral("intptr_t")] = {intpKind, ptrSize};
t[QStringLiteral("size_t")] = {uintpKind, ptrSize};
t[QStringLiteral("ptrdiff_t")] = {intpKind, ptrSize};
t[QStringLiteral("ssize_t")] = {intpKind, ptrSize};
// Pointer type aliases
t[QStringLiteral("PCHAR")] = {ptrKind, ptrSize};
t[QStringLiteral("LPSTR")] = {ptrKind, ptrSize};
t[QStringLiteral("LPCSTR")] = {ptrKind, ptrSize};
t[QStringLiteral("PCSTR")] = {ptrKind, ptrSize};
t[QStringLiteral("PWSTR")] = {ptrKind, ptrSize};
t[QStringLiteral("LPWSTR")] = {ptrKind, ptrSize};
t[QStringLiteral("LPCWSTR")]= {ptrKind, ptrSize};
t[QStringLiteral("PCWSTR")] = {ptrKind, ptrSize};
return t;
}
// ── Tokenizer ──
enum class TokKind {
Ident, Number, Star, Semi, LBrace, RBrace,
LBracket, RBracket, LParen, RParen, Comma, Colon,
Equals, Hash, Eof, Other
};
struct Token {
TokKind kind = TokKind::Eof;
QString text;
int line = 0;
};
// Parsed offset comment associated with a line
struct LineOffset {
int line;
int offset; // hex offset value
};
struct Tokenizer {
const QString& src;
int pos = 0;
int line = 1;
QVector<Token> tokens;
QVector<LineOffset> offsets; // captured // 0xNN comments
explicit Tokenizer(const QString& s) : src(s) {}
void tokenize() {
while (pos < src.size()) {
skipWhitespace();
if (pos >= src.size()) break;
QChar c = src[pos];
// Line comments
if (c == '/' && pos + 1 < src.size() && src[pos + 1] == '/') {
parseLineComment();
continue;
}
// Block comments
if (c == '/' && pos + 1 < src.size() && src[pos + 1] == '*') {
parseBlockComment();
continue;
}
// Preprocessor lines - skip entirely
if (c == '#') {
skipToEndOfLine();
continue;
}
// Identifiers / keywords
if (c.isLetter() || c == '_') {
parseIdent();
continue;
}
// Numbers
if (c.isDigit()) {
parseNumber();
continue;
}
// Single-character tokens
TokKind tk = TokKind::Other;
switch (c.toLatin1()) {
case '*': tk = TokKind::Star; break;
case ';': tk = TokKind::Semi; break;
case '{': tk = TokKind::LBrace; break;
case '}': tk = TokKind::RBrace; break;
case '[': tk = TokKind::LBracket; break;
case ']': tk = TokKind::RBracket; break;
case '(': tk = TokKind::LParen; break;
case ')': tk = TokKind::RParen; break;
case ',': tk = TokKind::Comma; break;
case ':': tk = TokKind::Colon; break;
case '=': tk = TokKind::Equals; break;
default: tk = TokKind::Other; break;
}
tokens.append({tk, QString(c), line});
pos++;
}
tokens.append({TokKind::Eof, {}, line});
}
private:
void skipWhitespace() {
while (pos < src.size()) {
if (src[pos] == '\n') { line++; pos++; }
else if (src[pos].isSpace()) { pos++; }
else break;
}
}
void skipToEndOfLine() {
while (pos < src.size() && src[pos] != '\n') pos++;
}
void parseLineComment() {
int commentLine = line;
pos += 2; // skip //
int start = pos;
while (pos < src.size() && src[pos] != '\n') pos++;
QString comment = src.mid(start, pos - start).trimmed();
// Capture offset comments like "0x10" or "// 0x10"
static QRegularExpression offsetRe(QStringLiteral("^(?:->\\s*\\S+\\s+)?0x([0-9A-Fa-f]+)$"));
// Also handle "-> TypeName 0x1A" style
static QRegularExpression offsetRe2(QStringLiteral("0x([0-9A-Fa-f]+)"));
auto m = offsetRe.match(comment);
if (!m.hasMatch()) {
// Try simpler: just look for "0xHEX" at end of comment
// Handles "// 0x10", "// -> Material* 0x10", etc.
static QRegularExpression endHexRe(QStringLiteral("\\b0x([0-9A-Fa-f]+)\\s*$"));
m = endHexRe.match(comment);
}
if (m.hasMatch()) {
bool ok;
int val = m.captured(1).toInt(&ok, 16);
if (ok) {
offsets.append({commentLine, val});
}
}
}
void parseBlockComment() {
pos += 2; // skip /*
while (pos + 1 < src.size()) {
if (src[pos] == '\n') line++;
if (src[pos] == '*' && src[pos + 1] == '/') { pos += 2; return; }
pos++;
}
pos = src.size(); // unterminated
}
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});
}
void parseNumber() {
int start = pos;
if (src[pos] == '0' && pos + 1 < src.size() &&
(src[pos + 1] == 'x' || src[pos + 1] == 'X')) {
pos += 2;
while (pos < src.size() && (src[pos].isDigit() ||
(src[pos] >= 'a' && src[pos] <= 'f') ||
(src[pos] >= 'A' && src[pos] <= 'F'))) pos++;
} else {
while (pos < src.size() && src[pos].isDigit()) pos++;
}
// 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});
}
};
// ── Parser ──
struct ParsedField {
QString typeName; // base type name (resolved through multi-word merge)
QString name;
bool isPointer = false;
int pointerDepth = 0; // number of * levels
QVector<int> arraySizes; // [4], [4][4] etc.
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", "class", or "enum"
QVector<ParsedField> fields;
int declaredSize = -1; // from static_assert
QVector<QPair<QString, int64_t>> enumValues; // for keyword="enum"
};
struct PendingRef {
uint64_t nodeId;
QString className;
};
// Multi-word type prefix keywords
static bool isTypeModifier(const QString& s) {
return s == QStringLiteral("unsigned") ||
s == QStringLiteral("signed") ||
s == QStringLiteral("long") ||
s == QStringLiteral("short");
}
static bool isQualifier(const QString& s) {
return s == QStringLiteral("const") ||
s == QStringLiteral("volatile") ||
s == QStringLiteral("mutable") ||
s == QStringLiteral("struct") ||
s == QStringLiteral("class") ||
s == QStringLiteral("enum");
}
struct Parser {
const QVector<Token>& tokens;
const QVector<LineOffset>& lineOffsets;
int cur = 0;
QVector<ParsedStruct> structs;
QSet<QString> forwardDecls;
QHash<QString, QString> typedefs; // alias -> real type
QHash<QString, int> sizeAsserts; // struct name -> declared size
explicit Parser(const QVector<Token>& t, const QVector<LineOffset>& lo)
: tokens(t), lineOffsets(lo) {}
const Token& peek(int ahead = 0) const {
int i = cur + ahead;
return (i < tokens.size()) ? tokens[i] : tokens.back();
}
Token advance() {
if (cur < tokens.size() - 1) return tokens[cur++];
return tokens.back();
}
bool check(TokKind k) const { return peek().kind == k; }
bool checkIdent(const QString& s) const { return peek().kind == TokKind::Ident && peek().text == s; }
bool match(TokKind k) {
if (check(k)) { advance(); return true; }
return false;
}
bool matchIdent(const QString& s) {
if (checkIdent(s)) { advance(); return true; }
return false;
}
void skipToSemiOrBrace() {
int depth = 0;
while (peek().kind != TokKind::Eof) {
if (peek().kind == TokKind::LBrace) depth++;
else if (peek().kind == TokKind::RBrace) {
if (depth == 0) break;
depth--;
}
else if (peek().kind == TokKind::Semi && depth == 0) {
advance(); return;
}
advance();
}
}
// ── Top-level parse ──
void parse() {
while (peek().kind != TokKind::Eof) {
if (checkIdent("struct") || checkIdent("class")) {
parseStructOrForward();
} else if (checkIdent("static_assert")) {
parseStaticAssert();
} else if (checkIdent("typedef")) {
parseTypedef();
} else if (checkIdent("enum")) {
parseEnumDef();
} else if (peek().kind == TokKind::Hash) {
// preprocessor (shouldn't reach here if tokenizer skipped them)
advance();
while (peek().kind != TokKind::Eof && peek().kind != TokKind::Semi) advance();
} else {
advance(); // skip unknown
}
}
}
void parseStructOrForward() {
QString keyword = advance().text; // "struct" or "class"
// Anonymous struct: struct { ... }
if (check(TokKind::LBrace)) {
// Skip anonymous struct at top level
skipToSemiOrBrace();
if (check(TokKind::RBrace)) { advance(); match(TokKind::Semi); }
return;
}
if (!check(TokKind::Ident)) { skipToSemiOrBrace(); return; }
QString name = advance().text;
// Check for inheritance: struct Foo : public Bar {
// Just skip the inheritance clause
if (check(TokKind::Colon)) {
advance(); // ':'
while (peek().kind != TokKind::LBrace && peek().kind != TokKind::Semi &&
peek().kind != TokKind::Eof) {
advance();
}
}
// Forward declaration: struct Foo;
if (check(TokKind::Semi)) {
advance();
forwardDecls.insert(name);
return;
}
if (!match(TokKind::LBrace)) { skipToSemiOrBrace(); return; }
ParsedStruct ps;
ps.name = name;
ps.keyword = keyword;
parseStructBody(ps);
if (!match(TokKind::RBrace)) { skipToSemiOrBrace(); return; }
match(TokKind::Semi);
structs.append(ps);
}
void parseStructBody(ParsedStruct& ps) {
while (peek().kind != TokKind::RBrace && peek().kind != TokKind::Eof) {
// Nested struct definition
if (checkIdent("struct") || checkIdent("class")) {
if (peek(1).kind == TokKind::Ident && peek(2).kind == TokKind::LBrace) {
// Nested named struct: parse as a top-level struct, then treat as embedded field
parseStructOrForward();
continue;
}
if (peek(1).kind == TokKind::LBrace) {
// Anonymous nested struct { ... } fieldName;
advance(); // skip "struct"
advance(); // skip "{"
// Skip body
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();
// field name
if (check(TokKind::Ident)) advance();
match(TokKind::Semi);
continue;
}
// Might be "struct TypeName fieldName;" - fall through to field parsing
}
// 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();
continue;
}
// Try to parse as a field
ParsedField field;
if (parseField(field)) {
ps.fields.append(field);
} else {
advance(); // skip unrecognized token
}
}
}
void parseUnion(ParsedStruct& ps) {
advance(); // skip "union"
// Optional union tag name (before {)
if (check(TokKind::Ident) && peek(1).kind == TokKind::LBrace) {
advance(); // skip union tag name
}
if (!match(TokKind::LBrace)) { skipToSemiOrBrace(); return; }
// Parse ALL members of the union
ParsedField unionField;
unionField.isUnion = true;
while (peek().kind != TokKind::RBrace && peek().kind != TokKind::Eof) {
// 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 {
advance();
}
}
match(TokKind::RBrace);
// 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) {
int startPos = cur;
// Skip qualifiers
while (isQualifier(peek().text)) advance();
// Parse type
QString typeName = parseTypeName();
if (typeName.isEmpty()) { cur = startPos; return false; }
// Resolve typedef
while (typedefs.contains(typeName))
typeName = typedefs[typeName];
// Pointer stars
bool isPointer = false;
int ptrDepth = 0;
while (match(TokKind::Star)) {
isPointer = true;
ptrDepth++;
}
// Skip const after pointer
while (checkIdent("const") || checkIdent("volatile")) advance();
// More pointer stars (const Type * const * name)
while (match(TokKind::Star)) {
isPointer = true;
ptrDepth++;
}
// Field name
if (!check(TokKind::Ident)) { cur = startPos; return false; }
field.name = advance().text;
// Array sizes: [N], [N][M], etc.
while (check(TokKind::LBracket)) {
advance(); // [
if (check(TokKind::Number)) {
bool ok;
QString numText = peek().text;
int val;
if (numText.startsWith(QStringLiteral("0x"), Qt::CaseInsensitive))
val = numText.mid(2).toInt(&ok, 16);
else
val = numText.toInt(&ok);
if (ok) field.arraySizes.append(val);
advance();
} else if (check(TokKind::RBracket)) {
field.arraySizes.append(0); // unsized array
}
match(TokKind::RBracket);
}
// Bitfield: Type name : width
if (check(TokKind::Colon)) {
advance();
if (check(TokKind::Number)) {
bool ok;
field.bitfieldWidth = peek().text.toInt(&ok);
advance();
}
}
// Expect semicolon
if (!match(TokKind::Semi)) { cur = startPos; return false; }
// Check if next token line has an offset comment
// We associate offset comments with the field's line
int fieldLine = tokens[startPos].line;
for (const auto& lo : lineOffsets) {
if (lo.line == fieldLine) {
field.commentOffset = lo.offset;
break;
}
}
field.typeName = typeName;
field.isPointer = isPointer;
field.pointerDepth = ptrDepth;
if (isPointer) field.pointerTarget = typeName;
return true;
}
QString parseTypeName() {
if (peek().kind != TokKind::Ident) return {};
QString first = peek().text;
// Handle "struct/class TypeName" as a type reference
if (first == QStringLiteral("struct") || first == QStringLiteral("class") ||
first == QStringLiteral("enum")) {
advance(); // skip struct/class/enum
if (check(TokKind::Ident))
return advance().text;
return {};
}
// Multi-word type building: unsigned, signed, long, short
if (isTypeModifier(first)) {
advance();
QStringList parts;
parts << first;
// Collect further modifiers and the base type
while (check(TokKind::Ident) && (isTypeModifier(peek().text) || peek().text == QStringLiteral("int") ||
peek().text == QStringLiteral("char") || peek().text == QStringLiteral("long"))) {
parts << advance().text;
}
return parts.join(' ');
}
// Simple identifier type
advance();
return first;
}
void parseStaticAssert() {
advance(); // "static_assert"
if (!match(TokKind::LParen)) { skipToSemiOrBrace(); return; }
// Parse: sizeof(X) == 0xNN
// Skip to find sizeof
int depth = 1;
QString structName;
int sizeVal = -1;
// Simple state machine to extract sizeof(StructName) and size value
while (depth > 0 && peek().kind != TokKind::Eof) {
if (checkIdent("sizeof")) {
advance();
if (match(TokKind::LParen)) {
if (check(TokKind::Ident))
structName = advance().text;
match(TokKind::RParen);
}
} else if (peek().kind == TokKind::Number && sizeVal < 0) {
bool ok;
QString numText = peek().text;
if (numText.startsWith(QStringLiteral("0x"), Qt::CaseInsensitive))
sizeVal = numText.mid(2).toInt(&ok, 16);
else
sizeVal = numText.toInt(&ok);
if (!ok) sizeVal = -1;
advance();
} else if (peek().kind == TokKind::LParen) {
depth++;
advance();
} else if (peek().kind == TokKind::RParen) {
depth--;
if (depth > 0) advance();
} else {
advance();
}
}
if (depth == 0) advance(); // consume closing ')'
match(TokKind::Semi);
if (!structName.isEmpty() && sizeVal > 0) {
sizeAsserts[structName] = sizeVal;
}
}
void parseTypedef() {
advance(); // "typedef"
// typedef struct { ... } Name;
if (checkIdent("struct") || checkIdent("class")) {
QString keyword = peek().text;
if (peek(1).kind == TokKind::LBrace ||
(peek(1).kind == TokKind::Ident && peek(2).kind == TokKind::LBrace)) {
// Full struct typedef - parse as struct, then register alias
parseStructOrForward();
return;
}
// typedef struct ExistingName AliasName;
advance(); // skip struct/class
if (check(TokKind::Ident)) {
QString existingName = advance().text;
// Pointer stars
while (match(TokKind::Star)) {}
if (check(TokKind::Ident)) {
QString aliasName = advance().text;
typedefs[aliasName] = existingName;
}
}
match(TokKind::Semi);
return;
}
// typedef BaseType AliasName;
QString baseType = parseTypeName();
if (baseType.isEmpty()) { skipToSemiOrBrace(); return; }
while (match(TokKind::Star)) {} // pointer typedefs
if (check(TokKind::Ident)) {
QString alias = advance().text;
typedefs[alias] = baseType;
}
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 ──
static bool isPaddingName(const QString& name) {
return name.startsWith(QStringLiteral("_pad"), Qt::CaseInsensitive) ||
name.startsWith(QStringLiteral("pad_"), Qt::CaseInsensitive) ||
name.startsWith(QStringLiteral("__pad"), Qt::CaseInsensitive) ||
name.startsWith(QStringLiteral("padding"), Qt::CaseInsensitive) ||
name.startsWith(QStringLiteral("_padding"), Qt::CaseInsensitive) ||
name.startsWith(QStringLiteral("__padding"), Qt::CaseInsensitive) ||
name.startsWith(QStringLiteral("_reserved"), Qt::CaseInsensitive) ||
name.startsWith(QStringLiteral("reserved"), Qt::CaseInsensitive);
}
// Expand padding into best-fit hex nodes (same approach as import_reclass_xml.cpp)
static void emitHexPadding(NodeTree& tree, uint64_t parentId, int offset, int size) {
if (size <= 0) return;
NodeKind hexKind;
int hexSize;
if (size >= 8 && size % 8 == 0) {
hexKind = NodeKind::Hex64; hexSize = 8;
} else if (size >= 4 && size % 4 == 0) {
hexKind = NodeKind::Hex32; hexSize = 4;
} else if (size >= 2 && size % 2 == 0) {
hexKind = NodeKind::Hex16; hexSize = 2;
} else {
hexKind = NodeKind::Hex8; hexSize = 1;
}
int count = size / hexSize;
for (int i = 0; i < count; i++) {
Node n;
n.kind = hexKind;
n.parentId = parentId;
n.offset = offset + i * hexSize;
tree.addNode(n);
}
}
// ── Bitfield grouping: emit a bitfield container with named members ──
static void emitBitfieldGroup(NodeTree& tree, uint64_t parentId, int offset,
const QVector<ParsedField>& fields,
int startIdx, int endIdx) {
int totalBits = 0;
for (int i = startIdx; i < endIdx; i++)
totalBits += fields[i].bitfieldWidth;
int bytes = (totalBits + 7) / 8;
NodeKind containerKind;
if (bytes <= 1) containerKind = NodeKind::Hex8;
else if (bytes <= 2) containerKind = NodeKind::Hex16;
else if (bytes <= 4) containerKind = NodeKind::Hex32;
else containerKind = NodeKind::Hex64;
Node n;
n.kind = NodeKind::Struct;
n.classKeyword = QStringLiteral("bitfield");
n.elementKind = containerKind;
n.parentId = parentId;
n.offset = offset;
n.collapsed = false;
// Populate bitfield members with computed bit offsets
uint8_t bitOffset = 0;
for (int i = startIdx; i < endIdx; i++) {
BitfieldMember bm;
bm.name = fields[i].name;
bm.bitOffset = bitOffset;
bm.bitWidth = (uint8_t)fields[i].bitfieldWidth;
n.bitfieldMembers.append(bm);
bitOffset += bm.bitWidth;
}
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)
int ptrSize = 8; // target pointer size (4 or 8)
};
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 bitfield container
if (field.bitfieldWidth >= 0) {
int groupOffset;
if (ctx.useCommentOffsets && field.commentOffset >= 0)
groupOffset = field.commentOffset - baseOffset;
else
groupOffset = computedOffset;
int startIdx = fi;
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,
fields, startIdx, fi + 1);
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 = (ctx.ptrSize >= 8) ? NodeKind::Pointer64 : NodeKind::Pointer32;
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 + ctx.ptrSize;
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, int pointerSize) {
if (sourceCode.trimmed().isEmpty()) {
if (errorMsg) *errorMsg = QStringLiteral("Empty source code");
return {};
}
// Tokenize
Tokenizer tokenizer(sourceCode);
tokenizer.tokenize();
// Parse
Parser parser(tokenizer.tokens, tokenizer.offsets);
parser.parse();
if (parser.structs.isEmpty()) {
if (errorMsg) *errorMsg = QStringLiteral("No struct or enum definitions found");
return {};
}
// Build type table (pointer-size types depend on target architecture)
QHash<QString, TypeInfo> typeTable = buildTypeTable(pointerSize);
// Register typedefs into type table
for (auto it = parser.typedefs.begin(); it != parser.typedefs.end(); ++it) {
if (typeTable.contains(it.value())) {
typeTable[it.key()] = typeTable[it.value()];
}
}
NodeTree tree;
tree.baseAddress = 0x00400000;
tree.pointerSize = pointerSize;
QHash<QString, uint64_t> classIds;
QVector<PendingRef> pendingRefs;
// 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) {
if (hasAnyCommentOffset(ps.fields)) { useCommentOffsets = true; break; }
}
// 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, pointerSize};
// Build nodes for each struct/enum
for (const auto& ps : parser.structs) {
Node structNode;
structNode.kind = NodeKind::Struct;
structNode.name = ps.name;
structNode.structTypeName = ps.name;
structNode.classKeyword = ps.keyword;
structNode.parentId = 0;
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;
buildFields(ctx, structId, 0, ps.fields);
// Apply static_assert size: add tail padding if needed
auto sizeIt = parser.sizeAsserts.find(ps.name);
if (sizeIt != parser.sizeAsserts.end()) {
int declaredSize = sizeIt.value();
int currentSpan = tree.structSpan(structId);
if (declaredSize > currentSpan) {
emitHexPadding(tree, structId, currentSpan, declaredSize - currentSpan);
}
}
}
if (tree.nodes.isEmpty()) {
if (errorMsg) *errorMsg = QStringLiteral("No nodes generated from source");
return {};
}
// Resolve deferred pointer/struct references
for (const auto& ref : pendingRefs) {
int nodeIdx = tree.indexOfId(ref.nodeId);
if (nodeIdx < 0) continue;
auto it = classIds.find(ref.className);
if (it != classIds.end()) {
tree.nodes[nodeIdx].refId = it.value();
}
}
return tree;
}
} // namespace rcx