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