88 Commits

Author SHA1 Message Date
Matt Nadareski
ef699ee1fb Bump version 2024-05-07 05:08:31 -04:00
Matt Nadareski
0910b716ba Fix build for BinaryWriter 2024-05-07 05:02:05 -04:00
Matt Nadareski
584feb33e6 Handle special struct types 2024-05-07 05:00:43 -04:00
Matt Nadareski
b99c80390e Bump version 2024-05-06 22:07:04 -04:00
Matt Nadareski
3a79646650 Handle unskippable IO errors 2024-05-02 11:37:52 -04:00
Matt Nadareski
f16f05beb9 Further wrap safe enumeration calls 2024-05-02 10:35:39 -04:00
Matt Nadareski
e901b52143 Be more explicit about InvalidOperationException 2024-05-02 10:26:55 -04:00
Matt Nadareski
a638b146b4 Handlie issues getting the enumerator 2024-05-02 10:25:53 -04:00
Matt Nadareski
6bfc961a87 Handle InvalidOperationException for SafeEnumerate 2024-05-02 10:21:41 -04:00
Matt Nadareski
a825bae039 Fix writing issues 2024-04-29 15:02:51 -04:00
Matt Nadareski
e0eba8e5bb Fix build 2024-04-29 14:50:58 -04:00
Matt Nadareski
fb4b533dfb Add type writing extensions 2024-04-29 14:45:31 -04:00
Matt Nadareski
6162af2216 Add string marshalling writer methods 2024-04-29 14:16:21 -04:00
Matt Nadareski
3b5fd128f0 Add UTF-8 and UTF-32 writing extensions 2024-04-29 14:10:20 -04:00
Matt Nadareski
9beb2177aa Add default UTF-32 null-terminated string reading 2024-04-29 12:57:37 -04:00
Matt Nadareski
f0033af712 Add short-circuiting for null-terminated UTF-8 2024-04-29 12:44:43 -04:00
Matt Nadareski
3de5b2378d Fix Unicode string reading 2024-04-29 12:43:44 -04:00
Matt Nadareski
ee7ce59627 Use safe string readers where possible 2024-04-29 12:39:02 -04:00
Matt Nadareski
39bf9c19ad Add narrow and wide reading helpers 2024-04-29 12:30:49 -04:00
Matt Nadareski
32cab49bae Clean up usings 2024-04-29 12:15:16 -04:00
Matt Nadareski
5d71957841 Slightly less verbose comments 2024-04-29 12:13:35 -04:00
Matt Nadareski
7ea182c7d8 Add marshalling helpers to ensure consistency across implementations 2024-04-29 11:58:50 -04:00
Matt Nadareski
b97ec13661 Handle LPUTF8Str implementations 2024-04-29 00:55:16 -04:00
Matt Nadareski
8caeea053f Handle LPTStr implementations 2024-04-29 00:49:45 -04:00
Matt Nadareski
a7476b6ac9 Handle TBStr implementations 2024-04-29 00:48:21 -04:00
Matt Nadareski
f0095f9e41 "marshalling" not "serialization" 2024-04-29 00:42:50 -04:00
Matt Nadareski
a0b5ea1368 Add disclaimer remarks to ReadType impelementations 2024-04-29 00:39:56 -04:00
Matt Nadareski
a94d2c8c64 Add "correct order" inheritence serialization 2024-04-29 00:36:55 -04:00
Matt Nadareski
8c19ad712a Add support for LPArray types 2024-04-28 23:47:33 -04:00
Matt Nadareski
0317f751b9 Limit current code to ByValArray 2024-04-28 23:12:48 -04:00
Matt Nadareski
b8d431b06b Handle array types properly 2024-04-28 22:58:42 -04:00
Matt Nadareski
3fcf10e2f7 Fix capitalization of TestStructStrings 2024-04-28 22:18:44 -04:00
Matt Nadareski
40e439b18c Add comprehensive strings test, fix issues 2024-04-28 20:35:18 -04:00
Matt Nadareski
bf707b1c11 Bump version 2024-04-28 19:25:33 -04:00
Matt Nadareski
d074a6a7ee Force underlying type to be used for enum 2024-04-28 19:25:12 -04:00
Matt Nadareski
0c736c2491 Bump version 2024-04-28 18:46:28 -04:00
Matt Nadareski
964506057d Handle enums like primatives 2024-04-28 17:55:54 -04:00
Matt Nadareski
cd08925411 Fix write tests, add notes 2024-04-28 16:55:16 -04:00
Matt Nadareski
6ea8aab7c7 Bump version 2024-04-28 16:43:39 -04:00
Matt Nadareski
561dbdcc9a Fix type deserialization extensions, leave some TODOs 2024-04-28 16:42:51 -04:00
Matt Nadareski
b4bad28823 Safer type reading 2024-04-28 09:41:37 -04:00
Matt Nadareski
ec9db7e732 Bump version 2024-04-26 20:49:19 -04:00
Matt Nadareski
3c7401fefc Make byte validation helper methods static 2024-04-25 20:37:16 -04:00
Matt Nadareski
09e66c9ec3 Enable type writing tests 2024-04-25 20:36:44 -04:00
Matt Nadareski
8d1bc3957c Add some more tests for BinaryReader 2024-04-25 20:29:21 -04:00
Matt Nadareski
245ca9010a Add decimal write tests 2024-04-25 20:24:46 -04:00
Matt Nadareski
88207100f1 Add decimal read tests 2024-04-25 20:19:24 -04:00
Matt Nadareski
3befd9255a Add decimal writing extensions 2024-04-25 16:36:02 -04:00
Matt Nadareski
37f2848bb2 Add U/Int24 and U/Int48 writing extensions 2024-04-25 16:13:03 -04:00
Matt Nadareski
c5dca60d28 Add Half writing implementations 2024-04-25 15:46:57 -04:00
Matt Nadareski
351e46534d Clean up duplicate write extensions 2024-04-25 15:35:19 -04:00
Matt Nadareski
d39324c887 Add Half reading implementations, add note to writers 2024-04-25 15:27:47 -04:00
Matt Nadareski
163f49281d Add U/Int48 extensions with notes 2024-04-25 15:11:58 -04:00
Matt Nadareski
03d0f7dd18 Add U/Int24 extensions 2024-04-25 15:02:48 -04:00
Matt Nadareski
fbbe77f5f2 Sanity checks before writing 2024-04-25 14:30:28 -04:00
Matt Nadareski
4bffd9d31c Use stringified characters for writing 2024-04-25 14:17:11 -04:00
Matt Nadareski
904aed1c44 Move SeekIfPossible to a better location 2024-04-25 14:11:19 -04:00
Matt Nadareski
69a41b2487 Split extensions classes 2024-04-25 14:09:34 -04:00
Matt Nadareski
f326c921e6 Rename write methods, add tests 2024-04-25 14:05:06 -04:00
Matt Nadareski
73c4e8dd50 Add writer extensions 2024-04-25 12:45:51 -04:00
Matt Nadareski
e4c8bbc3f9 Start prepping for writer extensions 2024-04-25 12:00:45 -04:00
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
33 changed files with 8897 additions and 1306 deletions

View File

@@ -1,17 +1,82 @@
using System;
using System.IO;
using System.Linq;
#if NET7_0_OR_GREATER
using System.Numerics;
#endif
using SabreTools.IO.Extensions;
using Xunit;
namespace SabreTools.IO.Test
namespace SabreTools.IO.Test.Extensions
{
// TODO: Add string reading tests
public class BinaryReaderExtensionsTests
{
/// <summary>
/// Test pattern from 0x00-0x0F
/// </summary>
private static readonly byte[] _bytes =
[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
];
/// <summary>
/// Represents the decimal value 0.0123456789
/// </summary>
private static readonly byte[] _decimalBytes =
[
0x15, 0xCD, 0x5B, 0x07, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00,
];
[Fact]
public void ReadByteTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
byte read = br.ReadByte();
Assert.Equal(0x00, read);
}
[Fact]
public void ReadBytesTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
int length = 4;
byte[] read = br.ReadBytes(length);
Assert.Equal(length, read.Length);
Assert.True(read.SequenceEqual(_bytes.Take(length)));
}
[Fact]
public void ReadSByteTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
sbyte read = br.ReadSByte();
Assert.Equal(0x00, read);
}
[Fact]
public void ReadCharTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
char read = br.ReadChar();
Assert.Equal('\0', read);
}
[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()
{
@@ -21,6 +86,15 @@ namespace SabreTools.IO.Test
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()
{
@@ -30,6 +104,73 @@ namespace SabreTools.IO.Test
Assert.Equal(0x0001, read);
}
#if NET6_0_OR_GREATER
[Fact]
public void ReadHalfTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
Half expected = BitConverter.Int16BitsToHalf(0x0100);
Half read = br.ReadHalf();
Assert.Equal(expected, read);
}
[Fact]
public void ReadHalfBigEndianTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
Half expected = BitConverter.Int16BitsToHalf(0x0001);
Half read = br.ReadHalfBigEndian();
Assert.Equal(expected, read);
}
#endif
[Fact]
public void ReadInt24Test()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
int read = br.ReadInt24();
Assert.Equal(0x020100, read);
}
[Fact]
public void ReadInt24BigEndianTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
int read = br.ReadInt24BigEndian();
Assert.Equal(0x000102, read);
}
[Fact]
public void ReadUInt24Test()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
uint read = br.ReadUInt24();
Assert.Equal((uint)0x020100, read);
}
[Fact]
public void ReadUInt24BigEndianTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
uint read = br.ReadUInt24BigEndian();
Assert.Equal((uint)0x000102, 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()
{
@@ -39,6 +180,15 @@ namespace SabreTools.IO.Test
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()
{
@@ -48,6 +198,71 @@ namespace SabreTools.IO.Test
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 ReadInt48Test()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
long read = br.ReadInt48();
Assert.Equal(0x050403020100, read);
}
[Fact]
public void ReadInt48BigEndianTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
long read = br.ReadInt48BigEndian();
Assert.Equal(0x000102030405, read);
}
[Fact]
public void ReadUInt48Test()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
ulong read = br.ReadUInt48();
Assert.Equal((ulong)0x050403020100, read);
}
[Fact]
public void ReadUInt48BigEndianTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
ulong read = br.ReadUInt48BigEndian();
Assert.Equal((ulong)0x000102030405, 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()
{
@@ -57,6 +272,15 @@ namespace SabreTools.IO.Test
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()
{
@@ -66,8 +290,297 @@ namespace SabreTools.IO.Test
Assert.Equal((ulong)0x0001020304050607, read);
}
// TODO: Add byte[], char[] tests
// TODO: Add float, double tests
// TODO: Add string reading tests
[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 ReadDecimalTest()
{
var stream = new MemoryStream(_decimalBytes);
var br = new BinaryReader(stream);
decimal expected = 0.0123456789M;
decimal read = br.ReadDecimal();
Assert.Equal(expected, read);
}
[Fact]
public void ReadDecimalBigEndianTest()
{
var stream = new MemoryStream(_decimalBytes.Reverse().ToArray());
var br = new BinaryReader(stream);
decimal expected = 0.0123456789M;
decimal read = br.ReadDecimalBigEndian();
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 = (Int128)new BigInteger(_bytes);
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 = (Int128)new BigInteger(reversed);
Int128 read = br.ReadInt128BigEndian();
Assert.Equal(expected, read);
}
[Fact]
public void ReadUInt128Test()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
var expected = (UInt128)new BigInteger(_bytes);
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 = (UInt128)new BigInteger(reversed);
UInt128 read = br.ReadUInt128BigEndian();
Assert.Equal(expected, read);
}
#endif
[Fact]
public void ReadTypeExplicitTest()
{
byte[] bytesWithString =
[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x41, 0x42, 0x43, 0x00,
];
var stream = new MemoryStream(bytesWithString);
var br = new BinaryReader(stream);
var expected = new TestStructExplicit
{
FirstValue = TestEnum.RecognizedTestValue,
SecondValue = 0x07060504,
ThirdValue = 0x0504,
FourthValue = 0x0706,
FifthValue = "ABC",
};
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);
Assert.Equal(expected.FifthValue, read.FifthValue);
}
[Fact]
public void ReadTypeSequentialTest()
{
byte[] bytesWithString =
[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x41, 0x42, 0x43, 0x00,
];
var stream = new MemoryStream(bytesWithString);
var br = new BinaryReader(stream);
var expected = new TestStructSequential
{
FirstValue = TestEnum.RecognizedTestValue,
SecondValue = 0x07060504,
ThirdValue = 0x0908,
FourthValue = 0x0B0A,
FifthValue = "ABC",
};
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);
Assert.Equal(expected.FifthValue, read.FifthValue);
}
[Fact]
public void ReadTypeStringsTest()
{
byte[] structBytes =
[
0x03, 0x41, 0x42, 0x43, // AnsiBStr
0x03, 0x00, 0x41, 0x00, 0x42, 0x00, 0x43, 0x00, // BStr
0x41, 0x42, 0x43, // ByValTStr
0x41, 0x42, 0x43, 0x00, // LPStr
0x41, 0x00, 0x42, 0x00, 0x43, 0x00, 0x00, 0x00, // LPWStr
];
var stream = new MemoryStream(structBytes);
var br = new BinaryReader(stream);
var expected = new TestStructStrings
{
AnsiBStr = "ABC",
BStr = "ABC",
ByValTStr = "ABC",
LPStr = "ABC",
LPWStr = "ABC",
};
var read = br.ReadType<TestStructStrings>();
Assert.Equal(expected.AnsiBStr, read.AnsiBStr);
Assert.Equal(expected.BStr, read.BStr);
Assert.Equal(expected.ByValTStr, read.ByValTStr);
Assert.Equal(expected.LPStr, read.LPStr);
Assert.Equal(expected.LPWStr, read.LPWStr);
}
[Fact]
public void ReadTypeArraysTest()
{
byte[] structBytes =
[
// Byte Array
0x00, 0x01, 0x02, 0x03,
// Int Array
0x03, 0x02, 0x01, 0x00,
0x04, 0x03, 0x02, 0x01,
0x05, 0x04, 0x03, 0x02,
0x06, 0x05, 0x04, 0x03,
// Struct Array (X, Y)
0xFF, 0x00, 0x00, 0xFF,
0x00, 0xFF, 0xFF, 0x00,
0xAA, 0x55, 0x55, 0xAA,
0x55, 0xAA, 0xAA, 0x55,
// LPArray
0x04, 0x00,
0x00, 0x01, 0x02, 0x03,
];
var stream = new MemoryStream(structBytes);
var br = new BinaryReader(stream);
var expected = new TestStructArrays
{
ByteArray = [0x00, 0x01, 0x02, 0x03],
IntArray = [0x00010203, 0x01020304, 0x02030405, 0x03040506],
StructArray =
[
new TestStructPoint { X = 0x00FF, Y = 0xFF00 },
new TestStructPoint { X = 0xFF00, Y = 0x00FF },
new TestStructPoint { X = 0x55AA, Y = 0xAA55 },
new TestStructPoint { X = 0xAA55, Y = 0x55AA },
],
LPByteArrayLength = 0x0004,
LPByteArray = [0x00, 0x01, 0x02, 0x03],
};
var read = br.ReadType<TestStructArrays>();
Assert.NotNull(read.ByteArray);
Assert.True(expected.ByteArray.SequenceEqual(read.ByteArray));
Assert.NotNull(read.IntArray);
Assert.True(expected.IntArray.SequenceEqual(read.IntArray));
Assert.NotNull(read.StructArray);
Assert.True(expected.StructArray.SequenceEqual(read.StructArray));
Assert.Equal(expected.LPByteArrayLength, read.LPByteArrayLength);
Assert.NotNull(read.LPByteArray);
Assert.True(expected.LPByteArray.SequenceEqual(read.LPByteArray));
}
[Fact]
public void ReadTypeInheritanceTest()
{
byte[] structBytes1 =
[
0x41, 0x42, 0x43, 0x44, // Signature
0x00, 0xFF, 0x00, 0xFF, // IdentifierType
0xAA, 0x55, 0xAA, 0x55, // FieldA
0x55, 0xAA, 0x55, 0xAA, // FieldB
];
var stream1 = new MemoryStream(structBytes1);
var br1 = new BinaryReader(stream1);
var expected1 = new TestStructInheritanceChild1
{
Signature = [0x41, 0x42, 0x43, 0x44],
IdentifierType = 0xFF00FF00,
FieldA = 0x55AA55AA,
FieldB = 0xAA55AA55,
};
var read1 = br1.ReadType<TestStructInheritanceChild1>();
Assert.NotNull(read1?.Signature);
Assert.Equal(expected1.Signature, read1.Signature);
Assert.Equal(expected1.IdentifierType, read1.IdentifierType);
Assert.Equal(expected1.FieldA, read1.FieldA);
Assert.Equal(expected1.FieldB, read1.FieldB);
byte[] structBytes2 =
[
0x41, 0x42, 0x43, 0x44, // Signature
0x00, 0xFF, 0x00, 0xFF, // IdentifierType
0xAA, 0x55, // FieldA
0x55, 0xAA, // FieldB
];
var stream2 = new MemoryStream(structBytes2);
var br2 = new BinaryReader(stream2);
var expected2 = new TestStructInheritanceChild2
{
Signature = [0x41, 0x42, 0x43, 0x44],
IdentifierType = 0xFF00FF00,
FieldA = 0x55AA,
FieldB = 0xAA55,
};
var read2 = br2.ReadType<TestStructInheritanceChild2>();
Assert.NotNull(read2?.Signature);
Assert.Equal(expected2.Signature, read2.Signature);
Assert.Equal(expected2.IdentifierType, read2.IdentifierType);
Assert.Equal(expected2.FieldA, read2.FieldA);
Assert.Equal(expected2.FieldB, read2.FieldB);
}
}
}

View File

