diff --git a/AssetStudio.PInvoke/AssetStudio.PInvoke.csproj b/AssetStudio.PInvoke/AssetStudio.PInvoke.csproj index 9d66c2e..b272e29 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 1.0.1 Copyright © Perfare 2020-2022; Copyright © hozuki 2020 diff --git a/AssetStudio/AssetStudio.csproj b/AssetStudio/AssetStudio.csproj index 2cb6b8a..aa96ad3 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 1.0.1 Copyright © Perfare 2018-2022; Copyright © aelurum 2023 embedded diff --git a/AssetStudio/AssetsManager.cs b/AssetStudio/AssetsManager.cs index 954d139..105ff30 100644 --- a/AssetStudio/AssetsManager.cs +++ b/AssetStudio/AssetsManager.cs @@ -42,6 +42,7 @@ namespace AssetStudio filteredAssetTypesList.UnionWith(new HashSet { ClassIDType.Texture2D, + ClassIDType.SpriteAtlas, ClassIDType.MonoBehaviour, ClassIDType.MonoScript }); @@ -87,7 +88,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); @@ -138,7 +139,7 @@ namespace AssetStudio private void LoadFile(FileReader reader) { - switch (reader.FileType) + switch (reader?.FileType) { case FileType.AssetsFile: LoadAssetsFile(reader); @@ -537,6 +538,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; @@ -640,14 +644,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."); + } } } } 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 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/AssetStudio/Classes/Sprite.cs b/AssetStudio/Classes/Sprite.cs index 28f7803..406f071 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(); } } 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/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/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; } } diff --git a/AssetStudioCLI/AssetStudioCLI.csproj b/AssetStudioCLI/AssetStudioCLI.csproj index 291219a..80173e3 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 ArknightsStudio by aelurum ArknightsStudioCLI 1.0.1 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/Components/Arknights/AkSpriteHelper.cs b/AssetStudioCLI/Components/Arknights/AkSpriteHelper.cs index b0d6e2b..fb42e3d 100644 --- a/AssetStudioCLI/Components/Arknights/AkSpriteHelper.cs +++ b/AssetStudioCLI/Components/Arknights/AkSpriteHelper.cs @@ -56,14 +56,14 @@ namespace Arknights { var faceImage = m_Texture2D.ConvertToImage(true); var faceAlpha = avgSprite.FaceSpriteAlphaTexture.ConvertToImage(true); - if (faceImage.Size() != avgSprite.FaceSize) + if (new Size(faceImage.Width, faceImage.Height) != avgSprite.FaceSize) { faceImage.Mutate(x => x.Resize(new ResizeOptions { Size = avgSprite.FaceSize, Sampler = KnownResamplers.Lanczos3, Mode = ResizeMode.Stretch })); faceAlpha.Mutate(x => x.Resize(new ResizeOptions { Size = avgSprite.FaceSize, Sampler = KnownResamplers.Lanczos3, Mode = ResizeMode.Stretch })); } tex = avgSprite.FullTexture.ConvertToImage(true); - tex.Mutate(x => x.DrawImage(faceImage, location: avgSprite.FacePos, opacity: 1f)); - alphaTex.Mutate(x => x.DrawImage(faceAlpha, location: avgSprite.FacePos, opacity: 1f)); + tex.Mutate(x => x.DrawImage(faceImage, avgSprite.FacePos, opacity: 1f)); + alphaTex.Mutate(x => x.DrawImage(faceAlpha, avgSprite.FacePos, opacity: 1f)); } else { @@ -87,11 +87,11 @@ namespace Arknights var faceImage = m_Texture2D.ConvertToImage(true); var tex = avgSprite.FullTexture.ConvertToImage(true); - if (faceImage.Size() != avgSprite.FaceSize) + if (new Size(faceImage.Width, faceImage.Height) != avgSprite.FaceSize) { faceImage.Mutate(x => x.Resize(new ResizeOptions { Size = avgSprite.FaceSize, Sampler = KnownResamplers.Lanczos3, Mode = ResizeMode.Stretch })); } - tex.Mutate(x => x.DrawImage(faceImage, location: avgSprite.FacePos, opacity: 1f)); + tex.Mutate(x => x.DrawImage(faceImage, avgSprite.FacePos, opacity: 1f)); return tex; } @@ -201,7 +201,7 @@ namespace Arknights { if (downscaleMultiplier > 0f && downscaleMultiplier != 1f) { - var newSize = (Size)(originalImage.Size() / downscaleMultiplier); + var newSize = (Size)(new Size(originalImage.Width, originalImage.Height) / downscaleMultiplier); originalImage.Mutate(x => x.Resize(newSize, KnownResamplers.Lanczos3, compand: true)); } var rectX = (int)Math.Floor(textureRect.x); diff --git a/AssetStudioCLI/Exporter.cs b/AssetStudioCLI/Exporter.cs index b55d327..4958c0b 100644 --- a/AssetStudioCLI/Exporter.cs +++ b/AssetStudioCLI/Exporter.cs @@ -409,7 +409,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 0f8dab7..e38798c 100644 --- a/AssetStudioCLI/Options/CLIOptions.cs +++ b/AssetStudioCLI/Options/CLIOptions.cs @@ -14,6 +14,7 @@ namespace AssetStudioCLI.Options General, Convert, Logger, + Live2D, FBX, Filter, Arknights, @@ -93,6 +94,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; @@ -222,7 +226,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 @@ -279,6 +283,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 ( @@ -340,7 +368,7 @@ namespace AssetStudioCLI.Options ); #endregion - #region Arknights Options + #region Init Arknights Options akResizedOnly = true; o_akSpriteAlphaMode = new GroupedOption ( @@ -385,7 +413,7 @@ namespace AssetStudioCLI.Options ( optionDefaultValue: false, optionName: "--original-avg-names", - optionDescription: "(Flag) If specified, names of avg character sprites will not be fixed\n", + optionDescription: "(Flag) If specified, names of avg character sprites will not be restored\n", optionHelpGroup: HelpGroups.Arknights, isFlag: true ); @@ -447,8 +475,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() == "-?")) { @@ -489,6 +517,7 @@ namespace AssetStudioCLI.Options } }; + #region Parse "Working Mode" Option var workModeOptionIndex = resplittedArgs.FindIndex(x => x.ToLower() == "-m" || x.ToLower() == "--mode"); if (workModeOptionIndex >= 0) { @@ -515,6 +544,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() @@ -546,6 +576,7 @@ namespace AssetStudioCLI.Options } resplittedArgs.RemoveRange(workModeOptionIndex, 2); } + #endregion #region Parse Flags for (int i = 0; i < resplittedArgs.Count; i++) @@ -581,6 +612,16 @@ namespace AssetStudioCLI.Options f_akAddAliases.Value = true; resplittedArgs.RemoveAt(i); 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 @@ -782,6 +823,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); @@ -949,7 +1012,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; } @@ -986,13 +1049,13 @@ 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())); - Console.WriteLine($"Did you mean [{ $"{rndOption.Key}".Color(CLIAnsiColors.BrightYellow) }] {arg}?"); + var rndOption = optionDesc.ElementAt(rand.Next(0, optionDesc.Length)); + 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; @@ -1034,7 +1097,7 @@ namespace AssetStudioCLI.Options else { var arch = Environment.Is64BitProcess ? "x64" : "x32"; - Console.WriteLine($"# {appAssembly.Name} [{arch}]\n# v{appAssembly.Version}\n# Based on AssetStudioMod v0.17.3\n"); + Console.WriteLine($"# {appAssembly.Name} [{arch}]\n# v{appAssembly.Version}\n# Based on AssetStudioMod v0.17.4\n"); Console.WriteLine($"{usage}\n\n{helpMessage}"); } } @@ -1105,7 +1168,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) { @@ -1134,7 +1197,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/ReadMe.md b/AssetStudioCLI/ReadMe.md index 4924620..493aa6d 100644 --- a/AssetStudioCLI/ReadMe.md +++ b/AssetStudioCLI/ReadMe.md @@ -10,6 +10,7 @@ ArknightsStudioCLI [-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 ] @@ -48,7 +49,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 @@ -72,6 +73,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 0 - Make the shadow lighter Example: "--shadow-gamma 0" - --original-avg-names (Flag) If specified, names of avg character sprites will not be fixed + --original-avg-names (Flag) If specified, names of avg character sprites will not be restored --add-aliases (Flag) If specified, aliases will be added to avg character sprite names (if exist) diff --git a/AssetStudioCLI/Studio.cs b/AssetStudioCLI/Studio.cs index ecd6c8f..bae976b 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 { @@ -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; @@ -153,11 +163,11 @@ namespace AssetStudioCLI } foreach (var asset in loadedAssetsList) { - if (containers.ContainsKey(asset.Asset)) + if (containers.TryGetValue(asset.Asset, out var container)) { - asset.Container = containers[asset.Asset]; + asset.Container = container; - if (asset.Type == ClassIDType.MonoBehaviour && asset.Container.Contains("/arts/charportraits/portraits")) + if (asset.Type == ClassIDType.MonoBehaviour && container.Contains("/arts/charportraits/portraits")) { var portraitsList = Arknights.AkSpriteHelper.GeneratePortraits(asset); foreach (var portrait in portraitsList) @@ -618,6 +628,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..."); @@ -631,6 +643,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); @@ -638,7 +651,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) { @@ -646,9 +670,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 @@ -656,31 +687,31 @@ 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); 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) { - 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/AssetStudioFBXWrapper/AssetStudioFBXWrapper.csproj b/AssetStudioFBXWrapper/AssetStudioFBXWrapper.csproj index b7c9262..96d122f 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 1.0.1 Copyright © Perfare 2018-2022; Copyright © hozuki 2020 diff --git a/AssetStudioGUI/AboutForm.cs b/AssetStudioGUI/AboutForm.cs index d3467e5..3f8473e 100644 --- a/AssetStudioGUI/AboutForm.cs +++ b/AssetStudioGUI/AboutForm.cs @@ -10,14 +10,16 @@ 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; - basedOnLabel.Text = "AssetStudioMod v0.17.3"; + modVersionLabel.Text = productVer; + basedOnLabel.Text = "AssetStudioMod v0.17.4"; licenseRichTextBox.Text = GetLicenseText(); } diff --git a/AssetStudioGUI/AssetStudioGUI.csproj b/AssetStudioGUI/AssetStudioGUI.csproj index 4158ba4..182d1c4 100644 --- a/AssetStudioGUI/AssetStudioGUI.csproj +++ b/AssetStudioGUI/AssetStudioGUI.csproj @@ -54,14 +54,14 @@ - - + + - - + + Libraries\OpenTK.WinForms.dll diff --git a/AssetStudioGUI/AssetStudioGUIForm.Designer.cs b/AssetStudioGUI/AssetStudioGUIForm.Designer.cs index 46f952b..ea402a9 100644 --- a/AssetStudioGUI/AssetStudioGUIForm.Designer.cs +++ b/AssetStudioGUI/AssetStudioGUIForm.Designer.cs @@ -46,6 +46,7 @@ this.akFixFaceSpriteNamesToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.akUseExternalAlphaToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.akSeparator2 = new System.Windows.Forms.ToolStripSeparator(); + 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(); @@ -82,6 +83,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(); @@ -241,6 +244,7 @@ this.akFixFaceSpriteNamesToolStripMenuItem, this.akUseExternalAlphaToolStripMenuItem, this.akSeparator2, + this.buildTreeStructureToolStripMenuItem, this.toolStripMenuItem14, this.showExpOpt}); this.optionsToolStripMenuItem.Name = "optionsToolStripMenuItem"; @@ -251,7 +255,7 @@ // this.displayAll.CheckOnClick = true; this.displayAll.Name = "displayAll"; - this.displayAll.Size = new System.Drawing.Size(252, 22); + this.displayAll.Size = new System.Drawing.Size(276, 22); this.displayAll.Text = "Display all assets"; this.displayAll.ToolTipText = "Check this option will display all types assets. Not extractable assets can expor" + "t the RAW file."; @@ -263,7 +267,7 @@ this.enablePreview.CheckOnClick = true; this.enablePreview.CheckState = System.Windows.Forms.CheckState.Checked; this.enablePreview.Name = "enablePreview"; - this.enablePreview.Size = new System.Drawing.Size(252, 22); + this.enablePreview.Size = new System.Drawing.Size(276, 22); this.enablePreview.Text = "Enable preview"; this.enablePreview.ToolTipText = "Toggle the loading and preview of readable assets, such as images, sounds, text, " + "etc.\r\nDisable preview if you have performance or compatibility issues."; @@ -275,7 +279,7 @@ this.displayInfo.CheckOnClick = true; this.displayInfo.CheckState = System.Windows.Forms.CheckState.Checked; this.displayInfo.Name = "displayInfo"; - this.displayInfo.Size = new System.Drawing.Size(252, 22); + this.displayInfo.Size = new System.Drawing.Size(276, 22); this.displayInfo.Text = "Display asset information"; this.displayInfo.ToolTipText = "Toggle the overlay that shows information about each asset, eg. image size, forma" + "t, audio bitrate, etc."; @@ -284,7 +288,7 @@ // akSeparator1 // this.akSeparator1.Name = "akSeparator1"; - this.akSeparator1.Size = new System.Drawing.Size(249, 6); + this.akSeparator1.Size = new System.Drawing.Size(273, 6); // // akTitleMenuItem // @@ -292,7 +296,7 @@ this.akTitleMenuItem.Enabled = false; this.akTitleMenuItem.Name = "akTitleMenuItem"; this.akTitleMenuItem.ShowShortcutKeys = false; - this.akTitleMenuItem.Size = new System.Drawing.Size(252, 22); + this.akTitleMenuItem.Size = new System.Drawing.Size(276, 22); this.akTitleMenuItem.Text = "Arknights"; // // akFixFaceSpriteNamesToolStripMenuItem @@ -301,8 +305,8 @@ this.akFixFaceSpriteNamesToolStripMenuItem.CheckOnClick = true; this.akFixFaceSpriteNamesToolStripMenuItem.CheckState = System.Windows.Forms.CheckState.Checked; this.akFixFaceSpriteNamesToolStripMenuItem.Name = "akFixFaceSpriteNamesToolStripMenuItem"; - this.akFixFaceSpriteNamesToolStripMenuItem.Size = new System.Drawing.Size(252, 22); - this.akFixFaceSpriteNamesToolStripMenuItem.Text = "Fix names of avg character sprites"; + this.akFixFaceSpriteNamesToolStripMenuItem.Size = new System.Drawing.Size(276, 22); + this.akFixFaceSpriteNamesToolStripMenuItem.Text = "Restore names of avg character sprites"; this.akFixFaceSpriteNamesToolStripMenuItem.ToolTipText = "Rename face sprites with numeric names to correct ones"; this.akFixFaceSpriteNamesToolStripMenuItem.CheckedChanged += new System.EventHandler(this.akFixFaceSpriteNamesToolStripMenuItem_Check); // @@ -312,7 +316,7 @@ this.akUseExternalAlphaToolStripMenuItem.CheckOnClick = true; this.akUseExternalAlphaToolStripMenuItem.CheckState = System.Windows.Forms.CheckState.Checked; this.akUseExternalAlphaToolStripMenuItem.Name = "akUseExternalAlphaToolStripMenuItem"; - this.akUseExternalAlphaToolStripMenuItem.Size = new System.Drawing.Size(265, 22); + this.akUseExternalAlphaToolStripMenuItem.Size = new System.Drawing.Size(276, 22); this.akUseExternalAlphaToolStripMenuItem.Text = "Use external alpha texture for sprites"; this.akUseExternalAlphaToolStripMenuItem.ToolTipText = "Trying to find an external alpha texture for preview/export sprite assets (Skins," + " Char arts, Avg char arts, etc.)"; @@ -321,14 +325,25 @@ // akSeparator2 // this.akSeparator2.Name = "akSeparator2"; - this.akSeparator2.Size = new System.Drawing.Size(249, 6); + this.akSeparator2.Size = new System.Drawing.Size(273, 6); + // + // 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(276, 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[] { this.specifyUnityVersion}); this.toolStripMenuItem14.Name = "toolStripMenuItem14"; - this.toolStripMenuItem14.Size = new System.Drawing.Size(252, 22); + this.toolStripMenuItem14.Size = new System.Drawing.Size(276, 22); this.toolStripMenuItem14.Text = "Specify Unity version"; // // specifyUnityVersion @@ -342,7 +357,7 @@ // showExpOpt // this.showExpOpt.Name = "showExpOpt"; - this.showExpOpt.Size = new System.Drawing.Size(252, 22); + this.showExpOpt.Size = new System.Drawing.Size(276, 22); this.showExpOpt.Text = "Export options"; this.showExpOpt.Click += new System.EventHandler(this.showExpOpt_Click); // @@ -588,6 +603,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); @@ -601,6 +618,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"; @@ -1308,6 +1343,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); @@ -1462,6 +1498,9 @@ private System.Windows.Forms.ToolStripSeparator akSeparator1; private System.Windows.Forms.ToolStripMenuItem akTitleMenuItem; private System.Windows.Forms.ToolStripSeparator akSeparator2; + 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 5da45fe..5b90381 100644 --- a/AssetStudioGUI/AssetStudioGUIForm.cs +++ b/AssetStudioGUI/AssetStudioGUIForm.cs @@ -116,23 +116,34 @@ 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"); + ConsoleWindow.RunConsole(Properties.Settings.Default.showConsole); 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); + delayTimer.Elapsed += delayTimer_Elapsed; displayAll.Checked = Properties.Settings.Default.displayAll; displayInfo.Checked = Properties.Settings.Default.displayInfo; enablePreview.Checked = Properties.Settings.Default.enablePreview; akFixFaceSpriteNamesToolStripMenuItem.Checked = Properties.Settings.Default.fixFaceSpriteNames; akUseExternalAlphaToolStripMenuItem.Checked = Properties.Settings.Default.useExternalAlpha; + showConsoleToolStripMenuItem.Checked = Properties.Settings.Default.showConsole; + buildTreeStructureToolStripMenuItem.Checked = Properties.Settings.Default.buildTreeStructure; 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; } @@ -141,21 +152,32 @@ namespace AssetStudioGUI { if (e.Data.GetDataPresent(DataFormats.FileDrop)) { - e.Effect = DragDropEffects.Move; + e.Effect = DragDropEffects.Copy; } } 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) @@ -224,17 +246,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 = $"{Application.ProductName} v{Application.ProductVersion} - {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} - {productName} - {assetsManager.assetsFileList[0].unityVersion} - {assetsManager.assetsFileList[0].m_TargetPlatform}"; assetListView.VirtualListSize = visibleAssets.Count; @@ -1423,7 +1439,7 @@ namespace AssetStudioGUI private void ResetForm() { - Text = $"{Application.ProductName} v{Application.ProductVersion}"; + Text = guiTitle; assetsManager.Clear(); assemblyLoader.Clear(); exportableAssets.Clear(); @@ -2127,6 +2143,38 @@ 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"); + } + + 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/Components/Arknights/AkSpriteHelper.cs b/AssetStudioGUI/Components/Arknights/AkSpriteHelper.cs index caebfd7..c429072 100644 --- a/AssetStudioGUI/Components/Arknights/AkSpriteHelper.cs +++ b/AssetStudioGUI/Components/Arknights/AkSpriteHelper.cs @@ -57,14 +57,14 @@ namespace Arknights { var faceImage = m_Texture2D.ConvertToImage(true); var faceAlpha = avgSprite.FaceSpriteAlphaTexture.ConvertToImage(true); - if (faceImage.Size() != avgSprite.FaceSize) + if (new Size(faceImage.Width, faceImage.Height) != avgSprite.FaceSize) { faceImage.Mutate(x => x.Resize(new ResizeOptions { Size = avgSprite.FaceSize, Sampler = KnownResamplers.Lanczos3, Mode = ResizeMode.Stretch })); faceAlpha.Mutate(x => x.Resize(new ResizeOptions { Size = avgSprite.FaceSize, Sampler = KnownResamplers.Lanczos3, Mode = ResizeMode.Stretch })); } tex = avgSprite.FullTexture.ConvertToImage(true); - tex.Mutate(x => x.DrawImage(faceImage, location: avgSprite.FacePos, opacity: 1f)); - alphaTex.Mutate(x => x.DrawImage(faceAlpha, location: avgSprite.FacePos, opacity: 1f)); + tex.Mutate(x => x.DrawImage(faceImage, avgSprite.FacePos, opacity: 1f)); + alphaTex.Mutate(x => x.DrawImage(faceAlpha, avgSprite.FacePos, opacity: 1f)); } else { @@ -91,11 +91,11 @@ namespace Arknights var faceImage = m_Texture2D.ConvertToImage(true); var tex = avgSprite.FullTexture.ConvertToImage(true); - if (faceImage.Size() != avgSprite.FaceSize) + if (new Size(faceImage.Width, faceImage.Height) != avgSprite.FaceSize) { faceImage.Mutate(x => x.Resize(new ResizeOptions {Size = avgSprite.FaceSize, Sampler = KnownResamplers.Lanczos3, Mode = ResizeMode.Stretch})); } - tex.Mutate(x => x.DrawImage(faceImage, location: avgSprite.FacePos, opacity: 1f)); + tex.Mutate(x => x.DrawImage(faceImage, avgSprite.FacePos, opacity: 1f)); return tex; } @@ -264,7 +264,7 @@ namespace Arknights { if (downscaleMultiplier > 0f && downscaleMultiplier != 1f) { - var newSize = (Size)(originalImage.Size() / downscaleMultiplier); + var newSize = (Size)(new Size(originalImage.Width, originalImage.Height) / downscaleMultiplier); originalImage.Mutate(x => x.Resize(newSize, KnownResamplers.Lanczos3, compand: true)); } var rectX = (int)Math.Floor(textureRect.x); 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/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); + } + } +} diff --git a/AssetStudioGUI/ExportOptions.Designer.cs b/AssetStudioGUI/ExportOptions.Designer.cs index 3bb6da3..0514680 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,7 +69,6 @@ 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.akResamplerLabel = new System.Windows.Forms.Label(); this.akResamplerComboBox = new System.Windows.Forms.ComboBox(); this.akSpritesAlphaGroupBox = new System.Windows.Forms.GroupBox(); @@ -75,8 +80,11 @@ this.akAlphaMaskGammaTrackBar = new System.Windows.Forms.TrackBar(); this.akSpritesExportGroupBox = new System.Windows.Forms.GroupBox(); this.akAddAliasesCheckBox = new System.Windows.Forms.CheckBox(); + 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(); @@ -120,8 +128,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"; // @@ -133,7 +141,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; // @@ -145,7 +153,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; // @@ -157,9 +165,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 @@ -175,7 +183,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 // @@ -183,7 +191,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 @@ -194,7 +202,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; // @@ -216,8 +224,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; // @@ -227,7 +234,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; // @@ -237,7 +244,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; // @@ -248,7 +255,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; @@ -259,7 +266,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; // @@ -271,10 +278,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; @@ -298,7 +371,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"; // @@ -309,9 +382,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; // @@ -323,7 +396,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; // @@ -335,7 +408,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; // @@ -350,7 +423,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, @@ -364,7 +437,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 @@ -377,7 +450,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 // @@ -385,7 +458,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 @@ -402,7 +475,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 // @@ -410,7 +483,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 @@ -442,7 +515,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; // @@ -452,7 +525,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 @@ -466,7 +539,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, @@ -479,7 +552,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; // @@ -503,7 +576,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; // @@ -513,9 +586,9 @@ this.akResamplerLabel.Location = new System.Drawing.Point(6, 21); this.akResamplerLabel.Name = "akResamplerLabel"; this.akResamplerLabel.Size = new System.Drawing.Size(120, 13); - this.akResamplerLabel.TabIndex = 5; + this.akResamplerLabel.TabIndex = 1; this.akResamplerLabel.Text = "Alpha texture resampler:"; - this.exportUvsTooltip.SetToolTip(this.akResamplerLabel, "Only affects exported images"); + this.optionTooltip.SetToolTip(this.akResamplerLabel, "Only affects exported images"); // // akResamplerComboBox // @@ -531,8 +604,8 @@ this.akResamplerComboBox.Location = new System.Drawing.Point(132, 18); this.akResamplerComboBox.Name = "akResamplerComboBox"; this.akResamplerComboBox.Size = new System.Drawing.Size(162, 21); - this.akResamplerComboBox.TabIndex = 4; - this.exportUvsTooltip.SetToolTip(this.akResamplerComboBox, "Only affects exported images"); + this.akResamplerComboBox.TabIndex = 2; + this.optionTooltip.SetToolTip(this.akResamplerComboBox, "Only affects exported images"); // // akSpritesAlphaGroupBox // @@ -547,7 +620,7 @@ this.akSpritesAlphaGroupBox.Location = new System.Drawing.Point(537, 13); this.akSpritesAlphaGroupBox.Name = "akSpritesAlphaGroupBox"; this.akSpritesAlphaGroupBox.Size = new System.Drawing.Size(300, 178); - this.akSpritesAlphaGroupBox.TabIndex = 12; + this.akSpritesAlphaGroupBox.TabIndex = 4; this.akSpritesAlphaGroupBox.TabStop = false; this.akSpritesAlphaGroupBox.Text = "Sprites: Alpha Texture [Arknights]"; // @@ -558,7 +631,7 @@ this.akGammaNoteLabel.Location = new System.Drawing.Point(6, 138); this.akGammaNoteLabel.Name = "akGammaNoteLabel"; this.akGammaNoteLabel.Size = new System.Drawing.Size(230, 13); - this.akGammaNoteLabel.TabIndex = 7; + this.akGammaNoteLabel.TabIndex = 8; this.akGammaNoteLabel.Text = "* Gamma settings also affect the preview image"; // // akResamplerDescLabel @@ -568,7 +641,7 @@ this.akResamplerDescLabel.Location = new System.Drawing.Point(6, 43); this.akResamplerDescLabel.Name = "akResamplerDescLabel"; this.akResamplerDescLabel.Size = new System.Drawing.Size(251, 13); - this.akResamplerDescLabel.TabIndex = 6; + this.akResamplerDescLabel.TabIndex = 3; this.akResamplerDescLabel.Text = "Alpha texture upscale method for 2048x2048 sprites"; // // akResizedOnlyCheckBox @@ -579,7 +652,7 @@ this.akResizedOnlyCheckBox.Location = new System.Drawing.Point(172, 85); this.akResizedOnlyCheckBox.Name = "akResizedOnlyCheckBox"; this.akResizedOnlyCheckBox.Size = new System.Drawing.Size(122, 17); - this.akResizedOnlyCheckBox.TabIndex = 3; + this.akResizedOnlyCheckBox.TabIndex = 6; this.akResizedOnlyCheckBox.Text = "Apply to resized only"; this.akResizedOnlyCheckBox.UseVisualStyleBackColor = true; // @@ -589,7 +662,7 @@ this.akGammaValueLabel.Location = new System.Drawing.Point(111, 86); this.akGammaValueLabel.Name = "akGammaValueLabel"; this.akGammaValueLabel.Size = new System.Drawing.Size(41, 13); - this.akGammaValueLabel.TabIndex = 2; + this.akGammaValueLabel.TabIndex = 5; this.akGammaValueLabel.Text = "Default"; // // akGammaLabel @@ -598,7 +671,7 @@ this.akGammaLabel.Location = new System.Drawing.Point(6, 86); this.akGammaLabel.Name = "akGammaLabel"; this.akGammaLabel.Size = new System.Drawing.Size(86, 13); - this.akGammaLabel.TabIndex = 1; + this.akGammaLabel.TabIndex = 4; this.akGammaLabel.Text = "Shadow gamma:"; // // akAlphaMaskGammaTrackBar @@ -609,7 +682,7 @@ this.akAlphaMaskGammaTrackBar.Minimum = -5; this.akAlphaMaskGammaTrackBar.Name = "akAlphaMaskGammaTrackBar"; this.akAlphaMaskGammaTrackBar.Size = new System.Drawing.Size(288, 45); - this.akAlphaMaskGammaTrackBar.TabIndex = 0; + this.akAlphaMaskGammaTrackBar.TabIndex = 7; this.akAlphaMaskGammaTrackBar.Scroll += new System.EventHandler(this.akAlphaMaskGammaTrackBar_Scroll); // // akSpritesExportGroupBox @@ -618,7 +691,7 @@ this.akSpritesExportGroupBox.Location = new System.Drawing.Point(537, 197); this.akSpritesExportGroupBox.Name = "akSpritesExportGroupBox"; this.akSpritesExportGroupBox.Size = new System.Drawing.Size(300, 178); - this.akSpritesExportGroupBox.TabIndex = 13; + this.akSpritesExportGroupBox.TabIndex = 5; this.akSpritesExportGroupBox.TabStop = false; this.akSpritesExportGroupBox.Text = "Sprites: Export [Arknights]"; // @@ -628,7 +701,7 @@ this.akAddAliasesCheckBox.Location = new System.Drawing.Point(6, 28); this.akAddAliasesCheckBox.Name = "akAddAliasesCheckBox"; this.akAddAliasesCheckBox.Size = new System.Drawing.Size(261, 17); - this.akAddAliasesCheckBox.TabIndex = 0; + this.akAddAliasesCheckBox.TabIndex = 1; this.akAddAliasesCheckBox.Text = "Add aliases to avg character sprite names (if exist)"; this.akAddAliasesCheckBox.UseVisualStyleBackColor = true; // @@ -639,6 +712,7 @@ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.CancelButton = this.Cancel; this.ClientSize = new System.Drawing.Size(849, 416); + this.Controls.Add(this.l2dGroupBox); this.Controls.Add(this.akSpritesExportGroupBox); this.Controls.Add(this.akSpritesAlphaGroupBox); this.Controls.Add(this.groupBox2); @@ -657,6 +731,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(); @@ -705,7 +783,7 @@ 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 akSpritesAlphaGroupBox; @@ -719,5 +797,11 @@ private System.Windows.Forms.GroupBox akSpritesExportGroupBox; private System.Windows.Forms.CheckBox akAddAliasesCheckBox; private System.Windows.Forms.Label akGammaNoteLabel; + 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 4b7717b..90ca130 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; @@ -44,6 +38,9 @@ namespace AssetStudioGUI akResizedOnlyCheckBox.Checked = Properties.Settings.Default.resizedOnly; akAddAliasesCheckBox.Checked = Properties.Settings.Default.addAliases; + 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) @@ -53,14 +50,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; @@ -81,6 +72,9 @@ namespace AssetStudioGUI Properties.Settings.Default.resizedOnly = akResizedOnlyCheckBox.Checked; Properties.Settings.Default.addAliases = akAddAliasesCheckBox.Checked; + 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/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 d83b923..b77e956 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.8.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); @@ -358,5 +358,65 @@ namespace AssetStudioGUI.Properties { this["alphaMaskGamma"] = 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; + } + } + + [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; + } + } + + [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 0746d55..665ff00 100644 --- a/AssetStudioGUI/Properties/Settings.settings +++ b/AssetStudioGUI/Properties/Settings.settings @@ -86,5 +86,20 @@ 2 + + MonoBehaviour + + + False + + + True + + + False + + + True + \ No newline at end of file diff --git a/AssetStudioGUI/Studio.cs b/AssetStudioGUI/Studio.cs index 95621c6..0bfcd6f 100644 --- a/AssetStudioGUI/Studio.cs +++ b/AssetStudioGUI/Studio.cs @@ -158,10 +158,13 @@ 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) { + var preloadTable = Array.Empty>(); + foreach (var asset in assetsFile.Objects) { var assetItem = new AssetItem(asset); @@ -170,6 +173,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 +193,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 +232,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) @@ -259,7 +271,7 @@ namespace AssetStudioGUI Progress.Report(++i, objectCount); } } - foreach ((var pptr, var container) in containers) + foreach (var (pptr, container) in containers) { if (pptr.TryGet(out var obj)) { @@ -285,12 +297,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) { @@ -358,7 +377,6 @@ namespace AssetStudioGUI Progress.Report(++j, assetsFileCount); } treeNodeDictionary.Clear(); - objectAssetItemDic.Clear(); return (productName, treeNodeCollection); @@ -396,7 +414,6 @@ namespace AssetStudioGUI typeMap.Add(assetsFile.unityVersion, items); } } - return typeMap; } @@ -755,6 +772,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 => { @@ -763,48 +782,73 @@ 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); 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) { - 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/AssetStudioUtility.csproj b/AssetStudioUtility/AssetStudioUtility.csproj index d428bcb..c60c6c8 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 1.0.1 Copyright © Perfare 2018-2022; Copyright © aelurum 2023 embedded @@ -22,7 +22,7 @@ 0.17.0 - + 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/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 c5cd623..a8eab60 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,238 @@ 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, HashSet paramNames, HashSet partNames, 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; + + 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 = target, + // Identifier for mapping curve to target. + 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. + 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 c312fb6..1f22cd6 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; @@ -26,20 +26,75 @@ namespace CubismLive2DExtractor Directory.CreateDirectory(destPath); Directory.CreateDirectory(destTexturePath); - var monoBehaviours = new List(); - var texture2Ds = new List(); + var expressionList = new List(); + var fadeMotionList = new List(); var gameObjects = new List(); var animationClips = new List(); + 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) { 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 "CubismFadeMotionData": + fadeMotionList.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; + 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; 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 +105,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,36 +125,51 @@ 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(); - if (gameObjects.Count > 0) + 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) + { + 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, parameterNames, partNames, 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 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)) { @@ -114,114 +181,37 @@ 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; + var motionJson = new CubismMotion3Json(animation, forceBezier); - json.UserData = new CubismMotion3Json.SerializableUserData[animation.Events.Count]; - var totalUserDataSize = 0; - for (var i = 0; i < animation.Events.Count; i++) + var animName = animation.Name; + if (motions.ContainsKey(animName)) { - var @event = animation.Events[i]; - json.UserData[i] = new CubismMotion3Json.SerializableUserData - { - Time = @event.time, - Value = @event.value - }; - totalUserDataSize += @event.value.Length; + animName = $"{animName}_{animation.GetHashCode()}"; + + if (motions.ContainsKey(animName)) + continue; } - 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 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())); } } + if (motions.Count == 0) + { + Logger.Warning($"No motions found for \"{modelName}\" model."); + } //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 +228,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 +267,7 @@ namespace CubismLive2DExtractor Ids = lipSyncParameters.ToArray() }); + //model var model3 = new CubismModel3Json { Version = 3, 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 + } +} diff --git a/AssetStudioUtility/SpriteHelper.cs b/AssetStudioUtility/SpriteHelper.cs index df3f108..adff64e 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}"); } } diff --git a/README.md b/README.md index 9c3a0b6..40d469f 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,9 @@ - ArknightsStudio-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) +- ArknightsStudio-net8 + - GUI/CLI (Windows) - [.NET Desktop Runtime 7.0](https://dotnet.microsoft.com/download/dotnet/8.0) + - CLI (Linux/Mac) - [.NET Runtime 7.0](https://dotnet.microsoft.com/download/dotnet/8.0) ## CLI Usage