mirror of
https://github.com/aelurum/AssetStudio.git
synced 2025-05-25 05:40:21 -04:00
* [GUI] - "container path full (with name)" * [CLI] - "containerFull" (https://github.com/Perfare/AssetStudio/issues/815)
390 lines
17 KiB
C#
390 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);
|
|
|
|
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 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.");
|
|
}
|
|
}
|
|
}
|