diff --git a/README.md b/README.md index 2880900..9f76c27 100644 --- a/README.md +++ b/README.md @@ -16,3 +16,14 @@ For this .net9 with AoT gets used in combination with a dedicated translation la - Il2CPPInspector-Redux as additional backend for il2cpp - using TPK dumps instead of Unity's reference (especially due to this usage of the reference most likely violating the license) + +## Test Locally +```bash +dotnet restore +# Change your runtime identifier if needed (e.g. linux-x64, osx-x64, win-x64, etc.) +dotnet publish -c Release -r win-x64 +# Build Python bindings +cd bindings\python +# Install +pip install . +``` \ No newline at end of file diff --git a/TypeTreeGeneratorAPI/NativeAPI.cs b/TypeTreeGeneratorAPI/NativeAPI.cs index 6e4431e..bb93691 100644 --- a/TypeTreeGeneratorAPI/NativeAPI.cs +++ b/TypeTreeGeneratorAPI/NativeAPI.cs @@ -208,8 +208,8 @@ public static int TypeTreeGenerator_freeTreeNodesRaw(IntPtr arrAddr, int arrLeng return 0; } - [UnmanagedCallersOnly(EntryPoint = "TypeTreeGenerator_getMonoBehaviorDefinitions")] - public static int TypeTreeGenerator_getMonoBehaviorDefinitions(IntPtr typeTreeGeneratorPtr, IntPtr arrAddrPtr, IntPtr arrLengthPtr) + [UnmanagedCallersOnly(EntryPoint = "TypeTreeGenerator_getClassDefinitions")] + public static int TypeTreeGenerator_getClassDefinitions(IntPtr typeTreeGeneratorPtr, IntPtr arrAddrPtr, IntPtr arrLengthPtr) { if (typeTreeGeneratorPtr == IntPtr.Zero) { @@ -219,7 +219,7 @@ public static int TypeTreeGenerator_getMonoBehaviorDefinitions(IntPtr typeTreeGe { var handle = (TypeTreeGeneratorHandle)GCHandle.FromIntPtr(typeTreeGeneratorPtr).Target!; - var typeNames = handle.Instance.GetMonoBehaviourDefinitions(); + var typeNames = handle.Instance.GetClassDefinitions(); var arrayLength = typeNames.Count; var arrayPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf() * arrayLength * 2); @@ -244,8 +244,8 @@ public static int TypeTreeGenerator_getMonoBehaviorDefinitions(IntPtr typeTreeGe } - [UnmanagedCallersOnly(EntryPoint = "TypeTreeGenerator_freeMonoBehaviorDefinitions")] - public static int TypeTreeGenerator_freeMonoBehaviorDefinitions(IntPtr arrAddr, int arrLength) + [UnmanagedCallersOnly(EntryPoint = "TypeTreeGenerator_freeClassDefinitions")] + public static int TypeTreeGenerator_freeClassDefinitions(IntPtr arrAddr, int arrLength) { if (arrAddr == IntPtr.Zero) { diff --git a/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetRipper/AssetRipper.cs b/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetRipper/AssetRipper.cs index 027d3a6..bc2e721 100644 --- a/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetRipper/AssetRipper.cs +++ b/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetRipper/AssetRipper.cs @@ -40,9 +40,9 @@ public AssetRipperGenerator(string unityVersionString) : base(unityVersionString return typeConverter.FromSerializableType(monoType); } - public override List<(string, string)> GetMonoBehaviourDefinitions() + public override List<(string, string)> GetClassDefinitions() { - var monoBehaviourDefinitions = new List<(string, string)>(); + var typedefs = new List<(string, string)>(); foreach (var assembly in assemblyDefinitions.Values) { if (assembly.Name is null) @@ -53,14 +53,14 @@ public AssetRipperGenerator(string unityVersionString) : base(unityVersionString { foreach (var type in module.TopLevelTypes) { - if (type.IsClass && type.BaseType?.FullName == "UnityEngine.MonoBehaviour") + if (type.IsClass) { - monoBehaviourDefinitions.Add((assembly.Name, type.FullName)); + typedefs.Add((assembly.Name, type.FullName)); } } } } - return monoBehaviourDefinitions; + return typedefs; } public override void LoadDll(Stream dllStream) diff --git a/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetStudio/AssetStudioGenerator.cs b/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetStudio/AssetStudioGenerator.cs index 6661e1c..760b01b 100644 --- a/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetStudio/AssetStudioGenerator.cs +++ b/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetStudio/AssetStudioGenerator.cs @@ -30,20 +30,17 @@ public override void LoadDll(Stream dllStream) moduleDic.Add(assembly.MainModule.Name, assembly.MainModule); } - public override List<(string, string)> GetMonoBehaviourDefinitions() + public override List<(string, string)> GetClassDefinitions() { - var monoBehaviourDefs = new List<(string, string)>(); + var typedefs = new List<(string, string)>(); foreach (var (moduleName, module) in moduleDic) { foreach (var type in module.Types) { - if (IsMonoBehaviour(type)) - { - monoBehaviourDefs.Add((moduleName, type.FullName)); - } + typedefs.Add((moduleName, type.FullName)); } } - return monoBehaviourDefs; + return typedefs; } public override List? GenerateTreeNodes(string assemblyName, string fullName) @@ -58,28 +55,6 @@ public override void LoadDll(Stream dllStream) return null; } - private static bool IsMonoBehaviour(TypeDefinition type) - { - while (type != null) - { - if (type.BaseType == null) - return false; - if (type.BaseType.FullName == "UnityEngine.MonoBehaviour") - return true; - try - { - // Resolve the base type to continue up the hierarchy - type = type.BaseType.Resolve(); - } - catch - { - // If we can't resolve, break out - break; - } - } - return false; - } - private List GenerateTreeNodes(TypeDefinition typeDef) { var converter = new TypeDefinitionConverter(typeDef, serializedTypeHelper, 1); diff --git a/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetStudio/AssetStudioUtility/CecilUtils.cs b/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetStudio/AssetStudioUtility/CecilUtils.cs new file mode 100644 index 0000000..a7e3cb0 --- /dev/null +++ b/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetStudio/AssetStudioUtility/CecilUtils.cs @@ -0,0 +1,65 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections.Generic; +using System.Linq; +using Mono.Cecil; +using Unity.CecilTools.Extensions; + +namespace Unity.CecilTools +{ + public static class CecilUtils + { + public static MethodDefinition FindInTypeExplicitImplementationFor(MethodDefinition interfaceMethod, TypeDefinition typeDefinition) + { + return typeDefinition.Methods.SingleOrDefault(m => m.Overrides.Any(o => o.CheckedResolve().SameAs(interfaceMethod))); + } + + public static IEnumerable AllInterfacesImplementedBy(TypeDefinition typeDefinition) + { + return TypeAndBaseTypesOf(typeDefinition).SelectMany(t => t.Interfaces).Select(i => i.InterfaceType.CheckedResolve()).Distinct(); + } + + public static IEnumerable TypeAndBaseTypesOf(TypeReference typeReference) + { + while (typeReference != null) + { + var typeDefinition = typeReference.CheckedResolve(); + yield return typeDefinition; + typeReference = typeDefinition.BaseType; + } + } + + public static IEnumerable BaseTypesOf(TypeReference typeReference) + { + return TypeAndBaseTypesOf(typeReference).Skip(1); + } + + public static bool IsGenericList(TypeReference type) + { + return type.Name == "List`1" && type.SafeNamespace() == "System.Collections.Generic"; + } + + public static bool IsGenericDictionary(TypeReference type) + { + if (type is GenericInstanceType) + type = ((GenericInstanceType)type).ElementType; + + return type.Name == "Dictionary`2" && type.SafeNamespace() == "System.Collections.Generic"; + } + + public static TypeReference ElementTypeOfCollection(TypeReference type) + { + var at = type as ArrayType; + if (at != null) + return at.ElementType; + + if (IsGenericList(type)) + return ((GenericInstanceType)type).GenericArguments.Single(); + + throw new ArgumentException(); + } + } +} diff --git a/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetStudio/AssetStudioUtility/ElementType.cs b/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetStudio/AssetStudioUtility/ElementType.cs new file mode 100644 index 0000000..a3d858d --- /dev/null +++ b/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetStudio/AssetStudioUtility/ElementType.cs @@ -0,0 +1,21 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using Mono.Cecil; + +namespace Unity.CecilTools +{ + static public class ElementType + { + public static TypeReference For(TypeReference byRefType) + { + var refType = byRefType as TypeSpecification; + if (refType != null) + return refType.ElementType; + + throw new ArgumentException(string.Format("TypeReference isn't a TypeSpecification {0} ", byRefType)); + } + } +} diff --git a/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetStudio/AssetStudioUtility/Extensions/MethodDefinitionExtensions.cs b/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetStudio/AssetStudioUtility/Extensions/MethodDefinitionExtensions.cs new file mode 100644 index 0000000..c99d8e7 --- /dev/null +++ b/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetStudio/AssetStudioUtility/Extensions/MethodDefinitionExtensions.cs @@ -0,0 +1,50 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using Mono.Cecil; + +namespace Unity.CecilTools.Extensions +{ + static class MethodDefinitionExtensions + { + public static bool SameAs(this MethodDefinition self, MethodDefinition other) + { + // FIXME: should be able to compare MethodDefinition references directly + return self.FullName == other.FullName; + } + + public static string PropertyName(this MethodDefinition self) + { + return self.Name.Substring(4); + } + + public static bool IsConversionOperator(this MethodDefinition method) + { + if (!method.IsSpecialName) + return false; + + return method.Name == "op_Implicit" || method.Name == "op_Explicit"; + } + + public static bool IsSimpleSetter(this MethodDefinition original) + { + return original.IsSetter && original.Parameters.Count == 1; + } + + public static bool IsSimpleGetter(this MethodDefinition original) + { + return original.IsGetter && original.Parameters.Count == 0; + } + + public static bool IsSimplePropertyAccessor(this MethodDefinition method) + { + return method.IsSimpleGetter() || method.IsSimpleSetter(); + } + + public static bool IsDefaultConstructor(MethodDefinition m) + { + return m.IsConstructor && !m.IsStatic && m.Parameters.Count == 0; + } + } +} diff --git a/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetStudio/AssetStudioUtility/Extensions/ResolutionExtensions.cs b/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetStudio/AssetStudioUtility/Extensions/ResolutionExtensions.cs new file mode 100644 index 0000000..29d8506 --- /dev/null +++ b/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetStudio/AssetStudioUtility/Extensions/ResolutionExtensions.cs @@ -0,0 +1,36 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using Mono.Cecil; + +namespace Unity.CecilTools.Extensions +{ + public static class ResolutionExtensions + { + public static TypeDefinition CheckedResolve(this TypeReference type) + { + return Resolve(type, reference => reference.Resolve()); + } + + public static MethodDefinition CheckedResolve(this MethodReference method) + { + return Resolve(method, reference => reference.Resolve()); + } + + private static TDefinition Resolve(TReference reference, Func resolve) + where TReference : MemberReference + where TDefinition : class, IMemberDefinition + { + if (reference.Module == null) + throw new ResolutionException(reference); + + var definition = resolve(reference); + if (definition == null) + throw new ResolutionException(reference); + + return definition; + } + } +} diff --git a/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetStudio/AssetStudioUtility/Extensions/TypeDefinitionExtensions.cs b/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetStudio/AssetStudioUtility/Extensions/TypeDefinitionExtensions.cs new file mode 100644 index 0000000..fea4610 --- /dev/null +++ b/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetStudio/AssetStudioUtility/Extensions/TypeDefinitionExtensions.cs @@ -0,0 +1,47 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Mono.Cecil; + +namespace Unity.CecilTools.Extensions +{ + public static class TypeDefinitionExtensions + { + public static bool IsSubclassOf(this TypeDefinition type, string baseTypeName) + { + var baseType = type.BaseType; + if (baseType == null) + return false; + if (baseType.FullName == baseTypeName) + return true; + + var baseTypeDef = baseType.Resolve(); + if (baseTypeDef == null) + return false; + + return IsSubclassOf(baseTypeDef, baseTypeName); + } + + public static bool IsSubclassOf(this TypeDefinition type, params string[] baseTypeNames) + { + var baseType = type.BaseType; + if (baseType == null) + return false; + + for (int i = 0; i < baseTypeNames.Length; i++) + if (baseType.FullName == baseTypeNames[i]) + return true; + + var baseTypeDef = baseType.Resolve(); + if (baseTypeDef == null) + return false; + + return IsSubclassOf(baseTypeDef, baseTypeNames); + } + } +} diff --git a/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetStudio/AssetStudioUtility/Extensions/TypeReferenceExtensions.cs b/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetStudio/AssetStudioUtility/Extensions/TypeReferenceExtensions.cs new file mode 100644 index 0000000..65bf501 --- /dev/null +++ b/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetStudio/AssetStudioUtility/Extensions/TypeReferenceExtensions.cs @@ -0,0 +1,53 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using Mono.Cecil; + +namespace Unity.CecilTools.Extensions +{ + public static class TypeReferenceExtensions + { + public static string SafeNamespace(this TypeReference type) + { + if (type.IsGenericInstance) + return ((GenericInstanceType)type).ElementType.SafeNamespace(); + if (type.IsNested) + return type.DeclaringType.SafeNamespace(); + return type.Namespace; + } + + public static bool IsAssignableTo(this TypeReference typeRef, string typeName) + { + try + { + if (typeRef.IsGenericInstance) + return ElementType.For(typeRef).IsAssignableTo(typeName); + + if (typeRef.FullName == typeName) + return true; + + return typeRef.CheckedResolve().IsSubclassOf(typeName); + } + catch (AssemblyResolutionException) // If we can't resolve our typeref or one of its base types, + { // let's assume it is not assignable to our target type + return false; + } + } + + public static bool IsEnum(this TypeReference type) + { + return type.IsValueType && !type.IsPrimitive && type.CheckedResolve().IsEnum; + } + + public static bool IsStruct(this TypeReference type) + { + return type.IsValueType && !type.IsPrimitive && !type.IsEnum() && !IsSystemDecimal(type); + } + + private static bool IsSystemDecimal(TypeReference type) + { + return type.FullName == "System.Decimal"; + } + } +} diff --git a/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetStudio/AssetStudioUtility/SerilizedTypeHelper.cs b/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetStudio/AssetStudioUtility/SerilizedTypeHelper.cs index 5559161..f07a147 100644 --- a/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetStudio/AssetStudioUtility/SerilizedTypeHelper.cs +++ b/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetStudio/AssetStudioUtility/SerilizedTypeHelper.cs @@ -261,6 +261,14 @@ public void AddSphericalHarmonicsL2(List nodes, string name, int i nodes.Add(new TypeTreeNode("float", "sh[26]", indent + 1, false)); } + public void AddMonoBehaviour(List nodes, int indent) + { + nodes.Add(new TypeTreeNode("MonoBehaviour", "Base", indent, false)); + AddPPtr(nodes, "GameObject", "m_GameObject", indent + 1); + nodes.Add(new TypeTreeNode("UInt8", "m_Enabled", indent + 1, true)); + AddPPtr(nodes, "MonoScript", "m_Script", indent + 1); + AddString(nodes, "m_Name", indent + 1); + } public void AddPropertyName(List nodes, string name, int indent) { nodes.Add(new TypeTreeNode("PropertyName", name, indent, false)); diff --git a/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetStudio/AssetStudioUtility/TypeDefinitionConverter.cs b/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetStudio/AssetStudioUtility/TypeDefinitionConverter.cs index 16b8b59..1cc61af 100644 --- a/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetStudio/AssetStudioUtility/TypeDefinitionConverter.cs +++ b/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetStudio/AssetStudioUtility/TypeDefinitionConverter.cs @@ -1,28 +1,43 @@ using Mono.Cecil; using Unity.CecilTools; +using Unity.CecilTools.Extensions; using Unity.SerializationLogic; namespace TypeTreeGeneratorAPI.TypeTreeGenerator.AssetStudio.AssetStudioUtility { public class TypeDefinitionConverter { + // Prevent SO with recursive definitions (e.g. List of self) + private const int kMaximumConversionDepth = 128; + private readonly TypeDefinition TypeDef; private readonly TypeResolver TypeResolver; private readonly SerializedTypeHelper Helper; private readonly int Indent; + public TypeDefinitionConverter(TypeDefinition typeDef, SerializedTypeHelper helper, int indent) { TypeDef = typeDef; TypeResolver = new TypeResolver(null); Helper = helper; - Indent = indent; + Indent = indent; } - - public List ConvertToTypeTreeNodes() + private IEnumerable FilteredFields() { + return TypeDef.Fields.Where(f => + UnitySerializationLogic.IsSupportedCollection(f.FieldType) || + !f.FieldType.IsGenericInstance || + UnitySerializationLogic.ShouldImplementIDeserializable(f.FieldType.Resolve())); + } + public List ConvertToTypeTreeNodes(int depth = 0) + { + if (depth >= kMaximumConversionDepth) + { + Console.WriteLine($"Maximum conversion depth ({kMaximumConversionDepth}) reached, terminating resolve of {TypeDef.FullName}"); + return new(); + } var nodes = new List(); - var baseTypes = new Stack(); var lastBaseType = TypeDef.BaseType; while (!UnitySerializationLogic.IsNonSerialized(lastBaseType)) @@ -43,7 +58,7 @@ public List ConvertToTypeTreeNodes() { if (!IsHiddenByParentClass(baseTypes, fieldDefinition, TypeDef)) { - nodes.AddRange(ProcessingFieldRef(ResolveGenericFieldReference(fieldDefinition))); + nodes.AddRange(ProcessingFieldRef(ResolveGenericFieldReference(fieldDefinition), depth + 1)); } } @@ -53,11 +68,11 @@ public List ConvertToTypeTreeNodes() TypeResolver.Remove(genericInstanceType); } } - foreach (var field in FilteredFields()) - { - nodes.AddRange(ProcessingFieldRef(field)); + foreach (var f in FilteredFields()) + { + if (WillUnitySerialize(f)) + nodes.AddRange(ProcessingFieldRef(f, depth + 1)); } - return nodes; } @@ -81,22 +96,17 @@ private bool WillUnitySerialize(FieldDefinition fieldDefinition) } catch (Exception ex) { - throw new Exception(string.Format("Exception while processing {0} {1}, error {2}", fieldDefinition.FieldType.FullName, fieldDefinition.FullName, ex.Message)); + // throw new Exception(string.Format("Exception while processing {0} {1}, error {2}", fieldDefinition.FieldType.FullName, fieldDefinition.FullName, ex.Message)); + // Templated types may fail to resolve, just skip them + return false; } } private static bool IsHiddenByParentClass(IEnumerable parentTypes, FieldDefinition fieldDefinition, TypeDefinition processingType) { return processingType.Fields.Any(f => f.Name == fieldDefinition.Name) || parentTypes.Any(t => t.Resolve().Fields.Any(f => f.Name == fieldDefinition.Name)); - } + } - private IEnumerable FilteredFields() - { - return TypeDef.Fields.Where(WillUnitySerialize).Where(f => - UnitySerializationLogic.IsSupportedCollection(f.FieldType) || - !f.FieldType.IsGenericInstance || - UnitySerializationLogic.ShouldImplementIDeserializable(f.FieldType.Resolve())); - } private FieldReference ResolveGenericFieldReference(FieldReference fieldRef) { @@ -119,10 +129,10 @@ private FieldReference ResolveGenericFieldReference(FieldReference fieldRef) return TypeResolver.Resolve(genericInstanceType); } - private List ProcessingFieldRef(FieldReference fieldDef) + private List ProcessingFieldRef(FieldReference fieldDef, int depth) { var typeRef = TypeResolver.Resolve(fieldDef.FieldType); - return TypeRefToTypeTreeNodes(typeRef, fieldDef.Name, Indent, false); + return TypeRefToTypeTreeNodes(typeRef, fieldDef.Name, Indent, false, depth); } private static bool IsStruct(TypeReference typeRef) @@ -156,7 +166,7 @@ private static bool IsSystemString(TypeReference typeRef) return typeRef.FullName == "System.String"; } - private List TypeRefToTypeTreeNodes(TypeReference typeRef, string name, int indent, bool isElement) + private List TypeRefToTypeTreeNodes(TypeReference typeRef, string name, int indent, bool isElement, int depth) { var align = false; @@ -210,6 +220,9 @@ private List TypeRefToTypeTreeNodes(TypeReference typeRef, string case "Single": primitiveName = "float"; break; + case "IntPtr": + primitiveName = "IntPtr"; + break; default: throw new NotSupportedException(); } @@ -232,14 +245,14 @@ private List TypeRefToTypeTreeNodes(TypeReference typeRef, string var elementRef = CecilUtils.ElementTypeOfCollection(typeRef); nodes.Add(new TypeTreeNode(typeRef.Name, name, indent, align)); Helper.AddArray(nodes, indent + 1); - nodes.AddRange(TypeRefToTypeTreeNodes(elementRef, "data", indent + 2, true)); + nodes.AddRange(TypeRefToTypeTreeNodes(elementRef, "data", indent + 2, true, depth + 1)); } else if (typeRef.IsArray) { var elementRef = typeRef.GetElementType(); nodes.Add(new TypeTreeNode(typeRef.Name, name, indent, align)); Helper.AddArray(nodes, indent + 1); - nodes.AddRange(TypeRefToTypeTreeNodes(elementRef, "data", indent + 2, true)); + nodes.AddRange(TypeRefToTypeTreeNodes(elementRef, "data", indent + 2, true, depth + 1)); } else if (UnityEngineTypePredicates.IsUnityEngineObject(typeRef)) { @@ -280,10 +293,10 @@ private List TypeRefToTypeTreeNodes(TypeReference typeRef, string nodes.Add(new TypeTreeNode(typeRef.Name, name, indent, align)); var typeDef = typeRef.Resolve(); var typeDefinitionConverter = new TypeDefinitionConverter(typeDef, Helper, indent + 1); - nodes.AddRange(typeDefinitionConverter.ConvertToTypeTreeNodes()); + nodes.AddRange(typeDefinitionConverter.ConvertToTypeTreeNodes(depth + 1)); } return nodes; } } -} \ No newline at end of file +} diff --git a/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetStudio/AssetStudioUtility/UnityEngineTypePredicates.cs b/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetStudio/AssetStudioUtility/UnityEngineTypePredicates.cs new file mode 100644 index 0000000..b6a0cbf --- /dev/null +++ b/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetStudio/AssetStudioUtility/UnityEngineTypePredicates.cs @@ -0,0 +1,169 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System.Collections.Generic; +using Unity.CecilTools.Extensions; +using Mono.Cecil; + +namespace Unity.SerializationLogic +{ + public class UnityEngineTypePredicates + { + private static readonly HashSet TypesThatShouldHaveHadSerializableAttribute = new HashSet + { + "Vector3", + "Vector2", + "Vector4", + "Rect", + "RectInt", + "Quaternion", + "Matrix4x4", + "Color", + "Color32", + "LayerMask", + "Bounds", + "BoundsInt", + "Vector3Int", + "Vector2Int", + "RenderingLayerMask" + }; + + private const string Gradient = "UnityEngine.Gradient"; + private const string GUIStyle = "UnityEngine.GUIStyle"; + private const string RectOffset = "UnityEngine.RectOffset"; + protected const string UnityEngineObject = "UnityEngine.Object"; + public const string MonoBehaviour = "UnityEngine.MonoBehaviour"; + public const string ScriptableObject = "UnityEngine.ScriptableObject"; + protected const string Matrix4x4 = "UnityEngine.Matrix4x4"; + protected const string Color32 = "UnityEngine.Color32"; + private const string SerializeFieldAttribute = "UnityEngine.SerializeField"; + private const string SerializeReferenceAttribute = "UnityEngine.SerializeReference"; + + private static string[] serializableClasses = new[] + { + "UnityEngine.AnimationCurve", + "UnityEngine.Gradient", + "UnityEngine.GUIStyle", + "UnityEngine.RectOffset" + }; + + private static string[] serializableStructs = new[] + { + // NOTE: assumes all types here are NOT interfaces + "UnityEngine.Color32", + "UnityEngine.Matrix4x4", + "UnityEngine.Rendering.SphericalHarmonicsL2", + "UnityEngine.PropertyName", + }; + + public static bool IsMonoBehaviour(TypeReference type) + { + return IsMonoBehaviour(type.CheckedResolve()); + } + + private static bool IsMonoBehaviour(TypeDefinition typeDefinition) + { + return typeDefinition.IsSubclassOf(MonoBehaviour); + } + + public static bool IsScriptableObject(TypeReference type) + { + return IsScriptableObject(type.CheckedResolve()); + } + + private static bool IsScriptableObject(TypeDefinition temp) + { + return temp.IsSubclassOf(ScriptableObject); + } + + public static bool IsColor32(TypeReference type) + { + return type.IsAssignableTo(Color32); + } + + //Do NOT remove these, cil2as still depends on these in 4.x + public static bool IsMatrix4x4(TypeReference type) + { + return type.IsAssignableTo(Matrix4x4); + } + + public static bool IsGradient(TypeReference type) + { + return type.IsAssignableTo(Gradient); + } + + public static bool IsGUIStyle(TypeReference type) + { + return type.IsAssignableTo(GUIStyle); + } + + public static bool IsRectOffset(TypeReference type) + { + return type.IsAssignableTo(RectOffset); + } + + public static bool IsSerializableUnityClass(TypeReference type) + { + foreach (var unityClasses in serializableClasses) + { + if (type.IsAssignableTo(unityClasses)) + return true; + } + return false; + } + + public static bool IsSerializableUnityStruct(TypeReference type) + { + foreach (var unityStruct in serializableStructs) + { + // NOTE: structs cannot inherit from structs, and can only inherit from interfaces + // since we know all types in serializableStructs are not interfaces, + // we can just do a direct comparison. + if (type.FullName == unityStruct) + return true; + } + + if (type.FullName.IndexOf("UnityEngine.LazyLoadReference`1") == 0) + return true; + + return false; + } + + public static bool IsUnityEngineObject(TypeReference type) + { + //todo: somehow solve this elegantly. CheckedResolve() drops the [] of a type. + if (type.IsArray) + return false; + + if (type.FullName == UnityEngineObject) + return true; + + var typeDefinition = type.Resolve(); + if (typeDefinition == null) + return false; + + return typeDefinition.IsSubclassOf(UnityEngineObject); + } + + public static bool ShouldHaveHadSerializableAttribute(TypeReference type) + { + return IsUnityEngineValueType(type); + } + + public static bool IsUnityEngineValueType(TypeReference type) + { + return type.SafeNamespace() == "UnityEngine" && TypesThatShouldHaveHadSerializableAttribute.Contains(type.Name); + } + + public static bool IsSerializeFieldAttribute(TypeReference attributeType) + { + return attributeType.FullName == SerializeFieldAttribute; + } + + public static bool IsSerializeReferenceAttribute(TypeReference attributeType) + { + return attributeType.FullName == SerializeReferenceAttribute; + } + } +} diff --git a/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetStudio/AssetStudioUtility/UnitySerializationLogic.cs b/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetStudio/AssetStudioUtility/UnitySerializationLogic.cs new file mode 100644 index 0000000..8d34326 --- /dev/null +++ b/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetStudio/AssetStudioUtility/UnitySerializationLogic.cs @@ -0,0 +1,620 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections.Generic; +using System.Linq; +using Mono.Cecil; +using Mono.Collections.Generic; +using Unity.CecilTools; +using Unity.CecilTools.Extensions; + +namespace Unity.SerializationLogic +{ + internal class GenericInstanceHolder + { + public int Count; + public IGenericInstance GenericInstance; + } + + public class TypeResolver + { + private readonly IGenericInstance _typeDefinitionContext; + private readonly IGenericInstance _methodDefinitionContext; + private readonly Dictionary _context = new Dictionary(); + + public TypeResolver() + { + } + + public TypeResolver(IGenericInstance typeDefinitionContext) + { + _typeDefinitionContext = typeDefinitionContext; + } + + public TypeResolver(GenericInstanceMethod methodDefinitionContext) + { + _methodDefinitionContext = methodDefinitionContext; + } + + public TypeResolver(IGenericInstance typeDefinitionContext, IGenericInstance methodDefinitionContext) + { + _typeDefinitionContext = typeDefinitionContext; + _methodDefinitionContext = methodDefinitionContext; + } + + public void Add(GenericInstanceType genericInstanceType) + { + Add(ElementTypeFor(genericInstanceType).FullName, genericInstanceType); + } + + public void Remove(GenericInstanceType genericInstanceType) + { + Remove(genericInstanceType.ElementType.FullName, genericInstanceType); + } + + public void Add(GenericInstanceMethod genericInstanceMethod) + { + Add(ElementTypeFor(genericInstanceMethod).FullName, genericInstanceMethod); + } + + private static MemberReference ElementTypeFor(TypeSpecification genericInstanceType) + { + return genericInstanceType.ElementType; + } + + private static MemberReference ElementTypeFor(MethodSpecification genericInstanceMethod) + { + return genericInstanceMethod.ElementMethod; + } + + public void Remove(GenericInstanceMethod genericInstanceMethod) + { + Remove(genericInstanceMethod.ElementMethod.FullName, genericInstanceMethod); + } + + public TypeReference Resolve(TypeReference typeReference) + { + var genericParameter = typeReference as GenericParameter; + if (genericParameter != null) + { + var resolved = ResolveGenericParameter(genericParameter); + if (genericParameter == resolved) // Resolving failed, return what we have. + return resolved; + + return Resolve(resolved); + } + + var arrayType = typeReference as ArrayType; + if (arrayType != null) + return new ArrayType(Resolve(arrayType.ElementType), arrayType.Rank); + + var pointerType = typeReference as PointerType; + if (pointerType != null) + return new PointerType(Resolve(pointerType.ElementType)); + + var byReferenceType = typeReference as ByReferenceType; + if (byReferenceType != null) + return new ByReferenceType(Resolve(byReferenceType.ElementType)); + + var genericInstanceType = typeReference as GenericInstanceType; + if (genericInstanceType != null) + { + var newGenericInstanceType = new GenericInstanceType(Resolve(genericInstanceType.ElementType)); + foreach (var genericArgument in genericInstanceType.GenericArguments) + newGenericInstanceType.GenericArguments.Add(Resolve(genericArgument)); + return newGenericInstanceType; + } + + var pinnedType = typeReference as PinnedType; + if (pinnedType != null) + return new PinnedType(Resolve(pinnedType.ElementType)); + + var reqModifierType = typeReference as RequiredModifierType; + if (reqModifierType != null) + return Resolve(reqModifierType.ElementType); + + var optModifierType = typeReference as OptionalModifierType; + if (optModifierType != null) + return new OptionalModifierType(Resolve(optModifierType.ModifierType), Resolve(optModifierType.ElementType)); + + var sentinelType = typeReference as SentinelType; + if (sentinelType != null) + return new SentinelType(Resolve(sentinelType.ElementType)); + + var funcPtrType = typeReference as FunctionPointerType; + if (funcPtrType != null) + throw new NotSupportedException("Function pointer types are not supported by the SerializationWeaver"); + + if (typeReference is TypeSpecification) + throw new NotSupportedException(); + + return typeReference; + } + + private TypeReference ResolveGenericParameter(GenericParameter genericParameter) + { + if (genericParameter.Owner == null) + throw new NotSupportedException(); + + var memberReference = genericParameter.Owner as MemberReference; + if (memberReference == null) + throw new NotSupportedException(); + + var key = memberReference.FullName; + if (!_context.ContainsKey(key)) + { + if (genericParameter.Type == GenericParameterType.Type) + { + if (_typeDefinitionContext != null) + return _typeDefinitionContext.GenericArguments[genericParameter.Position]; + + return genericParameter; + } + + if (_methodDefinitionContext != null) + return _methodDefinitionContext.GenericArguments[genericParameter.Position]; + + return genericParameter; + } + + return GenericArgumentAt(key, genericParameter.Position); + } + + private TypeReference GenericArgumentAt(string key, int position) + { + return _context[key].GenericInstance.GenericArguments[position]; + } + + private void Add(string key, IGenericInstance value) + { + GenericInstanceHolder oldValue; + + if (_context.TryGetValue(key, out oldValue)) + { + var memberReference = value as MemberReference; + if (memberReference == null) + throw new NotSupportedException(); + + var storedValue = (MemberReference)oldValue.GenericInstance; + + if (storedValue.FullName != memberReference.FullName) + throw new ArgumentException("Duplicate key!", "key"); + + oldValue.Count++; + return; + } + + _context.Add(key, new GenericInstanceHolder { Count = 1, GenericInstance = value }); + } + + private void Remove(string key, IGenericInstance value) + { + GenericInstanceHolder oldValue; + + if (_context.TryGetValue(key, out oldValue)) + { + var memberReference = value as MemberReference; + if (memberReference == null) + throw new NotSupportedException(); + + var storedValue = (MemberReference)oldValue.GenericInstance; + + if (storedValue.FullName != memberReference.FullName) + throw new ArgumentException("Invalid value!", "value"); + + oldValue.Count--; + if (oldValue.Count == 0) + _context.Remove(key); + + return; + } + + throw new ArgumentException("Invalid key!", "key"); + } + } + + public static class UnitySerializationLogic + { + public static bool WillUnitySerialize(FieldDefinition fieldDefinition) + { + return WillUnitySerialize(fieldDefinition, new TypeResolver(null)); + } + + public static bool WillUnitySerialize(FieldDefinition fieldDefinition, TypeResolver typeResolver) + { + if (fieldDefinition == null) + return false; + + //skip static, const and NotSerialized fields before even checking the type + if (fieldDefinition.IsStatic || IsConst(fieldDefinition) || fieldDefinition.IsNotSerialized || fieldDefinition.IsInitOnly) + return false; + + // The field must have correct visibility/decoration to be serialized. + if (!fieldDefinition.IsPublic && + !ShouldHaveHadAllFieldsPublic(fieldDefinition) && + !HasSerializeFieldAttribute(fieldDefinition) && + !HasSerializeReferenceAttribute(fieldDefinition)) + return false; + + // Don't try to resolve types that come from Windows assembly, + // as serialization weaver will fail to resolve that (due to it being in platform specific SDKs) + if (ShouldNotTryToResolve(fieldDefinition.FieldType)) + return false; + + if (IsFixedBuffer(fieldDefinition)) + return true; + + // Resolving types is more complex and slower than checking their names or attributes, + // thus keep those checks below + var typeReference = typeResolver.Resolve(fieldDefinition.FieldType); + + //the type of the field must be serializable in the first place. + + if (typeReference.MetadataType == MetadataType.String) + return true; + + if (typeReference.IsValueType) + return IsValueTypeSerializable(typeReference); + + if (typeReference is ArrayType || CecilUtils.IsGenericList(typeReference)) + { + if (!HasSerializeReferenceAttribute(fieldDefinition)) + return IsSupportedCollection(typeReference); + } + + + if (!IsReferenceTypeSerializable(typeReference) && !HasSerializeReferenceAttribute(fieldDefinition)) + return false; + + if (IsDelegate(typeReference)) + return false; + + // System types until these point shouldn't be serializable + if (fieldDefinition.FieldType.Scope.Name == "System" || fieldDefinition.FieldType.Scope.Name == "mscorlib") + return false; + + return true; + } + + private static bool IsDelegate(TypeReference typeReference) + { + return typeReference.IsAssignableTo("System.Delegate"); + } + + public static bool ShouldFieldBePPtrRemapped(FieldDefinition fieldDefinition) + { + return ShouldFieldBePPtrRemapped(fieldDefinition, new TypeResolver(null)); + } + + public static bool ShouldFieldBePPtrRemapped(FieldDefinition fieldDefinition, TypeResolver typeResolver) + { + if (!WillUnitySerialize(fieldDefinition, typeResolver)) + return false; + + return CanTypeContainUnityEngineObjectReference(typeResolver.Resolve(fieldDefinition.FieldType)); + } + + private static bool CanTypeContainUnityEngineObjectReference(TypeReference typeReference) + { + if (IsUnityEngineObject(typeReference)) + return true; + + if (typeReference.IsEnum()) + return false; + + if (IsSerializablePrimitive(typeReference)) + return false; + + if (IsSupportedCollection(typeReference)) + return CanTypeContainUnityEngineObjectReference(CecilUtils.ElementTypeOfCollection(typeReference)); + + var definition = typeReference.Resolve(); + if (definition == null) + return false; + + return HasFieldsThatCanContainUnityEngineObjectReferences(definition, new TypeResolver(typeReference as GenericInstanceType)); + } + + private static bool HasFieldsThatCanContainUnityEngineObjectReferences(TypeDefinition definition, TypeResolver typeResolver) + { + return AllFieldsFor(definition, typeResolver).Where(kv => kv.Value.Resolve(kv.Key.FieldType).Resolve() != definition).Any(kv => CanFieldContainUnityEngineObjectReference(definition, kv.Key, kv.Value)); + } + + private static IEnumerable> AllFieldsFor(TypeDefinition definition, TypeResolver typeResolver) + { + var baseType = definition.BaseType; + + if (baseType != null) + { + var genericBaseInstanceType = baseType as GenericInstanceType; + if (genericBaseInstanceType != null) + typeResolver.Add(genericBaseInstanceType); + foreach (var kv in AllFieldsFor(baseType.Resolve(), typeResolver)) + yield return kv; + if (genericBaseInstanceType != null) + typeResolver.Remove(genericBaseInstanceType); + } + + foreach (var fieldDefinition in definition.Fields) + yield return new KeyValuePair(fieldDefinition, typeResolver); + } + + private static bool CanFieldContainUnityEngineObjectReference(TypeReference typeReference, FieldDefinition t, TypeResolver typeResolver) + { + if (typeResolver.Resolve(t.FieldType) == typeReference) + return false; + + if (!WillUnitySerialize(t, typeResolver)) + return false; + + if (UnityEngineTypePredicates.IsUnityEngineValueType(typeReference)) + return false; + + return true; + } + + private static bool IsConst(FieldDefinition fieldDefinition) + { + return fieldDefinition.IsLiteral && !fieldDefinition.IsInitOnly; + } + + public static bool HasSerializeFieldAttribute(FieldDefinition field) + { + //return FieldAttributes(field).Any(UnityEngineTypePredicates.IsSerializeFieldAttribute); + foreach (var attribute in FieldAttributes(field)) + if (UnityEngineTypePredicates.IsSerializeFieldAttribute(attribute)) + return true; + return false; + } + + public static bool HasSerializeReferenceAttribute(FieldDefinition field) + { + foreach (var attribute in FieldAttributes(field)) + if (UnityEngineTypePredicates.IsSerializeReferenceAttribute(attribute)) + return true; + return false; + } + + private static IEnumerable FieldAttributes(FieldDefinition field) + { + return field.CustomAttributes.Select(_ => _.AttributeType); + } + + public static bool ShouldNotTryToResolve(TypeReference typeReference) + { + var typeReferenceScopeName = typeReference.Scope.Name; + if (typeReferenceScopeName == "Windows") + { + return true; + } + + if (typeReferenceScopeName == "mscorlib") + { + var resolved = typeReference.Resolve(); + return resolved == null; + } + + try + { // This will throw an exception if typereference thinks it's referencing a .dll, + // but actually there's .winmd file in the current directory. RRW will fix this + // at a later step, so we will not try to resolve this type. This is OK, as any + // type defined in a winmd cannot be serialized. + typeReference.Resolve(); + } + catch + { + return true; + } + + return false; + } + + private static bool IsFieldTypeSerializable(TypeReference typeReference, FieldDefinition fieldDefinition) + { + return IsTypeSerializable(typeReference) || IsSupportedCollection(typeReference) || IsFixedBuffer(fieldDefinition); + } + + private static bool IsValueTypeSerializable(TypeReference typeReference) + { + if (typeReference.IsPrimitive) + return IsSerializablePrimitive(typeReference); + return UnityEngineTypePredicates.IsSerializableUnityStruct(typeReference) || + typeReference.IsEnum() || + ShouldImplementIDeserializable(typeReference); + } + + private static bool IsReferenceTypeSerializable(TypeReference typeReference) + { + if (typeReference.MetadataType == MetadataType.String) + return IsSerializablePrimitive(typeReference); + + if (IsGenericDictionary(typeReference)) + return false; + + if (IsUnityEngineObject(typeReference) || + ShouldImplementIDeserializable(typeReference) || + UnityEngineTypePredicates.IsSerializableUnityClass(typeReference)) + return true; + + return true; + } + + private static bool IsTypeSerializable(TypeReference typeReference) + { + if (typeReference.MetadataType == MetadataType.String) + return true; + if (typeReference.IsValueType) + return IsValueTypeSerializable(typeReference); + return IsReferenceTypeSerializable(typeReference); + } + + private static bool IsGenericDictionary(TypeReference typeReference) + { + var current = typeReference; + + if (current != null) + { + if (CecilUtils.IsGenericDictionary(current)) + return true; + } + + return false; + } + + public static bool IsFixedBuffer(FieldDefinition fieldDefinition) + { + return GetFixedBufferAttribute(fieldDefinition) != null; + } + + public static CustomAttribute GetFixedBufferAttribute(FieldDefinition fieldDefinition) + { + if (!fieldDefinition.HasCustomAttributes) + return null; + + return fieldDefinition.CustomAttributes.SingleOrDefault(a => a.AttributeType.FullName == "System.Runtime.CompilerServices.FixedBufferAttribute"); + } + + public static int GetFixedBufferLength(FieldDefinition fieldDefinition) + { + var fixedBufferAttribute = GetFixedBufferAttribute(fieldDefinition); + + if (fixedBufferAttribute == null) + throw new ArgumentException(string.Format("Field '{0}' is not a fixed buffer field.", fieldDefinition.FullName)); + + var size = (Int32)fixedBufferAttribute.ConstructorArguments[1].Value; + + return size; + } + + public static int PrimitiveTypeSize(TypeReference type) + { + switch (type.MetadataType) + { + case MetadataType.Boolean: + case MetadataType.Byte: + case MetadataType.SByte: + return 1; + + case MetadataType.Char: + case MetadataType.Int16: + case MetadataType.UInt16: + return 2; + + case MetadataType.Int32: + case MetadataType.UInt32: + case MetadataType.Single: + return 4; + + case MetadataType.Int64: + case MetadataType.UInt64: + case MetadataType.Double: + return 8; + + default: + throw new ArgumentException(string.Format("Unsupported {0}", type.MetadataType)); + } + } + + private static bool IsSerializablePrimitive(TypeReference typeReference) + { + switch (typeReference.MetadataType) + { + case MetadataType.SByte: + case MetadataType.Byte: + case MetadataType.Char: + case MetadataType.Int16: + case MetadataType.UInt16: + case MetadataType.Int64: + case MetadataType.UInt64: + case MetadataType.Int32: + case MetadataType.UInt32: + case MetadataType.Single: + case MetadataType.Double: + case MetadataType.Boolean: + case MetadataType.String: + return true; + } + return false; + } + + public static bool IsSupportedCollection(TypeReference typeReference) + { + if (!(typeReference is ArrayType || CecilUtils.IsGenericList(typeReference))) + return false; + + // We don't support arrays like byte[,] etc + if (typeReference.IsArray && ((ArrayType)typeReference).Rank > 1) + return false; + + try + { + return IsTypeSerializable(CecilUtils.ElementTypeOfCollection(typeReference)); + } catch + { + return false; + } + + } + + private static bool ShouldHaveHadAllFieldsPublic(FieldDefinition field) + { + return UnityEngineTypePredicates.IsUnityEngineValueType(field.DeclaringType); + } + + private static bool IsUnityEngineObject(TypeReference typeReference) + { + return UnityEngineTypePredicates.IsUnityEngineObject(typeReference); + } + + public static bool IsNonSerialized(TypeReference typeDeclaration) + { + if (typeDeclaration == null) + return true; + if (typeDeclaration.HasGenericParameters) + return true; + if (typeDeclaration.MetadataType == MetadataType.Object) + return true; + if (typeDeclaration.IsArray) + return true; + if (typeDeclaration.FullName == UnityEngineTypePredicates.MonoBehaviour) + return true; + if (typeDeclaration.FullName == UnityEngineTypePredicates.ScriptableObject) + return true; + if (typeDeclaration.IsEnum()) + return true; + return false; + } + + public static bool ShouldImplementIDeserializable(TypeReference typeDeclaration) + { + if (typeDeclaration.FullName == "UnityEngine.ExposedReference`1") + return true; + + if (IsNonSerialized(typeDeclaration)) + return false; + + try + { + if (UnityEngineTypePredicates.ShouldHaveHadSerializableAttribute(typeDeclaration)) + return true; + + var resolvedTypeDeclaration = typeDeclaration.CheckedResolve(); + if (resolvedTypeDeclaration.IsValueType) + { + return resolvedTypeDeclaration.IsSerializable && !resolvedTypeDeclaration.CustomAttributes.Any(a => a.AttributeType.FullName.Contains("System.Runtime.CompilerServices.CompilerGenerated")); + } + else + { + return (resolvedTypeDeclaration.IsSerializable && !resolvedTypeDeclaration.CustomAttributes.Any(a => a.AttributeType.FullName.Contains("System.Runtime.CompilerServices.CompilerGenerated"))) || + resolvedTypeDeclaration.IsSubclassOf(UnityEngineTypePredicates.MonoBehaviour, UnityEngineTypePredicates.ScriptableObject); + } + } + catch (Exception) + { + return false; + } + } + } +} diff --git a/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetsTools/AssetsToolsGenerator.cs b/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetsTools/AssetsToolsGenerator.cs index 038451d..839a4dd 100644 --- a/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetsTools/AssetsToolsGenerator.cs +++ b/TypeTreeGeneratorAPI/TypeTreeGenerator/AssetsTools/AssetsToolsGenerator.cs @@ -117,16 +117,16 @@ public override void LoadIl2Cpp(byte[] assemblyData, byte[] metadataData) } #endif - public override List<(string, string)> GetMonoBehaviourDefinitions() + public override List<(string, string)> GetClassDefinitions() { if (monoLoaded) { - return GetMonoBehaviourDefinitions_Mono(); + return GetClassDefinitions_Mono(); } #if ENABLE_IL2CPP else if (LibCpp2IlMain.TheMetadata != null) { - return GetMonoBehaviourDefinitions_Il2Cpp(); + return GetClassDefinitions_Il2Cpp(); } #endif else @@ -136,30 +136,27 @@ public override void LoadIl2Cpp(byte[] assemblyData, byte[] metadataData) } } - public List<(string, string)> GetMonoBehaviourDefinitions_Mono() + public List<(string, string)> GetClassDefinitions_Mono() { - var monoBehaviourDefs = new List<(string, string)>(); + var typedefs = new List<(string, string)>(); foreach (var (asmName, asmDef) in monoCecilGenerator.loadedAssemblies) { foreach (var type in asmDef.MainModule.Types) { - if (IsMonoBehaviour(type)) - { - monoBehaviourDefs.Add((asmName, type.FullName)); - } + typedefs.Add((asmName, type.FullName)); } } - return monoBehaviourDefs; + return typedefs; } #if ENABLE_IL2CPP - public List<(string, string)> GetMonoBehaviourDefinitions_Il2Cpp() + public List<(string, string)> GetClassDefinitions_Il2Cpp() { - var monoBehaviourDefs = new List<(string, string)>(); + var typedefs = new List<(string, string)>(); if (LibCpp2IlMain.TheMetadata == null) { // TODO - err - return monoBehaviourDefs; + return typedefs; } foreach (var asmDef in LibCpp2IlMain.TheMetadata.AssemblyDefinitions) { @@ -169,19 +166,10 @@ public override void LoadIl2Cpp(byte[] assemblyData, byte[] metadataData) { if (asmType.FullName is null) continue; - var baseType = asmType.BaseType?.baseType; - while (baseType != null) - { - if (baseType.FullName == "UnityEngine.MonoBehaviour") - { - monoBehaviourDefs.Add((asmDef.AssemblyName.Name, asmType.FullName)); - break; - } - baseType = baseType.BaseType?.baseType; - } + typedefs.Add((asmDef.AssemblyName.Name, asmType.FullName)); } } - return monoBehaviourDefs; + return typedefs; } #endif diff --git a/TypeTreeGeneratorAPI/TypeTreeGenerator/TypeTreeGenerator.cs b/TypeTreeGeneratorAPI/TypeTreeGenerator/TypeTreeGenerator.cs index c6e9c25..68c1f1c 100644 --- a/TypeTreeGeneratorAPI/TypeTreeGenerator/TypeTreeGenerator.cs +++ b/TypeTreeGeneratorAPI/TypeTreeGenerator/TypeTreeGenerator.cs @@ -14,7 +14,7 @@ public TypeTreeGenerator(string unityVersionString) unityVersion = UnityVersion.Parse(unityVersionString); } - abstract public List<(string, string)> GetMonoBehaviourDefinitions(); + abstract public List<(string, string)> GetClassDefinitions(); abstract public List? GenerateTreeNodes(string assemblyName, string fullName); abstract public List GetAssemblyNames(); diff --git a/TypeTreeGeneratorAPI/TypeTreeGeneratorAPI.csproj b/TypeTreeGeneratorAPI/TypeTreeGeneratorAPI.csproj index 2a1bf7e..f0783b9 100644 --- a/TypeTreeGeneratorAPI/TypeTreeGeneratorAPI.csproj +++ b/TypeTreeGeneratorAPI/TypeTreeGeneratorAPI.csproj @@ -68,27 +68,4 @@ - - - - TypeTreeGenerator\AssetStudio\Unity.CecilTools\CecilUtils.cs - - - TypeTreeGenerator\AssetStudio\Unity.CecilTools\ElementType.cs - - - Backend\AssetStudio\Unity.CecilTools\Extensions\%(RecursiveDir)%(Filename)%(Extension) - - - TypeTreeGenerator\AssetStudio\Unity.SerializationLogic\UnitySerializationLogic.cs - - - TypeTreeGenerator\AssetStudio\Unity.SerializationLogic\UnityEngineTypePredicates.cs - - - - - - - diff --git a/TypeTreeGeneratorAPI/TypeTreeNodeSerializer.cs b/TypeTreeGeneratorAPI/TypeTreeNodeSerializer.cs index 9969dd0..020d1ba 100644 --- a/TypeTreeGeneratorAPI/TypeTreeNodeSerializer.cs +++ b/TypeTreeGeneratorAPI/TypeTreeNodeSerializer.cs @@ -5,19 +5,34 @@ [JsonSerializable(typeof(TypeTreeNode))] [JsonSerializable(typeof(List))] // For collections +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(Dictionary>))] [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1050:Declare types in namespaces", Justification = "")] -public partial class MyJsonContext : JsonSerializerContext +public partial class TypeTreeNodeSerializerJSONContext : JsonSerializerContext +{ + // Source generator will auto-implement this partial class +} +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(TypeTreeNode))] +[JsonSerializable(typeof(List))] // For collections +[JsonSerializable(typeof(Dictionary>))] +[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1050:Declare types in namespaces", Justification = "")] +public partial class TypeTreeNodeSerializerWithIndentJSONContext : JsonSerializerContext { // Source generator will auto-implement this partial class } - namespace TypeTreeGeneratorAPI { public class TypeTreeNodeSerializer { public static string ToJson(List nodes) { - return JsonSerializer.Serialize(nodes, MyJsonContext.Default.ListTypeTreeNode); + return JsonSerializer.Serialize(nodes, TypeTreeNodeSerializerJSONContext.Default.ListTypeTreeNode); + } + + public static string ToJson(Dictionary> nodes) + { + return JsonSerializer.Serialize(nodes, TypeTreeNodeSerializerWithIndentJSONContext.Default.DictionaryStringListTypeTreeNode); } public static (IntPtr, int) ToRaw(List nodes) diff --git a/TypeTreeGeneratorCLI/Program.cs b/TypeTreeGeneratorCLI/Program.cs index 21baffb..978323d 100644 --- a/TypeTreeGeneratorCLI/Program.cs +++ b/TypeTreeGeneratorCLI/Program.cs @@ -1,7 +1,9 @@ -using System.CommandLine; +using AssetsTools.NET; +using System.CommandLine; using TypeTreeGeneratorAPI; using TypeTreeGeneratorAPI.TypeTreeGenerator; - +using System.Text.Json.Serialization; +using System.Text.Json; class Program { static async Task Main(string[] args) @@ -15,7 +17,7 @@ static async Task Main(string[] args) var backendOption = new Option( aliases: ["--backend", "-b"], description: "The backend to use (AssetStudio, AssetsTools, AssetRipper)", - getDefaultValue: () => "AssetsTools" + getDefaultValue: () => "AssetStudio" ); @@ -34,14 +36,21 @@ static async Task Main(string[] args) description: "The path to an il2cpp metadata file (global-metadata.dat)" ); + var outputJsonFile = new Option( + aliases: ["--output", "-o"], + description: "The path to output the JSON file (if not specified, outputs to console)", + getDefaultValue: () => null + ); + var rootCommand = new RootCommand("TypeTreeGeneratorAPI"); rootCommand.AddOption(unityVersionOption); rootCommand.AddOption(backendOption); rootCommand.AddOption(monoDirectoryOption); rootCommand.AddOption(il2cppAssemblyPathOption); rootCommand.AddOption(il2cppMetadataPathOption); + rootCommand.AddOption(outputJsonFile); - rootCommand.SetHandler((unityVersion, backend, monoDirectory, il2cppAssembly, il2CppMetadata) => + rootCommand.SetHandler((unityVersion, backend, monoDirectory, il2cppAssembly, il2CppMetadata, outputJson) => { var handle = new TypeTreeGeneratorHandle(backend, unityVersion); if (monoDirectory is not null) @@ -58,18 +67,32 @@ static async Task Main(string[] args) var metadata = File.ReadAllBytes(il2CppMetadata); handle.Instance.LoadIl2Cpp(assembly, metadata); } - - foreach (var (assemblyName, fullName) in handle.Instance.GetMonoBehaviourDefinitions()) + if (outputJson == null) { - var nodes = handle.Instance.GenerateTreeNodes(assemblyName, fullName)!; - if (nodes == null || nodes.Count == 0) - continue; - Console.WriteLine($"{assemblyName} - {fullName}"); - Console.WriteLine(nodes.Count); - Console.WriteLine(TypeTreeNodeSerializer.ToJson(nodes)); + foreach (var (assemblyName, fullName) in handle.Instance.GetClassDefinitions()) + { + var nodes = handle.Instance.GenerateTreeNodes(assemblyName, fullName)!; + if (nodes == null || nodes.Count == 0) + continue; + Console.WriteLine($"{assemblyName} - {fullName}"); + Console.WriteLine(nodes.Count); + Console.WriteLine(TypeTreeNodeSerializer.ToJson(nodes)); + } + } else + { + Dictionary> nodes = new(); + foreach (var (assemblyName, fullName) in handle.Instance.GetClassDefinitions()) + { + var treeNodes = handle.Instance.GenerateTreeNodes(assemblyName, fullName); + if (treeNodes == null || treeNodes.Count == 0) + continue; + nodes[fullName] = treeNodes; + } + File.WriteAllText(outputJson, TypeTreeNodeSerializer.ToJson(nodes)); + Console.WriteLine($"Saved to {outputJson}"); } }, - unityVersionOption, backendOption, monoDirectoryOption, il2cppAssemblyPathOption, il2cppMetadataPathOption); + unityVersionOption, backendOption, monoDirectoryOption, il2cppAssemblyPathOption, il2cppMetadataPathOption, outputJsonFile); return await rootCommand.InvokeAsync(args); } diff --git a/TypeTreeGeneratorCLI/Properties/launchSettings.json b/TypeTreeGeneratorCLI/Properties/launchSettings.json new file mode 100644 index 0000000..1c87c4b --- /dev/null +++ b/TypeTreeGeneratorCLI/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "TypeTreeGeneratorCLI": { + "commandName": "Project", + "commandLineArgs": "-md C:\\Users\\mos9527\\UnityPyTypetreeCodegen\\.temp\\dummy --unity-version 2022.3.21f1 -o dump.json\r\n" + } + } +} \ No newline at end of file diff --git a/UnityCsReference b/UnityCsReference deleted file mode 160000 index 10f8718..0000000 --- a/UnityCsReference +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 10f8718268a7e34844ba7d59792117c28d75a99b diff --git a/bindings/python/TypeTreeGeneratorAPI/TypeTreeGenerator.py b/bindings/python/TypeTreeGeneratorAPI/TypeTreeGenerator.py index a168623..7eae1b1 100644 --- a/bindings/python/TypeTreeGeneratorAPI/TypeTreeGenerator.py +++ b/bindings/python/TypeTreeGeneratorAPI/TypeTreeGenerator.py @@ -83,12 +83,12 @@ def init_dll(asm_path: Optional[str] = None): ctypes.POINTER(TypeTreeNodeNative), ctypes.c_int, ] - dll.TypeTreeGenerator_getMonoBehaviorDefinitions.argtypes = [ + dll.TypeTreeGenerator_getClassDefinitions.argtypes = [ ctypes.c_void_p, ctypes.POINTER(ctypes.POINTER(ctypes.c_char_p)), ctypes.POINTER(ctypes.c_int), ] - dll.TypeTreeGenerator_freeMonoBehaviorDefinitions.argtypes = [ + dll.TypeTreeGenerator_freeClassDefinitions.argtypes = [ ctypes.POINTER(ctypes.c_char_p), ctypes.c_int, ] @@ -163,17 +163,17 @@ def get_nodes(self, assembly: str, fullname: str) -> List[TypeTreeNode]: DLL.TypeTreeGenerator_freeTreeNodesRaw(nodes_ptr, nodes_count) return nodes - def get_monobehavior_definitions(self) -> List[Tuple[str, str]]: + def get_class_definitions(self) -> List[Tuple[str, str]]: names_ptr = ctypes.POINTER(ctypes.c_char_p)() names_cnt = ctypes.c_int() - assert not DLL.TypeTreeGenerator_getMonoBehaviorDefinitions( + assert not DLL.TypeTreeGenerator_getClassDefinitions( self.ptr, ctypes.byref(names_ptr), ctypes.byref(names_cnt), ), "failed to get module exports" ptr_array = ctypes.cast(names_ptr, ctypes.POINTER(ctypes.c_char_p * names_cnt.value)) names = [name.decode("utf-8") for name in ptr_array.contents] - DLL.TypeTreeGenerator_freeMonoBehaviorDefinitions(names_ptr, names_cnt) + DLL.TypeTreeGenerator_freeClassDefinitions(names_ptr, names_cnt) return [(module, fullname) for module, fullname in zip(names[::2], names[1::2])] def get_loaded_dll_names(self) -> List[str]: