/// <summary> /// The event handler for the add custom texture button clicked /// </summary> private async void AddCustomTextureButton_Click(object sender, RoutedEventArgs e) { var selectedItem = TextureMapComboBox.SelectedItem as ModComboBox; var mod = selectedItem.SelectedMod; byte[] modData; var includedMod = new IncludedMods { Name = $"{Path.GetFileNameWithoutExtension(mod.fullPath)} ({((Category)ModListTreeView.SelectedItem).Name})", FullPath = mod.fullPath }; var includedModsList = IncludedModsList.Items.Cast <IncludedMods>().ToList(); var tex = new Tex(_gameDirectory); var ddsDirectory = new DirectoryInfo(CustomTextureTextBox.Text); if (selectedItem.TexTypePath.Type == XivTexType.ColorSet) { var mtrl = new Mtrl(_gameDirectory, XivDataFiles.GetXivDataFile(mod.datFile), GetLanguage()); var xivMtrl = await mtrl.GetMtrlData(mod.data.modOffset, mod.fullPath, int.Parse(Settings.Default.DX_Version)); modData = tex.DDStoMtrlData(xivMtrl, ddsDirectory, ((Category)ModListTreeView.SelectedItem).Item, GetLanguage()); } else { var texData = await tex.GetTexData(selectedItem.TexTypePath); modData = await tex.DDStoTexData(texData, ((Category)ModListTreeView.SelectedItem).Item, ddsDirectory); } if (includedModsList.Any(item => item.Name.Equals(includedMod.Name))) { if (FlexibleMessageBox.Show( string.Format(UIMessages.ExistingOption, includedMod.Name), UIMessages.OverwriteTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == System.Windows.Forms.DialogResult.Yes) { _selectedModOption.Mods[mod.fullPath].ModDataBytes = modData; } } else { IncludedModsList.Items.Add(includedMod); _selectedModOption.Mods.Add(mod.fullPath, new ModData { Name = mod.name, Category = mod.category, FullPath = mod.fullPath, ModDataBytes = modData }); } }
public async Task SaveMulti() { // 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(); // 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"; // Load and modify all the MTRLs. foreach (var item in sameModelItems) { // Resolve this item's material variant. // - This isn't always the same as the item model variant, for some reason. // - So it has to be resolved manually. var variantMtrlPath = ""; var itemType = ItemType.GetPrimaryItemType(_item); variantMtrlPath = (await _mtrl.GetMtrlPath(item, _material.GetRace(), oldMaterialIdentifier, itemType)).Folder; var match = Regex.Match(variantMtrlPath, "/v([0-9]+)"); var variant = 0; if (match.Success) { variant = Int32.Parse(match.Groups[1].Value); } // Only modify each Variant once. if (modifiedVariants.Contains(variant)) { continue; } var dxVersion = 11; XivMtrl itemXivMtrl; // Get mtrl path -- TODO: Need support here for offhand item materials. // But Offhand support is basically completely broken anyways, so this can wait. itemXivMtrl = await _mtrl.GetMtrlData(_item, _material.GetRace(), oldMaterialIdentifier, dxVersion); // Shift the MTRL to the new variant folder. itemXivMtrl.MTRLPath = Regex.Replace(itemXivMtrl.MTRLPath, oldVariantString, "/v" + variant.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()); } // Write the new Material await _mtrl.ImportMtrl(itemXivMtrl, item, XivStrings.TexTools); modifiedVariants.Add(variant); _view.SaveStatusLabel.Content = "Updated " + modifiedVariants.Count + " Variants..."; } }
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> /// Gets the materials for the model /// </summary> /// <returns>A dictionary containing the mesh number(key) and the associated texture data (value)</returns> public static Dictionary <int, ModelTextureData> GetMaterials( DirectoryInfo gameDirectory, IItemModel item, XivMdl mdlData, XivRace race) { var textureDataDictionary = new Dictionary <int, ModelTextureData>(); var mtrlDictionary = new Dictionary <int, XivMtrl>(); var mtrl = new Mtrl(gameDirectory, item.DataFile); var mtrlFilePaths = mdlData.PathData.MaterialList; var hasColorChangeShader = false; Color? customColor = null; WinColor winColor; var materialNum = 0; foreach (var mtrlFilePath in mtrlFilePaths) { var mtrlItem = new XivGenericItemModel { Category = item.Category, ItemCategory = item.ItemCategory, ItemSubCategory = item.ItemSubCategory, ModelInfo = new XivModelInfo { Body = item.ModelInfo.Body, ModelID = item.ModelInfo.ModelID, ModelType = item.ModelInfo.ModelType, Variant = item.ModelInfo.Variant }, Name = item.Name }; var modelID = mtrlItem.ModelInfo.ModelID; var bodyID = mtrlItem.ModelInfo.Body; var filePath = mtrlFilePath; if (!filePath.Contains("hou") && mtrlFilePath.Count(x => x == '/') > 1) { filePath = mtrlFilePath.Substring(mtrlFilePath.LastIndexOf("/")); } var typeChar = $"{mtrlFilePath[4]}{mtrlFilePath[9]}"; var raceString = ""; switch (typeChar) { // Character Body case "cb": var body = mtrlFilePath.Substring(mtrlFilePath.IndexOf("b") + 1, 4); raceString = mtrlFilePath.Substring(mtrlFilePath.IndexOf("c") + 1, 4); race = XivRaces.GetXivRace(raceString); if (!raceString.Equals("0901") && !raceString.Equals("1001") && !raceString.Equals("1101")) { var gender = 0; if (int.Parse(raceString.Substring(0, 2)) % 2 == 0) { gender = 1; } var settingsRace = GetSettingsRace(gender); race = settingsRace.Race; filePath = mtrlFilePath.Replace(raceString, race.GetRaceCode()).Replace(body, settingsRace.BodyID); body = settingsRace.BodyID; } mtrlItem = new XivGenericItemModel { Category = XivStrings.Character, ItemCategory = XivStrings.Body, Name = XivStrings.Body, ModelInfo = new XivModelInfo { Body = int.Parse(body) } }; winColor = (WinColor)ColorConverter.ConvertFromString(Settings.Default.Skin_Color); customColor = new Color(winColor.R, winColor.G, winColor.B, winColor.A); break; // Face case "cf": bodyID = int.Parse(mtrlFilePath.Substring(mtrlFilePath.IndexOf("f") + 1, 4)); raceString = mtrlFilePath.Substring(mtrlFilePath.IndexOf("c") + 1, 4); race = XivRaces.GetXivRace(raceString); mtrlItem = new XivGenericItemModel { Category = XivStrings.Character, ItemCategory = XivStrings.Face, Name = XivStrings.Face, ModelInfo = new XivModelInfo { Body = bodyID } }; break; // Hair case "ch": bodyID = int.Parse(mtrlFilePath.Substring(mtrlFilePath.IndexOf("h") + 1, 4)); raceString = mtrlFilePath.Substring(mtrlFilePath.IndexOf("c") + 1, 4); race = XivRaces.GetXivRace(raceString); mtrlItem = new XivGenericItemModel { Category = XivStrings.Character, ItemCategory = XivStrings.Hair, Name = XivStrings.Hair, ModelInfo = new XivModelInfo { Body = bodyID } }; winColor = (WinColor)ColorConverter.ConvertFromString(Settings.Default.Hair_Color); customColor = new Color(winColor.R, winColor.G, winColor.B, winColor.A); break; // Tail case "ct": var tempPath = mtrlFilePath.Substring(4); bodyID = int.Parse(tempPath.Substring(tempPath.IndexOf("t") + 1, 4)); raceString = mtrlFilePath.Substring(mtrlFilePath.IndexOf("c") + 1, 4); race = XivRaces.GetXivRace(raceString); mtrlItem = new XivGenericItemModel { Category = XivStrings.Character, ItemCategory = XivStrings.Tail, Name = XivStrings.Tail, ModelInfo = new XivModelInfo { Body = bodyID } }; winColor = (WinColor)ColorConverter.ConvertFromString(Settings.Default.Hair_Color); customColor = new Color(winColor.R, winColor.G, winColor.B, winColor.A); break; // Equipment case "ce": modelID = int.Parse(mtrlFilePath.Substring(mtrlFilePath.IndexOf("e") + 1, 4)); raceString = mtrlFilePath.Substring(mtrlFilePath.IndexOf("c") + 1, 4); race = XivRaces.GetXivRace(raceString); mtrlItem.ModelInfo.ModelID = modelID; break; // Accessory case "ca": modelID = int.Parse(mtrlFilePath.Substring(mtrlFilePath.IndexOf("a") + 1, 4)); raceString = mtrlFilePath.Substring(mtrlFilePath.IndexOf("c") + 1, 4); race = XivRaces.GetXivRace(raceString); mtrlItem.ModelInfo.ModelID = modelID; break; // Weapon case "wb": modelID = int.Parse(mtrlFilePath.Substring(mtrlFilePath.IndexOf("w") + 1, 4)); bodyID = int.Parse(mtrlFilePath.Substring(mtrlFilePath.IndexOf("b") + 1, 4)); mtrlItem.ModelInfo.ModelID = modelID; mtrlItem.ModelInfo.Body = bodyID; break; // Monster case "mb": modelID = int.Parse(mtrlFilePath.Substring(mtrlFilePath.IndexOf("_m") + 2, 4)); bodyID = int.Parse(mtrlFilePath.Substring(mtrlFilePath.IndexOf("b") + 1, 4)); mtrlItem.ModelInfo.ModelID = modelID; mtrlItem.ModelInfo.Body = bodyID; break; // DemiHuman case "de": modelID = int.Parse(mtrlFilePath.Substring(mtrlFilePath.IndexOf("d") + 1, 4)); bodyID = int.Parse(mtrlFilePath.Substring(mtrlFilePath.IndexOf("e") + 1, 4)); mtrlItem.ModelInfo.ModelID = modelID; mtrlItem.ModelInfo.Body = bodyID; break; default: break; } var dxVersion = int.Parse(Settings.Default.DX_Version); var mtrlFile = filePath.Remove(0, 1); XivMtrl mtrlData; try { mtrlData = mtrl.GetMtrlData(mtrlItem, race, mtrlFile, dxVersion); } catch (Exception) { if (mtrlItem.ModelInfo.ModelID == item.ModelInfo.ModelID) { throw; } // Fall back to material data from the primary model. mtrlData = mtrl.GetMtrlData(item, race, mtrlFile, dxVersion); } if (mtrlData.Shader.Contains("colorchange")) { hasColorChangeShader = true; } mtrlDictionary.Add(materialNum, mtrlData); materialNum++; } foreach (var xivMtrl in mtrlDictionary) { var modelTexture = new ModelTexture(gameDirectory, xivMtrl.Value); if (hasColorChangeShader) { var modelMaps = modelTexture.GetModelMaps(null, true); textureDataDictionary.Add(xivMtrl.Key, modelMaps); } else { if (item.ItemCategory.Equals(XivStrings.Face)) { var path = xivMtrl.Value.MTRLPath; if (path.Contains("_iri_")) { winColor = (WinColor)ColorConverter.ConvertFromString(Settings.Default.Iris_Color); } else if (path.Contains("_etc_")) { winColor = (WinColor)ColorConverter.ConvertFromString(Settings.Default.Etc_Color); } else { winColor = (WinColor)ColorConverter.ConvertFromString(Settings.Default.Skin_Color); } customColor = new Color(winColor.R, winColor.G, winColor.B, winColor.A); } var modelMaps = modelTexture.GetModelMaps(customColor); textureDataDictionary.Add(xivMtrl.Key, modelMaps); } } return(textureDataDictionary); }
/// <summary> /// Copies the entirety of a given root to a new root. /// </summary> /// <param name="Source">Original Root to copy from.</param> /// <param name="Destination">Destination root to copy to.</param> /// <param name="ApplicationSource">Application to list as the source for the resulting mod entries.</param> /// <returns>Returns a Dictionary of all the file conversion</returns> public static async Task <Dictionary <string, string> > CloneRoot(XivDependencyRoot Source, XivDependencyRoot Destination, string ApplicationSource, int singleVariant = -1, string saveDirectory = null, IProgress <string> ProgressReporter = null, IndexFile index = null, ModList modlist = null, ModPack modPack = null) { if (!IsSupported(Source) || !IsSupported(Destination)) { throw new InvalidDataException("Cannot clone unsupported root."); } if (ProgressReporter != null) { ProgressReporter.Report("Stopping Cache Worker..."); } var workerStatus = XivCache.CacheWorkerEnabled; XivCache.CacheWorkerEnabled = false; try { var df = IOUtil.GetDataFileFromPath(Source.ToString()); var _imc = new Imc(XivCache.GameInfo.GameDirectory); var _mdl = new Mdl(XivCache.GameInfo.GameDirectory, df); var _dat = new Dat(XivCache.GameInfo.GameDirectory); var _index = new Index(XivCache.GameInfo.GameDirectory); var _mtrl = new Mtrl(XivCache.GameInfo.GameDirectory); var _modding = new Modding(XivCache.GameInfo.GameDirectory); var doSave = false; if (index == null) { doSave = true; index = await _index.GetIndexFile(df); modlist = await _modding.GetModListAsync(); } bool locked = _index.IsIndexLocked(df); if (locked) { throw new Exception("Game files currently in use."); } if (ProgressReporter != null) { ProgressReporter.Report("Analyzing items and variants..."); } // First, try to get everything, to ensure it's all valid. ItemMetadata originalMetadata = await GetCachedMetadata(index, modlist, Source, df, _dat); var originalModelPaths = await Source.GetModelFiles(index, modlist); var originalMaterialPaths = await Source.GetMaterialFiles(-1, index, modlist); var originalTexturePaths = await Source.GetTextureFiles(-1, index, modlist); var originalVfxPaths = new HashSet <string>(); if (Imc.UsesImc(Source)) { var avfxSets = originalMetadata.ImcEntries.Select(x => x.Vfx).Distinct(); foreach (var avfx in avfxSets) { var avfxStuff = await ATex.GetVfxPath(Source.Info, avfx); if (String.IsNullOrEmpty(avfxStuff.Folder) || String.IsNullOrEmpty(avfxStuff.File)) { continue; } var path = avfxStuff.Folder + "/" + avfxStuff.File; if (index.FileExists(path)) { originalVfxPaths.Add(path); } } } // Time to start editing things. // First, get a new, clean copy of the metadata, pointed at the new root. var newMetadata = await GetCachedMetadata(index, modlist, Source, df, _dat); newMetadata.Root = Destination.Info.ToFullRoot(); ItemMetadata originalDestinationMetadata = null; try { originalDestinationMetadata = await GetCachedMetadata(index, modlist, Destination, df, _dat); } catch { originalDestinationMetadata = new ItemMetadata(Destination); } // Set 0 needs special handling. if (Source.Info.PrimaryType == XivItemType.equipment && Source.Info.PrimaryId == 0) { var set1Root = new XivDependencyRoot(Source.Info.PrimaryType, 1, null, null, Source.Info.Slot); var set1Metadata = await GetCachedMetadata(index, modlist, set1Root, df, _dat); newMetadata.EqpEntry = set1Metadata.EqpEntry; if (Source.Info.Slot == "met") { newMetadata.GmpEntry = set1Metadata.GmpEntry; } } else if (Destination.Info.PrimaryType == XivItemType.equipment && Destination.Info.PrimaryId == 0) { newMetadata.EqpEntry = null; newMetadata.GmpEntry = null; } // Now figure out the path names for all of our new paths. // These dictionarys map Old Path => New Path Dictionary <string, string> newModelPaths = new Dictionary <string, string>(); Dictionary <string, string> newMaterialPaths = new Dictionary <string, string>(); Dictionary <string, string> newMaterialFileNames = new Dictionary <string, string>(); Dictionary <string, string> newTexturePaths = new Dictionary <string, string>(); Dictionary <string, string> newAvfxPaths = new Dictionary <string, string>(); if (ProgressReporter != null) { ProgressReporter.Report("Calculating files to copy..."); } // For each path, replace any instances of our primary and secondary types. foreach (var path in originalModelPaths) { newModelPaths.Add(path, UpdatePath(Source, Destination, path)); } foreach (var path in originalMaterialPaths) { var nPath = UpdatePath(Source, Destination, path); newMaterialPaths.Add(path, nPath); var fName = Path.GetFileName(path); if (!newMaterialFileNames.ContainsKey(fName)) { newMaterialFileNames.Add(fName, Path.GetFileName(nPath)); } } foreach (var path in originalTexturePaths) { newTexturePaths.Add(path, UpdatePath(Source, Destination, path)); } foreach (var path in originalVfxPaths) { newAvfxPaths.Add(path, UpdatePath(Source, Destination, path)); } var destItem = Destination.GetFirstItem(); var srcItem = (await Source.GetAllItems(singleVariant))[0]; var iCat = destItem.SecondaryCategory; var iName = destItem.Name; var files = newModelPaths.Select(x => x.Value).Union( newMaterialPaths.Select(x => x.Value)).Union( newAvfxPaths.Select(x => x.Value)).Union( newTexturePaths.Select(x => x.Value)); var allFiles = new HashSet <string>(); foreach (var f in files) { allFiles.Add(f); } allFiles.Add(Destination.Info.GetRootFile()); if (ProgressReporter != null) { ProgressReporter.Report("Getting modlist..."); } if (ProgressReporter != null) { ProgressReporter.Report("Removing existing modifications to destination root..."); } if (Destination != Source) { var dPath = Destination.Info.GetRootFolder(); var allMods = modlist.Mods.ToList(); foreach (var mod in allMods) { if (mod.fullPath.StartsWith(dPath) && !mod.IsInternal()) { if (Destination.Info.SecondaryType != null || Destination.Info.Slot == null) { // If this is a slotless root, purge everything. await _modding.DeleteMod(mod.fullPath, false, index, modlist); } else if (allFiles.Contains(mod.fullPath) || mod.fullPath.Contains(Destination.Info.GetBaseFileName(true))) { // Otherwise, only purge the files we're replacing, and anything else that // contains our slot name. await _modding.DeleteMod(mod.fullPath, false, index, modlist); } } } } if (ProgressReporter != null) { ProgressReporter.Report("Copying models..."); } // Load, Edit, and resave the model files. foreach (var kv in newModelPaths) { var src = kv.Key; var dst = kv.Value; var offset = index.Get8xDataOffset(src); var xmdl = await _mdl.GetRawMdlData(src, false, offset); var tmdl = TTModel.FromRaw(xmdl); if (xmdl == null || tmdl == null) { continue; } tmdl.Source = dst; xmdl.MdlPath = dst; // Replace any material references as needed. foreach (var m in tmdl.MeshGroups) { foreach (var matKv in newMaterialFileNames) { m.Material = m.Material.Replace(matKv.Key, matKv.Value); } } // Save new Model. var bytes = await _mdl.MakeNewMdlFile(tmdl, xmdl, null); await _dat.WriteModFile(bytes, dst, ApplicationSource, destItem, index, modlist); } if (ProgressReporter != null) { ProgressReporter.Report("Copying textures..."); } // Raw Copy all Texture files to the new destinations to avoid having the MTRL save functions auto-generate blank textures. foreach (var kv in newTexturePaths) { var src = kv.Key; var dst = kv.Value; await _dat.CopyFile(src, dst, ApplicationSource, true, destItem, index, modlist); } if (ProgressReporter != null) { ProgressReporter.Report("Copying materials..."); } HashSet <string> CopiedMaterials = new HashSet <string>(); // Load every Material file and edit the texture references to the new texture paths. foreach (var kv in newMaterialPaths) { var src = kv.Key; var dst = kv.Value; try { var offset = index.Get8xDataOffset(src); if (offset == 0) { continue; } var xivMtrl = await _mtrl.GetMtrlData(offset, src, 11); xivMtrl.MTRLPath = dst; for (int i = 0; i < xivMtrl.TexturePathList.Count; i++) { foreach (var tkv in newTexturePaths) { xivMtrl.TexturePathList[i] = xivMtrl.TexturePathList[i].Replace(tkv.Key, tkv.Value); } } await _mtrl.ImportMtrl(xivMtrl, destItem, ApplicationSource, index, modlist); CopiedMaterials.Add(dst); } catch (Exception ex) { // Let functions later handle this mtrl then. } } if (ProgressReporter != null) { ProgressReporter.Report("Copying VFX..."); } // Copy VFX files. foreach (var kv in newAvfxPaths) { var src = kv.Key; var dst = kv.Value; await _dat.CopyFile(src, dst, ApplicationSource, true, destItem, index, modlist); } if (ProgressReporter != null) { ProgressReporter.Report("Creating missing variants..."); } // Check to see if we need to add any variants var cloneNum = newMetadata.ImcEntries.Count >= 2 ? 1 : 0; while (originalDestinationMetadata.ImcEntries.Count > newMetadata.ImcEntries.Count) { // Clone Variant 1 into the variants we are missing. newMetadata.ImcEntries.Add((XivImc)newMetadata.ImcEntries[cloneNum].Clone()); } if (singleVariant >= 0) { if (ProgressReporter != null) { ProgressReporter.Report("Setting single-variant data..."); } if (singleVariant < newMetadata.ImcEntries.Count) { var v = newMetadata.ImcEntries[singleVariant]; for (int i = 0; i < newMetadata.ImcEntries.Count; i++) { newMetadata.ImcEntries[i] = (XivImc)v.Clone(); } } } // Update Skeleton references to be for the correct set Id. var setId = Destination.Info.SecondaryId == null ? (ushort)Destination.Info.PrimaryId : (ushort)Destination.Info.SecondaryId; foreach (var entry in newMetadata.EstEntries) { entry.Value.SetId = setId; } if (ProgressReporter != null) { ProgressReporter.Report("Copying metdata..."); } // Poke through the variants and adjust any that point to null Material Sets to instead use a valid one. if (newMetadata.ImcEntries.Count > 0 && originalMetadata.ImcEntries.Count > 0) { var valid = newMetadata.ImcEntries.FirstOrDefault(x => x.MaterialSet != 0).MaterialSet; if (valid <= 0) { valid = originalMetadata.ImcEntries.FirstOrDefault(x => x.MaterialSet != 0).MaterialSet; } for (int i = 0; i < newMetadata.ImcEntries.Count; i++) { var entry = newMetadata.ImcEntries[i]; if (entry.MaterialSet == 0) { entry.MaterialSet = valid; } } } await ItemMetadata.SaveMetadata(newMetadata, ApplicationSource, index, modlist); // Save the new Metadata file via the batch function so that it's only written to the memory cache for now. await ItemMetadata.ApplyMetadataBatched(new List <ItemMetadata>() { newMetadata }, index, modlist, false); if (ProgressReporter != null) { ProgressReporter.Report("Filling in missing material sets..."); } // Validate all variants/material sets for valid materials, and copy materials as needed to fix. if (Imc.UsesImc(Destination)) { var mSets = newMetadata.ImcEntries.Select(x => x.MaterialSet).Distinct(); foreach (var mSetId in mSets) { var path = Destination.Info.GetRootFolder() + "material/v" + mSetId.ToString().PadLeft(4, '0') + "/"; foreach (var mkv in newMaterialFileNames) { // See if the material was copied over. var destPath = path + mkv.Value; if (CopiedMaterials.Contains(destPath)) { continue; } string existentCopy = null; // If not, find a material where one *was* copied over. foreach (var mSetId2 in mSets) { var p2 = Destination.Info.GetRootFolder() + "material/v" + mSetId2.ToString().PadLeft(4, '0') + "/"; foreach (var cmat2 in CopiedMaterials) { if (cmat2 == p2 + mkv.Value) { existentCopy = cmat2; break; } } } // Shouldn't ever actually hit this, but if we do, nothing to be done about it. if (existentCopy == null) { continue; } // Copy the material over. await _dat.CopyFile(existentCopy, destPath, ApplicationSource, true, destItem, index, modlist); } } } if (ProgressReporter != null) { ProgressReporter.Report("Updating modlist..."); } if (modPack == null) { modPack = new ModPack() { author = "System", name = "Item Copy - " + srcItem.Name + " to " + iName, url = "", version = "1.0" }; } List <Mod> mods = new List <Mod>(); foreach (var mod in modlist.Mods) { if (allFiles.Contains(mod.fullPath)) { // Ensure all of our modified files are attributed correctly. mod.name = iName; mod.category = iCat; mod.source = ApplicationSource; mod.modPack = modPack; mods.Add(mod); } } if (!modlist.ModPacks.Any(x => x.name == modPack.name)) { modlist.ModPacks.Add(modPack); } if (doSave) { // Save everything. await _index.SaveIndexFile(index); await _modding.SaveModListAsync(modlist); } XivCache.QueueDependencyUpdate(allFiles.ToList()); if (saveDirectory != null) { ProgressReporter.Report("Creating TTMP File..."); var desc = "Item Converter Modpack - " + srcItem.Name + " -> " + iName + "\nCreated at: " + DateTime.Now.ToString(); // Time to save the modlist to file. var dir = new DirectoryInfo(saveDirectory); var _ttmp = new TTMP(dir, ApplicationSource); var smpd = new SimpleModPackData() { Author = modPack.author, Description = desc, Url = modPack.url, Version = new Version(1, 0, 0), Name = modPack.name, SimpleModDataList = new List <SimpleModData>() }; foreach (var mod in mods) { var size = await _dat.GetCompressedFileSize(mod.data.modOffset, df); var smd = new SimpleModData() { Name = iName, FullPath = mod.fullPath, DatFile = df.GetDataFileName(), Category = iCat, IsDefault = false, ModSize = size, ModOffset = mod.data.modOffset }; smpd.SimpleModDataList.Add(smd); } await _ttmp.CreateSimpleModPack(smpd, XivCache.GameInfo.GameDirectory, null, true); } if (ProgressReporter != null) { ProgressReporter.Report("Root copy complete."); } // Return the final file conversion listing. var ret = newModelPaths.Union(newMaterialPaths).Union(newAvfxPaths).Union(newTexturePaths); var dict = ret.ToDictionary(x => x.Key, x => x.Value); dict.Add(Source.Info.GetRootFile(), Destination.Info.GetRootFile()); return(dict); } finally { XivCache.CacheWorkerEnabled = workerStatus; } }