Added support for saving XML files!

Support for binary files will be coming later.
This commit is contained in:
CloneTrooper1019 2019-05-17 07:08:06 -05:00
parent 45a84e34d0
commit 34642f5656
40 changed files with 838 additions and 113 deletions

View File

@ -100,5 +100,10 @@ namespace RobloxFiles.BinaryFormat
} }
} }
} }
public void WriteFile(Stream stream)
{
throw new NotImplementedException("Not implemented yet!");
}
} }
} }

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -12,6 +13,8 @@ namespace RobloxFiles
public interface IRobloxFile public interface IRobloxFile
{ {
Instance Contents { get; } Instance Contents { get; }
void ReadFile(byte[] buffer); void ReadFile(byte[] buffer);
void WriteFile(Stream stream);
} }
} }

View File

@ -10,6 +10,8 @@ namespace RobloxFiles.XmlFormat
public interface IXmlPropertyToken public interface IXmlPropertyToken
{ {
string Token { get; } string Token { get; }
bool ReadToken(Property prop, XmlNode token);
bool ReadProperty(Property prop, XmlNode token);
void WriteProperty(Property prop, XmlDocument doc, XmlNode node);
} }
} }

View File

@ -62,6 +62,11 @@ namespace RobloxFiles
} }
} }
public void WriteFile(Stream stream)
{
InnerFile.WriteFile(stream);
}
/// <summary> /// <summary>
/// Creates a RobloxFile from a provided byte sequence that represents the file. /// Creates a RobloxFile from a provided byte sequence that represents the file.
/// </summary> /// </summary>

View File

@ -97,11 +97,13 @@
<Compile Include="DataTypes\UDim2.cs" /> <Compile Include="DataTypes\UDim2.cs" />
<Compile Include="DataTypes\Vector2.cs" /> <Compile Include="DataTypes\Vector2.cs" />
<Compile Include="DataTypes\Vector3.cs" /> <Compile Include="DataTypes\Vector3.cs" />
<Compile Include="Utility\Formatting.cs" />
<Compile Include="Utility\MaterialInfo.cs" /> <Compile Include="Utility\MaterialInfo.cs" />
<Compile Include="Utility\Quaternion.cs" /> <Compile Include="Utility\Quaternion.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="XmlFormat\PropertyTokens\SharedString.cs" /> <Compile Include="XmlFormat\PropertyTokens\SharedString.cs" />
<Compile Include="XmlFormat\PropertyTokens\Vector3int16.cs" /> <Compile Include="XmlFormat\PropertyTokens\Vector3int16.cs" />
<Compile Include="XmlFormat\XmlDataWriter.cs" />
<Compile Include="XmlFormat\XmlPropertyTokens.cs" /> <Compile Include="XmlFormat\XmlPropertyTokens.cs" />
<Compile Include="XmlFormat\XmlDataReader.cs" /> <Compile Include="XmlFormat\XmlDataReader.cs" />
<Compile Include="XmlFormat\XmlRobloxFile.cs" /> <Compile Include="XmlFormat\XmlRobloxFile.cs" />

View File

