/// <summary>
        /// Deletes a file descriptor/stub from the Index files.
        /// </summary>
        /// <param name="fullPath">Full internal file path to the file that should be deleted.</param>
        /// <param name="dataFile">Which data file to use</param>
        /// <returns></returns>
        public async Task <bool> DeleteFileDescriptor(string fullPath, XivDataFile dataFile, bool updateCache = true)
        {
            await UpdateDataOffset(0, fullPath, false, true);

            // This is a metadata entry being deleted, we'll need to restore the metadata entries back to default.
            if (fullPath.EndsWith(".meta"))
            {
                var root = await XivCache.GetFirstRoot(fullPath);

                await ItemMetadata.RestoreDefaultMetadata(root);
            }

            if (fullPath.EndsWith(".rgsp"))
            {
                await CMP.RestoreDefaultScaling(fullPath);
            }

            if (updateCache)
            {
                // Queue us for updating, *after* updating the associated metadata files.
                XivCache.QueueDependencyUpdate(fullPath);
            }

            return(true);
        }
Exemple #2
0
        private async Task AddWithChildren(string file, IItem item, byte[] rawData = null)
        {
            var children = new HashSet<string>();
            if (Path.GetExtension(file) == ".meta")
            {
                // If we're the root file, use the proper get all function
                // which will throw in the AVFX/ATEX stuff as well.
                var root = await XivCache.GetFirstRoot(file);
                if(root != null)
                {
                    var files = await root.GetAllFiles();
                    children = files.ToHashSet();
                }
            } else
            {
                children = await XivCache.GetChildrenRecursive(file);
            }


            foreach (var child in children)
            {
                var fe = new FileEntry() { Name = MakeFriendlyFileName(child), Path = child };
                if (child == file && rawData != null)
                {
                    await AddFile(fe, item, rawData);
                } else 
                {
                    await AddFile(fe, item);
                }
            }
        }
Exemple #3
0
        /// <summary>
        /// Retrieves the base racial or body skeleton for a given model file, parsing it from the base
        /// game files to generate it, if necessary.
        /// </summary>
        /// <param name="fullMdlPath"></param>
        /// <returns></returns>
        public static async Task <string> GetBaseSkeletonFile(string fullMdlPath)
        {
            var root = await XivCache.GetFirstRoot(fullMdlPath);

            var race = XivRace.All_Races;

            if (root.Info.PrimaryType == XivItemType.human || root.Info.PrimaryType == XivItemType.equipment || root.Info.PrimaryType == XivItemType.accessory)
            {
                race = XivRaces.GetXivRace(fullMdlPath.Substring(1, 4));
            }

            return(await GetBaseSkeletonFile(root.Info, race));
        }
