This commit is contained in:
Razmoth
2023-04-01 17:51:33 +04:00
parent 29e99495de
commit c7d60450f8
25 changed files with 1077 additions and 735 deletions

View File

@@ -4,21 +4,21 @@ using System.Linq;
using System.Collections.Generic;
using System.Threading;
using System.Globalization;
using System.Xml.Linq;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json;
using System.Text.RegularExpressions;
using System.Xml;
namespace AssetStudio
{
public static class AssetsHelper
{
public const string CABMapName = "Maps";
public const string MapName = "Maps";
public static CancellationTokenSource tokenSource = new CancellationTokenSource();
private static string BaseFolder = "";
private static Dictionary<string, Entry> CABMap = new Dictionary<string, Entry>(StringComparer.OrdinalIgnoreCase);
private static Dictionary<string, Entry> Map = new Dictionary<string, Entry>(StringComparer.OrdinalIgnoreCase);
private static Dictionary<string, HashSet<long>> Offsets = new Dictionary<string, HashSet<long>>();
private static AssetsManager assetsManager = new AssetsManager() { Silent = true, SkipProcess = true, ResolveDependencies = false };
@@ -31,14 +31,14 @@ namespace AssetStudio
public static string[] GetMaps()
{
Directory.CreateDirectory(CABMapName);
var files = Directory.GetFiles(CABMapName, "*.bin", SearchOption.TopDirectoryOnly);
Directory.CreateDirectory(MapName);
var files = Directory.GetFiles(MapName, "*.bin", SearchOption.TopDirectoryOnly);
return files.Select(x => Path.GetFileNameWithoutExtension(x)).ToArray();
}
public static void Clear()
{
CABMap.Clear();
Map.Clear();
Offsets.Clear();
BaseFolder = string.Empty;
@@ -55,7 +55,7 @@ namespace AssetStudio
public static bool TryAdd(string name, out string path)
{
if (CABMap.TryGetValue(name, out var entry))
if (Map.TryGetValue(name, out var entry))
{
path = Path.Combine(BaseFolder, entry.Path);
if (!Offsets.ContainsKey(path))
@@ -80,12 +80,12 @@ namespace AssetStudio
return false;
}
public static void BuildCABMap(string[] files, string mapName, string baseFolder, Game game)
public static void BuildMap(string[] files, string mapName, string baseFolder, Game game)
{
Logger.Info($"Processing...");
try
{
CABMap.Clear();
Map.Clear();
var collision = 0;
BaseFolder = baseFolder;
assetsManager.Game = game;
@@ -100,7 +100,7 @@ namespace AssetStudio
{
if (tokenSource.IsCancellationRequested)
{
Logger.Info("Building CABMap has been aborted !!");
Logger.Info("Building Map has been aborted !!");
return;
}
var dependencies = assetsFile.m_Externals.Select(x => x.fileName).ToArray();
@@ -111,20 +111,20 @@ namespace AssetStudio
Dependencies = dependencies
};
if (CABMap.ContainsKey(assetsFile.fileName))
if (Map.ContainsKey(assetsFile.fileName))
{
collision++;
continue;
}
CABMap.Add(assetsFile.fileName, entry);
Map.Add(assetsFile.fileName, entry);
}
Logger.Info($"Processed {Path.GetFileName(file)}");
}
assetsManager.Clear();
}
CABMap = CABMap.OrderBy(pair => pair.Key).ToDictionary(pair => pair.Key, pair => pair.Value, StringComparer.OrdinalIgnoreCase);
var outputFile = Path.Combine(CABMapName, $"{mapName}.bin");
Map = Map.OrderBy(pair => pair.Key).ToDictionary(pair => pair.Key, pair => pair.Value, StringComparer.OrdinalIgnoreCase);
var outputFile = Path.Combine(MapName, $"{mapName}.bin");
Directory.CreateDirectory(Path.GetDirectoryName(outputFile));
@@ -132,8 +132,8 @@ namespace AssetStudio
using (var writer = new BinaryWriter(binaryFile))
{
writer.Write(BaseFolder);
writer.Write(CABMap.Count);
foreach (var kv in CABMap)
writer.Write(Map.Count);
foreach (var kv in Map)
{
writer.Write(kv.Key);
writer.Write(kv.Value.Path);
@@ -146,56 +146,19 @@ namespace AssetStudio
}
}
Logger.Info($"CABMap build successfully !! {collision} collisions found");
Logger.Info($"Map build successfully !! {collision} collisions found");
}
catch (Exception e)
{
Logger.Warning($"CABMap was not build, {e}");
Logger.Warning($"Map was not build, {e}");
}
}
public static void LoadMap(string mapName)
{
Logger.Info($"Loading {mapName}");
try
{
CABMap.Clear();
using (var fs = File.OpenRead(Path.Combine(CABMapName, $"{mapName}.bin")))
using (var reader = new BinaryReader(fs))
{
BaseFolder = reader.ReadString();
var count = reader.ReadInt32();
for (int i = 0; i < count; i++)
{
var cab = reader.ReadString();
var path = reader.ReadString();
var offset = reader.ReadInt64();
var depCount = reader.ReadInt32();
var dependencies = new List<string>();
for (int j = 0; j < depCount; j++)
{
var dependancy = reader.ReadString();
dependencies.Add(dependancy);
}
var entry = new Entry()
{
Path = path,
Offset = offset,
Dependencies = dependencies.ToArray()
};
CABMap.Add(cab, entry);
}
}
Logger.Info($"Loaded {mapName} !!");
}
catch (Exception e)
{
Logger.Warning($"{mapName} was not loaded, {e}");
}
}
public static AssetEntry[] BuildAssetMap(string[] files, Game game, ClassIDType[] typeFilters = null, Regex[] nameFilters = null, Regex[] containerFilters = null)
public static void BuildBoth(string[] files, string mapName, string baseFolder, Game game, string savePath, ExportListType exportListType, ManualResetEvent resetEvent = null, Regex[] nameFilters = null, Regex[] containerFilters = null)
{
Map.Clear();
var collision = 0;
BaseFolder = baseFolder;
assetsManager.Game = game;
var assets = new List<AssetEntry>();
for (int i = 0; i < files.Length; i++)
@@ -204,13 +167,32 @@ namespace AssetStudio
assetsManager.LoadFiles(file);
if (assetsManager.assetsFileList.Count > 0)
{
var relativePath = Path.GetRelativePath(BaseFolder, file);
var containers = new List<(PPtr<Object>, string)>();
var mihoyoBinDataNames = new List<(PPtr<Object>, string)>();
var objectAssetItemDic = new Dictionary<Object, AssetEntry>();
var animators = new List<(PPtr<Object>, AssetEntry)>();
foreach (var assetsFile in assetsManager.assetsFileList)
{
assetsFile.m_Objects = ObjectInfo.Filter(assetsFile.m_Objects);
if (tokenSource.IsCancellationRequested)
{
Logger.Info("Building Map has been aborted !!");
return;
}
var dependencies = assetsFile.m_Externals.Select(x => x.fileName).ToArray();
var entry = new Entry()
{
Path = relativePath,
Offset = assetsFile.offset,
Dependencies = dependencies
};
if (Map.ContainsKey(assetsFile.fileName))
{
collision++;
continue;
}
Map.Add(assetsFile.fileName, entry);
foreach (var objInfo in assetsFile.m_Objects)
{
@@ -264,7 +246,7 @@ namespace AssetStudio
case ClassIDType.MiHoYoBinData:
var MiHoYoBinData = new MiHoYoBinData(objectReader);
obj = MiHoYoBinData;
exportable = MiHoYoBinData.Exportable;
exportable = true;
break;
case ClassIDType.IndexObject:
var indexObject = new IndexObject(objectReader);
@@ -275,9 +257,20 @@ namespace AssetStudio
}
asset.Name = "IndexObject";
break;
default:
case ClassIDType.Font:
case ClassIDType.Material:
case ClassIDType.Texture:
case ClassIDType.Mesh:
case ClassIDType.Sprite:
case ClassIDType.TextAsset:
case ClassIDType.Texture2D:
case ClassIDType.VideoClip:
case ClassIDType.AudioClip:
asset.Name = objectReader.ReadAlignedString();
break;
default:
exportable = false;
break;
}
if (obj != null)
{
@@ -285,8 +278,7 @@ namespace AssetStudio
assetsFile.AddObject(obj);
}
var isMatchRegex = nameFilters.IsNullOrEmpty() || nameFilters.Any(x => x.IsMatch(asset.Name) || asset.Type == ClassIDType.Animator);
var isFilteredType = typeFilters.IsNullOrEmpty() || typeFilters.Contains(asset.Type) || asset.Type == ClassIDType.Animator;
if (isMatchRegex && isFilteredType && exportable)
if (isMatchRegex && exportable)
{
assets.Add(asset);
}
@@ -294,7 +286,7 @@ namespace AssetStudio
}
foreach ((var pptr, var asset) in animators)
{
if (pptr.TryGet<GameObject>(out var gameObject) && (nameFilters.IsNullOrEmpty() || nameFilters.Any(x => x.IsMatch(gameObject.m_Name))) && (typeFilters.IsNullOrEmpty() || typeFilters.Contains(asset.Type)))
if (pptr.TryGet<GameObject>(out var gameObject) && (nameFilters.IsNullOrEmpty() || nameFilters.Any(x => x.IsMatch(gameObject.m_Name))))
{
asset.Name = gameObject.m_Name;
}
@@ -339,7 +331,251 @@ namespace AssetStudio
if (int.TryParse(asset.Container, out var value))
{
var last = unchecked((uint)value);
var path = ResourceIndex.GetAssetPath(last);
var name = Path.GetFileNameWithoutExtension(asset.Source);
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.Name = Path.GetFileNameWithoutExtension(path);
}
}
}
}
}
Logger.Info("Updated !!");
}
Map = Map.OrderBy(pair => pair.Key).ToDictionary(pair => pair.Key, pair => pair.Value, StringComparer.OrdinalIgnoreCase);
var outputFile = Path.Combine(MapName, $"{mapName}.bin");
Directory.CreateDirectory(Path.GetDirectoryName(outputFile));
using (var binaryFile = File.OpenWrite(outputFile))
using (var writer = new BinaryWriter(binaryFile))
{
writer.Write(BaseFolder);
writer.Write(Map.Count);
foreach (var kv in Map)
{
writer.Write(kv.Key);
writer.Write(kv.Value.Path);
writer.Write(kv.Value.Offset);
writer.Write(kv.Value.Dependencies.Length);
foreach (var cab in kv.Value.Dependencies)
{
writer.Write(cab);
}
}
}
Logger.Info($"Map build successfully !! {collision} collisions found");
ExportAssetsMap(assets.ToArray(), mapName, savePath, exportListType, resetEvent);
}
public static bool LoadMap(string mapName)
{
Logger.Info($"Loading {mapName}");
try
{
Map.Clear();
using (var fs = File.OpenRead(Path.Combine(MapName, $"{mapName}.bin")))
using (var reader = new BinaryReader(fs))
{
BaseFolder = reader.ReadString();
var count = reader.ReadInt32();
for (int i = 0; i < count; i++)
{
var cab = reader.ReadString();
var path = reader.ReadString();
var offset = reader.ReadInt64();
var depCount = reader.ReadInt32();
var dependencies = new List<string>();
for (int j = 0; j < depCount; j++)
{
var dependancy = reader.ReadString();
dependencies.Add(dependancy);
}
var entry = new Entry()
{
Path = path,
Offset = offset,
Dependencies = dependencies.ToArray()
};
Map.Add(cab, entry);
}
}
Logger.Info($"Loaded {mapName} !!");
return true;
}
catch (Exception e)
{
Logger.Warning($"{mapName} was not loaded, {e}");
}
return false;
}
public static AssetEntry[] BuildAssetMap(string[] files, Game game, Regex[] nameFilters = null, Regex[] containerFilters = null)
{
assetsManager.Game = game;
var assets = new List<AssetEntry>();
for (int i = 0; i < files.Length; i++)
{
var file = files[i];
assetsManager.LoadFiles(file);
if (assetsManager.assetsFileList.Count > 0)
{
var containers = new List<(PPtr<Object>, string)>();
var mihoyoBinDataNames = new List<(PPtr<Object>, string)>();
var objectAssetItemDic = new Dictionary<Object, AssetEntry>();
var animators = new List<(PPtr<Object>, AssetEntry)>();
foreach (var assetsFile in assetsManager.assetsFileList)
{
foreach (var objInfo in assetsFile.m_Objects)
{
var objectReader = new ObjectReader(assetsFile.reader, assetsFile, objInfo, game);
var obj = new Object(objectReader);
var asset = new AssetEntry()
{
Source = file,
PathID = objectReader.m_PathID,
Type = objectReader.type,
Container = ""
};
var exportable = true;
switch (objectReader.type)
{
case ClassIDType.AssetBundle:
var assetBundle = new AssetBundle(objectReader);
foreach (var m_Container in 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((assetBundle.m_PreloadTable[k], m_Container.Key));
}
}
obj = null;
asset.Name = assetBundle.m_Name;
exportable = false;
break;
case ClassIDType.GameObject:
var gameObject = new GameObject(objectReader);
obj = gameObject;
asset.Name = gameObject.m_Name;
exportable = false;
break;
case ClassIDType.Shader:
asset.Name = objectReader.ReadAlignedString();
if (string.IsNullOrEmpty(asset.Name))
{
var m_parsedForm = new SerializedShader(objectReader);
asset.Name = m_parsedForm.m_Name;
}
break;
case ClassIDType.Animator:
var component = new PPtr<Object>(objectReader);
animators.Add((component, asset));
break;
case ClassIDType.MiHoYoBinData:
var MiHoYoBinData = new MiHoYoBinData(objectReader);
obj = MiHoYoBinData;
exportable = true;
break;
case ClassIDType.IndexObject:
var indexObject = new IndexObject(objectReader);
obj = null;
foreach (var index in indexObject.AssetMap)
{
mihoyoBinDataNames.Add((index.Value.Object, index.Key));
}
asset.Name = "IndexObject";
break;
case ClassIDType.Font:
case ClassIDType.Material:
case ClassIDType.Texture:
case ClassIDType.Mesh:
case ClassIDType.Sprite:
case ClassIDType.TextAsset:
case ClassIDType.Texture2D:
case ClassIDType.VideoClip:
case ClassIDType.AudioClip:
asset.Name = objectReader.ReadAlignedString();
break;
default:
exportable = false;
break;
}
if (obj != null)
{
objectAssetItemDic.Add(obj, asset);
assetsFile.AddObject(obj);
}
var isMatchRegex = nameFilters.IsNullOrEmpty() || nameFilters.Any(x => x.IsMatch(asset.Name) || asset.Type == ClassIDType.Animator);
if (isMatchRegex && exportable)
{
assets.Add(asset);
}
}
}
foreach ((var pptr, var asset) in animators)
{
if (pptr.TryGet<GameObject>(out var gameObject) && (nameFilters.IsNullOrEmpty() || nameFilters.Any(x => x.IsMatch(gameObject.m_Name))))
{
asset.Name = gameObject.m_Name;
}
}
foreach((var pptr, var name) in mihoyoBinDataNames)
{
if (pptr.TryGet<MiHoYoBinData>(out var miHoYoBinData))
{
var asset = objectAssetItemDic[miHoYoBinData];
if (int.TryParse(name, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var hash))
{
asset.Name = name;
asset.Container = hash.ToString();
}
else asset.Name = $"BinFile #{asset.PathID}";
}
}
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
{
assets.Remove(item);
}
}
}
Logger.Info($"Processed {Path.GetFileName(file)}");
}
assetsManager.Clear();
}
if (game.Type.IsGISubGroup() && assets.Count > 0)
{
Logger.Info("Updating Containers...");
foreach (var asset in assets)
{
if (int.TryParse(asset.Container, out var value))
{
var last = unchecked((uint)value);
var name = Path.GetFileNameWithoutExtension(asset.Source);
if (uint.TryParse(name, out var id))
{
var path = ResourceIndex.GetContainer(id, last);
if (!string.IsNullOrEmpty(path))
{
asset.Container = path;
@@ -352,10 +588,11 @@ namespace AssetStudio
}
Logger.Info("Updated !!");
}
}
return assets.ToArray();
}
public static void ExportAssetsMap(AssetEntry[] toExportAssets, string name, string savePath, ExportListType exportListType)
public static void ExportAssetsMap(AssetEntry[] toExportAssets, string name, string savePath, ExportListType exportListType, ManualResetEvent resetEvent = null)
{
ThreadPool.QueueUserWorkItem(state =>
{
@@ -368,28 +605,35 @@ namespace AssetStudio
{
case ExportListType.XML:
filename = Path.Combine(savePath, $"{name}.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, $"{name}.json");
using (StreamWriter file = File.CreateText(filename))
{
var serializer = new JsonSerializer() { Formatting = Formatting.Indented };
var serializer = new JsonSerializer() { Formatting = Newtonsoft.Json.Formatting.Indented };
serializer.Converters.Add(new StringEnumConverter());
serializer.Serialize(file, toExportAssets);
}
@@ -398,6 +642,8 @@ namespace AssetStudio
Logger.Info($"Finished exporting asset list with {toExportAssets.Length} items.");
Logger.Info($"AssetMap build successfully !!");
resetEvent?.Set();
});
}
}

View File

@@ -19,15 +19,34 @@ namespace AssetStudio
public string SpecifyUnityVersion;
public CancellationTokenSource tokenSource = new CancellationTokenSource();
public List<SerializedFile> assetsFileList = new List<SerializedFile>();
public Dictionary<ClassIDType, bool> ExportableTypes = new() { { ClassIDType.GameObject, true }, { ClassIDType.Material, true }, { ClassIDType.Texture2D, true }, { ClassIDType.Mesh, true }, { ClassIDType.Renderer, true }, { ClassIDType.Shader, true }, { ClassIDType.TextAsset, true }, { ClassIDType.AnimationClip, true }, { ClassIDType.Font, true }, { ClassIDType.Sprite, true }, { ClassIDType.Animator, true }, { ClassIDType.MiHoYoBinData, true }, { ClassIDType.AssetBundle, true } };
public Dictionary<ClassIDType, bool> ExportableTypes = new Dictionary<ClassIDType, bool>();
internal Dictionary<string, int> assetsFileIndexCache = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
internal Dictionary<string, BinaryReader> resourceFileReaders = new Dictionary<string, BinaryReader>(StringComparer.OrdinalIgnoreCase);
private List<string> importFiles = new List<string>();
private HashSet<string> importFilesHash = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
private HashSet<string> noexistFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
private HashSet<string> assetsFileListHash = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
internal List<string> importFiles = new List<string>();
internal HashSet<string> importFilesHash = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
internal HashSet<string> noexistFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
internal HashSet<string> assetsFileListHash = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
public AssetsManager()
{
ExportableTypes.Add(ClassIDType.GameObject, true);
ExportableTypes.Add(ClassIDType.Material, true);
ExportableTypes.Add(ClassIDType.Texture2D, true);
ExportableTypes.Add(ClassIDType.AudioClip, true);
ExportableTypes.Add(ClassIDType.VideoClip, true);
ExportableTypes.Add(ClassIDType.Mesh, false);
ExportableTypes.Add(ClassIDType.Renderer, false);
ExportableTypes.Add(ClassIDType.Shader, true);
ExportableTypes.Add(ClassIDType.TextAsset, true);
ExportableTypes.Add(ClassIDType.AnimationClip, true);
ExportableTypes.Add(ClassIDType.MonoBehaviour, true);
ExportableTypes.Add(ClassIDType.Font, true);
ExportableTypes.Add(ClassIDType.Sprite, true);
ExportableTypes.Add(ClassIDType.Animator, true);
ExportableTypes.Add(ClassIDType.MiHoYoBinData, true);
}
public void LoadFiles(params string[] files)
{
@@ -88,6 +107,11 @@ namespace AssetStudio
Logger.Info("Loading files has been aborted !!");
break;
}
if (!SkipProcess && !tokenSource.IsCancellationRequested)
{
ReadAssets();
ProcessAssets();
}
}
importFiles.Clear();
@@ -96,11 +120,11 @@ namespace AssetStudio
assetsFileListHash.Clear();
AssetsHelper.ClearOffsets();
if (!SkipProcess && !tokenSource.IsCancellationRequested)
{
ReadAssets();
ProcessAssets();
}
//if (!SkipProcess && !tokenSource.IsCancellationRequested)
//{
// ReadAssets();
// ProcessAssets();
//}
}
private void LoadFile(string fullName)
@@ -153,8 +177,6 @@ namespace AssetStudio
assetsFileList.Add(assetsFile);
assetsFileListHash.Add(assetsFile.fileName);
if (ResolveDependencies)
{
foreach (var sharedFile in assetsFile.m_Externals)
{
var sharedFileName = sharedFile.fileName;
@@ -185,7 +207,6 @@ namespace AssetStudio
}
}
}
}
catch (Exception e)
{
Logger.Error($"Error while reading assets file {reader.FullPath}", e);
@@ -216,37 +237,37 @@ namespace AssetStudio
assetsFileList.Add(assetsFile);
assetsFileListHash.Add(assetsFile.fileName);
if (ResolveDependencies)
{
foreach (var sharedFile in assetsFile.m_Externals)
{
var sharedFileName = sharedFile.fileName;
if (!importFilesHash.Contains(sharedFileName))
{
var sharedFilePath = Path.Combine(Path.GetDirectoryName(originalPath), sharedFileName);
if (!noexistFiles.Contains(sharedFilePath))
{
if (AssetsHelper.TryAdd(sharedFileName, out var path))
{
sharedFilePath = path;
}
if (File.Exists(sharedFilePath))
{
if (!importFiles.Contains(sharedFilePath))
{
importFiles.Add(sharedFilePath);
}
importFilesHash.Add(sharedFileName);
}
else
{
noexistFiles.Add(sharedFilePath);
}
}
}
}
}
//if (ResolveDependencies)
//{
// foreach (var sharedFile in assetsFile.m_Externals)
// {
// var sharedFileName = sharedFile.fileName;
//
// if (!importFilesHash.Contains(sharedFileName))
// {
// var sharedFilePath = Path.Combine(Path.GetDirectoryName(originalPath), sharedFileName);
// if (!noexistFiles.Contains(sharedFilePath))
// {
// if (AssetsHelper.TryAdd(sharedFileName, out var path))
// {
// sharedFilePath = path;
// }
// if (File.Exists(sharedFilePath))
// {
// if (!importFiles.Contains(sharedFilePath))
// {
// importFiles.Add(sharedFilePath);
// }
// importFilesHash.Add(sharedFileName);
// }
// else
// {
// noexistFiles.Add(sharedFilePath);
// }
// }
// }
// }
//}
}
catch (Exception e)
{
@@ -618,6 +639,10 @@ namespace AssetStudio
Logger.Info("Reading assets has been aborted !!");
return;
}
if (assetsFile.IsLoaded(objectInfo))
{
continue;
}
var objectReader = new ObjectReader(assetsFile.reader, assetsFile, objectInfo, Game);
try
{
@@ -639,10 +664,10 @@ namespace AssetStudio
case ClassIDType.AnimatorOverrideController:
obj = new AnimatorOverrideController(objectReader);
break;
case ClassIDType.AssetBundle when ExportableTypes[ClassIDType.AssetBundle]:
case ClassIDType.AssetBundle:
obj = new AssetBundle(objectReader);
break;
case ClassIDType.AudioClip:
case ClassIDType.AudioClip when ExportableTypes[ClassIDType.AudioClip]:
obj = new AudioClip(objectReader);
break;
case ClassIDType.Avatar:
@@ -672,7 +697,7 @@ namespace AssetStudio
case ClassIDType.MiHoYoBinData when ExportableTypes[ClassIDType.MiHoYoBinData]:
obj = new MiHoYoBinData(objectReader);
break;
case ClassIDType.MonoBehaviour:
case ClassIDType.MonoBehaviour when ExportableTypes[ClassIDType.MonoBehaviour]:
obj = new MonoBehaviour(objectReader);
break;
case ClassIDType.MonoScript:
@@ -708,7 +733,7 @@ namespace AssetStudio
case ClassIDType.Transform:
obj = new Transform(objectReader);
break;
case ClassIDType.VideoClip:
case ClassIDType.VideoClip when ExportableTypes[ClassIDType.VideoClip]:
obj = new VideoClip(objectReader);
break;
case ClassIDType.ResourceManager:

View File

@@ -33,5 +33,7 @@ namespace AssetStudio
var m_Layer = reader.ReadInt32();
m_Name = reader.ReadAlignedString();
}
public bool HasModel() => m_Transform != null && m_Transform.m_Father.IsNull && m_Transform.m_Children.Length > 0;
}
}

