61 Commits

Author SHA1 Message Date
Matt Nadareski
8f3a9d0ede Bump version 2025-05-12 08:19:12 -04:00
Matt Nadareski
4400f0bb87 Update to Matching 1.5.2 2025-05-12 08:18:40 -04:00
Matt Nadareski
9f12ee642d Update copyright 2024-12-30 21:22:05 -05:00
Matt Nadareski
b084b13ec3 Remove unnecessary action step 2024-12-30 21:21:45 -05:00
Matt Nadareski
93b3accbfd Ensure .NET versions are installed for testing 2024-12-19 10:50:26 -05:00
Matt Nadareski
e96c3114d9 Ensure .NET versions are installed for testing 2024-12-19 10:47:29 -05:00
Matt Nadareski
04f095018c Bump version 2024-12-16 13:59:19 -05:00
Matt Nadareski
ff18049832 Add explicit value building to extensions 2024-12-16 13:55:32 -05:00
Matt Nadareski
5e7d2901c8 Update IO to 1.5.1 2024-12-16 12:22:32 -05:00
Matt Nadareski
add7bc8ea5 Allow symbols to be packed 2024-12-16 12:20:05 -05:00
Matt Nadareski
f95853cc87 Use publish script and update README 2024-12-06 10:59:42 -05:00
Matt Nadareski
3f146d45a8 Bump version 2024-11-29 19:54:37 -05:00
Matt Nadareski
f3689087e6 Add alignment tests 2024-11-28 21:31:38 -05:00
Matt Nadareski
d2d191d86f Add boundary alignment stream extension 2024-11-28 21:17:33 -05:00
Matt Nadareski
d6cc7faea8 Bump version 2024-11-26 09:47:52 -05:00
Matt Nadareski
3b56150cc9 Fix getopts in publish script 2024-11-26 00:37:08 -05:00
Matt Nadareski
a7b50dfdf2 Fix bitstream read naming again 2024-11-25 23:12:05 -05:00
Matt Nadareski
0b7ab5b932 Add more Bitstream tests 2024-11-25 22:44:40 -05:00
Matt Nadareski
e43560cbbd Add logging tests 2024-11-25 22:33:11 -05:00
Matt Nadareski
861bfdc4f4 Update SV reader/writer and add tests 2024-11-25 22:10:41 -05:00
Matt Nadareski
933dd70654 Add INI reader/writer tests 2024-11-25 21:59:36 -05:00
Matt Nadareski
0d2a2a3b7d Update CMP reader/writer and add tests 2024-11-25 21:52:53 -05:00
Matt Nadareski
6ad4872bd4 Update PathTool and add tests 2024-11-25 21:19:45 -05:00
Matt Nadareski
8c0f54c059 Fix build for old .NET 2024-11-25 21:05:43 -05:00
Matt Nadareski
ab1b0646c4 Minor cleanup in ParentablePath 2024-11-25 21:04:09 -05:00
Matt Nadareski
450d8aab11 Update INI file and add tests 2024-11-25 21:01:52 -05:00
Matt Nadareski
ec8908aec0 Update XML writer extensions and add tests 2024-11-25 20:35:02 -05:00
Matt Nadareski
fb7ca7cde0 Update stream extensions and add tests 2024-11-25 17:09:33 -05:00
Matt Nadareski
67ca20f71b Update IO extensions and add tests 2024-11-25 16:52:49 -05:00
Matt Nadareski
ae3a27eee1 Update writer extensions and add tests 2024-11-25 16:15:12 -05:00
Matt Nadareski
3b7f910a98 Cover some bad SafeEnumerate paths 2024-11-25 15:23:51 -05:00
Matt Nadareski
244edc132f Remove unnecessary try/catch 2024-11-25 15:16:04 -05:00
Matt Nadareski
cec495d55a Update byte array extensions and add tests 2024-11-25 15:14:50 -05:00
Matt Nadareski
7656734bb2 Update reader extensions and add tests 2024-11-25 15:03:07 -05:00
Matt Nadareski
fa9310de39 Update packages 2024-11-25 13:06:17 -05:00
Matt Nadareski
fe466dfe25 Move IsNullOrEmpty from Matching 2024-11-25 11:29:22 -05:00
Matt Nadareski
d2c59c565f Make bitstream tests more robust; add notes 2024-11-18 11:26:54 -05:00
Matt Nadareski
4e221f33d5 Had it right the first time 2024-11-18 10:58:10 -05:00
Matt Nadareski
c5c8ce67ba Rename LSB/MSB to LE/BE 2024-11-18 10:30:55 -05:00
Matt Nadareski
8e3d204329 Fix LSB bit reads 2024-11-18 10:26:00 -05:00
Matt Nadareski
200d947f30 Bump version 2024-11-15 20:41:39 -05:00
Matt Nadareski
dfccdcfb05 Update Matching to 1.4.1 2024-11-15 20:40:31 -05:00
Matt Nadareski
72ff3ead48 Port extension attribute instead of framework gating 2024-11-15 20:40:01 -05:00
Matt Nadareski
fb15aecb87 Framework only matters for executable 2024-11-15 20:35:51 -05:00
Matt Nadareski
897e54ca61 Bump version 2024-11-13 00:44:44 -05:00
Matt Nadareski
3af6bc8365 Add .NET 9 to target frameworks 2024-11-13 00:44:16 -05:00
Matt Nadareski
de05bae3f8 Be smarter about framework gating 2024-11-05 21:47:43 -05:00
Matt Nadareski
97d603abb7 Update Matching to 1.3.4 2024-11-05 20:57:53 -05:00
Matt Nadareski
ed32302447 Update Matching to 1.3.3 2024-10-26 19:45:12 -04:00
Matt Nadareski
c125dc4ec0 Bump version 2024-10-24 00:22:28 -04:00
Matt Nadareski
f154ae47c0 Disable warnings, don't ignore them 2024-10-24 00:20:46 -04:00
Matt Nadareski
0d0e960b98 Use converters properly 2024-10-24 00:17:09 -04:00
Matt Nadareski
4a9f84ab66 Add LogLevel enum converters 2024-10-24 00:10:50 -04:00
Matt Nadareski
39277ee443 Add generic byte array extensions 2024-10-24 00:06:58 -04:00
Matt Nadareski
ed367ace6d Import logger from SabreTools 2024-10-24 00:04:50 -04:00
Matt Nadareski
80e72832a4 Add WORD/DWORD extensions, for fun 2024-10-15 20:52:32 -04:00
Matt Nadareski
8924a50432 Bump version 2024-10-01 13:24:53 -04:00
Matt Nadareski
97f00a2565 Update packages 2024-10-01 13:23:04 -04:00
Matt Nadareski
f35231d95b Remove Linq requirement from old .NET 2024-10-01 02:32:38 -04:00
Matt Nadareski
96c6bba93e Bump version 2024-05-13 16:19:17 -04:00
Matt Nadareski
b0d81f225b Fix thrown exception statements 2024-05-12 10:56:15 -04:00
66 changed files with 4878 additions and 807 deletions

View File

@@ -1,4 +1,4 @@
name: Nuget Pack
name: Build and Test
on:
push:
@@ -16,31 +16,22 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
dotnet-version: |
6.0.x
8.0.x
9.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build library
run: dotnet build
- name: Run tests
run: dotnet test
- name: Pack
run: dotnet pack
- name: Upload build
uses: actions/upload-artifact@v4
with:
name: 'Nuget Package'
path: 'SabreTools.IO/bin/Release/*.nupkg'
- name: Run publish script
run: ./publish-nix.sh
- name: Upload to rolling
uses: ncipollo/release-action@v1.14.0
with:
allowUpdates: True
artifacts: 'SabreTools.IO/bin/Release/*.nupkg'
artifacts: "*.nupkg,*.snupkg"
body: 'Last built commit: ${{ github.sha }}'
name: 'Rolling Release'
prerelease: True

View File

@@ -11,10 +11,13 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
dotnet-version: |
6.0.x
8.0.x
9.0.x
- name: Build
run: dotnet build
- name: Run tests
run: dotnet test
run: dotnet test

View File

