public SimpleModPackImporter(DirectoryInfo modPackDirectory, ModPackJson modPackJson, TextureViewModel textureViewModel, ModelViewModel modelViewModel, bool silent = false, bool messageInImport = false) { this.DataContext = this; InitializeComponent(); JsonEntries = new List <ModsJson>(); SelectedEntries = new HashSet <int>(); _modPackDirectory = modPackDirectory; _gameDirectory = new DirectoryInfo(Properties.Settings.Default.FFXIV_Directory); _modding = new Modding(_gameDirectory); _texToolsModPack = new TTMP(new DirectoryInfo(Properties.Settings.Default.ModPack_Directory), XivStrings.TexTools); _messageInImport = messageInImport; _textureViewModel = textureViewModel; _modelViewModel = modelViewModel; var index = new Index(_gameDirectory); _indexLockStatus = index.IsIndexLocked(XivDataFile._0A_Exd); _packJson = modPackJson; _silent = silent; ModListView.IsEnabled = false; LockedStatusLabel.Foreground = Brushes.Black; LockedStatusLabel.HorizontalContentAlignment = System.Windows.HorizontalAlignment.Left; LockedStatusLabel.Content = UIStrings.Loading; Task.Run(Initialize); }
public Task BackupIndexFiles(DirectoryInfo backupsDirectory) { return(Task.Run(async() => { var indexFiles = new XivDataFile[] { XivDataFile._0A_Exd, XivDataFile._04_Chara, XivDataFile._06_Ui, XivDataFile._01_Bgcommon }; var index = new Index(_gameDirectory); var modding = new Modding(_gameDirectory); if (index.IsIndexLocked(XivDataFile._0A_Exd)) { throw new Exception("Index files are in use by another process."); } try { // Toggle off all mods await modding.ToggleAllMods(false); } catch { throw new Exception("Failed to disable mods.\n\n" + "Please check For problems by selecting Help -> Check For Problems"); } var originalFiles = Directory.GetFiles(_gameDirectory.FullName); foreach (var originalFile in originalFiles) { try { if (originalFile.Contains(".win32.index")) { File.Copy(originalFile, $"{backupsDirectory}/{Path.GetFileName(originalFile)}", true); } } catch (Exception ex) { throw new Exception("Failed to copy index files.\n\n" + ex.Message); } } })); }
private async void RunChecks() { var index = new Index(_gameDirectory); IProgress <(int current, int total)> progress = new Progress <(int current, int total)>((update) => { ProgressBar.Value = (((float)update.current / (float)update.total) * 100); ProgressLabel.Content = $"{update.current} / {update.total}"; }); AddText($"{UIStrings.ProblemCheck_Initialize}\n\n", textColor); AddText($"{UIStrings.ProblemCheck_IndexDat}\n", secondaryTextColor); if (await CheckIndexDatCounts()) { AddText($"\n{UIStrings.ProblemCheck_ErrorsFound}\n", secondaryTextColor); if (!index.IsIndexLocked(XivDataFile._0A_Exd)) { await FixIndexDatCounts(); AddText($"{UIStrings.ProblemCheck_RepairComplete}\n", "Green"); await CheckIndexDatCounts(); } else { AddText($"\n{UIStrings.ProblemCheck_IndexLocked} \n", "Red"); } } AddText($"\n{UIStrings.ProblemCheck_IndexBackups}\n", secondaryTextColor); await CheckBackups(); AddText($"\n{UIStrings.ProblemCheck_DatSize}\n", secondaryTextColor); await CheckDatSizes(); try { AddText($"\n{UIStrings.ProblemCheck_ModList}\n", secondaryTextColor); await CheckMods(progress); } catch (Exception ex) { Debug.WriteLine($"Loading Canceled\n\n{ex.Message}"); } try { AddText($"\n{UIStrings.ProblemCheck_LoD}\n", secondaryTextColor); cfpTextBox.ScrollToEnd(); await CheckDXSettings(); } catch (Exception ex) { Debug.WriteLine($"Loading Canceled\n\n{ex.Message}"); } ProgressBar.Value = 0; ProgressLabel.Content = UIStrings.Done; }
/// <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 (XivCache.GameInfo.UseLumina) { // Using the root cloner with Lumina import mode enabled is unstable/not safe. // ( The state of the game files in the TT system may not match the state of the // Lumina mod setup, and the modlist/etc. can get bashed as this function directly // modifies the modlist during the cloning process. ) throw new Exception("Item Conversion cannot be used with Lumina import mode."); } 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; } }