46 Commits

Author SHA1 Message Date
VaDiM
87151e065b [CLI] Fix for portrait parsing 2025-06-30 00:15:39 +03:00
VaDiM
f3a748ba6b Fix for portrait parsing 2025-06-30 00:03:25 +03:00
VaDiM
d07fc6d6f5 Fix for avg sprites 2025-04-07 16:03:20 +03:00
VaDiM
ee2976aaf1 Update version & dependencies 2025-04-07 13:32:17 +03:00
VaDiM
f144037bc0 Fix for new compression type 2025-04-07 13:23:04 +03:00
VaDiM
0e097bda04 Update README.md 2024-05-01 14:25:55 +03:00
VaDiM
9686eee3b7 Fix for 512x512 avg sprites
temporarily?
2024-04-11 22:30:42 +03:00
VaDiM
b6318e6d9b Fix for empty alpha tex 2024-01-09 11:18:06 +03:00
VaDiM
e0d90d1b1e Update AssetStudioGUI.csproj 2023-12-16 23:17:56 +03:00
VaDiM
dd727758a8 Update ver to v1.1.0 2023-12-16 23:11:27 +03:00
VaDiM
d44ed315e3 Merge branch 'AssetStudioMod' into ArknightsStudio 2023-12-16 22:57:32 +03:00
VaDiM
a00c857ac3 Update version to v0.17.4 2023-12-16 04:41:00 +03:00
VaDiM
5333757843 Update dependencies 2023-12-16 04:16:36 +03:00
VaDiM
4747687bec Update README.md 2023-12-15 15:11:48 +03:00
VaDiM
5c2ac1a5e8 Add .NET8 to the TargetFrameworks 2023-12-15 14:45:07 +03:00
VaDiM
e2eae53ac0 Update Live2DExtractor.cs 2023-12-15 01:24:54 +03:00
VaDiM
87347e8b60 [CLI] Update readme 2023-12-11 01:58:05 +03:00
VaDiM
0fdbddea55 [CLI] Fix asset filter for sprites 2023-12-11 01:58:00 +03:00
VaDiM
823190abb7 Improve parsing of Live2D Fade motions 2023-12-09 02:31:54 +03:00
VaDiM
60aef1b8ed [GUI] Add option to not build a tree structure 2023-12-09 02:25:59 +03:00
VaDiM
f82a73f018 [GUI] Add console logger 2023-12-06 17:28:10 +03:00
VaDiM
d42a1879ab [GUI] Add support for Drag&Drop files from a browser
Co-Authored-By: Luke <17146677+lukefz@users.noreply.github.com>
2023-12-05 00:38:24 +03:00
VaDiM
632e5f8d08 [GUI] Add support for .lnk files via Drag&Drop. Close #13 2023-12-04 04:10:49 +03:00
VaDiM
51d259464b Merge branch 'pr/16' into AssetStudioMod 2023-11-21 02:15:19 +03:00
VaDiM
efd06648ad Add support for separate PreloadData
https://github.com/Perfare/AssetStudio/issues/690

https://docs.unity.cn/550/Documentation/Manual/AssetBundleInternalStructure.html
2023-11-21 01:35:25 +03:00
VaDiM
b27482e22b Don't display SourceRevisionId in titles
https://learn.microsoft.com/en-us/dotnet/core/compatibility/sdk/8.0/source-link
2023-11-21 00:31:09 +03:00
VaDiM
d572bd0e64 Add support for Live2D Fade motions 2023-11-20 19:56:41 +03:00
VaDiM
e415740373 Some fixes for Live2D export 2023-11-19 02:51:48 +03:00
VaDiM
e30b9e9e89 Fix bug with some avg sprites
- Changed face sprite detection method. We can't rely on the "IsWholeBody" flag
2023-11-08 13:57:57 +03:00
VaDiM
45bf9251c9 Fix crash when SpriteAtlas is missing 2023-11-07 01:31:27 +03:00
VaDiM
9632e88115 Update version to v1.0.1 2023-11-01 06:49:36 +03:00
VaDiM
bb19dd5019 Add support for avg sprite without separate alpha tex 2023-11-01 06:49:36 +03:00
Evaldas Ciakas
75ebe67713 Fix parsing secondary textures 2023-11-01 02:29:06 +02:00
VaDiM
ed7b0a2415 Unity 2022.2+ AnimationClip fix 2023-09-27 23:41:20 +03:00
VaDiM
22ab5c0633 Update README.md
- Added ArknightsStudio link
2023-09-24 16:19:09 +03:00
VaDiM
a2bc935850 [CLI] Improve help message 2023-09-24 16:14:26 +03:00
VaDiM
b6c6ceba1c Update CHANGELOG.md 2023-09-24 15:41:47 +03:00
VaDiM
7c0a6375b1 Update readme files 2023-09-24 15:41:21 +03:00
VaDiM
5c489c5f83 Merge branch 'AssetStudioMod' into ArknightsStudio 2023-09-24 15:40:23 +03:00
VaDiM
cb84c137e5 Add exception checking for gzip decompression 2023-09-24 10:41:19 +03:00
VaDiM
25c611fb9e Update README.md 2023-09-15 16:35:47 +03:00
VaDiM
e90af43459 Merge branch 'AssetStudioMod' into ArknightsStudio 2023-08-28 02:28:14 +03:00
VaDiM
381a7d89ae [AK][CLI] Add support for portrait sprites 2023-08-25 01:15:46 +03:00
VaDiM
572e3bf0d6 [AK][GUI] Add support for portrait sprites 2023-08-24 05:50:50 +03:00
VaDiM
3d7d51b54f [AK][CLI] Add support for sprites with an external alpha texture
- Added support for Arknights chararts sprites
- Added support for Arknights avg sprites
2023-08-17 01:22:16 +03:00
VaDiM
abbd27fde7 [AK][GUI] Add support for sprites with an external alpha texture
- Added support for Arknights chararts sprites
- Added support for Arknights avg sprites
2023-08-14 20:25:48 +03:00
60 changed files with 3715 additions and 711 deletions

View File

@@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net472;net6.0;net6.0-windows;net7.0;net7.0-windows</TargetFrameworks>
<TargetFrameworks>net472;net6.0;net6.0-windows;net7.0;net7.0-windows;net8.0;net8.0-windows</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Version>0.17.3.0</Version>
<Version>1.2.0</Version>
<Copyright>Copyright © Perfare 2020-2022; Copyright © hozuki 2020</Copyright>
<DebugType>embedded</DebugType>
</PropertyGroup>

View File

@@ -1,20 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net472;net6.0;net6.0-windows;net7.0;net7.0-windows</TargetFrameworks>
<Version>0.17.3.0</Version>
<Copyright>Copyright © Perfare 2018-2022; Copyright © aelurum 2023</Copyright>
<TargetFrameworks>net472;net6.0;net6.0-windows;net7.0;net7.0-windows;net8.0;net8.0-windows</TargetFrameworks>
<Version>1.2.0</Version>
<Copyright>Copyright © Perfare 2018-2022; Copyright © aelurum 2025</Copyright>
<DebugType>embedded</DebugType>
</PropertyGroup>
<ItemGroup Condition=" '$(TargetFramework)' != 'net472' ">
<PackageReference Include="K4os.Compression.LZ4" Version="1.3.6" />
<ItemGroup>
<PackageReference Include="K4os.Compression.LZ4" Version="1.3.8" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net472' ">
<PackageReference Include="System.Memory" Version="4.5.4" />
<PackageReference Include="System.IO.Compression" Version="4.0.0" />
<PackageReference Include="K4os.Compression.LZ4" Version="1.1.11" />
<PackageReference Include="System.Memory" Version="4.5.5" />
<PackageReference Include="System.IO.Compression" Version="4.3.0" />
</ItemGroup>
</Project>

View File

@@ -37,9 +37,15 @@ namespace AssetStudio
{
filteredAssetTypesList.Add(ClassIDType.MonoScript);
}
if (classIDTypes.Contains(ClassIDType.Sprite))
if (classIDTypes.Contains(ClassIDType.Sprite) || classIDTypes.Contains(ClassIDType.AkPortraitSprite))
{
filteredAssetTypesList.Add(ClassIDType.Texture2D);
filteredAssetTypesList.UnionWith(new HashSet<ClassIDType>
{
ClassIDType.Texture2D,
ClassIDType.SpriteAtlas,
ClassIDType.MonoBehaviour,
ClassIDType.MonoScript
});
}
filteredAssetTypesList.UnionWith(classIDTypes);
@@ -82,7 +88,7 @@ namespace AssetStudio
MergeSplitAssets(fullPath, true);
fileList.AddRange(Directory.GetFiles(fullPath, "*.*", SearchOption.AllDirectories));
}
else
else if (File.Exists(fullPath))
{
parentPath = Path.GetDirectoryName(fullPath);
fileList.Add(fullPath);
@@ -133,7 +139,7 @@ namespace AssetStudio
private void LoadFile(FileReader reader)
{
switch (reader.FileType)
switch (reader?.FileType)
{
case FileType.AssetsFile:
LoadAssetsFile(reader);
@@ -532,6 +538,9 @@ namespace AssetStudio
case ClassIDType.PlayerSettings:
obj = new PlayerSettings(objectReader);
break;
case ClassIDType.PreloadData:
obj = new PreloadData(objectReader);
break;
case ClassIDType.RectTransform:
obj = new RectTransform(objectReader);
break;
@@ -635,14 +644,17 @@ namespace AssetStudio
{
m_Sprite.m_SpriteAtlas.Set(m_SpriteAtlas);
}
else
else if (m_Sprite.m_SpriteAtlas.TryGet(out var m_SpriteAtlaOld))
{
m_Sprite.m_SpriteAtlas.TryGet(out var m_SpriteAtlaOld);
if (m_SpriteAtlaOld.m_IsVariant)
{
m_Sprite.m_SpriteAtlas.Set(m_SpriteAtlas);
}
}
else
{
Logger.Warning($"\"{m_Sprite.m_Name}\": Sprite loading error. SpriteAtlas with PathID: \"{m_Sprite.m_SpriteAtlas.m_PathID}\" was not found.");
}
}
}
}

View File

@@ -35,7 +35,7 @@ namespace AssetStudio
Lzma,
Lz4,
Lz4HC,
Lzham
Lz4Inv,
}
public class BundleFile
@@ -378,20 +378,31 @@ namespace AssetStudio
}
case CompressionType.Lz4:
case CompressionType.Lz4HC:
case CompressionType.Lz4Inv:
{
var compressedSize = (int)blockInfo.compressedSize;
var compressedBytes = BigArrayPool<byte>.Shared.Rent(compressedSize);
reader.Read(compressedBytes, 0, compressedSize);
_ = reader.Read(compressedBytes, 0, compressedSize);
var uncompressedSize = (int)blockInfo.uncompressedSize;
var uncompressedBytes = BigArrayPool<byte>.Shared.Rent(uncompressedSize);
var numWrite = LZ4Codec.Decode(compressedBytes, 0, compressedSize, uncompressedBytes, 0, uncompressedSize);
if (numWrite != uncompressedSize)
try
{
throw new IOException($"Lz4 decompression error, write {numWrite} bytes but expected {uncompressedSize} bytes");
var compressedSpan = compressedBytes.AsSpan(0, compressedSize);
var uncompressedSpan = uncompressedBytes.AsSpan(0, uncompressedSize);
var numWrite = compressionType == CompressionType.Lz4Inv
? LZ4Inv.Instance.Decompress(compressedSpan, uncompressedSpan)
: LZ4Codec.Decode(compressedSpan, uncompressedSpan);
if (numWrite != uncompressedSize)
{
throw new IOException($"Lz4 decompression error, write {numWrite} bytes but expected {uncompressedSize} bytes");
}
blocksStream.Write(uncompressedBytes, 0, uncompressedSize);
}
finally
{
BigArrayPool<byte>.Shared.Return(compressedBytes);
BigArrayPool<byte>.Shared.Return(uncompressedBytes);
}
blocksStream.Write(uncompressedBytes, 0, uncompressedSize);
BigArrayPool<byte>.Shared.Return(compressedBytes);
BigArrayPool<byte>.Shared.Return(uncompressedBytes);
break;
}
default:

View File

@@ -3,6 +3,7 @@ namespace AssetStudio
{
public enum ClassIDType
{
AkPortraitSprite = -2,
UnknownType = -1,
Object = 0,
GameObject = 1,

View File

@@ -15,7 +15,6 @@ namespace AssetStudio
public T inWeight;
public T outWeight;
public Keyframe(ObjectReader reader, Func<T> readerFunc)
{
time = reader.ReadSingle();
@@ -294,15 +293,20 @@ namespace AssetStudio
public string path;
public ClassIDType classID;
public PPtr<MonoScript> script;
public int flags;
public FloatCurve(ObjectReader reader)
{
var version = reader.version;
curve = new AnimationCurve<float>(reader, reader.ReadSingle);
attribute = reader.ReadAlignedString();
path = reader.ReadAlignedString();
classID = (ClassIDType)reader.ReadInt32();
script = new PPtr<MonoScript>(reader);
if (version[0] > 2022 || (version[0] == 2022 && version[1] >= 2)) //2022.2 and up
{
flags = reader.ReadInt32();
}
}
}
@@ -311,7 +315,6 @@ namespace AssetStudio
public float time;
public PPtr<Object> value;
public PPtrKeyframe(ObjectReader reader)
{
time = reader.ReadSingle();
@@ -326,10 +329,11 @@ namespace AssetStudio
public string path;
public int classID;
public PPtr<MonoScript> script;
public int flags;
public PPtrCurve(ObjectReader reader)
{
var version = reader.version;
int numCurves = reader.ReadInt32();
curve = new PPtrKeyframe[numCurves];
for (int i = 0; i < numCurves; i++)
@@ -341,6 +345,10 @@ namespace AssetStudio
path = reader.ReadAlignedString();
classID = reader.ReadInt32();
script = new PPtr<MonoScript>(reader);
if (version[0] > 2022 || (version[0] == 2022 && version[1] >= 2)) //2022.2 and up
{
flags = reader.ReadInt32();
}
}
}
@@ -940,7 +948,6 @@ namespace AssetStudio
public AnimationClipBindingConstant m_ClipBindingConstant;
public AnimationEvent[] m_Events;
public AnimationClip(ObjectReader reader) : base(reader)
{
if (version[0] >= 5)//5.0 and up

View File

@@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.Generic;
namespace AssetStudio
{
@@ -23,22 +20,47 @@ namespace AssetStudio
{
public PPtr<Object>[] m_PreloadTable;
public KeyValuePair<string, AssetInfo>[] m_Container;
public string m_AssetBundleName;
public string[] m_Dependencies;
public bool m_IsStreamedSceneAssetBundle;
public AssetBundle(ObjectReader reader) : base(reader)
{
var m_PreloadTableSize = reader.ReadInt32();
m_PreloadTable = new PPtr<Object>[m_PreloadTableSize];
for (int i = 0; i < m_PreloadTableSize; i++)
for (var i = 0; i < m_PreloadTableSize; i++)
{
m_PreloadTable[i] = new PPtr<Object>(reader);
}
var m_ContainerSize = reader.ReadInt32();
m_Container = new KeyValuePair<string, AssetInfo>[m_ContainerSize];
for (int i = 0; i < m_ContainerSize; i++)
for (var i = 0; i < m_ContainerSize; i++)
{
m_Container[i] = new KeyValuePair<string, AssetInfo>(reader.ReadAlignedString(), new AssetInfo(reader));
}
var m_MainAsset = new AssetInfo(reader);
if (version[0] > 4 || (version[0] == 4 && version[1] >= 2)) //4.2 and up
{
var m_RuntimeCompatibility = reader.ReadUInt32();
}
if (version[0] >= 5) //5.0 and up
{
m_AssetBundleName = reader.ReadAlignedString();
var m_DependenciesSize = reader.ReadInt32();
m_Dependencies = new string[m_DependenciesSize];
for (var i = 0; i < m_DependenciesSize; i++)
{
m_Dependencies[i] = reader.ReadAlignedString();
}
m_IsStreamedSceneAssetBundle = reader.ReadBoolean();
}
}
}
}

View File

@@ -0,0 +1,35 @@
namespace AssetStudio
{
public sealed class PreloadData : NamedObject
{
public PPtr<Object>[] m_Assets;
public PreloadData(ObjectReader reader) : base(reader)
{
var m_PreloadTableSize = reader.ReadInt32();
m_Assets = new PPtr<Object>[m_PreloadTableSize];
for (var i = 0; i < m_PreloadTableSize; i++)
{
m_Assets[i] = new PPtr<Object>(reader);
}
/*
if (version[0] >= 5) //5.0 and up
{
var m_DependenciesSize = reader.ReadInt32();
var m_Dependencies = new string[m_DependenciesSize];
for (var i = 0; i < m_DependenciesSize; i++)
{
m_Dependencies[i] = reader.ReadAlignedString();
}
}
if (version[0] > 2018 || (version[0] == 2018 && version[1] >= 2)) //2018.2 and up
{
var m_ExplicitDataLayout = reader.ReadBoolean();
}
*/
}
}
}

View File

@@ -12,7 +12,7 @@ namespace AssetStudio
public SecondarySpriteTexture(ObjectReader reader)
{
texture = new PPtr<Texture2D>(reader);
name = reader.ReadStringToNull();
name = reader.ReadAlignedString();
}
}
@@ -182,6 +182,13 @@ namespace AssetStudio
public float width;
public float height;
public Rectf(float x, float y, float w, float h) {
this.x = x;
this.y = y;
width = w;
height = h;
}
public Rectf(BinaryReader reader)
{
x = reader.ReadSingle();
@@ -205,6 +212,7 @@ namespace AssetStudio
public PPtr<SpriteAtlas> m_SpriteAtlas;
public SpriteRenderData m_RD;
public Vector2[][] m_PhysicsShape;
public bool akSplitAlpha;
public Sprite(ObjectReader reader) : base(reader)
{
@@ -254,6 +262,8 @@ namespace AssetStudio
}
}
akSplitAlpha = false;
//vector m_Bones 2018 and up
}
}

View File

@@ -1,10 +1,10 @@
using System;
namespace AssetStudioCLI
namespace AssetStudio
{
// 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 class ColorConsole
{
public static readonly string
Black = "\u001b[30m",
@@ -27,7 +27,7 @@ namespace AssetStudioCLI
public static string Color(this string str, string ansiColor)
{
if (!CLIWinAnsiFix.isAnsiSupported)
if (!ColorConsoleHelper.isAnsiCodesSupported)
{
return str;
}
@@ -35,10 +35,10 @@ namespace AssetStudioCLI
return $"{ansiColor}{str}{Reset}";
}
public static void ANSICodesTest()
public static void AnsiCodesTest()
{
Console.WriteLine("ANSI escape codes test");
Console.WriteLine($"Supported: {CLIWinAnsiFix.isAnsiSupported}");
Console.WriteLine($"Supported: {ColorConsoleHelper.isAnsiCodesSupported}");
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");

View File

@@ -2,11 +2,11 @@
using System;
using System.Runtime.InteropServices;
namespace AssetStudioCLI
namespace AssetStudio
{
static class CLIWinAnsiFix
internal static class ColorConsoleHelper
{
public static readonly bool isAnsiSupported;
public static readonly bool isAnsiCodesSupported;
private const int STD_OUTPUT_HANDLE = -11;
private const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004;
@@ -19,21 +19,21 @@ namespace AssetStudioCLI
[DllImport("kernel32.dll")]
private static extern IntPtr GetStdHandle(int nStdHandle);
static CLIWinAnsiFix()
static ColorConsoleHelper()
{
bool isWin = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
var isWin = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
if (isWin)
{
isAnsiSupported = TryEnableVTMode();
if (!isAnsiSupported)
isAnsiCodesSupported = TryEnableVTMode();
if (!isAnsiCodesSupported)
{
//Check for bash terminal emulator. E.g., Git Bash, Cmder
isAnsiSupported = Environment.GetEnvironmentVariable("TERM") != null;
isAnsiCodesSupported = Environment.GetEnvironmentVariable("TERM") != null;
}
}
else
{
isAnsiSupported = true;
isAnsiCodesSupported = true;
}
}
@@ -51,12 +51,7 @@ namespace AssetStudioCLI
outConsoleMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
if (!SetConsoleMode(iStdOut, outConsoleMode))
{
return false;
}
return true;
return SetConsoleMode(iStdOut, outConsoleMode);
}
}
}

View File

@@ -35,10 +35,13 @@ namespace AssetStudio
return "";
}
public static string ReadStringToNull(this BinaryReader reader, int maxLength = 32767)
public static string ReadStringToNull(this BinaryReader reader, int maxLength = 32767, Encoding encoding = null)
{
if (encoding?.CodePage == 1200) //Unicode (UTF-16LE)
return reader.ReadUnicodeStringToNull(maxLength * 2);
var bytes = new List<byte>();
int count = 0;
var count = 0;
while (reader.BaseStream.Position != reader.BaseStream.Length && count < maxLength)
{
var b = reader.ReadByte();
@@ -49,7 +52,24 @@ namespace AssetStudio
bytes.Add(b);
count++;
}
return Encoding.UTF8.GetString(bytes.ToArray());
return encoding?.GetString(bytes.ToArray()) ?? Encoding.UTF8.GetString(bytes.ToArray());
}
private static string ReadUnicodeStringToNull(this BinaryReader reader, int maxLength)
{
var bytes = new List<byte>();
var count = 0;
while (reader.BaseStream.Position != reader.BaseStream.Length && count < maxLength)
{
var b = reader.ReadBytes(2);
if (b.Length < 2 || (b[0] == 0 && b[1] == 0))
{
break;
}
bytes.AddRange(b);
count += 2;
}
return Encoding.Unicode.GetString(bytes.ToArray());
}
public static Quaternion ReadQuaternion(this BinaryReader reader)

View File

@@ -53,15 +53,24 @@ namespace AssetStudio
public static FileReader DecompressGZip(FileReader reader)
{
using (reader)
try
{
var stream = new MemoryStream();
using (var gs = new GZipStream(reader.BaseStream, CompressionMode.Decompress))
using (reader)
{
gs.CopyTo(stream);
var stream = new MemoryStream();
using (var gs = new GZipStream(reader.BaseStream, CompressionMode.Decompress))
{
gs.CopyTo(stream);
}
stream.Position = 0;
return new FileReader(reader.FullPath, stream);
}
stream.Position = 0;
return new FileReader(reader.FullPath, stream);
}
catch (System.Exception e)
{
Logger.Warning($"Error while decompressing gzip file {reader.FullPath}\r\n{e}");
reader.Dispose();
return null;
}
}

75
AssetStudio/LZ4/LZ4.cs Normal file
View File

@@ -0,0 +1,75 @@
using System;
namespace AssetStudio
{
public class LZ4
{
public static LZ4 Instance => new LZ4();
public virtual int Decompress(ReadOnlySpan<byte> cmp, Span<byte> dec)
{
int cmpPos = 0;
int decPos = 0;
do
{
var (encCount, litCount) = GetLiteralToken(cmp, ref cmpPos);
//Copy literal chunk
litCount = GetLength(litCount, cmp, ref cmpPos);
cmp.Slice(cmpPos, litCount).CopyTo(dec.Slice(decPos));
cmpPos += litCount;
decPos += litCount;
if (cmpPos >= cmp.Length)
{
break;
}
//Copy compressed chunk
int back = GetChunkEnd(cmp, ref cmpPos);
encCount = GetLength(encCount, cmp, ref cmpPos) + 4;
int encPos = decPos - back;
if (encCount <= back)
{
dec.Slice(encPos, encCount).CopyTo(dec.Slice(decPos));
decPos += encCount;
}
else
{
while (encCount-- > 0)
{
dec[decPos++] = dec[encPos++];
}
}
} while (cmpPos < cmp.Length && decPos < dec.Length);
return decPos;
}
protected virtual (int encCount, int litCount) GetLiteralToken(ReadOnlySpan<byte> cmp, ref int cmpPos) =>
((cmp[cmpPos] >> 0) & 0xf, (cmp[cmpPos++] >> 4) & 0xf);
protected virtual int GetChunkEnd(ReadOnlySpan<byte> cmp, ref int cmpPos) =>
cmp[cmpPos++] << 0 | cmp[cmpPos++] << 8;
protected virtual int GetLength(int length, ReadOnlySpan<byte> cmp, ref int cmpPos)
{
byte sum;
if (length == 0xf)
{
do
{
length += sum = cmp[cmpPos++];
} while (sum == 0xff);
}
return length;
}
}
}

15
AssetStudio/LZ4/LZ4Inv.cs Normal file
View File

@@ -0,0 +1,15 @@
using System;
namespace AssetStudio
{
public class LZ4Inv : LZ4
{
public new static LZ4Inv Instance => new LZ4Inv();
protected override (int encCount, int litCount) GetLiteralToken(ReadOnlySpan<byte> cmp, ref int cmpPos) =>
((cmp[cmpPos] >> 4) & 0xf, (cmp[cmpPos++] >> 0) & 0xf);
protected override int GetChunkEnd(ReadOnlySpan<byte> cmp, ref int cmpPos) =>
cmp[cmpPos++] << 8 | cmp[cmpPos++] << 0;
}
}

View File

@@ -2,11 +2,11 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net472;net6.0;net7.0</TargetFrameworks>
<AssemblyTitle>AssetStudioMod by aelurum</AssemblyTitle>
<AssemblyName>AssetStudioModCLI</AssemblyName>
<Version>0.17.3.0</Version>
<Copyright>Copyright © Perfare; Copyright © aelurum 2023</Copyright>
<TargetFrameworks>net472;net6.0;net7.0;net8.0</TargetFrameworks>
<AssemblyTitle>ArknightsStudio by aelurum</AssemblyTitle>
<AssemblyName>ArknightsStudioCLI</AssemblyName>
<Version>1.2.0</Version>
<Copyright>Copyright © Perfare; Copyright © aelurum 2025</Copyright>
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>embedded</DebugType>
</PropertyGroup>

View File

@@ -29,6 +29,7 @@ namespace AssetStudioCLI
LogName = $"{appAssembly.Name}_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.log";
LogPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, LogName);
var arch = Environment.Is64BitProcess ? "x64" : "x32";
Console.OutputEncoding = System.Text.Encoding.UTF8;
LogToFile(LoggerEvent.Verbose, $"---{appAssembly.Name} v{appAssembly.Version} [{arch}] | Logger launched---\n" +
$"CMD Args: {string.Join(" ", CLIOptions.cliArgs)}");
@@ -36,15 +37,15 @@ namespace AssetStudioCLI
private static string ColorLogLevel(LoggerEvent logLevel)
{
string formattedLevel = $"[{logLevel}]";
var formattedLevel = $"[{logLevel}]";
switch (logLevel)
{
case LoggerEvent.Info:
return $"{formattedLevel.Color(CLIAnsiColors.BrightCyan)}";
return $"{formattedLevel.Color(ColorConsole.BrightCyan)}";
case LoggerEvent.Warning:
return $"{formattedLevel.Color(CLIAnsiColors.BrightYellow)}";
return $"{formattedLevel.Color(ColorConsole.BrightYellow)}";
case LoggerEvent.Error:
return $"{formattedLevel.Color(CLIAnsiColors.BrightRed)}";
return $"{formattedLevel.Color(ColorConsole.BrightRed)}";
default:
return formattedLevel;
}
@@ -59,7 +60,7 @@ namespace AssetStudioCLI
string formattedMessage;
if (consoleMode)
{
string colorLogLevel = ColorLogLevel(logMsgLevel);
var colorLogLevel = ColorLogLevel(logMsgLevel);
formattedMessage = $"{colorLogLevel} {message}";
if (multiLine)
{

View File

@@ -0,0 +1,231 @@
using Arknights.PortraitSpriteMono;
using AssetStudio;
using AssetStudioCLI;
using AssetStudioCLI.Options;
using Newtonsoft.Json;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using System;
using System.Collections.Generic;
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;
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 (new Size(faceImage.Width, faceImage.Height) != 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, avgSprite.FacePos, opacity: 1f));
alphaTex.Mutate(x => x.DrawImage(faceAlpha, 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) && avgSprite != null && avgSprite.IsHubParsed)
{
if (!avgSprite.IsFaceSprite)
{
return m_Texture2D.ConvertToImage(true);
}
var faceImage = m_Texture2D.ConvertToImage(true);
var tex = avgSprite.FullTexture.ConvertToImage(true);
if (new Size(faceImage.Width, faceImage.Height) != avgSprite.FaceSize)
{
faceImage.Mutate(x => x.Resize(new ResizeOptions { Size = avgSprite.FaceSize, Sampler = KnownResamplers.Lanczos3, Mode = ResizeMode.Stretch }));
}
tex.Mutate(x => x.DrawImage(faceImage, avgSprite.FacePos, opacity: 1f));
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;
}
public static Image<Bgra32> AkGetImage(this PortraitSprite portraitSprite, SpriteMaskMode spriteMaskMode = SpriteMaskMode.On)
{
if (portraitSprite.Texture != null && portraitSprite.AlphaTexture != null)
{
var tex = CutImage(portraitSprite.Texture.ConvertToImage(false), portraitSprite.TextureRect, portraitSprite.DownscaleMultiplier, portraitSprite.Rotate);
if (spriteMaskMode == SpriteMaskMode.Off)
{
return tex;
}
else
{
var alphaTex = CutImage(portraitSprite.AlphaTexture.ConvertToImage(false), portraitSprite.TextureRect, portraitSprite.DownscaleMultiplier, portraitSprite.Rotate);
tex.ApplyRGBMask(alphaTex);
return tex;
}
}
return null;
}
public static List<PortraitSprite> GeneratePortraits(AssetItem asset)
{
var portraits = new List<PortraitSprite>();
var portraitsDict = ((MonoBehaviour)asset.Asset).ToType();
if (portraitsDict == null)
{
Logger.Warning("Portraits MonoBehaviour is not readable.");
return portraits;
}
var portraitsJson = JsonConvert.SerializeObject(portraitsDict);
var portraitsData = JsonConvert.DeserializeObject<PortraitSpriteConfig>(portraitsJson);
if (portraitsData._sprites.Length == 0)
return portraits;
var atlasTex = (Texture2D)Studio.loadedAssetsList.Find(x => x.m_PathID == portraitsData._atlas.Texture.m_PathID).Asset;
var atlasAlpha = (Texture2D)Studio.loadedAssetsList.Find(x => x.m_PathID == portraitsData._atlas.Alpha.m_PathID).Asset;
foreach (var portraitData in portraitsData._sprites)
{
var portraitSprite = new PortraitSprite()
{
Name = portraitData.Name,
AssetsFile = atlasTex.assetsFile,
Container = asset.Container,
Texture = atlasTex,
AlphaTexture = atlasAlpha,
TextureRect = new Rectf(portraitData.Rect.X, portraitData.Rect.Y, portraitData.Rect.W, portraitData.Rect.H),
Rotate = portraitData.Rotate,
};
portraits.Add(portraitSprite);
}
return portraits;
}
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_akAlphaTexResampler.Value));
resized = true;
}
var invGamma = 1.0 / (1.0 + CLIOptions.o_akShadowGamma.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, bool rotate = false)
{
if (originalImage != null)
{
if (downscaleMultiplier > 0f && downscaleMultiplier != 1f)
{
var newSize = (Size)(new Size(originalImage.Width, originalImage.Height) / 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));
originalImage.Dispose();
if (rotate)
{
spriteImage.Mutate(x => x.Rotate(RotateMode.Rotate270));
}
spriteImage.Mutate(x => x.Flip(FlipMode.Vertical));
return spriteImage;
}
return null;
}
}
}