@@ -1,5 +1,7 @@
# SabreTools.IO
[![Build and Test](https://github.com/SabreTools/SabreTools.IO/actions/workflows/build_and_test.yml/badge.svg)](https://github.com/SabreTools/SabreTools.IO/actions/workflows/build_and_test.yml)
This library comprises I/O functionality for the following file types:
- ClrMamePro-derived Metadata files
@@ -9,3 +11,9 @@ This library comprises I/O functionality for the following file types:
There are also some extensions that are useful for wrapping common functionality required by SabreTools.
Find the link to the Nuget package [here](https://www.nuget.org/packages/SabreTools.IO).
## Releases
For the most recent stable build, download the latest release here: [Releases Page](https://github.com/SabreTools/SabreTools.IO/releases)
For the latest WIP build here: [Rolling Release](https://github.com/SabreTools/SabreTools.IO/releases/rolling)

View File

@@ -4,12 +4,12 @@ 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 string reading tests
public class BinaryReaderExtensionsTests
{
/// <summary>
@@ -30,6 +30,50 @@ namespace SabreTools.IO.Test.Extensions
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00,
];
[Fact]
public void ReadByteArrayTest()
{
byte[] arr = new byte[4];
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
int read = br.Read(arr, 0, 4);
Assert.Equal(4, read);
Assert.True(arr.SequenceEqual(_bytes.Take(4)));
}
[Fact]
public void ReadByteArrayBigEndianTest()
{
byte[] arr = new byte[4];
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
int read = br.ReadBigEndian(arr, 0, 4);
Assert.Equal(4, read);
Assert.True(arr.SequenceEqual(_bytes.Take(4).Reverse()));
}
[Fact]
public void ReadCharArrayTest()
{
char[] arr = new char[4];
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
int read = br.Read(arr, 0, 4);
Assert.Equal(4, read);
Assert.True(arr.SequenceEqual(_bytes.Take(4).Select(b => (char)b)));
}
[Fact]
public void ReadCharArrayBigEndianTest()
{
char[] arr = new char[4];
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
int read = br.ReadBigEndian(arr, 0, 4);
Assert.Equal(4, read);
Assert.True(arr.SequenceEqual(_bytes.Take(4).Select(b => (char)b).Reverse()));
}
[Fact]
public void ReadByteTest()
{
@@ -50,6 +94,39 @@ namespace SabreTools.IO.Test.Extensions
Assert.True(read.SequenceEqual(_bytes.Take(length)));
}
[Fact]
public void ReadBytesBigEndianTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
int length = 4;
byte[] read = br.ReadBytesBigEndian(length);
Assert.Equal(length, read.Length);
Assert.True(read.SequenceEqual(_bytes.Take(length).Reverse()));
}
[Fact]
public void ReadCharsTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
int length = 4;
char[] read = br.ReadChars(length);
Assert.Equal(length, read.Length);
Assert.True(read.SequenceEqual(_bytes.Take(length).Select(b => (char)b)));
}
[Fact]
public void ReadCharsBigEndianTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
int length = 4;
char[] read = br.ReadCharsBigEndian(length);
Assert.Equal(length, read.Length);
Assert.True(read.SequenceEqual(_bytes.Take(length).Select(b => (char)b).Reverse()));
}
[Fact]
public void ReadSByteTest()
{
@@ -86,6 +163,15 @@ namespace SabreTools.IO.Test.Extensions
Assert.Equal(0x0001, read);
}
[Fact]
public void ReadInt16LittleEndianTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
short read = br.ReadInt16LittleEndian();
Assert.Equal(0x0100, read);
}
[Fact]
public void ReadUInt16Test()
{
@@ -104,6 +190,42 @@ namespace SabreTools.IO.Test.Extensions
Assert.Equal(0x0001, read);
}
[Fact]
public void ReadUInt16LittleEndianTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
ushort read = br.ReadUInt16LittleEndian();
Assert.Equal(0x0100, read);
}
[Fact]
public void ReadWORDTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
ushort read = br.ReadWORD();
Assert.Equal(0x0100, read);
}
[Fact]
public void ReadWORDBigEndianTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
ushort read = br.ReadWORDBigEndian();
Assert.Equal(0x0001, read);
}
[Fact]
public void ReadWORDLittleEndianTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
ushort read = br.ReadWORDLittleEndian();
Assert.Equal(0x0100, read);
}
#if NET6_0_OR_GREATER
[Fact]
public void ReadHalfTest()
@@ -144,6 +266,15 @@ namespace SabreTools.IO.Test.Extensions
Assert.Equal(0x000102, read);
}
[Fact]
public void ReadInt24LittleEndianTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
int read = br.ReadInt24LittleEndian();
Assert.Equal(0x020100, read);
}
[Fact]
public void ReadUInt24Test()
{
@@ -162,6 +293,15 @@ namespace SabreTools.IO.Test.Extensions
Assert.Equal((uint)0x000102, read);
}
[Fact]
public void ReadUInt24LittleEndianTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
uint read = br.ReadUInt24LittleEndian();
Assert.Equal((uint)0x020100, read);
}
[Fact]
public void ReadInt32Test()
{
@@ -180,6 +320,15 @@ namespace SabreTools.IO.Test.Extensions
Assert.Equal(0x00010203, read);
}
[Fact]
public void ReadInt32LittleEndianTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
int read = br.ReadInt32LittleEndian();
Assert.Equal(0x03020100, read);
}
[Fact]
public void ReadUInt32Test()
{
@@ -198,6 +347,42 @@ namespace SabreTools.IO.Test.Extensions
Assert.Equal((uint)0x00010203, read);
}
[Fact]
public void ReadUInt32LittleEndianTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
uint read = br.ReadUInt32LittleEndian();
Assert.Equal((uint)0x03020100, read);
}
[Fact]
public void ReadDWORDTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
uint read = br.ReadDWORD();
Assert.Equal((uint)0x03020100, read);
}
[Fact]
public void ReadDWORDBigEndianTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
uint read = br.ReadDWORDBigEndian();
Assert.Equal((uint)0x00010203, read);
}
[Fact]
public void ReadDWORDLittleEndianTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
uint read = br.ReadDWORDLittleEndian();
Assert.Equal((uint)0x03020100, read);
}
[Fact]
public void ReadSingleTest()
{
@@ -236,6 +421,15 @@ namespace SabreTools.IO.Test.Extensions
Assert.Equal(0x000102030405, read);
}
[Fact]
public void ReadInt48LittleEndianTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
long read = br.ReadInt48LittleEndian();
Assert.Equal(0x050403020100, read);
}
[Fact]
public void ReadUInt48Test()
{
@@ -254,6 +448,15 @@ namespace SabreTools.IO.Test.Extensions
Assert.Equal((ulong)0x000102030405, read);
}
[Fact]
public void ReadUInt48LittleEndianTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
ulong read = br.ReadUInt48LittleEndian();
Assert.Equal((ulong)0x050403020100, read);
}
[Fact]
public void ReadInt64Test()
{
@@ -272,6 +475,15 @@ namespace SabreTools.IO.Test.Extensions
Assert.Equal(0x0001020304050607, read);
}
[Fact]
public void ReadInt64LittleEndianTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
long read = br.ReadInt64LittleEndian();
Assert.Equal(0x0706050403020100, read);
}
[Fact]
public void ReadUInt64Test()
{
@@ -290,6 +502,42 @@ namespace SabreTools.IO.Test.Extensions
Assert.Equal((ulong)0x0001020304050607, read);
}
[Fact]
public void ReadUInt64LittleEndianTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
ulong read = br.ReadUInt64LittleEndian();
Assert.Equal((ulong)0x0706050403020100, read);
}
[Fact]
public void ReadQWORDTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
ulong read = br.ReadQWORD();
Assert.Equal((ulong)0x0706050403020100, read);
}
[Fact]
public void ReadQWORDBigEndianTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
ulong read = br.ReadQWORDBigEndian();
Assert.Equal((ulong)0x0001020304050607, read);
}
[Fact]
public void ReadQWORDLittleEndianTest()
{
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
ulong read = br.ReadQWORDLittleEndian();
Assert.Equal((ulong)0x0706050403020100, read);
}
[Fact]
public void ReadDoubleTest()
{
@@ -394,6 +642,88 @@ namespace SabreTools.IO.Test.Extensions
}
#endif
[Fact]
public void ReadNullTerminatedStringTest()
{
// Encoding.ASCII
byte[] bytes = [0x41, 0x42, 0x43, 0x00];
var stream = new MemoryStream(bytes);
var br = new BinaryReader(stream);
string? actual = br.ReadNullTerminatedString(Encoding.ASCII);
Assert.Equal("ABC", actual);
// Encoding.UTF8
bytes = [0x41, 0x42, 0x43, 0x00];
stream = new MemoryStream(bytes);
br = new BinaryReader(stream);
actual = br.ReadNullTerminatedString(Encoding.UTF8);
Assert.Equal("ABC", actual);
// Encoding.Unicode
bytes = [0x41, 0x00, 0x42, 0x00, 0x43, 0x00, 0x00, 0x00];
stream = new MemoryStream(bytes);
br = new BinaryReader(stream);
actual = br.ReadNullTerminatedString(Encoding.Unicode);
Assert.Equal("ABC", actual);
// Encoding.UTF32
bytes = [0x41, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
stream = new MemoryStream(bytes);
br = new BinaryReader(stream);
actual = br.ReadNullTerminatedString(Encoding.UTF32);
Assert.Equal("ABC", actual);
// Encoding.Latin1
bytes = [0x41, 0x42, 0x43, 0x00];
stream = new MemoryStream(bytes);
br = new BinaryReader(stream);
actual = br.ReadNullTerminatedString(Encoding.Latin1);
Assert.Equal("ABC", actual);
}
[Fact]
public void ReadTypeTest()
{
// Guid
var stream = new MemoryStream(_bytes);
var br = new BinaryReader(stream);
var expectedGuid = new Guid(_bytes);
Guid actualGuid = br.ReadType<Guid>();
Assert.Equal(expectedGuid, actualGuid);
#if NET6_0_OR_GREATER
// Half
stream = new MemoryStream(_bytes);
br = new BinaryReader(stream);
Half expectedHalf = BitConverter.Int16BitsToHalf(0x0100);
Half actualHalf = br.ReadType<Half>();
Assert.Equal(expectedHalf, actualHalf);
#endif
#if NET7_0_OR_GREATER
// Int128
stream = new MemoryStream(_bytes);
br = new BinaryReader(stream);
Int128 expectedInt128 = (Int128)new BigInteger(_bytes);
Int128 actualInt128 = br.ReadType<Int128>();
Assert.Equal(expectedHalf, actualHalf);
// UInt128
stream = new MemoryStream(_bytes);
br = new BinaryReader(stream);
UInt128 expectedUInt128 = (UInt128)new BigInteger(_bytes);
UInt128 actualUInt128 = br.ReadType<UInt128>();
Assert.Equal(expectedHalf, actualHalf);
#endif
// Enum
stream = new MemoryStream(_bytes);
br = new BinaryReader(stream);
TestEnum expectedTestEnum = (TestEnum)0x03020100;
TestEnum actualTestEnum = br.ReadType<TestEnum>();
Assert.Equal(expectedTestEnum, actualTestEnum);
}
[Fact]
public void ReadTypeExplicitTest()
{
@@ -492,6 +822,12 @@ namespace SabreTools.IO.Test.Extensions
0x05, 0x04, 0x03, 0x02,
0x06, 0x05, 0x04, 0x03,
// Enum 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,
@@ -509,6 +845,13 @@ namespace SabreTools.IO.Test.Extensions
{
ByteArray = [0x00, 0x01, 0x02, 0x03],
IntArray = [0x00010203, 0x01020304, 0x02030405, 0x03040506],
EnumArray =
[
(TestEnum)0x00010203,
(TestEnum)0x01020304,
(TestEnum)0x02030405,
(TestEnum)0x03040506,
],
StructArray =
[
new TestStructPoint { X = 0x00FF, Y = 0xFF00 },
@@ -524,6 +867,8 @@ namespace SabreTools.IO.Test.Extensions
Assert.True(expected.ByteArray.SequenceEqual(read.ByteArray));
Assert.NotNull(read.IntArray);
Assert.True(expected.IntArray.SequenceEqual(read.IntArray));
Assert.NotNull(read.EnumArray);
Assert.True(expected.EnumArray.SequenceEqual(read.EnumArray));
Assert.NotNull(read.StructArray);
Assert.True(expected.StructArray.SequenceEqual(read.StructArray));
Assert.Equal(expected.LPByteArrayLength, read.LPByteArrayLength);

View File

@@ -10,8 +10,6 @@ using Xunit;
namespace SabreTools.IO.Test.Extensions
{
// TODO: Add byte[], char[] tests
// TODO: Add string writing tests
public class BinaryWriterExtensionsTests
{
/// <summary>
@@ -52,6 +50,16 @@ namespace SabreTools.IO.Test.Extensions
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteBytesBigEndianTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = _bytes.Take(4).ToArray();
bw.WriteBigEndian([0x03, 0x02, 0x01, 0x00]);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteSByteTest()
{
@@ -446,6 +454,121 @@ namespace SabreTools.IO.Test.Extensions
}
#endif
[Fact]
public void WriteNullTerminatedAnsiStringTest()
{
var stream = new MemoryStream(new byte[4], 0, 4, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = [0x41, 0x42, 0x43, 0x00];
bool write = bw.WriteNullTerminatedAnsiString("ABC");
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteNullTerminatedUTF8StringTest()
{
var stream = new MemoryStream(new byte[4], 0, 4, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = [0x41, 0x42, 0x43, 0x00];
bool write = bw.WriteNullTerminatedUTF8String("ABC");
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteNullTerminatedUnicodeStringTest()
{
var stream = new MemoryStream(new byte[8], 0, 8, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = [0x41, 0x00, 0x42, 0x00, 0x43, 0x00, 0x00];
bool write = bw.WriteNullTerminatedUnicodeString("ABC");
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteNullTerminatedUTF32StringTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = [0x41, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
bool write = bw.WriteNullTerminatedUTF32String("ABC");
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WritePrefixedAnsiStringTest()
{
var stream = new MemoryStream(new byte[4], 0, 4, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = [0x03, 0x41, 0x42, 0x43];
bool write = bw.WritePrefixedAnsiString("ABC");
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WritePrefixedUnicodeStringTest()
{
var stream = new MemoryStream(new byte[8], 0, 8, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = [0x03, 0x00, 0x41, 0x00, 0x42, 0x00, 0x43, 0x00];
bool write = bw.WritePrefixedUnicodeString("ABC");
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteTypeTest()
{
// Guid
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
bool actual = bw.WriteType<Guid>(new Guid(_bytes));
Assert.True(actual);
ValidateBytes(_bytes, stream.GetBuffer());
#if NET6_0_OR_GREATER
// Half
stream = new MemoryStream(new byte[2], 0, 2, true, true);
bw = new BinaryWriter(stream);
actual = bw.WriteType<Half>(BitConverter.Int16BitsToHalf(0x0100));
Assert.True(actual);
ValidateBytes([.. _bytes.Take(2)], stream.GetBuffer());
#endif
#if NET7_0_OR_GREATER
// Int128
stream = new MemoryStream(new byte[16], 0, 16, true, true);
bw = new BinaryWriter(stream);
actual = bw.WriteType<Int128>((Int128)new BigInteger(_bytes));
Assert.True(actual);
ValidateBytes(_bytes, stream.GetBuffer());
// UInt128
stream = new MemoryStream(new byte[16], 0, 16, true, true);
bw = new BinaryWriter(stream);
actual = bw.WriteType<UInt128>((UInt128)new BigInteger(_bytes));
Assert.True(actual);
ValidateBytes(_bytes, stream.GetBuffer());
#endif
// Enum
stream = new MemoryStream(new byte[4], 0, 4, true, true);
bw = new BinaryWriter(stream);
actual = bw.WriteType<TestEnum>((TestEnum)0x03020100);
Assert.True(actual);
ValidateBytes([.. _bytes.Take(4)], stream.GetBuffer());
}
[Fact]
public void WriteTypeExplicitTest()
{

View File

@@ -0,0 +1,92 @@
using System;
using System.Linq;
using SabreTools.IO.Extensions;
using Xunit;
namespace SabreTools.IO.Test.Extensions
{
public class ByteArrayExtensionsTests
{
#region Is Null or Empty
[Fact]
public void IsNullOrEmpty_Null_True()
{
byte[]? arr = null;
bool actual = arr.IsNullOrEmpty();
Assert.True(actual);
}
[Fact]
public void IsNullOrEmpty_Empty_True()
{
byte[]? arr = [];
bool actual = arr.IsNullOrEmpty();
Assert.True(actual);
}
[Fact]
public void IsNullOrEmpty_NonEmpty_False()
{
byte[]? arr = [0x01];
bool actual = arr.IsNullOrEmpty();
Assert.False(actual);
}
#endregion
#region To Hex String
[Fact]
public void ToHexString_Null()
{
byte[]? arr = null;
string? actual = arr.ToHexString();
Assert.Null(actual);
}
[Fact]
public void ToHexString_Valid()
{
byte[]? arr = [0x01, 0x02, 0x03, 0x04];
string expected = "01020304";
string? actual = arr.ToHexString();
Assert.NotNull(actual);
Assert.Equal(expected, actual);
}
#endregion
#region From Hex String
[Fact]
public void FromHexString_Null()
{
string? str = null;
byte[]? actual = str.FromHexString();
Assert.Null(actual);
}
[Fact]
public void FromHexString_Valid()
{
string str = "01020304";
byte[]? expected = [0x01, 0x02, 0x03, 0x04];
byte[]? actual = str.FromHexString();
Assert.NotNull(actual);
Assert.True(expected.SequenceEqual(actual));
}
[Fact]
public void FromHexString_Invalid()
{
string str = "0102030G";
byte[]? actual = str.FromHexString();
Assert.Null(actual);
}
#endregion
}
}

View File

@@ -3,13 +3,13 @@ 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 string reading tests
public class ByteArrayExtensionsReadTests
public class ByteArrayReaderExtensionsTests
{
/// <summary>
/// Test pattern from 0x00-0x0F
@@ -86,6 +86,14 @@ namespace SabreTools.IO.Test.Extensions
Assert.Equal(0x0001, read);
}
[Fact]
public void ReadInt16LittleEndianTest()
{
int offset = 0;
short read = _bytes.ReadInt16LittleEndian(ref offset);
Assert.Equal(0x0100, read);
}
[Fact]
public void ReadUInt16Test()
{
@@ -102,6 +110,38 @@ namespace SabreTools.IO.Test.Extensions
Assert.Equal(0x0001, read);
}
[Fact]
public void ReadUInt16LittleEndianTest()
{
int offset = 0;
ushort read = _bytes.ReadUInt16LittleEndian(ref offset);
Assert.Equal(0x0100, read);
}
[Fact]
public void ReadWORDTest()
{
int offset = 0;
ushort read = _bytes.ReadWORD(ref offset);
Assert.Equal(0x0100, read);
}
[Fact]
public void ReadWORDBigEndianTest()
{
int offset = 0;
ushort read = _bytes.ReadWORDBigEndian(ref offset);
Assert.Equal(0x0001, read);
}
[Fact]
public void ReadWORDLittleEndianTest()
{
int offset = 0;
ushort read = _bytes.ReadWORDLittleEndian(ref offset);
Assert.Equal(0x0100, read);
}
#if NET6_0_OR_GREATER
[Fact]
public void ReadHalfTest()
@@ -138,6 +178,14 @@ namespace SabreTools.IO.Test.Extensions
Assert.Equal(0x000102, read);
}
[Fact]
public void ReadInt24LittleEndianTest()
{
int offset = 0;
int read = _bytes.ReadInt24LittleEndian(ref offset);
Assert.Equal(0x020100, read);
}
[Fact]
public void ReadUInt24Test()
{
@@ -154,6 +202,14 @@ namespace SabreTools.IO.Test.Extensions
Assert.Equal((uint)0x000102, read);
}
[Fact]
public void ReadUInt24LittleEndianTest()
{
int offset = 0;
uint read = _bytes.ReadUInt24LittleEndian(ref offset);
Assert.Equal((uint)0x020100, read);
}
[Fact]
public void ReadInt32Test()
{
@@ -170,6 +226,14 @@ namespace SabreTools.IO.Test.Extensions
Assert.Equal(0x00010203, read);
}
[Fact]
public void ReadInt32LittleEndianTest()
{
int offset = 0;
int read = _bytes.ReadInt32LittleEndian(ref offset);
Assert.Equal(0x03020100, read);
}
[Fact]
public void ReadUInt32Test()
{
@@ -186,6 +250,38 @@ namespace SabreTools.IO.Test.Extensions
Assert.Equal((uint)0x00010203, read);
}
[Fact]
public void ReadUInt32LittleEndianTest()
{
int offset = 0;
uint read = _bytes.ReadUInt32LittleEndian(ref offset);
Assert.Equal((uint)0x03020100, read);
}
[Fact]
public void ReadDWORDTest()
{
int offset = 0;
uint read = _bytes.ReadDWORD(ref offset);
Assert.Equal((uint)0x03020100, read);
}
[Fact]
public void ReadDWORDBigEndianTest()
{
int offset = 0;
uint read = _bytes.ReadDWORDBigEndian(ref offset);
Assert.Equal((uint)0x00010203, read);
}
[Fact]
public void ReadDWORDLittleEndianTest()
{
int offset = 0;
uint read = _bytes.ReadDWORDLittleEndian(ref offset);
Assert.Equal((uint)0x03020100, read);
}
[Fact]
public void ReadSingleTest()
{
@@ -220,6 +316,14 @@ namespace SabreTools.IO.Test.Extensions
Assert.Equal(0x000102030405, read);
}
[Fact]
public void ReadInt48LittleEndianTest()
{
int offset = 0;
long read = _bytes.ReadInt48LittleEndian(ref offset);
Assert.Equal(0x050403020100, read);
}
[Fact]
public void ReadUInt48Test()
{
@@ -236,6 +340,14 @@ namespace SabreTools.IO.Test.Extensions
Assert.Equal((ulong)0x000102030405, read);
}
[Fact]
public void ReadUInt48LittleEndianTest()
{
int offset = 0;
ulong read = _bytes.ReadUInt48LittleEndian(ref offset);
Assert.Equal((ulong)0x050403020100, read);
}
[Fact]
public void ReadInt64Test()
{
@@ -252,6 +364,14 @@ namespace SabreTools.IO.Test.Extensions
Assert.Equal(0x0001020304050607, read);
}
[Fact]
public void ReadInt64LittleEndianTest()
{
int offset = 0;
long read = _bytes.ReadInt64LittleEndian(ref offset);
Assert.Equal(0x0706050403020100, read);
}
[Fact]
public void ReadUInt64Test()
{
@@ -268,6 +388,38 @@ namespace SabreTools.IO.Test.Extensions
Assert.Equal((ulong)0x0001020304050607, read);
}
[Fact]
public void ReadUInt64LittleEndianTest()
{
int offset = 0;
ulong read = _bytes.ReadUInt64LittleEndian(ref offset);
Assert.Equal((ulong)0x0706050403020100, read);
}
[Fact]
public void ReadQWORDTest()
{
int offset = 0;
ulong read = _bytes.ReadQWORD(ref offset);
Assert.Equal((ulong)0x0706050403020100, read);
}
[Fact]
public void ReadQWORDBigEndianTest()
{
int offset = 0;
ulong read = _bytes.ReadQWORDBigEndian(ref offset);
Assert.Equal((ulong)0x0001020304050607, read);
}
[Fact]
public void ReadQWORDLittleEndianTest()
{
int offset = 0;
ulong read = _bytes.ReadQWORDLittleEndian(ref offset);
Assert.Equal((ulong)0x0706050403020100, read);
}
[Fact]
public void ReadDoubleTest()
{
@@ -362,6 +514,78 @@ namespace SabreTools.IO.Test.Extensions
}
#endif
[Fact]
public void ReadNullTerminatedStringTest()
{
// Encoding.ASCII
int offset = 0;
byte[] bytes = [0x41, 0x42, 0x43, 0x00];
string? actual = bytes.ReadNullTerminatedString(ref offset, Encoding.ASCII);
Assert.Equal("ABC", actual);
// Encoding.UTF8
offset = 0;
bytes = [0x41, 0x42, 0x43, 0x00];
actual = bytes.ReadNullTerminatedString(ref offset, Encoding.UTF8);
Assert.Equal("ABC", actual);
// Encoding.Unicode
offset = 0;
bytes = [0x41, 0x00, 0x42, 0x00, 0x43, 0x00, 0x00, 0x00];
actual = bytes.ReadNullTerminatedString(ref offset, Encoding.Unicode);
Assert.Equal("ABC", actual);
// Encoding.UTF32
offset = 0;
bytes = [0x41, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
actual = bytes.ReadNullTerminatedString(ref offset, Encoding.UTF32);
Assert.Equal("ABC", actual);
// Encoding.Latin1
offset = 0;
bytes = [0x41, 0x42, 0x43, 0x00];
actual = bytes.ReadNullTerminatedString(ref offset, Encoding.Latin1);
Assert.Equal("ABC", actual);
}
[Fact]
public void ReadTypeTest()
{
// Guid
int offset = 0;
var expectedGuid = new Guid(_bytes);
Guid actualGuid = _bytes.ReadType<Guid>(ref offset);
Assert.Equal(expectedGuid, actualGuid);
#if NET6_0_OR_GREATER
// Half
offset = 0;
Half expectedHalf = BitConverter.Int16BitsToHalf(0x0100);
Half actualHalf = _bytes.ReadType<Half>(ref offset);
Assert.Equal(expectedHalf, actualHalf);
#endif
#if NET7_0_OR_GREATER
// Int128
offset = 0;
Int128 expectedInt128 = (Int128)new BigInteger(_bytes);
Int128 actualInt128 = _bytes.ReadType<Int128>(ref offset);
Assert.Equal(expectedHalf, actualHalf);
// UInt128
offset = 0;
UInt128 expectedUInt128 = (UInt128)new BigInteger(_bytes);
UInt128 actualUInt128 = _bytes.ReadType<UInt128>(ref offset);
Assert.Equal(expectedHalf, actualHalf);
#endif
// Enum
offset = 0;
TestEnum expectedTestEnum = (TestEnum)0x03020100;
TestEnum actualTestEnum = _bytes.ReadType<TestEnum>(ref offset);
Assert.Equal(expectedTestEnum, actualTestEnum);
}
[Fact]
public void ReadTypeExplicitTest()
{
@@ -456,6 +680,12 @@ namespace SabreTools.IO.Test.Extensions
0x05, 0x04, 0x03, 0x02,
0x06, 0x05, 0x04, 0x03,
// Enum 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,
@@ -472,6 +702,13 @@ namespace SabreTools.IO.Test.Extensions
{
ByteArray = [0x00, 0x01, 0x02, 0x03],
IntArray = [0x00010203, 0x01020304, 0x02030405, 0x03040506],
EnumArray =
[
(TestEnum)0x00010203,
(TestEnum)0x01020304,
(TestEnum)0x02030405,
(TestEnum)0x03040506,
],
StructArray =
[
new TestStructPoint { X = 0x00FF, Y = 0xFF00 },
@@ -487,6 +724,8 @@ namespace SabreTools.IO.Test.Extensions
Assert.True(expected.ByteArray.SequenceEqual(read.ByteArray));
Assert.NotNull(read.IntArray);
Assert.True(expected.IntArray.SequenceEqual(read.IntArray));
Assert.NotNull(read.EnumArray);
Assert.True(expected.EnumArray.SequenceEqual(read.EnumArray));
Assert.NotNull(read.StructArray);
Assert.True(expected.StructArray.SequenceEqual(read.StructArray));
Assert.Equal(expected.LPByteArrayLength, read.LPByteArrayLength);

View File

@@ -3,13 +3,13 @@ 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 string writing tests
public class ByteArrayExtensionsWriteTests
public class ByteArrayWriterExtensionsTests
{
/// <summary>
/// Test pattern from 0x00-0x0F
@@ -51,6 +51,17 @@ namespace SabreTools.IO.Test.Extensions
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteBytesBigEndianTest()
{
byte[] buffer = new byte[16];
int offset = 0;
byte[] expected = _bytes.Take(4).ToArray();
bool write = buffer.WriteBigEndian(ref offset, [0x03, 0x02, 0x01, 0x00]);
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteSByteTest()
{
@@ -73,6 +84,17 @@ namespace SabreTools.IO.Test.Extensions
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteCharEncodingTest()
{
byte[] buffer = new byte[16];
int offset = 0;
byte[] expected = [0x00, 0x00];
bool write = buffer.Write(ref offset, '\0', Encoding.Unicode);
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteInt16Test()
{
@@ -339,6 +361,28 @@ namespace SabreTools.IO.Test.Extensions
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteDoubleTest()
{
byte[] buffer = new byte[16];
int offset = 0;
byte[] expected = _bytes.Take(8).ToArray();
bool write = buffer.Write(ref offset, BitConverter.Int64BitsToDouble(0x0706050403020100));
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteDoubleBigEndianTest()
{
byte[] buffer = new byte[16];
int offset = 0;
byte[] expected = _bytes.Take(8).ToArray();
bool write = buffer.WriteBigEndian(ref offset, BitConverter.Int64BitsToDouble(0x0001020304050607));
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteDecimalTest()
{
@@ -429,6 +473,121 @@ namespace SabreTools.IO.Test.Extensions
}
#endif
[Fact]
public void WriteNullTerminatedAnsiStringTest()
{
int offset = 0;
byte[] buffer = new byte[4];
byte[] expected = [0x41, 0x42, 0x43, 0x00];
bool write = buffer.WriteNullTerminatedAnsiString(ref offset, "ABC");
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteNullTerminatedUTF8StringTest()
{
int offset = 0;
byte[] buffer = new byte[4];
byte[] expected = [0x41, 0x42, 0x43, 0x00];
bool write = buffer.WriteNullTerminatedUTF8String(ref offset, "ABC");
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteNullTerminatedUnicodeStringTest()
{
int offset = 0;
byte[] buffer = new byte[8];
byte[] expected = [0x41, 0x00, 0x42, 0x00, 0x43, 0x00, 0x00];
bool write = buffer.WriteNullTerminatedUnicodeString(ref offset, "ABC");
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteNullTerminatedUTF32StringTest()
{
int offset = 0;
byte[] buffer = new byte[16];
byte[] expected = [0x41, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
bool write = buffer.WriteNullTerminatedUTF32String(ref offset, "ABC");
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WritePrefixedAnsiStringTest()
{
int offset = 0;
byte[] buffer = new byte[4];
byte[] expected = [0x03, 0x41, 0x42, 0x43];
bool write = buffer.WritePrefixedAnsiString(ref offset, "ABC");
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WritePrefixedUnicodeStringTest()
{
int offset = 0;
byte[] buffer = new byte[8];
byte[] expected = [0x03, 0x00, 0x41, 0x00, 0x42, 0x00, 0x43, 0x00];
bool write = buffer.WritePrefixedUnicodeString(ref offset, "ABC");
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteTypeTest()
{
// Guid
int offset = 0;
byte[] buffer = new byte[16];
bool actual = buffer.WriteType<Guid>(ref offset, new Guid(_bytes));
Assert.True(actual);
ValidateBytes(_bytes, buffer);
#if NET6_0_OR_GREATER
// Half
offset = 0;
buffer = new byte[2];
actual = buffer.WriteType<Half>(ref offset, BitConverter.Int16BitsToHalf(0x0100));
Assert.True(actual);
ValidateBytes([.. _bytes.Take(2)], buffer);
#endif
#if NET7_0_OR_GREATER
// Int128
offset = 0;
buffer = new byte[16];
actual = buffer.WriteType<Int128>(ref offset, (Int128)new BigInteger(_bytes));
Assert.True(actual);
ValidateBytes(_bytes, buffer);
// UInt128
offset = 0;
buffer = new byte[16];
actual = buffer.WriteType<UInt128>(ref offset, (UInt128)new BigInteger(_bytes));
Assert.True(actual);
ValidateBytes(_bytes, buffer);
#endif
// Enum
offset = 0;
buffer = new byte[4];
actual = buffer.WriteType<TestEnum>(ref offset, (TestEnum)0x03020100);
Assert.True(actual);
ValidateBytes([.. _bytes.Take(4)], buffer);
}
[Fact]
public void WriteTypeExplicitTest()
{

View File

@@ -10,7 +10,7 @@ namespace SabreTools.IO.Test.Extensions
public class EnumerableExtensionsTests
{
[Fact]
public void SafeEnumerateEmptyTest()
public void SafeEnumerate_Empty()
{
var source = Enumerable.Empty<string>();
var safe = source.SafeEnumerate();
@@ -19,7 +19,18 @@ namespace SabreTools.IO.Test.Extensions
}
[Fact]
public void SafeEnumerateNoErrorTest()
public void SafeEnumerate_Throws()
{
var source = new List<string> { "a", "ab", "abc" };
var wrapper = new ThrowsEnumerable(source);
var safe = wrapper.SafeEnumerate();
var list = safe.ToList();
Assert.Empty(list);
}
[Fact]
public void SafeEnumerate_NoError()
{
var source = new List<string> { "a", "ab", "abc" };
var safe = source.SafeEnumerate();
@@ -28,7 +39,7 @@ namespace SabreTools.IO.Test.Extensions
}
[Fact]
public void SafeEnumerateErrorMidTest()
public void SafeEnumerate_ErrorMid()
{
var source = new List<string> { "a", "ab", "abc" };
var wrapper = new ErrorEnumerable(source);
@@ -39,11 +50,11 @@ namespace SabreTools.IO.Test.Extensions
}
[Fact]
public void SafeEnumerateErrorLastTest()
public void SafeEnumerate_ErrorLast()
{
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);
@@ -134,5 +145,19 @@ namespace SabreTools.IO.Test.Extensions
_index = -1;
}
}
/// <summary>
/// Fake enumerable that throws an exception for the enumerator
/// </summary>
private class ThrowsEnumerable : IEnumerable<string>
{
public ThrowsEnumerable(IEnumerable<string> source) { }
/// <inheritdoc/>
public IEnumerator<string> GetEnumerator() => throw new Exception();
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => throw new Exception();
}
}
}

View File

@@ -1,3 +1,6 @@
using System;
using System.IO;
using System.Text;
using SabreTools.IO.Extensions;
using Xunit;
@@ -5,6 +8,116 @@ namespace SabreTools.IO.Test.Extensions
{
public class IOExtensionsTests
{
#region Ensure
[Theory]
[InlineData(null, null)]
[InlineData("", null)]
[InlineData(" ", " ")] // TODO: This is a bad result
[InlineData("dirname", "dirname")]
[InlineData("\"dirname\"", "dirname")]
public void EnsureTest(string? dir, string? expected)
{
// Handle test setup
expected ??= PathTool.GetRuntimeDirectory();
if (expected != null)
expected = Path.GetFullPath(expected);
string actual = dir.Ensure(create: false);
Assert.Equal(expected, actual);
}
#endregion
#region Get Encoding
[Fact]
public void GetEncoding_EmptyPath()
{
string path = "";
Encoding expected = Encoding.Default;
var actual = path.GetEncoding();
Assert.Equal(expected, actual);
}
[Fact]
public void GetEncoding_InvalidPath()
{
string path = Path.Combine(Environment.CurrentDirectory, "TestData", "INVALID");
Encoding expected = Encoding.Default;
var actual = path.GetEncoding();
Assert.Equal(expected, actual);
}
// Disable warning about UTF7 usage
#pragma warning disable SYSLIB0001
[Fact]
public void GetEncoding_UTF7()
{
string path = Path.Combine(Environment.CurrentDirectory, "TestData", "utf7bom.txt");
Encoding expected = Encoding.UTF7;
var actual = path.GetEncoding();
Assert.Equal(expected, actual);
}
#pragma warning restore SYSLIB0001
[Fact]
public void GetEncoding_UTF8()
{
string path = Path.Combine(Environment.CurrentDirectory, "TestData", "utf8bom.txt");
Encoding expected = Encoding.UTF8;
var actual = path.GetEncoding();
Assert.Equal(expected, actual);
}
[Fact]
public void GetEncoding_Unicode()
{
string path = Path.Combine(Environment.CurrentDirectory, "TestData", "utf16lebom.txt");
Encoding expected = Encoding.Unicode;
var actual = path.GetEncoding();
Assert.Equal(expected, actual);
}
[Fact]
public void GetEncoding_BigEndianUnicode()
{
string path = Path.Combine(Environment.CurrentDirectory, "TestData", "utf16bebom.txt");
Encoding expected = Encoding.BigEndianUnicode;
var actual = path.GetEncoding();
Assert.Equal(expected, actual);
}
[Fact]
public void GetEncoding_UTF32()
{
string path = Path.Combine(Environment.CurrentDirectory, "TestData", "utf32bom.txt");
Encoding expected = Encoding.UTF32;
var actual = path.GetEncoding();
Assert.Equal(expected, actual);
}
[Fact]
public void GetEncoding_ASCII()
{
string path = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt");
Encoding expected = Encoding.Default;
var actual = path.GetEncoding();
Assert.Equal(expected, actual);
}
#endregion
#region Get Normalized Extension
[Theory]
[InlineData(null, null)]
[InlineData("", null)]
@@ -15,10 +128,185 @@ namespace SabreTools.IO.Test.Extensions
[InlineData("NO-EXTENSION.", null)]
[InlineData("filename.ext", "ext")]
[InlineData("FILENAME.EXT", "ext")]
public void NormalizedExtensionTest(string? path, string? expected)
public void GetNormalizedExtensionTest(string? path, string? expected)
{
string? actual = path.GetNormalizedExtension();
Assert.Equal(expected, actual);
}
#endregion
#region Path
[Fact]
public void ListEmpty_NullDirectory()
{
string? dir = null;
var empty = dir.ListEmpty();
Assert.Null(empty);
}
[Fact]
public void ListEmpty_InvalidDirectory()
{
string dir = Path.Combine(Environment.CurrentDirectory, "TestData", "INVALID");
var empty = dir.ListEmpty();
Assert.Null(empty);
}
[Fact]
public void ListEmpty_ValidDirectory()
{
string dir = Path.Combine(Environment.CurrentDirectory, "TestData");
var empty = dir.ListEmpty();
Assert.NotNull(empty);
Assert.Empty(empty);
}
[Fact]
public void SafeGetDirectories_ValidDirectory()
{
string dir = Path.Combine(Environment.CurrentDirectory, "TestData");
var dirs = dir.SafeGetDirectories();
Assert.Single(dirs);
}
[Fact]
public void SafeGetDirectories_ValidDirectory_Pattern()
{
string dir = Path.Combine(Environment.CurrentDirectory, "TestData");
var dirs = dir.SafeGetDirectories("*");
Assert.Single(dirs);
}
[Fact]
public void SafeGetDirectories_ValidDirectory_PatternOption()
{
string dir = Path.Combine(Environment.CurrentDirectory, "TestData");
var dirs = dir.SafeGetDirectories("*", SearchOption.AllDirectories);
Assert.Single(dirs);
}
[Fact]
public void SafeGetFiles_ValidDirectory()
{
string dir = Path.Combine(Environment.CurrentDirectory, "TestData");
var files = dir.SafeGetFiles();
Assert.NotEmpty(files);
}
[Fact]
public void SafeGetFiles_ValidDirectory_Pattern()
{
string dir = Path.Combine(Environment.CurrentDirectory, "TestData");
var files = dir.SafeGetFiles("*");
Assert.NotEmpty(files);
}
[Fact]
public void SafeGetFiles_ValidDirectory_PatternOption()
{
string dir = Path.Combine(Environment.CurrentDirectory, "TestData");
var files = dir.SafeGetFiles("*", SearchOption.AllDirectories);
Assert.NotEmpty(files);
}
[Fact]
public void SafeGetFileSystemEntries_ValidDirectory()
{
string dir = Path.Combine(Environment.CurrentDirectory, "TestData");
var entries = dir.SafeGetFileSystemEntries();
Assert.NotEmpty(entries);
}
[Fact]
public void SafeGetFileSystemEntries_ValidDirectory_Pattern()
{
string dir = Path.Combine(Environment.CurrentDirectory, "TestData");
var entries = dir.SafeGetFileSystemEntries("*");
Assert.NotEmpty(entries);
}
[Fact]
public void SafeGetFileSystemEntries_ValidDirectory_PatternOption()
{
string dir = Path.Combine(Environment.CurrentDirectory, "TestData");
var entries = dir.SafeGetFileSystemEntries("*", SearchOption.AllDirectories);
Assert.NotEmpty(entries);
}
[Fact]
public void SafeEnumerateDirectories_ValidDirectory()
{
string dir = Path.Combine(Environment.CurrentDirectory, "TestData");
var dirs = dir.SafeEnumerateDirectories();
Assert.Single(dirs);
}
[Fact]
public void SafeEnumerateDirectories_ValidDirectory_Pattern()
{
string dir = Path.Combine(Environment.CurrentDirectory, "TestData");
var dirs = dir.SafeEnumerateDirectories("*");
Assert.Single(dirs);
}
[Fact]
public void SafeEnumerateDirectories_ValidDirectory_PatternOption()
{
string dir = Path.Combine(Environment.CurrentDirectory, "TestData");
var dirs = dir.SafeEnumerateDirectories("*", SearchOption.AllDirectories);
Assert.Single(dirs);
}
[Fact]
public void SafeEnumerateFiles_ValidDirectory()
{
string dir = Path.Combine(Environment.CurrentDirectory, "TestData");
var files = dir.SafeEnumerateFiles();
Assert.NotEmpty(files);
}
[Fact]
public void SafeEnumerateFiles_ValidDirectory_Pattern()
{
string dir = Path.Combine(Environment.CurrentDirectory, "TestData");
var files = dir.SafeEnumerateFiles("*");
Assert.NotEmpty(files);
}
[Fact]
public void SafeEnumerateFiles_ValidDirectory_PatternOption()
{
string dir = Path.Combine(Environment.CurrentDirectory, "TestData");
var files = dir.SafeEnumerateFiles("*", SearchOption.AllDirectories);
Assert.NotEmpty(files);
}
[Fact]
public void SafeEnumerateFileSystemEntries_ValidDirectory()
{
string dir = Path.Combine(Environment.CurrentDirectory, "TestData");
var entries = dir.SafeEnumerateFileSystemEntries();
Assert.NotEmpty(entries);
}
[Fact]
public void SafeEnumerateFileSystemEntries_ValidDirectory_Pattern()
{
string dir = Path.Combine(Environment.CurrentDirectory, "TestData");
var entries = dir.SafeEnumerateFileSystemEntries("*");
Assert.NotEmpty(entries);
}
[Fact]
public void SafeEnumerateFileSystemEntries_ValidDirectory_PatternOption()
{
string dir = Path.Combine(Environment.CurrentDirectory, "TestData");
var entries = dir.SafeEnumerateFileSystemEntries("*", SearchOption.AllDirectories);
Assert.NotEmpty(entries);
}
#endregion
}
}

View File

@@ -0,0 +1,232 @@
using System;
using System.IO;
using SabreTools.IO.Extensions;
using Xunit;
namespace SabreTools.IO.Test.Extensions
{
public class StreamExtensionsTests
{
#region Align to Boundary
[Fact]
public void AlignToBoundary_Null_False()
{
Stream? stream = null;
byte alignment = 4;
bool actual = stream.AlignToBoundary(alignment);
Assert.False(actual);
}
[Fact]
public void AlignToBoundary_Empty_False()
{
Stream? stream = new MemoryStream([]);
byte alignment = 4;
bool actual = stream.AlignToBoundary(alignment);
Assert.False(actual);
}
[Fact]
public void AlignToBoundary_EOF_False()
{
Stream? stream = new MemoryStream([0x01, 0x02]);
byte alignment = 4;
stream.Position = 1;
bool actual = stream.AlignToBoundary(alignment);
Assert.False(actual);
}
[Fact]
public void AlignToBoundary_TooShort_False()
{
Stream? stream = new MemoryStream([0x01, 0x02]);
byte alignment = 4;
stream.Position = 1;
bool actual = stream.AlignToBoundary(alignment);
Assert.False(actual);
}
[Fact]
public void AlignToBoundary_CanAlign_True()
{
Stream? stream = new MemoryStream([0x01, 0x02, 0x03, 0x04, 0x05]);
byte alignment = 4;
stream.Position = 1;
bool actual = stream.AlignToBoundary(alignment);
Assert.True(actual);
}
#endregion
#region Seek If Possible
[Fact]
public void SeekIfPossible_NonSeekable_CurrentPosition()
{
var stream = new NonSeekableStream();
long actual = stream.SeekIfPossible(0);
Assert.Equal(8, actual);
}
[Fact]
public void SeekIfPossible_NonPositionable_InvalidPosition()
{
var stream = new NonPositionableStream();
long actual = stream.SeekIfPossible(0);
Assert.Equal(-1, actual);
}
[Fact]
public void SeekIfPossible_HiddenNonSeekable_InvalidPosition()
{
var stream = new HiddenNonSeekableStream();
long actual = stream.SeekIfPossible(0);
Assert.Equal(-1, actual);
}
[Fact]
public void SeekIfPossible_NonNegative_ValidPosition()
{
var stream = new MemoryStream(new byte[16], 0, 16, false, true);
long actual = stream.SeekIfPossible(5);
Assert.Equal(5, actual);
}
[Fact]
public void SeekIfPossible_Negative_ValidPosition()
{
var stream = new MemoryStream(new byte[16], 0, 16, false, true);
long actual = stream.SeekIfPossible(-3);
Assert.Equal(13, actual);
}
#endregion
/// <summary>
/// Represents a hidden non-seekable stream
/// </summary>
private class HiddenNonSeekableStream : Stream
{
public override bool CanRead => true;
public override bool CanSeek => true;
public override bool CanWrite => true;
public override long Length => 16;
public override long Position { get => 8; set => throw new NotSupportedException(); }
public override void Flush()
{
throw new NotImplementedException();
}
public override int Read(byte[] buffer, int offset, int count)
{
throw new NotImplementedException();
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotImplementedException();
}
public override void SetLength(long value)
{
throw new NotImplementedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotImplementedException();
}
}
/// <summary>
/// Represents a non-seekable stream
/// </summary>
private class NonSeekableStream : Stream
{
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => true;
public override long Length => 16;
public override long Position { get => 8; set => throw new NotSupportedException(); }
public override void Flush()
{
throw new NotImplementedException();
}
public override int Read(byte[] buffer, int offset, int count)
{
throw new NotImplementedException();
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotImplementedException();
}
public override void SetLength(long value)
{
throw new NotImplementedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotImplementedException();
}
}
/// <summary>
/// Represents a non-seekable, non-positionable stream
/// </summary>
private class NonPositionableStream : Stream
{
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => true;
public override long Length => 16;
public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
public override void Flush()
{
throw new NotImplementedException();
}
public override int Read(byte[] buffer, int offset, int count)
{
throw new NotImplementedException();
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotImplementedException();
}
public override void SetLength(long value)
{
throw new NotImplementedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotImplementedException();
}
}
}
}

View File

@@ -4,13 +4,13 @@ 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 string reading tests
public class StreamExtensionsReadTests
public class StreamReaderExtensionsTests
{
/// <summary>
/// Test pattern from 0x00-0x0F
@@ -30,6 +30,16 @@ namespace SabreTools.IO.Test.Extensions
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00,
];
[Fact]
public void ReadByteArrayTest()
{
byte[] arr = new byte[4];
var stream = new MemoryStream(_bytes);
int read = stream.Read(arr, 0, 4);
Assert.Equal(4, read);
Assert.True(arr.SequenceEqual(_bytes.Take(4)));
}
[Fact]
public void ReadByteValueTest()
{
@@ -80,6 +90,14 @@ namespace SabreTools.IO.Test.Extensions
Assert.Equal(0x0001, read);
}
[Fact]
public void ReadInt16LittleEndianTest()
{
var stream = new MemoryStream(_bytes);
short read = stream.ReadInt16LittleEndian();
Assert.Equal(0x0100, read);
}
[Fact]
public void ReadUInt16Test()
{
@@ -96,6 +114,38 @@ namespace SabreTools.IO.Test.Extensions
Assert.Equal(0x0001, read);
}
[Fact]
public void ReadUInt16LittleEndianTest()
{
var stream = new MemoryStream(_bytes);
ushort read = stream.ReadUInt16LittleEndian();
Assert.Equal(0x0100, read);
}
[Fact]
public void ReadWORDTest()
{
var stream = new MemoryStream(_bytes);
ushort read = stream.ReadWORD();
Assert.Equal(0x0100, read);
}
[Fact]
public void ReadWORDBigEndianTest()
{
var stream = new MemoryStream(_bytes);
ushort read = stream.ReadWORDBigEndian();
Assert.Equal(0x0001, read);
}
[Fact]
public void ReadWORDLittleEndianTest()
{
var stream = new MemoryStream(_bytes);
ushort read = stream.ReadWORDLittleEndian();
Assert.Equal(0x0100, read);
}
#if NET6_0_OR_GREATER
[Fact]
public void ReadHalfTest()
@@ -132,6 +182,14 @@ namespace SabreTools.IO.Test.Extensions
Assert.Equal(0x000102, read);
}
[Fact]
public void ReadInt24LittleEndianTest()
{
var stream = new MemoryStream(_bytes);
int read = stream.ReadInt24LittleEndian();
Assert.Equal(0x020100, read);
}
[Fact]
public void ReadUInt24Test()
{
@@ -148,6 +206,14 @@ namespace SabreTools.IO.Test.Extensions
Assert.Equal((uint)0x000102, read);
}
[Fact]
public void ReadUInt24LittleEndianTest()
{
var stream = new MemoryStream(_bytes);
uint read = stream.ReadUInt24LittleEndian();
Assert.Equal((uint)0x020100, read);
}
[Fact]
public void ReadInt32Test()
{
@@ -164,6 +230,14 @@ namespace SabreTools.IO.Test.Extensions
Assert.Equal(0x00010203, read);
}
[Fact]
public void ReadInt32LittleEndianTest()
{
var stream = new MemoryStream(_bytes);
int read = stream.ReadInt32LittleEndian();
Assert.Equal(0x03020100, read);
}
[Fact]
public void ReadUInt32Test()
{
@@ -180,6 +254,38 @@ namespace SabreTools.IO.Test.Extensions
Assert.Equal((uint)0x00010203, read);
}
[Fact]
public void ReadUInt32LittleEndianTest()
{
var stream = new MemoryStream(_bytes);
uint read = stream.ReadUInt32LittleEndian();
Assert.Equal((uint)0x03020100, read);
}
[Fact]
public void ReadDWORDTest()
{
var stream = new MemoryStream(_bytes);
uint read = stream.ReadDWORD();
Assert.Equal((uint)0x03020100, read);
}
[Fact]
public void ReadDWORDBigEndianTest()
{
var stream = new MemoryStream(_bytes);
uint read = stream.ReadDWORDBigEndian();
Assert.Equal((uint)0x00010203, read);
}
[Fact]
public void ReadDWORDLittleEndianTest()
{
var stream = new MemoryStream(_bytes);
uint read = stream.ReadDWORDLittleEndian();
Assert.Equal((uint)0x03020100, read);
}
[Fact]
public void ReadSingleTest()
{
@@ -214,6 +320,14 @@ namespace SabreTools.IO.Test.Extensions
Assert.Equal(0x000102030405, read);
}
[Fact]
public void ReadInt48LittleEndianTest()
{
var stream = new MemoryStream(_bytes);
long read = stream.ReadInt48LittleEndian();
Assert.Equal(0x050403020100, read);
}
[Fact]
public void ReadUInt48Test()
{
@@ -230,6 +344,14 @@ namespace SabreTools.IO.Test.Extensions
Assert.Equal((ulong)0x000102030405, read);
}
[Fact]
public void ReadUInt48LittleEndianTest()
{
var stream = new MemoryStream(_bytes);
ulong read = stream.ReadUInt48LittleEndian();
Assert.Equal((ulong)0x050403020100, read);
}
[Fact]
public void ReadInt64Test()
{
@@ -246,6 +368,14 @@ namespace SabreTools.IO.Test.Extensions
Assert.Equal(0x0001020304050607, read);
}
[Fact]
public void ReadInt64LittleEndianTest()
{
var stream = new MemoryStream(_bytes);
long read = stream.ReadInt64LittleEndian();
Assert.Equal(0x0706050403020100, read);
}
[Fact]
public void ReadUInt64Test()
{
@@ -262,6 +392,38 @@ namespace SabreTools.IO.Test.Extensions
Assert.Equal((ulong)0x0001020304050607, read);
}
[Fact]
public void ReadUInt64LittleEndianTest()
{
var stream = new MemoryStream(_bytes);
ulong read = stream.ReadUInt64LittleEndian();
Assert.Equal((ulong)0x0706050403020100, read);
}
[Fact]
public void ReadQWORDTest()
{
var stream = new MemoryStream(_bytes);
ulong read = stream.ReadQWORD();
Assert.Equal((ulong)0x0706050403020100, read);
}
[Fact]
public void ReadQWORDBigEndianTest()
{
var stream = new MemoryStream(_bytes);
ulong read = stream.ReadQWORDBigEndian();
Assert.Equal((ulong)0x0001020304050607, read);
}
[Fact]
public void ReadQWORDLittleEndianTest()
{
var stream = new MemoryStream(_bytes);
ulong read = stream.ReadQWORDLittleEndian();
Assert.Equal((ulong)0x0706050403020100, read);
}
[Fact]
public void ReadDoubleTest()
{
@@ -356,6 +518,78 @@ namespace SabreTools.IO.Test.Extensions
}
#endif
[Fact]
public void ReadNullTerminatedStringTest()
{
// Encoding.ASCII
byte[] bytes = [0x41, 0x42, 0x43, 0x00];
var stream = new MemoryStream(bytes);
string? actual = stream.ReadNullTerminatedString(Encoding.ASCII);
Assert.Equal("ABC", actual);
// Encoding.UTF8
bytes = [0x41, 0x42, 0x43, 0x00];
stream = new MemoryStream(bytes);
actual = stream.ReadNullTerminatedString(Encoding.UTF8);
Assert.Equal("ABC", actual);
// Encoding.Unicode
bytes = [0x41, 0x00, 0x42, 0x00, 0x43, 0x00, 0x00, 0x00];
stream = new MemoryStream(bytes);
actual = stream.ReadNullTerminatedString(Encoding.Unicode);
Assert.Equal("ABC", actual);
// Encoding.UTF32
bytes = [0x41, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
stream = new MemoryStream(bytes);
actual = stream.ReadNullTerminatedString(Encoding.UTF32);
Assert.Equal("ABC", actual);
// Encoding.Latin1
bytes = [0x41, 0x42, 0x43, 0x00];
stream = new MemoryStream(bytes);
actual = stream.ReadNullTerminatedString(Encoding.Latin1);
Assert.Equal("ABC", actual);
}
[Fact]
public void ReadTypeTest()
{
// Guid
var stream = new MemoryStream(_bytes);
var expectedGuid = new Guid(_bytes);
Guid actualGuid = stream.ReadType<Guid>();
Assert.Equal(expectedGuid, actualGuid);
#if NET6_0_OR_GREATER
// Half
stream = new MemoryStream(_bytes);
Half expectedHalf = BitConverter.Int16BitsToHalf(0x0100);
Half actualHalf = stream.ReadType<Half>();
Assert.Equal(expectedHalf, actualHalf);
#endif
#if NET7_0_OR_GREATER
// Int128
stream = new MemoryStream(_bytes);
Int128 expectedInt128 = (Int128)new BigInteger(_bytes);
Int128 actualInt128 = stream.ReadType<Int128>();
Assert.Equal(expectedHalf, actualHalf);
// UInt128
stream = new MemoryStream(_bytes);
UInt128 expectedUInt128 = (UInt128)new BigInteger(_bytes);
UInt128 actualUInt128 = stream.ReadType<UInt128>();
Assert.Equal(expectedHalf, actualHalf);
#endif
// Enum
stream = new MemoryStream(_bytes);
TestEnum expectedTestEnum = (TestEnum)0x03020100;
TestEnum actualTestEnum = stream.ReadType<TestEnum>();
Assert.Equal(expectedTestEnum, actualTestEnum);
}
[Fact]
public void ReadTypeExplicitTest()
{
@@ -450,6 +684,12 @@ namespace SabreTools.IO.Test.Extensions
0x05, 0x04, 0x03, 0x02,
0x06, 0x05, 0x04, 0x03,
// Enum 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,
@@ -466,6 +706,13 @@ namespace SabreTools.IO.Test.Extensions
{
ByteArray = [0x00, 0x01, 0x02, 0x03],
IntArray = [0x00010203, 0x01020304, 0x02030405, 0x03040506],
EnumArray =
[
(TestEnum)0x00010203,
(TestEnum)0x01020304,
(TestEnum)0x02030405,
(TestEnum)0x03040506,
],
StructArray =
[
new TestStructPoint { X = 0x00FF, Y = 0xFF00 },
@@ -481,6 +728,8 @@ namespace SabreTools.IO.Test.Extensions
Assert.True(expected.ByteArray.SequenceEqual(read.ByteArray));
Assert.NotNull(read.IntArray);
Assert.True(expected.IntArray.SequenceEqual(read.IntArray));
Assert.NotNull(read.EnumArray);
Assert.True(expected.EnumArray.SequenceEqual(read.EnumArray));
Assert.NotNull(read.StructArray);
Assert.True(expected.StructArray.SequenceEqual(read.StructArray));
Assert.Equal(expected.LPByteArrayLength, read.LPByteArrayLength);

View File

@@ -4,13 +4,13 @@ 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 string writing tests
public class StreamExtensionsWriteTests
public class StreamWriterExtensionsTests
{
/// <summary>
/// Test pattern from 0x00-0x0F
@@ -50,6 +50,15 @@ namespace SabreTools.IO.Test.Extensions
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteBytesBigEndianTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = _bytes.Take(4).ToArray();
stream.WriteBigEndian([0x03, 0x02, 0x01, 0x00]);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteSByteTest()
{
@@ -70,6 +79,15 @@ namespace SabreTools.IO.Test.Extensions
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteCharEncodingTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = [0x00, 0x00];
stream.Write('\0', Encoding.Unicode);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteInt16Test()
{
@@ -312,6 +330,26 @@ namespace SabreTools.IO.Test.Extensions
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteDoubleTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = _bytes.Take(8).ToArray();
bool write = stream.Write(BitConverter.Int64BitsToDouble(0x0706050403020100));
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteDoubleBigEndianTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = _bytes.Take(8).ToArray();
bool write = stream.WriteBigEndian(BitConverter.Int64BitsToDouble(0x0001020304050607));
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteDecimalTest()
{
@@ -394,6 +432,110 @@ namespace SabreTools.IO.Test.Extensions
}
#endif
[Fact]
public void WriteNullTerminatedAnsiStringTest()
{
var stream = new MemoryStream(new byte[4], 0, 4, true, true);
byte[] expected = [0x41, 0x42, 0x43, 0x00];
bool write = stream.WriteNullTerminatedAnsiString("ABC");
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteNullTerminatedUTF8StringTest()
{
var stream = new MemoryStream(new byte[4], 0, 4, true, true);
byte[] expected = [0x41, 0x42, 0x43, 0x00];
bool write = stream.WriteNullTerminatedUTF8String("ABC");
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteNullTerminatedUnicodeStringTest()
{
var stream = new MemoryStream(new byte[8], 0, 8, true, true);
byte[] expected = [0x41, 0x00, 0x42, 0x00, 0x43, 0x00, 0x00];
bool write = stream.WriteNullTerminatedUnicodeString("ABC");
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteNullTerminatedUTF32StringTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = [0x41, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
bool write = stream.WriteNullTerminatedUTF32String("ABC");
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WritePrefixedAnsiStringTest()
{
var stream = new MemoryStream(new byte[4], 0, 4, true, true);
byte[] expected = [0x03, 0x41, 0x42, 0x43];
bool write = stream.WritePrefixedAnsiString("ABC");
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WritePrefixedUnicodeStringTest()
{
var stream = new MemoryStream(new byte[8], 0, 8, true, true);
byte[] expected = [0x03, 0x00, 0x41, 0x00, 0x42, 0x00, 0x43, 0x00];
bool write = stream.WritePrefixedUnicodeString("ABC");
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteTypeTest()
{
// Guid
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
bool actual = stream.WriteType<Guid>(new Guid(_bytes));
Assert.True(actual);
ValidateBytes(_bytes, stream.GetBuffer());
#if NET6_0_OR_GREATER
// Half
stream = new MemoryStream(new byte[2], 0, 2, true, true);
actual = stream.WriteType<Half>(BitConverter.Int16BitsToHalf(0x0100));
Assert.True(actual);
ValidateBytes([.. _bytes.Take(2)], stream.GetBuffer());
#endif
#if NET7_0_OR_GREATER
// Int128
stream = new MemoryStream(new byte[16], 0, 16, true, true);
actual = stream.WriteType<Int128>((Int128)new BigInteger(_bytes));
Assert.True(actual);
ValidateBytes(_bytes, stream.GetBuffer());
// UInt128
stream = new MemoryStream(new byte[16], 0, 16, true, true);
actual = stream.WriteType<UInt128>((UInt128)new BigInteger(_bytes));
Assert.True(actual);
ValidateBytes(_bytes, stream.GetBuffer());
#endif
// Enum
stream = new MemoryStream(new byte[4], 0, 4, true, true);
actual = stream.WriteType<TestEnum>((TestEnum)0x03020100);
Assert.True(actual);
ValidateBytes([.. _bytes.Take(4)], stream.GetBuffer());
}
[Fact]
public void WriteTypeExplicitTest()
{

View File

@@ -17,6 +17,12 @@ namespace SabreTools.IO.Test.Extensions
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public int[]? IntArray;
/// <summary>
/// 4 entry int array
/// </summary>
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public TestEnum[]? EnumArray;
/// <summary>
/// 4 entry struct array
/// </summary>
@@ -31,7 +37,7 @@ namespace SabreTools.IO.Test.Extensions
/// <summary>
/// 4 entry byte array whose length is defined by <see cref="LPByteArrayLength"/>
/// </summary>
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)]
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 4)]
public byte[]? LPByteArray;
// /// <summary>

View File

@@ -1,5 +1,6 @@
using System.Runtime.InteropServices;
#pragma warning disable CS0618 // Obsolete unmanaged types
namespace SabreTools.IO.Test.Extensions
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]

View File

@@ -0,0 +1,242 @@
using System;
using System.IO;
using System.Text;
using System.Xml;
using SabreTools.IO.Extensions;
using Xunit;
namespace SabreTools.IO.Test.Extensions
{
public class XmlTextWriterExtensionsTests
{
[Fact]
public void WriteRequiredAttributeString_NullInputThrow_Throws()
{
string expected = "<?xml version=\"1.0\" encoding=\"utf-8\"?><element />";
var stream = new MemoryStream();
var writer = new XmlTextWriter(stream, Encoding.UTF8);
writer.WriteStartDocument();
writer.WriteStartElement("element");
Assert.Throws<ArgumentNullException>(()
=> writer.WriteRequiredAttributeString("attr", null, throwOnError: true));
writer.WriteEndElement();
writer.Flush();
// Length includes UTF-8 BOM
Assert.Equal(52, stream.Length);
string actual = Encoding.UTF8.GetString(stream.ToArray(), 3, (int)stream.Length - 3);
Assert.Equal(expected, actual);
}
[Fact]
public void WriteRequiredAttributeString_NullInputNoThrow_Writes()
{
string expected = "<?xml version=\"1.0\" encoding=\"utf-8\"?><element attr=\"\" />";
var stream = new MemoryStream();
var writer = new XmlTextWriter(stream, Encoding.UTF8);
writer.WriteStartDocument();
writer.WriteStartElement("element");
writer.WriteRequiredAttributeString("attr", null, throwOnError: false);
writer.WriteEndElement();
writer.Flush();
// Length includes UTF-8 BOM
Assert.Equal(60, stream.Length);
string actual = Encoding.UTF8.GetString(stream.ToArray(), 3, (int)stream.Length - 3);
Assert.Equal(expected, actual);
}
[Fact]
public void WriteRequiredAttributeString_ValidInput_Writes()
{
string expected = "<?xml version=\"1.0\" encoding=\"utf-8\"?><element attr=\"val\" />";
var stream = new MemoryStream();
var writer = new XmlTextWriter(stream, Encoding.UTF8);
writer.WriteStartDocument();
writer.WriteStartElement("element");
writer.WriteRequiredAttributeString("attr", "val", throwOnError: false);
writer.WriteEndElement();
writer.Flush();
// Length includes UTF-8 BOM
Assert.Equal(63, stream.Length);
string actual = Encoding.UTF8.GetString(stream.ToArray(), 3, (int)stream.Length - 3);
Assert.Equal(expected, actual);
}
[Fact]
public void WriteRequiredElementString_NullInputThrow_Throws()
{
string expected = "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
var stream = new MemoryStream();
var writer = new XmlTextWriter(stream, Encoding.UTF8);
writer.WriteStartDocument();
Assert.Throws<ArgumentNullException>(()
=> writer.WriteRequiredElementString("element", null, throwOnError: true));
writer.Flush();
// Length includes UTF-8 BOM
Assert.Equal(41, stream.Length);
string actual = Encoding.UTF8.GetString(stream.ToArray(), 3, (int)stream.Length - 3);
Assert.Equal(expected, actual);
}
[Fact]
public void WriteRequiredElementString_NullInputNoThrow_Writes()
{
string expected = "<?xml version=\"1.0\" encoding=\"utf-8\"?><element></element>";
var stream = new MemoryStream();
var writer = new XmlTextWriter(stream, Encoding.UTF8);
writer.WriteStartDocument();
writer.WriteRequiredElementString("element", null, throwOnError: false);
writer.Flush();
// Length includes UTF-8 BOM
Assert.Equal(60, stream.Length);
string actual = Encoding.UTF8.GetString(stream.ToArray(), 3, (int)stream.Length - 3);
Assert.Equal(expected, actual);
}
[Fact]
public void WriteRequiredElementString_ValidInput_Writes()
{
string expected = "<?xml version=\"1.0\" encoding=\"utf-8\"?><element>val</element>";
var stream = new MemoryStream();
var writer = new XmlTextWriter(stream, Encoding.UTF8);
writer.WriteStartDocument();
writer.WriteRequiredElementString("element", "val", throwOnError: false);
writer.Flush();
// Length includes UTF-8 BOM
Assert.Equal(63, stream.Length);
string actual = Encoding.UTF8.GetString(stream.ToArray(), 3, (int)stream.Length - 3);
Assert.Equal(expected, actual);
}
[Fact]
public void WriteOptionalAttributeString_NullInput_NoWrite()
{
string expected = "<?xml version=\"1.0\" encoding=\"utf-8\"?><element />";
var stream = new MemoryStream();
var writer = new XmlTextWriter(stream, Encoding.UTF8);
writer.WriteStartDocument();
writer.WriteStartElement("element");
writer.WriteOptionalAttributeString("attr", null);
writer.WriteEndElement();
writer.Flush();
// Length includes UTF-8 BOM
Assert.Equal(52, stream.Length);
string actual = Encoding.UTF8.GetString(stream.ToArray(), 3, (int)stream.Length - 3);
Assert.Equal(expected, actual);
}
[Fact]
public void WriteOptionalAttributeString_EmptyInput_NoWrite()
{
string expected = "<?xml version=\"1.0\" encoding=\"utf-8\"?><element />";
var stream = new MemoryStream();
var writer = new XmlTextWriter(stream, Encoding.UTF8);
writer.WriteStartDocument();
writer.WriteStartElement("element");
writer.WriteOptionalAttributeString("attr", string.Empty);
writer.WriteEndElement();
writer.Flush();
// Length includes UTF-8 BOM
Assert.Equal(52, stream.Length);
string actual = Encoding.UTF8.GetString(stream.ToArray(), 3, (int)stream.Length - 3);
Assert.Equal(expected, actual);
}
[Fact]
public void WriteOptionalAttributeString_ValidInput_Writes()
{
string expected = "<?xml version=\"1.0\" encoding=\"utf-8\"?><element attr=\"val\" />";
var stream = new MemoryStream();
var writer = new XmlTextWriter(stream, Encoding.UTF8);
writer.WriteStartDocument();
writer.WriteStartElement("element");
writer.WriteOptionalAttributeString("attr", "val");
writer.WriteEndElement();
writer.Flush();
// Length includes UTF-8 BOM
Assert.Equal(63, stream.Length);
string actual = Encoding.UTF8.GetString(stream.ToArray(), 3, (int)stream.Length - 3);
Assert.Equal(expected, actual);
}
[Fact]
public void WriteOptionalElementString_NullInput_NoWrite()
{
string expected = "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
var stream = new MemoryStream();
var writer = new XmlTextWriter(stream, Encoding.UTF8);
writer.WriteStartDocument();
writer.WriteOptionalElementString("element", null);
writer.Flush();
// Length includes UTF-8 BOM
Assert.Equal(41, stream.Length);
string actual = Encoding.UTF8.GetString(stream.ToArray(), 3, (int)stream.Length - 3);
Assert.Equal(expected, actual);
}
[Fact]
public void WriteOptionalElementString_EmptyInput_NoWrite()
{
string expected = "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
var stream = new MemoryStream();
var writer = new XmlTextWriter(stream, Encoding.UTF8);
writer.WriteStartDocument();
writer.WriteOptionalElementString("element", string.Empty);
writer.Flush();
// Length includes UTF-8 BOM
Assert.Equal(41, stream.Length);
string actual = Encoding.UTF8.GetString(stream.ToArray(), 3, (int)stream.Length - 3);
Assert.Equal(expected, actual);
}
[Fact]
public void WriteOptionalElementString_ValidInput_Writes()
{
string expected = "<?xml version=\"1.0\" encoding=\"utf-8\"?><element>val</element>";
var stream = new MemoryStream();
var writer = new XmlTextWriter(stream, Encoding.UTF8);
writer.WriteStartDocument();
writer.WriteOptionalElementString("element", "val");
writer.Flush();
// Length includes UTF-8 BOM
Assert.Equal(63, stream.Length);
string actual = Encoding.UTF8.GetString(stream.ToArray(), 3, (int)stream.Length - 3);
Assert.Equal(expected, actual);
}
}
}

View File

@@ -0,0 +1,67 @@
using System.IO;
using System.Text;
using Xunit;
namespace SabreTools.IO.Test
{
public class IniFileTests
{
[Fact]
public void EndToEndTest()
{
string expected = "[section1]\nkey1=value1\nkey2=value2\n";
// Build the INI
var iniFile = new IniFile();
iniFile.AddOrUpdate("section1.key1", "value1");
iniFile["section1.key2"] = "value2";
iniFile["section2.key3"] = "REMOVEME";
bool removed = iniFile.Remove("section2.key3");
Assert.True(removed);
Assert.Equal("value1", iniFile["section1.key1"]);
Assert.Equal("value2", iniFile["section1.key2"]);
// Write the INI
var stream = new MemoryStream();
bool write = iniFile.Write(stream);
// Length includes UTF-8 BOM
Assert.True(write);
Assert.Equal(38, stream.Length);
string actual = Encoding.UTF8.GetString(stream.ToArray(), 3, (int)stream.Length - 3);
Assert.Equal(expected, actual);
// Parse the INI
stream.Seek(0, SeekOrigin.Begin);
var secondIni = new IniFile(stream);
Assert.Equal("value1", secondIni["section1.key1"]);
Assert.Equal("value2", secondIni["section1.key2"]);
}
[Fact]
public void RemoveInvalidKeyTest()
{
var iniFile = new IniFile();
bool removed = iniFile.Remove("invalid.key");
Assert.False(removed);
}
[Fact]
public void ReadEmptyStreamTest()
{
var stream = new MemoryStream();
var iniFile = new IniFile(stream);
Assert.Empty(iniFile);
}
[Fact]
public void WriteEmptyIniFileTest()
{
var iniFile = new IniFile();
var stream = new MemoryStream();
bool write = iniFile.Write(stream);
Assert.False(write);
}
}
}

View File

@@ -0,0 +1,38 @@
using SabreTools.IO.Logging;
using Xunit;
namespace SabreTools.IO.Test.Logging
{
public class ConvertersTests
{
[Theory]
[InlineData(null, LogLevel.VERBOSE)]
[InlineData("", LogLevel.VERBOSE)]
[InlineData("INVALID", LogLevel.VERBOSE)]
[InlineData("verbose", LogLevel.VERBOSE)]
[InlineData("VERBOSE", LogLevel.VERBOSE)]
[InlineData("user", LogLevel.USER)]
[InlineData("USER", LogLevel.USER)]
[InlineData("warning", LogLevel.WARNING)]
[InlineData("WARNING", LogLevel.WARNING)]
[InlineData("error", LogLevel.ERROR)]
[InlineData("ERROR", LogLevel.ERROR)]
public void AsLogLevelTest(string? level, LogLevel expected)
{
LogLevel actual = level.AsLogLevel();
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(LogLevel.VERBOSE, "VERBOSE")]
[InlineData(LogLevel.USER, "USER")]
[InlineData(LogLevel.WARNING, "WARNING")]
[InlineData(LogLevel.ERROR, "ERROR")]
[InlineData((LogLevel)99, null)]
public void FromLogLevelTest(LogLevel level, string? expected)
{
string? actual = level.FromLogLevel();
Assert.Equal(expected, actual);
}
}
}

View File

@@ -0,0 +1,40 @@
using SabreTools.IO.Logging;
using Xunit;
namespace SabreTools.IO.Test.Logging
{
public class InternalStopwatchTests
{
[Fact]
public void Stopwatch_NoSubject_StartNoSubject()
{
var stopwatch = new InternalStopwatch();
stopwatch.Start();
stopwatch.Stop();
}
[Fact]
public void Stopwatch_NoSubject_StartSubject()
{
var stopwatch = new InternalStopwatch();
stopwatch.Start("start");
stopwatch.Stop();
}
[Fact]
public void Stopwatch_Subject_StartNoSubject()
{
var stopwatch = new InternalStopwatch("init");
stopwatch.Start();
stopwatch.Stop();
}
[Fact]
public void Stopwatch_Subject_StartSubject()
{
var stopwatch = new InternalStopwatch("init");
stopwatch.Start("start");
stopwatch.Stop();
}
}
}

View File

@@ -0,0 +1,54 @@
using System;
using SabreTools.IO.Logging;
using Xunit;
namespace SabreTools.IO.Test.Logging
{
public class LoggerTests
{
[Fact]
public void EndToEnd()
{
Assert.Null(LoggerImpl.Filename);
Assert.False(LoggerImpl.LogToFile);
Assert.Null(LoggerImpl.LogDirectory);
Assert.True(LoggerImpl.AppendPrefix);
Assert.False(LoggerImpl.ThrowOnError);
LoggerImpl.Start();
var logger = new Logger();
logger.Verbose("verbose");
logger.Verbose(new Exception());
logger.Verbose(new Exception(), "verbose");
logger.Verbose(1, 1, "verbose");
logger.User("user");
logger.User(new Exception());
logger.User(new Exception(), "user");
logger.User(1, 1, "user");
logger.Warning("warning");
logger.Warning(new Exception());
logger.Warning(new Exception(), "warning");
logger.Warning(1, 1, "warning");
logger.Error("error");
logger.Error(new Exception());
logger.Error(new Exception(), "error");
logger.Error(1, 1, "error");
LoggerImpl.ThrowOnError = true;
Assert.Throws<Exception>(() => logger.Error(new Exception()));
Assert.True(LoggerImpl.StartTime < DateTime.Now);
Assert.True(LoggerImpl.LoggedWarnings);
Assert.True(LoggerImpl.LoggedErrors);
LoggerImpl.SetFilename("logfile.txt", addDate: true);
LoggerImpl.Close();
}
}
}

View File

@@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
using System.IO;
using Xunit;
namespace SabreTools.IO.Test
{
public class PathToolTests
{
[Fact]
public void GetDirectoriesOnly_NoAppendParent()
{
string expectedParent = Path.Combine(Environment.CurrentDirectory, "TestData");
string expectedCurrent = Path.Combine(expectedParent, "Subdirectory");
List<string> inputs =
[
string.Empty,
Path.Combine(Environment.CurrentDirectory, "TestData"),
Path.Combine(Environment.CurrentDirectory, "TestData", "Subdir*"),
];
var actual = PathTool.GetDirectoriesOnly(inputs, appendParent: true);
Assert.NotEmpty(actual);
var first = actual[0];
Assert.Equal(expectedCurrent, first.CurrentPath);
Assert.Equal(expectedParent, first.ParentPath);
}
[Fact]
public void GetDirectoriesOnly_AppendParent()
{
string expectedParent = Path.Combine(Environment.CurrentDirectory, "TestData");
string expectedCurrent = Path.Combine(expectedParent, "Subdirectory");
List<string> inputs =
[
string.Empty,
Path.Combine(Environment.CurrentDirectory, "TestData"),
Path.Combine(Environment.CurrentDirectory, "TestData", "Subdir*"),
];
var actual = PathTool.GetDirectoriesOnly(inputs, appendParent: false);
Assert.NotEmpty(actual);
var first = actual[0];
Assert.Equal(expectedCurrent, first.CurrentPath);
Assert.Equal(string.Empty, first.ParentPath);
}
[Fact]
public void GetFilesOnly_NoAppendParent()
{
string expectedParent = Path.Combine(Environment.CurrentDirectory, "TestData");
string expectedCurrent = Path.Combine(expectedParent, "ascii.txt");
List<string> inputs =
[
string.Empty,
Path.Combine(Environment.CurrentDirectory, "TestData"),
Path.Combine(Environment.CurrentDirectory, "TestData", "Subdir*"),
Path.Combine(Environment.CurrentDirectory, "TestData", "utf8bom.txt"),
];
var actual = PathTool.GetFilesOnly(inputs, appendParent: true);
Assert.NotEmpty(actual);
var first = actual[0];
Assert.Equal(expectedCurrent, first.CurrentPath);
Assert.Equal(expectedParent, first.ParentPath);
}
[Fact]
public void GetFilesOnly_AppendParent()
{
string expectedParent = Path.Combine(Environment.CurrentDirectory, "TestData");
string expectedCurrent = Path.Combine(expectedParent, "ascii.txt");
List<string> inputs =
[
string.Empty,
Path.Combine(Environment.CurrentDirectory, "TestData"),
Path.Combine(Environment.CurrentDirectory, "TestData", "Subdir*"),
Path.Combine(Environment.CurrentDirectory, "TestData", "utf8bom.txt"),
];
var actual = PathTool.GetFilesOnly(inputs, appendParent: false);
Assert.NotEmpty(actual);
var first = actual[0];
Assert.Equal(expectedCurrent, first.CurrentPath);
Assert.Equal(string.Empty, first.ParentPath);
}
}
}

View File

@@ -0,0 +1,60 @@
using System.IO;
using System.Text;
using SabreTools.IO.Readers;
using SabreTools.IO.Writers;
using Xunit;
namespace SabreTools.IO.Test.ReadersWriters
{
public class ClrMameProTests
{
[Fact]
public void EndToEndTest()
{
string expected = "header (\n\tstandalone \"value\"\n)\n\n# Comment\n\ngame (\n\titem ( attr \"value\" )\n)";
// Build and write the CMP file
var stream = new MemoryStream();
var writer = new ClrMameProWriter(stream, Encoding.UTF8);
Assert.True(writer.Quotes);
writer.WriteStartElement("header");
writer.WriteRequiredStandalone("standalone", "value");
writer.WriteOptionalStandalone("optstand", null);
writer.WriteFullEndElement();
writer.WriteString("\n\n# Comment\n");
writer.WriteStartElement("game");
writer.WriteStartElement("item");
writer.WriteRequiredAttributeString("attr", "value");
writer.WriteOptionalAttributeString("optional", null);
writer.WriteEndElement();
writer.WriteFullEndElement();
writer.Flush();
writer.Dispose();
// Length includes UTF-8 BOM
Assert.Equal(77, stream.Length);
string actual = Encoding.UTF8.GetString(stream.ToArray(), 3, (int)stream.Length - 3);
Assert.Equal(expected, actual);
// Parse the CMP file
stream.Seek(0, SeekOrigin.Begin);
var reader = new ClrMameProReader(stream, Encoding.UTF8);
Assert.False(reader.DosCenter);
Assert.True(reader.Quotes);
while (!reader.EndOfStream)
{
bool hasNext = reader.ReadNextLine();
Assert.True(hasNext);
Assert.NotNull(reader.CurrentLine);
Assert.True(reader.LineNumber >= 0);
}
reader.Dispose();
}
}
}

View File

@@ -0,0 +1,50 @@
using System.IO;
using System.Text;
using SabreTools.IO.Readers;
using SabreTools.IO.Writers;
using Xunit;
namespace SabreTools.IO.Test.ReadersWriters
{
public class IniTests
{
[Fact]
public void EndToEndTest()
{
string expected = "[section1]\nkey1=value1\nkey2=value2\n\n;comment\n;string\n";
// Build and write the INI
var stream = new MemoryStream();
var writer = new IniWriter(stream, Encoding.UTF8);
writer.WriteSection("section1");
writer.WriteKeyValuePair("key1", "value1");
writer.WriteKeyValuePair("key2", "value2");
writer.WriteLine();
writer.WriteComment("comment");
writer.WriteString(";string\n");
writer.Flush();
writer.Dispose();
// Length includes UTF-8 BOM
Assert.Equal(56, stream.Length);
string actual = Encoding.UTF8.GetString(stream.ToArray(), 3, (int)stream.Length - 3);
Assert.Equal(expected, actual);
// Parse the INI
stream.Seek(0, SeekOrigin.Begin);
var reader = new IniReader(stream, Encoding.UTF8);
while (!reader.EndOfStream)
{
bool hasNext = reader.ReadNextLine();
Assert.True(hasNext);
Assert.NotNull(reader.CurrentLine);
Assert.True(reader.LineNumber >= 0);
}
reader.Dispose();
}
}
}

View File

@@ -0,0 +1,60 @@
using System.IO;
using System.Text;
using SabreTools.IO.Readers;
using SabreTools.IO.Writers;
using Xunit;
namespace SabreTools.IO.Test.ReadersWriters
{
public class SeparatedValueTests
{
[Fact]
public void EndToEndTest()
{
string expected = "\"col1\",\"col2\",\"col3\"\n\"value1\",\"value2\",\"value3\"\n\"value4\",\"value5\",\"value6\"\n";
// Build and write the CSV
var stream = new MemoryStream();
var writer = new SeparatedValueWriter(stream, Encoding.UTF8);
Assert.True(writer.Quotes);
Assert.Equal(',', writer.Separator);
Assert.True(writer.VerifyFieldCount);
writer.WriteHeader(["col1", "col2", "col3"]);
writer.WriteValues(["value1", "value2", "value3"]);
writer.WriteString("\"value4\",\"value5\",\"value6\"\n");
writer.Flush();
writer.Dispose();
// Length includes UTF-8 BOM
Assert.Equal(78, stream.Length);
string actual = Encoding.UTF8.GetString(stream.ToArray(), 3, (int)stream.Length - 3);
Assert.Equal(expected, actual);
// Parse the CSV
stream.Seek(0, SeekOrigin.Begin);
var reader = new SeparatedValueReader(stream, Encoding.UTF8);
Assert.True(reader.Header);
Assert.True(reader.Quotes);
Assert.Equal(',', reader.Separator);
Assert.True(reader.VerifyFieldCount);
while (!reader.EndOfStream)
{
bool hasNext = reader.ReadNextLine();
Assert.True(hasNext);
Assert.NotNull(reader.CurrentLine);
Assert.True(reader.LineNumber >= 0);
if (reader.LineNumber > 0)
{
Assert.NotNull(reader.GetValue(0));
Assert.NotNull(reader.GetValue("col2"));
}
}
reader.Dispose();
}
}
}

View File

@@ -1,29 +1,39 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<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>
<ProjectReference Include="..\SabreTools.IO\SabreTools.IO.csproj" />
</ItemGroup>
</Project>
<PropertyGroup>
<TargetFrameworks>net6.0;net8.0;net9.0</TargetFrameworks>
<IsPackable>false</IsPackable>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningsNotAsErrors>CS0618</WarningsNotAsErrors>
</PropertyGroup>
<ItemGroup>
<None Remove="TestData\**" />
</ItemGroup>
<ItemGroup>
<Content Include="TestData\**">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</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.12.0" />
<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>
</Project>

View File

@@ -1,4 +1,5 @@
using System.IO;
using System.Linq;
using SabreTools.IO.Streams;
using Xunit;
@@ -24,31 +25,95 @@ namespace SabreTools.IO.Test.Streams
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()
[Theory]
[InlineData(4, 0b00000101, 1)]
[InlineData(9, 0b10101010_1, 2)]
public void ReadBitsBETest(int bits, uint expected, int position)
{
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);
uint? actual = stream.ReadBitsBE(bits);
Assert.NotNull(actual);
Assert.Equal(expected, actual);
Assert.Equal(position, stream.Position);
}
[Theory]
[InlineData(4, 0b00001010, 1)]
[InlineData(9, 0b10101010_1, 2)]
public void ReadBitsLETest(int bits, uint expected, int position)
{
byte[] data = [0b01010101, 0b01010101, 0b01010101, 0b01010101];
var stream = new ReadOnlyBitStream(new MemoryStream(data));
uint? actual = stream.ReadBitsLE(bits);
Assert.NotNull(actual);
Assert.Equal(expected, actual);
Assert.Equal(position, stream.Position);
}
[Fact]
public void ReadBitsMSBTest()
public void ReadByteTest()
{
byte[] data = [0b01010101, 0b01010101, 0b01010101, 0b01010101];
byte expected = 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);
byte? actual = stream.ReadByte();
Assert.NotNull(actual);
Assert.Equal(expected, actual);
}
[Fact]
public void ReadUInt16Test()
{
byte[] data = [0b01010101, 0b01010101, 0b01010101, 0b01010101];
ushort expected = 0b0101010101010101;
var stream = new ReadOnlyBitStream(new MemoryStream(data));
ushort? actual = stream.ReadUInt16();
Assert.NotNull(actual);
Assert.Equal(expected, actual);
}
[Fact]
public void ReadUInt32Test()
{
byte[] data = [0b01010101, 0b01010101, 0b01010101, 0b01010101];
uint expected = 0b01010101010101010101010101010101;
var stream = new ReadOnlyBitStream(new MemoryStream(data));
uint? actual = stream.ReadUInt32();
Assert.NotNull(actual);
Assert.Equal(expected, actual);
}
[Fact]
public void ReadUInt64Test()
{
byte[] data = [0b01010101, 0b01010101, 0b01010101, 0b01010101];
var stream = new ReadOnlyBitStream(new MemoryStream(data));
ulong? actual = stream.ReadUInt64();
Assert.Null(actual);
}
[Fact]
public void ReadBytesTest()
{
byte[] data = [0b01010101, 0b01010101, 0b01010101, 0b01010101];
var stream = new ReadOnlyBitStream(new MemoryStream(data));
byte[]? actual = stream.ReadBytes(4);
Assert.NotNull(actual);
Assert.True(data.SequenceEqual(actual));
}
}
}

View File

@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using System.IO;
using SabreTools.IO.Streams;

View File

@@ -0,0 +1 @@
Sample file for subdirectories

View File

@@ -0,0 +1 @@
This doesn't match anything

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

Binary file not shown.

View File

@@ -0,0 +1 @@
+/v

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,9 @@
#if NET20
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)]
internal sealed class ExtensionAttribute : Attribute {}
}
#endif

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
#if NET7_0_OR_GREATER
using System.Numerics;
#endif
@@ -14,7 +13,6 @@ 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)"/>
@@ -58,8 +56,17 @@ namespace SabreTools.IO.Extensions
public static short ReadInt16BigEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(2);
Array.Reverse(buffer);
return BitConverter.ToInt16(buffer, 0);
return (short)(buffer[1]
| (buffer[0] << 8));
}
/// <inheritdoc cref="BinaryReader.ReadInt16"/>
/// <remarks>Reads in little-endian format</remarks>
public static short ReadInt16LittleEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(2);
return (short)(buffer[0]
| (buffer[1] << 8));
}
/// <inheritdoc cref="BinaryReader.ReadUInt16"/>
@@ -67,10 +74,39 @@ namespace SabreTools.IO.Extensions
public static ushort ReadUInt16BigEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(2);
Array.Reverse(buffer);
return BitConverter.ToUInt16(buffer, 0);
return (ushort)(buffer[1]
| (buffer[0] << 8));
}
/// <inheritdoc cref="BinaryReader.ReadUInt16"/>
/// <remarks>Reads in little-endian format</remarks>
public static ushort ReadUInt16LittleEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(2);
return (ushort)(buffer[0]
| (buffer[1] << 8));
}
/// <summary>
/// Read a WORD (2-byte) from the base stream
/// </summary>
public static ushort ReadWORD(this BinaryReader reader)
=> reader.ReadUInt16();
/// <summary>
/// Read a WORD (2-byte) from the base stream
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static ushort ReadWORDBigEndian(this BinaryReader reader)
=> reader.ReadUInt16BigEndian();
/// <summary>
/// Read a WORD (2-byte) from the base stream
/// </summary>
/// <remarks>Reads in little-endian format</remarks>
public static ushort ReadWORDLittleEndian(this BinaryReader reader)
=> reader.ReadUInt16LittleEndian();
// 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"/>
@@ -84,55 +120,75 @@ namespace SabreTools.IO.Extensions
#endif
/// <summary>
/// Read an Int24 encoded as an Int32
/// Read an Int24 encoded as an Int32 from the base stream
/// </summary>
/// <remarks>Reads in machine native format</remarks>
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);
if (BitConverter.IsLittleEndian)
return reader.ReadInt24LittleEndian();
else
return reader.ReadInt24BigEndian();
}
/// <summary>
/// Read an Int24 encoded as an Int32
/// Read an Int24 encoded as an Int32 from the base stream
/// </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);
return (int)(buffer[2]
| (buffer[1] << 8)
| (buffer[0] << 16));
}
/// <summary>
/// Read a UInt24 encoded as a UInt32
/// Read an Int24 encoded as an Int32 from the base stream
/// </summary>
public static uint ReadUInt24(this BinaryReader reader)
/// <remarks>Reads in little-endian format</remarks>
public static int ReadInt24LittleEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(3);
byte[] padded = new byte[4];
Array.Copy(buffer, padded, 3);
return BitConverter.ToUInt32(padded, 0);
return (int)(buffer[0]
| (buffer[1] << 8)
| (buffer[2] << 16));
}
/// <summary>
/// Read a UInt24 encoded as a UInt32
/// Read a UInt24 encoded as a UInt32 from the base stream
/// </summary>
/// <remarks>Reads in machine native format</remarks>
public static uint ReadUInt24(this BinaryReader reader)
{
if (BitConverter.IsLittleEndian)
return reader.ReadUInt24LittleEndian();
else
return reader.ReadUInt24BigEndian();
}
/// <summary>
/// Read a UInt24 encoded as a UInt32 from the base stream
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static uint ReadUInt24BigEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(3);
Array.Reverse(buffer);
return (uint)(buffer[2]
| (buffer[1] << 8)
| (buffer[0] << 16));
}
byte[] padded = new byte[4];
Array.Copy(buffer, padded, 3);
return BitConverter.ToUInt32(padded, 0);
/// <summary>
/// Read a UInt24 encoded as a UInt32 from the base stream
/// </summary>
/// <remarks>Reads in little-endian format</remarks>
public static uint ReadUInt24LittleEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(3);
return (uint)(buffer[0]
| (buffer[1] << 8)
| (buffer[2] << 16));
}
/// <inheritdoc cref="BinaryReader.ReadInt32"/>
@@ -140,18 +196,65 @@ namespace SabreTools.IO.Extensions
public static int ReadInt32BigEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(4);
Array.Reverse(buffer);
return BitConverter.ToInt32(buffer, 0);
return (int)(buffer[3]
| (buffer[2] << 8)
| (buffer[1] << 16)
| (buffer[0] << 24));
}
/// <inheritdoc cref="BinaryReader.ReadInt32"/>
/// <remarks>Reads in little-endian format</remarks>
public static int ReadInt32LittleEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(4);
return (int)(buffer[0]
| (buffer[1] << 8)
| (buffer[2] << 16)
| (buffer[3] << 24));
}
/// <inheritdoc cref="BinaryReader.ReadUInt32"/>
/// <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);
return (uint)(buffer[3]
| (buffer[2] << 8)
| (buffer[1] << 16)
| (buffer[0] << 24));
}
/// <inheritdoc cref="BinaryReader.ReadUInt32"/>
/// <remarks>Reads in little-endian format</remarks>
public static uint ReadUInt32LittleEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(4);
return (uint)(buffer[0]
| (buffer[1] << 8)
| (buffer[2] << 16)
| (buffer[3] << 24));
}
/// <summary>
/// Read a DWORD (4-byte) from the base stream
/// </summary>
public static uint ReadDWORD(this BinaryReader reader)
=> reader.ReadUInt32();
/// <summary>
/// Read a DWORD (4-byte) from the base stream
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static uint ReadDWORDBigEndian(this BinaryReader reader)
=> reader.ReadUInt32BigEndian();
/// <summary>
/// Read a DWORD (4-byte) from the base stream
/// </summary>
/// <remarks>Reads in little-endian format</remarks>
public static uint ReadDWORDLittleEndian(this BinaryReader reader)
=> reader.ReadUInt32LittleEndian();
/// <inheritdoc cref="BinaryReader.ReadSingle"/>
/// <remarks>Reads in big-endian format</remarks>
public static float ReadSingleBigEndian(this BinaryReader reader)
@@ -162,55 +265,87 @@ namespace SabreTools.IO.Extensions
}
/// <summary>
/// Read an Int48 encoded as an Int64
/// Read an Int48 encoded as an Int64 from the base stream
/// </summary>
/// <remarks>Reads in machine native format</remarks>
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);
if (BitConverter.IsLittleEndian)
return reader.ReadInt48LittleEndian();
else
return reader.ReadInt48BigEndian();
}
/// <summary>
/// Read an Int48 encoded as an Int64
/// Read an Int48 encoded as an Int64 from the base stream
/// </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);
return ((long)buffer[5] << 0)
| ((long)buffer[4] << 8)
| ((long)buffer[3] << 16)
| ((long)buffer[2] << 24)
| ((long)buffer[1] << 32)
| ((long)buffer[0] << 40);
}
/// <summary>
/// Read a UInt48 encoded as a UInt64
/// Read an Int48 encoded as an Int64 from the base stream
/// </summary>
public static ulong ReadUInt48(this BinaryReader reader)
/// <remarks>Reads in little-endian format</remarks>
public static long ReadInt48LittleEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(6);
byte[] padded = new byte[8];
Array.Copy(buffer, padded, 6);
return BitConverter.ToUInt64(padded, 0);
return ((long)buffer[0] << 0)
| ((long)buffer[1] << 8)
| ((long)buffer[2] << 16)
| ((long)buffer[3] << 24)
| ((long)buffer[4] << 32)
| ((long)buffer[5] << 40);
}
/// <summary>
/// Read a UInt48 encoded as a UInt64
/// Read a UInt48 encoded as a UInt64 from the base stream
/// </summary>
/// <remarks>Reads in machine native format</remarks>
public static ulong ReadUInt48(this BinaryReader reader)
{
if (BitConverter.IsLittleEndian)
return reader.ReadUInt48LittleEndian();
else
return reader.ReadUInt48BigEndian();
}
/// <summary>
/// Read a UInt48 encoded as a UInt64 from the base stream
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static ulong ReadUInt48BigEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(6);
Array.Reverse(buffer);
return ((ulong)buffer[5] << 0)
| ((ulong)buffer[4] << 8)
| ((ulong)buffer[3] << 16)
| ((ulong)buffer[2] << 24)
| ((ulong)buffer[1] << 32)
| ((ulong)buffer[0] << 40);
}
byte[] padded = new byte[8];
Array.Copy(buffer, padded, 6);
return BitConverter.ToUInt64(padded, 0);
/// <summary>
/// Read an UInt48 encoded as an UInt64 from the base stream
/// </summary>
/// <remarks>Reads in little-endian format</remarks>
public static ulong ReadUInt48LittleEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(6);
return ((ulong)buffer[0] << 0)
| ((ulong)buffer[1] << 8)
| ((ulong)buffer[2] << 16)
| ((ulong)buffer[3] << 24)
| ((ulong)buffer[4] << 32)
| ((ulong)buffer[5] << 40);
}
/// <inheritdoc cref="BinaryReader.ReadInt64"/>
@@ -218,8 +353,29 @@ namespace SabreTools.IO.Extensions
public static long ReadInt64BigEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(8);
Array.Reverse(buffer);
return BitConverter.ToInt64(buffer, 0);
return ((long)buffer[7] << 0)
| ((long)buffer[6] << 8)
| ((long)buffer[5] << 16)
| ((long)buffer[4] << 24)
| ((long)buffer[3] << 32)
| ((long)buffer[2] << 40)
| ((long)buffer[1] << 48)
| ((long)buffer[0] << 56);
}
/// <inheritdoc cref="BinaryReader.ReadInt64"/>
/// <remarks>Reads in little-endian format</remarks>
public static long ReadInt64LittleEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(8);
return ((long)buffer[0] << 0)
| ((long)buffer[1] << 8)
| ((long)buffer[2] << 16)
| ((long)buffer[3] << 24)
| ((long)buffer[4] << 32)
| ((long)buffer[5] << 40)
| ((long)buffer[6] << 48)
| ((long)buffer[7] << 56);
}
/// <inheritdoc cref="BinaryReader.ReadUInt64"/>
@@ -227,10 +383,52 @@ namespace SabreTools.IO.Extensions
public static ulong ReadUInt64BigEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(8);
Array.Reverse(buffer);
return BitConverter.ToUInt64(buffer, 0);
return ((ulong)buffer[7] << 0)
| ((ulong)buffer[6] << 8)
| ((ulong)buffer[5] << 16)
| ((ulong)buffer[4] << 24)
| ((ulong)buffer[3] << 32)
| ((ulong)buffer[2] << 40)
| ((ulong)buffer[1] << 48)
| ((ulong)buffer[0] << 56);
}
/// <inheritdoc cref="BinaryReader.ReadUInt64"/>
/// <remarks>Reads in little-endian format</remarks>
public static ulong ReadUInt64LittleEndian(this BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(8);
return ((ulong)buffer[0] << 0)
| ((ulong)buffer[1] << 8)
| ((ulong)buffer[2] << 16)
| ((ulong)buffer[3] << 24)
| ((ulong)buffer[4] << 32)
| ((ulong)buffer[5] << 40)
| ((ulong)buffer[6] << 48)
| ((ulong)buffer[7] << 56);
}
/// <summary>
/// Read a QWORD (8-byte) from the base stream
/// </summary>
/// <remarks>Reads in machine native format</remarks>
public static ulong ReadQWORD(this BinaryReader reader)
=> reader.ReadUInt64();
/// <summary>
/// Read a QWORD (8-byte) from the base stream
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static ulong ReadQWORDBigEndian(this BinaryReader reader)
=> reader.ReadUInt64BigEndian();
/// <summary>
/// Read a QWORD (8-byte) from the base stream
/// </summary>
/// <remarks>Reads in little-endian format</remarks>
public static ulong ReadQWORDLittleEndian(this BinaryReader reader)
=> reader.ReadUInt64LittleEndian();
/// <inheritdoc cref="BinaryReader.ReadDouble"/>
/// <remarks>Reads in big-endian format</remarks>
public static double ReadDoubleBigEndian(this BinaryReader reader)
@@ -340,9 +538,10 @@ namespace SabreTools.IO.Extensions
while (reader.BaseStream.Position < reader.BaseStream.Length)
{
byte ch = reader.ReadByte();
buffer.Add(ch);
if (ch == '\0')
break;
buffer.Add(ch);
}
return encoding.GetString([.. buffer]);
@@ -393,7 +592,7 @@ namespace SabreTools.IO.Extensions
return null;
byte[] buffer = ReadUntilNull4Byte(reader);
return Encoding.Unicode.GetString(buffer);
return Encoding.UTF32.GetString(buffer);
}
/// <summary>
@@ -428,45 +627,6 @@ namespace SabreTools.IO.Extensions
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>
@@ -595,7 +755,10 @@ namespace SabreTools.IO.Extensions
else if (fi.FieldType.IsArray)
{
var value = ReadArrayType(reader, fields, instance, fi);
fi.SetValue(instance, Convert.ChangeType(value, fi.FieldType));
if (value.GetType() == fi.FieldType)
fi.SetValue(instance, value);
else
fi.SetValue(instance, Convert.ChangeType(value, fi.FieldType));
}
else
{
@@ -626,7 +789,10 @@ namespace SabreTools.IO.Extensions
for (int i = 0; i < elementCount; i++)
{
var value = ReadType(reader, elementType);
arr.SetValue(value, i);
if (value != null && elementType.IsEnum)
arr.SetValue(Enum.ToObject(elementType, value), i);
else
arr.SetValue(value, i);
}
// Return the built array
@@ -638,8 +804,17 @@ namespace SabreTools.IO.Extensions
/// </summary>
private static string? ReadStringType(BinaryReader reader, Encoding encoding, FieldInfo? fi)
{
var marshalAsAttr = fi?.GetCustomAttributes(typeof(MarshalAsAttribute), true)?.FirstOrDefault() as MarshalAsAttribute;
// If the FieldInfo is null
if (fi == null)
return null;
// Get all MarshalAs attributes for the field, if possible
var attributes = fi.GetCustomAttributes(typeof(MarshalAsAttribute), true);
if (attributes.Length == 0)
return null;
// Use the first found attribute
var marshalAsAttr = attributes[0] as MarshalAsAttribute;
switch (marshalAsAttr?.Value)
{
case UnmanagedType.AnsiBStr:

View File

@@ -12,7 +12,6 @@ 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
{
@@ -374,7 +373,7 @@ namespace SabreTools.IO.Extensions
byte[] buffer = Encoding.ASCII.GetBytes(value);
// Write the length as a byte
writer.Write((byte)buffer.Length);
writer.Write((byte)value.Length);
// Write the buffer
return WriteFromBuffer(writer, buffer);
@@ -393,34 +392,12 @@ namespace SabreTools.IO.Extensions
byte[] buffer = Encoding.Unicode.GetBytes(value);
// Write the length as a ushort
writer.Write((ushort)buffer.Length);
writer.Write((ushort)value.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>

View File

@@ -0,0 +1,54 @@
using System;
namespace SabreTools.IO.Extensions
{
public static class ByteArrayExtensions
{
/// <summary>
/// Indicates whether the specified array is null or has a length of zero
/// </summary>
public static bool IsNullOrEmpty(this Array? array)
{
return array == null || array.Length == 0;
}
/// <summary>
/// Convert a byte array to a hex string
/// </summary>
public static string? ToHexString(this byte[]? bytes)
{
// If we get null in, we send null out
if (bytes == null)
return null;
string hex = BitConverter.ToString(bytes);
return hex.Replace("-", string.Empty).ToLowerInvariant();
}
/// <summary>
/// Convert a hex string to a byte array
/// </summary>
public static byte[]? FromHexString(this string? hex)
{
// If we get null in, we send null out
if (string.IsNullOrEmpty(hex))
return null;
try
{
int chars = hex!.Length;
byte[] bytes = new byte[chars / 2];
for (int i = 0; i < chars; i += 2)
{
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
}
return bytes;
}
catch
{
return null;
}
}
}
}

View File

@@ -12,7 +12,6 @@ namespace SabreTools.IO.Extensions
/// <summary>
/// Extensions for byte arrays
/// </summary>
/// TODO: Handle proper negative values for Int24 and Int48
public static class ByteArrayReaderExtensions
{
/// <summary>
@@ -20,7 +19,7 @@ namespace SabreTools.IO.Extensions
/// </summary>
public static byte ReadByte(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 1);
byte[] buffer = ReadExactlyToBuffer(content, ref offset, 1);
return buffer[0];
}
@@ -34,7 +33,7 @@ namespace SabreTools.IO.Extensions
/// 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);
=> ReadExactlyToBuffer(content, ref offset, count);
/// <summary>
/// Read a UInt8[] and increment the pointer to an array
@@ -42,7 +41,7 @@ namespace SabreTools.IO.Extensions
/// <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);
byte[] buffer = ReadExactlyToBuffer(content, ref offset, count);
Array.Reverse(buffer);
return buffer;
}
@@ -52,7 +51,7 @@ namespace SabreTools.IO.Extensions
/// </summary>
public static sbyte ReadSByte(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 1);
byte[] buffer = ReadExactlyToBuffer(content, ref offset, 1);
return (sbyte)buffer[0];
}
@@ -61,17 +60,20 @@ namespace SabreTools.IO.Extensions
/// </summary>
public static char ReadChar(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 1);
byte[] buffer = ReadExactlyToBuffer(content, ref offset, 1);
return (char)buffer[0];
}
/// <summary>
/// Read an Int16 and increment the pointer to an array
/// </summary>
/// <remarks>Reads in machine native format</remarks>
public static short ReadInt16(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 2);
return BitConverter.ToInt16(buffer, 0);
if (BitConverter.IsLittleEndian)
return content.ReadInt16LittleEndian(ref offset);
else
return content.ReadInt16BigEndian(ref offset);
}
/// <summary>
@@ -80,18 +82,32 @@ namespace SabreTools.IO.Extensions
/// <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);
byte[] buffer = ReadExactlyToBuffer(content, ref offset, 2);
return (short)(buffer[1]
| (buffer[0] << 8));
}
/// <summary>
/// Read an Int16 and increment the pointer to an array
/// </summary>
/// <remarks>Reads in little-endian format</remarks>
public static short ReadInt16LittleEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadExactlyToBuffer(content, ref offset, 2);
return (short)(buffer[0]
| (buffer[1] << 8));
}
/// <summary>
/// Read a UInt16 and increment the pointer to an array
/// </summary>
/// <remarks>Reads in machine native format</remarks>
public static ushort ReadUInt16(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 2);
return BitConverter.ToUInt16(buffer, 0);
if (BitConverter.IsLittleEndian)
return content.ReadUInt16LittleEndian(ref offset);
else
return content.ReadUInt16BigEndian(ref offset);
}
/// <summary>
@@ -100,19 +116,52 @@ namespace SabreTools.IO.Extensions
/// <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);
byte[] buffer = ReadExactlyToBuffer(content, ref offset, 2);
return (ushort)(buffer[1]
| (buffer[0] << 8));
}
/// <summary>
/// Read a UInt16 and increment the pointer to an array
/// </summary>
/// <remarks>Reads in little-endian format</remarks>
public static ushort ReadUInt16LittleEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadExactlyToBuffer(content, ref offset, 2);
return (ushort)(buffer[0]
| (buffer[1] << 8));
}
/// <summary>
/// Read a WORD (2-byte) and increment the pointer to an array
/// </summary>
/// <remarks>Reads in machine native format</remarks>
public static ushort ReadWORD(this byte[] content, ref int offset)
=> content.ReadUInt16(ref offset);
/// <summary>
/// Read a WORD (2-byte) and increment the pointer to an array
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static ushort ReadWORDBigEndian(this byte[] content, ref int offset)
=> content.ReadUInt16BigEndian(ref offset);
/// <summary>
/// Read a WORD (2-byte) and increment the pointer to an array
/// </summary>
/// <remarks>Reads in little-endian format</remarks>
public static ushort ReadWORDLittleEndian(this byte[] content, ref int offset)
=> content.ReadUInt16LittleEndian(ref offset);
// 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>
/// <remarks>Reads in machine native format</remarks>
public static Half ReadHalf(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 2);
byte[] buffer = ReadExactlyToBuffer(content, ref offset, 2);
return BitConverter.ToHalf(buffer, 0);
}
@@ -122,7 +171,7 @@ namespace SabreTools.IO.Extensions
/// <remarks>Reads in big-endian format</remarks>
public static Half ReadHalfBigEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 2);
byte[] buffer = ReadExactlyToBuffer(content, ref offset, 2);
Array.Reverse(buffer);
return BitConverter.ToHalf(buffer, 0);
}
@@ -131,13 +180,13 @@ namespace SabreTools.IO.Extensions
/// <summary>
/// Read an Int24 encoded as an Int32 and increment the pointer to an array
/// </summary>
/// <remarks>Reads in machine native format</remarks>
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);
if (BitConverter.IsLittleEndian)
return content.ReadInt24LittleEndian(ref offset);
else
return content.ReadInt24BigEndian(ref offset);
}
/// <summary>
@@ -146,24 +195,34 @@ namespace SabreTools.IO.Extensions
/// <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[] buffer = ReadExactlyToBuffer(content, ref offset, 3);
return (int)(buffer[2]
| (buffer[1] << 8)
| (buffer[0] << 16));
}
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 little-endian format</remarks>
public static int ReadInt24LittleEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadExactlyToBuffer(content, ref offset, 3);
return (int)(buffer[0]
| (buffer[1] << 8)
| (buffer[2] << 16));
}
/// <summary>
/// Read a UInt24 encoded as a UInt32 and increment the pointer to an array
/// </summary>
/// <remarks>Reads in machine native format</remarks>
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);
if (BitConverter.IsLittleEndian)
return content.ReadUInt24LittleEndian(ref offset);
else
return content.ReadUInt24BigEndian(ref offset);
}
/// <summary>
@@ -172,21 +231,34 @@ namespace SabreTools.IO.Extensions
/// <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[] buffer = ReadExactlyToBuffer(content, ref offset, 3);
return (uint)(buffer[2]
| (buffer[1] << 8)
| (buffer[0] << 16));
}
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 little-endian format</remarks>
public static uint ReadUInt24LittleEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadExactlyToBuffer(content, ref offset, 3);
return (uint)(buffer[0]
| (buffer[1] << 8)
| (buffer[2] << 16));
}
/// <summary>
/// Read an Int32 and increment the pointer to an array
/// </summary>
/// <remarks>Reads in machine native format</remarks>
public static int ReadInt32(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 4);
return BitConverter.ToInt32(buffer, 0);
if (BitConverter.IsLittleEndian)
return content.ReadInt32LittleEndian(ref offset);
else
return content.ReadInt32BigEndian(ref offset);
}
/// <summary>
@@ -195,18 +267,36 @@ namespace SabreTools.IO.Extensions
/// <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);
byte[] buffer = ReadExactlyToBuffer(content, ref offset, 4);
return (int)(buffer[3]
| (buffer[2] << 8)
| (buffer[1] << 16)
| (buffer[0] << 24));
}
/// <summary>
/// Read an Int32 and increment the pointer to an array
/// </summary>
/// <remarks>Reads in little-endian format</remarks>
public static int ReadInt32LittleEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadExactlyToBuffer(content, ref offset, 4);
return (int)(buffer[0]
| (buffer[1] << 8)
| (buffer[2] << 16)
| (buffer[3] << 24));
}
/// <summary>
/// Read a UInt32 and increment the pointer to an array
/// </summary>
/// <remarks>Reads in machine native format</remarks>
public static uint ReadUInt32(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 4);
return BitConverter.ToUInt32(buffer, 0);
if (BitConverter.IsLittleEndian)
return content.ReadUInt32LittleEndian(ref offset);
else
return content.ReadUInt32BigEndian(ref offset);
}
/// <summary>
@@ -215,17 +305,54 @@ namespace SabreTools.IO.Extensions
/// <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);
byte[] buffer = ReadExactlyToBuffer(content, ref offset, 4);
return (uint)(buffer[3]
| (buffer[2] << 8)
| (buffer[1] << 16)
| (buffer[0] << 24));
}
/// <summary>
/// Read a UInt32 and increment the pointer to an array
/// </summary>
/// <remarks>Reads in little-endian format</remarks>
public static uint ReadUInt32LittleEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadExactlyToBuffer(content, ref offset, 4);
return (uint)(buffer[0]
| (buffer[1] << 8)
| (buffer[2] << 16)
| (buffer[3] << 24));
}
/// <summary>
/// Read a DWORD (4-byte) and increment the pointer to an array
/// </summary>
/// <remarks>Reads in machine native format</remarks>
public static uint ReadDWORD(this byte[] content, ref int offset)
=> content.ReadUInt32(ref offset);
/// <summary>
/// Read a DWORD (4-byte) and increment the pointer to an array
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static uint ReadDWORDBigEndian(this byte[] content, ref int offset)
=> content.ReadUInt32BigEndian(ref offset);
/// <summary>
/// Read a DWORD (4-byte) and increment the pointer to an array
/// </summary>
/// <remarks>Reads in little-endian format</remarks>
public static uint ReadDWORDLittleEndian(this byte[] content, ref int offset)
=> content.ReadUInt32LittleEndian(ref offset);
/// <summary>
/// Read a Single and increment the pointer to an array
/// </summary>
/// <remarks>Reads in machine native format</remarks>
public static float ReadSingle(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 4);
byte[] buffer = ReadExactlyToBuffer(content, ref offset, 4);
return BitConverter.ToSingle(buffer, 0);
}
@@ -235,7 +362,7 @@ namespace SabreTools.IO.Extensions
/// <remarks>Reads in big-endian format</remarks>
public static float ReadSingleBigEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 4);
byte[] buffer = ReadExactlyToBuffer(content, ref offset, 4);
Array.Reverse(buffer);
return BitConverter.ToSingle(buffer, 0);
}
@@ -243,13 +370,13 @@ namespace SabreTools.IO.Extensions
/// <summary>
/// Read an Int48 encoded as an Int64 and increment the pointer to an array
/// </summary>
/// <remarks>Reads in machine native format</remarks>
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);
if (BitConverter.IsLittleEndian)
return content.ReadInt48LittleEndian(ref offset);
else
return content.ReadInt48BigEndian(ref offset);
}
/// <summary>
@@ -258,47 +385,82 @@ namespace SabreTools.IO.Extensions
/// <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[] buffer = ReadExactlyToBuffer(content, ref offset, 6);
return ((long)buffer[5] << 0)
| ((long)buffer[4] << 8)
| ((long)buffer[3] << 16)
| ((long)buffer[2] << 24)
| ((long)buffer[1] << 32)
| ((long)buffer[0] << 40);
}
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 little-endian format</remarks>
public static long ReadInt48LittleEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadExactlyToBuffer(content, ref offset, 6);
return ((long)buffer[0] << 0)
| ((long)buffer[1] << 8)
| ((long)buffer[2] << 16)
| ((long)buffer[3] << 24)
| ((long)buffer[4] << 32)
| ((long)buffer[5] << 40);
}
/// <summary>
/// Read a UInt48 encoded as a UInt64 and increment the pointer to an array
/// </summary>
/// <remarks>Reads in machine native format</remarks>
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);
if (BitConverter.IsLittleEndian)
return content.ReadUInt48LittleEndian(ref offset);
else
return content.ReadUInt48BigEndian(ref offset);
}
/// <summary>
/// Read a UInt48 encoded as a UInt64 and increment the pointer to an array
/// Read an UInt48 encoded as an 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[] buffer = ReadExactlyToBuffer(content, ref offset, 6);
return ((ulong)buffer[5] << 0)
| ((ulong)buffer[4] << 8)
| ((ulong)buffer[3] << 16)
| ((ulong)buffer[2] << 24)
| ((ulong)buffer[1] << 32)
| ((ulong)buffer[0] << 40);
}
byte[] padded = new byte[8];
Array.Copy(buffer, padded, 6);
return BitConverter.ToUInt64(padded, 0);
/// <summary>
/// Read an UInt48 encoded as an UInt64 and increment the pointer to an array
/// </summary>
/// <remarks>Reads in little-endian format</remarks>
public static ulong ReadUInt48LittleEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadExactlyToBuffer(content, ref offset, 6);
return ((ulong)buffer[0] << 0)
| ((ulong)buffer[1] << 8)
| ((ulong)buffer[2] << 16)
| ((ulong)buffer[3] << 24)
| ((ulong)buffer[4] << 32)
| ((ulong)buffer[5] << 40);
}
/// <summary>
/// Read an Int64 and increment the pointer to an array
/// </summary>
/// <remarks>Reads in machine native format</remarks>
public static long ReadInt64(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 8);
return BitConverter.ToInt64(buffer, 0);
if (BitConverter.IsLittleEndian)
return content.ReadInt64LittleEndian(ref offset);
else
return content.ReadInt64BigEndian(ref offset);
}
/// <summary>
@@ -307,18 +469,44 @@ namespace SabreTools.IO.Extensions
/// <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);
byte[] buffer = ReadExactlyToBuffer(content, ref offset, 8);
return ((long)buffer[7] << 0)
| ((long)buffer[6] << 8)
| ((long)buffer[5] << 16)
| ((long)buffer[4] << 24)
| ((long)buffer[3] << 32)
| ((long)buffer[2] << 40)
| ((long)buffer[1] << 48)
| ((long)buffer[0] << 56);
}
/// <summary>
/// Read an Int64 and increment the pointer to an array
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static long ReadInt64LittleEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadExactlyToBuffer(content, ref offset, 8);
return ((long)buffer[0] << 0)
| ((long)buffer[1] << 8)
| ((long)buffer[2] << 16)
| ((long)buffer[3] << 24)
| ((long)buffer[4] << 32)
| ((long)buffer[5] << 40)
| ((long)buffer[6] << 48)
| ((long)buffer[7] << 56);
}
/// <summary>
/// Read a UInt64 and increment the pointer to an array
/// </summary>
/// <remarks>Reads in machine native format</remarks>
public static ulong ReadUInt64(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 8);
return BitConverter.ToUInt64(buffer, 0);
if (BitConverter.IsLittleEndian)
return content.ReadUInt64LittleEndian(ref offset);
else
return content.ReadUInt64BigEndian(ref offset);
}
/// <summary>
@@ -327,17 +515,62 @@ namespace SabreTools.IO.Extensions
/// <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);
byte[] buffer = ReadExactlyToBuffer(content, ref offset, 8);
return ((ulong)buffer[7] << 0)
| ((ulong)buffer[6] << 8)
| ((ulong)buffer[5] << 16)
| ((ulong)buffer[4] << 24)
| ((ulong)buffer[3] << 32)
| ((ulong)buffer[2] << 40)
| ((ulong)buffer[1] << 48)
| ((ulong)buffer[0] << 56);
}
/// <summary>
/// Read a UInt64 and increment the pointer to an array
/// </summary>
/// <remarks>Reads in little-endian format</remarks>
public static ulong ReadUInt64LittleEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadExactlyToBuffer(content, ref offset, 8);
return ((ulong)buffer[0] << 0)
| ((ulong)buffer[1] << 8)
| ((ulong)buffer[2] << 16)
| ((ulong)buffer[3] << 24)
| ((ulong)buffer[4] << 32)
| ((ulong)buffer[5] << 40)
| ((ulong)buffer[6] << 48)
| ((ulong)buffer[7] << 56);
}
/// <summary>
/// Read a QWORD (8-byte) and increment the pointer to an array
/// </summary>
/// <remarks>Reads in machine native format</remarks>
public static ulong ReadQWORD(this byte[] content, ref int offset)
=> content.ReadUInt64(ref offset);
/// <summary>
/// Read a QWORD (8-byte) and increment the pointer to an array
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static ulong ReadQWORDBigEndian(this byte[] content, ref int offset)
=> content.ReadUInt64BigEndian(ref offset);
/// <summary>
/// Read a QWORD (8-byte) and increment the pointer to an array
/// </summary>
/// <remarks>Reads in little-endian format</remarks>
public static ulong ReadQWORDLittleEndian(this byte[] content, ref int offset)
=> content.ReadUInt64LittleEndian(ref offset);
/// <summary>
/// Read a Double and increment the pointer to an array
/// </summary>
/// <remarks>Reads in machine native format</remarks>
public static double ReadDouble(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 8);
byte[] buffer = ReadExactlyToBuffer(content, ref offset, 8);
return BitConverter.ToDouble(buffer, 0);
}
@@ -347,7 +580,7 @@ namespace SabreTools.IO.Extensions
/// <remarks>Reads in big-endian format</remarks>
public static double ReadDoubleBigEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 8);
byte[] buffer = ReadExactlyToBuffer(content, ref offset, 8);
Array.Reverse(buffer);
return BitConverter.ToDouble(buffer, 0);
}
@@ -355,9 +588,10 @@ namespace SabreTools.IO.Extensions
/// <summary>
/// Read a Decimal and increment the pointer to an array
/// </summary>
/// <remarks>Reads in machine native format</remarks>
public static decimal ReadDecimal(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 16);
byte[] buffer = ReadExactlyToBuffer(content, ref offset, 16);
int lo = BitConverter.ToInt32(buffer, 0);
int mid = BitConverter.ToInt32(buffer, 4);
@@ -373,7 +607,7 @@ namespace SabreTools.IO.Extensions
/// <remarks>Reads in big-endian format</remarks>
public static decimal ReadDecimalBigEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 16);
byte[] buffer = ReadExactlyToBuffer(content, ref offset, 16);
Array.Reverse(buffer);
int lo = BitConverter.ToInt32(buffer, 0);
@@ -387,9 +621,10 @@ namespace SabreTools.IO.Extensions
/// <summary>
/// Read a Guid and increment the pointer to an array
/// </summary>
/// <remarks>Reads in machine native format</remarks>
public static Guid ReadGuid(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 16);
byte[] buffer = ReadExactlyToBuffer(content, ref offset, 16);
return new Guid(buffer);
}
@@ -399,7 +634,7 @@ namespace SabreTools.IO.Extensions
/// <remarks>Reads in big-endian format</remarks>
public static Guid ReadGuidBigEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 16);
byte[] buffer = ReadExactlyToBuffer(content, ref offset, 16);
Array.Reverse(buffer);
return new Guid(buffer);
}
@@ -408,9 +643,10 @@ namespace SabreTools.IO.Extensions
/// <summary>
/// Read an Int128 and increment the pointer to an array
/// </summary>
/// <remarks>Reads in machine native format</remarks>
public static Int128 ReadInt128(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 16);
byte[] buffer = ReadExactlyToBuffer(content, ref offset, 16);
return (Int128)new BigInteger(buffer);
}
@@ -420,7 +656,7 @@ namespace SabreTools.IO.Extensions
/// <remarks>Reads in big-endian format</remarks>
public static Int128 ReadInt128BigEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 16);
byte[] buffer = ReadExactlyToBuffer(content, ref offset, 16);
Array.Reverse(buffer);
return (Int128)new BigInteger(buffer);
}
@@ -428,9 +664,10 @@ namespace SabreTools.IO.Extensions
/// <summary>
/// Read a UInt128 and increment the pointer to an array
/// </summary>
/// <remarks>Reads in machine native format</remarks>
public static UInt128 ReadUInt128(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 16);
byte[] buffer = ReadExactlyToBuffer(content, ref offset, 16);
return (UInt128)new BigInteger(buffer);
}
@@ -440,7 +677,7 @@ namespace SabreTools.IO.Extensions
/// <remarks>Reads in big-endian format</remarks>
public static UInt128 ReadUInt128BigEndian(this byte[] content, ref int offset)
{
byte[] buffer = ReadToBuffer(content, ref offset, 16);
byte[] buffer = ReadExactlyToBuffer(content, ref offset, 16);
Array.Reverse(buffer);
return (UInt128)new BigInteger(buffer);
}
@@ -468,9 +705,10 @@ namespace SabreTools.IO.Extensions
while (offset < content.Length)
{
byte ch = content.ReadByteValue(ref offset);
buffer.Add(ch);
if (ch == '\0')
break;
buffer.Add(ch);
}
return encoding.GetString([.. buffer]);
@@ -521,7 +759,7 @@ namespace SabreTools.IO.Extensions
return null;
byte[] buffer = ReadUntilNull4Byte(content, ref offset);
return Encoding.Unicode.GetString(buffer);
return Encoding.UTF32.GetString(buffer);
}
/// <summary>
@@ -556,45 +794,6 @@ namespace SabreTools.IO.Extensions
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>
@@ -652,7 +851,7 @@ namespace SabreTools.IO.Extensions
try
{
int typeSize = Marshal.SizeOf(type);
byte[] buffer = ReadToBuffer(content, ref offset, typeSize);
byte[] buffer = ReadExactlyToBuffer(content, ref offset, typeSize);
var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
var data = Marshal.PtrToStructure(handle.AddrOfPinnedObject(), type);
@@ -723,7 +922,10 @@ namespace SabreTools.IO.Extensions
else if (fi.FieldType.IsArray)
{
var value = ReadArrayType(content, ref offset, fields, instance, fi);
fi.SetValue(instance, Convert.ChangeType(value, fi.FieldType));
if (value.GetType() == fi.FieldType)
fi.SetValue(instance, value);
else
fi.SetValue(instance, Convert.ChangeType(value, fi.FieldType));
}
else
{
@@ -754,7 +956,10 @@ namespace SabreTools.IO.Extensions
for (int i = 0; i < elementCount; i++)
{
var value = ReadType(content, ref offset, elementType);
arr.SetValue(value, i);
if (value != null && elementType.IsEnum)
arr.SetValue(Enum.ToObject(elementType, value), i);
else
arr.SetValue(value, i);
}
// Return the built array
@@ -858,11 +1063,11 @@ namespace SabreTools.IO.Extensions
/// <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)
private static byte[] ReadExactlyToBuffer(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");
throw new ArgumentOutOfRangeException($"{nameof(length)} must be 0 or a positive value, {length} requested");
// Handle the 0-byte case
if (length == 0)
@@ -870,7 +1075,7 @@ namespace SabreTools.IO.Extensions
// If there are not enough bytes
if (offset + length > content.Length)
throw new System.IO.EndOfStreamException($"Requested to read {nameof(length)} bytes from {nameof(content)}, {content.Length - offset} returned");
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];

View File

@@ -525,7 +525,7 @@ namespace SabreTools.IO.Extensions
byte[] buffer = Encoding.ASCII.GetBytes(value);
// Write the length as a byte
if (!content.Write(ref offset, (byte)buffer.Length))
if (!content.Write(ref offset, (byte)value.Length))
return false;
// Write the buffer
@@ -545,35 +545,13 @@ namespace SabreTools.IO.Extensions
byte[] buffer = Encoding.Unicode.GetBytes(value);
// Write the length as a ushort
if (!content.Write(ref offset, (ushort)buffer.Length))
if (!content.Write(ref offset, (ushort)value.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>

View File

@@ -1,12 +1,5 @@
#if NETCOREAPP3_1_OR_GREATER
using System;
#endif
using System.Collections.Generic;
using System.Collections.Generic;
using System.IO;
#if NETCOREAPP3_1_OR_GREATER
using System.IO.Enumeration;
#endif
using System.Linq;
using System.Text;
namespace SabreTools.IO.Extensions
@@ -56,13 +49,13 @@ namespace SabreTools.IO.Extensions
// Try to open the file
try
{
FileStream file = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
var 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);
int read = file.Read(bom, 0, 4);
file.Dispose();
// Disable warning about UTF7 usage
@@ -116,33 +109,33 @@ namespace SabreTools.IO.Extensions
public static List<string>? ListEmpty(this string? root)
{
// Check null or empty first
if (string.IsNullOrEmpty(root))
if (root == null)
return null;
// Then, check if the root exists
if (!Directory.Exists(root))
return null;
// If it does and it is empty, return a blank enumerable
if (!root!.SafeEnumerateFileSystemEntries("*", SearchOption.AllDirectories).Any())
return [];
// Otherwise, get the complete list
return root!.SafeEnumerateDirectories("*", SearchOption.AllDirectories)
.Where(dir => !dir.SafeEnumerateFileSystemEntries("*", SearchOption.AllDirectories).Any())
.ToList();
var empty = new List<string>();
foreach (var dir in SafeGetDirectories(root, "*", SearchOption.AllDirectories))
{
if (SafeGetFiles(dir).Length == 0)
empty.Add(dir);
}
return empty;
}
#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)
public static string[] SafeGetDirectories(this string path)
{
try
{
var enumerable = Directory.GetDirectories(path);
return enumerable.SafeEnumerate();
return Directory.GetDirectories(path);
}
catch
{
@@ -152,12 +145,11 @@ namespace SabreTools.IO.Extensions
/// <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)
public static string[] SafeGetDirectories(this string path, string searchPattern)
{
try
{
var enumerable = Directory.GetDirectories(path, searchPattern);
return enumerable.SafeEnumerate();
return Directory.GetDirectories(path, searchPattern);
}
catch
{
@@ -167,12 +159,11 @@ namespace SabreTools.IO.Extensions
/// <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)
public static string[] SafeGetDirectories(this string path, string searchPattern, SearchOption searchOption)
{
try
{
var enumerable = Directory.GetDirectories(path, searchPattern, searchOption);
return enumerable.SafeEnumerate();
return Directory.GetDirectories(path, searchPattern, searchOption);
}
catch
{
@@ -182,12 +173,11 @@ namespace SabreTools.IO.Extensions
/// <inheritdoc cref="Directory.GetFiles(string)"/>
/// <remarks>Returns an empty enumerable on any exception</remarks>
public static IEnumerable<string> SafeGetFiles(this string path)
public static string[] SafeGetFiles(this string path)
{
try
{
var enumerable = Directory.GetFiles(path);
return enumerable.SafeEnumerate();
return Directory.GetFiles(path);
}
catch
{
@@ -197,12 +187,11 @@ namespace SabreTools.IO.Extensions
/// <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)
public static string[] SafeGetFiles(this string path, string searchPattern)
{
try
{
var enumerable = Directory.GetFiles(path, searchPattern);
return enumerable.SafeEnumerate();
return Directory.GetFiles(path, searchPattern);
}
catch
{
@@ -212,12 +201,11 @@ namespace SabreTools.IO.Extensions
/// <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)
public static string[] SafeGetFiles(this string path, string searchPattern, SearchOption searchOption)
{
try
{
var enumerable = Directory.GetFiles(path, searchPattern, searchOption);
return enumerable.SafeEnumerate();
return Directory.GetFiles(path, searchPattern, searchOption);
}
catch
{
@@ -227,12 +215,11 @@ namespace SabreTools.IO.Extensions
/// <inheritdoc cref="Directory.GetFileSystemEntries(string)"/>
/// <remarks>Returns an empty enumerable on any exception</remarks>
public static IEnumerable<string> SafeGetFileSystemEntries(this string path)
public static string[] SafeGetFileSystemEntries(this string path)
{
try
{
var enumerable = Directory.GetFileSystemEntries(path);
return enumerable.SafeEnumerate();
return Directory.GetFileSystemEntries(path);
}
catch
{
@@ -242,12 +229,11 @@ namespace SabreTools.IO.Extensions
/// <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)
public static string[] SafeGetFileSystemEntries(this string path, string searchPattern)
{
try
{
var enumerable = Directory.GetFileSystemEntries(path, searchPattern);
return enumerable.SafeEnumerate();
return Directory.GetFileSystemEntries(path, searchPattern);
}
catch
{
@@ -600,7 +586,7 @@ namespace SabreTools.IO.Extensions
private static EnumerationOptions FromSearchOption(SearchOption searchOption)
{
if ((searchOption != SearchOption.TopDirectoryOnly) && (searchOption != SearchOption.AllDirectories))
throw new ArgumentOutOfRangeException(nameof(searchOption));
throw new System.ArgumentOutOfRangeException(nameof(searchOption));
return searchOption == SearchOption.AllDirectories
? new EnumerationOptions { RecurseSubdirectories = true, MatchType = MatchType.Win32, AttributesToSkip = 0, IgnoreInaccessible = false }

View File

@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
@@ -27,7 +26,7 @@ namespace SabreTools.IO.Extensions
return null;
// Get the first attribute that matches
return attributes.First() as T;
return attributes[0] as T;
}
/// <summary>
@@ -45,7 +44,7 @@ namespace SabreTools.IO.Extensions
return null;
// Get the first attribute that matches
return attributes.First() as T;
return attributes[0] as T;
}
/// <summary>
@@ -115,7 +114,9 @@ namespace SabreTools.IO.Extensions
var nextFields = nextType.GetFields();
foreach (var field in nextFields)
{
if (!fieldsList.Any(f => f.Name == field.Name && f.FieldType == field.FieldType))
// Add fields if they aren't already included
int index = fieldsList.FindIndex(f => f.Name == field.Name && f.FieldType == field.FieldType);
if (index == -1)
fieldsList.Add(field);
}
}

View File

@@ -4,6 +4,32 @@ namespace SabreTools.IO.Extensions
{
public static class StreamExtensions
{
/// <summary>
/// Align the stream position to a byte-size boundary
/// </summary>
/// <param name="input">Input stream to try aligning</param>
/// <param name="alignment">Number of bytes to align on</param>
/// <returns>True if the stream could be aligned, false otherwise</returns>
public static bool AlignToBoundary(this Stream? input, byte alignment)
{
// If the stream is invalid
if (input == null || input.Length == 0 || !input.CanRead)
return false;
// If already at the end of the stream
if (input.Position >= input.Length)
return false;
// Align the stream position
while (input.Position % alignment != 0 && input.Position < input.Length)
{
_ = input.ReadByteValue();
}
// Return if the alignment completed
return input.Position % alignment == 0;
}
/// <summary>
/// Seek to a specific point in the stream, if possible
/// </summary>
@@ -11,10 +37,6 @@ namespace SabreTools.IO.Extensions
/// <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)
{
@@ -32,10 +54,8 @@ namespace SabreTools.IO.Extensions
{
if (offset < 0)
return input.Seek(offset, SeekOrigin.End);
else if (offset >= 0)
else
return input.Seek(offset, SeekOrigin.Begin);
return input.Position;
}
catch
{

View File

@@ -13,7 +13,6 @@ namespace SabreTools.IO.Extensions
/// <summary>
/// Extensions for Streams
/// </summary>
/// TODO: Handle proper negative values for Int24 and Int48
public static class StreamReaderExtensions
{
/// <summary>
@@ -21,7 +20,7 @@ namespace SabreTools.IO.Extensions
/// </summary>
public static byte ReadByteValue(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 1);
byte[] buffer = ReadExactlyToBuffer(stream, 1);
return buffer[0];
}
@@ -29,14 +28,14 @@ namespace SabreTools.IO.Extensions
/// Read a UInt8[] from the stream
/// </summary>
public static byte[] ReadBytes(this Stream stream, int count)
=> ReadToBuffer(stream, count);
=> ReadExactlyToBuffer(stream, count);
/// <summary>
/// Read an Int8 from the stream
/// </summary>
public static sbyte ReadSByte(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 1);
byte[] buffer = ReadExactlyToBuffer(stream, 1);
return (sbyte)buffer[0];
}
@@ -45,17 +44,20 @@ namespace SabreTools.IO.Extensions
/// </summary>
public static char ReadChar(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 1);
byte[] buffer = ReadExactlyToBuffer(stream, 1);
return (char)buffer[0];
}
/// <summary>
/// Read an Int16 from the stream
/// </summary>
/// <remarks>Reads in machine native format</remarks>
public static short ReadInt16(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 2);
return BitConverter.ToInt16(buffer, 0);
if (BitConverter.IsLittleEndian)
return stream.ReadInt16LittleEndian();
else
return stream.ReadInt16BigEndian();
}
/// <summary>
@@ -64,18 +66,32 @@ namespace SabreTools.IO.Extensions
/// <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);
byte[] buffer = ReadExactlyToBuffer(stream, 2);
return (short)(buffer[1]
| (buffer[0] << 8));
}
/// <summary>
/// Read an Int16 from the stream
/// </summary>
/// <remarks>Reads in little-endian format</remarks>
public static short ReadInt16LittleEndian(this Stream stream)
{
byte[] buffer = ReadExactlyToBuffer(stream, 2);
return (short)(buffer[0]
| (buffer[1] << 8));
}
/// <summary>
/// Read a UInt16 from the stream
/// </summary>
/// <remarks>Reads in machine native format</remarks>
public static ushort ReadUInt16(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 2);
return BitConverter.ToUInt16(buffer, 0);
if (BitConverter.IsLittleEndian)
return stream.ReadUInt16LittleEndian();
else
return stream.ReadUInt16BigEndian();
}
/// <summary>
@@ -84,19 +100,52 @@ namespace SabreTools.IO.Extensions
/// <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);
byte[] buffer = ReadExactlyToBuffer(stream, 2);
return (ushort)(buffer[1]
| (buffer[0] << 8));
}
/// <summary>
/// Read a UInt16 from the stream
/// </summary>
/// <remarks>Reads in little-endian format</remarks>
public static ushort ReadUInt16LittleEndian(this Stream stream)
{
byte[] buffer = ReadExactlyToBuffer(stream, 2);
return (ushort)(buffer[0]
| (buffer[1] << 8));
}
/// <summary>
/// Read a WORD (2-byte) from the stream
/// </summary>
/// <remarks>Reads in machine native format</remarks>
public static ushort ReadWORD(this Stream stream)
=> stream.ReadUInt16();
/// <summary>
/// Read a WORD (2-byte) from the stream
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static ushort ReadWORDBigEndian(this Stream stream)
=> stream.ReadUInt16BigEndian();
/// <summary>
/// Read a WORD (2-byte) from the stream
/// </summary>
/// <remarks>Reads in little-endian format</remarks>
public static ushort ReadWORDLittleEndian(this Stream stream)
=> stream.ReadUInt16LittleEndian();
// 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>
/// <remarks>Reads in machine native format</remarks>
public static Half ReadHalf(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 2);
byte[] buffer = ReadExactlyToBuffer(stream, 2);
return BitConverter.ToHalf(buffer, 0);
}
@@ -106,71 +155,94 @@ namespace SabreTools.IO.Extensions
/// <remarks>Reads in big-endian format</remarks>
public static Half ReadHalfBigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 2);
byte[] buffer = ReadExactlyToBuffer(stream, 2);
Array.Reverse(buffer);
return BitConverter.ToHalf(buffer, 0);
}
#endif
/// <summary>
/// Read an Int24 encoded as an Int32
/// Read an Int24 encoded as an Int32 from the stream
/// </summary>
/// <remarks>Reads in machine native format</remarks>
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);
if (BitConverter.IsLittleEndian)
return stream.ReadInt24LittleEndian();
else
return stream.ReadInt24BigEndian();
}
/// <summary>
/// Read an Int24 encoded as an Int32
/// Read an Int24 encoded as an Int32 from the stream
/// </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);
byte[] buffer = ReadExactlyToBuffer(stream, 3);
return (int)(buffer[2]
| (buffer[1] << 8)
| (buffer[0] << 16));
}
/// <summary>
/// Read a UInt24 encoded as a UInt32
/// Read an Int24 encoded as an Int32 from the stream
/// </summary>
/// <remarks>Reads in little-endian format</remarks>
public static int ReadInt24LittleEndian(this Stream stream)
{
byte[] buffer = ReadExactlyToBuffer(stream, 3);
return (int)(buffer[0]
| (buffer[1] << 8)
| (buffer[2] << 16));
}
/// <summary>
/// Read a UInt24 encoded as a UInt32 from the stream
/// </summary>
/// <remarks>Reads in machine native format</remarks>
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);
if (BitConverter.IsLittleEndian)
return stream.ReadUInt24LittleEndian();
else
return stream.ReadUInt24BigEndian();
}
/// <summary>
/// Read a UInt24 encoded as a UInt32
/// Read a UInt24 encoded as a UInt32 from the stream
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static uint ReadUInt24BigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 3);
Array.Reverse(buffer);
byte[] buffer = ReadExactlyToBuffer(stream, 3);
return (uint)(buffer[2]
| (buffer[1] << 8)
| (buffer[0] << 16));
}
byte[] padded = new byte[4];
Array.Copy(buffer, padded, 3);
return BitConverter.ToUInt32(padded, 0);
/// <summary>
/// Read a UInt24 encoded as a UInt32 from the stream
/// </summary>
/// <remarks>Reads in little-endian format</remarks>
public static uint ReadUInt24LittleEndian(this Stream stream)
{
byte[] buffer = ReadExactlyToBuffer(stream, 3);
return (uint)(buffer[0]
| (buffer[1] << 8)
| (buffer[2] << 16));
}
/// <summary>
/// Read an Int32 from the stream
/// </summary>
/// <remarks>Reads in machine native format</remarks>
public static int ReadInt32(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 4);
return BitConverter.ToInt32(buffer, 0);
if (BitConverter.IsLittleEndian)
return stream.ReadInt32LittleEndian();
else
return stream.ReadInt32BigEndian();
}
/// <summary>
@@ -179,18 +251,36 @@ namespace SabreTools.IO.Extensions
/// <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);
byte[] buffer = ReadExactlyToBuffer(stream, 4);
return (int)(buffer[3]
| (buffer[2] << 8)
| (buffer[1] << 16)
| (buffer[0] << 24));
}
/// <summary>
/// Read an Int32 from the stream
/// </summary>
/// <remarks>Reads in little-endian format</remarks>
public static int ReadInt32LittleEndian(this Stream stream)
{
byte[] buffer = ReadExactlyToBuffer(stream, 4);
return (int)(buffer[0]
| (buffer[1] << 8)
| (buffer[2] << 16)
| (buffer[3] << 24));
}
/// <summary>
/// Read a UInt32 from the stream
/// </summary>
/// <remarks>Reads in machine native format</remarks>
public static uint ReadUInt32(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 4);
return BitConverter.ToUInt32(buffer, 0);
if (BitConverter.IsLittleEndian)
return stream.ReadUInt32LittleEndian();
else
return stream.ReadUInt32BigEndian();
}
/// <summary>
@@ -199,17 +289,54 @@ namespace SabreTools.IO.Extensions
/// <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);
byte[] buffer = ReadExactlyToBuffer(stream, 4);
return (uint)(buffer[3]
| (buffer[2] << 8)
| (buffer[1] << 16)
| (buffer[0] << 24));
}
/// <summary>
/// Read a UInt32 from the stream
/// </summary>
/// <remarks>Reads in little-endian format</remarks>
public static uint ReadUInt32LittleEndian(this Stream stream)
{
byte[] buffer = ReadExactlyToBuffer(stream, 4);
return (uint)(buffer[0]
| (buffer[1] << 8)
| (buffer[2] << 16)
| (buffer[3] << 24));
}
/// <summary>
/// Read a DWORD (4-byte) from the stream
/// </summary>
/// <remarks>Reads in machine native format</remarks>
public static uint ReadDWORD(this Stream stream)
=> stream.ReadUInt32();
/// <summary>
/// Read a DWORD (4-byte) from the stream
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static uint ReadDWORDBigEndian(this Stream stream)
=> stream.ReadUInt32BigEndian();
/// <summary>
/// Read a DWORD (4-byte) from the stream
/// </summary>
/// <remarks>Reads in little-endian format</remarks>
public static uint ReadDWORDLittleEndian(this Stream stream)
=> stream.ReadUInt32LittleEndian();
/// <summary>
/// Read a Single from the stream
/// </summary>
/// <remarks>Reads in machine native format</remarks>
public static float ReadSingle(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 4);
byte[] buffer = ReadExactlyToBuffer(stream, 4);
return BitConverter.ToSingle(buffer, 0);
}
@@ -219,70 +346,105 @@ namespace SabreTools.IO.Extensions
/// <remarks>Reads in big-endian format</remarks>
public static float ReadSingleBigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 4);
byte[] buffer = ReadExactlyToBuffer(stream, 4);
Array.Reverse(buffer);
return BitConverter.ToSingle(buffer, 0);
}
/// <summary>
/// Read an Int48 encoded as an Int64
/// Read an Int48 encoded as an Int64 from the stream
/// </summary>
/// <remarks>Reads in machine native format</remarks>
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);
if (BitConverter.IsLittleEndian)
return stream.ReadInt48LittleEndian();
else
return stream.ReadInt48BigEndian();
}
/// <summary>
/// Read an Int48 encoded as an Int64
/// Read an Int48 encoded as an Int64 from the stream
/// </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);
byte[] buffer = ReadExactlyToBuffer(stream, 6);
return ((long)buffer[5] << 0)
| ((long)buffer[4] << 8)
| ((long)buffer[3] << 16)
| ((long)buffer[2] << 24)
| ((long)buffer[1] << 32)
| ((long)buffer[0] << 40);
}
/// <summary>
/// Read a UInt48 encoded as a UInt64
/// Read an Int48 encoded as an Int64 from the stream
/// </summary>
/// <remarks>Reads in little-endian format</remarks>
public static long ReadInt48LittleEndian(this Stream stream)
{
byte[] buffer = ReadExactlyToBuffer(stream, 6);
return ((long)buffer[0] << 0)
| ((long)buffer[1] << 8)
| ((long)buffer[2] << 16)
| ((long)buffer[3] << 24)
| ((long)buffer[4] << 32)
| ((long)buffer[5] << 40);
}
/// <summary>
/// Read a UInt48 encoded as a UInt64 from the stream
/// </summary>
/// <remarks>Reads in machine native format</remarks>
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);
if (BitConverter.IsLittleEndian)
return stream.ReadUInt48LittleEndian();
else
return stream.ReadUInt48BigEndian();
}
/// <summary>
/// Read a UInt48 encoded as a UInt64
/// Read a UInt48 encoded as a UInt64 from the stream
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static ulong ReadUInt48BigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 6);
Array.Reverse(buffer);
byte[] buffer = ReadExactlyToBuffer(stream, 6);
return ((ulong)buffer[5] << 0)
| ((ulong)buffer[4] << 8)
| ((ulong)buffer[3] << 16)
| ((ulong)buffer[2] << 24)
| ((ulong)buffer[1] << 32)
| ((ulong)buffer[0] << 40);
}
byte[] padded = new byte[8];
Array.Copy(buffer, padded, 6);
return BitConverter.ToUInt64(padded, 0);
/// <summary>
/// Read an UInt48 encoded as an UInt64 from the stream
/// </summary>
/// <remarks>Reads in little-endian format</remarks>
public static ulong ReadUInt48LittleEndian(this Stream stream)
{
byte[] buffer = ReadExactlyToBuffer(stream, 6);
return ((ulong)buffer[0] << 0)
| ((ulong)buffer[1] << 8)
| ((ulong)buffer[2] << 16)
| ((ulong)buffer[3] << 24)
| ((ulong)buffer[4] << 32)
| ((ulong)buffer[5] << 40);
}
/// <summary>
/// Read an Int64 from the stream
/// </summary>
/// <remarks>Reads in machine native format</remarks>
public static long ReadInt64(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 8);
return BitConverter.ToInt64(buffer, 0);
if (BitConverter.IsLittleEndian)
return stream.ReadInt64LittleEndian();
else
return stream.ReadInt64BigEndian();
}
/// <summary>
@@ -291,18 +453,44 @@ namespace SabreTools.IO.Extensions
/// <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);
byte[] buffer = ReadExactlyToBuffer(stream, 8);
return ((long)buffer[7] << 0)
| ((long)buffer[6] << 8)
| ((long)buffer[5] << 16)
| ((long)buffer[4] << 24)
| ((long)buffer[3] << 32)
| ((long)buffer[2] << 40)
| ((long)buffer[1] << 48)
| ((long)buffer[0] << 56);
}
/// <summary>
/// Read an Int64 from the stream
/// </summary>
/// <remarks>Reads in little-endian format</remarks>
public static long ReadInt64LittleEndian(this Stream stream)
{
byte[] buffer = ReadExactlyToBuffer(stream, 8);
return ((long)buffer[0] << 0)
| ((long)buffer[1] << 8)
| ((long)buffer[2] << 16)
| ((long)buffer[3] << 24)
| ((long)buffer[4] << 32)
| ((long)buffer[5] << 40)
| ((long)buffer[6] << 48)
| ((long)buffer[7] << 56);
}
/// <summary>
/// Read a UInt64 from the stream
/// </summary>
/// <remarks>Reads in machine native format</remarks>
public static ulong ReadUInt64(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 8);
return BitConverter.ToUInt64(buffer, 0);
if (BitConverter.IsLittleEndian)
return stream.ReadUInt64LittleEndian();
else
return stream.ReadUInt64BigEndian();
}
/// <summary>
@@ -311,17 +499,62 @@ namespace SabreTools.IO.Extensions
/// <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);
byte[] buffer = ReadExactlyToBuffer(stream, 8);
return ((ulong)buffer[7] << 0)
| ((ulong)buffer[6] << 8)
| ((ulong)buffer[5] << 16)
| ((ulong)buffer[4] << 24)
| ((ulong)buffer[3] << 32)
| ((ulong)buffer[2] << 40)
| ((ulong)buffer[1] << 48)
| ((ulong)buffer[0] << 56);
}
/// <summary>
/// Read a UInt64 from the stream
/// </summary>
/// <remarks>Reads in little-endian format</remarks>
public static ulong ReadUInt64LittleEndian(this Stream stream)
{
byte[] buffer = ReadExactlyToBuffer(stream, 8);
return ((ulong)buffer[0] << 0)
| ((ulong)buffer[1] << 8)
| ((ulong)buffer[2] << 16)
| ((ulong)buffer[3] << 24)
| ((ulong)buffer[4] << 32)
| ((ulong)buffer[5] << 40)
| ((ulong)buffer[6] << 48)
| ((ulong)buffer[7] << 56);
}
/// <summary>
/// Read a QWORD (8-byte) from the stream
/// </summary>
/// <remarks>Reads in machine native format</remarks>
public static ulong ReadQWORD(this Stream stream)
=> stream.ReadUInt64();
/// <summary>
/// Read a QWORD (8-byte) from the stream
/// </summary>
/// <remarks>Reads in big-endian format</remarks>
public static ulong ReadQWORDBigEndian(this Stream stream)
=> stream.ReadUInt64BigEndian();
/// <summary>
/// Read a QWORD (8-byte) from the stream
/// </summary>
/// <remarks>Reads in little-endian format</remarks>
public static ulong ReadQWORDLittleEndian(this Stream stream)
=> stream.ReadUInt64LittleEndian();
/// <summary>
/// Read a Double from the stream
/// </summary>
/// <remarks>Reads in machine native format</remarks>
public static double ReadDouble(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 8);
byte[] buffer = ReadExactlyToBuffer(stream, 8);
return BitConverter.ToDouble(buffer, 0);
}
@@ -331,7 +564,7 @@ namespace SabreTools.IO.Extensions
/// <remarks>Reads in big-endian format</remarks>
public static double ReadDoubleBigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 8);
byte[] buffer = ReadExactlyToBuffer(stream, 8);
Array.Reverse(buffer);
return BitConverter.ToDouble(buffer, 0);
}
@@ -339,9 +572,10 @@ namespace SabreTools.IO.Extensions
/// <summary>
/// Read a Decimal from the stream
/// </summary>
/// <remarks>Reads in machine native format</remarks>
public static decimal ReadDecimal(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 16);
byte[] buffer = ReadExactlyToBuffer(stream, 16);
int lo = BitConverter.ToInt32(buffer, 0);
int mid = BitConverter.ToInt32(buffer, 4);
@@ -357,7 +591,7 @@ namespace SabreTools.IO.Extensions
/// <remarks>Reads in big-endian format</remarks>
public static decimal ReadDecimalBigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 16);
byte[] buffer = ReadExactlyToBuffer(stream, 16);
Array.Reverse(buffer);
int lo = BitConverter.ToInt32(buffer, 0);
@@ -371,9 +605,10 @@ namespace SabreTools.IO.Extensions
/// <summary>
/// Read a Guid from the stream
/// </summary>
/// <remarks>Reads in machine native format</remarks>
public static Guid ReadGuid(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 16);
byte[] buffer = ReadExactlyToBuffer(stream, 16);
return new Guid(buffer);
}
@@ -383,7 +618,7 @@ namespace SabreTools.IO.Extensions
/// <remarks>Reads in big-endian format</remarks>
public static Guid ReadGuidBigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 16);
byte[] buffer = ReadExactlyToBuffer(stream, 16);
Array.Reverse(buffer);
return new Guid(buffer);
}
@@ -392,9 +627,10 @@ namespace SabreTools.IO.Extensions
/// <summary>
/// Read an Int128 from the stream
/// </summary>
/// <remarks>Reads in machine native format</remarks>
public static Int128 ReadInt128(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 16);
byte[] buffer = ReadExactlyToBuffer(stream, 16);
return (Int128)new BigInteger(buffer);
}
@@ -404,7 +640,7 @@ namespace SabreTools.IO.Extensions
/// <remarks>Reads in big-endian format</remarks>
public static Int128 ReadInt128BigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 16);
byte[] buffer = ReadExactlyToBuffer(stream, 16);
Array.Reverse(buffer);
return (Int128)new BigInteger(buffer);
}
@@ -412,9 +648,10 @@ namespace SabreTools.IO.Extensions
/// <summary>
/// Read a UInt128 from the stream
/// </summary>
/// <remarks>Reads in machine native format</remarks>
public static UInt128 ReadUInt128(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 16);
byte[] buffer = ReadExactlyToBuffer(stream, 16);
return (UInt128)new BigInteger(buffer);
}
@@ -424,7 +661,7 @@ namespace SabreTools.IO.Extensions
/// <remarks>Reads in big-endian format</remarks>
public static UInt128 ReadUInt128BigEndian(this Stream stream)
{
byte[] buffer = ReadToBuffer(stream, 16);
byte[] buffer = ReadExactlyToBuffer(stream, 16);
Array.Reverse(buffer);
return (UInt128)new BigInteger(buffer);
}
@@ -452,9 +689,10 @@ namespace SabreTools.IO.Extensions
while (stream.Position < stream.Length)
{
byte ch = stream.ReadByteValue();
buffer.Add(ch);
if (ch == '\0')
break;
buffer.Add(ch);
}
return encoding.GetString([.. buffer]);
@@ -505,7 +743,7 @@ namespace SabreTools.IO.Extensions
return null;
byte[] buffer = ReadUntilNull4Byte(stream);
return Encoding.Unicode.GetString(buffer);
return Encoding.UTF32.GetString(buffer);
}
/// <summary>
@@ -540,45 +778,6 @@ namespace SabreTools.IO.Extensions
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>
@@ -636,7 +835,7 @@ namespace SabreTools.IO.Extensions
try
{
int typeSize = Marshal.SizeOf(type);
byte[] buffer = ReadToBuffer(stream, typeSize);
byte[] buffer = ReadExactlyToBuffer(stream, typeSize);
var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
var data = Marshal.PtrToStructure(handle.AddrOfPinnedObject(), type);
@@ -707,7 +906,10 @@ namespace SabreTools.IO.Extensions
else if (fi.FieldType.IsArray)
{
var value = ReadArrayType(stream, fields, instance, fi);
fi.SetValue(instance, Convert.ChangeType(value, fi.FieldType));
if (value.GetType() == fi.FieldType)
fi.SetValue(instance, value);
else
fi.SetValue(instance, Convert.ChangeType(value, fi.FieldType));
}
else
{
@@ -738,7 +940,10 @@ namespace SabreTools.IO.Extensions
for (int i = 0; i < elementCount; i++)
{
var value = ReadType(stream, elementType);
arr.SetValue(value, i);
if (value != null && elementType.IsEnum)
arr.SetValue(Enum.ToObject(elementType, value), i);
else
arr.SetValue(value, i);
}
// Return the built array
@@ -842,11 +1047,11 @@ namespace SabreTools.IO.Extensions
/// <summary>
/// Read a number of bytes from the stream to a buffer
/// </summary>
private static byte[] ReadToBuffer(Stream stream, int length)
private static byte[] ReadExactlyToBuffer(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");
throw new ArgumentOutOfRangeException($"{nameof(length)} must be 0 or a positive value, {length} requested");
// Handle the 0-byte case
if (length == 0)
@@ -856,7 +1061,7 @@ namespace SabreTools.IO.Extensions
byte[] buffer = new byte[length];
int read = stream.Read(buffer, 0, length);
if (read < length)
throw new EndOfStreamException($"Requested to read {nameof(length)} bytes from {nameof(stream)}, {read} returned");
throw new EndOfStreamException($"Requested to read {length} bytes from {nameof(stream)}, {read} returned");
return buffer;
}

View File

@@ -526,7 +526,7 @@ namespace SabreTools.IO.Extensions
byte[] buffer = Encoding.ASCII.GetBytes(value);
// Write the length as a byte
if (!stream.Write((byte)buffer.Length))
if (!stream.Write((byte)value.Length))
return false;
// Write the buffer
@@ -546,35 +546,13 @@ namespace SabreTools.IO.Extensions
byte[] buffer = Encoding.Unicode.GetBytes(value);
// Write the length as a ushort
if (!stream.Write((ushort)buffer.Length))
if (!stream.Write((ushort)value.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>

View File

@@ -15,7 +15,7 @@ namespace SabreTools.IO.Extensions
/// <param name="localName">Name of the element</param>
/// <param name="value">Value to write in the element</param>
/// <param name="throwOnError">Indicates if an error should be thrown on a missing required value</param>
public static void WriteRequiredAttributeString(this XmlTextWriter writer, string localName, string value, bool throwOnError = false)
public static void WriteRequiredAttributeString(this XmlTextWriter writer, string localName, string? value, bool throwOnError = false)
{
// Throw an exception if we are configured to
if (value == null && throwOnError)
@@ -31,7 +31,7 @@ namespace SabreTools.IO.Extensions
/// <param name="localName">Name of the element</param>
/// <param name="value">Value to write in the element</param>
/// <param name="throwOnError">Indicates if an error should be thrown on a missing required value</param>
public static void WriteRequiredElementString(this XmlTextWriter writer, string localName, string value, bool throwOnError = false)
public static void WriteRequiredElementString(this XmlTextWriter writer, string localName, string? value, bool throwOnError = false)
{
// Throw an exception if we are configured to
if (value == null && throwOnError)
@@ -51,7 +51,7 @@ namespace SabreTools.IO.Extensions
/// <param name="writer">XmlTextWriter to write out with</param>
/// <param name="localName">Name of the attribute</param>
/// <param name="value">Value to write in the attribute</param>
public static void WriteOptionalAttributeString(this XmlTextWriter writer, string localName, string value)
public static void WriteOptionalAttributeString(this XmlTextWriter writer, string localName, string? value)
{
if (!string.IsNullOrEmpty(value))
writer.WriteAttributeString(localName, value);
@@ -63,7 +63,7 @@ namespace SabreTools.IO.Extensions
/// <param name="writer">XmlTextWriter to write out with</param>
/// <param name="localName">Name of the element</param>
/// <param name="value">Value to write in the element</param>
public static void WriteOptionalElementString(this XmlTextWriter writer, string localName, string value)
public static void WriteOptionalElementString(this XmlTextWriter writer, string localName, string? value)
{
if (!string.IsNullOrEmpty(value))
writer.WriteElementString(localName, value);

View File

@@ -2,7 +2,6 @@
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using SabreTools.IO.Readers;
using SabreTools.IO.Writers;
@@ -14,13 +13,12 @@ namespace SabreTools.IO
/// </summary>
public class IniFile : IDictionary<string, string?>
{
private Dictionary<string, string?>? _keyValuePairs = [];
private readonly Dictionary<string, string?> _keyValuePairs = [];
public string? this[string? key]
{
get
{
_keyValuePairs ??= [];
key = key?.ToLowerInvariant() ?? string.Empty;
if (_keyValuePairs.ContainsKey(key))
return _keyValuePairs[key];
@@ -29,7 +27,6 @@ namespace SabreTools.IO
}
set
{
_keyValuePairs ??= [];
key = key?.ToLowerInvariant() ?? string.Empty;
_keyValuePairs[key] = value;
}
@@ -47,16 +44,19 @@ namespace SabreTools.IO
/// </summary>
public IniFile(string path)
{
this.Parse(path);
// If we don't have a file, we can't read it
if (!File.Exists(path))
throw new FileNotFoundException(nameof(path));
using var fileStream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
Parse(fileStream);
}
/// <summary>
/// Populate an INI file from stream
/// </summary>
public IniFile(Stream stream)
{
this.Parse(stream);
}
=> Parse(stream);
/// <summary>
/// Add or update a key and value to the INI file
@@ -71,7 +71,7 @@ namespace SabreTools.IO
/// </summary>
public bool Remove(string key)
{
if (_keyValuePairs != null && _keyValuePairs.ContainsKey(key))
if (_keyValuePairs.ContainsKey(key))
{
_keyValuePairs.Remove(key.ToLowerInvariant());
return true;
@@ -81,22 +81,84 @@ namespace SabreTools.IO
}
/// <summary>
/// Read an INI file based on the path
/// Write an INI file to a path
/// </summary>
public bool Parse(string path)
public bool Write(string path)
{
// If we don't have a file, we can't read it
if (!File.Exists(path))
// If we don't have a valid dictionary with values, we can't write out
if (_keyValuePairs.Count == 0)
return false;
using var fileStream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
return Parse(fileStream);
using var fileStream = File.OpenWrite(path);
return Write(fileStream);
}
/// <summary>
/// Write an INI file to a stream
/// </summary>
public bool Write(Stream stream)
{
// If we don't have a valid dictionary with values, we can't write out
if (_keyValuePairs.Count == 0)
return false;
// If the stream is invalid, we can't output to it
if (!stream.CanWrite)
return false;
try
{
using IniWriter writer = new(stream, Encoding.UTF8);
// Order the keys to link sections together
var orderedKeys = new string[_keyValuePairs.Keys.Count];
_keyValuePairs.Keys.CopyTo(orderedKeys, 0);
Array.Sort(orderedKeys);
string section = string.Empty;
for (int i = 0; i < orderedKeys.Length; i++)
{
// Retrive the key and value
string key = orderedKeys[i];
string? value = _keyValuePairs[key];
// We assume '.' is a section name separator
if (key.Contains("."))
{
// Split the key by '.'
string[] data = key.Split('.');
// If the key contains an '.', we need to put them back in
string newSection = data[0].Trim();
string[] keyArr = new string[data.Length - 1];
Array.Copy(data, 1, keyArr, 0, keyArr.Length);
key = string.Join(".", keyArr).Trim();
// If we have a new section, write it out
if (!string.Equals(newSection, section, StringComparison.OrdinalIgnoreCase))
{
writer.WriteSection(newSection);
section = newSection;
}
}
// Now write out the key and value in a standardized way
writer.WriteKeyValuePair(key, value);
}
}
catch
{
// We don't care what the error was, just catch and return
return false;
}
return true;
}
/// <summary>
/// Read an INI file from a stream
/// </summary>
public bool Parse(Stream? stream)
private bool Parse(Stream? stream)
{
// If the stream is invalid or unreadable, we can't process it
if (stream == null || !stream.CanRead || stream.Position >= stream.Length - 1)
@@ -148,82 +210,11 @@ namespace SabreTools.IO
return true;
}
/// <summary>
/// Write an INI file to a path
/// </summary>
public bool Write(string path)
{
// If we don't have a valid dictionary with values, we can't write out
if (_keyValuePairs == null || _keyValuePairs.Count == 0)
return false;
using var fileStream = File.OpenWrite(path);
return Write(fileStream);
}
/// <summary>
/// Write an INI file to a stream
/// </summary>
public bool Write(Stream stream)
{
// If we don't have a valid dictionary with values, we can't write out
if (_keyValuePairs == null || _keyValuePairs.Count == 0)
return false;
// If the stream is invalid or unwritable, we can't output to it
if (stream == null || !stream.CanWrite || stream.Position >= stream.Length - 1)
return false;
try
{
using IniWriter writer = new(stream, Encoding.UTF8);
// Order the dictionary by keys to link sections together
var orderedKeyValuePairs = _keyValuePairs.OrderBy(kvp => kvp.Key);
string section = string.Empty;
foreach (var keyValuePair in orderedKeyValuePairs)
{
// Extract the key and value
string key = keyValuePair.Key;
string? value = keyValuePair.Value;
// We assume '.' is a section name separator
if (key.Contains("."))
{
// Split the key by '.'
string[] data = keyValuePair.Key.Split('.');
// If the key contains an '.', we need to put them back in
string newSection = data[0].Trim();
key = string.Join(".", data.Skip(1).ToArray()).Trim();
// If we have a new section, write it out
if (!string.Equals(newSection, section, StringComparison.OrdinalIgnoreCase))
{
writer.WriteSection(newSection);
section = newSection;
}
}
// Now write out the key and value in a standardized way
writer.WriteKeyValuePair(key, value);
}
}
catch
{
// We don't care what the error was, just catch and return
return false;
}
return true;
}
#region IDictionary Impelementations
public ICollection<string> Keys => _keyValuePairs?.Keys?.ToArray() ?? [];
public ICollection<string> Keys => _keyValuePairs.Keys;
public ICollection<string?> Values => _keyValuePairs?.Values?.ToArray() ?? [];
public ICollection<string?> Values => _keyValuePairs.Values;
public int Count => (_keyValuePairs as ICollection<KeyValuePair<string, string>>)?.Count ?? 0;

View File

@@ -0,0 +1,47 @@
namespace SabreTools.IO.Logging
{
public static class Converters
{
#region String to Enum
/// <summary>
/// Get the LogLevel value for an input string, if possible
/// </summary>
/// <param name="value">String value to parse/param>
/// <returns></returns>
public static LogLevel AsLogLevel(this string? value)
{
return value?.ToLowerInvariant() switch
{
"verbose" => LogLevel.VERBOSE,
"user" => LogLevel.USER,
"warning" => LogLevel.WARNING,
"error" => LogLevel.ERROR,
_ => LogLevel.VERBOSE,
};
}
#endregion
#region Enum to String
/// <summary>
/// Get string value from input LogLevel
/// </summary>
/// <param name="value">LogLevel to get value from</param>
/// <returns>String corresponding to the LogLevel</returns>
public static string? FromLogLevel(this LogLevel value)
{
return value switch
{
LogLevel.VERBOSE => "VERBOSE",
LogLevel.USER => "USER",
LogLevel.WARNING => "WARNING",
LogLevel.ERROR => "ERROR",
_ => null,
};
}
#endregion
}
}

View File

@@ -0,0 +1,13 @@
namespace SabreTools.IO.Logging
{
/// <summary>
/// Severity of the logging statement
/// </summary>
public enum LogLevel
{
VERBOSE = 0,
USER,
WARNING,
ERROR,
}
}

View File

@@ -0,0 +1,61 @@
using System;
namespace SabreTools.IO.Logging
{
/// <summary>
/// Stopwatch class for keeping track of duration in the code
/// </summary>
public class InternalStopwatch
{
private string _subject;
private DateTime _startTime;
private readonly Logger _logger;
/// <summary>
/// Constructor that initalizes the stopwatch
/// </summary>
public InternalStopwatch()
{
_subject = string.Empty;
_logger = new Logger(this);
}
/// <summary>
/// Constructor that initalizes the stopwatch with a subject and starts immediately
/// </summary>
/// <param name="subject">Subject of the stopwatch</param>
public InternalStopwatch(string subject)
{
_subject = subject;
_logger = new Logger(this);
Start();
}
/// <summary>
/// Start the stopwatch and display subject text
/// </summary>
public void Start()
{
_startTime = DateTime.Now;
_logger.User($"{_subject}...");
}
/// <summary>
/// Start the stopwatch and display subject text
/// </summary>
/// <param name="subject">Text to show on stopwatch start</param>
public void Start(string subject)
{
_subject = subject;
Start();
}
/// <summary>
/// End the stopwatch and display subject text
/// </summary>
public void Stop()
{
_logger.User($"{_subject} completed in {DateTime.Now.Subtract(_startTime):G}");
}
}
}

View File

@@ -0,0 +1,79 @@
using System;
namespace SabreTools.IO.Logging
{
/// <summary>
/// Generic delegate type for log events
/// </summary>
public delegate void LogEventHandler(object? sender, LogEventArgs args);
/// <summary>
/// Logging specific event arguments
/// </summary>
public class LogEventArgs : EventArgs
{
/// <summary>
/// LogLevel for the event
/// </summary>
public readonly LogLevel LogLevel;
/// <summary>
/// Log statement to be printed
/// </summary>
public readonly string? Statement = null;
/// <summary>
/// Exception to be passed along to the event handler
/// </summary>
public readonly Exception? Exception = null;
/// <summary>
/// Total count for progress log events
/// </summary>
public readonly long? TotalCount = null;
/// <summary>
/// Current count for progress log events
/// </summary>
public readonly long? CurrentCount = null;
/// <summary>
/// Statement constructor
/// </summary>
public LogEventArgs(LogLevel logLevel, string statement)
{
LogLevel = logLevel;
Statement = statement;
}
/// <summary>
/// Statement constructor
/// </summary>
public LogEventArgs(LogLevel logLevel, Exception exception)
{
LogLevel = logLevel;
Exception = exception;
}
/// <summary>
/// Statement and exception constructor
/// </summary>
public LogEventArgs(LogLevel logLevel, string statement, Exception exception)
{
LogLevel = logLevel;
Statement = statement;
Exception = exception;
}
/// <summary>
/// Progress constructor
/// </summary>
public LogEventArgs(long total, long current, LogLevel logLevel, string? statement = null)
{
LogLevel = logLevel;
Statement = statement;
TotalCount = total;
CurrentCount = current;
}
}
}

View File

@@ -0,0 +1,180 @@
using System;
namespace SabreTools.IO.Logging
{
/// <summary>
/// Per-class logging
/// </summary>
public class Logger
{
/// <summary>
/// Instance associated with this logger
/// </summary>
/// TODO: Derive class name for this object, if possible
private readonly object? _instance;
/// <summary>
/// Constructor
/// </summary>
public Logger(object? instance = null)
{
_instance = instance;
}
#region Log Event Triggers
#region Verbose
/// <summary>
/// Write the given string as a verbose message to the log output
/// </summary>
/// <param name="output">String to be written log</param>
/// <returns>True if the output could be written, false otherwise</returns>
public void Verbose(string output)
=> LoggerImpl.Verbose(_instance, output);
/// <summary>
/// Write the given exception as a verbose message to the log output
/// </summary>
/// <param name="ex">Exception to be written log</param>
/// <returns>True if the output could be written, false otherwise</returns>
public void Verbose(Exception ex)
=> LoggerImpl.Verbose(_instance, ex);
/// <summary>
/// Write the given exception and string as a verbose message to the log output
/// </summary>
/// <param name="ex">Exception to be written log</param>
/// <param name="output">String to be written log</param>
/// <returns>True if the output could be written, false otherwise</returns>
public void Verbose(Exception ex, string output)
=> LoggerImpl.Verbose(_instance, ex, output);
/// <summary>
/// Write the given verbose progress message to the log output
/// </summary>
/// <param name="total">Total count for progress</param>
/// <param name="current">Current count for progres</param>
/// <param name="output">String to be written log</param>
public void Verbose(long total, long current, string? output = null)
=> LoggerImpl.Verbose(_instance, total, current, output);
#endregion
#region User
/// <summary>
/// Write the given string as a user message to the log output
/// </summary>
/// <param name="output">String to be written log</param>
/// <returns>True if the output could be written, false otherwise</returns>
public void User(string output)
=> LoggerImpl.User(_instance, output);
/// <summary>
/// Write the given exception as a user message to the log output
/// </summary>
/// <param name="ex">Exception to be written log</param>
/// <returns>True if the output could be written, false otherwise</returns>
public void User(Exception ex)
=> LoggerImpl.User(_instance, ex);
/// <summary>
/// Write the given exception and string as a user message to the log output
/// </summary>
/// <param name="ex">Exception to be written log</param>
/// <param name="output">String to be written log</param>
/// <returns>True if the output could be written, false otherwise</returns>
public void User(Exception ex, string output)
=> LoggerImpl.User(_instance, ex, output);
/// <summary>
/// Write the given user progress message to the log output
/// </summary>
/// <param name="total">Total count for progress</param>
/// <param name="current">Current count for progres</param>
/// <param name="output">String to be written log</param>
public void User(long total, long current, string? output = null)
=> LoggerImpl.User(_instance, total, current, output);
#endregion
#region Warning
/// <summary>
/// Write the given string as a warning to the log output
/// </summary>
/// <param name="output">String to be written log</param>
/// <returns>True if the output could be written, false otherwise</returns>
public void Warning(string output)
=> LoggerImpl.Warning(_instance, output);
/// <summary>
/// Write the given exception as a warning to the log output
/// </summary>
/// <param name="ex">Exception to be written log</param>
/// <returns>True if the output could be written, false otherwise</returns>
public void Warning(Exception ex)
=> LoggerImpl.Warning(_instance, ex);
/// <summary>
/// Write the given exception and string as a warning to the log output
/// </summary>
/// <param name="ex">Exception to be written log</param>
/// <param name="output">String to be written log</param>
/// <returns>True if the output could be written, false otherwise</returns>
public void Warning(Exception ex, string output)
=> LoggerImpl.Warning(_instance, ex, output);
/// <summary>
/// Write the given warning progress message to the log output
/// </summary>
/// <param name="total">Total count for progress</param>
/// <param name="current">Current count for progres</param>
/// <param name="output">String to be written log</param>
public void Warning(long total, long current, string? output = null)
=> LoggerImpl.Warning(_instance, total, current, output);
#endregion
#region Error
/// <summary>
/// Writes the given string as an error in the log
/// </summary>
/// <param name="output">String to be written log</param>
/// <returns>True if the output could be written, false otherwise</returns>
public void Error(string output)
=> LoggerImpl.Error(_instance, output);
/// <summary>
/// Writes the given exception as an error in the log
/// </summary>
/// <param name="ex">Exception to be written log</param>
/// <returns>True if the output could be written, false otherwise</returns>
public void Error(Exception ex)
=> LoggerImpl.Error(_instance, ex);
/// <summary>
/// Writes the given exception and string as an error in the log
/// </summary>
/// <param name="ex">Exception to be written log</param>
/// <param name="output">String to be written log</param>
/// <returns>True if the output could be written, false otherwise</returns>
public void Error(Exception ex, string output)
=> LoggerImpl.Error(_instance, ex, output);
/// <summary>
/// Write the given error progress message to the log output
/// </summary>
/// <param name="total">Total count for progress</param>
/// <param name="current">Current count for progres</param>
/// <param name="output">String to be written log</param>
public void Error(long total, long current, string? output = null)
=> LoggerImpl.Error(_instance, total, current, output);
#endregion
#endregion
}
}

View File

@@ -0,0 +1,458 @@
using System;
using System.IO;
using System.Text;
using SabreTools.IO.Extensions;
namespace SabreTools.IO.Logging
{
/// <summary>
/// Internal logging implementation
/// </summary>
public static class LoggerImpl
{
#region Fields
/// <summary>
/// Optional output filename for logs
/// </summary>
public static string? Filename { get; private set; } = null;
/// <summary>
/// Determines if we're logging to file or not
/// </summary>
public static bool LogToFile { get { return !string.IsNullOrEmpty(Filename); } }
/// <summary>
/// Optional output log directory
/// </summary>
public static string? LogDirectory { get; private set; } = null;
/// <summary>
/// Determines the lowest log level to output
/// </summary>
public static LogLevel LowestLogLevel { get; set; } = LogLevel.VERBOSE;
/// <summary>
/// Determines whether to prefix log lines with level and datetime
/// </summary>
public static bool AppendPrefix { get; set; } = true;
/// <summary>
/// Determines whether to throw if an exception is logged
/// </summary>
public static bool ThrowOnError { get; set; } = false;
/// <summary>
/// Logging start time for metrics
/// </summary>
public static DateTime StartTime { get; private set; }
/// <summary>
/// Determines if there were errors logged
/// </summary>
public static bool LoggedErrors { get; private set; } = false;
/// <summary>
/// Determines if there were warnings logged
/// </summary>
public static bool LoggedWarnings { get; private set; } = false;
#endregion
#region Private variables
/// <summary>
/// StreamWriter representing the output log file
/// </summary>
private static StreamWriter? _log;
/// <summary>
/// Object lock for multithreaded logging
/// </summary>
private static readonly object _lock = new();
#endregion
#region Control
/// <summary>
/// Generate and set the log filename
/// </summary>
/// <param name="filename">Base filename to use</param>
/// <param name="addDate">True to append a date to the filename, false otherwise</param>
public static void SetFilename(string filename, bool addDate = true)
{
// Get the full log path
string fullPath = Path.GetFullPath(filename);
// Set the log directory
LogDirectory = Path.GetDirectoryName(fullPath);
// Set the
if (addDate)
Filename = $"{Path.GetFileNameWithoutExtension(fullPath)} ({DateTime.Now:yyyy-MM-dd HH-mm-ss}).{fullPath.GetNormalizedExtension()}";
else
Filename = Path.GetFileName(fullPath);
}
/// <summary>
/// Start logging by opening output file (if necessary)
/// </summary>
/// <returns>True if the logging was started correctly, false otherwise</returns>
public static bool Start()
{
// Setup the logging handler to always use the internal log
LogEventHandler += HandleLogEvent;
// Start the logging
StartTime = DateTime.Now;
if (!LogToFile)
return true;
// Setup file output and perform initial log
try
{
if (!string.IsNullOrEmpty(LogDirectory) && !Directory.Exists(LogDirectory))
Directory.CreateDirectory(LogDirectory);
FileStream logfile = File.Create(Path.Combine(LogDirectory ?? string.Empty, Filename ?? string.Empty));
#if NET20 || NET35 || NET40
_log = new StreamWriter(logfile, Encoding.UTF8, 4096)
#else
_log = new StreamWriter(logfile, Encoding.UTF8, 4096, true)
#endif
{
AutoFlush = true
};
_log.WriteLine($"Logging started {StartTime:yyyy-MM-dd HH:mm:ss}");
_log.WriteLine($"Command run: {string.Join(" ", Environment.GetCommandLineArgs())}");
}
catch
{
return false;
}
return true;
}
/// <summary>
/// End logging by closing output file (if necessary)
/// </summary>
/// <param name="suppress">True if all ending output is to be suppressed, false otherwise (default)</param>
/// <returns>True if the logging was ended correctly, false otherwise</returns>
public static bool Close(bool suppress = false)
{
if (!suppress)
{
if (LoggedWarnings)
Console.WriteLine("There were warnings in the last run! Check the log for more details");
if (LoggedErrors)
Console.WriteLine("There were errors in the last run! Check the log for more details");
TimeSpan span = DateTime.Now.Subtract(StartTime);
#if NET20 || NET35
string total = span.ToString();
#else
// Special case for multi-day runs
string total;
if (span >= TimeSpan.FromDays(1))
total = span.ToString(@"d\:hh\:mm\:ss");
else
total = span.ToString(@"hh\:mm\:ss");
#endif
if (!LogToFile)
{
Console.WriteLine($"Total runtime: {total}");
return true;
}
try
{
_log?.WriteLine($"Logging ended {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
_log?.WriteLine($"Total runtime: {total}");
Console.WriteLine($"Total runtime: {total}");
_log?.Close();
}
catch
{
return false;
}
}
else
{
try
{
_log?.Close();
}
catch
{
return false;
}
}
return true;
}
#endregion
#region Event Handling
/// <summary>
/// Handler for log events
/// </summary>
public static event LogEventHandler LogEventHandler = delegate { };
/// <summary>
/// Default log event handling
/// </summary>
public static void HandleLogEvent(object? sender, LogEventArgs args)
{
// Null args means we can't handle it
if (args == null)
return;
// If we have an exception and we're throwing on that
if (ThrowOnError && args.Exception != null)
throw args.Exception;
// If we have a warning or error, set the flags accordingly
if (args.LogLevel == LogLevel.WARNING)
LoggedWarnings = true;
if (args.LogLevel == LogLevel.ERROR)
LoggedErrors = true;
// Setup the statement based on the inputs
string logLine;
if (args.Exception != null)
{
logLine = $"{(args.Statement != null ? args.Statement + ": " : string.Empty)}{args.Exception}";
}
else if (args.TotalCount != null && args.CurrentCount != null)
{
double percentage = ((double)args.CurrentCount.Value / args.TotalCount.Value) * 100;
logLine = $"{percentage:N2}%{(args.Statement != null ? ": " + args.Statement : string.Empty)}";
}
else
{
logLine = args.Statement ?? string.Empty;
}
// Then write to the log
Log(logLine, args.LogLevel);
}
/// <summary>
/// Write the given string to the log output
/// </summary>
/// <param name="output">String to be written log</param>
/// <param name="loglevel">Severity of the information being logged</param>
private static void Log(string output, LogLevel loglevel)
{
// If the log level is less than the filter level, we skip it but claim we didn't
if (loglevel < LowestLogLevel)
return;
// USER and ERROR writes to console
if (loglevel == LogLevel.USER || loglevel == LogLevel.ERROR)
Console.WriteLine((loglevel == LogLevel.ERROR && AppendPrefix ? loglevel.FromLogLevel() + " " : string.Empty) + output);
// If we're writing to file, use the existing stream
if (LogToFile)
{
try
{
lock (_lock)
{
_log?.WriteLine((AppendPrefix ? $"{loglevel.FromLogLevel()} - {DateTime.Now} - " : string.Empty) + output);
}
}
catch (Exception ex) when (ThrowOnError)
{
Console.WriteLine(ex);
Console.WriteLine("Could not write to log file!");
return;
}
}
return;
}
#endregion
#region Log Event Triggers
#region Verbose
/// <summary>
/// Write the given string as a verbose message to the log output
/// </summary>
/// <param name="instance">Instance object that's the source of logging</param>
/// <param name="output">String to be written log</param>
/// <returns>True if the output could be written, false otherwise</returns>
public static void Verbose(object? instance, string output)
=> LogEventHandler(instance, new LogEventArgs(LogLevel.VERBOSE, output));
/// <summary>
/// Write the given exception as a verbose message to the log output
/// </summary>
/// <param name="instance">Instance object that's the source of logging</param>
/// <param name="ex">Exception to be written log</param>
/// <returns>True if the output could be written, false otherwise</returns>
public static void Verbose(object? instance, Exception ex)
=> LogEventHandler(instance, new LogEventArgs(LogLevel.VERBOSE, ex));
/// <summary>
/// Write the given exception and string as a verbose message to the log output
/// </summary>
/// <param name="instance">Instance object that's the source of logging</param>
/// <param name="ex">Exception to be written log</param>
/// <param name="output">String to be written log</param>
/// <returns>True if the output could be written, false otherwise</returns>
public static void Verbose(object? instance, Exception ex, string output)
=> LogEventHandler(instance, new LogEventArgs(LogLevel.VERBOSE, output, ex));
/// <summary>
/// Write the given verbose progress message to the log output
/// </summary>
/// <param name="instance">Instance object that's the source of logging</param>
/// <param name="total">Total count for progress</param>
/// <param name="current">Current count for progres</param>
/// <param name="output">String to be written log</param>
public static void Verbose(object? instance, long total, long current, string? output = null)
=> LogEventHandler(instance, new LogEventArgs(total, current, LogLevel.VERBOSE, output));
#endregion
#region User
/// <summary>
/// Write the given string as a user message to the log output
/// </summary>
/// <param name="instance">Instance object that's the source of logging</param>
/// <param name="output">String to be written log</param>
/// <returns>True if the output could be written, false otherwise</returns>
public static void User(object? instance, string output)
=> LogEventHandler(instance, new LogEventArgs(LogLevel.USER, output));
/// <summary>
/// Write the given exception as a user message to the log output
/// </summary>
/// <param name="instance">Instance object that's the source of logging</param>
/// <param name="ex">Exception to be written log</param>
/// <returns>True if the output could be written, false otherwise</returns>
public static void User(object? instance, Exception ex)
=> LogEventHandler(instance, new LogEventArgs(LogLevel.USER, ex));
/// <summary>
/// Write the given exception and string as a user message to the log output
/// </summary>
/// <param name="instance">Instance object that's the source of logging</param>
/// <param name="ex">Exception to be written log</param>
/// <param name="output">String to be written log</param>
/// <returns>True if the output could be written, false otherwise</returns>
public static void User(object? instance, Exception ex, string output)
=> LogEventHandler(instance, new LogEventArgs(LogLevel.USER, output, ex));
/// <summary>
/// Write the given user progress message to the log output
/// </summary>
/// <param name="instance">Instance object that's the source of logging</param>
/// <param name="total">Total count for progress</param>
/// <param name="current">Current count for progres</param>
/// <param name="output">String to be written log</param>
public static void User(object? instance, long total, long current, string? output = null)
=> LogEventHandler(instance, new LogEventArgs(total, current, LogLevel.USER, output));
#endregion
#region Warning
/// <summary>
/// Write the given string as a warning to the log output
/// </summary>
/// <param name="instance">Instance object that's the source of logging</param>
/// <param name="output">String to be written log</param>
/// <returns>True if the output could be written, false otherwise</returns>
public static void Warning(object? instance, string output)
=> LogEventHandler(instance, new LogEventArgs(LogLevel.WARNING, output));
/// <summary>
/// Write the given exception as a warning to the log output
/// </summary>
/// <param name="instance">Instance object that's the source of logging</param>
/// <param name="ex">Exception to be written log</param>
/// <returns>True if the output could be written, false otherwise</returns>
public static void Warning(object? instance, Exception ex)
=> LogEventHandler(instance, new LogEventArgs(LogLevel.WARNING, ex));
//// <summary>
/// Write the given exception and string as a warning to the log output
/// </summary>
/// <param name="instance">Instance object that's the source of logging</param>
/// <param name="ex">Exception to be written log</param>
/// <param name="output">String to be written log</param>
/// <returns>True if the output could be written, false otherwise</returns>
public static void Warning(object? instance, Exception ex, string output)
=> LogEventHandler(instance, new LogEventArgs(LogLevel.WARNING, output, ex));
/// <summary>
/// Write the given warning progress message to the log output
/// </summary>
/// <param name="instance">Instance object that's the source of logging</param>
/// <param name="total">Total count for progress</param>
/// <param name="current">Current count for progres</param>
/// <param name="output">String to be written log</param>
public static void Warning(object? instance, long total, long current, string? output = null)
=> LogEventHandler(instance, new LogEventArgs(total, current, LogLevel.WARNING, output));
#endregion
#region Error
/// <summary>
/// Writes the given string as an error in the log
/// </summary>
/// <param name="instance">Instance object that's the source of logging</param>
/// <param name="output">String to be written log</param>
/// <returns>True if the output could be written, false otherwise</returns>
public static void Error(object? instance, string output)
=> LogEventHandler(instance, new LogEventArgs(LogLevel.ERROR, output));
/// <summary>
/// Writes the given exception as an error in the log
/// </summary>
/// <param name="instance">Instance object that's the source of logging</param>
/// <param name="ex">Exception to be written log</param>
/// <returns>True if the output could be written, false otherwise</returns>
public static void Error(object? instance, Exception ex)
=> LogEventHandler(instance, new LogEventArgs(LogLevel.ERROR, ex));
/// <summary>
/// Writes the given exception and string as an error in the log
/// </summary>
/// <param name="instance">Instance object that's the source of logging</param>
/// <param name="ex">Exception to be written log</param>
/// <param name="output">String to be written log</param>
/// <returns>True if the output could be written, false otherwise</returns>
public static void Error(object? instance, Exception ex, string output)
=> LogEventHandler(instance, new LogEventArgs(LogLevel.ERROR, output, ex));
/// <summary>
/// Write the given error progress message to the log output
/// </summary>
/// <param name="instance">Instance object that's the source of logging</param>
/// <param name="total">Total count for progress</param>
/// <param name="current">Current count for progres</param>
/// <param name="output">String to be written log</param>
public static void Error(object? instance, long total, long current, string? output = null)
=> LogEventHandler(instance, new LogEventArgs(total, current, LogLevel.ERROR, output));
#endregion
#endregion
}
}

View File

@@ -11,12 +11,12 @@ namespace SabreTools.IO
/// <summary>
/// Current full path represented
/// </summary>
public string CurrentPath { get; private set; }
public string CurrentPath { get; }
/// <summary>
/// Possible parent path represented (may be null or empty)
/// </summary>
public string? ParentPath { get; private set; }
public string? ParentPath { get; }
public ParentablePath(string currentPath, string? parentPath = null)
{
@@ -32,7 +32,7 @@ namespace SabreTools.IO
public string? GetNormalizedFileName(bool sanitize)
{
// If the current path is empty, we can't do anything
if (string.IsNullOrEmpty(CurrentPath))
if (CurrentPath.Length == 0)
return null;
// Assume the current path is the filename
@@ -58,7 +58,7 @@ namespace SabreTools.IO
public string? GetOutputPath(string? outDir, bool inplace)
{
// If the current path is empty
if (string.IsNullOrEmpty(CurrentPath))
if (CurrentPath.Length == 0)
return null;
// If we have an inplace output

View File

@@ -1,7 +1,7 @@
using System.Collections.Generic;
using System.IO;
using SabreTools.IO.Extensions;
using SabreTools.Matching;
using SabreTools.Matching.Compare;
namespace SabreTools.IO
{
@@ -14,9 +14,9 @@ namespace SabreTools.IO
/// Retrieve a list of just directories from inputs
/// </summary>
/// <param name="inputs">List of strings representing directories and files</param>
/// <param name="appendparent">True if the parent name should be included in the ParentablePath, false otherwise (default)</param>
/// <param name="appendParent">True if the parent name should be included in the ParentablePath, false otherwise (default)</param>
/// <returns>List of strings representing just directories from the inputs</returns>
public static List<ParentablePath> GetDirectoriesOnly(List<string> inputs, bool appendparent = false)
public static List<ParentablePath> GetDirectoriesOnly(List<string> inputs, bool appendParent = false)
{
var outputs = new List<ParentablePath>();
for (int i = 0; i < inputs.Count; i++)
@@ -42,7 +42,7 @@ namespace SabreTools.IO
List<string> directories = GetDirectoriesOrdered(input, pattern);
foreach (string dir in directories)
{
outputs.Add(new ParentablePath(Path.GetFullPath(dir), appendparent ? parentPath : string.Empty));
outputs.Add(new ParentablePath(Path.GetFullPath(dir), appendParent ? parentPath : string.Empty));
}
}
}
@@ -89,9 +89,9 @@ namespace SabreTools.IO
/// Retrieve a list of just files from inputs
/// </summary>
/// <param name="inputs">List of strings representing directories and files</param>
/// <param name="appendparent">True if the parent name should be be included in the ParentablePath, false otherwise (default)</param>
/// <param name="appendParent">True if the parent name should be be included in the ParentablePath, false otherwise (default)</param>
/// <returns>List of strings representing just files from the inputs</returns>
public static List<ParentablePath> GetFilesOnly(List<string> inputs, bool appendparent = false)
public static List<ParentablePath> GetFilesOnly(List<string> inputs, bool appendParent = false)
{
var outputs = new List<ParentablePath>();
for (int i = 0; i < inputs.Count; i++)
@@ -117,12 +117,12 @@ namespace SabreTools.IO
List<string> files = GetFilesOrdered(input, pattern);
foreach (string file in files)
{
outputs.Add(new ParentablePath(Path.GetFullPath(file), appendparent ? parentPath : string.Empty));
outputs.Add(new ParentablePath(Path.GetFullPath(file), appendParent ? parentPath : string.Empty));
}
}
else if (File.Exists(input))
{
outputs.Add(new ParentablePath(Path.GetFullPath(input), appendparent ? parentPath : string.Empty));
outputs.Add(new ParentablePath(Path.GetFullPath(input), appendParent ? parentPath : string.Empty));
}
}

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
@@ -295,13 +294,10 @@ namespace SabreTools.IO.Readers
s = s.Trim();
// Now we get each string, divided up as cleanly as possible
string[] matches = Regex
.Matches(s, InternalPatternAttributesCMP)
.Cast<Match>()
.Select(m => m.Groups[0].Value)
.ToArray();
return matches;
var matchList = Regex.Matches(s, InternalPatternAttributesCMP);
var matchArr = new Match[matchList.Count];
matchList.CopyTo(matchArr, 0);
return Array.ConvertAll(matchArr, m => m.Groups[0].Value);
}
/// <summary>

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace SabreTools.IO.Readers
@@ -118,7 +117,9 @@ namespace SabreTools.IO.Readers
// If the value field contains an '=', we need to put them back in
string key = data[0].Trim();
string value = string.Join("=", data.Skip(1).ToArray()).Trim();
var valueArr = new string[data.Length - 1];
Array.Copy(data, 1, valueArr, 0, valueArr.Length);
string value = string.Join("=", valueArr).Trim();
KeyValuePair = new KeyValuePair<string, string>(key, value);
RowType = IniRowType.KeyValue;

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
@@ -124,7 +123,7 @@ 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>();
foreach (Match? match in lineSplitRegex.Matches(fullLine).Cast<Match?>())
foreach (Match? match in lineSplitRegex.Matches(fullLine))
{
string? curr = match?.Value;
if (curr == null)
@@ -143,7 +142,9 @@ namespace SabreTools.IO.Readers
// Otherwise, just split on the delimiter
else
{
Line = fullLine.Split(Separator).Select(f => f.Trim()).ToList();
var lineArr = fullLine.Split(Separator);
lineArr = Array.ConvertAll(lineArr, f => f.Trim());
Line = [.. lineArr];
}
// If we don't have a header yet and are expecting one, read this as the header

View File

@@ -1,38 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Assembly Properties -->
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0</TargetFrameworks>
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>1.4.10</Version>
<WarningsNotAsErrors>CS0618</WarningsNotAsErrors>
<PropertyGroup>
<!-- Assembly Properties -->
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0</TargetFrameworks>
<IncludeSymbols>true</IncludeSymbols>
<LangVersion>latest</LangVersion>
<NoWarn>CS0618</NoWarn>
<Nullable>enable</Nullable>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>1.6.3</Version>
<!-- Package Properties -->
<Authors>Matt Nadareski</Authors>
<Description>Common IO utilities by other SabreTools projects</Description>
<Copyright>Copyright (c) Matt Nadareski 2019-2024</Copyright>
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/SabreTools/SabreTools.IO</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>io reading writing ini csv ssv tsv</PackageTags>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup>
<!-- Package Properties -->
<Authors>Matt Nadareski</Authors>
<Description>Common IO utilities by other SabreTools projects</Description>
<Copyright>Copyright (c) Matt Nadareski 2019-2025</Copyright>
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/SabreTools/SabreTools.IO</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>io reading writing ini csv ssv tsv</PackageTags>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup>
<ItemGroup>
<None Include="../README.md" Pack="true" PackagePath=""/>
</ItemGroup>
<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" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="SabreTools.Matching" Version="1.5.2" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="SabreTools.Matching" Version="1.3.1" />
</ItemGroup>
</Project>
</Project>

View File

@@ -88,10 +88,11 @@ namespace SabreTools.IO.Streams
}
/// <summary>
/// Read a multiple bits in LSB, if possible
/// Read a multiple bits MSB-first, if possible
/// </summary>
/// <returns>The next bits encoded in a UInt32, null on error or end of stream</returns>
public uint? ReadBitsLSB(int bits)
/// <remarks>[76543210] order within a byte, appended to output [76543210]</remarks>
public uint? ReadBitsBE(int bits)
{
uint value = 0;
for (int i = 0; i < bits; i++)
@@ -101,18 +102,19 @@ namespace SabreTools.IO.Streams
if (bitValue == null)
return null;
// Add the bit shifted by the current index
value += (uint)(bitValue.Value << i);
// Append the bit shifted by the current index
value |= (uint)(bitValue.Value << i);
}
return value;
}
/// <summary>
/// Read a multiple bits in MSB, if possible
/// Read a multiple bits in LSB-first, if possible
/// </summary>
/// <returns>The next bits encoded in a UInt32, null on error or end of stream</returns>
public uint? ReadBitsMSB(int bits)
/// <remarks>[76543210] order within a byte, appended to output [01234567]</remarks>
public uint? ReadBitsLE(int bits)
{
uint value = 0;
for (int i = 0; i < bits; i++)
@@ -122,8 +124,8 @@ namespace SabreTools.IO.Streams
if (bitValue == null)
return null;
// Add the bit shifted by the current index
value = (value << 1) + bitValue.Value;
// Append the bit shifted by the current index
value = (value << 1) | bitValue.Value;
}
return value;

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace SabreTools.IO.Streams
{
@@ -110,7 +109,7 @@ namespace SabreTools.IO.Streams
/// </summary>
public ReadOnlyCompositeStream(IEnumerable<Stream> streams)
{
_streams = streams.ToList();
_streams = new List<Stream>(streams);
_length = 0;
_position = 0;
@@ -148,7 +147,7 @@ namespace SabreTools.IO.Streams
public override int Read(byte[] buffer, int offset, int count)
{
// Determine which stream we start reading from
(int streamIndex, long streamOffset) = DetermineStreamIndex(_position);
int streamIndex = DetermineStreamIndex(_position, out long streamOffset);
if (streamIndex == -1)
return 0;
@@ -227,12 +226,16 @@ namespace SabreTools.IO.Streams
/// <summary>
/// Determine the index of the stream that contains a particular offset
/// </summary>
/// <returns>Index of the stream containing the offset and the real offset in the stream, (-1, -1) on error</returns>
private (int index, long realOffset) DetermineStreamIndex(long offset)
/// <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)
return (-1, -1);
{
realOffset = -1;
return -1;
}
// Seek through until we hit the correct offset
long currentLength = 0;
@@ -241,13 +244,14 @@ namespace SabreTools.IO.Streams
currentLength += _streams[i].Length;
if (currentLength > offset)
{
long realOffset = offset - (currentLength - _streams[i].Length);
return (i, realOffset);
realOffset = offset - (currentLength - _streams[i].Length);
return i;
}
}
// Should never happen
return (-1, -1);
realOffset = -1;
return -1;
}
/// <summary>

View File

@@ -118,7 +118,11 @@ namespace SabreTools.IO.Writers
/// </summary>
public ClrMameProWriter(Stream stream, Encoding encoding)
{
#if NET20 || NET35 || NET40
sw = new StreamWriter(stream, encoding);
#else
sw = new StreamWriter(stream, encoding, 1024, leaveOpen: true);
#endif
Quotes = true;
// Element stack
@@ -167,42 +171,6 @@ namespace SabreTools.IO.Writers
InternalWriteEndElement(true);
}
/// <summary>
/// Write a complete element with content
/// </summary>
public void WriteElementString(string name, string? value)
{
WriteStartElement(name);
WriteString(value);
WriteEndElement();
}
/// <summary>
/// Ensure writing writing null values as empty strings
/// </summary>
/// <param name="name">Name of the element</param>
/// <param name="value">Value to write in the element</param>
/// <param name="throwOnError">Indicates if an error should be thrown on a missing required value</param>
public void WriteRequiredElementString(string name, string? value, bool throwOnError = false)
{
// Throw an exception if we are configured to
if (value == null && throwOnError)
throw new ArgumentNullException(nameof(value));
WriteElementString(name, value ?? string.Empty);
}
/// <summary>
/// Write an element, if the value is not null or empty
/// </summary>
/// <param name="name">Name of the element</param>
/// <param name="value">Value to write in the element</param>
public void WriteOptionalElementString(string name, string? value)
{
if (!string.IsNullOrEmpty(value))
WriteElementString(name, value);
}
/// <summary>
/// Write the start of an attribute node
/// </summary>

View File

@@ -24,7 +24,11 @@ namespace SabreTools.IO.Writers
/// </summary>
public IniWriter(Stream stream, Encoding encoding)
{
#if NET20 || NET35 || NET40
sw = new StreamWriter(stream, encoding);
#else
sw = new StreamWriter(stream, encoding, 1024, leaveOpen: true);
#endif
}
/// <summary>

View File

@@ -54,7 +54,11 @@ namespace SabreTools.IO.Writers
/// </summary>
public SeparatedValueWriter(Stream stream, Encoding encoding)
{
#if NET20 || NET35 || NET40
sw = new StreamWriter(stream, encoding);
#else
sw = new StreamWriter(stream, encoding, 1024, leaveOpen: true);
#endif
}
/// <summary>
@@ -141,14 +145,6 @@ namespace SabreTools.IO.Writers
sw.Write(value);
}
/// <summary>
/// Write a newline
/// </summary>
public void WriteLine()
{
sw.WriteLine();
}
/// <summary>
/// Flush the underlying writer
/// </summary>

View File

@@ -1,14 +1,14 @@
#! /bin/bash
# This batch file assumes the following:
# - .NET 8.0 (or newer) SDK is installed and in PATH
# - .NET 9.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
while getopts "b" OPTION
do
case $OPTION in
b)

View File

@@ -1,5 +1,5 @@
# This batch file assumes the following:
# - .NET 8.0 (or newer) SDK is installed and in PATH
# - .NET 9.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.