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; } ///