- [Core] Project restructure

This commit is contained in:
Razmoth
2023-10-03 01:39:59 +04:00
parent caaab48cc0
commit ebf626d10a
127 changed files with 156 additions and 228 deletions

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="exportAll" value="False" />
<add key="openAfterExport" value="True" />
<add key="convertTexture" value="True" />
<add key="convertAudio" value="True" />
<add key="convertType" value="Png" />
<add key="eulerFilter" value="True" />
<add key="filterPrecision" value="0.25" />
<add key="exportAllNodes" value="True" />
<add key="exportSkins" value="True" />
<add key="exportAnimations" value="True" />
<add key="exportUV0UV1" value="False" />
<add key="boneSize" value="10" />
<add key="fbxVersion" value="3" />
<add key="fbxFormat" value="0" />
<add key="scaleFactor" value="1" />
<add key="exportBlendShape" value="True" />
<add key="castToBone" value="False" />
<add key="restoreExtensionName" value="True" />
<add key="exportAllUvsAsDiffuseMaps" value="False" />
<add key="disableShader" value="False" />
<add key="disableRenderer" value="False" />
<add key="disableAnimationClip" value="False" />
<add key="enableFileLogging" value="False" />
<add key="minimalAssetMap" value="True" />
</appSettings>
</configuration>

View File

@@ -0,0 +1,41 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net6.0-windows;net7.0-windows</TargetFrameworks>
<ApplicationIcon>Resources\as.ico</ApplicationIcon>
<Version>1.00.00</Version>
<AssemblyVersion>1.00.00</AssemblyVersion>
<FileVersion>1.00.00</FileVersion>
<Copyright>Copyright © Razmoth 2022; Copyright © Perfare 2018-2022</Copyright>
<BaseOutputPath>..\AssetStudio.GUI\bin</BaseOutputPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="8.0.0-preview.5.23280.8" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AssetStudio.Utility\AssetStudio.Utility.csproj" />
<ProjectReference Include="..\AssetStudio\AssetStudio.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Settings.cs">
<DesignTimeSharedInput>True</DesignTimeSharedInput>
<AutoGen>True</AutoGen>
</Compile>
</ItemGroup>
<Target Name="CopyExtraFiles" AfterTargets="AfterBuild">
<Copy SourceFiles="$(SolutionDir)AssetStudio.FBXNative\bin\Win32\$(Configuration)\AssetStudio.FBXNative.dll" DestinationFolder="$(TargetDir)x86" ContinueOnError="true" />
<Copy SourceFiles="$(SolutionDir)AssetStudio.FBXNative\bin\x64\$(Configuration)\AssetStudio.FBXNative.dll" DestinationFolder="$(TargetDir)x64" ContinueOnError="true" />
</Target>
<Target Name="PublishExtraFiles" AfterTargets="Publish">
<Copy SourceFiles="$(TargetDir)x86\AssetStudio.FBXNative.dll" DestinationFolder="$(PublishDir)x86" ContinueOnError="true" />
<Copy SourceFiles="$(TargetDir)x64\AssetStudio.FBXNative.dll" DestinationFolder="$(PublishDir)x64" ContinueOnError="true" />
</Target>
</Project>

View File

@@ -0,0 +1,27 @@
namespace AssetStudio.CLI
{
public class AssetItem
{
public string Text;
public Object Asset;
public SerializedFile SourceFile;
public string Container = string.Empty;
public string TypeString;
public long m_PathID;
public long FullSize;
public ClassIDType Type;
public string InfoText;
public string UniqueID;
public AssetItem(Object asset)
{
Asset = asset;
Text = asset.Name;
SourceFile = asset.assetsFile;
Type = asset.type;
TypeString = Type.ToString();
m_PathID = asset.m_PathID;
FullSize = asset.byteSize;
}
}
}

View File

