From b51a5bbdf654c3b96d5d35c9120f3665b8694020 Mon Sep 17 00:00:00 2001 From: VaDiM Date: Wed, 1 Jul 2026 08:59:49 +0300 Subject: [PATCH] Improve detection of Live2D models. Close #122 - Added an alternative way of model detection when a MonoScript asset is not loaded. - Fixed model detection regression after the https://github.com/aelurum/AssetStudio/commit/0066d673f45cfd07bbca4343e2fdf27660698c17 --- .../Addons/CubismLive2D/CubismHelpers.cs | 79 ++++++++++++++++++ .../Addons/CubismLive2D/CubismModel.cs | 11 +++ AssetStudio/AssetsManager.cs | 26 ++++++ ...L2DAssetManager.cs => L2DAssetsManager.cs} | 31 +++++-- AssetStudioCLI/Studio.cs | 83 +++++++++++-------- AssetStudioGUI/AssetStudioGUIForm.cs | 68 +++++++++++---- ...L2DAssetManager.cs => L2DAssetsManager.cs} | 31 +++++-- AssetStudioGUI/Studio.cs | 72 +++++++++------- .../CubismLive2DExtractor/CubismParsers.cs | 40 +++------ .../CubismLive2DExtractor/Live2DExtractor.cs | 60 ++++++++++++-- 10 files changed, 371 insertions(+), 130 deletions(-) create mode 100644 AssetStudio/Addons/CubismLive2D/CubismHelpers.cs rename AssetStudioCLI/Components/{L2DAssetManager.cs => L2DAssetsManager.cs} (80%) rename AssetStudioGUI/Components/{L2DAssetManager.cs => L2DAssetsManager.cs} (81%) diff --git a/AssetStudio/Addons/CubismLive2D/CubismHelpers.cs b/AssetStudio/Addons/CubismLive2D/CubismHelpers.cs new file mode 100644 index 0000000..7e24de8 --- /dev/null +++ b/AssetStudio/Addons/CubismLive2D/CubismHelpers.cs @@ -0,0 +1,79 @@ +namespace AssetStudio.Addons.CubismLive2D +{ + public static class CubismHelpers + { + public enum CubismMonoBehaviourType + { + Unknown = -1, + FadeController, + FadeMotionList, + FadeMotion, + ExpressionController, + ExpressionList, + Expression, + Physics, + DisplayInfo, + PosePart, + ModelPPtr, + Moc, + RenderTexture, + } + + public static bool TryGetCubismMonoBehaviourType(MonoBehaviour m_MonoBehaviour, out CubismMonoBehaviourType type) + { + type = CubismMonoBehaviourType.Unknown; + var orderedDict = m_MonoBehaviour.ToType(); + if (orderedDict == null) + return false; + + foreach (var key in orderedDict.Keys) + { + var fieldName = ((string)key).ToLower(); + switch (fieldName) + { + case "cubismfademotionlist": + type = CubismMonoBehaviourType.FadeController; + break; + case "cubismfademotionobjects": + type = CubismMonoBehaviourType.FadeMotionList; + break; + case "parameterids": + type = CubismMonoBehaviourType.FadeMotion; + break; + case "expressionslist": + type = CubismMonoBehaviourType.ExpressionController; + break; + case "cubismexpressionobjects": + type = CubismMonoBehaviourType.ExpressionList; + break; + case "parameters": + type = CubismMonoBehaviourType.Expression; + break; + case "_rig": + type = CubismMonoBehaviourType.Physics; + break; + case "name": + type = CubismMonoBehaviourType.DisplayInfo; + break; + case "groupindex": + type = CubismMonoBehaviourType.PosePart; + break; + case "_moc": + type = CubismMonoBehaviourType.ModelPPtr; + break; + case "_bytes": + type = CubismMonoBehaviourType.Moc; + break; + case "_maintexture": + type = CubismMonoBehaviourType.RenderTexture; + break; + } + + if (type != CubismMonoBehaviourType.Unknown) + return true; + } + + return false; + } + } +} diff --git a/AssetStudio/Addons/CubismLive2D/CubismModel.cs b/AssetStudio/Addons/CubismLive2D/CubismModel.cs index 9970b8f..9c0fc4e 100644 --- a/AssetStudio/Addons/CubismLive2D/CubismModel.cs +++ b/AssetStudio/Addons/CubismLive2D/CubismModel.cs @@ -27,5 +27,16 @@ namespace AssetStudio.Addons.CubismLive2D PosePartList = new List(); ClipMotionList = new List(); } + + public CubismModel(MonoBehaviour mocMono) + { + Name = mocMono.m_Name; + ModelGameObject = null; + RenderTextureList = new List(); + ParamDisplayInfoList = new List(); + PartDisplayInfoList = new List(); + PosePartList = new List(); + ClipMotionList = new List(); + } } } diff --git a/AssetStudio/AssetsManager.cs b/AssetStudio/AssetsManager.cs index f5fd025..0a40860 100644 --- a/AssetStudio/AssetsManager.cs +++ b/AssetStudio/AssetsManager.cs @@ -862,6 +862,32 @@ namespace AssetStudio break; } } + else if (CubismHelpers.TryGetCubismMonoBehaviourType(m_MonoBehaviour, out var type)) + { + switch (type) + { + case CubismHelpers.CubismMonoBehaviourType.ModelPPtr: + if (m_GameObject.m_Transform == null) + break; + m_GameObject.CubismModel = new CubismModel(m_GameObject) + { + CubismModelMono = m_MonoBehaviour + }; + break; + case CubismHelpers.CubismMonoBehaviourType.Physics: + if (m_GameObject.CubismModel != null) + m_GameObject.CubismModel.PhysicsController = m_MonoBehaviour; + break; + case CubismHelpers.CubismMonoBehaviourType.FadeController: + if (m_GameObject.CubismModel != null) + m_GameObject.CubismModel.FadeController = m_MonoBehaviour; + break; + case CubismHelpers.CubismMonoBehaviourType.ExpressionController: + if (m_GameObject.CubismModel != null) + m_GameObject.CubismModel.ExpressionController = m_MonoBehaviour; + break; + } + } break; } } diff --git a/AssetStudioCLI/Components/L2DAssetManager.cs b/AssetStudioCLI/Components/L2DAssetsManager.cs similarity index 80% rename from AssetStudioCLI/Components/L2DAssetManager.cs rename to AssetStudioCLI/Components/L2DAssetsManager.cs index 00243bb..7884ac4 100644 --- a/AssetStudioCLI/Components/L2DAssetManager.cs +++ b/AssetStudioCLI/Components/L2DAssetsManager.cs @@ -6,11 +6,11 @@ using static CubismLive2DExtractor.CubismParsers; namespace AssetStudioCLI { - public class L2DAssetManager + public class L2DAssetsManager { public Dictionary ModelDict = new Dictionary(); - public L2DAssetManager () { } + public L2DAssetsManager () { } public void BindCubismAssetsByGO(GameObject m_GameObject, AssemblyLoader assemblyLoader) { @@ -31,12 +31,6 @@ namespace AssetStudioCLI 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; @@ -52,10 +46,29 @@ namespace AssetStudioCLI } } + public void BindCubismAssetsByMono(MonoBehaviour m_MonoBehaviour, CubismHelpers.CubismMonoBehaviourType type) + { + if (!m_MonoBehaviour.m_GameObject.TryGet(out var m_GameObject)) + return; + + if (!TryGetModelGameObject(m_GameObject.m_Transform, out var modelGameObject)) + return; + + switch (type) + { + case CubismHelpers.CubismMonoBehaviourType.RenderTexture: + modelGameObject.CubismModel.RenderTextureList.Add(m_MonoBehaviour); + break; + case CubismHelpers.CubismMonoBehaviourType.PosePart: + 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"]; + var pptrDict = (OrderedDictionary)ParseMonoBehaviour(m_MonoBehaviour, CubismHelpers.CubismMonoBehaviourType.ModelPPtr, assemblyLoader)?["_moc"]; if (pptrDict == null) return false; diff --git a/AssetStudioCLI/Studio.cs b/AssetStudioCLI/Studio.cs index e7ada00..14c3e9a 100644 --- a/AssetStudioCLI/Studio.cs +++ b/AssetStudioCLI/Studio.cs @@ -21,7 +21,7 @@ namespace AssetStudioCLI public static List parsedAssetsList = new List(); public static List gameObjectTree = new List(); public static AssemblyLoader assemblyLoader = new AssemblyLoader(); - public static L2DAssetManager l2dAssetManager = new L2DAssetManager(); + public static L2DAssetsManager l2dAssetsManager = new L2DAssetsManager(); private static Dictionary containers = new Dictionary(); static Studio() @@ -281,12 +281,27 @@ namespace AssetStudioCLI assetName = assetName == "" ? m_Script.m_ClassName : assetName; switch (m_Script.m_ClassName) { - case "CubismMoc": + case "CubismMoc" when !l2dAssetsManager.ModelDict.ContainsKey(m_MonoBehaviour): + l2dAssetsManager.ModelDict[m_MonoBehaviour] = new CubismModel(m_MonoBehaviour); + break; case "CubismRenderer": case "CubismDisplayInfoParameterName": case "CubismDisplayInfoPartName": case "CubismPosePart": - l2dAssetManager.BindCubismAssetsByMono(m_MonoBehaviour, m_Script.m_ClassName); + l2dAssetsManager.BindCubismAssetsByMono(m_MonoBehaviour, m_Script.m_ClassName); + break; + } + } + else if (CubismHelpers.TryGetCubismMonoBehaviourType(m_MonoBehaviour, out var type)) + { + switch (type) + { + case CubismHelpers.CubismMonoBehaviourType.Moc when !l2dAssetsManager.ModelDict.ContainsKey(m_MonoBehaviour): + l2dAssetsManager.ModelDict[m_MonoBehaviour] = new CubismModel(m_MonoBehaviour); + break; + case CubismHelpers.CubismMonoBehaviourType.RenderTexture: + case CubismHelpers.CubismMonoBehaviourType.PosePart: + l2dAssetsManager.BindCubismAssetsByMono(m_MonoBehaviour, type); break; } } @@ -296,7 +311,7 @@ namespace AssetStudioCLI assetItem.Text = m_GameObject.m_Name; if (m_GameObject.CubismModel != null) { - l2dAssetManager.BindCubismAssetsByGO(m_GameObject, assemblyLoader); + l2dAssetsManager.BindCubismAssetsByGO(m_GameObject, assemblyLoader); } break; case Animator m_Animator: @@ -493,7 +508,7 @@ namespace AssetStudioCLI info += $"\n#\n# Total: {parsedAssetsList.Count} assets"; } - info += $"\n\n[Cubism Live2D]\n# Exportable Models: {l2dAssetManager.ModelDict.Count}"; + info += $"\n\n[Cubism Live2D]\n# Exportable Models: {l2dAssetsManager.ModelDict.Count}"; } else { @@ -1019,7 +1034,7 @@ namespace AssetStudioCLI { var tempMocPathDict = new Dictionary(); var mocPathDict = new Dictionary(); - foreach (var mocMono in l2dAssetManager.ModelDict.Keys) + foreach (var mocMono in l2dAssetsManager.ModelDict.Keys) { if (containers.TryGetValue(mocMono, out var fullContainerPath)) { @@ -1060,12 +1075,15 @@ namespace AssetStudioCLI return mocPathDict; } - private static Dictionary> GenerateMocAssetDict(Dictionary mocDict) + private static Dictionary> GenerateMocAssetDict(bool isFiltered) { var searchByFilename = CLIOptions.f_l2dAssetSearchByFilename.Value; var l2dContainers = searchByFilename ? new Dictionary() : containers; + var mocDict = isFiltered + ? FilterMocDict() + : l2dAssetsManager.ModelDict; var mode = searchByFilename ? "file names" : "containers"; Logger.Info($"Searching for Live2D assets using {mode}..."); @@ -1130,13 +1148,13 @@ namespace AssetStudioCLI return assetDict; } - private static void FilterMocDict() + private static Dictionary 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) + foreach (var kvp in l2dAssetsManager.ModelDict) { var mocName = kvp.Key.m_Name; var mocContainer = containers.TryGetValue(kvp.Key, out var container) @@ -1158,11 +1176,16 @@ namespace AssetStudioCLI : $"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; + return filteredDict; } public static void ExportLive2D() { + if (l2dAssetsManager.ModelDict.Count == 0) + { + Logger.Default.Log(LoggerEvent.Info, "Live2D Cubism models were not found.", ignoreLevel: true); + return; + } var baseDestPath = Path.Combine(CLIOptions.o_outputFolder.Value, "Live2DOutput"); var modelGroupOption = CLIOptions.o_l2dGroupOption.Value; var l2dOptions = new Live2DOptions() @@ -1171,42 +1194,36 @@ namespace AssetStudioCLI ForceBezier = CLIOptions.f_l2dForceBezier.Value, 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 == "") + if (l2dAssetsManager.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 assetDict = GenerateMocAssetDict(CLIOptions.filterBy == FilterBy.NameOrContainer); var totalModelCount = assetDict.Count; Logger.Info($"Found {totalModelCount} model(s)."); var parallelTaskCount = CLIOptions.o_maxParallelExportTasks.Value; var modelCounter = 0; - Live2DExtractor.MocDict = l2dAssetManager.ModelDict; Live2DExtractor.Assembly = assemblyLoader; foreach (var assetGroupKvp in assetDict) { - var srcContainer = containers.TryGetValue(assetGroupKvp.Key, out var result) + var mocMono = assetGroupKvp.Key; + var srcContainer = containers.TryGetValue(mocMono, out var result) ? result - : assetGroupKvp.Key.assetsFile.fullName; + : mocMono.assetsFile.fullName; + l2dAssetsManager.ModelDict.TryGetValue(mocMono, out var cubismModel); Logger.Info($"[{modelCounter + 1}/{totalModelCount}] Exporting Live2D from: \"{srcContainer.Color(Ansi.BrightCyan)}\"..."); try { - var cubismExtractor = new Live2DExtractor(assetGroupKvp, l2dOptions); - var fileName = string.IsNullOrEmpty(cubismExtractor.MocMono.assetsFile.originalPath) - ? Path.GetFileNameWithoutExtension(cubismExtractor.MocMono.assetsFile.fileName) - : Path.GetFileNameWithoutExtension(cubismExtractor.MocMono.assetsFile.originalPath); - var modelName = !string.IsNullOrEmpty(cubismExtractor.Model?.Name) - ? cubismExtractor.Model.Name + var cubismExtractor = new Live2DExtractor(assetGroupKvp.Value, cubismModel, l2dOptions); + var fileName = string.IsNullOrEmpty(mocMono.assetsFile.originalPath) + ? Path.GetFileNameWithoutExtension(mocMono.assetsFile.fileName) + : Path.GetFileNameWithoutExtension(mocMono.assetsFile.originalPath); + var modelName = !string.IsNullOrEmpty(cubismModel?.Name) + ? cubismModel.Name : fileName; Logger.Info($"Model name: \"{modelName}\""); @@ -1220,10 +1237,10 @@ namespace AssetStudioCLI modelPath = modelName; break; default: //ContainerPath - var container = l2dOptions.SearchByFilename && cubismExtractor.Model != null - ? cubismExtractor.Model.Container + var container = l2dOptions.SearchByFilename && cubismModel != null + ? cubismModel.Container : srcContainer; - container = container == assetGroupKvp.Key.assetsFile.fullName + container = container == mocMono.assetsFile.fullName ? fileName : container; modelPath = Path.HasExtension(container) @@ -1233,7 +1250,7 @@ namespace AssetStudioCLI } var destPath = Exporter.SafePathCombine(baseDestPath, modelPath); - cubismExtractor.ExtractCubismModel(destPath, parallelTaskCount); + cubismExtractor.ExtractCubismModel(mocMono, destPath, parallelTaskCount); modelCounter++; } catch (Exception ex) diff --git a/AssetStudioGUI/AssetStudioGUIForm.cs b/AssetStudioGUI/AssetStudioGUIForm.cs index 0414a8b..728cc79 100644 --- a/AssetStudioGUI/AssetStudioGUIForm.cs +++ b/AssetStudioGUI/AssetStudioGUIForm.cs @@ -1,4 +1,5 @@ using AssetStudio; +using AssetStudio.Addons.CubismLive2D; using OpenTK.Graphics.OpenGL; using System; using System.Collections.Generic; @@ -331,7 +332,7 @@ namespace AssetStudioGUI var types = new SortedSet(); types.UnionWith(exportableAssets.Select(x => x.TypeString)); - if (Studio.l2dAssetManager.ModelDict.Count > 0) + if (Studio.l2dAssetsManager.ModelDict.Count > 0) { types.Add("MonoBehaviour (Live2D Model)"); } @@ -939,6 +940,11 @@ namespace AssetStudioGUI break; } } + else if (l2dAssetsManager.ModelDict.ContainsKey(m_MonoBehaviour)) + { + PreviewMoc(assetItem, m_MonoBehaviour); + break; + } PreviewMonoBehaviour(m_MonoBehaviour); break; case ClassIDType.Font: @@ -1207,7 +1213,7 @@ namespace AssetStudioGUI using (var cubismMoc = new AssetStudio.Addons.CubismLive2D.CubismMoc(m_MonoBehaviour)) { var sb = new StringBuilder(); - if (Studio.l2dAssetManager.ModelDict.TryGetValue(m_MonoBehaviour, out var model) && model != null) + if (Studio.l2dAssetsManager.ModelDict.TryGetValue(m_MonoBehaviour, out var model) && model != null) { sb.AppendLine($"Model Name: {model.Name}"); } @@ -1599,7 +1605,7 @@ namespace AssetStudioGUI Studio.assemblyLoader.Clear(); Studio.exportableAssets.Clear(); Studio.visibleAssets.Clear(); - Studio.l2dAssetManager.Clear(); + Studio.l2dAssetsManager.Clear(); sceneTreeView.Nodes.Clear(); assetListView.VirtualListSize = 0; assetListView.Items.Clear(); @@ -1684,19 +1690,37 @@ namespace AssetStudioGUI switch (asset.Asset) { case MonoBehaviour m_MonoBehaviour: - if (Studio.l2dAssetManager.ModelDict.Count > 0 && m_MonoBehaviour.m_Script.TryGet(out var m_Script)) + if (Studio.l2dAssetsManager.ModelDict.Count > 0) { - if (m_Script.m_ClassName == "CubismMoc") + if (m_MonoBehaviour.m_Script.TryGet(out var m_Script)) { - selectedTypes |= SelectedAssetType.MonoBehaviourMoc; + switch (m_Script.m_ClassName) + { + case "CubismMoc": + selectedTypes |= SelectedAssetType.MonoBehaviourMoc; + break; + case "CubismFadeMotionData": + selectedTypes |= SelectedAssetType.MonoBehaviourFade; + break; + case "CubismFadeMotionList": + selectedTypes |= SelectedAssetType.MonoBehaviourFadeLst; + break; + } } - else if (m_Script.m_ClassName == "CubismFadeMotionData") + else if (CubismHelpers.TryGetCubismMonoBehaviourType(m_MonoBehaviour, out var type)) { - selectedTypes |= SelectedAssetType.MonoBehaviourFade; - } - else if (m_Script.m_ClassName == "CubismFadeMotionList") - { - selectedTypes |= SelectedAssetType.MonoBehaviourFadeLst; + switch (type) + { + case CubismHelpers.CubismMonoBehaviourType.Moc: + selectedTypes |= SelectedAssetType.MonoBehaviourMoc; + break; + case CubismHelpers.CubismMonoBehaviourType.FadeMotion: + selectedTypes |= SelectedAssetType.MonoBehaviourFade; + break; + case CubismHelpers.CubismMonoBehaviourType.FadeMotionList: + selectedTypes |= SelectedAssetType.MonoBehaviourFadeLst; + break; + } } } break; @@ -1990,7 +2014,7 @@ namespace AssetStudioGUI } } visibleAssets = filterMoc - ? exportableAssets.FindAll(x => (x.Asset is MonoBehaviour monoBehaviour && Studio.l2dAssetManager.ModelDict.ContainsKey(monoBehaviour)) || show.Contains(x.Type)) + ? exportableAssets.FindAll(x => (x.Asset is MonoBehaviour monoBehaviour && Studio.l2dAssetsManager.ModelDict.ContainsKey(monoBehaviour)) || show.Contains(x.Type)) : exportableAssets.FindAll(x => show.Contains(x.Type)); } else @@ -2325,7 +2349,7 @@ namespace AssetStudioGUI { if (exportableAssets.Count > 0) { - if (Studio.l2dAssetManager.ModelDict.Count == 0) + if (Studio.l2dAssetsManager.ModelDict.Count == 0) { Logger.Info("Live2D Cubism models were not found."); return; @@ -2365,7 +2389,7 @@ namespace AssetStudioGUI Logger.Info("No exportable assets loaded"); return; } - if (Studio.l2dAssetManager.ModelDict.Count == 0) + if (Studio.l2dAssetsManager.ModelDict.Count == 0) { Logger.Info("Live2D Cubism models were not found."); return; @@ -2396,6 +2420,20 @@ namespace AssetStudioGUI break; } break; + case MonoBehaviour m_MonoBehaviour when CubismHelpers.TryGetCubismMonoBehaviourType(m_MonoBehaviour, out var type): + switch (type) + { + case CubismHelpers.CubismMonoBehaviourType.Moc: + selectedMocs.Add(m_MonoBehaviour); + break; + case CubismHelpers.CubismMonoBehaviourType.FadeMotion: + selectedFadeMotions.Add(m_MonoBehaviour); + break; + case CubismHelpers.CubismMonoBehaviourType.FadeMotionList: + selectedFadeLst = m_MonoBehaviour; + break; + } + break; case AnimationClip m_AnimationClip: selectedClips.Add(m_AnimationClip); break; diff --git a/AssetStudioGUI/Components/L2DAssetManager.cs b/AssetStudioGUI/Components/L2DAssetsManager.cs similarity index 81% rename from AssetStudioGUI/Components/L2DAssetManager.cs rename to AssetStudioGUI/Components/L2DAssetsManager.cs index 5e3f248..8232437 100644 --- a/AssetStudioGUI/Components/L2DAssetManager.cs +++ b/AssetStudioGUI/Components/L2DAssetsManager.cs @@ -6,12 +6,12 @@ using static CubismLive2DExtractor.CubismParsers; namespace AssetStudioGUI { - public class L2DAssetManager + public class L2DAssetsManager { public readonly Dictionary ModelDict = new Dictionary(); public readonly Dictionary AssetContainers = new Dictionary(); - public L2DAssetManager () { } + public L2DAssetsManager () { } public void BindCubismAssetsByGO(GameObject m_GameObject, AssemblyLoader assemblyLoader) { @@ -32,12 +32,6 @@ namespace AssetStudioGUI 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; @@ -53,10 +47,29 @@ namespace AssetStudioGUI } } + public void BindCubismAssetsByMono(MonoBehaviour m_MonoBehaviour, CubismHelpers.CubismMonoBehaviourType type) + { + if (!m_MonoBehaviour.m_GameObject.TryGet(out var m_GameObject)) + return; + + if (!TryGetModelGameObject(m_GameObject.m_Transform, out var modelGameObject)) + return; + + switch (type) + { + case CubismHelpers.CubismMonoBehaviourType.RenderTexture: + modelGameObject.CubismModel.RenderTextureList.Add(m_MonoBehaviour); + break; + case CubismHelpers.CubismMonoBehaviourType.PosePart: + 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"]; + var pptrDict = (OrderedDictionary)ParseMonoBehaviour(m_MonoBehaviour, CubismHelpers.CubismMonoBehaviourType.ModelPPtr, assemblyLoader)?["_moc"]; if (pptrDict == null) return false; diff --git a/AssetStudioGUI/Studio.cs b/AssetStudioGUI/Studio.cs index 9455e01..cac86f9 100644 --- a/AssetStudioGUI/Studio.cs +++ b/AssetStudioGUI/Studio.cs @@ -86,7 +86,7 @@ namespace AssetStudioGUI public static AssemblyLoader assemblyLoader = new AssemblyLoader(); public static List exportableAssets = new List(); public static List visibleAssets = new List(); - public static L2DAssetManager l2dAssetManager = new L2DAssetManager(); + public static L2DAssetsManager l2dAssetsManager = new L2DAssetsManager(); internal static Action StatusStripUpdate = x => { }; internal static Fbx.Settings FbxSettings; @@ -241,7 +241,7 @@ namespace AssetStudioGUI assetItem.Text = m_GameObject.m_Name; if (m_GameObject.CubismModel != null) { - l2dAssetManager.BindCubismAssetsByGO(m_GameObject, assemblyLoader); + l2dAssetsManager.BindCubismAssetsByGO(m_GameObject, assemblyLoader); } break; case Texture2D m_Texture2D: @@ -296,12 +296,27 @@ namespace AssetStudioGUI assetName = assetName == "" ? m_Script.m_ClassName : assetName; switch (m_Script.m_ClassName) { - case "CubismMoc": + case "CubismMoc" when !l2dAssetsManager.ModelDict.ContainsKey(m_MonoBehaviour): + l2dAssetsManager.ModelDict[m_MonoBehaviour] = new CubismModel(m_MonoBehaviour); + break; case "CubismRenderer": case "CubismDisplayInfoParameterName": case "CubismDisplayInfoPartName": case "CubismPosePart": - l2dAssetManager.BindCubismAssetsByMono(m_MonoBehaviour, m_Script.m_ClassName); + l2dAssetsManager.BindCubismAssetsByMono(m_MonoBehaviour, m_Script.m_ClassName); + break; + } + } + else if (CubismHelpers.TryGetCubismMonoBehaviourType(m_MonoBehaviour, out var type)) + { + switch (type) + { + case CubismHelpers.CubismMonoBehaviourType.Moc when !l2dAssetsManager.ModelDict.ContainsKey(m_MonoBehaviour): + l2dAssetsManager.ModelDict[m_MonoBehaviour] = new CubismModel(m_MonoBehaviour); + break; + case CubismHelpers.CubismMonoBehaviourType.RenderTexture: + case CubismHelpers.CubismMonoBehaviourType.PosePart: + l2dAssetsManager.BindCubismAssetsByMono(m_MonoBehaviour, type); break; } } @@ -371,7 +386,7 @@ namespace AssetStudioGUI case AnimationClip _: case Texture2D _: case MonoBehaviour _: - l2dAssetManager.AssetContainers[obj] = container; + l2dAssetsManager.AssetContainers[obj] = container; break; } } @@ -1024,9 +1039,9 @@ namespace AssetStudioGUI { var tempMocPathDict = new Dictionary(); var mocPathDict = new Dictionary(); - foreach (var mocMono in l2dAssetManager.ModelDict.Keys) + foreach (var mocMono in l2dAssetsManager.ModelDict.Keys) { - if (l2dAssetManager.AssetContainers.TryGetValue(mocMono, out var fullContainerPath)) + if (l2dAssetsManager.AssetContainers.TryGetValue(mocMono, out var fullContainerPath)) { var pathSepIndex = fullContainerPath.LastIndexOf('/'); var basePath = pathSepIndex > 0 @@ -1065,12 +1080,15 @@ namespace AssetStudioGUI return mocPathDict; } - private static Dictionary> GenerateMocAssetDict(Dictionary mocDict) + private static Dictionary> GenerateMocAssetDict(List filteredMocs) { var searchByFilename = Properties.Settings.Default.l2dAssetSearchByFilename; var l2dContainers = searchByFilename ? new Dictionary() - : l2dAssetManager.AssetContainers; + : l2dAssetsManager.AssetContainers; + var mocDict = filteredMocs != null + ? filteredMocs.ToDictionary(moc => moc, moc => l2dAssetsManager.ModelDict[moc]) + : l2dAssetsManager.ModelDict; var mode = searchByFilename ? "file names" : "containers"; Logger.Info($"Searching for Live2D assets using {mode}..."); @@ -1131,7 +1149,7 @@ namespace AssetStudioGUI } if (searchByFilename) l2dContainers.Clear(); - + return assetDict; } @@ -1152,37 +1170,35 @@ namespace AssetStudioGUI l2dOptions.MotionMode = Live2DMotionMode.AnimationClipV2; else if (selFadeMotions != null || selFadeLst != null) l2dOptions.MotionMode = Live2DMotionMode.MonoBehaviour; - var mocDict = selMocs != null - ? selMocs.ToDictionary(moc => moc, moc => l2dAssetManager.ModelDict[moc]) - : l2dAssetManager.ModelDict; ThreadPool.QueueUserWorkItem(state => { - var assetDict = GenerateMocAssetDict(mocDict); - if (mocDict.Keys.First().serializedType?.m_Type == null && !assemblyLoader.Loaded) + var assetDict = GenerateMocAssetDict(selMocs); + if (assetDict.Count > 0 && assetDict.Keys.First().serializedType?.m_Type == null && !assemblyLoader.Loaded) { Logger.Warning("Specifying the assembly folder may be needed for proper extraction"); SelectAssemblyFolder(); } var totalModelCount = assetDict.Count; var modelCounter = 0; - Live2DExtractor.MocDict = mocDict; Live2DExtractor.Assembly = assemblyLoader; foreach (var assetGroupKvp in assetDict) { - var srcContainer = l2dAssetManager.AssetContainers.TryGetValue(assetGroupKvp.Key, out var result) + var mocMono = assetGroupKvp.Key; + var srcContainer = l2dAssetsManager.AssetContainers.TryGetValue(mocMono, out var result) ? result - : assetGroupKvp.Key.assetsFile.fullName; + : mocMono.assetsFile.fullName; + l2dAssetsManager.ModelDict.TryGetValue(mocMono, out var cubismModel); Logger.Info($"[{modelCounter + 1}/{totalModelCount}] Exporting Live2D from: \"{srcContainer}\"..."); try { - var cubismExtractor = new Live2DExtractor(assetGroupKvp, l2dOptions); - var fileName = string.IsNullOrEmpty(cubismExtractor.MocMono.assetsFile.originalPath) - ? Path.GetFileNameWithoutExtension(cubismExtractor.MocMono.assetsFile.fileName) - : Path.GetFileNameWithoutExtension(cubismExtractor.MocMono.assetsFile.originalPath); - var modelName = !string.IsNullOrEmpty(cubismExtractor.Model?.Name) - ? cubismExtractor.Model.Name + var cubismExtractor = new Live2DExtractor(assetGroupKvp.Value, cubismModel, l2dOptions); + var fileName = string.IsNullOrEmpty(mocMono.assetsFile.originalPath) + ? Path.GetFileNameWithoutExtension(mocMono.assetsFile.fileName) + : Path.GetFileNameWithoutExtension(mocMono.assetsFile.originalPath); + var modelName = !string.IsNullOrEmpty(cubismModel?.Name) + ? cubismModel.Name : fileName; Logger.Info($"Model name: \"{modelName}\""); @@ -1196,10 +1212,10 @@ namespace AssetStudioGUI modelPath = modelName; break; default: //ContainerPath - var container = l2dOptions.SearchByFilename && cubismExtractor.Model != null - ? cubismExtractor.Model.Container + var container = l2dOptions.SearchByFilename && cubismModel != null + ? cubismModel.Container : srcContainer; - container = container == assetGroupKvp.Key.assetsFile.fullName + container = container == mocMono.assetsFile.fullName ? fileName : container; modelPath = Path.HasExtension(container) @@ -1208,7 +1224,7 @@ namespace AssetStudioGUI break; } var destPath = Exporter.SafePathCombine(baseDestPath, modelPath); - cubismExtractor.ExtractCubismModel(destPath, GetParallelExportCount()); + cubismExtractor.ExtractCubismModel(mocMono, destPath, GetParallelExportCount()); modelCounter++; } catch (Exception ex) diff --git a/AssetStudioUtility/CubismLive2DExtractor/CubismParsers.cs b/AssetStudioUtility/CubismLive2DExtractor/CubismParsers.cs index cc2dc3a..6a7c1ca 100644 --- a/AssetStudioUtility/CubismLive2DExtractor/CubismParsers.cs +++ b/AssetStudioUtility/CubismLive2DExtractor/CubismParsers.cs @@ -1,4 +1,5 @@ using AssetStudio; +using AssetStudio.Addons.CubismLive2D; using CubismLive2DExtractor.CubismJsonConverters; using CubismLive2DExtractor.CubismUnityClasses; using System; @@ -13,21 +14,6 @@ namespace CubismLive2DExtractor { public static readonly JsonSerializerOptions CubismJsonOptions; - public enum CubismMonoBehaviourType - { - FadeController, - FadeMotionList, - FadeMotion, - ExpressionController, - ExpressionList, - Expression, - Physics, - DisplayInfo, - PosePart, - Model, - RenderTexture, - } - static CubismParsers() { CubismJsonOptions = new JsonSerializerOptions @@ -151,7 +137,7 @@ namespace CubismLive2DExtractor return JsonSerializer.Serialize(physicsJson, physicsOptions); } - public static OrderedDictionary ParseMonoBehaviour(MonoBehaviour m_MonoBehaviour, CubismMonoBehaviourType cubismMonoBehaviourType, AssemblyLoader assemblyLoader) + public static OrderedDictionary ParseMonoBehaviour(MonoBehaviour m_MonoBehaviour, CubismHelpers.CubismMonoBehaviourType cubismMonoBehaviourType, AssemblyLoader assemblyLoader) { var orderedDict = m_MonoBehaviour.ToType(); if (orderedDict != null) @@ -161,37 +147,37 @@ namespace CubismLive2DExtractor var m_Type = m_MonoBehaviour.ConvertToTypeTree(assemblyLoader); switch (cubismMonoBehaviourType) { - case CubismMonoBehaviourType.FadeController: + case CubismHelpers.CubismMonoBehaviourType.FadeController: fieldName = "cubismfademotionlist"; break; - case CubismMonoBehaviourType.FadeMotionList: + case CubismHelpers.CubismMonoBehaviourType.FadeMotionList: fieldName = "cubismfademotionobjects"; break; - case CubismMonoBehaviourType.FadeMotion: + case CubismHelpers.CubismMonoBehaviourType.FadeMotion: fieldName = "parameterids"; break; - case CubismMonoBehaviourType.ExpressionController: + case CubismHelpers.CubismMonoBehaviourType.ExpressionController: fieldName = "expressionslist"; break; - case CubismMonoBehaviourType.ExpressionList: + case CubismHelpers.CubismMonoBehaviourType.ExpressionList: fieldName = "cubismexpressionobjects"; break; - case CubismMonoBehaviourType.Expression: + case CubismHelpers.CubismMonoBehaviourType.Expression: fieldName = "parameters"; break; - case CubismMonoBehaviourType.Physics: + case CubismHelpers.CubismMonoBehaviourType.Physics: fieldName = "_rig"; break; - case CubismMonoBehaviourType.DisplayInfo: + case CubismHelpers.CubismMonoBehaviourType.DisplayInfo: fieldName = "name"; break; - case CubismMonoBehaviourType.PosePart: + case CubismHelpers.CubismMonoBehaviourType.PosePart: fieldName = "groupindex"; break; - case CubismMonoBehaviourType.Model: + case CubismHelpers.CubismMonoBehaviourType.ModelPPtr: fieldName = "_moc"; break; - case CubismMonoBehaviourType.RenderTexture: + case CubismHelpers.CubismMonoBehaviourType.RenderTexture: fieldName = "_maintexture"; break; } diff --git a/AssetStudioUtility/CubismLive2DExtractor/Live2DExtractor.cs b/AssetStudioUtility/CubismLive2DExtractor/Live2DExtractor.cs index 28db231..65ebd57 100644 --- a/AssetStudioUtility/CubismLive2DExtractor/Live2DExtractor.cs +++ b/AssetStudioUtility/CubismLive2DExtractor/Live2DExtractor.cs @@ -16,6 +16,7 @@ using System.Text; using System.Text.Json; using System.Text.Json.Nodes; using System.Threading.Tasks; +using static AssetStudio.Addons.CubismLive2D.CubismHelpers; using static CubismLive2DExtractor.CubismParsers; using Object = AssetStudio.Object; @@ -23,9 +24,8 @@ namespace CubismLive2DExtractor { public sealed class Live2DExtractor { - public static Dictionary MocDict { get; set; } public static AssemblyLoader Assembly { get; set; } - public CubismModel Model { get; set; } + private CubismModel Model { get; set; } private Live2DOptions Options { get; } private List FadeMotions { get; set; } private List AnimationClips { get; set; } @@ -34,7 +34,6 @@ namespace CubismLive2DExtractor private List PartsCdi { get; set; } private List PoseParts { get; set; } private List Texture2Ds { get; set; } - public MonoBehaviour MocMono { get; set; } private MonoBehaviour PhysicsMono { get; set; } private MonoBehaviour FadeMotionLst { get; set; } private MonoBehaviour ExpressionLst { get; set; } @@ -43,7 +42,7 @@ namespace CubismLive2DExtractor private HashSet EyeBlinkParameters { get; set; } private HashSet LipSyncParameters { get; set; } - public Live2DExtractor(KeyValuePair> assetGroupKvp, Live2DOptions l2dOptions) + public Live2DExtractor(List modelAssetsList, CubismModel model, Live2DOptions l2dOptions) { Options = l2dOptions; Expressions = new List(); @@ -71,8 +70,7 @@ namespace CubismLive2DExtractor Logger.Debug("Sorting model assets.."); - MocMono = assetGroupKvp.Key; - if (MocDict.TryGetValue(MocMono, out var model) && model != null) + if (model != null) { Model = model; PhysicsMono = Model.PhysicsController; @@ -116,7 +114,7 @@ namespace CubismLive2DExtractor AnimationClips.AddRange(Model.ClipMotionList); } } - foreach (var asset in assetGroupKvp.Value) + foreach (var asset in modelAssetsList) { switch (asset) { @@ -197,6 +195,50 @@ namespace CubismLive2DExtractor break; } } + else if (TryGetCubismMonoBehaviourType(m_MonoBehaviour, out var type)) + { + switch (type) + { + case CubismMonoBehaviourType.Physics: + if (PhysicsMono == null) + PhysicsMono = m_MonoBehaviour; + break; + case CubismMonoBehaviourType.Expression: + if (ExpressionLst == null) + Expressions.Add(m_MonoBehaviour); + break; + case CubismMonoBehaviourType.ExpressionList: + if (ExpressionLst == null) + ExpressionLst = m_MonoBehaviour; + break; + case CubismMonoBehaviourType.FadeMotion: + if (searchFadeMotions) + { + FadeMotions.Add(m_MonoBehaviour); + } + break; + case CubismMonoBehaviourType.FadeMotionList: + if (searchFadeMotions) + { + FadeMotionLst = m_MonoBehaviour; + } + break; + case CubismMonoBehaviourType.PosePart: + if (searchPoseParts && m_MonoBehaviour.m_GameObject.TryGet(out _)) + { + PoseParts.Add(m_MonoBehaviour); + } + break; + case CubismMonoBehaviourType.RenderTexture: + if (searchRenderTextures && isRenderReadable) + { + isRenderReadable = TryGetRenderTexture(m_MonoBehaviour, out var renderTex); + if (isRenderReadable) + renderTextureSet.Add(renderTex); + } + break; + } + } break; case AnimationClip m_AnimationClip: if (Options.SelectedClipMotions == null) @@ -225,14 +267,14 @@ namespace CubismLive2DExtractor } } - public void ExtractCubismModel(string destPath, int parallelTaskCount = 1) + public void ExtractCubismModel(MonoBehaviour mocMono, string destPath, int parallelTaskCount = 1) { var modelName = Model?.Name ?? destPath.Split('/', '\\').Last(); destPath += Path.DirectorySeparatorChar; Directory.CreateDirectory(destPath); #region moc3 - using (var cubismMoc = new CubismMoc(MocMono)) + using (var cubismMoc = new CubismMoc(mocMono)) { var sb = new StringBuilder(); sb.AppendLine("Model Stats:");