AssetStudio/AssetStudio/StudioClasses/Studio.cs

1139 lines
50 KiB
C#
Raw Normal View History

using System;
using System.Collections.Generic;
2018-04-09 20:45:09 +00:00
using System.Diagnostics;
using System.Globalization;
using System.IO;
2018-03-01 12:01:25 +00:00
using System.Linq;
2018-08-04 20:46:27 +00:00
using System.Text;
2018-04-09 20:45:09 +00:00
using System.Threading;
using System.Windows.Forms;
2018-08-04 20:46:27 +00:00
using dnlib.DotNet;
2018-04-09 20:45:09 +00:00
using static AssetStudio.Exporter;
2018-04-02 22:51:22 +00:00
namespace AssetStudio
{
internal static class Studio
{
2017-02-11 20:57:24 +00:00
public static List<AssetsFile> assetsfileList = new List<AssetsFile>(); //loaded files
public static Dictionary<string, int> assetsFileIndexCache = new Dictionary<string, int>();
2018-02-28 19:42:43 +00:00
public static Dictionary<string, EndianBinaryReader> resourceFileReaders = new Dictionary<string, EndianBinaryReader>(); //use for read res files
2017-02-11 20:57:24 +00:00
public static List<AssetPreloadData> exportableAssets = new List<AssetPreloadData>(); //used to hold all assets while the ListView is filtered
2018-07-26 02:00:32 +00:00
private static HashSet<string> assetsNameHash = new HashSet<string>(); //avoid the same name asset
2017-02-11 20:57:24 +00:00
public static List<AssetPreloadData> visibleAssets = new List<AssetPreloadData>(); //used to build the ListView from all or filtered assets
2018-10-16 16:43:34 +00:00
public static Dictionary<string, SortedDictionary<int, TypeTreeItem>> AllTypeMap = new Dictionary<string, SortedDictionary<int, TypeTreeItem>>();
2018-04-21 13:52:15 +00:00
public static string mainPath;
public static string productName = "";
2018-08-04 20:46:27 +00:00
public static bool moduleLoaded;
public static Dictionary<string, ModuleDef> LoadedModuleDic = new Dictionary<string, ModuleDef>();
2018-09-26 17:15:37 +00:00
public static List<GameObjectTreeNode> treeNodeCollection = new List<GameObjectTreeNode>();
public static Dictionary<GameObject, GameObjectTreeNode> treeNodeDictionary = new Dictionary<GameObject, GameObjectTreeNode>();
2017-02-11 20:57:24 +00:00
//UI
public static Action<int> SetProgressBarValue;
public static Action<int> SetProgressBarMaximum;
public static Action ProgressBarPerformStep;
public static Action<string> StatusStripUpdate;
public static Action<int> ProgressBarMaximumAdd;
2018-04-18 04:04:46 +00:00
public enum FileType
{
AssetsFile,
BundleFile,
WebFile
}
2018-07-14 15:46:32 +00:00
public static FileType CheckFileType(Stream stream, out EndianBinaryReader reader)
2018-04-18 04:04:46 +00:00
{
reader = new EndianBinaryReader(stream);
return CheckFileType(reader);
}
2018-04-09 20:45:09 +00:00
2018-04-18 04:04:46 +00:00
public static FileType CheckFileType(string fileName, out EndianBinaryReader reader)
{
reader = new EndianBinaryReader(File.OpenRead(fileName));
return CheckFileType(reader);
}
private static FileType CheckFileType(EndianBinaryReader reader)
{
var signature = reader.ReadStringToNull();
reader.Position = 0;
switch (signature)
{
case "UnityWeb":
case "UnityRaw":
case "\xFA\xFA\xFA\xFA\xFA\xFA\xFA\xFA":
case "UnityFS":
return FileType.BundleFile;
case "UnityWebData1.0":
return FileType.WebFile;
default:
{
var magic = reader.ReadBytes(2);
reader.Position = 0;
if (WebFile.gzipMagic.SequenceEqual(magic))
{
return FileType.WebFile;
}
reader.Position = 0x20;
magic = reader.ReadBytes(6);
reader.Position = 0;
if (WebFile.brotliMagic.SequenceEqual(magic))
{
return FileType.WebFile;
}
return FileType.AssetsFile;
}
}
}
public static void ExtractFile(string[] fileNames)
2018-03-04 18:35:53 +00:00
{
2018-04-09 20:45:09 +00:00
ThreadPool.QueueUserWorkItem(state =>
{
int extractedCount = 0;
2018-04-18 04:04:46 +00:00
foreach (var fileName in fileNames)
2018-04-09 20:45:09 +00:00
{
2018-04-18 04:04:46 +00:00
var type = CheckFileType(fileName, out var reader);
if (type == FileType.BundleFile)
extractedCount += ExtractBundleFile(fileName, reader);
else if (type == FileType.WebFile)
extractedCount += ExtractWebDataFile(fileName, reader);
else
reader.Dispose();
2018-04-09 20:45:09 +00:00
ProgressBarPerformStep();
}
StatusStripUpdate($"Finished extracting {extractedCount} files.");
});
2018-03-04 18:35:53 +00:00
}
2018-04-18 04:04:46 +00:00
private static int ExtractBundleFile(string bundleFileName, EndianBinaryReader reader)
{
2018-07-14 15:46:32 +00:00
StatusStripUpdate($"Decompressing {Path.GetFileName(bundleFileName)} ...");
var bundleFile = new BundleFile(reader, bundleFileName);
2018-04-09 20:45:09 +00:00
reader.Dispose();
if (bundleFile.fileList.Count > 0)
{
2018-04-18 04:04:46 +00:00
var extractPath = bundleFileName + "_unpacked\\";
Directory.CreateDirectory(extractPath);
2018-07-14 15:46:32 +00:00
return ExtractStreamFile(extractPath, bundleFile.fileList);
2018-04-18 04:04:46 +00:00
}
return 0;
}
private static int ExtractWebDataFile(string webFileName, EndianBinaryReader reader)
{
2018-07-14 15:46:32 +00:00
StatusStripUpdate($"Decompressing {Path.GetFileName(webFileName)} ...");
2018-04-18 04:04:46 +00:00
var webFile = new WebFile(reader);
reader.Dispose();
if (webFile.fileList.Count > 0)
{
var extractPath = webFileName + "_unpacked\\";
Directory.CreateDirectory(extractPath);
2018-07-14 15:46:32 +00:00
return ExtractStreamFile(extractPath, webFile.fileList);
2018-04-18 04:04:46 +00:00
}
return 0;
}
2018-07-14 15:46:32 +00:00
private static int ExtractStreamFile(string extractPath, List<StreamFile> fileList)
2018-04-18 04:04:46 +00:00
{
int extractedCount = 0;
2018-07-14 15:46:32 +00:00
foreach (var file in fileList)
2018-04-18 04:04:46 +00:00
{
2018-07-14 15:46:32 +00:00
var filePath = extractPath + file.fileName;
2018-04-18 04:04:46 +00:00
if (!Directory.Exists(extractPath))
{
2018-04-18 04:04:46 +00:00
Directory.CreateDirectory(extractPath);
}
2018-07-14 15:46:32 +00:00
if (!File.Exists(filePath) && file.stream is MemoryStream stream)
2018-04-18 04:04:46 +00:00
{
2018-07-14 15:46:32 +00:00
File.WriteAllBytes(filePath, stream.ToArray());
2018-04-18 04:04:46 +00:00
extractedCount += 1;
}
2018-07-14 15:46:32 +00:00
file.stream.Dispose();
}
return extractedCount;
}
2018-09-26 17:15:37 +00:00
public static void BuildAssetStructures(bool loadAssets, bool displayAll, bool buildHierarchy, bool buildClassStructures, bool displayOriginalName)
{
#region first loop - read asset data & create list
2018-04-21 13:52:15 +00:00
if (loadAssets)
{
2017-02-11 20:57:24 +00:00
SetProgressBarValue(0);
SetProgressBarMaximum(assetsfileList.Sum(x => x.preloadTable.Values.Count));
2018-04-18 00:19:30 +00:00
StatusStripUpdate("Building asset list...");
2017-02-11 20:57:24 +00:00
string fileIDfmt = "D" + assetsfileList.Count.ToString().Length;
2017-02-11 20:57:24 +00:00
for (var i = 0; i < assetsfileList.Count; i++)
{
2017-02-11 20:57:24 +00:00
var assetsFile = assetsfileList[i];
2017-02-11 20:57:24 +00:00
string fileID = i.ToString(fileIDfmt);
AssetBundle ab = null;
foreach (var asset in assetsFile.preloadTable.Values)
{
asset.uniqueID = fileID + asset.uniqueID;
var exportable = false;
2018-03-27 22:29:28 +00:00
switch (asset.Type)
{
2018-10-16 16:43:34 +00:00
case ClassIDType.GameObject:
2017-02-16 07:30:11 +00:00
{
2018-09-26 17:15:37 +00:00
var m_GameObject = new GameObject(asset);
asset.Text = m_GameObject.m_Name;
2017-02-16 07:30:11 +00:00
assetsFile.GameObjectList.Add(asset.m_PathID, m_GameObject);
break;
}
2018-10-16 16:43:34 +00:00
case ClassIDType.Transform:
2017-02-16 07:30:11 +00:00
{
2018-09-26 17:15:37 +00:00
var m_Transform = new Transform(asset);
2017-02-16 07:30:11 +00:00
assetsFile.TransformList.Add(asset.m_PathID, m_Transform);
break;
}
2018-10-16 16:43:34 +00:00
case ClassIDType.RectTransform:
2017-02-16 07:30:11 +00:00
{
2018-09-26 17:15:37 +00:00
var m_Rect = new RectTransform(asset);
assetsFile.TransformList.Add(asset.m_PathID, m_Rect);
2017-02-16 07:30:11 +00:00
break;
}
2018-10-16 16:43:34 +00:00
case ClassIDType.Texture2D:
2018-09-26 17:28:03 +00:00
{
var m_Texture2D = new Texture2D(asset, false);
if (!string.IsNullOrEmpty(m_Texture2D.path))
2018-10-16 17:51:25 +00:00
asset.FullSize = asset.Size + m_Texture2D.size;
2018-10-16 16:43:34 +00:00
goto case ClassIDType.NamedObject;
2018-09-26 17:28:03 +00:00
}
2018-10-16 16:43:34 +00:00
case ClassIDType.AudioClip:
2018-09-26 17:28:03 +00:00
{
var m_AudioClip = new AudioClip(asset, false);
if (!string.IsNullOrEmpty(m_AudioClip.m_Source))
2018-10-16 17:51:25 +00:00
asset.FullSize = asset.Size + m_AudioClip.m_Size;
2018-10-16 16:43:34 +00:00
goto case ClassIDType.NamedObject;
2018-09-26 17:28:03 +00:00
}
2018-10-16 16:43:34 +00:00
case ClassIDType.VideoClip:
2018-09-26 17:28:03 +00:00
{
var m_VideoClip = new VideoClip(asset, false);
if (!string.IsNullOrEmpty(m_VideoClip.m_OriginalPath))
2018-10-16 17:51:25 +00:00
asset.FullSize = asset.Size + (long)m_VideoClip.m_Size;
2018-10-16 16:43:34 +00:00
goto case ClassIDType.NamedObject;
2018-09-26 17:28:03 +00:00
}
2018-10-16 16:43:34 +00:00
case ClassIDType.NamedObject:
case ClassIDType.Mesh:
case ClassIDType.Shader:
case ClassIDType.TextAsset:
case ClassIDType.AnimationClip:
case ClassIDType.Font:
case ClassIDType.MovieTexture:
case ClassIDType.Sprite:
2017-02-16 07:30:11 +00:00
{
2018-09-26 17:15:37 +00:00
var obj = new NamedObject(asset);
asset.Text = obj.m_Name;
2017-02-16 07:30:11 +00:00
exportable = true;
break;
}
2018-10-16 16:43:34 +00:00
case ClassIDType.Avatar:
case ClassIDType.AnimatorController:
case ClassIDType.AnimatorOverrideController:
case ClassIDType.Material:
case ClassIDType.MonoScript:
case ClassIDType.SpriteAtlas:
2017-02-16 07:30:11 +00:00
{
2018-09-26 17:15:37 +00:00
var obj = new NamedObject(asset);
asset.Text = obj.m_Name;
2017-02-16 07:30:11 +00:00
break;
}
2018-10-16 16:43:34 +00:00
case ClassIDType.Animator:
2017-02-16 07:30:11 +00:00
{
exportable = true;
2017-02-16 07:30:11 +00:00
break;
}
2018-10-16 16:43:34 +00:00
case ClassIDType.MonoBehaviour:
2017-02-16 07:30:11 +00:00
{
2018-08-04 20:46:27 +00:00
var m_MonoBehaviour = new MonoBehaviour(asset);
if (m_MonoBehaviour.m_Name == "" && m_MonoBehaviour.m_Script.TryGetPD(out var script))
2018-08-04 20:46:27 +00:00
{
var m_Script = new MonoScript(script);
asset.Text = m_Script.m_ClassName;
}
else
{
asset.Text = m_MonoBehaviour.m_Name;
}
exportable = true;
2017-02-16 07:30:11 +00:00
break;
}
2018-10-16 16:43:34 +00:00
case ClassIDType.PlayerSettings:
2017-02-16 07:30:11 +00:00
{
var plSet = new PlayerSettings(asset);
productName = plSet.productName;
break;
}
2018-10-16 16:43:34 +00:00
case ClassIDType.AssetBundle:
{
ab = new AssetBundle(asset);
2018-09-26 17:15:37 +00:00
asset.Text = ab.m_Name;
2018-04-06 23:51:33 +00:00
break;
}
}
2018-07-26 02:00:32 +00:00
if (asset.Text == "")
{
asset.Text = asset.TypeString + " #" + asset.uniqueID;
}
2018-10-16 17:51:25 +00:00
asset.SubItems.AddRange(new[] { asset.TypeString, asset.FullSize.ToString() });
2018-07-26 02:00:32 +00:00
//处理同名文件
if (!assetsNameHash.Add((asset.TypeString + asset.Text).ToUpper()))
{
asset.Text += " #" + asset.uniqueID;
}
//处理非法文件名
asset.Text = FixFileName(asset.Text);
2018-04-17 23:11:10 +00:00
if (displayAll)
{
exportable = true;
}
if (exportable)
{
assetsFile.exportableAssets.Add(asset);
}
2017-02-11 20:57:24 +00:00
ProgressBarPerformStep();
}
if (displayOriginalName)
{
assetsFile.exportableAssets.ForEach(x =>
{
var replacename = ab?.m_Container.Find(y => y.second.asset.m_PathID == x.m_PathID)?.first;
if (!string.IsNullOrEmpty(replacename))
2017-06-22 00:51:20 +00:00
{
var ex = Path.GetExtension(replacename);
x.Text = !string.IsNullOrEmpty(ex) ? replacename.Replace(ex, "") : replacename;
}
});
}
exportableAssets.AddRange(assetsFile.exportableAssets);
}
2018-05-02 18:41:47 +00:00
visibleAssets = exportableAssets;
2018-07-26 02:00:32 +00:00
assetsNameHash.Clear();
}
#endregion
#region second loop - build tree structure
2018-04-21 13:52:15 +00:00
if (buildHierarchy)
{
2018-04-21 13:52:15 +00:00
var gameObjectCount = assetsfileList.Sum(x => x.GameObjectList.Values.Count);
if (gameObjectCount > 0)
{
2018-04-21 13:52:15 +00:00
SetProgressBarValue(0);
SetProgressBarMaximum(gameObjectCount);
StatusStripUpdate("Building tree structure...");
2018-04-21 13:52:15 +00:00
foreach (var assetsFile in assetsfileList)
{
2018-09-26 17:15:37 +00:00
var fileNode = new GameObjectTreeNode(null); //RootNode
fileNode.Text = assetsFile.fileName;
2018-04-21 13:52:15 +00:00
foreach (var m_GameObject in assetsFile.GameObjectList.Values)
2018-03-01 12:01:25 +00:00
{
2018-04-21 13:52:15 +00:00
foreach (var m_Component in m_GameObject.m_Components)
2018-03-01 12:01:25 +00:00
{
if (m_Component.TryGetPD(out var asset))
2018-03-01 12:01:25 +00:00
{
switch (asset.Type)
2018-03-01 12:01:25 +00:00
{
2018-10-16 16:43:34 +00:00
case ClassIDType.Transform:
{
m_GameObject.m_Transform = m_Component;
break;
}
2018-10-16 16:43:34 +00:00
case ClassIDType.MeshRenderer:
{
m_GameObject.m_MeshRenderer = m_Component;
break;
}
2018-10-16 16:43:34 +00:00
case ClassIDType.MeshFilter:
{
m_GameObject.m_MeshFilter = m_Component;
if (m_Component.TryGetPD(out var assetPreloadData))
2018-04-21 13:52:15 +00:00
{
var m_MeshFilter = new MeshFilter(assetPreloadData);
if (m_MeshFilter.m_Mesh.TryGetPD(out assetPreloadData))
{
assetPreloadData.gameObject = m_GameObject;
}
}
break;
}
2018-10-16 16:43:34 +00:00
case ClassIDType.SkinnedMeshRenderer:
{
m_GameObject.m_SkinnedMeshRenderer = m_Component;
if (m_Component.TryGetPD(out var assetPreloadData))
{
var m_SkinnedMeshRenderer = new SkinnedMeshRenderer(assetPreloadData);
if (m_SkinnedMeshRenderer.m_Mesh.TryGetPD(out assetPreloadData))
{
assetPreloadData.gameObject = m_GameObject;
}
2018-04-21 13:52:15 +00:00
}
break;
}
2018-10-16 16:43:34 +00:00
case ClassIDType.Animator:
{
m_GameObject.m_Animator = m_Component;
asset.Text = m_GameObject.preloadData.Text;
break;
}
2018-03-01 12:01:25 +00:00
}
}
}
2018-04-21 13:52:15 +00:00
var parentNode = fileNode;
if (m_GameObject.m_Transform != null && m_GameObject.m_Transform.TryGetTransform(out var m_Transform))
{
if (m_Transform.m_Father.TryGetTransform(out var m_Father))
{
if (m_Father.m_GameObject.TryGetGameObject(out var parentGameObject))
2018-04-21 13:52:15 +00:00
{
2018-09-26 17:15:37 +00:00
if (!treeNodeDictionary.TryGetValue(parentGameObject, out parentNode))
{
parentNode = new GameObjectTreeNode(parentGameObject);
treeNodeDictionary.Add(parentGameObject, parentNode);
}
2018-04-21 13:52:15 +00:00
}
}
}
2018-09-26 17:15:37 +00:00
if (!treeNodeDictionary.TryGetValue(m_GameObject, out var currentNode))
{
currentNode = new GameObjectTreeNode(m_GameObject);
treeNodeDictionary.Add(m_GameObject, currentNode);
}
parentNode.Nodes.Add(currentNode);
2018-04-21 13:52:15 +00:00
ProgressBarPerformStep();
}
2018-04-21 13:52:15 +00:00
if (fileNode.Nodes.Count > 0)
{
2018-09-26 17:15:37 +00:00
treeNodeCollection.Add(fileNode);
2018-04-21 13:52:15 +00:00
}
}
}
}
#endregion
#region build list of class strucutres
2018-04-21 13:52:15 +00:00
if (buildClassStructures)
{
foreach (var assetsFile in assetsfileList)
{
2018-09-26 21:23:10 +00:00
if (AllTypeMap.TryGetValue(assetsFile.unityVersion, out var curVer))
{
2018-10-16 16:43:34 +00:00
foreach (var type in assetsFile.m_Types.Where(x => x.m_Nodes != null))
{
2018-10-16 16:43:34 +00:00
var key = type.classID;
if (type.m_ScriptTypeIndex >= 0)
{
key = -1 - type.m_ScriptTypeIndex;
}
curVer[key] = new TypeTreeItem(key, type.m_Nodes);
}
}
else
{
2018-10-16 16:43:34 +00:00
var items = new SortedDictionary<int, TypeTreeItem>();
foreach (var type in assetsFile.m_Types.Where(x => x.m_Nodes != null))
{
var key = type.classID;
if (type.m_ScriptTypeIndex >= 0)
{
key = -1 - type.m_ScriptTypeIndex;
}
items.Add(key, new TypeTreeItem(key, type.m_Nodes));
}
AllTypeMap.Add(assetsFile.unityVersion, items);
}
}
}
#endregion
}
2018-03-01 12:01:25 +00:00
public static string FixFileName(string str)
{
2018-03-01 12:01:25 +00:00
if (str.Length >= 260) return Path.GetRandomFileName();
return Path.GetInvalidFileNameChars().Aggregate(str, (current, c) => current.Replace(c, '_'));
}
2018-03-01 12:01:25 +00:00
public static string[] ProcessingSplitFiles(List<string> selectFile)
{
var splitFiles = selectFile.Where(x => x.Contains(".split"))
.Select(x => Path.GetDirectoryName(x) + "\\" + Path.GetFileNameWithoutExtension(x))
.Distinct()
.ToList();
selectFile.RemoveAll(x => x.Contains(".split"));
foreach (var file in splitFiles)
{
if (File.Exists(file))
{
2018-03-01 12:01:25 +00:00
selectFile.Add(file);
}
}
return selectFile.Distinct().ToArray();
}
2018-04-09 20:45:09 +00:00
public static void ExportAssets(string savePath, List<AssetPreloadData> toExportAssets, int assetGroupSelectedIndex, bool openAfterExport)
{
ThreadPool.QueueUserWorkItem(state =>
{
Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
int toExport = toExportAssets.Count;
int exportedCount = 0;
SetProgressBarValue(0);
SetProgressBarMaximum(toExport);
foreach (var asset in toExportAssets)
{
var exportpath = savePath + "\\";
if (assetGroupSelectedIndex == 1)
{
exportpath += Path.GetFileNameWithoutExtension(asset.sourceFile.filePath) + "_export\\";
}
else if (assetGroupSelectedIndex == 0)
{
exportpath = savePath + "\\" + asset.TypeString + "\\";
}
StatusStripUpdate($"Exporting {asset.TypeString}: {asset.Text}");
2018-07-26 02:00:32 +00:00
try
2018-04-09 20:45:09 +00:00
{
2018-07-26 02:00:32 +00:00
switch (asset.Type)
{
2018-10-16 16:43:34 +00:00
case ClassIDType.Texture2D:
2018-07-26 02:00:32 +00:00
if (ExportTexture2D(asset, exportpath, true))
{
exportedCount++;
}
break;
2018-10-16 16:43:34 +00:00
case ClassIDType.AudioClip:
2018-07-26 02:00:32 +00:00
if (ExportAudioClip(asset, exportpath))
{
exportedCount++;
}
break;
2018-10-16 16:43:34 +00:00
case ClassIDType.Shader:
2018-07-26 02:00:32 +00:00
if (ExportShader(asset, exportpath))
{
exportedCount++;
}
break;
2018-10-16 16:43:34 +00:00
case ClassIDType.TextAsset:
2018-07-26 02:00:32 +00:00
if (ExportTextAsset(asset, exportpath))
{
exportedCount++;
}
break;
2018-10-16 16:43:34 +00:00
case ClassIDType.MonoBehaviour:
2018-07-26 02:00:32 +00:00
if (ExportMonoBehaviour(asset, exportpath))
{
exportedCount++;
}
break;
2018-10-16 16:43:34 +00:00
case ClassIDType.Font:
2018-07-26 02:00:32 +00:00
if (ExportFont(asset, exportpath))
{
exportedCount++;
}
break;
2018-10-16 16:43:34 +00:00
case ClassIDType.Mesh:
2018-07-26 02:00:32 +00:00
if (ExportMesh(asset, exportpath))
{
exportedCount++;
}
break;
2018-10-16 16:43:34 +00:00
case ClassIDType.VideoClip:
2018-07-26 02:00:32 +00:00
if (ExportVideoClip(asset, exportpath))
{
exportedCount++;
}
break;
2018-10-16 16:43:34 +00:00
case ClassIDType.MovieTexture:
2018-07-26 02:00:32 +00:00
if (ExportMovieTexture(asset, exportpath))
{
exportedCount++;
}
break;
2018-10-16 16:43:34 +00:00
case ClassIDType.Sprite:
2018-07-26 02:00:32 +00:00
if (ExportSprite(asset, exportpath))
{
exportedCount++;
}
break;
2018-10-16 16:43:34 +00:00
case ClassIDType.Animator:
2018-07-26 02:00:32 +00:00
if (ExportAnimator(asset, exportpath))
{
exportedCount++;
}
break;
2018-10-16 16:43:34 +00:00
case ClassIDType.AnimationClip:
2018-07-26 02:00:32 +00:00
break;
default:
if (ExportRawFile(asset, exportpath))
{
exportedCount++;
}
break;
2018-04-09 20:45:09 +00:00
2018-07-26 02:00:32 +00:00
}
}
catch (Exception ex)
{
MessageBox.Show($"Export {asset.Type}:{asset.Text} error\r\n{ex.Message}\r\n{ex.StackTrace}");
2018-04-09 20:45:09 +00:00
}
ProgressBarPerformStep();
}
var statusText = exportedCount == 0 ? "Nothing exported." : $"Finished exporting {exportedCount} assets.";
if (toExport > exportedCount)
{
statusText += $" {toExport - exportedCount} assets skipped (not extractable or files already exist)";
}
StatusStripUpdate(statusText);
if (openAfterExport && exportedCount > 0)
{
Process.Start(savePath);
}
});
}
2018-04-21 13:52:15 +00:00
public static void ExportSplitObjects(string savePath, TreeNodeCollection nodes, bool isNew = false)
2018-04-17 17:29:18 +00:00
{
ThreadPool.QueueUserWorkItem(state =>
2018-04-17 17:29:18 +00:00
{
2018-09-26 17:15:37 +00:00
foreach (GameObjectTreeNode node in nodes)
2018-04-17 17:29:18 +00:00
{
//遍历一级子节点
2018-09-26 17:15:37 +00:00
foreach (GameObjectTreeNode j in node.Nodes)
2018-04-18 00:19:30 +00:00
{
2018-07-14 21:15:05 +00:00
ProgressBarPerformStep();
//收集所有子节点
var gameObjects = new List<GameObject>();
CollectNode(j, gameObjects);
//跳过一些不需要导出的object
if (gameObjects.All(x => x.m_SkinnedMeshRenderer == null && x.m_MeshFilter == null))
continue;
//处理非法文件名
var filename = FixFileName(j.Text);
//每个文件存放在单独的文件夹
var targetPath = $"{savePath}{filename}\\";
//重名文件处理
for (int i = 1; ; i++)
2018-04-18 00:19:30 +00:00
{
if (Directory.Exists(targetPath))
{
targetPath = $"{savePath}{filename} ({i})\\";
}
else
{
break;
}
2018-04-18 00:19:30 +00:00
}
Directory.CreateDirectory(targetPath);
//导出FBX
StatusStripUpdate($"Exporting {filename}.fbx");
2018-04-21 13:52:15 +00:00
if (isNew)
2018-05-02 18:41:47 +00:00
{
try
{
2018-09-26 17:15:37 +00:00
ExportGameObject(j.gameObject, targetPath);
2018-05-02 18:41:47 +00:00
}
catch (Exception ex)
{
MessageBox.Show($"{ex.Message}\r\n{ex.StackTrace}");
}
}
2018-04-21 13:52:15 +00:00
else
FBXExporter.WriteFBX($"{targetPath}{filename}.fbx", gameObjects);
StatusStripUpdate($"Finished exporting {filename}.fbx");
2018-04-18 00:19:30 +00:00
}
}
});
2018-04-18 00:19:30 +00:00
}
2018-09-26 17:15:37 +00:00
private static void CollectNode(GameObjectTreeNode node, List<GameObject> gameObjects)
2018-04-17 17:29:18 +00:00
{
2018-09-26 17:15:37 +00:00
gameObjects.Add(node.gameObject);
foreach (GameObjectTreeNode i in node.Nodes)
2018-04-17 17:29:18 +00:00
{
CollectNode(i, gameObjects);
}
}
2018-04-09 20:45:09 +00:00
public static void ExportAnimatorWithAnimationClip(AssetPreloadData animator, List<AssetPreloadData> animationList, string exportPath)
{
ThreadPool.QueueUserWorkItem(state =>
{
2018-04-17 23:11:10 +00:00
StatusStripUpdate($"Exporting {animator.Text}");
2018-04-09 20:45:09 +00:00
try
{
2018-04-17 23:11:10 +00:00
ExportAnimator(animator, exportPath, animationList);
StatusStripUpdate($"Finished exporting {animator.Text}");
2018-04-09 20:45:09 +00:00
}
catch (Exception ex)
{
MessageBox.Show($"{ex.Message}\r\n{ex.StackTrace}");
2018-04-17 23:11:10 +00:00
StatusStripUpdate("Error in export");
2018-04-09 20:45:09 +00:00
}
ProgressBarPerformStep();
});
}
2018-04-11 07:52:37 +00:00
public static void ExportObjectsWithAnimationClip(string exportPath, TreeNodeCollection nodes, List<AssetPreloadData> animationList = null)
2018-04-11 07:52:37 +00:00
{
ThreadPool.QueueUserWorkItem(state =>
2018-04-12 19:16:09 +00:00
{
var gameObjects = new List<GameObject>();
GetSelectedParentNode(nodes, gameObjects);
2018-07-24 05:02:08 +00:00
if (gameObjects.Count > 0)
{
2018-07-24 05:02:08 +00:00
SetProgressBarValue(0);
SetProgressBarMaximum(gameObjects.Count);
foreach (var gameObject in gameObjects)
{
2018-09-26 17:15:37 +00:00
StatusStripUpdate($"Exporting {gameObject.m_Name}");
2018-07-24 05:02:08 +00:00
try
{
ExportGameObject(gameObject, exportPath, animationList);
2018-09-26 17:15:37 +00:00
StatusStripUpdate($"Finished exporting {gameObject.m_Name}");
2018-07-24 05:02:08 +00:00
}
catch (Exception ex)
{
MessageBox.Show($"{ex.Message}\r\n{ex.StackTrace}");
StatusStripUpdate("Error in export");
}
ProgressBarPerformStep();
}
2018-07-24 05:02:08 +00:00
}
else
{
StatusStripUpdate("No Object can be exported.");
}
});
2018-04-17 23:11:10 +00:00
}
private static void GetSelectedParentNode(TreeNodeCollection nodes, List<GameObject> gameObjects)
2018-04-17 23:11:10 +00:00
{
2018-09-26 17:15:37 +00:00
foreach (GameObjectTreeNode i in nodes)
2018-04-17 23:11:10 +00:00
{
if (i.Checked)
{
2018-09-26 17:15:37 +00:00
gameObjects.Add(i.gameObject);
2018-04-17 23:11:10 +00:00
}
else
{
GetSelectedParentNode(i.Nodes, gameObjects);
2018-04-17 23:11:10 +00:00
}
2018-04-12 19:16:09 +00:00
}
2018-04-11 07:52:37 +00:00
}
2018-08-04 20:46:27 +00:00
2018-08-16 12:46:23 +00:00
public static float[] QuatToEuler(float[] q)
{
double eax = 0;
double eay = 0;
double eaz = 0;
float qx = q[0];
float qy = q[1];
float qz = q[2];
float qw = q[3];
double[,] M = new double[4, 4];
double Nq = qx * qx + qy * qy + qz * qz + qw * qw;
double s = (Nq > 0.0) ? (2.0 / Nq) : 0.0;
double xs = qx * s, ys = qy * s, zs = qz * s;
double wx = qw * xs, wy = qw * ys, wz = qw * zs;
double xx = qx * xs, xy = qx * ys, xz = qx * zs;
double yy = qy * ys, yz = qy * zs, zz = qz * zs;
M[0, 0] = 1.0 - (yy + zz); M[0, 1] = xy - wz; M[0, 2] = xz + wy;
M[1, 0] = xy + wz; M[1, 1] = 1.0 - (xx + zz); M[1, 2] = yz - wx;
M[2, 0] = xz - wy; M[2, 1] = yz + wx; M[2, 2] = 1.0 - (xx + yy);
M[3, 0] = M[3, 1] = M[3, 2] = M[0, 3] = M[1, 3] = M[2, 3] = 0.0; M[3, 3] = 1.0;
double test = Math.Sqrt(M[0, 0] * M[0, 0] + M[1, 0] * M[1, 0]);
if (test > 16 * 1.19209290E-07F)//FLT_EPSILON
{
eax = Math.Atan2(M[2, 1], M[2, 2]);
eay = Math.Atan2(-M[2, 0], test);
eaz = Math.Atan2(M[1, 0], M[0, 0]);
}
else
{
eax = Math.Atan2(-M[1, 2], M[1, 1]);
eay = Math.Atan2(-M[2, 0], test);
eaz = 0;
}
return new[] { (float)(eax * 180 / Math.PI), (float)(eay * 180 / Math.PI), (float)(eaz * 180 / Math.PI) };
}
2018-08-04 20:46:27 +00:00
public static string GetScriptString(AssetPreloadData assetPreloadData)
{
if (!moduleLoaded)
{
var openFolderDialog = new OpenFolderDialog();
openFolderDialog.Title = "Select Assembly Folder";
if (openFolderDialog.ShowDialog() == DialogResult.OK)
{
var files = Directory.GetFiles(openFolderDialog.Folder, "*.dll");
var moduleContext = new ModuleContext();
var asmResolver = new AssemblyResolver(moduleContext, true);
var resolver = new Resolver(asmResolver);
moduleContext.AssemblyResolver = asmResolver;
moduleContext.Resolver = resolver;
try
{
foreach (var file in files)
{
var module = ModuleDefMD.Load(file, moduleContext);
LoadedModuleDic.Add(Path.GetFileName(file), module);
}
}
catch
{
// ignored
}
}
moduleLoaded = true;
}
var m_MonoBehaviour = new MonoBehaviour(assetPreloadData);
var sb = new StringBuilder();
sb.AppendLine("PPtr<GameObject> m_GameObject");
sb.AppendLine($"\tint m_FileID = {m_MonoBehaviour.m_GameObject.m_FileID}");
sb.AppendLine($"\tint64 m_PathID = {m_MonoBehaviour.m_GameObject.m_PathID}");
sb.AppendLine($"UInt8 m_Enabled = {m_MonoBehaviour.m_Enabled}");
sb.AppendLine("PPtr<MonoScript> m_Script");
sb.AppendLine($"\tint m_FileID = {m_MonoBehaviour.m_Script.m_FileID}");
sb.AppendLine($"\tint64 m_PathID = {m_MonoBehaviour.m_Script.m_PathID}");
sb.AppendLine($"string m_Name = \"{m_MonoBehaviour.m_Name}\"");
if (m_MonoBehaviour.m_Script.TryGetPD(out var script))
2018-08-04 20:46:27 +00:00
{
var m_Script = new MonoScript(script);
if (!LoadedModuleDic.TryGetValue(m_Script.m_AssemblyName, out var module))
{
/*using (var openFileDialog = new OpenFileDialog())
{
openFileDialog.Title = $"Select {m_Script.m_AssemblyName}";
openFileDialog.FileName = m_Script.m_AssemblyName;
openFileDialog.Filter = $"{m_Script.m_AssemblyName}|{m_Script.m_AssemblyName}";
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
var moduleContext = new ModuleContext();
var asmResolver = new AssemblyResolver(moduleContext, true);
var resolver = new Resolver(asmResolver);
moduleContext.AssemblyResolver = asmResolver;
moduleContext.Resolver = resolver;
module = ModuleDefMD.Load(openFileDialog.FileName, moduleContext);
LoadedModule.Add(m_Script.m_AssemblyName, module);
}
else
{
return sb.ToString();
}
}*/
return sb.ToString();
}
var typeDef = module.Assembly.Find(m_Script.m_Namespace != "" ? $"{m_Script.m_Namespace}.{m_Script.m_ClassName}" : m_Script.m_ClassName, false);
if (typeDef != null)
{
try
{
DumpType(typeDef.ToTypeSig(), sb, assetPreloadData.sourceFile, null, -1, true);
}
catch
{
sb = new StringBuilder();
sb.AppendLine("PPtr<GameObject> m_GameObject");
sb.AppendLine($"\tint m_FileID = {m_MonoBehaviour.m_GameObject.m_FileID}");
sb.AppendLine($"\tint64 m_PathID = {m_MonoBehaviour.m_GameObject.m_PathID}");
sb.AppendLine($"UInt8 m_Enabled = {m_MonoBehaviour.m_Enabled}");
sb.AppendLine("PPtr<MonoScript> m_Script");
sb.AppendLine($"\tint m_FileID = {m_MonoBehaviour.m_Script.m_FileID}");
sb.AppendLine($"\tint64 m_PathID = {m_MonoBehaviour.m_Script.m_PathID}");
sb.AppendLine($"string m_Name = \"{m_MonoBehaviour.m_Name}\"");
}
}
}
return sb.ToString();
}
private static void DumpType(TypeSig typeSig, StringBuilder sb, AssetsFile assetsFile, string name, int indent, bool isRoot = false)
{
var typeDef = typeSig.ToTypeDefOrRef().ResolveTypeDefThrow();
var reader = assetsFile.reader;
2018-08-05 12:03:54 +00:00
if (typeSig.IsPrimitive)
2018-08-04 20:46:27 +00:00
{
object value = null;
2018-08-05 12:03:54 +00:00
switch (typeSig.TypeName)
2018-08-04 20:46:27 +00:00
{
case "Boolean":
value = reader.ReadBoolean();
break;
case "Byte":
value = reader.ReadByte();
break;
case "SByte":
value = reader.ReadSByte();
break;
case "Int16":
value = reader.ReadInt16();
break;
case "UInt16":
value = reader.ReadUInt16();
break;
case "Int32":
value = reader.ReadInt32();
break;
case "UInt32":
value = reader.ReadUInt32();
break;
case "Int64":
value = reader.ReadInt64();
break;
case "UInt64":
value = reader.ReadUInt64();
break;
case "Single":
value = reader.ReadSingle();
break;
case "Double":
value = reader.ReadDouble();
break;
2018-08-05 12:03:54 +00:00
case "Char":
value = reader.ReadChar();
break;
2018-08-04 20:46:27 +00:00
}
reader.AlignStream(4);
sb.AppendLine($"{new string('\t', indent)}{typeDef.Name} {name} = {value}");
return;
}
2018-08-05 12:03:54 +00:00
if (typeSig.FullName == "System.String")
2018-08-04 20:46:27 +00:00
{
sb.AppendLine($"{new string('\t', indent)}{typeDef.Name} {name} = \"{reader.ReadAlignedString()}\"");
return;
}
2018-08-05 12:03:54 +00:00
if (typeSig.FullName == "System.Object")
{
return;
}
if (typeDef.IsDelegate)
2018-08-04 20:46:27 +00:00
{
return;
}
if (typeSig is ArraySigBase)
{
2018-08-05 12:03:54 +00:00
if (!typeDef.IsEnum && !IsBaseType(typeDef) && !IsAssignFromUnityObject(typeDef) && !IsEngineType(typeDef) && !typeDef.IsSerializable)
{
return;
}
2018-08-04 20:46:27 +00:00
var size = reader.ReadInt32();
sb.AppendLine($"{new string('\t', indent)}{typeSig.TypeName} {name}");
sb.AppendLine($"{new string('\t', indent + 1)}Array Array");
sb.AppendLine($"{new string('\t', indent + 1)}int size = {size}");
for (int i = 0; i < size; i++)
{
sb.AppendLine($"{new string('\t', indent + 2)}[{i}]");
DumpType(typeDef.ToTypeSig(), sb, assetsFile, "data", indent + 2);
}
return;
}
if (!isRoot && typeSig is GenericInstSig genericInstSig)
{
2018-08-05 12:03:54 +00:00
if (genericInstSig.GenericArguments.Count == 1)
2018-08-04 20:46:27 +00:00
{
2018-08-05 12:03:54 +00:00
var type = genericInstSig.GenericArguments[0].ToTypeDefOrRef().ResolveTypeDefThrow();
if (!type.IsEnum && !IsBaseType(type) && !IsAssignFromUnityObject(type) && !IsEngineType(type) && !type.IsSerializable)
2018-08-04 20:46:27 +00:00
{
2018-08-05 12:03:54 +00:00
return;
2018-08-04 20:46:27 +00:00
}
2018-08-05 12:03:54 +00:00
var size = reader.ReadInt32();
sb.AppendLine($"{new string('\t', indent)}{typeSig.TypeName} {name}");
sb.AppendLine($"{new string('\t', indent + 1)}Array Array");
sb.AppendLine($"{new string('\t', indent + 1)}int size = {size}");
2018-08-04 20:46:27 +00:00
for (int i = 0; i < size; i++)
{
sb.AppendLine($"{new string('\t', indent + 2)}[{i}]");
2018-08-05 12:03:54 +00:00
DumpType(genericInstSig.GenericArguments[0], sb, assetsFile, "data", indent + 2);
2018-08-04 20:46:27 +00:00
}
}
return;
}
2018-08-05 12:03:54 +00:00
if (indent != -1 && IsAssignFromUnityObject(typeDef))
2018-08-04 20:46:27 +00:00
{
var pptr = assetsFile.ReadPPtr();
sb.AppendLine($"{new string('\t', indent)}PPtr<{typeDef.Name}> {name} = {{fileID: {pptr.m_FileID}, pathID: {pptr.m_PathID}}}");
return;
}
2018-08-05 12:03:54 +00:00
if (typeDef.IsEnum)
2018-08-04 20:46:27 +00:00
{
2018-08-05 12:03:54 +00:00
sb.AppendLine($"{new string('\t', indent)}{typeDef.Name} {name} = {reader.ReadUInt32()}");
return;
}
if (indent != -1 && !IsEngineType(typeDef) && !typeDef.IsSerializable)
{
return;
}
if (typeDef.FullName == "UnityEngine.Rect")
{
sb.AppendLine($"{new string('\t', indent)}{typeDef.Name} {name}");
var rect = reader.ReadSingleArray(4);
return;
}
if (typeDef.FullName == "UnityEngine.LayerMask")
{
sb.AppendLine($"{new string('\t', indent)}{typeDef.Name} {name}");
var value = reader.ReadInt32();
return;
}
if (typeDef.FullName == "UnityEngine.AnimationCurve")
{
sb.AppendLine($"{new string('\t', indent)}{typeDef.Name} {name}");
var animationCurve = new AnimationCurve<float>(reader, reader.ReadSingle, assetsFile.version);
return;
}
if (typeDef.FullName == "UnityEngine.Gradient")
{
sb.AppendLine($"{new string('\t', indent)}{typeDef.Name} {name}");
if (assetsFile.version[0] == 5 && assetsFile.version[1] < 5)
reader.Position += 68;
else if (assetsFile.version[0] == 5 && assetsFile.version[1] < 6)
reader.Position += 72;
else
reader.Position += 168;
return;
}
if (typeDef.FullName == "UnityEngine.RectOffset")
{
sb.AppendLine($"{new string('\t', indent)}{typeDef.Name} {name}");
var left = reader.ReadSingle();
var right = reader.ReadSingle();
var top = reader.ReadSingle();
var bottom = reader.ReadSingle();
return;
}
if (typeDef.FullName == "UnityEngine.GUIStyle") //TODO
{
return;
2018-08-04 20:46:27 +00:00
}
if (typeDef.IsClass || typeDef.IsValueType)
{
if (name != null && indent != -1)
{
sb.AppendLine($"{new string('\t', indent)}{typeDef.Name} {name}");
}
if (indent == -1 && typeDef.BaseType.FullName != "UnityEngine.Object")
{
DumpType(typeDef.BaseType.ToTypeSig(), sb, assetsFile, null, indent, true);
}
if (indent != -1 && typeDef.BaseType.FullName != "System.Object")
{
DumpType(typeDef.BaseType.ToTypeSig(), sb, assetsFile, null, indent, true);
}
foreach (var fieldDef in typeDef.Fields)
{
var access = fieldDef.Access & FieldAttributes.FieldAccessMask;
if (access != FieldAttributes.Public)
{
if (fieldDef.CustomAttributes.Any(x => x.TypeFullName.Contains("SerializeField")))
{
DumpType(fieldDef.FieldType, sb, assetsFile, fieldDef.Name, indent + 1);
}
}
2018-08-05 12:03:54 +00:00
else if ((fieldDef.Attributes & FieldAttributes.Static) == 0 && (fieldDef.Attributes & FieldAttributes.InitOnly) == 0 && (fieldDef.Attributes & FieldAttributes.NotSerialized) == 0)
2018-08-04 20:46:27 +00:00
{
DumpType(fieldDef.FieldType, sb, assetsFile, fieldDef.Name, indent + 1);
}
}
}
}
2018-08-05 12:03:54 +00:00
private static bool IsAssignFromUnityObject(TypeDef typeDef)
{
if (typeDef.FullName == "UnityEngine.Object")
{
return true;
}
if (typeDef.BaseType != null)
{
if (typeDef.BaseType.FullName == "UnityEngine.Object")
{
return true;
}
while (true)
{
typeDef = typeDef.BaseType.ResolveTypeDefThrow();
if (typeDef.BaseType == null)
{
break;
}
if (typeDef.BaseType.FullName == "UnityEngine.Object")
{
return true;
}
}
}
return false;
}
private static bool IsBaseType(IFullName typeDef)
{
switch (typeDef.FullName)
{
case "System.Boolean":
case "System.Byte":
case "System.SByte":
case "System.Int16":
case "System.UInt16":
case "System.Int32":
case "System.UInt32":
case "System.Int64":
case "System.UInt64":
case "System.Single":
case "System.Double":
case "System.String":
return true;
default:
return false;
}
}
private static bool IsEngineType(IFullName typeDef)
{
switch (typeDef.FullName)
{
case "UnityEngine.Vector2":
case "UnityEngine.Vector3":
case "UnityEngine.Vector4":
case "UnityEngine.Rect":
case "UnityEngine.Quaternion":
case "UnityEngine.Matrix4x4":
case "UnityEngine.Color":
case "UnityEngine.Color32":
case "UnityEngine.LayerMask":
case "UnityEngine.AnimationCurve":
case "UnityEngine.Gradient":
case "UnityEngine.RectOffset":
case "UnityEngine.GUIStyle":
return true;
default:
return false;
}
}
}
}