diff --git a/BinaryFormat/BinaryFile.cs b/BinaryFormat/BinaryRobloxFile.cs similarity index 87% rename from BinaryFormat/BinaryFile.cs rename to BinaryFormat/BinaryRobloxFile.cs index 3d0e912..075cd24 100644 --- a/BinaryFormat/BinaryFile.cs +++ b/BinaryFormat/BinaryRobloxFile.cs @@ -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 = " BinaryTrunk = new List(); - public IReadOnlyList Trunk => BinaryTrunk.AsReadOnly(); + internal readonly Instance BinContents = new Instance("Folder", "BinaryRobloxFile"); + public Instance Contents => BinContents; // Runtime Specific public List Chunks = new List(); @@ -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 } } } -} +} \ No newline at end of file diff --git a/BinaryFormat/ChunkTypes/INST.cs b/BinaryFormat/ChunkTypes/INST.cs index bd43087..699b34b 100644 --- a/BinaryFormat/ChunkTypes/INST.cs +++ b/BinaryFormat/ChunkTypes/INST.cs @@ -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; } diff --git a/BinaryFormat/ChunkTypes/PRNT.cs b/BinaryFormat/ChunkTypes/PRNT.cs index 40784d2..9851e49 100644 --- a/BinaryFormat/ChunkTypes/PRNT.cs +++ b/BinaryFormat/ChunkTypes/PRNT.cs @@ -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; } } } diff --git a/BinaryFormat/ChunkTypes/PROP.cs b/BinaryFormat/ChunkTypes/PROP.cs index 8f395b1..6fb77b2 100644 --- a/BinaryFormat/ChunkTypes/PROP.cs +++ b/BinaryFormat/ChunkTypes/PROP.cs @@ -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); } diff --git a/Core/Instance.cs b/Core/Instance.cs index fc06157..aa8d505 100644 --- a/Core/Instance.cs +++ b/Core/Instance.cs @@ -5,12 +5,15 @@ using System.Linq; namespace Roblox { /// - /// 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. /// public class Instance { - public string ClassName = ""; + /// The ClassName of this Instance. + public readonly string ClassName; + + /// A list of properties that are defined under this Instance. public List Properties = new List(); private List Children = new List(); @@ -19,9 +22,29 @@ namespace Roblox public string Name => ReadProperty("Name", ClassName); public override string ToString() => Name; - /// - /// Returns true if this Instance is an ancestor to the provided Instance. - /// + /// Creates an instance using the provided ClassName. + /// The ClassName to use for this Instance. + public Instance(string className = "Instance") + { + ClassName = className; + } + + /// Creates an instance using the provided ClassName and Name. + /// The ClassName to use for this Instance. + /// The Name to use for this Instance. + 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); + } + + /// Returns true if this Instance is an ancestor to the provided Instance. /// The instance whose descendance will be tested against this Instance. public bool IsAncestorOf(Instance descendant) { @@ -36,9 +59,7 @@ namespace Roblox return false; } - /// - /// Returns true if this Instance is a descendant of the provided Instance. - /// + /// Returns true if this Instance is a descendant of the provided Instance. /// The instance whose ancestry will be tested against this Instance. public bool IsDescendantOf(Instance ancestor) { @@ -53,14 +74,17 @@ namespace Roblox /// 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 GetChildren() + /// + /// Returns a snapshot of the Instances currently parented to this Instance, as an array. + /// + public Instance[] GetChildren() { - var current = Children.ToArray(); - return current.AsEnumerable(); + return Children.ToArray(); } /// - /// 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. /// - /// The name of the instance to find. - /// The instance that was found with this name, or null. - 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; + } + + /// + /// Returns the first child of this Instance whose Name is the provided string name. + /// If the instance is not found, this returns null. + /// + /// The Name of the Instance to find. + /// Indicates if we should search descendants as well. + public Instance FindFirstChild(string name, bool recursive = false) { Instance result = null; @@ -92,6 +135,38 @@ namespace Roblox return result; } + /// + /// Returns the first Instance whose ClassName is the provided string className. If the instance is not found, this returns null. + /// + /// The ClassName of the Instance to find. + 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; + } + + /// + /// Returns a string descrbing the index traversal of this Instance, starting from its root ancestor. + /// + public string GetFullName() + { + string fullName = Name; + Instance at = Parent; + + while (at != null) + { + fullName = at.Name + '.' + fullName; + at = at.Parent; + } + + return fullName; + } + /// /// Looks for a property with the specified property name, and returns it as an object. /// 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 /// /// 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. - /// 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. + /// If it returns false, the outValue will not have its value set. /// /// The value type to convert to when finding the specified property name. /// The name of the property to be fetched from this Instance. diff --git a/Core/Property.cs b/Core/Property.cs index 1ae1abb..bc47a57 100644 --- a/Core/Property.cs +++ b/Core/Property.cs @@ -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; } } -} +} \ No newline at end of file diff --git a/Core/RobloxFile.cs b/Core/RobloxFile.cs index 3134338..4ae7062 100644 --- a/Core/RobloxFile.cs +++ b/Core/RobloxFile.cs @@ -13,8 +13,8 @@ namespace Roblox /// public interface IRobloxFile { - IReadOnlyList Trunk { get; } - void Initialize(byte[] buffer); + Instance Contents { get; } + void ReadFile(byte[] buffer); } /// @@ -26,9 +26,9 @@ namespace Roblox public bool Initialized { get; private set; } public IRobloxFile InnerFile { get; private set; } - public IReadOnlyList 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(" - + @@ -83,14 +83,40 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - +