Add support for XML files.

XML support is now implemented and should generally be working!
This library should be useable now, but I still need to set it up to
work as a NuGet package.
If there are any bugs, let me know!
This commit is contained in:
CloneTrooper1019
2019-01-30 00:36:56 -06:00
parent 5319ae72f9
commit 50561460ac
44 changed files with 1292 additions and 99 deletions

View File

@ -0,0 +1,31 @@
using System.Xml;
using Roblox.DataTypes;
namespace Roblox.XmlFormat.PropertyTokens
{
public class AxesToken : IXmlPropertyToken
{
public string Token => "Axes";
public bool ReadToken(Property prop, XmlNode token)
{
bool success = XmlPropertyTokens.ReadTokenGeneric<uint>(prop, PropertyType.Axes, token);
if (success)
{
uint value = (uint)prop.Value;
try
{
Axes axes = (Axes)value;
prop.Value = axes;
}
catch
{
success = false;
}
}
return success;
}
}
}

View File

@ -0,0 +1,23 @@
using System;
using System.Xml;
namespace Roblox.XmlFormat.PropertyTokens
{
public class BinaryStringToken : IXmlPropertyToken
{
public string Token => "BinaryString";
public bool ReadToken(Property prop, XmlNode token)
{
// BinaryStrings are encoded in base64
string base64 = token.InnerText;
prop.Type = PropertyType.String;
prop.Value = base64;
byte[] buffer = Convert.FromBase64String(base64);
prop.SetRawBuffer(buffer);
return true;
}
}
}

View File

@ -0,0 +1,14 @@
using System.Xml;
namespace Roblox.XmlFormat.PropertyTokens
{
public class BoolToken : IXmlPropertyToken
{
public string Token => "bool";
public bool ReadToken(Property prop, XmlNode token)
{
return XmlPropertyTokens.ReadTokenGeneric<bool>(prop, PropertyType.Bool, token);
}
}
}

View File

@ -0,0 +1,35 @@
using System.Xml;
using Roblox.DataTypes;
namespace Roblox.XmlFormat.PropertyTokens
{
public class BrickColorToken : IXmlPropertyToken
{
public string Token => "BrickColor";
// ^ This is a lie: The token is actually int, but that would cause a name collision.
// Since BrickColors are written as ints, the IntToken class will try to redirect
// to this handler if it believes that its representing a BrickColor.
public bool ReadToken(Property prop, XmlNode token)
{
bool success = XmlPropertyTokens.ReadTokenGeneric<int>(prop, PropertyType.BrickColor, token);
if (success)
{
int value = (int)prop.Value;
try
{
BrickColor brickColor = BrickColor.FromNumber(value);
prop.Value = brickColor;
}
catch
{
// Invalid BrickColor Id?
success = false;
}
}
return success;
}
}
}

View File

@ -0,0 +1,47 @@
using System.Xml;
using Roblox.DataTypes;
namespace Roblox.XmlFormat.PropertyTokens
{
public class CFrameToken : IXmlPropertyToken
{
public string Token => "CoordinateFrame; CFrame";
private static string[] Coords = new string[12] { "X", "Y", "Z", "R00", "R01", "R02", "R10", "R11", "R12", "R20", "R21", "R22"};
public static CFrame ReadCFrame(XmlNode token)
{
float[] components = new float[12];
for (int i = 0; i < 12; i++)
{
string key = Coords[i];
try
{
var coord = token[key];
components[i] = float.Parse(coord.InnerText);
}
catch
{
return null;
}
}
return new CFrame(components);
}
public bool ReadToken(Property property, XmlNode token)
{
CFrame result = ReadCFrame(token);
bool success = (result != null);
if (success)
{
property.Type = PropertyType.CFrame;
property.Value = result;
}
return success;
}
}
}

View File

@ -0,0 +1,50 @@
using System;
using System.Xml;
using Roblox.DataTypes;
namespace Roblox.XmlFormat.PropertyTokens
{
public class Color3Token : IXmlPropertyToken
{
public string Token => "Color3";
private string[] LegacyFields = new string[3] { "R", "G", "B" };
public bool ReadToken(Property prop, XmlNode token)
{
var color3uint8 = XmlPropertyTokens.GetHandler<Color3uint8Token>();
bool success = color3uint8.ReadToken(prop, token);
if (!success)
{
// Try the legacy technique.
float[] fields = new float[LegacyFields.Length];
for (int i = 0; i < fields.Length; i++)
{
string key = LegacyFields[i];
try
{
var coord = token[key];
fields[i] = XmlPropertyTokens.ParseFloat(coord.InnerText);
}
catch
{
return false;
}
}
float r = fields[0],
g = fields[1],
b = fields[2];
prop.Type = PropertyType.Color3;
prop.Value = new Color3(r, g, b);
success = true;
}
return success;
}
}
}

