diff --git a/BinaryFormat/BinaryFileChunk.cs b/BinaryFormat/BinaryFileChunk.cs index 60d5a0a..3846604 100644 --- a/BinaryFormat/BinaryFileChunk.cs +++ b/BinaryFormat/BinaryFileChunk.cs @@ -26,12 +26,6 @@ namespace RobloxFiles.BinaryFormat public bool HasWriteBuffer { get; private set; } public byte[] WriteBuffer { get; private set; } - public BinaryRobloxFileReader GetDataReader(BinaryRobloxFile file) - { - MemoryStream buffer = new MemoryStream(Data); - return new BinaryRobloxFileReader(file, buffer); - } - public override string ToString() { string chunkType = ChunkType.Replace('\0', ' '); @@ -81,7 +75,7 @@ namespace RobloxFiles.BinaryFormat if (!compress || CompressedSize > Size) { CompressedSize = 0; - CompressedData = new byte[0]; + CompressedData = Array.Empty(); } ChunkType = writer.ChunkType; diff --git a/BinaryFormat/BinaryRobloxFile.cs b/BinaryFormat/BinaryRobloxFile.cs index 8029cc2..ad4ac47 100644 --- a/BinaryFormat/BinaryRobloxFile.cs +++ b/BinaryFormat/BinaryRobloxFile.cs @@ -28,9 +28,9 @@ namespace RobloxFiles public Instance[] Instances { get; internal set; } public INST[] Classes { get; internal set; } - internal META META = null; - internal SSTR SSTR = null; - internal SIGN SIGN = null; + internal META META; + internal SSTR SSTR; + internal SIGN SIGN; public bool HasMetadata => (META != null); public Dictionary Metadata => META?.Data; @@ -104,18 +104,22 @@ namespace RobloxFiles reading = false; break; case string unhandled: - Console.WriteLine("BinaryRobloxFile - Unhandled chunk-type: {0}!", unhandled); + Console.Error.WriteLine("BinaryRobloxFile - Unhandled chunk-type: {0}!", unhandled); break; default: break; } if (handler != null) { - chunk.Handler = handler; - - using (var dataReader = chunk.GetDataReader(this)) - handler.Load(dataReader); - + using (var readBuffer = new MemoryStream(chunk.Data)) + { + using (var dataReader = new BinaryRobloxFileReader(this, readBuffer)) + { + chunk.Handler = handler; + handler.Load(dataReader); + } + } + ChunksImpl.Add(chunk); } } @@ -133,16 +137,17 @@ namespace RobloxFiles // Generate the chunk data. ////////////////////////////////////////////////////////////////////////// - using (var writer = new BinaryRobloxFileWriter(this)) + using (var workBuffer = new MemoryStream()) + using (var writer = new BinaryRobloxFileWriter(this, workBuffer)) { // Clear the existing data. Referent = "-1"; ChunksImpl.Clear(); - + NumInstances = 0; NumClasses = 0; SSTR = null; - + // Recursively capture all instances and classes. writer.RecordInstances(Children); @@ -168,7 +173,7 @@ namespace RobloxFiles // Write the PRNT chunk. var parents = new PRNT(); writer.SaveChunk(parents); - + // Write the SSTR chunk. if (HasSharedStrings) writer.SaveChunk(SSTR, 0); diff --git a/BinaryFormat/IO/BinaryFileReader.cs b/BinaryFormat/IO/BinaryFileReader.cs index 5c0cbde..c19e304 100644 --- a/BinaryFormat/IO/BinaryFileReader.cs +++ b/BinaryFormat/IO/BinaryFileReader.cs @@ -10,7 +10,7 @@ namespace RobloxFiles.BinaryFormat public class BinaryRobloxFileReader : BinaryReader { public readonly BinaryRobloxFile File; - private byte[] lastStringBuffer = new byte[0] { }; + private byte[] lastStringBuffer = Array.Empty(); public BinaryRobloxFileReader(BinaryRobloxFile file, Stream stream) : base(stream) { diff --git a/BinaryFormat/IO/BinaryFileWriter.cs b/BinaryFormat/IO/BinaryFileWriter.cs index f397900..78e525d 100644 --- a/BinaryFormat/IO/BinaryFileWriter.cs +++ b/BinaryFormat/IO/BinaryFileWriter.cs @@ -28,7 +28,7 @@ namespace RobloxFiles.BinaryFormat // Instances in child->parent order internal List PostInstances { get; private set; } - public BinaryRobloxFileWriter(BinaryRobloxFile file, Stream workBuffer = null) : base(workBuffer ?? new MemoryStream()) + public BinaryRobloxFileWriter(BinaryRobloxFile file, Stream workBuffer) : base(workBuffer) { File = file; @@ -167,14 +167,13 @@ namespace RobloxFiles.BinaryFormat if (!instance.Archivable) continue; - int instId = (int)(File.NumInstances++); - instance.Referent = instId.ToString(); + int instId = (int)File.NumInstances++; + string className = instance.ClassName; + + instance.Referent = instId.ToInvariantString(); Instances.Add(instance); - string className = instance.ClassName; - INST inst; - - if (!ClassMap.TryGetValue(className, out inst)) + if (!ClassMap.TryGetValue(className, out INST inst)) { inst = new INST() { diff --git a/DataTypes/BrickColor.cs b/DataTypes/BrickColor.cs index d85af38..ec55098 100644 --- a/DataTypes/BrickColor.cs +++ b/DataTypes/BrickColor.cs @@ -53,7 +53,7 @@ namespace RobloxFiles.DataTypes BrickColor result = null; var query = BrickColors.ColorMap.Where((bc) => bc.Name == name); - if (query.Count() > 0) + if (query.Any()) result = query.First(); else result = FromName(DefaultName); diff --git a/DataTypes/CFrame.cs b/DataTypes/CFrame.cs index 0726bb3..6955c44 100644 --- a/DataTypes/CFrame.cs +++ b/DataTypes/CFrame.cs @@ -1,13 +1,15 @@ using System; +using System.Diagnostics.Contracts; using RobloxFiles.Enums; namespace RobloxFiles.DataTypes { public class CFrame { - private float m11 = 1, m12 = 0, m13 = 0, m14 = 0; - private float m21 = 0, m22 = 1, m23 = 0, m24 = 0; - private float m31 = 0, m32 = 0, m33 = 1, m34 = 0; + private float m11 = 1, m12, m13, m14; + private float m21, m22 = 1, m23, m24; + private float m31, m32, m33 = 1, m34; + private const float m41 = 0, m42 = 0, m43 = 0, m44 = 1; public float X => m14; @@ -16,21 +18,21 @@ namespace RobloxFiles.DataTypes public Vector3 Position { - get - { - return new Vector3(X, Y, Z); - } + get => new Vector3(X, Y, Z); + set { + Contract.Requires(value != null); + m14 = value.X; m24 = value.Y; m34 = value.Z; } } - public Vector3 RightVector => new Vector3( m11, m12, m13); public Vector3 UpVector => new Vector3( m21, m22, m23); public Vector3 LookVector => new Vector3(-m31, -m32, -m33); + public Vector3 RightVector => new Vector3(m11, m12, m13); public CFrame() { @@ -41,6 +43,8 @@ namespace RobloxFiles.DataTypes public CFrame(Vector3 pos) { + Contract.Requires(pos != null); + m14 = pos.X; m24 = pos.Y; m34 = pos.Z; @@ -113,9 +117,8 @@ namespace RobloxFiles.DataTypes public CFrame(params float[] comp) { - if (comp.Length < 12) - throw new Exception("There should be 12 floats provided to construct CFrame with an array of floats"); - + Contract.Requires(comp.Length >= 12, "There should be 12 floats provided to construct a CFrame with an array of floats"); + m14 = comp[0]; m24 = comp[1]; m34 = comp[2]; m11 = comp[3]; m12 = comp[4]; m13 = comp[5]; m21 = comp[6]; m22 = comp[7]; m23 = comp[8]; @@ -135,6 +138,7 @@ namespace RobloxFiles.DataTypes public CFrame(Vector3 pos, Vector3 vX, Vector3 vY, Vector3 vZ = null) { + Contract.Requires(pos != null && vX != null && vY != null); initFromMatrix(pos, vX, vY, vZ); } @@ -192,16 +196,22 @@ namespace RobloxFiles.DataTypes public static Vector3 operator *(CFrame a, Vector3 b) { + if (a == null) + throw new ArgumentNullException(nameof(a)); + else if (b == null) + throw new ArgumentNullException(nameof(b)); + float[] ac = a.GetComponents(); - float x = ac[0], y = ac[1], z = ac[2], - m11 = ac[3], m12 = ac[4], m13 = ac[5], + float m11 = ac[3], m12 = ac[4], m13 = ac[5], m21 = ac[6], m22 = ac[7], m23 = ac[8], m31 = ac[9], m32 = ac[10], m33 = ac[11]; - Vector3 right = new Vector3(m11, m21, m31); - Vector3 up = new Vector3(m12, m22, m32); - Vector3 back = new Vector3(m13, m23, m33); + + var up = new Vector3(m12, m22, m32); + var back = new Vector3(m13, m23, m33); + var right = new Vector3(m11, m21, m31); + return a.Position + b.X * right + b.Y * up + b.Z * back; } @@ -235,11 +245,6 @@ namespace RobloxFiles.DataTypes float n33 = a31 * b13 + a32 * b23 + a33 * b33 + a34 * m43; float n34 = a31 * b14 + a32 * b24 + a33 * b34 + a34 * m44; - float n41 = m41 * b11 + m42 * b21 + m43 * b31 + m44 * m41; - float n42 = m41 * b12 + m42 * b22 + m43 * b32 + m44 * m42; - float n43 = m41 * b13 + m42 * b23 + m43 * b33 + m44 * m43; - float n44 = m41 * b14 + m42 * b24 + m43 * b34 + m44 * m44; - return new CFrame(n14, n24, n34, n11, n12, n13, n21, n22, n23, n31, n32, n33); } @@ -263,9 +268,9 @@ namespace RobloxFiles.DataTypes { float[] ac = GetComponents(); - float a14 = ac[0], a24 = ac[1], a34 = ac[2], - a11 = ac[3], a12 = ac[4], a13 = ac[5], - a21 = ac[6], a22 = ac[7], a23 = ac[8], + float a14 = ac[0], a24 = ac[1], a34 = ac[2], + a11 = ac[3], a12 = ac[4], a13 = ac[5], + a21 = ac[6], a22 = ac[7], a23 = ac[8], a31 = ac[9], a32 = ac[10], a33 = ac[11]; float det = ( a11 * a22 * a33 * m44 + a11 * a23 * a34 * m42 + a11 * a24 * a32 * m43 @@ -295,11 +300,6 @@ namespace RobloxFiles.DataTypes float b33 = (a11 * a22 * m44 + a12 * a24 * m41 + a14 * a21 * m42 - a11 * a24 * m42 - a12 * a21 * m44 - a14 * a22 * m41) / det; float b34 = (a11 * a24 * a32 + a12 * a21 * a34 + a14 * a22 * a31 - a11 * a22 * a34 - a12 * a24 * a31 - a14 * a21 * a32) / det; - float b41 = (a21 * a33 * m42 + a22 * a31 * m43 + a23 * a32 * m41 - a21 * a32 * m43 - a22 * a33 * m41 - a23 * a31 * m42) / det; - float b42 = (a11 * a32 * m43 + a12 * a33 * m41 + a13 * a31 * m42 - a11 * a33 * m42 - a12 * a31 * m43 - a13 * a32 * m41) / det; - float b43 = (a11 * a23 * m42 + a12 * a21 * m43 + a13 * a22 * m41 - a11 * a22 * m43 - a12 * a23 * m41 - a13 * a21 * m42) / det; - float b44 = (a11 * a22 * a33 + a12 * a23 * a31 + a13 * a21 * a32 - a11 * a23 * a32 - a12 * a21 * a33 - a13 * a22 * a31) / det; - return new CFrame(b14, b24, b34, b11, b12, b13, b21, b22, b23, b31, b32, b33); } diff --git a/RobloxFileFormat.csproj b/RobloxFileFormat.csproj index b9a34c0..da3da51 100644 --- a/RobloxFileFormat.csproj +++ b/RobloxFileFormat.csproj @@ -1,10 +1,5 @@  - - - - - @@ -181,17 +176,6 @@ - - - - - - - - - - - copy /y $(TargetPath) $(ProjectDir)$(TargetFileName) @@ -202,11 +186,6 @@ - - - - - \ No newline at end of file diff --git a/RobloxFileFormat.dll b/RobloxFileFormat.dll index 15dfaa2..354882e 100644 Binary files a/RobloxFileFormat.dll and b/RobloxFileFormat.dll differ diff --git a/Tree/Attributes.cs b/Tree/Attributes.cs index ef86b4a..bba5e16 100644 --- a/Tree/Attributes.cs +++ b/Tree/Attributes.cs @@ -41,7 +41,7 @@ namespace RobloxFiles Region3int16, } - public class Attribute + public class Attribute : IDisposable { public AttributeType DataType { get; private set; } public object Value { get; private set; } @@ -202,7 +202,12 @@ namespace RobloxFiles reader = null; } - + + public void Dispose() + { + reader.Dispose(); + } + internal Attribute(BinaryReader reader) { this.reader = reader; @@ -252,7 +257,7 @@ namespace RobloxFiles internal byte[] Serialize() { // TODO - return new byte[0]; + return Array.Empty(); } } } diff --git a/Tree/Instance.cs b/Tree/Instance.cs index 0f6d07c..74a651a 100644 --- a/Tree/Instance.cs +++ b/Tree/Instance.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.Contracts; using System.IO; using System.Linq; using System.Reflection; @@ -66,7 +67,7 @@ namespace RobloxFiles { get { - return Attributes?.Serialize() ?? new byte[0]; + return Attributes?.Serialize() ?? Array.Empty(); } set { @@ -136,6 +137,7 @@ namespace RobloxFiles /// The instance whose ancestry will be tested against this Instance. public bool IsDescendantOf(Instance ancestor) { + Contract.Requires(ancestor != null); return ancestor.IsAncestorOf(this); } @@ -265,7 +267,7 @@ namespace RobloxFiles .Where(child => name == child.Name) .Cast(); - if (query.Count() > 0) + if (query.Any()) { result = query.First(); } @@ -309,7 +311,7 @@ namespace RobloxFiles while (ancestor != null) { if (ancestor is T && ancestor.Name == name) - return (T)ancestor; + return ancestor as T; ancestor = ancestor.Parent; } @@ -334,15 +336,12 @@ namespace RobloxFiles /// The Name of the Instance to find. public T FindFirstAncestorOfClass() where T : Instance { - Type classType = typeof(T); - string className = classType.Name; - Instance ancestor = Parent; while (ancestor != null) { if (ancestor is T) - return (T)ancestor; + return ancestor as T; ancestor = ancestor.Parent; } @@ -387,7 +386,7 @@ namespace RobloxFiles T result = null; - if (query.Count() > 0) + if (query.Any()) { result = query.First(); } @@ -421,7 +420,7 @@ namespace RobloxFiles T result = null; - if (query.Count() > 0) + if (query.Any()) { result = query.First(); } diff --git a/Tree/Property.cs b/Tree/Property.cs index fedfb54..da959c0 100644 --- a/Tree/Property.cs +++ b/Tree/Property.cs @@ -152,7 +152,7 @@ namespace RobloxFiles { FieldInfo directField = instType .GetFields() - .Where(field => field.Name.StartsWith(Name)) + .Where(field => field.Name.StartsWith(Name, StringComparison.InvariantCulture)) .Where(field => field.DeclaringType == instType) .FirstOrDefault(); diff --git a/Utility/Formatting.cs b/Utility/Formatting.cs index 2136ab2..0b83233 100644 --- a/Utility/Formatting.cs +++ b/Utility/Formatting.cs @@ -55,6 +55,26 @@ internal static class Formatting } } + public static string ToLowerInvariant(this string str) + { + return str.ToLower(Invariant); + } + + public static string ToUpperInvariant(this string str) + { + return str.ToUpper(Invariant); + } + + public static bool StartsWithInvariant(this string str, string other) + { + return str.StartsWith(other, StringComparison.InvariantCulture); + } + + public static bool EndsWithInvariant(this string str, string other) + { + return str.EndsWith(other, StringComparison.InvariantCulture); + } + public static float ParseFloat(string value) { switch (value) diff --git a/XmlFormat/IO/XmlFileWriter.cs b/XmlFormat/IO/XmlFileWriter.cs index 14249b3..e019b3a 100644 --- a/XmlFormat/IO/XmlFileWriter.cs +++ b/XmlFormat/IO/XmlFileWriter.cs @@ -1,4 +1,6 @@ using System; +using System.Diagnostics.Contracts; +using System.Globalization; using System.Linq; using System.Text; using System.Xml; @@ -21,13 +23,15 @@ namespace RobloxFiles.XmlFormat public static string CreateReferent() { - Guid referentGuid = Guid.NewGuid(); + var referentGuid = Guid + .NewGuid() + .ToString(); string referent = "RBX" + referentGuid - .ToString() - .ToUpper(); + .ToUpper(CultureInfo.InvariantCulture) + .Replace("-", ""); - return referent.Replace("-", ""); + return referent; } private static string GetEnumName(T item) where T : struct @@ -77,7 +81,7 @@ namespace RobloxFiles.XmlFormat case PropertyType.Float: case PropertyType.Int64: case PropertyType.Double: - propType = propType.ToLower(); + propType = propType.ToLower(CultureInfo.InvariantCulture); break; case PropertyType.String: propType = (prop.HasRawBuffer ? "BinaryString" : "string"); @@ -166,6 +170,7 @@ namespace RobloxFiles.XmlFormat public static XmlNode WriteSharedStrings(XmlDocument doc, XmlRobloxFile file) { + Contract.Requires(doc != null && file != null); XmlElement sharedStrings = doc.CreateElement("SharedStrings"); var binaryWriter = XmlPropertyTokens.GetHandler(); diff --git a/XmlFormat/Tokens/Axes.cs b/XmlFormat/Tokens/Axes.cs index c43509b..d5bc2ab 100644 --- a/XmlFormat/Tokens/Axes.cs +++ b/XmlFormat/Tokens/Axes.cs @@ -9,9 +9,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens public bool ReadProperty(Property prop, XmlNode token) { - uint value; - - if (XmlPropertyTokens.ReadPropertyGeneric(token, out value)) + if (XmlPropertyTokens.ReadPropertyGeneric(token, out uint value)) { Axes axes = (Axes)value; prop.Value = axes; diff --git a/XmlFormat/Tokens/BrickColor.cs b/XmlFormat/Tokens/BrickColor.cs index c509c6a..2f6ff9d 100644 --- a/XmlFormat/Tokens/BrickColor.cs +++ b/XmlFormat/Tokens/BrickColor.cs @@ -13,9 +13,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens public bool ReadProperty(Property prop, XmlNode token) { - int value; - - if (XmlPropertyTokens.ReadPropertyGeneric(token, out value)) + if (XmlPropertyTokens.ReadPropertyGeneric(token, out int value)) { BrickColor brickColor = BrickColor.FromNumber(value); prop.XmlToken = "BrickColor"; diff --git a/XmlFormat/Tokens/Color3uint8.cs b/XmlFormat/Tokens/Color3uint8.cs index 3b3ce05..6b736aa 100644 --- a/XmlFormat/Tokens/Color3uint8.cs +++ b/XmlFormat/Tokens/Color3uint8.cs @@ -1,5 +1,7 @@ using System.Xml; using RobloxFiles.DataTypes; +using System.Diagnostics.Contracts; +using System; namespace RobloxFiles.XmlFormat.PropertyTokens { @@ -9,9 +11,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens public bool ReadProperty(Property prop, XmlNode token) { - uint value; - - if (XmlPropertyTokens.ReadPropertyGeneric(token, out value)) + if (XmlPropertyTokens.ReadPropertyGeneric(token, out uint value)) { uint r = (value >> 16) & 0xFF; uint g = (value >> 8) & 0xFF; @@ -28,14 +28,16 @@ namespace RobloxFiles.XmlFormat.PropertyTokens public void WriteProperty(Property prop, XmlDocument doc, XmlNode node) { - Color3uint8 color = prop.CastValue(); + Color3uint8 color = prop?.CastValue(); + Contract.Requires(node != null); + uint r = color.R, g = color.G, b = color.B; uint rgb = (255u << 24) | (r << 16) | (g << 8) | b; - node.InnerText = rgb.ToString(); + node.InnerText = rgb.ToInvariantString(); } } } diff --git a/XmlFormat/Tokens/Enum.cs b/XmlFormat/Tokens/Enum.cs index b9f8536..b237d68 100644 --- a/XmlFormat/Tokens/Enum.cs +++ b/XmlFormat/Tokens/Enum.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.Contracts; using System.Reflection; using System.Xml; @@ -10,9 +11,9 @@ namespace RobloxFiles.XmlFormat.PropertyTokens public bool ReadProperty(Property prop, XmlNode token) { - uint value; + Contract.Requires(prop != null); - if (XmlPropertyTokens.ReadPropertyGeneric(token, out value)) + if (XmlPropertyTokens.ReadPropertyGeneric(token, out uint value)) { Instance inst = prop.Instance; Type instType = inst?.GetType(); @@ -36,13 +37,13 @@ namespace RobloxFiles.XmlFormat.PropertyTokens public void WriteProperty(Property prop, XmlDocument doc, XmlNode node) { + Contract.Requires(prop != null && node != null); object rawValue = prop.Value; - Type valueType = rawValue.GetType(); - + int signed = (int)rawValue; uint value = (uint)signed; - node.InnerText = value.ToString(); + node.InnerText = value.ToInvariantString(); } } } diff --git a/XmlFormat/Tokens/Faces.cs b/XmlFormat/Tokens/Faces.cs index f29db4b..e1bb4e0 100644 --- a/XmlFormat/Tokens/Faces.cs +++ b/XmlFormat/Tokens/Faces.cs @@ -1,4 +1,5 @@ -using System.Xml; +using System.Diagnostics.Contracts; +using System.Xml; using RobloxFiles.DataTypes; namespace RobloxFiles.XmlFormat.PropertyTokens @@ -9,9 +10,9 @@ namespace RobloxFiles.XmlFormat.PropertyTokens public bool ReadProperty(Property prop, XmlNode token) { - uint value; + Contract.Requires(prop != null); - if (XmlPropertyTokens.ReadPropertyGeneric(token, out value)) + if (XmlPropertyTokens.ReadPropertyGeneric(token, out uint value)) { Faces faces = (Faces)value; prop.Value = faces; @@ -24,6 +25,8 @@ namespace RobloxFiles.XmlFormat.PropertyTokens public void WriteProperty(Property prop, XmlDocument doc, XmlNode node) { + Contract.Requires(prop != null && doc != null && node != null); + XmlElement faces = doc.CreateElement("faces"); node.AppendChild(faces); diff --git a/XmlFormat/XmlRobloxFile.cs b/XmlFormat/XmlRobloxFile.cs index 759bcd4..ab072b3 100644 --- a/XmlFormat/XmlRobloxFile.cs +++ b/XmlFormat/XmlRobloxFile.cs @@ -33,7 +33,14 @@ namespace RobloxFiles try { string xml = Encoding.UTF8.GetString(buffer); - XmlDocument.LoadXml(xml); + var settings = new XmlReaderSettings() { XmlResolver = null }; + + using (StringReader reader = new StringReader(xml)) + { + XmlReader xmlReader = XmlReader.Create(reader, settings); + XmlDocument.Load(xmlReader); + xmlReader.Dispose(); + } } catch { @@ -46,9 +53,8 @@ namespace RobloxFiles { // Verify the version we are using. XmlNode version = roblox.Attributes.GetNamedItem("version"); - int schemaVersion; - - if (version == null || !int.TryParse(version.Value, out schemaVersion)) + + if (version == null || !int.TryParse(version.Value, out int schemaVersion)) throw new Exception("XmlRobloxFile: No version number defined!"); else if (schemaVersion < 4) throw new Exception("XmlRobloxFile: Provided version must be at least 4!"); diff --git a/packages.config b/packages.config index f33cbab..146db44 100644 --- a/packages.config +++ b/packages.config @@ -4,10 +4,5 @@ - - - - - \ No newline at end of file