View File

@@ -41,6 +41,26 @@ namespace AssetStudio
var name = m_External.fileName;
if (!assetsFileIndexCache.TryGetValue(name, out index))
{
if (assetsManager.ResolveDependencies && !assetsManager.importFilesHash.Contains(name))
{
var sharedFilePath = Path.Combine(Path.GetDirectoryName(assetsFile.originalPath), name);
if (!assetsManager.noexistFiles.Contains(sharedFilePath))
{
if (TryAdd(name, out var path))
{
sharedFilePath = path;
}
if (File.Exists(sharedFilePath))
{
assetsManager.importFilesHash.Add(name);
assetsManager.LoadFiles(sharedFilePath);
}
else
{
assetsManager.noexistFiles.Add(sharedFilePath);
}
}
}
index = assetsFileList.FindIndex(x => x.fileName.Equals(name, StringComparison.OrdinalIgnoreCase));
assetsFileIndexCache.Add(name, index);
}

View File

@@ -4,7 +4,7 @@ namespace AssetStudio
{
public static class OPFPUtils
{
public static readonly string[] EncrytpedFolders = { "UI/", "Atlas/", "UITexture/", "DynamicAtlas/" };
public static readonly string[] EncrytpedFolders = { "UITexture", "DynamicAtlas", "UI", "Atlas" };
public static void Decrypt(Span<byte> data, string path)
{

View File

@@ -157,6 +157,9 @@ namespace AssetStudio
case GameType.ShiningNikki:
reader = ParseShiningNikki(reader);
break;
case GameType.HelixWaltz2:
reader = ParseHelixWaltz2(reader);
break;
}
}
if (reader.FileType == FileType.BundleFile && game.Type.IsBlockFile())

View File

@@ -29,6 +29,7 @@ namespace AssetStudio
Games.Add(index++, new Game(GameType.AlchemyStars));
Games.Add(index++, new Game(GameType.FantasyOfWind));
Games.Add(index++, new Game(GameType.ShiningNikki));
Games.Add(index++, new Game(GameType.HelixWaltz2));
}
public static Game GetGame(GameType gameType) => GetGame((int)gameType);
public static Game GetGame(int index)
@@ -128,7 +129,8 @@ namespace AssetStudio
OPFP,
AlchemyStars,
FantasyOfWind,
ShiningNikki
ShiningNikki,
HelixWaltz2
}
public static class GameTypes

