From 2bb2ee417c0affa701d22a0870bde8fa1f7516cd Mon Sep 17 00:00:00 2001 From: VNC Date: Tue, 21 May 2024 18:28:57 +0200 Subject: [PATCH] Support for `netstandard2.1;netstandard2.0` --- src/NuGetPackage.props | 10 + .../Conversions/Int16ConversionBenchmark.cs | 2 +- .../Conversions/Int32ConversionBenchmark.cs | 2 +- .../Conversions/Int64ConversionBenchmark.cs | 4 +- .../Conversions/UInt32ConversionBenchmark.cs | 2 +- src/Snowberry.IO.Benchmarks/Program.cs | 6 +- .../BinaryEndianConverter.Offsets.cs | 56 +++++- .../BinaryEndianConverter.cs | 28 ++- src/Snowberry.IO.Common/BinaryUtils.cs | 4 +- src/Snowberry.IO.Common/Reader/Analyzer.cs | 9 +- .../Reader/Interfaces/IEndianReader.Span.cs | 16 ++ .../Reader/Interfaces/IEndianReader.cs | 185 +++++++++++------- src/Snowberry.IO.Common/Reader/RegionRange.cs | 2 - src/Snowberry.IO.Common/Sha1.cs | 101 +++++----- .../Snowberry.IO.Common.csproj | 1 - src/Snowberry.IO.Tests/ReaderTests.cs | 6 +- src/Snowberry.IO.Tests/SharedTests.cs | 7 +- .../Snowberry.IO.Tests.csproj | 46 ++--- src/Snowberry.IO.Tests/TestHelper.cs | 2 + .../Extensions/ReaderExtensions.cs | 4 +- .../Reader/BaseEndianReader.Primitives.cs | 86 ++++++++ .../Reader/BaseEndianReader.Span.cs | 34 ++++ src/Snowberry.IO/Reader/BaseEndianReader.cs | 185 +++++++----------- .../Reader/EndianStreamReader.Span.cs | 24 +++ src/Snowberry.IO/Reader/EndianStreamReader.cs | 17 +- .../Reader/Win32ProcessReader.Span.cs | 35 ++++ src/Snowberry.IO/Reader/Win32ProcessReader.cs | 26 +-- src/Snowberry.IO/Snowberry.IO.csproj | 4 +- src/Snowberry.IO/Utils/Win32Helper.cs | 2 + src/Snowberry.IO/Writer/EndianStreamWriter.cs | 46 ++++- 30 files changed, 629 insertions(+), 323 deletions(-) create mode 100644 src/Snowberry.IO.Common/Reader/Interfaces/IEndianReader.Span.cs create mode 100644 src/Snowberry.IO/Reader/BaseEndianReader.Primitives.cs create mode 100644 src/Snowberry.IO/Reader/BaseEndianReader.Span.cs create mode 100644 src/Snowberry.IO/Reader/EndianStreamReader.Span.cs create mode 100644 src/Snowberry.IO/Reader/Win32ProcessReader.Span.cs diff --git a/src/NuGetPackage.props b/src/NuGetPackage.props index da612c5..95d38a0 100644 --- a/src/NuGetPackage.props +++ b/src/NuGetPackage.props @@ -50,4 +50,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/Snowberry.IO.Benchmarks/Conversions/Int16ConversionBenchmark.cs b/src/Snowberry.IO.Benchmarks/Conversions/Int16ConversionBenchmark.cs index 00cab76..b476c19 100644 --- a/src/Snowberry.IO.Benchmarks/Conversions/Int16ConversionBenchmark.cs +++ b/src/Snowberry.IO.Benchmarks/Conversions/Int16ConversionBenchmark.cs @@ -10,7 +10,7 @@ namespace Snowberry.IO.Benchmarks.Conversions; [MediumRunJob] public class Int16ConversionBenchmark { - private static byte[] _data = { 0x12, 0x23, 0x14, 0x19 }; + private static byte[] _data = [0x12, 0x23, 0x14, 0x19]; [Benchmark] public short BitConverter_Int16() diff --git a/src/Snowberry.IO.Benchmarks/Conversions/Int32ConversionBenchmark.cs b/src/Snowberry.IO.Benchmarks/Conversions/Int32ConversionBenchmark.cs index 594c36d..2ad6d59 100644 --- a/src/Snowberry.IO.Benchmarks/Conversions/Int32ConversionBenchmark.cs +++ b/src/Snowberry.IO.Benchmarks/Conversions/Int32ConversionBenchmark.cs @@ -10,7 +10,7 @@ namespace Snowberry.IO.Benchmarks.Conversions; [MediumRunJob] public class Int32ConversionBenchmark { - private static byte[] _data = { 0x12, 0x23, 0x14, 0x19, 0x12, 0x23, 0x14, 0x19 }; + private static byte[] _data = [0x12, 0x23, 0x14, 0x19, 0x12, 0x23, 0x14, 0x19]; [Benchmark] public int BitConverter_Int32() diff --git a/src/Snowberry.IO.Benchmarks/Conversions/Int64ConversionBenchmark.cs b/src/Snowberry.IO.Benchmarks/Conversions/Int64ConversionBenchmark.cs index f0474dd..ae9c2c5 100644 --- a/src/Snowberry.IO.Benchmarks/Conversions/Int64ConversionBenchmark.cs +++ b/src/Snowberry.IO.Benchmarks/Conversions/Int64ConversionBenchmark.cs @@ -10,7 +10,7 @@ namespace Snowberry.IO.Benchmarks.Conversions; [MediumRunJob] public class Int64ConversionBenchmark { - private static byte[] _data = { 0x12, 0x23, 0x14, 0x19, 0x12, 0x23, 0x14, 0x19, 0x12, 0x23, 0x14, 0x19, 0x12, 0x23, 0x14, 0x19 }; + private static byte[] _data = [0x12, 0x23, 0x14, 0x19, 0x12, 0x23, 0x14, 0x19, 0x12, 0x23, 0x14, 0x19, 0x12, 0x23, 0x14, 0x19]; [Benchmark] public long BitConverter_Int64() @@ -21,7 +21,7 @@ public long BitConverter_Int64() [Benchmark] public unsafe long BinaryEndianConverter_Int64() { - return BinaryEndianConverter.ToLong(_data.AsSpan(), EndianType.BIG); + return BinaryEndianConverter.ToLong(_data.AsSpan(), EndianType.LITTLE); } [Benchmark] diff --git a/src/Snowberry.IO.Benchmarks/Conversions/UInt32ConversionBenchmark.cs b/src/Snowberry.IO.Benchmarks/Conversions/UInt32ConversionBenchmark.cs index 24983f0..4301994 100644 --- a/src/Snowberry.IO.Benchmarks/Conversions/UInt32ConversionBenchmark.cs +++ b/src/Snowberry.IO.Benchmarks/Conversions/UInt32ConversionBenchmark.cs @@ -10,7 +10,7 @@ namespace Snowberry.IO.Benchmarks.Conversions; [MediumRunJob] public class UInt32ConversionBenchmark { - private static byte[] _data = { 0xD6, 0x87, 0x00, 0x00, 0xD6, 0x87, 0x00, 0x00 }; + private static byte[] _data = [0xD6, 0x87, 0x00, 0x00, 0xD6, 0x87, 0x00, 0x00]; [Benchmark] public uint BitConverter_UInt32() diff --git a/src/Snowberry.IO.Benchmarks/Program.cs b/src/Snowberry.IO.Benchmarks/Program.cs index 7eeee94..71870cd 100644 --- a/src/Snowberry.IO.Benchmarks/Program.cs +++ b/src/Snowberry.IO.Benchmarks/Program.cs @@ -5,9 +5,9 @@ internal class Program { private static void Main(string[] args) { - _ = BenchmarkRunner.Run(typeof(Int16ConversionBenchmark)); - _ = BenchmarkRunner.Run(typeof(Int32ConversionBenchmark)); - _ = BenchmarkRunner.Run(typeof(UInt32ConversionBenchmark)); + //_ = BenchmarkRunner.Run(typeof(Int16ConversionBenchmark)); + //_ = BenchmarkRunner.Run(typeof(Int32ConversionBenchmark)); + //_ = BenchmarkRunner.Run(typeof(UInt32ConversionBenchmark)); _ = BenchmarkRunner.Run(typeof(Int64ConversionBenchmark)); } } \ No newline at end of file diff --git a/src/Snowberry.IO.Common/BinaryEndianConverter.Offsets.cs b/src/Snowberry.IO.Common/BinaryEndianConverter.Offsets.cs index a1c8c9d..b2d71ca 100644 --- a/src/Snowberry.IO.Common/BinaryEndianConverter.Offsets.cs +++ b/src/Snowberry.IO.Common/BinaryEndianConverter.Offsets.cs @@ -1,13 +1,14 @@ -#if NET6_0_OR_GREATER -using System; +using System; using System.Runtime.CompilerServices; namespace Snowberry.IO.Common; public static partial class BinaryEndianConverter { +#if NETCOREAPP3_0_OR_GREATER [SkipLocalsInit] [MethodImpl(MethodImplOptions.AggressiveOptimization)] +#endif public static unsafe long ToLong(Span data, int offset, EndianType endian) { if (endian == EndianType.LITTLE) @@ -18,8 +19,10 @@ public static unsafe long ToLong(Span data, int offset, EndianType endian) | ((long)((bigSource[0] << 24) | (bigSource[1] << 16) | (bigSource[2] << 8) | bigSource[3]) << 32); } +#if NETCOREAPP3_0_OR_GREATER [SkipLocalsInit] [MethodImpl(MethodImplOptions.AggressiveOptimization)] +#endif public static unsafe ulong ToULong(Span data, int offset, EndianType endian) { if (endian == EndianType.LITTLE) @@ -30,8 +33,10 @@ public static unsafe ulong ToULong(Span data, int offset, EndianType endia | ((long)((bigSource[0] << 24) | (bigSource[1] << 16) | (bigSource[2] << 8) | bigSource[3]) << 32))); } +#if NETCOREAPP3_0_OR_GREATER [SkipLocalsInit] [MethodImpl(MethodImplOptions.AggressiveOptimization)] +#endif public static unsafe ushort ToUInt16(Span data, int offset, EndianType endian) { if (endian == EndianType.LITTLE) @@ -41,8 +46,10 @@ public static unsafe ushort ToUInt16(Span data, int offset, EndianType end return unchecked((ushort)(bigSource[1] | (bigSource[0] << 8))); } +#if NETCOREAPP3_0_OR_GREATER [SkipLocalsInit] [MethodImpl(MethodImplOptions.AggressiveOptimization)] +#endif public static unsafe short ToInt16(Span data, int offset, EndianType endian) { if (endian == EndianType.LITTLE) @@ -52,8 +59,10 @@ public static unsafe short ToInt16(Span data, int offset, EndianType endia return (short)(bigSource[1] | (bigSource[0] << 8)); } +#if NETCOREAPP3_0_OR_GREATER [SkipLocalsInit] [MethodImpl(MethodImplOptions.AggressiveOptimization)] +#endif public static unsafe uint ToUInt32(Span data, int offset, EndianType endian) { if (endian == EndianType.LITTLE) @@ -63,8 +72,10 @@ public static unsafe uint ToUInt32(Span data, int offset, EndianType endia return unchecked((uint)(bigSource[3] | (bigSource[2] << 8) | (bigSource[1] << 16) | (bigSource[0] << 24))); } +#if NETCOREAPP3_0_OR_GREATER [SkipLocalsInit] [MethodImpl(MethodImplOptions.AggressiveOptimization)] +#endif public static unsafe int ToInt32(Span data, int offset, EndianType endian) { if (endian == EndianType.LITTLE) @@ -74,37 +85,53 @@ public static unsafe int ToInt32(Span data, int offset, EndianType endian) return bigSource[3] | (bigSource[2] << 8) | (bigSource[1] << 16) | (bigSource[0] << 24); } +#if NETCOREAPP3_0_OR_GREATER [SkipLocalsInit] [MethodImpl(MethodImplOptions.AggressiveOptimization)] +#endif public static Guid ToGuid(Span data, int offset, EndianType endian) { if (endian == EndianType.BIG) { - return new(new[] - { + return new( + [ data[offset + 3], data[offset + 2], data[offset + 1], data[offset], data[offset + 5], data[offset + 4], data[offset + 7], data[offset + 6], data[offset + 8], data[offset + 9], data[offset + 10], data[offset + 11], data[offset + 12], data[offset + 13], data[offset + 14], data[offset + 15] - }); + ]); } +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER return new(data.Slice(offset, 16)); +#else + return new Guid(data.Slice(offset, 16).ToArray()); +#endif } +#if NETCOREAPP3_0_OR_GREATER [SkipLocalsInit] [MethodImpl(MethodImplOptions.AggressiveOptimization)] +#endif public static unsafe float ToFloat(Span data, int offset, EndianType endian) { if (endian == EndianType.LITTLE) return Unsafe.ReadUnaligned(ref data[offset]); int temp = ToInt32(data, offset, EndianType.BIG); + +#if NETSTANDARD2_0 + return Int32BitsToSingle(temp); +#else return BitConverter.Int32BitsToSingle(temp); +#endif + } +#if NETCOREAPP3_0_OR_GREATER [SkipLocalsInit] [MethodImpl(MethodImplOptions.AggressiveOptimization)] +#endif public static unsafe double ToDouble(Span data, int offset, EndianType endian) { if (endian == EndianType.LITTLE) @@ -113,5 +140,22 @@ public static unsafe double ToDouble(Span data, int offset, EndianType end long temp = ToLong(data, offset, EndianType.BIG); return BitConverter.Int64BitsToDouble(temp); } + +#if NETSTANDARD2_0 + /// + /// Converts the specified 32-bit signed integer to a single-precision floating-point number + /// by reinterpreting its bit pattern. + /// + /// The 32-bit signed integer to convert. + /// A single-precision floating-point number with the same bit representation as the input integer. + public static float Int32BitsToSingle(int value) + { + unsafe + { + // Create a float pointer and assign the address of the integer + float result = *(float*)&value; + return result; + } + } +#endif } -#endif \ No newline at end of file diff --git a/src/Snowberry.IO.Common/BinaryEndianConverter.cs b/src/Snowberry.IO.Common/BinaryEndianConverter.cs index 7156f64..51d2855 100644 --- a/src/Snowberry.IO.Common/BinaryEndianConverter.cs +++ b/src/Snowberry.IO.Common/BinaryEndianConverter.cs @@ -1,5 +1,4 @@ -#if NET6_0_OR_GREATER -using System; +using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -7,8 +6,11 @@ namespace Snowberry.IO.Common; public static partial class BinaryEndianConverter { + +#if NETCOREAPP3_0_OR_GREATER [SkipLocalsInit] [MethodImpl(MethodImplOptions.AggressiveOptimization)] +#endif public static unsafe int ToInt32(Span data, EndianType endian) { if (endian == EndianType.LITTLE) @@ -18,8 +20,10 @@ public static unsafe int ToInt32(Span data, EndianType endian) return bigSource[3] | (bigSource[2] << 8) | (bigSource[1] << 16) | (bigSource[0] << 24); } +#if NETCOREAPP3_0_OR_GREATER [SkipLocalsInit] [MethodImpl(MethodImplOptions.AggressiveOptimization)] +#endif public static unsafe uint ToUInt32(Span data, EndianType endian) { if (endian == EndianType.LITTLE) @@ -29,8 +33,10 @@ public static unsafe uint ToUInt32(Span data, EndianType endian) return unchecked((uint)(bigSource[3] | (bigSource[2] << 8) | (bigSource[1] << 16) | (bigSource[0] << 24))); } +#if NETCOREAPP3_0_OR_GREATER [SkipLocalsInit] [MethodImpl(MethodImplOptions.AggressiveOptimization)] +#endif public static unsafe ushort ToUInt16(Span data, EndianType endian) { if (endian == EndianType.LITTLE) @@ -40,8 +46,10 @@ public static unsafe ushort ToUInt16(Span data, EndianType endian) return unchecked((ushort)(bigSource[1] | (bigSource[0] << 8))); } +#if NETCOREAPP3_0_OR_GREATER [SkipLocalsInit] [MethodImpl(MethodImplOptions.AggressiveOptimization)] +#endif public static unsafe short ToInt16(Span data, EndianType endian) { if (endian == EndianType.LITTLE) @@ -51,19 +59,28 @@ public static unsafe short ToInt16(Span data, EndianType endian) return (short)(bigSource[1] | (bigSource[0] << 8)); } +#if NETCOREAPP3_0_OR_GREATER [SkipLocalsInit] [MethodImpl(MethodImplOptions.AggressiveOptimization)] +#endif public static unsafe float ToFloat(Span data, EndianType endian) { if (endian == EndianType.LITTLE) return Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(data)); int temp = ToInt32(data, EndianType.BIG); + +#if NETSTANDARD2_0 + return Int32BitsToSingle(temp); +#else return BitConverter.Int32BitsToSingle(temp); +#endif } +#if NETCOREAPP3_0_OR_GREATER [SkipLocalsInit] [MethodImpl(MethodImplOptions.AggressiveOptimization)] +#endif public static unsafe double ToDouble(Span data, EndianType endian) { if (endian == EndianType.LITTLE) @@ -73,8 +90,10 @@ public static unsafe double ToDouble(Span data, EndianType endian) return BitConverter.Int64BitsToDouble(temp); } +#if NETCOREAPP3_0_OR_GREATER [SkipLocalsInit] [MethodImpl(MethodImplOptions.AggressiveOptimization)] +#endif public static unsafe long ToLong(Span data, EndianType endian) { if (endian == EndianType.LITTLE) @@ -85,8 +104,10 @@ public static unsafe long ToLong(Span data, EndianType endian) | ((long)((bigSource[0] << 24) | (bigSource[1] << 16) | (bigSource[2] << 8) | bigSource[3]) << 32); } +#if NETCOREAPP3_0_OR_GREATER [SkipLocalsInit] [MethodImpl(MethodImplOptions.AggressiveOptimization)] +#endif public static unsafe ulong ToULong(Span data, EndianType endian) { if (endian == EndianType.LITTLE) @@ -96,5 +117,4 @@ public static unsafe ulong ToULong(Span data, EndianType endian) return unchecked((ulong)((uint)((bigSource[4] << 24) | (bigSource[5] << 16) | (bigSource[6] << 8) | bigSource[7]) | ((long)((bigSource[0] << 24) | (bigSource[1] << 16) | (bigSource[2] << 8) | bigSource[3]) << 32))); } -} -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/src/Snowberry.IO.Common/BinaryUtils.cs b/src/Snowberry.IO.Common/BinaryUtils.cs index 035a1ad..d3c9c58 100644 --- a/src/Snowberry.IO.Common/BinaryUtils.cs +++ b/src/Snowberry.IO.Common/BinaryUtils.cs @@ -25,11 +25,11 @@ public static void ApplyAlignment(ref long position, byte alignment) } /// - /// Calcualtes the number of padding bytes required to align the start of a data structure. + /// Calculates the number of padding bytes required to align the start of a data structure. /// /// The current position/offset. /// The alignment. - /// The number of padding bytest required to align the start of a data structure. + /// The number of padding bytes required to align the start of a data structure. #if NET6_0_OR_GREATER [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] #else diff --git a/src/Snowberry.IO.Common/Reader/Analyzer.cs b/src/Snowberry.IO.Common/Reader/Analyzer.cs index 902da3d..c5f6c9c 100644 --- a/src/Snowberry.IO.Common/Reader/Analyzer.cs +++ b/src/Snowberry.IO.Common/Reader/Analyzer.cs @@ -1,4 +1,5 @@ -using Snowberry.IO.Common.Reader.Interfaces; +using System; +using Snowberry.IO.Common.Reader.Interfaces; namespace Snowberry.IO.Common.Reader; @@ -17,8 +18,8 @@ public abstract class Analyzer /// Analyzes the read bytes in the specified . /// /// The current reader instance. - /// The buffer to analyze. + /// The span of bytes to analyze. /// The amount of bytes that were read. - /// The offset in the for the new read data, othwerise . - public abstract void AnalyzeReadBytes(IEndianReader reader, byte[] buffer, int amount, long offset = -1); + /// The offset in the for the new read data, otherwise . + public abstract void AnalyzeReadBytes(IEndianReader reader, Span buffer, int amount, long offset = -1); } \ No newline at end of file diff --git a/src/Snowberry.IO.Common/Reader/Interfaces/IEndianReader.Span.cs b/src/Snowberry.IO.Common/Reader/Interfaces/IEndianReader.Span.cs new file mode 100644 index 0000000..a675ccd --- /dev/null +++ b/src/Snowberry.IO.Common/Reader/Interfaces/IEndianReader.Span.cs @@ -0,0 +1,16 @@ +using System; + +namespace Snowberry.IO.Common.Reader.Interfaces; + +public partial interface IEndianReader +{ + /// + /// Reads a specified number of bytes from the current stream into a buffer. + /// + /// The span of bytes where the read bytes will be stored. + /// + /// The total number of bytes read into the buffer. This can be less than the number + /// of bytes requested if that many bytes are not currently available, or zero if the end of the stream is reached. + /// + int Read(Span buffer); +} diff --git a/src/Snowberry.IO.Common/Reader/Interfaces/IEndianReader.cs b/src/Snowberry.IO.Common/Reader/Interfaces/IEndianReader.cs index 5d5c5d9..6d316a5 100644 --- a/src/Snowberry.IO.Common/Reader/Interfaces/IEndianReader.cs +++ b/src/Snowberry.IO.Common/Reader/Interfaces/IEndianReader.cs @@ -4,178 +4,225 @@ namespace Snowberry.IO.Common.Reader.Interfaces; -public interface IEndianReader : IDisposable +/// +/// Binary reader with different endianness support. +/// +public partial interface IEndianReader : IDisposable { /// - /// Reads the bytes from the current stream and writes them to another stream. + /// Reads bytes from the current stream and writes them to the specified destination stream. /// /// The stream to which the contents of the current stream will be copied. void CopyTo(Stream destination); /// - /// Reads the bytes from the current stream and writes them to another stream. + /// Reads a specified number of bytes from the current stream and writes them to the specified destination stream. /// /// The stream to which the contents of the current stream will be copied. - /// The length to copy. - /// The size in bytes of the buffer. This value must be greater than zero. + /// The number of bytes to copy. + /// The size of the buffer in bytes. This value must be greater than zero. void CopyTo(Stream destination, int length, int bufferSize = 0x14000); /// - /// Reads a single byte. + /// Reads a single byte from the current stream. /// - /// This won't check if the current is greater than the current . - /// This should not be used in loops, if the case from the example above occurs then it could lead to an infinite loop as the last result from the buffer is returned. + /// + /// This method does not check if the current is greater than the current . + /// Avoid using this method in loops as it can lead to infinite loops if the end of the stream is reached. /// - /// A single byte. + /// The byte read. byte ReadByte(); /// - /// Same as with the difference that the result will be if the is greater than the . + /// Reads a single byte from the current stream, returning -1 if the is greater than the . /// - /// A single byte as . + /// The byte read as an , or -1 if the end of the stream is reached. int ReadByteSafe(); /// - /// Reads the given amount of bytes into the specified . + /// Reads a specified number of bytes from the current stream into a buffer. /// - /// The target buffer. - /// The zero-based byte offset in buffer at which to begin storing the data read from the current stream. - /// The maximum number of bytes to be read from the current stream. + /// The buffer to store the read bytes. + /// The zero-based byte offset in the buffer at which to begin storing the data read from the stream. + /// The maximum number of bytes to read from the current stream. /// /// The total number of bytes read into the buffer. This can be less than the number - /// of bytes requested if that many bytes are not currently available, or zero (0) - /// if the end of the stream has been reached. + /// of bytes requested if that many bytes are not currently available, or zero if the end of the stream is reached. /// int Read(byte[] buffer, int offset, int byteCount); /// - /// Reads the given amount of bytes into the . + /// Reads a specified number of bytes from the current stream into the internal buffer. /// - /// The byte count. - /// The zero-based byte offset in buffer at which to begin storing the data. + /// The number of bytes to read. + /// The zero-based byte offset in the buffer at which to begin storing the data. /// /// The total number of bytes read into the buffer. This can be less than the number - /// of bytes requested if that many bytes are not currently available, or zero (0) - /// if the end of the stream has been reached. + /// of bytes requested if that many bytes are not currently available, or zero if the end of the stream is reached. /// int ReadInInternalBuffer(int byteCount, int offset); /// - /// Reads the given amount of bytes from the current stream. + /// Reads a specified number of bytes from the current stream. /// - /// The amount of bytes to read from the current stream. - /// The read buffer. + /// The number of bytes to read. + /// A byte array containing the read bytes. byte[] ReadBytes(int count); /// - /// Reads a CString from the current stream. + /// Reads a null-terminated string from the current stream. /// - /// A zero () terminated , or if the end of the input stream is reached. + /// A zero-terminated string, or if the end of the stream is reached. string? ReadCString(); /// - /// Reads a fixed sized CString. + /// Reads a fixed-size null-terminated string from the current stream. /// /// - /// The reader's will automatically be adjusted by using . - /// For instance if the read is `Hello` (length ) but the expected is it will increase the by the remaining . + /// The reader's will be adjusted by the specified .
+ /// For example, if the read string is "Hello" (length 5) but the expected size is 8, the will be increased by the remaining 3 bytes. ///
- /// The CString size - /// Automatically adjust the to the remaining unread count of . - /// The CString with the fixed size. + /// The size of the string to read. + /// Automatically adjust the by the unread count of . + /// The fixed-size string. string? ReadSizedCString(int size, bool adjustPosition = true); /// - /// Reads a size-prefixed string from the current stream and returns the data as a string. + /// Reads a size-prefixed string from the current stream. /// - /// The size-prefixed string, or if the end of the input stream is reached. + /// The size-prefixed string, or if the end of the stream is reached. string? ReadString(); /// - /// Reads a line of characters from the current stream and returns the data as a string. + /// Reads a line of characters from the current stream. /// /// - /// Supported: + /// Supported line endings: /// /// CR LF /// LF /// /// - /// The next line from the input stream, or if the end of the input stream is reached. + /// The next line from the input stream, or if the end of the stream is reached. string? ReadLine(); /// - /// Read hash. + /// Reads a hash from the current stream. /// - /// The hash. + /// The read hash. Sha1 ReadSha1(); /// - /// Read padding with the given alignment. + /// Reads padding bytes aligned to the specified alignment. /// - /// The padding alignment. + /// The alignment for the padding. void ReadAlignment(byte alignment); + /// + /// Reads a 64-bit signed integer from the current stream with the specified endianness. + /// + /// The endianness to use. + /// The read 64-bit signed integer. long ReadLong(EndianType endian = EndianType.LITTLE); + /// + /// Reads a 64-bit unsigned integer from the current stream with the specified endianness. + /// + /// The endianness to use. + /// The read 64-bit unsigned integer. ulong ReadULong(EndianType endian = EndianType.LITTLE); + /// + /// Reads a 32-bit unsigned integer from the current stream with the specified endianness. + /// + /// The endianness to use. + /// The read 32-bit unsigned integer. uint ReadUInt32(EndianType endian = EndianType.LITTLE); + /// + /// Reads a 16-bit unsigned integer from the current stream with the specified endianness. + /// + /// The endianness to use. + /// The read 16-bit unsigned integer. ushort ReadUInt16(EndianType endian = EndianType.LITTLE); + /// + /// Reads a 32-bit signed integer from the current stream with the specified endianness. + /// + /// The endianness to use. + /// The read 32-bit signed integer. int ReadInt32(EndianType endian = EndianType.LITTLE); + /// + /// Reads a 16-bit signed integer from the current stream with the specified endianness. + /// + /// The endianness to use. + /// The read 16-bit signed integer. short ReadInt16(EndianType endian = EndianType.LITTLE); + /// + /// Reads a from the current stream with the specified endianness. + /// + /// The endianness to use. + /// The read . Guid ReadGuid(EndianType endian = EndianType.LITTLE); + /// + /// Reads a 32-bit floating-point value from the current stream with the specified endianness. + /// + /// The endianness to use. + /// The read 32-bit floating-point value. float ReadFloat(EndianType endian = EndianType.LITTLE); + /// + /// Reads a 64-bit floating-point value from the current stream with the specified endianness. + /// + /// The endianness to use. + /// The read 64-bit floating-point value. double ReadDouble(EndianType endian = EndianType.LITTLE); /// - /// Reads the complete stream starting from the current position into a byte array. + /// Reads the remaining bytes from the current position to the end of the stream into a byte array. /// - /// The max buffer size. - /// The rest of the stream as byte array. + /// The maximum buffer size in bytes. + /// A byte array containing the remaining bytes in the stream. byte[] ReadUntilEnd(int maxBufferSize = 16 * 1024); /// - /// Reads a value from the current stream and advances the current of the stream by one . + /// Reads a value from the current stream and advances the position by one byte. /// - /// if the byte is nonzero; otherwise, . + /// if the byte is non-zero; otherwise, . bool ReadBool(); /// - /// Reads in a 64-bit integer in compressed format. + /// Reads a 64-bit integer in a compressed format from the current stream. /// /// - /// More info. + /// For more information, see LEB128. /// - /// A 64-bit integer in compressed format. + /// The read 64-bit integer in compressed format. long Read7BitEncodedLong(); /// - /// Reads in a 32-bit integer in compressed format. + /// Reads a 32-bit integer in a compressed format from the current stream. /// /// - /// More info. + /// For more information, see LEB128. /// - /// A 32-bit integer in compressed format. + /// The read 32-bit integer in compressed format. int Read7BitEncodedInt(); /// - /// Enables a mode that only a specific area will be viewed. + /// Enables a view mode that restricts access to a specific region of the stream. /// - /// The viewed region range. + /// The region to restrict access to. void EnableRegionView(RegionRange region); /// - /// Ensures that the current size is at least as much as the given . - /// When the buffer is smaller, a new empty buffer will be initialized. + /// Ensures that the internal buffer size is at least the specified size. + /// If the current buffer is smaller, a new buffer will be initialized. /// - /// The minimum buffer size. + /// The minimum buffer size in bytes. void EnsureBufferSize(int bufferSize); /// @@ -184,59 +231,59 @@ public interface IEndianReader : IDisposable void DisableRegionView(); /// - /// Determines whether the reader can read any new data. + /// Gets a value indicating whether the reader can read any new data. /// bool CanReadData { get; } /// - /// The stream's length. + /// Gets the length of the stream. /// long Length { get; } /// - /// The stream's position. + /// Gets or sets the position within the stream. /// long Position { get; set; } /// - /// Returns the actual current position without any adjustments from the mode. + /// Gets the actual current position in the stream, without adjustments from region view mode. /// long ActualPosition { get; } /// - /// Returns the actual current length without any adjustments from the mode. + /// Gets the actual current length of the stream, without adjustments from region view mode. /// long ActualLength { get; } /// - /// Determines whether the current reader is in a view. + /// Gets a value indicating whether the reader is in region view mode. /// bool IsRegionViewEnabled { get; } /// - /// The stream view. + /// Gets the current region view of the stream. /// RegionRange RegionView { get; } /// - /// Returns the internal buffer of the reader. + /// Gets the internal buffer of the reader. /// #pragma warning disable CA1819 // Properties should not return arrays byte[] Buffer { get; } #pragma warning restore CA1819 // Properties should not return arrays /// - /// Returns whether the reader is dispoed or not. + /// Gets a value indicating whether the reader is disposed. /// bool Disposed { get; } /// - /// The encoding that will be used. + /// Gets the encoding used by the reader. /// Encoding Encoding { get; } /// - /// The current analyzer that is used. + /// Gets or sets the current analyzer used by the reader. /// Analyzer? Analyzer { get; set; } } diff --git a/src/Snowberry.IO.Common/Reader/RegionRange.cs b/src/Snowberry.IO.Common/Reader/RegionRange.cs index 27fd634..42e2f50 100644 --- a/src/Snowberry.IO.Common/Reader/RegionRange.cs +++ b/src/Snowberry.IO.Common/Reader/RegionRange.cs @@ -13,7 +13,6 @@ public struct RegionRange : IEquatable public long Size; #pragma warning restore CA1051 // Do not declare visible instance fields -#if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER /// /// Uses the to create a new . /// @@ -41,7 +40,6 @@ public static RegionRange ToRegionRange(Range range) { return range; } -#endif /// public override readonly bool Equals(object? obj) diff --git a/src/Snowberry.IO.Common/Sha1.cs b/src/Snowberry.IO.Common/Sha1.cs index 8e37f24..e14b0a1 100644 --- a/src/Snowberry.IO.Common/Sha1.cs +++ b/src/Snowberry.IO.Common/Sha1.cs @@ -5,22 +5,29 @@ namespace Snowberry.IO.Common; /// -/// Representation of a SHA1 hash. +/// Represents a SHA-1 hash. /// public struct Sha1 : IEquatable { // Reference: https://en.wikipedia.org/wiki/SHA-1 + /// + /// The size of the SHA-1 hash in bytes. + /// public const int StructSize = 20; + + /// + /// A instance with all components set to zero. + /// public static readonly Sha1 Zero = new(null as byte[]); private uint _a, _b, _c, _d, _e; /// - /// Creates a new using the given data. + /// Initializes a new instance of the struct using the given buffer. /// - /// The must have a length of minimum 20 bytes when specified. - /// The optional pre-defined buffer. + /// The must have a length of at least 20 bytes when specified. + /// The optional pre-defined buffer containing the SHA-1 hash. public Sha1(byte[]? buffer) { if (buffer == null) @@ -29,18 +36,11 @@ public Sha1(byte[]? buffer) return; } - if (buffer.Length < 20) - throw new ArgumentException($"{nameof(Sha1)} buffer must be a minimum of {StructSize} bytes in length", nameof(buffer)); - - _a = (uint)(buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24)); - _b = (uint)(buffer[4] | (buffer[5] << 8) | (buffer[6] << 16) | (buffer[7] << 24)); - _c = (uint)(buffer[8] | (buffer[9] << 8) | (buffer[10] << 16) | (buffer[11] << 24)); - _d = (uint)(buffer[12] | (buffer[13] << 8) | (buffer[14] << 16) | (buffer[15] << 24)); - _e = (uint)(buffer[16] | (buffer[17] << 8) | (buffer[18] << 16) | (buffer[19] << 24)); + From(buffer.AsSpan()); } /// - /// Creates a new using the given components. + /// Initializes a new instance of the struct using the given components. /// /// The A component. /// The B component. @@ -56,13 +56,22 @@ public Sha1(uint a, uint b, uint c, uint d, uint e) _e = e; } -#if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER /// - /// Create using the given span data. + /// Initializes a new instance of the struct using the given span data. /// - /// The must have a length of minimum 20 bytes. - /// The span data. + /// The must have a length of at least 20 bytes. + /// The span data containing the SHA-1 hash. public Sha1(ReadOnlySpan data) + { + From(data); + } + + /// + /// Populates the instance using the given span data. + /// + /// The span data containing the SHA-1 hash. + /// Thrown when the length of is less than 20 bytes. + public void From(ReadOnlySpan data) { if (data.Length < 20) throw new ArgumentException($"{nameof(Sha1)} must be a minimum of {StructSize} bytes in length", nameof(data)); @@ -73,13 +82,14 @@ public Sha1(ReadOnlySpan data) _d = (uint)(data[12] | (data[13] << 8) | (data[14] << 16) | (data[15] << 24)); _e = (uint)(data[16] | (data[17] << 8) | (data[18] << 16) | (data[19] << 24)); } -#endif /// - /// Create using the given . + /// Initializes a new instance of the struct using the given hash string. /// - /// The has to have a length of . - /// The hash text. + /// The must have a length of 40 characters. + /// The hash string. + /// Thrown when is null. + /// Thrown when the length of is not 40 characters. public unsafe Sha1(string hash) { #if NET6_0_OR_GREATER @@ -123,22 +133,22 @@ public unsafe Sha1(string hash) } /// - /// Compare to . + /// Compares two instances for inequality. /// - /// The left hash. - /// The right hash. - /// Whether both hashes aren't equal to each other. + /// The left instance. + /// The right instance. + /// if the instances are not equal; otherwise, . public static bool operator !=(Sha1 a, Sha1 b) { return !(a == b); } /// - /// Compare to + /// Compares two instances for equality. /// - /// The left hash. - /// The right hash. - /// Whether both hashes are equal to each other. + /// The left instance. + /// The right instance. + /// if the instances are equal; otherwise, . public static bool operator ==(Sha1 a, Sha1 b) { return a.Equals(b); @@ -147,7 +157,7 @@ public unsafe Sha1(string hash) /// public readonly bool Equals(Sha1 other) { - return _a == other._a && _b == other._b && _c == other._c && _d == other._d; + return _a == other._a && _b == other._b && _c == other._c && _d == other._d && _e == other._e; } /// @@ -156,6 +166,7 @@ public override readonly bool Equals(object? obj) return obj is Sha1 other && other.Equals(this); } + /// public override readonly int GetHashCode() { #if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER @@ -166,16 +177,14 @@ public override readonly int GetHashCode() } /// - /// Returns the value as a byte array. + /// Returns the value as a byte array. /// - /// The value as a byte array. + /// A byte array containing the SHA-1 hash. public readonly byte[] GetHashBuffer() { - // NOTE(VNC:) - // Take all hash components and turn their 32 bits into smaller 8 bit parts. - - return new byte[] - { + // Converts the SHA-1 hash components into a byte array. + return + [ // A (byte)(_a & 0xFF), (byte)((_a >> 8) & 0xFF), @@ -205,17 +214,17 @@ public readonly byte[] GetHashBuffer() (byte)((_e >> 8) & 0xFF), (byte)((_e >> 16) & 0xFF), (byte)((_e >> 24) & 0xFF) - }; + ]; } /// - /// Returns the hash as a string. + /// Returns the hash as a string. /// - /// The hash as a string value. + /// A string representation of the SHA-1 hash. public override readonly unsafe string ToString() { - // NOTE(VNC): The length will be 2 chars * (5*4) components. - var output = new StringBuilder(10); + // Converts the SHA-1 hash components into a hexadecimal string. + var output = new StringBuilder(40); uint* components = stackalloc uint[5]; components[0] = _a; @@ -227,11 +236,11 @@ public override readonly unsafe string ToString() for (int i = 0; i < 5; i++) output.Append(string.Concat( ((byte)(components[i] & 0xFF)).ToString("X2", CultureInfo.InvariantCulture), - ((byte)(components[i] >> 0x8)).ToString("X2", CultureInfo.InvariantCulture), - ((byte)(components[i] >> 0x10)).ToString("X2", CultureInfo.InvariantCulture), - ((byte)(components[i] >> 0x18)).ToString("X2", CultureInfo.InvariantCulture) + ((byte)(components[i] >> 8)).ToString("X2", CultureInfo.InvariantCulture), + ((byte)(components[i] >> 16)).ToString("X2", CultureInfo.InvariantCulture), + ((byte)(components[i] >> 24)).ToString("X2", CultureInfo.InvariantCulture) )); return output.ToString(); } -} +} \ No newline at end of file diff --git a/src/Snowberry.IO.Common/Snowberry.IO.Common.csproj b/src/Snowberry.IO.Common/Snowberry.IO.Common.csproj index b7da0de..41733c5 100644 --- a/src/Snowberry.IO.Common/Snowberry.IO.Common.csproj +++ b/src/Snowberry.IO.Common/Snowberry.IO.Common.csproj @@ -22,5 +22,4 @@ \ - diff --git a/src/Snowberry.IO.Tests/ReaderTests.cs b/src/Snowberry.IO.Tests/ReaderTests.cs index eab5eff..a75fb38 100644 --- a/src/Snowberry.IO.Tests/ReaderTests.cs +++ b/src/Snowberry.IO.Tests/ReaderTests.cs @@ -25,11 +25,11 @@ private void EnsureBufferSize(int setValue, int expectedValue) [Fact] private void ReadUntilEnd() { - var memory = new MemoryStream(new byte[] - { + var memory = new MemoryStream( + [ 33, 92, 82,33, 92, 82,33, 92, 82,33, 92, 82,33, 92, 82,33, 92, 82,33, 92, 82,33, 92, 82, 33, 92, 82,33, 92, 82,33, 92, 82,33, 92, 82,33, 92, 82,33, 92, 82,33, 92, 82,33, 92, 82 - }); + ]); using var reader = new EndianStreamReader(memory); byte[] data = reader.ReadUntilEnd(); diff --git a/src/Snowberry.IO.Tests/SharedTests.cs b/src/Snowberry.IO.Tests/SharedTests.cs index 0a1c5cd..09e07d8 100644 --- a/src/Snowberry.IO.Tests/SharedTests.cs +++ b/src/Snowberry.IO.Tests/SharedTests.cs @@ -37,8 +37,11 @@ private void ReadWrite_SizedCString(Encoding encoding, string expected, bool isU CreateShared((x, _) => { if (isUnicode) +#if NETFRAMEWORK + actualStringPayloadSize = x.Encoding.GetByteCount(expected); +#else actualStringPayloadSize = x.Encoding.GetByteCount(expected.AsSpan()); - +#endif if (actualStringPayloadSize < minFixedSize) actualStringPayloadSize = minFixedSize; @@ -474,7 +477,7 @@ private void ReadWrite_ByteArray() { CreateShared((x, _) => { - x.Write(new byte[] { 0x12, 0x13, 0x14 }); + x.Write([0x12, 0x13, 0x14]); }, (x, _) => { diff --git a/src/Snowberry.IO.Tests/Snowberry.IO.Tests.csproj b/src/Snowberry.IO.Tests/Snowberry.IO.Tests.csproj index 1279bed..0de4227 100644 --- a/src/Snowberry.IO.Tests/Snowberry.IO.Tests.csproj +++ b/src/Snowberry.IO.Tests/Snowberry.IO.Tests.csproj @@ -1,30 +1,32 @@  - - net8.0 - enable - enable + + net8.0;net6.0;net48;net481 + enable + latest + enable - false + false - AnyCPU;x64 - + AnyCPU;x64 + - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + - - - + + + + diff --git a/src/Snowberry.IO.Tests/TestHelper.cs b/src/Snowberry.IO.Tests/TestHelper.cs index 79da5cb..5f665cb 100644 --- a/src/Snowberry.IO.Tests/TestHelper.cs +++ b/src/Snowberry.IO.Tests/TestHelper.cs @@ -4,6 +4,7 @@ using Snowberry.IO.Common.Writer.Interfaces; using Snowberry.IO.Reader; using Snowberry.IO.Writer; +using Xunit; namespace Snowberry.IO.Tests; @@ -21,5 +22,6 @@ public static void CreateShared(Action writerAction using var reader = new EndianStreamReader(mem, null, 0, encoding ?? Encoding.UTF8); readerAction(reader, mem); + Assert.Equal(reader.Length, reader.ActualPosition); } } diff --git a/src/Snowberry.IO/Extensions/ReaderExtensions.cs b/src/Snowberry.IO/Extensions/ReaderExtensions.cs index 498993e..4dddd95 100644 --- a/src/Snowberry.IO/Extensions/ReaderExtensions.cs +++ b/src/Snowberry.IO/Extensions/ReaderExtensions.cs @@ -147,8 +147,8 @@ public static Sha1 ReadSha1At(this IEndianReader reader, int offset = 0) /// The offsets/addresses for the found signatures. public static unsafe IList ScanSignatures(this IEndianReader reader, string pattern, int maxCount, long maxAddress) { - ArgumentNullException.ThrowIfNull(reader); - ArgumentNullException.ThrowIfNull(pattern); + _ = reader ?? throw new ArgumentNullException(nameof(reader)); + _ = pattern ?? throw new ArgumentNullException(nameof(pattern)); var offsets = new List(); diff --git a/src/Snowberry.IO/Reader/BaseEndianReader.Primitives.cs b/src/Snowberry.IO/Reader/BaseEndianReader.Primitives.cs new file mode 100644 index 0000000..1de9e45 --- /dev/null +++ b/src/Snowberry.IO/Reader/BaseEndianReader.Primitives.cs @@ -0,0 +1,86 @@ +using Snowberry.IO.Common; + +namespace Snowberry.IO.Reader; + +public partial class BaseEndianReader +{ + /// + public Sha1 ReadSha1() + { + Span bytes = stackalloc byte[Sha1.StructSize]; + Read(bytes); + return new(bytes); + } + + /// + public long ReadLong(EndianType endian = EndianType.LITTLE) + { + Span bytes = stackalloc byte[8]; + Read(bytes); + return BinaryEndianConverter.ToLong(bytes, endian); + } + + /// + public ulong ReadULong(EndianType endian = EndianType.LITTLE) + { + Span bytes = stackalloc byte[8]; + Read(bytes); + return BinaryEndianConverter.ToULong(bytes, endian); + } + + /// + public uint ReadUInt32(EndianType endian = EndianType.LITTLE) + { + Span bytes = stackalloc byte[4]; + Read(bytes); + return BinaryEndianConverter.ToUInt32(bytes, endian); + } + + /// + public ushort ReadUInt16(EndianType endian = EndianType.LITTLE) + { + Span bytes = stackalloc byte[2]; + Read(bytes); + return BinaryEndianConverter.ToUInt16(bytes, endian); + } + + /// + public int ReadInt32(EndianType endian = EndianType.LITTLE) + { + Span bytes = stackalloc byte[4]; + Read(bytes); + return BinaryEndianConverter.ToInt32(bytes, endian); + } + + /// + public short ReadInt16(EndianType endian = EndianType.LITTLE) + { + Span bytes = stackalloc byte[2]; + Read(bytes); + return BinaryEndianConverter.ToInt16(bytes, endian); + } + + /// + public Guid ReadGuid(EndianType endian = EndianType.LITTLE) + { + Span bytes = stackalloc byte[16]; + Read(bytes); + return BinaryEndianConverter.ToGuid(bytes, 0, endian); + } + + /// + public float ReadFloat(EndianType endian = EndianType.LITTLE) + { + Span bytes = stackalloc byte[4]; + Read(bytes); + return BinaryEndianConverter.ToFloat(bytes, endian); + } + + /// + public unsafe double ReadDouble(EndianType endian = EndianType.LITTLE) + { + Span bytes = stackalloc byte[8]; + Read(bytes); + return BinaryEndianConverter.ToDouble(bytes, endian); + } +} diff --git a/src/Snowberry.IO/Reader/BaseEndianReader.Span.cs b/src/Snowberry.IO/Reader/BaseEndianReader.Span.cs new file mode 100644 index 0000000..755d6c9 --- /dev/null +++ b/src/Snowberry.IO/Reader/BaseEndianReader.Span.cs @@ -0,0 +1,34 @@ +using System.Runtime.CompilerServices; + +namespace Snowberry.IO.Reader; + +public partial class BaseEndianReader +{ + /// + /// Reads a specified number of bytes from the current stream into a buffer. + /// + /// The span of bytes to store the read bytes. + /// + /// The total number of bytes read into the buffer. This can be less than the number + /// of bytes requested if that many bytes are not currently available, or zero if the end of the stream is reached. + /// + /// + /// This method is intended to be implemented by derived classes to provide the actual logic for reading bytes. + /// + protected abstract int InternalReadBytes(Span inBuffer); + + /// +#if NETCOREAPP3_0_OR_GREATER + [MethodImpl(MethodImplOptions.AggressiveOptimization)] +#endif + public int Read(Span buffer) + { + int read = InternalReadBytes(buffer); + + if (IsRegionViewEnabled) + _viewOffset += read; + + Analyzer?.AnalyzeReadBytes(this, buffer, read, 0); + return read; + } +} diff --git a/src/Snowberry.IO/Reader/BaseEndianReader.cs b/src/Snowberry.IO/Reader/BaseEndianReader.cs index 9362572..aae79a6 100644 --- a/src/Snowberry.IO/Reader/BaseEndianReader.cs +++ b/src/Snowberry.IO/Reader/BaseEndianReader.cs @@ -6,7 +6,10 @@ namespace Snowberry.IO.Reader; -public abstract class BaseEndianReader : IEndianReader +/// +/// Base implementation of . +/// +public abstract partial class BaseEndianReader : IEndianReader { /// /// The largest data type is the type. @@ -22,7 +25,6 @@ public abstract class BaseEndianReader : IEndianReader protected readonly Decoder _decoder; protected readonly Encoding _encoding; - protected byte[]? _charBytes; protected char[]? _charBuffer; protected int _maxCharsSize; @@ -54,7 +56,7 @@ protected BaseEndianReader(Analyzer? analyzer = null, int bufferSize = 0) : this /// The encoding to use. protected BaseEndianReader(Analyzer? analyzer, int bufferSize, Encoding encoding) { - ArgumentNullException.ThrowIfNull(encoding); + _ = encoding ?? throw new ArgumentNullException(nameof(encoding)); if (bufferSize < MinBufferSize) bufferSize = MinBufferSize; @@ -71,18 +73,27 @@ protected BaseEndianReader(Analyzer? analyzer, int bufferSize, Encoding encoding _2BytesPerChar = encoding is UnicodeEncoding; } - /// - /// The internal implementation of . - /// - /// The amount of bytes that were read. - protected abstract int InternalReadBytes(byte[] inBuffer, int offset, int byteCount); - - internal void ThrowIfDisposed() + protected void ThrowIfDisposed() { if (Disposed) throw new ObjectDisposedException(nameof(BaseEndianReader)); } + /// + /// Reads a specified number of bytes from the current stream into a buffer. + /// + /// The buffer to store the read bytes. + /// The zero-based byte offset in the buffer at which to begin storing the data read from the stream. + /// The maximum number of bytes to read from the current stream. + /// + /// The total number of bytes read into the buffer. This can be less than the number + /// of bytes requested if that many bytes are not currently available, or zero if the end of the stream is reached. + /// + /// + /// This method is intended to be implemented by derived classes to provide the actual logic for reading bytes. + /// + protected abstract int InternalReadBytes(byte[] inBuffer, int offset, int byteCount); + /// public abstract void CopyTo(Stream destination); @@ -90,7 +101,11 @@ internal void ThrowIfDisposed() public abstract void CopyTo(Stream destination, int length, int bufferSize = 0x14000); /// +#if NETCOREAPP3_0_OR_GREATER [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] +#else + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif public byte ReadByte() { ReadInInternalBuffer(1, 0); @@ -98,7 +113,11 @@ public byte ReadByte() } /// +#if NETCOREAPP3_0_OR_GREATER [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] +#else + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif public int ReadByteSafe() { int read = ReadInInternalBuffer(1, 0); @@ -110,22 +129,22 @@ public int ReadByteSafe() } /// - [MethodImpl(MethodImplOptions.AggressiveOptimization)] +#if NETCOREAPP3_0_OR_GREATER + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] +#else + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif public int Read(byte[] buffer, int offset, int byteCount) { - ArgumentNullException.ThrowIfNull(buffer); - - int read = InternalReadBytes(buffer, offset, byteCount); - - if (IsRegionViewEnabled) - _viewOffset += read; - - Analyzer?.AnalyzeReadBytes(this, buffer, read, offset); - return read; + return Read(buffer.AsSpan()[offset..byteCount]); } /// - [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] +#if NETCOREAPP3_0_OR_GREATER + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] +#else + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif public int ReadInInternalBuffer(int byteCount, int offset) { return Read(Buffer, offset, byteCount); @@ -194,7 +213,7 @@ public byte[] ReadBytes(int count) var decoder = _encoding.GetDecoder(); Span decodedCharSpan = stackalloc char[_maxCharsSize]; - _charBytes ??= new byte[MaxCharBytesSize]; + Span charBytes = stackalloc byte[MaxCharBytesSize]; int readLength; int n; @@ -204,17 +223,17 @@ public byte[] ReadBytes(int count) { readLength = size > MaxCharBytesSize ? MaxCharBytesSize : size; - n = Read(_charBytes, 0, readLength); + n = Read(charBytes[..readLength]); if (n == 0) throw new EndOfStreamException(); size -= n; - int x = _charBytes.AsSpan().IndexOf(0); - AppendCharacters(decoder, x > 0 ? _charBytes.AsSpan()[..x] : _charBytes.AsSpan(), decodedCharSpan, out int decodedCharCount); + int endIndex = charBytes.IndexOf(0); + AppendCharacters(decoder, endIndex > 0 ? charBytes[..endIndex] : charBytes, decodedCharSpan, out int decodedCharCount); - if (size > 0 && x != -1) + if (size > 0 && endIndex != -1) { if (adjustPosition) Position += size; @@ -254,26 +273,33 @@ public byte[] ReadBytes(int count) int position = 0; int n; int readLength; - int charsRead; var sb = new StringBuilder(); - _charBytes ??= new byte[MaxCharBytesSize]; + Span charBytes = stackalloc byte[MaxCharBytesSize]; _charBuffer ??= new char[_maxCharsSize]; do { readLength = ((stringLength - position) > MaxCharBytesSize) ? MaxCharBytesSize : (stringLength - position); - n = Read(_charBytes, 0, readLength); + n = Read(charBytes[..readLength]); if (n == 0) throw new EndOfStreamException(); - charsRead = _decoder.GetChars(_charBytes, 0, n, _charBuffer, 0); - if (position == 0 && n == stringLength) - return new string(_charBuffer, 0, charsRead); +#if NETSTANDARD2_0 + return _encoding.GetString(charBytes[..n].ToArray()); +#else + return _encoding.GetString(charBytes[..n]); +#endif + +#if NETSTANDARD2_0 + int charsRead = _decoder.GetChars(charBytes[..n].ToArray(), 0, n, _charBuffer, 0); +#else + int charsRead = _decoder.GetChars(charBytes[..n], _charBuffer, flush: false); +#endif sb.Append(_charBuffer, 0, charsRead); position += n; @@ -328,34 +354,28 @@ public byte[] ReadBytes(int count) private void AppendCharacters(Decoder decoder, Span bytes, Span chars, out int decodedCharCount) { - // Single byte encoding... - //if (_encoding.IsSingleByte) - //{ - // decodedCharCount = 0; - // _stringBuilder.Append((char)characterValue); - // return; - //} + _ = decoder ?? throw new ArgumentNullException(nameof(decoder)); - ArgumentNullException.ThrowIfNull(decoder); - - // Multi byte encoding... - //bytes[0] = (byte)characterValue; +#if NETSTANDARD2_0 + char[] charBuffer = new char[chars.Length]; + decodedCharCount = decoder.GetChars(bytes.ToArray(), 0, bytes.Length, charBuffer, 0); + if (decodedCharCount != 0) + _stringBuilder.Append(charBuffer.AsSpan()[..decodedCharCount].ToArray()); +#else decodedCharCount = decoder.GetChars(bytes, chars, false); if (decodedCharCount != 0) _stringBuilder.Append(chars[..decodedCharCount]); +#endif } /// - public Sha1 ReadSha1() - { - ReadInInternalBuffer(Sha1.StructSize, 0); - return new(_buffer); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] +#if NETCOREAPP3_0_OR_GREATER + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] +#else + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif public void ReadAlignment(byte alignment) { long position = Position; @@ -363,69 +383,6 @@ public void ReadAlignment(byte alignment) Position = position; } - /// - public long ReadLong(EndianType endian = EndianType.LITTLE) - { - ReadInInternalBuffer(8, 0); - return BinaryEndianConverter.ToLong(Buffer, endian); - } - - /// - public ulong ReadULong(EndianType endian = EndianType.LITTLE) - { - ReadInInternalBuffer(8, 0); - return BinaryEndianConverter.ToULong(Buffer, endian); - } - - /// - public uint ReadUInt32(EndianType endian = EndianType.LITTLE) - { - ReadInInternalBuffer(4, 0); - return BinaryEndianConverter.ToUInt32(Buffer, endian); - } - - /// - public ushort ReadUInt16(EndianType endian = EndianType.LITTLE) - { - ReadInInternalBuffer(2, 0); - return BinaryEndianConverter.ToUInt16(Buffer, endian); - } - - /// - public int ReadInt32(EndianType endian = EndianType.LITTLE) - { - ReadInInternalBuffer(4, 0); - return BinaryEndianConverter.ToInt32(Buffer, endian); - } - - /// - public short ReadInt16(EndianType endian = EndianType.LITTLE) - { - ReadInInternalBuffer(2, 0); - return BinaryEndianConverter.ToInt16(Buffer, endian); - } - - /// - public Guid ReadGuid(EndianType endian = EndianType.LITTLE) - { - ReadInInternalBuffer(16, 0); - return BinaryEndianConverter.ToGuid(Buffer, 0, endian); - } - - /// - public float ReadFloat(EndianType endian = EndianType.LITTLE) - { - ReadInInternalBuffer(4, 0); - return BinaryEndianConverter.ToFloat(Buffer, endian); - } - - /// - public unsafe double ReadDouble(EndianType endian = EndianType.LITTLE) - { - ReadInInternalBuffer(8, 0); - return BinaryEndianConverter.ToDouble(Buffer, endian); - } - /// public byte[] ReadUntilEnd(int maxBufferSize = 16 * 1024) { diff --git a/src/Snowberry.IO/Reader/EndianStreamReader.Span.cs b/src/Snowberry.IO/Reader/EndianStreamReader.Span.cs new file mode 100644 index 0000000..28dcca2 --- /dev/null +++ b/src/Snowberry.IO/Reader/EndianStreamReader.Span.cs @@ -0,0 +1,24 @@ +namespace Snowberry.IO.Reader; + +public partial class EndianStreamReader +{ + /// + protected override int InternalReadBytes(Span inBuffer) + { + _ = Stream ?? throw new NullReferenceException(nameof(Stream)); + + // NOTE(VNC): Important because of region views. + if (!CanReadData) + return 0; + +#if NETSTANDARD2_0 + byte[] tempBuffer = new byte[inBuffer.Length]; + int read = Stream.Read(tempBuffer, 0, tempBuffer.Length); + tempBuffer.CopyTo(inBuffer); + return read; +#else + return Stream.Read(inBuffer); +#endif + + } +} diff --git a/src/Snowberry.IO/Reader/EndianStreamReader.cs b/src/Snowberry.IO/Reader/EndianStreamReader.cs index 7a2bfb0..753f7c9 100644 --- a/src/Snowberry.IO/Reader/EndianStreamReader.cs +++ b/src/Snowberry.IO/Reader/EndianStreamReader.cs @@ -6,7 +6,7 @@ namespace Snowberry.IO.Reader; /// /// Supports reading different endian types from streams. /// -public class EndianStreamReader : BaseEndianReader +public partial class EndianStreamReader : BaseEndianReader { protected Stream? _stream; @@ -38,7 +38,7 @@ public EndianStreamReader(Stream? stream, Analyzer? analyzer, int bufferSize, En /// public override void CopyTo(Stream destination) { - ArgumentNullException.ThrowIfNull(destination); + _ = destination ?? throw new ArgumentNullException(nameof(destination)); _ = Stream ?? throw new NullReferenceException(nameof(Stream)); Stream.CopyTo(destination); @@ -47,7 +47,7 @@ public override void CopyTo(Stream destination) /// public override void CopyTo(Stream destination, int length, int bufferSize = 0x14000) { - ArgumentNullException.ThrowIfNull(destination); + _ = destination ?? throw new ArgumentNullException(nameof(destination)); _ = Stream ?? throw new NullReferenceException(nameof(Stream)); byte[] buffer = new byte[bufferSize]; @@ -57,7 +57,7 @@ public override void CopyTo(Stream destination, int length, int bufferSize = 0x1 if (IsRegionViewEnabled) _viewOffset += read; - Analyzer?.AnalyzeReadBytes(this, buffer, read, 0); + Analyzer?.AnalyzeReadBytes(this, buffer.AsSpan(), read, 0); destination.Write(buffer, 0, read); length -= read; @@ -67,14 +67,7 @@ public override void CopyTo(Stream destination, int length, int bufferSize = 0x1 /// protected override int InternalReadBytes(byte[] inBuffer, int offset, int byteCount) { - _ = Stream ?? throw new NullReferenceException(nameof(Stream)); - - // NOTE(VNC): Important because of region views. - if (!CanReadData) - return 0; - //throw new EndOfStreamException(); - - return Stream.Read(inBuffer, offset, byteCount); + return InternalReadBytes(inBuffer.AsSpan()[offset..byteCount]); } /// diff --git a/src/Snowberry.IO/Reader/Win32ProcessReader.Span.cs b/src/Snowberry.IO/Reader/Win32ProcessReader.Span.cs new file mode 100644 index 0000000..e2b863b --- /dev/null +++ b/src/Snowberry.IO/Reader/Win32ProcessReader.Span.cs @@ -0,0 +1,35 @@ +using static Snowberry.IO.Utils.Win32Helper; + +namespace Snowberry.IO.Reader; + +public partial class Win32ProcessReader +{ + /// + protected override int InternalReadBytes(Span inBuffer) + { + uint lpflOldProtect = 0u; + int read = 0; + + int byteCount = inBuffer.Length; + + // Update protection on memory region + // https://docs.microsoft.com/en-us/windows/win32/memory/memory-protection-constants + // -> 0x2 -> PAGE_READONLY + VirtualProtectEx(_processHandle, _position, new((uint)byteCount), 0x2, ref lpflOldProtect); + + // Read into buffer + byte[] tempBuffer = new byte[byteCount]; + if (!ReadProcessMemory(_processHandle, _position, tempBuffer, byteCount, ref read)) + return 0; + + // Reset memory region protection + VirtualProtectEx(_processHandle, _position, new((uint)byteCount), lpflOldProtect, ref lpflOldProtect); + + tempBuffer.CopyTo(inBuffer); + + _position += byteCount; + Analyzer?.AnalyzeReadBytes(this, inBuffer, byteCount, 0); + + return byteCount; + } +} diff --git a/src/Snowberry.IO/Reader/Win32ProcessReader.cs b/src/Snowberry.IO/Reader/Win32ProcessReader.cs index 3f81b7e..257bdff 100644 --- a/src/Snowberry.IO/Reader/Win32ProcessReader.cs +++ b/src/Snowberry.IO/Reader/Win32ProcessReader.cs @@ -9,8 +9,10 @@ namespace Snowberry.IO.Reader; /// Used for reading memory from a windows process. /// /// String operations may not work (except ). +#if NET5_0_OR_GREATER [SupportedOSPlatform("windows")] -public class Win32ProcessReader : BaseEndianReader +#endif +public partial class Win32ProcessReader : BaseEndianReader { protected long _position; private readonly IntPtr _processHandle; @@ -43,25 +45,7 @@ public Win32ProcessReader(IntPtr handle, long startPosition, Analyzer? analyzer /// protected override int InternalReadBytes(byte[] inBuffer, int offset, int byteCount) { - uint lpflOldProtect = 0u; - int read = 0; - - // Update protection on memory region - // https://docs.microsoft.com/en-us/windows/win32/memory/memory-protection-constants - // -> 0x2 -> PAGE_READONLY - VirtualProtectEx(_processHandle, _position, new((uint)byteCount), 0x2, ref lpflOldProtect); - - // Read into buffer - if (!ReadProcessMemory(_processHandle, _position, inBuffer, byteCount, ref read)) - return 0; - - // Reset memory region protection - VirtualProtectEx(_processHandle, _position, new((uint)byteCount), lpflOldProtect, ref lpflOldProtect); - - _position += byteCount; - Analyzer?.AnalyzeReadBytes(this, inBuffer, byteCount, 0); - - return byteCount; + return InternalReadBytes(inBuffer.AsSpan()[offset..byteCount]); } /// @@ -85,7 +69,7 @@ public override void CopyTo(Stream destination, int length, int bufferSize = 819 /// Whether the dynamic library got successfully injected. public bool InjectDLL(string filePath) { - ArgumentNullException.ThrowIfNull(filePath); + _ = filePath ?? throw new ArgumentNullException(nameof(filePath)); filePath = Path.GetFullPath(filePath); diff --git a/src/Snowberry.IO/Snowberry.IO.csproj b/src/Snowberry.IO/Snowberry.IO.csproj index c8cdc2c..513144f 100644 --- a/src/Snowberry.IO/Snowberry.IO.csproj +++ b/src/Snowberry.IO/Snowberry.IO.csproj @@ -1,11 +1,11 @@  - net8.0;net7.0;net6.0 + net8.0;net7.0;net6.0;netstandard2.1;netstandard2.0 enable enable true - 7.0-all + latest Binary reader and writer that supports different endian types. diff --git a/src/Snowberry.IO/Utils/Win32Helper.cs b/src/Snowberry.IO/Utils/Win32Helper.cs index 58f05d8..439cf97 100644 --- a/src/Snowberry.IO/Utils/Win32Helper.cs +++ b/src/Snowberry.IO/Utils/Win32Helper.cs @@ -3,7 +3,9 @@ namespace Snowberry.IO.Utils; +#if NET5_0_OR_GREATER [SupportedOSPlatform("windows")] +#endif internal static class Win32Helper { [DllImport("kernel32.dll", SetLastError = true)] diff --git a/src/Snowberry.IO/Writer/EndianStreamWriter.cs b/src/Snowberry.IO/Writer/EndianStreamWriter.cs index 1c8f261..520b05b 100644 --- a/src/Snowberry.IO/Writer/EndianStreamWriter.cs +++ b/src/Snowberry.IO/Writer/EndianStreamWriter.cs @@ -63,7 +63,11 @@ public unsafe IEndianWriter Write(Guid value, EndianType endian = EndianType.LIT data[14] = guidBytes[14]; data[15] = guidBytes[15]; +#if NETSTANDARD2_0 + base.Write(data.ToArray()); +#else base.Write(data); +#endif return this; } @@ -196,12 +200,16 @@ public IEndianWriter Write(Sha1 value) /// public IEndianWriter WriteSizedCString(string text, int size) { - ArgumentNullException.ThrowIfNull(text); + _ = text ?? throw new ArgumentNullException(nameof(text)); if (size < text.Length) throw new ArgumentOutOfRangeException($"{nameof(size)}", "The size must be greater than the text length."); +#if NETSTANDARD2_0 + int byteCount = _encoding.GetByteCount(text); +#else int byteCount = _encoding.GetByteCount(text.AsSpan()); +#endif if (byteCount > size) throw new IOException($"The byte count of the text `{byteCount}` is greater than the specified size `{size}`.\nCheck the data of the text or the encoding that is used."); @@ -223,7 +231,8 @@ public IEndianWriter WriteSizedCString(string text, int size) public IEndianWriter WriteLine(string text) { WriteStringCharacters(text); - base.Write(Environment.NewLine.AsSpan()); + WriteStringCharacters(Environment.NewLine); + return this; } @@ -240,9 +249,13 @@ public IEndianWriter WritePadding(byte alignment) /// public IEndianWriter WriteStringCharacters(string text) { - ArgumentNullException.ThrowIfNull(text); + _ = text ?? throw new ArgumentNullException(nameof(text)); +#if NETSTANDARD2_0 + base.Write(Encoding.GetBytes(text)); +#else base.Write(text.AsSpan()); +#endif return this; } @@ -329,6 +342,33 @@ IEndianWriter IEndianWriter.Write(bool value) return this; } +#if NETSTANDARD2_0_OR_GREATER + public void Write7BitEncodedInt64(long value) + { + // Licensed to the .NET Foundation under one or more agreements. + // The .NET Foundation licenses this file to you under the MIT license. + // + // https://github.com/dotnet/corert/blob/master/src/System.Private.CoreLib/shared/System/IO/BinaryWriter.cs + // + + ulong uValue = (ulong)value; + + // Write out an int 7 bits at a time. The high bit of the byte, + // when on, tells reader to continue reading more bytes. + // + // Using the constants 0x7F and ~0x7F below offers smaller + // codegen than using the constant 0x80. + + while (uValue > 0x7Fu) + { + Write((byte)((uint)uValue | ~0x7Fu)); + uValue >>= 7; + } + + Write((byte)uValue); + } +#endif + /// public long Position {