Exemple #4
0
        /// <summary>
        /// Retrieves the Ex skeleton file for a given model file, parsing it from the base
        /// game files to generate it, if necessary.
        /// </summary>
        /// <param name="fullMdlPath"></param>
        /// <returns></returns>
        public static async Task <string> GetExtraSkeletonFile(string fullMdlPath)
        {
            var root = await XivCache.GetFirstRoot(fullMdlPath);

            var estType = Est.GetEstType(root);

            if (estType == Est.EstType.Invalid)
            {
                return(null);
            }

            // This is a hair/hat/face/body model at this point so this is a safe pull.
            var race = XivRaces.GetXivRace(fullMdlPath.Substring(1, 4));

            return(await GetExtraSkeletonFile(root.Info, race));
        }
        private async Task AsyncInit()
        {
            // Get this model's root.
            _root = await XivCache.GetFirstRoot(_oldModel.Source);

            if (_root != null && _root.Info.PrimaryType != XivItemType.indoor && _root.Info.PrimaryType != XivItemType.outdoor)
            {
                // Get all the materials in this root, and add them to the selectable list.

                var materials = await _root.GetMaterialFiles();

                foreach (var m in materials)
                {
                    var mName = Path.GetFileName(m);
                    mName = "/" + mName;
                    RootMaterials.Add(mName);
                }

                RootMaterials = RootMaterials.OrderBy(x => x).ToHashSet();
            }

            UpdateModelSizeWarning();
            UpdateMaterialsList();

            _view.MeshNumberBox.SelectionChanged       += MeshNumberBox_SelectionChanged;
            _view.PartNumberBox.SelectionChanged       += PartNumberBox_SelectionChanged;
            _view.MaterialSelectorBox.SelectionChanged += MaterialSelectorBox_SelectionChanged;
            _view.MaterialPathTextBox.KeyDown          += MaterialPathTextBox_KeyDown;
            _view.MaterialPathTextBox.LostFocus        += MaterialPathTextBox_LostFocus;

            _view.ShapesListBox.SelectionChanged     += ShapesListBox_SelectionChanged;
            _view.AttributesListBox.SelectionChanged += AttributesListBox_SelectionChanged;

            _view.RemoveShapeButton.Click     += RemoveShapeButton_Click;
            _view.RemoveAttributeButton.Click += RemoveAttributeButton_Click;

            _view.AddAttributeBox.SelectionChanged += AddAttributeBox_SelectionChanged;
            _view.AddAttributeTextBox.KeyDown      += AddAttributeTextBox_KeyDown;

            _view.ScaleComboBox.SelectionChanged += ScaleComboBox_SelectionChanged;

            _view.MeshNumberBox.SelectedIndex = 0;
        }
Exemple #6
0
        private async Task LoadFiles()
        {
            var parentFiles = _entry.MainFiles;
            var files       = new SortedSet <string>();

            if (_entry.Level == XivDependencyLevel.Root)
            {
                var root = await XivCache.GetFirstRoot(_entry.MainFiles[0]);

                files = await root.GetAllFiles();
            }
            else
            {
                foreach (var file in parentFiles)
                {
                    var children = await XivCache.GetChildrenRecursive(file);

                    foreach (var child in children)
                    {
                        files.Add(child);
                    }
                }
            }


            _entry.AllFiles.Clear();
            foreach (var file in files)
            {
                var exists = await AddFile(file);

                if (exists)
                {
                    _entry.AllFiles.Add(file);
                }
            }

            UpdateCounts();

            ConfirmButton.IsEnabled = true;
        }
Exemple #7
0
        private async void CopyButton_Click(object sender, RoutedEventArgs e)
        {
            var to   = ToBox.Text;
            var from = FromBox.Text;

            try
            {
                if (string.IsNullOrWhiteSpace(to) || string.IsNullOrWhiteSpace(from))
                {
                    return;
                }
                to   = to.Trim().ToLower();
                from = from.Trim().ToLower();

                if (!to.EndsWith(".mdl") || !from.EndsWith(".mdl"))
                {
                    return;
                }

                var toRoot = await XivCache.GetFirstRoot(to);

                var fromRoot = await XivCache.GetFirstRoot(from);

                var df = IOUtil.GetDataFileFromPath(to);

                var _mdl = new Mdl(XivCache.GameInfo.GameDirectory, df);

                await _mdl.CopyModel(from, to, XivStrings.TexTools, true);

                FlexibleMessageBox.Show("Model Copied Successfully.", "Model Copy Confirmation", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Information);
                Close();
            }
            catch (Exception ex)
            {
                FlexibleMessageBox.Show("Model Copied Failed.\n\nError: " + ex.Message, "Model Copy Error", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error);
            }
        }
