#include "generator.h" #include #include #include #include namespace rcx { namespace { // ── Identifier sanitisation ── static QString sanitizeIdent(const QString& name) { if (name.isEmpty()) return QStringLiteral("unnamed"); QString out; out.reserve(name.size()); for (QChar c : name) { if (c.isLetterOrNumber() || c == '_') out += c; else out += '_'; } if (!out[0].isLetter() && out[0] != '_') out.prepend('_'); return out; } // ── C type name for a primitive NodeKind ── static QString cTypeName(NodeKind kind) { switch (kind) { case NodeKind::Hex8: return QStringLiteral("uint8_t"); case NodeKind::Hex16: return QStringLiteral("uint16_t"); case NodeKind::Hex32: return QStringLiteral("uint32_t"); case NodeKind::Hex64: return QStringLiteral("uint64_t"); case NodeKind::Int8: return QStringLiteral("int8_t"); case NodeKind::Int16: return QStringLiteral("int16_t"); case NodeKind::Int32: return QStringLiteral("int32_t"); case NodeKind::Int64: return QStringLiteral("int64_t"); case NodeKind::UInt8: return QStringLiteral("uint8_t"); case NodeKind::UInt16: return QStringLiteral("uint16_t"); case NodeKind::UInt32: return QStringLiteral("uint32_t"); case NodeKind::UInt64: return QStringLiteral("uint64_t"); case NodeKind::Float: return QStringLiteral("float"); case NodeKind::Double: return QStringLiteral("double"); case NodeKind::Bool: return QStringLiteral("bool"); case NodeKind::Pointer32: return QStringLiteral("uint32_t"); case NodeKind::Pointer64: return QStringLiteral("uint64_t"); case NodeKind::FuncPtr32: return QStringLiteral("uint32_t"); case NodeKind::FuncPtr64: return QStringLiteral("uint64_t"); case NodeKind::Vec2: return QStringLiteral("float"); case NodeKind::Vec3: return QStringLiteral("float"); case NodeKind::Vec4: return QStringLiteral("float"); case NodeKind::Mat4x4: return QStringLiteral("float"); case NodeKind::UTF8: return QStringLiteral("char"); case NodeKind::UTF16: return QStringLiteral("wchar_t"); default: return QStringLiteral("uint8_t"); } } // ── Generator context ── struct GenContext { const NodeTree& tree; QHash> childMap; QSet emittedTypeNames; // struct type names already emitted QSet emittedIds; // struct node IDs already emitted QSet visiting; // cycle guard QSet forwardDeclared; // forward-declared type IDs QString output; int padCounter = 0; const QHash* typeAliases = nullptr; bool emitAsserts = false; QString uniquePadName() { return QStringLiteral("_pad%1").arg(padCounter++, 4, 16, QChar('0')); } // Resolve the C type name for a primitive, consulting aliases first QString cType(NodeKind kind) const { if (typeAliases) { auto it = typeAliases->find(kind); if (it != typeAliases->end() && !it.value().isEmpty()) return it.value(); } return cTypeName(kind); } // Resolve the canonical type name for a struct/array node QString structName(const Node& n) const { if (!n.structTypeName.isEmpty()) return sanitizeIdent(n.structTypeName); if (!n.name.isEmpty()) return sanitizeIdent(n.name); return QStringLiteral("anon_%1").arg(n.id, 0, 16); } }; // Forward declarations static void emitStruct(GenContext& ctx, uint64_t structId); // ── Field line with offset comment (code + marker + comment) ── // We use a \x01 marker to separate the code part from the offset comment. // After all output is generated, alignComments() replaces markers with padding. static const QChar kCommentMarker = QChar(0x01); static QString offsetComment(int offset, bool isSizeof = false) { if (isSizeof) return QString(kCommentMarker) + QStringLiteral("// sizeof 0x%1").arg(QString::number(offset, 16).toUpper()); return QString(kCommentMarker) + QStringLiteral("// 0x%1").arg(QString::number(offset, 16).toUpper()); } static QString indent(int depth) { return QString(depth * 4, ' '); } static QString emitField(GenContext& ctx, const Node& node, int depth, int baseOffset) { const NodeTree& tree = ctx.tree; QString ind = indent(depth); QString name = sanitizeIdent(node.name.isEmpty() ? QStringLiteral("field_%1").arg(node.offset, 2, 16, QChar('0')) : node.name); QString oc = offsetComment(baseOffset + node.offset); switch (node.kind) { case NodeKind::Vec2: return ind + QStringLiteral("%1 %2[2];").arg(ctx.cType(NodeKind::Float), name) + oc; case NodeKind::Vec3: return ind + QStringLiteral("%1 %2[3];").arg(ctx.cType(NodeKind::Float), name) + oc; case NodeKind::Vec4: return ind + QStringLiteral("%1 %2[4];").arg(ctx.cType(NodeKind::Float), name) + oc; case NodeKind::Mat4x4: return ind + QStringLiteral("%1 %2[4][4];").arg(ctx.cType(NodeKind::Float), name) + oc; case NodeKind::UTF8: return ind + QStringLiteral("%1 %2[%3];").arg(ctx.cType(NodeKind::UTF8), name).arg(node.strLen) + oc; case NodeKind::UTF16: return ind + QStringLiteral("%1 %2[%3];").arg(ctx.cType(NodeKind::UTF16), name).arg(node.strLen) + oc; case NodeKind::Pointer32: case NodeKind::Pointer64: { if (node.refId != 0) { int refIdx = tree.indexOfId(node.refId); if (refIdx >= 0) { QString target = ctx.structName(tree.nodes[refIdx]); return ind + QStringLiteral("struct %1* %2;").arg(target, name) + oc; } } // Native pointer: use void* when this is the target's natural pointer kind bool isNativePtr = (node.kind == NodeKind::Pointer32 && ctx.tree.pointerSize <= 4) || (node.kind == NodeKind::Pointer64 && ctx.tree.pointerSize >= 8); if (isNativePtr) return ind + QStringLiteral("void* %1;").arg(name) + oc; // Cross-size pointer: fall back to raw integer type return ind + QStringLiteral("%1 %2;").arg(ctx.cType(node.kind), name) + oc; } case NodeKind::FuncPtr32: return ind + QStringLiteral("void (*%1)();").arg(name) + oc; case NodeKind::FuncPtr64: return ind + QStringLiteral("void (*%1)();").arg(name) + oc; default: return ind + QStringLiteral("%1 %2;").arg(ctx.cType(node.kind), name) + oc; } } // ── Emit struct body (fields + padding) — Vergilius-style ── static void emitStructBody(GenContext& ctx, uint64_t structId, bool isUnion, int depth, int baseOffset) { const NodeTree& tree = ctx.tree; int idx = tree.indexOfId(structId); if (idx < 0) return; int structSize = tree.structSpan(structId, &ctx.childMap); QString ind = indent(depth); QVector allChildren = ctx.childMap.value(structId); QVector children, staticIdxs; for (int ci : allChildren) { if (tree.nodes[ci].isStatic) staticIdxs.append(ci); else children.append(ci); } std::sort(children.begin(), children.end(), [&](int a, int b) { return tree.nodes[a].offset < tree.nodes[b].offset; }); // Helper: emit a padding/hex run as a single collapsed byte array auto emitPadRun = [&](int relOffset, int size) { if (size <= 0) return; ctx.output += ind + QStringLiteral("uint8_t %1[0x%2];%3\n") .arg(ctx.uniquePadName()) .arg(QString::number(size, 16).toUpper()) .arg(offsetComment(baseOffset + relOffset)); }; int cursor = 0; int i = 0; while (i < children.size()) { const Node& child = tree.nodes[children[i]]; int childSize; if (child.kind == NodeKind::Struct || child.kind == NodeKind::Array) childSize = tree.structSpan(child.id, &ctx.childMap); else childSize = child.byteSize(); // Gap/overlap handling (skip for unions) if (!isUnion) { if (child.offset > cursor) emitPadRun(cursor, child.offset - cursor); else if (child.offset < cursor) ctx.output += ind + QStringLiteral("// WARNING: overlap at offset 0x%1 (previous field ends at 0x%2)\n") .arg(QString::number(baseOffset + child.offset, 16).toUpper()) .arg(QString::number(baseOffset + cursor, 16).toUpper()); } // Collapse consecutive hex nodes into a single padding array if (isHexNode(child.kind)) { int runStart = child.offset; int runEnd = child.offset + childSize; int j = i + 1; while (j < children.size()) { const Node& next = tree.nodes[children[j]]; if (!isHexNode(next.kind)) break; int nextSize = next.byteSize(); if (next.offset < runEnd) break; runEnd = next.offset + nextSize; j++; } emitPadRun(runStart, runEnd - runStart); cursor = runEnd; i = j; continue; } // Emit the field if (child.kind == NodeKind::Struct) { // Bitfield container — emit inline bitfield members if (child.classKeyword == QStringLiteral("bitfield") && !child.bitfieldMembers.isEmpty()) { QString bfType = ctx.cType(child.elementKind); if (bfType.isEmpty()) bfType = QStringLiteral("uint32_t"); QString fieldName = child.name.isEmpty() ? QString() : QStringLiteral(" ") + sanitizeIdent(child.name); ctx.output += ind + QStringLiteral("struct\n"); ctx.output += ind + QStringLiteral("{\n"); QString bfInd = indent(depth + 1); for (const auto& m : child.bitfieldMembers) { ctx.output += bfInd + bfType + QStringLiteral(" ") + sanitizeIdent(m.name) + QStringLiteral(" : ") + QString::number(m.bitWidth) + QStringLiteral(";") + offsetComment(baseOffset + child.offset) + QStringLiteral("\n"); } ctx.output += ind + QStringLiteral("}") + fieldName + QStringLiteral(";") + offsetComment(baseOffset + child.offset) + QStringLiteral("\n"); } else { bool isAnonymous = child.structTypeName.isEmpty(); if (isAnonymous) { // Inline anonymous struct/union QString kw = child.resolvedClassKeyword(); ctx.output += ind + kw + QStringLiteral("\n"); ctx.output += ind + QStringLiteral("{\n"); bool childIsUnion = (kw == QStringLiteral("union")); emitStructBody(ctx, child.id, childIsUnion, depth + 1, baseOffset + child.offset); QString fieldName = child.name.isEmpty() ? QString() : QStringLiteral(" ") + sanitizeIdent(child.name); ctx.output += ind + QStringLiteral("}") + fieldName + QStringLiteral(";") + offsetComment(baseOffset + child.offset) + QStringLiteral("\n"); } else { // Named struct — reference by name with struct keyword prefix QString kw = child.resolvedClassKeyword(); if (kw == QStringLiteral("enum") && child.enumMembers.isEmpty()) kw = QStringLiteral("struct"); QString typeName = sanitizeIdent(child.structTypeName); QString fieldName = sanitizeIdent(child.name); ctx.output += ind + kw + QStringLiteral(" ") + typeName + QStringLiteral(" ") + fieldName + QStringLiteral(";") + offsetComment(baseOffset + child.offset) + QStringLiteral("\n"); } } // end bitfield else } else if (child.kind == NodeKind::Array) { QVector arrayKids = ctx.childMap.value(child.id); bool hasStructChild = false; QString elemTypeName; for (int ak : arrayKids) { if (tree.nodes[ak].kind == NodeKind::Struct) { hasStructChild = true; elemTypeName = ctx.structName(tree.nodes[ak]); break; } } QString fieldName = sanitizeIdent(child.name); if (hasStructChild && !elemTypeName.isEmpty()) { ctx.output += ind + QStringLiteral("struct %1 %2[%3];%4\n") .arg(elemTypeName, fieldName).arg(child.arrayLen) .arg(offsetComment(baseOffset + child.offset)); } else { ctx.output += ind + QStringLiteral("%1 %2[%3];%4\n") .arg(ctx.cType(child.elementKind), fieldName).arg(child.arrayLen) .arg(offsetComment(baseOffset + child.offset)); } } else { ctx.output += emitField(ctx, child, depth, baseOffset) + QStringLiteral("\n"); } int childEnd = child.offset + childSize; if (childEnd > cursor) cursor = childEnd; i++; } // Tail padding (skip for unions) if (!isUnion && cursor < structSize) emitPadRun(cursor, structSize - cursor); // Emit static field comments (static fields are runtime-only, not part of struct layout) for (int si : staticIdxs) { const Node& sf = tree.nodes[si]; QString sfType = sf.structTypeName.isEmpty() ? ctx.cType(sf.kind) : sf.structTypeName; ctx.output += ind + QStringLiteral("// static: %1 %2 @ %3\n") .arg(sfType, sanitizeIdent(sf.name), sf.offsetExpr); } } // ── Emit a complete top-level struct definition (Vergilius-style) ── static void emitStruct(GenContext& ctx, uint64_t structId) { if (ctx.emittedIds.contains(structId)) return; if (ctx.visiting.contains(structId)) return; // cycle ctx.visiting.insert(structId); int idx = ctx.tree.indexOfId(structId); if (idx < 0) { ctx.visiting.remove(structId); return; } const Node& node = ctx.tree.nodes[idx]; if (node.kind != NodeKind::Struct && node.kind != NodeKind::Array) { ctx.visiting.remove(structId); return; } if (node.kind == NodeKind::Array) { ctx.visiting.remove(structId); return; } // Deduplicate by struct type name QString typeName = ctx.structName(node); if (ctx.emittedTypeNames.contains(typeName)) { ctx.emittedIds.insert(structId); ctx.visiting.remove(structId); return; } ctx.emittedIds.insert(structId); ctx.emittedTypeNames.insert(typeName); int structSize = ctx.tree.structSpan(structId, &ctx.childMap); QString kw = node.resolvedClassKeyword(); // Enum with members: emit as proper C enum if (kw == QStringLiteral("enum") && !node.enumMembers.isEmpty()) { ctx.output += QStringLiteral("enum %1 {\n").arg(typeName); for (const auto& m : node.enumMembers) { ctx.output += QStringLiteral(" %1 = %2,\n") .arg(sanitizeIdent(m.first)) .arg(m.second); } ctx.output += QStringLiteral("};\n\n"); ctx.visiting.remove(structId); return; } if (kw == QStringLiteral("enum")) kw = QStringLiteral("struct"); ctx.output += kw + QStringLiteral(" ") + typeName + QStringLiteral("\n{\n"); emitStructBody(ctx, structId, kw == QStringLiteral("union"), 1, 0); ctx.output += QStringLiteral("};") + offsetComment(structSize, true) + QStringLiteral("\n"); if (ctx.emitAsserts) ctx.output += QStringLiteral("static_assert(sizeof(%1) == 0x%2, \"Size mismatch for %1\");\n") .arg(typeName) .arg(QString::number(structSize, 16).toUpper()); ctx.output += QStringLiteral("\n"); ctx.visiting.remove(structId); } // ── Build the child map used by all generators ── static QHash> buildChildMap(const NodeTree& tree) { QHash> map; for (int i = 0; i < tree.nodes.size(); i++) map[tree.nodes[i].parentId].append(i); return map; } // ── Align offset comments ── // Replaces kCommentMarker with spaces so all "// 0x..." comments align to // the same column (the longest code portion + 1 space). static QString alignComments(const QString& raw) { QStringList lines = raw.split('\n'); // First pass: find the maximum code width (text before the marker) int maxCode = 0; for (const QString& line : lines) { int pos = line.indexOf(kCommentMarker); if (pos >= 0) maxCode = qMax(maxCode, pos); } // Second pass: replace markers with padding QString result; result.reserve(raw.size() + lines.size() * 8); for (int i = 0; i < lines.size(); i++) { if (i > 0) result += '\n'; const QString& line = lines[i]; int pos = line.indexOf(kCommentMarker); if (pos >= 0) { result += line.left(pos); int pad = maxCode - pos + 1; if (pad < 1) pad = 1; result += QString(pad, ' '); result += line.mid(pos + 1); // skip the marker char } else { result += line; } } return result; } // ═══════════════════════════════════════════════════════════════════ // ── Rust backend ── // ═══════════════════════════════════════════════════════════════════ static QString rustTypeName(NodeKind kind) { switch (kind) { case NodeKind::Hex8: return QStringLiteral("u8"); case NodeKind::Hex16: return QStringLiteral("u16"); case NodeKind::Hex32: return QStringLiteral("u32"); case NodeKind::Hex64: return QStringLiteral("u64"); case NodeKind::Int8: return QStringLiteral("i8"); case NodeKind::Int16: return QStringLiteral("i16"); case NodeKind::Int32: return QStringLiteral("i32"); case NodeKind::Int64: return QStringLiteral("i64"); case NodeKind::UInt8: return QStringLiteral("u8"); case NodeKind::UInt16: return QStringLiteral("u16"); case NodeKind::UInt32: return QStringLiteral("u32"); case NodeKind::UInt64: return QStringLiteral("u64"); case NodeKind::Float: return QStringLiteral("f32"); case NodeKind::Double: return QStringLiteral("f64"); case NodeKind::Bool: return QStringLiteral("bool"); case NodeKind::Pointer32: return QStringLiteral("u32"); case NodeKind::Pointer64: return QStringLiteral("u64"); case NodeKind::FuncPtr32: return QStringLiteral("u32"); case NodeKind::FuncPtr64: return QStringLiteral("u64"); case NodeKind::Vec2: return QStringLiteral("f32"); case NodeKind::Vec3: return QStringLiteral("f32"); case NodeKind::Vec4: return QStringLiteral("f32"); case NodeKind::Mat4x4: return QStringLiteral("f32"); case NodeKind::UTF8: return QStringLiteral("u8"); case NodeKind::UTF16: return QStringLiteral("u16"); default: return QStringLiteral("u8"); } } // Forward declaration static void emitRustStruct(GenContext& ctx, uint64_t structId); static QString rustType(GenContext& ctx, NodeKind kind) { if (ctx.typeAliases) { auto it = ctx.typeAliases->find(kind); if (it != ctx.typeAliases->end() && !it.value().isEmpty()) return it.value(); } return rustTypeName(kind); } static QString emitRustField(GenContext& ctx, const Node& node, int depth, int baseOffset) { const NodeTree& tree = ctx.tree; QString ind = indent(depth); QString name = sanitizeIdent(node.name.isEmpty() ? QStringLiteral("field_%1").arg(node.offset, 2, 16, QChar('0')) : node.name); QString oc = offsetComment(baseOffset + node.offset); switch (node.kind) { case NodeKind::Vec2: return ind + QStringLiteral("pub %1: [f32; 2],").arg(name) + oc; case NodeKind::Vec3: return ind + QStringLiteral("pub %1: [f32; 3],").arg(name) + oc; case NodeKind::Vec4: return ind + QStringLiteral("pub %1: [f32; 4],").arg(name) + oc; case NodeKind::Mat4x4: return ind + QStringLiteral("pub %1: [[f32; 4]; 4],").arg(name) + oc; case NodeKind::UTF8: return ind + QStringLiteral("pub %1: [u8; %2],").arg(name).arg(node.strLen) + oc; case NodeKind::UTF16: return ind + QStringLiteral("pub %1: [u16; %2],").arg(name).arg(node.strLen) + oc; case NodeKind::Pointer32: case NodeKind::Pointer64: { if (node.refId != 0) { int refIdx = tree.indexOfId(node.refId); if (refIdx >= 0) { QString target = ctx.structName(tree.nodes[refIdx]); return ind + QStringLiteral("pub %1: *mut %2,").arg(name, target) + oc; } } bool isNativePtr = (node.kind == NodeKind::Pointer32 && ctx.tree.pointerSize <= 4) || (node.kind == NodeKind::Pointer64 && ctx.tree.pointerSize >= 8); if (isNativePtr) return ind + QStringLiteral("pub %1: *mut core::ffi::c_void,").arg(name) + oc; return ind + QStringLiteral("pub %1: %2,").arg(name, rustType(ctx, node.kind)) + oc; } case NodeKind::FuncPtr32: case NodeKind::FuncPtr64: return ind + QStringLiteral("pub %1: Option,").arg(name) + oc; default: return ind + QStringLiteral("pub %1: %2,").arg(name, rustType(ctx, node.kind)) + oc; } } static void emitRustStructBody(GenContext& ctx, uint64_t structId, bool isUnion, int depth, int baseOffset) { const NodeTree& tree = ctx.tree; int idx = tree.indexOfId(structId); if (idx < 0) return; int structSize = tree.structSpan(structId, &ctx.childMap); QString ind = indent(depth); QVector allChildren = ctx.childMap.value(structId); QVector children, staticIdxs; for (int ci : allChildren) { if (tree.nodes[ci].isStatic) staticIdxs.append(ci); else children.append(ci); } std::sort(children.begin(), children.end(), [&](int a, int b) { return tree.nodes[a].offset < tree.nodes[b].offset; }); auto emitPadRun = [&](int relOffset, int size) { if (size <= 0) return; ctx.output += ind + QStringLiteral("pub %1: [u8; 0x%2],") .arg(ctx.uniquePadName()) .arg(QString::number(size, 16).toUpper()) + offsetComment(baseOffset + relOffset) + QStringLiteral("\n"); }; int cursor = 0; int i = 0; while (i < children.size()) { const Node& child = tree.nodes[children[i]]; int childSize; if (child.kind == NodeKind::Struct || child.kind == NodeKind::Array) childSize = tree.structSpan(child.id, &ctx.childMap); else childSize = child.byteSize(); if (!isUnion) { if (child.offset > cursor) emitPadRun(cursor, child.offset - cursor); } if (isHexNode(child.kind)) { int runStart = child.offset; int runEnd = child.offset + childSize; int j = i + 1; while (j < children.size()) { const Node& next = tree.nodes[children[j]]; if (!isHexNode(next.kind)) break; int nextSize = next.byteSize(); if (next.offset < runEnd) break; runEnd = next.offset + nextSize; j++; } emitPadRun(runStart, runEnd - runStart); cursor = runEnd; i = j; continue; } if (child.kind == NodeKind::Struct) { if (child.classKeyword == QStringLiteral("bitfield") && !child.bitfieldMembers.isEmpty()) { // Rust has no native bitfields — emit container + comment QString bfType = rustType(ctx, child.elementKind); if (bfType.isEmpty()) bfType = QStringLiteral("u32"); QString fieldName = sanitizeIdent(child.name.isEmpty() ? QStringLiteral("bitfield_%1").arg(child.offset, 2, 16, QChar('0')) : child.name); QStringList bits; for (const auto& m : child.bitfieldMembers) bits << QStringLiteral("%1:%2").arg(sanitizeIdent(m.name)).arg(m.bitWidth); ctx.output += ind + QStringLiteral("pub %1: %2,") .arg(fieldName, bfType) + QStringLiteral(" // bits: ") + bits.join(QStringLiteral(", ")) + offsetComment(baseOffset + child.offset) + QStringLiteral("\n"); } else { bool isAnonymous = child.structTypeName.isEmpty(); if (isAnonymous) { // Rust can't do anonymous inline structs — flatten as byte array int span = tree.structSpan(child.id, &ctx.childMap); QString fieldName = sanitizeIdent(child.name.isEmpty() ? QStringLiteral("anon_%1").arg(child.offset, 2, 16, QChar('0')) : child.name); ctx.output += ind + QStringLiteral("pub %1: [u8; 0x%2],") .arg(fieldName) .arg(QString::number(span, 16).toUpper()) + offsetComment(baseOffset + child.offset) + QStringLiteral("\n"); } else { QString kw = child.resolvedClassKeyword(); if (kw == QStringLiteral("enum") && child.enumMembers.isEmpty()) kw = QStringLiteral("struct"); QString typeName = sanitizeIdent(child.structTypeName); QString fieldName = sanitizeIdent(child.name); ctx.output += ind + QStringLiteral("pub %1: %2,") .arg(fieldName, typeName) + offsetComment(baseOffset + child.offset) + QStringLiteral("\n"); } } } else if (child.kind == NodeKind::Array) { QVector arrayKids = ctx.childMap.value(child.id); bool hasStructChild = false; QString elemTypeName; for (int ak : arrayKids) { if (tree.nodes[ak].kind == NodeKind::Struct) { hasStructChild = true; elemTypeName = ctx.structName(tree.nodes[ak]); break; } } QString fieldName = sanitizeIdent(child.name); if (hasStructChild && !elemTypeName.isEmpty()) { ctx.output += ind + QStringLiteral("pub %1: [%2; %3],") .arg(fieldName, elemTypeName).arg(child.arrayLen) + offsetComment(baseOffset + child.offset) + QStringLiteral("\n"); } else { ctx.output += ind + QStringLiteral("pub %1: [%2; %3],") .arg(fieldName, rustType(ctx, child.elementKind)).arg(child.arrayLen) + offsetComment(baseOffset + child.offset) + QStringLiteral("\n"); } } else { ctx.output += emitRustField(ctx, child, depth, baseOffset) + QStringLiteral("\n"); } int childEnd = child.offset + childSize; if (childEnd > cursor) cursor = childEnd; i++; } if (!isUnion && cursor < structSize) emitPadRun(cursor, structSize - cursor); for (int si : staticIdxs) { const Node& sf = tree.nodes[si]; QString sfType = sf.structTypeName.isEmpty() ? rustType(ctx, sf.kind) : sf.structTypeName; ctx.output += ind + QStringLiteral("// static: %1 %2 @ %3\n") .arg(sfType, sanitizeIdent(sf.name), sf.offsetExpr); } } static void emitRustStruct(GenContext& ctx, uint64_t structId) { if (ctx.emittedIds.contains(structId)) return; if (ctx.visiting.contains(structId)) return; ctx.visiting.insert(structId); int idx = ctx.tree.indexOfId(structId); if (idx < 0) { ctx.visiting.remove(structId); return; } const Node& node = ctx.tree.nodes[idx]; if (node.kind != NodeKind::Struct) { ctx.visiting.remove(structId); return; } QString typeName = ctx.structName(node); if (ctx.emittedTypeNames.contains(typeName)) { ctx.emittedIds.insert(structId); ctx.visiting.remove(structId); return; } ctx.emittedIds.insert(structId); ctx.emittedTypeNames.insert(typeName); int structSize = ctx.tree.structSpan(structId, &ctx.childMap); QString kw = node.resolvedClassKeyword(); // Enum with members if (kw == QStringLiteral("enum") && !node.enumMembers.isEmpty()) { ctx.output += QStringLiteral("#[repr(i64)]\npub enum %1 {\n").arg(typeName); for (const auto& m : node.enumMembers) { ctx.output += QStringLiteral(" %1 = %2,\n") .arg(sanitizeIdent(m.first)) .arg(m.second); } ctx.output += QStringLiteral("}\n\n"); ctx.visiting.remove(structId); return; } bool isUnion = (kw == QStringLiteral("union")); if (isUnion) ctx.output += QStringLiteral("#[repr(C)]\n#[derive(Copy, Clone)]\npub union %1 {\n").arg(typeName); else ctx.output += QStringLiteral("#[repr(C)]\npub struct %1 {\n").arg(typeName); emitRustStructBody(ctx, structId, isUnion, 1, 0); ctx.output += QStringLiteral("}") + offsetComment(structSize, true) + QStringLiteral("\n"); if (ctx.emitAsserts) ctx.output += QStringLiteral("const _: () = assert!(core::mem::size_of::<%1>() == 0x%2);\n") .arg(typeName) .arg(QString::number(structSize, 16).toUpper()); ctx.output += QStringLiteral("\n"); ctx.visiting.remove(structId); } // ═══════════════════════════════════════════════════════════════════ // ── #define offsets backend ── // ═══════════════════════════════════════════════════════════════════ static void emitDefinesForStruct(GenContext& ctx, uint64_t structId, const QString& prefix, int baseOffset) { int idx = ctx.tree.indexOfId(structId); if (idx < 0) return; const Node& node = ctx.tree.nodes[idx]; QString typeName = prefix.isEmpty() ? ctx.structName(node) : prefix; QString kw = node.resolvedClassKeyword(); // Enum with members: emit #define EnumName_MemberName value if (kw == QStringLiteral("enum") && !node.enumMembers.isEmpty()) { ctx.output += QStringLiteral("// %1 (enum)\n").arg(typeName); for (const auto& m : node.enumMembers) { ctx.output += QStringLiteral("#define %1_%2 %3\n") .arg(typeName, sanitizeIdent(m.first)) .arg(m.second); } ctx.output += QStringLiteral("\n"); return; } int structSize = ctx.tree.structSpan(structId, &ctx.childMap); ctx.output += QStringLiteral("// %1 (0x%2 bytes)\n") .arg(typeName) .arg(QString::number(structSize, 16).toUpper()); QVector children = ctx.childMap.value(structId); std::sort(children.begin(), children.end(), [&](int a, int b) { return ctx.tree.nodes[a].offset < ctx.tree.nodes[b].offset; }); for (int ci : children) { const Node& child = ctx.tree.nodes[ci]; if (child.isStatic) continue; if (isHexNode(child.kind)) continue; QString fieldName = sanitizeIdent(child.name.isEmpty() ? QStringLiteral("field_%1").arg(child.offset, 2, 16, QChar('0')) : child.name); int absOffset = baseOffset + child.offset; ctx.output += QStringLiteral("#define %1_%2 0x%3\n") .arg(typeName, fieldName) .arg(QString::number(absOffset, 16).toUpper()); // Recurse into named sub-structs if (child.kind == NodeKind::Struct && !child.structTypeName.isEmpty() && child.classKeyword != QStringLiteral("bitfield")) { emitDefinesForStruct(ctx, child.id, typeName + QStringLiteral("_") + fieldName, absOffset); } } ctx.output += QStringLiteral("\n"); } // ═══════════════════════════════════════════════════════════════════ // ── C# backend ── // ═══════════════════════════════════════════════════════════════════ static QString csTypeName(NodeKind kind) { switch (kind) { case NodeKind::Hex8: return QStringLiteral("byte"); case NodeKind::Hex16: return QStringLiteral("ushort"); case NodeKind::Hex32: return QStringLiteral("uint"); case NodeKind::Hex64: return QStringLiteral("ulong"); case NodeKind::Int8: return QStringLiteral("sbyte"); case NodeKind::Int16: return QStringLiteral("short"); case NodeKind::Int32: return QStringLiteral("int"); case NodeKind::Int64: return QStringLiteral("long"); case NodeKind::UInt8: return QStringLiteral("byte"); case NodeKind::UInt16: return QStringLiteral("ushort"); case NodeKind::UInt32: return QStringLiteral("uint"); case NodeKind::UInt64: return QStringLiteral("ulong"); case NodeKind::Float: return QStringLiteral("float"); case NodeKind::Double: return QStringLiteral("double"); case NodeKind::Bool: return QStringLiteral("bool"); case NodeKind::Pointer32: return QStringLiteral("uint"); case NodeKind::Pointer64: return QStringLiteral("ulong"); case NodeKind::FuncPtr32: return QStringLiteral("uint"); case NodeKind::FuncPtr64: return QStringLiteral("ulong"); case NodeKind::Vec2: return QStringLiteral("float"); case NodeKind::Vec3: return QStringLiteral("float"); case NodeKind::Vec4: return QStringLiteral("float"); case NodeKind::Mat4x4: return QStringLiteral("float"); case NodeKind::UTF8: return QStringLiteral("byte"); case NodeKind::UTF16: return QStringLiteral("char"); default: return QStringLiteral("byte"); } } // Forward declaration static void emitCSharpStruct(GenContext& ctx, uint64_t structId); static QString csType(GenContext& ctx, NodeKind kind) { if (ctx.typeAliases) { auto it = ctx.typeAliases->find(kind); if (it != ctx.typeAliases->end() && !it.value().isEmpty()) return it.value(); } return csTypeName(kind); } static void emitCSharpStructBody(GenContext& ctx, uint64_t structId, bool isUnion, int depth, int baseOffset) { const NodeTree& tree = ctx.tree; int idx = tree.indexOfId(structId); if (idx < 0) return; QString ind = indent(depth); QVector allChildren = ctx.childMap.value(structId); QVector children, staticIdxs; for (int ci : allChildren) { if (tree.nodes[ci].isStatic) staticIdxs.append(ci); else children.append(ci); } std::sort(children.begin(), children.end(), [&](int a, int b) { return tree.nodes[a].offset < tree.nodes[b].offset; }); // C# uses [FieldOffset(N)] for explicit layout — no manual padding needed for (int ci : children) { const Node& child = tree.nodes[ci]; if (isHexNode(child.kind)) continue; // skip padding/hex nodes int absOffset = baseOffset + child.offset; QString name = sanitizeIdent(child.name.isEmpty() ? QStringLiteral("field_%1").arg(child.offset, 2, 16, QChar('0')) : child.name); QString oc = offsetComment(absOffset); if (child.kind == NodeKind::Struct) { if (child.classKeyword == QStringLiteral("bitfield") && !child.bitfieldMembers.isEmpty()) { QString bfType = csType(ctx, child.elementKind); if (bfType.isEmpty()) bfType = QStringLiteral("uint"); QStringList bits; for (const auto& m : child.bitfieldMembers) bits << QStringLiteral("%1:%2").arg(sanitizeIdent(m.name)).arg(m.bitWidth); ctx.output += ind + QStringLiteral("[FieldOffset(0x%1)] public %2 %3;") .arg(QString::number(absOffset, 16).toUpper(), bfType, name) + QStringLiteral(" // bits: ") + bits.join(QStringLiteral(", ")) + oc + QStringLiteral("\n"); } else if (child.structTypeName.isEmpty()) { // Anonymous inline — emit as fixed byte array int span = tree.structSpan(child.id, &ctx.childMap); ctx.output += ind + QStringLiteral("[FieldOffset(0x%1)] public fixed byte %2[0x%3];") .arg(QString::number(absOffset, 16).toUpper(), name) .arg(QString::number(span, 16).toUpper()) + oc + QStringLiteral("\n"); } else { QString typeName = sanitizeIdent(child.structTypeName); ctx.output += ind + QStringLiteral("[FieldOffset(0x%1)] public %2 %3;") .arg(QString::number(absOffset, 16).toUpper(), typeName, name) + oc + QStringLiteral("\n"); } } else if (child.kind == NodeKind::Array) { QVector arrayKids = ctx.childMap.value(child.id); bool hasStructChild = false; QString elemTypeName; for (int ak : arrayKids) { if (tree.nodes[ak].kind == NodeKind::Struct) { hasStructChild = true; elemTypeName = ctx.structName(tree.nodes[ak]); break; } } if (hasStructChild && !elemTypeName.isEmpty()) { // MarshalAs for struct arrays ctx.output += ind + QStringLiteral("[FieldOffset(0x%1)] [MarshalAs(UnmanagedType.ByValArray, SizeConst = %2)] public %3[] %4;") .arg(QString::number(absOffset, 16).toUpper()) .arg(child.arrayLen) .arg(elemTypeName, name) + oc + QStringLiteral("\n"); } else { QString elemType = csType(ctx, child.elementKind); ctx.output += ind + QStringLiteral("[FieldOffset(0x%1)] public fixed %2 %3[%4];") .arg(QString::number(absOffset, 16).toUpper(), elemType, name) .arg(child.arrayLen) + oc + QStringLiteral("\n"); } } else { // Primitive fields switch (child.kind) { case NodeKind::Vec2: ctx.output += ind + QStringLiteral("[FieldOffset(0x%1)] public fixed float %2[2];") .arg(QString::number(absOffset, 16).toUpper(), name) + oc + QStringLiteral("\n"); break; case NodeKind::Vec3: ctx.output += ind + QStringLiteral("[FieldOffset(0x%1)] public fixed float %2[3];") .arg(QString::number(absOffset, 16).toUpper(), name) + oc + QStringLiteral("\n"); break; case NodeKind::Vec4: ctx.output += ind + QStringLiteral("[FieldOffset(0x%1)] public fixed float %2[4];") .arg(QString::number(absOffset, 16).toUpper(), name) + oc + QStringLiteral("\n"); break; case NodeKind::Mat4x4: ctx.output += ind + QStringLiteral("[FieldOffset(0x%1)] public fixed float %2[16];") .arg(QString::number(absOffset, 16).toUpper(), name) + oc + QStringLiteral("\n"); break; case NodeKind::UTF8: ctx.output += ind + QStringLiteral("[FieldOffset(0x%1)] public fixed byte %2[%3];") .arg(QString::number(absOffset, 16).toUpper(), name) .arg(child.strLen) + oc + QStringLiteral("\n"); break; case NodeKind::UTF16: ctx.output += ind + QStringLiteral("[FieldOffset(0x%1)] public fixed char %2[%3];") .arg(QString::number(absOffset, 16).toUpper(), name) .arg(child.strLen) + oc + QStringLiteral("\n"); break; case NodeKind::Pointer32: case NodeKind::Pointer64: { bool isNativePtr = (child.kind == NodeKind::Pointer32 && ctx.tree.pointerSize <= 4) || (child.kind == NodeKind::Pointer64 && ctx.tree.pointerSize >= 8); if (isNativePtr) ctx.output += ind + QStringLiteral("[FieldOffset(0x%1)] public IntPtr %2;") .arg(QString::number(absOffset, 16).toUpper(), name) + oc + QStringLiteral("\n"); else ctx.output += ind + QStringLiteral("[FieldOffset(0x%1)] public %2 %3;") .arg(QString::number(absOffset, 16).toUpper(), csType(ctx, child.kind), name) + oc + QStringLiteral("\n"); break; } case NodeKind::FuncPtr32: case NodeKind::FuncPtr64: ctx.output += ind + QStringLiteral("[FieldOffset(0x%1)] public IntPtr %2;") .arg(QString::number(absOffset, 16).toUpper(), name) + QStringLiteral(" // fn ptr") + oc + QStringLiteral("\n"); break; default: ctx.output += ind + QStringLiteral("[FieldOffset(0x%1)] public %2 %3;") .arg(QString::number(absOffset, 16).toUpper(), csType(ctx, child.kind), name) + oc + QStringLiteral("\n"); break; } } } for (int si : staticIdxs) { const Node& sf = tree.nodes[si]; QString sfType = sf.structTypeName.isEmpty() ? csType(ctx, sf.kind) : sf.structTypeName; ctx.output += ind + QStringLiteral("// static: %1 %2 @ %3\n") .arg(sfType, sanitizeIdent(sf.name), sf.offsetExpr); } } static void emitCSharpStruct(GenContext& ctx, uint64_t structId) { if (ctx.emittedIds.contains(structId)) return; if (ctx.visiting.contains(structId)) return; ctx.visiting.insert(structId); int idx = ctx.tree.indexOfId(structId); if (idx < 0) { ctx.visiting.remove(structId); return; } const Node& node = ctx.tree.nodes[idx]; if (node.kind != NodeKind::Struct) { ctx.visiting.remove(structId); return; } QString typeName = ctx.structName(node); if (ctx.emittedTypeNames.contains(typeName)) { ctx.emittedIds.insert(structId); ctx.visiting.remove(structId); return; } ctx.emittedIds.insert(structId); ctx.emittedTypeNames.insert(typeName); int structSize = ctx.tree.structSpan(structId, &ctx.childMap); QString kw = node.resolvedClassKeyword(); // Enum with members if (kw == QStringLiteral("enum") && !node.enumMembers.isEmpty()) { ctx.output += QStringLiteral("public enum %1 : long\n{\n").arg(typeName); for (const auto& m : node.enumMembers) { ctx.output += QStringLiteral(" %1 = %2,\n") .arg(sanitizeIdent(m.first)) .arg(m.second); } ctx.output += QStringLiteral("}\n\n"); ctx.visiting.remove(structId); return; } bool isUnion = (kw == QStringLiteral("union")); ctx.output += QStringLiteral("[StructLayout(LayoutKind.Explicit, Size = 0x%1)]\n") .arg(QString::number(structSize, 16).toUpper()); ctx.output += QStringLiteral("public unsafe struct %1\n{\n").arg(typeName); emitCSharpStructBody(ctx, structId, isUnion, 1, 0); ctx.output += QStringLiteral("}") + offsetComment(structSize, true) + QStringLiteral("\n\n"); ctx.visiting.remove(structId); } // ═══════════════════════════════════════════════════════════════════ // ── Python ctypes backend ── // ═══════════════════════════════════════════════════════════════════ static QString pyTypeName(NodeKind kind) { switch (kind) { case NodeKind::Hex8: return QStringLiteral("ctypes.c_uint8"); case NodeKind::Hex16: return QStringLiteral("ctypes.c_uint16"); case NodeKind::Hex32: return QStringLiteral("ctypes.c_uint32"); case NodeKind::Hex64: return QStringLiteral("ctypes.c_uint64"); case NodeKind::Int8: return QStringLiteral("ctypes.c_int8"); case NodeKind::Int16: return QStringLiteral("ctypes.c_int16"); case NodeKind::Int32: return QStringLiteral("ctypes.c_int32"); case NodeKind::Int64: return QStringLiteral("ctypes.c_int64"); case NodeKind::UInt8: return QStringLiteral("ctypes.c_uint8"); case NodeKind::UInt16: return QStringLiteral("ctypes.c_uint16"); case NodeKind::UInt32: return QStringLiteral("ctypes.c_uint32"); case NodeKind::UInt64: return QStringLiteral("ctypes.c_uint64"); case NodeKind::Float: return QStringLiteral("ctypes.c_float"); case NodeKind::Double: return QStringLiteral("ctypes.c_double"); case NodeKind::Bool: return QStringLiteral("ctypes.c_bool"); case NodeKind::Pointer32: return QStringLiteral("ctypes.c_uint32"); case NodeKind::Pointer64: return QStringLiteral("ctypes.c_uint64"); case NodeKind::FuncPtr32: return QStringLiteral("ctypes.c_uint32"); case NodeKind::FuncPtr64: return QStringLiteral("ctypes.c_uint64"); case NodeKind::Vec2: return QStringLiteral("ctypes.c_float"); case NodeKind::Vec3: return QStringLiteral("ctypes.c_float"); case NodeKind::Vec4: return QStringLiteral("ctypes.c_float"); case NodeKind::Mat4x4: return QStringLiteral("ctypes.c_float"); case NodeKind::UTF8: return QStringLiteral("ctypes.c_char"); case NodeKind::UTF16: return QStringLiteral("ctypes.c_wchar"); default: return QStringLiteral("ctypes.c_uint8"); } } // Forward declaration static void emitPythonStruct(GenContext& ctx, uint64_t structId); static void emitPythonStructBody(GenContext& ctx, uint64_t structId, bool isUnion, int baseOffset) { const NodeTree& tree = ctx.tree; int idx = tree.indexOfId(structId); if (idx < 0) return; int structSize = tree.structSpan(structId, &ctx.childMap); QString ind = QStringLiteral(" "); // 2 levels for inside _fields_ QVector allChildren = ctx.childMap.value(structId); QVector children; for (int ci : allChildren) { if (!tree.nodes[ci].isStatic) children.append(ci); } std::sort(children.begin(), children.end(), [&](int a, int b) { return tree.nodes[a].offset < tree.nodes[b].offset; }); auto emitPadField = [&](int relOffset, int size) { if (size <= 0) return; ctx.output += ind + QStringLiteral("(\"%1\", ctypes.c_uint8 * 0x%2),") .arg(ctx.uniquePadName()) .arg(QString::number(size, 16).toUpper()) + offsetComment(baseOffset + relOffset) + QStringLiteral("\n"); }; int cursor = 0; int i = 0; while (i < children.size()) { const Node& child = tree.nodes[children[i]]; int childSize; if (child.kind == NodeKind::Struct || child.kind == NodeKind::Array) childSize = tree.structSpan(child.id, &ctx.childMap); else childSize = child.byteSize(); if (!isUnion) { if (child.offset > cursor) emitPadField(cursor, child.offset - cursor); } // Collapse hex nodes into padding if (isHexNode(child.kind)) { int runStart = child.offset; int runEnd = child.offset + childSize; int j = i + 1; while (j < children.size()) { const Node& next = tree.nodes[children[j]]; if (!isHexNode(next.kind)) break; int nextSize = next.byteSize(); if (next.offset < runEnd) break; runEnd = next.offset + nextSize; j++; } emitPadField(runStart, runEnd - runStart); cursor = runEnd; i = j; continue; } int absOffset = baseOffset + child.offset; QString name = sanitizeIdent(child.name.isEmpty() ? QStringLiteral("field_%1").arg(child.offset, 2, 16, QChar('0')) : child.name); QString oc = offsetComment(absOffset); if (child.kind == NodeKind::Struct) { if (child.classKeyword == QStringLiteral("bitfield") && !child.bitfieldMembers.isEmpty()) { QString bfType = pyTypeName(child.elementKind); if (bfType.isEmpty()) bfType = QStringLiteral("ctypes.c_uint32"); QStringList bits; for (const auto& m : child.bitfieldMembers) bits << QStringLiteral("%1:%2").arg(sanitizeIdent(m.name)).arg(m.bitWidth); ctx.output += ind + QStringLiteral("(\"%1\", %2),") .arg(name, bfType) + QStringLiteral(" # bits: ") + bits.join(QStringLiteral(", ")) + oc + QStringLiteral("\n"); } else if (child.structTypeName.isEmpty()) { int span = tree.structSpan(child.id, &ctx.childMap); ctx.output += ind + QStringLiteral("(\"%1\", ctypes.c_uint8 * 0x%2),") .arg(name) .arg(QString::number(span, 16).toUpper()) + oc + QStringLiteral("\n"); } else { QString typeName = sanitizeIdent(child.structTypeName); ctx.output += ind + QStringLiteral("(\"%1\", %2),") .arg(name, typeName) + oc + QStringLiteral("\n"); } } else if (child.kind == NodeKind::Array) { QVector arrayKids = ctx.childMap.value(child.id); bool hasStructChild = false; QString elemTypeName; for (int ak : arrayKids) { if (tree.nodes[ak].kind == NodeKind::Struct) { hasStructChild = true; elemTypeName = ctx.structName(tree.nodes[ak]); break; } } if (hasStructChild && !elemTypeName.isEmpty()) { ctx.output += ind + QStringLiteral("(\"%1\", %2 * %3),") .arg(name, elemTypeName).arg(child.arrayLen) + oc + QStringLiteral("\n"); } else { ctx.output += ind + QStringLiteral("(\"%1\", %2 * %3),") .arg(name, pyTypeName(child.elementKind)).arg(child.arrayLen) + oc + QStringLiteral("\n"); } } else { // Primitive fields switch (child.kind) { case NodeKind::Vec2: ctx.output += ind + QStringLiteral("(\"%1\", ctypes.c_float * 2),").arg(name) + oc + QStringLiteral("\n"); break; case NodeKind::Vec3: ctx.output += ind + QStringLiteral("(\"%1\", ctypes.c_float * 3),").arg(name) + oc + QStringLiteral("\n"); break; case NodeKind::Vec4: ctx.output += ind + QStringLiteral("(\"%1\", ctypes.c_float * 4),").arg(name) + oc + QStringLiteral("\n"); break; case NodeKind::Mat4x4: ctx.output += ind + QStringLiteral("(\"%1\", (ctypes.c_float * 4) * 4),").arg(name) + oc + QStringLiteral("\n"); break; case NodeKind::UTF8: ctx.output += ind + QStringLiteral("(\"%1\", ctypes.c_char * %2),").arg(name) .arg(child.strLen) + oc + QStringLiteral("\n"); break; case NodeKind::UTF16: ctx.output += ind + QStringLiteral("(\"%1\", ctypes.c_wchar * %2),").arg(name) .arg(child.strLen) + oc + QStringLiteral("\n"); break; case NodeKind::Pointer32: case NodeKind::Pointer64: { bool isNativePtr = (child.kind == NodeKind::Pointer32 && ctx.tree.pointerSize <= 4) || (child.kind == NodeKind::Pointer64 && ctx.tree.pointerSize >= 8); if (child.refId != 0) { int refIdx = tree.indexOfId(child.refId); if (refIdx >= 0) { QString target = ctx.structName(tree.nodes[refIdx]); ctx.output += ind + QStringLiteral("(\"%1\", ctypes.POINTER(%2)),").arg(name, target) + oc + QStringLiteral("\n"); break; } } if (isNativePtr) ctx.output += ind + QStringLiteral("(\"%1\", ctypes.c_void_p),").arg(name) + oc + QStringLiteral("\n"); else ctx.output += ind + QStringLiteral("(\"%1\", %2),").arg(name, pyTypeName(child.kind)) + oc + QStringLiteral("\n"); break; } case NodeKind::FuncPtr32: case NodeKind::FuncPtr64: ctx.output += ind + QStringLiteral("(\"%1\", ctypes.c_void_p),").arg(name) + QStringLiteral(" # fn ptr") + oc + QStringLiteral("\n"); break; default: ctx.output += ind + QStringLiteral("(\"%1\", %2),").arg(name, pyTypeName(child.kind)) + oc + QStringLiteral("\n"); break; } } int childEnd = child.offset + childSize; if (childEnd > cursor) cursor = childEnd; i++; } // Tail padding if (!isUnion && cursor < structSize) emitPadField(cursor, structSize - cursor); } static void emitPythonStruct(GenContext& ctx, uint64_t structId) { if (ctx.emittedIds.contains(structId)) return; if (ctx.visiting.contains(structId)) return; ctx.visiting.insert(structId); int idx = ctx.tree.indexOfId(structId); if (idx < 0) { ctx.visiting.remove(structId); return; } const Node& node = ctx.tree.nodes[idx]; if (node.kind != NodeKind::Struct) { ctx.visiting.remove(structId); return; } QString typeName = ctx.structName(node); if (ctx.emittedTypeNames.contains(typeName)) { ctx.emittedIds.insert(structId); ctx.visiting.remove(structId); return; } ctx.emittedIds.insert(structId); ctx.emittedTypeNames.insert(typeName); int structSize = ctx.tree.structSpan(structId, &ctx.childMap); QString kw = node.resolvedClassKeyword(); // Enum with members — emit as class with constants if (kw == QStringLiteral("enum") && !node.enumMembers.isEmpty()) { ctx.output += QStringLiteral("class %1: # enum\n").arg(typeName); for (const auto& m : node.enumMembers) { ctx.output += QStringLiteral(" %1 = %2\n") .arg(sanitizeIdent(m.first)) .arg(m.second); } ctx.output += QStringLiteral("\n\n"); ctx.visiting.remove(structId); return; } bool isUnion = (kw == QStringLiteral("union")); QString baseClass = isUnion ? QStringLiteral("ctypes.Union") : QStringLiteral("ctypes.Structure"); ctx.output += QStringLiteral("class %1(%2):").arg(typeName, baseClass) + offsetComment(structSize, true) + QStringLiteral("\n"); ctx.output += QStringLiteral(" _fields_ = [\n"); emitPythonStructBody(ctx, structId, isUnion, 0); ctx.output += QStringLiteral(" ]\n\n"); ctx.visiting.remove(structId); } // ═══════════════════════════════════════════════════════════════════ // ── Reachable struct collector (for "Current + Children" scope) ── // ═══════════════════════════════════════════════════════════════════ // Walk the tree from rootId, collecting all struct IDs reachable via // named struct children and pointer references. Returns them in // dependency order (leaves first, root last). static QVector collectReachableStructs( const NodeTree& tree, const QHash>& childMap, uint64_t rootId) { QVector result; QSet visited; std::function walk = [&](uint64_t id) { if (visited.contains(id)) return; visited.insert(id); int idx = tree.indexOfId(id); if (idx < 0) return; const Node& node = tree.nodes[idx]; if (node.kind != NodeKind::Struct) return; // Walk children first so dependencies come before the parent for (int ci : childMap.value(id)) { const Node& child = tree.nodes[ci]; if (child.kind == NodeKind::Struct && !child.structTypeName.isEmpty()) walk(child.id); if ((child.kind == NodeKind::Pointer32 || child.kind == NodeKind::Pointer64) && child.refId != 0) walk(child.refId); if (child.kind == NodeKind::Array) { for (int ak : childMap.value(child.id)) if (tree.nodes[ak].kind == NodeKind::Struct) walk(tree.nodes[ak].id); } } result.append(id); }; walk(rootId); return result; } } // anonymous namespace // ── Public API ── const char* codeFormatName(CodeFormat fmt) { switch (fmt) { case CodeFormat::CppHeader: return "C/C++"; case CodeFormat::RustStruct: return "Rust"; case CodeFormat::DefineOffsets: return "#define"; case CodeFormat::CSharpStruct: return "C#"; case CodeFormat::PythonCtypes: return "Python"; default: return "C/C++"; } } const char* codeFormatFileFilter(CodeFormat fmt) { switch (fmt) { case CodeFormat::CppHeader: return "C++ Header (*.h);;All Files (*)"; case CodeFormat::RustStruct: return "Rust Source (*.rs);;All Files (*)"; case CodeFormat::DefineOffsets: return "C Header (*.h);;All Files (*)"; case CodeFormat::CSharpStruct: return "C# Source (*.cs);;All Files (*)"; case CodeFormat::PythonCtypes: return "Python Source (*.py);;All Files (*)"; default: return "All Files (*)"; } } const char* codeScopeName(CodeScope scope) { switch (scope) { case CodeScope::Current: return "Current"; case CodeScope::WithChildren: return "Current + Deps"; case CodeScope::FullSdk: return "Full SDK"; default: return "Current"; } } QString renderCpp(const NodeTree& tree, uint64_t rootStructId, const QHash* typeAliases, bool emitAsserts) { int idx = tree.indexOfId(rootStructId); if (idx < 0) return {}; const Node& root = tree.nodes[idx]; if (root.kind != NodeKind::Struct) return {}; GenContext ctx{tree, buildChildMap(tree), {}, {}, {}, {}, {}, 0, typeAliases, emitAsserts}; ctx.output += QStringLiteral("#pragma once\n\n"); emitStruct(ctx, rootStructId); return alignComments(ctx.output); } QString renderCppTree(const NodeTree& tree, uint64_t rootStructId, const QHash* typeAliases, bool emitAsserts) { int idx = tree.indexOfId(rootStructId); if (idx < 0) return {}; if (tree.nodes[idx].kind != NodeKind::Struct) return {}; auto childMap = buildChildMap(tree); GenContext ctx{tree, childMap, {}, {}, {}, {}, {}, 0, typeAliases, emitAsserts}; ctx.output += QStringLiteral("#pragma once\n\n"); for (uint64_t sid : collectReachableStructs(tree, childMap, rootStructId)) emitStruct(ctx, sid); return alignComments(ctx.output); } QString renderCppAll(const NodeTree& tree, const QHash* typeAliases, bool emitAsserts) { GenContext ctx{tree, buildChildMap(tree), {}, {}, {}, {}, {}, 0, typeAliases, emitAsserts}; ctx.output += QStringLiteral("#pragma once\n\n"); QVector roots = ctx.childMap.value(0); std::sort(roots.begin(), roots.end(), [&](int a, int b) { return tree.nodes[a].offset < tree.nodes[b].offset; }); for (int ri : roots) { if (tree.nodes[ri].kind == NodeKind::Struct) emitStruct(ctx, tree.nodes[ri].id); } return alignComments(ctx.output); } // ── Rust public API ── QString renderRust(const NodeTree& tree, uint64_t rootStructId, const QHash* typeAliases, bool emitAsserts) { int idx = tree.indexOfId(rootStructId); if (idx < 0) return {}; if (tree.nodes[idx].kind != NodeKind::Struct) return {}; GenContext ctx{tree, buildChildMap(tree), {}, {}, {}, {}, {}, 0, typeAliases, emitAsserts}; ctx.output += QStringLiteral("// Generated by Reclass 2027\n\n"); emitRustStruct(ctx, rootStructId); return alignComments(ctx.output); } QString renderRustTree(const NodeTree& tree, uint64_t rootStructId, const QHash* typeAliases, bool emitAsserts) { int idx = tree.indexOfId(rootStructId); if (idx < 0) return {}; if (tree.nodes[idx].kind != NodeKind::Struct) return {}; auto childMap = buildChildMap(tree); GenContext ctx{tree, childMap, {}, {}, {}, {}, {}, 0, typeAliases, emitAsserts}; ctx.output += QStringLiteral("// Generated by Reclass 2027\n\n"); for (uint64_t sid : collectReachableStructs(tree, childMap, rootStructId)) emitRustStruct(ctx, sid); return alignComments(ctx.output); } QString renderRustAll(const NodeTree& tree, const QHash* typeAliases, bool emitAsserts) { GenContext ctx{tree, buildChildMap(tree), {}, {}, {}, {}, {}, 0, typeAliases, emitAsserts}; ctx.output += QStringLiteral("// Generated by Reclass 2027\n\n"); QVector roots = ctx.childMap.value(0); std::sort(roots.begin(), roots.end(), [&](int a, int b) { return tree.nodes[a].offset < tree.nodes[b].offset; }); for (int ri : roots) { if (tree.nodes[ri].kind == NodeKind::Struct) emitRustStruct(ctx, tree.nodes[ri].id); } return alignComments(ctx.output); } // ── #define public API ── QString renderDefines(const NodeTree& tree, uint64_t rootStructId) { int idx = tree.indexOfId(rootStructId); if (idx < 0) return {}; if (tree.nodes[idx].kind != NodeKind::Struct) return {}; GenContext ctx{tree, buildChildMap(tree), {}, {}, {}, {}, {}, 0, nullptr, false}; ctx.output += QStringLiteral("#pragma once\n\n"); emitDefinesForStruct(ctx, rootStructId, QString(), 0); return ctx.output; } QString renderDefinesTree(const NodeTree& tree, uint64_t rootStructId) { int idx = tree.indexOfId(rootStructId); if (idx < 0) return {}; if (tree.nodes[idx].kind != NodeKind::Struct) return {}; auto childMap = buildChildMap(tree); GenContext ctx{tree, childMap, {}, {}, {}, {}, {}, 0, nullptr, false}; ctx.output += QStringLiteral("#pragma once\n\n"); for (uint64_t sid : collectReachableStructs(tree, childMap, rootStructId)) emitDefinesForStruct(ctx, sid, QString(), 0); return ctx.output; } QString renderDefinesAll(const NodeTree& tree) { GenContext ctx{tree, buildChildMap(tree), {}, {}, {}, {}, {}, 0, nullptr, false}; ctx.output += QStringLiteral("#pragma once\n\n"); QVector roots = ctx.childMap.value(0); std::sort(roots.begin(), roots.end(), [&](int a, int b) { return tree.nodes[a].offset < tree.nodes[b].offset; }); for (int ri : roots) { if (tree.nodes[ri].kind == NodeKind::Struct) emitDefinesForStruct(ctx, tree.nodes[ri].id, QString(), 0); } return ctx.output; } // ── C# public API ── QString renderCSharp(const NodeTree& tree, uint64_t rootStructId, const QHash* typeAliases, bool emitAsserts) { int idx = tree.indexOfId(rootStructId); if (idx < 0) return {}; if (tree.nodes[idx].kind != NodeKind::Struct) return {}; GenContext ctx{tree, buildChildMap(tree), {}, {}, {}, {}, {}, 0, typeAliases, emitAsserts}; ctx.output += QStringLiteral("using System.Runtime.InteropServices;\n\n"); emitCSharpStruct(ctx, rootStructId); return alignComments(ctx.output); } QString renderCSharpTree(const NodeTree& tree, uint64_t rootStructId, const QHash* typeAliases, bool emitAsserts) { int idx = tree.indexOfId(rootStructId); if (idx < 0) return {}; if (tree.nodes[idx].kind != NodeKind::Struct) return {}; auto childMap = buildChildMap(tree); GenContext ctx{tree, childMap, {}, {}, {}, {}, {}, 0, typeAliases, emitAsserts}; ctx.output += QStringLiteral("using System.Runtime.InteropServices;\n\n"); for (uint64_t sid : collectReachableStructs(tree, childMap, rootStructId)) emitCSharpStruct(ctx, sid); return alignComments(ctx.output); } QString renderCSharpAll(const NodeTree& tree, const QHash* typeAliases, bool emitAsserts) { GenContext ctx{tree, buildChildMap(tree), {}, {}, {}, {}, {}, 0, typeAliases, emitAsserts}; ctx.output += QStringLiteral("using System.Runtime.InteropServices;\n\n"); QVector roots = ctx.childMap.value(0); std::sort(roots.begin(), roots.end(), [&](int a, int b) { return tree.nodes[a].offset < tree.nodes[b].offset; }); for (int ri : roots) { if (tree.nodes[ri].kind == NodeKind::Struct) emitCSharpStruct(ctx, tree.nodes[ri].id); } return alignComments(ctx.output); } // ── Python public API ── QString renderPython(const NodeTree& tree, uint64_t rootStructId) { int idx = tree.indexOfId(rootStructId); if (idx < 0) return {}; if (tree.nodes[idx].kind != NodeKind::Struct) return {}; GenContext ctx{tree, buildChildMap(tree), {}, {}, {}, {}, {}, 0, nullptr, false}; ctx.output += QStringLiteral("import ctypes\n\n"); emitPythonStruct(ctx, rootStructId); return alignComments(ctx.output); } QString renderPythonTree(const NodeTree& tree, uint64_t rootStructId) { int idx = tree.indexOfId(rootStructId); if (idx < 0) return {}; if (tree.nodes[idx].kind != NodeKind::Struct) return {}; auto childMap = buildChildMap(tree); GenContext ctx{tree, childMap, {}, {}, {}, {}, {}, 0, nullptr, false}; ctx.output += QStringLiteral("import ctypes\n\n"); for (uint64_t sid : collectReachableStructs(tree, childMap, rootStructId)) emitPythonStruct(ctx, sid); return alignComments(ctx.output); } QString renderPythonAll(const NodeTree& tree) { GenContext ctx{tree, buildChildMap(tree), {}, {}, {}, {}, {}, 0, nullptr, false}; ctx.output += QStringLiteral("import ctypes\n\n"); QVector roots = ctx.childMap.value(0); std::sort(roots.begin(), roots.end(), [&](int a, int b) { return tree.nodes[a].offset < tree.nodes[b].offset; }); for (int ri : roots) { if (tree.nodes[ri].kind == NodeKind::Struct) emitPythonStruct(ctx, tree.nodes[ri].id); } return alignComments(ctx.output); } // ── Format dispatch ── QString renderCode(CodeFormat fmt, const NodeTree& tree, uint64_t rootStructId, const QHash* typeAliases, bool emitAsserts) { switch (fmt) { case CodeFormat::RustStruct: return renderRust(tree, rootStructId, typeAliases, emitAsserts); case CodeFormat::DefineOffsets: return renderDefines(tree, rootStructId); case CodeFormat::CSharpStruct: return renderCSharp(tree, rootStructId, typeAliases, emitAsserts); case CodeFormat::PythonCtypes: return renderPython(tree, rootStructId); default: return renderCpp(tree, rootStructId, typeAliases, emitAsserts); } } QString renderCodeTree(CodeFormat fmt, const NodeTree& tree, uint64_t rootStructId, const QHash* typeAliases, bool emitAsserts) { switch (fmt) { case CodeFormat::RustStruct: return renderRustTree(tree, rootStructId, typeAliases, emitAsserts); case CodeFormat::DefineOffsets: return renderDefinesTree(tree, rootStructId); case CodeFormat::CSharpStruct: return renderCSharpTree(tree, rootStructId, typeAliases, emitAsserts); case CodeFormat::PythonCtypes: return renderPythonTree(tree, rootStructId); default: return renderCppTree(tree, rootStructId, typeAliases, emitAsserts); } } QString renderCodeAll(CodeFormat fmt, const NodeTree& tree, const QHash* typeAliases, bool emitAsserts) { switch (fmt) { case CodeFormat::RustStruct: return renderRustAll(tree, typeAliases, emitAsserts); case CodeFormat::DefineOffsets: return renderDefinesAll(tree); case CodeFormat::CSharpStruct: return renderCSharpAll(tree, typeAliases, emitAsserts); case CodeFormat::PythonCtypes: return renderPythonAll(tree); default: return renderCppAll(tree, typeAliases, emitAsserts); } } QString renderNull(const NodeTree&, uint64_t) { return {}; } } // namespace rcx