Files
ImXui/src/XurParser.cpp
T
2026-08-27 17:15:05 -07:00

514 lines
20 KiB
C++

#include "ImXui/XurFormat.h"
#include <fstream>
#include <cstring>
#include <algorithm>
#include <iostream>
namespace ImXui {
namespace XurFormat {
XurPropertyValue::XurPropertyValue() : type(XurPropertyType_Integer), boolVal(false), intVal(0), uintVal(0), floatVal(0.0f) {}
XurPropertyValue::XurPropertyValue(bool v) : type(XurPropertyType_Bool), boolVal(v), intVal(0), uintVal(0), floatVal(0.0f) {}
XurPropertyValue::XurPropertyValue(int32_t v) : type(XurPropertyType_Integer), boolVal(false), intVal(v), uintVal(0), floatVal(0.0f) {}
XurPropertyValue::XurPropertyValue(uint32_t v) : type(XurPropertyType_Unsigned), boolVal(false), intVal(0), uintVal(v), floatVal(0.0f) {}
XurPropertyValue::XurPropertyValue(float v) : type(XurPropertyType_Float), boolVal(false), intVal(0), uintVal(0), floatVal(v) {}
XurPropertyValue::XurPropertyValue(const std::wstring& v) : type(XurPropertyType_String), boolVal(false), intVal(0), uintVal(0), floatVal(0.0f), stringVal(v) {}
XurPropertyValue::XurPropertyValue(const XurVector& v) : type(XurPropertyType_Vector), boolVal(false), intVal(0), uintVal(0), floatVal(0.0f), vectorVal(v) {}
XurPropertyValue::XurPropertyValue(const XurQuaternion& v) : type(XurPropertyType_Quaternion), boolVal(false), intVal(0), uintVal(0), floatVal(0.0f), quatVal(v) {}
XurPropertyValue::XurPropertyValue(const XurColor& v) : type(XurPropertyType_Colour), boolVal(false), intVal(0), uintVal(0), floatVal(0.0f), colorVal(v) {}
XurPropertyValue::XurPropertyValue(const XurFigure& v) : type(XurPropertyType_Custom), boolVal(false), intVal(0), uintVal(0), floatVal(0.0f), figureVal(v) {}
XurPropertyValue::XurPropertyValue(const std::vector<XurProperty>& v) : type(XurPropertyType_Object), boolVal(false), intVal(0), uintVal(0), floatVal(0.0f), compoundVal(v) {}
XurPropertyValue::XurPropertyValue(const XurPropertyValue& other) : type(other.type), boolVal(other.boolVal), intVal(other.intVal), uintVal(other.uintVal), floatVal(other.floatVal), stringVal(other.stringVal), vectorVal(other.vectorVal), quatVal(other.quatVal), colorVal(other.colorVal), figureVal(other.figureVal), compoundVal(other.compoundVal) {}
XurPropertyValue& XurPropertyValue::operator=(const XurPropertyValue& other) {
if (this != &other) {
type = other.type; boolVal = other.boolVal; intVal = other.intVal; uintVal = other.uintVal; floatVal = other.floatVal;
stringVal = other.stringVal; vectorVal = other.vectorVal; quatVal = other.quatVal; colorVal = other.colorVal;
figureVal = other.figureVal; compoundVal = other.compoundVal;
}
return *this;
}
XurPropertyValue::~XurPropertyValue() {}
static const uint8_t* s_readEnd = nullptr;
static bool s_readTruncated = false;
static bool s_maskDesync = false;
static void resetReadState(const uint8_t* end) {
s_readEnd = end;
s_readTruncated = false;
s_maskDesync = false;
}
static bool canRead(const uint8_t* p, size_t n) {
if (p == NULL || s_readEnd == NULL || p + n > s_readEnd) {
s_readTruncated = true;
return false;
}
return true;
}
static uint16_t readU16(const uint8_t*& p) {
if (!canRead(p, 2)) return 0;
uint16_t v; memcpy(&v, p, 2); p += 2; return swap16(v);
}
static int16_t readS16(const uint8_t*& p) {
return (int16_t)readU16(p);
}
static uint32_t readU32(const uint8_t*& p) {
if (!canRead(p, 4)) return 0;
uint32_t v; memcpy(&v, p, 4); p += 4; return swap32(v);
}
static int32_t readS32(const uint8_t*& p) {
return (int32_t)readU32(p);
}
static float readF32(const uint8_t*& p) {
if (!canRead(p, 4)) return 0.0f;
uint32_t v; memcpy(&v, p, 4); p += 4; return swapFloat(v);
}
static uint8_t readU8(const uint8_t*& p) {
if (!canRead(p, 1)) return 0;
return *p++;
}
static std::wstring readUTF16String(const uint8_t*& p, int16_t length) {
std::wstring result(length, L'\0');
for (int16_t i = 0; i < length; i++) {
if (!canRead(p, 2)) return result;
uint16_t ch = readU16(p);
result[i] = (wchar_t)ch;
}
return result;
}
static std::string ws2s(const std::wstring& ws) {
return std::string(ws.begin(), ws.end());
}
static std::wstring s2ws(const std::string& s) {
return std::wstring(s.begin(), s.end());
}
static bool parseStrn(const uint8_t* data, uint32_t length, std::vector<std::wstring>& strings) {
strings.clear();
strings.push_back(L"");
const uint8_t* p = data;
const uint8_t* end = data + length;
while (p + 2 <= end) {
int16_t strLen = readS16(p);
if (strLen < 0 || p + strLen * 2 > end) break;
strings.push_back(readUTF16String(p, strLen));
}
return strings.size() > 0;
}
static bool parseVect(const uint8_t* data, uint32_t length, std::vector<XurVector>& vectors) {
const uint8_t* p = data;
const uint8_t* end = data + length;
while (p + 12 <= end) {
XurVector v;
v.x = readF32(p); v.y = readF32(p); v.z = readF32(p);
vectors.push_back(v);
}
return true;
}
static bool parseQuat(const uint8_t* data, uint32_t length, std::vector<XurQuaternion>& quaternions) {
const uint8_t* p = data;
const uint8_t* end = data + length;
while (p + 16 <= end) {
XurQuaternion q;
q.x = readF32(p); q.y = readF32(p); q.z = readF32(p); q.w = readF32(p);
quaternions.push_back(q);
}
return true;
}
static bool parseCust(const uint8_t* data, uint32_t length, std::vector<XurFigure>& figures) {
const uint8_t* p = data;
const uint8_t* end = data + length;
while (p < end) {
int32_t dataLength = readS32(p);
if (dataLength < 0 || (size_t)dataLength > (size_t)(end - p)) break;
const uint8_t* figureEnd = p + dataLength;
XurFigure fig;
fig.boundingBox.x = readF32(p); fig.boundingBox.y = readF32(p);
int32_t pointCount = readS32(p);
for (int32_t i = 0; i < pointCount && p + 24 <= figureEnd; i++) {
XurBezierPoint bp;
bp.point.x = readF32(p); bp.point.y = readF32(p);
bp.controlOne.x = readF32(p); bp.controlOne.y = readF32(p);
bp.controlTwo.x = readF32(p); bp.controlTwo.y = readF32(p);
fig.points.push_back(bp);
}
figures.push_back(fig);
p = figureEnd;
}
return true;
}
static XurObject readObject(const uint8_t*& p, const uint8_t* end,
const std::vector<std::wstring>& strings,
const std::vector<XurVector>& vectors,
const std::vector<XurQuaternion>& quaternions,
const std::vector<XurFigure>& figures);
static XurPropertyValue readPropValue(const uint8_t*& p, XurPropertyType type,
const std::vector<std::wstring>& strings,
const std::vector<XurVector>& vectors,
const std::vector<XurQuaternion>& quaternions,
const std::vector<XurFigure>& figures) {
switch (type) {
case XurPropertyType_Bool:
return XurPropertyValue((bool)(readU8(p) != 0));
case XurPropertyType_Integer:
return XurPropertyValue(readS32(p));
case XurPropertyType_Unsigned:
return XurPropertyValue(readU32(p));
case XurPropertyType_Float:
return XurPropertyValue(readF32(p));
case XurPropertyType_Colour: {
XurColor c;
c.a = readU8(p); c.r = readU8(p); c.g = readU8(p); c.b = readU8(p);
return XurPropertyValue(c);
}
case XurPropertyType_String: {
int16_t idx = readS16(p);
if (idx >= 0 && idx < (int16_t)strings.size())
return XurPropertyValue(strings[idx]);
return XurPropertyValue(std::wstring(L""));
}
case XurPropertyType_Vector: {
int32_t idx = readS32(p);
if (idx >= 0 && idx < (int32_t)vectors.size())
return XurPropertyValue(vectors[idx]);
XurVector zero = {0,0,0};
return XurPropertyValue(zero);
}
case XurPropertyType_Quaternion: {
int32_t idx = readS32(p);
if (idx >= 0 && idx < (int32_t)quaternions.size())
return XurPropertyValue(quaternions[idx]);
XurQuaternion zero = {0,0,0,0};
return XurPropertyValue(zero);
}
case XurPropertyType_Custom: {
(void)readS32(p);
if (!figures.empty())
return XurPropertyValue(figures[0]);
return XurPropertyValue(XurFigure());
}
case XurPropertyType_Object: {
int16_t valuesCount = readS16(p); (void)valuesCount;
uint8_t depth = readU8(p); (void)depth;
uint8_t packedByte = readU8(p);
if (packedByte == 0) return XurPropertyValue(std::vector<XurProperty>());
int maskCount = packedByte & 0x07;
std::vector<uint8_t> masks;
for (int i = 0; i < maskCount; i++) masks.push_back(readU8(p));
std::reverse(masks.begin(), masks.end());
for (size_t mi = 0; mi < masks.size(); mi++) {
for (int bit = 0; bit < 8; bit++) {
if ((masks[mi] >> bit) & 1) {
if (!canRead(p, 4)) return XurPropertyValue(std::vector<XurProperty>());
p += 4;
}
}
}
return XurPropertyValue(std::vector<XurProperty>());
}
}
return XurPropertyValue((int32_t)0);
}
static std::vector<XurProperty> readProperties(const uint8_t*& p, const uint8_t* end,
const std::string& className,
const std::vector<std::wstring>& strings,
const std::vector<XurVector>& vectors,
const std::vector<XurQuaternion>& quaternions,
const std::vector<XurFigure>& figures) {
std::vector<XurProperty> props;
if (p + 2 > end) return props;
int16_t totalPropertyCount = readS16(p);
auto hierarchy = XuiClassRegistry::getHierarchy(className);
if (hierarchy.empty()) {
return props;
}
std::reverse(hierarchy.begin(), hierarchy.end());
for (size_t hi = 0; hi < hierarchy.size(); ++hi) {
const XuiClassInfo* cls = hierarchy[hi];
if (p >= end) break;
uint8_t packedByte = readU8(p);
if (packedByte == 0x00) continue;
if (cls->props.empty()) continue;
if ((int16_t)props.size() >= totalPropertyCount) continue;
int maskCount = packedByte & 0x07;
std::vector<uint8_t> masks;
for (int i = 0; i < maskCount && p < end; i++)
masks.push_back(readU8(p));
std::reverse(masks.begin(), masks.end());
int bitIdx = 0;
for (size_t mi = 0; mi < masks.size(); ++mi) {
uint8_t mask = masks[mi];
for (int bit = 0; bit < 8; bit++, bitIdx++) {
if (!((mask >> bit) & 1)) continue;
if (bitIdx >= (int)cls->props.size()) {
s_maskDesync = true;
bitIdx = (int)cls->props.size();
break;
}
const auto& propDef = cls->props[bitIdx];
XurProperty prop;
prop.name = propDef.name;
prop.type = propDef.type;
if (propDef.indexed) {
uint8_t indexCount = readU8(p);
prop.value = readPropValue(p, propDef.type, strings, vectors, quaternions, figures);
for (uint8_t idx = 1; idx < indexCount; idx++) {
readPropValue(p, propDef.type, strings, vectors, quaternions, figures);
}
} else {
prop.value = readPropValue(p, propDef.type, strings, vectors, quaternions, figures);
}
props.push_back(prop);
if (s_readTruncated || s_maskDesync) break;
}
if (s_readTruncated || s_maskDesync) break;
}
if (s_readTruncated || s_maskDesync) break;
}
while ((int16_t)props.size() < totalPropertyCount && p < end && !s_readTruncated && !s_maskDesync) {
XurProperty unknown;
unknown.name = "unknown";
unknown.type = XurPropertyType_Integer;
unknown.value = XurPropertyValue((int32_t)0);
props.push_back(unknown);
if (!canRead(p, 4)) break;
p += 4;
}
return props;
}
static XurNamedFrame readNamedFrame(const uint8_t*& p, const uint8_t* end,
const std::vector<std::wstring>& strings) {
XurNamedFrame nf;
if (p + 9 > end) { p = end; return nf; }
int16_t nameIdx = readS16(p);
nf.name = (nameIdx >= 0 && nameIdx < (int16_t)strings.size()) ? strings[nameIdx] : L"";
nf.keyframe = readS32(p);
nf.command = (XurNamedFrameCommand)readU8(p);
int16_t paramIdx = readS16(p);
nf.targetParameter = (paramIdx >= 0 && paramIdx < (int16_t)strings.size()) ? strings[paramIdx] : L"";
return nf;
}
static XurTimeline readTimeline(const uint8_t*& p, const uint8_t* end,
const std::vector<std::wstring>& strings,
const std::vector<XurVector>& vectors,
const std::vector<XurQuaternion>& quaternions,
const std::vector<XurFigure>& figures,
const std::string& parentClassName) {
XurTimeline tl;
if (p + 6 > end) { p = end; return tl; }
int16_t elemNameIdx = readS16(p);
tl.elementName = (elemNameIdx >= 0 && elemNameIdx < (int16_t)strings.size()) ? strings[elemNameIdx] : L"";
int32_t propDefCount = readS32(p);
std::string targetClass = parentClassName;
std::vector<XurPropertyType> animatedTypes;
for (int32_t i = 0; i < propDefCount && p < end; i++) {
uint8_t packedByte = readU8(p);
uint8_t classIndex = readU8(p);
uint8_t classDepth = packedByte & 0x7F;
for (uint8_t d = 0; d < classDepth && p < end; d++) {
readU8(p);
}
if (packedByte & 0x80) {
if (p + 4 > end) break;
readS32(p);
}
animatedTypes.push_back(XurPropertyType_Integer);
}
if (p + 4 > end) return tl;
int32_t kfCount = readS32(p);
for (int32_t k = 0; k < kfCount && p < end; k++) {
XurKeyframe kf;
if (p + 8 > end) break;
kf.time = readS32(p);
kf.interpolation = (XurInterpolationType)readU8(p);
kf.easeIn = readU8(p);
kf.easeOut = readU8(p);
kf.easeScale = readU8(p);
for (size_t ati = 0; ati < animatedTypes.size(); ++ati) {
if (p >= end) break;
XurProperty kfProp;
kfProp.name = "anim";
kfProp.type = animatedTypes[ati];
kfProp.value = readPropValue(p, animatedTypes[ati], strings, vectors, quaternions, figures);
kf.properties.push_back(kfProp);
}
tl.keyframes.push_back(kf);
}
return tl;
}
static int s_readDepth = 0;
static int s_totalObjects = 0;
static XurObject readObject(const uint8_t*& p, const uint8_t* end,
const std::vector<std::wstring>& strings,
const std::vector<XurVector>& vectors,
const std::vector<XurQuaternion>& quaternions,
const std::vector<XurFigure>& figures) {
XurObject obj;
if (p + 3 > end) { p = end; return obj; }
if (++s_readDepth > 500) { p = end; return obj; }
if (++s_totalObjects > 50000) { p = end; return obj; }
int16_t classNameIndex = readS16(p);
uint8_t flags = readU8(p);
bool idxOk = (classNameIndex >= 0 && classNameIndex < (int16_t)strings.size());
obj.className = ws2s(strings[idxOk ? classNameIndex : 0]);
if (obj.className.empty()) { s_readDepth--; return obj; }
if ((flags & 0x01) && p < end) {
obj.properties = readProperties(p, end, obj.className, strings, vectors, quaternions, figures);
}
if ((flags & 0x02) && p + 4 <= end) {
int32_t childCount = readS32(p);
if (childCount > 0 && childCount < 10000) {
for (int32_t i = 0; i < childCount && p < end; i++) {
XurObject child = readObject(p, end, strings, vectors, quaternions, figures);
if (child.className.empty()) break;
obj.children.push_back(std::move(child));
}
}
}
if ((flags & 0x04) && p < end) {
int32_t nfCount = readS32(p);
if (nfCount > 0 && nfCount < 50000) {
for (int32_t i = 0; i < nfCount && p < end; i++) {
obj.namedFrames.push_back(readNamedFrame(p, end, strings));
}
}
if (!obj.children.empty() && p + 4 <= end) {
int32_t tlCount = readS32(p);
if (tlCount > 0 && tlCount < 50000) {
for (int32_t i = 0; i < tlCount && p < end; i++) {
obj.timelines.push_back(readTimeline(p, end, strings, vectors, quaternions, figures, obj.className));
}
}
}
}
s_readDepth--;
return obj;
}
static void resetObjectTracking() {
s_readDepth = 0;
s_totalObjects = 0;
}
std::unique_ptr<XurDocument> XurParser::parse(const void* data, size_t size) {
if (!data || size < sizeof(XurHeader)) return nullptr;
resetObjectTracking();
XuiClassRegistry::initialize();
std::unique_ptr<XurDocument> doc(new XurDocument());
const uint8_t* bytes = static_cast<const uint8_t*>(data);
doc->rawData.assign(bytes, bytes + size);
memcpy(&doc->header, bytes, sizeof(XurHeader));
if (!doc->header.isValid()) return nullptr;
const uint8_t* p = bytes + sizeof(XurHeader);
const uint8_t* fileEnd = bytes + size;
if (doc->header.getFlags() & 0x01) {
if (p + sizeof(XurCountHeader) > fileEnd) return nullptr;
p += sizeof(XurCountHeader);
}
uint16_t sectionCount = doc->header.getSectionsCount();
if (sectionCount > 20) return nullptr;
std::vector<XurSectionEntry> entries;
for (uint16_t i = 0; i < sectionCount; i++) {
if (p + 12 > fileEnd) return nullptr;
XurSectionEntry entry;
memcpy(&entry, p, 12);
entry.fromBE();
entries.push_back(entry);
p += 12;
}
for (size_t ei = 0; ei < entries.size(); ++ei) {
const XurSectionEntry& entry = entries[ei];
if (entry.offset + entry.length > size) continue;
const uint8_t* sectionData = bytes + entry.offset;
resetReadState(sectionData + entry.length);
switch (entry.magic) {
case MAGIC_STRN:
parseStrn(sectionData, entry.length, doc->strings);
break;
case MAGIC_VECT:
parseVect(sectionData, entry.length, doc->vectors);
break;
case MAGIC_QUAT:
parseQuat(sectionData, entry.length, doc->quaternions);
break;
case MAGIC_CUST:
parseCust(sectionData, entry.length, doc->figures);
break;
case MAGIC_DATA: {
resetReadState(sectionData + entry.length);
doc->rootObject = readObject(sectionData, sectionData + entry.length,
doc->strings, doc->vectors,
doc->quaternions, doc->figures);
break;
}
}
}
return doc;
}
std::unique_ptr<XurDocument> XurParser::parseFromFile(const std::string& path) {
std::ifstream ifs(path, std::ios::binary | std::ios::ate);
if (!ifs) return nullptr;
std::streamsize fileSize = ifs.tellg();
ifs.seekg(0, std::ios::beg);
std::vector<char> buf(fileSize);
if (!ifs.read(buf.data(), fileSize)) return nullptr;
return parse(buf.data(), buf.size());
}
bool XurParser::validate(const XurDocument* doc) {
return doc && doc->header.isValid();
}
}
}