diff --git a/.gitignore b/.gitignore
index 227ea7a..081bed0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,4 +3,5 @@ obj/*
.vs/*
*.suo
*.ide
-*.user
\ No newline at end of file
+*.user
+Program.cs
\ No newline at end of file
diff --git a/BinaryFormat/BinaryChunk.cs b/BinaryFormat/BinaryFileChunk.cs
similarity index 70%
rename from BinaryFormat/BinaryChunk.cs
rename to BinaryFormat/BinaryFileChunk.cs
index c5f50e2..4a8376d 100644
--- a/BinaryFormat/BinaryChunk.cs
+++ b/BinaryFormat/BinaryFileChunk.cs
@@ -6,46 +6,41 @@ using LZ4;
namespace RobloxFiles.BinaryFormat
{
///
- /// BinaryRobloxChunk represents a generic LZ4-compressed chunk
+ /// BinaryRobloxFileChunk represents a generic LZ4-compressed chunk
/// of data in Roblox's Binary File Format.
///
- 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)
diff --git a/BinaryFormat/BinaryReader.cs b/BinaryFormat/BinaryFileReader.cs
similarity index 96%
rename from BinaryFormat/BinaryReader.cs
rename to BinaryFormat/BinaryFileReader.cs
index 8e04d86..e6de267 100644
--- a/BinaryFormat/BinaryReader.cs
+++ b/BinaryFormat/BinaryFileReader.cs
@@ -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
diff --git a/BinaryFormat/BinaryRobloxFile.cs b/BinaryFormat/BinaryRobloxFile.cs
index 7c231ab..e1bbbb2 100644
--- a/BinaryFormat/BinaryRobloxFile.cs
+++ b/BinaryFormat/BinaryRobloxFile.cs
@@ -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 = " BinContents;
-
// Runtime Specific
- public List Chunks = new List();
+ public List Chunks = new List();
public override string ToString() => GetType().Name;
public Instance[] Instances;
@@ -30,11 +26,17 @@ namespace RobloxFiles.BinaryFormat
public Dictionary Metadata;
public Dictionary 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!");
}
diff --git a/BinaryFormat/ChunkTypes/INST.cs b/BinaryFormat/Chunks/INST.cs
similarity index 82%
rename from BinaryFormat/ChunkTypes/INST.cs
rename to BinaryFormat/Chunks/INST.cs
index e51a852..2c97e54 100644
--- a/BinaryFormat/ChunkTypes/INST.cs
+++ b/BinaryFormat/Chunks/INST.cs
@@ -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;
}
diff --git a/BinaryFormat/ChunkTypes/META.cs b/BinaryFormat/Chunks/META.cs
similarity index 82%
rename from BinaryFormat/ChunkTypes/META.cs
rename to BinaryFormat/Chunks/META.cs
index bff903a..a4d1bb2 100644
--- a/BinaryFormat/ChunkTypes/META.cs
+++ b/BinaryFormat/Chunks/META.cs
@@ -7,9 +7,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
public int NumEntries;
public Dictionary Data = new Dictionary();
- public META(BinaryRobloxChunk chunk)
+ public META(BinaryRobloxFileChunk chunk)
{
- using (BinaryRobloxReader reader = chunk.GetReader("META"))
+ using (BinaryRobloxFileReader reader = chunk.GetDataReader())
{
NumEntries = reader.ReadInt32();
diff --git a/BinaryFormat/ChunkTypes/PRNT.cs b/BinaryFormat/Chunks/PRNT.cs
similarity index 69%
rename from BinaryFormat/ChunkTypes/PRNT.cs
rename to BinaryFormat/Chunks/PRNT.cs
index 6cb941c..5c96cb0 100644
--- a/BinaryFormat/ChunkTypes/PRNT.cs
+++ b/BinaryFormat/Chunks/PRNT.cs
@@ -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);
}
}
}
diff --git a/BinaryFormat/ChunkTypes/PROP.cs b/BinaryFormat/Chunks/PROP.cs
similarity index 98%
rename from BinaryFormat/ChunkTypes/PROP.cs
rename to BinaryFormat/Chunks/PROP.cs
index 069b286..0912618 100644
--- a/BinaryFormat/ChunkTypes/PROP.cs
+++ b/BinaryFormat/Chunks/PROP.cs
@@ -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;
});
diff --git a/BinaryFormat/ChunkTypes/SSTR.cs b/BinaryFormat/Chunks/SSTR.cs
similarity index 88%
rename from BinaryFormat/ChunkTypes/SSTR.cs
rename to BinaryFormat/Chunks/SSTR.cs
index 7a897c0..46aa6e5 100644
--- a/BinaryFormat/ChunkTypes/SSTR.cs
+++ b/BinaryFormat/Chunks/SSTR.cs
@@ -11,9 +11,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
public Dictionary Lookup = new Dictionary();
public Dictionary Strings = new Dictionary();
- 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();
diff --git a/Interfaces/IRobloxFile.cs b/Interfaces/IRobloxFile.cs
deleted file mode 100644
index b28d5c9..0000000
--- a/Interfaces/IRobloxFile.cs
+++ /dev/null
@@ -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
-{
- ///
- /// Interface which represents a RobloxFile implementation.
- ///
- public interface IRobloxFile
- {
- Instance Contents { get; }
-
- void ReadFile(byte[] buffer);
- void WriteFile(Stream stream);
- }
-}
diff --git a/RobloxFile.cs b/RobloxFile.cs
index e14b0ee..eb3a4c6 100644
--- a/RobloxFile.cs
+++ b/RobloxFile.cs
@@ -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.
///
- public class RobloxFile : IRobloxFile
+ public abstract class RobloxFile : Instance
{
- ///
- /// Indicates if this RobloxFile has loaded data already.
- ///
- public bool Initialized { get; private set; }
+ protected abstract void ReadFile(byte[] buffer);
+ public abstract void Save(Stream stream);
///
- /// A reference to the inner IRobloxFile implementation that this RobloxFile opened with.
- /// It can be a BinaryRobloxFile, or an XmlRobloxFile.
+ /// Opens a RobloxFile using the provided buffer.
///
- 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);
- }
+ 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("
/// Opens a Roblox file by reading from a provided Stream.
///
@@ -138,17 +95,5 @@ namespace RobloxFiles
{
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 2e4b406..d822947 100644
--- a/RobloxFileFormat.csproj
+++ b/RobloxFileFormat.csproj
@@ -62,14 +62,15 @@
-
-
+
+
-
-
-
-
-
+
+
+
+
+
+
@@ -87,8 +88,7 @@
-
-
+
@@ -101,38 +101,38 @@
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tree/Instance.cs b/Tree/Instance.cs
index 117f348..4ec8cf8 100644
--- a/Tree/Instance.cs
+++ b/Tree/Instance.cs
@@ -15,40 +15,23 @@ namespace RobloxFiles
public string ClassName;
/// A list of properties that are defined under this Instance.
- public Dictionary Properties = new Dictionary();
+ private Dictionary props = new Dictionary();
+ public IReadOnlyDictionary Properties => props;
private List Children = new List();
- private Instance rawParent;
+ private Instance parent;
/// The name of this Instance, if a Name property is defined.
- public string Name => ReadProperty("Name", ClassName);
public override string ToString() => Name;
- internal string XmlReferent;
+ /// A unique identifier for this instance when being serialized as XML.
+ public string XmlReferent { get; internal set; }
- /// Creates an instance using the provided ClassName.
- /// The ClassName to use for this Instance.
- public Instance(string className = "Instance")
- {
- ClassName = className;
- }
+ /// Indicates whether the parent of this object is locked.
+ public bool ParentLocked { get; protected set; }
- /// 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()
- {
- Type = PropertyType.String,
- Instance = this,
- Name = "Name",
- Value = name,
- };
-
- ClassName = className;
- AddProperty(ref propName);
- }
+ /// Indicates whether this object should be serialized.
+ public bool Archivable = true;
/// Returns true if this Instance is an ancestor to the provided Instance.
/// The instance whose descendance will be tested against this Instance.
@@ -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);
+ }
+ }
+
///
/// The parent of this Instance, or null if the instance is the root of a tree.
/// Setting the value of this property will throw an exception if:
@@ -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;
}
+ ///
+ /// Returns the first ancestor of this Instance whose ClassName is the provided string className.
+ /// If the instance is not found, this returns null.
+ ///
+ /// The Name of the Instance to find.
public Instance FindFirstAncestorOfClass(string className)
{
Instance ancestor = Parent;
@@ -196,7 +204,8 @@ namespace RobloxFiles
}
///
- /// 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.
///
/// The ClassName of the Instance to find.
public Instance FindFirstChildOfClass(string className, bool recursive = false)
@@ -228,21 +237,77 @@ namespace RobloxFiles
}
///
- /// 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.
+ ///
+ public Property GetProperty(string name)
+ {
+ Property result = null;
+
+ if (Properties.ContainsKey(name))
+ result = Properties[name];
+
+ return result;
+ }
+
+ ///
+ /// 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.
+ ///
+ 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;
+ }
+
+ ///
+ /// Looks for a property with the specified property name, and returns its value as an object.
/// The resulting value may be null if the property is not serialized.
/// You can use the templated ReadProperty overload to fetch it as a specific type with a default value provided.
///
/// The name of the property to be fetched from this Instance.
/// An object reference to the value of the specified property, if it exists.
- ///
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;
}
///
@@ -291,52 +356,39 @@ namespace RobloxFiles
///
/// Adds a property by reference to this Instance's property list.
- /// This is used during the file loading procedure.
///
/// A reference to the property that will be added.
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);
}
///
- /// 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;
- ///
+ /// Removes a property with the provided name if a property with the provided name exists.
///
- public object this[string accessor]
+ /// The name of the property to be removed.
+ /// True if a property with the provided name was removed.
+ 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);
}
}
}
\ No newline at end of file
diff --git a/Tree/Property.cs b/Tree/Property.cs
index bc95504..f422573 100644
--- a/Tree/Property.cs
+++ b/Tree/Property.cs
@@ -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;
- }
}
}
\ No newline at end of file
diff --git a/XmlFormat/XmlDataReader.cs b/XmlFormat/IO/XmlFileReader.cs
similarity index 87%
rename from XmlFormat/XmlDataReader.cs
rename to XmlFormat/IO/XmlFileReader.cs
index e06996d..80fd12d 100644
--- a/XmlFormat/XmlDataReader.cs
+++ b/XmlFormat/IO/XmlFileReader.cs
@@ -3,7 +3,7 @@ using System.Xml;
namespace RobloxFiles.XmlFormat
{
- public static class XmlDataReader
+ public static class XmlRobloxFileReader
{
private static Func 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.
diff --git a/XmlFormat/XmlDataWriter.cs b/XmlFormat/IO/XmlFileWriter.cs
similarity index 89%
rename from XmlFormat/XmlDataWriter.cs
rename to XmlFormat/IO/XmlFileWriter.cs
index 67b8f4e..dc97deb 100644
--- a/XmlFormat/XmlDataWriter.cs
+++ b/XmlFormat/IO/XmlFileWriter.cs
@@ -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);
diff --git a/Interfaces/IXmlPropertyToken.cs b/XmlFormat/PropertyTokens/IXmlPropertyToken.cs
similarity index 64%
rename from Interfaces/IXmlPropertyToken.cs
rename to XmlFormat/PropertyTokens/IXmlPropertyToken.cs
index 52ec3d8..5a53f94 100644
--- a/Interfaces/IXmlPropertyToken.cs
+++ b/XmlFormat/PropertyTokens/IXmlPropertyToken.cs
@@ -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
{
diff --git a/XmlFormat/PropertyTokens/Axes.cs b/XmlFormat/PropertyTokens/Tokens/Axes.cs
similarity index 100%
rename from XmlFormat/PropertyTokens/Axes.cs
rename to XmlFormat/PropertyTokens/Tokens/Axes.cs
diff --git a/XmlFormat/PropertyTokens/BinaryString.cs b/XmlFormat/PropertyTokens/Tokens/BinaryString.cs
similarity index 93%
rename from XmlFormat/PropertyTokens/BinaryString.cs
rename to XmlFormat/PropertyTokens/Tokens/BinaryString.cs
index 4ad735c..ad5cd15 100644
--- a/XmlFormat/PropertyTokens/BinaryString.cs
+++ b/XmlFormat/PropertyTokens/Tokens/BinaryString.cs
@@ -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)
diff --git a/XmlFormat/PropertyTokens/Boolean.cs b/XmlFormat/PropertyTokens/Tokens/Boolean.cs
similarity index 100%
rename from XmlFormat/PropertyTokens/Boolean.cs
rename to XmlFormat/PropertyTokens/Tokens/Boolean.cs
diff --git a/XmlFormat/PropertyTokens/BrickColor.cs b/XmlFormat/PropertyTokens/Tokens/BrickColor.cs
similarity index 100%
rename from XmlFormat/PropertyTokens/BrickColor.cs
rename to XmlFormat/PropertyTokens/Tokens/BrickColor.cs
diff --git a/XmlFormat/PropertyTokens/CFrame.cs b/XmlFormat/PropertyTokens/Tokens/CFrame.cs
similarity index 100%
rename from XmlFormat/PropertyTokens/CFrame.cs
rename to XmlFormat/PropertyTokens/Tokens/CFrame.cs
diff --git a/XmlFormat/PropertyTokens/Color3.cs b/XmlFormat/PropertyTokens/Tokens/Color3.cs
similarity index 100%
rename from XmlFormat/PropertyTokens/Color3.cs
rename to XmlFormat/PropertyTokens/Tokens/Color3.cs
diff --git a/XmlFormat/PropertyTokens/Color3uint8.cs b/XmlFormat/PropertyTokens/Tokens/Color3uint8.cs
similarity index 100%
rename from XmlFormat/PropertyTokens/Color3uint8.cs
rename to XmlFormat/PropertyTokens/Tokens/Color3uint8.cs
diff --git a/XmlFormat/PropertyTokens/ColorSequence.cs b/XmlFormat/PropertyTokens/Tokens/ColorSequence.cs
similarity index 100%
rename from XmlFormat/PropertyTokens/ColorSequence.cs
rename to XmlFormat/PropertyTokens/Tokens/ColorSequence.cs
diff --git a/XmlFormat/PropertyTokens/Content.cs b/XmlFormat/PropertyTokens/Tokens/Content.cs
similarity index 96%
rename from XmlFormat/PropertyTokens/Content.cs
rename to XmlFormat/PropertyTokens/Tokens/Content.cs
index 2bb7e60..514bbe8 100644
--- a/XmlFormat/PropertyTokens/Content.cs
+++ b/XmlFormat/PropertyTokens/Tokens/Content.cs
@@ -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;
}
}
diff --git a/XmlFormat/PropertyTokens/Double.cs b/XmlFormat/PropertyTokens/Tokens/Double.cs
similarity index 100%
rename from XmlFormat/PropertyTokens/Double.cs
rename to XmlFormat/PropertyTokens/Tokens/Double.cs
diff --git a/XmlFormat/PropertyTokens/Enum.cs b/XmlFormat/PropertyTokens/Tokens/Enum.cs
similarity index 100%
rename from XmlFormat/PropertyTokens/Enum.cs
rename to XmlFormat/PropertyTokens/Tokens/Enum.cs
diff --git a/XmlFormat/PropertyTokens/Faces.cs b/XmlFormat/PropertyTokens/Tokens/Faces.cs
similarity index 100%
rename from XmlFormat/PropertyTokens/Faces.cs
rename to XmlFormat/PropertyTokens/Tokens/Faces.cs
diff --git a/XmlFormat/PropertyTokens/Float.cs b/XmlFormat/PropertyTokens/Tokens/Float.cs
similarity index 100%
rename from XmlFormat/PropertyTokens/Float.cs
rename to XmlFormat/PropertyTokens/Tokens/Float.cs
diff --git a/XmlFormat/PropertyTokens/Int.cs b/XmlFormat/PropertyTokens/Tokens/Int.cs
similarity index 98%
rename from XmlFormat/PropertyTokens/Int.cs
rename to XmlFormat/PropertyTokens/Tokens/Int.cs
index c6d0d0c..2223036 100644
--- a/XmlFormat/PropertyTokens/Int.cs
+++ b/XmlFormat/PropertyTokens/Tokens/Int.cs
@@ -20,8 +20,6 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{
return XmlPropertyTokens.ReadPropertyGeneric(prop, PropertyType.Int, token);
}
-
-
}
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
diff --git a/XmlFormat/PropertyTokens/Int64.cs b/XmlFormat/PropertyTokens/Tokens/Int64.cs
similarity index 100%
rename from XmlFormat/PropertyTokens/Int64.cs
rename to XmlFormat/PropertyTokens/Tokens/Int64.cs
diff --git a/XmlFormat/PropertyTokens/NumberRange.cs b/XmlFormat/PropertyTokens/Tokens/NumberRange.cs
similarity index 100%
rename from XmlFormat/PropertyTokens/NumberRange.cs
rename to XmlFormat/PropertyTokens/Tokens/NumberRange.cs
diff --git a/XmlFormat/PropertyTokens/NumberSequence.cs b/XmlFormat/PropertyTokens/Tokens/NumberSequence.cs
similarity index 100%
rename from XmlFormat/PropertyTokens/NumberSequence.cs
rename to XmlFormat/PropertyTokens/Tokens/NumberSequence.cs
diff --git a/XmlFormat/PropertyTokens/PhysicalProperties.cs b/XmlFormat/PropertyTokens/Tokens/PhysicalProperties.cs
similarity index 99%
rename from XmlFormat/PropertyTokens/PhysicalProperties.cs
rename to XmlFormat/PropertyTokens/Tokens/PhysicalProperties.cs
index b1ccc6a..2950156 100644
--- a/XmlFormat/PropertyTokens/PhysicalProperties.cs
+++ b/XmlFormat/PropertyTokens/Tokens/PhysicalProperties.cs
@@ -78,6 +78,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
XmlElement element = doc.CreateElement(elementType);
element.InnerText = value.ToInvariantString();
+
node.AppendChild(element);
}
}
diff --git a/XmlFormat/PropertyTokens/Ray.cs b/XmlFormat/PropertyTokens/Tokens/Ray.cs
similarity index 97%
rename from XmlFormat/PropertyTokens/Ray.cs
rename to XmlFormat/PropertyTokens/Tokens/Ray.cs
index ef1acfb..7c3c98d 100644
--- a/XmlFormat/PropertyTokens/Ray.cs
+++ b/XmlFormat/PropertyTokens/Tokens/Ray.cs
@@ -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);
}
}
diff --git a/XmlFormat/PropertyTokens/Rect.cs b/XmlFormat/PropertyTokens/Tokens/Rect.cs
similarity index 100%
rename from XmlFormat/PropertyTokens/Rect.cs
rename to XmlFormat/PropertyTokens/Tokens/Rect.cs
diff --git a/XmlFormat/PropertyTokens/Ref.cs b/XmlFormat/PropertyTokens/Tokens/Ref.cs
similarity index 100%
rename from XmlFormat/PropertyTokens/Ref.cs
rename to XmlFormat/PropertyTokens/Tokens/Ref.cs
diff --git a/XmlFormat/PropertyTokens/SharedString.cs b/XmlFormat/PropertyTokens/Tokens/SharedString.cs
similarity index 100%
rename from XmlFormat/PropertyTokens/SharedString.cs
rename to XmlFormat/PropertyTokens/Tokens/SharedString.cs
diff --git a/XmlFormat/PropertyTokens/String.cs b/XmlFormat/PropertyTokens/Tokens/String.cs
similarity index 100%
rename from XmlFormat/PropertyTokens/String.cs
rename to XmlFormat/PropertyTokens/Tokens/String.cs
diff --git a/XmlFormat/PropertyTokens/UDim.cs b/XmlFormat/PropertyTokens/Tokens/UDim.cs
similarity index 100%
rename from XmlFormat/PropertyTokens/UDim.cs
rename to XmlFormat/PropertyTokens/Tokens/UDim.cs
diff --git a/XmlFormat/PropertyTokens/UDim2.cs b/XmlFormat/PropertyTokens/Tokens/UDim2.cs
similarity index 100%
rename from XmlFormat/PropertyTokens/UDim2.cs
rename to XmlFormat/PropertyTokens/Tokens/UDim2.cs
diff --git a/XmlFormat/PropertyTokens/Vector2.cs b/XmlFormat/PropertyTokens/Tokens/Vector2.cs
similarity index 100%
rename from XmlFormat/PropertyTokens/Vector2.cs
rename to XmlFormat/PropertyTokens/Tokens/Vector2.cs
diff --git a/XmlFormat/PropertyTokens/Vector3.cs b/XmlFormat/PropertyTokens/Tokens/Vector3.cs
similarity index 100%
rename from XmlFormat/PropertyTokens/Vector3.cs
rename to XmlFormat/PropertyTokens/Tokens/Vector3.cs
diff --git a/XmlFormat/PropertyTokens/Vector3int16.cs b/XmlFormat/PropertyTokens/Tokens/Vector3int16.cs
similarity index 100%
rename from XmlFormat/PropertyTokens/Vector3int16.cs
rename to XmlFormat/PropertyTokens/Tokens/Vector3int16.cs
diff --git a/XmlFormat/XmlPropertyTokens.cs b/XmlFormat/PropertyTokens/XmlPropertyTokens.cs
similarity index 98%
rename from XmlFormat/XmlPropertyTokens.cs
rename to XmlFormat/PropertyTokens/XmlPropertyTokens.cs
index ac56c20..4e31090 100644
--- a/XmlFormat/XmlPropertyTokens.cs
+++ b/XmlFormat/PropertyTokens/XmlPropertyTokens.cs
@@ -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
diff --git a/XmlFormat/XmlRobloxFile.cs b/XmlFormat/XmlRobloxFile.cs
index 6616de2..ddf2eba 100644
--- a/XmlFormat/XmlRobloxFile.cs
+++ b/XmlFormat/XmlRobloxFile.cs
@@ -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 Instances = new Dictionary();
- public Dictionary SharedStrings = new Dictionary();
-
- public void ReadFile(byte[] buffer)
- {
- Instances.Clear();
- SharedStrings.Clear();
+ internal Dictionary Instances = new Dictionary();
+ internal Dictionary SharedStrings = new Dictionary();
+ 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);