diff --git a/SabreTools.IO/Extensions/BinaryReaderExtensions.cs b/SabreTools.IO/Extensions/BinaryReaderExtensions.cs
index 5a59f41..694b251 100644
--- a/SabreTools.IO/Extensions/BinaryReaderExtensions.cs
+++ b/SabreTools.IO/Extensions/BinaryReaderExtensions.cs
@@ -527,7 +527,7 @@ namespace SabreTools.IO.Extensions
///
/// Read a from the underlying stream
///
- private static object? ReadComplexType(this BinaryReader reader, Type type)
+ private static object? ReadComplexType(BinaryReader reader, Type type)
{
try
{
diff --git a/SabreTools.IO/Extensions/BinaryWriterExtensions.cs b/SabreTools.IO/Extensions/BinaryWriterExtensions.cs
index 417bf25..b8c9c52 100644
--- a/SabreTools.IO/Extensions/BinaryWriterExtensions.cs
+++ b/SabreTools.IO/Extensions/BinaryWriterExtensions.cs
@@ -424,21 +424,155 @@ namespace SabreTools.IO.Extensions
///
/// Write a to the underlying stream
///
- /// TODO: Fix writing as reading was fixed
+ ///
+ /// This method is different than standard marshalling in a few notable ways:
+ /// - Strings are written by value, not by reference
+ /// - Complex objects are written by value, not by reference
+ /// - Enumeration values are written by the underlying value type
+ /// - Arrays of the above are handled sequentially as above
+ /// - Inherited fields from parents are serialized BEFORE fields in the child
+ ///
public static bool WriteType(this BinaryWriter writer, T? value)
+ => writer.WriteType(value, typeof(T));
+
+ ///
+ /// Write a to the underlying stream
+ ///
+ ///
+ /// This method is different than standard marshalling in a few notable ways:
+ /// - Strings are written by value, not by reference
+ /// - Complex objects are written by value, not by reference
+ /// - Enumeration values are written by the underlying value type
+ /// - Arrays of the above are handled sequentially as above
+ /// - Inherited fields from parents are serialized BEFORE fields in the child
+ ///
+ public static bool WriteType(this BinaryWriter writer, object? value, Type type)
{
- // Handle the null case
- if (value == null)
+ if (type.IsClass || (type.IsValueType && !type.IsEnum && !type.IsPrimitive))
+ return WriteComplexType(writer, value, type);
+ else if (type.IsValueType && type.IsEnum)
+ return WriteNormalType(writer, value, Enum.GetUnderlyingType(type));
+ else
+ return WriteNormalType(writer, value, type);
+ }
+
+ ///
+ /// Read a from the stream
+ ///
+ private static bool WriteNormalType(BinaryWriter writer, object? value, Type type)
+ {
+ try
+ {
+ int typeSize = Marshal.SizeOf(type);
+
+ var buffer = new byte[typeSize];
+ var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
+ Marshal.StructureToPtr(value, handle.AddrOfPinnedObject(), false);
+ handle.Free();
+
+ return WriteFromBuffer(writer, buffer);
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Read a from the stream
+ ///
+ private static bool WriteComplexType(BinaryWriter writer, object? value, Type type)
+ {
+ try
+ {
+ // Null values cannot be written
+ if (value == null)
+ return false;
+
+ // Get the layout information
+ var layoutAttr = MarshalHelpers.GetAttribute(type);
+ LayoutKind layoutKind = MarshalHelpers.DetermineLayoutKind(layoutAttr, type);
+ Encoding encoding = MarshalHelpers.DetermineEncoding(layoutAttr);
+
+ // Cache the current offset
+ long currentOffset = writer.BaseStream.Position;
+
+ // Generate the fields by parent first
+ var fields = MarshalHelpers.GetFields(type);
+
+ // Loop through the fields and set them
+ foreach (var fi in fields)
+ {
+ // If we have an explicit layout, move accordingly
+ if (layoutKind == LayoutKind.Explicit)
+ {
+ var fieldOffset = MarshalHelpers.GetAttribute(fi);
+ writer.BaseStream.Seek(currentOffset + fieldOffset?.Value ?? 0, SeekOrigin.Begin);
+ }
+
+ if (!GetField(writer, encoding, fields, value, fi))
+ return false;
+ }
+
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Write a single field from an object
+ ///
+ private static bool GetField(BinaryWriter writer, Encoding encoding, FieldInfo[] fields, object instance, FieldInfo fi)
+ {
+ if (fi.FieldType.IsAssignableFrom(typeof(string)))
+ {
+ return WriteStringType(writer, encoding, instance, fi);
+ }
+ else if (fi.FieldType.IsArray)
+ {
+ return WriteArrayType(writer, fields, instance, fi);
+ }
+ else
+ {
+ var value = fi.GetValue(instance);
+ return writer.WriteType(value, fi.FieldType);
+ }
+ }
+
+ ///
+ /// Write an array type field from an object
+ ///
+ private static bool WriteArrayType(BinaryWriter writer, FieldInfo[] fields, object instance, FieldInfo fi)
+ {
+ var marshalAsAttr = MarshalHelpers.GetAttribute(fi);
+ if (marshalAsAttr == null)
return false;
- int typeSize = Marshal.SizeOf(typeof(T));
+ // Get the array
+ Array? arr = fi.GetValue(instance) as Array;
+ if (arr == null)
+ return false;
- var buffer = new byte[typeSize];
- var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
- Marshal.StructureToPtr(value, handle.AddrOfPinnedObject(), false);
- handle.Free();
+ // Get the number of elements expected
+ int elementCount = MarshalHelpers.GetArrayElementCount(marshalAsAttr, fields, instance);
+ if (elementCount < 0)
+ return false;
- return WriteFromBuffer(writer, buffer);
+ // Get the item type for the array
+ Type elementType = fi.FieldType.GetElementType() ?? typeof(object);
+
+ // Loop through and write the array
+ for (int i = 0; i < elementCount; i++)
+ {
+ var value = arr.GetValue(i);
+ if (!WriteType(writer, value, elementType))
+ return false;
+ }
+
+ return true;
}
///
diff --git a/SabreTools.IO/Extensions/ByteArrayReaderExtensions.cs b/SabreTools.IO/Extensions/ByteArrayReaderExtensions.cs
index d33ae38..0662423 100644
--- a/SabreTools.IO/Extensions/ByteArrayReaderExtensions.cs
+++ b/SabreTools.IO/Extensions/ByteArrayReaderExtensions.cs
@@ -655,7 +655,7 @@ namespace SabreTools.IO.Extensions
///
/// Read a from the stream
///
- private static object? ReadComplexType(this byte[] content, ref int offset, Type type)
+ private static object? ReadComplexType(byte[] content, ref int offset, Type type)
{
try
{
diff --git a/SabreTools.IO/Extensions/ByteArrayWriterExtensions.cs b/SabreTools.IO/Extensions/ByteArrayWriterExtensions.cs
index b702778..8ebbeca 100644
--- a/SabreTools.IO/Extensions/ByteArrayWriterExtensions.cs
+++ b/SabreTools.IO/Extensions/ByteArrayWriterExtensions.cs
@@ -577,21 +577,155 @@ namespace SabreTools.IO.Extensions
///
/// Write a to the byte array
///
- /// TODO: Fix writing as reading was fixed
+ ///
+ /// This method is different than standard marshalling in a few notable ways:
+ /// - Strings are written by value, not by reference
+ /// - Complex objects are written by value, not by reference
+ /// - Enumeration values are written by the underlying value type
+ /// - Arrays of the above are handled sequentially as above
+ /// - Inherited fields from parents are serialized BEFORE fields in the child
+ ///
public static bool WriteType(this byte[] content, ref int offset, T? value)
+ => content.WriteType(ref offset, value, typeof(T));
+
+ ///
+ /// Write a to the byte array
+ ///
+ ///
+ /// This method is different than standard marshalling in a few notable ways:
+ /// - Strings are written by value, not by reference
+ /// - Complex objects are written by value, not by reference
+ /// - Enumeration values are written by the underlying value type
+ /// - Arrays of the above are handled sequentially as above
+ /// - Inherited fields from parents are serialized BEFORE fields in the child
+ ///
+ public static bool WriteType(this byte[] content, ref int offset, object? value, Type type)
{
- // Handle the null case
- if (value == null)
+ if (type.IsClass || (type.IsValueType && !type.IsEnum && !type.IsPrimitive))
+ return WriteComplexType(content, ref offset, value, type);
+ else if (type.IsValueType && type.IsEnum)
+ return WriteNormalType(content, ref offset, value, Enum.GetUnderlyingType(type));
+ else
+ return WriteNormalType(content, ref offset, value, type);
+ }
+
+ ///
+ /// Read a from the stream
+ ///
+ private static bool WriteNormalType(byte[] content, ref int offset, object? value, Type type)
+ {
+ try
+ {
+ int typeSize = Marshal.SizeOf(type);
+
+ var buffer = new byte[typeSize];
+ var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
+ Marshal.StructureToPtr(value, handle.AddrOfPinnedObject(), false);
+ handle.Free();
+
+ return WriteFromBuffer(content, ref offset, buffer);
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Read a from the stream
+ ///
+ private static bool WriteComplexType(byte[] content, ref int offset, object? value, Type type)
+ {
+ try
+ {
+ // Null values cannot be written
+ if (value == null)
+ return false;
+
+ // Get the layout information
+ var layoutAttr = MarshalHelpers.GetAttribute(type);
+ LayoutKind layoutKind = MarshalHelpers.DetermineLayoutKind(layoutAttr, type);
+ Encoding encoding = MarshalHelpers.DetermineEncoding(layoutAttr);
+
+ // Cache the current offset
+ int currentOffset = offset;
+
+ // Generate the fields by parent first
+ var fields = MarshalHelpers.GetFields(type);
+
+ // Loop through the fields and set them
+ foreach (var fi in fields)
+ {
+ // If we have an explicit layout, move accordingly
+ if (layoutKind == LayoutKind.Explicit)
+ {
+ var fieldOffset = MarshalHelpers.GetAttribute(fi);
+ offset = currentOffset + fieldOffset?.Value ?? 0;
+ }
+
+ if (!GetField(content, ref offset, encoding, fields, value, fi))
+ return false;
+ }
+
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Write a single field from an object
+ ///
+ private static bool GetField(byte[] content, ref int offset, Encoding encoding, FieldInfo[] fields, object instance, FieldInfo fi)
+ {
+ if (fi.FieldType.IsAssignableFrom(typeof(string)))
+ {
+ return WriteStringType(content, ref offset, encoding, instance, fi);
+ }
+ else if (fi.FieldType.IsArray)
+ {
+ return WriteArrayType(content, ref offset, fields, instance, fi);
+ }
+ else
+ {
+ var value = fi.GetValue(instance);
+ return content.WriteType(ref offset, value, fi.FieldType);
+ }
+ }
+
+ ///
+ /// Write an array type field from an object
+ ///
+ private static bool WriteArrayType(byte[] content, ref int offset, FieldInfo[] fields, object instance, FieldInfo fi)
+ {
+ var marshalAsAttr = MarshalHelpers.GetAttribute(fi);
+ if (marshalAsAttr == null)
return false;
- int typeSize = Marshal.SizeOf(typeof(T));
+ // Get the array
+ Array? arr = fi.GetValue(instance) as Array;
+ if (arr == null)
+ return false;
- var buffer = new byte[typeSize];
- var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
- Marshal.StructureToPtr(value, handle.AddrOfPinnedObject(), false);
- handle.Free();
+ // Get the number of elements expected
+ int elementCount = MarshalHelpers.GetArrayElementCount(marshalAsAttr, fields, instance);
+ if (elementCount < 0)
+ return false;
- return WriteFromBuffer(content, ref offset, buffer);
+ // Get the item type for the array
+ Type elementType = fi.FieldType.GetElementType() ?? typeof(object);
+
+ // Loop through and write the array
+ for (int i = 0; i < elementCount; i++)
+ {
+ var value = arr.GetValue(i);
+ if (!WriteType(content, ref offset, value, elementType))
+ return false;
+ }
+
+ return true;
}
///
diff --git a/SabreTools.IO/Extensions/StreamReaderExtensions.cs b/SabreTools.IO/Extensions/StreamReaderExtensions.cs
index 316c703..7b72179 100644
--- a/SabreTools.IO/Extensions/StreamReaderExtensions.cs
+++ b/SabreTools.IO/Extensions/StreamReaderExtensions.cs
@@ -639,7 +639,7 @@ namespace SabreTools.IO.Extensions
///
/// Read a from the stream
///
- private static object? ReadComplexType(this Stream stream, Type type)
+ private static object? ReadComplexType(Stream stream, Type type)
{
try
{
diff --git a/SabreTools.IO/Extensions/StreamWriterExtensions.cs b/SabreTools.IO/Extensions/StreamWriterExtensions.cs
index ef1f843..74b51c6 100644
--- a/SabreTools.IO/Extensions/StreamWriterExtensions.cs
+++ b/SabreTools.IO/Extensions/StreamWriterExtensions.cs
@@ -578,21 +578,156 @@ namespace SabreTools.IO.Extensions
///
/// Write a to the stream
///
- /// TODO: Fix writing as reading was fixed
+ ///
+ /// This method is different than standard marshalling in a few notable ways:
+ /// - Strings are written by value, not by reference
+ /// - Complex objects are written by value, not by reference
+ /// - Enumeration values are written by the underlying value type
+ /// - Arrays of the above are handled sequentially as above
+ /// - Inherited fields from parents are serialized BEFORE fields in the child
+ ///
public static bool WriteType(this Stream stream, T? value)
+ => stream.WriteType(value, typeof(T));
+
+ ///
+ /// Write a to the stream
+ ///
+ ///
+ /// This method is different than standard marshalling in a few notable ways:
+ /// - Strings are written by value, not by reference
+ /// - Complex objects are written by value, not by reference
+ /// - Enumeration values are written by the underlying value type
+ /// - Arrays of the above are handled sequentially as above
+ /// - Inherited fields from parents are serialized BEFORE fields in the child
+ ///
+ public static bool WriteType(this Stream stream, object? value, Type type)
{
- // Handle the null case
- if (value == null)
+ if (type.IsClass || (type.IsValueType && !type.IsEnum && !type.IsPrimitive))
+ return WriteComplexType(stream, value, type);
+ else if (type.IsValueType && type.IsEnum)
+ return WriteNormalType(stream, value, Enum.GetUnderlyingType(type));
+ else
+ return WriteNormalType(stream, value, type);
+ }
+
+ ///
+ /// Read a from the stream
+ ///
+ private static bool WriteNormalType(Stream stream, object? value, Type type)
+ {
+ try
+ {
+ int typeSize = Marshal.SizeOf(type);
+
+ var buffer = new byte[typeSize];
+ var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
+ Marshal.StructureToPtr(value, handle.AddrOfPinnedObject(), false);
+ handle.Free();
+
+ return WriteFromBuffer(stream, buffer);
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Read a from the stream
+ ///
+ private static bool WriteComplexType(Stream stream, object? value, Type type)
+ {
+ try
+ {
+ // Null values cannot be written
+ if (value == null)
+ return false;
+
+ // Get the layout information
+ var layoutAttr = MarshalHelpers.GetAttribute(type);
+ LayoutKind layoutKind = MarshalHelpers.DetermineLayoutKind(layoutAttr, type);
+ Encoding encoding = MarshalHelpers.DetermineEncoding(layoutAttr);
+
+ // Cache the current offset
+ long currentOffset = stream.Position;
+
+ // Generate the fields by parent first
+ var fields = MarshalHelpers.GetFields(type);
+
+ // Loop through the fields and set them
+ foreach (var fi in fields)
+ {
+ // If we have an explicit layout, move accordingly
+ if (layoutKind == LayoutKind.Explicit)
+ {
+ var fieldOffset = MarshalHelpers.GetAttribute(fi);
+ stream.Seek(currentOffset + fieldOffset?.Value ?? 0, SeekOrigin.Begin);
+ }
+
+ if (!GetField(stream, encoding, fields, value, fi))
+ return false;
+ }
+
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Write a single field from an object
+ ///
+ private static bool GetField(Stream stream, Encoding encoding, FieldInfo[] fields, object instance, FieldInfo fi)
+ {
+ if (fi.FieldType.IsAssignableFrom(typeof(string)))
+ {
+ return WriteStringType(stream, encoding, instance, fi);
+ }
+ else if (fi.FieldType.IsArray)
+ {
+ return WriteArrayType(stream, fields, instance, fi);
+ }
+ else
+ {
+ var value = fi.GetValue(instance);
+ return stream.WriteType(value, fi.FieldType);
+ }
+ }
+
+ ///
+ /// Write an array type field from an object
+ ///
+ private static bool WriteArrayType(Stream stream, FieldInfo[] fields, object instance, FieldInfo fi)
+ {
+ var marshalAsAttr = MarshalHelpers.GetAttribute(fi);
+ if (marshalAsAttr == null)
return false;
- int typeSize = Marshal.SizeOf(typeof(T));
+ // Get the array
+ Array? arr = fi.GetValue(instance) as Array;
+ if (arr == null)
+ return false;
- var buffer = new byte[typeSize];
- var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
- Marshal.StructureToPtr(value, handle.AddrOfPinnedObject(), false);
- handle.Free();
+ // Get the number of elements expected
+ int elementCount = MarshalHelpers.GetArrayElementCount(marshalAsAttr, fields, instance);
+ if (elementCount < 0)
+ return false;
- return WriteFromBuffer(stream, buffer);
+ // Get the item type for the array
+ Type elementType = fi.FieldType.GetElementType() ?? typeof(object);
+
+ // Loop through and write the array
+ for (int i = 0; i < elementCount; i++)
+ {
+ var value = arr.GetValue(i);
+ if (!WriteType(stream, value, elementType))
+ return false;
+ }
+
+ // Return the built array
+ return true;
}
///