mirror of
https://github.com/SabreTools/SabreTools.IO.git
synced 2026-02-08 05:37:52 +00:00
Compare commits
122 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8924a50432 | ||
|
|
97f00a2565 | ||
|
|
f35231d95b | ||
|
|
96c6bba93e | ||
|
|
b0d81f225b | ||
|
|
ef699ee1fb | ||
|
|
0910b716ba | ||
|
|
584feb33e6 | ||
|
|
b99c80390e | ||
|
|
3a79646650 | ||
|
|
f16f05beb9 | ||
|
|
e901b52143 | ||
|
|
a638b146b4 | ||
|
|
6bfc961a87 | ||
|
|
a825bae039 | ||
|
|
e0eba8e5bb | ||
|
|
fb4b533dfb | ||
|
|
6162af2216 | ||
|
|
3b5fd128f0 | ||
|
|
9beb2177aa | ||
|
|
f0033af712 | ||
|
|
3de5b2378d | ||
|
|
ee7ce59627 | ||
|
|
39bf9c19ad | ||
|
|
32cab49bae | ||
|
|
5d71957841 | ||
|
|
7ea182c7d8 | ||
|
|
b97ec13661 | ||
|
|
8caeea053f | ||
|
|
a7476b6ac9 | ||
|
|
f0095f9e41 | ||
|
|
a0b5ea1368 | ||
|
|
a94d2c8c64 | ||
|
|
8c19ad712a | ||
|
|
0317f751b9 | ||
|
|
b8d431b06b | ||
|
|
3fcf10e2f7 | ||
|
|
40e439b18c | ||
|
|
bf707b1c11 | ||
|
|
d074a6a7ee | ||
|
|
0c736c2491 | ||
|
|
964506057d | ||
|
|
cd08925411 | ||
|
|
6ea8aab7c7 | ||
|
|
561dbdcc9a | ||
|
|
b4bad28823 | ||
|
|
ec9db7e732 | ||
|
|
3c7401fefc | ||
|
|
09e66c9ec3 | ||
|
|
8d1bc3957c | ||
|
|
245ca9010a | ||
|
|
88207100f1 | ||
|
|
3befd9255a | ||
|
|
37f2848bb2 | ||
|
|
c5dca60d28 | ||
|
|
351e46534d | ||
|
|
d39324c887 | ||
|
|
163f49281d | ||
|
|
03d0f7dd18 | ||
|
|
fbbe77f5f2 | ||
|
|
4bffd9d31c | ||
|
|
904aed1c44 | ||
|
|
69a41b2487 | ||
|
|
f326c921e6 | ||
|
|
73c4e8dd50 | ||
|
|
e4c8bbc3f9 | ||
|
|
8df3fe2473 | ||
|
|
a2bb83ab9a | ||
|
|
bcb77f2de6 | ||
|
|
35d4c22a20 | ||
|
|
893cb73b0d | ||
|
|
607a0375c7 | ||
|
|
87d1dfe266 | ||
|
|
d8f16b12b5 | ||
|
|
cf08658b1e | ||
|
|
9547e1a355 | ||
|
|
20cdb5b65e | ||
|
|
fc10565186 | ||
|
|
c824db6b18 | ||
|
|
a3c26fed38 | ||
|
|
2dc259d978 | ||
|
|
2d950ddc54 | ||
|
|
294c5c26df | ||
|
|
dc356767ab | ||
|
|
0b6c7e9885 | ||
|
|
339da9fc16 | ||
|
|
7bc18c6952 | ||
|
|
ec3afeed73 | ||
|
|
658ceb5d0e | ||
|
|
e9a905d4a3 | ||
|
|
1127e96f26 | ||
|
|
89a8ad3703 | ||
|
|
ff7f7c0b8c | ||
|
|
32e948653a | ||
|
|
a93da97bbd | ||
|
|
65bdcc563d | ||
|
|
f3c5499754 | ||
|
|
02819f006b | ||
|
|
48512b486c | ||
|
|
6c289ee015 | ||
|
|
973e19366a | ||
|
|
f5ef39ab76 | ||
|
|
e4deb10db6 | ||
|
|
90d2382d5d | ||
|
|
c48d62fd5e | ||
|
|
0f10dc2ae4 | ||
|
|
c648ad9f5e | ||
|
|
cd01e170fe | ||
|
|
ecaec1d44a | ||
|
|
ce521d92ca | ||
|
|
a69b6dfa3a | ||
|
|
18b8357cd6 | ||
|
|
c19b59dc1c | ||
|
|
1bdb483205 | ||
|
|
b99f8531c1 | ||
|
|
602b951be7 | ||
|
|
046bb5875a | ||
|
|
6074fa3c3f | ||
|
|
bdad58c0dc | ||
|
|
4097a8cc8c | ||
|
|
8e3293dd7d | ||
|
|
7ac4df8201 |
586
SabreTools.IO.Test/Extensions/BinaryReaderExtensionsTests.cs
Normal file
586
SabreTools.IO.Test/Extensions/BinaryReaderExtensionsTests.cs
Normal file
@@ -0,0 +1,586 @@
|
||||
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 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()
|
||||
{
|
||||
var stream = new MemoryStream(_bytes);
|
||||
var br = new BinaryReader(stream);
|
||||
short read = br.ReadInt16BigEndian();
|
||||
Assert.Equal(0x0001, read);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadUInt16Test()
|
||||
{
|
||||
var stream = new MemoryStream(_bytes);
|
||||
var br = new BinaryReader(stream);
|
||||
ushort read = br.ReadUInt16();
|
||||
Assert.Equal(0x0100, read);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadUInt16BigEndianTest()
|
||||
{
|
||||
var stream = new MemoryStream(_bytes);
|
||||
var br = new BinaryReader(stream);
|
||||
ushort read = br.ReadUInt16BigEndian();
|
||||
Assert.Equal(0x0001, read);
|
||||
}
|
||||
|
||||
#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()
|
||||
{
|
||||
var stream = new MemoryStream(_bytes);
|
||||
var br = new BinaryReader(stream);
|
||||
int read = br.ReadInt32BigEndian();
|
||||
Assert.Equal(0x00010203, read);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadUInt32Test()
|
||||
{
|
||||
var stream = new MemoryStream(_bytes);
|
||||
var br = new BinaryReader(stream);
|
||||
uint read = br.ReadUInt32();
|
||||
Assert.Equal((uint)0x03020100, read);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadUInt32BigEndianTest()
|
||||
{
|
||||
var stream = new MemoryStream(_bytes);
|
||||
var br = new BinaryReader(stream);
|
||||
uint read = br.ReadUInt32BigEndian();
|
||||
Assert.Equal((uint)0x00010203, read);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadSingleTest()
|
||||
{
|
||||
var stream = new MemoryStream(_bytes);
|
||||
var br = new BinaryReader(stream);
|
||||
float expected = BitConverter.Int32BitsToSingle(0x03020100);
|
||||
float read = br.ReadSingle();
|
||||
Assert.Equal(expected, read);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadSingleBigEndianTest()
|
||||
{
|
||||
var stream = new MemoryStream(_bytes);
|
||||
var br = new BinaryReader(stream);
|
||||
float expected = BitConverter.Int32BitsToSingle(0x00010203);
|
||||
float read = br.ReadSingleBigEndian();
|
||||
Assert.Equal(expected, read);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void 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()
|
||||
{
|
||||
var stream = new MemoryStream(_bytes);
|
||||
var br = new BinaryReader(stream);
|
||||
long read = br.ReadInt64BigEndian();
|
||||
Assert.Equal(0x0001020304050607, read);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadUInt64Test()
|
||||
{
|
||||
var stream = new MemoryStream(_bytes);
|
||||
var br = new BinaryReader(stream);
|
||||
ulong read = br.ReadUInt64();
|
||||
Assert.Equal((ulong)0x0706050403020100, read);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadUInt64BigEndianTest()
|
||||
{
|
||||
var stream = new MemoryStream(_bytes);
|
||||
var br = new BinaryReader(stream);
|
||||
ulong read = br.ReadUInt64BigEndian();
|
||||
Assert.Equal((ulong)0x0001020304050607, read);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadDoubleTest()
|
||||
{
|
||||
var stream = new MemoryStream(_bytes);
|
||||
var br = new BinaryReader(stream);
|
||||
double expected = BitConverter.Int64BitsToDouble(0x0706050403020100);
|
||||
double read = br.ReadDouble();
|
||||
Assert.Equal(expected, read);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadDoubleBigEndianTest()
|
||||
{
|
||||
var stream = new MemoryStream(_bytes);
|
||||
var br = new BinaryReader(stream);
|
||||
double expected = BitConverter.Int64BitsToDouble(0x0001020304050607);
|
||||
double read = br.ReadDoubleBigEndian();
|
||||
Assert.Equal(expected, read);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
508
SabreTools.IO.Test/Extensions/BinaryWriterExtensionsTests.cs
Normal file
508
SabreTools.IO.Test/Extensions/BinaryWriterExtensionsTests.cs
Normal 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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
547
SabreTools.IO.Test/Extensions/ByteArrayExtensionsReadTests.cs
Normal file
547
SabreTools.IO.Test/Extensions/ByteArrayExtensionsReadTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
491
SabreTools.IO.Test/Extensions/ByteArrayExtensionsWriteTests.cs
Normal file
491
SabreTools.IO.Test/Extensions/ByteArrayExtensionsWriteTests.cs
Normal 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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
138
SabreTools.IO.Test/Extensions/EnumerableExtensionsTests.cs
Normal file
138
SabreTools.IO.Test/Extensions/EnumerableExtensionsTests.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using SabreTools.IO.Extensions;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.IO.Test
|
||||
namespace SabreTools.IO.Test.Extensions
|
||||
{
|
||||
public class IOExtensionsTests
|
||||
{
|
||||
541
SabreTools.IO.Test/Extensions/StreamExtensionsReadTests.cs
Normal file
541
SabreTools.IO.Test/Extensions/StreamExtensionsReadTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
454
SabreTools.IO.Test/Extensions/StreamExtensionsWriteTests.cs
Normal file
454
SabreTools.IO.Test/Extensions/StreamExtensionsWriteTests.cs
Normal 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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
SabreTools.IO.Test/Extensions/TestEnum.cs
Normal file
9
SabreTools.IO.Test/Extensions/TestEnum.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace SabreTools.IO.Test.Extensions
|
||||
{
|
||||
internal enum TestEnum : uint
|
||||
{
|
||||
None = 0x00000000,
|
||||
RecognizedTestValue = 0x03020100,
|
||||
UpperBoundaryValue = 0xFFFFFFFF,
|
||||
}
|
||||
}
|
||||
53
SabreTools.IO.Test/Extensions/TestStructArrays.cs
Normal file
53
SabreTools.IO.Test/Extensions/TestStructArrays.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
23
SabreTools.IO.Test/Extensions/TestStructExplicit.cs
Normal file
23
SabreTools.IO.Test/Extensions/TestStructExplicit.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
29
SabreTools.IO.Test/Extensions/TestStructInheritance.cs
Normal file
29
SabreTools.IO.Test/Extensions/TestStructInheritance.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
19
SabreTools.IO.Test/Extensions/TestStructSequential.cs
Normal file
19
SabreTools.IO.Test/Extensions/TestStructSequential.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
38
SabreTools.IO.Test/Extensions/TestStructStrings.cs
Normal file
38
SabreTools.IO.Test/Extensions/TestStructStrings.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,14 @@ namespace SabreTools.IO.Test
|
||||
[InlineData("C:\\Directory\\SubDir\\Filename.ext", "C:\\Directory", true, "SubDir-Filename.ext")]
|
||||
public void NormalizedFileNameTest(string current, string? parent, bool sanitize, string? expected)
|
||||
{
|
||||
// Hack to support Windows paths on Linux for testing only
|
||||
if (System.IO.Path.DirectorySeparatorChar == '/')
|
||||
{
|
||||
current = current.Replace('\\', '/');
|
||||
parent = parent?.Replace('\\', '/');
|
||||
expected = expected?.Replace('\\', '/');
|
||||
}
|
||||
|
||||
var path = new ParentablePath(current, parent);
|
||||
string? actual = path.GetNormalizedFileName(sanitize);
|
||||
Assert.Equal(expected, actual);
|
||||
@@ -62,6 +70,15 @@ namespace SabreTools.IO.Test
|
||||
if (expected?.Contains("%cd%") == true)
|
||||
expected = expected.Replace("%cd%", Environment.CurrentDirectory.TrimEnd('\\', '/'));
|
||||
|
||||
// Hack to support Windows paths on Linux for testing only
|
||||
if (System.IO.Path.DirectorySeparatorChar == '/')
|
||||
{
|
||||
current = current.Replace('\\', '/');
|
||||
parent = parent?.Replace('\\', '/');
|
||||
outDir = outDir?.Replace('\\', '/');
|
||||
expected = expected?.Replace('\\', '/');
|
||||
}
|
||||
|
||||
var path = new ParentablePath(current, parent);
|
||||
string? actual = path.GetOutputPath(outDir, inplace);
|
||||
Assert.Equal(expected, actual);
|
||||
|
||||
@@ -1,27 +1,29 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
|
||||
<IsPackable>false</IsPackable>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
|
||||
<IsPackable>false</IsPackable>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<WarningsNotAsErrors>CS0618</WarningsNotAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="xunit" Version="2.6.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.4">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SabreTools.IO\SabreTools.IO.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SabreTools.IO\SabreTools.IO.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
</Project>
|
||||
|
||||
54
SabreTools.IO.Test/Streams/ReadOnlyBitStreamTests.cs
Normal file
54
SabreTools.IO.Test/Streams/ReadOnlyBitStreamTests.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using System.IO;
|
||||
using SabreTools.IO.Streams;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.IO.Test.Streams
|
||||
{
|
||||
public class ReadOnlyBitStreamTests
|
||||
{
|
||||
[Fact]
|
||||
public void DefaultConstructorTest()
|
||||
{
|
||||
var stream = new ReadOnlyBitStream(new MemoryStream());
|
||||
Assert.Equal(0, stream.Length);
|
||||
Assert.Equal(0, stream.Position);
|
||||
|
||||
stream = new ReadOnlyBitStream(new MemoryStream(new byte[16], 0, 16, true, true));
|
||||
Assert.Equal(16, stream.Length);
|
||||
Assert.Equal(0, stream.Position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadSingleBitTest()
|
||||
{
|
||||
byte[] data = [0b01010101];
|
||||
var stream = new ReadOnlyBitStream(new MemoryStream(data));
|
||||
byte? bit = stream.ReadBit();
|
||||
Assert.NotNull(bit);
|
||||
Assert.Equal((byte)0b00000001, bit);
|
||||
Assert.Equal(1, stream.Position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadBitsLSBTest()
|
||||
{
|
||||
byte[] data = [0b01010101, 0b01010101, 0b01010101, 0b01010101];
|
||||
var stream = new ReadOnlyBitStream(new MemoryStream(data));
|
||||
uint? bits = stream.ReadBitsLSB(4);
|
||||
Assert.NotNull(bits);
|
||||
Assert.Equal((byte)0b00000101, bits);
|
||||
Assert.Equal(1, stream.Position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadBitsMSBTest()
|
||||
{
|
||||
byte[] data = [0b01010101, 0b01010101, 0b01010101, 0b01010101];
|
||||
var stream = new ReadOnlyBitStream(new MemoryStream(data));
|
||||
uint? bits = stream.ReadBitsMSB(4);
|
||||
Assert.NotNull(bits);
|
||||
Assert.Equal((byte)0b00001010, bits);
|
||||
Assert.Equal(1, stream.Position);
|
||||
}
|
||||
}
|
||||
}
|
||||
147
SabreTools.IO.Test/Streams/ReadOnlyCompositeStreamTests.cs
Normal file
147
SabreTools.IO.Test/Streams/ReadOnlyCompositeStreamTests.cs
Normal file
@@ -0,0 +1,147 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SabreTools.IO.Streams;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.IO.Test.Streams
|
||||
{
|
||||
public class ReadOnlyCompositeStreamTests
|
||||
{
|
||||
[Fact]
|
||||
public void DefaultConstructorTest()
|
||||
{
|
||||
var stream = new ReadOnlyCompositeStream();
|
||||
Assert.Equal(0, stream.Length);
|
||||
Assert.Equal(0, stream.Position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyArrayConstructorTest()
|
||||
{
|
||||
Stream[] arr = [new MemoryStream()];
|
||||
var stream = new ReadOnlyCompositeStream(arr);
|
||||
Assert.Equal(0, stream.Length);
|
||||
Assert.Equal(0, stream.Position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyEnumerableConstructorTest()
|
||||
{
|
||||
// Empty enumerable constructor
|
||||
List<Stream> list = [new MemoryStream()];
|
||||
var stream = new ReadOnlyCompositeStream(list);
|
||||
Assert.Equal(0, stream.Length);
|
||||
Assert.Equal(0, stream.Position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SingleStreamConstructorTest()
|
||||
{
|
||||
var stream = new ReadOnlyCompositeStream(new MemoryStream(new byte[1024]));
|
||||
Assert.Equal(1024, stream.Length);
|
||||
Assert.Equal(0, stream.Position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FilledArrayConstructorTest()
|
||||
{
|
||||
Stream[] arr = [new MemoryStream(new byte[1024]), new MemoryStream(new byte[1024])];
|
||||
var stream = new ReadOnlyCompositeStream(arr);
|
||||
Assert.Equal(2048, stream.Length);
|
||||
Assert.Equal(0, stream.Position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FilledEnumerableConstructorTest()
|
||||
{
|
||||
List<Stream> list = [new MemoryStream(new byte[1024]), new MemoryStream(new byte[1024])];
|
||||
var stream = new ReadOnlyCompositeStream(list);
|
||||
Assert.Equal(2048, stream.Length);
|
||||
Assert.Equal(0, stream.Position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddStreamTest()
|
||||
{
|
||||
var stream = new ReadOnlyCompositeStream();
|
||||
Assert.Equal(0, stream.Length);
|
||||
Assert.Equal(0, stream.Position);
|
||||
|
||||
stream.AddStream(new MemoryStream(new byte[1024]));
|
||||
Assert.Equal(1024, stream.Length);
|
||||
Assert.Equal(0, stream.Position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyStreamReadTest()
|
||||
{
|
||||
var stream = new ReadOnlyCompositeStream();
|
||||
|
||||
byte[] buf = new byte[512];
|
||||
int read = stream.Read(buf, 0, 512);
|
||||
|
||||
Assert.Equal(0, read);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SingleStreamReadTest()
|
||||
{
|
||||
Stream[] arr = [new MemoryStream(new byte[1024])];
|
||||
var stream = new ReadOnlyCompositeStream(arr);
|
||||
|
||||
byte[] buf = new byte[512];
|
||||
int read = stream.Read(buf, 0, 512);
|
||||
|
||||
Assert.Equal(512, read);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MultipleStreamSingleContainedReadTest()
|
||||
{
|
||||
Stream[] arr = [new MemoryStream(new byte[1024]), new MemoryStream(new byte[1024])];
|
||||
var stream = new ReadOnlyCompositeStream(arr);
|
||||
|
||||
byte[] buf = new byte[512];
|
||||
int read = stream.Read(buf, 0, 512);
|
||||
|
||||
Assert.Equal(512, read);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MultipleStreamMultipleContainedReadTest()
|
||||
{
|
||||
Stream[] arr = [new MemoryStream(new byte[256]), new MemoryStream(new byte[256])];
|
||||
var stream = new ReadOnlyCompositeStream(arr);
|
||||
|
||||
byte[] buf = new byte[512];
|
||||
int read = stream.Read(buf, 0, 512);
|
||||
|
||||
Assert.Equal(512, read);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SingleStreamExtraReadTest()
|
||||
{
|
||||
Stream[] arr = [new MemoryStream(new byte[256])];
|
||||
var stream = new ReadOnlyCompositeStream(arr);
|
||||
|
||||
byte[] buf = new byte[512];
|
||||
int read = stream.Read(buf, 0, 512);
|
||||
|
||||
Assert.Equal(256, read);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MultipleStreamExtraReadTest()
|
||||
{
|
||||
Stream[] arr = [new MemoryStream(new byte[128]), new MemoryStream(new byte[128])];
|
||||
var stream = new ReadOnlyCompositeStream(arr);
|
||||
|
||||
byte[] buf = new byte[512];
|
||||
int read = stream.Read(buf, 0, 512);
|
||||
|
||||
Assert.Equal(256, read);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace SabreTools.IO
|
||||
{
|
||||
/// <summary>
|
||||
/// Big endian reading overloads for BinaryReader
|
||||
/// </summary>
|
||||
public static class BinaryReaderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads the specified number of bytes from the stream, starting from a specified point in the byte array.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer to read data into.</param>
|
||||
/// <param name="index">The starting point in the buffer at which to begin reading into the buffer.</param>
|
||||
/// <param name="count">The number of bytes to read.</param>
|
||||
/// <returns>The number of bytes read into buffer. This might be less than the number of bytes requested if that many bytes are not available, or it might be zero if the end of the stream is reached.</returns>
|
||||
public static int ReadBigEndian(this BinaryReader reader, byte[] buffer, int index, int count)
|
||||
{
|
||||
int retval = reader.Read(buffer, index, count);
|
||||
Array.Reverse(buffer);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the specified number of characters from the stream, starting from a specified point in the character array.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer to read data into.</param>
|
||||
/// <param name="index">The starting point in the buffer at which to begin reading into the buffer.</param>
|
||||
/// <param name="count">The number of characters to read.</param>
|
||||
/// <returns>The total number of characters read into the buffer. This might be less than the number of characters requested if that many characters are not currently available, or it might be zero if the end of the stream is reached.</returns>
|
||||
public static int ReadBigEndian(this BinaryReader reader, char[] buffer, int index, int count)
|
||||
{
|
||||
int retval = reader.Read(buffer, index, count);
|
||||
Array.Reverse(buffer);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the specified number of bytes from the current stream into a byte array and advances the current position by that number of bytes.
|
||||
/// </summary>
|
||||
/// <param name="count">The number of bytes to read. This value must be 0 or a non-negative number or an exception will occur.</param>
|
||||
/// <returns>A byte array containing data read from the underlying stream. This might be less than the number of bytes requested if the end of the stream is reached.</returns>
|
||||
public static byte[] ReadBytesBigEndian(this BinaryReader reader, int count)
|
||||
{
|
||||
byte[] retval = reader.ReadBytes(count);
|
||||
Array.Reverse(retval);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the specified number of characters from the current stream, returns the data in a character array, and advances the current position in accordance with the Encoding used and the specific character being read from the stream.
|
||||
/// </summary>
|
||||
/// <param name="count">The number of characters to read. This value must be 0 or a non-negative number or an exception will occur.</param>
|
||||
/// <returns>A character array containing data read from the underlying stream. This might be less than the number of bytes requested if the end of the stream is reached.</returns>
|
||||
public static char[] ReadCharsBigEndian(this BinaryReader reader, int count)
|
||||
{
|
||||
char[] retval = reader.ReadChars(count);
|
||||
Array.Reverse(retval);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a decimal value from the current stream and advances the current position of the stream by sixteen bytes.
|
||||
/// </summary>
|
||||
/// <returns>A decimal value read from the current stream.</returns>
|
||||
public static decimal ReadDecimalBigEndian(this BinaryReader reader)
|
||||
{
|
||||
byte[] retval = reader.ReadBytes(16);
|
||||
Array.Reverse(retval);
|
||||
|
||||
int i1 = BitConverter.ToInt32(retval, 0);
|
||||
int i2 = BitConverter.ToInt32(retval, 4);
|
||||
int i3 = BitConverter.ToInt32(retval, 8);
|
||||
int i4 = BitConverter.ToInt32(retval, 12);
|
||||
|
||||
return new decimal([i1, i2, i3, i4]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// eads an 8-byte floating point value from the current stream and advances the current position of the stream by eight bytes.
|
||||
/// </summary>
|
||||
/// <returns>An 8-byte floating point value read from the current stream.</returns>
|
||||
public static double ReadDoubleBigEndian(this BinaryReader reader)
|
||||
{
|
||||
byte[] retval = reader.ReadBytes(8);
|
||||
Array.Reverse(retval);
|
||||
return BitConverter.ToDouble(retval, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a 2-byte signed integer from the current stream and advances the current position of the stream by two bytes.
|
||||
/// </summary>
|
||||
/// <returns>A 2-byte signed integer read from the current stream.</returns>
|
||||
public static short ReadInt16BigEndian(this BinaryReader reader)
|
||||
{
|
||||
byte[] retval = reader.ReadBytes(2);
|
||||
Array.Reverse(retval);
|
||||
return BitConverter.ToInt16(retval, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a 4-byte signed integer from the current stream and advances the current position of the stream by four bytes.
|
||||
/// </summary>
|
||||
/// <returns>A 4-byte signed integer read from the current stream.</returns>
|
||||
public static int ReadInt32BigEndian(this BinaryReader reader)
|
||||
{
|
||||
byte[] retval = reader.ReadBytes(4);
|
||||
Array.Reverse(retval);
|
||||
return BitConverter.ToInt32(retval, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an 8-byte signed integer from the current stream and advances the current position of the stream by eight bytes.
|
||||
/// </summary>
|
||||
/// <returns>An 8-byte signed integer read from the current stream.</returns>
|
||||
public static long ReadInt64BigEndian(this BinaryReader reader)
|
||||
{
|
||||
byte[] retval = reader.ReadBytes(8);
|
||||
Array.Reverse(retval);
|
||||
return BitConverter.ToInt64(retval, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a 4-byte floating point value from the current stream and advances the current position of the stream by four bytes.
|
||||
/// </summary>
|
||||
/// <returns>A 4-byte floating point value read from the current stream.</returns>
|
||||
public static float ReadSingleBigEndian(this BinaryReader reader)
|
||||
{
|
||||
byte[] retval = reader.ReadBytes(4);
|
||||
Array.Reverse(retval);
|
||||
return BitConverter.ToSingle(retval, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a 2-byte unsigned integer from the current stream using little-endian encoding and advances the position of the stream by two bytes.
|
||||
///
|
||||
/// This API is not CLS-compliant.
|
||||
/// </summary>
|
||||
/// <returns>A 2-byte unsigned integer read from this stream.</returns>
|
||||
public static ushort ReadUInt16BigEndian(this BinaryReader reader)
|
||||
{
|
||||
byte[] retval = reader.ReadBytes(2);
|
||||
Array.Reverse(retval);
|
||||
return BitConverter.ToUInt16(retval, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a 4-byte unsigned integer from the current stream and advances the position of the stream by four bytes.
|
||||
///
|
||||
/// This API is not CLS-compliant.
|
||||
/// </summary>
|
||||
/// <returns>A 4-byte unsigned integer read from this stream.</returns>
|
||||
public static uint ReadUInt32BigEndian(this BinaryReader reader)
|
||||
{
|
||||
byte[] retval = reader.ReadBytes(4);
|
||||
Array.Reverse(retval);
|
||||
return BitConverter.ToUInt32(retval, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an 8-byte unsigned integer from the current stream and advances the position of the stream by eight bytes.
|
||||
///
|
||||
/// This API is not CLS-compliant.
|
||||
/// </summary>
|
||||
/// <returns>An 8-byte unsigned integer read from this stream.</returns>
|
||||
public static ulong ReadUInt64BigEndian(this BinaryReader reader)
|
||||
{
|
||||
byte[] retval = reader.ReadBytes(8);
|
||||
Array.Reverse(retval);
|
||||
return BitConverter.ToUInt64(retval, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,283 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace SabreTools.IO
|
||||
{
|
||||
/// <summary>
|
||||
/// Extensions for byte arrays
|
||||
/// </summary>
|
||||
/// <remarks>TODO: Add U/Int24 and U/Int48 methods</remarks>
|
||||
public static class ByteArrayExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Read a UInt8 and increment the pointer to an array
|
||||
/// </summary>
|
||||
public static byte ReadByte(this byte[] content, ref int offset)
|
||||
{
|
||||
byte[]? buffer = content.ReadBytes(ref offset, 1);
|
||||
if (buffer == null)
|
||||
return default;
|
||||
|
||||
return buffer[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a UInt8[] and increment the pointer to an array
|
||||
/// </summary>
|
||||
public static byte[]? ReadBytes(this byte[]? content, ref int offset, int count)
|
||||
{
|
||||
// If the byte array is invalid, don't do anything
|
||||
if (content == null)
|
||||
return null;
|
||||
|
||||
// If there's an invalid byte count, don't do anything
|
||||
if (count <= 0 || offset >= content.Length)
|
||||
return null;
|
||||
|
||||
// Allocate enough space for the data requested
|
||||
byte[] buffer = new byte[count];
|
||||
|
||||
// If we have less data left than requested, only read until the end
|
||||
if (offset + count >= content.Length)
|
||||
count = content.Length - offset;
|
||||
|
||||
// If we have a non-zero count, copy the data into the array
|
||||
if (count > 0)
|
||||
Array.Copy(content, offset, buffer, 0, Math.Min(count, content.Length - offset));
|
||||
|
||||
// Increment the offset and return
|
||||
offset += count;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a Int8 and increment the pointer to an array
|
||||
/// </summary>
|
||||
public static sbyte ReadSByte(this byte[] content, ref int offset)
|
||||
{
|
||||
byte[]? buffer = content.ReadBytes(ref offset, 1);
|
||||
if (buffer == null)
|
||||
return default;
|
||||
|
||||
return (sbyte)buffer[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a Char and increment the pointer to an array
|
||||
/// </summary>
|
||||
public static char ReadChar(this byte[] content, ref int offset)
|
||||
{
|
||||
byte[]? buffer = content.ReadBytes(ref offset, 1);
|
||||
if (buffer == null)
|
||||
return default;
|
||||
|
||||
return (char)buffer[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a Int16 and increment the pointer to an array
|
||||
/// </summary>
|
||||
public static short ReadInt16(this byte[] content, ref int offset)
|
||||
{
|
||||
byte[]? buffer = content.ReadBytes(ref offset, 2);
|
||||
if (buffer == null)
|
||||
return default;
|
||||
|
||||
return BitConverter.ToInt16(buffer, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a Int16 in big-endian format and increment the pointer to an array
|
||||
/// </summary>
|
||||
public static short ReadInt16BigEndian(this byte[] content, ref int offset)
|
||||
{
|
||||
byte[]? buffer = content.ReadBytes(ref offset, 2);
|
||||
if (buffer == null)
|
||||
return default;
|
||||
|
||||
Array.Reverse(buffer);
|
||||
return BitConverter.ToInt16(buffer, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a UInt16 and increment the pointer to an array
|
||||
/// </summary>
|
||||
public static ushort ReadUInt16(this byte[] content, ref int offset)
|
||||
{
|
||||
byte[]? buffer = content.ReadBytes(ref offset, 2);
|
||||
if (buffer == null)
|
||||
return default;
|
||||
|
||||
return BitConverter.ToUInt16(buffer, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a UInt16 in big-endian format and increment the pointer to an array
|
||||
/// </summary>
|
||||
public static ushort ReadUInt16BigEndian(this byte[] content, ref int offset)
|
||||
{
|
||||
byte[]? buffer = content.ReadBytes(ref offset, 2);
|
||||
if (buffer == null)
|
||||
return default;
|
||||
|
||||
Array.Reverse(buffer);
|
||||
return BitConverter.ToUInt16(buffer, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a Int32 and increment the pointer to an array
|
||||
/// </summary>
|
||||
public static int ReadInt32(this byte[] content, ref int offset)
|
||||
{
|
||||
byte[]? buffer = content.ReadBytes(ref offset, 4);
|
||||
if (buffer == null)
|
||||
return default;
|
||||
|
||||
return BitConverter.ToInt32(buffer, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a Int32 in big-endian format and increment the pointer to an array
|
||||
/// </summary>
|
||||
public static int ReadInt32BigEndian(this byte[] content, ref int offset)
|
||||
{
|
||||
byte[]? buffer = content.ReadBytes(ref offset, 4);
|
||||
if (buffer == null)
|
||||
return default;
|
||||
|
||||
Array.Reverse(buffer);
|
||||
return BitConverter.ToInt32(buffer, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a UInt32 and increment the pointer to an array
|
||||
/// </summary>
|
||||
public static uint ReadUInt32(this byte[] content, ref int offset)
|
||||
{
|
||||
byte[]? buffer = content.ReadBytes(ref offset, 4);
|
||||
if (buffer == null)
|
||||
return default;
|
||||
|
||||
return BitConverter.ToUInt32(buffer, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a UInt32 in big-endian format and increment the pointer to an array
|
||||
/// </summary>
|
||||
public static uint ReadUInt32BigEndian(this byte[] content, ref int offset)
|
||||
{
|
||||
byte[]? buffer = content.ReadBytes(ref offset, 4);
|
||||
if (buffer == null)
|
||||
return default;
|
||||
|
||||
Array.Reverse(buffer);
|
||||
return BitConverter.ToUInt32(buffer, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a Int64 and increment the pointer to an array
|
||||
/// </summary>
|
||||
public static long ReadInt64(this byte[] content, ref int offset)
|
||||
{
|
||||
byte[]? buffer = content.ReadBytes(ref offset, 8);
|
||||
if (buffer == null)
|
||||
return default;
|
||||
|
||||
return BitConverter.ToInt64(buffer, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a Int64 in big-endian format and increment the pointer to an array
|
||||
/// </summary>
|
||||
public static long ReadInt64BigEndian(this byte[] content, ref int offset)
|
||||
{
|
||||
byte[]? buffer = content.ReadBytes(ref offset, 8);
|
||||
if (buffer == null)
|
||||
return default;
|
||||
|
||||
Array.Reverse(buffer);
|
||||
return BitConverter.ToInt64(buffer, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a UInt64 and increment the pointer to an array
|
||||
/// </summary>
|
||||
public static ulong ReadUInt64(this byte[] content, ref int offset)
|
||||
{
|
||||
byte[]? buffer = content.ReadBytes(ref offset, 8);
|
||||
if (buffer == null)
|
||||
return default;
|
||||
|
||||
return BitConverter.ToUInt64(buffer, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a UInt64 in big-endian format and increment the pointer to an array
|
||||
/// </summary>
|
||||
public static ulong ReadUInt64BigEndian(this byte[] content, ref int offset)
|
||||
{
|
||||
byte[]? buffer = content.ReadBytes(ref offset, 8);
|
||||
if (buffer == null)
|
||||
return default;
|
||||
|
||||
Array.Reverse(buffer);
|
||||
return BitConverter.ToUInt64(buffer, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a Guid and increment the pointer to an array
|
||||
/// </summary>
|
||||
public static Guid ReadGuid(this byte[] content, ref int offset)
|
||||
{
|
||||
byte[]? buffer = content.ReadBytes(ref offset, 16);
|
||||
if (buffer == null)
|
||||
return default;
|
||||
|
||||
return new Guid(buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a Guid in big-endian format and increment the pointer to an array
|
||||
/// </summary>
|
||||
public static Guid ReadGuidBigEndian(this byte[] content, ref int offset)
|
||||
{
|
||||
byte[]? buffer = content.ReadBytes(ref offset, 16);
|
||||
if (buffer == null)
|
||||
return default;
|
||||
|
||||
Array.Reverse(buffer);
|
||||
return new Guid(buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a null-terminated string from the stream
|
||||
/// </summary>
|
||||
public static string? ReadString(this byte[] content, ref int offset) => content.ReadString(ref offset, Encoding.Default);
|
||||
|
||||
/// <summary>
|
||||
/// Read a null-terminated string from the stream
|
||||
/// </summary>
|
||||
public static string? ReadString(this byte[] content, ref int offset, Encoding encoding)
|
||||
{
|
||||
if (offset >= content.Length)
|
||||
return null;
|
||||
|
||||
byte[] nullTerminator = encoding.GetBytes(new char[] { '\0' });
|
||||
int charWidth = nullTerminator.Length;
|
||||
|
||||
var keyChars = new List<char>();
|
||||
while (offset < content.Length)
|
||||
{
|
||||
char c = encoding.GetChars(content, offset, charWidth)[0];
|
||||
keyChars.Add(c);
|
||||
offset += charWidth;
|
||||
|
||||
if (c == '\0')
|
||||
break;
|
||||
}
|
||||
|
||||
return new string([.. keyChars]).TrimEnd('\0');
|
||||
}
|
||||
}
|
||||
}
|
||||
743
SabreTools.IO/Extensions/BinaryReaderExtensions.cs
Normal file
743
SabreTools.IO/Extensions/BinaryReaderExtensions.cs
Normal file
@@ -0,0 +1,743 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
#if NET40_OR_GREATER || NETCOREAPP
|
||||
using System.Linq;
|
||||
#endif
|
||||
#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 BinaryReader
|
||||
/// </summary>
|
||||
/// TODO: Handle proper negative values for Int24 and Int48
|
||||
public static class BinaryReaderExtensions
|
||||
{
|
||||
/// <inheritdoc cref="BinaryReader.Read(byte[], int, int)"/>
|
||||
/// <remarks>Reads in big-endian format</remarks>
|
||||
public static int ReadBigEndian(this BinaryReader reader, byte[] buffer, int index, int count)
|
||||
{
|
||||
int retval = reader.Read(buffer, index, count);
|
||||
Array.Reverse(buffer);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="BinaryReader.Read(char[], int, int)"/>
|
||||
/// <remarks>Reads in big-endian format</remarks>
|
||||
public static int ReadBigEndian(this BinaryReader reader, char[] buffer, int index, int count)
|
||||
{
|
||||
int retval = reader.Read(buffer, index, count);
|
||||
Array.Reverse(buffer);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="BinaryReader.ReadBytes(int)"/>
|
||||
/// <remarks>Reads in big-endian format</remarks>
|
||||
public static byte[] ReadBytesBigEndian(this BinaryReader reader, int count)
|
||||
{
|
||||
byte[] buffer = reader.ReadBytes(count);
|
||||
Array.Reverse(buffer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="BinaryReader.ReadChars(int)"/>
|
||||
/// <remarks>Reads in big-endian format</remarks>
|
||||
public static char[] ReadCharsBigEndian(this BinaryReader reader, int count)
|
||||
{
|
||||
char[] buffer = reader.ReadChars(count);
|
||||
Array.Reverse(buffer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="BinaryReader.ReadInt16"/>
|
||||
/// <remarks>Reads in big-endian format</remarks>
|
||||
public static short ReadInt16BigEndian(this BinaryReader reader)
|
||||
{
|
||||
byte[] buffer = reader.ReadBytes(2);
|
||||
Array.Reverse(buffer);
|
||||
return BitConverter.ToInt16(buffer, 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="BinaryReader.ReadUInt16"/>
|
||||
/// <remarks>Reads in big-endian format</remarks>
|
||||
public static ushort ReadUInt16BigEndian(this BinaryReader reader)
|
||||
{
|
||||
byte[] buffer = reader.ReadBytes(2);
|
||||
Array.Reverse(buffer);
|
||||
return BitConverter.ToUInt16(buffer, 0);
|
||||
}
|
||||
|
||||
// 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[] 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[] 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)
|
||||
{
|
||||
#if NET20 || NET35
|
||||
var attributes = fi?.GetCustomAttributes(typeof(MarshalAsAttribute), true);
|
||||
MarshalAsAttribute? marshalAsAttr;
|
||||
if (attributes == null || attributes.Length == 0)
|
||||
marshalAsAttr = default;
|
||||
else
|
||||
marshalAsAttr = attributes[0] as MarshalAsAttribute;
|
||||
#else
|
||||
var marshalAsAttr = fi?
|
||||
.GetCustomAttributes(typeof(MarshalAsAttribute), true)?
|
||||
.FirstOrDefault() as MarshalAsAttribute;
|
||||
#endif
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
}
|
||||
669
SabreTools.IO/Extensions/BinaryWriterExtensions.cs
Normal file
669
SabreTools.IO/Extensions/BinaryWriterExtensions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
883
SabreTools.IO/Extensions/ByteArrayReaderExtensions.cs
Normal file
883
SabreTools.IO/Extensions/ByteArrayReaderExtensions.cs
Normal 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, {length} requested");
|
||||
|
||||
// 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 {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;
|
||||
}
|
||||
}
|
||||
}
|
||||
820
SabreTools.IO/Extensions/ByteArrayWriterExtensions.cs
Normal file
820
SabreTools.IO/Extensions/ByteArrayWriterExtensions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
57
SabreTools.IO/Extensions/EnumerableExtensions.cs
Normal file
57
SabreTools.IO/Extensions/EnumerableExtensions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
627
SabreTools.IO/Extensions/IOExtensions.cs
Normal file
627
SabreTools.IO/Extensions/IOExtensions.cs
Normal file
@@ -0,0 +1,627 @@
|
||||
#if NETCOREAPP3_1_OR_GREATER
|
||||
using System;
|
||||
#endif
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
#if NET40_OR_GREATER || NETCOREAPP
|
||||
using System.Linq;
|
||||
#endif
|
||||
using System.Text;
|
||||
|
||||
namespace SabreTools.IO.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Methods around path operations
|
||||
/// </summary>
|
||||
public static class IOExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Ensure the output directory is a proper format and can be created
|
||||
/// </summary>
|
||||
/// <param name="dir">Directory to check</param>
|
||||
/// <param name="create">True if the directory should be created, false otherwise (default)</param>
|
||||
/// <returns>Full path to the directory</returns>
|
||||
public static string Ensure(this string? dir, bool create = false)
|
||||
{
|
||||
// If the output directory is invalid
|
||||
if (string.IsNullOrEmpty(dir))
|
||||
dir = PathTool.GetRuntimeDirectory();
|
||||
|
||||
// Get the full path for the output directory
|
||||
dir = Path.GetFullPath(dir!.Trim('"'));
|
||||
|
||||
// If we're creating the output folder, do so
|
||||
if (create && !Directory.Exists(dir))
|
||||
Directory.CreateDirectory(dir);
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines a text file's encoding by analyzing its byte order mark (BOM).
|
||||
/// Defaults to ASCII when detection of the text file's endianness fails.
|
||||
/// </summary>
|
||||
/// <param name="filename">The text file to analyze.</param>
|
||||
/// <returns>The detected encoding.</returns>
|
||||
/// <link>http://stackoverflow.com/questions/3825390/effective-way-to-find-any-files-encoding</link>
|
||||
public static Encoding GetEncoding(this string filename)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
return Encoding.Default;
|
||||
|
||||
if (!File.Exists(filename))
|
||||
return Encoding.Default;
|
||||
|
||||
// Try to open the file
|
||||
try
|
||||
{
|
||||
FileStream file = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
if (file == null)
|
||||
return Encoding.Default;
|
||||
|
||||
// Read the BOM
|
||||
var bom = new byte[4];
|
||||
file.Read(bom, 0, 4);
|
||||
file.Dispose();
|
||||
|
||||
// Disable warning about UTF7 usage
|
||||
#pragma warning disable SYSLIB0001
|
||||
|
||||
// Analyze the BOM
|
||||
if (bom[0] == 0x2b && bom[1] == 0x2f && bom[2] == 0x76) return Encoding.UTF7;
|
||||
if (bom[0] == 0xef && bom[1] == 0xbb && bom[2] == 0xbf) return Encoding.UTF8;
|
||||
if (bom[0] == 0xff && bom[1] == 0xfe) return Encoding.Unicode; //UTF-16LE
|
||||
if (bom[0] == 0xfe && bom[1] == 0xff) return Encoding.BigEndianUnicode; //UTF-16BE
|
||||
if (bom[0] == 0 && bom[1] == 0 && bom[2] == 0xfe && bom[3] == 0xff) return Encoding.UTF32;
|
||||
return Encoding.Default;
|
||||
|
||||
#pragma warning restore SYSLIB0001
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Encoding.Default;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the extension from the path, if possible
|
||||
/// </summary>
|
||||
/// <param name="path">Path to get extension from</param>
|
||||
/// <returns>Extension, if possible</returns>
|
||||
public static string? GetNormalizedExtension(this string? path)
|
||||
{
|
||||
// Check null or empty first
|
||||
if (string.IsNullOrEmpty(path))
|
||||
return null;
|
||||
|
||||
// Get the extension from the path, if possible
|
||||
string? ext = Path.GetExtension(path)?.ToLowerInvariant();
|
||||
|
||||
// Check if the extension is null or empty
|
||||
if (string.IsNullOrEmpty(ext))
|
||||
return null;
|
||||
|
||||
// Make sure that extensions are valid
|
||||
ext = ext!.TrimStart('.');
|
||||
|
||||
return ext;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all empty folders within a root folder
|
||||
/// </summary>
|
||||
/// <param name="root">Root directory to parse</param>
|
||||
/// <returns>IEumerable containing all directories that are empty, an empty enumerable if the root is empty, null otherwise</returns>
|
||||
public static List<string>? ListEmpty(this string? root)
|
||||
{
|
||||
// Check null or empty first
|
||||
if (string.IsNullOrEmpty(root))
|
||||
return null;
|
||||
|
||||
// Then, check if the root exists
|
||||
if (!Directory.Exists(root))
|
||||
return null;
|
||||
|
||||
// If it does and it is empty, return a blank enumerable
|
||||
#if NET20 || NET35
|
||||
if (new List<string>(root!.SafeEnumerateFileSystemEntries("*", SearchOption.AllDirectories)).Count == 0)
|
||||
#else
|
||||
if (!root!.SafeEnumerateFileSystemEntries("*", SearchOption.AllDirectories).Any())
|
||||
#endif
|
||||
return [];
|
||||
|
||||
// Otherwise, get the complete list
|
||||
#if NET20 || NET35
|
||||
var empty = new List<string>();
|
||||
foreach (var dir in root!.SafeEnumerateDirectories("*", SearchOption.AllDirectories))
|
||||
{
|
||||
if (new List<string>(dir!.SafeEnumerateFileSystemEntries("*", SearchOption.AllDirectories)).Count == 0)
|
||||
empty.Add(dir);
|
||||
}
|
||||
|
||||
return empty;
|
||||
#else
|
||||
return root!.SafeEnumerateDirectories("*", SearchOption.AllDirectories)
|
||||
.Where(dir => !dir.SafeEnumerateFileSystemEntries("*", 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
|
||||
}
|
||||
}
|
||||
192
SabreTools.IO/Extensions/MarshalHelpers.cs
Normal file
192
SabreTools.IO/Extensions/MarshalHelpers.cs
Normal file
@@ -0,0 +1,192 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
#if NET40_OR_GREATER || NETCOREAPP
|
||||
using System.Linq;
|
||||
#endif
|
||||
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[0] 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[0] 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 NET20 || NET35
|
||||
bool any = false;
|
||||
foreach (var f in fieldsList)
|
||||
{
|
||||
if (f.Name == field.Name && f.FieldType == field.FieldType)
|
||||
{
|
||||
any = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!any)
|
||||
#else
|
||||
if (!fieldsList.Any(f => f.Name == field.Name && f.FieldType == field.FieldType))
|
||||
#endif
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
46
SabreTools.IO/Extensions/StreamExtensions.cs
Normal file
46
SabreTools.IO/Extensions/StreamExtensions.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System.IO;
|
||||
|
||||
namespace SabreTools.IO.Extensions
|
||||
{
|
||||
public static class StreamExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Seek to a specific point in the stream, if possible
|
||||
/// </summary>
|
||||
/// <param name="input">Input stream to try seeking on</param>
|
||||
/// <param name="offset">Optional offset to seek to</param>
|
||||
public static long SeekIfPossible(this Stream input, long offset = 0)
|
||||
{
|
||||
// If the stream is null, don't even try
|
||||
if (input == null)
|
||||
return -1;
|
||||
|
||||
// If the input is not seekable, just return the current position
|
||||
if (!input.CanSeek)
|
||||
{
|
||||
try
|
||||
{
|
||||
return input.Position;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
// Attempt to seek to the offset
|
||||
try
|
||||
{
|
||||
if (offset < 0)
|
||||
return input.Seek(offset, SeekOrigin.End);
|
||||
else if (offset >= 0)
|
||||
return input.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
return input.Position;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
864
SabreTools.IO/Extensions/StreamReaderExtensions.cs
Normal file
864
SabreTools.IO/Extensions/StreamReaderExtensions.cs
Normal 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, {length} requested");
|
||||
|
||||
// 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 {length} bytes from {nameof(stream)}, {read} returned");
|
||||
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
820
SabreTools.IO/Extensions/StreamWriterExtensions.cs
Normal file
820
SabreTools.IO/Extensions/StreamWriterExtensions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.Xml;
|
||||
|
||||
namespace SabreTools.IO
|
||||
namespace SabreTools.IO.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Additional methods for XmlTextWriter
|
||||
@@ -1,140 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace SabreTools.IO
|
||||
{
|
||||
/// <summary>
|
||||
/// Methods around path operations
|
||||
/// </summary>
|
||||
public static class IOExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Ensure the output directory is a proper format and can be created
|
||||
/// </summary>
|
||||
/// <param name="dir">Directory to check</param>
|
||||
/// <param name="create">True if the directory should be created, false otherwise (default)</param>
|
||||
/// <returns>Full path to the directory</returns>
|
||||
public static string Ensure(this string? dir, bool create = false)
|
||||
{
|
||||
// If the output directory is invalid
|
||||
if (string.IsNullOrEmpty(dir))
|
||||
dir = PathTool.GetRuntimeDirectory();
|
||||
|
||||
// Get the full path for the output directory
|
||||
dir = Path.GetFullPath(dir!.Trim('"'));
|
||||
|
||||
// If we're creating the output folder, do so
|
||||
if (create && !Directory.Exists(dir))
|
||||
Directory.CreateDirectory(dir);
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines a text file's encoding by analyzing its byte order mark (BOM).
|
||||
/// Defaults to ASCII when detection of the text file's endianness fails.
|
||||
/// </summary>
|
||||
/// <param name="filename">The text file to analyze.</param>
|
||||
/// <returns>The detected encoding.</returns>
|
||||
/// <link>http://stackoverflow.com/questions/3825390/effective-way-to-find-any-files-encoding</link>
|
||||
public static Encoding GetEncoding(this string filename)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
return Encoding.Default;
|
||||
|
||||
if (!File.Exists(filename))
|
||||
return Encoding.Default;
|
||||
|
||||
// Try to open the file
|
||||
try
|
||||
{
|
||||
FileStream file = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
if (file == null)
|
||||
return Encoding.Default;
|
||||
|
||||
// Read the BOM
|
||||
var bom = new byte[4];
|
||||
file.Read(bom, 0, 4);
|
||||
file.Dispose();
|
||||
|
||||
// Disable warning about UTF7 usage
|
||||
#pragma warning disable SYSLIB0001
|
||||
|
||||
// Analyze the BOM
|
||||
if (bom[0] == 0x2b && bom[1] == 0x2f && bom[2] == 0x76) return Encoding.UTF7;
|
||||
if (bom[0] == 0xef && bom[1] == 0xbb && bom[2] == 0xbf) return Encoding.UTF8;
|
||||
if (bom[0] == 0xff && bom[1] == 0xfe) return Encoding.Unicode; //UTF-16LE
|
||||
if (bom[0] == 0xfe && bom[1] == 0xff) return Encoding.BigEndianUnicode; //UTF-16BE
|
||||
if (bom[0] == 0 && bom[1] == 0 && bom[2] == 0xfe && bom[3] == 0xff) return Encoding.UTF32;
|
||||
return Encoding.Default;
|
||||
|
||||
#pragma warning restore SYSLIB0001
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Encoding.Default;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the extension from the path, if possible
|
||||
/// </summary>
|
||||
/// <param name="path">Path to get extension from</param>
|
||||
/// <returns>Extension, if possible</returns>
|
||||
public static string? GetNormalizedExtension(this string? path)
|
||||
{
|
||||
// Check null or empty first
|
||||
if (string.IsNullOrEmpty(path))
|
||||
return null;
|
||||
|
||||
// Get the extension from the path, if possible
|
||||
string? ext = Path.GetExtension(path)?.ToLowerInvariant();
|
||||
|
||||
// Check if the extension is null or empty
|
||||
if (string.IsNullOrEmpty(ext))
|
||||
return null;
|
||||
|
||||
// Make sure that extensions are valid
|
||||
ext = ext!.TrimStart('.');
|
||||
|
||||
return ext;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all empty folders within a root folder
|
||||
/// </summary>
|
||||
/// <param name="root">Root directory to parse</param>
|
||||
/// <returns>IEumerable containing all directories that are empty, an empty enumerable if the root is empty, null otherwise</returns>
|
||||
public static List<string>? ListEmpty(this string? root)
|
||||
{
|
||||
// Check null or empty first
|
||||
if (string.IsNullOrEmpty(root))
|
||||
return null;
|
||||
|
||||
// Then, check if the root exists
|
||||
if (!Directory.Exists(root))
|
||||
return null;
|
||||
|
||||
// If it does and it is empty, return a blank enumerable
|
||||
#if NET20 || NET35
|
||||
if (!Directory.GetFiles(root, "*", SearchOption.AllDirectories).Any())
|
||||
#else
|
||||
if (!Directory.EnumerateFileSystemEntries(root, "*", SearchOption.AllDirectories).Any())
|
||||
#endif
|
||||
return [];
|
||||
|
||||
// Otherwise, get the complete list
|
||||
#if NET20 || NET35
|
||||
return Directory.GetDirectories(root, "*", SearchOption.AllDirectories)
|
||||
.Where(dir => !Directory.GetFiles(dir, "*", SearchOption.AllDirectories).Any())
|
||||
.ToList();
|
||||
#else
|
||||
return Directory.EnumerateDirectories(root, "*", SearchOption.AllDirectories)
|
||||
.Where(dir => !Directory.EnumerateFileSystemEntries(dir, "*", SearchOption.AllDirectories).Any())
|
||||
.ToList();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,9 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
#if NET40_OR_GREATER || NETCOREAPP
|
||||
using System.Linq;
|
||||
#endif
|
||||
using System.Text;
|
||||
using SabreTools.IO.Readers;
|
||||
using SabreTools.IO.Writers;
|
||||
@@ -179,7 +181,17 @@ namespace SabreTools.IO
|
||||
using IniWriter writer = new(stream, Encoding.UTF8);
|
||||
|
||||
// Order the dictionary by keys to link sections together
|
||||
#if NET20 || NET35
|
||||
var orderedKeyValuePairs = new List<KeyValuePair<string, string?>>();
|
||||
foreach (var kvp in _keyValuePairs)
|
||||
{
|
||||
orderedKeyValuePairs.Add(kvp);
|
||||
}
|
||||
|
||||
orderedKeyValuePairs.Sort((x, y) => x.Key.CompareTo(y.Key));
|
||||
#else
|
||||
var orderedKeyValuePairs = _keyValuePairs.OrderBy(kvp => kvp.Key);
|
||||
#endif
|
||||
|
||||
string section = string.Empty;
|
||||
foreach (var keyValuePair in orderedKeyValuePairs)
|
||||
@@ -196,7 +208,13 @@ namespace SabreTools.IO
|
||||
|
||||
// If the key contains an '.', we need to put them back in
|
||||
string newSection = data[0].Trim();
|
||||
#if NET20 || NET35
|
||||
string[] dataKey = new string[data.Length - 1];
|
||||
Array.Copy(data, 1, dataKey, 0, dataKey.Length);
|
||||
key = string.Join(".", dataKey).Trim();
|
||||
#else
|
||||
key = string.Join(".", data.Skip(1).ToArray()).Trim();
|
||||
#endif
|
||||
|
||||
// If we have a new section, write it out
|
||||
if (!string.Equals(newSection, section, StringComparison.OrdinalIgnoreCase))
|
||||
@@ -221,9 +239,39 @@ namespace SabreTools.IO
|
||||
|
||||
#region IDictionary Impelementations
|
||||
|
||||
#if NET20 || NET35
|
||||
public ICollection<string> Keys
|
||||
{
|
||||
get
|
||||
{
|
||||
var keys = _keyValuePairs?.Keys;
|
||||
if (keys == null || keys.Count == 0)
|
||||
return [];
|
||||
|
||||
var keyArr = new string[keys.Count];
|
||||
keys.CopyTo(keyArr, 0);
|
||||
return keyArr;
|
||||
}
|
||||
}
|
||||
|
||||
public ICollection<string?> Values
|
||||
{
|
||||
get
|
||||
{
|
||||
var values = _keyValuePairs?.Values;
|
||||
if (values == null || values.Count == 0)
|
||||
return [];
|
||||
|
||||
var valueArr = new string[values.Count];
|
||||
values.CopyTo(valueArr, 0);
|
||||
return valueArr;
|
||||
}
|
||||
}
|
||||
#else
|
||||
public ICollection<string> Keys => _keyValuePairs?.Keys?.ToArray() ?? [];
|
||||
|
||||
public ICollection<string?> Values => _keyValuePairs?.Values?.ToArray() ?? [];
|
||||
#endif
|
||||
|
||||
public int Count => (_keyValuePairs as ICollection<KeyValuePair<string, string>>)?.Count ?? 0;
|
||||
|
||||
|
||||
@@ -39,12 +39,12 @@ 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
|
||||
if (sanitize)
|
||||
filename = filename.Replace(Path.DirectorySeparatorChar, '-').Replace(Path.AltDirectorySeparatorChar, '-');
|
||||
filename = filename.Replace('\\', '-').Replace('/', '-');
|
||||
|
||||
return filename;
|
||||
}
|
||||
@@ -57,42 +57,45 @@ 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 += '/';
|
||||
|
||||
// Determine the correct subfolder based on the working parent directory
|
||||
int extraLength = workingParent.EndsWith(":")
|
||||
|| workingParent.EndsWith(Path.DirectorySeparatorChar.ToString())
|
||||
|| workingParent.EndsWith(Path.AltDirectorySeparatorChar.ToString()) ? 0 : 1;
|
||||
|| workingParent.EndsWith("\\")
|
||||
|| workingParent.EndsWith("/") ? 0 : 1;
|
||||
|
||||
return Path.GetDirectoryName(Path.Combine(outDir!, CurrentPath.Remove(0, workingParent.Length + extraLength)));
|
||||
string strippedPath = CurrentPath.Remove(0, workingParent.Length + extraLength);
|
||||
string combinedPath = Path.Combine(outDir!, strippedPath);
|
||||
return Path.GetDirectoryName(combinedPath);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Determine if two paths are equal or not
|
||||
/// </summary>
|
||||
@@ -123,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('\\', '/');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SabreTools.Matching;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Matching.Compare;
|
||||
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
#if NET40_OR_GREATER || NETCOREAPP
|
||||
using System.Linq;
|
||||
#endif
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
@@ -295,11 +297,22 @@ namespace SabreTools.IO.Readers
|
||||
s = s.Trim();
|
||||
|
||||
// Now we get each string, divided up as cleanly as possible
|
||||
#if NET20 || NET35
|
||||
var matchList = Regex.Matches(s, InternalPatternAttributesCMP);
|
||||
var matchStrings = new List<string>();
|
||||
foreach (Match m in matchList)
|
||||
{
|
||||
matchStrings.Add(m.Groups[0].Value);
|
||||
}
|
||||
|
||||
string[] matches = matchStrings.ToArray();
|
||||
#else
|
||||
string[] matches = Regex
|
||||
.Matches(s, InternalPatternAttributesCMP)
|
||||
.Cast<Match>()
|
||||
.Select(m => m.Groups[0].Value)
|
||||
.ToArray();
|
||||
#endif
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
#if NET40_OR_GREATER || NETCOREAPP
|
||||
using System.Linq;
|
||||
#endif
|
||||
using System.Text;
|
||||
|
||||
namespace SabreTools.IO.Readers
|
||||
@@ -118,7 +120,13 @@ namespace SabreTools.IO.Readers
|
||||
|
||||
// If the value field contains an '=', we need to put them back in
|
||||
string key = data[0].Trim();
|
||||
#if NET20 || NET35
|
||||
var valueArr = new string[data.Length - 1];
|
||||
Array.Copy(data, 1, valueArr, 0, valueArr.Length);
|
||||
string value = string.Join("=", valueArr).Trim();
|
||||
#else
|
||||
string value = string.Join("=", data.Skip(1).ToArray()).Trim();
|
||||
#endif
|
||||
|
||||
KeyValuePair = new KeyValuePair<string, string>(key, value);
|
||||
RowType = IniRowType.KeyValue;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
#if NET40_OR_GREATER || NETCOREAPP
|
||||
using System.Linq;
|
||||
#endif
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
@@ -124,7 +126,11 @@ namespace SabreTools.IO.Readers
|
||||
// https://stackoverflow.com/questions/3776458/split-a-comma-separated-string-with-both-quoted-and-unquoted-strings
|
||||
var lineSplitRegex = new Regex($"(?:^|{Separator})(\"(?:[^\"]+|\"\")*\"|[^{Separator}]*)");
|
||||
var temp = new List<string>();
|
||||
#if NET20 || NET35
|
||||
foreach (Match? match in lineSplitRegex.Matches(fullLine))
|
||||
#else
|
||||
foreach (Match? match in lineSplitRegex.Matches(fullLine).Cast<Match?>())
|
||||
#endif
|
||||
{
|
||||
string? curr = match?.Value;
|
||||
if (curr == null)
|
||||
@@ -143,7 +149,17 @@ namespace SabreTools.IO.Readers
|
||||
// Otherwise, just split on the delimiter
|
||||
else
|
||||
{
|
||||
Line = fullLine.Split(Separator).Select(f => f.Trim()).ToList();
|
||||
#if NET20 || NET35
|
||||
Line = new List<string>();
|
||||
foreach (string f in fullLine.Split(Separator))
|
||||
{
|
||||
Line.Add(f.Trim());
|
||||
}
|
||||
#else
|
||||
Line = fullLine.Split(Separator)
|
||||
.Select(f => f.Trim())
|
||||
.ToList();
|
||||
#endif
|
||||
}
|
||||
|
||||
// If we don't have a header yet and are expecting one, read this as the header
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Assembly Properties -->
|
||||
@@ -7,7 +7,8 @@
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<Version>1.3.2</Version>
|
||||
<Version>1.4.12</Version>
|
||||
<WarningsNotAsErrors>CS0618</WarningsNotAsErrors>
|
||||
|
||||
<!-- Package Properties -->
|
||||
<Authors>Matt Nadareski</Authors>
|
||||
@@ -22,16 +23,11 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="../README.md" Pack="true" PackagePath=""/>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Support for old .NET versions -->
|
||||
<ItemGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`))">
|
||||
<PackageReference Include="Net30.LinqBridge" Version="1.3.0" />
|
||||
<None Include="../README.md" Pack="true" PackagePath="" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SabreTools.Matching" Version="1.3.1" />
|
||||
<PackageReference Include="SabreTools.Matching" Version="1.3.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,272 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace SabreTools.IO
|
||||
{
|
||||
/// <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 = new byte[1];
|
||||
stream.Read(buffer, 0, 1);
|
||||
return buffer[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a UInt8[] from the stream
|
||||
/// </summary>
|
||||
public static byte[]? ReadBytes(this Stream stream, int count)
|
||||
{
|
||||
// If there's an invalid byte count, don't do anything
|
||||
if (count <= 0)
|
||||
return null;
|
||||
|
||||
byte[] buffer = new byte[count];
|
||||
stream.Read(buffer, 0, count);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a Int8 from the stream
|
||||
/// </summary>
|
||||
public static sbyte ReadSByte(this Stream stream)
|
||||
{
|
||||
byte[] buffer = new byte[1];
|
||||
stream.Read(buffer, 0, 1);
|
||||
return (sbyte)buffer[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a Char from the stream
|
||||
/// </summary>
|
||||
public static char ReadChar(this Stream stream)
|
||||
{
|
||||
byte[] buffer = new byte[1];
|
||||
stream.Read(buffer, 0, 1);
|
||||
return (char)buffer[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a Int16 from the stream
|
||||
/// </summary>
|
||||
public static short ReadInt16(this Stream stream)
|
||||
{
|
||||
byte[] buffer = new byte[2];
|
||||
stream.Read(buffer, 0, 2);
|
||||
return BitConverter.ToInt16(buffer, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a Int16 from the stream in big-endian format
|
||||
/// </summary>
|
||||
public static short ReadInt16BigEndian(this Stream stream)
|
||||
{
|
||||
byte[] buffer = new byte[2];
|
||||
stream.Read(buffer, 0, 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 = new byte[2];
|
||||
stream.Read(buffer, 0, 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 = new byte[2];
|
||||
stream.Read(buffer, 0, 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 = new byte[4];
|
||||
stream.Read(buffer, 0, 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 = new byte[4];
|
||||
stream.Read(buffer, 0, 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 = new byte[4];
|
||||
stream.Read(buffer, 0, 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 = new byte[4];
|
||||
stream.Read(buffer, 0, 4);
|
||||
Array.Reverse(buffer);
|
||||
return BitConverter.ToUInt32(buffer, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a Int64 from the stream
|
||||
/// </summary>
|
||||
public static long ReadInt64(this Stream stream)
|
||||
{
|
||||
byte[] buffer = new byte[8];
|
||||
stream.Read(buffer, 0, 8);
|
||||
return BitConverter.ToInt64(buffer, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a Int64 from the stream in big-endian format
|
||||
/// </summary>
|
||||
public static long ReadInt64BigEndian(this Stream stream)
|
||||
{
|
||||
byte[] buffer = new byte[8];
|
||||
stream.Read(buffer, 0, 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 = new byte[8];
|
||||
stream.Read(buffer, 0, 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 = new byte[8];
|
||||
stream.Read(buffer, 0, 8);
|
||||
Array.Reverse(buffer);
|
||||
return BitConverter.ToUInt64(buffer, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a Guid from the stream
|
||||
/// </summary>
|
||||
public static Guid ReadGuid(this Stream stream)
|
||||
{
|
||||
byte[] buffer = new byte[16];
|
||||
stream.Read(buffer, 0, 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 = new byte[16];
|
||||
stream.Read(buffer, 0, 16);
|
||||
Array.Reverse(buffer);
|
||||
return new Guid(buffer);
|
||||
}
|
||||
|
||||
/// <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(new char[] { '\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>
|
||||
/// Seek to a specific point in the stream, if possible
|
||||
/// </summary>
|
||||
/// <param name="input">Input stream to try seeking on</param>
|
||||
/// <param name="offset">Optional offset to seek to</param>
|
||||
public static long SeekIfPossible(this Stream input, long offset = 0)
|
||||
{
|
||||
// If the stream is null, don't even try
|
||||
if (input == null)
|
||||
return -1;
|
||||
|
||||
// If the input is not seekable, just return the current position
|
||||
if (!input.CanSeek)
|
||||
{
|
||||
try
|
||||
{
|
||||
return input.Position;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
// Attempt to seek to the offset
|
||||
try
|
||||
{
|
||||
if (offset < 0)
|
||||
return input.Seek(offset, SeekOrigin.End);
|
||||
else if (offset >= 0)
|
||||
return input.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
return input.Position;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
239
SabreTools.IO/Streams/ReadOnlyBitStream.cs
Normal file
239
SabreTools.IO/Streams/ReadOnlyBitStream.cs
Normal file
@@ -0,0 +1,239 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SabreTools.IO.Extensions;
|
||||
|
||||
namespace SabreTools.IO.Streams
|
||||
{
|
||||
/// <summary>
|
||||
/// Wrapper to allow reading bits from a source stream
|
||||
/// </summary>
|
||||
public class ReadOnlyBitStream
|
||||
{
|
||||
/// <inheritdoc cref="Stream.Position"/>
|
||||
public long Position => _source.Position;
|
||||
|
||||
/// <inheritdoc cref="Stream.Length"/>
|
||||
public long Length => _source.Length;
|
||||
|
||||
/// <summary>
|
||||
/// Original stream source
|
||||
/// </summary>
|
||||
private readonly Stream _source;
|
||||
|
||||
/// <summary>
|
||||
/// Last read byte value from the stream
|
||||
/// </summary>
|
||||
private byte? _bitBuffer;
|
||||
|
||||
/// <summary>
|
||||
/// Index in the byte of the current bit
|
||||
/// </summary>
|
||||
private int _bitIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new BitStream from a source Stream
|
||||
/// </summary>
|
||||
public ReadOnlyBitStream(Stream source)
|
||||
{
|
||||
_source = source;
|
||||
_bitBuffer = null;
|
||||
_bitIndex = 0;
|
||||
|
||||
// Verify the stream
|
||||
if (!source.CanRead || !source.CanSeek)
|
||||
throw new ArgumentException($"{nameof(source)} needs to be readable and seekable");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Discard the current cached byte
|
||||
/// </summary>
|
||||
public void Discard()
|
||||
{
|
||||
_bitBuffer = null;
|
||||
_bitIndex = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a single bit, if possible
|
||||
/// </summary>
|
||||
/// <returns>The next bit encoded in a byte, null on error or end of stream</returns>
|
||||
public byte? ReadBit()
|
||||
{
|
||||
// If we reached the end of the stream
|
||||
if (_source.Position >= _source.Length)
|
||||
return null;
|
||||
|
||||
// If we don't have a value cached
|
||||
if (_bitBuffer == null)
|
||||
{
|
||||
// Read the next byte, if possible
|
||||
_bitBuffer = ReadSourceByte();
|
||||
if (_bitBuffer == null)
|
||||
return null;
|
||||
|
||||
// Reset the bit index
|
||||
_bitIndex = 0;
|
||||
}
|
||||
|
||||
// Get the value by bit-shifting
|
||||
int value = _bitBuffer.Value & 0x01;
|
||||
_bitBuffer = (byte?)(_bitBuffer >> 1);
|
||||
_bitIndex++;
|
||||
|
||||
// Reset the byte if we're at the end
|
||||
if (_bitIndex >= 8)
|
||||
Discard();
|
||||
|
||||
return (byte)value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a multiple bits in LSB, if possible
|
||||
/// </summary>
|
||||
/// <returns>The next bits encoded in a UInt32, null on error or end of stream</returns>
|
||||
public uint? ReadBitsLSB(int bits)
|
||||
{
|
||||
uint value = 0;
|
||||
for (int i = 0; i < bits; i++)
|
||||
{
|
||||
// Read the next bit
|
||||
byte? bitValue = ReadBit();
|
||||
if (bitValue == null)
|
||||
return null;
|
||||
|
||||
// Add the bit shifted by the current index
|
||||
value += (uint)(bitValue.Value << i);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a multiple bits in MSB, if possible
|
||||
/// </summary>
|
||||
/// <returns>The next bits encoded in a UInt32, null on error or end of stream</returns>
|
||||
public uint? ReadBitsMSB(int bits)
|
||||
{
|
||||
uint value = 0;
|
||||
for (int i = 0; i < bits; i++)
|
||||
{
|
||||
// Read the next bit
|
||||
byte? bitValue = ReadBit();
|
||||
if (bitValue == null)
|
||||
return null;
|
||||
|
||||
// Add the bit shifted by the current index
|
||||
value = (value << 1) + bitValue.Value;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a byte, if possible
|
||||
/// </summary>
|
||||
/// <returns>The next byte, null on error or end of stream</returns>
|
||||
/// <remarks>Assumes the stream is byte-aligned</remarks>
|
||||
public byte? ReadByte()
|
||||
{
|
||||
try
|
||||
{
|
||||
Discard();
|
||||
return _source.ReadByteValue();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a UInt16, if possible
|
||||
/// </summary>
|
||||
/// <returns>The next UInt16, null on error or end of stream</returns>
|
||||
/// <remarks>Assumes the stream is byte-aligned</remarks>
|
||||
public ushort? ReadUInt16()
|
||||
{
|
||||
try
|
||||
{
|
||||
Discard();
|
||||
return _source.ReadUInt16();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a UInt32, if possible
|
||||
/// </summary>
|
||||
/// <returns>The next UInt32, null on error or end of stream</returns>
|
||||
/// <remarks>Assumes the stream is byte-aligned</remarks>
|
||||
public uint? ReadUInt32()
|
||||
{
|
||||
try
|
||||
{
|
||||
Discard();
|
||||
return _source.ReadUInt32();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a UInt64, if possible
|
||||
/// </summary>
|
||||
/// <returns>The next UInt64, null on error or end of stream</returns>
|
||||
/// <remarks>Assumes the stream is byte-aligned</remarks>
|
||||
public ulong? ReadUInt64()
|
||||
{
|
||||
try
|
||||
{
|
||||
Discard();
|
||||
return _source.ReadUInt64();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read <paramref name="bytes"/> bytes, if possible
|
||||
/// </summary>
|
||||
/// <param name="bytes">Number of bytes to read</param>
|
||||
/// <returns>The next <paramref name="bytes"/> bytes, null on error or end of stream</returns>
|
||||
/// <remarks>Assumes the stream is byte-aligned</remarks>
|
||||
public byte[]? ReadBytes(int bytes)
|
||||
{
|
||||
try
|
||||
{
|
||||
Discard();
|
||||
return _source.ReadBytes(bytes);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a single byte from the underlying stream, if possible
|
||||
/// </summary>
|
||||
/// <returns>The next full byte from the stream, null on error or end of stream</returns>
|
||||
private byte? ReadSourceByte()
|
||||
{
|
||||
try
|
||||
{
|
||||
return _source.ReadByteValue();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
281
SabreTools.IO/Streams/ReadOnlyCompositeStream.cs
Normal file
281
SabreTools.IO/Streams/ReadOnlyCompositeStream.cs
Normal file
@@ -0,0 +1,281 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
#if NET40_OR_GREATER || NETCOREAPP
|
||||
using System.Linq;
|
||||
#endif
|
||||
|
||||
namespace SabreTools.IO.Streams
|
||||
{
|
||||
/// <summary>
|
||||
/// Read-only stream wrapper around multiple, consecutive streams
|
||||
/// </summary>
|
||||
public class ReadOnlyCompositeStream : Stream
|
||||
{
|
||||
#region Properties
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanRead => true;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanSeek => true;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanWrite => false;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override long Length => _length;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override long Position
|
||||
{
|
||||
get => _position;
|
||||
set
|
||||
{
|
||||
_position = value;
|
||||
if (_position < 0)
|
||||
_position = 0;
|
||||
else if (_position >= _length)
|
||||
_position = _length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal State
|
||||
|
||||
/// <summary>
|
||||
/// Internal collection of streams to read from
|
||||
/// </summary>
|
||||
private readonly List<Stream> _streams;
|
||||
|
||||
/// <summary>
|
||||
/// Total length of all internal streams
|
||||
/// </summary>
|
||||
private long _length;
|
||||
|
||||
/// <summary>
|
||||
/// Overall position in the stream wrapper
|
||||
/// </summary>
|
||||
private long _position;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Create a new, empty ReadOnlyCompositeStream
|
||||
/// </summary>
|
||||
public ReadOnlyCompositeStream()
|
||||
{
|
||||
_streams = [];
|
||||
_length = 0;
|
||||
_position = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new ReadOnlyCompositeStream from a single Stream
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
public ReadOnlyCompositeStream(Stream stream)
|
||||
{
|
||||
_streams = [stream];
|
||||
_length = 0;
|
||||
_position = 0;
|
||||
|
||||
// Verify the stream and add to the length
|
||||
if (!stream.CanRead || !stream.CanSeek)
|
||||
throw new ArgumentException($"{nameof(stream)} needs to be readable and seekable");
|
||||
|
||||
_length += stream.Length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new ReadOnlyCompositeStream from an existing collection of Streams
|
||||
/// </summary>
|
||||
public ReadOnlyCompositeStream(Stream[] streams)
|
||||
{
|
||||
_streams = [.. streams];
|
||||
_length = 0;
|
||||
_position = 0;
|
||||
|
||||
// Verify the streams and add to the length
|
||||
foreach (var stream in streams)
|
||||
{
|
||||
if (!stream.CanRead || !stream.CanSeek)
|
||||
throw new ArgumentException($"All members of {nameof(streams)} need to be readable and seekable");
|
||||
|
||||
_length += stream.Length;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new ReadOnlyCompositeStream from an existing collection of Streams
|
||||
/// </summary>
|
||||
public ReadOnlyCompositeStream(IEnumerable<Stream> streams)
|
||||
{
|
||||
#if NET20 || NET35
|
||||
_streams = new List<Stream>(streams);
|
||||
#else
|
||||
_streams = streams.ToList();
|
||||
#endif
|
||||
_length = 0;
|
||||
_position = 0;
|
||||
|
||||
// Verify the streams and add to the length
|
||||
foreach (var stream in streams)
|
||||
{
|
||||
if (!stream.CanRead || !stream.CanSeek)
|
||||
throw new ArgumentException($"All members of {nameof(streams)} need to be readable and seekable");
|
||||
|
||||
_length += stream.Length;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a new stream to the collection
|
||||
/// </summary>
|
||||
public bool AddStream(Stream stream)
|
||||
{
|
||||
// Verify the stream
|
||||
if (!stream.CanRead || !stream.CanSeek)
|
||||
return false;
|
||||
|
||||
// Add the stream to the end
|
||||
_streams.Add(stream);
|
||||
_length += stream.Length;
|
||||
return true;
|
||||
}
|
||||
|
||||
#region Stream Implementations
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Flush() => throw new NotImplementedException();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
// Determine which stream we start reading from
|
||||
int streamIndex = DetermineStreamIndex(_position, out long streamOffset);
|
||||
if (streamIndex == -1)
|
||||
return 0;
|
||||
|
||||
// Determine if the stream fully contains the requested segment
|
||||
bool singleStream = StreamContains(streamIndex, streamOffset, count);
|
||||
|
||||
// If we can read from a single stream
|
||||
if (singleStream)
|
||||
{
|
||||
_position += count;
|
||||
_streams[streamIndex].Seek(streamOffset, SeekOrigin.Begin);
|
||||
return _streams[streamIndex].Read(buffer, offset, count);
|
||||
}
|
||||
|
||||
// For all other cases, we read until there's no more
|
||||
int readBytes = 0, originalCount = count;
|
||||
while (readBytes < originalCount)
|
||||
{
|
||||
// Determine how much can be read from the current stream
|
||||
long currentBytes = _streams[streamIndex].Length - streamOffset;
|
||||
int shouldRead = Math.Min((int)currentBytes, count);
|
||||
|
||||
// Read from the current stream
|
||||
_position += shouldRead;
|
||||
_streams[streamIndex].Seek(streamOffset, SeekOrigin.Begin);
|
||||
readBytes += _streams[streamIndex].Read(buffer, offset, shouldRead);
|
||||
|
||||
// Update the read variables
|
||||
offset += shouldRead;
|
||||
count -= shouldRead;
|
||||
|
||||
// Move to the next stream
|
||||
streamIndex++;
|
||||
streamOffset = 0;
|
||||
|
||||
// Validate the next stream exists
|
||||
if (streamIndex >= _streams.Count)
|
||||
break;
|
||||
}
|
||||
|
||||
// Return the number of bytes that could be read
|
||||
return readBytes;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
// Handle the "seek"
|
||||
switch (origin)
|
||||
{
|
||||
case SeekOrigin.Begin: _position = offset; break;
|
||||
case SeekOrigin.Current: _position += offset; break;
|
||||
case SeekOrigin.End: _position = _length - offset - 1; break;
|
||||
default: throw new ArgumentException($"Invalid value for {nameof(origin)}");
|
||||
};
|
||||
|
||||
// Handle out-of-bounds seeks
|
||||
if (_position < 0)
|
||||
_position = 0;
|
||||
else if (_position >= _length)
|
||||
_position = _length - 1;
|
||||
|
||||
return _position;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void SetLength(long value) => throw new NotImplementedException();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Write(byte[] buffer, int offset, int count) => throw new NotImplementedException();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Determine the index of the stream that contains a particular offset
|
||||
/// </summary>
|
||||
/// <param name="realOffset">Output parameter representing the real offset in the stream, -1 on error</param>
|
||||
/// <returns>Index of the stream containing the offset, -1 on error</returns>
|
||||
private int DetermineStreamIndex(long offset, out long realOffset)
|
||||
{
|
||||
// If the offset is out of bounds
|
||||
if (offset < 0 || offset >= _length)
|
||||
{
|
||||
realOffset = -1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Seek through until we hit the correct offset
|
||||
long currentLength = 0;
|
||||
for (int i = 0; i < _streams.Count; i++)
|
||||
{
|
||||
currentLength += _streams[i].Length;
|
||||
if (currentLength > offset)
|
||||
{
|
||||
realOffset = offset - (currentLength - _streams[i].Length);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
// Should never happen
|
||||
realOffset = -1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if a stream contains a particular segment
|
||||
/// </summary>
|
||||
private bool StreamContains(int streamIndex, long offset, int length)
|
||||
{
|
||||
// Ensure the arguments are valid
|
||||
if (streamIndex < 0 || streamIndex >= _streams.Count)
|
||||
throw new ArgumentOutOfRangeException(nameof(streamIndex));
|
||||
if (offset < 0 || offset >= _streams[streamIndex].Length)
|
||||
throw new ArgumentOutOfRangeException(nameof(offset));
|
||||
|
||||
// Handle the general case
|
||||
return _streams[streamIndex].Length - offset >= length;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
36
publish-nix.sh
Executable file
36
publish-nix.sh
Executable 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
26
publish-win.ps1
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user