[AK][CLI] Add support for sprites with an external alpha texture

- Added support for Arknights chararts sprites
- Added support for Arknights avg sprites
This commit is contained in:
VaDiM 2023-08-14 20:26:59 +03:00
parent abbd27fde7
commit 3d7d51b54f
7 changed files with 538 additions and 31 deletions

View File

@ -39,7 +39,12 @@ namespace AssetStudio
}
if (classIDTypes.Contains(ClassIDType.Sprite))
{
filteredAssetTypesList.Add(ClassIDType.Texture2D);
filteredAssetTypesList.UnionWith(new HashSet<ClassIDType>
{
ClassIDType.Texture2D,
ClassIDType.MonoBehaviour,
ClassIDType.MonoScript
});
}
filteredAssetTypesList.UnionWith(classIDTypes);

View File

@ -0,0 +1,151 @@
using AssetStudio;
using AssetStudioCLI;
using AssetStudioCLI.Options;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using System;
using System.IO;
namespace Arknights
{
internal static class AkSpriteHelper
{
public static Texture2D TryFindAlphaTex(AssetItem assetItem, AvgSprite avgSprite, bool isAvgSprite)
{
Sprite m_Sprite = (Sprite)assetItem.Asset;
var imgType = "arts/characters";
if (m_Sprite.m_RD.alphaTexture.m_PathID == 0)
{
if (isAvgSprite)
{
if (avgSprite?.FullAlphaTexture != null)
return avgSprite.FullAlphaTexture;
imgType = "avg/characters"; //since the avg hub was not found for some reason, let's try to find alpha tex by name
}
var spriteFullName = Path.GetFileNameWithoutExtension(assetItem.Container);
foreach (var item in Studio.loadedAssetsList)
{
if (item.Type == ClassIDType.Texture2D)
{
if (item.Container.Contains(imgType) && item.Container.Contains($"illust_{m_Sprite.m_Name}_material") && item.Text.Contains("[alpha]"))
return (Texture2D)item.Asset;
else if (item.Container.Contains(imgType) && item.Container.Contains(spriteFullName) && item.Text == $"{m_Sprite.m_Name}[alpha]")
return (Texture2D)item.Asset;
}
}
}
return null;
}
public static Image<Bgra32> AkGetImage(this Sprite m_Sprite, AvgSprite avgSprite = null, SpriteMaskMode spriteMaskMode = SpriteMaskMode.On)
{
if (m_Sprite.m_RD.texture.TryGet(out var m_Texture2D) && m_Sprite.m_RD.alphaTexture.TryGet(out var m_AlphaTexture2D) && spriteMaskMode != SpriteMaskMode.Off)
{
Image<Bgra32> tex;
Image<Bgra32> alphaTex;
if (avgSprite != null && avgSprite.IsHubParsed)
{
alphaTex = m_AlphaTexture2D.ConvertToImage(true);
if (avgSprite.IsFaceSprite)
{
var faceImage = m_Texture2D.ConvertToImage(true);
var faceAlpha = avgSprite.FaceSpriteAlphaTexture.ConvertToImage(true);
if (faceImage.Size() != avgSprite.FaceSize)
{
faceImage.Mutate(x => x.Resize(new ResizeOptions { Size = avgSprite.FaceSize, Sampler = KnownResamplers.Lanczos3, Mode = ResizeMode.Stretch }));
faceAlpha.Mutate(x => x.Resize(new ResizeOptions { Size = avgSprite.FaceSize, Sampler = KnownResamplers.Lanczos3, Mode = ResizeMode.Stretch }));
}
tex = avgSprite.FullTexture.ConvertToImage(true);
tex.Mutate(x => x.DrawImage(faceImage, location: avgSprite.FacePos, opacity: 1f));
alphaTex.Mutate(x => x.DrawImage(faceAlpha, location: avgSprite.FacePos, opacity: 1f));
}
else
{
tex = m_Texture2D.ConvertToImage(true);
}
}
else
{
tex = CutImage(m_Texture2D.ConvertToImage(false), m_Sprite.m_RD.textureRect, m_Sprite.m_RD.downscaleMultiplier);
alphaTex = CutImage(m_AlphaTexture2D.ConvertToImage(false), m_Sprite.m_RD.textureRect, m_Sprite.m_RD.downscaleMultiplier);
}
tex.ApplyRGBMask(alphaTex);
return tex;
}
else if (m_Sprite.m_RD.texture.TryGet(out m_Texture2D))
{
return CutImage(m_Texture2D.ConvertToImage(false), m_Sprite.m_RD.textureRect, m_Sprite.m_RD.downscaleMultiplier);
}
return null;
}
private static void ApplyRGBMask(this Image<Bgra32> tex, Image<Bgra32> texMask)
{
using (texMask)
{
bool resized = false;
if (tex.Width != texMask.Width || tex.Height != texMask.Height)
{
texMask.Mutate(x => x.Resize(tex.Width, tex.Height, CLIOptions.o_akAlphaMaskResampler.Value));
resized = true;
}
var invGamma = 1.0 / (1.0 + CLIOptions.o_akAlphaMaskGamma.Value / 10.0);
if (CLIOptions.akResizedOnly && !resized)
{
invGamma = 1.0;
}
tex.ProcessPixelRows(texMask, (sourceTex, targetTexMask) =>
{
for (int y = 0; y < texMask.Height; y++)
{
var texRow = sourceTex.GetRowSpan(y);
var maskRow = targetTexMask.GetRowSpan(y);
for (int x = 0; x < maskRow.Length; x++)
{
var grayscale = (maskRow[x].R + maskRow[x].G + maskRow[x].B) / 3.0;
if (invGamma != 1.0)
{
grayscale = 255 - Math.Pow((255 - grayscale) / 255, invGamma) * 255;
}
texRow[x].A = (byte)grayscale;
}
}
});
}
}
private static Image<Bgra32> CutImage(Image<Bgra32> originalImage, Rectf textureRect, float downscaleMultiplier)
{
if (originalImage != null)
{
using (originalImage)
{
if (downscaleMultiplier > 0f && downscaleMultiplier != 1f)
{
var newSize = (Size)(originalImage.Size() / downscaleMultiplier);
originalImage.Mutate(x => x.Resize(newSize, KnownResamplers.Lanczos3, compand: true));
}
var rectX = (int)Math.Floor(textureRect.x);
var rectY = (int)Math.Floor(textureRect.y);
var rectRight = (int)Math.Ceiling(textureRect.x + textureRect.width);
var rectBottom = (int)Math.Ceiling(textureRect.y + textureRect.height);
rectRight = Math.Min(rectRight, originalImage.Width);
rectBottom = Math.Min(rectBottom, originalImage.Height);
var rect = new Rectangle(rectX, rectY, rectRight - rectX, rectBottom - rectY);
var spriteImage = originalImage.Clone(x => x.Crop(rect));
spriteImage.Mutate(x => x.Flip(FlipMode.Vertical));
return spriteImage;
}
}
return null;
}
}
}

