Large scale refactor to add class support!

Instance classes are now strongly typed with real property fields that
are derived from the JSON API Dump! This required a lot of reworking
across the board:

- Classes and Enums are auto-generated in the 'Generated' folder now.
This is done using a custom built-in plugin, which can be found in
the Plugins folder of this project.
- Property objects are now tied to .NET's reflection system. Reading
and writing from them will try to redirect into a field of the
Instance they are bound to.
- Property types that were loosely defined now have proper data types
(such as Color3uint8, Content, ProtectedString, SharedString, etc)
- Fixed an error with the CFrame directional vectors.
- The binary PRNT chunk now writes instances in child->parent order.
- Enums are now generated correctly, with up-to-date values.
- INST chunks are now referred to as 'Classes' instead of 'Types'.
- Unary operator added to Vector2 and Vector3.
- CollectionService tags can now be manipulated per-instance using
the Instance.Tags member.
- The Instance.Archivable property now works correctly.
- XML files now save/load metadata correctly.
- Cleaned up the property tokens directory.

I probably missed a few things, but that's a general overview of
everything that changed.
This commit is contained in:
CloneTrooper1019
2019-06-30 17:01:19 -05:00
parent 8e01f33d6b
commit de8df15d3f
67 changed files with 6297 additions and 667 deletions

34
XmlFormat/Tokens/Axes.cs Normal file
View File

@ -0,0 +1,34 @@
using System.Xml;
using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens
{
public class AxesToken : IXmlPropertyToken
{
public string Token => "Axes";
public bool ReadProperty(Property prop, XmlNode token)
{
uint value;
if (XmlPropertyTokens.ReadPropertyGeneric(token, out value))
{
Axes axes = (Axes)value;
prop.Value = axes;
return true;
}
return false;
}
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
XmlElement axes = doc.CreateElement("axes");
node.AppendChild(axes);
int value = prop.CastValue<int>();
axes.InnerText = value.ToInvariantString();
}
}
}

View File

@ -0,0 +1,46 @@
using System;
using System.Xml;
namespace RobloxFiles.XmlFormat.PropertyTokens
{
public class BinaryStringToken : IXmlPropertyToken
{
public string Token => "BinaryString";
public bool ReadProperty(Property prop, XmlNode token)
{
// BinaryStrings are encoded in base64
string base64 = token.InnerText.Replace("\n", "");
prop.Value = Convert.FromBase64String(base64);
prop.Type = PropertyType.String;
byte[] buffer = Convert.FromBase64String(base64);
prop.RawBuffer = buffer;
return true;
}
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
byte[] data = prop.RawBuffer;
string value = Convert.ToBase64String(data);
if (value.Length > 72)
{
string buffer = "";
while (value.Length > 72)
{
string chunk = value.Substring(0, 72);
value = value.Substring(72);
buffer += chunk + '\n';
}
value = buffer + value;
}
XmlCDataSection cdata = doc.CreateCDataSection(value);
node.AppendChild(cdata);
}
}
}

View File

@ -0,0 +1,23 @@
using System.Xml;
namespace RobloxFiles.XmlFormat.PropertyTokens
{
public class BoolToken : IXmlPropertyToken
{
public string Token => "bool";
public bool ReadProperty(Property prop, XmlNode token)
{
return XmlPropertyTokens.ReadPropertyGeneric<bool>(prop, PropertyType.Bool, token);
}
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
string boolString = prop.Value
.ToString()
.ToLower();
node.InnerText = boolString;
}
}
}

View File

@ -0,0 +1,41 @@
using System;
using System.Xml;
using RobloxFiles.DataTypes;
namespace RobloxFiles.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 ReadProperty(Property prop, XmlNode token)
{
int value;
if (XmlPropertyTokens.ReadPropertyGeneric(token, out value))
{
BrickColor brickColor = BrickColor.FromNumber(value);
prop.XmlToken = "BrickColor";
prop.Value = brickColor;
return true;
}
return false;
}
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
BrickColor value = prop.CastValue<BrickColor>();
XmlElement brickColor = doc.CreateElement("int");
brickColor.InnerText = value.Number.ToInvariantString();
brickColor.SetAttribute("name", prop.Name);
brickColor.AppendChild(node);
}
}
}

View File

