45 Commits
1.3.4 ... 1.4.0

Author SHA1 Message Date
Matt Nadareski
8df3fe2473 Bump version 2024-04-23 14:06:04 -04:00
Matt Nadareski
a2bb83ab9a Make Linux publish script executable 2024-04-23 14:05:37 -04:00
Matt Nadareski
bcb77f2de6 Add publish scripts 2024-04-23 14:05:19 -04:00
Matt Nadareski
35d4c22a20 Clean usings 2024-04-23 14:01:51 -04:00
Matt Nadareski
893cb73b0d Slight formatting update 2024-04-23 13:40:39 -04:00
Matt Nadareski
607a0375c7 Better(?) string reading extensions 2024-04-23 13:23:09 -04:00
Matt Nadareski
87d1dfe266 Add ReadType extensions 2024-04-23 10:51:54 -04:00
Matt Nadareski
d8f16b12b5 Use correct variable name in BinaryReader extensions 2024-04-23 09:52:53 -04:00
Matt Nadareski
cf08658b1e Add tests for default BinaryReader methods for consistency 2024-04-23 09:38:58 -04:00
Matt Nadareski
9547e1a355 Add decimal extensions for byte array and stream 2024-04-23 09:34:15 -04:00
Matt Nadareski
20cdb5b65e Fix summary of BinaryReaderExtensions 2024-04-22 15:25:32 -04:00
Matt Nadareski
fc10565186 Add Int128 extensions for BinaryReader 2024-04-22 15:22:14 -04:00
Matt Nadareski
c824db6b18 Add GUID tests for BinaryReader 2024-04-22 15:18:00 -04:00
Matt Nadareski
a3c26fed38 Add GUID extensions for BinaryReader 2024-04-22 15:15:02 -04:00
Matt Nadareski
2dc259d978 Reorganize binary reader extensions a bit 2024-04-22 15:13:05 -04:00
Matt Nadareski
2d950ddc54 Add float and double tests for BinaryReader extensions 2024-04-22 15:02:04 -04:00
Matt Nadareski
294c5c26df Simplify ParentablePath logic a bit 2024-04-22 01:10:02 -04:00
Matt Nadareski
dc356767ab Simplify normalization for comparison 2024-04-22 00:32:41 -04:00
Matt Nadareski
0b6c7e9885 Whitespace 2024-04-22 00:30:50 -04:00
Matt Nadareski
339da9fc16 Hah, duplicate 2024-04-22 00:22:52 -04:00
Matt Nadareski
7bc18c6952 Ensure tests aren't packable 2024-04-22 00:17:13 -04:00
Matt Nadareski
ec3afeed73 Use new enumeration wrappers 2024-04-18 16:59:25 -04:00
Matt Nadareski
658ceb5d0e Add fake Enumeration versions for net20 and net35 2024-04-18 16:57:21 -04:00
Matt Nadareski
e9a905d4a3 Use extensions in PathTool 2024-04-18 16:47:36 -04:00
Matt Nadareski
1127e96f26 Convert ListEmpty to use new safe enumerators 2024-04-18 15:34:44 -04:00
Matt Nadareski
89a8ad3703 Add safe enumeration and file enumeration 2024-04-18 15:31:38 -04:00
Matt Nadareski
ff7f7c0b8c Fix namespace for extension tests 2024-04-18 15:06:27 -04:00
Matt Nadareski
32e948653a Bump version 2024-04-18 11:59:52 -04:00
Matt Nadareski
a93da97bbd Add floating-point extensions for byte arrays and streams 2024-04-18 11:55:25 -04:00
Matt Nadareski
65bdcc563d Add BinaryReader extension tests 2024-04-18 11:44:32 -04:00
Matt Nadareski
f3c5499754 Simplify method descriptions for BinaryReader extensions 2024-04-18 11:39:24 -04:00
Matt Nadareski
02819f006b Add byte array and stream extensions tests; fix issues 2024-04-18 11:30:53 -04:00
Matt Nadareski
48512b486c Fix null terminator byte check 2024-04-18 11:06:19 -04:00
Matt Nadareski
6c289ee015 Sync byte array extensions with stream extensions 2024-04-18 11:01:28 -04:00
Matt Nadareski
973e19366a Fix ROCS test 2024-04-17 00:39:52 -04:00
Matt Nadareski
f5ef39ab76 Return 0 read bytes if overreading 2024-04-17 00:36:01 -04:00
Matt Nadareski
e4deb10db6 Bump version 2024-04-16 22:50:06 -04:00
Matt Nadareski
90d2382d5d Fix ROCS reading 2024-04-16 22:49:54 -04:00
Matt Nadareski
c48d62fd5e Bump version 2024-04-16 21:49:55 -04:00
Matt Nadareski
0f10dc2ae4 Move more classes, fix build 2024-04-16 21:47:41 -04:00
Matt Nadareski
c648ad9f5e Move extensions to new namespace 2024-04-16 21:45:26 -04:00
Matt Nadareski
cd01e170fe Port ReadOnlyBitStream from Compression, add tests 2024-04-16 20:13:27 -04:00
Matt Nadareski
ecaec1d44a Add single-stream constructor to ROCS 2024-04-16 20:02:54 -04:00
Matt Nadareski
ce521d92ca Fix test build issue 2024-04-16 13:38:08 -04:00
Matt Nadareski
a69b6dfa3a Move composite stream to new namespace 2024-04-16 13:27:23 -04:00
25 changed files with 2884 additions and 656 deletions

View File

@@ -0,0 +1,271 @@
using System;
using System.IO;
using System.Linq;
using SabreTools.IO.Extensions;
using Xunit;
namespace SabreTools.IO.Test.Extensions
{
public class BinaryReaderExtensionsTests
{
private static readonly byte[] _bytes =
[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
];
[Fact]
public void ReadInt16Test()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
short read = br.ReadInt16();
Assert.Equal(0x0100, read);
}
[Fact]
public void ReadInt16BigEndianTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
short read = br.ReadInt16BigEndian();
Assert.Equal(0x0001, read);
}
[Fact]
public void ReadUInt16Test()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
ushort read = br.ReadUInt16();
Assert.Equal(0x0100, read);
}
[Fact]
public void ReadUInt16BigEndianTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
ushort read = br.ReadUInt16BigEndian();
Assert.Equal(0x0001, read);
}
[Fact]
public void ReadInt32Test()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
int read = br.ReadInt32();
Assert.Equal(0x03020100, read);
}
[Fact]
public void ReadInt32BigEndianTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
int read = br.ReadInt32BigEndian();
Assert.Equal(0x00010203, read);
}
[Fact]
public void ReadUInt32Test()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
uint read = br.ReadUInt32();
Assert.Equal((uint)0x03020100, read);
}
[Fact]
public void ReadUInt32BigEndianTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
uint read = br.ReadUInt32BigEndian();
Assert.Equal((uint)0x00010203, read);
}
[Fact]
public void ReadSingleTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
float expected = BitConverter.Int32BitsToSingle(0x03020100);
float read = br.ReadSingle();
Assert.Equal(expected, read);
}
[Fact]
public void ReadSingleBigEndianTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
float expected = BitConverter.Int32BitsToSingle(0x00010203);
float read = br.ReadSingleBigEndian();
Assert.Equal(expected, read);
}
[Fact]
public void ReadInt64Test()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
long read = br.ReadInt64();
Assert.Equal(0x0706050403020100, read);
}
[Fact]
public void ReadInt64BigEndianTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
long read = br.ReadInt64BigEndian();
Assert.Equal(0x0001020304050607, read);
}
[Fact]
public void ReadUInt64Test()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
ulong read = br.ReadUInt64();
Assert.Equal((ulong)0x0706050403020100, read);
}
[Fact]
public void ReadUInt64BigEndianTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
ulong read = br.ReadUInt64BigEndian();
Assert.Equal((ulong)0x0001020304050607, read);
}
[Fact]
public void ReadDoubleTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
double expected = BitConverter.Int64BitsToDouble(0x0706050403020100);
double read = br.ReadDouble();
Assert.Equal(expected, read);
}
[Fact]
public void ReadDoubleBigEndianTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
double expected = BitConverter.Int64BitsToDouble(0x0001020304050607);
double read = br.ReadDoubleBigEndian();
Assert.Equal(expected, read);
}
[Fact]
public void ReadGuidTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
var expected = new Guid(_bytes);
Guid read = br.ReadGuid();
Assert.Equal(expected, read);
}
[Fact]
public void ReadGuidBigEndianTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
var expected = new Guid(_bytes.Reverse().ToArray());
Guid read = br.ReadGuidBigEndian();
Assert.Equal(expected, read);
}
#if NET7_0_OR_GREATER
[Fact]
public void ReadInt128Test()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
var expected = new Int128(BitConverter.ToUInt64(_bytes, 0), BitConverter.ToUInt64(_bytes, 8));
Int128 read = br.ReadInt128();
Assert.Equal(expected, read);
}
[Fact]
public void ReadInt128BigEndianTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
var reversed = _bytes.Reverse().ToArray();
var expected = new Int128(BitConverter.ToUInt64(reversed, 0), BitConverter.ToUInt64(reversed, 8));
Int128 read = br.ReadInt128BigEndian();
Assert.Equal(expected, read);
}
[Fact]
public void ReadUInt128Test()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
var expected = new UInt128(BitConverter.ToUInt64(_bytes, 0), BitConverter.ToUInt64(_bytes, 8));
UInt128 read = br.ReadUInt128();
Assert.Equal(expected, read);
}
[Fact]
public void ReadUInt128BigEndianTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
var reversed = _bytes.Reverse().ToArray();
var expected = new UInt128(BitConverter.ToUInt64(reversed, 0), BitConverter.ToUInt64(reversed, 8));
UInt128 read = br.ReadUInt128BigEndian();
Assert.Equal(expected, read);
}
#endif
[Fact]
public void ReadTypeExplicitTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
var expected = new TestStructExplicit
{
FirstValue = 0x03020100,
SecondValue = 0x07060504,
ThirdValue = 0x0504,
FourthValue = 0x0706,
};
var read = br.ReadType<TestStructExplicit>();
Assert.Equal(expected.FirstValue, read.FirstValue);
Assert.Equal(expected.SecondValue, read.SecondValue);
Assert.Equal(expected.ThirdValue, read.ThirdValue);
Assert.Equal(expected.FourthValue, read.FourthValue);
}
[Fact]
public void ReadTypeSequentialTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
var expected = new TestStructSequential
{
FirstValue = 0x03020100,
SecondValue = 0x07060504,
ThirdValue = 0x0908,
FourthValue = 0x0B0A,
};
var read = br.ReadType<TestStructSequential>();
Assert.Equal(expected.FirstValue, read.FirstValue);
Assert.Equal(expected.SecondValue, read.SecondValue);
Assert.Equal(expected.ThirdValue, read.ThirdValue);
Assert.Equal(expected.FourthValue, read.FourthValue);
}
// TODO: Add byte[], char[] tests
// TODO: Add decimal tests
// TODO: Add string reading tests
}
}

View File

