Cleaning up some things.

This commit is contained in:
CloneTrooper1019 2019-05-18 23:44:51 -05:00
parent 34642f5656
commit 9c3a673d95
47 changed files with 303 additions and 351 deletions

3
.gitignore vendored
View File

@ -3,4 +3,5 @@ obj/*
.vs/*
*.suo
*.ide
*.user
*.user
Program.cs

View File

@ -6,46 +6,41 @@ using LZ4;
namespace RobloxFiles.BinaryFormat
{
/// <summary>
/// BinaryRobloxChunk represents a generic LZ4-compressed chunk
/// BinaryRobloxFileChunk represents a generic LZ4-compressed chunk
/// of data in Roblox's Binary File Format.
/// </summary>
public class BinaryRobloxChunk
public class BinaryRobloxFileChunk
{
public readonly string ChunkType;
public readonly byte[] Reserved;
public readonly int CompressedSize;
public readonly int Size;
public readonly byte[] Reserved;
public readonly byte[] CompressedData;
public readonly byte[] Data;
public bool HasCompressedData => (CompressedSize > 0);
public BinaryRobloxFileReader GetDataReader()
{
MemoryStream buffer = new MemoryStream(Data);
return new BinaryRobloxFileReader(buffer);
}
public override string ToString()
{
return ChunkType + " Chunk [" + Size + " bytes]";
}
public BinaryRobloxReader GetReader(string chunkType)
{
if (ChunkType == chunkType)
{
MemoryStream buffer = new MemoryStream(Data);
return new BinaryRobloxReader(buffer);
}
throw new Exception("Expected " + chunkType + " ChunkType from the input RobloxBinaryChunk");
}
public BinaryRobloxChunk(BinaryRobloxReader reader)
public BinaryRobloxFileChunk(BinaryRobloxFileReader reader)
{
byte[] bChunkType = reader.ReadBytes(4);
ChunkType = Encoding.ASCII.GetString(bChunkType);
CompressedSize = reader.ReadInt32();
Size = reader.ReadInt32();
Reserved = reader.ReadBytes(4);
if (HasCompressedData)

View File

@ -5,9 +5,9 @@ using System.Text;
namespace RobloxFiles.BinaryFormat
{
public class BinaryRobloxReader : BinaryReader
public class BinaryRobloxFileReader : BinaryReader
{
public BinaryRobloxReader(Stream stream) : base(stream) { }
public BinaryRobloxFileReader(Stream stream) : base(stream) { }
private byte[] lastStringBuffer = new byte[0] { };
// Reads 'count * sizeof(T)' interleaved bytes and converts

View File

@ -7,7 +7,7 @@ using RobloxFiles.BinaryFormat.Chunks;
namespace RobloxFiles.BinaryFormat
{
public class BinaryRobloxFile : IRobloxFile
public class BinaryRobloxFile : RobloxFile
{
// Header Specific
public const string MagicHeader = "<roblox!\x89\xff\x0d\x0a\x1a\x0a";
@ -17,12 +17,8 @@ namespace RobloxFiles.BinaryFormat
public uint NumInstances;
public byte[] Reserved;
// IRobloxFile
internal readonly Instance BinContents = new Instance("Folder", "BinaryRobloxFile");
public Instance Contents => BinContents;
// Runtime Specific
public List<BinaryRobloxChunk> Chunks = new List<BinaryRobloxChunk>();
public List<BinaryRobloxFileChunk> Chunks = new List<BinaryRobloxFileChunk>();
public override string ToString() => GetType().Name;
public Instance[] Instances;
@ -30,11 +26,17 @@ namespace RobloxFiles.BinaryFormat
public Dictionary<string, string> Metadata;
public Dictionary<uint, string> SharedStrings;
internal BinaryRobloxFile()
{
Name = "BinaryRobloxFile";
ParentLocked = true;
}
public void ReadFile(byte[] contents)
protected override void ReadFile(byte[] contents)
{
using (MemoryStream file = new MemoryStream(contents))
using (BinaryRobloxReader reader = new BinaryRobloxReader(file))
using (BinaryRobloxFileReader reader = new BinaryRobloxFileReader(file))
{
// Verify the signature of the file.
byte[] binSignature = reader.ReadBytes(14);
@ -59,7 +61,7 @@ namespace RobloxFiles.BinaryFormat
{
try
{
BinaryRobloxChunk chunk = new BinaryRobloxChunk(reader);
BinaryRobloxFileChunk chunk = new BinaryRobloxFileChunk(reader);
Chunks.Add(chunk);
switch (chunk.ChunkType)
@ -88,7 +90,7 @@ namespace RobloxFiles.BinaryFormat
reading = false;
break;
default:
Console.WriteLine("Unhandled chunk type: {0}!", chunk.ChunkType);
Console.WriteLine("BinaryRobloxFile: Unhandled chunk type: {0}!", chunk.ChunkType);
Chunks.Remove(chunk);
break;
}
@ -101,7 +103,7 @@ namespace RobloxFiles.BinaryFormat
}
}
public void WriteFile(Stream stream)
public override void Save(Stream stream)
{
throw new NotImplementedException("Not implemented yet!");
}

View File

@ -13,9 +13,9 @@
return TypeName;
}
public INST(BinaryRobloxChunk chunk)
public INST(BinaryRobloxFileChunk chunk)
{
using (BinaryRobloxReader reader = chunk.GetReader("INST"))
using (BinaryRobloxFileReader reader = chunk.GetDataReader())
{
TypeIndex = reader.ReadInt32();
TypeName = reader.ReadString();
@ -30,7 +30,7 @@
{
foreach (int instId in InstanceIds)
{
Instance inst = new Instance(TypeName);
Instance inst = new Instance() { ClassName = TypeName };
file.Instances[instId] = inst;
}

View File

@ -7,9 +7,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
public int NumEntries;
public Dictionary<string, string> Data = new Dictionary<string, string>();
public META(BinaryRobloxChunk chunk)
public META(BinaryRobloxFileChunk chunk)
{
using (BinaryRobloxReader reader = chunk.GetReader("META"))
using (BinaryRobloxFileReader reader = chunk.GetDataReader())
{
NumEntries = reader.ReadInt32();

View File

@ -8,9 +8,9 @@
public readonly int[] ChildrenIds;
public readonly int[] ParentIds;
public PRNT(BinaryRobloxChunk chunk)
public PRNT(BinaryRobloxFileChunk chunk)
{
using (BinaryRobloxReader reader = chunk.GetReader("PRNT"))
using (BinaryRobloxFileReader reader = chunk.GetDataReader())
{
Format = reader.ReadByte();
NumRelations = reader.ReadInt32();
@ -28,14 +28,7 @@
int parentId = ParentIds[i];
Instance child = file.Instances[childId];
Instance parent = null;
if (parentId >= 0)
parent = file.Instances[parentId];
else
parent = file.BinContents;
child.Parent = parent;
child.Parent = (parentId >= 0 ? file.Instances[parentId] : file);
}
}
}

View File

@ -14,11 +14,11 @@ namespace RobloxFiles.BinaryFormat.Chunks
public readonly int TypeIndex;
public readonly PropertyType Type;
private BinaryRobloxReader Reader;
private BinaryRobloxFileReader Reader;
public PROP(BinaryRobloxChunk chunk)
public PROP(BinaryRobloxFileChunk chunk)
{
Reader = chunk.GetReader("PROP");
Reader = chunk.GetDataReader();
TypeIndex = Reader.ReadInt32();
Name = Reader.ReadString();
@ -78,7 +78,7 @@ namespace RobloxFiles.BinaryFormat.Chunks
// 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();
props[i].SetRawBuffer(buffer);
props[i].RawBuffer = buffer;
return result;
});

View File

@ -11,9 +11,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
public Dictionary<string, uint> Lookup = new Dictionary<string, uint>();
public Dictionary<uint, string> Strings = new Dictionary<uint, string>();
public SSTR(BinaryRobloxChunk chunk)
public SSTR(BinaryRobloxFileChunk chunk)
{
using (BinaryRobloxReader reader = chunk.GetReader("SSTR"))
using (BinaryRobloxFileReader reader = chunk.GetDataReader())
{
Version = reader.ReadInt32();
NumHashes = reader.ReadInt32();

View File

@ -1,20 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RobloxFiles
{
/// <summary>
/// Interface which represents a RobloxFile implementation.
/// </summary>
public interface IRobloxFile
{
Instance Contents { get; }
void ReadFile(byte[] buffer);
void WriteFile(Stream stream);
}
}

View File

@ -12,79 +12,36 @@ namespace RobloxFiles
/// Represents a loaded *.rbxl/*.rbxm Roblox file.
/// All of the surface-level Instances are stored in the RobloxFile's 'Contents' property.
/// </summary>
public class RobloxFile : IRobloxFile
public abstract class RobloxFile : Instance
{
/// <summary>
/// Indicates if this RobloxFile has loaded data already.
/// </summary>
public bool Initialized { get; private set; }
protected abstract void ReadFile(byte[] buffer);
public abstract void Save(Stream stream);
/// <summary>
/// A reference to the inner IRobloxFile implementation that this RobloxFile opened with.<para/>
/// It can be a BinaryRobloxFile, or an XmlRobloxFile.
/// Opens a RobloxFile using the provided buffer.
/// </summary>
public IRobloxFile InnerFile { get; private set; }
/// <summary>
/// A reference to a Folder Instance that stores all of the contents that were loaded.
/// </summary>
public Instance Contents => InnerFile.Contents;
/// <summary>
/// Initializes the RobloxFile from the provided buffer, if it hasn't been Initialized yet.
/// </summary>
/// <param name="buffer"></param>
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("<roblox"))
file = new XmlRobloxFile();
if (file != null)
{
file.ReadFile(buffer);
InnerFile = file;
Initialized = true;
return;
}
}
throw new Exception("Unrecognized header!");
}
}
public void WriteFile(Stream stream)
{
InnerFile.WriteFile(stream);
}
/// <summary>
/// Creates a RobloxFile from a provided byte sequence that represents the file.
/// </summary>
/// <param name="buffer"></param>
private RobloxFile(byte[] buffer)
{
ReadFile(buffer);
}
/// <summary>
/// Opens a Roblox file from a byte sequence that represents the file.
/// </summary>
/// <param name="buffer">A byte sequence that represents the file.</param>
public static RobloxFile Open(byte[] buffer)
{
return new RobloxFile(buffer);
}
if (buffer.Length > 14)
{
string header = Encoding.UTF7.GetString(buffer, 0, 14);
RobloxFile file = null;
if (header == BinaryRobloxFile.MagicHeader)
file = new BinaryRobloxFile();
else if (header.StartsWith("<roblox"))
file = new XmlRobloxFile();
if (file != null)
{
file.ReadFile(buffer);
return file;
}
}
throw new Exception("Unrecognized header!");
}
/// <summary>
/// Opens a Roblox file by reading from a provided Stream.
/// </summary>
@ -138,17 +95,5 @@ namespace RobloxFiles
{
return Task.Run(() => Open(filePath));
}
/// <summary>
/// Allows you to access a child/descendant of this file's contents, and/or one of its properties.<para/>
/// The provided string should be a period-separated (.) path to what you wish to access.<para/>
/// This will throw an exception if any part of the path cannot be found.<para/>
///
/// ~ Examples ~<para/>
/// var terrain = robloxFile["Workspace.Terrain"] as Instance;<para/>
/// var currentCamera = robloxFile["Workspace.CurrentCamera"] as Property;<para/>
///
/// </summary>
public object this[string accessor] => Contents[accessor];
}
}

View File

@ -62,14 +62,15 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="BinaryFormat\BinaryChunk.cs" />
<Compile Include="BinaryFormat\BinaryReader.cs" />
<Compile Include="BinaryFormat\BinaryFileChunk.cs" />
<Compile Include="BinaryFormat\BinaryFileReader.cs" />
<Compile Include="BinaryFormat\BinaryRobloxFile.cs" />
<Compile Include="BinaryFormat\ChunkTypes\INST.cs" />
<Compile Include="BinaryFormat\ChunkTypes\META.cs" />
<Compile Include="BinaryFormat\ChunkTypes\PRNT.cs" />
<Compile Include="BinaryFormat\ChunkTypes\PROP.cs" />
<Compile Include="BinaryFormat\ChunkTypes\SSTR.cs" />
<Compile Include="BinaryFormat\Chunks\INST.cs" />
<Compile Include="BinaryFormat\Chunks\META.cs" />
<Compile Include="BinaryFormat\Chunks\PRNT.cs" />
<Compile Include="BinaryFormat\Chunks\PROP.cs" />
<Compile Include="BinaryFormat\Chunks\SSTR.cs" />
<Compile Include="Program.cs" />
<Compile Include="Tree\Enums.cs" />
<Compile Include="Tree\Property.cs" />
<Compile Include="Tree\Instance.cs" />
@ -87,8 +88,7 @@
<Compile Include="DataTypes\PhysicalProperties.cs" />
<Compile Include="DataTypes\Ray.cs" />
<Compile Include="DataTypes\Region3int16.cs" />
<Compile Include="Interfaces\IRobloxFile.cs" />
<Compile Include="Interfaces\IXmlPropertyToken.cs" />
<Compile Include="XmlFormat\PropertyTokens\IXmlPropertyToken.cs" />
<Compile Include="Utility\BrickColors.cs" />
<Compile Include="DataTypes\Vector3int16.cs" />
<Compile Include="DataTypes\Rect.cs" />
@ -101,38 +101,38 @@
<Compile Include="Utility\MaterialInfo.cs" />
<Compile Include="Utility\Quaternion.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="XmlFormat\PropertyTokens\SharedString.cs" />
<Compile Include="XmlFormat\PropertyTokens\Vector3int16.cs" />
<Compile Include="XmlFormat\XmlDataWriter.cs" />
<Compile Include="XmlFormat\XmlPropertyTokens.cs" />
<Compile Include="XmlFormat\XmlDataReader.cs" />
<Compile Include="XmlFormat\PropertyTokens\Tokens\SharedString.cs" />
<Compile Include="XmlFormat\PropertyTokens\Tokens\Vector3int16.cs" />
<Compile Include="XmlFormat\IO\XmlFileWriter.cs" />
<Compile Include="XmlFormat\PropertyTokens\XmlPropertyTokens.cs" />
<Compile Include="XmlFormat\IO\XmlFileReader.cs" />
<Compile Include="XmlFormat\XmlRobloxFile.cs" />
<Compile Include="XmlFormat\PropertyTokens\Axes.cs" />
<Compile Include="XmlFormat\PropertyTokens\BinaryString.cs" />
<Compile Include="XmlFormat\PropertyTokens\Boolean.cs" />
<Compile Include="XmlFormat\PropertyTokens\BrickColor.cs" />
<Compile Include="XmlFormat\PropertyTokens\CFrame.cs" />
<Compile Include="XmlFormat\PropertyTokens\Content.cs" />
<Compile Include="XmlFormat\PropertyTokens\Color3.cs" />
<Compile Include="XmlFormat\PropertyTokens\Color3uint8.cs" />
<Compile Include="XmlFormat\PropertyTokens\ColorSequence.cs" />
<Compile Include="XmlFormat\PropertyTokens\Double.cs" />
<Compile Include="XmlFormat\PropertyTokens\Enum.cs" />
<Compile Include="XmlFormat\PropertyTokens\Faces.cs" />
<Compile Include="XmlFormat\PropertyTokens\Float.cs" />
<Compile Include="XmlFormat\PropertyTokens\Int.cs" />
<Compile Include="XmlFormat\PropertyTokens\Int64.cs" />
<Compile Include="XmlFormat\PropertyTokens\NumberRange.cs" />
<Compile Include="XmlFormat\PropertyTokens\NumberSequence.cs" />
<Compile Include="XmlFormat\PropertyTokens\PhysicalProperties.cs" />
<Compile Include="XmlFormat\PropertyTokens\Ray.cs" />
<Compile Include="XmlFormat\PropertyTokens\Rect.cs" />
<Compile Include="XmlFormat\PropertyTokens\Ref.cs" />
<Compile Include="XmlFormat\PropertyTokens\String.cs" />
<Compile Include="XmlFormat\PropertyTokens\UDim.cs" />
<Compile Include="XmlFormat\PropertyTokens\UDim2.cs" />
<Compile Include="XmlFormat\PropertyTokens\Vector2.cs" />
<Compile Include="XmlFormat\PropertyTokens\Vector3.cs" />
<Compile Include="XmlFormat\PropertyTokens\Tokens\Axes.cs" />
<Compile Include="XmlFormat\PropertyTokens\Tokens\BinaryString.cs" />
<Compile Include="XmlFormat\PropertyTokens\Tokens\Boolean.cs" />
<Compile Include="XmlFormat\PropertyTokens\Tokens\BrickColor.cs" />
<Compile Include="XmlFormat\PropertyTokens\Tokens\CFrame.cs" />
<Compile Include="XmlFormat\PropertyTokens\Tokens\Content.cs" />
<Compile Include="XmlFormat\PropertyTokens\Tokens\Color3.cs" />
<Compile Include="XmlFormat\PropertyTokens\Tokens\Color3uint8.cs" />
<Compile Include="XmlFormat\PropertyTokens\Tokens\ColorSequence.cs" />
<Compile Include="XmlFormat\PropertyTokens\Tokens\Double.cs" />
<Compile Include="XmlFormat\PropertyTokens\Tokens\Enum.cs" />
<Compile Include="XmlFormat\PropertyTokens\Tokens\Faces.cs" />
<Compile Include="XmlFormat\PropertyTokens\Tokens\Float.cs" />
<Compile Include="XmlFormat\PropertyTokens\Tokens\Int.cs" />
<Compile Include="XmlFormat\PropertyTokens\Tokens\Int64.cs" />
<Compile Include="XmlFormat\PropertyTokens\Tokens\NumberRange.cs" />
<Compile Include="XmlFormat\PropertyTokens\Tokens\NumberSequence.cs" />
<Compile Include="XmlFormat\PropertyTokens\Tokens\PhysicalProperties.cs" />
<Compile Include="XmlFormat\PropertyTokens\Tokens\Ray.cs" />
<Compile Include="XmlFormat\PropertyTokens\Tokens\Rect.cs" />
<Compile Include="XmlFormat\PropertyTokens\Tokens\Ref.cs" />
<Compile Include="XmlFormat\PropertyTokens\Tokens\String.cs" />
<Compile Include="XmlFormat\PropertyTokens\Tokens\UDim.cs" />
<Compile Include="XmlFormat\PropertyTokens\Tokens\UDim2.cs" />
<Compile Include="XmlFormat\PropertyTokens\Tokens\Vector2.cs" />
<Compile Include="XmlFormat\PropertyTokens\Tokens\Vector3.cs" />
</ItemGroup>
<ItemGroup>
<BootstrapperPackage Include=".NETFramework,Version=v4.5.2">

View File

@ -15,40 +15,23 @@ namespace RobloxFiles
public string ClassName;
/// <summary>A list of properties that are defined under this Instance.</summary>
public Dictionary<string, Property> Properties = new Dictionary<string, Property>();
private Dictionary<string, Property> props = new Dictionary<string, Property>();
public IReadOnlyDictionary<string, Property> Properties => props;
private List<Instance> Children = new List<Instance>();
private Instance rawParent;
private Instance parent;
/// <summary>The name of this Instance, if a Name property is defined.</summary>
public string Name => ReadProperty("Name", ClassName);
public override string ToString() => Name;
internal string XmlReferent;
/// <summary>A unique identifier for this instance when being serialized as XML.</summary>
public string XmlReferent { get; internal set; }
/// <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>Indicates whether the parent of this object is locked.</summary>
public bool ParentLocked { get; protected set; }
/// <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()
{
Type = PropertyType.String,
Instance = this,
Name = "Name",
Value = name,
};
ClassName = className;
AddProperty(ref propName);
}
/// <summary>Indicates whether this object should be serialized.</summary>
public bool Archivable = true;
/// <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>
@ -72,6 +55,23 @@ namespace RobloxFiles
return ancestor.IsAncestorOf(this);
}
public string Name
{
get
{
Property propName = GetProperty("Name");
if (propName == null)
SetProperty("Name", "Instance");
return propName.Value.ToString();
}
set
{
SetProperty("Name", value);
}
}
/// <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/>
@ -82,21 +82,24 @@ namespace RobloxFiles
{
get
{
return rawParent;
return parent;
}
set
{
if (ParentLocked)
throw new Exception("The Parent property of this instance is locked.");
if (IsAncestorOf(value))
throw new Exception("Parent would result in circular reference.");
if (Parent == this)
throw new Exception("Attempt to set parent to self.");
if (rawParent != null)
rawParent.Children.Remove(this);
if (parent != null)
parent.Children.Remove(this);
value.Children.Add(this);
rawParent = value;
parent = value;
}
}
@ -180,6 +183,11 @@ namespace RobloxFiles
return ancestor;
}
/// <summary>
/// Returns the first ancestor of this Instance whose ClassName is the provided string className.
/// If the instance is not found, this returns null.
/// </summary>
/// <param name="name">The Name of the Instance to find.</param>
public Instance FindFirstAncestorOfClass(string className)
{
Instance ancestor = Parent;
@ -196,7 +204,8 @@ namespace RobloxFiles
}
/// <summary>
/// Returns the first Instance whose ClassName is the provided string className. If the instance is not found, this returns null.
/// 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, bool recursive = false)
@ -228,21 +237,77 @@ namespace RobloxFiles
}
/// <summary>
/// Looks for a property with the specified property name, and returns it as an object.
/// Returns a Property object if a property with the specified name is defined in this Instance.
/// </summary>
public Property GetProperty(string name)
{
Property result = null;
if (Properties.ContainsKey(name))
result = Properties[name];
return result;
}
/// <summary>
/// Finds or creates a property with the specified name, and sets its value to the provided object.
/// Returns the property object that had its value set, if the value is not null.
/// </summary>
public Property SetProperty(string name, object value, PropertyType? preferType = null)
{
Property prop = GetProperty(name) ?? new Property()
{
Type = preferType ?? PropertyType.Unknown,
Name = name
};
if (preferType == null)
{
object oldValue = prop.Value;
Type oldType = oldValue?.GetType();
Type newType = value?.GetType();
if (oldType != newType)
{
if (value == null)
{
RemoveProperty(name);
return prop;
}
string typeName = newType.Name;
if (value is Instance)
typeName = "Ref";
else if (value is int)
typeName = "Int";
else if (value is long)
typeName = "Int64";
Enum.TryParse(typeName, out prop.Type);
}
}
prop.Value = value;
if (prop.Instance == null)
AddProperty(ref prop);
return prop;
}
/// <summary>
/// Looks for a property with the specified property name, and returns its value 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)
{
Property property = null;
if (Properties.ContainsKey(propertyName))
property = Properties[propertyName];
return (property != null ? property.Value : null);
Property property = GetProperty(propertyName);
return property?.Value;
}
/// <summary>
@ -291,52 +356,39 @@ namespace RobloxFiles
/// <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>
internal void AddProperty(ref Property prop)
{
Properties.Add(prop.Name, prop);
prop.Instance = this;
if (prop.Name == "Name")
{
Property nameProp = GetProperty("Name");
if (nameProp != null)
{
nameProp.Value = prop.Value;
return;
}
}
props.Add(prop.Name, prop);
}
/// <summary>
/// Allows you to access a child/descendant of this Instance, and/or one of its properties.<para/>
/// The provided string should be a period-separated (.) path to what you wish to access.<para/>
/// This will throw an exception if any part of the path cannot be found.<para/>
///
/// ~ Examples ~<para/>
/// var terrain = robloxFile["Workspace.Terrain"] as Instance;<para/>
/// var currentCamera = robloxFile["Workspace.CurrentCamera"] as Property;<para/>
///
/// Removes a property with the provided name if a property with the provided name exists.
/// </summary>
public object this[string accessor]
/// <param name="name">The name of the property to be removed.</param>
/// <returns>True if a property with the provided name was removed.</returns>
public bool RemoveProperty(string name)
{
get
{
Instance result = this;
Property prop = GetProperty(name);
foreach (string name in accessor.Split('.'))
{
Instance next = result.FindFirstChild(name);
if (prop != null)
prop.Instance = null;
if (next == null)
{
// Check if there is any property with this name.
Property prop = null;
if (result.Properties.ContainsKey(name))
prop = result.Properties[name];
else
throw new Exception(name + " is not a valid member of " + result.Name);
return prop;
}
result = next;
}
return result;
}
return props.Remove(name);
}
}
}

View File

@ -39,13 +39,13 @@ namespace RobloxFiles
public class Property
{
public string Name;
public Instance Instance;
public Instance Instance { get; internal set; }
public PropertyType Type;
public object Value;
public string XmlToken = "";
private byte[] RawBuffer = null;
public byte[] RawBuffer { get; internal set; }
public bool HasRawBuffer
{
@ -116,15 +116,5 @@ namespace RobloxFiles
return string.Join(" ", typeName, Name, '=', valueLabel);
}
internal void SetRawBuffer(byte[] buffer)
{
RawBuffer = buffer;
}
public byte[] GetRawBuffer()
{
return RawBuffer;
}
}
}

View File

@ -3,7 +3,7 @@ using System.Xml;
namespace RobloxFiles.XmlFormat
{
public static class XmlDataReader
public static class XmlRobloxFileReader
{
private static Func<string, Exception> createErrorHandler(string label)
{
@ -59,10 +59,12 @@ namespace RobloxFiles.XmlFormat
if (tokenHandler != null)
{
Property prop = new Property();
prop.Name = propName.InnerText;
prop.Instance = instance;
prop.XmlToken = propType;
Property prop = new Property()
{
Name = propName.InnerText,
Instance = instance,
XmlToken = propType
};
if (!tokenHandler.ReadProperty(prop, propNode))
Console.WriteLine("Could not read property: " + prop.GetFullName() + '!');
@ -88,19 +90,20 @@ namespace RobloxFiles.XmlFormat
if (classToken == null)
throw error("Got an Item without a defined 'class' attribute!");
Instance inst = new Instance(classToken.InnerText);
Instance inst = new Instance() { ClassName = classToken.InnerText };
// 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 && file != null)
{
string refId = refToken.InnerText;
string referent = refToken.InnerText;
inst.XmlReferent = referent;
if (file.Instances.ContainsKey(refId))
if (file.Instances.ContainsKey(referent))
throw error("Got an Item with a duplicate 'referent' attribute!");
file.Instances.Add(refId, inst);
file.Instances.Add(referent, inst);
}
// Process the child nodes of this instance.

View File

@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Xml;
@ -7,19 +8,18 @@ using RobloxFiles.XmlFormat.PropertyTokens;
namespace RobloxFiles.XmlFormat
{
public static class XmlDataWriter
public static class XmlRobloxFileWriter
{
public static XmlWriterSettings Settings = new XmlWriterSettings()
public static readonly XmlWriterSettings Settings = new XmlWriterSettings()
{
Indent = true,
IndentChars = "\t",
NewLineChars = "\r\n",
Encoding = Encoding.UTF8,
OmitXmlDeclaration = true,
NamespaceHandling = NamespaceHandling.Default
OmitXmlDeclaration = true
};
private static string CreateReferent()
public static string CreateReferent()
{
Guid referentGuid = Guid.NewGuid();
@ -40,21 +40,18 @@ namespace RobloxFiles.XmlFormat
foreach (Instance child in inst.GetChildren())
RecordInstances(file, child);
string referent = CreateReferent();
file.Instances.Add(referent, inst);
inst.XmlReferent = referent;
if (inst.XmlReferent == "")
inst.XmlReferent = CreateReferent();
file.Instances.Add(inst.XmlReferent, inst);
}
public static XmlElement CreateRobloxElement(XmlDocument doc)
{
XmlElement roblox = doc.CreateElement("roblox");
doc.AppendChild(roblox);
roblox.SetAttribute("xmlns:xmime", "http://www.w3.org/2005/05/xmlmime");
roblox.SetAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
roblox.SetAttribute("xsi:noNamespaceSchemaLocation", "http://www.roblox.com/roblox.xsd");
roblox.SetAttribute("version", "4");
doc.AppendChild(roblox);
XmlElement externalNull = doc.CreateElement("External");
roblox.AppendChild(externalNull);
externalNull.InnerText = "null";
@ -150,7 +147,7 @@ namespace RobloxFiles.XmlFormat
instNode.AppendChild(propsNode);
var props = instance.Properties;
foreach (string propName in props.Keys)
{
Property prop = props[propName];
@ -182,7 +179,7 @@ namespace RobloxFiles.XmlFormat
string data = file.SharedStrings[md5];
byte[] buffer = Convert.FromBase64String(data);
bufferProp.SetRawBuffer(buffer);
bufferProp.RawBuffer = buffer;
binaryWriter.WriteProperty(bufferProp, doc, sharedString);
sharedStrings.AppendChild(sharedString);

View File

@ -1,9 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml;
namespace RobloxFiles.XmlFormat
{

View File

@ -15,14 +15,14 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
prop.Value = base64;
byte[] buffer = Convert.FromBase64String(base64);
prop.SetRawBuffer(buffer);
prop.RawBuffer = buffer;
return true;
}
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
byte[] data = prop.GetRawBuffer();
byte[] data = prop.RawBuffer;
string value = Convert.ToBase64String(data);
if (value.Length > 72)

View File

@ -22,7 +22,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{
// Roblox technically doesn't support this anymore, but load it anyway :P
byte[] buffer = Convert.FromBase64String(content);
prop.SetRawBuffer(buffer);
prop.RawBuffer = buffer;
}
}

View File

@ -20,8 +20,6 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{
return XmlPropertyTokens.ReadPropertyGeneric<int>(prop, PropertyType.Int, token);
}
}
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)

View File

@ -78,6 +78,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
XmlElement element = doc.CreateElement(elementType);
element.InnerText = value.ToInvariantString();
node.AppendChild(element);
}
}

View File

@ -22,7 +22,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
read[i] = Vector3Token.ReadVector3(fieldToken);
}
Vector3 origin = read[0],
Vector3 origin = read[0],
direction = read[1];
Ray ray = new Ray(origin, direction);
@ -42,11 +42,12 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
Ray ray = prop.Value as Ray;
XmlElement origin = doc.CreateElement("origin");
Vector3Token.WriteVector3(doc, origin, ray.Origin);
node.AppendChild(origin);
XmlElement direction = doc.CreateElement("direction");
Vector3Token.WriteVector3(doc, origin, ray.Origin);
Vector3Token.WriteVector3(doc, direction, ray.Direction);
node.AppendChild(origin);
node.AppendChild(direction);
}
}

View File

@ -4,8 +4,6 @@ using System.ComponentModel;
using System.Linq;
using System.Xml;
using RobloxFiles;
namespace RobloxFiles.XmlFormat
{
public static class XmlPropertyTokens
@ -60,7 +58,6 @@ namespace RobloxFiles.XmlFormat
}
prop.Type = propType;
return true;
}
catch

View File

@ -8,23 +8,22 @@ using System.Xml;
namespace RobloxFiles.XmlFormat
{
public class XmlRobloxFile : IRobloxFile
public class XmlRobloxFile : RobloxFile
{
// 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 Dictionary<string, string> SharedStrings = new Dictionary<string, string>();
public void ReadFile(byte[] buffer)
{
Instances.Clear();
SharedStrings.Clear();
internal Dictionary<string, Instance> Instances = new Dictionary<string, Instance>();
internal Dictionary<string, string> SharedStrings = new Dictionary<string, string>();
internal XmlRobloxFile()
{
Name = "XmlRobloxFile";
ParentLocked = true;
}
protected override void ReadFile(byte[] buffer)
{
try
{
string xml = Encoding.UTF8.GetString(buffer);
@ -53,22 +52,22 @@ namespace RobloxFiles.XmlFormat
{
if (child.Name == "Item")
{
Instance item = XmlDataReader.ReadInstance(child, this);
item.Parent = XmlContents;
Instance item = XmlRobloxFileReader.ReadInstance(child, this);
item.Parent = this;
}
else if (child.Name == "SharedStrings")
{
XmlDataReader.ReadSharedStrings(child, this);
XmlRobloxFileReader.ReadSharedStrings(child, this);
}
}
// Query the properties.
var props = Instances.Values
var allProps = Instances.Values
.SelectMany(inst => inst.Properties)
.Select(pair => pair.Value);
// Resolve referent properties.
var refProps = props.Where(prop => prop.Type == PropertyType.Ref);
var refProps = allProps.Where(prop => prop.Type == PropertyType.Ref);
foreach (Property refProp in refProps)
{
@ -87,7 +86,7 @@ namespace RobloxFiles.XmlFormat
}
// Resolve shared strings.
var sharedProps = props.Where(prop => prop.Type == PropertyType.SharedString);
var sharedProps = allProps.Where(prop => prop.Type == PropertyType.SharedString);
foreach (Property sharedProp in sharedProps)
{
@ -99,13 +98,13 @@ namespace RobloxFiles.XmlFormat
sharedProp.Value = value;
byte[] data = Convert.FromBase64String(value);
sharedProp.SetRawBuffer(data);
continue;
sharedProp.RawBuffer = data;
}
else
{
string name = sharedProp.GetFullName();
Console.WriteLine("XmlRobloxFile: Could not resolve shared string for {0}", name);
}
string name = sharedProp.GetFullName();
Console.WriteLine("XmlRobloxFile: Could not resolve shared string for {0}", name);
}
}
else
@ -114,38 +113,41 @@ namespace RobloxFiles.XmlFormat
}
}
public void WriteFile(Stream stream)
public override void Save(Stream stream)
{
XmlDocument doc = new XmlDocument();
XmlElement roblox = XmlDataWriter.CreateRobloxElement(doc);
XmlElement roblox = doc.CreateElement("roblox");
roblox.SetAttribute("version", "4");
doc.AppendChild(roblox);
Instances.Clear();
SharedStrings.Clear();
Instance[] topLevelItems = Contents.GetChildren();
Instance[] children = GetChildren();
// First, record all of the instances.
foreach (Instance inst in topLevelItems)
XmlDataWriter.RecordInstances(this, inst);
foreach (Instance inst in children)
XmlRobloxFileWriter.RecordInstances(this, inst);
// Now append them into the document.
foreach (Instance inst in Contents.GetChildren())
foreach (Instance inst in children)
{
XmlNode instNode = XmlDataWriter.WriteInstance(inst, doc, this);
XmlNode instNode = XmlRobloxFileWriter.WriteInstance(inst, doc, this);
roblox.AppendChild(instNode);
}
// Append the shared strings.
if (SharedStrings.Count > 0)
{
XmlNode sharedStrings = XmlDataWriter.WriteSharedStrings(doc, this);
XmlNode sharedStrings = XmlRobloxFileWriter.WriteSharedStrings(doc, this);
roblox.AppendChild(sharedStrings);
}
// Write the XML file.
using (StringWriter buffer = new StringWriter())
{
XmlWriterSettings settings = XmlDataWriter.Settings;
XmlWriterSettings settings = XmlRobloxFileWriter.Settings;
using (XmlWriter xmlWriter = XmlWriter.Create(buffer, settings))
doc.WriteContentTo(xmlWriter);