View File

@ -0,0 +1,125 @@
using Arknights.AvgCharHub;
using AssetStudio;
using AssetStudioCLI;
using SixLabors.ImageSharp;
using System.Linq;
using System;
using Newtonsoft.Json;
namespace Arknights
{
internal class AvgSprite
{
public Texture2D FaceSpriteAlphaTexture { get; }
public Texture2D FullTexture { get; }
public Texture2D FullAlphaTexture { get; }
public Point FacePos { get; }
public Size FaceSize { get; }
public string Alias { get; }
public bool IsWholeBodySprite { get; }
public bool IsFaceSprite { get; }
public bool IsHubParsed { get; }
private AvgSpriteConfig GetCurSpriteGroup(AvgSpriteConfigGroup spriteHubDataGrouped, long spriteItemID, string spriteName)
{
if (spriteHubDataGrouped.SpriteGroups.Length > 1)
{
if (!string.IsNullOrEmpty(spriteName))
{
var groupFromName = int.TryParse(spriteName?.Substring(spriteName.IndexOf('$') + 1, 1), out int groupIndex);
if (groupFromName)
{
return spriteHubDataGrouped.SpriteGroups[groupIndex - 1];
}
}
return spriteHubDataGrouped.SpriteGroups.FirstOrDefault(x => x.Sprites.Any(y => y.Sprite.m_PathID == spriteItemID));
}
else
{
return spriteHubDataGrouped.SpriteGroups[0];
}
}
private bool TryGetSpriteHub(AssetItem assetItem, out AvgSpriteConfig spriteHubData)
{
spriteHubData = null;
var avgSpriteHubItem = Studio.loadedAssetsList.Find(x =>
x.Type == ClassIDType.MonoBehaviour
&& x.Container == assetItem.Container
&& x.Text.IndexOf("AVGCharacterSpriteHub", StringComparison.OrdinalIgnoreCase) >= 0
);
if (avgSpriteHubItem == null)
{
Logger.Warning("AVGCharacterSpriteHub was not found.");
return false;
}
var spriteHubDict = ((MonoBehaviour)avgSpriteHubItem.Asset).ToType();
if (spriteHubDict == null)
{
Logger.Warning("AVGCharacterSpriteHub is not readable.");
return false;
}
var spriteHubJson = JsonConvert.SerializeObject(spriteHubDict);
if (avgSpriteHubItem.Text.ToLower().Contains("hubgroup"))
{
var groupedSpriteHub = JsonConvert.DeserializeObject<AvgSpriteConfigGroup>(spriteHubJson);
spriteHubData = GetCurSpriteGroup(groupedSpriteHub, assetItem.m_PathID, assetItem.Text);
}
else
{
spriteHubData = JsonConvert.DeserializeObject<AvgSpriteConfig>(spriteHubJson);
}
return true;
}
public AvgSprite(AssetItem assetItem)
{
if (TryGetSpriteHub(assetItem, out var spriteHubData))
{
IsHubParsed = spriteHubData?.Sprites.Length > 0;
}
if (IsHubParsed)
{
var curSpriteData = spriteHubData.Sprites.FirstOrDefault(x => x.Sprite.m_PathID == assetItem.m_PathID);
if (curSpriteData == null)
{
Logger.Warning($"Sprite \"{assetItem.Text}\" was not found in the avg sprite hub");
return;
}
Alias = curSpriteData.Alias;
IsWholeBodySprite = curSpriteData.IsWholeBody;
if (spriteHubData.FaceSize.X > 0 && spriteHubData.FaceSize.Y > 0)
{
var fullTexSpriteData = spriteHubData.Sprites.Last(); //Last sprite item in the list usually contains PathID of Sprite with full texture
if (IsWholeBodySprite || curSpriteData.Equals(fullTexSpriteData))
{
fullTexSpriteData = curSpriteData;
}
else
{
var faceAlphaID = curSpriteData.AlphaTex.m_PathID;
FaceSpriteAlphaTexture = (Texture2D)Studio.loadedAssetsList.Find(x => x.m_PathID == faceAlphaID).Asset;
}
var fullTexSpriteID = fullTexSpriteData.Sprite.m_PathID;
var fullTexAlphaID = fullTexSpriteData.AlphaTex.m_PathID;
var fullTexSprite = (Sprite)Studio.loadedAssetsList.Find(x => x.m_PathID == fullTexSpriteID).Asset;
FullTexture = fullTexSprite.m_RD.texture.TryGet(out var fullTex) ? fullTex : null;
FullAlphaTexture = (Texture2D)Studio.loadedAssetsList.Find(x => x.m_PathID == fullTexAlphaID).Asset;
FacePos = new Point((int)Math.Round(spriteHubData.FacePos.X), (int)Math.Round(spriteHubData.FacePos.Y));
FaceSize = new Size((int)Math.Round(spriteHubData.FaceSize.X), (int)Math.Round(spriteHubData.FaceSize.Y));
IsFaceSprite = assetItem.m_PathID != fullTexSpriteID;
}
else
{
FullAlphaTexture = (Texture2D)Studio.loadedAssetsList.Find(x => x.m_PathID == curSpriteData.AlphaTex.m_PathID).Asset;
}
}
}
}
}

