diff --git a/.editorconfig b/.editorconfig
index ea53a85b..ad69ddfa 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -267,6 +267,7 @@ dotnet_diagnostic.SA0001.severity = none # Disable documentation
dotnet_diagnostic.SA1402.severity = none # Multiple types in the same file
dotnet_diagnostic.SA1600.severity = none # Disable documentation
dotnet_diagnostic.SA1601.severity = none # Disable documentation
+dotnet_diagnostic.SA1602.severity = none # Disable documentation
dotnet_diagnostic.SA1201.severity = none # Allow enums inside classes
dotnet_diagnostic.S1144.severity = none # Remove unused setter
dotnet_diagnostic.S2094.severity = none # Remove empty class
diff --git a/build/orchestrator/BuildSystem.csproj b/build/orchestrator/BuildSystem.csproj
index ee8b40fb..4d63cfa9 100644
--- a/build/orchestrator/BuildSystem.csproj
+++ b/build/orchestrator/BuildSystem.csproj
@@ -11,7 +11,7 @@
-
+
diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
index 1d55a5b5..7616e76a 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -1,20 +1,17 @@
-
-
-
+
+
-
-
-
+
+
-
-
+
\ No newline at end of file
diff --git a/src/Yarhl.UnitTests/IO/DataReaderTests.cs b/src/Yarhl.UnitTests/IO/DataReaderTests.cs
index b1c2a5fc..cfacf4a6 100644
--- a/src/Yarhl.UnitTests/IO/DataReaderTests.cs
+++ b/src/Yarhl.UnitTests/IO/DataReaderTests.cs
@@ -33,13 +33,6 @@ public class DataReaderTests
DataStream stream;
DataReader reader;
- enum Enum1
- {
- Value1,
- Value2,
- Value3,
- }
-
[OneTimeSetUp]
public void FixtureSetUp()
{
@@ -911,570 +904,5 @@ public void ReadByTypeThrowExceptionForNullType()
stream.Position = 0;
Assert.Throws(() => reader.ReadByType((Type)null));
}
-
- [Test]
- public void ReadUsingReflection()
- {
- byte[] expected = {
- 0x01, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00,
- };
- stream.Write(expected, 0, expected.Length);
-
- stream.Position = 0;
-
- ComplexObject obj = reader.Read();
-
- Assert.AreEqual(1, obj.IntegerValue);
- Assert.AreEqual(2L, obj.LongValue);
- Assert.AreEqual(0, obj.IgnoredIntegerValue);
- Assert.AreEqual(3, obj.AnotherIntegerValue);
- }
-
- [Test]
- public void ReadNestedObjectUsingReflection()
- {
- byte[] expected = {
- 0x0A, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00,
- 0x14, 0x00, 0x00, 0x00,
- };
- stream.Write(expected, 0, expected.Length);
-
- stream.Position = 0;
-
- NestedObject obj = reader.Read();
-
- Assert.AreEqual(10, obj.IntegerValue);
- Assert.AreEqual(1, obj.ComplexValue.IntegerValue);
- Assert.AreEqual(2L, obj.ComplexValue.LongValue);
- Assert.AreEqual(0, obj.ComplexValue.IgnoredIntegerValue);
- Assert.AreEqual(3, obj.ComplexValue.AnotherIntegerValue);
- Assert.AreEqual(20, obj.AnotherIntegerValue);
- }
-
- [Test]
- public void ReadBooleanUsingReflection()
- {
- byte[] expected = {
- 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00,
- };
- stream.Write(expected, 0, expected.Length);
-
- stream.Position = 0;
-
- ObjectWithDefaultBooleanAttribute obj = reader.Read();
-
- Assert.AreEqual(1, obj.IntegerValue);
- Assert.AreEqual(false, obj.BooleanValue);
- Assert.AreEqual(0, obj.IgnoredIntegerValue);
- Assert.AreEqual(3, obj.AnotherIntegerValue);
- }
-
- [Test]
- public void ReadCustomBooleanUsingReflection()
- {
- byte[] expected = {
- 0x01, 0x00, 0x00, 0x00,
- 0x74, 0x72, 0x75, 0x65, 0x00, // "true"
- 0x03, 0x00, 0x00, 0x00,
- };
- stream.Write(expected, 0, expected.Length);
-
- stream.Position = 0;
-
- ObjectWithCustomBooleanAttribute obj = reader.Read();
-
- Assert.AreEqual(1, obj.IntegerValue);
- Assert.AreEqual(true, obj.BooleanValue);
- Assert.AreEqual(0, obj.IgnoredIntegerValue);
- Assert.AreEqual(3, obj.AnotherIntegerValue);
- }
-
- [Test]
- public void ReadBooleanWithoutAttributeThrowsException()
- {
- byte[] expected = {
- 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00,
- };
- stream.Write(expected, 0, expected.Length);
-
- stream.Position = 0;
-
- Assert.Throws(() => reader.Read());
- }
-
- [Test]
- public void ReadStringWithoutAttributeUsesDefaultReaderSettings()
- {
- byte[] expected = {
- 0x01, 0x00, 0x00, 0x00,
- 0xE3, 0x81, 0x82, 0xE3, 0x82, 0xA2, 0x00,
- 0x03, 0x00, 0x00, 0x00,
- };
- stream.Write(expected, 0, expected.Length);
-
- stream.Position = 0;
-
- ObjectWithoutStringAttribute obj = reader.Read();
-
- Assert.AreEqual(1, obj.IntegerValue);
- Assert.AreEqual("あア", obj.StringValue);
- Assert.AreEqual(0, obj.IgnoredIntegerValue);
- Assert.AreEqual(3, obj.AnotherIntegerValue);
- }
-
- [Test]
- public void ReadStringWithDefaultAttributeUsesDefaultReaderSettings()
- {
- byte[] expected = {
- 0x01, 0x00, 0x00, 0x00,
- 0xE3, 0x81, 0x82, 0xE3, 0x82, 0xA2, 0x00,
- 0x03, 0x00, 0x00, 0x00,
- };
- stream.Write(expected, 0, expected.Length);
-
- stream.Position = 0;
-
- ObjectWithDefaultStringAttribute obj = reader.Read();
-
- Assert.AreEqual(1, obj.IntegerValue);
- Assert.AreEqual("あア", obj.StringValue);
- Assert.AreEqual(0, obj.IgnoredIntegerValue);
- Assert.AreEqual(3, obj.AnotherIntegerValue);
- }
-
- [Test]
- public void ReadCustomStringWithSizeTypeUsingReflection()
- {
- byte[] expected = {
- 0x01, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0xE3, 0x81, 0x82,
- 0x04, 0x00, 0x00, 0x00,
- };
- stream.Write(expected, 0, expected.Length);
-
- stream.Position = 0;
-
- ObjectWithCustomStringAttributeSizeUshort obj = reader.Read();
-
- Assert.AreEqual(1, obj.IntegerValue);
- Assert.AreEqual("あ", obj.StringValue);
- Assert.AreEqual(0, obj.IgnoredIntegerValue);
- Assert.AreEqual(4, obj.AnotherIntegerValue);
- }
-
- [Test]
- public void ReadCustomFixedStringUsingReflection()
- {
- byte[] expected = {
- 0x01, 0x00, 0x00, 0x00,
- 0xE3, 0x81, 0x82,
- 0x03, 0x00, 0x00, 0x00,
- };
- stream.Write(expected, 0, expected.Length);
-
- stream.Position = 0;
-
- ObjectWithCustomStringAttributeFixedSize obj = reader.Read();
-
- Assert.AreEqual(1, obj.IntegerValue);
- Assert.AreEqual("あ", obj.StringValue);
- Assert.AreEqual(0, obj.IgnoredIntegerValue);
- Assert.AreEqual(3, obj.AnotherIntegerValue);
- }
-
- [Test]
- public void ReadCustomStringUsingReflectionWithDifferentEncoding()
- {
- byte[] expected = {
- 0x01, 0x00, 0x00, 0x00,
- 0x82, 0xA0, 0x83, 0x41, 0x00,
- 0x03, 0x00, 0x00, 0x00,
- };
- stream.Write(expected, 0, expected.Length);
-
- stream.Position = 0;
-
- ObjectWithCustomStringAttributeCustomEncoding obj = reader.Read();
-
- Assert.AreEqual(1, obj.IntegerValue);
- Assert.AreEqual("あア", obj.StringValue);
- Assert.AreEqual(0, obj.IgnoredIntegerValue);
- Assert.AreEqual(3, obj.AnotherIntegerValue);
- }
-
- [Test]
- public void ReadCustomStringUsingReflectionWithUnknownEncodingThrowsException()
- {
- byte[] expected = {
- 0x01, 0x00, 0x00, 0x00,
- 0x82, 0xA0, 0x83, 0x41, 0x00,
- 0x03, 0x00, 0x00, 0x00,
- };
- stream.Write(expected, 0, expected.Length);
-
- stream.Position = 0;
-
- Assert.Throws(() => reader.Read());
- }
-
- [Test]
- public void ReadObjectWithForcedEndianness()
- {
- byte[] expected = {
- 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x02,
- 0x03, 0x00, 0x00, 0x00,
- };
- stream.Write(expected, 0, expected.Length);
-
- stream.Position = 0;
-
- ObjectWithForcedEndianness obj = reader.Read();
-
- Assert.AreEqual(1, obj.LittleEndianInteger);
- Assert.AreEqual(2, obj.BigEndianInteger);
- Assert.AreEqual(3, obj.DefaultEndianInteger);
- }
-
- [Test]
- public void ReadObjectWithEnumValue()
- {
- byte[] expected = {
- 0x01,
- };
- stream.Write(expected, 0, expected.Length);
-
- stream.Position = 0;
-
- ObjectWithEnum obj = reader.Read();
-
- Assert.AreEqual(Enum1.Value2, obj.EnumValue);
- }
-
- [Test]
- public void ReadObjectWithInt24()
- {
- byte[] expected = {
- 0x01, 0x00, 0x00,
- };
- stream.Write(expected, 0, expected.Length);
-
- stream.Position = 0;
-
- ObjectWithInt24 obj = reader.Read();
-
- Assert.AreEqual(1, obj.Int24Value);
- }
-
- [Test]
- public void ReflectionReadingDoesNotSupportNullable()
- {
- stream.Write(new byte[4], 0, 4);
- stream.Position = 0;
-
- Assert.That(
- () => reader.Read(),
- Throws.InstanceOf());
- }
-
- [System.Diagnostics.CodeAnalysis.SuppressMessage(
- "Microsoft.Performance",
- "CA1812:Class never instantiated",
- Justification = "The class is instantiated by reflection")]
- [System.Diagnostics.CodeAnalysis.SuppressMessage(
- "Sonar.CodeSmell",
- "S3459:Unassigned auto-property",
- Justification = "The properties are assigned by reflection")]
- [Yarhl.IO.Serialization.Attributes.Serializable]
- private class ComplexObject
- {
- public int IntegerValue { get; set; }
-
- public long LongValue { get; set; }
-
- [BinaryIgnore]
- public int IgnoredIntegerValue { get; set; }
-
- public int AnotherIntegerValue { get; set; }
- }
-
- [System.Diagnostics.CodeAnalysis.SuppressMessage(
- "Microsoft.Performance",
- "CA1812:Class never instantiated",
- Justification = "The class is instantiated by reflection")]
- [System.Diagnostics.CodeAnalysis.SuppressMessage(
- "Sonar.CodeSmell",
- "S3459:Unassigned auto-property",
- Justification = "The properties are assigned by reflection")]
- [Yarhl.IO.Serialization.Attributes.Serializable]
- private class NestedObject
- {
- public int IntegerValue { get; set; }
-
- public ComplexObject ComplexValue { get; set; }
-
- public int AnotherIntegerValue { get; set; }
- }
-
- [System.Diagnostics.CodeAnalysis.SuppressMessage(
- "Microsoft.Performance",
- "CA1812:Class never instantiated",
- Justification = "The class is instantiated by reflection")]
- [System.Diagnostics.CodeAnalysis.SuppressMessage(
- "Sonar.CodeSmell",
- "S3459:Unassigned auto-property",
- Justification = "The properties are assigned by reflection")]
- [Yarhl.IO.Serialization.Attributes.Serializable]
- private class ObjectWithDefaultBooleanAttribute
- {
- public int IntegerValue { get; set; }
-
- [BinaryBoolean]
- public bool BooleanValue { get; set; }
-
- [BinaryIgnore]
- public int IgnoredIntegerValue { get; set; }
-
- public int AnotherIntegerValue { get; set; }
- }
-
- [System.Diagnostics.CodeAnalysis.SuppressMessage(
- "Microsoft.Performance",
- "CA1812:Class never instantiated",
- Justification = "The class is instantiated by reflection")]
- [System.Diagnostics.CodeAnalysis.SuppressMessage(
- "Sonar.CodeSmell",
- "S3459:Unassigned auto-property",
- Justification = "The properties are assigned by reflection")]
- [Yarhl.IO.Serialization.Attributes.Serializable]
- private class ObjectWithoutBooleanAttribute
- {
- public int IntegerValue { get; set; }
-
- public bool BooleanValue { get; set; }
-
- [BinaryIgnore]
- public int IgnoredIntegerValue { get; set; }
-
- public int AnotherIntegerValue { get; set; }
- }
-
- [System.Diagnostics.CodeAnalysis.SuppressMessage(
- "Microsoft.Performance",
- "CA1812:Class never instantiated",
- Justification = "The class is instantiated by reflection")]
- [System.Diagnostics.CodeAnalysis.SuppressMessage(
- "Sonar.CodeSmell",
- "S3459:Unassigned auto-property",
- Justification = "The properties are assigned by reflection")]
- [Yarhl.IO.Serialization.Attributes.Serializable]
- private class ObjectWithCustomBooleanAttribute
- {
- public int IntegerValue { get; set; }
-
- [BinaryBoolean(ReadAs = typeof(string), TrueValue = "true")]
- public bool BooleanValue { get; set; }
-
- [BinaryIgnore]
- public int IgnoredIntegerValue { get; set; }
-
- public int AnotherIntegerValue { get; set; }
- }
-
- [System.Diagnostics.CodeAnalysis.SuppressMessage(
- "Microsoft.Performance",
- "CA1812:Class never instantiated",
- Justification = "The class is instantiated by reflection")]
- [System.Diagnostics.CodeAnalysis.SuppressMessage(
- "Sonar.CodeSmell",
- "S3459:Unassigned auto-property",
- Justification = "The properties are assigned by reflection")]
- [Yarhl.IO.Serialization.Attributes.Serializable]
- private class ObjectWithDefaultStringAttribute
- {
- public int IntegerValue { get; set; }
-
- [BinaryString]
- public string StringValue { get; set; }
-
- [BinaryIgnore]
- public int IgnoredIntegerValue { get; set; }
-
- public int AnotherIntegerValue { get; set; }
- }
-
- [System.Diagnostics.CodeAnalysis.SuppressMessage(
- "Microsoft.Performance",
- "CA1812:Class never instantiated",
- Justification = "The class is instantiated by reflection")]
- [System.Diagnostics.CodeAnalysis.SuppressMessage(
- "Sonar.CodeSmell",
- "S3459:Unassigned auto-property",
- Justification = "The properties are assigned by reflection")]
- [Yarhl.IO.Serialization.Attributes.Serializable]
- private class ObjectWithoutStringAttribute
- {
- public int IntegerValue { get; set; }
-
- public string StringValue { get; set; }
-
- [BinaryIgnore]
- public int IgnoredIntegerValue { get; set; }
-
- public int AnotherIntegerValue { get; set; }
- }
-
- [System.Diagnostics.CodeAnalysis.SuppressMessage(
- "Microsoft.Performance",
- "CA1812:Class never instantiated",
- Justification = "The class is instantiated by reflection")]
- [System.Diagnostics.CodeAnalysis.SuppressMessage(
- "Sonar.CodeSmell",
- "S3459:Unassigned auto-property",
- Justification = "The properties are assigned by reflection")]
- [Yarhl.IO.Serialization.Attributes.Serializable]
- private class ObjectWithCustomStringAttributeSizeUshort
- {
- public int IntegerValue { get; set; }
-
- [BinaryString(SizeType = typeof(ushort), Terminator = "")]
- public string StringValue { get; set; }
-
- [BinaryIgnore]
- public int IgnoredIntegerValue { get; set; }
-
- public int AnotherIntegerValue { get; set; }
- }
-
- [System.Diagnostics.CodeAnalysis.SuppressMessage(
- "Microsoft.Performance",
- "CA1812:Class never instantiated",
- Justification = "The class is instantiated by reflection")]
- [System.Diagnostics.CodeAnalysis.SuppressMessage(
- "Sonar.CodeSmell",
- "S3459:Unassigned auto-property",
- Justification = "The properties are assigned by reflection")]
- [Yarhl.IO.Serialization.Attributes.Serializable]
- private class ObjectWithCustomStringAttributeFixedSize
- {
- public int IntegerValue { get; set; }
-
- [BinaryString(FixedSize = 3, Terminator = "")]
- public string StringValue { get; set; }
-
- [BinaryIgnore]
- public int IgnoredIntegerValue { get; set; }
-
- public int AnotherIntegerValue { get; set; }
- }
-
- [System.Diagnostics.CodeAnalysis.SuppressMessage(
- "Microsoft.Performance",
- "CA1812:Class never instantiated",
- Justification = "The class is instantiated by reflection")]
- [System.Diagnostics.CodeAnalysis.SuppressMessage(
- "Sonar.CodeSmell",
- "S3459:Unassigned auto-property",
- Justification = "The properties are assigned by reflection")]
- [Yarhl.IO.Serialization.Attributes.Serializable]
- private class ObjectWithCustomStringAttributeCustomEncoding
- {
- public int IntegerValue { get; set; }
-
- [BinaryString(CodePage = 932)]
- public string StringValue { get; set; }
-
- [BinaryIgnore]
- public int IgnoredIntegerValue { get; set; }
-
- public int AnotherIntegerValue { get; set; }
- }
-
- [System.Diagnostics.CodeAnalysis.SuppressMessage(
- "Microsoft.Performance",
- "CA1812:Class never instantiated",
- Justification = "The class is instantiated by reflection")]
- [System.Diagnostics.CodeAnalysis.SuppressMessage(
- "Sonar.CodeSmell",
- "S3459:Unassigned auto-property",
- Justification = "The properties are assigned by reflection")]
- [Yarhl.IO.Serialization.Attributes.Serializable]
- private class ObjectWithCustomStringAttributeUnknownEncoding
- {
- public int IntegerValue { get; set; }
-
- [BinaryString(CodePage = 666)]
- public string StringValue { get; set; }
-
- [BinaryIgnore]
- public int IgnoredIntegerValue { get; set; }
-
- public int AnotherIntegerValue { get; set; }
- }
-
- [System.Diagnostics.CodeAnalysis.SuppressMessage(
- "Microsoft.Performance",
- "CA1812:Class never instantiated",
- Justification = "The class is instantiated by reflection")]
- [System.Diagnostics.CodeAnalysis.SuppressMessage(
- "Sonar.CodeSmell",
- "S3459:Unassigned auto-property",
- Justification = "The properties are assigned by reflection")]
- [Yarhl.IO.Serialization.Attributes.Serializable]
- private class ObjectWithForcedEndianness
- {
- [BinaryForceEndianness(EndiannessMode.LittleEndian)]
- public int LittleEndianInteger { get; set; }
-
- [BinaryForceEndianness(EndiannessMode.BigEndian)]
- public int BigEndianInteger { get; set; }
-
- public int DefaultEndianInteger { get; set; }
- }
-
- [System.Diagnostics.CodeAnalysis.SuppressMessage(
- "Microsoft.Performance",
- "CA1812:Class never instantiated",
- Justification = "The class is instantiated by reflection")]
- [System.Diagnostics.CodeAnalysis.SuppressMessage(
- "Sonar.CodeSmell",
- "S3459:Unassigned auto-property",
- Justification = "The properties are assigned by reflection")]
- [Yarhl.IO.Serialization.Attributes.Serializable]
- private class ObjectWithEnum
- {
- [BinaryEnum(ReadAs = typeof(byte))]
- public Enum1 EnumValue { get; set; }
- }
-
- [System.Diagnostics.CodeAnalysis.SuppressMessage(
- "Microsoft.Performance",
- "CA1812:Class never instantiated",
- Justification = "The class is instantiated by reflection")]
- [System.Diagnostics.CodeAnalysis.SuppressMessage(
- "Sonar.CodeSmell",
- "S3459:Unassigned auto-property",
- Justification = "The properties are assigned by reflection")]
- [Yarhl.IO.Serialization.Attributes.Serializable]
- private class ObjectWithInt24
- {
- [BinaryInt24]
- public int Int24Value { get; set; }
- }
-
- [Yarhl.IO.Serialization.Attributes.Serializable]
- private class ObjectWithNullable
- {
- public int? NullValue { get; set; }
- }
}
}
diff --git a/src/Yarhl.UnitTests/IO/DataWriterTests.cs b/src/Yarhl.UnitTests/IO/DataWriterTests.cs
index 55e5f2b0..6f25c0b5 100644
--- a/src/Yarhl.UnitTests/IO/DataWriterTests.cs
+++ b/src/Yarhl.UnitTests/IO/DataWriterTests.cs
@@ -25,18 +25,10 @@ namespace Yarhl.UnitTests.IO
using System.Text;
using NUnit.Framework;
using Yarhl.IO;
- using Yarhl.IO.Serialization.Attributes;
[TestFixture]
public class DataWriterTests
{
- enum Enum1
- {
- Value1,
- Value2,
- Value3,
- }
-
[Test]
public void ConstructorSetProperties()
{
@@ -225,6 +217,56 @@ public void WriteUIntBig()
Assert.AreEqual(0xBE, stream.ReadByte());
}
+ [Test]
+ public void WriteInt24Big()
+ {
+ int value = 0x7F_FC0FFE;
+ byte[] expected = {
+ 0xFC, 0x0F, 0xFE,
+ };
+
+ using var stream = new DataStream();
+ var writer = new DataWriter(stream);
+ writer.Endianness = EndiannessMode.BigEndian;
+
+ writer.WriteInt24(value);
+
+ byte[] actual = new byte[expected.Length];
+ stream.Position = 0;
+ int read = stream.Read(actual);
+
+ Assert.Multiple(() => {
+ Assert.AreEqual(expected.Length, stream.Length);
+ Assert.That(read, Is.EqualTo(expected.Length));
+ Assert.That(expected, Is.EquivalentTo(actual));
+ });
+ }
+
+ [Test]
+ public void WriteInt24Little()
+ {
+ int value = 0x7F_FC0FFE;
+ byte[] expected = {
+ 0xFE, 0x0F, 0xFC,
+ };
+
+ using var stream = new DataStream();
+ var writer = new DataWriter(stream);
+ writer.Endianness = EndiannessMode.LittleEndian;
+
+ writer.WriteInt24(value);
+
+ byte[] actual = new byte[expected.Length];
+ stream.Position = 0;
+ int read = stream.Read(actual);
+
+ Assert.Multiple(() => {
+ Assert.AreEqual(expected.Length, stream.Length);
+ Assert.That(read, Is.EqualTo(expected.Length));
+ Assert.That(expected, Is.EquivalentTo(actual));
+ });
+ }
+
[Test]
public void WriteIntLittle()
{
@@ -1270,543 +1312,5 @@ public void WritePaddingLessEqualOneDoesNothing()
stream.Read(actual, 0, expected.Length);
Assert.IsTrue(expected.SequenceEqual(actual));
}
-
- [Test]
- public void WriteUsingReflection()
- {
- var obj = new ComplexObject {
- IntegerValue = 1,
- LongValue = 2,
- IgnoredIntegerValue = 3,
- AnotherIntegerValue = 4,
- };
-
- using DataStream stream = new DataStream();
- DataWriter writer = new DataWriter(stream);
-
- writer.WriteOfType(obj);
-
- byte[] expected = {
- 0x01, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00,
- };
- Assert.AreEqual(expected.Length, stream.Length);
-
- stream.Position = 0;
- byte[] actual = new byte[expected.Length];
- stream.Read(actual, 0, expected.Length);
- Assert.IsTrue(expected.SequenceEqual(actual));
- }
-
- [Test]
- public void WriteNestedObjectUsingReflection()
- {
- var obj = new NestedObject() {
- IntegerValue = 10,
- ComplexValue = new ComplexObject {
- IntegerValue = 1,
- LongValue = 2,
- IgnoredIntegerValue = 3,
- AnotherIntegerValue = 4,
- },
- AnotherIntegerValue = 20,
- };
-
- using DataStream stream = new DataStream();
- DataWriter writer = new DataWriter(stream);
-
- writer.WriteOfType(obj);
-
- byte[] expected = {
- 0x0A, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00,
- 0x14, 0x00, 0x00, 0x00,
- };
- Assert.AreEqual(expected.Length, stream.Length);
-
- stream.Position = 0;
- byte[] actual = new byte[expected.Length];
- stream.Read(actual, 0, expected.Length);
- Assert.IsTrue(expected.SequenceEqual(actual));
- }
-
- [Test]
- public void WriteBooleanUsingReflection()
- {
- var obj = new ObjectWithDefaultBooleanAttribute() {
- IntegerValue = 1,
- BooleanValue = false,
- IgnoredIntegerValue = 3,
- AnotherIntegerValue = 4,
- };
-
- using DataStream stream = new DataStream();
- DataWriter writer = new DataWriter(stream);
-
- writer.WriteOfType(obj);
-
- byte[] expected = {
- 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00,
- };
- Assert.AreEqual(expected.Length, stream.Length);
-
- stream.Position = 0;
- byte[] actual = new byte[expected.Length];
- stream.Read(actual, 0, expected.Length);
- Assert.IsTrue(expected.SequenceEqual(actual));
- }
-
- [Test]
- public void WriteCustomBooleanUsingReflection()
- {
- var obj = new ObjectWithCustomBooleanAttribute() {
- IntegerValue = 1,
- BooleanValue = false,
- IgnoredIntegerValue = 5,
- AnotherIntegerValue = 4,
- };
-
- using DataStream stream = new DataStream();
- DataWriter writer = new DataWriter(stream);
-
- writer.WriteOfType(obj);
-
- byte[] expected = {
- 0x01, 0x00, 0x00, 0x00,
- 0x66, 0x61, 0x6C, 0x73, 0x65, 0x00, // "false"
- 0x04, 0x00, 0x00, 0x00,
- };
- Assert.AreEqual(expected.Length, stream.Length);
-
- stream.Position = 0;
- byte[] actual = new byte[expected.Length];
- stream.Read(actual, 0, expected.Length);
- Assert.IsTrue(expected.SequenceEqual(actual));
- }
-
- [Test]
- public void WriteBooleanWithoutAttributeThrowsException()
- {
- var obj = new ObjectWithoutBooleanAttribute() {
- IntegerValue = 1,
- BooleanValue = true,
- IgnoredIntegerValue = 3,
- AnotherIntegerValue = 4,
- };
-
- using DataStream stream = new DataStream();
- DataWriter writer = new DataWriter(stream);
-
- Assert.Throws(
- () => writer.WriteOfType(obj));
- }
-
- [Test]
- public void WriteStringWithoutAttributeUsesDefaultWriterSettings()
- {
- var obj = new ObjectWithoutStringAttribute {
- IntegerValue = 1,
- StringValue = "あア",
- IgnoredIntegerValue = 2,
- AnotherIntegerValue = 3,
- };
-
- using DataStream stream = new DataStream();
- DataWriter writer = new DataWriter(stream);
-
- writer.WriteOfType(obj);
-
- byte[] expected = {
- 0x01, 0x00, 0x00, 0x00,
- 0xE3, 0x81, 0x82, 0xE3, 0x82, 0xA2, 0x00,
- 0x03, 0x00, 0x00, 0x00,
- };
- Assert.AreEqual(expected.Length, stream.Length);
-
- stream.Position = 0;
- byte[] actual = new byte[expected.Length];
- stream.Read(actual, 0, expected.Length);
- Assert.IsTrue(expected.SequenceEqual(actual));
- }
-
- [Test]
- public void WriteStringWithDefaultAttributeUsesDefaultWriterSettings()
- {
- var obj = new ObjectWithDefaultStringAttribute() {
- IntegerValue = 1,
- StringValue = "あア",
- IgnoredIntegerValue = 2,
- AnotherIntegerValue = 3,
- };
-
- using DataStream stream = new DataStream();
- DataWriter writer = new DataWriter(stream);
-
- writer.WriteOfType(obj);
-
- byte[] expected = {
- 0x01, 0x00, 0x00, 0x00,
- 0xE3, 0x81, 0x82, 0xE3, 0x82, 0xA2, 0x00,
- 0x03, 0x00, 0x00, 0x00,
- };
- Assert.AreEqual(expected.Length, stream.Length);
-
- stream.Position = 0;
- byte[] actual = new byte[expected.Length];
- stream.Read(actual, 0, expected.Length);
- Assert.IsTrue(expected.SequenceEqual(actual));
- }
-
- [Test]
- public void WriteCustomStringWithSizeTypeUsingReflection()
- {
- var obj = new ObjectWithCustomStringAttributeSizeUshort() {
- IntegerValue = 1,
- StringValue = "あ",
- IgnoredIntegerValue = 2,
- AnotherIntegerValue = 4,
- };
-
- using DataStream stream = new DataStream();
- DataWriter writer = new DataWriter(stream);
-
- writer.WriteOfType(obj);
-
- byte[] expected = {
- 0x01, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0xE3, 0x81, 0x82,
- 0x04, 0x00, 0x00, 0x00,
- };
- Assert.AreEqual(expected.Length, stream.Length);
-
- stream.Position = 0;
- byte[] actual = new byte[expected.Length];
- stream.Read(actual, 0, expected.Length);
- Assert.IsTrue(expected.SequenceEqual(actual));
- }
-
- [Test]
- public void WriteCustomFixedStringUsingReflection()
- {
- var obj = new ObjectWithCustomStringAttributeFixedSize() {
- IntegerValue = 1,
- StringValue = "あ",
- IgnoredIntegerValue = 2,
- AnotherIntegerValue = 4,
- };
-
- using DataStream stream = new DataStream();
- DataWriter writer = new DataWriter(stream);
-
- writer.WriteOfType(obj);
-
- byte[] expected = {
- 0x01, 0x00, 0x00, 0x00,
- 0xE3, 0x81, 0x82,
- 0x04, 0x00, 0x00, 0x00,
- };
- Assert.AreEqual(expected.Length, stream.Length);
-
- stream.Position = 0;
- byte[] actual = new byte[expected.Length];
- stream.Read(actual, 0, expected.Length);
- Assert.IsTrue(expected.SequenceEqual(actual));
- }
-
- [Test]
- public void WriteCustomStringUsingReflectionWithDifferentEncoding()
- {
- var obj = new ObjectWithCustomStringAttributeCustomEncoding() {
- IntegerValue = 1,
- StringValue = "あア",
- IgnoredIntegerValue = 2,
- AnotherIntegerValue = 4,
- };
-
- using DataStream stream = new DataStream();
- DataWriter writer = new DataWriter(stream);
-
- writer.WriteOfType(obj);
-
- byte[] expected = {
- 0x01, 0x00, 0x00, 0x00,
- 0x82, 0xA0, 0x83, 0x41, 0x00,
- 0x04, 0x00, 0x00, 0x00,
- };
- Assert.AreEqual(expected.Length, stream.Length);
-
- stream.Position = 0;
- byte[] actual = new byte[expected.Length];
- stream.Read(actual, 0, expected.Length);
- Assert.IsTrue(expected.SequenceEqual(actual));
- }
-
- [Test]
- public void WriteCustomStringUsingReflectionWithUnknownEncodingThrowsException()
- {
- var obj = new ObjectWithCustomStringAttributeUnknownEncoding() {
- IntegerValue = 1,
- StringValue = "あア",
- IgnoredIntegerValue = 2,
- AnotherIntegerValue = 4,
- };
-
- using DataStream stream = new DataStream();
- DataWriter writer = new DataWriter(stream);
-
- Assert.Throws(
- () => writer.WriteOfType(obj));
- }
-
- [Test]
- public void WriteObjectWithForcedEndianness()
- {
- ObjectWithForcedEndianness obj = new ObjectWithForcedEndianness() {
- LittleEndianInteger = 1,
- BigEndianInteger = 2,
- DefaultEndianInteger = 3,
- };
-
- using DataStream stream = new DataStream();
- DataWriter writer = new DataWriter(stream);
-
- writer.WriteOfType(obj);
-
- byte[] expected = {
- 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x02,
- 0x03, 0x00, 0x00, 0x00,
- };
- Assert.AreEqual(expected.Length, stream.Length);
-
- stream.Position = 0;
- byte[] actual = new byte[expected.Length];
- stream.Read(actual, 0, expected.Length);
- Assert.IsTrue(expected.SequenceEqual(actual));
- }
-
- [Test]
- public void WriteObjectWithEnum()
- {
- ObjectWithEnum obj = new ObjectWithEnum() {
- EnumValue = Enum1.Value2,
- };
-
- using DataStream stream = new DataStream();
- DataWriter writer = new DataWriter(stream);
-
- writer.WriteOfType(obj);
-
- byte[] expected = {
- 0x01,
- };
- Assert.AreEqual(expected.Length, stream.Length);
-
- stream.Position = 0;
- byte[] actual = new byte[expected.Length];
- stream.Read(actual, 0, expected.Length);
- Assert.IsTrue(expected.SequenceEqual(actual));
- }
-
- [Test]
- public void WriteObjectWithInt24()
- {
- ObjectWithInt24 obj = new ObjectWithInt24() {
- Int24Value = 1,
- };
-
- using DataStream stream = new DataStream();
- DataWriter writer = new DataWriter(stream);
-
- writer.WriteOfType(obj);
-
- byte[] expected = {
- 0x01, 0x00, 0x00,
- };
- Assert.AreEqual(expected.Length, stream.Length);
-
- stream.Position = 0;
- byte[] actual = new byte[expected.Length];
- stream.Read(actual, 0, expected.Length);
- Assert.IsTrue(expected.SequenceEqual(actual));
- }
-
- [Yarhl.IO.Serialization.Attributes.Serializable]
- private class ComplexObject
- {
- public int IntegerValue { get; set; }
-
- public long LongValue { get; set; }
-
- [BinaryIgnore]
- public int IgnoredIntegerValue { get; set; }
-
- public int AnotherIntegerValue { get; set; }
- }
-
- [Yarhl.IO.Serialization.Attributes.Serializable]
- private class NestedObject
- {
- public int IntegerValue { get; set; }
-
- public ComplexObject ComplexValue { get; set; }
-
- public int AnotherIntegerValue { get; set; }
- }
-
- [Yarhl.IO.Serialization.Attributes.Serializable]
- private class ObjectWithDefaultBooleanAttribute
- {
- public int IntegerValue { get; set; }
-
- [BinaryBoolean]
- public bool BooleanValue { get; set; }
-
- [BinaryIgnore]
- public int IgnoredIntegerValue { get; set; }
-
- public int AnotherIntegerValue { get; set; }
- }
-
- [Yarhl.IO.Serialization.Attributes.Serializable]
- private class ObjectWithoutBooleanAttribute
- {
- public int IntegerValue { get; set; }
-
- public bool BooleanValue { get; set; }
-
- [BinaryIgnore]
- public int IgnoredIntegerValue { get; set; }
-
- public int AnotherIntegerValue { get; set; }
- }
-
- [Yarhl.IO.Serialization.Attributes.Serializable]
- private class ObjectWithCustomBooleanAttribute
- {
- public int IntegerValue { get; set; }
-
- [BinaryBoolean(WriteAs = typeof(string), TrueValue = "true", FalseValue = "false")]
- public bool BooleanValue { get; set; }
-
- [BinaryIgnore]
- public int IgnoredIntegerValue { get; set; }
-
- public int AnotherIntegerValue { get; set; }
- }
-
- [Yarhl.IO.Serialization.Attributes.Serializable]
- private class ObjectWithDefaultStringAttribute
- {
- public int IntegerValue { get; set; }
-
- [BinaryString]
- public string StringValue { get; set; }
-
- [BinaryIgnore]
- public int IgnoredIntegerValue { get; set; }
-
- public int AnotherIntegerValue { get; set; }
- }
-
- [Yarhl.IO.Serialization.Attributes.Serializable]
- private class ObjectWithoutStringAttribute
- {
- public int IntegerValue { get; set; }
-
- public string StringValue { get; set; }
-
- [BinaryIgnore]
- public int IgnoredIntegerValue { get; set; }
-
- public int AnotherIntegerValue { get; set; }
- }
-
- [Yarhl.IO.Serialization.Attributes.Serializable]
- private class ObjectWithCustomStringAttributeSizeUshort
- {
- public int IntegerValue { get; set; }
-
- [BinaryString(SizeType = typeof(ushort), Terminator = "")]
- public string StringValue { get; set; }
-
- [BinaryIgnore]
- public int IgnoredIntegerValue { get; set; }
-
- public int AnotherIntegerValue { get; set; }
- }
-
- [Yarhl.IO.Serialization.Attributes.Serializable]
- private class ObjectWithCustomStringAttributeFixedSize
- {
- public int IntegerValue { get; set; }
-
- [BinaryString(FixedSize = 3, Terminator = "")]
- public string StringValue { get; set; }
-
- [BinaryIgnore]
- public int IgnoredIntegerValue { get; set; }
-
- public int AnotherIntegerValue { get; set; }
- }
-
- [Yarhl.IO.Serialization.Attributes.Serializable]
- private class ObjectWithCustomStringAttributeCustomEncoding
- {
- public int IntegerValue { get; set; }
-
- [BinaryString(CodePage = 932)]
- public string StringValue { get; set; }
-
- [BinaryIgnore]
- public int IgnoredIntegerValue { get; set; }
-
- public int AnotherIntegerValue { get; set; }
- }
-
- [Yarhl.IO.Serialization.Attributes.Serializable]
- private class ObjectWithCustomStringAttributeUnknownEncoding
- {
- public int IntegerValue { get; set; }
-
- [BinaryString(CodePage = 666)]
- public string StringValue { get; set; }
-
- [BinaryIgnore]
- public int IgnoredIntegerValue { get; set; }
-
- public int AnotherIntegerValue { get; set; }
- }
-
- [Yarhl.IO.Serialization.Attributes.Serializable]
- private class ObjectWithForcedEndianness
- {
- [BinaryForceEndianness(EndiannessMode.LittleEndian)]
- public int LittleEndianInteger { get; set; }
-
- [BinaryForceEndianness(EndiannessMode.BigEndian)]
- public int BigEndianInteger { get; set; }
-
- public int DefaultEndianInteger { get; set; }
- }
-
- [Yarhl.IO.Serialization.Attributes.Serializable]
- private class ObjectWithEnum
- {
- [BinaryEnum(WriteAs = typeof(byte))]
- public Enum1 EnumValue { get; set; }
- }
-
- [Yarhl.IO.Serialization.Attributes.Serializable]
- private class ObjectWithInt24
- {
- [BinaryInt24]
- public int Int24Value { get; set; }
- }
}
}
diff --git a/src/Yarhl.UnitTests/IO/Serialization/BinaryDeserializerTests.cs b/src/Yarhl.UnitTests/IO/Serialization/BinaryDeserializerTests.cs
new file mode 100644
index 00000000..ad8bf8ab
--- /dev/null
+++ b/src/Yarhl.UnitTests/IO/Serialization/BinaryDeserializerTests.cs
@@ -0,0 +1,475 @@
+namespace Yarhl.UnitTests.IO.Serialization;
+
+using System;
+using FluentAssertions;
+using NUnit.Framework;
+using Yarhl.IO;
+using Yarhl.IO.Serialization;
+
+[TestFixture]
+public class BinaryDeserializerTests
+{
+ [Test]
+ public void DeserializeByGenericType()
+ {
+ byte[] data = { 0x0A, 0x00, 0x00, 0x00 };
+ var expected = new SimpleType { Value = 10 };
+ using var stream = new DataStream();
+ stream.Write(data);
+
+ stream.Position = 0;
+ var deserializer = new BinaryDeserializer(stream, new DefaultTypePropertyNavigator());
+ SimpleType obj = deserializer.Deserialize();
+
+ _ = obj.Should().BeEquivalentTo(expected);
+ }
+
+ [Test]
+ public void DeserializeByTypeArg()
+ {
+ byte[] data = { 0x0A, 0x00, 0x00, 0x00 };
+ var expected = new SimpleType { Value = 10 };
+ using var stream = new DataStream();
+ stream.Write(data);
+
+ stream.Position = 0;
+ var deserializer = new BinaryDeserializer(stream);
+ object obj = deserializer.Deserialize(typeof(SimpleType));
+
+ _ = obj.Should().BeEquivalentTo(expected);
+ }
+
+ [Test]
+ public void DeserializeStaticByGenericType()
+ {
+ byte[] data = { 0x0A, 0x00, 0x00, 0x00 };
+ var expected = new SimpleType { Value = 10 };
+ using var stream = new DataStream();
+ stream.Write(data);
+
+ stream.Position = 0;
+ SimpleType obj = BinaryDeserializer.Deserialize(stream);
+
+ _ = obj.Should().BeEquivalentTo(expected);
+ }
+
+ [Test]
+ public void DeserializeStaticByTypeArg()
+ {
+ byte[] data = { 0x0A, 0x00, 0x00, 0x00 };
+ var expected = new SimpleType { Value = 10 };
+ using var stream = new DataStream();
+ stream.Write(data);
+
+ stream.Position = 0;
+ object obj = BinaryDeserializer.Deserialize(stream, typeof(SimpleType));
+
+ _ = obj.Should().BeEquivalentTo(expected);
+ }
+
+ [Test]
+ public void DeserializeIncludesInheritedFields()
+ {
+ byte[] data = { 0x0A, 0x00, 0x00, 0x00, 0xFE, 0xCA };
+ var obj = new InheritedType { Value = 0x0A, NewValue = 0xCAFE };
+
+ AssertDeserialization(data, obj);
+ }
+
+ [Test]
+ public void DeserializeIntegerTypes()
+ {
+ byte[] data = {
+ 0xCE, 0xA9, // char un UTF-8 (default encoding)
+ 0x84,
+ 0xF4,
+ 0x7F, 0x80,
+ 0xF0, 0xFF,
+ 0x78, 0x56, 0x34, 0x12,
+ 0xD6, 0xFF, 0xFF, 0xFF,
+ 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
+ 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ };
+ var expected = new TypeWithIntegers {
+ CharValue = 'Ω',
+ ByteValue = 0x84,
+ SByteValue = -12,
+ UShortValue = 0x807F,
+ ShortValue = -16,
+ UIntValue = 0x12345678,
+ IntegerValue = -42,
+ ULongValue = 0x800000000000002A,
+ LongValue = -2L,
+ };
+
+ AssertDeserialization(data, expected);
+ }
+
+ [Test]
+ public void DeserializeDecimalTypes()
+ {
+ byte[] data = {
+ 0xC3, 0xF5, 0x48, 0x40,
+ 0x1F, 0x85, 0xEB, 0x51, 0xB8, 0x1E, 0x09, 0xC0,
+ };
+ var obj = new TypeWithDecimals {
+ SingleValue = 3.14f,
+ DoubleValue = -3.14d,
+ };
+
+ AssertDeserialization(data, obj);
+ }
+
+ [Test]
+ public void DeserializeMultiPropertyStruct()
+ {
+ byte[] data = {
+ 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ (byte)'y', (byte)'a', (byte)'r', (byte)'h', (byte)'l', (byte)'\0',
+ };
+ var expected = new MultiPropertyStruct {
+ IntegerValue = 1,
+ LongValue = 2L,
+ TextValue = "yarhl",
+ };
+
+ AssertDeserialization(data, expected);
+ }
+
+ [Test]
+ public void DeserializeNestedObject()
+ {
+ byte[] data = {
+ 0x0A, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00,
+ 0x14, 0x00, 0x00, 0x00,
+ };
+ var expected = new TypeWithNestedObject {
+ IntegerValue = 10,
+ ComplexValue = new TypeWithNestedObject.NestedType {
+ NestedValue = 1,
+ },
+ AnotherIntegerValue = 20,
+ };
+
+ AssertDeserialization(data, expected);
+ }
+
+ [Test]
+ public void DeserializeIgnorePropertiesViaAttribute()
+ {
+ byte[] data = {
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ };
+ var expected = new TypeWithIgnoredProperties {
+ LongValue = 2L,
+ IgnoredIntegerValue = 0,
+ };
+
+ AssertDeserialization(data, expected);
+ }
+
+ [Test]
+ public void DeserializeBooleanType()
+ {
+ byte[] data = {
+ 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00,
+ };
+
+ var expected = new TypeWithBooleanDefaultAttribute {
+ BeforeValue = 1,
+ BooleanValue = false,
+ AfterValue = 3,
+ };
+
+ AssertDeserialization(data, expected);
+
+ data[4] = 0x01;
+ expected.BooleanValue = true;
+ AssertDeserialization(data, expected);
+ }
+
+ [Test]
+ public void DeserializeBooleanWithDefinedValues()
+ {
+ var falseObj = new TypeWithBooleanDefinedValue() {
+ BeforeValue = 1,
+ BooleanValue = false,
+ AfterValue = 3,
+ };
+ byte[] serializedFalse = {
+ 0x01, 0x00, 0x00, 0x00,
+ 0xD6, 0xFF,
+ 0x03, 0x00, 0x00, 0x00,
+ };
+
+ var trueObj = new TypeWithBooleanDefinedValue() {
+ BeforeValue = 1,
+ BooleanValue = true,
+ AfterValue = 3,
+ };
+ byte[] serializedTrue = {
+ 0x01, 0x00, 0x00, 0x00,
+ 0x2A, 0x00,
+ 0x03, 0x00, 0x00, 0x00,
+ };
+
+ AssertDeserialization(serializedFalse, falseObj);
+ AssertDeserialization(serializedTrue, trueObj);
+ }
+
+ [Test]
+ public void DeserializeBooleanWithTextValues()
+ {
+ var falseObj = new TypeWithBooleanTextValue() {
+ BeforeValue = 1,
+ BooleanValue = false,
+ AfterValue = 3,
+ };
+ byte[] serializedFalse = {
+ 0x01, 0x00, 0x00, 0x00,
+ (byte)'f', (byte)'a', (byte)'l', (byte)'s', (byte)'e', (byte)'\0',
+ 0x03, 0x00, 0x00, 0x00,
+ };
+
+ var trueObj = new TypeWithBooleanTextValue() {
+ BeforeValue = 1,
+ BooleanValue = true,
+ AfterValue = 3,
+ };
+ byte[] serializedTrue = {
+ 0x01, 0x00, 0x00, 0x00,
+ (byte)'t', (byte)'r', (byte)'u', (byte)'e', (byte)'\0',
+ 0x03, 0x00, 0x00, 0x00,
+ };
+
+ AssertDeserialization(serializedFalse, falseObj);
+ AssertDeserialization(serializedTrue, trueObj);
+ }
+
+ [Test]
+ public void TryDeserializeBooleanWithoutAttributeThrowsException()
+ {
+ byte[] data = {
+ 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00,
+ };
+
+ var stream = new DataStream();
+ stream.Write(data);
+
+ stream.Position = 0;
+ var deserializer = new BinaryDeserializer(stream);
+ Assert.That(
+ () => deserializer.Deserialize(),
+ Throws.InstanceOf());
+ }
+
+ [Test]
+ public void DeserializeInt24()
+ {
+ byte[] data = {
+ 0x01, 0x00, 0x00,
+ };
+
+ var expected = new TypeWithInt24 {
+ Int24Value = 1,
+ };
+
+ AssertDeserialization(data, expected);
+ }
+
+ [Test]
+ public void DeserializeStringWithoutAttributeUsesDefaultReaderSettings()
+ {
+ byte[] data = {
+ 0x01, 0x00, 0x00, 0x00,
+ 0xE3, 0x81, 0x82, 0xE3, 0x82, 0xA2, 0x00,
+ 0x03, 0x00, 0x00, 0x00,
+ };
+
+ var expected = new TypeWithStringWithoutAttribute {
+ BeforeValue = 1,
+ StringValue = "あア",
+ AfterValue = 3,
+ };
+
+ AssertDeserialization(data, expected);
+ }
+
+ [Test]
+ public void DeserializeStringWithDefaultAttributeUsesDefaultReaderSettings()
+ {
+ byte[] data = {
+ 0x01, 0x00, 0x00, 0x00,
+ 0xE3, 0x81, 0x82, 0xE3, 0x82, 0xA2, 0x00,
+ 0x03, 0x00, 0x00, 0x00,
+ };
+
+ var expected = new TypeWithStringDefaultAttribute {
+ BeforeValue = 1,
+ StringValue = "あア",
+ AfterValue = 3,
+ };
+
+ AssertDeserialization(data, expected);
+ }
+
+ [Test]
+ public void DeserializeStringWithSizeType()
+ {
+ byte[] data = {
+ 0x01, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0xE3, 0x81, 0x82,
+ 0x04, 0x00, 0x00, 0x00,
+ };
+
+ var expected = new TypeWithStringVariableSize {
+ BeforeValue = 1,
+ StringValue = "あ",
+ AfterValue = 4,
+ };
+
+ AssertDeserialization(data, expected);
+ }
+
+ [Test]
+ public void DeserializeStringWithFixedSize()
+ {
+ byte[] data = {
+ 0x01, 0x00, 0x00, 0x00,
+ 0xE3, 0x81, 0x82,
+ 0x03, 0x00, 0x00, 0x00,
+ };
+
+ var expected = new TypeWithStringFixedSize {
+ BeforeValue = 1,
+ StringValue = "あ",
+ AfterValue = 3,
+ };
+
+ AssertDeserialization(data, expected);
+ }
+
+ [Test]
+ public void DeserializeStringWithDifferentEncoding()
+ {
+ byte[] data = {
+ 0x01, 0x00, 0x00, 0x00,
+ 0x82, 0xA0, 0x83, 0x41, 0x00,
+ 0x03, 0x00, 0x00, 0x00,
+ };
+
+ var expected = new TypeWithStringDefinedEncoding {
+ BeforeValue = 1,
+ StringValue = "あア",
+ AfterValue = 3,
+ };
+
+ AssertDeserialization(data, expected);
+ }
+
+ [Test]
+ public void TryDeserializeStringWithUnknownEncodingThrowsException()
+ {
+ byte[] data = {
+ 0x01, 0x00, 0x00, 0x00,
+ 0x82, 0xA0, 0x83, 0x41, 0x00,
+ 0x03, 0x00, 0x00, 0x00,
+ };
+
+ var stream = new DataStream();
+ stream.Write(data);
+
+ stream.Position = 0;
+ var deserializer = new BinaryDeserializer(stream);
+
+ Assert.That(
+ () => deserializer.Deserialize(),
+ Throws.InstanceOf());
+ }
+
+ [Test]
+ public void DeserializeObjectWithSpecificEndianness()
+ {
+ byte[] data = {
+ 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x02,
+ 0x03, 0x00, 0x00, 0x00,
+ };
+
+ var expected = new TypeWithEndiannessChanges {
+ LittleEndianInteger = 1,
+ BigEndianInteger = 2,
+ DefaultEndianInteger = 3,
+ };
+
+ AssertDeserialization(data, expected);
+ }
+
+ [Test]
+ public void DeserializeEnumNoAttribute()
+ {
+ byte[] data = { 0x2A, 0x00, };
+
+ var expected = new TypeWithEnumNoAttribute {
+ EnumValue = SerializableEnum.Value42,
+ };
+
+ AssertDeserialization(data, expected);
+ }
+
+ [Test]
+ public void DeserializeEnumDefaultAttribute()
+ {
+ byte[] data = { 0x2A, 0x00, };
+
+ var expected = new TypeWithEnumDefaultAttribute {
+ EnumValue = SerializableEnum.Value42,
+ };
+
+ AssertDeserialization(data, expected);
+ }
+
+ [Test]
+ public void DeserializeEnumOverwritingType()
+ {
+ byte[] data = { 0x01, 0x00, 0x00, 0x00 };
+
+ var expected = new TypeWithEnumWithOverwrittenType {
+ EnumValue = SerializableEnum.None,
+ };
+
+ AssertDeserialization(data, expected);
+ }
+
+ [Test]
+ public void TryDeserializeNullableThrowsException()
+ {
+ var stream = new DataStream();
+ stream.Write(new byte[4]);
+
+ stream.Position = 0;
+ var deserializer = new BinaryDeserializer(stream);
+
+ Assert.That(
+ () => deserializer.Deserialize(),
+ Throws.InstanceOf());
+ }
+
+ private static void AssertDeserialization(byte[] data, T expected)
+ {
+ using var stream = new DataStream();
+ stream.Write(data);
+
+ stream.Position = 0;
+ var deserializer = new BinaryDeserializer(stream);
+ T obj = deserializer.Deserialize();
+
+ _ = obj.Should().BeEquivalentTo(expected);
+ }
+}
diff --git a/src/Yarhl.UnitTests/IO/Serialization/BinarySerializableTypes.cs b/src/Yarhl.UnitTests/IO/Serialization/BinarySerializableTypes.cs
new file mode 100644
index 00000000..6b67857b
--- /dev/null
+++ b/src/Yarhl.UnitTests/IO/Serialization/BinarySerializableTypes.cs
@@ -0,0 +1,289 @@
+namespace Yarhl.UnitTests.IO.Serialization;
+
+using Yarhl.IO;
+using Yarhl.IO.Serialization.Attributes;
+
+// Disable file may only contain a single class since we aren't going
+// to create a file per test converter.
+#pragma warning disable SA1649 // File name match type name
+#pragma warning disable SA1124 // do not use regions - I would agree but too many types
+
+public class SimpleType
+{
+ [BinaryOrder(0)]
+ public int Value { get; set; }
+}
+
+public class InheritedType : SimpleType
+{
+ [BinaryOrder(1)]
+ public ushort NewValue { get; set; }
+}
+
+public struct MultiPropertyStruct
+{
+ [BinaryOrder(0)]
+ public int IntegerValue { get; set; }
+
+ [BinaryOrder(1)]
+ public long LongValue { get; set; }
+
+ [BinaryOrder(2)]
+ public string TextValue { get; set; }
+}
+
+public class TypeWithIgnoredProperties
+{
+ [BinaryOrder(0)]
+ public long LongValue { get; set; }
+
+ [BinaryIgnore]
+ public int IgnoredIntegerValue { get; set; }
+}
+
+public class TypeWithNestedObject
+{
+ [BinaryOrder(0)]
+ public int IntegerValue { get; set; }
+
+ [BinaryOrder(1)]
+ public NestedType ComplexValue { get; set; }
+
+ [BinaryOrder(2)]
+ public int AnotherIntegerValue { get; set; }
+
+ public sealed class NestedType
+ {
+ [BinaryOrder(0)]
+ public int NestedValue { get; set; }
+ }
+}
+
+public class TypeWithEndiannessChanges
+{
+ [BinaryOrder(0)]
+ [BinaryEndianness(EndiannessMode.LittleEndian)]
+ public int LittleEndianInteger { get; set; }
+
+ [BinaryOrder(1)]
+ [BinaryEndianness(EndiannessMode.BigEndian)]
+ public int BigEndianInteger { get; set; }
+
+ [BinaryOrder(2)]
+ public int DefaultEndianInteger { get; set; }
+}
+
+public class TypeWithNullable
+{
+ [BinaryOrder(0)]
+ public int? NullValue { get; set; }
+}
+
+#region Integer types
+public class TypeWithIntegers
+{
+ [BinaryOrder(0)]
+ public char CharValue { get; set; }
+
+ [BinaryOrder(1)]
+ public byte ByteValue { get; set; }
+
+ [BinaryOrder(2)]
+ public sbyte SByteValue { get; set; }
+
+ [BinaryOrder(3)]
+ public ushort UShortValue { get; set; }
+
+ [BinaryOrder(4)]
+ public short ShortValue { get; set; }
+
+ [BinaryOrder(5)]
+ public uint UIntValue { get; set; }
+
+ [BinaryOrder(6)]
+ public int IntegerValue { get; set; }
+
+ [BinaryOrder(7)]
+ public ulong ULongValue { get; set; }
+
+ [BinaryOrder(8)]
+ public long LongValue { get; set; }
+}
+
+public class TypeWithDecimals
+{
+ [BinaryOrder(0)]
+ public float SingleValue { get; set; }
+
+ [BinaryOrder(1)]
+ public double DoubleValue { get; set; }
+}
+
+public class TypeWithInt24
+{
+ [BinaryOrder(0)]
+ [BinaryInt24]
+ public int Int24Value { get; set; }
+}
+#endregion
+
+#region Boolean types
+public class TypeWithBooleanDefaultAttribute
+{
+ [BinaryOrder(0)]
+ public int BeforeValue { get; set; }
+
+ [BinaryOrder(1)]
+ [BinaryBoolean]
+ public bool BooleanValue { get; set; }
+
+ [BinaryOrder(2)]
+ public int AfterValue { get; set; }
+}
+
+public class TypeWithBooleanWithoutAttribute
+{
+ [BinaryOrder(0)]
+ public int BeforeValue { get; set; }
+
+ [BinaryOrder(1)]
+ public bool BooleanValue { get; set; }
+
+ [BinaryOrder(2)]
+ public int AfterValue { get; set; }
+}
+
+public class TypeWithBooleanDefinedValue
+{
+ [BinaryOrder(0)]
+ public int BeforeValue { get; set; }
+
+ [BinaryOrder(1)]
+ [BinaryBoolean(UnderlyingType = typeof(short), TrueValue = (short)42, FalseValue = (short)-42)]
+ public bool BooleanValue { get; set; }
+
+ [BinaryOrder(2)]
+ public int AfterValue { get; set; }
+}
+
+public class TypeWithBooleanTextValue
+{
+ [BinaryOrder(0)]
+ public int BeforeValue { get; set; }
+
+ [BinaryOrder(1)]
+ [BinaryBoolean(UnderlyingType = typeof(string), TrueValue = "true", FalseValue = "false")]
+ public bool BooleanValue { get; set; }
+
+ [BinaryOrder(2)]
+ public int AfterValue { get; set; }
+}
+#endregion
+
+#region String types
+public class TypeWithStringDefaultAttribute
+{
+ [BinaryOrder(0)]
+ public int BeforeValue { get; set; }
+
+ [BinaryOrder(1)]
+ [BinaryString]
+ public string StringValue { get; set; }
+
+ [BinaryOrder(2)]
+ public int AfterValue { get; set; }
+}
+
+public class TypeWithStringWithoutAttribute
+{
+ [BinaryOrder(0)]
+ public int BeforeValue { get; set; }
+
+ [BinaryOrder(1)]
+ public string StringValue { get; set; }
+
+ [BinaryOrder(2)]
+ public int AfterValue { get; set; }
+}
+
+public class TypeWithStringVariableSize
+{
+ [BinaryOrder(0)]
+ public int BeforeValue { get; set; }
+
+ [BinaryOrder(1)]
+ [BinaryString(SizeType = typeof(ushort), Terminator = "")]
+ public string StringValue { get; set; }
+
+ [BinaryOrder(2)]
+ public int AfterValue { get; set; }
+}
+
+public class TypeWithStringFixedSize
+{
+ [BinaryOrder(0)]
+ public int BeforeValue { get; set; }
+
+ [BinaryOrder(1)]
+ [BinaryString(FixedSize = 3, Terminator = "")]
+ public string StringValue { get; set; }
+
+ [BinaryOrder(2)]
+ public int AfterValue { get; set; }
+}
+
+public class TypeWithStringDefinedEncoding
+{
+ [BinaryOrder(0)]
+ public int BeforeValue { get; set; }
+
+ [BinaryOrder(1)]
+ [BinaryString(CodePage = 932)]
+ public string StringValue { get; set; }
+
+ [BinaryOrder(2)]
+ public int AfterValue { get; set; }
+}
+
+public class TypeWithStringInvalidEncoding
+{
+ [BinaryOrder(0)]
+ public int BeforeValue { get; set; }
+
+ [BinaryOrder(1)]
+ [BinaryString(CodePage = 666)]
+ public string StringValue { get; set; }
+
+ [BinaryOrder(2)]
+ public int AfterValue { get; set; }
+}
+#endregion
+
+#region Enum types
+public class TypeWithEnumNoAttribute
+{
+ [BinaryOrder(0)]
+ public SerializableEnum EnumValue { get; set; }
+}
+
+public class TypeWithEnumDefaultAttribute
+{
+ [BinaryOrder(0)]
+ [BinaryEnum]
+ public SerializableEnum EnumValue { get; set; }
+}
+
+public class TypeWithEnumWithOverwrittenType
+{
+ [BinaryOrder(0)]
+ [BinaryEnum(UnderlyingType = typeof(uint))]
+ public SerializableEnum EnumValue { get; set; }
+}
+
+[System.Diagnostics.CodeAnalysis.SuppressMessage("", "S2344", Justification = "Test type")]
+public enum SerializableEnum : short
+{
+ None = 1,
+ Value42 = 42,
+}
+#endregion
diff --git a/src/Yarhl.UnitTests/IO/Serialization/BinarySerializerTests.cs b/src/Yarhl.UnitTests/IO/Serialization/BinarySerializerTests.cs
new file mode 100644
index 00000000..a20ce233
--- /dev/null
+++ b/src/Yarhl.UnitTests/IO/Serialization/BinarySerializerTests.cs
@@ -0,0 +1,471 @@
+namespace Yarhl.UnitTests.IO.Serialization;
+
+using System;
+using NUnit.Framework;
+using Yarhl.IO;
+using Yarhl.IO.Serialization;
+
+[TestFixture]
+public class BinarySerializerTests
+{
+ [Test]
+ public void SerializeByGenericType()
+ {
+ byte[] data = { 0x0A, 0x00, 0x00, 0x00 };
+ var obj = new SimpleType { Value = 0x0A, };
+
+ using var stream = new DataStream();
+ var serializer = new BinarySerializer(stream, new DefaultTypePropertyNavigator());
+ serializer.Serialize(obj);
+
+ AssertBinary(stream, data);
+ }
+
+ [Test]
+ public void SerializeByTypeArg()
+ {
+ byte[] data = { 0x0A, 0x00, 0x00, 0x00 };
+ var obj = new SimpleType { Value = 0x0A, };
+
+ using var stream = new DataStream();
+ var serializer = new BinarySerializer(stream);
+ serializer.Serialize(typeof(SimpleType), obj);
+
+ AssertBinary(stream, data);
+ }
+
+ [Test]
+ public void SerializeByStaticGenericType()
+ {
+ byte[] data = { 0x0A, 0x00, 0x00, 0x00 };
+ var obj = new SimpleType { Value = 0x0A, };
+
+ using var stream = new DataStream();
+ BinarySerializer.Serialize(stream, obj);
+
+ AssertBinary(stream, data);
+ }
+
+ [Test]
+ public void SerializeByStaticTypeArg()
+ {
+ byte[] data = { 0x0A, 0x00, 0x00, 0x00 };
+ var obj = new SimpleType { Value = 0x0A, };
+
+ using var stream = new DataStream();
+ BinarySerializer.Serialize(stream, typeof(SimpleType), obj);
+
+ AssertBinary(stream, data);
+ }
+
+ [Test]
+ public void SerializeIncludesInheritedFields()
+ {
+ byte[] data = { 0x0A, 0x00, 0x00, 0x00, 0xFE, 0xCA };
+ var obj = new InheritedType { Value = 0x0A, NewValue = 0xCAFE };
+
+ AssertSerialization(obj, data);
+ }
+
+ [Test]
+ public void SerializeBaseType()
+ {
+ byte[] data = { 0x0A, 0x00, 0x00, 0x00 };
+ var obj = new InheritedType { Value = 0x0A, NewValue = 0xCAFE };
+
+ using var stream = new DataStream();
+ BinarySerializer.Serialize(stream, typeof(SimpleType), obj);
+
+ AssertBinary(stream, data);
+ }
+
+ [Test]
+ public void SerializeIntegerTypes()
+ {
+ var obj = new TypeWithIntegers {
+ CharValue = 'Ω',
+ ByteValue = 0x84,
+ SByteValue = -12,
+ UShortValue = 0x807F,
+ ShortValue = -16,
+ UIntValue = 0x12345678,
+ IntegerValue = -42,
+ ULongValue = 0x8000000000002A,
+ LongValue = -2L,
+ };
+
+ byte[] data = {
+ 0xCE, 0xA9, // char un UTF-8 (default encoding)
+ 0x84,
+ 0xF4,
+ 0x7F, 0x80,
+ 0xF0, 0xFF,
+ 0x78, 0x56, 0x34, 0x12,
+ 0xD6, 0xFF, 0xFF, 0xFF,
+ 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
+ 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ };
+
+ AssertSerialization(obj, data);
+ }
+
+ [Test]
+ public void SerializeDecimalTypes()
+ {
+ byte[] data = {
+ 0xC3, 0xF5, 0x48, 0x40,
+ 0x1F, 0x85, 0xEB, 0x51, 0xB8, 0x1E, 0x09, 0xC0,
+ };
+ var obj = new TypeWithDecimals {
+ SingleValue = 3.14f,
+ DoubleValue = -3.14d,
+ };
+
+ AssertSerialization(obj, data);
+ }
+
+ [Test]
+ public void SerializeStruct()
+ {
+ var obj = new MultiPropertyStruct {
+ IntegerValue = 1,
+ LongValue = 2,
+ TextValue = "yarhl",
+ };
+
+ byte[] expected = {
+ 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ (byte)'y', (byte)'a', (byte)'r', (byte)'h', (byte)'l', (byte)'\0',
+ };
+
+ AssertSerialization(obj, expected);
+ }
+
+ [Test]
+ public void SerializeNestedObject()
+ {
+ var obj = new TypeWithNestedObject() {
+ IntegerValue = 10,
+ ComplexValue = new TypeWithNestedObject.NestedType {
+ NestedValue = 1,
+ },
+ AnotherIntegerValue = 20,
+ };
+
+ byte[] expected = {
+ 0x0A, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00,
+ 0x14, 0x00, 0x00, 0x00,
+ };
+
+ AssertSerialization(obj, expected);
+ }
+
+ [Test]
+ public void SerializeIgnorePropertiesViaAttribute()
+ {
+ var obj = new TypeWithIgnoredProperties {
+ LongValue = 2,
+ IgnoredIntegerValue = 42,
+ };
+
+ byte[] expected = {
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ };
+
+ AssertSerialization(obj, expected);
+ }
+
+ [Test]
+ public void SerializeBooleanType()
+ {
+ var obj = new TypeWithBooleanDefaultAttribute() {
+ BeforeValue = 1,
+ BooleanValue = false,
+ AfterValue = 3,
+ };
+
+ byte[] expected = {
+ 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00,
+ };
+
+ AssertSerialization(obj, expected);
+
+ obj.BooleanValue = true;
+ expected[4] = 0x01;
+ AssertSerialization(obj, expected);
+ }
+
+ [Test]
+ public void SerializeBooleanWithDefinedValues()
+ {
+ var falseObj = new TypeWithBooleanDefinedValue() {
+ BeforeValue = 1,
+ BooleanValue = false,
+ AfterValue = 3,
+ };
+ byte[] expectedFalse = {
+ 0x01, 0x00, 0x00, 0x00,
+ 0xD6, 0xFF,
+ 0x03, 0x00, 0x00, 0x00,
+ };
+
+ var trueObj = new TypeWithBooleanDefinedValue() {
+ BeforeValue = 1,
+ BooleanValue = true,
+ AfterValue = 3,
+ };
+ byte[] expectedTrue = {
+ 0x01, 0x00, 0x00, 0x00,
+ 0x2A, 0x00,
+ 0x03, 0x00, 0x00, 0x00,
+ };
+
+ AssertSerialization(falseObj, expectedFalse);
+ AssertSerialization(trueObj, expectedTrue);
+ }
+
+ [Test]
+ public void SerializeBooleanWithTextValues()
+ {
+ var falseObj = new TypeWithBooleanTextValue() {
+ BeforeValue = 1,
+ BooleanValue = false,
+ AfterValue = 3,
+ };
+ byte[] expectedFalse = {
+ 0x01, 0x00, 0x00, 0x00,
+ (byte)'f', (byte)'a', (byte)'l', (byte)'s', (byte)'e', (byte)'\0',
+ 0x03, 0x00, 0x00, 0x00,
+ };
+
+ var trueObj = new TypeWithBooleanTextValue() {
+ BeforeValue = 1,
+ BooleanValue = true,
+ AfterValue = 3,
+ };
+ byte[] expectedTrue = {
+ 0x01, 0x00, 0x00, 0x00,
+ (byte)'t', (byte)'r', (byte)'u', (byte)'e', (byte)'\0',
+ 0x03, 0x00, 0x00, 0x00,
+ };
+
+ AssertSerialization(falseObj, expectedFalse);
+ AssertSerialization(trueObj, expectedTrue);
+ }
+
+ [Test]
+ public void TrySerializeBooleanWithoutAttributeThrowsException()
+ {
+ var obj = new TypeWithBooleanWithoutAttribute() {
+ BeforeValue = 1,
+ BooleanValue = true,
+ AfterValue = 3,
+ };
+
+ using var stream = new DataStream();
+ var serializer = new BinarySerializer(stream);
+
+ _ = Assert.Throws(() => serializer.Serialize(obj));
+ }
+
+ [Test]
+ public void SerializeInt24()
+ {
+ var obj = new TypeWithInt24 {
+ Int24Value = 0x7F_FC0FFE,
+ };
+
+ byte[] expected = {
+ 0xFE, 0x0F, 0xFC,
+ };
+
+ AssertSerialization(obj, expected);
+ }
+
+ [Test]
+ public void SerializeStringWithoutAttributeUsesDefaultWriterSettings()
+ {
+ var obj = new TypeWithStringWithoutAttribute {
+ BeforeValue = 1,
+ StringValue = "あア",
+ AfterValue = 3,
+ };
+
+ byte[] expected = {
+ 0x01, 0x00, 0x00, 0x00,
+ 0xE3, 0x81, 0x82, 0xE3, 0x82, 0xA2, 0x00,
+ 0x03, 0x00, 0x00, 0x00,
+ };
+
+ AssertSerialization(obj, expected);
+ }
+
+ [Test]
+ public void SerializeStringWithDefaultAttributeUsesDefaultWriterSettings()
+ {
+ var obj = new TypeWithStringDefaultAttribute() {
+ BeforeValue = 1,
+ StringValue = "あア",
+ AfterValue = 3,
+ };
+
+ byte[] expected = {
+ 0x01, 0x00, 0x00, 0x00,
+ 0xE3, 0x81, 0x82, 0xE3, 0x82, 0xA2, 0x00,
+ 0x03, 0x00, 0x00, 0x00,
+ };
+
+ AssertSerialization(obj, expected);
+ }
+
+ [Test]
+ public void SerializeStringWithSizeType()
+ {
+ var obj = new TypeWithStringVariableSize() {
+ BeforeValue = 1,
+ StringValue = "あ",
+ AfterValue = 4,
+ };
+
+ byte[] expected = {
+ 0x01, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0xE3, 0x81, 0x82,
+ 0x04, 0x00, 0x00, 0x00,
+ };
+
+ AssertSerialization(obj, expected);
+ }
+
+ [Test]
+ public void SerializeStringWithFixedSize()
+ {
+ var obj = new TypeWithStringFixedSize() {
+ BeforeValue = 1,
+ StringValue = "あ",
+ AfterValue = 4,
+ };
+
+ byte[] expected = {
+ 0x01, 0x00, 0x00, 0x00,
+ 0xE3, 0x81, 0x82,
+ 0x04, 0x00, 0x00, 0x00,
+ };
+
+ AssertSerialization(obj, expected);
+ }
+
+ [Test]
+ public void SerializeStringWithDifferentEncoding()
+ {
+ var obj = new TypeWithStringDefinedEncoding() {
+ BeforeValue = 1,
+ StringValue = "あア",
+ AfterValue = 4,
+ };
+
+ byte[] expected = {
+ 0x01, 0x00, 0x00, 0x00,
+ 0x82, 0xA0, 0x83, 0x41, 0x00,
+ 0x04, 0x00, 0x00, 0x00,
+ };
+
+ AssertSerialization(obj, expected);
+ }
+
+ [Test]
+ public void TrySerializeStringWithUnknownEncodingThrowsException()
+ {
+ var obj = new TypeWithStringInvalidEncoding() {
+ BeforeValue = 1,
+ StringValue = "あア",
+ AfterValue = 4,
+ };
+
+ using var stream = new DataStream();
+ var serializer = new BinarySerializer(stream);
+
+ _ = Assert.Throws(() => serializer.Serialize(obj));
+ }
+
+ [Test]
+ public void SerializeObjectWithSpecificEndianness()
+ {
+ var obj = new TypeWithEndiannessChanges() {
+ LittleEndianInteger = 1,
+ BigEndianInteger = 2,
+ DefaultEndianInteger = 3,
+ };
+
+ byte[] expected = {
+ 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x02,
+ 0x03, 0x00, 0x00, 0x00,
+ };
+
+ AssertSerialization(obj, expected);
+ }
+
+ [Test]
+ public void SerializeEnumNoAttribute()
+ {
+ byte[] data = { 0x2A, 0x00, };
+
+ var obj = new TypeWithEnumNoAttribute {
+ EnumValue = SerializableEnum.Value42,
+ };
+
+ AssertSerialization(obj, data);
+ }
+
+ [Test]
+ public void SerializeEnumDefaultAttribute()
+ {
+ byte[] data = { 0x2A, 0x00, };
+
+ var obj = new TypeWithEnumDefaultAttribute {
+ EnumValue = SerializableEnum.Value42,
+ };
+
+ AssertSerialization(obj, data);
+ }
+
+ [Test]
+ public void SerializeEnumOverwritingType()
+ {
+ byte[] data = { 0x01, 0x00, 0x00, 0x00 };
+
+ var obj = new TypeWithEnumWithOverwrittenType {
+ EnumValue = SerializableEnum.None,
+ };
+
+ AssertSerialization(obj, data);
+ }
+
+ private static void AssertSerialization(T obj, byte[] expected)
+ {
+ using var stream = new DataStream();
+ var serializer = new BinarySerializer(stream);
+
+ serializer.Serialize(obj);
+
+ AssertBinary(stream, expected);
+ }
+
+ private static void AssertBinary(DataStream actual, byte[] expected)
+ {
+ Assert.That(actual.Length, Is.EqualTo(expected.Length), "Stream size mismatch");
+
+ byte[] actualData = new byte[expected.Length];
+ Assert.Multiple(() => {
+ actual.Position = 0;
+ int read = actual.Read(actualData);
+
+ Assert.That(read, Is.EqualTo(expected.Length), "Read mismatch");
+ Assert.That(actualData, Is.EquivalentTo(expected));
+ });
+ }
+}
diff --git a/src/Yarhl.UnitTests/IO/Serialization/DefaultTypePropertyNavigatorTests.cs b/src/Yarhl.UnitTests/IO/Serialization/DefaultTypePropertyNavigatorTests.cs
new file mode 100644
index 00000000..ead428b3
--- /dev/null
+++ b/src/Yarhl.UnitTests/IO/Serialization/DefaultTypePropertyNavigatorTests.cs
@@ -0,0 +1,253 @@
+namespace Yarhl.UnitTests.IO.Serialization;
+
+using System;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using NUnit.Framework;
+using Yarhl.IO.Serialization;
+using Yarhl.IO.Serialization.Attributes;
+
+[TestFixture]
+public class DefaultTypePropertyNavigatorTests
+{
+ [Test]
+ public void PropertiesReturnedInOrderViaAttributes()
+ {
+ var navigator = new DefaultTypePropertyNavigator();
+
+ FieldInfo[] fields = navigator.IterateFields(typeof(SimpleType)).ToArray();
+
+ Assert.That(fields, Has.Length.EqualTo(2));
+ Assert.Multiple(() => {
+ Assert.That(fields[0].Name, Is.EqualTo(nameof(SimpleType.Prop2)));
+ Assert.That(fields[1].Name, Is.EqualTo(nameof(SimpleType.Prop1)));
+ });
+ }
+
+ [Test]
+ public void IgnorePrivateProperties()
+ {
+ var navigator = new DefaultTypePropertyNavigator();
+
+ FieldInfo[] fields = navigator.IterateFields(typeof(IgnorePrivatePropertiesType)).ToArray();
+
+ Assert.That(fields, Has.Length.EqualTo(1));
+ Assert.That(fields[0].Name, Is.EqualTo(nameof(IgnorePrivatePropertiesType.Prop0)));
+ }
+
+ [Test]
+ public void IgnoreStaticProperties()
+ {
+ var navigator = new DefaultTypePropertyNavigator();
+
+ FieldInfo[] fields = navigator.IterateFields(typeof(IgnoreStaticPropertiesType)).ToArray();
+
+ Assert.That(fields, Has.Length.EqualTo(1));
+ Assert.That(fields[0].Name, Is.EqualTo(nameof(IgnoreStaticPropertiesType.Prop1)));
+ }
+
+ [Test]
+ public void IgnorePropertiesWithIgnoreAttribute()
+ {
+ var navigator = new DefaultTypePropertyNavigator();
+
+ FieldInfo[] fields = navigator.IterateFields(typeof(IgnorePropertiesWithIgnoreAttributeType)).ToArray();
+
+ Assert.That(fields, Has.Length.EqualTo(2));
+ Assert.Multiple(() => {
+ Assert.That(fields[0].Name, Is.EqualTo(nameof(IgnorePropertiesWithIgnoreAttributeType.Prop0)));
+ Assert.That(fields[1].Name, Is.EqualTo(nameof(IgnorePropertiesWithIgnoreAttributeType.Prop2)));
+ });
+ }
+
+ [Test]
+ public void IgnorePropertiesWithoutPublicGetterOrSetter()
+ {
+ var navigator = new DefaultTypePropertyNavigator();
+
+ FieldInfo[] fields = navigator.IterateFields(typeof(IgnorePropertiesWithoutPublicGetterOrSetterType)).ToArray();
+
+ Assert.That(fields, Has.Length.EqualTo(1));
+ Assert.That(fields[0].Name, Is.EqualTo(nameof(IgnorePropertiesWithoutPublicGetterOrSetterType.ValidProp)));
+ }
+
+ [Test]
+ public void PropertyOrderWithInheritance()
+ {
+ var navigator = new DefaultTypePropertyNavigator();
+
+ FieldInfo[] fields = navigator.IterateFields(typeof(InheritedType)).ToArray();
+
+ Assert.That(fields, Has.Length.EqualTo(5));
+ Assert.Multiple(() => {
+ Assert.That(fields[0].Name, Is.EqualTo(nameof(InheritedType.Prop0)));
+ Assert.That(fields[1].Name, Is.EqualTo(nameof(InheritedType.PropBase0)));
+ Assert.That(fields[2].Name, Is.EqualTo(nameof(InheritedType.Prop1)));
+ Assert.That(fields[3].Name, Is.EqualTo(nameof(InheritedType.PropBase1)));
+ Assert.That(fields[4].Name, Is.EqualTo(nameof(InheritedType.Prop2)));
+ });
+ }
+
+ [Test]
+ public void TypeWithoutPropertyOrderAttributeThrowsInNet60()
+ {
+ if (!RuntimeInformation.FrameworkDescription.StartsWith(".NET 6")) {
+ Assert.Ignore("Test for another platform");
+ return;
+ }
+
+ var navigator = new DefaultTypePropertyNavigator();
+
+ Assert.That(
+ () => navigator.IterateFields(typeof(PropertiesWithoutOrderAttributeType)).ToArray(),
+ Throws.Exception.InstanceOf());
+ }
+
+ [Test]
+ public void TypeWithoutPropertyOrderAttributeWorksInNet80()
+ {
+ if (RuntimeInformation.FrameworkDescription.StartsWith(".NET 6")) {
+ Assert.Ignore("Test for another platform");
+ return;
+ }
+
+ var navigator = new DefaultTypePropertyNavigator();
+
+ FieldInfo[] fields = navigator.IterateFields(typeof(PropertiesWithoutOrderAttributeType)).ToArray();
+
+ Assert.That(fields, Has.Length.EqualTo(2));
+ Assert.Multiple(() => {
+ Assert.That(fields[0].Name, Is.EqualTo(nameof(PropertiesWithoutOrderAttributeType.Prop0)));
+ Assert.That(fields[1].Name, Is.EqualTo(nameof(PropertiesWithoutOrderAttributeType.Prop1)));
+ });
+ }
+
+ [Test]
+ public void TypeWithSomePropertyMissingOrderAttributeThrows()
+ {
+ var navigator = new DefaultTypePropertyNavigator();
+
+ Assert.That(
+ () => navigator.IterateFields(typeof(SomePropertiesWithoutOrderAttributeType)).ToArray(),
+ Throws.Exception.InstanceOf());
+ }
+
+#pragma warning disable S3459 // unused properties
+
+ private sealed class SimpleType
+ {
+ [BinaryOrder(10)]
+ public int Prop1 { get; set; }
+
+ [BinaryOrder(-5)]
+ public int Prop2 { get; set; }
+ }
+
+ private sealed class IgnorePrivatePropertiesType
+ {
+ [BinaryOrder(0)]
+ public int Prop0 { get; set; }
+
+ [BinaryOrder(1)]
+ private int Prop1 { get; set; }
+ }
+
+ private sealed class IgnoreStaticPropertiesType
+ {
+ [BinaryOrder(-1)]
+ public static int Prop0 { get; set; }
+
+ [BinaryOrder(0)]
+ public int Prop1 { get; set; }
+ }
+
+ private sealed class IgnorePropertiesWithIgnoreAttributeType
+ {
+ [BinaryOrder(-1)]
+ public int Prop0 { get; set; }
+
+ [BinaryOrder(0)]
+ [BinaryIgnore]
+ public int Prop1 { get; set; }
+
+ [BinaryOrder(1)]
+ public int Prop2 { get; set; }
+ }
+
+ private class IgnorePropertiesWithoutPublicGetterOrSetterType
+ {
+ private int val;
+
+ [BinaryOrder(-1)]
+ public int Prop0 { private get; set; }
+
+ [BinaryOrder(0)]
+ public int ValidProp { get; set; }
+
+ [BinaryOrder(1)]
+ public int Prop2 { get; private set; }
+
+ [BinaryOrder(2)]
+ public int Prop3 { internal get; set; }
+
+ [BinaryOrder(3)]
+ public int Prop4 { get; protected set; }
+
+ [BinaryOrder(4)]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("", "S2376", Justification = "Test")]
+ public int Prop5 {
+ set => val = value;
+ }
+
+ [BinaryOrder(5)]
+ public int Prop6 {
+ get => val;
+ }
+
+ [BinaryOrder(6)]
+ internal int Prop7 { get; set; }
+
+ [BinaryOrder(7)]
+ protected int Prop8 { get; set; }
+
+ [BinaryOrder(8)]
+ private int Prop9 { get; set; }
+ }
+
+ private class BaseType
+ {
+ [BinaryOrder(5)]
+ public int PropBase0 { get; set; }
+
+ [BinaryOrder(10)]
+ public int PropBase1 { get; set; }
+ }
+
+ private sealed class InheritedType : BaseType
+ {
+ [BinaryOrder(4)]
+ public int Prop0 { get; set; }
+
+ [BinaryOrder(8)]
+ public int Prop1 { get; set; }
+
+ [BinaryOrder(15)]
+ public int Prop2 { get; set; }
+ }
+
+ private sealed class PropertiesWithoutOrderAttributeType
+ {
+ public int Prop0 { get; set; }
+
+ public int Prop1 { get; set; }
+ }
+
+ private sealed class SomePropertiesWithoutOrderAttributeType
+ {
+ [BinaryOrder(0)]
+ public int Prop0 { get; set; }
+
+ public int Prop1 { get; set; }
+ }
+}
diff --git a/src/Yarhl/IO/DataReader.cs b/src/Yarhl/IO/DataReader.cs
index db6e6927..c2152b4b 100644
--- a/src/Yarhl/IO/DataReader.cs
+++ b/src/Yarhl/IO/DataReader.cs
@@ -403,12 +403,13 @@ public string ReadString(int bytesCount, Encoding? encoding = null)
/// Optional encoding to use.
public string ReadString(Type sizeType, Encoding? encoding = null)
{
- if (encoding == null)
+ if (encoding is null) {
encoding = DefaultEncoding;
+ }
- dynamic size = ReadByType(sizeType);
+ object size = ReadByType(sizeType);
size = Convert.ChangeType(size, typeof(int), CultureInfo.InvariantCulture);
- return ReadString(size, encoding);
+ return ReadString((int)size, encoding);
}
///
@@ -417,39 +418,35 @@ public string ReadString(Type sizeType, Encoding? encoding = null)
/// The field.
/// Nullable types are not supported.
/// Type of the field.
- public dynamic ReadByType(Type type)
+ public object ReadByType(Type type)
{
- if (type == null)
- throw new ArgumentNullException(nameof(type));
+ ArgumentNullException.ThrowIfNull(type);
- bool serializable = Attribute.IsDefined(type, typeof(Serialization.Attributes.SerializableAttribute));
- if (serializable)
- return ReadUsingReflection(type);
-
- if (type == typeof(long))
+ if (type == typeof(long)) {
return ReadInt64();
- if (type == typeof(ulong))
+ } else if (type == typeof(ulong)) {
return ReadUInt64();
- if (type == typeof(int))
+ } else if (type == typeof(int)) {
return ReadInt32();
- if (type == typeof(uint))
+ } else if (type == typeof(uint)) {
return ReadUInt32();
- if (type == typeof(short))
+ } else if (type == typeof(short)) {
return ReadInt16();
- if (type == typeof(ushort))
+ } else if (type == typeof(ushort)) {
return ReadUInt16();
- if (type == typeof(byte))
+ } else if (type == typeof(byte)) {
return ReadByte();
- if (type == typeof(sbyte))
+ } else if (type == typeof(sbyte)) {
return ReadSByte();
- if (type == typeof(char))
+ } else if (type == typeof(char)) {
return ReadChar();
- if (type == typeof(string))
+ } else if (type == typeof(string)) {
return ReadString();
- if (type == typeof(float))
+ } else if (type == typeof(float)) {
return ReadSingle();
- if (type == typeof(double))
+ } else if (type == typeof(double)) {
return ReadDouble();
+ }
throw new FormatException("Unsupported type");
}
@@ -459,7 +456,7 @@ public dynamic ReadByType(Type type)
///
/// The field.
/// The type of the field.
- public dynamic Read()
+ public object Read()
{
return ReadByType(typeof(T));
}
@@ -470,8 +467,9 @@ public dynamic Read()
/// Padding value.
public void SkipPadding(int padding)
{
- if (padding < 0)
+ if (padding < 0) {
throw new ArgumentOutOfRangeException(nameof(padding));
+ }
if (padding <= 1) {
return;
@@ -482,73 +480,5 @@ public void SkipPadding(int padding)
_ = Stream.Seek(remainingBytes, SeekOrigin.Current);
}
}
-
- dynamic ReadUsingReflection(Type type)
- {
- // It returns null for Nullable, but as that is a class and
- // it won't have the serializable attribute, it will throw an
- // unsupported exception before. So this can't be null at this point.
- #pragma warning disable SA1009 // False positive
- object obj = Activator.CreateInstance(type)!;
- #pragma warning restore SA1009
-
- PropertyInfo[] properties = type.GetProperties(
- BindingFlags.DeclaredOnly |
- BindingFlags.Public |
- BindingFlags.Instance);
-
- foreach (PropertyInfo property in properties) {
- bool ignore = Attribute.IsDefined(property, typeof(BinaryIgnoreAttribute));
- if (ignore) {
- continue;
- }
-
- EndiannessMode currentEndianness = Endianness;
- bool forceEndianness = Attribute.IsDefined(property, typeof(BinaryForceEndiannessAttribute));
- if (forceEndianness) {
- var attr = Attribute.GetCustomAttribute(property, typeof(BinaryForceEndiannessAttribute)) as BinaryForceEndiannessAttribute;
- Endianness = attr!.Mode;
- }
-
- if (property.PropertyType == typeof(bool) && Attribute.IsDefined(property, typeof(BinaryBooleanAttribute))) {
- // booleans can only be read if they have the attribute.
- var attr = Attribute.GetCustomAttribute(property, typeof(BinaryBooleanAttribute)) as BinaryBooleanAttribute;
- dynamic value = ReadByType(attr!.ReadAs);
- property.SetValue(obj, value == (dynamic)attr.TrueValue);
- } else if (property.PropertyType == typeof(int) && Attribute.IsDefined(property, typeof(BinaryInt24Attribute))) {
- // read the number as int24.
- int value = ReadInt24();
- property.SetValue(obj, value);
- } else if (property.PropertyType.IsEnum && Attribute.IsDefined(property, typeof(BinaryEnumAttribute))) {
- // enums can only be read if they have the attribute.
- var attr = Attribute.GetCustomAttribute(property, typeof(BinaryEnumAttribute)) as BinaryEnumAttribute;
- dynamic value = ReadByType(attr!.ReadAs);
- property.SetValue(obj, Enum.ToObject(property.PropertyType, value));
- } else if (property.PropertyType == typeof(string) && Attribute.IsDefined(property, typeof(BinaryStringAttribute))) {
- var attr = Attribute.GetCustomAttribute(property, typeof(BinaryStringAttribute)) as BinaryStringAttribute;
- Encoding? encoding = null;
- if (attr!.CodePage != -1) {
- encoding = Encoding.GetEncoding(attr.CodePage);
- }
-
- dynamic value;
- if (attr.SizeType == null) {
- value = attr.FixedSize == -1 ? this.ReadString(encoding) : this.ReadString(attr.FixedSize, encoding);
- } else {
- value = ReadString(attr.SizeType, encoding);
- }
-
- property.SetValue(obj, value);
- } else {
- dynamic value = ReadByType(property.PropertyType);
- property.SetValue(obj, value);
- }
-
- // Restore previous endianness
- Endianness = currentEndianness;
- }
-
- return obj;
- }
}
}
diff --git a/src/Yarhl/IO/DataWriter.cs b/src/Yarhl/IO/DataWriter.cs
index c5ea2b8d..e8e4d89e 100644
--- a/src/Yarhl/IO/DataWriter.cs
+++ b/src/Yarhl/IO/DataWriter.cs
@@ -419,6 +419,15 @@ public void Write(char[] chars, Encoding? encoding = null)
Write(text, textSize, terminator, encoding);
}
+ ///
+ /// Write the specified 24-bits value.
+ ///
+ /// 24-bits value.
+ public void WriteInt24(int val)
+ {
+ WriteNumber((uint)val, 24);
+ }
+
///
/// Write the specified value converting to any supported type.
///
@@ -428,66 +437,59 @@ public void Write(char[] chars, Encoding? encoding = null)
/// The supported types are: long, ulong, int, uint, short,
/// ushort, byte, sbyte, char and string.
///
- public void WriteOfType(Type type, dynamic val)
+ public void WriteOfType(Type type, object val)
{
- if (val == null)
- throw new ArgumentNullException(nameof(val));
- if (type == null)
- throw new ArgumentNullException(nameof(type));
-
- val = Convert.ChangeType(val, type, CultureInfo.InvariantCulture);
-
- bool serializable = Attribute.IsDefined(type, typeof(Serialization.Attributes.SerializableAttribute));
- if (serializable) {
- WriteUsingReflection(type, val);
- } else {
- switch (val) {
- case long l:
- Write(l);
- break;
- case ulong ul:
- Write(ul);
- break;
-
- case int i:
- Write(i);
- break;
- case uint ui:
- Write(ui);
- break;
-
- case short s:
- Write(s);
- break;
- case ushort us:
- Write(us);
- break;
-
- case byte b:
- Write(b);
- break;
- case sbyte sb:
- Write(sb);
- break;
-
- case char ch:
- Write(ch);
- break;
- case string str:
- Write(str);
- break;
-
- case float f:
- Write(f);
- break;
-
- case double d:
- Write(d);
- break;
-
- default:
- throw new FormatException("Unsupported type");
- }
+ ArgumentNullException.ThrowIfNull(type);
+ ArgumentNullException.ThrowIfNull(val);
+
+ object converted = Convert.ChangeType(val, type, CultureInfo.InvariantCulture)!;
+
+ switch (converted) {
+ case long l:
+ Write(l);
+ break;
+ case ulong ul:
+ Write(ul);
+ break;
+
+ case int i:
+ Write(i);
+ break;
+ case uint ui:
+ Write(ui);
+ break;
+
+ case short s:
+ Write(s);
+ break;
+ case ushort us:
+ Write(us);
+ break;
+
+ case byte b:
+ Write(b);
+ break;
+ case sbyte sb:
+ Write(sb);
+ break;
+
+ case char ch:
+ Write(ch);
+ break;
+ case string str:
+ Write(str);
+ break;
+
+ case float f:
+ Write(f);
+ break;
+
+ case double d:
+ Write(d);
+ break;
+
+ default:
+ throw new FormatException("Unsupported type");
}
}
@@ -498,8 +500,7 @@ public void WriteOfType(Type type, dynamic val)
/// The type of the value.
public void WriteOfType(T val)
{
- if (val == null)
- throw new ArgumentNullException(nameof(val));
+ ArgumentNullException.ThrowIfNull(val);
WriteOfType(typeof(T), val);
}
@@ -570,7 +571,7 @@ public void WritePadding(byte val, int padding)
WriteTimes(val, Stream.Position.Pad(padding) - Stream.Position);
}
- void WriteNumber(ulong number, byte numBits)
+ private void WriteNumber(ulong number, byte numBits)
{
byte start;
byte end;
@@ -593,60 +594,5 @@ void WriteNumber(ulong number, byte numBits)
Stream.WriteByte(val);
}
}
-
- void WriteUsingReflection(Type type, dynamic obj)
- {
- PropertyInfo[] properties = type.GetProperties(
- BindingFlags.DeclaredOnly |
- BindingFlags.Public |
- BindingFlags.Instance);
-
- foreach (PropertyInfo property in properties) {
- bool ignore = Attribute.IsDefined(property, typeof(BinaryIgnoreAttribute));
- if (ignore) {
- continue;
- }
-
- EndiannessMode currentEndianness = Endianness;
- var endiannessAttr = property.GetCustomAttribute();
- if (endiannessAttr is not null) {
- Endianness = endiannessAttr.Mode;
- }
-
- dynamic value = property.GetValue(obj);
-
- if (property.PropertyType == typeof(bool) && property.GetCustomAttribute() is { } boolAttr) {
- // booleans can only be written if they have the attribute.
- dynamic typeValue = value ? boolAttr.TrueValue : boolAttr.FalseValue;
- WriteOfType(boolAttr.WriteAs, typeValue);
- } else if (property.PropertyType == typeof(int) && Attribute.IsDefined(property, typeof(BinaryInt24Attribute))) {
- // write the number as int24
- WriteNumber((uint)value, 24);
- } else if (property.PropertyType.IsEnum && property.GetCustomAttribute() is { } enumAttr) {
- // enums can only be written if they have the attribute.
- WriteOfType(enumAttr.WriteAs, value);
- } else if (property.PropertyType == typeof(string) && property.GetCustomAttribute() is { } stringAttr) {
- Encoding? encoding = null;
- if (stringAttr.CodePage != -1) {
- encoding = Encoding.GetEncoding(stringAttr.CodePage);
- }
-
- if (stringAttr.SizeType is null) {
- if (stringAttr.FixedSize == -1) {
- Write((string)value, stringAttr.Terminator, encoding, stringAttr.MaxSize);
- } else {
- Write((string)value, stringAttr.FixedSize, stringAttr.Terminator, encoding);
- }
- } else {
- Write((string)value, stringAttr.SizeType, stringAttr.Terminator, encoding, stringAttr.MaxSize);
- }
- } else {
- WriteOfType(property.PropertyType, value);
- }
-
- // Restore previous endianness
- Endianness = currentEndianness;
- }
- }
}
}
diff --git a/src/Yarhl/IO/Serialization/Attributes/BinaryBooleanAttribute.cs b/src/Yarhl/IO/Serialization/Attributes/BinaryBooleanAttribute.cs
index 1ede4b7c..c03ca802 100644
--- a/src/Yarhl/IO/Serialization/Attributes/BinaryBooleanAttribute.cs
+++ b/src/Yarhl/IO/Serialization/Attributes/BinaryBooleanAttribute.cs
@@ -1,4 +1,4 @@
-// Copyright (c) 2020 SceneGate
+// Copyright (c) 2020 SceneGate
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -33,24 +33,15 @@ public sealed class BinaryBooleanAttribute : Attribute
///
public BinaryBooleanAttribute()
{
- ReadAs = typeof(int);
- WriteAs = typeof(int);
+ UnderlyingType = typeof(int);
TrueValue = 1;
FalseValue = 0;
}
///
- /// Gets or sets the equivalent type for reading.
+ /// Gets or sets the underlying type to use to serialize and deserialize.
///
- public Type ReadAs {
- get;
- set;
- }
-
- ///
- /// Gets or sets the equivalent type for writing.
- ///
- public Type WriteAs {
+ public Type UnderlyingType {
get;
set;
}
diff --git a/src/Yarhl/IO/Serialization/Attributes/BinaryForceEndiannessAttribute.cs b/src/Yarhl/IO/Serialization/Attributes/BinaryEndiannessAttribute.cs
similarity index 82%
rename from src/Yarhl/IO/Serialization/Attributes/BinaryForceEndiannessAttribute.cs
rename to src/Yarhl/IO/Serialization/Attributes/BinaryEndiannessAttribute.cs
index be9f2c7e..a1986677 100644
--- a/src/Yarhl/IO/Serialization/Attributes/BinaryForceEndiannessAttribute.cs
+++ b/src/Yarhl/IO/Serialization/Attributes/BinaryEndiannessAttribute.cs
@@ -1,4 +1,4 @@
-// Copyright (c) 2020 SceneGate
+// Copyright (c) 2020 SceneGate
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -22,16 +22,16 @@ namespace Yarhl.IO.Serialization.Attributes
using System;
///
- /// Set to force the endianness in automatic serialization.
+ /// Specify the endianness to serialize or deserialize a field.
///
[AttributeUsage(AttributeTargets.Property)]
- public sealed class BinaryForceEndiannessAttribute : Attribute
+ public sealed class BinaryEndiannessAttribute : Attribute
{
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// Endianness mode for the property.
- public BinaryForceEndiannessAttribute(EndiannessMode mode)
+ public BinaryEndiannessAttribute(EndiannessMode mode)
{
Mode = mode;
}
diff --git a/src/Yarhl/IO/Serialization/Attributes/BinaryEnumAttribute.cs b/src/Yarhl/IO/Serialization/Attributes/BinaryEnumAttribute.cs
index b22ec75d..8c197936 100644
--- a/src/Yarhl/IO/Serialization/Attributes/BinaryEnumAttribute.cs
+++ b/src/Yarhl/IO/Serialization/Attributes/BinaryEnumAttribute.cs
@@ -1,4 +1,4 @@
-// Copyright (c) 2020 SceneGate
+// Copyright (c) 2020 SceneGate
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -23,7 +23,7 @@ namespace Yarhl.IO.Serialization.Attributes
///
/// Define how to read and write a Enum value.
- /// Default type is
+ /// Default type is defined in the enum type
///
[AttributeUsage(AttributeTargets.Property)]
public sealed class BinaryEnumAttribute : Attribute
@@ -33,22 +33,17 @@ public sealed class BinaryEnumAttribute : Attribute
///
public BinaryEnumAttribute()
{
- ReadAs = typeof(int);
- WriteAs = typeof(int);
+ UnderlyingType = null;
}
///
- /// Gets or sets the equivalent type for reading.
+ /// Gets or sets the underlying type to use to serialize and deserialize.
///
- public Type ReadAs {
- get;
- set;
- }
-
- ///
- /// Gets or sets the equivalent type for writing.
- ///
- public Type WriteAs {
+ ///
+ /// If set to null (default), it will use the defined underlying type
+ /// in the enumaration type.
+ ///
+ public Type? UnderlyingType {
get;
set;
}
diff --git a/src/Yarhl/IO/Serialization/Attributes/BinaryOrderAttribute.cs b/src/Yarhl/IO/Serialization/Attributes/BinaryOrderAttribute.cs
new file mode 100644
index 00000000..67b38508
--- /dev/null
+++ b/src/Yarhl/IO/Serialization/Attributes/BinaryOrderAttribute.cs
@@ -0,0 +1,24 @@
+namespace Yarhl.IO.Serialization.Attributes;
+
+using System;
+
+///
+/// Specify the order to serialize or deserialize the fields in binary format.
+///
+[AttributeUsage(AttributeTargets.Property)]
+public class BinaryOrderAttribute : Attribute
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The order of the field in the binary serialization.
+ public BinaryOrderAttribute(int order)
+ {
+ Order = order;
+ }
+
+ ///
+ /// Gets or sets the order of the field in the binary format.
+ ///
+ public int Order { get; set; }
+}
diff --git a/src/Yarhl/IO/Serialization/Attributes/SerializableAttribute.cs b/src/Yarhl/IO/Serialization/Attributes/SerializableAttribute.cs
deleted file mode 100644
index f019ad91..00000000
--- a/src/Yarhl/IO/Serialization/Attributes/SerializableAttribute.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (c) 2020 SceneGate
-
-// 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 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.
-namespace Yarhl.IO.Serialization.Attributes
-{
- using System;
-
- ///
- /// Set to enable automatic serialization.
- ///
- [AttributeUsage(AttributeTargets.Class)]
- public sealed class SerializableAttribute : Attribute
- {
- }
-}
diff --git a/src/Yarhl/IO/Serialization/BinaryDeserializer.cs b/src/Yarhl/IO/Serialization/BinaryDeserializer.cs
new file mode 100644
index 00000000..e51ce1a0
--- /dev/null
+++ b/src/Yarhl/IO/Serialization/BinaryDeserializer.cs
@@ -0,0 +1,165 @@
+namespace Yarhl.IO.Serialization;
+
+using System;
+using System.IO;
+using System.Text;
+using Yarhl.IO.Serialization.Attributes;
+
+///
+/// Binary deserialization of objects based on their attributes. Equivalent of
+/// converting a binary format into an object.
+///
+public class BinaryDeserializer
+{
+ private readonly DataReader reader;
+ private readonly ITypeFieldNavigator fieldNavigator;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The stream to read from.
+ public BinaryDeserializer(Stream stream)
+ {
+ ArgumentNullException.ThrowIfNull(stream);
+
+ reader = new DataReader(stream);
+ fieldNavigator = new DefaultTypePropertyNavigator();
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The stream to read from.
+ /// The strategy to iterate the field's type.
+ public BinaryDeserializer(Stream stream, ITypeFieldNavigator fieldNavigator)
+ {
+ ArgumentNullException.ThrowIfNull(stream);
+ ArgumentNullException.ThrowIfNull(fieldNavigator);
+
+ reader = new DataReader(stream);
+ this.fieldNavigator = fieldNavigator;
+ }
+
+ ///
+ /// Gets or sets the default endianness for the deserialization.
+ ///
+ public EndiannessMode DefaultEndianness { get; set; }
+
+ ///
+ /// Deserialize an object from the binary data of the stream.
+ ///
+ /// The type of the object to deserialize.
+ /// The stream to read from.
+ /// A new object deserialized.
+ public static T Deserialize(Stream stream)
+ {
+ return new BinaryDeserializer(stream).Deserialize();
+ }
+
+ ///
+ /// Deserialize an object from the binary data of the stream.
+ ///
+ /// The stream to read from.
+ /// The type of the object to deserialize.
+ /// A new object deserialized.
+ public static object Deserialize(Stream stream, Type objType)
+ {
+ return new BinaryDeserializer(stream).Deserialize(objType);
+ }
+
+ ///
+ /// Deserialize an object from the binary data of the stream.
+ ///
+ /// The type of the object to deserialize.
+ /// A new object deserialized.
+ public T Deserialize()
+ {
+ return (T)Deserialize(typeof(T));
+ }
+
+ ///
+ /// Deserialize an object from the binary data of the stream.
+ ///
+ /// The type of the object to deserialize.
+ /// A new object deserialized.
+ public object Deserialize(Type objType)
+ {
+ object obj = Activator.CreateInstance(objType)
+ ?? throw new FormatException("Nullable types are not supported");
+
+ foreach (FieldInfo fieldInfo in fieldNavigator.IterateFields(objType)) {
+ object propertyValue = DeserializePropertyValue(fieldInfo);
+ fieldInfo.SetValueFunc(obj, propertyValue);
+ }
+
+ return obj;
+ }
+
+ private object DeserializePropertyValue(FieldInfo fieldInfo)
+ {
+ reader.Endianness = DefaultEndianness;
+ var endiannessAttr = fieldInfo.GetAttribute();
+ if (endiannessAttr is not null) {
+ reader.Endianness = endiannessAttr.Mode;
+ }
+
+ if (fieldInfo.Type.IsPrimitive) {
+ return DeserializePrimitiveField(fieldInfo);
+ } else if (fieldInfo.Type.IsEnum) {
+ return DeserializeEnumField(fieldInfo);
+ } else if (fieldInfo.Type == typeof(string)) {
+ return DeserializeStringField(fieldInfo);
+ } else {
+ return Deserialize(fieldInfo.Type);
+ }
+ }
+
+ private object DeserializePrimitiveField(FieldInfo fieldInfo)
+ {
+ if (fieldInfo.Type == typeof(bool)) {
+ if (fieldInfo.GetAttribute() is not { } boolAttr) {
+ throw new FormatException("Properties of type 'bool' must have the attribute BinaryBoolean");
+ }
+
+ object value = reader.ReadByType(boolAttr.UnderlyingType);
+ return value.Equals(boolAttr.TrueValue);
+ }
+
+ if (fieldInfo.Type == typeof(int) && fieldInfo.GetAttribute() is not null) {
+ return reader.ReadInt24();
+ }
+
+ return reader.ReadByType(fieldInfo.Type);
+ }
+
+ private object DeserializeEnumField(FieldInfo fieldInfo)
+ {
+ var enumAttr = fieldInfo.GetAttribute();
+ Type underlyingType = enumAttr?.UnderlyingType
+ ?? Enum.GetUnderlyingType(fieldInfo.Type);
+
+ object value = reader.ReadByType(underlyingType);
+ return Enum.ToObject(fieldInfo.Type, value);
+ }
+
+ private string DeserializeStringField(FieldInfo fieldInfo)
+ {
+ if (fieldInfo.GetAttribute() is not { } stringAttr) {
+ // Use default settings if not specified.
+ return reader.ReadString();
+ }
+
+ Encoding? encoding = null;
+ if (stringAttr!.CodePage != -1) {
+ encoding = Encoding.GetEncoding(stringAttr.CodePage);
+ }
+
+ if (stringAttr.SizeType is null) {
+ return (stringAttr.FixedSize == -1)
+ ? reader.ReadString(encoding)
+ : reader.ReadString(stringAttr.FixedSize, encoding);
+ }
+
+ return reader.ReadString(stringAttr.SizeType, encoding);
+ }
+}
diff --git a/src/Yarhl/IO/Serialization/BinarySerializer.cs b/src/Yarhl/IO/Serialization/BinarySerializer.cs
new file mode 100644
index 00000000..0fc35361
--- /dev/null
+++ b/src/Yarhl/IO/Serialization/BinarySerializer.cs
@@ -0,0 +1,173 @@
+namespace Yarhl.IO.Serialization;
+
+using System;
+using System.IO;
+using System.Linq;
+using System.Text;
+using Yarhl.IO.Serialization.Attributes;
+
+///
+/// Binary serialization of objects based on attributes. Equivalent to convert
+/// an object into binary.
+///
+public class BinarySerializer
+{
+ private readonly ITypeFieldNavigator fieldNavigator;
+ private readonly DataWriter writer;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The stream to write the binary data.
+ public BinarySerializer(Stream stream)
+ {
+ ArgumentNullException.ThrowIfNull(stream);
+
+ writer = new DataWriter(stream);
+ fieldNavigator = new DefaultTypePropertyNavigator();
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The stream to write the binary data.
+ /// Strategy to iterate over type fields.
+ public BinarySerializer(Stream stream, ITypeFieldNavigator fieldNavigator)
+ {
+ ArgumentNullException.ThrowIfNull(stream);
+ ArgumentNullException.ThrowIfNull(fieldNavigator);
+
+ writer = new DataWriter(stream);
+ this.fieldNavigator = fieldNavigator;
+ }
+
+ ///
+ /// Gets or sets the default endianness for the serialization.
+ ///
+ public EndiannessMode DefaultEndianness { get; set; }
+
+ ///
+ /// Serialize the public properties of the object in binary data in the stream.
+ ///
+ /// The stream to write the binary data.
+ /// The object to serialize into the stream.
+ /// The type of the object.
+ public static void Serialize(Stream stream, T obj)
+ {
+ new BinarySerializer(stream).Serialize(obj);
+ }
+
+ ///
+ /// Serialize the public properties of the object in binary data in the stream.
+ ///
+ /// The stream to write the binary data.
+ /// The type of object to serialize.
+ /// The object to serialize into the stream.
+ public static void Serialize(Stream stream, Type objType, object obj)
+ {
+ new BinarySerializer(stream).Serialize(objType, obj);
+ }
+
+ ///
+ /// Serialize the public properties of the object in binary data in the stream.
+ ///
+ /// The object to serialize into the stream.
+ /// The type of the object.
+ public void Serialize(T obj)
+ {
+ ArgumentNullException.ThrowIfNull(obj);
+
+ Serialize(typeof(T), obj);
+ }
+
+ ///
+ /// Serialize the public properties of the object in binary data in the stream.
+ ///
+ /// The type of object to serialize.
+ /// The object to serialize into the stream.
+ public void Serialize(Type type, object obj)
+ {
+ foreach (FieldInfo property in fieldNavigator.IterateFields(type)) {
+ SerializeProperty(property, obj);
+ }
+ }
+
+ private void SerializeProperty(FieldInfo fieldInfo, object obj)
+ {
+ writer.Endianness = DefaultEndianness;
+ var endiannessAttr = fieldInfo.GetAttribute();
+ if (endiannessAttr is not null) {
+ writer.Endianness = endiannessAttr.Mode;
+ }
+
+ object value = fieldInfo.GetValueFunc(obj)
+ ?? throw new FormatException("Cannot serialize nullable values");
+
+ if (fieldInfo.Type.IsPrimitive) {
+ SerializePrimitiveField(fieldInfo, value);
+ } else if (fieldInfo.Type.IsEnum) {
+ SerializeEnumField(fieldInfo, value);
+ } else if (fieldInfo.Type == typeof(string)) {
+ SerializeString(fieldInfo, value);
+ } else {
+ Serialize(fieldInfo.Type, value);
+ }
+ }
+
+ private void SerializePrimitiveField(FieldInfo fieldInfo, object value)
+ {
+ // Handle first the special cases
+ if (fieldInfo.Type == typeof(bool)) {
+ if (fieldInfo.GetAttribute() is not { } boolAttr) {
+ throw new FormatException("Properties of type 'bool' must have the attribute BinaryBoolean");
+ }
+
+ object typeValue = (bool)value ? boolAttr.TrueValue : boolAttr.FalseValue;
+ writer.WriteOfType(boolAttr.UnderlyingType, typeValue);
+ return;
+ }
+
+ if (fieldInfo.Type == typeof(int) && fieldInfo.Attributes.Any(a => a is BinaryInt24Attribute)) {
+ writer.WriteInt24((int)value);
+ return;
+ }
+
+ // Fallback to DataWriter primitive write
+ writer.WriteOfType(fieldInfo.Type, value);
+ }
+
+ private void SerializeEnumField(FieldInfo fieldInfo, object value)
+ {
+ var enumAttr = fieldInfo.GetAttribute();
+ Type underlyingType = enumAttr?.UnderlyingType
+ ?? Enum.GetUnderlyingType(fieldInfo.Type);
+
+ writer.WriteOfType(underlyingType, value);
+ }
+
+ private void SerializeString(FieldInfo fieldInfo, object value)
+ {
+ if (fieldInfo.GetAttribute() is not { } stringAttr) {
+ // Use default settings if not specified.
+ writer.Write((string)value);
+ return;
+ }
+
+ Encoding? encoding = null;
+ if (stringAttr.CodePage != -1) {
+ encoding = Encoding.GetEncoding(stringAttr.CodePage);
+ }
+
+ string strValue = (string)value;
+
+ if (stringAttr.SizeType is null) {
+ if (stringAttr.FixedSize == -1) {
+ writer.Write(strValue, stringAttr.Terminator, encoding, stringAttr.MaxSize);
+ } else {
+ writer.Write(strValue, stringAttr.FixedSize, stringAttr.Terminator, encoding);
+ }
+ } else {
+ writer.Write(strValue, stringAttr.SizeType, stringAttr.Terminator, encoding, stringAttr.MaxSize);
+ }
+ }
+}
diff --git a/src/Yarhl/IO/Serialization/DefaultTypePropertyNavigator.cs b/src/Yarhl/IO/Serialization/DefaultTypePropertyNavigator.cs
new file mode 100644
index 00000000..0f681e01
--- /dev/null
+++ b/src/Yarhl/IO/Serialization/DefaultTypePropertyNavigator.cs
@@ -0,0 +1,62 @@
+namespace Yarhl.IO.Serialization;
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using Yarhl.IO.Serialization.Attributes;
+
+///
+/// Field navigator for types that iterate over public non-static properties only.
+/// It includes inherited properties. It follows the order given by the order attribute.
+///
+public class DefaultTypePropertyNavigator : ITypeFieldNavigator
+{
+ ///
+ public virtual IEnumerable IterateFields(Type type)
+ {
+ PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
+ .Where(p => p.CanRead && (p.GetGetMethod(false)?.IsPublic ?? false))
+ .Where(p => p.CanWrite && (p.GetSetMethod(false)?.IsPublic ?? false))
+ .Where(p => p.GetCustomAttribute() is null)
+ .ToArray();
+
+ SortProperties(properties);
+
+ foreach (PropertyInfo property in properties) {
+ var info = new FieldInfo(
+ property.Name,
+ property.PropertyType,
+ property.GetValue,
+ property.SetValue,
+ property.GetCustomAttributes());
+
+ yield return info;
+ }
+ }
+
+ private static void SortProperties(PropertyInfo[] properties)
+ {
+ int[] orderKeys = properties
+ .Select(p => p.GetCustomAttribute())
+ .Where(p => p is not null)
+ .Select(p => p!.Order)
+ .ToArray();
+
+#if NET6_0
+ if (orderKeys.Length != properties.Length) {
+ throw new FormatException("Prior .NET 8.0, every property must have the BinaryFieldOrder attribute");
+ }
+
+ Array.Sort(orderKeys, properties);
+#elif NET8_0_OR_GREATER
+ if (orderKeys.Length > 0 && orderKeys.Length != properties.Length) {
+ throw new FormatException("BinaryFieldOrder must be applied to none or all properties");
+ }
+
+ if (orderKeys.Length > 0) {
+ Array.Sort(orderKeys, properties);
+ }
+#endif
+ }
+}
diff --git a/src/Yarhl/IO/Serialization/FieldInfo.cs b/src/Yarhl/IO/Serialization/FieldInfo.cs
new file mode 100644
index 00000000..10c50478
--- /dev/null
+++ b/src/Yarhl/IO/Serialization/FieldInfo.cs
@@ -0,0 +1,35 @@
+namespace Yarhl.IO.Serialization;
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+///
+/// Information of a field member of a type.
+///
+/// Name of the field.
+/// Type of the field.
+/// Function that returns the fields' value given the object.
+///
+/// Function that sets the fields'value on the given object.
+/// The first argument is the object and the second the value to set.
+///
+/// Optional collection of attributes on the field.
+public record FieldInfo(
+ string Name,
+ Type Type,
+ Func