@@ -0,0 +1,286 @@
using System;
using System.Linq;
using SabreTools.IO.Extensions;
using Xunit;
namespace SabreTools.IO.Test.Extensions
{
public class ByteArrayExtensionsTests
{
private static readonly byte[] _bytes =
[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
];
[Fact]
public void ReadByteTest()
{
int offset = 0;
byte read = _bytes.ReadByte(ref offset);
Assert.Equal(0x00, read);
}
[Fact]
public void ReadByteValueTest()
{
int offset = 0;
byte read = _bytes.ReadByteValue(ref offset);
Assert.Equal(0x00, read);
}
[Fact]
public void ReadBytesTest()
{
int offset = 0, length = 4;
byte[] read = _bytes.ReadBytes(ref offset, length);
Assert.Equal(length, read.Length);
Assert.True(read.SequenceEqual(_bytes.Take(length)));
}
[Fact]
public void ReadSByteTest()
{
int offset = 0;
sbyte read = _bytes.ReadSByte(ref offset);
Assert.Equal(0x00, read);
}
[Fact]
public void ReadCharTest()
{
int offset = 0;
char read = _bytes.ReadChar(ref offset);
Assert.Equal('\0', read);
}
[Fact]
public void ReadInt16Test()
{
int offset = 0;
short read = _bytes.ReadInt16(ref offset);
Assert.Equal(0x0100, read);
}
[Fact]
public void ReadInt16BigEndianTest()
{
int offset = 0;
short read = _bytes.ReadInt16BigEndian(ref offset);
Assert.Equal(0x0001, read);
}
[Fact]
public void ReadUInt16Test()
{
int offset = 0;
ushort read = _bytes.ReadUInt16(ref offset);
Assert.Equal(0x0100, read);
}
[Fact]
public void ReadUInt16BigEndianTest()
{
int offset = 0;
ushort read = _bytes.ReadUInt16BigEndian(ref offset);
Assert.Equal(0x0001, read);
}
[Fact]
public void ReadInt32Test()
{
int offset = 0;
int read = _bytes.ReadInt32(ref offset);
Assert.Equal(0x03020100, read);
}
[Fact]
public void ReadInt32BigEndianTest()
{
int offset = 0;
int read = _bytes.ReadInt32BigEndian(ref offset);
Assert.Equal(0x00010203, read);
}
[Fact]
public void ReadUInt32Test()
{
int offset = 0;
uint read = _bytes.ReadUInt32(ref offset);
Assert.Equal((uint)0x03020100, read);
}
[Fact]
public void ReadUInt32BigEndianTest()
{
int offset = 0;
uint read = _bytes.ReadUInt32BigEndian(ref offset);
Assert.Equal((uint)0x00010203, read);
}
[Fact]
public void ReadSingleTest()
{
int offset = 0;
float expected = BitConverter.Int32BitsToSingle(0x03020100);
float read = _bytes.ReadSingle(ref offset);
Assert.Equal(expected, read);
}
[Fact]
public void ReadSingleBigEndianTest()
{
int offset = 0;
float expected = BitConverter.Int32BitsToSingle(0x00010203);
float read = _bytes.ReadSingleBigEndian(ref offset);
Assert.Equal(expected, read);
}
[Fact]
public void ReadInt64Test()
{
int offset = 0;
long read = _bytes.ReadInt64(ref offset);
Assert.Equal(0x0706050403020100, read);
}
[Fact]
public void ReadInt64BigEndianTest()
{
int offset = 0;
long read = _bytes.ReadInt64BigEndian(ref offset);
Assert.Equal(0x0001020304050607, read);
}
[Fact]
public void ReadUInt64Test()
{
int offset = 0;
ulong read = _bytes.ReadUInt64(ref offset);
Assert.Equal((ulong)0x0706050403020100, read);
}
[Fact]
public void ReadUInt64BigEndianTest()
{
int offset = 0;
ulong read = _bytes.ReadUInt64BigEndian(ref offset);
Assert.Equal((ulong)0x0001020304050607, read);
}
[Fact]
public void ReadDoubleTest()
{
int offset = 0;
double expected = BitConverter.Int64BitsToDouble(0x0706050403020100);
double read = _bytes.ReadDouble(ref offset);
Assert.Equal(expected, read);
}
[Fact]
public void ReadDoubleBigEndianTest()
{
int offset = 0;
double expected = BitConverter.Int64BitsToDouble(0x0001020304050607);
double read = _bytes.ReadDoubleBigEndian(ref offset);
Assert.Equal(expected, read);
}
[Fact]
public void ReadGuidTest()
{
int offset = 0;
var expected = new Guid(_bytes);
Guid read = _bytes.ReadGuid(ref offset);
Assert.Equal(expected, read);
}
[Fact]
public void ReadGuidBigEndianTest()
{
int offset = 0;
var expected = new Guid(_bytes.Reverse().ToArray());
Guid read = _bytes.ReadGuidBigEndian(ref offset);
Assert.Equal(expected, read);
}
#if NET7_0_OR_GREATER
[Fact]
public void ReadInt128Test()
{
int offset = 0;
var expected = new Int128(BitConverter.ToUInt64(_bytes, 0), BitConverter.ToUInt64(_bytes, 8));
Int128 read = _bytes.ReadInt128(ref offset);
Assert.Equal(expected, read);
}
[Fact]
public void ReadInt128BigEndianTest()
{
int offset = 0;
var reversed = _bytes.Reverse().ToArray();
var expected = new Int128(BitConverter.ToUInt64(reversed, 0), BitConverter.ToUInt64(reversed, 8));
Int128 read = _bytes.ReadInt128BigEndian(ref offset);
Assert.Equal(expected, read);
}
[Fact]
public void ReadUInt128Test()
{
int offset = 0;
var expected = new UInt128(BitConverter.ToUInt64(_bytes, 0), BitConverter.ToUInt64(_bytes, 8));
UInt128 read = _bytes.ReadUInt128(ref offset);
Assert.Equal(expected, read);
}
[Fact]
public void ReadUInt128BigEndianTest()
{
int offset = 0;
var reversed = _bytes.Reverse().ToArray();
var expected = new UInt128(BitConverter.ToUInt64(reversed, 0), BitConverter.ToUInt64(reversed, 8));
UInt128 read = _bytes.ReadUInt128BigEndian(ref offset);
Assert.Equal(expected, read);
}
#endif
[Fact]
public void ReadTypeExplicitTest()
{
int offset = 0;
var expected = new TestStructExplicit
{
FirstValue = 0x03020100,
SecondValue = 0x07060504,
ThirdValue = 0x0504,
FourthValue = 0x0706,
};
var read = _bytes.ReadType<TestStructExplicit>(ref offset);
Assert.Equal(expected.FirstValue, read.FirstValue);
Assert.Equal(expected.SecondValue, read.SecondValue);
Assert.Equal(expected.ThirdValue, read.ThirdValue);
Assert.Equal(expected.FourthValue, read.FourthValue);
}
[Fact]
public void ReadTypeSequentialTest()
{
int offset = 0;
var expected = new TestStructSequential
{
FirstValue = 0x03020100,
SecondValue = 0x07060504,
ThirdValue = 0x0908,
FourthValue = 0x0B0A,
};
var read = _bytes.ReadType<TestStructSequential>(ref offset);
Assert.Equal(expected.FirstValue, read.FirstValue);
Assert.Equal(expected.SecondValue, read.SecondValue);
Assert.Equal(expected.ThirdValue, read.ThirdValue);
Assert.Equal(expected.FourthValue, read.FourthValue);
}
// TODO: Add decimal tests
// TODO: Add string reading tests
}
}

View File

@@ -0,0 +1,138 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using SabreTools.IO.Extensions;
using Xunit;
namespace SabreTools.IO.Test.Extensions
{
public class EnumerableExtensionsTests
{
[Fact]
public void SafeEnumerateEmptyTest()
{
var source = Enumerable.Empty<string>();
var safe = source.SafeEnumerate();
var list = safe.ToList();
Assert.Empty(list);
}
[Fact]
public void SafeEnumerateNoErrorTest()
{
var source = new List<string> { "a", "ab", "abc" };
var safe = source.SafeEnumerate();
var list = safe.ToList();
Assert.Equal(3, list.Count);
}
[Fact]
public void SafeEnumerateErrorMidTest()
{
var source = new List<string> { "a", "ab", "abc" };
var wrapper = new ErrorEnumerable(source);
var safe = wrapper.SafeEnumerate();
var list = safe.ToList();
Assert.Equal(2, list.Count);
}
[Fact]
public void SafeEnumerateErrorLastTest()
{
var source = new List<string> { "a", "ab", "abc", "abcd" };
var wrapper = new ErrorEnumerable(source);
var safe = wrapper.SafeEnumerate();
var list = safe.ToList();
Assert.Equal(2, list.Count);
}
/// <summary>
/// Fake enumerable that uses <see cref="ErrorEnumerator"/>
/// </summary>
private class ErrorEnumerable : IEnumerable<string>
{
/// <summary>
/// Enumerator to use during enumeration
/// </summary>
private readonly ErrorEnumerator _enumerator;
public ErrorEnumerable(IEnumerable<string> source)
{
_enumerator = new ErrorEnumerator(source);
}
/// <inheritdoc/>
public IEnumerator<string> GetEnumerator() => _enumerator;
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => _enumerator;
}
/// <summary>
/// Fake enumerator that throws an exception every other item while moving to the next item
/// </summary>
private class ErrorEnumerator : IEnumerator<string>
{
/// <inheritdoc/>
public string Current
{
get
{
if (_index == -1)
throw new InvalidOperationException();
return _enumerator.Current;
}
}
/// <inheritdoc/>
object IEnumerator.Current => Current;
/// <summary>
/// Enumerator from the source enumerable
/// </summary>
private readonly IEnumerator<string> _enumerator;
/// <summary>
/// Enumerators start before the data
/// </summary>
private int _index = -1;
public ErrorEnumerator(IEnumerable<string> source)
{
_enumerator = source.GetEnumerator();
}
/// <inheritdoc/>
public void Dispose() { }
/// <inheritdoc/>
public bool MoveNext()
{
// Move to the next item, if possible
bool moved = _enumerator.MoveNext();
if (!moved)
return false;
// Get the next real item
_index++;
// Every other move, throw an exception
if (_index % 2 == 1)
throw new Exception("Access issue for this item in the enumerable");
return true;
}
/// <inheritdoc/>
public void Reset()
{
_enumerator.Reset();
_index = -1;
}
}
}
}

View File