@ -12,11 +12,11 @@ namespace RobloxFiles
public class Instance public class Instance
{ {
/// <summary>The ClassName of this Instance.</summary> /// <summary>The ClassName of this Instance.</summary>
public readonly string ClassName; public string ClassName;
/// <summary>A list of properties that are defined under this Instance.</summary> /// <summary>A list of properties that are defined under this Instance.</summary>
public Dictionary<string, Property> Properties = new Dictionary<string, Property>(); public Dictionary<string, Property> Properties = new Dictionary<string, Property>();
private List<Instance> Children = new List<Instance>(); private List<Instance> Children = new List<Instance>();
private Instance rawParent; private Instance rawParent;
@ -24,6 +24,8 @@ namespace RobloxFiles
public string Name => ReadProperty("Name", ClassName); public string Name => ReadProperty("Name", ClassName);
public override string ToString() => Name; public override string ToString() => Name;
internal string XmlReferent;
/// <summary>Creates an instance using the provided ClassName.</summary> /// <summary>Creates an instance using the provided ClassName.</summary>
/// <param name="className">The ClassName to use for this Instance.</param> /// <param name="className">The ClassName to use for this Instance.</param>
public Instance(string className = "Instance") public Instance(string className = "Instance")
@ -292,7 +294,7 @@ namespace RobloxFiles
/// This is used during the file loading procedure. /// This is used during the file loading procedure.
/// </summary> /// </summary>
/// <param name="prop">A reference to the property that will be added.</param> /// <param name="prop">A reference to the property that will be added.</param>
public void AddProperty(ref Property prop) internal void AddProperty(ref Property prop)
{ {
Properties.Add(prop.Name, prop); Properties.Add(prop.Name, prop);
} }

View File

@ -33,7 +33,7 @@ namespace RobloxFiles
PhysicalProperties, PhysicalProperties,
Color3uint8, Color3uint8,
Int64, Int64,
SharedString SharedString,
} }
public class Property public class Property
@ -44,7 +44,9 @@ namespace RobloxFiles
public PropertyType Type; public PropertyType Type;
public object Value; public object Value;
public string XmlToken = "";
private byte[] RawBuffer = null; private byte[] RawBuffer = null;
public bool HasRawBuffer public bool HasRawBuffer
{ {
get get

108
Utility/Formatting.cs Normal file
View File

@ -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);
}
}

View File

@ -1,4 +1,5 @@
using System.Xml; using System;
using System.Xml;
using RobloxFiles.DataTypes; using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens namespace RobloxFiles.XmlFormat.PropertyTokens
@ -6,10 +7,9 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
public class AxesToken : IXmlPropertyToken public class AxesToken : IXmlPropertyToken
{ {
public string Token => "Axes"; public string Token => "Axes";
public bool ReadProperty(Property prop, XmlNode token)
public bool ReadToken(Property prop, XmlNode token)
{ {
bool success = XmlPropertyTokens.ReadTokenGeneric<uint>(prop, PropertyType.Axes, token); bool success = XmlPropertyTokens.ReadPropertyGeneric<uint>(prop, PropertyType.Axes, token);
if (success) if (success)
{ {
@ -27,5 +27,14 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
return success; 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();
}
} }
} }

View File

@ -7,10 +7,10 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{ {
public string Token => "BinaryString"; public string Token => "BinaryString";
public bool ReadToken(Property prop, XmlNode token) public bool ReadProperty(Property prop, XmlNode token)
{ {
// BinaryStrings are encoded in base64 // BinaryStrings are encoded in base64
string base64 = token.InnerText; string base64 = token.InnerText.Replace("\n", "");
prop.Type = PropertyType.String; prop.Type = PropertyType.String;
prop.Value = base64; prop.Value = base64;
@ -19,5 +19,28 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
return true; 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);
}
} }
} }

View File

@ -6,9 +6,18 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{ {
public string Token => "bool"; public string Token => "bool";
public bool ReadToken(Property prop, XmlNode token) public bool ReadProperty(Property prop, XmlNode token)
{ {
return XmlPropertyTokens.ReadTokenGeneric<bool>(prop, PropertyType.Bool, 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

@ -1,4 +1,5 @@
using System.Xml; using System;
using System.Xml;
using RobloxFiles.DataTypes; using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens 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 // Since BrickColors are written as ints, the IntToken class will try to redirect
// to this handler if it believes that its representing a BrickColor. // 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<int>(prop, PropertyType.BrickColor, token); bool success = XmlPropertyTokens.ReadPropertyGeneric<int>(prop, PropertyType.BrickColor, token);
if (success) if (success)
{ {
int value = (int)prop.Value; int value = (int)prop.Value;
try try
{ {
BrickColor brickColor = BrickColor.FromNumber(value); BrickColor brickColor = BrickColor.FromNumber(value);
prop.XmlToken = "BrickColor";
prop.Value = brickColor; prop.Value = brickColor;
} }
catch catch
@ -28,8 +31,19 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
success = false; success = false;
} }
} }
return success; 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);
}
} }
} }

View File

