public static PseudoReflectionObject FromFile(string path)
        {
            var bytes = File.ReadAllBytes(path);

            // Read the sdef type metadata
            using (var bs = new BinaryStream(new MemoryStream(bytes)))
            {
                SDEFMetaData sdef = new SDEFMetaData();
                if (bs.ReadString(4) != MAGIC)
                {
                    throw new InvalidDataException("Not a SDEF file.");
                }

                uint unk = bs.ReadUInt32();
                int  fixedArrLengthVersion;
                if (unk >= 1)                  // GT7SP Uses that
                {
                    fixedArrLengthVersion = 1; // By default seems like its fixed arrays
                    uint unkSize = bs.ReadUInt32();
                    bs.Position += unkSize;    // Not figured
                }
                else
                {
                    // When 1, all arrays are fixed length and provided in the type metadata.
                    fixedArrLengthVersion = bs.ReadInt32();
                    if (fixedArrLengthVersion >= 1)
                    {
                        bs.ReadByte(); // Empty
                    }
                }

                int catCount = bs.ReadInt32();

                for (int i = 0; i < catCount; i++)
                {
                    int strLength    = bs.ReadInt32();
                    var categoryName = bs.ReadString(strLength - 1); bs.Position += 1; // Null

                    var category = new SDEFMetaDataCategory();
                    category.Name = categoryName;
                    sdef.Categories.Add(category);
                    int entryCount = bs.ReadInt32();
                    for (int j = 0; j < entryCount; j++)
                    {
                        int entryNameLength = bs.ReadInt32();
                        var entryName       = bs.ReadString(entryNameLength - 1); bs.Position += 1; // Null

                        var entry = new SDEFMetaDataEntry();
                        entry.Name = entryName;
                        category.Entries.Add(entry);

                        entry.TypeOrIndex   = bs.ReadUInt16();
                        entry.HasCustomType = bs.ReadBoolean(BooleanCoding.Word);

                        // If its an array, additional data is stored to know if its an array of types rather than raw values
                        if (entry.TypeOrIndex == (int)(ValueType.Array))
                        {
                            if (!entry.HasCustomType)
                            {
                                entry.ArrayCategoryIndex = bs.ReadUInt16();
                                entry.ArrayHasCustomType = bs.ReadBoolean(BooleanCoding.Word);

                                entry.ArrayLength = bs.ReadUInt32(); // 0 when version is 0 - its variable
                            }
                        }
                    }
                }

                // Final data is pretty much which type is first
                sdef.MasterTypeIndexOrID = bs.ReadUInt16();
                sdef.MasterHasCustomType = bs.ReadBoolean(BooleanCoding.Word);

                // The data part is the tree structure reassembling & data
                var def = new PseudoReflectionObject();
                def.Version = fixedArrLengthVersion;
                var mainCategory = sdef.Categories[sdef.MasterTypeIndexOrID];

                def.ParameterRoot = new SDEFParam();
                def.ParameterRoot.CustomTypeName = mainCategory.Name;
                def.ParameterRoot.NodeType       = NodeType.CustomType;

                int depth = 0;
                Traverse(bs, fixedArrLengthVersion, def, def.ParameterRoot, sdef, mainCategory, ref depth);
                return(def);
            }
        }
        public static void Traverse(BinaryStream reader, int version, PseudoReflectionObject sdef, SDEFBase parentNode, SDEFMetaData sdefMetadata, SDEFMetaDataCategory nodeCategory, ref int depth)
        {
            depth++;
            foreach (var entry in nodeCategory.Entries)
            {
                SDEFBase current;
                if (!entry.HasCustomType && (ValueType)entry.TypeOrIndex == ValueType.Array)
                {
                    current = new SDEFParamArray(); // Param is a param array
                }
                else
                {
                    current = new SDEFParam(); // Param is regular parameter, but if its a custom type it may have children parameters
                }
                current.Name = entry.Name;
                parentNode.ChildParameters.Add(current);

                sdef.ParameterList.Add(current);
                if (entry.HasCustomType)
                {
                    current.CustomTypeName = sdefMetadata.Categories[entry.TypeOrIndex].Name;
                    current.NodeType       = NodeType.CustomType;

                    // Traverse children parameter for this basic type
                    Traverse(reader, version, sdef, current, sdefMetadata, sdefMetadata.Categories[entry.TypeOrIndex], ref depth);
                }
                else if ((ValueType)entry.TypeOrIndex == ValueType.Array)
                {
                    if (entry.ArrayHasCustomType)
                    {
                        current.NodeType       = NodeType.CustomTypeArray;
                        current.CustomTypeName = sdefMetadata.Categories[entry.ArrayCategoryIndex].Name;
                        if (version == 0)
                        {
                            entry.ArrayLength = reader.ReadUInt32();
                        }

                        for (int i = 0; i < entry.ArrayLength; i++)
                        {
                            // Create the element for the array to add later
                            var arrayElement = new SDEFParam();
                            arrayElement.CustomTypeName = current.CustomTypeName;
                            arrayElement.NodeType       = NodeType.CustomType;
                            arrayElement.Name           = $"[{i}]";
                            Traverse(reader, version, sdef, arrayElement, sdefMetadata, sdefMetadata.Categories[entry.ArrayCategoryIndex], ref depth);

                            // Don't forget to add our array element to the global parameter list
                            sdef.ParameterList.Add(arrayElement);
                            (current as SDEFParamArray).Values.Add(arrayElement);
                        }
                    }
                    else
                    {
                        current.CustomTypeName = nodeCategory.Name;
                        current.NodeType       = NodeType.RawValueArray;
                        if (version == 0)
                        {
                            entry.ArrayLength = reader.ReadUInt32();
                        }

                        (current as SDEFParamArray).RawValuesArray = new SDEFVariant[entry.ArrayLength];
                        for (int i = 0; i < entry.ArrayLength; i++)
                        {
                            var val = ReadData(reader, entry, (ValueType)entry.ArrayCategoryIndex);
                            (current as SDEFParamArray).RawValuesArray[i] = val;
                        }
                    }
                }
                else
                {
                    current.NodeType = NodeType.RawValue;
                    var variant = ReadData(reader, entry, (ValueType)entry.TypeOrIndex);
                    (current as SDEFParam).RawValue = variant;
                }

                current.Depth = depth;
            }
            depth--;
        }