View File

@ -0,0 +1,29 @@
using System;
using System.Xml;
using Roblox.DataTypes;
namespace Roblox.XmlFormat.PropertyTokens
{
public class Color3uint8Token : IXmlPropertyToken
{
public string Token => "Color3uint8";
public bool ReadToken(Property prop, XmlNode token)
{
bool success = XmlPropertyTokens.ReadTokenGeneric<uint>(prop, PropertyType.Color3, token);
if (success)
{
uint value = (uint)prop.Value;
uint r = (value >> 16) & 0xFF;
uint g = (value >> 8) & 0xFF;
uint b = value & 0xFF;
prop.Value = Color3.fromRGB(r, g, b);
}
return success;
}
}
}

View File

@ -0,0 +1,48 @@
using System.Xml;
using Roblox.DataTypes;
namespace Roblox.XmlFormat.PropertyTokens
{
public class ColorSequenceToken : IXmlPropertyToken
{
public string Token => "ColorSequence";
public bool ReadToken(Property prop, XmlNode token)
{
string contents = token.InnerText.Trim();
string[] buffer = contents.Split(' ');
int length = buffer.Length;
bool valid = (length % 5 == 0);
if (valid)
{
try
{
ColorSequenceKeypoint[] keypoints = new ColorSequenceKeypoint[length / 5];
for (int i = 0; i < length; i += 5)
{
float Time = float.Parse(buffer[i]);
float R = float.Parse(buffer[i + 1]);
float G = float.Parse(buffer[i + 2]);
float B = float.Parse(buffer[i + 3]);
Color3 Value = new Color3(R, G, B);
keypoints[i / 5] = new ColorSequenceKeypoint(Time, Value);
}
prop.Type = PropertyType.ColorSequence;
prop.Value = new ColorSequence(keypoints);
}
catch
{
valid = false;
}
}
return valid;
}
}
}

View File

@ -0,0 +1,32 @@
using System;
using System.Xml;
namespace Roblox.XmlFormat.PropertyTokens
{
public class ContentToken : IXmlPropertyToken
{
public string Token => "Content";
public bool ReadToken(Property prop, XmlNode token)
{
string content = token.InnerText;
prop.Type = PropertyType.String;
prop.Value = content;
if (token.HasChildNodes)
{
XmlNode childNode = token.FirstChild;
string contentType = childNode.Name;
if (contentType.StartsWith("binary") || contentType == "hash")
{
// Roblox technically doesn't support this anymore, but load it anyway :P
byte[] buffer = Convert.FromBase64String(content);
prop.SetRawBuffer(buffer);
}
}
return true;
}
}
}

View File

@ -0,0 +1,14 @@
using System.Xml;
namespace Roblox.XmlFormat.PropertyTokens
{
public class DoubleToken : IXmlPropertyToken
{
public string Token => "double";
public bool ReadToken(Property prop, XmlNode token)
{
return XmlPropertyTokens.ReadTokenGeneric<double>(prop, PropertyType.Double, token);
}
}
}

View File

@ -0,0 +1,14 @@
using System.Xml;
namespace Roblox.XmlFormat.PropertyTokens
{
public class EnumToken : IXmlPropertyToken
{
public string Token => "token";
public bool ReadToken(Property prop, XmlNode token)
{
return XmlPropertyTokens.ReadTokenGeneric<uint>(prop, PropertyType.Enum, token);
}
}
}

View File

@ -0,0 +1,31 @@
using System.Xml;
using Roblox.DataTypes;
namespace Roblox.XmlFormat.PropertyTokens
{
public class FacesToken : IXmlPropertyToken
{
public string Token => "Faces";
public bool ReadToken(Property prop, XmlNode token)
{
bool success = XmlPropertyTokens.ReadTokenGeneric<uint>(prop, PropertyType.Faces, token);
if (success)
{
uint value = (uint)prop.Value;
try
{
Faces faces = (Faces)value;
prop.Value = faces;
}
catch
{
success = false;
}
}
return success;
}
}
}