View File

@@ -0,0 +1,149 @@
using Arknights.AvgCharHubMono;
using AssetStudio;
using AssetStudioCLI;
using SixLabors.ImageSharp;
using System.Linq;
using System;
using System.Collections.Specialized;
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 scriptAssets = Studio.loadedAssetsList.FindAll(x =>
x.Type == ClassIDType.MonoBehaviour
&& x.Container == assetItem.Container);
if (scriptAssets.Count == 0)
{
Logger.Warning("No MonoBehaviours were found.");
return false;
}
OrderedDictionary spriteHubDict = null;
var isGrouped = false;
foreach (var scriptAsset in scriptAssets)
{
var scriptAssetDict = ((MonoBehaviour)scriptAsset.Asset).ToType();
if (scriptAssetDict == null)
{
Logger.Warning("MonoBehaviour is not readable.");
return false;
}
if (scriptAssetDict.Contains("spriteGroups"))
{
spriteHubDict = scriptAssetDict;
isGrouped = true;
break;
}
if (scriptAssetDict.Contains("sprites"))
{
spriteHubDict = scriptAssetDict;
break;
}
}
if (spriteHubDict == null)
{
Logger.Warning("AVGCharacterSpriteHub is not readable.");
return false;
}
var spriteHubJson = JsonConvert.SerializeObject(spriteHubDict);
if (isGrouped)
{
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) //If face data exist
{
var fullTexSpriteData = spriteHubData.Sprites.Last(); //Last sprite item in the list usually contains PathID of Sprite with full texture
var curSprite = (Sprite)assetItem.Asset;
IsFaceSprite = curSprite.m_Rect.width <= 256 && curSprite.m_Rect.height <= 256 && curSprite.m_PathID != fullTexSpriteData.Sprite.m_PathID;
var curSpriteAlphaID = curSpriteData.AlphaTex.m_PathID;
var curSpriteAlphaTex = (Texture2D)Studio.loadedAssetsList.Find(x => x.m_PathID == curSpriteAlphaID)?.Asset;
if (curSpriteAlphaTex != null)
{
FaceSpriteAlphaTexture = IsFaceSprite ? curSpriteAlphaTex : null;
fullTexSpriteData = IsFaceSprite ? fullTexSpriteData : curSpriteData;
}
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));
}
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.AvgCharHubMono
{
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

@@ -0,0 +1,24 @@
using AssetStudio;
namespace Arknights
{
internal class PortraitSprite
{
public string Name { get; set; }
public ClassIDType Type { get; }
public SerializedFile AssetsFile { get; set; }
public string Container { get; set; }
public Texture2D Texture { get; set; }
public Texture2D AlphaTexture { get; set; }
public Rectf TextureRect { get; set; }
public bool Rotate { get; set; }
public float DownscaleMultiplier { get; }
public PortraitSprite()
{
Type = ClassIDType.AkPortraitSprite;
DownscaleMultiplier = 1f;
}
}
}

View File

@@ -0,0 +1,41 @@
namespace Arknights.PortraitSpriteMono
{
internal class PortraitRect
{
public float X { get; set; }
public float Y { get; set; }
public float W { get; set; }
public float H { get; set; }
}
internal class AtlasSprite
{
public string Name { get; set; }
public string Guid { get; set; }
public int Atlas { get; set; }
public PortraitRect Rect { get; set; }
public bool Rotate { get; set; }
}
internal class TextureIDs
{
public int m_FileID { get; set; }
public long m_PathID { get; set; }
}
internal class AtlasInfo
{
public int Index { get; set; }
public TextureIDs Texture { get; set; }
public TextureIDs Alpha { get; set; }
public int Size { get; set; }
}
internal class PortraitSpriteConfig
{
public string m_Name { get; set; }
public AtlasSprite[] _sprites { get; set; }
public AtlasInfo _atlas { get; set; }
public int _index { get; set; }
}
}

View File

@@ -1,4 +1,5 @@
using AssetStudio;
using Arknights;
using AssetStudio;
namespace AssetStudioCLI
{
@@ -14,6 +15,7 @@ namespace AssetStudioCLI
public string Text;
public string UniqueID;
public GameObjectNode Node;
public PortraitSprite AkPortraitSprite;
public AssetItem(Object asset)
{
@@ -24,5 +26,17 @@ namespace AssetStudioCLI
m_PathID = asset.m_PathID;
FullSize = asset.byteSize;
}
public AssetItem(PortraitSprite akPortraitSprite)
{
Asset = null;
SourceFile = akPortraitSprite.AssetsFile;
Container = akPortraitSprite.Container;
Type = akPortraitSprite.Type;
TypeString = Type.ToString();
Text = akPortraitSprite.Name;
m_PathID = -1;
AkPortraitSprite = akPortraitSprite;
}
}
}

View File