@ -0,0 +1,64 @@
using System.Xml;
using RobloxFiles.DataTypes;
namespace RobloxFiles.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] = Formatting.ParseFloat(coord.InnerText);
}
catch
{
return null;
}
}
return new CFrame(components);
}
public bool ReadProperty(Property prop, XmlNode token)
{
CFrame result = ReadCFrame(token);
bool success = (result != null);
if (success)
{
prop.Type = PropertyType.CFrame;
prop.Value = result;
}
return success;
}
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
CFrame cf = prop.CastValue<CFrame>();
float[] components = cf.GetComponents();
for (int i = 0; i < 12; i++)
{
string coordName = Coords[i];
float coordValue = components[i];
XmlElement coord = doc.CreateElement(coordName);
coord.InnerText = coordValue.ToInvariantString();
node.AppendChild(coord);
}
}
}
}

View File

@ -0,0 +1,69 @@
using System;
using System.Xml;
using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens
{
public class Color3Token : IXmlPropertyToken
{
public string Token => "Color3";
private string[] Fields = new string[3] { "R", "G", "B" };
public bool ReadProperty(Property prop, XmlNode token)
{
bool success = true;
float[] fields = new float[Fields.Length];
for (int i = 0; i < fields.Length; i++)
{
string key = Fields[i];
try
{
var coord = token[key];
fields[i] = Formatting.ParseFloat(coord.InnerText);
}
catch
{
success = false;
break;
}
}
if (success)
{
float r = fields[0],
g = fields[1],
b = fields[2];
prop.Type = PropertyType.Color3;
prop.Value = new Color3(r, g, b);
}
else
{
// Try falling back to the Color3uint8 technique...
var color3uint8 = XmlPropertyTokens.GetHandler<Color3uint8Token>();
success = color3uint8.ReadProperty(prop, token);
}
return success;
}
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
Color3 color = prop.CastValue<Color3>();
float[] rgb = new float[3] { color.R, color.G, color.B };
for (int i = 0; i < 3; i++)
{
string field = Fields[i];
float value = rgb[i];
XmlElement channel = doc.CreateElement(field);
channel.InnerText = value.ToInvariantString();
node.AppendChild(channel);
}
}
}
}

View File

@ -0,0 +1,41 @@
using System.Xml;
using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens
{
public class Color3uint8Token : IXmlPropertyToken
{
public string Token => "Color3uint8";
public bool ReadProperty(Property prop, XmlNode token)
{
uint value;
if (XmlPropertyTokens.ReadPropertyGeneric(token, out value))
{
uint r = (value >> 16) & 0xFF;
uint g = (value >> 8) & 0xFF;
uint b = value & 0xFF;
Color3uint8 result = Color3.FromRGB(r, g, b);
prop.Value = result;
return true;
}
return false;
}
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
Color3uint8 color = prop.CastValue<Color3uint8>();
uint r = color.R,
g = color.G,
b = color.B;
uint rgb = (255u << 24) | (r << 16) | (g << 8) | b;
node.InnerText = rgb.ToString();
}
}
}

View File

@ -0,0 +1,54 @@
using System.Xml;
using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens
{
public class ColorSequenceToken : IXmlPropertyToken
{
public string Token => "ColorSequence";
public bool ReadProperty(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 = Formatting.ParseFloat(buffer[i]);
float R = Formatting.ParseFloat(buffer[i + 1]);
float G = Formatting.ParseFloat(buffer[i + 2]);
float B = Formatting.ParseFloat(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;
}
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
ColorSequence value = prop.CastValue<ColorSequence>();
node.InnerText = value.ToString() + ' ';
}
}
}

View File

@ -0,0 +1,66 @@
using System;
using System.Xml;
using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens
{
public class ContentToken : IXmlPropertyToken
{
public string Token => "Content";
public bool ReadProperty(Property prop, XmlNode token)
{
string data = token.InnerText;
prop.Value = new Content(data);
prop.Type = PropertyType.String;
if (token.HasChildNodes)
{
XmlNode childNode = token.FirstChild;
string contentType = childNode.Name;
if (contentType.StartsWith("binary") || contentType == "hash")
{
try
{
// Roblox technically doesn't support this anymore, but load it anyway :P
byte[] buffer = Convert.FromBase64String(data);
prop.RawBuffer = buffer;
}
catch
{
Console.WriteLine("ContentToken: Got illegal base64 string: {0}", data);
}
}
}
return true;
}
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
string content = prop.CastValue<Content>();
string type = "null";
if (prop.HasRawBuffer)
type = "binary";
else if (content.Length > 0)
type = "url";
XmlElement contentType = doc.CreateElement(type);
if (type == "binary")
{
XmlCDataSection cdata = doc.CreateCDataSection(content);
contentType.AppendChild(cdata);
}
else
{
contentType.InnerText = content;
}
node.AppendChild(contentType);
}
}
}