@@ -1,6 +1,7 @@
using SabreTools.IO.Extensions;
using Xunit;
namespace SabreTools.IO.Test
namespace SabreTools.IO.Test.Extensions
{
public class IOExtensionsTests
{

View File

@@ -0,0 +1,280 @@
using System;
using System.IO;
using System.Linq;
using SabreTools.IO.Extensions;
using Xunit;
namespace SabreTools.IO.Test.Extensions
{
public class StreamExtensionsTests
{
private static readonly byte[] _bytes =
[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
];
[Fact]
public void ReadByteValueTest()
{
var stream = new MemoryStream(_bytes);
byte read = stream.ReadByteValue();
Assert.Equal(0x00, read);
}
[Fact]
public void ReadBytesTest()
{
var stream = new MemoryStream(_bytes);
int length = 4;
byte[] read = stream.ReadBytes(length);
Assert.Equal(length, read.Length);
Assert.True(read.SequenceEqual(_bytes.Take(length)));
}
[Fact]
public void ReadSByteTest()
{
var stream = new MemoryStream(_bytes);
sbyte read = stream.ReadSByte();
Assert.Equal(0x00, read);
}
[Fact]
public void ReadCharTest()
{
var stream = new MemoryStream(_bytes);
char read = stream.ReadChar();
Assert.Equal('\0', read);
}
[Fact]
public void ReadInt16Test()
{
var stream = new MemoryStream(_bytes);
short read = stream.ReadInt16();
Assert.Equal(0x0100, read);
}
[Fact]
public void ReadInt16BigEndianTest()
{
var stream = new MemoryStream(_bytes);
short read = stream.ReadInt16BigEndian();
Assert.Equal(0x0001, read);
}
[Fact]
public void ReadUInt16Test()
{
var stream = new MemoryStream(_bytes);
ushort read = stream.ReadUInt16();
Assert.Equal(0x0100, read);
}
[Fact]
public void ReadUInt16BigEndianTest()
{
var stream = new MemoryStream(_bytes);
ushort read = stream.ReadUInt16BigEndian();
Assert.Equal(0x0001, read);
}
[Fact]
public void ReadInt32Test()
{
var stream = new MemoryStream(_bytes);
int read = stream.ReadInt32();
Assert.Equal(0x03020100, read);
}
[Fact]
public void ReadInt32BigEndianTest()
{
var stream = new MemoryStream(_bytes);
int read = stream.ReadInt32BigEndian();
Assert.Equal(0x00010203, read);
}
[Fact]
public void ReadUInt32Test()
{
var stream = new MemoryStream(_bytes);
uint read = stream.ReadUInt32();
Assert.Equal((uint)0x03020100, read);
}
[Fact]
public void ReadUInt32BigEndianTest()
{
var stream = new MemoryStream(_bytes);
uint read = stream.ReadUInt32BigEndian();
Assert.Equal((uint)0x00010203, read);
}
[Fact]
public void ReadSingleTest()
{
var stream = new MemoryStream(_bytes);
float expected = BitConverter.Int32BitsToSingle(0x03020100);
float read = stream.ReadSingle();
Assert.Equal(expected, read);
}
[Fact]
public void ReadSingleBigEndianTest()
{
var stream = new MemoryStream(_bytes);
float expected = BitConverter.Int32BitsToSingle(0x00010203);
float read = stream.ReadSingleBigEndian();
Assert.Equal(expected, read);
}
[Fact]
public void ReadInt64Test()
{
var stream = new MemoryStream(_bytes);
long read = stream.ReadInt64();
Assert.Equal(0x0706050403020100, read);
}
[Fact]
public void ReadInt64BigEndianTest()
{
var stream = new MemoryStream(_bytes);
long read = stream.ReadInt64BigEndian();
Assert.Equal(0x0001020304050607, read);
}
[Fact]
public void ReadUInt64Test()
{
var stream = new MemoryStream(_bytes);
ulong read = stream.ReadUInt64();
Assert.Equal((ulong)0x0706050403020100, read);
}
[Fact]
public void ReadUInt64BigEndianTest()
{
var stream = new MemoryStream(_bytes);
ulong read = stream.ReadUInt64BigEndian();
Assert.Equal((ulong)0x0001020304050607, read);
}
[Fact]
public void ReadDoubleTest()
{
var stream = new MemoryStream(_bytes);
double expected = BitConverter.Int64BitsToDouble(0x0706050403020100);
double read = stream.ReadDouble();
Assert.Equal(expected, read);
}
[Fact]
public void ReadDoubleBigEndianTest()
{
var stream = new MemoryStream(_bytes);
double expected = BitConverter.Int64BitsToDouble(0x0001020304050607);
double read = stream.ReadDoubleBigEndian();
Assert.Equal(expected, read);
}
[Fact]
public void ReadGuidTest()
{
var stream = new MemoryStream(_bytes);
var expected = new Guid(_bytes);
Guid read = stream.ReadGuid();
Assert.Equal(expected, read);
}
[Fact]
public void ReadGuidBigEndianTest()
{
var stream = new MemoryStream(_bytes);
var expected = new Guid(_bytes.Reverse().ToArray());
Guid read = stream.ReadGuidBigEndian();
Assert.Equal(expected, read);
}
#if NET7_0_OR_GREATER
[Fact]
public void ReadInt128Test()
{
var stream = new MemoryStream(_bytes);
var expected = new Int128(BitConverter.ToUInt64(_bytes, 0), BitConverter.ToUInt64(_bytes, 8));
Int128 read = stream.ReadInt128();
Assert.Equal(expected, read);
}
[Fact]
public void ReadInt128BigEndianTest()
{
var stream = new MemoryStream(_bytes);
var reversed = _bytes.Reverse().ToArray();
var expected = new Int128(BitConverter.ToUInt64(reversed, 0), BitConverter.ToUInt64(reversed, 8));
Int128 read = stream.ReadInt128BigEndian();
Assert.Equal(expected, read);
}
[Fact]
public void ReadUInt128Test()
{
var stream = new MemoryStream(_bytes);
var expected = new UInt128(BitConverter.ToUInt64(_bytes, 0), BitConverter.ToUInt64(_bytes, 8));
UInt128 read = stream.ReadUInt128();
Assert.Equal(expected, read);
}
[Fact]
public void ReadUInt128BigEndianTest()
{
var stream = new MemoryStream(_bytes);
var reversed = _bytes.Reverse().ToArray();
var expected = new UInt128(BitConverter.ToUInt64(reversed, 0), BitConverter.ToUInt64(reversed, 8));
UInt128 read = stream.ReadUInt128BigEndian();
Assert.Equal(expected, read);
}
#endif
[Fact]
public void ReadTypeExplicitTest()
{
var stream = new MemoryStream(_bytes);
var expected = new TestStructExplicit
{
FirstValue = 0x03020100,
SecondValue = 0x07060504,
ThirdValue = 0x0504,
FourthValue = 0x0706,
};
var read = stream.ReadType<TestStructExplicit>();
Assert.Equal(expected.FirstValue, read.FirstValue);
Assert.Equal(expected.SecondValue, read.SecondValue);
Assert.Equal(expected.ThirdValue, read.ThirdValue);
Assert.Equal(expected.FourthValue, read.FourthValue);
}
[Fact]
public void ReadTypeSequentialTest()
{
var stream = new MemoryStream(_bytes);
var expected = new TestStructSequential
{
FirstValue = 0x03020100,
SecondValue = 0x07060504,
ThirdValue = 0x0908,
FourthValue = 0x0B0A,
};
var read = stream.ReadType<TestStructSequential>();
Assert.Equal(expected.FirstValue, read.FirstValue);
Assert.Equal(expected.SecondValue, read.SecondValue);
Assert.Equal(expected.ThirdValue, read.ThirdValue);
Assert.Equal(expected.FourthValue, read.FourthValue);
}
// TODO: Add decimal tests
// TODO: Add string reading tests
}
}

View File

@@ -0,0 +1,20 @@
using System.Runtime.InteropServices;
namespace SabreTools.IO.Test.Extensions
{
[StructLayout(LayoutKind.Explicit)]
internal struct TestStructExplicit
{
[FieldOffset(0)]
public int FirstValue;
[FieldOffset(4)]
public int SecondValue;
[FieldOffset(4)]
public ushort ThirdValue;
[FieldOffset(6)]
public short FourthValue;
}
}

View File

@@ -0,0 +1,16 @@
using System.Runtime.InteropServices;
namespace SabreTools.IO.Test.Extensions
{
[StructLayout(LayoutKind.Sequential)]
internal struct TestStructSequential
{
public int FirstValue;
public int SecondValue;
public ushort ThirdValue;
public short FourthValue;
}
}

View File

@@ -0,0 +1,54 @@
using System.IO;
using SabreTools.IO.Streams;
using Xunit;
namespace SabreTools.IO.Test.Streams
{
public class ReadOnlyBitStreamTests
{
[Fact]
public void DefaultConstructorTest()
{
var stream = new ReadOnlyBitStream(new MemoryStream());
Assert.Equal(0, stream.Length);
Assert.Equal(0, stream.Position);
stream = new ReadOnlyBitStream(new MemoryStream(new byte[16]));
Assert.Equal(16, stream.Length);
Assert.Equal(0, stream.Position);
}
[Fact]
public void ReadSingleBitTest()
{
byte[] data = [0b01010101];
var stream = new ReadOnlyBitStream(new MemoryStream(data));
byte? bit = stream.ReadBit();
Assert.NotNull(bit);
Assert.Equal((byte)0b00000001, bit);
Assert.Equal(1, stream.Position);
}
[Fact]
public void ReadBitsLSBTest()
{
byte[] data = [0b01010101, 0b01010101, 0b01010101, 0b01010101];
var stream = new ReadOnlyBitStream(new MemoryStream(data));
uint? bits = stream.ReadBitsLSB(4);
Assert.NotNull(bits);
Assert.Equal((byte)0b00000101, bits);
Assert.Equal(1, stream.Position);
}
[Fact]
public void ReadBitsMSBTest()
{
byte[] data = [0b01010101, 0b01010101, 0b01010101, 0b01010101];
var stream = new ReadOnlyBitStream(new MemoryStream(data));
uint? bits = stream.ReadBitsMSB(4);
Assert.NotNull(bits);
Assert.Equal((byte)0b00001010, bits);
Assert.Equal(1, stream.Position);
}
}
}

View File

@@ -1,9 +1,10 @@
using System;
using System.Collections.Generic;
using System.IO;
using SabreTools.IO.Streams;
using Xunit;
namespace SabreTools.IO.Test
namespace SabreTools.IO.Test.Streams
{
public class ReadOnlyCompositeStreamTests
{
@@ -34,6 +35,14 @@ namespace SabreTools.IO.Test
Assert.Equal(0, stream.Position);
}
[Fact]
public void SingleStreamConstructorTest()
{
var stream = new ReadOnlyCompositeStream(new MemoryStream(new byte[1024]));
Assert.Equal(1024, stream.Length);
Assert.Equal(0, stream.Position);
}
[Fact]
public void FilledArrayConstructorTest()
{
@@ -70,7 +79,9 @@ namespace SabreTools.IO.Test
var stream = new ReadOnlyCompositeStream();
byte[] buf = new byte[512];
Assert.Throws<ArgumentOutOfRangeException>(() => stream.Read(buf, 0, 512));
int read = stream.Read(buf, 0, 512);
Assert.Equal(0, read);
}
[Fact]

View File

@@ -1,174 +0,0 @@
using System;
using System.IO;
namespace SabreTools.IO
{
/// <summary>
/// Big endian reading overloads for BinaryReader
/// </summary>
public static class BinaryReaderExtensions
{
/// <summary>
/// Reads the specified number of bytes from the stream, starting from a specified point in the byte array.
/// </summary>
/// <param name="buffer">The buffer to read data into.</param>
/// <param name="index">The starting point in the buffer at which to begin reading into the buffer.</param>
/// <param name="count">The number of bytes to read.</param>
/// <returns>The number of bytes read into buffer. This might be less than the number of bytes requested if that many bytes are not available, or it might be zero if the end of the stream is reached.</returns>
public static int ReadBigEndian(this BinaryReader reader, byte[] buffer, int index, int count)
{
int retval = reader.Read(buffer, index, count);
Array.Reverse(buffer);
return retval;
}
/// <summary>
/// Reads the specified number of characters from the stream, starting from a specified point in the character array.
/// </summary>
/// <param name="buffer">The buffer to read data into.</param>
/// <param name="index">The starting point in the buffer at which to begin reading into the buffer.</param>
/// <param name="count">The number of characters to read.</param>
/// <returns>The total number of characters read into the buffer. This might be less than the number of characters requested if that many characters are not currently available, or it might be zero if the end of the stream is reached.</returns>
public static int ReadBigEndian(this BinaryReader reader, char[] buffer, int index, int count)
{
int retval = reader.Read(buffer, index, count);
Array.Reverse(buffer);
return retval;
}
/// <summary>
/// Reads the specified number of bytes from the current stream into a byte array and advances the current position by that number of bytes.
/// </summary>
/// <param name="count">The number of bytes to read. This value must be 0 or a non-negative number or an exception will occur.</param>
/// <returns>A byte array containing data read from the underlying stream. This might be less than the number of bytes requested if the end of the stream is reached.</returns>
public static byte[] ReadBytesBigEndian(this BinaryReader reader, int count)
{
byte[] retval = reader.ReadBytes(count);
Array.Reverse(retval);
return retval;
}
/// <summary>
/// Reads the specified number of characters from the current stream, returns the data in a character array, and advances the current position in accordance with the Encoding used and the specific character being read from the stream.
/// </summary>
/// <param name="count">The number of characters to read. This value must be 0 or a non-negative number or an exception will occur.</param>
/// <returns>A character array containing data read from the underlying stream. This might be less than the number of bytes requested if the end of the stream is reached.</returns>
public static char[] ReadCharsBigEndian(this BinaryReader reader, int count)
{
char[] retval = reader.ReadChars(count);
Array.Reverse(retval);
return retval;
}
/// <summary>
/// Reads a decimal value from the current stream and advances the current position of the stream by sixteen bytes.
/// </summary>
/// <returns>A decimal value read from the current stream.</returns>
public static decimal ReadDecimalBigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(16);
Array.Reverse(retval);
int i1 = BitConverter.ToInt32(retval, 0);
int i2 = BitConverter.ToInt32(retval, 4);
int i3 = BitConverter.ToInt32(retval, 8);
int i4 = BitConverter.ToInt32(retval, 12);
return new decimal([i1, i2, i3, i4]);
}
/// <summary>
/// eads an 8-byte floating point value from the current stream and advances the current position of the stream by eight bytes.
/// </summary>
/// <returns>An 8-byte floating point value read from the current stream.</returns>
public static double ReadDoubleBigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(8);
Array.Reverse(retval);
return BitConverter.ToDouble(retval, 0);
}
/// <summary>
/// Reads a 2-byte signed integer from the current stream and advances the current position of the stream by two bytes.
/// </summary>
/// <returns>A 2-byte signed integer read from the current stream.</returns>
public static short ReadInt16BigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(2);
Array.Reverse(retval);
return BitConverter.ToInt16(retval, 0);
}
/// <summary>
/// Reads a 4-byte signed integer from the current stream and advances the current position of the stream by four bytes.
/// </summary>
/// <returns>A 4-byte signed integer read from the current stream.</returns>
public static int ReadInt32BigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(4);
Array.Reverse(retval);
return BitConverter.ToInt32(retval, 0);
}
/// <summary>
/// Reads an 8-byte signed integer from the current stream and advances the current position of the stream by eight bytes.
/// </summary>
/// <returns>An 8-byte signed integer read from the current stream.</returns>
public static long ReadInt64BigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(8);
Array.Reverse(retval);
return BitConverter.ToInt64(retval, 0);
}
/// <summary>
/// Reads a 4-byte floating point value from the current stream and advances the current position of the stream by four bytes.
/// </summary>
/// <returns>A 4-byte floating point value read from the current stream.</returns>
public static float ReadSingleBigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(4);
Array.Reverse(retval);
return BitConverter.ToSingle(retval, 0);
}
/// <summary>
/// Reads a 2-byte unsigned integer from the current stream using little-endian encoding and advances the position of the stream by two bytes.
///
/// This API is not CLS-compliant.
/// </summary>
/// <returns>A 2-byte unsigned integer read from this stream.</returns>
public static ushort ReadUInt16BigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(2);
Array.Reverse(retval);
return BitConverter.ToUInt16(retval, 0);
}
/// <summary>
/// Reads a 4-byte unsigned integer from the current stream and advances the position of the stream by four bytes.
///
/// This API is not CLS-compliant.
/// </summary>
/// <returns>A 4-byte unsigned integer read from this stream.</returns>
public static uint ReadUInt32BigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(4);
Array.Reverse(retval);
return BitConverter.ToUInt32(retval, 0);
}
/// <summary>
/// Reads an 8-byte unsigned integer from the current stream and advances the position of the stream by eight bytes.
///
/// This API is not CLS-compliant.
/// </summary>
/// <returns>An 8-byte unsigned integer read from this stream.</returns>
public static ulong ReadUInt64BigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(8);
Array.Reverse(retval);
return BitConverter.ToUInt64(retval, 0);
}
}
}

View File

