// Copyright 2017-2018 Alexander Luzgarev using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; namespace MatFileHandler { /// /// Reader for "subsystem data" in .mat files. /// internal class SubsystemDataReader { /// /// Read subsystem data from a given byte array. /// /// Byte array with the data. /// /// Link to the existing subsystem data; this will be put in nested OpaqueLink objects /// and later replaced with the subsystem data that we are currently reading. /// Subsystem data read. public static SubsystemData Read(byte[] bytes, SubsystemData subsystemData) { List rawVariables = null; using (var stream = new MemoryStream(bytes)) { using (var reader = new BinaryReader(stream)) { reader.ReadBytes(8); rawVariables = MatFileReader.ReadRawVariables(reader, -1, subsystemData); } } // 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).Data; var (offsets, position) = ReadOffsets(info, 0); var fieldNames = ReadFieldNames(info, position, offsets[1]); var numberOfClasses = ((offsets[3] - offsets[2]) / 16) - 1; Dictionary classIdToName = null; using (var stream = new MemoryStream(info, offsets[2], offsets[3] - offsets[2])) { using (var reader = new BinaryReader(stream)) { classIdToName = ReadClassNames(reader, fieldNames, numberOfClasses); } } var numberOfObjects = ((offsets[5] - offsets[4]) / 24) - 1; Dictionary objectClasses = null; using (var stream = new MemoryStream(info, offsets[4], offsets[5] - offsets[4])) { using (var reader = new BinaryReader(stream)) { objectClasses = ReadObjectClasses(reader, numberOfObjects); } } Dictionary> objectToFields = null; using (var stream = new MemoryStream(info, offsets[5], offsets[6] - offsets[5])) { using (var reader = new BinaryReader(stream)) { objectToFields = ReadObjectToFieldsMapping(reader, numberOfObjects); } } var (classInformation, objectInformation) = GatherClassAndObjectInformation( classIdToName, fieldNames, objectClasses, objectToFields); var allFields = objectInformation.Values.SelectMany(obj => obj.FieldLinks.Values); var data = new Dictionary(); foreach (var i in allFields) { data[i] = TransformOpaqueData(opaqueData[i + 2], subsystemData); } return new SubsystemData(classInformation, objectInformation, data); } private static IArray TransformOpaqueData(IArray array, SubsystemData subsystemData) { if (array is MatNumericalArrayOf uintArray) { if (uintArray.Data[0] == 3707764736u) { var (dimensions, indexToObjectId, classIndex) = DataElementReader.ParseOpaqueData(uintArray.Data); return new OpaqueLink( uintArray.Name, string.Empty, string.Empty, dimensions, array as DataElement, indexToObjectId, classIndex, subsystemData); } } return array; } private static Dictionary ReadObjectClasses(BinaryReader reader, int numberOfObjects) { var result = new Dictionary(); reader.ReadBytes(24); for (var i = 0; i < numberOfObjects; i++) { var classId = reader.ReadInt32(); reader.ReadBytes(12); var objectPosition = reader.ReadInt32(); var objectId = reader.ReadInt32(); result[objectPosition] = (objectId, classId); } return result; } private static (Dictionary, Dictionary) GatherClassAndObjectInformation( Dictionary classIdToName, string[] fieldNames, Dictionary objectClasses, Dictionary> objectToFields) { var classInfos = new Dictionary(); foreach (var classId in classIdToName.Keys) { var className = classIdToName[classId]; var fieldIds = new SortedSet(); foreach (var objectPosition in objectToFields.Keys) { var (_, thisObjectClassId) = objectClasses[objectPosition]; if (thisObjectClassId != classId) { continue; } foreach (var fieldId in objectToFields[objectPosition].Keys) { fieldIds.Add(fieldId); } } var fieldToIndex = new Dictionary(); foreach (var fieldId in fieldIds) { fieldToIndex[fieldNames[fieldId - 1]] = fieldId; } classInfos[classId] = new SubsystemData.ClassInfo(className, fieldToIndex); } var objectInfos = new Dictionary(); foreach (var objectPosition in objectToFields.Keys) { var (objectId, _) = objectClasses[objectPosition]; objectInfos[objectId] = new SubsystemData.ObjectInfo(objectPosition, objectToFields[objectPosition]); } return (classInfos, objectInfos); } private static Dictionary ReadFieldToFieldDataMapping(BinaryReader reader) { var length = reader.ReadInt32(); var result = new Dictionary(); for (var i = 0; i < length; i++) { var x = reader.ReadInt32(); var y = reader.ReadInt32(); var index = x * y; var link = reader.ReadInt32(); result[index] = link; } return result; } private static Dictionary> ReadObjectToFieldsMapping(BinaryReader reader, int numberOfObjects) { var result = new Dictionary>(); reader.ReadBytes(8); for (var objectPosition = 1; objectPosition <= numberOfObjects; objectPosition++) { result[objectPosition] = ReadFieldToFieldDataMapping(reader); var position = reader.BaseStream.Position; if (position % 8 != 0) { reader.ReadBytes(8 - (int)(position % 8)); } } return result; } private static Dictionary ReadClassNames( BinaryReader reader, string[] fieldNames, int numberOfClasses) { var result = new Dictionary(); 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++) { result[i + 1] = fieldNames[indices[i + 1] - 1]; } return result; } private static (int[] offsets, int newPosition) ReadOffsets(byte[] bytes, int startPosition) { var position = startPosition; var offsets = new List(); 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(); while (bytes[position] != 0) { list.Add(bytes[position]); position++; } result[i] = Encoding.ASCII.GetString(list.ToArray()); position++; } return result; } } }