mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
1660 lines
70 KiB
C++
1660 lines
70 KiB
C++
#include "generator.h"
|
|
#include <QHash>
|
|
#include <QVector>
|
|
#include <QStringList>
|
|
#include <algorithm>
|
|
|
|
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<uint64_t, QVector<int>> childMap;
|
|
QSet<QString> emittedTypeNames; // struct type names already emitted
|
|
QSet<uint64_t> emittedIds; // struct node IDs already emitted
|
|
QSet<uint64_t> visiting; // cycle guard
|
|
QSet<uint64_t> forwardDeclared; // forward-declared type IDs
|
|
QString output;
|
|
int padCounter = 0;
|
|
const QHash<NodeKind, QString>* 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<int> allChildren = ctx.childMap.value(structId);
|
|
QVector<int> 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<int> 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<uint64_t, QVector<int>> buildChildMap(const NodeTree& tree) {
|
|
QHash<uint64_t, QVector<int>> 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<unsafe extern \"C\" fn()>,").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<int> allChildren = ctx.childMap.value(structId);
|
|
QVector<int> 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<int> 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<int> 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<int> allChildren = ctx.childMap.value(structId);
|
|
QVector<int> 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<int> 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<int> allChildren = ctx.childMap.value(structId);
|
|
QVector<int> 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<int> 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<uint64_t> collectReachableStructs(
|
|
const NodeTree& tree, const QHash<uint64_t, QVector<int>>& childMap,
|
|
uint64_t rootId)
|
|
{
|
|
QVector<uint64_t> result;
|
|
QSet<uint64_t> visited;
|
|
|
|
std::function<void(uint64_t)> 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<NodeKind, QString>* 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<NodeKind, QString>* 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<NodeKind, QString>* typeAliases,
|
|
bool emitAsserts) {
|
|
GenContext ctx{tree, buildChildMap(tree), {}, {}, {}, {}, {}, 0, typeAliases, emitAsserts};
|
|
|
|
ctx.output += QStringLiteral("#pragma once\n\n");
|
|
|
|
QVector<int> 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<NodeKind, QString>* 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<NodeKind, QString>* 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<NodeKind, QString>* typeAliases,
|
|
bool emitAsserts) {
|
|
GenContext ctx{tree, buildChildMap(tree), {}, {}, {}, {}, {}, 0, typeAliases, emitAsserts};
|
|
ctx.output += QStringLiteral("// Generated by Reclass 2027\n\n");
|
|
|
|
QVector<int> 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<int> 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<NodeKind, QString>* 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<NodeKind, QString>* 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<NodeKind, QString>* typeAliases,
|
|
bool emitAsserts) {
|
|
GenContext ctx{tree, buildChildMap(tree), {}, {}, {}, {}, {}, 0, typeAliases, emitAsserts};
|
|
ctx.output += QStringLiteral("using System.Runtime.InteropServices;\n\n");
|
|
|
|
QVector<int> 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<int> 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<NodeKind, QString>* 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<NodeKind, QString>* 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<NodeKind, QString>* 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
|