2019-05-17 12:08:06 +00:00
|
|
|
|
using System;
|
2020-08-17 05:33:59 +00:00
|
|
|
|
using System.Diagnostics.Contracts;
|
|
|
|
|
using System.Globalization;
|
2020-07-13 01:19:30 +00:00
|
|
|
|
using System.Linq;
|
2019-05-17 12:08:06 +00:00
|
|
|
|
using System.Text;
|
|
|
|
|
using System.Xml;
|
|
|
|
|
|
2019-06-30 22:01:19 +00:00
|
|
|
|
using RobloxFiles.DataTypes;
|
2020-08-18 01:12:24 +00:00
|
|
|
|
using RobloxFiles.Utility;
|
2019-05-17 12:08:06 +00:00
|
|
|
|
using RobloxFiles.XmlFormat.PropertyTokens;
|
|
|
|
|
|
|
|
|
|
namespace RobloxFiles.XmlFormat
|
|
|
|
|
{
|
2019-05-19 04:44:51 +00:00
|
|
|
|
public static class XmlRobloxFileWriter
|
2019-05-17 12:08:06 +00:00
|
|
|
|
{
|
2019-05-19 04:44:51 +00:00
|
|
|
|
public static readonly XmlWriterSettings Settings = new XmlWriterSettings()
|
2019-05-17 12:08:06 +00:00
|
|
|
|
{
|
|
|
|
|
Indent = true,
|
|
|
|
|
IndentChars = "\t",
|
|
|
|
|
NewLineChars = "\r\n",
|
|
|
|
|
Encoding = Encoding.UTF8,
|
2019-05-19 04:44:51 +00:00
|
|
|
|
OmitXmlDeclaration = true
|
2019-05-17 12:08:06 +00:00
|
|
|
|
};
|
|
|
|
|
|
2019-05-19 04:44:51 +00:00
|
|
|
|
public static string CreateReferent()
|
2019-05-17 12:08:06 +00:00
|
|
|
|
{
|
2020-08-17 05:33:59 +00:00
|
|
|
|
var referentGuid = Guid
|
|
|
|
|
.NewGuid()
|
|
|
|
|
.ToString();
|
2019-05-17 12:08:06 +00:00
|
|
|
|
|
|
|
|
|
string referent = "RBX" + referentGuid
|
2020-08-17 05:33:59 +00:00
|
|
|
|
.ToUpper(CultureInfo.InvariantCulture)
|
|
|
|
|
.Replace("-", "");
|
2019-05-17 12:08:06 +00:00
|
|
|
|
|
2020-08-17 05:33:59 +00:00
|
|
|
|
return referent;
|
2019-05-17 12:08:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string GetEnumName<T>(T item) where T : struct
|
|
|
|
|
{
|
|
|
|
|
return Enum.GetName(typeof(T), item);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal static void RecordInstances(XmlRobloxFile file, Instance inst)
|
|
|
|
|
{
|
2020-08-18 01:12:24 +00:00
|
|
|
|
inst.Referent = "RBX" + file.RefCounter++;
|
|
|
|
|
|
2019-05-17 12:08:06 +00:00
|
|
|
|
foreach (Instance child in inst.GetChildren())
|
|
|
|
|
RecordInstances(file, child);
|
|
|
|
|
|
2019-06-08 03:43:28 +00:00
|
|
|
|
file.Instances.Add(inst.Referent, inst);
|
2019-05-17 12:08:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static XmlNode WriteProperty(Property prop, XmlDocument doc, XmlRobloxFile file)
|
|
|
|
|
{
|
2020-07-13 01:19:30 +00:00
|
|
|
|
if (prop.Name == "Archivable")
|
|
|
|
|
return null;
|
|
|
|
|
|
2019-05-17 12:08:06 +00:00
|
|
|
|
string propType = prop.XmlToken;
|
|
|
|
|
|
2019-11-04 21:25:22 +00:00
|
|
|
|
if (propType == null)
|
|
|
|
|
propType = "";
|
|
|
|
|
|
|
|
|
|
if (propType.Length == 0)
|
2019-05-17 12:08:06 +00:00
|
|
|
|
{
|
|
|
|
|
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:
|
2020-08-17 05:33:59 +00:00
|
|
|
|
propType = propType.ToLower(CultureInfo.InvariantCulture);
|
2019-05-17 12:08:06 +00:00
|
|
|
|
break;
|
|
|
|
|
case PropertyType.String:
|
|
|
|
|
propType = (prop.HasRawBuffer ? "BinaryString" : "string");
|
|
|
|
|
break;
|
2020-07-13 01:19:30 +00:00
|
|
|
|
default: break;
|
2019-05-17 12:08:06 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
IXmlPropertyToken handler = XmlPropertyTokens.GetHandler(propType);
|
|
|
|
|
|
|
|
|
|
if (handler == null)
|
|
|
|
|
{
|
2021-01-20 20:45:58 +00:00
|
|
|
|
RobloxFile.LogError($"XmlRobloxFileWriter.WriteProperty: No token handler found for property type: {propType}");
|
2019-05-17 12:08:06 +00:00
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-13 01:19:30 +00:00
|
|
|
|
if (prop.Type == PropertyType.SharedString)
|
|
|
|
|
{
|
2020-08-18 01:12:24 +00:00
|
|
|
|
SharedString str = prop.CastValue<SharedString>();
|
|
|
|
|
|
|
|
|
|
if (str == null)
|
|
|
|
|
{
|
|
|
|
|
byte[] value = prop.CastValue<byte[]>();
|
|
|
|
|
str = SharedString.FromBuffer(value);
|
|
|
|
|
}
|
2020-07-13 01:19:30 +00:00
|
|
|
|
|
2020-08-18 01:12:24 +00:00
|
|
|
|
if (str.ComputedKey == null)
|
2020-07-13 01:19:30 +00:00
|
|
|
|
{
|
2020-08-18 01:12:24 +00:00
|
|
|
|
var newShared = SharedString.FromBuffer(str.SharedValue);
|
|
|
|
|
str.Key = newShared.ComputedKey;
|
2020-07-13 01:19:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-08-18 01:12:24 +00:00
|
|
|
|
file.SharedStrings.Add(str.Key);
|
2020-07-13 01:19:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-05-17 12:08:06 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return propNode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static XmlNode WriteInstance(Instance instance, XmlDocument doc, XmlRobloxFile file)
|
|
|
|
|
{
|
2019-06-30 22:01:19 +00:00
|
|
|
|
if (!instance.Archivable)
|
|
|
|
|
return null;
|
|
|
|
|
|
2019-05-17 12:08:06 +00:00
|
|
|
|
XmlElement instNode = doc.CreateElement("Item");
|
|
|
|
|
instNode.SetAttribute("class", instance.ClassName);
|
2019-06-08 03:43:28 +00:00
|
|
|
|
instNode.SetAttribute("referent", instance.Referent);
|
2019-05-17 12:08:06 +00:00
|
|
|
|
|
|
|
|
|
XmlElement propsNode = doc.CreateElement("Properties");
|
|
|
|
|
instNode.AppendChild(propsNode);
|
|
|
|
|
|
2019-06-30 22:01:19 +00:00
|
|
|
|
var props = instance.RefreshProperties();
|
2020-07-13 01:19:30 +00:00
|
|
|
|
|
|
|
|
|
var orderedKeys = props
|
|
|
|
|
.OrderBy(pair => pair.Key)
|
|
|
|
|
.Select(pair => pair.Key);
|
2019-05-19 04:44:51 +00:00
|
|
|
|
|
2020-07-13 01:19:30 +00:00
|
|
|
|
foreach (string propName in orderedKeys)
|
2019-05-17 12:08:06 +00:00
|
|
|
|
{
|
|
|
|
|
Property prop = props[propName];
|
2020-08-18 01:12:24 +00:00
|
|
|
|
bool isDefault = false;
|
|
|
|
|
|
|
|
|
|
object a = DefaultProperty.Get(instance, prop);
|
|
|
|
|
object b = prop.Value;
|
|
|
|
|
|
2020-09-14 16:20:34 +00:00
|
|
|
|
if (a is double d0 && b is double d1)
|
2020-08-18 01:12:24 +00:00
|
|
|
|
isDefault = d0.FuzzyEquals(d1);
|
2020-09-14 16:20:34 +00:00
|
|
|
|
else if (a is float f0 && b is float f1)
|
|
|
|
|
isDefault = f0.FuzzyEquals(f1);
|
2020-08-18 01:12:24 +00:00
|
|
|
|
else if (b != null)
|
|
|
|
|
isDefault = b.Equals(a);
|
|
|
|
|
else if (a == b)
|
|
|
|
|
isDefault = true;
|
2020-09-14 16:20:34 +00:00
|
|
|
|
|
2020-08-18 01:12:24 +00:00
|
|
|
|
if (!isDefault)
|
|
|
|
|
{
|
|
|
|
|
XmlNode propNode = WriteProperty(prop, doc, file);
|
2020-07-13 01:19:30 +00:00
|
|
|
|
|
2020-08-18 01:12:24 +00:00
|
|
|
|
if (propNode == null)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
propsNode.AppendChild(propNode);
|
|
|
|
|
}
|
2019-05-17 12:08:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (Instance child in instance.GetChildren())
|
|
|
|
|
{
|
2019-06-30 22:01:19 +00:00
|
|
|
|
if (child.Archivable)
|
|
|
|
|
{
|
|
|
|
|
XmlNode childNode = WriteInstance(child, doc, file);
|
|
|
|
|
instNode.AppendChild(childNode);
|
|
|
|
|
}
|
2019-05-17 12:08:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return instNode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static XmlNode WriteSharedStrings(XmlDocument doc, XmlRobloxFile file)
|
|
|
|
|
{
|
2020-08-17 05:33:59 +00:00
|
|
|
|
Contract.Requires(doc != null && file != null);
|
2019-05-17 12:08:06 +00:00
|
|
|
|
XmlElement sharedStrings = doc.CreateElement("SharedStrings");
|
|
|
|
|
|
|
|
|
|
var binaryWriter = XmlPropertyTokens.GetHandler<BinaryStringToken>();
|
2019-06-30 22:01:19 +00:00
|
|
|
|
var binaryBuffer = new Property("SharedString", PropertyType.String);
|
2019-05-17 12:08:06 +00:00
|
|
|
|
|
2020-07-13 01:19:30 +00:00
|
|
|
|
foreach (string key in file.SharedStrings)
|
2019-05-17 12:08:06 +00:00
|
|
|
|
{
|
|
|
|
|
XmlElement sharedString = doc.CreateElement("SharedString");
|
2020-07-13 01:19:30 +00:00
|
|
|
|
sharedString.SetAttribute("md5", key);
|
2019-05-17 12:08:06 +00:00
|
|
|
|
|
2020-07-13 01:19:30 +00:00
|
|
|
|
binaryBuffer.RawBuffer = SharedString.Find(key);
|
2019-06-30 22:01:19 +00:00
|
|
|
|
binaryWriter.WriteProperty(binaryBuffer, doc, sharedString);
|
2019-05-17 12:08:06 +00:00
|
|
|
|
|
|
|
|
|
sharedStrings.AppendChild(sharedString);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return sharedStrings;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|