private async Task <HashSet <string> > GetChildrenRecursive(string file) { var files = new HashSet <string>(); files.Add(file); var baseChildren = await XivCache.GetChildFiles(file); if (baseChildren == null || baseChildren.Count == 0) { // No children, just us. return(files); } else { // We have child files. foreach (var child in baseChildren) { // Recursively get their children. var children = await GetChildrenRecursive(child); foreach (var subchild in children) { // Add the results to the list. files.Add(subchild); } } } return(files); }
/// <summary> /// Deletes a file descriptor/stub from the Index files. /// </summary> /// <param name="fullPath">Full internal file path to the file that should be deleted.</param> /// <param name="dataFile">Which data file to use</param> /// <returns></returns> public async Task <bool> DeleteFileDescriptor(string fullPath, XivDataFile dataFile, bool updateCache = true) { await UpdateDataOffset(0, fullPath, false, true); // This is a metadata entry being deleted, we'll need to restore the metadata entries back to default. if (fullPath.EndsWith(".meta")) { var root = await XivCache.GetFirstRoot(fullPath); await ItemMetadata.RestoreDefaultMetadata(root); } if (fullPath.EndsWith(".rgsp")) { await CMP.RestoreDefaultScaling(fullPath); } if (updateCache) { // Queue us for updating, *after* updating the associated metadata files. XivCache.QueueDependencyUpdate(fullPath); } return(true); }
private async Task AddWithChildren(string file, IItem item, byte[] rawData = null) { var children = new HashSet<string>(); if (Path.GetExtension(file) == ".meta") { // If we're the root file, use the proper get all function // which will throw in the AVFX/ATEX stuff as well. var root = await XivCache.GetFirstRoot(file); if(root != null) { var files = await root.GetAllFiles(); children = files.ToHashSet(); } } else { children = await XivCache.GetChildrenRecursive(file); } foreach (var child in children) { var fe = new FileEntry() { Name = MakeFriendlyFileName(child), Path = child }; if (child == file && rawData != null) { await AddFile(fe, item, rawData); } else { await AddFile(fe, item); } } }
/// <summary> /// Creates the simple mod pack data list /// </summary> /// <returns>Task</returns> private async Task MakeSimpleDataList() { DirectoryInfo modListDirectory = new DirectoryInfo(Path.Combine(_gameDirectory.Parent.Parent.FullName, XivStrings.ModlistFilePath)); Modding modding = new Modding(_gameDirectory); this.ModList = modding.GetModList(); this.ParentsDictionary = XivCache.GetModListParents(); List <SimpleModpackEntry> entries = new List <SimpleModpackEntry>(); for (int i = 0; i < this.ModList.Mods.Count; i++) { SimpleModpackEntry entry = MakeEntry(i); if (entry == null) { continue; } entries.Add(entry); } await System.Windows.Application.Current.Dispatcher.InvokeAsync(() => { foreach (SimpleModpackEntry entry in entries) { this.Entries.Add(entry); } }); }
public void UpdateDependencyQueueCount(object sender, System.Timers.ElapsedEventArgs e) { var count = XivCache.GetDependencyQueueLength(); if (count > 0) { _mainWindow.ShowStatusMessage("Queue Length: " + count + ""); } }
/// <summary> /// Creates the simple mod pack data list /// </summary> /// <returns>Task</returns> private async Task MakeSimpleDataList() { DirectoryInfo modListDirectory = new DirectoryInfo(Path.Combine(_gameDirectory.Parent.Parent.FullName, XivStrings.ModlistFilePath)); Modding modding = new Modding(_gameDirectory); this.ModList = modding.GetModList(); // Don't show or list internal mods at all in this menu. this.ModList.Mods.RemoveAll(x => x.IsInternal()); // Rip through the mod list and get the correct raw compressed sizes for all the mods. var _dat = new Dat(XivCache.GameInfo.GameDirectory); foreach (var mod in ModList.Mods) { var compressedSize = mod.data.modSize; try { compressedSize = await _dat.GetCompressedFileSize(mod.data.modOffset, IOUtil.GetDataFileFromPath(mod.fullPath)); mod.data.modSize = compressedSize; } catch { // If the calculation failed, just use the original size I guess? // The main way this happens though is if the data is broken, so maybe we should error? // Though there's possibly filetypes from other framework applications in here that we don't know how to measure? } } this.ParentsDictionary = XivCache.GetModListParents(); List <SimpleModpackEntry> entries = new List <SimpleModpackEntry>(); for (int i = 0; i < this.ModList.Mods.Count; i++) { SimpleModpackEntry entry = MakeEntry(i); if (entry == null) { continue; } entries.Add(entry); } await System.Windows.Application.Current.Dispatcher.InvokeAsync(() => { foreach (SimpleModpackEntry entry in entries) { this.Entries.Add(entry); } }); }
private void TreeRefreshRequested(object sender, EventArgs e) { IProgress <(int current, string category)> progress = new Progress <(int current, string category)>((prog) => { if (prog.category == "Done") { ProgressBarVisible = Visibility.Collapsed; ProgressLabelVisible = Visibility.Collapsed; } else { ProgressValue = prog.current; ProgressLabel = $"Loading {prog.category} List"; } }); _mainWindow.ItemSearchTextBox.IsEnabled = false; var gameDirectory = new DirectoryInfo(Settings.Default.FFXIV_Directory); var lang = XivLanguages.GetXivLanguage(Settings.Default.Application_Language); try { // Settings are valid, application is updated, initialize the // Cache once so it can test if it needs to be updated as well. var _cache = new XivCache(gameDirectory, lang); FillTree(progress).GetAwaiter().OnCompleted(OnTreeRefreshCompleted); } catch (Exception ex) { // Revert to English when there were errors while loading the item tree/cache // and the game language was set to Chinese or Korean (they have separate clients) if (lang == XivLanguage.Chinese || lang == XivLanguage.Korean) { if (FlexibleMessageBox.Show(UIMessages.LanguageError, UIMessages.LanguageErrorTitle, MessageBoxButtons.OK, MessageBoxIcon.Error) == DialogResult.OK) { Properties.Settings.Default.Application_Language = "en"; Properties.Settings.Default.Save(); System.Windows.Forms.Application.Restart(); System.Windows.Application.Current.Shutdown(); } } else { // Make this error not totally silent at least. FlexibleMessageBox.Show("An error occurred while trying to populate the item list.", "Item list Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } }
private async Task CheckCache() { var gameDirectory = new DirectoryInfo(Settings.Default.FFXIV_Directory); var lang = XivLanguages.GetXivLanguage(Settings.Default.Application_Language); await Task.Run(async() => { var _cache = new XivCache(gameDirectory, lang); _cache.RebuildCache(); }); AddText("\tCache Rebuilt Successfully.", textColor); AddText("\t\u2714\n", "Green"); }
/// <summary> /// Retrieves the base racial or body skeleton for a given model file, parsing it from the base /// game files to generate it, if necessary. /// </summary> /// <param name="fullMdlPath"></param> /// <returns></returns> public static async Task <string> GetBaseSkeletonFile(string fullMdlPath) { var root = await XivCache.GetFirstRoot(fullMdlPath); var race = XivRace.All_Races; if (root.Info.PrimaryType == XivItemType.human || root.Info.PrimaryType == XivItemType.equipment || root.Info.PrimaryType == XivItemType.accessory) { race = XivRaces.GetXivRace(fullMdlPath.Substring(1, 4)); } return(await GetBaseSkeletonFile(root.Info, race)); }
bool CheckGameDirectory() { if (MainClass._indexDirectory == null) { string configGameDirectory = config.ReadConfig("GameDirectory"); MainClass._gameDirectory = new DirectoryInfo(Path.Combine(configGameDirectory, "game")); MainClass._indexDirectory = new DirectoryInfo(Path.Combine(configGameDirectory, "game", "sqpack", "ffxiv")); } if (MainClass._indexDirectory == null || !validation.ValidateDirectory(MainClass._indexDirectory, "GameDirectory")) { main.PrintMessage("Invalid game directory", 2); return(false); } XivCache.SetGameInfo(MainClass._indexDirectory, XivLanguage.English); return(true); }
/// <summary> /// Retrieves the Ex skeleton file for a given model file, parsing it from the base /// game files to generate it, if necessary. /// </summary> /// <param name="fullMdlPath"></param> /// <returns></returns> public static async Task <string> GetExtraSkeletonFile(string fullMdlPath) { var root = await XivCache.GetFirstRoot(fullMdlPath); var estType = Est.GetEstType(root); if (estType == Est.EstType.Invalid) { return(null); } // This is a hair/hat/face/body model at this point so this is a safe pull. var race = XivRaces.GetXivRace(fullMdlPath.Substring(1, 4)); return(await GetExtraSkeletonFile(root.Info, race)); }
private static async Task RunInternal(string inputFilePath) { try { await MainWin.LockUi("Exporting Anamnesis Character", "....", LockObj); MainWin.LockProgress.Report("Loading chara file..."); name = Path.GetFileNameWithoutExtension(inputFilePath); // read chara file string json = File.ReadAllText(inputFilePath); character = JsonConvert.DeserializeObject <CharacterFile>(json); XivRace race = character.GetXivRace(); // load all items we can MainWin.LockProgress.Report("Loading items..."); allItems = await XivCache.GetFullItemList(); // Body await Export("Face", GetFaceModel(race, character.Head, character.Eyebrows, character.Eyes, character.Nose, character.Jaw, character.Mouth), race); await Export("EarsTail", GetEarsTailModel(race, character.TailEarsType), race); await Export("Hair", GetHairModel(race, character.Hair), race); await Export("Head", GetItemModel(character.HeadGear, "Head"), race); await Export("Body", GetItemModel(character.Body, "Body"), race); await Export("Hands", GetItemModel(character.Hands, "Hands"), race); await Export("Legs", GetItemModel(character.Legs, "Legs"), race); await Export("Feet", GetItemModel(character.Feet, "Feet"), race); await Export("Earring", GetItemModel(character.Ears, "Earring"), race); await Export("Neck", GetItemModel(character.Neck, "Neck"), race); await Export("Wrists", GetItemModel(character.Wrists, "Wrists"), race); await Export("L Ring", GetItemModel(character.LeftRing, "Rings"), race); await Export("R ring", GetItemModel(character.RightRing, "Rings"), race); await Export("Weapon Main", GetWeaponModel(character.MainHand, true), race); await Export("Weapon Off", GetWeaponModel(character.OffHand, false), race); } catch (Exception ex) { MessageBox.Show(ex.Message, "Error running export"); } finally { await MainWin.UnlockUi(LockObj); } }
private async Task AsyncInit() { // Get this model's root. _root = await XivCache.GetFirstRoot(_oldModel.Source); if (_root != null && _root.Info.PrimaryType != XivItemType.indoor && _root.Info.PrimaryType != XivItemType.outdoor) { // Get all the materials in this root, and add them to the selectable list. var materials = await _root.GetMaterialFiles(); foreach (var m in materials) { var mName = Path.GetFileName(m); mName = "/" + mName; RootMaterials.Add(mName); } RootMaterials = RootMaterials.OrderBy(x => x).ToHashSet(); } UpdateModelSizeWarning(); UpdateMaterialsList(); _view.MeshNumberBox.SelectionChanged += MeshNumberBox_SelectionChanged; _view.PartNumberBox.SelectionChanged += PartNumberBox_SelectionChanged; _view.MaterialSelectorBox.SelectionChanged += MaterialSelectorBox_SelectionChanged; _view.MaterialPathTextBox.KeyDown += MaterialPathTextBox_KeyDown; _view.MaterialPathTextBox.LostFocus += MaterialPathTextBox_LostFocus; _view.ShapesListBox.SelectionChanged += ShapesListBox_SelectionChanged; _view.AttributesListBox.SelectionChanged += AttributesListBox_SelectionChanged; _view.RemoveShapeButton.Click += RemoveShapeButton_Click; _view.RemoveAttributeButton.Click += RemoveAttributeButton_Click; _view.AddAttributeBox.SelectionChanged += AddAttributeBox_SelectionChanged; _view.AddAttributeTextBox.KeyDown += AddAttributeTextBox_KeyDown; _view.ScaleComboBox.SelectionChanged += ScaleComboBox_SelectionChanged; _view.MeshNumberBox.SelectedIndex = 0; }
static void Main(string[] args) { ReadConfig(); var lang = XivLanguage.English; _gameDir = new DirectoryInfo(_gamePath); XivCache.SetGameInfo(_gameDir, XivLanguage.English); _ttObj = new Obj(_gameDir); _gear = new Gear(_gameDir, lang); _companions = new Companions(_gameDir, lang); _housing = new Housing(_gameDir, lang); BatchExport(); Console.WriteLine("Done"); Console.ReadKey(); }
private async Task LoadFiles() { var parentFiles = _entry.MainFiles; var files = new SortedSet <string>(); if (_entry.Level == XivDependencyLevel.Root) { var root = await XivCache.GetFirstRoot(_entry.MainFiles[0]); files = await root.GetAllFiles(); } else { foreach (var file in parentFiles) { var children = await XivCache.GetChildrenRecursive(file); foreach (var child in children) { files.Add(child); } } } _entry.AllFiles.Clear(); foreach (var file in files) { var exists = await AddFile(file); if (exists) { _entry.AllFiles.Add(file); } } UpdateCounts(); ConfirmButton.IsEnabled = true; }
bool CheckGameDirectory() { if (MainClass._indexDirectory == null) { string configGameDirectory = config.ReadConfig("GameDirectory"); MainClass._gameDirectory = new DirectoryInfo(Path.Combine(configGameDirectory, "game")); MainClass._indexDirectory = new DirectoryInfo(Path.Combine(configGameDirectory, "game", "sqpack", "ffxiv")); } if (MainClass._indexDirectory == null || !validation.ValidateDirectory(MainClass._indexDirectory, "GameDirectory")) { main.PrintMessage("Invalid game directory", 2); return(false); } if (!validation.ValidateCache()) { File.Delete(Path.Combine(MainClass._gameDirectory.FullName, "mod_cache.db")); File.Delete(Path.Combine(MainClass._gameDirectory.FullName, "item_sets.db")); } XivCache.SetGameInfo(MainClass._indexDirectory, XivLanguage.English); XivCache.CacheWorkerEnabled = false; return(true); }
/// <summary> /// Initializes the Cache and loads the item tree for the first time when done. /// </summary> /// <returns></returns> private async Task InitializeCache() { var gameDir = new DirectoryInfo(Properties.Settings.Default.FFXIV_Directory); var lang = XivLanguages.GetXivLanguage(Properties.Settings.Default.Application_Language); await LockUi(UIStrings.Updating_Cache, UIStrings.Updating_Cache_Message, this); // Kick this in a new thread because the cache call will lock up the one it's on if it has to do a rebuild. await Task.Run(async() => { bool cacheOK = true; try { // If the cache needs to be rebuilt, this will synchronously block until it is done. XivCache.SetGameInfo(gameDir, lang); } catch (Exception ex) { cacheOK = false; FlexibleMessageBox.Show("An error occurred while attempting to rebuild the cache.\n" + ex.Message, "Cache Rebuild Error.", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1); } await Dispatcher.Invoke(async() => { await UnlockUi(); if (cacheOK) { await RefreshTree(); } if (InitialLoadComplete != null) { InitialLoadComplete.Invoke(this, null); } }); }); }
private async void CopyButton_Click(object sender, RoutedEventArgs e) { var to = ToBox.Text; var from = FromBox.Text; try { if (string.IsNullOrWhiteSpace(to) || string.IsNullOrWhiteSpace(from)) { return; } to = to.Trim().ToLower(); from = from.Trim().ToLower(); if (!to.EndsWith(".mdl") || !from.EndsWith(".mdl")) { return; } var toRoot = await XivCache.GetFirstRoot(to); var fromRoot = await XivCache.GetFirstRoot(from); var df = IOUtil.GetDataFileFromPath(to); var _mdl = new Mdl(XivCache.GameInfo.GameDirectory, df); await _mdl.CopyModel(from, to, XivStrings.TexTools, true); FlexibleMessageBox.Show("Model Copied Successfully.", "Model Copy Confirmation", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Information); Close(); } catch (Exception ex) { FlexibleMessageBox.Show("Model Copied Failed.\n\nError: " + ex.Message, "Model Copy Error", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error); } }
/// <summary> /// Gets the list of all Housing Items /// </summary> /// <returns>A list of XivFurniture objects containing housing items</returns> public async Task <List <XivFurniture> > GetFurnitureList(string substring = null) { return(await XivCache.GetCachedFurnitureList(substring)); }
public async Task <List <XivUi> > GetUIList() { var cache = new XivCache(_gameDirectory, _xivLanguage); return(await cache.GetCachedUiList()); }
/// <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); } }
/// <summary> /// Builds the set tree from XivCache.GetAllRoots(). /// </summary> private void BuildSetTree() { // First we must generate all the dependency root nodes. var roots = XivCache.GetAllRootsDictionary(); var primaryTypeGroups = new Dictionary <XivItemType, ItemTreeElement>(); var primaryIdGroups = new Dictionary <XivItemType, Dictionary <int, ItemTreeElement> >(); var secondaryTypeGroups = new Dictionary <XivItemType, Dictionary <int, Dictionary <XivItemType, ItemTreeElement> > >(); var secondaryIdGroups = new Dictionary <XivItemType, Dictionary <int, Dictionary <XivItemType, Dictionary <int, ItemTreeElement> > > >(); // This giant for loop monstrosity builds the actual root nodes based on the dictionaries returned by XivCache.GetAllRoots() foreach (var kvPrimaryType in roots) { var primaryType = kvPrimaryType.Key; // Create the new node. primaryTypeGroups.Add(primaryType, new ItemTreeElement(null, null, XivItemTypes.NiceNames[primaryType], true)); // Add us to parent. SetElements.Add(primaryTypeGroups[primaryType]); // Ensure the other lists have our primary type reference. primaryIdGroups.Add(primaryType, new Dictionary <int, ItemTreeElement>()); secondaryTypeGroups.Add(primaryType, new Dictionary <int, Dictionary <XivItemType, ItemTreeElement> >()); secondaryIdGroups.Add(primaryType, new Dictionary <int, Dictionary <XivItemType, Dictionary <int, ItemTreeElement> > >()); foreach (var kvPrimaryId in kvPrimaryType.Value) { var primaryId = kvPrimaryId.Key; // Create the new node. primaryIdGroups[primaryType].Add(primaryId, new ItemTreeElement(null, primaryTypeGroups[primaryType], XivItemTypes.GetSystemPrefix(primaryType) + primaryId.ToString().PadLeft(4, '0'), true)); // Add us to parent. primaryTypeGroups[primaryType].Children.Add(primaryIdGroups[primaryType][primaryId]); // Ensure the other lists have our primary id reference. secondaryTypeGroups[primaryType].Add(primaryId, new Dictionary <XivItemType, ItemTreeElement>()); secondaryIdGroups[primaryType].Add(primaryId, new Dictionary <XivItemType, Dictionary <int, ItemTreeElement> >()); foreach (var kvSecondaryType in kvPrimaryId.Value) { var secondaryType = kvSecondaryType.Key; if (secondaryType != XivItemType.none) { // Create the new node. secondaryTypeGroups[primaryType][primaryId].Add(secondaryType, new ItemTreeElement(null, primaryIdGroups[primaryType][primaryId], XivItemTypes.NiceNames[secondaryType], true)); // Add us to parent. primaryIdGroups[primaryType][primaryId].Children.Add(secondaryTypeGroups[primaryType][primaryId][secondaryType]); // Ensure the other lists have our secondary type reference. secondaryIdGroups[primaryType][primaryId].Add(secondaryType, new Dictionary <int, ItemTreeElement>()); } foreach (var kvSecondaryId in kvSecondaryType.Value) { var secondaryId = kvSecondaryId.Key; if (secondaryType != XivItemType.none) { // Create the new node. secondaryIdGroups[primaryType][primaryId][secondaryType].Add(secondaryId, new ItemTreeElement(null, secondaryTypeGroups[primaryType][primaryId][secondaryType], XivItemTypes.GetSystemPrefix(secondaryType) + secondaryId.ToString().PadLeft(4, '0'), true)); // Add us to parent. secondaryTypeGroups[primaryType][primaryId][secondaryType].Children.Add(secondaryIdGroups[primaryType][primaryId][secondaryType][secondaryId]); } foreach (var kvSlot in kvSecondaryId.Value) { var root = kvSlot.Value; var slotName = Mdl.SlotAbbreviationDictionary.FirstOrDefault(x => x.Value == root.Slot).Key; if (secondaryType != XivItemType.none) { // This root has no slots, just list the parent as the root element. if (String.IsNullOrWhiteSpace(slotName)) { DependencyRootNodes.Add(root.ToString(), secondaryIdGroups[primaryType][primaryId][secondaryType][secondaryId]); break; } // Create the new node. var elem = new ItemTreeElement(null, secondaryIdGroups[primaryType][primaryId][secondaryType][secondaryId], slotName); // Add us to parent. secondaryIdGroups[primaryType][primaryId][secondaryType][secondaryId].Children.Add(elem); // Save us to the primary listing so the items can list themselves under us. DependencyRootNodes.Add(root.ToString(), elem); } else { // This root has no slots, just list the parent as the root element. if (String.IsNullOrWhiteSpace(slotName)) { DependencyRootNodes.Add(root.ToString(), primaryIdGroups[primaryType][primaryId]); break; } // Create the new node. var elem = new ItemTreeElement(null, primaryIdGroups[primaryType][primaryId], slotName); // Add us to parent. primaryIdGroups[primaryType][primaryId].Children.Add(elem); // Save us to the primary listing so the items can list themselves under us. DependencyRootNodes.Add(root.ToString(), elem); } } } } } } }
public Task PerformStartOver(DirectoryInfo backupsDirectory, IProgress <string> progress = null, XivLanguage language = XivLanguage.None) { return(Task.Run(async() => { progress?.Report("Deleting mods..."); var modding = new Modding(_gameDirectory); var backupsRestored = false; try { // Try to restore the index entries to their original values by deleting any files added by TexTools // and setting mods to disabled await modding.DeleteAllFilesAddedByTexTools(); await modding.ToggleAllMods(false); progress?.Report("Restoring index file backups..."); } catch { // If an exception occurred due to a corrupted modlist which couldn't be deserealized restore the backup index // files by force backupsRestored = await RestoreBackups(backupsDirectory); if (!backupsRestored) { throw new Exception("Start Over Failed: Index backups missing/outdated."); } } finally { // If no exception occured, restore the backups anyway just to be safe but don't throw an exception if it fails // due to outdated or missing backups since setting back the original index values should be enough hopefully if (!backupsRestored) { backupsRestored = await RestoreBackups(backupsDirectory); // If backups were not restored that means they were missing/outdated so try to make new backups now if (!backupsRestored) { try { await BackupIndexFiles(backupsDirectory); } catch (Exception ex) { throw new Exception("Start Over Failed: Failed to update outdated backups.\n\n" + ex.Message); } } } progress?.Report("Deleting modded dat files..."); var dat = new Dat(_gameDirectory); // Delete modded dat files foreach (var xivDataFile in (XivDataFile[])Enum.GetValues(typeof(XivDataFile))) { var datFiles = await dat.GetModdedDatList(xivDataFile); foreach (var datFile in datFiles) { File.Delete(datFile); } if (datFiles.Count > 0) { await RepairIndexDatCounts(xivDataFile); } } progress?.Report("Cleaning up mod list..."); var modListDirectory = new DirectoryInfo(Path.Combine(_gameDirectory.Parent.Parent.FullName, XivStrings.ModlistFilePath)); // Delete mod list File.Delete(modListDirectory.FullName); modding.CreateModlist(); progress?.Report("Rebuilding Cache..."); await Task.Run(async() => { var _cache = new XivCache(_gameDirectory, language); _cache.RebuildCache(); }); } })); }
public async Task SaveMulti() { var _imc = new Imc(XivCache.GameInfo.GameDirectory); var _index = new Index(XivCache.GameInfo.GameDirectory); // Get tokenized map info structs. // This will let us set them in the new Materials and // Detokenize them using the new paths. var mapInfos = _material.GetAllMapInfos(true); // Shader info likewise will be pumped into each new material. var shaderInfo = _material.GetShaderInfo(); // Add new Materials for shared model items. var oldMaterialIdentifier = _material.GetMaterialIdentifier(); var oldMtrlName = Path.GetFileName(_material.MTRLPath); // Ordering these by name ensures that we create textures for the new variants in the first // item alphabetically, just for consistency's sake. var sameModelItems = (await _item.GetSharedModelItems()).OrderBy(x => x.Name, new ItemNameComparer()); var oldVariantString = "/v" + _material.GetVariant().ToString().PadLeft(4, '0') + '/'; var modifiedVariants = new List <int>(); var mtrlReplacementRegex = "_" + oldMaterialIdentifier + ".mtrl"; var mtrlReplacementRegexResult = "_" + _newMaterialIdentifier + ".mtrl"; if (_mode == MaterialEditorMode.NewRace) { mtrlReplacementRegexResult = mtrlReplacementRegex; } var newMtrlName = oldMtrlName.Replace(mtrlReplacementRegex, mtrlReplacementRegexResult); var root = _item.GetRootInfo(); var imcEntries = new List <XivImc>(); var materialSets = new HashSet <byte>(); try { var imcInfo = await _imc.GetFullImcInfo(_item); imcEntries = imcInfo.GetAllEntries(root.Slot, true); materialSets = imcEntries.Select(x => x.MaterialSet).ToHashSet(); } catch { // Item doesn't use IMC entries, and thus only has a single variant. materialSets.Clear(); materialSets.Add(0); } // We need to save our non-existent base material once before we can continue. if (_mode == MaterialEditorMode.NewRace) { await _mtrl.ImportMtrl(_material, _item, XivStrings.TexTools); } var count = 0; var allItems = (await root.ToFullRoot().GetAllItems()); var matNumToItems = new Dictionary <int, List <IItemModel> >(); foreach (var i in allItems) { if (imcEntries.Count <= i.ModelInfo.ImcSubsetID) { continue; } var matSet = imcEntries[i.ModelInfo.ImcSubsetID].MaterialSet; if (!matNumToItems.ContainsKey(matSet)) { matNumToItems.Add(matSet, new List <IItemModel>()); } var saveItem = i; if (typeof(XivCharacter) == i.GetType()) { var temp = (XivCharacter)((XivCharacter)_item).Clone(); temp.Name = saveItem.SecondaryCategory; saveItem = temp; } matNumToItems[matSet].Add(saveItem); } var keys = matNumToItems.Keys.ToList(); foreach (var key in keys) { var list = matNumToItems[key]; matNumToItems[key] = list.OrderBy(x => x.Name, new ItemNameComparer()).ToList(); } // Load and modify all the MTRLs. foreach (var materialSetId in materialSets) { var variantPath = _mtrl.GetMtrlFolder(root, materialSetId); var oldMaterialPath = variantPath + "/" + oldMtrlName; var newMaterialPath = variantPath + "/" + newMtrlName; // Don't create materials for set 0. (SE sets the material ID to 0 when that particular set-slot doesn't actually exist as an item) if (materialSetId == 0 && imcEntries.Count > 0) { continue; } var dxVersion = 11; XivMtrl itemXivMtrl; // Get mtrl path if (await _index.FileExists(oldMaterialPath)) { // Use the Material from this variant as a base? itemXivMtrl = await _mtrl.GetMtrlData(_item, oldMaterialPath, dxVersion); } else { itemXivMtrl = await _mtrl.GetMtrlData(_item, _material.MTRLPath, dxVersion); } // If we're an item that doesn't use IMC variants, make sure we don't accidentally move the material around. if (materialSetId != 0) { // Shift the MTRL to the new variant folder. itemXivMtrl.MTRLPath = Regex.Replace(itemXivMtrl.MTRLPath, oldVariantString, "/v" + materialSetId.ToString().PadLeft(4, '0') + "/"); } if (_mode == MaterialEditorMode.NewMulti) { // Change the MTRL part identifier. itemXivMtrl.MTRLPath = Regex.Replace(itemXivMtrl.MTRLPath, mtrlReplacementRegex, mtrlReplacementRegexResult); } // Load the Shader Settings itemXivMtrl.SetShaderInfo(shaderInfo, true); // Loop our tokenized map infos and pump them back in // using the new modified material to detokenize them. foreach (var info in mapInfos) { itemXivMtrl.SetMapInfo(info.Usage, (MapInfo)info.Clone()); } IItem item; try { item = matNumToItems[materialSetId].First(); } catch { item = (await XivCache.GetFirstRoot(itemXivMtrl.MTRLPath)).GetFirstItem(); } count++; // Write the new Material await _mtrl.ImportMtrl(itemXivMtrl, item, XivStrings.TexTools); _view.SaveStatusLabel.Content = "Updated " + count + "/" + materialSets.Count + " Material Sets..."; } }
public Task PerformStartOver(DirectoryInfo backupsDirectory, IProgress <string> progress = null, XivLanguage language = XivLanguage.None) { return(Task.Run(async() => { var modding = new Modding(_gameDirectory); var backupsRestored = false; // Stop the cache worker since we're blowing up the entire index file and db anyways. // The cache rebuild will start it up again after the cache is rebuilt. XivCache.CacheWorkerEnabled = false; try { // Try restoring the indexes FIRST. backupsRestored = await RestoreBackups(backupsDirectory); progress?.Report("Restoring index file backups..."); if (!backupsRestored) { throw new Exception("Start Over Failed: Index backups missing/outdated."); } } catch (Exception ex) { try { // If the index restore failed, try just disabling. await modding.DeleteAllFilesAddedByTexTools(); await modding.ToggleAllMods(false); progress?.Report("Index restore failed, attempting to delete all mods instead..."); } catch { throw new Exception("Start Over Failed: Index Backups Invalid and Unable to Disable all mods."); } } finally { progress?.Report("Deleting modded dat files..."); var dat = new Dat(_gameDirectory); // Delete modded dat files foreach (var xivDataFile in (XivDataFile[])Enum.GetValues(typeof(XivDataFile))) { var datFiles = await dat.GetModdedDatList(xivDataFile); foreach (var datFile in datFiles) { File.Delete(datFile); } if (datFiles.Count > 0) { await RepairIndexDatCounts(xivDataFile); } } progress?.Report("Cleaning up mod list..."); var modListDirectory = new DirectoryInfo(Path.Combine(_gameDirectory.Parent.Parent.FullName, XivStrings.ModlistFilePath)); // Delete mod list File.Delete(modListDirectory.FullName); modding.CreateModlist(); progress?.Report("Rebuilding Cache..."); await Task.Run(async() => { XivCache.RebuildCache(); }); } })); }
/// <summary> /// Creates texture data ready to be imported into the DATs from an external file. /// If format is not specified, either the incoming file's DDS format is used (DDS files), /// or the existing internal file's DDS format is used. /// </summary> /// <param name="internalPath"></param> /// <param name="externalPath"></param> /// <param name="texFormat"></param> /// <returns></returns> public async Task <byte[]> MakeTexData(string internalPath, string externalPath, XivTexFormat texFormat = XivTexFormat.INVALID) { // Ensure file exists. if (!File.Exists(externalPath)) { throw new IOException($"Could not find file: {externalPath}"); } var root = await XivCache.GetFirstRoot(internalPath); bool isDds = Path.GetExtension(externalPath).ToLower() == ".dds"; var ddsContainer = new DDSContainer(); try { // If no format was specified... if (texFormat == XivTexFormat.INVALID) { if (isDds) { // If we're importing a DDS file, get the format from the incoming DDS file using (var fs = new FileStream(externalPath, FileMode.Open)) { using (var sr = new BinaryReader(fs)) { texFormat = GetDDSTexFormat(sr); } } } else { // Otherwise use the current internal format. var xivt = await _dat.GetType4Data(internalPath, false); texFormat = xivt.TextureFormat; } } // Check if the texture being imported has been imported before CompressionFormat compressionFormat = CompressionFormat.BGRA; switch (texFormat) { case XivTexFormat.DXT1: compressionFormat = CompressionFormat.BC1a; break; case XivTexFormat.DXT5: compressionFormat = CompressionFormat.BC3; break; case XivTexFormat.A8R8G8B8: compressionFormat = CompressionFormat.BGRA; break; default: if (!isDds) { throw new Exception($"Format {texFormat} is not currently supported for BMP import\n\nPlease use the DDS import option instead."); } break; } if (!isDds) { using (var surface = Surface.LoadFromFile(externalPath)) { if (surface == null) { throw new FormatException($"Unsupported texture format"); } surface.FlipVertically(); var maxMipCount = 1; if (root != null) { // For things that have real roots (things that have actual models/aren't UI textures), we always want mipMaps, even if the existing texture only has one. // (Ex. The Default Mat-Add textures) maxMipCount = -1; } using (var compressor = new Compressor()) { // UI/Paintings only have a single mipmap and will crash if more are generated, for everything else generate max levels compressor.Input.SetMipmapGeneration(true, maxMipCount); compressor.Input.SetData(surface); compressor.Compression.Format = compressionFormat; compressor.Compression.SetBGRAPixelFormat(); compressor.Process(out ddsContainer); } } } // If we're not a DDS, write the DDS to file temporarily. var ddsFilePath = externalPath; if (!isDds) { var tempFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + ".dds"); ddsContainer.Write(tempFile, DDSFlags.None); ddsFilePath = tempFile; } using (var br = new BinaryReader(File.OpenRead(ddsFilePath))) { br.BaseStream.Seek(12, SeekOrigin.Begin); var newHeight = br.ReadInt32(); var newWidth = br.ReadInt32(); br.ReadBytes(8); var newMipCount = br.ReadInt32(); if (newHeight % 2 != 0 || newWidth % 2 != 0) { throw new Exception("Resolution must be a multiple of 2"); } br.BaseStream.Seek(80, SeekOrigin.Begin); var textureFlags = br.ReadInt32(); var texType = br.ReadInt32(); var uncompressedLength = (int)new FileInfo(ddsFilePath).Length - 128; var newTex = new List <byte>(); if (!internalPath.Contains(".atex")) { var DDSInfo = await DDS.ReadDDS(br, texFormat, newWidth, newHeight, newMipCount); newTex.AddRange(_dat.MakeType4DatHeader(texFormat, DDSInfo.mipPartOffsets, DDSInfo.mipPartCounts, (int)uncompressedLength, newMipCount, newWidth, newHeight)); newTex.AddRange(MakeTextureInfoHeader(texFormat, newWidth, newHeight, newMipCount)); newTex.AddRange(DDSInfo.compressedDDS); return(newTex.ToArray()); } else { br.BaseStream.Seek(128, SeekOrigin.Begin); newTex.AddRange(MakeTextureInfoHeader(texFormat, newWidth, newHeight, newMipCount)); newTex.AddRange(br.ReadBytes((int)uncompressedLength)); var data = await _dat.CreateType2Data(newTex.ToArray()); return(data); } } } finally { ddsContainer.Dispose(); } }
private async void AnyTextChanged(object sender, TextChangedEventArgs e) { var to = ToBox.Text; var from = FromBox.Text; if (string.IsNullOrWhiteSpace(to) || string.IsNullOrWhiteSpace(from)) { CopyButton.IsEnabled = false; return; } to = to.Trim().ToLower(); from = from.Trim().ToLower(); if (!to.EndsWith(".mdl") || !from.EndsWith(".mdl")) { MaterialCopyNotice.Text = "--"; MaterialCopyNotice.Foreground = Brushes.Black; RaceChangeNotice.Text = "--"; RaceChangeNotice.Foreground = Brushes.Black; CopyButton.IsEnabled = false; return; } try { var df = IOUtil.GetDataFileFromPath(to); var df2 = IOUtil.GetDataFileFromPath(from); if (df != df2) { MaterialCopyNotice.Text = "Source and target files must exist within the same data file."; MaterialCopyNotice.Foreground = Brushes.Red; RaceChangeNotice.Text = "--"; RaceChangeNotice.Foreground = Brushes.Black; CopyButton.IsEnabled = false; return; } } catch { MaterialCopyNotice.Text = "At least one file path is not a valid internal FFXIV file path."; MaterialCopyNotice.Foreground = Brushes.Red; RaceChangeNotice.Text = "--"; RaceChangeNotice.Foreground = Brushes.Black; CopyButton.IsEnabled = false; return; } CopyButton.IsEnabled = true; var toRoot = await XivCache.GetFirstRoot(to); var fromRoot = await XivCache.GetFirstRoot(from); if (toRoot == null || fromRoot == null) { MaterialCopyNotice.Text = "Unknown File Root - Materials and textures will not be copied."; MaterialCopyNotice.Foreground = Brushes.DarkGoldenrod; RaceChangeNotice.Text = "Unknown File Root - Model will not be racially adjusted."; RaceChangeNotice.Foreground = Brushes.DarkGoldenrod; return; } else { MaterialCopyNotice.Text = "Materials and textures will be copied to destination root folder."; MaterialCopyNotice.Foreground = Brushes.Green; } var raceRegex = new Regex("c([0-9]{4})"); var toMatch = raceRegex.Match(to); var fromMatch = raceRegex.Match(from); if (!toMatch.Success || !fromMatch.Success) { RaceChangeNotice.Text = "Model is not racial - Model will not be racially adjusted."; RaceChangeNotice.Foreground = Brushes.Black; return; } var toRace = XivRaces.GetXivRace(toMatch.Groups[1].Value); var fromRace = XivRaces.GetXivRace(fromMatch.Groups[1].Value); if (toRace == fromRace) { RaceChangeNotice.Text = "Model races are identical - Model will not be racially adjusted."; RaceChangeNotice.Foreground = Brushes.Black; return; } RaceChangeNotice.Text = "Model will be adjusted from " + fromRace.GetDisplayName() + " to " + toRace.GetDisplayName() + "."; RaceChangeNotice.Foreground = Brushes.Green; }
private async Task <List <IItem> > BuildCategoryTree() { foreach (var kv in _categoryStructure) { // Make the top level node. var e = new ItemTreeElement(null, null, kv.Key); foreach (var secondary in kv.Value) { var e2 = new ItemTreeElement(e, null, secondary); e.Children.Add(e2); } CategoryElements.Add(e); } var gameDir = XivCache.GameInfo.GameDirectory; var language = XivCache.GameInfo.GameLanguage; var items = await XivCache.GetFullItemList(); foreach (var item in items) { // Find what node we should be attached to. ItemTreeElement catParent = null; var topLevel = CategoryElements.FirstOrDefault(x => x.DisplayName == item.PrimaryCategory); if (topLevel == null) { topLevel = new ItemTreeElement(null, null, item.PrimaryCategory); CategoryElements.Add(topLevel); } var secondLevel = topLevel.Children.FirstOrDefault(x => x.DisplayName == item.SecondaryCategory); if (secondLevel == null) { if (item.SecondaryCategory == item.Name) { // These are a special snowflake case. secondLevel = topLevel; } else { secondLevel = new ItemTreeElement(topLevel, null, item.SecondaryCategory); topLevel.Children.Add(secondLevel); } } catParent = secondLevel; ItemTreeElement setParent = null; // Try and see if we have a valid root parent to attach to in the sets tree. try { var type = item.GetType(); // Perf. Much faster to just not test those types at all, as we know they won't resolve. if (type != typeof(XivUi)) { var itemRoot = item.GetRootInfo(); if (itemRoot.PrimaryType != XivItemType.unknown) { var st = itemRoot.ToString(); if (DependencyRootNodes.ContainsKey(st)) { setParent = DependencyRootNodes[st]; } } } } catch (Exception ex) { throw; } var e2 = new ItemTreeElement(catParent, setParent, item); if (catParent != null) { catParent.Children.Add(e2); } if (setParent != null) { setParent.Children.Add(e2); } } return(items); }
public async Task <List <XivGear> > GetGearList(string substring = null) { return(await XivCache.GetCachedGearList(substring)); }
/// <summary> /// This should only really be called directly if the control was created with DeferLoading set to true. /// </summary> /// <returns></returns> public async Task LoadItems() { if (_READY) { SearchTimer.Stop(); SearchTimer.Dispose(); ClearSelection(); } if (LockUiFunction != null) { await LockUiFunction(UIStrings.Loading_Items, null, this); } // Pump us into another thread so the UI stays nice and fresh. await Task.Run(async() => { CategoryElements = new ObservableCollection <ItemTreeElement>(); SetElements = new ObservableCollection <ItemTreeElement>(); DependencyRootNodes = new Dictionary <string, ItemTreeElement>(); try { // Gotta build set tree first, so the items from the item list can latch onto the nodes there. BuildSetTree(); await BuildCategoryTree(); } catch (Exception ex) { FlexibleMessageBox.Show("An error occurred while loading the item list.\n" + ex.Message, "Item List Error", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Warning); return; } var toAdd = new List <(ItemTreeElement parent, ItemTreeElement child)>(); foreach (var kv in DependencyRootNodes) { // This dependency root had no EXD-Items associated with it. // Gotta make a generic item for it. if (kv.Value.Children.Count == 0) { // See if we can actually turn this root into a fully fledged item. try { var root = await XivCache.GetFirstRoot(kv.Key); if (root != null) { // If we can, add it into the list. var item = root.ToRawItem(); var e = new ItemTreeElement(null, kv.Value, item); toAdd.Add((kv.Value, e)); } else { var e = new ItemTreeElement(null, kv.Value, "[Unsupported]"); toAdd.Add((kv.Value, e)); } } catch (Exception ex) { throw; } } } // Loop back through to add the new items, so we're not affecting the previous iteration. foreach (var tup in toAdd) { tup.parent.Children.Add(tup.child); } }); var view = (CollectionView)CollectionViewSource.GetDefaultView(CategoryElements); view.Filter = SearchFilter; view = (CollectionView)CollectionViewSource.GetDefaultView(SetElements); view.Filter = SearchFilter; SearchTimer = new Timer(300); SearchTimer.Elapsed += Search; CategoryTree.ItemsSource = CategoryElements; SetTree.ItemsSource = SetElements; _READY = true; Search(this, null); if (UnlockUiFunction != null) { await UnlockUiFunction(this); } if (ItemsLoaded != null) { ItemsLoaded.Invoke(this, null); } }