@@ -1,283 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace SabreTools.IO
{
/// <summary>
/// Extensions for byte arrays
/// </summary>
/// <remarks>TODO: Add U/Int24 and U/Int48 methods</remarks>
public static class ByteArrayExtensions
{
/// <summary>
/// Read a UInt8 and increment the pointer to an array
/// </summary>
public static byte ReadByte(this byte[] content, ref int offset)
{
byte[]? buffer = content.ReadBytes(ref offset, 1);
if (buffer == null)
return default;
return buffer[0];
}
/// <summary>
/// Read a UInt8[] and increment the pointer to an array
/// </summary>
public static byte[]? ReadBytes(this byte[]? content, ref int offset, int count)
{
// If the byte array is invalid, don't do anything
if (content == null)
return null;
// If there's an invalid byte count, don't do anything
if (count <= 0 || offset >= content.Length)
return null;
// Allocate enough space for the data requested
byte[] buffer = new byte[count];
// If we have less data left than requested, only read until the end
if (offset + count >= content.Length)
count = content.Length - offset;
// If we have a non-zero count, copy the data into the array
if (count > 0)
Array.Copy(content, offset, buffer, 0, Math.Min(count, content.Length - offset));
// Increment the offset and return
offset += count;
return buffer;
}
/// <summary>
/// Read a Int8 and increment the pointer to an array
/// </summary>
public static sbyte ReadSByte(this byte[] content, ref int offset)
{
byte[]? buffer = content.ReadBytes(ref offset, 1);
if (buffer == null)
return default;
return (sbyte)buffer[0];
}
/// <summary>
/// Read a Char and increment the pointer to an array
/// </summary>
public static char ReadChar(this byte[] content, ref int offset)
{
byte[]? buffer = content.ReadBytes(ref offset, 1);
if (buffer == null)
return default;
return (char)buffer[0];
}
/// <summary>
/// Read a Int16 and increment the pointer to an array
/// </summary>
public static short ReadInt16(this byte[] content, ref int offset)
{
byte[]? buffer = content.ReadBytes(ref offset, 2);
if (buffer == null)
return default;
return BitConverter.ToInt16(buffer, 0);
}
/// <summary>
/// Read a Int16 in big-endian format and increment the pointer to an array
/// </summary>
public static short ReadInt16BigEndian(this byte[] content, ref int offset)
{
byte[]? buffer = content.ReadBytes(ref offset, 2);
if (buffer == null)
return default;
Array.Reverse(buffer);
return BitConverter.ToInt16(buffer, 0);
}
/// <summary>
/// Read a UInt16 and increment the pointer to an array
/// </summary>
public static ushort ReadUInt16(this byte[] content, ref int offset)
{
byte[]? buffer = content.ReadBytes(ref offset, 2);
if (buffer == null)
return default;
return BitConverter.ToUInt16(buffer, 0);
}
/// <summary>
/// Read a UInt16 in big-endian format and increment the pointer to an array
/// </summary>
public static ushort ReadUInt16BigEndian(this byte[] content, ref int offset)
{
byte[]? buffer = content.ReadBytes(ref offset, 2);
if (buffer == null)
return default;
Array.Reverse(buffer);
return BitConverter.ToUInt16(buffer, 0);
}
/// <summary>
/// Read a Int32 and increment the pointer to an array
/// </summary>
public static int ReadInt32(this byte[] content, ref int offset)
{
byte[]? buffer = content.ReadBytes(ref offset, 4);
if (buffer == null)
return default;
return BitConverter.ToInt32(buffer, 0);
}
/// <summary>
/// Read a Int32 in big-endian format and increment the pointer to an array
/// </summary>
public static int ReadInt32BigEndian(this byte[] content, ref int offset)
{
byte[]? buffer = content.ReadBytes(ref offset, 4);
if (buffer == null)
return default;
Array.Reverse(buffer);
return BitConverter.ToInt32(buffer, 0);
}
/// <summary>
/// Read a UInt32 and increment the pointer to an array
/// </summary>
public static uint ReadUInt32(this byte[] content, ref int offset)
{
byte[]? buffer = content.ReadBytes(ref offset, 4);
if (buffer == null)
return default;
return BitConverter.ToUInt32(buffer, 0);
}
/// <summary>
/// Read a UInt32 in big-endian format and increment the pointer to an array
/// </summary>
public static uint ReadUInt32BigEndian(this byte[] content, ref int offset)
{
byte[]? buffer = content.ReadBytes(ref offset, 4);
if (buffer == null)
return default;
Array.Reverse(buffer);
return BitConverter.ToUInt32(buffer, 0);
}
/// <summary>
/// Read a Int64 and increment the pointer to an array
/// </summary>
public static long ReadInt64(this byte[] content, ref int offset)
{
byte[]? buffer = content.ReadBytes(ref offset, 8);
if (buffer == null)
return default;
return BitConverter.ToInt64(buffer, 0);
}
/// <summary>
/// Read a Int64 in big-endian format and increment the pointer to an array
/// </summary>
public static long ReadInt64BigEndian(this byte[] content, ref int offset)
{
byte[]? buffer = content.ReadBytes(ref offset, 8);
if (buffer == null)
return default;
Array.Reverse(buffer);
return BitConverter.ToInt64(buffer, 0);
}
/// <summary>
/// Read a UInt64 and increment the pointer to an array
/// </summary>
public static ulong ReadUInt64(this byte[] content, ref int offset)
{
byte[]? buffer = content.ReadBytes(ref offset, 8);
if (buffer == null)
return default;
return BitConverter.ToUInt64(buffer, 0);
}
/// <summary>
/// Read a UInt64 in big-endian format and increment the pointer to an array
/// </summary>
public static ulong ReadUInt64BigEndian(this byte[] content, ref int offset)
{
byte[]? buffer = content.ReadBytes(ref offset, 8);
if (buffer == null)
return default;
Array.Reverse(buffer);
return BitConverter.ToUInt64(buffer, 0);
}
/// <summary>
/// Read a Guid and increment the pointer to an array
/// </summary>
public static Guid ReadGuid(this byte[] content, ref int offset)
{
byte[]? buffer = content.ReadBytes(ref offset, 16);
if (buffer == null)
return default;
return new Guid(buffer);
}
/// <summary>
/// Read a Guid in big-endian format and increment the pointer to an array
/// </summary>
public static Guid ReadGuidBigEndian(this byte[] content, ref int offset)
{
byte[]? buffer = content.ReadBytes(ref offset, 16);
if (buffer == null)
return default;
Array.Reverse(buffer);
return new Guid(buffer);
}
/// <summary>
/// Read a null-terminated string from the stream
/// </summary>
public static string? ReadString(this byte[] content, ref int offset) => content.ReadString(ref offset, Encoding.Default);
/// <summary>
/// Read a null-terminated string from the stream
/// </summary>
public static string? ReadString(this byte[] content, ref int offset, Encoding encoding)
{
if (offset >= content.Length)
return null;
byte[] nullTerminator = encoding.GetBytes(new char[] { '\0' });
int charWidth = nullTerminator.Length;
var keyChars = new List<char>();
while (offset < content.Length)
{
char c = encoding.GetChars(content, offset, charWidth)[0];
keyChars.Add(c);
offset += charWidth;
if (c == '\0')
break;
}
return new string([.. keyChars]).TrimEnd('\0');
}
}
}

View File

@@ -0,0 +1,352 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
namespace SabreTools.IO.Extensions
{
/// <summary>
/// Extensions for BinaryReader
/// </summary>
/// <remarks>TODO: Add U/Int24 and U/Int48 methods</remarks>
public static class BinaryReaderExtensions
{
/// <inheritdoc cref="BinaryReader.Read(byte[], int, int)"/>
/// <remarks>Reads in big-endian format</remarks>
public static int ReadBigEndian(this BinaryReader reader, byte[] buffer, int index, int count)
{
int retval = reader.Read(buffer, index, count);
Array.Reverse(buffer);
return retval;
}
/// <inheritdoc cref="BinaryReader.Read(char[], int, int)"/>
/// <remarks>Reads in big-endian format</remarks>
public static int ReadBigEndian(this BinaryReader reader, char[] buffer, int index, int count)
{
int retval = reader.Read(buffer, index, count);
Array.Reverse(buffer);
return retval;
}
/// <inheritdoc cref="BinaryReader.ReadBytes(int)"/>
/// <remarks>Reads in big-endian format</remarks>
public static byte[] ReadBytesBigEndian(this BinaryReader reader, int count)
{
byte[] buffer = reader.ReadBytes(count);
Array.Reverse(buffer);
return buffer;
}
/// <inheritdoc cref="BinaryReader.ReadChars(int)"/>
/// <remarks>Reads in big-endian format</remarks>
public static char[] ReadCharsBigEndian(this BinaryReader reader, int count)
{
char[] buffer = reader.ReadChars(count);
Array.Reverse(buffer);
return buffer;
}
/// <inheritdoc cref="BinaryReader.ReadInt16"/>
/// <remarks>Reads in big-endian format</remarks>
public static short ReadInt16BigEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(2);
Array.Reverse(buffer);
return BitConverter.ToInt16(buffer, 0);
}
/// <inheritdoc cref="BinaryReader.ReadUInt16"/>
/// <remarks>Reads in big-endian format</remarks>
public static ushort ReadUInt16BigEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(2);
Array.Reverse(buffer);
return BitConverter.ToUInt16(buffer, 0);
}
/// <inheritdoc cref="BinaryReader.ReadInt32"/>
/// <remarks>Reads in big-endian format</remarks>
public static int ReadInt32BigEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(4);
Array.Reverse(buffer);
return BitConverter.ToInt32(buffer, 0);
}
/// <remarks>Reads in big-endian format</remarks>
public static uint ReadUInt32BigEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(4);
Array.Reverse(buffer);
return BitConverter.ToUInt32(buffer, 0);
}
/// <inheritdoc cref="BinaryReader.ReadSingle"/>
/// <remarks>Reads in big-endian format</remarks>
public static float ReadSingleBigEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(4);
Array.Reverse(buffer);
return BitConverter.ToSingle(buffer, 0);
}
/// <inheritdoc cref="BinaryReader.ReadInt64"/>
/// <remarks>Reads in big-endian format</remarks>
public static long ReadInt64BigEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(8);
Array.Reverse(buffer);
return BitConverter.ToInt64(buffer, 0);
}
/// <inheritdoc cref="BinaryReader.ReadUInt64"/>
/// <remarks>Reads in big-endian format</remarks>
public static ulong ReadUInt64BigEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(8);
Array.Reverse(buffer);
return BitConverter.ToUInt64(buffer, 0);
}
/// <inheritdoc cref="BinaryReader.ReadDouble"/>
/// <remarks>Reads in big-endian format</remarks>
public static double ReadDoubleBigEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(8);
Array.Reverse(buffer);
return BitConverter.ToDouble(buffer, 0);
}
/// <inheritdoc cref="BinaryReader.ReadDecimal"/>
/// <remarks>Reads in big-endian format</remarks>
public static decimal ReadDecimalBigEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(16);
Array.Reverse(buffer);
int i1 = BitConverter.ToInt32(buffer, 0);
int i2 = BitConverter.ToInt32(buffer, 4);
int i3 = BitConverter.ToInt32(buffer, 8);
int i4 = BitConverter.ToInt32(buffer, 12);
return new decimal([i1, i2, i3, i4]);
}
/// <summary>
/// Read a Guid from the underlying stream
/// </summary>
public static Guid ReadGuid(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(16);
return new Guid(buffer);
}
/// <summary>
/// Read a Guid from the underlying stream
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static Guid ReadGuidBigEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(16);
Array.Reverse(buffer);
return new Guid(buffer);
}
// TODO: Determine if the reverse reads are doing what are expected
#if NET7_0_OR_GREATER
/// <summary>
/// Read an Int128 from the underlying stream
/// </summary>
public static Int128 ReadInt128(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(16);
return new Int128(BitConverter.ToUInt64(buffer, 0), BitConverter.ToUInt64(buffer, 8));
}
/// <summary>
/// Read an Int128 from the underlying stream
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static Int128 ReadInt128BigEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(16);
Array.Reverse(buffer);
return new Int128(BitConverter.ToUInt64(buffer, 0), BitConverter.ToUInt64(buffer, 8));
}
/// <summary>
/// Read a UInt128 from the underlying stream
/// </summary>
public static UInt128 ReadUInt128(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(16);
return new UInt128(BitConverter.ToUInt64(buffer, 0), BitConverter.ToUInt64(buffer, 8));
}
/// <summary>
/// Read a UInt128 from the underlying stream
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static UInt128 ReadUInt128BigEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(16);
Array.Reverse(buffer);
return new UInt128(BitConverter.ToUInt64(buffer, 0), BitConverter.ToUInt64(buffer, 8));
}
#endif
/// <summary>
/// Read a null-terminated string from the underlying stream
/// </summary>
public static string? ReadNullTerminatedString(this BinaryReader reader, Encoding encoding)
{
// Short-circuit to explicit implementations
if (encoding.Equals(Encoding.ASCII))
return reader.ReadNullTerminatedAnsiString();
else if (encoding.Equals(Encoding.Unicode))
return reader.ReadNullTerminatedUnicodeString();
if (reader.BaseStream.Position >= reader.BaseStream.Length)
return null;
List<byte> buffer = [];
while (reader.BaseStream.Position < reader.BaseStream.Length)
{
byte ch = reader.ReadByte();
buffer.Add(ch);
if (ch == '\0')
break;
}
return encoding.GetString([.. buffer]);
}
/// <summary>
/// Read a null-terminated ASCII string from the underlying stream
/// </summary>
public static string? ReadNullTerminatedAnsiString(this BinaryReader reader)
{
if (reader.BaseStream.Position >= reader.BaseStream.Length)
return null;
List<byte> buffer = [];
while (reader.BaseStream.Position < reader.BaseStream.Length)
{
byte ch = reader.ReadByte();
buffer.Add(ch);
if (ch == '\0')
break;
}
return Encoding.ASCII.GetString([.. buffer]);
}
/// <summary>
/// Read a null-terminated Unicode string from the underlying stream
/// </summary>
public static string? ReadNullTerminatedUnicodeString(this BinaryReader reader)
{
if (reader.BaseStream.Position >= reader.BaseStream.Length)
return null;
List<byte> buffer = [];
while (reader.BaseStream.Position < reader.BaseStream.Length)
{
byte[] ch = reader.ReadBytes(2);
buffer.AddRange(ch);
if (ch[0] == '\0' && ch[1] == '\0')
break;
}
return Encoding.Unicode.GetString([.. buffer]);
}
/// <summary>
/// Read a byte-prefixed ASCII string from the underlying stream
/// </summary>
public static string? ReadPrefixedAnsiString(this BinaryReader reader)
{
if (reader.BaseStream.Position >= reader.BaseStream.Length)
return null;
byte size = reader.ReadByte();
if (reader.BaseStream.Position + size >= reader.BaseStream.Length)
return null;
byte[] buffer = reader.ReadBytes(size);
return Encoding.ASCII.GetString(buffer);
}
/// <summary>
/// Read a ushort-prefixed Unicode string from the underlying stream
/// </summary>
public static string? ReadPrefixedUnicodeString(this BinaryReader reader)
{
if (reader.BaseStream.Position >= reader.BaseStream.Length)
return null;
ushort size = reader.ReadUInt16();
if (reader.BaseStream.Position + size >= reader.BaseStream.Length)
return null;
byte[] buffer = reader.ReadBytes(size);
return Encoding.Unicode.GetString(buffer);
}
/// <summary>
/// Read a string that is terminated by a newline but contains a quoted portion that
/// may also contain a newline from the stream
/// </summary>
public static string? ReadQuotedString(this BinaryReader reader)
=> reader.ReadQuotedString(Encoding.Default);
/// <summary>
/// Read a string that is terminated by a newline but contains a quoted portion that
/// may also contain a newline from the stream
/// </summary>
public static string? ReadQuotedString(this BinaryReader reader, Encoding encoding)
{
if (reader.BaseStream.Position >= reader.BaseStream.Length)
return null;
var bytes = new List<byte>();
bool openQuote = false;
while (reader.BaseStream.Position < reader.BaseStream.Length)
{
// Read the byte value
byte b = reader.ReadByte();
// If we have a quote, flip the flag
if (b == (byte)'"')
openQuote = !openQuote;
// If we have a newline not in a quoted string, exit the loop
else if (b == (byte)'\n' && !openQuote)
break;
// Add the byte to the set
bytes.Add(b);
}
var line = encoding.GetString([.. bytes]);
return line.TrimEnd();
}
/// <summary>
/// Read a <typeparamref name="T"/> from the underlying stream
/// </summary>
public static T? ReadType<T>(this BinaryReader reader)
{
int typeSize = Marshal.SizeOf(typeof(T));
byte[] buffer = reader.ReadBytes(typeSize);
var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
var data = (T?)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
handle.Free();
return data;
}
}
}