@@ -0,0 +1,508 @@
using System;
using System.IO;
using System.Linq;
#if NET7_0_OR_GREATER
using System.Numerics;
#endif
using System.Text;
using SabreTools.IO.Extensions;
using Xunit;
namespace SabreTools.IO.Test.Extensions
{
// TODO: Add byte[], char[] tests
// TODO: Add string writing tests
public class BinaryWriterExtensionsTests
{
/// <summary>
/// Test pattern from 0x00-0x0F
/// </summary>
private static readonly byte[] _bytes =
[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
];
/// <summary>
/// Represents the decimal value 0.0123456789
/// </summary>
private static readonly byte[] _decimalBytes =
[
0x15, 0xCD, 0x5B, 0x07, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00,
];
[Fact]
public void WriteByteValueTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = _bytes.Take(1).ToArray();
bw.Write((byte)0x00);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteBytesTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = _bytes.Take(4).ToArray();
bw.Write([0x00, 0x01, 0x02, 0x03]);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteSByteTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = _bytes.Take(1).ToArray();
bw.Write((sbyte)0x00);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteCharTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = _bytes.Take(1).ToArray();
bw.Write('\0');
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteCharEncodingTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = [0x00, 0x00];
bw.Write('\0', Encoding.Unicode);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteInt16Test()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = _bytes.Take(2).ToArray();
bw.Write((short)0x0100);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteInt16BigEndianTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = _bytes.Take(2).ToArray();
bool write = bw.WriteBigEndian((short)0x0001);
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteUInt16Test()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = _bytes.Take(2).ToArray();
bw.Write((ushort)0x0100);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteUInt16BigEndianTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = _bytes.Take(2).ToArray();
bool write = bw.WriteBigEndian((ushort)0x0001);
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
#if NET6_0_OR_GREATER
[Fact]
public void WriteHalfTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = _bytes.Take(2).ToArray();
bw.Write(BitConverter.Int16BitsToHalf(0x0100));
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteHalfBigEndianTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = _bytes.Take(2).ToArray();
bool write = bw.WriteBigEndian(BitConverter.Int16BitsToHalf(0x0001));
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
#endif
[Fact]
public void WriteInt24Test()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = _bytes.Take(3).ToArray();
bw.WriteAsInt24(0x020100);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteInt24BigEndianTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = _bytes.Take(3).ToArray();
bool write = bw.WriteAsInt24BigEndian(0x000102);
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteUInt24Test()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = _bytes.Take(3).ToArray();
bw.WriteAsUInt24(0x020100);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteUInt24BigEndianTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = _bytes.Take(3).ToArray();
bool write = bw.WriteAsUInt24BigEndian(0x000102);
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteInt32Test()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = _bytes.Take(4).ToArray();
bw.Write(0x03020100);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteInt32BigEndianTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = _bytes.Take(4).ToArray();
bool write = bw.WriteBigEndian(0x00010203);
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteUInt32Test()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = _bytes.Take(4).ToArray();
bw.Write((uint)0x03020100);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteUInt32BigEndianTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = _bytes.Take(4).ToArray();
bool write = bw.WriteBigEndian((uint)0x00010203);
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteSingleTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = _bytes.Take(4).ToArray();
bw.Write(BitConverter.Int32BitsToSingle(0x03020100));
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteSingleBigEndianTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = _bytes.Take(4).ToArray();
bool write = bw.WriteBigEndian(BitConverter.Int32BitsToSingle(0x00010203));
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteInt48Test()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = _bytes.Take(6).ToArray();
bw.WriteAsInt48(0x050403020100);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteInt48BigEndianTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = _bytes.Take(6).ToArray();
bool write = bw.WriteAsInt48BigEndian(0x000102030405);
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteUInt48Test()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = _bytes.Take(6).ToArray();
bw.WriteAsUInt48(0x050403020100);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteUInt48BigEndianTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = _bytes.Take(6).ToArray();
bool write = bw.WriteAsUInt48BigEndian(0x000102030405);
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteInt64Test()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = _bytes.Take(8).ToArray();
bw.Write(0x0706050403020100);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteInt64BigEndianTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = _bytes.Take(8).ToArray();
bool write = bw.WriteBigEndian(0x0001020304050607);
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteUInt64Test()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = _bytes.Take(8).ToArray();
bw.Write((ulong)0x0706050403020100);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteUInt64BigEndianTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = _bytes.Take(8).ToArray();
bool write = bw.WriteBigEndian((ulong)0x0001020304050607);
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteDoubleTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = _bytes.Take(8).ToArray();
bw.Write(BitConverter.Int64BitsToDouble(0x0706050403020100));
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteDoubleBigEndianTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = _bytes.Take(8).ToArray();
bool write = bw.WriteBigEndian(BitConverter.Int64BitsToDouble(0x0001020304050607));
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteDecimalTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = _decimalBytes.Take(16).ToArray();
bw.Write(0.0123456789M);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteDecimalBigEndianTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = _decimalBytes.Take(16).Reverse().ToArray();
bool write = bw.WriteBigEndian(0.0123456789M);
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteGuidTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = _bytes.Take(16).ToArray();
bool write = bw.Write(new Guid(_bytes));
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteGuidBigEndianTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = _bytes.Take(16).ToArray();
bool write = bw.WriteBigEndian(new Guid(_bytes.Reverse().ToArray()));
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
#if NET7_0_OR_GREATER
[Fact]
public void WriteInt128Test()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = _bytes.Take(16).ToArray();
bool write = bw.Write((Int128)new BigInteger(_bytes));
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteInt128BigEndianTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = _bytes.Take(16).ToArray();
bool write = bw.WriteBigEndian((Int128)new BigInteger(_bytes.Reverse().ToArray()));
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteUInt128Test()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = _bytes.Take(16).ToArray();
bool write = bw.Write((UInt128)new BigInteger(_bytes));
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteUInt128BigEndianTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = _bytes.Take(16).ToArray();
bool write = bw.WriteBigEndian((UInt128)new BigInteger(_bytes.Reverse().ToArray()));
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
#endif
[Fact]
public void WriteTypeExplicitTest()
{
byte[] bytesWithString =
[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x41, 0x42, 0x43, 0x00,
];
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
var obj = new TestStructExplicit
{
FirstValue = TestEnum.RecognizedTestValue,
SecondValue = 0x07060504,
FifthValue = "ABC",
};
byte[] expected = bytesWithString.Take(12).ToArray();
bool write = bw.WriteType(obj);
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteTypeSequentialTest()
{
byte[] bytesWithString =
[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x41, 0x42, 0x43, 0x00,
];
var stream = new MemoryStream(new byte[24], 0, count: 24, true, true);
var bw = new BinaryWriter(stream);
var obj = new TestStructSequential
{
FirstValue = TestEnum.RecognizedTestValue,
SecondValue = 0x07060504,
ThirdValue = 0x0908,
FourthValue = 0x0B0A,
FifthValue = "ABC",
};
byte[] expected = bytesWithString.Take(16).ToArray();
bool write = bw.WriteType(obj);
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
/// <summary>
/// Validate that a set of actual bytes matches the expected bytes
/// </summary>
private static void ValidateBytes(byte[] expected, byte[] actual)
{
for (int i = 0; i < expected.Length; i++)
{
Assert.Equal(expected[i], actual[i]);
}
}
}
}

View File

@@ -0,0 +1,547 @@
using System;
using System.Linq;
#if NET7_0_OR_GREATER
using System.Numerics;
#endif
using SabreTools.IO.Extensions;
using Xunit;
namespace SabreTools.IO.Test.Extensions
{
// TODO: Add string reading tests
public class ByteArrayExtensionsReadTests
{
/// <summary>
/// Test pattern from 0x00-0x0F
/// </summary>
private static readonly byte[] _bytes =
[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
];
/// <summary>
/// Represents the decimal value 0.0123456789
/// </summary>
private static readonly byte[] _decimalBytes =
[
0x15, 0xCD, 0x5B, 0x07, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00,
];
[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);
}
#if NET6_0_OR_GREATER
[Fact]
public void ReadHalfTest()
{
int offset = 0;
Half expected = BitConverter.Int16BitsToHalf(0x0100);
Half read = _bytes.ReadHalf(ref offset);
Assert.Equal(expected, read);
}
[Fact]
public void ReadHalfBigEndianTest()
{
int offset = 0;
Half expected = BitConverter.Int16BitsToHalf(0x0001);
Half read = _bytes.ReadHalfBigEndian(ref offset);
Assert.Equal(expected, read);
}
#endif
[Fact]
public void ReadInt24Test()
{
int offset = 0;
int read = _bytes.ReadInt24(ref offset);
Assert.Equal(0x020100, read);
}
[Fact]
public void ReadInt24BigEndianTest()
{
int offset = 0;
int read = _bytes.ReadInt24BigEndian(ref offset);
Assert.Equal(0x000102, read);
}
[Fact]
public void ReadUInt24Test()
{
int offset = 0;
uint read = _bytes.ReadUInt24(ref offset);
Assert.Equal((uint)0x020100, read);
}
[Fact]
public void ReadUInt24BigEndianTest()
{
int offset = 0;
uint read = _bytes.ReadUInt24BigEndian(ref offset);
Assert.Equal((uint)0x000102, 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 ReadInt48Test()
{
int offset = 0;
long read = _bytes.ReadInt48(ref offset);
Assert.Equal(0x050403020100, read);
}
[Fact]
public void ReadInt48BigEndianTest()
{
int offset = 0;
long read = _bytes.ReadInt48BigEndian(ref offset);
Assert.Equal(0x000102030405, read);
}
[Fact]
public void ReadUInt48Test()
{
int offset = 0;
ulong read = _bytes.ReadUInt48(ref offset);
Assert.Equal((ulong)0x050403020100, read);
}
[Fact]
public void ReadUInt48BigEndianTest()
{
int offset = 0;
ulong read = _bytes.ReadUInt48BigEndian(ref offset);
Assert.Equal((ulong)0x000102030405, 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 ReadDecimalTest()
{
int offset = 0;
decimal expected = 0.0123456789M;
decimal read = _decimalBytes.ReadDecimal(ref offset);
Assert.Equal(expected, read);
}
[Fact]
public void ReadDecimalBigEndianTest()
{
int offset = 0;
decimal expected = 0.0123456789M;
decimal read = _decimalBytes.Reverse().ToArray().ReadDecimalBigEndian(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 = (Int128)new BigInteger(_bytes);
Int128 read = _bytes.ReadInt128(ref offset);
Assert.Equal(expected, read);
}
[Fact]
public void ReadInt128BigEndianTest()
{
int offset = 0;
var reversed = _bytes.Reverse().ToArray();
var expected = (Int128)new BigInteger(reversed);
Int128 read = _bytes.ReadInt128BigEndian(ref offset);
Assert.Equal(expected, read);
}
[Fact]
public void ReadUInt128Test()
{
int offset = 0;
var expected = (UInt128)new BigInteger(_bytes);
UInt128 read = _bytes.ReadUInt128(ref offset);
Assert.Equal(expected, read);
}
[Fact]
public void ReadUInt128BigEndianTest()
{
int offset = 0;
var reversed = _bytes.Reverse().ToArray();
var expected = (UInt128)new BigInteger(reversed);
UInt128 read = _bytes.ReadUInt128BigEndian(ref offset);
Assert.Equal(expected, read);
}
#endif
[Fact]
public void ReadTypeExplicitTest()
{
byte[] bytesWithString =
[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x41, 0x42, 0x43, 0x00,
];
int offset = 0;
var expected = new TestStructExplicit
{
FirstValue = TestEnum.RecognizedTestValue,
SecondValue = 0x07060504,
ThirdValue = 0x0504,
FourthValue = 0x0706,
FifthValue = "ABC",
};
var read = bytesWithString.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()
{
byte[] bytesWithString =
[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x41, 0x42, 0x43, 0x00,
];
int offset = 0;
var expected = new TestStructSequential
{
FirstValue = TestEnum.RecognizedTestValue,
SecondValue = 0x07060504,
ThirdValue = 0x0908,
FourthValue = 0x0B0A,
FifthValue = "ABC",
};
var read = bytesWithString.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);
Assert.Equal(expected.FifthValue, read.FifthValue);
}
[Fact]
public void ReadTypeStringsTest()
{
byte[] structBytes =
[
0x03, 0x41, 0x42, 0x43, // AnsiBStr
0x03, 0x00, 0x41, 0x00, 0x42, 0x00, 0x43, 0x00, // BStr
0x41, 0x42, 0x43, // ByValTStr
0x41, 0x42, 0x43, 0x00, // LPStr
0x41, 0x00, 0x42, 0x00, 0x43, 0x00, 0x00, 0x00, // LPWStr
];
int offset = 0;
var expected = new TestStructStrings
{
AnsiBStr = "ABC",
BStr = "ABC",
ByValTStr = "ABC",
LPStr = "ABC",
LPWStr = "ABC",
};
var read = structBytes.ReadType<TestStructStrings>(ref offset);
Assert.Equal(expected.AnsiBStr, read.AnsiBStr);
Assert.Equal(expected.BStr, read.BStr);
Assert.Equal(expected.ByValTStr, read.ByValTStr);
Assert.Equal(expected.LPStr, read.LPStr);
Assert.Equal(expected.LPWStr, read.LPWStr);
}
[Fact]
public void ReadTypeArraysTest()
{
byte[] structBytes =
[
// Byte Array
0x00, 0x01, 0x02, 0x03,
// Int Array
0x03, 0x02, 0x01, 0x00,
0x04, 0x03, 0x02, 0x01,
0x05, 0x04, 0x03, 0x02,
0x06, 0x05, 0x04, 0x03,
// Struct Array (X, Y)
0xFF, 0x00, 0x00, 0xFF,
0x00, 0xFF, 0xFF, 0x00,
0xAA, 0x55, 0x55, 0xAA,
0x55, 0xAA, 0xAA, 0x55,
// LPArray
0x04, 0x00,
0x00, 0x01, 0x02, 0x03,
];
int offset = 0;
var expected = new TestStructArrays
{
ByteArray = [0x00, 0x01, 0x02, 0x03],
IntArray = [0x00010203, 0x01020304, 0x02030405, 0x03040506],
StructArray =
[
new TestStructPoint { X = 0x00FF, Y = 0xFF00 },
new TestStructPoint { X = 0xFF00, Y = 0x00FF },
new TestStructPoint { X = 0x55AA, Y = 0xAA55 },
new TestStructPoint { X = 0xAA55, Y = 0x55AA },
],
LPByteArrayLength = 0x0004,
LPByteArray = [0x00, 0x01, 0x02, 0x03],
};
var read = structBytes.ReadType<TestStructArrays>(ref offset);
Assert.NotNull(read.ByteArray);
Assert.True(expected.ByteArray.SequenceEqual(read.ByteArray));
Assert.NotNull(read.IntArray);
Assert.True(expected.IntArray.SequenceEqual(read.IntArray));
Assert.NotNull(read.StructArray);
Assert.True(expected.StructArray.SequenceEqual(read.StructArray));
Assert.Equal(expected.LPByteArrayLength, read.LPByteArrayLength);
Assert.NotNull(read.LPByteArray);
Assert.True(expected.LPByteArray.SequenceEqual(read.LPByteArray));
}
[Fact]
public void ReadTypeInheritanceTest()
{
byte[] structBytes1 =
[
0x41, 0x42, 0x43, 0x44, // Signature
0x00, 0xFF, 0x00, 0xFF, // IdentifierType
0xAA, 0x55, 0xAA, 0x55, // FieldA
0x55, 0xAA, 0x55, 0xAA, // FieldB
];
int offset1 = 0;
var expected1 = new TestStructInheritanceChild1
{
Signature = [0x41, 0x42, 0x43, 0x44],
IdentifierType = 0xFF00FF00,
FieldA = 0x55AA55AA,
FieldB = 0xAA55AA55,
};
var read1 = structBytes1.ReadType<TestStructInheritanceChild1>(ref offset1);
Assert.NotNull(read1?.Signature);
Assert.Equal(expected1.Signature, read1.Signature);
Assert.Equal(expected1.IdentifierType, read1.IdentifierType);
Assert.Equal(expected1.FieldA, read1.FieldA);
Assert.Equal(expected1.FieldB, read1.FieldB);
byte[] structBytes2 =
[
0x41, 0x42, 0x43, 0x44, // Signature
0x00, 0xFF, 0x00, 0xFF, // IdentifierType
0xAA, 0x55, // FieldA
0x55, 0xAA, // FieldB
];
int offset2 = 0;
var expected2 = new TestStructInheritanceChild2
{
Signature = [0x41, 0x42, 0x43, 0x44],
IdentifierType = 0xFF00FF00,
FieldA = 0x55AA,
FieldB = 0xAA55,
};
var read2 = structBytes2.ReadType<TestStructInheritanceChild2>(ref offset2);
Assert.NotNull(read2?.Signature);
Assert.Equal(expected2.Signature, read2.Signature);
Assert.Equal(expected2.IdentifierType, read2.IdentifierType);
Assert.Equal(expected2.FieldA, read2.FieldA);
Assert.Equal(expected2.FieldB, read2.FieldB);
}
}
}

View File

@@ -1,249 +0,0 @@
using System;
using System.Linq;
using SabreTools.IO.Extensions;
using Xunit;
namespace SabreTools.IO.Test
{
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 ReadGuidBigEndian()
{
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
// TODO: Add string reading tests
}
}

View File

@@ -0,0 +1,491 @@
using System;
using System.Linq;
#if NET7_0_OR_GREATER
using System.Numerics;
#endif
using SabreTools.IO.Extensions;
using Xunit;
namespace SabreTools.IO.Test.Extensions
{
// TODO: Add string writing tests
public class ByteArrayExtensionsWriteTests
{
/// <summary>
/// Test pattern from 0x00-0x0F
/// </summary>
private static readonly byte[] _bytes =
[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
];
/// <summary>
/// Represents the decimal value 0.0123456789
/// </summary>
private static readonly byte[] _decimalBytes =
[
0x15, 0xCD, 0x5B, 0x07, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00,
];
[Fact]
public void WriteByteTest()
{
byte[] buffer = new byte[16];
int offset = 0;
byte[] expected = _bytes.Take(1).ToArray();
bool write = buffer.Write(ref offset, (byte)0x00);
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteBytesTest()
{
byte[] buffer = new byte[16];
int offset = 0;
byte[] expected = _bytes.Take(4).ToArray();
bool write = buffer.Write(ref offset, [0x00, 0x01, 0x02, 0x03]);
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteSByteTest()
{
byte[] buffer = new byte[16];
int offset = 0;
byte[] expected = _bytes.Take(1).ToArray();
bool write = buffer.Write(ref offset, (sbyte)0x00);
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteCharTest()
{
byte[] buffer = new byte[16];
int offset = 0;
byte[] expected = _bytes.Take(1).ToArray();
bool write = buffer.Write(ref offset, '\0');
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteInt16Test()
{
byte[] buffer = new byte[16];
int offset = 0;
byte[] expected = _bytes.Take(2).ToArray();
bool write = buffer.Write(ref offset, (short)0x0100);
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteInt16BigEndianTest()
{
byte[] buffer = new byte[16];
int offset = 0;
byte[] expected = _bytes.Take(2).ToArray();
bool write = buffer.WriteBigEndian(ref offset, (short)0x0001);
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteUInt16Test()
{
byte[] buffer = new byte[16];
int offset = 0;
byte[] expected = _bytes.Take(2).ToArray();
bool write = buffer.Write(ref offset, (ushort)0x0100);
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteUInt16BigEndianTest()
{
byte[] buffer = new byte[16];
int offset = 0;
byte[] expected = _bytes.Take(2).ToArray();
bool write = buffer.WriteBigEndian(ref offset, (ushort)0x0001);
Assert.True(write);
ValidateBytes(expected, buffer);
}
#if NET6_0_OR_GREATER
[Fact]
public void WriteHalfTest()
{
byte[] buffer = new byte[16];
int offset = 0;
byte[] expected = _bytes.Take(2).ToArray();
bool write = buffer.Write(ref offset, BitConverter.Int16BitsToHalf(0x0100));
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteHalfBigEndianTest()
{
byte[] buffer = new byte[16];
int offset = 0;
byte[] expected = _bytes.Take(2).ToArray();
bool write = buffer.WriteBigEndian(ref offset, BitConverter.Int16BitsToHalf(0x0001));
Assert.True(write);
ValidateBytes(expected, buffer);
}
#endif
[Fact]
public void WriteInt24Test()
{
byte[] buffer = new byte[16];
int offset = 0;
byte[] expected = _bytes.Take(3).ToArray();
bool write = buffer.WriteAsInt24(ref offset, 0x020100);
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteInt24BigEndianTest()
{
byte[] buffer = new byte[16];
int offset = 0;
byte[] expected = _bytes.Take(3).ToArray();
bool write = buffer.WriteAsInt24BigEndian(ref offset, 0x000102);
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteUInt24Test()
{
byte[] buffer = new byte[16];
int offset = 0;
byte[] expected = _bytes.Take(3).ToArray();
bool write = buffer.WriteAsUInt24(ref offset, 0x020100);
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteUInt24BigEndianTest()
{
byte[] buffer = new byte[16];
int offset = 0;
byte[] expected = _bytes.Take(3).ToArray();
bool write = buffer.WriteAsUInt24BigEndian(ref offset, 0x000102);
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteInt32Test()
{
byte[] buffer = new byte[16];
int offset = 0;
byte[] expected = _bytes.Take(4).ToArray();
bool write = buffer.Write(ref offset, 0x03020100);
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteInt32BigEndianTest()
{
byte[] buffer = new byte[16];
int offset = 0;
byte[] expected = _bytes.Take(4).ToArray();
bool write = buffer.WriteBigEndian(ref offset, 0x00010203);
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteUInt32Test()
{
byte[] buffer = new byte[16];
int offset = 0;
byte[] expected = _bytes.Take(4).ToArray();
bool write = buffer.Write(ref offset, (uint)0x03020100);
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteUInt32BigEndianTest()
{
byte[] buffer = new byte[16];
int offset = 0;
byte[] expected = _bytes.Take(4).ToArray();
bool write = buffer.WriteBigEndian(ref offset, (uint)0x00010203);
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteSingleTest()
{
byte[] buffer = new byte[16];
int offset = 0;
byte[] expected = _bytes.Take(4).ToArray();
bool write = buffer.Write(ref offset, BitConverter.Int32BitsToSingle(0x03020100));
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteSingleBigEndianTest()
{
byte[] buffer = new byte[16];
int offset = 0;
byte[] expected = _bytes.Take(4).ToArray();
bool write = buffer.WriteBigEndian(ref offset, BitConverter.Int32BitsToSingle(0x00010203));
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteInt48Test()
{
byte[] buffer = new byte[16];
int offset = 0;
byte[] expected = _bytes.Take(6).ToArray();
bool write = buffer.WriteAsInt48(ref offset, 0x050403020100);
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteInt48BigEndianTest()
{
byte[] buffer = new byte[16];
int offset = 0;
byte[] expected = _bytes.Take(6).ToArray();
bool write = buffer.WriteAsInt48BigEndian(ref offset, 0x000102030405);
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteUInt48Test()
{
byte[] buffer = new byte[16];
int offset = 0;
byte[] expected = _bytes.Take(6).ToArray();
bool write = buffer.WriteAsUInt48(ref offset, 0x050403020100);
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteUInt48BigEndianTest()
{
byte[] buffer = new byte[16];
int offset = 0;
byte[] expected = _bytes.Take(6).ToArray();
bool write = buffer.WriteAsUInt48BigEndian(ref offset, 0x000102030405);
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteInt64Test()
{
byte[] buffer = new byte[16];
int offset = 0;
byte[] expected = _bytes.Take(8).ToArray();
bool write = buffer.Write(ref offset, 0x0706050403020100);
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteInt64BigEndianTest()
{
byte[] buffer = new byte[16];
int offset = 0;
byte[] expected = _bytes.Take(8).ToArray();
bool write = buffer.WriteBigEndian(ref offset, 0x0001020304050607);
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteUInt64Test()
{
byte[] buffer = new byte[16];
int offset = 0;
byte[] expected = _bytes.Take(8).ToArray();
bool write = buffer.Write(ref offset, (ulong)0x0706050403020100);
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteUInt64BigEndianTest()
{
byte[] buffer = new byte[16];
int offset = 0;
byte[] expected = _bytes.Take(8).ToArray();
bool write = buffer.WriteBigEndian(ref offset, (ulong)0x0001020304050607);
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteDecimalTest()
{
byte[] buffer = new byte[16];
int offset = 0;
byte[] expected = _decimalBytes.Take(16).ToArray();
bool write = buffer.Write(ref offset, 0.0123456789M);
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteDecimalBigEndianTest()
{
byte[] buffer = new byte[16];
int offset = 0;
byte[] expected = _decimalBytes.Take(16).Reverse().ToArray();
bool write = buffer.WriteBigEndian(ref offset, 0.0123456789M);
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteGuidTest()
{
byte[] buffer = new byte[16];
int offset = 0;
byte[] expected = _bytes.Take(16).ToArray();
bool write = buffer.Write(ref offset, new Guid(_bytes));
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteGuidBigEndianTest()
{
byte[] buffer = new byte[16];
int offset = 0;
byte[] expected = _bytes.Take(16).ToArray();
bool write = buffer.WriteBigEndian(ref offset, new Guid(_bytes.Reverse().ToArray()));
Assert.True(write);
ValidateBytes(expected, buffer);
}
#if NET7_0_OR_GREATER
[Fact]
public void WriteInt128Test()
{
byte[] buffer = new byte[16];
int offset = 0;
byte[] expected = _bytes.Take(16).ToArray();
bool write = buffer.Write(ref offset, (Int128)new BigInteger(_bytes));
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteInt128BigEndianTest()
{
byte[] buffer = new byte[16];
int offset = 0;
byte[] expected = _bytes.Take(16).ToArray();
bool write = buffer.WriteBigEndian(ref offset, (Int128)new BigInteger(_bytes.Reverse().ToArray()));
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteUInt128Test()
{
byte[] buffer = new byte[16];
int offset = 0;
byte[] expected = _bytes.Take(16).ToArray();
bool write = buffer.Write(ref offset, (UInt128)new BigInteger(_bytes));
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteUInt128BigEndianTest()
{
byte[] buffer = new byte[16];
int offset = 0;
byte[] expected = _bytes.Take(16).ToArray();
bool write = buffer.WriteBigEndian(ref offset, (UInt128)new BigInteger(_bytes.Reverse().ToArray()));
Assert.True(write);
ValidateBytes(expected, buffer);
}
#endif
[Fact]
public void WriteTypeExplicitTest()
{
byte[] bytesWithString =
[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x41, 0x42, 0x43, 0x00,
];
byte[] buffer = new byte[16];
int offset = 0;
var obj = new TestStructExplicit
{
FirstValue = TestEnum.RecognizedTestValue,
SecondValue = 0x07060504,
FifthValue = "ABC",
};
byte[] expected = bytesWithString.Take(12).ToArray();
bool write = buffer.WriteType(ref offset, obj);
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteTypeSequentialTest()
{
byte[] bytesWithString =
[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x41, 0x42, 0x43, 0x00,
];
byte[] buffer = new byte[24];
int offset = 0;
var obj = new TestStructSequential
{
FirstValue = TestEnum.RecognizedTestValue,
SecondValue = 0x07060504,
ThirdValue = 0x0908,
FourthValue = 0x0B0A,
FifthValue = "ABC",
};
byte[] expected = bytesWithString.Take(16).ToArray();
bool write = buffer.WriteType(ref offset, obj);
Assert.True(write);
ValidateBytes(expected, buffer);
}
/// <summary>
/// Validate that a set of actual bytes matches the expected bytes
/// </summary>
private static void ValidateBytes(byte[] expected, byte[] actual)
{
for (int i = 0; i < expected.Length; i++)
{
Assert.Equal(expected[i], actual[i]);
}
}
}
}

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

@@ -0,0 +1,541 @@
using System;
using System.IO;
using System.Linq;
#if NET7_0_OR_GREATER
using System.Numerics;
#endif
using SabreTools.IO.Extensions;
using Xunit;
namespace SabreTools.IO.Test.Extensions
{
// TODO: Add string reading tests
public class StreamExtensionsReadTests
{
/// <summary>
/// Test pattern from 0x00-0x0F
/// </summary>
private static readonly byte[] _bytes =
[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
];
/// <summary>
/// Represents the decimal value 0.0123456789
/// </summary>
private static readonly byte[] _decimalBytes =
[
0x15, 0xCD, 0x5B, 0x07, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00,
];
[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);
}
#if NET6_0_OR_GREATER
[Fact]
public void ReadHalfTest()
{
var stream = new MemoryStream(_bytes);
Half expected = BitConverter.Int16BitsToHalf(0x0100);
Half read = stream.ReadHalf();
Assert.Equal(expected, read);
}
[Fact]
public void ReadHalfBigEndianTest()
{
var stream = new MemoryStream(_bytes);
Half expected = BitConverter.Int16BitsToHalf(0x0001);
Half read = stream.ReadHalfBigEndian();
Assert.Equal(expected, read);
}
#endif
[Fact]
public void ReadInt24Test()
{
var stream = new MemoryStream(_bytes);
int read = stream.ReadInt24();
Assert.Equal(0x020100, read);
}
[Fact]
public void ReadInt24BigEndianTest()
{
var stream = new MemoryStream(_bytes);
int read = stream.ReadInt24BigEndian();
Assert.Equal(0x000102, read);
}
[Fact]
public void ReadUInt24Test()
{
var stream = new MemoryStream(_bytes);
uint read = stream.ReadUInt24();
Assert.Equal((uint)0x020100, read);
}
[Fact]
public void ReadUInt24BigEndianTest()
{
var stream = new MemoryStream(_bytes);
uint read = stream.ReadUInt24BigEndian();
Assert.Equal((uint)0x000102, 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 ReadInt48Test()
{
var stream = new MemoryStream(_bytes);
long read = stream.ReadInt48();
Assert.Equal(0x050403020100, read);
}
[Fact]
public void ReadInt48BigEndianTest()
{
var stream = new MemoryStream(_bytes);
long read = stream.ReadInt48BigEndian();
Assert.Equal(0x000102030405, read);
}
[Fact]
public void ReadUInt48Test()
{
var stream = new MemoryStream(_bytes);
ulong read = stream.ReadUInt48();
Assert.Equal((ulong)0x050403020100, read);
}
[Fact]
public void ReadUInt48BigEndianTest()
{
var stream = new MemoryStream(_bytes);
ulong read = stream.ReadUInt48BigEndian();
Assert.Equal((ulong)0x000102030405, 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 ReadDecimalTest()
{
var stream = new MemoryStream(_decimalBytes);
decimal expected = 0.0123456789M;
decimal read = stream.ReadDecimal();
Assert.Equal(expected, read);
}
[Fact]
public void ReadDecimalBigEndianTest()
{
var stream = new MemoryStream(_decimalBytes.Reverse().ToArray());
decimal expected = 0.0123456789M;
decimal read = stream.ReadDecimalBigEndian();
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 = (Int128)new BigInteger(_bytes);
Int128 read = stream.ReadInt128();
Assert.Equal(expected, read);
}
[Fact]
public void ReadInt128BigEndianTest()
{
var stream = new MemoryStream(_bytes);
var reversed = _bytes.Reverse().ToArray();
var expected = (Int128)new BigInteger(reversed);
Int128 read = stream.ReadInt128BigEndian();
Assert.Equal(expected, read);
}
[Fact]
public void ReadUInt128Test()
{
var stream = new MemoryStream(_bytes);
var expected = (UInt128)new BigInteger(_bytes);
UInt128 read = stream.ReadUInt128();
Assert.Equal(expected, read);
}
[Fact]
public void ReadUInt128BigEndianTest()
{
var stream = new MemoryStream(_bytes);
var reversed = _bytes.Reverse().ToArray();
var expected = (UInt128)new BigInteger(reversed);
UInt128 read = stream.ReadUInt128BigEndian();
Assert.Equal(expected, read);
}
#endif
[Fact]
public void ReadTypeExplicitTest()
{
byte[] bytesWithString =
[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x41, 0x42, 0x43, 0x00,
];
var stream = new MemoryStream(bytesWithString);
var expected = new TestStructExplicit
{
FirstValue = TestEnum.RecognizedTestValue,
SecondValue = 0x07060504,
ThirdValue = 0x0504,
FourthValue = 0x0706,
FifthValue = "ABC",
};
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()
{
byte[] bytesWithString =
[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x41, 0x42, 0x43, 0x00,
];
var stream = new MemoryStream(bytesWithString);
var expected = new TestStructSequential
{
FirstValue = TestEnum.RecognizedTestValue,
SecondValue = 0x07060504,
ThirdValue = 0x0908,
FourthValue = 0x0B0A,
FifthValue = "ABC",
};
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);
Assert.Equal(expected.FifthValue, read.FifthValue);
}
[Fact]
public void ReadTypeStringsTest()
{
byte[] structBytes =
[
0x03, 0x41, 0x42, 0x43, // AnsiBStr
0x03, 0x00, 0x41, 0x00, 0x42, 0x00, 0x43, 0x00, // BStr
0x41, 0x42, 0x43, // ByValTStr
0x41, 0x42, 0x43, 0x00, // LPStr
0x41, 0x00, 0x42, 0x00, 0x43, 0x00, 0x00, 0x00, // LPWStr
];
var stream = new MemoryStream(structBytes);
var expected = new TestStructStrings
{
AnsiBStr = "ABC",
BStr = "ABC",
ByValTStr = "ABC",
LPStr = "ABC",
LPWStr = "ABC",
};
var read = stream.ReadType<TestStructStrings>();
Assert.Equal(expected.AnsiBStr, read.AnsiBStr);
Assert.Equal(expected.BStr, read.BStr);
Assert.Equal(expected.ByValTStr, read.ByValTStr);
Assert.Equal(expected.LPStr, read.LPStr);
Assert.Equal(expected.LPWStr, read.LPWStr);
}
[Fact]
public void ReadTypeArraysTest()
{
byte[] structBytes =
[
// Byte Array
0x00, 0x01, 0x02, 0x03,
// Int Array
0x03, 0x02, 0x01, 0x00,
0x04, 0x03, 0x02, 0x01,
0x05, 0x04, 0x03, 0x02,
0x06, 0x05, 0x04, 0x03,
// Struct Array (X, Y)
0xFF, 0x00, 0x00, 0xFF,
0x00, 0xFF, 0xFF, 0x00,
0xAA, 0x55, 0x55, 0xAA,
0x55, 0xAA, 0xAA, 0x55,
// LPArray
0x04, 0x00,
0x00, 0x01, 0x02, 0x03,
];
var stream = new MemoryStream(structBytes);
var expected = new TestStructArrays
{
ByteArray = [0x00, 0x01, 0x02, 0x03],
IntArray = [0x00010203, 0x01020304, 0x02030405, 0x03040506],
StructArray =
[
new TestStructPoint { X = 0x00FF, Y = 0xFF00 },
new TestStructPoint { X = 0xFF00, Y = 0x00FF },
new TestStructPoint { X = 0x55AA, Y = 0xAA55 },
new TestStructPoint { X = 0xAA55, Y = 0x55AA },
],
LPByteArrayLength = 0x0004,
LPByteArray = [0x00, 0x01, 0x02, 0x03],
};
var read = stream.ReadType<TestStructArrays>();
Assert.NotNull(read.ByteArray);
Assert.True(expected.ByteArray.SequenceEqual(read.ByteArray));
Assert.NotNull(read.IntArray);
Assert.True(expected.IntArray.SequenceEqual(read.IntArray));
Assert.NotNull(read.StructArray);
Assert.True(expected.StructArray.SequenceEqual(read.StructArray));
Assert.Equal(expected.LPByteArrayLength, read.LPByteArrayLength);
Assert.NotNull(read.LPByteArray);
Assert.True(expected.LPByteArray.SequenceEqual(read.LPByteArray));
}
[Fact]
public void ReadTypeInheritanceTest()
{
byte[] structBytes1 =
[
0x41, 0x42, 0x43, 0x44, // Signature
0x00, 0xFF, 0x00, 0xFF, // IdentifierType
0xAA, 0x55, 0xAA, 0x55, // FieldA
0x55, 0xAA, 0x55, 0xAA, // FieldB
];
var stream1 = new MemoryStream(structBytes1);
var expected1 = new TestStructInheritanceChild1
{
Signature = [0x41, 0x42, 0x43, 0x44],
IdentifierType = 0xFF00FF00,
FieldA = 0x55AA55AA,
FieldB = 0xAA55AA55,
};
var read1 = stream1.ReadType<TestStructInheritanceChild1>();
Assert.NotNull(read1?.Signature);
Assert.Equal(expected1.Signature, read1.Signature);
Assert.Equal(expected1.IdentifierType, read1.IdentifierType);
Assert.Equal(expected1.FieldA, read1.FieldA);
Assert.Equal(expected1.FieldB, read1.FieldB);
byte[] structBytes2 =
[
0x41, 0x42, 0x43, 0x44, // Signature
0x00, 0xFF, 0x00, 0xFF, // IdentifierType
0xAA, 0x55, // FieldA
0x55, 0xAA, // FieldB
];
var stream2 = new MemoryStream(structBytes2);
var expected2 = new TestStructInheritanceChild2
{
Signature = [0x41, 0x42, 0x43, 0x44],
IdentifierType = 0xFF00FF00,
FieldA = 0x55AA,
FieldB = 0xAA55,
};
var read2 = stream2.ReadType<TestStructInheritanceChild2>();
Assert.NotNull(read2?.Signature);
Assert.Equal(expected2.Signature, read2.Signature);
Assert.Equal(expected2.IdentifierType, read2.IdentifierType);
Assert.Equal(expected2.FieldA, read2.FieldA);
Assert.Equal(expected2.FieldB, read2.FieldB);
}
}
}

View File

@@ -1,243 +0,0 @@
using System;
using System.IO;
using System.Linq;
using SabreTools.IO.Extensions;
using Xunit;
namespace SabreTools.IO.Test
{
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 ReadGuidBigEndian()
{
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
// TODO: Add string reading tests
}
}

View File

@@ -0,0 +1,454 @@
using System;
using System.IO;
using System.Linq;
#if NET7_0_OR_GREATER
using System.Numerics;
#endif
using SabreTools.IO.Extensions;
using Xunit;
namespace SabreTools.IO.Test.Extensions
{
// TODO: Add string writing tests
public class StreamExtensionsWriteTests
{
/// <summary>
/// Test pattern from 0x00-0x0F
/// </summary>
private static readonly byte[] _bytes =
[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
];
/// <summary>
/// Represents the decimal value 0.0123456789
/// </summary>
private static readonly byte[] _decimalBytes =
[
0x15, 0xCD, 0x5B, 0x07, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00,
];
[Fact]
public void WriteByteValueTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = _bytes.Take(1).ToArray();
bool write = stream.Write((byte)0x00);
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteBytesTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = _bytes.Take(4).ToArray();
bool write = StreamWriterExtensions.Write(stream, [0x00, 0x01, 0x02, 0x03]);
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteSByteTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = _bytes.Take(1).ToArray();
bool write = stream.Write((sbyte)0x00);
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteCharTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = _bytes.Take(1).ToArray();
bool write = stream.Write('\0');
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteInt16Test()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = _bytes.Take(2).ToArray();
bool write = stream.Write((short)0x0100);
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteInt16BigEndianTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = _bytes.Take(2).ToArray();
bool write = stream.WriteBigEndian((short)0x0001);
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteUInt16Test()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = _bytes.Take(2).ToArray();
bool write = stream.Write((ushort)0x0100);
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteUInt16BigEndianTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = _bytes.Take(2).ToArray();
bool write = stream.WriteBigEndian((ushort)0x0001);
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
#if NET6_0_OR_GREATER
[Fact]
public void WriteHalfTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = _bytes.Take(2).ToArray();
bool write = stream.Write(BitConverter.Int16BitsToHalf(0x0100));
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteHalfBigEndianTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = _bytes.Take(2).ToArray();
bool write = stream.WriteBigEndian(BitConverter.Int16BitsToHalf(0x0001));
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
#endif
[Fact]
public void WriteInt24Test()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = _bytes.Take(3).ToArray();
bool write = stream.WriteAsInt24(0x020100);
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteInt24BigEndianTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = _bytes.Take(3).ToArray();
bool write = stream.WriteAsInt24BigEndian(0x000102);
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteUInt24Test()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = _bytes.Take(3).ToArray();
bool write = stream.WriteAsUInt24(0x020100);
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteUInt24BigEndianTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = _bytes.Take(3).ToArray();
bool write = stream.WriteAsUInt24BigEndian(0x000102);
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteInt32Test()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = _bytes.Take(4).ToArray();
bool write = stream.Write(0x03020100);
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteInt32BigEndianTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = _bytes.Take(4).ToArray();
bool write = stream.WriteBigEndian(0x00010203);
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteUInt32Test()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = _bytes.Take(4).ToArray();
bool write = stream.Write((uint)0x03020100);
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteUInt32BigEndianTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = _bytes.Take(4).ToArray();
bool write = stream.WriteBigEndian((uint)0x00010203);
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteSingleTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = _bytes.Take(4).ToArray();
bool write = stream.Write(BitConverter.Int32BitsToSingle(0x03020100));
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteSingleBigEndianTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = _bytes.Take(4).ToArray();
bool write = stream.WriteBigEndian(BitConverter.Int32BitsToSingle(0x00010203));
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteInt48Test()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = _bytes.Take(6).ToArray();
bool write = stream.WriteAsInt48(0x050403020100);
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteInt48BigEndianTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = _bytes.Take(6).ToArray();
bool write = stream.WriteAsInt48BigEndian(0x000102030405);
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteUInt48Test()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = _bytes.Take(6).ToArray();
bool write = stream.WriteAsUInt48(0x050403020100);
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteUInt48BigEndianTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = _bytes.Take(6).ToArray();
bool write = stream.WriteAsUInt48BigEndian(0x000102030405);
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteInt64Test()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = _bytes.Take(8).ToArray();
bool write = stream.Write(0x0706050403020100);
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteInt64BigEndianTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = _bytes.Take(8).ToArray();
bool write = stream.WriteBigEndian(0x0001020304050607);
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteUInt64Test()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = _bytes.Take(8).ToArray();
bool write = stream.Write((ulong)0x0706050403020100);
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteUInt64BigEndianTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = _bytes.Take(8).ToArray();
bool write = stream.WriteBigEndian((ulong)0x0001020304050607);
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteDecimalTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = _decimalBytes.Take(16).ToArray();
bool write = stream.Write(0.0123456789M);
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteDecimalBigEndianTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = _decimalBytes.Take(16).Reverse().ToArray();
bool write = stream.WriteBigEndian(0.0123456789M);
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteGuidTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = _bytes.Take(16).ToArray();
bool write = stream.Write(new Guid(_bytes));
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteGuidBigEndianTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = _bytes.Take(16).ToArray();
bool write = stream.WriteBigEndian(new Guid(_bytes.Reverse().ToArray()));
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
#if NET7_0_OR_GREATER
[Fact]
public void WriteInt128Test()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = _bytes.Take(16).ToArray();
bool write = stream.Write((Int128)new BigInteger(_bytes));
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteInt128BigEndianTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = _bytes.Take(16).ToArray();
bool write = stream.WriteBigEndian((Int128)new BigInteger(_bytes.Reverse().ToArray()));
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteUInt128Test()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = _bytes.Take(16).ToArray();
bool write = stream.Write((UInt128)new BigInteger(_bytes));
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteUInt128BigEndianTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = _bytes.Take(16).ToArray();
bool write = stream.WriteBigEndian((UInt128)new BigInteger(_bytes.Reverse().ToArray()));
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
#endif
[Fact]
public void WriteTypeExplicitTest()
{
byte[] bytesWithString =
[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x41, 0x42, 0x43, 0x00,
];
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var obj = new TestStructExplicit
{
FirstValue = TestEnum.RecognizedTestValue,
SecondValue = 0x07060504,
FifthValue = "ABC",
};
byte[] expected = bytesWithString.Take(12).ToArray();
bool write = stream.WriteType(obj);
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteTypeSequentialTest()
{
byte[] bytesWithString =
[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x41, 0x42, 0x43, 0x00,
];
var stream = new MemoryStream(new byte[24], 0, 24, true, true);
var obj = new TestStructSequential
{
FirstValue = TestEnum.RecognizedTestValue,
SecondValue = 0x07060504,
ThirdValue = 0x0908,
FourthValue = 0x0B0A,
FifthValue = "ABC",
};
byte[] expected = bytesWithString.Take(16).ToArray();
bool write = stream.WriteType(obj);
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
/// <summary>
/// Validate that a set of actual bytes matches the expected bytes
/// </summary>
private static void ValidateBytes(byte[] expected, byte[] actual)
{
for (int i = 0; i < expected.Length; i++)
{
Assert.Equal(expected[i], actual[i]);
}
}
}
}

View File

@@ -0,0 +1,9 @@
namespace SabreTools.IO.Test.Extensions
{
internal enum TestEnum : uint
{
None = 0x00000000,
RecognizedTestValue = 0x03020100,
UpperBoundaryValue = 0xFFFFFFFF,
}
}

View File

@@ -0,0 +1,53 @@
using System.Runtime.InteropServices;
namespace SabreTools.IO.Test.Extensions
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
internal struct TestStructArrays
{
/// <summary>
/// 4 entry byte array
/// </summary>
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public byte[]? ByteArray;
/// <summary>
/// 4 entry int array
/// </summary>
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public int[]? IntArray;
/// <summary>
/// 4 entry struct array
/// </summary>
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public TestStructPoint[]? StructArray;
/// <summary>
/// Length of <see cref="LPByteArray"/>
/// </summary>
public ushort LPByteArrayLength;
/// <summary>
/// 4 entry byte array whose length is defined by <see cref="LPByteArrayLength"/>
/// </summary>
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)]
public byte[]? LPByteArray;
// /// <summary>
// /// 4 entry nested byte array
// /// </summary>
// /// <remarks>This will likely fail</remarks>
// [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
// public byte[][]? NestedByteArray;
}
/// <summary>
/// Struct for nested tests
/// </summary>
internal struct TestStructPoint
{
public ushort X;
public ushort Y;
}
}

View File

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

View File

@@ -0,0 +1,29 @@
using System.Runtime.InteropServices;
namespace SabreTools.IO.Test.Extensions
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
internal class TestStructInheritanceParent
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public byte[]? Signature;
public uint IdentifierType;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
internal class TestStructInheritanceChild1 : TestStructInheritanceParent
{
public uint FieldA;
public uint FieldB;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
internal class TestStructInheritanceChild2 : TestStructInheritanceParent
{
public ushort FieldA;
public ushort FieldB;
}
}

View File

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

View File

@@ -0,0 +1,38 @@
using System.Runtime.InteropServices;
namespace SabreTools.IO.Test.Extensions
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
internal struct TestStructStrings
{
/// <summary>
/// ASCII-encoded, byte-length-prefixed string
/// </summary>
[MarshalAs(UnmanagedType.AnsiBStr)]
public string? AnsiBStr;
/// <summary>
/// Unicode-encoded, WORD-length-prefixed string
/// </summary>
[MarshalAs(UnmanagedType.BStr)]
public string? BStr;
/// <summary>
/// Fixed length string
/// </summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 3)]
public string? ByValTStr;
/// <summary>
/// ASCII-encoded, null-terminated string
/// </summary>
[MarshalAs(UnmanagedType.LPStr)]
public string? LPStr;
/// <summary>
/// Unicode-encoded, null-terminated string
/// </summary>
[MarshalAs(UnmanagedType.LPWStr)]
public string? LPWStr;
}
}

View File

@@ -6,6 +6,7 @@
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningsNotAsErrors>CS0618</WarningsNotAsErrors>
</PropertyGroup>
<ItemGroup>

View File

@@ -13,7 +13,7 @@ namespace SabreTools.IO.Test.Streams
Assert.Equal(0, stream.Length);
Assert.Equal(0, stream.Position);
stream = new ReadOnlyBitStream(new MemoryStream(new byte[16]));
stream = new ReadOnlyBitStream(new MemoryStream(new byte[16], 0, 16, true, true));
Assert.Equal(16, stream.Length);
Assert.Equal(0, stream.Position);
}

View File

@@ -1,11 +1,20 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
#if NET7_0_OR_GREATER
using System.Numerics;
#endif
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
namespace SabreTools.IO.Extensions
{
/// <summary>
/// Big endian reading overloads for BinaryReader
/// Extensions for BinaryReader
/// </summary>
/// TODO: Handle proper negative values for Int24 and Int48
public static class BinaryReaderExtensions
{
/// <inheritdoc cref="BinaryReader.Read(byte[], int, int)"/>
@@ -30,104 +39,692 @@ namespace SabreTools.IO.Extensions
/// <remarks>Reads in big-endian format</remarks>
public static byte[] ReadBytesBigEndian(this BinaryReader reader, int count)
{
byte[] retval = reader.ReadBytes(count);
Array.Reverse(retval);
return retval;
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[] retval = reader.ReadChars(count);
Array.Reverse(retval);
return retval;
}
/// <inheritdoc cref="BinaryReader.ReadDecimal"/>
/// <remarks>Reads in big-endian format</remarks>
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]);
}
/// <inheritdoc cref="BinaryReader.ReadDouble"/>
/// <remarks>Reads in big-endian format</remarks>
public static double ReadDoubleBigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(8);
Array.Reverse(retval);
return BitConverter.ToDouble(retval, 0);
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[] retval = reader.ReadBytes(2);
Array.Reverse(retval);
return BitConverter.ToInt16(retval, 0);
}
/// <inheritdoc cref="BinaryReader.ReadInt32"/>
/// <remarks>Reads in big-endian format</remarks>
public static int ReadInt32BigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(4);
Array.Reverse(retval);
return BitConverter.ToInt32(retval, 0);
}
/// <inheritdoc cref="BinaryReader.ReadInt64"/>
/// <remarks>Reads in big-endian format</remarks>
public static long ReadInt64BigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(8);
Array.Reverse(retval);
return BitConverter.ToInt64(retval, 0);
}
/// <inheritdoc cref="BinaryReader.ReadSingle"/>
/// <remarks>Reads in big-endian format</remarks>
public static float ReadSingleBigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(4);
Array.Reverse(retval);
return BitConverter.ToSingle(retval, 0);
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[] retval = reader.ReadBytes(2);
Array.Reverse(retval);
return BitConverter.ToUInt16(retval, 0);
byte[] buffer = reader.ReadBytes(2);
Array.Reverse(buffer);
return BitConverter.ToUInt16(buffer, 0);
}
// Half was introduced in net5.0 but doesn't have a BitConverter implementation until net6.0
#if NET6_0_OR_GREATER
/// <inheritdoc cref="BinaryReader.ReadHalf"/>
/// <remarks>Reads in big-endian format</remarks>
public static Half ReadHalfBigEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(2);
Array.Reverse(buffer);
return BitConverter.ToHalf(buffer, 0);
}
#endif
/// <summary>
/// Read an Int24 encoded as an Int32
/// </summary>
public static int ReadInt24(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(3);
byte[] padded = new byte[4];
Array.Copy(buffer, padded, 3);
return BitConverter.ToInt32(padded, 0);
}
/// <summary>
/// Read an Int24 encoded as an Int32
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static int ReadInt24BigEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(3);
Array.Reverse(buffer);
byte[] padded = new byte[4];
Array.Copy(buffer, padded, 3);
return BitConverter.ToInt32(padded, 0);
}
/// <summary>
/// Read a UInt24 encoded as a UInt32
/// </summary>
public static uint ReadUInt24(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(3);
byte[] padded = new byte[4];
Array.Copy(buffer, padded, 3);
return BitConverter.ToUInt32(padded, 0);
}
/// <summary>
/// Read a UInt24 encoded as a UInt32
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static uint ReadUInt24BigEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(3);
Array.Reverse(buffer);
byte[] padded = new byte[4];
Array.Copy(buffer, padded, 3);
return BitConverter.ToUInt32(padded, 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[] retval = reader.ReadBytes(4);
Array.Reverse(retval);
return BitConverter.ToUInt32(retval, 0);
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);
}
/// <summary>
/// Read an Int48 encoded as an Int64
/// </summary>
public static long ReadInt48(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(6);
byte[] padded = new byte[8];
Array.Copy(buffer, padded, 6);
return BitConverter.ToInt64(padded, 0);
}
/// <summary>
/// Read an Int48 encoded as an Int64
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static long ReadInt48BigEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(6);
Array.Reverse(buffer);
byte[] padded = new byte[8];
Array.Copy(buffer, padded, 6);
return BitConverter.ToInt64(padded, 0);
}
/// <summary>
/// Read a UInt48 encoded as a UInt64
/// </summary>
public static ulong ReadUInt48(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(6);
byte[] padded = new byte[8];
Array.Copy(buffer, padded, 6);
return BitConverter.ToUInt64(padded, 0);
}
/// <summary>
/// Read a UInt48 encoded as a UInt64
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static ulong ReadUInt48BigEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(6);
Array.Reverse(buffer);
byte[] padded = new byte[8];
Array.Copy(buffer, padded, 6);
return BitConverter.ToUInt64(padded, 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[] retval = reader.ReadBytes(8);
Array.Reverse(retval);
return BitConverter.ToUInt64(retval, 0);
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 lo = BitConverter.ToInt32(buffer, 0);
int mid = BitConverter.ToInt32(buffer, 4);
int hi = BitConverter.ToInt32(buffer, 8);
int flags = BitConverter.ToInt32(buffer, 12);
return new decimal([lo, mid, hi, flags]);
}
/// <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 (Int128)new BigInteger(buffer);
}
/// <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 (Int128)new BigInteger(buffer);
}
/// <summary>
/// Read a UInt128 from the underlying stream
/// </summary>
public static UInt128 ReadUInt128(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(16);
return (UInt128)new BigInteger(buffer);
}
/// <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 (UInt128)new BigInteger(buffer);
}
#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.UTF8))
return reader.ReadNullTerminatedUTF8String();
else if (encoding.Equals(Encoding.Unicode))
return reader.ReadNullTerminatedUnicodeString();
else if (encoding.Equals(Encoding.UTF32))
return reader.ReadNullTerminatedUTF32String();
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;
byte[] buffer = ReadUntilNull1Byte(reader);
return Encoding.ASCII.GetString(buffer);
}
/// <summary>
/// Read a null-terminated UTF-8 string from the underlying stream
/// </summary>
public static string? ReadNullTerminatedUTF8String(this BinaryReader reader)
{
if (reader.BaseStream.Position >= reader.BaseStream.Length)
return null;
byte[] buffer = ReadUntilNull1Byte(reader);
return Encoding.ASCII.GetString(buffer);
}
/// <summary>
/// Read a null-terminated UTF-16 (Unicode) string from the underlying stream
/// </summary>
public static string? ReadNullTerminatedUnicodeString(this BinaryReader reader)
{
if (reader.BaseStream.Position >= reader.BaseStream.Length)
return null;
byte[] buffer = ReadUntilNull2Byte(reader);
return Encoding.Unicode.GetString(buffer);
}
/// <summary>
/// Read a null-terminated UTF-32 string from the underlying stream
/// </summary>
public static string? ReadNullTerminatedUTF32String(this BinaryReader reader)
{
if (reader.BaseStream.Position >= reader.BaseStream.Length)
return null;
byte[] buffer = ReadUntilNull4Byte(reader);
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 * 2) >= reader.BaseStream.Length)
return null;
byte[] buffer = reader.ReadBytes(size * 2);
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 underlying 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 underlying 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>
/// <remarks>
/// This method is different than standard marshalling in a few notable ways:
/// - Strings are read by value, not by reference
/// - Complex objects are read by value, not by reference
/// - Enumeration values are read by the underlying value type
/// - Arrays of the above are handled sequentially as above
/// - Inherited fields from parents are deserialized BEFORE fields in the child
/// </remarks>
public static T? ReadType<T>(this BinaryReader reader)
=> (T?)reader.ReadType(typeof(T));
/// <summary>
/// Read a <paramref name="type"/> from the underlying stream
/// </summary>
/// <remarks>
/// This method is different than standard marshalling in a few notable ways:
/// - Strings are read by value, not by reference
/// - Complex objects are read by value, not by reference
/// - Enumeration values are read by the underlying value type
/// - Arrays of the above are handled sequentially as above
/// - Inherited fields from parents are deserialized BEFORE fields in the child
/// </remarks>
public static object? ReadType(this BinaryReader reader, Type type)
{
// Handle special struct cases
if (type == typeof(Guid))
return reader.ReadGuid();
#if NET6_0_OR_GREATER
else if (type == typeof(Half))
return reader.ReadHalf();
#endif
#if NET7_0_OR_GREATER
else if (type == typeof(Int128))
return reader.ReadInt128();
else if (type == typeof(UInt128))
return reader.ReadUInt128();
#endif
if (type.IsClass || (type.IsValueType && !type.IsEnum && !type.IsPrimitive))
return ReadComplexType(reader, type);
else if (type.IsValueType && type.IsEnum)
return ReadNormalType(reader, Enum.GetUnderlyingType(type));
else
return ReadNormalType(reader, type);
}
/// <summary>
/// Read a <paramref name="type"/> from the underlying stream
/// </summary>
private static object? ReadNormalType(BinaryReader reader, Type type)
{
try
{
int typeSize = Marshal.SizeOf(type);
byte[] buffer = reader.ReadBytes(typeSize); ;
var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
var data = Marshal.PtrToStructure(handle.AddrOfPinnedObject(), type);
handle.Free();
return data;
}
catch
{
return null;
}
}
/// <summary>
/// Read a <paramref name="type"/> from the underlying stream
/// </summary>
private static object? ReadComplexType(BinaryReader reader, Type type)
{
try
{
// Try to create an instance of the type
var instance = Activator.CreateInstance(type);
if (instance == null)
return null;
// Get the layout information
var layoutAttr = MarshalHelpers.GetAttribute<StructLayoutAttribute>(type);
LayoutKind layoutKind = MarshalHelpers.DetermineLayoutKind(layoutAttr, type);
Encoding encoding = MarshalHelpers.DetermineEncoding(layoutAttr);
// Cache the current offset
long currentOffset = reader.BaseStream.Position;
// Generate the fields by parent first
var fields = MarshalHelpers.GetFields(type);
// Loop through the fields and set them
foreach (var fi in fields)
{
// If we have an explicit layout, move accordingly
if (layoutKind == LayoutKind.Explicit)
{
var fieldOffset = MarshalHelpers.GetAttribute<FieldOffsetAttribute>(fi);
reader.BaseStream.Seek(currentOffset + fieldOffset?.Value ?? 0, SeekOrigin.Begin);
}
SetField(reader, encoding, fields, instance, fi);
}
return instance;
}
catch
{
return null;
}
}
/// <summary>
/// Set a single field on an object
/// </summary>
private static void SetField(BinaryReader reader, Encoding encoding, FieldInfo[] fields, object instance, FieldInfo fi)
{
if (fi.FieldType.IsAssignableFrom(typeof(string)))
{
var value = ReadStringType(reader, encoding, fi);
fi.SetValue(instance, value);
}
else if (fi.FieldType.IsArray)
{
var value = ReadArrayType(reader, fields, instance, fi);
fi.SetValue(instance, Convert.ChangeType(value, fi.FieldType));
}
else
{
var value = reader.ReadType(fi.FieldType);
fi.SetValue(instance, value);
}
}
/// <summary>
/// Read an array type field for an object
/// </summary>
private static Array ReadArrayType(BinaryReader reader, FieldInfo[] fields, object instance, FieldInfo fi)
{
var marshalAsAttr = MarshalHelpers.GetAttribute<MarshalAsAttribute>(fi);
if (marshalAsAttr == null)
return new object[0];
// Get the number of elements expected
int elementCount = MarshalHelpers.GetArrayElementCount(marshalAsAttr, fields, instance);
if (elementCount < 0)
return new object[0];
// Get the item type for the array
Type elementType = fi.FieldType.GetElementType() ?? typeof(object);
// Loop through and build the array
Array arr = Array.CreateInstance(elementType, elementCount);
for (int i = 0; i < elementCount; i++)
{
var value = ReadType(reader, elementType);
arr.SetValue(value, i);
}
// Return the built array
return arr;
}
/// <summary>
/// Read a string type field for an object
/// </summary>
private static string? ReadStringType(BinaryReader reader, Encoding encoding, FieldInfo? fi)
{
var marshalAsAttr = fi?.GetCustomAttributes(typeof(MarshalAsAttribute), true)?.FirstOrDefault() as MarshalAsAttribute;
switch (marshalAsAttr?.Value)
{
case UnmanagedType.AnsiBStr:
return reader.ReadPrefixedAnsiString();
case UnmanagedType.BStr:
case UnmanagedType.TBStr: // Technically distinct; returns char[] instead
return reader.ReadPrefixedUnicodeString();
case UnmanagedType.ByValTStr:
int byvalLength = marshalAsAttr!.SizeConst;
byte[] byvalBytes = reader.ReadBytes(byvalLength);
return encoding.GetString(byvalBytes);
case UnmanagedType.LPStr:
case UnmanagedType.LPTStr: // Technically distinct; possibly not null-terminated
case null:
return reader.ReadNullTerminatedAnsiString();
#if NET472_OR_GREATER || NETCOREAPP
case UnmanagedType.LPUTF8Str:
return reader.ReadNullTerminatedUTF8String();
#endif
case UnmanagedType.LPWStr:
return reader.ReadNullTerminatedUnicodeString();
// No other string types are recognized
default:
return null;
}
}
/// <summary>
/// Read bytes until a 1-byte null terminator is found
/// </summary>
private static byte[] ReadUntilNull1Byte(BinaryReader reader)
{
var bytes = new List<byte>();
while (reader.BaseStream.Position < reader.BaseStream.Length)
{
byte next = reader.ReadByte();
if (next == 0x00)
break;
bytes.Add(next);
}
return [.. bytes];
}
/// <summary>
/// Read bytes until a 2-byte null terminator is found
/// </summary>
private static byte[] ReadUntilNull2Byte(BinaryReader reader)
{
var bytes = new List<byte>();
while (reader.BaseStream.Position < reader.BaseStream.Length)
{
ushort next = reader.ReadUInt16();
if (next == 0x0000)
break;
bytes.AddRange(BitConverter.GetBytes(next));
}
return [.. bytes];
}
/// <summary>
/// Read bytes until a 4-byte null terminator is found
/// </summary>
private static byte[] ReadUntilNull4Byte(BinaryReader reader)
{
var bytes = new List<byte>();
while (reader.BaseStream.Position < reader.BaseStream.Length)
{
uint next = reader.ReadUInt32();
if (next == 0x00000000)
break;
bytes.AddRange(BitConverter.GetBytes(next));
}
return [.. bytes];
}
}
}

View File

@@ -0,0 +1,669 @@
using System;
using System.IO;
#if NET7_0_OR_GREATER
using System.Numerics;
#endif
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
namespace SabreTools.IO.Extensions
{
/// <summary>
/// Extensions for BinaryWriter
/// </summary>
/// <remarks>TODO: Add WriteDecimal methods</remarks>
/// TODO: Handle proper negative values for Int24 and Int48
public static class BinaryWriterExtensions
{
/// <inheritdoc cref="BinaryWriter.Write(byte[])"/>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this BinaryWriter writer, byte[] value)
{
Array.Reverse(value);
return WriteFromBuffer(writer, value);
}
/// <inheritdoc cref="BinaryWriter.Write(char)"/>
public static bool Write(this BinaryWriter writer, char value, Encoding encoding)
{
byte[] buffer = encoding.GetBytes($"{value}");
return WriteFromBuffer(writer, buffer);
}
/// <inheritdoc cref="BinaryWriter.Write(short)"/>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this BinaryWriter writer, short value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
return WriteFromBuffer(writer, buffer);
}
/// <inheritdoc cref="BinaryWriter.Write(ushort)"/>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this BinaryWriter writer, ushort value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
return WriteFromBuffer(writer, buffer);
}
// Half was introduced in net5.0 but doesn't have a BitConverter implementation until net6.0
#if NET6_0_OR_GREATER
/// <inheritdoc cref="BinaryWriter.Write(Half)"/>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this BinaryWriter writer, Half value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
return WriteFromBuffer(writer, buffer);
}
#endif
/// <summary>
/// Write an Int32 as an Int24 to the underlying stream
/// </summary>
/// <remarks>Throws away top byte</remarks>
public static bool WriteAsInt24(this BinaryWriter writer, int value)
{
byte[] buffer = BitConverter.GetBytes(value);
byte[] reduced = new byte[3];
Array.Copy(buffer, reduced, 3);
return WriteFromBuffer(writer, reduced);
}
/// <summary>
/// Write an Int32 as an Int24 to the underlying stream
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
/// <remarks>Throws away top byte</remarks>
public static bool WriteAsInt24BigEndian(this BinaryWriter writer, int value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
byte[] reduced = new byte[3];
Array.Copy(buffer, 1, reduced, 0, 3);
return WriteFromBuffer(writer, reduced);
}
/// <summary>
/// Write a UInt32 as a UInt24 to the underlying stream
/// </summary>
/// <remarks>Throws away top byte</remarks>
public static bool WriteAsUInt24(this BinaryWriter writer, uint value)
{
byte[] buffer = BitConverter.GetBytes(value);
byte[] reduced = new byte[3];
Array.Copy(buffer, reduced, 3);
return WriteFromBuffer(writer, reduced);
}
/// <summary>
/// Write a UInt32 as a UInt24 to the underlying stream
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
/// <remarks>Throws away top byte</remarks>
public static bool WriteAsUInt24BigEndian(this BinaryWriter writer, uint value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
byte[] reduced = new byte[3];
Array.Copy(buffer, 1, reduced, 0, 3);
return WriteFromBuffer(writer, reduced);
}
/// <inheritdoc cref="BinaryWriter.Write(int)"/>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this BinaryWriter writer, int value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
return WriteFromBuffer(writer, buffer);
}
/// <inheritdoc cref="BinaryWriter.Write(uint)"/>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this BinaryWriter writer, uint value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
return WriteFromBuffer(writer, buffer);
}
/// <inheritdoc cref="BinaryWriter.Write(float)"/>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this BinaryWriter writer, float value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
return WriteFromBuffer(writer, buffer);
}
/// <summary>
/// Write an Int64 as an Int48 to the underlying stream
/// </summary>
/// <remarks>Throws away top 2 bytes</remarks>
public static bool WriteAsInt48(this BinaryWriter writer, long value)
{
byte[] buffer = BitConverter.GetBytes(value);
byte[] reduced = new byte[6];
Array.Copy(buffer, reduced, 6);
return WriteFromBuffer(writer, reduced);
}
/// <summary>
/// Write an Int64 as an Int48 to the underlying stream
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
/// <remarks>Throws away top 2 bytes</remarks>
public static bool WriteAsInt48BigEndian(this BinaryWriter writer, long value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
byte[] reduced = new byte[6];
Array.Copy(buffer, 2, reduced, 0, 6);
return WriteFromBuffer(writer, reduced);
}
/// <summary>
/// Write a UInt64 as a UInt48 to the underlying stream
/// </summary>
/// <remarks>Throws away top 2 bytes</remarks>
public static bool WriteAsUInt48(this BinaryWriter writer, ulong value)
{
byte[] buffer = BitConverter.GetBytes(value);
byte[] reduced = new byte[6];
Array.Copy(buffer, reduced, 6);
return WriteFromBuffer(writer, reduced);
}
/// <summary>
/// Write a UInt64 as a UInt48 to the underlying stream
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
/// <remarks>Throws away top 2 bytes</remarks>
public static bool WriteAsUInt48BigEndian(this BinaryWriter writer, ulong value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
byte[] reduced = new byte[6];
Array.Copy(buffer, 2, reduced, 0, 6);
return WriteFromBuffer(writer, reduced);
}
/// <inheritdoc cref="BinaryWriter.Write(long)"/>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this BinaryWriter writer, long value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
return WriteFromBuffer(writer, buffer);
}
/// <inheritdoc cref="BinaryWriter.Write(ulong)"/>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this BinaryWriter writer, ulong value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
return WriteFromBuffer(writer, buffer);
}
/// <inheritdoc cref="BinaryWriter.Write(double)"/>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this BinaryWriter writer, double value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
return WriteFromBuffer(writer, buffer);
}
/// <inheritdoc cref="BinaryWriter.Write(decimal)"/>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this BinaryWriter writer, decimal value)
{
int[] bits = decimal.GetBits(value);
byte[] lo = BitConverter.GetBytes(bits[0]);
byte[] mid = BitConverter.GetBytes(bits[1]);
byte[] hi = BitConverter.GetBytes(bits[2]);
byte[] flags = BitConverter.GetBytes(bits[3]);
byte[] buffer = new byte[16];
Array.Copy(lo, 0, buffer, 0, 4);
Array.Copy(mid, 0, buffer, 4, 4);
Array.Copy(hi, 0, buffer, 8, 4);
Array.Copy(flags, 0, buffer, 12, 4);
Array.Reverse(buffer);
return WriteFromBuffer(writer, buffer);
}
/// <summary>
/// Write a Guid
/// </summary>
public static bool Write(this BinaryWriter writer, Guid value)
{
byte[] buffer = value.ToByteArray();
return WriteFromBuffer(writer, buffer);
}
/// <summary>
/// Write a Guid
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this BinaryWriter writer, Guid value)
{
byte[] buffer = value.ToByteArray();
Array.Reverse(buffer);
return WriteFromBuffer(writer, buffer);
}
#if NET7_0_OR_GREATER
/// <summary>
/// Write an Int128
/// </summary>
public static bool Write(this BinaryWriter writer, Int128 value)
{
byte[] buffer = ((BigInteger)value).ToByteArray();
byte[] padded = new byte[16];
Array.Copy(buffer, 0, padded, 16 - buffer.Length, buffer.Length);
return WriteFromBuffer(writer, padded);
}
/// <summary>
/// Write an Int128
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this BinaryWriter writer, Int128 value)
{
byte[] buffer = ((BigInteger)value).ToByteArray();
Array.Reverse(buffer);
byte[] padded = new byte[16];
Array.Copy(buffer, 0, padded, 16 - buffer.Length, buffer.Length);
return WriteFromBuffer(writer, padded);
}
/// <summary>
/// Write a UInt128
/// </summary>
public static bool Write(this BinaryWriter writer, UInt128 value)
{
byte[] buffer = ((BigInteger)value).ToByteArray();
byte[] padded = new byte[16];
Array.Copy(buffer, 0, padded, 16 - buffer.Length, buffer.Length);
return WriteFromBuffer(writer, padded);
}
/// <summary>
/// Write a UInt128
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this BinaryWriter writer, UInt128 value)
{
byte[] buffer = ((BigInteger)value).ToByteArray();
Array.Reverse(buffer);
byte[] padded = new byte[16];
Array.Copy(buffer, 0, padded, 16 - buffer.Length, buffer.Length);
return WriteFromBuffer(writer, padded);
}
#endif
/// <summary>
/// Write a null-terminated string to the underlying stream
/// </summary>
public static bool WriteNullTerminatedString(this BinaryWriter writer, string? value, Encoding encoding)
{
// If the value is null
if (value == null)
return false;
// Add the null terminator and write
value += "\0";
byte[] buffer = encoding.GetBytes(value);
return WriteFromBuffer(writer, buffer);
}
/// <summary>
/// Write a null-terminated ASCII string to the underlying stream
/// </summary>
public static bool WriteNullTerminatedAnsiString(this BinaryWriter writer, string? value)
=> writer.WriteNullTerminatedString(value, Encoding.ASCII);
/// <summary>
/// Write a null-terminated UTF-8 string to the underlying stream
/// </summary>
public static bool WriteNullTerminatedUTF8String(this BinaryWriter writer, string? value)
=> writer.WriteNullTerminatedString(value, Encoding.UTF8);
/// <summary>
/// Write a null-terminated UTF-16 (Unicode) string to the underlying stream
/// </summary>
public static bool WriteNullTerminatedUnicodeString(this BinaryWriter writer, string? value)
=> writer.WriteNullTerminatedString(value, Encoding.Unicode);
/// <summary>
/// Write a null-terminated UTF-32 string to the underlying stream
/// </summary>
public static bool WriteNullTerminatedUTF32String(this BinaryWriter writer, string? value)
=> writer.WriteNullTerminatedString(value, Encoding.UTF32);
/// <summary>
/// Write a byte-prefixed ASCII string to the underlying stream
/// </summary>
public static bool WritePrefixedAnsiString(this BinaryWriter writer, string? value)
{
// If the value is null
if (value == null)
return false;
// Get the buffer
byte[] buffer = Encoding.ASCII.GetBytes(value);
// Write the length as a byte
writer.Write((byte)buffer.Length);
// Write the buffer
return WriteFromBuffer(writer, buffer);
}
/// <summary>
/// Write a ushort-prefixed Unicode string to the underlying stream
/// </summary>
public static bool WritePrefixedUnicodeString(this BinaryWriter writer, string? value)
{
// If the value is null
if (value == null)
return false;
// Get the buffer
byte[] buffer = Encoding.Unicode.GetBytes(value);
// Write the length as a ushort
writer.Write((ushort)buffer.Length);
// Write the buffer
return WriteFromBuffer(writer, buffer);
}
/// <summary>
/// Write a string that is terminated by a newline but contains a quoted portion that
/// may also contain a newline to the underlying stream
/// </summary>
public static bool WriteQuotedString(this BinaryWriter writer, string? value)
=> writer.WriteQuotedString(value, Encoding.UTF8);
/// <summary>
/// Write a string that is terminated by a newline but contains a quoted portion that
/// may also contain a newline to the underlying stream
/// </summary>
public static bool WriteQuotedString(this BinaryWriter writer, string? value, Encoding encoding)
{
// If the value is null
if (value == null)
return false;
// Write without the null terminator
byte[] buffer = encoding.GetBytes(value);
return WriteFromBuffer(writer, buffer);
}
/// <summary>
/// Write a <typeparamref name="T"/> to the underlying stream
/// </summary>
/// <remarks>
/// This method is different than standard marshalling in a few notable ways:
/// - Strings are written by value, not by reference
/// - Complex objects are written by value, not by reference
/// - Enumeration values are written by the underlying value type
/// - Arrays of the above are handled sequentially as above
/// - Inherited fields from parents are serialized BEFORE fields in the child
/// </remarks>
public static bool WriteType<T>(this BinaryWriter writer, T? value)
=> writer.WriteType(value, typeof(T));
/// <summary>
/// Write a <typeparamref name="T"/> to the underlying stream
/// </summary>
/// <remarks>
/// This method is different than standard marshalling in a few notable ways:
/// - Strings are written by value, not by reference
/// - Complex objects are written by value, not by reference
/// - Enumeration values are written by the underlying value type
/// - Arrays of the above are handled sequentially as above
/// - Inherited fields from parents are serialized BEFORE fields in the child
/// </remarks>
public static bool WriteType(this BinaryWriter writer, object? value, Type type)
{
// Null values cannot be written
if (value == null)
return true;
// Handle special struct cases
if (type == typeof(Guid))
return writer.Write((Guid)value);
#if NET6_0_OR_GREATER
else if (type == typeof(Half))
{
writer.Write((Half)value);
return true;
}
#endif
#if NET7_0_OR_GREATER
else if (type == typeof(Int128))
return writer.Write((Int128)value);
else if (type == typeof(UInt128))
return writer.Write((UInt128)value);
#endif
if (type.IsClass || (type.IsValueType && !type.IsEnum && !type.IsPrimitive))
return WriteComplexType(writer, value, type);
else if (type.IsValueType && type.IsEnum)
return WriteNormalType(writer, value, Enum.GetUnderlyingType(type));
else
return WriteNormalType(writer, value, type);
}
/// <summary>
/// Read a <paramref name="type"/> from the stream
/// </summary>
private static bool WriteNormalType(BinaryWriter writer, object? value, Type type)
{
try
{
// Null values cannot be written
if (value == null)
return true;
int typeSize = Marshal.SizeOf(type);
if (value.GetType() != type)
value = Convert.ChangeType(value, type);
var buffer = new byte[typeSize];
var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
Marshal.StructureToPtr(value, handle.AddrOfPinnedObject(), false);
handle.Free();
return WriteFromBuffer(writer, buffer);
}
catch
{
return false;
}
}
/// <summary>
/// Read a <paramref name="type"/> from the stream
/// </summary>
private static bool WriteComplexType(BinaryWriter writer, object? value, Type type)
{
try
{
// Null values cannot be written
if (value == null)
return true;
// Get the layout information
var layoutAttr = MarshalHelpers.GetAttribute<StructLayoutAttribute>(type);
LayoutKind layoutKind = MarshalHelpers.DetermineLayoutKind(layoutAttr, type);
Encoding encoding = MarshalHelpers.DetermineEncoding(layoutAttr);
// Cache the current offset
long currentOffset = writer.BaseStream.Position;
// Generate the fields by parent first
var fields = MarshalHelpers.GetFields(type);
// Loop through the fields and set them
foreach (var fi in fields)
{
// If we have an explicit layout, move accordingly
if (layoutKind == LayoutKind.Explicit)
{
var fieldOffset = MarshalHelpers.GetAttribute<FieldOffsetAttribute>(fi);
writer.BaseStream.Seek(currentOffset + fieldOffset?.Value ?? 0, SeekOrigin.Begin);
}
if (!GetField(writer, encoding, fields, value, fi))
return false;
}
return true;
}
catch
{
return false;
}
}
/// <summary>
/// Write a single field from an object
/// </summary>
private static bool GetField(BinaryWriter writer, Encoding encoding, FieldInfo[] fields, object instance, FieldInfo fi)
{
if (fi.FieldType.IsAssignableFrom(typeof(string)))
{
return WriteStringType(writer, encoding, instance, fi);
}
else if (fi.FieldType.IsArray)
{
return WriteArrayType(writer, fields, instance, fi);
}
else
{
var value = fi.GetValue(instance);
return writer.WriteType(value, fi.FieldType);
}
}
/// <summary>
/// Write an array type field from an object
/// </summary>
private static bool WriteArrayType(BinaryWriter writer, FieldInfo[] fields, object instance, FieldInfo fi)
{
var marshalAsAttr = MarshalHelpers.GetAttribute<MarshalAsAttribute>(fi);
if (marshalAsAttr == null)
return false;
// Get the array
Array? arr = fi.GetValue(instance) as Array;
if (arr == null)
return false;
// Get the number of elements expected
int elementCount = MarshalHelpers.GetArrayElementCount(marshalAsAttr, fields, instance);
if (elementCount < 0)
return false;
// Get the item type for the array
Type elementType = fi.FieldType.GetElementType() ?? typeof(object);
// Loop through and write the array
for (int i = 0; i < elementCount; i++)
{
var value = arr.GetValue(i);
if (!WriteType(writer, value, elementType))
return false;
}
return true;
}
/// <summary>
/// Write a string type field from an object
/// </summary>
private static bool WriteStringType(BinaryWriter writer, Encoding encoding, object instance, FieldInfo fi)
{
var marshalAsAttr = MarshalHelpers.GetAttribute<MarshalAsAttribute>(fi);
string? fieldValue = fi.GetValue(instance) as string;
if (fieldValue == null)
return true;
switch (marshalAsAttr?.Value)
{
case UnmanagedType.AnsiBStr:
return writer.WritePrefixedAnsiString(fieldValue);
case UnmanagedType.BStr:
case UnmanagedType.TBStr: // Technically distinct; returns char[] instead
return writer.WritePrefixedUnicodeString(fieldValue);
case UnmanagedType.ByValTStr:
int byvalLength = marshalAsAttr!.SizeConst;
byte[] byvalBytes = encoding.GetBytes(fieldValue);
byte[] byvalSizedBytes = new byte[byvalLength];
Array.Copy(byvalBytes, byvalSizedBytes, Math.Min(byvalBytes.Length, byvalSizedBytes.Length));
writer.Write(byvalSizedBytes);
return true;
case UnmanagedType.LPStr:
case UnmanagedType.LPTStr: // Technically distinct; possibly not null-terminated
case null:
return writer.WriteNullTerminatedAnsiString(fieldValue);
#if NET472_OR_GREATER || NETCOREAPP
case UnmanagedType.LPUTF8Str:
return writer.WriteNullTerminatedUTF8String(fieldValue);
#endif
case UnmanagedType.LPWStr:
return writer.WriteNullTerminatedUnicodeString(fieldValue);
// No other string types are recognized
default:
return false;
}
}
/// <summary>
/// Write an array of bytes to the underlying stream
/// </summary>
private static bool WriteFromBuffer(BinaryWriter writer, byte[] value)
{
// If the stream is not writable
if (!writer.BaseStream.CanWrite)
return false;
// Handle the 0-byte case
if (value.Length == 0)
return true;
// Handle the general case, forcing a write of the correct length
writer.Write(value, 0, value.Length);
return true;
}
}
}

View File

@@ -1,359 +0,0 @@
using System;
using System.Collections.Generic;
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 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 in big-endian format and increment the pointer to an array
/// </summary>
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 in big-endian format and increment the pointer to an array
/// </summary>
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 in big-endian format and increment the pointer to an array
/// </summary>
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 in big-endian format and increment the pointer to an array
/// </summary>
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 in big-endian format and increment the pointer to an array
/// </summary>
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 in big-endian format and increment the pointer to an array
/// </summary>
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 in big-endian format and increment the pointer to an array
/// </summary>
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 in big-endian format and increment the pointer to an array
/// </summary>
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 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 in big-endian format and increment the pointer to an array
/// </summary>
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 in big-endian format and increment the pointer to an array
/// </summary>
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 in big-endian format and increment the pointer to an array
/// </summary>
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 byte array
/// </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 byte array
/// </summary>
public static string? ReadString(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>();
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');
}
/// <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 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,883 @@
using System;
using System.Collections.Generic;
#if NET7_0_OR_GREATER
using System.Numerics;
#endif
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
namespace SabreTools.IO.Extensions
{
/// <summary>
/// Extensions for byte arrays
/// </summary>
/// TODO: Handle proper negative values for Int24 and Int48
public static class ByteArrayReaderExtensions
{
/// <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);
}
// Half was introduced in net5.0 but doesn't have a BitConverter implementation until net6.0
#if NET6_0_OR_GREATER
/// <summary>
/// Read a Half and increment the pointer to an array
/// </summary>
public static Half ReadHalf(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 2);
return BitConverter.ToHalf(buffer, 0);
}
/// <summary>
/// Read a Half and increment the pointer to an array
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static Half ReadHalfBigEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 2);
Array.Reverse(buffer);
return BitConverter.ToHalf(buffer, 0);
}
#endif
/// <summary>
/// Read an Int24 encoded as an Int32 and increment the pointer to an array
/// </summary>
public static int ReadInt24(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 3);
byte[] padded = new byte[4];
Array.Copy(buffer, padded, 3);
return BitConverter.ToInt32(padded, 0);
}
/// <summary>
/// Read an Int24 encoded as an Int32 and increment the pointer to an array
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static int ReadInt24BigEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 3);
Array.Reverse(buffer);
byte[] padded = new byte[4];
Array.Copy(buffer, padded, 3);
return BitConverter.ToInt32(padded, 0);
}
/// <summary>
/// Read a UInt24 encoded as a UInt32 and increment the pointer to an array
/// </summary>
public static uint ReadUInt24(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 3);
byte[] padded = new byte[4];
Array.Copy(buffer, padded, 3);
return BitConverter.ToUInt32(padded, 0);
}
/// <summary>
/// Read a UInt24 encoded as a UInt32 and increment the pointer to an array
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static uint ReadUInt24BigEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 3);
Array.Reverse(buffer);
byte[] padded = new byte[4];
Array.Copy(buffer, padded, 3);
return BitConverter.ToUInt32(padded, 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 Int48 encoded as an Int64 and increment the pointer to an array
/// </summary>
public static long ReadInt48(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 6);
byte[] padded = new byte[8];
Array.Copy(buffer, padded, 6);
return BitConverter.ToInt64(padded, 0);
}
/// <summary>
/// Read an Int48 encoded as an Int64 and increment the pointer to an array
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static long ReadInt48BigEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 6);
Array.Reverse(buffer);
byte[] padded = new byte[8];
Array.Copy(buffer, padded, 6);
return BitConverter.ToInt64(padded, 0);
}
/// <summary>
/// Read a UInt48 encoded as a UInt64 and increment the pointer to an array
/// </summary>
public static ulong ReadUInt48(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 6);
byte[] padded = new byte[8];
Array.Copy(buffer, padded, 6);
return BitConverter.ToUInt64(padded, 0);
}
/// <summary>
/// Read a UInt48 encoded as a UInt64 and increment the pointer to an array
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static ulong ReadUInt48BigEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 6);
Array.Reverse(buffer);
byte[] padded = new byte[8];
Array.Copy(buffer, padded, 6);
return BitConverter.ToUInt64(padded, 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 lo = BitConverter.ToInt32(buffer, 0);
int mid = BitConverter.ToInt32(buffer, 4);
int hi = BitConverter.ToInt32(buffer, 8);
int flags = BitConverter.ToInt32(buffer, 12);
return new decimal([lo, mid, hi, flags]);
}
/// <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 lo = BitConverter.ToInt32(buffer, 0);
int mid = BitConverter.ToInt32(buffer, 4);
int hi = BitConverter.ToInt32(buffer, 8);
int flags = BitConverter.ToInt32(buffer, 12);
return new decimal([lo, mid, hi, flags]);
}
/// <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);
}
#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 (Int128)new BigInteger(buffer);
}
/// <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 (Int128)new BigInteger(buffer);
}
/// <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 (UInt128)new BigInteger(buffer);
}
/// <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 (UInt128)new BigInteger(buffer);
}
#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.UTF8))
return content.ReadNullTerminatedUTF8String(ref offset);
else if (encoding.Equals(Encoding.Unicode))
return content.ReadNullTerminatedUnicodeString(ref offset);
else if (encoding.Equals(Encoding.UTF32))
return content.ReadNullTerminatedUTF32String(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;
byte[] buffer = ReadUntilNull1Byte(content, ref offset);
return Encoding.ASCII.GetString(buffer);
}
/// <summary>
/// Read a null-terminated UTF-8 string from the byte array
/// </summary>
public static string? ReadNullTerminatedUTF8String(this byte[] content, ref int offset)
{
if (offset >= content.Length)
return null;
byte[] buffer = ReadUntilNull1Byte(content, ref offset);
return Encoding.UTF8.GetString(buffer);
}
/// <summary>
/// Read a null-terminated UTF-16 (Unicode) string from the byte array
/// </summary>
public static string? ReadNullTerminatedUnicodeString(this byte[] content, ref int offset)
{
if (offset >= content.Length)
return null;
byte[] buffer = ReadUntilNull2Byte(content, ref offset);
return Encoding.Unicode.GetString(buffer);
}
/// <summary>
/// Read a null-terminated UTF-32 string from the byte array
/// </summary>
public static string? ReadNullTerminatedUTF32String(this byte[] content, ref int offset)
{
if (offset >= content.Length)
return null;
byte[] buffer = ReadUntilNull4Byte(content, ref offset);
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 * 2) >= content.Length)
return null;
byte[] buffer = content.ReadBytes(ref offset, size * 2);
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 byte array
/// </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 byte array
/// </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 stream
/// </summary>
/// <remarks>
/// This method is different than standard marshalling in a few notable ways:
/// - Strings are read by value, not by reference
/// - Complex objects are read by value, not by reference
/// - Enumeration values are read by the underlying value type
/// - Arrays of the above are handled sequentially as above
/// - Inherited fields from parents are deserialized BEFORE fields in the child
/// </remarks>
public static T? ReadType<T>(this byte[] content, ref int offset)
=> (T?)content.ReadType(ref offset, typeof(T));
/// <summary>
/// Read a <paramref name="type"/> from the stream
/// </summary>
/// <remarks>
/// This method is different than standard marshalling in a few notable ways:
/// - Strings are read by value, not by reference
/// - Complex objects are read by value, not by reference
/// - Enumeration values are read by the underlying value type
/// - Arrays of the above are handled sequentially as above
/// - Inherited fields from parents are deserialized BEFORE fields in the child
/// </remarks>
public static object? ReadType(this byte[] content, ref int offset, Type type)
{
// Handle special struct cases
if (type == typeof(Guid))
return content.ReadGuid(ref offset);
#if NET6_0_OR_GREATER
else if (type == typeof(Half))
return content.ReadHalf(ref offset);
#endif
#if NET7_0_OR_GREATER
else if (type == typeof(Int128))
return content.ReadInt128(ref offset);
else if (type == typeof(UInt128))
return content.ReadUInt128(ref offset);
#endif
if (type.IsClass || (type.IsValueType && !type.IsEnum && !type.IsPrimitive))
return ReadComplexType(content, ref offset, type);
else if (type.IsValueType && type.IsEnum)
return ReadNormalType(content, ref offset, Enum.GetUnderlyingType(type));
else
return ReadNormalType(content, ref offset, type);
}
/// <summary>
/// Read a <paramref name="type"/> from the stream
/// </summary>
private static object? ReadNormalType(byte[] content, ref int offset, Type type)
{
try
{
int typeSize = Marshal.SizeOf(type);
byte[] buffer = ReadToBuffer(content, ref offset, typeSize);
var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
var data = Marshal.PtrToStructure(handle.AddrOfPinnedObject(), type);
handle.Free();
return data;
}
catch
{
return null;
}
}
/// <summary>
/// Read a <paramref name="type"/> from the stream
/// </summary>
private static object? ReadComplexType(byte[] content, ref int offset, Type type)
{
try
{
// Try to create an instance of the type
var instance = Activator.CreateInstance(type);
if (instance == null)
return null;
// Get the layout information
var layoutAttr = MarshalHelpers.GetAttribute<StructLayoutAttribute>(type);
LayoutKind layoutKind = MarshalHelpers.DetermineLayoutKind(layoutAttr, type);
Encoding encoding = MarshalHelpers.DetermineEncoding(layoutAttr);
// Cache the current offset
int currentOffset = offset;
// Generate the fields by parent first
var fields = MarshalHelpers.GetFields(type);
// Loop through the fields and set them
foreach (var fi in fields)
{
// If we have an explicit layout, move accordingly
if (layoutKind == LayoutKind.Explicit)
{
var fieldOffset = MarshalHelpers.GetAttribute<FieldOffsetAttribute>(fi);
offset = currentOffset + fieldOffset?.Value ?? 0;
}
SetField(content, ref offset, encoding, fields, instance, fi);
}
return instance;
}
catch
{
return null;
}
}
/// <summary>
/// Set a single field on an object
/// </summary>
private static void SetField(byte[] content, ref int offset, Encoding encoding, FieldInfo[] fields, object instance, FieldInfo fi)
{
if (fi.FieldType.IsAssignableFrom(typeof(string)))
{
var value = ReadStringType(content, ref offset, encoding, instance, fi);
fi.SetValue(instance, value);
}
else if (fi.FieldType.IsArray)
{
var value = ReadArrayType(content, ref offset, fields, instance, fi);
fi.SetValue(instance, Convert.ChangeType(value, fi.FieldType));
}
else
{
var value = content.ReadType(ref offset, fi.FieldType);
fi.SetValue(instance, value);
}
}
/// <summary>
/// Read an array type field for an object
/// </summary>
private static Array ReadArrayType(byte[] content, ref int offset, FieldInfo[] fields, object instance, FieldInfo fi)
{
var marshalAsAttr = MarshalHelpers.GetAttribute<MarshalAsAttribute>(fi);
if (marshalAsAttr == null)
return new object[0];
// Get the number of elements expected
int elementCount = MarshalHelpers.GetArrayElementCount(marshalAsAttr, fields, instance);
if (elementCount < 0)
return new object[0];
// Get the item type for the array
Type elementType = fi.FieldType.GetElementType() ?? typeof(object);
// Loop through and build the array
Array arr = Array.CreateInstance(elementType, elementCount);
for (int i = 0; i < elementCount; i++)
{
var value = ReadType(content, ref offset, elementType);
arr.SetValue(value, i);
}
// Return the built array
return arr;
}
/// <summary>
/// Read a string type field for an object
/// </summary>
private static string? ReadStringType(byte[] content, ref int offset, Encoding encoding, object instance, FieldInfo fi)
{
var marshalAsAttr = MarshalHelpers.GetAttribute<MarshalAsAttribute>(fi);
switch (marshalAsAttr?.Value)
{
case UnmanagedType.AnsiBStr:
return content.ReadPrefixedAnsiString(ref offset);
case UnmanagedType.BStr:
case UnmanagedType.TBStr: // Technically distinct; returns char[] instead
return content.ReadPrefixedUnicodeString(ref offset);
case UnmanagedType.ByValTStr:
int byvalLength = marshalAsAttr!.SizeConst;
byte[] byvalBytes = content.ReadBytes(ref offset, byvalLength);
return encoding.GetString(byvalBytes);
case UnmanagedType.LPStr:
case UnmanagedType.LPTStr: // Technically distinct; possibly not null-terminated
case null:
return content.ReadNullTerminatedAnsiString(ref offset);
#if NET472_OR_GREATER || NETCOREAPP
case UnmanagedType.LPUTF8Str:
return content.ReadNullTerminatedUTF8String(ref offset);
#endif
case UnmanagedType.LPWStr:
return content.ReadNullTerminatedUnicodeString(ref offset);
// No other string types are recognized
default:
return null;
}
}
/// <summary>
/// Read bytes until a 1-byte null terminator is found
/// </summary>
private static byte[] ReadUntilNull1Byte(byte[] content, ref int offset)
{
var bytes = new List<byte>();
while (offset < content.Length)
{
byte next = content.ReadByte(ref offset);
if (next == 0x00)
break;
bytes.Add(next);
}
return [.. bytes];
}
/// <summary>
/// Read bytes until a 2-byte null terminator is found
/// </summary>
private static byte[] ReadUntilNull2Byte(byte[] content, ref int offset)
{
var bytes = new List<byte>();
while (offset < content.Length)
{
ushort next = content.ReadUInt16(ref offset);
if (next == 0x0000)
break;
bytes.AddRange(BitConverter.GetBytes(next));
}
return [.. bytes];
}
/// <summary>
/// Read bytes until a 4-byte null terminator is found
/// </summary>
private static byte[] ReadUntilNull4Byte(byte[] content, ref int offset)
{
var bytes = new List<byte>();
while (offset < content.Length)
{
uint next = content.ReadUInt32(ref offset);
if (next == 0x00000000)
break;
bytes.AddRange(BitConverter.GetBytes(next));
}
return [.. bytes];
}
/// <summary>
/// Read a number of bytes from the 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($"Requested to read {nameof(length)} bytes from {nameof(content)}, {content.Length - offset} returned");
// 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,820 @@
using System;
#if NET7_0_OR_GREATER
using System.Numerics;
#endif
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
namespace SabreTools.IO.Extensions
{
/// <summary>
/// Extensions for byte arrays
/// </summary>
/// TODO: Handle proper negative values for Int24 and Int48
public static class ByteArrayWriterExtensions
{
/// <summary>
/// Write a UInt8 and increment the pointer to an array
/// </summary>
public static bool Write(this byte[] content, ref int offset, byte value)
=> WriteFromBuffer(content, ref offset, [value]);
/// <summary>
/// Write a UInt8[] and increment the pointer to an array
/// </summary>
public static bool Write(this byte[] content, ref int offset, byte[] value)
=> WriteFromBuffer(content, ref offset, value);
/// <summary>
/// Write a UInt8[] and increment the pointer to an array
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this byte[] content, ref int offset, byte[] value)
{
Array.Reverse(value);
return WriteFromBuffer(content, ref offset, value);
}
/// <summary>
/// Write an Int8 and increment the pointer to an array
/// </summary>
public static bool Write(this byte[] content, ref int offset, sbyte value)
=> WriteFromBuffer(content, ref offset, [(byte)value]);
/// <summary>
/// Write a Char and increment the pointer to an array
/// </summary>
public static bool Write(this byte[] content, ref int offset, char value)
{
byte[] buffer = BitConverter.GetBytes(value);
return WriteFromBuffer(content, ref offset, buffer);
}
/// <summary>
/// Write a Char with an Encoding and increment the pointer to an array
/// </summary>
public static bool Write(this byte[] content, ref int offset, char value, Encoding encoding)
{
byte[] buffer = encoding.GetBytes($"{value}");
return WriteFromBuffer(content, ref offset, buffer);
}
/// <summary>
/// Write an Int16 and increment the pointer to an array
/// </summary>
public static bool Write(this byte[] content, ref int offset, short value)
{
byte[] buffer = BitConverter.GetBytes(value);
return WriteFromBuffer(content, ref offset, buffer);
}
/// <summary>
/// Write an Int16 and increment the pointer to an array
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this byte[] content, ref int offset, short value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
return WriteFromBuffer(content, ref offset, buffer);
}
/// <summary>
/// Write a UInt16 and increment the pointer to an array
/// </summary>
public static bool Write(this byte[] content, ref int offset, ushort value)
{
byte[] buffer = BitConverter.GetBytes(value);
return WriteFromBuffer(content, ref offset, buffer);
}
/// <summary>
/// Write a UInt16 and increment the pointer to an array
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this byte[] content, ref int offset, ushort value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
return WriteFromBuffer(content, ref offset, buffer);
}
// Half was introduced in net5.0 but doesn't have a BitConverter implementation until net6.0
#if NET6_0_OR_GREATER
/// <summary>
/// Write a Half and increment the pointer to an array
/// </summary>
public static bool Write(this byte[] content, ref int offset, Half value)
{
byte[] buffer = BitConverter.GetBytes(value);
return WriteFromBuffer(content, ref offset, buffer);
}
/// <summary>
/// Write a Half and increment the pointer to an array
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this byte[] content, ref int offset, Half value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
return WriteFromBuffer(content, ref offset, buffer);
}
#endif
/// <summary>
/// Write an Int32 as an Int24 and increment the pointer to an array
/// </summary>
/// <remarks>Throws away top byte</remarks>
public static bool WriteAsInt24(this byte[] content, ref int offset, int value)
{
byte[] buffer = BitConverter.GetBytes(value);
byte[] reduced = new byte[3];
Array.Copy(buffer, reduced, 3);
return WriteFromBuffer(content, ref offset, reduced);
}
/// <summary>
/// Write an Int32 as an Int24 and increment the pointer to an array
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
/// <remarks>Throws away top byte</remarks>
public static bool WriteAsInt24BigEndian(this byte[] content, ref int offset, int value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
byte[] reduced = new byte[3];
Array.Copy(buffer, 1, reduced, 0, 3);
return WriteFromBuffer(content, ref offset, reduced);
}
/// <summary>
/// Write a UInt32 as a UInt24 and increment the pointer to an array
/// </summary>
/// <remarks>Throws away top byte</remarks>
public static bool WriteAsUInt24(this byte[] content, ref int offset, uint value)
{
byte[] buffer = BitConverter.GetBytes(value);
byte[] reduced = new byte[3];
Array.Copy(buffer, reduced, 3);
return WriteFromBuffer(content, ref offset, reduced);
}
/// <summary>
/// Write a UInt32 as a UInt24 and increment the pointer to an array
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
/// <remarks>Throws away top byte</remarks>
public static bool WriteAsUInt24BigEndian(this byte[] content, ref int offset, uint value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
byte[] reduced = new byte[3];
Array.Copy(buffer, 1, reduced, 0, 3);
return WriteFromBuffer(content, ref offset, reduced);
}
/// <summary>
/// Write an Int32 and increment the pointer to an array
/// </summary>
public static bool Write(this byte[] content, ref int offset, int value)
{
byte[] buffer = BitConverter.GetBytes(value);
return WriteFromBuffer(content, ref offset, buffer);
}
/// <summary>
/// Write an Int32 and increment the pointer to an array
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this byte[] content, ref int offset, int value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
return WriteFromBuffer(content, ref offset, buffer);
}
/// <summary>
/// Write a UInt32 and increment the pointer to an array
/// </summary>
public static bool Write(this byte[] content, ref int offset, uint value)
{
byte[] buffer = BitConverter.GetBytes(value);
return WriteFromBuffer(content, ref offset, buffer);
}
/// <summary>
/// Write a UInt32 and increment the pointer to an array
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this byte[] content, ref int offset, uint value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
return WriteFromBuffer(content, ref offset, buffer);
}
/// <summary>
/// Write a Single and increment the pointer to an array
/// </summary>
public static bool Write(this byte[] content, ref int offset, float value)
{
byte[] buffer = BitConverter.GetBytes(value);
return WriteFromBuffer(content, ref offset, buffer);
}
/// <summary>
/// Write a Single and increment the pointer to an array
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this byte[] content, ref int offset, float value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
return WriteFromBuffer(content, ref offset, buffer);
}
/// <summary>
/// Write an Int64 as an Int48 and increment the pointer to an array
/// </summary>
/// <remarks>Throws away top 2 bytes</remarks>
public static bool WriteAsInt48(this byte[] content, ref int offset, long value)
{
byte[] buffer = BitConverter.GetBytes(value);
byte[] reduced = new byte[6];
Array.Copy(buffer, reduced, 6);
return WriteFromBuffer(content, ref offset, reduced);
}
/// <summary>
/// Write an Int64 as an Int48 and increment the pointer to an array
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
/// <remarks>Throws away top 2 bytes</remarks>
public static bool WriteAsInt48BigEndian(this byte[] content, ref int offset, long value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
byte[] reduced = new byte[6];
Array.Copy(buffer, 2, reduced, 0, 6);
return WriteFromBuffer(content, ref offset, reduced);
}
/// <summary>
/// Write a UInt64 as a UInt48 and increment the pointer to an array
/// </summary>
/// <remarks>Throws away top 2 bytes</remarks>
public static bool WriteAsUInt48(this byte[] content, ref int offset, ulong value)
{
byte[] buffer = BitConverter.GetBytes(value);
byte[] reduced = new byte[6];
Array.Copy(buffer, reduced, 6);
return WriteFromBuffer(content, ref offset, reduced);
}
/// <summary>
/// Write a UInt64 as a UInt48 and increment the pointer to an array
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
/// <remarks>Throws away top 2 bytes</remarks>
public static bool WriteAsUInt48BigEndian(this byte[] content, ref int offset, ulong value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
byte[] reduced = new byte[6];
Array.Copy(buffer, 2, reduced, 0, 6);
return WriteFromBuffer(content, ref offset, reduced);
}
/// <summary>
/// Write an Int64 and increment the pointer to an array
/// </summary>
public static bool Write(this byte[] content, ref int offset, long value)
{
byte[] buffer = BitConverter.GetBytes(value);
return WriteFromBuffer(content, ref offset, buffer);
}
/// <summary>
/// Write an Int64 and increment the pointer to an array
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this byte[] content, ref int offset, long value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
return WriteFromBuffer(content, ref offset, buffer);
}
/// <summary>
/// Write a UInt64 and increment the pointer to an array
/// </summary>
public static bool Write(this byte[] content, ref int offset, ulong value)
{
byte[] buffer = BitConverter.GetBytes(value);
return WriteFromBuffer(content, ref offset, buffer);
}
/// <summary>
/// Write a UInt64 and increment the pointer to an array
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this byte[] content, ref int offset, ulong value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
return WriteFromBuffer(content, ref offset, buffer);
}
/// <summary>
/// Write a Double and increment the pointer to an array
/// </summary>
public static bool Write(this byte[] content, ref int offset, double value)
{
byte[] buffer = BitConverter.GetBytes(value);
return WriteFromBuffer(content, ref offset, buffer);
}
/// <summary>
/// Write a Double and increment the pointer to an array
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this byte[] content, ref int offset, double value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
return WriteFromBuffer(content, ref offset, buffer);
}
/// <summary>
/// Write a Decimal and increment the pointer to an array
/// </summary>
public static bool Write(this byte[] content, ref int offset, decimal value)
{
int[] bits = decimal.GetBits(value);
byte[] lo = BitConverter.GetBytes(bits[0]);
byte[] mid = BitConverter.GetBytes(bits[1]);
byte[] hi = BitConverter.GetBytes(bits[2]);
byte[] flags = BitConverter.GetBytes(bits[3]);
byte[] buffer = new byte[16];
Array.Copy(lo, 0, buffer, 0, 4);
Array.Copy(mid, 0, buffer, 4, 4);
Array.Copy(hi, 0, buffer, 8, 4);
Array.Copy(flags, 0, buffer, 12, 4);
return WriteFromBuffer(content, ref offset, buffer);
}
/// <summary>
/// Write a Decimal and increment the pointer to an array
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this byte[] content, ref int offset, decimal value)
{
int[] bits = decimal.GetBits(value);
byte[] lo = BitConverter.GetBytes(bits[0]);
byte[] mid = BitConverter.GetBytes(bits[1]);
byte[] hi = BitConverter.GetBytes(bits[2]);
byte[] flags = BitConverter.GetBytes(bits[3]);
byte[] buffer = new byte[16];
Array.Copy(lo, 0, buffer, 0, 4);
Array.Copy(mid, 0, buffer, 4, 4);
Array.Copy(hi, 0, buffer, 8, 4);
Array.Copy(flags, 0, buffer, 12, 4);
Array.Reverse(buffer);
return WriteFromBuffer(content, ref offset, buffer);
}
/// <summary>
/// Write a Guid and increment the pointer to an array
/// </summary>
public static bool Write(this byte[] content, ref int offset, Guid value)
{
byte[] buffer = value.ToByteArray();
return WriteFromBuffer(content, ref offset, buffer);
}
/// <summary>
/// Write a Guid and increment the pointer to an array
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this byte[] content, ref int offset, Guid value)
{
byte[] buffer = value.ToByteArray();
Array.Reverse(buffer);
return WriteFromBuffer(content, ref offset, buffer);
}
#if NET7_0_OR_GREATER
/// <summary>
/// Write an Int128 and increment the pointer to an array
/// </summary>
public static bool Write(this byte[] content, ref int offset, Int128 value)
{
byte[] buffer = ((BigInteger)value).ToByteArray();
byte[] padded = new byte[16];
Array.Copy(buffer, 0, padded, 16 - buffer.Length, buffer.Length);
return WriteFromBuffer(content, ref offset, padded);
}
/// <summary>
/// Write an Int128 and increment the pointer to an array
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this byte[] content, ref int offset, Int128 value)
{
byte[] buffer = ((BigInteger)value).ToByteArray();
Array.Reverse(buffer);
byte[] padded = new byte[16];
Array.Copy(buffer, 0, padded, 16 - buffer.Length, buffer.Length);
return WriteFromBuffer(content, ref offset, padded);
}
/// <summary>
/// Write a UInt128 and increment the pointer to an array
/// </summary>
public static bool Write(this byte[] content, ref int offset, UInt128 value)
{
byte[] buffer = ((BigInteger)value).ToByteArray();
byte[] padded = new byte[16];
Array.Copy(buffer, 0, padded, 16 - buffer.Length, buffer.Length);
return WriteFromBuffer(content, ref offset, padded);
}
/// <summary>
/// Write a UInt128 and increment the pointer to an array
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this byte[] content, ref int offset, UInt128 value)
{
byte[] buffer = ((BigInteger)value).ToByteArray();
Array.Reverse(buffer);
byte[] padded = new byte[16];
Array.Copy(buffer, 0, padded, 16 - buffer.Length, buffer.Length);
return WriteFromBuffer(content, ref offset, padded);
}
#endif
/// <summary>
/// Write a null-terminated string to the array
/// </summary>
public static bool WriteNullTerminatedString(this byte[] content, ref int offset, string? value, Encoding encoding)
{
// If the value is null
if (value == null)
return false;
// Add the null terminator and write
value += "\0";
byte[] buffer = encoding.GetBytes(value);
return WriteFromBuffer(content, ref offset, buffer);
}
/// <summary>
/// Write a null-terminated ASCII string to the byte array
/// </summary>
public static bool WriteNullTerminatedAnsiString(this byte[] content, ref int offset, string? value)
=> content.WriteNullTerminatedString(ref offset, value, Encoding.ASCII);
/// <summary>
/// Write a null-terminated UTF-8 string to the byte array
/// </summary>
public static bool WriteNullTerminatedUTF8String(this byte[] content, ref int offset, string? value)
=> content.WriteNullTerminatedString(ref offset, value, Encoding.UTF8);
/// <summary>
/// Write a null-terminated UTF-16 (Unicode) string to the byte array
/// </summary>
public static bool WriteNullTerminatedUnicodeString(this byte[] content, ref int offset, string? value)
=> content.WriteNullTerminatedString(ref offset, value, Encoding.Unicode);
/// <summary>
/// Write a null-terminated UTF-32 string to the byte array
/// </summary>
public static bool WriteNullTerminatedUTF32String(this byte[] content, ref int offset, string? value)
=> content.WriteNullTerminatedString(ref offset, value, Encoding.UTF32);
/// <summary>
/// Write a byte-prefixed ASCII string to the byte array
/// </summary>
public static bool WritePrefixedAnsiString(this byte[] content, ref int offset, string? value)
{
// If the value is null
if (value == null)
return false;
// Get the buffer
byte[] buffer = Encoding.ASCII.GetBytes(value);
// Write the length as a byte
if (!content.Write(ref offset, (byte)buffer.Length))
return false;
// Write the buffer
return WriteFromBuffer(content, ref offset, buffer);
}
/// <summary>
/// Write a ushort-prefixed Unicode string to the byte array
/// </summary>
public static bool WritePrefixedUnicodeString(this byte[] content, ref int offset, string? value)
{
// If the value is null
if (value == null)
return false;
// Get the buffer
byte[] buffer = Encoding.Unicode.GetBytes(value);
// Write the length as a ushort
if (!content.Write(ref offset, (ushort)buffer.Length))
return false;
// Write the buffer
return WriteFromBuffer(content, ref offset, buffer);
}
/// <summary>
/// Write a string that is terminated by a newline but contains a quoted portion that
/// may also contain a newline to the byte array
/// </summary>
public static bool WriteQuotedString(this byte[] content, ref int offset, string? value)
=> content.WriteQuotedString(ref offset, value, Encoding.UTF8);
/// <summary>
/// Write a string that is terminated by a newline but contains a quoted portion that
/// may also contain a newline to the byte array
/// </summary>
public static bool WriteQuotedString(this byte[] content, ref int offset, string? value, Encoding encoding)
{
// If the value is null
if (value == null)
return false;
// Write without the null terminator
byte[] buffer = encoding.GetBytes(value);
return WriteFromBuffer(content, ref offset, buffer);
}
/// <summary>
/// Write a <typeparamref name="T"/> to the byte array
/// </summary>
/// <remarks>
/// This method is different than standard marshalling in a few notable ways:
/// - Strings are written by value, not by reference
/// - Complex objects are written by value, not by reference
/// - Enumeration values are written by the underlying value type
/// - Arrays of the above are handled sequentially as above
/// - Inherited fields from parents are serialized BEFORE fields in the child
/// </remarks>
public static bool WriteType<T>(this byte[] content, ref int offset, T? value)
=> content.WriteType(ref offset, value, typeof(T));
/// <summary>
/// Write a <typeparamref name="T"/> to the byte array
/// </summary>
/// <remarks>
/// This method is different than standard marshalling in a few notable ways:
/// - Strings are written by value, not by reference
/// - Complex objects are written by value, not by reference
/// - Enumeration values are written by the underlying value type
/// - Arrays of the above are handled sequentially as above
/// - Inherited fields from parents are serialized BEFORE fields in the child
/// </remarks>
public static bool WriteType(this byte[] content, ref int offset, object? value, Type type)
{
// Null values cannot be written
if (value == null)
return true;
// Handle special struct cases
if (type == typeof(Guid))
return content.Write(ref offset, (Guid)value);
#if NET6_0_OR_GREATER
else if (type == typeof(Half))
return content.Write(ref offset, (Half)value);
#endif
#if NET7_0_OR_GREATER
else if (type == typeof(Int128))
return content.Write(ref offset, (Int128)value);
else if (type == typeof(UInt128))
return content.Write(ref offset, (UInt128)value);
#endif
if (type.IsClass || (type.IsValueType && !type.IsEnum && !type.IsPrimitive))
return WriteComplexType(content, ref offset, value, type);
else if (type.IsValueType && type.IsEnum)
return WriteNormalType(content, ref offset, value, Enum.GetUnderlyingType(type));
else
return WriteNormalType(content, ref offset, value, type);
}
/// <summary>
/// Read a <paramref name="type"/> from the stream
/// </summary>
private static bool WriteNormalType(byte[] content, ref int offset, object? value, Type type)
{
try
{
// Null values cannot be written
if (value == null)
return true;
int typeSize = Marshal.SizeOf(type);
if (value.GetType() != type)
value = Convert.ChangeType(value, type);
var buffer = new byte[typeSize];
var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
Marshal.StructureToPtr(value, handle.AddrOfPinnedObject(), false);
handle.Free();
return WriteFromBuffer(content, ref offset, buffer);
}
catch
{
return false;
}
}
/// <summary>
/// Read a <paramref name="type"/> from the stream
/// </summary>
private static bool WriteComplexType(byte[] content, ref int offset, object? value, Type type)
{
try
{
// Null values cannot be written
if (value == null)
return true;
// Get the layout information
var layoutAttr = MarshalHelpers.GetAttribute<StructLayoutAttribute>(type);
LayoutKind layoutKind = MarshalHelpers.DetermineLayoutKind(layoutAttr, type);
Encoding encoding = MarshalHelpers.DetermineEncoding(layoutAttr);
// Cache the current offset
int currentOffset = offset;
// Generate the fields by parent first
var fields = MarshalHelpers.GetFields(type);
// Loop through the fields and set them
foreach (var fi in fields)
{
// If we have an explicit layout, move accordingly
if (layoutKind == LayoutKind.Explicit)
{
var fieldOffset = MarshalHelpers.GetAttribute<FieldOffsetAttribute>(fi);
offset = currentOffset + fieldOffset?.Value ?? 0;
}
if (!GetField(content, ref offset, encoding, fields, value, fi))
return false;
}
return true;
}
catch
{
return false;
}
}
/// <summary>
/// Write a single field from an object
/// </summary>
private static bool GetField(byte[] content, ref int offset, Encoding encoding, FieldInfo[] fields, object instance, FieldInfo fi)
{
if (fi.FieldType.IsAssignableFrom(typeof(string)))
{
return WriteStringType(content, ref offset, encoding, instance, fi);
}
else if (fi.FieldType.IsArray)
{
return WriteArrayType(content, ref offset, fields, instance, fi);
}
else
{
var value = fi.GetValue(instance);
return content.WriteType(ref offset, value, fi.FieldType);
}
}
/// <summary>
/// Write an array type field from an object
/// </summary>
private static bool WriteArrayType(byte[] content, ref int offset, FieldInfo[] fields, object instance, FieldInfo fi)
{
var marshalAsAttr = MarshalHelpers.GetAttribute<MarshalAsAttribute>(fi);
if (marshalAsAttr == null)
return false;
// Get the array
Array? arr = fi.GetValue(instance) as Array;
if (arr == null)
return false;
// Get the number of elements expected
int elementCount = MarshalHelpers.GetArrayElementCount(marshalAsAttr, fields, instance);
if (elementCount < 0)
return false;
// Get the item type for the array
Type elementType = fi.FieldType.GetElementType() ?? typeof(object);
// Loop through and write the array
for (int i = 0; i < elementCount; i++)
{
var value = arr.GetValue(i);
if (!WriteType(content, ref offset, value, elementType))
return false;
}
return true;
}
/// <summary>
/// Write a string type field from an object
/// </summary>
private static bool WriteStringType(byte[] content, ref int offset, Encoding encoding, object instance, FieldInfo fi)
{
var marshalAsAttr = MarshalHelpers.GetAttribute<MarshalAsAttribute>(fi);
string? fieldValue = fi.GetValue(instance) as string;
if (fieldValue == null)
return true;
switch (marshalAsAttr?.Value)
{
case UnmanagedType.AnsiBStr:
return content.WritePrefixedAnsiString(ref offset, fieldValue);
case UnmanagedType.BStr:
case UnmanagedType.TBStr: // Technically distinct; returns char[] instead
return content.WritePrefixedUnicodeString(ref offset, fieldValue);
case UnmanagedType.ByValTStr:
int byvalLength = marshalAsAttr!.SizeConst;
byte[] byvalBytes = encoding.GetBytes(fieldValue);
byte[] byvalSizedBytes = new byte[byvalLength];
Array.Copy(byvalBytes, byvalSizedBytes, Math.Min(byvalBytes.Length, byvalSizedBytes.Length));
return content.Write(ref offset, byvalSizedBytes);
case UnmanagedType.LPStr:
case UnmanagedType.LPTStr: // Technically distinct; possibly not null-terminated
case null:
return content.WriteNullTerminatedAnsiString(ref offset, fieldValue);
#if NET472_OR_GREATER || NETCOREAPP
case UnmanagedType.LPUTF8Str:
return content.WriteNullTerminatedUTF8String(ref offset, fieldValue);
#endif
case UnmanagedType.LPWStr:
return content.WriteNullTerminatedUnicodeString(ref offset, fieldValue);
// No other string types are recognized
default:
return false;
}
}
/// <summary>
/// Write an array of bytes to the byte array
/// </summary>
private static bool WriteFromBuffer(byte[] content, ref int offset, byte[] value)
{
// Handle the 0-byte case
if (value.Length == 0)
return true;
// If there are not enough bytes
if (offset + value.Length > content.Length)
throw new System.IO.EndOfStreamException(nameof(content));
// Handle the general case, forcing a write of the correct length
Array.Copy(value, 0, content, offset, value.Length);
offset += value.Length;
return true;
}
}
}

