2019-01-30 06:36:56 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Xml;
|
|
|
|
|
|
2019-06-30 22:01:19 +00:00
|
|
|
|
using RobloxFiles.DataTypes;
|
|
|
|
|
|
2019-02-01 17:19:20 +00:00
|
|
|
|
namespace RobloxFiles.XmlFormat
|
2019-01-30 06:36:56 +00:00
|
|
|
|
{
|
2019-05-19 04:44:51 +00:00
|
|
|
|
public static class XmlRobloxFileReader
|
2019-01-30 06:36:56 +00:00
|
|
|
|
{
|
2020-09-14 16:20:34 +00:00
|
|
|
|
private static Func<string, Exception> CreateErrorHandler(string label)
|
2019-05-17 06:14:04 +00:00
|
|
|
|
{
|
|
|
|
|
var errorHandler = new Func<string, Exception>((message) =>
|
|
|
|
|
{
|
2020-07-13 01:19:30 +00:00
|
|
|
|
string contents = $"XmlRobloxFileReader.{label}: {message}";
|
2019-05-17 06:14:04 +00:00
|
|
|
|
return new Exception(contents);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return errorHandler;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static void ReadSharedStrings(XmlNode sharedStrings, XmlRobloxFile file)
|
|
|
|
|
{
|
2020-09-14 16:20:34 +00:00
|
|
|
|
var error = CreateErrorHandler("ReadSharedStrings");
|
2019-05-17 06:14:04 +00:00
|
|
|
|
|
|
|
|
|
if (sharedStrings.Name != "SharedStrings")
|
|
|
|
|
throw error("Provided XmlNode's class should be 'SharedStrings'!");
|
|
|
|
|
|
|
|
|
|
foreach (XmlNode sharedString in sharedStrings)
|
|
|
|
|
{
|
|
|
|
|
if (sharedString.Name == "SharedString")
|
|
|
|
|
{
|
2020-07-13 01:19:30 +00:00
|
|
|
|
XmlNode hashNode = sharedString.Attributes.GetNamedItem("md5");
|
2019-05-17 06:14:04 +00:00
|
|
|
|
|
2020-07-13 01:19:30 +00:00
|
|
|
|
if (hashNode == null)
|
2019-05-17 06:14:04 +00:00
|
|
|
|
throw error("Got a SharedString without an 'md5' attribute!");
|
|
|
|
|
|
2020-07-13 01:19:30 +00:00
|
|
|
|
string key = hashNode.InnerText;
|
2019-05-17 06:14:04 +00:00
|
|
|
|
string value = sharedString.InnerText.Replace("\n", "");
|
|
|
|
|
|
2020-07-13 01:19:30 +00:00
|
|
|
|
byte[] hash = Convert.FromBase64String(key);
|
|
|
|
|
var record = SharedString.FromBase64(value);
|
2019-06-30 22:01:19 +00:00
|
|
|
|
|
2020-07-13 01:19:30 +00:00
|
|
|
|
if (hash.Length != 16)
|
|
|
|
|
throw error($"SharedString base64 key '{key}' must decode to byte[16]!");
|
|
|
|
|
|
|
|
|
|
if (key != record.Key)
|
|
|
|
|
{
|
|
|
|
|
SharedString.Register(key, record.SharedValue);
|
|
|
|
|
record.Key = key;
|
|
|
|
|
}
|
2019-06-30 22:01:19 +00:00
|
|
|
|
|
|
|
|
|
file.SharedStrings.Add(key);
|
2019-05-17 06:14:04 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-30 22:01:19 +00:00
|
|
|
|
public static void ReadMetadata(XmlNode meta, XmlRobloxFile file)
|
|
|
|
|
{
|
2020-09-14 16:20:34 +00:00
|
|
|
|
var error = CreateErrorHandler("ReadMetadata");
|
2019-06-30 22:01:19 +00:00
|
|
|
|
|
|
|
|
|
if (meta.Name != "Meta")
|
|
|
|
|
throw error("Provided XmlNode's class should be 'Meta'!");
|
|
|
|
|
|
|
|
|
|
XmlNode propName = meta.Attributes.GetNamedItem("name");
|
|
|
|
|
|
|
|
|
|
if (propName == null)
|
|
|
|
|
throw error("Got a Meta node without a 'name' attribute!");
|
|
|
|
|
|
|
|
|
|
string key = propName.InnerText;
|
|
|
|
|
string value = meta.InnerText;
|
|
|
|
|
|
|
|
|
|
file.Metadata[key] = value;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-30 06:36:56 +00:00
|
|
|
|
public static void ReadProperties(Instance instance, XmlNode propsNode)
|
|
|
|
|
{
|
2020-09-14 16:20:34 +00:00
|
|
|
|
var error = CreateErrorHandler("ReadProperties");
|
2019-05-17 06:14:04 +00:00
|
|
|
|
|
2019-01-30 06:36:56 +00:00
|
|
|
|
if (propsNode.Name != "Properties")
|
2019-05-17 06:14:04 +00:00
|
|
|
|
throw error("Provided XmlNode's class should be 'Properties'!");
|
2019-01-30 06:36:56 +00:00
|
|
|
|
|
|
|
|
|
foreach (XmlNode propNode in propsNode.ChildNodes)
|
|
|
|
|
{
|
2019-10-06 07:18:42 +00:00
|
|
|
|
if (propNode.NodeType == XmlNodeType.Comment)
|
|
|
|
|
continue;
|
|
|
|
|
|
2019-01-30 06:36:56 +00:00
|
|
|
|
string propType = propNode.Name;
|
|
|
|
|
XmlNode propName = propNode.Attributes.GetNamedItem("name");
|
|
|
|
|
|
|
|
|
|
if (propName == null)
|
2019-06-30 22:01:19 +00:00
|
|
|
|
{
|
|
|
|
|
if (propNode.Name == "Item")
|
|
|
|
|
continue;
|
|
|
|
|
|
2019-05-17 06:14:04 +00:00
|
|
|
|
throw error("Got a property node without a 'name' attribute!");
|
2019-06-30 22:01:19 +00:00
|
|
|
|
}
|
2019-01-30 06:36:56 +00:00
|
|
|
|
|
|
|
|
|
IXmlPropertyToken tokenHandler = XmlPropertyTokens.GetHandler(propType);
|
|
|
|
|
|
|
|
|
|
if (tokenHandler != null)
|
|
|
|
|
{
|
2019-05-19 04:44:51 +00:00
|
|
|
|
Property prop = new Property()
|
|
|
|
|
{
|
|
|
|
|
Name = propName.InnerText,
|
|
|
|
|
Instance = instance,
|
|
|
|
|
XmlToken = propType
|
|
|
|
|
};
|
2019-01-30 06:36:56 +00:00
|
|
|
|
|
2019-05-17 12:08:06 +00:00
|
|
|
|
if (!tokenHandler.ReadProperty(prop, propNode))
|
2020-09-13 01:16:19 +00:00
|
|
|
|
if (RobloxFile.LogErrors)
|
|
|
|
|
Console.Error.WriteLine("Could not read property: " + prop.GetFullName() + '!');
|
2019-01-30 06:36:56 +00:00
|
|
|
|
|
|
|
|
|
instance.AddProperty(ref prop);
|
|
|
|
|
}
|
2020-09-13 01:16:19 +00:00
|
|
|
|
else if (RobloxFile.LogErrors)
|
2019-01-30 06:36:56 +00:00
|
|
|
|
{
|
2020-09-21 18:29:31 +00:00
|
|
|
|
Console.Error.WriteLine("No IXmlPropertyToken found for property type: " + propType + '!');
|
2019-01-30 06:36:56 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-17 12:08:06 +00:00
|
|
|
|
public static Instance ReadInstance(XmlNode instNode, XmlRobloxFile file)
|
2019-01-30 06:36:56 +00:00
|
|
|
|
{
|
2020-09-14 16:20:34 +00:00
|
|
|
|
var error = CreateErrorHandler("ReadInstance");
|
2019-05-17 06:14:04 +00:00
|
|
|
|
|
2019-01-30 06:36:56 +00:00
|
|
|
|
// Process the instance itself
|
|
|
|
|
if (instNode.Name != "Item")
|
2019-05-17 06:14:04 +00:00
|
|
|
|
throw error("Provided XmlNode's name should be 'Item'!");
|
2019-01-30 06:36:56 +00:00
|
|
|
|
|
|
|
|
|
XmlNode classToken = instNode.Attributes.GetNamedItem("class");
|
|
|
|
|
if (classToken == null)
|
2019-05-17 06:14:04 +00:00
|
|
|
|
throw error("Got an Item without a defined 'class' attribute!");
|
2019-01-30 06:36:56 +00:00
|
|
|
|
|
|
|
|
|
|
2019-06-30 22:01:19 +00:00
|
|
|
|
string className = classToken.InnerText;
|
|
|
|
|
|
|
|
|
|
Type instType = Type.GetType($"RobloxFiles.{className}") ?? typeof(Instance);
|
|
|
|
|
Instance inst = Activator.CreateInstance(instType) as Instance;
|
|
|
|
|
|
2019-02-01 17:19:20 +00:00
|
|
|
|
// The 'referent' attribute is optional, but should be defined if a Ref property needs to link to this Instance.
|
2019-01-30 06:36:56 +00:00
|
|
|
|
XmlNode refToken = instNode.Attributes.GetNamedItem("referent");
|
|
|
|
|
|
2019-02-04 19:30:33 +00:00
|
|
|
|
if (refToken != null && file != null)
|
2019-01-30 06:36:56 +00:00
|
|
|
|
{
|
2019-05-19 04:44:51 +00:00
|
|
|
|
string referent = refToken.InnerText;
|
2019-06-08 03:43:28 +00:00
|
|
|
|
inst.Referent = referent;
|
2019-02-01 17:19:20 +00:00
|
|
|
|
|
2019-05-19 04:44:51 +00:00
|
|
|
|
if (file.Instances.ContainsKey(referent))
|
2019-05-17 06:14:04 +00:00
|
|
|
|
throw error("Got an Item with a duplicate 'referent' attribute!");
|
2019-01-30 06:36:56 +00:00
|
|
|
|
|
2019-05-19 04:44:51 +00:00
|
|
|
|
file.Instances.Add(referent, inst);
|
2019-01-30 06:36:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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")
|
|
|
|
|
{
|
2019-02-04 19:30:33 +00:00
|
|
|
|
Instance child = ReadInstance(childNode, file);
|
2019-01-30 06:36:56 +00:00
|
|
|
|
child.Parent = inst;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return inst;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|