View File

@@ -0,0 +1,496 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
namespace SabreTools.IO.Extensions
{
/// <summary>
/// Extensions for byte arrays
/// </summary>
/// <remarks>TODO: Add U/Int24 and U/Int48 methods</remarks>
public static class ByteArrayExtensions
{
/// <summary>
/// Read a UInt8 and increment the pointer to an array
/// </summary>
public static byte ReadByte(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 1);
return buffer[0];
}
/// <summary>
/// Read a UInt8 and increment the pointer to an array
/// </summary>
public static byte ReadByteValue(this byte[] content, ref int offset)
=> content.ReadByte(ref offset);
/// <summary>
/// Read a UInt8[] and increment the pointer to an array
/// </summary>
public static byte[] ReadBytes(this byte[] content, ref int offset, int count)
=> ReadToBuffer(content, ref offset, count);
/// <summary>
/// Read a UInt8[] and increment the pointer to an array
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static byte[] ReadBytesBigEndian(this byte[] content, ref int offset, int count)
{
byte[] buffer = ReadToBuffer(content, ref offset, count);
Array.Reverse(buffer);
return buffer;
}
/// <summary>
/// Read an Int8 and increment the pointer to an array
/// </summary>
public static sbyte ReadSByte(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 1);
return (sbyte)buffer[0];
}
/// <summary>
/// Read a Char and increment the pointer to an array
/// </summary>
public static char ReadChar(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 1);
return (char)buffer[0];
}
/// <summary>
/// Read an Int16 and increment the pointer to an array
/// </summary>
public static short ReadInt16(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 2);
return BitConverter.ToInt16(buffer, 0);
}
/// <summary>
/// Read an Int16 and increment the pointer to an array
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static short ReadInt16BigEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 2);
Array.Reverse(buffer);
return BitConverter.ToInt16(buffer, 0);
}
/// <summary>
/// Read a UInt16 and increment the pointer to an array
/// </summary>
public static ushort ReadUInt16(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 2);
return BitConverter.ToUInt16(buffer, 0);
}
/// <summary>
/// Read a UInt16 and increment the pointer to an array
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static ushort ReadUInt16BigEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 2);
Array.Reverse(buffer);
return BitConverter.ToUInt16(buffer, 0);
}
/// <summary>
/// Read an Int32 and increment the pointer to an array
/// </summary>
public static int ReadInt32(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 4);
return BitConverter.ToInt32(buffer, 0);
}
/// <summary>
/// Read an Int32 and increment the pointer to an array
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static int ReadInt32BigEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 4);
Array.Reverse(buffer);
return BitConverter.ToInt32(buffer, 0);
}
/// <summary>
/// Read a UInt32 and increment the pointer to an array
/// </summary>
public static uint ReadUInt32(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 4);
return BitConverter.ToUInt32(buffer, 0);
}
/// <summary>
/// Read a UInt32 and increment the pointer to an array
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static uint ReadUInt32BigEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 4);
Array.Reverse(buffer);
return BitConverter.ToUInt32(buffer, 0);
}
/// <summary>
/// Read a Single and increment the pointer to an array
/// </summary>
public static float ReadSingle(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 4);
return BitConverter.ToSingle(buffer, 0);
}
/// <summary>
/// Read a Single and increment the pointer to an array
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static float ReadSingleBigEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 4);
Array.Reverse(buffer);
return BitConverter.ToSingle(buffer, 0);
}
/// <summary>
/// Read an Int64 and increment the pointer to an array
/// </summary>
public static long ReadInt64(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 8);
return BitConverter.ToInt64(buffer, 0);
}
/// <summary>
/// Read an Int64 and increment the pointer to an array
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static long ReadInt64BigEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 8);
Array.Reverse(buffer);
return BitConverter.ToInt64(buffer, 0);
}
/// <summary>
/// Read a UInt64 and increment the pointer to an array
/// </summary>
public static ulong ReadUInt64(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 8);
return BitConverter.ToUInt64(buffer, 0);
}
/// <summary>
/// Read a UInt64 and increment the pointer to an array
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static ulong ReadUInt64BigEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 8);
Array.Reverse(buffer);
return BitConverter.ToUInt64(buffer, 0);
}
/// <summary>
/// Read a Double and increment the pointer to an array
/// </summary>
public static double ReadDouble(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 8);
return BitConverter.ToDouble(buffer, 0);
}
/// <summary>
/// Read a Double and increment the pointer to an array
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static double ReadDoubleBigEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 8);
Array.Reverse(buffer);
return BitConverter.ToDouble(buffer, 0);
}
/// <summary>
/// Read a Decimal and increment the pointer to an array
/// </summary>
public static decimal ReadDecimal(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 16);
int i1 = BitConverter.ToInt32(buffer, 0);
int i2 = BitConverter.ToInt32(buffer, 4);
int i3 = BitConverter.ToInt32(buffer, 8);
int i4 = BitConverter.ToInt32(buffer, 12);
return new decimal([i1, i2, i3, i4]);
}
/// <summary>
/// Read a Decimal and increment the pointer to an array
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static decimal ReadDecimalBigEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 16);
Array.Reverse(buffer);
int i1 = BitConverter.ToInt32(buffer, 0);
int i2 = BitConverter.ToInt32(buffer, 4);
int i3 = BitConverter.ToInt32(buffer, 8);
int i4 = BitConverter.ToInt32(buffer, 12);
return new decimal([i1, i2, i3, i4]);
}
/// <summary>
/// Read a Guid and increment the pointer to an array
/// </summary>
public static Guid ReadGuid(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 16);
return new Guid(buffer);
}
/// <summary>
/// Read a Guid and increment the pointer to an array
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static Guid ReadGuidBigEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 16);
Array.Reverse(buffer);
return new Guid(buffer);
}
// TODO: Determine if the reverse reads are doing what are expected
#if NET7_0_OR_GREATER
/// <summary>
/// Read an Int128 and increment the pointer to an array
/// </summary>
public static Int128 ReadInt128(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 16);
return new Int128(BitConverter.ToUInt64(buffer, 0), BitConverter.ToUInt64(buffer, 8));
}
/// <summary>
/// Read an Int128 and increment the pointer to an array
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static Int128 ReadInt128BigEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 16);
Array.Reverse(buffer);
return new Int128(BitConverter.ToUInt64(buffer, 0), BitConverter.ToUInt64(buffer, 8));
}
/// <summary>
/// Read a UInt128 and increment the pointer to an array
/// </summary>
public static UInt128 ReadUInt128(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 16);
return new UInt128(BitConverter.ToUInt64(buffer, 0), BitConverter.ToUInt64(buffer, 8));
}
/// <summary>
/// Read a UInt128 and increment the pointer to an array
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static UInt128 ReadUInt128BigEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 16);
Array.Reverse(buffer);
return new UInt128(BitConverter.ToUInt64(buffer, 0), BitConverter.ToUInt64(buffer, 8));
}
#endif
/// <summary>
/// Read a null-terminated string from the array
/// </summary>
public static string? ReadNullTerminatedString(this byte[] content, ref int offset, Encoding encoding)
{
// Short-circuit to explicit implementations
if (encoding.Equals(Encoding.ASCII))
return content.ReadNullTerminatedAnsiString(ref offset);
else if (encoding.Equals(Encoding.Unicode))
return content.ReadNullTerminatedUnicodeString(ref offset);
if (offset >= content.Length)
return null;
List<byte> buffer = [];
while (offset < content.Length)
{
byte ch = content.ReadByteValue(ref offset);
buffer.Add(ch);
if (ch == '\0')
break;
}
return encoding.GetString([.. buffer]);
}
/// <summary>
/// Read a null-terminated ASCII string from the byte array
/// </summary>
public static string? ReadNullTerminatedAnsiString(this byte[] content, ref int offset)
{
if (offset >= content.Length)
return null;
List<byte> buffer = [];
while (offset < content.Length)
{
byte ch = content.ReadByteValue(ref offset);
buffer.Add(ch);
if (ch == '\0')
break;
}
return Encoding.ASCII.GetString([.. buffer]);
}
/// <summary>
/// Read a null-terminated Unicode string from the byte array
/// </summary>
public static string? ReadNullTerminatedUnicodeString(this byte[] content, ref int offset)
{
if (offset >= content.Length)
return null;
List<byte> buffer = [];
while (offset < content.Length)
{
byte[] ch = content.ReadBytes(ref offset, 2);
buffer.AddRange(ch);
if (ch[0] == '\0' && ch[1] == '\0')
break;
}
return Encoding.Unicode.GetString([.. buffer]);
}
/// <summary>
/// Read a byte-prefixed ASCII string from the byte array
/// </summary>
public static string? ReadPrefixedAnsiString(this byte[] content, ref int offset)
{
if (offset >= content.Length)
return null;
byte size = content.ReadByteValue(ref offset);
if (offset + size >= content.Length)
return null;
byte[] buffer = content.ReadBytes(ref offset, size);
return Encoding.ASCII.GetString(buffer);
}
/// <summary>
/// Read a ushort-prefixed Unicode string from the byte array
/// </summary>
public static string? ReadPrefixedUnicodeString(this byte[] content, ref int offset)
{
if (offset >= content.Length)
return null;
ushort size = content.ReadUInt16(ref offset);
if (offset + size >= content.Length)
return null;
byte[] buffer = content.ReadBytes(ref offset, size);
return Encoding.Unicode.GetString(buffer);
}
/// <summary>
/// Read a string that is terminated by a newline but contains a quoted portion that
/// may also contain a newline from the stream
/// </summary>
public static string? ReadQuotedString(this byte[] content, ref int offset)
=> content.ReadQuotedString(ref offset, Encoding.Default);
/// <summary>
/// Read a string that is terminated by a newline but contains a quoted portion that
/// may also contain a newline from the stream
/// </summary>
public static string? ReadQuotedString(this byte[] content, ref int offset, Encoding encoding)
{
if (offset >= content.Length)
return null;
byte[] nullTerminator = encoding.GetBytes("\0");
int charWidth = nullTerminator.Length;
var keyChars = new List<char>();
bool openQuote = false;
while (offset < content.Length)
{
char c = encoding.GetChars(content, offset, charWidth)[0];
keyChars.Add(c);
offset += charWidth;
// If we have a quote, flip the flag
if (c == '"')
openQuote = !openQuote;
// If we have a newline not in a quoted string, exit the loop
else if (c == (byte)'\n' && !openQuote)
break;
}
return new string([.. keyChars]).TrimEnd();
}
/// <summary>
/// Read a <typeparamref name="T"/> from the underlying stream
/// </summary>
public static T? ReadType<T>(this byte[] content, ref int offset)
{
int typeSize = Marshal.SizeOf(typeof(T));
byte[] buffer = ReadToBuffer(content, ref offset, typeSize);
var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
var data = (T?)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
handle.Free();
return data;
}
/// <summary>
/// Read a number of bytes from the current byte array to a buffer
/// </summary>
private static byte[] ReadToBuffer(byte[] content, ref int offset, int length)
{
// If we have an invalid length
if (length < 0)
throw new ArgumentOutOfRangeException($"{nameof(length)} must be 0 or a positive value");
// Handle the 0-byte case
if (length == 0)
return [];
// If there are not enough bytes
if (offset + length > content.Length)
throw new System.IO.EndOfStreamException(nameof(content));
// Handle the general case, forcing a read of the correct length
byte[] buffer = new byte[length];
Array.Copy(content, offset, buffer, 0, length);
offset += length;
return buffer;
}
}
}