View File

@ -0,0 +1,24 @@
using System.Xml;
namespace Roblox.XmlFormat.PropertyTokens
{
public class FloatToken : IXmlPropertyToken
{
public string Token => "float";
public bool ReadToken(Property prop, XmlNode token)
{
try
{
float value = XmlPropertyTokens.ParseFloat(token.InnerText);
prop.Type = PropertyType.Float;
prop.Value = value;
return true;
}
catch
{
return false;
}
}
}
}

View File

@ -0,0 +1,25 @@
using System.Xml;
namespace Roblox.XmlFormat.PropertyTokens
{
public class IntToken : IXmlPropertyToken
{
public string Token => "int";
public bool ReadToken(Property prop, XmlNode token)
{
// BrickColors are represented by ints, see if
// we can infer when they should be a BrickColor.
if (prop.Name.Contains("Color") || prop.Instance.ClassName.Contains("Color"))
{
var brickColorToken = XmlPropertyTokens.GetHandler<BrickColorToken>();
return brickColorToken.ReadToken(prop, token);
}
else
{
return XmlPropertyTokens.ReadTokenGeneric<int>(prop, PropertyType.Int, token);
}
}
}
}

View File

@ -0,0 +1,14 @@
using System.Xml;
namespace Roblox.XmlFormat.PropertyTokens
{
public class Int64Token : IXmlPropertyToken
{
public string Token => "int64";
public bool ReadToken(Property prop, XmlNode token)
{
return XmlPropertyTokens.ReadTokenGeneric<long>(prop, PropertyType.Int64, token);
}
}
}

View File

@ -0,0 +1,35 @@
using System.Xml;
using Roblox.DataTypes;
namespace Roblox.XmlFormat.PropertyTokens
{
public class NumberRangeToken : IXmlPropertyToken
{
public string Token => "NumberRange";
public bool ReadToken(Property prop, XmlNode token)
{
string contents = token.InnerText.Trim();
string[] buffer = contents.Split(' ');
bool valid = (buffer.Length == 2);
if (valid)
{
try
{
float min = float.Parse(buffer[0]);
float max = float.Parse(buffer[1]);
prop.Type = PropertyType.NumberRange;
prop.Value = new NumberRange(min, max);
}
catch
{
valid = false;
}
}
return valid;
}
}
}

View File

@ -0,0 +1,45 @@
using System.Xml;
using Roblox.DataTypes;
namespace Roblox.XmlFormat.PropertyTokens
{
public class NumberSequenceToken : IXmlPropertyToken
{
public string Token => "NumberSequence";
public bool ReadToken(Property prop, XmlNode token)
{
string contents = token.InnerText.Trim();
string[] buffer = contents.Split(' ');
int length = buffer.Length;
bool valid = (length % 3 == 0);
if (valid)
{
try
{
NumberSequenceKeypoint[] keypoints = new NumberSequenceKeypoint[length / 3];
for (int i = 0; i < length; i += 3)
{
float Time = float.Parse(buffer[ i ]);
float Value = float.Parse(buffer[i + 1]);
float Envelope = float.Parse(buffer[i + 2]);
keypoints[i / 3] = new NumberSequenceKeypoint(Time, Value, Envelope);
}
prop.Type = PropertyType.NumberSequence;
prop.Value = new NumberSequence(keypoints);
}
catch
{
valid = false;
}
}
return valid;
}
}
}

View File

@ -0,0 +1,50 @@
using System;
using System.Xml;
using Roblox.DataTypes;
namespace Roblox.XmlFormat.PropertyTokens
{
public class PhysicalPropertiesToken : IXmlPropertyToken
{
public string Token => "PhysicalProperties";
private Func<string, T> createReader<T>(Func<string, T> parse, XmlNode token) where T : struct
{
return new Func<string, T>(key =>
{
XmlElement node = token[key];
return parse(node.InnerText);
});
}
public bool ReadToken(Property prop, XmlNode token)
{
var readBool = createReader(bool.Parse, token);
var readFloat = createReader(XmlPropertyTokens.ParseFloat, token);
try
{
bool custom = readBool("CustomPhysics");
if (custom)
{
prop.Value = new PhysicalProperties
(
readFloat("Density"),
readFloat("Friction"),
readFloat("Elasticity"),
readFloat("FrictionWeight"),
readFloat("ElasticityWeight")
);
prop.Type = PropertyType.PhysicalProperties;
}
return true;
}
catch
{
return false;
}
}
}
}

