diff --git a/BinaryFormat/BinaryChunk.cs b/BinaryFormat/BinaryChunk.cs
index da682e2..c5f50e2 100644
--- a/BinaryFormat/BinaryChunk.cs
+++ b/BinaryFormat/BinaryChunk.cs
@@ -5,15 +5,20 @@ using LZ4;
namespace RobloxFiles.BinaryFormat
{
- public class RobloxBinaryChunk
+ ///
+ /// BinaryRobloxChunk represents a generic LZ4-compressed chunk
+ /// of data in Roblox's Binary File Format.
+ ///
+ public class BinaryRobloxChunk
{
public readonly string ChunkType;
public readonly int CompressedSize;
- public readonly byte[] CompressedData;
-
public readonly int Size;
+
public readonly byte[] Reserved;
+
+ public readonly byte[] CompressedData;
public readonly byte[] Data;
public bool HasCompressedData => (CompressedSize > 0);
@@ -23,18 +28,18 @@ namespace RobloxFiles.BinaryFormat
return ChunkType + " Chunk [" + Size + " bytes]";
}
- public RobloxBinaryReader GetReader(string chunkType)
+ public BinaryRobloxReader GetReader(string chunkType)
{
if (ChunkType == chunkType)
{
MemoryStream buffer = new MemoryStream(Data);
- return new RobloxBinaryReader(buffer);
+ return new BinaryRobloxReader(buffer);
}
throw new Exception("Expected " + chunkType + " ChunkType from the input RobloxBinaryChunk");
}
- public RobloxBinaryChunk(RobloxBinaryReader reader)
+ public BinaryRobloxChunk(BinaryRobloxReader reader)
{
byte[] bChunkType = reader.ReadBytes(4);
ChunkType = Encoding.ASCII.GetString(bChunkType);
diff --git a/BinaryFormat/BinaryReader.cs b/BinaryFormat/BinaryReader.cs
index d594935..dab005e 100644
--- a/BinaryFormat/BinaryReader.cs
+++ b/BinaryFormat/BinaryReader.cs
@@ -5,9 +5,9 @@ using System.Text;
namespace RobloxFiles.BinaryFormat
{
- public class RobloxBinaryReader : BinaryReader
+ public class BinaryRobloxReader : BinaryReader
{
- public RobloxBinaryReader(Stream stream) : base(stream) { }
+ public BinaryRobloxReader(Stream stream) : base(stream) { }
private byte[] lastStringBuffer = new byte[0] { };
public T[] ReadInterlaced(int count, Func decode) where T : struct
diff --git a/BinaryFormat/BinaryRobloxFile.cs b/BinaryFormat/BinaryRobloxFile.cs
index fa31771..bc50153 100644
--- a/BinaryFormat/BinaryRobloxFile.cs
+++ b/BinaryFormat/BinaryRobloxFile.cs
@@ -22,7 +22,7 @@ namespace RobloxFiles.BinaryFormat
public Instance Contents => BinContents;
// Runtime Specific
- public List Chunks = new List();
+ public List Chunks = new List();
public override string ToString() => GetType().Name;
public Instance[] Instances;
@@ -32,7 +32,7 @@ namespace RobloxFiles.BinaryFormat
public void ReadFile(byte[] contents)
{
using (MemoryStream file = new MemoryStream(contents))
- using (RobloxBinaryReader reader = new RobloxBinaryReader(file))
+ using (BinaryRobloxReader reader = new BinaryRobloxReader(file))
{
// Verify the signature of the file.
byte[] binSignature = reader.ReadBytes(14);
@@ -57,7 +57,7 @@ namespace RobloxFiles.BinaryFormat
{
try
{
- RobloxBinaryChunk chunk = new RobloxBinaryChunk(reader);
+ BinaryRobloxChunk chunk = new BinaryRobloxChunk(reader);
Chunks.Add(chunk);
switch (chunk.ChunkType)
@@ -67,7 +67,8 @@ namespace RobloxFiles.BinaryFormat
type.Allocate(this);
break;
case "PROP":
- PROP.ReadProperties(this, chunk);
+ PROP prop = new PROP(chunk);
+ prop.ReadProperties(this);
break;
case "PRNT":
PRNT hierarchy = new PRNT(chunk);
diff --git a/BinaryFormat/ChunkTypes/INST.cs b/BinaryFormat/ChunkTypes/INST.cs
index f42d941..e51a852 100644
--- a/BinaryFormat/ChunkTypes/INST.cs
+++ b/BinaryFormat/ChunkTypes/INST.cs
@@ -13,9 +13,9 @@
return TypeName;
}
- public INST(RobloxBinaryChunk chunk)
+ public INST(BinaryRobloxChunk chunk)
{
- using (RobloxBinaryReader reader = chunk.GetReader("INST"))
+ using (BinaryRobloxReader reader = chunk.GetReader("INST"))
{
TypeIndex = reader.ReadInt32();
TypeName = reader.ReadString();
diff --git a/BinaryFormat/ChunkTypes/META.cs b/BinaryFormat/ChunkTypes/META.cs
index e07cc04..029ce0c 100644
--- a/BinaryFormat/ChunkTypes/META.cs
+++ b/BinaryFormat/ChunkTypes/META.cs
@@ -7,9 +7,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
public int NumEntries;
public Dictionary Entries;
- public META(RobloxBinaryChunk chunk)
+ public META(BinaryRobloxChunk chunk)
{
- using (RobloxBinaryReader reader = chunk.GetReader("META"))
+ using (BinaryRobloxReader reader = chunk.GetReader("META"))
{
NumEntries = reader.ReadInt32();
Entries = new Dictionary(NumEntries);
diff --git a/BinaryFormat/ChunkTypes/PRNT.cs b/BinaryFormat/ChunkTypes/PRNT.cs
index 15ef4ec..6cb941c 100644
--- a/BinaryFormat/ChunkTypes/PRNT.cs
+++ b/BinaryFormat/ChunkTypes/PRNT.cs
@@ -8,9 +8,9 @@
public readonly int[] ChildrenIds;
public readonly int[] ParentIds;
- public PRNT(RobloxBinaryChunk chunk)
+ public PRNT(BinaryRobloxChunk chunk)
{
- using (RobloxBinaryReader reader = chunk.GetReader("PRNT"))
+ using (BinaryRobloxReader reader = chunk.GetReader("PRNT"))
{
Format = reader.ReadByte();
NumRelations = reader.ReadInt32();
diff --git a/BinaryFormat/ChunkTypes/PROP.cs b/BinaryFormat/ChunkTypes/PROP.cs
index 99d13b7..24baccb 100644
--- a/BinaryFormat/ChunkTypes/PROP.cs
+++ b/BinaryFormat/ChunkTypes/PROP.cs
@@ -9,27 +9,34 @@ namespace RobloxFiles.BinaryFormat.Chunks
{
public class PROP
{
- public static void ReadProperties(BinaryRobloxFile file, RobloxBinaryChunk chunk)
- {
- RobloxBinaryReader reader = chunk.GetReader("PROP");
+ public readonly string Name;
+ public readonly int TypeIndex;
+ public readonly PropertyType Type;
- // Read the property's header info.
- int typeIndex = reader.ReadInt32();
- string name = reader.ReadString();
- PropertyType propType;
+ private BinaryRobloxReader Reader;
+
+ public PROP(BinaryRobloxChunk chunk)
+ {
+ Reader = chunk.GetReader("PROP");
+
+ TypeIndex = Reader.ReadInt32();
+ Name = Reader.ReadString();
try
{
- byte typeId = reader.ReadByte();
- propType = (PropertyType)typeId;
+ byte propType = Reader.ReadByte();
+ Type = (PropertyType)propType;
}
catch
{
- propType = PropertyType.Unknown;
+ Type = PropertyType.Unknown;
}
- // Create access arrays for the objects we will be adding properties to.
- INST type = file.Types[typeIndex];
+ }
+
+ public void ReadProperties(BinaryRobloxFile file)
+ {
+ INST type = file.Types[TypeIndex];
Property[] props = new Property[type.NumInstances];
int[] ids = type.InstanceIds;
@@ -37,16 +44,16 @@ namespace RobloxFiles.BinaryFormat.Chunks
for (int i = 0; i < instCount; i++)
{
- int instId = ids[i];
- Instance inst = file.Instances[instId];
+ int id = ids[i];
+ Instance instance = file.Instances[id];
Property prop = new Property();
- prop.Name = name;
- prop.Type = propType;
- prop.Instance = inst;
+ prop.Name = Name;
+ prop.Type = Type;
+ prop.Instance = instance;
props[i] = prop;
- inst.AddProperty(ref prop);
+ instance.AddProperty(ref prop);
}
// Setup some short-hand functions for actions frequently used during the read procedure.
@@ -59,20 +66,20 @@ namespace RobloxFiles.BinaryFormat.Chunks
}
});
- var readInts = new Func(() => reader.ReadInts(instCount));
- var readFloats = new Func(() => reader.ReadFloats(instCount));
+ var readInts = new Func(() => Reader.ReadInts(instCount));
+ var readFloats = new Func(() => Reader.ReadFloats(instCount));
// Read the property data based on the property type.
- switch (propType)
+ switch (Type)
{
case PropertyType.String:
loadProperties(i =>
{
- string result = reader.ReadString();
+ string result = Reader.ReadString();
// Leave an access point for the original byte sequence, in case this is a BinaryString.
// This will allow the developer to read the sequence without any mangling from C# strings.
- byte[] buffer = reader.GetLastStringBuffer();
+ byte[] buffer = Reader.GetLastStringBuffer();
props[i].SetRawBuffer(buffer);
return result;
@@ -80,7 +87,7 @@ namespace RobloxFiles.BinaryFormat.Chunks
break;
case PropertyType.Bool:
- loadProperties(i => reader.ReadBoolean());
+ loadProperties(i => Reader.ReadBoolean());
break;
case PropertyType.Int:
int[] ints = readInts();
@@ -91,7 +98,7 @@ namespace RobloxFiles.BinaryFormat.Chunks
loadProperties(i => floats[i]);
break;
case PropertyType.Double:
- loadProperties(i => reader.ReadDouble());
+ loadProperties(i => Reader.ReadDouble());
break;
case PropertyType.UDim:
float[] UDim_Scales = readFloats();
@@ -127,10 +134,10 @@ namespace RobloxFiles.BinaryFormat.Chunks
case PropertyType.Ray:
loadProperties(i =>
{
- float[] rawOrigin = reader.ReadFloats(3);
+ float[] rawOrigin = Reader.ReadFloats(3);
Vector3 origin = new Vector3(rawOrigin);
- float[] rawDirection = reader.ReadFloats(3);
+ float[] rawDirection = Reader.ReadFloats(3);
Vector3 direction = new Vector3(rawDirection);
return new Ray(origin, direction);
@@ -140,7 +147,7 @@ namespace RobloxFiles.BinaryFormat.Chunks
case PropertyType.Faces:
loadProperties(i =>
{
- byte faces = reader.ReadByte();
+ byte faces = Reader.ReadByte();
return (Faces)faces;
});
@@ -148,7 +155,7 @@ namespace RobloxFiles.BinaryFormat.Chunks
case PropertyType.Axes:
loadProperties(i =>
{
- byte axes = reader.ReadByte();
+ byte axes = Reader.ReadByte();
return (Axes)axes;
});
@@ -213,7 +220,7 @@ namespace RobloxFiles.BinaryFormat.Chunks
loadProperties(i =>
{
- int normXY = reader.ReadByte();
+ int normXY = Reader.ReadByte();
if (normXY > 0)
{
@@ -237,13 +244,13 @@ namespace RobloxFiles.BinaryFormat.Chunks
R2.X, R2.Y, R2.Z,
};
}
- else if (propType == PropertyType.Quaternion)
+ else if (Type == PropertyType.Quaternion)
{
- float qx = reader.ReadFloat(), qy = reader.ReadFloat(),
- qz = reader.ReadFloat(), qw = reader.ReadFloat();
+ float qx = Reader.ReadFloat(), qy = Reader.ReadFloat(),
+ qz = Reader.ReadFloat(), qw = Reader.ReadFloat();
- Quaternion quat = new Quaternion(qx, qy, qz, qw);
- var rotation = quat.ToCFrame();
+ Quaternion quaternion = new Quaternion(qx, qy, qz, qw);
+ var rotation = quaternion.ToCFrame();
return rotation.GetComponents();
}
@@ -253,7 +260,7 @@ namespace RobloxFiles.BinaryFormat.Chunks
for (int m = 0; m < 9; m++)
{
- float value = reader.ReadFloat();
+ float value = Reader.ReadFloat();
matrix[m] = value;
}
@@ -284,12 +291,12 @@ namespace RobloxFiles.BinaryFormat.Chunks
// TODO: I want to map these values to actual Roblox enums, but I'll have to add an
// interpreter for the JSON API Dump to do it properly.
- uint[] enums = reader.ReadInterlaced(instCount, BitConverter.ToUInt32);
+ uint[] enums = Reader.ReadInterlaced(instCount, BitConverter.ToUInt32);
loadProperties(i => enums[i]);
break;
case PropertyType.Ref:
- int[] instIds = reader.ReadInstanceIds(instCount);
+ int[] instIds = Reader.ReadInstanceIds(instCount);
loadProperties(i =>
{
@@ -301,9 +308,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
case PropertyType.Vector3int16:
loadProperties(i =>
{
- short x = reader.ReadInt16(),
- y = reader.ReadInt16(),
- z = reader.ReadInt16();
+ short x = Reader.ReadInt16(),
+ y = Reader.ReadInt16(),
+ z = Reader.ReadInt16();
return new Vector3int16(x, y, z);
});
@@ -312,14 +319,14 @@ namespace RobloxFiles.BinaryFormat.Chunks
case PropertyType.NumberSequence:
loadProperties(i =>
{
- int numKeys = reader.ReadInt32();
+ int numKeys = Reader.ReadInt32();
var keypoints = new NumberSequenceKeypoint[numKeys];
for (int key = 0; key < numKeys; key++)
{
- float Time = reader.ReadFloat(),
- Value = reader.ReadFloat(),
- Envelope = reader.ReadFloat();
+ float Time = Reader.ReadFloat(),
+ Value = Reader.ReadFloat(),
+ Envelope = Reader.ReadFloat();
keypoints[key] = new NumberSequenceKeypoint(Time, Value, Envelope);
}
@@ -331,23 +338,20 @@ namespace RobloxFiles.BinaryFormat.Chunks
case PropertyType.ColorSequence:
loadProperties(i =>
{
- int numKeys = reader.ReadInt32();
+ int numKeys = Reader.ReadInt32();
var keypoints = new ColorSequenceKeypoint[numKeys];
for (int key = 0; key < numKeys; key++)
{
- float Time = reader.ReadFloat(),
- R = reader.ReadFloat(),
- G = reader.ReadFloat(),
- B = reader.ReadFloat();
+ float Time = Reader.ReadFloat(),
+ R = Reader.ReadFloat(),
+ G = Reader.ReadFloat(),
+ B = Reader.ReadFloat();
- Color3 Color = new Color3(R, G, B);
- keypoints[key] = new ColorSequenceKeypoint(Time, Color);
+ Color3 Value = new Color3(R, G, B);
+ byte[] Reserved = Reader.ReadBytes(4);
- // ColorSequenceKeypoint has an unused `Envelope` float which has to be read.
- // Roblox Studio writes it because it does an std::memcpy call to the C++ type.
- // If we skip it, the stream will become misaligned.
- reader.ReadBytes(4);
+ keypoints[key] = new ColorSequenceKeypoint(Time, Value, Reserved);
}
return new ColorSequence(keypoints);
@@ -357,8 +361,8 @@ namespace RobloxFiles.BinaryFormat.Chunks
case PropertyType.NumberRange:
loadProperties(i =>
{
- float min = reader.ReadFloat();
- float max = reader.ReadFloat();
+ float min = Reader.ReadFloat();
+ float max = Reader.ReadFloat();
return new NumberRange(min, max);
});
@@ -380,15 +384,15 @@ namespace RobloxFiles.BinaryFormat.Chunks
case PropertyType.PhysicalProperties:
loadProperties(i =>
{
- bool custom = reader.ReadBoolean();
+ bool custom = Reader.ReadBoolean();
if (custom)
{
- float Density = reader.ReadFloat(),
- Friction = reader.ReadFloat(),
- Elasticity = reader.ReadFloat(),
- FrictionWeight = reader.ReadFloat(),
- ElasticityWeight = reader.ReadFloat();
+ float Density = Reader.ReadFloat(),
+ Friction = Reader.ReadFloat(),
+ Elasticity = Reader.ReadFloat(),
+ FrictionWeight = Reader.ReadFloat(),
+ ElasticityWeight = Reader.ReadFloat();
return new PhysicalProperties
(
@@ -405,9 +409,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
break;
case PropertyType.Color3uint8:
- byte[] color3uint8_R = reader.ReadBytes(instCount),
- color3uint8_G = reader.ReadBytes(instCount),
- color3uint8_B = reader.ReadBytes(instCount);
+ byte[] color3uint8_R = Reader.ReadBytes(instCount),
+ color3uint8_G = Reader.ReadBytes(instCount),
+ color3uint8_B = Reader.ReadBytes(instCount);
loadProperties(i =>
{
@@ -420,7 +424,7 @@ namespace RobloxFiles.BinaryFormat.Chunks
break;
case PropertyType.Int64:
- long[] int64s = reader.ReadInterlaced(instCount, (buffer, start) =>
+ long[] int64s = Reader.ReadInterlaced(instCount, (buffer, start) =>
{
long result = BitConverter.ToInt64(buffer, start);
return (long)((ulong)result >> 1) ^ (-(result & 1));
@@ -430,7 +434,7 @@ namespace RobloxFiles.BinaryFormat.Chunks
break;
}
- reader.Dispose();
+ Reader.Dispose();
}
}
}
diff --git a/Core/RobloxFile.cs b/Core/RobloxFile.cs
deleted file mode 100644
index 589b2f0..0000000
--- a/Core/RobloxFile.cs
+++ /dev/null
@@ -1,91 +0,0 @@
-using System;
-using System.IO;
-using System.Text;
-
-using RobloxFiles.BinaryFormat;
-using RobloxFiles.XmlFormat;
-
-namespace RobloxFiles
-{
- ///
- /// Interface which represents a RobloxFile implementation.
- ///
- public interface IRobloxFile
- {
- Instance Contents { get; }
- void ReadFile(byte[] buffer);
- }
-
- ///
- /// Represents a loaded *.rbxl/*.rbxm Roblox file.
- /// All of the surface-level Instances are stored in the RobloxFile's Trunk property.
- ///
- public class RobloxFile : IRobloxFile
- {
- public bool Initialized { get; private set; }
- public IRobloxFile InnerFile { get; private set; }
-
- public Instance Contents => InnerFile.Contents;
-
- public void ReadFile(byte[] buffer)
- {
- if (!Initialized)
- {
- if (buffer.Length > 14)
- {
- string header = Encoding.UTF7.GetString(buffer, 0, 14);
- IRobloxFile file = null;
-
- if (header == BinaryRobloxFile.MagicHeader)
- file = new BinaryRobloxFile();
- else if (header.StartsWith("
- /// Treats the provided string as if you were indexing a specific child or descendant of the `RobloxFile.Contents` folder.
- /// The provided string can either be:
- /// - The name of a child that is parented to RobloxFile.Contents ( Example: RobloxFile["Workspace"] )
- /// - A period (.) separated path to a descendant of RobloxFile.Contents ( Example: RobloxFile["Workspace.Terrain"] )
- /// This will throw an exception if any instance in the traversal is not found.
- ///
- public Instance this[string accessor] => Contents[accessor];
- }
-}
diff --git a/DataTypes/ColorSequenceKeypoint.cs b/DataTypes/ColorSequenceKeypoint.cs
index 662fb42..4c007cd 100644
--- a/DataTypes/ColorSequenceKeypoint.cs
+++ b/DataTypes/ColorSequenceKeypoint.cs
@@ -4,11 +4,13 @@
{
public readonly float Time;
public readonly Color3 Value;
+ public readonly byte[] Reserved;
- public ColorSequenceKeypoint(float time, Color3 value)
+ public ColorSequenceKeypoint(float time, Color3 value, byte[] reserved = null)
{
Time = time;
Value = value;
+ Reserved = reserved;
}
public override string ToString()
diff --git a/DataTypes/Ray.cs b/DataTypes/Ray.cs
index 8bc4647..1496ee5 100644
--- a/DataTypes/Ray.cs
+++ b/DataTypes/Ray.cs
@@ -34,10 +34,10 @@
public Vector3 ClosestPoint(Vector3 point)
{
Vector3 result = Origin;
- float t = Direction.Dot(point - result);
+ float dist = Direction.Dot(point - result);
- if (t >= 0)
- result += (Direction * t);
+ if (dist >= 0)
+ result += (Direction * dist);
return result;
}
@@ -45,7 +45,7 @@
public float Distance(Vector3 point)
{
Vector3 closestPoint = ClosestPoint(point);
- return (closestPoint - point).Magnitude;
+ return (point - closestPoint).Magnitude;
}
}
}
diff --git a/Interfaces/IRobloxFile.cs b/Interfaces/IRobloxFile.cs
new file mode 100644
index 0000000..c82edab
--- /dev/null
+++ b/Interfaces/IRobloxFile.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace RobloxFiles
+{
+ ///
+ /// Interface which represents a RobloxFile implementation.
+ ///
+ public interface IRobloxFile
+ {
+ Instance Contents { get; }
+ void ReadFile(byte[] buffer);
+ }
+}
diff --git a/Interfaces/IXmlPropertyToken.cs b/Interfaces/IXmlPropertyToken.cs
new file mode 100644
index 0000000..36251f2
--- /dev/null
+++ b/Interfaces/IXmlPropertyToken.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml;
+
+namespace RobloxFiles.XmlFormat
+{
+ public interface IXmlPropertyToken
+ {
+ string Token { get; }
+ bool ReadToken(Property prop, XmlNode token);
+ }
+}
diff --git a/RobloxFile.cs b/RobloxFile.cs
new file mode 100644
index 0000000..ff104e4
--- /dev/null
+++ b/RobloxFile.cs
@@ -0,0 +1,149 @@
+using System;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+
+using RobloxFiles.BinaryFormat;
+using RobloxFiles.XmlFormat;
+
+namespace RobloxFiles
+{
+ ///
+ /// Represents a loaded *.rbxl/*.rbxm Roblox file.
+ /// All of the surface-level Instances are stored in the RobloxFile's 'Contents' property.
+ ///
+ public class RobloxFile : IRobloxFile
+ {
+ ///
+ /// Indicates if this RobloxFile has loaded data already.
+ ///
+ public bool Initialized { get; private set; }
+
+ ///
+ /// A reference to the inner IRobloxFile implementation that this RobloxFile opened with.
+ /// It can be a BinaryRobloxFile, or an XmlRobloxFile.
+ ///
+ public IRobloxFile InnerFile { get; private set; }
+
+ ///
+ /// A reference to a Folder Instance that stores all of the contents that were loaded.
+ ///
+ public Instance Contents => InnerFile.Contents;
+
+ ///
+ /// Initializes the RobloxFile from the provided buffer, if it hasn't been Initialized yet.
+ ///
+ ///
+ public void ReadFile(byte[] buffer)
+ {
+ if (!Initialized)
+ {
+ if (buffer.Length > 14)
+ {
+ string header = Encoding.UTF7.GetString(buffer, 0, 14);
+ IRobloxFile file = null;
+
+ if (header == BinaryRobloxFile.MagicHeader)
+ file = new BinaryRobloxFile();
+ else if (header.StartsWith("
+ /// Creates a RobloxFile from a provided byte sequence that represents the file.
+ ///
+ ///
+ private RobloxFile(byte[] buffer)
+ {
+ ReadFile(buffer);
+ }
+
+ ///
+ /// Opens a Roblox file from a byte sequence that represents the file.
+ ///
+ /// A byte sequence that represents the file.
+ public static RobloxFile Open(byte[] buffer)
+ {
+ return new RobloxFile(buffer);
+ }
+
+ ///
+ /// Opens a Roblox file by reading from a provided Stream.
+ ///
+ /// The stream to read the Roblox file from.
+ public static RobloxFile Open(Stream stream)
+ {
+ byte[] buffer;
+
+ using (MemoryStream memoryStream = new MemoryStream())
+ {
+ stream.CopyTo(memoryStream);
+ buffer = memoryStream.ToArray();
+ }
+
+ return Open(buffer);
+ }
+
+ ///
+ /// Opens a Roblox file from a provided file path.
+ ///
+ /// A path to a Roblox file to be opened.
+ public static RobloxFile Open(string filePath)
+ {
+ byte[] buffer = File.ReadAllBytes(filePath);
+ return Open(buffer);
+ }
+
+ ///
+ /// Creates and runs a Task to open a Roblox file from a byte sequence that represents the file.
+ ///
+ /// A byte sequence that represents the file.
+ public static Task OpenAsync(byte[] buffer)
+ {
+ return Task.Run(() => Open(buffer));
+ }
+
+ ///
+ /// Creates and runs a Task to open a Roblox file using a provided Stream.
+ ///
+ /// The stream to read the Roblox file from.
+ public static Task OpenAsync(Stream stream)
+ {
+ return Task.Run(() => Open(stream));
+ }
+
+ ///
+ /// Opens a Roblox file from a provided file path.
+ ///
+ /// A path to a Roblox file to be opened.
+ public static Task OpenAsync(string filePath)
+ {
+ return Task.Run(() => Open(filePath));
+ }
+
+ ///
+ /// Allows you to access a child/descendant of this file's contents, and/or one of its properties.
+ /// The provided string should be a period-separated (.) path to what you wish to access.
+ /// This will throw an exception if any part of the path cannot be found.
+ ///
+ /// ~ Examples ~
+ /// var terrain = robloxFile["Workspace.Terrain"] as Instance;
+ /// var currentCamera = robloxFile["Workspace.CurrentCamera"] as Property;
+ ///
+ ///
+ public object this[string accessor] => Contents[accessor];
+ }
+}
diff --git a/RobloxFileFormat.csproj b/RobloxFileFormat.csproj
index 6466d66..c706faf 100644
--- a/RobloxFileFormat.csproj
+++ b/RobloxFileFormat.csproj
@@ -69,10 +69,10 @@
-
-
-
-
+
+
+
+
@@ -86,6 +86,8 @@
+
+
@@ -97,6 +99,7 @@
+
diff --git a/Core/Enums.cs b/Tree/Enums.cs
similarity index 99%
rename from Core/Enums.cs
rename to Tree/Enums.cs
index 785326f..78a40b8 100644
--- a/Core/Enums.cs
+++ b/Tree/Enums.cs
@@ -376,9 +376,10 @@ namespace RobloxFiles.Enums
public enum ContextActionPriority
{
Low = 1000,
- Default = 2000,
- Medium,
- High = 3000
+ Medium = 2000,
+ High = 3000,
+
+ Default = Medium
}
public enum ContextActionResult
diff --git a/Core/Instance.cs b/Tree/Instance.cs
similarity index 73%
rename from Core/Instance.cs
rename to Tree/Instance.cs
index 3210d60..4160707 100644
--- a/Core/Instance.cs
+++ b/Tree/Instance.cs
@@ -20,6 +20,7 @@ namespace RobloxFiles
private List Children = new List();
private Instance rawParent;
+ /// The name of this Instance, if a Name property is defined.
public string Name => ReadProperty("Name", ClassName);
public override string ToString() => Name;
@@ -108,15 +109,19 @@ namespace RobloxFiles
///
public Instance[] GetDescendants()
{
- Instance[] results = GetChildren();
+ List results = new List();
- foreach (Instance child in results)
+ foreach (Instance child in Children)
{
- Instance[] childResults = child.GetDescendants();
- results = results.Concat(childResults).ToArray();
+ // Add this child to the results.
+ results.Add(child);
+
+ // Add its descendants to the results.
+ Instance[] descendants = child.GetDescendants();
+ results.AddRange(descendants);
}
- return results;
+ return results.ToArray();
}
///
@@ -128,23 +133,73 @@ namespace RobloxFiles
public Instance FindFirstChild(string name, bool recursive = false)
{
Instance result = null;
+ var query = Children.Where((child) => name == child.Name);
- var query = Children.Where(child => child.Name == name);
if (query.Count() > 0)
+ {
result = query.First();
+ }
+ else if (recursive)
+ {
+ foreach (Instance child in Children)
+ {
+ Instance found = child.FindFirstChild(name, true);
+
+ if (found != null)
+ {
+ result = found;
+ break;
+ }
+ }
+ }
return result;
}
+ ///
+ /// Returns the first ancestor 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.
+ public Instance FindFirstAncestor(string name)
+ {
+ Instance ancestor = Parent;
+
+ while (ancestor != null)
+ {
+ if (ancestor.Name == name)
+ break;
+
+ ancestor = ancestor.Parent;
+ }
+
+ return ancestor;
+ }
+
+ public Instance FindFirstAncestorOfClass(string className)
+ {
+ Instance ancestor = Parent;
+
+ while (ancestor != null)
+ {
+ if (ancestor.ClassName == className)
+ break;
+
+ ancestor = ancestor.Parent;
+ }
+
+ return ancestor;
+ }
+
///
/// 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)
+ public Instance FindFirstChildOfClass(string className, bool recursive = false)
{
Instance result = null;
+ var query = Children.Where((child) => className == child.ClassName);
- var query = Children.Where(child => child.ClassName == className);
if (query.Count() > 0)
result = query.First();
@@ -242,13 +297,16 @@ namespace RobloxFiles
}
///
- /// Treats the provided string as if you were indexing a specific child or descendant of this Instance.
- /// The provided string can either be:
- /// - The name of a child that is parented to this Instance. ( Example: game["Workspace"] )
- /// - A period-separated path to a descendant of this Instance. ( Example: game["Workspace.Terrain"] )
- /// This will throw an exception if any instance in the traversal is not found.
+ /// Allows you to access a child/descendant of this Instance, and/or one of its properties.
+ /// The provided string should be a period-separated (.) path to what you wish to access.
+ /// This will throw an exception if any part of the path cannot be found.
+ ///
+ /// ~ Examples ~
+ /// var terrain = robloxFile["Workspace.Terrain"] as Instance;
+ /// var currentCamera = robloxFile["Workspace.CurrentCamera"] as Property;
+ ///
///
- public Instance this[string accessor]
+ public object this[string accessor]
{
get
{
@@ -259,7 +317,21 @@ namespace RobloxFiles
Instance next = result.FindFirstChild(name);
if (next == null)
- throw new Exception(name + " is not a valid member of " + result.Name);
+ {
+ // Check if there is any property with this name.
+ var propQuery = result.Properties
+ .Where((prop) => name == prop.Name);
+
+ if (propQuery.Count() > 0)
+ {
+ var prop = propQuery.First();
+ return prop;
+ }
+ else
+ {
+ throw new Exception(name + " is not a valid member of " + result.Name);
+ }
+ }
result = next;
}
diff --git a/Core/Property.cs b/Tree/Property.cs
similarity index 99%
rename from Core/Property.cs
rename to Tree/Property.cs
index 2046d18..5e261ef 100644
--- a/Core/Property.cs
+++ b/Tree/Property.cs
@@ -36,8 +36,9 @@ namespace RobloxFiles
public class Property
{
- public Instance Instance;
public string Name;
+ public Instance Instance;
+
public PropertyType Type;
public object Value;
diff --git a/Utility/BrickColors.cs b/Utility/BrickColors.cs
index fdc67e4..e3ff943 100644
--- a/Utility/BrickColors.cs
+++ b/Utility/BrickColors.cs
@@ -39,6 +39,7 @@ namespace RobloxFiles.Utility
///
/// This contains a list of all defined BrickColors on Roblox.
+ /// There are some name duplicates, but that's an issue on Roblox's end.
///
public static IReadOnlyList ColorMap = new List()
diff --git a/Utility/MaterialInfo.cs b/Utility/MaterialInfo.cs
index 58b4577..f0b5882 100644
--- a/Utility/MaterialInfo.cs
+++ b/Utility/MaterialInfo.cs
@@ -140,7 +140,8 @@ namespace RobloxFiles.Utility
///
/// A dictionary mapping materials to their default Friction.
- /// NOTE: This only maps materials that have different FrictionWeights. If it isn't in here, assume their FrictionWeight is 1.
+ /// NOTE: This only maps materials that have different FrictionWeights.
+ /// If it isn't in here, assume their FrictionWeight is 1.
///
public static IReadOnlyDictionary FrictionWeightMap = new Dictionary()
{
diff --git a/XmlFormat/PropertyTokens/Vector3int16.cs b/XmlFormat/PropertyTokens/Vector3int16.cs
new file mode 100644
index 0000000..1f4e82a
--- /dev/null
+++ b/XmlFormat/PropertyTokens/Vector3int16.cs
@@ -0,0 +1,40 @@
+using System.Xml;
+using RobloxFiles.DataTypes;
+
+namespace RobloxFiles.XmlFormat.PropertyTokens
+{
+ public class Vector3int16Token : IXmlPropertyToken
+ {
+ public string Token => "Vector3int16";
+ private static string[] Coords = new string[3] { "X", "Y", "Z" };
+
+ public bool ReadToken(Property property, XmlNode token)
+ {
+ short[] xyz = new short[3];
+
+ for (int i = 0; i < 3; i++)
+ {
+ string key = Coords[i];
+
+ try
+ {
+ var coord = token[key];
+ xyz[i] = short.Parse(coord.InnerText);
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ short x = xyz[0],
+ y = xyz[1],
+ z = xyz[2];
+
+ property.Type = PropertyType.Vector3int16;
+ property.Value = new Vector3int16(x, y, z);
+
+ return true;
+ }
+ }
+}
diff --git a/XmlFormat/XmlDataReader.cs b/XmlFormat/XmlDataReader.cs
index fc1396c..277d61a 100644
--- a/XmlFormat/XmlDataReader.cs
+++ b/XmlFormat/XmlDataReader.cs
@@ -4,7 +4,7 @@ using System.Xml;
namespace RobloxFiles.XmlFormat
{
- static class XmlDataReader
+ public static class XmlDataReader
{
public static void ReadProperties(Instance instance, XmlNode propsNode)
{
@@ -39,7 +39,7 @@ namespace RobloxFiles.XmlFormat
}
}
- public static Instance ReadInstance(XmlNode instNode, ref Dictionary instances)
+ public static Instance ReadInstance(XmlNode instNode, XmlRobloxFile file = null)
{
// Process the instance itself
if (instNode.Name != "Item")
@@ -54,14 +54,14 @@ namespace RobloxFiles.XmlFormat
// 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");
- if (refToken != null && instances != null)
+ if (refToken != null && file != null)
{
string refId = refToken.InnerText;
- if (instances.ContainsKey(refId))
+ if (file.Instances.ContainsKey(refId))
throw new Exception("XmlDataReader.ReadItem: Got an Item with a duplicate 'referent' attribute!");
- instances.Add(refId, inst);
+ file.Instances.Add(refId, inst);
}
// Process the child nodes of this instance.
@@ -73,7 +73,7 @@ namespace RobloxFiles.XmlFormat
}
else if (childNode.Name == "Item")
{
- Instance child = ReadInstance(childNode, ref instances);
+ Instance child = ReadInstance(childNode, file);
child.Parent = inst;
}
}
diff --git a/XmlFormat/XmlPropertyTokens.cs b/XmlFormat/XmlPropertyTokens.cs
index 92bd54f..1526752 100644
--- a/XmlFormat/XmlPropertyTokens.cs
+++ b/XmlFormat/XmlPropertyTokens.cs
@@ -6,12 +6,6 @@ using System.Xml;
namespace RobloxFiles.XmlFormat
{
- public interface IXmlPropertyToken
- {
- string Token { get; }
- bool ReadToken(Property prop, XmlNode token);
- }
-
public static class XmlPropertyTokens
{
public static IReadOnlyDictionary Handlers;
diff --git a/XmlFormat/XmlRobloxFile.cs b/XmlFormat/XmlRobloxFile.cs
index 519506a..1f34db3 100644
--- a/XmlFormat/XmlRobloxFile.cs
+++ b/XmlFormat/XmlRobloxFile.cs
@@ -47,12 +47,12 @@ namespace RobloxFiles.XmlFormat
{
if (child.Name == "Item")
{
- Instance item = XmlDataReader.ReadInstance(child, ref Instances);
+ Instance item = XmlDataReader.ReadInstance(child, this);
item.Parent = XmlContents;
}
}
- // Resolve references for Ref properties.
+ // Resolve referent properties.
var refProps = Instances.Values
.SelectMany(inst => inst.Properties)
.Where(prop => prop.Type == PropertyType.Ref);