From 861e0aebf5bf0ce5b2e61b190328cd4a106d8497 Mon Sep 17 00:00:00 2001 From: Demeter Dzadik Date: Sat, 1 Mar 2025 01:06:33 +0100 Subject: [PATCH 1/2] Batch Animation Export See PR: https://github.com/KillzXGaming/Switch-Toolbox/pull/700 --- BrawlboxHelper/BrawlboxHelper.dll | Bin 22016 -> 22016 bytes .../Syroot.NintenTools.NSW.Bfres.dll | Bin 259584 -> 259072 bytes .../BFRES/Bfres Structs/SubFiles/FSKA.cs | 168 +++++++++++++----- .../NodeWrappers/FileFilters.cs | 5 +- .../Forms/Custom/Treeview/TreeViewCustom.cs | 15 +- .../Generics/STGenericWrapper.cs | 23 ++- Toolbox/MainForm.Designer.cs | 14 +- Toolbox/MainForm.cs | 106 +++++++++-- Toolbox/Toolbox.csproj | 8 + 9 files changed, 274 insertions(+), 65 deletions(-) diff --git a/BrawlboxHelper/BrawlboxHelper.dll b/BrawlboxHelper/BrawlboxHelper.dll index ea6ec97fdce88ac1d85bc8e0b6a5fb4979e738f3..1ef41b39e4d03cb281690017ec91c903f22f4da0 100644 GIT binary patch delta 321 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 TziIq(T0_UH;s<4Ga`68ECY&Jh delta 19726 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 FileFilters.GetFilter(typeof(FSKA),null, true); public override string ReplaceFilter => FileFilters.GetFilter(typeof(FSKA)); - public override void Export(string FileName) + public override void Export(string FilePath) { - string ext = Utils.GetExtension(FileName); + Export(FilePath, null); + } + + public void Export(string FilePath, STSkeleton backupSkeleton) + { + Console.WriteLine($"Anim Export: {FilePath}"); + string sourcePath = ""; + if (SkeletalAnimU != null) + sourcePath = SkeletalAnimU.Path; + else if (SkeletalAnim != null) + sourcePath = SkeletalAnim.Path; + + string ext = Utils.GetExtension(FilePath); if (ext == ".bfska") { if (GetResFileU() != null) { - SkeletalAnimU.Export(FileName, GetResFileU()); + SkeletalAnimU.Export(FilePath, GetResFileU()); } else { - SkeletalAnim.Export(FileName, GetResFile()); + SkeletalAnim.Export(FilePath, GetResFile()); } } else if (ext == ".json") { if (SkeletalAnimU != null) - System.IO.File.WriteAllText(FileName, Newtonsoft.Json.JsonConvert.SerializeObject(SkeletalAnimU, + System.IO.File.WriteAllText(FilePath, Newtonsoft.Json.JsonConvert.SerializeObject(SkeletalAnimU, Newtonsoft.Json.Formatting.Indented)); else - System.IO.File.WriteAllText(FileName, Newtonsoft.Json.JsonConvert.SerializeObject(SkeletalAnim, + System.IO.File.WriteAllText(FilePath, Newtonsoft.Json.JsonConvert.SerializeObject(SkeletalAnim, Newtonsoft.Json.Formatting.Indented)); } + else if (ext == ".chr0") + { + if (SkeletalAnimU != null) + BrawlboxHelper.FSKAConverter.Fska2Chr0(BfresPlatformConverter.FSKAConvertWiiUToSwitch(SkeletalAnimU), FilePath); + else + BrawlboxHelper.FSKAConverter.Fska2Chr0(SkeletalAnim, FilePath); + } else { + // The export functions of the following formats require a skeleton. + // Try to find best matching skeleton in the visible viewport skeletons, + // or the animation's nodegroup, if it's in the nodetree (when this function + // is called by right click->Export) STSkeleton skeleton = GetActiveSkeleton(); if (skeleton == null) - throw new Exception("No skeleton found to assign! Make sure a model is open in the viewport."); - - if (ext == ".chr0") + skeleton = backupSkeleton; // When this func is called from Tools->Batch Export Animations, backupSkeleton is the one from the .sbfres. + if (skeleton == null) { - if (SkeletalAnimU != null) - BrawlboxHelper.FSKAConverter.Fska2Chr0(BfresPlatformConverter.FSKAConvertWiiUToSwitch(SkeletalAnimU), FileName); - else - BrawlboxHelper.FSKAConverter.Fska2Chr0(SkeletalAnim, FileName); + throw new Exception($"{Text} No skeleton found.\n Load a skeleton in the viewport with a skeleton matching the animations."); } - else if (ext == ".smd") - SMD.Save(this, skeleton, FileName); + var missingBones = new HashSet(); + foreach (KeyNode boneAnim in Bones) + { + STBone bone = skeleton.GetBone(boneAnim.Text); + if (bone == null) + { + missingBones.Add(boneAnim.Text); + } + } + + if (ext == ".smd") + SMD.Save(this, skeleton, FilePath); else if (ext == ".anim") - ANIM.CreateANIM(FileName, this, skeleton); + ANIM.CreateANIM(FilePath, this, skeleton); else if (ext == ".seanim") - SEANIM.SaveAnimation(FileName, this, skeleton); + { + SEANIM.SaveAnimation(FilePath, this, skeleton); + } + + if (missingBones.Count > 0) + throw new Exception($"{Text}: Discarded animation of {missingBones.Count} bones:\n {string.Join("\n ", missingBones)}\n Load a skeleton in the viewport with a skeleton matching the animations."); } } private STSkeleton GetActiveSkeleton() { + STSkeleton perfectSkeleton = GetPerfectMatchSkeleton_InViewport(); + if (perfectSkeleton != null) + return perfectSkeleton; + + perfectSkeleton = GetPerfectMatchSkeleton_InOwnNodeGroup(); + if (perfectSkeleton != null) + return perfectSkeleton; + + //If those didn't return, resort to the first model of the nodegroup, + //even though it's an imperfect match. if (Parent == null) return null; - //Check parent renderer and find skeleton var render = ((BFRES)Parent.Parent.Parent).BFRESRender; - if (render != null) - { - //Return individual skeleton for single model files - if (render.models.Count == 1) - return render.models[0].Skeleton; + if (render != null && render.models.Count == 1) + return render.models[0].Skeleton; - //Search multiple FMDL to find matching bones - foreach (var model in render.models) - { - //Check if all the bones in the animation are present in the skeleton - bool areAllBonesPresent = model.Skeleton.bones.Count > 0; - foreach (var bone in Bones) - { - var animBone = model.Skeleton.GetBone(bone.Text); + return null; + } - if (animBone == null) - areAllBonesPresent = false; - } - if (areAllBonesPresent) - return model.Skeleton; - } + public STSkeleton GetPerfectMatchSkeleton_InViewport() + { + //Try to find a skeleton matching this animation out of visible viewport skeletons. + var viewport = LibraryGUI.GetActiveViewport(); + if (viewport == null) + return null; - //If not all bones were present but models are present, use first model - if (render.models.Count > 0) - return render.models[0].Skeleton; - } + var containers = viewport.GetActiveContainers(); + if (containers == null || containers.Count == 0) + return null; - //Search by viewport active model in the event the animation is externally loaded - var viewport = LibraryGUI.GetActiveViewport(); - if (viewport != null) + var skeletons = new List(); + + foreach (var container in containers) { - foreach (var drawable in viewport.scene.objects) + foreach (var drawable in container.Drawables) { if (drawable is STSkeleton) - return ((STSkeleton)drawable); + skeletons.Add((STSkeleton)drawable); + } + } + + return GetPerfectMatchSkeleton(skeletons); + } + + public STSkeleton GetPerfectMatchSkeleton_InOwnNodeGroup() + { + if (Parent == null) + return null; + + //Check parent renderer and find skeleton + var render = ((BFRES)Parent.Parent.Parent).BFRESRender; + if (render == null) + return null; + + var skeletons = (from model in render.models select (STSkeleton)model.Skeleton).ToList(); + + STSkeleton bestMatchSkeleton = GetPerfectMatchSkeleton(skeletons); + if (bestMatchSkeleton != null) + return bestMatchSkeleton; + + return null; + } + + public STSkeleton GetPerfectMatchSkeleton(List skeletons) + { + //Search multiple FMDL to find matching bones + foreach (var skeleton in skeletons) + { + //Check if all the bones in the animation are present in the skeleton + bool areAllBonesPresent = skeleton.bones.Count > 0; + foreach (var bone in Bones) + { + var animBone = skeleton.GetBone(bone.Text); + + if (animBone == null) + { + areAllBonesPresent = false; + break; + } } + if (areAllBonesPresent) + return skeleton; } return null; diff --git a/File_Format_Library/NodeWrappers/FileFilters.cs b/File_Format_Library/NodeWrappers/FileFilters.cs index e68e2e917..1c040f24e 100644 --- a/File_Format_Library/NodeWrappers/FileFilters.cs +++ b/File_Format_Library/NodeWrappers/FileFilters.cs @@ -21,8 +21,8 @@ public class FileFilters public static string BONE = GetFilter(".bfbon"); public static string FMAT = GetFilter(".bfmat"); - public static string FSKA_EXPORT = GetFilter(".bfska", ".anim", ".seanim", ".smd", ".chr0"); - public static string FSKA_REPLACE = GetFilter(".bfska",".anim", ".seanim", ".smd", ".chr0"); + public static string FSKA_EXPORT = GetFilter(".seanim", ".smd", ".anim", ".bfska", ".chr0", ".json"); + public static string FSKA_REPLACE = GetFilter(".seanim", ".smd", ".anim", ".bfska", ".chr0"); public static string FMAA = GetFilter(".bfmaa",".yaml", ".gif"); @@ -142,6 +142,7 @@ public static Dictionary GetDescription(string[] extensions) case ".yaml": filters.Add(ext, "Yet Another Markup Language"); break; case ".gif": filters.Add(ext, "Graphics Interchange Format"); break; case ".cmdl": filters.Add(ext, "CTR Model"); break; + case ".json": filters.Add(ext, "JavaScript Object Notation"); break; default: filters.Add(ext, ""); break; } diff --git a/Switch_Toolbox_Library/Forms/Custom/Treeview/TreeViewCustom.cs b/Switch_Toolbox_Library/Forms/Custom/Treeview/TreeViewCustom.cs index e52e2724c..797790df3 100644 --- a/Switch_Toolbox_Library/Forms/Custom/Treeview/TreeViewCustom.cs +++ b/Switch_Toolbox_Library/Forms/Custom/Treeview/TreeViewCustom.cs @@ -22,9 +22,22 @@ public virtual void OnExpand() { } public TreeNodeCustom() { } + public TreeNodeCustom RootNode + { + get + { + TreeNodeCustom currentNode = this; + while (currentNode.Parent != null) + { + currentNode = currentNode.Parent as TreeNodeCustom; + } + + return currentNode; + } + } } - public class TreeNodeFile : TreeNodeCustom +public class TreeNodeFile : TreeNodeCustom { public bool CanDelete { diff --git a/Switch_Toolbox_Library/Generics/STGenericWrapper.cs b/Switch_Toolbox_Library/Generics/STGenericWrapper.cs index 82b369c29..bc53809df 100644 --- a/Switch_Toolbox_Library/Generics/STGenericWrapper.cs +++ b/Switch_Toolbox_Library/Generics/STGenericWrapper.cs @@ -7,6 +7,7 @@ using System.Windows.Forms; using Toolbox.Library; using Toolbox.Library.Forms; +using System.IO; namespace Toolbox.Library.NodeWrappers { @@ -146,21 +147,35 @@ protected void ExportAllAction(object sender, EventArgs e) FolderSelectDialog sfd = new FolderSelectDialog(); if (sfd.ShowDialog() == DialogResult.OK) { - string folderPath = sfd.SelectedPath; - BatchFormatExport form = new BatchFormatExport(Formats); if (form.ShowDialog() == DialogResult.OK) { + string folderPath = $"{sfd.SelectedPath}\\{RootNode.Text}"; + Directory.CreateDirectory(folderPath); + string extension = form.GetSelectedExtension(); extension.Replace(" ", string.Empty); + var failedExports = new List(); + foreach (TreeNode node in Nodes) { - if (node is STGenericWrapper) + try + { + if (node is STGenericWrapper) + { + ((STGenericWrapper)node).Export($"{folderPath}\\{node.Text}{extension}"); + } + } catch (Exception ex) { - ((STGenericWrapper)node).Export($"{folderPath}\\{node.Text}{extension}"); + failedExports.Add(ex.Message); } + } + + if (failedExports.Count > 0) + STErrorDialog.Show("Files exported with warnings.", "Switch Toolbox", string.Join("\n", failedExports)); + } } } diff --git a/Toolbox/MainForm.Designer.cs b/Toolbox/MainForm.Designer.cs index 2871bd050..49a147075 100644 --- a/Toolbox/MainForm.Designer.cs +++ b/Toolbox/MainForm.Designer.cs @@ -46,6 +46,7 @@ private void InitializeComponent() this.batchToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.hashCalculatorToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.batchExportTexturesAllSupportedFormatsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.batchExportAnimationsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.batchExportModelsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.batchReplaceFTPToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.batchRenameBNTXToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); @@ -195,6 +196,7 @@ private void InitializeComponent() this.batchToolStripMenuItem, this.hashCalculatorToolStripMenuItem, this.batchExportTexturesAllSupportedFormatsToolStripMenuItem, + this.batchExportAnimationsToolStripMenuItem, this.batchExportModelsToolStripMenuItem, this.batchReplaceFTPToolStripMenuItem, this.batchReplaceTXTGToolStripMenuItem, @@ -227,9 +229,18 @@ private void InitializeComponent() // this.batchExportTexturesAllSupportedFormatsToolStripMenuItem.Name = "batchExportTexturesAllSupportedFormatsToolStripMenuItem"; this.batchExportTexturesAllSupportedFormatsToolStripMenuItem.Size = new System.Drawing.Size(316, 22); - this.batchExportTexturesAllSupportedFormatsToolStripMenuItem.Text = "Batch Export Textures (All Supported Formats)"; + this.batchExportTexturesAllSupportedFormatsToolStripMenuItem.Text = "Batch Export Textures"; this.batchExportTexturesAllSupportedFormatsToolStripMenuItem.Click += new System.EventHandler(this.batchExportTexturesAllSupportedFormatsToolStripMenuItem_Click); // + + // batchExportAnimationsToolStripMenuItem + // + this.batchExportAnimationsToolStripMenuItem.Name = "batchExportAnimationsToolStripMenuItem"; + this.batchExportAnimationsToolStripMenuItem.Size = new System.Drawing.Size(316, 22); + this.batchExportAnimationsToolStripMenuItem.Text = "Batch Export Animations"; + this.batchExportAnimationsToolStripMenuItem.Click += new System.EventHandler(this.batchExportAnimationsToolStripMenuItem_Click); + // + // batchExportModelsToolStripMenuItem // this.batchExportModelsToolStripMenuItem.Name = "batchExportModelsToolStripMenuItem"; @@ -616,6 +627,7 @@ private void InitializeComponent() private System.Windows.Forms.ToolStripMenuItem batchToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem hashCalculatorToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem batchExportTexturesAllSupportedFormatsToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem batchExportAnimationsToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem batchExportModelsToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem batchReplaceFTPToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem batchReplaceTXTGToolStripMenuItem; diff --git a/Toolbox/MainForm.cs b/Toolbox/MainForm.cs index 58a940298..fa6d04a6e 100644 --- a/Toolbox/MainForm.cs +++ b/Toolbox/MainForm.cs @@ -1410,6 +1410,21 @@ private void batchExportTexturesAllSupportedFormatsToolStripMenuItem_Click(objec } } + private void batchExportAnimationsToolStripMenuItem_Click(object sender, EventArgs e) + { + OpenFileDialog ofd = new OpenFileDialog(); + ofd.Multiselect = true; + + if (ofd.ShowDialog() == DialogResult.OK) + { + FolderSelectDialog folderDlg = new FolderSelectDialog(); + if (folderDlg.ShowDialog() == DialogResult.OK) + { + BatchExportAnimations(ofd.FileNames, folderDlg.SelectedPath); + } + } + } + private void batchExportModelsToolStripMenuItem_Click(object sender, EventArgs e) { @@ -1550,14 +1565,15 @@ private void batchRenameBNTXToolStripMenuItem_Click(object sender, EventArgs e) ObjectEditor.Update(); } - private List failedFiles = new List(); + private List failedImports = new List(); + private List failedExports = new List(); private List batchExportFileList = new List(); private void BatchExportModels(string[] files, string outputFolder) { List Formats = new List(); Formats.Add("DAE (.dae)"); - failedFiles = new List(); + failedImports = new List(); BatchFormatExport form = new BatchFormatExport(Formats); if (form.ShowDialog() == DialogResult.OK) @@ -1572,7 +1588,7 @@ private void BatchExportModels(string[] files, string outputFolder) } catch (Exception ex) { - failedFiles.Add($"{file} \n Error:\n {ex} \n"); + failedImports.Add($"{file} \n Error:\n {ex} \n"); } SearchFileFormat(form.BatchSettings, fileFormat, extension, outputFolder, ExportMode.Models); @@ -1582,10 +1598,10 @@ private void BatchExportModels(string[] files, string outputFolder) else return; - if (failedFiles.Count > 0) + if (failedImports.Count > 0) { string detailList = ""; - foreach (var file in failedFiles) + foreach (var file in failedImports) detailList += $"{file}\n"; STErrorDialog.Show("Some files failed to export! See detail list of failed files.", "Switch Toolbox", detailList); @@ -1604,7 +1620,7 @@ private void BatchExportTextures(string[] files, string outputFolder) Formats.Add("Tagged Image File Format (.tiff)"); Formats.Add("ASTC (.astc)"); - failedFiles = new List(); + failedImports = new List(); BatchFormatExport form = new BatchFormatExport(Formats); if (form.ShowDialog() == DialogResult.OK) @@ -1615,11 +1631,11 @@ private void BatchExportTextures(string[] files, string outputFolder) IFileFormat fileFormat = null; try { - fileFormat = STFileLoader.OpenFileFormat(file); + fileFormat = STFileLoader.OpenFileFormat(file); } catch (Exception ex) { - failedFiles.Add($"{file} \n Error:\n {ex} \n"); + failedImports.Add($"{file} \n Error:\n {ex} \n"); } SearchFileFormat(form.BatchSettings, fileFormat, extension, outputFolder, ExportMode.Textures); @@ -1627,10 +1643,10 @@ private void BatchExportTextures(string[] files, string outputFolder) batchExportFileList.Clear(); } - if (failedFiles.Count > 0) + if (failedImports.Count > 0) { string detailList = ""; - foreach (var file in failedFiles) + foreach (var file in failedImports) detailList += $"{file}\n"; STErrorDialog.Show("Some files failed to export! See detail list of failed files.", "Switch Toolbox", detailList); @@ -1639,6 +1655,49 @@ private void BatchExportTextures(string[] files, string outputFolder) MessageBox.Show("Files batched successfully!"); } + + private void BatchExportAnimations(string[] files, string outputFolder) + { + List formats = FileFilters.GetFilter(typeof(FSKA), null, true) + .Split('|') + .Where(format => !format.StartsWith("*") && !format.StartsWith("All")) + .ToList(); + + failedImports = new List(); + failedExports = new List(); + + BatchFormatExport form = new BatchFormatExport(formats); + if (form.ShowDialog() == DialogResult.OK) + { + string extension = form.GetSelectedExtension().Substring(1); + foreach (var file in files) + { + IFileFormat fileFormat = null; + try + { + fileFormat = STFileLoader.OpenFileFormat(file); + } + catch (Exception ex) + { + failedImports.Add($"{file} \n Error:\n {ex} \n"); + } + + SearchFileFormat(form.BatchSettings, fileFormat, extension, outputFolder, ExportMode.Animations); + } + batchExportFileList.Clear(); + } else + { + return; + } + + if (failedImports.Count > 0) + STErrorDialog.Show("Files imported with warnings.", "Switch Toolbox: Failed Imports", string.Join("\n", failedImports)); + else if (failedExports.Count > 0) + STErrorDialog.Show("Files exported with warnings.", "Switch Toolbox: Failed Exports", string.Join("\n", failedExports)); + else + MessageBox.Show("All files batch-processed successfully!"); + } + private void SearchFileFormat(BatchFormatExport.Settings settings, IFileFormat fileFormat, string extension, string outputFolder, ExportMode exportMode) { @@ -1666,6 +1725,30 @@ private void SearchFileFormat(BatchFormatExport.Settings settings, IFileFormat f ExportTexture(tex, settings, $"{outputFolder}/{tex.Text}", extension); } } + else if (exportMode == ExportMode.Animations) + { + var bfres = fileFormat as BFRES; + string path = Path.Combine(outputFolder, bfres.Text); + Directory.CreateDirectory(path); + foreach (var elem in bfres.resFileU.SkeletalAnims) + { + string name = elem.Key; + var skelanim = elem.Value; + FSKA exportableAnim = new FSKA(skelanim); + // It matters for .seanim and .smd export what skeleton is used for the animation. + // We pass the skeleton included with the .bfres file here, so later code can + // use it as a backup in case no matching skeleton was found in the viewport. + STSkeleton backupSkeleton = null; + if (bfres.BFRESRender.models.Count > 0) + backupSkeleton = bfres.BFRESRender.models[0].Skeleton; + try + { + exportableAnim.Export($"{path}/{name}.{extension}", backupSkeleton); + } catch (Exception ex) { + failedExports.Add($"\n{Path.GetFileName(bfres.FilePath)} -> {ex.Message}"); + } + } + } else if (fileFormat is IExportableModelContainer && exportMode == ExportMode.Models) { string name = fileFormat.FileName.Split('.').FirstOrDefault(); @@ -1722,6 +1805,7 @@ public enum ExportMode { Models, Textures, + Animations, } private void ExportTexture(STGenericTexture tex, BatchFormatExport.Settings settings, string filePath, string ext) { @@ -1751,7 +1835,7 @@ private void SearchArchive(BatchFormatExport.Settings settings, IArchiveFile arc } catch (Exception ex) { - failedFiles.Add($"{file} \n Error:\n {ex} \n"); + failedImports.Add($"{file} \n Error:\n {ex} \n"); } } } diff --git a/Toolbox/Toolbox.csproj b/Toolbox/Toolbox.csproj index 615398eaa..63bd4cc27 100644 --- a/Toolbox/Toolbox.csproj +++ b/Toolbox/Toolbox.csproj @@ -73,6 +73,14 @@ Lib\OpenTK.GLControl.dll False + + False + Lib\Syroot.NintenTools.Bfres.dll + + + False + Lib\Syroot.NintenTools.NSW.Bfres.dll + From ae3dd12a232e6cdd478f46463d068b1d017da2a1 Mon Sep 17 00:00:00 2001 From: Demeter Dzadik Date: Fri, 21 Mar 2025 20:07:43 +0100 Subject: [PATCH 2/2] Fix Monk Maz Koshia not exporting even with best skeleton Even his best matching skeleton has a single bone that isn't a perfect match, so in this case we should fall back to using an imperfect skeleton that is in the viewport. --- BrawlboxHelper/BrawlboxHelper.dll | Bin 22016 -> 22016 bytes .../BFRES/Bfres Structs/SubFiles/FSKA.cs | 17 +++++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/BrawlboxHelper/BrawlboxHelper.dll b/BrawlboxHelper/BrawlboxHelper.dll index 1ef41b39e4d03cb281690017ec91c903f22f4da0..73ab6d900c297840602ce342eba5b183beeee89d 100644 GIT binary patch delta 48 zcmV-00MGw`tO0AlM@FHb@Ae3WQe(3e GN9+#A{1Vy# delta 48 zcmZoz!`QHfaY6@6gH`p{jXfo%EO(UcYZkMZ2?}g3I-MONdCb4(NAJ?WKoyD2BJOYa E0O$x4yZ`_I diff --git a/File_Format_Library/FileFormats/BFRES/Bfres Structs/SubFiles/FSKA.cs b/File_Format_Library/FileFormats/BFRES/Bfres Structs/SubFiles/FSKA.cs index 077d1fced..bf2f69ff5 100644 --- a/File_Format_Library/FileFormats/BFRES/Bfres Structs/SubFiles/FSKA.cs +++ b/File_Format_Library/FileFormats/BFRES/Bfres Structs/SubFiles/FSKA.cs @@ -262,12 +262,18 @@ private STSkeleton GetActiveSkeleton() if (render != null && render.models.Count == 1) return render.models[0].Skeleton; + // If even that didn't work, resort to just WHATEVER THE HECK is loaded in the viewport. + var skeletons = GetViewportSkeletons(); + if (skeletons.Count > 0) + { + return skeletons[0]; + } + return null; } - public STSkeleton GetPerfectMatchSkeleton_InViewport() + public List GetViewportSkeletons() { - //Try to find a skeleton matching this animation out of visible viewport skeletons. var viewport = LibraryGUI.GetActiveViewport(); if (viewport == null) return null; @@ -287,6 +293,13 @@ public STSkeleton GetPerfectMatchSkeleton_InViewport() } } + return skeletons; + } + + public STSkeleton GetPerfectMatchSkeleton_InViewport() + { + //Try to find a skeleton matching this animation out of visible viewport skeletons. + var skeletons = GetViewportSkeletons(); return GetPerfectMatchSkeleton(skeletons); }