View File

@@ -374,5 +374,52 @@ namespace AssetStudio
var stream = new BlockStream(reader.BaseStream, idx);
return new FileReader(reader.FullPath, stream);
}
public static FileReader ParseHelixWaltz2(FileReader reader)
{
var originalHeader = new byte[] { 0x55, 0x6E, 0x69, 0x74, 0x79, 0x46, 0x53, 0x00, 0x00, 0x00, 0x00, 0x07, 0x35, 0x2E, 0x78, 0x2E };
var signature = reader.ReadStringToNull();
reader.AlignStream();
if (signature != "SzxFS")
{
reader.Position = 0;
return reader;
}
var seed = reader.ReadInt32();
reader.Position = 0x10;
var data = reader.ReadBytes((int)reader.Remaining);
var sbox = new byte[0x100];
for (int i = 0; i < sbox.Length; i++)
{
sbox[i] = (byte)i;
}
var key = new byte[0x100];
var random = new Random(seed);
for (int i = 0; i < key.Length; i++)
{
var idx = random.Next(i, 0x100);
var b = sbox[idx];
sbox[idx] = sbox[i];
sbox[i] = b;
key[b] = (byte)i;
}
for (int i = 0; i < data.Length; i++)
{
var idx = data[i];
data[i] = key[idx];
}
MemoryStream ms = new();
ms.Write(originalHeader);
ms.Write(data);
ms.Position = 0;
return new FileReader(reader.FullPath, ms);
}
}
}

