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.
This commit is contained in:
CloneTrooper1019
2019-06-30 17:01:19 -05:00
parent 8e01f33d6b
commit de8df15d3f
67 changed files with 6297 additions and 667 deletions

@ -1,6 +1,8 @@
using System;
using System.Xml;
using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat
{
public static class XmlRobloxFileReader
@ -35,11 +37,35 @@ namespace RobloxFiles.XmlFormat
string key = md5Node.InnerText;
string value = sharedString.InnerText.Replace("\n", "");
file.SharedStrings.Add(key, value);
byte[] buffer = Convert.FromBase64String(value);
SharedString record = SharedString.FromBase64(value);
if (record.MD5_Key != key)
throw error("The provided md5 hash did not match with the md5 hash computed for the value!");
file.SharedStrings.Add(key);
}
}
}
public static void ReadMetadata(XmlNode meta, XmlRobloxFile file)
{
var error = createErrorHandler("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)
{
var error = createErrorHandler("ReadProperties");
@ -53,7 +79,12 @@ namespace RobloxFiles.XmlFormat
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);
@ -90,8 +121,12 @@ namespace RobloxFiles.XmlFormat
if (classToken == null)
throw error("Got an Item without a defined 'class' attribute!");
Instance inst = new Instance() { ClassName = classToken.InnerText };
string className = classToken.InnerText;
Type instType = Type.GetType($"RobloxFiles.{className}") ?? typeof(Instance);
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");

@ -1,9 +1,8 @@
using System;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Xml;
using RobloxFiles.DataTypes;
using RobloxFiles.XmlFormat.PropertyTokens;
namespace RobloxFiles.XmlFormat
@ -40,7 +39,7 @@ namespace RobloxFiles.XmlFormat
foreach (Instance child in inst.GetChildren())
RecordInstances(file, child);
if (inst.Referent.Length < 35)
if (inst.Referent == null || inst.Referent.Length < 35)
inst.Referent = CreateReferent();
file.Instances.Add(inst.Referent, inst);
@ -102,19 +101,8 @@ namespace RobloxFiles.XmlFormat
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;
}
SharedString value = prop.CastValue<SharedString>();
file.SharedStrings.Add(value.MD5_Key);
}
return propNode;
@ -122,6 +110,9 @@ namespace RobloxFiles.XmlFormat
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);
@ -129,7 +120,7 @@ namespace RobloxFiles.XmlFormat
XmlElement propsNode = doc.CreateElement("Properties");
instNode.AppendChild(propsNode);
var props = instance.Properties;
var props = instance.RefreshProperties();
foreach (string propName in props.Keys)
{
@ -140,8 +131,11 @@ namespace RobloxFiles.XmlFormat
foreach (Instance child in instance.GetChildren())
{
XmlNode childNode = WriteInstance(child, doc, file);
instNode.AppendChild(childNode);
if (child.Archivable)
{
XmlNode childNode = WriteInstance(child, doc, file);
instNode.AppendChild(childNode);
}
}
return instNode;
@ -152,18 +146,15 @@ namespace RobloxFiles.XmlFormat
XmlElement sharedStrings = doc.CreateElement("SharedStrings");
var binaryWriter = XmlPropertyTokens.GetHandler<BinaryStringToken>();
var bufferProp = new Property("SharedString", PropertyType.String);
var binaryBuffer = new Property("SharedString", PropertyType.String);
foreach (string md5 in file.SharedStrings.Keys)
foreach (string md5 in file.SharedStrings)
{
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);
binaryBuffer.RawBuffer = SharedString.FindRecord(md5);
binaryWriter.WriteProperty(binaryBuffer, doc, sharedString);
sharedStrings.AppendChild(sharedString);
}