@@ -1,10 +1,14 @@
using AssetStudio;
using Arknights;
using AssetStudio;
using AssetStudioCLI.Options;
using Newtonsoft.Json;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace AssetStudioCLI
{
@@ -235,11 +239,80 @@ 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;
var spriteMaskMode = CLIOptions.o_akSpriteAlphaMode.Value != AkSpriteAlphaMode.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)
{
var groupedPattern = new Regex(@"^\d{1,2}\$\d{1,2}$"); // "spriteIndex$groupIndex"
var notGroupedPattern = new Regex(@"^\d{1,2}$"); // "spriteIndex"
if (groupedPattern.IsMatch(m_Sprite.m_Name) || notGroupedPattern.IsMatch(m_Sprite.m_Name))
{
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;
if (CLIOptions.o_akSpriteAlphaMode.Value == AkSpriteAlphaMode.SearchExternal && (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)
{
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 ExportPortraitSprite(AssetItem item, string exportPath)
{
var type = CLIOptions.o_imageFormat.Value;
var spriteMaskMode = CLIOptions.o_akSpriteAlphaMode.Value != AkSpriteAlphaMode.None ? SpriteMaskMode.Export : SpriteMaskMode.Off;
if (!TryExportFile(exportPath, item, "." + type.ToString().ToLower(), out var exportFullPath))
return false;
var image = ((Sprite)item.Asset).GetImage(alphaMask);
var image = item.AkPortraitSprite.AkGetImage(spriteMaskMode: spriteMaskMode);
if (image != null)
{
using (image)
@@ -257,6 +330,11 @@ namespace AssetStudioCLI
public static bool ExportRawFile(AssetItem item, string exportPath)
{
if (item.Asset == null)
{
Logger.Warning($"Raw export is not supported for \"{item.Text}\" ({item.TypeString}) file");
return false;
}
if (!TryExportFile(exportPath, item, ".dat", out var exportFullPath))
return false;
File.WriteAllBytes(exportFullPath, item.Asset.GetRawData());
@@ -292,9 +370,13 @@ namespace AssetStudioCLI
exportAllNodes, exportSkins, exportAnimations, exportBlendShape, castToBone, boneSize, exportAllUvsAsDiffuseMaps, scaleFactor, fbxVersion, fbxFormat == 1);
}
public static bool ExportDumpFile(AssetItem item, string exportPath)
{
if (item.Asset == null)
{
Logger.Warning($"Dump is not supported for \"{item.Text}\" ({item.TypeString}) file");
return false;
}
if (!TryExportFile(exportPath, item, ".txt", out var exportFullPath))
return false;
var str = item.Asset.Dump();
@@ -312,9 +394,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))
{
@@ -327,7 +409,7 @@ namespace AssetStudioCLI
Directory.CreateDirectory(dir);
return true;
}
Logger.Error($"Export error. File \"{fullPath.Color(CLIAnsiColors.BrightRed)}\" already exist");
Logger.Error($"Export error. File \"{fullPath.Color(ColorConsole.BrightRed)}\" already exist");
return false;
}
@@ -450,6 +532,8 @@ namespace AssetStudioCLI
return ExportFont(item, exportPath);
case ClassIDType.Sprite:
return ExportSprite(item, exportPath);
case ClassIDType.AkPortraitSprite:
return ExportPortraitSprite(item, exportPath);
case ClassIDType.Mesh:
return ExportMesh(item, exportPath);
default:

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,8 +14,10 @@ namespace AssetStudioCLI.Options
General,
Convert,
Logger,
Live2D,
FBX,
Filter,
Arknights,
Advanced,
}
@@ -58,6 +62,13 @@ namespace AssetStudioCLI.Options
NameAndContainer,
}
internal enum AkSpriteAlphaMode
{
None,
InternalOnly,
SearchExternal
}
internal static class CLIOptions
{
public static bool isParsed;
@@ -83,6 +94,9 @@ namespace AssetStudioCLI.Options
public static bool convertTexture;
public static Option<ImageFormat> o_imageFormat;
public static Option<AudioFormat> o_audioFormat;
//live2d
public static Option<CubismLive2DExtractor.Live2DMotionMode> o_l2dMotionMode;
public static Option<bool> f_l2dForceBezier;
//fbx
public static Option<float> o_fbxScaleFactor;
public static Option<int> o_fbxBoneSize;
@@ -91,6 +105,14 @@ namespace AssetStudioCLI.Options
public static Option<List<string>> o_filterByContainer;
public static Option<List<string>> o_filterByPathID;
public static Option<List<string>> o_filterByText;
//arknights
public static bool akResizedOnly;
public static Option<AkSpriteAlphaMode> o_akSpriteAlphaMode;
public static Option<IResampler> o_akAlphaTexResampler;
private static string resamplerName;
public static Option<int> o_akShadowGamma;
public static Option<bool> f_akOriginalAvgNames;
public static Option<bool> f_akAddAliases;
//advanced
public static Option<ExportListType> o_exportAssetList;
public static Option<string> o_assemblyPath;
@@ -145,6 +167,7 @@ namespace AssetStudioCLI.Options
{
ClassIDType.Texture2D,
ClassIDType.Sprite,
ClassIDType.AkPortraitSprite,
ClassIDType.TextAsset,
ClassIDType.MonoBehaviour,
ClassIDType.Font,
@@ -177,8 +200,8 @@ namespace AssetStudioCLI.Options
optionDefaultValue: exportableAssetTypes,
optionName: "-t, --asset-type <value(s)>",
optionDescription: "Specify asset type(s) to export\n" +
"<Value(s): tex2d, sprite, textAsset, monoBehaviour, font, shader, movieTexture,\n" +
"audio, video, mesh | all(default)>\n" +
"<Value(s): tex2d, sprite, akPortrait, textAsset, monoBehaviour, font, shader,\n" +
"movieTexture, audio, video, mesh | 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 tex2d,sprite,audio\" or \"-t tex2d;sprite;font\"\n",
@@ -203,7 +226,7 @@ namespace AssetStudioCLI.Options
optionDefaultValue: "ASExport",
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",
"If path isn't specified, 'ASExport' folder will be created in the program's work folder\n",
optionHelpGroup: HelpGroups.General
);
o_displayHelp = new GroupedOption<bool>
@@ -260,6 +283,30 @@ namespace AssetStudioCLI.Options
);
#endregion
#region Init Cubism Live2D Options
o_l2dMotionMode = new GroupedOption<CubismLive2DExtractor.Live2DMotionMode>
(
optionDefaultValue: CubismLive2DExtractor.Live2DMotionMode.MonoBehaviour,
optionName: "--l2d-motion-mode <value>",
optionDescription: "Specify Live2D motion export mode\n" +
"<Value: monoBehaviour(default) | animationClip>\n" +
"MonoBehaviour - Try to export motions from MonoBehaviour Fade motions\n" +
"If no Fade motions are found, the AnimationClip method will be used\n" +
"AnimationClip - Try to export motions using AnimationClip assets\n" +
"Example: \"--l2d-motion-mode animationClip\"\n",
optionHelpGroup: HelpGroups.Live2D
);
f_l2dForceBezier = new GroupedOption<bool>
(
optionDefaultValue: false,
optionName: "--l2d-force-bezier",
optionDescription: "(Flag) If specified, Linear motion segments will be calculated as Bezier segments\n" +
"(May help if the exported motions look jerky/not smooth enough)",
optionHelpGroup: HelpGroups.Live2D,
isFlag: true
);
#endregion
#region Init FBX Options
o_fbxScaleFactor = new GroupedOption<float>
(
@@ -321,6 +368,65 @@ namespace AssetStudioCLI.Options
);
#endregion
#region Init Arknights Options
akResizedOnly = true;
o_akSpriteAlphaMode = new GroupedOption<AkSpriteAlphaMode>
(
optionDefaultValue: AkSpriteAlphaMode.SearchExternal,
optionName: "--spritealpha-mode <value>",
optionDescription: "Specify the mode in which you want to export sprites with alpha texture\n" +
"<Value: none | internalOnly | searchExternal(default)>\n" +
"None - Export sprites without alpha texture applied\n" +
"InternalOnly - Export sprites with internal alpha texture applied (if exist)\n" +
"SearchExternal - Export sprites with internal alpha texture applied,\n" +
"and in case it doesn't exist, Studio will try to find an external alpha texture\n" +
"Example: \"--spritealpha-mode internalOnly\"\n",
optionHelpGroup: HelpGroups.Arknights
);
o_akAlphaTexResampler = new GroupedOption<IResampler>
(
optionDefaultValue: KnownResamplers.MitchellNetravali,
optionName: "--alphatex-resampler <value>",
optionDescription: "Specify the alpha texture 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 the current use case)\n" +
"Spline - Similar to Mitchell Netravali but yielding smoother results\n" +
"Welch - A high speed algorithm that delivers very sharpened results\n" +
"Example: \"--alphatex-resampler bicubic\"\n",
optionHelpGroup: HelpGroups.Arknights
);
resamplerName = "Mitchell";
o_akShadowGamma = new GroupedOption<int>
(
optionDefaultValue: 2,
optionName: "--shadow-gamma <value>",
optionDescription: "Specify the gamma correction of semi-transparent shadow for 2048x2048 sprites\n" +
"<Value: integer number from -5 to 5 (default=2)>\n" +
"<0 - Make the shadow darker\n" +
"0 - Do not change the brightness of the shadow\n" +
">0 - Make the shadow lighter\n" +
"Example: \"--shadow-gamma 0\"\n",
optionHelpGroup: HelpGroups.Arknights
);
f_akOriginalAvgNames = new GroupedOption<bool>
(
optionDefaultValue: false,
optionName: "--original-avg-names",
optionDescription: "(Flag) If specified, names of avg character sprites will not be restored\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 character sprite names (if exist)",
optionHelpGroup: HelpGroups.Arknights,
isFlag: true
);
#endregion
#region Init Advanced Options
o_exportAssetList = new GroupedOption<ExportListType>
(
@@ -350,7 +456,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\n",
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\n",
optionHelpGroup: HelpGroups.Advanced,
isFlag: true
);
@@ -358,7 +464,7 @@ namespace AssetStudioCLI.Options
(
optionDefaultValue: false,
optionName: "--load-all",
optionDescription: "(Flag) If specified, AssetStudio will load assets of all types\n(Only for Dump, Info and ExportRaw modes)",
optionDescription: "(Flag) If specified, Studio will load assets of all types\n(Only for Dump, Info and ExportRaw modes)",
optionHelpGroup: HelpGroups.Advanced,
isFlag: true
);
@@ -369,8 +475,8 @@ namespace AssetStudioCLI.Options
{
cliArgs = args;
var brightYellow = CLIAnsiColors.BrightYellow;
var brightRed = CLIAnsiColors.BrightRed;
var brightYellow = ColorConsole.BrightYellow;
var brightRed = ColorConsole.BrightRed;
if (args.Length == 0 || args.Any(x => x.ToLower() == "-h" || x.ToLower() == "--help" || x.ToLower() == "-?"))
{
@@ -411,6 +517,7 @@ namespace AssetStudioCLI.Options
}
};
#region Parse "Working Mode" Option
var workModeOptionIndex = resplittedArgs.FindIndex(x => x.ToLower() == "-m" || x.ToLower() == "--mode");
if (workModeOptionIndex >= 0)
{
@@ -418,7 +525,7 @@ namespace AssetStudioCLI.Options
if (workModeOptionIndex + 1 >= resplittedArgs.Count)
{
Console.WriteLine($"{"Error during parsing options:".Color(brightRed)} Value for [{option.Color(brightRed)}] option was not found.\n");
TryShowOptionDescription(option, optionsDict);
TryFindOptionDescription(option, optionsDict);
return;
}
var value = resplittedArgs[workModeOptionIndex + 1];
@@ -437,6 +544,7 @@ namespace AssetStudioCLI.Options
case "info":
o_workMode.Value = WorkMode.Info;
break;
case "l2d":
case "live2d":
o_workMode.Value = WorkMode.ExportLive2D;
o_exportAssetTypes.Value = new List<ClassIDType>()
@@ -463,11 +571,12 @@ namespace AssetStudioCLI.Options
break;
default:
Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unsupported working mode: [{value.Color(brightRed)}].\n");
Console.WriteLine(o_workMode.Description);
ShowOptionDescription(o_workMode.Description);
return;
}
resplittedArgs.RemoveRange(workModeOptionIndex, 2);
}
#endregion
#region Parse Flags
for (int i = 0; i < resplittedArgs.Count; i++)
@@ -491,10 +600,28 @@ namespace AssetStudioCLI.Options
break;
default:
Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{flag}] flag. This flag is not suitable for the current working mode [{o_workMode.Value}].\n");
Console.WriteLine(f_loadAllAssets.Description);
ShowOptionDescription(f_loadAllAssets.Description, isFlag: true);
return;
}
break;
case "--original-avg-names":
f_akOriginalAvgNames.Value = true;
resplittedArgs.RemoveAt(i);
break;
case "--add-aliases":
f_akAddAliases.Value = true;
resplittedArgs.RemoveAt(i);
break;
case "--l2d-force-bezier":
if (o_workMode.Value != WorkMode.ExportLive2D)
{
Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{flag}] flag. This flag is not suitable for the current working mode [{o_workMode.Value}].\n");
ShowOptionDescription(o_workMode.Description);
return;
}
f_l2dForceBezier.Value = true;
resplittedArgs.RemoveAt(i);
break;
}
}
#endregion
@@ -524,6 +651,9 @@ namespace AssetStudioCLI.Options
case "tex2d":
o_exportAssetTypes.Value.Add(ClassIDType.Texture2D);
break;
case "akportrait":
o_exportAssetTypes.Value.Add(ClassIDType.AkPortraitSprite);
break;
case "audio":
o_exportAssetTypes.Value.Add(ClassIDType.AudioClip);
break;
@@ -546,11 +676,11 @@ namespace AssetStudioCLI.Options
else
{
Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unknown asset type specified [{type.Color(brightRed)}].\n");
Console.WriteLine(o_exportAssetTypes.Description);
ShowOptionDescription(o_exportAssetTypes.Description);
return;
}
Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Asset type [{type.Color(brightRed)}] is not supported for exporting.\n");
Console.WriteLine(o_exportAssetTypes.Description);
ShowOptionDescription(o_exportAssetTypes.Description);
return;
}
}
@@ -576,7 +706,7 @@ namespace AssetStudioCLI.Options
break;
default:
Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unsupported grouping option: [{value.Color(brightRed)}].\n");
Console.WriteLine(o_groupAssetsBy.Description);
ShowOptionDescription(o_groupAssetsBy.Description);
return;
}
break;
@@ -627,7 +757,7 @@ namespace AssetStudioCLI.Options
break;
default:
Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unsupported log level value: [{value.Color(brightRed)}].\n");
Console.WriteLine(o_logLevel.Description);
ShowOptionDescription(o_logLevel.Description);
return;
}
break;
@@ -645,7 +775,7 @@ namespace AssetStudioCLI.Options
break;
default:
Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unsupported log output mode: [{value.Color(brightRed)}].\n");
Console.WriteLine(o_logOutput.Description);
ShowOptionDescription(o_logOutput.Description);
return;
}
break;
@@ -673,7 +803,7 @@ namespace AssetStudioCLI.Options
break;
default:
Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unsupported image format: [{value.Color(brightRed)}].\n");
Console.WriteLine(o_imageFormat.Description);
ShowOptionDescription(o_imageFormat.Description);
return;
}
break;
@@ -689,51 +819,62 @@ namespace AssetStudioCLI.Options
break;
default:
Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unsupported audio format: [{value.Color(brightRed)}].\n");
Console.WriteLine(o_audioFormat.Description);
ShowOptionDescription(o_audioFormat.Description);
return;
}
break;
case "--l2d-motion-mode":
if (o_workMode.Value != WorkMode.ExportLive2D)
{
Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. This option is not suitable for the current working mode [{o_workMode.Value}].\n");
ShowOptionDescription(o_workMode.Description);
return;
}
switch (value.ToLower())
{
case "fade":
case "monobehaviour":
o_l2dMotionMode.Value = CubismLive2DExtractor.Live2DMotionMode.MonoBehaviour;
break;
case "animationclip":
o_l2dMotionMode.Value = CubismLive2DExtractor.Live2DMotionMode.AnimationClip;
break;
default:
Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unsupported Live2D motion mode: [{value.Color(brightRed)}].\n");
ShowOptionDescription(o_l2dMotionMode.Description);
return;
}
break;
case "--fbx-scale-factor":
var isFloat = float.TryParse(value, out float floatValue);
if (isFloat && floatValue >= 0 && floatValue <= 100)
{
o_fbxScaleFactor.Value = floatValue;
}
else
{
Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unsupported scale factor value: [{value.Color(brightRed)}].\n");
Console.WriteLine(o_fbxScaleFactor.Description);
return;
}
break;
case "--fbx-bone-size":
var isInt = int.TryParse(value, out int intValue);
if (isInt && intValue >= 0 && intValue <= 100)
{
o_fbxBoneSize.Value = intValue;
}
else
{
Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unsupported bone size value: [{value.Color(brightRed)}].\n");
Console.WriteLine(o_fbxBoneSize.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);
var isFloat = float.TryParse(value, out float floatValue);
if (isFloat && floatValue >= 0 && floatValue <= 100)
{
o_fbxScaleFactor.Value = floatValue;
}
else
{
Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unsupported scale factor value: [{value.Color(brightRed)}].\n");
ShowOptionDescription(o_fbxScaleFactor.Description);
return;
}
break;
}
case "--fbx-bone-size":
{
var isInt = int.TryParse(value, out int intValue);
if (isInt && intValue >= 0 && intValue <= 100)
{
o_fbxBoneSize.Value = intValue;
}
else
{
Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unsupported bone size value: [{value.Color(brightRed)}].\n");
ShowOptionDescription(o_fbxBoneSize.Description);
return;
}
break;
}
break;
case "--filter-by-name":
o_filterByName.Value.AddRange(ValueSplitter(value));
filterBy = filterBy == FilterBy.None ? FilterBy.Name : filterBy == FilterBy.Container ? FilterBy.NameAndContainer : filterBy;
@@ -750,6 +891,82 @@ namespace AssetStudioCLI.Options
o_filterByText.Value.AddRange(ValueSplitter(value));
filterBy = FilterBy.NameOrContainer;
break;
case "--spritealpha-mode":
switch (value.ToLower())
{
case "none":
o_akSpriteAlphaMode.Value = AkSpriteAlphaMode.None;
break;
case "internalonly":
o_akSpriteAlphaMode.Value = AkSpriteAlphaMode.InternalOnly;
break;
case "searchexternal":
o_akSpriteAlphaMode.Value = AkSpriteAlphaMode.SearchExternal;
break;
default:
Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unsupported sprite alpha mode: [{value.Color(brightRed)}].\n");
ShowOptionDescription(o_akSpriteAlphaMode.Description);
return;
}
break;
case "--alphatex-resampler":
switch (value.ToLower())
{
case "nearest":
o_akAlphaTexResampler.Value = KnownResamplers.NearestNeighbor;
break;
case "bilinear":
o_akAlphaTexResampler.Value = KnownResamplers.Triangle;
break;
case "bicubic":
o_akAlphaTexResampler.Value = KnownResamplers.Bicubic;
break;
case "mitchell":
o_akAlphaTexResampler.Value = KnownResamplers.MitchellNetravali;
break;
case "spline":
o_akAlphaTexResampler.Value = KnownResamplers.Spline;
break;
case "welch":
o_akAlphaTexResampler.Value = KnownResamplers.Welch;
break;
default:
Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unsupported alpha texture resampler: [{value.Color(brightRed)}].\n");
ShowOptionDescription(o_akAlphaTexResampler.Description);
return;
}
resamplerName = value.ToLower();
break;
case "--shadow-gamma":
{
var isInt = int.TryParse(value, out int intValue);
if (isInt && intValue >= -5 && intValue <= 5)
{
o_akShadowGamma.Value = intValue;
}
else
{
Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unsupported gamma correction value: [{value.Color(brightRed)}].\n");
ShowOptionDescription(o_akShadowGamma.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");
ShowOptionDescription(o_exportAssetList.Description);
return;
}
break;
case "--assembly-folder":
if (Directory.Exists(value))
{
@@ -767,9 +984,9 @@ namespace AssetStudioCLI.Options
break;
default:
Console.WriteLine($"{"Error:".Color(brightRed)} Unknown option [{option.Color(brightRed)}].\n");
if (!TryShowOptionDescription(option, optionsDict))
if (!TryFindOptionDescription(option, optionsDict))
{
TryShowOptionDescription(option, flagsDict);
TryFindOptionDescription(option, flagsDict, isFlag: true);
}
return;
}
@@ -780,12 +997,12 @@ namespace AssetStudioCLI.Options
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);
TryFindOptionDescription(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);
TryFindOptionDescription(option, flagsDict, isFlag: true);
}
else
{
@@ -795,7 +1012,7 @@ namespace AssetStudioCLI.Options
}
catch (Exception ex)
{
Console.WriteLine("Unknown Error.".Color(CLIAnsiColors.Red));
Console.WriteLine("Unknown Error.".Color(ColorConsole.Red));
Console.WriteLine(ex);
return;
}
@@ -824,14 +1041,21 @@ namespace AssetStudioCLI.Options
return value.Split(separator);
}
private static bool TryShowOptionDescription(string option, Dictionary<string, string> descDict)
private static void ShowOptionDescription(string desc, bool isFlag = false)
{
var optionDesc = descDict.Where(x => x.Key.Contains(option));
var arg = isFlag ? "Flag" : "Option";
Console.WriteLine($"{arg} description:\n{desc}");
}
private static bool TryFindOptionDescription(string option, Dictionary<string, string> dict, bool isFlag = false)
{
var optionDesc = dict.Where(x => x.Key.Contains(option)).ToArray();
if (optionDesc.Any())
{
var arg = isFlag ? "flag" : "option";
var rand = new Random();
var rndOption = optionDesc.ElementAt(rand.Next(0, optionDesc.Count()));
Console.WriteLine($"Did you mean [{ $"{rndOption.Key}".Color(CLIAnsiColors.BrightYellow) }] option?");
var rndOption = optionDesc.ElementAt(rand.Next(0, optionDesc.Length));
Console.WriteLine($"Did you mean [{$"{rndOption.Key}".Color(ColorConsole.BrightYellow)}] {arg}?");
Console.WriteLine($"Here's a description of it: \n\n{rndOption.Value}");
return true;
@@ -873,7 +1097,7 @@ namespace AssetStudioCLI.Options
else
{
var arch = Environment.Is64BitProcess ? "x64" : "x32";
Console.WriteLine($"# {appAssembly.Name} [{arch}]\n# Based on AssetStudioMod v{appAssembly.Version}\n");
Console.WriteLine($"# {appAssembly.Name} [{arch}]\n# v{appAssembly.Version}\n# Based on AssetStudioMod v0.17.4\n");
Console.WriteLine($"{usage}\n\n{helpMessage}");
}
}
@@ -924,7 +1148,7 @@ namespace AssetStudioCLI.Options
case WorkMode.ExportRaw:
case WorkMode.Dump:
sb.AppendLine($"# Output Path: \"{o_outputFolder}\"");
if (o_workMode.Value != WorkMode.Export)
if (o_workMode.Value != WorkMode.Export)
{
sb.AppendLine($"# Load All Assets: {f_loadAllAssets}");
}
@@ -934,12 +1158,17 @@ namespace AssetStudioCLI.Options
{
sb.AppendLine($"# Export Image Format: {o_imageFormat}");
sb.AppendLine($"# Export Audio Format: {o_audioFormat}");
sb.AppendLine($"# [Arkingths] Sprite Alpha Mode: {o_akSpriteAlphaMode}");
sb.AppendLine($"# [Arknights] Alpha Texture Resampler: {resamplerName}");
sb.AppendLine($"# [Arknights] Shadow Gamma Correction: {o_akShadowGamma.Value * 10:+#;-#;0}%");
}
sb.AppendLine($"# [Arknights] Don't Fix 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}");
sb.AppendLine(ShowCurrentFilter());
sb.AppendLine($"# Assebmly Path: \"{o_assemblyPath}\"");
sb.AppendLine($"# Assembly Path: \"{o_assemblyPath}\"");
sb.AppendLine($"# Unity Version: \"{o_unityVersion}\"");
if (o_workMode.Value == WorkMode.Export)
{
@@ -968,7 +1197,9 @@ namespace AssetStudioCLI.Options
}
else
{
sb.AppendLine($"# Assebmly Path: \"{o_assemblyPath}\"");
sb.AppendLine($"# Live2D Motion Export Method: {o_l2dMotionMode}");
sb.AppendLine($"# Force Bezier: {f_l2dForceBezier }");
sb.AppendLine($"# Assembly Path: \"{o_assemblyPath}\"");
}
sb.AppendLine($"# Unity Version: \"{o_unityVersion}\"");
break;

View File

@@ -1,21 +1,25 @@
## AssetStudioModCLI
CLI version of AssetStudioMod.
- Supported asset types for export: `Texture2D`, `Sprite`, `TextAsset`, `MonoBehaviour`, `Font`, `Shader`, `MovieTexture`, `AudioClip`, `VideoClip`, `Mesh`.
## ArknightsStudioCLI
CLI version of ArknightsStudio.
- Supported asset types for export: `Texture2D`, `Sprite`, `AkPortraitSprite`, `TextAsset`, `MonoBehaviour`, `Font`, `Shader`, `MovieTexture`, `AudioClip`, `VideoClip`, `Mesh`.
- *There are no plans to add support for `AnimationClip`, `Animator` for now.*
### Usage
```
AssetStudioModCLI <input path to asset file/folder> [-m, --mode <value>]
ArknightsStudioCLI <input path to asset file/folder> [-m, --mode <value>]
[-t, --asset-type <value(s)>] [-g, --group-option <value>]
[-o, --output <path>] [-h, --help]
[--log-level <value>] [--log-output <value>]
[--image-format <value>] [--audio-format <value>]
[--l2d-motion-mode <value>] [--l2d-force-bezier]
[--fbx-scale-factor <value>] [--fbx-bone-size <value>]
[--filter-by-name <text>] [--filter-by-container <text>]
[--filter-by-pathid <text>] [--filter-by-text <text>]
[--export-asset-list <value>] [--assembly-folder <path>]
[--unity-version <text>] [--not-restore-extension]
[--load-all]
[--spritealpha-mode <value>] [--alphatex-resampler <value>]
[--shadow-gamma <value>] [--original-avg-names]
[--add-aliases] [--export-asset-list <value>]
[--assembly-folder <path>] [--unity-version <text>]
[--not-restore-extension] [--load-all]
General Options:
-m, --mode <value> Specify working mode
@@ -29,8 +33,8 @@ General Options:
Example: "-m info"
-t, --asset-type <value(s)> Specify asset type(s) to export
<Value(s): tex2d, sprite, textAsset, monoBehaviour, font, shader, movieTexture,
audio, video, mesh | all(default)>
<Value(s): tex2d, sprite, akPortrait, textAsset, monoBehaviour, font, shader,
movieTexture, audio, video, mesh | all(default)>
All - export all asset types, which are listed in the values
*To specify multiple asset types, write them separated by ',' or ';' without spaces
Examples: "-t sprite" or "-t tex2d,sprite,audio" or "-t tex2d;sprite;font"
@@ -45,7 +49,7 @@ General Options:
Example: "-g container"
-o, --output <path> Specify path to the output folder
If path isn't specifyed, 'ASExport' folder will be created in the program's work folder
If path isn't specified, 'ASExport' folder will be created in the program's work folder
-h, --help Display help and exit
@@ -69,6 +73,17 @@ Convert Options:
None - Do not convert audios and export them in their own format
Example: "--audio-format wav"
Live2D Options:
--l2d-motion-mode <value> Specify Live2D motion export mode
<Value: monoBehaviour(default) | animationClip>
MonoBehaviour - Try to export motions from MonoBehaviour Fade motions
If no Fade motions are found, the AnimationClip method will be used
AnimationClip - Try to export motions using AnimationClip assets
Example: "--l2d-motion-mode animationClip"
--l2d-force-bezier (Flag) If specified, Linear motion segments will be calculated as Bezier segments
(May help if the exported motions look jerky/not smooth enough)
FBX Options:
--fbx-scale-factor <value> Specify the FBX Scale Factor
<Value: float number from 0 to 100 (default=1)
@@ -97,6 +112,34 @@ Filter Options:
Example: "--filter-by-text portrait" or "--filter-by-text portrait,art"
Arknights Options:
--spritealpha-mode <value> Specify the mode in which you want to export sprites with alpha texture
<Value: none | internalOnly | searchExternal(default)>
None - Export sprites without alpha texture applied
InternalOnly - Export sprites with internal alpha texture applied (if exist)
SearchExternal - Export sprites with internal alpha texture applied,
and in case it doesn't exist, Studio will try to find an external alpha texture
Example: "--spritealpha-mode internalOnly"
--alphatex-resampler <value> Specify the alpha texture upscale algorithm for 2048x2048 sprites
<Value: nearest | bilinear | bicubic | mitchell(default) | spline | welch>
Mitchell - Mitchell Netravali algorithm. Yields good equilibrium between
sharpness and smoothness (produces less artifacts than bicubic in the current use case)
Spline - Similar to Mitchell Netravali but yielding smoother results
Welch - A high speed algorithm that delivers very sharpened results
Example: "--alphatex-resampler bicubic"
--shadow-gamma <value> Specify the gamma correction of semi-transparent shadow for 2048x2048 sprites
<Value: integer number from -5 to 5 (default=2)>
<0 - Make the shadow darker
0 - Do not change the brightness of the shadow
>0 - Make the shadow lighter
Example: "--shadow-gamma 0"
--original-avg-names (Flag) If specified, names of avg character sprites will not be restored
--add-aliases (Flag) If specified, aliases will be added to avg character sprite names (if exist)
Advanced Options:
--export-asset-list <value> Specify the format in which you want to export asset list
<Value: none(default) | xml>
@@ -108,9 +151,9 @@ Advanced Options:
--unity-version <text> Specify Unity version
Example: "--unity-version 2017.4.39f1"
--not-restore-extension (Flag) If specified, AssetStudio will not try to use/restore original TextAsset
--not-restore-extension (Flag) If specified, Studio will not try to use/restore original TextAsset
extension name, and will just export all TextAssets with the ".txt" extension
--load-all (Flag) If specified, AssetStudio will load assets of all types
--load-all (Flag) If specified, Studio will load assets of all types
(Only for Dump, Info and ExportRaw modes)
```

View File

@@ -7,14 +7,15 @@ using System.Linq;
using System.Xml.Linq;
using static AssetStudioCLI.Exporter;
using static CubismLive2DExtractor.Live2DExtractor;
using Ansi = AssetStudioCLI.CLIAnsiColors;
using Ansi = AssetStudio.ColorConsole;
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 List<BaseNode> gameObjectTree = new List<BaseNode>();
public static AssemblyLoader assemblyLoader = new AssemblyLoader();
private static Dictionary<AssetStudio.Object, string> containers = new Dictionary<AssetStudio.Object, string>();
@@ -54,7 +55,6 @@ namespace AssetStudioCLI
{
Logger.Info("Parse assets...");
var fileAssetsList = new List<AssetItem>();
var objectCount = assetsManager.assetsFileList.Sum(x => x.Objects.Count);
var objectAssetItemDic = new Dictionary<AssetStudio.Object, AssetItem>(objectCount);
@@ -62,6 +62,7 @@ namespace AssetStudioCLI
var i = 0;
foreach (var assetsFile in assetsManager.assetsFileList)
{
var preloadTable = Array.Empty<PPtr<AssetStudio.Object>>();
foreach (var asset in assetsFile.Objects)
{
var assetItem = new AssetItem(asset);
@@ -70,22 +71,31 @@ namespace AssetStudioCLI
var isExportable = false;
switch (asset)
{
case PreloadData m_PreloadData:
preloadTable = m_PreloadData.m_Assets;
break;
case AssetBundle m_AssetBundle:
var isStreamedSceneAssetBundle = m_AssetBundle.m_IsStreamedSceneAssetBundle;
if (!isStreamedSceneAssetBundle)
{
preloadTable = m_AssetBundle.m_PreloadTable;
}
assetItem.Text = string.IsNullOrEmpty(m_AssetBundle.m_AssetBundleName) ? m_AssetBundle.m_Name : m_AssetBundle.m_AssetBundleName;
foreach (var m_Container in m_AssetBundle.m_Container)
{
var preloadIndex = m_Container.Value.preloadIndex;
var preloadSize = m_Container.Value.preloadSize;
var preloadSize = isStreamedSceneAssetBundle ? preloadTable.Length : m_Container.Value.preloadSize;
var preloadEnd = preloadIndex + preloadSize;
for (int k = preloadIndex; k < preloadEnd; k++)
for (var k = preloadIndex; k < preloadEnd; k++)
{
var pptr = m_AssetBundle.m_PreloadTable[k];
var pptr = preloadTable[k];
if (pptr.TryGet(out var obj))
{
containers[obj] = m_Container.Key;
}
}
}
assetItem.Text = m_AssetBundle.m_Name;
break;
case ResourceManager m_ResourceManager:
foreach (var m_Container in m_ResourceManager.m_Container)
@@ -142,23 +152,31 @@ namespace AssetStudioCLI
assetItem.Text = assetItem.TypeString + assetItem.UniqueID;
}
loadedAssetsList.Add(assetItem);
isExportable = CLIOptions.o_exportAssetTypes.Value.Contains(asset.type);
if (isExportable || (CLIOptions.f_loadAllAssets.Value && CLIOptions.o_exportAssetTypes.Value == CLIOptions.o_exportAssetTypes.DefaultValue))
{
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))
if (containers.TryGetValue(asset.Asset, out var container))
{
asset.Container = containers[asset.Asset];
asset.Container = container;
if (asset.Type == ClassIDType.MonoBehaviour && container.Contains("/arts/charportraits/portraits"))
{
var portraitsList = Arknights.AkSpriteHelper.GeneratePortraits(asset);
foreach (var portrait in portraitsList)
{
exportableAssetsList.Add(new AssetItem(portrait));
}
}
}
}
parsedAssetsList.AddRange(fileAssetsList);
fileAssetsList.Clear();
if (CLIOptions.o_workMode.Value != WorkMode.ExportLive2D)
{
containers.Clear();
@@ -169,8 +187,7 @@ namespace AssetStudioCLI
{
BuildTreeStructure(objectAssetItemDic);
}
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)
@@ -275,9 +292,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))
{
@@ -296,7 +313,7 @@ namespace AssetStudioCLI
}
if (exportableAssetsCountDict.Count > 1)
{
info += $"#\n# Total: {parsedAssetsList.Count} assets";
info += $"#\n# Total: {exportableAssetsList.Count} assets";
}
}
else
@@ -329,34 +346,34 @@ namespace AssetStudioCLI
private 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.IndexOf(y, StringComparison.OrdinalIgnoreCase) >= 0));
filteredAssets = exportableAssetsList.FindAll(x => CLIOptions.o_filterByName.Value.Any(y => x.Text.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.IndexOf(y, StringComparison.OrdinalIgnoreCase) >= 0));
filteredAssets = exportableAssetsList.FindAll(x => CLIOptions.o_filterByContainer.Value.Any(y => x.Container.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.IndexOf(y, StringComparison.OrdinalIgnoreCase) >= 0) ||
CLIOptions.o_filterByText.Value.Any(y => x.Container.IndexOf(y, StringComparison.OrdinalIgnoreCase) >= 0)
);
@@ -366,7 +383,7 @@ namespace AssetStudioCLI
);
break;
case FilterBy.NameAndContainer:
filteredAssets = parsedAssetsList.FindAll(x =>
filteredAssets = exportableAssetsList.FindAll(x =>
CLIOptions.o_filterByName.Value.Any(y => x.Text.IndexOf(y, StringComparison.OrdinalIgnoreCase) >= 0) &&
CLIOptions.o_filterByContainer.Value.Any(y => x.Container.IndexOf(y, StringComparison.OrdinalIgnoreCase) >= 0)
);
@@ -377,18 +394,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)
@@ -493,7 +510,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),
@@ -509,7 +526,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 ExportSplitObjects()
@@ -611,11 +628,13 @@ namespace AssetStudioCLI
{
var baseDestPath = Path.Combine(CLIOptions.o_outputFolder.Value, "Live2DOutput");
var useFullContainerPath = false;
var motionMode = CLIOptions.o_l2dMotionMode.Value;
var forceBezier = CLIOptions.f_l2dForceBezier.Value;
Progress.Reset();
Logger.Info($"Searching for Live2D files...");
var cubismMocs = parsedAssetsList.Where(x =>
var cubismMocs = exportableAssetsList.Where(x =>
{
if (x.Type == ClassIDType.MonoBehaviour)
{
@@ -624,6 +643,7 @@ namespace AssetStudioCLI
}
return false;
}).Select(x => x.Asset).ToArray();
if (cubismMocs.Length == 0)
{
Logger.Default.Log(LoggerEvent.Info, "Live2D Cubism models were not found.", ignoreLevel: true);
@@ -631,7 +651,18 @@ namespace AssetStudioCLI
}
if (cubismMocs.Length > 1)
{
var basePathSet = cubismMocs.Select(x => containers[x].Substring(0, containers[x].LastIndexOf("/"))).ToHashSet();
var basePathSet = cubismMocs.Select(x =>
{
var pathLen = containers.TryGetValue(x, out var itemContainer) ? itemContainer.LastIndexOf("/") : 0;
pathLen = pathLen < 0 ? containers[x].Length : pathLen;
return itemContainer?.Substring(0, pathLen);
}).ToHashSet();
if (basePathSet.All(x => x == null))
{
Logger.Error($"Live2D Cubism export error: Cannot find any model related files.");
return;
}
if (basePathSet.Count != cubismMocs.Length)
{
@@ -639,9 +670,16 @@ namespace AssetStudioCLI
Logger.Debug($"useFullContainerPath: {useFullContainerPath}");
}
}
var basePathList = useFullContainerPath ?
cubismMocs.Select(x => containers[x]).ToList() :
cubismMocs.Select(x => containers[x].Substring(0, containers[x].LastIndexOf("/"))).ToList();
var basePathList = cubismMocs.Select(x =>
{
containers.TryGetValue(x, out var container);
container = useFullContainerPath
? container
: container?.Substring(0, container.LastIndexOf("/"));
return container;
}).Where(x => x != null).ToList();
var lookup = containers.ToLookup(
x => basePathList.Find(b => x.Value.Contains(b) && x.Value.Split('/').Any(y => y == b.Substring(b.LastIndexOf("/") + 1))),
x => x.Key
@@ -649,31 +687,31 @@ namespace AssetStudioCLI
var totalModelCount = lookup.LongCount(x => x.Key != null);
Logger.Info($"Found {totalModelCount} model(s).");
var name = "";
var modelCounter = 0;
foreach (var assets in lookup)
{
var container = assets.Key;
if (container == null)
var srcContainer = assets.Key;
if (srcContainer == null)
continue;
name = container;
var container = srcContainer;
Logger.Info($"[{modelCounter + 1}/{totalModelCount}] Exporting Live2D: \"{container.Color(Ansi.BrightCyan)}\"");
Logger.Info($"[{modelCounter + 1}/{totalModelCount}] Exporting Live2D: \"{srcContainer.Color(Ansi.BrightCyan)}\"");
try
{
var modelName = useFullContainerPath ? Path.GetFileNameWithoutExtension(container) : container.Substring(container.LastIndexOf('/') + 1);
container = Path.HasExtension(container) ? container.Replace(Path.GetExtension(container), "") : container;
var destPath = Path.Combine(baseDestPath, container) + Path.DirectorySeparatorChar;
ExtractLive2D(assets, destPath, modelName, assemblyLoader);
ExtractLive2D(assets, destPath, modelName, assemblyLoader, motionMode, forceBezier);
modelCounter++;
}
catch (Exception ex)
{
Logger.Error($"Live2D model export error: \"{name}\"", ex);
Logger.Error($"Live2D model export error: \"{srcContainer}\"", ex);
}
Progress.Report(modelCounter, (int)totalModelCount);
}
var status = modelCounter > 0 ?
$"Finished exporting [{modelCounter}/{totalModelCount}] Live2D model(s) to \"{CLIOptions.o_outputFolder.Value.Color(Ansi.BrightCyan)}\"" :
"Nothing exported.";

View File

@@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net472;net6.0;net6.0-windows;net7.0;net7.0-windows</TargetFrameworks>
<TargetFrameworks>net472;net6.0;net6.0-windows;net7.0;net7.0-windows;net8.0;net8.0-windows</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Version>0.17.3.0</Version>
<Version>1.2.0</Version>
<Copyright>Copyright © Perfare 2018-2022; Copyright © hozuki 2020</Copyright>
<DebugType>embedded</DebugType>
</PropertyGroup>

View File

@@ -49,7 +49,7 @@
this.label7 = new System.Windows.Forms.Label();
this.modVersionLabel = new System.Windows.Forms.Label();
this.label4 = new System.Windows.Forms.Label();
this.label8 = new System.Windows.Forms.Label();
this.basedOnLabel = new System.Windows.Forms.Label();
this.checkUpdatesLinkLabel = new System.Windows.Forms.LinkLabel();
this.tabPage2 = new System.Windows.Forms.TabPage();
this.licenseRichTextBox = new System.Windows.Forms.RichTextBox();
@@ -116,8 +116,7 @@
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(347, 46);
this.label2.TabIndex = 0;
this.label2.Text = "AssetStudio is a tool for exploring, extracting, and exporting assets and asset b" +
"undles.";
this.label2.Text = "ArknightsStudio is a modified version of AssetStudio designed for Arknights.";
this.label2.UseCompatibleTextRendering = true;
//
// textBox2
@@ -164,7 +163,7 @@
this.tableLayoutPanel2.ColumnCount = 3;
this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 41.37931F));
this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 58.62069F));
this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 111F));
this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 113F));
this.tableLayoutPanel2.Controls.Add(this.label16, 0, 0);
this.tableLayoutPanel2.Controls.Add(this.label17, 1, 0);
this.tableLayoutPanel2.Controls.Add(this.gitPerfareLinkLabel, 2, 0);
@@ -194,7 +193,7 @@
//
this.label17.AutoSize = true;
this.label17.BackColor = System.Drawing.Color.Transparent;
this.label17.Location = new System.Drawing.Point(102, 2);
this.label17.Location = new System.Drawing.Point(101, 2);
this.label17.Name = "label17";
this.label17.Size = new System.Drawing.Size(110, 13);
this.label17.TabIndex = 10;
@@ -204,7 +203,7 @@
//
this.gitPerfareLinkLabel.AutoSize = true;
this.gitPerfareLinkLabel.BackColor = System.Drawing.Color.Transparent;
this.gitPerfareLinkLabel.Location = new System.Drawing.Point(239, 2);
this.gitPerfareLinkLabel.Location = new System.Drawing.Point(237, 2);
this.gitPerfareLinkLabel.Name = "gitPerfareLinkLabel";
this.gitPerfareLinkLabel.Size = new System.Drawing.Size(67, 13);
this.gitPerfareLinkLabel.TabIndex = 11;
@@ -226,7 +225,7 @@
//
this.label19.AutoSize = true;
this.label19.BackColor = System.Drawing.Color.Transparent;
this.label19.Location = new System.Drawing.Point(102, 20);
this.label19.Location = new System.Drawing.Point(101, 20);
this.label19.Name = "label19";
this.label19.Size = new System.Drawing.Size(113, 13);
this.label19.TabIndex = 13;
@@ -236,7 +235,7 @@
//
this.gitAelurumLinkLabel.AutoSize = true;
this.gitAelurumLinkLabel.BackColor = System.Drawing.Color.Transparent;
this.gitAelurumLinkLabel.Location = new System.Drawing.Point(239, 20);
this.gitAelurumLinkLabel.Location = new System.Drawing.Point(237, 20);
this.gitAelurumLinkLabel.Name = "gitAelurumLinkLabel";
this.gitAelurumLinkLabel.Size = new System.Drawing.Size(67, 13);
this.gitAelurumLinkLabel.TabIndex = 14;
@@ -250,13 +249,13 @@
this.tableLayoutPanel1.ColumnCount = 3;
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 41.37931F));
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 58.62069F));
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 111F));
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 113F));
this.tableLayoutPanel1.Controls.Add(this.label5, 0, 0);
this.tableLayoutPanel1.Controls.Add(this.productNamelabel, 1, 0);
this.tableLayoutPanel1.Controls.Add(this.label7, 0, 1);
this.tableLayoutPanel1.Controls.Add(this.modVersionLabel, 1, 1);
this.tableLayoutPanel1.Controls.Add(this.label4, 0, 2);
this.tableLayoutPanel1.Controls.Add(this.label8, 1, 2);
this.tableLayoutPanel1.Controls.Add(this.basedOnLabel, 1, 2);
this.tableLayoutPanel1.Controls.Add(this.checkUpdatesLinkLabel, 2, 1);
this.tableLayoutPanel1.Location = new System.Drawing.Point(6, 80);
this.tableLayoutPanel1.Name = "tableLayoutPanel1";
@@ -285,11 +284,11 @@
//
this.productNamelabel.AutoSize = true;
this.productNamelabel.BackColor = System.Drawing.Color.Transparent;
this.productNamelabel.Location = new System.Drawing.Point(102, 2);
this.productNamelabel.Location = new System.Drawing.Point(101, 2);
this.productNamelabel.Name = "productNamelabel";
this.productNamelabel.Size = new System.Drawing.Size(103, 13);
this.productNamelabel.Size = new System.Drawing.Size(100, 13);
this.productNamelabel.TabIndex = 1;
this.productNamelabel.Text = "AssetStudioModGUI";
this.productNamelabel.Text = "ArknightsStudioGUI";
//
// label7
//
@@ -297,16 +296,16 @@
this.label7.BackColor = System.Drawing.Color.Transparent;
this.label7.Location = new System.Drawing.Point(5, 20);
this.label7.Name = "label7";
this.label7.Size = new System.Drawing.Size(68, 13);
this.label7.Size = new System.Drawing.Size(45, 13);
this.label7.TabIndex = 2;
this.label7.Text = "Mod version:";
this.label7.Text = "Version:";
//
// modVersionLabel
//
this.modVersionLabel.AutoSize = true;
this.modVersionLabel.BackColor = System.Drawing.Color.Transparent;
this.modVersionLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.modVersionLabel.Location = new System.Drawing.Point(102, 20);
this.modVersionLabel.Location = new System.Drawing.Point(101, 20);
this.modVersionLabel.Name = "modVersionLabel";
this.modVersionLabel.Size = new System.Drawing.Size(52, 13);
this.modVersionLabel.TabIndex = 3;
@@ -323,21 +322,21 @@
this.label4.TabIndex = 4;
this.label4.Text = "Based on:";
//
// label8
// basedOnLabel
//
this.label8.AutoSize = true;
this.label8.BackColor = System.Drawing.Color.Transparent;
this.label8.Location = new System.Drawing.Point(102, 38);
this.label8.Name = "label8";
this.label8.Size = new System.Drawing.Size(108, 13);
this.label8.TabIndex = 5;
this.label8.Text = "AssetStudio v0.16.47";
this.basedOnLabel.AutoSize = true;
this.basedOnLabel.BackColor = System.Drawing.Color.Transparent;
this.basedOnLabel.Location = new System.Drawing.Point(101, 38);
this.basedOnLabel.Name = "basedOnLabel";
this.basedOnLabel.Size = new System.Drawing.Size(123, 13);
this.basedOnLabel.TabIndex = 5;
this.basedOnLabel.Text = "AssetStudioMod v0.17.0";
//
// checkUpdatesLinkLabel
//
this.checkUpdatesLinkLabel.AutoSize = true;
this.checkUpdatesLinkLabel.BackColor = System.Drawing.Color.Transparent;
this.checkUpdatesLinkLabel.Location = new System.Drawing.Point(239, 20);
this.checkUpdatesLinkLabel.Location = new System.Drawing.Point(237, 20);
this.checkUpdatesLinkLabel.Name = "checkUpdatesLinkLabel";
this.checkUpdatesLinkLabel.Size = new System.Drawing.Size(96, 13);
this.checkUpdatesLinkLabel.TabIndex = 6;
@@ -391,7 +390,7 @@
this.productTitleLabel.Name = "productTitleLabel";
this.productTitleLabel.Size = new System.Drawing.Size(384, 30);
this.productTitleLabel.TabIndex = 1;
this.productTitleLabel.Text = "AssetStudioModGUI";
this.productTitleLabel.Text = "ArknightsStudioGUI";
this.productTitleLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// CloseButton
@@ -492,7 +491,7 @@
private System.Windows.Forms.Label label7;
private System.Windows.Forms.Label modVersionLabel;
private System.Windows.Forms.Label label4;
private System.Windows.Forms.Label label8;
private System.Windows.Forms.Label basedOnLabel;
private System.Windows.Forms.LinkLabel checkUpdatesLinkLabel;
private System.Windows.Forms.RichTextBox licenseRichTextBox;
private System.Windows.Forms.TextBox textBox2;

View File

@@ -10,13 +10,16 @@ namespace AssetStudioGUI
public AboutForm()
{
InitializeComponent();
var productName = Application.ProductName;
var arch = Environment.Is64BitProcess ? "x64" : "x32";
var appAssembly = typeof(Program).Assembly.GetName();
var productName = appAssembly.Name;
var productVer = appAssembly.Version.ToString();
Text += " " + productName;
productTitleLabel.Text = productName;
productVersionLabel.Text = $"v{Application.ProductVersion} [{arch}]";
productVersionLabel.Text = $"v{productVer} [{arch}]";
productNamelabel.Text = productName;
modVersionLabel.Text = Application.ProductVersion;
modVersionLabel.Text = productVer;
basedOnLabel.Text = "AssetStudioMod v0.17.4";
licenseRichTextBox.Text = GetLicenseText();
}
@@ -41,7 +44,7 @@ namespace AssetStudioGUI
private void checkUpdatesLinkLabel_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
var ps = new ProcessStartInfo("https://github.com/aelurum/AssetStudio/releases")
var ps = new ProcessStartInfo("https://github.com/aelurum/AssetStudio/tags")
{
UseShellExecute = true
};

View File