View File

@@ -1,7 +1,4 @@
using System.Collections.Generic;
using System.Linq;
namespace AssetStudio
namespace AssetStudio
{
public class ObjectInfo
{
@@ -14,20 +11,5 @@ namespace AssetStudio
public long m_PathID;
public SerializedType serializedType;
public static List<ObjectInfo> Filter(List<ObjectInfo> objects) => objects.Where(x => x.IsExportableType()).OrderBy(x => ExportableTypes.IndexOf((ClassIDType)x.typeID)).ToList();
private bool IsExportableType()
{
var typeID = (ClassIDType)classID;
var isExportableType = ExportableTypes.Contains(typeID);
return typeID switch
{
ClassIDType.IndexObject or ClassIDType.MiHoYoBinData => isExportableType && MiHoYoBinData.Exportable,
_ => isExportableType,
};
}
private readonly static List<ClassIDType> ExportableTypes = new List<ClassIDType> { ClassIDType.GameObject, ClassIDType.IndexObject, ClassIDType.Material, ClassIDType.Texture2D, ClassIDType.Mesh, ClassIDType.Shader, ClassIDType.TextAsset, ClassIDType.AnimationClip, ClassIDType.Font, ClassIDType.Sprite, ClassIDType.Animator, ClassIDType.MiHoYoBinData, ClassIDType.AssetBundle };
}
}

