From cb84c137e511c97085633b250e084929b4aa0951 Mon Sep 17 00:00:00 2001 From: VaDiM Date: Sun, 24 Sep 2023 03:02:08 +0300 Subject: [PATCH 01/22] Add exception checking for gzip decompression --- AssetStudio/AssetsManager.cs | 2 +- AssetStudio/ImportHelper.cs | 21 +++++++++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/AssetStudio/AssetsManager.cs b/AssetStudio/AssetsManager.cs index 1bc3543..f243569 100644 --- a/AssetStudio/AssetsManager.cs +++ b/AssetStudio/AssetsManager.cs @@ -133,7 +133,7 @@ namespace AssetStudio private void LoadFile(FileReader reader) { - switch (reader.FileType) + switch (reader?.FileType) { case FileType.AssetsFile: LoadAssetsFile(reader); diff --git a/AssetStudio/ImportHelper.cs b/AssetStudio/ImportHelper.cs index d66c077..3e8f1a1 100644 --- a/AssetStudio/ImportHelper.cs +++ b/AssetStudio/ImportHelper.cs @@ -53,15 +53,24 @@ namespace AssetStudio public static FileReader DecompressGZip(FileReader reader) { - using (reader) + try { - var stream = new MemoryStream(); - using (var gs = new GZipStream(reader.BaseStream, CompressionMode.Decompress)) + using (reader) { - gs.CopyTo(stream); + var stream = new MemoryStream(); + using (var gs = new GZipStream(reader.BaseStream, CompressionMode.Decompress)) + { + gs.CopyTo(stream); + } + stream.Position = 0; + return new FileReader(reader.FullPath, stream); } - stream.Position = 0; - return new FileReader(reader.FullPath, stream); + } + catch (System.Exception e) + { + Logger.Warning($"Error while decompressing gzip file {reader.FullPath}\r\n{e}"); + reader.Dispose(); + return null; } } From a2bc935850d74329b61071a0453b020ec5ff77d9 Mon Sep 17 00:00:00 2001 From: VaDiM Date: Sun, 24 Sep 2023 16:14:26 +0300 Subject: [PATCH 02/22] [CLI] Improve help message --- AssetStudioCLI/Options/CLIOptions.cs | 49 ++++++++++++++++------------ 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/AssetStudioCLI/Options/CLIOptions.cs b/AssetStudioCLI/Options/CLIOptions.cs index 8e4ba52..5e5b8ea 100644 --- a/AssetStudioCLI/Options/CLIOptions.cs +++ b/AssetStudioCLI/Options/CLIOptions.cs @@ -418,7 +418,7 @@ namespace AssetStudioCLI.Options if (workModeOptionIndex + 1 >= resplittedArgs.Count) { Console.WriteLine($"{"Error during parsing options:".Color(brightRed)} Value for [{option.Color(brightRed)}] option was not found.\n"); - TryShowOptionDescription(option, optionsDict); + TryFindOptionDescription(option, optionsDict); return; } var value = resplittedArgs[workModeOptionIndex + 1]; @@ -463,7 +463,7 @@ namespace AssetStudioCLI.Options break; default: Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unsupported working mode: [{value.Color(brightRed)}].\n"); - Console.WriteLine(o_workMode.Description); + ShowOptionDescription(o_workMode.Description); return; } resplittedArgs.RemoveRange(workModeOptionIndex, 2); @@ -491,7 +491,7 @@ namespace AssetStudioCLI.Options break; default: Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{flag}] flag. This flag is not suitable for the current working mode [{o_workMode.Value}].\n"); - Console.WriteLine(f_loadAllAssets.Description); + ShowOptionDescription(f_loadAllAssets.Description, isFlag: true); return; } break; @@ -546,11 +546,11 @@ namespace AssetStudioCLI.Options else { Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unknown asset type specified [{type.Color(brightRed)}].\n"); - Console.WriteLine(o_exportAssetTypes.Description); + ShowOptionDescription(o_exportAssetTypes.Description); return; } Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Asset type [{type.Color(brightRed)}] is not supported for exporting.\n"); - Console.WriteLine(o_exportAssetTypes.Description); + ShowOptionDescription(o_exportAssetTypes.Description); return; } } @@ -576,7 +576,7 @@ namespace AssetStudioCLI.Options break; default: Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unsupported grouping option: [{value.Color(brightRed)}].\n"); - Console.WriteLine(o_groupAssetsBy.Description); + ShowOptionDescription(o_groupAssetsBy.Description); return; } break; @@ -627,7 +627,7 @@ namespace AssetStudioCLI.Options break; default: Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unsupported log level value: [{value.Color(brightRed)}].\n"); - Console.WriteLine(o_logLevel.Description); + ShowOptionDescription(o_logLevel.Description); return; } break; @@ -645,7 +645,7 @@ namespace AssetStudioCLI.Options break; default: Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unsupported log output mode: [{value.Color(brightRed)}].\n"); - Console.WriteLine(o_logOutput.Description); + ShowOptionDescription(o_logOutput.Description); return; } break; @@ -673,7 +673,7 @@ namespace AssetStudioCLI.Options break; default: Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unsupported image format: [{value.Color(brightRed)}].\n"); - Console.WriteLine(o_imageFormat.Description); + ShowOptionDescription(o_imageFormat.Description); return; } break; @@ -689,7 +689,7 @@ namespace AssetStudioCLI.Options break; default: Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unsupported audio format: [{value.Color(brightRed)}].\n"); - Console.WriteLine(o_audioFormat.Description); + ShowOptionDescription(o_audioFormat.Description); return; } break; @@ -702,7 +702,7 @@ namespace AssetStudioCLI.Options else { Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unsupported scale factor value: [{value.Color(brightRed)}].\n"); - Console.WriteLine(o_fbxScaleFactor.Description); + ShowOptionDescription(o_fbxScaleFactor.Description); return; } break; @@ -715,7 +715,7 @@ namespace AssetStudioCLI.Options else { Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unsupported bone size value: [{value.Color(brightRed)}].\n"); - Console.WriteLine(o_fbxBoneSize.Description); + ShowOptionDescription(o_fbxBoneSize.Description); return; } break; @@ -730,7 +730,7 @@ namespace AssetStudioCLI.Options break; default: Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unsupported asset list export option: [{value.Color(brightRed)}].\n"); - Console.WriteLine(o_exportAssetList.Description); + ShowOptionDescription(o_exportAssetList.Description); return; } break; @@ -767,9 +767,9 @@ namespace AssetStudioCLI.Options break; default: Console.WriteLine($"{"Error:".Color(brightRed)} Unknown option [{option.Color(brightRed)}].\n"); - if (!TryShowOptionDescription(option, optionsDict)) + if (!TryFindOptionDescription(option, optionsDict)) { - TryShowOptionDescription(option, flagsDict); + TryFindOptionDescription(option, flagsDict, isFlag: true); } return; } @@ -780,12 +780,12 @@ namespace AssetStudioCLI.Options if (optionsDict.Any(x => x.Key.Contains(option))) { Console.WriteLine($"{"Error during parsing options:".Color(brightRed)} Value for [{option.Color(brightRed)}] option was not found.\n"); - TryShowOptionDescription(option, optionsDict); + TryFindOptionDescription(option, optionsDict); } else if (flagsDict.Any(x => x.Key.Contains(option))) { Console.WriteLine($"{"Error:".Color(brightRed)} Unknown flag [{option.Color(brightRed)}].\n"); - TryShowOptionDescription(option, flagsDict); + TryFindOptionDescription(option, flagsDict, isFlag: true); } else { @@ -824,14 +824,21 @@ namespace AssetStudioCLI.Options return value.Split(separator); } - private static bool TryShowOptionDescription(string option, Dictionary descDict) + private static void ShowOptionDescription(string desc, bool isFlag = false) { - var optionDesc = descDict.Where(x => x.Key.Contains(option)); + var arg = isFlag ? "Flag" : "Option"; + Console.WriteLine($"{arg} description:\n{desc}"); + } + + private static bool TryFindOptionDescription(string option, Dictionary dict, bool isFlag = false) + { + var optionDesc = dict.Where(x => x.Key.Contains(option)); if (optionDesc.Any()) { + var arg = isFlag ? "flag" : "option"; var rand = new Random(); var rndOption = optionDesc.ElementAt(rand.Next(0, optionDesc.Count())); - Console.WriteLine($"Did you mean [{ $"{rndOption.Key}".Color(CLIAnsiColors.BrightYellow) }] option?"); + Console.WriteLine($"Did you mean [{$"{rndOption.Key}".Color(CLIAnsiColors.BrightYellow)}] {arg}?"); Console.WriteLine($"Here's a description of it: \n\n{rndOption.Value}"); return true; @@ -924,7 +931,7 @@ namespace AssetStudioCLI.Options case WorkMode.ExportRaw: case WorkMode.Dump: sb.AppendLine($"# Output Path: \"{o_outputFolder}\""); - if (o_workMode.Value != WorkMode.Export) + if (o_workMode.Value != WorkMode.Export) { sb.AppendLine($"# Load All Assets: {f_loadAllAssets}"); } From 22ab5c06335cc6a680e9fb63e5e87d5bf854047d Mon Sep 17 00:00:00 2001 From: VaDiM Date: Sun, 24 Sep 2023 16:18:22 +0300 Subject: [PATCH 03/22] Update README.md - Added ArknightsStudio link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ccbad86..fb17033 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Unfortunately, I can't continue Perfare's work and keep AssetStudio up to date. ## Game specific modifications -- ArknightsStudio - soon™ +- [ArknightsStudio](https://github.com/aelurum/AssetStudio/tree/ArknightsStudio) ## AssetStudio Features From ed7b0a241599b4ab121526132a0e40bf3bd5bd0a Mon Sep 17 00:00:00 2001 From: VaDiM Date: Wed, 27 Sep 2023 23:14:15 +0300 Subject: [PATCH 04/22] Unity 2022.2+ AnimationClip fix --- AssetStudio/Classes/AnimationClip.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/AssetStudio/Classes/AnimationClip.cs b/AssetStudio/Classes/AnimationClip.cs index 5769838..cf6220f 100644 --- a/AssetStudio/Classes/AnimationClip.cs +++ b/AssetStudio/Classes/AnimationClip.cs @@ -15,7 +15,6 @@ namespace AssetStudio public T inWeight; public T outWeight; - public Keyframe(ObjectReader reader, Func readerFunc) { time = reader.ReadSingle(); @@ -294,15 +293,20 @@ namespace AssetStudio public string path; public ClassIDType classID; public PPtr script; - + public int flags; public FloatCurve(ObjectReader reader) { + var version = reader.version; curve = new AnimationCurve(reader, reader.ReadSingle); attribute = reader.ReadAlignedString(); path = reader.ReadAlignedString(); classID = (ClassIDType)reader.ReadInt32(); script = new PPtr(reader); + if (version[0] > 2022 || (version[0] == 2022 && version[1] >= 2)) //2022.2 and up + { + flags = reader.ReadInt32(); + } } } @@ -311,7 +315,6 @@ namespace AssetStudio public float time; public PPtr value; - public PPtrKeyframe(ObjectReader reader) { time = reader.ReadSingle(); @@ -326,10 +329,11 @@ namespace AssetStudio public string path; public int classID; public PPtr script; - + public int flags; public PPtrCurve(ObjectReader reader) { + var version = reader.version; int numCurves = reader.ReadInt32(); curve = new PPtrKeyframe[numCurves]; for (int i = 0; i < numCurves; i++) @@ -341,6 +345,10 @@ namespace AssetStudio path = reader.ReadAlignedString(); classID = reader.ReadInt32(); script = new PPtr(reader); + if (version[0] > 2022 || (version[0] == 2022 && version[1] >= 2)) //2022.2 and up + { + flags = reader.ReadInt32(); + } } } @@ -940,7 +948,6 @@ namespace AssetStudio public AnimationClipBindingConstant m_ClipBindingConstant; public AnimationEvent[] m_Events; - public AnimationClip(ObjectReader reader) : base(reader) { if (version[0] >= 5)//5.0 and up From 75ebe677132deb0e7f8b9bfff430ab881b6d480f Mon Sep 17 00:00:00 2001 From: Evaldas Ciakas Date: Wed, 1 Nov 2023 02:29:06 +0200 Subject: [PATCH 05/22] Fix parsing secondary textures --- AssetStudio/Classes/Sprite.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AssetStudio/Classes/Sprite.cs b/AssetStudio/Classes/Sprite.cs index 752794f..2e597e6 100644 --- a/AssetStudio/Classes/Sprite.cs +++ b/AssetStudio/Classes/Sprite.cs @@ -12,7 +12,7 @@ namespace AssetStudio public SecondarySpriteTexture(ObjectReader reader) { texture = new PPtr(reader); - name = reader.ReadStringToNull(); + name = reader.ReadAlignedString(); } } From 45bf9251c95013141fe620835f764f13b66afb49 Mon Sep 17 00:00:00 2001 From: VaDiM Date: Sat, 28 Oct 2023 02:14:59 +0300 Subject: [PATCH 06/22] Fix crash when SpriteAtlas is missing --- AssetStudio/AssetsManager.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/AssetStudio/AssetsManager.cs b/AssetStudio/AssetsManager.cs index f243569..b00ef27 100644 --- a/AssetStudio/AssetsManager.cs +++ b/AssetStudio/AssetsManager.cs @@ -41,7 +41,7 @@ namespace AssetStudio { filteredAssetTypesList.Add(ClassIDType.Texture2D); } - + filteredAssetTypesList.UnionWith(classIDTypes); } @@ -635,14 +635,17 @@ namespace AssetStudio { m_Sprite.m_SpriteAtlas.Set(m_SpriteAtlas); } - else + else if (m_Sprite.m_SpriteAtlas.TryGet(out var m_SpriteAtlaOld)) { - m_Sprite.m_SpriteAtlas.TryGet(out var m_SpriteAtlaOld); if (m_SpriteAtlaOld.m_IsVariant) { m_Sprite.m_SpriteAtlas.Set(m_SpriteAtlas); } } + else + { + Logger.Warning($"\"{m_Sprite.m_Name}\": Sprite loading error. SpriteAtlas with PathID: \"{m_Sprite.m_SpriteAtlas.m_PathID}\" was not found."); + } } } } From e41574037387ac29ba52851acf1f9d1f4727398d Mon Sep 17 00:00:00 2001 From: VaDiM Date: Wed, 8 Nov 2023 01:25:32 +0300 Subject: [PATCH 07/22] Some fixes for Live2D export --- AssetStudioCLI/Studio.cs | 39 +++-- AssetStudioGUI/Studio.cs | 46 +++-- .../CubismLive2DExtractor/Live2DExtractor.cs | 158 +++++++++--------- 3 files changed, 143 insertions(+), 100 deletions(-) diff --git a/AssetStudioCLI/Studio.cs b/AssetStudioCLI/Studio.cs index 1e65df0..12f3529 100644 --- a/AssetStudioCLI/Studio.cs +++ b/AssetStudioCLI/Studio.cs @@ -624,6 +624,7 @@ namespace AssetStudioCLI } return false; }).Select(x => x.Asset).ToArray(); + if (cubismMocs.Length == 0) { Logger.Default.Log(LoggerEvent.Info, "Live2D Cubism models were not found.", ignoreLevel: true); @@ -631,7 +632,18 @@ namespace AssetStudioCLI } if (cubismMocs.Length > 1) { - var basePathSet = cubismMocs.Select(x => containers[x].Substring(0, containers[x].LastIndexOf("/"))).ToHashSet(); + var basePathSet = cubismMocs.Select(x => + { + var pathLen = containers.TryGetValue(x, out var itemContainer) ? itemContainer.LastIndexOf("/") : 0; + pathLen = pathLen < 0 ? containers[x].Length : pathLen; + return itemContainer?.Substring(0, pathLen); + }).ToHashSet(); + + if (basePathSet.All(x => x == null)) + { + Logger.Error($"Live2D Cubism export error: Cannot find any model related files."); + return; + } if (basePathSet.Count != cubismMocs.Length) { @@ -639,9 +651,16 @@ namespace AssetStudioCLI Logger.Debug($"useFullContainerPath: {useFullContainerPath}"); } } - var basePathList = useFullContainerPath ? - cubismMocs.Select(x => containers[x]).ToList() : - cubismMocs.Select(x => containers[x].Substring(0, containers[x].LastIndexOf("/"))).ToList(); + + var basePathList = cubismMocs.Select(x => + { + containers.TryGetValue(x, out var container); + container = useFullContainerPath + ? container + : container?.Substring(0, container.LastIndexOf("/")); + return container; + }).Where(x => x != null).ToList(); + var lookup = containers.ToLookup( x => basePathList.Find(b => x.Value.Contains(b) && x.Value.Split('/').Any(y => y == b.Substring(b.LastIndexOf("/") + 1))), x => x.Key @@ -649,16 +668,15 @@ namespace AssetStudioCLI var totalModelCount = lookup.LongCount(x => x.Key != null); Logger.Info($"Found {totalModelCount} model(s)."); - var name = ""; var modelCounter = 0; foreach (var assets in lookup) { - var container = assets.Key; - if (container == null) + var srcContainer = assets.Key; + if (srcContainer == null) continue; - name = container; + var container = srcContainer; - Logger.Info($"[{modelCounter + 1}/{totalModelCount}] Exporting Live2D: \"{container.Color(Ansi.BrightCyan)}\""); + Logger.Info($"[{modelCounter + 1}/{totalModelCount}] Exporting Live2D: \"{srcContainer.Color(Ansi.BrightCyan)}\""); try { var modelName = useFullContainerPath ? Path.GetFileNameWithoutExtension(container) : container.Substring(container.LastIndexOf('/') + 1); @@ -670,10 +688,11 @@ namespace AssetStudioCLI } catch (Exception ex) { - Logger.Error($"Live2D model export error: \"{name}\"", ex); + Logger.Error($"Live2D model export error: \"{srcContainer}\"", ex); } Progress.Report(modelCounter, (int)totalModelCount); } + var status = modelCounter > 0 ? $"Finished exporting [{modelCounter}/{totalModelCount}] Live2D model(s) to \"{CLIOptions.o_outputFolder.Value.Color(Ansi.BrightCyan)}\"" : "Nothing exported."; diff --git a/AssetStudioGUI/Studio.cs b/AssetStudioGUI/Studio.cs index 6d55393..8b0acc9 100644 --- a/AssetStudioGUI/Studio.cs +++ b/AssetStudioGUI/Studio.cs @@ -259,6 +259,7 @@ namespace AssetStudioGUI Progress.Report(++i, objectCount); } } + allContainers.Clear(); foreach ((var pptr, var container) in containers) { if (pptr.TryGet(out var obj)) @@ -753,32 +754,51 @@ namespace AssetStudioGUI var useFullContainerPath = false; if (cubismMocs.Length > 1) { - var basePathSet = cubismMocs.Select(x => allContainers[x].Substring(0, allContainers[x].LastIndexOf("/"))).ToHashSet(); + var basePathSet = cubismMocs.Select(x => + { + var pathLen = allContainers.TryGetValue(x, out var itemContainer) ? itemContainer.LastIndexOf("/") : 0; + pathLen = pathLen < 0 ? allContainers[x].Length : pathLen; + return itemContainer?.Substring(0, pathLen); + }).ToHashSet(); + + if (basePathSet.All(x => x == null)) + { + Logger.Error($"Live2D Cubism export error\r\nCannot find any model related files"); + StatusStripUpdate("Live2D export canceled"); + Progress.Reset(); + return; + } if (basePathSet.Count != cubismMocs.Length) { useFullContainerPath = true; } } - var basePathList = useFullContainerPath ? - cubismMocs.Select(x => allContainers[x]).ToList() : - cubismMocs.Select(x => allContainers[x].Substring(0, allContainers[x].LastIndexOf("/"))).ToList(); + + var basePathList = cubismMocs.Select(x => + { + allContainers.TryGetValue(x, out var container); + container = useFullContainerPath + ? container + : container?.Substring(0, container.LastIndexOf("/")); + return container; + }).Where(x => x != null).ToList(); + var lookup = allContainers.ToLookup( x => basePathList.Find(b => x.Value.Contains(b) && x.Value.Split('/').Any(y => y == b.Substring(b.LastIndexOf("/") + 1))), x => x.Key ); var totalModelCount = lookup.LongCount(x => x.Key != null); - var name = ""; var modelCounter = 0; foreach (var assets in lookup) { - var container = assets.Key; - if (container == null) + var srcContainer = assets.Key; + if (srcContainer == null) continue; - name = container; + var container = srcContainer; - Logger.Info($"[{modelCounter + 1}/{totalModelCount}] Exporting Live2D: \"{container}\"..."); + Logger.Info($"[{modelCounter + 1}/{totalModelCount}] Exporting Live2D: \"{srcContainer}\"..."); try { var modelName = useFullContainerPath ? Path.GetFileNameWithoutExtension(container) : container.Substring(container.LastIndexOf('/') + 1); @@ -790,11 +810,17 @@ namespace AssetStudioGUI } catch (Exception ex) { - Logger.Error($"Live2D model export error: \"{name}\"", ex); + Logger.Error($"Live2D model export error: \"{srcContainer}\"", ex); } Progress.Report(modelCounter, (int)totalModelCount); } + Logger.Info($"Finished exporting [{modelCounter}/{totalModelCount}] Live2D model(s)."); + if (modelCounter < totalModelCount) + { + var total = (int)totalModelCount; + Progress.Report(total, total); + } if (Properties.Settings.Default.openAfterExport && modelCounter > 0) { OpenFolderInExplorer(exportPath); diff --git a/AssetStudioUtility/CubismLive2DExtractor/Live2DExtractor.cs b/AssetStudioUtility/CubismLive2DExtractor/Live2DExtractor.cs index c312fb6..db4530c 100644 --- a/AssetStudioUtility/CubismLive2DExtractor/Live2DExtractor.cs +++ b/AssetStudioUtility/CubismLive2DExtractor/Live2DExtractor.cs @@ -26,20 +26,57 @@ namespace CubismLive2DExtractor Directory.CreateDirectory(destPath); Directory.CreateDirectory(destTexturePath); - var monoBehaviours = new List(); - var texture2Ds = new List(); + var expressionList = new List(); var gameObjects = new List(); var animationClips = new List(); + var textures = new SortedSet(); + var eyeBlinkParameters = new HashSet(); + var lipSyncParameters = new HashSet(); + MonoBehaviour physics = null; + foreach (var asset in assets) { switch (asset) { case MonoBehaviour m_MonoBehaviour: - monoBehaviours.Add(m_MonoBehaviour); + if (m_MonoBehaviour.m_Script.TryGet(out var m_Script)) + { + switch (m_Script.m_ClassName) + { + case "CubismMoc": + File.WriteAllBytes($"{destPath}{modelName}.moc3", ParseMoc(m_MonoBehaviour)); //moc + break; + case "CubismPhysicsController": + physics = physics ?? m_MonoBehaviour; + break; + case "CubismExpressionData": + expressionList.Add(m_MonoBehaviour); + break; + case "CubismEyeBlinkParameter": + if (m_MonoBehaviour.m_GameObject.TryGet(out var blinkGameObject)) + { + eyeBlinkParameters.Add(blinkGameObject.m_Name); + } + break; + case "CubismMouthParameter": + if (m_MonoBehaviour.m_GameObject.TryGet(out var mouthGameObject)) + { + lipSyncParameters.Add(mouthGameObject.m_Name); + } + break; + } + } break; case Texture2D m_Texture2D: - texture2Ds.Add(m_Texture2D); + using (var image = m_Texture2D.ConvertToImage(flip: true)) + { + using (var file = File.OpenWrite($"{destTexturePath}{m_Texture2D.m_Name}.png")) + { + image.WriteToStream(file, ImageFormat.Png); + } + textures.Add($"textures/{m_Texture2D.m_Name}.png"); //texture + } break; case GameObject m_GameObject: gameObjects.Add(m_GameObject); @@ -50,15 +87,12 @@ namespace CubismLive2DExtractor } } - //physics - var physics = monoBehaviours.FirstOrDefault(x => + if (textures.Count == 0) { - if (x.m_Script.TryGet(out var m_Script)) - { - return m_Script.m_ClassName == "CubismPhysicsController"; - } - return false; - }); + Logger.Warning($"No textures found for \"{modelName}\" model."); + } + + //physics if (physics != null) { try @@ -73,31 +107,6 @@ namespace CubismLive2DExtractor } } - //moc - var moc = monoBehaviours.First(x => - { - if (x.m_Script.TryGet(out var m_Script)) - { - return m_Script.m_ClassName == "CubismMoc"; - } - return false; - }); - File.WriteAllBytes($"{destPath}{modelName}.moc3", ParseMoc(moc)); - - //texture - var textures = new SortedSet(); - foreach (var texture2D in texture2Ds) - { - using (var image = texture2D.ConvertToImage(flip: true)) - { - textures.Add($"textures/{texture2D.m_Name}.png"); - using (var file = File.OpenWrite($"{destTexturePath}{texture2D.m_Name}.png")) - { - image.WriteToStream(file, ImageFormat.Png); - } - } - } - //motion var motions = new SortedDictionary(); @@ -205,23 +214,30 @@ namespace CubismLive2DExtractor } json.Meta.TotalUserDataSize = totalUserDataSize; - var motionPath = new JObject(new JProperty("File", $"motions/{animation.Name}.motion3.json")); - motions.Add(animation.Name, new JArray(motionPath)); - File.WriteAllText($"{destMotionPath}{animation.Name}.motion3.json", JsonConvert.SerializeObject(json, Formatting.Indented, new MyJsonConverter())); + var animName = animation.Name; + if (motions.ContainsKey(animName)) + { + animName = $"{animName}_{animation.GetHashCode()}"; + if (motions.ContainsKey(animName)) + { + continue; + } + } + var motionPath = new JObject(new JProperty("File", $"motions/{animName}.motion3.json")); + motions.Add(animName, new JArray(motionPath)); + File.WriteAllText($"{destMotionPath}{animName}.motion3.json", JsonConvert.SerializeObject(json, Formatting.Indented, new MyJsonConverter())); } } //expression var expressions = new JArray(); - var monoBehaviourArray = monoBehaviours.Where(x => x.m_Name.EndsWith(".exp3")).ToArray(); - if (monoBehaviourArray.Length > 0) + if (expressionList.Count > 0) { Directory.CreateDirectory(destExpressionPath); } - foreach (var monoBehaviour in monoBehaviourArray) + foreach (var monoBehaviour in expressionList) { - var fullName = monoBehaviour.m_Name; - var expressionName = fullName.Replace(".exp3", ""); + var expressionName = monoBehaviour.m_Name.Replace(".exp3", ""); var expressionObj = monoBehaviour.ToType(); if (expressionObj == null) { @@ -238,57 +254,38 @@ namespace CubismLive2DExtractor expressions.Add(new JObject { { "Name", expressionName }, - { "File", $"expressions/{fullName}.json" } + { "File", $"expressions/{expressionName}.exp3.json" } }); - File.WriteAllText($"{destExpressionPath}{fullName}.json", JsonConvert.SerializeObject(expression, Formatting.Indented)); + File.WriteAllText($"{destExpressionPath}{expressionName}.exp3.json", JsonConvert.SerializeObject(expression, Formatting.Indented)); } - //model + //group var groups = new List(); - var eyeBlinkParameters = monoBehaviours.Where(x => - { - x.m_Script.TryGet(out var m_Script); - return m_Script?.m_ClassName == "CubismEyeBlinkParameter"; - }).Select(x => - { - x.m_GameObject.TryGet(out var m_GameObject); - return m_GameObject?.m_Name; - }).ToHashSet(); + //Try looking for group IDs among the gameObjects if (eyeBlinkParameters.Count == 0) { eyeBlinkParameters = gameObjects.Where(x => - { - return x.m_Name.ToLower().Contains("eye") + x.m_Name.ToLower().Contains("eye") && x.m_Name.ToLower().Contains("open") - && (x.m_Name.ToLower().Contains('l') || x.m_Name.ToLower().Contains('r')); - }).Select(x => x.m_Name).ToHashSet(); + && (x.m_Name.ToLower().Contains('l') || x.m_Name.ToLower().Contains('r')) + ).Select(x => x.m_Name).ToHashSet(); } + if (lipSyncParameters.Count == 0) + { + lipSyncParameters = gameObjects.Where(x => + x.m_Name.ToLower().Contains("mouth") + && x.m_Name.ToLower().Contains("open") + && x.m_Name.ToLower().Contains('y') + ).Select(x => x.m_Name).ToHashSet(); + } + groups.Add(new CubismModel3Json.SerializableGroup { Target = "Parameter", Name = "EyeBlink", Ids = eyeBlinkParameters.ToArray() }); - - var lipSyncParameters = monoBehaviours.Where(x => - { - x.m_Script.TryGet(out var m_Script); - return m_Script?.m_ClassName == "CubismMouthParameter"; - }).Select(x => - { - x.m_GameObject.TryGet(out var m_GameObject); - return m_GameObject?.m_Name; - }).ToHashSet(); - if (lipSyncParameters.Count == 0) - { - lipSyncParameters = gameObjects.Where(x => - { - return x.m_Name.ToLower().Contains("mouth") - && x.m_Name.ToLower().Contains("open") - && x.m_Name.ToLower().Contains('y'); - }).Select(x => x.m_Name).ToHashSet(); - } groups.Add(new CubismModel3Json.SerializableGroup { Target = "Parameter", @@ -296,6 +293,7 @@ namespace CubismLive2DExtractor Ids = lipSyncParameters.ToArray() }); + //model var model3 = new CubismModel3Json { Version = 3, From d572bd0e64158b08e0b6bbb85f6c1dda1861b1fa Mon Sep 17 00:00:00 2001 From: VaDiM Date: Sun, 19 Nov 2023 02:52:55 +0300 Subject: [PATCH 08/22] Add support for Live2D Fade motions --- AssetStudioCLI/Options/CLIOptions.cs | 75 ++++++- AssetStudioCLI/Studio.cs | 4 +- AssetStudioGUI/ExportOptions.Designer.cs | 156 ++++++++++---- AssetStudioGUI/ExportOptions.cs | 29 +-- AssetStudioGUI/ExportOptions.resx | 2 +- .../Properties/Settings.Designer.cs | 26 ++- AssetStudioGUI/Properties/Settings.settings | 6 + AssetStudioGUI/Studio.cs | 4 +- .../CubismLive2DExtractor/CubismFadeMotion.cs | 30 +++ .../CubismKeyframeData.cs | 26 +++ .../CubismMotion3Json.cs | 199 +++++++++++++++++- .../CubismLive2DExtractor/Live2DExtractor.cs | 143 +++++-------- .../CubismLive2DExtractor/Live2DMotionMode.cs | 8 + 13 files changed, 547 insertions(+), 161 deletions(-) create mode 100644 AssetStudioUtility/CubismLive2DExtractor/CubismFadeMotion.cs create mode 100644 AssetStudioUtility/CubismLive2DExtractor/CubismKeyframeData.cs create mode 100644 AssetStudioUtility/CubismLive2DExtractor/Live2DMotionMode.cs diff --git a/AssetStudioCLI/Options/CLIOptions.cs b/AssetStudioCLI/Options/CLIOptions.cs index 5e5b8ea..7c09332 100644 --- a/AssetStudioCLI/Options/CLIOptions.cs +++ b/AssetStudioCLI/Options/CLIOptions.cs @@ -12,6 +12,7 @@ namespace AssetStudioCLI.Options General, Convert, Logger, + Live2D, FBX, Filter, Advanced, @@ -83,6 +84,9 @@ namespace AssetStudioCLI.Options public static bool convertTexture; public static Option o_imageFormat; public static Option o_audioFormat; + //live2d + public static Option o_l2dMotionMode; + public static Option f_l2dForceBezier; //fbx public static Option o_fbxScaleFactor; public static Option o_fbxBoneSize; @@ -203,7 +207,7 @@ namespace AssetStudioCLI.Options optionDefaultValue: "ASExport", optionName: "-o, --output ", optionDescription: "Specify path to the output folder\n" + - "If path isn't specifyed, 'ASExport' folder will be created in the program's work folder\n", + "If path isn't specified, 'ASExport' folder will be created in the program's work folder\n", optionHelpGroup: HelpGroups.General ); o_displayHelp = new GroupedOption @@ -260,6 +264,30 @@ namespace AssetStudioCLI.Options ); #endregion + #region Init Cubism Live2D Options + o_l2dMotionMode = new GroupedOption + ( + optionDefaultValue: CubismLive2DExtractor.Live2DMotionMode.MonoBehaviour, + optionName: "--l2d-motion-mode ", + optionDescription: "Specify Live2D motion export mode\n" + + "\n" + + "MonoBehaviour - Try to export motions from MonoBehaviour Fade motions\n" + + "If no Fade motions are found, the AnimationClip method will be used\n" + + "AnimationClip - Try to export motions using AnimationClip assets\n" + + "Example: \"--l2d-motion-mode animationClip\"\n", + optionHelpGroup: HelpGroups.Live2D + ); + f_l2dForceBezier = new GroupedOption + ( + optionDefaultValue: false, + optionName: "--l2d-force-bezier", + optionDescription: "(Flag) If specified, Linear motion segments will be calculated as Bezier segments\n" + + "(May help if the exported motions look jerky/not smooth enough)", + optionHelpGroup: HelpGroups.Live2D, + isFlag: true + ); + #endregion + #region Init FBX Options o_fbxScaleFactor = new GroupedOption ( @@ -411,6 +439,7 @@ namespace AssetStudioCLI.Options } }; + #region Parse "Working Mode" Option var workModeOptionIndex = resplittedArgs.FindIndex(x => x.ToLower() == "-m" || x.ToLower() == "--mode"); if (workModeOptionIndex >= 0) { @@ -437,6 +466,7 @@ namespace AssetStudioCLI.Options case "info": o_workMode.Value = WorkMode.Info; break; + case "l2d": case "live2d": o_workMode.Value = WorkMode.ExportLive2D; o_exportAssetTypes.Value = new List() @@ -468,6 +498,7 @@ namespace AssetStudioCLI.Options } resplittedArgs.RemoveRange(workModeOptionIndex, 2); } + #endregion #region Parse Flags for (int i = 0; i < resplittedArgs.Count; i++) @@ -495,6 +526,16 @@ namespace AssetStudioCLI.Options return; } break; + case "--l2d-force-bezier": + if (o_workMode.Value != WorkMode.ExportLive2D) + { + Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{flag}] flag. This flag is not suitable for the current working mode [{o_workMode.Value}].\n"); + ShowOptionDescription(o_workMode.Description); + return; + } + f_l2dForceBezier.Value = true; + resplittedArgs.RemoveAt(i); + break; } } #endregion @@ -693,6 +734,28 @@ namespace AssetStudioCLI.Options return; } break; + case "--l2d-motion-mode": + if (o_workMode.Value != WorkMode.ExportLive2D) + { + Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. This option is not suitable for the current working mode [{o_workMode.Value}].\n"); + ShowOptionDescription(o_workMode.Description); + return; + } + switch (value.ToLower()) + { + case "fade": + case "monobehaviour": + o_l2dMotionMode.Value = CubismLive2DExtractor.Live2DMotionMode.MonoBehaviour; + break; + case "animationclip": + o_l2dMotionMode.Value = CubismLive2DExtractor.Live2DMotionMode.AnimationClip; + break; + default: + Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unsupported Live2D motion mode: [{value.Color(brightRed)}].\n"); + ShowOptionDescription(o_l2dMotionMode.Description); + return; + } + break; case "--fbx-scale-factor": var isFloat = float.TryParse(value, out float floatValue); if (isFloat && floatValue >= 0 && floatValue <= 100) @@ -832,12 +895,12 @@ namespace AssetStudioCLI.Options private static bool TryFindOptionDescription(string option, Dictionary dict, bool isFlag = false) { - var optionDesc = dict.Where(x => x.Key.Contains(option)); + var optionDesc = dict.Where(x => x.Key.Contains(option)).ToArray(); if (optionDesc.Any()) { var arg = isFlag ? "flag" : "option"; var rand = new Random(); - var rndOption = optionDesc.ElementAt(rand.Next(0, optionDesc.Count())); + var rndOption = optionDesc.ElementAt(rand.Next(0, optionDesc.Length)); Console.WriteLine($"Did you mean [{$"{rndOption.Key}".Color(CLIAnsiColors.BrightYellow)}] {arg}?"); Console.WriteLine($"Here's a description of it: \n\n{rndOption.Value}"); @@ -946,7 +1009,7 @@ namespace AssetStudioCLI.Options sb.AppendLine($"# Log Output: {o_logOutput}"); sb.AppendLine($"# Export Asset List: {o_exportAssetList}"); sb.AppendLine(ShowCurrentFilter()); - sb.AppendLine($"# Assebmly Path: \"{o_assemblyPath}\""); + sb.AppendLine($"# Assembly Path: \"{o_assemblyPath}\""); sb.AppendLine($"# Unity Version: \"{o_unityVersion}\""); if (o_workMode.Value == WorkMode.Export) { @@ -975,7 +1038,9 @@ namespace AssetStudioCLI.Options } else { - sb.AppendLine($"# Assebmly Path: \"{o_assemblyPath}\""); + sb.AppendLine($"# Live2D Motion Export Method: {o_l2dMotionMode}"); + sb.AppendLine($"# Force Bezier: {f_l2dForceBezier }"); + sb.AppendLine($"# Assembly Path: \"{o_assemblyPath}\""); } sb.AppendLine($"# Unity Version: \"{o_unityVersion}\""); break; diff --git a/AssetStudioCLI/Studio.cs b/AssetStudioCLI/Studio.cs index 12f3529..d715566 100644 --- a/AssetStudioCLI/Studio.cs +++ b/AssetStudioCLI/Studio.cs @@ -611,6 +611,8 @@ namespace AssetStudioCLI { var baseDestPath = Path.Combine(CLIOptions.o_outputFolder.Value, "Live2DOutput"); var useFullContainerPath = false; + var motionMode = CLIOptions.o_l2dMotionMode.Value; + var forceBezier = CLIOptions.f_l2dForceBezier.Value; Progress.Reset(); Logger.Info($"Searching for Live2D files..."); @@ -683,7 +685,7 @@ namespace AssetStudioCLI container = Path.HasExtension(container) ? container.Replace(Path.GetExtension(container), "") : container; var destPath = Path.Combine(baseDestPath, container) + Path.DirectorySeparatorChar; - ExtractLive2D(assets, destPath, modelName, assemblyLoader); + ExtractLive2D(assets, destPath, modelName, assemblyLoader, motionMode, forceBezier); modelCounter++; } catch (Exception ex) diff --git a/AssetStudioGUI/ExportOptions.Designer.cs b/AssetStudioGUI/ExportOptions.Designer.cs index a99aa21..8941fe2 100644 --- a/AssetStudioGUI/ExportOptions.Designer.cs +++ b/AssetStudioGUI/ExportOptions.Designer.cs @@ -45,6 +45,12 @@ this.topng = new System.Windows.Forms.RadioButton(); this.tobmp = new System.Windows.Forms.RadioButton(); this.converttexture = new System.Windows.Forms.CheckBox(); + this.l2dGroupBox = new System.Windows.Forms.GroupBox(); + this.l2dMotionExportMethodPanel = new System.Windows.Forms.Panel(); + this.l2dMonoBehaviourRadioButton = new System.Windows.Forms.RadioButton(); + this.l2dAnimationClipRadioButton = new System.Windows.Forms.RadioButton(); + this.l2dMotionExportMethodLabel = new System.Windows.Forms.Label(); + this.l2dForceBezierCheckBox = new System.Windows.Forms.CheckBox(); this.groupBox2 = new System.Windows.Forms.GroupBox(); this.exportAllUvsAsDiffuseMaps = new System.Windows.Forms.CheckBox(); this.exportBlendShape = new System.Windows.Forms.CheckBox(); @@ -63,9 +69,11 @@ this.castToBone = new System.Windows.Forms.CheckBox(); this.exportAllNodes = new System.Windows.Forms.CheckBox(); this.eulerFilter = new System.Windows.Forms.CheckBox(); - this.exportUvsTooltip = new System.Windows.Forms.ToolTip(this.components); + this.optionTooltip = new System.Windows.Forms.ToolTip(this.components); this.groupBox1.SuspendLayout(); this.panel1.SuspendLayout(); + this.l2dGroupBox.SuspendLayout(); + this.l2dMotionExportMethodPanel.SuspendLayout(); this.groupBox2.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.scaleFactor)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.boneSize)).BeginInit(); @@ -77,7 +85,7 @@ this.OKbutton.Location = new System.Drawing.Point(381, 380); this.OKbutton.Name = "OKbutton"; this.OKbutton.Size = new System.Drawing.Size(75, 23); - this.OKbutton.TabIndex = 6; + this.OKbutton.TabIndex = 4; this.OKbutton.Text = "OK"; this.OKbutton.UseVisualStyleBackColor = true; this.OKbutton.Click += new System.EventHandler(this.OKbutton_Click); @@ -88,7 +96,7 @@ this.Cancel.Location = new System.Drawing.Point(462, 380); this.Cancel.Name = "Cancel"; this.Cancel.Size = new System.Drawing.Size(75, 23); - this.Cancel.TabIndex = 7; + this.Cancel.TabIndex = 5; this.Cancel.Text = "Cancel"; this.Cancel.UseVisualStyleBackColor = true; this.Cancel.Click += new System.EventHandler(this.Cancel_Click); @@ -106,8 +114,8 @@ this.groupBox1.Controls.Add(this.converttexture); this.groupBox1.Location = new System.Drawing.Point(12, 13); this.groupBox1.Name = "groupBox1"; - this.groupBox1.Size = new System.Drawing.Size(301, 362); - this.groupBox1.TabIndex = 9; + this.groupBox1.Size = new System.Drawing.Size(301, 272); + this.groupBox1.TabIndex = 1; this.groupBox1.TabStop = false; this.groupBox1.Text = "Export"; // @@ -119,7 +127,7 @@ this.exportSpriteWithAlphaMask.Location = new System.Drawing.Point(6, 150); this.exportSpriteWithAlphaMask.Name = "exportSpriteWithAlphaMask"; this.exportSpriteWithAlphaMask.Size = new System.Drawing.Size(205, 17); - this.exportSpriteWithAlphaMask.TabIndex = 11; + this.exportSpriteWithAlphaMask.TabIndex = 6; this.exportSpriteWithAlphaMask.Text = "Export sprites with alpha mask applied"; this.exportSpriteWithAlphaMask.UseVisualStyleBackColor = true; // @@ -131,7 +139,7 @@ this.openAfterExport.Location = new System.Drawing.Point(6, 196); this.openAfterExport.Name = "openAfterExport"; this.openAfterExport.Size = new System.Drawing.Size(137, 17); - this.openAfterExport.TabIndex = 10; + this.openAfterExport.TabIndex = 8; this.openAfterExport.Text = "Open folder after export"; this.openAfterExport.UseVisualStyleBackColor = true; // @@ -143,9 +151,9 @@ this.restoreExtensionName.Location = new System.Drawing.Point(6, 63); this.restoreExtensionName.Name = "restoreExtensionName"; this.restoreExtensionName.Size = new System.Drawing.Size(275, 17); - this.restoreExtensionName.TabIndex = 9; + this.restoreExtensionName.TabIndex = 3; this.restoreExtensionName.Text = "Try to restore/Use original TextAsset extension name"; - this.exportUvsTooltip.SetToolTip(this.restoreExtensionName, "If not checked, AssetStudio will export all TextAssets with the \".txt\" extension"); + this.optionTooltip.SetToolTip(this.restoreExtensionName, "If not checked, AssetStudio will export all TextAssets with the \".txt\" extension"); this.restoreExtensionName.UseVisualStyleBackColor = true; // // assetGroupOptions @@ -161,7 +169,7 @@ this.assetGroupOptions.Location = new System.Drawing.Point(6, 35); this.assetGroupOptions.Name = "assetGroupOptions"; this.assetGroupOptions.Size = new System.Drawing.Size(165, 21); - this.assetGroupOptions.TabIndex = 8; + this.assetGroupOptions.TabIndex = 2; // // label6 // @@ -169,7 +177,7 @@ this.label6.Location = new System.Drawing.Point(6, 18); this.label6.Name = "label6"; this.label6.Size = new System.Drawing.Size(127, 13); - this.label6.TabIndex = 7; + this.label6.TabIndex = 1; this.label6.Text = "Group exported assets by"; // // convertAudio @@ -180,7 +188,7 @@ this.convertAudio.Location = new System.Drawing.Point(6, 173); this.convertAudio.Name = "convertAudio"; this.convertAudio.Size = new System.Drawing.Size(179, 17); - this.convertAudio.TabIndex = 6; + this.convertAudio.TabIndex = 7; this.convertAudio.Text = "Convert AudioClip to WAV(PCM)"; this.convertAudio.UseVisualStyleBackColor = true; // @@ -202,8 +210,7 @@ this.towebp.Location = new System.Drawing.Point(201, 7); this.towebp.Name = "towebp"; this.towebp.Size = new System.Drawing.Size(54, 17); - this.towebp.TabIndex = 5; - this.towebp.TabStop = true; + this.towebp.TabIndex = 4; this.towebp.Text = "Webp"; this.towebp.UseVisualStyleBackColor = true; // @@ -213,7 +220,7 @@ this.totga.Location = new System.Drawing.Point(150, 7); this.totga.Name = "totga"; this.totga.Size = new System.Drawing.Size(44, 17); - this.totga.TabIndex = 2; + this.totga.TabIndex = 3; this.totga.Text = "Tga"; this.totga.UseVisualStyleBackColor = true; // @@ -223,7 +230,7 @@ this.tojpg.Location = new System.Drawing.Point(97, 7); this.tojpg.Name = "tojpg"; this.tojpg.Size = new System.Drawing.Size(48, 17); - this.tojpg.TabIndex = 4; + this.tojpg.TabIndex = 2; this.tojpg.Text = "Jpeg"; this.tojpg.UseVisualStyleBackColor = true; // @@ -234,7 +241,7 @@ this.topng.Location = new System.Drawing.Point(50, 7); this.topng.Name = "topng"; this.topng.Size = new System.Drawing.Size(44, 17); - this.topng.TabIndex = 3; + this.topng.TabIndex = 1; this.topng.TabStop = true; this.topng.Text = "Png"; this.topng.UseVisualStyleBackColor = true; @@ -245,7 +252,7 @@ this.tobmp.Location = new System.Drawing.Point(3, 7); this.tobmp.Name = "tobmp"; this.tobmp.Size = new System.Drawing.Size(46, 17); - this.tobmp.TabIndex = 2; + this.tobmp.TabIndex = 0; this.tobmp.Text = "Bmp"; this.tobmp.UseVisualStyleBackColor = true; // @@ -257,10 +264,76 @@ this.converttexture.Location = new System.Drawing.Point(6, 87); this.converttexture.Name = "converttexture"; this.converttexture.Size = new System.Drawing.Size(116, 17); - this.converttexture.TabIndex = 1; + this.converttexture.TabIndex = 4; this.converttexture.Text = "Convert Texture2D"; this.converttexture.UseVisualStyleBackColor = true; // + // l2dGroupBox + // + this.l2dGroupBox.Controls.Add(this.l2dMotionExportMethodPanel); + this.l2dGroupBox.Controls.Add(this.l2dMotionExportMethodLabel); + this.l2dGroupBox.Controls.Add(this.l2dForceBezierCheckBox); + this.l2dGroupBox.Location = new System.Drawing.Point(12, 275); + this.l2dGroupBox.Name = "l2dGroupBox"; + this.l2dGroupBox.Size = new System.Drawing.Size(301, 100); + this.l2dGroupBox.TabIndex = 2; + this.l2dGroupBox.TabStop = false; + this.l2dGroupBox.Text = "Cubism Live2D"; + // + // l2dMotionExportMethodPanel + // + this.l2dMotionExportMethodPanel.Controls.Add(this.l2dMonoBehaviourRadioButton); + this.l2dMotionExportMethodPanel.Controls.Add(this.l2dAnimationClipRadioButton); + this.l2dMotionExportMethodPanel.Location = new System.Drawing.Point(18, 40); + this.l2dMotionExportMethodPanel.Name = "l2dMotionExportMethodPanel"; + this.l2dMotionExportMethodPanel.Size = new System.Drawing.Size(263, 27); + this.l2dMotionExportMethodPanel.TabIndex = 2; + // + // l2dMonoBehaviourRadioButton + // + this.l2dMonoBehaviourRadioButton.AccessibleName = "MonoBehaviour"; + this.l2dMonoBehaviourRadioButton.AutoSize = true; + this.l2dMonoBehaviourRadioButton.Checked = true; + this.l2dMonoBehaviourRadioButton.Location = new System.Drawing.Point(3, 5); + this.l2dMonoBehaviourRadioButton.Name = "l2dMonoBehaviourRadioButton"; + this.l2dMonoBehaviourRadioButton.Size = new System.Drawing.Size(167, 17); + this.l2dMonoBehaviourRadioButton.TabIndex = 0; + this.l2dMonoBehaviourRadioButton.TabStop = true; + this.l2dMonoBehaviourRadioButton.Text = "MonoBehaviour (Fade motion)"; + this.optionTooltip.SetToolTip(this.l2dMonoBehaviourRadioButton, "If no Fade motions are found, the AnimationClip method will be used"); + this.l2dMonoBehaviourRadioButton.UseVisualStyleBackColor = true; + // + // l2dAnimationClipRadioButton + // + this.l2dAnimationClipRadioButton.AccessibleName = "AnimationClip"; + this.l2dAnimationClipRadioButton.AutoSize = true; + this.l2dAnimationClipRadioButton.Location = new System.Drawing.Point(172, 5); + this.l2dAnimationClipRadioButton.Name = "l2dAnimationClipRadioButton"; + this.l2dAnimationClipRadioButton.Size = new System.Drawing.Size(88, 17); + this.l2dAnimationClipRadioButton.TabIndex = 1; + this.l2dAnimationClipRadioButton.Text = "AnimationClip"; + this.l2dAnimationClipRadioButton.UseVisualStyleBackColor = true; + // + // l2dMotionExportMethodLabel + // + this.l2dMotionExportMethodLabel.AutoSize = true; + this.l2dMotionExportMethodLabel.Location = new System.Drawing.Point(6, 21); + this.l2dMotionExportMethodLabel.Name = "l2dMotionExportMethodLabel"; + this.l2dMotionExportMethodLabel.Size = new System.Drawing.Size(109, 13); + this.l2dMotionExportMethodLabel.TabIndex = 1; + this.l2dMotionExportMethodLabel.Text = "Motion export method"; + // + // l2dForceBezierCheckBox + // + this.l2dForceBezierCheckBox.AutoSize = true; + this.l2dForceBezierCheckBox.Location = new System.Drawing.Point(6, 77); + this.l2dForceBezierCheckBox.Name = "l2dForceBezierCheckBox"; + this.l2dForceBezierCheckBox.Size = new System.Drawing.Size(278, 17); + this.l2dForceBezierCheckBox.TabIndex = 3; + this.l2dForceBezierCheckBox.Text = "Calculate Linear motion segments as Bezier segments"; + this.optionTooltip.SetToolTip(this.l2dForceBezierCheckBox, "May help if the exported motions look jerky/not smooth enough"); + this.l2dForceBezierCheckBox.UseVisualStyleBackColor = true; + // // groupBox2 // this.groupBox2.AutoSize = true; @@ -284,7 +357,7 @@ this.groupBox2.Location = new System.Drawing.Point(313, 13); this.groupBox2.Name = "groupBox2"; this.groupBox2.Size = new System.Drawing.Size(224, 362); - this.groupBox2.TabIndex = 11; + this.groupBox2.TabIndex = 3; this.groupBox2.TabStop = false; this.groupBox2.Text = "Fbx"; // @@ -295,9 +368,9 @@ this.exportAllUvsAsDiffuseMaps.Location = new System.Drawing.Point(6, 185); this.exportAllUvsAsDiffuseMaps.Name = "exportAllUvsAsDiffuseMaps"; this.exportAllUvsAsDiffuseMaps.Size = new System.Drawing.Size(168, 17); - this.exportAllUvsAsDiffuseMaps.TabIndex = 23; + this.exportAllUvsAsDiffuseMaps.TabIndex = 9; this.exportAllUvsAsDiffuseMaps.Text = "Export all UVs as diffuse maps"; - this.exportUvsTooltip.SetToolTip(this.exportAllUvsAsDiffuseMaps, "Unchecked: UV1 exported as normal map. Check this if your export is missing a UV " + + this.optionTooltip.SetToolTip(this.exportAllUvsAsDiffuseMaps, "Unchecked: UV1 exported as normal map. Check this if your export is missing a UV " + "map."); this.exportAllUvsAsDiffuseMaps.UseVisualStyleBackColor = true; // @@ -309,7 +382,7 @@ this.exportBlendShape.Location = new System.Drawing.Point(6, 138); this.exportBlendShape.Name = "exportBlendShape"; this.exportBlendShape.Size = new System.Drawing.Size(114, 17); - this.exportBlendShape.TabIndex = 22; + this.exportBlendShape.TabIndex = 7; this.exportBlendShape.Text = "Export blendshape"; this.exportBlendShape.UseVisualStyleBackColor = true; // @@ -321,7 +394,7 @@ this.exportAnimations.Location = new System.Drawing.Point(6, 114); this.exportAnimations.Name = "exportAnimations"; this.exportAnimations.Size = new System.Drawing.Size(109, 17); - this.exportAnimations.TabIndex = 21; + this.exportAnimations.TabIndex = 6; this.exportAnimations.Text = "Export animations"; this.exportAnimations.UseVisualStyleBackColor = true; // @@ -336,7 +409,7 @@ this.scaleFactor.Location = new System.Drawing.Point(83, 243); this.scaleFactor.Name = "scaleFactor"; this.scaleFactor.Size = new System.Drawing.Size(60, 20); - this.scaleFactor.TabIndex = 20; + this.scaleFactor.TabIndex = 13; this.scaleFactor.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; this.scaleFactor.Value = new decimal(new int[] { 1, @@ -350,7 +423,7 @@ this.label5.Location = new System.Drawing.Point(6, 245); this.label5.Name = "label5"; this.label5.Size = new System.Drawing.Size(64, 13); - this.label5.TabIndex = 19; + this.label5.TabIndex = 12; this.label5.Text = "ScaleFactor"; // // fbxFormat @@ -363,7 +436,7 @@ this.fbxFormat.Location = new System.Drawing.Point(77, 275); this.fbxFormat.Name = "fbxFormat"; this.fbxFormat.Size = new System.Drawing.Size(61, 21); - this.fbxFormat.TabIndex = 18; + this.fbxFormat.TabIndex = 15; // // label4 // @@ -371,7 +444,7 @@ this.label4.Location = new System.Drawing.Point(6, 280); this.label4.Name = "label4"; this.label4.Size = new System.Drawing.Size(59, 13); - this.label4.TabIndex = 17; + this.label4.TabIndex = 14; this.label4.Text = "FBXFormat"; // // fbxVersion @@ -388,7 +461,7 @@ this.fbxVersion.Location = new System.Drawing.Point(77, 308); this.fbxVersion.Name = "fbxVersion"; this.fbxVersion.Size = new System.Drawing.Size(47, 21); - this.fbxVersion.TabIndex = 16; + this.fbxVersion.TabIndex = 17; // // label3 // @@ -396,7 +469,7 @@ this.label3.Location = new System.Drawing.Point(6, 311); this.label3.Name = "label3"; this.label3.Size = new System.Drawing.Size(62, 13); - this.label3.TabIndex = 15; + this.label3.TabIndex = 16; this.label3.Text = "FBXVersion"; // // boneSize @@ -428,7 +501,7 @@ this.exportSkins.Location = new System.Drawing.Point(6, 90); this.exportSkins.Name = "exportSkins"; this.exportSkins.Size = new System.Drawing.Size(83, 17); - this.exportSkins.TabIndex = 8; + this.exportSkins.TabIndex = 5; this.exportSkins.Text = "Export skins"; this.exportSkins.UseVisualStyleBackColor = true; // @@ -438,7 +511,7 @@ this.label1.Location = new System.Drawing.Point(26, 42); this.label1.Name = "label1"; this.label1.Size = new System.Drawing.Size(72, 13); - this.label1.TabIndex = 7; + this.label1.TabIndex = 2; this.label1.Text = "FilterPrecision"; // // filterPrecision @@ -452,7 +525,7 @@ this.filterPrecision.Location = new System.Drawing.Point(127, 40); this.filterPrecision.Name = "filterPrecision"; this.filterPrecision.Size = new System.Drawing.Size(51, 20); - this.filterPrecision.TabIndex = 6; + this.filterPrecision.TabIndex = 3; this.filterPrecision.Value = new decimal(new int[] { 25, 0, @@ -465,7 +538,7 @@ this.castToBone.Location = new System.Drawing.Point(6, 161); this.castToBone.Name = "castToBone"; this.castToBone.Size = new System.Drawing.Size(131, 17); - this.castToBone.TabIndex = 5; + this.castToBone.TabIndex = 8; this.castToBone.Text = "All nodes cast to bone"; this.castToBone.UseVisualStyleBackColor = true; // @@ -489,7 +562,7 @@ this.eulerFilter.Location = new System.Drawing.Point(6, 22); this.eulerFilter.Name = "eulerFilter"; this.eulerFilter.Size = new System.Drawing.Size(72, 17); - this.eulerFilter.TabIndex = 3; + this.eulerFilter.TabIndex = 1; this.eulerFilter.Text = "EulerFilter"; this.eulerFilter.UseVisualStyleBackColor = true; // @@ -500,6 +573,7 @@ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.CancelButton = this.Cancel; this.ClientSize = new System.Drawing.Size(549, 416); + this.Controls.Add(this.l2dGroupBox); this.Controls.Add(this.groupBox2); this.Controls.Add(this.groupBox1); this.Controls.Add(this.Cancel); @@ -516,6 +590,10 @@ this.groupBox1.PerformLayout(); this.panel1.ResumeLayout(false); this.panel1.PerformLayout(); + this.l2dGroupBox.ResumeLayout(false); + this.l2dGroupBox.PerformLayout(); + this.l2dMotionExportMethodPanel.ResumeLayout(false); + this.l2dMotionExportMethodPanel.PerformLayout(); this.groupBox2.ResumeLayout(false); this.groupBox2.PerformLayout(); ((System.ComponentModel.ISupportInitialize)(this.scaleFactor)).EndInit(); @@ -559,8 +637,14 @@ private System.Windows.Forms.CheckBox restoreExtensionName; private System.Windows.Forms.CheckBox openAfterExport; private System.Windows.Forms.CheckBox exportAllUvsAsDiffuseMaps; - private System.Windows.Forms.ToolTip exportUvsTooltip; + private System.Windows.Forms.ToolTip optionTooltip; private System.Windows.Forms.CheckBox exportSpriteWithAlphaMask; private System.Windows.Forms.RadioButton towebp; + private System.Windows.Forms.GroupBox l2dGroupBox; + private System.Windows.Forms.CheckBox l2dForceBezierCheckBox; + private System.Windows.Forms.Label l2dMotionExportMethodLabel; + private System.Windows.Forms.RadioButton l2dAnimationClipRadioButton; + private System.Windows.Forms.RadioButton l2dMonoBehaviourRadioButton; + private System.Windows.Forms.Panel l2dMotionExportMethodPanel; } } \ No newline at end of file diff --git a/AssetStudioGUI/ExportOptions.cs b/AssetStudioGUI/ExportOptions.cs index c3542a1..00407cc 100644 --- a/AssetStudioGUI/ExportOptions.cs +++ b/AssetStudioGUI/ExportOptions.cs @@ -1,5 +1,6 @@ using AssetStudio; using System; +using System.Linq; using System.Windows.Forms; namespace AssetStudioGUI @@ -14,15 +15,8 @@ namespace AssetStudioGUI converttexture.Checked = Properties.Settings.Default.convertTexture; exportSpriteWithAlphaMask.Checked = Properties.Settings.Default.exportSpriteWithMask; convertAudio.Checked = Properties.Settings.Default.convertAudio; - var str = Properties.Settings.Default.convertType.ToString(); - foreach (Control c in panel1.Controls) - { - if (c.Text == str) - { - ((RadioButton)c).Checked = true; - break; - } - } + var defaultImageType = Properties.Settings.Default.convertType.ToString(); + ((RadioButton)panel1.Controls.Cast().First(x => x.Text == defaultImageType)).Checked = true; openAfterExport.Checked = Properties.Settings.Default.openAfterExport; eulerFilter.Checked = Properties.Settings.Default.eulerFilter; filterPrecision.Value = Properties.Settings.Default.filterPrecision; @@ -36,7 +30,9 @@ namespace AssetStudioGUI scaleFactor.Value = Properties.Settings.Default.scaleFactor; fbxVersion.SelectedIndex = Properties.Settings.Default.fbxVersion; fbxFormat.SelectedIndex = Properties.Settings.Default.fbxFormat; - + var defaultMotionMode = Properties.Settings.Default.l2dMotionMode.ToString(); + ((RadioButton)l2dMotionExportMethodPanel.Controls.Cast().First(x => x.AccessibleName == defaultMotionMode)).Checked = true; + l2dForceBezierCheckBox.Checked = Properties.Settings.Default.l2dForceBezier; } private void OKbutton_Click(object sender, EventArgs e) @@ -46,14 +42,8 @@ namespace AssetStudioGUI Properties.Settings.Default.convertTexture = converttexture.Checked; Properties.Settings.Default.exportSpriteWithMask = exportSpriteWithAlphaMask.Checked; Properties.Settings.Default.convertAudio = convertAudio.Checked; - foreach (Control c in panel1.Controls) - { - if (((RadioButton)c).Checked) - { - Properties.Settings.Default.convertType = (ImageFormat)Enum.Parse(typeof(ImageFormat), c.Text); - break; - } - } + var checkedImageType = (RadioButton)panel1.Controls.Cast().First(x => ((RadioButton)x).Checked); + Properties.Settings.Default.convertType = (ImageFormat)Enum.Parse(typeof(ImageFormat), checkedImageType.Text); Properties.Settings.Default.openAfterExport = openAfterExport.Checked; Properties.Settings.Default.eulerFilter = eulerFilter.Checked; Properties.Settings.Default.filterPrecision = filterPrecision.Value; @@ -67,6 +57,9 @@ namespace AssetStudioGUI Properties.Settings.Default.scaleFactor = scaleFactor.Value; Properties.Settings.Default.fbxVersion = fbxVersion.SelectedIndex; Properties.Settings.Default.fbxFormat = fbxFormat.SelectedIndex; + var checkedMotionMode = (RadioButton)l2dMotionExportMethodPanel.Controls.Cast().First(x => ((RadioButton)x).Checked); + Properties.Settings.Default.l2dMotionMode = (CubismLive2DExtractor.Live2DMotionMode)Enum.Parse(typeof(CubismLive2DExtractor.Live2DMotionMode), checkedMotionMode.AccessibleName); + Properties.Settings.Default.l2dForceBezier = l2dForceBezierCheckBox.Checked; Properties.Settings.Default.Save(); DialogResult = DialogResult.OK; Close(); diff --git a/AssetStudioGUI/ExportOptions.resx b/AssetStudioGUI/ExportOptions.resx index 6966b18..7da86a7 100644 --- a/AssetStudioGUI/ExportOptions.resx +++ b/AssetStudioGUI/ExportOptions.resx @@ -117,7 +117,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + 17, 17 \ No newline at end of file diff --git a/AssetStudioGUI/Properties/Settings.Designer.cs b/AssetStudioGUI/Properties/Settings.Designer.cs index 9ba3bcf..038ad17 100644 --- a/AssetStudioGUI/Properties/Settings.Designer.cs +++ b/AssetStudioGUI/Properties/Settings.Designer.cs @@ -12,7 +12,7 @@ namespace AssetStudioGUI.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.5.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.7.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); @@ -286,5 +286,29 @@ namespace AssetStudioGUI.Properties { this["exportSpriteWithMask"] = value; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("MonoBehaviour")] + public global::CubismLive2DExtractor.Live2DMotionMode l2dMotionMode { + get { + return ((global::CubismLive2DExtractor.Live2DMotionMode)(this["l2dMotionMode"])); + } + set { + this["l2dMotionMode"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool l2dForceBezier { + get { + return ((bool)(this["l2dForceBezier"])); + } + set { + this["l2dForceBezier"] = value; + } + } } } diff --git a/AssetStudioGUI/Properties/Settings.settings b/AssetStudioGUI/Properties/Settings.settings index aef06e0..e8ab68d 100644 --- a/AssetStudioGUI/Properties/Settings.settings +++ b/AssetStudioGUI/Properties/Settings.settings @@ -68,5 +68,11 @@ True + + MonoBehaviour + + + False + \ No newline at end of file diff --git a/AssetStudioGUI/Studio.cs b/AssetStudioGUI/Studio.cs index 8b0acc9..94ab0c2 100644 --- a/AssetStudioGUI/Studio.cs +++ b/AssetStudioGUI/Studio.cs @@ -746,6 +746,8 @@ namespace AssetStudioGUI public static void ExportLive2D(Object[] cubismMocs, string exportPath) { var baseDestPath = Path.Combine(exportPath, "Live2DOutput"); + var motionMode = Properties.Settings.Default.l2dMotionMode; + var forceBezier = Properties.Settings.Default.l2dForceBezier; ThreadPool.QueueUserWorkItem(state => { @@ -805,7 +807,7 @@ namespace AssetStudioGUI container = Path.HasExtension(container) ? container.Replace(Path.GetExtension(container), "") : container; var destPath = Path.Combine(baseDestPath, container) + Path.DirectorySeparatorChar; - ExtractLive2D(assets, destPath, modelName, assemblyLoader); + ExtractLive2D(assets, destPath, modelName, assemblyLoader, motionMode, forceBezier); modelCounter++; } catch (Exception ex) diff --git a/AssetStudioUtility/CubismLive2DExtractor/CubismFadeMotion.cs b/AssetStudioUtility/CubismLive2DExtractor/CubismFadeMotion.cs new file mode 100644 index 0000000..f14b35a --- /dev/null +++ b/AssetStudioUtility/CubismLive2DExtractor/CubismFadeMotion.cs @@ -0,0 +1,30 @@ +using System; + +namespace CubismLive2DExtractor +{ + public class AnimationCurve + { + public CubismKeyframeData[] m_Curve { get; set; } + public int m_PreInfinity { get; set; } + public int m_PostInfinity { get; set; } + public int m_RotationOrder { get; set; } + } + + public class CubismFadeMotion + { + public string m_Name { get; set; } + public string MotionName { get; set; } + public float FadeInTime { get; set; } + public float FadeOutTime { get; set; } + public string[] ParameterIds { get; set; } + public AnimationCurve[] ParameterCurves { get; set; } + public float[] ParameterFadeInTimes { get; set; } + public float[] ParameterFadeOutTimes { get; set; } + public float MotionLength { get; set; } + + public CubismFadeMotion() + { + ParameterIds = Array.Empty(); + } + } +} diff --git a/AssetStudioUtility/CubismLive2DExtractor/CubismKeyframeData.cs b/AssetStudioUtility/CubismLive2DExtractor/CubismKeyframeData.cs new file mode 100644 index 0000000..2b01705 --- /dev/null +++ b/AssetStudioUtility/CubismLive2DExtractor/CubismKeyframeData.cs @@ -0,0 +1,26 @@ +namespace CubismLive2DExtractor +{ + public class CubismKeyframeData + { + public float time { get; set; } + public float value { get; set; } + public float inSlope { get; set; } + public float outSlope { get; set; } + public int weightedMode { get; set; } + public float inWeight { get; set; } + public float outWeight { get; set; } + + public CubismKeyframeData() { } + + public CubismKeyframeData(ImportedKeyframe keyframe) + { + time = keyframe.time; + value = keyframe.value; + inSlope = keyframe.inSlope; + outSlope = keyframe.outSlope; + weightedMode = 0; + inWeight = 0; + outWeight = 0; + } + } +} diff --git a/AssetStudioUtility/CubismLive2DExtractor/CubismMotion3Json.cs b/AssetStudioUtility/CubismLive2DExtractor/CubismMotion3Json.cs index c5cd623..2d0abec 100644 --- a/AssetStudioUtility/CubismLive2DExtractor/CubismMotion3Json.cs +++ b/AssetStudioUtility/CubismLive2DExtractor/CubismMotion3Json.cs @@ -1,7 +1,9 @@ -using System; +// File Format Specifications +// https://github.com/Live2D/CubismSpecs/blob/master/FileFormats/motion3.json.md + +using System; using System.Collections.Generic; using System.Linq; -using System.Text; namespace CubismLive2DExtractor { @@ -18,24 +20,213 @@ namespace CubismLive2DExtractor public float Fps; public bool Loop; public bool AreBeziersRestricted; + public float FadeInTime; + public float FadeOutTime; public int CurveCount; public int TotalSegmentCount; public int TotalPointCount; public int UserDataCount; public int TotalUserDataSize; - }; + } public class SerializableCurve { public string Target; public string Id; + public float FadeInTime; + public float FadeOutTime; public List Segments; - }; + } public class SerializableUserData { public float Time; public string Value; } + + private static void AddSegments( + CubismKeyframeData curve, + CubismKeyframeData preCurve, + CubismKeyframeData nextCurve, + SerializableCurve cubismCurve, + bool forceBezier, + ref int totalPointCount, + ref int totalSegmentCount, + ref int j + ) + { + if (Math.Abs(curve.time - preCurve.time - 0.01f) < 0.0001f) // InverseSteppedSegment + { + if (nextCurve.value == curve.value) + { + cubismCurve.Segments.Add(3f); // Segment ID + cubismCurve.Segments.Add(nextCurve.time); + cubismCurve.Segments.Add(nextCurve.value); + j += 1; + totalPointCount += 1; + totalSegmentCount++; + return; + } + } + if (float.IsPositiveInfinity(curve.inSlope)) // SteppedSegment + { + cubismCurve.Segments.Add(2f); // Segment ID + cubismCurve.Segments.Add(curve.time); + cubismCurve.Segments.Add(curve.value); + totalPointCount += 1; + } + else if (preCurve.outSlope == 0f && Math.Abs(curve.inSlope) < 0.0001f && !forceBezier) // LinearSegment + { + cubismCurve.Segments.Add(0f); // Segment ID + cubismCurve.Segments.Add(curve.time); + cubismCurve.Segments.Add(curve.value); + totalPointCount += 1; + } + else // BezierSegment + { + var tangentLength = (curve.time - preCurve.time) / 3f; + cubismCurve.Segments.Add(1f); // Segment ID + cubismCurve.Segments.Add(preCurve.time + tangentLength); + cubismCurve.Segments.Add(preCurve.outSlope * tangentLength + preCurve.value); + cubismCurve.Segments.Add(curve.time - tangentLength); + cubismCurve.Segments.Add(curve.value - curve.inSlope * tangentLength); + cubismCurve.Segments.Add(curve.time); + cubismCurve.Segments.Add(curve.value); + totalPointCount += 3; + } + totalSegmentCount++; + } + + public CubismMotion3Json(CubismFadeMotion fadeMotion, bool forceBezier) + { + Version = 3; + Meta = new SerializableMeta + { + // Duration of the motion in seconds. + Duration = fadeMotion.MotionLength, + // Framerate of the motion in seconds. + Fps = 30, + // [Optional] Status of the looping of the motion. + Loop = true, + // [Optional] Status of the restriction of Bezier handles'X translations. + AreBeziersRestricted = true, + // [Optional] Time of the overall Fade-In for easing in seconds. + FadeInTime = fadeMotion.FadeInTime, + // [Optional] Time of the overall Fade-Out for easing in seconds. + FadeOutTime = fadeMotion.FadeOutTime, + // The total number of curves. + CurveCount = (int)fadeMotion.ParameterCurves.LongCount(x => x.m_Curve.Length > 0), + // [Optional] The total number of UserData. + UserDataCount = 0 + }; + // Motion curves. + Curves = new SerializableCurve[Meta.CurveCount]; + + var totalSegmentCount = 1; + var totalPointCount = 1; + var actualCurveCount = 0; + for (var i = 0; i < fadeMotion.ParameterCurves.Length; i++) + { + if (fadeMotion.ParameterCurves[i].m_Curve.Length == 0) + continue; + + Curves[actualCurveCount] = new SerializableCurve + { + // Target type. + Target = "Parameter", + // Identifier for mapping curve to target. + Id = fadeMotion.ParameterIds[i], + // [Optional] Time of the Fade - In for easing in seconds. + FadeInTime = fadeMotion.ParameterFadeInTimes[i], + // [Optional] Time of the Fade - Out for easing in seconds. + FadeOutTime = fadeMotion.ParameterFadeOutTimes[i], + // Flattened segments. + Segments = new List + { + // First point + fadeMotion.ParameterCurves[i].m_Curve[0].time, + fadeMotion.ParameterCurves[i].m_Curve[0].value + } + }; + for (var j = 1; j < fadeMotion.ParameterCurves[i].m_Curve.Length; j++) + { + var curve = fadeMotion.ParameterCurves[i].m_Curve[j]; + var preCurve = fadeMotion.ParameterCurves[i].m_Curve[j - 1]; + var next = fadeMotion.ParameterCurves[i].m_Curve.ElementAtOrDefault(j + 1); + var nextCurve = next ?? new CubismKeyframeData(); + AddSegments(curve, preCurve, nextCurve, Curves[actualCurveCount], forceBezier, ref totalPointCount, ref totalSegmentCount, ref j); + } + actualCurveCount++; + } + + // The total number of segments (from all curves). + Meta.TotalSegmentCount = totalSegmentCount; + // The total number of points (from all segments of all curves). + Meta.TotalPointCount = totalPointCount; + + UserData = Array.Empty(); + // [Optional] The total size of UserData in bytes. + Meta.TotalUserDataSize = 0; + } + + public CubismMotion3Json(ImportedKeyframedAnimation animation, bool forceBezier) + { + Version = 3; + Meta = new SerializableMeta + { + Duration = animation.Duration, + Fps = animation.SampleRate, + Loop = true, + AreBeziersRestricted = true, + FadeInTime = 0, + FadeOutTime = 0, + CurveCount = animation.TrackList.Count, + UserDataCount = animation.Events.Count + }; + Curves = new SerializableCurve[Meta.CurveCount]; + + var totalSegmentCount = 1; + var totalPointCount = 1; + for (var i = 0; i < Meta.CurveCount; i++) + { + var track = animation.TrackList[i]; + Curves[i] = new SerializableCurve + { + Target = track.Target, + Id = track.Name, + FadeInTime = -1, + FadeOutTime = -1, + Segments = new List + { + 0f, + track.Curve[0].value + } + }; + for (var j = 1; j < track.Curve.Count; j++) + { + var curve = new CubismKeyframeData(track.Curve[j]); + var preCurve = new CubismKeyframeData(track.Curve[j - 1]); + var next = track.Curve.ElementAtOrDefault(j + 1); + var nextCurve = next != null ? new CubismKeyframeData(next) : new CubismKeyframeData(); + AddSegments(curve, preCurve, nextCurve, Curves[i], forceBezier, ref totalPointCount, ref totalSegmentCount, ref j); + } + } + Meta.TotalSegmentCount = totalSegmentCount; + Meta.TotalPointCount = totalPointCount; + + UserData = new SerializableUserData[Meta.UserDataCount]; + var totalUserDataSize = 0; + for (var i = 0; i < Meta.UserDataCount; i++) + { + var @event = animation.Events[i]; + UserData[i] = new SerializableUserData + { + Time = @event.time, + Value = @event.value + }; + totalUserDataSize += @event.value.Length; + } + Meta.TotalUserDataSize = totalUserDataSize; + } } } diff --git a/AssetStudioUtility/CubismLive2DExtractor/Live2DExtractor.cs b/AssetStudioUtility/CubismLive2DExtractor/Live2DExtractor.cs index db4530c..19de546 100644 --- a/AssetStudioUtility/CubismLive2DExtractor/Live2DExtractor.cs +++ b/AssetStudioUtility/CubismLive2DExtractor/Live2DExtractor.cs @@ -18,7 +18,7 @@ namespace CubismLive2DExtractor { public static class Live2DExtractor { - public static void ExtractLive2D(IGrouping assets, string destPath, string modelName, AssemblyLoader assemblyLoader) + public static void ExtractLive2D(IGrouping assets, string destPath, string modelName, AssemblyLoader assemblyLoader, Live2DMotionMode motionMode, bool forceBezier = false) { var destTexturePath = Path.Combine(destPath, "textures") + Path.DirectorySeparatorChar; var destMotionPath = Path.Combine(destPath, "motions") + Path.DirectorySeparatorChar; @@ -27,6 +27,7 @@ namespace CubismLive2DExtractor Directory.CreateDirectory(destTexturePath); var expressionList = new List(); + var fadeMotionList = new List(); var gameObjects = new List(); var animationClips = new List(); @@ -53,6 +54,9 @@ namespace CubismLive2DExtractor case "CubismExpressionData": expressionList.Add(m_MonoBehaviour); break; + case "CubismFadeMotionData": + fadeMotionList.Add(m_MonoBehaviour); + break; case "CubismEyeBlinkParameter": if (m_MonoBehaviour.m_GameObject.TryGet(out var blinkGameObject)) { @@ -110,7 +114,42 @@ namespace CubismLive2DExtractor //motion var motions = new SortedDictionary(); - if (gameObjects.Count > 0) + if (motionMode == Live2DMotionMode.MonoBehaviour && fadeMotionList.Count > 0) //motion from MonoBehaviour + { + Directory.CreateDirectory(destMotionPath); + foreach (var fadeMotionMono in fadeMotionList) + { + var fadeMotionObj = fadeMotionMono.ToType(); + if (fadeMotionObj == null) + { + var m_Type = fadeMotionMono.ConvertToTypeTree(assemblyLoader); + fadeMotionObj = fadeMotionMono.ToType(m_Type); + if (fadeMotionObj == null) + { + Logger.Warning($"Fade motion \"{fadeMotionMono.m_Name}\" is not readable."); + continue; + } + } + var fadeMotion = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(fadeMotionObj)); + if (fadeMotion.ParameterIds.Length == 0) + continue; + + var motionJson = new CubismMotion3Json(fadeMotion, forceBezier); + + var animName = Path.GetFileNameWithoutExtension(fadeMotion.m_Name); + if (motions.ContainsKey(animName)) + { + animName = $"{animName}_{fadeMotion.GetHashCode()}"; + + if (motions.ContainsKey(animName)) + continue; + } + var motionPath = new JObject(new JProperty("File", $"motions/{animName}.motion3.json")); + motions.Add(animName, new JArray(motionPath)); + File.WriteAllText($"{destMotionPath}{animName}.motion3.json", JsonConvert.SerializeObject(motionJson, Formatting.Indented, new MyJsonConverter())); + } + } + else if (gameObjects.Count > 0) //motion from AnimationClip { var rootTransform = gameObjects[0].m_Transform; while (rootTransform.m_Father.TryGet(out var m_Father)) @@ -123,111 +162,27 @@ namespace CubismLive2DExtractor { Directory.CreateDirectory(destMotionPath); } - foreach (ImportedKeyframedAnimation animation in converter.AnimationList) + foreach (var animation in converter.AnimationList) { - var json = new CubismMotion3Json - { - Version = 3, - Meta = new CubismMotion3Json.SerializableMeta - { - Duration = animation.Duration, - Fps = animation.SampleRate, - Loop = true, - AreBeziersRestricted = true, - CurveCount = animation.TrackList.Count, - UserDataCount = animation.Events.Count - }, - Curves = new CubismMotion3Json.SerializableCurve[animation.TrackList.Count] - }; - int totalSegmentCount = 1; - int totalPointCount = 1; - for (int i = 0; i < animation.TrackList.Count; i++) - { - var track = animation.TrackList[i]; - json.Curves[i] = new CubismMotion3Json.SerializableCurve - { - Target = track.Target, - Id = track.Name, - Segments = new List { 0f, track.Curve[0].value } - }; - for (var j = 1; j < track.Curve.Count; j++) - { - var curve = track.Curve[j]; - var preCurve = track.Curve[j - 1]; - if (Math.Abs(curve.time - preCurve.time - 0.01f) < 0.0001f) //InverseSteppedSegment - { - var nextCurve = track.Curve[j + 1]; - if (nextCurve.value == curve.value) - { - json.Curves[i].Segments.Add(3f); - json.Curves[i].Segments.Add(nextCurve.time); - json.Curves[i].Segments.Add(nextCurve.value); - j += 1; - totalPointCount += 1; - totalSegmentCount++; - continue; - } - } - if (float.IsPositiveInfinity(curve.inSlope)) //SteppedSegment - { - json.Curves[i].Segments.Add(2f); - json.Curves[i].Segments.Add(curve.time); - json.Curves[i].Segments.Add(curve.value); - totalPointCount += 1; - } - else if (preCurve.outSlope == 0f && Math.Abs(curve.inSlope) < 0.0001f) //LinearSegment - { - json.Curves[i].Segments.Add(0f); - json.Curves[i].Segments.Add(curve.time); - json.Curves[i].Segments.Add(curve.value); - totalPointCount += 1; - } - else //BezierSegment - { - var tangentLength = (curve.time - preCurve.time) / 3f; - json.Curves[i].Segments.Add(1f); - json.Curves[i].Segments.Add(preCurve.time + tangentLength); - json.Curves[i].Segments.Add(preCurve.outSlope * tangentLength + preCurve.value); - json.Curves[i].Segments.Add(curve.time - tangentLength); - json.Curves[i].Segments.Add(curve.value - curve.inSlope * tangentLength); - json.Curves[i].Segments.Add(curve.time); - json.Curves[i].Segments.Add(curve.value); - totalPointCount += 3; - } - totalSegmentCount++; - } - } - json.Meta.TotalSegmentCount = totalSegmentCount; - json.Meta.TotalPointCount = totalPointCount; - - json.UserData = new CubismMotion3Json.SerializableUserData[animation.Events.Count]; - var totalUserDataSize = 0; - for (var i = 0; i < animation.Events.Count; i++) - { - var @event = animation.Events[i]; - json.UserData[i] = new CubismMotion3Json.SerializableUserData - { - Time = @event.time, - Value = @event.value - }; - totalUserDataSize += @event.value.Length; - } - json.Meta.TotalUserDataSize = totalUserDataSize; + var motionJson = new CubismMotion3Json(animation, forceBezier); var animName = animation.Name; if (motions.ContainsKey(animName)) { animName = $"{animName}_{animation.GetHashCode()}"; + if (motions.ContainsKey(animName)) - { continue; - } } var motionPath = new JObject(new JProperty("File", $"motions/{animName}.motion3.json")); motions.Add(animName, new JArray(motionPath)); - File.WriteAllText($"{destMotionPath}{animName}.motion3.json", JsonConvert.SerializeObject(json, Formatting.Indented, new MyJsonConverter())); + File.WriteAllText($"{destMotionPath}{animName}.motion3.json", JsonConvert.SerializeObject(motionJson, Formatting.Indented, new MyJsonConverter())); } } + else + { + Logger.Warning($"No motions found for \"{modelName}\" model."); + } //expression var expressions = new JArray(); diff --git a/AssetStudioUtility/CubismLive2DExtractor/Live2DMotionMode.cs b/AssetStudioUtility/CubismLive2DExtractor/Live2DMotionMode.cs new file mode 100644 index 0000000..60c3acc --- /dev/null +++ b/AssetStudioUtility/CubismLive2DExtractor/Live2DMotionMode.cs @@ -0,0 +1,8 @@ +namespace CubismLive2DExtractor +{ + public enum Live2DMotionMode + { + MonoBehaviour, + AnimationClip + } +} From b27482e22b8382fa56fc90ff9871911946291995 Mon Sep 17 00:00:00 2001 From: VaDiM Date: Tue, 21 Nov 2023 00:31:09 +0300 Subject: [PATCH 09/22] Don't display SourceRevisionId in titles https://learn.microsoft.com/en-us/dotnet/core/compatibility/sdk/8.0/source-link --- AssetStudioGUI/AboutForm.cs | 8 +++++--- AssetStudioGUI/AssetStudioGUIForm.cs | 12 ++++++++---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/AssetStudioGUI/AboutForm.cs b/AssetStudioGUI/AboutForm.cs index 307a60e..1fb8ba0 100644 --- a/AssetStudioGUI/AboutForm.cs +++ b/AssetStudioGUI/AboutForm.cs @@ -10,13 +10,15 @@ namespace AssetStudioGUI public AboutForm() { InitializeComponent(); - var productName = Application.ProductName; var arch = Environment.Is64BitProcess ? "x64" : "x32"; + var appAssembly = typeof(Program).Assembly.GetName(); + var productName = appAssembly.Name; + var productVer = appAssembly.Version.ToString(); Text += " " + productName; productTitleLabel.Text = productName; - productVersionLabel.Text = $"v{Application.ProductVersion} [{arch}]"; + productVersionLabel.Text = $"v{productVer} [{arch}]"; productNamelabel.Text = productName; - modVersionLabel.Text = Application.ProductVersion; + modVersionLabel.Text = productVer; licenseRichTextBox.Text = GetLicenseText(); } diff --git a/AssetStudioGUI/AssetStudioGUIForm.cs b/AssetStudioGUI/AssetStudioGUIForm.cs index 4186eff..76d3c7e 100644 --- a/AssetStudioGUI/AssetStudioGUIForm.cs +++ b/AssetStudioGUI/AssetStudioGUIForm.cs @@ -112,11 +112,15 @@ namespace AssetStudioGUI [DllImport("gdi32.dll")] private static extern IntPtr AddFontMemResourceEx(IntPtr pbFont, uint cbFont, IntPtr pdv, [In] ref uint pcFonts); + private string guiTitle = string.Empty; + public AssetStudioGUIForm() { Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US"); InitializeComponent(); - Text = $"{Application.ProductName} v{Application.ProductVersion}"; + var appAssembly = typeof(Program).Assembly.GetName(); + guiTitle = $"{appAssembly.Name} v{appAssembly.Version}"; + Text = guiTitle; delayTimer = new System.Timers.Timer(800); delayTimer.Elapsed += new ElapsedEventHandler(delayTimer_Elapsed); displayAll.Checked = Properties.Settings.Default.displayAll; @@ -223,11 +227,11 @@ namespace AssetStudioGUI if (!string.IsNullOrEmpty(productName)) { - Text = $"{Application.ProductName} v{Application.ProductVersion} - {productName} - {assetsManager.assetsFileList[0].unityVersion} - {assetsManager.assetsFileList[0].m_TargetPlatform}"; + Text = $"{guiTitle} - {productName} - {assetsManager.assetsFileList[0].unityVersion} - {assetsManager.assetsFileList[0].m_TargetPlatform}"; } else { - Text = $"{Application.ProductName} v{Application.ProductVersion} - no productName - {assetsManager.assetsFileList[0].unityVersion} - {assetsManager.assetsFileList[0].m_TargetPlatform}"; + Text = $"{guiTitle} - no productName - {assetsManager.assetsFileList[0].unityVersion} - {assetsManager.assetsFileList[0].m_TargetPlatform}"; } assetListView.VirtualListSize = visibleAssets.Count; @@ -1340,7 +1344,7 @@ namespace AssetStudioGUI private void ResetForm() { - Text = $"{Application.ProductName} v{Application.ProductVersion}"; + Text = guiTitle; assetsManager.Clear(); assemblyLoader.Clear(); exportableAssets.Clear(); From efd06648ade2f455b3f7eecfd6388901bcb84404 Mon Sep 17 00:00:00 2001 From: VaDiM Date: Tue, 21 Nov 2023 01:35:25 +0300 Subject: [PATCH 10/22] Add support for separate PreloadData https://github.com/Perfare/AssetStudio/issues/690 https://docs.unity.cn/550/Documentation/Manual/AssetBundleInternalStructure.html --- AssetStudio/AssetsManager.cs | 3 +++ AssetStudio/Classes/AssetBundle.cs | 34 ++++++++++++++++++++++++----- AssetStudio/Classes/PreloadData.cs | 35 ++++++++++++++++++++++++++++++ AssetStudioCLI/Studio.cs | 24 ++++++++++++++------ AssetStudioGUI/Studio.cs | 23 ++++++++++++++------ 5 files changed, 99 insertions(+), 20 deletions(-) create mode 100644 AssetStudio/Classes/PreloadData.cs diff --git a/AssetStudio/AssetsManager.cs b/AssetStudio/AssetsManager.cs index b00ef27..fc9283b 100644 --- a/AssetStudio/AssetsManager.cs +++ b/AssetStudio/AssetsManager.cs @@ -532,6 +532,9 @@ namespace AssetStudio case ClassIDType.PlayerSettings: obj = new PlayerSettings(objectReader); break; + case ClassIDType.PreloadData: + obj = new PreloadData(objectReader); + break; case ClassIDType.RectTransform: obj = new RectTransform(objectReader); break; diff --git a/AssetStudio/Classes/AssetBundle.cs b/AssetStudio/Classes/AssetBundle.cs index 3883fce..0a6fd1c 100644 --- a/AssetStudio/Classes/AssetBundle.cs +++ b/AssetStudio/Classes/AssetBundle.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.Collections.Generic; namespace AssetStudio { @@ -23,22 +20,47 @@ namespace AssetStudio { public PPtr[] m_PreloadTable; public KeyValuePair[] m_Container; + public string m_AssetBundleName; + public string[] m_Dependencies; + public bool m_IsStreamedSceneAssetBundle; public AssetBundle(ObjectReader reader) : base(reader) { var m_PreloadTableSize = reader.ReadInt32(); m_PreloadTable = new PPtr[m_PreloadTableSize]; - for (int i = 0; i < m_PreloadTableSize; i++) + for (var i = 0; i < m_PreloadTableSize; i++) { m_PreloadTable[i] = new PPtr(reader); } var m_ContainerSize = reader.ReadInt32(); m_Container = new KeyValuePair[m_ContainerSize]; - for (int i = 0; i < m_ContainerSize; i++) + for (var i = 0; i < m_ContainerSize; i++) { m_Container[i] = new KeyValuePair(reader.ReadAlignedString(), new AssetInfo(reader)); } + + var m_MainAsset = new AssetInfo(reader); + + if (version[0] > 4 || (version[0] == 4 && version[1] >= 2)) //4.2 and up + { + var m_RuntimeCompatibility = reader.ReadUInt32(); + } + + if (version[0] >= 5) //5.0 and up + { + m_AssetBundleName = reader.ReadAlignedString(); + + var m_DependenciesSize = reader.ReadInt32(); + m_Dependencies = new string[m_DependenciesSize]; + + for (var i = 0; i < m_DependenciesSize; i++) + { + m_Dependencies[i] = reader.ReadAlignedString(); + } + + m_IsStreamedSceneAssetBundle = reader.ReadBoolean(); + } } } } diff --git a/AssetStudio/Classes/PreloadData.cs b/AssetStudio/Classes/PreloadData.cs new file mode 100644 index 0000000..a1f2224 --- /dev/null +++ b/AssetStudio/Classes/PreloadData.cs @@ -0,0 +1,35 @@ +namespace AssetStudio +{ + public sealed class PreloadData : NamedObject + { + public PPtr[] m_Assets; + + public PreloadData(ObjectReader reader) : base(reader) + { + var m_PreloadTableSize = reader.ReadInt32(); + m_Assets = new PPtr[m_PreloadTableSize]; + for (var i = 0; i < m_PreloadTableSize; i++) + { + m_Assets[i] = new PPtr(reader); + } + + /* + if (version[0] >= 5) //5.0 and up + { + var m_DependenciesSize = reader.ReadInt32(); + var m_Dependencies = new string[m_DependenciesSize]; + + for (var i = 0; i < m_DependenciesSize; i++) + { + m_Dependencies[i] = reader.ReadAlignedString(); + } + } + + if (version[0] > 2018 || (version[0] == 2018 && version[1] >= 2)) //2018.2 and up + { + var m_ExplicitDataLayout = reader.ReadBoolean(); + } + */ + } + } +} diff --git a/AssetStudioCLI/Studio.cs b/AssetStudioCLI/Studio.cs index d715566..f210f0c 100644 --- a/AssetStudioCLI/Studio.cs +++ b/AssetStudioCLI/Studio.cs @@ -62,6 +62,7 @@ namespace AssetStudioCLI var i = 0; foreach (var assetsFile in assetsManager.assetsFileList) { + var preloadTable = Array.Empty>(); foreach (var asset in assetsFile.Objects) { var assetItem = new AssetItem(asset); @@ -70,22 +71,31 @@ namespace AssetStudioCLI var isExportable = false; switch (asset) { + case PreloadData m_PreloadData: + preloadTable = m_PreloadData.m_Assets; + break; case AssetBundle m_AssetBundle: + var isStreamedSceneAssetBundle = m_AssetBundle.m_IsStreamedSceneAssetBundle; + if (!isStreamedSceneAssetBundle) + { + preloadTable = m_AssetBundle.m_PreloadTable; + } + assetItem.Text = string.IsNullOrEmpty(m_AssetBundle.m_AssetBundleName) ? m_AssetBundle.m_Name : m_AssetBundle.m_AssetBundleName; + foreach (var m_Container in m_AssetBundle.m_Container) { var preloadIndex = m_Container.Value.preloadIndex; - var preloadSize = m_Container.Value.preloadSize; + var preloadSize = isStreamedSceneAssetBundle ? preloadTable.Length : m_Container.Value.preloadSize; var preloadEnd = preloadIndex + preloadSize; - for (int k = preloadIndex; k < preloadEnd; k++) + for (var k = preloadIndex; k < preloadEnd; k++) { - var pptr = m_AssetBundle.m_PreloadTable[k]; + var pptr = preloadTable[k]; if (pptr.TryGet(out var obj)) { containers[obj] = m_Container.Key; } } } - assetItem.Text = m_AssetBundle.m_Name; break; case ResourceManager m_ResourceManager: foreach (var m_Container in m_ResourceManager.m_Container) @@ -110,7 +120,7 @@ namespace AssetStudioCLI if (!string.IsNullOrEmpty(m_VideoClip.m_OriginalPath)) assetItem.FullSize = asset.byteSize + m_VideoClip.m_ExternalResources.m_Size; assetItem.Text = m_VideoClip.m_Name; - break; + break; case Shader m_Shader: assetItem.Text = m_Shader.m_ParsedForm?.m_Name ?? m_Shader.m_Name; break; @@ -152,9 +162,9 @@ namespace AssetStudioCLI } foreach (var asset in fileAssetsList) { - if (containers.ContainsKey(asset.Asset)) + if (containers.TryGetValue(asset.Asset, out var container)) { - asset.Container = containers[asset.Asset]; + asset.Container = container; } } parsedAssetsList.AddRange(fileAssetsList); diff --git a/AssetStudioGUI/Studio.cs b/AssetStudioGUI/Studio.cs index 94ab0c2..02e6ee0 100644 --- a/AssetStudioGUI/Studio.cs +++ b/AssetStudioGUI/Studio.cs @@ -162,6 +162,8 @@ namespace AssetStudioGUI Progress.Reset(); foreach (var assetsFile in assetsManager.assetsFileList) { + var preloadTable = Array.Empty>(); + foreach (var asset in assetsFile.Objects) { var assetItem = new AssetItem(asset); @@ -170,6 +172,9 @@ namespace AssetStudioGUI var exportable = false; switch (asset) { + case PreloadData m_PreloadData: + preloadTable = m_PreloadData.m_Assets; + break; case GameObject m_GameObject: assetItem.Text = m_GameObject.m_Name; break; @@ -187,7 +192,7 @@ namespace AssetStudioGUI break; case VideoClip m_VideoClip: if (!string.IsNullOrEmpty(m_VideoClip.m_OriginalPath)) - assetItem.FullSize = asset.byteSize + (long)m_VideoClip.m_ExternalResources.m_Size; + assetItem.FullSize = asset.byteSize + m_VideoClip.m_ExternalResources.m_Size; assetItem.Text = m_VideoClip.m_Name; exportable = true; break; @@ -226,17 +231,23 @@ namespace AssetStudioGUI productName = m_PlayerSettings.productName; break; case AssetBundle m_AssetBundle: + var isStreamedSceneAssetBundle = m_AssetBundle.m_IsStreamedSceneAssetBundle; + if (!isStreamedSceneAssetBundle) + { + preloadTable = m_AssetBundle.m_PreloadTable; + } + assetItem.Text = string.IsNullOrEmpty(m_AssetBundle.m_AssetBundleName) ? m_AssetBundle.m_Name : m_AssetBundle.m_AssetBundleName; + foreach (var m_Container in m_AssetBundle.m_Container) { var preloadIndex = m_Container.Value.preloadIndex; - var preloadSize = m_Container.Value.preloadSize; + var preloadSize = isStreamedSceneAssetBundle ? preloadTable.Length : m_Container.Value.preloadSize; var preloadEnd = preloadIndex + preloadSize; - for (int k = preloadIndex; k < preloadEnd; k++) + for (var k = preloadIndex; k < preloadEnd; k++) { - containers.Add((m_AssetBundle.m_PreloadTable[k], m_Container.Key)); + containers.Add((preloadTable[k], m_Container.Key)); } } - assetItem.Text = m_AssetBundle.m_Name; break; case ResourceManager m_ResourceManager: foreach (var m_Container in m_ResourceManager.m_Container) @@ -349,7 +360,6 @@ namespace AssetStudioGUI Progress.Report(++j, assetsFileCount); } treeNodeDictionary.Clear(); - objectAssetItemDic.Clear(); return (productName, treeNodeCollection); @@ -387,7 +397,6 @@ namespace AssetStudioGUI typeMap.Add(assetsFile.unityVersion, items); } } - return typeMap; } From 632e5f8d08448fca30f927fc3dd5d59742c17863 Mon Sep 17 00:00:00 2001 From: VaDiM Date: Thu, 30 Nov 2023 04:35:58 +0300 Subject: [PATCH 11/22] [GUI] Add support for .lnk files via Drag&Drop. Close #13 --- AssetStudio/AssetsManager.cs | 2 +- .../Extensions/BinaryReaderExtensions.cs | 26 ++- AssetStudioGUI/AssetStudioGUIForm.cs | 23 ++- AssetStudioGUI/Components/LnkReader.cs | 164 ++++++++++++++++++ 4 files changed, 205 insertions(+), 10 deletions(-) create mode 100644 AssetStudioGUI/Components/LnkReader.cs diff --git a/AssetStudio/AssetsManager.cs b/AssetStudio/AssetsManager.cs index fc9283b..a07c521 100644 --- a/AssetStudio/AssetsManager.cs +++ b/AssetStudio/AssetsManager.cs @@ -82,7 +82,7 @@ namespace AssetStudio MergeSplitAssets(fullPath, true); fileList.AddRange(Directory.GetFiles(fullPath, "*.*", SearchOption.AllDirectories)); } - else + else if (File.Exists(fullPath)) { parentPath = Path.GetDirectoryName(fullPath); fileList.Add(fullPath); diff --git a/AssetStudio/Extensions/BinaryReaderExtensions.cs b/AssetStudio/Extensions/BinaryReaderExtensions.cs index 5d6fc06..b9753fb 100644 --- a/AssetStudio/Extensions/BinaryReaderExtensions.cs +++ b/AssetStudio/Extensions/BinaryReaderExtensions.cs @@ -35,10 +35,13 @@ namespace AssetStudio return ""; } - public static string ReadStringToNull(this BinaryReader reader, int maxLength = 32767) + public static string ReadStringToNull(this BinaryReader reader, int maxLength = 32767, Encoding encoding = null) { + if (encoding?.CodePage == 1200) //Unicode (UTF-16LE) + return reader.ReadUnicodeStringToNull(maxLength * 2); + var bytes = new List(); - int count = 0; + var count = 0; while (reader.BaseStream.Position != reader.BaseStream.Length && count < maxLength) { var b = reader.ReadByte(); @@ -49,7 +52,24 @@ namespace AssetStudio bytes.Add(b); count++; } - return Encoding.UTF8.GetString(bytes.ToArray()); + return encoding?.GetString(bytes.ToArray()) ?? Encoding.UTF8.GetString(bytes.ToArray()); + } + + private static string ReadUnicodeStringToNull(this BinaryReader reader, int maxLength) + { + var bytes = new List(); + var count = 0; + while (reader.BaseStream.Position != reader.BaseStream.Length && count < maxLength) + { + var b = reader.ReadBytes(2); + if (b.Length < 2 || (b[0] == 0 && b[1] == 0)) + { + break; + } + bytes.AddRange(b); + count += 2; + } + return Encoding.Unicode.GetString(bytes.ToArray()); } public static Quaternion ReadQuaternion(this BinaryReader reader) diff --git a/AssetStudioGUI/AssetStudioGUIForm.cs b/AssetStudioGUI/AssetStudioGUIForm.cs index 76d3c7e..87f9905 100644 --- a/AssetStudioGUI/AssetStudioGUIForm.cs +++ b/AssetStudioGUI/AssetStudioGUIForm.cs @@ -146,14 +146,25 @@ namespace AssetStudioGUI private async void AssetStudioGUIForm_DragDrop(object sender, DragEventArgs e) { var paths = (string[])e.Data.GetData(DataFormats.FileDrop); - if (paths.Length > 0) + if (paths.Length == 0) + return; + + ResetForm(); + for (var i = 0; i < paths.Length; i++) { - ResetForm(); - assetsManager.SpecifyUnityVersion = specifyUnityVersion.Text; - await Task.Run(() => assetsManager.LoadFilesAndFolders(out openDirectoryBackup, paths)); - saveDirectoryBackup = openDirectoryBackup; - BuildAssetStructures(); + if (paths[i].ToLower().EndsWith(".lnk")) + { + var targetPath = LnkReader.GetLnkTarget(paths[i]); + if (!string.IsNullOrEmpty(targetPath)) + { + paths[i] = targetPath; + } + } } + assetsManager.SpecifyUnityVersion = specifyUnityVersion.Text; + await Task.Run(() => assetsManager.LoadFilesAndFolders(out openDirectoryBackup, paths)); + saveDirectoryBackup = openDirectoryBackup; + BuildAssetStructures(); } private async void loadFile_Click(object sender, EventArgs e) diff --git a/AssetStudioGUI/Components/LnkReader.cs b/AssetStudioGUI/Components/LnkReader.cs new file mode 100644 index 0000000..aa3a3e3 --- /dev/null +++ b/AssetStudioGUI/Components/LnkReader.cs @@ -0,0 +1,164 @@ +// Shortcut (.lnk) file reader +// by aelurum +// Based on https://github.com/libyal/liblnk/blob/main/documentation/Windows%20Shortcut%20File%20(LNK)%20format.asciidoc + +using AssetStudio; +using System; +using System.IO; +using System.Text; + +namespace AssetStudioGUI +{ + public static class LnkReader + { + [Flags] + private enum LnkDataFlags + { + //The LNK file contains a link target identifier + HasTargetIDList = 0x00000001, + //The LNK file contains location information + HasLinkInfo = 0x00000002, + } + + [Flags] + private enum LnkLocFlags + { + //The linked file is on a volume + //If set the volume information and the local path contain data + VolumeIDAndLocalBasePath = 0x0001, + //The linked file is on a network share + //If set the network share information and common path contain data + CommonNetworkRelativeLinkAndPathSuffix = 0x0002 + } + + [Flags] + private enum PathTypeFlags + { + IsUnicodeLocalPath = 0x01, + IsUnicodeNetShareName = 0x02, + IsUnicodeCommonPath = 0x04 + } + + public static string GetLnkTarget(string filePath) + { + var targetPath = string.Empty; + var pathType = (PathTypeFlags)0; + Encoding sysEncoding; + try + { + sysEncoding = GetSysEncoding(); + Logger.Debug($"System default text encoding: {sysEncoding.CodePage}"); + } + catch (Exception ex) + { + Logger.Error("Text encoding error", ex); + return null; + } + + using (var reader = new FileReader(filePath)) + { + reader.Endian = EndianType.LittleEndian; + + var headerSize = reader.ReadUInt32(); //76 bytes + reader.Position = 20; //skip LNK class identifier (GUID) + var dataFlags = (LnkDataFlags)reader.ReadUInt32(); + if ((dataFlags & LnkDataFlags.HasLinkInfo) == 0) + { + Logger.Warning("Unsupported type of .lnk file. Link info was not found."); + return null; + } + reader.Position = headerSize; + + //Skip the shell item ID list + if ((dataFlags & LnkDataFlags.HasTargetIDList) != 0) + { + var itemIDListSize = reader.ReadUInt16(); + reader.Position += itemIDListSize; + } + + //The offsets is relative to the start of the location information block + var locInfoPos = reader.Position; + var locInfoFullSize = reader.ReadUInt32(); + if (locInfoFullSize == 0) + { + Logger.Warning("Unsupported type of .lnk file. Link info was not found."); + return null; + } + var locInfoHeaderSize = reader.ReadUInt32(); + var locFlags = (LnkLocFlags)reader.ReadUInt32(); + //Offset to the volume information block + var offsetVolumeInfo = reader.ReadUInt32(); + //Offset to the ANSI local path + var offsetLocalPath = reader.ReadUInt32(); + //Offset to the network share information block + var offsetNetInfo = reader.ReadUInt32(); + //Offset to the ANSI common path. 0 if not available + var offsetCommonPath = reader.ReadUInt32(); + if (locInfoHeaderSize > 28) + { + //Offset to the Unicode local path + offsetLocalPath = reader.ReadUInt32(); + pathType |= PathTypeFlags.IsUnicodeLocalPath; + } + if (locInfoHeaderSize > 32) + { + //Offset to the Unicode common path + offsetCommonPath = reader.ReadUInt32(); + pathType |= PathTypeFlags.IsUnicodeCommonPath; + } + + //Read local path, if exist + if (offsetLocalPath > 0) + { + reader.Position = locInfoPos + offsetLocalPath; + targetPath = (pathType & PathTypeFlags.IsUnicodeLocalPath) != 0 + ? reader.ReadStringToNull(encoding: Encoding.Unicode) + : reader.ReadStringToNull(encoding: sysEncoding); + } + + //Read network path, if exist + if (locFlags == LnkLocFlags.CommonNetworkRelativeLinkAndPathSuffix) + { + reader.Position = locInfoPos + offsetNetInfo; + var netInfoSize = reader.ReadUInt32(); + var netInfoFlags = reader.ReadUInt32(); + //Offset to the ANSI network share name. The offset is relative to the start of the network share information block + var offsetNetShareName = reader.ReadUInt32(); + if (offsetNetShareName > 20) + { + reader.Position = locInfoPos + offsetNetInfo + 20; + //Offset to the Unicode network share name + offsetNetShareName = reader.ReadUInt32(); + pathType |= PathTypeFlags.IsUnicodeNetShareName; + } + if (offsetNetShareName > 0) + { + reader.Position = locInfoPos + offsetNetInfo + offsetNetShareName; + targetPath = (pathType & PathTypeFlags.IsUnicodeNetShareName) != 0 + ? reader.ReadStringToNull(encoding: Encoding.Unicode) + : reader.ReadStringToNull(encoding: sysEncoding); + } + } + + //Read common path, if exist + if (offsetCommonPath > 0) + { + reader.Position = locInfoPos + offsetCommonPath; + var commonPath = (pathType & PathTypeFlags.IsUnicodeCommonPath) != 0 + ? reader.ReadStringToNull(encoding: Encoding.Unicode) + : reader.ReadStringToNull(encoding: sysEncoding); + targetPath = Path.Combine(targetPath, commonPath); + } + } + return targetPath; + } + + private static Encoding GetSysEncoding() + { +#if !NETFRAMEWORK + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); +#endif + return Encoding.GetEncoding(0); + } + } +} From d42a1879abe1e2c9b886a747943dbb114594f641 Mon Sep 17 00:00:00 2001 From: VaDiM Date: Tue, 5 Dec 2023 00:37:16 +0300 Subject: [PATCH 12/22] [GUI] Add support for Drag&Drop files from a browser Co-Authored-By: Luke <17146677+lukefz@users.noreply.github.com> --- AssetStudioGUI/AssetStudioGUIForm.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AssetStudioGUI/AssetStudioGUIForm.cs b/AssetStudioGUI/AssetStudioGUIForm.cs index 87f9905..9d7d97b 100644 --- a/AssetStudioGUI/AssetStudioGUIForm.cs +++ b/AssetStudioGUI/AssetStudioGUIForm.cs @@ -139,7 +139,7 @@ namespace AssetStudioGUI { if (e.Data.GetDataPresent(DataFormats.FileDrop)) { - e.Effect = DragDropEffects.Move; + e.Effect = DragDropEffects.Copy; } } From f82a73f01814f0b434c8eee0ed16f3eb9a199227 Mon Sep 17 00:00:00 2001 From: VaDiM Date: Wed, 6 Dec 2023 17:28:10 +0300 Subject: [PATCH 13/22] [GUI] Add console logger --- .../ColorConsole.cs | 10 +- .../ColorConsoleHelper.cs | 25 ++-- AssetStudioCLI/CLILogger.cs | 11 +- AssetStudioCLI/Exporter.cs | 2 +- AssetStudioCLI/Options/CLIOptions.cs | 8 +- AssetStudioCLI/Studio.cs | 2 +- AssetStudioGUI/AssetStudioGUIForm.Designer.cs | 25 ++++ AssetStudioGUI/AssetStudioGUIForm.cs | 46 ++++++-- AssetStudioGUI/Components/ConsoleWindow.cs | 67 +++++++++++ AssetStudioGUI/GUILogger.cs | 110 ++++++++++++++++++ .../Properties/Settings.Designer.cs | 26 ++++- AssetStudioGUI/Properties/Settings.settings | 6 + 12 files changed, 296 insertions(+), 42 deletions(-) rename AssetStudioCLI/Components/CLIAnsiColors.cs => AssetStudio/ColorConsole.cs (87%) rename AssetStudioCLI/Components/CLIWinAnsiFix.cs => AssetStudio/ColorConsoleHelper.cs (70%) create mode 100644 AssetStudioGUI/Components/ConsoleWindow.cs diff --git a/AssetStudioCLI/Components/CLIAnsiColors.cs b/AssetStudio/ColorConsole.cs similarity index 87% rename from AssetStudioCLI/Components/CLIAnsiColors.cs rename to AssetStudio/ColorConsole.cs index b830f67..afad630 100644 --- a/AssetStudioCLI/Components/CLIAnsiColors.cs +++ b/AssetStudio/ColorConsole.cs @@ -1,10 +1,10 @@ using System; -namespace AssetStudioCLI +namespace AssetStudio { // Represents set with 16 base colors using ANSI escape codes, which should be supported in most terminals // (well, except for windows editions before windows 10) - public static class CLIAnsiColors + public static class ColorConsole { public static readonly string Black = "\u001b[30m", @@ -27,7 +27,7 @@ namespace AssetStudioCLI public static string Color(this string str, string ansiColor) { - if (!CLIWinAnsiFix.isAnsiSupported) + if (!ColorConsoleHelper.isAnsiCodesSupported) { return str; } @@ -35,10 +35,10 @@ namespace AssetStudioCLI return $"{ansiColor}{str}{Reset}"; } - public static void ANSICodesTest() + public static void AnsiCodesTest() { Console.WriteLine("ANSI escape codes test"); - Console.WriteLine($"Supported: {CLIWinAnsiFix.isAnsiSupported}"); + Console.WriteLine($"Supported: {ColorConsoleHelper.isAnsiCodesSupported}"); Console.WriteLine("\u001b[30m A \u001b[31m B \u001b[32m C \u001b[33m D \u001b[0m"); Console.WriteLine("\u001b[34m E \u001b[35m F \u001b[36m G \u001b[37m H \u001b[0m"); Console.WriteLine("\u001b[30;1m A \u001b[31;1m B \u001b[32;1m C \u001b[33;1m D \u001b[0m"); diff --git a/AssetStudioCLI/Components/CLIWinAnsiFix.cs b/AssetStudio/ColorConsoleHelper.cs similarity index 70% rename from AssetStudioCLI/Components/CLIWinAnsiFix.cs rename to AssetStudio/ColorConsoleHelper.cs index 946c461..b048b8e 100644 --- a/AssetStudioCLI/Components/CLIWinAnsiFix.cs +++ b/AssetStudio/ColorConsoleHelper.cs @@ -2,11 +2,11 @@ using System; using System.Runtime.InteropServices; -namespace AssetStudioCLI +namespace AssetStudio { - static class CLIWinAnsiFix + internal static class ColorConsoleHelper { - public static readonly bool isAnsiSupported; + public static readonly bool isAnsiCodesSupported; private const int STD_OUTPUT_HANDLE = -11; private const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004; @@ -19,21 +19,21 @@ namespace AssetStudioCLI [DllImport("kernel32.dll")] private static extern IntPtr GetStdHandle(int nStdHandle); - static CLIWinAnsiFix() + static ColorConsoleHelper() { - bool isWin = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + var isWin = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); if (isWin) { - isAnsiSupported = TryEnableVTMode(); - if (!isAnsiSupported) + isAnsiCodesSupported = TryEnableVTMode(); + if (!isAnsiCodesSupported) { //Check for bash terminal emulator. E.g., Git Bash, Cmder - isAnsiSupported = Environment.GetEnvironmentVariable("TERM") != null; + isAnsiCodesSupported = Environment.GetEnvironmentVariable("TERM") != null; } } else { - isAnsiSupported = true; + isAnsiCodesSupported = true; } } @@ -51,12 +51,7 @@ namespace AssetStudioCLI outConsoleMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; - if (!SetConsoleMode(iStdOut, outConsoleMode)) - { - return false; - } - - return true; + return SetConsoleMode(iStdOut, outConsoleMode); } } } diff --git a/AssetStudioCLI/CLILogger.cs b/AssetStudioCLI/CLILogger.cs index 9aeac57..3c199a0 100644 --- a/AssetStudioCLI/CLILogger.cs +++ b/AssetStudioCLI/CLILogger.cs @@ -29,6 +29,7 @@ namespace AssetStudioCLI LogName = $"{appAssembly.Name}_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.log"; LogPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, LogName); var arch = Environment.Is64BitProcess ? "x64" : "x32"; + Console.OutputEncoding = System.Text.Encoding.UTF8; LogToFile(LoggerEvent.Verbose, $"---{appAssembly.Name} v{appAssembly.Version} [{arch}] | Logger launched---\n" + $"CMD Args: {string.Join(" ", CLIOptions.cliArgs)}"); @@ -36,15 +37,15 @@ namespace AssetStudioCLI private static string ColorLogLevel(LoggerEvent logLevel) { - string formattedLevel = $"[{logLevel}]"; + var formattedLevel = $"[{logLevel}]"; switch (logLevel) { case LoggerEvent.Info: - return $"{formattedLevel.Color(CLIAnsiColors.BrightCyan)}"; + return $"{formattedLevel.Color(ColorConsole.BrightCyan)}"; case LoggerEvent.Warning: - return $"{formattedLevel.Color(CLIAnsiColors.BrightYellow)}"; + return $"{formattedLevel.Color(ColorConsole.BrightYellow)}"; case LoggerEvent.Error: - return $"{formattedLevel.Color(CLIAnsiColors.BrightRed)}"; + return $"{formattedLevel.Color(ColorConsole.BrightRed)}"; default: return formattedLevel; } @@ -59,7 +60,7 @@ namespace AssetStudioCLI string formattedMessage; if (consoleMode) { - string colorLogLevel = ColorLogLevel(logMsgLevel); + var colorLogLevel = ColorLogLevel(logMsgLevel); formattedMessage = $"{colorLogLevel} {message}"; if (multiLine) { diff --git a/AssetStudioCLI/Exporter.cs b/AssetStudioCLI/Exporter.cs index ca7ac23..8b1289e 100644 --- a/AssetStudioCLI/Exporter.cs +++ b/AssetStudioCLI/Exporter.cs @@ -327,7 +327,7 @@ namespace AssetStudioCLI Directory.CreateDirectory(dir); return true; } - Logger.Error($"Export error. File \"{fullPath.Color(CLIAnsiColors.BrightRed)}\" already exist"); + Logger.Error($"Export error. File \"{fullPath.Color(ColorConsole.BrightRed)}\" already exist"); return false; } diff --git a/AssetStudioCLI/Options/CLIOptions.cs b/AssetStudioCLI/Options/CLIOptions.cs index 7c09332..3fd4810 100644 --- a/AssetStudioCLI/Options/CLIOptions.cs +++ b/AssetStudioCLI/Options/CLIOptions.cs @@ -397,8 +397,8 @@ namespace AssetStudioCLI.Options { cliArgs = args; - var brightYellow = CLIAnsiColors.BrightYellow; - var brightRed = CLIAnsiColors.BrightRed; + var brightYellow = ColorConsole.BrightYellow; + var brightRed = ColorConsole.BrightRed; if (args.Length == 0 || args.Any(x => x.ToLower() == "-h" || x.ToLower() == "--help" || x.ToLower() == "-?")) { @@ -858,7 +858,7 @@ namespace AssetStudioCLI.Options } catch (Exception ex) { - Console.WriteLine("Unknown Error.".Color(CLIAnsiColors.Red)); + Console.WriteLine("Unknown Error.".Color(ColorConsole.Red)); Console.WriteLine(ex); return; } @@ -901,7 +901,7 @@ namespace AssetStudioCLI.Options var arg = isFlag ? "flag" : "option"; var rand = new Random(); var rndOption = optionDesc.ElementAt(rand.Next(0, optionDesc.Length)); - Console.WriteLine($"Did you mean [{$"{rndOption.Key}".Color(CLIAnsiColors.BrightYellow)}] {arg}?"); + Console.WriteLine($"Did you mean [{$"{rndOption.Key}".Color(ColorConsole.BrightYellow)}] {arg}?"); Console.WriteLine($"Here's a description of it: \n\n{rndOption.Value}"); return true; diff --git a/AssetStudioCLI/Studio.cs b/AssetStudioCLI/Studio.cs index f210f0c..e095f40 100644 --- a/AssetStudioCLI/Studio.cs +++ b/AssetStudioCLI/Studio.cs @@ -7,7 +7,7 @@ using System.Linq; using System.Xml.Linq; using static AssetStudioCLI.Exporter; using static CubismLive2DExtractor.Live2DExtractor; -using Ansi = AssetStudioCLI.CLIAnsiColors; +using Ansi = AssetStudio.ColorConsole; namespace AssetStudioCLI { diff --git a/AssetStudioGUI/AssetStudioGUIForm.Designer.cs b/AssetStudioGUI/AssetStudioGUIForm.Designer.cs index 0752b33..0d396be 100644 --- a/AssetStudioGUI/AssetStudioGUIForm.Designer.cs +++ b/AssetStudioGUI/AssetStudioGUIForm.Designer.cs @@ -77,6 +77,8 @@ this.allToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.debugMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.toolStripMenuItem15 = new System.Windows.Forms.ToolStripMenuItem(); + this.showConsoleToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.writeLogToFileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.exportClassStructuresMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.aboutToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.splitContainer1 = new System.Windows.Forms.SplitContainer(); @@ -536,6 +538,8 @@ // this.debugMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.toolStripMenuItem15, + this.showConsoleToolStripMenuItem, + this.writeLogToFileToolStripMenuItem, this.exportClassStructuresMenuItem}); this.debugMenuItem.Name = "debugMenuItem"; this.debugMenuItem.Size = new System.Drawing.Size(54, 20); @@ -549,6 +553,24 @@ this.toolStripMenuItem15.Text = "Show all error messages"; this.toolStripMenuItem15.Click += new System.EventHandler(this.toolStripMenuItem15_Click); // + // showConsoleToolStripMenuItem + // + this.showConsoleToolStripMenuItem.Checked = true; + this.showConsoleToolStripMenuItem.CheckOnClick = true; + this.showConsoleToolStripMenuItem.CheckState = System.Windows.Forms.CheckState.Checked; + this.showConsoleToolStripMenuItem.Name = "showConsoleToolStripMenuItem"; + this.showConsoleToolStripMenuItem.Size = new System.Drawing.Size(200, 22); + this.showConsoleToolStripMenuItem.Text = "Show console logger"; + this.showConsoleToolStripMenuItem.Click += new System.EventHandler(this.showConsoleToolStripMenuItem_Click); + // + // writeLogToFileToolStripMenuItem + // + this.writeLogToFileToolStripMenuItem.CheckOnClick = true; + this.writeLogToFileToolStripMenuItem.Name = "writeLogToFileToolStripMenuItem"; + this.writeLogToFileToolStripMenuItem.Size = new System.Drawing.Size(200, 22); + this.writeLogToFileToolStripMenuItem.Text = "Write log to file"; + this.writeLogToFileToolStripMenuItem.CheckedChanged += new System.EventHandler(this.writeLogToFileToolStripMenuItem_CheckedChanged); + // // exportClassStructuresMenuItem // this.exportClassStructuresMenuItem.Name = "exportClassStructuresMenuItem"; @@ -1256,6 +1278,7 @@ this.Name = "AssetStudioGUIForm"; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; this.Text = "AssetStudioModGUI"; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.AssetStudioGUIForm_FormClosing); this.DragDrop += new System.Windows.Forms.DragEventHandler(this.AssetStudioGUIForm_DragDrop); this.DragEnter += new System.Windows.Forms.DragEventHandler(this.AssetStudioGUIForm_DragEnter); this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.AssetStudioForm_KeyDown); @@ -1405,6 +1428,8 @@ private System.Windows.Forms.ToolStripMenuItem showRelatedAssetsToolStripMenuItem; private System.Windows.Forms.ToolStripSeparator toolStripSeparator7; private System.Windows.Forms.ListView assetListView; + private System.Windows.Forms.ToolStripMenuItem showConsoleToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem writeLogToFileToolStripMenuItem; } } diff --git a/AssetStudioGUI/AssetStudioGUIForm.cs b/AssetStudioGUI/AssetStudioGUIForm.cs index 9d7d97b..7a3a004 100644 --- a/AssetStudioGUI/AssetStudioGUIForm.cs +++ b/AssetStudioGUI/AssetStudioGUIForm.cs @@ -117,20 +117,26 @@ namespace AssetStudioGUI public AssetStudioGUIForm() { Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US"); + ConsoleWindow.RunConsole(Properties.Settings.Default.showConsole); InitializeComponent(); + var appAssembly = typeof(Program).Assembly.GetName(); guiTitle = $"{appAssembly.Name} v{appAssembly.Version}"; Text = guiTitle; + delayTimer = new System.Timers.Timer(800); - delayTimer.Elapsed += new ElapsedEventHandler(delayTimer_Elapsed); + delayTimer.Elapsed += delayTimer_Elapsed; displayAll.Checked = Properties.Settings.Default.displayAll; displayInfo.Checked = Properties.Settings.Default.displayInfo; enablePreview.Checked = Properties.Settings.Default.enablePreview; + showConsoleToolStripMenuItem.Checked = Properties.Settings.Default.showConsole; FMODinit(); listSearchFilterMode.SelectedIndex = 0; logger = new GUILogger(StatusStripUpdate); Logger.Default = logger; + writeLogToFileToolStripMenuItem.Checked = Properties.Settings.Default.useFileLogger; + Progress.Default = new Progress(SetProgressBarValue); Studio.StatusStripUpdate = StatusStripUpdate; } @@ -233,17 +239,11 @@ namespace AssetStudioGUI return; } - (var productName, var treeNodeCollection) = await Task.Run(() => BuildAssetData()); + var (productName, treeNodeCollection) = await Task.Run(() => BuildAssetData()); var typeMap = await Task.Run(() => BuildClassStructure()); + productName = string.IsNullOrEmpty(productName) ? "no productName" : productName; - if (!string.IsNullOrEmpty(productName)) - { - Text = $"{guiTitle} - {productName} - {assetsManager.assetsFileList[0].unityVersion} - {assetsManager.assetsFileList[0].m_TargetPlatform}"; - } - else - { - Text = $"{guiTitle} - no productName - {assetsManager.assetsFileList[0].unityVersion} - {assetsManager.assetsFileList[0].m_TargetPlatform}"; - } + Text = $"{guiTitle} - {productName} - {assetsManager.assetsFileList[0].unityVersion} - {assetsManager.assetsFileList[0].m_TargetPlatform}"; assetListView.VirtualListSize = visibleAssets.Count; @@ -2011,6 +2011,32 @@ namespace AssetStudioGUI } } + private void showConsoleToolStripMenuItem_Click(object sender, EventArgs e) + { + var showConsole = showConsoleToolStripMenuItem.Checked; + if (showConsole) + ConsoleWindow.ShowConsoleWindow(); + else + ConsoleWindow.HideConsoleWindow(); + + Properties.Settings.Default.showConsole = showConsole; + Properties.Settings.Default.Save(); + } + + private void writeLogToFileToolStripMenuItem_CheckedChanged(object sender, EventArgs e) + { + var useFileLogger = writeLogToFileToolStripMenuItem.Checked; + logger.UseFileLogger = useFileLogger; + + Properties.Settings.Default.useFileLogger = useFileLogger; + Properties.Settings.Default.Save(); + } + + private void AssetStudioGUIForm_FormClosing(object sender, FormClosingEventArgs e) + { + Logger.Verbose("Closing AssetStudio"); + } + #region FMOD private void FMODinit() { diff --git a/AssetStudioGUI/Components/ConsoleWindow.cs b/AssetStudioGUI/Components/ConsoleWindow.cs new file mode 100644 index 0000000..efac8e2 --- /dev/null +++ b/AssetStudioGUI/Components/ConsoleWindow.cs @@ -0,0 +1,67 @@ +using System; +using System.Runtime.InteropServices; +using AssetStudio; + +namespace AssetStudioGUI +{ + internal static class ConsoleWindow + { + private enum CtrlSignalType + { + CTRL_C_EVENT, + CTRL_BREAK_EVENT, + } + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool AllocConsole(); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern IntPtr GetConsoleWindow(); + + [DllImport("user32.dll", SetLastError = true)] + private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool SetConsoleCtrlHandler(EventHandler handler, bool add); + + private delegate bool EventHandler(CtrlSignalType ctrlSignal); + private static EventHandler eventHandler; + private static IntPtr ConsoleWindowHandle; + private static readonly int SW_HIDE = 0; + private static readonly int SW_SHOW = 5; + + private static bool CloseEventHandler(CtrlSignalType ctrlSignal) + { + switch (ctrlSignal) + { + case CtrlSignalType.CTRL_C_EVENT: + case CtrlSignalType.CTRL_BREAK_EVENT: + return true; + default: + Logger.Verbose("Closing AssetStudio"); + return false; + } + } + + public static void RunConsole(bool showConsole) + { + AllocConsole(); + ConsoleWindowHandle = GetConsoleWindow(); + eventHandler += CloseEventHandler; + SetConsoleCtrlHandler(eventHandler, true); + + if (!showConsole) + HideConsoleWindow(); + } + + public static void ShowConsoleWindow() + { + ShowWindow(ConsoleWindowHandle, SW_SHOW); + } + + public static void HideConsoleWindow() + { + ShowWindow(ConsoleWindowHandle, SW_HIDE); + } + } +} diff --git a/AssetStudioGUI/GUILogger.cs b/AssetStudioGUI/GUILogger.cs index 0e780ed..c0c50f9 100644 --- a/AssetStudioGUI/GUILogger.cs +++ b/AssetStudioGUI/GUILogger.cs @@ -1,5 +1,8 @@ using AssetStudio; using System; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; using System.Windows.Forms; namespace AssetStudioGUI @@ -7,15 +10,122 @@ namespace AssetStudioGUI class GUILogger : ILogger { public bool ShowErrorMessage = false; + private bool IsFileLoggerRunning = false; + private string LoggerInitString; + private string FileLogName; + private string FileLogPath; private Action action; + private bool _useFileLogger = false; + public bool UseFileLogger + { + get => _useFileLogger; + set + { + _useFileLogger = value; + if (_useFileLogger && !IsFileLoggerRunning) + { + var appAssembly = typeof(Program).Assembly.GetName(); + FileLogName = $"{appAssembly.Name}_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.log"; + FileLogPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, FileLogName); + + LogToFile(LoggerEvent.Verbose, $"# {LoggerInitString} - Logger launched #"); + IsFileLoggerRunning = true; + } + else if (!_useFileLogger && IsFileLoggerRunning) + { + LogToFile(LoggerEvent.Verbose, "# Logger closed #"); + IsFileLoggerRunning = false; + } + } + } + public GUILogger(Action action) { this.action = action; + + var appAssembly = typeof(Program).Assembly.GetName(); + var arch = Environment.Is64BitProcess ? "x64" : "x32"; + var frameworkName = AppDomain.CurrentDomain.SetupInformation.TargetFrameworkName; + LoggerInitString = $"{appAssembly.Name} v{appAssembly.Version} [{arch}] [{frameworkName}]"; + try + { + Console.Title = $"Console Logger - {appAssembly.Name} v{appAssembly.Version}"; + Console.OutputEncoding = System.Text.Encoding.UTF8; + } + catch + { + // ignored + } + Console.WriteLine($"# {LoggerInitString}"); + } + + private static string ColorLogLevel(LoggerEvent logLevel) + { + var formattedLevel = $"[{logLevel}]"; + switch (logLevel) + { + case LoggerEvent.Info: + return $"{formattedLevel.Color(ColorConsole.BrightCyan)}"; + case LoggerEvent.Warning: + return $"{formattedLevel.Color(ColorConsole.BrightYellow)}"; + case LoggerEvent.Error: + return $"{formattedLevel.Color(ColorConsole.BrightRed)}"; + default: + return formattedLevel; + } + } + + private static string FormatMessage(LoggerEvent logMsgLevel, string message, bool toConsole) + { + message = message.TrimEnd(); + var multiLine = message.Contains('\n'); + + string formattedMessage; + if (toConsole) + { + var colorLogLevel = ColorLogLevel(logMsgLevel); + formattedMessage = $"{colorLogLevel} {message}"; + if (multiLine) + { + formattedMessage = formattedMessage.Replace("\n", $"\n{colorLogLevel} "); + } + } + else + { + var curTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); + message = Regex.Replace(message, @"\e\[[0-9;]*m(?:\e\[K)?", ""); //Delete ANSI colors + var logLevel = $"{logMsgLevel.ToString().ToUpper(),-7}"; + formattedMessage = $"{curTime} | {logLevel} | {message}"; + if (multiLine) + { + formattedMessage = formattedMessage.Replace("\n", $"\n{curTime} | {logLevel} | "); + } + } + + return formattedMessage; + } + + private async void LogToFile(LoggerEvent logMsgLevel, string message) + { + using (var sw = new StreamWriter(FileLogPath, append: true, System.Text.Encoding.UTF8)) + { + await sw.WriteLineAsync(FormatMessage(logMsgLevel, message, toConsole: false)); + } } public void Log(LoggerEvent loggerEvent, string message, bool ignoreLevel) { + //File logger + if (_useFileLogger) + { + LogToFile(loggerEvent, message); + } + + //Console logger + Console.WriteLine(FormatMessage(loggerEvent, message, toConsole: true)); + + //GUI logger switch (loggerEvent) { case LoggerEvent.Error: diff --git a/AssetStudioGUI/Properties/Settings.Designer.cs b/AssetStudioGUI/Properties/Settings.Designer.cs index 038ad17..60303fb 100644 --- a/AssetStudioGUI/Properties/Settings.Designer.cs +++ b/AssetStudioGUI/Properties/Settings.Designer.cs @@ -12,7 +12,7 @@ namespace AssetStudioGUI.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.7.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.8.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); @@ -310,5 +310,29 @@ namespace AssetStudioGUI.Properties { this["l2dForceBezier"] = value; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool showConsole { + get { + return ((bool)(this["showConsole"])); + } + set { + this["showConsole"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool useFileLogger { + get { + return ((bool)(this["useFileLogger"])); + } + set { + this["useFileLogger"] = value; + } + } } } diff --git a/AssetStudioGUI/Properties/Settings.settings b/AssetStudioGUI/Properties/Settings.settings index e8ab68d..eed8233 100644 --- a/AssetStudioGUI/Properties/Settings.settings +++ b/AssetStudioGUI/Properties/Settings.settings @@ -74,5 +74,11 @@ False + + True + + + False + \ No newline at end of file From 60aef1b8ed68c765a0de991272f6d8d43fe071e3 Mon Sep 17 00:00:00 2001 From: VaDiM Date: Wed, 6 Dec 2023 18:15:10 +0300 Subject: [PATCH 14/22] [GUI] Add option to not build a tree structure --- AssetStudioGUI/AssetStudioGUIForm.Designer.cs | 14 ++++++++++++++ AssetStudioGUI/AssetStudioGUIForm.cs | 7 +++++++ AssetStudioGUI/Properties/Settings.Designer.cs | 12 ++++++++++++ AssetStudioGUI/Properties/Settings.settings | 3 +++ AssetStudioGUI/Studio.cs | 15 +++++++++++---- 5 files changed, 47 insertions(+), 4 deletions(-) diff --git a/AssetStudioGUI/AssetStudioGUIForm.Designer.cs b/AssetStudioGUI/AssetStudioGUIForm.Designer.cs index 0d396be..38adb76 100644 --- a/AssetStudioGUI/AssetStudioGUIForm.Designer.cs +++ b/AssetStudioGUI/AssetStudioGUIForm.Designer.cs @@ -41,6 +41,7 @@ this.displayAll = new System.Windows.Forms.ToolStripMenuItem(); this.enablePreview = new System.Windows.Forms.ToolStripMenuItem(); this.displayInfo = new System.Windows.Forms.ToolStripMenuItem(); + this.buildTreeStructureToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.toolStripMenuItem14 = new System.Windows.Forms.ToolStripMenuItem(); this.specifyUnityVersion = new System.Windows.Forms.ToolStripTextBox(); this.showExpOpt = new System.Windows.Forms.ToolStripMenuItem(); @@ -233,6 +234,7 @@ this.displayAll, this.enablePreview, this.displayInfo, + this.buildTreeStructureToolStripMenuItem, this.toolStripMenuItem14, this.showExpOpt}); this.optionsToolStripMenuItem.Name = "optionsToolStripMenuItem"; @@ -273,6 +275,17 @@ "t, audio bitrate, etc."; this.displayInfo.CheckedChanged += new System.EventHandler(this.displayAssetInfo_Check); // + // buildTreeStructureToolStripMenuItem + // + this.buildTreeStructureToolStripMenuItem.Checked = true; + this.buildTreeStructureToolStripMenuItem.CheckOnClick = true; + this.buildTreeStructureToolStripMenuItem.CheckState = System.Windows.Forms.CheckState.Checked; + this.buildTreeStructureToolStripMenuItem.Name = "buildTreeStructureToolStripMenuItem"; + this.buildTreeStructureToolStripMenuItem.Size = new System.Drawing.Size(207, 22); + this.buildTreeStructureToolStripMenuItem.Text = "Build tree structure"; + this.buildTreeStructureToolStripMenuItem.ToolTipText = "You can disable tree structure building if you don\'t use the Scene Hierarchy tab"; + this.buildTreeStructureToolStripMenuItem.CheckedChanged += new System.EventHandler(this.buildTreeStructureToolStripMenuItem_CheckedChanged); + // // toolStripMenuItem14 // this.toolStripMenuItem14.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { @@ -1430,6 +1443,7 @@ private System.Windows.Forms.ListView assetListView; private System.Windows.Forms.ToolStripMenuItem showConsoleToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem writeLogToFileToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem buildTreeStructureToolStripMenuItem; } } diff --git a/AssetStudioGUI/AssetStudioGUIForm.cs b/AssetStudioGUI/AssetStudioGUIForm.cs index 7a3a004..4f52ae1 100644 --- a/AssetStudioGUI/AssetStudioGUIForm.cs +++ b/AssetStudioGUI/AssetStudioGUIForm.cs @@ -130,6 +130,7 @@ namespace AssetStudioGUI displayInfo.Checked = Properties.Settings.Default.displayInfo; enablePreview.Checked = Properties.Settings.Default.enablePreview; showConsoleToolStripMenuItem.Checked = Properties.Settings.Default.showConsole; + buildTreeStructureToolStripMenuItem.Checked = Properties.Settings.Default.buildTreeStructure; FMODinit(); listSearchFilterMode.SelectedIndex = 0; @@ -2037,6 +2038,12 @@ namespace AssetStudioGUI Logger.Verbose("Closing AssetStudio"); } + private void buildTreeStructureToolStripMenuItem_CheckedChanged(object sender, EventArgs e) + { + Properties.Settings.Default.buildTreeStructure = buildTreeStructureToolStripMenuItem.Checked; + Properties.Settings.Default.Save(); + } + #region FMOD private void FMODinit() { diff --git a/AssetStudioGUI/Properties/Settings.Designer.cs b/AssetStudioGUI/Properties/Settings.Designer.cs index 60303fb..ba4564d 100644 --- a/AssetStudioGUI/Properties/Settings.Designer.cs +++ b/AssetStudioGUI/Properties/Settings.Designer.cs @@ -334,5 +334,17 @@ namespace AssetStudioGUI.Properties { this["useFileLogger"] = value; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool buildTreeStructure { + get { + return ((bool)(this["buildTreeStructure"])); + } + set { + this["buildTreeStructure"] = value; + } + } } } diff --git a/AssetStudioGUI/Properties/Settings.settings b/AssetStudioGUI/Properties/Settings.settings index eed8233..203d1ad 100644 --- a/AssetStudioGUI/Properties/Settings.settings +++ b/AssetStudioGUI/Properties/Settings.settings @@ -80,5 +80,8 @@ False + + True + \ No newline at end of file diff --git a/AssetStudioGUI/Studio.cs b/AssetStudioGUI/Studio.cs index 02e6ee0..bf98efa 100644 --- a/AssetStudioGUI/Studio.cs +++ b/AssetStudioGUI/Studio.cs @@ -158,7 +158,8 @@ namespace AssetStudioGUI var objectCount = assetsManager.assetsFileList.Sum(x => x.Objects.Count); var objectAssetItemDic = new Dictionary(objectCount); var containers = new List<(PPtr, string)>(); - int i = 0; + allContainers.Clear(); + var i = 0; Progress.Reset(); foreach (var assetsFile in assetsManager.assetsFileList) { @@ -270,8 +271,7 @@ namespace AssetStudioGUI Progress.Report(++i, objectCount); } } - allContainers.Clear(); - foreach ((var pptr, var container) in containers) + foreach (var (pptr, container) in containers) { if (pptr.TryGet(out var obj)) { @@ -287,12 +287,19 @@ namespace AssetStudioGUI visibleAssets = exportableAssets; + if (!Properties.Settings.Default.buildTreeStructure) + { + Logger.Info("Building tree structure step is skipped"); + objectAssetItemDic.Clear(); + return (productName, new List()); + } + Logger.Info("Building tree structure..."); var treeNodeCollection = new List(); var treeNodeDictionary = new Dictionary(); var assetsFileCount = assetsManager.assetsFileList.Count; - int j = 0; + var j = 0; Progress.Reset(); foreach (var assetsFile in assetsManager.assetsFileList) { From 823190abb729cf6d622b03bc458014d064ba1f0e Mon Sep 17 00:00:00 2001 From: VaDiM Date: Sat, 9 Dec 2023 02:26:14 +0300 Subject: [PATCH 15/22] Improve parsing of Live2D Fade motions --- .../CubismMotion3Converter.cs | 7 ++--- .../CubismMotion3Json.cs | 31 +++++++++++++++++-- .../CubismLive2DExtractor/Live2DExtractor.cs | 21 ++++++++++++- 3 files changed, 51 insertions(+), 8 deletions(-) diff --git a/AssetStudioUtility/CubismLive2DExtractor/CubismMotion3Converter.cs b/AssetStudioUtility/CubismLive2DExtractor/CubismMotion3Converter.cs index 34590d7..07b9092 100644 --- a/AssetStudioUtility/CubismLive2DExtractor/CubismMotion3Converter.cs +++ b/AssetStudioUtility/CubismLive2DExtractor/CubismMotion3Converter.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Text; using AssetStudio; @@ -73,7 +72,7 @@ namespace CubismLive2DExtractor if (iAnim.TrackList.Count == 0 || iAnim.Events.Count == 0) { - Logger.Warning($"[Motion Converter] {iAnim.Name} has {iAnim.TrackList.Count} tracks and {iAnim.Events.Count} event!."); + Logger.Warning($"[Motion Converter] \"{iAnim.Name}\" has {iAnim.TrackList.Count} tracks and {iAnim.Events.Count} event!."); } } } @@ -84,7 +83,7 @@ namespace CubismLive2DExtractor GetLive2dPath(binding, out var target, out var boneName); if (string.IsNullOrEmpty(boneName)) { - Logger.Warning($"[Motion Converter] {iAnim.Name} read fail on binding {Array.IndexOf(m_ClipBindingConstant.genericBindings, binding)}"); + Logger.Warning($"[Motion Converter] \"{iAnim.Name}\" read fail on binding {Array.IndexOf(m_ClipBindingConstant.genericBindings, binding)}"); return; } @@ -99,7 +98,7 @@ namespace CubismLive2DExtractor GetLive2dPath(binding, out var target, out var boneName); if (string.IsNullOrEmpty(boneName)) { - Logger.Warning($"[Motion Converter] {iAnim.Name} read fail on binding {Array.IndexOf(m_ClipBindingConstant.genericBindings, binding)}"); + Logger.Warning($"[Motion Converter] \"{iAnim.Name}\" read fail on binding {Array.IndexOf(m_ClipBindingConstant.genericBindings, binding)}"); return; } diff --git a/AssetStudioUtility/CubismLive2DExtractor/CubismMotion3Json.cs b/AssetStudioUtility/CubismLive2DExtractor/CubismMotion3Json.cs index 2d0abec..a8eab60 100644 --- a/AssetStudioUtility/CubismLive2DExtractor/CubismMotion3Json.cs +++ b/AssetStudioUtility/CubismLive2DExtractor/CubismMotion3Json.cs @@ -97,7 +97,7 @@ namespace CubismLive2DExtractor totalSegmentCount++; } - public CubismMotion3Json(CubismFadeMotion fadeMotion, bool forceBezier) + public CubismMotion3Json(CubismFadeMotion fadeMotion, HashSet paramNames, HashSet partNames, bool forceBezier) { Version = 3; Meta = new SerializableMeta @@ -130,12 +130,37 @@ namespace CubismLive2DExtractor if (fadeMotion.ParameterCurves[i].m_Curve.Length == 0) continue; + string target; + string paramId = fadeMotion.ParameterIds[i]; + switch (paramId) + { + case "Opacity": + case "EyeBlink": + case "LipSync": + target = "Model"; + break; + default: + if (paramNames.Contains(paramId)) + { + target = "Parameter"; + } + else if (partNames.Contains(paramId)) + { + target = "PartOpacity"; + } + else + { + target = paramId.ToLower().Contains("part") ? "PartOpacity" : "Parameter"; + AssetStudio.Logger.Warning($"[{fadeMotion.m_Name}] Binding error: Unable to find \"{paramId}\" among the model parts/parameters"); + } + break; + } Curves[actualCurveCount] = new SerializableCurve { // Target type. - Target = "Parameter", + Target = target, // Identifier for mapping curve to target. - Id = fadeMotion.ParameterIds[i], + Id = paramId, // [Optional] Time of the Fade - In for easing in seconds. FadeInTime = fadeMotion.ParameterFadeInTimes[i], // [Optional] Time of the Fade - Out for easing in seconds. diff --git a/AssetStudioUtility/CubismLive2DExtractor/Live2DExtractor.cs b/AssetStudioUtility/CubismLive2DExtractor/Live2DExtractor.cs index 19de546..1a374ce 100644 --- a/AssetStudioUtility/CubismLive2DExtractor/Live2DExtractor.cs +++ b/AssetStudioUtility/CubismLive2DExtractor/Live2DExtractor.cs @@ -34,6 +34,8 @@ namespace CubismLive2DExtractor var textures = new SortedSet(); var eyeBlinkParameters = new HashSet(); var lipSyncParameters = new HashSet(); + var parameterNames = new HashSet(); + var partNames = new HashSet(); MonoBehaviour physics = null; foreach (var asset in assets) @@ -69,6 +71,18 @@ namespace CubismLive2DExtractor lipSyncParameters.Add(mouthGameObject.m_Name); } break; + case "CubismParameter": + if (m_MonoBehaviour.m_GameObject.TryGet(out var paramGameObject)) + { + parameterNames.Add(paramGameObject.m_Name); + } + break; + case "CubismPart": + if (m_MonoBehaviour.m_GameObject.TryGet(out var partGameObject)) + { + partNames.Add(partGameObject.m_Name); + } + break; } } break; @@ -116,6 +130,7 @@ namespace CubismLive2DExtractor if (motionMode == Live2DMotionMode.MonoBehaviour && fadeMotionList.Count > 0) //motion from MonoBehaviour { + Logger.Debug("Motion export method: MonoBehaviour (Fade motion)"); Directory.CreateDirectory(destMotionPath); foreach (var fadeMotionMono in fadeMotionList) { @@ -134,7 +149,7 @@ namespace CubismLive2DExtractor if (fadeMotion.ParameterIds.Length == 0) continue; - var motionJson = new CubismMotion3Json(fadeMotion, forceBezier); + var motionJson = new CubismMotion3Json(fadeMotion, parameterNames, partNames, forceBezier); var animName = Path.GetFileNameWithoutExtension(fadeMotion.m_Name); if (motions.ContainsKey(animName)) @@ -151,6 +166,10 @@ namespace CubismLive2DExtractor } else if (gameObjects.Count > 0) //motion from AnimationClip { + var exportMethod = motionMode == Live2DMotionMode.AnimationClip + ? "AnimationClip" + : "AnimationClip (no Fade motions found)"; + Logger.Debug($"Motion export method: {exportMethod}"); var rootTransform = gameObjects[0].m_Transform; while (rootTransform.m_Father.TryGet(out var m_Father)) { From 0fdbddea55d743bec45c8f08fa6f1acc3ad8f074 Mon Sep 17 00:00:00 2001 From: VaDiM Date: Mon, 11 Dec 2023 01:21:10 +0300 Subject: [PATCH 16/22] [CLI] Fix asset filter for sprites --- AssetStudio/AssetsManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/AssetStudio/AssetsManager.cs b/AssetStudio/AssetsManager.cs index a07c521..2a6590c 100644 --- a/AssetStudio/AssetsManager.cs +++ b/AssetStudio/AssetsManager.cs @@ -40,6 +40,7 @@ namespace AssetStudio if (classIDTypes.Contains(ClassIDType.Sprite)) { filteredAssetTypesList.Add(ClassIDType.Texture2D); + filteredAssetTypesList.Add(ClassIDType.SpriteAtlas); } filteredAssetTypesList.UnionWith(classIDTypes); From 87347e8b602deac0d49747852ef1a15576c0886c Mon Sep 17 00:00:00 2001 From: VaDiM Date: Mon, 11 Dec 2023 01:58:05 +0300 Subject: [PATCH 17/22] [CLI] Update readme --- AssetStudioCLI/ReadMe.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/AssetStudioCLI/ReadMe.md b/AssetStudioCLI/ReadMe.md index 24489a6..0222c22 100644 --- a/AssetStudioCLI/ReadMe.md +++ b/AssetStudioCLI/ReadMe.md @@ -10,6 +10,7 @@ AssetStudioModCLI [-m, --mode ] [-o, --output ] [-h, --help] [--log-level ] [--log-output ] [--image-format ] [--audio-format ] + [--l2d-motion-mode ] [--l2d-force-bezier] [--fbx-scale-factor ] [--fbx-bone-size ] [--filter-by-name ] [--filter-by-container ] [--filter-by-pathid ] [--filter-by-text ] @@ -45,7 +46,7 @@ General Options: Example: "-g container" -o, --output Specify path to the output folder - If path isn't specifyed, 'ASExport' folder will be created in the program's work folder + If path isn't specified, 'ASExport' folder will be created in the program's work folder -h, --help Display help and exit @@ -69,6 +70,17 @@ Convert Options: None - Do not convert audios and export them in their own format Example: "--audio-format wav" +Live2D Options: + --l2d-motion-mode Specify Live2D motion export mode + + MonoBehaviour - Try to export motions from MonoBehaviour Fade motions + If no Fade motions are found, the AnimationClip method will be used + AnimationClip - Try to export motions using AnimationClip assets + Example: "--l2d-motion-mode animationClip" + + --l2d-force-bezier (Flag) If specified, Linear motion segments will be calculated as Bezier segments + (May help if the exported motions look jerky/not smooth enough) + FBX Options: --fbx-scale-factor Specify the FBX Scale Factor Date: Fri, 15 Dec 2023 01:21:31 +0300 Subject: [PATCH 18/22] Update Live2DExtractor.cs --- AssetStudioUtility/CubismLive2DExtractor/Live2DExtractor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AssetStudioUtility/CubismLive2DExtractor/Live2DExtractor.cs b/AssetStudioUtility/CubismLive2DExtractor/Live2DExtractor.cs index 1a374ce..1f22cd6 100644 --- a/AssetStudioUtility/CubismLive2DExtractor/Live2DExtractor.cs +++ b/AssetStudioUtility/CubismLive2DExtractor/Live2DExtractor.cs @@ -198,7 +198,7 @@ namespace CubismLive2DExtractor File.WriteAllText($"{destMotionPath}{animName}.motion3.json", JsonConvert.SerializeObject(motionJson, Formatting.Indented, new MyJsonConverter())); } } - else + if (motions.Count == 0) { Logger.Warning($"No motions found for \"{modelName}\" model."); } From 5c2ac1a5e8d8f39b47346d267d0561a1fd4f4d26 Mon Sep 17 00:00:00 2001 From: VaDiM Date: Fri, 15 Dec 2023 14:37:08 +0300 Subject: [PATCH 19/22] Add .NET8 to the TargetFrameworks --- AssetStudio.PInvoke/AssetStudio.PInvoke.csproj | 2 +- AssetStudio/AssetStudio.csproj | 2 +- AssetStudioCLI/AssetStudioCLI.csproj | 2 +- AssetStudioFBXWrapper/AssetStudioFBXWrapper.csproj | 2 +- AssetStudioGUI/AssetStudioGUI.csproj | 2 +- AssetStudioUtility/AssetStudioUtility.csproj | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/AssetStudio.PInvoke/AssetStudio.PInvoke.csproj b/AssetStudio.PInvoke/AssetStudio.PInvoke.csproj index b44eede..a508311 100644 --- a/AssetStudio.PInvoke/AssetStudio.PInvoke.csproj +++ b/AssetStudio.PInvoke/AssetStudio.PInvoke.csproj @@ -1,7 +1,7 @@ - net472;net6.0;net6.0-windows;net7.0;net7.0-windows + net472;net6.0;net6.0-windows;net7.0;net7.0-windows;net8.0;net8.0-windows true 0.17.3.0 Copyright © Perfare 2020-2022; Copyright © hozuki 2020 diff --git a/AssetStudio/AssetStudio.csproj b/AssetStudio/AssetStudio.csproj index 58873a1..20864d1 100644 --- a/AssetStudio/AssetStudio.csproj +++ b/AssetStudio/AssetStudio.csproj @@ -1,7 +1,7 @@ - net472;net6.0;net6.0-windows;net7.0;net7.0-windows + net472;net6.0;net6.0-windows;net7.0;net7.0-windows;net8.0;net8.0-windows 0.17.3.0 Copyright © Perfare 2018-2022; Copyright © aelurum 2023 embedded diff --git a/AssetStudioCLI/AssetStudioCLI.csproj b/AssetStudioCLI/AssetStudioCLI.csproj index e5e2053..1da7f14 100644 --- a/AssetStudioCLI/AssetStudioCLI.csproj +++ b/AssetStudioCLI/AssetStudioCLI.csproj @@ -2,7 +2,7 @@ Exe - net472;net6.0;net7.0 + net472;net6.0;net7.0;net8.0 AssetStudioMod by aelurum AssetStudioModCLI 0.17.3.0 diff --git a/AssetStudioFBXWrapper/AssetStudioFBXWrapper.csproj b/AssetStudioFBXWrapper/AssetStudioFBXWrapper.csproj index f3945e1..c1d1c02 100644 --- a/AssetStudioFBXWrapper/AssetStudioFBXWrapper.csproj +++ b/AssetStudioFBXWrapper/AssetStudioFBXWrapper.csproj @@ -1,7 +1,7 @@ - net472;net6.0;net6.0-windows;net7.0;net7.0-windows + net472;net6.0;net6.0-windows;net7.0;net7.0-windows;net8.0;net8.0-windows true 0.17.3.0 Copyright © Perfare 2018-2022; Copyright © hozuki 2020 diff --git a/AssetStudioGUI/AssetStudioGUI.csproj b/AssetStudioGUI/AssetStudioGUI.csproj index 3627732..7261b35 100644 --- a/AssetStudioGUI/AssetStudioGUI.csproj +++ b/AssetStudioGUI/AssetStudioGUI.csproj @@ -2,7 +2,7 @@ WinExe - net472;net6.0-windows;net7.0-windows + net472;net6.0-windows;net7.0-windows;net8.0-windows true Resources\as.ico AssetStudioMod by aelurum diff --git a/AssetStudioUtility/AssetStudioUtility.csproj b/AssetStudioUtility/AssetStudioUtility.csproj index 1b435b7..04e58a6 100644 --- a/AssetStudioUtility/AssetStudioUtility.csproj +++ b/AssetStudioUtility/AssetStudioUtility.csproj @@ -1,7 +1,7 @@ - net472;net6.0;net6.0-windows;net7.0;net7.0-windows + net472;net6.0;net6.0-windows;net7.0;net7.0-windows;net8.0;net8.0-windows 0.17.3.0 Copyright © Perfare 2018-2022; Copyright © aelurum 2023 embedded From 4747687bec1a1f664f0bb751a37bc2c0778fae1c Mon Sep 17 00:00:00 2001 From: VaDiM Date: Fri, 15 Dec 2023 15:11:40 +0300 Subject: [PATCH 20/22] Update README.md --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fb17033..81a0e1c 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Unfortunately, I can't continue Perfare's work and keep AssetStudio up to date. ## AssetStudio Features - Support version: - - 3.4 - 2022.1 + - 3.4 - 2022.3 - Support asset types: - **Texture2D** : convert to png, tga, jpeg, bmp, webp - **Sprite** : crop Texture2D to png, tga, jpeg, bmp, webp @@ -36,7 +36,7 @@ Unfortunately, I can't continue Perfare's work and keep AssetStudio up to date. - `Animator` and `AnimationClip` assets are not supported in the CLI version - Support of sprites with alpha mask - Support of image export in WebP format -- Support of Live2D Cubism 3 model export +- Support of Live2D Cubism model export - Ported from my fork of Perfare's [UnityLive2DExtractor](https://github.com/aelurum/UnityLive2DExtractor) - Using the Live2D export in AssetStudio allows you to specify a Unity version and assembly folder if needed - Detecting bundles with UnityCN encryption @@ -54,6 +54,9 @@ Unfortunately, I can't continue Perfare's work and keep AssetStudio up to date. - AssetStudioMod.net7 - GUI/CLI (Windows) - [.NET Desktop Runtime 7.0](https://dotnet.microsoft.com/download/dotnet/7.0) - CLI (Linux/Mac) - [.NET Runtime 7.0](https://dotnet.microsoft.com/download/dotnet/7.0) +- AssetStudioMod.net8 + - GUI/CLI (Windows) - [.NET Desktop Runtime 8.0](https://dotnet.microsoft.com/download/dotnet/8.0) + - CLI (Linux/Mac) - [.NET Runtime 8.0](https://dotnet.microsoft.com/download/dotnet/8.0) ## CLI Usage @@ -97,7 +100,7 @@ AssetStudioModCLI -m dump -o ``` AssetStudioModCLI -m live2d ``` -> When running in live2d mode you can only specify `-o`, `--log-level`, `--log-output`, `--export-asset-list`, `--unity-version` and `--assembly-folder` options. +> When running in live2d mode you can only specify `-o`, `--log-level`, `--log-output`, `--l2d-motion-mode`, `--l2d-force-bezier`, `--export-asset-list`, `--unity-version` and `--assembly-folder` options. Any other options will be ignored. - Export all FBX objects (similar to "Export all objects (split)" option in the GUI) ``` From 53337578436dab48143b2d4339fc7eb1e276c0a0 Mon Sep 17 00:00:00 2001 From: VaDiM Date: Sat, 16 Dec 2023 04:16:27 +0300 Subject: [PATCH 21/22] Update dependencies --- AssetStudioGUI/AssetStudioGUI.csproj | 8 ++++---- AssetStudioUtility/AssetStudioUtility.csproj | 2 +- AssetStudioUtility/SpriteHelper.cs | 17 ++++++++++++----- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/AssetStudioGUI/AssetStudioGUI.csproj b/AssetStudioGUI/AssetStudioGUI.csproj index 7261b35..8ac7294 100644 --- a/AssetStudioGUI/AssetStudioGUI.csproj +++ b/AssetStudioGUI/AssetStudioGUI.csproj @@ -54,14 +54,14 @@ - - + + - - + + Libraries\OpenTK.WinForms.dll diff --git a/AssetStudioUtility/AssetStudioUtility.csproj b/AssetStudioUtility/AssetStudioUtility.csproj index 04e58a6..02d818a 100644 --- a/AssetStudioUtility/AssetStudioUtility.csproj +++ b/AssetStudioUtility/AssetStudioUtility.csproj @@ -22,7 +22,7 @@ 0.17.0 - + diff --git a/AssetStudioUtility/SpriteHelper.cs b/AssetStudioUtility/SpriteHelper.cs index 382bd08..f4cdcbd 100644 --- a/AssetStudioUtility/SpriteHelper.cs +++ b/AssetStudioUtility/SpriteHelper.cs @@ -154,9 +154,16 @@ namespace AssetStudio if (triangles.Length < 1024) { var rectP = new RectangularPolygon(0, 0, rect.Width, rect.Height); - spriteImage.Mutate(x => x.Fill(options, SixLabors.ImageSharp.Color.Red, rectP.Clip(path))); - spriteImage.Mutate(x => x.Flip(FlipMode.Vertical)); - return spriteImage; + try + { + spriteImage.Mutate(x => x.Fill(options, SixLabors.ImageSharp.Color.Red, rectP.Clip(path))); + spriteImage.Mutate(x => x.Flip(FlipMode.Vertical)); + return spriteImage; + } + catch (ArgumentOutOfRangeException) + { + // ignored + } } using (var mask = new Image(rect.Width, rect.Height, SixLabors.ImageSharp.Color.Black)) { @@ -167,9 +174,9 @@ namespace AssetStudio return spriteImage; } } - catch + catch (Exception e) { - // ignored + Logger.Warning($"{m_Sprite.m_Name} Unable to render the packed sprite correctly.\n{e}"); } } From a00c857ac3b3f18a7ab23f4def4dea3a09bc817b Mon Sep 17 00:00:00 2001 From: VaDiM Date: Sat, 16 Dec 2023 04:16:47 +0300 Subject: [PATCH 22/22] Update version to v0.17.4 --- AssetStudio.PInvoke/AssetStudio.PInvoke.csproj | 2 +- AssetStudio/AssetStudio.csproj | 2 +- AssetStudioCLI/AssetStudioCLI.csproj | 2 +- AssetStudioFBXWrapper/AssetStudioFBXWrapper.csproj | 2 +- AssetStudioGUI/AssetStudioGUI.csproj | 2 +- AssetStudioUtility/AssetStudioUtility.csproj | 2 +- CHANGELOG.md | 12 ++++++++++++ .../Texture2DDecoderWrapper.csproj | 2 +- 8 files changed, 19 insertions(+), 7 deletions(-) diff --git a/AssetStudio.PInvoke/AssetStudio.PInvoke.csproj b/AssetStudio.PInvoke/AssetStudio.PInvoke.csproj index a508311..e7d2a99 100644 --- a/AssetStudio.PInvoke/AssetStudio.PInvoke.csproj +++ b/AssetStudio.PInvoke/AssetStudio.PInvoke.csproj @@ -3,7 +3,7 @@ net472;net6.0;net6.0-windows;net7.0;net7.0-windows;net8.0;net8.0-windows true - 0.17.3.0 + 0.17.4.0 Copyright © Perfare 2020-2022; Copyright © hozuki 2020 embedded diff --git a/AssetStudio/AssetStudio.csproj b/AssetStudio/AssetStudio.csproj index 20864d1..d1bd35e 100644 --- a/AssetStudio/AssetStudio.csproj +++ b/AssetStudio/AssetStudio.csproj @@ -2,7 +2,7 @@ net472;net6.0;net6.0-windows;net7.0;net7.0-windows;net8.0;net8.0-windows - 0.17.3.0 + 0.17.4.0 Copyright © Perfare 2018-2022; Copyright © aelurum 2023 embedded diff --git a/AssetStudioCLI/AssetStudioCLI.csproj b/AssetStudioCLI/AssetStudioCLI.csproj index 1da7f14..022a076 100644 --- a/AssetStudioCLI/AssetStudioCLI.csproj +++ b/AssetStudioCLI/AssetStudioCLI.csproj @@ -5,7 +5,7 @@ net472;net6.0;net7.0;net8.0 AssetStudioMod by aelurum AssetStudioModCLI - 0.17.3.0 + 0.17.4.0 Copyright © Perfare; Copyright © aelurum 2023 AnyCPU embedded diff --git a/AssetStudioFBXWrapper/AssetStudioFBXWrapper.csproj b/AssetStudioFBXWrapper/AssetStudioFBXWrapper.csproj index c1d1c02..31cd6bc 100644 --- a/AssetStudioFBXWrapper/AssetStudioFBXWrapper.csproj +++ b/AssetStudioFBXWrapper/AssetStudioFBXWrapper.csproj @@ -3,7 +3,7 @@ net472;net6.0;net6.0-windows;net7.0;net7.0-windows;net8.0;net8.0-windows true - 0.17.3.0 + 0.17.4.0 Copyright © Perfare 2018-2022; Copyright © hozuki 2020 embedded diff --git a/AssetStudioGUI/AssetStudioGUI.csproj b/AssetStudioGUI/AssetStudioGUI.csproj index 8ac7294..fff416e 100644 --- a/AssetStudioGUI/AssetStudioGUI.csproj +++ b/AssetStudioGUI/AssetStudioGUI.csproj @@ -7,7 +7,7 @@ Resources\as.ico AssetStudioMod by aelurum AssetStudioModGUI - 0.17.3.0 + 0.17.4.0 Copyright © Perfare 2018-2022; Copyright © aelurum 2021-2023 embedded diff --git a/AssetStudioUtility/AssetStudioUtility.csproj b/AssetStudioUtility/AssetStudioUtility.csproj index 02d818a..b43150f 100644 --- a/AssetStudioUtility/AssetStudioUtility.csproj +++ b/AssetStudioUtility/AssetStudioUtility.csproj @@ -2,7 +2,7 @@ net472;net6.0;net6.0-windows;net7.0;net7.0-windows;net8.0;net8.0-windows - 0.17.3.0 + 0.17.4.0 Copyright © Perfare 2018-2022; Copyright © aelurum 2023 embedded diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b31626..214ffc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## v0.17.4.0 [16-12-2023] +- Added support for Live2D Fade motions + - [GUI] Added related settings to the Export Options window + - [CLI] Added options section with related settings +- Added support for separate PreloadData (https://github.com/Perfare/AssetStudio/issues/690) +- [GUI] Added support for .lnk files via Drag&Drop (https://github.com/aelurum/AssetStudio/issues/13) +- [GUI] Added support for Drag&Drop files from a browser +- [GUI] Added console logger + - Added option to save log to a file +- [GUI] Added option to not build a tree structure +- Made some other minor fixes and improvements + ## v0.17.3.0 [13-09-2023] - [CLI] Added support for exporting split objects (fbx) (https://github.com/aelurum/AssetStudio/pull/10) - [CLI] Fixed display of asset names in the exported asset list in some working modes diff --git a/Texture2DDecoderWrapper/Texture2DDecoderWrapper.csproj b/Texture2DDecoderWrapper/Texture2DDecoderWrapper.csproj index de342c0..5b66a6e 100644 --- a/Texture2DDecoderWrapper/Texture2DDecoderWrapper.csproj +++ b/Texture2DDecoderWrapper/Texture2DDecoderWrapper.csproj @@ -3,7 +3,7 @@ net472 true - 0.17.3.0 + 0.17.4.0 Copyright © Perfare 2020-2022; Copyright © hozuki 2020 embedded