/// <summary> /// Imports a ColorSet file /// </summary> /// <param name="xivMtrl">The XivMtrl data of the original</param> /// <param name="ddsFileDirectory">The dds directory of the new ColorSet</param> /// <param name="item">The item</param> /// <param name="source">The source importing the file</param> /// <returns>The new offset</returns> public int TexColorImporter(XivMtrl xivMtrl, DirectoryInfo ddsFileDirectory, IItem item, string source) { var colorSetData = new List <Half>(); using (var br = new BinaryReader(File.OpenRead(ddsFileDirectory.FullName))) { // skip DDS header br.BaseStream.Seek(128, SeekOrigin.Begin); // color data is always 512 (4w x 16h = 64 x 8bpp = 512) // this reads 256 ushort values which is 256 x 2 = 512 for (var i = 0; i < 256; i++) { colorSetData.Add(new Half(br.ReadUInt16())); } } // If the colorset size is 544, it contains extra data that must be imported if (xivMtrl.ColorSetDataSize == 544) { var flagsPath = Path.Combine(Path.GetDirectoryName(ddsFileDirectory.FullName), (Path.GetFileNameWithoutExtension(ddsFileDirectory.FullName) + ".dat")); if (File.Exists(flagsPath)) { using (var br = new BinaryReader(File.OpenRead(flagsPath))) { // The extra data after the colorset is always 32 bytes // This reads 16 ushort values which is 16 x 2 = 32 for (var i = 0; i < 16; i++) { colorSetData.Add(new Half(br.ReadUInt16())); } } } } // Replace the color set data with the imported data xivMtrl.ColorSetData = colorSetData; var mtrl = new Mtrl(_gameDirectory, xivMtrl.TextureTypePathList[0].DataFile); return(mtrl.ImportMtrl(xivMtrl, item, source)); }
/// <summary> /// Imports a ColorSet file /// </summary> /// <param name="xivMtrl">The XivMtrl data of the original</param> /// <param name="ddsFileDirectory">The dds directory of the new ColorSet</param> /// <param name="item">The item</param> /// <param name="source">The source importing the file</param> /// <returns>The new offset</returns> public async Task <long> TexColorImporter(XivMtrl xivMtrl, DirectoryInfo ddsFileDirectory, IItem item, string source, XivLanguage lang) { var colorSetData = new List <Half>(); byte[] colorSetExtraData = null; colorSetData = GetColorsetDataFromDDS(ddsFileDirectory); colorSetExtraData = GetColorsetExtraDataFromDDS(ddsFileDirectory); // Replace the color set data with the imported data xivMtrl.ColorSetData = colorSetData; xivMtrl.ColorSetDyeData = colorSetExtraData; if (xivMtrl.Unknown2.Length > 0) { // This byte enables the dye set if it's not already enabled. xivMtrl.Unknown2[0] = 12; } var _mtrl = new Mtrl(XivCache.GameInfo.GameDirectory); return(await _mtrl.ImportMtrl(xivMtrl, item, source)); }
/// <summary> /// Saves the material to file. /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private async void SaveButton_Click(object sender, RoutedEventArgs e) { var mw = MainWindow.GetMainWindow(); await mw.LockUi(); try { var mtrlLib = new Mtrl(XivCache.GameInfo.GameDirectory); var item = mw.GetSelectedItem(); await mtrlLib.ImportMtrl(_mtrl, item, XivStrings.TexTools); MaterialSaved.Invoke(this, null); } catch (Exception ex) { FlexibleMessageBox.Show("Unable to save Material.\n\nError: " + ex.Message, "Material Save Error", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error); return; } finally { await mw.UnlockUi(); } }
/// <summary> /// Updates the XivMtrl with the selected changes. /// </summary> public async Task <XivMtrl> SaveChanges() { _view.SaveButton.IsEnabled = false; _view.CancelButton.IsEnabled = false; _view.DisableButton.IsEnabled = false; _view.SaveButton.Content = UIStrings.Working_Ellipsis; _view.NormalTextBox.Text = SanitizePath(_view.NormalTextBox.Text); _view.DiffuseTextBox.Text = SanitizePath(_view.DiffuseTextBox.Text); _view.SpecularTextBox.Text = SanitizePath(_view.SpecularTextBox.Text); // New Data var newShader = new ShaderInfo() { Shader = (MtrlShader)_view.ShaderComboBox.SelectedValue, Preset = (MtrlShaderPreset)_view.PresetComboBox.SelectedValue, TransparencyEnabled = (bool)_view.TransparencyComboBox.SelectedValue, RenderBackfaces = (bool)_view.BackfacesComboBox.SelectedValue }; MapInfo newNormal = null; MapInfo newDiffuse = null; MapInfo newSpecular = null; MapInfo newMulti = null; MapInfo newReflection = null; // Don't know what defines the normal's format between the two types. // But setting it to the UseColorset value all the time causes extremely rare crashes // in the Try On menu sometimes. Possibly GFX card/Driver related? var oldNormal = _material.GetMapInfo(XivTexType.Normal); // Normal newNormal = new MapInfo() { Usage = XivTexType.Normal, Format = oldNormal.Format, path = _view.NormalTextBox.Text }; // Specular / Multi if (newShader.HasMulti) { newMulti = new MapInfo() { Usage = XivTexType.Multi, Format = MtrlTextureDescriptorFormat.NoColorset, path = _view.SpecularTextBox.Text }; } else { newSpecular = new MapInfo() { Usage = XivTexType.Specular, Format = MtrlTextureDescriptorFormat.NoColorset, path = _view.SpecularTextBox.Text }; } // Diffuse / Reflection if (newShader.HasDiffuse) { newDiffuse = new MapInfo() { Usage = XivTexType.Diffuse, Format = MtrlTextureDescriptorFormat.NoColorset, path = _view.DiffuseTextBox.Text }; } else if (newShader.HasReflection) { newReflection = new MapInfo() { Usage = XivTexType.Reflection, Format = MtrlTextureDescriptorFormat.NoColorset, path = _view.DiffuseTextBox.Text }; } if (_mode == MaterialEditorMode.NewSingle) { // This needs to be updated BEFORE setting texture paths. _material.MTRLPath = Regex.Replace(_material.MTRLPath, "_([a-z0-9])\\.mtrl", "_" + _newMaterialIdentifier + ".mtrl"); } _material.SetShaderInfo(newShader); // This should be set BEFORE changing the maps over. _material.SetMapInfo(XivTexType.Normal, newNormal); _material.SetMapInfo(XivTexType.Specular, newSpecular); _material.SetMapInfo(XivTexType.Multi, newMulti); _material.SetMapInfo(XivTexType.Diffuse, newDiffuse); _material.SetMapInfo(XivTexType.Reflection, newReflection); try { if (_mode == MaterialEditorMode.NewSingle || _mode == MaterialEditorMode.EditSingle) { // Save the existing MTRL. await _mtrl.ImportMtrl(_material, _item, XivStrings.TexTools); } else if (_mode == MaterialEditorMode.NewMulti || _mode == MaterialEditorMode.EditMulti) { // Ship it to the more complex save function. await SaveMulti(); // Change this after calling SaveMulti. Updated so that the external classes looking for the material after // can find the right type identifier and such. _material.MTRLPath = Regex.Replace(_material.MTRLPath, "_([a-z0-9])\\.mtrl", "_" + _newMaterialIdentifier + ".mtrl"); } } catch (Exception Ex) { FlexibleMessageBox.Show("An error occurred when saving the Material."); } return(_material); }
/// <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; } }
/// <summary> /// Imports a ColorSet file /// </summary> /// <param name="xivMtrl">The XivMtrl data of the original</param> /// <param name="ddsFileDirectory">The dds directory of the new ColorSet</param> /// <param name="item">The item</param> /// <param name="source">The source importing the file</param> /// <returns>The new offset</returns> public async Task <int> TexColorImporter(XivMtrl xivMtrl, DirectoryInfo ddsFileDirectory, IItem item, string source, XivLanguage lang) { var colorSetData = new List <Half>(); byte[] colorSetExtraData = null; using (var br = new BinaryReader(File.OpenRead(ddsFileDirectory.FullName))) { // Check DDS type br.BaseStream.Seek(84, SeekOrigin.Begin); var texType = br.ReadInt32(); XivTexFormat textureType; if (DDSType.ContainsKey(texType)) { textureType = DDSType[texType]; } else { throw new Exception($"DDS Type ({texType}) not recognized. Expecting A16B16G16R16F."); } if (textureType != XivTexFormat.A16B16G16R16F) { throw new Exception($"Incorrect file type. Expected: A16B16G16R16F Given: {textureType}"); } // Skip past rest of the DDS header br.BaseStream.Seek(128, SeekOrigin.Begin); // color data is always 512 (4w x 16h = 64 x 8bpp = 512) // this reads 256 ushort values which is 256 x 2 = 512 for (var i = 0; i < 256; i++) { colorSetData.Add(new Half(br.ReadUInt16())); } } // If the colorset size is 544, it contains extra data that must be imported if (xivMtrl.ColorSetDataSize == 544) { var flagsPath = Path.Combine(Path.GetDirectoryName(ddsFileDirectory.FullName), (Path.GetFileNameWithoutExtension(ddsFileDirectory.FullName) + ".dat")); if (File.Exists(flagsPath)) { // The extra data after the colorset is always 32 bytes // This reads 16 ushort values which is 16 x 2 = 32 colorSetExtraData = File.ReadAllBytes(flagsPath); // If for whatever reason there is a .dat file but it's missing data if (colorSetExtraData.Length != 32) { // Set all dye modifiers to 0 (undyeable) colorSetExtraData = new byte[32]; } } else { // If .dat file is missing set all values to 0 (undyeable) colorSetExtraData = new byte[32]; } } // Replace the color set data with the imported data xivMtrl.ColorSetData = colorSetData; xivMtrl.ColorSetExtraData = colorSetExtraData; var mtrl = new Mtrl(_gameDirectory, xivMtrl.TextureTypePathList[0].DataFile, lang); return(await mtrl.ImportMtrl(xivMtrl, item, source)); }