View File

@@ -9,26 +9,8 @@ namespace AssetStudio
{
public static class ResourceIndex
{
public static Dictionary<int, List<int>> BundleDependencyMap;
public static Dictionary<int, Block> BlockInfoMap;
public static Dictionary<int, byte> BlockMap;
public static Dictionary<ulong, ulong> AssetMap;
public static List<Dictionary<uint, BundleInfo>> AssetLocationMap;
public static List<int> BlockSortList;
static ResourceIndex()
{
BlockSortList = new List<int>();
AssetMap = new Dictionary<ulong, ulong>();
AssetLocationMap = new List<Dictionary<uint, BundleInfo>>(0x100);
for (int i = 0; i < AssetLocationMap.Capacity; i++)
{
AssetLocationMap.Add(new Dictionary<uint, BundleInfo>(0x1FF));
}
BundleDependencyMap = new Dictionary<int, List<int>>();
BlockInfoMap = new Dictionary<int, Block>();
BlockMap = new Dictionary<int, byte>();
}
private static AssetIndex Instance = new();
private static Dictionary<uint, Dictionary<uint, string>> BundleMap = new Dictionary<uint, Dictionary<uint, string>>();
public static void FromFile(string path)
{
if (!string.IsNullOrEmpty(path))
@@ -48,11 +30,10 @@ namespace AssetStudio
var json = Encoding.UTF8.GetString(bytes);
var obj = JsonConvert.DeserializeObject<AssetIndex>(json);
if (obj != null)
{
MapToResourceIndex(obj);
}
var settings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };
JsonConvert.PopulateObject(json, Instance, settings);
BuildBundleMap();
}
}
catch (Exception e)
@@ -64,117 +45,52 @@ namespace AssetStudio
Logger.Info("Loaded !!");
}
}
private static void BuildBundleMap()
{
foreach(var asset in Instance.Assets)
{
if (!BundleMap.ContainsKey(asset.Value.Id))
{
BundleMap[asset.Value.Id] = new Dictionary<uint, string>();
}
if (Instance.SubAssets.TryGetValue(asset.Key, out var subAssets))
{
foreach(var subAsset in subAssets)
{
BundleMap[asset.Value.Id].Add(subAsset.PathHashLast, subAsset.Name);
}
}
}
}
public static void Clear()
{
BundleDependencyMap.Clear();
BlockInfoMap.Clear();
BlockMap.Clear();
AssetMap.Clear();
AssetLocationMap.ForEach(x => x.Clear());
BlockSortList.Clear();
Instance.Types.Clear();
Instance.SubAssets.Clear();
Instance.Dependencies.Clear();
Instance.PreloadBlocks.Clear();
Instance.PreloadShaderBlocks.Clear();
Instance.Assets.Clear();
Instance.SortList.Clear();
BundleMap.Clear();
}
public static void MapToResourceIndex(AssetIndex assetIndex)
public static string GetContainer(uint id, uint last)
{
BundleDependencyMap = assetIndex.Dependencies;
BlockSortList = assetIndex.SortList.ConvertAll(x => (int)x);
foreach (var asset in assetIndex.SubAssets)
if (BundleMap.TryGetValue(id, out var bundles))
{
foreach (var subAsset in asset.Value)
if (bundles.TryGetValue(last, out var container))
{
var bundleInfo = new BundleInfo() { Bundle = asset.Key, Path = subAsset.Name };
AssetLocationMap[subAsset.PathHashPre].Add(subAsset.PathHashLast, bundleInfo);
AssetMap[subAsset.PathHashLast] = ((ulong)subAsset.PathHashLast) << 8 | subAsset.PathHashPre;
return container;
}
}
foreach (var asset in assetIndex.Assets)
{
var block = new Block() { Id = (int)asset.Value.Id, Offset = (int)asset.Value.Offset };
BlockInfoMap.Add(asset.Key, block);
if (!BlockMap.ContainsKey((int)asset.Value.Id))
BlockMap.Add((int)asset.Value.Id, asset.Value.Language);
}
}
public static List<BundleInfo> GetAllAssets() => AssetLocationMap.SelectMany(x => x.Values).ToList();
public static List<BundleInfo> GetAssets(int bundle) => AssetLocationMap.SelectMany(x => x.Values).Where(x => x.Bundle == bundle).ToList();
public static ulong GetAssetIndex(ulong blkHash) => AssetMap.TryGetValue(blkHash, out var value) ? value : 0;
public static Block GetBlockInfo(int bundle) => BlockInfoMap.TryGetValue(bundle, out var blk) ? blk : null;
public static BlockFile GetBlockFile(int id) => BlockMap.TryGetValue(id, out var languageCode) ? new BlockFile() { LanguageCode = languageCode, Id = id } : null;
public static int GetBlockID(int bundle) => BlockInfoMap.TryGetValue(bundle, out var block) ? block.Id : 0;
public static List<int> GetBundleDep(int bundle) => BundleDependencyMap.TryGetValue(bundle, out var dep) ? dep : new List<int>();
public static BundleInfo GetBundleInfo(ulong hash)
{
var asset = new Asset() { Hash = hash };
if (AssetLocationMap.ElementAtOrDefault(asset.Pre) != null)
if (AssetLocationMap[asset.Pre].TryGetValue(asset.Last, out var bundleInfo))
return bundleInfo;
return null;
}
public static string GetAssetPath(uint last)
{
foreach (var location in AssetLocationMap)
if (location.TryGetValue(last, out var bundleInfo))
return bundleInfo.Path;
return null;
}
public static List<uint> GetAllAssetIndices(int bundle)
{
var hashes = new List<uint>();
foreach (var location in AssetLocationMap)
foreach (var pair in location)
if (pair.Value.Bundle == bundle)
hashes.Add(pair.Key);
return hashes;
}
public static List<int> GetBundles(int id)
{
var bundles = new List<int>();
foreach (var block in BlockInfoMap)
if (block.Value.Id == id)
bundles.Add(block.Key);
return bundles;
}
public static void GetDepBundles(ref List<int> bundles)
{
for (int i = 0; i < bundles.Count; i++)
{
var bundle = bundles[i];
bundles.AddRange(GetBundleDep(bundle));
}
bundles = bundles.Distinct().ToList();
}
public static bool CheckIsLegitAssetPath(ulong hash)
{
var asset = new Asset() { Hash = hash };
return AssetLocationMap.ElementAtOrDefault(asset.Pre).ContainsKey(asset.Last);
}
}
public class BundleInfo
{
public int Bundle;
public string Path;
}
public class Asset
{
public ulong Hash;
public uint Last => (uint)(Hash >> 8);
public byte Pre => (byte)(Hash & 0xFF);
}
public class Block
{
public int Id;
public int Offset;
}
public class BlockFile
{
public int LanguageCode;
public int Id;
}
public class AssetIndex
return string.Empty;
}
}
public record AssetIndex
{
public Dictionary<string, string> Types { get; set; }
public class SubAssetInfo
public record SubAssetInfo
{
public string Name { get; set; }
public byte PathHashPre { get; set; }
@@ -184,7 +100,7 @@ namespace AssetStudio
public Dictionary<int, List<int>> Dependencies { get; set; }
public List<uint> PreloadBlocks { get; set; }
public List<uint> PreloadShaderBlocks { get; set; }
public class BlockInfo
public record BlockInfo
{
public byte Language { get; set; }
public uint Id { get; set; }
@@ -192,5 +108,16 @@ namespace AssetStudio
}
public Dictionary<int, BlockInfo> Assets { get; set; }
public List<uint> SortList { get; set; }
public AssetIndex()
{
Types = new Dictionary<string, string>();
SubAssets = new Dictionary<int, List<SubAssetInfo>>();
Dependencies = new Dictionary<int, List<int>>();
PreloadBlocks = new List<uint>();
PreloadShaderBlocks = new List<uint>();
Assets = new Dictionary<int, BlockInfo>();
SortList = new List<uint>();
}
}
}

