v0.80.60
This commit is contained in:
@@ -1,11 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<appSettings>
|
||||
<add key="displayAll" value="False" />
|
||||
<add key="enablePreview" value="True" />
|
||||
<add key="displayInfo" value="True" />
|
||||
<add key="exportAll" value="False" />
|
||||
<add key="openAfterExport" value="True" />
|
||||
<add key="assetGroupOption" value="0" />
|
||||
<add key="convertTexture" value="True" />
|
||||
<add key="convertAudio" value="True" />
|
||||
<add key="convertType" value="Png" />
|
||||
@@ -14,6 +11,7 @@
|
||||
<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" />
|
||||
@@ -22,14 +20,5 @@
|
||||
<add key="castToBone" value="False" />
|
||||
<add key="restoreExtensionName" value="True" />
|
||||
<add key="exportAllUvsAsDiffuseMaps" value="False" />
|
||||
<add key="key" value="147" />
|
||||
<add key="encrypted" value="True" />
|
||||
<add key="skipRenderer" value="False" />
|
||||
<add key="selectedGame" value="0" />
|
||||
<add key="collectAnimations" value="False" />
|
||||
<add key="enableResolveDependencies" value="True" />
|
||||
<add key="selectedCNUnityKey" value="0" />
|
||||
<add key="selectedAssetMapType" value="0" />
|
||||
<add key="exportMiHoYoBinData" value="True" />
|
||||
</appSettings>
|
||||
</configuration>
|
||||
@@ -22,7 +22,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Options.cs">
|
||||
<Compile Update="Settings.cs">
|
||||
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||
<AutoGen>True</AutoGen>
|
||||
</Compile>
|
||||
|
||||
189
AssetStudioCLI/Components/CommandLine.cs
Normal file
189
AssetStudioCLI/Components/CommandLine.cs
Normal file
@@ -0,0 +1,189 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.CommandLine;
|
||||
using System.CommandLine.Binding;
|
||||
using System.CommandLine.Parsing;
|
||||
using System.Text.RegularExpressions;
|
||||
using AssetStudio;
|
||||
|
||||
namespace AssetStudioCLI
|
||||
{
|
||||
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.TypeFilter,
|
||||
optionsBinder.NameFilter,
|
||||
optionsBinder.ContainerFilter,
|
||||
optionsBinder.GameName,
|
||||
optionsBinder.MapOp,
|
||||
optionsBinder.MapType,
|
||||
optionsBinder.MapName,
|
||||
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 ClassIDType[] TypeFilter { get; set; }
|
||||
public Regex[] NameFilter { get; set; }
|
||||
public Regex[] ContainerFilter { get; set; }
|
||||
public string GameName { get; set; }
|
||||
public MapOpType MapOp { get; set; }
|
||||
public ExportListType MapType { get; set; }
|
||||
public string MapName { 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<ClassIDType[]> TypeFilter;
|
||||
public readonly Option<Regex[]> NameFilter;
|
||||
public readonly Option<Regex[]> ContainerFilter;
|
||||
public readonly Option<string> GameName;
|
||||
public readonly Option<MapOpType> MapOp;
|
||||
public readonly Option<ExportListType> MapType;
|
||||
public readonly Option<string> MapName;
|
||||
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.");
|
||||
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 };
|
||||
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.");
|
||||
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.Load);
|
||||
MapType.SetDefaultValue(ExportListType.XML);
|
||||
}
|
||||
|
||||
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),
|
||||
TypeFilter = bindingContext.ParseResult.GetValueForOption(TypeFilter),
|
||||
NameFilter = bindingContext.ParseResult.GetValueForOption(NameFilter),
|
||||
ContainerFilter = bindingContext.ParseResult.GetValueForOption(ContainerFilter),
|
||||
GameName = bindingContext.ParseResult.GetValueForOption(GameName),
|
||||
MapOp = bindingContext.ParseResult.GetValueForOption(MapOp),
|
||||
MapType = bindingContext.ParseResult.GetValueForOption(MapType),
|
||||
MapName = bindingContext.ParseResult.GetValueForOption(MapName),
|
||||
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)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -91,6 +91,22 @@ namespace AssetStudioCLI
|
||||
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;
|
||||
@@ -315,6 +331,13 @@ namespace AssetStudioCLI
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ExportGameObject(AssetItem item, string exportPath, List <AssetItem> animationList = null)
|
||||
{
|
||||
var m_GameObject = (GameObject)item.Asset;
|
||||
ExportGameObject(m_GameObject, exportPath, animationList);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void ExportGameObject(GameObject gameObject, string exportPath, List<AssetItem> animationList = null)
|
||||
{
|
||||
var convert = animationList != null
|
||||
@@ -369,6 +392,8 @@ namespace AssetStudioCLI
|
||||
{
|
||||
switch (item.Type)
|
||||
{
|
||||
case ClassIDType.GameObject:
|
||||
return ExportGameObject(item, exportPath);
|
||||
case ClassIDType.Texture2D:
|
||||
return ExportTexture2D(item, exportPath);
|
||||
case ClassIDType.AudioClip:
|
||||
@@ -378,7 +403,7 @@ namespace AssetStudioCLI
|
||||
case ClassIDType.TextAsset:
|
||||
return ExportTextAsset(item, exportPath);
|
||||
case ClassIDType.MonoBehaviour:
|
||||
return false;
|
||||
return ExportMonoBehaviour(item, exportPath);
|
||||
case ClassIDType.Font:
|
||||
return ExportFont(item, exportPath);
|
||||
case ClassIDType.Mesh:
|
||||
@@ -390,7 +415,7 @@ namespace AssetStudioCLI
|
||||
case ClassIDType.Sprite:
|
||||
return ExportSprite(item, exportPath);
|
||||
case ClassIDType.Animator:
|
||||
return ExportAnimator(item, exportPath, new List<AssetItem>());
|
||||
return ExportAnimator(item, exportPath);
|
||||
case ClassIDType.AnimationClip:
|
||||
return false;
|
||||
case ClassIDType.MiHoYoBinData:
|
||||
|
||||
@@ -1,276 +1,132 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.CommandLine;
|
||||
using System.CommandLine.Binding;
|
||||
using System.Text.RegularExpressions;
|
||||
using static AssetStudioCLI.Studio;
|
||||
using System.Threading;
|
||||
using AssetStudio;
|
||||
using System.CommandLine.Parsing;
|
||||
using static AssetStudioCLI.Studio;
|
||||
|
||||
namespace AssetStudioCLI
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
public static void Main(string[] args) => CommandLine.Init(args);
|
||||
|
||||
public static void Run(Options o)
|
||||
{
|
||||
var rootCommand = RegisterOptions();
|
||||
rootCommand.Invoke(args);
|
||||
}
|
||||
|
||||
public static RootCommand RegisterOptions()
|
||||
{
|
||||
var optionsBinder = new OptionsBinder();
|
||||
var rootCommand = new RootCommand()
|
||||
try
|
||||
{
|
||||
optionsBinder.Silent,
|
||||
optionsBinder.TypeFilter,
|
||||
optionsBinder.NameFilter,
|
||||
optionsBinder.ContainerFilter,
|
||||
optionsBinder.GameName,
|
||||
optionsBinder.MapOp,
|
||||
optionsBinder.MapType,
|
||||
optionsBinder.MapName,
|
||||
optionsBinder.GroupAssetsType,
|
||||
optionsBinder.IncludeAnimators,
|
||||
optionsBinder.SkipMiHoYoBinData,
|
||||
optionsBinder.Key,
|
||||
optionsBinder.AIFile,
|
||||
optionsBinder.Input,
|
||||
optionsBinder.Output
|
||||
};
|
||||
var game = GameManager.GetGame(o.GameName);
|
||||
|
||||
rootCommand.SetHandler((Options o) =>
|
||||
{
|
||||
try
|
||||
if (game == null)
|
||||
{
|
||||
var game = GameManager.GetGame(o.GameName);
|
||||
|
||||
if (game == null)
|
||||
{
|
||||
Console.WriteLine("Invalid Game !!");
|
||||
Console.WriteLine(GameManager.SupportedGames());
|
||||
return;
|
||||
}
|
||||
|
||||
Studio.Game = game;
|
||||
Logger.Default = new ConsoleLogger();
|
||||
assetsManager.Silent = o.Silent;
|
||||
assetsManager.Game = game;
|
||||
MiHoYoBinData.Exportable = !o.SkipMiHoYoBinData;
|
||||
|
||||
if (o.Key != default)
|
||||
{
|
||||
if (o.SkipMiHoYoBinData)
|
||||
{
|
||||
Logger.Warning("Key is set but IndexObject/MiHoYoBinData is excluded, ignoring key...");
|
||||
}
|
||||
else
|
||||
{
|
||||
MiHoYoBinData.Encrypted = true;
|
||||
MiHoYoBinData.Key = o.Key;
|
||||
}
|
||||
}
|
||||
|
||||
if (o.AIFile != null && game.Type.IsGISubGroup())
|
||||
{
|
||||
ResourceIndex.FromFile(o.AIFile.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(string.Format("Found {0} file(s)", files.Length));
|
||||
|
||||
if (o.MapOp.Equals(MapOpType.None))
|
||||
{
|
||||
var i = 0;
|
||||
foreach (var file in files)
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
if (o.MapOp.HasFlag(MapOpType.CABMap))
|
||||
{
|
||||
AssetsHelper.BuildCABMap(files, "", "", game);
|
||||
}
|
||||
if (o.MapOp.HasFlag(MapOpType.AssetMap))
|
||||
{
|
||||
if (files.Length == 1)
|
||||
{
|
||||
throw new Exception("Unable to build AssetMap with input_path as a file !!");
|
||||
}
|
||||
var assets = AssetsHelper.BuildAssetMap(files, game, o.TypeFilter, o.NameFilter, o.ContainerFilter);
|
||||
if (!o.Output.Exists)
|
||||
{
|
||||
o.Output.Create();
|
||||
}
|
||||
if (string.IsNullOrEmpty(o.MapName))
|
||||
{
|
||||
o.MapName = "assets_map";
|
||||
}
|
||||
AssetsHelper.ExportAssetsMap(assets, o.MapName, o.Output.FullName, o.MapType);
|
||||
}
|
||||
Console.WriteLine("Invalid Game !!");
|
||||
Console.WriteLine(GameManager.SupportedGames());
|
||||
return;
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
Studio.Game = game;
|
||||
Logger.Default = new ConsoleLogger();
|
||||
assetsManager.Silent = o.Silent;
|
||||
assetsManager.Game = game;
|
||||
|
||||
|
||||
if (!o.TypeFilter.IsNullOrEmpty())
|
||||
{
|
||||
Console.WriteLine(e.Message);
|
||||
Console.WriteLine(e.StackTrace);
|
||||
}
|
||||
}, optionsBinder);
|
||||
|
||||
return rootCommand;
|
||||
}
|
||||
}
|
||||
|
||||
public class Options
|
||||
{
|
||||
public bool Silent { get; set; }
|
||||
public ClassIDType[] TypeFilter { get; set; }
|
||||
public Regex[] NameFilter { get; set; }
|
||||
public Regex[] ContainerFilter { get; set; }
|
||||
public string GameName { get; set; }
|
||||
public MapOpType MapOp { get; set; }
|
||||
public ExportListType MapType { get; set; }
|
||||
public string MapName { get; set; }
|
||||
public AssetGroupOption GroupAssetsType { get; set; }
|
||||
public bool SkipRenderer { get; set; }
|
||||
public bool SkipMiHoYoBinData { get; set; }
|
||||
public byte Key { get; set; }
|
||||
public FileInfo AIFile { 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<ClassIDType[]> TypeFilter;
|
||||
public readonly Option<Regex[]> NameFilter;
|
||||
public readonly Option<Regex[]> ContainerFilter;
|
||||
public readonly Option<string> GameName;
|
||||
public readonly Option<MapOpType> MapOp;
|
||||
public readonly Option<ExportListType> MapType;
|
||||
public readonly Option<string> MapName;
|
||||
public readonly Option<AssetGroupOption> GroupAssetsType;
|
||||
public readonly Option<bool> IncludeAnimators;
|
||||
public readonly Option<bool> SkipMiHoYoBinData;
|
||||
public readonly Option<byte> Key;
|
||||
public readonly Option<FileInfo> AIFile;
|
||||
public readonly Argument<FileInfo> Input;
|
||||
public readonly Argument<DirectoryInfo> Output;
|
||||
|
||||
public OptionsBinder()
|
||||
{
|
||||
Silent = new Option<bool>("--silent", "Hide log messages.");
|
||||
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 };
|
||||
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", "Specify AssetMap file name.");
|
||||
GroupAssetsType = new Option<AssetGroupOption>("--group_assets_type", "Specify how exported assets should be grouped.");
|
||||
IncludeAnimators = new Option<bool>("--skip_re", "Include Animator with Export.");
|
||||
SkipMiHoYoBinData = new Option<bool>("--skip_mihoyobindata", "Exclude IndexObject/MiHoYoBinData from AssetMap/Export.");
|
||||
AIFile = new Option<FileInfo>("--ai_file", "Specify asset_index json file path (to recover GI containers).").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.Substring(2);
|
||||
return Convert.ToByte(value, 0x10);
|
||||
}
|
||||
else
|
||||
{
|
||||
return byte.Parse(value);
|
||||
}
|
||||
}, false, "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"))
|
||||
foreach (var kv in assetsManager.ExportableTypes)
|
||||
{
|
||||
value = value.Substring(2);
|
||||
Convert.ToByte(value, 0x10);
|
||||
assetsManager.ExportableTypes[kv.Key] = o.TypeFilter.Contains(kv.Key);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (o.Model)
|
||||
{
|
||||
foreach (var kv in assetsManager.ExportableTypes)
|
||||
{
|
||||
assetsManager.ExportableTypes[kv.Key] = false;
|
||||
}
|
||||
|
||||
assetsManager.ExportableTypes[ClassIDType.Animator] = true;
|
||||
assetsManager.ExportableTypes[ClassIDType.GameObject] = true;
|
||||
assetsManager.ExportableTypes[ClassIDType.Texture2D] = true;
|
||||
assetsManager.ExportableTypes[ClassIDType.Material] = true;
|
||||
assetsManager.ExportableTypes[ClassIDType.Renderer] = true;
|
||||
assetsManager.ExportableTypes[ClassIDType.Mesh] = true;
|
||||
|
||||
ModelOnly = true;
|
||||
}
|
||||
|
||||
if (o.Key != default)
|
||||
{
|
||||
if (!assetsManager.ExportableTypes[ClassIDType.MiHoYoBinData])
|
||||
{
|
||||
Logger.Warning("Key is set but MiHoYoBinData is skipped, ignoring key...");
|
||||
}
|
||||
else
|
||||
{
|
||||
byte.Parse(value);
|
||||
MiHoYoBinData.Encrypted = true;
|
||||
MiHoYoBinData.Key = o.Key;
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
|
||||
if (o.AIFile != null && game.Type.IsGISubGroup())
|
||||
{
|
||||
result.ErrorMessage = "Invalid byte value.\n" + e.Message;
|
||||
}
|
||||
});
|
||||
ResourceIndex.FromFile(o.AIFile.FullName);
|
||||
}
|
||||
|
||||
GameName.FromAmong(GameManager.GetGameNames());
|
||||
|
||||
GroupAssetsType.SetDefaultValue(0);
|
||||
MapOp.SetDefaultValue(MapOpType.None);
|
||||
MapType.SetDefaultValue(ExportListType.XML);
|
||||
}
|
||||
|
||||
public void FilterValidator(OptionResult result)
|
||||
{
|
||||
{
|
||||
var values = result.Tokens.Select(x => x.Value).ToArray();
|
||||
foreach (var val in values)
|
||||
if (o.DummyDllFolder != null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(val))
|
||||
{
|
||||
result.ErrorMessage = "Empty string.";
|
||||
return;
|
||||
}
|
||||
assemblyLoader.Load(o.DummyDllFolder.FullName);
|
||||
}
|
||||
|
||||
try
|
||||
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(string.Format("Found {0} file(s)", files.Length));
|
||||
|
||||
if (o.MapOp.HasFlag(MapOpType.Build))
|
||||
{
|
||||
AssetsHelper.BuildMap(files, o.MapName, o.Input.FullName, game);
|
||||
}
|
||||
if (o.MapOp.HasFlag(MapOpType.Load))
|
||||
{
|
||||
AssetsHelper.LoadMap(o.MapName);
|
||||
assetsManager.ResolveDependencies = true;
|
||||
}
|
||||
if (o.MapOp.HasFlag(MapOpType.List))
|
||||
{
|
||||
if (files.Length == 1)
|
||||
{
|
||||
Regex.Match("", val, RegexOptions.IgnoreCase);
|
||||
throw new Exception("Unable to build AssetMap with input_path as a file !!");
|
||||
}
|
||||
catch (ArgumentException e)
|
||||
var assets = AssetsHelper.BuildAssetMap(files, game, o.NameFilter, o.ContainerFilter);
|
||||
if (!o.Output.Exists)
|
||||
{
|
||||
result.ErrorMessage = "Invalid Regex.\n" + e.Message;
|
||||
return;
|
||||
o.Output.Create();
|
||||
}
|
||||
var resetEvent = new ManualResetEvent(false);
|
||||
AssetsHelper.ExportAssetsMap(assets, o.MapName, o.Output.FullName, o.MapType, resetEvent);
|
||||
resetEvent.WaitOne();
|
||||
}
|
||||
if (o.MapOp.Equals(MapOpType.None) || o.MapOp.HasFlag(MapOpType.Load))
|
||||
{
|
||||
var i = 0;
|
||||
foreach (var file in files)
|
||||
{
|
||||
assetsManager.LoadFiles(file);
|
||||
if (assetsManager.assetsFileList.Count > 0)
|
||||
{
|
||||
BuildAssetData(o.NameFilter, o.ContainerFilter, ref i);
|
||||
ExportAssets(o.Output.FullName, exportableAssets, o.GroupAssetsType);
|
||||
}
|
||||
exportableAssets.Clear();
|
||||
assetsManager.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected override Options GetBoundValue(BindingContext bindingContext) =>
|
||||
new Options
|
||||
{
|
||||
Silent = bindingContext.ParseResult.GetValueForOption(Silent),
|
||||
TypeFilter = bindingContext.ParseResult.GetValueForOption(TypeFilter),
|
||||
NameFilter = bindingContext.ParseResult.GetValueForOption(NameFilter),
|
||||
ContainerFilter = bindingContext.ParseResult.GetValueForOption(ContainerFilter),
|
||||
GameName = bindingContext.ParseResult.GetValueForOption(GameName),
|
||||
MapOp = bindingContext.ParseResult.GetValueForOption(MapOp),
|
||||
MapType = bindingContext.ParseResult.GetValueForOption(MapType),
|
||||
MapName = bindingContext.ParseResult.GetValueForOption(MapName),
|
||||
GroupAssetsType = bindingContext.ParseResult.GetValueForOption(GroupAssetsType),
|
||||
SkipRenderer = bindingContext.ParseResult.GetValueForOption(IncludeAnimators),
|
||||
SkipMiHoYoBinData = bindingContext.ParseResult.GetValueForOption(SkipMiHoYoBinData),
|
||||
Key = bindingContext.ParseResult.GetValueForOption(Key),
|
||||
AIFile = bindingContext.ParseResult.GetValueForOption(AIFile),
|
||||
Input = bindingContext.ParseResult.GetValueForArgument(Input),
|
||||
Output = bindingContext.ParseResult.GetValueForArgument(Output)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ namespace AssetStudioCLI.Properties {
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Console.WriteLine($"Invalid value at \"{key}\", switching to default value [{defaultValue}] !!");
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ using System.Text.RegularExpressions;
|
||||
using static AssetStudioCLI.Exporter;
|
||||
using Object = AssetStudio.Object;
|
||||
using System.Globalization;
|
||||
using System.Xml;
|
||||
|
||||
namespace AssetStudioCLI
|
||||
{
|
||||
@@ -17,9 +18,11 @@ namespace AssetStudioCLI
|
||||
public enum MapOpType
|
||||
{
|
||||
None,
|
||||
AssetMap,
|
||||
CABMap,
|
||||
Both
|
||||
Load,
|
||||
Build,
|
||||
Both,
|
||||
List,
|
||||
All = Build | Load | List
|
||||
}
|
||||
|
||||
public enum AssetGroupOption
|
||||
@@ -32,9 +35,12 @@ namespace AssetStudioCLI
|
||||
|
||||
internal static class Studio
|
||||
{
|
||||
public static AssetsManager assetsManager = new AssetsManager() { ResolveDependencies = false };
|
||||
public static List<AssetItem> exportableAssets = new List<AssetItem>();
|
||||
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)
|
||||
{
|
||||
@@ -213,13 +219,17 @@ namespace AssetStudioCLI
|
||||
if (int.TryParse(asset.Container, out var value))
|
||||
{
|
||||
var last = unchecked((uint)value);
|
||||
var path = ResourceIndex.GetAssetPath(last);
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
var name = Path.GetFileNameWithoutExtension(asset.SourceFile.originalPath);
|
||||
if (uint.TryParse(name, out var id))
|
||||
{
|
||||
asset.Container = path;
|
||||
if (asset.Type == ClassIDType.MiHoYoBinData)
|
||||
var path = ResourceIndex.GetContainer(id, last);
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
asset.Text = Path.GetFileNameWithoutExtension(path);
|
||||
asset.Container = path;
|
||||
if (asset.Type == ClassIDType.MiHoYoBinData)
|
||||
{
|
||||
asset.Text = Path.GetFileNameWithoutExtension(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -228,121 +238,16 @@ namespace AssetStudioCLI
|
||||
}
|
||||
}
|
||||
|
||||
public static void BuildAssetData(ClassIDType[] typeFilters, Regex[] nameFilters, Regex[] containerFilters, ref int i)
|
||||
public static void BuildAssetData(Regex[] nameFilters, Regex[] containerFilters, ref int i)
|
||||
{
|
||||
string productName = null;
|
||||
var objectCount = assetsManager.assetsFileList.Sum(x => x.Objects.Count);
|
||||
var objectAssetItemDic = new Dictionary<Object, AssetItem>(objectCount);
|
||||
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)
|
||||
{
|
||||
var assetItem = new AssetItem(asset);
|
||||
objectAssetItemDic.Add(asset, assetItem);
|
||||
assetItem.UniqueID = "#" + i++;
|
||||
assetItem.Text = "";
|
||||
var exportable = false;
|
||||
switch (asset)
|
||||
{
|
||||
case GameObject m_GameObject:
|
||||
assetItem.Text = m_GameObject.m_Name;
|
||||
break;
|
||||
case Texture2D m_Texture2D:
|
||||
if (!string.IsNullOrEmpty(m_Texture2D.m_StreamData?.path))
|
||||
assetItem.FullSize = asset.byteSize + m_Texture2D.m_StreamData.size;
|
||||
assetItem.Text = m_Texture2D.m_Name;
|
||||
exportable = true;
|
||||
break;
|
||||
case AudioClip m_AudioClip:
|
||||
if (!string.IsNullOrEmpty(m_AudioClip.m_Source))
|
||||
assetItem.FullSize = asset.byteSize + m_AudioClip.m_Size;
|
||||
assetItem.Text = m_AudioClip.m_Name;
|
||||
break;
|
||||
case VideoClip m_VideoClip:
|
||||
if (!string.IsNullOrEmpty(m_VideoClip.m_OriginalPath))
|
||||
assetItem.FullSize = asset.byteSize + (long)m_VideoClip.m_ExternalResources.m_Size;
|
||||
assetItem.Text = m_VideoClip.m_Name;
|
||||
break;
|
||||
case Shader m_Shader:
|
||||
assetItem.Text = m_Shader.m_ParsedForm?.m_Name ?? m_Shader.m_Name;
|
||||
exportable = true;
|
||||
break;
|
||||
case Mesh _:
|
||||
case TextAsset _:
|
||||
case AnimationClip _:
|
||||
case Font _:
|
||||
case MovieTexture _:
|
||||
case Sprite _:
|
||||
case Material _:
|
||||
assetItem.Text = ((NamedObject)asset).m_Name;
|
||||
exportable = true;
|
||||
break;
|
||||
case Animator m_Animator:
|
||||
if (m_Animator.m_GameObject.TryGet(out var gameObject))
|
||||
{
|
||||
assetItem.Text = gameObject.m_Name;
|
||||
}
|
||||
exportable = true;
|
||||
break;
|
||||
case MonoBehaviour m_MonoBehaviour:
|
||||
if (m_MonoBehaviour.m_Name == "" && m_MonoBehaviour.m_Script.TryGet(out var m_Script))
|
||||
{
|
||||
assetItem.Text = m_Script.m_ClassName;
|
||||
}
|
||||
else
|
||||
{
|
||||
assetItem.Text = m_MonoBehaviour.m_Name;
|
||||
}
|
||||
break;
|
||||
case PlayerSettings m_PlayerSettings:
|
||||
productName = m_PlayerSettings.productName;
|
||||
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));
|
||||
}
|
||||
}
|
||||
assetItem.Text = m_AssetBundle.m_Name;
|
||||
break;
|
||||
case IndexObject m_IndexObject:
|
||||
foreach (var index in m_IndexObject.AssetMap)
|
||||
{
|
||||
mihoyoBinDataNames.Add((index.Value.Object, index.Key));
|
||||
}
|
||||
assetItem.Text = "IndexObject";
|
||||
break;
|
||||
case MiHoYoBinData m_MiHoYoBinData:
|
||||
exportable = MiHoYoBinData.Exportable;
|
||||
break;
|
||||
case ResourceManager m_ResourceManager:
|
||||
foreach (var m_Container in m_ResourceManager.m_Container)
|
||||
{
|
||||
containers.Add((m_Container.Value, m_Container.Key));
|
||||
}
|
||||
break;
|
||||
case NamedObject m_NamedObject:
|
||||
assetItem.Text = m_NamedObject.m_Name;
|
||||
exportable = true;
|
||||
break;
|
||||
}
|
||||
if (assetItem.Text == "")
|
||||
{
|
||||
assetItem.Text = assetItem.TypeString + assetItem.UniqueID;
|
||||
}
|
||||
var isMatchRegex = nameFilters.Length == 0 || nameFilters.Any(x => x.IsMatch(assetItem.Text));
|
||||
var isFilteredType = typeFilters.Length == 0 || typeFilters.Contains(assetItem.Asset.type);
|
||||
if (isMatchRegex && isFilteredType && exportable)
|
||||
{
|
||||
exportableAssets.Add(assetItem);
|
||||
}
|
||||
ProcessAssetData(asset, nameFilters, objectAssetItemDic, mihoyoBinDataNames, containers, ref i);
|
||||
}
|
||||
}
|
||||
foreach ((var pptr, var name) in mihoyoBinDataNames)
|
||||
@@ -358,26 +263,138 @@ namespace AssetStudioCLI
|
||||
else assetItem.Text = $"BinFile #{assetItem.m_PathID}";
|
||||
}
|
||||
}
|
||||
foreach ((var pptr, var container) in containers)
|
||||
if (!SkipContainer)
|
||||
{
|
||||
if (pptr.TryGet(out var obj))
|
||||
foreach ((var pptr, var container) in containers)
|
||||
{
|
||||
var item = objectAssetItemDic[obj];
|
||||
if (containerFilters.IsNullOrEmpty() || containerFilters.Any(x => x.IsMatch(container)))
|
||||
if (pptr.TryGet(out var obj))
|
||||
{
|
||||
item.Container = container;
|
||||
if (!objectAssetItemDic.TryGetValue(obj, out var item))
|
||||
{
|
||||
ProcessAssetData(obj, nameFilters, objectAssetItemDic, mihoyoBinDataNames, containers, ref i);
|
||||
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, 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:
|
||||
assetItem.Text = m_GameObject.m_Name;
|
||||
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;
|
||||
assetItem.Text = m_Texture2D.m_Name;
|
||||
exportable = !ModelOnly;
|
||||
break;
|
||||
case AudioClip m_AudioClip:
|
||||
if (!string.IsNullOrEmpty(m_AudioClip.m_Source))
|
||||
assetItem.FullSize = asset.byteSize + m_AudioClip.m_Size;
|
||||
assetItem.Text = m_AudioClip.m_Name;
|
||||
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;
|
||||
assetItem.Text = m_VideoClip.m_Name;
|
||||
exportable = !ModelOnly;
|
||||
break;
|
||||
case Shader m_Shader:
|
||||
assetItem.Text = m_Shader.m_ParsedForm?.m_Name ?? m_Shader.m_Name;
|
||||
exportable = !ModelOnly;
|
||||
break;
|
||||
case Mesh _:
|
||||
case TextAsset _:
|
||||
case AnimationClip _:
|
||||
case Font _:
|
||||
case Sprite _:
|
||||
case Material _:
|
||||
assetItem.Text = ((NamedObject)asset).m_Name;
|
||||
exportable = !ModelOnly;
|
||||
break;
|
||||
case Animator m_Animator:
|
||||
if (m_Animator.m_GameObject.TryGet(out var gameObject))
|
||||
{
|
||||
assetItem.Text = gameObject.m_Name;
|
||||
}
|
||||
exportable = !ModelOnly;
|
||||
break;
|
||||
case MonoBehaviour m_MonoBehaviour:
|
||||
if (m_MonoBehaviour.m_Name == "" && m_MonoBehaviour.m_Script.TryGet(out var m_Script))
|
||||
{
|
||||
assetItem.Text = m_Script.m_ClassName;
|
||||
}
|
||||
else
|
||||
{
|
||||
exportableAssets.Remove(item);
|
||||
assetItem.Text = m_MonoBehaviour.m_Name;
|
||||
}
|
||||
}
|
||||
exportable = !ModelOnly;
|
||||
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));
|
||||
}
|
||||
}
|
||||
assetItem.Text = m_AssetBundle.m_Name;
|
||||
break;
|
||||
case IndexObject m_IndexObject:
|
||||
foreach (var index in m_IndexObject.AssetMap)
|
||||
{
|
||||
mihoyoBinDataNames.Add((index.Value.Object, index.Key));
|
||||
}
|
||||
assetItem.Text = "IndexObject";
|
||||
break;
|
||||
case MiHoYoBinData m_MiHoYoBinData:
|
||||
exportable = !ModelOnly;
|
||||
break;
|
||||
case ResourceManager m_ResourceManager:
|
||||
foreach (var m_Container in m_ResourceManager.m_Container)
|
||||
{
|
||||
containers.Add((m_Container.Value, m_Container.Key));
|
||||
}
|
||||
break;
|
||||
case NamedObject m_NamedObject:
|
||||
assetItem.Text = m_NamedObject.m_Name;
|
||||
break;
|
||||
}
|
||||
if (Game.Type.IsGISubGroup())
|
||||
if (assetItem.Text == "")
|
||||
{
|
||||
UpdateContainers();
|
||||
assetItem.Text = assetItem.TypeString + assetItem.UniqueID;
|
||||
}
|
||||
var isMatchRegex = nameFilters.Length == 0 || nameFilters.Any(x => x.IsMatch(assetItem.Text));
|
||||
if (isMatchRegex && exportable)
|
||||
{
|
||||
exportableAssets.Add(assetItem);
|
||||
}
|
||||
containers.Clear();
|
||||
}
|
||||
|
||||
public static void ExportAssets(string savePath, List<AssetItem> toExportAssets, AssetGroupOption assetGroupOption)
|
||||
@@ -448,28 +465,35 @@ namespace AssetStudioCLI
|
||||
{
|
||||
case ExportListType.XML:
|
||||
filename = Path.Combine(savePath, $"{exportListName}.xml");
|
||||
var doc = new XDocument(
|
||||
new XElement("Assets",
|
||||
new XAttribute("filename", filename),
|
||||
new XAttribute("createdAt", DateTime.UtcNow.ToString("s")),
|
||||
toExportAssets.Select(
|
||||
asset => new XElement("Asset",
|
||||
new XElement("Name", asset.Name),
|
||||
new XElement("Container", asset.Container),
|
||||
new XElement("Type", new XAttribute("id", (int)asset.Type), asset.Type.ToString()),
|
||||
new XElement("PathID", asset.PathID),
|
||||
new XElement("Source", asset.Source)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
doc.Save(filename);
|
||||
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 = Formatting.Indented };
|
||||
JsonSerializer serializer = new JsonSerializer() { Formatting = Newtonsoft.Json.Formatting.Indented };
|
||||
serializer.Converters.Add(new StringEnumConverter());
|
||||
serializer.Serialize(file, toExportAssets);
|
||||
}
|
||||
@@ -482,5 +506,10 @@ namespace AssetStudioCLI
|
||||
|
||||
Logger.Info($"AssetMap build successfully !!");
|
||||
}
|
||||
|
||||
public static TypeTree MonoBehaviourToTypeTree(MonoBehaviour m_MonoBehaviour)
|
||||
{
|
||||
return m_MonoBehaviour.ConvertToTypeTree(assemblyLoader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user