Exemple #8
0
        public async Task SaveMulti()
        {
            var _imc   = new Imc(XivCache.GameInfo.GameDirectory);
            var _index = new Index(XivCache.GameInfo.GameDirectory);

            // Get tokenized map info structs.
            // This will let us set them in the new Materials and
            // Detokenize them using the new paths.
            var mapInfos = _material.GetAllMapInfos(true);


            // Shader info likewise will be pumped into each new material.
            var shaderInfo = _material.GetShaderInfo();

            // Add new Materials for shared model items.
            var oldMaterialIdentifier = _material.GetMaterialIdentifier();
            var oldMtrlName           = Path.GetFileName(_material.MTRLPath);

            // Ordering these by name ensures that we create textures for the new variants in the first
            // item alphabetically, just for consistency's sake.
            var sameModelItems = (await _item.GetSharedModelItems()).OrderBy(x => x.Name, new ItemNameComparer());

            var oldVariantString = "/v" + _material.GetVariant().ToString().PadLeft(4, '0') + '/';
            var modifiedVariants = new List <int>();


            var mtrlReplacementRegex       = "_" + oldMaterialIdentifier + ".mtrl";
            var mtrlReplacementRegexResult = "_" + _newMaterialIdentifier + ".mtrl";

            if (_mode == MaterialEditorMode.NewRace)
            {
                mtrlReplacementRegexResult = mtrlReplacementRegex;
            }

            var newMtrlName = oldMtrlName.Replace(mtrlReplacementRegex, mtrlReplacementRegexResult);

            var root = _item.GetRootInfo();

            var imcEntries   = new List <XivImc>();
            var materialSets = new HashSet <byte>();

            try
            {
                var imcInfo = await _imc.GetFullImcInfo(_item);

                imcEntries   = imcInfo.GetAllEntries(root.Slot, true);
                materialSets = imcEntries.Select(x => x.MaterialSet).ToHashSet();
            } catch
            {
                // Item doesn't use IMC entries, and thus only has a single variant.
                materialSets.Clear();
                materialSets.Add(0);
            }


            // We need to save our non-existent base material once before we can continue.
            if (_mode == MaterialEditorMode.NewRace)
            {
                await _mtrl.ImportMtrl(_material, _item, XivStrings.TexTools);
            }

            var count = 0;

            var allItems = (await root.ToFullRoot().GetAllItems());

            var matNumToItems = new Dictionary <int, List <IItemModel> >();

            foreach (var i in allItems)
            {
                if (imcEntries.Count <= i.ModelInfo.ImcSubsetID)
                {
                    continue;
                }

                var matSet = imcEntries[i.ModelInfo.ImcSubsetID].MaterialSet;
                if (!matNumToItems.ContainsKey(matSet))
                {
                    matNumToItems.Add(matSet, new List <IItemModel>());
                }

                var saveItem = i;

                if (typeof(XivCharacter) == i.GetType())
                {
                    var temp = (XivCharacter)((XivCharacter)_item).Clone();
                    temp.Name = saveItem.SecondaryCategory;
                    saveItem  = temp;
                }

                matNumToItems[matSet].Add(saveItem);
            }

            var keys = matNumToItems.Keys.ToList();

            foreach (var key in keys)
            {
                var list = matNumToItems[key];
                matNumToItems[key] = list.OrderBy(x => x.Name, new ItemNameComparer()).ToList();
            }

            // Load and modify all the MTRLs.
            foreach (var materialSetId in materialSets)
            {
                var variantPath     = _mtrl.GetMtrlFolder(root, materialSetId);
                var oldMaterialPath = variantPath + "/" + oldMtrlName;
                var newMaterialPath = variantPath + "/" + newMtrlName;

                // Don't create materials for set 0.  (SE sets the material ID to 0 when that particular set-slot doesn't actually exist as an item)
                if (materialSetId == 0 && imcEntries.Count > 0)
                {
                    continue;
                }

                var     dxVersion = 11;
                XivMtrl itemXivMtrl;

                // Get mtrl path
                if (await _index.FileExists(oldMaterialPath))
                {
                    // Use the Material from this variant as a base?
                    itemXivMtrl = await _mtrl.GetMtrlData(_item, oldMaterialPath, dxVersion);
                }
                else
                {
                    itemXivMtrl = await _mtrl.GetMtrlData(_item, _material.MTRLPath, dxVersion);
                }

                // If we're an item that doesn't use IMC variants, make sure we don't accidentally move the material around.
                if (materialSetId != 0)
                {
                    // Shift the MTRL to the new variant folder.
                    itemXivMtrl.MTRLPath = Regex.Replace(itemXivMtrl.MTRLPath, oldVariantString, "/v" + materialSetId.ToString().PadLeft(4, '0') + "/");
                }

                if (_mode == MaterialEditorMode.NewMulti)
                {
                    // Change the MTRL part identifier.
                    itemXivMtrl.MTRLPath = Regex.Replace(itemXivMtrl.MTRLPath, mtrlReplacementRegex, mtrlReplacementRegexResult);
                }

                // Load the Shader Settings
                itemXivMtrl.SetShaderInfo(shaderInfo, true);

                // Loop our tokenized map infos and pump them back in
                // using the new modified material to detokenize them.
                foreach (var info in mapInfos)
                {
                    itemXivMtrl.SetMapInfo(info.Usage, (MapInfo)info.Clone());
                }


                IItem item;
                try
                {
                    item = matNumToItems[materialSetId].First();
                } catch
                {
                    item = (await XivCache.GetFirstRoot(itemXivMtrl.MTRLPath)).GetFirstItem();
                }

                count++;
                // Write the new Material
                await _mtrl.ImportMtrl(itemXivMtrl, item, XivStrings.TexTools);

                _view.SaveStatusLabel.Content = "Updated " + count + "/" + materialSets.Count + " Material Sets...";
            }
        }
