mirror of
https://github.com/SabreTools/SabreTools.IO.git
synced 2026-02-08 13:49:55 +00:00
Compare commits
95 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16b238539b | ||
|
|
68f49eeb48 | ||
|
|
c5ecd41a8f | ||
|
|
9ab9dd4ff8 | ||
|
|
867c8d11da | ||
|
|
65dbb7a31a | ||
|
|
1eaf7954fe | ||
|
|
a602a07514 | ||
|
|
466b0e90e7 | ||
|
|
abdf50c9e0 | ||
|
|
12341ba6aa | ||
|
|
70b78f861c | ||
|
|
5b306ce9e8 | ||
|
|
2b6fc200e2 | ||
|
|
7c63f44c75 | ||
|
|
edd3e6eef2 | ||
|
|
244b7411d4 | ||
|
|
fb60f1fed5 | ||
|
|
8f06bf5859 | ||
|
|
2c5d7ad56b | ||
|
|
46996c10e5 | ||
|
|
7491821679 | ||
|
|
8fe404e732 | ||
|
|
793168fbe5 | ||
|
|
67b6118cc1 | ||
|
|
b12d122721 | ||
|
|
20f1679557 | ||
|
|
7ccedbeac5 | ||
|
|
72910cc1c0 | ||
|
|
8f4ea0da16 | ||
|
|
eb4975b261 | ||
|
|
995c19d903 | ||
|
|
f0fe9af467 | ||
|
|
d33b47d15a | ||
|
|
e4a0a08d13 | ||
|
|
24a69166f0 | ||
|
|
6c13cdcf31 | ||
|
|
4138c271e5 | ||
|
|
f80d31597b | ||
|
|
5054aeb077 | ||
|
|
d2e9b8d6e5 | ||
|
|
2c29aee834 | ||
|
|
576bafcb87 | ||
|
|
2b310ac528 | ||
|
|
4f6b6d7b59 | ||
|
|
17e55ee233 | ||
|
|
8b78906d1d | ||
|
|
cff2dcf4cc | ||
|
|
a56942cb73 | ||
|
|
5ed661b77c | ||
|
|
a0a0cd0386 | ||
|
|
bcc0fca4ad | ||
|
|
843e821e5f | ||
|
|
630b01283e | ||
|
|
22abb96013 | ||
|
|
314de12661 | ||
|
|
a0b24031b5 | ||
|
|
b4628485c3 | ||
|
|
4610ddc9b9 | ||
|
|
e392ddc8d7 | ||
|
|
1908d1b32e | ||
|
|
9d73195f86 | ||
|
|
335a486f17 | ||
|
|
d3e41ac187 | ||
|
|
8ddd9f3f78 | ||
|
|
54ad538c08 | ||
|
|
e6bc9ab3e3 | ||
|
|
94934b00a9 | ||
|
|
e49f56fccc | ||
|
|
79c64ddfa8 | ||
|
|
b22384d5f3 | ||
|
|
955c1b5641 | ||
|
|
535f9f928d | ||
|
|
f0cb15c2e4 | ||
|
|
ec99304c51 | ||
|
|
aefc931055 | ||
|
|
e7fe342379 | ||
|
|
f372999b1b | ||
|
|
2679975945 | ||
|
|
54dd7f2f8f | ||
|
|
aee5891c50 | ||
|
|
b81d3314ea | ||
|
|
4a3ffa5f90 | ||
|
|
a20c7529d6 | ||
|
|
baea5cb0d7 | ||
|
|
659674dd4a | ||
|
|
5c199a143b | ||
|
|
99ec814808 | ||
|
|
ea1f02798c | ||
|
|
e3d4cc5e45 | ||
|
|
c98eb5c42a | ||
|
|
d0392be2d8 | ||
|
|
8761629828 | ||
|
|
a3b258dfeb | ||
|
|
f7505effa1 |
10
.github/workflows/build_and_test.yml
vendored
10
.github/workflows/build_and_test.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
@@ -27,6 +27,14 @@ jobs:
|
||||
- name: Run publish script
|
||||
run: ./publish-nix.sh
|
||||
|
||||
- name: Update rolling tag
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git tag -f rolling
|
||||
git push origin :refs/tags/rolling || true
|
||||
git push origin rolling --force
|
||||
|
||||
- name: Upload to rolling
|
||||
uses: ncipollo/release-action@v1.14.0
|
||||
with:
|
||||
|
||||
7
LICENSE
Normal file
7
LICENSE
Normal file
@@ -0,0 +1,7 @@
|
||||
Copyright (c) 2018-2025 Matt Nadareski
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
57
README.MD
57
README.MD
@@ -39,16 +39,40 @@ Various compression implementations that are used across multiple projects. Most
|
||||
| [DotNetZip](https://github.com/DinoChiesa/DotNetZip) | BZip2 and DEFLATE implementations; minor edits have been made |
|
||||
| [ZLibPort](https://github.com/Nanook/zlib-C-To-CSharp-Port) | Adds zlib code for internal and external use; minor edits have been made |
|
||||
|
||||
### `SabreTools.IO.Encryption`
|
||||
|
||||
Various encryption implementations that are used across multiple projects. Most of the implementations are be ports of existing C and C++ code.
|
||||
|
||||
#### Supported Encryption Schemes
|
||||
|
||||
| Encryption Scheme | Encrypt | Decrypt | Notes |
|
||||
| --- | --- | --- | --- |
|
||||
| AES/CTR | Yes | Yes | Subset of functionality exposed from [The Bouncy Castle Cryptography Library For .NET](https://github.com/bcgit/bc-csharp) |
|
||||
| MoPaQ | No | Yes | Used to encrypt and decrypt MoPaQ tables for processing |
|
||||
|
||||
### `SabreTools.IO.Extensions`
|
||||
|
||||
Extensions for `BinaryReader`, `byte[]`, and `Stream` to help with reading and writing various data types. Some data types are locked behind .NET version support.
|
||||
|
||||
This namespace also contains other various extensions that help with common functionality and safe access.
|
||||
|
||||
### `SabreTools.IO.Interfaces`
|
||||
|
||||
Common interfaces used mainly internal to the library.
|
||||
|
||||
| Interface | Notes |
|
||||
| --- | --- |
|
||||
| `IMatch<T>` | Represents a matcher for a generic type |
|
||||
| `IMatchSet<T, U>` | Represents a set of `IMatch<T>` types |
|
||||
|
||||
### `SabreTools.IO.Logging`
|
||||
|
||||
Logic for a logging system, including writing to console and textfile outputs. There are 4 possible log levels for logging statements to be invoked with. There is also a stopwatch implementation included for logging statements with automatic timespan tracking.
|
||||
|
||||
### `SabreTools.IO.Matching`
|
||||
|
||||
Classes designed to make matching contents and paths easier. These classes allow for both grouped and single matching as well as post-processing of matched information.
|
||||
|
||||
### `SabreTools.IO.Readers` and `SabreTools.IO.Writers`
|
||||
|
||||
Reading and writing support for the following file types:
|
||||
@@ -63,8 +87,41 @@ For a generic INI implementation, see `SabreTools.IO.IniFile`.
|
||||
|
||||
Custom `Stream` implementations that are required for specialized use:
|
||||
|
||||
- `BufferedStream`: A format that is not a true stream implementation used for buffered, single-byte reads
|
||||
- `ReadOnlyBitStream`: A readonly stream implementation allowing bitwise reading
|
||||
- `ReadOnlyCompositeStream`: A readonly stream implementation that wraps multiple source streams in a set order
|
||||
- `ViewStream`: A readonly stream implementation representing a view into source data
|
||||
|
||||
### `SabreTools.IO.Transform`
|
||||
|
||||
File and stream implementations of common data transformations:
|
||||
|
||||
- Combine using either ordered concatenation or interleaving
|
||||
- Split by even/odd chunks or based on block size
|
||||
- Convert data either by bit-swapping, byte-swapping, word-swapping, or word/byte-swapping
|
||||
|
||||
### `SabreTools.Numerics`
|
||||
|
||||
Custom numeric types and related functionality.
|
||||
|
||||
#### Supported Numeric Types
|
||||
|
||||
| Type Name | Description |
|
||||
| --- | --- |
|
||||
| `BothInt8` | Both-endian `Int8` value |
|
||||
| `BothUInt8` | Both-endian `UInt8` value |
|
||||
| `BothInt16` | Both-endian `Int16` value |
|
||||
| `BothUInt16` | Both-endian `UInt16` value |
|
||||
| `BothInt32` | Both-endian `Int32` value |
|
||||
| `BothUInt32` | Both-endian `UInt32` value |
|
||||
| `BothInt64` | Both-endian `Int64` value |
|
||||
| `BothUInt64` | Both-endian `UInt64` value |
|
||||
|
||||
**Both-endian** or **bi-endian** numbers are represented by a little-endian value followed by a big-endian value, where both values are the same number.
|
||||
|
||||
### `SabreTools.Text.Compare`
|
||||
|
||||
Classes focused on string comparison by natural sorting. For example, "5" would be sorted before "100".
|
||||
|
||||
## Releases
|
||||
|
||||
|
||||
38
SabreTools.IO.Test/Compare/NaturalComparerTests.cs
Normal file
38
SabreTools.IO.Test/Compare/NaturalComparerTests.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using SabreTools.Text.Compare;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.IO.Test.Compare
|
||||
{
|
||||
public class NaturalComparerTests
|
||||
{
|
||||
[Fact]
|
||||
public void ListSort_Numeric()
|
||||
{
|
||||
// Setup arrays
|
||||
string[] sortable = ["0", "100", "5", "2", "1000"];
|
||||
string[] expected = ["0", "2", "5", "100", "1000"];
|
||||
|
||||
// Run sorting on array
|
||||
Array.Sort(sortable, new NaturalComparer());
|
||||
|
||||
// Check the output
|
||||
Assert.True(sortable.SequenceEqual(expected));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ListSort_Mixed()
|
||||
{
|
||||
// Setup arrays
|
||||
string[] sortable = ["b3b", "c", "b", "a", "a1"];
|
||||
string[] expected = ["a", "a1", "b", "b3b", "c"];
|
||||
|
||||
// Run sorting on array
|
||||
Array.Sort(sortable, new NaturalComparer());
|
||||
|
||||
// Check the output
|
||||
Assert.True(sortable.SequenceEqual(expected));
|
||||
}
|
||||
}
|
||||
}
|
||||
66
SabreTools.IO.Test/Compare/NaturalComparerUtilTests.cs
Normal file
66
SabreTools.IO.Test/Compare/NaturalComparerUtilTests.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using SabreTools.Text.Compare;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.IO.Test.Compare
|
||||
{
|
||||
public class NaturalComparerUtilTests
|
||||
{
|
||||
[Fact]
|
||||
public void CompareNumeric_BothNull_Equal()
|
||||
{
|
||||
int actual = NaturalComparerUtil.ComparePaths(null, null);
|
||||
Assert.Equal(0, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompareNumeric_SingleNull_Ordered()
|
||||
{
|
||||
int actual = NaturalComparerUtil.ComparePaths(null, "notnull");
|
||||
Assert.Equal(-1, actual);
|
||||
|
||||
actual = NaturalComparerUtil.ComparePaths("notnull", null);
|
||||
Assert.Equal(1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompareNumeric_BothEqual_Equal()
|
||||
{
|
||||
int actual = NaturalComparerUtil.ComparePaths("notnull", "notnull");
|
||||
Assert.Equal(0, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompareNumeric_BothEqualWithPath_Equal()
|
||||
{
|
||||
int actual = NaturalComparerUtil.ComparePaths("notnull/file.ext", "notnull/file.ext");
|
||||
Assert.Equal(0, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompareNumeric_BothEqualWithAltPath_Equal()
|
||||
{
|
||||
int actual = NaturalComparerUtil.ComparePaths("notnull/file.ext", "notnull\\file.ext");
|
||||
Assert.Equal(0, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompareNumeric_NumericNonDecimalString_Ordered()
|
||||
{
|
||||
int actual = NaturalComparerUtil.ComparePaths("100", "10");
|
||||
Assert.Equal(1, actual);
|
||||
|
||||
actual = NaturalComparerUtil.ComparePaths("10", "100");
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompareNumeric_NumericDecimalString_Ordered()
|
||||
{
|
||||
int actual = NaturalComparerUtil.ComparePaths("100.100", "100.10");
|
||||
Assert.Equal(1, actual);
|
||||
|
||||
actual = NaturalComparerUtil.ComparePaths("100.10", "100.100");
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
38
SabreTools.IO.Test/Compare/NaturalReversedComparerTests.cs
Normal file
38
SabreTools.IO.Test/Compare/NaturalReversedComparerTests.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using SabreTools.Text.Compare;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.IO.Test.Compare
|
||||
{
|
||||
public class NaturalReversedComparerTests
|
||||
{
|
||||
[Fact]
|
||||
public void ListSort_Numeric()
|
||||
{
|
||||
// Setup arrays
|
||||
string[] sortable = ["0", "100", "5", "2", "1000"];
|
||||
string[] expected = ["1000", "100", "5", "2", "0"];
|
||||
|
||||
// Run sorting on array
|
||||
Array.Sort(sortable, new NaturalReversedComparer());
|
||||
|
||||
// Check the output
|
||||
Assert.True(sortable.SequenceEqual(expected));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ListSort_Mixed()
|
||||
{
|
||||
// Setup arrays
|
||||
string[] sortable = ["b3b", "c", "b", "a", "a1"];
|
||||
string[] expected = ["c", "b3b", "b", "a1", "a"];
|
||||
|
||||
// Run sorting on array
|
||||
Array.Sort(sortable, new NaturalReversedComparer());
|
||||
|
||||
// Check the output
|
||||
Assert.True(sortable.SequenceEqual(expected));
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
#if NET7_0_OR_GREATER
|
||||
using System.Numerics;
|
||||
#endif
|
||||
using System.Text;
|
||||
using SabreTools.IO.Extensions;
|
||||
using Xunit;
|
||||
@@ -40,6 +38,18 @@ namespace SabreTools.IO.Test.Extensions
|
||||
ValidateBytes(expected, stream.GetBuffer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteByteBothEndianTest()
|
||||
{
|
||||
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
|
||||
var bw = new BinaryWriter(stream);
|
||||
byte[] expected = _bytes.Take(2).ToArray();
|
||||
|
||||
int offset = 0;
|
||||
bw.WriteBothEndian(_bytes.ReadByteBothEndian(ref offset));
|
||||
ValidateBytes(expected, stream.GetBuffer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteBytesTest()
|
||||
{
|
||||
@@ -70,6 +80,18 @@ namespace SabreTools.IO.Test.Extensions
|
||||
ValidateBytes(expected, stream.GetBuffer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteSByteBothEndianTest()
|
||||
{
|
||||
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
|
||||
var bw = new BinaryWriter(stream);
|
||||
byte[] expected = _bytes.Take(2).ToArray();
|
||||
|
||||
int offset = 0;
|
||||
bw.WriteBothEndian(_bytes.ReadSByteBothEndian(ref offset));
|
||||
ValidateBytes(expected, stream.GetBuffer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteCharTest()
|
||||
{
|
||||
@@ -111,6 +133,18 @@ namespace SabreTools.IO.Test.Extensions
|
||||
ValidateBytes(expected, stream.GetBuffer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteInt16BothEndianTest()
|
||||
{
|
||||
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
|
||||
var bw = new BinaryWriter(stream);
|
||||
byte[] expected = _bytes.Take(4).ToArray();
|
||||
|
||||
int offset = 0;
|
||||
bw.WriteBothEndian(_bytes.ReadInt16BothEndian(ref offset));
|
||||
ValidateBytes(expected, stream.GetBuffer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteUInt16Test()
|
||||
{
|
||||
@@ -132,7 +166,18 @@ namespace SabreTools.IO.Test.Extensions
|
||||
ValidateBytes(expected, stream.GetBuffer());
|
||||
}
|
||||
|
||||
#if NET6_0_OR_GREATER
|
||||
[Fact]
|
||||
public void WriteUInt16BothEndianTest()
|
||||
{
|
||||
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
|
||||
var bw = new BinaryWriter(stream);
|
||||
byte[] expected = _bytes.Take(4).ToArray();
|
||||
|
||||
int offset = 0;
|
||||
bw.WriteBothEndian(_bytes.ReadUInt16BothEndian(ref offset));
|
||||
ValidateBytes(expected, stream.GetBuffer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteHalfTest()
|
||||
{
|
||||
@@ -153,7 +198,6 @@ namespace SabreTools.IO.Test.Extensions
|
||||
Assert.True(write);
|
||||
ValidateBytes(expected, stream.GetBuffer());
|
||||
}
|
||||
#endif
|
||||
|
||||
[Fact]
|
||||
public void WriteInt24Test()
|
||||
@@ -218,6 +262,18 @@ namespace SabreTools.IO.Test.Extensions
|
||||
ValidateBytes(expected, stream.GetBuffer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteInt32BothEndianTest()
|
||||
{
|
||||
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
|
||||
var bw = new BinaryWriter(stream);
|
||||
byte[] expected = _bytes.Take(8).ToArray();
|
||||
|
||||
int offset = 0;
|
||||
bw.WriteBothEndian(_bytes.ReadInt32BothEndian(ref offset));
|
||||
ValidateBytes(expected, stream.GetBuffer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteUInt32Test()
|
||||
{
|
||||
@@ -239,6 +295,18 @@ namespace SabreTools.IO.Test.Extensions
|
||||
ValidateBytes(expected, stream.GetBuffer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteUInt32BothEndianTest()
|
||||
{
|
||||
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
|
||||
var bw = new BinaryWriter(stream);
|
||||
byte[] expected = _bytes.Take(8).ToArray();
|
||||
|
||||
int offset = 0;
|
||||
bw.WriteBothEndian(_bytes.ReadUInt32BothEndian(ref offset));
|
||||
ValidateBytes(expected, stream.GetBuffer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteSingleTest()
|
||||
{
|
||||
@@ -323,6 +391,18 @@ namespace SabreTools.IO.Test.Extensions
|
||||
ValidateBytes(expected, stream.GetBuffer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteInt64BothEndianTest()
|
||||
{
|
||||
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
|
||||
var bw = new BinaryWriter(stream);
|
||||
byte[] expected = _bytes.Take(16).ToArray();
|
||||
|
||||
int offset = 0;
|
||||
bw.WriteBothEndian(_bytes.ReadInt64BothEndian(ref offset));
|
||||
ValidateBytes(expected, stream.GetBuffer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteUInt64Test()
|
||||
{
|
||||
@@ -344,6 +424,18 @@ namespace SabreTools.IO.Test.Extensions
|
||||
ValidateBytes(expected, stream.GetBuffer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteUInt64BothEndianTest()
|
||||
{
|
||||
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
|
||||
var bw = new BinaryWriter(stream);
|
||||
byte[] expected = _bytes.Take(16).ToArray();
|
||||
|
||||
int offset = 0;
|
||||
bw.WriteBothEndian(_bytes.ReadUInt64BothEndian(ref offset));
|
||||
ValidateBytes(expected, stream.GetBuffer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteDoubleTest()
|
||||
{
|
||||
@@ -408,7 +500,6 @@ namespace SabreTools.IO.Test.Extensions
|
||||
ValidateBytes(expected, stream.GetBuffer());
|
||||
}
|
||||
|
||||
#if NET7_0_OR_GREATER
|
||||
[Fact]
|
||||
public void WriteInt128Test()
|
||||
{
|
||||
@@ -452,7 +543,6 @@ namespace SabreTools.IO.Test.Extensions
|
||||
Assert.True(write);
|
||||
ValidateBytes(expected, stream.GetBuffer());
|
||||
}
|
||||
#endif
|
||||
|
||||
[Fact]
|
||||
public void WriteNullTerminatedAnsiStringTest()
|
||||
@@ -536,16 +626,13 @@ namespace SabreTools.IO.Test.Extensions
|
||||
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);
|
||||
@@ -559,7 +646,6 @@ namespace SabreTools.IO.Test.Extensions
|
||||
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);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using SabreTools.IO.Extensions;
|
||||
using Xunit;
|
||||
|
||||
@@ -7,7 +8,7 @@ namespace SabreTools.IO.Test.Extensions
|
||||
{
|
||||
public class ByteArrayExtensionsTests
|
||||
{
|
||||
#region Is Null or Empty
|
||||
#region IsNullOrEmpty
|
||||
|
||||
[Fact]
|
||||
public void IsNullOrEmpty_Null_True()
|
||||
@@ -35,7 +36,475 @@ namespace SabreTools.IO.Test.Extensions
|
||||
|
||||
#endregion
|
||||
|
||||
#region To Hex String
|
||||
#region FindAllPositions
|
||||
|
||||
[Fact]
|
||||
public void FindAllPositions_EmptyStack_NoMatches()
|
||||
{
|
||||
byte[] stack = [];
|
||||
var positions = stack.FindAllPositions([0x01]);
|
||||
Assert.Empty(positions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindAllPositions_EmptyNeedle_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
var positions = stack.FindAllPositions(Array.Empty<byte>());
|
||||
Assert.Empty(positions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindAllPositions_LongerNeedle_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
var positions = stack.FindAllPositions([0x01, 0x02]);
|
||||
Assert.Empty(positions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindAllPositions_InvalidStart_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
var positions = stack.FindAllPositions([0x01, 0x02], start: -1);
|
||||
Assert.Empty(positions);
|
||||
|
||||
positions = stack.FindAllPositions([0x01, 0x02], start: 2);
|
||||
Assert.Empty(positions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindAllPositions_InvalidEnd_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
var positions = stack.FindAllPositions([0x01, 0x02], end: -2);
|
||||
Assert.Empty(positions);
|
||||
|
||||
positions = stack.FindAllPositions([0x01, 0x02], end: 0);
|
||||
Assert.Empty(positions);
|
||||
|
||||
positions = stack.FindAllPositions([0x01, 0x02], end: 2);
|
||||
Assert.Empty(positions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindAllPositions_Matching_Matches()
|
||||
{
|
||||
byte[] stack = [0x01, 0x02];
|
||||
var positions = stack.FindAllPositions([0x01, 0x02]);
|
||||
int position = Assert.Single(positions);
|
||||
Assert.Equal(0, position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindAllPositions_Mismatch_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01, 0x03];
|
||||
var positions = stack.FindAllPositions([0x01, 0x02]);
|
||||
Assert.Empty(positions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindAllPositions_Multiple_Matches()
|
||||
{
|
||||
byte[] stack = [0x01, 0x01];
|
||||
var positions = stack.FindAllPositions([0x01]);
|
||||
Assert.Equal(2, positions.Count);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region FirstPosition
|
||||
|
||||
[Fact]
|
||||
public void FirstPosition_EmptyStack_NoMatches()
|
||||
{
|
||||
byte[] stack = [];
|
||||
int position = stack.FirstPosition([0x01]);
|
||||
Assert.Equal(-1, position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FirstPosition_EmptyNeedle_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
int position = stack.FirstPosition(Array.Empty<byte>());
|
||||
Assert.Equal(-1, position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FirstPosition_LongerNeedle_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
int position = stack.FirstPosition([0x01, 0x02]);
|
||||
Assert.Equal(-1, position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FirstPosition_InvalidStart_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
int position = stack.FirstPosition([0x01, 0x02], start: -1);
|
||||
Assert.Equal(-1, position);
|
||||
|
||||
position = stack.FirstPosition([0x01, 0x02], start: 2);
|
||||
Assert.Equal(-1, position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FirstPosition_InvalidEnd_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
int position = stack.FirstPosition([0x01, 0x02], end: -2);
|
||||
Assert.Equal(-1, position);
|
||||
|
||||
position = stack.FirstPosition([0x01, 0x02], end: 0);
|
||||
Assert.Equal(-1, position);
|
||||
|
||||
position = stack.FirstPosition([0x01, 0x02], end: 2);
|
||||
Assert.Equal(-1, position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FirstPosition_Matching_Matches()
|
||||
{
|
||||
byte[] stack = [0x01, 0x02];
|
||||
int position = stack.FirstPosition([0x01, 0x02]);
|
||||
Assert.Equal(0, position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FirstPosition_Mismatch_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01, 0x03];
|
||||
int position = stack.FirstPosition([0x01, 0x02]);
|
||||
Assert.Equal(-1, position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FirstPosition_Multiple_Matches()
|
||||
{
|
||||
byte[] stack = [0x01, 0x01];
|
||||
int position = stack.FirstPosition([0x01]);
|
||||
Assert.Equal(0, position);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region LastPosition
|
||||
|
||||
[Fact]
|
||||
public void LastPosition_EmptyStack_NoMatches()
|
||||
{
|
||||
byte[] stack = [];
|
||||
int position = stack.LastPosition([0x01]);
|
||||
Assert.Equal(-1, position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LastPosition_EmptyNeedle_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
int position = stack.LastPosition(Array.Empty<byte>());
|
||||
Assert.Equal(-1, position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LastPosition_LongerNeedle_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
int position = stack.LastPosition([0x01, 0x02]);
|
||||
Assert.Equal(-1, position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LastPosition_InvalidStart_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
int position = stack.LastPosition([0x01, 0x02], start: -1);
|
||||
Assert.Equal(-1, position);
|
||||
|
||||
position = stack.LastPosition([0x01, 0x02], start: 2);
|
||||
Assert.Equal(-1, position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LastPosition_InvalidEnd_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
int position = stack.LastPosition([0x01, 0x02], end: -2);
|
||||
Assert.Equal(-1, position);
|
||||
|
||||
position = stack.LastPosition([0x01, 0x02], end: 0);
|
||||
Assert.Equal(-1, position);
|
||||
|
||||
position = stack.LastPosition([0x01, 0x02], end: 2);
|
||||
Assert.Equal(-1, position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LastPosition_Matching_Matches()
|
||||
{
|
||||
byte[] stack = [0x01, 0x02];
|
||||
int position = stack.LastPosition([0x01, 0x02]);
|
||||
Assert.Equal(0, position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LastPosition_Mismatch_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01, 0x03];
|
||||
int position = stack.LastPosition([0x01, 0x02]);
|
||||
Assert.Equal(-1, position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LastPosition_Multiple_Matches()
|
||||
{
|
||||
byte[] stack = [0x01, 0x01];
|
||||
int position = stack.LastPosition([0x01]);
|
||||
Assert.Equal(1, position);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region EqualsExactly
|
||||
|
||||
[Fact]
|
||||
public void EqualsExactly_EmptyStack_NoMatches()
|
||||
{
|
||||
byte[] stack = [];
|
||||
bool found = stack.EqualsExactly([0x01]);
|
||||
Assert.False(found);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualsExactly_EmptyNeedle_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
bool found = stack.EqualsExactly(Array.Empty<byte>());
|
||||
Assert.False(found);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualsExactly_ShorterNeedle_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01, 0x02];
|
||||
bool found = stack.EqualsExactly([0x01]);
|
||||
Assert.False(found);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualsExactly_LongerNeedle_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
bool found = stack.EqualsExactly([0x01, 0x02]);
|
||||
Assert.False(found);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualsExactly_Matching_Matches()
|
||||
{
|
||||
byte[] stack = [0x01, 0x02];
|
||||
bool found = stack.EqualsExactly([0x01, 0x02]);
|
||||
Assert.True(found);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualsExactly_Mismatch_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01, 0x03];
|
||||
bool found = stack.EqualsExactly([0x01, 0x02]);
|
||||
Assert.False(found);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region StartsWith
|
||||
|
||||
[Fact]
|
||||
public void StartsWith_EmptyStack_NoMatches()
|
||||
{
|
||||
byte[] stack = [];
|
||||
bool found = stack.StartsWith([0x01]);
|
||||
Assert.False(found);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StartsWith_EmptyNeedle_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
bool found = stack.StartsWith(Array.Empty<byte>());
|
||||
Assert.False(found);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StartsWith_LongerNeedle_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
bool found = stack.StartsWith([0x01, 0x02]);
|
||||
Assert.False(found);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StartsWith_Matching_Matches()
|
||||
{
|
||||
byte[] stack = [0x01, 0x02];
|
||||
bool found = stack.StartsWith([0x01, 0x02]);
|
||||
Assert.True(found);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StartsWith_Mismatch_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01, 0x03];
|
||||
bool found = stack.StartsWith([0x01, 0x02]);
|
||||
Assert.False(found);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StartsWith_Multiple_Matches()
|
||||
{
|
||||
byte[] stack = [0x01, 0x01];
|
||||
bool found = stack.StartsWith([0x01]);
|
||||
Assert.True(found);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region EndsWith
|
||||
|
||||
[Fact]
|
||||
public void EndsWith_EmptyStack_NoMatches()
|
||||
{
|
||||
byte[] stack = [];
|
||||
bool found = stack.EndsWith([0x01]);
|
||||
Assert.False(found);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EndsWith_EmptyNeedle_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
bool found = stack.EndsWith(Array.Empty<byte>());
|
||||
Assert.False(found);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EndsWith_LongerNeedle_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
bool found = stack.StartsWith([0x01, 0x02]);
|
||||
Assert.False(found);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EndsWith_Matching_Matches()
|
||||
{
|
||||
byte[] stack = [0x01, 0x02];
|
||||
bool found = stack.EndsWith([0x01, 0x02]);
|
||||
Assert.True(found);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EndsWith_Mismatch_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01, 0x03];
|
||||
bool found = stack.EndsWith([0x01, 0x02]);
|
||||
Assert.False(found);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EndsWith_Multiple_Matches()
|
||||
{
|
||||
byte[] stack = [0x01, 0x01];
|
||||
bool found = stack.EndsWith([0x01]);
|
||||
Assert.True(found);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Add
|
||||
|
||||
[Theory]
|
||||
[InlineData(new byte[0], 0, new byte[0])]
|
||||
[InlineData(new byte[0], 1234, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xD2 })]
|
||||
[InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xD2 }, 0, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xD2 })]
|
||||
[InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xD2 }, 1234, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0xA4 })]
|
||||
public void Add_NumericInput(byte[] self, uint add, byte[] expected)
|
||||
{
|
||||
byte[] actual = self.Add(add);
|
||||
|
||||
Assert.Equal(expected.Length, actual.Length);
|
||||
if (actual.Length > 0)
|
||||
Assert.True(actual.EqualsExactly(expected));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(new byte[0], new byte[0], new byte[0])]
|
||||
[InlineData(new byte[0], new byte[] { 0x04, 0xD2 }, new byte[] { 0x04, 0xD2 })]
|
||||
[InlineData(new byte[] { 0x04, 0xD2 }, new byte[0], new byte[] { 0x04, 0xD2 })]
|
||||
[InlineData(new byte[] { 0x04, 0xD2 }, new byte[] { 0x00, 0x00 }, new byte[] { 0x04, 0xD2 })]
|
||||
[InlineData(new byte[] { 0x00, 0x00 }, new byte[] { 0x04, 0xD2 }, new byte[] { 0x04, 0xD2 })]
|
||||
[InlineData(new byte[] { 0x04, 0xD2 }, new byte[] { 0x04, 0xD2 }, new byte[] { 0x09, 0xA4 })]
|
||||
[InlineData(new byte[] { 0xAB, 0x04, 0xD2 }, new byte[] { 0x04, 0xD2 }, new byte[] { 0xAB, 0x09, 0xA4 })]
|
||||
[InlineData(new byte[] { 0x04, 0xD2 }, new byte[] { 0xAB, 0x04, 0xD2 }, new byte[] { 0xAB, 0x09, 0xA4 })]
|
||||
public void Add_ArrayInput(byte[] self, byte[] add, byte[] expected)
|
||||
{
|
||||
byte[] actual = self.Add(add);
|
||||
|
||||
Assert.Equal(expected.Length, actual.Length);
|
||||
if (actual.Length > 0)
|
||||
Assert.True(actual.EqualsExactly(expected));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region RotateLeft
|
||||
|
||||
[Theory]
|
||||
[InlineData(new byte[0], 0, new byte[0])]
|
||||
[InlineData(new byte[] { 0x01 }, 0, new byte[] { 0x01 })]
|
||||
[InlineData(new byte[] { 0x01 }, 1, new byte[] { 0x02 })]
|
||||
[InlineData(new byte[] { 0x80 }, 1, new byte[] { 0x01 })]
|
||||
[InlineData(new byte[] { 0x00, 0x01 }, 0, new byte[] { 0x00, 0x01 })]
|
||||
[InlineData(new byte[] { 0x00, 0x01 }, 1, new byte[] { 0x00, 0x02 })]
|
||||
[InlineData(new byte[] { 0x00, 0x80 }, 1, new byte[] { 0x01, 0x00 })]
|
||||
[InlineData(new byte[] { 0x80, 0x00 }, 1, new byte[] { 0x00, 0x01 })]
|
||||
public void RotateLeftTest(byte[] self, int numBits, byte[] expected)
|
||||
{
|
||||
byte[] actual = self.RotateLeft(numBits);
|
||||
|
||||
Assert.Equal(expected.Length, actual.Length);
|
||||
if (actual.Length > 0)
|
||||
Assert.True(actual.EqualsExactly(expected));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Xor
|
||||
|
||||
[Theory]
|
||||
[InlineData(new byte[0], new byte[0], new byte[0])]
|
||||
[InlineData(new byte[0], new byte[] { 0x04, 0xD2 }, new byte[] { 0x04, 0xD2 })]
|
||||
[InlineData(new byte[] { 0x04, 0xD2 }, new byte[0], new byte[] { 0x04, 0xD2 })]
|
||||
[InlineData(new byte[] { 0x04, 0xD2 }, new byte[] { 0x00, 0x00 }, new byte[] { 0x04, 0xD2 })]
|
||||
[InlineData(new byte[] { 0x00, 0x00 }, new byte[] { 0x04, 0xD2 }, new byte[] { 0x04, 0xD2 })]
|
||||
[InlineData(new byte[] { 0x04, 0xD2 }, new byte[] { 0x04, 0xD2 }, new byte[] { 0x00, 0x00 })]
|
||||
[InlineData(new byte[] { 0xAB, 0x04, 0xD2 }, new byte[] { 0x04, 0xD2 }, new byte[] { 0xAB, 0x00, 0x00 })]
|
||||
[InlineData(new byte[] { 0x04, 0xD2 }, new byte[] { 0xAB, 0x04, 0xD2 }, new byte[] { 0xAB, 0x00, 0x00 })]
|
||||
public void XorTest(byte[] self, byte[] add, byte[] expected)
|
||||
{
|
||||
byte[] actual = self.Xor(add);
|
||||
|
||||
Assert.Equal(expected.Length, actual.Length);
|
||||
if (actual.Length > 0)
|
||||
Assert.True(actual.EqualsExactly(expected));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ToHexString
|
||||
|
||||
[Fact]
|
||||
public void ToHexString_Null()
|
||||
@@ -58,7 +527,7 @@ namespace SabreTools.IO.Test.Extensions
|
||||
|
||||
#endregion
|
||||
|
||||
#region From Hex String
|
||||
#region FromHexString
|
||||
|
||||
[Fact]
|
||||
public void FromHexString_Null()
|
||||
@@ -88,5 +557,385 @@ namespace SabreTools.IO.Test.Extensions
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ReadStringsFrom
|
||||
|
||||
[Fact]
|
||||
public void ReadStringsFrom_Null_Null()
|
||||
{
|
||||
byte[]? arr = null;
|
||||
var actual = arr.ReadStringsFrom(3);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadStringsFrom_Empty_Null()
|
||||
{
|
||||
byte[]? arr = [];
|
||||
var actual = arr.ReadStringsFrom(3);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(-1)]
|
||||
[InlineData(0)]
|
||||
[InlineData(2048)]
|
||||
public void ReadStringsFrom_InvalidLimit_Empty(int charLimit)
|
||||
{
|
||||
byte[]? arr = new byte[1024];
|
||||
var actual = arr.ReadStringsFrom(charLimit);
|
||||
Assert.NotNull(actual);
|
||||
Assert.Empty(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadStringsFrom_NoValidStrings_Empty()
|
||||
{
|
||||
byte[]? arr = new byte[1024];
|
||||
var actual = arr.ReadStringsFrom(4);
|
||||
Assert.NotNull(actual);
|
||||
Assert.Empty(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadStringsFrom_AsciiStrings_Filled()
|
||||
{
|
||||
byte[]? arr =
|
||||
[
|
||||
.. Encoding.ASCII.GetBytes("TEST"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.ASCII.GetBytes("TWO"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.ASCII.GetBytes("DATA"),
|
||||
.. new byte[] { 0x00 },
|
||||
];
|
||||
var actual = arr.ReadStringsFrom(4);
|
||||
Assert.NotNull(actual);
|
||||
Assert.Equal(2, actual.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadStringsFrom_Latin1Strings_Filled()
|
||||
{
|
||||
byte[]? arr =
|
||||
[
|
||||
.. Encoding.Latin1.GetBytes("TEST"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.Latin1.GetBytes("TWO"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.Latin1.GetBytes("DATA"),
|
||||
.. new byte[] { 0x00 },
|
||||
];
|
||||
var actual = arr.ReadStringsFrom(4);
|
||||
Assert.NotNull(actual);
|
||||
Assert.Equal(2, actual.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadStringsFrom_UTF16_Filled()
|
||||
{
|
||||
byte[]? arr =
|
||||
[
|
||||
.. Encoding.Unicode.GetBytes("TEST"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.Unicode.GetBytes("TWO"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.Unicode.GetBytes("DATA"),
|
||||
.. new byte[] { 0x00 },
|
||||
];
|
||||
var actual = arr.ReadStringsFrom(4);
|
||||
Assert.NotNull(actual);
|
||||
Assert.Equal(2, actual.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadStringsFrom_Mixed_Filled()
|
||||
{
|
||||
byte[]? arr =
|
||||
[
|
||||
.. Encoding.ASCII.GetBytes("TEST1"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.ASCII.GetBytes("TWO1"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.ASCII.GetBytes("DATA1"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.Latin1.GetBytes("TEST2"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.Latin1.GetBytes("TWO2"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.Latin1.GetBytes("DATA2"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.Unicode.GetBytes("TEST3"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.Unicode.GetBytes("TWO3"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.Unicode.GetBytes("DATA3"),
|
||||
.. new byte[] { 0x00 },
|
||||
];
|
||||
var actual = arr.ReadStringsFrom(5);
|
||||
Assert.NotNull(actual);
|
||||
Assert.Equal(6, actual.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This test is here mainly for performance testing
|
||||
/// and should not be enabled unless there are changes
|
||||
/// to the core reading methods that need comparison.
|
||||
/// </summary>
|
||||
// [Fact]
|
||||
// public void ReadStringsFrom_Mixed_MASSIVE()
|
||||
// {
|
||||
// byte[]? arr =
|
||||
// [
|
||||
// .. Encoding.ASCII.GetBytes("TEST1"),
|
||||
// .. new byte[] { 0x00 },
|
||||
// .. Encoding.ASCII.GetBytes("TWO1"),
|
||||
// .. new byte[] { 0x00 },
|
||||
// .. Encoding.ASCII.GetBytes("DATA1"),
|
||||
// .. new byte[] { 0x00 },
|
||||
// .. Encoding.UTF8.GetBytes("TEST2"),
|
||||
// .. new byte[] { 0x00 },
|
||||
// .. Encoding.UTF8.GetBytes("TWO2"),
|
||||
// .. new byte[] { 0x00 },
|
||||
// .. Encoding.UTF8.GetBytes("DATA2"),
|
||||
// .. new byte[] { 0x00 },
|
||||
// .. Encoding.Unicode.GetBytes("TEST3"),
|
||||
// .. new byte[] { 0x00 },
|
||||
// .. Encoding.Unicode.GetBytes("TWO3"),
|
||||
// .. new byte[] { 0x00 },
|
||||
// .. Encoding.Unicode.GetBytes("DATA3"),
|
||||
// .. new byte[] { 0x00 },
|
||||
// ];
|
||||
// arr = [.. arr, .. arr, .. arr, .. arr];
|
||||
// arr = [.. arr, .. arr, .. arr, .. arr];
|
||||
// arr = [.. arr, .. arr, .. arr, .. arr];
|
||||
// arr = [.. arr, .. arr, .. arr, .. arr];
|
||||
// arr = [.. arr, .. arr, .. arr, .. arr];
|
||||
// arr = [.. arr, .. arr, .. arr, .. arr];
|
||||
// arr = [.. arr, .. arr, .. arr, .. arr];
|
||||
// arr = [.. arr, .. arr, .. arr, .. arr];
|
||||
// arr = [.. arr, .. arr, .. arr, .. arr];
|
||||
// arr = [.. arr, .. arr, .. arr, .. arr];
|
||||
// // arr = [.. arr, .. arr, .. arr, .. arr];
|
||||
// // arr = [.. arr, .. arr, .. arr, .. arr];
|
||||
|
||||
// var actual = arr.ReadStringsFrom(5);
|
||||
// Assert.NotNull(actual);
|
||||
// Assert.NotEmpty(actual);
|
||||
// }
|
||||
|
||||
#endregion
|
||||
|
||||
#region ReadStringsWithEncoding
|
||||
|
||||
[Fact]
|
||||
public void ReadStringsWithEncoding_Null_Empty()
|
||||
{
|
||||
byte[]? bytes = null;
|
||||
var actual = bytes.ReadStringsWithEncoding(1, Encoding.ASCII);
|
||||
Assert.Empty(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadStringsWithEncoding_Empty_Empty()
|
||||
{
|
||||
byte[]? bytes = [];
|
||||
var actual = bytes.ReadStringsWithEncoding(1, Encoding.ASCII);
|
||||
Assert.Empty(actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(-1)]
|
||||
[InlineData(0)]
|
||||
[InlineData(2048)]
|
||||
public void ReadStringsWithEncoding_InvalidLimit_Empty(int charLimit)
|
||||
{
|
||||
byte[]? bytes = new byte[1024];
|
||||
var actual = bytes.ReadStringsWithEncoding(charLimit, Encoding.ASCII);
|
||||
Assert.Empty(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadStringsWithEncoding_NoValidStrings_Empty()
|
||||
{
|
||||
byte[]? bytes = new byte[1024];
|
||||
var actual = bytes.ReadStringsWithEncoding(5, Encoding.ASCII);
|
||||
Assert.Empty(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadStringsWithEncoding_AsciiStrings_Filled()
|
||||
{
|
||||
byte[]? bytes =
|
||||
[
|
||||
.. Encoding.ASCII.GetBytes("TEST"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.ASCII.GetBytes("ONE"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.ASCII.GetBytes("TWO"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.ASCII.GetBytes("DATA"),
|
||||
.. new byte[] { 0x00 },
|
||||
];
|
||||
var actual = bytes.ReadStringsWithEncoding(4, Encoding.ASCII);
|
||||
Assert.Equal(2, actual.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadStringsWithEncoding_InvalidAsciiChars_Empty()
|
||||
{
|
||||
byte[]? arr =
|
||||
[
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
||||
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
|
||||
.. Enumerable.Range(0x80, 0x80).Select(i => (byte)i),
|
||||
];
|
||||
var actual = arr.ReadStringsWithEncoding(1, Encoding.ASCII);
|
||||
Assert.NotNull(actual);
|
||||
Assert.Empty(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadStringsWithEncoding_Latin1_Filled()
|
||||
{
|
||||
byte[]? bytes =
|
||||
[
|
||||
.. Encoding.Latin1.GetBytes("TEST"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.Latin1.GetBytes("ONE"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.Latin1.GetBytes("TWO"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.Latin1.GetBytes("DATA"),
|
||||
.. new byte[] { 0x00 },
|
||||
];
|
||||
var actual = bytes.ReadStringsWithEncoding(4, Encoding.Latin1);
|
||||
Assert.Equal(2, actual.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadStringsWithEncoding_InvalidLatin1Chars_Empty()
|
||||
{
|
||||
byte[]? arr =
|
||||
[
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
||||
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
|
||||
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
|
||||
0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
|
||||
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
|
||||
0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
|
||||
];
|
||||
var actual = arr.ReadStringsWithEncoding(1, Encoding.Latin1);
|
||||
Assert.NotNull(actual);
|
||||
Assert.Empty(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadStringsWithEncoding_UTF8_Filled()
|
||||
{
|
||||
byte[]? bytes =
|
||||
[
|
||||
.. Encoding.UTF8.GetBytes("TEST"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.UTF8.GetBytes("ONE"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.UTF8.GetBytes("TWO"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.UTF8.GetBytes("DATA"),
|
||||
.. new byte[] { 0x00 },
|
||||
];
|
||||
var actual = bytes.ReadStringsWithEncoding(4, Encoding.UTF8);
|
||||
Assert.Equal(2, actual.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadStringsWithEncoding_InvalidUTF8Chars_Empty()
|
||||
{
|
||||
byte[]? arr =
|
||||
[
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
||||
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
|
||||
.. Enumerable.Range(0x80, 0x42).Select(i => (byte)i),
|
||||
0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC,
|
||||
0xFD, 0xFE, 0xFF,
|
||||
];
|
||||
var actual = arr.ReadStringsWithEncoding(1, Encoding.UTF8);
|
||||
Assert.NotNull(actual);
|
||||
Assert.Empty(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadStringsWithEncoding_UTF16_Filled()
|
||||
{
|
||||
byte[]? bytes =
|
||||
[
|
||||
.. Encoding.Unicode.GetBytes("TEST"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.Unicode.GetBytes("ONE"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.Unicode.GetBytes("TWO"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.Unicode.GetBytes("DATA"),
|
||||
.. new byte[] { 0x00 },
|
||||
];
|
||||
var actual = bytes.ReadStringsWithEncoding(4, Encoding.Unicode);
|
||||
Assert.Equal(2, actual.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadStringsWithEncoding_InvalidUTF16Chars_Empty()
|
||||
{
|
||||
byte[]? arr =
|
||||
[
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
||||
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
|
||||
];
|
||||
var actual = arr.ReadStringsWithEncoding(1, Encoding.Unicode);
|
||||
Assert.NotNull(actual);
|
||||
Assert.Empty(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadStringsWithEncoding_UTF32_Filled()
|
||||
{
|
||||
byte[]? bytes =
|
||||
[
|
||||
.. Encoding.UTF32.GetBytes("TEST"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.UTF32.GetBytes("ONE"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.UTF32.GetBytes("TWO"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.UTF32.GetBytes("DATA"),
|
||||
.. new byte[] { 0x00 },
|
||||
];
|
||||
var actual = bytes.ReadStringsWithEncoding(4, Encoding.UTF32);
|
||||
Assert.Equal(2, actual.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadStringsWithEncoding_InvalidUTF32Chars_Empty()
|
||||
{
|
||||
byte[]? arr =
|
||||
[
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
||||
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
|
||||
];
|
||||
var actual = arr.ReadStringsWithEncoding(1, Encoding.UTF32);
|
||||
Assert.NotNull(actual);
|
||||
Assert.Empty(actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
#if NET7_0_OR_GREATER
|
||||
using System.Numerics;
|
||||
#endif
|
||||
using System.Text;
|
||||
using SabreTools.IO.Extensions;
|
||||
using Xunit;
|
||||
@@ -40,6 +38,18 @@ namespace SabreTools.IO.Test.Extensions
|
||||
ValidateBytes(expected, buffer);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteByteBothEndianTest()
|
||||
{
|
||||
byte[] buffer = new byte[16];
|
||||
int offset = 0;
|
||||
byte[] expected = _bytes.Take(2).ToArray();
|
||||
|
||||
int readOffset = 0;
|
||||
buffer.WriteBothEndian(ref offset, _bytes.ReadByteBothEndian(ref readOffset));
|
||||
ValidateBytes(expected, buffer);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteBytesTest()
|
||||
{
|
||||
@@ -73,6 +83,18 @@ namespace SabreTools.IO.Test.Extensions
|
||||
ValidateBytes(expected, buffer);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteSByteBothEndianTest()
|
||||
{
|
||||
byte[] buffer = new byte[16];
|
||||
int offset = 0;
|
||||
byte[] expected = _bytes.Take(2).ToArray();
|
||||
|
||||
int readOffset = 0;
|
||||
buffer.WriteBothEndian(ref offset, _bytes.ReadSByteBothEndian(ref readOffset));
|
||||
ValidateBytes(expected, buffer);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteCharTest()
|
||||
{
|
||||
@@ -117,6 +139,18 @@ namespace SabreTools.IO.Test.Extensions
|
||||
ValidateBytes(expected, buffer);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteInt16BothEndianTest()
|
||||
{
|
||||
byte[] buffer = new byte[16];
|
||||
int offset = 0;
|
||||
byte[] expected = _bytes.Take(4).ToArray();
|
||||
|
||||
int readOffset = 0;
|
||||
buffer.WriteBothEndian(ref offset, _bytes.ReadInt16BothEndian(ref readOffset));
|
||||
ValidateBytes(expected, buffer);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteUInt16Test()
|
||||
{
|
||||
@@ -139,7 +173,18 @@ namespace SabreTools.IO.Test.Extensions
|
||||
ValidateBytes(expected, buffer);
|
||||
}
|
||||
|
||||
#if NET6_0_OR_GREATER
|
||||
[Fact]
|
||||
public void WriteUInt16BothEndianTest()
|
||||
{
|
||||
byte[] buffer = new byte[16];
|
||||
int offset = 0;
|
||||
byte[] expected = _bytes.Take(4).ToArray();
|
||||
|
||||
int readOffset = 0;
|
||||
buffer.WriteBothEndian(ref offset, _bytes.ReadUInt16BothEndian(ref readOffset));
|
||||
ValidateBytes(expected, buffer);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteHalfTest()
|
||||
{
|
||||
@@ -161,7 +206,6 @@ namespace SabreTools.IO.Test.Extensions
|
||||
Assert.True(write);
|
||||
ValidateBytes(expected, buffer);
|
||||
}
|
||||
#endif
|
||||
|
||||
[Fact]
|
||||
public void WriteInt24Test()
|
||||
@@ -229,6 +273,18 @@ namespace SabreTools.IO.Test.Extensions
|
||||
ValidateBytes(expected, buffer);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteInt32BothEndianTest()
|
||||
{
|
||||
byte[] buffer = new byte[16];
|
||||
int offset = 0;
|
||||
byte[] expected = _bytes.Take(8).ToArray();
|
||||
|
||||
int readOffset = 0;
|
||||
buffer.WriteBothEndian(ref offset, _bytes.ReadInt32BothEndian(ref readOffset));
|
||||
ValidateBytes(expected, buffer);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteUInt32Test()
|
||||
{
|
||||
@@ -251,6 +307,18 @@ namespace SabreTools.IO.Test.Extensions
|
||||
ValidateBytes(expected, buffer);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteUInt32BothEndianTest()
|
||||
{
|
||||
byte[] buffer = new byte[16];
|
||||
int offset = 0;
|
||||
byte[] expected = _bytes.Take(8).ToArray();
|
||||
|
||||
int readOffset = 0;
|
||||
buffer.WriteBothEndian(ref offset, _bytes.ReadUInt32BothEndian(ref readOffset));
|
||||
ValidateBytes(expected, buffer);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteSingleTest()
|
||||
{
|
||||
@@ -339,6 +407,18 @@ namespace SabreTools.IO.Test.Extensions
|
||||
ValidateBytes(expected, buffer);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteInt64BothEndianTest()
|
||||
{
|
||||
byte[] buffer = new byte[16];
|
||||
int offset = 0;
|
||||
byte[] expected = _bytes.Take(16).ToArray();
|
||||
|
||||
int readOffset = 0;
|
||||
buffer.WriteBothEndian(ref offset, _bytes.ReadInt64BothEndian(ref readOffset));
|
||||
ValidateBytes(expected, buffer);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteUInt64Test()
|
||||
{
|
||||
@@ -361,6 +441,18 @@ namespace SabreTools.IO.Test.Extensions
|
||||
ValidateBytes(expected, buffer);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteUInt64BothEndianTest()
|
||||
{
|
||||
byte[] buffer = new byte[16];
|
||||
int offset = 0;
|
||||
byte[] expected = _bytes.Take(16).ToArray();
|
||||
|
||||
int readOffset = 0;
|
||||
buffer.WriteBothEndian(ref offset, _bytes.ReadUInt64BothEndian(ref readOffset));
|
||||
ValidateBytes(expected, buffer);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteDoubleTest()
|
||||
{
|
||||
@@ -427,7 +519,6 @@ namespace SabreTools.IO.Test.Extensions
|
||||
ValidateBytes(expected, buffer);
|
||||
}
|
||||
|
||||
#if NET7_0_OR_GREATER
|
||||
[Fact]
|
||||
public void WriteInt128Test()
|
||||
{
|
||||
@@ -471,7 +562,6 @@ namespace SabreTools.IO.Test.Extensions
|
||||
Assert.True(write);
|
||||
ValidateBytes(expected, buffer);
|
||||
}
|
||||
#endif
|
||||
|
||||
[Fact]
|
||||
public void WriteNullTerminatedAnsiStringTest()
|
||||
@@ -555,16 +645,13 @@ namespace SabreTools.IO.Test.Extensions
|
||||
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];
|
||||
@@ -578,7 +665,6 @@ namespace SabreTools.IO.Test.Extensions
|
||||
actual = buffer.WriteType<UInt128>(ref offset, (UInt128)new BigInteger(_bytes));
|
||||
Assert.True(actual);
|
||||
ValidateBytes(_bytes, buffer);
|
||||
#endif
|
||||
|
||||
// Enum
|
||||
offset = 0;
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using SabreTools.IO.Extensions;
|
||||
using Xunit;
|
||||
|
||||
@@ -9,6 +10,22 @@ namespace SabreTools.IO.Test.Extensions
|
||||
{
|
||||
public class EnumerableExtensionsTests
|
||||
{
|
||||
#region IterateWithAction
|
||||
|
||||
[Fact]
|
||||
public void IterateWithActionTest()
|
||||
{
|
||||
List<int> source = [1, 2, 3, 4];
|
||||
int actual = 0;
|
||||
|
||||
source.IterateWithAction(i => Interlocked.Add(ref actual, i));
|
||||
Assert.Equal(10, actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region SafeEnumerate
|
||||
|
||||
[Fact]
|
||||
public void SafeEnumerate_Empty()
|
||||
{
|
||||
@@ -60,6 +77,8 @@ namespace SabreTools.IO.Test.Extensions
|
||||
Assert.Equal(2, list.Count);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Fake enumerable that uses <see cref="ErrorEnumerator"/>
|
||||
/// </summary>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using SabreTools.IO.Extensions;
|
||||
using Xunit;
|
||||
|
||||
@@ -7,7 +8,7 @@ namespace SabreTools.IO.Test.Extensions
|
||||
{
|
||||
public class StreamExtensionsTests
|
||||
{
|
||||
#region Align to Boundary
|
||||
#region AlignToBoundary
|
||||
|
||||
[Fact]
|
||||
public void AlignToBoundary_Null_False()
|
||||
@@ -62,7 +63,215 @@ namespace SabreTools.IO.Test.Extensions
|
||||
|
||||
#endregion
|
||||
|
||||
#region Seek If Possible
|
||||
#region ReadFrom
|
||||
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public void ReadFrom_Null_Null(bool retainPosition)
|
||||
{
|
||||
Stream? stream = null;
|
||||
byte[]? actual = stream.ReadFrom(0, 1, retainPosition);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public void ReadFrom_NonSeekable_Null(bool retainPosition)
|
||||
{
|
||||
Stream? stream = new NonSeekableStream();
|
||||
byte[]? actual = stream.ReadFrom(0, 1, retainPosition);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public void ReadFrom_Empty_Null(bool retainPosition)
|
||||
{
|
||||
Stream? stream = new MemoryStream([]);
|
||||
byte[]? actual = stream.ReadFrom(0, 1, retainPosition);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(-1, true)]
|
||||
[InlineData(2048, true)]
|
||||
[InlineData(-1, false)]
|
||||
[InlineData(2048, false)]
|
||||
public void ReadFrom_InvalidOffset_Null(long offset, bool retainPosition)
|
||||
{
|
||||
Stream? stream = new MemoryStream(new byte[1024]);
|
||||
byte[]? actual = stream.ReadFrom(offset, 1, retainPosition);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(-1, true)]
|
||||
[InlineData(2048, true)]
|
||||
[InlineData(-1, false)]
|
||||
[InlineData(2048, false)]
|
||||
public void ReadFrom_InvalidLength_Null(int length, bool retainPosition)
|
||||
{
|
||||
Stream? stream = new MemoryStream(new byte[1024]);
|
||||
byte[]? actual = stream.ReadFrom(0, length, retainPosition);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public void ReadFrom_Valid_Filled(bool retainPosition)
|
||||
{
|
||||
Stream? stream = new MemoryStream(new byte[1024]);
|
||||
byte[]? actual = stream.ReadFrom(0, 512, retainPosition);
|
||||
|
||||
Assert.NotNull(actual);
|
||||
Assert.Equal(512, actual.Length);
|
||||
|
||||
if (retainPosition)
|
||||
Assert.Equal(0, stream.Position);
|
||||
else
|
||||
Assert.Equal(512, stream.Position);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ReadStringsFrom
|
||||
|
||||
[Fact]
|
||||
public void ReadStringsFrom_Null_Null()
|
||||
{
|
||||
Stream? stream = null;
|
||||
var actual = stream.ReadStringsFrom(0, 1, 3);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadStringsFrom_NonSeekable_Null()
|
||||
{
|
||||
Stream? stream = new NonSeekableStream();
|
||||
var actual = stream.ReadStringsFrom(0, 1, 3);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadStringsFrom_Empty_Null()
|
||||
{
|
||||
Stream? stream = new MemoryStream([]);
|
||||
var actual = stream.ReadStringsFrom(0, 1, 3);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(-1)]
|
||||
[InlineData(0)]
|
||||
[InlineData(2048)]
|
||||
public void ReadStringsFrom_InvalidLimit_Empty(int charLimit)
|
||||
{
|
||||
Stream? stream = new MemoryStream(new byte[1024]);
|
||||
var actual = stream.ReadStringsFrom(0, 1024, charLimit);
|
||||
Assert.NotNull(actual);
|
||||
Assert.Empty(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadStringsFrom_NoValidStrings_Empty()
|
||||
{
|
||||
Stream? stream = new MemoryStream(new byte[1024]);
|
||||
var actual = stream.ReadStringsFrom(0, 1024, 4);
|
||||
Assert.NotNull(actual);
|
||||
Assert.Empty(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadStringsFrom_AsciiStrings_Filled()
|
||||
{
|
||||
byte[]? bytes =
|
||||
[
|
||||
.. Encoding.ASCII.GetBytes("TEST"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.ASCII.GetBytes("TWO"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.ASCII.GetBytes("DATA"),
|
||||
.. new byte[] { 0x00 },
|
||||
];
|
||||
Stream? stream = new MemoryStream(bytes);
|
||||
var actual = stream.ReadStringsFrom(0, bytes.Length, 4);
|
||||
Assert.NotNull(actual);
|
||||
Assert.Equal(2, actual.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadStringsFrom_Latin1Strings_Filled()
|
||||
{
|
||||
byte[]? bytes =
|
||||
[
|
||||
.. Encoding.Latin1.GetBytes("TEST"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.Latin1.GetBytes("TWO"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.Latin1.GetBytes("DATA"),
|
||||
.. new byte[] { 0x00 },
|
||||
];
|
||||
Stream? stream = new MemoryStream(bytes);
|
||||
var actual = stream.ReadStringsFrom(0, bytes.Length, 4);
|
||||
Assert.NotNull(actual);
|
||||
Assert.Equal(2, actual.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadStringsFrom_UTF16_Filled()
|
||||
{
|
||||
byte[]? bytes =
|
||||
[
|
||||
.. Encoding.Unicode.GetBytes("TEST"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.Unicode.GetBytes("TWO"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.Unicode.GetBytes("DATA"),
|
||||
.. new byte[] { 0x00 },
|
||||
];
|
||||
Stream? stream = new MemoryStream(bytes);
|
||||
var actual = stream.ReadStringsFrom(0, bytes.Length, 4);
|
||||
Assert.NotNull(actual);
|
||||
Assert.Equal(2, actual.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadStringsFrom_Mixed_Filled()
|
||||
{
|
||||
byte[]? bytes =
|
||||
[
|
||||
.. Encoding.ASCII.GetBytes("TEST1"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.ASCII.GetBytes("TWO1"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.ASCII.GetBytes("DATA1"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.Latin1.GetBytes("TEST2"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.Latin1.GetBytes("TWO2"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.Latin1.GetBytes("DATA2"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.Unicode.GetBytes("TEST3"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.Unicode.GetBytes("TWO3"),
|
||||
.. new byte[] { 0x00 },
|
||||
.. Encoding.Unicode.GetBytes("DATA3"),
|
||||
.. new byte[] { 0x00 },
|
||||
];
|
||||
Stream? stream = new MemoryStream(bytes);
|
||||
var actual = stream.ReadStringsFrom(0, bytes.Length, 5);
|
||||
Assert.NotNull(actual);
|
||||
Assert.Equal(6, actual.Count);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region SeekIfPossible
|
||||
|
||||
[Fact]
|
||||
public void SeekIfPossible_NonSeekable_CurrentPosition()
|
||||
@@ -104,6 +313,92 @@ namespace SabreTools.IO.Test.Extensions
|
||||
Assert.Equal(13, actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(SeekOrigin.Begin)]
|
||||
[InlineData(SeekOrigin.Current)]
|
||||
[InlineData(SeekOrigin.End)]
|
||||
public void SeekIfPossible_NonSeekable_OriginTest(SeekOrigin origin)
|
||||
{
|
||||
var stream = new NonSeekableStream();
|
||||
long actual = stream.SeekIfPossible(0, origin);
|
||||
Assert.Equal(8, actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(SeekOrigin.Begin)]
|
||||
[InlineData(SeekOrigin.Current)]
|
||||
[InlineData(SeekOrigin.End)]
|
||||
public void SeekIfPossible_NonPositionable_OriginTest(SeekOrigin origin)
|
||||
{
|
||||
var stream = new NonPositionableStream();
|
||||
long actual = stream.SeekIfPossible(0, origin);
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(SeekOrigin.Begin)]
|
||||
[InlineData(SeekOrigin.Current)]
|
||||
[InlineData(SeekOrigin.End)]
|
||||
public void SeekIfPossible_HiddenNonSeekable_OriginTest(SeekOrigin origin)
|
||||
{
|
||||
var stream = new HiddenNonSeekableStream();
|
||||
long actual = stream.SeekIfPossible(0, origin);
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(SeekOrigin.Begin, 5, 5)]
|
||||
[InlineData(SeekOrigin.Current, 5, 7)]
|
||||
[InlineData(SeekOrigin.End, -5, 11)]
|
||||
public void SeekIfPossible_Seekable_OriginTest(SeekOrigin origin, long offset, long expected)
|
||||
{
|
||||
var stream = new MemoryStream(new byte[16], 0, 16, false, true);
|
||||
stream.Position = 2;
|
||||
|
||||
long actual = stream.SeekIfPossible(offset, origin);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region SegmentValid
|
||||
|
||||
[Fact]
|
||||
public void SegmentValid_Null_False()
|
||||
{
|
||||
Stream? stream = null;
|
||||
bool actual = stream.SegmentValid(0, 1);
|
||||
Assert.False(actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(-1)]
|
||||
[InlineData(2048)]
|
||||
public void SegmentValid_InvalidOffset_False(long offset)
|
||||
{
|
||||
Stream? stream = new MemoryStream(new byte[1024]);
|
||||
bool actual = stream.SegmentValid(offset, 1);
|
||||
Assert.False(actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(-1)]
|
||||
[InlineData(2048)]
|
||||
public void SegmentValid_InvalidLength_False(int length)
|
||||
{
|
||||
Stream? stream = new MemoryStream(new byte[1024]);
|
||||
bool actual = stream.SegmentValid(0, length);
|
||||
Assert.False(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SegmentValid_ValidSegment_True()
|
||||
{
|
||||
Stream? stream = new MemoryStream(new byte[1024]);
|
||||
bool actual = stream.SegmentValid(0, 1024);
|
||||
Assert.True(actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
#if NET7_0_OR_GREATER
|
||||
using System.Numerics;
|
||||
#endif
|
||||
using System.Text;
|
||||
using SabreTools.IO.Extensions;
|
||||
using Xunit;
|
||||
@@ -40,6 +38,17 @@ namespace SabreTools.IO.Test.Extensions
|
||||
ValidateBytes(expected, stream.GetBuffer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteByteBothEndianTest()
|
||||
{
|
||||
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
|
||||
byte[] expected = _bytes.Take(2).ToArray();
|
||||
|
||||
int offset = 0;
|
||||
stream.WriteBothEndian(_bytes.ReadByteBothEndian(ref offset));
|
||||
ValidateBytes(expected, stream.GetBuffer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteBytesTest()
|
||||
{
|
||||
@@ -69,6 +78,17 @@ namespace SabreTools.IO.Test.Extensions
|
||||
ValidateBytes(expected, stream.GetBuffer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteSByteBothEndianTest()
|
||||
{
|
||||
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
|
||||
byte[] expected = _bytes.Take(2).ToArray();
|
||||
|
||||
int offset = 0;
|
||||
stream.WriteBothEndian(_bytes.ReadSByteBothEndian(ref offset));
|
||||
ValidateBytes(expected, stream.GetBuffer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteCharTest()
|
||||
{
|
||||
@@ -108,6 +128,17 @@ namespace SabreTools.IO.Test.Extensions
|
||||
ValidateBytes(expected, stream.GetBuffer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteInt16BothEndianTest()
|
||||
{
|
||||
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
|
||||
byte[] expected = _bytes.Take(4).ToArray();
|
||||
|
||||
int offset = 0;
|
||||
stream.WriteBothEndian(_bytes.ReadInt16BothEndian(ref offset));
|
||||
ValidateBytes(expected, stream.GetBuffer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteUInt16Test()
|
||||
{
|
||||
@@ -128,7 +159,17 @@ namespace SabreTools.IO.Test.Extensions
|
||||
ValidateBytes(expected, stream.GetBuffer());
|
||||
}
|
||||
|
||||
#if NET6_0_OR_GREATER
|
||||
[Fact]
|
||||
public void WriteUInt16BothEndianTest()
|
||||
{
|
||||
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
|
||||
byte[] expected = _bytes.Take(4).ToArray();
|
||||
|
||||
int offset = 0;
|
||||
stream.WriteBothEndian(_bytes.ReadUInt16BothEndian(ref offset));
|
||||
ValidateBytes(expected, stream.GetBuffer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteHalfTest()
|
||||
{
|
||||
@@ -148,7 +189,6 @@ namespace SabreTools.IO.Test.Extensions
|
||||
Assert.True(write);
|
||||
ValidateBytes(expected, stream.GetBuffer());
|
||||
}
|
||||
#endif
|
||||
|
||||
[Fact]
|
||||
public void WriteInt24Test()
|
||||
@@ -210,6 +250,17 @@ namespace SabreTools.IO.Test.Extensions
|
||||
ValidateBytes(expected, stream.GetBuffer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteInt32BothEndianTest()
|
||||
{
|
||||
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
|
||||
byte[] expected = _bytes.Take(8).ToArray();
|
||||
|
||||
int offset = 0;
|
||||
stream.WriteBothEndian(_bytes.ReadInt32BothEndian(ref offset));
|
||||
ValidateBytes(expected, stream.GetBuffer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteUInt32Test()
|
||||
{
|
||||
@@ -230,6 +281,17 @@ namespace SabreTools.IO.Test.Extensions
|
||||
ValidateBytes(expected, stream.GetBuffer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteUInt32BothEndianTest()
|
||||
{
|
||||
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
|
||||
byte[] expected = _bytes.Take(8).ToArray();
|
||||
|
||||
int offset = 0;
|
||||
stream.WriteBothEndian(_bytes.ReadUInt32BothEndian(ref offset));
|
||||
ValidateBytes(expected, stream.GetBuffer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteSingleTest()
|
||||
{
|
||||
@@ -310,6 +372,17 @@ namespace SabreTools.IO.Test.Extensions
|
||||
ValidateBytes(expected, stream.GetBuffer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteInt64BothEndianTest()
|
||||
{
|
||||
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
|
||||
byte[] expected = _bytes.Take(16).ToArray();
|
||||
|
||||
int offset = 0;
|
||||
stream.WriteBothEndian(_bytes.ReadInt64BothEndian(ref offset));
|
||||
ValidateBytes(expected, stream.GetBuffer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteUInt64Test()
|
||||
{
|
||||
@@ -330,6 +403,17 @@ namespace SabreTools.IO.Test.Extensions
|
||||
ValidateBytes(expected, stream.GetBuffer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteUInt64BothEndianTest()
|
||||
{
|
||||
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
|
||||
byte[] expected = _bytes.Take(16).ToArray();
|
||||
|
||||
int offset = 0;
|
||||
stream.WriteBothEndian(_bytes.ReadUInt64BothEndian(ref offset));
|
||||
ValidateBytes(expected, stream.GetBuffer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteDoubleTest()
|
||||
{
|
||||
@@ -390,7 +474,6 @@ namespace SabreTools.IO.Test.Extensions
|
||||
ValidateBytes(expected, stream.GetBuffer());
|
||||
}
|
||||
|
||||
#if NET7_0_OR_GREATER
|
||||
[Fact]
|
||||
public void WriteInt128Test()
|
||||
{
|
||||
@@ -430,7 +513,6 @@ namespace SabreTools.IO.Test.Extensions
|
||||
Assert.True(write);
|
||||
ValidateBytes(expected, stream.GetBuffer());
|
||||
}
|
||||
#endif
|
||||
|
||||
[Fact]
|
||||
public void WriteNullTerminatedAnsiStringTest()
|
||||
@@ -507,15 +589,12 @@ namespace SabreTools.IO.Test.Extensions
|
||||
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));
|
||||
@@ -527,7 +606,6 @@ namespace SabreTools.IO.Test.Extensions
|
||||
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);
|
||||
|
||||
76
SabreTools.IO.Test/Extensions/StringExtensionsTests.cs
Normal file
76
SabreTools.IO.Test/Extensions/StringExtensionsTests.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
using SabreTools.IO.Extensions;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.IO.Test.Extensions
|
||||
{
|
||||
public class StringExtensionsTests
|
||||
{
|
||||
#region OptionalContains
|
||||
|
||||
[Theory]
|
||||
[InlineData(null, "ANY", false)]
|
||||
[InlineData("", "ANY", false)]
|
||||
[InlineData("ANY", "ANY", true)]
|
||||
[InlineData("ANYTHING", "ANY", true)]
|
||||
[InlineData("THING", "ANY", false)]
|
||||
[InlineData("THINGANY", "ANY", true)]
|
||||
public void OptionalContainsTest(string? haystack, string needle, bool expected)
|
||||
{
|
||||
bool actual = haystack.OptionalContains(needle);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region OptionalEndsWith
|
||||
|
||||
[Theory]
|
||||
[InlineData(null, "ANY", false)]
|
||||
[InlineData("", "ANY", false)]
|
||||
[InlineData("ANY", "ANY", true)]
|
||||
[InlineData("ANYTHING", "ANY", false)]
|
||||
[InlineData("THING", "ANY", false)]
|
||||
[InlineData("THINGANY", "ANY", true)]
|
||||
public void OptionalEndsWithTest(string? haystack, string needle, bool expected)
|
||||
{
|
||||
bool actual = haystack.OptionalEndsWith(needle);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region OptionalEquals
|
||||
|
||||
[Theory]
|
||||
[InlineData(null, "ANY", false)]
|
||||
[InlineData("", "ANY", false)]
|
||||
[InlineData("ANY", "ANY", true)]
|
||||
[InlineData("ANYTHING", "ANY", false)]
|
||||
[InlineData("THING", "ANY", false)]
|
||||
[InlineData("THINGANY", "ANY", false)]
|
||||
public void OptionalEqualsTest(string? haystack, string needle, bool expected)
|
||||
{
|
||||
bool actual = haystack.OptionalEquals(needle);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region OptionalStartsWith
|
||||
|
||||
[Theory]
|
||||
[InlineData(null, "ANY", false)]
|
||||
[InlineData("", "ANY", false)]
|
||||
[InlineData("ANY", "ANY", true)]
|
||||
[InlineData("ANYTHING", "ANY", true)]
|
||||
[InlineData("THING", "ANY", false)]
|
||||
[InlineData("THINGANY", "ANY", false)]
|
||||
public void OptionalStartsWithTest(string? haystack, string needle, bool expected)
|
||||
{
|
||||
bool actual = haystack.OptionalStartsWith(needle);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
324
SabreTools.IO.Test/MatchUtilTests.cs
Normal file
324
SabreTools.IO.Test/MatchUtilTests.cs
Normal file
@@ -0,0 +1,324 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SabreTools.IO.Matching;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.IO.Test
|
||||
{
|
||||
public class MatchUtilTests
|
||||
{
|
||||
#region Array
|
||||
|
||||
[Fact]
|
||||
public void ArrayGetAllMatches_NullStack_NoMatches()
|
||||
{
|
||||
byte[]? stack = null;
|
||||
List<ContentMatchSet> matchSets = [new ContentMatchSet(new byte[1], "name")];
|
||||
var matches = MatchUtil.GetAllMatches("file", stack, matchSets);
|
||||
Assert.Empty(matches);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArrayGetAllMatches_EmptyStack_NoMatches()
|
||||
{
|
||||
byte[] stack = [];
|
||||
List<ContentMatchSet> matchSets = [new ContentMatchSet(new byte[1], "name")];
|
||||
var matches = MatchUtil.GetAllMatches("file", stack, matchSets);
|
||||
Assert.Empty(matches);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArrayGetAllMatches_EmptyMatchSets_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
List<ContentMatchSet> matchSets = [];
|
||||
var matches = MatchUtil.GetAllMatches("file", stack, matchSets);
|
||||
Assert.Empty(matches);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArrayGetAllMatches_Matching_Matches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
List<ContentMatchSet> matchSets = [new ContentMatchSet(new byte[] { 0x01 }, "name")];
|
||||
var matches = MatchUtil.GetAllMatches("file", stack, matchSets);
|
||||
string setName = Assert.Single(matches);
|
||||
Assert.Equal("name", setName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArrayGetAllMatches_PartialMatchingAny_Matches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
List<ContentMatchSet> matchSets =
|
||||
[
|
||||
new ContentMatchSet([
|
||||
new byte[] { 0x00 },
|
||||
new ContentMatch([0x01]),
|
||||
], "name")
|
||||
];
|
||||
var matches = MatchUtil.GetAllMatches("file", stack, matchSets, any: true);
|
||||
string setName = Assert.Single(matches);
|
||||
Assert.Equal("name", setName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArrayGetAllMatches_PartialMatchingAll_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
List<ContentMatchSet> matchSets =
|
||||
[
|
||||
new ContentMatchSet([
|
||||
new byte[] { 0x00 },
|
||||
new ContentMatch([0x01]),
|
||||
], "name")
|
||||
];
|
||||
var matches = MatchUtil.GetAllMatches("file", stack, matchSets, any: false);
|
||||
Assert.Empty(matches);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArrayGetFirstMatch_NullStack_NoMatches()
|
||||
{
|
||||
byte[]? stack = null;
|
||||
List<ContentMatchSet> matchSets = [new ContentMatchSet(new byte[1], "name")];
|
||||
string? match = MatchUtil.GetFirstMatch("file", stack, matchSets);
|
||||
Assert.Null(match);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArrayGetFirstMatch_EmptyStack_NoMatches()
|
||||
{
|
||||
byte[] stack = [];
|
||||
List<ContentMatchSet> matchSets = [new ContentMatchSet(new byte[1], "name")];
|
||||
string? match = MatchUtil.GetFirstMatch("file", stack, matchSets);
|
||||
Assert.Null(match);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArrayGetFirstMatch_EmptyMatchSets_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
List<ContentMatchSet> matchSets = [];
|
||||
string? match = MatchUtil.GetFirstMatch("file", stack, matchSets);
|
||||
Assert.Null(match);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArrayGetFirstMatch_Matching_Matches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
List<ContentMatchSet> matchSets = [new ContentMatchSet(new byte[] { 0x01 }, "name")];
|
||||
string? setName = MatchUtil.GetFirstMatch("file", stack, matchSets);
|
||||
Assert.Equal("name", setName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArrayGetFirstMatch_PartialMatchingAny_Matches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
List<ContentMatchSet> matchSets =
|
||||
[
|
||||
new ContentMatchSet([
|
||||
new byte[] { 0x00 },
|
||||
new ContentMatch([0x01]),
|
||||
], "name")
|
||||
];
|
||||
string? setName = MatchUtil.GetFirstMatch("file", stack, matchSets, any: true);
|
||||
Assert.Equal("name", setName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArrayGetFirstMatch_PartialMatchingAll_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
List<ContentMatchSet> matchSets =
|
||||
[
|
||||
new ContentMatchSet([
|
||||
new byte[] { 0x00 },
|
||||
new ContentMatch([0x01]),
|
||||
], "name")
|
||||
];
|
||||
string? setName = MatchUtil.GetFirstMatch("file", stack, matchSets, any: false);
|
||||
Assert.Null(setName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExactSizeArrayMatch()
|
||||
{
|
||||
byte[] source = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07];
|
||||
byte?[] check = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07];
|
||||
string expected = "match";
|
||||
|
||||
var matchers = new List<ContentMatchSet>
|
||||
{
|
||||
new(check, expected),
|
||||
};
|
||||
|
||||
string? actual = MatchUtil.GetFirstMatch("testfile", source, matchers, any: false);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Stream
|
||||
|
||||
[Fact]
|
||||
public void StreamGetAllMatches_NullStack_NoMatches()
|
||||
{
|
||||
Stream? stack = null;
|
||||
List<ContentMatchSet> matchSets = [new ContentMatchSet(new byte[1], "name")];
|
||||
var matches = MatchUtil.GetAllMatches("file", stack, matchSets);
|
||||
Assert.Empty(matches);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StreamGetAllMatches_EmptyStack_NoMatches()
|
||||
{
|
||||
Stream stack = new MemoryStream();
|
||||
List<ContentMatchSet> matchSets = [new ContentMatchSet(new byte[1], "name")];
|
||||
var matches = MatchUtil.GetAllMatches("file", stack, matchSets);
|
||||
Assert.Empty(matches);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StreamGetAllMatches_EmptyMatchSets_NoMatches()
|
||||
{
|
||||
Stream stack = new MemoryStream([0x01]);
|
||||
List<ContentMatchSet> matchSets = [];
|
||||
var matches = MatchUtil.GetAllMatches("file", stack, matchSets);
|
||||
Assert.Empty(matches);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StreamGetAllMatches_Matching_Matches()
|
||||
{
|
||||
Stream stack = new MemoryStream([0x01]);
|
||||
List<ContentMatchSet> matchSets = [new ContentMatchSet(new byte[] { 0x01 }, "name")];
|
||||
var matches = MatchUtil.GetAllMatches("file", stack, matchSets);
|
||||
string setName = Assert.Single(matches);
|
||||
Assert.Equal("name", setName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StreamGetAllMatches_PartialMatchingAny_Matches()
|
||||
{
|
||||
Stream stack = new MemoryStream([0x01]);
|
||||
List<ContentMatchSet> matchSets =
|
||||
[
|
||||
new ContentMatchSet([
|
||||
new byte[] { 0x00 },
|
||||
new ContentMatch([0x01]),
|
||||
], "name")
|
||||
];
|
||||
var matches = MatchUtil.GetAllMatches("file", stack, matchSets, any: true);
|
||||
string setName = Assert.Single(matches);
|
||||
Assert.Equal("name", setName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StreamGetAllMatches_PartialMatchingAll_NoMatches()
|
||||
{
|
||||
Stream stack = new MemoryStream([0x01]);
|
||||
List<ContentMatchSet> matchSets =
|
||||
[
|
||||
new ContentMatchSet([
|
||||
new byte[] { 0x00 },
|
||||
new ContentMatch([0x01]),
|
||||
], "name")
|
||||
];
|
||||
var matches = MatchUtil.GetAllMatches("file", stack, matchSets, any: false);
|
||||
Assert.Empty(matches);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StreamGetFirstMatch_NullStack_NoMatches()
|
||||
{
|
||||
Stream? stack = null;
|
||||
List<ContentMatchSet> matchSets = [new ContentMatchSet(new byte[1], "name")];
|
||||
string? match = MatchUtil.GetFirstMatch("file", stack, matchSets);
|
||||
Assert.Null(match);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StreamGetFirstMatch_EmptyStack_NoMatches()
|
||||
{
|
||||
Stream stack = new MemoryStream();
|
||||
List<ContentMatchSet> matchSets = [new ContentMatchSet(new byte[1], "name")];
|
||||
string? match = MatchUtil.GetFirstMatch("file", stack, matchSets);
|
||||
Assert.Null(match);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StreamGetFirstMatch_EmptyMatchSets_NoMatches()
|
||||
{
|
||||
Stream stack = new MemoryStream([0x01]);
|
||||
List<ContentMatchSet> matchSets = [];
|
||||
string? match = MatchUtil.GetFirstMatch("file", stack, matchSets);
|
||||
Assert.Null(match);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StreamGetFirstMatch_Matching_Matches()
|
||||
{
|
||||
Stream stack = new MemoryStream([0x01]);
|
||||
List<ContentMatchSet> matchSets = [new ContentMatchSet(new byte[] { 0x01 }, "name")];
|
||||
string? setName = MatchUtil.GetFirstMatch("file", stack, matchSets);
|
||||
Assert.Equal("name", setName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StreamGetFirstMatch_PartialMatchingAny_Matches()
|
||||
{
|
||||
Stream stack = new MemoryStream([0x01]);
|
||||
List<ContentMatchSet> matchSets =
|
||||
[
|
||||
new ContentMatchSet([
|
||||
new byte[] { 0x00 },
|
||||
new ContentMatch([0x01]),
|
||||
], "name")
|
||||
];
|
||||
string? setName = MatchUtil.GetFirstMatch("file", stack, matchSets, any: true);
|
||||
Assert.Equal("name", setName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StreamGetFirstMatch_PartialMatchingAll_NoMatches()
|
||||
{
|
||||
Stream stack = new MemoryStream([0x01]);
|
||||
List<ContentMatchSet> matchSets =
|
||||
[
|
||||
new ContentMatchSet([
|
||||
new byte[] { 0x00 },
|
||||
new ContentMatch([0x01]),
|
||||
], "name")
|
||||
];
|
||||
string? setName = MatchUtil.GetFirstMatch("file", stack, matchSets, any: false);
|
||||
Assert.Null(setName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExactSizeStreamMatch()
|
||||
{
|
||||
byte[] source = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07];
|
||||
var stream = new MemoryStream(source);
|
||||
|
||||
byte?[] check = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07];
|
||||
string expected = "match";
|
||||
|
||||
var matchers = new List<ContentMatchSet>
|
||||
{
|
||||
new(check, expected),
|
||||
};
|
||||
|
||||
string? actual = MatchUtil.GetFirstMatch("testfile", stream, matchers, any: false);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Path
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
202
SabreTools.IO.Test/Matching/ContentMatchSetTests.cs
Normal file
202
SabreTools.IO.Test/Matching/ContentMatchSetTests.cs
Normal file
@@ -0,0 +1,202 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SabreTools.IO.Matching;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.IO.Test.Matching
|
||||
{
|
||||
public class ContentMatchSetTests
|
||||
{
|
||||
[Fact]
|
||||
public void InvalidNeedle_ThrowsException()
|
||||
{
|
||||
Assert.Throws<InvalidDataException>(() => new ContentMatchSet(Array.Empty<byte>(), "name"));
|
||||
Assert.Throws<InvalidDataException>(() => new ContentMatchSet(Array.Empty<byte>(), ArrayVersionMock, "name"));
|
||||
Assert.Throws<InvalidDataException>(() => new ContentMatchSet(Array.Empty<byte>(), StreamVersionMock, "name"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidNeedles_ThrowsException()
|
||||
{
|
||||
Assert.Throws<InvalidDataException>(() => new ContentMatchSet([], "name"));
|
||||
Assert.Throws<InvalidDataException>(() => new ContentMatchSet([], ArrayVersionMock, "name"));
|
||||
Assert.Throws<InvalidDataException>(() => new ContentMatchSet([], StreamVersionMock, "name"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GenericConstructor_NoDelegates()
|
||||
{
|
||||
var needles = new List<ContentMatch> { new byte[] { 0x01, 0x02, 0x03, 0x04 } };
|
||||
var cms = new ContentMatchSet(needles, "name");
|
||||
Assert.Null(cms.GetArrayVersion);
|
||||
Assert.Null(cms.GetStreamVersion);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArrayConstructor_SingleDelegate()
|
||||
{
|
||||
var needles = new List<ContentMatch> { new byte[] { 0x01, 0x02, 0x03, 0x04 } };
|
||||
var cms = new ContentMatchSet(needles, ArrayVersionMock, "name");
|
||||
Assert.NotNull(cms.GetArrayVersion);
|
||||
Assert.Null(cms.GetStreamVersion);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StreamConstructor_SingleDelegate()
|
||||
{
|
||||
var needles = new List<ContentMatch> { new byte[] { 0x01, 0x02, 0x03, 0x04 } };
|
||||
var cms = new ContentMatchSet(needles, StreamVersionMock, "name");
|
||||
Assert.Null(cms.GetArrayVersion);
|
||||
Assert.NotNull(cms.GetStreamVersion);
|
||||
}
|
||||
|
||||
#region Array
|
||||
|
||||
[Fact]
|
||||
public void MatchesAll_NullArray_NoMatches()
|
||||
{
|
||||
var cms = new ContentMatchSet(new byte[] { 0x01, 0x02, 0x03, 0x04 }, "name");
|
||||
var actual = cms.MatchesAll((byte[]?)null);
|
||||
Assert.Empty(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAll_EmptyArray_NoMatches()
|
||||
{
|
||||
var cms = new ContentMatchSet(new byte[] { 0x01, 0x02, 0x03, 0x04 }, "name");
|
||||
var actual = cms.MatchesAll([]);
|
||||
Assert.Empty(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAll_MatchingArray_Matches()
|
||||
{
|
||||
var cms = new ContentMatchSet(new byte[] { 0x01, 0x02, 0x03, 0x04 }, "name");
|
||||
var actual = cms.MatchesAll([0x01, 0x02, 0x03, 0x04]);
|
||||
int position = Assert.Single(actual);
|
||||
Assert.Equal(0, position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAll_MismatchedArray_NoMatches()
|
||||
{
|
||||
var cms = new ContentMatchSet(new byte[] { 0x01, 0x02, 0x03, 0x04 }, "name");
|
||||
var actual = cms.MatchesAll([0x01, 0x03]);
|
||||
Assert.Empty(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAny_NullArray_NoMatches()
|
||||
{
|
||||
var cms = new ContentMatchSet(new byte[] { 0x01, 0x02, 0x03, 0x04 }, "name");
|
||||
int actual = cms.MatchesAny((byte[]?)null);
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAny_EmptyArray_NoMatches()
|
||||
{
|
||||
var cms = new ContentMatchSet(new byte[] { 0x01, 0x02, 0x03, 0x04 }, "name");
|
||||
int actual = cms.MatchesAny([]);
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAny_MatchingArray_Matches()
|
||||
{
|
||||
var cms = new ContentMatchSet(new byte[] { 0x01, 0x02, 0x03, 0x04 }, "name");
|
||||
int actual = cms.MatchesAny([0x01, 0x02, 0x03, 0x04]);
|
||||
Assert.Equal(0, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAny_MismatchedArray_NoMatches()
|
||||
{
|
||||
var cms = new ContentMatchSet(new byte[] { 0x01, 0x02, 0x03, 0x04 }, "name");
|
||||
int actual = cms.MatchesAny([0x01, 0x03]);
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Stream
|
||||
|
||||
[Fact]
|
||||
public void MatchesAll_NullStream_NoMatches()
|
||||
{
|
||||
var cms = new ContentMatchSet(new byte[] { 0x01, 0x02, 0x03, 0x04 }, "name");
|
||||
var actual = cms.MatchesAll((Stream?)null);
|
||||
Assert.Empty(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAll_EmptyStream_NoMatches()
|
||||
{
|
||||
var cms = new ContentMatchSet(new byte[] { 0x01, 0x02, 0x03, 0x04 }, "name");
|
||||
var actual = cms.MatchesAll(new MemoryStream());
|
||||
Assert.Empty(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAll_MatchingStream_Matches()
|
||||
{
|
||||
var cms = new ContentMatchSet(new byte[] { 0x01, 0x02, 0x03, 0x04 }, "name");
|
||||
var actual = cms.MatchesAll(new MemoryStream([0x01, 0x02, 0x03, 0x04]));
|
||||
int position = Assert.Single(actual);
|
||||
Assert.Equal(0, position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAll_MismatchedStream_NoMatches()
|
||||
{
|
||||
var cms = new ContentMatchSet(new byte[] { 0x01, 0x02, 0x03, 0x04 }, "name");
|
||||
var actual = cms.MatchesAll([0x01, 0x03]);
|
||||
Assert.Empty(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAny_NullStream_NoMatches()
|
||||
{
|
||||
var cms = new ContentMatchSet(new byte[] { 0x01, 0x02, 0x03, 0x04 }, "name");
|
||||
int actual = cms.MatchesAny((Stream?)null);
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAny_EmptyStream_NoMatches()
|
||||
{
|
||||
var cms = new ContentMatchSet(new byte[] { 0x01, 0x02, 0x03, 0x04 }, "name");
|
||||
int actual = cms.MatchesAny(new MemoryStream());
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAny_MatchingStream_Matches()
|
||||
{
|
||||
var cms = new ContentMatchSet(new byte[] { 0x01, 0x02, 0x03, 0x04 }, "name");
|
||||
int actual = cms.MatchesAny(new MemoryStream([0x01, 0x02, 0x03, 0x04]));
|
||||
Assert.Equal(0, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAny_MismatchedStream_NoMatches()
|
||||
{
|
||||
var cms = new ContentMatchSet(new byte[] { 0x01, 0x02, 0x03, 0x04 }, "name");
|
||||
int actual = cms.MatchesAny([0x01, 0x03]);
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Mock Delegates
|
||||
|
||||
/// <inheritdoc cref="GetArrayVersion"/>
|
||||
private static string? ArrayVersionMock(string path, byte[]? content, List<int> positions) => null;
|
||||
|
||||
/// <inheritdoc cref="GetStreamVersion"/>
|
||||
private static string? StreamVersionMock(string path, Stream? content, List<int> positions) => null;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
271
SabreTools.IO.Test/Matching/ContentMatchTests.cs
Normal file
271
SabreTools.IO.Test/Matching/ContentMatchTests.cs
Normal file
@@ -0,0 +1,271 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SabreTools.IO.Matching;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.IO.Test.Matching
|
||||
{
|
||||
public class ContentMatchTests
|
||||
{
|
||||
[Fact]
|
||||
public void InvalidNeedle_ThrowsException()
|
||||
{
|
||||
Assert.Throws<InvalidDataException>(() => new ContentMatch(Array.Empty<byte>()));
|
||||
Assert.Throws<InvalidDataException>(() => new ContentMatch(Array.Empty<byte?>()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidStart_ThrowsException()
|
||||
{
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => new ContentMatch(new byte[1], start: -1));
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => new ContentMatch(new byte?[1], start: -1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidEnd_ThrowsException()
|
||||
{
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => new ContentMatch(new byte[1], end: -2));
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => new ContentMatch(new byte?[1], end: -2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ImplicitOperatorArray_Success()
|
||||
{
|
||||
byte[] needle = [0x01, 0x02, 0x03, 0x04];
|
||||
var cm = (ContentMatch)needle;
|
||||
Assert.NotNull(cm);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ImplicitOperatorNullableArray_Success()
|
||||
{
|
||||
byte?[] needle = [0x01, 0x02, 0x03, 0x04];
|
||||
var cm = (ContentMatch)needle;
|
||||
Assert.NotNull(cm);
|
||||
}
|
||||
|
||||
#region Byte Array
|
||||
|
||||
[Fact]
|
||||
public void NullArray_NoMatch()
|
||||
{
|
||||
var cm = new ContentMatch(new byte?[1]);
|
||||
int actual = cm.Match((byte[]?)null);
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyArray_NoMatch()
|
||||
{
|
||||
var cm = new ContentMatch(new byte?[1]);
|
||||
int actual = cm.Match([]);
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LargerNeedleArray_NoMatch()
|
||||
{
|
||||
var cm = new ContentMatch(new byte?[2]);
|
||||
int actual = cm.Match(new byte[1]);
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualLengthMatchingArray_Match()
|
||||
{
|
||||
byte[] needle = [0x01, 0x02, 0x03, 0x04];
|
||||
var cm = new ContentMatch(needle);
|
||||
|
||||
int actual = cm.Match(needle);
|
||||
Assert.Equal(0, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualLengthMatchingArrayReverse_Match()
|
||||
{
|
||||
byte[] needle = [0x01, 0x02, 0x03, 0x04];
|
||||
var cm = new ContentMatch(needle);
|
||||
|
||||
int actual = cm.Match(needle, reverse: true);
|
||||
Assert.Equal(0, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualLengthMismatchedArray_NoMatch()
|
||||
{
|
||||
byte[] needle = [0x01, 0x02, 0x03, 0x04];
|
||||
var cm = new ContentMatch(needle);
|
||||
|
||||
int actual = cm.Match(new byte[4]);
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualLengthMismatchedArrayReverse_NoMatch()
|
||||
{
|
||||
byte[] needle = [0x01, 0x02, 0x03, 0x04];
|
||||
var cm = new ContentMatch(needle);
|
||||
|
||||
int actual = cm.Match(new byte[4], reverse: true);
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InequalLengthMatchingArray_Match()
|
||||
{
|
||||
byte[] stack = [0x01, 0x02, 0x03, 0x04];
|
||||
byte[] needle = [0x02, 0x03];
|
||||
var cm = new ContentMatch(needle);
|
||||
|
||||
int actual = cm.Match(stack);
|
||||
Assert.Equal(1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InequalLengthMatchingArrayReverse_Match()
|
||||
{
|
||||
byte[] stack = [0x01, 0x02, 0x03, 0x04];
|
||||
byte[] needle = [0x02, 0x03];
|
||||
var cm = new ContentMatch(needle);
|
||||
|
||||
int actual = cm.Match(stack, reverse: true);
|
||||
Assert.Equal(1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InequalLengthMismatchedArray_NoMatch()
|
||||
{
|
||||
byte[] stack = [0x01, 0x02, 0x03, 0x04];
|
||||
byte[] needle = [0x02, 0x04];
|
||||
var cm = new ContentMatch(needle);
|
||||
|
||||
int actual = cm.Match(stack);
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InequalLengthMismatchedArrayReverse_NoMatch()
|
||||
{
|
||||
byte[] stack = [0x01, 0x02, 0x03, 0x04];
|
||||
byte[] needle = [0x02, 0x04];
|
||||
var cm = new ContentMatch(needle);
|
||||
|
||||
int actual = cm.Match(stack, reverse: true);
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Stream
|
||||
|
||||
[Fact]
|
||||
public void NullStream_NoMatch()
|
||||
{
|
||||
var cm = new ContentMatch(new byte?[1]);
|
||||
int actual = cm.Match((Stream?)null);
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyStream_NoMatch()
|
||||
{
|
||||
var cm = new ContentMatch(new byte?[1]);
|
||||
int actual = cm.Match(new MemoryStream());
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LargerNeedleStream_NoMatch()
|
||||
{
|
||||
var cm = new ContentMatch(new byte?[2]);
|
||||
int actual = cm.Match(new MemoryStream(new byte[1]));
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualLengthMatchingStream_Match()
|
||||
{
|
||||
byte[] needle = [0x01, 0x02, 0x03, 0x04];
|
||||
var cm = new ContentMatch(needle);
|
||||
|
||||
int actual = cm.Match(new MemoryStream(needle));
|
||||
Assert.Equal(0, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualLengthMatchingStreamReverse_Match()
|
||||
{
|
||||
byte[] needle = [0x01, 0x02, 0x03, 0x04];
|
||||
var cm = new ContentMatch(needle);
|
||||
|
||||
int actual = cm.Match(new MemoryStream(needle), reverse: true);
|
||||
Assert.Equal(0, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualLengthMismatchedStream_NoMatch()
|
||||
{
|
||||
byte[] needle = [0x01, 0x02, 0x03, 0x04];
|
||||
var cm = new ContentMatch(needle);
|
||||
|
||||
int actual = cm.Match(new MemoryStream(new byte[4]));
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualLengthMismatchedStreamReverse_NoMatch()
|
||||
{
|
||||
byte[] needle = [0x01, 0x02, 0x03, 0x04];
|
||||
var cm = new ContentMatch(needle);
|
||||
|
||||
int actual = cm.Match(new MemoryStream(new byte[4]), reverse: true);
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InequalLengthMatchingStream_Match()
|
||||
{
|
||||
Stream stack = new MemoryStream([0x01, 0x02, 0x03, 0x04]);
|
||||
byte[] needle = [0x02, 0x03];
|
||||
var cm = new ContentMatch(needle);
|
||||
|
||||
int actual = cm.Match(stack);
|
||||
Assert.Equal(1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InequalLengthMatchingStreamReverse_Match()
|
||||
{
|
||||
Stream stack = new MemoryStream([0x01, 0x02, 0x03, 0x04]);
|
||||
byte[] needle = [0x02, 0x03];
|
||||
var cm = new ContentMatch(needle);
|
||||
|
||||
int actual = cm.Match(stack, reverse: true);
|
||||
Assert.Equal(1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InequalLengthMismatchedStream_NoMatch()
|
||||
{
|
||||
Stream stack = new MemoryStream([0x01, 0x02, 0x03, 0x04]);
|
||||
byte[] needle = [0x02, 0x04];
|
||||
var cm = new ContentMatch(needle);
|
||||
|
||||
int actual = cm.Match(stack);
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InequalLengthMismatchedStreamReverse_NoMatch()
|
||||
{
|
||||
Stream stack = new MemoryStream([0x01, 0x02, 0x03, 0x04]);
|
||||
byte[] needle = [0x02, 0x04];
|
||||
var cm = new ContentMatch(needle);
|
||||
|
||||
int actual = cm.Match(stack, reverse: true);
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
22
SabreTools.IO.Test/Matching/FilePathMatchTests.cs
Normal file
22
SabreTools.IO.Test/Matching/FilePathMatchTests.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System.IO;
|
||||
using SabreTools.IO.Matching;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.IO.Test.Matching
|
||||
{
|
||||
/// <remarks>
|
||||
/// All other test cases are covered by <see cref="PathMatchTests"/>
|
||||
/// </remarks>
|
||||
public class FilePathMatchTests
|
||||
{
|
||||
[Fact]
|
||||
public void ConstructorFormatsNeedle()
|
||||
{
|
||||
string needle = "test";
|
||||
string expected = $"{Path.DirectorySeparatorChar}{needle}";
|
||||
|
||||
var fpm = new FilePathMatch(needle);
|
||||
Assert.Equal(expected, fpm.Needle);
|
||||
}
|
||||
}
|
||||
}
|
||||
186
SabreTools.IO.Test/Matching/PathMatchSetTests.cs
Normal file
186
SabreTools.IO.Test/Matching/PathMatchSetTests.cs
Normal file
@@ -0,0 +1,186 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SabreTools.IO.Matching;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.IO.Test.Matching
|
||||
{
|
||||
public class PathMatchSetTests
|
||||
{
|
||||
[Fact]
|
||||
public void InvalidNeedle_ThrowsException()
|
||||
{
|
||||
Assert.Throws<InvalidDataException>(() => new PathMatchSet(string.Empty, "name"));
|
||||
Assert.Throws<InvalidDataException>(() => new PathMatchSet(string.Empty, PathVersionMock, "name"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidNeedles_ThrowsException()
|
||||
{
|
||||
Assert.Throws<InvalidDataException>(() => new PathMatchSet([], "name"));
|
||||
Assert.Throws<InvalidDataException>(() => new PathMatchSet([], PathVersionMock, "name"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GenericConstructor_NoDelegates()
|
||||
{
|
||||
var needles = new List<PathMatch> { "test" };
|
||||
var cms = new PathMatchSet(needles, "name");
|
||||
Assert.Null(cms.GetVersion);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VersionConstructor_SingleDelegate()
|
||||
{
|
||||
var needles = new List<PathMatch> { "test" };
|
||||
var cms = new PathMatchSet(needles, PathVersionMock, "name");
|
||||
Assert.NotNull(cms.GetVersion);
|
||||
}
|
||||
|
||||
#region Array
|
||||
|
||||
[Fact]
|
||||
public void MatchesAll_NullArray_NoMatches()
|
||||
{
|
||||
var cms = new PathMatchSet("test", "name");
|
||||
var actual = cms.MatchesAll((string[]?)null);
|
||||
Assert.Empty(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAll_EmptyArray_NoMatches()
|
||||
{
|
||||
var cms = new PathMatchSet("test", "name");
|
||||
var actual = cms.MatchesAll(Array.Empty<string>());
|
||||
Assert.Empty(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAll_MatchingArray_Matches()
|
||||
{
|
||||
var cms = new PathMatchSet("test", "name");
|
||||
var actual = cms.MatchesAll(new string[] { "test" });
|
||||
string path = Assert.Single(actual);
|
||||
Assert.Equal("test", path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAll_MismatchedArray_NoMatches()
|
||||
{
|
||||
var cms = new PathMatchSet("test", "name");
|
||||
var actual = cms.MatchesAll(new string[] { "not" });
|
||||
Assert.Empty(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAny_NullArray_NoMatches()
|
||||
{
|
||||
var cms = new PathMatchSet("test", "name");
|
||||
string? actual = cms.MatchesAny((string[]?)null);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAny_EmptyArray_NoMatches()
|
||||
{
|
||||
var cms = new PathMatchSet("test", "name");
|
||||
string? actual = cms.MatchesAny(Array.Empty<string>());
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAny_MatchingArray_Matches()
|
||||
{
|
||||
var cms = new PathMatchSet("test", "name");
|
||||
string? actual = cms.MatchesAny(new string[] { "test" });
|
||||
Assert.Equal("test", actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAny_MismatchedArray_NoMatches()
|
||||
{
|
||||
var cms = new PathMatchSet("test", "name");
|
||||
string? actual = cms.MatchesAny(new string[] { "not" });
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region List
|
||||
|
||||
[Fact]
|
||||
public void MatchesAll_NullList_NoMatches()
|
||||
{
|
||||
var cms = new PathMatchSet("test", "name");
|
||||
var actual = cms.MatchesAll((List<string>?)null);
|
||||
Assert.Empty(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAll_EmptyList_NoMatches()
|
||||
{
|
||||
var cms = new PathMatchSet("test", "name");
|
||||
var actual = cms.MatchesAll(new List<string>());
|
||||
Assert.Empty(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAll_MatchingList_Matches()
|
||||
{
|
||||
var cms = new PathMatchSet("test", "name");
|
||||
var actual = cms.MatchesAll(new List<string> { "test" });
|
||||
string path = Assert.Single(actual);
|
||||
Assert.Equal("test", path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAll_MismatchedList_NoMatches()
|
||||
{
|
||||
var cms = new PathMatchSet("test", "name");
|
||||
var actual = cms.MatchesAll(new List<string> { "not" });
|
||||
Assert.Empty(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAny_NullList_NoMatches()
|
||||
{
|
||||
var cms = new PathMatchSet("test", "name");
|
||||
string? actual = cms.MatchesAny((List<string>?)null);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAny_EmptyList_NoMatches()
|
||||
{
|
||||
var cms = new PathMatchSet("test", "name");
|
||||
string? actual = cms.MatchesAny(new List<string>());
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAny_MatchingList_Matches()
|
||||
{
|
||||
var cms = new PathMatchSet("test", "name");
|
||||
string? actual = cms.MatchesAny(new List<string> { "test" });
|
||||
Assert.Equal("test", actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAny_MismatchedList_NoMatches()
|
||||
{
|
||||
var cms = new PathMatchSet("test", "name");
|
||||
string? actual = cms.MatchesAny(new List<string> { "not" });
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Mock Delegates
|
||||
|
||||
/// <inheritdoc cref="GetPathVersion"/>
|
||||
private static string? PathVersionMock(string path, List<string>? files) => null;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
293
SabreTools.IO.Test/Matching/PathMatchTests.cs
Normal file
293
SabreTools.IO.Test/Matching/PathMatchTests.cs
Normal file
@@ -0,0 +1,293 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SabreTools.IO.Matching;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.IO.Test.Matching
|
||||
{
|
||||
public class PathMatchTests
|
||||
{
|
||||
[Fact]
|
||||
public void InvalidNeedle_ThrowsException()
|
||||
{
|
||||
Assert.Throws<InvalidDataException>(() => new PathMatch(string.Empty));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ImplicitOperatorArray_Success()
|
||||
{
|
||||
string needle = "test";
|
||||
var pm = (PathMatch)needle;
|
||||
Assert.NotNull(pm);
|
||||
}
|
||||
|
||||
#region Array
|
||||
|
||||
[Fact]
|
||||
public void NullArray_NoMatch()
|
||||
{
|
||||
var pm = new PathMatch("test");
|
||||
string? actual = pm.Match((string[]?)null);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyArray_NoMatch()
|
||||
{
|
||||
var pm = new PathMatch("test");
|
||||
string? actual = pm.Match(Array.Empty<string>());
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SingleItemArrayMatching_Match()
|
||||
{
|
||||
string needle = "test";
|
||||
string[] stack = [needle];
|
||||
var pm = new PathMatch(needle);
|
||||
|
||||
string? actual = pm.Match(stack);
|
||||
Assert.Equal(needle, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SingleItemArrayMismatched_NoMatch()
|
||||
{
|
||||
string needle = "test";
|
||||
string[] stack = ["not"];
|
||||
var pm = new PathMatch(needle);
|
||||
|
||||
string? actual = pm.Match(stack);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MultiItemArrayMatching_Match()
|
||||
{
|
||||
string needle = "test";
|
||||
string[] stack = ["not", needle, "far"];
|
||||
var pm = new PathMatch(needle);
|
||||
|
||||
string? actual = pm.Match(stack);
|
||||
Assert.Equal(needle, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MultiItemArrayMismatched_NoMatch()
|
||||
{
|
||||
string needle = "test";
|
||||
string[] stack = ["not", "too", "far"];
|
||||
var pm = new PathMatch(needle);
|
||||
|
||||
string? actual = pm.Match(stack);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region List
|
||||
|
||||
[Fact]
|
||||
public void NullList_NoMatch()
|
||||
{
|
||||
var pm = new PathMatch("test");
|
||||
string? actual = pm.Match((List<string>?)null);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyList_NoMatch()
|
||||
{
|
||||
var pm = new PathMatch("test");
|
||||
string? actual = pm.Match(new List<string>());
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SingleItemListMatching_Match()
|
||||
{
|
||||
string needle = "test";
|
||||
List<string> stack = [needle];
|
||||
var pm = new PathMatch(needle);
|
||||
|
||||
string? actual = pm.Match(stack);
|
||||
Assert.Equal(needle, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SingleItemListMismatched_NoMatch()
|
||||
{
|
||||
string needle = "test";
|
||||
List<string> stack = ["not"];
|
||||
var pm = new PathMatch(needle);
|
||||
|
||||
string? actual = pm.Match(stack);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MultiItemListMatching_Match()
|
||||
{
|
||||
string needle = "test";
|
||||
List<string> stack = ["not", needle, "far"];
|
||||
var pm = new PathMatch(needle);
|
||||
|
||||
string? actual = pm.Match(stack);
|
||||
Assert.Equal(needle, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MultiItemListMismatched_NoMatch()
|
||||
{
|
||||
string needle = "test";
|
||||
List<string> stack = ["not", "too", "far"];
|
||||
var pm = new PathMatch(needle);
|
||||
|
||||
string? actual = pm.Match(stack);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Match Case
|
||||
|
||||
[Fact]
|
||||
public void MatchCaseEqual_Match()
|
||||
{
|
||||
string needle = "test";
|
||||
List<string> stack = [needle];
|
||||
var pm = new PathMatch(needle, matchCase: true);
|
||||
|
||||
string? actual = pm.Match(stack);
|
||||
Assert.Equal(needle, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NoMatchCaseEqual_Match()
|
||||
{
|
||||
string needle = "test";
|
||||
List<string> stack = [needle];
|
||||
var pm = new PathMatch(needle, matchCase: false);
|
||||
|
||||
string? actual = pm.Match(stack);
|
||||
Assert.Equal(needle, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchCaseInequal_NoMatch()
|
||||
{
|
||||
string needle = "test";
|
||||
List<string> stack = [needle.ToUpperInvariant()];
|
||||
var pm = new PathMatch(needle, matchCase: true);
|
||||
|
||||
string? actual = pm.Match(stack);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NoMatchCaseInequal_Match()
|
||||
{
|
||||
string needle = "test";
|
||||
List<string> stack = [needle.ToUpperInvariant()];
|
||||
var pm = new PathMatch(needle, matchCase: false);
|
||||
|
||||
string? actual = pm.Match(stack);
|
||||
Assert.Equal(needle.ToUpperInvariant(), actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchCaseContains_Match()
|
||||
{
|
||||
string needle = "test";
|
||||
List<string> stack = [$"prefix_{needle}_postfix"];
|
||||
var pm = new PathMatch(needle, matchCase: true);
|
||||
|
||||
string? actual = pm.Match(stack);
|
||||
Assert.Equal($"prefix_{needle}_postfix", actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NoMatchCaseContains_Match()
|
||||
{
|
||||
string needle = "test";
|
||||
List<string> stack = [$"prefix_{needle}_postfix"];
|
||||
var pm = new PathMatch(needle, matchCase: false);
|
||||
|
||||
string? actual = pm.Match(stack);
|
||||
Assert.Equal($"prefix_{needle}_postfix", actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Use Ends With
|
||||
|
||||
[Fact]
|
||||
public void EndsWithEqual_Match()
|
||||
{
|
||||
string needle = "test";
|
||||
List<string> stack = [needle];
|
||||
var pm = new PathMatch(needle, useEndsWith: true);
|
||||
|
||||
string? actual = pm.Match(stack);
|
||||
Assert.Equal(needle, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NoEndsWithEqual_Match()
|
||||
{
|
||||
string needle = "test";
|
||||
List<string> stack = [needle];
|
||||
var pm = new PathMatch(needle, useEndsWith: false);
|
||||
|
||||
string? actual = pm.Match(stack);
|
||||
Assert.Equal(needle, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EndsWithInequal_Match()
|
||||
{
|
||||
string needle = "test";
|
||||
List<string> stack = [needle.ToUpperInvariant()];
|
||||
var pm = new PathMatch(needle, useEndsWith: true);
|
||||
|
||||
string? actual = pm.Match(stack);
|
||||
Assert.Equal(needle.ToUpperInvariant(), actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NoEndsWithInequal_Match()
|
||||
{
|
||||
string needle = "test";
|
||||
List<string> stack = [needle.ToUpperInvariant()];
|
||||
var pm = new PathMatch(needle, useEndsWith: false);
|
||||
|
||||
string? actual = pm.Match(stack);
|
||||
Assert.Equal(needle.ToUpperInvariant(), actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EndsWithContains_NoMatch()
|
||||
{
|
||||
string needle = "test";
|
||||
List<string> stack = [$"prefix_{needle}_postfix"];
|
||||
var pm = new PathMatch(needle, useEndsWith: true);
|
||||
|
||||
string? actual = pm.Match(stack);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NoEndsWithContains_Match()
|
||||
{
|
||||
string needle = "test";
|
||||
List<string> stack = [$"prefix_{needle}_postfix"];
|
||||
var pm = new PathMatch(needle, useEndsWith: false);
|
||||
|
||||
string? actual = pm.Match(stack);
|
||||
Assert.Equal($"prefix_{needle}_postfix", actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
249
SabreTools.IO.Test/Numerics/BothInt16Tests.cs
Normal file
249
SabreTools.IO.Test/Numerics/BothInt16Tests.cs
Normal file
@@ -0,0 +1,249 @@
|
||||
using System;
|
||||
using SabreTools.Numerics;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.IO.Test.Numerics
|
||||
{
|
||||
public class BothInt16Tests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(0, 0, true)]
|
||||
[InlineData(0, 1, false)]
|
||||
public void IsValidTest(short le, short be, bool expected)
|
||||
{
|
||||
var val = new BothInt16(le, be);
|
||||
|
||||
Assert.Equal(le, val.LittleEndian);
|
||||
Assert.Equal(be, val.BigEndian);
|
||||
Assert.Equal(expected, val.IsValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ImplicitConversionTest()
|
||||
{
|
||||
short expected = 1;
|
||||
var val = new BothInt16(expected, expected);
|
||||
|
||||
short to = (short)val;
|
||||
Assert.Equal(expected, to);
|
||||
|
||||
BothInt16 back = (BothInt16)to;
|
||||
Assert.Equal(expected, back.LittleEndian);
|
||||
Assert.Equal(expected, back.BigEndian);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, -1)]
|
||||
[InlineData(1, 0)]
|
||||
[InlineData(2, 1)]
|
||||
public void CompareToTest(short le, int expected)
|
||||
{
|
||||
short compare = 1;
|
||||
var val = new BothInt16(le, le);
|
||||
|
||||
int actual = val.CompareTo(compare);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetTypeCodeTest()
|
||||
{
|
||||
TypeCode expected = ((short)1).GetTypeCode();
|
||||
|
||||
var val = new BothInt16(1, 1);
|
||||
Assert.Equal(expected, val.GetTypeCode());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToTypesTest()
|
||||
{
|
||||
var val = new BothInt16(1, 1);
|
||||
|
||||
bool expectedBool = Convert.ToBoolean((short)1);
|
||||
Assert.Equal(expectedBool, val.ToBoolean(null));
|
||||
|
||||
char expectedChar = Convert.ToChar((short)1);
|
||||
Assert.Equal(expectedChar, val.ToChar(null));
|
||||
|
||||
sbyte expectedSByte = Convert.ToSByte((short)1);
|
||||
Assert.Equal(expectedSByte, val.ToSByte(null));
|
||||
|
||||
byte expectedByte = Convert.ToByte((short)1);
|
||||
Assert.Equal(expectedByte, val.ToByte(null));
|
||||
|
||||
short expectedInt16 = Convert.ToInt16((short)1);
|
||||
Assert.Equal(expectedInt16, val.ToInt16(null));
|
||||
|
||||
ushort expectedUInt16 = Convert.ToUInt16((short)1);
|
||||
Assert.Equal(expectedUInt16, val.ToUInt16(null));
|
||||
|
||||
int expectedInt32 = Convert.ToInt32((short)1);
|
||||
Assert.Equal(expectedInt32, val.ToInt32(null));
|
||||
|
||||
uint expectedUInt32 = Convert.ToUInt32((short)1);
|
||||
Assert.Equal(expectedUInt32, val.ToUInt32(null));
|
||||
|
||||
long expectedInt64 = Convert.ToInt64((short)1);
|
||||
Assert.Equal(expectedInt64, val.ToInt64(null));
|
||||
|
||||
ulong expectedUInt64 = Convert.ToUInt64((short)1);
|
||||
Assert.Equal(expectedUInt64, val.ToUInt64(null));
|
||||
|
||||
float expectedSingle = Convert.ToSingle((short)1);
|
||||
Assert.Equal(expectedSingle, val.ToSingle(null));
|
||||
|
||||
double expectedDouble = Convert.ToDouble((short)1);
|
||||
Assert.Equal(expectedDouble, val.ToDouble(null));
|
||||
|
||||
decimal expectedDecimal = Convert.ToDecimal((short)1);
|
||||
Assert.Equal(expectedDecimal, val.ToDecimal(null));
|
||||
|
||||
Assert.Throws<InvalidCastException>(() => val.ToDateTime(null));
|
||||
|
||||
string expectedString = Convert.ToString((short)1);
|
||||
Assert.Equal(expectedString, val.ToString(null));
|
||||
|
||||
ulong expectedObject = Convert.ToUInt64((short)1);
|
||||
Assert.Equal(expectedObject, val.ToType(typeof(ulong), null));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 0, false)]
|
||||
[InlineData(0, 1, false)]
|
||||
[InlineData(1, 0, false)]
|
||||
[InlineData(1, 1, true)]
|
||||
public void Equals_BothEndian(short le, short be, bool expected)
|
||||
{
|
||||
var val = new BothInt16(le, be);
|
||||
var equalTo = new BothInt16(1, 1);
|
||||
|
||||
bool actual = val.Equals(equalTo);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 0, false)]
|
||||
[InlineData(1, 1, true)]
|
||||
public void Equals_BaseType(short le, short be, bool expected)
|
||||
{
|
||||
var val = new BothInt16(le, be);
|
||||
short equalTo = 1;
|
||||
|
||||
bool actual = val.Equals(equalTo);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArithmeticUnaryOperatorsTest()
|
||||
{
|
||||
var valA = new BothInt16(2, 2);
|
||||
short expected = 3;
|
||||
valA++;
|
||||
Assert.Equal(expected, valA.LittleEndian);
|
||||
Assert.Equal(expected, valA.BigEndian);
|
||||
|
||||
valA = new BothInt16(2, 2);
|
||||
expected = 1;
|
||||
valA--;
|
||||
Assert.Equal(expected, valA.LittleEndian);
|
||||
Assert.Equal(expected, valA.BigEndian);
|
||||
|
||||
valA = new BothInt16(2, 2);
|
||||
expected = 2;
|
||||
BothInt16 actual = +valA;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = -2;
|
||||
actual = -valA;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArithmeticBinaryOperatorsTest()
|
||||
{
|
||||
var valA = new BothInt16(3, 3);
|
||||
var valB = new BothInt16(2, 2);
|
||||
|
||||
short expected = 6;
|
||||
BothInt16 actual = valA * valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 1;
|
||||
actual = valA / valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 1;
|
||||
actual = valA % valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 5;
|
||||
actual = valA + valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 1;
|
||||
actual = valA - valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BitwiseUnaryOperatorsTest()
|
||||
{
|
||||
var valA = new BothInt16(2, 2);
|
||||
short expected = ~2;
|
||||
BothInt16 actual = ~valA;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShiftBinaryOperatorsTest()
|
||||
{
|
||||
var valA = new BothInt16(2, 2);
|
||||
var valB = new BothInt16(1, 1);
|
||||
|
||||
short expected = 2 << 1;
|
||||
BothInt16 actual = valA << valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 2 >> 1;
|
||||
actual = valA >> valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 2 >>> 1;
|
||||
actual = valA >>> valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BitwiseBinaryOperatorsTest()
|
||||
{
|
||||
var valA = new BothInt16(3, 3);
|
||||
var valB = new BothInt16(2, 2);
|
||||
|
||||
short expected = 3 & 2;
|
||||
BothInt16 actual = valA & valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 3 | 2;
|
||||
actual = valA | valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 3 ^ 2;
|
||||
actual = valA ^ valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
}
|
||||
}
|
||||
}
|
||||
249
SabreTools.IO.Test/Numerics/BothInt32Tests.cs
Normal file
249
SabreTools.IO.Test/Numerics/BothInt32Tests.cs
Normal file
@@ -0,0 +1,249 @@
|
||||
using System;
|
||||
using SabreTools.Numerics;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.IO.Test.Numerics
|
||||
{
|
||||
public class BothInt32Tests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(0, 0, true)]
|
||||
[InlineData(0, 1, false)]
|
||||
public void IsValidTest(int le, int be, bool expected)
|
||||
{
|
||||
var val = new BothInt32(le, be);
|
||||
|
||||
Assert.Equal(le, val.LittleEndian);
|
||||
Assert.Equal(be, val.BigEndian);
|
||||
Assert.Equal(expected, val.IsValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ImplicitConversionTest()
|
||||
{
|
||||
int expected = 1;
|
||||
var val = new BothInt32(expected, expected);
|
||||
|
||||
int to = (int)val;
|
||||
Assert.Equal(expected, to);
|
||||
|
||||
BothInt32 back = (BothInt32)to;
|
||||
Assert.Equal(expected, back.LittleEndian);
|
||||
Assert.Equal(expected, back.BigEndian);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, -1)]
|
||||
[InlineData(1, 0)]
|
||||
[InlineData(2, 1)]
|
||||
public void CompareToTest(int le, int expected)
|
||||
{
|
||||
int compare = 1;
|
||||
var val = new BothInt32(le, le);
|
||||
|
||||
int actual = val.CompareTo(compare);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetTypeCodeTest()
|
||||
{
|
||||
TypeCode expected = ((int)1).GetTypeCode();
|
||||
|
||||
var val = new BothInt32(1, 1);
|
||||
Assert.Equal(expected, val.GetTypeCode());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToTypesTest()
|
||||
{
|
||||
var val = new BothInt32(1, 1);
|
||||
|
||||
bool expectedBool = Convert.ToBoolean((int)1);
|
||||
Assert.Equal(expectedBool, val.ToBoolean(null));
|
||||
|
||||
char expectedChar = Convert.ToChar((int)1);
|
||||
Assert.Equal(expectedChar, val.ToChar(null));
|
||||
|
||||
sbyte expectedSByte = Convert.ToSByte((int)1);
|
||||
Assert.Equal(expectedSByte, val.ToSByte(null));
|
||||
|
||||
byte expectedByte = Convert.ToByte((int)1);
|
||||
Assert.Equal(expectedByte, val.ToByte(null));
|
||||
|
||||
short expectedInt16 = Convert.ToInt16((int)1);
|
||||
Assert.Equal(expectedInt16, val.ToInt16(null));
|
||||
|
||||
ushort expectedUInt16 = Convert.ToUInt16((int)1);
|
||||
Assert.Equal(expectedUInt16, val.ToUInt16(null));
|
||||
|
||||
int expectedInt32 = Convert.ToInt32((int)1);
|
||||
Assert.Equal(expectedInt32, val.ToInt32(null));
|
||||
|
||||
uint expectedUInt32 = Convert.ToUInt32((int)1);
|
||||
Assert.Equal(expectedUInt32, val.ToUInt32(null));
|
||||
|
||||
long expectedInt64 = Convert.ToInt64((int)1);
|
||||
Assert.Equal(expectedInt64, val.ToInt64(null));
|
||||
|
||||
ulong expectedUInt64 = Convert.ToUInt64((int)1);
|
||||
Assert.Equal(expectedUInt64, val.ToUInt64(null));
|
||||
|
||||
float expectedSingle = Convert.ToSingle((int)1);
|
||||
Assert.Equal(expectedSingle, val.ToSingle(null));
|
||||
|
||||
double expectedDouble = Convert.ToDouble((int)1);
|
||||
Assert.Equal(expectedDouble, val.ToDouble(null));
|
||||
|
||||
decimal expectedDecimal = Convert.ToDecimal((int)1);
|
||||
Assert.Equal(expectedDecimal, val.ToDecimal(null));
|
||||
|
||||
Assert.Throws<InvalidCastException>(() => val.ToDateTime(null));
|
||||
|
||||
string expectedString = Convert.ToString((int)1);
|
||||
Assert.Equal(expectedString, val.ToString(null));
|
||||
|
||||
ulong expectedObject = Convert.ToUInt64((int)1);
|
||||
Assert.Equal(expectedObject, val.ToType(typeof(ulong), null));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 0, false)]
|
||||
[InlineData(0, 1, false)]
|
||||
[InlineData(1, 0, false)]
|
||||
[InlineData(1, 1, true)]
|
||||
public void Equals_BothEndian(int le, int be, bool expected)
|
||||
{
|
||||
var val = new BothInt32(le, be);
|
||||
var equalTo = new BothInt32(1, 1);
|
||||
|
||||
bool actual = val.Equals(equalTo);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 0, false)]
|
||||
[InlineData(1, 1, true)]
|
||||
public void Equals_BaseType(int le, int be, bool expected)
|
||||
{
|
||||
var val = new BothInt32(le, be);
|
||||
int equalTo = 1;
|
||||
|
||||
bool actual = val.Equals(equalTo);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArithmeticUnaryOperatorsTest()
|
||||
{
|
||||
var valA = new BothInt32(2, 2);
|
||||
int expected = 3;
|
||||
valA++;
|
||||
Assert.Equal(expected, valA.LittleEndian);
|
||||
Assert.Equal(expected, valA.BigEndian);
|
||||
|
||||
valA = new BothInt32(2, 2);
|
||||
expected = 1;
|
||||
valA--;
|
||||
Assert.Equal(expected, valA.LittleEndian);
|
||||
Assert.Equal(expected, valA.BigEndian);
|
||||
|
||||
valA = new BothInt32(2, 2);
|
||||
expected = 2;
|
||||
BothInt32 actual = +valA;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = -2;
|
||||
actual = -valA;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArithmeticBinaryOperatorsTest()
|
||||
{
|
||||
var valA = new BothInt32(3, 3);
|
||||
var valB = new BothInt32(2, 2);
|
||||
|
||||
int expected = 6;
|
||||
BothInt32 actual = valA * valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 1;
|
||||
actual = valA / valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 1;
|
||||
actual = valA % valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 5;
|
||||
actual = valA + valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 1;
|
||||
actual = valA - valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BitwiseUnaryOperatorsTest()
|
||||
{
|
||||
var valA = new BothInt32(2, 2);
|
||||
int expected = ~2;
|
||||
BothInt32 actual = ~valA;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShiftBinaryOperatorsTest()
|
||||
{
|
||||
var valA = new BothInt32(2, 2);
|
||||
var valB = new BothInt32(1, 1);
|
||||
|
||||
int expected = 2 << 1;
|
||||
BothInt32 actual = valA << valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 2 >> 1;
|
||||
actual = valA >> valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 2 >>> 1;
|
||||
actual = valA >>> valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BitwiseBinaryOperatorsTest()
|
||||
{
|
||||
var valA = new BothInt32(3, 3);
|
||||
var valB = new BothInt32(2, 2);
|
||||
|
||||
int expected = 3 & 2;
|
||||
BothInt32 actual = valA & valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 3 | 2;
|
||||
actual = valA | valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 3 ^ 2;
|
||||
actual = valA ^ valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
}
|
||||
}
|
||||
}
|
||||
249
SabreTools.IO.Test/Numerics/BothInt64Tests.cs
Normal file
249
SabreTools.IO.Test/Numerics/BothInt64Tests.cs
Normal file
@@ -0,0 +1,249 @@
|
||||
using System;
|
||||
using SabreTools.Numerics;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.IO.Test.Numerics
|
||||
{
|
||||
public class BothInt64Tests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(0, 0, true)]
|
||||
[InlineData(0, 1, false)]
|
||||
public void IsValidTest(long le, long be, bool expected)
|
||||
{
|
||||
var val = new BothInt64(le, be);
|
||||
|
||||
Assert.Equal(le, val.LittleEndian);
|
||||
Assert.Equal(be, val.BigEndian);
|
||||
Assert.Equal(expected, val.IsValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ImplicitConversionTest()
|
||||
{
|
||||
long expected = 1;
|
||||
var val = new BothInt64(expected, expected);
|
||||
|
||||
long to = (long)val;
|
||||
Assert.Equal(expected, to);
|
||||
|
||||
BothInt64 back = (BothInt64)to;
|
||||
Assert.Equal(expected, back.LittleEndian);
|
||||
Assert.Equal(expected, back.BigEndian);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, -1)]
|
||||
[InlineData(1, 0)]
|
||||
[InlineData(2, 1)]
|
||||
public void CompareToTest(long le, int expected)
|
||||
{
|
||||
long compare = 1;
|
||||
var val = new BothInt64(le, le);
|
||||
|
||||
int actual = val.CompareTo(compare);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetTypeCodeTest()
|
||||
{
|
||||
TypeCode expected = ((long)1).GetTypeCode();
|
||||
|
||||
var val = new BothInt64(1, 1);
|
||||
Assert.Equal(expected, val.GetTypeCode());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToTypesTest()
|
||||
{
|
||||
var val = new BothInt64(1, 1);
|
||||
|
||||
bool expectedBool = Convert.ToBoolean((long)1);
|
||||
Assert.Equal(expectedBool, val.ToBoolean(null));
|
||||
|
||||
char expectedChar = Convert.ToChar((long)1);
|
||||
Assert.Equal(expectedChar, val.ToChar(null));
|
||||
|
||||
sbyte expectedSByte = Convert.ToSByte((long)1);
|
||||
Assert.Equal(expectedSByte, val.ToSByte(null));
|
||||
|
||||
byte expectedByte = Convert.ToByte((long)1);
|
||||
Assert.Equal(expectedByte, val.ToByte(null));
|
||||
|
||||
short expectedInt16 = Convert.ToInt16((long)1);
|
||||
Assert.Equal(expectedInt16, val.ToInt16(null));
|
||||
|
||||
ushort expectedUInt16 = Convert.ToUInt16((long)1);
|
||||
Assert.Equal(expectedUInt16, val.ToUInt16(null));
|
||||
|
||||
int expectedInt32 = Convert.ToInt32((long)1);
|
||||
Assert.Equal(expectedInt32, val.ToInt32(null));
|
||||
|
||||
uint expectedUInt32 = Convert.ToUInt32((long)1);
|
||||
Assert.Equal(expectedUInt32, val.ToUInt32(null));
|
||||
|
||||
long expectedInt64 = Convert.ToInt64((long)1);
|
||||
Assert.Equal(expectedInt64, val.ToInt64(null));
|
||||
|
||||
ulong expectedUInt64 = Convert.ToUInt64((long)1);
|
||||
Assert.Equal(expectedUInt64, val.ToUInt64(null));
|
||||
|
||||
float expectedSingle = Convert.ToSingle((long)1);
|
||||
Assert.Equal(expectedSingle, val.ToSingle(null));
|
||||
|
||||
double expectedDouble = Convert.ToDouble((long)1);
|
||||
Assert.Equal(expectedDouble, val.ToDouble(null));
|
||||
|
||||
decimal expectedDecimal = Convert.ToDecimal((long)1);
|
||||
Assert.Equal(expectedDecimal, val.ToDecimal(null));
|
||||
|
||||
Assert.Throws<InvalidCastException>(() => val.ToDateTime(null));
|
||||
|
||||
string expectedString = Convert.ToString((long)1);
|
||||
Assert.Equal(expectedString, val.ToString(null));
|
||||
|
||||
ulong expectedObject = Convert.ToUInt64((long)1);
|
||||
Assert.Equal(expectedObject, val.ToType(typeof(ulong), null));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 0, false)]
|
||||
[InlineData(0, 1, false)]
|
||||
[InlineData(1, 0, false)]
|
||||
[InlineData(1, 1, true)]
|
||||
public void Equals_BothEndian(long le, long be, bool expected)
|
||||
{
|
||||
var val = new BothInt64(le, be);
|
||||
var equalTo = new BothInt64(1, 1);
|
||||
|
||||
bool actual = val.Equals(equalTo);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 0, false)]
|
||||
[InlineData(1, 1, true)]
|
||||
public void Equals_BaseType(long le, long be, bool expected)
|
||||
{
|
||||
var val = new BothInt64(le, be);
|
||||
long equalTo = 1;
|
||||
|
||||
bool actual = val.Equals(equalTo);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArithmeticUnaryOperatorsTest()
|
||||
{
|
||||
var valA = new BothInt64(2, 2);
|
||||
long expected = 3;
|
||||
valA++;
|
||||
Assert.Equal(expected, valA.LittleEndian);
|
||||
Assert.Equal(expected, valA.BigEndian);
|
||||
|
||||
valA = new BothInt64(2, 2);
|
||||
expected = 1;
|
||||
valA--;
|
||||
Assert.Equal(expected, valA.LittleEndian);
|
||||
Assert.Equal(expected, valA.BigEndian);
|
||||
|
||||
valA = new BothInt64(2, 2);
|
||||
expected = 2;
|
||||
BothInt64 actual = +valA;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = -2;
|
||||
actual = -valA;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArithmeticBinaryOperatorsTest()
|
||||
{
|
||||
var valA = new BothInt64(3, 3);
|
||||
var valB = new BothInt64(2, 2);
|
||||
|
||||
long expected = 6;
|
||||
BothInt64 actual = valA * valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 1;
|
||||
actual = valA / valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 1;
|
||||
actual = valA % valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 5;
|
||||
actual = valA + valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 1;
|
||||
actual = valA - valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BitwiseUnaryOperatorsTest()
|
||||
{
|
||||
var valA = new BothInt64(2, 2);
|
||||
long expected = ~2;
|
||||
BothInt64 actual = ~valA;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShiftBinaryOperatorsTest()
|
||||
{
|
||||
var valA = new BothInt64(2, 2);
|
||||
var valB = new BothInt32(1, 1);
|
||||
|
||||
long expected = 2 << 1;
|
||||
BothInt64 actual = valA << valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 2 >> 1;
|
||||
actual = valA >> valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 2 >>> 1;
|
||||
actual = valA >>> valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BitwiseBinaryOperatorsTest()
|
||||
{
|
||||
var valA = new BothInt64(3, 3);
|
||||
var valB = new BothInt64(2, 2);
|
||||
|
||||
long expected = 3 & 2;
|
||||
BothInt64 actual = valA & valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 3 | 2;
|
||||
actual = valA | valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 3 ^ 2;
|
||||
actual = valA ^ valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
}
|
||||
}
|
||||
}
|
||||
249
SabreTools.IO.Test/Numerics/BothInt8Tests.cs
Normal file
249
SabreTools.IO.Test/Numerics/BothInt8Tests.cs
Normal file
@@ -0,0 +1,249 @@
|
||||
using System;
|
||||
using SabreTools.Numerics;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.IO.Test.Numerics
|
||||
{
|
||||
public class BothInt8Tests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(0, 0, true)]
|
||||
[InlineData(0, 1, false)]
|
||||
public void IsValidTest(sbyte le, sbyte be, bool expected)
|
||||
{
|
||||
var val = new BothInt8(le, be);
|
||||
|
||||
Assert.Equal(le, val.LittleEndian);
|
||||
Assert.Equal(be, val.BigEndian);
|
||||
Assert.Equal(expected, val.IsValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ImplicitConversionTest()
|
||||
{
|
||||
sbyte expected = 1;
|
||||
var val = new BothInt8(expected, expected);
|
||||
|
||||
sbyte to = (sbyte)val;
|
||||
Assert.Equal(expected, to);
|
||||
|
||||
BothInt8 back = (BothInt8)to;
|
||||
Assert.Equal(expected, back.LittleEndian);
|
||||
Assert.Equal(expected, back.BigEndian);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, -1)]
|
||||
[InlineData(1, 0)]
|
||||
[InlineData(2, 1)]
|
||||
public void CompareToTest(sbyte le, int expected)
|
||||
{
|
||||
sbyte compare = 1;
|
||||
var val = new BothInt8(le, le);
|
||||
|
||||
int actual = val.CompareTo(compare);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetTypeCodeTest()
|
||||
{
|
||||
TypeCode expected = ((sbyte)1).GetTypeCode();
|
||||
|
||||
var val = new BothInt8(1, 1);
|
||||
Assert.Equal(expected, val.GetTypeCode());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToTypesTest()
|
||||
{
|
||||
var val = new BothInt8(1, 1);
|
||||
|
||||
bool expectedBool = Convert.ToBoolean((sbyte)1);
|
||||
Assert.Equal(expectedBool, val.ToBoolean(null));
|
||||
|
||||
char expectedChar = Convert.ToChar((sbyte)1);
|
||||
Assert.Equal(expectedChar, val.ToChar(null));
|
||||
|
||||
sbyte expectedSByte = Convert.ToSByte((sbyte)1);
|
||||
Assert.Equal(expectedSByte, val.ToSByte(null));
|
||||
|
||||
byte expectedByte = Convert.ToByte((sbyte)1);
|
||||
Assert.Equal(expectedByte, val.ToByte(null));
|
||||
|
||||
short expectedInt16 = Convert.ToInt16((sbyte)1);
|
||||
Assert.Equal(expectedInt16, val.ToInt16(null));
|
||||
|
||||
ushort expectedUInt16 = Convert.ToUInt16((sbyte)1);
|
||||
Assert.Equal(expectedUInt16, val.ToUInt16(null));
|
||||
|
||||
int expectedInt32 = Convert.ToInt32((sbyte)1);
|
||||
Assert.Equal(expectedInt32, val.ToInt32(null));
|
||||
|
||||
uint expectedUInt32 = Convert.ToUInt32((sbyte)1);
|
||||
Assert.Equal(expectedUInt32, val.ToUInt32(null));
|
||||
|
||||
long expectedInt64 = Convert.ToInt64((sbyte)1);
|
||||
Assert.Equal(expectedInt64, val.ToInt64(null));
|
||||
|
||||
ulong expectedUInt64 = Convert.ToUInt64((sbyte)1);
|
||||
Assert.Equal(expectedUInt64, val.ToUInt64(null));
|
||||
|
||||
float expectedSingle = Convert.ToSingle((sbyte)1);
|
||||
Assert.Equal(expectedSingle, val.ToSingle(null));
|
||||
|
||||
double expectedDouble = Convert.ToDouble((sbyte)1);
|
||||
Assert.Equal(expectedDouble, val.ToDouble(null));
|
||||
|
||||
decimal expectedDecimal = Convert.ToDecimal((sbyte)1);
|
||||
Assert.Equal(expectedDecimal, val.ToDecimal(null));
|
||||
|
||||
Assert.Throws<InvalidCastException>(() => val.ToDateTime(null));
|
||||
|
||||
string expectedString = Convert.ToString((sbyte)1);
|
||||
Assert.Equal(expectedString, val.ToString(null));
|
||||
|
||||
ulong expectedObject = Convert.ToUInt64((sbyte)1);
|
||||
Assert.Equal(expectedObject, val.ToType(typeof(ulong), null));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 0, false)]
|
||||
[InlineData(0, 1, false)]
|
||||
[InlineData(1, 0, false)]
|
||||
[InlineData(1, 1, true)]
|
||||
public void Equals_BothEndian(sbyte le, sbyte be, bool expected)
|
||||
{
|
||||
var val = new BothInt8(le, be);
|
||||
var equalTo = new BothInt8(1, 1);
|
||||
|
||||
bool actual = val.Equals(equalTo);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 0, false)]
|
||||
[InlineData(1, 1, true)]
|
||||
public void Equals_BaseType(sbyte le, sbyte be, bool expected)
|
||||
{
|
||||
var val = new BothInt8(le, be);
|
||||
sbyte equalTo = 1;
|
||||
|
||||
bool actual = val.Equals(equalTo);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArithmeticUnaryOperatorsTest()
|
||||
{
|
||||
var valA = new BothInt8(2, 2);
|
||||
sbyte expected = 3;
|
||||
valA++;
|
||||
Assert.Equal(expected, valA.LittleEndian);
|
||||
Assert.Equal(expected, valA.BigEndian);
|
||||
|
||||
valA = new BothInt8(2, 2);
|
||||
expected = 1;
|
||||
valA--;
|
||||
Assert.Equal(expected, valA.LittleEndian);
|
||||
Assert.Equal(expected, valA.BigEndian);
|
||||
|
||||
valA = new BothInt8(2, 2);
|
||||
expected = 2;
|
||||
BothInt8 actual = +valA;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = -2;
|
||||
actual = -valA;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArithmeticBinaryOperatorsTest()
|
||||
{
|
||||
var valA = new BothInt8(3, 3);
|
||||
var valB = new BothInt8(2, 2);
|
||||
|
||||
sbyte expected = 6;
|
||||
BothInt8 actual = valA * valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 1;
|
||||
actual = valA / valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 1;
|
||||
actual = valA % valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 5;
|
||||
actual = valA + valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 1;
|
||||
actual = valA - valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BitwiseUnaryOperatorsTest()
|
||||
{
|
||||
var valA = new BothInt8(2, 2);
|
||||
sbyte expected = ~2;
|
||||
BothInt8 actual = ~valA;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShiftBinaryOperatorsTest()
|
||||
{
|
||||
var valA = new BothInt8(2, 2);
|
||||
var valB = new BothInt8(1, 1);
|
||||
|
||||
sbyte expected = 2 << 1;
|
||||
BothInt8 actual = valA << valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 2 >> 1;
|
||||
actual = valA >> valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 2 >>> 1;
|
||||
actual = valA >>> valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BitwiseBinaryOperatorsTest()
|
||||
{
|
||||
var valA = new BothInt8(3, 3);
|
||||
var valB = new BothInt8(2, 2);
|
||||
|
||||
sbyte expected = 3 & 2;
|
||||
BothInt8 actual = valA & valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 3 | 2;
|
||||
actual = valA | valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 3 ^ 2;
|
||||
actual = valA ^ valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
}
|
||||
}
|
||||
}
|
||||
238
SabreTools.IO.Test/Numerics/BothUInt16Tests.cs
Normal file
238
SabreTools.IO.Test/Numerics/BothUInt16Tests.cs
Normal file
@@ -0,0 +1,238 @@
|
||||
using System;
|
||||
using SabreTools.Numerics;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.IO.Test.Numerics
|
||||
{
|
||||
public class BothUInt16Tests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(0, 0, true)]
|
||||
[InlineData(0, 1, false)]
|
||||
public void IsValidTest(ushort le, ushort be, bool expected)
|
||||
{
|
||||
var val = new BothUInt16(le, be);
|
||||
|
||||
Assert.Equal(le, val.LittleEndian);
|
||||
Assert.Equal(be, val.BigEndian);
|
||||
Assert.Equal(expected, val.IsValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ImplicitConversionTest()
|
||||
{
|
||||
ushort expected = 1;
|
||||
var val = new BothUInt16(expected, expected);
|
||||
|
||||
ushort to = (ushort)val;
|
||||
Assert.Equal(expected, to);
|
||||
|
||||
BothUInt16 back = (BothUInt16)to;
|
||||
Assert.Equal(expected, back.LittleEndian);
|
||||
Assert.Equal(expected, back.BigEndian);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, -1)]
|
||||
[InlineData(1, 0)]
|
||||
[InlineData(2, 1)]
|
||||
public void CompareToTest(ushort le, int expected)
|
||||
{
|
||||
ushort compare = 1;
|
||||
var val = new BothUInt16(le, le);
|
||||
|
||||
int actual = val.CompareTo(compare);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetTypeCodeTest()
|
||||
{
|
||||
TypeCode expected = ((ushort)1).GetTypeCode();
|
||||
|
||||
var val = new BothUInt16(1, 1);
|
||||
Assert.Equal(expected, val.GetTypeCode());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToTypesTest()
|
||||
{
|
||||
var val = new BothUInt16(1, 1);
|
||||
|
||||
bool expectedBool = Convert.ToBoolean((ushort)1);
|
||||
Assert.Equal(expectedBool, val.ToBoolean(null));
|
||||
|
||||
char expectedChar = Convert.ToChar((ushort)1);
|
||||
Assert.Equal(expectedChar, val.ToChar(null));
|
||||
|
||||
sbyte expectedSByte = Convert.ToSByte((ushort)1);
|
||||
Assert.Equal(expectedSByte, val.ToSByte(null));
|
||||
|
||||
byte expectedByte = Convert.ToByte((ushort)1);
|
||||
Assert.Equal(expectedByte, val.ToByte(null));
|
||||
|
||||
short expectedInt16 = Convert.ToInt16((ushort)1);
|
||||
Assert.Equal(expectedInt16, val.ToInt16(null));
|
||||
|
||||
ushort expectedUInt16 = Convert.ToUInt16((ushort)1);
|
||||
Assert.Equal(expectedUInt16, val.ToUInt16(null));
|
||||
|
||||
int expectedInt32 = Convert.ToInt32((ushort)1);
|
||||
Assert.Equal(expectedInt32, val.ToInt32(null));
|
||||
|
||||
uint expectedUInt32 = Convert.ToUInt32((ushort)1);
|
||||
Assert.Equal(expectedUInt32, val.ToUInt32(null));
|
||||
|
||||
long expectedInt64 = Convert.ToInt64((ushort)1);
|
||||
Assert.Equal(expectedInt64, val.ToInt64(null));
|
||||
|
||||
ulong expectedUInt64 = Convert.ToUInt64((ushort)1);
|
||||
Assert.Equal(expectedUInt64, val.ToUInt64(null));
|
||||
|
||||
float expectedSingle = Convert.ToSingle((ushort)1);
|
||||
Assert.Equal(expectedSingle, val.ToSingle(null));
|
||||
|
||||
double expectedDouble = Convert.ToDouble((ushort)1);
|
||||
Assert.Equal(expectedDouble, val.ToDouble(null));
|
||||
|
||||
decimal expectedDecimal = Convert.ToDecimal((ushort)1);
|
||||
Assert.Equal(expectedDecimal, val.ToDecimal(null));
|
||||
|
||||
Assert.Throws<InvalidCastException>(() => val.ToDateTime(null));
|
||||
|
||||
string expectedString = Convert.ToString((ushort)1);
|
||||
Assert.Equal(expectedString, val.ToString(null));
|
||||
|
||||
ulong expectedObject = Convert.ToUInt64((ushort)1);
|
||||
Assert.Equal(expectedObject, val.ToType(typeof(ulong), null));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 0, false)]
|
||||
[InlineData(0, 1, false)]
|
||||
[InlineData(1, 0, false)]
|
||||
[InlineData(1, 1, true)]
|
||||
public void Equals_BothEndian(ushort le, ushort be, bool expected)
|
||||
{
|
||||
var val = new BothUInt16(le, be);
|
||||
var equalTo = new BothUInt16(1, 1);
|
||||
|
||||
bool actual = val.Equals(equalTo);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 0, false)]
|
||||
[InlineData(1, 1, true)]
|
||||
public void Equals_BaseType(ushort le, ushort be, bool expected)
|
||||
{
|
||||
var val = new BothUInt16(le, be);
|
||||
ushort equalTo = 1;
|
||||
|
||||
bool actual = val.Equals(equalTo);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArithmeticUnaryOperatorsTest()
|
||||
{
|
||||
var valA = new BothUInt16(2, 2);
|
||||
ushort expected = 3;
|
||||
valA++;
|
||||
Assert.Equal(expected, valA.LittleEndian);
|
||||
Assert.Equal(expected, valA.BigEndian);
|
||||
|
||||
valA = new BothUInt16(2, 2);
|
||||
expected = 1;
|
||||
valA--;
|
||||
Assert.Equal(expected, valA.LittleEndian);
|
||||
Assert.Equal(expected, valA.BigEndian);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArithmeticBinaryOperatorsTest()
|
||||
{
|
||||
var valA = new BothUInt16(3, 3);
|
||||
var valB = new BothUInt16(2, 2);
|
||||
|
||||
ushort expected = 6;
|
||||
BothUInt16 actual = valA * valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 1;
|
||||
actual = valA / valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 1;
|
||||
actual = valA % valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 5;
|
||||
actual = valA + valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 1;
|
||||
actual = valA - valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BitwiseUnaryOperatorsTest()
|
||||
{
|
||||
var valA = new BothUInt16(2, 2);
|
||||
ushort expected = 65533;
|
||||
BothUInt16 actual = ~valA;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShiftBinaryOperatorsTest()
|
||||
{
|
||||
var valA = new BothUInt16(2, 2);
|
||||
var valB = new BothUInt16(1, 1);
|
||||
|
||||
ushort expected = 2 << 1;
|
||||
BothUInt16 actual = valA << valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 2 >> 1;
|
||||
actual = valA >> valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 2 >>> 1;
|
||||
actual = valA >>> valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BitwiseBinaryOperatorsTest()
|
||||
{
|
||||
var valA = new BothUInt16(3, 3);
|
||||
var valB = new BothUInt16(2, 2);
|
||||
|
||||
ushort expected = 3 & 2;
|
||||
BothUInt16 actual = valA & valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 3 | 2;
|
||||
actual = valA | valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 3 ^ 2;
|
||||
actual = valA ^ valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
}
|
||||
}
|
||||
}
|
||||
238
SabreTools.IO.Test/Numerics/BothUInt32Tests.cs
Normal file
238
SabreTools.IO.Test/Numerics/BothUInt32Tests.cs
Normal file
@@ -0,0 +1,238 @@
|
||||
using System;
|
||||
using SabreTools.Numerics;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.IO.Test.Numerics
|
||||
{
|
||||
public class BothUInt32Tests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(0, 0, true)]
|
||||
[InlineData(0, 1, false)]
|
||||
public void IsValidTest(uint le, uint be, bool expected)
|
||||
{
|
||||
var val = new BothUInt32(le, be);
|
||||
|
||||
Assert.Equal(le, val.LittleEndian);
|
||||
Assert.Equal(be, val.BigEndian);
|
||||
Assert.Equal(expected, val.IsValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ImplicitConversionTest()
|
||||
{
|
||||
uint expected = 1;
|
||||
var val = new BothUInt32(expected, expected);
|
||||
|
||||
uint to = (uint)val;
|
||||
Assert.Equal(expected, to);
|
||||
|
||||
BothUInt32 back = (BothUInt32)to;
|
||||
Assert.Equal(expected, back.LittleEndian);
|
||||
Assert.Equal(expected, back.BigEndian);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, -1)]
|
||||
[InlineData(1, 0)]
|
||||
[InlineData(2, 1)]
|
||||
public void CompareToTest(uint le, int expected)
|
||||
{
|
||||
uint compare = 1;
|
||||
var val = new BothUInt32(le, le);
|
||||
|
||||
int actual = val.CompareTo(compare);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetTypeCodeTest()
|
||||
{
|
||||
TypeCode expected = ((uint)1).GetTypeCode();
|
||||
|
||||
var val = new BothUInt32(1, 1);
|
||||
Assert.Equal(expected, val.GetTypeCode());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToTypesTest()
|
||||
{
|
||||
var val = new BothUInt32(1, 1);
|
||||
|
||||
bool expectedBool = Convert.ToBoolean((uint)1);
|
||||
Assert.Equal(expectedBool, val.ToBoolean(null));
|
||||
|
||||
char expectedChar = Convert.ToChar((uint)1);
|
||||
Assert.Equal(expectedChar, val.ToChar(null));
|
||||
|
||||
sbyte expectedSByte = Convert.ToSByte((uint)1);
|
||||
Assert.Equal(expectedSByte, val.ToSByte(null));
|
||||
|
||||
byte expectedByte = Convert.ToByte((uint)1);
|
||||
Assert.Equal(expectedByte, val.ToByte(null));
|
||||
|
||||
short expectedInt16 = Convert.ToInt16((uint)1);
|
||||
Assert.Equal(expectedInt16, val.ToInt16(null));
|
||||
|
||||
ushort expectedUInt16 = Convert.ToUInt16((uint)1);
|
||||
Assert.Equal(expectedUInt16, val.ToUInt16(null));
|
||||
|
||||
int expectedInt32 = Convert.ToInt32((uint)1);
|
||||
Assert.Equal(expectedInt32, val.ToInt32(null));
|
||||
|
||||
uint expectedUInt32 = Convert.ToUInt32((uint)1);
|
||||
Assert.Equal(expectedUInt32, val.ToUInt32(null));
|
||||
|
||||
long expectedInt64 = Convert.ToInt64((uint)1);
|
||||
Assert.Equal(expectedInt64, val.ToInt64(null));
|
||||
|
||||
ulong expectedUInt64 = Convert.ToUInt64((uint)1);
|
||||
Assert.Equal(expectedUInt64, val.ToUInt64(null));
|
||||
|
||||
float expectedSingle = Convert.ToSingle((uint)1);
|
||||
Assert.Equal(expectedSingle, val.ToSingle(null));
|
||||
|
||||
double expectedDouble = Convert.ToDouble((uint)1);
|
||||
Assert.Equal(expectedDouble, val.ToDouble(null));
|
||||
|
||||
decimal expectedDecimal = Convert.ToDecimal((uint)1);
|
||||
Assert.Equal(expectedDecimal, val.ToDecimal(null));
|
||||
|
||||
Assert.Throws<InvalidCastException>(() => val.ToDateTime(null));
|
||||
|
||||
string expectedString = Convert.ToString((uint)1);
|
||||
Assert.Equal(expectedString, val.ToString(null));
|
||||
|
||||
ulong expectedObject = Convert.ToUInt64((uint)1);
|
||||
Assert.Equal(expectedObject, val.ToType(typeof(ulong), null));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 0, false)]
|
||||
[InlineData(0, 1, false)]
|
||||
[InlineData(1, 0, false)]
|
||||
[InlineData(1, 1, true)]
|
||||
public void Equals_BothEndian(uint le, uint be, bool expected)
|
||||
{
|
||||
var val = new BothUInt32(le, be);
|
||||
var equalTo = new BothUInt32(1, 1);
|
||||
|
||||
bool actual = val.Equals(equalTo);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 0, false)]
|
||||
[InlineData(1, 1, true)]
|
||||
public void Equals_BaseType(uint le, uint be, bool expected)
|
||||
{
|
||||
var val = new BothUInt32(le, be);
|
||||
uint equalTo = 1;
|
||||
|
||||
bool actual = val.Equals(equalTo);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArithmeticUnaryOperatorsTest()
|
||||
{
|
||||
var valA = new BothUInt32(2, 2);
|
||||
uint expected = 3;
|
||||
valA++;
|
||||
Assert.Equal(expected, valA.LittleEndian);
|
||||
Assert.Equal(expected, valA.BigEndian);
|
||||
|
||||
valA = new BothUInt32(2, 2);
|
||||
expected = 1;
|
||||
valA--;
|
||||
Assert.Equal(expected, valA.LittleEndian);
|
||||
Assert.Equal(expected, valA.BigEndian);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArithmeticBinaryOperatorsTest()
|
||||
{
|
||||
var valA = new BothUInt32(3, 3);
|
||||
var valB = new BothUInt32(2, 2);
|
||||
|
||||
uint expected = 6;
|
||||
BothUInt32 actual = valA * valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 1;
|
||||
actual = valA / valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 1;
|
||||
actual = valA % valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 5;
|
||||
actual = valA + valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 1;
|
||||
actual = valA - valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BitwiseUnaryOperatorsTest()
|
||||
{
|
||||
var valA = new BothUInt32(2, 2);
|
||||
uint expected = ~((uint)2);
|
||||
BothUInt32 actual = ~valA;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShiftBinaryOperatorsTest()
|
||||
{
|
||||
var valA = new BothUInt32(2, 2);
|
||||
var valB = new BothInt32(1, 1);
|
||||
|
||||
uint expected = 2 << 1;
|
||||
BothUInt32 actual = valA << valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 2 >> 1;
|
||||
actual = valA >> valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 2 >>> 1;
|
||||
actual = valA >>> valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BitwiseBinaryOperatorsTest()
|
||||
{
|
||||
var valA = new BothUInt32(3, 3);
|
||||
var valB = new BothUInt32(2, 2);
|
||||
|
||||
uint expected = 3 & 2;
|
||||
BothUInt32 actual = valA & valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 3 | 2;
|
||||
actual = valA | valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 3 ^ 2;
|
||||
actual = valA ^ valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
}
|
||||
}
|
||||
}
|
||||
238
SabreTools.IO.Test/Numerics/BothUInt64Tests.cs
Normal file
238
SabreTools.IO.Test/Numerics/BothUInt64Tests.cs
Normal file
@@ -0,0 +1,238 @@
|
||||
using System;
|
||||
using SabreTools.Numerics;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.IO.Test.Numerics
|
||||
{
|
||||
public class BothUInt64Tests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(0, 0, true)]
|
||||
[InlineData(0, 1, false)]
|
||||
public void IsValidTest(ulong le, ulong be, bool expected)
|
||||
{
|
||||
var val = new BothUInt64(le, be);
|
||||
|
||||
Assert.Equal(le, val.LittleEndian);
|
||||
Assert.Equal(be, val.BigEndian);
|
||||
Assert.Equal(expected, val.IsValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ImplicitConversionTest()
|
||||
{
|
||||
ulong expected = 1;
|
||||
var val = new BothUInt64(expected, expected);
|
||||
|
||||
ulong to = (ulong)val;
|
||||
Assert.Equal(expected, to);
|
||||
|
||||
BothUInt64 back = (BothUInt64)to;
|
||||
Assert.Equal(expected, back.LittleEndian);
|
||||
Assert.Equal(expected, back.BigEndian);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, -1)]
|
||||
[InlineData(1, 0)]
|
||||
[InlineData(2, 1)]
|
||||
public void CompareToTest(ulong le, int expected)
|
||||
{
|
||||
ulong compare = 1;
|
||||
var val = new BothUInt64(le, le);
|
||||
|
||||
int actual = val.CompareTo(compare);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetTypeCodeTest()
|
||||
{
|
||||
TypeCode expected = ((ulong)1).GetTypeCode();
|
||||
|
||||
var val = new BothUInt64(1, 1);
|
||||
Assert.Equal(expected, val.GetTypeCode());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToTypesTest()
|
||||
{
|
||||
var val = new BothUInt64(1, 1);
|
||||
|
||||
bool expectedBool = Convert.ToBoolean((ulong)1);
|
||||
Assert.Equal(expectedBool, val.ToBoolean(null));
|
||||
|
||||
char expectedChar = Convert.ToChar((ulong)1);
|
||||
Assert.Equal(expectedChar, val.ToChar(null));
|
||||
|
||||
sbyte expectedSByte = Convert.ToSByte((ulong)1);
|
||||
Assert.Equal(expectedSByte, val.ToSByte(null));
|
||||
|
||||
byte expectedByte = Convert.ToByte((ulong)1);
|
||||
Assert.Equal(expectedByte, val.ToByte(null));
|
||||
|
||||
short expectedInt16 = Convert.ToInt16((ulong)1);
|
||||
Assert.Equal(expectedInt16, val.ToInt16(null));
|
||||
|
||||
ushort expectedUInt16 = Convert.ToUInt16((ulong)1);
|
||||
Assert.Equal(expectedUInt16, val.ToUInt16(null));
|
||||
|
||||
int expectedInt32 = Convert.ToInt32((ulong)1);
|
||||
Assert.Equal(expectedInt32, val.ToInt32(null));
|
||||
|
||||
uint expectedUInt32 = Convert.ToUInt32((ulong)1);
|
||||
Assert.Equal(expectedUInt32, val.ToUInt32(null));
|
||||
|
||||
long expectedInt64 = Convert.ToInt64((ulong)1);
|
||||
Assert.Equal(expectedInt64, val.ToInt64(null));
|
||||
|
||||
ulong expectedUInt64 = Convert.ToUInt64((ulong)1);
|
||||
Assert.Equal(expectedUInt64, val.ToUInt64(null));
|
||||
|
||||
float expectedSingle = Convert.ToSingle((ulong)1);
|
||||
Assert.Equal(expectedSingle, val.ToSingle(null));
|
||||
|
||||
double expectedDouble = Convert.ToDouble((ulong)1);
|
||||
Assert.Equal(expectedDouble, val.ToDouble(null));
|
||||
|
||||
decimal expectedDecimal = Convert.ToDecimal((ulong)1);
|
||||
Assert.Equal(expectedDecimal, val.ToDecimal(null));
|
||||
|
||||
Assert.Throws<InvalidCastException>(() => val.ToDateTime(null));
|
||||
|
||||
string expectedString = Convert.ToString((ulong)1);
|
||||
Assert.Equal(expectedString, val.ToString(null));
|
||||
|
||||
ulong expectedObject = Convert.ToUInt64((ulong)1);
|
||||
Assert.Equal(expectedObject, val.ToType(typeof(ulong), null));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 0, false)]
|
||||
[InlineData(0, 1, false)]
|
||||
[InlineData(1, 0, false)]
|
||||
[InlineData(1, 1, true)]
|
||||
public void Equals_BothEndian(ulong le, ulong be, bool expected)
|
||||
{
|
||||
var val = new BothUInt64(le, be);
|
||||
var equalTo = new BothUInt64(1, 1);
|
||||
|
||||
bool actual = val.Equals(equalTo);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 0, false)]
|
||||
[InlineData(1, 1, true)]
|
||||
public void Equals_BaseType(ulong le, ulong be, bool expected)
|
||||
{
|
||||
var val = new BothUInt64(le, be);
|
||||
ulong equalTo = 1;
|
||||
|
||||
bool actual = val.Equals(equalTo);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArithmeticUnaryOperatorsTest()
|
||||
{
|
||||
var valA = new BothUInt64(2, 2);
|
||||
ulong expected = 3;
|
||||
valA++;
|
||||
Assert.Equal(expected, valA.LittleEndian);
|
||||
Assert.Equal(expected, valA.BigEndian);
|
||||
|
||||
valA = new BothUInt64(2, 2);
|
||||
expected = 1;
|
||||
valA--;
|
||||
Assert.Equal(expected, valA.LittleEndian);
|
||||
Assert.Equal(expected, valA.BigEndian);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArithmeticBinaryOperatorsTest()
|
||||
{
|
||||
var valA = new BothUInt64(3, 3);
|
||||
var valB = new BothUInt64(2, 2);
|
||||
|
||||
ulong expected = 6;
|
||||
BothUInt64 actual = valA * valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 1;
|
||||
actual = valA / valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 1;
|
||||
actual = valA % valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 5;
|
||||
actual = valA + valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 1;
|
||||
actual = valA - valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BitwiseUnaryOperatorsTest()
|
||||
{
|
||||
var valA = new BothUInt64(2, 2);
|
||||
ulong expected = ~((ulong)2);
|
||||
BothUInt64 actual = ~valA;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShiftBinaryOperatorsTest()
|
||||
{
|
||||
var valA = new BothUInt64(2, 2);
|
||||
var valB = new BothInt32(1, 1);
|
||||
|
||||
ulong expected = 2 << 1;
|
||||
BothUInt64 actual = valA << valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 2 >> 1;
|
||||
actual = valA >> valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 2 >>> 1;
|
||||
actual = valA >>> valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BitwiseBinaryOperatorsTest()
|
||||
{
|
||||
var valA = new BothUInt64(3, 3);
|
||||
var valB = new BothUInt64(2, 2);
|
||||
|
||||
ulong expected = 3 & 2;
|
||||
BothUInt64 actual = valA & valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 3 | 2;
|
||||
actual = valA | valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 3 ^ 2;
|
||||
actual = valA ^ valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
}
|
||||
}
|
||||
}
|
||||
238
SabreTools.IO.Test/Numerics/BothUInt8Tests.cs
Normal file
238
SabreTools.IO.Test/Numerics/BothUInt8Tests.cs
Normal file
@@ -0,0 +1,238 @@
|
||||
using System;
|
||||
using SabreTools.Numerics;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.IO.Test.Numerics
|
||||
{
|
||||
public class BothUInt8Tests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(0, 0, true)]
|
||||
[InlineData(0, 1, false)]
|
||||
public void IsValidTest(byte le, byte be, bool expected)
|
||||
{
|
||||
var val = new BothUInt8(le, be);
|
||||
|
||||
Assert.Equal(le, val.LittleEndian);
|
||||
Assert.Equal(be, val.BigEndian);
|
||||
Assert.Equal(expected, val.IsValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ImplicitConversionTest()
|
||||
{
|
||||
byte expected = 1;
|
||||
var val = new BothUInt8(expected, expected);
|
||||
|
||||
byte to = (byte)val;
|
||||
Assert.Equal(expected, to);
|
||||
|
||||
BothUInt8 back = (BothUInt8)to;
|
||||
Assert.Equal(expected, back.LittleEndian);
|
||||
Assert.Equal(expected, back.BigEndian);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, -1)]
|
||||
[InlineData(1, 0)]
|
||||
[InlineData(2, 1)]
|
||||
public void CompareToTest(byte le, int expected)
|
||||
{
|
||||
byte compare = 1;
|
||||
var val = new BothUInt8(le, le);
|
||||
|
||||
int actual = val.CompareTo(compare);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetTypeCodeTest()
|
||||
{
|
||||
TypeCode expected = ((byte)1).GetTypeCode();
|
||||
|
||||
var val = new BothUInt8(1, 1);
|
||||
Assert.Equal(expected, val.GetTypeCode());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToTypesTest()
|
||||
{
|
||||
var val = new BothUInt8(1, 1);
|
||||
|
||||
bool expectedBool = Convert.ToBoolean((byte)1);
|
||||
Assert.Equal(expectedBool, val.ToBoolean(null));
|
||||
|
||||
char expectedChar = Convert.ToChar((byte)1);
|
||||
Assert.Equal(expectedChar, val.ToChar(null));
|
||||
|
||||
sbyte expectedSByte = Convert.ToSByte((byte)1);
|
||||
Assert.Equal(expectedSByte, val.ToSByte(null));
|
||||
|
||||
byte expectedByte = Convert.ToByte((byte)1);
|
||||
Assert.Equal(expectedByte, val.ToByte(null));
|
||||
|
||||
short expectedInt16 = Convert.ToInt16((byte)1);
|
||||
Assert.Equal(expectedInt16, val.ToInt16(null));
|
||||
|
||||
ushort expectedUInt16 = Convert.ToUInt16((byte)1);
|
||||
Assert.Equal(expectedUInt16, val.ToUInt16(null));
|
||||
|
||||
int expectedInt32 = Convert.ToInt32((byte)1);
|
||||
Assert.Equal(expectedInt32, val.ToInt32(null));
|
||||
|
||||
uint expectedUInt32 = Convert.ToUInt32((byte)1);
|
||||
Assert.Equal(expectedUInt32, val.ToUInt32(null));
|
||||
|
||||
long expectedInt64 = Convert.ToInt64((byte)1);
|
||||
Assert.Equal(expectedInt64, val.ToInt64(null));
|
||||
|
||||
ulong expectedUInt64 = Convert.ToUInt64((byte)1);
|
||||
Assert.Equal(expectedUInt64, val.ToUInt64(null));
|
||||
|
||||
float expectedSingle = Convert.ToSingle((byte)1);
|
||||
Assert.Equal(expectedSingle, val.ToSingle(null));
|
||||
|
||||
double expectedDouble = Convert.ToDouble((byte)1);
|
||||
Assert.Equal(expectedDouble, val.ToDouble(null));
|
||||
|
||||
decimal expectedDecimal = Convert.ToDecimal((byte)1);
|
||||
Assert.Equal(expectedDecimal, val.ToDecimal(null));
|
||||
|
||||
Assert.Throws<InvalidCastException>(() => val.ToDateTime(null));
|
||||
|
||||
string expectedString = Convert.ToString((byte)1);
|
||||
Assert.Equal(expectedString, val.ToString(null));
|
||||
|
||||
ulong expectedObject = Convert.ToUInt64((byte)1);
|
||||
Assert.Equal(expectedObject, val.ToType(typeof(ulong), null));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 0, false)]
|
||||
[InlineData(0, 1, false)]
|
||||
[InlineData(1, 0, false)]
|
||||
[InlineData(1, 1, true)]
|
||||
public void Equals_BothEndian(byte le, byte be, bool expected)
|
||||
{
|
||||
var val = new BothUInt8(le, be);
|
||||
var equalTo = new BothUInt8(1, 1);
|
||||
|
||||
bool actual = val.Equals(equalTo);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 0, false)]
|
||||
[InlineData(1, 1, true)]
|
||||
public void Equals_BaseType(byte le, byte be, bool expected)
|
||||
{
|
||||
var val = new BothUInt8(le, be);
|
||||
byte equalTo = 1;
|
||||
|
||||
bool actual = val.Equals(equalTo);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArithmeticUnaryOperatorsTest()
|
||||
{
|
||||
var valA = new BothUInt8(2, 2);
|
||||
byte expected = 3;
|
||||
valA++;
|
||||
Assert.Equal(expected, valA.LittleEndian);
|
||||
Assert.Equal(expected, valA.BigEndian);
|
||||
|
||||
valA = new BothUInt8(2, 2);
|
||||
expected = 1;
|
||||
valA--;
|
||||
Assert.Equal(expected, valA.LittleEndian);
|
||||
Assert.Equal(expected, valA.BigEndian);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArithmeticBinaryOperatorsTest()
|
||||
{
|
||||
var valA = new BothUInt8(3, 3);
|
||||
var valB = new BothUInt8(2, 2);
|
||||
|
||||
byte expected = 6;
|
||||
BothUInt8 actual = valA * valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 1;
|
||||
actual = valA / valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 1;
|
||||
actual = valA % valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 5;
|
||||
actual = valA + valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 1;
|
||||
actual = valA - valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BitwiseUnaryOperatorsTest()
|
||||
{
|
||||
var valA = new BothUInt8(2, 2);
|
||||
byte expected = 253;
|
||||
BothUInt8 actual = ~valA;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShiftBinaryOperatorsTest()
|
||||
{
|
||||
var valA = new BothUInt8(2, 2);
|
||||
var valB = new BothUInt8(1, 1);
|
||||
|
||||
byte expected = 2 << 1;
|
||||
BothUInt8 actual = valA << valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 2 >> 1;
|
||||
actual = valA >> valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 2 >>> 1;
|
||||
actual = valA >>> valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BitwiseBinaryOperatorsTest()
|
||||
{
|
||||
var valA = new BothUInt8(3, 3);
|
||||
var valB = new BothUInt8(2, 2);
|
||||
|
||||
byte expected = 3 & 2;
|
||||
BothUInt8 actual = valA & valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 3 | 2;
|
||||
actual = valA | valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
|
||||
expected = 3 ^ 2;
|
||||
actual = valA ^ valB;
|
||||
Assert.Equal(expected, actual.LittleEndian);
|
||||
Assert.Equal(expected, actual.BigEndian);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.3">
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
||||
40
SabreTools.IO.Test/Streams/BufferedStreamTests.cs
Normal file
40
SabreTools.IO.Test/Streams/BufferedStreamTests.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System.IO;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.IO.Test.Streams
|
||||
{
|
||||
public class BufferedStreamTests
|
||||
{
|
||||
#region ReadNextByte
|
||||
|
||||
[Fact]
|
||||
public void ReadNextByte_Empty_Null()
|
||||
{
|
||||
var source = new MemoryStream();
|
||||
var stream = new IO.Streams.BufferedStream(source);
|
||||
byte? actual = stream.ReadNextByte();
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadNextByte_Filled_ValidPosition_Byte()
|
||||
{
|
||||
var source = new MemoryStream(new byte[1024]);
|
||||
var stream = new IO.Streams.BufferedStream(source);
|
||||
byte? actual = stream.ReadNextByte();
|
||||
Assert.Equal((byte)0x00, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadNextByte_Filled_InvalidPosition_Null()
|
||||
{
|
||||
var source = new MemoryStream(new byte[1024]);
|
||||
source.Seek(0, SeekOrigin.End);
|
||||
var stream = new IO.Streams.BufferedStream(source);
|
||||
byte? actual = stream.ReadNextByte();
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SabreTools.IO.Streams;
|
||||
@@ -7,8 +8,10 @@ namespace SabreTools.IO.Test.Streams
|
||||
{
|
||||
public class ReadOnlyCompositeStreamTests
|
||||
{
|
||||
#region Constructor
|
||||
|
||||
[Fact]
|
||||
public void DefaultConstructorTest()
|
||||
public void Constructor_Default()
|
||||
{
|
||||
var stream = new ReadOnlyCompositeStream();
|
||||
Assert.Equal(0, stream.Length);
|
||||
@@ -16,7 +19,7 @@ namespace SabreTools.IO.Test.Streams
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyArrayConstructorTest()
|
||||
public void Constructor_EmptyArray()
|
||||
{
|
||||
Stream[] arr = [new MemoryStream()];
|
||||
var stream = new ReadOnlyCompositeStream(arr);
|
||||
@@ -25,9 +28,8 @@ namespace SabreTools.IO.Test.Streams
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyEnumerableConstructorTest()
|
||||
public void Constructor_EmptyEnumerable()
|
||||
{
|
||||
// Empty enumerable constructor
|
||||
List<Stream> list = [new MemoryStream()];
|
||||
var stream = new ReadOnlyCompositeStream(list);
|
||||
Assert.Equal(0, stream.Length);
|
||||
@@ -35,7 +37,7 @@ namespace SabreTools.IO.Test.Streams
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SingleStreamConstructorTest()
|
||||
public void Constructor_SingleStream()
|
||||
{
|
||||
var stream = new ReadOnlyCompositeStream(new MemoryStream(new byte[1024]));
|
||||
Assert.Equal(1024, stream.Length);
|
||||
@@ -43,7 +45,7 @@ namespace SabreTools.IO.Test.Streams
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FilledArrayConstructorTest()
|
||||
public void Constructor_FilledArray()
|
||||
{
|
||||
Stream[] arr = [new MemoryStream(new byte[1024]), new MemoryStream(new byte[1024])];
|
||||
var stream = new ReadOnlyCompositeStream(arr);
|
||||
@@ -52,7 +54,7 @@ namespace SabreTools.IO.Test.Streams
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FilledEnumerableConstructorTest()
|
||||
public void Constructor_FilledEnumerable()
|
||||
{
|
||||
List<Stream> list = [new MemoryStream(new byte[1024]), new MemoryStream(new byte[1024])];
|
||||
var stream = new ReadOnlyCompositeStream(list);
|
||||
@@ -60,6 +62,10 @@ namespace SabreTools.IO.Test.Streams
|
||||
Assert.Equal(0, stream.Position);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region AddStream
|
||||
|
||||
[Fact]
|
||||
public void AddStreamTest()
|
||||
{
|
||||
@@ -70,10 +76,18 @@ namespace SabreTools.IO.Test.Streams
|
||||
stream.AddStream(new MemoryStream(new byte[1024]));
|
||||
Assert.Equal(1024, stream.Length);
|
||||
Assert.Equal(0, stream.Position);
|
||||
|
||||
stream.AddStream(new MemoryStream([]));
|
||||
Assert.Equal(1024, stream.Length);
|
||||
Assert.Equal(0, stream.Position);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Read
|
||||
|
||||
[Fact]
|
||||
public void EmptyStreamReadTest()
|
||||
public void Read_EmptyStream()
|
||||
{
|
||||
var stream = new ReadOnlyCompositeStream();
|
||||
|
||||
@@ -84,7 +98,7 @@ namespace SabreTools.IO.Test.Streams
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SingleStreamReadTest()
|
||||
public void Read_SingleStream()
|
||||
{
|
||||
Stream[] arr = [new MemoryStream(new byte[1024])];
|
||||
var stream = new ReadOnlyCompositeStream(arr);
|
||||
@@ -96,7 +110,7 @@ namespace SabreTools.IO.Test.Streams
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MultipleStreamSingleContainedReadTest()
|
||||
public void Read_MultipleStream_SingleContained()
|
||||
{
|
||||
Stream[] arr = [new MemoryStream(new byte[1024]), new MemoryStream(new byte[1024])];
|
||||
var stream = new ReadOnlyCompositeStream(arr);
|
||||
@@ -108,7 +122,7 @@ namespace SabreTools.IO.Test.Streams
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MultipleStreamMultipleContainedReadTest()
|
||||
public void Read_MultipleStream_MultipleContained()
|
||||
{
|
||||
Stream[] arr = [new MemoryStream(new byte[256]), new MemoryStream(new byte[256])];
|
||||
var stream = new ReadOnlyCompositeStream(arr);
|
||||
@@ -120,7 +134,7 @@ namespace SabreTools.IO.Test.Streams
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SingleStreamExtraReadTest()
|
||||
public void Read_SingleStream_Extra()
|
||||
{
|
||||
Stream[] arr = [new MemoryStream(new byte[256])];
|
||||
var stream = new ReadOnlyCompositeStream(arr);
|
||||
@@ -132,7 +146,7 @@ namespace SabreTools.IO.Test.Streams
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MultipleStreamExtraReadTest()
|
||||
public void Read_MultipleStream_Extra()
|
||||
{
|
||||
Stream[] arr = [new MemoryStream(new byte[128]), new MemoryStream(new byte[128])];
|
||||
var stream = new ReadOnlyCompositeStream(arr);
|
||||
@@ -142,5 +156,32 @@ namespace SabreTools.IO.Test.Streams
|
||||
|
||||
Assert.Equal(256, read);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unimplemented
|
||||
|
||||
[Fact]
|
||||
public void Flush_Throws()
|
||||
{
|
||||
var stream = new ReadOnlyCompositeStream();
|
||||
Assert.Throws<NotImplementedException>(() => stream.Flush());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetLength_Throws()
|
||||
{
|
||||
var stream = new ReadOnlyCompositeStream();
|
||||
Assert.Throws<NotImplementedException>(() => stream.SetLength(0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Write_Throws()
|
||||
{
|
||||
var stream = new ReadOnlyCompositeStream();
|
||||
Assert.Throws<NotImplementedException>(() => stream.Write([], 0, 0));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
418
SabreTools.IO.Test/Streams/ViewStreamTests.cs
Normal file
418
SabreTools.IO.Test/Streams/ViewStreamTests.cs
Normal file
@@ -0,0 +1,418 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SabreTools.IO.Streams;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.IO.Test.Streams
|
||||
{
|
||||
public class ViewStreamTests
|
||||
{
|
||||
#region Constructor
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 0, 0)]
|
||||
[InlineData(1024, 0, 1024)]
|
||||
[InlineData(1024, 256, 768)]
|
||||
public void Constructor_Array(int size, long offset, long expectedLength)
|
||||
{
|
||||
byte[] data = new byte[size];
|
||||
var stream = new ViewStream(data, offset);
|
||||
Assert.Equal(expectedLength, stream.Length);
|
||||
Assert.Equal(0, stream.Position);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 0, 0, 0)]
|
||||
[InlineData(1024, 0, 1024, 1024)]
|
||||
[InlineData(1024, 256, 512, 512)]
|
||||
public void Constructor_Array_Length(int size, long offset, long length, long expectedLength)
|
||||
{
|
||||
byte[] data = new byte[size];
|
||||
var stream = new ViewStream(data, offset, length);
|
||||
Assert.Equal(expectedLength, stream.Length);
|
||||
Assert.Equal(0, stream.Position);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, -1, 0)]
|
||||
[InlineData(0, 2048, 0)]
|
||||
[InlineData(1024, -1, 1024)]
|
||||
[InlineData(1024, 2048, 1024)]
|
||||
[InlineData(1024, -1, 512)]
|
||||
[InlineData(1024, 2048, 512)]
|
||||
public void Constructor_Array_InvalidOffset(int size, long offset, long length)
|
||||
{
|
||||
byte[] data = new byte[size];
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => _ = new ViewStream(data, offset, length));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 0, -1)]
|
||||
[InlineData(0, 0, 2048)]
|
||||
[InlineData(1024, 0, -1)]
|
||||
[InlineData(1024, 0, 2048)]
|
||||
[InlineData(1024, 256, -1)]
|
||||
[InlineData(1024, 256, 2048)]
|
||||
public void Constructor_Array_InvalidLength(int size, long offset, long length)
|
||||
{
|
||||
byte[] data = new byte[size];
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => _ = new ViewStream(data, offset, length));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 0, 0)]
|
||||
[InlineData(1024, 0, 1024)]
|
||||
[InlineData(1024, 256, 768)]
|
||||
public void Constructor_Stream(int size, long offset, long expectedLength)
|
||||
{
|
||||
Stream data = new MemoryStream(new byte[size]);
|
||||
var stream = new ViewStream(data, offset);
|
||||
Assert.Equal(expectedLength, stream.Length);
|
||||
Assert.Equal(0, stream.Position);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 0, 0, 0)]
|
||||
[InlineData(1024, 0, 1024, 1024)]
|
||||
[InlineData(1024, 256, 512, 512)]
|
||||
public void Constructor_Stream_Length(int size, long offset, long length, long expectedLength)
|
||||
{
|
||||
Stream data = new MemoryStream(new byte[size]);
|
||||
var stream = new ViewStream(data, offset, length);
|
||||
Assert.Equal(expectedLength, stream.Length);
|
||||
Assert.Equal(0, stream.Position);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, -1, 0)]
|
||||
[InlineData(0, 2048, 0)]
|
||||
[InlineData(1024, -1, 1024)]
|
||||
[InlineData(1024, 2048, 1024)]
|
||||
[InlineData(1024, -1, 512)]
|
||||
[InlineData(1024, 2048, 512)]
|
||||
public void Constructor_Stream_InvalidOffset(int size, long offset, long length)
|
||||
{
|
||||
Stream data = new MemoryStream(new byte[size]);
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => _ = new ViewStream(data, offset, length));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 0, -1)]
|
||||
[InlineData(0, 0, 2048)]
|
||||
[InlineData(1024, 0, -1)]
|
||||
[InlineData(1024, 0, 2048)]
|
||||
[InlineData(1024, 256, -1)]
|
||||
[InlineData(1024, 256, 2048)]
|
||||
public void Constructor_Stream_InvalidLength(int size, long offset, long length)
|
||||
{
|
||||
Stream data = new MemoryStream(new byte[size]);
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => _ = new ViewStream(data, offset, length));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Position
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 0, 0, -1, 0)]
|
||||
[InlineData(0, 0, 0, 0, 0)]
|
||||
[InlineData(0, 0, 0, 256, 0)]
|
||||
[InlineData(0, 0, 0, 2048, 0)]
|
||||
[InlineData(1024, 0, 1024, -1, 0)]
|
||||
[InlineData(1024, 0, 1024, 0, 0)]
|
||||
[InlineData(1024, 0, 1024, 256, 256)]
|
||||
[InlineData(1024, 0, 1024, 2048, 1023)]
|
||||
[InlineData(1024, 256, 512, -1, 0)]
|
||||
[InlineData(1024, 256, 512, 0, 0)]
|
||||
[InlineData(1024, 256, 512, 256, 256)]
|
||||
[InlineData(1024, 256, 512, 2048, 511)]
|
||||
public void Position_Array(int size, long offset, long length, long position, long expectedPosition)
|
||||
{
|
||||
byte[] data = new byte[size];
|
||||
var stream = new ViewStream(data, offset, length);
|
||||
stream.Position = position;
|
||||
Assert.Equal(expectedPosition, stream.Position);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 0, 0, -1, 0)]
|
||||
[InlineData(0, 0, 0, 0, 0)]
|
||||
[InlineData(0, 0, 0, 256, 0)]
|
||||
[InlineData(0, 0, 0, 2048, 0)]
|
||||
[InlineData(1024, 0, 1024, -1, 0)]
|
||||
[InlineData(1024, 0, 1024, 0, 0)]
|
||||
[InlineData(1024, 0, 1024, 256, 256)]
|
||||
[InlineData(1024, 0, 1024, 2048, 1023)]
|
||||
[InlineData(1024, 256, 512, -1, 0)]
|
||||
[InlineData(1024, 256, 512, 0, 0)]
|
||||
[InlineData(1024, 256, 512, 256, 256)]
|
||||
[InlineData(1024, 256, 512, 2048, 511)]
|
||||
public void Position_Stream(int size, long offset, long length, long position, long expectedPosition)
|
||||
{
|
||||
Stream data = new MemoryStream(new byte[size]);
|
||||
var stream = new ViewStream(data, offset, length);
|
||||
stream.Position = position;
|
||||
Assert.Equal(expectedPosition, stream.Position);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region SegmentValid
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 0, 0, -1, 0, false)]
|
||||
[InlineData(0, 0, 0, 2048, 0, false)]
|
||||
[InlineData(0, 0, 0, 0, 0, true)]
|
||||
[InlineData(0, 0, 0, 0, -1, false)]
|
||||
[InlineData(0, 0, 0, 0, 2048, false)]
|
||||
[InlineData(1024, 0, 1024, -1, 0, false)]
|
||||
[InlineData(1024, 0, 1024, 2048, 0, false)]
|
||||
[InlineData(1024, 0, 1024, 0, 0, true)]
|
||||
[InlineData(1024, 0, 1024, 256, 0, true)]
|
||||
[InlineData(1024, 0, 1024, 256, 256, true)]
|
||||
[InlineData(1024, 0, 1024, 0, -1, false)]
|
||||
[InlineData(1024, 0, 1024, 0, 2048, false)]
|
||||
[InlineData(1024, 256, 512, -1, 0, false)]
|
||||
[InlineData(1024, 256, 512, 2048, 0, false)]
|
||||
[InlineData(1024, 256, 512, 0, 0, true)]
|
||||
[InlineData(1024, 256, 512, 256, 0, true)]
|
||||
[InlineData(1024, 256, 512, 256, 256, true)]
|
||||
[InlineData(1024, 256, 512, 0, -1, false)]
|
||||
[InlineData(1024, 256, 512, 0, 2048, false)]
|
||||
public void SegmentValid_Array(int size, long offset, long length, int segmentStart, int segmentLength, bool expected)
|
||||
{
|
||||
byte[] data = new byte[size];
|
||||
var stream = new ViewStream(data, offset, length);
|
||||
bool actual = stream.SegmentValid(segmentStart, segmentLength);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 0, 0, -1, 0, false)]
|
||||
[InlineData(0, 0, 0, 2048, 0, false)]
|
||||
[InlineData(0, 0, 0, 0, 0, true)]
|
||||
[InlineData(0, 0, 0, 0, -1, false)]
|
||||
[InlineData(0, 0, 0, 0, 2048, false)]
|
||||
[InlineData(1024, 0, 1024, -1, 0, false)]
|
||||
[InlineData(1024, 0, 1024, 2048, 0, false)]
|
||||
[InlineData(1024, 0, 1024, 0, 0, true)]
|
||||
[InlineData(1024, 0, 1024, 256, 0, true)]
|
||||
[InlineData(1024, 0, 1024, 256, 256, true)]
|
||||
[InlineData(1024, 0, 1024, 0, -1, false)]
|
||||
[InlineData(1024, 0, 1024, 0, 2048, false)]
|
||||
[InlineData(1024, 256, 512, -1, 0, false)]
|
||||
[InlineData(1024, 256, 512, 2048, 0, false)]
|
||||
[InlineData(1024, 256, 512, 0, 0, true)]
|
||||
[InlineData(1024, 256, 512, 256, 0, true)]
|
||||
[InlineData(1024, 256, 512, 256, 256, true)]
|
||||
[InlineData(1024, 256, 512, 0, -1, false)]
|
||||
[InlineData(1024, 256, 512, 0, 2048, false)]
|
||||
public void SegmentValid_Stream(int size, long offset, long length, int segmentStart, int segmentLength, bool expected)
|
||||
{
|
||||
Stream data = new MemoryStream(new byte[size]);
|
||||
var stream = new ViewStream(data, offset, length);
|
||||
bool actual = stream.SegmentValid(segmentStart, segmentLength);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Read
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 0, 0, -1, 0)]
|
||||
[InlineData(0, 0, 0, 0, 0)]
|
||||
[InlineData(0, 0, 0, 2048, 0)]
|
||||
[InlineData(1024, 0, 1024, -1, 0)]
|
||||
[InlineData(1024, 0, 1024, 0, 0)]
|
||||
[InlineData(1024, 0, 1024, 256, 256)]
|
||||
[InlineData(1024, 0, 1024, 1024, 1024)]
|
||||
[InlineData(1024, 0, 1024, 2048, 0)]
|
||||
[InlineData(1024, 256, 512, -1, 0)]
|
||||
[InlineData(1024, 256, 512, 0, 0)]
|
||||
[InlineData(1024, 256, 512, 256, 256)]
|
||||
[InlineData(1024, 256, 512, 512, 512)]
|
||||
[InlineData(1024, 256, 512, 2048, 0)]
|
||||
public void Read_Array(int size, long offset, long length, int count, int expectedRead)
|
||||
{
|
||||
byte[] data = new byte[size];
|
||||
var stream = new ViewStream(data, offset, length);
|
||||
|
||||
byte[] buffer = new byte[1024];
|
||||
int actual = stream.Read(buffer, 0, count);
|
||||
Assert.Equal(expectedRead, actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 0, 0, -1, 0)]
|
||||
[InlineData(0, 0, 0, 0, 0)]
|
||||
[InlineData(0, 0, 0, 2048, 0)]
|
||||
[InlineData(1024, 0, 1024, -1, 0)]
|
||||
[InlineData(1024, 0, 1024, 0, 0)]
|
||||
[InlineData(1024, 0, 1024, 256, 256)]
|
||||
[InlineData(1024, 0, 1024, 1024, 1024)]
|
||||
[InlineData(1024, 0, 1024, 2048, 0)]
|
||||
[InlineData(1024, 256, 512, -1, 0)]
|
||||
[InlineData(1024, 256, 512, 0, 0)]
|
||||
[InlineData(1024, 256, 512, 256, 256)]
|
||||
[InlineData(1024, 256, 512, 512, 512)]
|
||||
[InlineData(1024, 256, 512, 2048, 0)]
|
||||
public void Read_Stream(int size, long offset, long length, int count, int expectedRead)
|
||||
{
|
||||
Stream data = new MemoryStream(new byte[size]);
|
||||
var stream = new ViewStream(data, offset, length);
|
||||
|
||||
byte[] buffer = new byte[1024];
|
||||
int actual = stream.Read(buffer, 0, count);
|
||||
Assert.Equal(expectedRead, actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Seek
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 0, 0, -1, SeekOrigin.Begin, 0)]
|
||||
[InlineData(0, 0, 0, -1, SeekOrigin.End, 0)]
|
||||
[InlineData(0, 0, 0, -1, SeekOrigin.Current, 0)]
|
||||
[InlineData(0, 0, 0, 0, SeekOrigin.Begin, 0)]
|
||||
[InlineData(0, 0, 0, 0, SeekOrigin.End, 0)]
|
||||
[InlineData(0, 0, 0, 0, SeekOrigin.Current, 0)]
|
||||
[InlineData(0, 0, 0, 256, SeekOrigin.Begin, 0)]
|
||||
[InlineData(0, 0, 0, 256, SeekOrigin.End, 0)]
|
||||
[InlineData(0, 0, 0, 256, SeekOrigin.Current, 0)]
|
||||
[InlineData(0, 0, 0, 2048, SeekOrigin.Begin, 0)]
|
||||
[InlineData(0, 0, 0, 2048, SeekOrigin.End, 0)]
|
||||
[InlineData(0, 0, 0, 2048, SeekOrigin.Current, 0)]
|
||||
[InlineData(1024, 0, 1024, -1, SeekOrigin.Begin, 0)]
|
||||
[InlineData(1024, 0, 1024, -1, SeekOrigin.End, 1022)]
|
||||
[InlineData(1024, 0, 1024, -1, SeekOrigin.Current, 0)]
|
||||
[InlineData(1024, 0, 1024, 0, SeekOrigin.Begin, 0)]
|
||||
[InlineData(1024, 0, 1024, 0, SeekOrigin.End, 1023)]
|
||||
[InlineData(1024, 0, 1024, 0, SeekOrigin.Current, 0)]
|
||||
[InlineData(1024, 0, 1024, 256, SeekOrigin.Begin, 256)]
|
||||
[InlineData(1024, 0, 1024, 256, SeekOrigin.End, 1023)]
|
||||
[InlineData(1024, 0, 1024, 256, SeekOrigin.Current, 256)]
|
||||
[InlineData(1024, 0, 1024, 2048, SeekOrigin.Begin, 1023)]
|
||||
[InlineData(1024, 0, 1024, 2048, SeekOrigin.End, 1023)]
|
||||
[InlineData(1024, 0, 1024, 2048, SeekOrigin.Current, 1023)]
|
||||
[InlineData(1024, 256, 512, -1, SeekOrigin.Begin, 0)]
|
||||
[InlineData(1024, 256, 512, -1, SeekOrigin.End, 510)]
|
||||
[InlineData(1024, 256, 512, -1, SeekOrigin.Current, 0)]
|
||||
[InlineData(1024, 256, 512, 0, SeekOrigin.Begin, 0)]
|
||||
[InlineData(1024, 256, 512, 0, SeekOrigin.End, 511)]
|
||||
[InlineData(1024, 256, 512, 0, SeekOrigin.Current, 0)]
|
||||
[InlineData(1024, 256, 512, 256, SeekOrigin.Begin, 256)]
|
||||
[InlineData(1024, 256, 512, 256, SeekOrigin.End, 511)]
|
||||
[InlineData(1024, 256, 512, 256, SeekOrigin.Current, 256)]
|
||||
[InlineData(1024, 256, 512, 2048, SeekOrigin.Begin, 511)]
|
||||
[InlineData(1024, 256, 512, 2048, SeekOrigin.End, 511)]
|
||||
[InlineData(1024, 256, 512, 2048, SeekOrigin.Current, 511)]
|
||||
public void Seek_Array(int size, long offset, long length, long position, SeekOrigin seekOrigin, long expectedPosition)
|
||||
{
|
||||
byte[] data = new byte[size];
|
||||
var stream = new ViewStream(data, offset, length);
|
||||
stream.Seek(position, seekOrigin);
|
||||
Assert.Equal(expectedPosition, stream.Position);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 0, 0, -1, SeekOrigin.Begin, 0)]
|
||||
[InlineData(0, 0, 0, -1, SeekOrigin.End, 0)]
|
||||
[InlineData(0, 0, 0, -1, SeekOrigin.Current, 0)]
|
||||
[InlineData(0, 0, 0, 0, SeekOrigin.Begin, 0)]
|
||||
[InlineData(0, 0, 0, 0, SeekOrigin.End, 0)]
|
||||
[InlineData(0, 0, 0, 0, SeekOrigin.Current, 0)]
|
||||
[InlineData(0, 0, 0, 256, SeekOrigin.Begin, 0)]
|
||||
[InlineData(0, 0, 0, 256, SeekOrigin.End, 0)]
|
||||
[InlineData(0, 0, 0, 256, SeekOrigin.Current, 0)]
|
||||
[InlineData(0, 0, 0, 2048, SeekOrigin.Begin, 0)]
|
||||
[InlineData(0, 0, 0, 2048, SeekOrigin.End, 0)]
|
||||
[InlineData(0, 0, 0, 2048, SeekOrigin.Current, 0)]
|
||||
[InlineData(1024, 0, 1024, -1, SeekOrigin.Begin, 0)]
|
||||
[InlineData(1024, 0, 1024, -1, SeekOrigin.End, 1022)]
|
||||
[InlineData(1024, 0, 1024, -1, SeekOrigin.Current, 0)]
|
||||
[InlineData(1024, 0, 1024, 0, SeekOrigin.Begin, 0)]
|
||||
[InlineData(1024, 0, 1024, 0, SeekOrigin.End, 1023)]
|
||||
[InlineData(1024, 0, 1024, 0, SeekOrigin.Current, 0)]
|
||||
[InlineData(1024, 0, 1024, 256, SeekOrigin.Begin, 256)]
|
||||
[InlineData(1024, 0, 1024, 256, SeekOrigin.End, 1023)]
|
||||
[InlineData(1024, 0, 1024, 256, SeekOrigin.Current, 256)]
|
||||
[InlineData(1024, 0, 1024, 2048, SeekOrigin.Begin, 1023)]
|
||||
[InlineData(1024, 0, 1024, 2048, SeekOrigin.End, 1023)]
|
||||
[InlineData(1024, 0, 1024, 2048, SeekOrigin.Current, 1023)]
|
||||
[InlineData(1024, 256, 512, -1, SeekOrigin.Begin, 0)]
|
||||
[InlineData(1024, 256, 512, -1, SeekOrigin.End, 510)]
|
||||
[InlineData(1024, 256, 512, -1, SeekOrigin.Current, 0)]
|
||||
[InlineData(1024, 256, 512, 0, SeekOrigin.Begin, 0)]
|
||||
[InlineData(1024, 256, 512, 0, SeekOrigin.End, 511)]
|
||||
[InlineData(1024, 256, 512, 0, SeekOrigin.Current, 0)]
|
||||
[InlineData(1024, 256, 512, 256, SeekOrigin.Begin, 256)]
|
||||
[InlineData(1024, 256, 512, 256, SeekOrigin.End, 511)]
|
||||
[InlineData(1024, 256, 512, 256, SeekOrigin.Current, 256)]
|
||||
[InlineData(1024, 256, 512, 2048, SeekOrigin.Begin, 511)]
|
||||
[InlineData(1024, 256, 512, 2048, SeekOrigin.End, 511)]
|
||||
[InlineData(1024, 256, 512, 2048, SeekOrigin.Current, 511)]
|
||||
public void Seek_Stream(int size, long offset, long length, long position, SeekOrigin seekOrigin, long expectedPosition)
|
||||
{
|
||||
Stream data = new MemoryStream(new byte[size]);
|
||||
var stream = new ViewStream(data, offset, length);
|
||||
stream.Seek(position, seekOrigin);
|
||||
Assert.Equal(expectedPosition, stream.Position);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unimplemented
|
||||
|
||||
[Fact]
|
||||
public void Flush_Array_Throws()
|
||||
{
|
||||
byte[] data = new byte[1024];
|
||||
var stream = new ViewStream(data, 0, 1024);
|
||||
Assert.Throws<NotImplementedException>(() => stream.Flush());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Flush_Stream_Throws()
|
||||
{
|
||||
Stream data = new MemoryStream(new byte[1024]);
|
||||
var stream = new ViewStream(data, 0, 1024);
|
||||
Assert.Throws<NotImplementedException>(() => stream.Flush());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetLength_Array_Throws()
|
||||
{
|
||||
byte[] data = new byte[1024];
|
||||
var stream = new ViewStream(data, 0, 1024);
|
||||
Assert.Throws<NotImplementedException>(() => stream.SetLength(0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetLength_Stream_Throws()
|
||||
{
|
||||
Stream data = new MemoryStream(new byte[1024]);
|
||||
var stream = new ViewStream(data, 0, 1024);
|
||||
Assert.Throws<NotImplementedException>(() => stream.SetLength(0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Write_Array_Throws()
|
||||
{
|
||||
byte[] data = new byte[1024];
|
||||
var stream = new ViewStream(data, 0, 1024);
|
||||
Assert.Throws<NotImplementedException>(() => stream.Write([], 0, 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Write_Stream_Throws()
|
||||
{
|
||||
Stream data = new MemoryStream(new byte[1024]);
|
||||
var stream = new ViewStream(data, 0, 1024);
|
||||
Assert.Throws<NotImplementedException>(() => stream.Write([], 0, 0));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
124
SabreTools.IO.Test/Transform/CombineTests.cs
Normal file
124
SabreTools.IO.Test/Transform/CombineTests.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SabreTools.IO.Transform;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.IO.Test.Transform
|
||||
{
|
||||
public class CombineTests
|
||||
{
|
||||
#region Concatenate
|
||||
|
||||
[Fact]
|
||||
public void Concatenate_EmptyList_False()
|
||||
{
|
||||
List<string> paths = [];
|
||||
string output = string.Empty;
|
||||
bool actual = Combine.Concatenate(paths, output);
|
||||
Assert.False(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Concatenate_InvalidOutput_False()
|
||||
{
|
||||
List<string> paths = ["a"];
|
||||
string output = string.Empty;
|
||||
bool actual = Combine.Concatenate(paths, output);
|
||||
Assert.False(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Concatenate_FilledList_True()
|
||||
{
|
||||
List<string> paths = [
|
||||
Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt"),
|
||||
Path.Combine(Environment.CurrentDirectory, "TestData", "file-to-compress.bin"),
|
||||
];
|
||||
string output = Guid.NewGuid().ToString();
|
||||
bool actual = Combine.Concatenate(paths, output);
|
||||
Assert.True(actual);
|
||||
|
||||
string text = File.ReadAllText(output);
|
||||
Assert.Equal("This doesn't match anythingThis is just a file that has a known set of hashes to make sure that everything with hashing is still working as anticipated.", text);
|
||||
|
||||
File.Delete(output);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Interleave
|
||||
|
||||
[Fact]
|
||||
public void Interleave_EvenNotExists_False()
|
||||
{
|
||||
string even = "NOT A REAL PATH";
|
||||
string odd = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt");
|
||||
string output = Guid.NewGuid().ToString();
|
||||
|
||||
bool actual = Combine.Interleave(even, odd, output, BlockSize.Byte);
|
||||
Assert.False(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interleave_OddNotExists_False()
|
||||
{
|
||||
string even = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt");
|
||||
string odd = "NOT A REAL PATH";
|
||||
string output = Guid.NewGuid().ToString();
|
||||
|
||||
bool actual = Combine.Interleave(even, odd, output, BlockSize.Byte);
|
||||
Assert.False(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interleave_InvalidType_False()
|
||||
{
|
||||
string even = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt");
|
||||
string odd = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt");
|
||||
string output = Guid.NewGuid().ToString();
|
||||
|
||||
bool actual = Combine.Interleave(even, odd, output, (BlockSize)int.MaxValue);
|
||||
Assert.False(actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(BlockSize.Byte, "TThhiiss ddooeessnn''tt mmaattcchh aannyytthhiinngg")]
|
||||
[InlineData(BlockSize.Word, "ThThisis d doeoesnsn't't m matatchch a anynyththiningg")]
|
||||
[InlineData(BlockSize.Dword, "ThisThis doe doesn'tsn't mat match ach anythnythinging")]
|
||||
[InlineData(BlockSize.Qword, "This doeThis doesn't matsn't match anythch anythinging")]
|
||||
public void Interleave_SameLength_True(BlockSize type, string expected)
|
||||
{
|
||||
string even = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt");
|
||||
string odd = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt");
|
||||
string output = Guid.NewGuid().ToString();
|
||||
|
||||
bool actual = Combine.Interleave(even, odd, output, type);
|
||||
Assert.True(actual);
|
||||
|
||||
string text = File.ReadAllText(output);
|
||||
Assert.Equal(expected, text);
|
||||
|
||||
File.Delete(output);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interleave_DifferentLength_True()
|
||||
{
|
||||
string even = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt");
|
||||
string odd = Path.Combine(Environment.CurrentDirectory, "TestData", "file-to-compress.bin");
|
||||
|
||||
string output = Guid.NewGuid().ToString();
|
||||
|
||||
bool actual = Combine.Interleave(even, odd, output, BlockSize.Byte);
|
||||
Assert.True(actual);
|
||||
|
||||
string text = File.ReadAllText(output);
|
||||
Assert.Equal("TThhiiss diose sjnu'stt maa tfcihl ea ntyhtahti nhgas a known set of hashes to make sure that everything with hashing is still working as anticipated.", text);
|
||||
|
||||
File.Delete(output);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
124
SabreTools.IO.Test/Transform/SplitTests.cs
Normal file
124
SabreTools.IO.Test/Transform/SplitTests.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SabreTools.IO.Transform;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.IO.Test.Transform
|
||||
{
|
||||
public class SplitTests
|
||||
{
|
||||
#region BlockSplit
|
||||
|
||||
[Fact]
|
||||
public void BlockSplit_EmptyFileName_False()
|
||||
{
|
||||
string input = string.Empty;
|
||||
string outputDir = string.Empty;
|
||||
bool actual = Split.BlockSplit(input, outputDir, BlockSize.Byte);
|
||||
Assert.False(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BlockSplit_InvalidFile_False()
|
||||
{
|
||||
string input = "INVALID";
|
||||
string outputDir = string.Empty;
|
||||
bool actual = Split.BlockSplit(input, outputDir, BlockSize.Byte);
|
||||
Assert.False(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BlockSplit_InvalidType_False()
|
||||
{
|
||||
string input = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt");
|
||||
string outputDir = Guid.NewGuid().ToString();
|
||||
|
||||
bool actual = Split.BlockSplit(input, outputDir, (BlockSize)int.MaxValue);
|
||||
Assert.False(actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(BlockSize.Byte, "Ti os' ac ntig", "hsdentmthayhn")]
|
||||
[InlineData(BlockSize.Word, "Th dsn mchnyin", "isoe'tat athg")]
|
||||
[InlineData(BlockSize.Dword, "Thissn'tch aing", " doe matnyth")]
|
||||
[InlineData(BlockSize.Qword, "This doech anyth", "sn't mating")]
|
||||
public void BlockSplit_ValidFile_True(BlockSize type, string expectedEven, string expectedOdd)
|
||||
{
|
||||
string input = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt");
|
||||
string outputDir = Guid.NewGuid().ToString();
|
||||
|
||||
bool actual = Split.BlockSplit(input, outputDir, type);
|
||||
Assert.True(actual);
|
||||
|
||||
string baseFilename = Path.GetFileName(input);
|
||||
string text = File.ReadAllText(Path.Combine(outputDir, $"{baseFilename}.even"));
|
||||
Assert.Equal(expectedEven, text);
|
||||
text = File.ReadAllText(Path.Combine(outputDir, $"{baseFilename}.odd"));
|
||||
Assert.Equal(expectedOdd, text);
|
||||
|
||||
File.Delete($"{baseFilename}.even");
|
||||
File.Delete($"{baseFilename}.odd");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region SizeSplit
|
||||
|
||||
[Fact]
|
||||
public void SizeSplit_EmptyFileName_False()
|
||||
{
|
||||
string input = string.Empty;
|
||||
string outputDir = string.Empty;
|
||||
int size = 1;
|
||||
|
||||
bool actual = Split.SizeSplit(input, outputDir, size);
|
||||
Assert.False(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SizeSplit_InvalidFile_False()
|
||||
{
|
||||
string input = "INVALID";
|
||||
string outputDir = string.Empty;
|
||||
int size = 1;
|
||||
|
||||
bool actual = Split.SizeSplit(input, outputDir, size);
|
||||
Assert.False(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SizeSplit_InvalidSize_False()
|
||||
{
|
||||
string input = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt");
|
||||
string outputDir = string.Empty;
|
||||
int size = 0;
|
||||
|
||||
bool actual = Split.SizeSplit(input, outputDir, size);
|
||||
Assert.False(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SizeSplit_Valid_True()
|
||||
{
|
||||
string input = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt");
|
||||
string outputDir = Guid.NewGuid().ToString();
|
||||
int size = 16;
|
||||
|
||||
bool actual = Split.SizeSplit(input, outputDir, size);
|
||||
Assert.True(actual);
|
||||
|
||||
Assert.Equal(2, Directory.GetFiles(outputDir).Length);
|
||||
|
||||
string baseFilename = Path.GetFileName(input);
|
||||
string text = File.ReadAllText(Path.Combine(outputDir, $"{baseFilename}.0"));
|
||||
Assert.Equal("This doesn't mat", text);
|
||||
text = File.ReadAllText(Path.Combine(outputDir, $"{baseFilename}.1"));
|
||||
Assert.Equal("ch anything", text);
|
||||
|
||||
File.Delete($"{baseFilename}.0");
|
||||
File.Delete($"{baseFilename}.1");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
76
SabreTools.IO.Test/Transform/SwapTests.cs
Normal file
76
SabreTools.IO.Test/Transform/SwapTests.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.IO.Transform;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.IO.Test.Transform
|
||||
{
|
||||
public class SwapTests
|
||||
{
|
||||
#region Process
|
||||
|
||||
[Fact]
|
||||
public void Process_EmptyFileName_False()
|
||||
{
|
||||
string input = string.Empty;
|
||||
string output = string.Empty;
|
||||
bool actual = Swap.Process(input, output, Operation.Byteswap);
|
||||
Assert.False(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Process_InvalidFile_False()
|
||||
{
|
||||
string input = "INVALID";
|
||||
string output = string.Empty;
|
||||
bool actual = Swap.Process(input, output, Operation.Byteswap);
|
||||
Assert.False(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Process_InvalidType_False()
|
||||
{
|
||||
string input = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt");
|
||||
string output = Guid.NewGuid().ToString();
|
||||
|
||||
bool actual = Swap.Process(input, output, (Operation)int.MaxValue);
|
||||
Assert.False(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Process_Valid_True()
|
||||
{
|
||||
string input = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt");
|
||||
string output = Guid.NewGuid().ToString();
|
||||
|
||||
// Bitswap
|
||||
bool actual = Swap.Process(input, output, Operation.Bitswap);
|
||||
Assert.True(actual);
|
||||
byte[] actualBytes = File.ReadAllBytes(output);
|
||||
Assert.True(new byte[] { 0x2A, 0x16, 0x96, 0xCE, 0x04, 0x26, 0xF6, 0xA6, 0xCE, 0x76, 0xE4, 0x2E, 0x04, 0xB6, 0x86, 0x2E, 0xC6, 0x16, 0x04, 0x86, 0x76, 0x9E, 0x2E, 0x16, 0x96, 0x76, 0xE6 }.EqualsExactly(actualBytes));
|
||||
|
||||
// Byteswap
|
||||
actual = Swap.Process(input, output, Operation.Byteswap);
|
||||
Assert.True(actual);
|
||||
actualBytes = File.ReadAllBytes(output);
|
||||
Assert.True(new byte[] { 0x68, 0x54, 0x73, 0x69, 0x64, 0x20, 0x65, 0x6F, 0x6E, 0x73, 0x74, 0x27, 0x6D, 0x20, 0x74, 0x61, 0x68, 0x63, 0x61, 0x20, 0x79, 0x6E, 0x68, 0x74, 0x6E, 0x69, 0x67 }.EqualsExactly(actualBytes));
|
||||
|
||||
// Wordswap
|
||||
actual = Swap.Process(input, output, Operation.Wordswap);
|
||||
Assert.True(actual);
|
||||
actualBytes = File.ReadAllBytes(output);
|
||||
Assert.True(new byte[] { 0x69, 0x73, 0x54, 0x68, 0x6F, 0x65, 0x20, 0x64, 0x27, 0x74, 0x73, 0x6E, 0x61, 0x74, 0x20, 0x6D, 0x20, 0x61, 0x63, 0x68, 0x74, 0x68, 0x6E, 0x79, 0x69, 0x6E, 0x67 }.EqualsExactly(actualBytes));
|
||||
|
||||
// WordByteswap
|
||||
actual = Swap.Process(input, output, Operation.WordByteswap);
|
||||
Assert.True(actual);
|
||||
actualBytes = File.ReadAllBytes(output);
|
||||
Assert.True(new byte[] { 0x73, 0x69, 0x68, 0x54, 0x65, 0x6F, 0x64, 0x20, 0x74, 0x27, 0x6E, 0x73, 0x74, 0x61, 0x6D, 0x20, 0x61, 0x20, 0x68, 0x63, 0x68, 0x74, 0x79, 0x6E, 0x69, 0x6E, 0x67 }.EqualsExactly(actualBytes));
|
||||
|
||||
File.Delete(output);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
92
SabreTools.IO/Compare/NaturalComparer.cs
Normal file
92
SabreTools.IO/Compare/NaturalComparer.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
*
|
||||
* Links for info and original source code:
|
||||
*
|
||||
* https://blog.codinghorror.com/sorting-for-humans-natural-sort-order/
|
||||
* http://www.codeproject.com/Articles/22517/Natural-Sort-Comparer
|
||||
*
|
||||
* Exact code implementation used with permission, originally by motoschifo
|
||||
*
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SabreTools.Text.Compare
|
||||
{
|
||||
public class NaturalComparer : Comparer<string>, IDisposable
|
||||
{
|
||||
private readonly Dictionary<string, string[]> _table;
|
||||
|
||||
public NaturalComparer()
|
||||
{
|
||||
_table = [];
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_table.Clear();
|
||||
}
|
||||
|
||||
public override int Compare(string? x, string? y)
|
||||
{
|
||||
if (x == null || y == null)
|
||||
{
|
||||
if (x == null && y != null)
|
||||
return -1;
|
||||
else if (x != null && y == null)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (x.ToLowerInvariant() == y.ToLowerInvariant())
|
||||
return x.CompareTo(y);
|
||||
|
||||
if (!_table.TryGetValue(x, out string[]? x1))
|
||||
{
|
||||
//x1 = Regex.Split(x.Replace(" ", string.Empty), "([0-9]+)");
|
||||
x1 = Regex.Split(x.ToLowerInvariant(), "([0-9]+)");
|
||||
x1 = Array.FindAll(x1, s => !string.IsNullOrEmpty(s));
|
||||
_table.Add(x, x1);
|
||||
}
|
||||
|
||||
if (!_table.TryGetValue(y, out string[]? y1))
|
||||
{
|
||||
//y1 = Regex.Split(y.Replace(" ", string.Empty), "([0-9]+)");
|
||||
y1 = Regex.Split(y.ToLowerInvariant(), "([0-9]+)");
|
||||
y1 = Array.FindAll(y1, s => !string.IsNullOrEmpty(s));
|
||||
_table.Add(y, y1);
|
||||
}
|
||||
|
||||
for (int i = 0; i < x1.Length && i < y1.Length; i++)
|
||||
{
|
||||
if (x1[i] != y1[i])
|
||||
return PartCompare(x1[i], y1[i]);
|
||||
}
|
||||
|
||||
if (x1.Length > y1.Length)
|
||||
return 1;
|
||||
else if (y1.Length > x1.Length)
|
||||
return -1;
|
||||
else
|
||||
return x.CompareTo(y);
|
||||
}
|
||||
|
||||
private static int PartCompare(string left, string right)
|
||||
{
|
||||
if (!long.TryParse(left, out long x))
|
||||
return NaturalComparerUtil.ComparePaths(left, right);
|
||||
|
||||
if (!long.TryParse(right, out long y))
|
||||
return NaturalComparerUtil.ComparePaths(left, right);
|
||||
|
||||
// If we have an equal part, then make sure that "longer" ones are taken into account
|
||||
if (x.CompareTo(y) == 0)
|
||||
return left.Length - right.Length;
|
||||
|
||||
return x.CompareTo(y);
|
||||
}
|
||||
}
|
||||
}
|
||||
96
SabreTools.IO/Compare/NaturalComparerUtil.cs
Normal file
96
SabreTools.IO/Compare/NaturalComparerUtil.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
namespace SabreTools.Text.Compare
|
||||
{
|
||||
internal static class NaturalComparerUtil
|
||||
{
|
||||
/// <summary>
|
||||
/// Compare two strings by path parts
|
||||
/// </summary>
|
||||
public static int ComparePaths(string? left, string? right)
|
||||
{
|
||||
// If both strings are null, return
|
||||
if (left == null && right == null)
|
||||
return 0;
|
||||
|
||||
// If one is null, then say that's less than
|
||||
if (left == null)
|
||||
return -1;
|
||||
if (right == null)
|
||||
return 1;
|
||||
|
||||
// Normalize the path seperators
|
||||
left = left.Replace('\\', '/');
|
||||
right = right.Replace('\\', '/');
|
||||
|
||||
// Save the orginal adjusted strings
|
||||
string leftOrig = left;
|
||||
string rightOrig = right;
|
||||
|
||||
// Normalize strings by lower-case
|
||||
left = leftOrig.ToLowerInvariant();
|
||||
right = rightOrig.ToLowerInvariant();
|
||||
|
||||
// If the strings are the same exactly, return
|
||||
if (left == right)
|
||||
return leftOrig.CompareTo(rightOrig);
|
||||
|
||||
// Now split into path parts
|
||||
string[] leftParts = left.Split('/');
|
||||
string[] rightParts = right.Split('/');
|
||||
|
||||
// Then compare each part in turn
|
||||
for (int i = 0; i < leftParts.Length && i < rightParts.Length; i++)
|
||||
{
|
||||
int partCompare = ComparePathSegment(leftParts[i], rightParts[i]);
|
||||
if (partCompare != 0)
|
||||
return partCompare;
|
||||
}
|
||||
|
||||
// If we got out here, then it looped through at least one of the strings
|
||||
if (leftParts.Length > rightParts.Length)
|
||||
return 1;
|
||||
if (leftParts.Length < rightParts.Length)
|
||||
return -1;
|
||||
|
||||
return leftOrig.CompareTo(rightOrig);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two path segments deterministically
|
||||
/// </summary>
|
||||
private static int ComparePathSegment(string left, string right)
|
||||
{
|
||||
// If the lengths are both zero, they're equal
|
||||
if (left.Length == 0 && right.Length == 0)
|
||||
return 0;
|
||||
|
||||
// Shorter strings are sorted before
|
||||
if (left.Length == 0)
|
||||
return -1;
|
||||
if (right.Length == 0)
|
||||
return 1;
|
||||
|
||||
// Otherwise, loop through until we have an answer
|
||||
for (int i = 0; i < left.Length && i < right.Length; i++)
|
||||
{
|
||||
// Get the next characters from the inputs as integers
|
||||
int leftChar = left[i];
|
||||
int rightChar = right[i];
|
||||
|
||||
// If the characters are the same, continue
|
||||
if (leftChar == rightChar)
|
||||
continue;
|
||||
|
||||
// If they're different, check which one was larger
|
||||
return leftChar > rightChar ? 1 : -1;
|
||||
}
|
||||
|
||||
// If we got out here, then it looped through at least one of the strings
|
||||
if (left.Length > right.Length)
|
||||
return 1;
|
||||
if (left.Length < right.Length)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
92
SabreTools.IO/Compare/NaturalReversedComparer.cs
Normal file
92
SabreTools.IO/Compare/NaturalReversedComparer.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
*
|
||||
* Links for info and original source code:
|
||||
*
|
||||
* https://blog.codinghorror.com/sorting-for-humans-natural-sort-order/
|
||||
* http://www.codeproject.com/Articles/22517/Natural-Sort-Comparer
|
||||
*
|
||||
* Exact code implementation used with permission, originally by motoschifo
|
||||
*
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SabreTools.Text.Compare
|
||||
{
|
||||
public class NaturalReversedComparer : Comparer<string>, IDisposable
|
||||
{
|
||||
private readonly Dictionary<string, string[]> _table;
|
||||
|
||||
public NaturalReversedComparer()
|
||||
{
|
||||
_table = [];
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_table.Clear();
|
||||
}
|
||||
|
||||
public override int Compare(string? x, string? y)
|
||||
{
|
||||
if (x == null || y == null)
|
||||
{
|
||||
if (x == null && y != null)
|
||||
return -1;
|
||||
else if (x != null && y == null)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (y.ToLowerInvariant() == x.ToLowerInvariant())
|
||||
return y.CompareTo(x);
|
||||
|
||||
if (!_table.TryGetValue(x, out string[]? x1))
|
||||
{
|
||||
//x1 = Regex.Split(x.Replace(" ", string.Empty), "([0-9]+)");
|
||||
x1 = Regex.Split(x.ToLowerInvariant(), "([0-9]+)");
|
||||
x1 = Array.FindAll(x1, s => !string.IsNullOrEmpty(s));
|
||||
_table.Add(x, x1);
|
||||
}
|
||||
|
||||
if (!_table.TryGetValue(y, out string[]? y1))
|
||||
{
|
||||
//y1 = Regex.Split(y.Replace(" ", string.Empty), "([0-9]+)");
|
||||
y1 = Regex.Split(y.ToLowerInvariant(), "([0-9]+)");
|
||||
y1 = Array.FindAll(y1, s => !string.IsNullOrEmpty(s));
|
||||
_table.Add(y, y1);
|
||||
}
|
||||
|
||||
for (int i = 0; i < x1.Length && i < y1.Length; i++)
|
||||
{
|
||||
if (x1[i] != y1[i])
|
||||
return PartCompare(x1[i], y1[i]);
|
||||
}
|
||||
|
||||
if (y1.Length > x1.Length)
|
||||
return 1;
|
||||
else if (x1.Length > y1.Length)
|
||||
return -1;
|
||||
else
|
||||
return y.CompareTo(x);
|
||||
}
|
||||
|
||||
private static int PartCompare(string left, string right)
|
||||
{
|
||||
if (!long.TryParse(left, out long x))
|
||||
return NaturalComparerUtil.ComparePaths(right, left);
|
||||
|
||||
if (!long.TryParse(right, out long y))
|
||||
return NaturalComparerUtil.ComparePaths(right, left);
|
||||
|
||||
// If we have an equal part, then make sure that "longer" ones are taken into account
|
||||
if (y.CompareTo(x) == 0)
|
||||
return right.Length - left.Length;
|
||||
|
||||
return y.CompareTo(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
26
SabreTools.IO/Compression/Deflate/DeflateInfo.cs
Normal file
26
SabreTools.IO/Compression/Deflate/DeflateInfo.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
namespace SabreTools.IO.Compression.Deflate
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents information about a DEFLATE stream
|
||||
/// </summary>
|
||||
public class DeflateInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Size of the deflated data
|
||||
/// </summary>
|
||||
/// <remarks>Set to a value less than 0 to ignore</remarks>
|
||||
public long InputSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Size of the inflated data
|
||||
/// </summary>
|
||||
/// <remarks>Set to a value less than 0 to ignore</remarks>
|
||||
public long OutputSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// CRC-32 of the inflated data
|
||||
/// </summary>
|
||||
/// <remarks>Set to a value of 0 to ignore</remarks>
|
||||
public uint Crc32 { get; set; }
|
||||
}
|
||||
}
|
||||
34
SabreTools.IO/Compression/Deflate/Enums.cs
Normal file
34
SabreTools.IO/Compression/Deflate/Enums.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
namespace SabreTools.IO.Compression.Deflate
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the status returned from extracting
|
||||
/// </summary>
|
||||
public enum ExtractionStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// Extraction wasn't performed because the inputs were invalid
|
||||
/// </summary>
|
||||
INVALID,
|
||||
|
||||
/// <summary>
|
||||
/// No issues with the extraction
|
||||
/// </summary>
|
||||
GOOD,
|
||||
|
||||
/// <summary>
|
||||
/// File extracted but was the wrong size
|
||||
/// </summary>
|
||||
/// <remarks>Rewinds the stream and deletes the bad file</remarks>
|
||||
WRONG_SIZE,
|
||||
|
||||
/// <summary>
|
||||
/// File extracted but had the wrong CRC-32 value
|
||||
/// </summary>
|
||||
BAD_CRC,
|
||||
|
||||
/// <summary>
|
||||
/// Extraction failed entirely
|
||||
/// </summary>
|
||||
FAIL,
|
||||
}
|
||||
}
|
||||
470
SabreTools.IO/Compression/Deflate/InflateWrapper.cs
Normal file
470
SabreTools.IO/Compression/Deflate/InflateWrapper.cs
Normal file
@@ -0,0 +1,470 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using SabreTools.Hashing;
|
||||
using SabreTools.IO.Extensions;
|
||||
|
||||
namespace SabreTools.IO.Compression.Deflate
|
||||
{
|
||||
/// <summary>
|
||||
/// Wrapper to handle DEFLATE decompression with data verification
|
||||
/// </summary>
|
||||
public class InflateWrapper
|
||||
{
|
||||
#region Constants
|
||||
|
||||
/// <summary>
|
||||
/// Buffer size for decompression
|
||||
/// </summary>
|
||||
private const int BufferSize = 1024 * 1024;
|
||||
|
||||
/// <summary>
|
||||
/// Local file header signature
|
||||
/// </summary>
|
||||
private const uint LocalFileHeaderSignature = 0x04034B50;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Classes
|
||||
|
||||
/// <summary>
|
||||
/// Minimal PKZIP local file header information
|
||||
/// </summary>
|
||||
private class MinLocalFileHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// Signature (0x04034B50)
|
||||
/// </summary>
|
||||
public uint Signature { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// CRC-32
|
||||
/// </summary>
|
||||
public uint CRC32 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Compressed size
|
||||
/// </summary>
|
||||
public uint CompressedSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Uncompressed size
|
||||
/// </summary>
|
||||
public uint UncompressedSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// File name (variable size)
|
||||
/// </summary>
|
||||
public string? FileName { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Extraction
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to extract a file defined by a filename
|
||||
/// </summary>
|
||||
/// <param name="source">Stream representing the deflated data</param>
|
||||
/// <param name="filename">Output filename, null to auto-generate</param>
|
||||
/// <param name="outputDirectory">Output directory to write to</param>
|
||||
/// <param name="expected">Expected DEFLATE stream information</param>
|
||||
/// <param name="pkzip">Indicates if PKZIP containers are used</param>
|
||||
/// <param name="includeDebug">True to include debug data, false otherwise</param>
|
||||
/// <returns>Extraction status representing the final state</returns>
|
||||
/// <remarks>Assumes that the current stream position is where the compressed data lives</remarks>
|
||||
public static ExtractionStatus ExtractFile(Stream source,
|
||||
string? filename,
|
||||
string outputDirectory,
|
||||
DeflateInfo expected,
|
||||
bool pkzip,
|
||||
bool includeDebug)
|
||||
{
|
||||
// Debug output
|
||||
if (includeDebug) Console.WriteLine($"Attempting to extract {filename}");
|
||||
|
||||
// Extract the file
|
||||
var destination = new MemoryStream();
|
||||
ExtractionStatus status = ExtractStream(source,
|
||||
destination,
|
||||
expected,
|
||||
pkzip,
|
||||
includeDebug,
|
||||
out var foundFilename);
|
||||
|
||||
// If the extracted data is invalid
|
||||
if (status != ExtractionStatus.GOOD || destination == null)
|
||||
return status;
|
||||
|
||||
// Ensure directory separators are consistent
|
||||
filename ??= foundFilename ?? $"FILE_[{expected.InputSize}, {expected.OutputSize}, {expected.Crc32}]";
|
||||
if (Path.DirectorySeparatorChar == '\\')
|
||||
filename = filename.Replace('/', '\\');
|
||||
else if (Path.DirectorySeparatorChar == '/')
|
||||
filename = filename.Replace('\\', '/');
|
||||
|
||||
// Ensure the full output directory exists
|
||||
filename = Path.Combine(outputDirectory, filename);
|
||||
var directoryName = Path.GetDirectoryName(filename);
|
||||
if (directoryName != null && !Directory.Exists(directoryName))
|
||||
Directory.CreateDirectory(directoryName);
|
||||
|
||||
// Write the output file
|
||||
File.WriteAllBytes(filename, destination.ToArray());
|
||||
return status;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to extract a file to a stream
|
||||
/// </summary>
|
||||
/// <param name="source">Stream representing the deflated data</param>
|
||||
/// <param name="destination">Stream where the inflated data will be written</param>
|
||||
/// <param name="expected">Expected DEFLATE stream information</param>
|
||||
/// <param name="pkzip">Indicates if PKZIP containers are used</param>
|
||||
/// <param name="includeDebug">True to include debug data, false otherwise</param>
|
||||
/// <param name="filename">Output filename if extracted from the data, null otherwise</param>
|
||||
/// <returns>Extraction status representing the final state</returns>
|
||||
/// <remarks>Assumes that the current stream position is where the compressed data lives</remarks>
|
||||
public static ExtractionStatus ExtractStream(Stream source,
|
||||
Stream destination,
|
||||
DeflateInfo expected,
|
||||
bool pkzip,
|
||||
bool includeDebug,
|
||||
out string? filename)
|
||||
{
|
||||
// If PKZIP containers are used
|
||||
if (pkzip)
|
||||
return ExtractStreamWithContainer(source, destination, expected, includeDebug, out filename);
|
||||
|
||||
// If post-data checksums are used
|
||||
filename = null;
|
||||
return ExtractStreamWithChecksum(source, destination, expected, includeDebug);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract source data in a PKZIP container
|
||||
/// </summary>
|
||||
/// <param name="source">Stream representing the deflated data</param>
|
||||
/// <param name="destination">Stream where the inflated data will be written</param>
|
||||
/// <param name="expected">Expected DEFLATE stream information</param>
|
||||
/// <param name="includeDebug">True to include debug data, false otherwise</param>
|
||||
/// <param name="filename">Filename from the PKZIP header, if it exists</param>
|
||||
/// <returns></returns>
|
||||
public static ExtractionStatus ExtractStreamWithContainer(Stream source,
|
||||
Stream destination,
|
||||
DeflateInfo expected,
|
||||
bool includeDebug,
|
||||
out string? filename)
|
||||
{
|
||||
// Set default values
|
||||
filename = null;
|
||||
|
||||
// Debug output
|
||||
if (includeDebug) Console.WriteLine($"Offset: {source.Position:X8}, Expected Read: {expected.InputSize}, Expected Write: {expected.OutputSize}, Expected CRC-32: {expected.Crc32:X8}");
|
||||
|
||||
// Check the validity of the inputs
|
||||
if (expected.InputSize == 0)
|
||||
{
|
||||
if (includeDebug) Console.Error.WriteLine($"Not attempting to extract, expected to read 0 bytes");
|
||||
return ExtractionStatus.INVALID;
|
||||
}
|
||||
else if (expected.InputSize > (source.Length - source.Position))
|
||||
{
|
||||
if (includeDebug) Console.Error.WriteLine($"Not attempting to extract, expected to read {expected.InputSize} bytes but only {source.Length - source.Position} bytes remain");
|
||||
return ExtractionStatus.INVALID;
|
||||
}
|
||||
|
||||
// Cache the current offset
|
||||
long current = source.Position;
|
||||
|
||||
// Parse the PKZIP header, if it exists
|
||||
MinLocalFileHeader? zipHeader = ParseLocalFileHeader(source);
|
||||
long zipHeaderBytes = source.Position - current;
|
||||
|
||||
// Always trust the PKZIP CRC-32 value over what is supplied
|
||||
if (zipHeader != null)
|
||||
expected.Crc32 = zipHeader.CRC32;
|
||||
|
||||
// If the filename is [NULL], replace with the zip filename
|
||||
if (zipHeader?.FileName != null)
|
||||
{
|
||||
filename = zipHeader.FileName;
|
||||
if (includeDebug) Console.WriteLine($"Filename from PKZIP header: {filename}");
|
||||
}
|
||||
|
||||
// Debug output
|
||||
if (includeDebug) Console.WriteLine($"PKZIP Filename: {zipHeader?.FileName}, PKZIP Expected Read: {zipHeader?.CompressedSize}, PKZIP Expected Write: {zipHeader?.UncompressedSize}, PKZIP Expected CRC-32: {zipHeader?.CRC32:X4}");
|
||||
|
||||
// Extract the file
|
||||
var actual = Inflate(source, destination);
|
||||
if (actual == null)
|
||||
{
|
||||
if (includeDebug) Console.Error.WriteLine($"Could not extract {filename}");
|
||||
return ExtractionStatus.FAIL;
|
||||
}
|
||||
|
||||
// Account for the header bytes read
|
||||
actual.InputSize += zipHeaderBytes;
|
||||
source.Seek(current + actual.InputSize, SeekOrigin.Begin);
|
||||
|
||||
// Verify the extracted data
|
||||
return VerifyExtractedData(source, current, expected, actual, includeDebug);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract source data with a trailing CRC-32 checksum
|
||||
/// </summary>
|
||||
/// <param name="source">Stream representing the deflated data</param>
|
||||
/// <param name="destination">Stream where the inflated data will be written</param>
|
||||
/// <param name="expected">Expected DEFLATE stream information</param>
|
||||
/// <param name="includeDebug">True to include debug data, false otherwise</param>
|
||||
/// <returns></returns>
|
||||
public static ExtractionStatus ExtractStreamWithChecksum(Stream source,
|
||||
Stream destination,
|
||||
DeflateInfo expected,
|
||||
bool includeDebug)
|
||||
{
|
||||
// Debug output
|
||||
if (includeDebug) Console.WriteLine($"Offset: {source.Position:X8}, Expected Read: {expected.InputSize}, Expected Write: {expected.OutputSize}, Expected CRC-32: {expected.Crc32:X8}");
|
||||
|
||||
// Check the validity of the inputs
|
||||
if (expected.InputSize == 0)
|
||||
{
|
||||
if (includeDebug) Console.Error.WriteLine($"Not attempting to extract, expected to read 0 bytes");
|
||||
return ExtractionStatus.INVALID;
|
||||
}
|
||||
else if (expected.InputSize > (source.Length - source.Position))
|
||||
{
|
||||
if (includeDebug) Console.Error.WriteLine($"Not attempting to extract, expected to read {expected.InputSize} bytes but only {source.Length - source.Position} bytes remain");
|
||||
return ExtractionStatus.INVALID;
|
||||
}
|
||||
|
||||
// Cache the current offset
|
||||
long current = source.Position;
|
||||
|
||||
// Extract the file
|
||||
var actual = Inflate(source, destination);
|
||||
if (actual == null)
|
||||
{
|
||||
if (includeDebug) Console.Error.WriteLine($"Could not extract");
|
||||
return ExtractionStatus.FAIL;
|
||||
}
|
||||
|
||||
// Seek to the true end of the data
|
||||
source.Seek(current + actual.InputSize, SeekOrigin.Begin);
|
||||
|
||||
// If the read value is off-by-one after checksum
|
||||
if (actual.InputSize == expected.InputSize - 5)
|
||||
{
|
||||
// If not at the end of the file, get the corrected offset
|
||||
if (source.Position + 5 < source.Length)
|
||||
{
|
||||
// TODO: What does this byte represent?
|
||||
byte padding = source.ReadByteValue();
|
||||
actual.InputSize += 1;
|
||||
|
||||
// Debug output
|
||||
if (includeDebug) Console.WriteLine($"Off-by-one padding byte detected: 0x{padding:X2}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Debug output
|
||||
if (includeDebug) Console.WriteLine($"Not enough data to adjust offset");
|
||||
}
|
||||
}
|
||||
|
||||
// If there is enough data to read the full CRC
|
||||
uint deflateCrc;
|
||||
if (source.Position + 4 < source.Length)
|
||||
{
|
||||
deflateCrc = source.ReadUInt32LittleEndian();
|
||||
actual.InputSize += 4;
|
||||
}
|
||||
// Otherwise, read what is possible and pad with 0x00
|
||||
else
|
||||
{
|
||||
byte[] deflateCrcBytes = new byte[4];
|
||||
int realCrcLength = source.Read(deflateCrcBytes, 0, (int)(source.Length - source.Position));
|
||||
|
||||
// Parse as a little-endian 32-bit value
|
||||
deflateCrc = (uint)(deflateCrcBytes[0]
|
||||
| (deflateCrcBytes[1] << 8)
|
||||
| (deflateCrcBytes[2] << 16)
|
||||
| (deflateCrcBytes[3] << 24));
|
||||
|
||||
actual.InputSize += realCrcLength;
|
||||
}
|
||||
|
||||
// If the CRC to check isn't set
|
||||
if (expected.Crc32 == 0)
|
||||
expected.Crc32 = deflateCrc;
|
||||
|
||||
// Debug output
|
||||
if (includeDebug) Console.WriteLine($"DeflateStream CRC-32: {deflateCrc:X8}");
|
||||
|
||||
// Verify the extracted data
|
||||
return VerifyExtractedData(source, current, expected, actual, includeDebug);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a minimal local file header
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled minimal local file header on success, null on error</returns>
|
||||
/// <remarks>Partial mirror of method in Serialization</remarks>
|
||||
private static MinLocalFileHeader? ParseLocalFileHeader(Stream data)
|
||||
{
|
||||
var header = new MinLocalFileHeader();
|
||||
|
||||
header.Signature = data.ReadUInt32LittleEndian();
|
||||
if (header.Signature != LocalFileHeaderSignature)
|
||||
return null;
|
||||
|
||||
_ = data.ReadUInt16LittleEndian(); // Version
|
||||
_ = data.ReadUInt16LittleEndian(); // Flags
|
||||
_ = data.ReadUInt16LittleEndian(); // CompressionMethod
|
||||
_ = data.ReadUInt16LittleEndian(); // LastModifedFileTime
|
||||
_ = data.ReadUInt16LittleEndian(); // LastModifiedFileDate
|
||||
header.CRC32 = data.ReadUInt32LittleEndian();
|
||||
header.CompressedSize = data.ReadUInt32LittleEndian();
|
||||
header.UncompressedSize = data.ReadUInt32LittleEndian();
|
||||
ushort fileNameLength = data.ReadUInt16LittleEndian();
|
||||
ushort extraFieldLength = data.ReadUInt16LittleEndian();
|
||||
|
||||
if (fileNameLength > 0 && data.Position + fileNameLength <= data.Length)
|
||||
{
|
||||
byte[] filenameBytes = data.ReadBytes(fileNameLength);
|
||||
header.FileName = Encoding.ASCII.GetString(filenameBytes);
|
||||
}
|
||||
|
||||
// Parsing extras is skipped here, unlike in Serialization
|
||||
if (extraFieldLength > 0 && data.Position + extraFieldLength <= data.Length)
|
||||
_ = data.ReadBytes(extraFieldLength);
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify the extracted stream data, seeking to the original location on failure
|
||||
/// </summary>
|
||||
/// <param name="source">Stream representing the deflated data</param>
|
||||
/// <param name="start">Position representing the start of the deflated data</param>
|
||||
/// <param name="expected">Expected deflation info</param>
|
||||
/// <param name="actual">Actual deflation info</param>
|
||||
/// <param name="includeDebug">True to include debug data, false otherwise</param>
|
||||
/// <returns>Extraction status representing the final state</returns>
|
||||
private static ExtractionStatus VerifyExtractedData(Stream source,
|
||||
long start,
|
||||
DeflateInfo expected,
|
||||
DeflateInfo actual,
|
||||
bool includeDebug)
|
||||
{
|
||||
// Debug output
|
||||
if (includeDebug) Console.WriteLine($"Actual Read: {actual.InputSize}, Actual Write: {actual.OutputSize}, Actual CRC-32: {actual.Crc32:X8}");
|
||||
|
||||
// If there's a mismatch during both reading and writing
|
||||
if (expected.InputSize >= 0 && expected.InputSize != actual.InputSize)
|
||||
{
|
||||
// This in/out check helps catch false positives, such as
|
||||
// files that have an off-by-one mismatch for read values
|
||||
// but properly match the output written values.
|
||||
|
||||
// If the written bytes not correct as well
|
||||
if (expected.OutputSize >= 0 && expected.OutputSize != actual.OutputSize)
|
||||
{
|
||||
if (includeDebug) Console.Error.WriteLine($"Mismatched read/write values!");
|
||||
source.Seek(start, SeekOrigin.Begin);
|
||||
return ExtractionStatus.WRONG_SIZE;
|
||||
}
|
||||
|
||||
// If the written bytes are not being verified
|
||||
else if (expected.OutputSize < 0)
|
||||
{
|
||||
if (includeDebug) Console.Error.WriteLine($"Mismatched read/write values!");
|
||||
source.Seek(start, SeekOrigin.Begin);
|
||||
return ExtractionStatus.WRONG_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
// If there's just a mismatch during only writing
|
||||
if (expected.InputSize >= 0 && expected.InputSize == actual.InputSize)
|
||||
{
|
||||
// We want to log this but ignore the error
|
||||
if (expected.OutputSize >= 0 && expected.OutputSize != actual.OutputSize)
|
||||
{
|
||||
if (includeDebug) Console.WriteLine($"Ignoring mismatched write values because read values match!");
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, the write size should be checked normally
|
||||
else if (expected.InputSize == 0 && expected.OutputSize >= 0 && expected.OutputSize != actual.OutputSize)
|
||||
{
|
||||
if (includeDebug) Console.Error.WriteLine($"Mismatched write values!");
|
||||
source.Seek(start, SeekOrigin.Begin);
|
||||
return ExtractionStatus.WRONG_SIZE;
|
||||
}
|
||||
|
||||
// If there's a mismatch with the CRC-32
|
||||
if (expected.Crc32 != 0 && expected.Crc32 != actual.Crc32)
|
||||
{
|
||||
if (includeDebug) Console.Error.WriteLine($"Mismatched CRC-32 values!");
|
||||
source.Seek(start, SeekOrigin.Begin);
|
||||
return ExtractionStatus.BAD_CRC;
|
||||
}
|
||||
|
||||
return ExtractionStatus.GOOD;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Inflation
|
||||
|
||||
/// <summary>
|
||||
/// Inflate an input stream to an output stream
|
||||
/// </summary>
|
||||
/// <param name="source">Stream representing the deflated data</param>
|
||||
/// <param name="destination">Stream where the inflated data will be written</param>
|
||||
/// <returns>Deflate information representing the processed data on success, null on error</returns>
|
||||
public static DeflateInfo? Inflate(Stream source, Stream destination)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Setup the hasher for CRC-32 calculation
|
||||
using var hasher = new HashWrapper(HashType.CRC32);
|
||||
|
||||
// Create a DeflateStream from the input
|
||||
using var ds = new DeflateStream(source, CompressionMode.Decompress, leaveOpen: true);
|
||||
|
||||
// Decompress in blocks
|
||||
while (true)
|
||||
{
|
||||
byte[] buf = new byte[BufferSize];
|
||||
int read = ds.Read(buf, 0, buf.Length);
|
||||
if (read == 0)
|
||||
break;
|
||||
|
||||
hasher.Process(buf, 0, read);
|
||||
destination.Write(buf, 0, read);
|
||||
}
|
||||
|
||||
// Finalize the hash
|
||||
hasher.Terminate();
|
||||
byte[] hashBytes = hasher.CurrentHashBytes!;
|
||||
|
||||
// Save the deflate values
|
||||
return new DeflateInfo
|
||||
{
|
||||
InputSize = ds.TotalIn,
|
||||
OutputSize = ds.TotalOut,
|
||||
Crc32 = BitConverter.ToUInt32(hashBytes, 0),
|
||||
};
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
58
SabreTools.IO/Compression/LZX/AlignedOffsetBlockData.cs
Normal file
58
SabreTools.IO/Compression/LZX/AlignedOffsetBlockData.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
namespace SabreTools.IO.Compression.LZX
|
||||
{
|
||||
/// <summary>
|
||||
/// An aligned offset block is identical to the verbatim block except for the presence of the aligned offset
|
||||
/// tree preceding the other trees.
|
||||
/// </summary>
|
||||
/// <see href="https://interoperability.blob.core.windows.net/files/MS-PATCH/%5bMS-PATCH%5d.pdf"/>
|
||||
internal class AlignedOffsetBlockData : BlockData
|
||||
{
|
||||
/// <summary>
|
||||
/// Aligned offset tree
|
||||
/// </summary>
|
||||
/// <remarks>8 elements, 3 bits each</remarks>
|
||||
public byte[]? AlignedOffsetTree { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Pretree for first 256 elements of main tree
|
||||
/// </summary>
|
||||
/// <remarks>20 elements, 4 bits each</remarks>
|
||||
public byte[]? PretreeFirst256 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Path lengths of first 256 elements of main tree
|
||||
/// </summary>
|
||||
/// <remarks>Encoded using pretree</remarks>
|
||||
public int[]? PathLengthsFirst256 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Pretree for remainder of main tree
|
||||
/// </summary>
|
||||
/// <remarks>20 elements, 4 bits each</remarks>
|
||||
public byte[]? PretreeRemainder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Path lengths of remaining elements of main tree
|
||||
/// </summary>
|
||||
/// <remarks>Encoded using pretree</remarks>
|
||||
public int[]? PathLengthsRemainder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Pretree for length tree
|
||||
/// </summary>
|
||||
/// <remarks>20 elements, 4 bits each</remarks>
|
||||
public byte[]? PretreeLengthTree { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Path lengths of elements in length tree
|
||||
/// </summary>
|
||||
/// <remarks>Encoded using pretree</remarks>
|
||||
public int[]? PathLengthsLengthTree { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Token sequence (matches and literals)
|
||||
/// </summary>
|
||||
/// <remarks>Variable</remarks>
|
||||
public byte[]? TokenSequence { get; set; }
|
||||
}
|
||||
}
|
||||
24
SabreTools.IO/Compression/LZX/Block.cs
Normal file
24
SabreTools.IO/Compression/LZX/Block.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
namespace SabreTools.IO.Compression.LZX
|
||||
{
|
||||
/// <summary>
|
||||
/// An LZXD block represents a sequence of compressed data that is encoded with the same set of
|
||||
/// Huffman trees, or a sequence of uncompressed data. There can be one or more LZXD blocks in a
|
||||
/// compressed stream, each with its own set of Huffman trees. Blocks do not have to start or end on a
|
||||
/// chunk boundary; blocks can span multiple chunks, or a single chunk can contain multiple blocks. The
|
||||
/// number of chunks is related to the size of the data being compressed, while the number of blocks is
|
||||
/// related to how well the data is compressed.
|
||||
/// </summary>
|
||||
/// <see href="https://interoperability.blob.core.windows.net/files/MS-PATCH/%5bMS-PATCH%5d.pdf"/>
|
||||
internal class Block
|
||||
{
|
||||
/// <summary>
|
||||
/// Block header
|
||||
/// </summary>
|
||||
public BlockHeader? Header { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Block data
|
||||
/// </summary>
|
||||
public BlockData? BlockData { get; set; }
|
||||
}
|
||||
}
|
||||
8
SabreTools.IO/Compression/LZX/BlockData.cs
Normal file
8
SabreTools.IO/Compression/LZX/BlockData.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace SabreTools.IO.Compression.LZX
|
||||
{
|
||||
/// <see href="https://interoperability.blob.core.windows.net/files/MS-PATCH/%5bMS-PATCH%5d.pdf"/>
|
||||
internal abstract class BlockData
|
||||
{
|
||||
// No common fields between all block data
|
||||
}
|
||||
}
|
||||
33
SabreTools.IO/Compression/LZX/BlockHeader.cs
Normal file
33
SabreTools.IO/Compression/LZX/BlockHeader.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
namespace SabreTools.IO.Compression.LZX
|
||||
{
|
||||
/// <summary>
|
||||
/// The Block Type field, as specified in section 2.3.1.1, indicates which type of block follows,
|
||||
/// and the Block Size field, as specified in section 2.3.1.2, indicates the number of
|
||||
/// uncompressed bytes represented by the block. Following the generic block
|
||||
/// header is a type-specific header that describes the remainder of the block.
|
||||
/// </summary>
|
||||
/// <see href="https://interoperability.blob.core.windows.net/files/MS-PATCH/%5bMS-PATCH%5d.pdf"/>
|
||||
internal class BlockHeader
|
||||
{
|
||||
/// <remarks>3 bits</remarks>
|
||||
public BlockType BlockType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Block size is the high 8 bits of 24
|
||||
/// </summary>
|
||||
/// <remarks>8 bits</remarks>
|
||||
public byte BlockSizeMSB { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Block size is the middle 8 bits of 24
|
||||
/// </summary>
|
||||
/// <remarks>8 bits</remarks>
|
||||
public byte BlockSizeByte2 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Block size is the low 8 bits of 24
|
||||
/// </summary>
|
||||
/// <remarks>8 bits</remarks>
|
||||
public byte BlocksizeLSB { get; set; }
|
||||
}
|
||||
}
|
||||
25
SabreTools.IO/Compression/LZX/Chunk.cs
Normal file
25
SabreTools.IO/Compression/LZX/Chunk.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
namespace SabreTools.IO.Compression.LZX
|
||||
{
|
||||
/// <summary>
|
||||
/// The LZXD compressor emits chunks of compressed data. A chunk represents exactly 32 KB of
|
||||
/// uncompressed data until the last chunk in the stream, which can represent less than 32 KB. To
|
||||
/// ensure that an exact number of input bytes represent an exact number of output bytes for each
|
||||
/// chunk, after each 32 KB of uncompressed data is represented in the output compressed bitstream, the
|
||||
/// output bitstream is padded with up to 15 bits of zeros to realign the bitstream on a 16-bit boundary
|
||||
/// (even byte boundary) for the next 32 KB of data. This results in a compressed chunk of a byte-aligned
|
||||
/// size. The compressed chunk could be smaller than 32 KB or larger than 32 KB if the data is
|
||||
/// incompressible when the chunk is not the last one.
|
||||
/// </summary>
|
||||
internal class Chunk
|
||||
{
|
||||
/// <summary>
|
||||
/// Chunk header
|
||||
/// </summary>
|
||||
public ChunkHeader? Header { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Block headers and data
|
||||
/// </summary>
|
||||
public Block[]? Blocks { get; set; }
|
||||
}
|
||||
}
|
||||
46
SabreTools.IO/Compression/LZX/ChunkHeader.cs
Normal file
46
SabreTools.IO/Compression/LZX/ChunkHeader.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
namespace SabreTools.IO.Compression.LZX
|
||||
{
|
||||
/// <summary>
|
||||
/// The LZXD compressor emits chunks of compressed data. A chunk represents exactly 32 KB of
|
||||
/// uncompressed data until the last chunk in the stream, which can represent less than 32 KB. To
|
||||
/// ensure that an exact number of input bytes represent an exact number of output bytes for each
|
||||
/// chunk, after each 32 KB of uncompressed data is represented in the output compressed bitstream, the
|
||||
/// output bitstream is padded with up to 15 bits of zeros to realign the bitstream on a 16-bit boundary
|
||||
/// (even byte boundary) for the next 32 KB of data. This results in a compressed chunk of a byte-aligned
|
||||
/// size. The compressed chunk could be smaller than 32 KB or larger than 32 KB if the data is
|
||||
/// incompressible when the chunk is not the last one.
|
||||
/// </summary>
|
||||
internal class ChunkHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// The LZXD engine encodes a compressed, chunk-size prefix field preceding each compressed chunk in
|
||||
/// the compressed byte stream. The compressed, chunk-size prefix field is a byte aligned, little-endian,
|
||||
/// 16-bit field. The chunk prefix chain could be followed in the compressed stream without
|
||||
/// decompressing any data. The next chunk prefix is at a location computed by the absolute byte offset
|
||||
/// location of this chunk prefix plus 2 (for the size of the chunk-size prefix field) plus the current chunk
|
||||
/// size.
|
||||
/// </summary>
|
||||
public ushort ChunkSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The first bit in the first chunk in the LZXD bitstream (following the 2-byte, chunk-size prefix described
|
||||
/// in section 2.2.1) indicates the presence or absence of two 16-bit fields immediately following the
|
||||
/// single bit. If the bit is set, E8 translation is enabled for all the following chunks in the stream using the
|
||||
/// 32-bit value derived from the two 16-bit fields as the E8_file_size provided to the compressor when E8
|
||||
/// translation was enabled. Note that E8_file_size is completely independent of the length of the
|
||||
/// uncompressed data. E8 call translation is disabled after the 32,768th chunk (after 1 gigabyte (GB) of
|
||||
/// uncompressed data).
|
||||
/// </summary>
|
||||
public byte E8Translation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// E8 translation size, high WORD
|
||||
/// </summary>
|
||||
public ushort? TranslationSizeHighWord { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// E8 translation size, low WORD
|
||||
/// </summary>
|
||||
public ushort? TranslationSizeLowWord { get; set; }
|
||||
}
|
||||
}
|
||||
38
SabreTools.IO/Compression/LZX/Constants.cs
Normal file
38
SabreTools.IO/Compression/LZX/Constants.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
namespace SabreTools.IO.Compression.LZX
|
||||
{
|
||||
internal static class Constants
|
||||
{
|
||||
/* some constants defined by the LZX specification */
|
||||
public const int LZX_MIN_MATCH = 2;
|
||||
public const int LZX_MAX_MATCH = 257;
|
||||
public const int LZX_NUM_CHARS = 256;
|
||||
public const int LZX_PRETREE_NUM_ELEMENTS = 20;
|
||||
|
||||
/// <summary>
|
||||
/// aligned offset tree #elements
|
||||
/// </summary>
|
||||
public const int LZX_ALIGNED_NUM_ELEMENTS = 8;
|
||||
|
||||
/// <summary>
|
||||
/// this one missing from spec!
|
||||
/// </summary>
|
||||
public const int LZX_NUM_PRIMARY_LENGTHS = 7;
|
||||
|
||||
/// <summary>
|
||||
/// length tree #elements
|
||||
/// </summary>
|
||||
public const int LZX_NUM_SECONDARY_LENGTHS = 249;
|
||||
|
||||
/* LZX huffman defines: tweak tablebits as desired */
|
||||
public const int LZX_PRETREE_MAXSYMBOLS = LZX_PRETREE_NUM_ELEMENTS;
|
||||
public const int LZX_PRETREE_TABLEBITS = 6;
|
||||
public const int LZX_MAINTREE_MAXSYMBOLS = LZX_NUM_CHARS + 50 * 8;
|
||||
public const int LZX_MAINTREE_TABLEBITS = 12;
|
||||
public const int LZX_LENGTH_MAXSYMBOLS = LZX_NUM_SECONDARY_LENGTHS + 1;
|
||||
public const int LZX_LENGTH_TABLEBITS = 12;
|
||||
public const int LZX_ALIGNED_MAXSYMBOLS = LZX_ALIGNED_NUM_ELEMENTS;
|
||||
public const int LZX_ALIGNED_TABLEBITS = 7;
|
||||
|
||||
public const int LZX_LENTABLE_SAFETY = 64; /* we allow length table decoding overruns */
|
||||
}
|
||||
}
|
||||
48
SabreTools.IO/Compression/LZX/Enums.cs
Normal file
48
SabreTools.IO/Compression/LZX/Enums.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
namespace SabreTools.IO.Compression.LZX
|
||||
{
|
||||
/// <summary>
|
||||
/// 3-bit block type
|
||||
/// </summary>
|
||||
internal enum BlockType : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Not valid
|
||||
/// </summary>
|
||||
INVALID_0 = 0b000,
|
||||
|
||||
/// <summary>
|
||||
/// Verbatim block
|
||||
/// </summary>
|
||||
Verbatim = 0b001,
|
||||
|
||||
/// <summary>
|
||||
/// Aligned offset block
|
||||
/// </summary>
|
||||
AlignedOffset = 0b010,
|
||||
|
||||
/// <summary>
|
||||
/// Uncompressed block
|
||||
/// </summary>
|
||||
Uncompressed = 0b011,
|
||||
|
||||
/// <summary>
|
||||
/// Not valid
|
||||
/// </summary>
|
||||
INVALID_4 = 0b100,
|
||||
|
||||
/// <summary>
|
||||
/// Not valid
|
||||
/// </summary>
|
||||
INVALID_5 = 0b101,
|
||||
|
||||
/// <summary>
|
||||
/// Not valid
|
||||
/// </summary>
|
||||
INVALID_6 = 0b110,
|
||||
|
||||
/// <summary>
|
||||
/// Not valid
|
||||
/// </summary>
|
||||
INVALID_7 = 0b111,
|
||||
}
|
||||
}
|
||||
54
SabreTools.IO/Compression/LZX/UncompressedBlockData.cs
Normal file
54
SabreTools.IO/Compression/LZX/UncompressedBlockData.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
namespace SabreTools.IO.Compression.LZX
|
||||
{
|
||||
/// <summary>
|
||||
/// Following the generic block header, an uncompressed block begins with 1 to 16 bits of zero padding
|
||||
/// to align the bit buffer on a 16-bit boundary. At this point, the bitstream ends and a byte stream
|
||||
/// begins. Following the zero padding, new 32-bit values for R0, R1, and R2 are output in little-endian
|
||||
/// form, followed by the uncompressed data bytes themselves. Finally, if the uncompressed data length
|
||||
/// is odd, one extra byte of zero padding is encoded to realign the following bitstream.
|
||||
///
|
||||
/// Then the bitstream of byte-swapped 16-bit integers resumes for the next Block Type field (if there
|
||||
/// are subsequent blocks).
|
||||
///
|
||||
/// The decoded R0, R1, and R2 values are used as initial repeated offset values to decode the
|
||||
/// subsequent compressed block if present.
|
||||
/// </summary>
|
||||
/// <see href="https://interoperability.blob.core.windows.net/files/MS-PATCH/%5bMS-PATCH%5d.pdf"/>
|
||||
internal class UncompressedBlockData : BlockData
|
||||
{
|
||||
/// <summary>
|
||||
/// Padding to align following field on 16-bit boundary
|
||||
/// </summary>
|
||||
/// <remarks>Bits have a value of zero</remarks>
|
||||
public ushort PaddingBits { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Least significant to most significant byte (little-endian DWORD ([MS-DTYP]))
|
||||
/// </summary>
|
||||
/// <remarks>Encoded directly in the byte stream, not in the bitstream of byte-swapped 16-bit words</remarks>
|
||||
public uint R0 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Least significant to most significant byte (little-endian DWORD)
|
||||
/// </summary>
|
||||
/// <remarks>Encoded directly in the byte stream, not in the bitstream of byte-swapped 16-bit words</remarks>
|
||||
public uint R1 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Least significant to most significant byte (little-endian DWORD)
|
||||
/// </summary>
|
||||
/// <remarks>Encoded directly in the byte stream, not in the bitstream of byte-swapped 16-bit words</remarks>
|
||||
public uint R2 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Can use the direct memcpy function, as specified in [IEEE1003.1]
|
||||
/// </summary>
|
||||
/// <remarks>Encoded directly in the byte stream, not in the bitstream of byte-swapped 16-bit words</remarks>
|
||||
public byte[]? RawDataBytes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Only if uncompressed size is odd
|
||||
/// </summary>
|
||||
public byte AlignmentByte { get; set; }
|
||||
}
|
||||
}
|
||||
51
SabreTools.IO/Compression/LZX/VerbatimBlockData.cs
Normal file
51
SabreTools.IO/Compression/LZX/VerbatimBlockData.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
namespace SabreTools.IO.Compression.LZX
|
||||
{
|
||||
/// <summary>
|
||||
/// The fields of a verbatim block that follow the generic block header
|
||||
/// </summary>
|
||||
/// <see href="https://interoperability.blob.core.windows.net/files/MS-PATCH/%5bMS-PATCH%5d.pdf"/>
|
||||
internal class VerbatimBlockData : BlockData
|
||||
{
|
||||
/// <summary>
|
||||
/// Pretree for first 256 elements of main tree
|
||||
/// </summary>
|
||||
/// <remarks>20 elements, 4 bits each</remarks>
|
||||
public byte[]? PretreeFirst256 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Path lengths of first 256 elements of main tree
|
||||
/// </summary>
|
||||
/// <remarks>Encoded using pretree</remarks>
|
||||
public int[]? PathLengthsFirst256 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Pretree for remainder of main tree
|
||||
/// </summary>
|
||||
/// <remarks>20 elements, 4 bits each</remarks>
|
||||
public byte[]? PretreeRemainder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Path lengths of remaining elements of main tree
|
||||
/// </summary>
|
||||
/// <remarks>Encoded using pretree</remarks>
|
||||
public int[]? PathLengthsRemainder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Pretree for length tree
|
||||
/// </summary>
|
||||
/// <remarks>20 elements, 4 bits each</remarks>
|
||||
public byte[]? PretreeLengthTree { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Path lengths of elements in length tree
|
||||
/// </summary>
|
||||
/// <remarks>Encoded using pretree</remarks>
|
||||
public int[]? PathLengthsLengthTree { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Token sequence (matches and literals)
|
||||
/// </summary>
|
||||
/// <remarks>Variable</remarks>
|
||||
public byte[]? TokenSequence { get; set; }
|
||||
}
|
||||
}
|
||||
28
SabreTools.IO/Compression/MSZIP/BlockHeader.cs
Normal file
28
SabreTools.IO/Compression/MSZIP/BlockHeader.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
namespace SabreTools.IO.Compression.MSZIP
|
||||
{
|
||||
/// <summary>
|
||||
/// Each MSZIP block MUST consist of a 2-byte MSZIP signature and one or more RFC 1951 blocks. The
|
||||
/// 2-byte MSZIP signature MUST consist of the bytes 0x43 and 0x4B. The MSZIP signature MUST be
|
||||
/// the first 2 bytes in the MSZIP block. The MSZIP signature is shown in the following packet diagram.
|
||||
///
|
||||
/// Each MSZIP block is the result of a single deflate compression operation, as defined in [RFC1951].
|
||||
/// The compressor that performs the compression operation MUST generate one or more RFC 1951
|
||||
/// blocks, as defined in [RFC1951]. The number, deflation mode, and type of RFC 1951 blocks in each
|
||||
/// MSZIP block is determined by the compressor, as defined in [RFC1951]. The last RFC 1951 block in
|
||||
/// each MSZIP block MUST be marked as the "end" of the stream(1), as defined by [RFC1951]
|
||||
/// section 3.2.3. Decoding trees MUST be discarded after each RFC 1951 block, but the history buffer
|
||||
/// MUST be maintained.Each MSZIP block MUST represent no more than 32 KB of uncompressed data.
|
||||
///
|
||||
/// The maximum compressed size of each MSZIP block is 32 KB + 12 bytes. This enables the MSZIP
|
||||
/// block to contain 32 KB of data split between two noncompressed RFC 1951 blocks, each of which
|
||||
/// has a value of BTYPE = 00.
|
||||
/// </summary>
|
||||
/// <see href="https://interoperability.blob.core.windows.net/files/MS-MCI/%5bMS-MCI%5d.pdf"/>
|
||||
internal class BlockHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// 'CK'
|
||||
/// </summary>
|
||||
public ushort Signature { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Models.Compression.MSZIP;
|
||||
|
||||
namespace SabreTools.IO.Compression.MSZIP
|
||||
{
|
||||
|
||||
50
SabreTools.IO/Compression/Quantum/Constants.cs
Normal file
50
SabreTools.IO/Compression/Quantum/Constants.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
namespace SabreTools.IO.Compression.Quantum
|
||||
{
|
||||
/// <see href="http://www.russotto.net/quantumcomp.html"/>
|
||||
internal static class Constants
|
||||
{
|
||||
public static readonly int[] PositionSlot =
|
||||
[
|
||||
0x00000, 0x00001, 0x00002, 0x00003, 0x00004, 0x00006, 0x00008, 0x0000c,
|
||||
0x00010, 0x00018, 0x00020, 0x00030, 0x00040, 0x00060, 0x00080, 0x000c0,
|
||||
0x00100, 0x00180, 0x00200, 0x00300, 0x00400, 0x00600, 0x00800, 0x00c00,
|
||||
0x01000, 0x01800, 0x02000, 0x03000, 0x04000, 0x06000, 0x08000, 0x0c000,
|
||||
0x10000, 0x18000, 0x20000, 0x30000, 0x40000, 0x60000, 0x80000, 0xc0000,
|
||||
0x100000, 0x180000
|
||||
];
|
||||
|
||||
public static readonly int[] PositionExtraBits =
|
||||
[
|
||||
0, 0, 0, 0, 1, 1, 2, 2,
|
||||
3, 3, 4, 4, 5, 5, 6, 6,
|
||||
7, 7, 8, 8, 9, 9, 10, 10,
|
||||
11, 11, 12, 12, 13, 13, 14, 14,
|
||||
15, 15, 16, 16, 17, 17, 18, 18,
|
||||
19, 19
|
||||
];
|
||||
|
||||
public static readonly int[] LengthSlot =
|
||||
[
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08,
|
||||
0x0a, 0x0c, 0x0e, 0x12, 0x16, 0x1a, 0x1e, 0x26,
|
||||
0x2e, 0x36, 0x3e, 0x4e, 0x5e, 0x6e, 0x7e, 0x9e,
|
||||
0xbe, 0xde, 0xfe
|
||||
];
|
||||
|
||||
public static readonly int[] LengthExtraBits =
|
||||
[
|
||||
0, 0, 0, 0, 0, 0, 1, 1,
|
||||
1, 1, 2, 2, 2, 2, 3, 3,
|
||||
3, 3, 4, 4, 4, 4, 5, 5,
|
||||
5, 5, 0
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Number of position slots for (tsize - 10)
|
||||
/// </summary>
|
||||
public static readonly int[] NumPositionSlots =
|
||||
[
|
||||
20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SabreTools.IO.Streams;
|
||||
using SabreTools.Models.Compression.Quantum;
|
||||
using static SabreTools.Models.Compression.Quantum.Constants;
|
||||
using static SabreTools.IO.Compression.Quantum.Constants;
|
||||
|
||||
namespace SabreTools.IO.Compression.Quantum
|
||||
{
|
||||
|
||||
45
SabreTools.IO/Compression/Quantum/Enums.cs
Normal file
45
SabreTools.IO/Compression/Quantum/Enums.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
namespace SabreTools.IO.Compression.Quantum
|
||||
{
|
||||
internal enum SelectorModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Literal model, 64 entries, start at symbol 0
|
||||
/// </summary>
|
||||
SELECTOR_0 = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Literal model, 64 entries, start at symbol 64
|
||||
/// </summary>
|
||||
SELECTOR_1 = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Literal model, 64 entries, start at symbol 128
|
||||
/// </summary>
|
||||
SELECTOR_2 = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Literal model, 64 entries, start at symbol 192
|
||||
/// </summary>
|
||||
SELECTOR_3 = 3,
|
||||
|
||||
/// <summary>
|
||||
/// LZ model, 3 character matches, max 24 entries, start at symbol 0
|
||||
/// </summary>
|
||||
SELECTOR_4 = 4,
|
||||
|
||||
/// <summary>
|
||||
/// LZ model, 4 character matches, max 36 entries, start at symbol 0
|
||||
/// </summary>
|
||||
SELECTOR_5 = 5,
|
||||
|
||||
/// <summary>
|
||||
/// LZ model, 5+ character matches, max 42 entries, start at symbol 0
|
||||
/// </summary>
|
||||
SELECTOR_6_POSITION = 6,
|
||||
|
||||
/// <summary>
|
||||
/// LZ model, 5+ character matches, 27 entries, start at symbol 0
|
||||
/// </summary>
|
||||
SELECTOR_6_LENGTH = 7,
|
||||
}
|
||||
}
|
||||
24
SabreTools.IO/Compression/Quantum/Model.cs
Normal file
24
SabreTools.IO/Compression/Quantum/Model.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
namespace SabreTools.IO.Compression.Quantum
|
||||
{
|
||||
/// <see href="http://www.russotto.net/quantumcomp.html"/>
|
||||
internal sealed class Model
|
||||
{
|
||||
public int Entries { get; set; }
|
||||
|
||||
/// <remarks>
|
||||
/// All the models are initialized with the symbols in symbol
|
||||
/// order in the table, and with every symbol in the table
|
||||
/// having a frequency of 1
|
||||
/// </remarks>
|
||||
public ModelSymbol[]? Symbols { get; set; }
|
||||
|
||||
/// <remarks>
|
||||
/// The initial total frequency is equal to the number of entries
|
||||
/// in the table
|
||||
/// </remarks>
|
||||
public int TotalFrequency { get; set; }
|
||||
|
||||
/// <remarks>The initial time_to_reorder value is 4</remarks>
|
||||
public int TimeToReorder { get; set; }
|
||||
}
|
||||
}
|
||||
15
SabreTools.IO/Compression/Quantum/ModelSymbol.cs
Normal file
15
SabreTools.IO/Compression/Quantum/ModelSymbol.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace SabreTools.IO.Compression.Quantum
|
||||
{
|
||||
/// <see href="http://www.russotto.net/quantumcomp.html"/>
|
||||
internal sealed class ModelSymbol
|
||||
{
|
||||
public ushort Symbol { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The cumulative frequency is the frequency of all the symbols
|
||||
/// which are at a higher index in the table than that symbol —
|
||||
/// thus the last entry in the table has a cumulative frequency of 0.
|
||||
/// </summary>
|
||||
public ushort CumulativeFrequency { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SabreTools.Models.LZ;
|
||||
|
||||
namespace SabreTools.IO.Compression.SZDD
|
||||
{
|
||||
@@ -15,7 +14,7 @@ namespace SabreTools.IO.Compression.SZDD
|
||||
/// <summary>
|
||||
/// Source stream for the decompressor
|
||||
/// </summary>
|
||||
private readonly BufferedStream _source;
|
||||
private readonly Streams.BufferedStream _source;
|
||||
|
||||
/// <summary>
|
||||
/// SZDD format being decompressed
|
||||
@@ -37,19 +36,19 @@ namespace SabreTools.IO.Compression.SZDD
|
||||
|
||||
// Initialize the window with space characters
|
||||
_window = Array.ConvertAll(_window, b => (byte)0x20);
|
||||
_source = new BufferedStream(source);
|
||||
_source = new Streams.BufferedStream(source);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a KWAJ decompressor
|
||||
/// </summary>
|
||||
public static Decompressor CreateKWAJ(byte[] source, KWAJCompressionType compressionType)
|
||||
public static Decompressor CreateKWAJ(byte[] source, ushort compressionType)
|
||||
=> CreateKWAJ(new MemoryStream(source), compressionType);
|
||||
|
||||
/// <summary>
|
||||
/// Create a KWAJ decompressor
|
||||
/// </summary>
|
||||
public static Decompressor CreateKWAJ(Stream source, KWAJCompressionType compressionType)
|
||||
public static Decompressor CreateKWAJ(Stream source, ushort compressionType)
|
||||
{
|
||||
// Create the decompressor
|
||||
var decompressor = new Decompressor(source);
|
||||
@@ -57,11 +56,11 @@ namespace SabreTools.IO.Compression.SZDD
|
||||
// Set the format and return
|
||||
decompressor._format = compressionType switch
|
||||
{
|
||||
KWAJCompressionType.NoCompression => Format.KWAJNoCompression,
|
||||
KWAJCompressionType.NoCompressionXor => Format.KWAJXor,
|
||||
KWAJCompressionType.QBasic => Format.KWAJQBasic,
|
||||
KWAJCompressionType.LZH => Format.KWAJLZH,
|
||||
KWAJCompressionType.MSZIP => Format.KWAJMSZIP,
|
||||
0x0000 => Format.KWAJNoCompression,
|
||||
0x0001 => Format.KWAJXor,
|
||||
0x0002 => Format.KWAJQBasic,
|
||||
0x0003 => Format.KWAJLZH,
|
||||
0x0004 => Format.KWAJMSZIP,
|
||||
_ => throw new IndexOutOfRangeException(nameof(source)),
|
||||
};
|
||||
return decompressor;
|
||||
@@ -229,77 +228,5 @@ namespace SabreTools.IO.Compression.SZDD
|
||||
dest.Flush();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Buffered stream that reads in blocks
|
||||
/// </summary>
|
||||
private class BufferedStream
|
||||
{
|
||||
/// <summary>
|
||||
/// Source stream for populating the buffer
|
||||
/// </summary>
|
||||
private readonly Stream _source;
|
||||
|
||||
/// <summary>
|
||||
/// Internal buffer to read
|
||||
/// </summary>
|
||||
private readonly byte[] _buffer = new byte[2048];
|
||||
|
||||
/// <summary>
|
||||
/// Current pointer into the buffer
|
||||
/// </summary>
|
||||
private int _bufferPtr = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the number of available bytes
|
||||
/// </summary>
|
||||
private int _available = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new buffered stream
|
||||
/// </summary>
|
||||
public BufferedStream(Stream source)
|
||||
{
|
||||
_source = source;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read the next byte from the buffer, if possible
|
||||
/// </summary>
|
||||
public byte? ReadNextByte()
|
||||
{
|
||||
// Ensure the buffer first
|
||||
if (!EnsureBuffer())
|
||||
return null;
|
||||
|
||||
// Return the next available value
|
||||
return _buffer[_bufferPtr++];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensure the buffer has data to read
|
||||
/// </summary>
|
||||
private bool EnsureBuffer()
|
||||
{
|
||||
// Force an update if in the initial state
|
||||
if (_available == -1)
|
||||
{
|
||||
_available = _source.Read(_buffer, 0, _buffer.Length);
|
||||
_bufferPtr = 0;
|
||||
return _available != 0;
|
||||
}
|
||||
|
||||
// If the pointer is out of range
|
||||
if (_bufferPtr >= _available)
|
||||
{
|
||||
_available = _source.Read(_buffer, 0, _buffer.Length);
|
||||
_bufferPtr = 0;
|
||||
return _available != 0;
|
||||
}
|
||||
|
||||
// Otherwise, assume data is available
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
125
SabreTools.IO/Encryption/AESCTR.cs
Normal file
125
SabreTools.IO/Encryption/AESCTR.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Org.BouncyCastle.Crypto;
|
||||
using Org.BouncyCastle.Crypto.Parameters;
|
||||
using Org.BouncyCastle.Security;
|
||||
using SabreTools.IO.Extensions;
|
||||
|
||||
namespace SabreTools.IO.Encryption
|
||||
{
|
||||
public static class AESCTR
|
||||
{
|
||||
/// <summary>
|
||||
/// Create AES decryption cipher and intialize
|
||||
/// </summary>
|
||||
/// <param name="key">Byte array representation of 128-bit encryption key</param>
|
||||
/// <param name="iv">AES initial value for counter</param>
|
||||
/// <returns>Initialized AES cipher</returns>
|
||||
public static IBufferedCipher CreateDecryptionCipher(byte[] key, byte[] iv)
|
||||
{
|
||||
if (key.Length != 16)
|
||||
throw new ArgumentOutOfRangeException(nameof(key));
|
||||
|
||||
var keyParam = new KeyParameter(key);
|
||||
var cipher = CipherUtilities.GetCipher("AES/CTR");
|
||||
cipher.Init(forEncryption: false, new ParametersWithIV(keyParam, iv));
|
||||
return cipher;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create AES encryption cipher and intialize
|
||||
/// </summary>
|
||||
/// <param name="key">Byte array representation of 128-bit encryption key</param>
|
||||
/// <param name="iv">AES initial value for counter</param>
|
||||
/// <returns>Initialized AES cipher</returns>
|
||||
public static IBufferedCipher CreateEncryptionCipher(byte[] key, byte[] iv)
|
||||
{
|
||||
if (key.Length != 16)
|
||||
throw new ArgumentOutOfRangeException(nameof(key));
|
||||
|
||||
var keyParam = new KeyParameter(key);
|
||||
var cipher = CipherUtilities.GetCipher("AES/CTR");
|
||||
cipher.Init(forEncryption: true, new ParametersWithIV(keyParam, iv));
|
||||
return cipher;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform an AES operation using an existing cipher
|
||||
/// </summary>
|
||||
public static void PerformOperation(uint size,
|
||||
IBufferedCipher cipher,
|
||||
Stream input,
|
||||
Stream output,
|
||||
Action<string>? progress = null)
|
||||
{
|
||||
// Get MiB-aligned block count and extra byte count
|
||||
int blockCount = (int)((long)size / (1024 * 1024));
|
||||
int extraBytes = (int)((long)size % (1024 * 1024));
|
||||
|
||||
// Process MiB-aligned data
|
||||
if (blockCount > 0)
|
||||
{
|
||||
for (int i = 0; i < blockCount; i++)
|
||||
{
|
||||
byte[] readBytes = input.ReadBytes(1024 * 1024);
|
||||
byte[] processedBytes = cipher.ProcessBytes(readBytes);
|
||||
output.Write(processedBytes);
|
||||
output.Flush();
|
||||
progress?.Invoke($"{i} / {blockCount + 1} MB");
|
||||
}
|
||||
}
|
||||
|
||||
// Process additional data
|
||||
if (extraBytes > 0)
|
||||
{
|
||||
byte[] readBytes = input.ReadBytes(extraBytes);
|
||||
byte[] finalBytes = cipher.DoFinal(readBytes);
|
||||
output.Write(finalBytes);
|
||||
output.Flush();
|
||||
}
|
||||
|
||||
progress?.Invoke($"{blockCount + 1} / {blockCount + 1} MB... Done!\r\n");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform an AES operation using two existing ciphers
|
||||
/// </summary>
|
||||
public static void PerformOperation(uint size,
|
||||
IBufferedCipher firstCipher,
|
||||
IBufferedCipher secondCipher,
|
||||
Stream input,
|
||||
Stream output,
|
||||
Action<string>? progress = null)
|
||||
{
|
||||
// Get MiB-aligned block count and extra byte count
|
||||
int blockCount = (int)((long)size / (1024 * 1024));
|
||||
int extraBytes = (int)((long)size % (1024 * 1024));
|
||||
|
||||
// Process MiB-aligned data
|
||||
if (blockCount > 0)
|
||||
{
|
||||
for (int i = 0; i < blockCount; i++)
|
||||
{
|
||||
byte[] readBytes = input.ReadBytes(1024 * 1024);
|
||||
byte[] firstProcessedBytes = firstCipher.ProcessBytes(readBytes);
|
||||
byte[] secondProcessedBytes = secondCipher.ProcessBytes(firstProcessedBytes);
|
||||
output.Write(secondProcessedBytes);
|
||||
output.Flush();
|
||||
progress?.Invoke($"{i} / {blockCount + 1} MB");
|
||||
}
|
||||
}
|
||||
|
||||
// Process additional data
|
||||
if (extraBytes > 0)
|
||||
{
|
||||
byte[] readBytes = input.ReadBytes(extraBytes);
|
||||
byte[] firstFinalBytes = firstCipher.DoFinal(readBytes);
|
||||
byte[] secondFinalBytes = secondCipher.DoFinal(firstFinalBytes);
|
||||
output.Write(secondFinalBytes);
|
||||
output.Flush();
|
||||
}
|
||||
|
||||
progress?.Invoke($"{blockCount + 1} / {blockCount + 1} MB... Done!\r\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
179
SabreTools.IO/Encryption/MoPaQDecrypter.cs
Normal file
179
SabreTools.IO/Encryption/MoPaQDecrypter.cs
Normal file
@@ -0,0 +1,179 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SabreTools.Hashing;
|
||||
using SabreTools.IO.Extensions;
|
||||
|
||||
namespace SabreTools.IO.Encryption
|
||||
{
|
||||
/// <summary>
|
||||
/// Handler for decrypting MoPaQ block and table data
|
||||
/// </summary>
|
||||
public class MoPaQDecrypter
|
||||
{
|
||||
#region Constants
|
||||
|
||||
private const uint MPQ_HASH_KEY2_MIX = 0x400;
|
||||
|
||||
private const uint STORM_BUFFER_SIZE = 0x500;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Instance Variables
|
||||
|
||||
/// <summary>
|
||||
/// Buffer for encryption and decryption
|
||||
/// </summary>
|
||||
private readonly uint[] _stormBuffer = new uint[STORM_BUFFER_SIZE];
|
||||
|
||||
#endregion
|
||||
|
||||
public MoPaQDecrypter()
|
||||
{
|
||||
PrepareCryptTable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prepare the encryption table
|
||||
/// </summary>
|
||||
private void PrepareCryptTable()
|
||||
{
|
||||
uint seed = 0x00100001;
|
||||
for (uint index1 = 0; index1 < 0x100; index1++)
|
||||
{
|
||||
for (uint index2 = index1, i = 0; i < 5; i++, index2 += 0x100)
|
||||
{
|
||||
seed = (seed * 125 + 3) % 0x2AAAAB;
|
||||
uint temp1 = (seed & 0xFFFF) << 0x10;
|
||||
|
||||
seed = (seed * 125 + 3) % 0x2AAAAB;
|
||||
uint temp2 = (seed & 0xFFFF);
|
||||
|
||||
_stormBuffer[index2] = (temp1 | temp2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load a table block by optionally decompressing and
|
||||
/// decrypting before returning the data.
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <param name="offset">Data offset to parse</param>
|
||||
/// <param name="expectedHash">Optional MD5 hash for validation</param>
|
||||
/// <param name="compressedSize">Size of the table in the file</param>
|
||||
/// <param name="tableSize">Expected size of the table</param>
|
||||
/// <param name="key">Encryption key to use</param>
|
||||
/// <param name="realTableSize">Output represening the real table size</param>
|
||||
/// <returns>Byte array representing the processed table</returns>
|
||||
public byte[]? LoadTable(Stream data,
|
||||
long offset,
|
||||
byte[]? expectedHash,
|
||||
uint compressedSize,
|
||||
uint tableSize,
|
||||
uint key,
|
||||
out long realTableSize)
|
||||
{
|
||||
byte[]? tableData;
|
||||
byte[]? readBytes;
|
||||
long bytesToRead = tableSize;
|
||||
|
||||
// Allocate the MPQ table
|
||||
tableData = readBytes = new byte[tableSize];
|
||||
|
||||
// Check if the MPQ table is compressed
|
||||
if (compressedSize != 0 && compressedSize < tableSize)
|
||||
{
|
||||
// Allocate temporary buffer for holding compressed data
|
||||
readBytes = new byte[compressedSize];
|
||||
bytesToRead = compressedSize;
|
||||
}
|
||||
|
||||
// Get the file offset from which we will read the table
|
||||
// Note: According to Storm.dll from Warcraft III (version 2002),
|
||||
// if the hash table position is 0xFFFFFFFF, no SetFilePointer call is done
|
||||
// and the table is loaded from the current file offset
|
||||
if (offset == 0xFFFFFFFF)
|
||||
offset = data.Position;
|
||||
|
||||
// Is the sector table within the file?
|
||||
if (offset >= data.Length)
|
||||
{
|
||||
realTableSize = 0;
|
||||
return null;
|
||||
}
|
||||
|
||||
// The hash table and block table can go beyond EOF.
|
||||
// Storm.dll reads as much as possible, then fills the missing part with zeros.
|
||||
// Abused by Spazzler map protector which sets hash table size to 0x00100000
|
||||
// Abused by NP_Protect in MPQs v4 as well
|
||||
if ((offset + bytesToRead) > data.Length)
|
||||
bytesToRead = (uint)(data.Length - offset);
|
||||
|
||||
// Give the caller information that the table was cut
|
||||
realTableSize = bytesToRead;
|
||||
|
||||
// If everything succeeded, read the raw table from the MPQ
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
_ = data.Read(readBytes, 0, (int)bytesToRead);
|
||||
|
||||
// Verify the MD5 of the table, if present
|
||||
byte[]? actualHash = HashTool.GetByteArrayHashArray(readBytes, HashType.MD5);
|
||||
if (expectedHash != null && actualHash != null && !actualHash.EqualsExactly(expectedHash))
|
||||
{
|
||||
Console.WriteLine("Table is corrupt!");
|
||||
return null;
|
||||
}
|
||||
|
||||
// First of all, decrypt the table
|
||||
if (key != 0)
|
||||
tableData = DecryptBlock(readBytes, bytesToRead, key);
|
||||
|
||||
// If the table is compressed, decompress it
|
||||
if (compressedSize != 0 && compressedSize < tableSize)
|
||||
{
|
||||
Console.WriteLine("Table is compressed, it will not read properly!");
|
||||
return null;
|
||||
|
||||
// TODO: Handle decompression
|
||||
// int cbOutBuffer = (int)tableSize;
|
||||
// int cbInBuffer = (int)compressedSize;
|
||||
|
||||
// if (!SCompDecompress2(readBytes, &cbOutBuffer, tableData, cbInBuffer))
|
||||
// errorCode = SErrGetLastError();
|
||||
|
||||
// tableData = readBytes;
|
||||
}
|
||||
|
||||
// Return the MPQ table
|
||||
return tableData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrypt a single block of data
|
||||
/// </summary>
|
||||
public unsafe byte[] DecryptBlock(byte[] block, long length, uint key)
|
||||
{
|
||||
uint seed = 0xEEEEEEEE;
|
||||
|
||||
uint[] castBlock = new uint[length >> 2];
|
||||
Buffer.BlockCopy(block, 0, castBlock, 0, (int)length);
|
||||
int castBlockPtr = 0;
|
||||
|
||||
// Round to uints
|
||||
length >>= 2;
|
||||
|
||||
while (length-- > 0)
|
||||
{
|
||||
seed += _stormBuffer[MPQ_HASH_KEY2_MIX + (key & 0xFF)];
|
||||
uint ch = castBlock[castBlockPtr] ^ (key + seed);
|
||||
|
||||
key = ((~key << 0x15) + 0x11111111) | (key >> 0x0B);
|
||||
seed = ch + seed + (seed << 5) + 3;
|
||||
castBlock[castBlockPtr++] = ch;
|
||||
}
|
||||
|
||||
Buffer.BlockCopy(castBlock, 0, block, 0, block.Length >> 2);
|
||||
return block;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,7 @@ using System.Numerics;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using SabreTools.Numerics;
|
||||
|
||||
namespace SabreTools.IO.Extensions
|
||||
{
|
||||
@@ -15,6 +16,24 @@ namespace SabreTools.IO.Extensions
|
||||
/// TODO: Handle proper negative values for Int24 and Int48
|
||||
public static class BinaryWriterExtensions
|
||||
{
|
||||
/// <inheritdoc cref="BinaryWriter.Write(byte)"/>
|
||||
/// <remarks>Writes in both-endian format</remarks>
|
||||
public static bool WriteBothEndian(this BinaryWriter writer, BothUInt8 value)
|
||||
{
|
||||
writer.Write(value.LittleEndian);
|
||||
writer.Write(value.BigEndian);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="BinaryWriter.Write(sbyte)"/>
|
||||
/// <remarks>Writes in both-endian format</remarks>
|
||||
public static bool WriteBothEndian(this BinaryWriter writer, BothInt8 value)
|
||||
{
|
||||
writer.Write(value.LittleEndian);
|
||||
writer.Write(value.BigEndian);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="BinaryWriter.Write(byte[])"/>
|
||||
/// <remarks>Writes in big-endian format</remarks>
|
||||
public static bool WriteBigEndian(this BinaryWriter writer, byte[] value)
|
||||
@@ -39,6 +58,15 @@ namespace SabreTools.IO.Extensions
|
||||
return WriteFromBuffer(writer, buffer);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="BinaryWriter.Write(short)"/>
|
||||
/// <remarks>Writes in both-endian format</remarks>
|
||||
public static bool WriteBothEndian(this BinaryWriter writer, BothInt16 value)
|
||||
{
|
||||
writer.Write(value.LittleEndian);
|
||||
writer.WriteBigEndian(value.BigEndian);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="BinaryWriter.Write(ushort)"/>
|
||||
/// <remarks>Writes in big-endian format</remarks>
|
||||
public static bool WriteBigEndian(this BinaryWriter writer, ushort value)
|
||||
@@ -48,6 +76,15 @@ namespace SabreTools.IO.Extensions
|
||||
return WriteFromBuffer(writer, buffer);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="BinaryWriter.Write(ushort)"/>
|
||||
/// <remarks>Writes in both-endian format</remarks>
|
||||
public static bool WriteBothEndian(this BinaryWriter writer, BothUInt16 value)
|
||||
{
|
||||
writer.Write(value.LittleEndian);
|
||||
writer.WriteBigEndian(value.BigEndian);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Half was introduced in net5.0 but doesn't have a BitConverter implementation until net6.0
|
||||
#if NET6_0_OR_GREATER
|
||||
/// <inheritdoc cref="BinaryWriter.Write(Half)"/>
|
||||
@@ -125,6 +162,15 @@ namespace SabreTools.IO.Extensions
|
||||
return WriteFromBuffer(writer, buffer);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="BinaryWriter.Write(int)"/>
|
||||
/// <remarks>Writes in both-endian format</remarks>
|
||||
public static bool WriteBothEndian(this BinaryWriter writer, BothInt32 value)
|
||||
{
|
||||
writer.Write(value.LittleEndian);
|
||||
writer.WriteBigEndian(value.BigEndian);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="BinaryWriter.Write(uint)"/>
|
||||
/// <remarks>Writes in big-endian format</remarks>
|
||||
public static bool WriteBigEndian(this BinaryWriter writer, uint value)
|
||||
@@ -134,6 +180,15 @@ namespace SabreTools.IO.Extensions
|
||||
return WriteFromBuffer(writer, buffer);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="BinaryWriter.Write(uint)"/>
|
||||
/// <remarks>Writes in both-endian format</remarks>
|
||||
public static bool WriteBothEndian(this BinaryWriter writer, BothUInt32 value)
|
||||
{
|
||||
writer.Write(value.LittleEndian);
|
||||
writer.WriteBigEndian(value.BigEndian);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="BinaryWriter.Write(float)"/>
|
||||
/// <remarks>Writes in big-endian format</remarks>
|
||||
public static bool WriteBigEndian(this BinaryWriter writer, float value)
|
||||
@@ -208,6 +263,15 @@ namespace SabreTools.IO.Extensions
|
||||
return WriteFromBuffer(writer, buffer);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="BinaryWriter.Write(long)"/>
|
||||
/// <remarks>Writes in both-endian format</remarks>
|
||||
public static bool WriteBothEndian(this BinaryWriter writer, BothInt64 value)
|
||||
{
|
||||
writer.Write(value.LittleEndian);
|
||||
writer.WriteBigEndian(value.BigEndian);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="BinaryWriter.Write(ulong)"/>
|
||||
/// <remarks>Writes in big-endian format</remarks>
|
||||
public static bool WriteBigEndian(this BinaryWriter writer, ulong value)
|
||||
@@ -217,6 +281,15 @@ namespace SabreTools.IO.Extensions
|
||||
return WriteFromBuffer(writer, buffer);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="BinaryWriter.Write(ulong)"/>
|
||||
/// <remarks>Writes in both-endian format</remarks>
|
||||
public static bool WriteBothEndian(this BinaryWriter writer, BothUInt64 value)
|
||||
{
|
||||
writer.Write(value.LittleEndian);
|
||||
writer.WriteBigEndian(value.BigEndian);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="BinaryWriter.Write(double)"/>
|
||||
/// <remarks>Writes in big-endian format</remarks>
|
||||
public static bool WriteBigEndian(this BinaryWriter writer, double value)
|
||||
@@ -342,6 +415,14 @@ namespace SabreTools.IO.Extensions
|
||||
public static bool WriteNullTerminatedAnsiString(this BinaryWriter writer, string? value)
|
||||
=> writer.WriteNullTerminatedString(value, Encoding.ASCII);
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
/// <summary>
|
||||
/// Write a null-terminated Latin1 string to the underlying stream
|
||||
/// </summary>
|
||||
public static bool WriteNullTerminatedLatin1String(this BinaryWriter writer, string? value)
|
||||
=> writer.WriteNullTerminatedString(value, Encoding.Latin1);
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Write a null-terminated UTF-8 string to the underlying stream
|
||||
/// </summary>
|
||||
@@ -354,6 +435,12 @@ namespace SabreTools.IO.Extensions
|
||||
public static bool WriteNullTerminatedUnicodeString(this BinaryWriter writer, string? value)
|
||||
=> writer.WriteNullTerminatedString(value, Encoding.Unicode);
|
||||
|
||||
/// <summary>
|
||||
/// Write a null-terminated UTF-16 (Unicode) string to the underlying stream
|
||||
/// </summary>
|
||||
public static bool WriteNullTerminatedBigEndianUnicodeString(this BinaryWriter writer, string? value)
|
||||
=> writer.WriteNullTerminatedString(value, Encoding.BigEndianUnicode);
|
||||
|
||||
/// <summary>
|
||||
/// Write a null-terminated UTF-32 string to the underlying stream
|
||||
/// </summary>
|
||||
@@ -379,6 +466,27 @@ namespace SabreTools.IO.Extensions
|
||||
return WriteFromBuffer(writer, buffer);
|
||||
}
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
/// <summary>
|
||||
/// Write a byte-prefixed Latin1 string to the underlying stream
|
||||
/// </summary>
|
||||
public static bool WritePrefixedLatin1String(this BinaryWriter writer, string? value)
|
||||
{
|
||||
// If the value is null
|
||||
if (value == null)
|
||||
return false;
|
||||
|
||||
// Get the buffer
|
||||
byte[] buffer = Encoding.Latin1.GetBytes(value);
|
||||
|
||||
// Write the length as a byte
|
||||
writer.Write((byte)value.Length);
|
||||
|
||||
// Write the buffer
|
||||
return WriteFromBuffer(writer, buffer);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Write a ushort-prefixed Unicode string to the underlying stream
|
||||
/// </summary>
|
||||
@@ -398,6 +506,25 @@ namespace SabreTools.IO.Extensions
|
||||
return WriteFromBuffer(writer, buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a ushort-prefixed Unicode string to the underlying stream
|
||||
/// </summary>
|
||||
public static bool WritePrefixedBigEndianUnicodeString(this BinaryWriter writer, string? value)
|
||||
{
|
||||
// If the value is null
|
||||
if (value == null)
|
||||
return false;
|
||||
|
||||
// Get the buffer
|
||||
byte[] buffer = Encoding.BigEndianUnicode.GetBytes(value);
|
||||
|
||||
// Write the length as a ushort
|
||||
writer.Write((ushort)value.Length);
|
||||
|
||||
// Write the buffer
|
||||
return WriteFromBuffer(writer, buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a <typeparamref name="T"/> to the underlying stream
|
||||
/// </summary>
|
||||
@@ -437,7 +564,7 @@ namespace SabreTools.IO.Extensions
|
||||
{
|
||||
writer.Write((Half)value);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if NET7_0_OR_GREATER
|
||||
else if (type == typeof(Int128))
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using SabreTools.IO.Matching;
|
||||
|
||||
namespace SabreTools.IO.Extensions
|
||||
{
|
||||
@@ -12,6 +16,399 @@ namespace SabreTools.IO.Extensions
|
||||
return array == null || array.Length == 0;
|
||||
}
|
||||
|
||||
#region Matching
|
||||
|
||||
/// <summary>
|
||||
/// Find all positions of one array in another, if possible
|
||||
/// </summary>
|
||||
/// <param name="stack">Byte array to search within</param>
|
||||
/// <param name="needle">Byte array representing the search value</param>
|
||||
/// <param name="start">Optional starting position in the stack, defaults to 0</param>
|
||||
/// <param name="end">Optional ending position in the stack, defaults to -1 (length of stack)</param>
|
||||
public static List<int> FindAllPositions(this byte[] stack, byte[] needle, int start = 0, int end = -1)
|
||||
{
|
||||
byte?[] nullableNeedle = Array.ConvertAll(needle, b => (byte?)b);
|
||||
return FindAllPositions(stack, nullableNeedle, start, end);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find all positions of one array in another, if possible
|
||||
/// </summary>
|
||||
/// <param name="stack">Byte array to search within</param>
|
||||
/// <param name="needle">Byte array representing the search value</param>
|
||||
/// <param name="start">Optional starting position in the stack, defaults to 0</param>
|
||||
/// <param name="end">Optional ending position in the stack, defaults to -1 (length of stack)</param>
|
||||
public static List<int> FindAllPositions(this byte[] stack, byte?[] needle, int start = 0, int end = -1)
|
||||
{
|
||||
// Get the outgoing list
|
||||
List<int> positions = [];
|
||||
|
||||
// If either set is null or empty
|
||||
if (stack.Length == 0 || needle.Length == 0)
|
||||
return positions;
|
||||
|
||||
// If the needle is longer than the stack
|
||||
if (needle.Length > stack.Length)
|
||||
return positions;
|
||||
|
||||
// Normalize the end value, if necessary
|
||||
if (end == -1)
|
||||
end = stack.Length;
|
||||
|
||||
// Validate the start and end values
|
||||
if (start < 0 || start >= stack.Length)
|
||||
return positions;
|
||||
if (end < -1 || end < start || end > stack.Length)
|
||||
return positions;
|
||||
|
||||
// Loop while there is data to check
|
||||
while (start < end)
|
||||
{
|
||||
// Create a new matcher for this segment
|
||||
var matcher = new ContentMatch(needle, start, end);
|
||||
|
||||
// Get the next matching position
|
||||
int position = matcher.Match(stack, reverse: false);
|
||||
if (position < 0)
|
||||
break;
|
||||
|
||||
// Append the position and reset the start index
|
||||
positions.Add(position);
|
||||
start = position + 1;
|
||||
}
|
||||
|
||||
return positions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the first position of one array in another, if possible
|
||||
/// </summary>
|
||||
/// <param name="stack">Byte array to search within</param>
|
||||
/// <param name="needle">Byte array representing the search value</param>
|
||||
/// <param name="start">Optional starting position in the stack, defaults to 0</param>
|
||||
/// <param name="end">Optional ending position in the stack, defaults to -1 (length of stack)</param>
|
||||
public static int FirstPosition(this byte[] stack, byte[] needle, int start = 0, int end = -1)
|
||||
{
|
||||
byte?[] nullableNeedle = Array.ConvertAll(needle, b => (byte?)b);
|
||||
return FirstPosition(stack, nullableNeedle, start, end);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the first position of one array in another, if possible
|
||||
/// </summary>
|
||||
/// <param name="stack">Byte array to search within</param>
|
||||
/// <param name="needle">Byte array representing the search value</param>
|
||||
/// <param name="start">Optional starting position in the stack, defaults to 0</param>
|
||||
/// <param name="end">Optional ending position in the stack, defaults to -1 (length of stack)</param>
|
||||
public static int FirstPosition(this byte[] stack, byte?[] needle, int start = 0, int end = -1)
|
||||
{
|
||||
// If either set is null or empty
|
||||
if (stack.Length == 0 || needle.Length == 0)
|
||||
return -1;
|
||||
|
||||
// If the needle is longer than the stack
|
||||
if (needle.Length > stack.Length)
|
||||
return -1;
|
||||
|
||||
var matcher = new ContentMatch(needle, start, end);
|
||||
return matcher.Match(stack, reverse: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the last position of one array in another, if possible
|
||||
/// </summary>
|
||||
/// <param name="stack">Byte array to search within</param>
|
||||
/// <param name="needle">Byte array representing the search value</param>
|
||||
/// <param name="start">Optional starting position in the stack, defaults to 0</param>
|
||||
/// <param name="end">Optional ending position in the stack, defaults to -1 (length of stack)</param>
|
||||
public static int LastPosition(this byte[] stack, byte[] needle, int start = 0, int end = -1)
|
||||
{
|
||||
byte?[] nullableNeedle = Array.ConvertAll(needle, b => (byte?)b);
|
||||
return LastPosition(stack, nullableNeedle, start, end);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the last position of one array in another, if possible
|
||||
/// </summary>
|
||||
/// <param name="stack">Byte array to search within</param>
|
||||
/// <param name="needle">Byte array representing the search value</param>
|
||||
/// <param name="start">Optional starting position in the stack, defaults to 0</param>
|
||||
/// <param name="end">Optional ending position in the stack, defaults to -1 (length of stack)</param>
|
||||
public static int LastPosition(this byte[] stack, byte?[] needle, int start = 0, int end = -1)
|
||||
{
|
||||
// If either set is null or empty
|
||||
if (stack.Length == 0 || needle.Length == 0)
|
||||
return -1;
|
||||
|
||||
// If the needle is longer than the stack
|
||||
if (needle.Length > stack.Length)
|
||||
return -1;
|
||||
|
||||
var matcher = new ContentMatch(needle, start, end);
|
||||
return matcher.Match(stack, reverse: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a byte array exactly matches another
|
||||
/// </summary>
|
||||
/// <param name="stack">Byte array to search within</param>
|
||||
/// <param name="needle">Byte array representing the search value</param>
|
||||
public static bool EqualsExactly(this byte[] stack, byte[] needle)
|
||||
{
|
||||
byte?[] nullableNeedle = Array.ConvertAll(needle, b => (byte?)b);
|
||||
return EqualsExactly(stack, nullableNeedle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a byte array exactly matches another
|
||||
/// </summary>
|
||||
/// <param name="stack">Byte array to search within</param>
|
||||
/// <param name="needle">Byte array representing the search value</param>
|
||||
public static bool EqualsExactly(this byte[] stack, byte?[] needle)
|
||||
{
|
||||
// If either set is null or empty
|
||||
if (stack.Length == 0 || needle.Length == 0)
|
||||
return false;
|
||||
|
||||
// If the needle is not the exact length of the stack
|
||||
if (needle.Length != stack.Length)
|
||||
return false;
|
||||
|
||||
return FirstPosition(stack, needle, start: 0, end: 1) == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a byte array starts with another
|
||||
/// </summary>
|
||||
/// <param name="stack">Byte array to search within</param>
|
||||
/// <param name="needle">Byte array representing the search value</param>
|
||||
public static bool StartsWith(this byte[] stack, byte[] needle)
|
||||
{
|
||||
byte?[] nullableNeedle = Array.ConvertAll(needle, b => (byte?)b);
|
||||
return StartsWith(stack, nullableNeedle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a byte array starts with another
|
||||
/// </summary>
|
||||
/// <param name="stack">Byte array to search within</param>
|
||||
/// <param name="needle">Byte array representing the search value</param>
|
||||
public static bool StartsWith(this byte[] stack, byte?[] needle)
|
||||
{
|
||||
// If either set is null or empty
|
||||
if (stack.Length == 0 || needle.Length == 0)
|
||||
return false;
|
||||
|
||||
// If the needle is longer than the stack
|
||||
if (needle.Length > stack.Length)
|
||||
return false;
|
||||
|
||||
return FirstPosition(stack, needle, start: 0, end: 1) > -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a byte array ends with another
|
||||
/// </summary>
|
||||
/// <param name="stack">Byte array to search within</param>
|
||||
/// <param name="needle">Byte array representing the search value</param>
|
||||
public static bool EndsWith(this byte[] stack, byte[] needle)
|
||||
{
|
||||
byte?[] nullableNeedle = Array.ConvertAll(needle, b => (byte?)b);
|
||||
return EndsWith(stack, nullableNeedle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a byte array ends with another
|
||||
/// </summary>
|
||||
/// <param name="stack">Byte array to search within</param>
|
||||
/// <param name="needle">Byte array representing the search value</param>
|
||||
public static bool EndsWith(this byte[] stack, byte?[] needle)
|
||||
{
|
||||
// If either set is null or empty
|
||||
if (stack.Length == 0 || needle.Length == 0)
|
||||
return false;
|
||||
|
||||
// If the needle is longer than the stack
|
||||
if (needle.Length > stack.Length)
|
||||
return false;
|
||||
|
||||
return FirstPosition(stack, needle, start: stack.Length - needle.Length) > -1;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Math
|
||||
|
||||
/// <summary>
|
||||
/// Add an integer value to a number represented by a byte array
|
||||
/// </summary>
|
||||
/// <param name="self">Byte array to add to</param>
|
||||
/// <param name="add">Amount to add</param>
|
||||
/// <returns>Byte array representing the new value</returns>
|
||||
/// <remarks>Assumes array values are in big-endian format</remarks>
|
||||
public static byte[] Add(this byte[] self, uint add)
|
||||
{
|
||||
// If nothing is being added, just return
|
||||
if (add == 0)
|
||||
return self;
|
||||
|
||||
// Get the big-endian representation of the value
|
||||
byte[] addBytes = BitConverter.GetBytes(add);
|
||||
Array.Reverse(addBytes);
|
||||
|
||||
// Pad the array out to 16 bytes
|
||||
byte[] paddedBytes = new byte[16];
|
||||
Array.Copy(addBytes, 0, paddedBytes, 12, 4);
|
||||
|
||||
// If the input is empty, just return the added value
|
||||
if (self.Length == 0)
|
||||
return paddedBytes;
|
||||
|
||||
return self.Add(paddedBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add two numbers represented by byte arrays
|
||||
/// </summary>
|
||||
/// <param name="self">Byte array to add to</param>
|
||||
/// <param name="add">Amount to add</param>
|
||||
/// <returns>Byte array representing the new value</returns>
|
||||
/// <remarks>Assumes array values are in big-endian format</remarks>
|
||||
public static byte[] Add(this byte[] self, byte[] add)
|
||||
{
|
||||
// If either input is empty
|
||||
if (self.Length == 0 && add.Length == 0)
|
||||
return [];
|
||||
else if (self.Length > 0 && add.Length == 0)
|
||||
return self;
|
||||
else if (self.Length == 0 && add.Length > 0)
|
||||
return add;
|
||||
|
||||
// Setup the output array
|
||||
int outLength = Math.Max(self.Length, add.Length);
|
||||
byte[] output = new byte[outLength];
|
||||
|
||||
// Loop adding with carry
|
||||
uint carry = 0;
|
||||
for (int i = 0; i < outLength; i++)
|
||||
{
|
||||
int selfIndex = self.Length - i - 1;
|
||||
uint selfValue = selfIndex >= 0 ? self[selfIndex] : 0u;
|
||||
|
||||
int addIndex = add.Length - i - 1;
|
||||
uint addValue = addIndex >= 0 ? add[addIndex] : 0u;
|
||||
|
||||
uint next = selfValue + addValue + carry;
|
||||
carry = next >> 8;
|
||||
|
||||
int outputIndex = output.Length - i - 1;
|
||||
output[outputIndex] = (byte)(next & 0xFF);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform a rotate left on a byte array
|
||||
/// </summary>
|
||||
/// <param name="self">Byte array value to rotate</param>
|
||||
/// <param name="numBits">Number of bits to rotate</param>
|
||||
/// <returns>Rotated byte array value</returns>
|
||||
/// <remarks>Assumes array values are in big-endian format</remarks>
|
||||
public static byte[] RotateLeft(this byte[] self, int numBits)
|
||||
{
|
||||
// If either input is empty
|
||||
if (self.Length == 0)
|
||||
return [];
|
||||
else if (numBits == 0)
|
||||
return self;
|
||||
|
||||
byte[] output = new byte[self.Length];
|
||||
Array.Copy(self, output, output.Length);
|
||||
|
||||
// Shift by bytes
|
||||
while (numBits >= 8)
|
||||
{
|
||||
byte temp = output[0];
|
||||
for (int i = 0; i < output.Length - 1; i++)
|
||||
{
|
||||
output[i] = output[i + 1];
|
||||
}
|
||||
|
||||
output[output.Length - 1] = temp;
|
||||
numBits -= 8;
|
||||
}
|
||||
|
||||
// Shift by bits
|
||||
if (numBits > 0)
|
||||
{
|
||||
byte bitMask = (byte)(8 - numBits), carry, wrap = 0;
|
||||
for (int i = 0; i < output.Length; i++)
|
||||
{
|
||||
carry = (byte)((255 << bitMask & output[i]) >> bitMask);
|
||||
|
||||
// Make sure the first byte carries to the end
|
||||
if (i == 0)
|
||||
wrap = carry;
|
||||
|
||||
// Otherwise, move to the last byte
|
||||
else
|
||||
output[i - 1] |= carry;
|
||||
|
||||
// Shift the current bits
|
||||
output[i] <<= numBits;
|
||||
}
|
||||
|
||||
// Make sure the wrap happens
|
||||
output[output.Length - 1] |= wrap;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// XOR two numbers represented by byte arrays
|
||||
/// </summary>
|
||||
/// <param name="self">Byte array to XOR to</param>
|
||||
/// <param name="xor">Amount to XOR</param>
|
||||
/// <returns>Byte array representing the new value</returns>
|
||||
/// <remarks>Assumes array values are in big-endian format</remarks>
|
||||
public static byte[] Xor(this byte[] self, byte[] xor)
|
||||
{
|
||||
// If either input is empty
|
||||
if (self.Length == 0 && xor.Length == 0)
|
||||
return [];
|
||||
else if (self.Length > 0 && xor.Length == 0)
|
||||
return self;
|
||||
else if (self.Length == 0 && xor.Length > 0)
|
||||
return xor;
|
||||
|
||||
// Setup the output array
|
||||
int outLength = Math.Max(self.Length, xor.Length);
|
||||
byte[] output = new byte[outLength];
|
||||
|
||||
// Loop XOR
|
||||
for (int i = 0; i < outLength; i++)
|
||||
{
|
||||
int selfIndex = self.Length - i - 1;
|
||||
uint selfValue = selfIndex >= 0 ? self[selfIndex] : 0u;
|
||||
|
||||
int xorIndex = xor.Length - i - 1;
|
||||
uint xorValue = xorIndex >= 0 ? xor[xorIndex] : 0u;
|
||||
|
||||
uint next = selfValue ^ xorValue;
|
||||
|
||||
int outputIndex = output.Length - i - 1;
|
||||
output[outputIndex] = (byte)(next & 0xFF);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Strings
|
||||
|
||||
/// <summary>
|
||||
/// Convert a byte array to a hex string
|
||||
/// </summary>
|
||||
@@ -50,5 +447,270 @@ namespace SabreTools.IO.Extensions
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read string data from a byte array
|
||||
/// </summary>
|
||||
/// <param name="charLimit">Number of characters needed to be a valid string, default 5</param>
|
||||
/// <returns>String list containing the requested data, null on error</returns>
|
||||
#if NET5_0_OR_GREATER
|
||||
/// <remarks>This reads both Latin1 and UTF-16 strings from the input data</remarks>
|
||||
#else
|
||||
/// <remarks>This reads both ASCII and UTF-16 strings from the input data</remarks>
|
||||
#endif
|
||||
public static List<string>? ReadStringsFrom(this byte[]? input, int charLimit = 5)
|
||||
{
|
||||
// Validate the data
|
||||
if (input == null || input.Length == 0)
|
||||
return null;
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
// Check for Latin1 strings
|
||||
var asciiStrings = input.ReadStringsWithEncoding(charLimit, Encoding.Latin1);
|
||||
#else
|
||||
// Check for ASCII strings
|
||||
var asciiStrings = input.ReadStringsWithEncoding(charLimit, Encoding.ASCII);
|
||||
#endif
|
||||
|
||||
// Check for Unicode strings
|
||||
// We are limiting the check for Unicode characters with a second byte of 0x00 for now
|
||||
var unicodeStrings = input.ReadStringsWithEncoding(charLimit, Encoding.Unicode);
|
||||
|
||||
// Ignore duplicate strings across encodings
|
||||
List<string> sourceStrings = [.. asciiStrings, .. unicodeStrings];
|
||||
|
||||
// Sort the strings and return
|
||||
sourceStrings.Sort();
|
||||
return sourceStrings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read string data from a byte array with an encoding
|
||||
/// </summary>
|
||||
/// <param name="bytes">Byte array representing the source data</param>
|
||||
/// <param name="charLimit">Number of characters needed to be a valid string</param>
|
||||
/// <param name="encoding">Character encoding to use for checking</param>
|
||||
/// <returns>String list containing the requested data, empty on error</returns>
|
||||
/// <remarks>Characters with the higher bytes set are unused</remarks>
|
||||
#if NET20
|
||||
public static List<string> ReadStringsWithEncoding(this byte[]? bytes, int charLimit, Encoding encoding)
|
||||
#else
|
||||
public static HashSet<string> ReadStringsWithEncoding(this byte[]? bytes, int charLimit, Encoding encoding)
|
||||
#endif
|
||||
{
|
||||
if (bytes == null || bytes.Length == 0)
|
||||
return [];
|
||||
if (charLimit <= 0 || charLimit > bytes.Length)
|
||||
return [];
|
||||
|
||||
// Short-circuit for some encoding types
|
||||
if (encoding.CodePage == Encoding.ASCII.CodePage)
|
||||
return bytes.ReadAsciiStrings(charLimit);
|
||||
#if NET5_0_OR_GREATER
|
||||
else if (encoding.CodePage == Encoding.Latin1.CodePage)
|
||||
return bytes.ReadFixedWidthEncodingStrings(charLimit, Encoding.Latin1, 1);
|
||||
#endif
|
||||
else if (encoding.IsSingleByte)
|
||||
return bytes.ReadFixedWidthEncodingStrings(charLimit, encoding, 1);
|
||||
else if (encoding.CodePage == Encoding.Unicode.CodePage)
|
||||
return bytes.ReadFixedWidthEncodingStrings(charLimit, Encoding.Unicode, 2);
|
||||
else if (encoding.CodePage == Encoding.BigEndianUnicode.CodePage)
|
||||
return bytes.ReadFixedWidthEncodingStrings(charLimit, Encoding.BigEndianUnicode, 2);
|
||||
else if (encoding.CodePage == Encoding.UTF32.CodePage)
|
||||
return bytes.ReadFixedWidthEncodingStrings(charLimit, Encoding.UTF32, 4);
|
||||
|
||||
// Create the string set to return
|
||||
#if NET20
|
||||
var strings = new List<string>();
|
||||
#else
|
||||
var strings = new HashSet<string>();
|
||||
#endif
|
||||
|
||||
// Open the text reader with the correct encoding
|
||||
using var ms = new MemoryStream(bytes);
|
||||
using var reader = new StreamReader(ms, encoding);
|
||||
|
||||
// Create a string builder for the loop
|
||||
var sb = new StringBuilder();
|
||||
|
||||
// Check for strings
|
||||
long lastOffset = 0;
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
// Read the next character from the stream
|
||||
char c = (char)reader.Read();
|
||||
|
||||
// If the character is invalid
|
||||
if (char.IsControl(c) || (c & 0xFFFFFF00) != 0)
|
||||
{
|
||||
// Seek to the end of the last found string
|
||||
string str = sb.ToString();
|
||||
lastOffset += encoding.GetByteCount(str) + 1;
|
||||
ms.Seek(lastOffset, SeekOrigin.Begin);
|
||||
reader.DiscardBufferedData();
|
||||
|
||||
// If there is no cached string
|
||||
if (str.Length == 0)
|
||||
continue;
|
||||
|
||||
// Add the string if long enough
|
||||
if (str.Length >= charLimit)
|
||||
strings.Add(str);
|
||||
|
||||
// Clear the builder and continue
|
||||
#if NET20 || NET35
|
||||
sb = new();
|
||||
#else
|
||||
sb.Clear();
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise, add the character to the builder and continue
|
||||
sb.Append(c);
|
||||
}
|
||||
|
||||
// Handle any remaining data
|
||||
if (sb.Length >= charLimit)
|
||||
strings.Add(sb.ToString());
|
||||
|
||||
return strings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read string data from a byte array using an encoding with a fixed width
|
||||
/// </summary>
|
||||
/// <param name="bytes">Byte array representing the source data</param>
|
||||
/// <param name="charLimit">Number of characters needed to be a valid string</param>
|
||||
/// <param name="encoding">Character encoding to use for checking</param>
|
||||
/// <param name="width">Character width of the encoding</param>
|
||||
/// <returns>String list containing the requested data, empty on error</returns>
|
||||
/// <remarks>Characters with the higher bytes set are unused</remarks>
|
||||
#if NET20
|
||||
private static List<string> ReadFixedWidthEncodingStrings(this byte[] bytes, int charLimit, Encoding encoding, int width)
|
||||
#else
|
||||
private static HashSet<string> ReadFixedWidthEncodingStrings(this byte[] bytes, int charLimit, Encoding encoding, int width)
|
||||
#endif
|
||||
{
|
||||
if (charLimit <= 0 || charLimit > bytes.Length)
|
||||
return [];
|
||||
|
||||
// Create the string set to return
|
||||
#if NET20
|
||||
var strings = new List<string>();
|
||||
#else
|
||||
var strings = new HashSet<string>();
|
||||
#endif
|
||||
|
||||
// Create a string builder for the loop
|
||||
var sb = new StringBuilder();
|
||||
|
||||
// Check for strings
|
||||
int offset = 0;
|
||||
while (offset <= bytes.Length - width)
|
||||
{
|
||||
// Read the next character from the stream
|
||||
char c = encoding.GetChars(bytes, offset, width)[0];
|
||||
offset += width;
|
||||
|
||||
// If the character is invalid
|
||||
if (char.IsControl(c) || (c & 0xFFFFFF00) != 0)
|
||||
{
|
||||
// Pretend only one byte was read
|
||||
offset -= width - 1;
|
||||
|
||||
// If there is no cached string
|
||||
if (sb.Length == 0)
|
||||
continue;
|
||||
|
||||
// Add the string if long enough
|
||||
if (sb.Length >= charLimit)
|
||||
strings.Add(sb.ToString());
|
||||
|
||||
// Clear the builder and continue
|
||||
#if NET20 || NET35
|
||||
sb = new();
|
||||
#else
|
||||
sb.Clear();
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise, add the character to the builder and continue
|
||||
sb.Append(c);
|
||||
}
|
||||
|
||||
// Handle any remaining data
|
||||
if (sb.Length >= charLimit)
|
||||
strings.Add(sb.ToString());
|
||||
|
||||
return strings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read string data from a byte array using ASCII encoding
|
||||
/// </summary>
|
||||
/// <param name="bytes">Byte array representing the source data</param>
|
||||
/// <param name="charLimit">Number of characters needed to be a valid string</param>
|
||||
/// <returns>String list containing the requested data, empty on error</returns>
|
||||
/// <remarks>Handling for 7-bit ASCII needs to be done differently than other fixed-width encodings</remarks>
|
||||
#if NET20
|
||||
private static List<string> ReadAsciiStrings(this byte[] bytes, int charLimit)
|
||||
#else
|
||||
private static HashSet<string> ReadAsciiStrings(this byte[] bytes, int charLimit)
|
||||
#endif
|
||||
{
|
||||
if (charLimit <= 0 || charLimit > bytes.Length)
|
||||
return [];
|
||||
|
||||
// Create the string set to return
|
||||
#if NET20
|
||||
var strings = new List<string>();
|
||||
#else
|
||||
var strings = new HashSet<string>();
|
||||
#endif
|
||||
|
||||
// Create a string builder for the loop
|
||||
var sb = new StringBuilder();
|
||||
|
||||
// Check for strings
|
||||
int offset = 0;
|
||||
while (offset < bytes.Length)
|
||||
{
|
||||
// Read the next character from the stream
|
||||
char c = bytes.ReadChar(ref offset);
|
||||
|
||||
// If the character is invalid
|
||||
if (char.IsControl(c) || c > 0x7F)
|
||||
{
|
||||
// If there is no cached string
|
||||
if (sb.Length == 0)
|
||||
continue;
|
||||
|
||||
// Add the string if long enough
|
||||
if (sb.Length >= charLimit)
|
||||
strings.Add(sb.ToString());
|
||||
|
||||
// Clear the builder and continue
|
||||
#if NET20 || NET35
|
||||
sb = new();
|
||||
#else
|
||||
sb.Clear();
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise, add the character to the builder and continue
|
||||
sb.Append(c);
|
||||
}
|
||||
|
||||
// Handle any remaining data
|
||||
if (sb.Length >= charLimit)
|
||||
strings.Add(sb.ToString());
|
||||
|
||||
return strings;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,7 @@ using System.Numerics;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using SabreTools.Numerics;
|
||||
|
||||
namespace SabreTools.IO.Extensions
|
||||
{
|
||||
@@ -20,6 +21,17 @@ namespace SabreTools.IO.Extensions
|
||||
public static bool Write(this byte[] content, ref int offset, byte value)
|
||||
=> WriteFromBuffer(content, ref offset, [value]);
|
||||
|
||||
/// <summary>
|
||||
/// Write a UInt8 and increment the pointer to an array
|
||||
/// </summary>
|
||||
/// <remarks>Writes in both-endian format</remarks>
|
||||
public static bool WriteBothEndian(this byte[] content, ref int offset, BothUInt8 value)
|
||||
{
|
||||
bool actual = content.Write(ref offset, value.LittleEndian);
|
||||
actual &= content.Write(ref offset, value.BigEndian);
|
||||
return actual;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a UInt8[] and increment the pointer to an array
|
||||
/// </summary>
|
||||
@@ -42,6 +54,17 @@ namespace SabreTools.IO.Extensions
|
||||
public static bool Write(this byte[] content, ref int offset, sbyte value)
|
||||
=> WriteFromBuffer(content, ref offset, [(byte)value]);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Int8 and increment the pointer to an array
|
||||
/// </summary>
|
||||
/// <remarks>Writes in both-endian format</remarks>
|
||||
public static bool WriteBothEndian(this byte[] content, ref int offset, BothInt8 value)
|
||||
{
|
||||
bool actual = content.Write(ref offset, value.LittleEndian);
|
||||
actual &= content.Write(ref offset, value.BigEndian);
|
||||
return actual;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a Char and increment the pointer to an array
|
||||
/// </summary>
|
||||
@@ -80,6 +103,17 @@ namespace SabreTools.IO.Extensions
|
||||
return WriteFromBuffer(content, ref offset, buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a Int16 and increment the pointer to an array
|
||||
/// </summary>
|
||||
/// <remarks>Writes in both-endian format</remarks>
|
||||
public static bool WriteBothEndian(this byte[] content, ref int offset, BothInt16 value)
|
||||
{
|
||||
bool actual = content.Write(ref offset, value.LittleEndian);
|
||||
actual &= content.WriteBigEndian(ref offset, value.BigEndian);
|
||||
return actual;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a UInt16 and increment the pointer to an array
|
||||
/// </summary>
|
||||
@@ -100,6 +134,17 @@ namespace SabreTools.IO.Extensions
|
||||
return WriteFromBuffer(content, ref offset, buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a UInt16 and increment the pointer to an array
|
||||
/// </summary>
|
||||
/// <remarks>Writes in both-endian format</remarks>
|
||||
public static bool WriteBothEndian(this byte[] content, ref int offset, BothUInt16 value)
|
||||
{
|
||||
bool actual = content.Write(ref offset, value.LittleEndian);
|
||||
actual &= content.WriteBigEndian(ref offset, value.BigEndian);
|
||||
return actual;
|
||||
}
|
||||
|
||||
// Half was introduced in net5.0 but doesn't have a BitConverter implementation until net6.0
|
||||
#if NET6_0_OR_GREATER
|
||||
/// <summary>
|
||||
@@ -199,6 +244,17 @@ namespace SabreTools.IO.Extensions
|
||||
return WriteFromBuffer(content, ref offset, buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a Int32 and increment the pointer to an array
|
||||
/// </summary>
|
||||
/// <remarks>Writes in both-endian format</remarks>
|
||||
public static bool WriteBothEndian(this byte[] content, ref int offset, BothInt32 value)
|
||||
{
|
||||
bool actual = content.Write(ref offset, value.LittleEndian);
|
||||
actual &= content.WriteBigEndian(ref offset, value.BigEndian);
|
||||
return actual;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a UInt32 and increment the pointer to an array
|
||||
/// </summary>
|
||||
@@ -219,6 +275,17 @@ namespace SabreTools.IO.Extensions
|
||||
return WriteFromBuffer(content, ref offset, buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a UInt32 and increment the pointer to an array
|
||||
/// </summary>
|
||||
/// <remarks>Writes in both-endian format</remarks>
|
||||
public static bool WriteBothEndian(this byte[] content, ref int offset, BothUInt32 value)
|
||||
{
|
||||
bool actual = content.Write(ref offset, value.LittleEndian);
|
||||
actual &= content.WriteBigEndian(ref offset, value.BigEndian);
|
||||
return actual;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a Single and increment the pointer to an array
|
||||
/// </summary>
|
||||
@@ -315,6 +382,17 @@ namespace SabreTools.IO.Extensions
|
||||
return WriteFromBuffer(content, ref offset, buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a Int64 and increment the pointer to an array
|
||||
/// </summary>
|
||||
/// <remarks>Writes in both-endian format</remarks>
|
||||
public static bool WriteBothEndian(this byte[] content, ref int offset, BothInt64 value)
|
||||
{
|
||||
bool actual = content.Write(ref offset, value.LittleEndian);
|
||||
actual &= content.WriteBigEndian(ref offset, value.BigEndian);
|
||||
return actual;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a UInt64 and increment the pointer to an array
|
||||
/// </summary>
|
||||
@@ -335,6 +413,17 @@ namespace SabreTools.IO.Extensions
|
||||
return WriteFromBuffer(content, ref offset, buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a UInt64 and increment the pointer to an array
|
||||
/// </summary>
|
||||
/// <remarks>Writes in both-endian format</remarks>
|
||||
public static bool WriteBothEndian(this byte[] content, ref int offset, BothUInt64 value)
|
||||
{
|
||||
bool actual = content.Write(ref offset, value.LittleEndian);
|
||||
actual &= content.WriteBigEndian(ref offset, value.BigEndian);
|
||||
return actual;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a Double and increment the pointer to an array
|
||||
/// </summary>
|
||||
@@ -426,7 +515,7 @@ namespace SabreTools.IO.Extensions
|
||||
public static bool Write(this byte[] content, ref int offset, Int128 value)
|
||||
{
|
||||
byte[] buffer = ((BigInteger)value).ToByteArray();
|
||||
|
||||
|
||||
byte[] padded = new byte[16];
|
||||
Array.Copy(buffer, 0, padded, 16 - buffer.Length, buffer.Length);
|
||||
return WriteFromBuffer(content, ref offset, padded);
|
||||
@@ -440,7 +529,7 @@ namespace SabreTools.IO.Extensions
|
||||
{
|
||||
byte[] buffer = ((BigInteger)value).ToByteArray();
|
||||
Array.Reverse(buffer);
|
||||
|
||||
|
||||
byte[] padded = new byte[16];
|
||||
Array.Copy(buffer, 0, padded, 16 - buffer.Length, buffer.Length);
|
||||
return WriteFromBuffer(content, ref offset, padded);
|
||||
@@ -452,7 +541,7 @@ namespace SabreTools.IO.Extensions
|
||||
public static bool Write(this byte[] content, ref int offset, UInt128 value)
|
||||
{
|
||||
byte[] buffer = ((BigInteger)value).ToByteArray();
|
||||
|
||||
|
||||
byte[] padded = new byte[16];
|
||||
Array.Copy(buffer, 0, padded, 16 - buffer.Length, buffer.Length);
|
||||
return WriteFromBuffer(content, ref offset, padded);
|
||||
@@ -466,7 +555,7 @@ namespace SabreTools.IO.Extensions
|
||||
{
|
||||
byte[] buffer = ((BigInteger)value).ToByteArray();
|
||||
Array.Reverse(buffer);
|
||||
|
||||
|
||||
byte[] padded = new byte[16];
|
||||
Array.Copy(buffer, 0, padded, 16 - buffer.Length, buffer.Length);
|
||||
return WriteFromBuffer(content, ref offset, padded);
|
||||
@@ -494,6 +583,14 @@ namespace SabreTools.IO.Extensions
|
||||
public static bool WriteNullTerminatedAnsiString(this byte[] content, ref int offset, string? value)
|
||||
=> content.WriteNullTerminatedString(ref offset, value, Encoding.ASCII);
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
/// <summary>
|
||||
/// Write a null-terminated Latin1 string to the byte array
|
||||
/// </summary>
|
||||
public static bool WriteNullTerminatedLatin1String(this byte[] content, ref int offset, string? value)
|
||||
=> content.WriteNullTerminatedString(ref offset, value, Encoding.Latin1);
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Write a null-terminated UTF-8 string to the byte array
|
||||
/// </summary>
|
||||
@@ -506,6 +603,12 @@ namespace SabreTools.IO.Extensions
|
||||
public static bool WriteNullTerminatedUnicodeString(this byte[] content, ref int offset, string? value)
|
||||
=> content.WriteNullTerminatedString(ref offset, value, Encoding.Unicode);
|
||||
|
||||
/// <summary>
|
||||
/// Write a null-terminated UTF-16 (Unicode) string to the byte array
|
||||
/// </summary>
|
||||
public static bool WriteNullTerminatedBigEndianUnicodeString(this byte[] content, ref int offset, string? value)
|
||||
=> content.WriteNullTerminatedString(ref offset, value, Encoding.BigEndianUnicode);
|
||||
|
||||
/// <summary>
|
||||
/// Write a null-terminated UTF-32 string to the byte array
|
||||
/// </summary>
|
||||
@@ -532,6 +635,28 @@ namespace SabreTools.IO.Extensions
|
||||
return WriteFromBuffer(content, ref offset, buffer);
|
||||
}
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
/// <summary>
|
||||
/// Write a byte-prefixed Latin1 string to the byte array
|
||||
/// </summary>
|
||||
public static bool WritePrefixedLatin1String(this byte[] content, ref int offset, string? value)
|
||||
{
|
||||
// If the value is null
|
||||
if (value == null)
|
||||
return false;
|
||||
|
||||
// Get the buffer
|
||||
byte[] buffer = Encoding.Latin1.GetBytes(value);
|
||||
|
||||
// Write the length as a byte
|
||||
if (!content.Write(ref offset, (byte)value.Length))
|
||||
return false;
|
||||
|
||||
// Write the buffer
|
||||
return WriteFromBuffer(content, ref offset, buffer);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Write a ushort-prefixed Unicode string to the byte array
|
||||
/// </summary>
|
||||
@@ -552,6 +677,26 @@ namespace SabreTools.IO.Extensions
|
||||
return WriteFromBuffer(content, ref offset, buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a ushort-prefixed Unicode string to the byte array
|
||||
/// </summary>
|
||||
public static bool WritePrefixedBigEndianUnicodeString(this byte[] content, ref int offset, string? value)
|
||||
{
|
||||
// If the value is null
|
||||
if (value == null)
|
||||
return false;
|
||||
|
||||
// Get the buffer
|
||||
byte[] buffer = Encoding.BigEndianUnicode.GetBytes(value);
|
||||
|
||||
// Write the length as a ushort
|
||||
if (!content.Write(ref offset, (ushort)value.Length))
|
||||
return false;
|
||||
|
||||
// Write the buffer
|
||||
return WriteFromBuffer(content, ref offset, buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a <typeparamref name="T"/> to the byte array
|
||||
/// </summary>
|
||||
|
||||
@@ -5,6 +5,25 @@ namespace SabreTools.IO.Extensions
|
||||
{
|
||||
public static class EnumerableExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Wrap iterating through an enumerable with an action
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// .NET Frameworks 2.0 and 3.5 process in series.
|
||||
/// .NET Frameworks 4.0 onward process in parallel.
|
||||
/// </remarks>
|
||||
public static void IterateWithAction<T>(this IEnumerable<T> source, Action<T> action)
|
||||
{
|
||||
#if NET20 || NET35
|
||||
foreach (var item in source)
|
||||
{
|
||||
action(item);
|
||||
}
|
||||
#else
|
||||
System.Threading.Tasks.Parallel.ForEach(source, action);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Safely iterate through an enumerable, skipping any errors
|
||||
/// </summary>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace SabreTools.IO.Extensions
|
||||
@@ -10,7 +11,7 @@ namespace SabreTools.IO.Extensions
|
||||
/// <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)
|
||||
public static bool AlignToBoundary(this Stream? input, int alignment)
|
||||
{
|
||||
// If the stream is invalid
|
||||
if (input == null || input.Length == 0 || !input.CanRead)
|
||||
@@ -30,12 +31,83 @@ namespace SabreTools.IO.Extensions
|
||||
return input.Position % alignment == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a number of bytes from an offset in a stream, if possible
|
||||
/// </summary>
|
||||
/// <param name="input">Input stream to read from</param>
|
||||
/// <param name="offset">Offset within the stream to start reading</param>
|
||||
/// <param name="length">Number of bytes to read from the offset</param>
|
||||
/// <param name="retainPosition">Indicates if the original position of the stream should be retained after reading</param>
|
||||
/// <returns>Filled byte array on success, null on error</returns>
|
||||
/// <remarks>
|
||||
/// This method will return a null array if the length is greater than what is left
|
||||
/// in the stream. This is different behavior than a normal stream read that would
|
||||
/// attempt to read as much as possible, returning the amount of bytes read.
|
||||
/// </remarks>
|
||||
public static byte[]? ReadFrom(this Stream? input, long offset, int length, bool retainPosition)
|
||||
{
|
||||
if (input == null || !input.CanRead || !input.CanSeek)
|
||||
return null;
|
||||
if (offset < 0 || offset >= input.Length)
|
||||
return null;
|
||||
if (length < 0 || offset + length > input.Length)
|
||||
return null;
|
||||
|
||||
// Cache the current location
|
||||
long currentLocation = input.Position;
|
||||
|
||||
// Seek to the requested offset
|
||||
long newPosition = input.SeekIfPossible(offset);
|
||||
if (newPosition != offset)
|
||||
return null;
|
||||
|
||||
// Read from the position
|
||||
byte[] data = input.ReadBytes(length);
|
||||
|
||||
// Seek back if requested
|
||||
if (retainPosition)
|
||||
_ = input.SeekIfPossible(currentLocation);
|
||||
|
||||
// Return the read data
|
||||
return data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read string data from a Stream
|
||||
/// </summary>
|
||||
/// <param name="charLimit">Number of characters needed to be a valid string, default 5</param>
|
||||
/// <param name="position">Position in the source to read from</param>
|
||||
/// <param name="length">Length of the requested data</param>
|
||||
/// <returns>String list containing the requested data, null on error</returns>
|
||||
#if NET5_0_OR_GREATER
|
||||
/// <remarks>This reads both Latin1 and UTF-16 strings from the input data</remarks>
|
||||
#else
|
||||
/// <remarks>This reads both ASCII and UTF-16 strings from the input data</remarks>
|
||||
#endif
|
||||
public static List<string>? ReadStringsFrom(this Stream? input, int position, int length, int charLimit = 5)
|
||||
{
|
||||
// Read the data as a byte array first
|
||||
byte[]? data = input.ReadFrom(position, length, retainPosition: true);
|
||||
if (data == null)
|
||||
return null;
|
||||
|
||||
return data.ReadStringsFrom(charLimit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Seek to a specific point in the stream, if possible
|
||||
/// </summary>
|
||||
/// <param name="input">Input stream to try seeking on</param>
|
||||
/// <param name="offset">Optional offset to seek to</param>
|
||||
public static long SeekIfPossible(this Stream input, long offset = 0)
|
||||
=> input.SeekIfPossible(offset, offset < 0 ? SeekOrigin.End : SeekOrigin.Begin);
|
||||
|
||||
/// <summary>
|
||||
/// Seek to a specific point in the stream, if possible
|
||||
/// </summary>
|
||||
/// <param name="input">Input stream to try seeking on</param>
|
||||
/// <param name="offset">Optional offset to seek to</param>
|
||||
public static long SeekIfPossible(this Stream input, long offset, SeekOrigin origin)
|
||||
{
|
||||
// If the input is not seekable, just return the current position
|
||||
if (!input.CanSeek)
|
||||
@@ -52,15 +124,31 @@ namespace SabreTools.IO.Extensions
|
||||
// Attempt to seek to the offset
|
||||
try
|
||||
{
|
||||
if (offset < 0)
|
||||
return input.Seek(offset, SeekOrigin.End);
|
||||
else
|
||||
return input.Seek(offset, SeekOrigin.Begin);
|
||||
return input.Seek(offset, origin);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a segment is valid in the stream
|
||||
/// </summary>
|
||||
/// <param name="input">Input stream to validate</param>
|
||||
/// <param name="offset">Position in the source</param>
|
||||
/// <param name="count">Length of the data to check</param>
|
||||
/// <returns>True if segment could be read fully, false otherwise</returns>
|
||||
public static bool SegmentValid(this Stream? input, long offset, long count)
|
||||
{
|
||||
if (input == null)
|
||||
return false;
|
||||
if (offset < 0 || offset > input.Length)
|
||||
return false;
|
||||
if (count < 0 || offset + count > input.Length)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,7 @@ using System.Numerics;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using SabreTools.Numerics;
|
||||
|
||||
namespace SabreTools.IO.Extensions
|
||||
{
|
||||
@@ -21,6 +22,17 @@ namespace SabreTools.IO.Extensions
|
||||
public static bool Write(this Stream stream, byte value)
|
||||
=> WriteFromBuffer(stream, [value]);
|
||||
|
||||
/// <summary>
|
||||
/// Write a UInt8
|
||||
/// </summary>
|
||||
/// <remarks>Writes in both-endian format</remarks>
|
||||
public static bool WriteBothEndian(this Stream stream, BothUInt8 value)
|
||||
{
|
||||
bool actual = stream.Write(value.LittleEndian);
|
||||
actual &= stream.Write(value.BigEndian);
|
||||
return actual;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a UInt8[]
|
||||
/// </summary>
|
||||
@@ -43,6 +55,17 @@ namespace SabreTools.IO.Extensions
|
||||
public static bool Write(this Stream stream, sbyte value)
|
||||
=> WriteFromBuffer(stream, [(byte)value]);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Int8
|
||||
/// </summary>
|
||||
/// <remarks>Writes in both-endian format</remarks>
|
||||
public static bool WriteBothEndian(this Stream stream, BothInt8 value)
|
||||
{
|
||||
bool actual = stream.Write(value.LittleEndian);
|
||||
actual &= stream.Write(value.BigEndian);
|
||||
return actual;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a Char
|
||||
/// </summary>
|
||||
@@ -81,6 +104,17 @@ namespace SabreTools.IO.Extensions
|
||||
return WriteFromBuffer(stream, buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a Int16
|
||||
/// </summary>
|
||||
/// <remarks>Writes in both-endian format</remarks>
|
||||
public static bool WriteBothEndian(this Stream stream, BothInt16 value)
|
||||
{
|
||||
bool actual = stream.Write(value.LittleEndian);
|
||||
actual &= stream.WriteBigEndian(value.BigEndian);
|
||||
return actual;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a UInt16
|
||||
/// </summary>
|
||||
@@ -101,6 +135,17 @@ namespace SabreTools.IO.Extensions
|
||||
return WriteFromBuffer(stream, buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a UInt16
|
||||
/// </summary>
|
||||
/// <remarks>Writes in both-endian format</remarks>
|
||||
public static bool WriteBothEndian(this Stream stream, BothUInt16 value)
|
||||
{
|
||||
bool actual = stream.Write(value.LittleEndian);
|
||||
actual &= stream.WriteBigEndian(value.BigEndian);
|
||||
return actual;
|
||||
}
|
||||
|
||||
// Half was introduced in net5.0 but doesn't have a BitConverter implementation until net6.0
|
||||
#if NET6_0_OR_GREATER
|
||||
/// <summary>
|
||||
@@ -200,6 +245,17 @@ namespace SabreTools.IO.Extensions
|
||||
return WriteFromBuffer(stream, buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a Int32
|
||||
/// </summary>
|
||||
/// <remarks>Writes in both-endian format</remarks>
|
||||
public static bool WriteBothEndian(this Stream stream, BothInt32 value)
|
||||
{
|
||||
bool actual = stream.Write(value.LittleEndian);
|
||||
actual &= stream.WriteBigEndian(value.BigEndian);
|
||||
return actual;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a UInt32
|
||||
/// </summary>
|
||||
@@ -220,6 +276,17 @@ namespace SabreTools.IO.Extensions
|
||||
return WriteFromBuffer(stream, buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a UInt32
|
||||
/// </summary>
|
||||
/// <remarks>Writes in both-endian format</remarks>
|
||||
public static bool WriteBothEndian(this Stream stream, BothUInt32 value)
|
||||
{
|
||||
bool actual = stream.Write(value.LittleEndian);
|
||||
actual &= stream.WriteBigEndian(value.BigEndian);
|
||||
return actual;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a Single
|
||||
/// </summary>
|
||||
@@ -316,6 +383,17 @@ namespace SabreTools.IO.Extensions
|
||||
return WriteFromBuffer(stream, buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a Int64
|
||||
/// </summary>
|
||||
/// <remarks>Writes in both-endian format</remarks>
|
||||
public static bool WriteBothEndian(this Stream stream, BothInt64 value)
|
||||
{
|
||||
bool actual = stream.Write(value.LittleEndian);
|
||||
actual &= stream.WriteBigEndian(value.BigEndian);
|
||||
return actual;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a UInt64
|
||||
/// </summary>
|
||||
@@ -336,6 +414,17 @@ namespace SabreTools.IO.Extensions
|
||||
return WriteFromBuffer(stream, buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a UInt64
|
||||
/// </summary>
|
||||
/// <remarks>Writes in both-endian format</remarks>
|
||||
public static bool WriteBothEndian(this Stream stream, BothUInt64 value)
|
||||
{
|
||||
bool actual = stream.Write(value.LittleEndian);
|
||||
actual &= stream.WriteBigEndian(value.BigEndian);
|
||||
return actual;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a Double
|
||||
/// </summary>
|
||||
@@ -495,6 +584,14 @@ namespace SabreTools.IO.Extensions
|
||||
public static bool WriteNullTerminatedAnsiString(this Stream stream, string? value)
|
||||
=> stream.WriteNullTerminatedString(value, Encoding.ASCII);
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
/// <summary>
|
||||
/// Write a null-terminated Latin1 string to the stream
|
||||
/// </summary>
|
||||
public static bool WriteNullTerminatedLatin1String(this Stream stream, string? value)
|
||||
=> stream.WriteNullTerminatedString(value, Encoding.Latin1);
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Write a null-terminated UTF-8 string to the stream
|
||||
/// </summary>
|
||||
@@ -507,6 +604,12 @@ namespace SabreTools.IO.Extensions
|
||||
public static bool WriteNullTerminatedUnicodeString(this Stream stream, string? value)
|
||||
=> stream.WriteNullTerminatedString(value, Encoding.Unicode);
|
||||
|
||||
/// <summary>
|
||||
/// Write a null-terminated UTF-16 (Unicode) string to the stream
|
||||
/// </summary>
|
||||
public static bool WriteNullTerminatedBigEndianUnicodeString(this Stream stream, string? value)
|
||||
=> stream.WriteNullTerminatedString(value, Encoding.BigEndianUnicode);
|
||||
|
||||
/// <summary>
|
||||
/// Write a null-terminated UTF-32 string to the stream
|
||||
/// </summary>
|
||||
@@ -533,6 +636,28 @@ namespace SabreTools.IO.Extensions
|
||||
return WriteFromBuffer(stream, buffer);
|
||||
}
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
//// <summary>
|
||||
/// Write a byte-prefixed Latin1 string to the stream
|
||||
/// </summary>
|
||||
public static bool WritePrefixedLatin1String(this Stream stream, string? value)
|
||||
{
|
||||
// If the value is null
|
||||
if (value == null)
|
||||
return false;
|
||||
|
||||
// Get the buffer
|
||||
byte[] buffer = Encoding.Latin1.GetBytes(value);
|
||||
|
||||
// Write the length as a byte
|
||||
if (!stream.Write((byte)value.Length))
|
||||
return false;
|
||||
|
||||
// Write the buffer
|
||||
return WriteFromBuffer(stream, buffer);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Write a ushort-prefixed Unicode string to the stream
|
||||
/// </summary>
|
||||
@@ -553,6 +678,26 @@ namespace SabreTools.IO.Extensions
|
||||
return WriteFromBuffer(stream, buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a ushort-prefixed Unicode string to the stream
|
||||
/// </summary>
|
||||
public static bool WritePrefixedBigEndianUnicodeString(this Stream stream, string? value)
|
||||
{
|
||||
// If the value is null
|
||||
if (value == null)
|
||||
return false;
|
||||
|
||||
// Get the buffer
|
||||
byte[] buffer = Encoding.BigEndianUnicode.GetBytes(value);
|
||||
|
||||
// Write the length as a ushort
|
||||
if (!stream.Write((ushort)value.Length))
|
||||
return false;
|
||||
|
||||
// Write the buffer
|
||||
return WriteFromBuffer(stream, buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a <typeparamref name="T"/> to the stream
|
||||
/// </summary>
|
||||
|
||||
63
SabreTools.IO/Extensions/StringExtensions.cs
Normal file
63
SabreTools.IO/Extensions/StringExtensions.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using System;
|
||||
|
||||
namespace SabreTools.IO.Extensions
|
||||
{
|
||||
public static class StringExtensions
|
||||
{
|
||||
/// <inheritdoc cref="string.Contains(string)"/>
|
||||
public static bool OptionalContains(this string? self, string value)
|
||||
=> OptionalContains(self, value, StringComparison.Ordinal);
|
||||
|
||||
/// <inheritdoc cref="string.Contains(string, StringComparison)"/>
|
||||
public static bool OptionalContains(this string? self, string value, StringComparison comparisonType)
|
||||
{
|
||||
if (self == null)
|
||||
return false;
|
||||
|
||||
#if NETFRAMEWORK || NETSTANDARD2_0
|
||||
return self.Contains(value);
|
||||
#else
|
||||
return self.Contains(value, comparisonType);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="string.EndsWith(string)"/>
|
||||
public static bool OptionalEndsWith(this string? self, string value)
|
||||
=> OptionalEndsWith(self, value, StringComparison.Ordinal);
|
||||
|
||||
/// <inheritdoc cref="string.EndsWith(string, StringComparison)"/>
|
||||
public static bool OptionalEndsWith(this string? self, string value, StringComparison comparisonType)
|
||||
{
|
||||
if (self == null)
|
||||
return false;
|
||||
|
||||
return self.EndsWith(value, comparisonType);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="string.Equals(string)"/>
|
||||
public static bool OptionalEquals(this string? self, string value)
|
||||
=> OptionalEquals(self, value, StringComparison.Ordinal);
|
||||
|
||||
/// <inheritdoc cref="string.Equals(string, StringComparison)"/>
|
||||
public static bool OptionalEquals(this string? self, string value, StringComparison comparisonType)
|
||||
{
|
||||
if (self == null)
|
||||
return false;
|
||||
|
||||
return self.Equals(value, comparisonType);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="string.StartsWith(string)"/>
|
||||
public static bool OptionalStartsWith(this string? self, string value)
|
||||
=> OptionalStartsWith(self, value, StringComparison.Ordinal);
|
||||
|
||||
/// <inheritdoc cref="string.StartsWith(string, StringComparison)"/>
|
||||
public static bool OptionalStartsWith(this string? self, string value, StringComparison comparisonType)
|
||||
{
|
||||
if (self == null)
|
||||
return false;
|
||||
|
||||
return self.StartsWith(value, comparisonType);
|
||||
}
|
||||
}
|
||||
}
|
||||
13
SabreTools.IO/Interfaces/IMatch.cs
Normal file
13
SabreTools.IO/Interfaces/IMatch.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace SabreTools.IO.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a matcher for a particular type
|
||||
/// </summary>
|
||||
public interface IMatch<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Nullable typed data to be matched
|
||||
/// </summary>
|
||||
T? Needle { get; }
|
||||
}
|
||||
}
|
||||
20
SabreTools.IO/Interfaces/IMatchSet.cs
Normal file
20
SabreTools.IO/Interfaces/IMatchSet.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SabreTools.IO.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// Wrapper for a single set of matching criteria
|
||||
/// </summary>
|
||||
public interface IMatchSet<T, U> where T : IMatch<U>
|
||||
{
|
||||
/// <summary>
|
||||
/// Set of all matchers
|
||||
/// </summary>
|
||||
public List<T> Matchers { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Unique name for the match set
|
||||
/// </summary>
|
||||
public string SetName { get; }
|
||||
}
|
||||
}
|
||||
363
SabreTools.IO/MatchUtil.cs
Normal file
363
SabreTools.IO/MatchUtil.cs
Normal file
@@ -0,0 +1,363 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using SabreTools.IO.Matching;
|
||||
|
||||
namespace SabreTools.IO
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class for matching
|
||||
/// </summary>
|
||||
public static class MatchUtil
|
||||
{
|
||||
#region Array Content Matching
|
||||
|
||||
/// <summary>
|
||||
/// Get all content matches for a given list of matchers
|
||||
/// </summary>
|
||||
/// <param name="file">File to check for matches</param>
|
||||
/// <param name="stack">Array to search</param>
|
||||
/// <param name="matchSets">List of ContentMatchSets to be run on the file</param>
|
||||
/// <param name="any">True if any content match is a success, false if all have to match</param>
|
||||
/// <param name="includeDebug">True to include positional data, false otherwise</param>
|
||||
/// <returns>List of strings representing the matches, null or empty otherwise</returns>
|
||||
public static List<string> GetAllMatches(string file,
|
||||
byte[]? stack,
|
||||
List<ContentMatchSet> matchSets,
|
||||
bool any = false,
|
||||
bool includeDebug = false)
|
||||
=> FindAllMatches(file, stack, matchSets, any, includeDebug, false);
|
||||
|
||||
/// <summary>
|
||||
/// Get first content match for a given list of matchers
|
||||
/// </summary>
|
||||
/// <param name="file">File to check for matches</param>
|
||||
/// <param name="stack">Array to search</param>
|
||||
/// <param name="matchSets">List of ContentMatchSets to be run on the file</param>
|
||||
/// <param name="any">True if any content match is a success, false if all have to match</param>
|
||||
/// <param name="includeDebug">True to include positional data, false otherwise</param>
|
||||
/// <returns>String representing the match, null otherwise</returns>
|
||||
public static string? GetFirstMatch(string file,
|
||||
byte[]? stack,
|
||||
List<ContentMatchSet> matchSets,
|
||||
bool any = false,
|
||||
bool includeDebug = false)
|
||||
{
|
||||
var contentMatches = FindAllMatches(file, stack, matchSets, any, includeDebug, true);
|
||||
if (contentMatches == null || contentMatches.Count == 0)
|
||||
return null;
|
||||
|
||||
return contentMatches[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the required set of content matches on a per Matcher basis
|
||||
/// </summary>
|
||||
/// <param name="file">File to check for matches</param>
|
||||
/// <param name="stack">Array to search</param>
|
||||
/// <param name="matchSets">List of ContentMatchSets to be run on the file</param>
|
||||
/// <param name="any">True if any content match is a success, false if all have to match</param>
|
||||
/// <param name="includeDebug">True to include positional data, false otherwise</param>
|
||||
/// <param name="stopAfterFirst">True to stop after the first match, false otherwise</param>
|
||||
/// <returns>List of strings representing the matches, empty otherwise</returns>
|
||||
private static List<string> FindAllMatches(string file,
|
||||
byte[]? stack,
|
||||
List<ContentMatchSet> matchSets,
|
||||
bool any,
|
||||
bool includeDebug,
|
||||
bool stopAfterFirst)
|
||||
{
|
||||
// If either set is null or empty
|
||||
if (stack == null || stack.Length == 0 || matchSets.Count == 0)
|
||||
return [];
|
||||
|
||||
// Initialize the list of matches
|
||||
var matchesList = new List<string>();
|
||||
|
||||
// Loop through and try everything otherwise
|
||||
foreach (var matcher in matchSets)
|
||||
{
|
||||
// Determine if the matcher passes
|
||||
List<int> positions = any
|
||||
? [matcher.MatchesAny(stack)]
|
||||
: matcher.MatchesAll(stack);
|
||||
|
||||
// If we don't have a pass, just continue
|
||||
if (positions.Count == 0 || positions[0] == -1)
|
||||
continue;
|
||||
|
||||
// Build the output string
|
||||
var matchString = new StringBuilder();
|
||||
matchString.Append(matcher.SetName);
|
||||
|
||||
// Invoke the version delegate, if it exists
|
||||
if (matcher.GetArrayVersion != null)
|
||||
{
|
||||
// A null version returned means the check didn't pass at the version step
|
||||
var version = matcher.GetArrayVersion(file, stack, positions);
|
||||
if (version == null)
|
||||
continue;
|
||||
|
||||
// Trim and add the version
|
||||
version = version.Trim();
|
||||
if (version.Length > 0)
|
||||
matchString.Append($" {version}");
|
||||
}
|
||||
|
||||
// Append the positional data if required
|
||||
if (includeDebug)
|
||||
{
|
||||
string positionsString = string.Join(", ", [.. positions.ConvertAll(p => p.ToString())]);
|
||||
matchString.Append($" (Index {positionsString})");
|
||||
}
|
||||
|
||||
// Append the match to the list
|
||||
matchesList.Add(matchString.ToString());
|
||||
|
||||
// If we're stopping after the first match, bail out here
|
||||
if (stopAfterFirst)
|
||||
return matchesList;
|
||||
}
|
||||
|
||||
return matchesList;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Stream Content Matching
|
||||
|
||||
/// <summary>
|
||||
/// Get all content matches for a given list of matchers
|
||||
/// </summary>
|
||||
/// <param name="file">File to check for matches</param>
|
||||
/// <param name="stack">Stream to search</param>
|
||||
/// <param name="matchSets">List of ContentMatchSets to be run on the file</param>
|
||||
/// <param name="any">True if any content match is a success, false if all have to match</param>
|
||||
/// <param name="includeDebug">True to include positional data, false otherwise</param>
|
||||
/// <returns>List of strings representing the matches, null or empty otherwise</returns>
|
||||
public static List<string> GetAllMatches(string file,
|
||||
Stream? stack,
|
||||
List<ContentMatchSet> matchSets,
|
||||
bool any = false,
|
||||
bool includeDebug = false)
|
||||
=> FindAllMatches(file, stack, matchSets, any, includeDebug, false);
|
||||
|
||||
/// <summary>
|
||||
/// Get first content match for a given list of matchers
|
||||
/// </summary>
|
||||
/// <param name="file">File to check for matches</param>
|
||||
/// <param name="stack">Stream to search</param>
|
||||
/// <param name="matchSets">List of ContentMatchSets to be run on the file</param>
|
||||
/// <param name="any">True if any content match is a success, false if all have to match</param>
|
||||
/// <param name="includeDebug">True to include positional data, false otherwise</param>
|
||||
/// <returns>String representing the match, null otherwise</returns>
|
||||
public static string? GetFirstMatch(string file,
|
||||
Stream? stack,
|
||||
List<ContentMatchSet> matchSets,
|
||||
bool any = false,
|
||||
bool includeDebug = false)
|
||||
{
|
||||
var contentMatches = FindAllMatches(file, stack, matchSets, any, includeDebug, true);
|
||||
if (contentMatches == null || contentMatches.Count == 0)
|
||||
return null;
|
||||
|
||||
return contentMatches[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the required set of content matches on a per Matcher basis
|
||||
/// </summary>
|
||||
/// <param name="file">File to check for matches</param>
|
||||
/// <param name="stack">Stream to search</param>
|
||||
/// <param name="matchSets">List of ContentMatchSets to be run on the file</param>
|
||||
/// <param name="any">True if any content match is a success, false if all have to match</param>
|
||||
/// <param name="includeDebug">True to include positional data, false otherwise</param>
|
||||
/// <param name="stopAfterFirst">True to stop after the first match, false otherwise</param>
|
||||
/// <returns>List of strings representing the matches, empty otherwise</returns>
|
||||
private static List<string> FindAllMatches(string file,
|
||||
Stream? stack,
|
||||
List<ContentMatchSet> matchSets,
|
||||
bool any,
|
||||
bool includeDebug,
|
||||
bool stopAfterFirst)
|
||||
{
|
||||
// If either set is null or empty
|
||||
if (stack == null || stack.Length == 0 || matchSets.Count == 0)
|
||||
return [];
|
||||
|
||||
// Initialize the list of matches
|
||||
var matchesList = new List<string>();
|
||||
|
||||
// Loop through and try everything otherwise
|
||||
foreach (var matcher in matchSets)
|
||||
{
|
||||
// Determine if the matcher passes
|
||||
List<int> positions = any
|
||||
? [matcher.MatchesAny(stack)]
|
||||
: matcher.MatchesAll(stack);
|
||||
|
||||
// If we don't have a pass, just continue
|
||||
if (positions.Count == 0 || positions[0] == -1)
|
||||
continue;
|
||||
|
||||
// Build the output string
|
||||
var matchString = new StringBuilder();
|
||||
matchString.Append(matcher.SetName);
|
||||
|
||||
// Invoke the version delegate, if it exists
|
||||
if (matcher.GetStreamVersion != null)
|
||||
{
|
||||
// A null version returned means the check didn't pass at the version step
|
||||
var version = matcher.GetStreamVersion(file, stack, positions);
|
||||
if (version == null)
|
||||
continue;
|
||||
|
||||
// Trim and add the version
|
||||
version = version.Trim();
|
||||
if (version.Length > 0)
|
||||
matchString.Append($" {version}");
|
||||
}
|
||||
|
||||
// Append the positional data if required
|
||||
if (includeDebug)
|
||||
{
|
||||
string positionsString = string.Join(", ", [.. positions.ConvertAll(p => p.ToString())]);
|
||||
matchString.Append($" (Index {positionsString})");
|
||||
}
|
||||
|
||||
// Append the match to the list
|
||||
matchesList.Add(matchString.ToString());
|
||||
|
||||
// If we're stopping after the first match, bail out here
|
||||
if (stopAfterFirst)
|
||||
return matchesList;
|
||||
}
|
||||
|
||||
return matchesList;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Path Matching
|
||||
|
||||
/// <summary>
|
||||
/// Get all path matches for a given list of matchers
|
||||
/// </summary>
|
||||
/// <param name="stack">File path to check for matches</param>
|
||||
/// <param name="matchSets">List of PathMatchSets to be run on the file</param>
|
||||
/// <param name="any">True if any path match is a success, false if all have to match</param>
|
||||
/// <returns>List of strings representing the matches, null or empty otherwise</returns>
|
||||
public static List<string> GetAllMatches(string stack, List<PathMatchSet> matchSets, bool any = false)
|
||||
=> FindAllMatches([stack], matchSets, any, false);
|
||||
|
||||
/// <summary>
|
||||
/// Get all path matches for a given list of matchers
|
||||
/// </summary>
|
||||
/// <param name="files">File paths to check for matches</param>
|
||||
/// <param name="matchSets">List of PathMatchSets to be run on the file</param>
|
||||
/// <param name="any">True if any path match is a success, false if all have to match</param>
|
||||
/// <returns>List of strings representing the matches, null or empty otherwise</returns>
|
||||
public static List<string> GetAllMatches(List<string>? stack, List<PathMatchSet> matchSets, bool any = false)
|
||||
=> FindAllMatches(stack, matchSets, any, false);
|
||||
|
||||
/// <summary>
|
||||
/// Get first path match for a given list of matchers
|
||||
/// </summary>
|
||||
/// <param name="stack">File path to check for matches</param>
|
||||
/// <param name="matchSets">List of PathMatchSets to be run on the file</param>
|
||||
/// <param name="any">True if any path match is a success, false if all have to match</param>
|
||||
/// <returns>String representing the match, null otherwise</returns>
|
||||
public static string? GetFirstMatch(string stack, List<PathMatchSet> matchSets, bool any = false)
|
||||
{
|
||||
var contentMatches = FindAllMatches([stack], matchSets, any, true);
|
||||
if (contentMatches == null || contentMatches.Count == 0)
|
||||
return null;
|
||||
|
||||
return contentMatches[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get first path match for a given list of matchers
|
||||
/// </summary>
|
||||
/// <param name="stack">File paths to check for matches</param>
|
||||
/// <param name="matchSets">List of PathMatchSets to be run on the file</param>
|
||||
/// <param name="any">True if any path match is a success, false if all have to match</param>
|
||||
/// <returns>String representing the match, null otherwise</returns>
|
||||
public static string? GetFirstMatch(List<string> stack, List<PathMatchSet> matchSets, bool any = false)
|
||||
{
|
||||
var contentMatches = FindAllMatches(stack, matchSets, any, true);
|
||||
if (contentMatches == null || contentMatches.Count == 0)
|
||||
return null;
|
||||
|
||||
return contentMatches[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the required set of path matches on a per Matcher basis
|
||||
/// </summary>
|
||||
/// <param name="stack">File paths to check for matches</param>
|
||||
/// <param name="matchSets">List of PathMatchSets to be run on the file</param>
|
||||
/// <param name="any">True if any path match is a success, false if all have to match</param>
|
||||
/// <param name="stopAfterFirst">True to stop after the first match, false otherwise</param>
|
||||
/// <returns>List of strings representing the matches, null or empty otherwise</returns>
|
||||
private static List<string> FindAllMatches(List<string>? stack, List<PathMatchSet> matchSets, bool any, bool stopAfterFirst)
|
||||
{
|
||||
// If either set is null or empty
|
||||
if (stack == null || stack.Count == 0 || matchSets.Count == 0)
|
||||
return [];
|
||||
|
||||
// Initialize the list of matches
|
||||
var matchesList = new List<string>();
|
||||
|
||||
// Loop through and try everything otherwise
|
||||
foreach (var matcher in matchSets)
|
||||
{
|
||||
// Determine if the matcher passes
|
||||
List<string> matches = [];
|
||||
if (any)
|
||||
{
|
||||
string? anyMatch = matcher.MatchesAny(stack);
|
||||
if (anyMatch != null)
|
||||
matches = [anyMatch];
|
||||
}
|
||||
else
|
||||
{
|
||||
matches = matcher.MatchesAll(stack);
|
||||
}
|
||||
|
||||
// If we don't have a pass, just continue
|
||||
if (matches.Count == 0)
|
||||
continue;
|
||||
|
||||
// Build the output string
|
||||
var matchString = new StringBuilder();
|
||||
matchString.Append(matcher.SetName);
|
||||
|
||||
// Invoke the version delegate, if it exists
|
||||
if (matcher.GetVersion != null)
|
||||
{
|
||||
// A null version returned means the check didn't pass at the version step
|
||||
var version = matcher.GetVersion(matches[0], stack);
|
||||
if (version == null)
|
||||
continue;
|
||||
|
||||
// Trim and add the version
|
||||
version = version.Trim();
|
||||
if (version.Length > 0)
|
||||
matchString.Append($" {version}");
|
||||
}
|
||||
|
||||
// Append the match to the list
|
||||
matchesList.Add(matchString.ToString());
|
||||
|
||||
// If we're stopping after the first match, bail out here
|
||||
if (stopAfterFirst)
|
||||
return matchesList;
|
||||
}
|
||||
|
||||
return matchesList;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
333
SabreTools.IO/Matching/ContentMatch.cs
Normal file
333
SabreTools.IO/Matching/ContentMatch.cs
Normal file
@@ -0,0 +1,333 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SabreTools.IO.Interfaces;
|
||||
|
||||
namespace SabreTools.IO.Matching
|
||||
{
|
||||
/// <summary>
|
||||
/// Content matching criteria
|
||||
/// </summary>
|
||||
public class ContentMatch : IMatch<byte?[]>
|
||||
{
|
||||
/// <summary>
|
||||
/// Content to match
|
||||
/// </summary>
|
||||
public byte?[] Needle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Starting index for matching
|
||||
/// </summary>
|
||||
private readonly int _start;
|
||||
|
||||
/// <summary>
|
||||
/// Ending index for matching
|
||||
/// </summary>
|
||||
private readonly int _end;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="needle">Byte array representing the search</param>
|
||||
/// <param name="start">Optional starting position in the stack, defaults to 0</param>
|
||||
/// <param name="end">Optional ending position in the stack, defaults to -1 (length of stack)</param>
|
||||
public ContentMatch(byte[] needle, int start = 0, int end = -1)
|
||||
{
|
||||
// Validate the inputs
|
||||
if (needle.Length == 0)
|
||||
throw new InvalidDataException(nameof(needle));
|
||||
if (start < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(start));
|
||||
if (end < -1)
|
||||
throw new ArgumentOutOfRangeException(nameof(end));
|
||||
|
||||
Needle = Array.ConvertAll(needle, b => (byte?)b);
|
||||
_start = start;
|
||||
_end = end;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="needle">Nullable byte array representing the search</param>
|
||||
/// <param name="start">Optional starting position in the stack, defaults to 0</param>
|
||||
/// <param name="end">Optional ending position in the stack, defaults to -1 (length of stack)</param>
|
||||
public ContentMatch(byte?[] needle, int start = 0, int end = -1)
|
||||
{
|
||||
// Validate the inputs
|
||||
if (needle.Length == 0)
|
||||
throw new InvalidDataException(nameof(needle));
|
||||
if (start < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(start));
|
||||
if (end < -1)
|
||||
throw new ArgumentOutOfRangeException(nameof(end));
|
||||
|
||||
Needle = needle;
|
||||
_start = start;
|
||||
_end = end;
|
||||
}
|
||||
|
||||
#region Conversion
|
||||
|
||||
/// <summary>
|
||||
/// Allow conversion from byte array to ContentMatch
|
||||
/// </summary>
|
||||
public static implicit operator ContentMatch(byte[] needle) => new ContentMatch(needle);
|
||||
|
||||
/// <summary>
|
||||
/// Allow conversion from nullable byte array to ContentMatch
|
||||
/// </summary>
|
||||
public static implicit operator ContentMatch(byte?[] needle) => new ContentMatch(needle);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Array Matching
|
||||
|
||||
/// <summary>
|
||||
/// Get if this match can be found in a stack
|
||||
/// </summary>
|
||||
/// <param name="stack">Array to search for the given content</param>
|
||||
/// <param name="reverse">True to search from the end of the array, false from the start</param>
|
||||
/// <returns>Found position on success, -1 otherwise</returns>
|
||||
public int Match(byte[]? stack, bool reverse = false)
|
||||
{
|
||||
// If either set is null or empty
|
||||
if (stack == null || stack.Length == 0 || Needle.Length == 0)
|
||||
return -1;
|
||||
|
||||
// Get the adjusted end value for comparison
|
||||
int end = _end < 0 ? stack.Length : _end;
|
||||
end = end > stack.Length ? stack.Length : end;
|
||||
|
||||
// If the stack window is invalid
|
||||
if (end < _start)
|
||||
return -1;
|
||||
|
||||
// If the needle is larger than the stack window, it can't be contained within
|
||||
if (Needle.Length > stack.Length - _start)
|
||||
return -1;
|
||||
|
||||
// If the needle and stack window are identically sized, short-circuit
|
||||
if (Needle.Length == stack.Length - _start)
|
||||
return EqualAt(stack, _start) ? _start : -1;
|
||||
|
||||
// Return based on the direction of search
|
||||
return reverse ? MatchReverse(stack) : MatchForward(stack);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Match within a stack starting from the smallest index
|
||||
/// </summary>
|
||||
/// <param name="stack">Array to search for the given content</param>
|
||||
/// <returns>Found position on success, -1 otherwise</returns>
|
||||
private int MatchForward(byte[] stack)
|
||||
{
|
||||
// Set the default start and end values
|
||||
int start = _start < 0 ? 0 : _start;
|
||||
int end = _end < 0 ? stack.Length - Needle.Length : _end;
|
||||
|
||||
// Loop starting from the smallest index
|
||||
for (int i = start; i < end; i++)
|
||||
{
|
||||
// If we somehow have an invalid end and we haven't matched, return
|
||||
if (i > stack.Length)
|
||||
return -1;
|
||||
|
||||
// Check to see if the values are equal
|
||||
if (EqualAt(stack, i))
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Match within a stack starting from the largest index
|
||||
/// </summary>
|
||||
/// <param name="stack">Array to search for the given content</param>
|
||||
/// <returns>Found position on success, -1 otherwise</returns>
|
||||
private int MatchReverse(byte[] stack)
|
||||
{
|
||||
// Set the default start and end values
|
||||
int start = _start < 0 ? 0 : _start;
|
||||
int end = _end < 0 ? stack.Length - Needle.Length : _end;
|
||||
|
||||
// Loop starting from the largest index
|
||||
for (int i = end; i > start; i--)
|
||||
{
|
||||
// If we somehow have an invalid end and we haven't matched, return
|
||||
if (i > stack.Length)
|
||||
return -1;
|
||||
|
||||
// Check to see if the values are equal
|
||||
if (EqualAt(stack, i))
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get if a stack at a certain index is equal to a needle
|
||||
/// </summary>
|
||||
/// <param name="stack">Array to search for the given content</param>
|
||||
/// <param name="index">Starting index to check equality</param>
|
||||
/// <returns>True if the needle matches the stack at a given index</returns>
|
||||
private bool EqualAt(byte[] stack, int index)
|
||||
{
|
||||
// If the index is invalid, we can't do anything
|
||||
if (index < 0)
|
||||
return false;
|
||||
|
||||
// If we're too close to the end of the stack, return false
|
||||
if (Needle.Length > stack.Length - index)
|
||||
return false;
|
||||
|
||||
// Loop through and check the value
|
||||
for (int i = 0; i < Needle.Length; i++)
|
||||
{
|
||||
// A null value is a wildcard
|
||||
if (Needle[i] == null)
|
||||
continue;
|
||||
else if (stack[i + index] != Needle[i])
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Stream Matching
|
||||
|
||||
/// <summary>
|
||||
/// Get if this match can be found in a stack
|
||||
/// </summary>
|
||||
/// <param name="stack">Stream to search for the given content</param>
|
||||
/// <param name="reverse">True to search from the end of the array, false from the start</param>
|
||||
/// <returns>Found position on success, -1 otherwise</returns>
|
||||
public int Match(Stream? stack, bool reverse = false)
|
||||
{
|
||||
// If either set is null or empty
|
||||
if (stack == null || stack.Length == 0 || Needle.Length == 0)
|
||||
return -1;
|
||||
|
||||
// Get the adjusted end value for comparison
|
||||
int end = _end < 0 ? (int)stack.Length : _end;
|
||||
end = end > (int)stack.Length ? (int)stack.Length : end;
|
||||
|
||||
// If the stack window is invalid
|
||||
if (end < _start)
|
||||
return -1;
|
||||
|
||||
// If the needle is larger than the stack window, it can't be contained within
|
||||
if (Needle.Length > stack.Length - _start)
|
||||
return -1;
|
||||
|
||||
// If the needle and stack window are identically sized, short-circuit
|
||||
if (Needle.Length == stack.Length - _start)
|
||||
return EqualAt(stack, _start) ? _start : -1;
|
||||
|
||||
// Return based on the direction of search
|
||||
return reverse ? MatchReverse(stack) : MatchForward(stack);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Match within a stack starting from the smallest index
|
||||
/// </summary>
|
||||
/// <param name="stack">Stream to search for the given content</param>
|
||||
/// <returns>Found position on success, -1 otherwise</returns>
|
||||
private int MatchForward(Stream stack)
|
||||
{
|
||||
// Set the default start and end values
|
||||
int start = _start < 0 ? 0 : _start;
|
||||
int end = _end < 0 ? (int)stack.Length - Needle.Length : _end;
|
||||
|
||||
// Loop starting from the smallest index
|
||||
for (int i = start; i < end; i++)
|
||||
{
|
||||
// If we somehow have an invalid end and we haven't matched, return
|
||||
if (i > stack.Length)
|
||||
return -1;
|
||||
|
||||
// Check to see if the values are equal
|
||||
if (EqualAt(stack, i))
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Match within a stack starting from the largest index
|
||||
/// </summary>
|
||||
/// <param name="stack">Stream to search for the given content</param>
|
||||
/// <returns>Found position on success, -1 otherwise</returns>
|
||||
private int MatchReverse(Stream stack)
|
||||
{
|
||||
// Set the default start and end values
|
||||
int start = _start < 0 ? 0 : _start;
|
||||
int end = _end < 0 ? (int)stack.Length - Needle.Length : _end;
|
||||
|
||||
// Loop starting from the largest index
|
||||
for (int i = end; i > start; i--)
|
||||
{
|
||||
// If we somehow have an invalid end and we haven't matched, return
|
||||
if (i > stack.Length)
|
||||
return -1;
|
||||
|
||||
// Check to see if the values are equal
|
||||
if (EqualAt(stack, i))
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get if a stack at a certain index is equal to a needle
|
||||
/// </summary>
|
||||
/// <param name="stack">Stream to search for the given content</param>
|
||||
/// <param name="index">Starting index to check equality</param>
|
||||
/// <returns>True if the needle matches the stack at a given index</returns>
|
||||
private bool EqualAt(Stream stack, int index)
|
||||
{
|
||||
// If the index is invalid, we can't do anything
|
||||
if (index < 0)
|
||||
return false;
|
||||
|
||||
// If we're too close to the end of the stack, return false
|
||||
if (Needle.Length > stack.Length - index)
|
||||
return false;
|
||||
|
||||
// Save the current position and move to the index
|
||||
long currentPosition = stack.Position;
|
||||
stack.Seek(index, SeekOrigin.Begin);
|
||||
|
||||
// Set the return value
|
||||
bool matched = true;
|
||||
|
||||
// Loop through and check the value
|
||||
for (int i = 0; i < Needle.Length; i++)
|
||||
{
|
||||
byte stackValue = (byte)stack.ReadByte();
|
||||
|
||||
// A null value is a wildcard
|
||||
if (Needle[i] == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (stackValue != Needle[i])
|
||||
{
|
||||
matched = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the position and return the value
|
||||
stack.Seek(currentPosition, SeekOrigin.Begin);
|
||||
return matched;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
239
SabreTools.IO/Matching/ContentMatchSet.cs
Normal file
239
SabreTools.IO/Matching/ContentMatchSet.cs
Normal file
@@ -0,0 +1,239 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SabreTools.IO.Interfaces;
|
||||
|
||||
namespace SabreTools.IO.Matching
|
||||
{
|
||||
/// <summary>
|
||||
/// A set of content matches that work together
|
||||
/// </summary>
|
||||
public class ContentMatchSet : IMatchSet<ContentMatch, byte?[]>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public List<ContentMatch> Matchers { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string SetName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Function to get a content version
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A content version method takes the file path, the file contents,
|
||||
/// and a list of found positions and returns a single string. That
|
||||
/// string is either a version string, in which case it will be appended
|
||||
/// to the match name, or `null`, in which case it will cause
|
||||
/// the match name to be omitted.
|
||||
/// </remarks>
|
||||
public GetArrayVersion? GetArrayVersion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Function to get a content version
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A content version method takes the file path, the file contents,
|
||||
/// and a list of found positions and returns a single string. That
|
||||
/// string is either a version string, in which case it will be appended
|
||||
/// to the match name, or `null`, in which case it will cause
|
||||
/// the match name to be omitted.
|
||||
/// </remarks>
|
||||
public GetStreamVersion? GetStreamVersion { get; }
|
||||
|
||||
#region Generic Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="needle">ContentMatch representing the comparisons</param>
|
||||
/// <param name="setName">Unique name for the set</param>
|
||||
public ContentMatchSet(ContentMatch needle, string setName)
|
||||
: this([needle], setName) { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="needles">List of ContentMatch objects representing the comparisons</param>
|
||||
/// <param name="setName">Unique name for the set</param>
|
||||
public ContentMatchSet(List<ContentMatch> needles, string setName)
|
||||
{
|
||||
// Validate the inputs
|
||||
if (needles.Count == 0)
|
||||
throw new InvalidDataException(nameof(needles));
|
||||
|
||||
Matchers = needles;
|
||||
SetName = setName;
|
||||
GetArrayVersion = null;
|
||||
GetStreamVersion = null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Array Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="needle">ContentMatch representing the comparisons</param>
|
||||
/// <param name="getVersion">Delegate for deriving a version on match of an array</param>
|
||||
/// <param name="setName">Unique name for the set</param>
|
||||
public ContentMatchSet(ContentMatch needle, GetArrayVersion getVersion, string setName)
|
||||
: this([needle], getVersion, setName) { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="needles">List of ContentMatch objects representing the comparisons</param>
|
||||
/// <param name="getVersion">Delegate for deriving a version on match of an array</param>
|
||||
/// <param name="setName">Unique name for the set</param>
|
||||
public ContentMatchSet(List<ContentMatch> needles, GetArrayVersion getVersion, string setName)
|
||||
{
|
||||
// Validate the inputs
|
||||
if (needles.Count == 0)
|
||||
throw new InvalidDataException(nameof(needles));
|
||||
|
||||
Matchers = needles;
|
||||
SetName = setName;
|
||||
GetArrayVersion = getVersion;
|
||||
GetStreamVersion = null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Stream Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="needle">ContentMatch representing the comparisons</param>
|
||||
/// <param name="getVersion">Delegate for deriving a version on match of a Stream</param>
|
||||
/// <param name="setName">Unique name for the set</param>
|
||||
public ContentMatchSet(ContentMatch needle, GetStreamVersion getVersion, string setName)
|
||||
: this([needle], getVersion, setName) { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="needles">List of ContentMatch objects representing the comparisons</param>
|
||||
/// <param name="getVersion">Delegate for deriving a version on match of a Stream</param>
|
||||
/// <param name="setName">Unique name for the set</param>
|
||||
public ContentMatchSet(List<ContentMatch> needles, GetStreamVersion getVersion, string setName)
|
||||
{
|
||||
// Validate the inputs
|
||||
if (needles.Count == 0)
|
||||
throw new InvalidDataException(nameof(needles));
|
||||
|
||||
Matchers = needles;
|
||||
SetName = setName;
|
||||
GetArrayVersion = null;
|
||||
GetStreamVersion = getVersion;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Array Matching
|
||||
|
||||
/// <summary>
|
||||
/// Determine whether all content matches pass
|
||||
/// </summary>
|
||||
/// <param name="stack">Array to search</param>
|
||||
/// <returns>List of matching positions, if any</returns>
|
||||
public List<int> MatchesAll(byte[]? stack)
|
||||
{
|
||||
// If either set is null or empty
|
||||
if (stack == null || stack.Length == 0 || Matchers.Count == 0)
|
||||
return [];
|
||||
|
||||
// Initialize the position list
|
||||
var positions = new List<int>();
|
||||
|
||||
// Loop through all content matches and make sure all pass
|
||||
foreach (var contentMatch in Matchers)
|
||||
{
|
||||
int position = contentMatch.Match(stack);
|
||||
if (position < 0)
|
||||
return [];
|
||||
|
||||
positions.Add(position);
|
||||
}
|
||||
|
||||
return positions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine whether any content matches pass
|
||||
/// </summary>
|
||||
/// <param name="stack">Array to search</param>
|
||||
/// <returns>First matching position on success, -1 on error</returns>
|
||||
public int MatchesAny(byte[]? stack)
|
||||
{
|
||||
// If either set is null or empty
|
||||
if (stack == null || stack.Length == 0 || Matchers.Count == 0)
|
||||
return -1;
|
||||
|
||||
// Loop through all content matches and make sure all pass
|
||||
foreach (var contentMatch in Matchers)
|
||||
{
|
||||
int position = contentMatch.Match(stack);
|
||||
if (position >= 0)
|
||||
return position;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Stream Matching
|
||||
|
||||
/// <summary>
|
||||
/// Determine whether all content matches pass
|
||||
/// </summary>
|
||||
/// <param name="stack">Stream to search</param>
|
||||
/// <returns>List of matching positions, if any</returns>
|
||||
public List<int> MatchesAll(Stream? stack)
|
||||
{
|
||||
// If either set is null or empty
|
||||
if (stack == null || stack.Length == 0 || Matchers.Count == 0)
|
||||
return [];
|
||||
|
||||
// Initialize the position list
|
||||
var positions = new List<int>();
|
||||
|
||||
// Loop through all content matches and make sure all pass
|
||||
foreach (var contentMatch in Matchers)
|
||||
{
|
||||
int position = contentMatch.Match(stack);
|
||||
if (position < 0)
|
||||
return [];
|
||||
|
||||
positions.Add(position);
|
||||
}
|
||||
|
||||
return positions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine whether any content matches pass
|
||||
/// </summary>
|
||||
/// <param name="stack">Stream to search</param>
|
||||
/// <returns>First matching position on success, -1 on error</returns>
|
||||
public int MatchesAny(Stream? stack)
|
||||
{
|
||||
// If either set is null or empty
|
||||
if (stack == null || stack.Length == 0 || Matchers.Count == 0)
|
||||
return -1;
|
||||
|
||||
// Loop through all content matches and make sure all pass
|
||||
foreach (var contentMatch in Matchers)
|
||||
{
|
||||
int position = contentMatch.Match(stack);
|
||||
if (position >= 0)
|
||||
return position;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
31
SabreTools.IO/Matching/Delegates.cs
Normal file
31
SabreTools.IO/Matching/Delegates.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace SabreTools.IO.Matching
|
||||
{
|
||||
/// <summary>
|
||||
/// Get a version number from a file
|
||||
/// </summary>
|
||||
/// <param name="path">File path to get the version from</param>
|
||||
/// <param name="content">Optional file contents as a byte array</param>
|
||||
/// <param name="positions">List of positions in the array that were matched</param>
|
||||
/// <returns>Version string on success, null on failure</returns>
|
||||
public delegate string? GetArrayVersion(string path, byte[]? content, List<int> positions);
|
||||
|
||||
/// <summary>
|
||||
/// Get a version number from an input path
|
||||
/// </summary>
|
||||
/// <param name="path">File or directory path to get the version from</param>
|
||||
/// <param name="files">Optional set of files in the directory</param>
|
||||
/// <returns>Version string on success, null on failure</returns>
|
||||
public delegate string? GetPathVersion(string path, List<string>? files);
|
||||
|
||||
/// <summary>
|
||||
/// Get a version number from a file
|
||||
/// </summary>
|
||||
/// <param name="path">File path to get the version from</param>
|
||||
/// <param name="content">Optional file contents as a Stream</param>
|
||||
/// <param name="positions">List of positions in the Stream that were matched</param>
|
||||
/// <returns>Version string on success, null on failure</returns>
|
||||
public delegate string? GetStreamVersion(string path, Stream? content, List<int> positions);
|
||||
}
|
||||
18
SabreTools.IO/Matching/FilePathMatch.cs
Normal file
18
SabreTools.IO/Matching/FilePathMatch.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.IO;
|
||||
|
||||
namespace SabreTools.IO.Matching
|
||||
{
|
||||
/// <summary>
|
||||
/// File path matching criteria
|
||||
/// </summary>
|
||||
public class FilePathMatch : PathMatch
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="needle">String representing the search</param>
|
||||
/// <param name="matchCase">True to match exact casing, false otherwise</param>
|
||||
public FilePathMatch(string needle, bool matchCase = false)
|
||||
: base($"{Path.DirectorySeparatorChar}{needle}", matchCase, true) { }
|
||||
}
|
||||
}
|
||||
93
SabreTools.IO/Matching/PathMatch.cs
Normal file
93
SabreTools.IO/Matching/PathMatch.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SabreTools.IO.Interfaces;
|
||||
|
||||
namespace SabreTools.IO.Matching
|
||||
{
|
||||
/// <summary>
|
||||
/// Path matching criteria
|
||||
/// </summary>
|
||||
public class PathMatch : IMatch<string>
|
||||
{
|
||||
/// <summary>
|
||||
/// String to match
|
||||
/// </summary>
|
||||
public string Needle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Match casing instead of invariant
|
||||
/// </summary>
|
||||
private readonly bool _matchCase;
|
||||
|
||||
/// <summary>
|
||||
/// Match that values end with the needle and not just contains
|
||||
/// </summary>
|
||||
private readonly bool _useEndsWith;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="needle">String representing the search</param>
|
||||
/// <param name="matchCase">True to match exact casing, false otherwise</param>
|
||||
/// <param name="useEndsWith">True to match the end only, false for contains</param>
|
||||
public PathMatch(string needle, bool matchCase = false, bool useEndsWith = false)
|
||||
{
|
||||
// Validate the inputs
|
||||
if (needle.Length == 0)
|
||||
throw new InvalidDataException(nameof(needle));
|
||||
|
||||
Needle = needle;
|
||||
_matchCase = matchCase;
|
||||
_useEndsWith = useEndsWith;
|
||||
}
|
||||
|
||||
#region Conversion
|
||||
|
||||
/// <summary>
|
||||
/// Allow conversion from string to PathMatch
|
||||
/// </summary>
|
||||
public static implicit operator PathMatch(string needle) => new PathMatch(needle);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Matching
|
||||
|
||||
/// <summary>
|
||||
/// Get if this match can be found in a stack
|
||||
/// </summary>
|
||||
/// <param name="stack">Array of strings to search for the given content</param>
|
||||
/// <returns>Matched item on success, null on error</returns>
|
||||
public string? Match(string[]? stack)
|
||||
=> Match(stack == null ? null : new List<string>(stack));
|
||||
|
||||
/// <summary>
|
||||
/// Get if this match can be found in a stack
|
||||
/// </summary>
|
||||
/// <param name="stack">List of strings to search for the given content</param>
|
||||
/// <returns>Matched item on success, null on error</returns>
|
||||
public string? Match(List<string>? stack)
|
||||
{
|
||||
// If either set is null or empty
|
||||
if (stack == null || stack.Count == 0 || Needle.Length == 0)
|
||||
return null;
|
||||
|
||||
// Preprocess the needle, if necessary
|
||||
string procNeedle = _matchCase ? Needle : Needle.ToLowerInvariant();
|
||||
|
||||
foreach (string stackItem in stack)
|
||||
{
|
||||
// Preprocess the stack item, if necessary
|
||||
string procStackItem = _matchCase ? stackItem : stackItem.ToLowerInvariant();
|
||||
|
||||
if (_useEndsWith && procStackItem.EndsWith(procNeedle))
|
||||
return stackItem;
|
||||
else if (!_useEndsWith && procStackItem.Contains(procNeedle))
|
||||
return stackItem;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
156
SabreTools.IO/Matching/PathMatchSet.cs
Normal file
156
SabreTools.IO/Matching/PathMatchSet.cs
Normal file
@@ -0,0 +1,156 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SabreTools.IO.Interfaces;
|
||||
|
||||
namespace SabreTools.IO.Matching
|
||||
{
|
||||
/// <summary>
|
||||
/// A set of path matches that work together
|
||||
/// </summary>
|
||||
public class PathMatchSet : IMatchSet<PathMatch, string>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public List<PathMatch> Matchers { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string SetName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Function to get a path version for this Matcher
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A path version method takes the matched path and an enumerable of files
|
||||
/// and returns a single string. That string is either a version string,
|
||||
/// in which case it will be appended to the match name, or `null`,
|
||||
/// in which case it will cause the match name to be omitted.
|
||||
/// </remarks>
|
||||
public GetPathVersion? GetVersion { get; }
|
||||
|
||||
#region Generic Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="needle">PathMatch representing the comparisons</param>
|
||||
/// <param name="setName">Unique name for the set</param>
|
||||
public PathMatchSet(PathMatch needle, string setName)
|
||||
: this([needle], setName) { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="needles">List of PathMatch objects representing the comparisons</param>
|
||||
/// <param name="setName">Unique name for the set</param>
|
||||
public PathMatchSet(List<PathMatch> needles, string setName)
|
||||
{
|
||||
// Validate the inputs
|
||||
if (needles.Count == 0)
|
||||
throw new InvalidDataException(nameof(needles));
|
||||
|
||||
Matchers = needles;
|
||||
SetName = setName;
|
||||
GetVersion = null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Version Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="needle">PathMatch representing the comparisons</param>
|
||||
/// <param name="getVersion">Delegate for deriving a version on match</param>
|
||||
/// <param name="setName">Unique name for the set</param>
|
||||
public PathMatchSet(PathMatch needle, GetPathVersion getVersion, string setName)
|
||||
: this([needle], getVersion, setName) { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="needles">List of PathMatch objects representing the comparisons</param>
|
||||
/// <param name="getVersion">Delegate for deriving a version on match</param>
|
||||
/// <param name="setName">Unique name for the set</param>
|
||||
public PathMatchSet(List<PathMatch> needles, GetPathVersion getVersion, string setName)
|
||||
{
|
||||
// Validate the inputs
|
||||
if (needles.Count == 0)
|
||||
throw new InvalidDataException(nameof(needles));
|
||||
|
||||
Matchers = needles;
|
||||
SetName = setName;
|
||||
GetVersion = getVersion;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Matching
|
||||
|
||||
/// <summary>
|
||||
/// Get if this match can be found in a stack
|
||||
/// </summary>
|
||||
/// <param name="stack">List of strings to search for the given content</param>
|
||||
/// <returns>Matched item on success, null on error</returns>
|
||||
public List<string> MatchesAll(string[]? stack)
|
||||
=> MatchesAll(stack == null ? null : new List<string>(stack));
|
||||
|
||||
/// <summary>
|
||||
/// Determine whether all path matches pass
|
||||
/// </summary>
|
||||
/// <param name="stack">List of strings to try to match</param>
|
||||
/// <returns>List of matching values, if any</returns>
|
||||
public List<string> MatchesAll(List<string>? stack)
|
||||
{
|
||||
// If either set is null or empty, we can't do anything
|
||||
if (stack == null || stack.Count == 0 || Matchers.Count == 0)
|
||||
return [];
|
||||
|
||||
// Initialize the value list
|
||||
List<string> values = [];
|
||||
|
||||
// Loop through all path matches and make sure all pass
|
||||
foreach (var pathMatch in Matchers)
|
||||
{
|
||||
string? value = pathMatch.Match(stack);
|
||||
if (value == null)
|
||||
return [];
|
||||
else
|
||||
values.Add(value);
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get if this match can be found in a stack
|
||||
/// </summary>
|
||||
/// <param name="stack">List of strings to search for the given content</param>
|
||||
/// <returns>Matched item on success, null on error</returns>
|
||||
public string? MatchesAny(string[]? stack)
|
||||
=> MatchesAny(stack == null ? null : new List<string>(stack));
|
||||
|
||||
/// <summary>
|
||||
/// Determine whether any path matches pass
|
||||
/// </summary>
|
||||
/// <param name="stack">List of strings to try to match</param>
|
||||
/// <returns>First matching value on success, null on error</returns>
|
||||
public string? MatchesAny(List<string>? stack)
|
||||
{
|
||||
// If either set is null or empty, we can't do anything
|
||||
if (stack == null || stack.Count == 0 || Matchers.Count == 0)
|
||||
return null;
|
||||
|
||||
// Loop through all path matches and make sure all pass
|
||||
foreach (var pathMatch in Matchers)
|
||||
{
|
||||
string? value = pathMatch.Match(stack);
|
||||
if (value != null)
|
||||
return value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
234
SabreTools.IO/Numerics/BothEndian.cs
Normal file
234
SabreTools.IO/Numerics/BothEndian.cs
Normal file
@@ -0,0 +1,234 @@
|
||||
using System;
|
||||
|
||||
namespace SabreTools.Numerics
|
||||
{
|
||||
/// <summary>
|
||||
/// Both-endian numeric value
|
||||
/// </summary>
|
||||
public abstract class BothEndian<T>(T le, T be) : IComparable, IConvertible, IEquatable<BothEndian<T>>, IEquatable<T>
|
||||
where T : notnull, IComparable, IConvertible, IEquatable<T>
|
||||
{
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Little-endian representation of the number
|
||||
/// </summary>
|
||||
/// <remarks>Value should match <see cref="BigEndian"/></remarks>
|
||||
public readonly T LittleEndian = le;
|
||||
|
||||
/// <summary>
|
||||
/// Big-endian representation of the number
|
||||
/// </summary>
|
||||
/// <remarks>Value should match <see cref="LittleEndian"/></remarks>
|
||||
public readonly T BigEndian = be;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the value is valid
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Validity of a both-endian value is determined based on if both
|
||||
/// endianness values match. These values should always match, based
|
||||
/// on all implementations.
|
||||
/// </remarks>
|
||||
public bool IsValid => LittleEndian.Equals(BigEndian);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Operators
|
||||
|
||||
/// <remarks>
|
||||
/// Returns either <see cref="LittleEndian"/> or <see cref="BigEndian"/>
|
||||
/// depending on the system endianness.
|
||||
/// </remarks>
|
||||
public static implicit operator T(BothEndian<T> val)
|
||||
=> BitConverter.IsLittleEndian ? val.LittleEndian : val.BigEndian;
|
||||
|
||||
public static bool operator ==(BothEndian<T> a, BothEndian<T> b) => a.Equals(b);
|
||||
|
||||
public static bool operator !=(BothEndian<T> a, BothEndian<T> b) => !a.Equals(b);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Object
|
||||
|
||||
#if NETCOREAPP
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj is BothEndian<T> be)
|
||||
return Equals(be);
|
||||
|
||||
if (obj is T t)
|
||||
return Equals(t);
|
||||
|
||||
return base.Equals(obj);
|
||||
}
|
||||
#else
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is BothEndian<T> be)
|
||||
return Equals(be);
|
||||
|
||||
if (obj is T t)
|
||||
return Equals(t);
|
||||
|
||||
return base.Equals(obj);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode() => ((T)this).GetHashCode();
|
||||
|
||||
#if NETCOREAPP
|
||||
/// <inheritdoc/>
|
||||
public override string? ToString() => ((T)this).ToString();
|
||||
#else
|
||||
/// <inheritdoc/>
|
||||
public override string ToString() => ((T)this).ToString();
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
#region IComparable
|
||||
|
||||
/// <inheritdoc/>
|
||||
#if NETCOREAPP
|
||||
public int CompareTo(object? obj) => ((T)this).CompareTo(obj);
|
||||
#else
|
||||
public int CompareTo(object obj) => ((T)this).CompareTo(obj);
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
#region IConvertible
|
||||
|
||||
/// <inheritdoc/>
|
||||
public TypeCode GetTypeCode() => ((T)this).GetTypeCode();
|
||||
|
||||
#if NETCOREAPP
|
||||
/// <inheritdoc/>
|
||||
public bool ToBoolean(IFormatProvider? provider) => ((T)this).ToBoolean(provider);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public char ToChar(IFormatProvider? provider) => ((T)this).ToChar(provider);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sbyte ToSByte(IFormatProvider? provider) => ((T)this).ToSByte(provider);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte ToByte(IFormatProvider? provider) => ((T)this).ToByte(provider);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public short ToInt16(IFormatProvider? provider) => ((T)this).ToInt16(provider);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ushort ToUInt16(IFormatProvider? provider) => ((T)this).ToUInt16(provider);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int ToInt32(IFormatProvider? provider) => ((T)this).ToInt32(provider);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public uint ToUInt32(IFormatProvider? provider) => ((T)this).ToUInt32(provider);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public long ToInt64(IFormatProvider? provider) => ((T)this).ToInt64(provider);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ulong ToUInt64(IFormatProvider? provider) => ((T)this).ToUInt64(provider);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public float ToSingle(IFormatProvider? provider) => ((T)this).ToSingle(provider);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public double ToDouble(IFormatProvider? provider) => ((T)this).ToDouble(provider);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public decimal ToDecimal(IFormatProvider? provider) => ((T)this).ToDecimal(provider);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DateTime ToDateTime(IFormatProvider? provider) => ((T)this).ToDateTime(provider);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string ToString(IFormatProvider? provider) => ((T)this).ToString(provider);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public object ToType(Type conversionType, IFormatProvider? provider) => ((T)this).ToType(conversionType, provider);
|
||||
#else
|
||||
/// <inheritdoc/>
|
||||
public bool ToBoolean(IFormatProvider provider) => ((T)this).ToBoolean(provider);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public char ToChar(IFormatProvider provider) => ((T)this).ToChar(provider);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sbyte ToSByte(IFormatProvider provider) => ((T)this).ToSByte(provider);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte ToByte(IFormatProvider provider) => ((T)this).ToByte(provider);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public short ToInt16(IFormatProvider provider) => ((T)this).ToInt16(provider);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ushort ToUInt16(IFormatProvider provider) => ((T)this).ToUInt16(provider);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int ToInt32(IFormatProvider provider) => ((T)this).ToInt32(provider);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public uint ToUInt32(IFormatProvider provider) => ((T)this).ToUInt32(provider);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public long ToInt64(IFormatProvider provider) => ((T)this).ToInt64(provider);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ulong ToUInt64(IFormatProvider provider) => ((T)this).ToUInt64(provider);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public float ToSingle(IFormatProvider provider) => ((T)this).ToSingle(provider);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public double ToDouble(IFormatProvider provider) => ((T)this).ToDouble(provider);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public decimal ToDecimal(IFormatProvider provider) => ((T)this).ToDecimal(provider);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DateTime ToDateTime(IFormatProvider provider) => ((T)this).ToDateTime(provider);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string ToString(IFormatProvider provider) => ((T)this).ToString(provider);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public object ToType(Type conversionType, IFormatProvider provider) => ((T)this).ToType(conversionType, provider);
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
#region IEquatable
|
||||
|
||||
#if NETCOREAPP
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(BothEndian<T>? other)
|
||||
{
|
||||
if (other is null)
|
||||
return false;
|
||||
|
||||
return LittleEndian.Equals(other.LittleEndian) && BigEndian.Equals(other.BigEndian);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(T? other) => ((T)this).Equals(other);
|
||||
#else
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(BothEndian<T> other)
|
||||
=> LittleEndian.Equals(other.LittleEndian) && BigEndian.Equals(other.BigEndian);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(T other) => ((T)this).Equals(other);
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
143
SabreTools.IO/Numerics/BothInt16.cs
Normal file
143
SabreTools.IO/Numerics/BothInt16.cs
Normal file
@@ -0,0 +1,143 @@
|
||||
namespace SabreTools.Numerics
|
||||
{
|
||||
/// <summary>
|
||||
/// Both-endian 16-bit signed value
|
||||
/// </summary>
|
||||
public sealed class BothInt16(short le, short be) : BothEndian<short>(le, be)
|
||||
{
|
||||
public static implicit operator BothInt16(short val)
|
||||
=> new(val, val);
|
||||
|
||||
#region Arithmetic Unary Operators
|
||||
|
||||
public static BothInt16 operator ++(BothInt16 a)
|
||||
{
|
||||
short le = (short)(a.LittleEndian + 1);
|
||||
short be = (short)(a.BigEndian + 1);
|
||||
return new BothInt16(le, be);
|
||||
}
|
||||
|
||||
public static BothInt16 operator --(BothInt16 a)
|
||||
{
|
||||
short le = (short)(a.LittleEndian - 1);
|
||||
short be = (short)(a.BigEndian - 1);
|
||||
return new BothInt16(le, be);
|
||||
}
|
||||
|
||||
public static BothInt16 operator +(BothInt16 a)
|
||||
{
|
||||
short le = (short)(+a.LittleEndian);
|
||||
short be = (short)(+a.BigEndian);
|
||||
return new BothInt16(le, be);
|
||||
}
|
||||
|
||||
public static BothInt16 operator -(BothInt16 a)
|
||||
{
|
||||
short le = (short)(-a.LittleEndian);
|
||||
short be = (short)(-a.BigEndian);
|
||||
return new BothInt16(le, be);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Arithmetic Binary Operators
|
||||
|
||||
public static BothInt16 operator *(BothInt16 a, BothInt16 b)
|
||||
{
|
||||
short le = (short)(a.LittleEndian * b.LittleEndian);
|
||||
short be = (short)(a.BigEndian * b.BigEndian);
|
||||
return new BothInt16(le, be);
|
||||
}
|
||||
|
||||
public static BothInt16 operator /(BothInt16 a, BothInt16 b)
|
||||
{
|
||||
short le = (short)(a.LittleEndian / b.LittleEndian);
|
||||
short be = (short)(a.BigEndian / b.BigEndian);
|
||||
return new BothInt16(le, be);
|
||||
}
|
||||
|
||||
public static BothInt16 operator %(BothInt16 a, BothInt16 b)
|
||||
{
|
||||
short le = (short)(a.LittleEndian % b.LittleEndian);
|
||||
short be = (short)(a.BigEndian % b.BigEndian);
|
||||
return new BothInt16(le, be);
|
||||
}
|
||||
|
||||
public static BothInt16 operator +(BothInt16 a, BothInt16 b)
|
||||
{
|
||||
short le = (short)(a.LittleEndian + b.LittleEndian);
|
||||
short be = (short)(a.BigEndian + b.BigEndian);
|
||||
return new BothInt16(le, be);
|
||||
}
|
||||
|
||||
public static BothInt16 operator -(BothInt16 a, BothInt16 b)
|
||||
{
|
||||
short le = (short)(a.LittleEndian - b.LittleEndian);
|
||||
short be = (short)(a.BigEndian - b.BigEndian);
|
||||
return new BothInt16(le, be);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Bitwise Unary Operators
|
||||
|
||||
public static BothInt16 operator ~(BothInt16 a)
|
||||
{
|
||||
short le = (short)(~a.LittleEndian);
|
||||
short be = (short)(~a.BigEndian);
|
||||
return new BothInt16(le, be);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Shift Binary Operators
|
||||
|
||||
public static BothInt16 operator <<(BothInt16 a, BothInt16 b)
|
||||
{
|
||||
short le = (short)(a.LittleEndian << b.LittleEndian);
|
||||
short be = (short)(a.BigEndian << b.BigEndian);
|
||||
return new BothInt16(le, be);
|
||||
}
|
||||
|
||||
public static BothInt16 operator >>(BothInt16 a, BothInt16 b)
|
||||
{
|
||||
short le = (short)(a.LittleEndian >> b.LittleEndian);
|
||||
short be = (short)(a.BigEndian >> b.BigEndian);
|
||||
return new BothInt16(le, be);
|
||||
}
|
||||
|
||||
public static BothInt16 operator >>>(BothInt16 a, BothInt16 b)
|
||||
{
|
||||
short le = (short)(a.LittleEndian >>> b.LittleEndian);
|
||||
short be = (short)(a.BigEndian >>> b.BigEndian);
|
||||
return new BothInt16(le, be);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Bitwise Binary Operators
|
||||
|
||||
public static BothInt16 operator &(BothInt16 a, BothInt16 b)
|
||||
{
|
||||
short le = (short)(a.LittleEndian & b.LittleEndian);
|
||||
short be = (short)(a.BigEndian & b.BigEndian);
|
||||
return new BothInt16(le, be);
|
||||
}
|
||||
|
||||
public static BothInt16 operator |(BothInt16 a, BothInt16 b)
|
||||
{
|
||||
short le = (short)(a.LittleEndian | b.LittleEndian);
|
||||
short be = (short)(a.BigEndian | b.BigEndian);
|
||||
return new BothInt16(le, be);
|
||||
}
|
||||
|
||||
public static BothInt16 operator ^(BothInt16 a, BothInt16 b)
|
||||
{
|
||||
short le = (short)(a.LittleEndian ^ b.LittleEndian);
|
||||
short be = (short)(a.BigEndian ^ b.BigEndian);
|
||||
return new BothInt16(le, be);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
143
SabreTools.IO/Numerics/BothInt32.cs
Normal file
143
SabreTools.IO/Numerics/BothInt32.cs
Normal file
@@ -0,0 +1,143 @@
|
||||
namespace SabreTools.Numerics
|
||||
{
|
||||
/// <summary>
|
||||
/// Both-endian 32-bit signed value
|
||||
/// </summary>
|
||||
public sealed class BothInt32(int le, int be) : BothEndian<int>(le, be)
|
||||
{
|
||||
public static implicit operator BothInt32(int val)
|
||||
=> new(val, val);
|
||||
|
||||
#region Arithmetic Unary Operators
|
||||
|
||||
public static BothInt32 operator ++(BothInt32 a)
|
||||
{
|
||||
int le = (int)(a.LittleEndian + 1);
|
||||
int be = (int)(a.BigEndian + 1);
|
||||
return new BothInt32(le, be);
|
||||
}
|
||||
|
||||
public static BothInt32 operator --(BothInt32 a)
|
||||
{
|
||||
int le = (int)(a.LittleEndian - 1);
|
||||
int be = (int)(a.BigEndian - 1);
|
||||
return new BothInt32(le, be);
|
||||
}
|
||||
|
||||
public static BothInt32 operator +(BothInt32 a)
|
||||
{
|
||||
int le = (int)(+a.LittleEndian);
|
||||
int be = (int)(+a.BigEndian);
|
||||
return new BothInt32(le, be);
|
||||
}
|
||||
|
||||
public static BothInt32 operator -(BothInt32 a)
|
||||
{
|
||||
int le = (int)(-a.LittleEndian);
|
||||
int be = (int)(-a.BigEndian);
|
||||
return new BothInt32(le, be);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Arithmetic Binary Operators
|
||||
|
||||
public static BothInt32 operator *(BothInt32 a, BothInt32 b)
|
||||
{
|
||||
int le = (int)(a.LittleEndian * b.LittleEndian);
|
||||
int be = (int)(a.BigEndian * b.BigEndian);
|
||||
return new BothInt32(le, be);
|
||||
}
|
||||
|
||||
public static BothInt32 operator /(BothInt32 a, BothInt32 b)
|
||||
{
|
||||
int le = (int)(a.LittleEndian / b.LittleEndian);
|
||||
int be = (int)(a.BigEndian / b.BigEndian);
|
||||
return new BothInt32(le, be);
|
||||
}
|
||||
|
||||
public static BothInt32 operator %(BothInt32 a, BothInt32 b)
|
||||
{
|
||||
int le = (int)(a.LittleEndian % b.LittleEndian);
|
||||
int be = (int)(a.BigEndian % b.BigEndian);
|
||||
return new BothInt32(le, be);
|
||||
}
|
||||
|
||||
public static BothInt32 operator +(BothInt32 a, BothInt32 b)
|
||||
{
|
||||
int le = (int)(a.LittleEndian + b.LittleEndian);
|
||||
int be = (int)(a.BigEndian + b.BigEndian);
|
||||
return new BothInt32(le, be);
|
||||
}
|
||||
|
||||
public static BothInt32 operator -(BothInt32 a, BothInt32 b)
|
||||
{
|
||||
int le = (int)(a.LittleEndian - b.LittleEndian);
|
||||
int be = (int)(a.BigEndian - b.BigEndian);
|
||||
return new BothInt32(le, be);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Bitwise Unary Operators
|
||||
|
||||
public static BothInt32 operator ~(BothInt32 a)
|
||||
{
|
||||
int le = (int)(~a.LittleEndian);
|
||||
int be = (int)(~a.BigEndian);
|
||||
return new BothInt32(le, be);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Shift Binary Operators
|
||||
|
||||
public static BothInt32 operator <<(BothInt32 a, BothInt32 b)
|
||||
{
|
||||
int le = (int)(a.LittleEndian << b.LittleEndian);
|
||||
int be = (int)(a.BigEndian << b.BigEndian);
|
||||
return new BothInt32(le, be);
|
||||
}
|
||||
|
||||
public static BothInt32 operator >>(BothInt32 a, BothInt32 b)
|
||||
{
|
||||
int le = (int)(a.LittleEndian >> b.LittleEndian);
|
||||
int be = (int)(a.BigEndian >> b.BigEndian);
|
||||
return new BothInt32(le, be);
|
||||
}
|
||||
|
||||
public static BothInt32 operator >>>(BothInt32 a, BothInt32 b)
|
||||
{
|
||||
int le = (int)(a.LittleEndian >>> b.LittleEndian);
|
||||
int be = (int)(a.BigEndian >>> b.BigEndian);
|
||||
return new BothInt32(le, be);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Bitwise Binary Operators
|
||||
|
||||
public static BothInt32 operator &(BothInt32 a, BothInt32 b)
|
||||
{
|
||||
int le = (int)(a.LittleEndian & b.LittleEndian);
|
||||
int be = (int)(a.BigEndian & b.BigEndian);
|
||||
return new BothInt32(le, be);
|
||||
}
|
||||
|
||||
public static BothInt32 operator |(BothInt32 a, BothInt32 b)
|
||||
{
|
||||
int le = (int)(a.LittleEndian | b.LittleEndian);
|
||||
int be = (int)(a.BigEndian | b.BigEndian);
|
||||
return new BothInt32(le, be);
|
||||
}
|
||||
|
||||
public static BothInt32 operator ^(BothInt32 a, BothInt32 b)
|
||||
{
|
||||
int le = (int)(a.LittleEndian ^ b.LittleEndian);
|
||||
int be = (int)(a.BigEndian ^ b.BigEndian);
|
||||
return new BothInt32(le, be);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
143
SabreTools.IO/Numerics/BothInt64.cs
Normal file
143
SabreTools.IO/Numerics/BothInt64.cs
Normal file
@@ -0,0 +1,143 @@
|
||||
namespace SabreTools.Numerics
|
||||
{
|
||||
/// <summary>
|
||||
/// Both-endian 64-bit signed value
|
||||
/// </summary>
|
||||
public sealed class BothInt64(long le, long be) : BothEndian<long>(le, be)
|
||||
{
|
||||
public static implicit operator BothInt64(long val)
|
||||
=> new(val, val);
|
||||
|
||||
#region Arithmetic Unary Operators
|
||||
|
||||
public static BothInt64 operator ++(BothInt64 a)
|
||||
{
|
||||
long le = (long)(a.LittleEndian + 1);
|
||||
long be = (long)(a.BigEndian + 1);
|
||||
return new BothInt64(le, be);
|
||||
}
|
||||
|
||||
public static BothInt64 operator --(BothInt64 a)
|
||||
{
|
||||
long le = (long)(a.LittleEndian - 1);
|
||||
long be = (long)(a.BigEndian - 1);
|
||||
return new BothInt64(le, be);
|
||||
}
|
||||
|
||||
public static BothInt64 operator +(BothInt64 a)
|
||||
{
|
||||
long le = (long)(+a.LittleEndian);
|
||||
long be = (long)(+a.BigEndian);
|
||||
return new BothInt64(le, be);
|
||||
}
|
||||
|
||||
public static BothInt64 operator -(BothInt64 a)
|
||||
{
|
||||
long le = (long)(-a.LittleEndian);
|
||||
long be = (long)(-a.BigEndian);
|
||||
return new BothInt64(le, be);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Arithmetic Binary Operators
|
||||
|
||||
public static BothInt64 operator *(BothInt64 a, BothInt64 b)
|
||||
{
|
||||
long le = (long)(a.LittleEndian * b.LittleEndian);
|
||||
long be = (long)(a.BigEndian * b.BigEndian);
|
||||
return new BothInt64(le, be);
|
||||
}
|
||||
|
||||
public static BothInt64 operator /(BothInt64 a, BothInt64 b)
|
||||
{
|
||||
long le = (long)(a.LittleEndian / b.LittleEndian);
|
||||
long be = (long)(a.BigEndian / b.BigEndian);
|
||||
return new BothInt64(le, be);
|
||||
}
|
||||
|
||||
public static BothInt64 operator %(BothInt64 a, BothInt64 b)
|
||||
{
|
||||
long le = (long)(a.LittleEndian % b.LittleEndian);
|
||||
long be = (long)(a.BigEndian % b.BigEndian);
|
||||
return new BothInt64(le, be);
|
||||
}
|
||||
|
||||
public static BothInt64 operator +(BothInt64 a, BothInt64 b)
|
||||
{
|
||||
long le = (long)(a.LittleEndian + b.LittleEndian);
|
||||
long be = (long)(a.BigEndian + b.BigEndian);
|
||||
return new BothInt64(le, be);
|
||||
}
|
||||
|
||||
public static BothInt64 operator -(BothInt64 a, BothInt64 b)
|
||||
{
|
||||
long le = (long)(a.LittleEndian - b.LittleEndian);
|
||||
long be = (long)(a.BigEndian - b.BigEndian);
|
||||
return new BothInt64(le, be);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Bitwise Unary Operators
|
||||
|
||||
public static BothInt64 operator ~(BothInt64 a)
|
||||
{
|
||||
long le = (long)(~a.LittleEndian);
|
||||
long be = (long)(~a.BigEndian);
|
||||
return new BothInt64(le, be);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Shift Binary Operators
|
||||
|
||||
public static BothInt64 operator <<(BothInt64 a, BothInt32 b)
|
||||
{
|
||||
long le = (long)(a.LittleEndian << b.LittleEndian);
|
||||
long be = (long)(a.BigEndian << b.BigEndian);
|
||||
return new BothInt64(le, be);
|
||||
}
|
||||
|
||||
public static BothInt64 operator >>(BothInt64 a, BothInt32 b)
|
||||
{
|
||||
long le = (long)(a.LittleEndian >> b.LittleEndian);
|
||||
long be = (long)(a.BigEndian >> b.BigEndian);
|
||||
return new BothInt64(le, be);
|
||||
}
|
||||
|
||||
public static BothInt64 operator >>>(BothInt64 a, BothInt32 b)
|
||||
{
|
||||
long le = (long)(a.LittleEndian >>> b.LittleEndian);
|
||||
long be = (long)(a.BigEndian >>> b.BigEndian);
|
||||
return new BothInt64(le, be);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Bitwise Binary Operators
|
||||
|
||||
public static BothInt64 operator &(BothInt64 a, BothInt64 b)
|
||||
{
|
||||
long le = (long)(a.LittleEndian & b.LittleEndian);
|
||||
long be = (long)(a.BigEndian & b.BigEndian);
|
||||
return new BothInt64(le, be);
|
||||
}
|
||||
|
||||
public static BothInt64 operator |(BothInt64 a, BothInt64 b)
|
||||
{
|
||||
long le = (long)(a.LittleEndian | b.LittleEndian);
|
||||
long be = (long)(a.BigEndian | b.BigEndian);
|
||||
return new BothInt64(le, be);
|
||||
}
|
||||
|
||||
public static BothInt64 operator ^(BothInt64 a, BothInt64 b)
|
||||
{
|
||||
long le = (long)(a.LittleEndian ^ b.LittleEndian);
|
||||
long be = (long)(a.BigEndian ^ b.BigEndian);
|
||||
return new BothInt64(le, be);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
143
SabreTools.IO/Numerics/BothInt8.cs
Normal file
143
SabreTools.IO/Numerics/BothInt8.cs
Normal file
@@ -0,0 +1,143 @@
|
||||
namespace SabreTools.Numerics
|
||||
{
|
||||
/// <summary>
|
||||
/// Both-endian 8-bit signed value
|
||||
/// </summary>
|
||||
public sealed class BothInt8(sbyte le, sbyte be) : BothEndian<sbyte>(le, be)
|
||||
{
|
||||
public static implicit operator BothInt8(sbyte val)
|
||||
=> new(val, val);
|
||||
|
||||
#region Arithmetic Unary Operators
|
||||
|
||||
public static BothInt8 operator ++(BothInt8 a)
|
||||
{
|
||||
sbyte le = (sbyte)(a.LittleEndian + 1);
|
||||
sbyte be = (sbyte)(a.BigEndian + 1);
|
||||
return new BothInt8(le, be);
|
||||
}
|
||||
|
||||
public static BothInt8 operator --(BothInt8 a)
|
||||
{
|
||||
sbyte le = (sbyte)(a.LittleEndian - 1);
|
||||
sbyte be = (sbyte)(a.BigEndian - 1);
|
||||
return new BothInt8(le, be);
|
||||
}
|
||||
|
||||
public static BothInt8 operator +(BothInt8 a)
|
||||
{
|
||||
sbyte le = (sbyte)(+a.LittleEndian);
|
||||
sbyte be = (sbyte)(+a.BigEndian);
|
||||
return new BothInt8(le, be);
|
||||
}
|
||||
|
||||
public static BothInt8 operator -(BothInt8 a)
|
||||
{
|
||||
sbyte le = (sbyte)(-a.LittleEndian);
|
||||
sbyte be = (sbyte)(-a.BigEndian);
|
||||
return new BothInt8(le, be);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Arithmetic Binary Operators
|
||||
|
||||
public static BothInt8 operator *(BothInt8 a, BothInt8 b)
|
||||
{
|
||||
sbyte le = (sbyte)(a.LittleEndian * b.LittleEndian);
|
||||
sbyte be = (sbyte)(a.BigEndian * b.BigEndian);
|
||||
return new BothInt8(le, be);
|
||||
}
|
||||
|
||||
public static BothInt8 operator /(BothInt8 a, BothInt8 b)
|
||||
{
|
||||
sbyte le = (sbyte)(a.LittleEndian / b.LittleEndian);
|
||||
sbyte be = (sbyte)(a.BigEndian / b.BigEndian);
|
||||
return new BothInt8(le, be);
|
||||
}
|
||||
|
||||
public static BothInt8 operator %(BothInt8 a, BothInt8 b)
|
||||
{
|
||||
sbyte le = (sbyte)(a.LittleEndian % b.LittleEndian);
|
||||
sbyte be = (sbyte)(a.BigEndian % b.BigEndian);
|
||||
return new BothInt8(le, be);
|
||||
}
|
||||
|
||||
public static BothInt8 operator +(BothInt8 a, BothInt8 b)
|
||||
{
|
||||
sbyte le = (sbyte)(a.LittleEndian + b.LittleEndian);
|
||||
sbyte be = (sbyte)(a.BigEndian + b.BigEndian);
|
||||
return new BothInt8(le, be);
|
||||
}
|
||||
|
||||
public static BothInt8 operator -(BothInt8 a, BothInt8 b)
|
||||
{
|
||||
sbyte le = (sbyte)(a.LittleEndian - b.LittleEndian);
|
||||
sbyte be = (sbyte)(a.BigEndian - b.BigEndian);
|
||||
return new BothInt8(le, be);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Bitwise Unary Operators
|
||||
|
||||
public static BothInt8 operator ~(BothInt8 a)
|
||||
{
|
||||
sbyte le = (sbyte)(~a.LittleEndian);
|
||||
sbyte be = (sbyte)(~a.BigEndian);
|
||||
return new BothInt8(le, be);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Shift Binary Operators
|
||||
|
||||
public static BothInt8 operator <<(BothInt8 a, BothInt8 b)
|
||||
{
|
||||
sbyte le = (sbyte)(a.LittleEndian << b.LittleEndian);
|
||||
sbyte be = (sbyte)(a.BigEndian << b.BigEndian);
|
||||
return new BothInt8(le, be);
|
||||
}
|
||||
|
||||
public static BothInt8 operator >>(BothInt8 a, BothInt8 b)
|
||||
{
|
||||
sbyte le = (sbyte)(a.LittleEndian >> b.LittleEndian);
|
||||
sbyte be = (sbyte)(a.BigEndian >> b.BigEndian);
|
||||
return new BothInt8(le, be);
|
||||
}
|
||||
|
||||
public static BothInt8 operator >>>(BothInt8 a, BothInt8 b)
|
||||
{
|
||||
sbyte le = (sbyte)(a.LittleEndian >>> b.LittleEndian);
|
||||
sbyte be = (sbyte)(a.BigEndian >>> b.BigEndian);
|
||||
return new BothInt8(le, be);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Bitwise Binary Operators
|
||||
|
||||
public static BothInt8 operator &(BothInt8 a, BothInt8 b)
|
||||
{
|
||||
sbyte le = (sbyte)(a.LittleEndian & b.LittleEndian);
|
||||
sbyte be = (sbyte)(a.BigEndian & b.BigEndian);
|
||||
return new BothInt8(le, be);
|
||||
}
|
||||
|
||||
public static BothInt8 operator |(BothInt8 a, BothInt8 b)
|
||||
{
|
||||
sbyte le = (sbyte)(a.LittleEndian | b.LittleEndian);
|
||||
sbyte be = (sbyte)(a.BigEndian | b.BigEndian);
|
||||
return new BothInt8(le, be);
|
||||
}
|
||||
|
||||
public static BothInt8 operator ^(BothInt8 a, BothInt8 b)
|
||||
{
|
||||
sbyte le = (sbyte)(a.LittleEndian ^ b.LittleEndian);
|
||||
sbyte be = (sbyte)(a.BigEndian ^ b.BigEndian);
|
||||
return new BothInt8(le, be);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
129
SabreTools.IO/Numerics/BothUInt16.cs
Normal file
129
SabreTools.IO/Numerics/BothUInt16.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
namespace SabreTools.Numerics
|
||||
{
|
||||
/// <summary>
|
||||
/// Both-endian 16-bit unsigned value
|
||||
/// </summary>
|
||||
public sealed class BothUInt16(ushort le, ushort be) : BothEndian<ushort>(le, be)
|
||||
{
|
||||
public static implicit operator BothUInt16(ushort val)
|
||||
=> new(val, val);
|
||||
|
||||
#region Arithmetic Unary Operators
|
||||
|
||||
public static BothUInt16 operator ++(BothUInt16 a)
|
||||
{
|
||||
ushort le = (ushort)(a.LittleEndian + 1);
|
||||
ushort be = (ushort)(a.BigEndian + 1);
|
||||
return new BothUInt16(le, be);
|
||||
}
|
||||
|
||||
public static BothUInt16 operator --(BothUInt16 a)
|
||||
{
|
||||
ushort le = (ushort)(a.LittleEndian - 1);
|
||||
ushort be = (ushort)(a.BigEndian - 1);
|
||||
return new BothUInt16(le, be);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Arithmetic Binary Operators
|
||||
|
||||
public static BothUInt16 operator *(BothUInt16 a, BothUInt16 b)
|
||||
{
|
||||
ushort le = (ushort)(a.LittleEndian * b.LittleEndian);
|
||||
ushort be = (ushort)(a.BigEndian * b.BigEndian);
|
||||
return new BothUInt16(le, be);
|
||||
}
|
||||
|
||||
public static BothUInt16 operator /(BothUInt16 a, BothUInt16 b)
|
||||
{
|
||||
ushort le = (ushort)(a.LittleEndian / b.LittleEndian);
|
||||
ushort be = (ushort)(a.BigEndian / b.BigEndian);
|
||||
return new BothUInt16(le, be);
|
||||
}
|
||||
|
||||
public static BothUInt16 operator %(BothUInt16 a, BothUInt16 b)
|
||||
{
|
||||
ushort le = (ushort)(a.LittleEndian % b.LittleEndian);
|
||||
ushort be = (ushort)(a.BigEndian % b.BigEndian);
|
||||
return new BothUInt16(le, be);
|
||||
}
|
||||
|
||||
public static BothUInt16 operator +(BothUInt16 a, BothUInt16 b)
|
||||
{
|
||||
ushort le = (ushort)(a.LittleEndian + b.LittleEndian);
|
||||
ushort be = (ushort)(a.BigEndian + b.BigEndian);
|
||||
return new BothUInt16(le, be);
|
||||
}
|
||||
|
||||
public static BothUInt16 operator -(BothUInt16 a, BothUInt16 b)
|
||||
{
|
||||
ushort le = (ushort)(a.LittleEndian - b.LittleEndian);
|
||||
ushort be = (ushort)(a.BigEndian - b.BigEndian);
|
||||
return new BothUInt16(le, be);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Bitwise Unary Operators
|
||||
|
||||
public static BothUInt16 operator ~(BothUInt16 a)
|
||||
{
|
||||
ushort le = (ushort)(~a.LittleEndian);
|
||||
ushort be = (ushort)(~a.BigEndian);
|
||||
return new BothUInt16(le, be);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Shift Binary Operators
|
||||
|
||||
public static BothUInt16 operator <<(BothUInt16 a, BothUInt16 b)
|
||||
{
|
||||
ushort le = (ushort)(a.LittleEndian << b.LittleEndian);
|
||||
ushort be = (ushort)(a.BigEndian << b.BigEndian);
|
||||
return new BothUInt16(le, be);
|
||||
}
|
||||
|
||||
public static BothUInt16 operator >>(BothUInt16 a, BothUInt16 b)
|
||||
{
|
||||
ushort le = (ushort)(a.LittleEndian >> b.LittleEndian);
|
||||
ushort be = (ushort)(a.BigEndian >> b.BigEndian);
|
||||
return new BothUInt16(le, be);
|
||||
}
|
||||
|
||||
public static BothUInt16 operator >>>(BothUInt16 a, BothUInt16 b)
|
||||
{
|
||||
ushort le = (ushort)(a.LittleEndian >>> b.LittleEndian);
|
||||
ushort be = (ushort)(a.BigEndian >>> b.BigEndian);
|
||||
return new BothUInt16(le, be);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Bitwise Binary Operators
|
||||
|
||||
public static BothUInt16 operator &(BothUInt16 a, BothUInt16 b)
|
||||
{
|
||||
ushort le = (ushort)(a.LittleEndian & b.LittleEndian);
|
||||
ushort be = (ushort)(a.BigEndian & b.BigEndian);
|
||||
return new BothUInt16(le, be);
|
||||
}
|
||||
|
||||
public static BothUInt16 operator |(BothUInt16 a, BothUInt16 b)
|
||||
{
|
||||
ushort le = (ushort)(a.LittleEndian | b.LittleEndian);
|
||||
ushort be = (ushort)(a.BigEndian | b.BigEndian);
|
||||
return new BothUInt16(le, be);
|
||||
}
|
||||
|
||||
public static BothUInt16 operator ^(BothUInt16 a, BothUInt16 b)
|
||||
{
|
||||
ushort le = (ushort)(a.LittleEndian ^ b.LittleEndian);
|
||||
ushort be = (ushort)(a.BigEndian ^ b.BigEndian);
|
||||
return new BothUInt16(le, be);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
129
SabreTools.IO/Numerics/BothUInt32.cs
Normal file
129
SabreTools.IO/Numerics/BothUInt32.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
namespace SabreTools.Numerics
|
||||
{
|
||||
/// <summary>
|
||||
/// Both-endian 32-bit unsigned value
|
||||
/// </summary>
|
||||
public sealed class BothUInt32(uint le, uint be) : BothEndian<uint>(le, be)
|
||||
{
|
||||
public static implicit operator BothUInt32(uint val)
|
||||
=> new(val, val);
|
||||
|
||||
#region Arithmetic Unary Operators
|
||||
|
||||
public static BothUInt32 operator ++(BothUInt32 a)
|
||||
{
|
||||
uint le = (uint)(a.LittleEndian + 1);
|
||||
uint be = (uint)(a.BigEndian + 1);
|
||||
return new BothUInt32(le, be);
|
||||
}
|
||||
|
||||
public static BothUInt32 operator --(BothUInt32 a)
|
||||
{
|
||||
uint le = (uint)(a.LittleEndian - 1);
|
||||
uint be = (uint)(a.BigEndian - 1);
|
||||
return new BothUInt32(le, be);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Arithmetic Binary Operators
|
||||
|
||||
public static BothUInt32 operator *(BothUInt32 a, BothUInt32 b)
|
||||
{
|
||||
uint le = (uint)(a.LittleEndian * b.LittleEndian);
|
||||
uint be = (uint)(a.BigEndian * b.BigEndian);
|
||||
return new BothUInt32(le, be);
|
||||
}
|
||||
|
||||
public static BothUInt32 operator /(BothUInt32 a, BothUInt32 b)
|
||||
{
|
||||
uint le = (uint)(a.LittleEndian / b.LittleEndian);
|
||||
uint be = (uint)(a.BigEndian / b.BigEndian);
|
||||
return new BothUInt32(le, be);
|
||||
}
|
||||
|
||||
public static BothUInt32 operator %(BothUInt32 a, BothUInt32 b)
|
||||
{
|
||||
uint le = (uint)(a.LittleEndian % b.LittleEndian);
|
||||
uint be = (uint)(a.BigEndian % b.BigEndian);
|
||||
return new BothUInt32(le, be);
|
||||
}
|
||||
|
||||
public static BothUInt32 operator +(BothUInt32 a, BothUInt32 b)
|
||||
{
|
||||
uint le = (uint)(a.LittleEndian + b.LittleEndian);
|
||||
uint be = (uint)(a.BigEndian + b.BigEndian);
|
||||
return new BothUInt32(le, be);
|
||||
}
|
||||
|
||||
public static BothUInt32 operator -(BothUInt32 a, BothUInt32 b)
|
||||
{
|
||||
uint le = (uint)(a.LittleEndian - b.LittleEndian);
|
||||
uint be = (uint)(a.BigEndian - b.BigEndian);
|
||||
return new BothUInt32(le, be);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Bitwise Unary Operators
|
||||
|
||||
public static BothUInt32 operator ~(BothUInt32 a)
|
||||
{
|
||||
uint le = (uint)(~a.LittleEndian);
|
||||
uint be = (uint)(~a.BigEndian);
|
||||
return new BothUInt32(le, be);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Shift Binary Operators
|
||||
|
||||
public static BothUInt32 operator <<(BothUInt32 a, BothInt32 b)
|
||||
{
|
||||
uint le = (uint)(a.LittleEndian << b.LittleEndian);
|
||||
uint be = (uint)(a.BigEndian << b.BigEndian);
|
||||
return new BothUInt32(le, be);
|
||||
}
|
||||
|
||||
public static BothUInt32 operator >>(BothUInt32 a, BothInt32 b)
|
||||
{
|
||||
uint le = (uint)(a.LittleEndian >> b.LittleEndian);
|
||||
uint be = (uint)(a.BigEndian >> b.BigEndian);
|
||||
return new BothUInt32(le, be);
|
||||
}
|
||||
|
||||
public static BothUInt32 operator >>>(BothUInt32 a, BothInt32 b)
|
||||
{
|
||||
uint le = (uint)(a.LittleEndian >>> b.LittleEndian);
|
||||
uint be = (uint)(a.BigEndian >>> b.BigEndian);
|
||||
return new BothUInt32(le, be);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Bitwise Binary Operators
|
||||
|
||||
public static BothUInt32 operator &(BothUInt32 a, BothUInt32 b)
|
||||
{
|
||||
uint le = (uint)(a.LittleEndian & b.LittleEndian);
|
||||
uint be = (uint)(a.BigEndian & b.BigEndian);
|
||||
return new BothUInt32(le, be);
|
||||
}
|
||||
|
||||
public static BothUInt32 operator |(BothUInt32 a, BothUInt32 b)
|
||||
{
|
||||
uint le = (uint)(a.LittleEndian | b.LittleEndian);
|
||||
uint be = (uint)(a.BigEndian | b.BigEndian);
|
||||
return new BothUInt32(le, be);
|
||||
}
|
||||
|
||||
public static BothUInt32 operator ^(BothUInt32 a, BothUInt32 b)
|
||||
{
|
||||
uint le = (uint)(a.LittleEndian ^ b.LittleEndian);
|
||||
uint be = (uint)(a.BigEndian ^ b.BigEndian);
|
||||
return new BothUInt32(le, be);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
129
SabreTools.IO/Numerics/BothUInt64.cs
Normal file
129
SabreTools.IO/Numerics/BothUInt64.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
namespace SabreTools.Numerics
|
||||
{
|
||||
/// <summary>
|
||||
/// Both-endian 64-bit unsigned value
|
||||
/// </summary>
|
||||
public sealed class BothUInt64(ulong le, ulong be) : BothEndian<ulong>(le, be)
|
||||
{
|
||||
public static implicit operator BothUInt64(ulong val)
|
||||
=> new(val, val);
|
||||
|
||||
#region Arithmetic Unary Operators
|
||||
|
||||
public static BothUInt64 operator ++(BothUInt64 a)
|
||||
{
|
||||
ulong le = (ulong)(a.LittleEndian + 1);
|
||||
ulong be = (ulong)(a.BigEndian + 1);
|
||||
return new BothUInt64(le, be);
|
||||
}
|
||||
|
||||
public static BothUInt64 operator --(BothUInt64 a)
|
||||
{
|
||||
ulong le = (ulong)(a.LittleEndian - 1);
|
||||
ulong be = (ulong)(a.BigEndian - 1);
|
||||
return new BothUInt64(le, be);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Arithmetic Binary Operators
|
||||
|
||||
public static BothUInt64 operator *(BothUInt64 a, BothUInt64 b)
|
||||
{
|
||||
ulong le = (ulong)(a.LittleEndian * b.LittleEndian);
|
||||
ulong be = (ulong)(a.BigEndian * b.BigEndian);
|
||||
return new BothUInt64(le, be);
|
||||
}
|
||||
|
||||
public static BothUInt64 operator /(BothUInt64 a, BothUInt64 b)
|
||||
{
|
||||
ulong le = (ulong)(a.LittleEndian / b.LittleEndian);
|
||||
ulong be = (ulong)(a.BigEndian / b.BigEndian);
|
||||
return new BothUInt64(le, be);
|
||||
}
|
||||
|
||||
public static BothUInt64 operator %(BothUInt64 a, BothUInt64 b)
|
||||
{
|
||||
ulong le = (ulong)(a.LittleEndian % b.LittleEndian);
|
||||
ulong be = (ulong)(a.BigEndian % b.BigEndian);
|
||||
return new BothUInt64(le, be);
|
||||
}
|
||||
|
||||
public static BothUInt64 operator +(BothUInt64 a, BothUInt64 b)
|
||||
{
|
||||
ulong le = (ulong)(a.LittleEndian + b.LittleEndian);
|
||||
ulong be = (ulong)(a.BigEndian + b.BigEndian);
|
||||
return new BothUInt64(le, be);
|
||||
}
|
||||
|
||||
public static BothUInt64 operator -(BothUInt64 a, BothUInt64 b)
|
||||
{
|
||||
ulong le = (ulong)(a.LittleEndian - b.LittleEndian);
|
||||
ulong be = (ulong)(a.BigEndian - b.BigEndian);
|
||||
return new BothUInt64(le, be);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Bitwise Unary Operators
|
||||
|
||||
public static BothUInt64 operator ~(BothUInt64 a)
|
||||
{
|
||||
ulong le = (ulong)(~a.LittleEndian);
|
||||
ulong be = (ulong)(~a.BigEndian);
|
||||
return new BothUInt64(le, be);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Shift Binary Operators
|
||||
|
||||
public static BothUInt64 operator <<(BothUInt64 a, BothInt32 b)
|
||||
{
|
||||
ulong le = (ulong)(a.LittleEndian << b.LittleEndian);
|
||||
ulong be = (ulong)(a.BigEndian << b.BigEndian);
|
||||
return new BothUInt64(le, be);
|
||||
}
|
||||
|
||||
public static BothUInt64 operator >>(BothUInt64 a, BothInt32 b)
|
||||
{
|
||||
ulong le = (ulong)(a.LittleEndian >> b.LittleEndian);
|
||||
ulong be = (ulong)(a.BigEndian >> b.BigEndian);
|
||||
return new BothUInt64(le, be);
|
||||
}
|
||||
|
||||
public static BothUInt64 operator >>>(BothUInt64 a, BothInt32 b)
|
||||
{
|
||||
ulong le = (ulong)(a.LittleEndian >>> b.LittleEndian);
|
||||
ulong be = (ulong)(a.BigEndian >>> b.BigEndian);
|
||||
return new BothUInt64(le, be);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Bitwise Binary Operators
|
||||
|
||||
public static BothUInt64 operator &(BothUInt64 a, BothUInt64 b)
|
||||
{
|
||||
ulong le = (ulong)(a.LittleEndian & b.LittleEndian);
|
||||
ulong be = (ulong)(a.BigEndian & b.BigEndian);
|
||||
return new BothUInt64(le, be);
|
||||
}
|
||||
|
||||
public static BothUInt64 operator |(BothUInt64 a, BothUInt64 b)
|
||||
{
|
||||
ulong le = (ulong)(a.LittleEndian | b.LittleEndian);
|
||||
ulong be = (ulong)(a.BigEndian | b.BigEndian);
|
||||
return new BothUInt64(le, be);
|
||||
}
|
||||
|
||||
public static BothUInt64 operator ^(BothUInt64 a, BothUInt64 b)
|
||||
{
|
||||
ulong le = (ulong)(a.LittleEndian ^ b.LittleEndian);
|
||||
ulong be = (ulong)(a.BigEndian ^ b.BigEndian);
|
||||
return new BothUInt64(le, be);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
129
SabreTools.IO/Numerics/BothUInt8.cs
Normal file
129
SabreTools.IO/Numerics/BothUInt8.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
namespace SabreTools.Numerics
|
||||
{
|
||||
/// <summary>
|
||||
/// Both-endian 8-bit unsigned value
|
||||
/// </summary>
|
||||
public sealed class BothUInt8(byte le, byte be) : BothEndian<byte>(le, be)
|
||||
{
|
||||
public static implicit operator BothUInt8(byte val)
|
||||
=> new(val, val);
|
||||
|
||||
#region Arithmetic Unary Operators
|
||||
|
||||
public static BothUInt8 operator ++(BothUInt8 a)
|
||||
{
|
||||
byte le = (byte)(a.LittleEndian + 1);
|
||||
byte be = (byte)(a.BigEndian + 1);
|
||||
return new BothUInt8(le, be);
|
||||
}
|
||||
|
||||
public static BothUInt8 operator --(BothUInt8 a)
|
||||
{
|
||||
byte le = (byte)(a.LittleEndian - 1);
|
||||
byte be = (byte)(a.BigEndian - 1);
|
||||
return new BothUInt8(le, be);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Arithmetic Binary Operators
|
||||
|
||||
public static BothUInt8 operator *(BothUInt8 a, BothUInt8 b)
|
||||
{
|
||||
byte le = (byte)(a.LittleEndian * b.LittleEndian);
|
||||
byte be = (byte)(a.BigEndian * b.BigEndian);
|
||||
return new BothUInt8(le, be);
|
||||
}
|
||||
|
||||
public static BothUInt8 operator /(BothUInt8 a, BothUInt8 b)
|
||||
{
|
||||
byte le = (byte)(a.LittleEndian / b.LittleEndian);
|
||||
byte be = (byte)(a.BigEndian / b.BigEndian);
|
||||
return new BothUInt8(le, be);
|
||||
}
|
||||
|
||||
public static BothUInt8 operator %(BothUInt8 a, BothUInt8 b)
|
||||
{
|
||||
byte le = (byte)(a.LittleEndian % b.LittleEndian);
|
||||
byte be = (byte)(a.BigEndian % b.BigEndian);
|
||||
return new BothUInt8(le, be);
|
||||
}
|
||||
|
||||
public static BothUInt8 operator +(BothUInt8 a, BothUInt8 b)
|
||||
{
|
||||
byte le = (byte)(a.LittleEndian + b.LittleEndian);
|
||||
byte be = (byte)(a.BigEndian + b.BigEndian);
|
||||
return new BothUInt8(le, be);
|
||||
}
|
||||
|
||||
public static BothUInt8 operator -(BothUInt8 a, BothUInt8 b)
|
||||
{
|
||||
byte le = (byte)(a.LittleEndian - b.LittleEndian);
|
||||
byte be = (byte)(a.BigEndian - b.BigEndian);
|
||||
return new BothUInt8(le, be);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Bitwise Unary Operators
|
||||
|
||||
public static BothUInt8 operator ~(BothUInt8 a)
|
||||
{
|
||||
byte le = (byte)(~a.LittleEndian);
|
||||
byte be = (byte)(~a.BigEndian);
|
||||
return new BothUInt8(le, be);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Shift Binary Operators
|
||||
|
||||
public static BothUInt8 operator <<(BothUInt8 a, BothUInt8 b)
|
||||
{
|
||||
byte le = (byte)(a.LittleEndian << b.LittleEndian);
|
||||
byte be = (byte)(a.BigEndian << b.BigEndian);
|
||||
return new BothUInt8(le, be);
|
||||
}
|
||||
|
||||
public static BothUInt8 operator >>(BothUInt8 a, BothUInt8 b)
|
||||
{
|
||||
byte le = (byte)(a.LittleEndian >> b.LittleEndian);
|
||||
byte be = (byte)(a.BigEndian >> b.BigEndian);
|
||||
return new BothUInt8(le, be);
|
||||
}
|
||||
|
||||
public static BothUInt8 operator >>>(BothUInt8 a, BothUInt8 b)
|
||||
{
|
||||
byte le = (byte)(a.LittleEndian >>> b.LittleEndian);
|
||||
byte be = (byte)(a.BigEndian >>> b.BigEndian);
|
||||
return new BothUInt8(le, be);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Bitwise Binary Operators
|
||||
|
||||
public static BothUInt8 operator &(BothUInt8 a, BothUInt8 b)
|
||||
{
|
||||
byte le = (byte)(a.LittleEndian & b.LittleEndian);
|
||||
byte be = (byte)(a.BigEndian & b.BigEndian);
|
||||
return new BothUInt8(le, be);
|
||||
}
|
||||
|
||||
public static BothUInt8 operator |(BothUInt8 a, BothUInt8 b)
|
||||
{
|
||||
byte le = (byte)(a.LittleEndian | b.LittleEndian);
|
||||
byte be = (byte)(a.BigEndian | b.BigEndian);
|
||||
return new BothUInt8(le, be);
|
||||
}
|
||||
|
||||
public static BothUInt8 operator ^(BothUInt8 a, BothUInt8 b)
|
||||
{
|
||||
byte le = (byte)(a.LittleEndian ^ b.LittleEndian);
|
||||
byte be = (byte)(a.BigEndian ^ b.BigEndian);
|
||||
return new BothUInt8(le, be);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Matching.Compare;
|
||||
using SabreTools.Text.Compare;
|
||||
|
||||
namespace SabreTools.IO
|
||||
{
|
||||
|
||||
@@ -8,9 +8,10 @@
|
||||
<LangVersion>latest</LangVersion>
|
||||
<NoWarn>CS0618</NoWarn>
|
||||
<Nullable>enable</Nullable>
|
||||
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<Version>1.7.0</Version>
|
||||
<Version>1.8.0</Version>
|
||||
|
||||
<!-- Package Properties -->
|
||||
<Authors>Matt Nadareski</Authors>
|
||||
@@ -29,8 +30,13 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SabreTools.Matching" Version="1.6.0" />
|
||||
<PackageReference Include="SabreTools.Models" Version="1.6.0" />
|
||||
<InternalsVisibleTo Include="SabreTools.IO.Test" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BouncyCastle.NetCore" Version="1.9.0" Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net40`))" />
|
||||
<PackageReference Include="BouncyCastle.NetCore" Version="2.2.1" Condition="!$(TargetFramework.StartsWith(`net2`)) AND !$(TargetFramework.StartsWith(`net3`)) AND !$(TargetFramework.StartsWith(`net40`))" />
|
||||
<PackageReference Include="SabreTools.Hashing" Version="[1.5.1]" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
77
SabreTools.IO/Streams/BufferedStream.cs
Normal file
77
SabreTools.IO/Streams/BufferedStream.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using System.IO;
|
||||
|
||||
namespace SabreTools.IO.Streams
|
||||
{
|
||||
/// <summary>
|
||||
/// Buffered stream that reads in blocks
|
||||
/// </summary>
|
||||
/// <remarks>Not a true <see cref="Stream"/> implementation yet</remarks>
|
||||
public class BufferedStream
|
||||
{
|
||||
/// <summary>
|
||||
/// Source stream for populating the buffer
|
||||
/// </summary>
|
||||
private readonly Stream _source;
|
||||
|
||||
/// <summary>
|
||||
/// Internal buffer to read
|
||||
/// </summary>
|
||||
private readonly byte[] _buffer = new byte[2048];
|
||||
|
||||
/// <summary>
|
||||
/// Current pointer into the buffer
|
||||
/// </summary>
|
||||
private int _bufferPtr = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the number of available bytes
|
||||
/// </summary>
|
||||
private int _available = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new buffered stream
|
||||
/// </summary>
|
||||
public BufferedStream(Stream source)
|
||||
{
|
||||
_source = source;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read the next byte from the buffer, if possible
|
||||
/// </summary>
|
||||
public byte? ReadNextByte()
|
||||
{
|
||||
// Ensure the buffer first
|
||||
if (!EnsureBuffer())
|
||||
return null;
|
||||
|
||||
// Return the next available value
|
||||
return _buffer[_bufferPtr++];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensure the buffer has data to read
|
||||
/// </summary>
|
||||
private bool EnsureBuffer()
|
||||
{
|
||||
// Force an update if in the initial state
|
||||
if (_available == -1)
|
||||
{
|
||||
_available = _source.Read(_buffer, 0, _buffer.Length);
|
||||
_bufferPtr = 0;
|
||||
return _available != 0;
|
||||
}
|
||||
|
||||
// If the pointer is out of range
|
||||
if (_bufferPtr >= _available)
|
||||
{
|
||||
_available = _source.Read(_buffer, 0, _buffer.Length);
|
||||
_bufferPtr = 0;
|
||||
return _available != 0;
|
||||
}
|
||||
|
||||
// Otherwise, assume data is available
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ namespace SabreTools.IO.Streams
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal State
|
||||
#region Instance Variables
|
||||
|
||||
/// <summary>
|
||||
/// Internal collection of streams to read from
|
||||
@@ -58,6 +58,8 @@ namespace SabreTools.IO.Streams
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Create a new, empty ReadOnlyCompositeStream
|
||||
/// </summary>
|
||||
@@ -109,7 +111,7 @@ namespace SabreTools.IO.Streams
|
||||
/// </summary>
|
||||
public ReadOnlyCompositeStream(IEnumerable<Stream> streams)
|
||||
{
|
||||
_streams = new List<Stream>(streams);
|
||||
_streams = [.. streams];
|
||||
_length = 0;
|
||||
_position = 0;
|
||||
|
||||
@@ -123,6 +125,10 @@ namespace SabreTools.IO.Streams
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Data
|
||||
|
||||
/// <summary>
|
||||
/// Add a new stream to the collection
|
||||
/// </summary>
|
||||
@@ -138,10 +144,13 @@ namespace SabreTools.IO.Streams
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Stream Implementations
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Flush() => throw new NotImplementedException();
|
||||
public override void Flush()
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
@@ -198,27 +207,22 @@ namespace SabreTools.IO.Streams
|
||||
// Handle the "seek"
|
||||
switch (origin)
|
||||
{
|
||||
case SeekOrigin.Begin: _position = offset; break;
|
||||
case SeekOrigin.Current: _position += offset; break;
|
||||
case SeekOrigin.End: _position = _length - offset - 1; break;
|
||||
case SeekOrigin.Begin: Position = offset; break;
|
||||
case SeekOrigin.Current: Position += offset; break;
|
||||
case SeekOrigin.End: Position = _length + offset - 1; break;
|
||||
default: throw new ArgumentException($"Invalid value for {nameof(origin)}");
|
||||
}
|
||||
;
|
||||
|
||||
// Handle out-of-bounds seeks
|
||||
if (_position < 0)
|
||||
_position = 0;
|
||||
else if (_position >= _length)
|
||||
_position = _length - 1;
|
||||
|
||||
return _position;
|
||||
return Position;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void SetLength(long value) => throw new NotImplementedException();
|
||||
public override void SetLength(long value)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Write(byte[] buffer, int offset, int count) => throw new NotImplementedException();
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
254
SabreTools.IO/Streams/ViewStream.cs
Normal file
254
SabreTools.IO/Streams/ViewStream.cs
Normal file
@@ -0,0 +1,254 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace SabreTools.IO.Streams
|
||||
{
|
||||
/// <summary>
|
||||
/// Stream representing a view into a source
|
||||
/// </summary>
|
||||
public class ViewStream : Stream
|
||||
{
|
||||
#region Properties
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanRead => true;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanSeek => _source.CanSeek;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanWrite => false;
|
||||
|
||||
/// <summary>
|
||||
/// Filename from the source, if possible
|
||||
/// </summary>
|
||||
public string? Filename
|
||||
{
|
||||
get
|
||||
{
|
||||
// A subset of streams have a filename
|
||||
if (_source is FileStream fs)
|
||||
return fs.Name;
|
||||
else if (_source is ViewStream vs)
|
||||
return vs.Filename;
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override long Length => _length;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override long Position
|
||||
{
|
||||
get
|
||||
{
|
||||
// Handle 0-length sources
|
||||
if (_length <= 0)
|
||||
return 0;
|
||||
|
||||
return _source.Position - _initialPosition;
|
||||
}
|
||||
set
|
||||
{
|
||||
// Handle 0-length sources
|
||||
if (_length <= 0)
|
||||
{
|
||||
_source.Position = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
long position = value;
|
||||
|
||||
// Handle out-of-bounds seeks
|
||||
if (position < 0)
|
||||
position = 0;
|
||||
else if (position >= _length)
|
||||
position = _length - 1;
|
||||
|
||||
_source.Position = _initialPosition + position;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Instance Variables
|
||||
|
||||
/// <summary>
|
||||
/// Initial position within the underlying data
|
||||
/// </summary>
|
||||
protected long _initialPosition;
|
||||
|
||||
/// <summary>
|
||||
/// Usable length in the underlying data
|
||||
/// </summary>
|
||||
protected long _length;
|
||||
|
||||
/// <summary>
|
||||
/// Source data
|
||||
/// </summary>
|
||||
protected Stream _source;
|
||||
|
||||
/// <summary>
|
||||
/// Lock object for reading from the source
|
||||
/// </summary>
|
||||
private readonly object _sourceLock = new();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new ViewStream from a Stream
|
||||
/// </summary>
|
||||
public ViewStream(Stream data, long offset)
|
||||
{
|
||||
if (!data.CanRead)
|
||||
throw new ArgumentException(nameof(data));
|
||||
if (offset < 0 || offset > data.Length)
|
||||
throw new ArgumentOutOfRangeException(nameof(offset));
|
||||
|
||||
_source = data;
|
||||
_initialPosition = offset;
|
||||
_length = data.Length - offset;
|
||||
|
||||
_source.Seek(_initialPosition, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new ViewStream from a Stream
|
||||
/// </summary>
|
||||
public ViewStream(Stream data, long offset, long length)
|
||||
{
|
||||
if (!data.CanRead)
|
||||
throw new ArgumentException(nameof(data));
|
||||
if (offset < 0 || offset > data.Length)
|
||||
throw new ArgumentOutOfRangeException(nameof(offset));
|
||||
if (length < 0 || offset + length > data.Length)
|
||||
throw new ArgumentOutOfRangeException(nameof(length));
|
||||
|
||||
_source = data;
|
||||
_initialPosition = offset;
|
||||
_length = length;
|
||||
|
||||
_source.Seek(_initialPosition, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new ViewStream from a byte array
|
||||
/// </summary>
|
||||
public ViewStream(byte[] data, long offset)
|
||||
{
|
||||
if (offset < 0 || offset > data.Length)
|
||||
throw new ArgumentOutOfRangeException(nameof(offset));
|
||||
|
||||
long length = data.Length - offset;
|
||||
_source = new MemoryStream(data, (int)offset, (int)length);
|
||||
_initialPosition = 0;
|
||||
_length = length;
|
||||
|
||||
_source.Seek(_initialPosition, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new ViewStream from a byte array
|
||||
/// </summary>
|
||||
public ViewStream(byte[] data, long offset, long length)
|
||||
{
|
||||
if (offset < 0 || offset > data.Length)
|
||||
throw new ArgumentOutOfRangeException(nameof(offset));
|
||||
if (length < 0 || offset + length > data.Length)
|
||||
throw new ArgumentOutOfRangeException(nameof(length));
|
||||
|
||||
_source = new MemoryStream(data, (int)offset, (int)length);
|
||||
_initialPosition = 0;
|
||||
_length = length;
|
||||
|
||||
_source.Seek(_initialPosition, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Data
|
||||
|
||||
/// <summary>
|
||||
/// Check if a data segment is valid in the data source
|
||||
/// </summary>
|
||||
/// <param name="offset">Position in the source</param>
|
||||
/// <param name="count">Length of the data to check</param>
|
||||
/// <returns>True if the positional data is valid, false otherwise</returns>
|
||||
public bool SegmentValid(long offset, long count)
|
||||
{
|
||||
if (offset < 0 || offset > Length)
|
||||
return false;
|
||||
if (count < 0 || offset + count > Length)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Stream Implementations
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Flush()
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
// Invalid cases always return 0
|
||||
if (buffer.Length == 0)
|
||||
return 0;
|
||||
if (offset < 0 || offset >= buffer.Length)
|
||||
return 0;
|
||||
if (count < 0 || offset + count > buffer.Length)
|
||||
return 0;
|
||||
|
||||
// Short-circuit 0-byte reads
|
||||
if (count == 0)
|
||||
return 0;
|
||||
|
||||
try
|
||||
{
|
||||
lock (_sourceLock)
|
||||
{
|
||||
return _source.Read(buffer, offset, count);
|
||||
}
|
||||
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Absorb the error
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
// Handle the "seek"
|
||||
switch (origin)
|
||||
{
|
||||
case SeekOrigin.Begin: Position = offset; break;
|
||||
case SeekOrigin.Current: Position += offset; break;
|
||||
case SeekOrigin.End: Position = _length + offset - 1; break;
|
||||
default: throw new ArgumentException($"Invalid value for {nameof(origin)}");
|
||||
}
|
||||
|
||||
return Position;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void SetLength(long value)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
158
SabreTools.IO/Transform/Combine.cs
Normal file
158
SabreTools.IO/Transform/Combine.cs
Normal file
@@ -0,0 +1,158 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace SabreTools.IO.Transform
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers to combine inputs
|
||||
/// </summary>
|
||||
public static class Combine
|
||||
{
|
||||
/// <summary>
|
||||
/// Concatenate all files in the order provided, if possible
|
||||
/// </summary>
|
||||
/// <param name="paths">List of paths to combine</param>
|
||||
/// <param name="output">Path to the output file</param>
|
||||
/// <returns>True if the files were concatenated successfully, false otherwise</returns>
|
||||
public static bool Concatenate(List<string> paths, string output)
|
||||
{
|
||||
// If the path list is empty
|
||||
if (paths.Count == 0)
|
||||
return false;
|
||||
|
||||
// If the output filename is invalid
|
||||
if (string.IsNullOrEmpty(output))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
// Try to build the new output file
|
||||
using var ofs = File.Open(output, FileMode.Create, FileAccess.Write, FileShare.None);
|
||||
|
||||
for (int i = 0; i < paths.Count; i++)
|
||||
{
|
||||
// Get the next file
|
||||
string next = paths[i];
|
||||
if (!File.Exists(next))
|
||||
break;
|
||||
|
||||
// Copy the next input to the output
|
||||
using var ifs = File.Open(next, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
|
||||
// Write in blocks
|
||||
int read = 0;
|
||||
do
|
||||
{
|
||||
byte[] buffer = new byte[3 * 1024 * 1024];
|
||||
|
||||
read = ifs.Read(buffer, 0, buffer.Length);
|
||||
if (read == 0)
|
||||
break;
|
||||
|
||||
ofs.Write(buffer, 0, read);
|
||||
ofs.Flush();
|
||||
} while (read > 0);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Absorb the exception right now
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interleave two files into a single output
|
||||
/// </summary>
|
||||
/// <param name="even">First file to interleave</param>
|
||||
/// <param name="odd">Second file to interleave</param>
|
||||
/// <param name="output">Path to the output file</param>
|
||||
/// <param name="type"><see cref="BlockSize"> representing how to process the inputs</param>
|
||||
/// <returns>True if the files were interleaved successfully, false otherwise</returns>
|
||||
public static bool Interleave(string even, string odd, string output, BlockSize type)
|
||||
{
|
||||
// If either file does not exist
|
||||
if (!File.Exists(even) || !File.Exists(odd))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
// Get the input streams
|
||||
using var evenStream = File.Open(even, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
using var oddStream = File.Open(odd, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
|
||||
// Interleave the streams
|
||||
using var interleaved = Interleave(evenStream, oddStream, type);
|
||||
if (interleaved == null)
|
||||
return false;
|
||||
|
||||
// Open the output file
|
||||
using var outputStream = File.Open(output, FileMode.Create, FileAccess.Write, FileShare.None);
|
||||
|
||||
// Write the interleaved data
|
||||
interleaved.CopyTo(outputStream);
|
||||
outputStream.Flush();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Absorb all errors for now
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interleave two streams into a single output
|
||||
/// </summary>
|
||||
/// <param name="even">First stream to interleave</param>
|
||||
/// <param name="odd">Second stream to interleave</param>
|
||||
/// <param name="output">Path to the output file</param>
|
||||
/// <param name="type"><see cref="BlockSize"> representing how to process the inputs</param>
|
||||
/// <returns>A filled stream on success, null otherwise</returns>
|
||||
public static Stream? Interleave(Stream even, Stream odd, BlockSize type)
|
||||
{
|
||||
// If either stream is unreadable
|
||||
if (!even.CanRead || !odd.CanRead)
|
||||
return null;
|
||||
|
||||
// Get the number of bytes to process
|
||||
int byteCount = type switch
|
||||
{
|
||||
BlockSize.Byte => 1,
|
||||
BlockSize.Word => 2,
|
||||
BlockSize.Dword => 4,
|
||||
BlockSize.Qword => 8,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type)),
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
// Create an output stream
|
||||
var outputStream = new MemoryStream();
|
||||
|
||||
// Alternate between inputs during reading
|
||||
bool useEven = true;
|
||||
while (even.Position < even.Length || odd.Position < odd.Length)
|
||||
{
|
||||
byte[] read = new byte[byteCount];
|
||||
int actual = (useEven ? even : odd).Read(read, 0, byteCount);
|
||||
outputStream.Write(read, 0, actual);
|
||||
outputStream.Flush();
|
||||
useEven = !useEven;
|
||||
}
|
||||
|
||||
outputStream.Seek(0, SeekOrigin.Begin);
|
||||
return outputStream;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Absorb all errors for now
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
54
SabreTools.IO/Transform/Enums.cs
Normal file
54
SabreTools.IO/Transform/Enums.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
namespace SabreTools.IO.Transform
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines the block size of an operation
|
||||
/// </summary>
|
||||
public enum BlockSize
|
||||
{
|
||||
/// <summary>
|
||||
/// 1 byte blocks
|
||||
/// </summary>
|
||||
Byte = 1,
|
||||
|
||||
/// <summary>
|
||||
/// 2 byte blocks
|
||||
/// </summary>
|
||||
Word = 2,
|
||||
|
||||
/// <summary>
|
||||
/// 4 byte blocks
|
||||
/// </summary>
|
||||
Dword = 4,
|
||||
|
||||
/// <summary>
|
||||
/// 8 byte blocks
|
||||
/// </summary>
|
||||
Qword = 8,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines the swapping operation
|
||||
/// </summary>
|
||||
public enum Operation
|
||||
{
|
||||
/// <summary>
|
||||
/// Reverse endianness of each byte
|
||||
/// </summary>
|
||||
Bitswap,
|
||||
|
||||
/// <summary>
|
||||
/// Swap every 1 byte
|
||||
/// </summary>
|
||||
Byteswap,
|
||||
|
||||
/// <summary>
|
||||
/// Swap every 2 bytes
|
||||
/// </summary>
|
||||
Wordswap,
|
||||
|
||||
/// <summary>
|
||||
/// Swap every 2 bytes and bytes within the 2 bytes
|
||||
/// </summary>
|
||||
WordByteswap,
|
||||
}
|
||||
}
|
||||
177
SabreTools.IO/Transform/Split.cs
Normal file
177
SabreTools.IO/Transform/Split.cs
Normal file
@@ -0,0 +1,177 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace SabreTools.IO.Transform
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers to split inputs
|
||||
/// </summary>
|
||||
public static class Split
|
||||
{
|
||||
/// <summary>
|
||||
/// Split an input file into two outputs
|
||||
/// </summary>
|
||||
/// <param name="input">Input file name</param>
|
||||
/// <param name="outputDir">Path to the output directory</param>
|
||||
/// <param name="type"><see cref="BlockSize"> representing how to process the inputs</param>
|
||||
/// <returns>True if the file could be split, false otherwise</returns>
|
||||
public static bool BlockSplit(string input, string? outputDir, BlockSize type)
|
||||
{
|
||||
// If the file does not exist
|
||||
if (!File.Exists(input))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
// Get the input stream
|
||||
using var inputStream = File.Open(input, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
|
||||
// Split the stream
|
||||
if (!BlockSplit(inputStream, type, out Stream? evenStream, out Stream? oddStream))
|
||||
return false;
|
||||
else if (evenStream == null || oddStream == null)
|
||||
return false;
|
||||
|
||||
// Get the base filename for output files
|
||||
outputDir ??= Path.GetDirectoryName(input);
|
||||
string baseFilename = Path.GetFileName(input);
|
||||
if (!string.IsNullOrEmpty(outputDir))
|
||||
baseFilename = Path.Combine(outputDir, baseFilename);
|
||||
|
||||
// Create the output directory, if possible
|
||||
if (outputDir != null && !Directory.Exists(outputDir))
|
||||
Directory.CreateDirectory(outputDir);
|
||||
|
||||
// Open the output files
|
||||
using var outEvenStream = File.Open($"{baseFilename}.even", FileMode.Create, FileAccess.Write, FileShare.None);
|
||||
using var outOddStream = File.Open($"{baseFilename}.odd", FileMode.Create, FileAccess.Write, FileShare.None);
|
||||
|
||||
// Write the split data
|
||||
evenStream.CopyTo(outEvenStream);
|
||||
outEvenStream.Flush();
|
||||
oddStream.CopyTo(outOddStream);
|
||||
outOddStream.Flush();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Absorb all errors for now
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Split an input stream into two output streams
|
||||
/// </summary>
|
||||
/// <param name="input">Input stream</param>
|
||||
/// <param name="type"><see cref="BlockSize"> representing how to process the inputs</param>
|
||||
/// <param name="even">Even block output stream on success, null otherwise</param>
|
||||
/// <param name="odd">Odd block output stream on success, null otherwise</param>
|
||||
/// <returns>True if the stream could be split, false otherwise</returns>
|
||||
public static bool BlockSplit(Stream input, BlockSize type, out Stream? even, out Stream? odd)
|
||||
{
|
||||
// Set default values for the outputs
|
||||
even = null;
|
||||
odd = null;
|
||||
|
||||
// If the stream is unreadable
|
||||
if (!input.CanRead)
|
||||
return false;
|
||||
|
||||
// Get the number of bytes to process
|
||||
int byteCount = type switch
|
||||
{
|
||||
BlockSize.Byte => 1,
|
||||
BlockSize.Word => 2,
|
||||
BlockSize.Dword => 4,
|
||||
BlockSize.Qword => 8,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type)),
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
// Create the output streams
|
||||
even = new MemoryStream();
|
||||
odd = new MemoryStream();
|
||||
|
||||
// Alternate between inputs during reading
|
||||
bool useEven = true;
|
||||
while (input.Position < input.Length)
|
||||
{
|
||||
byte[] read = new byte[byteCount];
|
||||
int actual = input.Read(read, 0, byteCount);
|
||||
(useEven ? even : odd).Write(read, 0, actual);
|
||||
(useEven ? even : odd).Flush();
|
||||
useEven = !useEven;
|
||||
}
|
||||
|
||||
even.Seek(0, SeekOrigin.Begin);
|
||||
odd.Seek(0, SeekOrigin.Begin);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Absorb all errors for now
|
||||
even = null;
|
||||
odd = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Split an input file into files of up to <paramref name="size"/> bytes
|
||||
/// </summary>
|
||||
/// <param name="input">Input file name</param>
|
||||
/// <param name="outputDir">Path to the output directory</param>
|
||||
/// <param name="size">Maximum number of bytes to split on</param>
|
||||
/// <returns>True if the file could be split, false otherwise</returns>
|
||||
public static bool SizeSplit(string input, string? outputDir, int size)
|
||||
{
|
||||
// If the file does not exist
|
||||
if (!File.Exists(input))
|
||||
return false;
|
||||
|
||||
// If the size is invalid
|
||||
if (size <= 0)
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
// Get the input stream
|
||||
using var inputStream = File.Open(input, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
|
||||
// Get the base filename for output files
|
||||
outputDir ??= Path.GetDirectoryName(input);
|
||||
string baseFilename = Path.GetFileName(input);
|
||||
if (!string.IsNullOrEmpty(outputDir))
|
||||
baseFilename = Path.Combine(outputDir, baseFilename);
|
||||
|
||||
// Create the output directory, if possible
|
||||
if (outputDir != null && !Directory.Exists(outputDir))
|
||||
Directory.CreateDirectory(outputDir);
|
||||
|
||||
// Loop while there is data left
|
||||
int part = 0;
|
||||
while (inputStream.Position < inputStream.Length)
|
||||
{
|
||||
// Create the next output file
|
||||
using var partStream = File.Open($"{baseFilename}.{part++}", FileMode.Create, FileAccess.Write, FileShare.None);
|
||||
|
||||
// Process the next block of data
|
||||
byte[] data = new byte[size];
|
||||
int actual = inputStream.Read(data, 0, size);
|
||||
partStream.Write(data, 0, actual);
|
||||
partStream.Flush();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Absorb all errors for now
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
162
SabreTools.IO/Transform/Swap.cs
Normal file
162
SabreTools.IO/Transform/Swap.cs
Normal file
@@ -0,0 +1,162 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SabreTools.IO.Extensions;
|
||||
|
||||
namespace SabreTools.IO.Transform
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers to perform swapping operations
|
||||
/// </summary>
|
||||
public static class Swap
|
||||
{
|
||||
/// <summary>
|
||||
/// Transform an input file using the given rule
|
||||
/// </summary>
|
||||
/// <param name="input">Input file name</param>
|
||||
/// <param name="output">Output file name</param>
|
||||
/// <param name="operation">Transform operation to carry out</param>
|
||||
/// <returns>True if the file was transformed properly, false otherwise</returns>
|
||||
public static bool Process(string input, string output, Operation operation)
|
||||
{
|
||||
// If the file does not exist
|
||||
if (!File.Exists(input))
|
||||
return false;
|
||||
|
||||
// Create the output directory if it doesn't already
|
||||
string? outputDirectory = Path.GetDirectoryName(Path.GetFullPath(output));
|
||||
if (outputDirectory != null && !Directory.Exists(outputDirectory))
|
||||
Directory.CreateDirectory(outputDirectory);
|
||||
|
||||
try
|
||||
{
|
||||
// Get the input stream
|
||||
using var inputStream = File.Open(input, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
|
||||
// Transform the stream
|
||||
var transformed = Process(inputStream, operation);
|
||||
if (transformed == null)
|
||||
return false;
|
||||
|
||||
// Open the output file
|
||||
using var outputStream = File.Open(output, FileMode.Create, FileAccess.Write, FileShare.None);
|
||||
|
||||
// Write the transformed data
|
||||
transformed.CopyTo(outputStream);
|
||||
outputStream.Flush();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Absorb all errors for now
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transform an input stream using the given rule
|
||||
/// </summary>
|
||||
/// <param name="input">Input stream</param>
|
||||
/// <param name="operation">Transform operation to carry out</param>
|
||||
/// <returns>True if the file was transformed properly, false otherwise</returns>
|
||||
public static Stream? Process(Stream input, Operation operation)
|
||||
{
|
||||
// If the stream is unreadable
|
||||
if (!input.CanRead)
|
||||
return null;
|
||||
|
||||
// If the operation is not defined
|
||||
if (!Enum.IsDefined(typeof(Operation), operation))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
// Create an output stream
|
||||
var output = new MemoryStream();
|
||||
|
||||
// Determine the cutoff boundary for the operation
|
||||
long endBoundary = operation switch
|
||||
{
|
||||
Operation.Bitswap => input.Length,
|
||||
Operation.Byteswap => input.Length - (input.Length % 2),
|
||||
Operation.Wordswap => input.Length - (input.Length % 4),
|
||||
Operation.WordByteswap => input.Length - (input.Length % 4),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(operation)),
|
||||
};
|
||||
|
||||
// Loop over the input and process in blocks
|
||||
byte[] buffer = new byte[4];
|
||||
int pos = 0;
|
||||
while (input.Position < endBoundary)
|
||||
{
|
||||
byte b = input.ReadByteValue();
|
||||
switch (operation)
|
||||
{
|
||||
case Operation.Bitswap:
|
||||
uint r = b;
|
||||
int s = 7;
|
||||
for (b >>= 1; b != 0; b >>= 1)
|
||||
{
|
||||
r <<= 1;
|
||||
r |= (byte)(b & 1);
|
||||
s--;
|
||||
}
|
||||
|
||||
r <<= s;
|
||||
buffer[pos] = (byte)r;
|
||||
break;
|
||||
case Operation.Byteswap:
|
||||
if (pos % 2 == 1)
|
||||
buffer[pos - 1] = b;
|
||||
else
|
||||
buffer[pos + 1] = b;
|
||||
|
||||
break;
|
||||
case Operation.Wordswap:
|
||||
buffer[(pos + 2) % 4] = b;
|
||||
break;
|
||||
case Operation.WordByteswap:
|
||||
buffer[3 - pos] = b;
|
||||
break;
|
||||
default:
|
||||
buffer[pos] = b;
|
||||
break;
|
||||
}
|
||||
|
||||
// Set the buffer position to default write to
|
||||
pos = (pos + 1) % 4;
|
||||
|
||||
// If the buffer pointer has been reset
|
||||
if (pos == 0)
|
||||
{
|
||||
output.Write(buffer);
|
||||
output.Flush();
|
||||
buffer = new byte[4];
|
||||
}
|
||||
}
|
||||
|
||||
// If there's anything more in the buffer
|
||||
for (int i = 0; i < pos; i++)
|
||||
{
|
||||
output.Write(buffer[i]);
|
||||
}
|
||||
|
||||
// If the stream still has data
|
||||
if (input.Position < input.Length)
|
||||
{
|
||||
byte[] bytes = input.ReadBytes((int)(input.Length - input.Position));
|
||||
output.Write(bytes);
|
||||
output.Flush();
|
||||
}
|
||||
|
||||
output.Seek(0, SeekOrigin.Begin);
|
||||
return output;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Absorb all errors for now
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user