mirror of
https://github.com/SabreTools/SabreTools.IO.git
synced 2026-02-10 13:54:50 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd08925411 | ||
|
|
6ea8aab7c7 | ||
|
|
561dbdcc9a | ||
|
|
b4bad28823 |
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -16,5 +16,8 @@ namespace SabreTools.IO.Test.Extensions
|
||||
|
||||
[FieldOffset(6)]
|
||||
public short FourthValue;
|
||||
|
||||
[FieldOffset(8), MarshalAs(UnmanagedType.LPStr)]
|
||||
public string? FifthValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,5 +12,8 @@ namespace SabreTools.IO.Test.Extensions
|
||||
public ushort ThirdValue;
|
||||
|
||||
public short FourthValue;
|
||||
|
||||
[MarshalAs(UnmanagedType.LPStr)]
|
||||
public string? FifthValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user