@@ -0,0 +1,204 @@
using System;
using System.IO;
using System.Linq;
using System.CommandLine;
using System.CommandLine.Binding;
using System.CommandLine.Parsing;
using System.Text.RegularExpressions;
namespace AssetStudio.CLI
{
public static class CommandLine
{
public static void Init(string[] args)
{
var rootCommand = RegisterOptions();
rootCommand.Invoke(args);
}
public static RootCommand RegisterOptions()
{
var optionsBinder = new OptionsBinder();
var rootCommand = new RootCommand()
{
optionsBinder.Silent,
optionsBinder.Verbose,
optionsBinder.TypeFilter,
optionsBinder.NameFilter,
optionsBinder.ContainerFilter,
optionsBinder.GameName,
optionsBinder.KeyIndex,
optionsBinder.MapOp,
optionsBinder.MapType,
optionsBinder.MapName,
optionsBinder.UnityVersion,
optionsBinder.GroupAssetsType,
optionsBinder.Model,
optionsBinder.Key,
optionsBinder.AIFile,
optionsBinder.DummyDllFolder,
optionsBinder.Input,
optionsBinder.Output
};
rootCommand.SetHandler(Program.Run, optionsBinder);
return rootCommand;
}
}
public class Options
{
public bool Silent { get; set; }
public bool Verbose { get; set; }
public ClassIDType[] TypeFilter { get; set; }
public Regex[] NameFilter { get; set; }
public Regex[] ContainerFilter { get; set; }
public string GameName { get; set; }
public int KeyIndex { get; set; }
public MapOpType MapOp { get; set; }
public ExportListType MapType { get; set; }
public string MapName { get; set; }
public string UnityVersion { get; set; }
public AssetGroupOption GroupAssetsType { get; set; }
public bool Model { get; set; }
public byte Key { get; set; }
public FileInfo AIFile { get; set; }
public DirectoryInfo DummyDllFolder { get; set; }
public FileInfo Input { get; set; }
public DirectoryInfo Output { get; set; }
}
public class OptionsBinder : BinderBase<Options>
{
public readonly Option<bool> Silent;
public readonly Option<bool> Verbose;
public readonly Option<ClassIDType[]> TypeFilter;
public readonly Option<Regex[]> NameFilter;
public readonly Option<Regex[]> ContainerFilter;
public readonly Option<string> GameName;
public readonly Option<int> KeyIndex;
public readonly Option<MapOpType> MapOp;
public readonly Option<ExportListType> MapType;
public readonly Option<string> MapName;
public readonly Option<string> UnityVersion;
public readonly Option<AssetGroupOption> GroupAssetsType;
public readonly Option<bool> Model;
public readonly Option<byte> Key;
public readonly Option<FileInfo> AIFile;
public readonly Option<DirectoryInfo> DummyDllFolder;
public readonly Argument<FileInfo> Input;
public readonly Argument<DirectoryInfo> Output;
public OptionsBinder()
{
Silent = new Option<bool>("--silent", "Hide log messages.");
Verbose = new Option<bool>("--verbose", "Enable verbose logging.");
TypeFilter = new Option<ClassIDType[]>("--types", "Specify unity class type(s)") { AllowMultipleArgumentsPerToken = true, ArgumentHelpName = "Texture2D|Sprite|etc.." };
NameFilter = new Option<Regex[]>("--names", result => result.Tokens.Select(x => new Regex(x.Value, RegexOptions.IgnoreCase)).ToArray(), false, "Specify name regex filter(s).") { AllowMultipleArgumentsPerToken = true };
ContainerFilter = new Option<Regex[]>("--containers", result => result.Tokens.Select(x => new Regex(x.Value, RegexOptions.IgnoreCase)).ToArray(), false, "Specify container regex filter(s).") { AllowMultipleArgumentsPerToken = true };
GameName = new Option<string>("--game", $"Specify Game.") { IsRequired = true };
KeyIndex = new Option<int>("--key_index", "Specify key index.") { ArgumentHelpName = UnityCNManager.ToString() };
MapOp = new Option<MapOpType>("--map_op", "Specify which map to build.");
MapType = new Option<ExportListType>("--map_type", "AssetMap output type.");
MapName = new Option<string>("--map_name", () => "assets_map", "Specify AssetMap file name.");
UnityVersion = new Option<string>("--unity_version", "Specify Unity version.");
GroupAssetsType = new Option<AssetGroupOption>("--group_assets", "Specify how exported assets should be grouped.");
Model = new Option<bool>("--models", "Enable to export models only");
AIFile = new Option<FileInfo>("--ai_file", "Specify asset_index json file path (to recover GI containers).").LegalFilePathsOnly();
DummyDllFolder = new Option<DirectoryInfo>("--dummy_dlls", "Specify DummyDll path.").LegalFilePathsOnly();
Input = new Argument<FileInfo>("input_path", "Input file/folder.").LegalFilePathsOnly();
Output = new Argument<DirectoryInfo>("output_path", "Output folder.").LegalFilePathsOnly();
Key = new Option<byte>("--key", result =>
{
var value = result.Tokens.Single().Value;
if (value.StartsWith("0x"))
{
value = value[2..];
return Convert.ToByte(value, 0x10);
}
else
{
return byte.Parse(value);
}
}, false, "XOR key to decrypt MiHoYoBinData.");
TypeFilter.AddValidator(FilterValidator);
NameFilter.AddValidator(FilterValidator);
ContainerFilter.AddValidator(FilterValidator);
Key.AddValidator(result =>
{
var value = result.Tokens.Single().Value;
try
{
if (value.StartsWith("0x"))
{
value = value.Substring(2);
Convert.ToByte(value, 0x10);
}
else
{
byte.Parse(value);
}
}
catch (Exception e)
{
result.ErrorMessage = "Invalid byte value.\n" + e.Message;
}
});
GameName.FromAmong(GameManager.GetGameNames());
GroupAssetsType.SetDefaultValue(AssetGroupOption.ByType);
MapOp.SetDefaultValue(MapOpType.None);
MapType.SetDefaultValue(ExportListType.XML);
KeyIndex.SetDefaultValue(0);
}
public void FilterValidator(OptionResult result)
{
var values = result.Tokens.Select(x => x.Value).ToArray();
foreach (var val in values)
{
if (string.IsNullOrWhiteSpace(val))
{
result.ErrorMessage = "Empty string.";
return;
}
try
{
Regex.Match("", val, RegexOptions.IgnoreCase);
}
catch (ArgumentException e)
{
result.ErrorMessage = "Invalid Regex.\n" + e.Message;
return;
}
}
}
protected override Options GetBoundValue(BindingContext bindingContext) =>
new()
{
Silent = bindingContext.ParseResult.GetValueForOption(Silent),
Verbose = bindingContext.ParseResult.GetValueForOption(Verbose),
TypeFilter = bindingContext.ParseResult.GetValueForOption(TypeFilter),
NameFilter = bindingContext.ParseResult.GetValueForOption(NameFilter),
ContainerFilter = bindingContext.ParseResult.GetValueForOption(ContainerFilter),
GameName = bindingContext.ParseResult.GetValueForOption(GameName),
KeyIndex = bindingContext.ParseResult.GetValueForOption(KeyIndex),
MapOp = bindingContext.ParseResult.GetValueForOption(MapOp),
MapType = bindingContext.ParseResult.GetValueForOption(MapType),
MapName = bindingContext.ParseResult.GetValueForOption(MapName),
UnityVersion = bindingContext.ParseResult.GetValueForOption(UnityVersion),
GroupAssetsType = bindingContext.ParseResult.GetValueForOption(GroupAssetsType),
Model = bindingContext.ParseResult.GetValueForOption(Model),
Key = bindingContext.ParseResult.GetValueForOption(Key),
AIFile = bindingContext.ParseResult.GetValueForOption(AIFile),
DummyDllFolder = bindingContext.ParseResult.GetValueForOption(DummyDllFolder),
Input = bindingContext.ParseResult.GetValueForArgument(Input),
Output = bindingContext.ParseResult.GetValueForArgument(Output)
};
}
}

457
AssetStudio.CLI/Exporter.cs Normal file
View File