@@ -2,13 +2,13 @@
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFrameworks>net472;net6.0-windows;net7.0-windows</TargetFrameworks>
<TargetFrameworks>net472;net6.0-windows;net7.0-windows;net8.0-windows</TargetFrameworks>
<UseWindowsForms>true</UseWindowsForms>
<ApplicationIcon>Resources\as.ico</ApplicationIcon>
<AssemblyTitle>AssetStudioMod by aelurum</AssemblyTitle>
<AssemblyName>AssetStudioModGUI</AssemblyName>
<Version>0.17.3.0</Version>
<Copyright>Copyright © Perfare 2018-2022; Copyright © aelurum 2021-2023</Copyright>
<AssemblyTitle>ArknightsStudio by aelurum</AssemblyTitle>
<AssemblyName>ArknightsStudioGUI</AssemblyName>
<Version>1.2.0</Version>
<Copyright>Copyright © Perfare 2018-2022; Copyright © aelurum 2021-2025</Copyright>
<DebugType>embedded</DebugType>
</PropertyGroup>
@@ -54,14 +54,14 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft-WindowsAPICodePack-Core-6.0" Version="1.1.6-preview.2.24bd88f" />
<PackageReference Include="Microsoft-WindowsAPICodePack-Shell-6.0" Version="1.1.6-preview.2.24bd88f" />
<PackageReference Include="Microsoft-WindowsAPICodePack-Core-6.0" Version="1.1.6" />
<PackageReference Include="Microsoft-WindowsAPICodePack-Shell-6.0" Version="1.1.6" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' != 'net472' ">
<PackageReference Include="OpenTK.Graphics" Version="4.8.0" />
<PackageReference Include="OpenTK.Windowing.Desktop" Version="4.8.0" />
<PackageReference Include="OpenTK.Graphics" Version="4.8.2" />
<PackageReference Include="OpenTK.Windowing.Desktop" Version="4.8.2" />
<Reference Include="OpenTK.WinForms">
<HintPath>Libraries\OpenTK.WinForms.dll</HintPath>
</Reference>

View File

@@ -41,6 +41,12 @@
this.displayAll = new System.Windows.Forms.ToolStripMenuItem();
this.enablePreview = new System.Windows.Forms.ToolStripMenuItem();
this.displayInfo = new System.Windows.Forms.ToolStripMenuItem();
this.akSeparator1 = new System.Windows.Forms.ToolStripSeparator();
this.akTitleMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.akFixFaceSpriteNamesToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.akUseExternalAlphaToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.akSeparator2 = new System.Windows.Forms.ToolStripSeparator();
this.buildTreeStructureToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.toolStripMenuItem14 = new System.Windows.Forms.ToolStripMenuItem();
this.specifyUnityVersion = new System.Windows.Forms.ToolStripTextBox();
this.showExpOpt = new System.Windows.Forms.ToolStripMenuItem();
@@ -77,6 +83,8 @@
this.allToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.debugMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.toolStripMenuItem15 = new System.Windows.Forms.ToolStripMenuItem();
this.showConsoleToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.writeLogToFileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.exportClassStructuresMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.aboutToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.splitContainer1 = new System.Windows.Forms.SplitContainer();
@@ -231,6 +239,12 @@
this.displayAll,
this.enablePreview,
this.displayInfo,
this.akSeparator1,
this.akTitleMenuItem,
this.akFixFaceSpriteNamesToolStripMenuItem,
this.akUseExternalAlphaToolStripMenuItem,
this.akSeparator2,
this.buildTreeStructureToolStripMenuItem,
this.toolStripMenuItem14,
this.showExpOpt});
this.optionsToolStripMenuItem.Name = "optionsToolStripMenuItem";
@@ -241,7 +255,7 @@
//
this.displayAll.CheckOnClick = true;
this.displayAll.Name = "displayAll";
this.displayAll.Size = new System.Drawing.Size(207, 22);
this.displayAll.Size = new System.Drawing.Size(276, 22);
this.displayAll.Text = "Display all assets";
this.displayAll.ToolTipText = "Check this option will display all types assets. Not extractable assets can expor" +
"t the RAW file.";
@@ -253,7 +267,7 @@
this.enablePreview.CheckOnClick = true;
this.enablePreview.CheckState = System.Windows.Forms.CheckState.Checked;
this.enablePreview.Name = "enablePreview";
this.enablePreview.Size = new System.Drawing.Size(207, 22);
this.enablePreview.Size = new System.Drawing.Size(276, 22);
this.enablePreview.Text = "Enable preview";
this.enablePreview.ToolTipText = "Toggle the loading and preview of readable assets, such as images, sounds, text, " +
"etc.\r\nDisable preview if you have performance or compatibility issues.";
@@ -265,18 +279,71 @@
this.displayInfo.CheckOnClick = true;
this.displayInfo.CheckState = System.Windows.Forms.CheckState.Checked;
this.displayInfo.Name = "displayInfo";
this.displayInfo.Size = new System.Drawing.Size(207, 22);
this.displayInfo.Size = new System.Drawing.Size(276, 22);
this.displayInfo.Text = "Display asset information";
this.displayInfo.ToolTipText = "Toggle the overlay that shows information about each asset, eg. image size, forma" +
"t, audio bitrate, etc.";
this.displayInfo.CheckedChanged += new System.EventHandler(this.displayAssetInfo_Check);
//
// akSeparator1
//
this.akSeparator1.Name = "akSeparator1";
this.akSeparator1.Size = new System.Drawing.Size(273, 6);
//
// akTitleMenuItem
//
this.akTitleMenuItem.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
this.akTitleMenuItem.Enabled = false;
this.akTitleMenuItem.Name = "akTitleMenuItem";
this.akTitleMenuItem.ShowShortcutKeys = false;
this.akTitleMenuItem.Size = new System.Drawing.Size(276, 22);
this.akTitleMenuItem.Text = "Arknights";
//
// akFixFaceSpriteNamesToolStripMenuItem
//
this.akFixFaceSpriteNamesToolStripMenuItem.Checked = true;
this.akFixFaceSpriteNamesToolStripMenuItem.CheckOnClick = true;
this.akFixFaceSpriteNamesToolStripMenuItem.CheckState = System.Windows.Forms.CheckState.Checked;
this.akFixFaceSpriteNamesToolStripMenuItem.Name = "akFixFaceSpriteNamesToolStripMenuItem";
this.akFixFaceSpriteNamesToolStripMenuItem.Size = new System.Drawing.Size(276, 22);
this.akFixFaceSpriteNamesToolStripMenuItem.Text = "Restore names of avg character sprites";
this.akFixFaceSpriteNamesToolStripMenuItem.ToolTipText = "Rename face sprites with numeric names to correct ones";
this.akFixFaceSpriteNamesToolStripMenuItem.CheckedChanged += new System.EventHandler(this.akFixFaceSpriteNamesToolStripMenuItem_Check);
//
// akUseExternalAlphaToolStripMenuItem
//
this.akUseExternalAlphaToolStripMenuItem.Checked = true;
this.akUseExternalAlphaToolStripMenuItem.CheckOnClick = true;
this.akUseExternalAlphaToolStripMenuItem.CheckState = System.Windows.Forms.CheckState.Checked;
this.akUseExternalAlphaToolStripMenuItem.Name = "akUseExternalAlphaToolStripMenuItem";
this.akUseExternalAlphaToolStripMenuItem.Size = new System.Drawing.Size(276, 22);
this.akUseExternalAlphaToolStripMenuItem.Text = "Use external alpha texture for sprites";
this.akUseExternalAlphaToolStripMenuItem.ToolTipText = "Trying to find an external alpha texture for preview/export sprite assets (Skins," +
" Char arts, Avg char arts, etc.)";
this.akUseExternalAlphaToolStripMenuItem.CheckedChanged += new System.EventHandler(this.akUseExternalAlphaToolStripMenuItem_Check);
//
// akSeparator2
//
this.akSeparator2.Name = "akSeparator2";
this.akSeparator2.Size = new System.Drawing.Size(273, 6);
//
// buildTreeStructureToolStripMenuItem
//
this.buildTreeStructureToolStripMenuItem.Checked = true;
this.buildTreeStructureToolStripMenuItem.CheckOnClick = true;
this.buildTreeStructureToolStripMenuItem.CheckState = System.Windows.Forms.CheckState.Checked;
this.buildTreeStructureToolStripMenuItem.Name = "buildTreeStructureToolStripMenuItem";
this.buildTreeStructureToolStripMenuItem.Size = new System.Drawing.Size(276, 22);
this.buildTreeStructureToolStripMenuItem.Text = "Build tree structure";
this.buildTreeStructureToolStripMenuItem.ToolTipText = "You can disable tree structure building if you don\'t use the Scene Hierarchy tab";
this.buildTreeStructureToolStripMenuItem.CheckedChanged += new System.EventHandler(this.buildTreeStructureToolStripMenuItem_CheckedChanged);
//
// toolStripMenuItem14
//
this.toolStripMenuItem14.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.specifyUnityVersion});
this.toolStripMenuItem14.Name = "toolStripMenuItem14";
this.toolStripMenuItem14.Size = new System.Drawing.Size(207, 22);
this.toolStripMenuItem14.Size = new System.Drawing.Size(276, 22);
this.toolStripMenuItem14.Text = "Specify Unity version";
//
// specifyUnityVersion
@@ -290,7 +357,7 @@
// showExpOpt
//
this.showExpOpt.Name = "showExpOpt";
this.showExpOpt.Size = new System.Drawing.Size(207, 22);
this.showExpOpt.Size = new System.Drawing.Size(276, 22);
this.showExpOpt.Text = "Export options";
this.showExpOpt.Click += new System.EventHandler(this.showExpOpt_Click);
//
@@ -536,6 +603,8 @@
//
this.debugMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.toolStripMenuItem15,
this.showConsoleToolStripMenuItem,
this.writeLogToFileToolStripMenuItem,
this.exportClassStructuresMenuItem});
this.debugMenuItem.Name = "debugMenuItem";
this.debugMenuItem.Size = new System.Drawing.Size(54, 20);
@@ -549,6 +618,24 @@
this.toolStripMenuItem15.Text = "Show all error messages";
this.toolStripMenuItem15.Click += new System.EventHandler(this.toolStripMenuItem15_Click);
//
// showConsoleToolStripMenuItem
//
this.showConsoleToolStripMenuItem.Checked = true;
this.showConsoleToolStripMenuItem.CheckOnClick = true;
this.showConsoleToolStripMenuItem.CheckState = System.Windows.Forms.CheckState.Checked;
this.showConsoleToolStripMenuItem.Name = "showConsoleToolStripMenuItem";
this.showConsoleToolStripMenuItem.Size = new System.Drawing.Size(200, 22);
this.showConsoleToolStripMenuItem.Text = "Show console logger";
this.showConsoleToolStripMenuItem.Click += new System.EventHandler(this.showConsoleToolStripMenuItem_Click);
//
// writeLogToFileToolStripMenuItem
//
this.writeLogToFileToolStripMenuItem.CheckOnClick = true;
this.writeLogToFileToolStripMenuItem.Name = "writeLogToFileToolStripMenuItem";
this.writeLogToFileToolStripMenuItem.Size = new System.Drawing.Size(200, 22);
this.writeLogToFileToolStripMenuItem.Text = "Write log to file";
this.writeLogToFileToolStripMenuItem.CheckedChanged += new System.EventHandler(this.writeLogToFileToolStripMenuItem_CheckedChanged);
//
// exportClassStructuresMenuItem
//
this.exportClassStructuresMenuItem.Name = "exportClassStructuresMenuItem";
@@ -1256,6 +1343,7 @@
this.Name = "AssetStudioGUIForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "AssetStudioModGUI";
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.AssetStudioGUIForm_FormClosing);
this.DragDrop += new System.Windows.Forms.DragEventHandler(this.AssetStudioGUIForm_DragDrop);
this.DragEnter += new System.Windows.Forms.DragEventHandler(this.AssetStudioGUIForm_DragEnter);
this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.AssetStudioForm_KeyDown);
@@ -1405,6 +1493,14 @@
private System.Windows.Forms.ToolStripMenuItem showRelatedAssetsToolStripMenuItem;
private System.Windows.Forms.ToolStripSeparator toolStripSeparator7;
private System.Windows.Forms.ListView assetListView;
private System.Windows.Forms.ToolStripMenuItem akFixFaceSpriteNamesToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem akUseExternalAlphaToolStripMenuItem;
private System.Windows.Forms.ToolStripSeparator akSeparator1;
private System.Windows.Forms.ToolStripMenuItem akTitleMenuItem;
private System.Windows.Forms.ToolStripSeparator akSeparator2;
private System.Windows.Forms.ToolStripMenuItem showConsoleToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem writeLogToFileToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem buildTreeStructureToolStripMenuItem;
}
}

View File

@@ -1,4 +1,5 @@
using AssetStudio;
using Arknights;
using AssetStudio;
using Newtonsoft.Json;
using OpenTK.Graphics.OpenGL;
using System;
@@ -18,6 +19,8 @@ using System.Timers;
using System.Windows.Forms;
using static AssetStudioGUI.Studio;
using Font = AssetStudio.Font;
using SharpImage = SixLabors.ImageSharp;
using SharpImageFormat = SixLabors.ImageSharp.PixelFormats;
using Microsoft.WindowsAPICodePack.Taskbar;
#if NET472
using OpenTK;
@@ -47,6 +50,7 @@ namespace AssetStudioGUI
#region SpriteControl
private SpriteMaskMode spriteMaskVisibleMode = SpriteMaskMode.On;
private bool showDebugInfo = false;
#endregion
#region TexControl
@@ -112,21 +116,34 @@ namespace AssetStudioGUI
[DllImport("gdi32.dll")]
private static extern IntPtr AddFontMemResourceEx(IntPtr pbFont, uint cbFont, IntPtr pdv, [In] ref uint pcFonts);
private string guiTitle = string.Empty;
public AssetStudioGUIForm()
{
Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
ConsoleWindow.RunConsole(Properties.Settings.Default.showConsole);
InitializeComponent();
Text = $"{Application.ProductName} v{Application.ProductVersion}";
var appAssembly = typeof(Program).Assembly.GetName();
guiTitle = $"{appAssembly.Name} v{appAssembly.Version}";
Text = guiTitle;
delayTimer = new System.Timers.Timer(800);
delayTimer.Elapsed += new ElapsedEventHandler(delayTimer_Elapsed);
delayTimer.Elapsed += delayTimer_Elapsed;
displayAll.Checked = Properties.Settings.Default.displayAll;
displayInfo.Checked = Properties.Settings.Default.displayInfo;
enablePreview.Checked = Properties.Settings.Default.enablePreview;
akFixFaceSpriteNamesToolStripMenuItem.Checked = Properties.Settings.Default.fixFaceSpriteNames;
akUseExternalAlphaToolStripMenuItem.Checked = Properties.Settings.Default.useExternalAlpha;
showConsoleToolStripMenuItem.Checked = Properties.Settings.Default.showConsole;
buildTreeStructureToolStripMenuItem.Checked = Properties.Settings.Default.buildTreeStructure;
FMODinit();
listSearchFilterMode.SelectedIndex = 0;
logger = new GUILogger(StatusStripUpdate);
Logger.Default = logger;
writeLogToFileToolStripMenuItem.Checked = Properties.Settings.Default.useFileLogger;
Progress.Default = new Progress<int>(SetProgressBarValue);
Studio.StatusStripUpdate = StatusStripUpdate;
}
@@ -135,21 +152,32 @@ namespace AssetStudioGUI
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
e.Effect = DragDropEffects.Move;
e.Effect = DragDropEffects.Copy;
}
}
private async void AssetStudioGUIForm_DragDrop(object sender, DragEventArgs e)
{
var paths = (string[])e.Data.GetData(DataFormats.FileDrop);
if (paths.Length > 0)
if (paths.Length == 0)
return;
ResetForm();
for (var i = 0; i < paths.Length; i++)
{
ResetForm();
assetsManager.SpecifyUnityVersion = specifyUnityVersion.Text;
await Task.Run(() => assetsManager.LoadFilesAndFolders(out openDirectoryBackup, paths));
saveDirectoryBackup = openDirectoryBackup;
BuildAssetStructures();
if (paths[i].ToLower().EndsWith(".lnk"))
{
var targetPath = LnkReader.GetLnkTarget(paths[i]);
if (!string.IsNullOrEmpty(targetPath))
{
paths[i] = targetPath;
}
}
}
assetsManager.SpecifyUnityVersion = specifyUnityVersion.Text;
await Task.Run(() => assetsManager.LoadFilesAndFolders(out openDirectoryBackup, paths));
saveDirectoryBackup = openDirectoryBackup;
BuildAssetStructures();
}
private async void loadFile_Click(object sender, EventArgs e)
@@ -218,17 +246,11 @@ namespace AssetStudioGUI
return;
}
(var productName, var treeNodeCollection) = await Task.Run(() => BuildAssetData());
var (productName, treeNodeCollection) = await Task.Run(() => BuildAssetData());
var typeMap = await Task.Run(() => BuildClassStructure());
productName = string.IsNullOrEmpty(productName) ? "no productName" : productName;
if (!string.IsNullOrEmpty(productName))
{
Text = $"{Application.ProductName} v{Application.ProductVersion} - {productName} - {assetsManager.assetsFileList[0].unityVersion} - {assetsManager.assetsFileList[0].m_TargetPlatform}";
}
else
{
Text = $"{Application.ProductName} v{Application.ProductVersion} - no productName - {assetsManager.assetsFileList[0].unityVersion} - {assetsManager.assetsFileList[0].m_TargetPlatform}";
}
Text = $"{guiTitle} - {productName} - {assetsManager.assetsFileList[0].unityVersion} - {assetsManager.assetsFileList[0].m_TargetPlatform}";
assetListView.VirtualListSize = visibleAssets.Count;
@@ -252,6 +274,9 @@ namespace AssetStudioGUI
typeMap.Clear();
classesListView.EndUpdate();
if (akFixFaceSpriteNamesToolStripMenuItem.Checked)
FixFaceSpriteNames();
var types = exportableAssets.Select(x => x.Type).Distinct().OrderBy(x => x.ToString()).ToArray();
foreach (var type in types)
{
@@ -351,7 +376,7 @@ namespace AssetStudioGUI
break;
}
}
else if (lastSelectedItem?.Type == ClassIDType.Sprite && !((Sprite)lastSelectedItem.Asset).m_RD.alphaTexture.IsNull)
else if ((lastSelectedItem?.Type == ClassIDType.Sprite && !((Sprite)lastSelectedItem.Asset).m_RD.alphaTexture.IsNull) || lastSelectedItem?.Type == ClassIDType.AkPortraitSprite)
{
switch (e.KeyCode)
{
@@ -363,6 +388,10 @@ namespace AssetStudioGUI
spriteMaskVisibleMode = spriteMaskVisibleMode == SpriteMaskMode.MaskOnly ? SpriteMaskMode.On : SpriteMaskMode.MaskOnly;
need = true;
break;
case Keys.D:
showDebugInfo = !showDebugInfo;
need = true;
break;
}
}
if (need)
@@ -417,6 +446,7 @@ namespace AssetStudioGUI
switch (lastSelectedItem.Type)
{
case ClassIDType.Texture2D:
case ClassIDType.AkPortraitSprite:
case ClassIDType.Sprite:
{
if (enablePreview.Checked && imageTexture != null)
@@ -817,6 +847,9 @@ namespace AssetStudioGUI
case ClassIDType.Sprite:
PreviewSprite(assetItem, assetItem.Asset as Sprite);
break;
case ClassIDType.AkPortraitSprite:
PreviewAkPortraitSprite(assetItem);
break;
case ClassIDType.Animator:
StatusStripUpdate("Can be exported to FBX file.");
break;
@@ -1262,7 +1295,31 @@ namespace AssetStudioGUI
private void PreviewSprite(AssetItem assetItem, Sprite m_Sprite)
{
var image = m_Sprite.GetImage(spriteMaskMode: spriteMaskVisibleMode);
SharpImage.Image<SharpImageFormat.Bgra32> image;
AvgSprite avgSprite = null;
bool isCharAvgSprite = assetItem.Container.Contains("avg/characters");
bool isCharArt = assetItem.Container.Contains("arts/characters");
if (akUseExternalAlphaToolStripMenuItem.Checked && (isCharAvgSprite || isCharArt))
{
avgSprite = isCharAvgSprite ? new AvgSprite(assetItem) : null;
if (m_Sprite.m_RD.alphaTexture.IsNull)
{
var charAlphaTex = AkSpriteHelper.TryFindAlphaTex(assetItem, avgSprite, isCharAvgSprite);
if (charAlphaTex != null)
{
m_Sprite.m_RD.alphaTexture.Set(charAlphaTex);
m_Sprite.akSplitAlpha = true;
}
}
image = m_Sprite.AkGetImage(avgSprite, spriteMaskMode: spriteMaskVisibleMode);
}
else
{
image = m_Sprite.GetImage(spriteMaskMode: spriteMaskVisibleMode);
}
if (image != null)
{
var bitmap = new DirectBitmap(image);
@@ -1270,10 +1327,33 @@ namespace AssetStudioGUI
assetItem.InfoText = $"Width: {bitmap.Width}\nHeight: {bitmap.Height}\n";
PreviewTexture(bitmap);
if (!m_Sprite.m_RD.alphaTexture.IsNull)
if (!m_Sprite.m_RD.alphaTexture.IsNull && (akUseExternalAlphaToolStripMenuItem.Checked || !m_Sprite.akSplitAlpha))
{
assetItem.InfoText += $"Alpha Mask: {spriteMaskVisibleMode}\n";
StatusStripUpdate("'Ctrl'+'A' - Enable/Disable alpha mask usage. 'Ctrl'+'M' - Show alpha mask only.");
var sb = new StringBuilder();
sb.AppendLine($"Alpha mask: {spriteMaskVisibleMode}");
sb.Append(spriteMaskVisibleMode != SpriteMaskMode.Off ? $"Is external mask: {m_Sprite.akSplitAlpha}\n" : "");
if (avgSprite != null)
{
sb.AppendLine($"Alias: \"{avgSprite.Alias}\"");
if (showDebugInfo)
{
sb.AppendLine($"[Debug]");
sb.AppendLine($"Is avg hub parsed: {avgSprite.IsHubParsed}");
if (avgSprite.IsHubParsed)
{
sb.AppendLine($"Is face data exist: {avgSprite.FaceSize.Width > 0}");
sb.AppendLine($"Is face sprite: {avgSprite.IsFaceSprite}");
sb.AppendLine($"Is whole body sprite: {avgSprite.IsWholeBodySprite}");
}
}
StatusStripUpdate("'Ctrl'+'A' - Enable/Disable alpha mask usage. 'Ctrl'+'M' - Show alpha mask only. 'Ctrl'+'D' - Show debug info.");
}
else
{
StatusStripUpdate("'Ctrl'+'A' - Enable/Disable alpha mask usage. 'Ctrl'+'M' - Show alpha mask only.");
}
assetItem.InfoText += sb.ToString();
}
}
else
@@ -1282,6 +1362,25 @@ namespace AssetStudioGUI
}
}
private void PreviewAkPortraitSprite(AssetItem assetItem)
{
var image = assetItem.AkPortraitSprite.AkGetImage(spriteMaskMode: spriteMaskVisibleMode);
if (image != null)
{
var bitmap = new DirectBitmap(image);
image.Dispose();
assetItem.InfoText = $"Width: {bitmap.Width}\nHeight: {bitmap.Height}\n";
assetItem.InfoText += $"Alpha mask: {spriteMaskVisibleMode}";
PreviewTexture(bitmap);
StatusStripUpdate("'Ctrl'+'A' - Enable/Disable alpha mask usage. 'Ctrl'+'M' - Show alpha mask only.");
}
else
{
StatusStripUpdate("Unsupported sprite for preview.");
}
}
private void PreviewTexture(DirectBitmap bitmap)
{
imageTexture?.Dispose();
@@ -1340,7 +1439,7 @@ namespace AssetStudioGUI
private void ResetForm()
{
Text = $"{Application.ProductName} v{Application.ProductVersion}";
Text = guiTitle;
assetsManager.Clear();
assemblyLoader.Clear();
exportableAssets.Clear();
@@ -1379,6 +1478,34 @@ namespace AssetStudioGUI
FMODreset();
}
private void FixFaceSpriteNames()
{
assetListView.BeginUpdate();
for (int i = 0; i < assetListView.Items.Count; i++)
{
var assetItem = (AssetItem)assetListView.Items[i];
if (assetItem.Type == ClassIDType.Sprite)
{
var m_Sprite = (Sprite)assetItem.Asset;
if (akFixFaceSpriteNamesToolStripMenuItem.Checked)
{
var groupedPattern = new Regex(@"^\d{1,2}\$\d{1,2}$"); // "spriteIndex$groupIndex"
var notGroupedPattern = new Regex(@"^\d{1,2}$"); // "spriteIndex"
if (groupedPattern.IsMatch(m_Sprite.m_Name) || notGroupedPattern.IsMatch(m_Sprite.m_Name))
{
var fullName = Path.GetFileNameWithoutExtension(assetItem.Container);
assetItem.Text = $"{fullName}#{m_Sprite.m_Name}";
}
}
else if (assetItem.Text != m_Sprite.m_Name)
{
assetItem.Text = m_Sprite.m_Name;
}
}
}
assetListView.EndUpdate();
}
private void tabControl2_SelectedIndexChanged(object sender, EventArgs e)
{
if (tabControl2.SelectedIndex == 1 && lastSelectedItem != null)
@@ -1867,6 +1994,26 @@ namespace AssetStudioGUI
sceneTreeView.EndUpdate();
}
private void akFixFaceSpriteNamesToolStripMenuItem_Check(object sender, EventArgs e)
{
Properties.Settings.Default.fixFaceSpriteNames = akFixFaceSpriteNamesToolStripMenuItem.Checked;
Properties.Settings.Default.Save();
FixFaceSpriteNames();
}
private void akUseExternalAlphaToolStripMenuItem_Check(object sender, EventArgs e)
{
Properties.Settings.Default.useExternalAlpha = akUseExternalAlphaToolStripMenuItem.Checked;
Properties.Settings.Default.Save();
if (lastSelectedItem?.Type == ClassIDType.Sprite)
{
StatusStripUpdate("");
PreviewAsset(lastSelectedItem);
assetInfoLabel.Text = lastSelectedItem.InfoText;
}
}
private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
{
var aboutForm = new AboutForm();
@@ -1996,6 +2143,38 @@ namespace AssetStudioGUI
}
}
private void showConsoleToolStripMenuItem_Click(object sender, EventArgs e)
{
var showConsole = showConsoleToolStripMenuItem.Checked;
if (showConsole)
ConsoleWindow.ShowConsoleWindow();
else
ConsoleWindow.HideConsoleWindow();
Properties.Settings.Default.showConsole = showConsole;
Properties.Settings.Default.Save();
}
private void writeLogToFileToolStripMenuItem_CheckedChanged(object sender, EventArgs e)
{
var useFileLogger = writeLogToFileToolStripMenuItem.Checked;
logger.UseFileLogger = useFileLogger;
Properties.Settings.Default.useFileLogger = useFileLogger;
Properties.Settings.Default.Save();
}
private void AssetStudioGUIForm_FormClosing(object sender, FormClosingEventArgs e)
{
Logger.Verbose("Closing AssetStudio");
}
private void buildTreeStructureToolStripMenuItem_CheckedChanged(object sender, EventArgs e)
{
Properties.Settings.Default.buildTreeStructure = buildTreeStructureToolStripMenuItem.Checked;
Properties.Settings.Default.Save();
}
#region FMOD
private void FMODinit()
{

View File

@@ -0,0 +1,299 @@
using Arknights.PortraitSpriteMono;
using AssetStudio;
using AssetStudioGUI;
using AssetStudioGUI.Properties;
using Newtonsoft.Json;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Transforms;
using System;
using System.Collections.Generic;
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.exportableAssets)
{
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;
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 = null;
Image<Bgra32> alphaTex = null;
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);
tex = avgSprite.FullTexture.ConvertToImage(true);
var facePos = tex.Width == 512 ? avgSprite.FacePos / 2 : avgSprite.FacePos; // ?
var faceSize = tex.Width == 512 ? avgSprite.FaceSize / 2 : avgSprite.FaceSize;
if (new Size(faceImage.Width, faceImage.Height) != avgSprite.FaceSize)
{
faceImage.Mutate(x => x.Resize(new ResizeOptions { Size = faceSize, Sampler = KnownResamplers.Lanczos3, Mode = ResizeMode.Stretch }));
faceAlpha.Mutate(x => x.Resize(new ResizeOptions { Size = faceSize, Sampler = KnownResamplers.Lanczos3, Mode = ResizeMode.Stretch }));
}
tex.Mutate(x => x.DrawImage(faceImage, facePos, opacity: 1f));
alphaTex.Mutate(x => x.DrawImage(faceAlpha, facePos, opacity: 1f));
}
else
{
tex = m_Texture2D.ConvertToImage(true);
}
}
else
{
if (spriteMaskMode != SpriteMaskMode.MaskOnly)
{
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);
}
return ImageRender(tex, alphaTex, spriteMaskMode);
}
else if (m_Sprite.m_RD.texture.TryGet(out m_Texture2D) && avgSprite != null && avgSprite.IsHubParsed)
{
if (!avgSprite.IsFaceSprite)
{
return m_Texture2D.ConvertToImage(true);
}
var faceImage = m_Texture2D.ConvertToImage(true);
var tex = avgSprite.FullTexture.ConvertToImage(true);
if (new Size(faceImage.Width, faceImage.Height) != avgSprite.FaceSize)
{
faceImage.Mutate(x => x.Resize(new ResizeOptions {Size = avgSprite.FaceSize, Sampler = KnownResamplers.Lanczos3, Mode = ResizeMode.Stretch}));
}
tex.Mutate(x => x.DrawImage(faceImage, avgSprite.FacePos, opacity: 1f));
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;
}
public static Image<Bgra32> AkGetImage(this PortraitSprite portraitSprite, SpriteMaskMode spriteMaskMode = SpriteMaskMode.On)
{
if (portraitSprite.Texture != null && portraitSprite.AlphaTexture != null)
{
Image<Bgra32> tex = null;
Image<Bgra32> alphaTex = null;
if (spriteMaskMode != SpriteMaskMode.MaskOnly)
{
tex = CutImage(portraitSprite.Texture.ConvertToImage(false), portraitSprite.TextureRect, portraitSprite.DownscaleMultiplier, portraitSprite.Rotate);
}
if (spriteMaskMode != SpriteMaskMode.Off)
{
alphaTex = CutImage(portraitSprite.AlphaTexture.ConvertToImage(false), portraitSprite.TextureRect, portraitSprite.DownscaleMultiplier, portraitSprite.Rotate);
}
return ImageRender(tex, alphaTex, spriteMaskMode);
}
return null;
}
public static List<PortraitSprite> GeneratePortraits(AssetItem asset)
{
var portraits = new List<PortraitSprite>();
var portraitsDict = ((MonoBehaviour)asset.Asset).ToType();
if (portraitsDict == null)
{
Logger.Warning("Portraits MonoBehaviour is not readable.");
return portraits;
}
var portraitsJson = JsonConvert.SerializeObject(portraitsDict);
var portraitsData = JsonConvert.DeserializeObject<PortraitSpriteConfig>(portraitsJson);
if (portraitsData._sprites.Length == 0)
return portraits;
var atlasTex = (Texture2D)Studio.exportableAssets.Find(x => x.m_PathID == portraitsData._atlas.Texture.m_PathID).Asset;
var atlasAlpha = (Texture2D)Studio.exportableAssets.Find(x => x.m_PathID == portraitsData._atlas.Alpha.m_PathID).Asset;
foreach (var portraitData in portraitsData._sprites)
{
var portraitSprite = new PortraitSprite()
{
Name = portraitData.Name,
AssetsFile = atlasTex.assetsFile,
Container = asset.Container,
Texture = atlasTex,
AlphaTexture = atlasAlpha,
TextureRect = new Rectf(portraitData.Rect.X, portraitData.Rect.Y, portraitData.Rect.W, portraitData.Rect.H),
Rotate = portraitData.Rotate,
};
portraits.Add(portraitSprite);
}
return portraits;
}
private static Image<Bgra32> ImageRender(Image<Bgra32> tex, Image<Bgra32> alpha, SpriteMaskMode maskMode)
{
switch (maskMode)
{
case SpriteMaskMode.On:
tex.ApplyRGBMask(alpha, isPreview: true);
return tex;
case SpriteMaskMode.Off:
alpha?.Dispose();
return tex;
case SpriteMaskMode.MaskOnly:
tex?.Dispose();
return alpha;
case SpriteMaskMode.Export:
tex.ApplyRGBMask(alpha);
return tex;
}
return null;
}
private static IResampler GetResampler(bool isPreview)
{
IResampler resampler;
if (isPreview)
{
resampler = KnownResamplers.NearestNeighbor;
}
else
{
switch (Settings.Default.resamplerIndex)
{
case 0:
resampler = KnownResamplers.NearestNeighbor;
break;
case 1: //Bilinear
resampler = KnownResamplers.Triangle;
break;
case 2:
resampler = KnownResamplers.Bicubic;
break;
case 3:
resampler = KnownResamplers.MitchellNetravali;
break;
case 4:
resampler = KnownResamplers.Spline;
break;
case 5:
resampler = KnownResamplers.Welch;
break;
default:
resampler = KnownResamplers.MitchellNetravali;
break;
}
}
return resampler;
}
private static void ApplyRGBMask(this Image<Bgra32> tex, Image<Bgra32> texMask, bool isPreview = false)
{
using (texMask)
{
bool resized = false;
if (tex.Width != texMask.Width || tex.Height != texMask.Height)
{
texMask.Mutate(x => x.Resize(tex.Width, tex.Height, GetResampler(isPreview)));
resized = true;
}
var invGamma = 1.0 / (1.0 + Settings.Default.alphaMaskGamma / 10.0);
if (Settings.Default.resizedOnly && !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)
{
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, bool rotate = false)
{
if (originalImage != null)
{
if (downscaleMultiplier > 0f && downscaleMultiplier != 1f)
{
var newSize = (Size)(new Size(originalImage.Width, originalImage.Height) / 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));
originalImage.Dispose();
if (rotate)
{
spriteImage.Mutate(x => x.Rotate(RotateMode.Rotate270));
}
spriteImage.Mutate(x => x.Flip(FlipMode.Vertical));
return spriteImage;
}
return null;
}
}
}

