From 3d7d51b54ff026037ca7bdc14b958c86542e53b4 Mon Sep 17 00:00:00 2001 From: VaDiM Date: Mon, 14 Aug 2023 20:26:59 +0300 Subject: [PATCH] [AK][CLI] Add support for sprites with an external alpha texture - Added support for Arknights chararts sprites - Added support for Arknights avg sprites --- AssetStudio/AssetsManager.cs | 7 +- .../Components/Arknights/AkSpriteHelper.cs | 151 ++++++++++++++++++ .../Components/Arknights/AvgSprite.cs | 125 +++++++++++++++ .../Components/Arknights/AvgSpriteConfig.cs | 30 ++++ AssetStudioCLI/Exporter.cs | 60 ++++++- AssetStudioCLI/Options/CLIOptions.cs | 151 +++++++++++++++++- AssetStudioCLI/Studio.cs | 45 +++--- 7 files changed, 538 insertions(+), 31 deletions(-) create mode 100644 AssetStudioCLI/Components/Arknights/AkSpriteHelper.cs create mode 100644 AssetStudioCLI/Components/Arknights/AvgSprite.cs create mode 100644 AssetStudioCLI/Components/Arknights/AvgSpriteConfig.cs diff --git a/AssetStudio/AssetsManager.cs b/AssetStudio/AssetsManager.cs index 1bc3543..40e7527 100644 --- a/AssetStudio/AssetsManager.cs +++ b/AssetStudio/AssetsManager.cs @@ -39,7 +39,12 @@ namespace AssetStudio } if (classIDTypes.Contains(ClassIDType.Sprite)) { - filteredAssetTypesList.Add(ClassIDType.Texture2D); + filteredAssetTypesList.UnionWith(new HashSet + { + ClassIDType.Texture2D, + ClassIDType.MonoBehaviour, + ClassIDType.MonoScript + }); } filteredAssetTypesList.UnionWith(classIDTypes); diff --git a/AssetStudioCLI/Components/Arknights/AkSpriteHelper.cs b/AssetStudioCLI/Components/Arknights/AkSpriteHelper.cs new file mode 100644 index 0000000..fec1f3e --- /dev/null +++ b/AssetStudioCLI/Components/Arknights/AkSpriteHelper.cs @@ -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 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 tex; + Image 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 tex, Image 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 CutImage(Image 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; + } + } +} diff --git a/AssetStudioCLI/Components/Arknights/AvgSprite.cs b/AssetStudioCLI/Components/Arknights/AvgSprite.cs new file mode 100644 index 0000000..906afbc --- /dev/null +++ b/AssetStudioCLI/Components/Arknights/AvgSprite.cs @@ -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(spriteHubJson); + spriteHubData = GetCurSpriteGroup(groupedSpriteHub, assetItem.m_PathID, assetItem.Text); + } + else + { + spriteHubData = JsonConvert.DeserializeObject(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; + } + } + } + } +} diff --git a/AssetStudioCLI/Components/Arknights/AvgSpriteConfig.cs b/AssetStudioCLI/Components/Arknights/AvgSpriteConfig.cs new file mode 100644 index 0000000..d834743 --- /dev/null +++ b/AssetStudioCLI/Components/Arknights/AvgSpriteConfig.cs @@ -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; } + } +} diff --git a/AssetStudioCLI/Exporter.cs b/AssetStudioCLI/Exporter.cs index 77cc99a..b48d6f9 100644 --- a/AssetStudioCLI/Exporter.cs +++ b/AssetStudioCLI/Exporter.cs @@ -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 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)) { diff --git a/AssetStudioCLI/Options/CLIOptions.cs b/AssetStudioCLI/Options/CLIOptions.cs index b1d87a6..13ad51a 100644 --- a/AssetStudioCLI/Options/CLIOptions.cs +++ b/AssetStudioCLI/Options/CLIOptions.cs @@ -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 o_imageFormat; public static Option o_audioFormat; + //arknights + public static bool akResizedOnly; + public static Option o_akSpriteMaskMode; + public static Option o_akAlphaMaskResampler; + private static string resamplerName; + public static Option o_akAlphaMaskGamma; + public static Option f_akOriginalAvgNames; + public static Option f_akAddAliases; //advanced public static Option o_exportAssetList; public static Option> o_filterByName; @@ -249,6 +267,65 @@ namespace AssetStudioCLI.Options ); #endregion + #region Arknights Options + akResizedOnly = true; + o_akSpriteMaskMode = new GroupedOption + ( + optionDefaultValue: AkSpriteMaskMode.External, + optionName: "--spritemask-mode ", + optionDescription: "Specify the mode in which you want to export sprites with alpha mask\n" + + "\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 + ( + optionDefaultValue: KnownResamplers.MitchellNetravali, + optionName: "--alphamask-resampler ", + optionDescription: "Specify the alpha mask upscale algorithm for 2048x2048 sprites\n" + + "\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 + ( + optionDefaultValue: 2, + optionName: "--alphamask-gamma ", + optionDescription: "Specify the alpha mask gamma correction for 2048x2048 sprites\n" + + "\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 + ( + 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 + ( + 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 ( @@ -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}"); diff --git a/AssetStudioCLI/Studio.cs b/AssetStudioCLI/Studio.cs index 6c56b15..d7efccd 100644 --- a/AssetStudioCLI/Studio.cs +++ b/AssetStudioCLI/Studio.cs @@ -14,7 +14,8 @@ namespace AssetStudioCLI internal static class Studio { public static AssetsManager assetsManager = new AssetsManager(); - public static List parsedAssetsList = new List(); + public static List exportableAssetsList = new List(); + public static List loadedAssetsList = new List(); public static AssemblyLoader assemblyLoader = new AssemblyLoader(); private static Dictionary containers = new Dictionary(); @@ -51,7 +52,6 @@ namespace AssetStudioCLI { Logger.Info("Parse assets..."); - var fileAssetsList = new List(); 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(); 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(); 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) {