mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
- Enum inline editing: name/value commit handling, auto-sort by value - Bitfield support in PDB import with proper container nodes - Per-member hover/selection highlighting (kMemberBit encoding) - Context menu fixes for enum/bitfield member lines - MCP pagination (limit/offset), includeMembers param, tree.search tool - MCP status bar activity indicator for tool calls - PDB anonymous type inlining: inline <unnamed-tag> types as children - Skip anonymous pointer targets to prevent root orphans - Enum import diagnostics for debugging missing enums
223 lines
8.9 KiB
C++
223 lines
8.9 KiB
C++
#include "export_reclass_xml.h"
|
|
#include <QFile>
|
|
#include <QXmlStreamWriter>
|
|
#include <QHash>
|
|
#include <QVector>
|
|
#include <algorithm>
|
|
|
|
namespace rcx {
|
|
|
|
// Reverse type map: NodeKind -> ReClassEx V2016 XML Type integer
|
|
static int xmlTypeForKind(NodeKind kind) {
|
|
switch (kind) {
|
|
case NodeKind::Struct: return 1; // ClassInstance
|
|
case NodeKind::Hex32: return 4;
|
|
case NodeKind::Hex64: return 5;
|
|
case NodeKind::Hex16: return 6;
|
|
case NodeKind::Hex8: return 7;
|
|
case NodeKind::Pointer64: return 8; // ClassPointer
|
|
case NodeKind::Pointer32: return 8;
|
|
case NodeKind::Int64: return 9;
|
|
case NodeKind::Int32: return 10;
|
|
case NodeKind::Int16: return 11;
|
|
case NodeKind::Int8: return 12;
|
|
case NodeKind::Float: return 13;
|
|
case NodeKind::Double: return 14;
|
|
case NodeKind::UInt32: return 15;
|
|
case NodeKind::UInt16: return 16;
|
|
case NodeKind::UInt8: return 17;
|
|
case NodeKind::UInt64: return 32;
|
|
case NodeKind::UTF8: return 18;
|
|
case NodeKind::UTF16: return 19;
|
|
case NodeKind::Bool: return 17; // No native bool in ReClass, map to UInt8
|
|
case NodeKind::Vec2: return 22;
|
|
case NodeKind::Vec3: return 23;
|
|
case NodeKind::Vec4: return 24;
|
|
case NodeKind::Mat4x4: return 25;
|
|
case NodeKind::Array: return 27; // ClassInstanceArray
|
|
}
|
|
return 7; // fallback to Hex8
|
|
}
|
|
|
|
static int nodeSizeForExport(const Node& node) {
|
|
switch (node.kind) {
|
|
case NodeKind::UTF8: return node.strLen;
|
|
case NodeKind::UTF16: return node.strLen * 2;
|
|
case NodeKind::Array: {
|
|
int elemSz = sizeForKind(node.elementKind);
|
|
return node.arrayLen * (elemSz > 0 ? elemSz : 0);
|
|
}
|
|
default: return sizeForKind(node.kind);
|
|
}
|
|
}
|
|
|
|
// Resolve a struct type name from a node ID
|
|
static QString resolveStructName(const NodeTree& tree, uint64_t refId) {
|
|
int idx = tree.indexOfId(refId);
|
|
if (idx < 0) return {};
|
|
const Node& ref = tree.nodes[idx];
|
|
if (!ref.structTypeName.isEmpty()) return ref.structTypeName;
|
|
return ref.name;
|
|
}
|
|
|
|
bool exportReclassXml(const NodeTree& tree, const QString& filePath, QString* errorMsg) {
|
|
if (tree.nodes.isEmpty()) {
|
|
if (errorMsg) *errorMsg = QStringLiteral("No nodes to export");
|
|
return false;
|
|
}
|
|
|
|
QFile file(filePath);
|
|
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
|
if (errorMsg) *errorMsg = QStringLiteral("Cannot open file for writing: ") + filePath;
|
|
return false;
|
|
}
|
|
|
|
// Build child map
|
|
QHash<uint64_t, QVector<int>> childMap;
|
|
for (int i = 0; i < tree.nodes.size(); i++)
|
|
childMap[tree.nodes[i].parentId].append(i);
|
|
|
|
QXmlStreamWriter xml(&file);
|
|
xml.setAutoFormatting(true);
|
|
xml.setAutoFormattingIndent(4);
|
|
xml.writeStartDocument();
|
|
|
|
xml.writeStartElement(QStringLiteral("ReClass"));
|
|
xml.writeComment(QStringLiteral("ReClassEx"));
|
|
|
|
// Get root structs
|
|
QVector<int> roots = childMap.value(0);
|
|
std::sort(roots.begin(), roots.end(), [&](int a, int b) {
|
|
return tree.nodes[a].offset < tree.nodes[b].offset;
|
|
});
|
|
|
|
int classCount = 0;
|
|
|
|
for (int ri : roots) {
|
|
const Node& root = tree.nodes[ri];
|
|
if (root.kind != NodeKind::Struct) continue;
|
|
|
|
xml.writeStartElement(QStringLiteral("Class"));
|
|
xml.writeAttribute(QStringLiteral("Name"), root.name.isEmpty() ? root.structTypeName : root.name);
|
|
xml.writeAttribute(QStringLiteral("Type"), QStringLiteral("28"));
|
|
xml.writeAttribute(QStringLiteral("Comment"), QString());
|
|
xml.writeAttribute(QStringLiteral("Offset"), QStringLiteral("0"));
|
|
xml.writeAttribute(QStringLiteral("strOffset"), QStringLiteral("0"));
|
|
xml.writeAttribute(QStringLiteral("Code"), QString());
|
|
|
|
// Get children sorted by offset
|
|
QVector<int> children = childMap.value(root.id);
|
|
std::sort(children.begin(), children.end(), [&](int a, int b) {
|
|
return tree.nodes[a].offset < tree.nodes[b].offset;
|
|
});
|
|
|
|
int i = 0;
|
|
while (i < children.size()) {
|
|
const Node& child = tree.nodes[children[i]];
|
|
|
|
// Bitfield container: export as hex node (ReClassEx has no bitfield concept)
|
|
if (child.kind == NodeKind::Struct
|
|
&& child.resolvedClassKeyword() == QStringLiteral("bitfield")) {
|
|
int sz = child.byteSize();
|
|
if (sz <= 0) sz = 4;
|
|
xml.writeStartElement(QStringLiteral("Node"));
|
|
xml.writeAttribute(QStringLiteral("Name"), child.name);
|
|
NodeKind hexKind = (sz <= 1) ? NodeKind::Hex8 : (sz <= 2) ? NodeKind::Hex16
|
|
: (sz <= 4) ? NodeKind::Hex32 : NodeKind::Hex64;
|
|
xml.writeAttribute(QStringLiteral("Type"), QString::number(xmlTypeForKind(hexKind)));
|
|
xml.writeAttribute(QStringLiteral("Size"), QString::number(sz));
|
|
xml.writeAttribute(QStringLiteral("bHidden"), QStringLiteral("false"));
|
|
xml.writeAttribute(QStringLiteral("Comment"), QStringLiteral("bitfield"));
|
|
xml.writeEndElement();
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
// Collapse consecutive hex nodes into a single Custom node (Type=21)
|
|
if (isHexNode(child.kind)) {
|
|
int runStart = child.offset;
|
|
int runEnd = child.offset + child.byteSize();
|
|
int j = i + 1;
|
|
while (j < children.size()) {
|
|
const Node& next = tree.nodes[children[j]];
|
|
if (!isHexNode(next.kind)) break;
|
|
if (next.offset < runEnd) break; // overlap
|
|
runEnd = next.offset + next.byteSize();
|
|
j++;
|
|
}
|
|
int totalSize = runEnd - runStart;
|
|
xml.writeStartElement(QStringLiteral("Node"));
|
|
// Use first hex node's name if it's a single node, otherwise generate
|
|
QString hexName = (j - i == 1 && !child.name.isEmpty()) ? child.name : QString();
|
|
xml.writeAttribute(QStringLiteral("Name"), hexName);
|
|
xml.writeAttribute(QStringLiteral("Type"), QStringLiteral("21")); // Custom
|
|
xml.writeAttribute(QStringLiteral("Size"), QString::number(totalSize));
|
|
xml.writeAttribute(QStringLiteral("bHidden"), QStringLiteral("false"));
|
|
xml.writeAttribute(QStringLiteral("Comment"), QString());
|
|
xml.writeEndElement(); // Node
|
|
i = j;
|
|
continue;
|
|
}
|
|
|
|
xml.writeStartElement(QStringLiteral("Node"));
|
|
xml.writeAttribute(QStringLiteral("Name"), child.name);
|
|
xml.writeAttribute(QStringLiteral("Type"), QString::number(xmlTypeForKind(child.kind)));
|
|
xml.writeAttribute(QStringLiteral("Size"), QString::number(nodeSizeForExport(child)));
|
|
xml.writeAttribute(QStringLiteral("bHidden"), QStringLiteral("false"));
|
|
xml.writeAttribute(QStringLiteral("Comment"), QString());
|
|
|
|
// Pointer with target
|
|
if ((child.kind == NodeKind::Pointer64 || child.kind == NodeKind::Pointer32) && child.refId != 0) {
|
|
QString target = resolveStructName(tree, child.refId);
|
|
if (!target.isEmpty())
|
|
xml.writeAttribute(QStringLiteral("Pointer"), target);
|
|
}
|
|
|
|
// Embedded struct instance
|
|
if (child.kind == NodeKind::Struct) {
|
|
QString instName = child.structTypeName.isEmpty() ? child.name : child.structTypeName;
|
|
xml.writeAttribute(QStringLiteral("Instance"), instName);
|
|
}
|
|
|
|
// Array: Total attribute and child <Array> element
|
|
if (child.kind == NodeKind::Array) {
|
|
xml.writeAttribute(QStringLiteral("Total"), QString::number(child.arrayLen));
|
|
|
|
// Resolve element type name
|
|
QString elemName;
|
|
if (child.elementKind == NodeKind::Struct && !child.structTypeName.isEmpty()) {
|
|
elemName = child.structTypeName;
|
|
} else if (child.refId != 0) {
|
|
elemName = resolveStructName(tree, child.refId);
|
|
}
|
|
if (elemName.isEmpty())
|
|
elemName = kindToString(child.elementKind);
|
|
|
|
xml.writeStartElement(QStringLiteral("Array"));
|
|
xml.writeAttribute(QStringLiteral("Name"), elemName);
|
|
xml.writeAttribute(QStringLiteral("Total"), QString::number(child.arrayLen));
|
|
xml.writeEndElement(); // Array
|
|
}
|
|
|
|
xml.writeEndElement(); // Node
|
|
i++;
|
|
}
|
|
|
|
xml.writeEndElement(); // Class
|
|
classCount++;
|
|
}
|
|
|
|
xml.writeEndElement(); // ReClass
|
|
xml.writeEndDocument();
|
|
file.close();
|
|
|
|
if (classCount == 0) {
|
|
if (errorMsg) *errorMsg = QStringLiteral("No struct classes found to export");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace rcx
|