4 Commits
1.4.5 ... 1.4.6

Author SHA1 Message Date
Matt Nadareski
cd08925411 Fix write tests, add notes 2024-04-28 16:55:16 -04:00
Matt Nadareski
6ea8aab7c7 Bump version 2024-04-28 16:43:39 -04:00
Matt Nadareski
561dbdcc9a Fix type deserialization extensions, leave some TODOs 2024-04-28 16:42:51 -04:00
Matt Nadareski
b4bad28823 Safer type reading 2024-04-28 09:41:37 -04:00
15 changed files with 602 additions and 33 deletions

View File

@@ -397,7 +397,13 @@ namespace SabreTools.IO.Test.Extensions
[Fact]
public void ReadTypeExplicitTest()
{
var stream = new MemoryStream(_bytes);
byte[] bytesWithString =
[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x41, 0x42, 0x43, 0x00,
];
var stream = new MemoryStream(bytesWithString);
var br = new BinaryReader(stream);
var expected = new TestStructExplicit
{
@@ -405,18 +411,26 @@ namespace SabreTools.IO.Test.Extensions
SecondValue = 0x07060504,
ThirdValue = 0x0504,
FourthValue = 0x0706,
FifthValue = "ABC",
};
var read = br.ReadType<TestStructExplicit>();
Assert.Equal(expected.FirstValue, read.FirstValue);
Assert.Equal(expected.SecondValue, read.SecondValue);
Assert.Equal(expected.ThirdValue, read.ThirdValue);
Assert.Equal(expected.FourthValue, read.FourthValue);
Assert.Equal(expected.FifthValue, read.FifthValue);
}
[Fact]
public void ReadTypeSequentialTest()
{
var stream = new MemoryStream(_bytes);
byte[] bytesWithString =
[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x41, 0x42, 0x43, 0x00,
];
var stream = new MemoryStream(bytesWithString);
var br = new BinaryReader(stream);
var expected = new TestStructSequential
{
@@ -424,12 +438,14 @@ namespace SabreTools.IO.Test.Extensions
SecondValue = 0x07060504,
ThirdValue = 0x0908,
FourthValue = 0x0B0A,
FifthValue = "ABC",
};
var read = br.ReadType<TestStructSequential>();
Assert.Equal(expected.FirstValue, read.FirstValue);
Assert.Equal(expected.SecondValue, read.SecondValue);
Assert.Equal(expected.ThirdValue, read.ThirdValue);
Assert.Equal(expected.FourthValue, read.FourthValue);
Assert.Equal(expected.FifthValue, read.FifthValue);
}
}
}

View File

@@ -465,7 +465,7 @@ namespace SabreTools.IO.Test.Extensions
[Fact]
public void WriteTypeSequentialTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var stream = new MemoryStream(new byte[24], 0, count: 24, true, true);
var bw = new BinaryWriter(stream);
var obj = new TestStructSequential
{

View File

@@ -365,6 +365,12 @@ namespace SabreTools.IO.Test.Extensions
[Fact]
public void ReadTypeExplicitTest()
{
byte[] bytesWithString =
[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x41, 0x42, 0x43, 0x00,
];
int offset = 0;
var expected = new TestStructExplicit
{
@@ -372,8 +378,9 @@ namespace SabreTools.IO.Test.Extensions
SecondValue = 0x07060504,
ThirdValue = 0x0504,
FourthValue = 0x0706,
FifthValue = "ABC",
};
var read = _bytes.ReadType<TestStructExplicit>(ref offset);
var read = bytesWithString.ReadType<TestStructExplicit>(ref offset);
Assert.Equal(expected.FirstValue, read.FirstValue);
Assert.Equal(expected.SecondValue, read.SecondValue);
Assert.Equal(expected.ThirdValue, read.ThirdValue);
@@ -383,6 +390,12 @@ namespace SabreTools.IO.Test.Extensions
[Fact]
public void ReadTypeSequentialTest()
{
byte[] bytesWithString =
[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x41, 0x42, 0x43, 0x00,
];
int offset = 0;
var expected = new TestStructSequential
{
@@ -390,12 +403,14 @@ namespace SabreTools.IO.Test.Extensions
SecondValue = 0x07060504,
ThirdValue = 0x0908,
FourthValue = 0x0B0A,
FifthValue = "ABC",
};
var read = _bytes.ReadType<TestStructSequential>(ref offset);
var read = bytesWithString.ReadType<TestStructSequential>(ref offset);
Assert.Equal(expected.FirstValue, read.FirstValue);
Assert.Equal(expected.SecondValue, read.SecondValue);
Assert.Equal(expected.ThirdValue, read.ThirdValue);
Assert.Equal(expected.FourthValue, read.FourthValue);
Assert.Equal(expected.FifthValue, read.FifthValue);
}
}
}