@ -19,7 +19,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
try try
{ {
var coord = token[key]; var coord = token[key];
components[i] = float.Parse(coord.InnerText); components[i] = Formatting.ParseFloat(coord.InnerText);
} }
catch catch
{ {
@ -30,18 +30,35 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
return new CFrame(components); return new CFrame(components);
} }
public bool ReadToken(Property property, XmlNode token) public bool ReadProperty(Property prop, XmlNode token)
{ {
CFrame result = ReadCFrame(token); CFrame result = ReadCFrame(token);
bool success = (result != null); bool success = (result != null);
if (success) if (success)
{ {
property.Type = PropertyType.CFrame; prop.Type = PropertyType.CFrame;
property.Value = result; prop.Value = result;
} }
return success; 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);
}
}
} }
} }

View File

@ -9,7 +9,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
public string Token => "Color3"; public string Token => "Color3";
private string[] Fields = new string[3] { "R", "G", "B" }; 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; bool success = true;
float[] fields = new float[Fields.Length]; float[] fields = new float[Fields.Length];
@ -21,7 +21,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
try try
{ {
var coord = token[key]; var coord = token[key];
fields[i] = XmlPropertyTokens.ParseFloat(coord.InnerText); fields[i] = Formatting.ParseFloat(coord.InnerText);
} }
catch catch
{ {
@ -43,10 +43,35 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{ {
// Try falling back to the Color3uint8 technique... // Try falling back to the Color3uint8 technique...
var color3uint8 = XmlPropertyTokens.GetHandler<Color3uint8Token>(); var color3uint8 = XmlPropertyTokens.GetHandler<Color3uint8Token>();
success = color3uint8.ReadToken(prop, token); success = color3uint8.ReadProperty(prop, token);
} }
return success; return success;
} }
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
if (prop.Name == "Color3uint8")
{
var handler = XmlPropertyTokens.GetHandler<Color3uint8Token>();
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);
}
}
}
} }
} }

View File

@ -8,9 +8,9 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{ {
public string Token => "Color3uint8"; public string Token => "Color3uint8";
public bool ReadToken(Property prop, XmlNode token) public bool ReadProperty(Property prop, XmlNode token)
{ {
bool success = XmlPropertyTokens.ReadTokenGeneric<uint>(prop, PropertyType.Color3, token); bool success = XmlPropertyTokens.ReadPropertyGeneric<uint>(prop, PropertyType.Color3, token);
if (success) if (success)
{ {
@ -25,5 +25,17 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
return success; 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();
}
} }
} }

View File

@ -7,7 +7,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{ {
public string Token => "ColorSequence"; public string Token => "ColorSequence";
public bool ReadToken(Property prop, XmlNode token) public bool ReadProperty(Property prop, XmlNode token)
{ {
string contents = token.InnerText.Trim(); string contents = token.InnerText.Trim();
string[] buffer = contents.Split(' '); string[] buffer = contents.Split(' ');
@ -23,11 +23,11 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
for (int i = 0; i < length; i += 5) 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 R = Formatting.ParseFloat(buffer[i + 1]);
float G = float.Parse(buffer[i + 2]); float G = Formatting.ParseFloat(buffer[i + 2]);
float B = float.Parse(buffer[i + 3]); float B = Formatting.ParseFloat(buffer[i + 3]);
Color3 Value = new Color3(R, G, B); Color3 Value = new Color3(R, G, B);
keypoints[i / 5] = new ColorSequenceKeypoint(Time, Value); keypoints[i / 5] = new ColorSequenceKeypoint(Time, Value);
@ -44,5 +44,10 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
return valid; return valid;
} }
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
node.InnerText = prop.Value.ToString() + ' ';
}
} }
} }

View File

@ -7,7 +7,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{ {
public string Token => "Content"; public string Token => "Content";
public bool ReadToken(Property prop, XmlNode token) public bool ReadProperty(Property prop, XmlNode token)
{ {
string content = token.InnerText; string content = token.InnerText;
prop.Type = PropertyType.String; prop.Type = PropertyType.String;
@ -28,5 +28,21 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
return true; 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);
}
} }
} }