View File

@@ -387,6 +387,11 @@ namespace AssetStudio
ObjectsDic.Add(obj.m_PathID, obj);
}
public bool IsLoaded(ObjectInfo objInfo)
{
return ObjectsDic.TryGetValue(objInfo.m_PathID, out var obj) && obj.type == (ClassIDType)objInfo.classID;
}
private static int DecodeClassID(int value)
{
var bytes = BitConverter.GetBytes(value);

View File

@@ -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>

View File

@@ -22,7 +22,7 @@
</ItemGroup>
<ItemGroup>
<Compile Update="Options.cs">
<Compile Update="Settings.cs">
<DesignTimeSharedInput>True</DesignTimeSharedInput>
<AutoGen>True</AutoGen>
</Compile>

View 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)
};
}
}

View File

@@ -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:

View File

@@ -1,46 +1,17 @@
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)
{
var rootCommand = RegisterOptions();
rootCommand.Invoke(args);
}
public static void Main(string[] args) => CommandLine.Init(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.IncludeAnimators,
optionsBinder.SkipMiHoYoBinData,
optionsBinder.Key,
optionsBinder.AIFile,
optionsBinder.Input,
optionsBinder.Output
};
rootCommand.SetHandler((Options o) =>
public static void Run(Options o)
{
try
{
@@ -57,13 +28,39 @@ namespace AssetStudioCLI
Logger.Default = new ConsoleLogger();
assetsManager.Silent = o.Silent;
assetsManager.Game = game;
MiHoYoBinData.Exportable = !o.SkipMiHoYoBinData;
if (!o.TypeFilter.IsNullOrEmpty())
{
foreach (var kv in assetsManager.ExportableTypes)
{
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 (o.SkipMiHoYoBinData)
if (!assetsManager.ExportableTypes[ClassIDType.MiHoYoBinData])
{
Logger.Warning("Key is set but IndexObject/MiHoYoBinData is excluded, ignoring key...");
Logger.Warning("Key is set but MiHoYoBinData is skipped, ignoring key...");
}
else
{
@@ -77,11 +74,40 @@ namespace AssetStudioCLI
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(string.Format("Found {0} file(s)", files.Length));
if (o.MapOp.Equals(MapOpType.None))
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)
{
throw new Exception("Unable to build AssetMap with input_path as a file !!");
}
var assets = AssetsHelper.BuildAssetMap(files, game, o.NameFilter, o.ContainerFilter);
if (!o.Output.Exists)
{
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)
@@ -89,188 +115,18 @@ namespace AssetStudioCLI
assetsManager.LoadFiles(file);
if (assetsManager.assetsFileList.Count > 0)
{
BuildAssetData(o.TypeFilter, o.NameFilter, o.ContainerFilter, ref i);
BuildAssetData(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);
}
}
catch (Exception e)
{
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"))
{
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(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 (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;
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)
};
}
}

View File

@@ -24,6 +24,7 @@ namespace AssetStudioCLI.Properties {
}
catch (Exception)
{
Console.WriteLine($"Invalid value at \"{key}\", switching to default value [{defaultValue}] !!");
return defaultValue;
}

View File

@@ -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,7 +219,10 @@ namespace AssetStudioCLI
if (int.TryParse(asset.Container, out var value))
{
var last = unchecked((uint)value);
var path = ResourceIndex.GetAssetPath(last);
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;
@@ -224,67 +233,114 @@ namespace AssetStudioCLI
}
}
}
}
Logger.Info("Updated !!");
}
}
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)
{
ProcessAssetData(asset, 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))
{
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++;
assetItem.Text = "";
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 = true;
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 = true;
exportable = !ModelOnly;
break;
case Mesh _:
case TextAsset _:
case AnimationClip _:
case Font _:
case MovieTexture _:
case Sprite _:
case Material _:
assetItem.Text = ((NamedObject)asset).m_Name;
exportable = true;
exportable = !ModelOnly;
break;
case Animator m_Animator:
if (m_Animator.m_GameObject.TryGet(out var gameObject))
{
assetItem.Text = gameObject.m_Name;
}
exportable = true;
exportable = !ModelOnly;
break;
case MonoBehaviour m_MonoBehaviour:
if (m_MonoBehaviour.m_Name == "" && m_MonoBehaviour.m_Script.TryGet(out var m_Script))
@@ -295,9 +351,7 @@ namespace AssetStudioCLI
{
assetItem.Text = m_MonoBehaviour.m_Name;
}
break;
case PlayerSettings m_PlayerSettings:
productName = m_PlayerSettings.productName;
exportable = !ModelOnly;
break;
case AssetBundle m_AssetBundle:
foreach (var m_Container in m_AssetBundle.m_Container)
@@ -320,7 +374,7 @@ namespace AssetStudioCLI
assetItem.Text = "IndexObject";
break;
case MiHoYoBinData m_MiHoYoBinData:
exportable = MiHoYoBinData.Exportable;
exportable = !ModelOnly;
break;
case ResourceManager m_ResourceManager:
foreach (var m_Container in m_ResourceManager.m_Container)
@@ -330,7 +384,6 @@ namespace AssetStudioCLI
break;
case NamedObject m_NamedObject:
assetItem.Text = m_NamedObject.m_Name;
exportable = true;
break;
}
if (assetItem.Text == "")
@@ -338,47 +391,11 @@ namespace AssetStudioCLI
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)
if (isMatchRegex && exportable)
{
exportableAssets.Add(assetItem);
}
}
}
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}";
}
}
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);
}
}
}
if (Game.Type.IsGISubGroup())
{
UpdateContainers();
}
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);
}
}
}

