diff --git a/BinaryFormat/BinaryRobloxFile.cs b/BinaryFormat/BinaryRobloxFile.cs
index bd81f3f..7c231ab 100644
--- a/BinaryFormat/BinaryRobloxFile.cs
+++ b/BinaryFormat/BinaryRobloxFile.cs
@@ -100,5 +100,10 @@ namespace RobloxFiles.BinaryFormat
}
}
}
+
+ public void WriteFile(Stream stream)
+ {
+ throw new NotImplementedException("Not implemented yet!");
+ }
}
}
\ No newline at end of file
diff --git a/Interfaces/IRobloxFile.cs b/Interfaces/IRobloxFile.cs
index c82edab..b28d5c9 100644
--- a/Interfaces/IRobloxFile.cs
+++ b/Interfaces/IRobloxFile.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
@@ -12,6 +13,8 @@ namespace RobloxFiles
public interface IRobloxFile
{
Instance Contents { get; }
+
void ReadFile(byte[] buffer);
+ void WriteFile(Stream stream);
}
}
diff --git a/Interfaces/IXmlPropertyToken.cs b/Interfaces/IXmlPropertyToken.cs
index 36251f2..52ec3d8 100644
--- a/Interfaces/IXmlPropertyToken.cs
+++ b/Interfaces/IXmlPropertyToken.cs
@@ -10,6 +10,8 @@ namespace RobloxFiles.XmlFormat
public interface IXmlPropertyToken
{
string Token { get; }
- bool ReadToken(Property prop, XmlNode token);
+
+ bool ReadProperty(Property prop, XmlNode token);
+ void WriteProperty(Property prop, XmlDocument doc, XmlNode node);
}
}
diff --git a/RobloxFile.cs b/RobloxFile.cs
index ff104e4..e14b0ee 100644
--- a/RobloxFile.cs
+++ b/RobloxFile.cs
@@ -62,6 +62,11 @@ namespace RobloxFiles
}
}
+ public void WriteFile(Stream stream)
+ {
+ InnerFile.WriteFile(stream);
+ }
+
///
/// Creates a RobloxFile from a provided byte sequence that represents the file.
///
diff --git a/RobloxFileFormat.csproj b/RobloxFileFormat.csproj
index edeab34..2e4b406 100644
--- a/RobloxFileFormat.csproj
+++ b/RobloxFileFormat.csproj
@@ -97,11 +97,13 @@
+
+
diff --git a/Tree/Instance.cs b/Tree/Instance.cs
index 629bc8c..117f348 100644
--- a/Tree/Instance.cs
+++ b/Tree/Instance.cs
@@ -12,11 +12,11 @@ namespace RobloxFiles
public class Instance
{
/// The ClassName of this Instance.
- public readonly string ClassName;
+ public string ClassName;
/// A list of properties that are defined under this Instance.
public Dictionary Properties = new Dictionary();
-
+
private List Children = new List();
private Instance rawParent;
@@ -24,6 +24,8 @@ namespace RobloxFiles
public string Name => ReadProperty("Name", ClassName);
public override string ToString() => Name;
+ internal string XmlReferent;
+
/// Creates an instance using the provided ClassName.
/// The ClassName to use for this Instance.
public Instance(string className = "Instance")
@@ -292,7 +294,7 @@ namespace RobloxFiles
/// This is used during the file loading procedure.
///
/// A reference to the property that will be added.
- public void AddProperty(ref Property prop)
+ internal void AddProperty(ref Property prop)
{
Properties.Add(prop.Name, prop);
}
diff --git a/Tree/Property.cs b/Tree/Property.cs
index dd5fdbf..bc95504 100644
--- a/Tree/Property.cs
+++ b/Tree/Property.cs
@@ -33,7 +33,7 @@ namespace RobloxFiles
PhysicalProperties,
Color3uint8,
Int64,
- SharedString
+ SharedString,
}
public class Property
@@ -44,7 +44,9 @@ namespace RobloxFiles
public PropertyType Type;
public object Value;
+ public string XmlToken = "";
private byte[] RawBuffer = null;
+
public bool HasRawBuffer
{
get
diff --git a/Utility/Formatting.cs b/Utility/Formatting.cs
new file mode 100644
index 0000000..d9c680b
--- /dev/null
+++ b/Utility/Formatting.cs
@@ -0,0 +1,108 @@
+using System.Globalization;
+using System.Linq;
+
+// This global class defines extension methods to numeric types
+// where I don't want system globalization to come into play.
+
+internal static class Formatting
+{
+ private static CultureInfo invariant => CultureInfo.InvariantCulture;
+
+ public static string ToInvariantString(this float value)
+ {
+ string result;
+
+ if (float.IsPositiveInfinity(value))
+ result = "INF";
+ else if (float.IsNegativeInfinity(value))
+ result = "-INF";
+ else if (float.IsNaN(value))
+ result = "NAN";
+ else
+ result = value.ToString(invariant);
+
+ return result;
+ }
+
+ public static string ToInvariantString(this double value)
+ {
+ string result;
+
+ if (double.IsPositiveInfinity(value))
+ result = "INF";
+ else if (double.IsNegativeInfinity(value))
+ result = "-INF";
+ else if (double.IsNaN(value))
+ result = "NAN";
+ else
+ result = value.ToString(invariant);
+
+ return result;
+ }
+
+ public static string ToInvariantString(this int value)
+ {
+ return value.ToString(invariant);
+ }
+
+ public static string ToInvariantString(this object value)
+ {
+ if (value is float)
+ {
+ float f = (float)value;
+ return f.ToInvariantString();
+ }
+ else if (value is double)
+ {
+ double d = (double)value;
+ return d.ToInvariantString();
+ }
+ else if (value is int)
+ {
+ int i = (int)value;
+ return i.ToInvariantString();
+ }
+ else
+ {
+ // Unhandled
+ return value.ToString();
+ }
+ }
+
+ 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, invariant);
+
+ return result;
+ }
+
+ public static double ParseDouble(string value)
+ {
+ double result;
+
+ if (value == "INF")
+ result = double.PositiveInfinity;
+ else if (value == "-INF")
+ result = double.NegativeInfinity;
+ else if (value == "NAN")
+ result = double.NaN;
+ else
+ result = double.Parse(value, invariant);
+
+ return result;
+ }
+
+ public static int ParseInt(string s)
+ {
+ return int.Parse(s, invariant);
+ }
+}
\ No newline at end of file
diff --git a/XmlFormat/PropertyTokens/Axes.cs b/XmlFormat/PropertyTokens/Axes.cs
index 521f14b..844363e 100644
--- a/XmlFormat/PropertyTokens/Axes.cs
+++ b/XmlFormat/PropertyTokens/Axes.cs
@@ -1,4 +1,5 @@
-using System.Xml;
+using System;
+using System.Xml;
using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens
@@ -6,10 +7,9 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
public class AxesToken : IXmlPropertyToken
{
public string Token => "Axes";
-
- public bool ReadToken(Property prop, XmlNode token)
+ public bool ReadProperty(Property prop, XmlNode token)
{
- bool success = XmlPropertyTokens.ReadTokenGeneric(prop, PropertyType.Axes, token);
+ bool success = XmlPropertyTokens.ReadPropertyGeneric(prop, PropertyType.Axes, token);
if (success)
{
@@ -27,5 +27,14 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
return success;
}
+
+ public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
+ {
+ XmlElement axes = doc.CreateElement("axes");
+ node.AppendChild(axes);
+
+ int value = (int)prop.Value;
+ axes.InnerText = value.ToInvariantString();
+ }
}
}
diff --git a/XmlFormat/PropertyTokens/BinaryString.cs b/XmlFormat/PropertyTokens/BinaryString.cs
index 380c668..4ad735c 100644
--- a/XmlFormat/PropertyTokens/BinaryString.cs
+++ b/XmlFormat/PropertyTokens/BinaryString.cs
@@ -7,10 +7,10 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{
public string Token => "BinaryString";
- public bool ReadToken(Property prop, XmlNode token)
+ public bool ReadProperty(Property prop, XmlNode token)
{
// BinaryStrings are encoded in base64
- string base64 = token.InnerText;
+ string base64 = token.InnerText.Replace("\n", "");
prop.Type = PropertyType.String;
prop.Value = base64;
@@ -19,5 +19,28 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
return true;
}
+
+ public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
+ {
+ byte[] data = prop.GetRawBuffer();
+ 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);
+ }
}
}
diff --git a/XmlFormat/PropertyTokens/Boolean.cs b/XmlFormat/PropertyTokens/Boolean.cs
index 5bc9067..f353334 100644
--- a/XmlFormat/PropertyTokens/Boolean.cs
+++ b/XmlFormat/PropertyTokens/Boolean.cs
@@ -6,9 +6,18 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{
public string Token => "bool";
- public bool ReadToken(Property prop, XmlNode token)
+ public bool ReadProperty(Property prop, XmlNode token)
{
- return XmlPropertyTokens.ReadTokenGeneric(prop, PropertyType.Bool, token);
+ return XmlPropertyTokens.ReadPropertyGeneric(prop, PropertyType.Bool, token);
+ }
+
+ public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
+ {
+ string boolString = prop.Value
+ .ToString()
+ .ToLower();
+
+ node.InnerText = boolString;
}
}
}
diff --git a/XmlFormat/PropertyTokens/BrickColor.cs b/XmlFormat/PropertyTokens/BrickColor.cs
index 8af2c4b..3e811cf 100644
--- a/XmlFormat/PropertyTokens/BrickColor.cs
+++ b/XmlFormat/PropertyTokens/BrickColor.cs
@@ -1,4 +1,5 @@
-using System.Xml;
+using System;
+using System.Xml;
using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens
@@ -10,16 +11,18 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
// 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)
+ public bool ReadProperty(Property prop, XmlNode token)
{
- bool success = XmlPropertyTokens.ReadTokenGeneric(prop, PropertyType.BrickColor, token);
+ bool success = XmlPropertyTokens.ReadPropertyGeneric(prop, PropertyType.BrickColor, token);
if (success)
{
int value = (int)prop.Value;
+
try
{
BrickColor brickColor = BrickColor.FromNumber(value);
+ prop.XmlToken = "BrickColor";
prop.Value = brickColor;
}
catch
@@ -28,8 +31,19 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
success = false;
}
}
-
+
return success;
}
+
+ public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
+ {
+ BrickColor value = prop.Value as BrickColor;
+
+ XmlElement brickColor = doc.CreateElement("int");
+ brickColor.InnerText = value.Number.ToInvariantString();
+
+ brickColor.SetAttribute("name", prop.Name);
+ brickColor.AppendChild(node);
+ }
}
}
diff --git a/XmlFormat/PropertyTokens/CFrame.cs b/XmlFormat/PropertyTokens/CFrame.cs
index 9dbd500..0d46dd8 100644
--- a/XmlFormat/PropertyTokens/CFrame.cs
+++ b/XmlFormat/PropertyTokens/CFrame.cs
@@ -19,7 +19,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
try
{
var coord = token[key];
- components[i] = float.Parse(coord.InnerText);
+ components[i] = Formatting.ParseFloat(coord.InnerText);
}
catch
{
@@ -30,18 +30,35 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
return new CFrame(components);
}
- public bool ReadToken(Property property, XmlNode token)
+ public bool ReadProperty(Property prop, XmlNode token)
{
CFrame result = ReadCFrame(token);
bool success = (result != null);
if (success)
{
- property.Type = PropertyType.CFrame;
- property.Value = result;
+ prop.Type = PropertyType.CFrame;
+ prop.Value = result;
}
return success;
}
+
+ public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
+ {
+ CFrame cf = prop.Value as 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);
+ }
+ }
}
}
diff --git a/XmlFormat/PropertyTokens/Color3.cs b/XmlFormat/PropertyTokens/Color3.cs
index 4ab89d3..9ada037 100644
--- a/XmlFormat/PropertyTokens/Color3.cs
+++ b/XmlFormat/PropertyTokens/Color3.cs
@@ -9,7 +9,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
public string Token => "Color3";
private string[] Fields = new string[3] { "R", "G", "B" };
- public bool ReadToken(Property prop, XmlNode token)
+ public bool ReadProperty(Property prop, XmlNode token)
{
bool success = true;
float[] fields = new float[Fields.Length];
@@ -21,7 +21,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
try
{
var coord = token[key];
- fields[i] = XmlPropertyTokens.ParseFloat(coord.InnerText);
+ fields[i] = Formatting.ParseFloat(coord.InnerText);
}
catch
{
@@ -43,10 +43,35 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{
// Try falling back to the Color3uint8 technique...
var color3uint8 = XmlPropertyTokens.GetHandler();
- success = color3uint8.ReadToken(prop, token);
+ success = color3uint8.ReadProperty(prop, token);
}
return success;
}
+
+ public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
+ {
+ if (prop.Name == "Color3uint8")
+ {
+ var handler = XmlPropertyTokens.GetHandler();
+ handler.WriteProperty(prop, doc, node);
+ }
+ else
+ {
+ Color3 color = prop.Value as 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);
+ }
+ }
+ }
}
}
diff --git a/XmlFormat/PropertyTokens/Color3uint8.cs b/XmlFormat/PropertyTokens/Color3uint8.cs
index 74db424..c1ad0fc 100644
--- a/XmlFormat/PropertyTokens/Color3uint8.cs
+++ b/XmlFormat/PropertyTokens/Color3uint8.cs
@@ -8,9 +8,9 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{
public string Token => "Color3uint8";
- public bool ReadToken(Property prop, XmlNode token)
+ public bool ReadProperty(Property prop, XmlNode token)
{
- bool success = XmlPropertyTokens.ReadTokenGeneric(prop, PropertyType.Color3, token);
+ bool success = XmlPropertyTokens.ReadPropertyGeneric(prop, PropertyType.Color3, token);
if (success)
{
@@ -25,5 +25,17 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
return success;
}
+
+ public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
+ {
+ Color3 color = prop.Value as Color3;
+
+ uint r = (uint)(color.R * 256);
+ uint g = (uint)(color.G * 256);
+ uint b = (uint)(color.B * 256);
+
+ uint rgb = (255u << 24) | (r << 16) | (g << 8) | b;
+ node.InnerText = rgb.ToString();
+ }
}
}
diff --git a/XmlFormat/PropertyTokens/ColorSequence.cs b/XmlFormat/PropertyTokens/ColorSequence.cs
index a1e96e5..50bac61 100644
--- a/XmlFormat/PropertyTokens/ColorSequence.cs
+++ b/XmlFormat/PropertyTokens/ColorSequence.cs
@@ -7,7 +7,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{
public string Token => "ColorSequence";
- public bool ReadToken(Property prop, XmlNode token)
+ public bool ReadProperty(Property prop, XmlNode token)
{
string contents = token.InnerText.Trim();
string[] buffer = contents.Split(' ');
@@ -23,11 +23,11 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
for (int i = 0; i < length; i += 5)
{
- float Time = float.Parse(buffer[i]);
+ float Time = Formatting.ParseFloat(buffer[i]);
- float R = float.Parse(buffer[i + 1]);
- float G = float.Parse(buffer[i + 2]);
- float B = float.Parse(buffer[i + 3]);
+ 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);
@@ -44,5 +44,10 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
return valid;
}
+
+ public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
+ {
+ node.InnerText = prop.Value.ToString() + ' ';
+ }
}
}
diff --git a/XmlFormat/PropertyTokens/Content.cs b/XmlFormat/PropertyTokens/Content.cs
index 6ccecbd..2bb7e60 100644
--- a/XmlFormat/PropertyTokens/Content.cs
+++ b/XmlFormat/PropertyTokens/Content.cs
@@ -7,7 +7,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{
public string Token => "Content";
- public bool ReadToken(Property prop, XmlNode token)
+ public bool ReadProperty(Property prop, XmlNode token)
{
string content = token.InnerText;
prop.Type = PropertyType.String;
@@ -28,5 +28,21 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
return true;
}
+
+ public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
+ {
+ string content = prop.Value.ToString();
+ string type = "null";
+
+ if (prop.HasRawBuffer)
+ type = "binary";
+ else if (content.Length > 0)
+ type = "url";
+
+ XmlElement contentType = doc.CreateElement(type);
+ contentType.InnerText = content;
+
+ node.AppendChild(contentType);
+ }
}
}
diff --git a/XmlFormat/PropertyTokens/Double.cs b/XmlFormat/PropertyTokens/Double.cs
index 572dfe9..dc6face 100644
--- a/XmlFormat/PropertyTokens/Double.cs
+++ b/XmlFormat/PropertyTokens/Double.cs
@@ -6,9 +6,14 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{
public string Token => "double";
- public bool ReadToken(Property prop, XmlNode token)
+ public bool ReadProperty(Property prop, XmlNode token)
{
- return XmlPropertyTokens.ReadTokenGeneric(prop, PropertyType.Double, token);
+ return XmlPropertyTokens.ReadPropertyGeneric(prop, PropertyType.Double, token);
+ }
+
+ public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
+ {
+ node.InnerText = prop.Value.ToInvariantString();
}
}
}
diff --git a/XmlFormat/PropertyTokens/Enum.cs b/XmlFormat/PropertyTokens/Enum.cs
index dbe4224..aef6d4b 100644
--- a/XmlFormat/PropertyTokens/Enum.cs
+++ b/XmlFormat/PropertyTokens/Enum.cs
@@ -6,9 +6,14 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{
public string Token => "token";
- public bool ReadToken(Property prop, XmlNode token)
+ public bool ReadProperty(Property prop, XmlNode token)
{
- return XmlPropertyTokens.ReadTokenGeneric(prop, PropertyType.Enum, token);
+ return XmlPropertyTokens.ReadPropertyGeneric(prop, PropertyType.Enum, token);
+ }
+
+ public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
+ {
+ node.InnerText = prop.Value.ToString();
}
}
}
diff --git a/XmlFormat/PropertyTokens/Faces.cs b/XmlFormat/PropertyTokens/Faces.cs
index 426c054..2ab9963 100644
--- a/XmlFormat/PropertyTokens/Faces.cs
+++ b/XmlFormat/PropertyTokens/Faces.cs
@@ -7,9 +7,9 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{
public string Token => "Faces";
- public bool ReadToken(Property prop, XmlNode token)
+ public bool ReadProperty(Property prop, XmlNode token)
{
- bool success = XmlPropertyTokens.ReadTokenGeneric(prop, PropertyType.Faces, token);
+ bool success = XmlPropertyTokens.ReadPropertyGeneric(prop, PropertyType.Faces, token);
if (success)
{
@@ -27,5 +27,14 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
return success;
}
+
+ public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
+ {
+ XmlElement faces = doc.CreateElement("faces");
+ node.AppendChild(faces);
+
+ int value = (int)prop.Value;
+ faces.InnerText = value.ToInvariantString();
+ }
}
}
diff --git a/XmlFormat/PropertyTokens/Float.cs b/XmlFormat/PropertyTokens/Float.cs
index 045cbf7..31caa3c 100644
--- a/XmlFormat/PropertyTokens/Float.cs
+++ b/XmlFormat/PropertyTokens/Float.cs
@@ -6,19 +6,14 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{
public string Token => "float";
- public bool ReadToken(Property prop, XmlNode token)
+ public bool ReadProperty(Property prop, XmlNode token)
{
- try
- {
- float value = XmlPropertyTokens.ParseFloat(token.InnerText);
- prop.Type = PropertyType.Float;
- prop.Value = value;
- return true;
- }
- catch
- {
- return false;
- }
+ return XmlPropertyTokens.ReadPropertyGeneric(prop, PropertyType.Float, token);
+ }
+
+ public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
+ {
+ node.InnerText = prop.Value.ToInvariantString();
}
}
}
\ No newline at end of file
diff --git a/XmlFormat/PropertyTokens/Int.cs b/XmlFormat/PropertyTokens/Int.cs
index fdb86d2..c6d0d0c 100644
--- a/XmlFormat/PropertyTokens/Int.cs
+++ b/XmlFormat/PropertyTokens/Int.cs
@@ -6,7 +6,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{
public string Token => "int";
- public bool ReadToken(Property prop, XmlNode token)
+ public bool ReadProperty(Property prop, XmlNode token)
{
// BrickColors are represented by ints, see if
// we can infer when they should be a BrickColor.
@@ -14,12 +14,19 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
if (prop.Name.Contains("Color") || prop.Instance.ClassName.Contains("Color"))
{
var brickColorToken = XmlPropertyTokens.GetHandler();
- return brickColorToken.ReadToken(prop, token);
+ return brickColorToken.ReadProperty(prop, token);
}
else
{
- return XmlPropertyTokens.ReadTokenGeneric(prop, PropertyType.Int, token);
+ return XmlPropertyTokens.ReadPropertyGeneric(prop, PropertyType.Int, token);
}
+
+
+ }
+
+ public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
+ {
+ node.InnerText = prop.Value.ToInvariantString();
}
}
}
diff --git a/XmlFormat/PropertyTokens/Int64.cs b/XmlFormat/PropertyTokens/Int64.cs
index 28672fa..bdbbbf3 100644
--- a/XmlFormat/PropertyTokens/Int64.cs
+++ b/XmlFormat/PropertyTokens/Int64.cs
@@ -6,9 +6,14 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{
public string Token => "int64";
- public bool ReadToken(Property prop, XmlNode token)
+ public bool ReadProperty(Property prop, XmlNode token)
{
- return XmlPropertyTokens.ReadTokenGeneric(prop, PropertyType.Int64, token);
+ return XmlPropertyTokens.ReadPropertyGeneric(prop, PropertyType.Int64, token);
+ }
+
+ public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
+ {
+ node.InnerText = prop.Value.ToString();
}
}
}
\ No newline at end of file
diff --git a/XmlFormat/PropertyTokens/NumberRange.cs b/XmlFormat/PropertyTokens/NumberRange.cs
index ff524b1..cf5c919 100644
--- a/XmlFormat/PropertyTokens/NumberRange.cs
+++ b/XmlFormat/PropertyTokens/NumberRange.cs
@@ -1,4 +1,5 @@
-using System.Xml;
+using System;
+using System.Xml;
using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens
@@ -7,7 +8,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{
public string Token => "NumberRange";
- public bool ReadToken(Property prop, XmlNode token)
+ public bool ReadProperty(Property prop, XmlNode token)
{
string contents = token.InnerText.Trim();
string[] buffer = contents.Split(' ');
@@ -17,8 +18,8 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{
try
{
- float min = float.Parse(buffer[0]);
- float max = float.Parse(buffer[1]);
+ float min = Formatting.ParseFloat(buffer[0]);
+ float max = Formatting.ParseFloat(buffer[1]);
prop.Type = PropertyType.NumberRange;
prop.Value = new NumberRange(min, max);
@@ -31,5 +32,10 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
return valid;
}
+
+ public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
+ {
+ node.InnerText = prop.Value.ToString() + ' ';
+ }
}
}
diff --git a/XmlFormat/PropertyTokens/NumberSequence.cs b/XmlFormat/PropertyTokens/NumberSequence.cs
index aa50d69..db0183b 100644
--- a/XmlFormat/PropertyTokens/NumberSequence.cs
+++ b/XmlFormat/PropertyTokens/NumberSequence.cs
@@ -7,7 +7,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{
public string Token => "NumberSequence";
- public bool ReadToken(Property prop, XmlNode token)
+ public bool ReadProperty(Property prop, XmlNode token)
{
string contents = token.InnerText.Trim();
string[] buffer = contents.Split(' ');
@@ -23,9 +23,9 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
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]);
+ 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);
}
@@ -41,5 +41,10 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
return valid;
}
+
+ public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
+ {
+ node.InnerText = prop.Value.ToString() + ' ';
+ }
}
}
diff --git a/XmlFormat/PropertyTokens/PhysicalProperties.cs b/XmlFormat/PropertyTokens/PhysicalProperties.cs
index 05fac4f..b1ccc6a 100644
--- a/XmlFormat/PropertyTokens/PhysicalProperties.cs
+++ b/XmlFormat/PropertyTokens/PhysicalProperties.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Xml;
using RobloxFiles.DataTypes;
@@ -17,10 +18,10 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
});
}
- public bool ReadToken(Property prop, XmlNode token)
+ public bool ReadProperty(Property prop, XmlNode token)
{
var readBool = createReader(bool.Parse, token);
- var readFloat = createReader(XmlPropertyTokens.ParseFloat, token);
+ var readFloat = createReader(Formatting.ParseFloat, token);
try
{
@@ -46,5 +47,40 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
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.Value as PhysicalProperties;
+
+ var data = new Dictionary()
+ {
+ { "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);
+ }
+ }
+ }
}
}
diff --git a/XmlFormat/PropertyTokens/Ray.cs b/XmlFormat/PropertyTokens/Ray.cs
index 70061cc..ef1acfb 100644
--- a/XmlFormat/PropertyTokens/Ray.cs
+++ b/XmlFormat/PropertyTokens/Ray.cs
@@ -1,4 +1,5 @@
-using System.Xml;
+using System;
+using System.Xml;
using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens
@@ -8,7 +9,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
public string Token => "Ray";
private static string[] Fields = new string[2] { "origin", "direction" };
- public bool ReadToken(Property prop, XmlNode token)
+ public bool ReadProperty(Property prop, XmlNode token)
{
try
{
@@ -35,5 +36,18 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
return false;
}
}
+
+ public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
+ {
+ Ray ray = prop.Value as Ray;
+
+ XmlElement origin = doc.CreateElement("origin");
+ Vector3Token.WriteVector3(doc, origin, ray.Origin);
+ node.AppendChild(origin);
+
+ XmlElement direction = doc.CreateElement("direction");
+ Vector3Token.WriteVector3(doc, direction, ray.Direction);
+ node.AppendChild(direction);
+ }
}
}
diff --git a/XmlFormat/PropertyTokens/Rect.cs b/XmlFormat/PropertyTokens/Rect.cs
index a03ba7b..3b31f55 100644
--- a/XmlFormat/PropertyTokens/Rect.cs
+++ b/XmlFormat/PropertyTokens/Rect.cs
@@ -1,4 +1,5 @@
-using System.Xml;
+using System;
+using System.Xml;
using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens
@@ -8,7 +9,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
public string Token => "Rect2D";
private static string[] Fields = new string[2] { "min", "max" };
- public bool ReadToken(Property prop, XmlNode token)
+ public bool ReadProperty(Property prop, XmlNode token)
{
try
{
@@ -35,5 +36,18 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
return false;
}
}
+
+ public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
+ {
+ Rect rect = prop.Value as 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);
+ }
}
}
diff --git a/XmlFormat/PropertyTokens/Ref.cs b/XmlFormat/PropertyTokens/Ref.cs
index 09229f8..3b959d6 100644
--- a/XmlFormat/PropertyTokens/Ref.cs
+++ b/XmlFormat/PropertyTokens/Ref.cs
@@ -6,7 +6,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{
public string Token => "Ref";
- public bool ReadToken(Property prop, XmlNode token)
+ public bool ReadProperty(Property prop, XmlNode token)
{
string refId = token.InnerText;
prop.Type = PropertyType.Ref;
@@ -14,5 +14,18 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
return true;
}
+
+ public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
+ {
+ string result = "null";
+
+ if (prop.Value != null && prop.Value.ToString() != "null")
+ {
+ Instance inst = prop.Value as Instance;
+ result = inst.XmlReferent;
+ }
+
+ node.InnerText = result;
+ }
}
}
diff --git a/XmlFormat/PropertyTokens/SharedString.cs b/XmlFormat/PropertyTokens/SharedString.cs
index 6cb0140..8ef7b85 100644
--- a/XmlFormat/PropertyTokens/SharedString.cs
+++ b/XmlFormat/PropertyTokens/SharedString.cs
@@ -1,4 +1,5 @@
-using System.Text;
+using System;
+using System.Text;
using System.Xml;
namespace RobloxFiles.XmlFormat.PropertyTokens
@@ -7,7 +8,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{
public string Token => "SharedString";
- public bool ReadToken(Property prop, XmlNode token)
+ public bool ReadProperty(Property prop, XmlNode token)
{
string contents = token.InnerText;
prop.Type = PropertyType.SharedString;
@@ -15,5 +16,11 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
return true;
}
+
+ public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
+ {
+ var BinaryStringToken = XmlPropertyTokens.GetHandler();
+ BinaryStringToken.WriteProperty(prop, doc, node);
+ }
}
}
diff --git a/XmlFormat/PropertyTokens/String.cs b/XmlFormat/PropertyTokens/String.cs
index c2ee170..86bfdf3 100644
--- a/XmlFormat/PropertyTokens/String.cs
+++ b/XmlFormat/PropertyTokens/String.cs
@@ -1,4 +1,5 @@
-using System.Text;
+using System;
+using System.Text;
using System.Xml;
namespace RobloxFiles.XmlFormat.PropertyTokens
@@ -7,16 +8,28 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{
public string Token => "string; ProtectedString";
- public bool ReadToken(Property prop, XmlNode token)
+ public bool ReadProperty(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;
}
+
+ 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;
+ }
+ }
}
}
diff --git a/XmlFormat/PropertyTokens/UDim.cs b/XmlFormat/PropertyTokens/UDim.cs
index b6dd52e..1b3d97b 100644
--- a/XmlFormat/PropertyTokens/UDim.cs
+++ b/XmlFormat/PropertyTokens/UDim.cs
@@ -1,4 +1,5 @@
-using System.Xml;
+using System;
+using System.Xml;
using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens
@@ -12,7 +13,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
try
{
XmlElement scaleToken = token[prefix + 'S'];
- float scale = XmlPropertyTokens.ParseFloat(scaleToken.InnerText);
+ float scale = Formatting.ParseFloat(scaleToken.InnerText);
XmlElement offsetToken = token[prefix + 'O'];
int offset = int.Parse(offsetToken.InnerText);
@@ -25,7 +26,18 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
}
}
- public bool ReadToken(Property property, XmlNode token)
+ 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);
@@ -38,5 +50,11 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
return success;
}
+
+ public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
+ {
+ UDim value = prop.Value as UDim;
+ WriteUDim(doc, node, value);
+ }
}
}
diff --git a/XmlFormat/PropertyTokens/UDim2.cs b/XmlFormat/PropertyTokens/UDim2.cs
index cc6be0c..3affceb 100644
--- a/XmlFormat/PropertyTokens/UDim2.cs
+++ b/XmlFormat/PropertyTokens/UDim2.cs
@@ -1,4 +1,5 @@
-using System.Xml;
+using System;
+using System.Xml;
using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens
@@ -7,7 +8,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{
public string Token => "UDim2";
- public bool ReadToken(Property property, XmlNode token)
+ public bool ReadProperty(Property property, XmlNode token)
{
UDim xUDim = UDimToken.ReadUDim(token, "X");
UDim yUDim = UDimToken.ReadUDim(token, "Y");
@@ -22,5 +23,16 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
return false;
}
+
+ public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
+ {
+ UDim2 value = prop.Value as UDim2;
+
+ UDim xUDim = value.X;
+ UDimToken.WriteUDim(doc, node, xUDim, "X");
+
+ UDim yUDim = value.Y;
+ UDimToken.WriteUDim(doc, node, yUDim, "Y");
+ }
}
}
diff --git a/XmlFormat/PropertyTokens/Vector2.cs b/XmlFormat/PropertyTokens/Vector2.cs
index 86926ef..a5b13b7 100644
--- a/XmlFormat/PropertyTokens/Vector2.cs
+++ b/XmlFormat/PropertyTokens/Vector2.cs
@@ -20,7 +20,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{
var coord = token[key];
string text = coord.InnerText;
- xy[i] = XmlPropertyTokens.ParseFloat(text);
+ xy[i] = Formatting.ParseFloat(text);
}
catch
{
@@ -31,7 +31,18 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
return new Vector2(xy);
}
- public bool ReadToken(Property property, XmlNode token)
+ 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);
@@ -44,5 +55,11 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
return success;
}
+
+ public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
+ {
+ Vector2 value = prop.Value as Vector2;
+ WriteVector2(doc, node, value);
+ }
}
}
diff --git a/XmlFormat/PropertyTokens/Vector3.cs b/XmlFormat/PropertyTokens/Vector3.cs
index feda2b4..f45ee9f 100644
--- a/XmlFormat/PropertyTokens/Vector3.cs
+++ b/XmlFormat/PropertyTokens/Vector3.cs
@@ -1,4 +1,5 @@
-using System.Xml;
+using System;
+using System.Xml;
using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens
@@ -19,7 +20,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
try
{
var coord = token[key];
- xyz[i] = XmlPropertyTokens.ParseFloat(coord.InnerText);
+ xyz[i] = Formatting.ParseFloat(coord.InnerText);
}
catch
{
@@ -30,7 +31,22 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
return new Vector3(xyz);
}
- public bool ReadToken(Property property, XmlNode token)
+ 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);
@@ -43,5 +59,11 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
return success;
}
+
+ public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
+ {
+ Vector3 value = prop.Value as Vector3;
+ WriteVector3(doc, node, value);
+ }
}
}
diff --git a/XmlFormat/PropertyTokens/Vector3int16.cs b/XmlFormat/PropertyTokens/Vector3int16.cs
index 1f4e82a..2893b06 100644
--- a/XmlFormat/PropertyTokens/Vector3int16.cs
+++ b/XmlFormat/PropertyTokens/Vector3int16.cs
@@ -8,7 +8,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
public string Token => "Vector3int16";
private static string[] Coords = new string[3] { "X", "Y", "Z" };
- public bool ReadToken(Property property, XmlNode token)
+ public bool ReadProperty(Property property, XmlNode token)
{
short[] xyz = new short[3];
@@ -36,5 +36,22 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
return true;
}
+
+ public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
+ {
+ Vector3int16 value = prop.Value as 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);
+ }
}
}
diff --git a/XmlFormat/XmlDataReader.cs b/XmlFormat/XmlDataReader.cs
index da4f330..e06996d 100644
--- a/XmlFormat/XmlDataReader.cs
+++ b/XmlFormat/XmlDataReader.cs
@@ -62,8 +62,9 @@ namespace RobloxFiles.XmlFormat
Property prop = new Property();
prop.Name = propName.InnerText;
prop.Instance = instance;
+ prop.XmlToken = propType;
- if (!tokenHandler.ReadToken(prop, propNode))
+ if (!tokenHandler.ReadProperty(prop, propNode))
Console.WriteLine("Could not read property: " + prop.GetFullName() + '!');
instance.AddProperty(ref prop);
@@ -75,7 +76,7 @@ namespace RobloxFiles.XmlFormat
}
}
- public static Instance ReadInstance(XmlNode instNode, XmlRobloxFile file = null)
+ public static Instance ReadInstance(XmlNode instNode, XmlRobloxFile file)
{
var error = createErrorHandler("ReadInstance");
diff --git a/XmlFormat/XmlDataWriter.cs b/XmlFormat/XmlDataWriter.cs
new file mode 100644
index 0000000..67b8f4e
--- /dev/null
+++ b/XmlFormat/XmlDataWriter.cs
@@ -0,0 +1,194 @@
+using System;
+using System.Security.Cryptography;
+using System.Text;
+using System.Xml;
+
+using RobloxFiles.XmlFormat.PropertyTokens;
+
+namespace RobloxFiles.XmlFormat
+{
+ public static class XmlDataWriter
+ {
+ public static XmlWriterSettings Settings = new XmlWriterSettings()
+ {
+ Indent = true,
+ IndentChars = "\t",
+ NewLineChars = "\r\n",
+ Encoding = Encoding.UTF8,
+ OmitXmlDeclaration = true,
+ NamespaceHandling = NamespaceHandling.Default
+ };
+
+ private static string CreateReferent()
+ {
+ Guid referentGuid = Guid.NewGuid();
+
+ string referent = "RBX" + referentGuid
+ .ToString()
+ .ToUpper();
+
+ return referent.Replace("-", "");
+ }
+
+ private static string GetEnumName(T item) where T : struct
+ {
+ return Enum.GetName(typeof(T), item);
+ }
+
+ internal static void RecordInstances(XmlRobloxFile file, Instance inst)
+ {
+ foreach (Instance child in inst.GetChildren())
+ RecordInstances(file, child);
+
+ string referent = CreateReferent();
+ file.Instances.Add(referent, inst);
+ inst.XmlReferent = referent;
+ }
+
+ public static XmlElement CreateRobloxElement(XmlDocument doc)
+ {
+ XmlElement roblox = doc.CreateElement("roblox");
+ doc.AppendChild(roblox);
+
+ roblox.SetAttribute("xmlns:xmime", "http://www.w3.org/2005/05/xmlmime");
+ roblox.SetAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
+ roblox.SetAttribute("xsi:noNamespaceSchemaLocation", "http://www.roblox.com/roblox.xsd");
+ roblox.SetAttribute("version", "4");
+
+ XmlElement externalNull = doc.CreateElement("External");
+ roblox.AppendChild(externalNull);
+ externalNull.InnerText = "null";
+
+ XmlElement externalNil = doc.CreateElement("External");
+ roblox.AppendChild(externalNil);
+ externalNil.InnerText = "nil";
+
+ return roblox;
+ }
+
+ public static XmlNode WriteProperty(Property prop, XmlDocument doc, XmlRobloxFile file)
+ {
+ string propType = prop.XmlToken;
+
+ if (prop.XmlToken.Length == 0)
+ {
+ propType = GetEnumName(prop.Type);
+
+ switch (prop.Type)
+ {
+ case PropertyType.CFrame:
+ case PropertyType.Quaternion:
+ propType = "CoordinateFrame";
+ break;
+ case PropertyType.Enum:
+ propType = "token";
+ break;
+ case PropertyType.Rect:
+ propType = "Rect2D";
+ break;
+ case PropertyType.Int:
+ case PropertyType.Bool:
+ case PropertyType.Float:
+ case PropertyType.Int64:
+ case PropertyType.Double:
+ propType = propType.ToLower();
+ break;
+ case PropertyType.String:
+ propType = (prop.HasRawBuffer ? "BinaryString" : "string");
+ break;
+ }
+ }
+
+ IXmlPropertyToken handler = XmlPropertyTokens.GetHandler(propType);
+
+ if (handler == null)
+ {
+ Console.WriteLine("XmlDataWriter.WriteProperty: No token handler found for property type: {0}", propType);
+ return null;
+ }
+
+ XmlElement propElement = doc.CreateElement(propType);
+ propElement.SetAttribute("name", prop.Name);
+
+ XmlNode propNode = propElement;
+ handler.WriteProperty(prop, doc, propNode);
+
+ if (propNode.ParentNode != null)
+ {
+ XmlNode newNode = propNode.ParentNode;
+ newNode.RemoveChild(propNode);
+ propNode = newNode;
+ }
+
+ if (prop.Type == PropertyType.SharedString)
+ {
+ string data = prop.Value.ToString();
+ byte[] buffer = Convert.FromBase64String(data);
+
+ using (MD5 md5 = MD5.Create())
+ {
+ byte[] hash = md5.ComputeHash(buffer);
+ string key = Convert.ToBase64String(hash);
+
+ if (!file.SharedStrings.ContainsKey(key))
+ file.SharedStrings.Add(key, data);
+
+ propNode.InnerText = key;
+ }
+ }
+
+ return propNode;
+ }
+
+ public static XmlNode WriteInstance(Instance instance, XmlDocument doc, XmlRobloxFile file)
+ {
+ XmlElement instNode = doc.CreateElement("Item");
+ instNode.SetAttribute("class", instance.ClassName);
+ instNode.SetAttribute("referent", instance.XmlReferent);
+
+ XmlElement propsNode = doc.CreateElement("Properties");
+ instNode.AppendChild(propsNode);
+
+ var props = instance.Properties;
+
+ foreach (string propName in props.Keys)
+ {
+ Property prop = props[propName];
+ XmlNode propNode = WriteProperty(prop, doc, file);
+ propsNode.AppendChild(propNode);
+ }
+
+ foreach (Instance child in instance.GetChildren())
+ {
+ XmlNode childNode = WriteInstance(child, doc, file);
+ instNode.AppendChild(childNode);
+ }
+
+ return instNode;
+ }
+
+ public static XmlNode WriteSharedStrings(XmlDocument doc, XmlRobloxFile file)
+ {
+ XmlElement sharedStrings = doc.CreateElement("SharedStrings");
+
+ var binaryWriter = XmlPropertyTokens.GetHandler();
+ var bufferProp = new Property("SharedString", PropertyType.String);
+
+ foreach (string md5 in file.SharedStrings.Keys)
+ {
+ XmlElement sharedString = doc.CreateElement("SharedString");
+ sharedString.SetAttribute("md5", md5);
+
+ string data = file.SharedStrings[md5];
+ byte[] buffer = Convert.FromBase64String(data);
+
+ bufferProp.SetRawBuffer(buffer);
+ binaryWriter.WriteProperty(bufferProp, doc, sharedString);
+
+ sharedStrings.AppendChild(sharedString);
+ }
+
+ return sharedStrings;
+ }
+ }
+}
diff --git a/XmlFormat/XmlPropertyTokens.cs b/XmlFormat/XmlPropertyTokens.cs
index 570b266..ac56c20 100644
--- a/XmlFormat/XmlPropertyTokens.cs
+++ b/XmlFormat/XmlPropertyTokens.cs
@@ -4,6 +4,8 @@ using System.ComponentModel;
using System.Linq;
using System.Xml;
+using RobloxFiles;
+
namespace RobloxFiles.XmlFormat
{
public static class XmlPropertyTokens
@@ -35,17 +37,30 @@ namespace RobloxFiles.XmlFormat
Handlers = tokenHandlers;
}
- public static bool ReadTokenGeneric(Property prop, PropertyType propType, XmlNode token) where T : struct
+ public static bool ReadPropertyGeneric(Property prop, PropertyType propType, XmlNode token) where T : struct
{
try
{
- Type resultType = typeof(T);
- TypeConverter converter = TypeDescriptor.GetConverter(resultType);
+ string value = token.InnerText;
+
+ if (typeof(T) == typeof(int))
+ prop.Value = Formatting.ParseInt(value);
+ else if (typeof(T) == typeof(float))
+ prop.Value = Formatting.ParseFloat(value);
+ else if (typeof(T) == typeof(double))
+ prop.Value = Formatting.ParseDouble(value);
+
+ if (prop.Value == null)
+ {
+ Type resultType = typeof(T);
+ TypeConverter converter = TypeDescriptor.GetConverter(resultType);
+
+ object result = converter.ConvertFromString(token.InnerText);
+ prop.Value = result;
+ }
- object result = converter.ConvertFromString(token.InnerText);
prop.Type = propType;
- prop.Value = result;
-
+
return true;
}
catch
@@ -72,21 +87,5 @@ namespace RobloxFiles.XmlFormat
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;
- }
}
}
diff --git a/XmlFormat/XmlRobloxFile.cs b/XmlFormat/XmlRobloxFile.cs
index 0efe44d..6616de2 100644
--- a/XmlFormat/XmlRobloxFile.cs
+++ b/XmlFormat/XmlRobloxFile.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Text;
@@ -15,11 +16,15 @@ namespace RobloxFiles.XmlFormat
// Runtime Specific
public readonly XmlDocument Root = new XmlDocument();
+
public Dictionary Instances = new Dictionary();
public Dictionary SharedStrings = new Dictionary();
public void ReadFile(byte[] buffer)
{
+ Instances.Clear();
+ SharedStrings.Clear();
+
try
{
string xml = Encoding.UTF8.GetString(buffer);
@@ -31,7 +36,7 @@ namespace RobloxFiles.XmlFormat
}
XmlNode roblox = Root.FirstChild;
-
+
if (roblox != null && roblox.Name == "roblox")
{
// Verify the version we are using.
@@ -108,5 +113,52 @@ namespace RobloxFiles.XmlFormat
throw new Exception("XmlRobloxFile: No 'roblox' tag found!");
}
}
+
+ public void WriteFile(Stream stream)
+ {
+ XmlDocument doc = new XmlDocument();
+ XmlElement roblox = XmlDataWriter.CreateRobloxElement(doc);
+
+ Instances.Clear();
+ SharedStrings.Clear();
+
+ Instance[] topLevelItems = Contents.GetChildren();
+
+ // First, record all of the instances.
+ foreach (Instance inst in topLevelItems)
+ XmlDataWriter.RecordInstances(this, inst);
+
+ // Now append them into the document.
+ foreach (Instance inst in Contents.GetChildren())
+ {
+ XmlNode instNode = XmlDataWriter.WriteInstance(inst, doc, this);
+ roblox.AppendChild(instNode);
+ }
+
+ // Append the shared strings.
+ if (SharedStrings.Count > 0)
+ {
+ XmlNode sharedStrings = XmlDataWriter.WriteSharedStrings(doc, this);
+ roblox.AppendChild(sharedStrings);
+ }
+
+ // Write the XML file.
+ using (StringWriter buffer = new StringWriter())
+ {
+ XmlWriterSettings settings = XmlDataWriter.Settings;
+
+ using (XmlWriter xmlWriter = XmlWriter.Create(buffer, settings))
+ doc.WriteContentTo(xmlWriter);
+
+ string result = buffer.ToString()
+ .Replace("", "");
+
+ using (BinaryWriter writer = new BinaryWriter(stream))
+ {
+ byte[] data = Encoding.UTF8.GetBytes(result);
+ writer.Write(data);
+ }
+ }
+ }
}
}
\ No newline at end of file