72 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
6d3875cb2c Update version to v0.17.3 2023-09-13 10:55:57 +03:00
VaDiM
be4ced77ea Update readmes 2023-09-12 03:44:18 +03:00
VaDiM
2bd762e8f4 [CLI] Add names for AssetBundle and Animator assets 2023-09-12 03:33:59 +03:00
VaDiM
a926644ff6 [CLI] Add --load-all flag
- Added --load-all flag to load assets of all types
- Improved option grouping on the help screen
2023-09-12 03:33:53 +03:00
VaDiM
a4cdff5934 Fix a bit of incorrect copy-paste
in commit e216abd6be
2023-09-08 23:47:26 +03:00
VaDiM
2e10e627b0 AssemblyLoader fix (#6) 2023-09-08 06:18:41 +03:00
VaDiM
e216abd6be Unity 2022.2+ Texture2D fix 2023-09-08 06:18:34 +03:00
VaDiM
93c7e617d8 [CLI] Show names of all loaded assets in the exported asset list 2023-09-08 06:18:34 +03:00
VaDiM
000916913e [CLI] Fix a bug where the default output folder might not exist 2023-09-08 06:17:34 +03:00
VaDiM
28f9744497 Update README.md 2023-09-08 06:17:34 +03:00
VaDiM
c3f99216b6 [CLI] Add --filter-by-name support for fbx export mode 2023-09-08 01:45:25 +03:00
VaDiM
19c4835ea3 Merge branch 'pr/10' into AssetStudioMod 2023-09-05 19:38:04 +03:00
VaDiM
6b321da695 [CLI] Add FBX native libs (Linux, Mac) 2023-09-05 00:48:44 +03:00
svn
7cca301f7a Add split object fbx export to CLI 2023-08-31 21:07:02 +01:00
svn
5ac597c935 Add CMakeLists for cross platform FBXNative build 2023-08-31 21:03:07 +01:00
VaDiM
e90af43459 Merge branch 'AssetStudioMod' into ArknightsStudio 2023-08-28 02:28:14 +03:00
VaDiM
171962e61f Update version to v0.17.2
- Updated projects and dependencies
2023-08-27 01:47:06 +03:00
VaDiM
c8a21838c9 Some changes to motion list for l2d models
- Motion list is now sorted
- Motions divided into groups (each motion is a separate group)
- Motion names are used as group names
2023-08-27 01:46:06 +03:00
VaDiM
cf67815d53 Minor fixes 2023-08-26 03:22:19 +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
VaDiM
94c8b355fe [CLI] Fix asset filter
- Fixed sprite export in sprite only mode
2023-08-14 00:46:27 +03:00
VaDiM
4e41caf203 [CLI] Refactor 2023-08-13 20:58:26 +03:00
VaDiM
74a8555514 Merge branch 'relatedAssets' into AssetStudioMod 2023-08-07 23:20:11 +03:00
VaDiM
e1d883adf6 [CLI] Refactor
- Made some classes static
2023-08-07 23:10:57 +03:00
VaDiM
9784df0e16 [GUI] Add app.manifest for net472 build
- Added long paths support (win10 v1607+)
- Fixed blurring at high DPI with scaling
2023-08-06 20:08:05 +03:00
VaDiM
4d919a2bfe [GUI] Improve Scene Hierarchy tab context menu
- Added "Related assets" item to the context menu
2023-08-05 18:10:36 +03:00
VaDiM
6701f467b7 [CLI] Show the number of successfully loaded assets 2023-07-14 20:20:58 +03:00
VaDiM
50f5da5554 [GUI] Don't count Shaders in unity 2021+ assets 2023-07-14 19:17:56 +03:00
72 changed files with 4857 additions and 1087 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.1.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.1.0</Version>
<Copyright>Copyright © Perfare 2018-2022</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.5" />
<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

@@ -12,7 +12,7 @@ namespace AssetStudio
{
public string SpecifyUnityVersion;
public List<SerializedFile> assetsFileList = new List<SerializedFile>();
private List<ClassIDType> filteredAssetTypesList = new List<ClassIDType>();
private HashSet<ClassIDType> filteredAssetTypesList = new HashSet<ClassIDType>();
internal Dictionary<string, int> assetsFileIndexCache = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
internal Dictionary<string, BinaryReader> resourceFileReaders = new Dictionary<string, BinaryReader>(StringComparer.OrdinalIgnoreCase);
@@ -22,35 +22,38 @@ namespace AssetStudio
private HashSet<string> noexistFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
private HashSet<string> assetsFileListHash = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
public void SetAssetFilter(ClassIDType classIDType)
public void SetAssetFilter(params ClassIDType[] classIDTypes)
{
if (filteredAssetTypesList.Count == 0)
{
filteredAssetTypesList.AddRange(new List<ClassIDType>
filteredAssetTypesList.UnionWith(new HashSet<ClassIDType>
{
ClassIDType.AssetBundle,
ClassIDType.ResourceManager,
});
}
if (classIDType == ClassIDType.MonoBehaviour)
if (classIDTypes.Contains(ClassIDType.MonoBehaviour))
{
filteredAssetTypesList.AddRange(new List<ClassIDType>
filteredAssetTypesList.Add(ClassIDType.MonoScript);
}
if (classIDTypes.Contains(ClassIDType.Sprite) || classIDTypes.Contains(ClassIDType.AkPortraitSprite))
{
filteredAssetTypesList.UnionWith(new HashSet<ClassIDType>
{
ClassIDType.MonoScript,
ClassIDType.MonoBehaviour
ClassIDType.Texture2D,
ClassIDType.SpriteAtlas,
ClassIDType.MonoBehaviour,
ClassIDType.MonoScript
});
}
else
{
filteredAssetTypesList.Add(classIDType);
}
filteredAssetTypesList.UnionWith(classIDTypes);
}
public void SetAssetFilter(List<ClassIDType> classIDTypeList)
{
foreach (ClassIDType classIDType in classIDTypeList)
SetAssetFilter(classIDType);
SetAssetFilter(classIDTypeList.ToArray());
}
public void LoadFilesAndFolders(params string[] path)
@@ -85,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);
@@ -136,7 +139,7 @@ namespace AssetStudio
private void LoadFile(FileReader reader)
{
switch (reader.FileType)
switch (reader?.FileType)
{
case FileType.AssetsFile:
LoadAssetsFile(reader);
@@ -535,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;
@@ -638,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

@@ -91,7 +91,14 @@ namespace AssetStudio
}
if (version[0] > 2019 || (version[0] == 2019 && version[1] >= 3)) //2019.3 and up
{
var m_IgnoreMasterTextureLimit = reader.ReadBoolean();
if (version[0] > 2022 || (version[0] == 2022 && version[1] >= 2)) //2022.2 and up
{
var m_IgnoreMipmapLimit = reader.ReadBoolean();
}
else
{
var m_IgnoreMasterTextureLimit = reader.ReadBoolean();
}
}
if (version[0] >= 3) //3.0.0 - 5.4
{
@@ -100,6 +107,11 @@ namespace AssetStudio
var m_ReadAllowed = reader.ReadBoolean();
}
}
if (version[0] > 2022 || (version[0] == 2022 && version[1] >= 2)) //2022.2 and up
{
var m_MipmapLimitGroupName = reader.ReadAlignedString();
reader.AlignStream();
}
if (version[0] > 2018 || (version[0] == 2018 && version[1] >= 2)) //2018.2 and up
{
var m_StreamingMipmaps = reader.ReadBoolean();

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.1.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>
@@ -24,6 +24,8 @@
<Target Name="CopyExtraFilesPortable" AfterTargets="AfterBuild" Condition=" '$(RuntimeIdentifier)' == '' ">
<Message Text="Copying windows extra files for $(TargetFramework)... " Importance="high" />
<Copy SourceFiles="$(SolutionDir)AssetStudioFBXNative\bin\Win32\$(Configuration)\AssetStudioFBXNative.dll" DestinationFolder="$(TargetDir)runtimes\win-x86\native" ContinueOnError="false" />
<Copy SourceFiles="$(SolutionDir)AssetStudioFBXNative\bin\x64\$(Configuration)\AssetStudioFBXNative.dll" DestinationFolder="$(TargetDir)runtimes\win-x64\native" ContinueOnError="false" />
<Copy SourceFiles="$(SolutionDir)Texture2DDecoderNative\bin\Win32\$(Configuration)\Texture2DDecoderNative.dll" DestinationFolder="$(TargetDir)runtimes\win-x86\native" ContinueOnError="false" />
<Copy SourceFiles="$(SolutionDir)Texture2DDecoderNative\bin\x64\$(Configuration)\Texture2DDecoderNative.dll" DestinationFolder="$(TargetDir)runtimes\win-x64\native" ContinueOnError="false" />
<Copy SourceFiles="$(ProjectDir)Libraries\win-x86\fmod.dll" DestinationFolder="$(TargetDir)runtimes\win-x86\native" ContinueOnError="false" />
@@ -32,6 +34,9 @@
<Target Name="CopyExtraFilesPortableNet" AfterTargets="AfterBuild" Condition=" '$(RuntimeIdentifier)' == '' AND '$(TargetFramework)' != 'net472' ">
<Message Text="Copying other platforms extra files for $(TargetFramework)... " Importance="high" />
<Copy SourceFiles="$(ProjectDir)Libraries\linux-x64\libAssetStudioFBXNative.so" DestinationFolder="$(TargetDir)runtimes\linux-x64\native" ContinueOnError="false" />
<Copy SourceFiles="$(ProjectDir)Libraries\osx-x64\libAssetStudioFBXNative.dylib" DestinationFolder="$(TargetDir)runtimes\osx-x64\native" ContinueOnError="false" />
<Copy SourceFiles="$(ProjectDir)Libraries\osx-arm64\libAssetStudioFBXNative.dylib" DestinationFolder="$(TargetDir)runtimes\osx-arm64\native" ContinueOnError="false" />
<Copy SourceFiles="$(ProjectDir)Libraries\linux-x86\libfmod.so" DestinationFolder="$(TargetDir)runtimes\linux-x86\native" ContinueOnError="false" />
<Copy SourceFiles="$(ProjectDir)Libraries\linux-x64\libfmod.so" DestinationFolder="$(TargetDir)runtimes\linux-x64\native" ContinueOnError="false" />
<Copy SourceFiles="$(ProjectDir)Libraries\osx-x64\libfmod.dylib" DestinationFolder="$(TargetDir)runtimes\osx-x64\native" ContinueOnError="false" />
@@ -42,6 +47,8 @@
The dll is cross-platform while the executable isn't -->
<Target Name="PublishExtraFilesPortable" AfterTargets="Publish" Condition=" '$(RuntimeIdentifier)' == '' ">
<Message Text="Publishing windows extra files for Portable build ($(TargetFramework))... " Importance="high" />
<Copy SourceFiles="$(TargetDir)runtimes\win-x86\native\AssetStudioFBXNative.dll" DestinationFolder="$(PublishDir)runtimes\win-x86\native" ContinueOnError="false" />
<Copy SourceFiles="$(TargetDir)runtimes\win-x64\native\AssetStudioFBXNative.dll" DestinationFolder="$(PublishDir)runtimes\win-x64\native" ContinueOnError="false" />
<Copy SourceFiles="$(TargetDir)runtimes\win-x86\native\Texture2DDecoderNative.dll" DestinationFolder="$(PublishDir)runtimes\win-x86\native" ContinueOnError="false" />
<Copy SourceFiles="$(TargetDir)runtimes\win-x64\native\Texture2DDecoderNative.dll" DestinationFolder="$(PublishDir)runtimes\win-x64\native" ContinueOnError="false" />
<Copy SourceFiles="$(TargetDir)runtimes\win-x86\native\fmod.dll" DestinationFolder="$(PublishDir)runtimes\win-x86\native" ContinueOnError="false" />
@@ -50,6 +57,9 @@
<Target Name="PublishExtraFilesPortableNet" AfterTargets="Publish" Condition=" '$(RuntimeIdentifier)' == '' AND '$(TargetFramework)' != 'net472' ">
<Message Text="Publishing other platforms extra files for Portable build ($(TargetFramework))... " Importance="high" />
<Copy SourceFiles="$(TargetDir)runtimes\linux-x64\native\libAssetStudioFBXNative.so" DestinationFolder="$(PublishDir)runtimes\linux-x64\native" ContinueOnError="false" />
<Copy SourceFiles="$(TargetDir)runtimes\osx-x64\native\libAssetStudioFBXNative.dylib" DestinationFolder="$(PublishDir)runtimes\osx-x64\native" ContinueOnError="false" />
<Copy SourceFiles="$(TargetDir)runtimes\osx-arm64\native\libAssetStudioFBXNative.dylib" DestinationFolder="$(PublishDir)runtimes\osx-arm64\native" ContinueOnError="false" />
<Copy SourceFiles="$(TargetDir)runtimes\linux-x86\native\libfmod.so" DestinationFolder="$(PublishDir)runtimes\linux-x86\native" ContinueOnError="false" />
<Copy SourceFiles="$(TargetDir)runtimes\linux-x64\native\libfmod.so" DestinationFolder="$(PublishDir)runtimes\linux-x64\native" ContinueOnError="false" />
<Copy SourceFiles="$(TargetDir)runtimes\osx-x64\native\libfmod.dylib" DestinationFolder="$(PublishDir)runtimes\osx-x64\native" ContinueOnError="false" />
@@ -58,39 +68,46 @@
<Target Name="CopyExtraFilesWin86" AfterTargets="AfterBuild" Condition=" '$(RuntimeIdentifier)' == 'win-x86' ">
<Message Text="Copying extra files for $(RuntimeIdentifier)($(TargetFramework))... " Importance="high" />
<Copy SourceFiles="$(SolutionDir)AssetStudioFBXNative\bin\Win32\$(Configuration)\AssetStudioFBXNative.dll" DestinationFolder="$(TargetDir)" ContinueOnError="false" />
<Copy SourceFiles="$(SolutionDir)Texture2DDecoderNative\bin\Win32\$(Configuration)\Texture2DDecoderNative.dll" DestinationFolder="$(TargetDir)" ContinueOnError="false" />
<Copy SourceFiles="$(ProjectDir)Libraries\win-x86\fmod.dll" DestinationFolder="$(TargetDir)" ContinueOnError="false" />
</Target>
<Target Name="CopyExtraFilesWin64" AfterTargets="AfterBuild" Condition=" '$(RuntimeIdentifier)' == 'win-x64' ">
<Message Text="Copying extra files for $(RuntimeIdentifier)($(TargetFramework))... " Importance="high" />
<Copy SourceFiles="$(SolutionDir)AssetStudioFBXNative\bin\x64\$(Configuration)\AssetStudioFBXNative.dll" DestinationFolder="$(TargetDir)" ContinueOnError="false" />
<Copy SourceFiles="$(SolutionDir)Texture2DDecoderNative\bin\x64\$(Configuration)\Texture2DDecoderNative.dll" DestinationFolder="$(TargetDir)" ContinueOnError="false" />
<Copy SourceFiles="$(ProjectDir)Libraries\win-x64\fmod.dll" DestinationFolder="$(TargetDir)" ContinueOnError="false" />
</Target>
<Target Name="PublishExtraFilesWin" AfterTargets="Publish" Condition=" $(RuntimeIdentifier.Contains('win-x')) ">
<Message Text="Publishing extra files for $(RuntimeIdentifier)($(TargetFramework))... " Importance="high" />
<Copy SourceFiles="$(TargetDir)\AssetStudioFBXNative.dll" DestinationFolder="$(PublishDir)" ContinueOnError="false" />
<Copy SourceFiles="$(TargetDir)\Texture2DDecoderNative.dll" DestinationFolder="$(PublishDir)" ContinueOnError="false" />
<Copy SourceFiles="$(TargetDir)\fmod.dll" DestinationFolder="$(PublishDir)" ContinueOnError="false" />
</Target>
<Target Name="CopyExtraFilesLinux64" AfterTargets="AfterBuild" Condition=" '$(RuntimeIdentifier)' == 'linux-x64' ">
<Message Text="Copying extra files for $(RuntimeIdentifier)($(TargetFramework))... " Importance="high" />
<Copy SourceFiles="$(ProjectDir)Libraries\linux-x64\libAssetStudioFBXNative.so" DestinationFolder="$(TargetDir)" ContinueOnError="false" />
<Copy SourceFiles="$(ProjectDir)Libraries\linux-x64\libfmod.so" DestinationFolder="$(TargetDir)" ContinueOnError="false" />
</Target>
<Target Name="PublishExtraFilesLinux64" AfterTargets="Publish" Condition=" '$(RuntimeIdentifier)' == 'linux-x64' ">
<Message Text="Publishing extra files for $(RuntimeIdentifier)($(TargetFramework))... " Importance="high" />
<Copy SourceFiles="$(TargetDir)\libAssetStudioFBXNative.so" DestinationFolder="$(PublishDir)" ContinueOnError="false" />
<Copy SourceFiles="$(TargetDir)\libfmod.so" DestinationFolder="$(PublishDir)" ContinueOnError="false" />
</Target>
<Target Name="CopyExtraFilesMac" AfterTargets="AfterBuild" Condition=" $(RuntimeIdentifier.Contains('osx-')) ">
<Message Text="Copying extra files for $(RuntimeIdentifier)($(TargetFramework))... " Importance="high" />
<Copy SourceFiles="$(ProjectDir)Libraries\$(RuntimeIdentifier)\libAssetStudioFBXNative.dylib" DestinationFolder="$(TargetDir)" ContinueOnError="false" />
<Copy SourceFiles="$(ProjectDir)Libraries\$(RuntimeIdentifier)\libfmod.dylib" DestinationFolder="$(TargetDir)" ContinueOnError="false" />
</Target>
<Target Name="PublishExtraFilesMac" AfterTargets="Publish" Condition=" $(RuntimeIdentifier.Contains('osx-')) ">
<Message Text="Publishing extra files for $(RuntimeIdentifier)($(TargetFramework))... " Importance="high" />
<Copy SourceFiles="$(TargetDir)\libAssetStudioFBXNative.dylib" DestinationFolder="$(PublishDir)" ContinueOnError="false" />
<Copy SourceFiles="$(TargetDir)\libfmod.dylib" DestinationFolder="$(PublishDir)" ContinueOnError="false" />
</Target>

View File

@@ -1,9 +1,9 @@
using AssetStudio;
using AssetStudioCLI.Options;
using System;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using AssetStudioCLI.Options;
namespace AssetStudioCLI
{
@@ -21,29 +21,31 @@ namespace AssetStudioCLI
public string LogName;
public string LogPath;
public CLILogger(CLIOptions options)
public CLILogger()
{
logOutput = options.o_logOutput.Value;
logMinLevel = options.o_logLevel.Value;
LogName = $"AssetStudioCLI_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.log";
logOutput = CLIOptions.o_logOutput.Value;
logMinLevel = CLIOptions.o_logLevel.Value;
var appAssembly = typeof(Program).Assembly.GetName();
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;
var ver = typeof(Program).Assembly.GetName().Version;
LogToFile(LoggerEvent.Verbose, $"---AssetStudioCLI v{ver} | Logger launched---\n" +
$"CMD Args: {string.Join(" ", options.cliArgs)}");
LogToFile(LoggerEvent.Verbose, $"---{appAssembly.Name} v{appAssembly.Version} [{arch}] | Logger launched---\n" +
$"CMD Args: {string.Join(" ", CLIOptions.cliArgs)}");
}
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;
}
@@ -58,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
{
@@ -13,6 +14,8 @@ namespace AssetStudioCLI
public ClassIDType Type;
public string Text;
public string UniqueID;
public GameObjectNode Node;
public PortraitSprite AkPortraitSprite;
public AssetItem(Object asset)
{
@@ -23,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

@@ -0,0 +1,16 @@
using AssetStudio;
using System.Collections.Generic;
namespace AssetStudioCLI
{
internal class BaseNode
{
public List<BaseNode> nodes = new List<BaseNode>();
public BaseNode()
{
}
}
}

View File

@@ -0,0 +1,16 @@
using AssetStudio;
using System.Collections.Generic;
namespace AssetStudioCLI
{
internal class GameObjectNode : BaseNode
{
public GameObject gameObject;
public GameObjectNode(GameObject gameObject)
{
this.gameObject = gameObject;
}
}
}

View File

@@ -1,22 +1,51 @@
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
{
internal static class Exporter
{
public static bool ExportTexture2D(AssetItem item, string exportPath, CLIOptions options)
public static bool ExportTexture2D(AssetItem item, string exportPath)
{
var m_Texture2D = (Texture2D)item.Asset;
if (options.convertTexture)
if (CLIOptions.convertTexture)
{
var type = options.o_imageFormat.Value;
var type = CLIOptions.o_imageFormat.Value;
if (!TryExportFile(exportPath, item, "." + type.ToString().ToLower(), out var exportFullPath))
return false;
if (CLIOptions.o_logLevel.Value <= LoggerEvent.Debug)
{
var sb = new StringBuilder();
sb.AppendLine($"Converting \"{m_Texture2D.m_Name}\" to {type}..");
sb.AppendLine($"Width: {m_Texture2D.m_Width}");
sb.AppendLine($"Height: {m_Texture2D.m_Height}");
sb.AppendLine($"Format: {m_Texture2D.m_TextureFormat}");
switch (m_Texture2D.m_TextureSettings.m_FilterMode)
{
case 0: sb.AppendLine("Filter Mode: Point "); break;
case 1: sb.AppendLine("Filter Mode: Bilinear "); break;
case 2: sb.AppendLine("Filter Mode: Trilinear "); break;
}
sb.AppendLine($"Anisotropic level: {m_Texture2D.m_TextureSettings.m_Aniso}");
sb.AppendLine($"Mip map bias: {m_Texture2D.m_TextureSettings.m_MipBias}");
switch (m_Texture2D.m_TextureSettings.m_WrapMode)
{
case 0: sb.AppendLine($"Wrap mode: Repeat"); break;
case 1: sb.AppendLine($"Wrap mode: Clamp"); break;
}
Logger.Debug(sb.ToString());
}
var image = m_Texture2D.ConvertToImage(flip: true);
if (image == null)
{
@@ -29,7 +58,7 @@ namespace AssetStudioCLI
{
image.WriteToStream(file, type);
}
Logger.Debug($"{item.TypeString}: \"{item.Text}\" exported to \"{exportFullPath}\"");
Logger.Debug($"{item.TypeString} \"{item.Text}\" exported to \"{exportFullPath}\"");
return true;
}
}
@@ -38,12 +67,12 @@ namespace AssetStudioCLI
if (!TryExportFile(exportPath, item, ".tex", out var exportFullPath))
return false;
File.WriteAllBytes(exportFullPath, m_Texture2D.image_data.GetData());
Logger.Debug($"{item.TypeString}: \"{item.Text}\" exported to \"{exportFullPath}\"");
Logger.Debug($"{item.TypeString} \"{item.Text}\" exported to \"{exportFullPath}\"");
return true;
}
}
public static bool ExportAudioClip(AssetItem item, string exportPath, CLIOptions options)
public static bool ExportAudioClip(AssetItem item, string exportPath)
{
string exportFullPath;
var m_AudioClip = (AudioClip)item.Asset;
@@ -54,18 +83,21 @@ namespace AssetStudioCLI
return false;
}
var converter = new AudioClipConverter(m_AudioClip);
if (options.o_audioFormat.Value != AudioFormat.None && converter.IsSupport)
if (CLIOptions.o_audioFormat.Value != AudioFormat.None && converter.IsSupport)
{
if (!TryExportFile(exportPath, item, ".wav", out exportFullPath))
return false;
var sb = new StringBuilder();
sb.AppendLine($"Converting \"{m_AudioClip.m_Name}\" to wav..");
sb.AppendLine(m_AudioClip.version[0] < 5 ? $"AudioClip type: {m_AudioClip.m_Type}" : $"AudioClip compression format: {m_AudioClip.m_CompressionFormat}");
sb.AppendLine($"AudioClip channel count: {m_AudioClip.m_Channels}");
sb.AppendLine($"AudioClip sample rate: {m_AudioClip.m_Frequency}");
sb.AppendLine($"AudioClip bit depth: {m_AudioClip.m_BitsPerSample}");
Logger.Debug(sb.ToString());
if (CLIOptions.o_logLevel.Value <= LoggerEvent.Debug)
{
var sb = new StringBuilder();
sb.AppendLine($"Converting \"{m_AudioClip.m_Name}\" to wav..");
sb.AppendLine(m_AudioClip.version[0] < 5 ? $"AudioClip type: {m_AudioClip.m_Type}" : $"AudioClip compression format: {m_AudioClip.m_CompressionFormat}");
sb.AppendLine($"AudioClip channel count: {m_AudioClip.m_Channels}");
sb.AppendLine($"AudioClip sample rate: {m_AudioClip.m_Frequency}");
sb.AppendLine($"AudioClip bit depth: {m_AudioClip.m_BitsPerSample}");
Logger.Debug(sb.ToString());
}
var buffer = converter.ConvertToWav(m_AudioData);
if (buffer == null)
@@ -82,7 +114,7 @@ namespace AssetStudioCLI
File.WriteAllBytes(exportFullPath, m_AudioData);
}
Logger.Debug($"{item.TypeString}: \"{item.Text}\" exported to \"{exportFullPath}\"");
Logger.Debug($"{item.TypeString} \"{item.Text}\" exported to \"{exportFullPath}\"");
return true;
}
@@ -94,16 +126,19 @@ namespace AssetStudioCLI
if (!TryExportFile(exportPath, item, Path.GetExtension(m_VideoClip.m_OriginalPath), out var exportFullPath))
return false;
var sb = new StringBuilder();
sb.AppendLine($"VideoClip format: {m_VideoClip.m_Format}");
sb.AppendLine($"VideoClip width: {m_VideoClip.Width}");
sb.AppendLine($"VideoClip height: {m_VideoClip.Height}");
sb.AppendLine($"VideoClip frame rate: {m_VideoClip.m_FrameRate}");
sb.AppendLine($"VideoClip split alpha: {m_VideoClip.m_HasSplitAlpha}");
Logger.Debug(sb.ToString());
if (CLIOptions.o_logLevel.Value <= LoggerEvent.Debug)
{
var sb = new StringBuilder();
sb.AppendLine($"VideoClip format: {m_VideoClip.m_Format}");
sb.AppendLine($"VideoClip width: {m_VideoClip.Width}");
sb.AppendLine($"VideoClip height: {m_VideoClip.Height}");
sb.AppendLine($"VideoClip frame rate: {m_VideoClip.m_FrameRate:.0##}");
sb.AppendLine($"VideoClip split alpha: {m_VideoClip.m_HasSplitAlpha}");
Logger.Debug(sb.ToString());
}
m_VideoClip.m_VideoData.WriteData(exportFullPath);
Logger.Debug($"{item.TypeString}: \"{item.Text}\" exported to \"{exportFullPath}\"");
Logger.Debug($"{item.TypeString} \"{item.Text}\" exported to \"{exportFullPath}\"");
return true;
}
return false;
@@ -116,7 +151,7 @@ namespace AssetStudioCLI
return false;
File.WriteAllBytes(exportFullPath, m_MovieTexture.m_MovieData);
Logger.Debug($"{item.TypeString}: \"{item.Text}\" exported to \"{exportFullPath}\"");
Logger.Debug($"{item.TypeString} \"{item.Text}\" exported to \"{exportFullPath}\"");
return true;
}
@@ -128,16 +163,16 @@ namespace AssetStudioCLI
var str = m_Shader.Convert();
File.WriteAllText(exportFullPath, str);
Logger.Debug($"{item.TypeString}: \"{item.Text}\" exported to \"{exportFullPath}\"");
Logger.Debug($"{item.TypeString} \"{item.Text}\" exported to \"{exportFullPath}\"");
return true;
}
public static bool ExportTextAsset(AssetItem item, string exportPath, CLIOptions options)
public static bool ExportTextAsset(AssetItem item, string exportPath)
{
var m_TextAsset = (TextAsset)item.Asset;
var extension = ".txt";
var assetExtension = Path.GetExtension(m_TextAsset.m_Name);
if (!options.f_notRestoreExtensionName.Value)
if (!CLIOptions.f_notRestoreExtensionName.Value)
{
if (!string.IsNullOrEmpty(assetExtension))
{
@@ -156,11 +191,11 @@ namespace AssetStudioCLI
return false;
File.WriteAllBytes(exportFullPath, m_TextAsset.m_Script);
Logger.Debug($"{item.TypeString}: \"{item.Text}\" exported to \"{exportFullPath}\"");
Logger.Debug($"{item.TypeString} \"{item.Text}\" exported to \"{exportFullPath}\"");
return true;
}
public static bool ExportMonoBehaviour(AssetItem item, string exportPath, AssemblyLoader assemblyLoader)
public static bool ExportMonoBehaviour(AssetItem item, string exportPath)
{
if (!TryExportFile(exportPath, item, ".json", out var exportFullPath))
return false;
@@ -168,7 +203,7 @@ namespace AssetStudioCLI
var type = m_MonoBehaviour.ToType();
if (type == null)
{
var m_Type = m_MonoBehaviour.ConvertToTypeTree(assemblyLoader);
var m_Type = m_MonoBehaviour.ConvertToTypeTree(Studio.assemblyLoader);
type = m_MonoBehaviour.ToType(m_Type);
}
if (type != null)
@@ -176,7 +211,7 @@ namespace AssetStudioCLI
var str = JsonConvert.SerializeObject(type, Formatting.Indented);
File.WriteAllText(exportFullPath, str);
Logger.Debug($"{item.TypeString}: \"{item.Text}\" exported to \"{exportFullPath}\"");
Logger.Debug($"{item.TypeString} \"{item.Text}\" exported to \"{exportFullPath}\"");
return true;
}
return false;
@@ -196,19 +231,65 @@ namespace AssetStudioCLI
return false;
File.WriteAllBytes(exportFullPath, m_Font.m_FontData);
Logger.Debug($"{item.TypeString}: \"{item.Text}\" exported to \"{exportFullPath}\"");
Logger.Debug($"{item.TypeString} \"{item.Text}\" exported to \"{exportFullPath}\"");
return true;
}
return false;
}
public static bool ExportSprite(AssetItem item, string exportPath, CLIOptions options)
public static bool ExportSprite(AssetItem item, string exportPath)
{
var type = options.o_imageFormat.Value;
var alphaMask = SpriteMaskMode.On;
if (!TryExportFile(exportPath, item, "." + type.ToString().ToLower(), out var exportFullPath))
Image<Bgra32> image;
AvgSprite avgSprite = null;
var alias = "";
var m_Sprite = (Sprite)item.Asset;
var type = CLIOptions.o_imageFormat.Value;
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;
var image = ((Sprite)item.Asset).GetImage(alphaMask);
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)
@@ -224,38 +305,98 @@ namespace AssetStudioCLI
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 = item.AkPortraitSprite.AkGetImage(spriteMaskMode: 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 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());
Logger.Debug($"{item.TypeString}: \"{item.Text}\" exported to \"{exportFullPath}\"");
Logger.Debug($"{item.TypeString} \"{item.Text}\" exported to \"{exportFullPath}\"");
return true;
}
public static bool ExportDumpFile(AssetItem item, string exportPath, AssemblyLoader assemblyLoader)
public static void ExportGameObject(GameObject gameObject, string exportPath, List<AssetItem> animationList = null)
{
var convert = animationList != null
? new ModelConverter(gameObject, CLIOptions.o_imageFormat.Value, animationList.Select(x => (AnimationClip)x.Asset).ToArray())
: new ModelConverter(gameObject, CLIOptions.o_imageFormat.Value);
exportPath = exportPath + FixFileName(gameObject.m_Name) + ".fbx";
ExportFbx(convert, exportPath);
}
private static void ExportFbx(IImported convert, string exportPath)
{
var eulerFilter = true;
var filterPrecision = (float)0.25f;
var exportAllNodes = true;
var exportSkins = true;
var exportAnimations = true;
var exportBlendShape = true;
var castToBone = false;
var boneSize = CLIOptions.o_fbxBoneSize.Value;
var exportAllUvsAsDiffuseMaps = false;
var scaleFactor = CLIOptions.o_fbxScaleFactor.Value;
var fbxVersion = 3;
var fbxFormat = 0;
ModelExporter.ExportFbx(exportPath, convert, eulerFilter, filterPrecision,
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();
if (str == null && item.Asset is MonoBehaviour m_MonoBehaviour)
{
var m_Type = m_MonoBehaviour.ConvertToTypeTree(assemblyLoader);
var m_Type = m_MonoBehaviour.ConvertToTypeTree(Studio.assemblyLoader);
str = m_MonoBehaviour.Dump(m_Type);
}
if (str != null)
{
File.WriteAllText(exportFullPath, str);
Logger.Debug($"{item.TypeString}: \"{item.Text}\" saved to \"{exportFullPath}\"");
Logger.Debug($"{item.TypeString} \"{item.Text}\" saved to \"{exportFullPath}\"");
return true;
}
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))
{
@@ -268,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;
}
@@ -365,18 +506,18 @@ namespace AssetStudioCLI
sb.Replace("NaN", "0");
File.WriteAllText(exportFullPath, sb.ToString());
Logger.Debug($"{item.TypeString}: \"{item.Text}\" exported to \"{exportFullPath}\"");
Logger.Debug($"{item.TypeString} \"{item.Text}\" exported to \"{exportFullPath}\"");
return true;
}
public static bool ExportConvertFile(AssetItem item, string exportPath, CLIOptions options, AssemblyLoader assemblyLoader)
public static bool ExportConvertFile(AssetItem item, string exportPath)
{
switch (item.Type)
{
case ClassIDType.Texture2D:
return ExportTexture2D(item, exportPath, options);
return ExportTexture2D(item, exportPath);
case ClassIDType.AudioClip:
return ExportAudioClip(item, exportPath, options);
return ExportAudioClip(item, exportPath);
case ClassIDType.VideoClip:
return ExportVideoClip(item, exportPath);
case ClassIDType.MovieTexture:
@@ -384,13 +525,15 @@ namespace AssetStudioCLI
case ClassIDType.Shader:
return ExportShader(item, exportPath);
case ClassIDType.TextAsset:
return ExportTextAsset(item, exportPath, options);
return ExportTextAsset(item, exportPath);
case ClassIDType.MonoBehaviour:
return ExportMonoBehaviour(item, exportPath, assemblyLoader);
return ExportMonoBehaviour(item, exportPath);
case ClassIDType.Font:
return ExportFont(item, exportPath);
case ClassIDType.Sprite:
return ExportSprite(item, exportPath, options);
return ExportSprite(item, exportPath);
case ClassIDType.AkPortraitSprite:
return ExportPortraitSprite(item, exportPath);
case ClassIDType.Mesh:
return ExportMesh(item, exportPath);
default:

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,17 @@
using System;
namespace AssetStudioCLI.Options
{
internal static class OptionExtensions
{
public static Action<string, string, HelpGroups, bool> OptionGrouping = (name, desc, group, isFlag) => { };
}
internal class GroupedOption<T> : Option<T>
{
public GroupedOption(T optionDefaultValue, string optionName, string optionDescription, HelpGroups optionHelpGroup, bool isFlag = false) : base(optionDefaultValue, optionName, optionDescription, optionHelpGroup, isFlag)
{
OptionExtensions.OptionGrouping(optionName, optionDescription, optionHelpGroup, isFlag);
}
}
}

View File

@@ -8,52 +8,54 @@ namespace AssetStudioCLI
{
public static void Main(string[] args)
{
var options = new CLIOptions(args);
if (options.isParsed)
CLIOptions.ParseArgs(args);
if (CLIOptions.isParsed)
{
CLIRun(options);
CLIRun();
}
else if (options.showHelp)
else if (CLIOptions.showHelp)
{
options.ShowHelp();
CLIOptions.ShowHelp();
}
else
{
Console.WriteLine();
options.ShowHelp(showUsageOnly: true);
CLIOptions.ShowHelp(showUsageOnly: true);
}
}
private static void CLIRun(CLIOptions options)
private static void CLIRun()
{
var cliLogger = new CLILogger(options);
var cliLogger = new CLILogger();
Logger.Default = cliLogger;
var studio = new Studio(options);
options.ShowCurrentOptions();
CLIOptions.ShowCurrentOptions();
try
{
if (studio.LoadAssets())
if (Studio.LoadAssets())
{
studio.ParseAssets();
if (options.filterBy != FilterBy.None && options.o_workMode.Value != WorkMode.ExportLive2D)
Studio.ParseAssets();
if (CLIOptions.filterBy != FilterBy.None)
{
studio.FilterAssets();
Studio.Filter();
}
if (options.o_exportAssetList.Value != ExportListType.None)
if (CLIOptions.o_exportAssetList.Value != ExportListType.None)
{
studio.ExportAssetList();
Studio.ExportAssetList();
}
switch (options.o_workMode.Value)
switch (CLIOptions.o_workMode.Value)
{
case WorkMode.Info:
studio.ShowExportableAssetsInfo();
Studio.ShowExportableAssetsInfo();
break;
case WorkMode.ExportLive2D:
studio.ExportLive2D();
Studio.ExportLive2D();
break;
case WorkMode.SplitObjects:
Studio.ExportSplitObjects();
break;
default:
studio.ExportAssets();
Studio.ExportAssets();
break;
}
}

View File

@@ -1,34 +1,40 @@
## AssetStudioModCLI
CLI version of AssetStudioMod.
- Supported asset types: `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>]
[--export-asset-list <value>] [--filter-by-name <text>]
[--filter-by-container <text>] [--filter-by-pathid <text>]
[--filter-by-text <text>] [--assembly-folder <path>]
[--unity-version <text>] [--not-restore-extension]
[--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>]
[--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
<Value: export(default) | exportRaw | dump | info | live2d>
<Value: export(default) | exportRaw | dump | info | live2d | splitObjects>
Export - Exports converted assets
ExportRaw - Exports raw data
Dump - Makes asset dumps
Info - Loads file(s), shows the number of available for export assets and exits
Live2D - Exports Live2D Cubism 3 models
SplitObjects - Exports split objects (fbx)
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"
@@ -43,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
@@ -67,12 +73,27 @@ Convert Options:
None - Do not convert audios and export them in their own format
Example: "--audio-format wav"
Advanced Options:
--export-asset-list <value> Specify the format in which you want to export asset list
<Value: none(default) | xml>
None - Do not export asset list
Example: "--export-asset-list xml"
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)
Example: "--fbx-scale-factor 50"
--fbx-bone-size <value> Specify the FBX Bone Size
<Value: integer number from 0 to 100 (default=10)
Example: "--fbx-bone-size 10"
Filter Options:
--filter-by-name <text> Specify the name by which assets should be filtered
*To specify multiple names write them separated by ',' or ';' without spaces
Example: "--filter-by-name char" or "--filter-by-name char,bg"
@@ -90,8 +111,49 @@ Advanced Options:
*To specify multiple values write them separated by ',' or ';' without spaces
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>
None - Do not export asset list
Example: "--export-asset-list xml"
--assembly-folder <path> Specify the path to the assembly folder
--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
--unity-version <text> Specify Unity version
Example: "--unity-version 2017.4.39f1"
--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, Studio will load assets of all types
(Only for Dump, Info and ExportRaw modes)
```

View File

@@ -7,44 +7,38 @@ 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 class Studio
internal static class Studio
{
public AssetsManager assetsManager = new AssetsManager();
public List<AssetItem> parsedAssetsList = new List<AssetItem>();
public static AssetsManager assetsManager = new AssetsManager();
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>();
private static AssemblyLoader assemblyLoader = new AssemblyLoader();
private readonly CLIOptions options;
public Studio(CLIOptions cliOptions)
static Studio()
{
Progress.Default = new Progress<int>(ShowCurProgressValue);
options = cliOptions;
if (options.o_assemblyPath.Value != "")
{
assemblyLoader.Load(options.o_assemblyPath.Value);
}
else
{
assemblyLoader.Loaded = true;
}
}
private void ShowCurProgressValue(int value)
private static void ShowCurProgressValue(int value)
{
Console.Write($"[{value:000}%]\r");
}
public bool LoadAssets()
public static bool LoadAssets()
{
var isLoaded = false;
assetsManager.SpecifyUnityVersion = options.o_unityVersion.Value;
assetsManager.SetAssetFilter(options.o_exportAssetTypes.Value);
assetsManager.LoadFilesAndFolders(options.inputPath);
assetsManager.SpecifyUnityVersion = CLIOptions.o_unityVersion.Value;
if (!CLIOptions.f_loadAllAssets.Value)
{
assetsManager.SetAssetFilter(CLIOptions.o_exportAssetTypes.Value);
}
assetsManager.LoadFilesAndFolders(CLIOptions.inputPath);
if (assetsManager.assetsFileList.Count == 0)
{
Logger.Warning("No Unity file can be loaded.");
@@ -57,33 +51,45 @@ namespace AssetStudioCLI
return isLoaded;
}
public void ParseAssets()
public static void ParseAssets()
{
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);
Progress.Reset();
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);
objectAssetItemDic.Add(asset, assetItem);
assetItem.UniqueID = "_#" + i;
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;
@@ -115,13 +121,6 @@ namespace AssetStudioCLI
assetItem.FullSize = asset.byteSize + m_VideoClip.m_ExternalResources.m_Size;
assetItem.Text = m_VideoClip.m_Name;
break;
case Mesh _:
case MovieTexture _:
case TextAsset _:
case Font _:
case Sprite _:
assetItem.Text = ((NamedObject)asset).m_Name;
break;
case Shader m_Shader:
assetItem.Text = m_Shader.m_ParsedForm?.m_Name ?? m_Shader.m_Name;
break;
@@ -135,43 +134,167 @@ namespace AssetStudioCLI
assetItem.Text = m_MonoBehaviour.m_Name;
}
break;
case GameObject m_GameObject:
assetItem.Text = m_GameObject.m_Name;
break;
case Animator m_Animator:
if (m_Animator.m_GameObject.TryGet(out var gameObject))
{
assetItem.Text = gameObject.m_Name;
}
break;
case NamedObject m_NamedObject:
assetItem.Text = m_NamedObject.m_Name;
break;
}
if (assetItem.Text == "")
if (string.IsNullOrEmpty(assetItem.Text))
{
assetItem.Text = assetItem.TypeString + assetItem.UniqueID;
}
isExportable = options.o_exportAssetTypes.Value.Contains(asset.type);
if (isExportable)
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 (options.o_workMode.Value != WorkMode.ExportLive2D)
if (CLIOptions.o_workMode.Value != WorkMode.ExportLive2D)
{
containers.Clear();
}
}
if (CLIOptions.o_workMode.Value == WorkMode.SplitObjects)
{
BuildTreeStructure(objectAssetItemDic);
}
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)
{
m_ObjectsCount = assetsManager.assetsFileList.Sum(x => x.m_Objects.LongCount(y =>
y.classID != (int)ClassIDType.Shader
&& CLIOptions.o_exportAssetTypes.Value.Any(k => (int)k == y.classID))
);
}
else
{
m_ObjectsCount = assetsManager.assetsFileList.Sum(x => x.m_Objects.LongCount(y => CLIOptions.o_exportAssetTypes.Value.Any(k => (int)k == y.classID)));
}
var objectsCount = assetsManager.assetsFileList.Sum(x => x.Objects.LongCount(y => CLIOptions.o_exportAssetTypes.Value.Any(k => k == y.type)));
if (m_ObjectsCount != objectsCount)
{
log += $" and {m_ObjectsCount - objectsCount} assets failed to read";
}
Logger.Info(log);
}
public void ShowExportableAssetsInfo()
public static void BuildTreeStructure(Dictionary<AssetStudio.Object, AssetItem> objectAssetItemDic)
{
Logger.Info("Building tree structure...");
var treeNodeDictionary = new Dictionary<GameObject, GameObjectNode>();
var assetsFileCount = assetsManager.assetsFileList.Count;
int j = 0;
Progress.Reset();
foreach (var assetsFile in assetsManager.assetsFileList)
{
var fileNode = new BaseNode(); //RootNode
foreach (var obj in assetsFile.Objects)
{
if (obj is GameObject m_GameObject)
{
if (!treeNodeDictionary.TryGetValue(m_GameObject, out var currentNode))
{
currentNode = new GameObjectNode(m_GameObject);
treeNodeDictionary.Add(m_GameObject, currentNode);
}
foreach (var pptr in m_GameObject.m_Components)
{
if (pptr.TryGet(out var m_Component))
{
objectAssetItemDic[m_Component].Node = currentNode;
if (m_Component is MeshFilter m_MeshFilter)
{
if (m_MeshFilter.m_Mesh.TryGet(out var m_Mesh))
{
objectAssetItemDic[m_Mesh].Node = currentNode;
}
}
else if (m_Component is SkinnedMeshRenderer m_SkinnedMeshRenderer)
{
if (m_SkinnedMeshRenderer.m_Mesh.TryGet(out var m_Mesh))
{
objectAssetItemDic[m_Mesh].Node = currentNode;
}
}
}
}
var parentNode = fileNode;
if (m_GameObject.m_Transform != null)
{
if (m_GameObject.m_Transform.m_Father.TryGet(out var m_Father))
{
if (m_Father.m_GameObject.TryGet(out var parentGameObject))
{
if (!treeNodeDictionary.TryGetValue(parentGameObject, out var parentGameObjectNode))
{
parentGameObjectNode = new GameObjectNode(parentGameObject);
treeNodeDictionary.Add(parentGameObject, parentGameObjectNode);
}
parentNode = parentGameObjectNode;
}
}
}
parentNode.nodes.Add(currentNode);
}
}
if (fileNode.nodes.Count > 0)
{
gameObjectTree.Add(fileNode);
}
Progress.Report(++j, assetsFileCount);
}
treeNodeDictionary.Clear();
objectAssetItemDic.Clear();
}
public static void ShowExportableAssetsInfo()
{
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))
{
@@ -190,7 +313,7 @@ namespace AssetStudioCLI
}
if (exportableAssetsCountDict.Count > 1)
{
info += $"#\n# Total: {parsedAssetsList.Count} assets";
info += $"#\n# Total: {exportableAssetsList.Count} assets";
}
}
else
@@ -198,7 +321,7 @@ namespace AssetStudioCLI
info += "No exportable assets found.";
}
if (options.o_logLevel.Value > LoggerEvent.Info)
if (CLIOptions.o_logLevel.Value > LoggerEvent.Info)
{
Console.WriteLine(info);
}
@@ -208,68 +331,81 @@ namespace AssetStudioCLI
}
}
public void FilterAssets()
public static void Filter()
{
var assetsCount = parsedAssetsList.Count;
switch (CLIOptions.o_workMode.Value)
{
case WorkMode.ExportLive2D:
case WorkMode.SplitObjects:
break;
default:
FilterAssets();
break;
}
}
private static void FilterAssets()
{
var assetsCount = exportableAssetsList.Count;
var filteredAssets = new List<AssetItem>();
switch(options.filterBy)
switch(CLIOptions.filterBy)
{
case FilterBy.Name:
filteredAssets = parsedAssetsList.FindAll(x => options.o_filterByName.Value.Any(y => x.Text.ToString().IndexOf(y, StringComparison.OrdinalIgnoreCase) >= 0));
filteredAssets = exportableAssetsList.FindAll(x => CLIOptions.o_filterByName.Value.Any(y => x.Text.IndexOf(y, StringComparison.OrdinalIgnoreCase) >= 0));
Logger.Info(
$"Found [{filteredAssets.Count}/{assetsCount}] asset(s) " +
$"that contain {$"\"{string.Join("\", \"", options.o_filterByName.Value)}\"".Color(Ansi.BrightYellow)} in their Names."
$"that contain {$"\"{string.Join("\", \"", CLIOptions.o_filterByName.Value)}\"".Color(Ansi.BrightYellow)} in their Names."
);
break;
case FilterBy.Container:
filteredAssets = parsedAssetsList.FindAll(x => options.o_filterByContainer.Value.Any(y => x.Container.ToString().IndexOf(y, StringComparison.OrdinalIgnoreCase) >= 0));
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("\", \"", options.o_filterByContainer.Value)}\"".Color(Ansi.BrightYellow)} in their Containers."
$"that contain {$"\"{string.Join("\", \"", CLIOptions.o_filterByContainer.Value)}\"".Color(Ansi.BrightYellow)} in their Containers."
);
break;
case FilterBy.PathID:
filteredAssets = parsedAssetsList.FindAll(x => options.o_filterByPathID.Value.Any(y => x.m_PathID.ToString().IndexOf(y, StringComparison.OrdinalIgnoreCase) >= 0));
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("\", \"", options.o_filterByPathID.Value)}\"".Color(Ansi.BrightYellow)} in their PathIDs."
$"that contain {$"\"{string.Join("\", \"", CLIOptions.o_filterByPathID.Value)}\"".Color(Ansi.BrightYellow)} in their PathIDs."
);
break;
case FilterBy.NameOrContainer:
filteredAssets = parsedAssetsList.FindAll(x =>
options.o_filterByText.Value.Any(y => x.Text.ToString().IndexOf(y, StringComparison.OrdinalIgnoreCase) >= 0) ||
options.o_filterByText.Value.Any(y => x.Container.ToString().IndexOf(y, StringComparison.OrdinalIgnoreCase) >= 0)
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)
);
Logger.Info(
$"Found [{filteredAssets.Count}/{assetsCount}] asset(s) " +
$"that contain {$"\"{string.Join("\", \"", options.o_filterByText.Value)}\"".Color(Ansi.BrightYellow)} in their Names or Contaniers."
$"that contain {$"\"{string.Join("\", \"", CLIOptions.o_filterByText.Value)}\"".Color(Ansi.BrightYellow)} in their Names or Contaniers."
);
break;
case FilterBy.NameAndContainer:
filteredAssets = parsedAssetsList.FindAll(x =>
options.o_filterByName.Value.Any(y => x.Text.ToString().IndexOf(y, StringComparison.OrdinalIgnoreCase) >= 0) &&
options.o_filterByContainer.Value.Any(y => x.Container.ToString().IndexOf(y, StringComparison.OrdinalIgnoreCase) >= 0)
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)
);
Logger.Info(
$"Found [{filteredAssets.Count}/{assetsCount}] asset(s) " +
$"that contain {$"\"{string.Join("\", \"", options.o_filterByContainer.Value)}\"".Color(Ansi.BrightYellow)} in their Containers " +
$"and {$"\"{string.Join("\", \"", options.o_filterByName.Value)}\"".Color(Ansi.BrightYellow)} in their Names."
$"that contain {$"\"{string.Join("\", \"", CLIOptions.o_filterByContainer.Value)}\"".Color(Ansi.BrightYellow)} in their Containers " +
$"and {$"\"{string.Join("\", \"", CLIOptions.o_filterByName.Value)}\"".Color(Ansi.BrightYellow)} in their Names."
);
break;
}
parsedAssetsList.Clear();
parsedAssetsList = filteredAssets;
exportableAssetsList.Clear();
exportableAssetsList = filteredAssets;
}
public void ExportAssets()
public static void ExportAssets()
{
var savePath = options.o_outputFolder.Value;
var toExportCount = parsedAssetsList.Count;
var savePath = CLIOptions.o_outputFolder.Value;
var toExportCount = exportableAssetsList.Count;
var exportedCount = 0;
var groupOption = options.o_groupAssetsBy.Value;
foreach (var asset in parsedAssetsList)
var groupOption = CLIOptions.o_groupAssetsBy.Value;
foreach (var asset in exportableAssetsList)
{
string exportPath;
switch (groupOption)
@@ -310,25 +446,25 @@ namespace AssetStudioCLI
exportPath += Path.DirectorySeparatorChar;
try
{
switch (options.o_workMode.Value)
switch (CLIOptions.o_workMode.Value)
{
case WorkMode.ExportRaw:
Logger.Debug($"{options.o_workMode}: {asset.Type} : {asset.Container} : {asset.Text}");
Logger.Debug($"{CLIOptions.o_workMode}: {asset.Type} : {asset.Container} : {asset.Text}");
if (ExportRawFile(asset, exportPath))
{
exportedCount++;
}
break;
case WorkMode.Dump:
Logger.Debug($"{options.o_workMode}: {asset.Type} : {asset.Container} : {asset.Text}");
if (ExportDumpFile(asset, exportPath, assemblyLoader))
Logger.Debug($"{CLIOptions.o_workMode}: {asset.Type} : {asset.Container} : {asset.Text}");
if (ExportDumpFile(asset, exportPath))
{
exportedCount++;
}
break;
case WorkMode.Export:
Logger.Debug($"{options.o_workMode}: {asset.Type} : {asset.Container} : {asset.Text}");
if (ExportConvertFile(asset, exportPath, options, assemblyLoader))
Logger.Debug($"{CLIOptions.o_workMode}: {asset.Type} : {asset.Container} : {asset.Text}");
if (ExportConvertFile(asset, exportPath))
{
exportedCount++;
}
@@ -349,11 +485,11 @@ namespace AssetStudioCLI
}
else if (toExportCount > exportedCount)
{
Logger.Default.Log(LoggerEvent.Info, $"Finished exporting {exportedCount} asset(s) to \"{options.o_outputFolder.Value.Color(Ansi.BrightYellow)}\".", ignoreLevel: true);
Logger.Default.Log(LoggerEvent.Info, $"Finished exporting {exportedCount} asset(s) to \"{CLIOptions.o_outputFolder.Value.Color(Ansi.BrightYellow)}\".", ignoreLevel: true);
}
else
{
Logger.Default.Log(LoggerEvent.Info, $"Finished exporting {exportedCount} asset(s) to \"{options.o_outputFolder.Value.Color(Ansi.BrightGreen)}\".", ignoreLevel: true);
Logger.Default.Log(LoggerEvent.Info, $"Finished exporting {exportedCount} asset(s) to \"{CLIOptions.o_outputFolder.Value.Color(Ansi.BrightGreen)}\".", ignoreLevel: true);
}
if (toExportCount > exportedCount)
@@ -362,11 +498,11 @@ namespace AssetStudioCLI
}
}
public void ExportAssetList()
public static void ExportAssetList()
{
var savePath = options.o_outputFolder.Value;
var savePath = CLIOptions.o_outputFolder.Value;
switch (options.o_exportAssetList.Value)
switch (CLIOptions.o_exportAssetList.Value)
{
case ExportListType.XML:
var filename = Path.Combine(savePath, "assets.xml");
@@ -374,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),
@@ -390,18 +526,115 @@ namespace AssetStudioCLI
break;
}
Logger.Info($"Finished exporting asset list with {parsedAssetsList.Count} items.");
Logger.Info($"Finished exporting asset list with {exportableAssetsList.Count} items.");
}
public void ExportLive2D()
public static void ExportSplitObjects()
{
var baseDestPath = Path.Combine(options.o_outputFolder.Value, "Live2DOutput");
var savePath = CLIOptions.o_outputFolder.Value;
var searchList = CLIOptions.o_filterByName.Value;
var isFiltered = CLIOptions.filterBy == FilterBy.Name;
var exportableObjects = new List<GameObjectNode>();
var exportedCount = 0;
var k = 0;
Logger.Info($"Searching for objects to export..");
Progress.Reset();
var count = gameObjectTree.Sum(x => x.nodes.Count);
foreach (var node in gameObjectTree)
{
foreach (GameObjectNode j in node.nodes)
{
if (isFiltered)
{
if (!searchList.Any(searchText => j.gameObject.m_Name.IndexOf(searchText, StringComparison.OrdinalIgnoreCase) >= 0))
continue;
}
var gameObjects = new List<GameObject>();
CollectNode(j, gameObjects);
if (gameObjects.All(x => x.m_SkinnedMeshRenderer == null && x.m_MeshFilter == null))
{
Progress.Report(++k, count);
continue;
}
exportableObjects.Add(j);
}
}
gameObjectTree.Clear();
var exportableCount = exportableObjects.Count;
var log = $"Found {exportableCount} exportable object(s) ";
if (isFiltered)
{
log += $"that contain {$"\"{string.Join("\", \"", CLIOptions.o_filterByName.Value)}\"".Color(Ansi.BrightYellow)} in their Names";
}
Logger.Info(log);
if (exportableCount > 0)
{
Progress.Reset();
k = 0;
foreach (var gameObjectNode in exportableObjects)
{
var gameObject = gameObjectNode.gameObject;
var filename = FixFileName(gameObject.m_Name);
var targetPath = $"{savePath}{filename}{Path.DirectorySeparatorChar}";
//重名文件处理
for (int i = 1; ; i++)
{
if (Directory.Exists(targetPath))
{
targetPath = $"{savePath}{filename} ({i}){Path.DirectorySeparatorChar}";
}
else
{
break;
}
}
Directory.CreateDirectory(targetPath);
//导出FBX
Logger.Info($"Exporting {filename}.fbx");
Progress.Report(k, exportableCount);
try
{
ExportGameObject(gameObject, targetPath);
Logger.Debug($"{gameObject.type} \"{filename}\" saved to \"{targetPath}\"");
exportedCount++;
}
catch (Exception ex)
{
Logger.Error($"Export GameObject:{gameObject.m_Name} error", ex);
}
k++;
}
}
var status = exportedCount > 0
? $"Finished exporting [{exportedCount}/{exportableCount}] object(s) to \"{CLIOptions.o_outputFolder.Value.Color(Ansi.BrightCyan)}\""
: "Nothing exported";
Logger.Default.Log(LoggerEvent.Info, status, ignoreLevel: true);
}
private static void CollectNode(GameObjectNode node, List<GameObject> gameObjects)
{
gameObjects.Add(node.gameObject);
foreach (GameObjectNode i in node.nodes)
{
CollectNode(i, gameObjects);
}
}
public static void ExportLive2D()
{
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)
{
@@ -410,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);
@@ -417,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)
{
@@ -425,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
@@ -435,33 +687,33 @@ 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 \"{options.o_outputFolder.Value.Color(Ansi.BrightCyan)}\"" :
$"Finished exporting [{modelCounter}/{totalModelCount}] Live2D model(s) to \"{CLIOptions.o_outputFolder.Value.Color(Ansi.BrightCyan)}\"" :
"Nothing exported.";
Logger.Default.Log(LoggerEvent.Info, status, ignoreLevel: true);
}

