/// <summary> /// The event handler for the advanced options button clicked /// </summary> private async void AdvOptionsButton_Click(object sender, RoutedEventArgs e) { var selectedItem = ModelTypeComboBox.SelectedItem as ModComboBox; var mod = selectedItem.SelectedMod; var includedMod = new IncludedMods { Name = $"{Path.GetFileNameWithoutExtension(mod.fullPath)} ({((Category)ModListTreeView.SelectedItem).Name})", FullPath = mod.fullPath }; var itemModel = MakeItemModel(mod); var includedModsList = IncludedModsList.Items.Cast <IncludedMods>().ToList(); var mdl = new Mdl(_gameDirectory, XivDataFiles.GetXivDataFile(mod.datFile)); try { // TODO - Include Submesh ID ? // Do we even have any kind of UI To specify this in the wizard? // Submeshes are only used for Furniture anyways, so it might be a 'will not fix' bool success = await ImportModelView.ImportModel(itemModel, IOUtil.GetRaceFromPath(mod.fullPath), null, this, null, true); if (!success) { return; } } catch (Exception ex) { FlexibleMessageBox.Show(ex.Message, UIMessages.AdvancedImportErrorTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); return; } var mdlData = ImportModelView.GetData(); 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 = mdlData; } } else { IncludedModsList.Items.Add(includedMod); _selectedModOption.Mods.Add(mod.fullPath, new ModData { Name = mod.name, Category = mod.category, FullPath = mod.fullPath, ModDataBytes = mdlData, }); } }
/// <summary> /// Toggles all mods on or off /// </summary> /// <param name="enable">The status to switch the mods to True if enable False if disable</param> public void ToggleAllMods(bool enable) { var index = new Index(_gameDirectory); var modList = JsonConvert.DeserializeObject <ModList>(File.ReadAllText(ModListDirectory.FullName)); if (modList == null || modList.modCount == 0) { return; } foreach (var modEntry in modList.Mods) { if (enable && !modEntry.enabled) { index.UpdateIndex(modEntry.data.modOffset, modEntry.fullPath, XivDataFiles.GetXivDataFile(modEntry.datFile)); index.UpdateIndex2(modEntry.data.modOffset, modEntry.fullPath, XivDataFiles.GetXivDataFile(modEntry.datFile)); modEntry.enabled = true; } else if (!enable && modEntry.enabled) { index.UpdateIndex(modEntry.data.originalOffset, modEntry.fullPath, XivDataFiles.GetXivDataFile(modEntry.datFile)); index.UpdateIndex2(modEntry.data.originalOffset, modEntry.fullPath, XivDataFiles.GetXivDataFile(modEntry.datFile)); modEntry.enabled = false; } } File.WriteAllText(ModListDirectory.FullName, JsonConvert.SerializeObject(modList, Formatting.Indented)); }
/// <summary> /// The event handler for the add current material button clicked /// </summary> private void AddCurrentMaterialButton_Click(object sender, RoutedEventArgs e) { var dat = new Dat(_gameDirectory); var selectedItem = MaterialComboBox.SelectedItem as ModComboBox; var mod = selectedItem.SelectedMod; var includedMod = new IncludedMods { Name = $"{Path.GetFileNameWithoutExtension(mod.fullPath)} ({((Category)ModListTreeView.SelectedItem).Name})", FullPath = mod.fullPath }; var includedModsList = IncludedModsList.Items.Cast <IncludedMods>().ToList(); 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) { var rawData = dat.GetRawData(mod.data.modOffset, XivDataFiles.GetXivDataFile(mod.datFile), mod.data.modSize); if (rawData == null) { FlexibleMessageBox.Show( string.Format(UIMessages.RawDataErrorMessage, mod.data.modOffset, mod.datFile), UIMessages.ModDataReadErrorTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); return; } _selectedModOption.Mods[mod.fullPath].ModDataBytes = rawData; } } else { var rawData = dat.GetRawData(mod.data.modOffset, XivDataFiles.GetXivDataFile(mod.datFile), mod.data.modSize); if (rawData == null) { FlexibleMessageBox.Show( string.Format(UIMessages.RawDataErrorMessage, mod.data.modOffset, mod.datFile), UIMessages.ModDataReadErrorTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); return; } var modData = new ModData { Name = mod.name, Category = mod.category, FullPath = mod.fullPath, ModDataBytes = rawData, }; IncludedModsList.Items.Add(includedMod); _selectedModOption.Mods.Add(mod.fullPath, modData); } }
/// <summary> /// Toggles the mod on or off /// </summary> /// <param name="internalFilePath">The internal file path of the mod</param> /// <param name="enable">The status of the mod</param> public void ToggleModStatus(string internalFilePath, bool enable) { var index = new Index(_gameDirectory); var modEntry = TryGetModEntry(internalFilePath); if (modEntry == null) { throw new Exception("Unable to find mod entry in modlist."); } if (enable) { index.UpdateIndex(modEntry.data.modOffset, internalFilePath, XivDataFiles.GetXivDataFile(modEntry.datFile)); index.UpdateIndex2(modEntry.data.modOffset, internalFilePath, XivDataFiles.GetXivDataFile(modEntry.datFile)); } else { index.UpdateIndex(modEntry.data.originalOffset, internalFilePath, XivDataFiles.GetXivDataFile(modEntry.datFile)); index.UpdateIndex2(modEntry.data.originalOffset, internalFilePath, XivDataFiles.GetXivDataFile(modEntry.datFile)); } var modListDirectory = new DirectoryInfo(Path.Combine(_gameDirectory.Parent.Parent.FullName, XivStrings.ModlistFilePath)); var modList = JsonConvert.DeserializeObject <ModList>(File.ReadAllText(modListDirectory.FullName)); var entryEnableUpdate = (from entry in modList.Mods where entry.fullPath.Equals(modEntry.fullPath) select entry).FirstOrDefault(); entryEnableUpdate.enabled = enable; File.WriteAllText(modListDirectory.FullName, JsonConvert.SerializeObject(modList, Formatting.Indented)); }
/// <summary> /// Toggles the mod on or off /// </summary> /// <param name="internalFilePath">The internal file path of the mod</param> /// <param name="enable">The status of the mod</param> public async Task ToggleModStatus(string internalFilePath, bool enable) { if (string.IsNullOrEmpty(internalFilePath)) { throw new Exception("File Path missing, unable to toggle mod."); } var modEntry = await TryGetModEntry(internalFilePath); if (modEntry == null) { throw new Exception("Unable to find mod entry in modlist."); } if (enable) { await Index.UpdateIndex(modEntry.data.modOffset, internalFilePath, XivDataFiles.GetXivDataFile(modEntry.datFile)); await Index.UpdateIndex2(modEntry.data.modOffset, internalFilePath, XivDataFiles.GetXivDataFile(modEntry.datFile)); } else { await Index.UpdateIndex(modEntry.data.originalOffset, internalFilePath, XivDataFiles.GetXivDataFile(modEntry.datFile)); await Index.UpdateIndex2(modEntry.data.originalOffset, internalFilePath, XivDataFiles.GetXivDataFile(modEntry.datFile)); } var modList = GetModList(); var entryEnableUpdate = modList.Mods.Find(m => m.fullPath == modEntry.fullPath); entryEnableUpdate.enabled = enable; WriteModList(modList); }
/// <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 }); } }
/// <summary> /// Checks to see whether the mod is currently enabled /// </summary> /// <param name="internalPath">The internal path of the file</param> /// <param name="dataFile">The data file to check in</param> /// <param name="indexCheck">Flag to determine whether to check the index file or just the modlist</param> /// <returns></returns> public async Task <XivModStatus> IsModEnabled(string internalPath, bool indexCheck) { if (!File.Exists(ModListDirectory.FullName)) { return(XivModStatus.Original); } if (indexCheck) { var index = new Index(_gameDirectory); var modEntry = await TryGetModEntry(internalPath); if (modEntry == null) { return(XivModStatus.Original); } var originalOffset = modEntry.data.originalOffset; var moddedOffset = modEntry.data.modOffset; var offset = await index.GetDataOffset( HashGenerator.GetHash(Path.GetDirectoryName(internalPath).Replace("\\", "/")), HashGenerator.GetHash(Path.GetFileName(internalPath)), XivDataFiles.GetXivDataFile(modEntry.datFile)); if (offset.Equals(originalOffset)) { return(XivModStatus.Disabled); } if (offset.Equals(moddedOffset)) { return(XivModStatus.Enabled); } throw new Exception("Offset in Index does not match either original or modded offset in modlist."); } else { var modEntry = await TryGetModEntry(internalPath); if (modEntry == null) { return(XivModStatus.Original); } // If modEntry not null but modded offset and original offset are the same as is the case with matadd textures if (modEntry.data.modOffset == modEntry.data.originalOffset) { // Return original to disable the disable/enable button as there's nothing to toggle between return(XivModStatus.MatAdd); } return(modEntry.enabled ? XivModStatus.Enabled : XivModStatus.Disabled); } }
/// <summary> /// The event handler for the custom model button clicked /// </summary> private async void AddCustomModelButton_Click(object sender, RoutedEventArgs e) { var selectedItem = ModelTypeComboBox.SelectedItem as ModComboBox; var mod = selectedItem.SelectedMod; var includedMod = new IncludedMods { Name = $"{Path.GetFileNameWithoutExtension(mod.fullPath)} ({((Category)ModListTreeView.SelectedItem).Name})", FullPath = mod.fullPath }; var itemModel = MakeItemModel(mod); var includedModsList = IncludedModsList.Items.Cast <IncludedMods>().ToList(); var mdl = new Mdl(_gameDirectory, XivDataFiles.GetXivDataFile(mod.datFile)); var xivMdl = await mdl.GetMdlData(itemModel, GetRace(mod.fullPath), null, null, mod.data.originalOffset); var warnings = await mdl.ImportModel(itemModel, xivMdl, new DirectoryInfo(CustomModelTextBox.Text), null, XivStrings.TexTools, Settings.Default.DAE_Plugin_Target, true); if (warnings.Count > 0) { foreach (var warning in warnings) { FlexibleMessageBox.Show( $"{warning.Value}", $"{warning.Key}", MessageBoxButtons.OK, MessageBoxIcon.Warning); } } var mdlData = mdl.MDLRawData; 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 = mdlData; } } else { IncludedModsList.Items.Add(includedMod); _selectedModOption.Mods.Add(mod.fullPath, new ModData { Name = mod.name, Category = mod.category, FullPath = mod.fullPath, ModDataBytes = mdlData }); } }
/// <summary> /// Imports a mod pack /// </summary> /// <param name="modPackDirectory">The directory of the mod pack</param> /// <param name="modsJson">The list of mods to be imported</param> /// <param name="gameDirectory">The game directory</param> /// <param name="modListDirectory">The mod list directory</param> public void ImportModPack(DirectoryInfo modPackDirectory, List <ModsJson> modsJson, DirectoryInfo gameDirectory, DirectoryInfo modListDirectory) { var dat = new Dat(gameDirectory); var modListFullPaths = new List <string>(); var modList = JsonConvert.DeserializeObject <ModList>(File.ReadAllText(modListDirectory.FullName)); foreach (var modListMod in modList.Mods) { modListFullPaths.Add(modListMod.fullPath); } using (var archive = ZipFile.OpenRead(modPackDirectory.FullName)) { foreach (var zipEntry in archive.Entries) { if (zipEntry.FullName.EndsWith(".mpd")) { using (var binaryReader = new BinaryReader(zipEntry.Open())) { foreach (var modJson in modsJson) { if (modListFullPaths.Contains(modJson.FullPath)) { var existingEntry = (from entry in modList.Mods where entry.fullPath.Equals(modJson.FullPath) select entry).FirstOrDefault(); binaryReader.BaseStream.Seek(modJson.ModOffset, SeekOrigin.Begin); var data = binaryReader.ReadBytes(modJson.ModSize); dat.WriteToDat(new List <byte>(data), existingEntry, modJson.FullPath, modJson.Category, modJson.Name, XivDataFiles.GetXivDataFile(modJson.DatFile), _source, GetDataType(modJson.FullPath)); } else { binaryReader.BaseStream.Seek(modJson.ModOffset, SeekOrigin.Begin); var data = binaryReader.ReadBytes(modJson.ModSize); dat.WriteToDat(new List <byte>(data), null, modJson.FullPath, modJson.Category, modJson.Name, XivDataFiles.GetXivDataFile(modJson.DatFile), _source, GetDataType(modJson.FullPath)); } } } break; } } } }
/// <summary> /// Toggles the mod on or off /// </summary> /// <param name="internalFilePath">The internal file path of the mod</param> /// <param name="enable">The status of the mod</param> public void ToggleModPackStatus(string modPackName, bool enable) { var index = new Index(_gameDirectory); var modList = JsonConvert.DeserializeObject <ModList>(File.ReadAllText(ModListDirectory.FullName)); var modListDirectory = new DirectoryInfo(Path.Combine(_gameDirectory.Parent.Parent.FullName, XivStrings.ModlistFilePath)); List <Mod> mods = null; if (modPackName.Equals("Standalone (Non-ModPack)")) { mods = (from mod in modList.Mods where mod.modPack == null select mod).ToList(); } else { mods = (from mod in modList.Mods where mod.modPack != null && mod.modPack.name.Equals(modPackName) select mod).ToList(); } if (mods == null) { throw new Exception("Unable to find mods with given Mod Pack Name in modlist."); } foreach (var modEntry in mods) { if (modEntry.name.Equals(string.Empty)) { continue; } if (enable) { index.UpdateIndex(modEntry.data.modOffset, modEntry.fullPath, XivDataFiles.GetXivDataFile(modEntry.datFile)); index.UpdateIndex2(modEntry.data.modOffset, modEntry.fullPath, XivDataFiles.GetXivDataFile(modEntry.datFile)); modEntry.enabled = true; } else { index.UpdateIndex(modEntry.data.originalOffset, modEntry.fullPath, XivDataFiles.GetXivDataFile(modEntry.datFile)); index.UpdateIndex2(modEntry.data.originalOffset, modEntry.fullPath, XivDataFiles.GetXivDataFile(modEntry.datFile)); modEntry.enabled = false; } } File.WriteAllText(modListDirectory.FullName, JsonConvert.SerializeObject(modList, Formatting.Indented)); }
/// <summary> /// Checks to see whether the mod is currently enabled /// </summary> /// <param name="internalPath">The internal path of the file</param> /// <param name="dataFile">The data file to check in</param> /// <param name="indexCheck">Flag to determine whether to check the index file or just the modlist</param> /// <returns></returns> public async Task <XivModStatus> IsModEnabled(string internalPath, bool indexCheck) { if (!File.Exists(ModListDirectory.FullName)) { return(XivModStatus.Original); } if (indexCheck) { var index = new Index(_gameDirectory); var modEntry = await TryGetModEntry(internalPath); if (modEntry == null) { return(XivModStatus.Original); } var originalOffset = modEntry.data.originalOffset; var moddedOffset = modEntry.data.modOffset; var offset = await index.GetDataOffset( HashGenerator.GetHash(Path.GetDirectoryName(internalPath).Replace("\\", "/")), HashGenerator.GetHash(Path.GetFileName(internalPath)), XivDataFiles.GetXivDataFile(modEntry.datFile)); if (offset.Equals(originalOffset)) { return(XivModStatus.Disabled); } if (offset.Equals(moddedOffset)) { return(XivModStatus.Enabled); } throw new Exception("Offset in Index does not match either original or modded offset in modlist."); } else { var modEntry = await TryGetModEntry(internalPath); if (modEntry == null) { return(XivModStatus.Original); } return(modEntry.enabled ? XivModStatus.Enabled : XivModStatus.Disabled); } }
/// <summary> /// Toggles the mod on or off /// </summary> /// <param name="internalFilePath">The internal file path of the mod</param> /// <param name="enable">The status of the mod</param> public async Task ToggleModStatus(string internalFilePath, bool enable) { var index = new Index(_gameDirectory); if (string.IsNullOrEmpty(internalFilePath)) { throw new Exception("File Path missing, unable to toggle mod."); } var modEntry = await TryGetModEntry(internalFilePath); if (modEntry == null) { throw new Exception("Unable to find mod entry in modlist."); } // Matadd textures have the same mod offset as original so nothing to toggle if (modEntry.data.originalOffset == modEntry.data.modOffset) { return; } if (enable) { await index.UpdateIndex(modEntry.data.modOffset, internalFilePath, XivDataFiles.GetXivDataFile(modEntry.datFile)); await index.UpdateIndex2(modEntry.data.modOffset, internalFilePath, XivDataFiles.GetXivDataFile(modEntry.datFile)); } else { await index.UpdateIndex(modEntry.data.originalOffset, internalFilePath, XivDataFiles.GetXivDataFile(modEntry.datFile)); await index.UpdateIndex2(modEntry.data.originalOffset, internalFilePath, XivDataFiles.GetXivDataFile(modEntry.datFile)); } var modListDirectory = new DirectoryInfo(Path.Combine(_gameDirectory.Parent.Parent.FullName, XivStrings.ModlistFilePath)); var modList = JsonConvert.DeserializeObject <ModList>(File.ReadAllText(modListDirectory.FullName)); var entryEnableUpdate = (from entry in modList.Mods where entry.fullPath.Equals(modEntry.fullPath) select entry).FirstOrDefault(); entryEnableUpdate.enabled = enable; File.WriteAllText(modListDirectory.FullName, JsonConvert.SerializeObject(modList, Formatting.Indented)); }
/// <summary> /// The event handler for the advanced options button clicked /// </summary> private void AdvOptionsButton_Click(object sender, RoutedEventArgs e) { var selectedItem = ModelTypeComboBox.SelectedItem as ModComboBox; var mod = selectedItem.SelectedMod; var includedMod = new IncludedMods { Name = $"{Path.GetFileNameWithoutExtension(mod.fullPath)} ({((Category)ModListTreeView.SelectedItem).Name})", FullPath = mod.fullPath }; var itemModel = MakeItemModel(mod); var includedModsList = IncludedModsList.Items.Cast <IncludedMods>().ToList(); var mdl = new Mdl(_gameDirectory, XivDataFiles.GetXivDataFile(mod.datFile)); var xivMdl = mdl.GetMdlData(itemModel, GetRace(mod.fullPath), null, null, mod.data.originalOffset); var advancedImportView = new AdvancedModelImportView(xivMdl, itemModel, GetRace(mod.fullPath), true); var result = advancedImportView.ShowDialog(); if (result == true) { if (includedModsList.Any(item => item.Name.Equals(includedMod.Name))) { if (FlexibleMessageBox.Show( $"This Option already includes {includedMod.Name} \n\n Would you like to overwrite the existing mod for this option?", "Overwrite?", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == System.Windows.Forms.DialogResult.Yes) { _selectedModOption.Mods[mod.fullPath].ModDataBytes = advancedImportView.RawModelData; } } else { IncludedModsList.Items.Add(includedMod); _selectedModOption.Mods.Add(mod.fullPath, new ModData { Name = mod.name, Category = mod.category, FullPath = mod.fullPath, ModDataBytes = advancedImportView.RawModelData }); } } }
/// <summary> /// The event handler for the custom model button clicked /// </summary> private void AddCustomModelButton_Click(object sender, RoutedEventArgs e) { var selectedItem = ModelTypeComboBox.SelectedItem as ModComboBox; var mod = selectedItem.SelectedMod; var includedMod = new IncludedMods { Name = $"{Path.GetFileNameWithoutExtension(mod.fullPath)} ({((Category)ModListTreeView.SelectedItem).Name})", FullPath = mod.fullPath }; var itemModel = MakeItemModel(mod); var includedModsList = IncludedModsList.Items.Cast <IncludedMods>().ToList(); var mdl = new Mdl(_gameDirectory, XivDataFiles.GetXivDataFile(mod.datFile)); var xivMdl = mdl.GetMdlData(itemModel, GetRace(mod.fullPath)); var importResults = mdl.ImportModel(itemModel, xivMdl, new DirectoryInfo(CustomModelTextBox.Text), null, XivStrings.TexTools, true); //TODO: Add dialogs for import results (warning messages) var mdlData = mdl.MDLRawData; if (includedModsList.Any(item => item.Name.Equals(includedMod.Name))) { if (FlexibleMessageBox.Show( $"This Option already includes {includedMod.Name} \n\n Would you like to overwrite the existing mod for this option?", "Overwrite?", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == System.Windows.Forms.DialogResult.Yes) { _selectedModOption.Mods[mod.fullPath].ModDataBytes = mdlData; } } else { IncludedModsList.Items.Add(includedMod); _selectedModOption.Mods.Add(mod.fullPath, new ModData { Name = mod.name, Category = mod.category, FullPath = mod.fullPath, ModDataBytes = mdlData }); } }
/// <summary> /// The event handler for the current model button clicked /// </summary> private void AddCurrentModelButton_Click(object sender, RoutedEventArgs e) { var dat = new Dat(_gameDirectory); var selectedItem = ModelTypeComboBox.SelectedItem as ModComboBox; var mod = selectedItem.SelectedMod; var includedMod = new IncludedMods { Name = $"{Path.GetFileNameWithoutExtension(mod.fullPath)} ({((Category)ModListTreeView.SelectedItem).Name})", FullPath = mod.fullPath }; var includedModsList = IncludedModsList.Items.Cast <IncludedMods>().ToList(); if (includedModsList.Any(item => item.Name.Equals(includedMod.Name))) { if (FlexibleMessageBox.Show( $"This Option already includes {includedMod.Name} \n\n Would you like to overwrite the existing mod for this option?", "Overwrite?", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == System.Windows.Forms.DialogResult.Yes) { _selectedModOption.Mods[mod.fullPath].ModDataBytes = dat.GetRawData(mod.data.modOffset, XivDataFiles.GetXivDataFile(mod.datFile), mod.data.modSize); } } else { IncludedModsList.Items.Add(includedMod); var modData = new ModData { Name = mod.name, Category = mod.category, FullPath = mod.fullPath, ModDataBytes = dat.GetRawData(mod.data.modOffset, XivDataFiles.GetXivDataFile(mod.datFile), mod.data.modSize) }; _selectedModOption.Mods.Add(mod.fullPath, modData); } }
/// <summary> /// Imports a mod pack asynchronously /// </summary> /// <param name="modPackDirectory">The directory of the mod pack</param> /// <param name="modsJson">The list of mods to be imported</param> /// <param name="gameDirectory">The game directory</param> /// <param name="modListDirectory">The mod list directory</param> /// <param name="progress">The progress of the import</param> /// <returns>The number of total mods imported</returns> public async Task <int> ImportModPackAsync(DirectoryInfo modPackDirectory, List <ModsJson> modsJson, DirectoryInfo gameDirectory, DirectoryInfo modListDirectory, IProgress <double> progress) { var processCount = await Task.Run <int>(() => { var dat = new Dat(gameDirectory); var modListFullPaths = new List <string>(); var modList = JsonConvert.DeserializeObject <ModList>(File.ReadAllText(modListDirectory.FullName)); var modCount = 1; foreach (var modListMod in modList.Mods) { modListFullPaths.Add(modListMod.fullPath); } using (var archive = ZipFile.OpenRead(modPackDirectory.FullName)) { foreach (var zipEntry in archive.Entries) { if (zipEntry.FullName.EndsWith(".mpd")) { _tempMPD = Path.GetTempFileName(); zipEntry.ExtractToFile(_tempMPD, true); using (var binaryReader = new BinaryReader(File.OpenRead(_tempMPD))) { foreach (var modJson in modsJson) { if (modListFullPaths.Contains(modJson.FullPath)) { var existingEntry = (from entry in modList.Mods where entry.fullPath.Equals(modJson.FullPath) select entry).FirstOrDefault(); binaryReader.BaseStream.Seek(modJson.ModOffset, SeekOrigin.Begin); var data = binaryReader.ReadBytes(modJson.ModSize); dat.WriteToDat(new List <byte>(data), existingEntry, modJson.FullPath, modJson.Category, modJson.Name, XivDataFiles.GetXivDataFile(modJson.DatFile), _source, GetDataType(modJson.FullPath)); } else { binaryReader.BaseStream.Seek(modJson.ModOffset, SeekOrigin.Begin); var data = binaryReader.ReadBytes(modJson.ModSize); dat.WriteToDat(new List <byte>(data), null, modJson.FullPath, modJson.Category, modJson.Name, XivDataFiles.GetXivDataFile(modJson.DatFile), _source, GetDataType(modJson.FullPath)); } progress?.Report((double)modCount / modsJson.Count); modCount++; } } File.Delete(_tempMPD); break; } } } return(modCount - 1); }); return(processCount); }
/// <summary> /// Makes a generic item model from the mod item /// </summary> /// <param name="modItem">The mod item</param> /// <returns>The mod item as a XivGenericItemModel</returns> private static XivGenericItemModel MakeItemModel(Mod modItem) { var fullPath = modItem.fullPath; var item = new XivGenericItemModel { Name = modItem.name, ItemCategory = modItem.category, DataFile = XivDataFiles.GetXivDataFile(modItem.datFile) }; if (modItem.fullPath.Contains("chara/equipment") || modItem.fullPath.Contains("chara/accessory")) { item.Category = XivStrings.Gear; item.ModelInfo = new XivModelInfo { ModelID = int.Parse(fullPath.Substring(17, 4)) }; } if (modItem.fullPath.Contains("chara/weapon")) { item.Category = XivStrings.Gear; item.ModelInfo = new XivModelInfo { ModelID = int.Parse(fullPath.Substring(14, 4)) }; } if (modItem.fullPath.Contains("chara/human")) { item.Category = XivStrings.Character; if (item.Name.Equals(XivStrings.Body)) { item.ModelInfo = new XivModelInfo { ModelID = int.Parse(fullPath.Substring(fullPath.IndexOf("/body", StringComparison.Ordinal) + 7, 4)) }; } else if (item.Name.Equals(XivStrings.Hair)) { item.ModelInfo = new XivModelInfo { ModelID = int.Parse(fullPath.Substring(fullPath.IndexOf("/hair", StringComparison.Ordinal) + 7, 4)) }; } else if (item.Name.Equals(XivStrings.Face)) { item.ModelInfo = new XivModelInfo { ModelID = int.Parse(fullPath.Substring(fullPath.IndexOf("/face", StringComparison.Ordinal) + 7, 4)) }; } else if (item.Name.Equals(XivStrings.Tail)) { item.ModelInfo = new XivModelInfo { ModelID = int.Parse(fullPath.Substring(fullPath.IndexOf("/tail", StringComparison.Ordinal) + 7, 4)) }; } } if (modItem.fullPath.Contains("chara/common")) { item.Category = XivStrings.Character; if (item.Name.Equals(XivStrings.Face_Paint)) { item.ModelInfo = new XivModelInfo { ModelID = int.Parse(fullPath.Substring(fullPath.LastIndexOf("_", StringComparison.Ordinal) + 1, 1)) }; } else if (item.Name.Equals(XivStrings.Equip_Decals)) { item.ModelInfo = new XivModelInfo { ModelID = int.Parse(fullPath.Substring(fullPath.LastIndexOf("_", StringComparison.Ordinal) + 1, 3)) }; } } if (modItem.fullPath.Contains("chara/monster")) { item.Category = XivStrings.Companions; item.ModelInfo = new XivModelInfo { ModelID = int.Parse(fullPath.Substring(15, 4)), Body = int.Parse(fullPath.Substring(fullPath.IndexOf("/body", StringComparison.Ordinal) + 7, 4)) }; } if (modItem.fullPath.Contains("chara/demihuman")) { item.Category = XivStrings.Companions; item.ModelInfo = new XivModelInfo { Body = int.Parse(fullPath.Substring(17, 4)), ModelID = int.Parse(fullPath.Substring(fullPath.IndexOf("t/e", StringComparison.Ordinal) + 3, 4)) }; } if (modItem.fullPath.Contains("ui/")) { item.Category = XivStrings.UI; if (modItem.fullPath.Contains("ui/uld") || modItem.fullPath.Contains("ui/map")) { item.ModelInfo = new XivModelInfo { ModelID = 0 }; } else { item.ModelInfo = new XivModelInfo { ModelID = int.Parse(fullPath.Substring(fullPath.LastIndexOf("/", StringComparison.Ordinal) + 1, 6)) }; } } if (modItem.fullPath.Contains("/hou/")) { item.Category = XivStrings.Housing; item.ModelInfo = new XivModelInfo { ModelID = int.Parse(fullPath.Substring(fullPath.LastIndexOf("_m", StringComparison.Ordinal) + 2, 3)) }; } return(item); }
/// <summary> /// The event handler for the tree view item selection changed /// </summary> private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs <object> e) { TextureMapComboBox.Items.Clear(); ModelTypeComboBox.Items.Clear(); MaterialComboBox.Items.Clear(); CustomTextureTextBox.Text = string.Empty; CustomModelTextBox.Text = string.Empty; var selectedItem = e.NewValue as Category; var modList = JsonConvert.DeserializeObject <ModList>(File.ReadAllText(_modListDirectory.FullName)); var modItems = from mod in modList.Mods where mod.name.Equals(selectedItem.Name) select mod; foreach (var modItem in modItems) { var itemPath = modItem.fullPath; var modCB = new ModComboBox(); var ttp = new TexTypePath { Path = itemPath, DataFile = XivDataFiles.GetXivDataFile(modItem.datFile) }; // Textures if (itemPath.Contains("_d.")) { modCB.Name = $"{XivTexType.Diffuse} ({Path.GetFileNameWithoutExtension(itemPath)})"; modCB.SelectedMod = modItem; ttp.Type = XivTexType.Diffuse; } else if (itemPath.Contains("_n.")) { modCB.Name = $"{XivTexType.Normal} ({Path.GetFileNameWithoutExtension(itemPath)})"; modCB.SelectedMod = modItem; ttp.Type = XivTexType.Normal; } else if (itemPath.Contains("_s.")) { modCB.Name = $"{XivTexType.Specular} ({Path.GetFileNameWithoutExtension(itemPath)})"; modCB.SelectedMod = modItem; ttp.Type = XivTexType.Specular; } else if (itemPath.Contains("_m.")) { modCB.Name = $"{XivTexType.Multi} ({Path.GetFileNameWithoutExtension(itemPath)})"; modCB.SelectedMod = modItem; ttp.Type = XivTexType.Multi; } else if (itemPath.Contains("material")) { modCB.Name = $"{XivTexType.ColorSet} ({Path.GetFileNameWithoutExtension(itemPath)})"; modCB.SelectedMod = modItem; ttp.Type = XivTexType.ColorSet; } else if (itemPath.Contains("decal")) { modCB.Name = $"{XivTexType.Mask} ({Path.GetFileNameWithoutExtension(itemPath)})"; modCB.SelectedMod = modItem; ttp.Type = XivTexType.Mask; } else if (itemPath.Contains("vfx")) { modCB.Name = $"{XivTexType.Vfx} ({Path.GetFileNameWithoutExtension(itemPath)})"; modCB.SelectedMod = modItem; ttp.Type = XivTexType.Vfx; } else if (itemPath.Contains("ui/")) { if (itemPath.Contains("icon")) { modCB.Name = $"{XivTexType.Icon} ({Path.GetFileNameWithoutExtension(itemPath)})"; modCB.SelectedMod = modItem; ttp.Type = XivTexType.Icon; } else if (itemPath.Contains("map")) { modCB.Name = $"{XivTexType.Map} ({Path.GetFileNameWithoutExtension(itemPath)})"; modCB.SelectedMod = modItem; ttp.Type = XivTexType.Map; } else { modCB.Name = $"UI ({Path.GetFileNameWithoutExtension(itemPath)})"; modCB.SelectedMod = modItem; ttp.Type = XivTexType.Other; } } if (modCB.Name != null) { modCB.TexTypePath = ttp; TextureMapComboBox.Items.Add(modCB); } // Models if (itemPath.Contains(".mdl")) { modCB.Name = $"{((IItemModel)selectedItem.Item).ModelInfo.ModelID} ({Path.GetFileNameWithoutExtension(itemPath)})"; modCB.SelectedMod = modItem; modCB.TexTypePath = null; ModelTypeComboBox.Items.Add(modCB); } // Material if (itemPath.Contains(".mtrl")) { modCB.Name = $"Material ({Path.GetFileNameWithoutExtension(itemPath)})"; modCB.SelectedMod = modItem; modCB.TexTypePath = null; MaterialComboBox.Items.Add(modCB); MaterialTabItem.IsEnabled = true; } } if (TextureMapComboBox.Items.Count > 0) { AddCurrentTextureButton.IsEnabled = true; GetCustomTextureButton.IsEnabled = true; CustomTextureTextBox.IsEnabled = true; AddCustomTextureButton.IsEnabled = false; TextureMapComboBox.SelectedIndex = 0; NoTextureModsLabel.Content = string.Empty; } else { AddCurrentTextureButton.IsEnabled = false; GetCustomTextureButton.IsEnabled = false; CustomTextureTextBox.IsEnabled = false; AddCustomTextureButton.IsEnabled = false; NoTextureModsLabel.Content = "There are no Texture mods present.\n\nIf you would like to enable this, first import a texture for the selected item."; } if (ModelTypeComboBox.Items.Count > 0) { AddCurrentModelButton.IsEnabled = true; GetCustomModelButton.IsEnabled = true; CustomModelTextBox.IsEnabled = true; AdvOptionsButton.IsEnabled = true; AddCustomModelButton.IsEnabled = false; ModelTypeComboBox.SelectedIndex = 0; NoModelModsLabel.Content = string.Empty; } else { AddCurrentModelButton.IsEnabled = false; GetCustomModelButton.IsEnabled = false; CustomModelTextBox.IsEnabled = false; AdvOptionsButton.IsEnabled = false; AddCustomModelButton.IsEnabled = false; NoModelModsLabel.Content = "There are no 3D Model mods present.\n\nIf you would like to enable this, first import a model for the selected item."; } if (MaterialComboBox.Items.Count > 0) { AddCurrentMaterialButton.IsEnabled = true; MaterialComboBox.SelectedIndex = 0; NoMaterialsModsLabel.Content = string.Empty; } else { AddCurrentMaterialButton.IsEnabled = false; NoMaterialsModsLabel.Content = "There are no Material mods present.\n\nIf you would like to enable this, first import a Material for the selected item."; } SelectModGroup.IsEnabled = true; }
/// <summary> /// Toggles the mod on or off /// </summary> /// <param name="internalFilePath">The internal file path of the mod</param> /// <param name="enable">The status of the mod</param> public async Task ToggleModPackStatus(string modPackName, bool enable) { var modList = GetModList(); List <Mod> mods = null; if (modPackName.Equals("Standalone (Non-ModPack)")) { mods = (from mod in modList.Mods where mod.modPack == null select mod).ToList(); } else { mods = (from mod in modList.Mods where mod.modPack != null && mod.modPack.name.Equals(modPackName) select mod).ToList(); } if (mods == null) { throw new Exception("Unable to find mods with given Mod Pack Name in modlist."); } foreach (var modEntry in mods) { if (modEntry.name.Equals(string.Empty)) { continue; } if (enable) { await Index.UpdateIndex(modEntry.data.modOffset, modEntry.fullPath, XivDataFiles.GetXivDataFile(modEntry.datFile)); await Index.UpdateIndex2(modEntry.data.modOffset, modEntry.fullPath, XivDataFiles.GetXivDataFile(modEntry.datFile)); modEntry.enabled = true; } else { await Index.UpdateIndex(modEntry.data.originalOffset, modEntry.fullPath, XivDataFiles.GetXivDataFile(modEntry.datFile)); await Index.UpdateIndex2(modEntry.data.originalOffset, modEntry.fullPath, XivDataFiles.GetXivDataFile(modEntry.datFile)); modEntry.enabled = false; } } WriteModList(modList); }
/// <summary> /// Creates a mod pack that uses simple installation /// </summary> /// <param name="modPackData">The data that will go into the mod pack</param> /// <param name="gameDirectory">The game directory</param> /// <param name="progress">The progress of the mod pack creation</param> /// <returns>The number of mods processed for the mod pack</returns> public async Task <int> CreateSimpleModPack(SimpleModPackData modPackData, DirectoryInfo gameDirectory, IProgress <double> progress) { var processCount = await Task.Run <int>(() => { var dat = new Dat(gameDirectory); _tempMPD = Path.GetTempFileName(); _tempMPL = Path.GetTempFileName(); var modCount = 1; var modPackJson = new ModPackJson { TTMPVersion = _currentSimpleTTMPVersion, Name = modPackData.Name, Author = modPackData.Author, Version = modPackData.Version.ToString(), Description = modPackData.Description, SimpleModsList = new List <ModsJson>() }; using (var binaryWriter = new BinaryWriter(File.Open(_tempMPD, FileMode.Open))) { foreach (var simpleModData in modPackData.SimpleModDataList) { var modsJson = new ModsJson { Name = simpleModData.Name, Category = simpleModData.Category, FullPath = simpleModData.FullPath, ModSize = simpleModData.ModSize, DatFile = simpleModData.DatFile, ModOffset = binaryWriter.BaseStream.Position }; var rawData = dat.GetRawData((int)simpleModData.ModOffset, XivDataFiles.GetXivDataFile(simpleModData.DatFile), simpleModData.ModSize); binaryWriter.Write(rawData); modPackJson.SimpleModsList.Add(modsJson); progress?.Report((double)modCount / modPackData.SimpleModDataList.Count); modCount++; } } File.WriteAllText(_tempMPL, JsonConvert.SerializeObject(modPackJson)); var modPackPath = $"{_modPackDirectory}\\{modPackData.Name}.ttmp"; if (File.Exists(modPackPath)) { var fileNum = 1; modPackPath = $"{_modPackDirectory}\\{modPackData.Name}({fileNum}).ttmp"; while (File.Exists(modPackPath)) { fileNum++; modPackPath = $"{_modPackDirectory}\\{modPackData.Name}({fileNum}).ttmp"; } } using (var zip = ZipFile.Open(modPackPath, ZipArchiveMode.Create)) { zip.CreateEntryFromFile(_tempMPL, "TTMPL.mpl"); zip.CreateEntryFromFile(_tempMPD, "TTMPD.mpd"); } File.Delete(_tempMPD); File.Delete(_tempMPL); return(modCount); }); return(processCount); }
/// <summary> /// The event handler for the tree view item selection changed /// </summary> private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs <object> e) { var selectedItem = e.NewValue as Category; if (selectedItem == null || selectedItem.Item == null) { return; } TextureMapComboBox.Items.Clear(); ModelTypeComboBox.Items.Clear(); MaterialComboBox.Items.Clear(); CustomTextureTextBox.Text = string.Empty; var modding = new Modding(_gameDirectory); var modList = modding.GetModList(); var modItems = from mod in modList.Mods where mod.name.Equals(selectedItem.Name) select mod; foreach (var modItem in modItems) { var itemPath = modItem.fullPath; var modCB = new ModComboBox(); var ttp = new TexTypePath { Path = itemPath, DataFile = XivDataFiles.GetXivDataFile(modItem.datFile) }; // Textures if (itemPath.Contains("_d.")) { modCB.Name = $"{XivTexType.Diffuse} ({Path.GetFileNameWithoutExtension(itemPath)})"; modCB.SelectedMod = modItem; ttp.Type = XivTexType.Diffuse; } else if (itemPath.Contains("_n.")) { modCB.Name = $"{XivTexType.Normal} ({Path.GetFileNameWithoutExtension(itemPath)})"; modCB.SelectedMod = modItem; ttp.Type = XivTexType.Normal; } else if (itemPath.Contains("_s.")) { modCB.Name = $"{XivTexType.Specular} ({Path.GetFileNameWithoutExtension(itemPath)})"; modCB.SelectedMod = modItem; ttp.Type = XivTexType.Specular; } else if (itemPath.Contains("_m.")) { modCB.Name = $"{XivTexType.Multi} ({Path.GetFileNameWithoutExtension(itemPath)})"; modCB.SelectedMod = modItem; ttp.Type = XivTexType.Multi; } else if (itemPath.Contains("material")) { modCB.Name = $"{XivTexType.ColorSet} ({Path.GetFileNameWithoutExtension(itemPath)})"; modCB.SelectedMod = modItem; ttp.Type = XivTexType.ColorSet; } else if (itemPath.Contains("decal")) { modCB.Name = $"{XivTexType.Mask} ({Path.GetFileNameWithoutExtension(itemPath)})"; modCB.SelectedMod = modItem; ttp.Type = XivTexType.Mask; } else if (itemPath.Contains("vfx")) { modCB.Name = $"{XivTexType.Vfx} ({Path.GetFileNameWithoutExtension(itemPath)})"; modCB.SelectedMod = modItem; ttp.Type = XivTexType.Vfx; } else if (itemPath.Contains("ui/")) { if (itemPath.Contains("icon")) { modCB.Name = $"{XivTexType.Icon} ({Path.GetFileNameWithoutExtension(itemPath)})"; modCB.SelectedMod = modItem; ttp.Type = XivTexType.Icon; } else if (itemPath.Contains("map")) { modCB.Name = $"{XivTexType.Map} ({Path.GetFileNameWithoutExtension(itemPath)})"; modCB.SelectedMod = modItem; ttp.Type = XivTexType.Map; } else { modCB.Name = $"UI ({Path.GetFileNameWithoutExtension(itemPath)})"; modCB.SelectedMod = modItem; ttp.Type = XivTexType.Other; } } else if (itemPath.Contains(".tex")) { modCB.Name = $"{XivTexType.Other} ({Path.GetFileNameWithoutExtension(itemPath)})"; modCB.SelectedMod = modItem; ttp.Type = XivTexType.Other; } if (modCB.Name != null) { modCB.TexTypePath = ttp; TextureMapComboBox.Items.Add(modCB); } // Models if (itemPath.Contains(".mdl")) { //esrinzou for Repair program crash when selecting [character/Body] item //modCB.Name = $"{((IItemModel)selectedItem.Item).ModelInfo.ModelID} ({Path.GetFileNameWithoutExtension(itemPath)})"; //esrinzou begin if (((IItemModel)selectedItem.Item).ModelInfo == null) { modCB.Name = $"{((IItemModel)selectedItem.Item).Name} ({Path.GetFileNameWithoutExtension(itemPath)})"; } else { var modelId = ((IItemModel)selectedItem.Item).ModelInfo.PrimaryID; if (selectedItem.Item.PrimaryCategory.Equals(XivStrings.Character)) { var item = selectedItem.Item; if (item.Name.Equals(XivStrings.Body)) { modelId = int.Parse( itemPath.Substring(itemPath.IndexOf("/body", StringComparison.Ordinal) + 7, 4)); } else if (item.Name.Equals(XivStrings.Hair)) { modelId = int.Parse( itemPath.Substring(itemPath.IndexOf("/hair", StringComparison.Ordinal) + 7, 4)); } else if (item.Name.Equals(XivStrings.Face)) { modelId = int.Parse( itemPath.Substring(itemPath.IndexOf("/face", StringComparison.Ordinal) + 7, 4)); } else if (item.Name.Equals(XivStrings.Tail)) { modelId = int.Parse( itemPath.Substring(itemPath.IndexOf("/tail", StringComparison.Ordinal) + 7, 4)); } } modCB.Name = $"{modelId} ({Path.GetFileNameWithoutExtension(itemPath)})"; } //esrinzou end modCB.SelectedMod = modItem; modCB.TexTypePath = null; ModelTypeComboBox.Items.Add(modCB); } // Material if (itemPath.Contains(".mtrl")) { var materialModCB = new ModComboBox { Name = $"Material ({Path.GetFileNameWithoutExtension(itemPath)})", SelectedMod = modItem, TexTypePath = null }; MaterialComboBox.Items.Add(materialModCB); MaterialTabItem.IsEnabled = true; } } if (TextureMapComboBox.Items.Count > 0) { AddCurrentTextureButton.IsEnabled = true; GetCustomTextureButton.IsEnabled = true; CustomTextureTextBox.IsEnabled = true; AddCustomTextureButton.IsEnabled = false; TextureMapComboBox.SelectedIndex = 0; NoTextureModsLabel.Content = string.Empty; } else { AddCurrentTextureButton.IsEnabled = false; GetCustomTextureButton.IsEnabled = false; CustomTextureTextBox.IsEnabled = false; AddCustomTextureButton.IsEnabled = false; NoTextureModsLabel.Content = UIStrings.No_Texture_Mods; } if (ModelTypeComboBox.Items.Count > 0) { AddCurrentModelButton.IsEnabled = true; AdvOptionsButton.IsEnabled = true; ModelTypeComboBox.SelectedIndex = 0; NoModelModsLabel.Content = string.Empty; } else { AddCurrentModelButton.IsEnabled = false; AdvOptionsButton.IsEnabled = false; NoModelModsLabel.Content = UIStrings.No_3D_Mods; } if (MaterialComboBox.Items.Count > 0) { AddCurrentMaterialButton.IsEnabled = true; MaterialComboBox.SelectedIndex = 0; NoMaterialsModsLabel.Content = string.Empty; } else { AddCurrentMaterialButton.IsEnabled = false; NoMaterialsModsLabel.Content = UIStrings.No_Material_Mods; } SelectModGroup.IsEnabled = true; }
/// <summary> /// Checks the mods for any problems /// </summary> private void CheckMods() { var modListDirectory = new DirectoryInfo(Path.Combine(_gameDirectory.Parent.Parent.FullName, XivStrings.ModlistFilePath)); var modList = JsonConvert.DeserializeObject <ModList>(File.ReadAllText(modListDirectory.FullName)); var dat = new Dat(_gameDirectory); if (modList.modCount > 0) { foreach (var mod in modList.Mods) { if (mod.name.Equals(string.Empty)) { continue; } var fileName = Path.GetFileName(mod.fullPath); var tabs = ""; if (fileName.Length < 21 && fileName.Length > 12) { tabs = "\t"; } else if (fileName.Length < 12) { tabs = "\t\t"; } AddText($"\t{fileName}{tabs}", textColor); if (mod.data.originalOffset == 0) { AddText("\t\u2716\n", "Red"); AddText("\tOriginal Offset was 0, you will be unable to revert to original, consider starting over. \n", "Red"); } else if (mod.data.modOffset == 0) { AddText("\t\u2716\n", "Red"); AddText("\tMod Offset was 0, Disable from File > Modlist and reimport.\n", "Red"); } else { AddText("\t\u2714", "Green"); } var fileType = 0; try { fileType = dat.GetFileType(mod.data.modOffset, XivDataFiles.GetXivDataFile(mod.datFile)); } catch (Exception ex) { AddText("\t\u2716\n", "Red"); AddText($"\tError: {ex.Message}\n", "Red"); } if (fileType != 2 && fileType != 3 && fileType != 4) { AddText("\t\u2716\n", "Red"); AddText($"\tFound unknown file type ( {fileType} ) offset is most likely corrupt.\n", "Red"); } else { AddText("\t\u2714\n", "Green"); } } } else { AddText("\tNo entries found in modlist.\n", "Orange"); } }
/// <summary> /// Performs post-patch modlist corrections and validation, prompting user also to generate backups after a successful completion. /// </summary> /// <returns></returns> public async Task DoPostPatchCleanup() { FlexibleMessageBox.Show(_mainWindow.Win32Window, UIMessages.PatchDetectedMessage, "Post Patch Cleanup Starting", MessageBoxButtons.OK, MessageBoxIcon.Information, MessageBoxDefaultButton.Button1); MainWindow.MakeHighlander(); var resetLumina = false; await _mainWindow.LockUi("Performing Post-Patch Maintenence", "This may take a few minutes if you have many mods installed.", this); var gi = XivCache.GameInfo; if (XivCache.GameInfo.UseLumina) { resetLumina = true; XivCache.SetGameInfo(gi.GameDirectory, gi.GameLanguage, gi.DxMode, false, false, gi.LuminaDirectory, gi.UseLumina); } var workerStatus = XivCache.CacheWorkerEnabled; if (workerStatus) { // Stop the cache worker if it's running. XivCache.CacheWorkerEnabled = false; } try { var modding = new Modding(_gameDirectory); var _index = new Index(_gameDirectory); var _dat = new Dat(_gameDirectory); var validTypes = new List <int>() { 2, 3, 4 }; // We have to do a few things here. // 1. Save a list of what mods were enabled. // 2. Go through and validate everything that says it is enabled actually is enabled, or mark it as disabled and update its original index offset if it is not. // 3. Prompt the user for either a full disable and backup creation, or a restore to normal state (re-enable anything that was enabled before but is not now) var modList = modding.GetModList(); Dictionary <XivDataFile, IndexFile> indexFiles = new Dictionary <XivDataFile, IndexFile>(); // Cache our currently enabled stuff. List <Mod> enabledMods = modList.Mods.Where(x => x.enabled == true).ToList(); var toRemove = new List <Mod>(); foreach (var mod in modList.Mods) { if (!String.IsNullOrEmpty(mod.fullPath)) { var df = IOUtil.GetDataFileFromPath(mod.fullPath); if (!indexFiles.ContainsKey(df)) { indexFiles[df] = await _index.GetIndexFile(df); } var index1Value = indexFiles[df].Get8xDataOffset(mod.fullPath); var index2Value = indexFiles[df].Get8xDataOffsetIndex2(mod.fullPath); var oldOriginalOffset = mod.data.originalOffset; var modOffset = mod.data.modOffset; // In any event where an offset does not match either of our saved offsets, we must assume this is a new // default file offset for post-patch. if (index1Value != oldOriginalOffset && index1Value != modOffset && index1Value != 0) { // Index 1 value is our new base offset. var type = _dat.GetFileType(index1Value, df); // Make sure the file it's trying to point to is actually valid. if (validTypes.Contains(type)) { mod.data.originalOffset = index1Value; mod.enabled = false; } else { // Oh dear. The new index is f****d. Is the old Index Ok? type = _dat.GetFileType(oldOriginalOffset, df); if (validTypes.Contains(type) && oldOriginalOffset != 0) { // Old index is fine, so keep using that. // But mark the index value as invalid, so that we stomp on the index value after this. index1Value = -1; mod.enabled = false; } else { // Okay... Maybe the new Index2 Value? if (index2Value != 0) { type = _dat.GetFileType(index2Value, df); if (validTypes.Contains(type)) { // Set the index 1 value to invalid so that the if later down the chain stomps the index1 value. index1Value = -1; mod.data.originalOffset = index2Value; mod.enabled = false; } else { // We be f****d. throw new Exception("Unable to determine working original offset for file:" + mod.fullPath); } } else { // We be f****d. throw new Exception("Unable to determine working original offset for file:" + mod.fullPath); } } } } else if (index2Value != oldOriginalOffset && index2Value != modOffset && index2Value != 0) { // Our Index 1 was normal, but our Index 2 is changed to an unknown value. // If the index 2 points to a valid file, we must assume that this new file // is our new base data offset. var type = _dat.GetFileType(index2Value, df); if (validTypes.Contains(type) && index2Value != 0) { mod.data.originalOffset = index2Value; mod.enabled = false; } else { // Oh dear. The new index is f****d. Is the old Index Ok? type = _dat.GetFileType(oldOriginalOffset, df); if (validTypes.Contains(type) && oldOriginalOffset != 0) { // Old index is fine, so keep using that, but set the index2 value to invalid to ensure we // stomp on the current broken index value. index2Value = -1; } else { // We be f****d. throw new Exception("Unable to determine working original offset for file:" + mod.fullPath); } } } // Indexes don't match. This can occur if SE adds something to index2 that didn't exist in index2 before. if (index1Value != index2Value && index2Value != 0) { // We should never actually get to this state for file-addition mods. If we do, uh.. I guess correct the indexes and yolo? // ( Only way we get here is if SE added a new file at the same name as a file the user had created via modding, in which case, it's technically no longer a file addition mod ) indexFiles[df].SetDataOffset(mod.fullPath, mod.data.originalOffset); index1Value = mod.data.originalOffset; index2Value = mod.data.originalOffset; mod.enabled = false; } // Set it to the corrected state. if (index1Value == mod.data.modOffset) { mod.enabled = true; } else { mod.enabled = false; } // Perform a basic file type check on our results. var fileType = _dat.GetFileType(mod.data.modOffset, IOUtil.GetDataFileFromPath(mod.fullPath)); var originalFileType = _dat.GetFileType(mod.data.modOffset, IOUtil.GetDataFileFromPath(mod.fullPath)); if (!validTypes.Contains(fileType) || mod.data.modOffset == 0) { // Mod data is busted. Fun. toRemove.Add(mod); } if ((!validTypes.Contains(originalFileType)) || mod.data.originalOffset == 0) { if (mod.IsCustomFile()) { // Okay, in this case this is recoverable as the mod is a custom addition anyways, so we can just delete it. if (!toRemove.Contains(mod)) { toRemove.Add(mod); } } else { // Update ended up with us unable to find a working offset. Double fun. throw new Exception("Unable to determine working offset for file:" + mod.fullPath); } } } // Okay, this mod is now represented in the modlist in it's actual post-patch index state. var datNum = (int)((mod.data.modOffset / 8) & 0x0F) / 2; var dat = XivDataFiles.GetXivDataFile(mod.datFile); var originalDats = await _dat.GetUnmoddedDatList(dat); var datPath = $"{dat.GetDataFileName()}{Dat.DatExtension}{datNum}"; // Test for SE Dat file rollover. if (originalDats.Contains(datPath)) { // Shit. This means that the dat file where this mod lived got eaten by SE. We have to destroy the modlist entry at this point. toRemove.Add(mod); } } // Save any index changes we made. foreach (var dkv in indexFiles) { await _index.SaveIndexFile(dkv.Value); } // The modlist is now saved in its current index-represented post patch state. modding.SaveModList(modList); // We now need to clear out any mods that are irreparably f****d, and clear out all of our // internal data files so we can rebuild them later. var internalFiles = modList.Mods.Where(x => x.IsInternal()); toRemove.AddRange(internalFiles); if (toRemove.Count > 0) { var removedString = ""; // Soft-Disable all metadata mods, since we're going to purge their internal file entries. var metadata = modList.Mods.Where(x => x.fullPath.EndsWith(".meta") || x.fullPath.EndsWith(".rgsp")); foreach (var mod in metadata) { var df = IOUtil.GetDataFileFromPath(mod.fullPath); await modding.ToggleModUnsafe(false, mod, true, false, indexFiles[df], modList); } foreach (var mod in toRemove) { if (mod.data.modOffset == 0 || mod.data.originalOffset == 0) { if (mod.data.originalOffset == 0 && mod.enabled) { // This is awkward. We have a mod whose data got bashed, but has no valid original offset to restore. // So the indexes here are f****d if we do, f****d if we don't. throw new Exception("Patch-Broken file has no valid index to restore. Clean Index Restoration required."); } modList.Mods.Remove(mod); enabledMods.Remove(mod); removedString += mod.fullPath + "\n"; } else { if (mod.enabled) { var df = IOUtil.GetDataFileFromPath(mod.fullPath); await modding.ToggleModUnsafe(false, mod, true, false, indexFiles[df], modList); } modList.Mods.Remove(mod); // Since we're deleting this entry entirely, we can't leave it in the other cached list either to get re-enabled later. enabledMods.Remove(mod); if (!mod.IsInternal()) { removedString += mod.fullPath + "\n"; } } } // Save the index files and modlist again now that we've removed all the invalid entries. foreach (var dkv in indexFiles) { await _index.SaveIndexFile(dkv.Value); } modding.SaveModList(modList); // Show the user a message if we purged any real files. if (toRemove.Any(x => !String.IsNullOrEmpty(x.fullPath) && !x.IsInternal())) { var text = String.Format(UIMessages.PatchDestroyedFiles, removedString); FlexibleMessageBox.Show(_mainWindow.Win32Window, text, "Destroyed Files Notification", MessageBoxButtons.OK, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button1); } } // Always create clean index backups after this process is completed. _mainWindow.LockProgress.Report("Disabling Mods..."); await modding.ToggleAllMods(false); _mainWindow.LockProgress.Report("Creating Index Backups..."); var pc = new ProblemChecker(_gameDirectory); DirectoryInfo backupDir; try { Directory.CreateDirectory(Settings.Default.Backup_Directory); backupDir = new DirectoryInfo(Settings.Default.Backup_Directory); } catch { throw new Exception("Unable to create index backups.\nThe Index Backup directory is invalid or inaccessible: " + Settings.Default.Backup_Directory); } await pc.BackupIndexFiles(backupDir); // Now restore the modlist enable/disable state back to how the user had it before. _mainWindow.LockProgress.Report("Re-Enabling mods..."); // Re-enable things. await modding.ToggleMods(true, enabledMods.Select(x => x.fullPath)); FlexibleMessageBox.Show(_mainWindow.Win32Window, UIMessages.PostPatchComplete, "Post-Patch Process Complete", MessageBoxButtons.OK, MessageBoxIcon.Information, MessageBoxDefaultButton.Button1); } catch (Exception Ex) { // Show the user the error, then let them go about their business of fixing things. FlexibleMessageBox.Show(_mainWindow.Win32Window, String.Format(UIMessages.PostPatchError, Ex.Message), "Post-Patch Failure", MessageBoxButtons.OK, MessageBoxIcon.Information, MessageBoxDefaultButton.Button1); } finally { if (resetLumina) { // Reset lumina mode back to on if we disabled it to perform update checks. XivCache.SetGameInfo(gi.GameDirectory, gi.GameLanguage, gi.DxMode, true, false, gi.LuminaDirectory, true); } XivCache.CacheWorkerEnabled = workerStatus; await _mainWindow.UnlockUi(this); } }