View File

@ -0,0 +1,30 @@
using AssetStudio;
namespace Arknights.AvgCharHub
{
internal class AvgAssetIDs
{
public int m_FileID { get; set; }
public long m_PathID { get; set; }
}
internal class AvgSpriteData
{
public AvgAssetIDs Sprite { get; set; }
public AvgAssetIDs AlphaTex { get; set; }
public string Alias { get; set; }
public bool IsWholeBody { get; set; }
}
internal class AvgSpriteConfig
{
public AvgSpriteData[] Sprites { get; set; }
public Vector2 FaceSize { get; set; }
public Vector3 FacePos { get; set; }
}
internal class AvgSpriteConfigGroup
{
public AvgSpriteConfig[] SpriteGroups { get; set; }
}
}

View File

@ -1,6 +1,9 @@
using AssetStudio;
using Arknights;
using AssetStudio;
using AssetStudioCLI.Options;
using Newtonsoft.Json;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp;
using System.IO;
using System.Linq;
using System.Text;
@ -204,11 +207,56 @@ namespace AssetStudioCLI
public static bool ExportSprite(AssetItem item, string exportPath)
{
Image<Bgra32> image;
AvgSprite avgSprite = null;
var alias = "";
var m_Sprite = (Sprite)item.Asset;
var type = CLIOptions.o_imageFormat.Value;
var alphaMask = SpriteMaskMode.On;
if (!TryExportFile(exportPath, item, "." + type.ToString().ToLower(), out var exportFullPath))
var spriteMaskMode = CLIOptions.o_akSpriteMaskMode.Value != AkSpriteMaskMode.None ? SpriteMaskMode.Export : SpriteMaskMode.Off;
var isCharAvgSprite = item.Container.Contains("avg/characters");
var isCharArt = item.Container.Contains("arts/characters");
if (isCharAvgSprite)
{
avgSprite = new AvgSprite(item);
if (CLIOptions.f_akAddAliases.Value && !string.IsNullOrEmpty(avgSprite.Alias))
{
alias = $"_{avgSprite.Alias}";
}
}
if (!CLIOptions.f_akOriginalAvgNames.Value)
{
if ((m_Sprite.m_Name.Length < 3 && m_Sprite.m_Name.All(char.IsDigit)) //not grouped ("spriteIndex")
|| (m_Sprite.m_Name.Length < 5 && m_Sprite.m_Name.Contains('$') && m_Sprite.m_Name.Split('$')[0].All(char.IsDigit))) //grouped ("spriteIndex$groupIndex")
{
var fullName = Path.GetFileNameWithoutExtension(item.Container);
item.Text = $"{fullName}#{m_Sprite.m_Name}";
}
}
if (!TryExportFile(exportPath, item, "." + type.ToString().ToLower(), out var exportFullPath, alias))
return false;
var image = ((Sprite)item.Asset).GetImage(alphaMask);
if (CLIOptions.o_akSpriteMaskMode.Value == AkSpriteMaskMode.External && (isCharAvgSprite || isCharArt))
{
if (m_Sprite.m_RD.alphaTexture.IsNull)
{
var charAlphaAtlas = AkSpriteHelper.TryFindAlphaTex(item, avgSprite, isCharAvgSprite);
if (charAlphaAtlas != null)
{
m_Sprite.m_RD.alphaTexture.Set(charAlphaAtlas);
m_Sprite.akSplitAlpha = true;
}
}
image = m_Sprite.AkGetImage(avgSprite, spriteMaskMode);
}
else
{
image = m_Sprite.GetImage(spriteMaskMode);
}
if (image != null)
{
using (image)
@ -253,9 +301,9 @@ namespace AssetStudioCLI
return false;
}
private static bool TryExportFile(string dir, AssetItem item, string extension, out string fullPath)
private static bool TryExportFile(string dir, AssetItem item, string extension, out string fullPath, string alias = "")
{
var fileName = FixFileName(item.Text);
var fileName = FixFileName(item.Text) + alias;
fullPath = Path.Combine(dir, fileName + extension);
if (!File.Exists(fullPath))
{

View File

@ -1,4 +1,6 @@
using AssetStudio;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Transforms;
using System;
using System.Collections.Generic;
using System.IO;
@ -12,6 +14,7 @@ namespace AssetStudioCLI.Options
General,
Convert,
Logger,
Arknights,
Advanced,
}
@ -55,6 +58,13 @@ namespace AssetStudioCLI.Options
NameAndContainer,
}
internal enum AkSpriteMaskMode
{
None,
Internal,
External
}
internal static class CLIOptions
{
public static bool isParsed;
@ -79,6 +89,14 @@ namespace AssetStudioCLI.Options
public static bool convertTexture;
public static Option<ImageFormat> o_imageFormat;
public static Option<AudioFormat> o_audioFormat;
//arknights
public static bool akResizedOnly;
public static Option<AkSpriteMaskMode> o_akSpriteMaskMode;
public static Option<IResampler> o_akAlphaMaskResampler;
private static string resamplerName;
public static Option<int> o_akAlphaMaskGamma;
public static Option<bool> f_akOriginalAvgNames;
public static Option<bool> f_akAddAliases;
//advanced
public static Option<ExportListType> o_exportAssetList;
public static Option<List<string>> o_filterByName;
@ -249,6 +267,65 @@ namespace AssetStudioCLI.Options
);
#endregion
#region Arknights Options
akResizedOnly = true;
o_akSpriteMaskMode = new GroupedOption<AkSpriteMaskMode>
(
optionDefaultValue: AkSpriteMaskMode.External,
optionName: "--spritemask-mode <value>",
optionDescription: "Specify the mode in which you want to export sprites with alpha mask\n" +
"<Value: none | internalOnly | searchExternal(default)>\n" +
"None - Export sprites without alpha mask applied\n" +
"Internal - Export sprites with internal alpha mask applied (if exist)\n" +
"SearchExternal - Export sprites with internal alpha mask applied,\n" +
"and in case it doesn't exist, Studio will try to find an external alpha mask\n" +
"Example: \"--spritemask-mode internalOnly\"\n",
optionHelpGroup: HelpGroups.Arknights
);
o_akAlphaMaskResampler = new GroupedOption<IResampler>
(
optionDefaultValue: KnownResamplers.MitchellNetravali,
optionName: "--alphamask-resampler <value>",
optionDescription: "Specify the alpha mask upscale algorithm for 2048x2048 sprites\n" +
"<Value: nearest | bilinear | bicubic | mitchell(default) | spline | welch>\n" +
"Mitchell - Mitchell Netravali algorithm. Yields good equilibrium between \n" +
"sharpness and smoothness (produces less artifacts than bicubic in case of alpha masks)\n" +
"Spline - Similar to Mitchell Netravali but yielding smoother results\n" +
"Welch - A high speed algorithm that delivers very sharpened results\n" +
"Example: \"--alphamask-resampler bicubic\"\n",
optionHelpGroup: HelpGroups.Arknights
);
resamplerName = "Mitchell";
o_akAlphaMaskGamma = new GroupedOption<int>
(
optionDefaultValue: 2,
optionName: "--alphamask-gamma <value>",
optionDescription: "Specify the alpha mask gamma correction for 2048x2048 sprites\n" +
"<Value: integer number from -5 to 5 (default=2)>\n" +
"<0 - Make the alpha mask darker\n" +
"0 - Do not change the gamma of alpha mask\n" +
">0 - Make the alpha mask lighter\n" +
"Example: \"--alphamask-gamma 0\"\n",
optionHelpGroup: HelpGroups.Arknights
);
f_akOriginalAvgNames = new GroupedOption<bool>
(
optionDefaultValue: false,
optionName: "--original-avg-names",
optionDescription: "(Flag) If specified, names of avg sprites with faces will not be fixed\n",
optionHelpGroup: HelpGroups.Arknights,
isFlag: true
);
f_akAddAliases = new GroupedOption<bool>
(
optionDefaultValue: false,
optionName: "--add-aliases",
optionDescription: "(Flag) If specified, aliases will be added to avg sprite names (if exist)",
optionHelpGroup: HelpGroups.Arknights,
isFlag: true
);
#endregion
#region Init Advanced Options
o_exportAssetList = new GroupedOption<ExportListType>
(
@ -315,7 +392,7 @@ namespace AssetStudioCLI.Options
(
optionDefaultValue: false,
optionName: "--not-restore-extension",
optionDescription: "(Flag) If specified, AssetStudio will not try to use/restore original TextAsset\nextension name, and will just export all TextAssets with the \".txt\" extension",
optionDescription: "(Flag) If specified, Studio will not try to use/restore original TextAsset\nextension name, and will just export all TextAssets with the \".txt\" extension",
optionHelpGroup: HelpGroups.Advanced,
isFlag: true
);
@ -380,6 +457,14 @@ namespace AssetStudioCLI.Options
f_notRestoreExtensionName.Value = true;
resplittedArgs.RemoveAt(i);
break;
case "--original-avg-names":
f_akOriginalAvgNames.Value = true;
resplittedArgs.RemoveAt(i);
break;
case "--add-aliases":
f_akAddAliases.Value = true;
resplittedArgs.RemoveAt(i);
break;
}
}
#endregion
@ -617,6 +702,65 @@ namespace AssetStudioCLI.Options
return;
}
break;
case "--spritemask-mode":
switch (value.ToLower())
{
case "none":
o_akSpriteMaskMode.Value = AkSpriteMaskMode.None;
break;
case "internal":
o_akSpriteMaskMode.Value = AkSpriteMaskMode.Internal;
break;
case "searchexternal":
o_akSpriteMaskMode.Value = AkSpriteMaskMode.External;
break;
default:
Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unsupported sprite mask mode: [{value.Color(brightRed)}].\n");
Console.WriteLine(o_akSpriteMaskMode.Description);
return;
}
break;
case "--alphamask-resampler":
switch (value.ToLower())
{
case "nearest":
o_akAlphaMaskResampler.Value = KnownResamplers.NearestNeighbor;
break;
case "bilinear":
o_akAlphaMaskResampler.Value = KnownResamplers.Triangle;
break;
case "bicubic":
o_akAlphaMaskResampler.Value = KnownResamplers.Bicubic;
break;
case "mitchell":
o_akAlphaMaskResampler.Value = KnownResamplers.MitchellNetravali;
break;
case "spline":
o_akAlphaMaskResampler.Value = KnownResamplers.Spline;
break;
case "welch":
o_akAlphaMaskResampler.Value = KnownResamplers.Welch;
break;
default:
Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unsupported alpha mask resampler: [{value.Color(brightRed)}].\n");
Console.WriteLine(o_akAlphaMaskResampler.Description);
return;
}
resamplerName = value.ToLower();
break;
case "--alphamask-gamma":
var isInt = int.TryParse(value, out int intValue);
if (isInt && intValue >= -5 && intValue <= 5)
{
o_akAlphaMaskGamma.Value = intValue;
}
else
{
Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unsupported gamma correction value: [{value.Color(brightRed)}].\n");
Console.WriteLine(o_akAlphaMaskGamma.Description);
return;
}
break;
case "--export-asset-list":
switch (value.ToLower())
{
@ -815,6 +959,11 @@ namespace AssetStudioCLI.Options
sb.AppendLine($"# Asset Group Option: {o_groupAssetsBy}");
sb.AppendLine($"# Export Image Format: {o_imageFormat}");
sb.AppendLine($"# Export Audio Format: {o_audioFormat}");
sb.AppendLine($"# [Arkingths] Sprite Mode: {o_akSpriteMaskMode}");
sb.AppendLine($"# [Arknights] Mask Resampler: {resamplerName}");
sb.AppendLine($"# [Arknights] Mask Gamma Correction: {o_akAlphaMaskGamma.Value * 10:+#;-#;0}%");
sb.AppendLine($"# [Arknights] Original Avg Names: {f_akOriginalAvgNames}");
sb.AppendLine($"# [Arknights] Add Aliases: {f_akAddAliases}");
sb.AppendLine($"# Log Level: {o_logLevel}");
sb.AppendLine($"# Log Output: {o_logOutput}");
sb.AppendLine($"# Export Asset List: {o_exportAssetList}");

View File

@ -14,7 +14,8 @@ namespace AssetStudioCLI
internal static class Studio
{
public static AssetsManager assetsManager = new AssetsManager();
public static List<AssetItem> parsedAssetsList = new List<AssetItem>();
public static List<AssetItem> exportableAssetsList = new List<AssetItem>();
public static List<AssetItem> loadedAssetsList = new List<AssetItem>();
public static AssemblyLoader assemblyLoader = new AssemblyLoader();
private static Dictionary<AssetStudio.Object, string> containers = new Dictionary<AssetStudio.Object, string>();
@ -51,7 +52,6 @@ namespace AssetStudioCLI
{
Logger.Info("Parse assets...");
var fileAssetsList = new List<AssetItem>();
var objectCount = assetsManager.assetsFileList.Sum(x => x.Objects.Count);
Progress.Reset();
@ -131,29 +131,28 @@ namespace AssetStudioCLI
assetItem.Text = assetItem.TypeString + assetItem.UniqueID;
}
loadedAssetsList.Add(assetItem);
isExportable = CLIOptions.o_exportAssetTypes.Value.Contains(asset.type);
if (isExportable)
{
fileAssetsList.Add(assetItem);
exportableAssetsList.Add(assetItem);
}
Progress.Report(++i, objectCount);
}
foreach (var asset in fileAssetsList)
foreach (var asset in loadedAssetsList)
{
if (containers.ContainsKey(asset.Asset))
{
asset.Container = containers[asset.Asset];
}
}
parsedAssetsList.AddRange(fileAssetsList);
fileAssetsList.Clear();
if (CLIOptions.o_workMode.Value != WorkMode.ExportLive2D)
{
containers.Clear();
}
}
var log = $"Finished loading {assetsManager.assetsFileList.Count} files with {parsedAssetsList.Count} exportable assets";
var log = $"Finished loading {assetsManager.assetsFileList.Count} files with {exportableAssetsList.Count} exportable assets";
var unityVer = assetsManager.assetsFileList[0].version;
long m_ObjectsCount;
if (unityVer[0] > 2020)
@ -179,9 +178,9 @@ namespace AssetStudioCLI
{
var exportableAssetsCountDict = new Dictionary<ClassIDType, int>();
string info = "";
if (parsedAssetsList.Count > 0)
if (exportableAssetsList.Count > 0)
{
foreach (var asset in parsedAssetsList)
foreach (var asset in exportableAssetsList)
{
if (exportableAssetsCountDict.ContainsKey(asset.Type))
{
@ -200,7 +199,7 @@ namespace AssetStudioCLI
}
if (exportableAssetsCountDict.Count > 1)
{
info += $"#\n# Total: {parsedAssetsList.Count} assets";
info += $"#\n# Total: {exportableAssetsList.Count} assets";
}
}
else
@ -220,34 +219,34 @@ namespace AssetStudioCLI
public static void FilterAssets()
{
var assetsCount = parsedAssetsList.Count;
var assetsCount = exportableAssetsList.Count;
var filteredAssets = new List<AssetItem>();
switch(CLIOptions.filterBy)
{
case FilterBy.Name:
filteredAssets = parsedAssetsList.FindAll(x => CLIOptions.o_filterByName.Value.Any(y => x.Text.ToString().IndexOf(y, StringComparison.OrdinalIgnoreCase) >= 0));
filteredAssets = exportableAssetsList.FindAll(x => CLIOptions.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("\", \"", CLIOptions.o_filterByName.Value)}\"".Color(Ansi.BrightYellow)} in their Names."
);
break;
case FilterBy.Container:
filteredAssets = parsedAssetsList.FindAll(x => CLIOptions.o_filterByContainer.Value.Any(y => x.Container.ToString().IndexOf(y, StringComparison.OrdinalIgnoreCase) >= 0));
filteredAssets = exportableAssetsList.FindAll(x => CLIOptions.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("\", \"", CLIOptions.o_filterByContainer.Value)}\"".Color(Ansi.BrightYellow)} in their Containers."
);
break;
case FilterBy.PathID:
filteredAssets = parsedAssetsList.FindAll(x => CLIOptions.o_filterByPathID.Value.Any(y => x.m_PathID.ToString().IndexOf(y, StringComparison.OrdinalIgnoreCase) >= 0));
filteredAssets = exportableAssetsList.FindAll(x => CLIOptions.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("\", \"", CLIOptions.o_filterByPathID.Value)}\"".Color(Ansi.BrightYellow)} in their PathIDs."
);
break;
case FilterBy.NameOrContainer:
filteredAssets = parsedAssetsList.FindAll(x =>
filteredAssets = exportableAssetsList.FindAll(x =>
CLIOptions.o_filterByText.Value.Any(y => x.Text.ToString().IndexOf(y, StringComparison.OrdinalIgnoreCase) >= 0) ||
CLIOptions.o_filterByText.Value.Any(y => x.Container.ToString().IndexOf(y, StringComparison.OrdinalIgnoreCase) >= 0)
);
@ -257,7 +256,7 @@ namespace AssetStudioCLI
);
break;
case FilterBy.NameAndContainer:
filteredAssets = parsedAssetsList.FindAll(x =>
filteredAssets = exportableAssetsList.FindAll(x =>
CLIOptions.o_filterByName.Value.Any(y => x.Text.ToString().IndexOf(y, StringComparison.OrdinalIgnoreCase) >= 0) &&
CLIOptions.o_filterByContainer.Value.Any(y => x.Container.ToString().IndexOf(y, StringComparison.OrdinalIgnoreCase) >= 0)
);
@ -268,18 +267,18 @@ namespace AssetStudioCLI
);
break;
}
parsedAssetsList.Clear();
parsedAssetsList = filteredAssets;
exportableAssetsList.Clear();
exportableAssetsList = filteredAssets;
}
public static void ExportAssets()
{
var savePath = CLIOptions.o_outputFolder.Value;
var toExportCount = parsedAssetsList.Count;
var toExportCount = exportableAssetsList.Count;
var exportedCount = 0;
var groupOption = CLIOptions.o_groupAssetsBy.Value;
foreach (var asset in parsedAssetsList)
foreach (var asset in exportableAssetsList)
{
string exportPath;
switch (groupOption)
@ -384,7 +383,7 @@ namespace AssetStudioCLI
new XElement("Assets",
new XAttribute("filename", filename),
new XAttribute("createdAt", DateTime.UtcNow.ToString("s")),
parsedAssetsList.Select(
exportableAssetsList.Select(
asset => new XElement("Asset",
new XElement("Name", asset.Text),
new XElement("Container", asset.Container),
@ -400,7 +399,7 @@ namespace AssetStudioCLI
break;
}
Logger.Info($"Finished exporting asset list with {parsedAssetsList.Count} items.");
Logger.Info($"Finished exporting asset list with {exportableAssetsList.Count} items.");
}
public static void ExportLive2D()
@ -411,7 +410,7 @@ namespace AssetStudioCLI
Progress.Reset();
Logger.Info($"Searching for Live2D files...");
var cubismMocs = parsedAssetsList.Where(x =>
var cubismMocs = exportableAssetsList.Where(x =>
{
if (x.Type == ClassIDType.MonoBehaviour)
{