Exemple #9
0
        /// <summary>
        /// Creates texture data ready to be imported into the DATs from an external file.
        /// If format is not specified, either the incoming file's DDS format is used (DDS files),
        /// or the existing internal file's DDS format is used.
        /// </summary>
        /// <param name="internalPath"></param>
        /// <param name="externalPath"></param>
        /// <param name="texFormat"></param>
        /// <returns></returns>
        public async Task <byte[]> MakeTexData(string internalPath, string externalPath, XivTexFormat texFormat = XivTexFormat.INVALID)
        {
            // Ensure file exists.
            if (!File.Exists(externalPath))
            {
                throw new IOException($"Could not find file: {externalPath}");
            }

            var root = await XivCache.GetFirstRoot(internalPath);

            bool isDds = Path.GetExtension(externalPath).ToLower() == ".dds";

            var ddsContainer = new DDSContainer();

            try
            {
                // If no format was specified...
                if (texFormat == XivTexFormat.INVALID)
                {
                    if (isDds)
                    {
                        // If we're importing a DDS file, get the format from the incoming DDS file
                        using (var fs = new FileStream(externalPath, FileMode.Open))
                        {
                            using (var sr = new BinaryReader(fs))
                            {
                                texFormat = GetDDSTexFormat(sr);
                            }
                        }
                    }
                    else
                    {
                        // Otherwise use the current internal format.
                        var xivt = await _dat.GetType4Data(internalPath, false);

                        texFormat = xivt.TextureFormat;
                    }
                }

                // Check if the texture being imported has been imported before
                CompressionFormat compressionFormat = CompressionFormat.BGRA;

                switch (texFormat)
                {
                case XivTexFormat.DXT1:
                    compressionFormat = CompressionFormat.BC1a;
                    break;

                case XivTexFormat.DXT5:
                    compressionFormat = CompressionFormat.BC3;
                    break;

                case XivTexFormat.A8R8G8B8:
                    compressionFormat = CompressionFormat.BGRA;
                    break;

                default:
                    if (!isDds)
                    {
                        throw new Exception($"Format {texFormat} is not currently supported for BMP import\n\nPlease use the DDS import option instead.");
                    }
                    break;
                }

                if (!isDds)
                {
                    using (var surface = Surface.LoadFromFile(externalPath))
                    {
                        if (surface == null)
                        {
                            throw new FormatException($"Unsupported texture format");
                        }

                        surface.FlipVertically();

                        var maxMipCount = 1;
                        if (root != null)
                        {
                            // For things that have real roots (things that have actual models/aren't UI textures), we always want mipMaps, even if the existing texture only has one.
                            // (Ex. The Default Mat-Add textures)
                            maxMipCount = -1;
                        }

                        using (var compressor = new Compressor())
                        {
                            // UI/Paintings only have a single mipmap and will crash if more are generated, for everything else generate max levels
                            compressor.Input.SetMipmapGeneration(true, maxMipCount);
                            compressor.Input.SetData(surface);
                            compressor.Compression.Format = compressionFormat;
                            compressor.Compression.SetBGRAPixelFormat();

                            compressor.Process(out ddsContainer);
                        }
                    }
                }

                // If we're not a DDS, write the DDS to file temporarily.
                var ddsFilePath = externalPath;
                if (!isDds)
                {
                    var tempFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + ".dds");
                    ddsContainer.Write(tempFile, DDSFlags.None);
                    ddsFilePath = tempFile;
                }

                using (var br = new BinaryReader(File.OpenRead(ddsFilePath)))
                {
                    br.BaseStream.Seek(12, SeekOrigin.Begin);

                    var newHeight = br.ReadInt32();
                    var newWidth  = br.ReadInt32();
                    br.ReadBytes(8);
                    var newMipCount = br.ReadInt32();

                    if (newHeight % 2 != 0 || newWidth % 2 != 0)
                    {
                        throw new Exception("Resolution must be a multiple of 2");
                    }

                    br.BaseStream.Seek(80, SeekOrigin.Begin);

                    var textureFlags = br.ReadInt32();
                    var texType      = br.ReadInt32();

                    var uncompressedLength = (int)new FileInfo(ddsFilePath).Length - 128;
                    var newTex             = new List <byte>();

                    if (!internalPath.Contains(".atex"))
                    {
                        var DDSInfo = await DDS.ReadDDS(br, texFormat, newWidth, newHeight, newMipCount);

                        newTex.AddRange(_dat.MakeType4DatHeader(texFormat, DDSInfo.mipPartOffsets, DDSInfo.mipPartCounts, (int)uncompressedLength, newMipCount, newWidth, newHeight));
                        newTex.AddRange(MakeTextureInfoHeader(texFormat, newWidth, newHeight, newMipCount));
                        newTex.AddRange(DDSInfo.compressedDDS);

                        return(newTex.ToArray());
                    }
                    else
                    {
                        br.BaseStream.Seek(128, SeekOrigin.Begin);
                        newTex.AddRange(MakeTextureInfoHeader(texFormat, newWidth, newHeight, newMipCount));
                        newTex.AddRange(br.ReadBytes((int)uncompressedLength));
                        var data = await _dat.CreateType2Data(newTex.ToArray());

                        return(data);
                    }
                }
            }
            finally
            {
                ddsContainer.Dispose();
            }
        }