View File

@ -0,0 +1,40 @@
using System.Xml;
using Roblox.DataTypes;
namespace Roblox.XmlFormat.PropertyTokens
{
public class RayToken : IXmlPropertyToken
{
public string Token => "Ray";
private static string[] Fields = new string[2] { "origin", "direction" };
public bool ReadToken(Property prop, XmlNode token)
{
Vector3[] read = new Vector3[Fields.Length];
for (int i = 0; i < read.Length; i++)
{
string field = Fields[i];
try
{
var fieldToken = token[field];
Vector3? vector3 = Vector3Token.ReadVector3(fieldToken);
read[i] = vector3.Value;
}
catch
{
return false;
}
}
Vector3 origin = read[0],
direction = read[1];
Ray ray = new Ray(origin, direction);
prop.Type = PropertyType.Ray;
prop.Value = ray;
return true;
}
}
}

View File

@ -0,0 +1,40 @@
using System.Xml;
using Roblox.DataTypes;
namespace Roblox.XmlFormat.PropertyTokens
{
public class RectToken : IXmlPropertyToken
{
public string Token => "Rect2D";
private static string[] Fields = new string[2] { "min", "max" };
public bool ReadToken(Property prop, XmlNode token)
{
Vector2[] read = new Vector2[Fields.Length];
for (int i = 0; i < read.Length; i++)
{
string field = Fields[i];
try
{
var fieldToken = token[field];
Vector2? vector2 = Vector2Token.ReadVector2(fieldToken);
read[i] = vector2.Value;
}
catch
{
return false;
}
}
Vector2 min = read[0],
max = read[1];
Rect rect = new Rect(min, max);
prop.Type = PropertyType.Rect;
prop.Value = rect;
return true;
}
}
}

View File

@ -0,0 +1,18 @@
using System.Xml;
namespace Roblox.XmlFormat.PropertyTokens
{
public class RefToken : IXmlPropertyToken
{
public string Token => "Ref";
public bool ReadToken(Property prop, XmlNode token)
{
string refId = token.InnerText;
prop.Type = PropertyType.Ref;
prop.Value = refId;
return true;
}
}
}

View File

@ -0,0 +1,22 @@
using System.Text;
using System.Xml;
namespace Roblox.XmlFormat.PropertyTokens
{
public class StringToken : IXmlPropertyToken
{
public string Token => "string; ProtectedString";
public bool ReadToken(Property prop, XmlNode token)
{
string contents = token.InnerText;
prop.Type = PropertyType.String;
prop.Value = contents;
byte[] buffer = Encoding.UTF8.GetBytes(contents);
prop.SetRawBuffer(buffer);
return true;
}
}
}

View File

@ -0,0 +1,42 @@
using System.Xml;
using Roblox.DataTypes;
namespace Roblox.XmlFormat.PropertyTokens
{
public class UDimToken : IXmlPropertyToken
{
public string Token => "UDim";
public static UDim? ReadUDim(XmlNode token, string prefix = "")
{
try
{
XmlElement scaleToken = token[prefix + 'S'];
float scale = XmlPropertyTokens.ParseFloat(scaleToken.InnerText);
XmlElement offsetToken = token[prefix + 'O'];
int offset = int.Parse(offsetToken.InnerText);
return new UDim(scale, offset);
}
catch
{
return null;
}
}
public bool ReadToken(Property property, XmlNode token)
{
UDim? result = ReadUDim(token);
bool success = (result != null);
if (success)
{
property.Type = PropertyType.UDim;
property.Value = result;
}
return success;
}
}
}

View File

@ -0,0 +1,26 @@
using System.Xml;
using Roblox.DataTypes;
namespace Roblox.XmlFormat.PropertyTokens
{
public class UDim2Token : IXmlPropertyToken
{
public string Token => "UDim2";
public bool ReadToken(Property property, XmlNode token)
{
UDim? xDim = UDimToken.ReadUDim(token, "X");
UDim? yDim = UDimToken.ReadUDim(token, "Y");
if (xDim != null && yDim != null)
{
property.Type = PropertyType.UDim2;
property.Value = new UDim2(xDim.Value, yDim.Value);
return true;
}
return false;
}
}
}

View File

