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:
@ -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);
|
||||
}
|
||||
|
@ -1,19 +0,0 @@
|
||||
using System.Xml;
|
||||
|
||||
namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
{
|
||||
public class EnumToken : IXmlPropertyToken
|
||||
{
|
||||
public string Token => "token";
|
||||
|
||||
public bool ReadProperty(Property prop, XmlNode token)
|
||||
{
|
||||
return XmlPropertyTokens.ReadPropertyGeneric<uint>(prop, PropertyType.Enum, token);
|
||||
}
|
||||
|
||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||
{
|
||||
node.InnerText = prop.Value.ToString();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Xml;
|
||||
using System.Xml;
|
||||
using RobloxFiles.DataTypes;
|
||||
|
||||
namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
@ -7,25 +6,20 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
public class AxesToken : IXmlPropertyToken
|
||||
{
|
||||
public string Token => "Axes";
|
||||
|
||||
public bool ReadProperty(Property prop, XmlNode token)
|
||||
{
|
||||
bool success = XmlPropertyTokens.ReadPropertyGeneric<uint>(prop, PropertyType.Axes, token);
|
||||
uint value;
|
||||
|
||||
if (success)
|
||||
if (XmlPropertyTokens.ReadPropertyGeneric(token, out value))
|
||||
{
|
||||
uint value = (uint)prop.Value;
|
||||
try
|
||||
{
|
||||
Axes axes = (Axes)value;
|
||||
prop.Value = axes;
|
||||
}
|
||||
catch
|
||||
{
|
||||
success = false;
|
||||
}
|
||||
Axes axes = (Axes)value;
|
||||
prop.Value = axes;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return success;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||
@ -33,7 +27,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
XmlElement axes = doc.CreateElement("axes");
|
||||
node.AppendChild(axes);
|
||||
|
||||
int value = (int)prop.Value;
|
||||
int value = prop.CastValue<int>();
|
||||
axes.InnerText = value.ToInvariantString();
|
||||
}
|
||||
}
|
@ -11,9 +11,9 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
{
|
||||
// BinaryStrings are encoded in base64
|
||||
string base64 = token.InnerText.Replace("\n", "");
|
||||
prop.Value = Convert.FromBase64String(base64);
|
||||
prop.Type = PropertyType.String;
|
||||
prop.Value = base64;
|
||||
|
||||
|
||||
byte[] buffer = Convert.FromBase64String(base64);
|
||||
prop.RawBuffer = buffer;
|
||||
|
@ -13,31 +13,23 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
|
||||
public bool ReadProperty(Property prop, XmlNode token)
|
||||
{
|
||||
bool success = XmlPropertyTokens.ReadPropertyGeneric<int>(prop, PropertyType.BrickColor, token);
|
||||
int value;
|
||||
|
||||
if (success)
|
||||
if (XmlPropertyTokens.ReadPropertyGeneric(token, out value))
|
||||
{
|
||||
int value = (int)prop.Value;
|
||||
BrickColor brickColor = BrickColor.FromNumber(value);
|
||||
prop.XmlToken = "BrickColor";
|
||||
prop.Value = brickColor;
|
||||
|
||||
try
|
||||
{
|
||||
BrickColor brickColor = BrickColor.FromNumber(value);
|
||||
prop.XmlToken = "BrickColor";
|
||||
prop.Value = brickColor;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Invalid BrickColor Id?
|
||||
success = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return success;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||
{
|
||||
BrickColor value = prop.Value as BrickColor;
|
||||
BrickColor value = prop.CastValue<BrickColor>();
|
||||
|
||||
XmlElement brickColor = doc.CreateElement("int");
|
||||
brickColor.InnerText = value.Number.ToInvariantString();
|
@ -46,7 +46,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
|
||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||
{
|
||||
CFrame cf = prop.Value as CFrame;
|
||||
CFrame cf = prop.CastValue<CFrame>();
|
||||
float[] components = cf.GetComponents();
|
||||
|
||||
for (int i = 0; i < 12; i++)
|
@ -51,26 +51,18 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
|
||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||
{
|
||||
if (prop.Name == "Color3uint8")
|
||||
Color3 color = prop.CastValue<Color3>();
|
||||
float[] rgb = new float[3] { color.R, color.G, color.B };
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
var handler = XmlPropertyTokens.GetHandler<Color3uint8Token>();
|
||||
handler.WriteProperty(prop, doc, node);
|
||||
}
|
||||
else
|
||||
{
|
||||
Color3 color = prop.Value as Color3;
|
||||
float[] rgb = new float[3] { color.R, color.G, color.B };
|
||||
string field = Fields[i];
|
||||
float value = rgb[i];
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
string field = Fields[i];
|
||||
float value = rgb[i];
|
||||
XmlElement channel = doc.CreateElement(field);
|
||||
channel.InnerText = value.ToInvariantString();
|
||||
|
||||
XmlElement channel = doc.CreateElement(field);
|
||||
channel.InnerText = value.ToInvariantString();
|
||||
|
||||
node.AppendChild(channel);
|
||||
}
|
||||
node.AppendChild(channel);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Xml;
|
||||
using System.Xml;
|
||||
using RobloxFiles.DataTypes;
|
||||
|
||||
namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
@ -10,29 +9,30 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
|
||||
public bool ReadProperty(Property prop, XmlNode token)
|
||||
{
|
||||
bool success = XmlPropertyTokens.ReadPropertyGeneric<uint>(prop, PropertyType.Color3, token);
|
||||
uint value;
|
||||
|
||||
if (success)
|
||||
if (XmlPropertyTokens.ReadPropertyGeneric(token, out value))
|
||||
{
|
||||
uint value = (uint)prop.Value;
|
||||
|
||||
uint r = (value >> 16) & 0xFF;
|
||||
uint g = (value >> 8) & 0xFF;
|
||||
uint b = value & 0xFF;
|
||||
|
||||
prop.Value = Color3.FromRGB(r, g, b);
|
||||
Color3uint8 result = Color3.FromRGB(r, g, b);
|
||||
prop.Value = result;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return success;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||
{
|
||||
Color3 color = prop.Value as Color3;
|
||||
Color3uint8 color = prop.CastValue<Color3uint8>();
|
||||
|
||||
uint r = (uint)(color.R * 256);
|
||||
uint g = (uint)(color.G * 256);
|
||||
uint b = (uint)(color.B * 256);
|
||||
uint r = color.R,
|
||||
g = color.G,
|
||||
b = color.B;
|
||||
|
||||
uint rgb = (255u << 24) | (r << 16) | (g << 8) | b;
|
||||
node.InnerText = rgb.ToString();
|
@ -47,7 +47,8 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
|
||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||
{
|
||||
node.InnerText = prop.Value.ToString() + ' ';
|
||||
ColorSequence value = prop.CastValue<ColorSequence>();
|
||||
node.InnerText = value.ToString() + ' ';
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.Xml;
|
||||
|
||||
using RobloxFiles.DataTypes;
|
||||
|
||||
namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
{
|
||||
public class ContentToken : IXmlPropertyToken
|
||||
@ -9,9 +11,9 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
|
||||
public bool ReadProperty(Property prop, XmlNode token)
|
||||
{
|
||||
string content = token.InnerText;
|
||||
string data = token.InnerText;
|
||||
prop.Value = new Content(data);
|
||||
prop.Type = PropertyType.String;
|
||||
prop.Value = content;
|
||||
|
||||
if (token.HasChildNodes)
|
||||
{
|
||||
@ -23,12 +25,12 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
try
|
||||
{
|
||||
// Roblox technically doesn't support this anymore, but load it anyway :P
|
||||
byte[] buffer = Convert.FromBase64String(content);
|
||||
byte[] buffer = Convert.FromBase64String(data);
|
||||
prop.RawBuffer = buffer;
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine("ContentToken: Got illegal base64 string: {0}", content);
|
||||
Console.WriteLine("ContentToken: Got illegal base64 string: {0}", data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -38,7 +40,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
|
||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||
{
|
||||
string content = prop.Value.ToString();
|
||||
string content = prop.CastValue<Content>();
|
||||
string type = "null";
|
||||
|
||||
if (prop.HasRawBuffer)
|
@ -13,7 +13,8 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
|
||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||
{
|
||||
node.InnerText = prop.Value.ToInvariantString();
|
||||
double value = prop.CastValue<double>();
|
||||
node.InnerText = value.ToInvariantString();
|
||||
}
|
||||
}
|
||||
}
|
48
XmlFormat/Tokens/Enum.cs
Normal file
48
XmlFormat/Tokens/Enum.cs
Normal file
@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Xml;
|
||||
|
||||
namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
{
|
||||
public class EnumToken : IXmlPropertyToken
|
||||
{
|
||||
public string Token => "token";
|
||||
|
||||
public bool ReadProperty(Property prop, XmlNode token)
|
||||
{
|
||||
uint value;
|
||||
|
||||
if (XmlPropertyTokens.ReadPropertyGeneric(token, out value))
|
||||
{
|
||||
Instance inst = prop.Instance;
|
||||
Type instType = inst?.GetType();
|
||||
|
||||
FieldInfo info = instType.GetField(prop.Name, Property.BindingFlags);
|
||||
|
||||
if (info != null)
|
||||
{
|
||||
Type enumType = info.FieldType;
|
||||
string item = value.ToInvariantString();
|
||||
|
||||
prop.Type = PropertyType.Enum;
|
||||
prop.Value = Enum.Parse(enumType, item);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||
{
|
||||
object rawValue = prop.Value;
|
||||
Type valueType = rawValue.GetType();
|
||||
|
||||
int signed = (int)rawValue;
|
||||
uint value = (uint)signed;
|
||||
|
||||
node.InnerText = value.ToString();
|
||||
}
|
||||
}
|
||||
}
|
@ -9,23 +9,17 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
|
||||
public bool ReadProperty(Property prop, XmlNode token)
|
||||
{
|
||||
bool success = XmlPropertyTokens.ReadPropertyGeneric<uint>(prop, PropertyType.Faces, token);
|
||||
uint value;
|
||||
|
||||
if (success)
|
||||
if (XmlPropertyTokens.ReadPropertyGeneric(token, out value))
|
||||
{
|
||||
uint value = (uint)prop.Value;
|
||||
try
|
||||
{
|
||||
Faces faces = (Faces)value;
|
||||
prop.Value = faces;
|
||||
}
|
||||
catch
|
||||
{
|
||||
success = false;
|
||||
}
|
||||
Faces faces = (Faces)value;
|
||||
prop.Value = faces;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return success;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||
@ -33,7 +27,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
XmlElement faces = doc.CreateElement("faces");
|
||||
node.AppendChild(faces);
|
||||
|
||||
int value = (int)prop.Value;
|
||||
int value = prop.CastValue<int>();
|
||||
faces.InnerText = value.ToInvariantString();
|
||||
}
|
||||
}
|
@ -13,7 +13,8 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
|
||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||
{
|
||||
node.InnerText = prop.Value.ToInvariantString();
|
||||
float value = prop.CastValue<float>();
|
||||
node.InnerText = value.ToInvariantString();
|
||||
}
|
||||
}
|
||||
}
|
@ -24,7 +24,8 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
|
||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||
{
|
||||
node.InnerText = prop.Value.ToInvariantString();
|
||||
int value = prop.CastValue<int>();
|
||||
node.InnerText = value.ToInvariantString();
|
||||
}
|
||||
}
|
||||
}
|
@ -13,7 +13,8 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
|
||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||
{
|
||||
node.InnerText = prop.Value.ToString();
|
||||
long value = prop.CastValue<long>();
|
||||
node.InnerText = value.ToString();
|
||||
}
|
||||
}
|
||||
}
|
@ -35,7 +35,8 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
|
||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||
{
|
||||
node.InnerText = prop.Value.ToString() + ' ';
|
||||
NumberRange value = prop.CastValue<NumberRange>();
|
||||
node.InnerText = value.ToString() + ' ';
|
||||
}
|
||||
}
|
||||
}
|
@ -44,7 +44,8 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
|
||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||
{
|
||||
node.InnerText = prop.Value.ToString() + ' ';
|
||||
NumberSequence value = prop.CastValue<NumberSequence>();
|
||||
node.InnerText = value.ToString() + ' ';
|
||||
}
|
||||
}
|
||||
}
|
@ -61,7 +61,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
|
||||
if (hasCustomPhysics)
|
||||
{
|
||||
var customProps = prop.Value as PhysicalProperties;
|
||||
var customProps = prop.CastValue<PhysicalProperties>();
|
||||
|
||||
var data = new Dictionary<string, float>()
|
||||
{
|
34
XmlFormat/Tokens/ProtectedString.cs
Normal file
34
XmlFormat/Tokens/ProtectedString.cs
Normal file
@ -0,0 +1,34 @@
|
||||
using System.Xml;
|
||||
using RobloxFiles.DataTypes;
|
||||
|
||||
namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
{
|
||||
public class ProtectedStringToken : IXmlPropertyToken
|
||||
{
|
||||
public string Token => "ProtectedString";
|
||||
|
||||
public bool ReadProperty(Property prop, XmlNode token)
|
||||
{
|
||||
ProtectedString contents = token.InnerText;
|
||||
prop.Type = PropertyType.String;
|
||||
prop.Value = contents;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||
{
|
||||
string value = prop.CastValue<ProtectedString>();
|
||||
|
||||
if (value.Contains("\r") || value.Contains("\n"))
|
||||
{
|
||||
XmlCDataSection cdata = doc.CreateCDataSection(value);
|
||||
node.AppendChild(cdata);
|
||||
}
|
||||
else
|
||||
{
|
||||
node.InnerText = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -39,7 +39,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
|
||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||
{
|
||||
Ray ray = prop.Value as Ray;
|
||||
Ray ray = prop.CastValue<Ray>();
|
||||
|
||||
XmlElement origin = doc.CreateElement("origin");
|
||||
XmlElement direction = doc.CreateElement("direction");
|
@ -39,7 +39,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
|
||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||
{
|
||||
Rect rect = prop.Value as Rect;
|
||||
Rect rect = prop.CastValue<Rect>();
|
||||
|
||||
XmlElement min = doc.CreateElement("min");
|
||||
Vector2Token.WriteVector2(doc, min, rect.Min);
|
@ -10,7 +10,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
{
|
||||
string refId = token.InnerText;
|
||||
prop.Type = PropertyType.Ref;
|
||||
prop.Value = refId;
|
||||
prop.XmlToken = refId;
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -19,9 +19,9 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
{
|
||||
string result = "null";
|
||||
|
||||
if (prop.Value != null && prop.Value.ToString() != "null")
|
||||
if (prop.Value != null)
|
||||
{
|
||||
Instance inst = prop.Value as Instance;
|
||||
Instance inst = prop.CastValue<Instance>();
|
||||
result = inst.Referent;
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using System.Xml;
|
||||
using RobloxFiles.DataTypes;
|
||||
|
||||
namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
{
|
||||
@ -10,17 +9,17 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
|
||||
public bool ReadProperty(Property prop, XmlNode token)
|
||||
{
|
||||
string contents = token.InnerText;
|
||||
string md5 = token.InnerText;
|
||||
prop.Type = PropertyType.SharedString;
|
||||
prop.Value = contents;
|
||||
prop.Value = new SharedString(md5);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||
{
|
||||
var BinaryStringToken = XmlPropertyTokens.GetHandler<BinaryStringToken>();
|
||||
BinaryStringToken.WriteProperty(prop, doc, node);
|
||||
SharedString value = prop.CastValue<SharedString>();
|
||||
node.InnerText = value.MD5_Key;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +1,10 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using System.Xml;
|
||||
|
||||
namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
{
|
||||
public class StringToken : IXmlPropertyToken
|
||||
{
|
||||
public string Token => "string; ProtectedString";
|
||||
public string Token => "string";
|
||||
|
||||
public bool ReadProperty(Property prop, XmlNode token)
|
||||
{
|
@ -53,7 +53,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
|
||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||
{
|
||||
UDim value = prop.Value as UDim;
|
||||
UDim value = prop.CastValue<UDim>();
|
||||
WriteUDim(doc, node, value);
|
||||
}
|
||||
}
|
@ -26,7 +26,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
|
||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||
{
|
||||
UDim2 value = prop.Value as UDim2;
|
||||
UDim2 value = prop.CastValue<UDim2>();
|
||||
|
||||
UDim xUDim = value.X;
|
||||
UDimToken.WriteUDim(doc, node, xUDim, "X");
|
@ -58,7 +58,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
|
||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||
{
|
||||
Vector2 value = prop.Value as Vector2;
|
||||
Vector2 value = prop.CastValue<Vector2>();
|
||||
WriteVector2(doc, node, value);
|
||||
}
|
||||
}
|
@ -62,7 +62,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
|
||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||
{
|
||||
Vector3 value = prop.Value as Vector3;
|
||||
Vector3 value = prop.CastValue<Vector3>();
|
||||
WriteVector3(doc, node, value);
|
||||
}
|
||||
}
|
@ -39,7 +39,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
|
||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||
{
|
||||
Vector3int16 value = prop.Value as Vector3int16;
|
||||
Vector3int16 value = prop.CastValue<Vector3int16>();
|
||||
|
||||
XmlElement x = doc.CreateElement("X");
|
||||
x.InnerText = value.X.ToString();
|
@ -35,38 +35,54 @@ namespace RobloxFiles.XmlFormat
|
||||
Handlers = tokenHandlers;
|
||||
}
|
||||
|
||||
public static bool ReadPropertyGeneric<T>(Property prop, PropertyType propType, XmlNode token) where T : struct
|
||||
public static bool ReadPropertyGeneric<T>(XmlNode token, out T outValue) where T : struct
|
||||
{
|
||||
try
|
||||
{
|
||||
string value = token.InnerText;
|
||||
Type type = typeof(T);
|
||||
|
||||
if (type == typeof(int))
|
||||
prop.Value = Formatting.ParseInt(value);
|
||||
else if (type == typeof(float))
|
||||
prop.Value = Formatting.ParseFloat(value);
|
||||
else if (type == typeof(double))
|
||||
prop.Value = Formatting.ParseDouble(value);
|
||||
object result = null;
|
||||
|
||||
if (prop.Value == null)
|
||||
if (type == typeof(int))
|
||||
result = Formatting.ParseInt(value);
|
||||
else if (type == typeof(float))
|
||||
result = Formatting.ParseFloat(value);
|
||||
else if (type == typeof(double))
|
||||
result = Formatting.ParseDouble(value);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
Type resultType = typeof(T);
|
||||
TypeConverter converter = TypeDescriptor.GetConverter(resultType);
|
||||
|
||||
object result = converter.ConvertFromString(token.InnerText);
|
||||
prop.Value = result;
|
||||
var converter = TypeDescriptor.GetConverter(resultType);
|
||||
result = converter.ConvertFromString(token.InnerText);
|
||||
}
|
||||
|
||||
prop.Type = propType;
|
||||
outValue = (T)result;
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
outValue = default(T);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool ReadPropertyGeneric<T>(Property prop, PropertyType propType, XmlNode token) where T : struct
|
||||
{
|
||||
T result;
|
||||
|
||||
if (ReadPropertyGeneric(token, out result))
|
||||
{
|
||||
prop.Type = propType;
|
||||
prop.Value = result;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static IXmlPropertyToken GetHandler(string tokenName)
|
||||
{
|
||||
IXmlPropertyToken result = null;
|
@ -6,20 +6,26 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
|
||||
namespace RobloxFiles.XmlFormat
|
||||
using RobloxFiles.DataTypes;
|
||||
using RobloxFiles.XmlFormat;
|
||||
|
||||
namespace RobloxFiles
|
||||
{
|
||||
public class XmlRobloxFile : RobloxFile
|
||||
{
|
||||
// Runtime Specific
|
||||
public readonly XmlDocument Root = new XmlDocument();
|
||||
public readonly XmlDocument XmlDocument = new XmlDocument();
|
||||
|
||||
internal Dictionary<string, Instance> Instances = new Dictionary<string, Instance>();
|
||||
internal Dictionary<string, string> SharedStrings = new Dictionary<string, string>();
|
||||
internal HashSet<string> SharedStrings = new HashSet<string>();
|
||||
|
||||
internal XmlRobloxFile()
|
||||
private Dictionary<string, string> RawMetadata = new Dictionary<string, string>();
|
||||
public Dictionary<string, string> Metadata => RawMetadata;
|
||||
|
||||
public XmlRobloxFile()
|
||||
{
|
||||
Name = "XmlRobloxFile";
|
||||
ParentLocked = true;
|
||||
Referent = "null";
|
||||
}
|
||||
|
||||
protected override void ReadFile(byte[] buffer)
|
||||
@ -27,14 +33,14 @@ namespace RobloxFiles.XmlFormat
|
||||
try
|
||||
{
|
||||
string xml = Encoding.UTF8.GetString(buffer);
|
||||
Root.LoadXml(xml);
|
||||
XmlDocument.LoadXml(xml);
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw new Exception("XmlRobloxFile: Could not read provided buffer as XML!");
|
||||
}
|
||||
|
||||
XmlNode roblox = Root.FirstChild;
|
||||
XmlNode roblox = XmlDocument.FirstChild;
|
||||
|
||||
if (roblox != null && roblox.Name == "roblox")
|
||||
{
|
||||
@ -59,6 +65,10 @@ namespace RobloxFiles.XmlFormat
|
||||
{
|
||||
XmlRobloxFileReader.ReadSharedStrings(child, this);
|
||||
}
|
||||
else if (child.Name == "Meta")
|
||||
{
|
||||
XmlRobloxFileReader.ReadMetadata(child, this);
|
||||
}
|
||||
}
|
||||
|
||||
// Query the properties.
|
||||
@ -71,7 +81,8 @@ namespace RobloxFiles.XmlFormat
|
||||
|
||||
foreach (Property refProp in refProps)
|
||||
{
|
||||
string refId = refProp.Value as string;
|
||||
string refId = refProp.XmlToken;
|
||||
refProp.XmlToken = "Ref";
|
||||
|
||||
if (Instances.ContainsKey(refId))
|
||||
{
|
||||
@ -86,26 +97,13 @@ namespace RobloxFiles.XmlFormat
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve shared strings.
|
||||
// Record shared strings.
|
||||
var sharedProps = allProps.Where(prop => prop.Type == PropertyType.SharedString);
|
||||
|
||||
foreach (Property sharedProp in sharedProps)
|
||||
{
|
||||
string md5 = sharedProp.Value as string;
|
||||
|
||||
if (SharedStrings.ContainsKey(md5))
|
||||
{
|
||||
string value = SharedStrings[md5];
|
||||
sharedProp.Value = value;
|
||||
|
||||
byte[] data = Convert.FromBase64String(value);
|
||||
sharedProp.RawBuffer = data;
|
||||
}
|
||||
else
|
||||
{
|
||||
string name = sharedProp.GetFullName();
|
||||
Console.WriteLine("XmlRobloxFile: Could not resolve shared string for {0}", name);
|
||||
}
|
||||
SharedString shared = sharedProp.CastValue<SharedString>();
|
||||
SharedStrings.Add(shared.MD5_Key);
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -125,17 +123,32 @@ namespace RobloxFiles.XmlFormat
|
||||
Instances.Clear();
|
||||
SharedStrings.Clear();
|
||||
|
||||
// First, append the metadata
|
||||
foreach (string key in Metadata.Keys)
|
||||
{
|
||||
string value = Metadata[key];
|
||||
|
||||
XmlElement meta = doc.CreateElement("Meta");
|
||||
meta.SetAttribute("name", key);
|
||||
meta.InnerText = value;
|
||||
|
||||
roblox.AppendChild(meta);
|
||||
}
|
||||
|
||||
Instance[] children = GetChildren();
|
||||
|
||||
// First, record all of the instances.
|
||||
// Record all of the instances.
|
||||
foreach (Instance inst in children)
|
||||
XmlRobloxFileWriter.RecordInstances(this, inst);
|
||||
|
||||
// Now append them into the document.
|
||||
foreach (Instance inst in children)
|
||||
{
|
||||
XmlNode instNode = XmlRobloxFileWriter.WriteInstance(inst, doc, this);
|
||||
roblox.AppendChild(instNode);
|
||||
if (inst.Archivable)
|
||||
{
|
||||
XmlNode instNode = XmlRobloxFileWriter.WriteInstance(inst, doc, this);
|
||||
roblox.AppendChild(instNode);
|
||||
}
|
||||
}
|
||||
|
||||
// Append the shared strings.
|
||||
|
Reference in New Issue
Block a user