Add support for XML files.
XML support is now implemented and should generally be working! This library should be useable now, but I still need to set it up to work as a NuGet package. If there are any bugs, let me know!
This commit is contained in:
parent
5319ae72f9
commit
50561460ac
@ -6,7 +6,7 @@ using Roblox.BinaryFormat.Chunks;
|
||||
|
||||
namespace Roblox.BinaryFormat
|
||||
{
|
||||
public class RobloxBinaryFile : IRobloxFile
|
||||
public class BinaryRobloxFile : IRobloxFile
|
||||
{
|
||||
// Header Specific
|
||||
public const string MagicHeader = "<roblox!\x89\xff\x0d\x0a\x1a\x0a";
|
||||
@ -17,8 +17,8 @@ namespace Roblox.BinaryFormat
|
||||
public byte[] Reserved;
|
||||
|
||||
// IRobloxFile
|
||||
public List<Instance> BinaryTrunk = new List<Instance>();
|
||||
public IReadOnlyList<Instance> Trunk => BinaryTrunk.AsReadOnly();
|
||||
internal readonly Instance BinContents = new Instance("Folder", "BinaryRobloxFile");
|
||||
public Instance Contents => BinContents;
|
||||
|
||||
// Runtime Specific
|
||||
public List<RobloxBinaryChunk> Chunks = new List<RobloxBinaryChunk>();
|
||||
@ -28,7 +28,7 @@ namespace Roblox.BinaryFormat
|
||||
public META Metadata;
|
||||
public INST[] Types;
|
||||
|
||||
public void Initialize(byte[] contents)
|
||||
public void ReadFile(byte[] contents)
|
||||
{
|
||||
using (MemoryStream file = new MemoryStream(contents))
|
||||
using (RobloxBinaryReader reader = new RobloxBinaryReader(file))
|
||||
@ -38,7 +38,7 @@ namespace Roblox.BinaryFormat
|
||||
string signature = Encoding.UTF7.GetString(binSignature);
|
||||
|
||||
if (signature != MagicHeader)
|
||||
throw new InvalidDataException("Provided file's signature does not match RobloxBinaryFile.MagicHeader!");
|
||||
throw new InvalidDataException("Provided file's signature does not match BinaryRobloxFile.MagicHeader!");
|
||||
|
||||
// Read header data.
|
||||
Version = reader.ReadUInt16();
|
||||
@ -69,8 +69,8 @@ namespace Roblox.BinaryFormat
|
||||
PROP.ReadProperties(this, chunk);
|
||||
break;
|
||||
case "PRNT":
|
||||
PRNT prnt = new PRNT(chunk);
|
||||
prnt.Assemble(this);
|
||||
PRNT hierarchy = new PRNT(chunk);
|
||||
hierarchy.Assemble(this);
|
||||
break;
|
||||
case "META":
|
||||
Metadata = new META(chunk);
|
||||
@ -91,4 +91,4 @@ namespace Roblox.BinaryFormat
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -26,13 +26,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
public void Allocate(RobloxBinaryFile file)
|
||||
public void Allocate(BinaryRobloxFile file)
|
||||
{
|
||||
foreach (int instId in InstanceIds)
|
||||
{
|
||||
Instance inst = new Instance();
|
||||
inst.ClassName = TypeName;
|
||||
|
||||
Instance inst = new Instance(TypeName);
|
||||
file.Instances[instId] = inst;
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
public void Assemble(RobloxBinaryFile file)
|
||||
public void Assemble(BinaryRobloxFile file)
|
||||
{
|
||||
for (int i = 0; i < NumRelations; i++)
|
||||
{
|
||||
@ -28,16 +28,14 @@
|
||||
int parentId = ParentIds[i];
|
||||
|
||||
Instance child = file.Instances[childId];
|
||||
Instance parent = null;
|
||||
|
||||
if (parentId >= 0)
|
||||
{
|
||||
Instance parent = file.Instances[parentId];
|
||||
child.Parent = parent;
|
||||
}
|
||||
parent = file.Instances[parentId];
|
||||
else
|
||||
{
|
||||
file.BinaryTrunk.Add(child);
|
||||
}
|
||||
parent = file.BinContents;
|
||||
|
||||
child.Parent = parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ namespace Roblox.BinaryFormat.Chunks
|
||||
{
|
||||
public class PROP
|
||||
{
|
||||
public static void ReadProperties(RobloxBinaryFile file, RobloxBinaryChunk chunk)
|
||||
public static void ReadProperties(BinaryRobloxFile file, RobloxBinaryChunk chunk)
|
||||
{
|
||||
RobloxBinaryReader reader = chunk.GetReader("PROP");
|
||||
|
||||
@ -38,13 +38,14 @@ namespace Roblox.BinaryFormat.Chunks
|
||||
for (int i = 0; i < instCount; i++)
|
||||
{
|
||||
int instId = ids[i];
|
||||
Instance inst = file.Instances[instId];
|
||||
|
||||
Property prop = new Property();
|
||||
prop.Name = name;
|
||||
prop.Type = propType;
|
||||
prop.Instance = inst;
|
||||
|
||||
props[i] = prop;
|
||||
|
||||
Instance inst = file.Instances[instId];
|
||||
inst.AddProperty(ref prop);
|
||||
}
|
||||
|
||||
|
114
Core/Instance.cs
114
Core/Instance.cs
@ -5,12 +5,15 @@ using System.Linq;
|
||||
namespace Roblox
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes an object in Roblox's Parent->Child hierarchy.
|
||||
/// Describes an object in Roblox's DataModel hierarchy.
|
||||
/// Instances can have sets of properties loaded from *.rbxl/*.rbxm files.
|
||||
/// </summary>
|
||||
public class Instance
|
||||
{
|
||||
public string ClassName = "";
|
||||
/// <summary>The ClassName of this Instance.</summary>
|
||||
public readonly string ClassName;
|
||||
|
||||
/// <summary>A list of properties that are defined under this Instance.</summary>
|
||||
public List<Property> Properties = new List<Property>();
|
||||
|
||||
private List<Instance> Children = new List<Instance>();
|
||||
@ -19,9 +22,29 @@ namespace Roblox
|
||||
public string Name => ReadProperty("Name", ClassName);
|
||||
public override string ToString() => Name;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this Instance is an ancestor to the provided Instance.
|
||||
/// </summary>
|
||||
/// <summary>Creates an instance using the provided ClassName.</summary>
|
||||
/// <param name="className">The ClassName to use for this Instance.</param>
|
||||
public Instance(string className = "Instance")
|
||||
{
|
||||
ClassName = className;
|
||||
}
|
||||
|
||||
/// <summary>Creates an instance using the provided ClassName and Name.</summary>
|
||||
/// <param name="className">The ClassName to use for this Instance.</param>
|
||||
/// <param name="name">The Name to use for this Instance.</param>
|
||||
public Instance(string className = "Instance", string name = "Instance")
|
||||
{
|
||||
Property propName = new Property();
|
||||
propName.Name = "Name";
|
||||
propName.Type = PropertyType.String;
|
||||
propName.Value = name;
|
||||
propName.Instance = this;
|
||||
|
||||
ClassName = className;
|
||||
Properties.Add(propName);
|
||||
}
|
||||
|
||||
/// <summary>Returns true if this Instance is an ancestor to the provided Instance.</summary>
|
||||
/// <param name="descendant">The instance whose descendance will be tested against this Instance.</param>
|
||||
public bool IsAncestorOf(Instance descendant)
|
||||
{
|
||||
@ -36,9 +59,7 @@ namespace Roblox
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this Instance is a descendant of the provided Instance.
|
||||
/// </summary>
|
||||
/// <summary>Returns true if this Instance is a descendant of the provided Instance.</summary>
|
||||
/// <param name="ancestor">The instance whose ancestry will be tested against this Instance.</param>
|
||||
public bool IsDescendantOf(Instance ancestor)
|
||||
{
|
||||
@ -53,14 +74,17 @@ namespace Roblox
|
||||
/// </summary>
|
||||
public Instance Parent
|
||||
{
|
||||
get { return rawParent; }
|
||||
get
|
||||
{
|
||||
return rawParent;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (IsAncestorOf(value))
|
||||
throw new Exception("Parent would result in circular reference.");
|
||||
|
||||
if (Parent == this)
|
||||
throw new Exception("Attempt to set parent to self");
|
||||
throw new Exception("Attempt to set parent to self.");
|
||||
|
||||
if (rawParent != null)
|
||||
rawParent.Children.Remove(this);
|
||||
@ -70,18 +94,37 @@ namespace Roblox
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<Instance> GetChildren()
|
||||
/// <summary>
|
||||
/// Returns a snapshot of the Instances currently parented to this Instance, as an array.
|
||||
/// </summary>
|
||||
public Instance[] GetChildren()
|
||||
{
|
||||
var current = Children.ToArray();
|
||||
return current.AsEnumerable();
|
||||
return Children.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the first Instance whose Name is the provided string name. If the instance is not found, this returns null.
|
||||
/// Returns a snapshot of the Instances that are descendants of this Instance, as an array.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the instance to find.</param>
|
||||
/// <returns>The instance that was found with this name, or null.</returns>
|
||||
public Instance FindFirstChild(string name)
|
||||
public Instance[] GetDescendants()
|
||||
{
|
||||
Instance[] results = GetChildren();
|
||||
|
||||
foreach (Instance child in results)
|
||||
{
|
||||
Instance[] childResults = child.GetDescendants();
|
||||
results = results.Concat(childResults).ToArray();
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the first child of this Instance whose Name is the provided string name.
|
||||
/// If the instance is not found, this returns null.
|
||||
/// </summary>
|
||||
/// <param name="name">The Name of the Instance to find.</param>
|
||||
/// <param name="recursive">Indicates if we should search descendants as well.</param>
|
||||
public Instance FindFirstChild(string name, bool recursive = false)
|
||||
{
|
||||
Instance result = null;
|
||||
|
||||
@ -92,6 +135,38 @@ namespace Roblox
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the first Instance whose ClassName is the provided string className. If the instance is not found, this returns null.
|
||||
/// </summary>
|
||||
/// <param name="className">The ClassName of the Instance to find.</param>
|
||||
public Instance FindFirstChildOfClass(string className)
|
||||
{
|
||||
Instance result = null;
|
||||
|
||||
var query = Children.Where(child => child.ClassName == className);
|
||||
if (query.Count() > 0)
|
||||
result = query.First();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string descrbing the index traversal of this Instance, starting from its root ancestor.
|
||||
/// </summary>
|
||||
public string GetFullName()
|
||||
{
|
||||
string fullName = Name;
|
||||
Instance at = Parent;
|
||||
|
||||
while (at != null)
|
||||
{
|
||||
fullName = at.Name + '.' + fullName;
|
||||
at = at.Parent;
|
||||
}
|
||||
|
||||
return fullName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks for a property with the specified property name, and returns it as an object.
|
||||
/// <para/>The resulting value may be null if the property is not serialized.
|
||||
@ -126,7 +201,7 @@ namespace Roblox
|
||||
object result = ReadProperty(propertyName);
|
||||
return (T)result;
|
||||
}
|
||||
catch (Exception e)
|
||||
catch
|
||||
{
|
||||
return nullFallback;
|
||||
}
|
||||
@ -134,7 +209,8 @@ namespace Roblox
|
||||
|
||||
/// <summary>
|
||||
/// Looks for a property with the specified property name. If found, it will try to set the value of the referenced outValue to its value.<para/>
|
||||
/// Returns true if the property was found and its value was casted to the referenced outValue. If it returns false, the outValue has not been set.
|
||||
/// Returns true if the property was found and its value was casted to the referenced outValue.<para/>
|
||||
/// If it returns false, the outValue will not have its value set.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The value type to convert to when finding the specified property name.</typeparam>
|
||||
/// <param name="propertyName">The name of the property to be fetched from this Instance.</param>
|
||||
|
@ -36,6 +36,7 @@ namespace Roblox
|
||||
|
||||
public class Property
|
||||
{
|
||||
public Instance Instance;
|
||||
public string Name;
|
||||
public PropertyType Type;
|
||||
public object Value;
|
||||
@ -47,7 +48,7 @@ namespace Roblox
|
||||
{
|
||||
if (RawBuffer == null && Value != null)
|
||||
{
|
||||
// Infer what the buffer should be if this is a primitive.
|
||||
// Improvise what the buffer should be if this is a primitive.
|
||||
switch (Type)
|
||||
{
|
||||
case PropertyType.Int:
|
||||
@ -72,6 +73,16 @@ namespace Roblox
|
||||
}
|
||||
}
|
||||
|
||||
public string GetFullName()
|
||||
{
|
||||
string result = Name;
|
||||
|
||||
if (Instance != null)
|
||||
result = Instance.GetFullName() + '.' + result;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
string typeName = Enum.GetName(typeof(PropertyType), Type);
|
||||
@ -93,4 +104,4 @@ namespace Roblox
|
||||
return RawBuffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -13,8 +13,8 @@ namespace Roblox
|
||||
/// </summary>
|
||||
public interface IRobloxFile
|
||||
{
|
||||
IReadOnlyList<Instance> Trunk { get; }
|
||||
void Initialize(byte[] buffer);
|
||||
Instance Contents { get; }
|
||||
void ReadFile(byte[] buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -26,9 +26,9 @@ namespace Roblox
|
||||
public bool Initialized { get; private set; }
|
||||
public IRobloxFile InnerFile { get; private set; }
|
||||
|
||||
public IReadOnlyList<Instance> Trunk => InnerFile.Trunk;
|
||||
public Instance Contents => InnerFile.Contents;
|
||||
|
||||
public void Initialize(byte[] buffer)
|
||||
public void ReadFile(byte[] buffer)
|
||||
{
|
||||
if (!Initialized)
|
||||
{
|
||||
@ -37,14 +37,14 @@ namespace Roblox
|
||||
string header = Encoding.UTF7.GetString(buffer, 0, 14);
|
||||
IRobloxFile file = null;
|
||||
|
||||
if (header == RobloxBinaryFile.MagicHeader)
|
||||
file = new RobloxBinaryFile();
|
||||
if (header == BinaryRobloxFile.MagicHeader)
|
||||
file = new BinaryRobloxFile();
|
||||
else if (header.StartsWith("<roblox"))
|
||||
file = new RobloxXmlFile();
|
||||
file = new XmlRobloxFile();
|
||||
|
||||
if (file != null)
|
||||
{
|
||||
file.Initialize(buffer);
|
||||
file.ReadFile(buffer);
|
||||
InnerFile = file;
|
||||
|
||||
Initialized = true;
|
||||
@ -58,7 +58,7 @@ namespace Roblox
|
||||
|
||||
public RobloxFile(byte[] buffer)
|
||||
{
|
||||
Initialize(buffer);
|
||||
ReadFile(buffer);
|
||||
}
|
||||
|
||||
public RobloxFile(Stream stream)
|
||||
@ -71,13 +71,13 @@ namespace Roblox
|
||||
buffer = memoryStream.ToArray();
|
||||
}
|
||||
|
||||
Initialize(buffer);
|
||||
ReadFile(buffer);
|
||||
}
|
||||
|
||||
public RobloxFile(string filePath)
|
||||
{
|
||||
byte[] buffer = File.ReadAllBytes(filePath);
|
||||
Initialize(buffer);
|
||||
ReadFile(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,8 +6,8 @@ namespace Roblox.DataTypes
|
||||
[Flags]
|
||||
public enum Axes
|
||||
{
|
||||
X = 0 << Axis.X,
|
||||
Y = 0 << Axis.Y,
|
||||
Z = 0 << Axis.Z,
|
||||
X = 1 << Axis.X,
|
||||
Y = 1 << Axis.Y,
|
||||
Z = 1 << Axis.Z,
|
||||
}
|
||||
}
|
||||
|
@ -26,11 +26,11 @@ namespace Roblox.DataTypes
|
||||
private const string DefaultName = "Medium stone grey";
|
||||
private const int DefaultNumber = 194;
|
||||
|
||||
internal BrickColor(int number, int rgb, string name)
|
||||
internal BrickColor(int number, uint rgb, string name)
|
||||
{
|
||||
int r = (rgb / 65536) % 256;
|
||||
int g = (rgb / 256) % 256;
|
||||
int b = rgb % 256;
|
||||
uint r = (rgb / 65536) % 256;
|
||||
uint g = (rgb / 256) % 256;
|
||||
uint b = (rgb % 256);
|
||||
|
||||
Name = name;
|
||||
Number = number;
|
||||
|
@ -18,7 +18,7 @@ namespace Roblox.DataTypes
|
||||
return string.Join(", ", R, G, B);
|
||||
}
|
||||
|
||||
public static Color3 fromRGB(int r = 0, int g = 0, int b = 0)
|
||||
public static Color3 fromRGB(uint r = 0, uint g = 0, uint b = 0)
|
||||
{
|
||||
return new Color3(r / 255f, g / 255f, b / 255f);
|
||||
}
|
||||
|
@ -3,17 +3,17 @@
|
||||
public struct ColorSequenceKeypoint
|
||||
{
|
||||
public readonly float Time;
|
||||
public readonly Color3 Color;
|
||||
public readonly Color3 Value;
|
||||
|
||||
public ColorSequenceKeypoint(float time, Color3 color)
|
||||
public ColorSequenceKeypoint(float time, Color3 value)
|
||||
{
|
||||
Time = time;
|
||||
Color = color;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Join(" ", Time, Color.R, Color.G, Color.B, 0);
|
||||
return string.Join(" ", Time, Value.R, Value.G, Value.B, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,11 +6,11 @@ namespace Roblox.DataTypes
|
||||
[Flags]
|
||||
public enum Faces
|
||||
{
|
||||
Right = 0 << NormalId.Right,
|
||||
Top = 0 << NormalId.Top,
|
||||
Back = 0 << NormalId.Back,
|
||||
Left = 0 << NormalId.Left,
|
||||
Bottom = 0 << NormalId.Bottom,
|
||||
Front = 0 << NormalId.Front,
|
||||
Right = 1 << NormalId.Right,
|
||||
Top = 1 << NormalId.Top,
|
||||
Back = 1 << NormalId.Back,
|
||||
Left = 1 << NormalId.Left,
|
||||
Bottom = 1 << NormalId.Bottom,
|
||||
Front = 1 << NormalId.Front,
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return '{' + Origin + "}, {" + Direction + '}';
|
||||
return '{' + Origin.ToString() + "}, {" + Direction.ToString() + '}';
|
||||
}
|
||||
|
||||
public Vector3 ClosestPoint(Vector3 point)
|
||||
|
@ -48,7 +48,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="BinaryFormat\BinaryChunk.cs" />
|
||||
<Compile Include="BinaryFormat\BinaryFile.cs" />
|
||||
<Compile Include="BinaryFormat\BinaryRobloxFile.cs" />
|
||||
<Compile Include="BinaryFormat\ChunkTypes\INST.cs" />
|
||||
<Compile Include="BinaryFormat\ChunkTypes\META.cs" />
|
||||
<Compile Include="BinaryFormat\ChunkTypes\PRNT.cs" />
|
||||
@ -83,14 +83,40 @@
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Core\RobloxFile.cs" />
|
||||
<Compile Include="Core\Instance.cs" />
|
||||
<Compile Include="XmlFormat\XmlFile.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\PhysicalProperties.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\NumberRange.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\NumberSequence.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\ColorSequence.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Faces.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Axes.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Color3uint8.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Color3.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Content.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\BinaryString.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Rect.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\String.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Double.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Float.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Boolean.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\BrickColor.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Enum.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Int64.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Int.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Ref.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\UDim2.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Vector2.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\CFrame.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Ray.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\UDim.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Vector3.cs" />
|
||||
<Compile Include="XmlFormat\XmlPropertyTokens.cs" />
|
||||
<Compile Include="XmlFormat\XmlDataReader.cs" />
|
||||
<Compile Include="XmlFormat\XmlRobloxFile.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="XmlFormat\TokenHandlers\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
|
31
XmlFormat/PropertyTokens/Axes.cs
Normal file
31
XmlFormat/PropertyTokens/Axes.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using System.Xml;
|
||||
using Roblox.DataTypes;
|
||||
|
||||
namespace Roblox.XmlFormat.PropertyTokens
|
||||
{
|
||||
public class AxesToken : IXmlPropertyToken
|
||||
{
|
||||
public string Token => "Axes";
|
||||
|
||||
public bool ReadToken(Property prop, XmlNode token)
|
||||
{
|
||||
bool success = XmlPropertyTokens.ReadTokenGeneric<uint>(prop, PropertyType.Axes, token);
|
||||
|
||||
if (success)
|
||||
{
|
||||
uint value = (uint)prop.Value;
|
||||
try
|
||||
{
|
||||
Axes axes = (Axes)value;
|
||||
prop.Value = axes;
|
||||
}
|
||||
catch
|
||||
{
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
}
|
||||
}
|
23
XmlFormat/PropertyTokens/BinaryString.cs
Normal file
23
XmlFormat/PropertyTokens/BinaryString.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Xml;
|
||||
|
||||
namespace Roblox.XmlFormat.PropertyTokens
|
||||
{
|
||||
public class BinaryStringToken : IXmlPropertyToken
|
||||
{
|
||||
public string Token => "BinaryString";
|
||||
|
||||
public bool ReadToken(Property prop, XmlNode token)
|
||||
{
|
||||
// BinaryStrings are encoded in base64
|
||||
string base64 = token.InnerText;
|
||||
prop.Type = PropertyType.String;
|
||||
prop.Value = base64;
|
||||
|
||||
byte[] buffer = Convert.FromBase64String(base64);
|
||||
prop.SetRawBuffer(buffer);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
14
XmlFormat/PropertyTokens/Boolean.cs
Normal file
14
XmlFormat/PropertyTokens/Boolean.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System.Xml;
|
||||
|
||||
namespace Roblox.XmlFormat.PropertyTokens
|
||||
{
|
||||
public class BoolToken : IXmlPropertyToken
|
||||
{
|
||||
public string Token => "bool";
|
||||
|
||||
public bool ReadToken(Property prop, XmlNode token)
|
||||
{
|
||||
return XmlPropertyTokens.ReadTokenGeneric<bool>(prop, PropertyType.Bool, token);
|
||||
}
|
||||
}
|
||||
}
|
35
XmlFormat/PropertyTokens/BrickColor.cs
Normal file
35
XmlFormat/PropertyTokens/BrickColor.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using System.Xml;
|
||||
using Roblox.DataTypes;
|
||||
|
||||
namespace Roblox.XmlFormat.PropertyTokens
|
||||
{
|
||||
public class BrickColorToken : IXmlPropertyToken
|
||||
{
|
||||
public string Token => "BrickColor";
|
||||
// ^ This is a lie: The token is actually int, but that would cause a name collision.
|
||||
// Since BrickColors are written as ints, the IntToken class will try to redirect
|
||||
// to this handler if it believes that its representing a BrickColor.
|
||||
|
||||
public bool ReadToken(Property prop, XmlNode token)
|
||||
{
|
||||
bool success = XmlPropertyTokens.ReadTokenGeneric<int>(prop, PropertyType.BrickColor, token);
|
||||
|
||||
if (success)
|
||||
{
|
||||
int value = (int)prop.Value;
|
||||
try
|
||||
{
|
||||
BrickColor brickColor = BrickColor.FromNumber(value);
|
||||
prop.Value = brickColor;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Invalid BrickColor Id?
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
}
|
||||
}
|
47
XmlFormat/PropertyTokens/CFrame.cs
Normal file
47
XmlFormat/PropertyTokens/CFrame.cs
Normal file
@ -0,0 +1,47 @@
|
||||
using System.Xml;
|
||||
using Roblox.DataTypes;
|
||||
|
||||
namespace Roblox.XmlFormat.PropertyTokens
|
||||
{
|
||||
public class CFrameToken : IXmlPropertyToken
|
||||
{
|
||||
public string Token => "CoordinateFrame; CFrame";
|
||||
private static string[] Coords = new string[12] { "X", "Y", "Z", "R00", "R01", "R02", "R10", "R11", "R12", "R20", "R21", "R22"};
|
||||
|
||||
public static CFrame ReadCFrame(XmlNode token)
|
||||
{
|
||||
float[] components = new float[12];
|
||||
|
||||
for (int i = 0; i < 12; i++)
|
||||
{
|
||||
string key = Coords[i];
|
||||
|
||||
try
|
||||
{
|
||||
var coord = token[key];
|
||||
components[i] = float.Parse(coord.InnerText);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return new CFrame(components);
|
||||
}
|
||||
|
||||
public bool ReadToken(Property property, XmlNode token)
|
||||
{
|
||||
CFrame result = ReadCFrame(token);
|
||||
bool success = (result != null);
|
||||
|
||||
if (success)
|
||||
{
|
||||
property.Type = PropertyType.CFrame;
|
||||
property.Value = result;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
}
|
||||
}
|
50
XmlFormat/PropertyTokens/Color3.cs
Normal file
50
XmlFormat/PropertyTokens/Color3.cs
Normal file
@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Xml;
|
||||
using Roblox.DataTypes;
|
||||
|
||||
namespace Roblox.XmlFormat.PropertyTokens
|
||||
{
|
||||
public class Color3Token : IXmlPropertyToken
|
||||
{
|
||||
public string Token => "Color3";
|
||||
private string[] LegacyFields = new string[3] { "R", "G", "B" };
|
||||
|
||||
public bool ReadToken(Property prop, XmlNode token)
|
||||
{
|
||||
var color3uint8 = XmlPropertyTokens.GetHandler<Color3uint8Token>();
|
||||
bool success = color3uint8.ReadToken(prop, token);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
// Try the legacy technique.
|
||||
float[] fields = new float[LegacyFields.Length];
|
||||
|
||||
for (int i = 0; i < fields.Length; i++)
|
||||
{
|
||||
string key = LegacyFields[i];
|
||||
|
||||
try
|
||||
{
|
||||
var coord = token[key];
|
||||
fields[i] = XmlPropertyTokens.ParseFloat(coord.InnerText);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
float r = fields[0],
|
||||
g = fields[1],
|
||||
b = fields[2];
|
||||
|
||||
prop.Type = PropertyType.Color3;
|
||||
prop.Value = new Color3(r, g, b);
|
||||
|
||||
success = true;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
}
|
||||
}
|
29
XmlFormat/PropertyTokens/Color3uint8.cs
Normal file
29
XmlFormat/PropertyTokens/Color3uint8.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Xml;
|
||||
using Roblox.DataTypes;
|
||||
|
||||
namespace Roblox.XmlFormat.PropertyTokens
|
||||
{
|
||||
public class Color3uint8Token : IXmlPropertyToken
|
||||
{
|
||||
public string Token => "Color3uint8";
|
||||
|
||||
public bool ReadToken(Property prop, XmlNode token)
|
||||
{
|
||||
bool success = XmlPropertyTokens.ReadTokenGeneric<uint>(prop, PropertyType.Color3, token);
|
||||
|
||||
if (success)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
}
|
||||
}
|
48
XmlFormat/PropertyTokens/ColorSequence.cs
Normal file
48
XmlFormat/PropertyTokens/ColorSequence.cs
Normal file
@ -0,0 +1,48 @@
|
||||
using System.Xml;
|
||||
using Roblox.DataTypes;
|
||||
|
||||
namespace Roblox.XmlFormat.PropertyTokens
|
||||
{
|
||||
public class ColorSequenceToken : IXmlPropertyToken
|
||||
{
|
||||
public string Token => "ColorSequence";
|
||||
|
||||
public bool ReadToken(Property prop, XmlNode token)
|
||||
{
|
||||
string contents = token.InnerText.Trim();
|
||||
string[] buffer = contents.Split(' ');
|
||||
|
||||
int length = buffer.Length;
|
||||
bool valid = (length % 5 == 0);
|
||||
|
||||
if (valid)
|
||||
{
|
||||
try
|
||||
{
|
||||
ColorSequenceKeypoint[] keypoints = new ColorSequenceKeypoint[length / 5];
|
||||
|
||||
for (int i = 0; i < length; i += 5)
|
||||
{
|
||||
float Time = float.Parse(buffer[i]);
|
||||
|
||||
float R = float.Parse(buffer[i + 1]);
|
||||
float G = float.Parse(buffer[i + 2]);
|
||||
float B = float.Parse(buffer[i + 3]);
|
||||
|
||||
Color3 Value = new Color3(R, G, B);
|
||||
keypoints[i / 5] = new ColorSequenceKeypoint(Time, Value);
|
||||
}
|
||||
|
||||
prop.Type = PropertyType.ColorSequence;
|
||||
prop.Value = new ColorSequence(keypoints);
|
||||
}
|
||||
catch
|
||||
{
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
}
|
||||
}
|
32
XmlFormat/PropertyTokens/Content.cs
Normal file
32
XmlFormat/PropertyTokens/Content.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Xml;
|
||||
|
||||
namespace Roblox.XmlFormat.PropertyTokens
|
||||
{
|
||||
public class ContentToken : IXmlPropertyToken
|
||||
{
|
||||
public string Token => "Content";
|
||||
|
||||
public bool ReadToken(Property prop, XmlNode token)
|
||||
{
|
||||
string content = token.InnerText;
|
||||
prop.Type = PropertyType.String;
|
||||
prop.Value = content;
|
||||
|
||||
if (token.HasChildNodes)
|
||||
{
|
||||
XmlNode childNode = token.FirstChild;
|
||||
string contentType = childNode.Name;
|
||||
|
||||
if (contentType.StartsWith("binary") || contentType == "hash")
|
||||
{
|
||||
// Roblox technically doesn't support this anymore, but load it anyway :P
|
||||
byte[] buffer = Convert.FromBase64String(content);
|
||||
prop.SetRawBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
14
XmlFormat/PropertyTokens/Double.cs
Normal file
14
XmlFormat/PropertyTokens/Double.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System.Xml;
|
||||
|
||||
namespace Roblox.XmlFormat.PropertyTokens
|
||||
{
|
||||
public class DoubleToken : IXmlPropertyToken
|
||||
{
|
||||
public string Token => "double";
|
||||
|
||||
public bool ReadToken(Property prop, XmlNode token)
|
||||
{
|
||||
return XmlPropertyTokens.ReadTokenGeneric<double>(prop, PropertyType.Double, token);
|
||||
}
|
||||
}
|
||||
}
|
14
XmlFormat/PropertyTokens/Enum.cs
Normal file
14
XmlFormat/PropertyTokens/Enum.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System.Xml;
|
||||
|
||||
namespace Roblox.XmlFormat.PropertyTokens
|
||||
{
|
||||
public class EnumToken : IXmlPropertyToken
|
||||
{
|
||||
public string Token => "token";
|
||||
|
||||
public bool ReadToken(Property prop, XmlNode token)
|
||||
{
|
||||
return XmlPropertyTokens.ReadTokenGeneric<uint>(prop, PropertyType.Enum, token);
|
||||
}
|
||||
}
|
||||
}
|
31
XmlFormat/PropertyTokens/Faces.cs
Normal file
31
XmlFormat/PropertyTokens/Faces.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using System.Xml;
|
||||
using Roblox.DataTypes;
|
||||
|
||||
namespace Roblox.XmlFormat.PropertyTokens
|
||||
{
|
||||
public class FacesToken : IXmlPropertyToken
|
||||
{
|
||||
public string Token => "Faces";
|
||||
|
||||
public bool ReadToken(Property prop, XmlNode token)
|
||||
{
|
||||
bool success = XmlPropertyTokens.ReadTokenGeneric<uint>(prop, PropertyType.Faces, token);
|
||||
|
||||
if (success)
|
||||
{
|
||||
uint value = (uint)prop.Value;
|
||||
try
|
||||
{
|
||||
Faces faces = (Faces)value;
|
||||
prop.Value = faces;
|
||||
}
|
||||
catch
|
||||
{
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
}
|
||||
}
|
24
XmlFormat/PropertyTokens/Float.cs
Normal file
24
XmlFormat/PropertyTokens/Float.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using System.Xml;
|
||||
|
||||
namespace Roblox.XmlFormat.PropertyTokens
|
||||
{
|
||||
public class FloatToken : IXmlPropertyToken
|
||||
{
|
||||
public string Token => "float";
|
||||
|
||||
public bool ReadToken(Property prop, XmlNode token)
|
||||
{
|
||||
try
|
||||
{
|
||||
float value = XmlPropertyTokens.ParseFloat(token.InnerText);
|
||||
prop.Type = PropertyType.Float;
|
||||
prop.Value = value;
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
25
XmlFormat/PropertyTokens/Int.cs
Normal file
25
XmlFormat/PropertyTokens/Int.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using System.Xml;
|
||||
|
||||
namespace Roblox.XmlFormat.PropertyTokens
|
||||
{
|
||||
public class IntToken : IXmlPropertyToken
|
||||
{
|
||||
public string Token => "int";
|
||||
|
||||
public bool ReadToken(Property prop, XmlNode token)
|
||||
{
|
||||
// BrickColors are represented by ints, see if
|
||||
// we can infer when they should be a BrickColor.
|
||||
|
||||
if (prop.Name.Contains("Color") || prop.Instance.ClassName.Contains("Color"))
|
||||
{
|
||||
var brickColorToken = XmlPropertyTokens.GetHandler<BrickColorToken>();
|
||||
return brickColorToken.ReadToken(prop, token);
|
||||
}
|
||||
else
|
||||
{
|
||||
return XmlPropertyTokens.ReadTokenGeneric<int>(prop, PropertyType.Int, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
14
XmlFormat/PropertyTokens/Int64.cs
Normal file
14
XmlFormat/PropertyTokens/Int64.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System.Xml;
|
||||
|
||||
namespace Roblox.XmlFormat.PropertyTokens
|
||||
{
|
||||
public class Int64Token : IXmlPropertyToken
|
||||
{
|
||||
public string Token => "int64";
|
||||
|
||||
public bool ReadToken(Property prop, XmlNode token)
|
||||
{
|
||||
return XmlPropertyTokens.ReadTokenGeneric<long>(prop, PropertyType.Int64, token);
|
||||
}
|
||||
}
|
||||
}
|
35
XmlFormat/PropertyTokens/NumberRange.cs
Normal file
35
XmlFormat/PropertyTokens/NumberRange.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using System.Xml;
|
||||
using Roblox.DataTypes;
|
||||
|
||||
namespace Roblox.XmlFormat.PropertyTokens
|
||||
{
|
||||
public class NumberRangeToken : IXmlPropertyToken
|
||||
{
|
||||
public string Token => "NumberRange";
|
||||
|
||||
public bool ReadToken(Property prop, XmlNode token)
|
||||
{
|
||||
string contents = token.InnerText.Trim();
|
||||
string[] buffer = contents.Split(' ');
|
||||
bool valid = (buffer.Length == 2);
|
||||
|
||||
if (valid)
|
||||
{
|
||||
try
|
||||
{
|
||||
float min = float.Parse(buffer[0]);
|
||||
float max = float.Parse(buffer[1]);
|
||||
|
||||
prop.Type = PropertyType.NumberRange;
|
||||
prop.Value = new NumberRange(min, max);
|
||||
}
|
||||
catch
|
||||
{
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
}
|
||||
}
|
45
XmlFormat/PropertyTokens/NumberSequence.cs
Normal file
45
XmlFormat/PropertyTokens/NumberSequence.cs
Normal file
@ -0,0 +1,45 @@
|
||||
using System.Xml;
|
||||
using Roblox.DataTypes;
|
||||
|
||||
namespace Roblox.XmlFormat.PropertyTokens
|
||||
{
|
||||
public class NumberSequenceToken : IXmlPropertyToken
|
||||
{
|
||||
public string Token => "NumberSequence";
|
||||
|
||||
public bool ReadToken(Property prop, XmlNode token)
|
||||
{
|
||||
string contents = token.InnerText.Trim();
|
||||
string[] buffer = contents.Split(' ');
|
||||
|
||||
int length = buffer.Length;
|
||||
bool valid = (length % 3 == 0);
|
||||
|
||||
if (valid)
|
||||
{
|
||||
try
|
||||
{
|
||||
NumberSequenceKeypoint[] keypoints = new NumberSequenceKeypoint[length / 3];
|
||||
|
||||
for (int i = 0; i < length; i += 3)
|
||||
{
|
||||
float Time = float.Parse(buffer[ i ]);
|
||||
float Value = float.Parse(buffer[i + 1]);
|
||||
float Envelope = float.Parse(buffer[i + 2]);
|
||||
|
||||
keypoints[i / 3] = new NumberSequenceKeypoint(Time, Value, Envelope);
|
||||
}
|
||||
|
||||
prop.Type = PropertyType.NumberSequence;
|
||||
prop.Value = new NumberSequence(keypoints);
|
||||
}
|
||||
catch
|
||||
{
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
}
|
||||
}
|
50
XmlFormat/PropertyTokens/PhysicalProperties.cs
Normal file
50
XmlFormat/PropertyTokens/PhysicalProperties.cs
Normal file
@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Xml;
|
||||
using Roblox.DataTypes;
|
||||
|
||||
namespace Roblox.XmlFormat.PropertyTokens
|
||||
{
|
||||
public class PhysicalPropertiesToken : IXmlPropertyToken
|
||||
{
|
||||
public string Token => "PhysicalProperties";
|
||||
|
||||
private Func<string, T> createReader<T>(Func<string, T> parse, XmlNode token) where T : struct
|
||||
{
|
||||
return new Func<string, T>(key =>
|
||||
{
|
||||
XmlElement node = token[key];
|
||||
return parse(node.InnerText);
|
||||
});
|
||||
}
|
||||
|
||||
public bool ReadToken(Property prop, XmlNode token)
|
||||
{
|
||||
var readBool = createReader(bool.Parse, token);
|
||||
var readFloat = createReader(XmlPropertyTokens.ParseFloat, token);
|
||||
|
||||
try
|
||||
{
|
||||
bool custom = readBool("CustomPhysics");
|
||||
if (custom)
|
||||
{
|
||||
prop.Value = new PhysicalProperties
|
||||
(
|
||||
readFloat("Density"),
|
||||
readFloat("Friction"),
|
||||
readFloat("Elasticity"),
|
||||
readFloat("FrictionWeight"),
|
||||
readFloat("ElasticityWeight")
|
||||
);
|
||||
|
||||
prop.Type = PropertyType.PhysicalProperties;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
40
XmlFormat/PropertyTokens/Ray.cs
Normal file
40
XmlFormat/PropertyTokens/Ray.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using System.Xml;
|
||||
using Roblox.DataTypes;
|
||||
|
||||
namespace Roblox.XmlFormat.PropertyTokens
|
||||
{
|
||||
public class RayToken : IXmlPropertyToken
|
||||
{
|
||||
public string Token => "Ray";
|
||||
private static string[] Fields = new string[2] { "origin", "direction" };
|
||||
|
||||
public bool ReadToken(Property prop, XmlNode token)
|
||||
{
|
||||
Vector3[] read = new Vector3[Fields.Length];
|
||||
|
||||
for (int i = 0; i < read.Length; i++)
|
||||
{
|
||||
string field = Fields[i];
|
||||
try
|
||||
{
|
||||
var fieldToken = token[field];
|
||||
Vector3? vector3 = Vector3Token.ReadVector3(fieldToken);
|
||||
read[i] = vector3.Value;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Vector3 origin = read[0],
|
||||
direction = read[1];
|
||||
|
||||
Ray ray = new Ray(origin, direction);
|
||||
prop.Type = PropertyType.Ray;
|
||||
prop.Value = ray;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
40
XmlFormat/PropertyTokens/Rect.cs
Normal file
40
XmlFormat/PropertyTokens/Rect.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using System.Xml;
|
||||
using Roblox.DataTypes;
|
||||
|
||||
namespace Roblox.XmlFormat.PropertyTokens
|
||||
{
|
||||
public class RectToken : IXmlPropertyToken
|
||||
{
|
||||
public string Token => "Rect2D";
|
||||
private static string[] Fields = new string[2] { "min", "max" };
|
||||
|
||||
public bool ReadToken(Property prop, XmlNode token)
|
||||
{
|
||||
Vector2[] read = new Vector2[Fields.Length];
|
||||
|
||||
for (int i = 0; i < read.Length; i++)
|
||||
{
|
||||
string field = Fields[i];
|
||||
try
|
||||
{
|
||||
var fieldToken = token[field];
|
||||
Vector2? vector2 = Vector2Token.ReadVector2(fieldToken);
|
||||
read[i] = vector2.Value;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 min = read[0],
|
||||
max = read[1];
|
||||
|
||||
Rect rect = new Rect(min, max);
|
||||
prop.Type = PropertyType.Rect;
|
||||
prop.Value = rect;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
18
XmlFormat/PropertyTokens/Ref.cs
Normal file
18
XmlFormat/PropertyTokens/Ref.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using System.Xml;
|
||||
|
||||
namespace Roblox.XmlFormat.PropertyTokens
|
||||
{
|
||||
public class RefToken : IXmlPropertyToken
|
||||
{
|
||||
public string Token => "Ref";
|
||||
|
||||
public bool ReadToken(Property prop, XmlNode token)
|
||||
{
|
||||
string refId = token.InnerText;
|
||||
prop.Type = PropertyType.Ref;
|
||||
prop.Value = refId;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
22
XmlFormat/PropertyTokens/String.cs
Normal file
22
XmlFormat/PropertyTokens/String.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
|
||||
namespace Roblox.XmlFormat.PropertyTokens
|
||||
{
|
||||
public class StringToken : IXmlPropertyToken
|
||||
{
|
||||
public string Token => "string; ProtectedString";
|
||||
|
||||
public bool ReadToken(Property prop, XmlNode token)
|
||||
{
|
||||
string contents = token.InnerText;
|
||||
prop.Type = PropertyType.String;
|
||||
prop.Value = contents;
|
||||
|
||||
byte[] buffer = Encoding.UTF8.GetBytes(contents);
|
||||
prop.SetRawBuffer(buffer);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
42
XmlFormat/PropertyTokens/UDim.cs
Normal file
42
XmlFormat/PropertyTokens/UDim.cs
Normal file
@ -0,0 +1,42 @@
|
||||
using System.Xml;
|
||||
using Roblox.DataTypes;
|
||||
|
||||
namespace Roblox.XmlFormat.PropertyTokens
|
||||
{
|
||||
public class UDimToken : IXmlPropertyToken
|
||||
{
|
||||
public string Token => "UDim";
|
||||
|
||||
public static UDim? ReadUDim(XmlNode token, string prefix = "")
|
||||
{
|
||||
try
|
||||
{
|
||||
XmlElement scaleToken = token[prefix + 'S'];
|
||||
float scale = XmlPropertyTokens.ParseFloat(scaleToken.InnerText);
|
||||
|
||||
XmlElement offsetToken = token[prefix + 'O'];
|
||||
int offset = int.Parse(offsetToken.InnerText);
|
||||
|
||||
return new UDim(scale, offset);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public bool ReadToken(Property property, XmlNode token)
|
||||
{
|
||||
UDim? result = ReadUDim(token);
|
||||
bool success = (result != null);
|
||||
|
||||
if (success)
|
||||
{
|
||||
property.Type = PropertyType.UDim;
|
||||
property.Value = result;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
}
|
||||
}
|
26
XmlFormat/PropertyTokens/UDim2.cs
Normal file
26
XmlFormat/PropertyTokens/UDim2.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using System.Xml;
|
||||
using Roblox.DataTypes;
|
||||
|
||||
namespace Roblox.XmlFormat.PropertyTokens
|
||||
{
|
||||
public class UDim2Token : IXmlPropertyToken
|
||||
{
|
||||
public string Token => "UDim2";
|
||||
|
||||
public bool ReadToken(Property property, XmlNode token)
|
||||
{
|
||||
UDim? xDim = UDimToken.ReadUDim(token, "X");
|
||||
UDim? yDim = UDimToken.ReadUDim(token, "Y");
|
||||
|
||||
if (xDim != null && yDim != null)
|
||||
{
|
||||
property.Type = PropertyType.UDim2;
|
||||
property.Value = new UDim2(xDim.Value, yDim.Value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
48
XmlFormat/PropertyTokens/Vector2.cs
Normal file
48
XmlFormat/PropertyTokens/Vector2.cs
Normal file
@ -0,0 +1,48 @@
|
||||
using System.Xml;
|
||||
using Roblox.DataTypes;
|
||||
|
||||
namespace Roblox.XmlFormat.PropertyTokens
|
||||
{
|
||||
public class Vector2Token : IXmlPropertyToken
|
||||
{
|
||||
public string Token => "Vector2";
|
||||
private static string[] Coords = new string[2] { "X", "Y" };
|
||||
|
||||
public static Vector2? ReadVector2(XmlNode token)
|
||||
{
|
||||
float[] xy = new float[2];
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
string key = Coords[i];
|
||||
|
||||
try
|
||||
{
|
||||
var coord = token[key];
|
||||
string text = coord.InnerText;
|
||||
xy[i] = XmlPropertyTokens.ParseFloat(text);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return new Vector2(xy);
|
||||
}
|
||||
|
||||
public bool ReadToken(Property property, XmlNode token)
|
||||
{
|
||||
Vector2? result = ReadVector2(token);
|
||||
bool success = (result != null);
|
||||
|
||||
if (success)
|
||||
{
|
||||
property.Type = PropertyType.Vector2;
|
||||
property.Value = result;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
}
|
||||
}
|
47
XmlFormat/PropertyTokens/Vector3.cs
Normal file
47
XmlFormat/PropertyTokens/Vector3.cs
Normal file
@ -0,0 +1,47 @@
|
||||
using System.Xml;
|
||||
using Roblox.DataTypes;
|
||||
|
||||
namespace Roblox.XmlFormat.PropertyTokens
|
||||
{
|
||||
public class Vector3Token : IXmlPropertyToken
|
||||
{
|
||||
public string Token => "Vector3";
|
||||
private static string[] Coords = new string[3] { "X", "Y", "Z" };
|
||||
|
||||
public static Vector3? ReadVector3(XmlNode token)
|
||||
{
|
||||
float[] xyz = new float[3];
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
string key = Coords[i];
|
||||
|
||||
try
|
||||
{
|
||||
var coord = token[key];
|
||||
xyz[i] = XmlPropertyTokens.ParseFloat(coord.InnerText);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return new Vector3(xyz);
|
||||
}
|
||||
|
||||
public bool ReadToken(Property property, XmlNode token)
|
||||
{
|
||||
Vector3? result = ReadVector3(token);
|
||||
bool success = (result != null);
|
||||
|
||||
if (success)
|
||||
{
|
||||
property.Type = PropertyType.Vector3;
|
||||
property.Value = result;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
}
|
||||
}
|
83
XmlFormat/XmlDataReader.cs
Normal file
83
XmlFormat/XmlDataReader.cs
Normal file
@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml;
|
||||
|
||||
namespace Roblox.XmlFormat
|
||||
{
|
||||
static class XmlDataReader
|
||||
{
|
||||
public static void ReadProperties(Instance instance, XmlNode propsNode)
|
||||
{
|
||||
if (propsNode.Name != "Properties")
|
||||
throw new Exception("XmlDataReader.ReadProperties: Provided XmlNode's class should be 'Properties'!");
|
||||
|
||||
foreach (XmlNode propNode in propsNode.ChildNodes)
|
||||
{
|
||||
string propType = propNode.Name;
|
||||
XmlNode propName = propNode.Attributes.GetNamedItem("name");
|
||||
|
||||
if (propName == null)
|
||||
throw new Exception("XmlDataReader.ReadProperties: Got a property node without a 'name' attribute!");
|
||||
|
||||
IXmlPropertyToken tokenHandler = XmlPropertyTokens.GetHandler(propType);
|
||||
|
||||
if (tokenHandler != null)
|
||||
{
|
||||
Property prop = new Property();
|
||||
prop.Name = propName.InnerText;
|
||||
prop.Instance = instance;
|
||||
|
||||
if (!tokenHandler.ReadToken(prop, propNode))
|
||||
Console.WriteLine("XmlDataReader.ReadProperties: Could not read property: " + prop.GetFullName() + '!');
|
||||
|
||||
instance.AddProperty(ref prop);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("XmlDataReader.ReadProperties: No IXmlPropertyToken found for property type: " + propType + '!');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Instance ReadInstance(XmlNode instNode, ref Dictionary<string, Instance> instances)
|
||||
{
|
||||
// Process the instance itself
|
||||
if (instNode.Name != "Item")
|
||||
throw new Exception("XmlDataReader.ReadItem: Provided XmlNode's class should be 'Item'!");
|
||||
|
||||
XmlNode classToken = instNode.Attributes.GetNamedItem("class");
|
||||
if (classToken == null)
|
||||
throw new Exception("XmlDataReader.ReadItem: Got an Item without a defined 'class' attribute!");
|
||||
|
||||
Instance inst = new Instance(classToken.InnerText);
|
||||
|
||||
// The 'reference' 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 && instances != null)
|
||||
{
|
||||
string refId = refToken.InnerText;
|
||||
if (instances.ContainsKey(refId))
|
||||
throw new Exception("XmlDataReader.ReadItem: Got an Item with a duplicate 'referent' attribute!");
|
||||
|
||||
instances.Add(refId, inst);
|
||||
}
|
||||
|
||||
// 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")
|
||||
{
|
||||
Instance child = ReadInstance(childNode, ref instances);
|
||||
child.Parent = inst;
|
||||
}
|
||||
}
|
||||
|
||||
return inst;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
|
||||
namespace Roblox.XmlFormat
|
||||
{
|
||||
public class RobloxXmlFile : IRobloxFile
|
||||
{
|
||||
private List<Instance> XmlTrunk = new List<Instance>();
|
||||
public IReadOnlyList<Instance> Trunk => XmlTrunk;
|
||||
|
||||
public void Initialize(byte[] buffer)
|
||||
{
|
||||
// TODO!
|
||||
}
|
||||
}
|
||||
}
|
96
XmlFormat/XmlPropertyTokens.cs
Normal file
96
XmlFormat/XmlPropertyTokens.cs
Normal file
@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
|
||||
namespace Roblox.XmlFormat
|
||||
{
|
||||
public interface IXmlPropertyToken
|
||||
{
|
||||
string Token { get; }
|
||||
bool ReadToken(Property prop, XmlNode token);
|
||||
}
|
||||
|
||||
public static class XmlPropertyTokens
|
||||
{
|
||||
public static IReadOnlyDictionary<string, IXmlPropertyToken> Handlers;
|
||||
|
||||
static XmlPropertyTokens()
|
||||
{
|
||||
// Initialize the PropertyToken handler singletons.
|
||||
Type IXmlPropertyToken = typeof(IXmlPropertyToken);
|
||||
|
||||
var handlerTypes = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.SelectMany(assembly => assembly.GetTypes())
|
||||
.Where(type => type != IXmlPropertyToken)
|
||||
.Where(type => IXmlPropertyToken.IsAssignableFrom(type));
|
||||
|
||||
var propTokens = handlerTypes.Select(handlerType => Activator.CreateInstance(handlerType) as IXmlPropertyToken);
|
||||
var tokenHandlers = new Dictionary<string, IXmlPropertyToken>();
|
||||
|
||||
foreach (IXmlPropertyToken propToken in propTokens)
|
||||
{
|
||||
var tokens = propToken.Token.Split(';')
|
||||
.Select(token => token.Trim())
|
||||
.ToList();
|
||||
|
||||
tokens.ForEach(token => tokenHandlers.Add(token, propToken));
|
||||
}
|
||||
|
||||
Handlers = tokenHandlers;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
object result = converter.ConvertFromString(token.InnerText);
|
||||
prop.Type = propType;
|
||||
prop.Value = result;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static IXmlPropertyToken GetHandler(string tokenName)
|
||||
{
|
||||
IXmlPropertyToken result = null;
|
||||
|
||||
if (Handlers.ContainsKey(tokenName))
|
||||
result = Handlers[tokenName];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static T GetHandler<T>() where T : IXmlPropertyToken
|
||||
{
|
||||
IXmlPropertyToken result = Handlers.Values
|
||||
.Where(token => token is T)
|
||||
.First();
|
||||
|
||||
return (T)result;
|
||||
}
|
||||
|
||||
public static float ParseFloat(string value)
|
||||
{
|
||||
float result;
|
||||
|
||||
if (value == "INF")
|
||||
result = float.PositiveInfinity;
|
||||
else if (value == "-INF")
|
||||
result = float.NegativeInfinity;
|
||||
else if (value == "NAN")
|
||||
result = float.NaN;
|
||||
else
|
||||
result = float.Parse(value);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
80
XmlFormat/XmlRobloxFile.cs
Normal file
80
XmlFormat/XmlRobloxFile.cs
Normal file
@ -0,0 +1,80 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
|
||||
namespace Roblox.XmlFormat
|
||||
{
|
||||
public class XmlRobloxFile : IRobloxFile
|
||||
{
|
||||
// IRobloxFile
|
||||
internal readonly Instance XmlContents = new Instance("Folder", "XmlRobloxFile");
|
||||
public Instance Contents => XmlContents;
|
||||
|
||||
// Runtime Specific
|
||||
public readonly XmlDocument Root = new XmlDocument();
|
||||
public Dictionary<string, Instance> Instances = new Dictionary<string, Instance>();
|
||||
|
||||
public void ReadFile(byte[] buffer)
|
||||
{
|
||||
try
|
||||
{
|
||||
string xml = Encoding.UTF8.GetString(buffer);
|
||||
Root.LoadXml(xml);
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw new Exception("XmlRobloxFile: Could not read XML!");
|
||||
}
|
||||
|
||||
XmlNode roblox = Root.FirstChild;
|
||||
|
||||
if (roblox != null && roblox.Name == "roblox")
|
||||
{
|
||||
// Verify the version we are using.
|
||||
XmlNode version = roblox.Attributes.GetNamedItem("version");
|
||||
int schemaVersion;
|
||||
|
||||
if (version == null || !int.TryParse(version.Value, out schemaVersion))
|
||||
throw new Exception("XmlRobloxFile: No version number defined!");
|
||||
else if (schemaVersion < 4)
|
||||
throw new Exception("XmlRobloxFile: Provided version must be at least 4!");
|
||||
|
||||
// Process the instances.
|
||||
foreach (XmlNode child in roblox.ChildNodes)
|
||||
{
|
||||
if (child.Name == "Item")
|
||||
{
|
||||
Instance item = XmlDataReader.ReadInstance(child, ref Instances);
|
||||
item.Parent = XmlContents;
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve references for Ref properties.
|
||||
var refProps = Instances.Values
|
||||
.SelectMany(inst => inst.Properties)
|
||||
.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];
|
||||
refProp.Value = refInst;
|
||||
}
|
||||
else if (refId != "null")
|
||||
{
|
||||
Console.WriteLine("XmlRobloxFile: Could not resolve reference for " + refProp.GetFullName());
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("XmlRobloxFile: No `roblox` tag found!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user