View File

@ -0,0 +1,20 @@
using System.Xml;
namespace RobloxFiles.XmlFormat.PropertyTokens
{
public class DoubleToken : IXmlPropertyToken
{
public string Token => "double";
public bool ReadProperty(Property prop, XmlNode token)
{
return XmlPropertyTokens.ReadPropertyGeneric<double>(prop, PropertyType.Double, token);
}
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
double value = prop.CastValue<double>();
node.InnerText = value.ToInvariantString();
}
}
}

48
XmlFormat/Tokens/Enum.cs Normal file
View File

@ -0,0 +1,48 @@
using System;
using System.Reflection;
using System.Xml;
namespace RobloxFiles.XmlFormat.PropertyTokens
{
public class EnumToken : IXmlPropertyToken
{
public string Token => "token";
public bool ReadProperty(Property prop, XmlNode token)
{
uint value;
if (XmlPropertyTokens.ReadPropertyGeneric(token, out value))
{
Instance inst = prop.Instance;
Type instType = inst?.GetType();
FieldInfo info = instType.GetField(prop.Name, Property.BindingFlags);
if (info != null)
{
Type enumType = info.FieldType;
string item = value.ToInvariantString();
prop.Type = PropertyType.Enum;
prop.Value = Enum.Parse(enumType, item);
return true;
}
}
return false;
}
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
object rawValue = prop.Value;
Type valueType = rawValue.GetType();
int signed = (int)rawValue;
uint value = (uint)signed;
node.InnerText = value.ToString();
}
}
}

34
XmlFormat/Tokens/Faces.cs Normal file
View File

@ -0,0 +1,34 @@
using System.Xml;
using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens
{
public class FacesToken : IXmlPropertyToken
{
public string Token => "Faces";
public bool ReadProperty(Property prop, XmlNode token)
{
uint value;
if (XmlPropertyTokens.ReadPropertyGeneric(token, out value))
{
Faces faces = (Faces)value;
prop.Value = faces;
return true;
}
return false;
}
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
XmlElement faces = doc.CreateElement("faces");
node.AppendChild(faces);
int value = prop.CastValue<int>();
faces.InnerText = value.ToInvariantString();
}
}
}

20
XmlFormat/Tokens/Float.cs Normal file
View File

@ -0,0 +1,20 @@
using System.Xml;
namespace RobloxFiles.XmlFormat.PropertyTokens
{
public class FloatToken : IXmlPropertyToken
{
public string Token => "float";
public bool ReadProperty(Property prop, XmlNode token)
{
return XmlPropertyTokens.ReadPropertyGeneric<float>(prop, PropertyType.Float, token);
}
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
float value = prop.CastValue<float>();
node.InnerText = value.ToInvariantString();
}
}
}

31
XmlFormat/Tokens/Int.cs Normal file
View File

@ -0,0 +1,31 @@
using System.Xml;
namespace RobloxFiles.XmlFormat.PropertyTokens
{
public class IntToken : IXmlPropertyToken
{
public string Token => "int";
public bool ReadProperty(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.ReadProperty(prop, token);
}
else
{
return XmlPropertyTokens.ReadPropertyGeneric<int>(prop, PropertyType.Int, token);
}
}
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
int value = prop.CastValue<int>();
node.InnerText = value.ToInvariantString();
}
}
}

20
XmlFormat/Tokens/Int64.cs Normal file
View File

@ -0,0 +1,20 @@
using System.Xml;
namespace RobloxFiles.XmlFormat.PropertyTokens
{
public class Int64Token : IXmlPropertyToken
{
public string Token => "int64";
public bool ReadProperty(Property prop, XmlNode token)
{
return XmlPropertyTokens.ReadPropertyGeneric<long>(prop, PropertyType.Int64, token);
}
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
long value = prop.CastValue<long>();
node.InnerText = value.ToString();
}
}
}

