mirror of
https://github.com/aelurum/AssetStudio.git
synced 2026-08-26 02:46:59 -04:00
Add AssetStudioCLI project
- Updated projects and dependencies
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>net472;net6.0;net7.0</TargetFrameworks>
|
||||
<AssemblyTitle>AssetStudio Mod by VaDiM</AssemblyTitle>
|
||||
<Version>0.16.48.1</Version>
|
||||
<Copyright>Copyright © Perfare; Copyright © aelurum 2023</Copyright>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\AssetStudioUtility\AssetStudioUtility.csproj" />
|
||||
<ProjectReference Include="..\AssetStudio\AssetStudio.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Use local compiled win-x86 and win-x64 Texture2DDecoder libs, because libs from Kyaru.Texture2DDecoder.Windows were compiled with /MD flag -->
|
||||
|
||||
<Target Name="CopyExtraFilesPortable" AfterTargets="AfterBuild" Condition=" '$(RuntimeIdentifier)' == '' ">
|
||||
<Message Text="Copying extra files for $(TargetFramework)... " Importance="high" />
|
||||
<Copy SourceFiles="$(SolutionDir)Texture2DDecoderNative\bin\Win32\$(Configuration)\Texture2DDecoderNative.dll" DestinationFolder="$(TargetDir)runtimes\win-x86\native" ContinueOnError="false" />
|
||||
<Copy SourceFiles="$(SolutionDir)Texture2DDecoderNative\bin\x64\$(Configuration)\Texture2DDecoderNative.dll" DestinationFolder="$(TargetDir)runtimes\win-x64\native" ContinueOnError="false" />
|
||||
<Copy SourceFiles="$(ProjectDir)Libraries\win-x86\fmod.dll" DestinationFolder="$(TargetDir)runtimes\win-x86\native" ContinueOnError="false" />
|
||||
<Copy SourceFiles="$(ProjectDir)Libraries\win-x64\fmod.dll" DestinationFolder="$(TargetDir)runtimes\win-x64\native" ContinueOnError="false" />
|
||||
</Target>
|
||||
|
||||
<Target Name="CopyExtraFilesPortableNet" AfterTargets="AfterBuild" Condition=" '$(RuntimeIdentifier)' == '' AND '$(TargetFramework)' != 'net472' ">
|
||||
<Message Text="Copying extra files for .NET ($(TargetFramework))... " Importance="high" />
|
||||
<Copy SourceFiles="$(ProjectDir)Libraries\linux-x86\libfmod.so" DestinationFolder="$(TargetDir)runtimes\linux-x86\native" ContinueOnError="false" />
|
||||
<Copy SourceFiles="$(ProjectDir)Libraries\linux-x64\libfmod.so" DestinationFolder="$(TargetDir)runtimes\linux-x64\native" ContinueOnError="false" />
|
||||
<Copy SourceFiles="$(ProjectDir)Libraries\osx-x64\libfmod.dylib" DestinationFolder="$(TargetDir)runtimes\osx-x64\native" ContinueOnError="false" />
|
||||
<Copy SourceFiles="$(ProjectDir)Libraries\osx-arm64\libfmod.dylib" DestinationFolder="$(TargetDir)runtimes\osx-arm64\native" ContinueOnError="false" />
|
||||
</Target>
|
||||
|
||||
<Target Name="PublishExtraFilesPortable" AfterTargets="Publish" Condition=" '$(RuntimeIdentifier)' == '' ">
|
||||
<Message Text="Publishing extra files for Portable build ($(TargetFramework))... " Importance="high" />
|
||||
<Copy SourceFiles="$(TargetDir)runtimes\win-x86\native\Texture2DDecoderNative.dll" DestinationFolder="$(PublishDir)runtimes\win-x86\native" ContinueOnError="false" />
|
||||
<Copy SourceFiles="$(TargetDir)runtimes\win-x64\native\Texture2DDecoderNative.dll" DestinationFolder="$(PublishDir)runtimes\win-x64\native" ContinueOnError="false" />
|
||||
<Copy SourceFiles="$(TargetDir)runtimes\win-x86\native\fmod.dll" DestinationFolder="$(PublishDir)runtimes\win-x86\native" ContinueOnError="false" />
|
||||
<Copy SourceFiles="$(TargetDir)runtimes\win-x64\native\fmod.dll" DestinationFolder="$(PublishDir)runtimes\win-x64\native" ContinueOnError="false" />
|
||||
</Target>
|
||||
|
||||
<Target Name="PublishExtraFilesPortableNet" AfterTargets="Publish" Condition=" '$(RuntimeIdentifier)' == '' AND '$(TargetFramework)' != 'net472' ">
|
||||
<Message Text="Publishing extra files for Portable .NET build ($(TargetFramework))... " Importance="high" />
|
||||
<Copy SourceFiles="$(TargetDir)runtimes\linux-x86\native\libfmod.so" DestinationFolder="$(PublishDir)runtimes\linux-x86\native" ContinueOnError="false" />
|
||||
<Copy SourceFiles="$(TargetDir)runtimes\linux-x64\native\libfmod.so" DestinationFolder="$(PublishDir)runtimes\linux-x64\native" ContinueOnError="false" />
|
||||
<Copy SourceFiles="$(TargetDir)runtimes\osx-x64\native\libfmod.dylib" DestinationFolder="$(PublishDir)runtimes\osx-x64\native" ContinueOnError="false" />
|
||||
<Copy SourceFiles="$(TargetDir)runtimes\osx-arm64\native\libfmod.dylib" DestinationFolder="$(PublishDir)runtimes\osx-arm64\native" ContinueOnError="false" />
|
||||
</Target>
|
||||
|
||||
<Target Name="CopyExtraFilesWin86" AfterTargets="AfterBuild" Condition=" '$(RuntimeIdentifier)' == 'win-x86' ">
|
||||
<Message Text="Copying extra files for $(RuntimeIdentifier)($(TargetFramework))... " Importance="high" />
|
||||
<Copy SourceFiles="$(SolutionDir)Texture2DDecoderNative\bin\Win32\$(Configuration)\Texture2DDecoderNative.dll" DestinationFolder="$(TargetDir)" ContinueOnError="false" />
|
||||
<Copy SourceFiles="$$(ProjectDir)Libraries\win-x86\fmod.dll" DestinationFolder="$(TargetDir)" ContinueOnError="false" />
|
||||
</Target>
|
||||
|
||||
<Target Name="CopyExtraFilesWin64" AfterTargets="AfterBuild" Condition=" '$(RuntimeIdentifier)' == 'win-x64' ">
|
||||
<Message Text="Copying extra files for $(RuntimeIdentifier)($(TargetFramework))... " Importance="high" />
|
||||
<Copy SourceFiles="$(SolutionDir)Texture2DDecoderNative\bin\x64\$(Configuration)\Texture2DDecoderNative.dll" DestinationFolder="$(TargetDir)" ContinueOnError="false" />
|
||||
<Copy SourceFiles="$(ProjectDir)Libraries\win-x64\fmod.dll" DestinationFolder="$(TargetDir)" ContinueOnError="false" />
|
||||
</Target>
|
||||
|
||||
<Target Name="PublishExtraFilesWin" AfterTargets="Publish" Condition=" $(RuntimeIdentifier.Contains('win-x')) ">
|
||||
<Message Text="Publishing extra files for $(RuntimeIdentifier)($(TargetFramework))... " Importance="high" />
|
||||
<Copy SourceFiles="$(TargetDir)\Texture2DDecoderNative.dll" DestinationFolder="$(PublishDir)" ContinueOnError="false" />
|
||||
<Copy SourceFiles="$(TargetDir)\fmod.dll" DestinationFolder="$(PublishDir)" ContinueOnError="false" />
|
||||
</Target>
|
||||
|
||||
<Target Name="CopyExtraFilesLinux64" AfterTargets="AfterBuild" Condition=" '$(RuntimeIdentifier)' == 'linux-x64' ">
|
||||
<Message Text="Copying extra files for $(RuntimeIdentifier)($(TargetFramework))... " Importance="high" />
|
||||
<Copy SourceFiles="$(ProjectDir)Libraries\linux-x64\libfmod.so" DestinationFolder="$(TargetDir)" ContinueOnError="false" />
|
||||
</Target>
|
||||
|
||||
<Target Name="PublishExtraFilesLinux64" AfterTargets="Publish" Condition=" '$(RuntimeIdentifier)' == 'linux-x64' ">
|
||||
<Message Text="Publishing extra files for $(RuntimeIdentifier)($(TargetFramework))... " Importance="high" />
|
||||
<Copy SourceFiles="$(TargetDir)\libfmod.so" DestinationFolder="$(PublishDir)" ContinueOnError="false" />
|
||||
</Target>
|
||||
|
||||
<Target Name="CopyExtraFilesMac64" AfterTargets="AfterBuild" Condition=" '$(RuntimeIdentifier)' == 'osx-x64' ">
|
||||
<Message Text="Copying extra files for $(RuntimeIdentifier)($(TargetFramework))... " Importance="high" />
|
||||
<Copy SourceFiles="$(ProjectDir)Libraries\osx-x64\libfmod.dylib" DestinationFolder="$(TargetDir)" ContinueOnError="false" />
|
||||
</Target>
|
||||
|
||||
<Target Name="CopyExtraFilesMacArm64" AfterTargets="AfterBuild" Condition=" '$(RuntimeIdentifier)' == 'osx-arm64' ">
|
||||
<Message Text="Copying extra files for $(RuntimeIdentifier)($(TargetFramework))... " Importance="high" />
|
||||
<Copy SourceFiles="$(ProjectDir)Libraries\osx-arm64\libfmod.dylib" DestinationFolder="$(TargetDir)" ContinueOnError="false" />
|
||||
</Target>
|
||||
|
||||
<Target Name="PublishExtraFilesMac" AfterTargets="Publish" Condition=" $(RuntimeIdentifier.Contains('osx-')) ">
|
||||
<Message Text="Publishing extra files for $(RuntimeIdentifier)($(TargetFramework))... " Importance="high" />
|
||||
<Copy SourceFiles="$(TargetDir)\libfmod.dylib" DestinationFolder="$(PublishDir)" ContinueOnError="false" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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, '_'));
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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<T> : Option<T>
|
||||
{
|
||||
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<string, string> optionsDict;
|
||||
private static Dictionary<string, string> flagsDict;
|
||||
private static Dictionary<HelpGroups, Dictionary<string, string>> optionGroups;
|
||||
private List<ClassIDType> supportedAssetTypes;
|
||||
//general
|
||||
public Option<WorkMode> o_workMode;
|
||||
public Option<List<ClassIDType>> o_exportAssetTypes;
|
||||
public Option<AssetGroupOption> o_groupAssetsBy;
|
||||
public Option<string> o_outputFolder;
|
||||
public Option<bool> o_displayHelp;
|
||||
//logger
|
||||
public Option<LoggerEvent> o_logLevel;
|
||||
public Option<LogOutputMode> o_logOutput;
|
||||
//convert
|
||||
public bool convertTexture;
|
||||
public Option<ImageFormat> o_imageFormat;
|
||||
public Option<AudioFormat> o_audioFormat;
|
||||
//advanced
|
||||
public Option<ExportListType> o_exportAssetList;
|
||||
public Option<List<string>> o_filterByName;
|
||||
public Option<List<string>> o_filterByContainer;
|
||||
public Option<List<string>> o_filterByPathID;
|
||||
public Option<List<string>> o_filterByText;
|
||||
public Option<string> o_assemblyPath;
|
||||
public Option<string> o_unityVersion;
|
||||
public Option<bool> 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<string, string>();
|
||||
flagsDict = new Dictionary<string, string>();
|
||||
optionGroups = new Dictionary<HelpGroups, Dictionary<string, string>>();
|
||||
supportedAssetTypes = new List<ClassIDType>
|
||||
{
|
||||
ClassIDType.Texture2D,
|
||||
ClassIDType.Sprite,
|
||||
ClassIDType.TextAsset,
|
||||
ClassIDType.MonoBehaviour,
|
||||
ClassIDType.Font,
|
||||
ClassIDType.Shader,
|
||||
ClassIDType.AudioClip,
|
||||
ClassIDType.VideoClip,
|
||||
};
|
||||
|
||||
#region Init General Options
|
||||
o_workMode = new GroupedOption<WorkMode>
|
||||
(
|
||||
optionDefaultValue: WorkMode.Export,
|
||||
optionName: "-m, --mode <value>",
|
||||
optionDescription: "Specify working mode\n" +
|
||||
"<Value: export(default) | exportRaw | dump | info>\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<List<ClassIDType>>
|
||||
(
|
||||
optionDefaultValue: supportedAssetTypes,
|
||||
optionName: "-t, --asset-type <value(s)>",
|
||||
optionDescription: "Specify asset type(s) to export\n" +
|
||||
"<Value(s): tex2d, sprite, textAsset, monoBehaviour, font, shader, \naudio, video | all(default)>\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<AssetGroupOption>
|
||||
(
|
||||
optionDefaultValue: AssetGroupOption.ContainerPath,
|
||||
optionName: "-g, --group-option <value>",
|
||||
optionDescription: "Specify the way in which exported assets should be grouped\n" +
|
||||
"<Value: none | type | container(default) | filename>\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<string>
|
||||
(
|
||||
optionDefaultValue: "",
|
||||
optionName: "-o, --output <path>",
|
||||
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<bool>
|
||||
(
|
||||
optionDefaultValue: false,
|
||||
optionName: "-h, --help",
|
||||
optionDescription: "Display help and exit",
|
||||
optionHelpGroup: HelpGroups.General
|
||||
);
|
||||
#endregion
|
||||
|
||||
#region Init Logger Options
|
||||
o_logLevel = new GroupedOption<LoggerEvent>
|
||||
(
|
||||
optionDefaultValue: LoggerEvent.Info,
|
||||
optionName: "--log-level <value>",
|
||||
optionDescription: "Specify the log level\n" +
|
||||
"<Value: verbose | debug | info(default) | warning | error>\n" +
|
||||
"Example: \"--log-level warning\"\n",
|
||||
optionHelpGroup: HelpGroups.Logger
|
||||
);
|
||||
o_logOutput = new GroupedOption<LogOutputMode>
|
||||
(
|
||||
optionDefaultValue: LogOutputMode.Console,
|
||||
optionName: "--log-output <value>",
|
||||
optionDescription: "Specify the log output\n" +
|
||||
"<Value: console(default) | file | both>\n" +
|
||||
"Example: \"--log-output both\"",
|
||||
optionHelpGroup: HelpGroups.Logger
|
||||
);
|
||||
#endregion
|
||||
|
||||
#region Init Convert Options
|
||||
convertTexture = true;
|
||||
o_imageFormat = new GroupedOption<ImageFormat>
|
||||
(
|
||||
optionDefaultValue: ImageFormat.Png,
|
||||
optionName: "--image-format <value>",
|
||||
optionDescription: "Specify the format for converting image assets\n" +
|
||||
"<Value: none | jpg | png(default) | bmp | tga | webp>\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<AudioFormat>
|
||||
(
|
||||
optionDefaultValue: AudioFormat.Wav,
|
||||
optionName: "--audio-format <value>",
|
||||
optionDescription: "Specify the format for converting audio assets\n" +
|
||||
"<Value: none | wav(default)>\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<ExportListType>
|
||||
(
|
||||
optionDefaultValue: ExportListType.None,
|
||||
optionName: "--export-asset-list <value>",
|
||||
optionDescription: "Specify the format in which you want to export asset list\n" +
|
||||
"<Value: none(default) | xml>\n" +
|
||||
"None - Do not export asset list\n" +
|
||||
"Example: \"--export-asset-list xml\"\n",
|
||||
optionHelpGroup: HelpGroups.Advanced
|
||||
);
|
||||
o_filterByName = new GroupedOption<List<string>>
|
||||
(
|
||||
optionDefaultValue: new List<string>(),
|
||||
optionName: "--filter-by-name <text>",
|
||||
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<List<string>>
|
||||
(
|
||||
optionDefaultValue: new List<string>(),
|
||||
optionName: "--filter-by-container <text>",
|
||||
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<List<string>>
|
||||
(
|
||||
optionDefaultValue: new List<string>(),
|
||||
optionName: "--filter-by-pathid <text>",
|
||||
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<List<string>>
|
||||
(
|
||||
optionDefaultValue: new List<string>(),
|
||||
optionName: "--filter-by-text <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<string>
|
||||
(
|
||||
optionDefaultValue: "",
|
||||
optionName: "--assembly-folder <path>",
|
||||
optionDescription: "Specify the path to the assembly folder",
|
||||
optionHelpGroup: HelpGroups.Advanced
|
||||
);
|
||||
o_unityVersion = new GroupedOption<string>
|
||||
(
|
||||
optionDefaultValue: "",
|
||||
optionName: "--unity-version <text>",
|
||||
optionDescription: "Specify Unity version. Example: \"--unity-version 2017.4.39f1\"",
|
||||
optionHelpGroup: HelpGroups.Advanced
|
||||
);
|
||||
f_notRestoreExtensionName = new GroupedOption<bool>
|
||||
(
|
||||
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<string, string>() { { 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<string>();
|
||||
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<ClassIDType>();
|
||||
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<string, string> 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} <input path to asset file/folder> ");
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace AssetStudioCLI.Options
|
||||
{
|
||||
internal class Option<T>
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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---");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<AssetItem> parsedAssetsList = new List<AssetItem>();
|
||||
private readonly CLIOptions options;
|
||||
|
||||
public Studio(CLIOptions cliOptions)
|
||||
{
|
||||
Progress.Default = new Progress<int>(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<AssetItem>();
|
||||
var containers = new Dictionary<AssetStudio.Object, string>();
|
||||
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<ClassIDType, int>();
|
||||
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<AssetItem>();
|
||||
|
||||
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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user