View File

@@ -0,0 +1,22 @@
# Set the minimum version of CMake that can be used
cmake_minimum_required (VERSION 3.8)
# Set the project name
project("AssetStudioFBXNative")
# Set the C++ standard to C++ 14
set(CMAKE_CXX_STANDARD 14)
# Generate the shared library from the library sources
add_library(AssetStudioFBXNative SHARED
asfbx_skin_context.cpp
asfbx_morph_context.cpp
api.cpp
utils.cpp
asfbx_context.cpp
asfbx_anim_context.cpp)
# Add the given directories to those the compiler uses to search for include files
target_include_directories(AssetStudioFBXNative PRIVATE .)
target_link_libraries(AssetStudioFBXNative PRIVATE fbxsdk xml2)

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.1.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

@@ -1,14 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Reflection.Emit;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace AssetStudioGUI
@@ -18,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();
}
@@ -49,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,18 +2,19 @@
<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.1.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>
<PropertyGroup Condition=" '$(TargetFramework)' == 'net472' ">
<IsPublishable>false</IsPublishable>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
@@ -52,8 +53,15 @@
</ContentWithTargetPath>
</ItemGroup>
<ItemGroup>
<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" Version="4.7.7" />
<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>
@@ -64,12 +72,6 @@
<PackageReference Include="OpenTK.GLControl" Version="3.3.3" />
</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="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
<!-- Use local compiled win-x86 and win-x64 Texture2DDecoder libs, because libs from Kyaru.Texture2DDecoder.Windows were compiled with /MD flag -->
<Target Name="CopyExtraFilesPortable" AfterTargets="AfterBuild" Condition=" '$(RuntimeIdentifier)' == '' OR '$(TargetFramework)' == 'net472' ">

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();
@@ -125,6 +133,8 @@
this.statusStrip1 = new System.Windows.Forms.StatusStrip();
this.toolStripStatusLabel1 = new System.Windows.Forms.ToolStripStatusLabel();
this.contextMenuStrip2 = new System.Windows.Forms.ContextMenuStrip(this.components);
this.showRelatedAssetsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.toolStripSeparator7 = new System.Windows.Forms.ToolStripSeparator();
this.selectAllToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.clearSelectionToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.toolStripSeparator5 = new System.Windows.Forms.ToolStripSeparator();
@@ -229,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";
@@ -239,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.";
@@ -251,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.";
@@ -263,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
@@ -288,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);
//
@@ -526,7 +595,7 @@
this.allToolStripMenuItem.CheckOnClick = true;
this.allToolStripMenuItem.CheckState = System.Windows.Forms.CheckState.Checked;
this.allToolStripMenuItem.Name = "allToolStripMenuItem";
this.allToolStripMenuItem.Size = new System.Drawing.Size(180, 22);
this.allToolStripMenuItem.Size = new System.Drawing.Size(88, 22);
this.allToolStripMenuItem.Text = "All";
this.allToolStripMenuItem.Click += new System.EventHandler(this.typeToolStripMenuItem_Click);
//
@@ -534,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);
@@ -547,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";
@@ -620,7 +709,7 @@
this.sceneTreeView.Size = new System.Drawing.Size(472, 587);
this.sceneTreeView.TabIndex = 1;
this.sceneTreeView.AfterCheck += new System.Windows.Forms.TreeViewEventHandler(this.sceneTreeView_AfterCheck);
this.sceneTreeView.MouseClick += new System.Windows.Forms.MouseEventHandler(this.sceneTreeView_MouseClick);
this.sceneTreeView.NodeMouseClick += new System.Windows.Forms.TreeNodeMouseClickEventHandler(this.sceneTreeView_NodeMouseClick);
//
// treeSearch
//
@@ -629,7 +718,7 @@
this.treeSearch.Location = new System.Drawing.Point(0, 0);
this.treeSearch.Name = "treeSearch";
this.treeSearch.Size = new System.Drawing.Size(472, 20);
this.treeSearch.TabIndex = 0;
this.treeSearch.TabIndex = 2;
this.treeSearch.Text = " Search ";
this.treeSearch.TextChanged += new System.EventHandler(this.treeSearch_TextChanged);
this.treeSearch.Enter += new System.EventHandler(this.treeSearch_Enter);
@@ -1115,13 +1204,28 @@
// contextMenuStrip2
//
this.contextMenuStrip2.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.showRelatedAssetsToolStripMenuItem,
this.toolStripSeparator7,
this.selectAllToolStripMenuItem,
this.clearSelectionToolStripMenuItem,
this.toolStripSeparator5,
this.expandAllToolStripMenuItem,
this.collapseAllToolStripMenuItem});
this.contextMenuStrip2.Name = "contextMenuStrip2";
this.contextMenuStrip2.Size = new System.Drawing.Size(152, 98);
this.contextMenuStrip2.Size = new System.Drawing.Size(152, 126);
this.contextMenuStrip2.Opening += new System.ComponentModel.CancelEventHandler(this.contextMenuStrip2_Opening);
//
// showRelatedAssetsToolStripMenuItem
//
this.showRelatedAssetsToolStripMenuItem.Name = "showRelatedAssetsToolStripMenuItem";
this.showRelatedAssetsToolStripMenuItem.Size = new System.Drawing.Size(151, 22);
this.showRelatedAssetsToolStripMenuItem.Text = "Related assets";
this.showRelatedAssetsToolStripMenuItem.Click += new System.EventHandler(this.showRelatedAssetsToolStripMenuItem_Click);
//
// toolStripSeparator7
//
this.toolStripSeparator7.Name = "toolStripSeparator7";
this.toolStripSeparator7.Size = new System.Drawing.Size(148, 6);
//
// selectAllToolStripMenuItem
//
@@ -1239,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);
@@ -1286,7 +1391,6 @@
private System.Windows.Forms.TextBox treeSearch;
private System.Windows.Forms.ToolStripMenuItem loadFileToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem loadFolderToolStripMenuItem;
private System.Windows.Forms.ListView assetListView;
private System.Windows.Forms.ColumnHeader columnHeaderName;
private System.Windows.Forms.ColumnHeader columnHeaderSize;
private System.Windows.Forms.ColumnHeader columnHeaderType;
@@ -1386,6 +1490,17 @@
private System.Windows.Forms.RichTextBox listSearch;
private System.Windows.Forms.ToolStripSeparator toolStripSeparator6;
private System.Windows.Forms.ToolStripMenuItem allLive2DModelsToolStripMenuItem;
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,6 +1,6 @@
using AssetStudio;
using Arknights;
using AssetStudio;
using Newtonsoft.Json;
using OpenTK;
using OpenTK.Graphics.OpenGL;
using System;
using System.Collections.Generic;
@@ -19,8 +19,11 @@ 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;
using Vector3 = OpenTK.Vector3;
using Vector4 = OpenTK.Vector4;
#else
@@ -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)
{
@@ -267,7 +292,10 @@ namespace AssetStudioGUI
}
allToolStripMenuItem.Checked = true;
var log = $"Finished loading {assetsManager.assetsFileList.Count} files with {assetListView.Items.Count} exportable assets";
var m_ObjectsCount = assetsManager.assetsFileList.Sum(x => x.m_Objects.Count);
var unityVer = assetsManager.assetsFileList[0].version;
var m_ObjectsCount = unityVer[0] > 2020 ?
assetsManager.assetsFileList.Sum(x => x.m_Objects.LongCount(y => y.classID != (int)ClassIDType.Shader)) :
assetsManager.assetsFileList.Sum(x => x.m_Objects.Count);
var objectsCount = assetsManager.assetsFileList.Sum(x => x.Objects.Count);
if (m_ObjectsCount != objectsCount)
{
@@ -348,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)
{
@@ -360,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)
@@ -414,6 +446,7 @@ namespace AssetStudioGUI
switch (lastSelectedItem.Type)
{
case ClassIDType.Texture2D:
case ClassIDType.AkPortraitSprite:
case ClassIDType.Sprite:
{
if (enablePreview.Checked && imageTexture != null)
@@ -499,10 +532,10 @@ namespace AssetStudioGUI
switch (e.TabPageIndex)
{
case 0:
treeSearch.Select();
sceneTreeView.Select();
break;
case 1:
listSearch.Select();
assetListView.Select();
break;
}
}
@@ -782,42 +815,45 @@ namespace AssetStudioGUI
return;
try
{
switch (assetItem.Asset)
switch (assetItem.Type)
{
case Texture2D m_Texture2D:
PreviewTexture2D(assetItem, m_Texture2D);
case ClassIDType.Texture2D:
PreviewTexture2D(assetItem, assetItem.Asset as Texture2D);
break;
case AudioClip m_AudioClip:
PreviewAudioClip(assetItem, m_AudioClip);
case ClassIDType.AudioClip:
PreviewAudioClip(assetItem, assetItem.Asset as AudioClip);
break;
case Shader m_Shader:
PreviewShader(m_Shader);
case ClassIDType.Shader:
PreviewShader(assetItem.Asset as Shader);
break;
case TextAsset m_TextAsset:
PreviewTextAsset(m_TextAsset);
case ClassIDType.TextAsset:
PreviewTextAsset(assetItem.Asset as TextAsset);
break;
case MonoBehaviour m_MonoBehaviour:
PreviewMonoBehaviour(m_MonoBehaviour);
case ClassIDType.MonoBehaviour:
PreviewMonoBehaviour(assetItem.Asset as MonoBehaviour);
break;
case Font m_Font:
PreviewFont(m_Font);
case ClassIDType.Font:
PreviewFont(assetItem.Asset as Font);
break;
case Mesh m_Mesh:
PreviewMesh(m_Mesh);
case ClassIDType.Mesh:
PreviewMesh(assetItem.Asset as Mesh);
break;
case VideoClip m_VideoClip:
PreviewVideoClip(assetItem, m_VideoClip);
case ClassIDType.VideoClip:
PreviewVideoClip(assetItem, assetItem.Asset as VideoClip);
break;
case MovieTexture _:
case ClassIDType.MovieTexture:
StatusStripUpdate("Only supported export.");
break;
case Sprite m_Sprite:
PreviewSprite(assetItem, m_Sprite);
case ClassIDType.Sprite:
PreviewSprite(assetItem, assetItem.Asset as Sprite);
break;
case Animator _:
case ClassIDType.AkPortraitSprite:
PreviewAkPortraitSprite(assetItem);
break;
case ClassIDType.Animator:
StatusStripUpdate("Can be exported to FBX file.");
break;
case AnimationClip _:
case ClassIDType.AnimationClip:
StatusStripUpdate("Can be exported with Animator or Objects");
break;
default:
@@ -1026,7 +1062,7 @@ namespace AssetStudioGUI
var sb = new StringBuilder();
sb.AppendLine($"Width: {m_VideoClip.Width}");
sb.AppendLine($"Height: {m_VideoClip.Height}");
sb.AppendLine($"Frame rate: {m_VideoClip.m_FrameRate}");
sb.AppendLine($"Frame rate: {m_VideoClip.m_FrameRate:.0##}");
sb.AppendLine($"Split alpha: {m_VideoClip.m_HasSplitAlpha}");
assetItem.InfoText = sb.ToString();
@@ -1259,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);
@@ -1267,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
@@ -1279,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();
@@ -1337,7 +1439,7 @@ namespace AssetStudioGUI
private void ResetForm()
{
Text = $"{Application.ProductName} v{Application.ProductVersion}";
Text = guiTitle;
assetsManager.Clear();
assemblyLoader.Clear();
exportableAssets.Clear();
@@ -1376,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)
@@ -1809,10 +1939,11 @@ namespace AssetStudioGUI
logger.ShowErrorMessage = toolStripMenuItem15.Checked;
}
private void sceneTreeView_MouseClick(object sender, MouseEventArgs e)
private void sceneTreeView_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e)
{
if (e.Button == MouseButtons.Right && sceneTreeView.Nodes.Count > 0)
if (e.Button == MouseButtons.Right)
{
sceneTreeView.SelectedNode = e.Node;
contextMenuStrip2.Show(sceneTreeView, e.Location.X, e.Location.Y);
}
}
@@ -1863,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();
@@ -1921,6 +2072,109 @@ namespace AssetStudioGUI
}
}
private void selectRelatedAsset(object sender, EventArgs e)
{
var selectedItem = (ToolStripMenuItem)sender;
var index = int.Parse(selectedItem.Name.Split('_')[0]);
assetListView.SelectedIndices.Clear();
tabControl1.SelectedTab = tabPage2;
var assetItem = assetListView.Items[index];
assetItem.Selected = true;
assetItem.EnsureVisible();
}
private void selectAllRelatedAssets(object sender, EventArgs e)
{
var selectedNode = sceneTreeView.SelectedNode;
var relatedAssets = visibleAssets.FindAll(x => x.TreeNode == selectedNode);
if (relatedAssets.Count > 0)
{
assetListView.SelectedIndices.Clear();
tabControl1.SelectedTab = tabPage2;
foreach (var asset in relatedAssets)
{
var assetItem = assetListView.Items[assetListView.Items.IndexOf(asset)];
assetItem.Selected = true;
}
assetListView.Items[assetListView.Items.IndexOf(relatedAssets[0])].EnsureVisible();
}
}
private void showRelatedAssetsToolStripMenuItem_Click(object sender, EventArgs e)
{
var selectedNode = sceneTreeView.SelectedNode;
var relatedAssets = visibleAssets.FindAll(x => x.TreeNode == selectedNode);
if (relatedAssets.Count == 0)
{
StatusStripUpdate("No related assets were found among the visible assets.");
}
}
private void contextMenuStrip2_Opening(object sender, System.ComponentModel.CancelEventArgs e)
{
var selectedNode = sceneTreeView.SelectedNode;
var relatedAssets = visibleAssets.FindAll(x => x.TreeNode == selectedNode);
showRelatedAssetsToolStripMenuItem.DropDownItems.Clear();
if (relatedAssets.Count > 1)
{
var assetItem = new ToolStripMenuItem
{
CheckOnClick = false,
Name = "selectAllRelatedAssetsToolStripMenuItem",
Size = new Size(180, 22),
Text = "Select all"
};
assetItem.Click += selectAllRelatedAssets;
showRelatedAssetsToolStripMenuItem.DropDownItems.Add(assetItem);
}
foreach (var asset in relatedAssets)
{
var index = assetListView.Items.IndexOf(asset);
var assetItem = new ToolStripMenuItem
{
CheckOnClick = false,
Name = $"{index}_{asset.TypeString}",
Size = new Size(180, 22),
Text = $"({asset.TypeString}) {asset.Text}"
};
assetItem.Click += selectRelatedAsset;
showRelatedAssetsToolStripMenuItem.DropDownItems.Add(assetItem);
}
}
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