View File

@ -0,0 +1,42 @@
using System;
using System.Xml;
using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens
{
public class NumberRangeToken : IXmlPropertyToken
{
public string Token => "NumberRange";
public bool ReadProperty(Property prop, XmlNode token)
{
string contents = token.InnerText.Trim();
string[] buffer = contents.Split(' ');
bool valid = (buffer.Length == 2);
if (valid)
{
try
{
float min = Formatting.ParseFloat(buffer[0]);
float max = Formatting.ParseFloat(buffer[1]);
prop.Type = PropertyType.NumberRange;
prop.Value = new NumberRange(min, max);
}
catch
{
valid = false;
}
}
return valid;
}
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
NumberRange value = prop.CastValue<NumberRange>();
node.InnerText = value.ToString() + ' ';
}
}
}

View File

@ -0,0 +1,51 @@
using System.Xml;
using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens
{
public class NumberSequenceToken : IXmlPropertyToken
{
public string Token => "NumberSequence";
public bool ReadProperty(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 = Formatting.ParseFloat(buffer[ i ]);
float Value = Formatting.ParseFloat(buffer[i + 1]);
float Envelope = Formatting.ParseFloat(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;
}
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
NumberSequence value = prop.CastValue<NumberSequence>();
node.InnerText = value.ToString() + ' ';
}
}
}

View File

@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.Xml;
using RobloxFiles.DataTypes;
namespace RobloxFiles.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 ReadProperty(Property prop, XmlNode token)
{
var readBool = createReader(bool.Parse, token);
var readFloat = createReader(Formatting.ParseFloat, token);
try
{
bool custom = readBool("CustomPhysics");
prop.Type = PropertyType.PhysicalProperties;
if (custom)
{
prop.Value = new PhysicalProperties
(
readFloat("Density"),
readFloat("Friction"),
readFloat("Elasticity"),
readFloat("FrictionWeight"),
readFloat("ElasticityWeight")
);
}
return true;
}
catch
{
return false;
}
}
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
bool hasCustomPhysics = (prop.Value != null);
XmlElement customPhysics = doc.CreateElement("CustomPhysics");
customPhysics.InnerText = hasCustomPhysics
.ToString()
.ToLower();
node.AppendChild(customPhysics);
if (hasCustomPhysics)
{
var customProps = prop.CastValue<PhysicalProperties>();
var data = new Dictionary<string, float>()
{
{ "Density", customProps.Density },
{ "Friction", customProps.Friction },
{ "Elasticity", customProps.Elasticity },
{ "FrictionWeight", customProps.FrictionWeight },
{ "ElasticityWeight", customProps.ElasticityWeight }
};
foreach (string elementType in data.Keys)
{
float value = data[elementType];
XmlElement element = doc.CreateElement(elementType);
element.InnerText = value.ToInvariantString();
node.AppendChild(element);
}
}
}
}
}

View File

@ -0,0 +1,34 @@
using System.Xml;
using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens
{
public class ProtectedStringToken : IXmlPropertyToken
{
public string Token => "ProtectedString";
public bool ReadProperty(Property prop, XmlNode token)
{
ProtectedString contents = token.InnerText;
prop.Type = PropertyType.String;
prop.Value = contents;
return true;
}
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
string value = prop.CastValue<ProtectedString>();
if (value.Contains("\r") || value.Contains("\n"))
{
XmlCDataSection cdata = doc.CreateCDataSection(value);
node.AppendChild(cdata);
}
else
{
node.InnerText = value;
}
}
}
}

54
XmlFormat/Tokens/Ray.cs Normal file
View File

@ -0,0 +1,54 @@
using System;
using System.Xml;
using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens
{
public class RayToken : IXmlPropertyToken
{
public string Token => "Ray";
private static string[] Fields = new string[2] { "origin", "direction" };
public bool ReadProperty(Property prop, XmlNode token)
{
try
{
Vector3[] read = new Vector3[Fields.Length];
for (int i = 0; i < read.Length; i++)
{
string field = Fields[i];
var fieldToken = token[field];
read[i] = Vector3Token.ReadVector3(fieldToken);
}
Vector3 origin = read[0],
direction = read[1];
Ray ray = new Ray(origin, direction);
prop.Type = PropertyType.Ray;
prop.Value = ray;
return true;
}
catch
{
return false;
}
}
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
Ray ray = prop.CastValue<Ray>();
XmlElement origin = doc.CreateElement("origin");
XmlElement direction = doc.CreateElement("direction");
Vector3Token.WriteVector3(doc, origin, ray.Origin);
Vector3Token.WriteVector3(doc, direction, ray.Direction);
node.AppendChild(origin);
node.AppendChild(direction);
}
}
}

