private async Task <bool> AddFile(string file) { var _index = new Index(XivCache.GameInfo.GameDirectory); if (Path.GetExtension(file) != ".meta") { if (!(await _index.FileExists(file))) { // File doesn't actually exist, can't be added. return(false); } } var match = _suffixRegex.Match(file); if (match.Success && match.Groups[1].Value == "mdl") { ModelListBox.Items.Add(new StandardModpackFileSelect.FileEntry(file)); } else if (match.Success && (match.Groups[1].Value == "mtrl" || match.Groups[1].Value == "avfx")) { MaterialListBox.Items.Add(new StandardModpackFileSelect.FileEntry(file)); } else if (match.Success && (match.Groups[1].Value == "tex" || match.Groups[1].Value == "atex")) { TextureListBox.Items.Add(new StandardModpackFileSelect.FileEntry(file)); } else { MetaListBox.Items.Add(new StandardModpackFileSelect.FileEntry(file)); } return(true); }
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); }
/// <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 <bool> ToggleModStatus(string internalFilePath, bool enable) { if (XivCache.GameInfo.UseLumina) { throw new Exception("TexTools mods cannot be altered in Lumina mode."); } var index = new Index(_gameDirectory); if (string.IsNullOrEmpty(internalFilePath)) { throw new Exception("File Path missing, unable to toggle mod."); } var modList = GetModList(); var modEntry = modList.Mods.FirstOrDefault(x => x.fullPath == internalFilePath); var result = await ToggleModUnsafe(enable, modEntry, false, true); if (!result) { return(result); } var modListDirectory = new DirectoryInfo(Path.Combine(_gameDirectory.Parent.Parent.FullName, XivStrings.ModlistFilePath)); SaveModList(modList); return(result); }
/// <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) { if (XivCache.GameInfo.UseLumina) { throw new Exception("TexTools mods cannot be altered in Lumina mode."); } var index = new Index(_gameDirectory); var modList = GetModList(); 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."); } await ToggleMods(enable, mods.Select(x => x.fullPath)); }
/// <summary> /// Resolves the original skeleton path in the FFXIV file system and raw extracts it. /// </summary> /// <param name="fullMdlPath">Full path to the MDL.</param> /// <param name="internalSkelName">Internal skeleton name (for hair). This can be resolved if missing, though it is slightly expensive to do so.</param> private static async Task <string> ExtractSkelb(string skelBPath) { var index = new Index(XivCache.GameInfo.GameDirectory); var dat = new Dat(XivCache.GameInfo.GameDirectory); var dataFile = IOUtil.GetDataFileFromPath(skelBPath); var offset = await index.GetDataOffset(skelBPath); if (offset == 0) { throw new Exception($"Could not find offset for {skelBPath}"); } var sklbData = await dat.GetType2Data(offset, dataFile); using (var br = new BinaryReader(new MemoryStream(sklbData))) { br.BaseStream.Seek(0, SeekOrigin.Begin); var magic = br.ReadInt32(); var format = br.ReadInt32(); br.ReadBytes(2); if (magic != 0x736B6C62) { throw new FormatException(); } var dataOffset = 0; switch (format) { case 0x31323030: dataOffset = br.ReadInt16(); break; case 0x31333030: case 0x31333031: br.ReadBytes(2); dataOffset = br.ReadInt16(); break; default: throw new Exception($"Unkown Data Format ({format})"); } br.BaseStream.Seek(dataOffset, SeekOrigin.Begin); var havokData = br.ReadBytes(sklbData.Length - dataOffset); var skelName = Path.GetFileNameWithoutExtension(skelBPath).Replace("skl_", ""); var outputFile = skelName + ".sklb"; var cwd = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location); var parsedFile = Path.Combine(cwd, SkeletonsFolder, outputFile); File.WriteAllBytes(parsedFile, havokData); return(parsedFile); } }
/// <summary> /// Reads and parses Ex Header files. /// </summary> /// <param name="gameDirectory">The install directory for the game.</param> /// <param name="lang">The language in which to read the data.</param> public Ex(DirectoryInfo gameDirectory, XivLanguage lang) { _gameDirectory = gameDirectory; _langCode = lang.GetLanguageCode(); _index = new Index(_gameDirectory); _dat = new Dat(_gameDirectory); }
public Tex(DirectoryInfo gameDirectory, XivDataFile dataFile) { _gameDirectory = gameDirectory; _index = new Index(_gameDirectory); _dat = new Dat(_gameDirectory); _dataFile = dataFile; }
/// <summary> /// Reads and parses Ex Header files, uses english as default language. /// </summary> /// <remarks> /// Used for ex files that do not have language data /// </remarks> /// <param name="gameDirectory">The install directory for the game.</param> public Ex(DirectoryInfo gameDirectory) { _gameDirectory = gameDirectory; _langCode = XivLanguage.English.GetLanguageCode(); _index = new Index(_gameDirectory); _dat = new Dat(_gameDirectory); }
private async Task AddFile(FileEntry file, IItem item, byte[] rawData = null) { var dat = new Dat(_gameDirectory); var index = new Index(_gameDirectory); if (file == null || file.Path == null || _selectedModOption == null) return; var includedModsList = IncludedModsList.Items.Cast<FileEntry>().ToList(); if (includedModsList.Any(f => f.Path.Equals(file.Path))) { if (FlexibleMessageBox.Show( string.Format(UIMessages.ExistingOption, file.Name), UIMessages.OverwriteTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Question) != System.Windows.Forms.DialogResult.Yes) { return; } } if (rawData == null) { var df = IOUtil.GetDataFileFromPath(file.Path); var offset = await index.GetDataOffset(file.Path); if (offset <= 0) { FlexibleMessageBox.Show("Cannot include file, file offset invalid.", UIMessages.ModDataReadErrorTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); return; } var size = await dat.GetCompressedFileSize(offset, df); rawData = dat.GetRawData(offset, df, size); if (rawData == null) { FlexibleMessageBox.Show("Cannot include file, file offset invalid.", UIMessages.ModDataReadErrorTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); return; } } if(_selectedModOption.Mods.ContainsKey(file.Path)) { _selectedModOption.Mods[file.Path].ModDataBytes = rawData; } else { IncludedModsList.Items.Add(file); var modData = new ModData { Name = item.Name, Category = item.SecondaryCategory, FullPath = file.Path, ModDataBytes = rawData, }; _selectedModOption.Mods.Add(file.Path, modData); } }
private async void ImportButton_Click(object sender, RoutedEventArgs e) { var path = PathBox.Text; if (String.IsNullOrWhiteSpace(path)) { return; } var od = new OpenFileDialog(); var result = od.ShowDialog(); if (result != System.Windows.Forms.DialogResult.OK) { return; } byte[] data = null; var _dat = new Dat(XivCache.GameInfo.GameDirectory); var _index = new Index(XivCache.GameInfo.GameDirectory); if (DecompressedType2.IsChecked == true) { var temp = File.ReadAllBytes(od.FileName); data = await _dat.CreateType2Data(temp); } else { data = File.ReadAllBytes(od.FileName); } var type = BitConverter.ToInt32(data, 4); if (type < 2 || type > 4) { FlexibleMessageBox.Show("Invalid Data Type.\nGeneric binary files should be imported as decompressed type 2 Data.", "Data Type Error", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } try { var dummyItem = new XivGenericItemModel(); dummyItem.Name = Path.GetFileName(path); dummyItem.SecondaryCategory = "Raw File Imports"; await _dat.WriteModFile(data, path, XivStrings.TexTools, dummyItem); } catch (Exception Ex) { FlexibleMessageBox.Show("Unable to import file.\n\nError: " + Ex.Message, "Import Error", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } FlexibleMessageBox.Show("File Imported Successfully.", "Import Success", MessageBoxButtons.OK, MessageBoxIcon.Information); this.Close(); }
public Task <bool> RestoreBackups(DirectoryInfo backupsDirectory) { return(Task.Run(async() => { var backupFiles = Directory.GetFiles(backupsDirectory.FullName); var filesToCheck = new XivDataFile[] { XivDataFile._0A_Exd, XivDataFile._04_Chara, XivDataFile._06_Ui, XivDataFile._01_Bgcommon }; var outdated = false; foreach (var xivDataFile in filesToCheck) { var backupFile = new DirectoryInfo($"{backupsDirectory.FullName}\\{xivDataFile.GetDataFileName()}.win32.index"); if (!File.Exists(backupFile.FullName)) { continue; } try { var outdatedCheck = await CheckForOutdatedBackups(xivDataFile, backupsDirectory); if (!outdatedCheck) { outdated = true; } } catch { // If the outdated check errored out, we likely have completely broken internal dat files. // ( Either deleted or 0 byte files ), so replacing them with *anything* is an improvement. } } var _index = new Index(_gameDirectory); // Make sure backups exist and are up to date unless called with forceRestore true if (backupFiles.Length != 0 && !outdated) { // Copy backups to ffxiv folder foreach (var backupFile in backupFiles) { if (backupFile.Contains(".win32.index")) { File.Copy(backupFile, $"{_gameDirectory}/{Path.GetFileName(backupFile)}", true); } } // Update all the index counts to be safe, in case the user's index backups were generated when some mod dats existed. _index.UpdateAllIndexDatCounts(); return true; } return false; })); }
private async Task DoCopy() { var from = FromBox.Text; var to = ToBox.Text; if (String.IsNullOrWhiteSpace(to) || String.IsNullOrWhiteSpace(to)) { return; } var _dat = new Dat(XivCache.GameInfo.GameDirectory); var _index = new Index(XivCache.GameInfo.GameDirectory); try { var exists = await _index.FileExists(from); if (!exists) { throw new InvalidDataException("Source file does not exist."); } exists = await _index.FileExists(to); if (exists) { var cancel = false; Dispatcher.Invoke(() => { var result = FlexibleMessageBox.Show("Destination file already exists. Overwrite?", "Overwrite Confirmation", System.Windows.Forms.MessageBoxButtons.YesNo, System.Windows.Forms.MessageBoxIcon.Warning); cancel = result == System.Windows.Forms.DialogResult.No; }); if (cancel) { return; } } await _dat.CopyFile(from, to, XivStrings.TexTools, true); Dispatcher.Invoke(() => { FlexibleMessageBox.Show("File Copied Successfully.", "Copy Success", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Information); Close(); }); } catch (Exception ex) { FlexibleMessageBox.Show("File Copy Failed:\n" + ex.Message, "Copy Failure", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error); } }
/// <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> /// Searches for housing items given a model ID /// </summary> /// <param name="modelID">The Model ID of the housing item</param> /// <param name="type">The type of housing item to search for</param> /// <returns>A list of Search Results</returns> public async Task <List <SearchResults> > SearchHousingByModelID(int modelID, XivItemType type) { var searchResultsList = new List <SearchResults>(); var index = new Index(_gameDirectory); var id = modelID.ToString().PadLeft(4, '0'); var folder = ""; if (type == XivItemType.furniture) { folder = $"bgcommon/hou/indoor/general/{id}/material"; } if (await index.FolderExists(folder, XivDataFile._01_Bgcommon)) { var searchResults = new SearchResults { Body = "-", Slot = XivStrings.Furniture_Indoor, Variant = int.Parse(id) }; searchResultsList.Add(searchResults); } folder = $"bgcommon/hou/outdoor/general/{id}/material"; if (await index.FolderExists(folder, XivDataFile._01_Bgcommon)) { var searchResults = new SearchResults { Body = "-", Slot = XivStrings.Furniture_Outdoor, Variant = int.Parse(id) }; searchResultsList.Add(searchResults); } searchResultsList.Sort(); return(searchResultsList); }
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); } } })); }
public async Task <List <TexTypePath> > GetAtexPaths(string vfxPath) { var index = new Index(_gameDirectory); var avfx = new Avfx(_gameDirectory, _dataFile); var folder = vfxPath.Substring(0, vfxPath.LastIndexOf("/")); var file = Path.GetFileName(vfxPath); var vfxOffset = await index.GetDataOffset(HashGenerator.GetHash(folder), HashGenerator.GetHash(file), _dataFile); var atexTexTypePathList = new List <TexTypePath>(); if (vfxOffset <= 0) { return(new List <TexTypePath>()); } var aTexPaths = new List <string>(); try { aTexPaths = await avfx.GetATexPaths(vfxOffset); } catch (Exception e) { throw new Exception(e.Message); } foreach (var atexPath in aTexPaths) { var ttp = new TexTypePath { DataFile = _dataFile, Name = "VFX: " + Path.GetFileNameWithoutExtension(atexPath), Path = atexPath }; atexTexTypePathList.Add(ttp); } return(atexTexTypePathList); }
public MainViewModel(MainWindow mainWindow) { _mainWindow = mainWindow; // This is actually synchronous and can just be called immediately... SetDirectories(true); _gameDirectory = new DirectoryInfo(Properties.Settings.Default.FFXIV_Directory); _index = new Index(_gameDirectory); if (ProgressLabel == null) { ProgressLabel = ""; } // And the rest of this can be pushed into a new thread. var task = Task.Run(Initialize); // Now we can wait on it. But we have to thread-safety it. task.Wait(); var exception = task.Exception; if (exception != null) { throw exception; } var result = task.Result; if (!result) { // We need to die NOW, and not risk any other functions possibly // f*****g with broken files. Process.GetCurrentProcess().Kill(); return; } CacheTimer.Elapsed += UpdateDependencyQueueCount; }
public async Task SaveFullImcInfo(FullImcInfo info, string path, string source, IItem referenceItem = null, IndexFile cachedIndexFile = null, ModList cachedModList = null) { if (info == null || info.TypeIdentifier != ImcType.Set && info.TypeIdentifier != ImcType.NonSet) { throw new InvalidDataException("Cannot save invalid IMC file."); } var index = new Index(_gameDirectory); var dat = new Dat(_gameDirectory); var data = new List <byte>(); // 4 Header bytes. data.AddRange(BitConverter.GetBytes((short)info.SubsetCount)); data.AddRange(BitConverter.GetBytes((short)info.TypeIdentifier)); // The rest of this is easy, it's literally just post all the sets in order. foreach (var entry in info.DefaultSubset) { data.AddRange(entry.GetBytes(info.TypeIdentifier)); } foreach (var set in info.SubsetList) { foreach (var entry in set) { data.AddRange(entry.GetBytes(info.TypeIdentifier)); } } // That's it. source ??= "Unknown"; await dat.ImportType2Data(data.ToArray(), path, source, referenceItem, cachedIndexFile, cachedModList); }
/// <summary> /// Gets additional assets when the original asset file contains asset file paths within it /// </summary> /// <param name="assets">The current asset object</param> private async Task GetAdditionalAssets(HousingAssets assets) { var index = new Index(_gameDirectory); var dat = new Dat(_gameDirectory); foreach (var additionalAsset in assets.AdditionalAssetList.ToList()) { var assetFolder = Path.GetDirectoryName(additionalAsset).Replace("\\", "/"); var assetFile = Path.GetFileName(additionalAsset); var assetOffset = await index.GetDataOffset(HashGenerator.GetHash(assetFolder), HashGenerator.GetHash(assetFile), XivDataFile._01_Bgcommon); var assetData = await dat.GetType2Data(assetOffset, XivDataFile._01_Bgcommon); await Task.Run(() => { using (var br = new BinaryReader(new MemoryStream(assetData))) { br.BaseStream.Seek(20, SeekOrigin.Begin); var skip = br.ReadInt32() + 20; br.BaseStream.Seek(skip + 4, SeekOrigin.Begin); var stringsOffset = br.ReadInt32(); br.BaseStream.Seek(skip + stringsOffset, SeekOrigin.Begin); var pathCounts = 0; while (true) { // Because we don't know the length of the string, we read the data until we reach a 0 value // That 0 value is the space between strings byte a; var pathName = new List <byte>(); while ((a = br.ReadByte()) != 0) { if (a == 0xFF) { break; } pathName.Add(a); } if (a == 0xFF) { break; } // Read the string from the byte array and remove null terminators var path = Encoding.ASCII.GetString(pathName.ToArray()).Replace("\0", ""); if (path.Equals(string.Empty)) { continue; } // Add the attribute to the list if (pathCounts == 0) { assets.Shared = path; } else if (pathCounts == 1) { assets.BaseFileName = path; } else { if (path.Contains(".mdl")) { assets.MdlList.Add(path); } else if (path.Contains(".sgb")) { assets.AdditionalAssetList.Add(path); } else if (!path.Contains(".")) { assets.BaseFolder = path; } else { assets.OthersList.Add(path); } } pathCounts++; } } }); } }
/// <summary> /// Gets the assets for furniture /// </summary> /// <param name="modelID">The model id to get the assets for</param> /// <returns>A HousingAssets object containing the asset info</returns> private async Task <HousingAssets> GetFurnitureAssets(int modelID, string category) { var index = new Index(_gameDirectory); var dat = new Dat(_gameDirectory); var id = modelID.ToString().PadLeft(4, '0'); var assetFolder = ""; var assetFile = ""; if (category.Equals(XivStrings.Furniture_Indoor)) { assetFolder = $"bgcommon/hou/indoor/general/{id}/asset"; assetFile = $"fun_b0_m{id}.sgb"; } else if (category.Equals(XivStrings.Furniture_Outdoor)) { assetFolder = $"bgcommon/hou/outdoor/general/{id}/asset"; assetFile = $"gar_b0_m{id}.sgb"; } var assetOffset = await index.GetDataOffset(HashGenerator.GetHash(assetFolder), HashGenerator.GetHash(assetFile), XivDataFile._01_Bgcommon); var assetData = await dat.GetType2Data(assetOffset, XivDataFile._01_Bgcommon); var housingAssets = new HousingAssets(); await Task.Run(() => { using (var br = new BinaryReader(new MemoryStream(assetData))) { br.BaseStream.Seek(20, SeekOrigin.Begin); var skip = br.ReadInt32() + 20; br.BaseStream.Seek(skip + 4, SeekOrigin.Begin); var stringsOffset = br.ReadInt32(); br.BaseStream.Seek(skip + stringsOffset, SeekOrigin.Begin); var pathCounts = 0; while (true) { // Because we don't know the length of the string, we read the data until we reach a 0 value // That 0 value is the space between strings byte a; var pathName = new List <byte>(); while ((a = br.ReadByte()) != 0) { if (a == 0xFF) { break; } pathName.Add(a); } if (a == 0xFF) { break; } // Read the string from the byte array and remove null terminators var path = Encoding.ASCII.GetString(pathName.ToArray()).Replace("\0", ""); if (path.Equals(string.Empty)) { continue; } // Add the attribute to the list if (pathCounts == 0) { housingAssets.Shared = path; } else if (pathCounts == 1) { housingAssets.BaseFileName = path; } else { if (path.Contains(".mdl")) { housingAssets.MdlList.Add(path); } else if (path.Contains(".sgb")) { housingAssets.AdditionalAssetList.Add(path); } else if (!path.Contains(".")) { housingAssets.BaseFolder = path; } else { housingAssets.OthersList.Add(path); } } pathCounts++; } } }); if (housingAssets.AdditionalAssetList.Count > 0) { await GetAdditionalAssets(housingAssets); } return(housingAssets); }
private async Task CreateBasic() { string modPackPath = Path.Combine(Properties.Settings.Default.ModPack_Directory, $"{ViewModel.Name}.ttmp2"); if (File.Exists(modPackPath)) { DialogResult overwriteDialogResult = FlexibleMessageBox.Show(new Wpf32Window(this), UIMessages.ModPackOverwriteMessage, UIMessages.OverwriteTitle, MessageBoxButtons.YesNoCancel, MessageBoxIcon.Warning); if (overwriteDialogResult != System.Windows.Forms.DialogResult.Yes) { return; } } TTMP texToolsModPack = new TTMP(new DirectoryInfo(Settings.Default.ModPack_Directory), XivStrings.TexTools); var index = new Index(XivCache.GameInfo.GameDirectory); var dat = new Dat(XivCache.GameInfo.GameDirectory); var modding = new Modding(XivCache.GameInfo.GameDirectory); var ModList = modding.GetModList(); SimpleModPackData simpleModPackData = new SimpleModPackData { Name = ViewModel.Name, Author = ViewModel.Author, Version = ViewModel.Version, Description = ViewModel.Description, Url = ViewModel.Url, SimpleModDataList = new List <SimpleModData>() }; foreach (var entry in ViewModel.Entries) { foreach (var file in entry.AllFiles) { var exists = await index.FileExists(file); // This is a funny case where in order to create the modpack we actually have to write a default meta entry to the dats first. // If we had the right functions we could just load and serialize the data, but we don't atm. if (!exists && Path.GetExtension(file) == ".meta") { var meta = await ItemMetadata.GetMetadata(file); await ItemMetadata.SaveMetadata(meta, XivStrings.TexTools); } var offset = await index.GetDataOffset(file); var dataFile = IOUtil.GetDataFileFromPath(file); var compressedSize = await dat.GetCompressedFileSize(offset, dataFile); var modEntry = ModList.Mods.FirstOrDefault(x => x.fullPath == file); var modded = modEntry != null && modEntry.enabled == true; SimpleModData simpleData = new SimpleModData { Name = entry.Item.Name, Category = entry.Item.SecondaryCategory, FullPath = file, ModOffset = offset, ModSize = compressedSize, IsDefault = !modded, DatFile = dataFile.GetDataFileName() }; simpleModPackData.SimpleModDataList.Add(simpleData); } } try { await LockUi(UIStrings.Creating_Modpack, null, null); Progress <(int current, int total, string message)> progressIndicator = new Progress <(int current, int total, string message)>(ReportProgress); await texToolsModPack.CreateSimpleModPack(simpleModPackData, XivCache.GameInfo.GameDirectory, progressIndicator, true); FlexibleMessageBox.Show(new Wpf32Window(this), "Modpack Created Successfully.", "Modpack Created", MessageBoxButtons.OK, MessageBoxIcon.Information); await UnlockUi(this); DialogResult = true; } catch (Exception ex) { FlexibleMessageBox.Show(new Wpf32Window(this), "An Error occured while creating the modpack.\n\n" + ex.Message, "Modpack Creation Error", MessageBoxButtons.OK, MessageBoxIcon.Error); await UnlockUi(this); } }
private async Task CreateAdvanced() { string modPackPath = Path.Combine(Properties.Settings.Default.ModPack_Directory, $"{ViewModel.Name}.ttmp2"); if (File.Exists(modPackPath)) { DialogResult overwriteDialogResult = FlexibleMessageBox.Show(new Wpf32Window(this), UIMessages.ModPackOverwriteMessage, UIMessages.OverwriteTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Warning); if (overwriteDialogResult != System.Windows.Forms.DialogResult.Yes) { return; } } await LockUi(UIStrings.Creating_Modpack, null, null); try { TTMP texToolsModPack = new TTMP(new DirectoryInfo(Settings.Default.ModPack_Directory), XivStrings.TexTools); var index = new Index(XivCache.GameInfo.GameDirectory); var dat = new Dat(XivCache.GameInfo.GameDirectory); var modding = new Modding(XivCache.GameInfo.GameDirectory); var ModList = modding.GetModList(); var wizardData = new ModPackData() { Name = ViewModel.Name, Author = ViewModel.Author, Version = ViewModel.Version, Description = ViewModel.Description, Url = ViewModel.Url, ModPackPages = new List <ModPackData.ModPackPage>() }; var page = new ModPackData.ModPackPage() { PageIndex = 1, ModGroups = new List <ModGroup>() }; wizardData.ModPackPages.Add(page); foreach (var e in ViewModel.Entries) { var item = e.Item; var files = e.AllFiles; var group = new ModGroup() { GroupName = item.Name, SelectionType = "Multi", OptionList = new List <ModOption>() }; page.ModGroups.Add(group); var option = new ModOption { GroupName = group.GroupName, IsChecked = true, Name = GetNiceLevelName(e.Level, true, true), Description = "Item: " + item.Name + "\nInclusion Level: " + GetNiceLevelName(e.Level) + "\nPrimary Files:" + e.MainFiles.Count + "\nTotal Files:" + e.AllFiles.Count, SelectionType = "Multi", }; group.OptionList.Add(option); foreach (var file in e.AllFiles) { var exists = await index.FileExists(file); // This is a funny case where in order to create the modpack we actually have to write a default meta entry to the dats first. // If we had the right functions we could just load and serialize the data, but we don't atm. if (!exists && Path.GetExtension(file) == ".meta") { var meta = await ItemMetadata.GetMetadata(file); await ItemMetadata.SaveMetadata(meta, XivStrings.TexTools); } var offset = await index.GetDataOffset(file); var dataFile = IOUtil.GetDataFileFromPath(file); var compressedSize = await dat.GetCompressedFileSize(offset, dataFile); var modEntry = ModList.Mods.FirstOrDefault(x => x.fullPath == file); var modded = modEntry != null && modEntry.enabled == true; var fData = new ModData { Name = e.Item.Name, Category = e.Item.SecondaryCategory, FullPath = file, IsDefault = !modded, ModDataBytes = dat.GetRawData(offset, dataFile, compressedSize) }; option.Mods.Add(file, fData); } } // Okay modpack is now created internally, just need to save it. var progressIndicator = new Progress <double>(ReportProgressAdv); await texToolsModPack.CreateWizardModPack(wizardData, progressIndicator, true); FlexibleMessageBox.Show(new Wpf32Window(this), "Modpack Created Successfully.", "Modpack Created", MessageBoxButtons.OK, MessageBoxIcon.Information); await UnlockUi(this); DialogResult = true; } catch (Exception ex) { FlexibleMessageBox.Show(new Wpf32Window(this), "An Error occured while creating the modpack.\n\n" + ex.Message, "Modpack Creation Error", MessageBoxButtons.OK, MessageBoxIcon.Error); await UnlockUi(this); } }
/// <summary> /// Gets the full IMC information for a given item /// </summary> /// <param name="item"></param> /// <param name="useSecondary">Determines if the SecondaryModelInfo should be used instead.(XivGear only)</param> /// <returns>The ImcData data</returns> public async Task <FullImcInfo> GetFullImcInfo(string path, IndexFile index = null, ModList modlist = null) { if (index == null) { var _index = new Index(_gameDirectory); index = await _index.GetIndexFile(IOUtil.GetDataFileFromPath(path), false, true); } var dat = new Dat(_gameDirectory); var imcOffset = index.Get8xDataOffset(path); if (imcOffset == 0) { throw new InvalidDataException($"Could not find offset for {path}"); } var imcByteData = await dat.GetType2Data(imcOffset, IOUtil.GetDataFileFromPath(path)); return(await Task.Run(() => { using (var br = new BinaryReader(new MemoryStream(imcByteData))) { var subsetCount = br.ReadInt16(); var identifier = br.ReadInt16(); var imcData = new FullImcInfo() { TypeIdentifier = (ImcType)identifier, DefaultSubset = new List <XivImc>(), SubsetList = new List <List <XivImc> >(subsetCount) }; //weapons and monsters do not have variant sets if (imcData.TypeIdentifier == ImcType.NonSet) { // This type uses the first short for both Variant and VFX. byte variant = br.ReadByte(); byte unknown = br.ReadByte(); ushort mask = br.ReadUInt16(); byte vfx = br.ReadByte(); byte anim = br.ReadByte(); imcData.DefaultSubset.Add(new XivImc { MaterialSet = variant, Decal = unknown, Mask = mask, Vfx = variant, Animation = anim }); for (var i = 0; i < subsetCount; i++) { variant = br.ReadByte(); unknown = br.ReadByte(); mask = br.ReadUInt16(); vfx = br.ReadByte(); anim = br.ReadByte(); var newEntry = new XivImc { MaterialSet = variant, Decal = unknown, Mask = mask, Vfx = vfx, Animation = anim }; var subset = new List <XivImc>() { newEntry }; imcData.SubsetList.Add(subset); } } else if (imcData.TypeIdentifier == ImcType.Set) { // Identifier used by Equipment. imcData.DefaultSubset = new List <XivImc>() { new XivImc { MaterialSet = br.ReadByte(), Decal = br.ReadByte(), Mask = br.ReadUInt16(), Vfx = br.ReadByte(), Animation = br.ReadByte() }, new XivImc { MaterialSet = br.ReadByte(), Decal = br.ReadByte(), Mask = br.ReadUInt16(), Vfx = br.ReadByte(), Animation = br.ReadByte() }, new XivImc { MaterialSet = br.ReadByte(), Decal = br.ReadByte(), Mask = br.ReadUInt16(), Vfx = br.ReadByte(), Animation = br.ReadByte() }, new XivImc { MaterialSet = br.ReadByte(), Decal = br.ReadByte(), Mask = br.ReadUInt16(), Vfx = br.ReadByte(), Animation = br.ReadByte() }, new XivImc { MaterialSet = br.ReadByte(), Decal = br.ReadByte(), Mask = br.ReadUInt16(), Vfx = br.ReadByte(), Animation = br.ReadByte() }, }; for (var i = 0; i < subsetCount; i++) { // gets the data for each slot in the current variant set var imcGear = new List <XivImc>() { new XivImc { MaterialSet = br.ReadByte(), Decal = br.ReadByte(), Mask = br.ReadUInt16(), Vfx = br.ReadByte(), Animation = br.ReadByte() }, new XivImc { MaterialSet = br.ReadByte(), Decal = br.ReadByte(), Mask = br.ReadUInt16(), Vfx = br.ReadByte(), Animation = br.ReadByte() }, new XivImc { MaterialSet = br.ReadByte(), Decal = br.ReadByte(), Mask = br.ReadUInt16(), Vfx = br.ReadByte(), Animation = br.ReadByte() }, new XivImc { MaterialSet = br.ReadByte(), Decal = br.ReadByte(), Mask = br.ReadUInt16(), Vfx = br.ReadByte(), Animation = br.ReadByte() }, new XivImc { MaterialSet = br.ReadByte(), Decal = br.ReadByte(), Mask = br.ReadUInt16(), Vfx = br.ReadByte(), Animation = br.ReadByte() }, }; imcData.SubsetList.Add(imcGear); } } else { throw new NotSupportedException("Unknown IMC Type Identifier. (Please report this item in the TexTools Discord #bug_reports channel.)"); } return imcData; } })); }
/// <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); } }
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; } }
private async void ExtractButton_Click(object sender, RoutedEventArgs e) { if (String.IsNullOrWhiteSpace(FromBox.Text)) { return; } var path = FromBox.Text; var ext = Path.GetExtension(path); var _dat = new Dat(XivCache.GameInfo.GameDirectory); var _index = new Index(XivCache.GameInfo.GameDirectory); byte[] data = null; var sd = new SaveFileDialog(); if (ext.Length > 0) { ext = ext.Substring(1); sd.Filter = $"{ext.ToUpper()} Files (*.{ext})|*.{ext}"; } sd.FileName = Path.GetFileName(path); sd.RestoreDirectory = true; if (sd.ShowDialog() != System.Windows.Forms.DialogResult.OK) { return; } try { var offset = await _index.GetDataOffset(path); var df = IOUtil.GetDataFileFromPath(path); if (offset <= 0) { FlexibleMessageBox.Show("File does not exist.\n\nFile: " + path, "File Not Found", MessageBoxButtons.OK, MessageBoxIcon.Warning); return; } var type = _dat.GetFileType(offset, df); if (type < 2 || type > 4) { throw new InvalidDataException("Invalid or Unknown Data Type."); } var size = await _dat.GetCompressedFileSize(offset, df); if (type == 2) { if (DecompressType2Box.IsChecked == true) { data = await _dat.GetType2Data(offset, df); } else { data = _dat.GetRawData(offset, df, size); } } if (type == 3) { data = _dat.GetRawData(offset, df, size); } if (type == 4) { data = _dat.GetRawData(offset, df, size); } using (var stream = new BinaryWriter(sd.OpenFile())) { stream.Write(data); } FlexibleMessageBox.Show("Raw file extracted successfully to path:\n" + sd.FileName, "Extraction Success", MessageBoxButtons.OK, MessageBoxIcon.Information); this.Close(); } catch (Exception Ex) { FlexibleMessageBox.Show("Unable to decompress or read file:\n" + path + "\n\nError: " + Ex.Message, "File Not Found", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } }
public Gear(DirectoryInfo gameDirectory, XivLanguage xivLanguage) { _gameDirectory = gameDirectory; _xivLanguage = xivLanguage; _index = new Index(_gameDirectory); }
private async Task LoadItems() { List <string> children = new List <string>(); var root = _item.GetRoot(); if (root != null) { if (_level == XivDependencyLevel.Model) { children = await root.GetModelFiles(); } else if (_level == XivDependencyLevel.Material) { var imc = new Imc(XivCache.GameInfo.GameDirectory); try { var entry = await imc.GetImcInfo((IItemModel)_item); children = await root.GetMaterialFiles(entry.MaterialSet); } catch { if (root.Info.SecondaryType == XivItemType.hair || root.Info.SecondaryType == XivItemType.tail || (root.Info.PrimaryType == XivItemType.human && root.Info.SecondaryType == XivItemType.body)) { // These types don't have IMC entries, but have a material variant number. // Kind of weird, but whatever. children = await root.GetMaterialFiles(1); } else { children = await root.GetMaterialFiles(0); } } } else if (_level == XivDependencyLevel.Texture) { try { var imc = new Imc(XivCache.GameInfo.GameDirectory); var entry = await imc.GetImcInfo((IItemModel)_item); children = await root.GetTextureFiles(entry.MaterialSet); } catch { if (root.Info.SecondaryType == XivItemType.hair || root.Info.SecondaryType == XivItemType.tail || (root.Info.PrimaryType == XivItemType.human && root.Info.SecondaryType == XivItemType.body)) { // These types don't have IMC entries, but have a material variant number. // Kind of weird, but whatever. children = await root.GetTextureFiles(1); } else { children = await root.GetTextureFiles(0); } } } else { // Invalid or root, nothing listed. } } var index = new Index(XivCache.GameInfo.GameDirectory); foreach (var file in children) { var exists = await index.FileExists(file); if (!exists) { continue; } Files.Add(new FileEntry(file)); } }
/// <summary> /// Saves a set of IMC entries to file. /// </summary> /// <param name="path"></param> /// <param name="entries"></param> /// <returns></returns> internal async Task SaveEntries(string path, string slot, List <XivImc> entries, IItem referenceItem = null, IndexFile cachedIndexFile = null, ModList cachedModList = null) { var dat = new Dat(_gameDirectory); var index = new Index(_gameDirectory); var exists = await index.FileExists(path); FullImcInfo info; if (exists) { info = await GetFullImcInfo(path, cachedIndexFile, cachedModList); } else { var ri = XivDependencyGraph.ExtractRootInfo(path); if (ri.SecondaryType == null) { info = new FullImcInfo() { DefaultSubset = new List <XivImc>() { new XivImc(), new XivImc(), new XivImc(), new XivImc(), new XivImc() }, SubsetList = new List <List <XivImc> >(), TypeIdentifier = ImcType.Set }; } else { info = new FullImcInfo() { DefaultSubset = new List <XivImc>() { new XivImc() }, SubsetList = new List <List <XivImc> >(), TypeIdentifier = ImcType.NonSet }; } } for (int i = 0; i < entries.Count; i++) { XivImc e; if (i >= info.SubsetCount + 1) { e = new XivImc(); } else { e = info.GetEntry(i, slot); } e.Mask = entries[i].Mask; e.Decal = entries[i].Decal; e.Vfx = entries[i].Vfx; e.Animation = entries[i].Animation; e.MaterialSet = entries[i].MaterialSet; if (i >= info.SubsetCount + 1) { info.SetEntry(e, i, slot, true); } } // Save the modified info. await SaveFullImcInfo(info, path, Constants.InternalModSourceName, referenceItem, cachedIndexFile, cachedModList); }