Exemple #10
0
        private async void AnyTextChanged(object sender, TextChangedEventArgs e)
        {
            var to   = ToBox.Text;
            var from = FromBox.Text;

            if (string.IsNullOrWhiteSpace(to) || string.IsNullOrWhiteSpace(from))
            {
                CopyButton.IsEnabled = false;
                return;
            }

            to   = to.Trim().ToLower();
            from = from.Trim().ToLower();

            if (!to.EndsWith(".mdl") || !from.EndsWith(".mdl"))
            {
                MaterialCopyNotice.Text       = "--";
                MaterialCopyNotice.Foreground = Brushes.Black;

                RaceChangeNotice.Text       = "--";
                RaceChangeNotice.Foreground = Brushes.Black;

                CopyButton.IsEnabled = false;
                return;
            }

            try
            {
                var df  = IOUtil.GetDataFileFromPath(to);
                var df2 = IOUtil.GetDataFileFromPath(from);

                if (df != df2)
                {
                    MaterialCopyNotice.Text       = "Source and target files must exist within the same data file.";
                    MaterialCopyNotice.Foreground = Brushes.Red;

                    RaceChangeNotice.Text       = "--";
                    RaceChangeNotice.Foreground = Brushes.Black;

                    CopyButton.IsEnabled = false;
                    return;
                }
            } catch
            {
                MaterialCopyNotice.Text       = "At least one file path is not a valid internal FFXIV file path.";
                MaterialCopyNotice.Foreground = Brushes.Red;

                RaceChangeNotice.Text       = "--";
                RaceChangeNotice.Foreground = Brushes.Black;

                CopyButton.IsEnabled = false;
                return;
            }

            CopyButton.IsEnabled = true;
            var toRoot = await XivCache.GetFirstRoot(to);

            var fromRoot = await XivCache.GetFirstRoot(from);

            if (toRoot == null || fromRoot == null)
            {
                MaterialCopyNotice.Text       = "Unknown File Root - Materials and textures will not be copied.";
                MaterialCopyNotice.Foreground = Brushes.DarkGoldenrod;

                RaceChangeNotice.Text       = "Unknown File Root - Model will not be racially adjusted.";
                RaceChangeNotice.Foreground = Brushes.DarkGoldenrod;
                return;
            }
            else
            {
                MaterialCopyNotice.Text       = "Materials and textures will be copied to destination root folder.";
                MaterialCopyNotice.Foreground = Brushes.Green;
            }

            var raceRegex = new Regex("c([0-9]{4})");

            var toMatch   = raceRegex.Match(to);
            var fromMatch = raceRegex.Match(from);

            if (!toMatch.Success || !fromMatch.Success)
            {
                RaceChangeNotice.Text       = "Model is not racial - Model will not be racially adjusted.";
                RaceChangeNotice.Foreground = Brushes.Black;
                return;
            }

            var toRace   = XivRaces.GetXivRace(toMatch.Groups[1].Value);
            var fromRace = XivRaces.GetXivRace(fromMatch.Groups[1].Value);

            if (toRace == fromRace)
            {
                RaceChangeNotice.Text       = "Model races are identical - Model will not be racially adjusted.";
                RaceChangeNotice.Foreground = Brushes.Black;
                return;
            }


            RaceChangeNotice.Text       = "Model will be adjusted from " + fromRace.GetDisplayName() + " to " + toRace.GetDisplayName() + ".";
            RaceChangeNotice.Foreground = Brushes.Green;
        }
        /// <summary>
        /// This should only really be called directly if the control was created with DeferLoading set to true.
        /// </summary>
        /// <returns></returns>
        public async Task LoadItems()
        {
            if (_READY)
            {
                SearchTimer.Stop();
                SearchTimer.Dispose();
                ClearSelection();
            }

            if (LockUiFunction != null)
            {
                await LockUiFunction(UIStrings.Loading_Items, null, this);
            }


            // Pump us into another thread so the UI stays nice and fresh.
            await Task.Run(async() =>
            {
                CategoryElements    = new ObservableCollection <ItemTreeElement>();
                SetElements         = new ObservableCollection <ItemTreeElement>();
                DependencyRootNodes = new Dictionary <string, ItemTreeElement>();

                try
                {
                    // Gotta build set tree first, so the items from the item list can latch onto the nodes there.
                    BuildSetTree();
                    await BuildCategoryTree();
                }
                catch (Exception ex)
                {
                    FlexibleMessageBox.Show("An error occurred while loading the item list.\n" + ex.Message, "Item List Error", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Warning);
                    return;
                }


                var toAdd = new List <(ItemTreeElement parent, ItemTreeElement child)>();
                foreach (var kv in DependencyRootNodes)
                {
                    // This dependency root had no EXD-Items associated with it.
                    // Gotta make a generic item for it.
                    if (kv.Value.Children.Count == 0)
                    {
                        // See if we can actually turn this root into a fully fledged item.
                        try
                        {
                            var root = await XivCache.GetFirstRoot(kv.Key);
                            if (root != null)
                            {
                                // If we can, add it into the list.
                                var item = root.ToRawItem();
                                var e    = new ItemTreeElement(null, kv.Value, item);
                                toAdd.Add((kv.Value, e));
                            }
                            else
                            {
                                var e = new ItemTreeElement(null, kv.Value, "[Unsupported]");
                                toAdd.Add((kv.Value, e));
                            }
                        }
                        catch (Exception ex)
                        {
                            throw;
                        }
                    }
                }

                // Loop back through to add the new items, so we're not affecting the previous iteration.
                foreach (var tup in toAdd)
                {
                    tup.parent.Children.Add(tup.child);
                }
            });

            var view = (CollectionView)CollectionViewSource.GetDefaultView(CategoryElements);

            view.Filter = SearchFilter;


            view        = (CollectionView)CollectionViewSource.GetDefaultView(SetElements);
            view.Filter = SearchFilter;

            SearchTimer          = new Timer(300);
            SearchTimer.Elapsed += Search;

            CategoryTree.ItemsSource = CategoryElements;
            SetTree.ItemsSource      = SetElements;

            _READY = true;

            Search(this, null);

            if (UnlockUiFunction != null)
            {
                await UnlockUiFunction(this);
            }

            if (ItemsLoaded != null)
            {
                ItemsLoaded.Invoke(this, null);
            }
        }
