Generally working out the library's flow.
I've setup a system for supporting multiple implementations for Roblox's file format. This will allow me to cover the binary format and xml format under the same general-purpose object. Haven't done much with the XML format yet, but I've been making some adjustments to the binary format implementation so that its more evenly branched out and doesn't retain more information than it needs to. I've also fixed some issues with the data-types, and documented the Instance object.
This commit is contained in:
141
Core/Instance.cs
141
Core/Instance.cs
@ -1,39 +1,59 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace Roblox
|
||||
{
|
||||
public class RobloxInstance
|
||||
/// <summary>
|
||||
/// Describes an object in Roblox's Parent->Child hierarchy.
|
||||
/// Instances can have sets of properties loaded from *.rbxl/*.rbxm files.
|
||||
/// </summary>
|
||||
public class Instance
|
||||
{
|
||||
private List<RobloxInstance> _children = new List<RobloxInstance>();
|
||||
private RobloxInstance _parent;
|
||||
public string ClassName = "";
|
||||
public List<Property> Properties = new List<Property>();
|
||||
|
||||
private List<Instance> Children = new List<Instance>();
|
||||
private Instance rawParent;
|
||||
|
||||
public string ClassName;
|
||||
public List<RobloxProperty> Properties = new List<RobloxProperty>();
|
||||
public string Name => ReadProperty("Name", ClassName);
|
||||
public override string ToString() => Name;
|
||||
|
||||
public bool IsAncestorOf(RobloxInstance other)
|
||||
/// <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)
|
||||
{
|
||||
while (other != null)
|
||||
while (descendant != null)
|
||||
{
|
||||
if (other == this)
|
||||
if (descendant == this)
|
||||
return true;
|
||||
|
||||
other = other.Parent;
|
||||
descendant = descendant.Parent;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsDescendantOf(RobloxInstance other)
|
||||
/// <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)
|
||||
{
|
||||
return other.IsAncestorOf(this);
|
||||
return ancestor.IsAncestorOf(this);
|
||||
}
|
||||
|
||||
public RobloxInstance Parent
|
||||
/// <summary>
|
||||
/// The parent of this Instance, or null if the instance is the root of a tree.<para/>
|
||||
/// Setting the value of this property will throw an exception if:<para/>
|
||||
/// - The value is set to itself.<para/>
|
||||
/// - The value is a descendant of the Instance.
|
||||
/// </summary>
|
||||
public Instance Parent
|
||||
{
|
||||
get { return _parent; }
|
||||
get { return rawParent; }
|
||||
set
|
||||
{
|
||||
if (IsAncestorOf(value))
|
||||
@ -42,50 +62,105 @@ namespace Roblox
|
||||
if (Parent == this)
|
||||
throw new Exception("Attempt to set parent to self");
|
||||
|
||||
if (_parent != null)
|
||||
_parent._children.Remove(this);
|
||||
if (rawParent != null)
|
||||
rawParent.Children.Remove(this);
|
||||
|
||||
value._children.Add(this);
|
||||
_parent = value;
|
||||
value.Children.Add(this);
|
||||
rawParent = value;
|
||||
}
|
||||
}
|
||||
|
||||
public ReadOnlyCollection<RobloxInstance> Children
|
||||
public IEnumerable<Instance> GetChildren()
|
||||
{
|
||||
get { return _children.AsReadOnly(); }
|
||||
var current = Children.ToArray();
|
||||
return current.AsEnumerable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the first 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>
|
||||
/// <returns>The instance that was found with this name, or null.</returns>
|
||||
public Instance FindFirstChild(string name)
|
||||
{
|
||||
Instance result = null;
|
||||
|
||||
var query = Children.Where(child => child.Name == name);
|
||||
if (query.Count() > 0)
|
||||
result = query.First();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <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.
|
||||
/// <para/>You can use the templated ReadProperty overload to fetch it as a specific type with a default value provided.
|
||||
/// </summary>
|
||||
/// <param name="propertyName">The name of the property to be fetched from this Instance.</param>
|
||||
/// <returns>An object reference to the value of the specified property, if it exists.</returns>
|
||||
///
|
||||
public object ReadProperty(string propertyName)
|
||||
{
|
||||
RobloxProperty property = Properties
|
||||
.Where((prop) => prop.Name == propertyName)
|
||||
.First();
|
||||
Property property = null;
|
||||
|
||||
return property.Value;
|
||||
if (query.Count() > 0)
|
||||
property = query.First();
|
||||
|
||||
return (property != null ? property.Value : null);
|
||||
}
|
||||
|
||||
public bool TryReadProperty<T>(string propertyName, out T value)
|
||||
/// <summary>
|
||||
/// Looks for a property with the specified property name, and returns it as the specified type.<para/>
|
||||
/// If it cannot be converted, the provided nullFallback value will be returned instead.
|
||||
/// </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>
|
||||
/// <param name="nullFallback">A fallback value to be returned if casting to T fails, or the property is not found.</param>
|
||||
/// <returns></returns>
|
||||
public T ReadProperty<T>(string propertyName, T nullFallback)
|
||||
{
|
||||
try
|
||||
{
|
||||
object result = ReadProperty(propertyName);
|
||||
value = (T)result;
|
||||
return (T)result;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return nullFallback;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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.
|
||||
/// </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>
|
||||
/// <param name="outValue">The value to write to if the property can be casted to T correctly.</param>
|
||||
public bool TryReadProperty<T>(string propertyName, ref T outValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
object result = ReadProperty(propertyName);
|
||||
outValue = (T)result;
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
value = default(T);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
/// <summary>
|
||||
/// Adds a property by reference to this Instance's property list.
|
||||
/// This is used during the file loading procedure.
|
||||
/// </summary>
|
||||
/// <param name="prop">A reference to the property that will be added.</param>
|
||||
public void AddProperty(ref Property prop)
|
||||
{
|
||||
var name = "";
|
||||
TryReadProperty("Name", out name);
|
||||
|
||||
return '[' + ClassName + ']' + name;
|
||||
Properties.Add(prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Roblox
|
||||
{
|
||||
@ -38,25 +34,63 @@ namespace Roblox
|
||||
Int64
|
||||
}
|
||||
|
||||
public class RobloxProperty
|
||||
public class Property
|
||||
{
|
||||
public string Name;
|
||||
public PropertyType Type;
|
||||
public object Value;
|
||||
|
||||
private byte[] RawBuffer = null;
|
||||
public bool HasRawBuffer
|
||||
{
|
||||
get
|
||||
{
|
||||
if (RawBuffer == null && Value != null)
|
||||
{
|
||||
// Infer what the buffer should be if this is a primitive.
|
||||
switch (Type)
|
||||
{
|
||||
case PropertyType.Int:
|
||||
RawBuffer = BitConverter.GetBytes((int)Value);
|
||||
break;
|
||||
case PropertyType.Int64:
|
||||
RawBuffer = BitConverter.GetBytes((long)Value);
|
||||
break;
|
||||
case PropertyType.Bool:
|
||||
RawBuffer = BitConverter.GetBytes((bool)Value);
|
||||
break;
|
||||
case PropertyType.Float:
|
||||
RawBuffer = BitConverter.GetBytes((float)Value);
|
||||
break;
|
||||
case PropertyType.Double:
|
||||
RawBuffer = BitConverter.GetBytes((double)Value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (RawBuffer != null);
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
Type PropertyType = typeof(PropertyType);
|
||||
string typeName = Enum.GetName(typeof(PropertyType), Type);
|
||||
string valueLabel = (Value != null ? Value.ToString() : "null");
|
||||
|
||||
string typeName = Enum.GetName(PropertyType, Type);
|
||||
string valueLabel;
|
||||
|
||||
if (Value != null)
|
||||
valueLabel = Value.ToString();
|
||||
else
|
||||
valueLabel = "?";
|
||||
if (Type == PropertyType.String)
|
||||
valueLabel = '"' + valueLabel + '"';
|
||||
|
||||
return string.Join(" ", typeName, Name, '=', valueLabel);
|
||||
}
|
||||
|
||||
internal void SetRawBuffer(byte[] buffer)
|
||||
{
|
||||
RawBuffer = buffer;
|
||||
}
|
||||
|
||||
public byte[] GetRawBuffer()
|
||||
{
|
||||
return RawBuffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,83 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Roblox.BinaryFormat;
|
||||
using Roblox.XmlFormat;
|
||||
|
||||
namespace Roblox
|
||||
{
|
||||
public class RobloxFile
|
||||
/// <summary>
|
||||
/// Interface which represents a RobloxFile implementation.
|
||||
/// </summary>
|
||||
public interface IRobloxFile
|
||||
{
|
||||
public List<RobloxInstance> Trunk { get; private set; }
|
||||
IReadOnlyList<Instance> Trunk { get; }
|
||||
void Initialize(byte[] buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a loaded *.rbxl/*.rbxm Roblox file.
|
||||
/// All of the surface-level Instances are stored in the RobloxFile's Trunk property.
|
||||
/// </summary>
|
||||
public class RobloxFile : IRobloxFile
|
||||
{
|
||||
public bool Initialized { get; private set; }
|
||||
public IRobloxFile InnerFile { get; private set; }
|
||||
|
||||
public IReadOnlyList<Instance> Trunk => InnerFile.Trunk;
|
||||
|
||||
public void Initialize(byte[] buffer)
|
||||
{
|
||||
if (!Initialized)
|
||||
{
|
||||
if (buffer.Length > 14)
|
||||
{
|
||||
string header = Encoding.UTF7.GetString(buffer, 0, 14);
|
||||
IRobloxFile file = null;
|
||||
|
||||
if (header == RobloxBinaryFile.MagicHeader)
|
||||
file = new RobloxBinaryFile();
|
||||
else if (header.StartsWith("<roblox"))
|
||||
file = new RobloxXmlFile();
|
||||
|
||||
if (file != null)
|
||||
{
|
||||
file.Initialize(buffer);
|
||||
InnerFile = file;
|
||||
|
||||
Initialized = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception("Unrecognized header!");
|
||||
}
|
||||
}
|
||||
|
||||
public RobloxFile(byte[] buffer)
|
||||
{
|
||||
Initialize(buffer);
|
||||
}
|
||||
|
||||
public RobloxFile(Stream stream)
|
||||
{
|
||||
byte[] buffer;
|
||||
|
||||
using (MemoryStream memoryStream = new MemoryStream())
|
||||
{
|
||||
stream.CopyTo(memoryStream);
|
||||
buffer = memoryStream.ToArray();
|
||||
}
|
||||
|
||||
Initialize(buffer);
|
||||
}
|
||||
|
||||
public RobloxFile(string filePath)
|
||||
{
|
||||
byte[] buffer = File.ReadAllBytes(filePath);
|
||||
Initialize(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user