View File

@@ -448,7 +448,7 @@ namespace SabreTools.IO.Test.Extensions
[Fact]
public void WriteTypeSequentialTest()
{
byte[] buffer = new byte[16];
byte[] buffer = new byte[24];
int offset = 0;
var obj = new TestStructSequential
{

View File

@@ -359,13 +359,20 @@ namespace SabreTools.IO.Test.Extensions
[Fact]
public void ReadTypeExplicitTest()
{
var stream = new MemoryStream(_bytes);
byte[] bytesWithString =
[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x41, 0x42, 0x43, 0x00,
];
var stream = new MemoryStream(bytesWithString);
var expected = new TestStructExplicit
{
FirstValue = 0x03020100,
SecondValue = 0x07060504,
ThirdValue = 0x0504,
FourthValue = 0x0706,
FifthValue = "ABC",
};
var read = stream.ReadType<TestStructExplicit>();
Assert.Equal(expected.FirstValue, read.FirstValue);
@@ -377,19 +384,27 @@ namespace SabreTools.IO.Test.Extensions
[Fact]
public void ReadTypeSequentialTest()
{
var stream = new MemoryStream(_bytes);
byte[] bytesWithString =
[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x41, 0x42, 0x43, 0x00,
];
var stream = new MemoryStream(bytesWithString);
var expected = new TestStructSequential
{
FirstValue = 0x03020100,
SecondValue = 0x07060504,
ThirdValue = 0x0908,
FourthValue = 0x0B0A,
FifthValue = "ABC",
};
var read = stream.ReadType<TestStructSequential>();
Assert.Equal(expected.FirstValue, read.FirstValue);
Assert.Equal(expected.SecondValue, read.SecondValue);
Assert.Equal(expected.ThirdValue, read.ThirdValue);
Assert.Equal(expected.FourthValue, read.FourthValue);
Assert.Equal(expected.FifthValue, read.FifthValue);
}
}
}

View File

@@ -412,7 +412,7 @@ namespace SabreTools.IO.Test.Extensions
[Fact]
public void WriteTypeSequentialTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var stream = new MemoryStream(new byte[24], 0, 24, true, true);
var obj = new TestStructSequential
{
FirstValue = 0x03020100,

View File

@@ -16,5 +16,8 @@ namespace SabreTools.IO.Test.Extensions
[FieldOffset(6)]
public short FourthValue;
[FieldOffset(8), MarshalAs(UnmanagedType.LPStr)]
public string? FifthValue;
}
}

View File

@@ -12,5 +12,8 @@ namespace SabreTools.IO.Test.Extensions
public ushort ThirdValue;
public short FourthValue;
[MarshalAs(UnmanagedType.LPStr)]
public string? FifthValue;
}
}

View File

