diff --git a/README.md b/README.md index 6a1e74a..8d752ed 100644 --- a/README.md +++ b/README.md @@ -3,14 +3,21 @@ [![NuGet](https://img.shields.io/nuget/v/EndianBinaryIO.svg)](https://www.nuget.org/packages/EndianBinaryIO) [![NuGet downloads](https://img.shields.io/nuget/dt/EndianBinaryIO)](https://www.nuget.org/packages/EndianBinaryIO) -A C# library that can read and write primitives, enums, arrays, and strings to streams using specified endianness, string encoding, and boolean sizes. -Objects can also be read from/written to streams via reflection and attributes. +This .NET library provides a simple API to read/write bytes from/to streams and spans using user-specified endianness. +By default, supported types include primitives, enums, arrays, strings, and some common .NET struct types. +Objects can also be read/written from/to streams via reflection and attributes. +The developer can use the API even if their target behavior or data is not directly supported by using the `IBinarySerializable` interface, inheritting from the reader/writer, or using the manual `Span`/`ReadOnlySpan` methods without streams. +Performance is the focus when not using reflection; no allocations unless absolutely necessary! -The `IBinarySerializable` interface allows an object to be read and written in a customizable fashion. +The `IBinarySerializable` interface allows an object to be read and written in a customizable fashion during reflection. Also included are attributes that can make reading and writing objects less of a headache. For example, classes and structs in C# cannot have ignored members when marshalling, but **EndianBinaryIO** has a `BinaryIgnoreAttribute` that will ignore properties when reading and writing. -There is also an `EndianBitConverter` static class which resembles `System.BitConverter`. With it you can convert to/from data types using arrays rather than streams, all with specific endianness. +The `EndianBinaryPrimitives` static class which resembles `System.Buffers.Binary.BinaryPrimitives` is an API that converts to/from data types using `Span`/`ReadOnlySpan` with specific endianness, rather than streams. + +---- +## Changelog For v2.0.0.0 +Be sure to check the comment at https://github.com/Kermalis/EndianBinaryIO/pull/28! ---- ## 🚀 Usage: @@ -23,47 +30,46 @@ Assume we have the following definitions: ```cs enum ByteSizedEnum : byte { - Val1 = 0x20, - Val2 = 0x80 + Val1 = 0x20, + Val2 = 0x80, } enum ShortSizedEnum : short { - Val1 = 0x40, - Val2 = 0x800 + Val1 = 0x40, + Val2 = 0x800, } class MyBasicObj { - // Properties - public ShortSizedEnum Type { get; set; } - public short Version { get; set; } - public DateTime Date { get; set; } - - // Property that is ignored when reading and writing - [BinaryIgnore(true)] - public ByteSizedEnum DoNotReadOrWrite { get; set; } - - // Arrays work as well - [BinaryArrayFixedLength(16)] - public uint[] ArrayWith16Elements { get; set; } - - // Boolean that occupies 4 bytes instead of one - [BinaryBooleanSize(BooleanSize.U32)] - public bool Bool32 { get; set; } - - // String encoded in ASCII - // Reads chars until the stream encounters a '\0' - // Writing will append a '\0' at the end of the string - [BinaryEncoding("ASCII")] - [BinaryStringNullTerminated(true)] - public string NullTerminatedASCIIString { get; set; } - - // String encoded in UTF16-LE that will only read/write 10 chars - // The BinaryStringTrimNullTerminatorsAttribute will indicate that every char from the first \0 will be removed from the string. This attribute also works with char arrays - [BinaryEncoding("UTF-16")] - [BinaryStringFixedLength(10)] - [BinaryStringTrimNullTerminators(true)] - public string UTF16String { get; set; } + // Properties + public ShortSizedEnum Type { get; set; } + public short Version { get; set; } + public DateTime Date { get; set; } + + // Property that is ignored when reading and writing + [BinaryIgnore] + public ByteSizedEnum DoNotReadOrWrite { get; set; } + + // Arrays work as well + [BinaryArrayFixedLength(16)] + public uint[] ArrayWith16Elements { get; set; } + + // Boolean that occupies 4 bytes instead of one + [BinaryBooleanSize(BooleanSize.U32)] + public bool Bool32 { get; set; } + + // String encoded in ASCII + // Reads chars until the stream encounters a '\0' + // Writing will append a '\0' at the end of the string + [BinaryASCII] + [BinaryStringNullTerminated] + public string NullTerminatedASCIIString { get; set; } + + // String encoded in UTF16-LE that will only read/write 10 chars + // The BinaryStringTrimNullTerminatorsAttribute will indicate that every char from the first \0 will be removed from the string. This attribute also works with char arrays + [BinaryStringFixedLength(10)] + [BinaryStringTrimNullTerminators] + public string UTF16String { get; set; } } ``` And assume these are our input bytes (in little endian): @@ -107,89 +113,93 @@ obj.Type = reader.ReadEnum(); // Reads the enum type based on th obj.Version = reader.ReadInt16(); // Reads a 'short' (2 bytes) obj.Date = reader.ReadDateTime(); // Reads a 'DateTime' (8 bytes) -obj.ArrayWith16Elements = reader.ReadUInt32s(16); // Reads 16 'uint's (4 bytes each) +obj.ArrayWith16Elements = new uint[16]; +reader.ReadUInt32s(obj.ArrayWith16Elements); // Reads 16 'uint's (4 bytes each) -obj.Bool32 = reader.ReadBoolean(); // Reads a 'bool' (4 bytes in this case, since the reader was initiated with a default of BooleanSize.U32, but there is an overload to pass in one) +obj.Bool32 = reader.ReadBoolean(); // Reads a 'bool' (4 bytes in this case, since the reader's current bool state is BooleanSize.U32) -obj.NullTerminatedASCIIString = reader.ReadStringNullTerminated(Encoding.ASCII); // Reads ASCII chars until a '\0' is read, then returns a 'string' -obj.UTF16String = reader.ReadString(10, true, Encoding.Unicode); // Reads 10 UTF16-LE chars as a 'string' with the '\0's removed +reader.ASCII = true; // Set the reader's ASCII state to true +obj.NullTerminatedASCIIString = reader.ReadString_NullTerminated(); // Reads ASCII chars until a '\0' is read, then returns a 'string' + +reader.ASCII = false; // Set the reader's ASCII state to false (UTF16-LE) +obj.UTF16String = reader.ReadString_Count_TrimNullTerminators(10); // Reads 10 UTF16-LE chars as a 'string' with the '\0's removed ``` ### Reading Automatically (With Reflection): ```cs var reader = new EndianBinaryReader(stream, endianness: Endianness.LittleEndian); var obj = reader.ReadObject(); // Create a 'MyBasicObj' and read all properties in order, ignoring any with a 'BinaryIgnoreAttribute' - // Other objects that are properties in this object will also be read in the same way recursively +// Other objects that are properties in this object will also be read in the same way recursively ``` ### Writing Manually: ```cs var obj = new MyBasicObj { - Type = ShortSizedEnum.Val2, - Version = 511, - Date = new DateTime(1998, 12, 30), + Type = ShortSizedEnum.Val2, + Version = 511, + Date = new DateTime(1998, 12, 30), - DoNotReadOrWrite = ByteSizedEnum.Val1, + DoNotReadOrWrite = ByteSizedEnum.Val1, - ArrayWith16Elements = new uint[16] - { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 - }, + ArrayWith16Elements = new uint[16] + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + }, - Bool32 = false, + Bool32 = false, - NullTerminatedASCIIString = "EndianBinaryIO", - UTF16String = "Kermalis" + NullTerminatedASCIIString = "EndianBinaryIO", + UTF16String = "Kermalis", }; var writer = new EndianBinaryWriter(stream, endianness: Endianness.LittleEndian, booleanSize: BooleanSize.U32); -writer.Write(obj.Type); // Writes the enum type based on the amount of bytes of the enum's underlying type (short/2 in this case) -writer.Write(obj.Version); // Writes a 'short' (2 bytes) -writer.Write(obj.Date); // Writes a 'DateTime' (8 bytes) -writer.Write(obj.ArrayWith16Elements); // Writes 16 'uint's (4 bytes each) -writer.Write(obj.Bool32); // Writes a 'bool' (4 bytes in this case, since the reader was initiated with a default of BooleanSize.U32, but there is an overload to pass in one) -writer.Write(obj.NullTerminatedASCIIString, true, Encoding.ASCII); // Writes the chars in the 'string' as ASCII and appends a '\0' at the end -writer.Write(obj.UTF16String, 10, Encoding.Unicode); // Writes 10 UTF16-LE chars as a 'string'. If the string has more than 10 chars, it is truncated; if it has less, it is padded with '\0' +writer.WriteEnum(obj.Type); // Writes the enum type based on the amount of bytes of the enum's underlying type (short/2 in this case) +writer.WriteInt16(obj.Version); // Writes a 'short' (2 bytes) +writer.WriteDateTime(obj.Date); // Writes a 'DateTime' (8 bytes) +writer.WriteUInt32s(obj.ArrayWith16Elements); // Writes 16 'uint's (4 bytes each) +writer.WriteBoolean(obj.Bool32); // Writes a 'bool' (4 bytes in this case, since the reader's current bool state is BooleanSize.U32) + +writer.ASCII = true; // Set the reader's ASCII state to true +writer.WriteChars_NullTerminated(obj.NullTerminatedASCIIString); // Writes the chars in the 'string' as ASCII and appends a '\0' at the end + +writer.ASCII = false; // Set the reader's ASCII state to false (UTF16-LE) +writer.WriteChars_Count(obj.UTF16String, 10); // Writes 10 UTF16-LE chars as a 'string'. If the string has more than 10 chars, it is truncated; if it has less, it is padded with '\0' ``` ### Writing Automatically (With Reflection): ```cs var obj = new MyBasicObj { - Type = ShortSizedEnum.Val2, - Version = 511, - Date = new DateTime(1998, 12, 30), + Type = ShortSizedEnum.Val2, + Version = 511, + Date = new DateTime(1998, 12, 30), - DoNotReadOrWrite = ByteSizedEnum.Val1, + DoNotReadOrWrite = ByteSizedEnum.Val1, - ArrayWith16Elements = new uint[16] - { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 - }, + ArrayWith16Elements = new uint[16] + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 + }, - Bool32 = false, + Bool32 = false, - NullTerminatedASCIIString = "EndianBinaryIO", - UTF16String = "Kermalis" + NullTerminatedASCIIString = "EndianBinaryIO", + UTF16String = "Kermalis", }; var writer = new EndianBinaryWriter(stream, endianness: Endianness.LittleEndian); writer.Write(obj); // Write all properties in the 'MyBasicObj' in order, ignoring any with a 'BinaryIgnoreAttribute' - // Other objects that are properties in this object will also be written in the same way recursively +// Other objects that are properties in this object will also be written in the same way recursively ``` -### EndianBitConverter Example: +### EndianBinaryPrimitives Example: ```cs -byte[] bytes = new byte[] { 0xFF, 0x00, 0x00, 0x00 }; -uint value = (uint)EndianBitConverter.BytesToInt32(bytes, 0, Endianness.LittleEndian); // Will return (int)255 +byte[] bytes = new byte[] { 0xFF, 0x00, 0x00, 0x00, 0xBB, 0xEE, 0xEE, 0xFF }; +uint value = EndianBinaryPrimitives.ReadUInt32(bytes, Endianness.LittleEndian); // Will return 255 value = 128; -bytes = EndianBitConverter.Int32ToBytes((int)value, Endianness.LittleEndian); // Will return (byte[]){ 0x80, 0x00, 0x00, 0x00 } +EndianBinaryPrimitives.WriteUInt32(bytes.AsSpan(4, 4), value, Endianness.LittleEndian); // bytes is now { 0xFF, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00 } ``` ----- -## To Do: -* Documentation - ---- ## EndianBinaryIOTests Uses: * [xUnit.net](https://github.com/xunit/xunit) \ No newline at end of file diff --git a/Source/Attributes.cs b/Source/Attributes.cs index 8e3e98f..20d95d1 100644 --- a/Source/Attributes.cs +++ b/Source/Attributes.cs @@ -1,117 +1,112 @@ using System; -using System.Text; namespace Kermalis.EndianBinaryIO { - public interface IBinaryAttribute - { - T Value { get; } - } + public interface IBinaryAttribute + { + T Value { get; } + } - [AttributeUsage(AttributeTargets.Property)] - public sealed class BinaryIgnoreAttribute : Attribute, IBinaryAttribute - { - public bool Value { get; } + [AttributeUsage(AttributeTargets.Property)] + public sealed class BinaryIgnoreAttribute : Attribute, IBinaryAttribute + { + public bool Value { get; } - public BinaryIgnoreAttribute(bool ignore = true) - { - Value = ignore; - } - } - [AttributeUsage(AttributeTargets.Property)] - public sealed class BinaryBooleanSizeAttribute : Attribute, IBinaryAttribute - { - public BooleanSize Value { get; } + public BinaryIgnoreAttribute(bool ignore = true) + { + Value = ignore; + } + } + [AttributeUsage(AttributeTargets.Property)] + public sealed class BinaryBooleanSizeAttribute : Attribute, IBinaryAttribute + { + public BooleanSize Value { get; } - public BinaryBooleanSizeAttribute(BooleanSize booleanSize) - { - if (booleanSize >= BooleanSize.MAX) - { - throw new ArgumentOutOfRangeException($"{nameof(BinaryBooleanSizeAttribute)} cannot be created with a size of {booleanSize}."); - } - Value = booleanSize; - } - } - [AttributeUsage(AttributeTargets.Property)] - public sealed class BinaryEncodingAttribute : Attribute, IBinaryAttribute - { - public Encoding Value { get; } + public BinaryBooleanSizeAttribute(BooleanSize booleanSize) + { + if (booleanSize >= BooleanSize.MAX) + { + throw new ArgumentOutOfRangeException(nameof(booleanSize), $"{nameof(BinaryBooleanSizeAttribute)} cannot be created with a size of {booleanSize}."); + } + Value = booleanSize; + } + } + [AttributeUsage(AttributeTargets.Property)] + public sealed class BinaryASCIIAttribute : Attribute, IBinaryAttribute + { + public bool Value { get; } - public BinaryEncodingAttribute(string encodingName) - { - Value = Encoding.GetEncoding(encodingName); - } - public BinaryEncodingAttribute(int encodingCodepage) - { - Value = Encoding.GetEncoding(encodingCodepage); - } - } - [AttributeUsage(AttributeTargets.Property)] - public sealed class BinaryStringNullTerminatedAttribute : Attribute, IBinaryAttribute - { - public bool Value { get; } + public BinaryASCIIAttribute(bool ascii = true) + { + Value = ascii; + } + } + [AttributeUsage(AttributeTargets.Property)] + public sealed class BinaryStringNullTerminatedAttribute : Attribute, IBinaryAttribute + { + public bool Value { get; } - public BinaryStringNullTerminatedAttribute(bool nullTerminated = true) - { - Value = nullTerminated; - } - } - [AttributeUsage(AttributeTargets.Property)] - public sealed class BinaryArrayFixedLengthAttribute : Attribute, IBinaryAttribute - { - public int Value { get; } + public BinaryStringNullTerminatedAttribute(bool nullTerminated = true) + { + Value = nullTerminated; + } + } + [AttributeUsage(AttributeTargets.Property)] + public sealed class BinaryArrayFixedLengthAttribute : Attribute, IBinaryAttribute + { + public int Value { get; } - public BinaryArrayFixedLengthAttribute(int length) - { - if (length < 0) - { - throw new ArgumentOutOfRangeException($"{nameof(BinaryArrayFixedLengthAttribute)} cannot be created with a length of {length}. Length must be 0 or greater."); - } - Value = length; - } - } - [AttributeUsage(AttributeTargets.Property)] - public sealed class BinaryArrayVariableLengthAttribute : Attribute, IBinaryAttribute - { - public string Value { get; } + public BinaryArrayFixedLengthAttribute(int length) + { + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length), $"{nameof(BinaryArrayFixedLengthAttribute)} cannot be created with a length of {length}. Length must be 0 or greater."); + } + Value = length; + } + } + [AttributeUsage(AttributeTargets.Property)] + public sealed class BinaryArrayVariableLengthAttribute : Attribute, IBinaryAttribute + { + public string Value { get; } - public BinaryArrayVariableLengthAttribute(string anchor) - { - Value = anchor; - } - } - [AttributeUsage(AttributeTargets.Property)] - public sealed class BinaryStringFixedLengthAttribute : Attribute, IBinaryAttribute - { - public int Value { get; } + public BinaryArrayVariableLengthAttribute(string anchor) + { + Value = anchor; + } + } + [AttributeUsage(AttributeTargets.Property)] + public sealed class BinaryStringFixedLengthAttribute : Attribute, IBinaryAttribute + { + public int Value { get; } - public BinaryStringFixedLengthAttribute(int length) - { - if (length <= 0) - { - throw new ArgumentOutOfRangeException($"{nameof(BinaryStringFixedLengthAttribute)} cannot be created with a length of {length}. Length must be 0 or greater."); - } - Value = length; - } - } - [AttributeUsage(AttributeTargets.Property)] - public sealed class BinaryStringVariableLengthAttribute : Attribute, IBinaryAttribute - { - public string Value { get; } + public BinaryStringFixedLengthAttribute(int length) + { + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length), $"{nameof(BinaryStringFixedLengthAttribute)} cannot be created with a length of {length}. Length must be 0 or greater."); + } + Value = length; + } + } + [AttributeUsage(AttributeTargets.Property)] + public sealed class BinaryStringVariableLengthAttribute : Attribute, IBinaryAttribute + { + public string Value { get; } - public BinaryStringVariableLengthAttribute(string anchor) - { - Value = anchor; - } - } - [AttributeUsage(AttributeTargets.Property)] - public sealed class BinaryStringTrimNullTerminatorsAttribute : Attribute, IBinaryAttribute - { - public bool Value { get; } + public BinaryStringVariableLengthAttribute(string anchor) + { + Value = anchor; + } + } + [AttributeUsage(AttributeTargets.Property)] + public sealed class BinaryStringTrimNullTerminatorsAttribute : Attribute, IBinaryAttribute + { + public bool Value { get; } - public BinaryStringTrimNullTerminatorsAttribute(bool trim = true) - { - Value = trim; - } - } + public BinaryStringTrimNullTerminatorsAttribute(bool trim = true) + { + Value = trim; + } + } } diff --git a/Source/EndianBinaryIO.csproj b/Source/EndianBinaryIO.csproj index 111e088..0e64818 100644 --- a/Source/EndianBinaryIO.csproj +++ b/Source/EndianBinaryIO.csproj @@ -1,39 +1,70 @@  - - netstandard2.0 - Library - Kermalis.EndianBinaryIO - Kermalis - Kermalis - EndianBinaryIO - EndianBinaryIO - EndianBinaryIO - 1.1.2.0 - https://github.com/Kermalis/EndianBinaryIO - git - - true - - A .NET Standard library that can read and write primitives, enums, arrays, and strings to streams and byte arrays using specified endianness, string encoding, and boolean sizes. -Objects can also be read from/written to streams via reflection and attributes. -Project URL ― https://github.com/Kermalis/EndianBinaryIO - https://github.com/Kermalis/EndianBinaryIO - en-001 - Serialization;Reflection;Endianness;LittleEndian;BigEndian;EndianBinaryIO - + + net6.0 + latest + Library + Kermalis.EndianBinaryIO + enable - - Auto - none - false - + Kermalis + Kermalis + EndianBinaryIO + EndianBinaryIO + EndianBinaryIO + EndianBinaryIO + 2.0.0 + https://github.com/Kermalis/EndianBinaryIO + git + + This .NET library provides a simple API to read/write bytes from/to streams and spans using user-specified endianness. + By default, supported types include primitives, enums, arrays, strings, and some common .NET struct types. + Objects can also be read/written from/to streams via reflection and attributes. + The developer can use the API even if their target behavior or data is not directly supported by using the IBinarySerializable interface, inheritting from the reader/writer, or using the manual Span methods without streams. + Performance is the focus when not using reflection; no allocations unless absolutely necessary! - - false - DEBUG;TRACE - full - true - + Project URL and Samples ― https://github.com/Kermalis/EndianBinaryIO + + https://github.com/Kermalis/EndianBinaryIO + en-001 + Serialization;Reflection;Endianness;LittleEndian;BigEndian;EndianBinaryIO + README.md + LICENSE.md + + * Rewritten with Span<T> and performance in mind. No allocations unless absolutely necessary + * The compiler will now inline certain methods. For example, ReadEnum<TEnum>() will only include code that will be executed for the given enum size. So passing a TEnum that is the size of a byte will condense down to just a ReadByte() call with no size/type checks + * Implemented reading and writing for Half, DateOnly, TimeOnly, Vector2, Vector3, Vector4, Quaternion, and Matrix4x4 + * Removed bloated overloads (with array offset/count, alternate Encoding/BooleanSize, null termination, etc.). The reader/writer now respects its state (such as whether to use ASCII, and which BooleanSize to use) which you can change at any time + * decimal int order now matches with .net APIs + * Removed EndianBitConverter in favor of EndianBinaryPrimitives, which has similar API while using modern programming like Span<T> + * API uses nullable notations + * You can now ReadObject() and WriteObject() with primitives and other supported types like DateTime, Vector3, etc. + * Removed Encoding usage. The whole thing was very complicated before, and it barely functioned. Now you have ASCII and .net (UTF16-LE) support by default, and can add your own requirements either by extension methods or inheriting the reader/writer + + + + + Auto + none + false + + + + false + DEBUG;TRACE + full + true + + + + + True + \ + + + True + \ + + diff --git a/Source/EndianBinaryPrimitives.cs b/Source/EndianBinaryPrimitives.cs new file mode 100644 index 0000000..08c2f6d --- /dev/null +++ b/Source/EndianBinaryPrimitives.cs @@ -0,0 +1,678 @@ +using System; +using System.Buffers.Binary; +using System.Numerics; + +namespace Kermalis.EndianBinaryIO +{ + public static class EndianBinaryPrimitives + { + public static readonly Endianness SystemEndianness = BitConverter.IsLittleEndian ? Endianness.LittleEndian : Endianness.BigEndian; + + public static int GetBytesForBooleanSize(BooleanSize boolSize) + { + switch (boolSize) + { + case BooleanSize.U8: return 1; + case BooleanSize.U16: return 2; + case BooleanSize.U32: return 4; + default: throw new ArgumentOutOfRangeException(nameof(boolSize), boolSize, null); + } + } + + #region Read + + public static short ReadInt16(ReadOnlySpan src, Endianness endianness) + { + return endianness == Endianness.LittleEndian + ? BinaryPrimitives.ReadInt16LittleEndian(src) + : BinaryPrimitives.ReadInt16BigEndian(src); + } + public static void ReadInt16s(ReadOnlySpan src, Span dest, Endianness endianness) + { + for (int i = 0; i < dest.Length; i++) + { + dest[i] = ReadInt16(src.Slice(i * 2, 2), endianness); + } + } + + public static ushort ReadUInt16(ReadOnlySpan src, Endianness endianness) + { + return endianness == Endianness.LittleEndian + ? BinaryPrimitives.ReadUInt16LittleEndian(src) + : BinaryPrimitives.ReadUInt16BigEndian(src); + } + public static void ReadUInt16s(ReadOnlySpan src, Span dest, Endianness endianness) + { + for (int i = 0; i < dest.Length; i++) + { + dest[i] = ReadUInt16(src.Slice(i * 2, 2), endianness); + } + } + + public static int ReadInt32(ReadOnlySpan src, Endianness endianness) + { + return endianness == Endianness.LittleEndian + ? BinaryPrimitives.ReadInt32LittleEndian(src) + : BinaryPrimitives.ReadInt32BigEndian(src); + } + public static void ReadInt32s(ReadOnlySpan src, Span dest, Endianness endianness) + { + for (int i = 0; i < dest.Length; i++) + { + dest[i] = ReadInt32(src.Slice(i * 4, 4), endianness); + } + } + + public static uint ReadUInt32(ReadOnlySpan src, Endianness endianness) + { + return endianness == Endianness.LittleEndian + ? BinaryPrimitives.ReadUInt32LittleEndian(src) + : BinaryPrimitives.ReadUInt32BigEndian(src); + } + public static void ReadUInt32s(ReadOnlySpan src, Span dest, Endianness endianness) + { + for (int i = 0; i < dest.Length; i++) + { + dest[i] = ReadUInt32(src.Slice(i * 4, 4), endianness); + } + } + + public static long ReadInt64(ReadOnlySpan src, Endianness endianness) + { + return endianness == Endianness.LittleEndian + ? BinaryPrimitives.ReadInt64LittleEndian(src) + : BinaryPrimitives.ReadInt64BigEndian(src); + } + public static void ReadInt64s(ReadOnlySpan src, Span dest, Endianness endianness) + { + for (int i = 0; i < dest.Length; i++) + { + dest[i] = ReadInt64(src.Slice(i * 8, 8), endianness); + } + } + + public static ulong ReadUInt64(ReadOnlySpan src, Endianness endianness) + { + return endianness == Endianness.LittleEndian + ? BinaryPrimitives.ReadUInt64LittleEndian(src) + : BinaryPrimitives.ReadUInt64BigEndian(src); + } + public static void ReadUInt64s(ReadOnlySpan src, Span dest, Endianness endianness) + { + for (int i = 0; i < dest.Length; i++) + { + dest[i] = ReadUInt64(src.Slice(i * 8, 8), endianness); + } + } + + public static Half ReadHalf(ReadOnlySpan src, Endianness endianness) + { + return endianness == Endianness.LittleEndian + ? BinaryPrimitives.ReadHalfLittleEndian(src) + : BinaryPrimitives.ReadHalfBigEndian(src); + } + public static void ReadHalves(ReadOnlySpan src, Span dest, Endianness endianness) + { + for (int i = 0; i < dest.Length; i++) + { + dest[i] = ReadHalf(src.Slice(i * 2, 2), endianness); + } + } + + public static float ReadSingle(ReadOnlySpan src, Endianness endianness) + { + return endianness == Endianness.LittleEndian + ? BinaryPrimitives.ReadSingleLittleEndian(src) + : BinaryPrimitives.ReadSingleBigEndian(src); + } + public static void ReadSingles(ReadOnlySpan src, Span dest, Endianness endianness) + { + for (int i = 0; i < dest.Length; i++) + { + dest[i] = ReadSingle(src.Slice(i * 4, 4), endianness); + } + } + + public static double ReadDouble(ReadOnlySpan src, Endianness endianness) + { + return endianness == Endianness.LittleEndian + ? BinaryPrimitives.ReadDoubleLittleEndian(src) + : BinaryPrimitives.ReadDoubleBigEndian(src); + } + public static void ReadDoubles(ReadOnlySpan src, Span dest, Endianness endianness) + { + for (int i = 0; i < dest.Length; i++) + { + dest[i] = ReadDouble(src.Slice(i * 8, 8), endianness); + } + } + + public static decimal ReadDecimal(ReadOnlySpan src, Endianness endianness) + { + Span buffer = stackalloc int[4]; + ReadInt32s(src, buffer, endianness); + return new decimal(buffer); + } + public static void ReadDecimals(ReadOnlySpan src, Span dest, Endianness endianness) + { + for (int i = 0; i < dest.Length; i++) + { + dest[i] = ReadDecimal(src.Slice(i * 16, 16), endianness); + } + } + + public static bool ReadBoolean(ReadOnlySpan src, Endianness endianness, BooleanSize boolSize) + { + return ReadBoolean(src, endianness, GetBytesForBooleanSize(boolSize)); + } + public static void ReadBooleans(ReadOnlySpan src, Span dest, Endianness endianness, BooleanSize boolSize) + { + ReadBooleans(src, dest, endianness, GetBytesForBooleanSize(boolSize)); + } + public static bool ReadBoolean(ReadOnlySpan src, Endianness endianness, int boolSize) + { + switch (boolSize) + { + case 1: + { + return src[0] != 0; + } + case 2: + { + return ReadUInt16(src, endianness) != 0; + } + case 4: + { + return ReadUInt32(src, endianness) != 0; + } + default: throw new ArgumentOutOfRangeException(nameof(boolSize), boolSize, null); + } + } + public static void ReadBooleans(ReadOnlySpan src, Span dest, Endianness endianness, int boolSize) + { + for (int i = 0; i < dest.Length; i++) + { + dest[i] = ReadBoolean(src.Slice(i * boolSize, boolSize), endianness, boolSize); + } + } + + public static DateTime ReadDateTime(ReadOnlySpan src, Endianness endianness) + { + return DateTime.FromBinary(ReadInt64(src, endianness)); + } + public static void ReadDateTimes(ReadOnlySpan src, Span dest, Endianness endianness) + { + for (int i = 0; i < dest.Length; i++) + { + dest[i] = ReadDateTime(src.Slice(i * 8, 8), endianness); + } + } + + public static DateOnly ReadDateOnly(ReadOnlySpan src, Endianness endianness) + { + return DateOnly.FromDayNumber(ReadInt32(src, endianness)); + } + public static void ReadDateOnlys(ReadOnlySpan src, Span dest, Endianness endianness) + { + for (int i = 0; i < dest.Length; i++) + { + dest[i] = ReadDateOnly(src.Slice(i * 4, 4), endianness); + } + } + + public static TimeOnly ReadTimeOnly(ReadOnlySpan src, Endianness endianness) + { + return new TimeOnly(ReadInt64(src, endianness)); + } + public static void ReadTimeOnlys(ReadOnlySpan src, Span dest, Endianness endianness) + { + for (int i = 0; i < dest.Length; i++) + { + dest[i] = ReadTimeOnly(src.Slice(i * 8, 8), endianness); + } + } + + public static Vector2 ReadVector2(ReadOnlySpan src, Endianness endianness) + { + Vector2 v; + v.X = ReadSingle(src.Slice(0, 4), endianness); + v.Y = ReadSingle(src.Slice(4, 4), endianness); + return v; + } + public static void ReadVector2s(ReadOnlySpan src, Span dest, Endianness endianness) + { + for (int i = 0; i < dest.Length; i++) + { + dest[i] = ReadVector2(src.Slice(i * 8, 8), endianness); + } + } + + public static Vector3 ReadVector3(ReadOnlySpan src, Endianness endianness) + { + Vector3 v; + v.X = ReadSingle(src.Slice(0, 4), endianness); + v.Y = ReadSingle(src.Slice(4, 4), endianness); + v.Z = ReadSingle(src.Slice(8, 4), endianness); + return v; + } + public static void ReadVector3s(ReadOnlySpan src, Span dest, Endianness endianness) + { + for (int i = 0; i < dest.Length; i++) + { + dest[i] = ReadVector3(src.Slice(i * 12, 12), endianness); + } + } + + public static Vector4 ReadVector4(ReadOnlySpan src, Endianness endianness) + { + Vector4 v; + v.W = ReadSingle(src.Slice(0, 4), endianness); + v.X = ReadSingle(src.Slice(4, 4), endianness); + v.Y = ReadSingle(src.Slice(8, 4), endianness); + v.Z = ReadSingle(src.Slice(12, 4), endianness); + return v; + } + public static void ReadVector4s(ReadOnlySpan src, Span dest, Endianness endianness) + { + for (int i = 0; i < dest.Length; i++) + { + dest[i] = ReadVector4(src.Slice(i * 16, 16), endianness); + } + } + + public static Quaternion ReadQuaternion(ReadOnlySpan src, Endianness endianness) + { + Quaternion v; + v.W = ReadSingle(src.Slice(0, 4), endianness); + v.X = ReadSingle(src.Slice(4, 4), endianness); + v.Y = ReadSingle(src.Slice(8, 4), endianness); + v.Z = ReadSingle(src.Slice(12, 4), endianness); + return v; + } + public static void ReadQuaternions(ReadOnlySpan src, Span dest, Endianness endianness) + { + for (int i = 0; i < dest.Length; i++) + { + dest[i] = ReadQuaternion(src.Slice(i * 16, 16), endianness); + } + } + + public static Matrix4x4 ReadMatrix4x4(ReadOnlySpan src, Endianness endianness) + { + Matrix4x4 v; + v.M11 = ReadSingle(src.Slice(0, 4), endianness); + v.M12 = ReadSingle(src.Slice(4, 4), endianness); + v.M13 = ReadSingle(src.Slice(8, 4), endianness); + v.M14 = ReadSingle(src.Slice(12, 4), endianness); + v.M21 = ReadSingle(src.Slice(16, 4), endianness); + v.M22 = ReadSingle(src.Slice(20, 4), endianness); + v.M23 = ReadSingle(src.Slice(24, 4), endianness); + v.M24 = ReadSingle(src.Slice(28, 4), endianness); + v.M31 = ReadSingle(src.Slice(32, 4), endianness); + v.M32 = ReadSingle(src.Slice(36, 4), endianness); + v.M33 = ReadSingle(src.Slice(40, 4), endianness); + v.M34 = ReadSingle(src.Slice(44, 4), endianness); + v.M41 = ReadSingle(src.Slice(48, 4), endianness); + v.M42 = ReadSingle(src.Slice(52, 4), endianness); + v.M43 = ReadSingle(src.Slice(56, 4), endianness); + v.M44 = ReadSingle(src.Slice(60, 4), endianness); + return v; + } + public static void ReadMatrix4x4s(ReadOnlySpan src, Span dest, Endianness endianness) + { + for (int i = 0; i < dest.Length; i++) + { + dest[i] = ReadMatrix4x4(src.Slice(i * 64, 64), endianness); + } + } + + #endregion + + #region Write + + public static void WriteInt16(Span dest, short value, Endianness endianness) + { + if (endianness == Endianness.LittleEndian) + { + BinaryPrimitives.WriteInt16LittleEndian(dest, value); + } + else + { + BinaryPrimitives.WriteInt16BigEndian(dest, value); + } + } + public static void WriteInt16s(Span dest, ReadOnlySpan src, Endianness endianness) + { + for (int i = 0; i < src.Length; i++) + { + WriteInt16(dest.Slice(i * 2, 2), src[i], endianness); + } + } + + public static void WriteUInt16(Span dest, ushort value, Endianness endianness) + { + if (endianness == Endianness.LittleEndian) + { + BinaryPrimitives.WriteUInt16LittleEndian(dest, value); + } + else + { + BinaryPrimitives.WriteUInt16BigEndian(dest, value); + } + } + public static void WriteUInt16s(Span dest, ReadOnlySpan src, Endianness endianness) + { + for (int i = 0; i < src.Length; i++) + { + WriteUInt16(dest.Slice(i * 2, 2), src[i], endianness); + } + } + + public static void WriteInt32(Span dest, int value, Endianness endianness) + { + if (endianness == Endianness.LittleEndian) + { + BinaryPrimitives.WriteInt32LittleEndian(dest, value); + } + else + { + BinaryPrimitives.WriteInt32BigEndian(dest, value); + } + } + public static void WriteInt32s(Span dest, ReadOnlySpan src, Endianness endianness) + { + for (int i = 0; i < src.Length; i++) + { + WriteInt32(dest.Slice(i * 4, 4), src[i], endianness); + } + } + + public static void WriteUInt32(Span dest, uint value, Endianness endianness) + { + if (endianness == Endianness.LittleEndian) + { + BinaryPrimitives.WriteUInt32LittleEndian(dest, value); + } + else + { + BinaryPrimitives.WriteUInt32BigEndian(dest, value); + } + } + public static void WriteUInt32s(Span dest, ReadOnlySpan src, Endianness endianness) + { + for (int i = 0; i < src.Length; i++) + { + WriteUInt32(dest.Slice(i * 4, 4), src[i], endianness); + } + } + + public static void WriteInt64(Span dest, long value, Endianness endianness) + { + if (endianness == Endianness.LittleEndian) + { + BinaryPrimitives.WriteInt64LittleEndian(dest, value); + } + else + { + BinaryPrimitives.WriteInt64BigEndian(dest, value); + } + } + public static void WriteInt64s(Span dest, ReadOnlySpan src, Endianness endianness) + { + for (int i = 0; i < src.Length; i++) + { + WriteInt64(dest.Slice(i * 8, 8), src[i], endianness); + } + } + + public static void WriteUInt64(Span dest, ulong value, Endianness endianness) + { + if (endianness == Endianness.LittleEndian) + { + BinaryPrimitives.WriteUInt64LittleEndian(dest, value); + } + else + { + BinaryPrimitives.WriteUInt64BigEndian(dest, value); + } + } + public static void WriteUInt64s(Span dest, ReadOnlySpan src, Endianness endianness) + { + for (int i = 0; i < src.Length; i++) + { + WriteUInt64(dest.Slice(i * 8, 8), src[i], endianness); + } + } + + public static void WriteHalf(Span dest, Half value, Endianness endianness) + { + if (endianness == Endianness.LittleEndian) + { + BinaryPrimitives.WriteHalfLittleEndian(dest, value); + } + else + { + BinaryPrimitives.WriteHalfBigEndian(dest, value); + } + } + public static void WriteHalves(Span dest, ReadOnlySpan src, Endianness endianness) + { + for (int i = 0; i < src.Length; i++) + { + WriteHalf(dest.Slice(i * 2, 2), src[i], endianness); + } + } + + public static void WriteSingle(Span dest, float value, Endianness endianness) + { + if (endianness == Endianness.LittleEndian) + { + BinaryPrimitives.WriteSingleLittleEndian(dest, value); + } + else + { + BinaryPrimitives.WriteSingleBigEndian(dest, value); + } + } + public static void WriteSingles(Span dest, ReadOnlySpan src, Endianness endianness) + { + for (int i = 0; i < src.Length; i++) + { + WriteSingle(dest.Slice(i * 4, 4), src[i], endianness); + } + } + + public static void WriteDouble(Span dest, double value, Endianness endianness) + { + if (endianness == Endianness.LittleEndian) + { + BinaryPrimitives.WriteDoubleLittleEndian(dest, value); + } + else + { + BinaryPrimitives.WriteDoubleBigEndian(dest, value); + } + } + public static void WriteDoubles(Span dest, ReadOnlySpan src, Endianness endianness) + { + for (int i = 0; i < src.Length; i++) + { + WriteDouble(dest.Slice(i * 8, 8), src[i], endianness); + } + } + + public static void WriteDecimal(Span dest, in decimal value, Endianness endianness) + { + Span buffer = stackalloc int[4]; + decimal.GetBits(value, buffer); + WriteInt32s(dest, buffer, endianness); + } + public static void WriteDecimals(Span dest, ReadOnlySpan src, Endianness endianness) + { + for (int i = 0; i < src.Length; i++) + { + WriteDecimal(dest.Slice(i * 16, 16), src[i], endianness); + } + } + + public static void WriteBoolean(Span dest, bool value, Endianness endianness, BooleanSize boolSize) + { + WriteBoolean(dest, value, endianness, GetBytesForBooleanSize(boolSize)); + } + public static void WriteBooleans(Span dest, ReadOnlySpan src, Endianness endianness, BooleanSize boolSize) + { + WriteBooleans(dest, src, endianness, GetBytesForBooleanSize(boolSize)); + } + public static void WriteBoolean(Span dest, bool value, Endianness endianness, int boolSize) + { + switch (boolSize) + { + case 1: + { + dest[0] = (byte)(value ? 1 : 0); + break; + } + case 2: + { + WriteUInt16(dest.Slice(0, 2), (ushort)(value ? 1 : 0), endianness); + break; + } + case 4: + { + WriteUInt32(dest.Slice(0, 4), value ? 1u : 0, endianness); + break; + } + default: throw new ArgumentOutOfRangeException(nameof(boolSize), boolSize, null); + } + } + public static void WriteBooleans(Span dest, ReadOnlySpan src, Endianness endianness, int boolSize) + { + for (int i = 0; i < src.Length; i++) + { + WriteBoolean(dest.Slice(i * boolSize, boolSize), src[i], endianness, boolSize); + } + } + + public static void WriteDateTime(Span dest, DateTime value, Endianness endianness) + { + WriteInt64(dest, value.ToBinary(), endianness); + } + public static void WriteDateTimes(Span dest, ReadOnlySpan src, Endianness endianness) + { + for (int i = 0; i < src.Length; i++) + { + WriteDateTime(dest.Slice(i * 8, 8), src[i], endianness); + } + } + + public static void WriteDateOnly(Span dest, DateOnly value, Endianness endianness) + { + WriteInt32(dest, value.DayNumber, endianness); + } + public static void WriteDateOnlys(Span dest, ReadOnlySpan src, Endianness endianness) + { + for (int i = 0; i < src.Length; i++) + { + WriteDateOnly(dest.Slice(i * 4, 4), src[i], endianness); + } + } + + public static void WriteTimeOnly(Span dest, TimeOnly value, Endianness endianness) + { + WriteInt64(dest, value.Ticks, endianness); + } + public static void WriteTimeOnlys(Span dest, ReadOnlySpan src, Endianness endianness) + { + for (int i = 0; i < src.Length; i++) + { + WriteTimeOnly(dest.Slice(i * 8, 8), src[i], endianness); + } + } + + public static void WriteVector2(Span dest, Vector2 value, Endianness endianness) + { + WriteSingle(dest.Slice(0, 4), value.X, endianness); + WriteSingle(dest.Slice(4, 4), value.Y, endianness); + } + public static void WriteVector2s(Span dest, ReadOnlySpan src, Endianness endianness) + { + for (int i = 0; i < src.Length; i++) + { + WriteVector2(dest.Slice(i * 8, 8), src[i], endianness); + } + } + + public static void WriteVector3(Span dest, Vector3 value, Endianness endianness) + { + WriteSingle(dest.Slice(0, 4), value.X, endianness); + WriteSingle(dest.Slice(4, 4), value.Y, endianness); + WriteSingle(dest.Slice(8, 4), value.Z, endianness); + } + public static void WriteVector3s(Span dest, ReadOnlySpan src, Endianness endianness) + { + for (int i = 0; i < src.Length; i++) + { + WriteVector3(dest.Slice(i * 12, 12), src[i], endianness); + } + } + + public static void WriteVector4(Span dest, in Vector4 value, Endianness endianness) + { + WriteSingle(dest.Slice(0, 4), value.W, endianness); + WriteSingle(dest.Slice(4, 4), value.X, endianness); + WriteSingle(dest.Slice(8, 4), value.Y, endianness); + WriteSingle(dest.Slice(12, 4), value.Z, endianness); + } + public static void WriteVector4s(Span dest, ReadOnlySpan src, Endianness endianness) + { + for (int i = 0; i < src.Length; i++) + { + WriteVector4(dest.Slice(i * 16, 16), src[i], endianness); + } + } + + public static void WriteQuaternion(Span dest, in Quaternion value, Endianness endianness) + { + WriteSingle(dest.Slice(0, 4), value.W, endianness); + WriteSingle(dest.Slice(4, 4), value.X, endianness); + WriteSingle(dest.Slice(8, 4), value.Y, endianness); + WriteSingle(dest.Slice(12, 4), value.Z, endianness); + } + public static void WriteQuaternions(Span dest, ReadOnlySpan src, Endianness endianness) + { + for (int i = 0; i < src.Length; i++) + { + WriteQuaternion(dest.Slice(i * 16, 16), src[i], endianness); + } + } + + public static void WriteMatrix4x4(Span dest, in Matrix4x4 value, Endianness endianness) + { + WriteSingle(dest.Slice(0, 4), value.M11, endianness); + WriteSingle(dest.Slice(4, 4), value.M12, endianness); + WriteSingle(dest.Slice(8, 4), value.M13, endianness); + WriteSingle(dest.Slice(12, 4), value.M14, endianness); + WriteSingle(dest.Slice(16, 4), value.M21, endianness); + WriteSingle(dest.Slice(20, 4), value.M22, endianness); + WriteSingle(dest.Slice(24, 4), value.M23, endianness); + WriteSingle(dest.Slice(28, 4), value.M24, endianness); + WriteSingle(dest.Slice(32, 4), value.M31, endianness); + WriteSingle(dest.Slice(36, 4), value.M32, endianness); + WriteSingle(dest.Slice(40, 4), value.M33, endianness); + WriteSingle(dest.Slice(44, 4), value.M34, endianness); + WriteSingle(dest.Slice(48, 4), value.M41, endianness); + WriteSingle(dest.Slice(52, 4), value.M42, endianness); + WriteSingle(dest.Slice(56, 4), value.M43, endianness); + WriteSingle(dest.Slice(60, 4), value.M44, endianness); + } + public static void WriteMatrix4x4s(Span dest, ReadOnlySpan src, Endianness endianness) + { + for (int i = 0; i < src.Length; i++) + { + WriteMatrix4x4(dest.Slice(i * 64, 64), src[i], endianness); + } + } + + #endregion + } +} diff --git a/Source/EndianBinaryReader.cs b/Source/EndianBinaryReader.cs index 1acb3fa..7ef592e 100644 --- a/Source/EndianBinaryReader.cs +++ b/Source/EndianBinaryReader.cs @@ -1,893 +1,466 @@ using System; using System.IO; -using System.Reflection; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Text; namespace Kermalis.EndianBinaryIO { - public class EndianBinaryReader - { - public Stream BaseStream { get; } - private Endianness _endianness; - public Endianness Endianness - { - get => _endianness; - set - { - if (value >= Endianness.MAX) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - _endianness = value; - } - } - private BooleanSize _booleanSize; - public BooleanSize BooleanSize - { - get => _booleanSize; - set - { - if (value >= BooleanSize.MAX) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - _booleanSize = value; - } - } - public Encoding Encoding { get; set; } + public partial class EndianBinaryReader + { + protected const int BUF_LEN = 64; // Must be a multiple of 64 - private byte[] _buffer; + public Stream Stream { get; } + private Endianness _endianness; + public Endianness Endianness + { + get => _endianness; + set + { + if (value >= Endianness.MAX) + { + throw new ArgumentOutOfRangeException(nameof(value), value, null); + } + _endianness = value; + } + } + private BooleanSize _booleanSize; + public BooleanSize BooleanSize + { + get => _booleanSize; + set + { + if (value >= BooleanSize.MAX) + { + throw new ArgumentOutOfRangeException(nameof(value), value, null); + } + _booleanSize = value; + } + } + public bool ASCII { get; set; } - public EndianBinaryReader(Stream baseStream, Endianness endianness = Endianness.LittleEndian, BooleanSize booleanSize = BooleanSize.U8) - { - if (baseStream is null) - { - throw new ArgumentNullException(nameof(baseStream)); - } - if (!baseStream.CanRead) - { - throw new ArgumentException(nameof(baseStream)); - } - BaseStream = baseStream; - Endianness = endianness; - BooleanSize = booleanSize; - Encoding = Encoding.Default; - } - public EndianBinaryReader(Stream baseStream, Encoding encoding, Endianness endianness = Endianness.LittleEndian, BooleanSize booleanSize = BooleanSize.U8) - { - if (baseStream is null) - { - throw new ArgumentNullException(nameof(baseStream)); - } - if (!baseStream.CanRead) - { - throw new ArgumentException(nameof(baseStream)); - } - BaseStream = baseStream; - Endianness = endianness; - BooleanSize = booleanSize; - Encoding = encoding; - } + protected readonly byte[] _buffer; - private void ReadBytesIntoBuffer(int byteCount) - { - if (_buffer is null || _buffer.Length < byteCount) - { - _buffer = new byte[byteCount]; - } - if (BaseStream.Read(_buffer, 0, byteCount) != byteCount) - { - throw new EndOfStreamException(); - } - } - private char[] DecodeChars(Encoding encoding, int charCount) - { - Utils.ThrowIfCannotUseEncoding(encoding); - int maxBytes = encoding.GetMaxByteCount(charCount); - byte[] buffer = new byte[maxBytes]; - int amtRead = BaseStream.Read(buffer, 0, maxBytes); // Do not throw EndOfStreamException if there aren't enough bytes at the end of the stream - if (amtRead == 0) - { - throw new EndOfStreamException(); - } - // If the maxBytes would be 4, and the string only takes 2, we'd not have enough bytes, but if it's a proper string it doesn't matter - char[] chars = encoding.GetChars(buffer); - if (chars.Length < charCount) - { - throw new InvalidDataException(); // Too few chars means the decoding went wrong - } - // If we read too many chars, we need to shrink the array - // For example, if we want 1 char and the max bytes is 2, but we manage to read 2 1-byte chars, we'd want to shrink back to 1 char - Array.Resize(ref chars, charCount); - int actualBytes = encoding.GetByteCount(chars); - if (amtRead != actualBytes) - { - BaseStream.Position -= amtRead - actualBytes; // Set the stream back to compensate for the extra bytes we read - } - return chars; - } + public EndianBinaryReader(Stream stream, Endianness endianness = Endianness.LittleEndian, BooleanSize booleanSize = BooleanSize.U8, bool ascii = false) + { + if (!stream.CanRead) + { + throw new ArgumentOutOfRangeException(nameof(stream), "Stream is not open for reading."); + } - public byte PeekByte() - { - long pos = BaseStream.Position; - byte b = ReadByte(); - BaseStream.Position = pos; - return b; - } - public byte PeekByte(long offset) - { - BaseStream.Position = offset; - return PeekByte(); - } - public byte[] PeekBytes(int count) - { - long pos = BaseStream.Position; - byte[] b = ReadBytes(count); - BaseStream.Position = pos; - return b; - } - public byte[] PeekBytes(int count, long offset) - { - BaseStream.Position = offset; - return PeekBytes(count); - } - public char PeekChar() - { - long pos = BaseStream.Position; - char c = ReadChar(); - BaseStream.Position = pos; - return c; - } - public char PeekChar(long offset) - { - BaseStream.Position = offset; - return PeekChar(); - } - public char PeekChar(Encoding encoding) - { - long pos = BaseStream.Position; - char c = ReadChar(encoding); - BaseStream.Position = pos; - return c; - } - public char PeekChar(Encoding encoding, long offset) - { - BaseStream.Position = offset; - return PeekChar(encoding); - } + Stream = stream; + Endianness = endianness; + BooleanSize = booleanSize; + ASCII = ascii; + _buffer = new byte[BUF_LEN]; + } - public bool ReadBoolean() - { - return ReadBoolean(BooleanSize); - } - public bool ReadBoolean(long offset) - { - BaseStream.Position = offset; - return ReadBoolean(BooleanSize); - } - public bool ReadBoolean(BooleanSize booleanSize) - { - switch (booleanSize) - { - case BooleanSize.U8: - { - ReadBytesIntoBuffer(1); - return _buffer[0] != 0; - } - case BooleanSize.U16: - { - ReadBytesIntoBuffer(2); - return EndianBitConverter.BytesToInt16(_buffer, 0, Endianness) != 0; - } - case BooleanSize.U32: - { - ReadBytesIntoBuffer(4); - return EndianBitConverter.BytesToInt32(_buffer, 0, Endianness) != 0; - } - default: throw new ArgumentOutOfRangeException(nameof(booleanSize)); - } - } - public bool ReadBoolean(BooleanSize booleanSize, long offset) - { - BaseStream.Position = offset; - return ReadBoolean(booleanSize); - } - public bool[] ReadBooleans(int count) - { - return ReadBooleans(count, BooleanSize); - } - public bool[] ReadBooleans(int count, long offset) - { - BaseStream.Position = offset; - return ReadBooleans(count, BooleanSize); - } - public bool[] ReadBooleans(int count, BooleanSize size) - { - if (!Utils.ValidateReadArraySize(count, out bool[] array)) - { - array = new bool[count]; - for (int i = 0; i < count; i++) - { - array[i] = ReadBoolean(size); - } - } - return array; - } - public bool[] ReadBooleans(int count, BooleanSize size, long offset) - { - BaseStream.Position = offset; - return ReadBooleans(count, size); - } - public byte ReadByte() - { - ReadBytesIntoBuffer(1); - return _buffer[0]; - } - public byte ReadByte(long offset) - { - BaseStream.Position = offset; - return ReadByte(); - } - public byte[] ReadBytes(int count) - { - if (!Utils.ValidateReadArraySize(count, out byte[] array)) - { - ReadBytesIntoBuffer(count); - array = new byte[count]; - for (int i = 0; i < count; i++) - { - array[i] = _buffer[i]; - } - } - return array; - } - public byte[] ReadBytes(int count, long offset) - { - BaseStream.Position = offset; - return ReadBytes(count); - } - public sbyte ReadSByte() - { - ReadBytesIntoBuffer(1); - return (sbyte)_buffer[0]; - } - public sbyte ReadSByte(long offset) - { - BaseStream.Position = offset; - return ReadSByte(); - } - public sbyte[] ReadSBytes(int count) - { - if (!Utils.ValidateReadArraySize(count, out sbyte[] array)) - { - ReadBytesIntoBuffer(count); - array = new sbyte[count]; - for (int i = 0; i < count; i++) - { - array[i] = (sbyte)_buffer[i]; - } - } - return array; - } - public sbyte[] ReadSBytes(int count, long offset) - { - BaseStream.Position = offset; - return ReadSBytes(count); - } - public char ReadChar() - { - return ReadChar(Encoding); - } - public char ReadChar(long offset) - { - BaseStream.Position = offset; - return ReadChar(); - } - public char ReadChar(Encoding encoding) - { - return DecodeChars(encoding, 1)[0]; - } - public char ReadChar(Encoding encoding, long offset) - { - BaseStream.Position = offset; - return ReadChar(encoding); - } - public char[] ReadChars(int count, bool trimNullTerminators) - { - return ReadChars(count, trimNullTerminators, Encoding); - } - public char[] ReadChars(int count, bool trimNullTerminators, long offset) - { - BaseStream.Position = offset; - return ReadChars(count, trimNullTerminators); - } - public char[] ReadChars(int count, bool trimNullTerminators, Encoding encoding) - { - if (Utils.ValidateReadArraySize(count, out char[] array)) - { - return array; - } - array = DecodeChars(encoding, count); - if (trimNullTerminators) - { - int i = Array.IndexOf(array, '\0'); - if (i != -1) - { - Array.Resize(ref array, i); - } - } - return array; - } - public char[] ReadChars(int count, bool trimNullTerminators, Encoding encoding, long offset) - { - BaseStream.Position = offset; - return ReadChars(count, trimNullTerminators, encoding); - } - public string ReadStringNullTerminated() - { - return ReadStringNullTerminated(Encoding); - } - public string ReadStringNullTerminated(long offset) - { - BaseStream.Position = offset; - return ReadStringNullTerminated(); - } - public string ReadStringNullTerminated(Encoding encoding) - { - string text = string.Empty; - while (true) - { - char c = ReadChar(encoding); - if (c == '\0') - { - break; - } - text += c; - } - return text; - } - public string ReadStringNullTerminated(Encoding encoding, long offset) - { - BaseStream.Position = offset; - return ReadStringNullTerminated(encoding); - } - public string ReadString(int charCount, bool trimNullTerminators) - { - return ReadString(charCount, trimNullTerminators, Encoding); - } - public string ReadString(int charCount, bool trimNullTerminators, long offset) - { - BaseStream.Position = offset; - return ReadString(charCount, trimNullTerminators); - } - public string ReadString(int charCount, bool trimNullTerminators, Encoding encoding) - { - return new string(ReadChars(charCount, trimNullTerminators, encoding)); - } - public string ReadString(int charCount, bool trimNullTerminators, Encoding encoding, long offset) - { - BaseStream.Position = offset; - return ReadString(charCount, trimNullTerminators, encoding); - } - public string[] ReadStringsNullTerminated(int count) - { - return ReadStringsNullTerminated(count, Encoding); - } - public string[] ReadStringsNullTerminated(int count, long offset) - { - BaseStream.Position = offset; - return ReadStringsNullTerminated(count); - } - public string[] ReadStringsNullTerminated(int count, Encoding encoding) - { - if (!Utils.ValidateReadArraySize(count, out string[] array)) - { - array = new string[count]; - for (int i = 0; i < count; i++) - { - array[i] = ReadStringNullTerminated(encoding); - } - } - return array; - } - public string[] ReadStringsNullTerminated(int count, Encoding encoding, long offset) - { - BaseStream.Position = offset; - return ReadStringsNullTerminated(count, encoding); - } - public string[] ReadStrings(int count, int charCount, bool trimNullTerminators) - { - return ReadStrings(count, charCount, trimNullTerminators, Encoding); - } - public string[] ReadStrings(int count, int charCount, bool trimNullTerminators, long offset) - { - BaseStream.Position = offset; - return ReadStrings(count, charCount, trimNullTerminators); - } - public string[] ReadStrings(int count, int charCount, bool trimNullTerminators, Encoding encoding) - { - if (!Utils.ValidateReadArraySize(count, out string[] array)) - { - array = new string[count]; - for (int i = 0; i < count; i++) - { - array[i] = ReadString(charCount, trimNullTerminators, encoding); - } - } - return array; - } - public string[] ReadStrings(int count, int charCount, bool trimNullTerminators, Encoding encoding, long offset) - { - BaseStream.Position = offset; - return ReadStrings(count, charCount, trimNullTerminators, encoding); - } - public short ReadInt16() - { - ReadBytesIntoBuffer(2); - return EndianBitConverter.BytesToInt16(_buffer, 0, Endianness); - } - public short ReadInt16(long offset) - { - BaseStream.Position = offset; - return ReadInt16(); - } - public short[] ReadInt16s(int count) - { - ReadBytesIntoBuffer(count * 2); - return EndianBitConverter.BytesToInt16s(_buffer, 0, count, Endianness); - } - public short[] ReadInt16s(int count, long offset) - { - BaseStream.Position = offset; - return ReadInt16s(count); - } - public ushort ReadUInt16() - { - ReadBytesIntoBuffer(2); - return (ushort)EndianBitConverter.BytesToInt16(_buffer, 0, Endianness); - } - public ushort ReadUInt16(long offset) - { - BaseStream.Position = offset; - return ReadUInt16(); - } - public ushort[] ReadUInt16s(int count) - { - ReadBytesIntoBuffer(count * 2); - return EndianBitConverter.BytesToUInt16s(_buffer, 0, count, Endianness); - } - public ushort[] ReadUInt16s(int count, long offset) - { - BaseStream.Position = offset; - return ReadUInt16s(count); - } - public int ReadInt32() - { - ReadBytesIntoBuffer(4); - return EndianBitConverter.BytesToInt32(_buffer, 0, Endianness); - } - public int ReadInt32(long offset) - { - BaseStream.Position = offset; - return ReadInt32(); - } - public int[] ReadInt32s(int count) - { - ReadBytesIntoBuffer(count * 4); - return EndianBitConverter.BytesToInt32s(_buffer, 0, count, Endianness); - } - public int[] ReadInt32s(int count, long offset) - { - BaseStream.Position = offset; - return ReadInt32s(count); - } - public uint ReadUInt32() - { - ReadBytesIntoBuffer(4); - return (uint)EndianBitConverter.BytesToInt32(_buffer, 0, Endianness); - } - public uint ReadUInt32(long offset) - { - BaseStream.Position = offset; - return ReadUInt32(); - } - public uint[] ReadUInt32s(int count) - { - ReadBytesIntoBuffer(count * 4); - return EndianBitConverter.BytesToUInt32s(_buffer, 0, count, Endianness); - } - public uint[] ReadUInt32s(int count, long offset) - { - BaseStream.Position = offset; - return ReadUInt32s(count); - } - public long ReadInt64() - { - ReadBytesIntoBuffer(8); - return EndianBitConverter.BytesToInt64(_buffer, 0, Endianness); - } - public long ReadInt64(long offset) - { - BaseStream.Position = offset; - return ReadInt64(); - } - public long[] ReadInt64s(int count) - { - ReadBytesIntoBuffer(count * 8); - return EndianBitConverter.BytesToInt64s(_buffer, 0, count, Endianness); - } - public long[] ReadInt64s(int count, long offset) - { - BaseStream.Position = offset; - return ReadInt64s(count); - } - public ulong ReadUInt64() - { - ReadBytesIntoBuffer(8); - return (ulong)EndianBitConverter.BytesToInt64(_buffer, 0, Endianness); - } - public ulong ReadUInt64(long offset) - { - BaseStream.Position = offset; - return ReadUInt64(); - } - public ulong[] ReadUInt64s(int count) - { - ReadBytesIntoBuffer(count * 8); - return EndianBitConverter.BytesToUInt64s(_buffer, 0, count, Endianness); - } - public ulong[] ReadUInt64s(int count, long offset) - { - BaseStream.Position = offset; - return ReadUInt64s(count); - } - public float ReadSingle() - { - ReadBytesIntoBuffer(4); - return EndianBitConverter.BytesToSingle(_buffer, 0, Endianness); - } - public float ReadSingle(long offset) - { - BaseStream.Position = offset; - return ReadSingle(); - } - public float[] ReadSingles(int count) - { - ReadBytesIntoBuffer(count * 4); - return EndianBitConverter.BytesToSingles(_buffer, 0, count, Endianness); - } - public float[] ReadSingles(int count, long offset) - { - BaseStream.Position = offset; - return ReadSingles(count); - } - public double ReadDouble() - { - ReadBytesIntoBuffer(8); - return EndianBitConverter.BytesToDouble(_buffer, 0, Endianness); - } - public double ReadDouble(long offset) - { - BaseStream.Position = offset; - return ReadDouble(); - } - public double[] ReadDoubles(int count) - { - ReadBytesIntoBuffer(count * 8); - return EndianBitConverter.BytesToDoubles(_buffer, 0, count, Endianness); - } - public double[] ReadDoubles(int count, long offset) - { - BaseStream.Position = offset; - return ReadDoubles(count); - } - public decimal ReadDecimal() - { - ReadBytesIntoBuffer(16); - return EndianBitConverter.BytesToDecimal(_buffer, 0, Endianness); - } - public decimal ReadDecimal(long offset) - { - BaseStream.Position = offset; - return ReadDecimal(); - } - public decimal[] ReadDecimals(int count) - { - ReadBytesIntoBuffer(count * 16); - return EndianBitConverter.BytesToDecimals(_buffer, 0, count, Endianness); - } - public decimal[] ReadDecimals(int count, long offset) - { - BaseStream.Position = offset; - return ReadDecimals(count); - } + protected delegate void ReadArrayMethod(ReadOnlySpan src, Span dest, Endianness endianness); + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected void ReadArray(Span dest, int elementSize, ReadArrayMethod readArray) + { + int numBytes = dest.Length * elementSize; + int start = 0; + while (numBytes != 0) + { + int consumeBytes = Math.Min(numBytes, BUF_LEN); - // Do not allow writing abstract "Enum" because there is no way to know which underlying type to read - // Yes "struct" restriction on reads - public TEnum ReadEnum() where TEnum : struct, Enum - { - Type enumType = typeof(TEnum); - Type underlyingType = Enum.GetUnderlyingType(enumType); - object value; - switch (Type.GetTypeCode(underlyingType)) - { - case TypeCode.Byte: value = ReadByte(); break; - case TypeCode.SByte: value = ReadSByte(); break; - case TypeCode.Int16: value = ReadInt16(); break; - case TypeCode.UInt16: value = ReadUInt16(); break; - case TypeCode.Int32: value = ReadInt32(); break; - case TypeCode.UInt32: value = ReadUInt32(); break; - case TypeCode.Int64: value = ReadInt64(); break; - case TypeCode.UInt64: value = ReadUInt64(); break; - default: throw new ArgumentOutOfRangeException(nameof(underlyingType)); - } - return (TEnum)Enum.ToObject(enumType, value); - } - public TEnum ReadEnum(long offset) where TEnum : struct, Enum - { - BaseStream.Position = offset; - return ReadEnum(); - } - public TEnum[] ReadEnums(int count) where TEnum : struct, Enum - { - if (!Utils.ValidateReadArraySize(count, out TEnum[] array)) - { - array = new TEnum[count]; - for (int i = 0; i < count; i++) - { - array[i] = ReadEnum(); - } - } - return array; - } - public TEnum[] ReadEnums(int count, long offset) where TEnum : struct, Enum - { - BaseStream.Position = offset; - return ReadEnums(count); - } + Span buffer = _buffer.AsSpan(0, consumeBytes); + ReadBytes(buffer); + readArray(buffer, dest.Slice(start, consumeBytes / elementSize), Endianness); - public DateTime ReadDateTime() - { - return DateTime.FromBinary(ReadInt64()); - } - public DateTime ReadDateTime(long offset) - { - BaseStream.Position = offset; - return ReadDateTime(); - } - public DateTime[] ReadDateTimes(int count) - { - if (!Utils.ValidateReadArraySize(count, out DateTime[] array)) - { - array = new DateTime[count]; - for (int i = 0; i < count; i++) - { - array[i] = ReadDateTime(); - } - } - return array; - } - public DateTime[] ReadDateTimes(int count, long offset) - { - BaseStream.Position = offset; - return ReadDateTimes(count); - } + numBytes -= consumeBytes; + start += consumeBytes / elementSize; + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private void ReadBoolArray(Span dest, int boolSize) + { + int numBytes = dest.Length * boolSize; + int start = 0; + while (numBytes != 0) + { + int consumeBytes = Math.Min(numBytes, BUF_LEN); - public T ReadObject() where T : new() - { - return (T)ReadObject(typeof(T)); - } - public object ReadObject(Type objType) - { - Utils.ThrowIfCannotReadWriteType(objType); - object obj = Activator.CreateInstance(objType); - ReadIntoObject(obj); - return obj; - } - public T ReadObject(long offset) where T : new() - { - BaseStream.Position = offset; - return ReadObject(); - } - public object ReadObject(Type objType, long offset) - { - BaseStream.Position = offset; - return ReadObject(objType); - } - public void ReadIntoObject(IBinarySerializable obj) - { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - obj.Read(this); - } - public void ReadIntoObject(IBinarySerializable obj, long offset) - { - BaseStream.Position = offset; - ReadIntoObject(obj); - } - public void ReadIntoObject(object obj) - { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - if (obj is IBinarySerializable bs) - { - bs.Read(this); - return; - } + Span buffer = _buffer.AsSpan(0, consumeBytes); + ReadBytes(buffer); + EndianBinaryPrimitives.ReadBooleans(buffer, dest.Slice(start, consumeBytes / boolSize), Endianness, boolSize); - Type objType = obj.GetType(); - Utils.ThrowIfCannotReadWriteType(objType); + numBytes -= consumeBytes; + start += consumeBytes / boolSize; + } + } - // Get public non-static properties - foreach (PropertyInfo propertyInfo in objType.GetProperties(BindingFlags.Instance | BindingFlags.Public)) - { - if (Utils.AttributeValueOrDefault(propertyInfo, false)) - { - continue; // Skip properties with BinaryIgnoreAttribute - } + public byte PeekByte() + { + long offset = Stream.Position; - Type propertyType = propertyInfo.PropertyType; - object value; + Span buffer = _buffer.AsSpan(0, 1); + ReadBytes(buffer); - if (propertyType.IsArray) - { - int arrayLength = Utils.GetArrayLength(obj, objType, propertyInfo); - // Get array type - Type elementType = propertyType.GetElementType(); - if (arrayLength == 0) - { - value = Array.CreateInstance(elementType, 0); // Create 0 length array regardless of type - } - else - { - if (elementType.IsEnum) - { - elementType = Enum.GetUnderlyingType(elementType); - } - switch (Type.GetTypeCode(elementType)) - { - case TypeCode.Boolean: - { - BooleanSize booleanSize = Utils.AttributeValueOrDefault(propertyInfo, BooleanSize); - value = ReadBooleans(arrayLength, booleanSize); - break; - } - case TypeCode.Byte: value = ReadBytes(arrayLength); break; - case TypeCode.SByte: value = ReadSBytes(arrayLength); break; - case TypeCode.Char: - { - Encoding encoding = Utils.AttributeValueOrDefault(propertyInfo, Encoding); - bool trimNullTerminators = Utils.AttributeValueOrDefault(propertyInfo, false); - value = ReadChars(arrayLength, trimNullTerminators, encoding); - break; - } - case TypeCode.Int16: value = ReadInt16s(arrayLength); break; - case TypeCode.UInt16: value = ReadUInt16s(arrayLength); break; - case TypeCode.Int32: value = ReadInt32s(arrayLength); break; - case TypeCode.UInt32: value = ReadUInt32s(arrayLength); break; - case TypeCode.Int64: value = ReadInt64s(arrayLength); break; - case TypeCode.UInt64: value = ReadUInt64s(arrayLength); break; - case TypeCode.Single: value = ReadSingles(arrayLength); break; - case TypeCode.Double: value = ReadDoubles(arrayLength); break; - case TypeCode.Decimal: value = ReadDecimals(arrayLength); break; - case TypeCode.DateTime: value = ReadDateTimes(arrayLength); break; - case TypeCode.String: - { - Utils.GetStringLength(obj, objType, propertyInfo, true, out bool? nullTerminated, out int stringLength); - Encoding encoding = Utils.AttributeValueOrDefault(propertyInfo, Encoding); - if (nullTerminated == true) - { - value = ReadStringsNullTerminated(arrayLength, encoding); - } - else - { - bool trimNullTerminators = Utils.AttributeValueOrDefault(propertyInfo, false); - value = ReadStrings(arrayLength, stringLength, trimNullTerminators, encoding); - } - break; - } - case TypeCode.Object: - { - value = Array.CreateInstance(elementType, arrayLength); - if (typeof(IBinarySerializable).IsAssignableFrom(elementType)) - { - for (int i = 0; i < arrayLength; i++) - { - var serializable = (IBinarySerializable)Activator.CreateInstance(elementType); - serializable.Read(this); - ((Array)value).SetValue(serializable, i); - } - } - else // Element's type is not supported so try to read the array's objects - { - for (int i = 0; i < arrayLength; i++) - { - object elementObj = ReadObject(elementType); - ((Array)value).SetValue(elementObj, i); - } - } - break; - } - default: throw new ArgumentOutOfRangeException(nameof(elementType)); - } - } - } - else - { - if (propertyType.IsEnum) - { - propertyType = Enum.GetUnderlyingType(propertyType); - } - switch (Type.GetTypeCode(propertyType)) - { - case TypeCode.Boolean: - { - BooleanSize booleanSize = Utils.AttributeValueOrDefault(propertyInfo, BooleanSize); - value = ReadBoolean(booleanSize); - break; - } - case TypeCode.Byte: value = ReadByte(); break; - case TypeCode.SByte: value = ReadSByte(); break; - case TypeCode.Char: - { - Encoding encoding = Utils.AttributeValueOrDefault(propertyInfo, Encoding); - value = ReadChar(encoding); - break; - } - case TypeCode.Int16: value = ReadInt16(); break; - case TypeCode.UInt16: value = ReadUInt16(); break; - case TypeCode.Int32: value = ReadInt32(); break; - case TypeCode.UInt32: value = ReadUInt32(); break; - case TypeCode.Int64: value = ReadInt64(); break; - case TypeCode.UInt64: value = ReadUInt64(); break; - case TypeCode.Single: value = ReadSingle(); break; - case TypeCode.Double: value = ReadDouble(); break; - case TypeCode.Decimal: value = ReadDecimal(); break; - case TypeCode.DateTime: value = ReadDateTime(); break; - case TypeCode.String: - { - Utils.GetStringLength(obj, objType, propertyInfo, true, out bool? nullTerminated, out int stringLength); - Encoding encoding = Utils.AttributeValueOrDefault(propertyInfo, Encoding); - if (nullTerminated == true) - { - value = ReadStringNullTerminated(encoding); - } - else - { - bool trimNullTerminators = Utils.AttributeValueOrDefault(propertyInfo, false); - value = ReadString(stringLength, trimNullTerminators, encoding); - } - break; - } - case TypeCode.Object: - { - if (typeof(IBinarySerializable).IsAssignableFrom(propertyType)) - { - value = Activator.CreateInstance(propertyType); - ((IBinarySerializable)value).Read(this); - } - else // The property's type is not supported so try to read the object - { - value = ReadObject(propertyType); - } - break; - } - default: throw new ArgumentOutOfRangeException(nameof(propertyType)); - } - } + Stream.Position = offset; + return buffer[0]; + } - // Set the value into the property - propertyInfo.SetValue(obj, value); - } - } - public void ReadIntoObject(object obj, long offset) - { - BaseStream.Position = offset; - ReadIntoObject(obj); - } - } + public sbyte ReadSByte() + { + Span buffer = _buffer.AsSpan(0, 1); + ReadBytes(buffer); + return (sbyte)buffer[0]; + } + public void ReadSBytes(Span dest) + { + Span buffer = MemoryMarshal.Cast(dest); + ReadBytes(buffer); + } + public byte ReadByte() + { + Span buffer = _buffer.AsSpan(0, 1); + ReadBytes(buffer); + return buffer[0]; + } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void ReadBytes(Span dest) + { + if (Stream.Read(dest) != dest.Length) + { + throw new EndOfStreamException(); + } + } + public short ReadInt16() + { + Span buffer = _buffer.AsSpan(0, 2); + ReadBytes(buffer); + return EndianBinaryPrimitives.ReadInt16(buffer, Endianness); + } + public void ReadInt16s(Span dest) + { + ReadArray(dest, 2, EndianBinaryPrimitives.ReadInt16s); + } + public ushort ReadUInt16() + { + Span buffer = _buffer.AsSpan(0, 2); + ReadBytes(buffer); + return EndianBinaryPrimitives.ReadUInt16(buffer, Endianness); + } + public void ReadUInt16s(Span dest) + { + ReadArray(dest, 2, EndianBinaryPrimitives.ReadUInt16s); + } + public int ReadInt32() + { + Span buffer = _buffer.AsSpan(0, 4); + ReadBytes(buffer); + return EndianBinaryPrimitives.ReadInt32(buffer, Endianness); + } + public void ReadInt32s(Span dest) + { + ReadArray(dest, 4, EndianBinaryPrimitives.ReadInt32s); + } + public uint ReadUInt32() + { + Span buffer = _buffer.AsSpan(0, 4); + ReadBytes(buffer); + return EndianBinaryPrimitives.ReadUInt32(buffer, Endianness); + } + public void ReadUInt32s(Span dest) + { + ReadArray(dest, 4, EndianBinaryPrimitives.ReadUInt32s); + } + public long ReadInt64() + { + Span buffer = _buffer.AsSpan(0, 8); + ReadBytes(buffer); + return EndianBinaryPrimitives.ReadInt64(buffer, Endianness); + } + public void ReadInt64s(Span dest) + { + ReadArray(dest, 8, EndianBinaryPrimitives.ReadInt64s); + } + public ulong ReadUInt64() + { + Span buffer = _buffer.AsSpan(0, 8); + ReadBytes(buffer); + return EndianBinaryPrimitives.ReadUInt64(buffer, Endianness); + } + public void ReadUInt64s(Span dest) + { + ReadArray(dest, 8, EndianBinaryPrimitives.ReadUInt64s); + } + + public Half ReadHalf() + { + Span buffer = _buffer.AsSpan(0, 2); + ReadBytes(buffer); + return EndianBinaryPrimitives.ReadHalf(buffer, Endianness); + } + public void ReadHalves(Span dest) + { + ReadArray(dest, 2, EndianBinaryPrimitives.ReadHalves); + } + public float ReadSingle() + { + Span buffer = _buffer.AsSpan(0, 4); + ReadBytes(buffer); + return EndianBinaryPrimitives.ReadSingle(buffer, Endianness); + } + public void ReadSingles(Span dest) + { + ReadArray(dest, 4, EndianBinaryPrimitives.ReadSingles); + } + public double ReadDouble() + { + Span buffer = _buffer.AsSpan(0, 8); + ReadBytes(buffer); + return EndianBinaryPrimitives.ReadDouble(buffer, Endianness); + } + public void ReadDoubles(Span dest) + { + ReadArray(dest, 8, EndianBinaryPrimitives.ReadDoubles); + } + public decimal ReadDecimal() + { + Span buffer = _buffer.AsSpan(0, 16); + ReadBytes(buffer); + return EndianBinaryPrimitives.ReadDecimal(buffer, Endianness); + } + public void ReadDecimals(Span dest) + { + ReadArray(dest, 16, EndianBinaryPrimitives.ReadDecimals); + } + + public bool ReadBoolean() + { + int elementSize = EndianBinaryPrimitives.GetBytesForBooleanSize(BooleanSize); + Span buffer = _buffer.AsSpan(0, elementSize); + ReadBytes(buffer); + return EndianBinaryPrimitives.ReadBoolean(buffer, Endianness, elementSize); + } + public void ReadBooleans(Span dest) + { + int elementSize = EndianBinaryPrimitives.GetBytesForBooleanSize(BooleanSize); + ReadBoolArray(dest, elementSize); + } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public TEnum ReadEnum() where TEnum : unmanaged, Enum + { + int size = Unsafe.SizeOf(); + if (size == 1) + { + byte b = ReadByte(); + return Unsafe.As(ref b); + } + if (size == 2) + { + ushort s = ReadUInt16(); + return Unsafe.As(ref s); + } + if (size == 4) + { + uint i = ReadUInt32(); + return Unsafe.As(ref i); + } + ulong l = ReadUInt64(); + return Unsafe.As(ref l); + } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void ReadEnums(Span dest) where TEnum : unmanaged, Enum + { + int size = Unsafe.SizeOf(); + if (size == 1) + { + ReadBytes(MemoryMarshal.Cast(dest)); + } + else if (size == 2) + { + ReadUInt16s(MemoryMarshal.Cast(dest)); + } + else if (size == 4) + { + ReadUInt32s(MemoryMarshal.Cast(dest)); + } + else + { + ReadUInt64s(MemoryMarshal.Cast(dest)); + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public object ReadEnum(Type enumType) + { + // Type.IsEnum is also false for base Enum type, so don't worry about it + Type underlyingType = Enum.GetUnderlyingType(enumType); + switch (Type.GetTypeCode(underlyingType)) + { + case TypeCode.SByte: return Enum.ToObject(enumType, ReadSByte()); + case TypeCode.Byte: return Enum.ToObject(enumType, ReadByte()); + case TypeCode.Int16: return Enum.ToObject(enumType, ReadInt16()); + case TypeCode.UInt16: return Enum.ToObject(enumType, ReadUInt16()); + case TypeCode.Int32: return Enum.ToObject(enumType, ReadInt32()); + case TypeCode.UInt32: return Enum.ToObject(enumType, ReadUInt32()); + case TypeCode.Int64: return Enum.ToObject(enumType, ReadInt64()); + case TypeCode.UInt64: return Enum.ToObject(enumType, ReadUInt64()); + } + throw new ArgumentOutOfRangeException(nameof(enumType), enumType, null); + } + + public DateTime ReadDateTime() + { + Span buffer = _buffer.AsSpan(0, 8); + ReadBytes(buffer); + return EndianBinaryPrimitives.ReadDateTime(buffer, Endianness); + } + public void ReadDateTimes(Span dest) + { + ReadArray(dest, 8, EndianBinaryPrimitives.ReadDateTimes); + } + public DateOnly ReadDateOnly() + { + Span buffer = _buffer.AsSpan(0, 4); + ReadBytes(buffer); + return EndianBinaryPrimitives.ReadDateOnly(buffer, Endianness); + } + public void ReadDateOnlys(Span dest) + { + ReadArray(dest, 4, EndianBinaryPrimitives.ReadDateOnlys); + } + public TimeOnly ReadTimeOnly() + { + Span buffer = _buffer.AsSpan(0, 8); + ReadBytes(buffer); + return EndianBinaryPrimitives.ReadTimeOnly(buffer, Endianness); + } + public void ReadTimeOnlys(Span dest) + { + ReadArray(dest, 8, EndianBinaryPrimitives.ReadTimeOnlys); + } + + public Vector2 ReadVector2() + { + Span buffer = _buffer.AsSpan(0, 8); + ReadBytes(buffer); + return EndianBinaryPrimitives.ReadVector2(buffer, Endianness); + } + public void ReadVector2s(Span dest) + { + ReadArray(dest, 8, EndianBinaryPrimitives.ReadVector2s); + } + public Vector3 ReadVector3() + { + Span buffer = _buffer.AsSpan(0, 12); + ReadBytes(buffer); + return EndianBinaryPrimitives.ReadVector3(buffer, Endianness); + } + public void ReadVector3s(Span dest) + { + ReadArray(dest, 12, EndianBinaryPrimitives.ReadVector3s); + } + public Vector4 ReadVector4() + { + Span buffer = _buffer.AsSpan(0, 16); + ReadBytes(buffer); + return EndianBinaryPrimitives.ReadVector4(buffer, Endianness); + } + public void ReadVector4s(Span dest) + { + ReadArray(dest, 16, EndianBinaryPrimitives.ReadVector4s); + } + public Quaternion ReadQuaternion() + { + Span buffer = _buffer.AsSpan(0, 16); + ReadBytes(buffer); + return EndianBinaryPrimitives.ReadQuaternion(buffer, Endianness); + } + public void ReadQuaternions(Span dest) + { + ReadArray(dest, 16, EndianBinaryPrimitives.ReadQuaternions); + } + public Matrix4x4 ReadMatrix4x4() + { + Span buffer = _buffer.AsSpan(0, 64); + ReadBytes(buffer); + return EndianBinaryPrimitives.ReadMatrix4x4(buffer, Endianness); + } + public void ReadMatrix4x4s(Span dest) + { + ReadArray(dest, 64, EndianBinaryPrimitives.ReadMatrix4x4s); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public char ReadChar() + { + return ASCII ? (char)ReadByte() : (char)ReadUInt16(); + } + public void ReadChars(Span dest) + { + if (ASCII) + { + Span buffer = MemoryMarshal.Cast(dest).Slice(dest.Length); + ReadBytes(buffer); + for (int i = 0; i < dest.Length; i++) + { + dest[i] = (char)buffer[i]; + } + } + else + { + Span buffer = MemoryMarshal.Cast(dest); + ReadUInt16s(buffer); + } + } + public char[] ReadChars_TrimNullTerminators(int charCount) + { + char[] chars = new char[charCount]; + ReadChars(chars); + Utils.TrimNullTerminators(ref chars); + return chars; + } + public string ReadString_Count(int charCount) + { + void Create(Span dest, byte _) + { + ReadChars(dest); + } + return string.Create(charCount, byte.MinValue, Create); + } + public void ReadStrings_Count(Span dest, int charCount) + { + for (int i = 0; i < dest.Length; i++) + { + dest[i] = ReadString_Count(charCount); + } + } + public string ReadString_Count_TrimNullTerminators(int charCount) + { + return new string(ReadChars_TrimNullTerminators(charCount)); + } + public void ReadStrings_Count_TrimNullTerminators(Span dest, int charCount) + { + for (int i = 0; i < dest.Length; i++) + { + dest[i] = ReadString_Count_TrimNullTerminators(charCount); + } + } + public string ReadString_NullTerminated() + { + var v = new StringBuilder(); + while (true) + { + char c = ReadChar(); + if (c == '\0') + { + break; + } + v.Append(c); + } + return v.ToString(); // Returns string.Empty if length is 0 + } + public void ReadStrings_NullTerminated(Span dest) + { + for (int i = 0; i < dest.Length; i++) + { + dest[i] = ReadString_NullTerminated(); + } + } + } } diff --git a/Source/EndianBinaryReader_Reflection.cs b/Source/EndianBinaryReader_Reflection.cs new file mode 100644 index 0000000..4ee52d8 --- /dev/null +++ b/Source/EndianBinaryReader_Reflection.cs @@ -0,0 +1,343 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Numerics; +using System.Reflection; + +namespace Kermalis.EndianBinaryIO +{ + public partial class EndianBinaryReader + { + public void ReadIntoObject(object obj) + { + if (obj is IBinarySerializable bs) + { + bs.Read(this); + return; + } + + Type objType = obj.GetType(); + Utils.ThrowIfCannotReadWriteType(objType); + + // Get public non-static properties + foreach (PropertyInfo propertyInfo in objType.GetProperties(BindingFlags.Instance | BindingFlags.Public)) + { + if (Utils.AttributeValueOrDefault(propertyInfo, false)) + { + continue; // Skip properties with BinaryIgnoreAttribute + } + + Type propertyType = propertyInfo.PropertyType; + object value = propertyType.IsArray + ? ReadPropertyValue_Array(obj, objType, propertyInfo, propertyType) + : ReadPropertyValue_NonArray(obj, objType, propertyInfo, propertyType); + + propertyInfo.SetValue(obj, value); // Set the value into the property + } + } + public T ReadObject() + { + return (T)ReadObject(typeof(T)); + } + public object ReadObject(Type objType) + { + if (!TryReadSupportedObject(objType, out object? obj)) + { + Utils.ThrowIfCannotReadWriteType(objType); + obj = Activator.CreateInstance(objType)!; + ReadIntoObject(obj); + } + return obj; + } + + private bool TryReadSupportedObject(Type objType, [NotNullWhen(true)] out object? obj) + { + switch (objType) + { + case Type t when t == typeof(sbyte): { obj = ReadSByte(); return true; } + case Type t when t == typeof(byte): { obj = ReadByte(); return true; } + case Type t when t == typeof(short): { obj = ReadInt16(); return true; } + case Type t when t == typeof(ushort): { obj = ReadUInt16(); return true; } + case Type t when t == typeof(int): { obj = ReadInt32(); return true; } + case Type t when t == typeof(uint): { obj = ReadUInt32(); return true; } + case Type t when t == typeof(long): { obj = ReadInt64(); return true; } + case Type t when t == typeof(ulong): { obj = ReadUInt64(); return true; } + case Type t when t == typeof(Half): { obj = ReadHalf(); return true; } + case Type t when t == typeof(float): { obj = ReadSingle(); return true; } + case Type t when t == typeof(double): { obj = ReadDouble(); return true; } + case Type t when t == typeof(decimal): { obj = ReadDecimal(); return true; } + case Type t when t == typeof(bool): { obj = ReadBoolean(); return true; } + case Type t when t.IsEnum: { obj = ReadEnum(t); return true; } + case Type t when t == typeof(DateTime): { obj = ReadDateTime(); return true; } + case Type t when t == typeof(DateOnly): { obj = ReadDateOnly(); return true; } + case Type t when t == typeof(TimeOnly): { obj = ReadTimeOnly(); return true; } + case Type t when t == typeof(Vector2): { obj = ReadVector2(); return true; } + case Type t when t == typeof(Vector3): { obj = ReadVector3(); return true; } + case Type t when t == typeof(Vector4): { obj = ReadVector4(); return true; } + case Type t when t == typeof(Quaternion): { obj = ReadQuaternion(); return true; } + case Type t when t == typeof(Matrix4x4): { obj = ReadMatrix4x4(); return true; } + case Type t when t == typeof(char): { obj = ReadChar(); return true; } + case Type t when t == typeof(string): { obj = ReadString_NullTerminated(); return true; } + } + + obj = default; + return false; + } + + private object ReadPropertyValue_NonArray(object obj, Type objType, PropertyInfo propertyInfo, Type propertyType) + { + switch (propertyType) + { + case Type t when t == typeof(bool): + { + BooleanSize old = BooleanSize; + BooleanSize = Utils.AttributeValueOrDefault(propertyInfo, old); + bool value = ReadBoolean(); + BooleanSize = old; + return value; + } + case Type t when t == typeof(char): + { + bool old = ASCII; + ASCII = Utils.AttributeValueOrDefault(propertyInfo, old); + char value = ReadChar(); + ASCII = old; + return value; + } + case Type t when t == typeof(string): + { + Utils.GetStringLength(obj, objType, propertyInfo, true, out bool? nullTerminated, out int stringLength); + + bool old = ASCII; + ASCII = Utils.AttributeValueOrDefault(propertyInfo, old); + string value; + if (nullTerminated == true) + { + value = ReadString_NullTerminated(); + } + else + { + bool trimNullTerminators = Utils.AttributeValueOrDefault(propertyInfo, false); + if (trimNullTerminators) + { + value = ReadString_Count_TrimNullTerminators(stringLength); + } + else + { + value = ReadString_Count(stringLength); + } + } + ASCII = old; + return value; + } + default: + { + return ReadObject(propertyType); + } + } + } + private object ReadPropertyValue_Array(object obj, Type objType, PropertyInfo propertyInfo, Type propertyType) + { + int arrayLength = Utils.GetArrayLength(obj, objType, propertyInfo); + Type elementType = propertyType.GetElementType()!; + if (arrayLength == 0) + { + return Array.CreateInstance(elementType, 0); // Create 0 length array regardless of type + } + + switch (elementType) + { + case Type t when t == typeof(sbyte): + { + sbyte[] value = new sbyte[arrayLength]; + ReadSBytes(value); + return value; + } + case Type t when t == typeof(byte): + { + byte[] value = new byte[arrayLength]; + ReadBytes(value); + return value; + } + case Type t when t == typeof(short): + { + short[] value = new short[arrayLength]; + ReadInt16s(value); + return value; + } + case Type t when t == typeof(ushort): + { + ushort[] value = new ushort[arrayLength]; + ReadUInt16s(value); + return value; + } + case Type t when t == typeof(int): + { + int[] value = new int[arrayLength]; + ReadInt32s(value); + return value; + } + case Type t when t == typeof(uint): + { + uint[] value = new uint[arrayLength]; + ReadUInt32s(value); + return value; + } + case Type t when t == typeof(long): + { + long[] value = new long[arrayLength]; + ReadInt64s(value); + return value; + } + case Type t when t == typeof(ulong): + { + ulong[] value = new ulong[arrayLength]; + ReadUInt64s(value); + return value; + } + case Type t when t == typeof(Half): + { + var value = new Half[arrayLength]; + ReadHalves(value); + return value; + } + case Type t when t == typeof(float): + { + float[] value = new float[arrayLength]; + ReadSingles(value); + return value; + } + case Type t when t == typeof(double): + { + double[] value = new double[arrayLength]; + ReadDoubles(value); + return value; + } + case Type t when t == typeof(decimal): + { + decimal[] value = new decimal[arrayLength]; + ReadDecimals(value); + return value; + } + case Type t when t == typeof(bool): + { + BooleanSize old = BooleanSize; + BooleanSize = Utils.AttributeValueOrDefault(propertyInfo, old); + bool[] value = new bool[arrayLength]; + ReadBooleans(value); + BooleanSize = old; + return value; + } + case Type t when t.IsEnum: + { + var value = Array.CreateInstance(elementType, arrayLength); + for (int i = 0; i < arrayLength; i++) + { + value.SetValue(ReadEnum(elementType), i); + } + return value; + } + case Type t when t == typeof(DateTime): + { + var value = new DateTime[arrayLength]; + ReadDateTimes(value); + return value; + } + case Type t when t == typeof(DateOnly): + { + var value = new DateOnly[arrayLength]; + ReadDateOnlys(value); + return value; + } + case Type t when t == typeof(TimeOnly): + { + var value = new TimeOnly[arrayLength]; + ReadTimeOnlys(value); + return value; + } + case Type t when t == typeof(Vector2): + { + var value = new Vector2[arrayLength]; + ReadVector2s(value); + return value; + } + case Type t when t == typeof(Vector3): + { + var value = new Vector3[arrayLength]; + ReadVector3s(value); + return value; + } + case Type t when t == typeof(Vector4): + { + var value = new Vector4[arrayLength]; + ReadVector4s(value); + return value; + } + case Type t when t == typeof(Quaternion): + { + var value = new Quaternion[arrayLength]; + ReadQuaternions(value); + return value; + } + case Type t when t == typeof(Matrix4x4): + { + var value = new Matrix4x4[arrayLength]; + ReadMatrix4x4s(value); + return value; + } + case Type t when t == typeof(char): + { + bool old = ASCII; + ASCII = Utils.AttributeValueOrDefault(propertyInfo, old); + bool trimNullTerminators = Utils.AttributeValueOrDefault(propertyInfo, false); + char[] value; + if (trimNullTerminators) + { + value = ReadChars_TrimNullTerminators(arrayLength); + } + else + { + value = new char[arrayLength]; + ReadChars(value); + } + ASCII = old; + return value; + } + case Type t when t == typeof(string): + { + Utils.GetStringLength(obj, objType, propertyInfo, true, out bool? nullTerminated, out int stringLength); + + bool old = ASCII; + ASCII = Utils.AttributeValueOrDefault(propertyInfo, old); + string[] value = new string[arrayLength]; + if (nullTerminated == true) + { + ReadStrings_NullTerminated(value); + } + else + { + bool trimNullTerminators = Utils.AttributeValueOrDefault(propertyInfo, false); + if (trimNullTerminators) + { + ReadStrings_Count_TrimNullTerminators(value, stringLength); + } + else + { + ReadStrings_Count(value, stringLength); + } + } + ASCII = old; + return value; + } + default: + { + var value = Array.CreateInstance(elementType, arrayLength); + for (int i = 0; i < arrayLength; i++) + { + value.SetValue(ReadObject(elementType), i); + } + return value; + } + } + } + } +} diff --git a/Source/EndianBinaryWriter.cs b/Source/EndianBinaryWriter.cs index 7800f28..463a7b4 100644 --- a/Source/EndianBinaryWriter.cs +++ b/Source/EndianBinaryWriter.cs @@ -1,926 +1,497 @@ using System; using System.IO; -using System.Reflection; -using System.Text; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace Kermalis.EndianBinaryIO { - public class EndianBinaryWriter - { - public Stream BaseStream { get; } - private Endianness _endianness; - public Endianness Endianness - { - get => _endianness; - set - { - if (value >= Endianness.MAX) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - _endianness = value; - } - } - private BooleanSize _booleanSize; - public BooleanSize BooleanSize - { - get => _booleanSize; - set - { - if (value >= BooleanSize.MAX) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - _booleanSize = value; - } - } - public Encoding Encoding { get; set; } + public partial class EndianBinaryWriter + { + protected const int BUF_LEN = 64; // Must be a multiple of 64 - private byte[] _buffer; + public Stream Stream { get; } + private Endianness _endianness; + public Endianness Endianness + { + get => _endianness; + set + { + if (value >= Endianness.MAX) + { + throw new ArgumentOutOfRangeException(nameof(value), value, null); + } + _endianness = value; + } + } + private BooleanSize _booleanSize; + public BooleanSize BooleanSize + { + get => _booleanSize; + set + { + if (value >= BooleanSize.MAX) + { + throw new ArgumentOutOfRangeException(nameof(value), value, null); + } + _booleanSize = value; + } + } + public bool ASCII { get; set; } - public EndianBinaryWriter(Stream baseStream, Endianness endianness = Endianness.LittleEndian, BooleanSize booleanSize = BooleanSize.U8) - { - if (baseStream is null) - { - throw new ArgumentNullException(nameof(baseStream)); - } - if (!baseStream.CanWrite) - { - throw new ArgumentException(nameof(baseStream)); - } - BaseStream = baseStream; - Endianness = endianness; - BooleanSize = booleanSize; - Encoding = Encoding.Default; - } - public EndianBinaryWriter(Stream baseStream, Encoding encoding, Endianness endianness = Endianness.LittleEndian, BooleanSize booleanSize = BooleanSize.U8) - { - if (baseStream is null) - { - throw new ArgumentNullException(nameof(baseStream)); - } - if (!baseStream.CanWrite) - { - throw new ArgumentException(nameof(baseStream)); - } - BaseStream = baseStream; - Endianness = endianness; - BooleanSize = booleanSize; - Encoding = encoding; - } + protected readonly byte[] _buffer; - private void SetBufferSize(int size) - { - if (_buffer is null || _buffer.Length < size) - { - _buffer = new byte[size]; - } - } - private void WriteBytesFromBuffer(int byteCount) - { - BaseStream.Write(_buffer, 0, byteCount); - } + public EndianBinaryWriter(Stream stream, Endianness endianness = Endianness.LittleEndian, BooleanSize booleanSize = BooleanSize.U8, bool ascii = false) + { + if (!stream.CanWrite) + { + throw new ArgumentOutOfRangeException(nameof(stream), "Stream is not open for writing."); + } - public void Write(bool value) - { - Write(value, BooleanSize); - } - public void Write(bool value, long offset) - { - BaseStream.Position = offset; - Write(value, BooleanSize); - } - public void Write(bool value, BooleanSize booleanSize) - { - switch (booleanSize) - { - case BooleanSize.U8: - { - SetBufferSize(1); - _buffer[0] = value ? (byte)1 : (byte)0; - WriteBytesFromBuffer(1); - break; - } - case BooleanSize.U16: - { - _buffer = EndianBitConverter.Int16ToBytes(value ? (short)1 : (short)0, Endianness); - WriteBytesFromBuffer(2); - break; - } - case BooleanSize.U32: - { - _buffer = EndianBitConverter.Int32ToBytes(value ? 1 : 0, Endianness); - WriteBytesFromBuffer(4); - break; - } - default: throw new ArgumentOutOfRangeException(nameof(booleanSize)); - } - } - public void Write(bool value, BooleanSize booleanSize, long offset) - { - BaseStream.Position = offset; - Write(value, booleanSize); - } - public void Write(bool[] value) - { - Write(value, 0, value.Length, BooleanSize); - } - public void Write(bool[] value, long offset) - { - BaseStream.Position = offset; - Write(value, 0, value.Length, BooleanSize); - } - public void Write(bool[] value, BooleanSize booleanSize) - { - Write(value, 0, value.Length, booleanSize); - } - public void Write(bool[] value, BooleanSize booleanSize, long offset) - { - BaseStream.Position = offset; - Write(value, 0, value.Length, booleanSize); - } - public void Write(bool[] value, int startIndex, int count) - { - Write(value, startIndex, count, BooleanSize); - } - public void Write(bool[] value, int startIndex, int count, long offset) - { - BaseStream.Position = offset; - Write(value, startIndex, count, BooleanSize); - } - public void Write(bool[] value, int startIndex, int count, BooleanSize booleanSize) - { - if (Utils.ValidateArrayIndexAndCount(value, startIndex, count)) - { - return; - } - for (int i = startIndex; i < count; i++) - { - Write(value[i], booleanSize); - } - } - public void Write(bool[] value, int startIndex, int count, BooleanSize booleanSize, long offset) - { - BaseStream.Position = offset; - Write(value, startIndex, count, booleanSize); - } - public void Write(byte value) - { - SetBufferSize(1); - _buffer[0] = value; - WriteBytesFromBuffer(1); - } - public void Write(byte value, long offset) - { - BaseStream.Position = offset; - Write(value); - } - public void Write(byte[] value) - { - Write(value, 0, value.Length); - } - public void Write(byte[] value, long offset) - { - BaseStream.Position = offset; - Write(value, 0, value.Length); - } - public void Write(byte[] value, int startIndex, int count) - { - if (Utils.ValidateArrayIndexAndCount(value, startIndex, count)) - { - return; - } - SetBufferSize(count); - for (int i = 0; i < count; i++) - { - _buffer[i] = value[i + startIndex]; - } - WriteBytesFromBuffer(count); - } - public void Write(byte[] value, int startIndex, int count, long offset) - { - BaseStream.Position = offset; - Write(value, startIndex, count); - } - public void Write(sbyte value) - { - SetBufferSize(1); - _buffer[0] = (byte)value; - WriteBytesFromBuffer(1); - } - public void Write(sbyte value, long offset) - { - BaseStream.Position = offset; - Write(value); - } - public void Write(sbyte[] value) - { - Write(value, 0, value.Length); - } - public void Write(sbyte[] value, long offset) - { - BaseStream.Position = offset; - Write(value, 0, value.Length); - } - public void Write(sbyte[] value, int startIndex, int count) - { - if (Utils.ValidateArrayIndexAndCount(value, startIndex, count)) - { - return; - } - SetBufferSize(count); - for (int i = 0; i < count; i++) - { - _buffer[i] = (byte)value[i + startIndex]; - } - WriteBytesFromBuffer(count); - } - public void Write(sbyte[] value, int startIndex, int count, long offset) - { - BaseStream.Position = offset; - Write(value, startIndex, count); - } - public void Write(char value) - { - Write(value, Encoding); - } - public void Write(char value, long offset) - { - BaseStream.Position = offset; - Write(value, Encoding); - } - public void Write(char value, Encoding encoding) - { - Utils.ThrowIfCannotUseEncoding(encoding); - _buffer = encoding.GetBytes(new[] { value }); - WriteBytesFromBuffer(_buffer.Length); - } - public void Write(char value, Encoding encoding, long offset) - { - BaseStream.Position = offset; - Write(value, encoding); - } - public void Write(char[] value) - { - Write(value, 0, value.Length, Encoding); - } - public void Write(char[] value, long offset) - { - BaseStream.Position = offset; - Write(value, 0, value.Length, Encoding); - } - public void Write(char[] value, Encoding encoding) - { - Write(value, 0, value.Length, encoding); - } - public void Write(char[] value, Encoding encoding, long offset) - { - BaseStream.Position = offset; - Write(value, 0, value.Length, encoding); - } - public void Write(char[] value, int startIndex, int count) - { - Write(value, startIndex, count, Encoding); - } - public void Write(char[] value, int startIndex, int count, long offset) - { - BaseStream.Position = offset; - Write(value, startIndex, count, Encoding); - } - public void Write(char[] value, int startIndex, int count, Encoding encoding) - { - if (Utils.ValidateArrayIndexAndCount(value, startIndex, count)) - { - return; - } - Utils.ThrowIfCannotUseEncoding(encoding); - _buffer = encoding.GetBytes(value, startIndex, count); - WriteBytesFromBuffer(_buffer.Length); - } - public void Write(char[] value, int startIndex, int count, Encoding encoding, long offset) - { - BaseStream.Position = offset; - Write(value, startIndex, count, encoding); - } - public void Write(string value, bool nullTerminated) - { - Write(value, nullTerminated, Encoding); - } - public void Write(string value, bool nullTerminated, long offset) - { - BaseStream.Position = offset; - Write(value, nullTerminated, Encoding); - } - public void Write(string value, bool nullTerminated, Encoding encoding) - { - Write(value.ToCharArray(), encoding); - if (nullTerminated) - { - Write('\0', encoding); - } - } - public void Write(string value, bool nullTerminated, Encoding encoding, long offset) - { - BaseStream.Position = offset; - Write(value, nullTerminated, encoding); - } - public void Write(string value, int charCount) - { - Write(value, charCount, Encoding); - } - public void Write(string value, int charCount, long offset) - { - BaseStream.Position = offset; - Write(value, charCount, Encoding); - } - public void Write(string value, int charCount, Encoding encoding) - { - Utils.TruncateString(value, charCount, out char[] chars); - Write(chars, encoding); - } - public void Write(string value, int charCount, Encoding encoding, long offset) - { - BaseStream.Position = offset; - Write(value, charCount, encoding); - } - public void Write(string[] value, int startIndex, int count, bool nullTerminated) - { - Write(value, startIndex, count, nullTerminated, Encoding); - } - public void Write(string[] value, int startIndex, int count, bool nullTerminated, long offset) - { - BaseStream.Position = offset; - Write(value, startIndex, count, nullTerminated, Encoding); - } - public void Write(string[] value, int startIndex, int count, bool nullTerminated, Encoding encoding) - { - if (Utils.ValidateArrayIndexAndCount(value, startIndex, count)) - { - return; - } - for (int i = 0; i < count; i++) - { - Write(value[i + startIndex], nullTerminated, encoding); - } - } - public void Write(string[] value, int startIndex, int count, bool nullTerminated, Encoding encoding, long offset) - { - BaseStream.Position = offset; - Write(value, startIndex, count, nullTerminated, encoding); - } - public void Write(string[] value, int startIndex, int count, int charCount) - { - Write(value, startIndex, count, charCount, Encoding); - } - public void Write(string[] value, int startIndex, int count, int charCount, long offset) - { - BaseStream.Position = offset; - Write(value, startIndex, count, charCount, Encoding); - } - public void Write(string[] value, int startIndex, int count, int charCount, Encoding encoding) - { - if (Utils.ValidateArrayIndexAndCount(value, startIndex, count)) - { - return; - } - for (int i = 0; i < count; i++) - { - Write(value[i + startIndex], charCount, encoding); - } - } - public void Write(string[] value, int startIndex, int count, int charCount, Encoding encoding, long offset) - { - BaseStream.Position = offset; - Write(value, startIndex, count, charCount, encoding); - } - public void Write(short value) - { - _buffer = EndianBitConverter.Int16ToBytes(value, Endianness); - WriteBytesFromBuffer(2); - } - public void Write(short value, long offset) - { - BaseStream.Position = offset; - Write(value); - } - public void Write(short[] value) - { - Write(value, 0, value.Length); - } - public void Write(short[] value, long offset) - { - BaseStream.Position = offset; - Write(value, 0, value.Length); - } - public void Write(short[] value, int startIndex, int count) - { - _buffer = EndianBitConverter.Int16sToBytes(value, startIndex, count, Endianness); - WriteBytesFromBuffer(count * 2); - } - public void Write(short[] value, int startIndex, int count, long offset) - { - BaseStream.Position = offset; - Write(value, startIndex, count); - } - public void Write(ushort value) - { - _buffer = EndianBitConverter.Int16ToBytes((short)value, Endianness); - WriteBytesFromBuffer(2); - } - public void Write(ushort value, long offset) - { - BaseStream.Position = offset; - Write(value); - } - public void Write(ushort[] value) - { - Write(value, 0, value.Length); - } - public void Write(ushort[] value, long offset) - { - BaseStream.Position = offset; - Write(value, 0, value.Length); - } - public void Write(ushort[] value, int startIndex, int count) - { - _buffer = EndianBitConverter.UInt16sToBytes(value, startIndex, count, Endianness); - WriteBytesFromBuffer(count * 2); - } - public void Write(ushort[] value, int startIndex, int count, long offset) - { - BaseStream.Position = offset; - Write(value, startIndex, count); - } - public void Write(int value) - { - _buffer = EndianBitConverter.Int32ToBytes(value, Endianness); - WriteBytesFromBuffer(4); - } - public void Write(int value, long offset) - { - BaseStream.Position = offset; - Write(value); - } - public void Write(int[] value) - { - Write(value, 0, value.Length); - } - public void Write(int[] value, long offset) - { - BaseStream.Position = offset; - Write(value, 0, value.Length); - } - public void Write(int[] value, int startIndex, int count) - { - _buffer = EndianBitConverter.Int32sToBytes(value, startIndex, count, Endianness); - WriteBytesFromBuffer(count * 4); - } - public void Write(int[] value, int startIndex, int count, long offset) - { - BaseStream.Position = offset; - Write(value, startIndex, count); - } - public void Write(uint value) - { - _buffer = EndianBitConverter.Int32ToBytes((int)value, Endianness); - WriteBytesFromBuffer(4); - } - public void Write(uint value, long offset) - { - BaseStream.Position = offset; - Write(value); - } - public void Write(uint[] value) - { - Write(value, 0, value.Length); - } - public void Write(uint[] value, long offset) - { - BaseStream.Position = offset; - Write(value, 0, value.Length); - } - public void Write(uint[] value, int startIndex, int count) - { - _buffer = EndianBitConverter.UInt32sToBytes(value, startIndex, count, Endianness); - WriteBytesFromBuffer(count * 4); - } - public void Write(uint[] value, int startIndex, int count, long offset) - { - BaseStream.Position = offset; - Write(value, startIndex, count); - } - public void Write(long value) - { - _buffer = EndianBitConverter.Int64ToBytes(value, Endianness); - WriteBytesFromBuffer(8); - } - public void Write(long value, long offset) - { - BaseStream.Position = offset; - Write(value); - } - public void Write(long[] value) - { - Write(value, 0, value.Length); - } - public void Write(long[] value, long offset) - { - BaseStream.Position = offset; - Write(value, 0, value.Length); - } - public void Write(long[] value, int startIndex, int count) - { - _buffer = EndianBitConverter.Int64sToBytes(value, startIndex, count, Endianness); - WriteBytesFromBuffer(count * 8); - } - public void Write(long[] value, int startIndex, int count, long offset) - { - BaseStream.Position = offset; - Write(value, startIndex, count); - } - public void Write(ulong value) - { - _buffer = EndianBitConverter.Int64ToBytes((long)value, Endianness); - WriteBytesFromBuffer(8); - } - public void Write(ulong value, long offset) - { - BaseStream.Position = offset; - Write(value); - } - public void Write(ulong[] value) - { - Write(value, 0, value.Length); - } - public void Write(ulong[] value, long offset) - { - BaseStream.Position = offset; - Write(value, 0, value.Length); - } - public void Write(ulong[] value, int startIndex, int count) - { - _buffer = EndianBitConverter.UInt64sToBytes(value, startIndex, count, Endianness); - WriteBytesFromBuffer(count * 8); - } - public void Write(ulong[] value, int startIndex, int count, long offset) - { - BaseStream.Position = offset; - Write(value, startIndex, count); - } - public void Write(float value) - { - _buffer = EndianBitConverter.SingleToBytes(value, Endianness); - WriteBytesFromBuffer(4); - } - public void Write(float value, long offset) - { - BaseStream.Position = offset; - Write(value); - } - public void Write(float[] value) - { - Write(value, 0, value.Length); - } - public void Write(float[] value, long offset) - { - BaseStream.Position = offset; - Write(value, 0, value.Length); - } - public void Write(float[] value, int startIndex, int count) - { - _buffer = EndianBitConverter.SinglesToBytes(value, startIndex, count, Endianness); - WriteBytesFromBuffer(count * 4); - } - public void Write(float[] value, int startIndex, int count, long offset) - { - BaseStream.Position = offset; - Write(value, startIndex, count); - } - public void Write(double value) - { - _buffer = EndianBitConverter.DoubleToBytes(value, Endianness); - WriteBytesFromBuffer(8); - } - public void Write(double value, long offset) - { - BaseStream.Position = offset; - Write(value); - } - public void Write(double[] value) - { - Write(value, 0, value.Length); - } - public void Write(double[] value, long offset) - { - BaseStream.Position = offset; - Write(value, 0, value.Length); - } - public void Write(double[] value, int startIndex, int count) - { - _buffer = EndianBitConverter.DoublesToBytes(value, startIndex, count, Endianness); - WriteBytesFromBuffer(count * 8); - } - public void Write(double[] value, int startIndex, int count, long offset) - { - BaseStream.Position = offset; - Write(value, startIndex, count); - } - public void Write(decimal value) - { - _buffer = EndianBitConverter.DecimalToBytes(value, Endianness); - WriteBytesFromBuffer(16); - } - public void Write(decimal value, long offset) - { - BaseStream.Position = offset; - Write(value); - } - public void Write(decimal[] value) - { - Write(value, 0, value.Length); - } - public void Write(decimal[] value, long offset) - { - BaseStream.Position = offset; - Write(value, 0, value.Length); - } - public void Write(decimal[] value, int startIndex, int count) - { - _buffer = EndianBitConverter.DecimalsToBytes(value, startIndex, count, Endianness); - WriteBytesFromBuffer(count * 16); - } - public void Write(decimal[] value, int startIndex, int count, long offset) - { - BaseStream.Position = offset; - Write(value, startIndex, count); - } + Stream = stream; + Endianness = endianness; + BooleanSize = booleanSize; + ASCII = ascii; + _buffer = new byte[BUF_LEN]; + } - // #13 - Handle "Enum" abstract type so we get the correct type in that case - // For example, writer.Write((Enum)Enum.Parse(enumType, value)) - // No "struct" restriction on writes - public void Write(TEnum value) where TEnum : Enum - { - Type underlyingType = Enum.GetUnderlyingType(value.GetType()); - switch (Type.GetTypeCode(underlyingType)) - { - case TypeCode.Byte: Write(Convert.ToByte(value)); break; - case TypeCode.SByte: Write(Convert.ToSByte(value)); break; - case TypeCode.Int16: Write(Convert.ToInt16(value)); break; - case TypeCode.UInt16: Write(Convert.ToUInt16(value)); break; - case TypeCode.Int32: Write(Convert.ToInt32(value)); break; - case TypeCode.UInt32: Write(Convert.ToUInt32(value)); break; - case TypeCode.Int64: Write(Convert.ToInt64(value)); break; - case TypeCode.UInt64: Write(Convert.ToUInt64(value)); break; - default: throw new ArgumentOutOfRangeException(nameof(underlyingType)); - } - } - public void Write(TEnum value, long offset) where TEnum : Enum - { - BaseStream.Position = offset; - Write(value); - } - public void Write(TEnum[] value) where TEnum : Enum - { - Write(value, 0, value.Length); - } - public void Write(TEnum[] value, long offset) where TEnum : Enum - { - BaseStream.Position = offset; - Write(value, 0, value.Length); - } - public void Write(TEnum[] value, int startIndex, int count) where TEnum : Enum - { - if (Utils.ValidateArrayIndexAndCount(value, startIndex, count)) - { - return; - } - for (int i = 0; i < count; i++) - { - Write(value[i + startIndex]); - } - } - public void Write(TEnum[] value, int startIndex, int count, long offset) where TEnum : Enum - { - BaseStream.Position = offset; - Write(value, startIndex, count); - } + protected delegate void WriteArrayMethod(Span dest, ReadOnlySpan src, Endianness endianness); + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected void WriteArray(ReadOnlySpan src, int elementSize, WriteArrayMethod writeArray) + { + int numBytes = src.Length * elementSize; + int start = 0; + while (numBytes != 0) + { + int consumeBytes = Math.Min(numBytes, BUF_LEN); - public void Write(DateTime value) - { - Write(value.ToBinary()); - } - public void Write(DateTime value, long offset) - { - BaseStream.Position = offset; - Write(value); - } - public void Write(DateTime[] value) - { - Write(value, 0, value.Length); - } - public void Write(DateTime[] value, long offset) - { - BaseStream.Position = offset; - Write(value, 0, value.Length); - } - public void Write(DateTime[] value, int startIndex, int count) - { - if (Utils.ValidateArrayIndexAndCount(value, startIndex, count)) - { - return; - } - for (int i = 0; i < count; i++) - { - Write(value[i + startIndex]); - } - } - public void Write(DateTime[] value, int startIndex, int count, long offset) - { - BaseStream.Position = offset; - Write(value, startIndex, count); - } + Span buffer = _buffer.AsSpan(0, consumeBytes); + writeArray(buffer, src.Slice(start, consumeBytes / elementSize), Endianness); + Stream.Write(buffer); - public void Write(IBinarySerializable obj) - { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - obj.Write(this); - } - public void Write(IBinarySerializable obj, long offset) - { - BaseStream.Position = offset; - Write(obj); - } - public void Write(object obj) - { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - if (obj is IBinarySerializable bs) - { - bs.Write(this); - return; - } + numBytes -= consumeBytes; + start += consumeBytes / elementSize; + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private void WriteBoolArray(ReadOnlySpan src, int elementSize) + { + int numBytes = src.Length * elementSize; + int start = 0; + while (numBytes != 0) + { + int consumeBytes = Math.Min(numBytes, BUF_LEN); - Type objType = obj.GetType(); - Utils.ThrowIfCannotReadWriteType(objType); + Span buffer = _buffer.AsSpan(0, consumeBytes); + EndianBinaryPrimitives.WriteBooleans(buffer, src.Slice(start, consumeBytes / elementSize), Endianness, elementSize); + Stream.Write(buffer); - // Get public non-static properties - foreach (PropertyInfo propertyInfo in objType.GetProperties(BindingFlags.Instance | BindingFlags.Public)) - { - if (Utils.AttributeValueOrDefault(propertyInfo, false)) - { - continue; // Skip properties with BinaryIgnoreAttribute - } + numBytes -= consumeBytes; + start += consumeBytes / elementSize; + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private void WriteASCIIArray(ReadOnlySpan src) + { + int numBytes = src.Length; + int start = 0; + while (numBytes != 0) + { + int consumeBytes = Math.Min(numBytes, BUF_LEN); - Type propertyType = propertyInfo.PropertyType; - object value = propertyInfo.GetValue(obj); + Span buffer = _buffer.AsSpan(0, consumeBytes); + for (int i = 0; i < consumeBytes; i++) + { + buffer[i] = (byte)src[i + start]; + } + Stream.Write(buffer); - if (propertyType.IsArray) - { - int arrayLength = Utils.GetArrayLength(obj, objType, propertyInfo); - if (arrayLength != 0) // Do not need to do anything for length 0 - { - // Get array type - Type elementType = propertyType.GetElementType(); - if (elementType.IsEnum) - { - elementType = Enum.GetUnderlyingType(elementType); - } - switch (Type.GetTypeCode(elementType)) - { - case TypeCode.Boolean: - { - BooleanSize booleanSize = Utils.AttributeValueOrDefault(propertyInfo, BooleanSize); - Write((bool[])value, 0, arrayLength, booleanSize); - break; - } - case TypeCode.Byte: Write((byte[])value, 0, arrayLength); break; - case TypeCode.SByte: Write((sbyte[])value, 0, arrayLength); break; - case TypeCode.Char: - { - Encoding encoding = Utils.AttributeValueOrDefault(propertyInfo, Encoding); - Write((char[])value, 0, arrayLength, encoding); - break; - } - case TypeCode.Int16: Write((short[])value, 0, arrayLength); break; - case TypeCode.UInt16: Write((ushort[])value, 0, arrayLength); break; - case TypeCode.Int32: Write((int[])value, 0, arrayLength); break; - case TypeCode.UInt32: Write((uint[])value, 0, arrayLength); break; - case TypeCode.Int64: Write((long[])value, 0, arrayLength); break; - case TypeCode.UInt64: Write((ulong[])value, 0, arrayLength); break; - case TypeCode.Single: Write((float[])value, 0, arrayLength); break; - case TypeCode.Double: Write((double[])value, 0, arrayLength); break; - case TypeCode.Decimal: Write((decimal[])value, 0, arrayLength); break; - case TypeCode.DateTime: Write((DateTime[])value, 0, arrayLength); break; - case TypeCode.String: - { - Utils.GetStringLength(obj, objType, propertyInfo, false, out bool? nullTerminated, out int stringLength); - Encoding encoding = Utils.AttributeValueOrDefault(propertyInfo, Encoding); - if (nullTerminated.HasValue) - { - Write((string[])value, 0, arrayLength, nullTerminated.Value, encoding); - } - else - { - Write((string[])value, 0, arrayLength, stringLength, encoding); - } - break; - } - case TypeCode.Object: - { - if (typeof(IBinarySerializable).IsAssignableFrom(elementType)) - { - for (int i = 0; i < arrayLength; i++) - { - var serializable = (IBinarySerializable)((Array)value).GetValue(i); - serializable.Write(this); - } - } - else // Element's type is not supported so try to write the array's objects - { - for (int i = 0; i < arrayLength; i++) - { - object elementObj = ((Array)value).GetValue(i); - Write(elementObj); - } - } - break; - } - default: throw new ArgumentOutOfRangeException(nameof(elementType)); - } - } - } - else - { - if (propertyType.IsEnum) - { - propertyType = Enum.GetUnderlyingType(propertyType); - } - switch (Type.GetTypeCode(propertyType)) - { - case TypeCode.Boolean: - { - BooleanSize booleanSize = Utils.AttributeValueOrDefault(propertyInfo, BooleanSize); - Write((bool)value, booleanSize); - break; - } - case TypeCode.Byte: Write((byte)value); break; - case TypeCode.SByte: Write((sbyte)value); break; - case TypeCode.Char: - { - Encoding encoding = Utils.AttributeValueOrDefault(propertyInfo, Encoding); - Write((char)value, encoding); - break; - } - case TypeCode.Int16: Write((short)value); break; - case TypeCode.UInt16: Write((ushort)value); break; - case TypeCode.Int32: Write((int)value); break; - case TypeCode.UInt32: Write((uint)value); break; - case TypeCode.Int64: Write((long)value); break; - case TypeCode.UInt64: Write((ulong)value); break; - case TypeCode.Single: Write((float)value); break; - case TypeCode.Double: Write((double)value); break; - case TypeCode.Decimal: Write((decimal)value); break; - case TypeCode.DateTime: Write((DateTime)value); break; - case TypeCode.String: - { - Utils.GetStringLength(obj, objType, propertyInfo, false, out bool? nullTerminated, out int stringLength); - Encoding encoding = Utils.AttributeValueOrDefault(propertyInfo, Encoding); - if (nullTerminated.HasValue) - { - Write((string)value, nullTerminated.Value, encoding); - } - else - { - Write((string)value, stringLength, encoding); - } - break; - } - case TypeCode.Object: - { - if (typeof(IBinarySerializable).IsAssignableFrom(propertyType)) - { - ((IBinarySerializable)value).Write(this); - } - else // property's type is not supported so try to write the object - { - Write(value); - } - break; - } - default: throw new ArgumentOutOfRangeException(nameof(propertyType)); - } - } - } - } - public void Write(object obj, long offset) - { - BaseStream.Position = offset; - Write(obj); - } - } + numBytes -= consumeBytes; + start += consumeBytes; + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void WriteZeroes(int numBytes) + { + bool cleared = false; + while (numBytes != 0) + { + int consumeBytes = Math.Min(numBytes, BUF_LEN); + + Span buffer = _buffer.AsSpan(0, consumeBytes); + if (!cleared) + { + buffer.Clear(); + cleared = true; + } + Stream.Write(buffer); + + numBytes -= consumeBytes; + } + } + + public void WriteSByte(sbyte value) + { + Span buffer = _buffer.AsSpan(0, 1); + buffer[0] = (byte)value; + Stream.Write(buffer); + } + public void WriteSBytes(ReadOnlySpan values) + { + ReadOnlySpan buffer = MemoryMarshal.Cast(values); + Stream.Write(buffer); + } + public void WriteByte(byte value) + { + Span buffer = _buffer.AsSpan(0, 1); + buffer[0] = value; + Stream.Write(buffer); + } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void WriteBytes(ReadOnlySpan values) + { + Stream.Write(values); + } + public void WriteInt16(short value) + { + Span buffer = _buffer.AsSpan(0, 2); + EndianBinaryPrimitives.WriteInt16(buffer, value, Endianness); + Stream.Write(buffer); + } + public void WriteInt16s(ReadOnlySpan values) + { + WriteArray(values, 2, EndianBinaryPrimitives.WriteInt16s); + } + public void WriteUInt16(ushort value) + { + Span buffer = _buffer.AsSpan(0, 2); + EndianBinaryPrimitives.WriteUInt16(buffer, value, Endianness); + Stream.Write(buffer); + } + public void WriteUInt16s(ReadOnlySpan values) + { + WriteArray(values, 2, EndianBinaryPrimitives.WriteUInt16s); + } + public void WriteInt32(int value) + { + Span buffer = _buffer.AsSpan(0, 4); + EndianBinaryPrimitives.WriteInt32(buffer, value, Endianness); + Stream.Write(buffer); + } + public void WriteInt32s(ReadOnlySpan values) + { + WriteArray(values, 4, EndianBinaryPrimitives.WriteInt32s); + } + public void WriteUInt32(uint value) + { + Span buffer = _buffer.AsSpan(0, 4); + EndianBinaryPrimitives.WriteUInt32(buffer, value, Endianness); + Stream.Write(buffer); + } + public void WriteUInt32s(ReadOnlySpan values) + { + WriteArray(values, 4, EndianBinaryPrimitives.WriteUInt32s); + } + public void WriteInt64(long value) + { + Span buffer = _buffer.AsSpan(0, 8); + EndianBinaryPrimitives.WriteInt64(buffer, value, Endianness); + Stream.Write(buffer); + } + public void WriteInt64s(ReadOnlySpan values) + { + WriteArray(values, 8, EndianBinaryPrimitives.WriteInt64s); + } + public void WriteUInt64(ulong value) + { + Span buffer = _buffer.AsSpan(0, 8); + EndianBinaryPrimitives.WriteUInt64(buffer, value, Endianness); + Stream.Write(buffer); + } + public void WriteUInt64s(ReadOnlySpan values) + { + WriteArray(values, 8, EndianBinaryPrimitives.WriteUInt64s); + } + + public void WriteHalf(Half value) + { + Span buffer = _buffer.AsSpan(0, 2); + EndianBinaryPrimitives.WriteHalf(buffer, value, Endianness); + Stream.Write(buffer); + } + public void WriteHalves(ReadOnlySpan values) + { + WriteArray(values, 2, EndianBinaryPrimitives.WriteHalves); + } + public void WriteSingle(float value) + { + Span buffer = _buffer.AsSpan(0, 4); + EndianBinaryPrimitives.WriteSingle(buffer, value, Endianness); + Stream.Write(buffer); + } + public void WriteSingles(ReadOnlySpan values) + { + WriteArray(values, 4, EndianBinaryPrimitives.WriteSingles); + } + public void WriteDouble(double value) + { + Span buffer = _buffer.AsSpan(0, 8); + EndianBinaryPrimitives.WriteDouble(buffer, value, Endianness); + Stream.Write(buffer); + } + public void WriteDoubles(ReadOnlySpan values) + { + WriteArray(values, 8, EndianBinaryPrimitives.WriteDoubles); + } + public void WriteDecimal(in decimal value) + { + Span buffer = _buffer.AsSpan(0, 16); + EndianBinaryPrimitives.WriteDecimal(buffer, value, Endianness); + Stream.Write(buffer); + } + public void WriteDecimals(ReadOnlySpan values) + { + WriteArray(values, 16, EndianBinaryPrimitives.WriteDecimals); + } + + public void WriteBoolean(bool value) + { + int elementSize = EndianBinaryPrimitives.GetBytesForBooleanSize(BooleanSize); + Span buffer = _buffer.AsSpan(0, elementSize); + EndianBinaryPrimitives.WriteBoolean(buffer, value, Endianness, elementSize); + Stream.Write(buffer); + } + public void WriteBooleans(ReadOnlySpan values) + { + int elementSize = EndianBinaryPrimitives.GetBytesForBooleanSize(BooleanSize); + WriteBoolArray(values, elementSize); + } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void WriteEnum(TEnum value) where TEnum : unmanaged, Enum + { + int size = Unsafe.SizeOf(); + if (size == 1) + { + WriteByte(Unsafe.As(ref value)); + } + else if (size == 2) + { + WriteUInt16(Unsafe.As(ref value)); + } + else if (size == 4) + { + WriteUInt32(Unsafe.As(ref value)); + } + else + { + WriteUInt64(Unsafe.As(ref value)); + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void WriteEnums(ReadOnlySpan values) where TEnum : unmanaged, Enum + { + int size = Unsafe.SizeOf(); + if (size == 1) + { + WriteBytes(MemoryMarshal.Cast(values)); + } + else if (size == 2) + { + WriteUInt16s(MemoryMarshal.Cast(values)); + } + else if (size == 4) + { + WriteUInt32s(MemoryMarshal.Cast(values)); + } + else + { + WriteUInt64s(MemoryMarshal.Cast(values)); + } + } + // #13 - Allow writing the abstract "Enum" type + // For example, writer.Write((Enum)Enum.Parse(enumType, value)) + // Don't allow writing Enum[] though, since there is no way to read that + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void WriteEnum(Enum value) + { + Type underlyingType = Enum.GetUnderlyingType(value.GetType()); + ref byte data = ref Utils.GetRawData(value); // Use memory tricks to skip object header of generic Enum + switch (Type.GetTypeCode(underlyingType)) + { + case TypeCode.SByte: + case TypeCode.Byte: WriteByte(data); break; + case TypeCode.Int16: + case TypeCode.UInt16: WriteUInt16(Unsafe.As(ref data)); break; + case TypeCode.Int32: + case TypeCode.UInt32: WriteUInt32(Unsafe.As(ref data)); break; + case TypeCode.Int64: + case TypeCode.UInt64: WriteUInt64(Unsafe.As(ref data)); break; + } + } + + public void WriteDateTime(DateTime value) + { + Span buffer = _buffer.AsSpan(0, 8); + EndianBinaryPrimitives.WriteDateTime(buffer, value, Endianness); + Stream.Write(buffer); + } + public void WriteDateTimes(ReadOnlySpan values) + { + WriteArray(values, 8, EndianBinaryPrimitives.WriteDateTimes); + } + public void WriteDateOnly(DateOnly value) + { + Span buffer = _buffer.AsSpan(0, 4); + EndianBinaryPrimitives.WriteDateOnly(buffer, value, Endianness); + Stream.Write(buffer); + } + public void WriteDateOnlys(ReadOnlySpan values) + { + WriteArray(values, 4, EndianBinaryPrimitives.WriteDateOnlys); + } + public void WriteTimeOnly(TimeOnly value) + { + Span buffer = _buffer.AsSpan(0, 8); + EndianBinaryPrimitives.WriteTimeOnly(buffer, value, Endianness); + Stream.Write(buffer); + } + public void WriteTimeOnlys(ReadOnlySpan values) + { + WriteArray(values, 8, EndianBinaryPrimitives.WriteTimeOnlys); + } + + public void WriteVector2(Vector2 value) + { + Span buffer = _buffer.AsSpan(0, 8); + EndianBinaryPrimitives.WriteVector2(buffer, value, Endianness); + Stream.Write(buffer); + } + public void WriteVector2s(ReadOnlySpan values) + { + WriteArray(values, 8, EndianBinaryPrimitives.WriteVector2s); + } + public void WriteVector3(Vector3 value) + { + Span buffer = _buffer.AsSpan(0, 12); + EndianBinaryPrimitives.WriteVector3(buffer, value, Endianness); + Stream.Write(buffer); + } + public void WriteVector3s(ReadOnlySpan values) + { + WriteArray(values, 12, EndianBinaryPrimitives.WriteVector3s); + } + public void WriteVector4(in Vector4 value) + { + Span buffer = _buffer.AsSpan(0, 16); + EndianBinaryPrimitives.WriteVector4(buffer, value, Endianness); + Stream.Write(buffer); + } + public void WriteVector4s(ReadOnlySpan values) + { + WriteArray(values, 16, EndianBinaryPrimitives.WriteVector4s); + } + public void WriteQuaternion(in Quaternion value) + { + Span buffer = _buffer.AsSpan(0, 16); + EndianBinaryPrimitives.WriteQuaternion(buffer, value, Endianness); + Stream.Write(buffer); + } + public void WriteQuaternions(ReadOnlySpan values) + { + WriteArray(values, 16, EndianBinaryPrimitives.WriteQuaternions); + } + public void WriteMatrix4x4(in Matrix4x4 value) + { + Span buffer = _buffer.AsSpan(0, 64); + EndianBinaryPrimitives.WriteMatrix4x4(buffer, value, Endianness); + Stream.Write(buffer); + } + public void WriteMatrix4x4s(ReadOnlySpan values) + { + WriteArray(values, 64, EndianBinaryPrimitives.WriteMatrix4x4s); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void WriteChar(char v) + { + if (ASCII) + { + WriteByte((byte)v); + } + else + { + WriteUInt16(v); + } + } + public void WriteChars(ReadOnlySpan values) + { + if (values.Length == 0) + { + return; + } + if (ASCII) + { + WriteASCIIArray(values); + } + else + { + ReadOnlySpan buffer = MemoryMarshal.Cast(values); + WriteUInt16s(buffer); + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void WriteChars_Count(ReadOnlySpan values, int charCount) + { + if (charCount == 0) + { + return; + } + if (values.Length >= charCount) + { + WriteChars(values.Slice(0, charCount)); + } + else // Less + { + WriteChars(values); + + // Append '\0' + int emptyBytes = charCount - values.Length; + if (!ASCII) + { + emptyBytes *= 2; + } + WriteZeroes(emptyBytes); + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void WriteChars_NullTerminated(ReadOnlySpan values) + { + WriteChars(values); + WriteChar('\0'); + } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void WriteStrings(ReadOnlySpan values) + { + for (int i = 0; i < values.Length; i++) + { + WriteChars(values[i]); + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void WriteStrings_Count(ReadOnlySpan values, int charCount) + { + for (int i = 0; i < values.Length; i++) + { + WriteChars_Count(values[i], charCount); + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void WriteStrings_NullTerminated(ReadOnlySpan values) + { + for (int i = 0; i < values.Length; i++) + { + WriteChars_NullTerminated(values[i]); + } + } + } } diff --git a/Source/EndianBinaryWriter_Reflection.cs b/Source/EndianBinaryWriter_Reflection.cs new file mode 100644 index 0000000..1967630 --- /dev/null +++ b/Source/EndianBinaryWriter_Reflection.cs @@ -0,0 +1,240 @@ +using System; +using System.Numerics; +using System.Reflection; + +namespace Kermalis.EndianBinaryIO +{ + public partial class EndianBinaryWriter + { + public void WriteObject(object obj) + { + WriteObject(obj, false); + } + private void WriteObject(object obj, bool skipCheck) + { + if (!skipCheck && TryWriteSupportedObject(obj)) + { + return; + } + + Type objType = obj.GetType(); + Utils.ThrowIfCannotReadWriteType(objType); + + // Get public non-static properties + foreach (PropertyInfo propertyInfo in objType.GetProperties(BindingFlags.Instance | BindingFlags.Public)) + { + if (Utils.AttributeValueOrDefault(propertyInfo, false)) + { + continue; // Skip properties with BinaryIgnoreAttribute + } + + object? value = propertyInfo.GetValue(obj); + if (value is null) + { + throw new NullReferenceException($"Null property in {objType.FullName} ({propertyInfo.Name})"); + } + if (value is Array arr) + { + int arrayLength = Utils.GetArrayLength(obj, objType, propertyInfo); + if (arrayLength != 0) // Do not need to do anything for length 0 + { + if (arrayLength > arr.Length) + { + throw new InvalidOperationException($"Expected too many elements for array in {objType.FullName} (expected {arrayLength}, have {arr.Length})."); + } + WritePropertyValue_Array(obj, objType, propertyInfo, arr, arrayLength); + } + } + else + { + WritePropertyValue_NonArray(obj, objType, propertyInfo, value); + } + } + } + + private bool TryWriteSupportedObject(object obj) + { + return obj is Array arr ? TryWriteSupportedObject_Array(arr, arr.Length) : TryWriteSupportedObject_NonArray(obj); + } + private bool TryWriteSupportedObject_NonArray(object obj) + { + switch (obj) + { + case sbyte v: WriteSByte(v); return true; + case byte v: WriteByte(v); return true; + case short v: WriteInt16(v); return true; + case ushort v: WriteUInt16(v); return true; + case int v: WriteInt32(v); return true; + case uint v: WriteUInt32(v); return true; + case long v: WriteInt64(v); return true; + case ulong v: WriteUInt64(v); return true; + case Half v: WriteHalf(v); return true; + case float v: WriteSingle(v); return true; + case double v: WriteDouble(v); return true; + case decimal v: WriteDecimal(v); return true; + case bool v: WriteBoolean(v); return true; + case Enum v: WriteEnum(v); return true; + case DateTime v: WriteDateTime(v); return true; + case DateOnly v: WriteDateOnly(v); return true; + case TimeOnly v: WriteTimeOnly(v); return true; + case Vector2 v: WriteVector2(v); return true; + case Vector3 v: WriteVector3(v); return true; + case Vector4 v: WriteVector4(v); return true; + case Quaternion v: WriteQuaternion(v); return true; + case Matrix4x4 v: WriteMatrix4x4(v); return true; + case char v: WriteChar(v); return true; + case string v: WriteChars_NullTerminated(v); return true; + case IBinarySerializable v: v.Write(this); return true; + } + return false; + } + private bool TryWriteSupportedObject_Array(Array obj, int length) + { + Type elementType = obj.GetType().GetElementType()!; + if (elementType.IsEnum) + { + for (int i = 0; i < length; i++) + { + WriteObject(obj.GetValue(i)!); + } + return true; + } + + switch (obj) + { + case sbyte[] v: WriteSBytes(v.AsSpan(0, length)); return true; + case byte[] v: WriteBytes(v.AsSpan(0, length)); return true; + case short[] v: WriteInt16s(v.AsSpan(0, length)); return true; + case ushort[] v: WriteUInt16s(v.AsSpan(0, length)); return true; + case int[] v: WriteInt32s(v.AsSpan(0, length)); return true; + case uint[] v: WriteUInt32s(v.AsSpan(0, length)); return true; + case long[] v: WriteInt64s(v.AsSpan(0, length)); return true; + case ulong[] v: WriteUInt64s(v.AsSpan(0, length)); return true; + case Half[] v: WriteHalves(v.AsSpan(0, length)); return true; + case float[] v: WriteSingles(v.AsSpan(0, length)); return true; + case double[] v: WriteDoubles(v.AsSpan(0, length)); return true; + case decimal[] v: WriteDecimals(v.AsSpan(0, length)); return true; + case bool[] v: WriteBooleans(v.AsSpan(0, length)); return true; + case DateTime[] v: WriteDateTimes(v.AsSpan(0, length)); return true; + case DateOnly[] v: WriteDateOnlys(v.AsSpan(0, length)); return true; + case TimeOnly[] v: WriteTimeOnlys(v.AsSpan(0, length)); return true; + case Vector2[] v: WriteVector2s(v.AsSpan(0, length)); return true; + case Vector3[] v: WriteVector3s(v.AsSpan(0, length)); return true; + case Vector4[] v: WriteVector4s(v.AsSpan(0, length)); return true; + case Quaternion[] v: WriteQuaternions(v.AsSpan(0, length)); return true; + case Matrix4x4[] v: WriteMatrix4x4s(v.AsSpan(0, length)); return true; + case char[] v: WriteChars(v.AsSpan(0, length)); return true; + case string[] v: WriteStrings_NullTerminated(v.AsSpan(0, length)); return true; + } + return false; + } + + private void WritePropertyValue_NonArray(object obj, Type objType, PropertyInfo propertyInfo, object value) + { + switch (value) + { + case bool v: + { + BooleanSize old = BooleanSize; + BooleanSize = Utils.AttributeValueOrDefault(propertyInfo, old); + WriteBoolean(v); + BooleanSize = old; + break; + } + case char v: + { + bool old = ASCII; + ASCII = Utils.AttributeValueOrDefault(propertyInfo, old); + WriteChar(v); + ASCII = old; + break; + } + case string v: + { + Utils.GetStringLength(obj, objType, propertyInfo, false, out bool? nullTerminated, out int stringLength); + + bool old = ASCII; + ASCII = Utils.AttributeValueOrDefault(propertyInfo, old); + if (nullTerminated == true) + { + WriteChars_NullTerminated(v); + } + else if (nullTerminated == false) + { + WriteChars(v); + } + else + { + WriteChars_Count(v, stringLength); + } + ASCII = old; + break; + } + default: + { + WriteObject(value); + break; + } + } + } + private void WritePropertyValue_Array(object obj, Type objType, PropertyInfo propertyInfo, Array value, int arrayLength) + { + switch (value) + { + case bool[] v: + { + BooleanSize old = BooleanSize; + BooleanSize = Utils.AttributeValueOrDefault(propertyInfo, old); + WriteBooleans(v.AsSpan(0, arrayLength)); + BooleanSize = old; + break; + } + case char[] v: + { + bool old = ASCII; + ASCII = Utils.AttributeValueOrDefault(propertyInfo, old); + WriteChars(v.AsSpan(0, arrayLength)); + ASCII = old; + break; + } + case string[] v: + { + Utils.GetStringLength(obj, objType, propertyInfo, false, out bool? nullTerminated, out int stringLength); + + bool old = ASCII; + ASCII = Utils.AttributeValueOrDefault(propertyInfo, old); + if (nullTerminated == true) + { + WriteStrings_NullTerminated(v.AsSpan(0, arrayLength)); + } + else if (nullTerminated == false) + { + WriteStrings(v.AsSpan(0, arrayLength)); + } + else + { + WriteStrings_Count(v.AsSpan(0, arrayLength), stringLength); + } + ASCII = old; + break; + } + default: + { + if (!TryWriteSupportedObject_Array(value, arrayLength)) + { + for (int i = 0; i < arrayLength; i++) + { + object? val = value.GetValue(i); + if (val is null) + { + throw new NullReferenceException("Array element was null."); + } + WriteObject(val, true); + } + } + break; + } + } + } + } +} diff --git a/Source/EndianBitConverter.cs b/Source/EndianBitConverter.cs deleted file mode 100644 index cb00743..0000000 --- a/Source/EndianBitConverter.cs +++ /dev/null @@ -1,587 +0,0 @@ -using System; - -namespace Kermalis.EndianBinaryIO -{ - public static class EndianBitConverter - { - public static Endianness SystemEndianness { get; } = BitConverter.IsLittleEndian ? Endianness.LittleEndian : Endianness.BigEndian; - - public static unsafe byte[] Int16ToBytes(short value, Endianness targetEndianness) - { - byte[] bytes = new byte[2]; - fixed (byte* b = bytes) - { - *(short*)b = value; - } - if (SystemEndianness != targetEndianness) - { - FlipPrimitives(bytes, 0, 1, 2); - } - return bytes; - } - public static unsafe byte[] Int16sToBytes(short[] value, int startIndex, int count, Endianness targetEndianness) - { - if (Utils.ValidateArrayIndexAndCount(value, startIndex, count)) - { - return Array.Empty(); - } - if (!Utils.ValidateReadArraySize(count, out byte[] array)) - { - array = new byte[2 * count]; - fixed (byte* b = array) - { - for (int i = 0; i < count; i++) - { - ((short*)b)[i] = value[startIndex + i]; - } - } - if (SystemEndianness != targetEndianness) - { - FlipPrimitives(array, 0, count, 2); - } - } - return array; - } - public static unsafe byte[] UInt16sToBytes(ushort[] value, int startIndex, int count, Endianness targetEndianness) - { - if (Utils.ValidateArrayIndexAndCount(value, startIndex, count)) - { - return Array.Empty(); - } - if (!Utils.ValidateReadArraySize(count, out byte[] array)) - { - array = new byte[2 * count]; - fixed (byte* b = array) - { - for (int i = 0; i < count; i++) - { - ((ushort*)b)[i] = value[startIndex + i]; - } - } - if (SystemEndianness != targetEndianness) - { - FlipPrimitives(array, 0, count, 2); - } - } - return array; - } - public static unsafe byte[] Int32ToBytes(int value, Endianness targetEndianness) - { - byte[] bytes = new byte[4]; - fixed (byte* b = bytes) - { - *(int*)b = value; - } - if (SystemEndianness != targetEndianness) - { - FlipPrimitives(bytes, 0, 1, 4); - } - return bytes; - } - public static unsafe byte[] Int32sToBytes(int[] value, int startIndex, int count, Endianness targetEndianness) - { - if (Utils.ValidateArrayIndexAndCount(value, startIndex, count)) - { - return Array.Empty(); - } - if (!Utils.ValidateReadArraySize(count, out byte[] array)) - { - array = new byte[4 * count]; - fixed (byte* b = array) - { - for (int i = 0; i < count; i++) - { - ((int*)b)[i] = value[startIndex + i]; - } - } - if (SystemEndianness != targetEndianness) - { - FlipPrimitives(array, 0, count, 4); - } - } - return array; - } - public static unsafe byte[] UInt32sToBytes(uint[] value, int startIndex, int count, Endianness targetEndianness) - { - if (Utils.ValidateArrayIndexAndCount(value, startIndex, count)) - { - return Array.Empty(); - } - if (!Utils.ValidateReadArraySize(count, out byte[] array)) - { - array = new byte[4 * count]; - fixed (byte* b = array) - { - for (int i = 0; i < count; i++) - { - ((uint*)b)[i] = value[startIndex + i]; - } - } - if (SystemEndianness != targetEndianness) - { - FlipPrimitives(array, 0, count, 4); - } - } - return array; - } - public static unsafe byte[] Int64ToBytes(long value, Endianness targetEndianness) - { - byte[] bytes = new byte[8]; - fixed (byte* b = bytes) - { - *(long*)b = value; - } - if (SystemEndianness != targetEndianness) - { - FlipPrimitives(bytes, 0, 1, 8); - } - return bytes; - } - public static unsafe byte[] Int64sToBytes(long[] value, int startIndex, int count, Endianness targetEndianness) - { - if (Utils.ValidateArrayIndexAndCount(value, startIndex, count)) - { - return Array.Empty(); - } - if (!Utils.ValidateReadArraySize(count, out byte[] array)) - { - array = new byte[8 * count]; - fixed (byte* b = array) - { - for (int i = 0; i < count; i++) - { - ((long*)b)[i] = value[startIndex + i]; - } - } - if (SystemEndianness != targetEndianness) - { - FlipPrimitives(array, 0, count, 8); - } - } - return array; - } - public static unsafe byte[] UInt64sToBytes(ulong[] value, int startIndex, int count, Endianness targetEndianness) - { - if (Utils.ValidateArrayIndexAndCount(value, startIndex, count)) - { - return Array.Empty(); - } - if (!Utils.ValidateReadArraySize(count, out byte[] array)) - { - array = new byte[8 * count]; - fixed (byte* b = array) - { - for (int i = 0; i < count; i++) - { - ((ulong*)b)[i] = value[startIndex + i]; - } - } - if (SystemEndianness != targetEndianness) - { - FlipPrimitives(array, 0, count, 8); - } - } - return array; - } - public static unsafe byte[] SingleToBytes(float value, Endianness targetEndianness) - { - byte[] bytes = new byte[4]; - fixed (byte* b = bytes) - { - *(float*)b = value; - } - if (SystemEndianness != targetEndianness) - { - FlipPrimitives(bytes, 0, 1, 4); - } - return bytes; - } - public static unsafe byte[] SinglesToBytes(float[] value, int startIndex, int count, Endianness targetEndianness) - { - if (Utils.ValidateArrayIndexAndCount(value, startIndex, count)) - { - return Array.Empty(); - } - if (!Utils.ValidateReadArraySize(count, out byte[] array)) - { - array = new byte[4 * count]; - fixed (byte* b = array) - { - for (int i = 0; i < count; i++) - { - ((float*)b)[i] = value[startIndex + i]; - } - } - if (SystemEndianness != targetEndianness) - { - FlipPrimitives(array, 0, count, 4); - } - } - return array; - } - public static unsafe byte[] DoubleToBytes(double value, Endianness targetEndianness) - { - byte[] bytes = new byte[8]; - fixed (byte* b = bytes) - { - *(double*)b = value; - } - if (SystemEndianness != targetEndianness) - { - FlipPrimitives(bytes, 0, 1, 8); - } - return bytes; - } - public static unsafe byte[] DoublesToBytes(double[] value, int startIndex, int count, Endianness targetEndianness) - { - if (Utils.ValidateArrayIndexAndCount(value, startIndex, count)) - { - return Array.Empty(); - } - if (!Utils.ValidateReadArraySize(count, out byte[] array)) - { - array = new byte[8 * count]; - fixed (byte* b = array) - { - for (int i = 0; i < count; i++) - { - ((double*)b)[i] = value[startIndex + i]; - } - } - if (SystemEndianness != targetEndianness) - { - FlipPrimitives(array, 0, count, 8); - } - } - return array; - } - public static unsafe byte[] DecimalToBytes(decimal value, Endianness targetEndianness) - { - byte[] bytes = new byte[16]; - fixed (byte* b = bytes) - { - *(decimal*)b = value; - } - if (SystemEndianness != targetEndianness) - { - FlipPrimitives(bytes, 0, 1, 16); - } - return bytes; - } - public static unsafe byte[] DecimalsToBytes(decimal[] value, int startIndex, int count, Endianness targetEndianness) - { - if (Utils.ValidateArrayIndexAndCount(value, startIndex, count)) - { - return Array.Empty(); - } - if (!Utils.ValidateReadArraySize(count, out byte[] array)) - { - array = new byte[16 * count]; - fixed (byte* b = array) - { - for (int i = 0; i < count; i++) - { - ((decimal*)b)[i] = value[startIndex + i]; - } - } - if (SystemEndianness != targetEndianness) - { - FlipPrimitives(array, 0, count, 16); - } - } - return array; - } - - public static unsafe short BytesToInt16(byte[] value, int startIndex, Endianness sourceEndianness) - { - if (SystemEndianness != sourceEndianness) - { - FlipPrimitives(value, startIndex, 1, 2); - } - fixed (byte* b = &value[startIndex]) - { - return *(short*)b; - } - } - public static unsafe short[] BytesToInt16s(byte[] value, int startIndex, int count, Endianness sourceEndianness) - { - if (Utils.ValidateArrayIndexAndCount(value, startIndex, count * 2)) - { - return Array.Empty(); - } - if (!Utils.ValidateReadArraySize(count, out short[] array)) - { - if (SystemEndianness != sourceEndianness) - { - FlipPrimitives(value, startIndex, count, 2); - } - array = new short[count]; - fixed (byte* b = &value[startIndex]) - { - for (int i = 0; i < count; i++) - { - array[i] = ((short*)b)[i]; - } - } - } - return array; - } - public static unsafe ushort[] BytesToUInt16s(byte[] value, int startIndex, int count, Endianness sourceEndianness) - { - if (Utils.ValidateArrayIndexAndCount(value, startIndex, count * 2)) - { - return Array.Empty(); - } - if (!Utils.ValidateReadArraySize(count, out ushort[] array)) - { - if (SystemEndianness != sourceEndianness) - { - FlipPrimitives(value, startIndex, count, 2); - } - array = new ushort[count]; - fixed (byte* b = &value[startIndex]) - { - for (int i = 0; i < count; i++) - { - array[i] = ((ushort*)b)[i]; - } - } - } - return array; - } - public static unsafe int BytesToInt32(byte[] value, int startIndex, Endianness sourceEndianness) - { - if (SystemEndianness != sourceEndianness) - { - FlipPrimitives(value, startIndex, 1, 4); - } - fixed (byte* b = &value[startIndex]) - { - return *(int*)b; - } - } - public static unsafe int[] BytesToInt32s(byte[] value, int startIndex, int count, Endianness sourceEndianness) - { - if (Utils.ValidateArrayIndexAndCount(value, startIndex, count * 4)) - { - return Array.Empty(); - } - if (!Utils.ValidateReadArraySize(count, out int[] array)) - { - if (SystemEndianness != sourceEndianness) - { - FlipPrimitives(value, startIndex, count, 4); - } - array = new int[count]; - fixed (byte* b = &value[startIndex]) - { - for (int i = 0; i < count; i++) - { - array[i] = ((int*)b)[i]; - } - } - } - return array; - } - public static unsafe uint[] BytesToUInt32s(byte[] value, int startIndex, int count, Endianness sourceEndianness) - { - if (Utils.ValidateArrayIndexAndCount(value, startIndex, count * 4)) - { - return Array.Empty(); - } - if (!Utils.ValidateReadArraySize(count, out uint[] array)) - { - if (SystemEndianness != sourceEndianness) - { - FlipPrimitives(value, startIndex, count, 4); - } - array = new uint[count]; - fixed (byte* b = &value[startIndex]) - { - for (int i = 0; i < count; i++) - { - array[i] = ((uint*)b)[i]; - } - } - } - return array; - } - public static unsafe long BytesToInt64(byte[] value, int startIndex, Endianness sourceEndianness) - { - if (SystemEndianness != sourceEndianness) - { - FlipPrimitives(value, startIndex, 1, 8); - } - fixed (byte* b = &value[startIndex]) - { - return *(long*)b; - } - } - public static unsafe long[] BytesToInt64s(byte[] value, int startIndex, int count, Endianness sourceEndianness) - { - if (Utils.ValidateArrayIndexAndCount(value, startIndex, count * 8)) - { - return Array.Empty(); - } - if (!Utils.ValidateReadArraySize(count, out long[] array)) - { - if (SystemEndianness != sourceEndianness) - { - FlipPrimitives(value, startIndex, count, 8); - } - array = new long[count]; - fixed (byte* b = &value[startIndex]) - { - for (int i = 0; i < count; i++) - { - array[i] = ((long*)b)[i]; - } - } - } - return array; - } - public static unsafe ulong[] BytesToUInt64s(byte[] value, int startIndex, int count, Endianness sourceEndianness) - { - if (Utils.ValidateArrayIndexAndCount(value, startIndex, count * 8)) - { - return Array.Empty(); - } - if (!Utils.ValidateReadArraySize(count, out ulong[] array)) - { - if (SystemEndianness != sourceEndianness) - { - FlipPrimitives(value, startIndex, count, 8); - } - array = new ulong[count]; - fixed (byte* b = &value[startIndex]) - { - for (int i = 0; i < count; i++) - { - array[i] = ((ulong*)b)[i]; - } - } - } - return array; - } - public static unsafe float BytesToSingle(byte[] value, int startIndex, Endianness sourceEndianness) - { - if (SystemEndianness != sourceEndianness) - { - FlipPrimitives(value, startIndex, 1, 4); - } - fixed (byte* b = &value[startIndex]) - { - return *(float*)b; - } - } - public static unsafe float[] BytesToSingles(byte[] value, int startIndex, int count, Endianness sourceEndianness) - { - if (Utils.ValidateArrayIndexAndCount(value, startIndex, count * 4)) - { - return Array.Empty(); - } - if (!Utils.ValidateReadArraySize(count, out float[] array)) - { - if (SystemEndianness != sourceEndianness) - { - FlipPrimitives(value, startIndex, count, 4); - } - array = new float[count]; - fixed (byte* b = &value[startIndex]) - { - for (int i = 0; i < count; i++) - { - array[i] = ((float*)b)[i]; - } - } - } - return array; - } - public static unsafe double BytesToDouble(byte[] value, int startIndex, Endianness sourceEndianness) - { - if (SystemEndianness != sourceEndianness) - { - FlipPrimitives(value, startIndex, 1, 8); - } - fixed (byte* b = &value[startIndex]) - { - return *(double*)b; - } - } - public static unsafe double[] BytesToDoubles(byte[] value, int startIndex, int count, Endianness sourceEndianness) - { - if (Utils.ValidateArrayIndexAndCount(value, startIndex, count * 8)) - { - return Array.Empty(); - } - if (!Utils.ValidateReadArraySize(count, out double[] array)) - { - if (SystemEndianness != sourceEndianness) - { - FlipPrimitives(value, startIndex, count, 8); - } - array = new double[count]; - fixed (byte* b = &value[startIndex]) - { - for (int i = 0; i < count; i++) - { - array[i] = ((double*)b)[i]; - } - } - } - return array; - } - public static unsafe decimal BytesToDecimal(byte[] value, int startIndex, Endianness sourceEndianness) - { - if (SystemEndianness != sourceEndianness) - { - FlipPrimitives(value, startIndex, 1, 16); - } - fixed (byte* b = &value[startIndex]) - { - return *(decimal*)b; - } - } - public static unsafe decimal[] BytesToDecimals(byte[] value, int startIndex, int count, Endianness sourceEndianness) - { - if (Utils.ValidateArrayIndexAndCount(value, startIndex, count * 16)) - { - return Array.Empty(); - } - if (!Utils.ValidateReadArraySize(count, out decimal[] array)) - { - if (SystemEndianness != sourceEndianness) - { - FlipPrimitives(value, startIndex, count, 16); - } - array = new decimal[count]; - fixed (byte* b = &value[startIndex]) - { - for (int i = 0; i < count; i++) - { - array[i] = ((decimal*)b)[i]; - } - } - } - return array; - } - - private static void FlipPrimitives(byte[] buffer, int startIndex, int primitiveCount, int primitiveSize) - { - int byteCount = primitiveCount * primitiveSize; - for (int i = startIndex; i < byteCount + startIndex; i += primitiveSize) - { - int a = i; - int b = i + primitiveSize - 1; - while (a < b) - { - byte by = buffer[a]; - buffer[a] = buffer[b]; - buffer[b] = by; - a++; - b--; - } - } - } - } -} diff --git a/Source/Enums.cs b/Source/Enums.cs index 5a93b91..2f64099 100644 --- a/Source/Enums.cs +++ b/Source/Enums.cs @@ -1,18 +1,17 @@ namespace Kermalis.EndianBinaryIO { - public enum BooleanSize : byte - { - U8, - U16, - U32, - MAX - } - - public enum Endianness : byte - { - LittleEndian, - BigEndian, - MAX - } -} + public enum BooleanSize : byte + { + U8, + U16, + U32, + MAX + } + public enum Endianness : byte + { + LittleEndian, + BigEndian, + MAX + } +} \ No newline at end of file diff --git a/Source/IBinarySerializable.cs b/Source/IBinarySerializable.cs index 69cbfcf..2056e41 100644 --- a/Source/IBinarySerializable.cs +++ b/Source/IBinarySerializable.cs @@ -1,8 +1,8 @@ namespace Kermalis.EndianBinaryIO { - public interface IBinarySerializable - { - void Read(EndianBinaryReader r); - void Write(EndianBinaryWriter w); - } + public interface IBinarySerializable + { + void Read(EndianBinaryReader r); + void Write(EndianBinaryWriter w); + } } diff --git a/Source/Utils.cs b/Source/Utils.cs index bdaa7aa..42cd750 100644 --- a/Source/Utils.cs +++ b/Source/Utils.cs @@ -1,178 +1,164 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Reflection; -using System.Text; +using System.Runtime.CompilerServices; namespace Kermalis.EndianBinaryIO { - internal sealed class Utils - { - public static void TruncateString(string str, int charCount, out char[] toArray) - { - toArray = new char[charCount]; - int numCharsToCopy = Math.Min(charCount, str.Length); - for (int i = 0; i < numCharsToCopy; i++) - { - toArray[i] = str[i]; - } - } + internal static class Utils + { + private sealed class RawData + { + public byte Data; + } + public static ref byte GetRawData(object value) + { + return ref Unsafe.As(value).Data; // Skip object header + } - public static bool TryGetAttribute(PropertyInfo propertyInfo, out TAttribute attribute) where TAttribute : Attribute - { - object[] attributes = propertyInfo.GetCustomAttributes(typeof(TAttribute), true); - if (attributes.Length == 1) - { - attribute = (TAttribute)attributes[0]; - return true; - } - attribute = null; - return false; - } - public static TValue GetAttributeValue(TAttribute attribute) where TAttribute : Attribute, IBinaryAttribute - { - return (TValue)typeof(TAttribute).GetProperty(nameof(IBinaryAttribute.Value)).GetValue(attribute); - } - public static TValue AttributeValueOrDefault(PropertyInfo propertyInfo, TValue defaultValue) where TAttribute : Attribute, IBinaryAttribute - { - if (TryGetAttribute(propertyInfo, out TAttribute attribute)) - { - return GetAttributeValue(attribute); - } - return defaultValue; - } + public static void TrimNullTerminators(ref char[] chars) + { + int i = Array.IndexOf(chars, '\0'); + if (i != -1) + { + Array.Resize(ref chars, i); + } + } + private static bool TryConvertToInt32(object? obj, out int value) + { + try + { + value = Convert.ToInt32(obj); + return true; + } + catch + { + value = -1; + return false; + } + } - public static void ThrowIfCannotReadWriteType(Type type) - { - if (type.IsArray || type.IsEnum || type.IsInterface || type.IsPointer || type.IsPrimitive) - { - throw new ArgumentException(nameof(type), $"Cannot read/write \"{type.FullName}\" objects."); - } - } - public static void ThrowIfCannotUseEncoding(Encoding encoding) - { - if (encoding is null) - { - throw new ArgumentNullException(nameof(encoding), "EndianBinaryIO could not read/write chars because an encoding was null; make sure you pass one in properly."); - } - } + public static bool TryGetAttribute(PropertyInfo propertyInfo, [NotNullWhen(true)] out TAttribute? attribute) + where TAttribute : Attribute + { + object[] attributes = propertyInfo.GetCustomAttributes(typeof(TAttribute), true); + if (attributes.Length == 1) + { + attribute = (TAttribute)attributes[0]; + return true; + } + attribute = default; + return false; + } + public static TValue GetAttributeValue(TAttribute attribute) + where TAttribute : Attribute, IBinaryAttribute + { + return (TValue)typeof(TAttribute).GetProperty(nameof(IBinaryAttribute.Value))!.GetValue(attribute)!; + } + public static TValue AttributeValueOrDefault(PropertyInfo propertyInfo, TValue defaultValue) + where TAttribute : Attribute, IBinaryAttribute + { + if (TryGetAttribute(propertyInfo, out TAttribute? attribute)) + { + return GetAttributeValue(attribute); + } + return defaultValue; + } - // Returns true if count is 0 - public static bool ValidateReadArraySize(int count, out T[] array) - { - if (count < 0) - { - throw new ArgumentOutOfRangeException($"Invalid array length ({count})"); - } - if (count == 0) - { - array = Array.Empty(); - return true; - } - array = null; - return false; - } - // Returns true if count is 0 - public static bool ValidateArrayIndexAndCount(Array array, int startIndex, int count) - { - if (array is null) - { - throw new ArgumentNullException(nameof(array)); - } - if (count < 0) - { - throw new ArgumentOutOfRangeException($"Invalid array length ({count})"); - } - if (startIndex + count > array.Length) - { - throw new ArgumentOutOfRangeException($"Invalid array index + count (StartIndex: {startIndex}, Count: {count}, Array Length: {array.Length})"); - } - return count == 0; - } + public static void ThrowIfCannotReadWriteType(Type type) + { + if (type.IsArray || type.IsEnum || type.IsInterface || type.IsPointer || type.IsPrimitive) + { + throw new ArgumentException($"Cannot read/write \"{type.FullName}\" objects.", nameof(type)); + } + } - public static int GetArrayLength(object obj, Type objType, PropertyInfo propertyInfo) - { - int Validate(int value) - { - if (value < 0) - { - throw new ArgumentOutOfRangeException($"An array property in \"{objType.FullName}\" has an invalid length attribute ({value})"); - } - return value; - } + public static int GetArrayLength(object obj, Type objType, PropertyInfo propertyInfo) + { + if (TryGetAttribute(propertyInfo, out BinaryArrayFixedLengthAttribute? fixedLenAttribute)) + { + if (propertyInfo.IsDefined(typeof(BinaryArrayVariableLengthAttribute))) + { + throw new ArgumentException($"An array property in \"{objType.FullName}\" has two array length attributes. Only one should be provided."); + } + return GetAttributeValue(fixedLenAttribute); + } - if (TryGetAttribute(propertyInfo, out BinaryArrayFixedLengthAttribute fixedLenAttribute)) - { - if (propertyInfo.IsDefined(typeof(BinaryArrayVariableLengthAttribute))) - { - throw new ArgumentException($"An array property in \"{objType.FullName}\" has two array length attributes. Only one should be provided."); - } - return Validate(GetAttributeValue(fixedLenAttribute)); - } - if (TryGetAttribute(propertyInfo, out BinaryArrayVariableLengthAttribute varLenAttribute)) - { - string anchorName = GetAttributeValue(varLenAttribute); - PropertyInfo anchor = objType.GetProperty(anchorName, BindingFlags.Instance | BindingFlags.Public); - if (anchor is null) - { - throw new MissingMemberException($"An array property in \"{objType.FullName}\" has an invalid {nameof(BinaryArrayVariableLengthAttribute)} ({anchorName})."); - } - return Validate(Convert.ToInt32(anchor.GetValue(obj))); - } - throw new MissingMemberException($"An array property in \"{objType.FullName}\" is missing an array length attribute. One should be provided."); - } - public static void GetStringLength(object obj, Type objType, PropertyInfo propertyInfo, bool forReads, out bool? nullTerminated, out int stringLength) - { - int Validate(int value) - { - if (value < 0) - { - throw new ArgumentOutOfRangeException($"A string property in \"{objType.FullName}\" has an invalid length attribute ({value})"); - } - return value; - } + if (TryGetAttribute(propertyInfo, out BinaryArrayVariableLengthAttribute? varLenAttribute)) + { + string anchorName = GetAttributeValue(varLenAttribute); + PropertyInfo? anchor = objType.GetProperty(anchorName, BindingFlags.Instance | BindingFlags.Public); + if (anchor is null) + { + throw new MissingMemberException($"An array property in \"{objType.FullName}\" has an invalid {nameof(BinaryArrayVariableLengthAttribute)} ({anchorName})."); + } - if (TryGetAttribute(propertyInfo, out BinaryStringNullTerminatedAttribute nullTermAttribute)) - { - if (propertyInfo.IsDefined(typeof(BinaryStringFixedLengthAttribute)) || propertyInfo.IsDefined(typeof(BinaryStringVariableLengthAttribute))) - { - throw new ArgumentException($"A string property in \"{objType.FullName}\" has a string length attribute and a {nameof(BinaryStringNullTerminatedAttribute)}; cannot use both."); - } - if (propertyInfo.IsDefined(typeof(BinaryStringTrimNullTerminatorsAttribute))) - { - throw new ArgumentException($"A string property in \"{objType.FullName}\" has a {nameof(BinaryStringNullTerminatedAttribute)} and a {nameof(BinaryStringTrimNullTerminatorsAttribute)}; cannot use both."); - } - bool nt = GetAttributeValue(nullTermAttribute); - if (forReads && !nt) // Not forcing BinaryStringNullTerminatedAttribute to be treated as true since you may only write objects without reading them. - { - throw new ArgumentException($"A string property in \"{objType.FullName}\" has a {nameof(BinaryStringNullTerminatedAttribute)} that's set to false." + - $" Must use null termination or provide a string length when reading."); - } - nullTerminated = nt; - stringLength = -1; - return; - } - if (TryGetAttribute(propertyInfo, out BinaryStringFixedLengthAttribute fixedLenAttribute)) - { - if (propertyInfo.IsDefined(typeof(BinaryStringVariableLengthAttribute))) - { - throw new ArgumentException($"A string property in \"{objType.FullName}\" has two string length attributes. Only one should be provided."); - } - nullTerminated = null; - stringLength = Validate(GetAttributeValue(fixedLenAttribute)); - return; - } - if (TryGetAttribute(propertyInfo, out BinaryStringVariableLengthAttribute varLenAttribute)) - { - string anchorName = GetAttributeValue(varLenAttribute); - PropertyInfo anchor = objType.GetProperty(anchorName, BindingFlags.Instance | BindingFlags.Public); - if (anchor is null) - { - throw new MissingMemberException($"A string property in \"{objType.FullName}\" has an invalid {nameof(BinaryStringVariableLengthAttribute)} ({anchorName})."); - } - nullTerminated = null; - stringLength = Validate(Convert.ToInt32(anchor.GetValue(obj))); - return; - } - throw new MissingMemberException($"A string property in \"{objType.FullName}\" is missing a string length attribute and has no {nameof(BinaryStringNullTerminatedAttribute)}. One should be provided."); - } - } + object? anchorValue = anchor.GetValue(obj); + if (!TryConvertToInt32(anchorValue, out int length) || length < 0) + { + throw new InvalidOperationException($"An array property in \"{objType.FullName}\" has an invalid length attribute ({anchorName} = {anchorValue})."); + } + return length; + } + + throw new MissingMemberException($"An array property in \"{objType.FullName}\" is missing an array length attribute. One should be provided."); + } + public static void GetStringLength(object obj, Type objType, PropertyInfo propertyInfo, bool forReads, out bool? nullTerminated, out int stringLength) + { + if (TryGetAttribute(propertyInfo, out BinaryStringNullTerminatedAttribute? nullTermAttribute)) + { + if (propertyInfo.IsDefined(typeof(BinaryStringFixedLengthAttribute)) || propertyInfo.IsDefined(typeof(BinaryStringVariableLengthAttribute))) + { + throw new ArgumentException($"A string property in \"{objType.FullName}\" has a string length attribute and a {nameof(BinaryStringNullTerminatedAttribute)}; cannot use both."); + } + if (propertyInfo.IsDefined(typeof(BinaryStringTrimNullTerminatorsAttribute))) + { + throw new ArgumentException($"A string property in \"{objType.FullName}\" has a {nameof(BinaryStringNullTerminatedAttribute)} and a {nameof(BinaryStringTrimNullTerminatorsAttribute)}; cannot use both."); + } + + bool nt = GetAttributeValue(nullTermAttribute); + if (forReads && !nt) // Not forcing BinaryStringNullTerminatedAttribute to be treated as true since you may only write objects without reading them. + { + throw new ArgumentException($"A string property in \"{objType.FullName}\" has a {nameof(BinaryStringNullTerminatedAttribute)} that's set to false." + + $" Must use null termination or provide a string length when reading."); + } + + nullTerminated = nt; + stringLength = -1; + return; + } + + if (TryGetAttribute(propertyInfo, out BinaryStringFixedLengthAttribute? fixedLenAttribute)) + { + if (propertyInfo.IsDefined(typeof(BinaryStringVariableLengthAttribute))) + { + throw new ArgumentException($"A string property in \"{objType.FullName}\" has two string length attributes. Only one should be provided."); + } + + nullTerminated = null; + stringLength = GetAttributeValue(fixedLenAttribute); + return; + } + + if (TryGetAttribute(propertyInfo, out BinaryStringVariableLengthAttribute? varLenAttribute)) + { + string anchorName = GetAttributeValue(varLenAttribute); + PropertyInfo? anchor = objType.GetProperty(anchorName, BindingFlags.Instance | BindingFlags.Public); + if (anchor is null) + { + throw new MissingMemberException($"A string property in \"{objType.FullName}\" has an invalid {nameof(BinaryStringVariableLengthAttribute)} ({anchorName})."); + } + + nullTerminated = null; + object? anchorValue = anchor.GetValue(obj); + if (!TryConvertToInt32(anchorValue, out stringLength) || stringLength < 0) + { + throw new InvalidOperationException($"A string property in \"{objType.FullName}\" has an invalid length attribute ({anchorName} = {stringLength})."); + } + return; + } + + throw new MissingMemberException($"A string property in \"{objType.FullName}\" is missing a string length attribute and has no {nameof(BinaryStringNullTerminatedAttribute)}. One should be provided."); + } + } } diff --git a/Testing/BasicTests.cs b/Testing/BasicTests.cs index 24dbeca..3981b3a 100644 --- a/Testing/BasicTests.cs +++ b/Testing/BasicTests.cs @@ -2,189 +2,195 @@ using System; using System.IO; using System.Linq; -using System.Text; using Xunit; namespace Kermalis.EndianBinaryIOTests { - public sealed class BasicTests - { - private sealed class MyBasicObj - { - // Properties - public ShortSizedEnum Type { get; set; } - public short Version { get; set; } - public DateTime Date { get; set; } - - // Property that is ignored when reading and writing - [BinaryIgnore(true)] - public ByteSizedEnum DoNotReadOrWrite { get; set; } - - // Arrays work as well - [BinaryArrayFixedLength(16)] - public uint[] ArrayWith16Elements { get; set; } - - // Boolean that occupies 4 bytes instead of one - [BinaryBooleanSize(BooleanSize.U32)] - public bool Bool32 { get; set; } - - // String encoded in ASCII - // Reads chars until the stream encounters a '\0' - // Writing will append a '\0' at the end of the string - [BinaryEncoding("ASCII")] - [BinaryStringNullTerminated(true)] - public string NullTerminatedASCIIString { get; set; } - - // String encoded in UTF16-LE that will only read/write 10 chars - [BinaryEncoding("UTF-16")] - [BinaryStringFixedLength(10)] - [BinaryStringTrimNullTerminators(true)] - public string UTF16String { get; set; } - } - - #region Constants - private static readonly byte[] _bytes = new byte[] - { - 0x00, 0x08, // ShortSizedEnum.Val2 - 0xFF, 0x01, // (short)511 - 0x00, 0x00, 0x4A, 0x7A, 0x9E, 0x01, 0xC0, 0x08, // (DateTime)Dec. 30, 1998 - - 0x00, 0x00, 0x00, 0x00, // (uint)0 - 0x01, 0x00, 0x00, 0x00, // (uint)1 - 0x02, 0x00, 0x00, 0x00, // (uint)2 - 0x03, 0x00, 0x00, 0x00, // (uint)3 - 0x04, 0x00, 0x00, 0x00, // (uint)4 - 0x05, 0x00, 0x00, 0x00, // (uint)5 - 0x06, 0x00, 0x00, 0x00, // (uint)6 - 0x07, 0x00, 0x00, 0x00, // (uint)7 - 0x08, 0x00, 0x00, 0x00, // (uint)8 - 0x09, 0x00, 0x00, 0x00, // (uint)9 - 0x0A, 0x00, 0x00, 0x00, // (uint)10 - 0x0B, 0x00, 0x00, 0x00, // (uint)11 - 0x0C, 0x00, 0x00, 0x00, // (uint)12 - 0x0D, 0x00, 0x00, 0x00, // (uint)13 - 0x0E, 0x00, 0x00, 0x00, // (uint)14 - 0x0F, 0x00, 0x00, 0x00, // (uint)15 - - 0x00, 0x00, 0x00, 0x00, // (bool32)false - - 0x45, 0x6E, 0x64, 0x69, 0x61, 0x6E, 0x42, 0x69, 0x6E, 0x61, 0x72, 0x79, 0x49, 0x4F, 0x00, // (ASCII)"EndianBinaryIO\0" - - 0x4B, 0x00, 0x65, 0x00, 0x72, 0x00, 0x6D, 0x00, 0x61, 0x00, 0x6C, 0x00, 0x69, 0x00, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, // (UTF16-LE)"Kermalis\0\0" - }; - private MyBasicObj GetObj() - { - return new MyBasicObj - { - Type = ShortSizedEnum.Val2, - Version = 511, - Date = new DateTime(1998, 12, 30), - - DoNotReadOrWrite = ByteSizedEnum.Val1, - - ArrayWith16Elements = new uint[16] - { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 - }, - - Bool32 = false, - - NullTerminatedASCIIString = "EndianBinaryIO", - UTF16String = "Kermalis" - }; - } - #endregion - - [Fact] - public void ReadObject() - { - MyBasicObj obj; - using (var stream = new MemoryStream(_bytes)) - { - obj = new EndianBinaryReader(stream, endianness: Endianness.LittleEndian).ReadObject(); - } - - Assert.Equal(ShortSizedEnum.Val2, obj.Type); // Enum works - Assert.Equal(511, obj.Version); // short works - Assert.Equal(new DateTime(1998, 12, 30), obj.Date); // DateTime works - - Assert.Equal(default, obj.DoNotReadOrWrite); // Ignored - - Assert.Equal(16, obj.ArrayWith16Elements.Length); // Fixed size array works - for (uint i = 0; i < 16; i++) - { - Assert.Equal(i, obj.ArrayWith16Elements[i]); // Array works - } - - Assert.False(obj.Bool32); // bool32 works - - Assert.Equal("EndianBinaryIO", obj.NullTerminatedASCIIString); // Stops reading at null terminator - Assert.Equal("Kermalis", obj.UTF16String); // Fixed size (10 chars) UTF16-LE, with the \0s trimmed - } - - [Fact] - public void ReadManually() - { - using (var stream = new MemoryStream(_bytes)) - { - var reader = new EndianBinaryReader(stream, endianness: Endianness.LittleEndian, booleanSize: BooleanSize.U32); - var obj = new MyBasicObj(); - - obj.Type = reader.ReadEnum(); - Assert.Equal(ShortSizedEnum.Val2, obj.Type); // Enum works - obj.Version = reader.ReadInt16(); - Assert.Equal(511, obj.Version); // short works - obj.Date = reader.ReadDateTime(); - Assert.Equal(new DateTime(1998, 12, 30), obj.Date); // DateTime works - - obj.ArrayWith16Elements = reader.ReadUInt32s(16); - Assert.Equal(16, obj.ArrayWith16Elements.Length); // Fixed size array works - for (uint i = 0; i < 16; i++) - { - Assert.Equal(i, obj.ArrayWith16Elements[i]); // Array works - } - - obj.Bool32 = reader.ReadBoolean(); - Assert.False(obj.Bool32); // bool32 works - - obj.NullTerminatedASCIIString = reader.ReadStringNullTerminated(Encoding.ASCII); - Assert.Equal("EndianBinaryIO", obj.NullTerminatedASCIIString); // Stops reading at null terminator - obj.UTF16String = reader.ReadString(10, true, Encoding.Unicode); - Assert.Equal("Kermalis", obj.UTF16String); // Fixed size (10 chars) UTF16-LE, with the \0s trimmed - } - } - - [Fact] - public void WriteObject() - { - byte[] bytes = new byte[_bytes.Length]; - using (var stream = new MemoryStream(bytes)) - { - new EndianBinaryWriter(stream, endianness: Endianness.LittleEndian).Write(GetObj()); - } - - Assert.True(bytes.SequenceEqual(_bytes)); - } - - [Fact] - public void WriteManually() - { - MyBasicObj obj = GetObj(); - - byte[] bytes = new byte[_bytes.Length]; - using (var stream = new MemoryStream(bytes)) - { - var writer = new EndianBinaryWriter(stream, endianness: Endianness.LittleEndian, booleanSize: BooleanSize.U32); - writer.Write(obj.Type); - writer.Write(obj.Version); - writer.Write(obj.Date); - writer.Write(obj.ArrayWith16Elements); - writer.Write(obj.Bool32); - writer.Write(obj.NullTerminatedASCIIString, true, Encoding.ASCII); - writer.Write(obj.UTF16String, 10, Encoding.Unicode); - } - - Assert.True(bytes.SequenceEqual(_bytes)); - } - } + public sealed class BasicTests + { + private sealed class MyBasicObj + { + // Properties + public ShortSizedEnum Type { get; set; } + public short Version { get; set; } + public DateTime Date { get; set; } + + // Property that is ignored when reading and writing + [BinaryIgnore] + public ByteSizedEnum DoNotReadOrWrite { get; set; } + + // Arrays work as well + [BinaryArrayFixedLength(16)] + public uint[] ArrayWith16Elements { get; set; } + + // Boolean that occupies 4 bytes instead of one + [BinaryBooleanSize(BooleanSize.U32)] + public bool Bool32 { get; set; } + + // String encoded in ASCII + // Reads chars until the stream encounters a '\0' + // Writing will append a '\0' at the end of the string + [BinaryASCII] + [BinaryStringNullTerminated] + public string NullTerminatedASCIIString { get; set; } + + // String encoded in UTF16-LE that will only read/write 10 chars + [BinaryStringFixedLength(10)] + [BinaryStringTrimNullTerminators] + public string UTF16String { get; set; } + } + + #region Constants + + private static readonly byte[] _bytes = new byte[] + { + 0x00, 0x08, // ShortSizedEnum.Val2 + 0xFF, 0x01, // (short)511 + 0x00, 0x00, 0x4A, 0x7A, 0x9E, 0x01, 0xC0, 0x08, // (DateTime)Dec. 30, 1998 + + 0x00, 0x00, 0x00, 0x00, // (uint)0 + 0x01, 0x00, 0x00, 0x00, // (uint)1 + 0x02, 0x00, 0x00, 0x00, // (uint)2 + 0x03, 0x00, 0x00, 0x00, // (uint)3 + 0x04, 0x00, 0x00, 0x00, // (uint)4 + 0x05, 0x00, 0x00, 0x00, // (uint)5 + 0x06, 0x00, 0x00, 0x00, // (uint)6 + 0x07, 0x00, 0x00, 0x00, // (uint)7 + 0x08, 0x00, 0x00, 0x00, // (uint)8 + 0x09, 0x00, 0x00, 0x00, // (uint)9 + 0x0A, 0x00, 0x00, 0x00, // (uint)10 + 0x0B, 0x00, 0x00, 0x00, // (uint)11 + 0x0C, 0x00, 0x00, 0x00, // (uint)12 + 0x0D, 0x00, 0x00, 0x00, // (uint)13 + 0x0E, 0x00, 0x00, 0x00, // (uint)14 + 0x0F, 0x00, 0x00, 0x00, // (uint)15 + + 0x00, 0x00, 0x00, 0x00, // (bool32)false + + 0x45, 0x6E, 0x64, 0x69, 0x61, 0x6E, 0x42, 0x69, 0x6E, 0x61, 0x72, 0x79, 0x49, 0x4F, 0x00, // (ASCII)"EndianBinaryIO\0" + + 0x4B, 0x00, 0x65, 0x00, 0x72, 0x00, 0x6D, 0x00, 0x61, 0x00, 0x6C, 0x00, 0x69, 0x00, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, // (UTF16-LE)"Kermalis\0\0" + }; + private static MyBasicObj GetObj() + { + return new MyBasicObj + { + Type = ShortSizedEnum.Val2, + Version = 511, + Date = new DateTime(1998, 12, 30), + + DoNotReadOrWrite = ByteSizedEnum.Val1, + + ArrayWith16Elements = new uint[16] + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 + }, + + Bool32 = false, + + NullTerminatedASCIIString = "EndianBinaryIO", + UTF16String = "Kermalis", + }; + } + + #endregion + + [Fact] + public void ReadObject() + { + MyBasicObj obj; + using (var stream = new MemoryStream(_bytes)) + { + obj = new EndianBinaryReader(stream, endianness: Endianness.LittleEndian).ReadObject(); + } + + Assert.Equal(ShortSizedEnum.Val2, obj.Type); // Enum works + Assert.Equal(511, obj.Version); // short works + Assert.Equal(new DateTime(1998, 12, 30), obj.Date); // DateTime works + + Assert.Equal(default, obj.DoNotReadOrWrite); // Ignored + + Assert.Equal(16, obj.ArrayWith16Elements.Length); // Fixed size array works + for (uint i = 0; i < 16; i++) + { + Assert.Equal(i, obj.ArrayWith16Elements[i]); // Array works + } + + Assert.False(obj.Bool32); // bool32 works + + Assert.Equal("EndianBinaryIO", obj.NullTerminatedASCIIString); // Stops reading at null terminator + Assert.Equal("Kermalis", obj.UTF16String); // Fixed size (10 chars) UTF16-LE, with the \0s trimmed + } + + [Fact] + public void ReadManually() + { + using (var stream = new MemoryStream(_bytes)) + { + var reader = new EndianBinaryReader(stream, endianness: Endianness.LittleEndian, booleanSize: BooleanSize.U32); + var obj = new MyBasicObj(); + + obj.Type = reader.ReadEnum(); + Assert.Equal(ShortSizedEnum.Val2, obj.Type); // Enum works + obj.Version = reader.ReadInt16(); + Assert.Equal(511, obj.Version); // short works + obj.Date = reader.ReadDateTime(); + Assert.Equal(new DateTime(1998, 12, 30), obj.Date); // DateTime works + + obj.ArrayWith16Elements = new uint[16]; + reader.ReadUInt32s(obj.ArrayWith16Elements); + for (uint i = 0; i < 16; i++) + { + Assert.Equal(i, obj.ArrayWith16Elements[i]); // Array works + } + + obj.Bool32 = reader.ReadBoolean(); + Assert.False(obj.Bool32); // bool32 works + + reader.ASCII = true; + obj.NullTerminatedASCIIString = reader.ReadString_NullTerminated(); + Assert.Equal("EndianBinaryIO", obj.NullTerminatedASCIIString); // Stops reading at null terminator + + reader.ASCII = false; + obj.UTF16String = reader.ReadString_Count_TrimNullTerminators(10); + Assert.Equal("Kermalis", obj.UTF16String); // Fixed size (10 chars) UTF16-LE, with the \0s trimmed + } + } + + [Fact] + public void WriteObject() + { + byte[] bytes = new byte[_bytes.Length]; + using (var stream = new MemoryStream(bytes)) + { + new EndianBinaryWriter(stream, endianness: Endianness.LittleEndian).WriteObject(GetObj()); + } + + Assert.True(bytes.SequenceEqual(_bytes)); + } + + [Fact] + public void WriteManually() + { + MyBasicObj obj = GetObj(); + + byte[] bytes = new byte[_bytes.Length]; + using (var stream = new MemoryStream(bytes)) + { + var writer = new EndianBinaryWriter(stream, endianness: Endianness.LittleEndian, booleanSize: BooleanSize.U32); + writer.WriteEnum(obj.Type); + writer.WriteInt16(obj.Version); + writer.WriteDateTime(obj.Date); + writer.WriteUInt32s(obj.ArrayWith16Elements); + writer.WriteBoolean(obj.Bool32); + + writer.ASCII = true; + writer.WriteChars_NullTerminated(obj.NullTerminatedASCIIString); + writer.ASCII = false; + writer.WriteChars_Count(obj.UTF16String, 10); + } + + Assert.True(bytes.SequenceEqual(_bytes)); + } + } } diff --git a/Testing/EncodingTests.cs b/Testing/EncodingTests.cs index 1f197e5..249e93b 100644 --- a/Testing/EncodingTests.cs +++ b/Testing/EncodingTests.cs @@ -2,200 +2,105 @@ using System; using System.IO; using System.Linq; -using System.Text; using Xunit; namespace Kermalis.EndianBinaryIOTests { - public sealed class EncodingTests - { - private interface ICharObj - { - string Str { get; set; } - } - private sealed class CharObj : ICharObj - { - public byte Len { get; set; } - [BinaryStringVariableLength(nameof(Len))] - public string Str { get; set; } - } - private sealed class CustomEncodingCharObj : ICharObj - { - public byte Len { get; set; } - [BinaryEncoding("CHPOOSH")] - [BinaryStringVariableLength(nameof(Len))] - public string Str { get; set; } - } + public sealed class EncodingTests + { + private interface ICharObj + { + string Str { get; set; } + } + private sealed class CharObj : ICharObj + { + public byte Len { get; set; } + [BinaryStringVariableLength(nameof(Len))] + public string Str { get; set; } + } - #region Constants - private const string TestString_ASCII = "Jummy\r\nFunnies"; - private static readonly byte[] _testBytes_ASCII = new byte[] - { - 0x0E, // Len - 0x4A, 0x75, 0x6D, 0x6D, 0x79, // "Jummy" - 0x0D, 0x0A, // "\r\n" - 0x46, 0x75, 0x6E, 0x6E, 0x69, 0x65, 0x73 // "Funnies" - }; - private const string TestString_UTF = "Jummy😀\r\n😳Funnies"; - private static readonly byte[] _testBytes_UTF7 = new byte[] - { - 0x12, // Len - 0x4A, 0x75, 0x6D, 0x6D, 0x79, // "Jummy" - 0x2B, 0x32, 0x44, 0x33, 0x65, 0x41, 0x41, 0x2D, // "😀" - 0x0D, 0x0A, // "\r\n" - 0x2B, 0x32, 0x44, 0x33, 0x65, 0x4D, 0x77, 0x2D, - 0x46, 0x75, 0x6E, 0x6E, 0x69, 0x65, 0x73 // "Funnies" - }; - private static readonly byte[] _testBytes_UTF8 = new byte[] - { - 0x12, // Len - 0x4A, 0x75, 0x6D, 0x6D, 0x79, // "Jummy" - 0xF0, 0x9F, 0x98, 0x80, // "😀" - 0x0D, 0x0A, // "\r\n" - 0xF0, 0x9F, 0x98, 0xB3, // "😳" - 0x46, 0x75, 0x6E, 0x6E, 0x69, 0x65, 0x73 // "Funnies" - }; - private static readonly byte[] _testBytes_UTF16LE = new byte[] - { - 0x12, // Len - 0x4A, 0x00, 0x75, 0x00, 0x6D, 0x00, 0x6D, 0x00, 0x79, 0x00, // "Jummy" - 0x3D, 0xD8, 0x00, 0xDE, // "😀" - 0x0D, 0x00, 0x0A, 0x00, // "\r\n" - 0x3D, 0xD8, 0x33, 0xDE, // "😳" - 0x46, 0x00, 0x75, 0x00, 0x6E, 0x00, 0x6E, 0x00, 0x69, 0x00, 0x65, 0x00, 0x73, 0x00 // "Funnies" - }; - private static readonly byte[] _testBytes_UTF16BE = new byte[] - { - 0x12, // Len - 0x00, 0x4A, 0x00, 0x75, 0x00, 0x6D, 0x00, 0x6D, 0x00, 0x79, // "Jummy" - 0xD8, 0x3D, 0xDE, 0x00, // "😀" - 0x00, 0x0D, 0x00, 0x0A, // "\r\n" - 0xD8, 0x3D, 0xDE, 0x33, // "😳" - 0x00, 0x46, 0x00, 0x75, 0x00, 0x6E, 0x00, 0x6E, 0x00, 0x69, 0x00, 0x65, 0x00, 0x73 // "Funnies" - }; - private static readonly byte[] _testBytes_UTF32LE = new byte[] - { - 0x12, // Len - 0x4A, 0x00, 0x00, 0x00, 0x75, 0x00, 0x00, 0x00, 0x6D, 0x00, 0x00, 0x00, 0x6D, 0x00, 0x00, 0x00, 0x79, 0x00, 0x00, 0x00, // "Jummy" - 0x00, 0xF6, 0x01, 0x00, // "😀" - 0x0D, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, // "\r\n" - 0x33, 0xF6, 0x01, 0x00, // "😳" - 0x46, 0x00, 0x00, 0x00, 0x75, 0x00, 0x00, 0x00, 0x6E, 0x00, 0x00, 0x00, 0x6E, 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x73, 0x00, 0x00, 0x00 // "Funnies" - }; - private static readonly byte[] _testBytes_UTF32BE = new byte[] - { - 0x12, // Len - 0x00, 0x00, 0x00, 0x4A, 0x00, 0x00, 0x00, 0x75, 0x00, 0x00, 0x00, 0x6D, 0x00, 0x00, 0x00, 0x6D, 0x00, 0x00, 0x00, 0x79, // "Jummy" - 0x00, 0x01, 0xF6, 0x00, // "😀" - 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x0A, // "\r\n" - 0x00, 0x01, 0xF6, 0x33, // "😳" - 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x75, 0x00, 0x00, 0x00, 0x6E, 0x00, 0x00, 0x00, 0x6E, 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x73 // "Funnies" - }; - #endregion + #region Constants - #region Chpoosh Encoding - private sealed class ChpooshProvider : EncodingProvider - { - private readonly Encoding _encoding; - public ChpooshProvider(Encoding encoding) - { - _encoding = encoding; - } + private const string TEST_STR_ASCII = "Jummy\r\nFunnies"; + private static readonly byte[] _testBytes_ASCII = new byte[] + { + 0x0E, // Len + 0x4A, 0x75, 0x6D, 0x6D, 0x79, // "Jummy" + 0x0D, 0x0A, // "\r\n" + 0x46, 0x75, 0x6E, 0x6E, 0x69, 0x65, 0x73 // "Funnies" + }; + private const string TEST_STR_UTF = "Jummy😀\r\n😳Funnies"; + private static readonly byte[] _testBytes_UTF8 = new byte[] + { + 0x12, // Len + 0x4A, 0x75, 0x6D, 0x6D, 0x79, // "Jummy" + 0xF0, 0x9F, 0x98, 0x80, // "😀" + 0x0D, 0x0A, // "\r\n" + 0xF0, 0x9F, 0x98, 0xB3, // "😳" + 0x46, 0x75, 0x6E, 0x6E, 0x69, 0x65, 0x73 // "Funnies" + }; + private static readonly byte[] _testBytes_UTF16LE = new byte[] + { + 0x12, // Len + 0x4A, 0x00, 0x75, 0x00, 0x6D, 0x00, 0x6D, 0x00, 0x79, 0x00, // "Jummy" + 0x3D, 0xD8, 0x00, 0xDE, // "😀" + 0x0D, 0x00, 0x0A, 0x00, // "\r\n" + 0x3D, 0xD8, 0x33, 0xDE, // "😳" + 0x46, 0x00, 0x75, 0x00, 0x6E, 0x00, 0x6E, 0x00, 0x69, 0x00, 0x65, 0x00, 0x73, 0x00 // "Funnies" + }; - public override Encoding GetEncoding(int codepage) - { - return default; - } - public override Encoding GetEncoding(string name) - { - if (name == "CHPOOSH") - { - return _encoding; - } - return default; - } - } - private Encoding CreateChpoosh() - { - return new UTF32Encoding(true, false, true); - } - #endregion + #endregion - private void Get(string encodingType, out Encoding encoding, out byte[] input, out string str) - { - switch (encodingType) - { - case "ASCII": encoding = Encoding.ASCII; input = _testBytes_ASCII; str = TestString_ASCII; break; - case "UTF7": encoding = Encoding.UTF7; input = _testBytes_UTF7; str = TestString_UTF; break; - case "UTF8": encoding = Encoding.UTF8; input = _testBytes_UTF8; str = TestString_UTF; break; - case "UTF16-LE": encoding = Encoding.Unicode; input = _testBytes_UTF16LE; str = TestString_UTF; break; - case "UTF16-BE": encoding = Encoding.BigEndianUnicode; input = _testBytes_UTF16BE; str = TestString_UTF; break; - case "UTF32-LE": encoding = Encoding.UTF32; input = _testBytes_UTF32LE; str = TestString_UTF; break; - default: throw new Exception(); - } - } - private void TestRead(Encoding encoding, byte[] input, string str) where T : ICharObj, new() - { - using (var stream = new MemoryStream(input)) - { - T obj = new EndianBinaryReader(stream, encoding, endianness: Endianness.LittleEndian).ReadObject(); - Assert.Equal(str, obj.Str); - } - } - private void TestWrite(Encoding encoding, byte[] input, string str) - { - byte[] bytes = new byte[input.Length]; - using (var stream = new MemoryStream(bytes)) - { - var obj = new CharObj - { - Len = (byte)str.Length, - Str = str - }; - new EndianBinaryWriter(stream, encoding, endianness: Endianness.LittleEndian).Write(obj); - } - Assert.True(bytes.SequenceEqual(input)); - } + private static void Get(bool ascii, out byte[] input, out string str) + { + if (ascii) + { + input = _testBytes_ASCII; str = TEST_STR_ASCII; + } + else + { + input = _testBytes_UTF16LE; str = TEST_STR_UTF; + } + } + private static void TestRead(bool ascii, byte[] input, string str) where T : ICharObj, new() + { + using (var stream = new MemoryStream(input)) + { + T obj = new EndianBinaryReader(stream, endianness: Endianness.LittleEndian, ascii: ascii).ReadObject(); + Assert.Equal(str, obj.Str); + } + } + private static void TestWrite(bool ascii, byte[] input, string str) + { + byte[] bytes = new byte[input.Length]; + using (var stream = new MemoryStream(bytes)) + { + var obj = new CharObj + { + Len = (byte)str.Length, + Str = str, + }; + new EndianBinaryWriter(stream, endianness: Endianness.LittleEndian, ascii: ascii).WriteObject(obj); + } + Assert.True(bytes.SequenceEqual(input)); + } - [Theory] - [InlineData("ASCII")] - [InlineData("UTF7")] - [InlineData("UTF8")] - [InlineData("UTF16-LE")] - [InlineData("UTF16-BE")] - [InlineData("UTF32-LE")] - public void ReadDefaults(string encodingType) - { - Get(encodingType, out Encoding encoding, out byte[] input, out string str); - TestRead(encoding, input, str); - } + [Theory] + [InlineData(true)] + [InlineData(false)] + public void ReadDefaults(bool ascii) + { + Get(ascii, out byte[] input, out string str); + TestRead(ascii, input, str); + } - [Fact] - public void ReadCustom() - { - Encoding encoding = CreateChpoosh(); - Encoding.RegisterProvider(new ChpooshProvider(encoding)); - TestRead(encoding, _testBytes_UTF32BE, TestString_UTF); - } - - [Theory] - [InlineData("ASCII")] - [InlineData("UTF7")] - [InlineData("UTF8")] - [InlineData("UTF16-LE")] - [InlineData("UTF16-BE")] - [InlineData("UTF32-LE")] - public void WriteDefaults(string encodingType) - { - Get(encodingType, out Encoding encoding, out byte[] input, out string str); - TestWrite(encoding, input, str); - } - - [Fact] - public void WriteCustom() - { - Encoding encoding = CreateChpoosh(); - TestWrite(encoding, _testBytes_UTF32BE, TestString_UTF); - } - } + [Theory] + [InlineData(true)] + [InlineData(false)] + public void WriteDefaults(bool ascii) + { + Get(ascii, out byte[] input, out string str); + TestWrite(ascii, input, str); + } + } } diff --git a/Testing/EndianBinaryTesting.csproj b/Testing/EndianBinaryTesting.csproj index b04085d..440394e 100644 --- a/Testing/EndianBinaryTesting.csproj +++ b/Testing/EndianBinaryTesting.csproj @@ -1,24 +1,25 @@  - - netcoreapp3.1 - Kermalis.EndianBinaryIOTests - Kermalis - Kermalis - https://github.com/Kermalis/EndianBinaryIO - - false - true - + + net6.0 + latest + Kermalis.EndianBinaryIOTests + false + true - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - + Kermalis + Kermalis + https://github.com/Kermalis/EndianBinaryIO + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + diff --git a/Testing/FloatTests.cs b/Testing/FloatTests.cs index 88d9767..9369e41 100644 --- a/Testing/FloatTests.cs +++ b/Testing/FloatTests.cs @@ -5,118 +5,126 @@ namespace Kermalis.EndianBinaryIOTests { - public sealed class FloatTests - { - #region Constants - private const float TestValue_Single = 1234.1234f; - private const double TestValue_Double = 12345678.12345678d; - private const decimal TestValue_Decimal = 12345678909876543210.123456789M; + public sealed class FloatTests + { + #region Constants - private static readonly byte[] _bigEndianBytes_Single = new byte[4] - { - 0x44, 0x9A, 0x43, 0xF3 - }; - private static readonly byte[] _littleEndianBytes_Single = new byte[4] - { - 0xF3, 0x43, 0x9A, 0x44 - }; - private static readonly byte[] _bigEndianBytes_Double = new byte[8] - { - 0x41, 0x67, 0x8C, 0x29, 0xC3, 0xF3, 0x5B, 0xA2 - }; - private static readonly byte[] _littleEndianBytes_Double = new byte[8] - { - 0xA2, 0x5B, 0xF3, 0xC3, 0x29, 0x8C, 0x67, 0x41 - }; - private static readonly byte[] _bigEndianBytes_Decimal = new byte[16] - { - 0xBE, 0xAD, 0x40, 0x75, 0xA0, 0x84, 0x71, 0x15, 0x27, 0xE4, 0x1B, 0x32, 0x00, 0x09, 0x00, 0x00 - }; - private static readonly byte[] _littleEndianBytes_Decimal = new byte[16] - { - 0x00, 0x00, 0x09, 0x00, 0x32, 0x1B, 0xE4, 0x27, 0x15, 0x71, 0x84, 0xA0, 0x75, 0x40, 0xAD, 0xBE - }; - #endregion + private const float TEST_VAL_SINGLE = 1234.1234f; + private const double TEST_VAL_DOUBLE = 12345678.12345678d; + private const decimal TEST_VAL_DECIMAL = 12345678909876543210.123456789m; - [Theory] - [InlineData(true)] - [InlineData(false)] - public void ReadSingle(bool le) - { - byte[] input = le ? _littleEndianBytes_Single : _bigEndianBytes_Single; - Endianness e = le ? Endianness.LittleEndian : Endianness.BigEndian; - using (var stream = new MemoryStream(input)) - { - Assert.Equal(TestValue_Single, new EndianBinaryReader(stream, endianness: e).ReadSingle()); - } - } - [Theory] - [InlineData(true)] - [InlineData(false)] - public void WriteSingle(bool le) - { - byte[] input = le ? _littleEndianBytes_Single : _bigEndianBytes_Single; - Endianness e = le ? Endianness.LittleEndian : Endianness.BigEndian; - byte[] bytes = new byte[4]; - using (var stream = new MemoryStream(bytes)) - { - new EndianBinaryWriter(stream, endianness: e).Write(TestValue_Single); - } - Assert.True(bytes.SequenceEqual(input)); - } + private static readonly byte[] _bigEndianBytes_Single = new byte[4] + { + 0x44, 0x9A, 0x43, 0xF3, + }; + private static readonly byte[] _littleEndianBytes_Single = new byte[4] + { + 0xF3, 0x43, 0x9A, 0x44, + }; + private static readonly byte[] _bigEndianBytes_Double = new byte[8] + { + 0x41, 0x67, 0x8C, 0x29, 0xC3, 0xF3, 0x5B, 0xA2, + }; + private static readonly byte[] _littleEndianBytes_Double = new byte[8] + { + 0xA2, 0x5B, 0xF3, 0xC3, 0x29, 0x8C, 0x67, 0x41, + }; + private static readonly byte[] _bigEndianBytes_Decimal = new byte[16] + { + 0xA0, 0x84, 0x71, 0x15, + 0xBE, 0xAD, 0x40, 0x75, + 0x27, 0xE4, 0x1B, 0x32, + 0x00, 0x09, 0x00, 0x00, + }; + private static readonly byte[] _littleEndianBytes_Decimal = new byte[16] + { + 0x15, 0x71, 0x84, 0xA0, + 0x75, 0x40, 0xAD, 0xBE, + 0x32, 0x1B, 0xE4, 0x27, + 0x00, 0x00, 0x09, 0x00, + }; - [Theory] - [InlineData(true)] - [InlineData(false)] - public void ReadDouble(bool le) - { - byte[] input = le ? _littleEndianBytes_Double : _bigEndianBytes_Double; - Endianness e = le ? Endianness.LittleEndian : Endianness.BigEndian; - using (var stream = new MemoryStream(input)) - { - Assert.Equal(TestValue_Double, new EndianBinaryReader(stream, endianness: e).ReadDouble()); - } - } - [Theory] - [InlineData(true)] - [InlineData(false)] - public void WriteDouble(bool le) - { - byte[] input = le ? _littleEndianBytes_Double : _bigEndianBytes_Double; - Endianness e = le ? Endianness.LittleEndian : Endianness.BigEndian; - byte[] bytes = new byte[8]; - using (var stream = new MemoryStream(bytes)) - { - new EndianBinaryWriter(stream, endianness: e).Write(TestValue_Double); - } - Assert.True(bytes.SequenceEqual(input)); - } + #endregion - [Theory] - [InlineData(true)] - [InlineData(false)] - public void ReadDecimal(bool le) - { - byte[] input = le ? _littleEndianBytes_Decimal : _bigEndianBytes_Decimal; - Endianness e = le ? Endianness.LittleEndian : Endianness.BigEndian; - using (var stream = new MemoryStream(input)) - { - Assert.Equal(TestValue_Decimal, new EndianBinaryReader(stream, endianness: e).ReadDecimal()); - } - } - [Theory] - [InlineData(true)] - [InlineData(false)] - public void WriteDecimal(bool le) - { - byte[] input = le ? _littleEndianBytes_Decimal : _bigEndianBytes_Decimal; - Endianness e = le ? Endianness.LittleEndian : Endianness.BigEndian; - byte[] bytes = new byte[16]; - using (var stream = new MemoryStream(bytes)) - { - new EndianBinaryWriter(stream, endianness: e).Write(TestValue_Decimal); - } - Assert.True(bytes.SequenceEqual(input)); - } - } + [Theory] + [InlineData(true)] + [InlineData(false)] + public void ReadSingle(bool le) + { + byte[] input = le ? _littleEndianBytes_Single : _bigEndianBytes_Single; + Endianness e = le ? Endianness.LittleEndian : Endianness.BigEndian; + using (var stream = new MemoryStream(input)) + { + Assert.Equal(TEST_VAL_SINGLE, new EndianBinaryReader(stream, endianness: e).ReadSingle()); + } + } + [Theory] + [InlineData(true)] + [InlineData(false)] + public void WriteSingle(bool le) + { + byte[] input = le ? _littleEndianBytes_Single : _bigEndianBytes_Single; + Endianness e = le ? Endianness.LittleEndian : Endianness.BigEndian; + byte[] bytes = new byte[4]; + using (var stream = new MemoryStream(bytes)) + { + new EndianBinaryWriter(stream, endianness: e).WriteSingle(TEST_VAL_SINGLE); + } + Assert.True(bytes.SequenceEqual(input)); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void ReadDouble(bool le) + { + byte[] input = le ? _littleEndianBytes_Double : _bigEndianBytes_Double; + Endianness e = le ? Endianness.LittleEndian : Endianness.BigEndian; + using (var stream = new MemoryStream(input)) + { + Assert.Equal(TEST_VAL_DOUBLE, new EndianBinaryReader(stream, endianness: e).ReadDouble()); + } + } + [Theory] + [InlineData(true)] + [InlineData(false)] + public void WriteDouble(bool le) + { + byte[] input = le ? _littleEndianBytes_Double : _bigEndianBytes_Double; + Endianness e = le ? Endianness.LittleEndian : Endianness.BigEndian; + byte[] bytes = new byte[8]; + using (var stream = new MemoryStream(bytes)) + { + new EndianBinaryWriter(stream, endianness: e).WriteDouble(TEST_VAL_DOUBLE); + } + Assert.True(bytes.SequenceEqual(input)); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void ReadDecimal(bool le) + { + byte[] input = le ? _littleEndianBytes_Decimal : _bigEndianBytes_Decimal; + Endianness e = le ? Endianness.LittleEndian : Endianness.BigEndian; + using (var stream = new MemoryStream(input)) + { + Assert.Equal(TEST_VAL_DECIMAL, new EndianBinaryReader(stream, endianness: e).ReadDecimal()); + } + } + [Theory] + [InlineData(true)] + [InlineData(false)] + public void WriteDecimal(bool le) + { + byte[] input = le ? _littleEndianBytes_Decimal : _bigEndianBytes_Decimal; + Endianness e = le ? Endianness.LittleEndian : Endianness.BigEndian; + byte[] bytes = new byte[16]; + using (var stream = new MemoryStream(bytes)) + { + new EndianBinaryWriter(stream, endianness: e).WriteDecimal(TEST_VAL_DECIMAL); + } + Assert.True(bytes.SequenceEqual(input)); + } + } } diff --git a/Testing/LengthsTests.cs b/Testing/LengthsTests.cs index 65e9152..86ef791 100644 --- a/Testing/LengthsTests.cs +++ b/Testing/LengthsTests.cs @@ -6,138 +6,140 @@ namespace Kermalis.EndianBinaryIOTests { - public sealed class LengthsTests - { - private sealed class MyLengthyObj - { - [BinaryArrayFixedLength(3)] - [BinaryEncoding("ASCII")] - [BinaryStringNullTerminated(true)] - public string[] NullTerminatedStringArray { get; set; } - - [BinaryArrayFixedLength(3)] - [BinaryEncoding("ASCII")] - [BinaryStringFixedLength(5)] - public string[] SizedStringArray { get; set; } - - public byte VariableLengthProperty { get; set; } - [BinaryArrayVariableLength(nameof(VariableLengthProperty))] - public ShortSizedEnum[] VariableSizedArray { get; set; } - } - private sealed class ZeroLenArrayObj - { - [BinaryArrayFixedLength(0)] - public byte[] SizedArray { get; set; } - - public byte VariableLength { get; set; } - [BinaryArrayVariableLength(nameof(VariableLength))] - public byte[] VariableArray { get; set; } - } - - #region Constants - private static readonly byte[] _lengthyObjBytes = new byte[] - { - 0x48, 0x69, 0x00, // "Hi\0" - 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x00, // "Hello\0" - 0x48, 0x6F, 0x6C, 0x61, 0x00, // "Hola\0" - - 0x53, 0x65, 0x65, 0x79, 0x61, // "Seeya" - 0x42, 0x79, 0x65, 0x00, 0x00, // "Bye\0\0" - 0x41, 0x64, 0x69, 0x6F, 0x73, // "Adios" - - 0x02, // (byte)2 - 0x40, 0x00, // ShortSizedEnum.Val1 - 0x00, 0x08, // ShortSizedEnum.Val2 - }; - private static readonly byte[] _zeroLenArrayObjBytes = new byte[] - { - 0x00, // (byte)0 - }; - #endregion - - [Fact] - public void ReadLengthyObject() - { - MyLengthyObj obj; - using (var stream = new MemoryStream(_lengthyObjBytes)) - { - obj = new EndianBinaryReader(stream, Endianness.LittleEndian).ReadObject(); - } - - Assert.Equal(3, obj.NullTerminatedStringArray.Length); // Fixed size array works - Assert.Equal("Hi", obj.NullTerminatedStringArray[0]); // Null terminated strings - Assert.Equal("Hello", obj.NullTerminatedStringArray[1]); - Assert.Equal("Hola", obj.NullTerminatedStringArray[2]); - - Assert.Equal(3, obj.SizedStringArray.Length); // Fixed size array again - Assert.Equal("Seeya", obj.SizedStringArray[0]); // Strings 5 chars long - Assert.Equal("Bye\0\0", obj.SizedStringArray[1]); - Assert.Equal("Adios", obj.SizedStringArray[2]); - - Assert.Equal(2, obj.VariableLengthProperty); // This determines how long the following array is - Assert.Equal(2, obj.VariableSizedArray.Length); // Retrieves the proper size - Assert.Equal(ShortSizedEnum.Val1, obj.VariableSizedArray[0]); - Assert.Equal(ShortSizedEnum.Val2, obj.VariableSizedArray[1]); - } - - [Fact] - public void WriteLengthyObject() - { - byte[] bytes = new byte[_lengthyObjBytes.Length]; - using (var stream = new MemoryStream(bytes)) - { - new EndianBinaryWriter(stream, Endianness.LittleEndian).Write(new MyLengthyObj - { - NullTerminatedStringArray = new string[3] - { - "Hi", "Hello", "Hola" - }, - - SizedStringArray = new string[3] - { - "Seeya", "Bye", "Adios" - }, - - VariableLengthProperty = 2, - VariableSizedArray = new ShortSizedEnum[2] - { - ShortSizedEnum.Val1, ShortSizedEnum.Val2 - } - }); - } - Assert.True(bytes.SequenceEqual(_lengthyObjBytes)); - } - - [Fact] - public void ReadZeroLenArrayObject() - { - ZeroLenArrayObj obj; - using (var stream = new MemoryStream(_zeroLenArrayObjBytes)) - { - obj = new EndianBinaryReader(stream, Endianness.LittleEndian).ReadObject(); - } - - Assert.Empty(obj.SizedArray); // Fixed size array works - - Assert.Equal(0, obj.VariableLength); // This determines how long the following array is - Assert.Empty(obj.VariableArray); // Retrieves the proper size - } - - [Fact] - public void WriteZeroLenArrayObject() - { - byte[] bytes = new byte[_zeroLenArrayObjBytes.Length]; - using (var stream = new MemoryStream(bytes)) - { - new EndianBinaryWriter(stream, Endianness.LittleEndian).Write(new ZeroLenArrayObj - { - SizedArray = Array.Empty(), - - VariableLength = 0, - VariableArray = Array.Empty() - }); - } - Assert.True(bytes.SequenceEqual(_zeroLenArrayObjBytes)); - } - } + public sealed class LengthsTests + { + private sealed class MyLengthyObj + { + [BinaryArrayFixedLength(3)] + [BinaryASCII] + [BinaryStringNullTerminated] + public string[] NullTerminatedStringArray { get; set; } + + [BinaryArrayFixedLength(3)] + [BinaryASCII] + [BinaryStringFixedLength(5)] + public string[] SizedStringArray { get; set; } + + public byte VariableLengthProperty { get; set; } + [BinaryArrayVariableLength(nameof(VariableLengthProperty))] + public ShortSizedEnum[] VariableSizedArray { get; set; } + } + private sealed class ZeroLenArrayObj + { + [BinaryArrayFixedLength(0)] + public byte[] SizedArray { get; set; } + + public byte VariableLength { get; set; } + [BinaryArrayVariableLength(nameof(VariableLength))] + public byte[] VariableArray { get; set; } + } + + #region Constants + + private static readonly byte[] _lengthyObjBytes = new byte[] + { + 0x48, 0x69, 0x00, // "Hi\0" + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x00, // "Hello\0" + 0x48, 0x6F, 0x6C, 0x61, 0x00, // "Hola\0" + + 0x53, 0x65, 0x65, 0x79, 0x61, // "Seeya" + 0x42, 0x79, 0x65, 0x00, 0x00, // "Bye\0\0" + 0x41, 0x64, 0x69, 0x6F, 0x73, // "Adios" + + 0x02, // (byte)2 + 0x40, 0x00, // ShortSizedEnum.Val1 + 0x00, 0x08, // ShortSizedEnum.Val2 + }; + private static readonly byte[] _zeroLenArrayObjBytes = new byte[] + { + 0x00, // (byte)0 + }; + + #endregion + + [Fact] + public void ReadLengthyObject() + { + MyLengthyObj obj; + using (var stream = new MemoryStream(_lengthyObjBytes)) + { + obj = new EndianBinaryReader(stream, Endianness.LittleEndian).ReadObject(); + } + + Assert.Equal(3, obj.NullTerminatedStringArray.Length); // Fixed size array works + Assert.Equal("Hi", obj.NullTerminatedStringArray[0]); // Null terminated strings + Assert.Equal("Hello", obj.NullTerminatedStringArray[1]); + Assert.Equal("Hola", obj.NullTerminatedStringArray[2]); + + Assert.Equal(3, obj.SizedStringArray.Length); // Fixed size array again + Assert.Equal("Seeya", obj.SizedStringArray[0]); // Strings 5 chars long + Assert.Equal("Bye\0\0", obj.SizedStringArray[1]); + Assert.Equal("Adios", obj.SizedStringArray[2]); + + Assert.Equal(2, obj.VariableLengthProperty); // This determines how long the following array is + Assert.Equal(2, obj.VariableSizedArray.Length); // Retrieves the proper size + Assert.Equal(ShortSizedEnum.Val1, obj.VariableSizedArray[0]); + Assert.Equal(ShortSizedEnum.Val2, obj.VariableSizedArray[1]); + } + + [Fact] + public void WriteLengthyObject() + { + byte[] bytes = new byte[_lengthyObjBytes.Length]; + using (var stream = new MemoryStream(bytes)) + { + new EndianBinaryWriter(stream, Endianness.LittleEndian).WriteObject(new MyLengthyObj + { + NullTerminatedStringArray = new string[3] + { + "Hi", "Hello", "Hola", + }, + + SizedStringArray = new string[3] + { + "Seeya", "Bye", "Adios", + }, + + VariableLengthProperty = 2, + VariableSizedArray = new ShortSizedEnum[2] + { + ShortSizedEnum.Val1, ShortSizedEnum.Val2, + }, + }); + } + Assert.True(bytes.SequenceEqual(_lengthyObjBytes)); + } + + [Fact] + public void ReadZeroLenArrayObject() + { + ZeroLenArrayObj obj; + using (var stream = new MemoryStream(_zeroLenArrayObjBytes)) + { + obj = new EndianBinaryReader(stream, Endianness.LittleEndian).ReadObject(); + } + + Assert.Empty(obj.SizedArray); // Fixed size array works + + Assert.Equal(0, obj.VariableLength); // This determines how long the following array is + Assert.Empty(obj.VariableArray); // Retrieves the proper size + } + + [Fact] + public void WriteZeroLenArrayObject() + { + byte[] bytes = new byte[_zeroLenArrayObjBytes.Length]; + using (var stream = new MemoryStream(bytes)) + { + new EndianBinaryWriter(stream, Endianness.LittleEndian).WriteObject(new ZeroLenArrayObj + { + SizedArray = Array.Empty(), + + VariableLength = 0, + VariableArray = Array.Empty(), + }); + } + Assert.True(bytes.SequenceEqual(_zeroLenArrayObjBytes)); + } + } } diff --git a/Testing/TestUtils.cs b/Testing/TestUtils.cs index 0d02a7c..05c129d 100644 --- a/Testing/TestUtils.cs +++ b/Testing/TestUtils.cs @@ -1,13 +1,13 @@ namespace Kermalis.EndianBinaryIOTests { - internal enum ByteSizedEnum : byte - { - Val1 = 0x20, - Val2 = 0x80 - } - internal enum ShortSizedEnum : short - { - Val1 = 0x40, - Val2 = 0x800 - } + internal enum ByteSizedEnum : byte + { + Val1 = 0x20, + Val2 = 0x80, + } + internal enum ShortSizedEnum : short + { + Val1 = 0x40, + Val2 = 0x800, + } }