@ -0,0 +1,48 @@
using System.Xml;
using Roblox.DataTypes;
namespace Roblox.XmlFormat.PropertyTokens
{
public class Vector2Token : IXmlPropertyToken
{
public string Token => "Vector2";
private static string[] Coords = new string[2] { "X", "Y" };
public static Vector2? ReadVector2(XmlNode token)
{
float[] xy = new float[2];
for (int i = 0; i < 2; i++)
{
string key = Coords[i];
try
{
var coord = token[key];
string text = coord.InnerText;
xy[i] = XmlPropertyTokens.ParseFloat(text);
}
catch
{
return null;
}
}
return new Vector2(xy);
}
public bool ReadToken(Property property, XmlNode token)
{
Vector2? result = ReadVector2(token);
bool success = (result != null);
if (success)
{
property.Type = PropertyType.Vector2;
property.Value = result;
}
return success;
}
}
}

View File

@ -0,0 +1,47 @@
using System.Xml;
using Roblox.DataTypes;
namespace Roblox.XmlFormat.PropertyTokens
{
public class Vector3Token : IXmlPropertyToken
{
public string Token => "Vector3";
private static string[] Coords = new string[3] { "X", "Y", "Z" };
public static Vector3? ReadVector3(XmlNode token)
{
float[] xyz = new float[3];
for (int i = 0; i < 3; i++)
{
string key = Coords[i];
try
{
var coord = token[key];
xyz[i] = XmlPropertyTokens.ParseFloat(coord.InnerText);
}
catch
{
return null;
}
}
return new Vector3(xyz);
}
public bool ReadToken(Property property, XmlNode token)
{
Vector3? result = ReadVector3(token);
bool success = (result != null);
if (success)
{
property.Type = PropertyType.Vector3;
property.Value = result;
}
return success;
}
}
}

View File

@ -0,0 +1,83 @@
using System;
using System.Collections.Generic;
using System.Xml;
namespace Roblox.XmlFormat
{
static class XmlDataReader
{
public static void ReadProperties(Instance instance, XmlNode propsNode)
{
if (propsNode.Name != "Properties")
throw new Exception("XmlDataReader.ReadProperties: Provided XmlNode's class should be 'Properties'!");
foreach (XmlNode propNode in propsNode.ChildNodes)
{
string propType = propNode.Name;
XmlNode propName = propNode.Attributes.GetNamedItem("name");
if (propName == null)
throw new Exception("XmlDataReader.ReadProperties: Got a property node without a 'name' attribute!");
IXmlPropertyToken tokenHandler = XmlPropertyTokens.GetHandler(propType);
if (tokenHandler != null)
{
Property prop = new Property();
prop.Name = propName.InnerText;
prop.Instance = instance;
if (!tokenHandler.ReadToken(prop, propNode))
Console.WriteLine("XmlDataReader.ReadProperties: Could not read property: " + prop.GetFullName() + '!');
instance.AddProperty(ref prop);
}
else
{
Console.WriteLine("XmlDataReader.ReadProperties: No IXmlPropertyToken found for property type: " + propType + '!');
}
}
}
public static Instance ReadInstance(XmlNode instNode, ref Dictionary<string, Instance> instances)
{
// Process the instance itself
if (instNode.Name != "Item")
throw new Exception("XmlDataReader.ReadItem: Provided XmlNode's class should be 'Item'!");
XmlNode classToken = instNode.Attributes.GetNamedItem("class");
if (classToken == null)
throw new Exception("XmlDataReader.ReadItem: Got an Item without a defined 'class' attribute!");
Instance inst = new Instance(classToken.InnerText);
// The 'reference' attribute is optional, but should be defined if a Ref property needs to link to this Instance.
XmlNode refToken = instNode.Attributes.GetNamedItem("referent");
if (refToken != null && instances != null)
{
string refId = refToken.InnerText;
if (instances.ContainsKey(refId))
throw new Exception("XmlDataReader.ReadItem: Got an Item with a duplicate 'referent' attribute!");
instances.Add(refId, inst);
}
// Process the child nodes of this instance.
foreach (XmlNode childNode in instNode.ChildNodes)
{
if (childNode.Name == "Properties")
{
ReadProperties(inst, childNode);
}
else if (childNode.Name == "Item")
{
Instance child = ReadInstance(childNode, ref instances);
child.Parent = inst;
}
}
return inst;
}
}
}

View File