Exemple #12
0
        public async Task SetFile(string filePath)
        {
            await LockUi();

            WarningLabel.Text = "";
            _path             = filePath;

            ChildFilesBox.Items.Clear();
            ParentFilesBox.Items.Clear();
            SiblingFilesBox.Items.Clear();
            AffectedItemsBox.Items.Clear();
            ModelLevelBox.Text    = "";
            MaterialLevelBox.Text = "";



            FilePathLabel.Text = filePath;
            FileNameBox.Text   = Path.GetFileName(filePath);

            var root = await XivCache.GetFirstRoot(filePath);

            if (root == null)
            {
                RootNameBox.Text  = "NULL";
                WarningLabel.Text = "File dependency information not supported for this item.";
                await UnlockUi();

                return;
            }
            RootNameBox.Text = root.ToRawItem().Name;

            var ext = Path.GetExtension(filePath).Substring(1);

            var allItems = (await root.GetAllItems());

            if (allItems.Count == 0)
            {
                WarningLabel.Text = "File dependency information not supported for this item.";
                await UnlockUi();

                return;
            }

            if (root.Info.PrimaryType == XivItemType.human)
            {
                WarningLabel.Text = "Parent file references & affected items lists \n may not be complete for this file. \n (Unknown cross-references may exist.)";
            }

            var orderedItems = allItems.OrderBy((x => x.Name), new ItemNameComparer()).ToList();
            var modelItem    = orderedItems[0];

            ModelLevelBox.Text = modelItem.Name;


            var children = await XivCache.GetChildFiles(filePath);

            var parents = await XivCache.GetParentFiles(filePath);

            var siblings = await XivCache.GetSiblingFiles(filePath);

            foreach (var s in children)
            {
                ChildFilesBox.Items.Add(s);
            }
            foreach (var s in parents)
            {
                ParentFilesBox.Items.Add(s);
            }
            foreach (var s in siblings)
            {
                SiblingFilesBox.Items.Add(s);
            }

            Dictionary <XivDependencyRoot, HashSet <int> > mVariants = new Dictionary <XivDependencyRoot, HashSet <int> >();
            var affectedItems = new List <IItem>();

            if (ext == "tex")
            {
                foreach (var parent in parents)
                {
                    // Each of our parents has a root...
                    var pRoot = await XivCache.GetFirstRoot(parent);

                    if (pRoot == null)
                    {
                        continue;
                    }

                    // And each of these files also has a material set id.
                    var match    = _extractVariant.Match(parent);
                    var mVariant = 0;
                    if (match.Success)
                    {
                        mVariant = Int32.Parse(match.Groups[1].Value);
                    }

                    if (!mVariants.ContainsKey(pRoot))
                    {
                        mVariants.Add(pRoot, new HashSet <int>());
                    }
                    mVariants[pRoot].Add(mVariant);


                    var pItems = (await pRoot.GetAllItems());
                }
            }
            else if (ext == "mtrl")
            {
                var match    = _extractVariant.Match(filePath);
                var mVariant = 0;
                if (match.Success)
                {
                    mVariant = Int32.Parse(match.Groups[1].Value);
                }

                if (!mVariants.ContainsKey(root))
                {
                    mVariants.Add(root, new HashSet <int>());
                }
                mVariants[root].Add(mVariant);
            }
            else
            {
                foreach (var item in allItems)
                {
                    affectedItems.Add(item);
                }
                MaterialLevelBox.Text = "";
            }


            Dictionary <XivDependencyRoot, HashSet <int> > sharedImcSubsets = new Dictionary <XivDependencyRoot, HashSet <int> >();

            var _imc = new Imc(XivCache.GameInfo.GameDirectory);

            try
            {
                // We now have a dictionary of <Root>, <Material Set Id> that comprises all of our referencing material sets.
                // We now need to convert that into a list of <root> => <Imc Subset IDs>, for all IMC subsets in that root which use our material ID.
                foreach (var kv in mVariants)
                {
                    var rt = kv.Key;
                    sharedImcSubsets.Add(rt, new HashSet <int>());
                    var imcPath = rt.GetRawImcFilePath();

                    var fullImcInfo = await _imc.GetFullImcInfo(imcPath);

                    var setCount = fullImcInfo.SubsetCount + 1;
                    for (int i = 0; i < setCount; i++)
                    {
                        var info = fullImcInfo.GetEntry(i, rt.Info.Slot);

                        // This IMC subset references the one of our material sets.
                        if (kv.Value.Contains(info.Variant))
                        {
                            sharedImcSubsets[rt].Add(i);
                        }
                    }
                }

                // We now have a dictionary of <root>, <imc subset ids>.  At this point, we can compare this to our original alll items list.
                var sh = allItems.Where(x =>
                {
                    var iRoot = x.GetRoot();

                    // The root must be one of our roots we care about.
                    if (!sharedImcSubsets.ContainsKey(iRoot))
                    {
                        return(false);
                    }

                    var imcs = sharedImcSubsets[iRoot];

                    // The imc subset must be in the imc subsets that use this material.
                    if (!imcs.Contains(x.ModelInfo.ImcSubsetID))
                    {
                        return(false);
                    }

                    return(true);
                });

                foreach (var i in sh)
                {
                    affectedItems.Add(i);
                }
            } catch
            {
                // The item doesn't have a valid IMC entry.  In that case, it affects all items in the tree.
                affectedItems.AddRange(allItems);
            }



            if (affectedItems.Count == 0)
            {
                MaterialLevelBox.Text = "Unknown";
            }
            else
            {
                if (ext == "tex" || ext == "mtrl")
                {
                    var ordered = affectedItems.OrderBy((x => x.Name), new ItemNameComparer()).ToList();
                    MaterialLevelBox.Text = ordered[0].Name;
                }
            }

            foreach (var item in affectedItems)
            {
                AffectedItemsBox.Items.Add(item);
            }

            await UnlockUi();
        }
        /// <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>
        /// Gets the metadata file from an internal file path.
        /// Can be the raw path to the meta file, the file root, or any file in the root's file tree.
        /// </summary>
        /// <param name="internalFilePath"></param>
        /// <returns></returns>
        public static async Task <ItemMetadata> GetMetadata(string internalFilePath, bool forceDefault = false)
        {
            var root = await XivCache.GetFirstRoot(internalFilePath);

            return(await GetMetadata(root, forceDefault));
        }