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)
{