@@ -120,9 +120,6 @@
<metadata name="menuStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>312, 17</value>
</metadata>
<metadata name="statusStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>432, 17</value>
</metadata>
<data name="fontPreviewBox.Text" xml:space="preserve">
<value>abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWYZ
1234567890.:,;'\"(!?)+-*/=
@@ -141,6 +138,9 @@ The quick brown fox jumps over the lazy dog. 1234567890
The quick brown fox jumps over the lazy dog. 1234567890</value>
</data>
<metadata name="statusStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>432, 17</value>
</metadata>
<metadata name="contextMenuStrip2.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>775, 21</value>
</metadata>

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

@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="AssetStudioMod.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<!-- UAC Manifest Options
If you want to change the Windows User Account Control level replace the
requestedExecutionLevel node with one of the following.
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
Specifying requestedExecutionLevel element will disable file and registry virtualization.
Remove this element if your application requires this virtualization for backwards
compatibility.
-->
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows Vista -->
<!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
<!-- Windows 10 and Windows 11 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
<!-- Indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher
DPIs. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need
to opt in. Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should
also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config.
Makes the application long-path aware. See https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation -->
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">system</dpiAwareness>
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
</windowsSettings>
</application>
<!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
</assembly>

View File

@@ -33,7 +33,12 @@ namespace AssetStudio
public TypeDefinition GetTypeDefinition(string assemblyName, string fullName)
{
if (moduleDic.TryGetValue(assemblyName, out var module))
moduleDic.TryGetValue(assemblyName, out var module);
if (module == null && !assemblyName.Contains(".dll"))
{
moduleDic.TryGetValue(assemblyName + ".dll", out module);
}
if (module != null)
{
var typeDef = module.GetType(fullName);
if (typeDef == null && assemblyName == "UnityEngine.dll")

View File

@@ -1,16 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net472;net6.0;net6.0-windows;net7.0;net7.0-windows</TargetFrameworks>
<Version>0.17.1.0</Version>
<Copyright>Copyright © Perfare 2018-2022</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>
<PackageReference Include="Mono.Cecil" Version="0.11.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta15" />
</ItemGroup>
<ItemGroup Condition=" !$(TargetFramework.Contains('windows')) ">
@@ -23,6 +22,11 @@
<PackageReference Include="Kyaru.Texture2DDecoder">
<Version>0.17.0</Version>
</PackageReference>
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.0" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net472' ">
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta15" />
</ItemGroup>
<ItemGroup>

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));
//motion
var motions = new SortedDictionary<string, JArray>();
//texture
var textures = new SortedSet<string>();
foreach (var texture2D in texture2Ds)
if (motionMode == Live2DMotionMode.MonoBehaviour && fadeMotionList.Count > 0) //motion from MonoBehaviour
{
using (var image = texture2D.ConvertToImage(flip: true))
Logger.Debug("Motion export method: MonoBehaviour (Fade motion)");
Directory.CreateDirectory(destMotionPath);
foreach (var fadeMotionMono in fadeMotionList)
{
textures.Add($"textures/{texture2D.m_Name}.png");
using (var file = File.OpenWrite($"{destTexturePath}{texture2D.m_Name}.png"))
var fadeMotionObj = fadeMotionMono.ToType();
if (fadeMotionObj == null)
{
image.WriteToStream(file, ImageFormat.Png);
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()));
}
}
//motion
var motions = new JArray();
if (gameObjects.Count > 0)
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,117 +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()}";
motions.Add(new JObject
{
{ "Name", animation.Name },
{ "File", $"motions/{animation.Name}.motion3.json" }
});
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)
{
@@ -241,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",
@@ -299,6 +267,7 @@ namespace CubismLive2DExtractor
Ids = lipSyncParameters.ToArray()
});
//model
var model3 = new CubismModel3Json
{
Version = 3,
@@ -307,7 +276,7 @@ namespace CubismLive2DExtractor
{
Moc = $"{modelName}.moc3",
Textures = textures.ToArray(),
Motions = new JObject { { "", motions } },
Motions = JObject.FromObject(motions),
Expressions = expressions,
},
Groups = groups.ToArray()

View File

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

View File

@@ -32,9 +32,13 @@ 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)
{
var tex = CutImage(m_Sprite, m_Texture2D, m_Sprite.m_RD.textureRect, m_Sprite.m_RD.textureRectOffset, m_Sprite.m_RD.downscaleMultiplier, m_Sprite.m_RD.settingsRaw);
Image<Bgra32> tex = null;
if (spriteMaskMode != SpriteMaskMode.MaskOnly)
{
tex = CutImage(m_Sprite, m_Texture2D, m_Sprite.m_RD.textureRect, m_Sprite.m_RD.textureRectOffset, m_Sprite.m_RD.downscaleMultiplier, m_Sprite.m_RD.settingsRaw);
}
var alphaTex = CutImage(m_Sprite, m_AlphaTexture2D, m_Sprite.m_RD.textureRect, m_Sprite.m_RD.textureRectOffset, m_Sprite.m_RD.downscaleMultiplier, m_Sprite.m_RD.settingsRaw);
switch (spriteMaskMode)
@@ -46,7 +50,6 @@ namespace AssetStudio
tex.ApplyRGBMask(alphaTex);
return tex;
case SpriteMaskMode.MaskOnly:
tex.Dispose();
return alphaTex;
}
}
@@ -89,92 +92,97 @@ namespace AssetStudio
var originalImage = m_Texture2D.ConvertToImage(false);
if (originalImage != null)
{
using (originalImage)
if (downscaleMultiplier > 0f && downscaleMultiplier != 1f)
{
if (downscaleMultiplier > 0f && downscaleMultiplier != 1f)
var width = (int)(m_Texture2D.m_Width / downscaleMultiplier);
var height = (int)(m_Texture2D.m_Height / downscaleMultiplier);
originalImage.Mutate(x => x.Resize(width, height));
}
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 (settingsRaw.packed == 1)
{
//RotateAndFlip
switch (settingsRaw.packingRotation)
{
var width = (int)(m_Texture2D.m_Width / downscaleMultiplier);
var height = (int)(m_Texture2D.m_Height / downscaleMultiplier);
originalImage.Mutate(x => x.Resize(width, height));
}
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));
if (settingsRaw.packed == 1)
{
//RotateAndFlip
switch (settingsRaw.packingRotation)
{
case SpritePackingRotation.FlipHorizontal:
spriteImage.Mutate(x => x.Flip(FlipMode.Horizontal));
break;
case SpritePackingRotation.FlipVertical:
spriteImage.Mutate(x => x.Flip(FlipMode.Vertical));
break;
case SpritePackingRotation.Rotate180:
spriteImage.Mutate(x => x.Rotate(180));
break;
case SpritePackingRotation.Rotate90:
spriteImage.Mutate(x => x.Rotate(270));
break;
}
case SpritePackingRotation.FlipHorizontal:
spriteImage.Mutate(x => x.Flip(FlipMode.Horizontal));
break;
case SpritePackingRotation.FlipVertical:
spriteImage.Mutate(x => x.Flip(FlipMode.Vertical));
break;
case SpritePackingRotation.Rotate180:
spriteImage.Mutate(x => x.Rotate(180));
break;
case SpritePackingRotation.Rotate90:
spriteImage.Mutate(x => x.Rotate(270));
break;
}
}
//Tight
if (settingsRaw.packingMode == SpritePackingMode.Tight)
//Tight
if (settingsRaw.packingMode == SpritePackingMode.Tight)
{
try
{
try
var matrix = Matrix3x2.CreateScale(m_Sprite.m_PixelsToUnits);
matrix *= Matrix3x2.CreateTranslation(m_Sprite.m_Rect.width * m_Sprite.m_Pivot.X - textureRectOffset.X, m_Sprite.m_Rect.height * m_Sprite.m_Pivot.Y - textureRectOffset.Y);
var triangles = GetTriangles(m_Sprite.m_RD);
var points = triangles.Select(x => x.Select(y => new PointF(y.X, y.Y)).ToArray());
var pathBuilder = new PathBuilder(matrix);
foreach (var p in points)
{
var matrix = Matrix3x2.CreateScale(m_Sprite.m_PixelsToUnits);
matrix *= Matrix3x2.CreateTranslation(m_Sprite.m_Rect.width * m_Sprite.m_Pivot.X - textureRectOffset.X, m_Sprite.m_Rect.height * m_Sprite.m_Pivot.Y - textureRectOffset.Y);
var triangles = GetTriangles(m_Sprite.m_RD);
var points = triangles.Select(x => x.Select(y => new PointF(y.X, y.Y)).ToArray());
var pathBuilder = new PathBuilder(matrix);
foreach (var p in points)
pathBuilder.AddLines(p);
pathBuilder.CloseFigure();
}
var path = pathBuilder.Build();
var options = new DrawingOptions
{
GraphicsOptions = new GraphicsOptions
{
pathBuilder.AddLines(p);
pathBuilder.CloseFigure();
Antialias = false,
AlphaCompositionMode = PixelAlphaCompositionMode.DestOut
}
var path = pathBuilder.Build();
var options = new DrawingOptions
};
if (triangles.Length < 1024)
{
var rectP = new RectangularPolygon(0, 0, rect.Width, rect.Height);
try
{
GraphicsOptions = new GraphicsOptions
{
Antialias = false,
AlphaCompositionMode = PixelAlphaCompositionMode.DestOut
}
};
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;
}
using (var mask = new Image<Bgra32>(rect.Width, rect.Height, SixLabors.ImageSharp.Color.Black))
catch (ArgumentOutOfRangeException)
{
mask.Mutate(x => x.Fill(options, SixLabors.ImageSharp.Color.Red, path));
var brush = new ImageBrush(mask);
spriteImage.Mutate(x => x.Fill(options, brush));
spriteImage.Mutate(x => x.Flip(FlipMode.Vertical));
return spriteImage;
// ignored
}
}
catch
using (var mask = new Image<Bgra32>(rect.Width, rect.Height, SixLabors.ImageSharp.Color.Black))
{
// ignored
mask.Mutate(x => x.Fill(options, SixLabors.ImageSharp.Color.Red, path));
var brush = new ImageBrush(mask);
spriteImage.Mutate(x => x.Fill(options, brush));
spriteImage.Mutate(x => x.Flip(FlipMode.Vertical));
return spriteImage;
}
}
//Rectangle
spriteImage.Mutate(x => x.Flip(FlipMode.Vertical));
return spriteImage;
catch (Exception e)
{
Logger.Warning($"{m_Sprite.m_Name} Unable to render the packed sprite correctly.\n{e}");
}
}
//Rectangle
spriteImage.Mutate(x => x.Flip(FlipMode.Vertical));
return spriteImage;
}
return null;