View File

@ -6,9 +6,14 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{ {
public string Token => "double"; public string Token => "double";
public bool ReadToken(Property prop, XmlNode token) public bool ReadProperty(Property prop, XmlNode token)
{ {
return XmlPropertyTokens.ReadTokenGeneric<double>(prop, PropertyType.Double, token); return XmlPropertyTokens.ReadPropertyGeneric<double>(prop, PropertyType.Double, token);
}
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
node.InnerText = prop.Value.ToInvariantString();
} }
} }
} }

View File

@ -6,9 +6,14 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{ {
public string Token => "token"; public string Token => "token";
public bool ReadToken(Property prop, XmlNode token) public bool ReadProperty(Property prop, XmlNode token)
{ {
return XmlPropertyTokens.ReadTokenGeneric<uint>(prop, PropertyType.Enum, token); return XmlPropertyTokens.ReadPropertyGeneric<uint>(prop, PropertyType.Enum, token);
}
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
node.InnerText = prop.Value.ToString();
} }
} }
} }

View File

@ -7,9 +7,9 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{ {
public string Token => "Faces"; public string Token => "Faces";
public bool ReadToken(Property prop, XmlNode token) public bool ReadProperty(Property prop, XmlNode token)
{ {
bool success = XmlPropertyTokens.ReadTokenGeneric<uint>(prop, PropertyType.Faces, token); bool success = XmlPropertyTokens.ReadPropertyGeneric<uint>(prop, PropertyType.Faces, token);
if (success) if (success)
{ {
@ -27,5 +27,14 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
return success; 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();
}
} }
} }

View File

@ -6,19 +6,14 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{ {
public string Token => "float"; public string Token => "float";
public bool ReadToken(Property prop, XmlNode token) public bool ReadProperty(Property prop, XmlNode token)
{ {
try return XmlPropertyTokens.ReadPropertyGeneric<float>(prop, PropertyType.Float, token);
{ }
float value = XmlPropertyTokens.ParseFloat(token.InnerText);
prop.Type = PropertyType.Float; public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
prop.Value = value; {
return true; node.InnerText = prop.Value.ToInvariantString();
}
catch
{
return false;
}
} }
} }
} }

View File

@ -6,7 +6,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{ {
public string Token => "int"; 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 // BrickColors are represented by ints, see if
// we can infer when they should be a BrickColor. // 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")) if (prop.Name.Contains("Color") || prop.Instance.ClassName.Contains("Color"))
{ {
var brickColorToken = XmlPropertyTokens.GetHandler<BrickColorToken>(); var brickColorToken = XmlPropertyTokens.GetHandler<BrickColorToken>();
return brickColorToken.ReadToken(prop, token); return brickColorToken.ReadProperty(prop, token);
} }
else else
{ {
return XmlPropertyTokens.ReadTokenGeneric<int>(prop, PropertyType.Int, token); return XmlPropertyTokens.ReadPropertyGeneric<int>(prop, PropertyType.Int, token);
} }
}
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
node.InnerText = prop.Value.ToInvariantString();
} }
} }
} }

View File

@ -6,9 +6,14 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{ {
public string Token => "int64"; public string Token => "int64";
public bool ReadToken(Property prop, XmlNode token) public bool ReadProperty(Property prop, XmlNode token)
{ {
return XmlPropertyTokens.ReadTokenGeneric<long>(prop, PropertyType.Int64, token); return XmlPropertyTokens.ReadPropertyGeneric<long>(prop, PropertyType.Int64, token);
}
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
node.InnerText = prop.Value.ToString();
} }
} }
} }

View File

