From 0066d673f45cfd07bbca4343e2fdf27660698c17 Mon Sep 17 00:00:00 2001 From: VaDiM Date: Sun, 25 Jan 2026 10:12:10 +0300 Subject: [PATCH] Some refactor for Live2D code --- AssetStudioCLI/Components/L2DAssetManager.cs | 116 ++++++++ AssetStudioCLI/Studio.cs | 220 +++++---------- AssetStudioGUI/AssetStudioGUIForm.cs | 14 +- AssetStudioGUI/Components/L2DAssetManager.cs | 123 +++++++++ AssetStudioGUI/Studio.cs | 276 +++++++------------ 5 files changed, 402 insertions(+), 347 deletions(-) create mode 100644 AssetStudioCLI/Components/L2DAssetManager.cs create mode 100644 AssetStudioGUI/Components/L2DAssetManager.cs diff --git a/AssetStudioCLI/Components/L2DAssetManager.cs b/AssetStudioCLI/Components/L2DAssetManager.cs new file mode 100644 index 0000000..00243bb --- /dev/null +++ b/AssetStudioCLI/Components/L2DAssetManager.cs @@ -0,0 +1,116 @@ +using AssetStudio; +using AssetStudio.Addons.CubismLive2D; +using System.Collections.Generic; +using System.Collections.Specialized; +using static CubismLive2DExtractor.CubismParsers; + +namespace AssetStudioCLI +{ + public class L2DAssetManager + { + public Dictionary ModelDict = new Dictionary(); + + public L2DAssetManager () { } + + public void BindCubismAssetsByGO(GameObject m_GameObject, AssemblyLoader assemblyLoader) + { + if (!TryGetCubismMoc(m_GameObject.CubismModel.CubismModelMono, assemblyLoader, out var mocMono)) + return; + + ModelDict[mocMono] = m_GameObject.CubismModel; + BindAnimationClips(m_GameObject); + } + + public void BindCubismAssetsByMono(MonoBehaviour m_MonoBehaviour, string m_ClassName) + { + if (!m_MonoBehaviour.m_GameObject.TryGet(out var m_GameObject)) + return; + + if (!TryGetModelGameObject(m_GameObject.m_Transform, out var modelGameObject)) + return; + + switch (m_ClassName) + { + case "CubismMoc": + if (!ModelDict.ContainsKey(m_MonoBehaviour)) + { + ModelDict.Add(m_MonoBehaviour, null); + } + break; + case "CubismRenderer": + modelGameObject.CubismModel.RenderTextureList.Add(m_MonoBehaviour); + break; + case "CubismDisplayInfoParameterName": + modelGameObject.CubismModel.ParamDisplayInfoList.Add(m_MonoBehaviour); + break; + case "CubismDisplayInfoPartName": + modelGameObject.CubismModel.PartDisplayInfoList.Add(m_MonoBehaviour); + break; + case "CubismPosePart": + modelGameObject.CubismModel.PosePartList.Add(m_MonoBehaviour); + break; + } + } + + private static bool TryGetCubismMoc(MonoBehaviour m_MonoBehaviour, AssemblyLoader assemblyLoader, out MonoBehaviour mocMono) + { + mocMono = null; + var pptrDict = (OrderedDictionary)ParseMonoBehaviour(m_MonoBehaviour, CubismMonoBehaviourType.Model, assemblyLoader)?["_moc"]; + if (pptrDict == null) + return false; + + var mocPPtr = new PPtr + { + m_FileID = (int)pptrDict["m_FileID"], + m_PathID = (long)pptrDict["m_PathID"], + AssetsFile = m_MonoBehaviour.assetsFile + }; + return mocPPtr.TryGet(out mocMono); + } + + private static void BindAnimationClips(GameObject gameObject) + { + if (gameObject.m_Animator == null || gameObject.m_Animator.m_Controller.IsNull) + return; + + if (!gameObject.m_Animator.m_Controller.TryGet(out var controller)) + return; + + AnimatorController animatorController; + if (controller is AnimatorOverrideController overrideController) + { + if (!overrideController.m_Controller.TryGet(out animatorController)) + return; + } + else + { + animatorController = (AnimatorController)controller; + } + + foreach (var clipPptr in animatorController.m_AnimationClips) + { + if (clipPptr.TryGet(out var m_AnimationClip)) + { + gameObject.CubismModel.ClipMotionList.Add(m_AnimationClip); + } + } + } + + private static bool TryGetModelGameObject(Transform m_Transform, out GameObject m_GameObject) + { + m_GameObject = null; + if (m_Transform == null) + return false; + + while (m_Transform.m_Father.TryGet(out var m_Father)) + { + m_Transform = m_Father; + if (m_Transform.m_GameObject.TryGet(out m_GameObject) && m_GameObject.CubismModel != null) + { + return true; + } + } + return false; + } + } +} diff --git a/AssetStudioCLI/Studio.cs b/AssetStudioCLI/Studio.cs index ae4e255..98986b4 100644 --- a/AssetStudioCLI/Studio.cs +++ b/AssetStudioCLI/Studio.cs @@ -5,7 +5,6 @@ using CubismLive2DExtractor; using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Collections.Specialized; using System.IO; using System.Linq; using System.Text.RegularExpressions; @@ -13,7 +12,6 @@ using System.Threading; using System.Threading.Tasks; using System.Xml.Linq; using static AssetStudioCLI.Exporter; -using static CubismLive2DExtractor.CubismParsers; using Ansi = AssetStudio.ColorConsole; namespace AssetStudioCLI @@ -24,7 +22,7 @@ namespace AssetStudioCLI public static List parsedAssetsList = new List(); public static List gameObjectTree = new List(); public static AssemblyLoader assemblyLoader = new AssemblyLoader(); - public static Dictionary l2dModelDict = new Dictionary(); + public static L2DAssetManager l2dAssetManager = new L2DAssetManager(); private static Dictionary containers = new Dictionary(); static Studio() @@ -286,22 +284,11 @@ namespace AssetStudioCLI switch (m_Script.m_ClassName) { case "CubismMoc": - if (!l2dModelDict.ContainsKey(m_MonoBehaviour)) - { - l2dModelDict.Add(m_MonoBehaviour, null); - } - break; case "CubismRenderer": - BindCubismAsset(m_MonoBehaviour, CubismMonoBehaviourType.RenderTexture); - break; case "CubismDisplayInfoParameterName": - BindCubismAsset(m_MonoBehaviour, CubismMonoBehaviourType.DisplayInfo, isParamInfo: true); - break; case "CubismDisplayInfoPartName": - BindCubismAsset(m_MonoBehaviour, CubismMonoBehaviourType.DisplayInfo); - break; case "CubismPosePart": - BindCubismAsset(m_MonoBehaviour, CubismMonoBehaviourType.PosePart); + l2dAssetManager.BindCubismAssetsByMono(m_MonoBehaviour, m_Script.m_ClassName); break; } } @@ -309,10 +296,9 @@ namespace AssetStudioCLI break; case GameObject m_GameObject: assetItem.Text = m_GameObject.m_Name; - if (m_GameObject.CubismModel != null && TryGetCubismMoc(m_GameObject.CubismModel.CubismModelMono, out var mocMono)) + if (m_GameObject.CubismModel != null) { - l2dModelDict[mocMono] = m_GameObject.CubismModel; - BindAnimationClips(m_GameObject); + l2dAssetManager.BindCubismAssetsByGO(m_GameObject, assemblyLoader); } break; case Animator m_Animator: @@ -521,7 +507,7 @@ namespace AssetStudioCLI info += $"\n#\n# Total: {parsedAssetsList.Count} assets"; } - info += $"\n\n[Cubism Live2D]\n# Exportable Models: {l2dModelDict.Count}"; + info += $"\n\n[Cubism Live2D]\n# Exportable Models: {l2dAssetManager.ModelDict.Count}"; } else { @@ -999,97 +985,11 @@ namespace AssetStudioCLI Logger.Default.Log(LoggerEvent.Info, status, ignoreLevel: true); } - private static bool TryGetCubismMoc(MonoBehaviour m_MonoBehaviour, out MonoBehaviour mocMono) - { - mocMono = null; - var pptrDict = (OrderedDictionary)CubismParsers.ParseMonoBehaviour(m_MonoBehaviour, CubismMonoBehaviourType.Model, assemblyLoader)?["_moc"]; - if (pptrDict == null) - return false; - - var mocPPtr = new PPtr - { - m_FileID = (int)pptrDict["m_FileID"], - m_PathID = (long)pptrDict["m_PathID"], - AssetsFile = m_MonoBehaviour.assetsFile - }; - return mocPPtr.TryGet(out mocMono); - } - - private static void BindCubismAsset(MonoBehaviour m_MonoBehaviour, CubismMonoBehaviourType type, bool isParamInfo = false) - { - if (!m_MonoBehaviour.m_GameObject.TryGet(out var m_GameObject)) - return; - - if (!TryGetModelGameObject(m_GameObject.m_Transform, out var modelGameObject)) - return; - - switch (type) - { - case CubismMonoBehaviourType.PosePart: - modelGameObject.CubismModel.PosePartList.Add(m_MonoBehaviour); - break; - case CubismMonoBehaviourType.DisplayInfo when isParamInfo: - modelGameObject.CubismModel.ParamDisplayInfoList.Add(m_MonoBehaviour); - break; - case CubismMonoBehaviourType.DisplayInfo: - modelGameObject.CubismModel.PartDisplayInfoList.Add(m_MonoBehaviour); - break; - case CubismMonoBehaviourType.RenderTexture: - modelGameObject.CubismModel.RenderTextureList.Add(m_MonoBehaviour); - break; - } - } - - private static void BindAnimationClips(GameObject gameObject) - { - if (gameObject.m_Animator == null || gameObject.m_Animator.m_Controller.IsNull) - return; - - if (!gameObject.m_Animator.m_Controller.TryGet(out var controller)) - return; - - AnimatorController animatorController; - if (controller is AnimatorOverrideController overrideController) - { - if (!overrideController.m_Controller.TryGet(out animatorController)) - return; - } - else - { - animatorController = (AnimatorController)controller; - } - - foreach (var clipPptr in animatorController.m_AnimationClips) - { - if (clipPptr.TryGet(out var m_AnimationClip)) - { - gameObject.CubismModel.ClipMotionList.Add(m_AnimationClip); - } - } - } - - private static bool TryGetModelGameObject(Transform m_Transform, out GameObject m_GameObject) - { - m_GameObject = null; - if (m_Transform == null) - return false; - - while (m_Transform.m_Father.TryGet(out var m_Father)) - { - m_Transform = m_Father; - if (m_Transform.m_GameObject.TryGet(out m_GameObject) && m_GameObject.CubismModel != null) - { - return true; - } - } - return false; - } - private static Dictionary GenerateMocPathDict(Dictionary mocDict, bool searchByFilename) { var tempMocPathDict = new Dictionary(); var mocPathDict = new Dictionary(); - foreach (var mocMono in l2dModelDict.Keys) + foreach (var mocMono in l2dAssetManager.ModelDict.Keys) { if (containers.TryGetValue(mocMono, out var fullContainerPath)) { @@ -1130,61 +1030,18 @@ namespace AssetStudioCLI return mocPathDict; } - public static void ExportLive2D() + private static Dictionary> GenerateMocAssetDict(Dictionary mocDict) { - var baseDestPath = Path.Combine(CLIOptions.o_outputFolder.Value, "Live2DOutput"); - var motionMode = CLIOptions.o_l2dMotionMode.Value; - var forceBezier = CLIOptions.f_l2dForceBezier.Value; - var modelGroupOption = CLIOptions.o_l2dGroupOption.Value; var searchByFilename = CLIOptions.f_l2dAssetSearchByFilename.Value; - var mocDict = l2dModelDict; var l2dContainers = searchByFilename ? new Dictionary() : containers; - if (l2dModelDict.Count == 0) - { - Logger.Default.Log(LoggerEvent.Info, "Live2D Cubism models were not found.", ignoreLevel: true); - return; - } - - if (CLIOptions.filterBy == FilterBy.NameOrContainer) - { - var regexMode = CLIOptions.f_filterWithRegex.Value; - var filterList = CLIOptions.o_filterByText.Value; - var filteredDict = new Dictionary(); - var regex = regexMode ? new Regex(filterList[0]) : null; - foreach (var kvp in l2dModelDict) - { - var mocName = kvp.Key.m_Name; - var mocContainer = containers.TryGetValue(kvp.Key, out var container) - ? container - : ""; - if (regexMode) - { - if (regex.IsMatch(mocContainer) || regex.IsMatch(mocName)) - filteredDict[kvp.Key] = kvp.Value; - continue; - } - if (filterList.Any(str => mocContainer.IndexOf(str, StringComparison.OrdinalIgnoreCase) >= 0 || mocName.IndexOf(str, StringComparison.OrdinalIgnoreCase) >= 0)) - { - filteredDict[kvp.Key] = kvp.Value; - } - } - mocDict = filteredDict; - var log = regexMode - ? $"Found {filteredDict.Count} Live2D model(s) which names or containers match {regex.ToString().Color(Ansi.BrightYellow)} regexp" - : $"Found {filteredDict.Count} Live2D model(s) that contain {$"\"{string.Join("\", \"", filterList)}\"".Color(Ansi.BrightYellow)} in their names or containers"; - Logger.Info(log); - } - if (mocDict.Count == 0) - return; - - Progress.Reset(); - Logger.Info("Searching for Live2D files..."); + var mode = searchByFilename ? "file names" : "containers"; + Logger.Info($"Searching for Live2D assets using {mode}..."); var mocPathDict = GenerateMocPathDict(mocDict, searchByFilename); - if (!searchByFilename && mocPathDict.Count != l2dModelDict.Count) + if (!searchByFilename && mocPathDict.Count != mocDict.Count) { Logger.Warning($"Some Live2D models cannot be exported using containers\nTry to specify \"{"--l2d-search-by-filename".Color(Ansi.BrightCyan)}\" flag"); } @@ -1237,19 +1094,70 @@ namespace AssetStudioCLI assetDict[mocKvp.Key] = result; } } - if (searchByFilename) l2dContainers.Clear(); - if (mocDict.Keys.First().serializedType?.m_Type == null && CLIOptions.o_assemblyPath.Value == "") + + return assetDict; + } + + private static void FilterMocDict() + { + var regexMode = CLIOptions.f_filterWithRegex.Value; + var filterList = CLIOptions.o_filterByText.Value; + var filteredDict = new Dictionary(); + var regex = regexMode ? new Regex(filterList[0]) : null; + foreach (var kvp in l2dAssetManager.ModelDict) + { + var mocName = kvp.Key.m_Name; + var mocContainer = containers.TryGetValue(kvp.Key, out var container) + ? container + : ""; + if (regexMode) + { + if (regex.IsMatch(mocContainer) || regex.IsMatch(mocName)) + filteredDict[kvp.Key] = kvp.Value; + continue; + } + if (filterList.Any(str => mocContainer.IndexOf(str, StringComparison.OrdinalIgnoreCase) >= 0 || mocName.IndexOf(str, StringComparison.OrdinalIgnoreCase) >= 0)) + { + filteredDict[kvp.Key] = kvp.Value; + } + } + var log = regexMode + ? $"Found {filteredDict.Count} Live2D model(s) which names or containers match {regex.ToString().Color(Ansi.BrightYellow)} regexp" + : $"Found {filteredDict.Count} Live2D model(s) that contain {$"\"{string.Join("\", \"", filterList)}\"".Color(Ansi.BrightYellow)} in their names or containers"; + Logger.Info(log); + + l2dAssetManager.ModelDict = filteredDict; + } + + public static void ExportLive2D() + { + var baseDestPath = Path.Combine(CLIOptions.o_outputFolder.Value, "Live2DOutput"); + var motionMode = CLIOptions.o_l2dMotionMode.Value; + var forceBezier = CLIOptions.f_l2dForceBezier.Value; + var modelGroupOption = CLIOptions.o_l2dGroupOption.Value; + var searchByFilename = CLIOptions.f_l2dAssetSearchByFilename.Value; + if (CLIOptions.filterBy == FilterBy.NameOrContainer) + FilterMocDict(); + if (l2dAssetManager.ModelDict.Count == 0) + { + Logger.Default.Log(LoggerEvent.Info, "Live2D Cubism models were not found.", ignoreLevel: true); + return; + } + if (l2dAssetManager.ModelDict.Keys.First().serializedType?.m_Type == null && CLIOptions.o_assemblyPath.Value == "") { Logger.Warning("Specifying the assembly folder may be needed for proper extraction\n" + $"Use \"{"--assembly-folder ".Color(Ansi.BrightCyan)}\" to specify it"); } + + Progress.Reset(); + var assetDict = GenerateMocAssetDict(l2dAssetManager.ModelDict); var totalModelCount = assetDict.Count; Logger.Info($"Found {totalModelCount} model(s)."); var parallelTaskCount = CLIOptions.o_maxParallelExportTasks.Value; var modelCounter = 0; - Live2DExtractor.MocDict = mocDict; + Live2DExtractor.MocDict = l2dAssetManager.ModelDict; Live2DExtractor.Assembly = assemblyLoader; foreach (var assetGroupKvp in assetDict) { diff --git a/AssetStudioGUI/AssetStudioGUIForm.cs b/AssetStudioGUI/AssetStudioGUIForm.cs index f65076e..e4054af 100644 --- a/AssetStudioGUI/AssetStudioGUIForm.cs +++ b/AssetStudioGUI/AssetStudioGUIForm.cs @@ -312,7 +312,7 @@ namespace AssetStudioGUI var types = new SortedSet(); types.UnionWith(exportableAssets.Select(x => x.TypeString)); - if (Studio.l2dModelDict.Count > 0) + if (Studio.l2dAssetManager.ModelDict.Count > 0) { types.Add("MonoBehaviour (Live2D Model)"); } @@ -1247,7 +1247,7 @@ namespace AssetStudioGUI using (var cubismMoc = new AssetStudio.Addons.CubismLive2D.CubismMoc(m_MonoBehaviour)) { var sb = new StringBuilder(); - if (Studio.l2dModelDict.TryGetValue(m_MonoBehaviour, out var model) && model != null) + if (Studio.l2dAssetManager.ModelDict.TryGetValue(m_MonoBehaviour, out var model) && model != null) { sb.AppendLine($"Model Name: {model.Name}"); } @@ -1590,7 +1590,7 @@ namespace AssetStudioGUI Studio.assemblyLoader.Clear(); Studio.exportableAssets.Clear(); Studio.visibleAssets.Clear(); - Studio.l2dModelDict.Clear(); + Studio.l2dAssetManager.Clear(); sceneTreeView.Nodes.Clear(); assetListView.VirtualListSize = 0; assetListView.Items.Clear(); @@ -1675,7 +1675,7 @@ namespace AssetStudioGUI switch (asset.Asset) { case MonoBehaviour m_MonoBehaviour: - if (Studio.l2dModelDict.Count > 0 && m_MonoBehaviour.m_Script.TryGet(out var m_Script)) + if (Studio.l2dAssetManager.ModelDict.Count > 0 && m_MonoBehaviour.m_Script.TryGet(out var m_Script)) { if (m_Script.m_ClassName == "CubismMoc") { @@ -1995,7 +1995,7 @@ namespace AssetStudioGUI } } visibleAssets = filterMoc - ? exportableAssets.FindAll(x => (x.Asset is MonoBehaviour monoBehaviour && l2dModelDict.ContainsKey(monoBehaviour)) || show.Contains(x.Type)) + ? exportableAssets.FindAll(x => (x.Asset is MonoBehaviour monoBehaviour && Studio.l2dAssetManager.ModelDict.ContainsKey(monoBehaviour)) || show.Contains(x.Type)) : exportableAssets.FindAll(x => show.Contains(x.Type)); } else @@ -2322,7 +2322,7 @@ namespace AssetStudioGUI { if (exportableAssets.Count > 0) { - if (Studio.l2dModelDict.Count == 0) + if (Studio.l2dAssetManager.ModelDict.Count == 0) { Logger.Info("Live2D Cubism models were not found."); return; @@ -2362,7 +2362,7 @@ namespace AssetStudioGUI Logger.Info("No exportable assets loaded"); return; } - if (Studio.l2dModelDict.Count == 0) + if (Studio.l2dAssetManager.ModelDict.Count == 0) { Logger.Info("Live2D Cubism models were not found."); return; diff --git a/AssetStudioGUI/Components/L2DAssetManager.cs b/AssetStudioGUI/Components/L2DAssetManager.cs new file mode 100644 index 0000000..5e3f248 --- /dev/null +++ b/AssetStudioGUI/Components/L2DAssetManager.cs @@ -0,0 +1,123 @@ +using AssetStudio; +using AssetStudio.Addons.CubismLive2D; +using System.Collections.Generic; +using System.Collections.Specialized; +using static CubismLive2DExtractor.CubismParsers; + +namespace AssetStudioGUI +{ + public class L2DAssetManager + { + public readonly Dictionary ModelDict = new Dictionary(); + public readonly Dictionary AssetContainers = new Dictionary(); + + public L2DAssetManager () { } + + public void BindCubismAssetsByGO(GameObject m_GameObject, AssemblyLoader assemblyLoader) + { + if (!TryGetCubismMoc(m_GameObject.CubismModel.CubismModelMono, assemblyLoader, out var mocMono)) + return; + + ModelDict[mocMono] = m_GameObject.CubismModel; + BindAnimationClips(m_GameObject); + } + + public void BindCubismAssetsByMono(MonoBehaviour m_MonoBehaviour, string m_ClassName) + { + if (!m_MonoBehaviour.m_GameObject.TryGet(out var m_GameObject)) + return; + + if (!TryGetModelGameObject(m_GameObject.m_Transform, out var modelGameObject)) + return; + + switch (m_ClassName) + { + case "CubismMoc": + if (!ModelDict.ContainsKey(m_MonoBehaviour)) + { + ModelDict.Add(m_MonoBehaviour, null); + } + break; + case "CubismRenderer": + modelGameObject.CubismModel.RenderTextureList.Add(m_MonoBehaviour); + break; + case "CubismDisplayInfoParameterName": + modelGameObject.CubismModel.ParamDisplayInfoList.Add(m_MonoBehaviour); + break; + case "CubismDisplayInfoPartName": + modelGameObject.CubismModel.PartDisplayInfoList.Add(m_MonoBehaviour); + break; + case "CubismPosePart": + modelGameObject.CubismModel.PosePartList.Add(m_MonoBehaviour); + break; + } + } + + private static bool TryGetCubismMoc(MonoBehaviour m_MonoBehaviour, AssemblyLoader assemblyLoader, out MonoBehaviour mocMono) + { + mocMono = null; + var pptrDict = (OrderedDictionary)ParseMonoBehaviour(m_MonoBehaviour, CubismMonoBehaviourType.Model, assemblyLoader)?["_moc"]; + if (pptrDict == null) + return false; + + var mocPPtr = new PPtr + { + m_FileID = (int)pptrDict["m_FileID"], + m_PathID = (long)pptrDict["m_PathID"], + AssetsFile = m_MonoBehaviour.assetsFile + }; + return mocPPtr.TryGet(out mocMono); + } + + private static void BindAnimationClips(GameObject gameObject) + { + if (gameObject.m_Animator == null || gameObject.m_Animator.m_Controller.IsNull) + return; + + if (!gameObject.m_Animator.m_Controller.TryGet(out var controller)) + return; + + AnimatorController animatorController; + if (controller is AnimatorOverrideController overrideController) + { + if (!overrideController.m_Controller.TryGet(out animatorController)) + return; + } + else + { + animatorController = (AnimatorController)controller; + } + + foreach (var clipPptr in animatorController.m_AnimationClips) + { + if (clipPptr.TryGet(out var m_AnimationClip)) + { + gameObject.CubismModel.ClipMotionList.Add(m_AnimationClip); + } + } + } + + private static bool TryGetModelGameObject(Transform m_Transform, out GameObject m_GameObject) + { + m_GameObject = null; + if (m_Transform == null) + return false; + + while (m_Transform.m_Father.TryGet(out var m_Father)) + { + m_Transform = m_Father; + if (m_Transform.m_GameObject.TryGet(out m_GameObject) && m_GameObject.CubismModel != null) + { + return true; + } + } + return false; + } + + public void Clear() + { + ModelDict.Clear(); + AssetContainers.Clear(); + } + } +} diff --git a/AssetStudioGUI/Studio.cs b/AssetStudioGUI/Studio.cs index 83726e4..c056b8f 100644 --- a/AssetStudioGUI/Studio.cs +++ b/AssetStudioGUI/Studio.cs @@ -4,7 +4,6 @@ using CubismLive2DExtractor; using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Collections.Specialized; using System.Diagnostics; using System.Globalization; using System.IO; @@ -15,7 +14,6 @@ using System.Threading.Tasks; using System.Windows.Forms; using System.Xml.Linq; using static AssetStudioGUI.Exporter; -using static CubismLive2DExtractor.CubismParsers; using Object = AssetStudio.Object; namespace AssetStudioGUI @@ -88,8 +86,7 @@ namespace AssetStudioGUI public static AssemblyLoader assemblyLoader = new AssemblyLoader(); public static List exportableAssets = new List(); public static List visibleAssets = new List(); - public static Dictionary l2dModelDict = new Dictionary(); - private static Dictionary l2dAssetContainers = new Dictionary(); + public static L2DAssetManager l2dAssetManager = new L2DAssetManager(); internal static Action StatusStripUpdate = x => { }; internal static Fbx.Settings FbxSettings; @@ -225,7 +222,6 @@ namespace AssetStudioGUI var objectAssetItemDic = new Dictionary(objectCount); var containers = new List<(PPtr, string)>(); var tex2dArrayAssetList = new List(); - l2dAssetContainers.Clear(); var i = 0; Progress.Reset(); foreach (var assetsFile in assetsManager.AssetsFileList) @@ -245,10 +241,9 @@ namespace AssetStudioGUI break; case GameObject m_GameObject: assetItem.Text = m_GameObject.m_Name; - if (m_GameObject.CubismModel != null && TryGetCubismMoc(m_GameObject.CubismModel.CubismModelMono, out var mocMono)) + if (m_GameObject.CubismModel != null) { - l2dModelDict[mocMono] = m_GameObject.CubismModel; - BindAnimationClips(m_GameObject); + l2dAssetManager.BindCubismAssetsByGO(m_GameObject, assemblyLoader); } break; case Texture2D m_Texture2D: @@ -304,22 +299,11 @@ namespace AssetStudioGUI switch (m_Script.m_ClassName) { case "CubismMoc": - if (!l2dModelDict.ContainsKey(m_MonoBehaviour)) - { - l2dModelDict.Add(m_MonoBehaviour, null); - } - break; case "CubismRenderer": - BindCubismAsset(m_MonoBehaviour, CubismMonoBehaviourType.RenderTexture); - break; case "CubismDisplayInfoParameterName": - BindCubismAsset(m_MonoBehaviour, CubismMonoBehaviourType.DisplayInfo, isParamInfo: true); - break; case "CubismDisplayInfoPartName": - BindCubismAsset(m_MonoBehaviour, CubismMonoBehaviourType.DisplayInfo); - break; case "CubismPosePart": - BindCubismAsset(m_MonoBehaviour, CubismMonoBehaviourType.PosePart); + l2dAssetManager.BindCubismAssetsByMono(m_MonoBehaviour, m_Script.m_ClassName); break; } } @@ -389,7 +373,7 @@ namespace AssetStudioGUI case AnimationClip _: case Texture2D _: case MonoBehaviour _: - l2dAssetContainers[obj] = container; + l2dAssetManager.AssetContainers[obj] = container; break; } } @@ -545,13 +529,10 @@ namespace AssetStudioGUI Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US"); var groupOption = (AssetGroupOption)Properties.Settings.Default.assetGroupOption; - var parallelExportCount = Properties.Settings.Default.parallelExportCount <= 0 - ? Environment.ProcessorCount - 1 - : Math.Min(Properties.Settings.Default.parallelExportCount, Environment.ProcessorCount - 1); - parallelExportCount = Properties.Settings.Default.parallelExport ? parallelExportCount : 1; var toExportAssetDict = new ConcurrentDictionary(); var toParallelExportAssetDict = new ConcurrentDictionary(); var exceptionMsgs = new ConcurrentDictionary(); + var parallelExportCount = GetParallelExportCount(); var mode = exportType == ExportType.Dump ? "Dump" : "Export"; var toExportCount = toExportAssets.Count; var exportedCount = 0; @@ -1025,99 +1006,13 @@ namespace AssetStudioGUI Process.Start(info); } - private static bool TryGetCubismMoc(MonoBehaviour m_MonoBehaviour, out MonoBehaviour mocMono) - { - mocMono = null; - var pptrDict = (OrderedDictionary)CubismParsers.ParseMonoBehaviour(m_MonoBehaviour, CubismParsers.CubismMonoBehaviourType.Model, assemblyLoader)?["_moc"]; - if (pptrDict == null) - return false; - - var mocPPtr = new PPtr - { - m_FileID = (int)pptrDict["m_FileID"], - m_PathID = (long)pptrDict["m_PathID"], - AssetsFile = m_MonoBehaviour.assetsFile - }; - return mocPPtr.TryGet(out mocMono); - } - - private static void BindCubismAsset(MonoBehaviour m_MonoBehaviour, CubismMonoBehaviourType type, bool isParamInfo = false) - { - if (!m_MonoBehaviour.m_GameObject.TryGet(out var m_GameObject)) - return; - - if (!TryGetModelGameObject(m_GameObject.m_Transform, out var modelGameObject)) - return; - - switch (type) - { - case CubismMonoBehaviourType.PosePart: - modelGameObject.CubismModel.PosePartList.Add(m_MonoBehaviour); - break; - case CubismMonoBehaviourType.DisplayInfo when isParamInfo: - modelGameObject.CubismModel.ParamDisplayInfoList.Add(m_MonoBehaviour); - break; - case CubismMonoBehaviourType.DisplayInfo: - modelGameObject.CubismModel.PartDisplayInfoList.Add(m_MonoBehaviour); - break; - case CubismMonoBehaviourType.RenderTexture: - modelGameObject.CubismModel.RenderTextureList.Add(m_MonoBehaviour); - break; - } - } - - private static void BindAnimationClips(GameObject gameObject) - { - if (gameObject.m_Animator == null || gameObject.m_Animator.m_Controller.IsNull) - return; - - if (!gameObject.m_Animator.m_Controller.TryGet(out var controller)) - return; - - AnimatorController animatorController; - if (controller is AnimatorOverrideController overrideController) - { - if (!overrideController.m_Controller.TryGet(out animatorController)) - return; - } - else - { - animatorController = (AnimatorController)controller; - } - - foreach (var clipPptr in animatorController.m_AnimationClips) - { - if (clipPptr.TryGet(out var m_AnimationClip)) - { - gameObject.CubismModel.ClipMotionList.Add(m_AnimationClip); - } - } - } - - private static bool TryGetModelGameObject(Transform m_Transform, out GameObject m_GameObject) - { - m_GameObject = null; - if (m_Transform == null) - return false; - - while (m_Transform.m_Father.TryGet(out var m_Father)) - { - m_Transform = m_Father; - if (m_Transform.m_GameObject.TryGet(out m_GameObject) && m_GameObject.CubismModel != null) - { - return true; - } - } - return false; - } - private static Dictionary GenerateMocPathDict(Dictionary mocDict, bool searchByFilename) { var tempMocPathDict = new Dictionary(); var mocPathDict = new Dictionary(); - foreach (var mocMono in l2dModelDict.Keys) + foreach (var mocMono in l2dAssetManager.ModelDict.Keys) { - if (l2dAssetContainers.TryGetValue(mocMono, out var fullContainerPath)) + if (l2dAssetManager.AssetContainers.TryGetValue(mocMono, out var fullContainerPath)) { var pathSepIndex = fullContainerPath.LastIndexOf('/'); var basePath = pathSepIndex > 0 @@ -1156,6 +1051,76 @@ namespace AssetStudioGUI return mocPathDict; } + private static Dictionary> GenerateMocAssetDict(Dictionary mocDict) + { + var searchByFilename = Properties.Settings.Default.l2dAssetSearchByFilename; + var l2dContainers = searchByFilename + ? new Dictionary() + : l2dAssetManager.AssetContainers; + + var mode = searchByFilename ? "file names" : "containers"; + Logger.Info($"Searching for Live2D assets using {mode}..."); + + var mocPathDict = GenerateMocPathDict(mocDict, searchByFilename); + if (!searchByFilename && mocPathDict.Count != mocDict.Count) + { + Logger.Warning("Some Live2D models cannot be exported using containers\nTry to enable search by file name in the options"); + } + + if (searchByFilename) + { + foreach (var asset in exportableAssets) + { + switch (asset.Type) + { + case ClassIDType.AnimationClip: + case ClassIDType.Texture2D: + case ClassIDType.MonoBehaviour: + l2dContainers[asset.Asset] = asset.Asset.assetsFile.fullName; + break; + } + } + } + + var assetDict = new Dictionary>(); + foreach (var mocKvp in mocPathDict) + { + var mocPath = searchByFilename + ? mocKvp.Key.assetsFile.fullName + : mocKvp.Value; + var result = l2dContainers.Select(assetKvp => + { + if (!assetKvp.Value.Contains(mocPath)) + return null; + var mocPathSpan = mocPath.AsSpan(); + var modelNameFromPath = mocPathSpan.Slice(mocPathSpan.LastIndexOf('/') + 1); +#if NET9_0_OR_GREATER + foreach (var range in assetKvp.Value.AsSpan().Split('/')) + { + if (modelNameFromPath.SequenceEqual(assetKvp.Value.AsSpan()[range])) + return assetKvp.Key; + } +#else + foreach (var str in assetKvp.Value.Split('/')) + { + if (modelNameFromPath.SequenceEqual(str.AsSpan())) + return assetKvp.Key; + } +#endif + return null; + }).Where(x => x != null).ToList(); + + if (result.Count > 0) + { + assetDict[mocKvp.Key] = result; + } + } + if (searchByFilename) + l2dContainers.Clear(); + + return assetDict; + } + public static void ExportLive2D(string exportPath, List selMocs = null, List selClipMotions = null, List selFadeMotions = null, MonoBehaviour selFadeLst = null) { var baseDestPath = Path.Combine(exportPath, "Live2DOutput"); @@ -1168,74 +1133,12 @@ namespace AssetStudioGUI else if (selFadeMotions != null || selFadeLst != null) motionMode = Live2DMotionMode.MonoBehaviour; var mocDict = selMocs != null - ? selMocs.ToDictionary(moc => moc, moc => l2dModelDict[moc]) - : l2dModelDict; - var l2dContainers = searchByFilename - ? new Dictionary() - : l2dAssetContainers; + ? selMocs.ToDictionary(moc => moc, moc => l2dAssetManager.ModelDict[moc]) + : l2dAssetManager.ModelDict; ThreadPool.QueueUserWorkItem(state => { - var mode = searchByFilename ? "file names" : "containers"; - Logger.Info($"Searching for Live2D assets using {mode}..."); - - var mocPathDict = GenerateMocPathDict(mocDict, searchByFilename); - if (!searchByFilename && mocPathDict.Count != l2dModelDict.Count) - { - Logger.Warning("Some Live2D models cannot be exported using containers\nTry to enable search by file name in the options"); - } - - if (searchByFilename) - { - foreach (var asset in exportableAssets) - { - switch (asset.Type) - { - case ClassIDType.AnimationClip: - case ClassIDType.Texture2D: - case ClassIDType.MonoBehaviour: - l2dContainers[asset.Asset] = asset.Asset.assetsFile.fullName; - break; - } - } - } - - var assetDict = new Dictionary>(); - foreach (var mocKvp in mocPathDict) - { - var mocPath = searchByFilename - ? mocKvp.Key.assetsFile.fullName - : mocKvp.Value; - var result = l2dContainers.Select(assetKvp => - { - if (!assetKvp.Value.Contains(mocPath)) - return null; - var mocPathSpan = mocPath.AsSpan(); - var modelNameFromPath = mocPathSpan.Slice(mocPathSpan.LastIndexOf('/') + 1); -#if NET9_0_OR_GREATER - foreach (var range in assetKvp.Value.AsSpan().Split('/')) - { - if (modelNameFromPath.SequenceEqual(assetKvp.Value.AsSpan()[range])) - return assetKvp.Key; - } -#else - foreach (var str in assetKvp.Value.Split('/')) - { - if (modelNameFromPath.SequenceEqual(str.AsSpan())) - return assetKvp.Key; - } -#endif - return null; - }).Where(x => x != null).ToList(); - - if (result.Count > 0) - { - assetDict[mocKvp.Key] = result; - } - } - - if (searchByFilename) - l2dContainers.Clear(); + var assetDict = GenerateMocAssetDict(mocDict); if (mocDict.Keys.First().serializedType?.m_Type == null && !assemblyLoader.Loaded) { Logger.Warning("Specifying the assembly folder may be needed for proper extraction"); @@ -1243,15 +1146,11 @@ namespace AssetStudioGUI } var totalModelCount = assetDict.Count; var modelCounter = 0; - var parallelExportCount = Properties.Settings.Default.parallelExportCount <= 0 - ? Environment.ProcessorCount - 1 - : Math.Min(Properties.Settings.Default.parallelExportCount, Environment.ProcessorCount - 1); - parallelExportCount = Properties.Settings.Default.parallelExport ? parallelExportCount : 1; Live2DExtractor.MocDict = mocDict; Live2DExtractor.Assembly = assemblyLoader; foreach (var assetGroupKvp in assetDict) { - var srcContainer = l2dAssetContainers.TryGetValue(assetGroupKvp.Key, out var result) + var srcContainer = l2dAssetManager.AssetContainers.TryGetValue(assetGroupKvp.Key, out var result) ? result : assetGroupKvp.Key.assetsFile.fullName; @@ -1289,7 +1188,7 @@ namespace AssetStudioGUI break; } var destPath = Path.Combine(baseDestPath, modelPath); - cubismExtractor.ExtractCubismModel(destPath, motionMode, forceBezier, parallelExportCount); + cubismExtractor.ExtractCubismModel(destPath, motionMode, forceBezier, GetParallelExportCount()); modelCounter++; } catch (Exception ex) @@ -1308,5 +1207,14 @@ namespace AssetStudioGUI } }); } + + private static int GetParallelExportCount() + { + var parallelExportCount = Properties.Settings.Default.parallelExportCount <= 0 + ? Environment.ProcessorCount - 1 + : Math.Min(Properties.Settings.Default.parallelExportCount, Environment.ProcessorCount - 1); + + return Properties.Settings.Default.parallelExport ? parallelExportCount : 1; + } } }