53
XmlFormat/Tokens/Rect.cs Normal file
View File

@ -0,0 +1,53 @@
using System;
using System.Xml;
using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens
{
public class RectToken : IXmlPropertyToken
{
public string Token => "Rect2D";
private static string[] Fields = new string[2] { "min", "max" };
public bool ReadProperty(Property prop, XmlNode token)
{
try
{
Vector2[] read = new Vector2[Fields.Length];
for (int i = 0; i < read.Length; i++)
{
string field = Fields[i];
var fieldToken = token[field];
read[i] = Vector2Token.ReadVector2(fieldToken);
}
Vector2 min = read[0],
max = read[1];
Rect rect = new Rect(min, max);
prop.Type = PropertyType.Rect;
prop.Value = rect;
return true;
}
catch
{
return false;
}
}
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
Rect rect = prop.CastValue<Rect>();
XmlElement min = doc.CreateElement("min");
Vector2Token.WriteVector2(doc, min, rect.Min);
node.AppendChild(min);
XmlElement max = doc.CreateElement("max");
Vector2Token.WriteVector2(doc, max, rect.Max);
node.AppendChild(max);
}
}
}

31
XmlFormat/Tokens/Ref.cs Normal file
View File

@ -0,0 +1,31 @@
using System.Xml;
namespace RobloxFiles.XmlFormat.PropertyTokens
{
public class RefToken : IXmlPropertyToken
{
public string Token => "Ref";
public bool ReadProperty(Property prop, XmlNode token)
{
string refId = token.InnerText;
prop.Type = PropertyType.Ref;
prop.XmlToken = refId;
return true;
}
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
string result = "null";
if (prop.Value != null)
{
Instance inst = prop.CastValue<Instance>();
result = inst.Referent;
}
node.InnerText = result;
}
}
}

View File

@ -0,0 +1,25 @@
using System.Xml;
using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens
{
public class SharedStringToken : IXmlPropertyToken
{
public string Token => "SharedString";
public bool ReadProperty(Property prop, XmlNode token)
{
string md5 = token.InnerText;
prop.Type = PropertyType.SharedString;
prop.Value = new SharedString(md5);
return true;
}
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
SharedString value = prop.CastValue<SharedString>();
node.InnerText = value.MD5_Key;
}
}
}

View File

@ -0,0 +1,33 @@
using System.Xml;
namespace RobloxFiles.XmlFormat.PropertyTokens
{
public class StringToken : IXmlPropertyToken
{
public string Token => "string";
public bool ReadProperty(Property prop, XmlNode token)
{
string contents = token.InnerText;
prop.Type = PropertyType.String;
prop.Value = contents;
return true;
}
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
string value = prop.Value.ToInvariantString();
if (value.Contains("\r") || value.Contains("\n"))
{
XmlCDataSection cdata = doc.CreateCDataSection(value);
node.AppendChild(cdata);
}
else
{
node.InnerText = value;
}
}
}
}

60
XmlFormat/Tokens/UDim.cs Normal file
View File

@ -0,0 +1,60 @@
using System;
using System.Xml;
using RobloxFiles.DataTypes;
namespace RobloxFiles.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 = Formatting.ParseFloat(scaleToken.InnerText);
XmlElement offsetToken = token[prefix + 'O'];
int offset = int.Parse(offsetToken.InnerText);
return new UDim(scale, offset);
}
catch
{
return null;
}
}
public static void WriteUDim(XmlDocument doc, XmlNode node, UDim value, string prefix = "")
{
XmlElement scale = doc.CreateElement(prefix + 'S');
scale.InnerText = value.Scale.ToInvariantString();
node.AppendChild(scale);
XmlElement offset = doc.CreateElement(prefix + 'O');
offset.InnerText = value.Offset.ToInvariantString();
node.AppendChild(offset);
}
public bool ReadProperty(Property property, XmlNode token)
{
UDim result = ReadUDim(token);
bool success = (result != null);
if (success)
{
property.Type = PropertyType.UDim;
property.Value = result;
}
return success;
}
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
UDim value = prop.CastValue<UDim>();
WriteUDim(doc, node, value);
}
}
}

