Roblox-File-Format/XmlFormat/XmlFileReader.cs

191 lines
6.5 KiB
C#
Raw Normal View History

using System;
using System.Xml;
using RobloxFiles.DataTypes;
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 XmlRobloxFileReader
{
2020-09-14 16:20:34 +00:00
private static Func<string, Exception> CreateErrorHandler(string label)
{
var errorHandler = new Func<string, Exception>((message) =>
{
2020-10-30 20:07:11 +00:00
string contents = $"{nameof(XmlRobloxFileReader)}.{label} - {message}";
return new Exception(contents);
});
return errorHandler;
}
public static void ReadSharedStrings(XmlNode sharedStrings, XmlRobloxFile file)
{
2020-10-30 20:07:11 +00:00
var error = CreateErrorHandler(nameof(ReadSharedStrings));
if (sharedStrings.Name != "SharedStrings")
2020-10-30 20:07:11 +00:00
throw error("Provided XmlNode's class must be 'SharedStrings'!");
foreach (XmlNode sharedString in sharedStrings)
{
if (sharedString.Name == "SharedString")
{
XmlNode hashNode = sharedString.Attributes.GetNamedItem("md5");
if (hashNode == null)
throw error("Got a SharedString without an 'md5' attribute!");
string key = hashNode.InnerText;
string value = sharedString.InnerText.Replace("\n", "");
byte[] hash = Convert.FromBase64String(key);
var record = SharedString.FromBase64(value);
if (hash.Length != 16)
2020-10-30 20:07:11 +00:00
throw error($"SharedString base64 key '{key}' must align to byte[16]!");
if (key != record.Key)
{
SharedString.Register(key, record.SharedValue);
record.Key = key;
}
file.SharedStrings.Add(key);
}
}
}
public static void ReadMetadata(XmlNode meta, XmlRobloxFile file)
{
2020-10-30 20:07:11 +00:00
var error = CreateErrorHandler(nameof(ReadMetadata));
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;
}
public static void ReadProperties(Instance instance, XmlNode propsNode)
{
2020-10-30 20:07:11 +00:00
var error = CreateErrorHandler(nameof(ReadProperties));
if (propsNode.Name != "Properties")
throw error("Provided XmlNode's class should be 'Properties'!");
foreach (XmlNode propNode in propsNode.ChildNodes)
{
if (propNode.NodeType == XmlNodeType.Comment)
continue;
string propType = propNode.Name;
XmlNode propName = propNode.Attributes.GetNamedItem("name");
if (propName == null)
{
if (propNode.Name == "Item")
continue;
throw error("Got a property node without a 'name' attribute!");
}
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
};
2020-10-30 20:07:11 +00:00
if (!tokenHandler.ReadProperty(prop, propNode) && RobloxFile.LogErrors)
{
var readError = error($"Could not read property: {prop.GetFullName()}!");
2021-01-20 20:45:58 +00:00
RobloxFile.LogError(readError.Message);
2020-10-30 20:07:11 +00:00
}
2023-11-29 03:07:30 +00:00
instance.AddProperty(prop);
}
2020-09-13 01:16:19 +00:00
else if (RobloxFile.LogErrors)
{
2020-10-30 20:07:11 +00:00
var tokenError = error($"No {nameof(IXmlPropertyToken)} found for property type: {propType}!");
2021-01-20 20:45:58 +00:00
RobloxFile.LogError(tokenError.Message);
}
}
}
public static Instance ReadInstance(XmlNode instNode, XmlRobloxFile file)
{
2020-10-30 20:07:11 +00:00
var error = CreateErrorHandler(nameof(ReadInstance));
// Process the instance itself
if (instNode.Name != "Item")
throw error("Provided XmlNode's name should be 'Item'!");
XmlNode classToken = instNode.Attributes.GetNamedItem("class");
2020-10-30 20:07:11 +00:00
if (classToken == null)
throw error("Got an Item without a defined 'class' attribute!");
string className = classToken.InnerText;
2020-10-30 20:07:11 +00:00
Type instType = Type.GetType($"RobloxFiles.{className}");
if (instType == null)
{
if (RobloxFile.LogErrors)
{
var typeError = error($"Unknown class {className} while reading Item.");
2021-01-20 20:45:58 +00:00
RobloxFile.LogError(typeError.Message);
2020-10-30 20:07:11 +00:00
}
return null;
}
Instance inst = Activator.CreateInstance(instType) as Instance;
// 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)
{
2019-05-19 04:44:51 +00:00
string referent = refToken.InnerText;
inst.Referent = referent;
2019-05-19 04:44:51 +00:00
if (file.Instances.ContainsKey(referent))
throw error("Got an Item with a duplicate 'referent' attribute!");
2019-05-19 04:44:51 +00:00
file.Instances.Add(referent, inst);
}
// Process the child nodes of this instance.
foreach (XmlNode childNode in instNode.ChildNodes)
{
2020-10-30 20:07:11 +00:00
switch (childNode.Name)
{
2020-10-30 20:07:11 +00:00
case "Item":
Instance child = ReadInstance(childNode, file);
if (child != null)
child.Parent = inst;
break;
case "Properties":
ReadProperties(inst, childNode);
break;
default: break;
}
}
return inst;
}
}
}