Roblox-File-Format/XmlFormat/XmlFileWriter.cs

242 lines
7.6 KiB
C#
Raw Permalink Normal View History

using System;
2020-08-17 05:33:59 +00:00
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Xml;
using RobloxFiles.DataTypes;
2020-08-18 01:12:24 +00:00
using RobloxFiles.Utility;
2021-02-25 20:09:54 +00:00
using RobloxFiles.Tokens;
namespace RobloxFiles.XmlFormat
{
2019-05-19 04:44:51 +00:00
public static class XmlRobloxFileWriter
{
2019-05-19 04:44:51 +00:00
public static readonly XmlWriterSettings Settings = new XmlWriterSettings()
{
Indent = true,
IndentChars = "\t",
NewLineChars = "\r\n",
Encoding = Encoding.UTF8,
2019-05-19 04:44:51 +00:00
OmitXmlDeclaration = true
};
2019-05-19 04:44:51 +00:00
public static string CreateReferent()
{
2020-08-17 05:33:59 +00:00
var referentGuid = Guid
.NewGuid()
.ToString();
string referent = "RBX" + referentGuid
2020-08-17 05:33:59 +00:00
.ToUpper(CultureInfo.InvariantCulture)
.Replace("-", "");
2020-08-17 05:33:59 +00:00
return referent;
}
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++;
foreach (Instance child in inst.GetChildren())
RecordInstances(file, child);
file.Instances.Add(inst.Referent, inst);
}
public static XmlNode WriteProperty(Property prop, XmlDocument doc, XmlRobloxFile file)
{
if (prop.Name == "Archivable")
return null;
string propType = prop.XmlToken;
2019-11-04 21:25:22 +00:00
if (propType == null)
propType = "";
if (propType.Length == 0)
{
propType = GetEnumName(prop.Type);
switch (prop.Type)
{
case PropertyType.Ref:
{
propType = "Ref";
break;
}
case PropertyType.CFrame:
case PropertyType.Quaternion:
2021-05-03 22:26:55 +00:00
{
propType = "CoordinateFrame";
break;
2021-05-03 22:26:55 +00:00
}
case PropertyType.Enum:
2021-05-03 22:26:55 +00:00
{
propType = "token";
break;
2021-05-03 22:26:55 +00:00
}
case PropertyType.Rect:
2021-05-03 22:26:55 +00:00
{
propType = "Rect2D";
break;
2021-05-03 22:26:55 +00:00
}
case PropertyType.Int:
case PropertyType.Bool:
case PropertyType.Float:
case PropertyType.Int64:
case PropertyType.Double:
2021-05-03 22:26:55 +00:00
{
2020-08-17 05:33:59 +00:00
propType = propType.ToLower(CultureInfo.InvariantCulture);
break;
2021-05-03 22:26:55 +00:00
}
case PropertyType.String:
2021-05-03 22:26:55 +00:00
{
if (prop.Value is Content)
propType = "Content";
else if (prop.Value is ProtectedString)
propType = "ProtectedString";
else if (prop.Value is byte[])
propType = "BinaryString";
else
propType = "string";
break;
2021-05-03 22:26:55 +00:00
}
}
}
if (prop.Type == PropertyType.Ref)
propType = "Ref";
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}");
return null;
}
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-08-18 01:12:24 +00:00
if (str.ComputedKey == null)
{
2020-08-18 01:12:24 +00:00
var newShared = SharedString.FromBuffer(str.SharedValue);
str.Key = newShared.ComputedKey;
}
2020-08-18 01:12:24 +00:00
file.SharedStrings.Add(str.Key);
}
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)
{
if (!instance.Archivable)
return null;
XmlElement instNode = doc.CreateElement("Item");
instNode.SetAttribute("class", instance.ClassName);
instNode.SetAttribute("referent", instance.Referent);
XmlElement propsNode = doc.CreateElement("Properties");
instNode.AppendChild(propsNode);
var props = instance.RefreshProperties();
var orderedKeys = props
.OrderBy(pair => pair.Key)
.Select(pair => pair.Key);
2019-05-19 04:44:51 +00:00
foreach (string propName in orderedKeys)
{
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
2022-10-12 00:35:27 +00:00
if (!isDefault || propName == "Name")
2020-08-18 01:12:24 +00:00
{
XmlNode propNode = WriteProperty(prop, doc, file);
2020-08-18 01:12:24 +00:00
if (propNode == null)
continue;
propsNode.AppendChild(propNode);
}
}
foreach (Instance child in instance.GetChildren())
{
if (child.Archivable)
{
XmlNode childNode = WriteInstance(child, doc, file);
instNode.AppendChild(childNode);
}
}
return instNode;
}
public static XmlNode WriteSharedStrings(XmlDocument doc, XmlRobloxFile file)
{
2020-08-17 05:33:59 +00:00
Contract.Requires(doc != null && file != null);
XmlElement sharedStrings = doc.CreateElement("SharedStrings");
var binaryWriter = XmlPropertyTokens.GetHandler<BinaryStringToken>();
var binaryBuffer = new Property("SharedString", PropertyType.String);
foreach (string key in file.SharedStrings)
{
XmlElement sharedString = doc.CreateElement("SharedString");
sharedString.SetAttribute("md5", key);
binaryBuffer.RawBuffer = SharedString.Find(key);
binaryWriter.WriteProperty(binaryBuffer, doc, sharedString);
sharedStrings.AppendChild(sharedString);
}
return sharedStrings;
}
}
}