Preliminary support for Matlab objects
This commit is contained in:
parent
126d8a7483
commit
10aa558152
@ -292,7 +292,38 @@ namespace MatFileHandler.Tests
|
||||
[Test]
|
||||
public void TestObject()
|
||||
{
|
||||
Assert.That(() => GetTests("bad")["object"], Throws.TypeOf<HandlerException>());
|
||||
var matFile = GetTests("good")["object"];
|
||||
var obj = matFile["object_"].Value as IMatObject;
|
||||
Assert.IsNotNull(obj);
|
||||
Assert.That(obj.ClassName, Is.EqualTo("Point"));
|
||||
Assert.That(obj.FieldNames, Is.EquivalentTo(new[] { "x", "y" }));
|
||||
Assert.That(obj["x", 0].ConvertToDoubleArray(), Is.EqualTo(new[] { 3.0 }));
|
||||
Assert.That(obj["y", 0].ConvertToDoubleArray(), Is.EqualTo(new[] { 5.0 }));
|
||||
Assert.That(obj["x", 1].ConvertToDoubleArray(), Is.EqualTo(new[] { -2.0 }));
|
||||
Assert.That(obj["y", 1].ConvertToDoubleArray(), Is.EqualTo(new[] { 6.0 }));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test reading another object.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestObject2()
|
||||
{
|
||||
var matFile = GetTests("good")["object2"];
|
||||
var obj = matFile["object2"].Value as IMatObject;
|
||||
Assert.IsNotNull(obj);
|
||||
Assert.That(obj.ClassName, Is.EqualTo("Point"));
|
||||
Assert.That(obj.FieldNames, Is.EquivalentTo(new[] { "x", "y" }));
|
||||
Assert.That(obj["x", 0, 0].ConvertToDoubleArray(), Is.EqualTo(new[] { 3.0 }));
|
||||
Assert.That(obj["y", 0, 0].ConvertToDoubleArray(), Is.EqualTo(new[] { 5.0 }));
|
||||
Assert.That(obj["x", 1, 0].ConvertToDoubleArray(), Is.EqualTo(new[] { 1.0 }));
|
||||
Assert.That(obj["y", 1, 0].ConvertToDoubleArray(), Is.EqualTo(new[] { 0.0 }));
|
||||
Assert.That(obj["x", 0, 1].ConvertToDoubleArray(), Is.EqualTo(new[] { -2.0 }));
|
||||
Assert.That(obj["y", 0, 1].ConvertToDoubleArray(), Is.EqualTo(new[] { 6.0 }));
|
||||
Assert.That(obj["x", 1, 1].ConvertToDoubleArray(), Is.EqualTo(new[] { 0.0 }));
|
||||
Assert.That(obj["y", 1, 1].ConvertToDoubleArray(), Is.EqualTo(new[] { 1.0 }));
|
||||
Assert.That(obj[0, 1]["x"].ConvertToDoubleArray(), Is.EqualTo(new[] { -2.0 }));
|
||||
Assert.That(obj[2]["x"].ConvertToDoubleArray(), Is.EqualTo(new[] { -2.0 }));
|
||||
}
|
||||
|
||||
private static AbstractTestDataFactory<IMatFile> GetTests(string factoryName) =>
|
||||
|
0
MatFileHandler.Tests/test-data/bad/object.mat → MatFileHandler.Tests/test-data/good/object.mat
Executable file → Normal file
0
MatFileHandler.Tests/test-data/bad/object.mat → MatFileHandler.Tests/test-data/good/object.mat
Executable file → Normal file
BIN
MatFileHandler.Tests/test-data/good/object2.mat
Normal file
BIN
MatFileHandler.Tests/test-data/good/object2.mat
Normal file
Binary file not shown.
@ -85,9 +85,9 @@ namespace MatFileHandler
|
||||
MxUInt64 = 15,
|
||||
|
||||
/// <summary>
|
||||
/// Undocumented object (?) array type.
|
||||
/// Undocumented opaque object type.
|
||||
/// </summary>
|
||||
MxNewObject = 17,
|
||||
MxOpaque = 17,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -12,14 +12,25 @@ namespace MatFileHandler
|
||||
/// <summary>
|
||||
/// Functions for reading data elements from a .mat file.
|
||||
/// </summary>
|
||||
internal static class DataElementReader
|
||||
internal class DataElementReader
|
||||
{
|
||||
private readonly SubsystemData subsystemData;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DataElementReader"/> class.
|
||||
/// </summary>
|
||||
/// <param name="subsystemData">Reference to file's SubsystemData.</param>
|
||||
public DataElementReader(SubsystemData subsystemData)
|
||||
{
|
||||
this.subsystemData = subsystemData ?? throw new ArgumentNullException(nameof(subsystemData));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a data element.
|
||||
/// </summary>
|
||||
/// <param name="reader">Input reader.</param>
|
||||
/// <returns>Data element.</returns>
|
||||
public static DataElement Read(BinaryReader reader)
|
||||
public DataElement Read(BinaryReader reader)
|
||||
{
|
||||
var (dataReader, tag) = ReadTag(reader);
|
||||
DataElement result;
|
||||
@ -66,6 +77,7 @@ namespace MatFileHandler
|
||||
default:
|
||||
throw new NotSupportedException("Unknown element.");
|
||||
}
|
||||
|
||||
if (tag.Type != DataType.MiCompressed)
|
||||
{
|
||||
var position = reader.BaseStream.Position;
|
||||
@ -74,9 +86,109 @@ namespace MatFileHandler
|
||||
reader.ReadBytes(8 - (int)(position % 8));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static (int[] dimensions, int[] links, int classIndex) ParseOpaqueData(MatNumericalArrayOf<uint> data)
|
||||
{
|
||||
var nDims = data.Data[1];
|
||||
var dimensions = new int[nDims];
|
||||
var position = 2;
|
||||
for (var i = 0; i < nDims; i++)
|
||||
{
|
||||
dimensions[i] = (int)data.Data[position];
|
||||
position++;
|
||||
}
|
||||
|
||||
var count = dimensions.NumberOfElements();
|
||||
var links = new int[count];
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
links[i] = (int)data.Data[position];
|
||||
position++;
|
||||
}
|
||||
|
||||
var classIndex = (int)data.Data[position];
|
||||
|
||||
return (dimensions, links, classIndex);
|
||||
}
|
||||
|
||||
private static ArrayFlags ReadArrayFlags(DataElement element)
|
||||
{
|
||||
var flagData = (element as MiNum<uint>)?.Data ??
|
||||
throw new HandlerException("Unexpected type in array flags.");
|
||||
var class_ = (ArrayType)(flagData[0] & 0xff);
|
||||
var variableFlags = (flagData[0] >> 8) & 0x0e;
|
||||
return new ArrayFlags
|
||||
{
|
||||
Class = class_,
|
||||
Variable = (Variable)variableFlags,
|
||||
};
|
||||
}
|
||||
|
||||
private static DataElement ReadData(DataElement element)
|
||||
{
|
||||
return element;
|
||||
}
|
||||
|
||||
private static int[] ReadDimensionsArray(MiNum<int> element)
|
||||
{
|
||||
return element.Data;
|
||||
}
|
||||
|
||||
private static string[] ReadFieldNames(MiNum<sbyte> element, int fieldNameLength)
|
||||
{
|
||||
var numberOfFields = element.Data.Length / fieldNameLength;
|
||||
var result = new string[numberOfFields];
|
||||
for (var i = 0; i < numberOfFields; i++)
|
||||
{
|
||||
var list = new List<byte>();
|
||||
var position = i * fieldNameLength;
|
||||
while (element.Data[position] != 0)
|
||||
{
|
||||
list.Add((byte)element.Data[position]);
|
||||
position++;
|
||||
}
|
||||
|
||||
result[i] = Encoding.ASCII.GetString(list.ToArray());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string ReadName(MiNum<sbyte> element)
|
||||
{
|
||||
return Encoding.ASCII.GetString(element.Data.Select(x => (byte)x).ToArray());
|
||||
}
|
||||
|
||||
private static DataElement ReadNum<T>(Tag tag, BinaryReader reader)
|
||||
where T : struct
|
||||
{
|
||||
var bytes = reader.ReadBytes(tag.Length);
|
||||
if (tag.Type == DataType.MiUInt8)
|
||||
{
|
||||
return new MiNum<byte>(bytes);
|
||||
}
|
||||
|
||||
var result = new T[bytes.Length / tag.ElementSize];
|
||||
Buffer.BlockCopy(bytes, 0, result, 0, bytes.Length);
|
||||
return new MiNum<T>(result);
|
||||
}
|
||||
|
||||
private static SparseArrayFlags ReadSparseArrayFlags(DataElement element)
|
||||
{
|
||||
var arrayFlags = ReadArrayFlags(element);
|
||||
var flagData = (element as MiNum<uint>)?.Data ??
|
||||
throw new HandlerException("Unexpected type in sparse array flags.");
|
||||
var nzMax = flagData[1];
|
||||
return new SparseArrayFlags
|
||||
{
|
||||
ArrayFlags = arrayFlags,
|
||||
NzMax = nzMax,
|
||||
};
|
||||
}
|
||||
|
||||
private static (BinaryReader, Tag) ReadTag(BinaryReader reader)
|
||||
{
|
||||
var type = reader.ReadInt32();
|
||||
@ -95,87 +207,66 @@ namespace MatFileHandler
|
||||
}
|
||||
}
|
||||
|
||||
private static ArrayFlags ReadArrayFlags(DataElement element)
|
||||
private DataElement ContinueReadingCellArray(
|
||||
BinaryReader reader,
|
||||
ArrayFlags flags,
|
||||
int[] dimensions,
|
||||
string name)
|
||||
{
|
||||
var flagData = (element as MiNum<uint>)?.Data ??
|
||||
throw new HandlerException("Unexpected type in array flags.");
|
||||
var class_ = (ArrayType)(flagData[0] & 0xff);
|
||||
var variableFlags = (flagData[0] >> 8) & 0x0e;
|
||||
return new ArrayFlags
|
||||
var numberOfElements = dimensions.NumberOfElements();
|
||||
var elements = new List<IArray>();
|
||||
for (var i = 0; i < numberOfElements; i++)
|
||||
{
|
||||
Class = class_,
|
||||
Variable = (Variable)variableFlags,
|
||||
};
|
||||
var element = Read(reader) as IArray;
|
||||
elements.Add(element);
|
||||
}
|
||||
|
||||
private static SparseArrayFlags ReadSparseArrayFlags(DataElement element)
|
||||
{
|
||||
var arrayFlags = ReadArrayFlags(element);
|
||||
var flagData = (element as MiNum<uint>)?.Data ??
|
||||
throw new HandlerException("Unexpected type in sparse array flags.");
|
||||
var nzMax = flagData[1];
|
||||
return new SparseArrayFlags
|
||||
{
|
||||
ArrayFlags = arrayFlags,
|
||||
NzMax = nzMax,
|
||||
};
|
||||
return new MatCellArray(flags, dimensions, name, elements);
|
||||
}
|
||||
|
||||
private static int[] ReadDimensionsArray(MiNum<int> element)
|
||||
private DataElement ContinueReadingOpaque(BinaryReader reader)
|
||||
{
|
||||
return element.Data;
|
||||
var nameElement = Read(reader) as MiNum<sbyte> ??
|
||||
throw new HandlerException("Unexpected type in object name.");
|
||||
var name = ReadName(nameElement);
|
||||
var anotherElement = Read(reader) as MiNum<sbyte> ??
|
||||
throw new HandlerException("Unexpected type in object type description.");
|
||||
var typeDescription = ReadName(anotherElement);
|
||||
var classNameElement = Read(reader) as MiNum<sbyte> ??
|
||||
throw new HandlerException("Unexpected type in class name.");
|
||||
var className = ReadName(classNameElement);
|
||||
var dataElement = Read(reader);
|
||||
var data = ReadData(dataElement);
|
||||
if (data is MatNumericalArrayOf<uint> linkElement)
|
||||
{
|
||||
var (dimensions, links, classIndex) = ParseOpaqueData(linkElement);
|
||||
return new OpaqueLink(
|
||||
name,
|
||||
typeDescription,
|
||||
className,
|
||||
dimensions,
|
||||
data,
|
||||
links,
|
||||
classIndex,
|
||||
subsystemData);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new Opaque(name, typeDescription, className, new int[] { }, data);
|
||||
}
|
||||
}
|
||||
|
||||
private static DataElement ReadData(DataElement element)
|
||||
{
|
||||
return element;
|
||||
}
|
||||
|
||||
private static string ReadName(MiNum<sbyte> element)
|
||||
{
|
||||
return Encoding.ASCII.GetString(element.Data.Select(x => (byte)x).ToArray());
|
||||
}
|
||||
|
||||
private static DataElement ReadNum<T>(Tag tag, BinaryReader reader)
|
||||
where T : struct
|
||||
{
|
||||
var bytes = reader.ReadBytes(tag.Length);
|
||||
if (tag.Type == DataType.MiUInt8)
|
||||
{
|
||||
return new MiNum<byte>(bytes);
|
||||
}
|
||||
var result = new T[bytes.Length / tag.ElementSize];
|
||||
Buffer.BlockCopy(bytes, 0, result, 0, bytes.Length);
|
||||
return new MiNum<T>(result);
|
||||
}
|
||||
|
||||
private static string[] ReadFieldNames(MiNum<sbyte> element, int fieldNameLength)
|
||||
{
|
||||
var numberOfFields = element.Data.Length / fieldNameLength;
|
||||
var result = new string[numberOfFields];
|
||||
for (var i = 0; i < numberOfFields; i++)
|
||||
{
|
||||
var list = new List<byte>();
|
||||
var position = i * fieldNameLength;
|
||||
while (element.Data[position] != 0)
|
||||
{
|
||||
list.Add((byte)element.Data[position]);
|
||||
position++;
|
||||
}
|
||||
result[i] = Encoding.ASCII.GetString(list.ToArray());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static DataElement ContinueReadingSparseArray(
|
||||
private DataElement ContinueReadingSparseArray(
|
||||
BinaryReader reader,
|
||||
DataElement firstElement,
|
||||
int[] dimensions,
|
||||
string name)
|
||||
{
|
||||
var sparseArrayFlags = ReadSparseArrayFlags(firstElement);
|
||||
var rowIndex = Read(reader) as MiNum<int> ?? throw new HandlerException("Unexpected type in row indices of a sparse array.");
|
||||
var columnIndex = Read(reader) as MiNum<int> ?? throw new HandlerException("Unexpected type in column indices of a sparse array.");
|
||||
var rowIndex = Read(reader) as MiNum<int> ??
|
||||
throw new HandlerException("Unexpected type in row indices of a sparse array.");
|
||||
var columnIndex = Read(reader) as MiNum<int> ??
|
||||
throw new HandlerException("Unexpected type in column indices of a sparse array.");
|
||||
var data = Read(reader);
|
||||
if (sparseArrayFlags.ArrayFlags.Variable.HasFlag(Variable.IsLogical))
|
||||
{
|
||||
@ -187,6 +278,7 @@ namespace MatFileHandler
|
||||
columnIndex.Data,
|
||||
data);
|
||||
}
|
||||
|
||||
if (sparseArrayFlags.ArrayFlags.Variable.HasFlag(Variable.IsComplex))
|
||||
{
|
||||
var imaginaryData = Read(reader);
|
||||
@ -199,6 +291,7 @@ namespace MatFileHandler
|
||||
data,
|
||||
imaginaryData);
|
||||
}
|
||||
|
||||
switch (data)
|
||||
{
|
||||
case MiNum<double> _:
|
||||
@ -214,23 +307,7 @@ namespace MatFileHandler
|
||||
}
|
||||
}
|
||||
|
||||
private static DataElement ContinueReadingCellArray(
|
||||
BinaryReader reader,
|
||||
ArrayFlags flags,
|
||||
int[] dimensions,
|
||||
string name)
|
||||
{
|
||||
var numberOfElements = dimensions.NumberOfElements();
|
||||
var elements = new List<IArray>();
|
||||
for (var i = 0; i < numberOfElements; i++)
|
||||
{
|
||||
var element = Read(reader) as IArray;
|
||||
elements.Add(element);
|
||||
}
|
||||
return new MatCellArray(flags, dimensions, name, elements);
|
||||
}
|
||||
|
||||
private static DataElement ContinueReadingStructure(
|
||||
private DataElement ContinueReadingStructure(
|
||||
BinaryReader reader,
|
||||
ArrayFlags flags,
|
||||
int[] dimensions,
|
||||
@ -244,6 +321,7 @@ namespace MatFileHandler
|
||||
{
|
||||
fields[fieldName] = new List<IArray>();
|
||||
}
|
||||
|
||||
var numberOfElements = dimensions.NumberOfElements();
|
||||
for (var i = 0; i < numberOfElements; i++)
|
||||
{
|
||||
@ -253,27 +331,53 @@ namespace MatFileHandler
|
||||
fields[fieldName].Add(field);
|
||||
}
|
||||
}
|
||||
|
||||
return new MatStructureArray(flags, dimensions, name, fields);
|
||||
}
|
||||
|
||||
private static DataElement ContinueReadingNewObject()
|
||||
private DataElement Read(Stream stream)
|
||||
{
|
||||
throw new HandlerException("Cannot read objects.");
|
||||
using (var reader = new BinaryReader(stream))
|
||||
{
|
||||
return Read(reader);
|
||||
}
|
||||
}
|
||||
|
||||
private static DataElement ReadMatrix(Tag tag, BinaryReader reader)
|
||||
private DataElement ReadCompressed(Tag tag, BinaryReader reader)
|
||||
{
|
||||
reader.ReadBytes(2);
|
||||
var compressedData = new byte[tag.Length - 6];
|
||||
reader.BaseStream.Read(compressedData, 0, tag.Length - 6);
|
||||
reader.ReadBytes(4);
|
||||
var resultStream = new MemoryStream();
|
||||
using (var compressedStream = new MemoryStream(compressedData))
|
||||
{
|
||||
using (var stream = new DeflateStream(compressedStream, CompressionMode.Decompress, leaveOpen: true))
|
||||
{
|
||||
stream.CopyTo(resultStream);
|
||||
}
|
||||
}
|
||||
|
||||
resultStream.Position = 0;
|
||||
return Read(resultStream);
|
||||
}
|
||||
|
||||
private DataElement ReadMatrix(Tag tag, BinaryReader reader)
|
||||
{
|
||||
if (tag.Length == 0)
|
||||
{
|
||||
return MatArray.Empty();
|
||||
}
|
||||
|
||||
var element1 = Read(reader);
|
||||
var flags = ReadArrayFlags(element1);
|
||||
if (flags.Class == ArrayType.MxNewObject)
|
||||
if (flags.Class == ArrayType.MxOpaque)
|
||||
{
|
||||
return ContinueReadingNewObject();
|
||||
return ContinueReadingOpaque(reader);
|
||||
}
|
||||
var element2 = Read(reader) as MiNum<int> ?? throw new HandlerException("Unexpected type in array dimensions data.");
|
||||
|
||||
var element2 = Read(reader) as MiNum<int> ??
|
||||
throw new HandlerException("Unexpected type in array dimensions data.");
|
||||
var dimensions = ReadDimensionsArray(element2);
|
||||
var element3 = Read(reader) as MiNum<sbyte> ?? throw new HandlerException("Unexpected type in array name.");
|
||||
var name = ReadName(element3);
|
||||
@ -281,10 +385,12 @@ namespace MatFileHandler
|
||||
{
|
||||
return ContinueReadingCellArray(reader, flags, dimensions, name);
|
||||
}
|
||||
|
||||
if (flags.Class == ArrayType.MxSparse)
|
||||
{
|
||||
return ContinueReadingSparseArray(reader, element1, dimensions, name);
|
||||
}
|
||||
|
||||
var element4 = Read(reader);
|
||||
var data = ReadData(element4);
|
||||
DataElement imaginaryData = null;
|
||||
@ -293,12 +399,15 @@ namespace MatFileHandler
|
||||
var element5 = Read(reader);
|
||||
imaginaryData = ReadData(element5);
|
||||
}
|
||||
|
||||
if (flags.Class == ArrayType.MxStruct)
|
||||
{
|
||||
var fieldNameLengthElement = data as MiNum<int> ??
|
||||
throw new HandlerException("Unexpected type in structure field name length.");
|
||||
throw new HandlerException(
|
||||
"Unexpected type in structure field name length.");
|
||||
return ContinueReadingStructure(reader, flags, dimensions, name, fieldNameLengthElement.Data[0]);
|
||||
}
|
||||
|
||||
switch (flags.Class)
|
||||
{
|
||||
case ArrayType.MxChar:
|
||||
@ -339,6 +448,7 @@ namespace MatFileHandler
|
||||
data,
|
||||
imaginaryData);
|
||||
}
|
||||
|
||||
return DataElementConverter.ConvertToMatNumericalArrayOf<byte>(
|
||||
flags,
|
||||
dimensions,
|
||||
@ -405,31 +515,5 @@ namespace MatFileHandler
|
||||
throw new HandlerException("Unknown data type.");
|
||||
}
|
||||
}
|
||||
|
||||
private static DataElement ReadCompressed(Tag tag, BinaryReader reader)
|
||||
{
|
||||
reader.ReadBytes(2);
|
||||
var compressedData = new byte[tag.Length - 6];
|
||||
reader.BaseStream.Read(compressedData, 0, tag.Length - 6);
|
||||
reader.ReadBytes(4);
|
||||
var resultStream = new MemoryStream();
|
||||
using (var compressedStream = new MemoryStream(compressedData))
|
||||
{
|
||||
using (var stream = new DeflateStream(compressedStream, CompressionMode.Decompress, leaveOpen: true))
|
||||
{
|
||||
stream.CopyTo(resultStream);
|
||||
}
|
||||
}
|
||||
resultStream.Position = 0;
|
||||
return Read(resultStream);
|
||||
}
|
||||
|
||||
private static DataElement Read(Stream stream)
|
||||
{
|
||||
using (var reader = new BinaryReader(stream))
|
||||
{
|
||||
return Read(reader);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -13,7 +13,7 @@ namespace MatFileHandler
|
||||
/// </summary>
|
||||
internal class Header
|
||||
{
|
||||
private Header(string text, byte[] subsystemDataOffset, int version)
|
||||
private Header(string text, long subsystemDataOffset, int version)
|
||||
{
|
||||
Text = text;
|
||||
SubsystemDataOffset = subsystemDataOffset;
|
||||
@ -28,7 +28,7 @@ namespace MatFileHandler
|
||||
/// <summary>
|
||||
/// Gets subsystem data offset.
|
||||
/// </summary>
|
||||
public byte[] SubsystemDataOffset { get; }
|
||||
public long SubsystemDataOffset { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets file version.
|
||||
@ -55,7 +55,7 @@ namespace MatFileHandler
|
||||
platform = platform.Remove(length);
|
||||
}
|
||||
var text = $"MATLAB 5.0 MAT-file, Platform: {platform}, Created on: {dateTime}{padding}";
|
||||
return new Header(text, subsystemDataOffset, 256);
|
||||
return new Header(text, 0, 256);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -67,7 +67,8 @@ namespace MatFileHandler
|
||||
{
|
||||
var textBytes = reader.ReadBytes(116);
|
||||
var text = System.Text.Encoding.UTF8.GetString(textBytes);
|
||||
var subsystemDataOffset = reader.ReadBytes(8);
|
||||
var subsystemDataOffsetBytes = reader.ReadBytes(8);
|
||||
var subsystemDataOffset = BitConverter.ToInt64(subsystemDataOffsetBytes, 0);
|
||||
var version = reader.ReadInt16();
|
||||
var endian = reader.ReadInt16();
|
||||
var isLittleEndian = endian == 19785;
|
||||
|
34
MatFileHandler/IMatObject.cs
Normal file
34
MatFileHandler/IMatObject.cs
Normal file
@ -0,0 +1,34 @@
|
||||
// Copyright 2017-2018 Alexander Luzgarev
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MatFileHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// An interface to access Matlab objects (more precisely, "object arrays").
|
||||
/// This is very similar to the <see cref="IStructureArray"/> interface:
|
||||
/// an object holds fields that you can access, and the name of its class.
|
||||
/// Additionally, you can treat is as an array of dictionaries mapping
|
||||
/// field names to contents of fields.
|
||||
/// </summary>
|
||||
public interface IMatObject : IArrayOf<IReadOnlyDictionary<string, IArray>>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name of object's class.
|
||||
/// </summary>
|
||||
string ClassName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the names of object's fields.
|
||||
/// </summary>
|
||||
IEnumerable<string> FieldNames { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Access a given field of a given object in the array.
|
||||
/// </summary>
|
||||
/// <param name="field">Field name.</param>
|
||||
/// <param name="list">Index of the object to access.</param>
|
||||
/// <returns>The value of the field in the selected object.</returns>
|
||||
IArray this[string field, params int[] list] { get; set; }
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
// Copyright 2017-2018 Alexander Luzgarev
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
@ -33,35 +34,72 @@ namespace MatFileHandler
|
||||
}
|
||||
}
|
||||
|
||||
private static void ReadHeader(BinaryReader reader)
|
||||
/// <summary>
|
||||
/// Read raw variables from a .mat file.
|
||||
/// </summary>
|
||||
/// <param name="reader">Binary reader.</param>
|
||||
/// <param name="subsystemDataOffset">Offset to the subsystem data to use (read from the file header).</param>
|
||||
/// <returns>Raw variables read.</returns>
|
||||
internal static List<RawVariable> ReadRawVariables(BinaryReader reader, long subsystemDataOffset)
|
||||
{
|
||||
Header.Read(reader);
|
||||
}
|
||||
|
||||
private static IMatFile Read(BinaryReader reader)
|
||||
{
|
||||
ReadHeader(reader);
|
||||
var variables = new List<IVariable>();
|
||||
var variables = new List<RawVariable>();
|
||||
var subsystemData = new SubsystemData();
|
||||
var dataElementReader = new DataElementReader(subsystemData);
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dataElement = DataElementReader.Read(reader) as MatArray;
|
||||
if (dataElement == null)
|
||||
var position = reader.BaseStream.Position;
|
||||
var dataElement = dataElementReader.Read(reader);
|
||||
if (position == subsystemDataOffset)
|
||||
{
|
||||
continue;
|
||||
var subsystemDataElement = dataElement as IArrayOf<byte>;
|
||||
subsystemData.Set(ReadSubsystemData(subsystemDataElement.Data));
|
||||
}
|
||||
else
|
||||
{
|
||||
variables.Add(new RawVariable(position, dataElement));
|
||||
}
|
||||
variables.Add(new MatVariable(
|
||||
dataElement,
|
||||
dataElement.Name,
|
||||
dataElement.Flags.Variable.HasFlag(Variable.IsGlobal)));
|
||||
}
|
||||
catch (EndOfStreamException)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return variables;
|
||||
}
|
||||
|
||||
private static IMatFile Read(BinaryReader reader)
|
||||
{
|
||||
var header = ReadHeader(reader);
|
||||
var rawVariables = ReadRawVariables(reader, header.SubsystemDataOffset);
|
||||
var variables = new List<IVariable>();
|
||||
foreach (var variable in rawVariables)
|
||||
{
|
||||
var array = variable.DataElement as MatArray;
|
||||
if (array is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
variables.Add(new MatVariable(
|
||||
array,
|
||||
array.Name,
|
||||
array.Flags.Variable.HasFlag(Variable.IsGlobal)));
|
||||
}
|
||||
|
||||
return new MatFile(variables);
|
||||
}
|
||||
|
||||
private static Header ReadHeader(BinaryReader reader)
|
||||
{
|
||||
return Header.Read(reader);
|
||||
}
|
||||
|
||||
private static SubsystemData ReadSubsystemData(byte[] bytes)
|
||||
{
|
||||
return SubsystemDataReader.Read(bytes);
|
||||
}
|
||||
}
|
||||
}
|
58
MatFileHandler/Opaque.cs
Normal file
58
MatFileHandler/Opaque.cs
Normal file
@ -0,0 +1,58 @@
|
||||
// Copyright 2017-2018 Alexander Luzgarev
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace MatFileHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Matlab "opaque object" structure.
|
||||
/// If this object appears in the "main" section of the .mat file,
|
||||
/// it just contains a small data structure pointing to the object's
|
||||
/// storage in the "subsystem data" portion of the file.
|
||||
/// In this case, an instance of <see cref="OpaqueLink"/> class
|
||||
/// will be created.
|
||||
/// If this object appears in the "subsystem data" part, it contains
|
||||
/// the data of all opaque objects in the file, and that is what we
|
||||
/// put into <see cref="RawData"/> property.
|
||||
/// </summary>
|
||||
internal class Opaque : MatArray, IArray
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Opaque"/> class.
|
||||
/// </summary>
|
||||
/// <param name="name">Name of the object.</param>
|
||||
/// <param name="typeDescription">Type description.</param>
|
||||
/// <param name="className">Class name.</param>
|
||||
/// <param name="dimensions">Dimensions of the object.</param>
|
||||
/// <param name="rawData">Raw object's data.</param>
|
||||
public Opaque(string name, string typeDescription, string className, int[] dimensions, DataElement rawData)
|
||||
: base(new ArrayFlags(ArrayType.MxOpaque, 0), dimensions, name)
|
||||
{
|
||||
TypeDescription = typeDescription ?? throw new ArgumentNullException(nameof(typeDescription));
|
||||
ClassName = className ?? throw new ArgumentNullException(nameof(className));
|
||||
RawData = rawData ?? throw new ArgumentNullException(nameof(rawData));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets class name of the opaque object.
|
||||
/// </summary>
|
||||
public string ClassName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets raw object's data: either links to subsystem data, or actual data.
|
||||
/// </summary>
|
||||
public DataElement RawData { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets "type description" of the opaque object.
|
||||
/// </summary>
|
||||
public string TypeDescription { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Complex[] ConvertToComplexArray() => null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override double[] ConvertToDoubleArray() => null;
|
||||
}
|
||||
}
|
169
MatFileHandler/OpaqueLink.cs
Normal file
169
MatFileHandler/OpaqueLink.cs
Normal file
@ -0,0 +1,169 @@
|
||||
// Copyright 2017-2018 Alexander Luzgarev
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace MatFileHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementation of Matlab's "opaque objects" via links to subsystem data.
|
||||
/// </summary>
|
||||
internal class OpaqueLink : Opaque, IMatObject
|
||||
{
|
||||
private readonly SubsystemData subsystemData;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OpaqueLink"/> class.
|
||||
/// </summary>
|
||||
/// <param name="name">Name of the object.</param>
|
||||
/// <param name="typeDescription">Description of object's class.</param>
|
||||
/// <param name="className">Name of the object's class.</param>
|
||||
/// <param name="dimensions">Dimensions of the object.</param>
|
||||
/// <param name="data">Raw data containing links to object's storage.</param>
|
||||
/// <param name="links">Links to object's storage.</param>
|
||||
/// <param name="classIndex">Index of object's class.</param>
|
||||
/// <param name="subsystemData">Reference to global subsystem data.</param>
|
||||
public OpaqueLink(
|
||||
string name,
|
||||
string typeDescription,
|
||||
string className,
|
||||
int[] dimensions,
|
||||
DataElement data,
|
||||
int[] links,
|
||||
int classIndex,
|
||||
SubsystemData subsystemData)
|
||||
: base(name, typeDescription, className, dimensions, data)
|
||||
{
|
||||
Links = links ?? throw new ArgumentNullException(nameof(links));
|
||||
ClassIndex = classIndex;
|
||||
this.subsystemData = subsystemData ?? throw new ArgumentNullException(nameof(subsystemData));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets index of this object's class in subsystem data class list.
|
||||
/// </summary>
|
||||
public int ClassIndex { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyDictionary<string, IArray>[] Data => null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<string> FieldNames => FieldNamesArray;
|
||||
|
||||
/// <summary>
|
||||
/// Gets links to the fields stored in subsystem data.
|
||||
/// </summary>
|
||||
public int[] Links { get; }
|
||||
|
||||
private string[] FieldNamesArray => subsystemData.ClassInformation[ClassIndex - 1].FieldNames.ToArray();
|
||||
|
||||
/// <inheritdoc />
|
||||
public IArray this[string field, params int[] list]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (TryGetValue(field, out var result, list))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
set => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyDictionary<string, IArray> this[params int[] list]
|
||||
{
|
||||
get => ExtractObject(Dimensions.DimFlatten(list));
|
||||
set => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private IReadOnlyDictionary<string, IArray> ExtractObject(int i)
|
||||
{
|
||||
return new OpaqueObjectArrayElement(this, i);
|
||||
}
|
||||
|
||||
private bool TryGetValue(string field, out IArray output, params int[] list)
|
||||
{
|
||||
var index = Dimensions.DimFlatten(list);
|
||||
var maybeFieldIndex = subsystemData.ClassInformation[ClassIndex - 1].FindField(field);
|
||||
if (!(maybeFieldIndex is int fieldIndex))
|
||||
{
|
||||
output = default(IArray);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (index >= subsystemData.ObjectInformation.Length)
|
||||
{
|
||||
output = default(IArray);
|
||||
return false;
|
||||
}
|
||||
|
||||
var dataIndex = subsystemData.ObjectInformation[index].FieldLinks[fieldIndex + 1];
|
||||
output = subsystemData.Data[dataIndex];
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides access to a single object in object array.
|
||||
/// </summary>
|
||||
internal class OpaqueObjectArrayElement : IReadOnlyDictionary<string, IArray>
|
||||
{
|
||||
private readonly int index;
|
||||
private readonly OpaqueLink parent;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OpaqueObjectArrayElement"/> class.
|
||||
/// </summary>
|
||||
/// <param name="parent">Parent object array.</param>
|
||||
/// <param name="index">Index of the object in the array.</param>
|
||||
public OpaqueObjectArrayElement(OpaqueLink parent, int index)
|
||||
{
|
||||
this.parent = parent ?? throw new ArgumentNullException(nameof(parent));
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Count => parent.FieldNamesArray.Length;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<string> Keys => parent.FieldNames;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IArray> Values => parent.FieldNames.Select(fieldName => parent[fieldName, index]);
|
||||
|
||||
/// <inheritdoc />
|
||||
public IArray this[string key] => parent[key, index];
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ContainsKey(string key)
|
||||
{
|
||||
return parent.FieldNames.Contains(key);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerator<KeyValuePair<string, IArray>> GetEnumerator()
|
||||
{
|
||||
foreach (var field in parent.FieldNamesArray)
|
||||
{
|
||||
yield return new KeyValuePair<string, IArray>(field, parent[field, index]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGetValue(string key, out IArray value)
|
||||
{
|
||||
return parent.TryGetValue(key, out value, index);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
36
MatFileHandler/RawVariable.cs
Normal file
36
MatFileHandler/RawVariable.cs
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright 2017-2018 Alexander Luzgarev
|
||||
|
||||
using System;
|
||||
|
||||
namespace MatFileHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Raw variable read from the file.
|
||||
/// This gives a way to deal with "subsystem data" which looks like
|
||||
/// a variable and can only be detected by comparing its offset with
|
||||
/// the value stored in the file's header.
|
||||
/// </summary>
|
||||
internal class RawVariable
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RawVariable"/> class.
|
||||
/// </summary>
|
||||
/// <param name="offset">Offset of the variable in the source file.</param>
|
||||
/// <param name="dataElement">Data element parsed from the file.</param>
|
||||
internal RawVariable(long offset, DataElement dataElement)
|
||||
{
|
||||
Offset = offset;
|
||||
DataElement = dataElement ?? throw new ArgumentNullException(nameof(dataElement));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets data element with the variable's contents.
|
||||
/// </summary>
|
||||
public DataElement DataElement { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets offset of the variable in the .mat file.
|
||||
/// </summary>
|
||||
public long Offset { get; }
|
||||
}
|
||||
}
|
144
MatFileHandler/SubsystemData.cs
Normal file
144
MatFileHandler/SubsystemData.cs
Normal file
@ -0,0 +1,144 @@
|
||||
// Copyright 2017-2018 Alexander Luzgarev
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MatFileHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// "Subsystem data" of the .mat file.
|
||||
/// Subsystem data stores the actual contents
|
||||
/// of all the "opaque objects" in the file.
|
||||
/// </summary>
|
||||
internal class SubsystemData
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SubsystemData"/> class.
|
||||
/// Default constructor: initializes everything to null.
|
||||
/// </summary>
|
||||
public SubsystemData()
|
||||
{
|
||||
ClassInformation = null;
|
||||
ObjectInformation = null;
|
||||
Data = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SubsystemData"/> class.
|
||||
/// The actual constructor.
|
||||
/// </summary>
|
||||
/// <param name="classInformation">Information about the classes.</param>
|
||||
/// <param name="objectInformation">Information about the objects.</param>
|
||||
/// <param name="data">Field values.</param>
|
||||
public SubsystemData(ClassInfo[] classInformation, ObjectInfo[] objectInformation, Dictionary<int, IArray> data)
|
||||
{
|
||||
this.ClassInformation =
|
||||
classInformation ?? throw new ArgumentNullException(nameof(classInformation));
|
||||
this.ObjectInformation =
|
||||
objectInformation ?? throw new ArgumentNullException(nameof(objectInformation));
|
||||
this.Data = data ?? throw new ArgumentNullException(nameof(data));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets information about all the classes occurring in the file.
|
||||
/// </summary>
|
||||
public ClassInfo[] ClassInformation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the actual data: mapping of "object field" indices to their values.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<int, IArray> Data { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets information about all the objects occurring in the file.
|
||||
/// </summary>
|
||||
public ObjectInfo[] ObjectInformation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initialize this object from another object.
|
||||
/// This ugly hack allows us to read the opaque objects and store references to
|
||||
/// the subsystem data in them before parsing the actual subsystem data (which
|
||||
/// comes later in the file).
|
||||
/// </summary>
|
||||
/// <param name="data">Another subsystem data.</param>
|
||||
public void Set(SubsystemData data)
|
||||
{
|
||||
this.ClassInformation = data.ClassInformation;
|
||||
this.ObjectInformation = data.ObjectInformation;
|
||||
this.Data = data.Data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stores information about a class.
|
||||
/// </summary>
|
||||
internal class ClassInfo
|
||||
{
|
||||
private readonly string[] fieldNames;
|
||||
|
||||
private readonly Dictionary<string, int> fieldToIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ClassInfo"/> class.
|
||||
/// </summary>
|
||||
/// <param name="name">Class name.</param>
|
||||
/// <param name="fieldNames">Names of the fields.</param>
|
||||
public ClassInfo(string name, string[] fieldNames)
|
||||
{
|
||||
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
this.fieldNames = fieldNames ?? throw new ArgumentNullException(nameof(fieldNames));
|
||||
fieldToIndex = new Dictionary<string, int>();
|
||||
for (var i = 0; i < fieldNames.Length; i++)
|
||||
{
|
||||
fieldToIndex[fieldNames[i]] = i;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets names of the fields in the class.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<string> FieldNames => fieldNames;
|
||||
|
||||
/// <summary>
|
||||
/// Gets name of the class.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Find a field index given its name.
|
||||
/// </summary>
|
||||
/// <param name="fieldName">Field name.</param>
|
||||
/// <returns>Field index.</returns>
|
||||
public int? FindField(string fieldName)
|
||||
{
|
||||
if (fieldToIndex.TryGetValue(fieldName, out var index))
|
||||
{
|
||||
return index;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stores information about an object.
|
||||
/// </summary>
|
||||
internal class ObjectInfo
|
||||
{
|
||||
private readonly Dictionary<int, int> fieldLinks;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ObjectInfo"/> class.
|
||||
/// </summary>
|
||||
/// <param name="fieldLinks">A dictionary mapping the field indices to "field values" indices.</param>
|
||||
public ObjectInfo(Dictionary<int, int> fieldLinks)
|
||||
{
|
||||
this.fieldLinks = fieldLinks ?? throw new ArgumentNullException(nameof(fieldLinks));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets mapping between the field indices and "field values" indices.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<int, int> FieldLinks => fieldLinks;
|
||||
}
|
||||
}
|
||||
}
|
168
MatFileHandler/SubsystemDataReader.cs
Normal file
168
MatFileHandler/SubsystemDataReader.cs
Normal file
@ -0,0 +1,168 @@
|
||||
// Copyright 2017-2018 Alexander Luzgarev
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace MatFileHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Reader for "subsystem data" in .mat files.
|
||||
/// </summary>
|
||||
internal class SubsystemDataReader
|
||||
{
|
||||
/// <summary>
|
||||
/// Read subsystem data from a given byte array.
|
||||
/// </summary>
|
||||
/// <param name="bytes">Byte array with the data.</param>
|
||||
/// <returns>Subsystem data read.</returns>
|
||||
public static SubsystemData Read(byte[] bytes)
|
||||
{
|
||||
List<RawVariable> rawVariables = null;
|
||||
using (var stream = new MemoryStream(bytes))
|
||||
{
|
||||
using (var reader = new BinaryReader(stream))
|
||||
{
|
||||
reader.ReadBytes(8);
|
||||
rawVariables = MatFileReader.ReadRawVariables(reader, -1);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse subsystem data.
|
||||
var mainVariable = rawVariables[0].DataElement as IStructureArray;
|
||||
var mcosData = mainVariable["MCOS", 0] as Opaque;
|
||||
var opaqueData = mcosData.RawData as ICellArray;
|
||||
var info = (opaqueData[0] as IArrayOf<byte>).Data;
|
||||
var (offsets, position) = ReadOffsets(info, 0);
|
||||
var fieldNames = ReadFieldNames(info, position, offsets[1]);
|
||||
var numberOfClasses = ((offsets[3] - offsets[2]) / 16) - 1;
|
||||
SubsystemData.ClassInfo[] classInformation = null;
|
||||
using (var stream = new MemoryStream(info, offsets[2], offsets[3] - offsets[2]))
|
||||
{
|
||||
using (var reader = new BinaryReader(stream))
|
||||
{
|
||||
classInformation = ReadClassInformation(reader, fieldNames, numberOfClasses);
|
||||
}
|
||||
}
|
||||
var numberOfObjects = ((offsets[5] - offsets[4]) / 24) - 1;
|
||||
SubsystemData.ObjectInfo[] objectInformation = null;
|
||||
using (var stream = new MemoryStream(info, offsets[5], offsets[6] - offsets[5]))
|
||||
{
|
||||
using (var reader = new BinaryReader(stream))
|
||||
{
|
||||
objectInformation = ReadObjectInformation(reader, numberOfObjects);
|
||||
}
|
||||
}
|
||||
|
||||
var allFields = objectInformation.SelectMany(obj => obj.FieldLinks.Values);
|
||||
var data = new Dictionary<int, IArray>();
|
||||
foreach (var i in allFields)
|
||||
{
|
||||
data[i] = opaqueData[i + 2];
|
||||
}
|
||||
return new SubsystemData(classInformation, objectInformation, data);
|
||||
}
|
||||
|
||||
private static SubsystemData.ObjectInfo ReadObjectInformation(BinaryReader reader)
|
||||
{
|
||||
var length = reader.ReadInt32();
|
||||
var fieldLinks = new Dictionary<int, int>();
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
var x = reader.ReadInt32();
|
||||
var y = reader.ReadInt32();
|
||||
var index = x * y;
|
||||
var link = reader.ReadInt32();
|
||||
fieldLinks[index] = link;
|
||||
}
|
||||
return new SubsystemData.ObjectInfo(fieldLinks);
|
||||
}
|
||||
|
||||
private static SubsystemData.ObjectInfo[] ReadObjectInformation(BinaryReader reader, int numberOfObjects)
|
||||
{
|
||||
var result = new SubsystemData.ObjectInfo[numberOfObjects];
|
||||
reader.ReadBytes(8);
|
||||
for (var objectIndex = 0; objectIndex < numberOfObjects; objectIndex++)
|
||||
{
|
||||
result[objectIndex] = ReadObjectInformation(reader);
|
||||
var position = reader.BaseStream.Position;
|
||||
if (position % 8 != 0)
|
||||
{
|
||||
reader.ReadBytes(8 - (int)(position % 8));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static SubsystemData.ClassInfo[] ReadClassInformation(
|
||||
BinaryReader reader,
|
||||
string[] fieldNames,
|
||||
int numberOfClasses)
|
||||
{
|
||||
var result = new SubsystemData.ClassInfo[numberOfClasses];
|
||||
var indices = new int[numberOfClasses + 1];
|
||||
for (var i = 0; i <= numberOfClasses; i++)
|
||||
{
|
||||
reader.ReadInt32();
|
||||
indices[i] = reader.ReadInt32();
|
||||
reader.ReadInt32();
|
||||
reader.ReadInt32();
|
||||
}
|
||||
|
||||
for (var i = 0; i < numberOfClasses; i++)
|
||||
{
|
||||
var numberOfFields = indices[i + 1] - indices[i] - 1;
|
||||
var names = new string[numberOfFields];
|
||||
Array.Copy(fieldNames, indices[i], names, 0, numberOfFields);
|
||||
var className = fieldNames[indices[i + 1] - 1];
|
||||
result[i] = new SubsystemData.ClassInfo(className, names);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static (int[] offsets, int newPosition) ReadOffsets(byte[] bytes, int startPosition)
|
||||
{
|
||||
var position = startPosition;
|
||||
var offsets = new List<int>();
|
||||
while (true)
|
||||
{
|
||||
var next = BitConverter.ToInt32(bytes, position);
|
||||
position += 4;
|
||||
if (next == 0)
|
||||
{
|
||||
if (position % 8 != 0)
|
||||
{
|
||||
position += 4;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
offsets.Add(next);
|
||||
}
|
||||
|
||||
return (offsets.ToArray(), position);
|
||||
}
|
||||
|
||||
private static string[] ReadFieldNames(byte[] bytes, int startPosition, int numberOfFields)
|
||||
{
|
||||
var result = new string[numberOfFields];
|
||||
var position = startPosition;
|
||||
for (var i = 0; i < numberOfFields; i++)
|
||||
{
|
||||
var list = new List<byte>();
|
||||
while (bytes[position] != 0)
|
||||
{
|
||||
list.Add(bytes[position]);
|
||||
position++;
|
||||
}
|
||||
result[i] = Encoding.ASCII.GetString(list.ToArray());
|
||||
position++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user