@@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
#if NET7_0_OR_GREATER
using System.Numerics;
#endif
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
@@ -416,14 +418,14 @@ namespace SabreTools.IO.Extensions
/// <summary>
/// Read a string that is terminated by a newline but contains a quoted portion that
/// may also contain a newline from the stream
/// may also contain a newline from the underlying stream
/// </summary>
public static string? ReadQuotedString(this BinaryReader reader)
=> reader.ReadQuotedString(Encoding.Default);
/// <summary>
/// Read a string that is terminated by a newline but contains a quoted portion that
/// may also contain a newline from the stream
/// may also contain a newline from the underlying stream
/// </summary>
public static string? ReadQuotedString(this BinaryReader reader, Encoding encoding)
{
@@ -457,15 +459,184 @@ namespace SabreTools.IO.Extensions
/// Read a <typeparamref name="T"/> from the underlying stream
/// </summary>
public static T? ReadType<T>(this BinaryReader reader)
=> (T?)reader.ReadType(typeof(T));
/// <summary>
/// Read a <paramref name="type"/> from the underlying stream
/// </summary>
public static object? ReadType(this BinaryReader reader, Type type)
{
int typeSize = Marshal.SizeOf(typeof(T));
byte[] buffer = reader.ReadBytes(typeSize);
if (type.IsClass || (type.IsValueType && !type.IsPrimitive))
return ReadComplexType(reader, type);
else
return ReadNormalType(reader, type);
}
var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
var data = (T?)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
handle.Free();
/// <summary>
/// Read a <paramref name="type"/> from the underlying stream
/// </summary>
private static object? ReadNormalType(BinaryReader reader, Type type)
{
try
{
int typeSize = Marshal.SizeOf(type);
byte[] buffer = reader.ReadBytes(typeSize);;
return data;
var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
var data = Marshal.PtrToStructure(handle.AddrOfPinnedObject(), type);
handle.Free();
return data;
}
catch
{
return null;
}
}
/// <summary>
/// Read a <paramref name="type"/> from the underlying stream
/// </summary>
private static object? ReadComplexType(this BinaryReader reader, Type type)
{
try
{
var instance = Activator.CreateInstance(type);
if (instance == null)
return null;
// Get the layout attribute
var layoutAttr = type.GetCustomAttributes(typeof(StructLayoutAttribute), true).FirstOrDefault() as StructLayoutAttribute;
// Get the layout type
LayoutKind layoutKind = LayoutKind.Auto;
if (layoutAttr != null)
layoutKind = layoutAttr.Value;
else if (type.IsAutoLayout)
layoutKind = LayoutKind.Auto;
else if (type.IsExplicitLayout)
layoutKind = LayoutKind.Explicit;
else if (type.IsLayoutSequential)
layoutKind = LayoutKind.Sequential;
// Get the encoding to use
Encoding encoding = layoutAttr?.CharSet switch
{
CharSet.None => Encoding.ASCII,
CharSet.Ansi => Encoding.ASCII,
CharSet.Unicode => Encoding.Unicode,
CharSet.Auto => Encoding.ASCII, // UTF-8 on Unix
_ => Encoding.ASCII,
};
// Cache the current offset
long currentOffset = reader.BaseStream.Position;
// Loop through the fields and set them
var fields = type.GetFields();
foreach (var fi in fields)
{
// If we have an explicit layout, move accordingly
if (layoutKind == LayoutKind.Explicit)
{
var fieldOffset = fi.GetCustomAttributes(typeof(FieldOffsetAttribute), true).FirstOrDefault() as FieldOffsetAttribute;
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)))
{
var value = ReadStringType(reader, encoding, instance, fi);
fi.SetValue(instance, value);
}
else
{
var value = reader.ReadType(fi.FieldType);
fi.SetValue(instance, value);
}
}
/// <summary>
/// Read a string type field for an object
/// </summary>
private static string? ReadStringType(BinaryReader reader, Encoding encoding, object instance, FieldInfo fi)
{
var marshalAsAttr = fi.GetCustomAttributes(typeof(MarshalAsAttribute), true).FirstOrDefault() as MarshalAsAttribute;
switch (marshalAsAttr?.Value)
{
case UnmanagedType.AnsiBStr:
byte ansiLength = reader.ReadByte();
byte[] ansiBytes = reader.ReadBytes(ansiLength);
return Encoding.ASCII.GetString(ansiBytes);
case UnmanagedType.BStr:
ushort bstrLength = reader.ReadUInt16();
byte[] bstrBytes = reader.ReadBytes(bstrLength);
return Encoding.ASCII.GetString(bstrBytes);
// TODO: Handle length from another field
case UnmanagedType.ByValTStr:
int byvalLength = marshalAsAttr.SizeConst;
byte[] byvalBytes = reader.ReadBytes(byvalLength);
return encoding.GetString(byvalBytes);
case UnmanagedType.LPStr:
case null:
var lpstrBytes = new List<byte>();
while (true)
{
byte next = reader.ReadByte();
if (next == 0x00)
break;
lpstrBytes.Add(next);
if (reader.BaseStream.Position >= reader.BaseStream.Length)
break;
}
return Encoding.ASCII.GetString([.. lpstrBytes]);
case UnmanagedType.LPWStr:
var lpwstrBytes = new List<byte>();
while (true)
{
ushort next = reader.ReadUInt16();
if (next == 0x0000)
break;
lpwstrBytes.AddRange(BitConverter.GetBytes(next));
if (reader.BaseStream.Position >= reader.BaseStream.Length)
break;
}
return Encoding.ASCII.GetString([.. lpwstrBytes]);
// No support required yet
case UnmanagedType.LPTStr:
#if NET472_OR_GREATER || NETCOREAPP
case UnmanagedType.LPUTF8Str:
#endif
case UnmanagedType.TBStr:
default:
return null;
}
}
}
}

View File