View File

@@ -85,9 +85,6 @@
<setting name="selectedAssetMapType" serializeAs="String">
<value>0</value>
</setting>
<setting name="exportMiHoYoBinData" serializeAs="String">
<value>True</value>
</setting>
<setting name="collectAnimations" serializeAs="String">
<value>True</value>
</setting>

View File

@@ -93,8 +93,8 @@ namespace AssetStudioGUI
clearConsoleToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
miscToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
assetHelpersToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
cABMapToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
CABMapNameComboBox = new System.Windows.Forms.ToolStripComboBox();
MapToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
MapNameComboBox = new System.Windows.Forms.ToolStripComboBox();
toolStripMenuItem20 = new System.Windows.Forms.ToolStripMenuItem();
toolStripMenuItem21 = new System.Windows.Forms.ToolStripMenuItem();
assetMapToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
@@ -604,37 +604,37 @@ namespace AssetStudioGUI
//
// assetHelpersToolStripMenuItem
//
assetHelpersToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { cABMapToolStripMenuItem, assetMapToolStripMenuItem });
assetHelpersToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { MapToolStripMenuItem, assetMapToolStripMenuItem });
assetHelpersToolStripMenuItem.Name = "assetHelpersToolStripMenuItem";
assetHelpersToolStripMenuItem.Size = new System.Drawing.Size(145, 22);
assetHelpersToolStripMenuItem.Text = "Asset Helpers";
assetHelpersToolStripMenuItem.DropDownOpening += assetHelpersToolStripMenuItem_DropDownOpening;
//
// cABMapToolStripMenuItem
// MapToolStripMenuItem
//
cABMapToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { CABMapNameComboBox, toolStripMenuItem20, toolStripMenuItem21 });
cABMapToolStripMenuItem.Name = "cABMapToolStripMenuItem";
cABMapToolStripMenuItem.Size = new System.Drawing.Size(126, 22);
cABMapToolStripMenuItem.Text = "CABMap";
MapToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { MapNameComboBox, toolStripMenuItem20, toolStripMenuItem21 });
MapToolStripMenuItem.Name = "MapToolStripMenuItem";
MapToolStripMenuItem.Size = new System.Drawing.Size(126, 22);
MapToolStripMenuItem.Text = "Map";
//
// CABMapNameComboBox
// MapNameComboBox
//
CABMapNameComboBox.Name = "CABMapNameComboBox";
CABMapNameComboBox.Size = new System.Drawing.Size(121, 23);
CABMapNameComboBox.ToolTipText = "Enter name of CABMap here";
MapNameComboBox.Name = "MapNameComboBox";
MapNameComboBox.Size = new System.Drawing.Size(121, 23);
MapNameComboBox.ToolTipText = "Enter name of Map here";
//
// toolStripMenuItem20
//
toolStripMenuItem20.Name = "toolStripMenuItem20";
toolStripMenuItem20.Size = new System.Drawing.Size(181, 22);
toolStripMenuItem20.Text = "Build CABMap";
toolStripMenuItem20.Text = "Build Map";
toolStripMenuItem20.Click += toolStripMenuItem20_Click;
//
// toolStripMenuItem21
//
toolStripMenuItem21.Name = "toolStripMenuItem21";
toolStripMenuItem21.Size = new System.Drawing.Size(181, 22);
toolStripMenuItem21.Text = "Clear CABMap";
toolStripMenuItem21.Text = "Clear Map";
toolStripMenuItem21.Click += toolStripMenuItem21_Click;
//
// assetMapToolStripMenuItem
@@ -1362,13 +1362,13 @@ namespace AssetStudioGUI
private System.Windows.Forms.ToolStripMenuItem assetHelpersToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem toolStripMenuItem20;
private System.Windows.Forms.ToolStripMenuItem toolStripMenuItem22;
private System.Windows.Forms.ToolStripComboBox CABMapNameComboBox;
private System.Windows.Forms.ToolStripComboBox MapNameComboBox;
private System.Windows.Forms.ToolStripSeparator toolStripSeparator5;
private System.Windows.Forms.ToolStripSeparator toolStripSeparator6;
private System.Windows.Forms.ToolStripMenuItem resetToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem abortStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem toolStripMenuItem21;
private System.Windows.Forms.ToolStripMenuItem cABMapToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem MapToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem assetMapToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem loadAIToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem clearConsoleToolStripMenuItem;

View File