@ -1,20 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
namespace Roblox.XmlFormat
{
public class RobloxXmlFile : IRobloxFile
{
private List<Instance> XmlTrunk = new List<Instance>();
public IReadOnlyList<Instance> Trunk => XmlTrunk;
public void Initialize(byte[] buffer)
{
// TODO!
}
}
}

View File

@ -0,0 +1,96 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Xml;
namespace Roblox.XmlFormat
{
public interface IXmlPropertyToken
{
string Token { get; }
bool ReadToken(Property prop, XmlNode token);
}
public static class XmlPropertyTokens
{
public static IReadOnlyDictionary<string, IXmlPropertyToken> Handlers;
static XmlPropertyTokens()
{
// Initialize the PropertyToken handler singletons.
Type IXmlPropertyToken = typeof(IXmlPropertyToken);
var handlerTypes = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(assembly => assembly.GetTypes())
.Where(type => type != IXmlPropertyToken)
.Where(type => IXmlPropertyToken.IsAssignableFrom(type));
var propTokens = handlerTypes.Select(handlerType => Activator.CreateInstance(handlerType) as IXmlPropertyToken);
var tokenHandlers = new Dictionary<string, IXmlPropertyToken>();
foreach (IXmlPropertyToken propToken in propTokens)
{
var tokens = propToken.Token.Split(';')
.Select(token => token.Trim())
.ToList();
tokens.ForEach(token => tokenHandlers.Add(token, propToken));
}
Handlers = tokenHandlers;
}
public static bool ReadTokenGeneric<T>(Property prop, PropertyType propType, XmlNode token) where T : struct
{
Type resultType = typeof(T);
TypeConverter converter = TypeDescriptor.GetConverter(resultType);
if (converter != null)
{
object result = converter.ConvertFromString(token.InnerText);
prop.Type = propType;
prop.Value = result;
return true;
}
return false;
}
public static IXmlPropertyToken GetHandler(string tokenName)
{
IXmlPropertyToken result = null;
if (Handlers.ContainsKey(tokenName))
result = Handlers[tokenName];
return result;
}
public static T GetHandler<T>() where T : IXmlPropertyToken
{
IXmlPropertyToken result = Handlers.Values
.Where(token => token is T)
.First();
return (T)result;
}
public static float ParseFloat(string value)
{
float result;
if (value == "INF")
result = float.PositiveInfinity;
else if (value == "-INF")
result = float.NegativeInfinity;
else if (value == "NAN")
result = float.NaN;
else
result = float.Parse(value);
return result;
}
}
}

View File

@ -0,0 +1,80 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
namespace Roblox.XmlFormat
{
public class XmlRobloxFile : IRobloxFile
{
// IRobloxFile
internal readonly Instance XmlContents = new Instance("Folder", "XmlRobloxFile");
public Instance Contents => XmlContents;
// Runtime Specific
public readonly XmlDocument Root = new XmlDocument();
public Dictionary<string, Instance> Instances = new Dictionary<string, Instance>();
public void ReadFile(byte[] buffer)
{
try
{
string xml = Encoding.UTF8.GetString(buffer);
Root.LoadXml(xml);
}
catch
{
throw new Exception("XmlRobloxFile: Could not read XML!");
}
XmlNode roblox = Root.FirstChild;
if (roblox != null && roblox.Name == "roblox")
{
// Verify the version we are using.
XmlNode version = roblox.Attributes.GetNamedItem("version");
int schemaVersion;
if (version == null || !int.TryParse(version.Value, out schemaVersion))
throw new Exception("XmlRobloxFile: No version number defined!");
else if (schemaVersion < 4)
throw new Exception("XmlRobloxFile: Provided version must be at least 4!");
// Process the instances.
foreach (XmlNode child in roblox.ChildNodes)
{
if (child.Name == "Item")
{
Instance item = XmlDataReader.ReadInstance(child, ref Instances);
item.Parent = XmlContents;
}
}
// Resolve references for Ref properties.
var refProps = Instances.Values
.SelectMany(inst => inst.Properties)
.Where(prop => prop.Type == PropertyType.Ref);
foreach (Property refProp in refProps)
{
string refId = refProp.Value as string;
if (Instances.ContainsKey(refId))
{
Instance refInst = Instances[refId];
refProp.Value = refInst;
}
else if (refId != "null")
{
Console.WriteLine("XmlRobloxFile: Could not resolve reference for " + refProp.GetFullName());
}
}
}
else
{
throw new Exception("XmlRobloxFile: No `roblox` tag found!");
}
}
}
}