View File

@@ -1,57 +1,42 @@
# Changelog
## 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`
## 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
#### 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

152
README.md
View File

@@ -1,129 +1,137 @@
# 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>
```
- Export Live2D Cubism models
- Dump assets to a specified output folder
```
AssetStudioModCLI <asset folder path> -m live2d
ArknightsStudioCLI <asset folder path> -m dump -o <output folder path>
```
> When running in live2d mode you can only specify `-o`, `--log-level`, `--log-output`, `--export-asset-list`, `--unity-version` and `--assembly-folder` options.
- Export assets and create a log file
```
ArknightsStudioCLI <asset folder path> --log-output both
```
- Export all FBX objects (similar to "Export all objects (split)" option in the GUI)
```
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.
### 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
```
- Show the number of audio assets that have "voice" in their containers
```
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
```
```
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 MonoBehaviour assets that require an assembly folder to read and create a log file
- Export FBX objects that have "model" or "scene" in their names and set the scale factor to 10
```
AssetStudioModCLI <asset folder path> -t monobehaviour --assembly-folder <assembly folder path> --log-output both
ArknightsStudioCLI <asset folder path> -m splitObjects --filter-by-name model,scene --fbx-scale-factor 10
```
- Export assets that require to specify a Unity version
- Load assets of all types and show them (similar to "Display all assets" option in the GUI)
```
AssetStudioModCLI <asset folder path> --unity-version 2017.4.39f1
ArknightsStudioCLI <asset folder path> -m info --load-all
```
- Load assets of all types and dump Material assets
```
ArknightsStudioCLI <asset folder path> -m dump -t material --load-all
```
## GUI Usage
@@ -132,15 +140,15 @@ AssetStudioModCLI <asset folder path> --unity-version 2017.4.39f1
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.
Use **Export** menu.
### Export Model
@@ -154,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.1.0</Version>
<Version>1.2.0</Version>
<Copyright>Copyright © Perfare 2020-2022; Copyright © hozuki 2020</Copyright>
<DebugType>embedded</DebugType>
</PropertyGroup>