@ -1,4 +1,5 @@
using System.Xml; using System;
using System.Xml;
using RobloxFiles.DataTypes; using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens namespace RobloxFiles.XmlFormat.PropertyTokens
@ -7,7 +8,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{ {
public string Token => "NumberRange"; public string Token => "NumberRange";
public bool ReadToken(Property prop, XmlNode token) public bool ReadProperty(Property prop, XmlNode token)
{ {
string contents = token.InnerText.Trim(); string contents = token.InnerText.Trim();
string[] buffer = contents.Split(' '); string[] buffer = contents.Split(' ');
@ -17,8 +18,8 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{ {
try try
{ {
float min = float.Parse(buffer[0]); float min = Formatting.ParseFloat(buffer[0]);
float max = float.Parse(buffer[1]); float max = Formatting.ParseFloat(buffer[1]);
prop.Type = PropertyType.NumberRange; prop.Type = PropertyType.NumberRange;
prop.Value = new NumberRange(min, max); prop.Value = new NumberRange(min, max);
@ -31,5 +32,10 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
return valid; return valid;
} }
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
node.InnerText = prop.Value.ToString() + ' ';
}
} }
} }

View File

@ -7,7 +7,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{ {
public string Token => "NumberSequence"; public string Token => "NumberSequence";
public bool ReadToken(Property prop, XmlNode token) public bool ReadProperty(Property prop, XmlNode token)
{ {
string contents = token.InnerText.Trim(); string contents = token.InnerText.Trim();
string[] buffer = contents.Split(' '); string[] buffer = contents.Split(' ');
@ -23,9 +23,9 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
for (int i = 0; i < length; i += 3) for (int i = 0; i < length; i += 3)
{ {
float Time = float.Parse(buffer[ i ]); float Time = Formatting.ParseFloat(buffer[ i ]);
float Value = float.Parse(buffer[i + 1]); float Value = Formatting.ParseFloat(buffer[i + 1]);
float Envelope = float.Parse(buffer[i + 2]); float Envelope = Formatting.ParseFloat(buffer[i + 2]);
keypoints[i / 3] = new NumberSequenceKeypoint(Time, Value, Envelope); keypoints[i / 3] = new NumberSequenceKeypoint(Time, Value, Envelope);
} }
@ -41,5 +41,10 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
return valid; return valid;
} }
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
node.InnerText = prop.Value.ToString() + ' ';
}
} }
} }

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Xml; using System.Xml;
using RobloxFiles.DataTypes; 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 readBool = createReader(bool.Parse, token);
var readFloat = createReader(XmlPropertyTokens.ParseFloat, token); var readFloat = createReader(Formatting.ParseFloat, token);
try try
{ {
@ -46,5 +47,40 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
return false; 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<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

@ -1,4 +1,5 @@
using System.Xml; using System;
using System.Xml;
using RobloxFiles.DataTypes; using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens namespace RobloxFiles.XmlFormat.PropertyTokens
@ -8,7 +9,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
public string Token => "Ray"; public string Token => "Ray";
private static string[] Fields = new string[2] { "origin", "direction" }; private static string[] Fields = new string[2] { "origin", "direction" };
public bool ReadToken(Property prop, XmlNode token) public bool ReadProperty(Property prop, XmlNode token)
{ {
try try
{ {
@ -35,5 +36,18 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
return false; 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);
}
} }
} }

View File

@ -1,4 +1,5 @@
using System.Xml; using System;
using System.Xml;
using RobloxFiles.DataTypes; using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens namespace RobloxFiles.XmlFormat.PropertyTokens
@ -8,7 +9,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
public string Token => "Rect2D"; public string Token => "Rect2D";
private static string[] Fields = new string[2] { "min", "max" }; private static string[] Fields = new string[2] { "min", "max" };
public bool ReadToken(Property prop, XmlNode token) public bool ReadProperty(Property prop, XmlNode token)
{ {
try try
{ {
@ -35,5 +36,18 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
return false; 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);
}
} }
} }

View File

@ -6,7 +6,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{ {
public string Token => "Ref"; public string Token => "Ref";
public bool ReadToken(Property prop, XmlNode token) public bool ReadProperty(Property prop, XmlNode token)
{ {
string refId = token.InnerText; string refId = token.InnerText;
prop.Type = PropertyType.Ref; prop.Type = PropertyType.Ref;
@ -14,5 +14,18 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
return true; 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;
}
} }
} }

View File

