diff --git a/SabreTools.IO.Test/Extensions/BinaryReaderExtensionsTests.cs b/SabreTools.IO.Test/Extensions/BinaryReaderExtensionsTests.cs index 5ebebdd..6ccf9e6 100644 --- a/SabreTools.IO.Test/Extensions/BinaryReaderExtensionsTests.cs +++ b/SabreTools.IO.Test/Extensions/BinaryReaderExtensionsTests.cs @@ -497,6 +497,10 @@ namespace SabreTools.IO.Test.Extensions 0x00, 0xFF, 0xFF, 0x00, 0xAA, 0x55, 0x55, 0xAA, 0x55, 0xAA, 0xAA, 0x55, + + // LPArray + 0x04, 0x00, + 0x00, 0x01, 0x02, 0x03, ]; var stream = new MemoryStream(structBytes); @@ -512,6 +516,8 @@ namespace SabreTools.IO.Test.Extensions new TestStructPoint { X = 0x55AA, Y = 0xAA55 }, new TestStructPoint { X = 0xAA55, Y = 0x55AA }, ], + LPByteArrayLength = 0x0004, + LPByteArray = [0x00, 0x01, 0x02, 0x03], }; var read = br.ReadType(); Assert.NotNull(read.ByteArray); @@ -520,6 +526,9 @@ namespace SabreTools.IO.Test.Extensions Assert.True(expected.IntArray.SequenceEqual(read.IntArray)); Assert.NotNull(read.StructArray); Assert.True(expected.StructArray.SequenceEqual(read.StructArray)); + Assert.Equal(expected.LPByteArrayLength, read.LPByteArrayLength); + Assert.NotNull(read.LPByteArray); + Assert.True(expected.LPByteArray.SequenceEqual(read.LPByteArray)); } } } \ No newline at end of file diff --git a/SabreTools.IO.Test/Extensions/ByteArrayExtensionsReadTests.cs b/SabreTools.IO.Test/Extensions/ByteArrayExtensionsReadTests.cs index 730ac34..bbde3aa 100644 --- a/SabreTools.IO.Test/Extensions/ByteArrayExtensionsReadTests.cs +++ b/SabreTools.IO.Test/Extensions/ByteArrayExtensionsReadTests.cs @@ -461,6 +461,10 @@ namespace SabreTools.IO.Test.Extensions 0x00, 0xFF, 0xFF, 0x00, 0xAA, 0x55, 0x55, 0xAA, 0x55, 0xAA, 0xAA, 0x55, + + // LPArray + 0x04, 0x00, + 0x00, 0x01, 0x02, 0x03, ]; int offset = 0; @@ -475,6 +479,8 @@ namespace SabreTools.IO.Test.Extensions new TestStructPoint { X = 0x55AA, Y = 0xAA55 }, new TestStructPoint { X = 0xAA55, Y = 0x55AA }, ], + LPByteArrayLength = 0x0004, + LPByteArray = [0x00, 0x01, 0x02, 0x03], }; var read = structBytes.ReadType(ref offset); Assert.NotNull(read.ByteArray); @@ -483,6 +489,9 @@ namespace SabreTools.IO.Test.Extensions Assert.True(expected.IntArray.SequenceEqual(read.IntArray)); Assert.NotNull(read.StructArray); Assert.True(expected.StructArray.SequenceEqual(read.StructArray)); + Assert.Equal(expected.LPByteArrayLength, read.LPByteArrayLength); + Assert.NotNull(read.LPByteArray); + Assert.True(expected.LPByteArray.SequenceEqual(read.LPByteArray)); } } } \ No newline at end of file diff --git a/SabreTools.IO.Test/Extensions/StreamExtensionsReadTests.cs b/SabreTools.IO.Test/Extensions/StreamExtensionsReadTests.cs index 9e05520..3adbf4a 100644 --- a/SabreTools.IO.Test/Extensions/StreamExtensionsReadTests.cs +++ b/SabreTools.IO.Test/Extensions/StreamExtensionsReadTests.cs @@ -455,6 +455,10 @@ namespace SabreTools.IO.Test.Extensions 0x00, 0xFF, 0xFF, 0x00, 0xAA, 0x55, 0x55, 0xAA, 0x55, 0xAA, 0xAA, 0x55, + + // LPArray + 0x04, 0x00, + 0x00, 0x01, 0x02, 0x03, ]; var stream = new MemoryStream(structBytes); @@ -469,6 +473,8 @@ namespace SabreTools.IO.Test.Extensions new TestStructPoint { X = 0x55AA, Y = 0xAA55 }, new TestStructPoint { X = 0xAA55, Y = 0x55AA }, ], + LPByteArrayLength = 0x0004, + LPByteArray = [0x00, 0x01, 0x02, 0x03], }; var read = stream.ReadType(); Assert.NotNull(read.ByteArray); @@ -477,6 +483,9 @@ namespace SabreTools.IO.Test.Extensions Assert.True(expected.IntArray.SequenceEqual(read.IntArray)); Assert.NotNull(read.StructArray); Assert.True(expected.StructArray.SequenceEqual(read.StructArray)); + Assert.Equal(expected.LPByteArrayLength, read.LPByteArrayLength); + Assert.NotNull(read.LPByteArray); + Assert.True(expected.LPByteArray.SequenceEqual(read.LPByteArray)); } } } \ No newline at end of file diff --git a/SabreTools.IO.Test/Extensions/TestStructArrays.cs b/SabreTools.IO.Test/Extensions/TestStructArrays.cs index a5c4f6b..4f94166 100644 --- a/SabreTools.IO.Test/Extensions/TestStructArrays.cs +++ b/SabreTools.IO.Test/Extensions/TestStructArrays.cs @@ -22,6 +22,17 @@ namespace SabreTools.IO.Test.Extensions /// [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] public TestStructPoint[]? StructArray; + + /// + /// Length of + /// + public ushort LPByteArrayLength; + + /// + /// 4 entry byte array whose length is defined by + /// + [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] + public byte[]? LPByteArray; // /// // /// 4 entry nested byte array diff --git a/SabreTools.IO/Extensions/BinaryReaderExtensions.cs b/SabreTools.IO/Extensions/BinaryReaderExtensions.cs index 4600943..6e2527e 100644 --- a/SabreTools.IO/Extensions/BinaryReaderExtensions.cs +++ b/SabreTools.IO/Extensions/BinaryReaderExtensions.cs @@ -570,7 +570,7 @@ namespace SabreTools.IO.Extensions } else if (fi.FieldType.IsArray) { - var value = ReadArrayType(reader, fi); + var value = ReadArrayType(reader, fields, instance, fi); fi.SetValue(instance, Convert.ChangeType(value, fi.FieldType)); } else @@ -583,15 +583,24 @@ namespace SabreTools.IO.Extensions /// /// Read an array type field for an object /// - /// TODO: Handle LPArray + SizeParamIndex - private static Array ReadArrayType(BinaryReader reader, FieldInfo fi) + private static Array ReadArrayType(BinaryReader reader, FieldInfo[] fields, object instance, FieldInfo fi) { var marshalAsAttr = fi.GetCustomAttributes(typeof(MarshalAsAttribute), true).FirstOrDefault() as MarshalAsAttribute; - if (marshalAsAttr?.Value != UnmanagedType.ByValArray) + if (marshalAsAttr == null) return new object[0]; // Get the number of elements expected - int elementCount = marshalAsAttr?.SizeConst ?? -1; + int elementCount = -1; + if (marshalAsAttr.Value == UnmanagedType.ByValArray) + { + elementCount = marshalAsAttr.SizeConst; + } + else if (marshalAsAttr.Value == UnmanagedType.LPArray) + { + elementCount = marshalAsAttr.SizeConst; + if (marshalAsAttr.SizeParamIndex >= 0) + elementCount = GetSizeFromField(marshalAsAttr, fields, instance); + } // Get the item type for the array Type elementType = fi.FieldType.GetElementType() ?? typeof(object); @@ -610,6 +619,35 @@ namespace SabreTools.IO.Extensions return arr; } + /// + /// Get the expected LPArray size + /// + private static int GetSizeFromField(MarshalAsAttribute marshalAsAttr, FieldInfo[] fields, object instance) + { + // If the index is invalid + if (marshalAsAttr.SizeParamIndex < 0) + return -1; + + // Get the size field + var sizeField = fields[marshalAsAttr.SizeParamIndex]; + if (sizeField == null) + return -1; + + // Cast based on the field type + return sizeField.GetValue(instance) switch + { + sbyte val => val, + byte val => val, + short val => val, + ushort val => val, + int val => val, + uint val => (int)val, + long val => (int)val, + ulong val => (int)val, + _ => -1, + }; + } + /// /// Read a string type field for an object /// diff --git a/SabreTools.IO/Extensions/ByteArrayReaderExtensions.cs b/SabreTools.IO/Extensions/ByteArrayReaderExtensions.cs index 06132d0..744dc49 100644 --- a/SabreTools.IO/Extensions/ByteArrayReaderExtensions.cs +++ b/SabreTools.IO/Extensions/ByteArrayReaderExtensions.cs @@ -697,7 +697,7 @@ namespace SabreTools.IO.Extensions } else if (fi.FieldType.IsArray) { - var value = ReadArrayType(content, ref offset, fi); + var value = ReadArrayType(content, ref offset, fields, instance, fi); fi.SetValue(instance, Convert.ChangeType(value, fi.FieldType)); } else @@ -710,15 +710,24 @@ namespace SabreTools.IO.Extensions /// /// Read an array type field for an object /// - /// TODO: Handle LPArray + SizeParamIndex - private static Array ReadArrayType(byte[] content, ref int offset, FieldInfo fi) + private static Array ReadArrayType(byte[] content, ref int offset, FieldInfo[] fields, object instance, FieldInfo fi) { var marshalAsAttr = fi.GetCustomAttributes(typeof(MarshalAsAttribute), true).FirstOrDefault() as MarshalAsAttribute; - if (marshalAsAttr?.Value != UnmanagedType.ByValArray) + if (marshalAsAttr == null) return new object[0]; // Get the number of elements expected - int elementCount = marshalAsAttr?.SizeConst ?? -1; + int elementCount = -1; + if (marshalAsAttr.Value == UnmanagedType.ByValArray) + { + elementCount = marshalAsAttr.SizeConst; + } + else if (marshalAsAttr.Value == UnmanagedType.LPArray) + { + elementCount = marshalAsAttr.SizeConst; + if (marshalAsAttr.SizeParamIndex >= 0) + elementCount = GetSizeFromField(marshalAsAttr, fields, instance); + } // Get the item type for the array Type elementType = fi.FieldType.GetElementType() ?? typeof(object); @@ -737,6 +746,35 @@ namespace SabreTools.IO.Extensions return arr; } + /// + /// Get the expected LPArray size + /// + private static int GetSizeFromField(MarshalAsAttribute marshalAsAttr, FieldInfo[] fields, object instance) + { + // If the index is invalid + if (marshalAsAttr.SizeParamIndex < 0) + return -1; + + // Get the size field + var sizeField = fields[marshalAsAttr.SizeParamIndex]; + if (sizeField == null) + return -1; + + // Cast based on the field type + return sizeField.GetValue(instance) switch + { + sbyte val => val, + byte val => val, + short val => val, + ushort val => val, + int val => val, + uint val => (int)val, + long val => (int)val, + ulong val => (int)val, + _ => -1, + }; + } + /// /// Read a string type field for an object /// diff --git a/SabreTools.IO/Extensions/StreamReaderExtensions.cs b/SabreTools.IO/Extensions/StreamReaderExtensions.cs index 4ec2932..c02017f 100644 --- a/SabreTools.IO/Extensions/StreamReaderExtensions.cs +++ b/SabreTools.IO/Extensions/StreamReaderExtensions.cs @@ -681,7 +681,7 @@ namespace SabreTools.IO.Extensions } else if (fi.FieldType.IsArray) { - var value = ReadArrayType(stream, fi); + var value = ReadArrayType(stream, fields, instance, fi); fi.SetValue(instance, Convert.ChangeType(value, fi.FieldType)); } else @@ -694,15 +694,24 @@ namespace SabreTools.IO.Extensions /// /// Read an array type field for an object /// - /// TODO: Handle LPArray + SizeParamIndex - private static Array ReadArrayType(Stream stream, FieldInfo fi) + private static Array ReadArrayType(Stream stream, FieldInfo[] fields, object instance, FieldInfo fi) { var marshalAsAttr = fi.GetCustomAttributes(typeof(MarshalAsAttribute), true).FirstOrDefault() as MarshalAsAttribute; - if (marshalAsAttr?.Value != UnmanagedType.ByValArray) + if (marshalAsAttr == null) return new object[0]; // Get the number of elements expected - int elementCount = marshalAsAttr?.SizeConst ?? -1; + int elementCount = -1; + if (marshalAsAttr.Value == UnmanagedType.ByValArray) + { + elementCount = marshalAsAttr.SizeConst; + } + else if (marshalAsAttr.Value == UnmanagedType.LPArray) + { + elementCount = marshalAsAttr.SizeConst; + if (marshalAsAttr.SizeParamIndex >= 0) + elementCount = GetSizeFromField(marshalAsAttr, fields, instance); + } // Get the item type for the array Type elementType = fi.FieldType.GetElementType() ?? typeof(object); @@ -721,6 +730,35 @@ namespace SabreTools.IO.Extensions return arr; } + /// + /// Get the expected LPArray size + /// + private static int GetSizeFromField(MarshalAsAttribute marshalAsAttr, FieldInfo[] fields, object instance) + { + // If the index is invalid + if (marshalAsAttr.SizeParamIndex < 0) + return -1; + + // Get the size field + var sizeField = fields[marshalAsAttr.SizeParamIndex]; + if (sizeField == null) + return -1; + + // Cast based on the field type + return sizeField.GetValue(instance) switch + { + sbyte val => val, + byte val => val, + short val => val, + ushort val => val, + int val => val, + uint val => (int)val, + long val => (int)val, + ulong val => (int)val, + _ => -1, + }; + } + /// /// Read a string type field for an object ///