AssetStudio/AssetStudioCLI/Studio.cs
VaDiM 2f8f57c1a6 [GUI] Improve file and folder loading (drag&drop)
- Added support for drag&drop of multiple folders
- Open/Export dialog can now also use a path taken from drag&drop items
2023-05-29 05:14:50 +03:00

383 lines
17 KiB
C#

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;
assetsManager.SetAssetFilter(options.o_exportAssetTypes.Value);
assetsManager.LoadFilesAndFolders(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 MovieTexture _:
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>();
string info = "";
if (parsedAssetsList.Count > 0)
{
foreach (var asset in parsedAssetsList)
{
if (exportableAssetsCountDict.ContainsKey(asset.Type))
{
exportableAssetsCountDict[asset.Type] += 1;
}
else
{
exportableAssetsCountDict.Add(asset.Type, 1);
}
}
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";
}
}
else
{
info += "No exportable assets found.";
}
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;
var groupOption = options.o_groupAssetsBy.Value;
foreach (var asset in parsedAssetsList)
{
string exportPath;
switch (groupOption)
{
case AssetGroupOption.TypeName:
exportPath = Path.Combine(savePath, asset.TypeString);
break;
case AssetGroupOption.ContainerPath:
case AssetGroupOption.ContainerPathFull:
if (!string.IsNullOrEmpty(asset.Container))
{
exportPath = Path.Combine(savePath, Path.GetDirectoryName(asset.Container));
if (groupOption == AssetGroupOption.ContainerPathFull)
{
exportPath = Path.Combine(exportPath, Path.GetFileNameWithoutExtension(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.Default.Log(LoggerEvent.Info, "Nothing exported.", ignoreLevel: true);
}
else if (toExportCount > exportedCount)
{
Logger.Default.Log(LoggerEvent.Info, $"Finished exporting {exportedCount} asset(s) to \"{options.o_outputFolder.Value.Color(Ansi.BrightYellow)}\".", ignoreLevel: true);
}
else
{
Logger.Default.Log(LoggerEvent.Info, $"Finished exporting {exportedCount} asset(s) to \"{options.o_outputFolder.Value.Color(Ansi.BrightGreen)}\".", ignoreLevel: true);
}
if (toExportCount > exportedCount)
{
Logger.Default.Log(LoggerEvent.Info, $"{toExportCount - exportedCount} asset(s) skipped (not extractable or file(s) already exist).", ignoreLevel: true);
}
}
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.");
}
}
}