@ -1,4 +1,5 @@
using System.Text; using System;
using System.Text;
using System.Xml; using System.Xml;
namespace RobloxFiles.XmlFormat.PropertyTokens namespace RobloxFiles.XmlFormat.PropertyTokens
@ -7,7 +8,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{ {
public string Token => "SharedString"; public string Token => "SharedString";
public bool ReadToken(Property prop, XmlNode token) public bool ReadProperty(Property prop, XmlNode token)
{ {
string contents = token.InnerText; string contents = token.InnerText;
prop.Type = PropertyType.SharedString; prop.Type = PropertyType.SharedString;
@ -15,5 +16,11 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
return true; return true;
} }
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
var BinaryStringToken = XmlPropertyTokens.GetHandler<BinaryStringToken>();
BinaryStringToken.WriteProperty(prop, doc, node);
}
} }
} }

View File

@ -1,4 +1,5 @@
using System.Text; using System;
using System.Text;
using System.Xml; using System.Xml;
namespace RobloxFiles.XmlFormat.PropertyTokens namespace RobloxFiles.XmlFormat.PropertyTokens
@ -7,16 +8,28 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{ {
public string Token => "string; ProtectedString"; public string Token => "string; ProtectedString";
public bool ReadToken(Property prop, XmlNode token) public bool ReadProperty(Property prop, XmlNode token)
{ {
string contents = token.InnerText; string contents = token.InnerText;
prop.Type = PropertyType.String; prop.Type = PropertyType.String;
prop.Value = contents; prop.Value = contents;
byte[] buffer = Encoding.UTF8.GetBytes(contents);
prop.SetRawBuffer(buffer);
return true; 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;
}
}
} }
} }

View File

@ -1,4 +1,5 @@
using System.Xml; using System;
using System.Xml;
using RobloxFiles.DataTypes; using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens namespace RobloxFiles.XmlFormat.PropertyTokens
@ -12,7 +13,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
try try
{ {
XmlElement scaleToken = token[prefix + 'S']; XmlElement scaleToken = token[prefix + 'S'];
float scale = XmlPropertyTokens.ParseFloat(scaleToken.InnerText); float scale = Formatting.ParseFloat(scaleToken.InnerText);
XmlElement offsetToken = token[prefix + 'O']; XmlElement offsetToken = token[prefix + 'O'];
int offset = int.Parse(offsetToken.InnerText); 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); UDim result = ReadUDim(token);
bool success = (result != null); bool success = (result != null);
@ -38,5 +50,11 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
return success; return success;
} }
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
UDim value = prop.Value as UDim;
WriteUDim(doc, node, value);
}
} }
} }

View File

@ -1,4 +1,5 @@
using System.Xml; using System;
using System.Xml;
using RobloxFiles.DataTypes; using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens namespace RobloxFiles.XmlFormat.PropertyTokens
@ -7,7 +8,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{ {
public string Token => "UDim2"; 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 xUDim = UDimToken.ReadUDim(token, "X");
UDim yUDim = UDimToken.ReadUDim(token, "Y"); UDim yUDim = UDimToken.ReadUDim(token, "Y");
@ -22,5 +23,16 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
return false; 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");
}
} }
} }

View File

@ -20,7 +20,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{ {
var coord = token[key]; var coord = token[key];
string text = coord.InnerText; string text = coord.InnerText;
xy[i] = XmlPropertyTokens.ParseFloat(text); xy[i] = Formatting.ParseFloat(text);
} }
catch catch
{ {
@ -31,7 +31,18 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
return new Vector2(xy); 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); Vector2 result = ReadVector2(token);
bool success = (result != null); bool success = (result != null);
@ -44,5 +55,11 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
return success; return success;
} }
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
Vector2 value = prop.Value as Vector2;
WriteVector2(doc, node, value);
}
} }
} }

View File