View File

@@ -0,0 +1,149 @@
using Arknights.AvgCharHubMono;
using AssetStudio;
using AssetStudioGUI;
using SixLabors.ImageSharp;
using System.Linq;
using System;
using System.Collections.Specialized;
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 scriptAssets = Studio.exportableAssets.FindAll(x =>
x.Type == ClassIDType.MonoBehaviour
&& x.Container == assetItem.Container);
if (scriptAssets.Count == 0)
{
Logger.Warning("No MonoBehaviours were found.");
return false;
}
OrderedDictionary spriteHubDict = null;
var isGrouped = false;
foreach (var scriptAsset in scriptAssets)
{
var scriptAssetDict = ((MonoBehaviour)scriptAsset.Asset).ToType();
if (scriptAssetDict == null)
{
Logger.Warning("MonoBehaviour is not readable.");
return false;
}
if (scriptAssetDict.Contains("spriteGroups"))
{
spriteHubDict = scriptAssetDict;
isGrouped = true;
break;
}
if (scriptAssetDict.Contains("sprites"))
{
spriteHubDict = scriptAssetDict;
break;
}
}
if (spriteHubDict == null)
{
Logger.Warning("AVGCharacterSpriteHub is not readable.");
return false;
}
var spriteHubJson = JsonConvert.SerializeObject(spriteHubDict);
if (isGrouped)
{
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)
{
Studio.StatusStripUpdate($"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) //If face data exist
{
var fullTexSpriteData = spriteHubData.Sprites.Last(); //Last sprite item in the list usually contains PathID of Sprite with full texture
var curSprite = (Sprite)assetItem.Asset;
IsFaceSprite = curSprite.m_Rect.width <= 256 && curSprite.m_Rect.height <= 256 && curSprite.m_PathID != fullTexSpriteData.Sprite.m_PathID;
var curSpriteAlphaID = curSpriteData.AlphaTex.m_PathID;
var curSpriteAlphaTex = (Texture2D)Studio.exportableAssets.Find(x => x.m_PathID == curSpriteAlphaID)?.Asset;
if (curSpriteAlphaTex != null)
{
FaceSpriteAlphaTexture = IsFaceSprite ? curSpriteAlphaTex : null;
fullTexSpriteData = IsFaceSprite ? fullTexSpriteData : curSpriteData;
}
var fullTexSpriteID = fullTexSpriteData.Sprite.m_PathID;
var fullTexAlphaID = fullTexSpriteData.AlphaTex.m_PathID;
var fullTexSprite = (Sprite)Studio.exportableAssets.Find(x => x.m_PathID == fullTexSpriteID).Asset;
FullTexture = fullTexSprite.m_RD.texture.TryGet(out var fullTex) ? fullTex : null;
FullAlphaTexture = (Texture2D)Studio.exportableAssets.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));
}
else
{
FullAlphaTexture = (Texture2D)Studio.exportableAssets.Find(x => x.m_PathID == curSpriteData.AlphaTex.m_PathID)?.Asset;
}
}
}
}
}

View File

@@ -0,0 +1,30 @@
using AssetStudio;
namespace Arknights.AvgCharHubMono
{
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

@@ -0,0 +1,24 @@
using AssetStudio;
namespace Arknights
{
internal class PortraitSprite
{
public string Name { get; set; }
public ClassIDType Type { get; }
public SerializedFile AssetsFile { get; set; }
public string Container { get; set; }
public Texture2D Texture { get; set; }
public Texture2D AlphaTexture { get; set; }
public Rectf TextureRect { get; set; }
public bool Rotate { get; set; }
public float DownscaleMultiplier { get; }
public PortraitSprite()
{
Type = ClassIDType.AkPortraitSprite;
DownscaleMultiplier = 1f;
}
}
}

View File

@@ -0,0 +1,41 @@
namespace Arknights.PortraitSpriteMono
{
internal class PortraitRect
{
public float X { get; set; }
public float Y { get; set; }
public float W { get; set; }
public float H { get; set; }
}
internal class AtlasSprite
{
public string Name { get; set; }
public string Guid { get; set; }
public int Atlas { get; set; }
public PortraitRect Rect { get; set; }
public bool Rotate { get; set; }
}
internal class TextureIDs
{
public int m_FileID { get; set; }
public long m_PathID { get; set; }
}
internal class AtlasInfo
{
public int Index { get; set; }
public TextureIDs Texture { get; set; }
public TextureIDs Alpha { get; set; }
public int Size { get; set; }
}
internal class PortraitSpriteConfig
{
public string m_Name { get; set; }
public AtlasSprite[] _sprites { get; set; }
public AtlasInfo _atlas { get; set; }
public int _index { get; set; }
}
}

View File

@@ -1,5 +1,6 @@
using System.Windows.Forms;
using AssetStudio;
using Arknights;
namespace AssetStudioGUI
{
@@ -15,6 +16,7 @@ namespace AssetStudioGUI
public string InfoText;
public string UniqueID;
public GameObjectTreeNode TreeNode;
public PortraitSprite AkPortraitSprite;
public AssetItem(Object asset)
{
@@ -26,6 +28,19 @@ namespace AssetStudioGUI
FullSize = asset.byteSize;
}
public AssetItem(PortraitSprite akPortraitSprite)
{
Asset = null;
SourceFile = akPortraitSprite.AssetsFile;
Container = akPortraitSprite.Container;
Type = akPortraitSprite.Type;
TypeString = Type.ToString();
Text = akPortraitSprite.Name;
m_PathID = -1;
FullSize = (long)(akPortraitSprite.TextureRect.width * akPortraitSprite.TextureRect.height * 4);
AkPortraitSprite = akPortraitSprite;
}
public void SetSubItems()
{
SubItems.AddRange(new[]

View File

@@ -0,0 +1,67 @@
using System;
using System.Runtime.InteropServices;
using AssetStudio;
namespace AssetStudioGUI
{
internal static class ConsoleWindow
{
private enum CtrlSignalType
{
CTRL_C_EVENT,
CTRL_BREAK_EVENT,
}
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool AllocConsole();
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr GetConsoleWindow();
[DllImport("user32.dll", SetLastError = true)]
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetConsoleCtrlHandler(EventHandler handler, bool add);
private delegate bool EventHandler(CtrlSignalType ctrlSignal);
private static EventHandler eventHandler;
private static IntPtr ConsoleWindowHandle;
private static readonly int SW_HIDE = 0;
private static readonly int SW_SHOW = 5;
private static bool CloseEventHandler(CtrlSignalType ctrlSignal)
{
switch (ctrlSignal)
{
case CtrlSignalType.CTRL_C_EVENT:
case CtrlSignalType.CTRL_BREAK_EVENT:
return true;
default:
Logger.Verbose("Closing AssetStudio");
return false;
}
}
public static void RunConsole(bool showConsole)
{
AllocConsole();
ConsoleWindowHandle = GetConsoleWindow();
eventHandler += CloseEventHandler;
SetConsoleCtrlHandler(eventHandler, true);
if (!showConsole)
HideConsoleWindow();
}
public static void ShowConsoleWindow()
{
ShowWindow(ConsoleWindowHandle, SW_SHOW);
}
public static void HideConsoleWindow()
{
ShowWindow(ConsoleWindowHandle, SW_HIDE);
}
}
}

View File

@@ -0,0 +1,164 @@
// Shortcut (.lnk) file reader
// by aelurum
// Based on https://github.com/libyal/liblnk/blob/main/documentation/Windows%20Shortcut%20File%20(LNK)%20format.asciidoc
using AssetStudio;
using System;
using System.IO;
using System.Text;
namespace AssetStudioGUI
{
public static class LnkReader
{
[Flags]
private enum LnkDataFlags
{
//The LNK file contains a link target identifier
HasTargetIDList = 0x00000001,
//The LNK file contains location information
HasLinkInfo = 0x00000002,
}
[Flags]
private enum LnkLocFlags
{
//The linked file is on a volume
//If set the volume information and the local path contain data
VolumeIDAndLocalBasePath = 0x0001,
//The linked file is on a network share
//If set the network share information and common path contain data
CommonNetworkRelativeLinkAndPathSuffix = 0x0002
}
[Flags]
private enum PathTypeFlags
{
IsUnicodeLocalPath = 0x01,
IsUnicodeNetShareName = 0x02,
IsUnicodeCommonPath = 0x04
}
public static string GetLnkTarget(string filePath)
{
var targetPath = string.Empty;
var pathType = (PathTypeFlags)0;
Encoding sysEncoding;
try
{
sysEncoding = GetSysEncoding();
Logger.Debug($"System default text encoding: {sysEncoding.CodePage}");
}
catch (Exception ex)
{
Logger.Error("Text encoding error", ex);
return null;
}
using (var reader = new FileReader(filePath))
{
reader.Endian = EndianType.LittleEndian;
var headerSize = reader.ReadUInt32(); //76 bytes
reader.Position = 20; //skip LNK class identifier (GUID)
var dataFlags = (LnkDataFlags)reader.ReadUInt32();
if ((dataFlags & LnkDataFlags.HasLinkInfo) == 0)
{
Logger.Warning("Unsupported type of .lnk file. Link info was not found.");
return null;
}
reader.Position = headerSize;
//Skip the shell item ID list
if ((dataFlags & LnkDataFlags.HasTargetIDList) != 0)
{
var itemIDListSize = reader.ReadUInt16();
reader.Position += itemIDListSize;
}
//The offsets is relative to the start of the location information block
var locInfoPos = reader.Position;
var locInfoFullSize = reader.ReadUInt32();
if (locInfoFullSize == 0)
{
Logger.Warning("Unsupported type of .lnk file. Link info was not found.");
return null;
}
var locInfoHeaderSize = reader.ReadUInt32();
var locFlags = (LnkLocFlags)reader.ReadUInt32();
//Offset to the volume information block
var offsetVolumeInfo = reader.ReadUInt32();
//Offset to the ANSI local path
var offsetLocalPath = reader.ReadUInt32();
//Offset to the network share information block
var offsetNetInfo = reader.ReadUInt32();
//Offset to the ANSI common path. 0 if not available
var offsetCommonPath = reader.ReadUInt32();
if (locInfoHeaderSize > 28)
{
//Offset to the Unicode local path
offsetLocalPath = reader.ReadUInt32();
pathType |= PathTypeFlags.IsUnicodeLocalPath;
}
if (locInfoHeaderSize > 32)
{
//Offset to the Unicode common path
offsetCommonPath = reader.ReadUInt32();
pathType |= PathTypeFlags.IsUnicodeCommonPath;
}
//Read local path, if exist
if (offsetLocalPath > 0)
{
reader.Position = locInfoPos + offsetLocalPath;
targetPath = (pathType & PathTypeFlags.IsUnicodeLocalPath) != 0
? reader.ReadStringToNull(encoding: Encoding.Unicode)
: reader.ReadStringToNull(encoding: sysEncoding);
}
//Read network path, if exist
if (locFlags == LnkLocFlags.CommonNetworkRelativeLinkAndPathSuffix)
{
reader.Position = locInfoPos + offsetNetInfo;
var netInfoSize = reader.ReadUInt32();
var netInfoFlags = reader.ReadUInt32();
//Offset to the ANSI network share name. The offset is relative to the start of the network share information block
var offsetNetShareName = reader.ReadUInt32();
if (offsetNetShareName > 20)
{
reader.Position = locInfoPos + offsetNetInfo + 20;
//Offset to the Unicode network share name
offsetNetShareName = reader.ReadUInt32();
pathType |= PathTypeFlags.IsUnicodeNetShareName;
}
if (offsetNetShareName > 0)
{
reader.Position = locInfoPos + offsetNetInfo + offsetNetShareName;
targetPath = (pathType & PathTypeFlags.IsUnicodeNetShareName) != 0
? reader.ReadStringToNull(encoding: Encoding.Unicode)
: reader.ReadStringToNull(encoding: sysEncoding);
}
}
//Read common path, if exist
if (offsetCommonPath > 0)
{
reader.Position = locInfoPos + offsetCommonPath;
var commonPath = (pathType & PathTypeFlags.IsUnicodeCommonPath) != 0
? reader.ReadStringToNull(encoding: Encoding.Unicode)
: reader.ReadStringToNull(encoding: sysEncoding);
targetPath = Path.Combine(targetPath, commonPath);
}
}
return targetPath;
}
private static Encoding GetSysEncoding()
{
#if !NETFRAMEWORK
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
#endif
return Encoding.GetEncoding(0);
}
}
}

View File

@@ -45,6 +45,12 @@
this.topng = new System.Windows.Forms.RadioButton();
this.tobmp = new System.Windows.Forms.RadioButton();
this.converttexture = new System.Windows.Forms.CheckBox();
this.l2dGroupBox = new System.Windows.Forms.GroupBox();
this.l2dMotionExportMethodPanel = new System.Windows.Forms.Panel();
this.l2dMonoBehaviourRadioButton = new System.Windows.Forms.RadioButton();
this.l2dAnimationClipRadioButton = new System.Windows.Forms.RadioButton();
this.l2dMotionExportMethodLabel = new System.Windows.Forms.Label();
this.l2dForceBezierCheckBox = new System.Windows.Forms.CheckBox();
this.groupBox2 = new System.Windows.Forms.GroupBox();
this.exportAllUvsAsDiffuseMaps = new System.Windows.Forms.CheckBox();
this.exportBlendShape = new System.Windows.Forms.CheckBox();
@@ -63,18 +69,34 @@
this.castToBone = new System.Windows.Forms.CheckBox();
this.exportAllNodes = new System.Windows.Forms.CheckBox();
this.eulerFilter = new System.Windows.Forms.CheckBox();
this.exportUvsTooltip = new System.Windows.Forms.ToolTip(this.components);
this.akResamplerLabel = new System.Windows.Forms.Label();
this.akResamplerComboBox = new System.Windows.Forms.ComboBox();
this.akSpritesAlphaGroupBox = new System.Windows.Forms.GroupBox();
this.akGammaNoteLabel = new System.Windows.Forms.Label();
this.akResamplerDescLabel = new System.Windows.Forms.Label();
this.akResizedOnlyCheckBox = new System.Windows.Forms.CheckBox();
this.akGammaValueLabel = new System.Windows.Forms.Label();
this.akGammaLabel = new System.Windows.Forms.Label();
this.akAlphaMaskGammaTrackBar = new System.Windows.Forms.TrackBar();
this.akSpritesExportGroupBox = new System.Windows.Forms.GroupBox();
this.akAddAliasesCheckBox = new System.Windows.Forms.CheckBox();
this.optionTooltip = new System.Windows.Forms.ToolTip(this.components);
this.groupBox1.SuspendLayout();
this.panel1.SuspendLayout();
this.l2dGroupBox.SuspendLayout();
this.l2dMotionExportMethodPanel.SuspendLayout();
this.groupBox2.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.scaleFactor)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.boneSize)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.filterPrecision)).BeginInit();
this.akSpritesAlphaGroupBox.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.akAlphaMaskGammaTrackBar)).BeginInit();
this.akSpritesExportGroupBox.SuspendLayout();
this.SuspendLayout();
//
// OKbutton
//
this.OKbutton.Location = new System.Drawing.Point(381, 380);
this.OKbutton.Location = new System.Drawing.Point(681, 381);
this.OKbutton.Name = "OKbutton";
this.OKbutton.Size = new System.Drawing.Size(75, 23);
this.OKbutton.TabIndex = 6;
@@ -85,7 +107,7 @@
// Cancel
//
this.Cancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.Cancel.Location = new System.Drawing.Point(462, 380);
this.Cancel.Location = new System.Drawing.Point(762, 381);
this.Cancel.Name = "Cancel";
this.Cancel.Size = new System.Drawing.Size(75, 23);
this.Cancel.TabIndex = 7;
@@ -106,8 +128,8 @@
this.groupBox1.Controls.Add(this.converttexture);
this.groupBox1.Location = new System.Drawing.Point(12, 13);
this.groupBox1.Name = "groupBox1";
this.groupBox1.Size = new System.Drawing.Size(301, 362);
this.groupBox1.TabIndex = 9;
this.groupBox1.Size = new System.Drawing.Size(301, 272);
this.groupBox1.TabIndex = 1;
this.groupBox1.TabStop = false;
this.groupBox1.Text = "Export";
//
@@ -119,7 +141,7 @@
this.exportSpriteWithAlphaMask.Location = new System.Drawing.Point(6, 150);
this.exportSpriteWithAlphaMask.Name = "exportSpriteWithAlphaMask";
this.exportSpriteWithAlphaMask.Size = new System.Drawing.Size(205, 17);
this.exportSpriteWithAlphaMask.TabIndex = 11;
this.exportSpriteWithAlphaMask.TabIndex = 6;
this.exportSpriteWithAlphaMask.Text = "Export sprites with alpha mask applied";
this.exportSpriteWithAlphaMask.UseVisualStyleBackColor = true;
//
@@ -131,7 +153,7 @@
this.openAfterExport.Location = new System.Drawing.Point(6, 196);
this.openAfterExport.Name = "openAfterExport";
this.openAfterExport.Size = new System.Drawing.Size(137, 17);
this.openAfterExport.TabIndex = 10;
this.openAfterExport.TabIndex = 8;
this.openAfterExport.Text = "Open folder after export";
this.openAfterExport.UseVisualStyleBackColor = true;
//
@@ -143,9 +165,9 @@
this.restoreExtensionName.Location = new System.Drawing.Point(6, 63);
this.restoreExtensionName.Name = "restoreExtensionName";
this.restoreExtensionName.Size = new System.Drawing.Size(275, 17);
this.restoreExtensionName.TabIndex = 9;
this.restoreExtensionName.TabIndex = 3;
this.restoreExtensionName.Text = "Try to restore/Use original TextAsset extension name";
this.exportUvsTooltip.SetToolTip(this.restoreExtensionName, "If not checked, AssetStudio will export all TextAssets with the \".txt\" extension");
this.optionTooltip.SetToolTip(this.restoreExtensionName, "If not checked, AssetStudio will export all TextAssets with the \".txt\" extension");
this.restoreExtensionName.UseVisualStyleBackColor = true;
//
// assetGroupOptions
@@ -161,7 +183,7 @@
this.assetGroupOptions.Location = new System.Drawing.Point(6, 35);
this.assetGroupOptions.Name = "assetGroupOptions";
this.assetGroupOptions.Size = new System.Drawing.Size(165, 21);
this.assetGroupOptions.TabIndex = 8;
this.assetGroupOptions.TabIndex = 2;
//
// label6
//
@@ -169,7 +191,7 @@
this.label6.Location = new System.Drawing.Point(6, 18);
this.label6.Name = "label6";
this.label6.Size = new System.Drawing.Size(127, 13);
this.label6.TabIndex = 7;
this.label6.TabIndex = 1;
this.label6.Text = "Group exported assets by";
//
// convertAudio
@@ -180,7 +202,7 @@
this.convertAudio.Location = new System.Drawing.Point(6, 173);
this.convertAudio.Name = "convertAudio";
this.convertAudio.Size = new System.Drawing.Size(179, 17);
this.convertAudio.TabIndex = 6;
this.convertAudio.TabIndex = 7;
this.convertAudio.Text = "Convert AudioClip to WAV(PCM)";
this.convertAudio.UseVisualStyleBackColor = true;
//
@@ -202,8 +224,7 @@
this.towebp.Location = new System.Drawing.Point(201, 7);
this.towebp.Name = "towebp";
this.towebp.Size = new System.Drawing.Size(54, 17);
this.towebp.TabIndex = 5;
this.towebp.TabStop = true;
this.towebp.TabIndex = 4;
this.towebp.Text = "Webp";
this.towebp.UseVisualStyleBackColor = true;
//
@@ -213,7 +234,7 @@
this.totga.Location = new System.Drawing.Point(150, 7);
this.totga.Name = "totga";
this.totga.Size = new System.Drawing.Size(44, 17);
this.totga.TabIndex = 2;
this.totga.TabIndex = 3;
this.totga.Text = "Tga";
this.totga.UseVisualStyleBackColor = true;
//
@@ -223,7 +244,7 @@
this.tojpg.Location = new System.Drawing.Point(97, 7);
this.tojpg.Name = "tojpg";
this.tojpg.Size = new System.Drawing.Size(48, 17);
this.tojpg.TabIndex = 4;
this.tojpg.TabIndex = 2;
this.tojpg.Text = "Jpeg";
this.tojpg.UseVisualStyleBackColor = true;
//
@@ -234,7 +255,7 @@
this.topng.Location = new System.Drawing.Point(50, 7);
this.topng.Name = "topng";
this.topng.Size = new System.Drawing.Size(44, 17);
this.topng.TabIndex = 3;
this.topng.TabIndex = 1;
this.topng.TabStop = true;
this.topng.Text = "Png";
this.topng.UseVisualStyleBackColor = true;
@@ -245,7 +266,7 @@
this.tobmp.Location = new System.Drawing.Point(3, 7);
this.tobmp.Name = "tobmp";
this.tobmp.Size = new System.Drawing.Size(46, 17);
this.tobmp.TabIndex = 2;
this.tobmp.TabIndex = 0;
this.tobmp.Text = "Bmp";
this.tobmp.UseVisualStyleBackColor = true;
//
@@ -257,10 +278,76 @@
this.converttexture.Location = new System.Drawing.Point(6, 87);
this.converttexture.Name = "converttexture";
this.converttexture.Size = new System.Drawing.Size(116, 17);
this.converttexture.TabIndex = 1;
this.converttexture.TabIndex = 4;
this.converttexture.Text = "Convert Texture2D";
this.converttexture.UseVisualStyleBackColor = true;
//
// l2dGroupBox
//
this.l2dGroupBox.Controls.Add(this.l2dMotionExportMethodPanel);
this.l2dGroupBox.Controls.Add(this.l2dMotionExportMethodLabel);
this.l2dGroupBox.Controls.Add(this.l2dForceBezierCheckBox);
this.l2dGroupBox.Location = new System.Drawing.Point(12, 275);
this.l2dGroupBox.Name = "l2dGroupBox";
this.l2dGroupBox.Size = new System.Drawing.Size(301, 100);
this.l2dGroupBox.TabIndex = 2;
this.l2dGroupBox.TabStop = false;
this.l2dGroupBox.Text = "Cubism Live2D";
//
// l2dMotionExportMethodPanel
//
this.l2dMotionExportMethodPanel.Controls.Add(this.l2dMonoBehaviourRadioButton);
this.l2dMotionExportMethodPanel.Controls.Add(this.l2dAnimationClipRadioButton);
this.l2dMotionExportMethodPanel.Location = new System.Drawing.Point(18, 40);
this.l2dMotionExportMethodPanel.Name = "l2dMotionExportMethodPanel";
this.l2dMotionExportMethodPanel.Size = new System.Drawing.Size(263, 27);
this.l2dMotionExportMethodPanel.TabIndex = 2;
//
// l2dMonoBehaviourRadioButton
//
this.l2dMonoBehaviourRadioButton.AccessibleName = "MonoBehaviour";
this.l2dMonoBehaviourRadioButton.AutoSize = true;
this.l2dMonoBehaviourRadioButton.Checked = true;
this.l2dMonoBehaviourRadioButton.Location = new System.Drawing.Point(3, 5);
this.l2dMonoBehaviourRadioButton.Name = "l2dMonoBehaviourRadioButton";
this.l2dMonoBehaviourRadioButton.Size = new System.Drawing.Size(167, 17);
this.l2dMonoBehaviourRadioButton.TabIndex = 0;
this.l2dMonoBehaviourRadioButton.TabStop = true;
this.l2dMonoBehaviourRadioButton.Text = "MonoBehaviour (Fade motion)";
this.optionTooltip.SetToolTip(this.l2dMonoBehaviourRadioButton, "If no Fade motions are found, the AnimationClip method will be used");
this.l2dMonoBehaviourRadioButton.UseVisualStyleBackColor = true;
//
// l2dAnimationClipRadioButton
//
this.l2dAnimationClipRadioButton.AccessibleName = "AnimationClip";
this.l2dAnimationClipRadioButton.AutoSize = true;
this.l2dAnimationClipRadioButton.Location = new System.Drawing.Point(172, 5);
this.l2dAnimationClipRadioButton.Name = "l2dAnimationClipRadioButton";
this.l2dAnimationClipRadioButton.Size = new System.Drawing.Size(88, 17);
this.l2dAnimationClipRadioButton.TabIndex = 1;
this.l2dAnimationClipRadioButton.Text = "AnimationClip";
this.l2dAnimationClipRadioButton.UseVisualStyleBackColor = true;
//
// l2dMotionExportMethodLabel
//
this.l2dMotionExportMethodLabel.AutoSize = true;
this.l2dMotionExportMethodLabel.Location = new System.Drawing.Point(6, 21);
this.l2dMotionExportMethodLabel.Name = "l2dMotionExportMethodLabel";
this.l2dMotionExportMethodLabel.Size = new System.Drawing.Size(109, 13);
this.l2dMotionExportMethodLabel.TabIndex = 1;
this.l2dMotionExportMethodLabel.Text = "Motion export method";
//
// l2dForceBezierCheckBox
//
this.l2dForceBezierCheckBox.AutoSize = true;
this.l2dForceBezierCheckBox.Location = new System.Drawing.Point(6, 77);
this.l2dForceBezierCheckBox.Name = "l2dForceBezierCheckBox";
this.l2dForceBezierCheckBox.Size = new System.Drawing.Size(278, 17);
this.l2dForceBezierCheckBox.TabIndex = 3;
this.l2dForceBezierCheckBox.Text = "Calculate Linear motion segments as Bezier segments";
this.optionTooltip.SetToolTip(this.l2dForceBezierCheckBox, "May help if the exported motions look jerky/not smooth enough");
this.l2dForceBezierCheckBox.UseVisualStyleBackColor = true;
//
// groupBox2
//
this.groupBox2.AutoSize = true;
@@ -284,7 +371,7 @@
this.groupBox2.Location = new System.Drawing.Point(313, 13);
this.groupBox2.Name = "groupBox2";
this.groupBox2.Size = new System.Drawing.Size(224, 362);
this.groupBox2.TabIndex = 11;
this.groupBox2.TabIndex = 3;
this.groupBox2.TabStop = false;
this.groupBox2.Text = "Fbx";
//
@@ -295,9 +382,9 @@
this.exportAllUvsAsDiffuseMaps.Location = new System.Drawing.Point(6, 185);
this.exportAllUvsAsDiffuseMaps.Name = "exportAllUvsAsDiffuseMaps";
this.exportAllUvsAsDiffuseMaps.Size = new System.Drawing.Size(168, 17);
this.exportAllUvsAsDiffuseMaps.TabIndex = 23;
this.exportAllUvsAsDiffuseMaps.TabIndex = 9;
this.exportAllUvsAsDiffuseMaps.Text = "Export all UVs as diffuse maps";
this.exportUvsTooltip.SetToolTip(this.exportAllUvsAsDiffuseMaps, "Unchecked: UV1 exported as normal map. Check this if your export is missing a UV " +
this.optionTooltip.SetToolTip(this.exportAllUvsAsDiffuseMaps, "Unchecked: UV1 exported as normal map. Check this if your export is missing a UV " +
"map.");
this.exportAllUvsAsDiffuseMaps.UseVisualStyleBackColor = true;
//
@@ -309,7 +396,7 @@
this.exportBlendShape.Location = new System.Drawing.Point(6, 138);
this.exportBlendShape.Name = "exportBlendShape";
this.exportBlendShape.Size = new System.Drawing.Size(114, 17);
this.exportBlendShape.TabIndex = 22;
this.exportBlendShape.TabIndex = 7;
this.exportBlendShape.Text = "Export blendshape";
this.exportBlendShape.UseVisualStyleBackColor = true;
//
@@ -321,7 +408,7 @@
this.exportAnimations.Location = new System.Drawing.Point(6, 114);
this.exportAnimations.Name = "exportAnimations";
this.exportAnimations.Size = new System.Drawing.Size(109, 17);
this.exportAnimations.TabIndex = 21;
this.exportAnimations.TabIndex = 6;
this.exportAnimations.Text = "Export animations";
this.exportAnimations.UseVisualStyleBackColor = true;
//
@@ -336,7 +423,7 @@
this.scaleFactor.Location = new System.Drawing.Point(83, 243);
this.scaleFactor.Name = "scaleFactor";
this.scaleFactor.Size = new System.Drawing.Size(60, 20);
this.scaleFactor.TabIndex = 20;
this.scaleFactor.TabIndex = 13;
this.scaleFactor.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
this.scaleFactor.Value = new decimal(new int[] {
1,
@@ -350,7 +437,7 @@
this.label5.Location = new System.Drawing.Point(6, 245);
this.label5.Name = "label5";
this.label5.Size = new System.Drawing.Size(64, 13);
this.label5.TabIndex = 19;
this.label5.TabIndex = 12;
this.label5.Text = "ScaleFactor";
//
// fbxFormat
@@ -363,7 +450,7 @@
this.fbxFormat.Location = new System.Drawing.Point(77, 275);
this.fbxFormat.Name = "fbxFormat";
this.fbxFormat.Size = new System.Drawing.Size(61, 21);
this.fbxFormat.TabIndex = 18;
this.fbxFormat.TabIndex = 15;
//
// label4
//
@@ -371,7 +458,7 @@
this.label4.Location = new System.Drawing.Point(6, 280);
this.label4.Name = "label4";
this.label4.Size = new System.Drawing.Size(59, 13);
this.label4.TabIndex = 17;
this.label4.TabIndex = 14;
this.label4.Text = "FBXFormat";
//
// fbxVersion
@@ -388,7 +475,7 @@
this.fbxVersion.Location = new System.Drawing.Point(77, 308);
this.fbxVersion.Name = "fbxVersion";
this.fbxVersion.Size = new System.Drawing.Size(47, 21);
this.fbxVersion.TabIndex = 16;
this.fbxVersion.TabIndex = 17;
//
// label3
//
@@ -396,7 +483,7 @@
this.label3.Location = new System.Drawing.Point(6, 311);
this.label3.Name = "label3";
this.label3.Size = new System.Drawing.Size(62, 13);
this.label3.TabIndex = 15;
this.label3.TabIndex = 16;
this.label3.Text = "FBXVersion";
//
// boneSize
@@ -428,7 +515,7 @@
this.exportSkins.Location = new System.Drawing.Point(6, 90);
this.exportSkins.Name = "exportSkins";
this.exportSkins.Size = new System.Drawing.Size(83, 17);
this.exportSkins.TabIndex = 8;
this.exportSkins.TabIndex = 5;
this.exportSkins.Text = "Export skins";
this.exportSkins.UseVisualStyleBackColor = true;
//
@@ -438,7 +525,7 @@
this.label1.Location = new System.Drawing.Point(26, 42);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(72, 13);
this.label1.TabIndex = 7;
this.label1.TabIndex = 2;
this.label1.Text = "FilterPrecision";
//
// filterPrecision
@@ -452,7 +539,7 @@
this.filterPrecision.Location = new System.Drawing.Point(127, 40);
this.filterPrecision.Name = "filterPrecision";
this.filterPrecision.Size = new System.Drawing.Size(51, 20);
this.filterPrecision.TabIndex = 6;
this.filterPrecision.TabIndex = 3;
this.filterPrecision.Value = new decimal(new int[] {
25,
0,
@@ -465,7 +552,7 @@
this.castToBone.Location = new System.Drawing.Point(6, 161);
this.castToBone.Name = "castToBone";
this.castToBone.Size = new System.Drawing.Size(131, 17);
this.castToBone.TabIndex = 5;
this.castToBone.TabIndex = 8;
this.castToBone.Text = "All nodes cast to bone";
this.castToBone.UseVisualStyleBackColor = true;
//
@@ -489,17 +576,145 @@
this.eulerFilter.Location = new System.Drawing.Point(6, 22);
this.eulerFilter.Name = "eulerFilter";
this.eulerFilter.Size = new System.Drawing.Size(72, 17);
this.eulerFilter.TabIndex = 3;
this.eulerFilter.TabIndex = 1;
this.eulerFilter.Text = "EulerFilter";
this.eulerFilter.UseVisualStyleBackColor = true;
//
// akResamplerLabel
//
this.akResamplerLabel.AutoSize = true;
this.akResamplerLabel.Location = new System.Drawing.Point(6, 21);
this.akResamplerLabel.Name = "akResamplerLabel";
this.akResamplerLabel.Size = new System.Drawing.Size(120, 13);
this.akResamplerLabel.TabIndex = 1;
this.akResamplerLabel.Text = "Alpha texture resampler:";
this.optionTooltip.SetToolTip(this.akResamplerLabel, "Only affects exported images");
//
// akResamplerComboBox
//
this.akResamplerComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.akResamplerComboBox.FormattingEnabled = true;
this.akResamplerComboBox.Items.AddRange(new object[] {
"Nearest Neighbor",
"Bilinear",
"Bicubic",
"Mitchell-Netravali",
"Spline",
"Welch"});
this.akResamplerComboBox.Location = new System.Drawing.Point(132, 18);
this.akResamplerComboBox.Name = "akResamplerComboBox";
this.akResamplerComboBox.Size = new System.Drawing.Size(162, 21);
this.akResamplerComboBox.TabIndex = 2;
this.optionTooltip.SetToolTip(this.akResamplerComboBox, "Only affects exported images");
//
// akSpritesAlphaGroupBox
//
this.akSpritesAlphaGroupBox.Controls.Add(this.akGammaNoteLabel);
this.akSpritesAlphaGroupBox.Controls.Add(this.akResamplerDescLabel);
this.akSpritesAlphaGroupBox.Controls.Add(this.akResamplerLabel);
this.akSpritesAlphaGroupBox.Controls.Add(this.akResamplerComboBox);
this.akSpritesAlphaGroupBox.Controls.Add(this.akResizedOnlyCheckBox);
this.akSpritesAlphaGroupBox.Controls.Add(this.akGammaValueLabel);
this.akSpritesAlphaGroupBox.Controls.Add(this.akGammaLabel);
this.akSpritesAlphaGroupBox.Controls.Add(this.akAlphaMaskGammaTrackBar);
this.akSpritesAlphaGroupBox.Location = new System.Drawing.Point(537, 13);
this.akSpritesAlphaGroupBox.Name = "akSpritesAlphaGroupBox";
this.akSpritesAlphaGroupBox.Size = new System.Drawing.Size(300, 178);
this.akSpritesAlphaGroupBox.TabIndex = 4;
this.akSpritesAlphaGroupBox.TabStop = false;
this.akSpritesAlphaGroupBox.Text = "Sprites: Alpha Texture [Arknights]";
//
// akGammaNoteLabel
//
this.akGammaNoteLabel.AutoSize = true;
this.akGammaNoteLabel.ForeColor = System.Drawing.SystemColors.GrayText;
this.akGammaNoteLabel.Location = new System.Drawing.Point(6, 138);
this.akGammaNoteLabel.Name = "akGammaNoteLabel";
this.akGammaNoteLabel.Size = new System.Drawing.Size(230, 13);
this.akGammaNoteLabel.TabIndex = 8;
this.akGammaNoteLabel.Text = "* Gamma settings also affect the preview image";
//
// akResamplerDescLabel
//
this.akResamplerDescLabel.AutoSize = true;
this.akResamplerDescLabel.ForeColor = System.Drawing.SystemColors.GrayText;
this.akResamplerDescLabel.Location = new System.Drawing.Point(6, 43);
this.akResamplerDescLabel.Name = "akResamplerDescLabel";
this.akResamplerDescLabel.Size = new System.Drawing.Size(251, 13);
this.akResamplerDescLabel.TabIndex = 3;
this.akResamplerDescLabel.Text = "Alpha texture upscale method for 2048x2048 sprites";
//
// akResizedOnlyCheckBox
//
this.akResizedOnlyCheckBox.AutoSize = true;
this.akResizedOnlyCheckBox.Checked = true;
this.akResizedOnlyCheckBox.CheckState = System.Windows.Forms.CheckState.Checked;
this.akResizedOnlyCheckBox.Location = new System.Drawing.Point(172, 85);
this.akResizedOnlyCheckBox.Name = "akResizedOnlyCheckBox";
this.akResizedOnlyCheckBox.Size = new System.Drawing.Size(122, 17);
this.akResizedOnlyCheckBox.TabIndex = 6;
this.akResizedOnlyCheckBox.Text = "Apply to resized only";
this.akResizedOnlyCheckBox.UseVisualStyleBackColor = true;
//
// akGammaValueLabel
//
this.akGammaValueLabel.AutoSize = true;
this.akGammaValueLabel.Location = new System.Drawing.Point(111, 86);
this.akGammaValueLabel.Name = "akGammaValueLabel";
this.akGammaValueLabel.Size = new System.Drawing.Size(41, 13);
this.akGammaValueLabel.TabIndex = 5;
this.akGammaValueLabel.Text = "Default";
//
// akGammaLabel
//
this.akGammaLabel.AutoSize = true;
this.akGammaLabel.Location = new System.Drawing.Point(6, 86);
this.akGammaLabel.Name = "akGammaLabel";
this.akGammaLabel.Size = new System.Drawing.Size(86, 13);
this.akGammaLabel.TabIndex = 4;
this.akGammaLabel.Text = "Shadow gamma:";
//
// akAlphaMaskGammaTrackBar
//
this.akAlphaMaskGammaTrackBar.LargeChange = 2;
this.akAlphaMaskGammaTrackBar.Location = new System.Drawing.Point(6, 102);
this.akAlphaMaskGammaTrackBar.Maximum = 5;
this.akAlphaMaskGammaTrackBar.Minimum = -5;
this.akAlphaMaskGammaTrackBar.Name = "akAlphaMaskGammaTrackBar";
this.akAlphaMaskGammaTrackBar.Size = new System.Drawing.Size(288, 45);
this.akAlphaMaskGammaTrackBar.TabIndex = 7;
this.akAlphaMaskGammaTrackBar.Scroll += new System.EventHandler(this.akAlphaMaskGammaTrackBar_Scroll);
//
// akSpritesExportGroupBox
//
this.akSpritesExportGroupBox.Controls.Add(this.akAddAliasesCheckBox);
this.akSpritesExportGroupBox.Location = new System.Drawing.Point(537, 197);
this.akSpritesExportGroupBox.Name = "akSpritesExportGroupBox";
this.akSpritesExportGroupBox.Size = new System.Drawing.Size(300, 178);
this.akSpritesExportGroupBox.TabIndex = 5;
this.akSpritesExportGroupBox.TabStop = false;
this.akSpritesExportGroupBox.Text = "Sprites: Export [Arknights]";
//
// akAddAliasesCheckBox
//
this.akAddAliasesCheckBox.AutoSize = true;
this.akAddAliasesCheckBox.Location = new System.Drawing.Point(6, 28);
this.akAddAliasesCheckBox.Name = "akAddAliasesCheckBox";
this.akAddAliasesCheckBox.Size = new System.Drawing.Size(261, 17);
this.akAddAliasesCheckBox.TabIndex = 1;
this.akAddAliasesCheckBox.Text = "Add aliases to avg character sprite names (if exist)";
this.akAddAliasesCheckBox.UseVisualStyleBackColor = true;
//
// ExportOptions
//
this.AcceptButton = this.OKbutton;
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.CancelButton = this.Cancel;
this.ClientSize = new System.Drawing.Size(549, 416);
this.ClientSize = new System.Drawing.Size(849, 416);
this.Controls.Add(this.l2dGroupBox);
this.Controls.Add(this.akSpritesExportGroupBox);
this.Controls.Add(this.akSpritesAlphaGroupBox);
this.Controls.Add(this.groupBox2);
this.Controls.Add(this.groupBox1);
this.Controls.Add(this.Cancel);
@@ -516,11 +731,20 @@
this.groupBox1.PerformLayout();
this.panel1.ResumeLayout(false);
this.panel1.PerformLayout();
this.l2dGroupBox.ResumeLayout(false);
this.l2dGroupBox.PerformLayout();
this.l2dMotionExportMethodPanel.ResumeLayout(false);
this.l2dMotionExportMethodPanel.PerformLayout();
this.groupBox2.ResumeLayout(false);
this.groupBox2.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.scaleFactor)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.boneSize)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.filterPrecision)).EndInit();
this.akSpritesAlphaGroupBox.ResumeLayout(false);
this.akSpritesAlphaGroupBox.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.akAlphaMaskGammaTrackBar)).EndInit();
this.akSpritesExportGroupBox.ResumeLayout(false);
this.akSpritesExportGroupBox.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
@@ -559,8 +783,25 @@
private System.Windows.Forms.CheckBox restoreExtensionName;
private System.Windows.Forms.CheckBox openAfterExport;
private System.Windows.Forms.CheckBox exportAllUvsAsDiffuseMaps;
private System.Windows.Forms.ToolTip exportUvsTooltip;
private System.Windows.Forms.ToolTip optionTooltip;
private System.Windows.Forms.CheckBox exportSpriteWithAlphaMask;
private System.Windows.Forms.RadioButton towebp;
private System.Windows.Forms.GroupBox akSpritesAlphaGroupBox;
private System.Windows.Forms.TrackBar akAlphaMaskGammaTrackBar;
private System.Windows.Forms.Label akResamplerDescLabel;
private System.Windows.Forms.Label akResamplerLabel;
private System.Windows.Forms.ComboBox akResamplerComboBox;
private System.Windows.Forms.CheckBox akResizedOnlyCheckBox;
private System.Windows.Forms.Label akGammaValueLabel;
private System.Windows.Forms.Label akGammaLabel;
private System.Windows.Forms.GroupBox akSpritesExportGroupBox;
private System.Windows.Forms.CheckBox akAddAliasesCheckBox;
private System.Windows.Forms.Label akGammaNoteLabel;
private System.Windows.Forms.GroupBox l2dGroupBox;
private System.Windows.Forms.CheckBox l2dForceBezierCheckBox;
private System.Windows.Forms.Label l2dMotionExportMethodLabel;
private System.Windows.Forms.RadioButton l2dAnimationClipRadioButton;
private System.Windows.Forms.RadioButton l2dMonoBehaviourRadioButton;
private System.Windows.Forms.Panel l2dMotionExportMethodPanel;
}
}