@@ -413,6 +413,7 @@ namespace SabreTools.IO.Extensions
/// <summary>
/// Write a <typeparamref name="T"/> to the underlying stream
/// </summary>
/// TODO: Fix writing as reading was fixed
public static bool WriteType<T>(this BinaryWriter writer, T? value)
{
// Handle the null case

View File

@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
#if NET7_0_OR_GREATER
using System.Numerics;
#endif
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
@@ -583,18 +585,187 @@ namespace SabreTools.IO.Extensions
}
/// <summary>
/// Read a <typeparamref name="T"/> from the byte array
/// Read a <typeparamref name="T"/> from the stream
/// </summary>
public static T? ReadType<T>(this byte[] content, ref int offset)
=> (T?)content.ReadType(ref offset, typeof(T));
/// <summary>
/// Read a <paramref name="type"/> from the stream
/// </summary>
public static object? ReadType(this byte[] content, ref int offset, Type type)
{
int typeSize = Marshal.SizeOf(typeof(T));
byte[] buffer = ReadToBuffer(content, ref offset, typeSize);
if (type.IsClass || (type.IsValueType && !type.IsPrimitive))
return ReadComplexType(content, ref offset, type);
else
return ReadNormalType(content, ref offset, type);
}
var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
var data = (T?)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
handle.Free();
/// <summary>
/// Read a <paramref name="type"/> from the stream
/// </summary>
private static object? ReadNormalType(byte[] content, ref int offset, Type type)
{
try
{
int typeSize = Marshal.SizeOf(type);
byte[] buffer = ReadToBuffer(content, ref offset, typeSize);
return data;
var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
var data = Marshal.PtrToStructure(handle.AddrOfPinnedObject(), type);
handle.Free();
return data;
}
catch
{
return null;
}
}
/// <summary>
/// Read a <paramref name="type"/> from the stream
/// </summary>
private static object? ReadComplexType(this byte[] content, ref int offset, Type type)
{
try
{
var instance = Activator.CreateInstance(type);
if (instance == null)
return null;
// Get the layout attribute
var layoutAttr = type.GetCustomAttributes(typeof(StructLayoutAttribute), true).FirstOrDefault() as StructLayoutAttribute;
// Get the layout type
LayoutKind layoutKind = LayoutKind.Auto;
if (layoutAttr != null)
layoutKind = layoutAttr.Value;
else if (type.IsAutoLayout)
layoutKind = LayoutKind.Auto;
else if (type.IsExplicitLayout)
layoutKind = LayoutKind.Explicit;
else if (type.IsLayoutSequential)
layoutKind = LayoutKind.Sequential;
// Get the encoding to use
Encoding encoding = layoutAttr?.CharSet switch
{
CharSet.None => Encoding.ASCII,
CharSet.Ansi => Encoding.ASCII,
CharSet.Unicode => Encoding.Unicode,
CharSet.Auto => Encoding.ASCII, // UTF-8 on Unix
_ => Encoding.ASCII,
};
// Cache the current offset
int currentOffset = offset;
// Loop through the fields and set them
var fields = type.GetFields();
foreach (var fi in fields)
{
// If we have an explicit layout, move accordingly
if (layoutKind == LayoutKind.Explicit)
{
var fieldOffset = fi.GetCustomAttributes(typeof(FieldOffsetAttribute), true).FirstOrDefault() as FieldOffsetAttribute;
offset = currentOffset + fieldOffset?.Value ?? 0;
}
SetField(content, ref offset, encoding, fields, instance, fi);
}
return instance;
}
catch
{
return null;
}
}
/// <summary>
/// Set a single field on an object
/// </summary>
private static void SetField(byte[] content, ref int offset, Encoding encoding, FieldInfo[] fields, object instance, FieldInfo fi)
{
if (fi.FieldType.IsAssignableFrom(typeof(string)))
{
var value = ReadStringType(content, ref offset, encoding, instance, fi);
fi.SetValue(instance, value);
}
else
{
var value = content.ReadType(ref offset, fi.FieldType);
fi.SetValue(instance, value);
}
}
/// <summary>
/// Read a string type field for an object
/// </summary>
private static string? ReadStringType(byte[] content, ref int offset, Encoding encoding, object instance, FieldInfo fi)
{
var marshalAsAttr = fi.GetCustomAttributes(typeof(MarshalAsAttribute), true).FirstOrDefault() as MarshalAsAttribute;
switch (marshalAsAttr?.Value)
{
case UnmanagedType.AnsiBStr:
byte ansiLength = content.ReadByteValue(ref offset);
byte[] ansiBytes = content.ReadBytes(ref offset, ansiLength);
return Encoding.ASCII.GetString(ansiBytes);
case UnmanagedType.BStr:
ushort bstrLength = content.ReadUInt16(ref offset);
byte[] bstrBytes = content.ReadBytes(ref offset, bstrLength);
return Encoding.ASCII.GetString(bstrBytes);
// TODO: Handle length from another field
case UnmanagedType.ByValTStr:
int byvalLength = marshalAsAttr.SizeConst;
byte[] byvalBytes = content.ReadBytes(ref offset, byvalLength);
return encoding.GetString(byvalBytes);
case UnmanagedType.LPStr:
case null:
var lpstrBytes = new List<byte>();
while (true)
{
byte next = content.ReadByteValue(ref offset);
if (next == 0x00)
break;
lpstrBytes.Add(next);
if (offset >= content.Length)
break;
}
return Encoding.ASCII.GetString([.. lpstrBytes]);
case UnmanagedType.LPWStr:
var lpwstrBytes = new List<byte>();
while (true)
{
ushort next = content.ReadUInt16(ref offset);
if (next == 0x0000)
break;
lpwstrBytes.AddRange(BitConverter.GetBytes(next));
if (offset >= content.Length)
break;
}
return Encoding.ASCII.GetString([.. lpwstrBytes]);
// No support required yet
case UnmanagedType.LPTStr:
#if NET472_OR_GREATER || NETCOREAPP
case UnmanagedType.LPUTF8Str:
#endif
case UnmanagedType.TBStr:
default:
return null;
}
}
/// <summary>
@@ -612,7 +783,7 @@ namespace SabreTools.IO.Extensions
// If there are not enough bytes
if (offset + length > content.Length)
throw new System.IO.EndOfStreamException(nameof(content));
throw new System.IO.EndOfStreamException($"Requested to read {nameof(length)} bytes from {nameof(content)}, {content.Length - offset} returned");
// Handle the general case, forcing a read of the correct length
byte[] buffer = new byte[length];