@@ -0,0 +1,457 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace AssetStudio.CLI
{
internal static class Exporter
{
public static bool ExportTexture2D(AssetItem item, string exportPath)
{
var m_Texture2D = (Texture2D)item.Asset;
if (Properties.Settings.Default.convertTexture)
{
var type = Properties.Settings.Default.convertType;
if (!TryExportFile(exportPath, item, "." + type.ToString().ToLower(), out var exportFullPath))
return false;
var image = m_Texture2D.ConvertToImage(true);
if (image == null)
return false;
using (image)
{
using (var file = File.OpenWrite(exportFullPath))
{
image.WriteToStream(file, type);
}
return true;
}
}
else
{
if (!TryExportFile(exportPath, item, ".tex", out var exportFullPath))
return false;
File.WriteAllBytes(exportFullPath, m_Texture2D.image_data.GetData());
return true;
}
}
public static bool ExportAudioClip(AssetItem item, string exportPath)
{
var m_AudioClip = (AudioClip)item.Asset;
var m_AudioData = m_AudioClip.m_AudioData.GetData();
if (m_AudioData == null || m_AudioData.Length == 0)
return false;
var converter = new AudioClipConverter(m_AudioClip);
if (Properties.Settings.Default.convertAudio && converter.IsSupport)
{
if (!TryExportFile(exportPath, item, ".wav", out var exportFullPath))
return false;
var buffer = converter.ConvertToWav();
if (buffer == null)
return false;
File.WriteAllBytes(exportFullPath, buffer);
}
else
{
if (!TryExportFile(exportPath, item, converter.GetExtensionName(), out var exportFullPath))
return false;
File.WriteAllBytes(exportFullPath, m_AudioData);
}
return true;
}
public static bool ExportShader(AssetItem item, string exportPath)
{
if (!TryExportFile(exportPath, item, ".shader", out var exportFullPath))
return false;
var m_Shader = (Shader)item.Asset;
var str = m_Shader.Convert();
File.WriteAllText(exportFullPath, str);
return true;
}
public static bool ExportTextAsset(AssetItem item, string exportPath)
{
var m_TextAsset = (TextAsset)(item.Asset);
var extension = ".txt";
if (Properties.Settings.Default.restoreExtensionName)
{
if (!string.IsNullOrEmpty(item.Container))
{
extension = Path.GetExtension(item.Container);
}
}
if (!TryExportFile(exportPath, item, extension, out var exportFullPath))
return false;
File.WriteAllBytes(exportFullPath, m_TextAsset.m_Script);
return true;
}
public static bool ExportMonoBehaviour(AssetItem item, string exportPath)
{
if (!TryExportFile(exportPath, item, ".json", out var exportFullPath))
return false;
var m_MonoBehaviour = (MonoBehaviour)item.Asset;
var type = m_MonoBehaviour.ToType();
if (type == null)
{
var m_Type = Studio.MonoBehaviourToTypeTree(m_MonoBehaviour);
type = m_MonoBehaviour.ToType(m_Type);
}
var str = JsonConvert.SerializeObject(type, Formatting.Indented);
File.WriteAllText(exportFullPath, str);
return true;
}
public static bool ExportMiHoYoBinData(AssetItem item, string exportPath)
{
string exportFullPath;
if (item.Asset is MiHoYoBinData m_MiHoYoBinData)
{
switch (m_MiHoYoBinData.Type)
{
case MiHoYoBinDataType.JSON:
if (!TryExportFile(exportPath, item, ".json", out exportFullPath))
return false;
var json = m_MiHoYoBinData.Dump() as string;
if (json.Length != 0)
{
File.WriteAllText(exportFullPath, json);
return true;
}
break;
case MiHoYoBinDataType.Bytes:
var extension = ".bin";
if (Properties.Settings.Default.restoreExtensionName)
{
if (!string.IsNullOrEmpty(item.Container))
{
extension = Path.GetExtension(item.Container);
}
}
if (!TryExportFile(exportPath, item, extension, out exportFullPath))
return false;
var bytes = m_MiHoYoBinData.Dump() as byte[];
if (!bytes.IsNullOrEmpty())
{
File.WriteAllBytes(exportFullPath, bytes);
return true;
}
break;
}
}
return false;
}
public static bool ExportFont(AssetItem item, string exportPath)
{
var m_Font = (Font)item.Asset;
if (m_Font.m_FontData != null)
{
var extension = ".ttf";
if (m_Font.m_FontData[0] == 79 && m_Font.m_FontData[1] == 84 && m_Font.m_FontData[2] == 84 && m_Font.m_FontData[3] == 79)
{
extension = ".otf";
}
if (!TryExportFile(exportPath, item, extension, out var exportFullPath))
return false;
File.WriteAllBytes(exportFullPath, m_Font.m_FontData);
return true;
}
return false;
}
public static bool ExportMesh(AssetItem item, string exportPath)
{
var m_Mesh = (Mesh)item.Asset;
if (m_Mesh.m_VertexCount <= 0)
return false;
if (!TryExportFile(exportPath, item, ".obj", out var exportFullPath))
return false;
var sb = new StringBuilder();
sb.AppendLine("g " + m_Mesh.m_Name);
#region Vertices
if (m_Mesh.m_Vertices == null || m_Mesh.m_Vertices.Length == 0)
{
return false;
}
int c = 3;
if (m_Mesh.m_Vertices.Length == m_Mesh.m_VertexCount * 4)
{
c = 4;
}
for (int v = 0; v < m_Mesh.m_VertexCount; v++)
{
sb.AppendFormat("v {0} {1} {2}\r\n", -m_Mesh.m_Vertices[v * c], m_Mesh.m_Vertices[v * c + 1], m_Mesh.m_Vertices[v * c + 2]);
}
#endregion
#region UV
if (m_Mesh.m_UV0?.Length > 0)
{
c = 4;
if (m_Mesh.m_UV0.Length == m_Mesh.m_VertexCount * 2)
{
c = 2;
}
else if (m_Mesh.m_UV0.Length == m_Mesh.m_VertexCount * 3)
{
c = 3;
}
for (int v = 0; v < m_Mesh.m_VertexCount; v++)
{
sb.AppendFormat("vt {0} {1}\r\n", m_Mesh.m_UV0[v * c], m_Mesh.m_UV0[v * c + 1]);
}
}
#endregion
#region Normals
if (m_Mesh.m_Normals?.Length > 0)
{
if (m_Mesh.m_Normals.Length == m_Mesh.m_VertexCount * 3)
{
c = 3;
}
else if (m_Mesh.m_Normals.Length == m_Mesh.m_VertexCount * 4)
{
c = 4;
}
for (int v = 0; v < m_Mesh.m_VertexCount; v++)
{
sb.AppendFormat("vn {0} {1} {2}\r\n", -m_Mesh.m_Normals[v * c], m_Mesh.m_Normals[v * c + 1], m_Mesh.m_Normals[v * c + 2]);
}
}
#endregion
#region Face
int sum = 0;
for (var i = 0; i < m_Mesh.m_SubMeshes.Length; i++)
{
sb.AppendLine($"g {m_Mesh.m_Name}_{i}");
int indexCount = (int)m_Mesh.m_SubMeshes[i].indexCount;
var end = sum + indexCount / 3;
for (int f = sum; f < end; f++)
{
sb.AppendFormat("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\r\n", m_Mesh.m_Indices[f * 3 + 2] + 1, m_Mesh.m_Indices[f * 3 + 1] + 1, m_Mesh.m_Indices[f * 3] + 1);
}
sum = end;
}
#endregion
sb.Replace("NaN", "0");
File.WriteAllText(exportFullPath, sb.ToString());
return true;
}
public static bool ExportVideoClip(AssetItem item, string exportPath)
{
var m_VideoClip = (VideoClip)item.Asset;
if (m_VideoClip.m_ExternalResources.m_Size > 0)
{
if (!TryExportFile(exportPath, item, Path.GetExtension(m_VideoClip.m_OriginalPath), out var exportFullPath))
return false;
m_VideoClip.m_VideoData.WriteData(exportFullPath);
return true;
}
return false;
}
public static bool ExportMovieTexture(AssetItem item, string exportPath)
{
var m_MovieTexture = (MovieTexture)item.Asset;
if (!TryExportFile(exportPath, item, ".ogv", out var exportFullPath))
return false;
File.WriteAllBytes(exportFullPath, m_MovieTexture.m_MovieData);
return true;
}
public static bool ExportSprite(AssetItem item, string exportPath)
{
var type = Properties.Settings.Default.convertType;
if (!TryExportFile(exportPath, item, "." + type.ToString().ToLower(), out var exportFullPath))
return false;
var image = ((Sprite)item.Asset).GetImage();
if (image != null)
{
using (image)
{
using (var file = File.OpenWrite(exportFullPath))
{
image.WriteToStream(file, type);
}
return true;
}
}
return false;
}
public static bool ExportRawFile(AssetItem item, string exportPath)
{
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)
{
var fileName = FixFileName(item.Text);
fullPath = Path.Combine(dir, fileName + extension);
if (!File.Exists(fullPath))
{
Directory.CreateDirectory(dir);
return true;
}
fullPath = Path.Combine(dir, fileName + item.UniqueID + extension);
if (!File.Exists(fullPath))
{
Directory.CreateDirectory(dir);
return true;
}
return false;
}
public static bool ExportAnimationClip(AssetItem item, string exportPath)
{
if (!TryExportFile(exportPath, item, ".anim", out var exportFullPath))
return false;
var m_AnimationClip = (AnimationClip)item.Asset;
var str = m_AnimationClip.Convert();
if (string.IsNullOrEmpty(str))
return false;
File.WriteAllText(exportFullPath, str);
return true;
}
public static bool ExportAnimator(AssetItem item, string exportPath, List<AssetItem> animationList = null)
{
var exportFullPath = Path.Combine(exportPath, item.Text, item.Text + ".fbx");
if (File.Exists(exportFullPath))
{
exportFullPath = Path.Combine(exportPath, item.Text + item.UniqueID, item.Text + ".fbx");
}
var m_Animator = (Animator)item.Asset;
var convert = animationList != null
? new ModelConverter(m_Animator, Properties.Settings.Default.convertType, Studio.Game, Properties.Settings.Default.collectAnimations, animationList.Select(x => (AnimationClip)x.Asset).ToArray())
: new ModelConverter(m_Animator, Properties.Settings.Default.convertType, Studio.Game, Properties.Settings.Default.collectAnimations);
ExportFbx(convert, exportFullPath);
return true;
}
public static bool ExportGameObject(AssetItem item, string exportPath, List <AssetItem> animationList = null)
{
var m_GameObject = (GameObject)item.Asset;
exportPath = Path.Combine(exportPath, m_GameObject.m_Name) + Path.DirectorySeparatorChar;
return ExportGameObject(m_GameObject, exportPath, animationList);
}
public static bool ExportGameObject(GameObject gameObject, string exportPath, List<AssetItem> animationList = null)
{
var convert = animationList != null
? new ModelConverter(gameObject, Properties.Settings.Default.convertType, Studio.Game, Properties.Settings.Default.collectAnimations, animationList.Select(x => (AnimationClip)x.Asset).ToArray())
: new ModelConverter(gameObject, Properties.Settings.Default.convertType, Studio.Game, Properties.Settings.Default.collectAnimations);
if (convert.MeshList.Count == 0)
{
Logger.Info($"GameObject {gameObject.m_Name} has no mesh, skipping...");
return false;
}
exportPath = exportPath + FixFileName(gameObject.m_Name) + ".fbx";
ExportFbx(convert, exportPath);
return true;
}
private static void ExportFbx(IImported convert, string exportPath)
{
var eulerFilter = Properties.Settings.Default.eulerFilter;
var filterPrecision = (float)Properties.Settings.Default.filterPrecision;
var exportAllNodes = Properties.Settings.Default.exportAllNodes;
var exportSkins = Properties.Settings.Default.exportSkins;
var exportAnimations = Properties.Settings.Default.exportAnimations;
var exportBlendShape = Properties.Settings.Default.exportBlendShape;
var castToBone = Properties.Settings.Default.castToBone;
var boneSize = (int)Properties.Settings.Default.boneSize;
var exportAllUvsAsDiffuseMaps = Properties.Settings.Default.exportAllUvsAsDiffuseMaps;
var exportUV0UV1 = Properties.Settings.Default.exportUV0UV1;
var scaleFactor = (float)Properties.Settings.Default.scaleFactor;
var fbxVersion = Properties.Settings.Default.fbxVersion;
var fbxFormat = Properties.Settings.Default.fbxFormat;
ModelExporter.ExportFbx(exportPath, convert, eulerFilter, filterPrecision,
exportAllNodes, exportSkins, exportAnimations, exportBlendShape, castToBone, boneSize, exportAllUvsAsDiffuseMaps, exportUV0UV1, scaleFactor, fbxVersion, fbxFormat == 1);
}
public static bool ExportDumpFile(AssetItem item, string exportPath)
{
if (!TryExportFile(exportPath, item, ".txt", out var exportFullPath))
return false;
var str = item.Asset.Dump();
if (str != null)
{
File.WriteAllText(exportFullPath, str);
return true;
}
return false;
}
public static bool ExportConvertFile(AssetItem item, string exportPath)
{
switch (item.Type)
{
case ClassIDType.GameObject:
return ExportGameObject(item, exportPath);
case ClassIDType.Texture2D:
return ExportTexture2D(item, exportPath);
case ClassIDType.AudioClip:
return ExportAudioClip(item, exportPath);
case ClassIDType.Shader:
return ExportShader(item, exportPath);
case ClassIDType.TextAsset:
return ExportTextAsset(item, exportPath);
case ClassIDType.MonoBehaviour:
return ExportMonoBehaviour(item, exportPath);
case ClassIDType.Font:
return ExportFont(item, exportPath);
case ClassIDType.Mesh:
return ExportMesh(item, exportPath);
case ClassIDType.VideoClip:
return ExportVideoClip(item, exportPath);
case ClassIDType.MovieTexture:
return ExportMovieTexture(item, exportPath);
case ClassIDType.Sprite:
return ExportSprite(item, exportPath);
case ClassIDType.Animator:
return ExportAnimator(item, exportPath);
case ClassIDType.AnimationClip:
return ExportAnimationClip(item, exportPath);
case ClassIDType.MiHoYoBinData:
return ExportMiHoYoBinData(item, exportPath);
case ClassIDType.Material:
return ExportJSONFile(item, exportPath);
default:
return ExportRawFile(item, exportPath);
}
}
public static bool ExportJSONFile(AssetItem item, string exportPath)
{
if (!TryExportFile(exportPath, item, ".json", out var exportFullPath))
return false;
var settings = new JsonSerializerSettings();
settings.Converters.Add(new StringEnumConverter());
var str = JsonConvert.SerializeObject(item.Asset, Formatting.Indented, settings);
File.WriteAllText(exportFullPath, str);
return true;
}
public static string FixFileName(string str)
{
if (str.Length >= 260) return Path.GetRandomFileName();
return Path.GetInvalidFileNameChars().Aggregate(str, (current, c) => current.Replace(c, '_'));
}
}
}