View File

@@ -1,5 +1,6 @@
using AssetStudio;
using System;
using System.Linq;
using System.Windows.Forms;
namespace AssetStudioGUI
@@ -14,15 +15,8 @@ namespace AssetStudioGUI
converttexture.Checked = Properties.Settings.Default.convertTexture;
exportSpriteWithAlphaMask.Checked = Properties.Settings.Default.exportSpriteWithMask;
convertAudio.Checked = Properties.Settings.Default.convertAudio;
var str = Properties.Settings.Default.convertType.ToString();
foreach (Control c in panel1.Controls)
{
if (c.Text == str)
{
((RadioButton)c).Checked = true;
break;
}
}
var defaultImageType = Properties.Settings.Default.convertType.ToString();
((RadioButton)panel1.Controls.Cast<Control>().First(x => x.Text == defaultImageType)).Checked = true;
openAfterExport.Checked = Properties.Settings.Default.openAfterExport;
eulerFilter.Checked = Properties.Settings.Default.eulerFilter;
filterPrecision.Value = Properties.Settings.Default.filterPrecision;
@@ -37,6 +31,16 @@ namespace AssetStudioGUI
fbxVersion.SelectedIndex = Properties.Settings.Default.fbxVersion;
fbxFormat.SelectedIndex = Properties.Settings.Default.fbxFormat;
//Arknights
akResamplerComboBox.SelectedIndex = Properties.Settings.Default.resamplerIndex;
akAlphaMaskGammaTrackBar.Value = Properties.Settings.Default.alphaMaskGamma;
akGammaValueLabel.Text = akAlphaMaskGammaTrackBar.Value == 0 ? "Default" : $"{akAlphaMaskGammaTrackBar.Value * 10:+#;-#;0}%";
akResizedOnlyCheckBox.Checked = Properties.Settings.Default.resizedOnly;
akAddAliasesCheckBox.Checked = Properties.Settings.Default.addAliases;
var defaultMotionMode = Properties.Settings.Default.l2dMotionMode.ToString();
((RadioButton)l2dMotionExportMethodPanel.Controls.Cast<Control>().First(x => x.AccessibleName == defaultMotionMode)).Checked = true;
l2dForceBezierCheckBox.Checked = Properties.Settings.Default.l2dForceBezier;
}
private void OKbutton_Click(object sender, EventArgs e)
@@ -46,14 +50,8 @@ namespace AssetStudioGUI
Properties.Settings.Default.convertTexture = converttexture.Checked;
Properties.Settings.Default.exportSpriteWithMask = exportSpriteWithAlphaMask.Checked;
Properties.Settings.Default.convertAudio = convertAudio.Checked;
foreach (Control c in panel1.Controls)
{
if (((RadioButton)c).Checked)
{
Properties.Settings.Default.convertType = (ImageFormat)Enum.Parse(typeof(ImageFormat), c.Text);
break;
}
}
var checkedImageType = (RadioButton)panel1.Controls.Cast<Control>().First(x => ((RadioButton)x).Checked);
Properties.Settings.Default.convertType = (ImageFormat)Enum.Parse(typeof(ImageFormat), checkedImageType.Text);
Properties.Settings.Default.openAfterExport = openAfterExport.Checked;
Properties.Settings.Default.eulerFilter = eulerFilter.Checked;
Properties.Settings.Default.filterPrecision = filterPrecision.Value;
@@ -67,11 +65,27 @@ namespace AssetStudioGUI
Properties.Settings.Default.scaleFactor = scaleFactor.Value;
Properties.Settings.Default.fbxVersion = fbxVersion.SelectedIndex;
Properties.Settings.Default.fbxFormat = fbxFormat.SelectedIndex;
//Arknights
Properties.Settings.Default.resamplerIndex = akResamplerComboBox.SelectedIndex;
Properties.Settings.Default.alphaMaskGamma = akAlphaMaskGammaTrackBar.Value;
Properties.Settings.Default.resizedOnly = akResizedOnlyCheckBox.Checked;
Properties.Settings.Default.addAliases = akAddAliasesCheckBox.Checked;
var checkedMotionMode = (RadioButton)l2dMotionExportMethodPanel.Controls.Cast<Control>().First(x => ((RadioButton)x).Checked);
Properties.Settings.Default.l2dMotionMode = (CubismLive2DExtractor.Live2DMotionMode)Enum.Parse(typeof(CubismLive2DExtractor.Live2DMotionMode), checkedMotionMode.AccessibleName);
Properties.Settings.Default.l2dForceBezier = l2dForceBezierCheckBox.Checked;
Properties.Settings.Default.Save();
DialogResult = DialogResult.OK;
Close();
}
//Arknights
private void akAlphaMaskGammaTrackBar_Scroll(object sender, EventArgs e)
{
akGammaValueLabel.Text = akAlphaMaskGammaTrackBar.Value == 0 ? "Default" : $"{akAlphaMaskGammaTrackBar.Value * 10:+#;-#;0}%";
}
private void Cancel_Click(object sender, EventArgs e)
{
DialogResult = DialogResult.Cancel;

View File

@@ -117,7 +117,7 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="exportUvsTooltip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<metadata name="optionTooltip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
</root>

View File

@@ -1,4 +1,7 @@
using AssetStudio;
using Arknights;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using AssetStudio;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.IO;
@@ -238,12 +241,69 @@ namespace AssetStudioGUI
}
public static bool ExportSprite(AssetItem item, string exportPath)
{
Image<Bgra32> image;
AvgSprite avgSprite = null;
var alias = "";
var m_Sprite = (Sprite)item.Asset;
var spriteMaskMode = Properties.Settings.Default.exportSpriteWithMask ? SpriteMaskMode.Export : SpriteMaskMode.Off;
var type = Properties.Settings.Default.convertType;
var isCharAvgSprite = item.Container.Contains("avg/characters");
var isCharArt = item.Container.Contains("arts/characters");
if (isCharAvgSprite)
{
avgSprite = new AvgSprite(item);
if (Properties.Settings.Default.addAliases && !string.IsNullOrEmpty(avgSprite.Alias))
{
alias = $"_{avgSprite.Alias}";
}
}
if (!TryExportFile(exportPath, item, "." + type.ToString().ToLower(), out var exportFullPath, alias))
return false;
if (Properties.Settings.Default.useExternalAlpha && (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: spriteMaskMode);
}
else
{
image = m_Sprite.GetImage(spriteMaskMode: spriteMaskMode);
}
if (image != null)
{
using (image)
{
using (var file = File.OpenWrite(exportFullPath))
{
image.WriteToStream(file, type);
}
return true;
}
}
return false;
}
public static bool ExportPortraitSprite(AssetItem item, string exportPath)
{
var type = Properties.Settings.Default.convertType;
var spriteMaskMode = Properties.Settings.Default.exportSpriteWithMask ? SpriteMaskMode.Export : SpriteMaskMode.Off;
if (!TryExportFile(exportPath, item, "." + type.ToString().ToLower(), out var exportFullPath))
return false;
var image = ((Sprite)item.Asset).GetImage(spriteMaskMode: spriteMaskMode);
var image = item.AkPortraitSprite.AkGetImage(spriteMaskMode: spriteMaskMode);
if (image != null)
{
using (image)
@@ -260,15 +320,17 @@ namespace AssetStudioGUI
public static bool ExportRawFile(AssetItem item, string exportPath)
{
if (item.Asset == null)
return false;
if (!TryExportFile(exportPath, item, ".dat", out var exportFullPath))
return false;
File.WriteAllBytes(exportFullPath, item.Asset.GetRawData());
return true;
}
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))
{
@@ -337,6 +399,8 @@ namespace AssetStudioGUI
public static bool ExportDumpFile(AssetItem item, string exportPath)
{
if (item.Asset == null)
return false;
if (!TryExportFile(exportPath, item, ".txt", out var exportFullPath))
return false;
var str = item.Asset.Dump();
@@ -377,6 +441,8 @@ namespace AssetStudioGUI
return ExportMovieTexture(item, exportPath);
case ClassIDType.Sprite:
return ExportSprite(item, exportPath);
case ClassIDType.AkPortraitSprite:
return ExportPortraitSprite(item, exportPath);
case ClassIDType.Animator:
return ExportAnimator(item, exportPath);
case ClassIDType.AnimationClip:

View File

@@ -1,5 +1,8 @@
using AssetStudio;
using System;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows.Forms;
namespace AssetStudioGUI
@@ -7,15 +10,122 @@ namespace AssetStudioGUI
class GUILogger : ILogger
{
public bool ShowErrorMessage = false;
private bool IsFileLoggerRunning = false;
private string LoggerInitString;
private string FileLogName;
private string FileLogPath;
private Action<string> action;
private bool _useFileLogger = false;
public bool UseFileLogger
{
get => _useFileLogger;
set
{
_useFileLogger = value;
if (_useFileLogger && !IsFileLoggerRunning)
{
var appAssembly = typeof(Program).Assembly.GetName();
FileLogName = $"{appAssembly.Name}_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.log";
FileLogPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, FileLogName);
LogToFile(LoggerEvent.Verbose, $"# {LoggerInitString} - Logger launched #");
IsFileLoggerRunning = true;
}
else if (!_useFileLogger && IsFileLoggerRunning)
{
LogToFile(LoggerEvent.Verbose, "# Logger closed #");
IsFileLoggerRunning = false;
}
}
}
public GUILogger(Action<string> action)
{
this.action = action;
var appAssembly = typeof(Program).Assembly.GetName();
var arch = Environment.Is64BitProcess ? "x64" : "x32";
var frameworkName = AppDomain.CurrentDomain.SetupInformation.TargetFrameworkName;
LoggerInitString = $"{appAssembly.Name} v{appAssembly.Version} [{arch}] [{frameworkName}]";
try
{
Console.Title = $"Console Logger - {appAssembly.Name} v{appAssembly.Version}";
Console.OutputEncoding = System.Text.Encoding.UTF8;
}
catch
{
// ignored
}
Console.WriteLine($"# {LoggerInitString}");
}
private static string ColorLogLevel(LoggerEvent logLevel)
{
var formattedLevel = $"[{logLevel}]";
switch (logLevel)
{
case LoggerEvent.Info:
return $"{formattedLevel.Color(ColorConsole.BrightCyan)}";
case LoggerEvent.Warning:
return $"{formattedLevel.Color(ColorConsole.BrightYellow)}";
case LoggerEvent.Error:
return $"{formattedLevel.Color(ColorConsole.BrightRed)}";
default:
return formattedLevel;
}
}
private static string FormatMessage(LoggerEvent logMsgLevel, string message, bool toConsole)
{
message = message.TrimEnd();
var multiLine = message.Contains('\n');
string formattedMessage;
if (toConsole)
{
var colorLogLevel = ColorLogLevel(logMsgLevel);
formattedMessage = $"{colorLogLevel} {message}";
if (multiLine)
{
formattedMessage = formattedMessage.Replace("\n", $"\n{colorLogLevel} ");
}
}
else
{
var curTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
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;
}
private async void LogToFile(LoggerEvent logMsgLevel, string message)
{
using (var sw = new StreamWriter(FileLogPath, append: true, System.Text.Encoding.UTF8))
{
await sw.WriteLineAsync(FormatMessage(logMsgLevel, message, toConsole: false));
}
}
public void Log(LoggerEvent loggerEvent, string message, bool ignoreLevel)
{
//File logger
if (_useFileLogger)
{
LogToFile(loggerEvent, message);
}
//Console logger
Console.WriteLine(FormatMessage(loggerEvent, message, toConsole: true));
//GUI logger
switch (loggerEvent)
{
case LoggerEvent.Error:

View File

@@ -12,7 +12,7 @@ namespace AssetStudioGUI.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.5.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.8.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
@@ -286,5 +286,137 @@ namespace AssetStudioGUI.Properties {
this["exportSpriteWithMask"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("True")]
public bool fixFaceSpriteNames {
get {
return ((bool)(this["fixFaceSpriteNames"]));
}
set {
this["fixFaceSpriteNames"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("True")]
public bool useExternalAlpha {
get {
return ((bool)(this["useExternalAlpha"]));
}
set {
this["useExternalAlpha"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("False")]
public bool addAliases {
get {
return ((bool)(this["addAliases"]));
}
set {
this["addAliases"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("3")]
public int resamplerIndex {
get {
return ((int)(this["resamplerIndex"]));
}
set {
this["resamplerIndex"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("True")]
public bool resizedOnly {
get {
return ((bool)(this["resizedOnly"]));
}
set {
this["resizedOnly"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("2")]
public int alphaMaskGamma {
get {
return ((int)(this["alphaMaskGamma"]));
}
set {
this["alphaMaskGamma"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("MonoBehaviour")]
public global::CubismLive2DExtractor.Live2DMotionMode l2dMotionMode {
get {
return ((global::CubismLive2DExtractor.Live2DMotionMode)(this["l2dMotionMode"]));
}
set {
this["l2dMotionMode"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("False")]
public bool l2dForceBezier {
get {
return ((bool)(this["l2dForceBezier"]));
}
set {
this["l2dForceBezier"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("True")]
public bool showConsole {
get {
return ((bool)(this["showConsole"]));
}
set {
this["showConsole"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("False")]
public bool useFileLogger {
get {
return ((bool)(this["useFileLogger"]));
}
set {
this["useFileLogger"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("True")]
public bool buildTreeStructure {
get {
return ((bool)(this["buildTreeStructure"]));
}
set {
this["buildTreeStructure"] = value;
}
}
}
}

View File

@@ -68,5 +68,38 @@
<Setting Name="exportSpriteWithMask" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
</Setting>
<Setting Name="fixFaceSpriteNames" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
</Setting>
<Setting Name="useExternalAlpha" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
</Setting>
<Setting Name="addAliases" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
<Setting Name="resamplerIndex" Type="System.Int32" Scope="User">
<Value Profile="(Default)">3</Value>
</Setting>
<Setting Name="resizedOnly" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
</Setting>
<Setting Name="alphaMaskGamma" Type="System.Int32" Scope="User">
<Value Profile="(Default)">2</Value>
</Setting>
<Setting Name="l2dMotionMode" Type="CubismLive2DExtractor.Live2DMotionMode" Scope="User">
<Value Profile="(Default)">MonoBehaviour</Value>
</Setting>
<Setting Name="l2dForceBezier" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
<Setting Name="showConsole" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
</Setting>
<Setting Name="useFileLogger" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
<Setting Name="buildTreeStructure" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
</Setting>
</Settings>
</SettingsFile>

View File

@@ -158,10 +158,13 @@ namespace AssetStudioGUI
var objectCount = assetsManager.assetsFileList.Sum(x => x.Objects.Count);
var objectAssetItemDic = new Dictionary<Object, AssetItem>(objectCount);
var containers = new List<(PPtr<Object>, string)>();
int i = 0;
allContainers.Clear();
var i = 0;
Progress.Reset();
foreach (var assetsFile in assetsManager.assetsFileList)
{
var preloadTable = Array.Empty<PPtr<Object>>();
foreach (var asset in assetsFile.Objects)
{
var assetItem = new AssetItem(asset);
@@ -170,6 +173,9 @@ namespace AssetStudioGUI
var exportable = false;
switch (asset)
{
case PreloadData m_PreloadData:
preloadTable = m_PreloadData.m_Assets;
break;
case GameObject m_GameObject:
assetItem.Text = m_GameObject.m_Name;
break;
@@ -187,7 +193,7 @@ namespace AssetStudioGUI
break;
case VideoClip m_VideoClip:
if (!string.IsNullOrEmpty(m_VideoClip.m_OriginalPath))
assetItem.FullSize = asset.byteSize + (long)m_VideoClip.m_ExternalResources.m_Size;
assetItem.FullSize = asset.byteSize + m_VideoClip.m_ExternalResources.m_Size;
assetItem.Text = m_VideoClip.m_Name;
exportable = true;
break;
@@ -226,17 +232,23 @@ namespace AssetStudioGUI
productName = m_PlayerSettings.productName;
break;
case AssetBundle m_AssetBundle:
var isStreamedSceneAssetBundle = m_AssetBundle.m_IsStreamedSceneAssetBundle;
if (!isStreamedSceneAssetBundle)
{
preloadTable = m_AssetBundle.m_PreloadTable;
}
assetItem.Text = string.IsNullOrEmpty(m_AssetBundle.m_AssetBundleName) ? m_AssetBundle.m_Name : m_AssetBundle.m_AssetBundleName;
foreach (var m_Container in m_AssetBundle.m_Container)
{
var preloadIndex = m_Container.Value.preloadIndex;
var preloadSize = m_Container.Value.preloadSize;
var preloadSize = isStreamedSceneAssetBundle ? preloadTable.Length : m_Container.Value.preloadSize;
var preloadEnd = preloadIndex + preloadSize;
for (int k = preloadIndex; k < preloadEnd; k++)
for (var k = preloadIndex; k < preloadEnd; k++)
{
containers.Add((m_AssetBundle.m_PreloadTable[k], m_Container.Key));
containers.Add((preloadTable[k], m_Container.Key));
}
}
assetItem.Text = m_AssetBundle.m_Name;
break;
case ResourceManager m_ResourceManager:
foreach (var m_Container in m_ResourceManager.m_Container)
@@ -259,12 +271,22 @@ namespace AssetStudioGUI
Progress.Report(++i, objectCount);
}
}
foreach ((var pptr, var container) in containers)
foreach (var (pptr, container) in containers)
{
if (pptr.TryGet(out var obj))
{
objectAssetItemDic[obj].Container = container;
var asset = objectAssetItemDic[obj];
asset.Container = container;
allContainers[obj] = container;
if (asset.Type == ClassIDType.MonoBehaviour && container.Contains("/arts/charportraits/portraits"))
{
var portraitsList = Arknights.AkSpriteHelper.GeneratePortraits(asset);
foreach (var portrait in portraitsList)
{
exportableAssets.Add(new AssetItem(portrait));
}
}
}
}
foreach (var tmp in exportableAssets)
@@ -275,12 +297,19 @@ namespace AssetStudioGUI
visibleAssets = exportableAssets;
if (!Properties.Settings.Default.buildTreeStructure)
{
Logger.Info("Building tree structure step is skipped");
objectAssetItemDic.Clear();
return (productName, new List<TreeNode>());
}
Logger.Info("Building tree structure...");
var treeNodeCollection = new List<TreeNode>();
var treeNodeDictionary = new Dictionary<GameObject, GameObjectTreeNode>();
var assetsFileCount = assetsManager.assetsFileList.Count;
int j = 0;
var j = 0;
Progress.Reset();
foreach (var assetsFile in assetsManager.assetsFileList)
{
@@ -348,7 +377,6 @@ namespace AssetStudioGUI
Progress.Report(++j, assetsFileCount);
}
treeNodeDictionary.Clear();
objectAssetItemDic.Clear();
return (productName, treeNodeCollection);
@@ -386,7 +414,6 @@ namespace AssetStudioGUI
typeMap.Add(assetsFile.unityVersion, items);
}
}
return typeMap;
}
@@ -726,7 +753,7 @@ namespace AssetStudioGUI
public static string DumpAsset(Object obj)
{
var str = obj.Dump();
var str = obj?.Dump();
if (str == null && obj is MonoBehaviour m_MonoBehaviour)
{
var type = MonoBehaviourToTypeTree(m_MonoBehaviour);
@@ -745,6 +772,8 @@ namespace AssetStudioGUI
public static void ExportLive2D(Object[] cubismMocs, string exportPath)
{
var baseDestPath = Path.Combine(exportPath, "Live2DOutput");
var motionMode = Properties.Settings.Default.l2dMotionMode;
var forceBezier = Properties.Settings.Default.l2dForceBezier;
ThreadPool.QueueUserWorkItem(state =>
{
@@ -753,48 +782,73 @@ namespace AssetStudioGUI
var useFullContainerPath = false;
if (cubismMocs.Length > 1)
{
var basePathSet = cubismMocs.Select(x => allContainers[x].Substring(0, allContainers[x].LastIndexOf("/"))).ToHashSet();
var basePathSet = cubismMocs.Select(x =>
{
var pathLen = allContainers.TryGetValue(x, out var itemContainer) ? itemContainer.LastIndexOf("/") : 0;
pathLen = pathLen < 0 ? allContainers[x].Length : pathLen;
return itemContainer?.Substring(0, pathLen);
}).ToHashSet();
if (basePathSet.All(x => x == null))
{
Logger.Error($"Live2D Cubism export error\r\nCannot find any model related files");
StatusStripUpdate("Live2D export canceled");
Progress.Reset();
return;
}
if (basePathSet.Count != cubismMocs.Length)
{
useFullContainerPath = true;
}
}
var basePathList = useFullContainerPath ?
cubismMocs.Select(x => allContainers[x]).ToList() :
cubismMocs.Select(x => allContainers[x].Substring(0, allContainers[x].LastIndexOf("/"))).ToList();
var basePathList = cubismMocs.Select(x =>
{
allContainers.TryGetValue(x, out var container);
container = useFullContainerPath
? container
: container?.Substring(0, container.LastIndexOf("/"));
return container;
}).Where(x => x != null).ToList();
var lookup = allContainers.ToLookup(
x => basePathList.Find(b => x.Value.Contains(b) && x.Value.Split('/').Any(y => y == b.Substring(b.LastIndexOf("/") + 1))),
x => x.Key
);
var totalModelCount = lookup.LongCount(x => x.Key != null);
var name = "";
var modelCounter = 0;
foreach (var assets in lookup)
{
var container = assets.Key;
if (container == null)
var srcContainer = assets.Key;
if (srcContainer == null)
continue;
name = container;
var container = srcContainer;
Logger.Info($"[{modelCounter + 1}/{totalModelCount}] Exporting Live2D: \"{container}\"...");
Logger.Info($"[{modelCounter + 1}/{totalModelCount}] Exporting Live2D: \"{srcContainer}\"...");
try
{
var modelName = useFullContainerPath ? Path.GetFileNameWithoutExtension(container) : container.Substring(container.LastIndexOf('/') + 1);
container = Path.HasExtension(container) ? container.Replace(Path.GetExtension(container), "") : container;
var destPath = Path.Combine(baseDestPath, container) + Path.DirectorySeparatorChar;
ExtractLive2D(assets, destPath, modelName, assemblyLoader);
ExtractLive2D(assets, destPath, modelName, assemblyLoader, motionMode, forceBezier);
modelCounter++;
}
catch (Exception ex)
{
Logger.Error($"Live2D model export error: \"{name}\"", ex);
Logger.Error($"Live2D model export error: \"{srcContainer}\"", ex);
}
Progress.Report(modelCounter, (int)totalModelCount);
}
Logger.Info($"Finished exporting [{modelCounter}/{totalModelCount}] Live2D model(s).");
if (modelCounter < totalModelCount)
{
var total = (int)totalModelCount;
Progress.Report(total, total);
}
if (Properties.Settings.Default.openAfterExport && modelCounter > 0)
{
OpenFolderInExplorer(exportPath);

View File

@@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net472;net6.0;net6.0-windows;net7.0;net7.0-windows</TargetFrameworks>
<Version>0.17.3.0</Version>
<Copyright>Copyright © Perfare 2018-2022; Copyright © aelurum 2023</Copyright>
<TargetFrameworks>net472;net6.0;net6.0-windows;net7.0;net7.0-windows;net8.0;net8.0-windows</TargetFrameworks>
<Version>1.2.0</Version>
<Copyright>Copyright © Perfare 2018-2022; Copyright © aelurum 2025</Copyright>
<DebugType>embedded</DebugType>
</PropertyGroup>
@@ -22,7 +22,7 @@
<PackageReference Include="Kyaru.Texture2DDecoder">
<Version>0.17.0</Version>
</PackageReference>
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0" />
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.0" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net472' ">

View File

@@ -0,0 +1,30 @@
using System;
namespace CubismLive2DExtractor
{
public class AnimationCurve
{
public CubismKeyframeData[] m_Curve { get; set; }
public int m_PreInfinity { get; set; }
public int m_PostInfinity { get; set; }
public int m_RotationOrder { get; set; }
}
public class CubismFadeMotion
{
public string m_Name { get; set; }
public string MotionName { get; set; }
public float FadeInTime { get; set; }
public float FadeOutTime { get; set; }
public string[] ParameterIds { get; set; }
public AnimationCurve[] ParameterCurves { get; set; }
public float[] ParameterFadeInTimes { get; set; }
public float[] ParameterFadeOutTimes { get; set; }
public float MotionLength { get; set; }
public CubismFadeMotion()
{
ParameterIds = Array.Empty<string>();
}
}
}

View File

@@ -0,0 +1,26 @@
namespace CubismLive2DExtractor
{
public class CubismKeyframeData
{
public float time { get; set; }
public float value { get; set; }
public float inSlope { get; set; }
public float outSlope { get; set; }
public int weightedMode { get; set; }
public float inWeight { get; set; }
public float outWeight { get; set; }
public CubismKeyframeData() { }
public CubismKeyframeData(ImportedKeyframe<float> keyframe)
{
time = keyframe.time;
value = keyframe.value;
inSlope = keyframe.inSlope;
outSlope = keyframe.outSlope;
weightedMode = 0;
inWeight = 0;
outWeight = 0;
}
}
}

View File

@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AssetStudio;
@@ -73,7 +72,7 @@ namespace CubismLive2DExtractor
if (iAnim.TrackList.Count == 0 || iAnim.Events.Count == 0)
{
Logger.Warning($"[Motion Converter] {iAnim.Name} has {iAnim.TrackList.Count} tracks and {iAnim.Events.Count} event!.");
Logger.Warning($"[Motion Converter] \"{iAnim.Name}\" has {iAnim.TrackList.Count} tracks and {iAnim.Events.Count} event!.");
}
}
}
@@ -84,7 +83,7 @@ namespace CubismLive2DExtractor
GetLive2dPath(binding, out var target, out var boneName);
if (string.IsNullOrEmpty(boneName))
{
Logger.Warning($"[Motion Converter] {iAnim.Name} read fail on binding {Array.IndexOf(m_ClipBindingConstant.genericBindings, binding)}");
Logger.Warning($"[Motion Converter] \"{iAnim.Name}\" read fail on binding {Array.IndexOf(m_ClipBindingConstant.genericBindings, binding)}");
return;
}
@@ -99,7 +98,7 @@ namespace CubismLive2DExtractor
GetLive2dPath(binding, out var target, out var boneName);
if (string.IsNullOrEmpty(boneName))
{
Logger.Warning($"[Motion Converter] {iAnim.Name} read fail on binding {Array.IndexOf(m_ClipBindingConstant.genericBindings, binding)}");
Logger.Warning($"[Motion Converter] \"{iAnim.Name}\" read fail on binding {Array.IndexOf(m_ClipBindingConstant.genericBindings, binding)}");
return;
}

View File

@@ -1,7 +1,9 @@
using System;
// File Format Specifications
// https://github.com/Live2D/CubismSpecs/blob/master/FileFormats/motion3.json.md
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace CubismLive2DExtractor
{
@@ -18,24 +20,238 @@ namespace CubismLive2DExtractor
public float Fps;
public bool Loop;
public bool AreBeziersRestricted;
public float FadeInTime;
public float FadeOutTime;
public int CurveCount;
public int TotalSegmentCount;
public int TotalPointCount;
public int UserDataCount;
public int TotalUserDataSize;
};
}
public class SerializableCurve
{
public string Target;
public string Id;
public float FadeInTime;
public float FadeOutTime;
public List<float> Segments;
};
}
public class SerializableUserData
{
public float Time;
public string Value;
}
private static void AddSegments(
CubismKeyframeData curve,
CubismKeyframeData preCurve,
CubismKeyframeData nextCurve,
SerializableCurve cubismCurve,
bool forceBezier,
ref int totalPointCount,
ref int totalSegmentCount,
ref int j
)
{
if (Math.Abs(curve.time - preCurve.time - 0.01f) < 0.0001f) // InverseSteppedSegment
{
if (nextCurve.value == curve.value)
{
cubismCurve.Segments.Add(3f); // Segment ID
cubismCurve.Segments.Add(nextCurve.time);
cubismCurve.Segments.Add(nextCurve.value);
j += 1;
totalPointCount += 1;
totalSegmentCount++;
return;
}
}
if (float.IsPositiveInfinity(curve.inSlope)) // SteppedSegment
{
cubismCurve.Segments.Add(2f); // Segment ID
cubismCurve.Segments.Add(curve.time);
cubismCurve.Segments.Add(curve.value);
totalPointCount += 1;
}
else if (preCurve.outSlope == 0f && Math.Abs(curve.inSlope) < 0.0001f && !forceBezier) // LinearSegment
{
cubismCurve.Segments.Add(0f); // Segment ID
cubismCurve.Segments.Add(curve.time);
cubismCurve.Segments.Add(curve.value);
totalPointCount += 1;
}
else // BezierSegment
{
var tangentLength = (curve.time - preCurve.time) / 3f;
cubismCurve.Segments.Add(1f); // Segment ID
cubismCurve.Segments.Add(preCurve.time + tangentLength);
cubismCurve.Segments.Add(preCurve.outSlope * tangentLength + preCurve.value);
cubismCurve.Segments.Add(curve.time - tangentLength);
cubismCurve.Segments.Add(curve.value - curve.inSlope * tangentLength);
cubismCurve.Segments.Add(curve.time);
cubismCurve.Segments.Add(curve.value);
totalPointCount += 3;
}
totalSegmentCount++;
}
public CubismMotion3Json(CubismFadeMotion fadeMotion, HashSet<string> paramNames, HashSet<string> partNames, bool forceBezier)
{
Version = 3;
Meta = new SerializableMeta
{
// Duration of the motion in seconds.
Duration = fadeMotion.MotionLength,
// Framerate of the motion in seconds.
Fps = 30,
// [Optional] Status of the looping of the motion.
Loop = true,
// [Optional] Status of the restriction of Bezier handles'X translations.
AreBeziersRestricted = true,
// [Optional] Time of the overall Fade-In for easing in seconds.
FadeInTime = fadeMotion.FadeInTime,
// [Optional] Time of the overall Fade-Out for easing in seconds.
FadeOutTime = fadeMotion.FadeOutTime,
// The total number of curves.
CurveCount = (int)fadeMotion.ParameterCurves.LongCount(x => x.m_Curve.Length > 0),
// [Optional] The total number of UserData.
UserDataCount = 0
};
// Motion curves.
Curves = new SerializableCurve[Meta.CurveCount];
var totalSegmentCount = 1;
var totalPointCount = 1;
var actualCurveCount = 0;
for (var i = 0; i < fadeMotion.ParameterCurves.Length; i++)
{
if (fadeMotion.ParameterCurves[i].m_Curve.Length == 0)
continue;
string target;
string paramId = fadeMotion.ParameterIds[i];
switch (paramId)
{
case "Opacity":
case "EyeBlink":
case "LipSync":
target = "Model";
break;
default:
if (paramNames.Contains(paramId))
{
target = "Parameter";
}
else if (partNames.Contains(paramId))
{
target = "PartOpacity";
}
else
{
target = paramId.ToLower().Contains("part") ? "PartOpacity" : "Parameter";
AssetStudio.Logger.Warning($"[{fadeMotion.m_Name}] Binding error: Unable to find \"{paramId}\" among the model parts/parameters");
}
break;
}
Curves[actualCurveCount] = new SerializableCurve
{
// Target type.
Target = target,
// Identifier for mapping curve to target.
Id = paramId,
// [Optional] Time of the Fade - In for easing in seconds.
FadeInTime = fadeMotion.ParameterFadeInTimes[i],
// [Optional] Time of the Fade - Out for easing in seconds.
FadeOutTime = fadeMotion.ParameterFadeOutTimes[i],
// Flattened segments.
Segments = new List<float>
{
// First point
fadeMotion.ParameterCurves[i].m_Curve[0].time,
fadeMotion.ParameterCurves[i].m_Curve[0].value
}
};
for (var j = 1; j < fadeMotion.ParameterCurves[i].m_Curve.Length; j++)
{
var curve = fadeMotion.ParameterCurves[i].m_Curve[j];
var preCurve = fadeMotion.ParameterCurves[i].m_Curve[j - 1];
var next = fadeMotion.ParameterCurves[i].m_Curve.ElementAtOrDefault(j + 1);
var nextCurve = next ?? new CubismKeyframeData();
AddSegments(curve, preCurve, nextCurve, Curves[actualCurveCount], forceBezier, ref totalPointCount, ref totalSegmentCount, ref j);
}
actualCurveCount++;
}
// The total number of segments (from all curves).
Meta.TotalSegmentCount = totalSegmentCount;
// The total number of points (from all segments of all curves).
Meta.TotalPointCount = totalPointCount;
UserData = Array.Empty<SerializableUserData>();
// [Optional] The total size of UserData in bytes.
Meta.TotalUserDataSize = 0;
}
public CubismMotion3Json(ImportedKeyframedAnimation animation, bool forceBezier)
{
Version = 3;
Meta = new SerializableMeta
{
Duration = animation.Duration,
Fps = animation.SampleRate,
Loop = true,
AreBeziersRestricted = true,
FadeInTime = 0,
FadeOutTime = 0,
CurveCount = animation.TrackList.Count,
UserDataCount = animation.Events.Count
};
Curves = new SerializableCurve[Meta.CurveCount];
var totalSegmentCount = 1;
var totalPointCount = 1;
for (var i = 0; i < Meta.CurveCount; i++)
{
var track = animation.TrackList[i];
Curves[i] = new SerializableCurve
{
Target = track.Target,
Id = track.Name,
FadeInTime = -1,
FadeOutTime = -1,
Segments = new List<float>
{
0f,
track.Curve[0].value
}
};
for (var j = 1; j < track.Curve.Count; j++)
{
var curve = new CubismKeyframeData(track.Curve[j]);
var preCurve = new CubismKeyframeData(track.Curve[j - 1]);
var next = track.Curve.ElementAtOrDefault(j + 1);
var nextCurve = next != null ? new CubismKeyframeData(next) : new CubismKeyframeData();
AddSegments(curve, preCurve, nextCurve, Curves[i], forceBezier, ref totalPointCount, ref totalSegmentCount, ref j);
}
}
Meta.TotalSegmentCount = totalSegmentCount;
Meta.TotalPointCount = totalPointCount;
UserData = new SerializableUserData[Meta.UserDataCount];
var totalUserDataSize = 0;
for (var i = 0; i < Meta.UserDataCount; i++)
{
var @event = animation.Events[i];
UserData[i] = new SerializableUserData
{
Time = @event.time,
Value = @event.value
};
totalUserDataSize += @event.value.Length;
}
Meta.TotalUserDataSize = totalUserDataSize;
}
}
}

View File

@@ -18,7 +18,7 @@ namespace CubismLive2DExtractor
{
public static class Live2DExtractor
{
public static void ExtractLive2D(IGrouping<string, AssetStudio.Object> assets, string destPath, string modelName, AssemblyLoader assemblyLoader)
public static void ExtractLive2D(IGrouping<string, AssetStudio.Object> assets, string destPath, string modelName, AssemblyLoader assemblyLoader, Live2DMotionMode motionMode, bool forceBezier = false)
{
var destTexturePath = Path.Combine(destPath, "textures") + Path.DirectorySeparatorChar;
var destMotionPath = Path.Combine(destPath, "motions") + Path.DirectorySeparatorChar;
@@ -26,20 +26,75 @@ namespace CubismLive2DExtractor
Directory.CreateDirectory(destPath);
Directory.CreateDirectory(destTexturePath);
var monoBehaviours = new List<MonoBehaviour>();
var texture2Ds = new List<Texture2D>();
var expressionList = new List<MonoBehaviour>();
var fadeMotionList = new List<MonoBehaviour>();
var gameObjects = new List<GameObject>();
var animationClips = new List<AnimationClip>();
var textures = new SortedSet<string>();
var eyeBlinkParameters = new HashSet<string>();
var lipSyncParameters = new HashSet<string>();
var parameterNames = new HashSet<string>();
var partNames = new HashSet<string>();
MonoBehaviour physics = null;
foreach (var asset in assets)
{
switch (asset)
{
case MonoBehaviour m_MonoBehaviour:
monoBehaviours.Add(m_MonoBehaviour);
if (m_MonoBehaviour.m_Script.TryGet(out var m_Script))
{
switch (m_Script.m_ClassName)
{
case "CubismMoc":
File.WriteAllBytes($"{destPath}{modelName}.moc3", ParseMoc(m_MonoBehaviour)); //moc
break;
case "CubismPhysicsController":
physics = physics ?? m_MonoBehaviour;
break;
case "CubismExpressionData":
expressionList.Add(m_MonoBehaviour);
break;
case "CubismFadeMotionData":
fadeMotionList.Add(m_MonoBehaviour);
break;
case "CubismEyeBlinkParameter":
if (m_MonoBehaviour.m_GameObject.TryGet(out var blinkGameObject))
{
eyeBlinkParameters.Add(blinkGameObject.m_Name);
}
break;
case "CubismMouthParameter":
if (m_MonoBehaviour.m_GameObject.TryGet(out var mouthGameObject))
{
lipSyncParameters.Add(mouthGameObject.m_Name);
}
break;
case "CubismParameter":
if (m_MonoBehaviour.m_GameObject.TryGet(out var paramGameObject))
{
parameterNames.Add(paramGameObject.m_Name);
}
break;
case "CubismPart":
if (m_MonoBehaviour.m_GameObject.TryGet(out var partGameObject))
{
partNames.Add(partGameObject.m_Name);
}
break;
}
}
break;
case Texture2D m_Texture2D:
texture2Ds.Add(m_Texture2D);
using (var image = m_Texture2D.ConvertToImage(flip: true))
{
using (var file = File.OpenWrite($"{destTexturePath}{m_Texture2D.m_Name}.png"))
{
image.WriteToStream(file, ImageFormat.Png);
}
textures.Add($"textures/{m_Texture2D.m_Name}.png"); //texture
}
break;
case GameObject m_GameObject:
gameObjects.Add(m_GameObject);
@@ -50,15 +105,12 @@ namespace CubismLive2DExtractor
}
}
//physics
var physics = monoBehaviours.FirstOrDefault(x =>
if (textures.Count == 0)
{
if (x.m_Script.TryGet(out var m_Script))
{
return m_Script.m_ClassName == "CubismPhysicsController";
}
return false;
});
Logger.Warning($"No textures found for \"{modelName}\" model.");
}
//physics
if (physics != null)
{
try
@@ -73,36 +125,51 @@ namespace CubismLive2DExtractor
}
}
//moc
var moc = monoBehaviours.First(x =>
{
if (x.m_Script.TryGet(out var m_Script))
{
return m_Script.m_ClassName == "CubismMoc";
}
return false;
});
File.WriteAllBytes($"{destPath}{modelName}.moc3", ParseMoc(moc));
//texture
var textures = new SortedSet<string>();
foreach (var texture2D in texture2Ds)
{
using (var image = texture2D.ConvertToImage(flip: true))
{
textures.Add($"textures/{texture2D.m_Name}.png");
using (var file = File.OpenWrite($"{destTexturePath}{texture2D.m_Name}.png"))
{
image.WriteToStream(file, ImageFormat.Png);
}
}
}
//motion
var motions = new SortedDictionary<string, JArray>();
if (gameObjects.Count > 0)
if (motionMode == Live2DMotionMode.MonoBehaviour && fadeMotionList.Count > 0) //motion from MonoBehaviour
{
Logger.Debug("Motion export method: MonoBehaviour (Fade motion)");
Directory.CreateDirectory(destMotionPath);
foreach (var fadeMotionMono in fadeMotionList)
{
var fadeMotionObj = fadeMotionMono.ToType();
if (fadeMotionObj == null)
{
var m_Type = fadeMotionMono.ConvertToTypeTree(assemblyLoader);
fadeMotionObj = fadeMotionMono.ToType(m_Type);
if (fadeMotionObj == null)
{
Logger.Warning($"Fade motion \"{fadeMotionMono.m_Name}\" is not readable.");
continue;
}
}
var fadeMotion = JsonConvert.DeserializeObject<CubismFadeMotion>(JsonConvert.SerializeObject(fadeMotionObj));
if (fadeMotion.ParameterIds.Length == 0)
continue;
var motionJson = new CubismMotion3Json(fadeMotion, parameterNames, partNames, forceBezier);
var animName = Path.GetFileNameWithoutExtension(fadeMotion.m_Name);
if (motions.ContainsKey(animName))
{
animName = $"{animName}_{fadeMotion.GetHashCode()}";
if (motions.ContainsKey(animName))
continue;
}
var motionPath = new JObject(new JProperty("File", $"motions/{animName}.motion3.json"));
motions.Add(animName, new JArray(motionPath));
File.WriteAllText($"{destMotionPath}{animName}.motion3.json", JsonConvert.SerializeObject(motionJson, Formatting.Indented, new MyJsonConverter()));
}
}
else if (gameObjects.Count > 0) //motion from AnimationClip
{
var exportMethod = motionMode == Live2DMotionMode.AnimationClip
? "AnimationClip"
: "AnimationClip (no Fade motions found)";
Logger.Debug($"Motion export method: {exportMethod}");
var rootTransform = gameObjects[0].m_Transform;
while (rootTransform.m_Father.TryGet(out var m_Father))
{
@@ -114,114 +181,37 @@ namespace CubismLive2DExtractor
{
Directory.CreateDirectory(destMotionPath);
}
foreach (ImportedKeyframedAnimation animation in converter.AnimationList)
foreach (var animation in converter.AnimationList)
{
var json = new CubismMotion3Json
{
Version = 3,
Meta = new CubismMotion3Json.SerializableMeta
{
Duration = animation.Duration,
Fps = animation.SampleRate,
Loop = true,
AreBeziersRestricted = true,
CurveCount = animation.TrackList.Count,
UserDataCount = animation.Events.Count
},
Curves = new CubismMotion3Json.SerializableCurve[animation.TrackList.Count]
};
int totalSegmentCount = 1;
int totalPointCount = 1;
for (int i = 0; i < animation.TrackList.Count; i++)
{
var track = animation.TrackList[i];
json.Curves[i] = new CubismMotion3Json.SerializableCurve
{
Target = track.Target,
Id = track.Name,
Segments = new List<float> { 0f, track.Curve[0].value }
};
for (var j = 1; j < track.Curve.Count; j++)
{
var curve = track.Curve[j];
var preCurve = track.Curve[j - 1];
if (Math.Abs(curve.time - preCurve.time - 0.01f) < 0.0001f) //InverseSteppedSegment
{
var nextCurve = track.Curve[j + 1];
if (nextCurve.value == curve.value)
{
json.Curves[i].Segments.Add(3f);
json.Curves[i].Segments.Add(nextCurve.time);
json.Curves[i].Segments.Add(nextCurve.value);
j += 1;
totalPointCount += 1;
totalSegmentCount++;
continue;
}
}
if (float.IsPositiveInfinity(curve.inSlope)) //SteppedSegment
{
json.Curves[i].Segments.Add(2f);
json.Curves[i].Segments.Add(curve.time);
json.Curves[i].Segments.Add(curve.value);
totalPointCount += 1;
}
else if (preCurve.outSlope == 0f && Math.Abs(curve.inSlope) < 0.0001f) //LinearSegment
{
json.Curves[i].Segments.Add(0f);
json.Curves[i].Segments.Add(curve.time);
json.Curves[i].Segments.Add(curve.value);
totalPointCount += 1;
}
else //BezierSegment
{
var tangentLength = (curve.time - preCurve.time) / 3f;
json.Curves[i].Segments.Add(1f);
json.Curves[i].Segments.Add(preCurve.time + tangentLength);
json.Curves[i].Segments.Add(preCurve.outSlope * tangentLength + preCurve.value);
json.Curves[i].Segments.Add(curve.time - tangentLength);
json.Curves[i].Segments.Add(curve.value - curve.inSlope * tangentLength);
json.Curves[i].Segments.Add(curve.time);
json.Curves[i].Segments.Add(curve.value);
totalPointCount += 3;
}
totalSegmentCount++;
}
}
json.Meta.TotalSegmentCount = totalSegmentCount;
json.Meta.TotalPointCount = totalPointCount;
var motionJson = new CubismMotion3Json(animation, forceBezier);
json.UserData = new CubismMotion3Json.SerializableUserData[animation.Events.Count];
var totalUserDataSize = 0;
for (var i = 0; i < animation.Events.Count; i++)
var animName = animation.Name;
if (motions.ContainsKey(animName))
{
var @event = animation.Events[i];
json.UserData[i] = new CubismMotion3Json.SerializableUserData
{
Time = @event.time,
Value = @event.value
};
totalUserDataSize += @event.value.Length;
}
json.Meta.TotalUserDataSize = totalUserDataSize;
animName = $"{animName}_{animation.GetHashCode()}";
var motionPath = new JObject(new JProperty("File", $"motions/{animation.Name}.motion3.json"));
motions.Add(animation.Name, new JArray(motionPath));
File.WriteAllText($"{destMotionPath}{animation.Name}.motion3.json", JsonConvert.SerializeObject(json, Formatting.Indented, new MyJsonConverter()));
if (motions.ContainsKey(animName))
continue;
}
var motionPath = new JObject(new JProperty("File", $"motions/{animName}.motion3.json"));
motions.Add(animName, new JArray(motionPath));
File.WriteAllText($"{destMotionPath}{animName}.motion3.json", JsonConvert.SerializeObject(motionJson, Formatting.Indented, new MyJsonConverter()));
}
}
if (motions.Count == 0)
{
Logger.Warning($"No motions found for \"{modelName}\" model.");
}
//expression
var expressions = new JArray();
var monoBehaviourArray = monoBehaviours.Where(x => x.m_Name.EndsWith(".exp3")).ToArray();
if (monoBehaviourArray.Length > 0)
if (expressionList.Count > 0)
{
Directory.CreateDirectory(destExpressionPath);
}
foreach (var monoBehaviour in monoBehaviourArray)
foreach (var monoBehaviour in expressionList)
{
var fullName = monoBehaviour.m_Name;
var expressionName = fullName.Replace(".exp3", "");
var expressionName = monoBehaviour.m_Name.Replace(".exp3", "");
var expressionObj = monoBehaviour.ToType();
if (expressionObj == null)
{
@@ -238,57 +228,38 @@ namespace CubismLive2DExtractor
expressions.Add(new JObject
{
{ "Name", expressionName },
{ "File", $"expressions/{fullName}.json" }
{ "File", $"expressions/{expressionName}.exp3.json" }
});
File.WriteAllText($"{destExpressionPath}{fullName}.json", JsonConvert.SerializeObject(expression, Formatting.Indented));
File.WriteAllText($"{destExpressionPath}{expressionName}.exp3.json", JsonConvert.SerializeObject(expression, Formatting.Indented));
}
//model
//group
var groups = new List<CubismModel3Json.SerializableGroup>();
var eyeBlinkParameters = monoBehaviours.Where(x =>
{
x.m_Script.TryGet(out var m_Script);
return m_Script?.m_ClassName == "CubismEyeBlinkParameter";
}).Select(x =>
{
x.m_GameObject.TryGet(out var m_GameObject);
return m_GameObject?.m_Name;
}).ToHashSet();
//Try looking for group IDs among the gameObjects
if (eyeBlinkParameters.Count == 0)
{
eyeBlinkParameters = gameObjects.Where(x =>
{
return x.m_Name.ToLower().Contains("eye")
x.m_Name.ToLower().Contains("eye")
&& x.m_Name.ToLower().Contains("open")
&& (x.m_Name.ToLower().Contains('l') || x.m_Name.ToLower().Contains('r'));
}).Select(x => x.m_Name).ToHashSet();
&& (x.m_Name.ToLower().Contains('l') || x.m_Name.ToLower().Contains('r'))
).Select(x => x.m_Name).ToHashSet();
}
if (lipSyncParameters.Count == 0)
{
lipSyncParameters = gameObjects.Where(x =>
x.m_Name.ToLower().Contains("mouth")
&& x.m_Name.ToLower().Contains("open")
&& x.m_Name.ToLower().Contains('y')
).Select(x => x.m_Name).ToHashSet();
}
groups.Add(new CubismModel3Json.SerializableGroup
{
Target = "Parameter",
Name = "EyeBlink",
Ids = eyeBlinkParameters.ToArray()
});
var lipSyncParameters = monoBehaviours.Where(x =>
{
x.m_Script.TryGet(out var m_Script);
return m_Script?.m_ClassName == "CubismMouthParameter";
}).Select(x =>
{
x.m_GameObject.TryGet(out var m_GameObject);
return m_GameObject?.m_Name;
}).ToHashSet();
if (lipSyncParameters.Count == 0)
{
lipSyncParameters = gameObjects.Where(x =>
{
return x.m_Name.ToLower().Contains("mouth")
&& x.m_Name.ToLower().Contains("open")
&& x.m_Name.ToLower().Contains('y');
}).Select(x => x.m_Name).ToHashSet();
}
groups.Add(new CubismModel3Json.SerializableGroup
{
Target = "Parameter",
@@ -296,6 +267,7 @@ namespace CubismLive2DExtractor
Ids = lipSyncParameters.ToArray()
});
//model
var model3 = new CubismModel3Json
{
Version = 3,

View File

@@ -0,0 +1,8 @@
namespace CubismLive2DExtractor
{
public enum Live2DMotionMode
{
MonoBehaviour,
AnimationClip
}
}

View File

@@ -32,7 +32,7 @@ namespace AssetStudio
}
else
{
if (m_Sprite.m_RD.texture.TryGet(out var m_Texture2D) && m_Sprite.m_RD.alphaTexture.TryGet(out var m_AlphaTexture2D) && spriteMaskMode != SpriteMaskMode.Off)
if (m_Sprite.m_RD.texture.TryGet(out var m_Texture2D) && m_Sprite.m_RD.alphaTexture.TryGet(out var m_AlphaTexture2D) && spriteMaskMode != SpriteMaskMode.Off && !m_Sprite.akSplitAlpha)
{
Image<Bgra32> tex = null;
if (spriteMaskMode != SpriteMaskMode.MaskOnly)
@@ -154,9 +154,16 @@ namespace AssetStudio
if (triangles.Length < 1024)
{
var rectP = new RectangularPolygon(0, 0, rect.Width, rect.Height);
spriteImage.Mutate(x => x.Fill(options, SixLabors.ImageSharp.Color.Red, rectP.Clip(path)));
spriteImage.Mutate(x => x.Flip(FlipMode.Vertical));
return spriteImage;
try
{
spriteImage.Mutate(x => x.Fill(options, SixLabors.ImageSharp.Color.Red, rectP.Clip(path)));
spriteImage.Mutate(x => x.Flip(FlipMode.Vertical));
return spriteImage;
}
catch (ArgumentOutOfRangeException)
{
// ignored
}
}
using (var mask = new Image<Bgra32>(rect.Width, rect.Height, SixLabors.ImageSharp.Color.Black))
{
@@ -167,9 +174,9 @@ namespace AssetStudio
return spriteImage;
}
}
catch
catch (Exception e)
{
// ignored
Logger.Warning($"{m_Sprite.m_Name} Unable to render the packed sprite correctly.\n{e}");
}
}

View File

@@ -1,80 +1,42 @@
# Changelog
## v0.17.3.0 [13-09-2023]
- [CLI] Added support for exporting split objects (fbx) (https://github.com/aelurum/AssetStudio/pull/10)
- [CLI] Fixed display of asset names in the exported asset list in some working modes
- [CLI] Fixed a bug where the default output folder might not exist
- Added support of Texture2D assets from Unity 2022.2+
- Fixed AssemblyLoader (https://github.com/aelurum/AssetStudio/issues/6)
- [CLI] Added --load-all flag to load assets of all types
- [CLI] Improved option grouping on the help screen
## v1.1.0 (based on v0.17.4) [16-12-2023]
- Added support for avg sprite without separate alpha tex
- Changed avg face sprite detection method, since we can't rely on the "IsWholeBody" flag
## v0.17.2.0 [27-08-2023]
- [GUI] Improved Scene Hierarchy tab
- Added "Related assets" item to the context menu (https://github.com/aelurum/AssetStudio/issues/7)
- [GUI] Added app.manifest for net472 build
- Added long paths support (win10 v1607+)
- Fixed blurring at high DPI with scaling
- [CLI] Fixed sprite export in sprite only mode
- Made some changes to motion list for live2d models
- Motion list is now sorted
- Motions divided into groups (each motion is a separate group)
- Motion names are used as group names
- Updated dependencies
- Made some other minor fixes and improvements
## v0.17.1.0 [12-07-2023]
#### Breaking Changes
- With the drag&drop fix (https://github.com/aelurum/AssetStudio/commit/2f8f57c1a63893c0b0d2a55349d6cb6d8f8a5a3b), functions `LoadFiles` and `LoadFolder` in AssetsManager have been replaced with one universal function `LoadFilesAndFolders`
#### Changes
- Fixed Texture2DDecoderNative compatibility issue with Linux/macOS (CLI preparation #1)
- Changed image library to ImageSharp (CLI preparation #2)
- Added support for sprites with alpha mask
- Sprites with alpha mask can now be viewed and exported with transparency
- Added hotkeys to control display of an alpha mask on the preview tab
- Added an option to the export settings to enable/disable export with alpha mask as well
- Prevented texture2D preview options from being changed with hotkeys outside of texture preview (e.g. when some other asset is selected)
- Added image export in WebP format
- Updated FMOD to 0.2.0.22 (CLI preparation #3)
- Added progress info about zip(apk) file loading process
## v1.0.0 (based on v0.17.3) [24-09-2023]
- Base version changed to AssetStudioMod v0.17.x
- Replaced NetVips lib with ImageSharp lib
- Removed arknights related grouping options
- Added option to export avg character sprites with aliases in their names (if exist)
- Added support for portrait sprites
- Added CLI version
- [GUI] Added context menu with "Select all", "Clear selection", "Expand all" and "Collapse all" options to the "Scene Hierarchy" tab
- Selected objects count is now displayed in the status bar
- [GUI] Improved error handling
- [CLI] Added support for partial assets reading
- [GUI] Added some videoClip info to preview tab
- [GUI] Improved memory usage of image previews
- Disabled Shader support for Unity > 2020
- Added error message for bundles with UnityCN encryption
- Added error message on incorrect format of specified Unity version
- Block alignment fix for Unity 2019.4.X (source: https://github.com/K0lb3/UnityPy/commit/10346b4f02f2dbe0fa707799130c9f83c24f8e24)
- [GUI] Added "About" window
- Fixed cutout glitch in some packed sprites (https://github.com/Perfare/AssetStudio/issues/1015)
- Optimized drawing performance of packed sprites
- [GUI] Improved asset list filtering
- Added filter history
- Added more filtering modes: Include, Exclude, Regex (Name/Container)
- Added grouping option with full container path (https://github.com/Perfare/AssetStudio/issues/815)
- [GUI] - "container path full (with name)"
- [CLI] - "containerFull"
- Improved "Restore TextAsset extension name" option
- If checked, AssetStudio will first try to find an extension in an asset's name and only then in its container. If no extension is found, ".txt" will be used
- [GUI] Fixed audio player position in maximized window
- [GUI] Improved 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
- [GUI] Added showing of progress bar in the taskbar button
- Added option to export Live2D Cubism 3 models
- Fixed some bugs
## v0.16.8.1 [25-11-2021]
- Uses System.Drawing lib instead of ImageSharp for process textures
- Added alphanumeric sorting to the column with asset names for more natural presentation of asset list
- Improved "Copy text" option in right click menu, to display what exactly to copy
- Added "Dump selected assets" option to right click menu
- Added 'selected assets count' info to status strip when you select assets
- Added 'exported count / total export count' info to status strip during export
- "Show error message" option on the "Debug" tab has been renamed to "Show all error messages" and is now disabled by default
- "Fixed" an issue with getting stuck during the "Building tree structure" step
- Fixed a bug with listSearch that could make it not work in some conditions
- Fixed a rare bug for resource files with the same name, that caused their data to be overwritten and become incorrect
## v0.15.47.4 [25-01-2022]
- Fixed bug with wrong "isWholeBody" flag for the last sprite in a sprite list
## v0.15.47.3 [17-09-2021]
- Added support for avg Sprite Groups
## v0.15.47.2 [04-08-2021]
- Added support for avg character sprites with "isWholeBody" flag
## v0.15.47.1 [06-02-2021]
- Added support for avg character face sprites
- Added option to fix avg character sprite names
- Added NetVips lib for sprite processing
## v0.15.20.3 [24-09-2020]
- Added support for sprites with an external alpha texture
- Added support of alpha texture resizing for 2048x2048 sprites
## v0.15.20.2 [09-09-2020]
- Added support for sprites with alpha textures
- Added some arknights related grouping options to the "Export options" window
## v0.15.20.1 [01-09-2020]
- Base version updated to v0.15.x
## v0.14.38.1 [2020]
- Initial version

147
README.md
View File

@@ -1,107 +1,86 @@
# AssetStudioMod
# ArknightsStudio
[![Build status](https://ci.appveyor.com/api/projects/status/5qyai0hqs0ktyara/branch/AssetStudioMod?svg=true)](https://ci.appveyor.com/project/aelurum/assetstudiomod/branch/AssetStudioMod)
[![Latest release](https://img.shields.io/github/v/release/aelurum/AssetStudio?filter=ak*&label=Latest%20release)](https://github.com/aelurum/AssetStudio/tags) [![Download latest release](https://img.shields.io/github/v/release/aelurum/AssetStudio?filter=ak*&label=Download&labelColor=blue)](https://github.com/aelurum/AssetStudio/tags)
**AssetStudioMod** - modified version of Perfare's [AssetStudio](https://github.com/Perfare/AssetStudio), mainly focused on UI optimization and some functionality enhancements.
[![Build status](https://ci.appveyor.com/api/projects/status/857ucvvp0cykv1ni?svg=true)](https://ci.appveyor.com/project/aelurum/arknightsstudio) [![Download latest build](https://img.shields.io/badge/Download_latest_build-brightgreen)](https://ci.appveyor.com/project/aelurum/arknightsstudio/build/artifacts)
**ArknightsStudio** is a modified version of AssetStudio designed for Arknights. Based on [AssetStudioMod](https://github.com/aelurum/AssetStudio).
**Neither the repository, nor the tool, nor the author of the tool, nor the author of the modification is affiliated with, sponsored, or authorized by Unity Technologies or its affiliates.**
Since the original repo has been archived, it's worth saying that you shouldn't expect support for newer versions of Unity from this fork.
Unfortunately, I can't continue Perfare's work and keep AssetStudio up to date.
## Game specific modifications
- ArknightsStudio - soon<6F>
## AssetStudio Features
- Support version:
- 3.4 - 2022.1
- Support asset types:
- **Texture2D** : convert to png, tga, jpeg, bmp, webp
- **Sprite** : crop Texture2D to png, tga, jpeg, bmp, webp
- **AudioClip** : mp3, ogg, wav, m4a, fsb. Support converting FSB file to WAV(PCM)
- **Font** : ttf, otf
- **Mesh** : obj
- **TextAsset**
- **Shader** (for Unity < 2021)
- **MovieTexture**
- **VideoClip**
- **MonoBehaviour** : json
- **Animator** : export to FBX file with bound AnimationClip
## AssetStudioMod Features
## ArknightsStudio Features
- CLI version (for Windows, Linux, Mac)
- `Animator` and `AnimationClip` assets are not supported in the CLI version
- Support of sprites with alpha mask
- Support of image export in WebP format
- Support of Live2D Cubism 3 model export
- Ported from my fork of Perfare's [UnityLive2DExtractor](https://github.com/aelurum/UnityLive2DExtractor)
- Using the Live2D export in AssetStudio allows you to specify a Unity version and assembly folder if needed
- Detecting bundles with UnityCN encryption
- Detection only. If you want to open them, please use Razmoth's [Studio](https://github.com/RazTools/Studio)
- Some UI optimizations and bug fixes (See [CHANGELOG](https://github.com/aelurum/AssetStudio/blob/AssetStudioMod/CHANGELOG.md) for details)
- Support of sprites with alpha texture
- Support of portrait sprites
- Correct support of avg character sprites
- Correct support of character art sprites
## Requirements
- AssetStudioMod.net472
- ArknightsStudio-net472
- GUI/CLI - [.NET Framework 4.7.2](https://dotnet.microsoft.com/download/dotnet-framework/net472)
- AssetStudioMod.net6
- ArknightsStudio-net6
- GUI/CLI (Windows) - [.NET Desktop Runtime 6.0](https://dotnet.microsoft.com/download/dotnet/6.0)
- CLI (Linux/Mac) - [.NET Runtime 6.0](https://dotnet.microsoft.com/download/dotnet/6.0)
- AssetStudioMod.net7
- ArknightsStudio-net7
- GUI/CLI (Windows) - [.NET Desktop Runtime 7.0](https://dotnet.microsoft.com/download/dotnet/7.0)
- CLI (Linux/Mac) - [.NET Runtime 7.0](https://dotnet.microsoft.com/download/dotnet/7.0)
- ArknightsStudio-net8
- GUI/CLI (Windows) - [.NET Desktop Runtime 8.0](https://dotnet.microsoft.com/download/dotnet/8.0)
- CLI (Linux/Mac) - [.NET Runtime 8.0](https://dotnet.microsoft.com/download/dotnet/8.0)
## CLI Usage
You can read CLI readme [here](https://github.com/aelurum/AssetStudio/blob/AssetStudioMod/AssetStudioCLI/ReadMe.md).
You can read CLI readme [here](https://github.com/aelurum/AssetStudio/blob/ArknightsStudio/AssetStudioCLI/ReadMe.md).
### Run
- Command-line: `AssetStudioModCLI <asset folder path>`
- Command-line for Portable versions (.NET 6+): `dotnet AssetStudioModCLI.dll <asset folder path>`
- Command-line: `ArknightsStudioCLI <asset folder path>`
- Command-line for Portable versions (.NET 6+): `dotnet ArknightsStudioCLI.dll <asset folder path>`
### Basic Samples
- Show a list with a number of assets of each type available for export
```
AssetStudioModCLI <asset folder path> -m info
ArknightsStudioCLI <asset folder path> -m info
```
- Export assets of all supported types
- Export assets of all supported for export types
```
AssetStudioModCLI <asset folder path>
ArknightsStudioCLI <asset folder path>
```
- Export assets of specific types
```
AssetStudioModCLI <asset folder path> -t tex2d
ArknightsStudioCLI <asset folder path> -t sprite
```
```
AssetStudioModCLI <asset folder path> -t tex2d,sprite,audio
ArknightsStudioCLI <asset folder path> -t tex2d,sprite,audio
```
- Export portrait sprites
```
ArknightsStudioCLI <asset folder path> -t akPortrait
```
- Export assets grouped by type
```
AssetStudioModCLI <asset folder path> -g type
ArknightsStudioCLI <asset folder path> -g type
```
- Export assets to a specified output folder
```
AssetStudioModCLI <asset folder path> -o <output folder path>
ArknightsStudioCLI <asset folder path> -o <output folder path>
```
- Dump assets to a specified output folder
```
AssetStudioModCLI <asset folder path> -m dump -o <output folder path>
ArknightsStudioCLI <asset folder path> -m dump -o <output folder path>
```
- Export Live2D Cubism models
- Export assets and create a log file
```
AssetStudioModCLI <asset folder path> -m live2d
ArknightsStudioCLI <asset folder path> --log-output both
```
> When running in live2d mode you can only specify `-o`, `--log-level`, `--log-output`, `--export-asset-list`, `--unity-version` and `--assembly-folder` options.
Any other options will be ignored.
- Export all FBX objects (similar to "Export all objects (split)" option in the GUI)
```
AssetStudioModCLI <asset folder path> -m splitObjects
ArknightsStudioCLI <asset folder path> -m splitObjects
```
> When running in splitObjects mode you can only specify `-o`, `--log-level`, `--log-output`, `--export-asset-list`, `--image-format`, `--filter-by-name` and `--unity-version` options.
Any other options will be ignored.
@@ -109,50 +88,50 @@ Any other options will be ignored.
### Advanced Samples
- Export image assets converted to webp format to a specified output folder
```
AssetStudioModCLI <asset folder path> -o <output folder path> -t sprite,tex2d --image-format webp
ArknightsStudioCLI <asset folder path> -o <output folder path> -t sprite,akPortrait,tex2d --image-format webp
```
- Show the number of audio assets that have "voice" in their names
- Export avg character sprites with aliases in their names
```
AssetStudioModCLI <asset folder path> -m info -t audio --filter-by-name voice
ArknightsStudioCLI <asset folder path> -t sprite --add-aliases
```
- Export audio assets that have "voice" in their names
- Export character art sprites without brightness change of semi-transparent shadow for 2048x2048 images
```
AssetStudioModCLI <asset folder path> -t audio --filter-by-name voice
ArknightsStudioCLI <asset folder path> -t sprite --shadow-gamma 0
```
- Export audio assets that have "music" or "voice" in their names
- Show the number of audio assets that have "voice" in their containers
```
AssetStudioModCLI <asset folder path> -t audio --filter-by-name music,voice
ArknightsStudioCLI <asset folder path> -m info -t audio --filter-by-container voice
```
- Export audio assets that have "voice" in their containers
```
ArknightsStudioCLI <asset folder path> -t audio --filter-by-container voice
```
- Export audio assets that have "music" or "voice" in their containers
```
ArknightsStudioCLI <asset folder path> -t audio --filter-by-container music,voice
```
```
AssetStudioModCLI <asset folder path> -t audio --filter-by-name music --filter-by-name voice
ArknightsStudioCLI <asset folder path> -t audio --filter-by-container music --filter-by-container voice
```
- Export audio assets that have "char" in their names **or** containers
```
AssetStudioModCLI <asset folder path> -t audio --filter-by-text char
ArknightsStudioCLI <asset folder path> -t audio --filter-by-text char
```
- Export audio assets that have "voice" in their names **and** "char" in their containers
- Export audio assets that have "loop" in their names **and** "music" in their containers
```
AssetStudioModCLI <asset folder path> -t audio --filter-by-name voice --filter-by-container char
ArknightsStudioCLI <asset folder path> -t audio --filter-by-name loop --filter-by-container music
```
- Export FBX objects that have "model" or "scene" in their names and set the scale factor to 10
```
AssetStudioModCLI <asset folder path> -m splitObjects --filter-by-name model,scene --fbx-scale-factor 10
```
- Export MonoBehaviour assets that require an assembly folder to read and create a log file
```
AssetStudioModCLI <asset folder path> -t monobehaviour --assembly-folder <assembly folder path> --log-output both
```
- Export assets that require to specify a Unity version
```
AssetStudioModCLI <asset folder path> --unity-version 2017.4.39f1
ArknightsStudioCLI <asset folder path> -m splitObjects --filter-by-name model,scene --fbx-scale-factor 10
```
- Load assets of all types and show them (similar to "Display all assets" option in the GUI)
```
AssetStudioModCLI <asset folder path> -m info --load-all
ArknightsStudioCLI <asset folder path> -m info --load-all
```
- Load assets of all types and dump Material assets
```
AssetStudioModCLI <asset folder path> -m dump -t material --load-all
ArknightsStudioCLI <asset folder path> -m dump -t material --load-all
```
## GUI Usage
@@ -161,13 +140,13 @@ AssetStudioModCLI <asset folder path> -m dump -t material --load-all
Use **File->Load file** or **File->Load folder**.
When AssetStudio loads AssetBundles, it decompresses and reads it directly in memory, which may cause a large amount of memory to be used. You can use **File->Extract file** or **File->Extract folder** to extract AssetBundles to another folder, and then read.
When ArknightsStudio loads AssetBundles, it decompresses and reads it directly in memory, which may cause a large amount of memory to be used. You can use **File->Extract file** or **File->Extract folder** to extract AssetBundles to another folder, and then read.
### Extract/Decompress AssetBundles
Use **File->Extract file** or **File->Extract folder**.
### Export Assets, Live2D models
### Export Assets
Use **Export** menu.
@@ -183,14 +162,6 @@ Select model from "Scene Hierarchy" then select the AnimationClip from "Asset Li
Export Animator will export bound AnimationClip or use **Ctrl** to select Animator and AnimationClip from "Asset List", using **Export->Export Animator with selected AnimationClip** to export.
### Export MonoBehaviour
When you select an asset of the MonoBehaviour type for the first time, AssetStudio will ask you the directory where the assembly is located, please select the directory where the assembly is located, such as the `Managed` folder.
#### For Il2Cpp
First, use [Il2CppDumper](https://github.com/Perfare/Il2CppDumper) to generate dummy dll, then when using AssetStudio to select the assembly directory, select the dummy dll folder.
## Build
* Visual Studio 2022 or newer

View File

@@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Version>0.17.3.0</Version>
<Version>1.2.0</Version>
<Copyright>Copyright © Perfare 2020-2022; Copyright © hozuki 2020</Copyright>
<DebugType>embedded</DebugType>
</PropertyGroup>