Roblox-File-Format/XmlFormat/IO/XmlFileWriter.cs
CloneTrooper1019 de8df15d3f Large scale refactor to add class support!
Instance classes are now strongly typed with real property fields that
are derived from the JSON API Dump! This required a lot of reworking
across the board:

- Classes and Enums are auto-generated in the 'Generated' folder now.
This is done using a custom built-in plugin, which can be found in
the Plugins folder of this project.
- Property objects are now tied to .NET's reflection system. Reading
and writing from them will try to redirect into a field of the
Instance they are bound to.
- Property types that were loosely defined now have proper data types
(such as Color3uint8, Content, ProtectedString, SharedString, etc)
- Fixed an error with the CFrame directional vectors.
- The binary PRNT chunk now writes instances in child->parent order.
- Enums are now generated correctly, with up-to-date values.
- INST chunks are now referred to as 'Classes' instead of 'Types'.
- Unary operator added to Vector2 and Vector3.
- CollectionService tags can now be manipulated per-instance using
the Instance.Tags member.
- The Instance.Archivable property now works correctly.
- XML files now save/load metadata correctly.
- Cleaned up the property tokens directory.

I probably missed a few things, but that's a general overview of
everything that changed.
2019-06-30 17:01:19 -05:00

166 lines
5.3 KiB
C#

using System;
using System.Text;
using System.Xml;
using RobloxFiles.DataTypes;
using RobloxFiles.XmlFormat.PropertyTokens;
namespace RobloxFiles.XmlFormat
{
public static class XmlRobloxFileWriter
{
public static readonly XmlWriterSettings Settings = new XmlWriterSettings()
{
Indent = true,
IndentChars = "\t",
NewLineChars = "\r\n",
Encoding = Encoding.UTF8,
OmitXmlDeclaration = true
};
public 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);
if (inst.Referent == null || inst.Referent.Length < 35)
inst.Referent = CreateReferent();
file.Instances.Add(inst.Referent, inst);
}
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)
{
SharedString value = prop.CastValue<SharedString>();
file.SharedStrings.Add(value.MD5_Key);
}
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();
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())
{
if (child.Archivable)
{
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 binaryBuffer = new Property("SharedString", PropertyType.String);
foreach (string md5 in file.SharedStrings)
{
XmlElement sharedString = doc.CreateElement("SharedString");
sharedString.SetAttribute("md5", md5);
binaryBuffer.RawBuffer = SharedString.FindRecord(md5);
binaryWriter.WriteProperty(binaryBuffer, doc, sharedString);
sharedStrings.AppendChild(sharedString);
}
return sharedStrings;
}
}
}