View File

@@ -0,0 +1,38 @@
using System.Collections.Generic;
namespace SabreTools.IO.Extensions
{
public static class EnumerableExtensions
{
/// <summary>
/// Safely iterate through an enumerable, skipping any errors
/// </summary>
public static IEnumerable<T> SafeEnumerate<T>(this IEnumerable<T> enumerable)
{
// Get the enumerator for the enumerable
var enumerator = enumerable.GetEnumerator();
// Iterate through and absorb any errors
while (true)
{
// Attempt to move to the next item
bool moved;
try
{
moved = enumerator.MoveNext();
}
catch
{
continue;
}
// If the end of the enumeration is reached
if (!moved)
yield break;
// Return the next value from the enumeration
yield return enumerator.Current;
}
}
}
}

View File

@@ -0,0 +1,379 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace SabreTools.IO.Extensions
{
/// <summary>
/// Methods around path operations
/// </summary>
public static class IOExtensions
{
/// <summary>
/// Ensure the output directory is a proper format and can be created
/// </summary>
/// <param name="dir">Directory to check</param>
/// <param name="create">True if the directory should be created, false otherwise (default)</param>
/// <returns>Full path to the directory</returns>
public static string Ensure(this string? dir, bool create = false)
{
// If the output directory is invalid
if (string.IsNullOrEmpty(dir))
dir = PathTool.GetRuntimeDirectory();
// Get the full path for the output directory
dir = Path.GetFullPath(dir!.Trim('"'));
// If we're creating the output folder, do so
if (create && !Directory.Exists(dir))
Directory.CreateDirectory(dir);
return dir;
}
/// <summary>
/// Determines a text file's encoding by analyzing its byte order mark (BOM).
/// Defaults to ASCII when detection of the text file's endianness fails.
/// </summary>
/// <param name="filename">The text file to analyze.</param>
/// <returns>The detected encoding.</returns>
/// <link>http://stackoverflow.com/questions/3825390/effective-way-to-find-any-files-encoding</link>
public static Encoding GetEncoding(this string filename)
{
if (string.IsNullOrEmpty(filename))
return Encoding.Default;
if (!File.Exists(filename))
return Encoding.Default;
// Try to open the file
try
{
FileStream file = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
if (file == null)
return Encoding.Default;
// Read the BOM
var bom = new byte[4];
file.Read(bom, 0, 4);
file.Dispose();
// Disable warning about UTF7 usage
#pragma warning disable SYSLIB0001
// Analyze the BOM
if (bom[0] == 0x2b && bom[1] == 0x2f && bom[2] == 0x76) return Encoding.UTF7;
if (bom[0] == 0xef && bom[1] == 0xbb && bom[2] == 0xbf) return Encoding.UTF8;
if (bom[0] == 0xff && bom[1] == 0xfe) return Encoding.Unicode; //UTF-16LE
if (bom[0] == 0xfe && bom[1] == 0xff) return Encoding.BigEndianUnicode; //UTF-16BE
if (bom[0] == 0 && bom[1] == 0 && bom[2] == 0xfe && bom[3] == 0xff) return Encoding.UTF32;
return Encoding.Default;
#pragma warning restore SYSLIB0001
}
catch
{
return Encoding.Default;
}
}
/// <summary>
/// Get the extension from the path, if possible
/// </summary>
/// <param name="path">Path to get extension from</param>
/// <returns>Extension, if possible</returns>
public static string? GetNormalizedExtension(this string? path)
{
// Check null or empty first
if (string.IsNullOrEmpty(path))
return null;
// Get the extension from the path, if possible
string? ext = Path.GetExtension(path)?.ToLowerInvariant();
// Check if the extension is null or empty
if (string.IsNullOrEmpty(ext))
return null;
// Make sure that extensions are valid
ext = ext!.TrimStart('.');
return ext;
}
/// <summary>
/// Get all empty folders within a root folder
/// </summary>
/// <param name="root">Root directory to parse</param>
/// <returns>IEumerable containing all directories that are empty, an empty enumerable if the root is empty, null otherwise</returns>
public static List<string>? ListEmpty(this string? root)
{
// Check null or empty first
if (string.IsNullOrEmpty(root))
return null;
// Then, check if the root exists
if (!Directory.Exists(root))
return null;
// If it does and it is empty, return a blank enumerable
if (!root!.SafeEnumerateFileSystemEntries("*", SearchOption.AllDirectories).Any())
return [];
// Otherwise, get the complete list
return root!.SafeEnumerateDirectories("*", SearchOption.AllDirectories)
.Where(dir => !dir.SafeEnumerateFileSystemEntries("*", SearchOption.AllDirectories).Any())
.ToList();
}
#region Safe Directory Enumeration
/// <inheritdoc cref="Directory.GetDirectories(string)"/>
/// <remarks>Returns an empty enumerable on any exception</remarks>
public static IEnumerable<string> SafeGetDirectories(this string path)
{
try
{
var enumerable = Directory.GetDirectories(path);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
/// <inheritdoc cref="Directory.GetDirectories(string, string)"/>
/// <remarks>Returns an empty enumerable on any exception</remarks>
public static IEnumerable<string> SafeGetDirectories(this string path, string searchPattern)
{
try
{
var enumerable = Directory.GetDirectories(path, searchPattern);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
/// <inheritdoc cref="Directory.GetDirectories(string, string, SearchOption)"/>
/// <remarks>Returns an empty enumerable on any exception</remarks>
public static IEnumerable<string> SafeGetDirectories(this string path, string searchPattern, SearchOption searchOption)
{
try
{
var enumerable = Directory.GetDirectories(path, searchPattern, searchOption);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
/// <inheritdoc cref="Directory.GetFiles(string)"/>
/// <remarks>Returns an empty enumerable on any exception</remarks>
public static IEnumerable<string> SafeGetFiles(this string path)
{
try
{
var enumerable = Directory.GetFiles(path);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
/// <inheritdoc cref="Directory.GetFiles(string, string)"/>
/// <remarks>Returns an empty enumerable on any exception</remarks>
public static IEnumerable<string> SafeGetFiles(this string path, string searchPattern)
{
try
{
var enumerable = Directory.GetFiles(path, searchPattern);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
/// <inheritdoc cref="Directory.GetFiles(string, string, SearchOption)"/>
/// <remarks>Returns an empty enumerable on any exception</remarks>
public static IEnumerable<string> SafeGetFiles(this string path, string searchPattern, SearchOption searchOption)
{
try
{
var enumerable = Directory.GetFiles(path, searchPattern, searchOption);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
/// <inheritdoc cref="Directory.GetFileSystemEntries(string)"/>
/// <remarks>Returns an empty enumerable on any exception</remarks>
public static IEnumerable<string> SafeGetFileSystemEntries(this string path)
{
try
{
var enumerable = Directory.GetFileSystemEntries(path);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
/// <inheritdoc cref="Directory.GetDirectories(string, string)"/>
/// <remarks>Returns an empty enumerable on any exception</remarks>
public static IEnumerable<string> SafeGetFileSystemEntries(this string path, string searchPattern)
{
try
{
var enumerable = Directory.GetFileSystemEntries(path, searchPattern);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
/// <inheritdoc cref="Directory.GetDirectories(string, string, SearchOption)"/>
/// <remarks>Returns an empty enumerable on any exception</remarks>
public static IEnumerable<string> SafeGetFileSystemEntries(this string path, string searchPattern, SearchOption searchOption)
{
try
{
var enumerable = Directory.GetFileSystemEntries(path, searchPattern);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
#if NET20 || NET35
/// <inheritdoc cref="Directory.GetDirectories(string)"/>
/// <remarks>Calls <see cref="SafeGetDirectories(string)"/> implementation</remarks>
public static IEnumerable<string> SafeEnumerateDirectories(this string path)
=> path.SafeGetDirectories();
/// <inheritdoc cref="Directory.GetDirectories(string, string)"/>
/// <remarks>Calls <see cref="SafeGetDirectories(string, string)"/> implementation</remarks>
public static IEnumerable<string> SafeEnumerateDirectories(this string path, string searchPattern)
=> path.SafeGetDirectories(searchPattern);
/// <inheritdoc cref="Directory.GetDirectories(string, string, SearchOption)"/>
/// <remarks>Calls <see cref="SafeGetDirectories(string, string, SearchOption)"/> implementation</remarks>
public static IEnumerable<string> SafeEnumerateDirectories(this string path, string searchPattern, SearchOption searchOption)
=> path.SafeGetDirectories(searchPattern, searchOption);
/// <inheritdoc cref="Directory.GetFiles(string)"/>
/// <remarks>Calls <see cref="SafeGetFiles(string)"/> implementation</remarks>
public static IEnumerable<string> SafeEnumerateFiles(this string path)
=> path.SafeGetFiles();
/// <inheritdoc cref="Directory.GetFiles(string, string)"/>
/// <remarks>Calls <see cref="SafeGetFiles(string, string)"/> implementation</remarks>
public static IEnumerable<string> SafeEnumerateFiles(this string path, string searchPattern)
=> path.SafeGetFiles(searchPattern);
/// <inheritdoc cref="Directory.GetFiles(string, string, SearchOption)"/>
/// <remarks>Calls <see cref="SafeGetFiles(string, string, SearchOption)"/> implementation</remarks>
public static IEnumerable<string> SafeEnumerateFiles(this string path, string searchPattern, SearchOption searchOption)
=> path.SafeGetFiles(searchPattern, searchOption);
/// <inheritdoc cref="Directory.GetFileSystemEntries(string)"/>
/// <remarks>Calls <see cref="SafeGetFileSystemEntries(string)"/> implementation</remarks>
public static IEnumerable<string> SafeEnumerateFileSystemEntries(this string path)
=> path.SafeGetFileSystemEntries();
/// <inheritdoc cref="Directory.GetFileSystemEntries(string, string)"/>
/// <remarks>Calls <see cref="SafeGetFileSystemEntries(string, string)"/> implementation</remarks>
public static IEnumerable<string> SafeEnumerateFileSystemEntries(this string path, string searchPattern)
=> path.SafeGetFileSystemEntries(searchPattern);
/// <inheritdoc cref="Directory.GetFileSystemEntries(string, string)"/>
/// <remarks>Calls <see cref="SafeGetFileSystemEntries(string, string, SearchOption)"/> implementation</remarks>
public static IEnumerable<string> SafeEnumerateFileSystemEntries(this string path, string searchPattern, SearchOption searchOption)
=> path.SafeGetFileSystemEntries(searchPattern, searchOption);
#else
/// <inheritdoc cref="Directory.EnumerateDirectories(string)"/>
public static IEnumerable<string> SafeEnumerateDirectories(this string path)
{
var enumerable = Directory.EnumerateDirectories(path);
return enumerable.SafeEnumerate();
}
/// <inheritdoc cref="Directory.EnumerateDirectories(string, string)"/>
public static IEnumerable<string> SafeEnumerateDirectories(this string path, string searchPattern)
{
var enumerable = Directory.EnumerateDirectories(path, searchPattern);
return enumerable.SafeEnumerate();
}
/// <inheritdoc cref="Directory.EnumerateDirectories(string, string, SearchOption)"/>
public static IEnumerable<string> SafeEnumerateDirectories(this string path, string searchPattern, SearchOption searchOption)
{
var enumerable = Directory.EnumerateDirectories(path, searchPattern, searchOption);
return enumerable.SafeEnumerate();
}
/// <inheritdoc cref="Directory.EnumerateFiles(string)"/>
public static IEnumerable<string> SafeEnumerateFiles(this string path)
{
var enumerable = Directory.EnumerateFiles(path);
return enumerable.SafeEnumerate();
}
/// <inheritdoc cref="Directory.EnumerateFiles(string, string)"/>
public static IEnumerable<string> SafeEnumerateFiles(this string path, string searchPattern)
{
var enumerable = Directory.EnumerateFiles(path, searchPattern);
return enumerable.SafeEnumerate();
}
/// <inheritdoc cref="Directory.EnumerateFiles(string, string, SearchOption)"/>
public static IEnumerable<string> SafeEnumerateFiles(this string path, string searchPattern, SearchOption searchOption)
{
var enumerable = Directory.EnumerateFiles(path, searchPattern, searchOption);
return enumerable.SafeEnumerate();
}
/// <inheritdoc cref="Directory.EnumerateFileSystemEntries(string)"/>
public static IEnumerable<string> SafeEnumerateFileSystemEntries(this string path)
{
var enumerable = Directory.EnumerateFileSystemEntries(path);
return enumerable.SafeEnumerate();
}
/// <inheritdoc cref="Directory.EnumerateFileSystemEntries(string, string)"/>
public static IEnumerable<string> SafeEnumerateFileSystemEntries(this string path, string searchPattern)
{
var enumerable = Directory.EnumerateFileSystemEntries(path, searchPattern);
return enumerable.SafeEnumerate();
}
/// <inheritdoc cref="Directory.EnumerateFileSystemEntries(string, string, SearchOption)"/>
public static IEnumerable<string> SafeEnumerateFileSystemEntries(this string path, string searchPattern, SearchOption searchOption)
{
var enumerable = Directory.EnumerateFileSystemEntries(path, searchPattern, searchOption);
return enumerable.SafeEnumerate();
}
#endif
#endregion
}
}

View File

@@ -1,10 +1,10 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace SabreTools.IO
namespace SabreTools.IO.Extensions
{
/// <summary>
/// Extensions for Streams
@@ -28,7 +28,7 @@ namespace SabreTools.IO
=> ReadToBuffer(stream, count);
/// <summary>
/// Read a Int8 from the stream
/// Read an Int8 from the stream
/// </summary>
public static sbyte ReadSByte(this Stream stream)
{
@@ -46,7 +46,7 @@ namespace SabreTools.IO
}
/// <summary>
/// Read a Int16 from the stream
/// Read an Int16 from the stream
/// </summary>
public static short ReadInt16(this Stream stream)
{
@@ -55,8 +55,9 @@ namespace SabreTools.IO
}
/// <summary>
/// Read a Int16 from the stream in big-endian format
/// Read an Int16 from the stream
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static short ReadInt16BigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 2);
@@ -74,8 +75,9 @@ namespace SabreTools.IO
}
/// <summary>
/// Read a UInt16 from the stream in big-endian format
/// Read a UInt16 from the stream
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static ushort ReadUInt16BigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 2);
@@ -93,8 +95,9 @@ namespace SabreTools.IO
}
/// <summary>
/// Read an Int32 from the stream in big-endian format
/// Read an Int32 from the stream
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static int ReadInt32BigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 4);
@@ -112,8 +115,9 @@ namespace SabreTools.IO
}
/// <summary>
/// Read a UInt32 from the stream in big-endian format
/// Read a UInt32 from the stream
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static uint ReadUInt32BigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 4);
@@ -122,7 +126,27 @@ namespace SabreTools.IO
}
/// <summary>
/// Read a Int64 from the stream
/// Read a Single from the stream
/// </summary>
public static float ReadSingle(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 4);
return BitConverter.ToSingle(buffer, 0);
}
/// <summary>
/// Read a Single from the stream
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static float ReadSingleBigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 4);
Array.Reverse(buffer);
return BitConverter.ToSingle(buffer, 0);
}
/// <summary>
/// Read an Int64 from the stream
/// </summary>
public static long ReadInt64(this Stream stream)
{
@@ -131,8 +155,9 @@ namespace SabreTools.IO
}
/// <summary>
/// Read a Int64 from the stream in big-endian format
/// Read an Int64 from the stream
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static long ReadInt64BigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 8);
@@ -150,8 +175,9 @@ namespace SabreTools.IO
}
/// <summary>
/// Read a UInt64 from the stream in big-endian format
/// Read a UInt64 from the stream
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static ulong ReadUInt64BigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 8);
@@ -159,6 +185,58 @@ namespace SabreTools.IO
return BitConverter.ToUInt64(buffer, 0);
}
/// <summary>
/// Read a Double from the stream
/// </summary>
public static double ReadDouble(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 8);
return BitConverter.ToDouble(buffer, 0);
}
/// <summary>
/// Read a Double from the stream
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static double ReadDoubleBigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 8);
Array.Reverse(buffer);
return BitConverter.ToDouble(buffer, 0);
}
/// <summary>
/// Read a Decimal from the stream
/// </summary>
public static decimal ReadDecimal(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 16);
int i1 = BitConverter.ToInt32(buffer, 0);
int i2 = BitConverter.ToInt32(buffer, 4);
int i3 = BitConverter.ToInt32(buffer, 8);
int i4 = BitConverter.ToInt32(buffer, 12);
return new decimal([i1, i2, i3, i4]);
}
/// <summary>
/// Read a Decimal from the stream
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static decimal ReadDecimalBigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 16);
Array.Reverse(buffer);
int i1 = BitConverter.ToInt32(buffer, 0);
int i2 = BitConverter.ToInt32(buffer, 4);
int i3 = BitConverter.ToInt32(buffer, 8);
int i4 = BitConverter.ToInt32(buffer, 12);
return new decimal([i1, i2, i3, i4]);
}
/// <summary>
/// Read a Guid from the stream
/// </summary>
@@ -169,8 +247,9 @@ namespace SabreTools.IO
}
/// <summary>
/// Read a Guid from the stream in big-endian format
/// Read a Guid from the stream
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static Guid ReadGuidBigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 16);
@@ -178,9 +257,10 @@ namespace SabreTools.IO
return new Guid(buffer);
}
// TODO: Determine if the reverse reads are doing what are expected
#if NET7_0_OR_GREATER
/// <summary>
/// Read a Int128 from the stream
/// Read an Int128 from the stream
/// </summary>
public static Int128 ReadInt128(this Stream stream)
{
@@ -189,8 +269,9 @@ namespace SabreTools.IO
}
/// <summary>
/// Read a Int128 from the stream in big-endian format
/// Read an Int128 from the stream
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static Int128 ReadInt128BigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 16);
@@ -208,8 +289,9 @@ namespace SabreTools.IO
}
/// <summary>
/// Read a UInt128 from the stream in big-endian format
/// Read a UInt128 from the stream
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static UInt128 ReadUInt128BigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 16);
@@ -221,29 +303,99 @@ namespace SabreTools.IO
/// <summary>
/// Read a null-terminated string from the stream
/// </summary>
public static string? ReadString(this Stream stream)
=> stream.ReadString(Encoding.Default);
public static string? ReadNullTerminatedString(this Stream stream, Encoding encoding)
{
// Short-circuit to explicit implementations
if (encoding.Equals(Encoding.ASCII))
return stream.ReadNullTerminatedAnsiString();
else if (encoding.Equals(Encoding.Unicode))
return stream.ReadNullTerminatedUnicodeString();
if (stream.Position >= stream.Length)
return null;
List<byte> buffer = [];
while (stream.Position < stream.Length)
{
byte ch = stream.ReadByteValue();
buffer.Add(ch);
if (ch == '\0')
break;
}
return encoding.GetString([.. buffer]);
}
/// <summary>
/// Read a null-terminated string from the stream
/// Read a null-terminated ASCII string from the stream
/// </summary>
public static string? ReadString(this Stream stream, Encoding encoding)
public static string? ReadNullTerminatedAnsiString(this Stream stream)
{
if (stream.Position >= stream.Length)
return null;
byte[] nullTerminator = encoding.GetBytes("\0");
int charWidth = nullTerminator.Length;
var tempBuffer = new List<byte>();
byte[] buffer = new byte[charWidth];
while (stream.Position < stream.Length && stream.Read(buffer, 0, charWidth) != 0 && !buffer.SequenceEqual(nullTerminator))
List<byte> buffer = [];
while (stream.Position < stream.Length)
{
tempBuffer.AddRange(buffer);
byte ch = stream.ReadByteValue();
buffer.Add(ch);
if (ch == '\0')
break;
}
return encoding.GetString([.. tempBuffer]);
return Encoding.ASCII.GetString([.. buffer]);
}
/// <summary>
/// Read a null-terminated Unicode string from the stream
/// </summary>
public static string? ReadNullTerminatedUnicodeString(this Stream stream)
{
if (stream.Position >= stream.Length)
return null;
List<byte> buffer = [];
while (stream.Position < stream.Length)
{
byte[] ch = stream.ReadBytes(2);
buffer.AddRange(ch);
if (ch[0] == '\0' && ch[1] == '\0')
break;
}
return Encoding.Unicode.GetString([.. buffer]);
}
/// <summary>
/// Read a byte-prefixed ASCII string from the stream
/// </summary>
public static string? ReadPrefixedAnsiString(this Stream stream)
{
if (stream.Position >= stream.Length)
return null;
byte size = stream.ReadByteValue();
if (stream.Position + size >= stream.Length)
return null;
byte[] buffer = stream.ReadBytes(size);
return Encoding.ASCII.GetString(buffer);
}
/// <summary>
/// Read a ushort-prefixed Unicode string from the stream
/// </summary>
public static string? ReadPrefixedUnicodeString(this Stream stream)
{
if (stream.Position >= stream.Length)
return null;
ushort size = stream.ReadUInt16();
if (stream.Position + size >= stream.Length)
return null;
byte[] buffer = stream.ReadBytes(size);
return Encoding.Unicode.GetString(buffer);
}
/// <summary>
@@ -285,6 +437,21 @@ namespace SabreTools.IO
return line.TrimEnd();
}
/// <summary>
/// Read a <typeparamref name="T"/> from the stream
/// </summary>
public static T? ReadType<T>(this Stream stream)
{
int typeSize = Marshal.SizeOf(typeof(T));
byte[] buffer = ReadToBuffer(stream, typeSize);
var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
var data = (T?)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
handle.Free();
return data;
}
/// <summary>
/// Seek to a specific point in the stream, if possible
/// </summary>

