diff --git a/.gitignore b/.gitignore index 4ce6fdde..bef36869 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,9 @@ bld/ # Visual Studio 2017 auto generated files Generated\ Files/ +# Launch Settings +*launchSettings.json + # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* diff --git a/AssetStudio.PInvoke/AssetStudio.PInvoke.csproj b/AssetStudio.PInvoke/AssetStudio.PInvoke.csproj index ebd2a49b..c7bc9c2a 100644 --- a/AssetStudio.PInvoke/AssetStudio.PInvoke.csproj +++ b/AssetStudio.PInvoke/AssetStudio.PInvoke.csproj @@ -3,7 +3,7 @@ net472;net6.0;net7.0 true - 0.16.47.1 + 0.16.48.1 Copyright © Perfare 2020-2022; Copyright © hozuki 2020 embedded diff --git a/AssetStudio.PInvoke/DllLoader.cs b/AssetStudio.PInvoke/DllLoader.cs index b4e85ea9..347ea0a9 100644 --- a/AssetStudio.PInvoke/DllLoader.cs +++ b/AssetStudio.PInvoke/DllLoader.cs @@ -4,33 +4,29 @@ using System.IO; using System.Runtime.InteropServices; +#if NETFRAMEWORK namespace AssetStudio.PInvoke { public static class DllLoader { - public static void PreloadDll(string dllName) { - var dllDir = GetDirectedDllDirectory(); + var localPath = Process.GetCurrentProcess().MainModule.FileName; + var localDir = Path.GetDirectoryName(localPath); // Not using OperatingSystem.Platform. // See: https://www.mono-project.com/docs/faq/technical/#how-to-detect-the-execution-platform if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - Win32.LoadDll(dllDir, dllName); - } - else - { - Posix.LoadDll(dllDir, dllName); + Win32.LoadDll(GetDirectedDllDirectory(localDir), dllName); } } - private static string GetDirectedDllDirectory() + private static string GetDirectedDllDirectory(string localDir) { - var localPath = Process.GetCurrentProcess().MainModule.FileName; - var localDir = Path.GetDirectoryName(localPath); - - var subDir = Environment.Is64BitProcess ? "x64" : "x86"; + var win32Path = Path.Combine("runtimes", "win-x86", "native"); + var win64Path = Path.Combine("runtimes", "win-x64", "native"); + var subDir = Environment.Is64BitProcess ? win64Path : win32Path; var directedDllDir = Path.Combine(localDir, subDir); @@ -64,61 +60,7 @@ internal static void LoadDll(string dllDir, string dllName) private const uint LOAD_LIBRARY_SEARCH_DEFAULT_DIRS = 0x1000; private const uint LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR = 0x100; - - } - - private static class Posix - { - - internal static void LoadDll(string dllDir, string dllName) - { - string dllExtension; - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - dllExtension = ".so"; - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - dllExtension = ".dylib"; - } - else - { - throw new NotSupportedException(); - } - - var dllFileName = $"lib{dllName}{dllExtension}"; - var directedDllPath = Path.Combine(dllDir, dllFileName); - - const int ldFlags = RTLD_NOW | RTLD_GLOBAL; - var hLibrary = DlOpen(directedDllPath, ldFlags); - - if (hLibrary == IntPtr.Zero) - { - var pErrStr = DlError(); - // `PtrToStringAnsi` always uses the specific constructor of `String` (see dotnet/core#2325), - // which in turn interprets the byte sequence with system default codepage. On OSX and Linux - // the codepage is UTF-8 so the error message should be handled correctly. - var errorMessage = Marshal.PtrToStringAnsi(pErrStr); - - throw new DllNotFoundException(errorMessage); - } - } - - // OSX and most Linux OS use LP64 so `int` is still 32-bit even on 64-bit platforms. - // void *dlopen(const char *filename, int flag); - [DllImport("libdl", EntryPoint = "dlopen")] - private static extern IntPtr DlOpen([MarshalAs(UnmanagedType.LPStr)] string fileName, int flags); - - // char *dlerror(void); - [DllImport("libdl", EntryPoint = "dlerror")] - private static extern IntPtr DlError(); - - private const int RTLD_LAZY = 0x1; - private const int RTLD_NOW = 0x2; - private const int RTLD_GLOBAL = 0x100; - } - } } +#endif diff --git a/AssetStudio.sln b/AssetStudio.sln index ade7a3a8..bed47e09 100644 --- a/AssetStudio.sln +++ b/AssetStudio.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.31410.357 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.33414.496 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AssetStudio", "AssetStudio\AssetStudio.csproj", "{422FEC21-EF60-4F29-AA56-95DFDA23C913}" EndProject @@ -25,6 +25,12 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AssetStudioFBXNative", "Ass EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Texture2DDecoderNative", "Texture2DDecoderNative\Texture2DDecoderNative.vcxproj", "{29356642-C46E-4144-83D8-22DC09D0D7FD}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AssetStudioCLI", "AssetStudioCLI\AssetStudioCLI.csproj", "{34B6329B-0E73-45AC-B8CC-015F119F63DC}" + ProjectSection(ProjectDependencies) = postProject + {422FEC21-EF60-4F29-AA56-95DFDA23C913} = {422FEC21-EF60-4F29-AA56-95DFDA23C913} + {65EAFFA3-01D3-4EF5-B092-8B4647E9A1FF} = {65EAFFA3-01D3-4EF5-B092-8B4647E9A1FF} + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -131,6 +137,18 @@ Global {29356642-C46E-4144-83D8-22DC09D0D7FD}.Release|x64.Build.0 = Release|x64 {29356642-C46E-4144-83D8-22DC09D0D7FD}.Release|x86.ActiveCfg = Release|Win32 {29356642-C46E-4144-83D8-22DC09D0D7FD}.Release|x86.Build.0 = Release|Win32 + {34B6329B-0E73-45AC-B8CC-015F119F63DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {34B6329B-0E73-45AC-B8CC-015F119F63DC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {34B6329B-0E73-45AC-B8CC-015F119F63DC}.Debug|x64.ActiveCfg = Debug|Any CPU + {34B6329B-0E73-45AC-B8CC-015F119F63DC}.Debug|x64.Build.0 = Debug|Any CPU + {34B6329B-0E73-45AC-B8CC-015F119F63DC}.Debug|x86.ActiveCfg = Debug|Any CPU + {34B6329B-0E73-45AC-B8CC-015F119F63DC}.Debug|x86.Build.0 = Debug|Any CPU + {34B6329B-0E73-45AC-B8CC-015F119F63DC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {34B6329B-0E73-45AC-B8CC-015F119F63DC}.Release|Any CPU.Build.0 = Release|Any CPU + {34B6329B-0E73-45AC-B8CC-015F119F63DC}.Release|x64.ActiveCfg = Release|Any CPU + {34B6329B-0E73-45AC-B8CC-015F119F63DC}.Release|x64.Build.0 = Release|Any CPU + {34B6329B-0E73-45AC-B8CC-015F119F63DC}.Release|x86.ActiveCfg = Release|Any CPU + {34B6329B-0E73-45AC-B8CC-015F119F63DC}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/AssetStudio/AssetStudio.csproj b/AssetStudio/AssetStudio.csproj index d7c1a2c7..603bebb7 100644 --- a/AssetStudio/AssetStudio.csproj +++ b/AssetStudio/AssetStudio.csproj @@ -2,13 +2,13 @@ net472;net6.0;net7.0 - 0.16.47.1 + 0.16.48.1 Copyright © Perfare 2018-2022 embedded - + diff --git a/AssetStudioCLI/AssetStudioCLI.csproj b/AssetStudioCLI/AssetStudioCLI.csproj new file mode 100644 index 00000000..0c56cc76 --- /dev/null +++ b/AssetStudioCLI/AssetStudioCLI.csproj @@ -0,0 +1,99 @@ + + + + Exe + net472;net6.0;net7.0 + AssetStudio Mod by VaDiM + 0.16.48.1 + Copyright © Perfare; Copyright © aelurum 2023 + AnyCPU + embedded + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AssetStudioCLI/CLILogger.cs b/AssetStudioCLI/CLILogger.cs new file mode 100644 index 00000000..90346f35 --- /dev/null +++ b/AssetStudioCLI/CLILogger.cs @@ -0,0 +1,117 @@ +using AssetStudio; +using System; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using AssetStudioCLI.Options; + +namespace AssetStudioCLI +{ + internal enum LogOutputMode + { + Console, + File, + Both, + } + + internal class CLILogger : ILogger + { + private readonly LogOutputMode logOutput; + private readonly LoggerEvent logMinLevel; + public string LogName; + public string LogPath; + + public CLILogger(CLIOptions options) + { + logOutput = options.o_logOutput.Value; + logMinLevel = options.o_logLevel.Value; + LogName = $"AssetStudioCLI_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.log"; + LogPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, LogName); + + var ver = typeof(Program).Assembly.GetName().Version; + LogToFile(LoggerEvent.Verbose, $"---AssetStudioCLI v{ver} | Logger launched---\n" + + $"CMD Args: {string.Join(" ", options.cliArgs)}"); + } + + private static string ColorLogLevel(LoggerEvent logLevel) + { + string formattedLevel = $"[{logLevel}]"; + switch (logLevel) + { + case LoggerEvent.Info: + return $"{formattedLevel.Color(CLIAnsiColors.BrightCyan)}"; + case LoggerEvent.Warning: + return $"{formattedLevel.Color(CLIAnsiColors.BrightYellow)}"; + case LoggerEvent.Error: + return $"{formattedLevel.Color(CLIAnsiColors.BrightRed)}"; + default: + return formattedLevel; + } + } + + private static string FormatMessage(LoggerEvent logMsgLevel, string message, bool consoleMode = false) + { + var curTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); + message = message.TrimEnd(); + var multiLine = message.Contains('\n'); + + string formattedMessage; + if (consoleMode) + { + string colorLogLevel = ColorLogLevel(logMsgLevel); + formattedMessage = $"{colorLogLevel} {message}"; + if (multiLine) + { + formattedMessage = formattedMessage.Replace("\n", $"\n{colorLogLevel} "); + } + } + else + { + 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; + } + + public void LogToConsole(LoggerEvent logMsgLevel, string message) + { + if (logOutput != LogOutputMode.File) + { + Console.WriteLine(FormatMessage(logMsgLevel, message, consoleMode: true)); + } + } + + public async void LogToFile(LoggerEvent logMsgLevel, string message) + { + if (logOutput != LogOutputMode.Console) + { + using (var sw = new StreamWriter(LogPath, append: true, System.Text.Encoding.UTF8)) + { + await sw.WriteLineAsync(FormatMessage(logMsgLevel, message)); + } + } + } + + public void Log(LoggerEvent logMsgLevel, string message) + { + if (logMsgLevel < logMinLevel || string.IsNullOrEmpty(message)) + { + return; + } + + if (logOutput != LogOutputMode.File) + { + LogToConsole(logMsgLevel, message); + } + if (logOutput != LogOutputMode.Console) + { + LogToFile(logMsgLevel, message); + } + } + } +} diff --git a/AssetStudioCLI/Components/AssetItem.cs b/AssetStudioCLI/Components/AssetItem.cs new file mode 100644 index 00000000..00f929a4 --- /dev/null +++ b/AssetStudioCLI/Components/AssetItem.cs @@ -0,0 +1,27 @@ +using AssetStudio; + +namespace AssetStudioCLI +{ + internal class AssetItem + { + public Object Asset; + public SerializedFile SourceFile; + public string Container = string.Empty; + public string TypeString; + public long m_PathID; + public long FullSize; + public ClassIDType Type; + public string Text; + public string UniqueID; + + public AssetItem(Object asset) + { + Asset = asset; + SourceFile = asset.assetsFile; + Type = asset.type; + TypeString = Type.ToString(); + m_PathID = asset.m_PathID; + FullSize = asset.byteSize; + } + } +} diff --git a/AssetStudioCLI/Components/CLIAnsiColors.cs b/AssetStudioCLI/Components/CLIAnsiColors.cs new file mode 100644 index 00000000..b830f67a --- /dev/null +++ b/AssetStudioCLI/Components/CLIAnsiColors.cs @@ -0,0 +1,49 @@ +using System; + +namespace AssetStudioCLI +{ + // 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 readonly string + Black = "\u001b[30m", + Red = "\u001b[31m", + Green = "\u001b[32m", + Yellow = "\u001b[33m", //remapped to ~BrightWhite in Windows PowerShell 6 + Blue = "\u001b[34m", + Magenta = "\u001b[35m", //remapped to ~Blue in Windows PowerShell 6 + Cyan = "\u001b[36m", + White = "\u001b[37m", + BrightBlack = "\u001b[30;1m", + BrightRed = "\u001b[31;1m", + BrightGreen = "\u001b[32;1m", + BrightYellow = "\u001b[33;1m", + BrightBlue = "\u001b[34;1m", + BrightMagenta = "\u001b[35;1m", + BrightCyan = "\u001b[36;1m", + BrightWhite = "\u001b[37;1m"; + private static readonly string Reset = "\u001b[0m"; + + public static string Color(this string str, string ansiColor) + { + if (!CLIWinAnsiFix.isAnsiSupported) + { + return str; + } + + return $"{ansiColor}{str}{Reset}"; + } + + public static void ANSICodesTest() + { + Console.WriteLine("ANSI escape codes test"); + Console.WriteLine($"Supported: {CLIWinAnsiFix.isAnsiSupported}"); + 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"); + Console.WriteLine("\u001b[34;1m E \u001b[35;1m F \u001b[36;1m G \u001b[37;1m H \u001b[0m"); + } + } + +} diff --git a/AssetStudioCLI/Components/CLIWinAnsiFix.cs b/AssetStudioCLI/Components/CLIWinAnsiFix.cs new file mode 100644 index 00000000..946c4619 --- /dev/null +++ b/AssetStudioCLI/Components/CLIWinAnsiFix.cs @@ -0,0 +1,62 @@ +// Based on code by tomzorz (https://gist.github.com/tomzorz/6142d69852f831fb5393654c90a1f22e) +using System; +using System.Runtime.InteropServices; + +namespace AssetStudioCLI +{ + static class CLIWinAnsiFix + { + public static readonly bool isAnsiSupported; + private const int STD_OUTPUT_HANDLE = -11; + private const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004; + + [DllImport("kernel32.dll")] + private static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode); + + [DllImport("kernel32.dll")] + private static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode); + + [DllImport("kernel32.dll")] + private static extern IntPtr GetStdHandle(int nStdHandle); + + static CLIWinAnsiFix() + { + bool isWin = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + if (isWin) + { + isAnsiSupported = TryEnableVTMode(); + if (!isAnsiSupported) + { + //Check for bash terminal emulator. E.g., Git Bash, Cmder + isAnsiSupported = Environment.GetEnvironmentVariable("TERM") != null; + } + } + else + { + isAnsiSupported = true; + } + } + + // Enable support for ANSI escape codes + // (but probably only suitable for windows 10+) + // https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences + private static bool TryEnableVTMode() + { + var iStdOut = GetStdHandle(STD_OUTPUT_HANDLE); + + if (!GetConsoleMode(iStdOut, out uint outConsoleMode)) + { + return false; + } + + outConsoleMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; + + if (!SetConsoleMode(iStdOut, outConsoleMode)) + { + return false; + } + + return true; + } + } +} diff --git a/AssetStudioCLI/Exporter.cs b/AssetStudioCLI/Exporter.cs new file mode 100644 index 00000000..1606aca7 --- /dev/null +++ b/AssetStudioCLI/Exporter.cs @@ -0,0 +1,289 @@ +using AssetStudio; +using AssetStudioCLI.Options; +using Newtonsoft.Json; +using System.IO; +using System.Linq; + +namespace AssetStudioCLI +{ + internal static class Exporter + { + public static AssemblyLoader assemblyLoader = new AssemblyLoader(); + + public static bool ExportTexture2D(AssetItem item, string exportPath, CLIOptions options) + { + var m_Texture2D = (Texture2D)item.Asset; + if (options.convertTexture) + { + var type = options.o_imageFormat.Value; + if (!TryExportFile(exportPath, item, "." + type.ToString().ToLower(), out var exportFullPath)) + return false; + var image = m_Texture2D.ConvertToImage(flip: true); + if (image == null) + { + Logger.Error($"Failed to convert texture \"{m_Texture2D.m_Name}\" into image"); + return false; + } + using (image) + { + using (var file = File.OpenWrite(exportFullPath)) + { + image.WriteToStream(file, type); + } + Logger.Debug($"{item.TypeString}: \"{item.Text}\" exported to \"{exportFullPath}\""); + return true; + } + } + else + { + if (!TryExportFile(exportPath, item, ".tex", out var exportFullPath)) + return false; + File.WriteAllBytes(exportFullPath, m_Texture2D.image_data.GetData()); + Logger.Debug($"{item.TypeString}: \"{item.Text}\" exported to \"{exportFullPath}\""); + return true; + } + } + + public static bool ExportAudioClip(AssetItem item, string exportPath, CLIOptions options) + { + string exportFullPath; + var m_AudioClip = (AudioClip)item.Asset; + var m_AudioData = m_AudioClip.m_AudioData.GetData(); + if (m_AudioData == null || m_AudioData.Length == 0) + { + Logger.Error($"[{item.Text}]: AudioData was not found"); + return false; + } + var converter = new AudioClipConverter(m_AudioClip); + if (options.o_audioFormat.Value != AudioFormat.None && converter.IsSupport) + { + if (!TryExportFile(exportPath, item, ".wav", out exportFullPath)) + return false; + + Logger.Debug($"Converting \"{m_AudioClip.m_Name}\" to wav..\n" + + $"AudioClip sound compression: {m_AudioClip.m_CompressionFormat}\n" + + $"AudioClip channel count: {m_AudioClip.m_Channels}\n" + + $"AudioClip sample rate: {m_AudioClip.m_Frequency}\n" + + $"AudioClip bit depth: {m_AudioClip.m_BitsPerSample}"); + + var buffer = converter.ConvertToWav(m_AudioData); + if (buffer == null) + { + Logger.Error($"[{item.Text}]: Failed to convert to Wav"); + return false; + } + File.WriteAllBytes(exportFullPath, buffer); + } + else + { + if (!TryExportFile(exportPath, item, converter.GetExtensionName(), out exportFullPath)) + return false; + File.WriteAllBytes(exportFullPath, m_AudioData); + } + + Logger.Debug($"{item.TypeString}: \"{item.Text}\" exported to \"{exportFullPath}\""); + return true; + } + + public static bool ExportVideoClip(AssetItem item, string exportPath) + { + var m_VideoClip = (VideoClip)item.Asset; + if (m_VideoClip.m_ExternalResources.m_Size > 0) + { + if (!TryExportFile(exportPath, item, Path.GetExtension(m_VideoClip.m_OriginalPath), out var exportFullPath)) + return false; + m_VideoClip.m_VideoData.WriteData(exportFullPath); + Logger.Debug($"{item.TypeString}: \"{item.Text}\" exported to \"{exportFullPath}\""); + return true; + } + return false; + } + + public static bool ExportShader(AssetItem item, string exportPath) + { + if (!TryExportFile(exportPath, item, ".shader", out var exportFullPath)) + return false; + var m_Shader = (Shader)item.Asset; + var str = m_Shader.Convert(); + File.WriteAllText(exportFullPath, str); + + Logger.Debug($"{item.TypeString}: \"{item.Text}\" exported to \"{exportFullPath}\""); + return true; + } + + public static bool ExportTextAsset(AssetItem item, string exportPath, CLIOptions options) + { + var m_TextAsset = (TextAsset)item.Asset; + var extension = ".txt"; + if (!options.f_notRestoreExtensionName.Value) + { + if (!string.IsNullOrEmpty(item.Container)) + { + extension = Path.GetExtension(item.Container); + } + } + if (!TryExportFile(exportPath, item, extension, out var exportFullPath)) + return false; + File.WriteAllBytes(exportFullPath, m_TextAsset.m_Script); + + Logger.Debug($"{item.TypeString}: \"{item.Text}\" exported to \"{exportFullPath}\""); + return true; + } + + public static bool ExportMonoBehaviour(AssetItem item, string exportPath, CLIOptions options) + { + if (!TryExportFile(exportPath, item, ".json", out var exportFullPath)) + return false; + var m_MonoBehaviour = (MonoBehaviour)item.Asset; + var type = m_MonoBehaviour.ToType(); + if (type == null) + { + var m_Type = MonoBehaviourToTypeTree(m_MonoBehaviour, options); + type = m_MonoBehaviour.ToType(m_Type); + } + var str = JsonConvert.SerializeObject(type, Formatting.Indented); + File.WriteAllText(exportFullPath, str); + + Logger.Debug($"{item.TypeString}: \"{item.Text}\" exported to \"{exportFullPath}\""); + return true; + } + + public static bool ExportFont(AssetItem item, string exportPath) + { + var m_Font = (Font)item.Asset; + if (m_Font.m_FontData != null) + { + var extension = ".ttf"; + if (m_Font.m_FontData[0] == 79 && m_Font.m_FontData[1] == 84 && m_Font.m_FontData[2] == 84 && m_Font.m_FontData[3] == 79) + { + extension = ".otf"; + } + if (!TryExportFile(exportPath, item, extension, out var exportFullPath)) + return false; + File.WriteAllBytes(exportFullPath, m_Font.m_FontData); + + Logger.Debug($"{item.TypeString}: \"{item.Text}\" exported to \"{exportFullPath}\""); + return true; + } + return false; + } + + public static bool ExportSprite(AssetItem item, string exportPath, CLIOptions options) + { + var type = options.o_imageFormat.Value; + var alphaMask = SpriteMaskMode.On; + if (!TryExportFile(exportPath, item, "." + type.ToString().ToLower(), out var exportFullPath)) + return false; + var image = ((Sprite)item.Asset).GetImage(alphaMask); + if (image != null) + { + using (image) + { + using (var file = File.OpenWrite(exportFullPath)) + { + image.WriteToStream(file, type); + } + Logger.Debug($"{item.TypeString}: \"{item.Text}\" exported to \"{exportFullPath}\""); + return true; + } + } + return false; + } + + public static bool ExportRawFile(AssetItem item, string exportPath) + { + if (!TryExportFile(exportPath, item, ".dat", out var exportFullPath)) + return false; + File.WriteAllBytes(exportFullPath, item.Asset.GetRawData()); + + Logger.Debug($"{item.TypeString}: \"{item.Text}\" exported to \"{exportFullPath}\""); + return true; + } + + public static bool ExportDumpFile(AssetItem item, string exportPath, CLIOptions options) + { + if (!TryExportFile(exportPath, item, ".txt", out var exportFullPath)) + return false; + var str = item.Asset.Dump(); + if (str == null && item.Asset is MonoBehaviour m_MonoBehaviour) + { + var m_Type = MonoBehaviourToTypeTree(m_MonoBehaviour, options); + str = m_MonoBehaviour.Dump(m_Type); + } + if (str != null) + { + File.WriteAllText(exportFullPath, str); + Logger.Debug($"{item.TypeString}: \"{item.Text}\" saved to \"{exportFullPath}\""); + return true; + } + return false; + } + + private static bool TryExportFile(string dir, AssetItem item, string extension, out string fullPath) + { + var fileName = FixFileName(item.Text); + fullPath = Path.Combine(dir, fileName + extension); + if (!File.Exists(fullPath)) + { + Directory.CreateDirectory(dir); + return true; + } + fullPath = Path.Combine(dir, fileName + item.UniqueID + extension); + if (!File.Exists(fullPath)) + { + Directory.CreateDirectory(dir); + return true; + } + Logger.Error($"Export error. File \"{fullPath.Color(CLIAnsiColors.BrightRed)}\" already exist"); + return false; + } + + public static bool ExportConvertFile(AssetItem item, string exportPath, CLIOptions options) + { + switch (item.Type) + { + case ClassIDType.Texture2D: + return ExportTexture2D(item, exportPath, options); + case ClassIDType.AudioClip: + return ExportAudioClip(item, exportPath, options); + case ClassIDType.VideoClip: + return ExportVideoClip(item, exportPath); + case ClassIDType.Shader: + return ExportShader(item, exportPath); + case ClassIDType.TextAsset: + return ExportTextAsset(item, exportPath, options); + case ClassIDType.MonoBehaviour: + return ExportMonoBehaviour(item, exportPath, options); + case ClassIDType.Font: + return ExportFont(item, exportPath); + case ClassIDType.Sprite: + return ExportSprite(item, exportPath, options); + default: + return ExportRawFile(item, exportPath); + } + } + + public static TypeTree MonoBehaviourToTypeTree(MonoBehaviour m_MonoBehaviour, CLIOptions options) + { + if (!assemblyLoader.Loaded) + { + var assemblyFolder = options.o_assemblyPath.Value; + if (assemblyFolder != "") + { + assemblyLoader.Load(assemblyFolder); + } + else + { + assemblyLoader.Loaded = true; + } + } + return m_MonoBehaviour.ConvertToTypeTree(assemblyLoader); + } + + public static string FixFileName(string str) + { + if (str.Length >= 260) return Path.GetRandomFileName(); + return Path.GetInvalidFileNameChars().Aggregate(str, (current, c) => current.Replace(c, '_')); + } + } +} diff --git a/AssetStudioCLI/Libraries/linux-x64/libTexture2DDecoderNative.so b/AssetStudioCLI/Libraries/linux-x64/libTexture2DDecoderNative.so new file mode 100644 index 00000000..b473c128 Binary files /dev/null and b/AssetStudioCLI/Libraries/linux-x64/libTexture2DDecoderNative.so differ diff --git a/AssetStudioCLI/Libraries/linux-x64/libfmod.so b/AssetStudioCLI/Libraries/linux-x64/libfmod.so new file mode 100644 index 00000000..b1f09cf3 Binary files /dev/null and b/AssetStudioCLI/Libraries/linux-x64/libfmod.so differ diff --git a/AssetStudioCLI/Libraries/linux-x86/libfmod.so b/AssetStudioCLI/Libraries/linux-x86/libfmod.so new file mode 100644 index 00000000..6f2e31dc Binary files /dev/null and b/AssetStudioCLI/Libraries/linux-x86/libfmod.so differ diff --git a/AssetStudioCLI/Libraries/osx-arm64/libTexture2DDecoderNative.dylib b/AssetStudioCLI/Libraries/osx-arm64/libTexture2DDecoderNative.dylib new file mode 100644 index 00000000..027c1a63 Binary files /dev/null and b/AssetStudioCLI/Libraries/osx-arm64/libTexture2DDecoderNative.dylib differ diff --git a/AssetStudioCLI/Libraries/osx-arm64/libfmod.dylib b/AssetStudioCLI/Libraries/osx-arm64/libfmod.dylib new file mode 100644 index 00000000..a6476ebc Binary files /dev/null and b/AssetStudioCLI/Libraries/osx-arm64/libfmod.dylib differ diff --git a/AssetStudioCLI/Libraries/osx-x64/libTexture2DDecoderNative.dylib b/AssetStudioCLI/Libraries/osx-x64/libTexture2DDecoderNative.dylib new file mode 100644 index 00000000..243635f5 Binary files /dev/null and b/AssetStudioCLI/Libraries/osx-x64/libTexture2DDecoderNative.dylib differ diff --git a/AssetStudioCLI/Libraries/osx-x64/libfmod.dylib b/AssetStudioCLI/Libraries/osx-x64/libfmod.dylib new file mode 100644 index 00000000..d605afc4 Binary files /dev/null and b/AssetStudioCLI/Libraries/osx-x64/libfmod.dylib differ diff --git a/AssetStudioCLI/Libraries/win-x64/fmod.dll b/AssetStudioCLI/Libraries/win-x64/fmod.dll new file mode 100644 index 00000000..22d5d7bf Binary files /dev/null and b/AssetStudioCLI/Libraries/win-x64/fmod.dll differ diff --git a/AssetStudioCLI/Libraries/win-x86/fmod.dll b/AssetStudioCLI/Libraries/win-x86/fmod.dll new file mode 100644 index 00000000..b19e554d Binary files /dev/null and b/AssetStudioCLI/Libraries/win-x86/fmod.dll differ diff --git a/AssetStudioCLI/Options/CLIOptions.cs b/AssetStudioCLI/Options/CLIOptions.cs new file mode 100644 index 00000000..e10bb033 --- /dev/null +++ b/AssetStudioCLI/Options/CLIOptions.cs @@ -0,0 +1,790 @@ +using AssetStudio; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace AssetStudioCLI.Options +{ + internal enum HelpGroups + { + General, + Convert, + Logger, + Advanced, + } + + internal enum WorkMode + { + Export, + ExportRaw, + Dump, + Info, + } + + internal enum AssetGroupOption + { + None, + TypeName, + ContainerPath, + SourceFileName, + } + + internal enum ExportListType + { + None, + XML, + } + + internal enum AudioFormat + { + None, + Wav, + } + + internal enum FilterBy + { + None, + Name, + Container, + PathID, + NameOrContainer, + NameAndContainer, + } + + internal class GroupedOption : Option + { + public GroupedOption(T optionDefaultValue, string optionName, string optionDescription, HelpGroups optionHelpGroup, bool isFlag = false) : base(optionDefaultValue, optionName, optionDescription, optionHelpGroup, isFlag) + { + CLIOptions.OptionGrouping(optionName, optionDescription, optionHelpGroup, isFlag); + } + } + + internal class CLIOptions + { + public bool isParsed; + public bool showHelp; + public string[] cliArgs; + public string inputPath; + public FilterBy filterBy; + private static Dictionary optionsDict; + private static Dictionary flagsDict; + private static Dictionary> optionGroups; + private List supportedAssetTypes; + //general + public Option o_workMode; + public Option> o_exportAssetTypes; + public Option o_groupAssetsBy; + public Option o_outputFolder; + public Option o_displayHelp; + //logger + public Option o_logLevel; + public Option o_logOutput; + //convert + public bool convertTexture; + public Option o_imageFormat; + public Option o_audioFormat; + //advanced + public Option o_exportAssetList; + public Option> o_filterByName; + public Option> o_filterByContainer; + public Option> o_filterByPathID; + public Option> o_filterByText; + public Option o_assemblyPath; + public Option o_unityVersion; + public Option f_notRestoreExtensionName; + + public CLIOptions(string[] args) + { + cliArgs = args; + InitOptions(); + ParseArgs(args); + } + + private void InitOptions() + { + isParsed = false; + showHelp = false; + inputPath = ""; + filterBy = FilterBy.None; + optionsDict = new Dictionary(); + flagsDict = new Dictionary(); + optionGroups = new Dictionary>(); + supportedAssetTypes = new List + { + ClassIDType.Texture2D, + ClassIDType.Sprite, + ClassIDType.TextAsset, + ClassIDType.MonoBehaviour, + ClassIDType.Font, + ClassIDType.Shader, + ClassIDType.AudioClip, + ClassIDType.VideoClip, + }; + + #region Init General Options + o_workMode = new GroupedOption + ( + optionDefaultValue: WorkMode.Export, + optionName: "-m, --mode ", + optionDescription: "Specify working mode\n" + + "\n" + + "Export - Exports converted assets\n" + + "ExportRaw - Exports raw data\n" + + "Dump - Makes asset dumps\n" + + "Info - Loads file(s), shows the number of supported for export assets and exits\n" + + "Example: \"-m info\"\n", + optionHelpGroup: HelpGroups.General + ); + o_exportAssetTypes = new GroupedOption> + ( + optionDefaultValue: supportedAssetTypes, + optionName: "-t, --asset-type ", + optionDescription: "Specify asset type(s) to export\n" + + "\n" + + "All - export all asset types, which are listed in the values\n" + + "*To specify multiple asset types, write them separated by ',' or ';' without spaces\n" + + "Examples: \"-t sprite\" or \"-t all\" or \"-t tex2d,sprite,audio\" or \"-t tex2d;sprite;font\"\n", + optionHelpGroup: HelpGroups.General + ); + o_groupAssetsBy = new GroupedOption + ( + optionDefaultValue: AssetGroupOption.ContainerPath, + optionName: "-g, --group-option ", + optionDescription: "Specify the way in which exported assets should be grouped\n" + + "\n" + + "None - Do not group exported assets\n" + + "Type - Group exported assets by type name\n" + + "Container - Group exported assets by container path\n" + + "Filename - Group exported assets by source file name\n" + + "Example: \"-g container\"\n", + optionHelpGroup: HelpGroups.General + ); + o_outputFolder = new GroupedOption + ( + optionDefaultValue: "", + 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", + optionHelpGroup: HelpGroups.General + ); + o_displayHelp = new GroupedOption + ( + optionDefaultValue: false, + optionName: "-h, --help", + optionDescription: "Display help and exit", + optionHelpGroup: HelpGroups.General + ); + #endregion + + #region Init Logger Options + o_logLevel = new GroupedOption + ( + optionDefaultValue: LoggerEvent.Info, + optionName: "--log-level ", + optionDescription: "Specify the log level\n" + + "\n" + + "Example: \"--log-level warning\"\n", + optionHelpGroup: HelpGroups.Logger + ); + o_logOutput = new GroupedOption + ( + optionDefaultValue: LogOutputMode.Console, + optionName: "--log-output ", + optionDescription: "Specify the log output\n" + + "\n" + + "Example: \"--log-output both\"", + optionHelpGroup: HelpGroups.Logger + ); + #endregion + + #region Init Convert Options + convertTexture = true; + o_imageFormat = new GroupedOption + ( + optionDefaultValue: ImageFormat.Png, + optionName: "--image-format ", + optionDescription: "Specify the format for converting image assets\n" + + "\n" + + "None - Do not convert images and export them as texture data (.tex)\n" + + "Example: \"--image-format jpg\"\n", + optionHelpGroup: HelpGroups.Convert + ); + o_audioFormat = new GroupedOption + ( + optionDefaultValue: AudioFormat.Wav, + optionName: "--audio-format ", + optionDescription: "Specify the format for converting audio assets\n" + + "\n" + + "None - Do not convert audios and export them in their own format\n" + + "Example: \"--audio-format wav\"", + optionHelpGroup: HelpGroups.Convert + ); + #endregion + + #region Init Advanced Options + o_exportAssetList = new GroupedOption + ( + optionDefaultValue: ExportListType.None, + optionName: "--export-asset-list ", + optionDescription: "Specify the format in which you want to export asset list\n" + + "\n" + + "None - Do not export asset list\n" + + "Example: \"--export-asset-list xml\"\n", + optionHelpGroup: HelpGroups.Advanced + ); + o_filterByName = new GroupedOption> + ( + optionDefaultValue: new List(), + optionName: "--filter-by-name ", + optionDescription: "Specify the name by which assets should be filtered\n" + + "*To specify multiple names write them separated by ',' or ';' without spaces\n" + + "Example: \"--filter-by-name char\" or \"--filter-by-name char,bg\"\n", + optionHelpGroup: HelpGroups.Advanced + ); + o_filterByContainer = new GroupedOption> + ( + optionDefaultValue: new List(), + optionName: "--filter-by-container ", + optionDescription: "Specify the container by which assets should be filtered\n" + + "*To specify multiple containers write them separated by ',' or ';' without spaces\n" + + "Example: \"--filter-by-container arts\" or \"--filter-by-container arts,icons\"\n", + optionHelpGroup: HelpGroups.Advanced + ); + o_filterByPathID = new GroupedOption> + ( + optionDefaultValue: new List(), + optionName: "--filter-by-pathid ", + optionDescription: "Specify the PathID by which assets should be filtered\n" + + "*To specify multiple PathIDs write them separated by ',' or ';' without spaces\n" + + "Example: \"--filter-by-pathid 7238605633795851352,-2430306240205277265\"\n", + optionHelpGroup: HelpGroups.Advanced + ); + o_filterByText = new GroupedOption> + ( + optionDefaultValue: new List(), + optionName: "--filter-by-text ", + optionDescription: "Specify the text by which assets should be filtered\n" + + "Looks for assets that contain the specified text in their names or containers\n" + + "*To specify multiple values write them separated by ',' or ';' without spaces\n" + + "Example: \"--filter-by-text portrait\" or \"--filter-by-text portrait,art\"\n", + optionHelpGroup: HelpGroups.Advanced + ); + o_assemblyPath = new GroupedOption + ( + optionDefaultValue: "", + optionName: "--assembly-folder ", + optionDescription: "Specify the path to the assembly folder", + optionHelpGroup: HelpGroups.Advanced + ); + o_unityVersion = new GroupedOption + ( + optionDefaultValue: "", + optionName: "--unity-version ", + optionDescription: "Specify Unity version. Example: \"--unity-version 2017.4.39f1\"", + optionHelpGroup: HelpGroups.Advanced + ); + f_notRestoreExtensionName = new GroupedOption + ( + optionDefaultValue: false, + optionName: "--not-restore-extension", + optionDescription: "(Flag) If specified, AssetStudio will not try to restore TextAssets extension name, \nand will just export all TextAssets with the \".txt\" extension", + optionHelpGroup: HelpGroups.Advanced, + isFlag: true + ); + #endregion + } + + internal static void OptionGrouping(string name, string desc, HelpGroups group, bool isFlag) + { + if (string.IsNullOrEmpty(name)) + { + return; + } + + var optionDict = new Dictionary() { { name, desc } }; + if (!optionGroups.ContainsKey(group)) + { + optionGroups.Add(group, optionDict); + } + else + { + optionGroups[group].Add(name, desc); + } + + if (isFlag) + { + flagsDict.Add(name, desc); + } + else + { + optionsDict.Add(name, desc); + } + } + + private void ParseArgs(string[] args) + { + var brightYellow = CLIAnsiColors.BrightYellow; + var brightRed = CLIAnsiColors.BrightRed; + + if (args.Length == 0 || args.Any(x => x == "-h" || x == "--help")) + { + showHelp = true; + return; + } + + if (!args[0].StartsWith("-")) + { + inputPath = Path.GetFullPath(args[0]).Replace("\"", ""); + if (!Directory.Exists(inputPath) && !File.Exists(inputPath)) + { + Console.WriteLine($"{"Error:".Color(brightRed)} Invalid input path \"{args[0].Color(brightRed)}\".\n" + + $"Specified file or folder was not found. The input path must be specified as the first argument."); + return; + } + o_outputFolder.Value = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ASExport"); + } + else + { + Console.WriteLine($"{"Error:".Color(brightRed)} Input path was empty. Specify the input path as the first argument."); + return; + } + + var resplittedArgs = new List(); + for (int i = 1; i < args.Length; i++) + { + string arg = args[i]; + + if (arg.Contains('=')) + { + var splittedArgs = arg.Split('='); + resplittedArgs.Add(splittedArgs[0]); + resplittedArgs.Add(splittedArgs[1]); + } + else + { + resplittedArgs.Add(arg); + } + }; + + #region Parse Flags + for (int i = 0; i < resplittedArgs.Count; i++) + { + string flag = resplittedArgs[i].ToLower(); + + switch(flag) + { + case "--not-restore-extension": + f_notRestoreExtensionName.Value = true; + resplittedArgs.RemoveAt(i); + break; + } + } + #endregion + + #region Parse Options + for (int i = 0; i < resplittedArgs.Count; i++) + { + var option = resplittedArgs[i].ToLower(); + try + { + var value = resplittedArgs[i + 1].Replace("\"", ""); + switch (option) + { + case "-m": + case "--mode": + switch (value.ToLower()) + { + case "export": + o_workMode.Value = WorkMode.Export; + break; + case "raw": + case "exportraw": + o_workMode.Value = WorkMode.ExportRaw; + break; + case "dump": + o_workMode.Value = WorkMode.Dump; + break; + case "info": + o_workMode.Value = WorkMode.Info; + break; + default: + Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unsupported working mode: [{value.Color(brightRed)}].\n"); + Console.WriteLine(o_workMode.Description); + return; + } + break; + case "-t": + case "--asset-type": + var splittedTypes = ValueSplitter(value); + o_exportAssetTypes.Value = new List(); + foreach (var type in splittedTypes) + { + switch (type.ToLower()) + { + case "tex2d": + case "texture2d": + o_exportAssetTypes.Value.Add(ClassIDType.Texture2D); + break; + case "sprite": + o_exportAssetTypes.Value.Add(ClassIDType.Sprite); + break; + case "textasset": + o_exportAssetTypes.Value.Add(ClassIDType.TextAsset); + break; + case "monobehaviour": + o_exportAssetTypes.Value.Add(ClassIDType.MonoBehaviour); + break; + case "font": + o_exportAssetTypes.Value.Add(ClassIDType.Font); + break; + case "shader": + o_exportAssetTypes.Value.Add(ClassIDType.Shader); + break; + case "audio": + case "audioclip": + o_exportAssetTypes.Value.Add(ClassIDType.AudioClip); + break; + case "video": + case "videoclip": + o_exportAssetTypes.Value.Add(ClassIDType.VideoClip); + break; + case "all": + o_exportAssetTypes.Value = supportedAssetTypes; + break; + default: + Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unsupported asset type: [{value.Color(brightRed)}].\n"); + Console.WriteLine(o_exportAssetTypes.Description); + return; + } + } + break; + case "-g": + case "--group-option": + switch (value.ToLower()) + { + case "type": + o_groupAssetsBy.Value = AssetGroupOption.TypeName; + break; + case "container": + o_groupAssetsBy.Value = AssetGroupOption.ContainerPath; + break; + case "filename": + o_groupAssetsBy.Value = AssetGroupOption.SourceFileName; + break; + case "none": + o_groupAssetsBy.Value = AssetGroupOption.None; + break; + default: + Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unsupported grouping option: [{value.Color(brightRed)}].\n"); + Console.WriteLine(o_groupAssetsBy.Description); + return; + } + break; + case "-o": + case "--output": + try + { + value = Path.GetFullPath(value); + if (!Directory.Exists(value)) + { + Directory.CreateDirectory(value); + } + o_outputFolder.Value = value; + } + catch (Exception ex) + { + Console.WriteLine($"{"Warning:".Color(brightYellow)} Invalid output folder \"{value.Color(brightYellow)}\".\n{ex.Message}"); + Console.WriteLine($"Working folder \"{o_outputFolder.Value.Color(brightYellow)}\" will be used as the output folder.\n"); + Console.WriteLine("Press ESC to exit or any other key to continue...\n"); + switch (Console.ReadKey(intercept: true).Key) + { + case ConsoleKey.Escape: + return; + } + } + break; + case "--log-level": + switch (value.ToLower()) + { + case "verbose": + o_logLevel.Value = LoggerEvent.Verbose; + break; + case "debug": + o_logLevel.Value = LoggerEvent.Debug; + break; + case "info": + o_logLevel.Value = LoggerEvent.Info; + break; + case "warning": + o_logLevel.Value = LoggerEvent.Warning; + break; + case "error": + o_logLevel.Value = LoggerEvent.Error; + break; + default: + Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unsupported log level value: [{value.Color(brightRed)}].\n"); + Console.WriteLine(o_logLevel.Description); + return; + } + break; + case "--log-output": + switch (value.ToLower()) + { + case "console": + o_logOutput.Value = LogOutputMode.Console; + break; + case "file": + o_logOutput.Value = LogOutputMode.File; + break; + case "both": + o_logOutput.Value = LogOutputMode.Both; + break; + default: + Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unsupported log output mode: [{value.Color(brightRed)}].\n"); + Console.WriteLine(o_logOutput.Description); + return; + } + break; + case "--image-format": + switch (value.ToLower()) + { + case "jpg": + case "jpeg": + o_imageFormat.Value = ImageFormat.Jpeg; + break; + case "png": + o_imageFormat.Value = ImageFormat.Png; + break; + case "bmp": + o_imageFormat.Value = ImageFormat.Bmp; + break; + case "tga": + o_imageFormat.Value = ImageFormat.Tga; + break; + case "webp": + o_imageFormat.Value = ImageFormat.Webp; + break; + case "none": + convertTexture = false; + break; + default: + Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unsupported image format: [{value.Color(brightRed)}].\n"); + Console.WriteLine(o_imageFormat.Description); + return; + } + break; + case "--audio-format": + switch (value.ToLower()) + { + case "wav": + case "wave": + o_audioFormat.Value = AudioFormat.Wav; + break; + case "none": + o_audioFormat.Value = AudioFormat.None; + break; + default: + Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unsupported audio format: [{value.Color(brightRed)}].\n"); + Console.WriteLine(o_audioFormat.Description); + return; + } + break; + case "--export-asset-list": + switch (value.ToLower()) + { + case "xml": + o_exportAssetList.Value = ExportListType.XML; + break; + case "none": + o_exportAssetList.Value = ExportListType.None; + break; + default: + Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unsupported asset list export option: [{value.Color(brightRed)}].\n"); + Console.WriteLine(o_exportAssetList.Description); + return; + } + break; + case "--filter-by-name": + o_filterByName.Value.AddRange(ValueSplitter(value)); + filterBy = filterBy == FilterBy.None ? FilterBy.Name : filterBy == FilterBy.Container ? FilterBy.NameAndContainer : filterBy; + break; + case "--filter-by-container": + o_filterByContainer.Value.AddRange(ValueSplitter(value)); + filterBy = filterBy == FilterBy.None ? FilterBy.Container : filterBy == FilterBy.Name ? FilterBy.NameAndContainer : filterBy; + break; + case "--filter-by-pathid": + o_filterByPathID.Value.AddRange(ValueSplitter(value)); + filterBy = FilterBy.PathID; + break; + case "--filter-by-text": + o_filterByText.Value.AddRange(ValueSplitter(value)); + filterBy = FilterBy.NameOrContainer; + break; + case "--assembly-folder": + if (Directory.Exists(value)) + { + o_assemblyPath.Value = value; + } + else + { + Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Assembly folder [{value.Color(brightRed)}] was not found."); + return; + } + break; + case "--unity-version": + o_unityVersion.Value = value; + break; + default: + Console.WriteLine($"{"Error:".Color(brightRed)} Unknown option [{option.Color(brightRed)}].\n"); + if (!TryShowOptionDescription(option, optionsDict)) + { + TryShowOptionDescription(option, flagsDict); + } + return; + } + i++; + } + catch (IndexOutOfRangeException) + { + if (optionsDict.Any(x => x.Key.Contains(option))) + { + Console.WriteLine($"{"Error during parsing options:".Color(brightRed)} Value for [{option.Color(brightRed)}] option was not found.\n"); + TryShowOptionDescription(option, optionsDict); + } + else if (flagsDict.Any(x => x.Key.Contains(option))) + { + Console.WriteLine($"{"Error:".Color(brightRed)} Unknown flag [{option.Color(brightRed)}].\n"); + TryShowOptionDescription(option, flagsDict); + } + else + { + Console.WriteLine($"{"Error:".Color(brightRed)} Unknown option [{option.Color(brightRed)}]."); + } + return; + } + catch (Exception ex) + { + Console.WriteLine("Unknown Error.".Color(CLIAnsiColors.Red)); + Console.WriteLine(ex); + return; + } + } + isParsed = true; + #endregion + } + + private static string[] ValueSplitter(string value) + { + var separator = value.Contains(';') ? ';' : ','; + return value.Split(separator); + } + + private bool TryShowOptionDescription(string option, Dictionary descDict) + { + var optionDesc = descDict.Where(x => x.Key.Contains(option)); + if (optionDesc.Any()) + { + var rand = new Random(); + var rndOption = optionDesc.ElementAt(rand.Next(0, optionDesc.Count())); + Console.WriteLine($"Did you mean [{ $"{rndOption.Key}".Color(CLIAnsiColors.BrightYellow) }] option?"); + Console.WriteLine($"Here's a description of it: \n\n{rndOption.Value}"); + + return true; + } + return false; + } + + public void ShowHelp(bool showUsageOnly = false) + { + const int indent = 22; + var helpMessage = new StringBuilder(); + var usage = new StringBuilder(); + var appAssembly = typeof(Program).Assembly.GetName(); + usage.Append($"Usage: {appAssembly.Name} "); + + var i = 0; + foreach (var optionsGroup in optionGroups.Keys) + { + helpMessage.AppendLine($"{optionsGroup} Options:"); + foreach (var optionDict in optionGroups[optionsGroup]) + { + var optionName = $"{optionDict.Key,-indent - 8}"; + var optionDesc = optionDict.Value.Replace("\n", $"{"\n",-indent - 11}"); + helpMessage.AppendLine($" {optionName}{optionDesc}"); + + usage.Append($"[{optionDict.Key}] "); + if (i++ % 2 == 0) + { + usage.Append($"\n{"",indent}"); + } + } + helpMessage.AppendLine(); + } + + if (showUsageOnly) + { + Console.WriteLine(usage); + } + else + { + Console.WriteLine($"# {appAssembly.Name}\n# Based on AssetStudio Mod v{appAssembly.Version}\n"); + Console.WriteLine($"{usage}\n\n{helpMessage}"); + } + } + + private string ShowCurrentFilter() + { + switch (filterBy) + { + case FilterBy.Name: + return $"# Filter by {filterBy}(s): \"{string.Join("\", \"", o_filterByName.Value)}\""; + case FilterBy.Container: + return $"# Filter by {filterBy}(s): \"{string.Join("\", \"", o_filterByContainer.Value)}\""; + case FilterBy.PathID: + return $"# Filter by {filterBy}(s): \"{string.Join("\", \"", o_filterByPathID.Value)}\""; + case FilterBy.NameOrContainer: + return $"# Filter by Text: \"{string.Join("\", \"", o_filterByText.Value)}\""; + case FilterBy.NameAndContainer: + return $"# Filter by Name(s): \"{string.Join("\", \"", o_filterByName.Value)}\"\n# Filter by Container(s): \"{string.Join("\", \"", o_filterByContainer.Value)}\""; + default: + return $"# Filter by: {filterBy}"; + } + } + + public void ShowCurrentOptions() + { + var sb = new StringBuilder(); + sb.AppendLine("[Current Options]"); + sb.AppendLine($"# Working Mode: {o_workMode}"); + sb.AppendLine($"# Input Path: \"{inputPath}\""); + if (o_workMode.Value != WorkMode.Info) + { + sb.AppendLine($"# Output Path: \"{o_outputFolder}\""); + sb.AppendLine($"# Export Asset Type(s): {string.Join(", ", o_exportAssetTypes.Value)}"); + sb.AppendLine($"# Asset Group Option: {o_groupAssetsBy}"); + sb.AppendLine($"# Export Image Format: {o_imageFormat}"); + sb.AppendLine($"# Export Audio Format: {o_audioFormat}"); + sb.AppendLine($"# Log Level: {o_logLevel}"); + sb.AppendLine($"# Log Output: {o_logOutput}"); + sb.AppendLine($"# Export Asset List: {o_exportAssetList}"); + sb.AppendLine(ShowCurrentFilter()); + sb.AppendLine($"# Assebmly Path: \"{o_assemblyPath}\""); + sb.AppendLine($"# Unity Version: \"{o_unityVersion}\""); + sb.AppendLine($"# Restore TextAsset extension: {!f_notRestoreExtensionName.Value}"); + } + else + { + sb.AppendLine($"# Export Asset Type(s): {string.Join(", ", o_exportAssetTypes.Value)}"); + sb.AppendLine($"# Log Level: {o_logLevel}"); + sb.AppendLine($"# Log Output: {o_logOutput}"); + sb.AppendLine($"# Export Asset List: {o_exportAssetList}"); + sb.AppendLine(ShowCurrentFilter()); + sb.AppendLine($"# Unity Version: \"{o_unityVersion}\""); + } + sb.AppendLine("======"); + Logger.Info(sb.ToString()); + } + } +} diff --git a/AssetStudioCLI/Options/Option.cs b/AssetStudioCLI/Options/Option.cs new file mode 100644 index 00000000..2626276e --- /dev/null +++ b/AssetStudioCLI/Options/Option.cs @@ -0,0 +1,27 @@ +namespace AssetStudioCLI.Options +{ + internal class Option + { + public string Name { get; } + public string Description { get; } + public T Value { get; set; } + public T DefaultValue { get; } + public HelpGroups HelpGroup { get; } + public bool IsFlag { get; } + + public Option(T optionDefaultValue, string optionName, string optionDescription, HelpGroups optionHelpGroup, bool isFlag) + { + Name = optionName; + Description = optionDescription; + DefaultValue = optionDefaultValue; + Value = DefaultValue; + HelpGroup = optionHelpGroup; + IsFlag = isFlag; + } + + public override string ToString() + { + return Value != null ? Value.ToString() : string.Empty; + } + } +} diff --git a/AssetStudioCLI/Program.cs b/AssetStudioCLI/Program.cs new file mode 100644 index 00000000..6342fc68 --- /dev/null +++ b/AssetStudioCLI/Program.cs @@ -0,0 +1,65 @@ +using AssetStudio; +using AssetStudioCLI.Options; +using System; + +namespace AssetStudioCLI +{ + class Program + { + public static void Main(string[] args) + { + var options = new CLIOptions(args); + if (options.isParsed) + { + CLIRun(options); + } + else if (options.showHelp) + { + options.ShowHelp(); + } + else + { + Console.WriteLine(); + options.ShowHelp(showUsageOnly: true); + } + } + + private static void CLIRun(CLIOptions options) + { + var cliLogger = new CLILogger(options); + Logger.Default = cliLogger; + var studio = new Studio(options); + options.ShowCurrentOptions(); + + try + { + if (studio.LoadAssets()) + { + studio.ParseAssets(); + if (options.filterBy != FilterBy.None) + { + studio.FilterAssets(); + } + if (options.o_exportAssetList.Value != ExportListType.None) + { + studio.ExportAssetList(); + } + if (options.o_workMode.Value == WorkMode.Info) + { + studio.ShowExportableAssetsInfo(); + return; + } + studio.ExportAssets(); + } + } + catch (Exception ex) + { + Logger.Error(ex.ToString()); + } + finally + { + cliLogger.LogToFile(LoggerEvent.Verbose, "---Program ended---"); + } + } + } +} diff --git a/AssetStudioCLI/Studio.cs b/AssetStudioCLI/Studio.cs new file mode 100644 index 00000000..95cab44a --- /dev/null +++ b/AssetStudioCLI/Studio.cs @@ -0,0 +1,376 @@ +using AssetStudio; +using AssetStudioCLI.Options; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using static AssetStudioCLI.Exporter; +using Ansi = AssetStudioCLI.CLIAnsiColors; + +namespace AssetStudioCLI +{ + internal class Studio + { + public AssetsManager assetsManager = new AssetsManager(); + public List parsedAssetsList = new List(); + private readonly CLIOptions options; + + public Studio(CLIOptions cliOptions) + { + Progress.Default = new Progress(ShowCurProgressValue); + options = cliOptions; + } + + private void ShowCurProgressValue(int value) + { + Console.Write($"[{value:000}%]\r"); + } + + public bool LoadAssets() + { + var isLoaded = false; + assetsManager.SpecifyUnityVersion = options.o_unityVersion.Value; + + if (Directory.Exists(options.inputPath)) + { + assetsManager.LoadFolder(options.inputPath); + } + else + { + assetsManager.LoadFiles(options.inputPath); + } + if (assetsManager.assetsFileList.Count == 0) + { + Logger.Warning("No Unity file can be loaded."); + } + else + { + isLoaded = true; + } + + return isLoaded; + } + + public void ParseAssets() + { + Logger.Info("Parse assets..."); + + var fileAssetsList = new List(); + var containers = new Dictionary(); + var objectCount = assetsManager.assetsFileList.Sum(x => x.Objects.Count); + + Progress.Reset(); + var i = 0; + foreach (var assetsFile in assetsManager.assetsFileList) + { + foreach (var asset in assetsFile.Objects) + { + var assetItem = new AssetItem(asset); + assetItem.UniqueID = "_#" + i; + var isExportable = false; + switch (asset) + { + case AssetBundle m_AssetBundle: + foreach (var m_Container in m_AssetBundle.m_Container) + { + var preloadIndex = m_Container.Value.preloadIndex; + var preloadSize = m_Container.Value.preloadSize; + var preloadEnd = preloadIndex + preloadSize; + for (int k = preloadIndex; k < preloadEnd; k++) + { + var pptr = m_AssetBundle.m_PreloadTable[k]; + if (pptr.TryGet(out var obj)) + { + containers[obj] = m_Container.Key; + } + } + } + break; + case ResourceManager m_ResourceManager: + foreach (var m_Container in m_ResourceManager.m_Container) + { + if (m_Container.Value.TryGet(out var obj)) + { + containers[obj] = m_Container.Key; + } + } + break; + case Texture2D m_Texture2D: + if (!string.IsNullOrEmpty(m_Texture2D.m_StreamData?.path)) + assetItem.FullSize = asset.byteSize + m_Texture2D.m_StreamData.size; + assetItem.Text = m_Texture2D.m_Name; + break; + case AudioClip m_AudioClip: + if (!string.IsNullOrEmpty(m_AudioClip.m_Source)) + assetItem.FullSize = asset.byteSize + m_AudioClip.m_Size; + assetItem.Text = m_AudioClip.m_Name; + break; + case VideoClip m_VideoClip: + if (!string.IsNullOrEmpty(m_VideoClip.m_OriginalPath)) + assetItem.FullSize = asset.byteSize + m_VideoClip.m_ExternalResources.m_Size; + assetItem.Text = m_VideoClip.m_Name; + break; + case TextAsset _: + case Font _: + case Sprite _: + assetItem.Text = ((NamedObject)asset).m_Name; + break; + case Shader m_Shader: + assetItem.Text = m_Shader.m_ParsedForm?.m_Name ?? m_Shader.m_Name; + break; + case MonoBehaviour m_MonoBehaviour: + if (m_MonoBehaviour.m_Name == "" && m_MonoBehaviour.m_Script.TryGet(out var m_Script)) + { + assetItem.Text = m_Script.m_ClassName; + } + else + { + assetItem.Text = m_MonoBehaviour.m_Name; + } + break; + } + if (assetItem.Text == "") + { + assetItem.Text = assetItem.TypeString + assetItem.UniqueID; + } + + isExportable = options.o_exportAssetTypes.Value.Contains(asset.type); + if (isExportable) + { + fileAssetsList.Add(assetItem); + } + + Progress.Report(++i, objectCount); + } + foreach (var asset in fileAssetsList) + { + if (containers.ContainsKey(asset.Asset)) + { + asset.Container = containers[asset.Asset]; + } + } + parsedAssetsList.AddRange(fileAssetsList); + containers.Clear(); + fileAssetsList.Clear(); + } + } + + public void ShowExportableAssetsInfo() + { + var exportableAssetsCountDict = new Dictionary(); + if (parsedAssetsList.Count > 0) + { + foreach (var asset in parsedAssetsList) + { + if (exportableAssetsCountDict.ContainsKey(asset.Type)) + { + exportableAssetsCountDict[asset.Type] += 1; + } + else + { + exportableAssetsCountDict.Add(asset.Type, 1); + } + } + + var info = "\n[Exportable Assets Count]\n"; + foreach (var assetType in exportableAssetsCountDict.Keys) + { + info += $"# {assetType}: {exportableAssetsCountDict[assetType]}\n"; + } + if (exportableAssetsCountDict.Count > 1) + { + info += $"#\n# Total: {parsedAssetsList.Count} assets"; + } + + if (options.o_logLevel.Value > LoggerEvent.Info) + { + Console.WriteLine(info); + } + else + { + Logger.Info(info); + } + } + } + + public void FilterAssets() + { + var assetsCount = parsedAssetsList.Count; + var filteredAssets = new List(); + + switch(options.filterBy) + { + case FilterBy.Name: + filteredAssets = parsedAssetsList.FindAll(x => options.o_filterByName.Value.Any(y => x.Text.ToString().IndexOf(y, StringComparison.OrdinalIgnoreCase) >= 0)); + Logger.Info( + $"Found [{filteredAssets.Count}/{assetsCount}] asset(s) " + + $"that contain {$"\"{string.Join("\", \"", options.o_filterByName.Value)}\"".Color(Ansi.BrightYellow)} in their Names." + ); + break; + case FilterBy.Container: + filteredAssets = parsedAssetsList.FindAll(x => options.o_filterByContainer.Value.Any(y => x.Container.ToString().IndexOf(y, StringComparison.OrdinalIgnoreCase) >= 0)); + Logger.Info( + $"Found [{filteredAssets.Count}/{assetsCount}] asset(s) " + + $"that contain {$"\"{string.Join("\", \"", options.o_filterByContainer.Value)}\"".Color(Ansi.BrightYellow)} in their Containers." + ); + break; + case FilterBy.PathID: + filteredAssets = parsedAssetsList.FindAll(x => options.o_filterByPathID.Value.Any(y => x.m_PathID.ToString().IndexOf(y, StringComparison.OrdinalIgnoreCase) >= 0)); + Logger.Info( + $"Found [{filteredAssets.Count}/{assetsCount}] asset(s) " + + $"that contain {$"\"{string.Join("\", \"", options.o_filterByPathID.Value)}\"".Color(Ansi.BrightYellow)} in their PathIDs." + ); + break; + case FilterBy.NameOrContainer: + filteredAssets = parsedAssetsList.FindAll(x => + options.o_filterByText.Value.Any(y => x.Text.ToString().IndexOf(y, StringComparison.OrdinalIgnoreCase) >= 0) || + options.o_filterByText.Value.Any(y => x.Container.ToString().IndexOf(y, StringComparison.OrdinalIgnoreCase) >= 0) + ); + Logger.Info( + $"Found [{filteredAssets.Count}/{assetsCount}] asset(s) " + + $"that contain {$"\"{string.Join("\", \"", options.o_filterByText.Value)}\"".Color(Ansi.BrightYellow)} in their Names or Contaniers." + ); + break; + case FilterBy.NameAndContainer: + filteredAssets = parsedAssetsList.FindAll(x => + options.o_filterByName.Value.Any(y => x.Text.ToString().IndexOf(y, StringComparison.OrdinalIgnoreCase) >= 0) && + options.o_filterByContainer.Value.Any(y => x.Container.ToString().IndexOf(y, StringComparison.OrdinalIgnoreCase) >= 0) + ); + Logger.Info( + $"Found [{filteredAssets.Count}/{assetsCount}] asset(s) " + + $"that contain {$"\"{string.Join("\", \"", options.o_filterByContainer.Value)}\"".Color(Ansi.BrightYellow)} in their Containers " + + $"and {$"\"{string.Join("\", \"", options.o_filterByName.Value)}\"".Color(Ansi.BrightYellow)} in their Names." + ); + break; + } + parsedAssetsList.Clear(); + parsedAssetsList = filteredAssets; + } + + public void ExportAssets() + { + var savePath = options.o_outputFolder.Value; + var toExportCount = parsedAssetsList.Count; + var exportedCount = 0; + + foreach (var asset in parsedAssetsList) + { + string exportPath; + switch (options.o_groupAssetsBy.Value) + { + case AssetGroupOption.TypeName: + exportPath = Path.Combine(savePath, asset.TypeString); + break; + case AssetGroupOption.ContainerPath: + if (!string.IsNullOrEmpty(asset.Container)) + { + exportPath = Path.Combine(savePath, Path.GetDirectoryName(asset.Container)); + } + else + { + exportPath = savePath; + } + break; + case AssetGroupOption.SourceFileName: + if (string.IsNullOrEmpty(asset.SourceFile.originalPath)) + { + exportPath = Path.Combine(savePath, asset.SourceFile.fileName + "_export"); + } + else + { + exportPath = Path.Combine(savePath, Path.GetFileName(asset.SourceFile.originalPath) + "_export", asset.SourceFile.fileName); + } + break; + default: + exportPath = savePath; + break; + } + + exportPath += Path.DirectorySeparatorChar; + try + { + switch (options.o_workMode.Value) + { + case WorkMode.ExportRaw: + Logger.Debug($"{options.o_workMode}: {asset.Type} : {asset.Container} : {asset.Text}"); + if (ExportRawFile(asset, exportPath)) + { + exportedCount++; + } + break; + case WorkMode.Dump: + Logger.Debug($"{options.o_workMode}: {asset.Type} : {asset.Container} : {asset.Text}"); + if (ExportDumpFile(asset, exportPath, options)) + { + exportedCount++; + } + break; + case WorkMode.Export: + Logger.Debug($"{options.o_workMode}: {asset.Type} : {asset.Container} : {asset.Text}"); + if (ExportConvertFile(asset, exportPath, options)) + { + exportedCount++; + } + break; + } + } + catch (Exception ex) + { + Logger.Error($"{asset.SourceFile.originalPath}: [{$"{asset.Type}: {asset.Text}".Color(Ansi.BrightRed)}] : Export error\n{ex}"); + } + Console.Write($"Exported [{exportedCount}/{toExportCount}]\r"); + } + Console.WriteLine(""); + + if (exportedCount == 0) + { + Logger.Info("Nothing exported."); + } + else if (toExportCount > exportedCount) + { + Logger.Info($"Finished exporting {exportedCount} asset(s) to \"{options.o_outputFolder.Value.Color(Ansi.BrightYellow)}\"."); + } + else + { + Logger.Info($"Finished exporting {exportedCount} asset(s) to \"{options.o_outputFolder.Value.Color(Ansi.BrightGreen)}\"."); + } + + if (toExportCount > exportedCount) + { + Logger.Info($"{toExportCount - exportedCount} asset(s) skipped (not extractable or file(s) already exist)."); + } + } + + public void ExportAssetList() + { + var savePath = options.o_outputFolder.Value; + + switch (options.o_exportAssetList.Value) + { + case ExportListType.XML: + var filename = Path.Combine(savePath, "assets.xml"); + var doc = new XDocument( + new XElement("Assets", + new XAttribute("filename", filename), + new XAttribute("createdAt", DateTime.UtcNow.ToString("s")), + parsedAssetsList.Select( + asset => new XElement("Asset", + new XElement("Name", asset.Text), + new XElement("Container", asset.Container), + new XElement("Type", new XAttribute("id", (int)asset.Type), asset.TypeString), + new XElement("PathID", asset.m_PathID), + new XElement("Source", asset.SourceFile.fullName), + new XElement("Size", asset.FullSize) + ) + ) + ) + ); + doc.Save(filename); + + break; + } + Logger.Info($"Finished exporting asset list with {parsedAssetsList.Count} items."); + } + } +} diff --git a/AssetStudioFBXWrapper/AssetStudioFBXWrapper.csproj b/AssetStudioFBXWrapper/AssetStudioFBXWrapper.csproj index 15e822ec..a62fb1d7 100644 --- a/AssetStudioFBXWrapper/AssetStudioFBXWrapper.csproj +++ b/AssetStudioFBXWrapper/AssetStudioFBXWrapper.csproj @@ -3,7 +3,7 @@ net472;net6.0;net7.0 true - 0.16.47.1 + 0.16.48.1 Copyright © Perfare 2018-2022; Copyright © hozuki 2020 embedded diff --git a/AssetStudioFBXWrapper/Fbx.cs b/AssetStudioFBXWrapper/Fbx.cs index 543706b0..4b4e6e7f 100644 --- a/AssetStudioFBXWrapper/Fbx.cs +++ b/AssetStudioFBXWrapper/Fbx.cs @@ -1,16 +1,20 @@ using AssetStudio.FbxInterop; -using AssetStudio.PInvoke; using System.IO; +#if NETFRAMEWORK +using AssetStudio.PInvoke; +#endif + namespace AssetStudio { public static partial class Fbx { - +#if NETFRAMEWORK static Fbx() { DllLoader.PreloadDll(FbxDll.DllName); } +#endif public static Vector3 QuaternionToEuler(Quaternion q) { diff --git a/AssetStudioGUI/AssetStudioGUI.csproj b/AssetStudioGUI/AssetStudioGUI.csproj index 1c68cd84..e2faee16 100644 --- a/AssetStudioGUI/AssetStudioGUI.csproj +++ b/AssetStudioGUI/AssetStudioGUI.csproj @@ -6,7 +6,7 @@ true Resources\as.ico AssetStudio Mod by VaDiM - 0.16.47.1 + 0.16.48.1 Copyright © Perfare 2018-2022 embedded @@ -40,44 +40,76 @@ - - - PreserveNewest - x86\fmod.dll - - - PreserveNewest - x64\fmod.dll - - - - + Libraries\OpenTK.WinForms.dll - - + + - - - - - + + + + + + + + + + - - - - - + + + + + + + + + - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AssetStudioGUI/AssetStudioGUIForm.Designer.cs b/AssetStudioGUI/AssetStudioGUIForm.Designer.cs index 8f735fa7..3a44cc54 100644 --- a/AssetStudioGUI/AssetStudioGUIForm.Designer.cs +++ b/AssetStudioGUI/AssetStudioGUIForm.Designer.cs @@ -79,6 +79,7 @@ private void InitializeComponent() this.splitContainer1 = new System.Windows.Forms.SplitContainer(); this.tabControl1 = new System.Windows.Forms.TabControl(); this.tabPage1 = new System.Windows.Forms.TabPage(); + this.sceneTreeView = new AssetStudioGUI.GOHierarchy(); this.treeSearch = new System.Windows.Forms.TextBox(); this.tabPage2 = new System.Windows.Forms.TabPage(); this.filterExcludeMode = new System.Windows.Forms.CheckBox(); @@ -127,7 +128,6 @@ private void InitializeComponent() this.dumpSelectedAssetsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.goToSceneHierarchyToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.showOriginalFileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.sceneTreeView = new AssetStudioGUI.GOHierarchy(); this.menuStrip1.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); this.splitContainer1.Panel1.SuspendLayout(); @@ -573,6 +573,17 @@ private void InitializeComponent() this.tabPage1.Text = "Scene Hierarchy"; this.tabPage1.UseVisualStyleBackColor = true; // + // sceneTreeView + // + this.sceneTreeView.CheckBoxes = true; + this.sceneTreeView.Dock = System.Windows.Forms.DockStyle.Fill; + this.sceneTreeView.HideSelection = false; + this.sceneTreeView.Location = new System.Drawing.Point(0, 20); + this.sceneTreeView.Name = "sceneTreeView"; + this.sceneTreeView.Size = new System.Drawing.Size(472, 587); + this.sceneTreeView.TabIndex = 1; + this.sceneTreeView.AfterCheck += new System.Windows.Forms.TreeViewEventHandler(this.sceneTreeView_AfterCheck); + // // treeSearch // this.treeSearch.Dock = System.Windows.Forms.DockStyle.Top; @@ -599,7 +610,7 @@ private void InitializeComponent() this.tabPage2.Text = "Asset List"; this.tabPage2.UseVisualStyleBackColor = true; // - // filterExludeMode + // filterExcludeMode // this.filterExcludeMode.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); this.filterExcludeMode.AutoSize = true; @@ -607,14 +618,15 @@ private void InitializeComponent() this.filterExcludeMode.Enabled = false; this.filterExcludeMode.FlatStyle = System.Windows.Forms.FlatStyle.Flat; this.filterExcludeMode.ForeColor = System.Drawing.SystemColors.ControlText; + this.filterExcludeMode.ImeMode = System.Windows.Forms.ImeMode.NoControl; this.filterExcludeMode.Location = new System.Drawing.Point(409, 2); - this.filterExcludeMode.Name = "filterExludeMode"; + this.filterExcludeMode.Name = "filterExcludeMode"; this.filterExcludeMode.Size = new System.Drawing.Size(61, 17); this.filterExcludeMode.TabIndex = 2; this.filterExcludeMode.Text = "Exclude"; this.filterExcludeMode.TextAlign = System.Drawing.ContentAlignment.BottomRight; this.filterExcludeMode.UseVisualStyleBackColor = false; - this.filterExcludeMode.CheckedChanged += new System.EventHandler(this.filterExludeMode_CheckedChanged); + this.filterExcludeMode.CheckedChanged += new System.EventHandler(this.filterExcludeMode_CheckedChanged); // // assetListView // @@ -731,6 +743,7 @@ private void InitializeComponent() // progressBar1 // this.progressBar1.Dock = System.Windows.Forms.DockStyle.Bottom; + this.progressBar1.ImeMode = System.Windows.Forms.ImeMode.NoControl; this.progressBar1.Location = new System.Drawing.Point(1, 3); this.progressBar1.Name = "progressBar1"; this.progressBar1.Size = new System.Drawing.Size(478, 18); @@ -782,6 +795,7 @@ private void InitializeComponent() this.assetInfoLabel.AutoSize = true; this.assetInfoLabel.BackColor = System.Drawing.Color.Transparent; this.assetInfoLabel.ForeColor = System.Drawing.SystemColors.ControlLightLight; + this.assetInfoLabel.ImeMode = System.Windows.Forms.ImeMode.NoControl; this.assetInfoLabel.Location = new System.Drawing.Point(4, 8); this.assetInfoLabel.Name = "assetInfoLabel"; this.assetInfoLabel.Size = new System.Drawing.Size(0, 13); @@ -811,6 +825,7 @@ private void InitializeComponent() // this.FMODcopyright.AutoSize = true; this.FMODcopyright.ForeColor = System.Drawing.SystemColors.ControlLight; + this.FMODcopyright.ImeMode = System.Windows.Forms.ImeMode.NoControl; this.FMODcopyright.Location = new System.Drawing.Point(214, 365); this.FMODcopyright.Name = "FMODcopyright"; this.FMODcopyright.Size = new System.Drawing.Size(283, 13); @@ -821,6 +836,7 @@ private void InitializeComponent() // this.FMODinfoLabel.AutoSize = true; this.FMODinfoLabel.ForeColor = System.Drawing.SystemColors.ControlLightLight; + this.FMODinfoLabel.ImeMode = System.Windows.Forms.ImeMode.NoControl; this.FMODinfoLabel.Location = new System.Drawing.Point(275, 255); this.FMODinfoLabel.Name = "FMODinfoLabel"; this.FMODinfoLabel.Size = new System.Drawing.Size(0, 13); @@ -830,6 +846,7 @@ private void InitializeComponent() // this.FMODtimerLabel.AutoSize = true; this.FMODtimerLabel.ForeColor = System.Drawing.SystemColors.ControlLightLight; + this.FMODtimerLabel.ImeMode = System.Windows.Forms.ImeMode.NoControl; this.FMODtimerLabel.Location = new System.Drawing.Point(457, 253); this.FMODtimerLabel.Name = "FMODtimerLabel"; this.FMODtimerLabel.Size = new System.Drawing.Size(102, 13); @@ -840,6 +857,7 @@ private void InitializeComponent() // this.FMODstatusLabel.AutoSize = true; this.FMODstatusLabel.ForeColor = System.Drawing.SystemColors.ControlLightLight; + this.FMODstatusLabel.ImeMode = System.Windows.Forms.ImeMode.NoControl; this.FMODstatusLabel.Location = new System.Drawing.Point(214, 255); this.FMODstatusLabel.Name = "FMODstatusLabel"; this.FMODstatusLabel.Size = new System.Drawing.Size(47, 13); @@ -849,6 +867,7 @@ private void InitializeComponent() // FMODprogressBar // this.FMODprogressBar.AutoSize = false; + this.FMODprogressBar.ImeMode = System.Windows.Forms.ImeMode.NoControl; this.FMODprogressBar.Location = new System.Drawing.Point(213, 274); this.FMODprogressBar.Maximum = 1000; this.FMODprogressBar.Name = "FMODprogressBar"; @@ -861,6 +880,7 @@ private void InitializeComponent() // // FMODvolumeBar // + this.FMODvolumeBar.ImeMode = System.Windows.Forms.ImeMode.NoControl; this.FMODvolumeBar.LargeChange = 2; this.FMODvolumeBar.Location = new System.Drawing.Point(460, 303); this.FMODvolumeBar.Name = "FMODvolumeBar"; @@ -873,6 +893,7 @@ private void InitializeComponent() // FMODloopButton // this.FMODloopButton.Appearance = System.Windows.Forms.Appearance.Button; + this.FMODloopButton.ImeMode = System.Windows.Forms.ImeMode.NoControl; this.FMODloopButton.Location = new System.Drawing.Point(399, 303); this.FMODloopButton.Name = "FMODloopButton"; this.FMODloopButton.Size = new System.Drawing.Size(55, 46); @@ -884,6 +905,7 @@ private void InitializeComponent() // // FMODstopButton // + this.FMODstopButton.ImeMode = System.Windows.Forms.ImeMode.NoControl; this.FMODstopButton.Location = new System.Drawing.Point(338, 303); this.FMODstopButton.Name = "FMODstopButton"; this.FMODstopButton.Size = new System.Drawing.Size(55, 46); @@ -894,6 +916,7 @@ private void InitializeComponent() // // FMODpauseButton // + this.FMODpauseButton.ImeMode = System.Windows.Forms.ImeMode.NoControl; this.FMODpauseButton.Location = new System.Drawing.Point(277, 303); this.FMODpauseButton.Name = "FMODpauseButton"; this.FMODpauseButton.Size = new System.Drawing.Size(55, 46); @@ -904,6 +927,7 @@ private void InitializeComponent() // // FMODplayButton // + this.FMODplayButton.ImeMode = System.Windows.Forms.ImeMode.NoControl; this.FMODplayButton.Location = new System.Drawing.Point(216, 303); this.FMODplayButton.Name = "FMODplayButton"; this.FMODplayButton.Size = new System.Drawing.Size(55, 46); @@ -945,7 +969,7 @@ private void InitializeComponent() // textPreviewBox // this.textPreviewBox.Dock = System.Windows.Forms.DockStyle.Fill; - this.textPreviewBox.Font = new System.Drawing.Font("Consolas", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.textPreviewBox.Font = new System.Drawing.Font("Consolas", 9.75F); this.textPreviewBox.Location = new System.Drawing.Point(0, 0); this.textPreviewBox.Multiline = true; this.textPreviewBox.Name = "textPreviewBox"; @@ -1080,17 +1104,6 @@ private void InitializeComponent() this.showOriginalFileToolStripMenuItem.Visible = false; this.showOriginalFileToolStripMenuItem.Click += new System.EventHandler(this.showOriginalFileToolStripMenuItem_Click); // - // sceneTreeView - // - this.sceneTreeView.CheckBoxes = true; - this.sceneTreeView.Dock = System.Windows.Forms.DockStyle.Fill; - this.sceneTreeView.HideSelection = false; - this.sceneTreeView.Location = new System.Drawing.Point(0, 20); - this.sceneTreeView.Name = "sceneTreeView"; - this.sceneTreeView.Size = new System.Drawing.Size(472, 587); - this.sceneTreeView.TabIndex = 1; - this.sceneTreeView.AfterCheck += new System.Windows.Forms.TreeViewEventHandler(this.sceneTreeView_AfterCheck); - // // AssetStudioGUIForm // this.AllowDrop = true; diff --git a/AssetStudioGUI/AssetStudioGUIForm.cs b/AssetStudioGUI/AssetStudioGUIForm.cs index c5fb434c..e2faef3a 100644 --- a/AssetStudioGUI/AssetStudioGUIForm.cs +++ b/AssetStudioGUI/AssetStudioGUIForm.cs @@ -1579,7 +1579,7 @@ private List GetSelectedAssets() return selectedAssets; } - private void filterExludeMode_CheckedChanged(object sender, EventArgs e) + private void filterExcludeMode_CheckedChanged(object sender, EventArgs e) { FilterAssetList(); } diff --git a/AssetStudioUtility/AssetStudioUtility.csproj b/AssetStudioUtility/AssetStudioUtility.csproj index 9c199287..11838b77 100644 --- a/AssetStudioUtility/AssetStudioUtility.csproj +++ b/AssetStudioUtility/AssetStudioUtility.csproj @@ -1,22 +1,37 @@ - - net472;net6.0;net7.0 - 0.16.47.1 - Copyright © Perfare 2018-2022 - embedded - + + net472;net6.0;net6.0-windows;net7.0;net7.0-windows + 0.16.48.1 + Copyright © Perfare 2018-2022 + embedded + - - - - + + + + - - - - - - + + + + + + + + + 0.17.0 + + + + + + + + + + + + diff --git a/AssetStudioUtility/FMOD Studio API/fmod.cs b/AssetStudioUtility/FMOD Studio API/fmod.cs index 9f6fadc4..a7040f14 100644 --- a/AssetStudioUtility/FMOD Studio API/fmod.cs +++ b/AssetStudioUtility/FMOD Studio API/fmod.cs @@ -10,7 +10,10 @@ using System.Text; using System.Runtime.InteropServices; using System.Collections.Generic; + +#if NETFRAMEWORK using AssetStudio.PInvoke; +#endif namespace FMOD { @@ -749,10 +752,12 @@ FMOD System factory functions. Use this to create an FMOD System Instance. bel */ public struct Factory { +#if NETFRAMEWORK static Factory() { DllLoader.PreloadDll(VERSION.dll); } +#endif public static RESULT System_Create(out System system) { diff --git a/Texture2DDecoderWrapper/Texture2DDecoderWrapper.csproj b/Texture2DDecoderWrapper/Texture2DDecoderWrapper.csproj index 3686acfc..0091ff4a 100644 --- a/Texture2DDecoderWrapper/Texture2DDecoderWrapper.csproj +++ b/Texture2DDecoderWrapper/Texture2DDecoderWrapper.csproj @@ -1,9 +1,9 @@ - net472;net6.0;net7.0 + net472 true - 0.16.47.1 + 0.16.48.1 Copyright © Perfare 2020-2022; Copyright © hozuki 2020 embedded diff --git a/Texture2DDecoderWrapper/TextureDecoder.cs b/Texture2DDecoderWrapper/TextureDecoder.cs index 797b78f8..a3bcad24 100644 --- a/Texture2DDecoderWrapper/TextureDecoder.cs +++ b/Texture2DDecoderWrapper/TextureDecoder.cs @@ -1,16 +1,20 @@ using System; using System.Runtime.InteropServices; + +#if NETFRAMEWORK using AssetStudio.PInvoke; +#endif namespace Texture2DDecoder { public static unsafe partial class TextureDecoder { - +#if NETFRAMEWORK static TextureDecoder() { DllLoader.PreloadDll(T2DDll.DllName); } +#endif public static bool DecodeDXT1(byte[] data, int width, int height, byte[] image) {