@ -1,4 +1,5 @@
using System.Xml; using System;
using System.Xml;
using RobloxFiles.DataTypes; using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens namespace RobloxFiles.XmlFormat.PropertyTokens
@ -19,7 +20,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
try try
{ {
var coord = token[key]; var coord = token[key];
xyz[i] = XmlPropertyTokens.ParseFloat(coord.InnerText); xyz[i] = Formatting.ParseFloat(coord.InnerText);
} }
catch catch
{ {
@ -30,7 +31,22 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
return new Vector3(xyz); 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); Vector3 result = ReadVector3(token);
bool success = (result != null); bool success = (result != null);
@ -43,5 +59,11 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
return success; return success;
} }
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
Vector3 value = prop.Value as Vector3;
WriteVector3(doc, node, value);
}
} }
} }

View File

@ -8,7 +8,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
public string Token => "Vector3int16"; public string Token => "Vector3int16";
private static string[] Coords = new string[3] { "X", "Y", "Z" }; 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]; short[] xyz = new short[3];
@ -36,5 +36,22 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
return true; 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);
}
} }
} }

View File

@ -62,8 +62,9 @@ namespace RobloxFiles.XmlFormat
Property prop = new Property(); Property prop = new Property();
prop.Name = propName.InnerText; prop.Name = propName.InnerText;
prop.Instance = instance; prop.Instance = instance;
prop.XmlToken = propType;
if (!tokenHandler.ReadToken(prop, propNode)) if (!tokenHandler.ReadProperty(prop, propNode))
Console.WriteLine("Could not read property: " + prop.GetFullName() + '!'); Console.WriteLine("Could not read property: " + prop.GetFullName() + '!');
instance.AddProperty(ref prop); 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"); var error = createErrorHandler("ReadInstance");

194
XmlFormat/XmlDataWriter.cs Normal file
View File

@ -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>(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<BinaryStringToken>();
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;
}
}
}

View File

@ -4,6 +4,8 @@ using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Xml; using System.Xml;
using RobloxFiles;
namespace RobloxFiles.XmlFormat namespace RobloxFiles.XmlFormat
{ {
public static class XmlPropertyTokens public static class XmlPropertyTokens
@ -35,17 +37,30 @@ namespace RobloxFiles.XmlFormat
Handlers = tokenHandlers; Handlers = tokenHandlers;
} }
public static bool ReadTokenGeneric<T>(Property prop, PropertyType propType, XmlNode token) where T : struct public static bool ReadPropertyGeneric<T>(Property prop, PropertyType propType, XmlNode token) where T : struct
{ {
try try
{ {
Type resultType = typeof(T); string value = token.InnerText;
TypeConverter converter = TypeDescriptor.GetConverter(resultType);
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.Type = propType;
prop.Value = result;
return true; return true;
} }
catch catch
@ -72,21 +87,5 @@ namespace RobloxFiles.XmlFormat
return (T)result; 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

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@ -15,11 +16,15 @@ namespace RobloxFiles.XmlFormat
// Runtime Specific // Runtime Specific
public readonly XmlDocument Root = new XmlDocument(); public readonly XmlDocument Root = new XmlDocument();
public Dictionary<string, Instance> Instances = new Dictionary<string, Instance>(); public Dictionary<string, Instance> Instances = new Dictionary<string, Instance>();
public Dictionary<string, string> SharedStrings = new Dictionary<string, string>(); public Dictionary<string, string> SharedStrings = new Dictionary<string, string>();
public void ReadFile(byte[] buffer) public void ReadFile(byte[] buffer)
{ {
Instances.Clear();
SharedStrings.Clear();
try try
{ {
string xml = Encoding.UTF8.GetString(buffer); string xml = Encoding.UTF8.GetString(buffer);
@ -31,7 +36,7 @@ namespace RobloxFiles.XmlFormat
} }
XmlNode roblox = Root.FirstChild; XmlNode roblox = Root.FirstChild;
if (roblox != null && roblox.Name == "roblox") if (roblox != null && roblox.Name == "roblox")
{ {
// Verify the version we are using. // Verify the version we are using.
@ -108,5 +113,52 @@ namespace RobloxFiles.XmlFormat
throw new Exception("XmlRobloxFile: No 'roblox' tag found!"); 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("<![CDATA[]]>", "");
using (BinaryWriter writer = new BinaryWriter(stream))
{
byte[] data = Encoding.UTF8.GetBytes(result);
writer.Write(data);
}
}
}
} }
} }