Files
SabreTools.IO/SabreTools.IO.Extensions/BinaryReaderExtensions.cs

240 lines
8.9 KiB
C#
Raw Permalink Normal View History

2023-09-08 11:55:44 -04:00
using System;
using System.IO;
using System.Reflection;
2024-04-23 10:51:54 -04:00
using System.Runtime.InteropServices;
2024-04-23 13:23:09 -04:00
using System.Text;
2026-03-21 20:57:03 -04:00
using SabreTools.Numerics.Extensions;
using SabreTools.Text.Extensions;
2023-09-08 11:55:44 -04:00
2024-04-16 21:45:26 -04:00
namespace SabreTools.IO.Extensions
2023-09-08 11:55:44 -04:00
{
/// <summary>
2024-04-22 15:25:32 -04:00
/// Extensions for BinaryReader
2023-09-08 11:55:44 -04:00
/// </summary>
public static class BinaryReaderExtensions
{
2024-04-23 10:51:54 -04:00
/// <summary>
/// Read a <typeparamref name="T"/> from the underlying stream
/// </summary>
/// <remarks>
2024-04-29 00:42:50 -04:00
/// This method is different than standard marshalling in a few notable ways:
/// - Strings are read by value, not by reference
/// - Complex objects are read by value, not by reference
/// - Enumeration values are read by the underlying value type
/// - Arrays of the above are handled sequentially as above
/// - Inherited fields from parents are deserialized BEFORE fields in the child
/// </remarks>
2024-04-23 10:51:54 -04:00
public static T? ReadType<T>(this BinaryReader reader)
=> (T?)reader.ReadType(typeof(T));
/// <summary>
/// Read a <paramref name="type"/> from the underlying stream
/// </summary>
/// <remarks>
2024-04-29 00:42:50 -04:00
/// This method is different than standard marshalling in a few notable ways:
/// - Strings are read by value, not by reference
/// - Complex objects are read by value, not by reference
/// - Enumeration values are read by the underlying value type
/// - Arrays of the above are handled sequentially as above
/// - Inherited fields from parents are deserialized BEFORE fields in the child
/// </remarks>
public static object? ReadType(this BinaryReader reader, Type type)
{
2024-05-07 05:00:43 -04:00
// Handle special struct cases
if (type == typeof(Guid))
return reader.ReadGuid();
2026-03-16 02:57:06 -04:00
#if NET5_0_OR_GREATER
2024-05-07 05:00:43 -04:00
else if (type == typeof(Half))
return reader.ReadHalf();
#endif
#if NET7_0_OR_GREATER
else if (type == typeof(Int128))
return reader.ReadInt128();
else if (type == typeof(UInt128))
return reader.ReadUInt128();
#endif
2024-04-28 17:55:54 -04:00
if (type.IsClass || (type.IsValueType && !type.IsEnum && !type.IsPrimitive))
return ReadComplexType(reader, type);
else if (type.IsValueType && type.IsEnum)
return ReadNormalType(reader, Enum.GetUnderlyingType(type));
else
return ReadNormalType(reader, type);
}
/// <summary>
/// Read a <paramref name="type"/> from the underlying stream
/// </summary>
private static object? ReadNormalType(BinaryReader reader, Type type)
2024-04-23 10:51:54 -04:00
{
2024-04-28 09:41:37 -04:00
try
{
int typeSize = Marshal.SizeOf(type);
byte[] buffer = reader.ReadBytes(typeSize); ;
2024-04-23 13:40:39 -04:00
2024-04-28 09:41:37 -04:00
var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
var data = Marshal.PtrToStructure(handle.AddrOfPinnedObject(), type);
2024-04-28 09:41:37 -04:00
handle.Free();
2024-04-23 13:40:39 -04:00
2024-04-28 09:41:37 -04:00
return data;
}
catch
{
return null;
}
}
/// <summary>
/// Read a <paramref name="type"/> from the underlying stream
/// </summary>
2024-04-29 14:45:31 -04:00
private static object? ReadComplexType(BinaryReader reader, Type type)
{
try
{
// Try to create an instance of the type
var instance = Activator.CreateInstance(type);
2026-01-25 17:04:11 -05:00
if (instance is null)
return null;
// Get the layout information
var layoutAttr = MarshalHelpers.GetAttribute<StructLayoutAttribute>(type);
LayoutKind layoutKind = MarshalHelpers.DetermineLayoutKind(layoutAttr, type);
Encoding encoding = MarshalHelpers.DetermineEncoding(layoutAttr);
// Cache the current offset
long currentOffset = reader.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<FieldOffsetAttribute>(fi);
2026-01-25 17:04:11 -05:00
reader.BaseStream.Seek(currentOffset + (fieldOffset?.Value ?? 0), SeekOrigin.Begin);
}
SetField(reader, encoding, fields, instance, fi);
}
return instance;
}
catch
{
return null;
}
}
/// <summary>
/// Set a single field on an object
/// </summary>
private static void SetField(BinaryReader reader, Encoding encoding, FieldInfo[] fields, object instance, FieldInfo fi)
{
if (fi.FieldType.IsAssignableFrom(typeof(string)))
{
2024-04-28 22:58:42 -04:00
var value = ReadStringType(reader, encoding, fi);
fi.SetValue(instance, value);
}
2024-04-28 22:58:42 -04:00
else if (fi.FieldType.IsArray)
{
2024-04-28 23:47:33 -04:00
var value = ReadArrayType(reader, fields, instance, fi);
2024-11-25 15:03:07 -05:00
if (value.GetType() == fi.FieldType)
fi.SetValue(instance, value);
else
fi.SetValue(instance, Convert.ChangeType(value, fi.FieldType));
2024-04-28 22:58:42 -04:00
}
else
{
var value = reader.ReadType(fi.FieldType);
fi.SetValue(instance, value);
}
}
/// <summary>
2024-04-28 22:58:42 -04:00
/// Read an array type field for an object
/// </summary>
2024-04-28 23:47:33 -04:00
private static Array ReadArrayType(BinaryReader reader, FieldInfo[] fields, object instance, FieldInfo fi)
{
var marshalAsAttr = MarshalHelpers.GetAttribute<MarshalAsAttribute>(fi);
2026-01-25 17:04:11 -05:00
if (marshalAsAttr is null)
2024-04-28 23:12:48 -04:00
return new object[0];
2024-04-28 22:58:42 -04:00
// Get the number of elements expected
int elementCount = MarshalHelpers.GetArrayElementCount(marshalAsAttr, fields, instance);
if (elementCount < 0)
return new object[0];
2024-04-28 22:58:42 -04:00
// Get the item type for the array
Type elementType = fi.FieldType.GetElementType() ?? typeof(object);
// Loop through and build the array
2024-04-29 12:13:35 -04:00
Array arr = Array.CreateInstance(elementType, elementCount);
2024-04-28 22:58:42 -04:00
for (int i = 0; i < elementCount; i++)
{
var value = ReadType(reader, elementType);
2026-01-25 17:04:11 -05:00
if (value is not null && elementType.IsEnum)
2024-11-25 15:03:07 -05:00
arr.SetValue(Enum.ToObject(elementType, value), i);
else
arr.SetValue(value, i);
2024-04-28 22:58:42 -04:00
}
// Return the built array
return arr;
}
/// <summary>
/// Read a string type field for an object
/// </summary>
private static string? ReadStringType(BinaryReader reader, Encoding encoding, FieldInfo? fi)
{
2024-11-05 21:47:43 -05:00
// If the FieldInfo is null
2026-01-25 17:04:11 -05:00
if (fi is null)
2024-11-05 21:47:43 -05:00
return null;
// Get all MarshalAs attributes for the field, if possible
var attributes = fi.GetCustomAttributes(typeof(MarshalAsAttribute), true);
if (attributes.Length == 0)
return null;
2024-04-28 22:58:42 -04:00
2024-11-05 21:47:43 -05:00
// Use the first found attribute
var marshalAsAttr = attributes[0] as MarshalAsAttribute;
2026-01-25 17:04:11 -05:00
#pragma warning disable IDE0010
switch (marshalAsAttr?.Value)
{
case UnmanagedType.AnsiBStr:
2024-04-29 12:39:02 -04:00
return reader.ReadPrefixedAnsiString();
case UnmanagedType.BStr:
2024-04-29 00:48:21 -04:00
case UnmanagedType.TBStr: // Technically distinct; returns char[] instead
2024-04-29 12:39:02 -04:00
return reader.ReadPrefixedUnicodeString();
case UnmanagedType.ByValTStr:
2024-04-28 22:58:42 -04:00
int byvalLength = marshalAsAttr!.SizeConst;
byte[] byvalBytes = reader.ReadBytes(byvalLength);
return encoding.GetString(byvalBytes);
case UnmanagedType.LPStr:
2024-04-29 00:49:45 -04:00
case UnmanagedType.LPTStr: // Technically distinct; possibly not null-terminated
case null:
2024-04-29 12:39:02 -04:00
return reader.ReadNullTerminatedAnsiString();
2025-07-23 10:39:58 -04:00
#if NET472_OR_GREATER || NETCOREAPP || NETSTANDARD2_1_OR_GREATER
2024-04-29 00:55:16 -04:00
case UnmanagedType.LPUTF8Str:
2024-04-29 12:39:02 -04:00
return reader.ReadNullTerminatedUTF8String();
2024-04-29 00:55:16 -04:00
#endif
case UnmanagedType.LPWStr:
2024-04-29 12:39:02 -04:00
return reader.ReadNullTerminatedUnicodeString();
2024-04-29 00:55:16 -04:00
// No other string types are recognized
default:
return null;
2024-04-28 09:41:37 -04:00
}
2026-01-25 17:04:11 -05:00
#pragma warning restore IDE0010
2024-04-23 10:51:54 -04:00
}
2023-09-08 11:55:44 -04:00
}
}