130
AssetStudio.CLI/Program.cs Normal file
View File

@@ -0,0 +1,130 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using AssetStudio.CLI.Properties;
using static AssetStudio.CLI.Studio;
namespace AssetStudio.CLI
{
public class Program
{
public static void Main(string[] args) => CommandLine.Init(args);
public static void Run(Options o)
{
try
{
var game = GameManager.GetGame(o.GameName);
if (game == null)
{
Console.WriteLine("Invalid Game !!");
Console.WriteLine(GameManager.SupportedGames());
return;
}
if (game.Type.IsUnityCN())
{
if (!UnityCNManager.TryGetEntry(o.KeyIndex, out var unityCN))
{
Console.WriteLine("Invalid key index !!");
Console.WriteLine($"Available Options: \n{UnityCNManager.ToString()}");
return;
}
UnityCN.SetKey(unityCN);
Logger.Info($"[UnityCN] Selected Key is {unityCN}");
}
Studio.Game = game;
Logger.LogVerbose = o.Verbose;
Logger.Default = new ConsoleLogger();
Logger.FileLogging = Settings.Default.enableFileLogging;
AssetsHelper.Minimal = Settings.Default.minimalAssetMap;
Shader.Parsable = !Settings.Default.disableShader;
Renderer.Parsable = !Settings.Default.disableRenderer;
AnimationClip.Parsable = !Settings.Default.disableAnimationClip;
AssetsHelper.SetUnityVersion(o.UnityVersion);
assetsManager.Silent = o.Silent;
assetsManager.Game = game;
assetsManager.SpecifyUnityVersion = o.UnityVersion;
ModelOnly = o.Model;
o.Output.Create();
if (o.Key != default)
{
MiHoYoBinData.Encrypted = true;
MiHoYoBinData.Key = o.Key;
}
if (o.AIFile != null && game.Type.IsGISubGroup())
{
ResourceIndex.FromFile(o.AIFile.FullName);
}
if (o.DummyDllFolder != null)
{
assemblyLoader.Load(o.DummyDllFolder.FullName);
}
Logger.Info("Scanning for files...");
var files = o.Input.Attributes.HasFlag(FileAttributes.Directory) ? Directory.GetFiles(o.Input.FullName, "*.*", SearchOption.AllDirectories).OrderBy(x => x.Length).ToArray() : new string[] { o.Input.FullName };
Logger.Info($"Found {files.Length} files");
if (o.MapOp.HasFlag(MapOpType.CABMap))
{
AssetsHelper.BuildCABMap(files, o.MapName, o.Input.FullName, game);
}
if (o.MapOp.HasFlag(MapOpType.Load))
{
AssetsHelper.LoadCABMap(o.MapName);
assetsManager.ResolveDependencies = true;
}
if (o.MapOp.HasFlag(MapOpType.AssetMap))
{
if (files.Length == 1)
{
throw new Exception("Unable to build AssetMap with input_path as a file !!");
}
var resetEvent = new ManualResetEvent(false);
AssetsHelper.BuildAssetMap(files, o.MapName, game, o.Output.FullName, o.MapType, resetEvent, o.TypeFilter, o.NameFilter, o.ContainerFilter);
resetEvent.WaitOne();
}
if (o.MapOp.HasFlag(MapOpType.Both))
{
var resetEvent = new ManualResetEvent(false);
AssetsHelper.BuildBoth(files, o.MapName, o.Input.FullName, game, o.Output.FullName, o.MapType, resetEvent, o.TypeFilter, o.NameFilter, o.ContainerFilter);
resetEvent.WaitOne();
}
if (o.MapOp.Equals(MapOpType.None) || o.MapOp.HasFlag(MapOpType.Load))
{
var i = 0;
var path = Path.GetDirectoryName(Path.GetFullPath(files[0]));
ImportHelper.MergeSplitAssets(path);
var toReadFile = ImportHelper.ProcessingSplitFiles(files.ToList());
var fileList = new List<string>(toReadFile);
foreach (var file in fileList)
{
assetsManager.LoadFiles(file);
if (assetsManager.assetsFileList.Count > 0)
{
BuildAssetData(o.TypeFilter, o.NameFilter, o.ContainerFilter, ref i);
ExportAssets(o.Output.FullName, exportableAssets, o.GroupAssetsType);
}
exportableAssets.Clear();
assetsManager.Clear();
}
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

@@ -0,0 +1,76 @@
using System;
using System.ComponentModel;
using System.Configuration;
namespace AssetStudio.CLI.Properties {
public static class AppSettings
{
public static string Get(string key)
{
return ConfigurationManager.AppSettings[key];
}
public static TValue Get<TValue>(string key, TValue defaultValue)
{
try
{
var value = Get(key);
if (string.IsNullOrEmpty(value))
return defaultValue;
return (TValue)TypeDescriptor.GetConverter(typeof(TValue)).ConvertFromInvariantString(value);
}
catch (Exception)
{
Console.WriteLine($"Invalid value at \"{key}\", switching to default value [{defaultValue}] !!");
return defaultValue;
}
}
}
public class Settings
{
private static Settings defaultInstance = new Settings();
public static Settings Default => defaultInstance;
public bool displayAll => AppSettings.Get("displayAll", false);
public bool enablePreview => AppSettings.Get("enablePreview", true);
public bool displayInfo => AppSettings.Get("displayInfo", true);
public bool openAfterExport => AppSettings.Get("openAfterExport", true);
public int assetGroupOption => AppSettings.Get("assetGroupOption", 0);
public bool convertTexture => AppSettings.Get("convertTexture", true);
public bool convertAudio => AppSettings.Get("convertAudio", true);
public ImageFormat convertType => AppSettings.Get("convertType", ImageFormat.Png);
public bool eulerFilter => AppSettings.Get("eulerFilter", true);
public decimal filterPrecision => AppSettings.Get("filterPrecision", (decimal)0.25);
public bool exportAllNodes => AppSettings.Get("exportAllNodes", true);
public bool exportSkins => AppSettings.Get("exportSkins", true);
public bool collectAnimations => AppSettings.Get("collectAnimations", true);
public bool exportAnimations => AppSettings.Get("exportAnimations", true);
public decimal boneSize => AppSettings.Get("boneSize", (decimal)10);
public int fbxVersion => AppSettings.Get("fbxVersion", 3);
public int fbxFormat => AppSettings.Get("fbxFormat", 0);
public decimal scaleFactor => AppSettings.Get("scaleFactor", (decimal)1);
public bool exportBlendShape => AppSettings.Get("exportBlendShape", true);
public bool castToBone => AppSettings.Get("castToBone", false);
public bool restoreExtensionName => AppSettings.Get("restoreExtensionName", true);
public bool exportAllUvsAsDiffuseMaps => AppSettings.Get("exportAllUvsAsDiffuseMaps", false);
public bool exportUV0UV1 => AppSettings.Get("exportUV0UV1", false);
public bool encrypted => AppSettings.Get("encrypted", true);
public byte key => AppSettings.Get("key", (byte)0x93);
public int selectedGame => AppSettings.Get("selectedGame", 0);
public bool enableResolveDependencies => AppSettings.Get("enableResolveDependencies", true);
public int selectedCNUnityKey => AppSettings.Get("selectedCNUnityKey", 0);
public int selectedAssetMapType => AppSettings.Get("selectedAssetMapType", 0);
public bool exportMiHoYoBinData => AppSettings.Get("exportMiHoYoBinData", true);
public bool disableShader => AppSettings.Get("disableShader", false);
public bool disableRenderer => AppSettings.Get("disableRenderer", false);
public bool disableAnimationClip => AppSettings.Get("disableAnimationClip", false);
public bool enableFileLogging => AppSettings.Get("enableFileLogging", false);
public bool minimalAssetMap => AppSettings.Get("minimalAssetMap", true);
}
}

482
AssetStudio.CLI/Studio.cs Normal file
View File

@@ -0,0 +1,482 @@
using System;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using static AssetStudio.CLI.Exporter;
using System.Globalization;
using System.Xml;
namespace AssetStudio.CLI
{
[Flags]
public enum MapOpType
{
None,
Load,
CABMap,
AssetMap = 4,
Both = 8,
All = Both | Load,
}
public enum AssetGroupOption
{
ByType,
ByContainer,
BySource,
None,
}
internal static class Studio
{
public static Game Game;
public static bool ModelOnly = false;
public static bool SkipContainer = false;
public static AssetsManager assetsManager = new AssetsManager() { ResolveDependencies = false };
public static AssemblyLoader assemblyLoader = new AssemblyLoader();
public static List<AssetItem> exportableAssets = new List<AssetItem>();
public static int ExtractFolder(string path, string savePath)
{
int extractedCount = 0;
var files = Directory.GetFiles(path, "*.*", SearchOption.AllDirectories);
for (int i = 0; i < files.Length; i++)
{
var file = files[i];
var fileOriPath = Path.GetDirectoryName(file);
var fileSavePath = fileOriPath.Replace(path, savePath);
extractedCount += ExtractFile(file, fileSavePath);
}
return extractedCount;
}
public static int ExtractFile(string[] fileNames, string savePath)
{
int extractedCount = 0;
for (var i = 0; i < fileNames.Length; i++)
{
var fileName = fileNames[i];
extractedCount += ExtractFile(fileName, savePath);
}
return extractedCount;
}
public static int ExtractFile(string fileName, string savePath)
{
int extractedCount = 0;
var reader = new FileReader(fileName);
reader = reader.PreProcessing(Game);
if (reader.FileType == FileType.BundleFile)
extractedCount += ExtractBundleFile(reader, savePath);
else if (reader.FileType == FileType.WebFile)
extractedCount += ExtractWebDataFile(reader, savePath);
else if (reader.FileType == FileType.BlkFile)
extractedCount += ExtractBlkFile(reader, savePath);
else if (reader.FileType == FileType.BlockFile)
extractedCount += ExtractBlockFile(reader, savePath);
else
reader.Dispose();
return extractedCount;
}
private static int ExtractBundleFile(FileReader reader, string savePath)
{
Logger.Info($"Decompressing {reader.FileName} ...");
try
{
var bundleFile = new BundleFile(reader, Game);
reader.Dispose();
if (bundleFile.fileList.Length > 0)
{
var extractPath = Path.Combine(savePath, reader.FileName + "_unpacked");
return ExtractStreamFile(extractPath, bundleFile.fileList);
}
}
catch (InvalidCastException)
{
Logger.Error($"Game type mismatch, Expected {nameof(Mr0k)} but got {Game.Name} ({Game.GetType().Name}) !!");
}
return 0;
}
private static int ExtractWebDataFile(FileReader reader, string savePath)
{
Logger.Info($"Decompressing {reader.FileName} ...");
var webFile = new WebFile(reader);
reader.Dispose();
if (webFile.fileList.Length > 0)
{
var extractPath = Path.Combine(savePath, reader.FileName + "_unpacked");
return ExtractStreamFile(extractPath, webFile.fileList);
}
return 0;
}
private static int ExtractBlkFile(FileReader reader, string savePath)
{
int total = 0;
Logger.Info($"Decompressing {reader.FileName} ...");
try
{
using var stream = BlkUtils.Decrypt(reader, (Blk)Game);
do
{
stream.Offset = stream.AbsolutePosition;
var dummyPath = Path.Combine(reader.FullPath, stream.AbsolutePosition.ToString("X8"));
var subReader = new FileReader(dummyPath, stream, true);
var subSavePath = Path.Combine(savePath, reader.FileName + "_unpacked");
switch (subReader.FileType)
{
case FileType.BundleFile:
total += ExtractBundleFile(subReader, subSavePath);
break;
case FileType.Mhy0File:
total += ExtractMhy0File(subReader, subSavePath);
break;
}
} while (stream.Remaining > 0);
}
catch (InvalidCastException)
{
Logger.Error($"Game type mismatch, Expected {nameof(Blk)} but got {Game.Name} ({Game.GetType().Name}) !!");
}
return total;
}
private static int ExtractBlockFile(FileReader reader, string savePath)
{
int total = 0;
Logger.Info($"Decompressing {reader.FileName} ...");
using var stream = new OffsetStream(reader.BaseStream, 0);
do
{
stream.Offset = stream.AbsolutePosition;
var subSavePath = Path.Combine(savePath, reader.FileName + "_unpacked");
var dummyPath = Path.Combine(reader.FullPath, stream.AbsolutePosition.ToString("X8"));
var subReader = new FileReader(dummyPath, stream, true);
total += ExtractBundleFile(subReader, subSavePath);
} while (stream.Remaining > 0);
return total;
}
private static int ExtractMhy0File(FileReader reader, string savePath)
{
Logger.Info($"Decompressing {reader.FileName} ...");
try
{
var mhy0File = new Mhy0File(reader, reader.FullPath, (Mhy0)Game);
reader.Dispose();
if (mhy0File.fileList.Length > 0)
{
var extractPath = Path.Combine(savePath, reader.FileName + "_unpacked");
return ExtractStreamFile(extractPath, mhy0File.fileList);
}
}
catch (InvalidCastException)
{
Logger.Error($"Game type mismatch, Expected {nameof(Mhy0)} but got {Game.Name} ({Game.GetType().Name}) !!");
}
return 0;
}
private static int ExtractStreamFile(string extractPath, StreamFile[] fileList)
{
int extractedCount = 0;
foreach (var file in fileList)
{
var filePath = Path.Combine(extractPath, file.path);
var fileDirectory = Path.GetDirectoryName(filePath);
if (!Directory.Exists(fileDirectory))
{
Directory.CreateDirectory(fileDirectory);
}
if (!File.Exists(filePath))
{
using (var fileStream = File.Create(filePath))
{
file.stream.CopyTo(fileStream);
}
extractedCount += 1;
}
file.stream.Dispose();
}
return extractedCount;
}
public static void UpdateContainers()
{
if (exportableAssets.Count > 0)
{
Logger.Info("Updating Containers...");
foreach (var asset in exportableAssets)
{
if (int.TryParse(asset.Container, out var value))
{
var last = unchecked((uint)value);
var name = Path.GetFileNameWithoutExtension(asset.SourceFile.originalPath);
if (uint.TryParse(name, out var id))
{
var path = ResourceIndex.GetContainer(id, last);
if (!string.IsNullOrEmpty(path))
{
asset.Container = path;
if (asset.Type == ClassIDType.MiHoYoBinData)
{
asset.Text = Path.GetFileNameWithoutExtension(path);
}
}
}
}
}
Logger.Info("Updated !!");
}
}
public static void BuildAssetData(ClassIDType[] typeFilters, Regex[] nameFilters, Regex[] containerFilters, ref int i)
{
var objectAssetItemDic = new Dictionary<Object, AssetItem>();
var mihoyoBinDataNames = new List<(PPtr<Object>, string)>();
var containers = new List<(PPtr<Object>, string)>();
foreach (var assetsFile in assetsManager.assetsFileList)
{
foreach (var asset in assetsFile.Objects)
{
ProcessAssetData(asset, typeFilters, nameFilters, objectAssetItemDic, mihoyoBinDataNames, containers, ref i);
}
}
foreach ((var pptr, var name) in mihoyoBinDataNames)
{
if (pptr.TryGet<MiHoYoBinData>(out var obj))
{
var assetItem = objectAssetItemDic[obj];
if (int.TryParse(name, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var hash))
{
assetItem.Text = name;
assetItem.Container = hash.ToString();
}
else assetItem.Text = $"BinFile #{assetItem.m_PathID}";
}
}
if (!SkipContainer)
{
foreach ((var pptr, var container) in containers)
{
if (pptr.TryGet(out var obj))
{
var item = objectAssetItemDic[obj];
if (containerFilters.IsNullOrEmpty() || containerFilters.Any(x => x.IsMatch(container)))
{
item.Container = container;
}
else
{
exportableAssets.Remove(item);
}
}
}
containers.Clear();
if (Game.Type.IsGISubGroup())
{
UpdateContainers();
}
}
}
public static void ProcessAssetData(Object asset, ClassIDType[] typeFilters, Regex[] nameFilters, Dictionary<Object, AssetItem> objectAssetItemDic, List<(PPtr<Object>, string)> mihoyoBinDataNames, List<(PPtr<Object>, string)> containers, ref int i)
{
var assetItem = new AssetItem(asset);
objectAssetItemDic.Add(asset, assetItem);
assetItem.UniqueID = "#" + i++;
var exportable = false;
switch (asset)
{
case GameObject m_GameObject:
exportable = ModelOnly && m_GameObject.HasModel();
break;
case Texture2D m_Texture2D:
if (!string.IsNullOrEmpty(m_Texture2D.m_StreamData?.path))
assetItem.FullSize = asset.byteSize + m_Texture2D.m_StreamData.size;
exportable = !ModelOnly;
break;
case AudioClip m_AudioClip:
if (!string.IsNullOrEmpty(m_AudioClip.m_Source))
assetItem.FullSize = asset.byteSize + m_AudioClip.m_Size;
exportable = !ModelOnly;
break;
case VideoClip m_VideoClip:
if (!string.IsNullOrEmpty(m_VideoClip.m_OriginalPath))
assetItem.FullSize = asset.byteSize + (long)m_VideoClip.m_ExternalResources.m_Size;
exportable = !ModelOnly;
break;
case MonoBehaviour m_MonoBehaviour:
exportable = !ModelOnly && assemblyLoader.Loaded;
break;
case AssetBundle m_AssetBundle:
foreach (var m_Container in m_AssetBundle.m_Container)
{
var preloadIndex = m_Container.Value.preloadIndex;
var preloadSize = m_Container.Value.preloadSize;
var preloadEnd = preloadIndex + preloadSize;
for (int k = preloadIndex; k < preloadEnd; k++)
{
containers.Add((m_AssetBundle.m_PreloadTable[k], m_Container.Key));
}
}
break;
case IndexObject m_IndexObject:
foreach (var index in m_IndexObject.AssetMap)
{
mihoyoBinDataNames.Add((index.Value.Object, index.Key));
}
break;
case ResourceManager m_ResourceManager:
foreach (var m_Container in m_ResourceManager.m_Container)
{
containers.Add((m_Container.Value, m_Container.Key));
}
break;
case Mesh _:
case TextAsset _:
case AnimationClip _ when AnimationClip.Parsable:
case Font _:
case MovieTexture _:
case Sprite _:
case Material _:
case MiHoYoBinData _:
case Shader _ when Shader.Parsable:
case Animator _:
exportable = !ModelOnly;
break;
}
if (assetItem.Text == "")
{
assetItem.Text = assetItem.TypeString + assetItem.UniqueID;
}
var isMatchRegex = nameFilters.IsNullOrEmpty() || nameFilters.Any(x => x.IsMatch(assetItem.Text));
var isFilteredType = typeFilters.IsNullOrEmpty() || typeFilters.Contains(assetItem.Type);
if (isMatchRegex && isFilteredType && exportable)
{
exportableAssets.Add(assetItem);
}
}
public static void ExportAssets(string savePath, List<AssetItem> toExportAssets, AssetGroupOption assetGroupOption)
{
int toExportCount = toExportAssets.Count;
int exportedCount = 0;
foreach (var asset in toExportAssets)
{
string exportPath;
switch (assetGroupOption)
{
case AssetGroupOption.ByType: //type name
exportPath = Path.Combine(savePath, asset.TypeString);
break;
case AssetGroupOption.ByContainer: //container path
if (!string.IsNullOrEmpty(asset.Container))
{
exportPath = Path.HasExtension(asset.Container) ? Path.Combine(savePath, Path.GetDirectoryName(asset.Container)) : Path.Combine(savePath, asset.Container);
}
else
{
exportPath = Path.Combine(savePath, asset.TypeString);
}
break;
case AssetGroupOption.BySource: //source file
if (string.IsNullOrEmpty(asset.SourceFile.originalPath))
{
exportPath = Path.Combine(savePath, asset.SourceFile.fileName + "_export");
}
else
{
exportPath = Path.Combine(savePath, Path.GetFileName(asset.SourceFile.originalPath) + "_export", asset.SourceFile.fileName);
}
break;
default:
exportPath = savePath;
break;
}
exportPath += Path.DirectorySeparatorChar;
Logger.Info($"[{exportedCount}/{toExportCount}] Exporting {asset.TypeString}: {asset.Text}");
try
{
if (ExportConvertFile(asset, exportPath))
{
exportedCount++;
}
}
catch (Exception ex)
{
Logger.Error($"Export {asset.Type}:{asset.Text} error\r\n{ex.Message}\r\n{ex.StackTrace}");
}
}
var statusText = exportedCount == 0 ? "Nothing exported." : $"Finished exporting {exportedCount} assets.";
if (toExportCount > exportedCount)
{
statusText += $" {toExportCount - exportedCount} assets skipped (not extractable or files already exist)";
}
Logger.Info(statusText);
}
public static void ExportAssetsMap(string savePath, List<AssetEntry> toExportAssets, string exportListName, ExportListType exportListType)
{
string filename;
switch (exportListType)
{
case ExportListType.XML:
filename = Path.Combine(savePath, $"{exportListName}.xml");
var settings = new XmlWriterSettings() { Indent = true };
using (XmlWriter writer = XmlWriter.Create(filename, settings))
{
writer.WriteStartDocument();
writer.WriteStartElement("Assets");
writer.WriteAttributeString("filename", filename);
writer.WriteAttributeString("createdAt", DateTime.UtcNow.ToString("s"));
foreach (var asset in toExportAssets)
{
writer.WriteStartElement("Asset");
writer.WriteElementString("Name", asset.Name);
writer.WriteElementString("Container", asset.Container);
writer.WriteStartElement("Type");
writer.WriteAttributeString("id", ((int)asset.Type).ToString());
writer.WriteValue(asset.Type.ToString());
writer.WriteEndElement();
writer.WriteElementString("PathID", asset.PathID.ToString());
writer.WriteElementString("Source", asset.Source);
writer.WriteEndElement();
}
writer.WriteEndElement();
writer.WriteEndDocument();
}
break;
case ExportListType.JSON:
filename = Path.Combine(savePath, $"{exportListName}.json");
using (StreamWriter file = File.CreateText(filename))
{
JsonSerializer serializer = new JsonSerializer() { Formatting = Newtonsoft.Json.Formatting.Indented };
serializer.Converters.Add(new StringEnumConverter());
serializer.Serialize(file, toExportAssets);
}
break;
}
var statusText = $"Finished exporting asset list with {toExportAssets.Count()} items.";
Logger.Info(statusText);
Logger.Info($"AssetMap build successfully !!");
}
public static TypeTree MonoBehaviourToTypeTree(MonoBehaviour m_MonoBehaviour)
{
return m_MonoBehaviour.ConvertToTypeTree(assemblyLoader);
}
}
}