View File

@@ -564,6 +564,7 @@ namespace SabreTools.IO.Extensions
/// <summary>
/// Write a <typeparamref name="T"/> to the byte array
/// </summary>
/// TODO: Fix writing as reading was fixed
public static bool WriteType<T>(this byte[] content, ref int offset, T? value)
{
// Handle the null case

View File

@@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
#if NET7_0_OR_GREATER
using System.Numerics;
#endif
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
@@ -570,15 +572,184 @@ namespace SabreTools.IO.Extensions
/// Read a <typeparamref name="T"/> from the stream
/// </summary>
public static T? ReadType<T>(this Stream stream)
=> (T?)stream.ReadType(typeof(T));
/// <summary>
/// Read a <paramref name="type"/> from the stream
/// </summary>
public static object? ReadType(this Stream stream, Type type)
{
int typeSize = Marshal.SizeOf(typeof(T));
byte[] buffer = ReadToBuffer(stream, typeSize);
if (type.IsClass || (type.IsValueType && !type.IsPrimitive))
return ReadComplexType(stream, type);
else
return ReadNormalType(stream, type);
}
var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
var data = (T?)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
handle.Free();
/// <summary>
/// Read a <paramref name="type"/> from the stream
/// </summary>
private static object? ReadNormalType(Stream stream, Type type)
{
try
{
int typeSize = Marshal.SizeOf(type);
byte[] buffer = ReadToBuffer(stream, typeSize);
return data;
var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
var data = Marshal.PtrToStructure(handle.AddrOfPinnedObject(), type);
handle.Free();
return data;
}
catch
{
return null;
}
}
/// <summary>
/// Read a <paramref name="type"/> from the stream
/// </summary>
private static object? ReadComplexType(this Stream stream, Type type)
{
try
{
var instance = Activator.CreateInstance(type);
if (instance == null)
return null;
// Get the layout attribute
var layoutAttr = type.GetCustomAttributes(typeof(StructLayoutAttribute), true).FirstOrDefault() as StructLayoutAttribute;
// Get the layout type
LayoutKind layoutKind = LayoutKind.Auto;
if (layoutAttr != null)
layoutKind = layoutAttr.Value;
else if (type.IsAutoLayout)
layoutKind = LayoutKind.Auto;
else if (type.IsExplicitLayout)
layoutKind = LayoutKind.Explicit;
else if (type.IsLayoutSequential)
layoutKind = LayoutKind.Sequential;
// Get the encoding to use
Encoding encoding = layoutAttr?.CharSet switch
{
CharSet.None => Encoding.ASCII,
CharSet.Ansi => Encoding.ASCII,
CharSet.Unicode => Encoding.Unicode,
CharSet.Auto => Encoding.ASCII, // UTF-8 on Unix
_ => Encoding.ASCII,
};
// Cache the current offset
long currentOffset = stream.Position;
// Loop through the fields and set them
var fields = type.GetFields();
foreach (var fi in fields)
{
// If we have an explicit layout, move accordingly
if (layoutKind == LayoutKind.Explicit)
{
var fieldOffset = fi.GetCustomAttributes(typeof(FieldOffsetAttribute), true).FirstOrDefault() as FieldOffsetAttribute;
stream.Seek(currentOffset + fieldOffset?.Value ?? 0, SeekOrigin.Begin);
}
SetField(stream, encoding, fields, instance, fi);
}
return instance;
}
catch
{
return null;
}
}
/// <summary>
/// Set a single field on an object
/// </summary>
private static void SetField(Stream stream, Encoding encoding, FieldInfo[] fields, object instance, FieldInfo fi)
{
if (fi.FieldType.IsAssignableFrom(typeof(string)))
{
var value = ReadStringType(stream, encoding, instance, fi);
fi.SetValue(instance, value);
}
else
{
var value = stream.ReadType(fi.FieldType);
fi.SetValue(instance, value);
}
}
/// <summary>
/// Read a string type field for an object
/// </summary>
private static string? ReadStringType(Stream stream, Encoding encoding, object instance, FieldInfo fi)
{
var marshalAsAttr = fi.GetCustomAttributes(typeof(MarshalAsAttribute), true).FirstOrDefault() as MarshalAsAttribute;
switch (marshalAsAttr?.Value)
{
case UnmanagedType.AnsiBStr:
byte ansiLength = stream.ReadByteValue();
byte[] ansiBytes = stream.ReadBytes(ansiLength);
return Encoding.ASCII.GetString(ansiBytes);
case UnmanagedType.BStr:
ushort bstrLength = stream.ReadUInt16();
byte[] bstrBytes = stream.ReadBytes(bstrLength);
return Encoding.ASCII.GetString(bstrBytes);
// TODO: Handle length from another field
case UnmanagedType.ByValTStr:
int byvalLength = marshalAsAttr.SizeConst;
byte[] byvalBytes = stream.ReadBytes(byvalLength);
return encoding.GetString(byvalBytes);
case UnmanagedType.LPStr:
case null:
var lpstrBytes = new List<byte>();
while (true)
{
byte next = stream.ReadByteValue();
if (next == 0x00)
break;
lpstrBytes.Add(next);
if (stream.Position >= stream.Length)
break;
}
return Encoding.ASCII.GetString([.. lpstrBytes]);
case UnmanagedType.LPWStr:
var lpwstrBytes = new List<byte>();
while (true)
{
ushort next = stream.ReadUInt16();
if (next == 0x0000)
break;
lpwstrBytes.AddRange(BitConverter.GetBytes(next));
if (stream.Position >= stream.Length)
break;
}
return Encoding.ASCII.GetString([.. lpwstrBytes]);
// No support required yet
case UnmanagedType.LPTStr:
#if NET472_OR_GREATER || NETCOREAPP
case UnmanagedType.LPUTF8Str:
#endif
case UnmanagedType.TBStr:
default:
return null;
}
}
/// <summary>
@@ -598,7 +769,7 @@ namespace SabreTools.IO.Extensions
byte[] buffer = new byte[length];
int read = stream.Read(buffer, 0, length);
if (read < length)
throw new EndOfStreamException(nameof(stream));
throw new EndOfStreamException($"Requested to read {nameof(length)} bytes from {nameof(stream)}, {read} returned");
return buffer;
}

View File

@@ -565,6 +565,7 @@ namespace SabreTools.IO.Extensions
/// <summary>
/// Write a <typeparamref name="T"/> to the stream
/// </summary>
/// TODO: Fix writing as reading was fixed
public static bool WriteType<T>(this Stream stream, T? value)
{
// Handle the null case

View File

@@ -7,7 +7,8 @@
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>1.4.5</Version>
<Version>1.4.6</Version>
<WarningsNotAsErrors>CS0618</WarningsNotAsErrors>
<!-- Package Properties -->
<Authors>Matt Nadareski</Authors>