Added support for SharedStrings and SSTR chunk type
This commit is contained in:
@ -7,43 +7,45 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
public class Color3Token : IXmlPropertyToken
|
||||
{
|
||||
public string Token => "Color3";
|
||||
private string[] LegacyFields = new string[3] { "R", "G", "B" };
|
||||
private string[] Fields = new string[3] { "R", "G", "B" };
|
||||
|
||||
public bool ReadToken(Property prop, XmlNode token)
|
||||
{
|
||||
var color3uint8 = XmlPropertyTokens.GetHandler<Color3uint8Token>();
|
||||
bool success = color3uint8.ReadToken(prop, token);
|
||||
bool success = true;
|
||||
float[] fields = new float[Fields.Length];
|
||||
|
||||
if (!success)
|
||||
for (int i = 0; i < fields.Length; i++)
|
||||
{
|
||||
// Try the legacy technique.
|
||||
float[] fields = new float[LegacyFields.Length];
|
||||
string key = Fields[i];
|
||||
|
||||
for (int i = 0; i < fields.Length; i++)
|
||||
try
|
||||
{
|
||||
string key = LegacyFields[i];
|
||||
|
||||
try
|
||||
{
|
||||
var coord = token[key];
|
||||
fields[i] = XmlPropertyTokens.ParseFloat(coord.InnerText);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var coord = token[key];
|
||||
fields[i] = XmlPropertyTokens.ParseFloat(coord.InnerText);
|
||||
}
|
||||
catch
|
||||
{
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (success)
|
||||
{
|
||||
float r = fields[0],
|
||||
g = fields[1],
|
||||
b = fields[2];
|
||||
|
||||
prop.Type = PropertyType.Color3;
|
||||
prop.Value = new Color3(r, g, b);
|
||||
|
||||
success = true;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
// Try falling back to the Color3uint8 technique...
|
||||
var color3uint8 = XmlPropertyTokens.GetHandler<Color3uint8Token>();
|
||||
success = color3uint8.ReadToken(prop, token);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
}
|
||||
|
19
XmlFormat/PropertyTokens/SharedString.cs
Normal file
19
XmlFormat/PropertyTokens/SharedString.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
|
||||
namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
{
|
||||
public class SharedStringToken : IXmlPropertyToken
|
||||
{
|
||||
public string Token => "SharedString";
|
||||
|
||||
public bool ReadToken(Property prop, XmlNode token)
|
||||
{
|
||||
string contents = token.InnerText;
|
||||
prop.Type = PropertyType.SharedString;
|
||||
prop.Value = contents;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,15 +1,51 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml;
|
||||
|
||||
namespace RobloxFiles.XmlFormat
|
||||
{
|
||||
public static class XmlDataReader
|
||||
{
|
||||
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 new Exception("XmlDataReader.ReadProperties: Provided XmlNode's class should be 'Properties'!");
|
||||
throw error("Provided XmlNode's class should be 'Properties'!");
|
||||
|
||||
foreach (XmlNode propNode in propsNode.ChildNodes)
|
||||
{
|
||||
@ -17,7 +53,7 @@ namespace RobloxFiles.XmlFormat
|
||||
XmlNode propName = propNode.Attributes.GetNamedItem("name");
|
||||
|
||||
if (propName == null)
|
||||
throw new Exception("XmlDataReader.ReadProperties: Got a property node without a 'name' attribute!");
|
||||
throw error("Got a property node without a 'name' attribute!");
|
||||
|
||||
IXmlPropertyToken tokenHandler = XmlPropertyTokens.GetHandler(propType);
|
||||
|
||||
@ -28,26 +64,28 @@ namespace RobloxFiles.XmlFormat
|
||||
prop.Instance = instance;
|
||||
|
||||
if (!tokenHandler.ReadToken(prop, propNode))
|
||||
Console.WriteLine("XmlDataReader.ReadProperties: Could not read property: " + prop.GetFullName() + '!');
|
||||
Console.WriteLine("Could not read property: " + prop.GetFullName() + '!');
|
||||
|
||||
instance.AddProperty(ref prop);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("XmlDataReader.ReadProperties: No IXmlPropertyToken found for property type: " + propType + '!');
|
||||
Console.WriteLine("No IXmlPropertyToken found for property type: " + propType + '!');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Instance ReadInstance(XmlNode instNode, XmlRobloxFile file = null)
|
||||
{
|
||||
var error = createErrorHandler("ReadInstance");
|
||||
|
||||
// Process the instance itself
|
||||
if (instNode.Name != "Item")
|
||||
throw new Exception("XmlDataReader.ReadInstance: Provided XmlNode's name should be 'Item'!");
|
||||
throw error("Provided XmlNode's name should be 'Item'!");
|
||||
|
||||
XmlNode classToken = instNode.Attributes.GetNamedItem("class");
|
||||
if (classToken == null)
|
||||
throw new Exception("XmlDataReader.ReadInstance: Got an Item without a defined 'class' attribute!");
|
||||
throw error("Got an Item without a defined 'class' attribute!");
|
||||
|
||||
Instance inst = new Instance(classToken.InnerText);
|
||||
|
||||
@ -59,7 +97,7 @@ namespace RobloxFiles.XmlFormat
|
||||
string refId = refToken.InnerText;
|
||||
|
||||
if (file.Instances.ContainsKey(refId))
|
||||
throw new Exception("XmlDataReader.ReadInstance: Got an Item with a duplicate 'referent' attribute!");
|
||||
throw error("Got an Item with a duplicate 'referent' attribute!");
|
||||
|
||||
file.Instances.Add(refId, inst);
|
||||
}
|
||||
|
@ -37,19 +37,21 @@ namespace RobloxFiles.XmlFormat
|
||||
|
||||
public static bool ReadTokenGeneric<T>(Property prop, PropertyType propType, XmlNode token) where T : struct
|
||||
{
|
||||
Type resultType = typeof(T);
|
||||
TypeConverter converter = TypeDescriptor.GetConverter(resultType);
|
||||
|
||||
if (converter != null)
|
||||
try
|
||||
{
|
||||
Type resultType = typeof(T);
|
||||
TypeConverter converter = TypeDescriptor.GetConverter(resultType);
|
||||
|
||||
object result = converter.ConvertFromString(token.InnerText);
|
||||
prop.Type = propType;
|
||||
prop.Value = result;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static IXmlPropertyToken GetHandler(string tokenName)
|
||||
|
@ -16,6 +16,7 @@ namespace RobloxFiles.XmlFormat
|
||||
// Runtime Specific
|
||||
public readonly XmlDocument Root = new XmlDocument();
|
||||
public Dictionary<string, Instance> Instances = new Dictionary<string, Instance>();
|
||||
public Dictionary<string, string> SharedStrings = new Dictionary<string, string>();
|
||||
|
||||
public void ReadFile(byte[] buffer)
|
||||
{
|
||||
@ -50,16 +51,24 @@ namespace RobloxFiles.XmlFormat
|
||||
Instance item = XmlDataReader.ReadInstance(child, this);
|
||||
item.Parent = XmlContents;
|
||||
}
|
||||
else if (child.Name == "SharedStrings")
|
||||
{
|
||||
XmlDataReader.ReadSharedStrings(child, this);
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve referent properties.
|
||||
var refProps = Instances.Values
|
||||
// Query the properties.
|
||||
var props = Instances.Values
|
||||
.SelectMany(inst => inst.Properties)
|
||||
.Where(prop => prop.Type == PropertyType.Ref);
|
||||
|
||||
.Select(pair => pair.Value);
|
||||
|
||||
// Resolve referent properties.
|
||||
var refProps = props.Where(prop => prop.Type == PropertyType.Ref);
|
||||
|
||||
foreach (Property refProp in refProps)
|
||||
{
|
||||
string refId = refProp.Value as string;
|
||||
|
||||
if (Instances.ContainsKey(refId))
|
||||
{
|
||||
Instance refInst = Instances[refId];
|
||||
@ -67,13 +76,36 @@ namespace RobloxFiles.XmlFormat
|
||||
}
|
||||
else if (refId != "null")
|
||||
{
|
||||
Console.WriteLine("XmlRobloxFile: Could not resolve reference for " + refProp.GetFullName());
|
||||
string name = refProp.GetFullName();
|
||||
Console.WriteLine("XmlRobloxFile: Could not resolve reference for {0}", name);
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve shared strings.
|
||||
var sharedProps = props.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.SetRawBuffer(data);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
string name = sharedProp.GetFullName();
|
||||
Console.WriteLine("XmlRobloxFile: Could not resolve shared string for {0}", name);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("XmlRobloxFile: No `roblox` tag found!");
|
||||
throw new Exception("XmlRobloxFile: No 'roblox' tag found!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user