v0.80.60
This commit is contained in:
@@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,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)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
32
AssetStudioGUI/AssetStudioGUIForm.Designer.cs
generated
32
AssetStudioGUI/AssetStudioGUIForm.Designer.cs
generated
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
4
AssetStudioGUI/ExportOptions.Designer.cs
generated
4
AssetStudioGUI/ExportOptions.Designer.cs
generated
@@ -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
|
||||
//
|
||||
|
||||
12
AssetStudioGUI/Properties/Settings.Designer.cs
generated
12
AssetStudioGUI/Properties/Settings.Designer.cs
generated
@@ -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")]
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user