/// <summary>
        /// Deserializes binary byte data into an IteMetadata object.
        /// </summary>
        /// <param name="internalPath"></param>
        /// <param name="bytes"></param>
        /// <returns></returns>
        public static async Task <ItemMetadata> Deserialize(byte[] data)
        {
            using (var reader = new BinaryReader(new MemoryStream(data))) {
                uint version = reader.ReadUInt32();

                // File path name.
                var  path = "";
                char c;
                while ((c = reader.ReadChar()) != '\0')
                {
                    path += c;
                }

                var root = await XivCache.GetFirstRoot(path);

                var ret = new ItemMetadata(root);

                // General header data.
                uint headerCount      = reader.ReadUInt32();
                uint perHeaderSize    = reader.ReadUInt32();
                uint headerEntryStart = reader.ReadUInt32();

                // Per-Segment Header data.
                reader.BaseStream.Seek(headerEntryStart, SeekOrigin.Begin);

                List <(MetaDataType type, uint offset, uint size)> entries = new List <(MetaDataType type, uint size, uint offset)>();

                for (int i = 0; i < headerCount; i++)
                {
                    // Save offset.
                    var currentOffset = reader.BaseStream.Position;

                    // Read data.
                    MetaDataType type   = (MetaDataType)reader.ReadUInt32();
                    uint         offset = reader.ReadUInt32();
                    uint         size   = reader.ReadUInt32();

                    entries.Add((type, offset, size));

                    // Seek to next.
                    reader.BaseStream.Seek(currentOffset + perHeaderSize, SeekOrigin.Begin);
                }


                var imc = entries.FirstOrDefault(x => x.type == MetaDataType.Imc);
                if (imc.type != MetaDataType.Invalid)
                {
                    reader.BaseStream.Seek(imc.offset, SeekOrigin.Begin);
                    var bytes = reader.ReadBytes((int)imc.size);

                    // Deserialize IMC entry bytes here.
                    ret.ImcEntries = DeserializeImcData(bytes, root, version);
                }

                var eqp = entries.FirstOrDefault(x => x.type == MetaDataType.Eqp);
                if (eqp.type != MetaDataType.Invalid)
                {
                    reader.BaseStream.Seek(eqp.offset, SeekOrigin.Begin);
                    var bytes = reader.ReadBytes((int)eqp.size);

                    // Deserialize EQP entry bytes here.
                    ret.EqpEntry = DeserializeEqpData(bytes, root, version);
                }

                var eqdp = entries.FirstOrDefault(x => x.type == MetaDataType.Eqdp);
                if (eqdp.type != MetaDataType.Invalid)
                {
                    reader.BaseStream.Seek(eqdp.offset, SeekOrigin.Begin);
                    var bytes = reader.ReadBytes((int)eqdp.size);

                    // Deserialize EQDP entry bytes here.
                    ret.EqdpEntries = DeserializeEqdpData(bytes, root, version);
                }

                var est = entries.FirstOrDefault(x => x.type == MetaDataType.Est);
                if (est.type != MetaDataType.Invalid)
                {
                    reader.BaseStream.Seek(est.offset, SeekOrigin.Begin);
                    var bytes = reader.ReadBytes((int)est.size);

                    // Deserialize EQDP entry bytes here.
                    ret.EstEntries = await DeserializeEstData(bytes, root, version);
                }

                var gmp = entries.FirstOrDefault(x => x.type == MetaDataType.Gmp);
                if (gmp.type != MetaDataType.Invalid)
                {
                    reader.BaseStream.Seek(gmp.offset, SeekOrigin.Begin);
                    var bytes = reader.ReadBytes((int)gmp.size);

                    // Deserialize EQDP entry bytes here.
                    ret.GmpEntry = await DeserializeGmpData(bytes, root, version);
                }


                // Done deserializing all the parts.
                return(ret);
            }
        }
 /// <summary>
 /// Serializes the EQP data entries for the given meta file.
 /// </summary>
 /// <param name="meta"></param>
 /// <returns></returns>
 private static byte[] SerializeEqpData(ItemMetadata meta)
 {
     return(meta.EqpEntry.GetBytes());
 }
 private static byte[] SerializeGmpData(ItemMetadata meta)
 {
     // GMP data is relatively straight forward.  Just stick the 5 btes in the file.
     return(meta.GmpEntry.GetBytes());
 }
        /// <summary>
        /// Serializes a Metadata object into a byte representation for storage in dat or mod files.
        /// </summary>
        /// <param name="meta"></param>
        /// <returns></returns>
        public static async Task <byte[]> Serialize(ItemMetadata meta)
        {
            List <byte> bytes = new List <byte>();


            // Write general header.
            bytes.AddRange(BitConverter.GetBytes(_METADATA_VERSION));
            var path = meta.Root.Info.GetRootFile();

            bytes.AddRange(System.Text.Encoding.UTF8.GetBytes(path));
            bytes.Add(0);

            uint entries = 0;
            bool hasImc = false, hasEqp = false, hasEqdp = false, hasEst = false, hasGmp = false;

            if (meta.ImcEntries.Count > 0)
            {
                entries++;
                hasImc = true;
            }

            // Set 0 is a unique exception where SE hard-coded it to use Set 1's EQP data.  As such, we don't allow that data to be altered
            // via writing to set 0, just for safety purposes.
            if (meta.EqpEntry != null && !(meta.Root.Info.PrimaryType == Items.Enums.XivItemType.equipment && meta.Root.Info.PrimaryId == 0))
            {
                entries++;
                hasEqp = true;
            }

            if (meta.EqdpEntries.Count > 0)
            {
                entries++;
                hasEqdp = true;
            }

            if (meta.EstEntries.Count > 0)
            {
                entries++;
                hasEst = true;
            }
            if (meta.GmpEntry != null)
            {
                entries++;
                hasGmp = true;
            }

            bytes.AddRange(BitConverter.GetBytes(entries));
            bytes.AddRange(BitConverter.GetBytes(_METADATA_HEADER_SIZE));
            bytes.AddRange(BitConverter.GetBytes((uint)bytes.Count + 4));



            // Write per part header; save the offset for the metadata we need to fill in later.
            int imcHeaderInfoOffset = 0;

            if (hasImc)
            {
                imcHeaderInfoOffset = bytes.Count;
                bytes.AddRange(BitConverter.GetBytes((uint)MetaDataType.Imc));
                bytes.AddRange(BitConverter.GetBytes(0));
                bytes.AddRange(BitConverter.GetBytes(0));
            }

            int eqpHeaderInfoOffset = 0;

            if (hasEqp)
            {
                eqpHeaderInfoOffset = bytes.Count;
                bytes.AddRange(BitConverter.GetBytes((uint)MetaDataType.Eqp));
                bytes.AddRange(BitConverter.GetBytes(0));
                bytes.AddRange(BitConverter.GetBytes(0));
            }

            int eqdpHeaderInfoOffset = 0;

            if (hasEqdp)
            {
                eqdpHeaderInfoOffset = bytes.Count;
                bytes.AddRange(BitConverter.GetBytes((uint)MetaDataType.Eqdp));
                bytes.AddRange(BitConverter.GetBytes(0));
                bytes.AddRange(BitConverter.GetBytes(0));
            }

            int estHeaderInfoOffset = 0;

            if (hasEst)
            {
                estHeaderInfoOffset = bytes.Count;
                bytes.AddRange(BitConverter.GetBytes((uint)MetaDataType.Est));
                bytes.AddRange(BitConverter.GetBytes(0));
                bytes.AddRange(BitConverter.GetBytes(0));
            }

            int gmpHeaderOffset = 0;

            if (hasGmp)
            {
                gmpHeaderOffset = bytes.Count;
                bytes.AddRange(BitConverter.GetBytes((uint)MetaDataType.Gmp));
                bytes.AddRange(BitConverter.GetBytes(0));
                bytes.AddRange(BitConverter.GetBytes(0));
            }



            // Write the actual data.
            if (hasImc)
            {
                // Serialize IMC Data here.
                var imcData = SerializeImcData(meta);
                var offset  = bytes.Count;
                bytes.AddRange(imcData);

                IOUtil.ReplaceBytesAt(bytes, BitConverter.GetBytes((uint)offset), imcHeaderInfoOffset + 4);
                IOUtil.ReplaceBytesAt(bytes, BitConverter.GetBytes((uint)imcData.Length), imcHeaderInfoOffset + 8);
            }

            if (hasEqp)
            {
                // Serialize EQP Data here.
                var eqpData = SerializeEqpData(meta);
                var offset  = bytes.Count;
                bytes.AddRange(eqpData);

                IOUtil.ReplaceBytesAt(bytes, BitConverter.GetBytes((uint)offset), eqpHeaderInfoOffset + 4);
                IOUtil.ReplaceBytesAt(bytes, BitConverter.GetBytes((uint)eqpData.Length), eqpHeaderInfoOffset + 8);
            }

            if (hasEqdp)
            {
                // Serialize EQP Data here.
                var eqdpData = SerializeEqdpData(meta);
                var offset   = bytes.Count;
                bytes.AddRange(eqdpData);

                IOUtil.ReplaceBytesAt(bytes, BitConverter.GetBytes((uint)offset), eqdpHeaderInfoOffset + 4);
                IOUtil.ReplaceBytesAt(bytes, BitConverter.GetBytes((uint)eqdpData.Length), eqdpHeaderInfoOffset + 8);
            }

            if (hasEst)
            {
                // Serialize EST Data here.
                var estData = SerializeEstData(meta);
                var offset  = bytes.Count;
                bytes.AddRange(estData);

                IOUtil.ReplaceBytesAt(bytes, BitConverter.GetBytes((uint)offset), estHeaderInfoOffset + 4);
                IOUtil.ReplaceBytesAt(bytes, BitConverter.GetBytes((uint)estData.Length), estHeaderInfoOffset + 8);
            }

            if (hasGmp)
            {
                // Serialize EST Data here.
                var gmpData = SerializeGmpData(meta);
                var offset  = bytes.Count;
                bytes.AddRange(gmpData);

                IOUtil.ReplaceBytesAt(bytes, BitConverter.GetBytes((uint)offset), gmpHeaderOffset + 4);
                IOUtil.ReplaceBytesAt(bytes, BitConverter.GetBytes((uint)gmpData.Length), gmpHeaderOffset + 8);
            }


            return(bytes.ToArray());
        }
        /// <summary>
        /// Restores the original SE metadata for this root.
        /// This should only be called by Index.DeleteFileDescriptor().
        /// </summary>
        public static async Task RestoreDefaultMetadata(XivDependencyRoot root, IndexFile index = null, ModList modlist = null)
        {
            var original = await ItemMetadata.CreateFromRaw(root, true);

            await ApplyMetadata(original, index, modlist);
        }
        /// <summary>
        /// Applies this Metadata object to the FFXIV file system.
        /// This should only called by Dat.WriteToDat() / RestoreDefaultMetadata()
        /// </summary>
        internal static async Task ApplyMetadata(ItemMetadata meta, IndexFile index = null, ModList modlist = null)
        {
            var _eqp     = new Eqp(XivCache.GameInfo.GameDirectory);
            var _modding = new Modding(XivCache.GameInfo.GameDirectory);
            var _index   = new Index(XivCache.GameInfo.GameDirectory);
            var df       = IOUtil.GetDataFileFromPath(meta.Root.Info.GetRootFile());

            var dummyItem = new XivGenericItemModel();

            dummyItem.Name = Constants.InternalModSourceName;
            dummyItem.SecondaryCategory = Constants.InternalModSourceName;


            // Beep boop
            bool doSave = false;

            if (index == null)
            {
                doSave = true;
                index  = await _index.GetIndexFile(df);

                modlist = await _modding.GetModListAsync();
            }


            if (meta.ImcEntries.Count > 0)
            {
                var _imc    = new Imc(XivCache.GameInfo.GameDirectory);
                var imcPath = meta.Root.GetRawImcFilePath();
                await _imc.SaveEntries(imcPath, meta.Root.Info.Slot, meta.ImcEntries, dummyItem, index, modlist);
            }

            // Applying EQP data via set 0 is not allowed, as it is a special set hard-coded to use Set 1's data.
            if (meta.EqpEntry != null && !(meta.Root.Info.PrimaryType == Items.Enums.XivItemType.equipment && meta.Root.Info.PrimaryId == 0))
            {
                await _eqp.SaveEqpEntry(meta.Root.Info.PrimaryId, meta.EqpEntry, dummyItem, index, modlist);
            }

            if (meta.EqdpEntries.Count > 0)
            {
                await _eqp.SaveEqdpEntries((uint)meta.Root.Info.PrimaryId, meta.Root.Info.Slot, meta.EqdpEntries, dummyItem, index, modlist);
            }

            if (meta.EstEntries.Count > 0)
            {
                var type    = Est.GetEstType(meta.Root);
                var entries = meta.EstEntries.Values.ToList();
                await Est.SaveExtraSkeletonEntries(type, entries, dummyItem, index, modlist);
            }

            if (meta.GmpEntry != null)
            {
                await _eqp.SaveGimmickParameter(meta.Root.Info.PrimaryId, meta.GmpEntry, dummyItem, index, modlist);
            }

            if (doSave && !XivCache.GameInfo.UseLumina)
            {
                await _index.SaveIndexFile(index);

                await _modding.SaveModListAsync(modlist);
            }
        }