38
XmlFormat/Tokens/UDim2.cs Normal file
View File

@ -0,0 +1,38 @@
using System;
using System.Xml;
using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens
{
public class UDim2Token : IXmlPropertyToken
{
public string Token => "UDim2";
public bool ReadProperty(Property property, XmlNode token)
{
UDim xUDim = UDimToken.ReadUDim(token, "X");
UDim yUDim = UDimToken.ReadUDim(token, "Y");
if (xUDim != null && yUDim != null)
{
property.Type = PropertyType.UDim2;
property.Value = new UDim2(xUDim, yUDim);
return true;
}
return false;
}
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
UDim2 value = prop.CastValue<UDim2>();
UDim xUDim = value.X;
UDimToken.WriteUDim(doc, node, xUDim, "X");
UDim yUDim = value.Y;
UDimToken.WriteUDim(doc, node, yUDim, "Y");
}
}
}

View File

@ -0,0 +1,65 @@
using System.Xml;
using RobloxFiles.DataTypes;
namespace RobloxFiles.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] = Formatting.ParseFloat(text);
}
catch
{
return null;
}
}
return new Vector2(xy);
}
public static void WriteVector2(XmlDocument doc, XmlNode node, Vector2 value)
{
XmlElement x = doc.CreateElement("X");
x.InnerText = value.X.ToInvariantString();
node.AppendChild(x);
XmlElement y = doc.CreateElement("Y");
y.InnerText = value.Y.ToInvariantString();
node.AppendChild(y);
}
public bool ReadProperty(Property property, XmlNode token)
{
Vector2 result = ReadVector2(token);
bool success = (result != null);
if (success)
{
property.Type = PropertyType.Vector2;
property.Value = result;
}
return success;
}
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
Vector2 value = prop.CastValue<Vector2>();
WriteVector2(doc, node, value);
}
}
}

View File

@ -0,0 +1,69 @@
using System;
using System.Xml;
using RobloxFiles.DataTypes;
namespace RobloxFiles.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] = Formatting.ParseFloat(coord.InnerText);
}
catch
{
return null;
}
}
return new Vector3(xyz);
}
public static void WriteVector3(XmlDocument doc, XmlNode node, Vector3 value)
{
XmlElement x = doc.CreateElement("X");
x.InnerText = value.X.ToInvariantString();
node.AppendChild(x);
XmlElement y = doc.CreateElement("Y");
y.InnerText = value.Y.ToInvariantString();
node.AppendChild(y);
XmlElement z = doc.CreateElement("Z");
z.InnerText = value.Z.ToInvariantString();
node.AppendChild(z);
}
public bool ReadProperty(Property property, XmlNode token)
{
Vector3 result = ReadVector3(token);
bool success = (result != null);
if (success)
{
property.Type = PropertyType.Vector3;
property.Value = result;
}
return success;
}
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
Vector3 value = prop.CastValue<Vector3>();
WriteVector3(doc, node, value);
}
}
}

View File

@ -0,0 +1,57 @@
using System.Xml;
using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens
{
public class Vector3int16Token : IXmlPropertyToken
{
public string Token => "Vector3int16";
private static string[] Coords = new string[3] { "X", "Y", "Z" };
public bool ReadProperty(Property property, XmlNode token)
{
short[] xyz = new short[3];
for (int i = 0; i < 3; i++)
{
string key = Coords[i];
try
{
var coord = token[key];
xyz[i] = short.Parse(coord.InnerText);
}
catch
{
return false;
}
}
short x = xyz[0],
y = xyz[1],
z = xyz[2];
property.Type = PropertyType.Vector3int16;
property.Value = new Vector3int16(x, y, z);
return true;
}
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
Vector3int16 value = prop.CastValue<Vector3int16>();
XmlElement x = doc.CreateElement("X");
x.InnerText = value.X.ToString();
node.AppendChild(x);
XmlElement y = doc.CreateElement("Y");
y.InnerText = value.Y.ToString();
node.AppendChild(y);
XmlElement z = doc.CreateElement("Z");
z.InnerText = value.Z.ToString();
node.AppendChild(z);
}
}
}