View File

@@ -0,0 +1,57 @@
using System;
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
IEnumerator<T> enumerator;
try
{
enumerator = enumerable.GetEnumerator();
}
catch
{
yield break;
}
// Iterate through and absorb any errors
while (true)
{
// Attempt to move to the next item
bool moved;
try
{
moved = enumerator.MoveNext();
}
catch (InvalidOperationException)
{
// Specific case for collections that were modified
yield break;
}
catch (System.IO.IOException ex) when (ex.Message.Contains("The file or directory is corrupted and unreadable."))
{
// Specific case we can't circumvent
yield break;
}
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

@@ -1,5 +1,11 @@
using System.Collections.Generic;
#if NETCOREAPP3_1_OR_GREATER
using System;
#endif
using System.Collections.Generic;
using System.IO;
#if NETCOREAPP3_1_OR_GREATER
using System.IO.Enumeration;
#endif
using System.Linq;
using System.Text;
@@ -118,23 +124,490 @@ namespace SabreTools.IO.Extensions
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
if (!root!.SafeEnumerateFileSystemEntries("*", SearchOption.AllDirectories).Any())
return [];
// Otherwise, get the complete list
#if NET20 || NET35
return Directory.GetDirectories(root, "*", SearchOption.AllDirectories)
.Where(dir => !Directory.GetFiles(dir, "*", SearchOption.AllDirectories).Any())
return root!.SafeEnumerateDirectories("*", SearchOption.AllDirectories)
.Where(dir => !dir.SafeEnumerateFileSystemEntries("*", SearchOption.AllDirectories).Any())
.ToList();
#else
return Directory.EnumerateDirectories(root, "*", SearchOption.AllDirectories)
.Where(dir => !Directory.EnumerateFileSystemEntries(dir, "*", SearchOption.AllDirectories).Any())
.ToList();
#endif
}
#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);
#elif NET40_OR_GREATER
/// <inheritdoc cref="Directory.EnumerateDirectories(string)"/>
public static IEnumerable<string> SafeEnumerateDirectories(this string path)
{
try
{
var enumerable = Directory.EnumerateDirectories(path);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
/// <inheritdoc cref="Directory.EnumerateDirectories(string, string)"/>
public static IEnumerable<string> SafeEnumerateDirectories(this string path, string searchPattern)
{
try
{
var enumerable = Directory.EnumerateDirectories(path, searchPattern);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
/// <inheritdoc cref="Directory.EnumerateDirectories(string, string, SearchOption)"/>
public static IEnumerable<string> SafeEnumerateDirectories(this string path, string searchPattern, SearchOption searchOption)
{
try
{
var enumerable = Directory.EnumerateDirectories(path, searchPattern, searchOption);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
/// <inheritdoc cref="Directory.EnumerateFiles(string)"/>
public static IEnumerable<string> SafeEnumerateFiles(this string path)
{
try
{
var enumerable = Directory.EnumerateFiles(path);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
/// <inheritdoc cref="Directory.EnumerateFiles(string, string)"/>
public static IEnumerable<string> SafeEnumerateFiles(this string path, string searchPattern)
{
try
{
var enumerable = Directory.EnumerateFiles(path, searchPattern);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
/// <inheritdoc cref="Directory.EnumerateFiles(string, string, SearchOption)"/>
public static IEnumerable<string> SafeEnumerateFiles(this string path, string searchPattern, SearchOption searchOption)
{
try
{
var enumerable = Directory.EnumerateFiles(path, searchPattern, searchOption);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
/// <inheritdoc cref="Directory.EnumerateFileSystemEntries(string)"/>
public static IEnumerable<string> SafeEnumerateFileSystemEntries(this string path)
{
try
{
var enumerable = Directory.EnumerateFileSystemEntries(path);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
/// <inheritdoc cref="Directory.EnumerateFileSystemEntries(string, string)"/>
public static IEnumerable<string> SafeEnumerateFileSystemEntries(this string path, string searchPattern)
{
try
{
var enumerable = Directory.EnumerateFileSystemEntries(path, searchPattern);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
/// <inheritdoc cref="Directory.EnumerateFileSystemEntries(string, string, SearchOption)"/>
public static IEnumerable<string> SafeEnumerateFileSystemEntries(this string path, string searchPattern, SearchOption searchOption)
{
try
{
var enumerable = Directory.EnumerateFileSystemEntries(path, searchPattern, searchOption);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
#else
/// <inheritdoc cref="Directory.EnumerateDirectories(string)"/>
public static IEnumerable<string> SafeEnumerateDirectories(this string path)
{
try
{
string searchPattern = "*";
SearchOption searchOption = SearchOption.TopDirectoryOnly;
var enumerationOptions = FromSearchOption(searchOption);
enumerationOptions.IgnoreInaccessible = true;
var enumerable = Directory.EnumerateDirectories(path, searchPattern, enumerationOptions);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
/// <inheritdoc cref="Directory.EnumerateDirectories(string, string)"/>
public static IEnumerable<string> SafeEnumerateDirectories(this string path, string searchPattern)
{
try
{
SearchOption searchOption = SearchOption.TopDirectoryOnly;
var enumerationOptions = FromSearchOption(searchOption);
enumerationOptions.IgnoreInaccessible = true;
var enumerable = Directory.EnumerateDirectories(path, searchPattern, enumerationOptions);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
/// <inheritdoc cref="Directory.EnumerateDirectories(string, string, SearchOption)"/>
public static IEnumerable<string> SafeEnumerateDirectories(this string path, string searchPattern, SearchOption searchOption)
{
try
{
var enumerationOptions = FromSearchOption(searchOption);
enumerationOptions.IgnoreInaccessible = true;
var enumerable = Directory.EnumerateDirectories(path, searchPattern, enumerationOptions);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
/// <inheritdoc cref="Directory.EnumerateFiles(string)"/>
public static IEnumerable<string> SafeEnumerateFiles(this string path)
{
try
{
string searchPattern = "*";
SearchOption searchOption = SearchOption.TopDirectoryOnly;
var enumerationOptions = FromSearchOption(searchOption);
enumerationOptions.IgnoreInaccessible = true;
var enumerable = Directory.EnumerateFiles(path, searchPattern, enumerationOptions);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
/// <inheritdoc cref="Directory.EnumerateFiles(string, string)"/>
public static IEnumerable<string> SafeEnumerateFiles(this string path, string searchPattern)
{
try
{
SearchOption searchOption = SearchOption.TopDirectoryOnly;
var enumerationOptions = FromSearchOption(searchOption);
enumerationOptions.IgnoreInaccessible = true;
var enumerable = Directory.EnumerateFiles(path, searchPattern, enumerationOptions);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
/// <inheritdoc cref="Directory.EnumerateFiles(string, string, SearchOption)"/>
public static IEnumerable<string> SafeEnumerateFiles(this string path, string searchPattern, SearchOption searchOption)
{
try
{
var enumerationOptions = FromSearchOption(searchOption);
enumerationOptions.IgnoreInaccessible = true;
var enumerable = Directory.EnumerateFiles(path, searchPattern, enumerationOptions);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
/// <inheritdoc cref="Directory.EnumerateFileSystemEntries(string)"/>
public static IEnumerable<string> SafeEnumerateFileSystemEntries(this string path)
{
try
{
string searchPattern = "*";
SearchOption searchOption = SearchOption.TopDirectoryOnly;
var enumerationOptions = FromSearchOption(searchOption);
enumerationOptions.IgnoreInaccessible = true;
var enumerable = Directory.EnumerateFileSystemEntries(path, searchPattern, enumerationOptions);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
/// <inheritdoc cref="Directory.EnumerateFileSystemEntries(string, string)"/>
public static IEnumerable<string> SafeEnumerateFileSystemEntries(this string path, string searchPattern)
{
try
{
SearchOption searchOption = SearchOption.TopDirectoryOnly;
var enumerationOptions = FromSearchOption(searchOption);
enumerationOptions.IgnoreInaccessible = true;
var enumerable = Directory.EnumerateFileSystemEntries(path, searchPattern, enumerationOptions);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
/// <inheritdoc cref="Directory.EnumerateFileSystemEntries(string, string, SearchOption)"/>
public static IEnumerable<string> SafeEnumerateFileSystemEntries(this string path, string searchPattern, SearchOption searchOption)
{
try
{
var enumerationOptions = FromSearchOption(searchOption);
enumerationOptions.IgnoreInaccessible = true;
var enumerable = Directory.EnumerateFileSystemEntries(path, searchPattern, enumerationOptions);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
/// <summary>Initializes a new instance of the <see cref="EnumerationOptions" /> class with the recommended default options.</summary>
/// <see href="https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/IO/EnumerationOptions.cs#L42"</remarks>
private static EnumerationOptions FromSearchOption(SearchOption searchOption)
{
if ((searchOption != SearchOption.TopDirectoryOnly) && (searchOption != SearchOption.AllDirectories))
throw new ArgumentOutOfRangeException(nameof(searchOption));
return searchOption == SearchOption.AllDirectories
? new EnumerationOptions { RecurseSubdirectories = true, MatchType = MatchType.Win32, AttributesToSkip = 0, IgnoreInaccessible = false }
: new EnumerationOptions { MatchType = MatchType.Win32, AttributesToSkip = 0, IgnoreInaccessible = false };
}
#endif
#endregion
}
}

View File

@@ -0,0 +1,176 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
namespace SabreTools.IO.Extensions
{
/// <summary>
/// Common methods for use during marshalling
/// </summary>
internal static class MarshalHelpers
{
/// <summary>
/// Get an attribute of the requested type
/// </summary>
public static T? GetAttribute<T>(FieldInfo? fi) where T : Attribute
{
// If the field info is invalid
if (fi == null)
return null;
// Get all matching attributes
var attributes = fi.GetCustomAttributes(typeof(T), true);
if (attributes == null || attributes.Length == 0)
return null;
// Get the first attribute that matches
return attributes.First() as T;
}
/// <summary>
/// Get an attribute of the requested type
/// </summary>
public static T? GetAttribute<T>(Type? type) where T : Attribute
{
// If the field info is invalid
if (type == null)
return null;
// Get all matching attributes
var attributes = type.GetCustomAttributes(typeof(T), true);
if (attributes == null || attributes.Length == 0)
return null;
// Get the first attribute that matches
return attributes.First() as T;
}
/// <summary>
/// Determine the layout kind for a type
/// </summary>
public static LayoutKind DetermineLayoutKind(StructLayoutAttribute? layoutAttr, Type type)
{
LayoutKind layoutKind = LayoutKind.Auto;
if (layoutAttr != null)
layoutKind = layoutAttr.Value;
else if (type.IsAutoLayout)
layoutKind = LayoutKind.Auto;
else if (type.IsExplicitLayout)
layoutKind = LayoutKind.Explicit;
else if (type.IsLayoutSequential)
layoutKind = LayoutKind.Sequential;
return layoutKind;
}
/// <summary>
/// Determine the encoding for a type
/// </summary>
public static Encoding DetermineEncoding(StructLayoutAttribute? layoutAttr)
{
return layoutAttr?.CharSet switch
{
CharSet.None => Encoding.ASCII,
CharSet.Ansi => Encoding.ASCII,
CharSet.Unicode => Encoding.Unicode,
CharSet.Auto => Encoding.ASCII, // UTF-8 on Unix
_ => Encoding.ASCII,
};
}
/// <summary>
/// Determine the parent hierarchy for a given type
/// </summary>
/// <remarks>Returns the highest parent as the first element</remarks>
public static IEnumerable<Type> DetermineTypeLineage(Type type)
{
var lineage = new List<Type>();
while (type != typeof(object) && type != typeof(ValueType))
{
lineage.Add(type);
type = type.BaseType ?? typeof(object);
}
lineage.Reverse();
return lineage;
}
/// <summary>
/// Get an ordered set of fields from a type
/// </summary>
/// <remarks>Returns fields from the parents before fields from the type</remarks>
public static FieldInfo[] GetFields(Type type)
{
// Get the type hierarchy for ensuring serialization order
var lineage = DetermineTypeLineage(type);
// Generate the fields by parent first
var fieldsList = new List<FieldInfo>();
foreach (var nextType in lineage)
{
var nextFields = nextType.GetFields();
foreach (var field in nextFields)
{
if (!fieldsList.Any(f => f.Name == field.Name && f.FieldType == field.FieldType))
fieldsList.Add(field);
}
}
return [.. fieldsList];
}
/// <summary>
/// Get the expected array size for a field
/// </summary>
/// <returns>Array size on success, -1 on failure</returns>
public static int GetArrayElementCount(MarshalAsAttribute marshalAsAttr, FieldInfo[] fields, object instance)
{
int elementCount = -1;
if (marshalAsAttr.Value == UnmanagedType.ByValArray)
{
elementCount = marshalAsAttr.SizeConst;
}
else if (marshalAsAttr.Value == UnmanagedType.LPArray)
{
elementCount = marshalAsAttr.SizeConst;
if (marshalAsAttr.SizeParamIndex >= 0)
elementCount = GetLPArraySizeFromField(marshalAsAttr, fields, instance);
}
return elementCount;
}
/// <summary>
/// Get the expected LPArray size from a field
/// </summary>
public static int GetLPArraySizeFromField(MarshalAsAttribute marshalAsAttr, FieldInfo[] fields, object instance)
{
// If the index is invalid
if (marshalAsAttr.SizeParamIndex < 0)
return -1;
// Get the size field
var sizeField = fields[marshalAsAttr.SizeParamIndex];
if (sizeField == null)
return -1;
// Cast based on the field type
return sizeField.GetValue(instance) switch
{
sbyte val => val,
byte val => val,
short val => val,
ushort val => val,
int val => val,
uint val => (int)val,
long val => (int)val,
ulong val => (int)val,
_ => -1,
};
}
}
}

View File

@@ -1,329 +1,9 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace SabreTools.IO.Extensions
{
/// <summary>
/// Extensions for Streams
/// </summary>
/// <remarks>TODO: Add U/Int24 and U/Int48 methods</remarks>
public static class StreamExtensions
{
/// <summary>
/// Read a UInt8 from the stream
/// </summary>
public static byte ReadByteValue(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 1);
return buffer[0];
}
/// <summary>
/// Read a UInt8[] from the stream
/// </summary>
public static byte[] ReadBytes(this Stream stream, int count)
=> ReadToBuffer(stream, count);
/// <summary>
/// Read an Int8 from the stream
/// </summary>
public static sbyte ReadSByte(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 1);
return (sbyte)buffer[0];
}
/// <summary>
/// Read a Char from the stream
/// </summary>
public static char ReadChar(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 1);
return (char)buffer[0];
}
/// <summary>
/// Read an Int16 from the stream
/// </summary>
public static short ReadInt16(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 2);
return BitConverter.ToInt16(buffer, 0);
}
/// <summary>
/// Read an Int16 from the stream in big-endian format
/// </summary>
public static short ReadInt16BigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 2);
Array.Reverse(buffer);
return BitConverter.ToInt16(buffer, 0);
}
/// <summary>
/// Read a UInt16 from the stream
/// </summary>
public static ushort ReadUInt16(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 2);
return BitConverter.ToUInt16(buffer, 0);
}
/// <summary>
/// Read a UInt16 from the stream in big-endian format
/// </summary>
public static ushort ReadUInt16BigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 2);
Array.Reverse(buffer);
return BitConverter.ToUInt16(buffer, 0);
}
/// <summary>
/// Read an Int32 from the stream
/// </summary>
public static int ReadInt32(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 4);
return BitConverter.ToInt32(buffer, 0);
}
/// <summary>
/// Read an Int32 from the stream in big-endian format
/// </summary>
public static int ReadInt32BigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 4);
Array.Reverse(buffer);
return BitConverter.ToInt32(buffer, 0);
}
/// <summary>
/// Read a UInt32 from the stream
/// </summary>
public static uint ReadUInt32(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 4);
return BitConverter.ToUInt32(buffer, 0);
}
/// <summary>
/// Read a UInt32 from the stream in big-endian format
/// </summary>
public static uint ReadUInt32BigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 4);
Array.Reverse(buffer);
return BitConverter.ToUInt32(buffer, 0);
}
/// <summary>
/// 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 in big-endian format
/// </summary>
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)
{
byte[] buffer = ReadToBuffer(stream, 8);
return BitConverter.ToInt64(buffer, 0);
}
/// <summary>
/// Read an Int64 from the stream in big-endian format
/// </summary>
public static long ReadInt64BigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 8);
Array.Reverse(buffer);
return BitConverter.ToInt64(buffer, 0);
}
/// <summary>
/// Read a UInt64 from the stream
/// </summary>
public static ulong ReadUInt64(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 8);
return BitConverter.ToUInt64(buffer, 0);
}
/// <summary>
/// Read a UInt64 from the stream in big-endian format
/// </summary>
public static ulong ReadUInt64BigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 8);
Array.Reverse(buffer);
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 in big-endian format
/// </summary>
public static double ReadDoubleBigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 8);
Array.Reverse(buffer);
return BitConverter.ToDouble(buffer, 0);
}
/// <summary>
/// Read a Guid from the stream
/// </summary>
public static Guid ReadGuid(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 16);
return new Guid(buffer);
}
/// <summary>
/// Read a Guid from the stream in big-endian format
/// </summary>
public static Guid ReadGuidBigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 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 stream
/// </summary>
public static Int128 ReadInt128(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 16);
return new Int128(BitConverter.ToUInt64(buffer, 0), BitConverter.ToUInt64(buffer, 8));
}
/// <summary>
/// Read an Int128 from the stream in big-endian format
/// </summary>
public static Int128 ReadInt128BigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 16);
Array.Reverse(buffer);
return new Int128(BitConverter.ToUInt64(buffer, 0), BitConverter.ToUInt64(buffer, 8));
}
/// <summary>
/// Read a UInt128 from the stream
/// </summary>
public static UInt128 ReadUInt128(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 16);
return new UInt128(BitConverter.ToUInt64(buffer, 0), BitConverter.ToUInt64(buffer, 8));
}
/// <summary>
/// Read a UInt128 from the stream in big-endian format
/// </summary>
public static UInt128 ReadUInt128BigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 16);
Array.Reverse(buffer);
return new UInt128(BitConverter.ToUInt64(buffer, 0), BitConverter.ToUInt64(buffer, 8));
}
#endif
/// <summary>
/// Read a null-terminated string from the stream
/// </summary>
public static string? ReadString(this Stream stream)
=> stream.ReadString(Encoding.Default);
/// <summary>
/// Read a null-terminated string from the stream
/// </summary>
public static string? ReadString(this Stream stream, Encoding encoding)
{
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))
{
tempBuffer.AddRange(buffer);
}
return encoding.GetString([.. tempBuffer]);
}
/// <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 Stream stream)
=> stream.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 Stream stream, Encoding encoding)
{
if (stream.Position >= stream.Length)
return null;
var bytes = new List<byte>();
bool openQuote = false;
while (stream.Position < stream.Length)
{
// Read the byte value
byte b = stream.ReadByteValue();
// 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>
/// Seek to a specific point in the stream, if possible
/// </summary>
@@ -362,27 +42,5 @@ namespace SabreTools.IO.Extensions
return -1;
}
}
/// <summary>
/// Read a number of bytes from the current Stream to a buffer
/// </summary>
private static byte[] ReadToBuffer(Stream stream, 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 [];
// Handle the general case, forcing a read of the correct length
byte[] buffer = new byte[length];
int read = stream.Read(buffer, 0, length);
if (read < length)
throw new EndOfStreamException(nameof(stream));
return buffer;
}
}
}
}

View File

@@ -0,0 +1,864 @@
using System;
using System.Collections.Generic;
using System.IO;
#if NET7_0_OR_GREATER
using System.Numerics;
#endif
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
namespace SabreTools.IO.Extensions
{
/// <summary>
/// Extensions for Streams
/// </summary>
/// TODO: Handle proper negative values for Int24 and Int48
public static class StreamReaderExtensions
{
/// <summary>
/// Read a UInt8 from the stream
/// </summary>
public static byte ReadByteValue(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 1);
return buffer[0];
}
/// <summary>
/// Read a UInt8[] from the stream
/// </summary>
public static byte[] ReadBytes(this Stream stream, int count)
=> ReadToBuffer(stream, count);
/// <summary>
/// Read an Int8 from the stream
/// </summary>
public static sbyte ReadSByte(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 1);
return (sbyte)buffer[0];
}
/// <summary>
/// Read a Char from the stream
/// </summary>
public static char ReadChar(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 1);
return (char)buffer[0];
}
/// <summary>
/// Read an Int16 from the stream
/// </summary>
public static short ReadInt16(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 2);
return BitConverter.ToInt16(buffer, 0);
}
/// <summary>
/// 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);
Array.Reverse(buffer);
return BitConverter.ToInt16(buffer, 0);
}
/// <summary>
/// Read a UInt16 from the stream
/// </summary>
public static ushort ReadUInt16(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 2);
return BitConverter.ToUInt16(buffer, 0);
}
/// <summary>
/// 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);
Array.Reverse(buffer);
return BitConverter.ToUInt16(buffer, 0);
}
// Half was introduced in net5.0 but doesn't have a BitConverter implementation until net6.0
#if NET6_0_OR_GREATER
/// <summary>
/// Read a Half from the stream
/// </summary>
public static Half ReadHalf(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 2);
return BitConverter.ToHalf(buffer, 0);
}
/// <summary>
/// Read a Half from the stream
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static Half ReadHalfBigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 2);
Array.Reverse(buffer);
return BitConverter.ToHalf(buffer, 0);
}
#endif
/// <summary>
/// Read an Int24 encoded as an Int32
/// </summary>
public static int ReadInt24(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 3);
byte[] padded = new byte[4];
Array.Copy(buffer, padded, 3);
return BitConverter.ToInt32(padded, 0);
}
/// <summary>
/// Read an Int24 encoded as an Int32
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static int ReadInt24BigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 3);
Array.Reverse(buffer);
byte[] padded = new byte[4];
Array.Copy(buffer, padded, 3);
return BitConverter.ToInt32(padded, 0);
}
/// <summary>
/// Read a UInt24 encoded as a UInt32
/// </summary>
public static uint ReadUInt24(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 3);
byte[] padded = new byte[4];
Array.Copy(buffer, padded, 3);
return BitConverter.ToUInt32(padded, 0);
}
/// <summary>
/// Read a UInt24 encoded as a UInt32
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static uint ReadUInt24BigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 3);
Array.Reverse(buffer);
byte[] padded = new byte[4];
Array.Copy(buffer, padded, 3);
return BitConverter.ToUInt32(padded, 0);
}
/// <summary>
/// Read an Int32 from the stream
/// </summary>
public static int ReadInt32(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 4);
return BitConverter.ToInt32(buffer, 0);
}
/// <summary>
/// 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);
Array.Reverse(buffer);
return BitConverter.ToInt32(buffer, 0);
}
/// <summary>
/// Read a UInt32 from the stream
/// </summary>
public static uint ReadUInt32(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 4);
return BitConverter.ToUInt32(buffer, 0);
}
/// <summary>
/// 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);
Array.Reverse(buffer);
return BitConverter.ToUInt32(buffer, 0);
}
/// <summary>
/// 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 Int48 encoded as an Int64
/// </summary>
public static long ReadInt48(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 6);
byte[] padded = new byte[8];
Array.Copy(buffer, padded, 6);
return BitConverter.ToInt64(padded, 0);
}
/// <summary>
/// Read an Int48 encoded as an Int64
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static long ReadInt48BigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 6);
Array.Reverse(buffer);
byte[] padded = new byte[8];
Array.Copy(buffer, padded, 6);
return BitConverter.ToInt64(padded, 0);
}
/// <summary>
/// Read a UInt48 encoded as a UInt64
/// </summary>
public static ulong ReadUInt48(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 6);
byte[] padded = new byte[8];
Array.Copy(buffer, padded, 6);
return BitConverter.ToUInt64(padded, 0);
}
/// <summary>
/// Read a UInt48 encoded as a UInt64
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static ulong ReadUInt48BigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 6);
Array.Reverse(buffer);
byte[] padded = new byte[8];
Array.Copy(buffer, padded, 6);
return BitConverter.ToUInt64(padded, 0);
}
/// <summary>
/// Read an Int64 from the stream
/// </summary>
public static long ReadInt64(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 8);
return BitConverter.ToInt64(buffer, 0);
}
/// <summary>
/// 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);
Array.Reverse(buffer);
return BitConverter.ToInt64(buffer, 0);
}
/// <summary>
/// Read a UInt64 from the stream
/// </summary>
public static ulong ReadUInt64(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 8);
return BitConverter.ToUInt64(buffer, 0);
}
/// <summary>
/// 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);
Array.Reverse(buffer);
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 lo = BitConverter.ToInt32(buffer, 0);
int mid = BitConverter.ToInt32(buffer, 4);
int hi = BitConverter.ToInt32(buffer, 8);
int flags = BitConverter.ToInt32(buffer, 12);
return new decimal([lo, mid, hi, flags]);
}
/// <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 lo = BitConverter.ToInt32(buffer, 0);
int mid = BitConverter.ToInt32(buffer, 4);
int hi = BitConverter.ToInt32(buffer, 8);
int flags = BitConverter.ToInt32(buffer, 12);
return new decimal([lo, mid, hi, flags]);
}
/// <summary>
/// Read a Guid from the stream
/// </summary>
public static Guid ReadGuid(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 16);
return new Guid(buffer);
}
/// <summary>
/// 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);
Array.Reverse(buffer);
return new Guid(buffer);
}
#if NET7_0_OR_GREATER
/// <summary>
/// Read an Int128 from the stream
/// </summary>
public static Int128 ReadInt128(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 16);
return (Int128)new BigInteger(buffer);
}
/// <summary>
/// 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);
Array.Reverse(buffer);
return (Int128)new BigInteger(buffer);
}
/// <summary>
/// Read a UInt128 from the stream
/// </summary>
public static UInt128 ReadUInt128(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 16);
return (UInt128)new BigInteger(buffer);
}
/// <summary>
/// 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);
Array.Reverse(buffer);
return (UInt128)new BigInteger(buffer);
}
#endif
/// <summary>
/// Read a null-terminated string from the stream
/// </summary>
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.UTF8))
return stream.ReadNullTerminatedUTF8String();
else if (encoding.Equals(Encoding.Unicode))
return stream.ReadNullTerminatedUnicodeString();
else if (encoding.Equals(Encoding.UTF32))
return stream.ReadNullTerminatedUTF32String();
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 ASCII string from the stream
/// </summary>
public static string? ReadNullTerminatedAnsiString(this Stream stream)
{
if (stream.Position >= stream.Length)
return null;
byte[] buffer = ReadUntilNull1Byte(stream);
return Encoding.ASCII.GetString(buffer);
}
/// <summary>
/// Read a null-terminated UTF-8 string from the stream
/// </summary>
public static string? ReadNullTerminatedUTF8String(this Stream stream)
{
if (stream.Position >= stream.Length)
return null;
byte[] buffer = ReadUntilNull1Byte(stream);
return Encoding.UTF8.GetString(buffer);
}
/// <summary>
/// Read a null-terminated UTF-16 (Unicode) string from the stream
/// </summary>
public static string? ReadNullTerminatedUnicodeString(this Stream stream)
{
if (stream.Position >= stream.Length)
return null;
byte[] buffer = ReadUntilNull2Byte(stream);
return Encoding.Unicode.GetString(buffer);
}
/// <summary>
/// Read a null-terminated UTF-32 string from the stream
/// </summary>
public static string? ReadNullTerminatedUTF32String(this Stream stream)
{
if (stream.Position >= stream.Length)
return null;
byte[] buffer = ReadUntilNull4Byte(stream);
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 * 2) >= stream.Length)
return null;
byte[] buffer = stream.ReadBytes(size * 2);
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 Stream stream)
=> stream.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 Stream stream, Encoding encoding)
{
if (stream.Position >= stream.Length)
return null;
var bytes = new List<byte>();
bool openQuote = false;
while (stream.Position < stream.Length)
{
// Read the byte value
byte b = stream.ReadByteValue();
// 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 stream
/// </summary>
/// <remarks>
/// This method is different than standard marshalling in a few notable ways:
/// - Strings are read by value, not by reference
/// - Complex objects are read by value, not by reference
/// - Enumeration values are read by the underlying value type
/// - Arrays of the above are handled sequentially as above
/// - Inherited fields from parents are deserialized BEFORE fields in the child
/// </remarks>
public static T? ReadType<T>(this Stream stream)
=> (T?)stream.ReadType(typeof(T));
/// <summary>
/// Read a <paramref name="type"/> from the stream
/// </summary>
/// <remarks>
/// This method is different than standard marshalling in a few notable ways:
/// - Strings are read by value, not by reference
/// - Complex objects are read by value, not by reference
/// - Enumeration values are read by the underlying value type
/// - Arrays of the above are handled sequentially as above
/// - Inherited fields from parents are deserialized BEFORE fields in the child
/// </remarks>
public static object? ReadType(this Stream stream, Type type)
{
// Handle special struct cases
if (type == typeof(Guid))
return stream.ReadGuid();
#if NET6_0_OR_GREATER
else if (type == typeof(Half))
return stream.ReadHalf();
#endif
#if NET7_0_OR_GREATER
else if (type == typeof(Int128))
return stream.ReadInt128();
else if (type == typeof(UInt128))
return stream.ReadUInt128();
#endif
if (type.IsClass || (type.IsValueType && !type.IsEnum && !type.IsPrimitive))
return ReadComplexType(stream, type);
else if (type.IsValueType && type.IsEnum)
return ReadNormalType(stream, Enum.GetUnderlyingType(type));
else
return ReadNormalType(stream, type);
}
/// <summary>
/// Read a <paramref name="type"/> from the stream
/// </summary>
private static object? ReadNormalType(Stream stream, Type type)
{
try
{
int typeSize = Marshal.SizeOf(type);
byte[] buffer = ReadToBuffer(stream, typeSize);
var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
var data = Marshal.PtrToStructure(handle.AddrOfPinnedObject(), type);
handle.Free();
return data;
}
catch
{
return null;
}
}
/// <summary>
/// Read a <paramref name="type"/> from the stream
/// </summary>
private static object? ReadComplexType(Stream stream, Type type)
{
try
{
// Try to create an instance of the type
var instance = Activator.CreateInstance(type);
if (instance == null)
return null;
// Get the layout information
var layoutAttr = MarshalHelpers.GetAttribute<StructLayoutAttribute>(type);
LayoutKind layoutKind = MarshalHelpers.DetermineLayoutKind(layoutAttr, type);
Encoding encoding = MarshalHelpers.DetermineEncoding(layoutAttr);
// Cache the current offset
long currentOffset = stream.Position;
// Generate the fields by parent first
var fields = MarshalHelpers.GetFields(type);
// Loop through the fields and set them
foreach (var fi in fields)
{
// If we have an explicit layout, move accordingly
if (layoutKind == LayoutKind.Explicit)
{
var fieldOffset = MarshalHelpers.GetAttribute<FieldOffsetAttribute>(fi);
stream.Seek(currentOffset + fieldOffset?.Value ?? 0, SeekOrigin.Begin);
}
SetField(stream, encoding, fields, instance, fi);
}
return instance;
}
catch
{
return null;
}
}
/// <summary>
/// Set a single field on an object
/// </summary>
private static void SetField(Stream stream, Encoding encoding, FieldInfo[] fields, object instance, FieldInfo fi)
{
if (fi.FieldType.IsAssignableFrom(typeof(string)))
{
var value = ReadStringType(stream, encoding, instance, fi);
fi.SetValue(instance, value);
}
else if (fi.FieldType.IsArray)
{
var value = ReadArrayType(stream, fields, instance, fi);
fi.SetValue(instance, Convert.ChangeType(value, fi.FieldType));
}
else
{
var value = stream.ReadType(fi.FieldType);
fi.SetValue(instance, value);
}
}
/// <summary>
/// Read an array type field for an object
/// </summary>
private static Array ReadArrayType(Stream stream, FieldInfo[] fields, object instance, FieldInfo fi)
{
var marshalAsAttr = MarshalHelpers.GetAttribute<MarshalAsAttribute>(fi);
if (marshalAsAttr == null)
return new object[0];
// Get the number of elements expected
int elementCount = MarshalHelpers.GetArrayElementCount(marshalAsAttr, fields, instance);
if (elementCount < 0)
return new object[0];
// Get the item type for the array
Type elementType = fi.FieldType.GetElementType() ?? typeof(object);
// Loop through and build the array
Array arr = Array.CreateInstance(elementType, elementCount);
for (int i = 0; i < elementCount; i++)
{
var value = ReadType(stream, elementType);
arr.SetValue(value, i);
}
// Return the built array
return arr;
}
/// <summary>
/// Read a string type field for an object
/// </summary>
private static string? ReadStringType(Stream stream, Encoding encoding, object instance, FieldInfo fi)
{
var marshalAsAttr = MarshalHelpers.GetAttribute<MarshalAsAttribute>(fi);
switch (marshalAsAttr?.Value)
{
case UnmanagedType.AnsiBStr:
return stream.ReadPrefixedAnsiString();
case UnmanagedType.BStr:
case UnmanagedType.TBStr: // Technically distinct; returns char[] instead
return stream.ReadPrefixedUnicodeString();
case UnmanagedType.ByValTStr:
int byvalLength = marshalAsAttr!.SizeConst;
byte[] byvalBytes = stream.ReadBytes(byvalLength);
return encoding.GetString(byvalBytes);
case UnmanagedType.LPStr:
case UnmanagedType.LPTStr: // Technically distinct; possibly not null-terminated
case null:
return stream.ReadNullTerminatedAnsiString();
#if NET472_OR_GREATER || NETCOREAPP
case UnmanagedType.LPUTF8Str:
return stream.ReadNullTerminatedUTF8String();
#endif
case UnmanagedType.LPWStr:
return stream.ReadNullTerminatedUnicodeString();
// No other string types are recognized
default:
return null;
}
}
/// <summary>
/// Read bytes until a 1-byte null terminator is found
/// </summary>
private static byte[] ReadUntilNull1Byte(Stream stream)
{
var bytes = new List<byte>();
while (stream.Position < stream.Length)
{
byte next = stream.ReadByteValue();
if (next == 0x00)
break;
bytes.Add(next);
}
return [.. bytes];
}
/// <summary>
/// Read bytes until a 2-byte null terminator is found
/// </summary>
private static byte[] ReadUntilNull2Byte(Stream stream)
{
var bytes = new List<byte>();
while (stream.Position < stream.Length)
{
ushort next = stream.ReadUInt16();
if (next == 0x0000)
break;
bytes.AddRange(BitConverter.GetBytes(next));
}
return [.. bytes];
}
/// <summary>
/// Read bytes until a 4-byte null terminator is found
/// </summary>
private static byte[] ReadUntilNull4Byte(Stream stream)
{
var bytes = new List<byte>();
while (stream.Position < stream.Length)
{
uint next = stream.ReadUInt32();
if (next == 0x00000000)
break;
bytes.AddRange(BitConverter.GetBytes(next));
}
return [.. bytes];
}
/// <summary>
/// Read a number of bytes from the stream to a buffer
/// </summary>
private static byte[] ReadToBuffer(Stream stream, 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 [];
// Handle the general case, forcing a read of the correct length
byte[] buffer = new byte[length];
int read = stream.Read(buffer, 0, length);
if (read < length)
throw new EndOfStreamException($"Requested to read {nameof(length)} bytes from {nameof(stream)}, {read} returned");
return buffer;
}
}
}