View File

@@ -1,7 +1,7 @@
using System;
using System.Xml;
namespace SabreTools.IO
namespace SabreTools.IO.Extensions
{
/// <summary>
/// Additional methods for XmlTextWriter

View File

@@ -1,140 +0,0 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace SabreTools.IO
{
/// <summary>
/// Methods around path operations
/// </summary>
public static class IOExtensions
{
/// <summary>
/// Ensure the output directory is a proper format and can be created
/// </summary>
/// <param name="dir">Directory to check</param>
/// <param name="create">True if the directory should be created, false otherwise (default)</param>
/// <returns>Full path to the directory</returns>
public static string Ensure(this string? dir, bool create = false)
{
// If the output directory is invalid
if (string.IsNullOrEmpty(dir))
dir = PathTool.GetRuntimeDirectory();
// Get the full path for the output directory
dir = Path.GetFullPath(dir!.Trim('"'));
// If we're creating the output folder, do so
if (create && !Directory.Exists(dir))
Directory.CreateDirectory(dir);
return dir;
}
/// <summary>
/// Determines a text file's encoding by analyzing its byte order mark (BOM).
/// Defaults to ASCII when detection of the text file's endianness fails.
/// </summary>
/// <param name="filename">The text file to analyze.</param>
/// <returns>The detected encoding.</returns>
/// <link>http://stackoverflow.com/questions/3825390/effective-way-to-find-any-files-encoding</link>
public static Encoding GetEncoding(this string filename)
{
if (string.IsNullOrEmpty(filename))
return Encoding.Default;
if (!File.Exists(filename))
return Encoding.Default;
// Try to open the file
try
{
FileStream file = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
if (file == null)
return Encoding.Default;
// Read the BOM
var bom = new byte[4];
file.Read(bom, 0, 4);
file.Dispose();
// Disable warning about UTF7 usage
#pragma warning disable SYSLIB0001
// Analyze the BOM
if (bom[0] == 0x2b && bom[1] == 0x2f && bom[2] == 0x76) return Encoding.UTF7;
if (bom[0] == 0xef && bom[1] == 0xbb && bom[2] == 0xbf) return Encoding.UTF8;
if (bom[0] == 0xff && bom[1] == 0xfe) return Encoding.Unicode; //UTF-16LE
if (bom[0] == 0xfe && bom[1] == 0xff) return Encoding.BigEndianUnicode; //UTF-16BE
if (bom[0] == 0 && bom[1] == 0 && bom[2] == 0xfe && bom[3] == 0xff) return Encoding.UTF32;
return Encoding.Default;
#pragma warning restore SYSLIB0001
}
catch
{
return Encoding.Default;
}
}
/// <summary>
/// Get the extension from the path, if possible
/// </summary>
/// <param name="path">Path to get extension from</param>
/// <returns>Extension, if possible</returns>
public static string? GetNormalizedExtension(this string? path)
{
// Check null or empty first
if (string.IsNullOrEmpty(path))
return null;
// Get the extension from the path, if possible
string? ext = Path.GetExtension(path)?.ToLowerInvariant();
// Check if the extension is null or empty
if (string.IsNullOrEmpty(ext))
return null;
// Make sure that extensions are valid
ext = ext!.TrimStart('.');
return ext;
}
/// <summary>
/// Get all empty folders within a root folder
/// </summary>
/// <param name="root">Root directory to parse</param>
/// <returns>IEumerable containing all directories that are empty, an empty enumerable if the root is empty, null otherwise</returns>
public static List<string>? ListEmpty(this string? root)
{
// Check null or empty first
if (string.IsNullOrEmpty(root))
return null;
// Then, check if the root exists
if (!Directory.Exists(root))
return null;
// If it does and it is empty, return a blank enumerable
#if NET20 || NET35
if (!Directory.GetFiles(root, "*", SearchOption.AllDirectories).Any())
#else
if (!Directory.EnumerateFileSystemEntries(root, "*", SearchOption.AllDirectories).Any())
#endif
return [];
// Otherwise, get the complete list
#if NET20 || NET35
return Directory.GetDirectories(root, "*", SearchOption.AllDirectories)
.Where(dir => !Directory.GetFiles(dir, "*", SearchOption.AllDirectories).Any())
.ToList();
#else
return Directory.EnumerateDirectories(root, "*", SearchOption.AllDirectories)
.Where(dir => !Directory.EnumerateFileSystemEntries(dir, "*", SearchOption.AllDirectories).Any())
.ToList();
#endif
}
}
}

View File

@@ -39,7 +39,7 @@ namespace SabreTools.IO
string filename = Path.GetFileName(CurrentPath);
// If we have a true ParentPath, remove it from CurrentPath and return the remainder
if (!string.IsNullOrEmpty(ParentPath) && !PathsEqual(CurrentPath, ParentPath))
if (!string.IsNullOrEmpty(ParentPath) && !PathsEqual(CurrentPath, ParentPath))
filename = CurrentPath.Remove(0, ParentPath!.Length + 1);
// If we're sanitizing the path after, do so
@@ -57,34 +57,31 @@ namespace SabreTools.IO
/// <returns>Complete output path</returns>
public string? GetOutputPath(string? outDir, bool inplace)
{
// If the current path is empty, we can't do anything
// If the current path is empty
if (string.IsNullOrEmpty(CurrentPath))
return null;
// If the output dir is empty (and we're not inplace), we can't do anything
outDir = outDir?.Trim();
if (string.IsNullOrEmpty(outDir) && !inplace)
return null;
// Check if we have a split path or not
bool splitpath = !string.IsNullOrEmpty(ParentPath);
// If we have an inplace output, use the directory name from the input path
// If we have an inplace output
if (inplace)
return Path.GetDirectoryName(CurrentPath);
// If the current and parent paths are the same, just use the output directory
if (!splitpath || CurrentPath.Length == (ParentPath?.Length ?? 0))
// If the output dir is empty after trimming
outDir = outDir?.Trim();
if (string.IsNullOrEmpty(outDir))
return null;
// If the parent path is empty or the paths are equal
if (string.IsNullOrEmpty(ParentPath) || PathsEqual(CurrentPath, ParentPath))
return outDir;
// By default, the working parent directory is the parent path
string workingParent = ParentPath ?? string.Empty;
string workingParent = ParentPath!;
// TODO: Should this be the default? Always create a subfolder if a folder is found?
// If we are processing a path that is coming from a directory and we are outputting to the current directory, we want to get the subfolder to write to
if (outDir == Environment.CurrentDirectory)
workingParent = Path.GetDirectoryName(ParentPath ?? string.Empty) ?? string.Empty;
workingParent = Path.GetDirectoryName(ParentPath) ?? string.Empty;
// Handle bizarre Windows-like paths on Linux
if (workingParent.EndsWith(":") && Path.DirectorySeparatorChar == '/')
workingParent += '/';
@@ -98,7 +95,7 @@ namespace SabreTools.IO
string combinedPath = Path.Combine(outDir!, strippedPath);
return Path.GetDirectoryName(combinedPath);
}
/// <summary>
/// Determine if two paths are equal or not
/// </summary>
@@ -129,8 +126,8 @@ namespace SabreTools.IO
if (input == null)
return null;
// Replace alternate directory separators with the correct one
return input.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
// Replace '\' with '/'
return input.Replace('\\', '/');
}
}
}

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.IO;
using SabreTools.IO.Extensions;
using SabreTools.Matching;
namespace SabreTools.IO
@@ -70,7 +71,7 @@ namespace SabreTools.IO
private static List<string> GetDirectoriesOrderedHelper(string dir, List<string> infiles, string pattern)
{
// Take care of the files in the top directory
List<string> toadd = [.. Directory.GetDirectories(dir, pattern, SearchOption.TopDirectoryOnly)];
List<string> toadd = [.. dir.SafeEnumerateDirectories(pattern, SearchOption.TopDirectoryOnly)];
toadd.Sort(new NaturalComparer());
infiles.AddRange(toadd);
@@ -149,12 +150,12 @@ namespace SabreTools.IO
private static List<string> GetFilesOrderedHelper(string dir, List<string> infiles, string pattern)
{
// Take care of the files in the top directory
List<string> toadd = [.. Directory.GetFiles(dir, pattern, SearchOption.TopDirectoryOnly)];
List<string> toadd = [.. dir.SafeEnumerateFiles(pattern, SearchOption.TopDirectoryOnly)];
toadd.Sort(new NaturalComparer());
infiles.AddRange(toadd);
// Then recurse through and add from the directories
List<string> subDirs = [.. Directory.GetDirectories(dir, pattern, SearchOption.TopDirectoryOnly)];
List<string> subDirs = [.. dir.SafeEnumerateDirectories(pattern, SearchOption.TopDirectoryOnly)];
subDirs.Sort(new NaturalComparer());
foreach (string subdir in subDirs)
{

View File

@@ -7,7 +7,7 @@
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>1.3.4</Version>
<Version>1.4.0</Version>
<!-- Package Properties -->
<Authors>Matt Nadareski</Authors>

View File

@@ -0,0 +1,239 @@
using System;
using System.IO;
using SabreTools.IO.Extensions;
namespace SabreTools.IO.Streams
{
/// <summary>
/// Wrapper to allow reading bits from a source stream
/// </summary>
public class ReadOnlyBitStream
{
/// <inheritdoc cref="Stream.Position"/>
public long Position => _source.Position;
/// <inheritdoc cref="Stream.Length"/>
public long Length => _source.Length;
/// <summary>
/// Original stream source
/// </summary>
private readonly Stream _source;
/// <summary>
/// Last read byte value from the stream
/// </summary>
private byte? _bitBuffer;
/// <summary>
/// Index in the byte of the current bit
/// </summary>
private int _bitIndex;
/// <summary>
/// Create a new BitStream from a source Stream
/// </summary>
public ReadOnlyBitStream(Stream source)
{
_source = source;
_bitBuffer = null;
_bitIndex = 0;
// Verify the stream
if (!source.CanRead || !source.CanSeek)
throw new ArgumentException($"{nameof(source)} needs to be readable and seekable");
}
/// <summary>
/// Discard the current cached byte
/// </summary>
public void Discard()
{
_bitBuffer = null;
_bitIndex = 0;
}
/// <summary>
/// Read a single bit, if possible
/// </summary>
/// <returns>The next bit encoded in a byte, null on error or end of stream</returns>
public byte? ReadBit()
{
// If we reached the end of the stream
if (_source.Position >= _source.Length)
return null;
// If we don't have a value cached
if (_bitBuffer == null)
{
// Read the next byte, if possible
_bitBuffer = ReadSourceByte();
if (_bitBuffer == null)
return null;
// Reset the bit index
_bitIndex = 0;
}
// Get the value by bit-shifting
int value = _bitBuffer.Value & 0x01;
_bitBuffer = (byte?)(_bitBuffer >> 1);
_bitIndex++;
// Reset the byte if we're at the end
if (_bitIndex >= 8)
Discard();
return (byte)value;
}
/// <summary>
/// Read a multiple bits in LSB, if possible
/// </summary>
/// <returns>The next bits encoded in a UInt32, null on error or end of stream</returns>
public uint? ReadBitsLSB(int bits)
{
uint value = 0;
for (int i = 0; i < bits; i++)
{
// Read the next bit
byte? bitValue = ReadBit();
if (bitValue == null)
return null;
// Add the bit shifted by the current index
value += (uint)(bitValue.Value << i);
}
return value;
}
/// <summary>
/// Read a multiple bits in MSB, if possible
/// </summary>
/// <returns>The next bits encoded in a UInt32, null on error or end of stream</returns>
public uint? ReadBitsMSB(int bits)
{
uint value = 0;
for (int i = 0; i < bits; i++)
{
// Read the next bit
byte? bitValue = ReadBit();
if (bitValue == null)
return null;
// Add the bit shifted by the current index
value = (value << 1) + bitValue.Value;
}
return value;
}
/// <summary>
/// Read a byte, if possible
/// </summary>
/// <returns>The next byte, null on error or end of stream</returns>
/// <remarks>Assumes the stream is byte-aligned</remarks>
public byte? ReadByte()
{
try
{
Discard();
return _source.ReadByteValue();
}
catch
{
return null;
}
}
/// <summary>
/// Read a UInt16, if possible
/// </summary>
/// <returns>The next UInt16, null on error or end of stream</returns>
/// <remarks>Assumes the stream is byte-aligned</remarks>
public ushort? ReadUInt16()
{
try
{
Discard();
return _source.ReadUInt16();
}
catch
{
return null;
}
}
/// <summary>
/// Read a UInt32, if possible
/// </summary>
/// <returns>The next UInt32, null on error or end of stream</returns>
/// <remarks>Assumes the stream is byte-aligned</remarks>
public uint? ReadUInt32()
{
try
{
Discard();
return _source.ReadUInt32();
}
catch
{
return null;
}
}
/// <summary>
/// Read a UInt64, if possible
/// </summary>
/// <returns>The next UInt64, null on error or end of stream</returns>
/// <remarks>Assumes the stream is byte-aligned</remarks>
public ulong? ReadUInt64()
{
try
{
Discard();
return _source.ReadUInt64();
}
catch
{
return null;
}
}
/// <summary>
/// Read <paramref name="bytes"/> bytes, if possible
/// </summary>
/// <param name="bytes">Number of bytes to read</param>
/// <returns>The next <paramref name="bytes"/> bytes, null on error or end of stream</returns>
/// <remarks>Assumes the stream is byte-aligned</remarks>
public byte[]? ReadBytes(int bytes)
{
try
{
Discard();
return _source.ReadBytes(bytes);
}
catch
{
return null;
}
}
/// <summary>
/// Read a single byte from the underlying stream, if possible
/// </summary>
/// <returns>The next full byte from the stream, null on error or end of stream</returns>
private byte? ReadSourceByte()
{
try
{
return _source.ReadByteValue();
}
catch
{
return null;
}
}
}
}

View File

@@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace SabreTools.IO
namespace SabreTools.IO.Streams
{
/// <summary>
/// Read-only stream wrapper around multiple, consecutive streams
@@ -69,6 +69,23 @@ namespace SabreTools.IO
_position = 0;
}
/// <summary>
/// Create a new ReadOnlyCompositeStream from a single Stream
/// </summary>
/// <param name="stream"></param>
public ReadOnlyCompositeStream(Stream stream)
{
_streams = [stream];
_length = 0;
_position = 0;
// Verify the stream and add to the length
if (!stream.CanRead || !stream.CanSeek)
throw new ArgumentException($"{nameof(stream)} needs to be readable and seekable");
_length += stream.Length;
}
/// <summary>
/// Create a new ReadOnlyCompositeStream from an existing collection of Streams
/// </summary>
@@ -131,9 +148,9 @@ namespace SabreTools.IO
public override int Read(byte[] buffer, int offset, int count)
{
// Determine which stream we start reading from
(int streamIndex, long streamOffset) = DetermineStreamIndex(offset);
(int streamIndex, long streamOffset) = DetermineStreamIndex(_position);
if (streamIndex == -1)
throw new ArgumentOutOfRangeException(nameof(offset));
return 0;
// Determine if the stream fully contains the requested segment
bool singleStream = StreamContains(streamIndex, streamOffset, count);
@@ -211,7 +228,7 @@ namespace SabreTools.IO
/// Determine the index of the stream that contains a particular offset
/// </summary>
/// <returns>Index of the stream containing the offset and the real offset in the stream, (-1, -1) on error</returns>
private (int index, long realOffset) DetermineStreamIndex(int offset)
private (int index, long realOffset) DetermineStreamIndex(long offset)
{
// If the offset is out of bounds
if (offset < 0 || offset >= _length)

36
publish-nix.sh Executable file
View File

@@ -0,0 +1,36 @@
#! /bin/bash
# This batch file assumes the following:
# - .NET 8.0 (or newer) SDK is installed and in PATH
#
# If any of these are not satisfied, the operation may fail
# in an unpredictable way and result in an incomplete output.
# Optional parameters
NO_BUILD=false
while getopts "uba" OPTION
do
case $OPTION in
b)
NO_BUILD=true
;;
*)
echo "Invalid option provided"
exit 1
;;
esac
done
# Set the current directory as a variable
BUILD_FOLDER=$PWD
# Only build if requested
if [ $NO_BUILD = false ]
then
# Restore Nuget packages for all builds
echo "Restoring Nuget packages"
dotnet restore
# Create Nuget Package
dotnet pack SabreTools.IO/SabreTools.IO.csproj --output $BUILD_FOLDER
fi

26
publish-win.ps1 Normal file
View File

@@ -0,0 +1,26 @@
# This batch file assumes the following:
# - .NET 8.0 (or newer) SDK is installed and in PATH
#
# If any of these are not satisfied, the operation may fail
# in an unpredictable way and result in an incomplete output.
# Optional parameters
param(
[Parameter(Mandatory = $false)]
[Alias("NoBuild")]
[switch]$NO_BUILD
)
# Set the current directory as a variable
$BUILD_FOLDER = $PSScriptRoot
# Only build if requested
if (!$NO_BUILD.IsPresent)
{
# Restore Nuget packages for all builds
Write-Host "Restoring Nuget packages"
dotnet restore
# Create Nuget Package
dotnet pack SabreTools.IO\SabreTools.IO.csproj --output $BUILD_FOLDER
}