diff --git a/AssetStudioCLI/Options/CLIOptions.cs b/AssetStudioCLI/Options/CLIOptions.cs index f30df449..96aff73b 100644 --- a/AssetStudioCLI/Options/CLIOptions.cs +++ b/AssetStudioCLI/Options/CLIOptions.cs @@ -215,13 +215,13 @@ private static void InitOptions() optionDefaultValue: AssetGroupOption.ContainerPath, optionName: "-g, --group-option ", optionDescription: "Specify the way in which exported assets should be grouped\n" + - "\n" + + "\n" + "None - Do not group exported assets\n" + "Type - Group exported assets by type name\n" + "Container - Group exported assets by container path\n" + "ContainerFull - Group exported assets by full container path (e.g. with prefab name)\n" + "SceneHierarchy - Group exported assets by their node path in scene hierarchy\n" + - "Filename - Group exported assets by source file name\n", + "FileName - Group exported assets by source file name\n", optionExample: "Example: \"-g containerFull\"\n", optionHelpGroup: HelpGroups.General ); @@ -307,10 +307,11 @@ private static void InitOptions() optionDefaultValue: CubismLive2DExtractor.Live2DModelGroupOption.ContainerPath, optionName: "--l2d-group-option ", optionDescription: "Specify the way in which exported models should be grouped\n" + - "\n" + + "\n" + "Container - Group exported models by container path\n" + - "Filename - Group exported models by source file name\n", - optionExample: "Example: \"--l2d-group-option filename\"\n", + "FileName - Group exported models by source file name\n" + + "ModelName - Group exported models by model name\n", + optionExample: "Example: \"--l2d-group-option modelName\"\n", optionHelpGroup: HelpGroups.Live2D ); o_l2dMotionMode = new GroupedOption @@ -331,7 +332,8 @@ private static void InitOptions() optionName: "--l2d-search-by-filename", optionDescription: "(Flag) If specified, Studio will search for model-related Live2D assets by file name\n" + "rather than by container\n" + - "(Preferred option when all model-related assets are stored in a single file)\n", + "(Preferred option if all l2d assets of a single model are stored in a single file\n" + + "or containers are obfuscated)\n", optionExample: "", optionHelpGroup: HelpGroups.Live2D, isFlag: true @@ -880,6 +882,9 @@ public static void ParseArgs(string[] args) case "filename": o_l2dGroupOption.Value = CubismLive2DExtractor.Live2DModelGroupOption.SourceFileName; break; + case "modelname": + o_l2dGroupOption.Value = CubismLive2DExtractor.Live2DModelGroupOption.ModelName; + break; default: Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option.Color(brightYellow)}] option. Unsupported model grouping option: [{value.Color(brightRed)}].\n"); ShowOptionDescription(o_l2dGroupOption); @@ -1255,7 +1260,7 @@ public static void ShowCurrentOptions() else { sb.AppendLine($"# Model Group Option: {o_l2dGroupOption}"); - sb.AppendFormat("# Search model-related assets by: {0}", f_l2dAssetSearchByFilename.Value ? "Filename" : "Container"); + sb.AppendFormat("# Search model-related assets by: {0}\n", f_l2dAssetSearchByFilename.Value ? "FileName" : "Container"); sb.AppendLine($"# Motion Export Method: {o_l2dMotionMode}"); sb.AppendLine($"# Force Bezier: {f_l2dForceBezier }"); sb.AppendLine($"# Assembly Path: \"{o_assemblyPath}\""); diff --git a/AssetStudioCLI/Studio.cs b/AssetStudioCLI/Studio.cs index 2f595bf1..c3075404 100644 --- a/AssetStudioCLI/Studio.cs +++ b/AssetStudioCLI/Studio.cs @@ -67,7 +67,6 @@ public static void ParseAssets() var objectCount = assetsManager.assetsFileList.Sum(x => x.Objects.Count); var objectAssetItemDic = new Dictionary(objectCount); var isL2dMode = CLIOptions.o_workMode.Value == WorkMode.Live2D; - var l2dSearchByFilename = CLIOptions.f_l2dAssetSearchByFilename.Value; Progress.Reset(); var i = 0; @@ -175,10 +174,6 @@ public static void ParseAssets() if (m_GameObject.CubismModel != null && TryGetCubismMoc(m_GameObject.CubismModel.CubismModelMono, out var mocMono)) { l2dModelDict[mocMono] = m_GameObject.CubismModel; - if (!m_GameObject.CubismModel.IsRoot) - { - FixCubismModelName(m_GameObject); - } } break; case Animator m_Animator: @@ -210,9 +205,7 @@ public static void ParseAssets() { if (containers.TryGetValue(asset.Asset, out var container)) { - asset.Container = isL2dMode && l2dSearchByFilename - ? Path.GetFileName(asset.Asset.assetsFile.originalPath) - : container; + asset.Container = container; if (asset.Asset is GameObject m_GameObject && m_GameObject.CubismModel != null) { @@ -778,15 +771,6 @@ private static bool TryGetCubismMoc(MonoBehaviour m_MonoBehaviour, out MonoBehav return mocPPtr.TryGet(out mocMono); } - private static void FixCubismModelName(GameObject m_GameObject) - { - var rootTransform = GetRootTransform(m_GameObject.m_Transform); - if (rootTransform.m_GameObject.TryGet(out var rootGameObject)) - { - m_GameObject.CubismModel.Name = rootGameObject.m_Name; - } - } - private static void BindCubismRenderer(MonoBehaviour m_MonoBehaviour) { if (!m_MonoBehaviour.m_GameObject.TryGet(out var m_GameObject)) @@ -847,58 +831,57 @@ private static Transform GetRootTransform(Transform m_Transform) return m_Transform; } - private static List GenerateMocPathList(Dictionary mocDict, bool searchByFilename, ref bool useFullContainerPath) + private static Dictionary GenerateMocPathDict(Dictionary mocDict, Dictionary assetContainers, bool searchByFilename) { - var mocPathDict = new Dictionary(); - var mocPathList = new List(); + var tempMocPathDict = new Dictionary(); + var mocPathDict = new Dictionary(); foreach (var mocMono in l2dModelDict.Keys) { - if (!containers.TryGetValue(mocMono, out var containerPath)) + if (!containers.TryGetValue(mocMono, out var fullContainerPath)) continue; - var fullContainerPath = searchByFilename - ? l2dModelDict[mocMono]?.Container ?? containerPath - : containerPath; var pathSepIndex = fullContainerPath.LastIndexOf('/'); var basePath = pathSepIndex > 0 ? fullContainerPath.Substring(0, pathSepIndex) : fullContainerPath; - mocPathDict.Add(mocMono, (fullContainerPath, basePath)); + tempMocPathDict.Add(mocMono, (fullContainerPath, basePath)); } - if (mocPathDict.Count > 0) + if (tempMocPathDict.Count > 0) { - var basePathSet = mocPathDict.Values.Select(x => x.Item2).ToHashSet(); - useFullContainerPath = mocPathDict.Count != basePathSet.Count; + var basePathSet = tempMocPathDict.Values.Select(x => x.Item2).ToHashSet(); + var useFullContainerPath = tempMocPathDict.Count != basePathSet.Count; foreach (var moc in mocDict.Keys) { var mocPath = useFullContainerPath - ? mocPathDict[moc].Item1 //fullContainerPath - : mocPathDict[moc].Item2; //basePath + ? tempMocPathDict[moc].Item1 //fullContainerPath + : tempMocPathDict[moc].Item2; //basePath if (searchByFilename) { - mocPathList.Add(containers[moc]); + mocPathDict.Add(moc, assetContainers[moc]); if (mocDict.TryGetValue(moc, out var model) && model != null) model.Container = mocPath; } else { - mocPathList.Add(mocPath); + mocPathDict.Add(moc, mocPath); } } - mocPathDict.Clear(); + tempMocPathDict.Clear(); } - return mocPathList; + return mocPathDict; } public static void ExportLive2D() { var baseDestPath = Path.Combine(CLIOptions.o_outputFolder.Value, "Live2DOutput"); - var useFullContainerPath = true; 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; //TODO: filter by name + var l2dContainers = searchByFilename + ? new Dictionary() + : containers; if (l2dModelDict.Count == 0) { @@ -907,41 +890,50 @@ public static void ExportLive2D() } Progress.Reset(); - Logger.Info($"Searching for Live2D files..."); + Logger.Info("Searching for Live2D files..."); - var mocPathList = GenerateMocPathList(mocDict, searchByFilename, ref useFullContainerPath); + if (searchByFilename) + { + foreach (var assetKvp in containers) + { + l2dContainers[assetKvp.Key] = Path.GetFileName(assetKvp.Key.assetsFile.originalPath); + } + } + var mocPathDict = GenerateMocPathDict(mocDict, l2dContainers, searchByFilename); -#if NET9_0_OR_GREATER - var assetDict = new Dictionary>(); - foreach (var (asset, container) in containers) + var assetDict = new Dictionary>(); + foreach (var mocKvp in mocPathDict) + { + var mocPath = mocKvp.Value; + var result = l2dContainers.Select(assetKvp => { - var result = mocPathList.Find(mocPath => - { - if (!container.Contains(mocPath)) - return false; - var mocPathSpan = mocPath.AsSpan(); - var mocPathLastSlice = mocPathSpan[(mocPathSpan.LastIndexOf('/') + 1)..]; - foreach (var range in container.AsSpan().Split('/')) - { - if (mocPathLastSlice.SequenceEqual(container.AsSpan()[range])) - return true; - } - return false; - }); - if (result != null) + 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 (assetDict.TryGetValue(result, out var assets)) - assets.Add(asset); - else - assetDict[result] = [asset]; + if (modelNameFromPath.SequenceEqual(assetKvp.Value.AsSpan()[range])) + return assetKvp.Key; } - } #else - var assetDict = containers.AsParallel().ToLookup( - x => mocPathList.Find(b => x.Value.Contains(b) && x.Value.Split('/').Any(y => y == b.Substring(b.LastIndexOf("/") + 1))), - x => x.Key - ).Where(x => x.Key != null).ToDictionary(x => x.Key, x => x.ToList()); + 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(); if (mocDict.Keys.First().serializedType?.m_Type == null && CLIOptions.o_assemblyPath.Value == "") { Logger.Warning("Specifying the assembly folder may be needed for proper extraction"); @@ -953,28 +945,34 @@ public static void ExportLive2D() var modelCounter = 0; Live2DExtractor.MocDict = mocDict; Live2DExtractor.Assembly = assemblyLoader; - foreach (var assetKvp in assetDict) + foreach (var assetGroupKvp in assetDict) { - var srcContainer = assetKvp.Key; + var srcContainer = containers[assetGroupKvp.Key]; Logger.Info($"[{modelCounter + 1}/{totalModelCount}] Exporting Live2D: \"{srcContainer.Color(Ansi.BrightCyan)}\""); try { - var cubismExtractor = new Live2DExtractor(assetKvp.Value); + var cubismExtractor = new Live2DExtractor(assetGroupKvp); string modelPath; - if (modelGroupOption == Live2DModelGroupOption.SourceFileName) - { - modelPath = Path.GetFileNameWithoutExtension(cubismExtractor.MocMono.assetsFile.originalPath); - } - else - { - var container = searchByFilename && cubismExtractor.Model != null - ? cubismExtractor.Model.Container - : srcContainer; - modelPath = Path.HasExtension(container) - ? container.Replace(Path.GetExtension(container), "") - : container; - } + switch (modelGroupOption) + { + case Live2DModelGroupOption.SourceFileName: + modelPath = Path.GetFileNameWithoutExtension(cubismExtractor.MocMono.assetsFile.originalPath); + break; + case Live2DModelGroupOption.ModelName: + modelPath = !string.IsNullOrEmpty(cubismExtractor.Model?.Name) + ? cubismExtractor.Model.Name + : Path.GetFileNameWithoutExtension(cubismExtractor.MocMono.assetsFile.originalPath); + break; + default: //ContainerPath + var container = searchByFilename && cubismExtractor.Model != null + ? cubismExtractor.Model.Container + : srcContainer; + modelPath = Path.HasExtension(container) + ? container.Replace(Path.GetExtension(container), "") + : container; + break; + } var destPath = Path.Combine(baseDestPath, modelPath) + Path.DirectorySeparatorChar; cubismExtractor.ExtractCubismModel(destPath, motionMode, forceBezier, parallelTaskCount); diff --git a/AssetStudioGUI/AssetStudioGUIForm.cs b/AssetStudioGUI/AssetStudioGUIForm.cs index 38d08245..fcffc5eb 100644 --- a/AssetStudioGUI/AssetStudioGUIForm.cs +++ b/AssetStudioGUI/AssetStudioGUIForm.cs @@ -1216,6 +1216,10 @@ private void PreviewMoc(AssetItem assetItem, MonoBehaviour m_MonoBehaviour) using (var cubismMoc = new CubismMoc(m_MonoBehaviour)) { var sb = new StringBuilder(); + if (Studio.l2dModelDict.TryGetValue(m_MonoBehaviour, out var model) && model != null) + { + sb.AppendLine($"Model Name: {model.Name}"); + } sb.AppendLine($"SDK Version: {cubismMoc.VersionDescription}"); if (cubismMoc.Version > 0) { @@ -1512,11 +1516,11 @@ private void StatusStripUpdate(string statusText) private void ResetForm() { Text = guiTitle; - assetsManager.Clear(); - assemblyLoader.Clear(); - exportableAssets.Clear(); - visibleAssets.Clear(); - l2dModelDict.Clear(); + Studio.assetsManager.Clear(); + Studio.assemblyLoader.Clear(); + Studio.exportableAssets.Clear(); + Studio.visibleAssets.Clear(); + Studio.l2dModelDict.Clear(); sceneTreeView.Nodes.Clear(); assetListView.VirtualListSize = 0; assetListView.Items.Clear(); diff --git a/AssetStudioGUI/ExportOptions.Designer.cs b/AssetStudioGUI/ExportOptions.Designer.cs index b08bcfd8..e7ec9358 100644 --- a/AssetStudioGUI/ExportOptions.Designer.cs +++ b/AssetStudioGUI/ExportOptions.Designer.cs @@ -379,7 +379,8 @@ private void InitializeComponent() this.l2dAssetSearchByFilenameCheckBox.Size = new System.Drawing.Size(270, 17); this.l2dAssetSearchByFilenameCheckBox.TabIndex = 3; this.l2dAssetSearchByFilenameCheckBox.Text = "Search for model-related Live2D assets by file name"; - this.optionTooltip.SetToolTip(this.l2dAssetSearchByFilenameCheckBox, "Preferred option when all model-related assets are stored in a single file"); + this.optionTooltip.SetToolTip(this.l2dAssetSearchByFilenameCheckBox, "Preferred option if all l2d assets of a single model are stored in a single file " + + "or containers are obfuscated"); this.l2dAssetSearchByFilenameCheckBox.UseVisualStyleBackColor = true; // // l2dModelGroupComboBox @@ -388,7 +389,8 @@ private void InitializeComponent() this.l2dModelGroupComboBox.FormattingEnabled = true; this.l2dModelGroupComboBox.Items.AddRange(new object[] { "container path", - "source file name"}); + "source file name", + "model name"}); this.l2dModelGroupComboBox.Location = new System.Drawing.Point(142, 18); this.l2dModelGroupComboBox.Name = "l2dModelGroupComboBox"; this.l2dModelGroupComboBox.Size = new System.Drawing.Size(154, 21); diff --git a/AssetStudioGUI/Studio.cs b/AssetStudioGUI/Studio.cs index 0b5b87e3..2ee55cf2 100644 --- a/AssetStudioGUI/Studio.cs +++ b/AssetStudioGUI/Studio.cs @@ -191,7 +191,6 @@ public static (string, List) BuildAssetData() var objectAssetItemDic = new Dictionary(objectCount); var containers = new List<(PPtr, string)>(); var tex2dArrayAssetList = new List(); - var l2dSearchByFilename = Properties.Settings.Default.l2dAssetSearchByFilename; l2dAssetContainers.Clear(); var i = 0; Progress.Reset(); @@ -215,10 +214,6 @@ public static (string, List) BuildAssetData() if (m_GameObject.CubismModel != null && TryGetCubismMoc(m_GameObject.CubismModel.CubismModelMono, out var mocMono)) { l2dModelDict[mocMono] = m_GameObject.CubismModel; - if (!m_GameObject.CubismModel.IsRoot) - { - FixCubismModelName(m_GameObject); - } } break; case Texture2D m_Texture2D: @@ -359,9 +354,7 @@ public static (string, List) BuildAssetData() case AnimationClip _: case Texture2D _: case MonoBehaviour _: - l2dAssetContainers[obj] = l2dSearchByFilename - ? Path.GetFileName(obj.assetsFile.originalPath) - : container; + l2dAssetContainers[obj] = container; break; } } @@ -1012,15 +1005,6 @@ private static bool TryGetCubismMoc(MonoBehaviour m_MonoBehaviour, out MonoBehav return mocPPtr.TryGet(out mocMono); } - private static void FixCubismModelName(GameObject m_GameObject) - { - var rootTransform = GetRootTransform(m_GameObject.m_Transform); - if (rootTransform.m_GameObject.TryGet(out var rootGameObject)) - { - m_GameObject.CubismModel.Name = rootGameObject.m_Name; - } - } - private static void BindCubismRenderer(MonoBehaviour m_MonoBehaviour) { if (!m_MonoBehaviour.m_GameObject.TryGet(out var m_GameObject)) @@ -1081,47 +1065,44 @@ private static Transform GetRootTransform(Transform m_Transform) return m_Transform; } - private static List GenerateMocPathList(Dictionary mocDict, bool searchByFilename, ref bool useFullContainerPath) + private static Dictionary GenerateMocPathDict(Dictionary mocDict, Dictionary assetContainers, bool searchByFilename) { - var mocPathDict = new Dictionary(); - var mocPathList = new List(); + var tempMocPathDict = new Dictionary(); + var mocPathDict = new Dictionary(); foreach (var mocMono in l2dModelDict.Keys) { - if (!l2dAssetContainers.TryGetValue(mocMono, out var containerPath)) + if (!l2dAssetContainers.TryGetValue(mocMono, out var fullContainerPath)) continue; - var fullContainerPath = searchByFilename - ? l2dModelDict[mocMono]?.Container ?? containerPath - : containerPath; var pathSepIndex = fullContainerPath.LastIndexOf('/'); var basePath = pathSepIndex > 0 ? fullContainerPath.Substring(0, pathSepIndex) : fullContainerPath; - mocPathDict.Add(mocMono, (fullContainerPath, basePath)); + tempMocPathDict.Add(mocMono, (fullContainerPath, basePath)); } - if (mocPathDict.Count > 0) + if (tempMocPathDict.Count > 0) { - var basePathSet = mocPathDict.Values.Select(x => x.Item2).ToHashSet(); - useFullContainerPath = mocPathDict.Count != basePathSet.Count; + var basePathSet = tempMocPathDict.Values.Select(x => x.Item2).ToHashSet(); + var useFullContainerPath = tempMocPathDict.Count != basePathSet.Count; foreach (var moc in mocDict.Keys) { var mocPath = useFullContainerPath - ? mocPathDict[moc].Item1 //fullContainerPath - : mocPathDict[moc].Item2; //basePath + ? tempMocPathDict[moc].Item1 //fullContainerPath + : tempMocPathDict[moc].Item2; //basePath if (searchByFilename) { - mocPathList.Add(l2dAssetContainers[moc]); + mocPathDict.Add(moc, assetContainers[moc]); if (mocDict.TryGetValue(moc, out var model) && model != null) model.Container = mocPath; } else { - mocPathList.Add(mocPath); + mocPathDict.Add(moc, mocPath); } } - mocPathDict.Clear(); + tempMocPathDict.Clear(); } - return mocPathList; + return mocPathDict; } public static void ExportLive2D(string exportPath, List selMocs = null, List selClipMotions = null, List selFadeMotions = null, MonoBehaviour selFadeLst = null) @@ -1138,46 +1119,57 @@ public static void ExportLive2D(string exportPath, List selMocs = var mocDict = selMocs != null ? selMocs.ToDictionary(moc => moc, moc => l2dModelDict[moc]) : l2dModelDict; + var l2dContainers = searchByFilename + ? new Dictionary() + : l2dAssetContainers; ThreadPool.QueueUserWorkItem(state => { Logger.Info("Searching for Live2D assets..."); - var useFullContainerPath = true; - var mocPathList = GenerateMocPathList(mocDict, searchByFilename, ref useFullContainerPath); + if (searchByFilename) + { + foreach (var assetKvp in l2dAssetContainers) + { + l2dContainers[assetKvp.Key] = Path.GetFileName(assetKvp.Key.assetsFile.originalPath); + } + } + var mocPathDict = GenerateMocPathDict(mocDict, l2dContainers, searchByFilename); -#if NET9_0_OR_GREATER - var assetDict = new Dictionary>(); - foreach (var (asset, container) in l2dAssetContainers) + var assetDict = new Dictionary>(); + foreach (var mocKvp in mocPathDict) { - var result = mocPathList.Find(mocPath => + var mocPath = mocKvp.Value; + var result = l2dContainers.Select(assetKvp => { - if (!container.Contains(mocPath)) - return false; + if (!assetKvp.Value.Contains(mocPath)) + return null; var mocPathSpan = mocPath.AsSpan(); - var mocPathLastSlice = mocPathSpan[(mocPathSpan.LastIndexOf('/') + 1)..]; - foreach (var range in container.AsSpan().Split('/')) + var modelNameFromPath = mocPathSpan.Slice(mocPathSpan.LastIndexOf('/') + 1); +#if NET9_0_OR_GREATER + foreach (var range in assetKvp.Value.AsSpan().Split('/')) { - if (mocPathLastSlice.SequenceEqual(container.AsSpan()[range])) - return true; + if (modelNameFromPath.SequenceEqual(assetKvp.Value.AsSpan()[range])) + return assetKvp.Key; } - return false; - }); - if (result != null) - { - if (assetDict.TryGetValue(result, out var assets)) - assets.Add(asset); - else - assetDict[result] = [asset]; - } - } #else - var assetDict = l2dAssetContainers.AsParallel().ToLookup( - x => mocPathList.Find(b => x.Value.Contains(b) && x.Value.Split('/').Any(y => y == b.Substring(b.LastIndexOf("/") + 1))), - x => x.Key - ).Where(x => x.Key != null).ToDictionary(x=> x.Key, x => x.ToList()); + 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(); if (mocDict.Keys.First().serializedType?.m_Type == null && !assemblyLoader.Loaded) { Logger.Warning("Specifying the assembly folder may be needed for proper extraction"); @@ -1192,27 +1184,33 @@ public static void ExportLive2D(string exportPath, List selMocs = parallelExportCount = Properties.Settings.Default.parallelExport ? parallelExportCount : 1; Live2DExtractor.MocDict = mocDict; Live2DExtractor.Assembly = assemblyLoader; - foreach (var assetKvp in assetDict) + foreach (var assetGroupKvp in assetDict) { - var srcContainer = assetKvp.Key; + var srcContainer = l2dAssetContainers[assetGroupKvp.Key]; Logger.Info($"[{modelCounter + 1}/{totalModelCount}] Exporting Live2D: \"{srcContainer}\"..."); try { - var cubismExtractor = new Live2DExtractor(assetKvp.Value, selClipMotions, selFadeMotions, selFadeLst); + var cubismExtractor = new Live2DExtractor(assetGroupKvp, selClipMotions, selFadeMotions, selFadeLst); string modelPath; - if (modelGroupOption == Live2DModelGroupOption.SourceFileName) - { - modelPath = Path.GetFileNameWithoutExtension(cubismExtractor.MocMono.assetsFile.originalPath); - } - else + switch (modelGroupOption) { - var container = searchByFilename && cubismExtractor.Model != null - ? cubismExtractor.Model.Container - : srcContainer; - modelPath = Path.HasExtension(container) - ? container.Replace(Path.GetExtension(container), "") - : container; + case Live2DModelGroupOption.SourceFileName: + modelPath = Path.GetFileNameWithoutExtension(cubismExtractor.MocMono.assetsFile.originalPath); + break; + case Live2DModelGroupOption.ModelName: + modelPath = !string.IsNullOrEmpty(cubismExtractor.Model?.Name) + ? cubismExtractor.Model.Name + : Path.GetFileNameWithoutExtension(cubismExtractor.MocMono.assetsFile.originalPath); + break; + default: //ContainerPath + var container = searchByFilename && cubismExtractor.Model != null + ? cubismExtractor.Model.Container + : srcContainer; + modelPath = Path.HasExtension(container) + ? container.Replace(Path.GetExtension(container), "") + : container; + break; } var destPath = Path.Combine(baseDestPath, modelPath) + Path.DirectorySeparatorChar; diff --git a/AssetStudioUtility/CubismLive2DExtractor/Live2DExtractor.cs b/AssetStudioUtility/CubismLive2DExtractor/Live2DExtractor.cs index bf09603d..c8ca1ce0 100644 --- a/AssetStudioUtility/CubismLive2DExtractor/Live2DExtractor.cs +++ b/AssetStudioUtility/CubismLive2DExtractor/Live2DExtractor.cs @@ -41,7 +41,7 @@ public sealed class Live2DExtractor private HashSet EyeBlinkParameters { get; set; } private HashSet LipSyncParameters { get; set; } - public Live2DExtractor(List assets, List inClipMotions = null, List inFadeMotions = null, MonoBehaviour inFadeMotionLst = null) + public Live2DExtractor(KeyValuePair> assetGroupKvp, List inClipMotions = null, List inFadeMotions = null, MonoBehaviour inFadeMotionLst = null) { Expressions = new List(); FadeMotions = inFadeMotions ?? new List(); @@ -63,7 +63,48 @@ public Live2DExtractor(List assets, List inClipMotions = var searchPoseParts = true; Logger.Debug("Sorting model assets.."); - foreach (var asset in assets) + + MocMono = assetGroupKvp.Key; + if (MocDict.TryGetValue(MocMono, out var model) && model != null) + { + Model = model; + PhysicsMono = Model.PhysicsController; + if (inFadeMotionLst == null && TryGetFadeList(Model.FadeController, out var fadeMono)) + { + FadeMotionLst = inFadeMotionLst = fadeMono; + } + if (TryGetExpressionList(Model.ExpressionController, out var expressionMono)) + { + ExpressionLst = expressionMono; + } + if (Model.RenderTextureList.Count > 0) + { + var renderList = Model.RenderTextureList; + foreach (var renderMono in renderList) + { + if (!TryGetRenderTexture(renderMono, out var tex)) + break; + renderTextureSet.Add(tex); + } + searchRenderTextures = renderTextureSet.Count == 0; + } + if (Model.ParamDisplayInfoList.Count > 0) + { + ParametersCdi = Model.ParamDisplayInfoList; + searchModelParamCdi = false; + } + if (Model.PartDisplayInfoList.Count > 0) + { + PartsCdi = Model.PartDisplayInfoList; + searchModelPartCdi = false; + } + if (Model.PosePartList.Count > 0) + { + PoseParts = Model.PosePartList; + searchPoseParts = false; + } + } + foreach (var asset in assetGroupKvp.Value) { switch (asset) { @@ -72,48 +113,6 @@ public Live2DExtractor(List assets, List inClipMotions = { switch (m_Script.m_ClassName) { - case "CubismMoc": - MocMono = m_MonoBehaviour; - Model = MocDict[MocMono]; - if (Model != null) - { - PhysicsMono = Model.PhysicsController; - if (inFadeMotionLst == null && TryGetFadeList(Model.FadeController, out var fadeMono)) - { - FadeMotionLst = inFadeMotionLst = fadeMono; - } - if (TryGetExpressionList(Model.ExpressionController, out var expressionMono)) - { - ExpressionLst = expressionMono; - } - if (Model.RenderTextureList.Count > 0) - { - var renderList = Model.RenderTextureList; - foreach (var renderMono in renderList) - { - if (!TryGetRenderTexture(renderMono, out var tex)) - break; - renderTextureSet.Add(tex); - } - searchRenderTextures = renderTextureSet.Count == 0; - } - if (Model.ParamDisplayInfoList.Count > 0) - { - ParametersCdi = Model.ParamDisplayInfoList; - searchModelParamCdi = false; - } - if (Model.PartDisplayInfoList.Count > 0) - { - PartsCdi = Model.PartDisplayInfoList; - searchModelPartCdi = false; - } - if (Model.PosePartList.Count > 0) - { - PoseParts = Model.PosePartList; - searchPoseParts = false; - } - } - break; case "CubismPhysicsController": if (PhysicsMono == null) PhysicsMono = m_MonoBehaviour; @@ -252,7 +251,11 @@ public void ExtractCubismModel(string destPath, Live2DMotionMode motionMode, boo { var savePath = $"{destTexturePath}{texture2D.m_Name}.png"; if (!savePathHash.TryAdd(savePath, true)) - return; + { + savePath = $"{destTexturePath}{texture2D.m_Name}_#{texture2D.GetHashCode()}.png"; + if (!savePathHash.TryAdd(savePath, true)) + return; + } using (var image = texture2D.ConvertToImage(flip: true)) { @@ -293,19 +296,19 @@ public void ExtractCubismModel(string destPath, Live2DMotionMode motionMode, boo if (fadeMotionLstDict != null) { var cubismFadeList = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(fadeMotionLstDict)); - var fadeMotionAssetList = new List(); + var fadeMotionAssetSet = new HashSet(); foreach (var motionPPtr in cubismFadeList.CubismFadeMotionObjects) { if (motionPPtr.TryGet(out var fadeMono, FadeMotionLst.assetsFile)) { - fadeMotionAssetList.Add(fadeMono); + fadeMotionAssetSet.Add(fadeMono); } } - if (fadeMotionAssetList.Count > 0) + if (fadeMotionAssetSet.Count > 0) { - FadeMotions = fadeMotionAssetList; - Logger.Debug($"\"{FadeMotionLst.m_Name}\": found {fadeMotionAssetList.Count} motion(s)"); + FadeMotions = fadeMotionAssetSet.ToList(); + Logger.Debug($"\"{FadeMotionLst.m_Name}\": found {fadeMotionAssetSet.Count} motion(s)"); } } } @@ -361,19 +364,19 @@ public void ExtractCubismModel(string destPath, Live2DMotionMode motionMode, boo if (expLstDict != null) { var cubismExpList = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(expLstDict)); - var expAssetList = new List(); + var expAssetSet = new HashSet(); foreach (var expPPtr in cubismExpList.CubismExpressionObjects) { if (expPPtr.TryGet(out var expMono, ExpressionLst.assetsFile)) { - expAssetList.Add(expMono); + expAssetSet.Add(expMono); } } - if (expAssetList.Count > 0) + if (expAssetSet.Count > 0) { - Expressions = expAssetList; - Logger.Debug($"\"{ExpressionLst.m_Name}\": found {expAssetList.Count} expression(s)"); + Expressions = expAssetSet.ToList(); + Logger.Debug($"\"{ExpressionLst.m_Name}\": found {expAssetSet.Count} expression(s)"); } } } diff --git a/AssetStudioUtility/CubismLive2DExtractor/Live2DModelGroupOption.cs b/AssetStudioUtility/CubismLive2DExtractor/Live2DModelGroupOption.cs index e15800e8..ca65be19 100644 --- a/AssetStudioUtility/CubismLive2DExtractor/Live2DModelGroupOption.cs +++ b/AssetStudioUtility/CubismLive2DExtractor/Live2DModelGroupOption.cs @@ -4,5 +4,6 @@ public enum Live2DModelGroupOption { ContainerPath, SourceFileName, + ModelName, } }