View File

@@ -0,0 +1,820 @@
using System;
using System.IO;
#if NET7_0_OR_GREATER
using System.Numerics;
#endif
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
namespace SabreTools.IO.Extensions
{
/// <summary>
/// Extensions for Streams
/// </summary>
/// TODO: Handle proper negative values for Int24 and Int48
public static class StreamWriterExtensions
{
/// <summary>
/// Write a UInt8
/// </summary>
public static bool Write(this Stream stream, byte value)
=> WriteFromBuffer(stream, [value]);
/// <summary>
/// Write a UInt8[]
/// </summary>
public static bool Write(this Stream stream, byte[] value)
=> WriteFromBuffer(stream, value);
/// <summary>
/// Write a UInt8[]
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this Stream stream, byte[] value)
{
Array.Reverse(value);
return WriteFromBuffer(stream, value);
}
/// <summary>
/// Write an Int8
/// </summary>
public static bool Write(this Stream stream, sbyte value)
=> WriteFromBuffer(stream, [(byte)value]);
/// <summary>
/// Write a Char
/// </summary>
public static bool Write(this Stream stream, char value)
{
byte[] buffer = BitConverter.GetBytes(value);
return WriteFromBuffer(stream, buffer);
}
/// <summary>
/// Write a Char with an Encoding
/// </summary>
public static bool Write(this Stream stream, char value, Encoding encoding)
{
byte[] buffer = encoding.GetBytes($"{value}");
return WriteFromBuffer(stream, buffer);
}
/// <summary>
/// Write an Int16
/// </summary>
public static bool Write(this Stream stream, short value)
{
byte[] buffer = BitConverter.GetBytes(value);
return WriteFromBuffer(stream, buffer);
}
/// <summary>
/// Write an Int16
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this Stream stream, short value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
return WriteFromBuffer(stream, buffer);
}
/// <summary>
/// Write a UInt16
/// </summary>
public static bool Write(this Stream stream, ushort value)
{
byte[] buffer = BitConverter.GetBytes(value);
return WriteFromBuffer(stream, buffer);
}
/// <summary>
/// Write a UInt16
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this Stream stream, ushort value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
return WriteFromBuffer(stream, buffer);
}
// Half was introduced in net5.0 but doesn't have a BitConverter implementation until net6.0
#if NET6_0_OR_GREATER
/// <summary>
/// Write a Half
/// </summary>
public static bool Write(this Stream stream, Half value)
{
byte[] buffer = BitConverter.GetBytes(value);
return WriteFromBuffer(stream, buffer);
}
/// <summary>
/// Write a Half
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this Stream stream, Half value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
return WriteFromBuffer(stream, buffer);
}
#endif
/// <summary>
/// Write an Int32 as an Int24
/// </summary>
/// <remarks>Throws away top byte</remarks>
public static bool WriteAsInt24(this Stream stream, int value)
{
byte[] buffer = BitConverter.GetBytes(value);
byte[] reduced = new byte[3];
Array.Copy(buffer, reduced, 3);
return WriteFromBuffer(stream, reduced);
}
/// <summary>
/// Write an Int32 as an Int24
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
/// <remarks>Throws away top byte</remarks>
public static bool WriteAsInt24BigEndian(this Stream stream, int value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
byte[] reduced = new byte[3];
Array.Copy(buffer, 1, reduced, 0, 3);
return WriteFromBuffer(stream, reduced);
}
/// <summary>
/// Write a UInt32 as a UInt24
/// </summary>
/// <remarks>Throws away top byte</remarks>
public static bool WriteAsUInt24(this Stream stream, uint value)
{
byte[] buffer = BitConverter.GetBytes(value);
byte[] reduced = new byte[3];
Array.Copy(buffer, reduced, 3);
return WriteFromBuffer(stream, reduced);
}
/// <summary>
/// Write a UInt32 as a UInt24
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
/// <remarks>Throws away top byte</remarks>
public static bool WriteAsUInt24BigEndian(this Stream stream, uint value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
byte[] reduced = new byte[3];
Array.Copy(buffer, 1, reduced, 0, 3);
return WriteFromBuffer(stream, reduced);
}
/// <summary>
/// Write an Int32
/// </summary>
public static bool Write(this Stream stream, int value)
{
byte[] buffer = BitConverter.GetBytes(value);
return WriteFromBuffer(stream, buffer);
}
/// <summary>
/// Write an Int32
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this Stream stream, int value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
return WriteFromBuffer(stream, buffer);
}
/// <summary>
/// Write a UInt32
/// </summary>
public static bool Write(this Stream stream, uint value)
{
byte[] buffer = BitConverter.GetBytes(value);
return WriteFromBuffer(stream, buffer);
}
/// <summary>
/// Write a UInt32
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this Stream stream, uint value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
return WriteFromBuffer(stream, buffer);
}
/// <summary>
/// Write a Single
/// </summary>
public static bool Write(this Stream stream, float value)
{
byte[] buffer = BitConverter.GetBytes(value);
return WriteFromBuffer(stream, buffer);
}
/// <summary>
/// Write a Single
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this Stream stream, float value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
return WriteFromBuffer(stream, buffer);
}
/// <summary>
/// Write an Int64 as an Int48
/// </summary>
/// <remarks>Throws away top 2 bytes</remarks>
public static bool WriteAsInt48(this Stream stream, long value)
{
byte[] buffer = BitConverter.GetBytes(value);
byte[] reduced = new byte[6];
Array.Copy(buffer, reduced, 6);
return WriteFromBuffer(stream, reduced);
}
/// <summary>
/// Write an Int64 as an Int48
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
/// <remarks>Throws away top 2 bytes</remarks>
public static bool WriteAsInt48BigEndian(this Stream stream, long value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
byte[] reduced = new byte[6];
Array.Copy(buffer, 2, reduced, 0, 6);
return WriteFromBuffer(stream, reduced);
}
/// <summary>
/// Write a UInt64 as a UInt48
/// </summary>
/// <remarks>Throws away top 2 bytes</remarks>
public static bool WriteAsUInt48(this Stream stream, ulong value)
{
byte[] buffer = BitConverter.GetBytes(value);
byte[] reduced = new byte[6];
Array.Copy(buffer, reduced, 6);
return WriteFromBuffer(stream, reduced);
}
/// <summary>
/// Write a UInt64 as a UInt48
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
/// <remarks>Throws away top 2 bytes</remarks>
public static bool WriteAsUInt48BigEndian(this Stream stream, ulong value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
byte[] reduced = new byte[6];
Array.Copy(buffer, 2, reduced, 0, 6);
return WriteFromBuffer(stream, reduced);
}
/// <summary>
/// Write an Int64
/// </summary>
public static bool Write(this Stream stream, long value)
{
byte[] buffer = BitConverter.GetBytes(value);
return WriteFromBuffer(stream, buffer);
}
/// <summary>
/// Write an Int64
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this Stream stream, long value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
return WriteFromBuffer(stream, buffer);
}
/// <summary>
/// Write a UInt64
/// </summary>
public static bool Write(this Stream stream, ulong value)
{
byte[] buffer = BitConverter.GetBytes(value);
return WriteFromBuffer(stream, buffer);
}
/// <summary>
/// Write a UInt64
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this Stream stream, ulong value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
return WriteFromBuffer(stream, buffer);
}
/// <summary>
/// Write a Double
/// </summary>
public static bool Write(this Stream stream, double value)
{
byte[] buffer = BitConverter.GetBytes(value);
return WriteFromBuffer(stream, buffer);
}
/// <summary>
/// Write a Double
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this Stream stream, double value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
return WriteFromBuffer(stream, buffer);
}
/// <summary>
/// Write a Decimal and increment the pointer to an array
/// </summary>
public static bool Write(this Stream stream, decimal value)
{
int[] bits = decimal.GetBits(value);
byte[] lo = BitConverter.GetBytes(bits[0]);
byte[] mid = BitConverter.GetBytes(bits[1]);
byte[] hi = BitConverter.GetBytes(bits[2]);
byte[] flags = BitConverter.GetBytes(bits[3]);
byte[] buffer = new byte[16];
Array.Copy(lo, 0, buffer, 0, 4);
Array.Copy(mid, 0, buffer, 4, 4);
Array.Copy(hi, 0, buffer, 8, 4);
Array.Copy(flags, 0, buffer, 12, 4);
return WriteFromBuffer(stream, buffer);
}
/// <summary>
/// Write a Decimal and increment the pointer to an array
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this Stream stream, decimal value)
{
int[] bits = decimal.GetBits(value);
byte[] lo = BitConverter.GetBytes(bits[0]);
byte[] mid = BitConverter.GetBytes(bits[1]);
byte[] hi = BitConverter.GetBytes(bits[2]);
byte[] flags = BitConverter.GetBytes(bits[3]);
byte[] buffer = new byte[16];
Array.Copy(lo, 0, buffer, 0, 4);
Array.Copy(mid, 0, buffer, 4, 4);
Array.Copy(hi, 0, buffer, 8, 4);
Array.Copy(flags, 0, buffer, 12, 4);
Array.Reverse(buffer);
return WriteFromBuffer(stream, buffer);
}
/// <summary>
/// Write a Guid
/// </summary>
public static bool Write(this Stream stream, Guid value)
{
byte[] buffer = value.ToByteArray();
return WriteFromBuffer(stream, buffer);
}
/// <summary>
/// Write a Guid
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this Stream stream, Guid value)
{
byte[] buffer = value.ToByteArray();
Array.Reverse(buffer);
return WriteFromBuffer(stream, buffer);
}
#if NET7_0_OR_GREATER
/// <summary>
/// Write an Int128
/// </summary>
public static bool Write(this Stream stream, Int128 value)
{
byte[] buffer = ((BigInteger)value).ToByteArray();
byte[] padded = new byte[16];
Array.Copy(buffer, 0, padded, 16 - buffer.Length, buffer.Length);
return WriteFromBuffer(stream, padded);
}
/// <summary>
/// Write an Int128
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this Stream stream, Int128 value)
{
byte[] buffer = ((BigInteger)value).ToByteArray();
Array.Reverse(buffer);
byte[] padded = new byte[16];
Array.Copy(buffer, 0, padded, 16 - buffer.Length, buffer.Length);
return WriteFromBuffer(stream, padded);
}
/// <summary>
/// Write a UInt128
/// </summary>
public static bool Write(this Stream stream, UInt128 value)
{
byte[] buffer = ((BigInteger)value).ToByteArray();
byte[] padded = new byte[16];
Array.Copy(buffer, 0, padded, 16 - buffer.Length, buffer.Length);
return WriteFromBuffer(stream, padded);
}
/// <summary>
/// Write a UInt128
/// </summary>
/// <remarks>Writes in big-endian format</remarks>
public static bool WriteBigEndian(this Stream stream, UInt128 value)
{
byte[] buffer = ((BigInteger)value).ToByteArray();
Array.Reverse(buffer);
byte[] padded = new byte[16];
Array.Copy(buffer, 0, padded, 16 - buffer.Length, buffer.Length);
return WriteFromBuffer(stream, padded);
}
#endif
/// <summary>
/// Write a null-terminated string to the stream
/// </summary>
public static bool WriteNullTerminatedString(this Stream stream, string? value, Encoding encoding)
{
// If the value is null
if (value == null)
return false;
// Add the null terminator and write
value += "\0";
byte[] buffer = encoding.GetBytes(value);
return WriteFromBuffer(stream, buffer);
}
/// <summary>
/// Write a null-terminated ASCII string to the stream
/// </summary>
public static bool WriteNullTerminatedAnsiString(this Stream stream, string? value)
=> stream.WriteNullTerminatedString(value, Encoding.ASCII);
/// <summary>
/// Write a null-terminated UTF-8 string to the stream
/// </summary>
public static bool WriteNullTerminatedUTF8String(this Stream stream, string? value)
=> stream.WriteNullTerminatedString(value, Encoding.UTF8);
/// <summary>
/// Write a null-terminated UTF-16 (Unicode) string to the stream
/// </summary>
public static bool WriteNullTerminatedUnicodeString(this Stream stream, string? value)
=> stream.WriteNullTerminatedString(value, Encoding.Unicode);
/// <summary>
/// Write a null-terminated UTF-32 string to the stream
/// </summary>
public static bool WriteNullTerminatedUTF32String(this Stream stream, string? value)
=> stream.WriteNullTerminatedString(value, Encoding.UTF32);
//// <summary>
/// Write a byte-prefixed ASCII string to the stream
/// </summary>
public static bool WritePrefixedAnsiString(this Stream stream, string? value)
{
// If the value is null
if (value == null)
return false;
// Get the buffer
byte[] buffer = Encoding.ASCII.GetBytes(value);
// Write the length as a byte
if (!stream.Write((byte)buffer.Length))
return false;
// Write the buffer
return WriteFromBuffer(stream, buffer);
}
/// <summary>
/// Write a ushort-prefixed Unicode string to the stream
/// </summary>
public static bool WritePrefixedUnicodeString(this Stream stream, string? value)
{
// If the value is null
if (value == null)
return false;
// Get the buffer
byte[] buffer = Encoding.Unicode.GetBytes(value);
// Write the length as a ushort
if (!stream.Write((ushort)buffer.Length))
return false;
// Write the buffer
return WriteFromBuffer(stream, buffer);
}
/// <summary>
/// Write a string that is terminated by a newline but contains a quoted portion that
/// may also contain a newline to the stream
/// </summary>
public static bool WriteQuotedString(this Stream stream, string? value)
=> stream.WriteQuotedString(value, Encoding.UTF8);
/// <summary>
/// Write a string that is terminated by a newline but contains a quoted portion that
/// may also contain a newline to the stream
/// </summary>
public static bool WriteQuotedString(this Stream stream, string? value, Encoding encoding)
{
// If the value is null
if (value == null)
return false;
// Write without the null terminator
byte[] buffer = encoding.GetBytes(value);
return WriteFromBuffer(stream, buffer);
}
/// <summary>
/// Write a <typeparamref name="T"/> to the stream
/// </summary>
/// <remarks>
/// This method is different than standard marshalling in a few notable ways:
/// - Strings are written by value, not by reference
/// - Complex objects are written by value, not by reference
/// - Enumeration values are written by the underlying value type
/// - Arrays of the above are handled sequentially as above
/// - Inherited fields from parents are serialized BEFORE fields in the child
/// </remarks>
public static bool WriteType<T>(this Stream stream, T? value)
=> stream.WriteType(value, typeof(T));
/// <summary>
/// Write a <typeparamref name="T"/> to the stream
/// </summary>
/// <remarks>
/// This method is different than standard marshalling in a few notable ways:
/// - Strings are written by value, not by reference
/// - Complex objects are written by value, not by reference
/// - Enumeration values are written by the underlying value type
/// - Arrays of the above are handled sequentially as above
/// - Inherited fields from parents are serialized BEFORE fields in the child
/// </remarks>
public static bool WriteType(this Stream stream, object? value, Type type)
{
// Null values cannot be written
if (value == null)
return true;
// Handle special struct cases
if (type == typeof(Guid))
return stream.Write((Guid)value);
#if NET6_0_OR_GREATER
else if (type == typeof(Half))
return stream.Write((Half)value);
#endif
#if NET7_0_OR_GREATER
else if (type == typeof(Int128))
return stream.Write((Int128)value);
else if (type == typeof(UInt128))
return stream.Write((UInt128)value);
#endif
if (type.IsClass || (type.IsValueType && !type.IsEnum && !type.IsPrimitive))
return WriteComplexType(stream, value, type);
else if (type.IsValueType && type.IsEnum)
return WriteNormalType(stream, value, Enum.GetUnderlyingType(type));
else
return WriteNormalType(stream, value, type);
}
/// <summary>
/// Read a <paramref name="type"/> from the stream
/// </summary>
private static bool WriteNormalType(Stream stream, object? value, Type type)
{
try
{
// Null values cannot be written
if (value == null)
return true;
int typeSize = Marshal.SizeOf(type);
if (value.GetType() != type)
value = Convert.ChangeType(value, type);
var buffer = new byte[typeSize];
var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
Marshal.StructureToPtr(value, handle.AddrOfPinnedObject(), false);
handle.Free();
return WriteFromBuffer(stream, buffer);
}
catch
{
return false;
}
}
/// <summary>
/// Read a <paramref name="type"/> from the stream
/// </summary>
private static bool WriteComplexType(Stream stream, object? value, Type type)
{
try
{
// Null values cannot be written
if (value == null)
return true;
// Get the layout information
var layoutAttr = MarshalHelpers.GetAttribute<StructLayoutAttribute>(type);
LayoutKind layoutKind = MarshalHelpers.DetermineLayoutKind(layoutAttr, type);
Encoding encoding = MarshalHelpers.DetermineEncoding(layoutAttr);
// Cache the current offset
long currentOffset = stream.Position;
// Generate the fields by parent first
var fields = MarshalHelpers.GetFields(type);
// Loop through the fields and set them
foreach (var fi in fields)
{
// If we have an explicit layout, move accordingly
if (layoutKind == LayoutKind.Explicit)
{
var fieldOffset = MarshalHelpers.GetAttribute<FieldOffsetAttribute>(fi);
stream.Seek(currentOffset + fieldOffset?.Value ?? 0, SeekOrigin.Begin);
}
if (!GetField(stream, encoding, fields, value, fi))
return false;
}
return true;
}
catch
{
return false;
}
}
/// <summary>
/// Write a single field from an object
/// </summary>
private static bool GetField(Stream stream, Encoding encoding, FieldInfo[] fields, object instance, FieldInfo fi)
{
if (fi.FieldType.IsAssignableFrom(typeof(string)))
{
return WriteStringType(stream, encoding, instance, fi);
}
else if (fi.FieldType.IsArray)
{
return WriteArrayType(stream, fields, instance, fi);
}
else
{
var value = fi.GetValue(instance);
return stream.WriteType(value, fi.FieldType);
}
}
/// <summary>
/// Write an array type field from an object
/// </summary>
private static bool WriteArrayType(Stream stream, FieldInfo[] fields, object instance, FieldInfo fi)
{
var marshalAsAttr = MarshalHelpers.GetAttribute<MarshalAsAttribute>(fi);
if (marshalAsAttr == null)
return false;
// Get the array
Array? arr = fi.GetValue(instance) as Array;
if (arr == null)
return false;
// Get the number of elements expected
int elementCount = MarshalHelpers.GetArrayElementCount(marshalAsAttr, fields, instance);
if (elementCount < 0)
return false;
// Get the item type for the array
Type elementType = fi.FieldType.GetElementType() ?? typeof(object);
// Loop through and write the array
for (int i = 0; i < elementCount; i++)
{
var value = arr.GetValue(i);
if (!WriteType(stream, value, elementType))
return false;
}
// Return the built array
return true;
}
/// <summary>
/// Write a string type field from an object
/// </summary>
private static bool WriteStringType(Stream stream, Encoding encoding, object instance, FieldInfo fi)
{
var marshalAsAttr = MarshalHelpers.GetAttribute<MarshalAsAttribute>(fi);
string? fieldValue = fi.GetValue(instance) as string;
if (fieldValue == null)
return true;
switch (marshalAsAttr?.Value)
{
case UnmanagedType.AnsiBStr:
return stream.WritePrefixedAnsiString(fieldValue);
case UnmanagedType.BStr:
case UnmanagedType.TBStr: // Technically distinct; returns char[] instead
return stream.WritePrefixedUnicodeString(fieldValue);
case UnmanagedType.ByValTStr:
int byvalLength = marshalAsAttr!.SizeConst;
byte[] byvalBytes = encoding.GetBytes(fieldValue);
byte[] byvalSizedBytes = new byte[byvalLength];
Array.Copy(byvalBytes, byvalSizedBytes, Math.Min(byvalBytes.Length, byvalSizedBytes.Length));
return Write(stream, byvalSizedBytes);
case UnmanagedType.LPStr:
case UnmanagedType.LPTStr: // Technically distinct; possibly not null-terminated
case null:
return stream.WriteNullTerminatedAnsiString(fieldValue);
#if NET472_OR_GREATER || NETCOREAPP
case UnmanagedType.LPUTF8Str:
return stream.WriteNullTerminatedUTF8String(fieldValue);
#endif
case UnmanagedType.LPWStr:
return stream.WriteNullTerminatedUnicodeString(fieldValue);
// No other string types are recognized
default:
return false;
}
}
/// <summary>
/// Write an array of bytes to the stream
/// </summary>
private static bool WriteFromBuffer(Stream stream, byte[] value)
{
// If the stream is not writable
if (!stream.CanWrite)
return false;
// Handle the 0-byte case
if (value.Length == 0)
return true;
// Handle the general case, forcing a write of the correct length
stream.Write(value, 0, value.Length);
return true;
}
}
}

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,8 @@
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>1.3.7</Version>
<Version>1.4.10</Version>
<WarningsNotAsErrors>CS0618</WarningsNotAsErrors>
<!-- Package Properties -->
<Authors>Matt Nadareski</Authors>

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
}