/// <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); }
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); } } }
/// <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)); }
/// <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; }
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; }
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); } }
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..."; } }
/// <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(); } }
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); } }
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)); }