@@ -108,7 +108,6 @@ namespace AssetStudioGUI
displayInfo.Checked = Properties.Settings.Default.displayInfo;
enablePreview.Checked = Properties.Settings.Default.enablePreview;
assetsManager.ResolveDependencies = Properties.Settings.Default.enableResolveDependencies;
MiHoYoBinData.Exportable = Properties.Settings.Default.exportMiHoYoBinData;
MiHoYoBinData.Encrypted = Properties.Settings.Default.encrypted;
MiHoYoBinData.Key = Properties.Settings.Default.key;
@@ -155,7 +154,7 @@ namespace AssetStudioGUI
Studio.Game = GameManager.GetGame(Properties.Settings.Default.selectedGame);
Logger.Info($"Target Game type is {Studio.Game.Type}");
CABMapNameComboBox.SelectedIndexChanged += new EventHandler(specifyNameComboBox_SelectedIndexChanged);
MapNameComboBox.SelectedIndexChanged += new EventHandler(specifyNameComboBox_SelectedIndexChanged);
}
private void AssetStudioGUIForm_DragEnter(object sender, DragEventArgs e)
{
@@ -1719,8 +1718,8 @@ namespace AssetStudioGUI
{
if (assetHelpersToolStripMenuItem.Enabled)
{
CABMapNameComboBox.Items.Clear();
CABMapNameComboBox.Items.AddRange(AssetsHelper.GetMaps());
MapNameComboBox.Items.Clear();
MapNameComboBox.Items.AddRange(AssetsHelper.GetMaps());
}
}
@@ -1780,7 +1779,10 @@ namespace AssetStudioGUI
if (int.TryParse(asset.Container, out var value))
{
var last = unchecked((uint)value);
var path = ResourceIndex.GetAssetPath(last);
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;
@@ -1792,6 +1794,7 @@ namespace AssetStudioGUI
}
}
}
}
assetListView.EndUpdate();
Logger.Info("Updated !!");
}
@@ -1841,7 +1844,7 @@ namespace AssetStudioGUI
miscToolStripMenuItem.DropDown.Visible = false;
InvokeUpdate(miscToolStripMenuItem, false);
var name = CABMapNameComboBox.SelectedItem.ToString();
var name = MapNameComboBox.SelectedItem.ToString();
await Task.Run(() => AssetsHelper.LoadMap(name));
ResetForm();
@@ -1856,8 +1859,8 @@ namespace AssetStudioGUI
miscToolStripMenuItem.DropDown.Visible = false;
InvokeUpdate(miscToolStripMenuItem, false);
var input = CABMapNameComboBox.Text;
var selectedText = CABMapNameComboBox.SelectedText;
var input = MapNameComboBox.Text;
var selectedText = MapNameComboBox.SelectedText;
var name = "";
if (!string.IsNullOrEmpty(selectedText))
@@ -1877,14 +1880,14 @@ namespace AssetStudioGUI
}
else
{
Logger.Error("CABMap name is empty, please enter any name in ComboBox above");
Logger.Error("Map name is empty, please enter any name in ComboBox above");
InvokeUpdate(miscToolStripMenuItem, true);
return;
}
if (File.Exists(Path.Combine(AssetsHelper.CABMapName, $"{name}.bin")))
if (File.Exists(Path.Combine(AssetsHelper.MapName, $"{name}.bin")))
{
var acceptOverride = MessageBox.Show("CABMap already exist, Do you want to override it ?", "Warning", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
var acceptOverride = MessageBox.Show("Map already exist, Do you want to override it ?", "Warning", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
if (acceptOverride != DialogResult.Yes)
{
InvokeUpdate(miscToolStripMenuItem, true);
@@ -1899,7 +1902,7 @@ namespace AssetStudioGUI
Logger.Info("Scanning for files");
var files = Directory.GetFiles(openFolderDialog.Folder, "*.*", SearchOption.AllDirectories).ToArray();
Logger.Info($"Found {files.Length} files");
await Task.Run(() => AssetsHelper.BuildCABMap(files, name, openFolderDialog.Folder, Studio.Game));
await Task.Run(() => AssetsHelper.BuildMap(files, name, openFolderDialog.Folder, Studio.Game));
}
InvokeUpdate(miscToolStripMenuItem, true);
}
@@ -1909,22 +1912,22 @@ namespace AssetStudioGUI
miscToolStripMenuItem.DropDown.Visible = false;
InvokeUpdate(miscToolStripMenuItem, false);
var acceptDelete = MessageBox.Show("CABMap will be deleted, this can't be undone, continue ?", "Warning", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
var acceptDelete = MessageBox.Show("Map will be deleted, this can't be undone, continue ?", "Warning", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
if (acceptDelete != DialogResult.Yes)
{
InvokeUpdate(miscToolStripMenuItem, true);
return;
}
var name = CABMapNameComboBox.Text.ToString();
var path = Path.Combine(AssetsHelper.CABMapName, $"{name}.bin");
var name = MapNameComboBox.Text.ToString();
var path = Path.Combine(AssetsHelper.MapName, $"{name}.bin");
if (File.Exists(path))
{
File.Delete(path);
Logger.Info($"{name} deleted successfully !!");
CABMapNameComboBox.SelectedIndexChanged -= new EventHandler(specifyNameComboBox_SelectedIndexChanged);
CABMapNameComboBox.SelectedIndex = 0;
CABMapNameComboBox.SelectedIndexChanged += new EventHandler(specifyNameComboBox_SelectedIndexChanged);
MapNameComboBox.SelectedIndexChanged -= new EventHandler(specifyNameComboBox_SelectedIndexChanged);
MapNameComboBox.SelectedIndex = 0;
MapNameComboBox.SelectedIndexChanged += new EventHandler(specifyNameComboBox_SelectedIndexChanged);
}
InvokeUpdate(miscToolStripMenuItem, true);

View File

@@ -557,9 +557,9 @@ namespace AssetStudioGUI
label7.AutoSize = true;
label7.Location = new System.Drawing.Point(7, 94);
label7.Name = "label7";
label7.Size = new System.Drawing.Size(91, 15);
label7.Size = new System.Drawing.Size(95, 15);
label7.TabIndex = 16;
label7.Text = "Exporable Types";
label7.Text = "Exportable Types";
//
// exportableTypes
//

View File

@@ -347,18 +347,6 @@ namespace AssetStudioGUI.Properties {
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("True")]
public bool exportMiHoYoBinData {
get {
return ((bool)(this["exportMiHoYoBinData"]));
}
set {
this["exportMiHoYoBinData"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("True")]

View File

@@ -83,9 +83,6 @@
<Setting Name="selectedAssetMapType" Type="System.Int32" Scope="User">
<Value Profile="(Default)">0</Value>
</Setting>
<Setting Name="exportMiHoYoBinData" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
</Setting>
<Setting Name="collectAnimations" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
</Setting>

View File

@@ -7,8 +7,8 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Windows.Forms;
using System.Xml;
using System.Xml.Linq;
using static AssetStudio.ImportHelper;
using static AssetStudioGUI.Exporter;
using Object = AssetStudio.Object;
@@ -220,7 +220,10 @@ namespace AssetStudioGUI
if (int.TryParse(asset.Container, out var value))
{
var last = unchecked((uint)value);
var path = ResourceIndex.GetAssetPath(last);
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;
@@ -231,6 +234,7 @@ namespace AssetStudioGUI
}
}
}
}
Logger.Info("Updated !!");
}
}
@@ -239,12 +243,12 @@ namespace AssetStudioGUI
{
StatusStripUpdate("Building asset list...");
int i = 0;
string productName = null;
var objectCount = assetsManager.assetsFileList.Sum(x => x.Objects.Count);
var objectAssetItemDic = new Dictionary<Object, AssetItem>(objectCount);
var mihoyoBinDataNames = new List<(PPtr<Object>, string)>();
var containers = new List<(PPtr<Object>, string)>();
int i = 0;
Progress.Reset();
foreach (var assetsFile in assetsManager.assetsFileList)
{
@@ -340,7 +344,7 @@ namespace AssetStudioGUI
assetItem.Text = "IndexObject";
break;
case MiHoYoBinData m_MiHoYoBinData:
exportable = MiHoYoBinData.Exportable;
exportable = true;
break;
case ResourceManager m_ResourceManager:
foreach (var m_Container in m_ResourceManager.m_Container)
@@ -629,25 +633,30 @@ namespace AssetStudioGUI
{
case ExportListType.XML:
var filename = Path.Combine(savePath, "assets.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.Text),
new XElement("Container", asset.Container),
new XElement("Type", new XAttribute("id", (int)asset.Type), asset.TypeString),
new XElement("PathID", asset.m_PathID),
new XElement("Source", asset.SourceFile.fullName),
new XElement("Size", asset.FullSize)
)
)
)
);
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.TypeString);
writer.WriteEndElement();
writer.WriteElementString("PathID", asset.m_PathID.ToString());
writer.WriteElementString("Source", asset.SourceFile.fullName);
writer.WriteElementString("Size", asset.FullSize.ToString());
writer.WriteEndElement();
}
writer.WriteEndElement();
writer.WriteEndDocument();
}
break;
}