diff --git a/AssetStudio/AssetStudio-x86.csproj b/AssetStudio/AssetStudio-x86.csproj index 0498ab3..e9dc167 100644 --- a/AssetStudio/AssetStudio-x86.csproj +++ b/AssetStudio/AssetStudio-x86.csproj @@ -55,6 +55,10 @@ MinimumRecommendedRules.ruleset + + Library\dnlib.dll + False + False Library\OpenTK.dll @@ -158,6 +162,7 @@ + diff --git a/AssetStudio/AssetStudio.csproj b/AssetStudio/AssetStudio.csproj index dac62fc..839e589 100644 --- a/AssetStudio/AssetStudio.csproj +++ b/AssetStudio/AssetStudio.csproj @@ -55,6 +55,10 @@ OnBuildSuccess + + Library\dnlib.dll + False + False Library\OpenTK.dll @@ -146,6 +150,7 @@ + Code diff --git a/AssetStudio/AssetStudioForm.cs b/AssetStudio/AssetStudioForm.cs index 49f405b..9eecd70 100644 --- a/AssetStudio/AssetStudioForm.cs +++ b/AssetStudio/AssetStudioForm.cs @@ -778,8 +778,15 @@ namespace AssetStudio } case ClassIDReference.MonoBehaviour: { - MonoBehaviour m_MonoBehaviour = new MonoBehaviour(asset, true); - textPreviewBox.Text = m_MonoBehaviour.serializedText; + var m_MonoBehaviour = new MonoBehaviour(asset); + if (asset.Type1 != asset.Type2 && asset.sourceFile.ClassStructures.ContainsKey(asset.Type1)) + { + textPreviewBox.Text = asset.GetClassString(); + } + else + { + textPreviewBox.Text = GetScriptString(asset); + } textPreviewBox.Visible = true; break; @@ -1685,6 +1692,9 @@ namespace AssetStudio } FMODreset(); + + moduleLoaded = false; + LoadedModuleDic.Clear(); } private void assetListView_MouseClick(object sender, MouseEventArgs e) diff --git a/AssetStudio/Classes/MonoBehaviour.cs b/AssetStudio/Classes/MonoBehaviour.cs index 7b89ec7..ab3b3e1 100644 --- a/AssetStudio/Classes/MonoBehaviour.cs +++ b/AssetStudio/Classes/MonoBehaviour.cs @@ -7,38 +7,21 @@ namespace AssetStudio { class MonoBehaviour { - public string serializedText; + public PPtr m_GameObject; + public byte m_Enabled; + public PPtr m_Script; + public string m_Name; - public MonoBehaviour(AssetPreloadData preloadData, bool readSwitch) + public MonoBehaviour(AssetPreloadData preloadData) { var sourceFile = preloadData.sourceFile; var reader = preloadData.InitReader(); - var m_GameObject = sourceFile.ReadPPtr(); - var m_Enabled = reader.ReadByte(); + m_GameObject = sourceFile.ReadPPtr(); + m_Enabled = reader.ReadByte(); reader.AlignStream(4); - var m_Script = sourceFile.ReadPPtr(); - var m_Name = reader.ReadAlignedString(); - if (readSwitch) - { - if ((serializedText = preloadData.GetClassString()) == null) - { - var str = "PPtr m_GameObject\r\n"; - str += "\tint m_FileID = " + m_GameObject.m_FileID + "\r\n"; - str += "\tint64 m_PathID = " + m_GameObject.m_PathID + "\r\n"; - str += "UInt8 m_Enabled = " + m_Enabled + "\r\n"; - str += "PPtr m_Script\r\n"; - str += "\tint m_FileID = " + m_Script.m_FileID + "\r\n"; - str += "\tint64 m_PathID = " + m_Script.m_PathID + "\r\n"; - str += "string m_Name = \"" + m_Name + "\"\r\n"; - serializedText = str; - } - } - else - { - preloadData.extension = ".txt"; - preloadData.Text = m_Name; - } + m_Script = sourceFile.ReadPPtr(); + m_Name = reader.ReadAlignedString(); } } } diff --git a/AssetStudio/Classes/MonoScript.cs b/AssetStudio/Classes/MonoScript.cs new file mode 100644 index 0000000..7dac486 --- /dev/null +++ b/AssetStudio/Classes/MonoScript.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace AssetStudio +{ + class MonoScript + { + public string m_Name; + public string m_ClassName; + public string m_Namespace = string.Empty; + public string m_AssemblyName; + + public MonoScript(AssetPreloadData preloadData) + { + var sourceFile = preloadData.sourceFile; + var reader = preloadData.InitReader(); + var version = sourceFile.version; + + m_Name = reader.ReadAlignedString(); + if (version[0] > 3 || (version[0] == 3 && version[1] >= 4)) + { + var m_ExecutionOrder = reader.ReadAlignedString(); + } + if (version[0] < 5) + { + var m_PropertiesHash = reader.ReadUInt32(); + } + else + { + var m_PropertiesHash = reader.ReadBytes(16); + } + if (version[0] < 3) + { + var m_PathName = reader.ReadAlignedString(); + } + m_ClassName = reader.ReadAlignedString(); + if (version[0] >= 3) + { + m_Namespace = reader.ReadAlignedString(); + } + m_AssemblyName = reader.ReadAlignedString(); + if (version[0] < 2018 || (version[0] == 2018 && version[1] < 2)) + { + var m_IsEditorScript = reader.ReadBoolean(); + } + } + } +} diff --git a/AssetStudio/Library/dnlib.dll b/AssetStudio/Library/dnlib.dll new file mode 100644 index 0000000..718b4bf Binary files /dev/null and b/AssetStudio/Library/dnlib.dll differ diff --git a/AssetStudio/OpenFolderDialog.cs b/AssetStudio/OpenFolderDialog.cs index 370d753..d363b1a 100644 --- a/AssetStudio/OpenFolderDialog.cs +++ b/AssetStudio/OpenFolderDialog.cs @@ -8,32 +8,19 @@ namespace AssetStudio { class OpenFolderDialog { - /// - /// Gets/sets folder in which dialog will be open. - /// public string InitialFolder { get; set; } - - /// - /// Gets/sets directory in which dialog will be open if there is no recent directory available. - /// public string DefaultFolder { get; set; } - - /// - /// Gets selected folder. - /// public string Folder { get; private set; } + public string Title { get; set; } - - internal DialogResult ShowDialog(IWin32Window owner) + internal DialogResult ShowDialog(IWin32Window owner = null) { if (Environment.OSVersion.Version.Major >= 6) { return ShowVistaDialog(owner); } - else - { - return ShowLegacyDialog(owner); - } + + return ShowLegacyDialog(owner); } private DialogResult ShowVistaDialog(IWin32Window owner) @@ -42,24 +29,26 @@ namespace AssetStudio frm.GetOptions(out var options); options |= NativeMethods.FOS_PICKFOLDERS | NativeMethods.FOS_FORCEFILESYSTEM | NativeMethods.FOS_NOVALIDATE | NativeMethods.FOS_NOTESTFILECREATE | NativeMethods.FOS_DONTADDTORECENT; frm.SetOptions(options); - if (this.InitialFolder != null) + if (Title != null) + frm.SetTitle(Title); + if (InitialFolder != null) { var riid = new Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"); //IShellItem - if (NativeMethods.SHCreateItemFromParsingName(this.InitialFolder, IntPtr.Zero, ref riid, out var directoryShellItem) == NativeMethods.S_OK) + if (NativeMethods.SHCreateItemFromParsingName(InitialFolder, IntPtr.Zero, ref riid, out var directoryShellItem) == NativeMethods.S_OK) { frm.SetFolder(directoryShellItem); } } - if (this.DefaultFolder != null) + if (DefaultFolder != null) { var riid = new Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"); //IShellItem - if (NativeMethods.SHCreateItemFromParsingName(this.DefaultFolder, IntPtr.Zero, ref riid, out var directoryShellItem) == NativeMethods.S_OK) + if (NativeMethods.SHCreateItemFromParsingName(DefaultFolder, IntPtr.Zero, ref riid, out var directoryShellItem) == NativeMethods.S_OK) { frm.SetDefaultFolder(directoryShellItem); } } - if (frm.Show(owner.Handle) == NativeMethods.S_OK) + if ((owner == null ? frm.Show() : frm.Show(owner.Handle)) == NativeMethods.S_OK) { if (frm.GetResult(out var shellItem) == NativeMethods.S_OK) { @@ -69,7 +58,7 @@ namespace AssetStudio { try { - this.Folder = Marshal.PtrToStringAuto(pszString); + Folder = Marshal.PtrToStringAuto(pszString); return DialogResult.OK; } finally @@ -87,16 +76,17 @@ namespace AssetStudio { using (var frm = new FolderBrowserDialog()) { - if (this.InitialFolder != null) { frm.SelectedPath = this.InitialFolder; } - if (frm.ShowDialog(owner) == DialogResult.OK) + if (InitialFolder != null) { - this.Folder = Path.GetDirectoryName(frm.SelectedPath); + frm.SelectedPath = InitialFolder; + } + if ((owner == null ? frm.ShowDialog() : frm.ShowDialog(owner)) == DialogResult.OK) + { + Folder = Path.GetDirectoryName(frm.SelectedPath); return DialogResult.OK; } - else - { - return DialogResult.Cancel; - } + + return DialogResult.Cancel; } } } diff --git a/AssetStudio/StudioClasses/Exporter.cs b/AssetStudio/StudioClasses/Exporter.cs index 96f2a7c..1ef21dd 100644 --- a/AssetStudio/StudioClasses/Exporter.cs +++ b/AssetStudio/StudioClasses/Exporter.cs @@ -145,11 +145,20 @@ namespace AssetStudio public static bool ExportMonoBehaviour(AssetPreloadData asset, string exportPath) { - var m_MonoBehaviour = new MonoBehaviour(asset, true); - var exportFullName = exportPath + asset.Text + asset.extension; + var exportFullName = exportPath + asset.Text + ".txt"; if (ExportFileExists(exportFullName)) return false; - File.WriteAllText(exportFullName, m_MonoBehaviour.serializedText); + var m_MonoBehaviour = new MonoBehaviour(asset); + string str; + if (asset.Type1 != asset.Type2 && asset.sourceFile.ClassStructures.ContainsKey(asset.Type1)) + { + str = asset.GetClassString(); + } + else + { + str = Studio.GetScriptString(asset); + } + File.WriteAllText(exportFullName, str); return true; } diff --git a/AssetStudio/StudioClasses/Studio.cs b/AssetStudio/StudioClasses/Studio.cs index 8e2bcbf..239b4b3 100644 --- a/AssetStudio/StudioClasses/Studio.cs +++ b/AssetStudio/StudioClasses/Studio.cs @@ -4,8 +4,10 @@ using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; +using System.Text; using System.Threading; using System.Windows.Forms; +using dnlib.DotNet; using static AssetStudio.Exporter; namespace AssetStudio @@ -21,6 +23,8 @@ namespace AssetStudio public static Dictionary> AllClassStructures = new Dictionary>(); public static string mainPath; public static string productName = ""; + public static bool moduleLoaded; + public static Dictionary LoadedModuleDic = new Dictionary(); //UI public static Action SetProgressBarValue; @@ -218,9 +222,17 @@ namespace AssetStudio } case ClassIDReference.MonoBehaviour: { - var m_MonoBehaviour = new MonoBehaviour(asset, false); - if (asset.Type1 != asset.Type2 && assetsFile.ClassStructures.ContainsKey(asset.Type1)) - exportable = true; + var m_MonoBehaviour = new MonoBehaviour(asset); + if (m_MonoBehaviour.m_Name == "" && assetsfileList.TryGetPD(m_MonoBehaviour.m_Script, out var script)) + { + var m_Script = new MonoScript(script); + asset.Text = m_Script.m_ClassName; + } + else + { + asset.Text = m_MonoBehaviour.m_Name; + } + exportable = true; break; } case ClassIDReference.Font: @@ -722,5 +734,257 @@ namespace AssetStudio } } } + + 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 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 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 (assetsfileList.TryGetPD(m_MonoBehaviour.m_Script, out var script)) + { + 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 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 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; + if (typeDef.IsPrimitive) + { + object value = null; + switch (typeDef.Name) + { + 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; + } + reader.AlignStream(4); + sb.AppendLine($"{new string('\t', indent)}{typeDef.Name} {name} = {value}"); + return; + } + if (typeDef.FullName == "System.String") + { + sb.AppendLine($"{new string('\t', indent)}{typeDef.Name} {name} = \"{reader.ReadAlignedString()}\""); + return; + } + if (typeDef.IsEnum) + { + sb.AppendLine($"{new string('\t', indent)}{typeDef.Name} {name} = {reader.ReadUInt32()}"); + return; + } + if (typeSig is ArraySigBase) + { + 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) + { + 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}"); + if (genericInstSig.GenericArguments.Count == 1) //vector + { + for (int i = 0; i < size; i++) + { + sb.AppendLine($"{new string('\t', indent + 2)}[{i}]"); + DumpType(genericInstSig.GenericArguments[0], sb, assetsFile, "data", indent + 2); + } + } + else if (genericInstSig.GenericArguments.Count == 2) //map + { + for (int i = 0; i < size; i++) + { + sb.AppendLine($"{new string('\t', indent + 2)}[{i}]"); + DumpType(genericInstSig.GenericArguments[0], sb, assetsFile, "first", indent + 2); + DumpType(genericInstSig.GenericArguments[1], sb, assetsFile, "second", indent + 2); + } + } + return; + } + if (indent != -1 && typeDef.FullName == "UnityEngine.Object") + { + var pptr = assetsFile.ReadPPtr(); + sb.AppendLine($"{new string('\t', indent)}PPtr<{typeDef.Name}> {name} = {{fileID: {pptr.m_FileID}, pathID: {pptr.m_PathID}}}"); + return; + } + if (indent != -1 && typeDef.BaseType != null && typeDef.BaseType.FullName != "System.Object") + { + var flag = false; + var type = typeDef; + while (true) + { + if (type.BaseType.FullName == "UnityEngine.Object") + { + flag = true; + break; + } + type = type.BaseType.ResolveTypeDefThrow(); + if (type.BaseType == null) + { + break; + } + } + if (flag) + { + var pptr = assetsFile.ReadPPtr(); + sb.AppendLine($"{new string('\t', indent)}PPtr<{typeDef.Name}> {name} = {{fileID: {pptr.m_FileID}, pathID: {pptr.m_PathID}}}"); + return; + } + } + 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); + } + /*if (typeDef.FullName == "UnityEngine.AnimationCurve") //TODO + { + var AnimationCurve = new AnimationCurve(reader, reader.ReadSingle, assetsFile.version); + }*/ + 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); + } + } + else if ((fieldDef.Attributes & FieldAttributes.Static) == 0 && (fieldDef.Attributes & FieldAttributes.InitOnly) == 0) + { + DumpType(fieldDef.FieldType, sb, assetsFile, fieldDef.Name, indent + 1); + } + } + } + } } }