Cleaning up some things.

This commit is contained in:
CloneTrooper1019
2019-05-18 23:44:51 -05:00
parent 34642f5656
commit 9c3a673d95
47 changed files with 303 additions and 351 deletions

View File

@ -0,0 +1,126 @@
using System;
using System.Xml;
namespace RobloxFiles.XmlFormat
{
public static class XmlRobloxFileReader
{
private static Func<string, Exception> createErrorHandler(string label)
{
var errorHandler = new Func<string, Exception>((message) =>
{
string contents = $"XmlDataReader.{label}: {message}";
return new Exception(contents);
});
return errorHandler;
}
public static void ReadSharedStrings(XmlNode sharedStrings, XmlRobloxFile file)
{
var error = createErrorHandler("ReadSharedStrings");
if (sharedStrings.Name != "SharedStrings")
throw error("Provided XmlNode's class should be 'SharedStrings'!");
foreach (XmlNode sharedString in sharedStrings)
{
if (sharedString.Name == "SharedString")
{
XmlNode md5Node = sharedString.Attributes.GetNamedItem("md5");
if (md5Node == null)
throw error("Got a SharedString without an 'md5' attribute!");
string key = md5Node.InnerText;
string value = sharedString.InnerText.Replace("\n", "");
file.SharedStrings.Add(key, value);
}
}
}
public static void ReadProperties(Instance instance, XmlNode propsNode)
{
var error = createErrorHandler("ReadProperties");
if (propsNode.Name != "Properties")
throw error("Provided XmlNode's class should be 'Properties'!");
foreach (XmlNode propNode in propsNode.ChildNodes)
{
string propType = propNode.Name;
XmlNode propName = propNode.Attributes.GetNamedItem("name");
if (propName == null)
throw error("Got a property node without a 'name' attribute!");
IXmlPropertyToken tokenHandler = XmlPropertyTokens.GetHandler(propType);
if (tokenHandler != null)
{
Property prop = new Property()
{
Name = propName.InnerText,
Instance = instance,
XmlToken = propType
};
if (!tokenHandler.ReadProperty(prop, propNode))
Console.WriteLine("Could not read property: " + prop.GetFullName() + '!');
instance.AddProperty(ref prop);
}
else
{
Console.WriteLine("No IXmlPropertyToken found for property type: " + propType + '!');
}
}
}
public static Instance ReadInstance(XmlNode instNode, XmlRobloxFile file)
{
var error = createErrorHandler("ReadInstance");
// Process the instance itself
if (instNode.Name != "Item")
throw error("Provided XmlNode's name should be 'Item'!");
XmlNode classToken = instNode.Attributes.GetNamedItem("class");
if (classToken == null)
throw error("Got an Item without a defined 'class' attribute!");
Instance inst = new Instance() { ClassName = classToken.InnerText };
// The 'referent' attribute is optional, but should be defined if a Ref property needs to link to this Instance.
XmlNode refToken = instNode.Attributes.GetNamedItem("referent");
if (refToken != null && file != null)
{
string referent = refToken.InnerText;
inst.XmlReferent = referent;
if (file.Instances.ContainsKey(referent))
throw error("Got an Item with a duplicate 'referent' attribute!");
file.Instances.Add(referent, inst);
}
// Process the child nodes of this instance.
foreach (XmlNode childNode in instNode.ChildNodes)
{
if (childNode.Name == "Properties")
{
ReadProperties(inst, childNode);
}
else if (childNode.Name == "Item")
{
Instance child = ReadInstance(childNode, file);
child.Parent = inst;
}
}
return inst;
}
}
}

View File

@ -0,0 +1,191 @@
using System;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Xml;
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.XmlReferent == "")
inst.XmlReferent = CreateReferent();
file.Instances.Add(inst.XmlReferent, inst);
}
public static XmlElement CreateRobloxElement(XmlDocument doc)
{
XmlElement roblox = doc.CreateElement("roblox");
roblox.SetAttribute("version", "4");
doc.AppendChild(roblox);
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.RawBuffer = buffer;
binaryWriter.WriteProperty(bufferProp, doc, sharedString);
sharedStrings.AppendChild(sharedString);
}
return sharedStrings;
}
}
}