Initial Commit
This commit is contained in:
@@ -0,0 +1,382 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using ModestTree;
|
||||
using Zenject.ReflectionBaking.Mono.Cecil;
|
||||
using Zenject.ReflectionBaking.Mono.Collections.Generic;
|
||||
using ICustomAttributeProvider = Zenject.ReflectionBaking.Mono.Cecil.ICustomAttributeProvider;
|
||||
|
||||
namespace Zenject.ReflectionBaking
|
||||
{
|
||||
public static class CecilExtensions
|
||||
{
|
||||
public static Type TryGetActualType(this TypeReference typeRef, Assembly assembly)
|
||||
{
|
||||
var reflectionName = GetReflectionName(typeRef);
|
||||
return assembly.GetType(reflectionName);
|
||||
}
|
||||
|
||||
static string GetReflectionName(TypeReference type)
|
||||
{
|
||||
if (type.IsGenericInstance)
|
||||
{
|
||||
var genericInstance = (GenericInstanceType)type;
|
||||
|
||||
return string.Format(
|
||||
"{0}.{1}[{2}]", genericInstance.Namespace, type.Name,
|
||||
String.Join(",", genericInstance.GenericArguments.Select(p => GetReflectionName(p)).ToArray()));
|
||||
}
|
||||
|
||||
return type.FullName;
|
||||
}
|
||||
|
||||
public static List<TypeDefinition> LookupAllTypes(this ModuleDefinition module)
|
||||
{
|
||||
var allTypes = new List<TypeDefinition>();
|
||||
|
||||
foreach (var type in module.Types)
|
||||
{
|
||||
LookupAllTypesInternal(type, allTypes);
|
||||
}
|
||||
|
||||
return allTypes;
|
||||
}
|
||||
|
||||
static void LookupAllTypesInternal(TypeDefinition type, List<TypeDefinition> buffer)
|
||||
{
|
||||
buffer.Add(type);
|
||||
|
||||
foreach (var nestedType in type.NestedTypes)
|
||||
{
|
||||
LookupAllTypesInternal(nestedType, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
public static TypeReference ImportType<T>(this ModuleDefinition module)
|
||||
{
|
||||
return module.ImportType(typeof(T));
|
||||
}
|
||||
|
||||
public static TypeReference ImportType(this ModuleDefinition module, Type type)
|
||||
{
|
||||
return module.Import(type);
|
||||
}
|
||||
|
||||
public static MethodReference ImportMethod<T>(this ModuleDefinition module, string methodName)
|
||||
{
|
||||
return module.ImportMethod(typeof(T), methodName);
|
||||
}
|
||||
|
||||
public static MethodReference ImportMethod(
|
||||
this ModuleDefinition module, Type type, string methodName)
|
||||
{
|
||||
return module.Import(
|
||||
module.ImportType(type).Resolve().GetMethod(methodName));
|
||||
}
|
||||
|
||||
public static MethodReference ImportMethod<T>(
|
||||
this ModuleDefinition module, string methodName, int numArgs)
|
||||
{
|
||||
return module.ImportMethod(typeof(T), methodName, numArgs);
|
||||
}
|
||||
|
||||
public static MethodReference ImportMethod(
|
||||
this ModuleDefinition module, Type type, string methodName, int numArgs)
|
||||
{
|
||||
return module.Import(
|
||||
module.ImportType(type).Resolve().GetMethod(methodName, numArgs));
|
||||
}
|
||||
|
||||
public static MethodDefinition GetMethod(this TypeDefinition instance, string name)
|
||||
{
|
||||
for (int i = 0; i < instance.Methods.Count; i++)
|
||||
{
|
||||
MethodDefinition methodDef = instance.Methods[i];
|
||||
|
||||
if (string.CompareOrdinal(methodDef.Name, name) == 0)
|
||||
{
|
||||
return methodDef;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static MethodDefinition GetMethod(this TypeDefinition instance, string name, params Type[] parameterTypes)
|
||||
{
|
||||
for (int i = 0; i < instance.Methods.Count; i++)
|
||||
{
|
||||
MethodDefinition methodDefinition = instance.Methods[i];
|
||||
|
||||
if (!string.Equals(methodDefinition.Name, name, StringComparison.Ordinal) ||
|
||||
parameterTypes.Length != methodDefinition.Parameters.Count)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
MethodDefinition result = methodDefinition;
|
||||
for (int x = methodDefinition.Parameters.Count - 1; x >= 0; x--)
|
||||
{
|
||||
ParameterDefinition parameter = methodDefinition.Parameters[x];
|
||||
if (!string.Equals(parameter.ParameterType.Name, parameterTypes[x].Name, StringComparison.Ordinal))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (x == 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static MethodDefinition GetMethod(this TypeDefinition instance, string name, params TypeReference[] parameterTypes)
|
||||
{
|
||||
if (instance.Methods != null)
|
||||
{
|
||||
for (int i = 0; i < instance.Methods.Count; i++)
|
||||
{
|
||||
MethodDefinition methodDefinition = instance.Methods[i];
|
||||
if (string.Equals(methodDefinition.Name, name, StringComparison.Ordinal) // Names Match
|
||||
&& parameterTypes.Length == methodDefinition.Parameters.Count) // The same number of parameters
|
||||
{
|
||||
MethodDefinition result = methodDefinition;
|
||||
for (int x = methodDefinition.Parameters.Count - 1; x >= 0; x--)
|
||||
{
|
||||
ParameterDefinition parameter = methodDefinition.Parameters[x];
|
||||
if (!string.Equals(parameter.ParameterType.Name, parameterTypes[x].Name, StringComparison.Ordinal))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (x == 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static MethodDefinition GetMethod(this TypeDefinition instance, string name, int argCount)
|
||||
{
|
||||
for (int i = 0; i < instance.Methods.Count; i++)
|
||||
{
|
||||
MethodDefinition methodDef = instance.Methods[i];
|
||||
|
||||
if (string.CompareOrdinal(methodDef.Name, name) == 0 && methodDef.Parameters.Count == argCount)
|
||||
{
|
||||
return methodDef;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static PropertyDefinition GetPropertyDefinition(this TypeDefinition instance, string name)
|
||||
{
|
||||
for (int i = 0; i < instance.Properties.Count; i++)
|
||||
{
|
||||
PropertyDefinition preopertyDef = instance.Properties[i];
|
||||
|
||||
// Properties can only have one argument or they are an indexer.
|
||||
if (string.CompareOrdinal(preopertyDef.Name, name) == 0 && preopertyDef.Parameters.Count == 0)
|
||||
{
|
||||
return preopertyDef;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static bool HasCustomAttribute<T>(this ICustomAttributeProvider instance)
|
||||
{
|
||||
if (!instance.HasCustomAttributes)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Collection<CustomAttribute> attributes = instance.CustomAttributes;
|
||||
|
||||
for(int i = 0; i < attributes.Count; i++)
|
||||
{
|
||||
if (attributes[i].AttributeType.FullName.Equals(typeof(T).FullName, StringComparison.Ordinal))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static MethodReference ChangeDeclaringType(
|
||||
this MethodReference methodDef, TypeReference typeRef)
|
||||
{
|
||||
var newMethodRef = new MethodReference(
|
||||
methodDef.Name, methodDef.ReturnType, typeRef);
|
||||
|
||||
newMethodRef.HasThis = methodDef.HasThis;
|
||||
|
||||
foreach (var arg in methodDef.Parameters)
|
||||
{
|
||||
var paramDef = new ParameterDefinition(arg.ParameterType);
|
||||
|
||||
newMethodRef.Parameters.Add(paramDef);
|
||||
}
|
||||
|
||||
return newMethodRef;
|
||||
}
|
||||
|
||||
public static FieldReference ChangeDeclaringType(
|
||||
this FieldReference fieldDef, TypeReference typeRef)
|
||||
{
|
||||
return new FieldReference(
|
||||
fieldDef.Name, fieldDef.FieldType, typeRef);
|
||||
}
|
||||
|
||||
public static CustomAttribute GetCustomAttribute<T>(this ICustomAttributeProvider instance)
|
||||
{
|
||||
if (!instance.HasCustomAttributes)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Collection<CustomAttribute> attributes = instance.CustomAttributes;
|
||||
|
||||
for (int i = 0; i < attributes.Count; i++)
|
||||
{
|
||||
if (attributes[i].AttributeType.FullName.Equals(typeof(T).FullName, StringComparison.Ordinal))
|
||||
{
|
||||
return attributes[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static IEnumerable<TypeReference> GetSpecificBaseTypesAndSelf(
|
||||
this TypeReference specificTypeRef)
|
||||
{
|
||||
yield return specificTypeRef;
|
||||
|
||||
foreach (var ancestor in specificTypeRef.GetSpecificBaseTypesAndSelf())
|
||||
{
|
||||
yield return ancestor;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<TypeReference> GetSpecificBaseTypes(
|
||||
this TypeReference specificTypeRef)
|
||||
{
|
||||
var specificBaseTypeRef = specificTypeRef.TryGetSpecificBaseType();
|
||||
|
||||
if (specificBaseTypeRef != null)
|
||||
{
|
||||
yield return specificBaseTypeRef;
|
||||
|
||||
foreach (var ancestor in GetSpecificBaseTypes(specificBaseTypeRef))
|
||||
{
|
||||
yield return ancestor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<TypeReference> AllNestParentsAndSelf(this TypeReference specificTypeRef)
|
||||
{
|
||||
yield return specificTypeRef;
|
||||
|
||||
foreach (var ancestor in specificTypeRef.AllNestParents())
|
||||
{
|
||||
yield return ancestor;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<TypeReference> AllNestParents(this TypeReference specificTypeRef)
|
||||
{
|
||||
if (specificTypeRef.DeclaringType != null)
|
||||
{
|
||||
yield return specificTypeRef.DeclaringType;
|
||||
|
||||
foreach (var ancestor in specificTypeRef.DeclaringType.AllNestParents())
|
||||
{
|
||||
yield return ancestor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static TypeReference TryResolve(this TypeReference typeRef)
|
||||
{
|
||||
try
|
||||
{
|
||||
return typeRef.Resolve();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static TypeReference TryGetSpecificBaseType(this TypeReference specificTypeRef)
|
||||
{
|
||||
var typeDef = specificTypeRef.Resolve();
|
||||
|
||||
if (typeDef.BaseType == null
|
||||
|| typeDef.BaseType.FullName == "System.Object")
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var specificBaseTypeRef = typeDef.BaseType;
|
||||
|
||||
if (specificBaseTypeRef.ContainsGenericParameter)
|
||||
{
|
||||
var genericArgMap = new Dictionary<string, TypeReference>();
|
||||
|
||||
foreach (var ancestor in specificTypeRef.AllNestParentsAndSelf())
|
||||
{
|
||||
var specificTypeRefGenericInstance = ancestor as GenericInstanceType;
|
||||
|
||||
if (specificTypeRefGenericInstance != null)
|
||||
{
|
||||
for (int i = 0; i < typeDef.GenericParameters.Count; i++)
|
||||
{
|
||||
genericArgMap[typeDef.GenericParameters[i].Name] = specificTypeRefGenericInstance.GenericArguments[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
specificBaseTypeRef = FillInGenericParameters(specificBaseTypeRef, genericArgMap);
|
||||
}
|
||||
|
||||
return specificBaseTypeRef;
|
||||
}
|
||||
|
||||
public static TypeReference FillInGenericParameters(
|
||||
TypeReference type, Dictionary<string, TypeReference> genericArgMap)
|
||||
{
|
||||
var genericType = type as GenericInstanceType;
|
||||
Assert.IsNotNull(genericType);
|
||||
|
||||
var genericTypeClone = new GenericInstanceType(type.Resolve());
|
||||
|
||||
for (int i = 0; i < genericType.GenericArguments.Count; i++)
|
||||
{
|
||||
var arg = genericType.GenericArguments[i];
|
||||
|
||||
if (arg.IsGenericParameter)
|
||||
{
|
||||
Assert.That(genericArgMap.ContainsKey(arg.Name), "Could not find key '{0}' for type '{1}'", arg.Name, type.FullName);
|
||||
|
||||
genericTypeClone.GenericArguments.Add(genericArgMap[arg.Name]);
|
||||
}
|
||||
else
|
||||
{
|
||||
genericTypeClone.GenericArguments.Add(arg);
|
||||
}
|
||||
}
|
||||
|
||||
return genericTypeClone;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 89602ba5a473d064387392d8d2055aa4
|
||||
timeCreated: 1537234617
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+752
@@ -0,0 +1,752 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using ModestTree;
|
||||
using Zenject.Internal;
|
||||
using Zenject.ReflectionBaking.Mono.Cecil;
|
||||
using Zenject.ReflectionBaking.Mono.Cecil.Cil;
|
||||
using Zenject.ReflectionBaking.Mono.Collections.Generic;
|
||||
using MethodAttributes = Zenject.ReflectionBaking.Mono.Cecil.MethodAttributes;
|
||||
|
||||
namespace Zenject.ReflectionBaking
|
||||
{
|
||||
public class ReflectionBakingModuleEditor
|
||||
{
|
||||
readonly Assembly _assembly;
|
||||
readonly ModuleDefinition _module;
|
||||
readonly List<Regex> _namespaceRegexes;
|
||||
|
||||
MethodReference _zenjectTypeInfoConstructor;
|
||||
MethodReference _injectableInfoConstructor;
|
||||
MethodReference _injectMethodInfoConstructor;
|
||||
MethodReference _injectMemberInfoConstructor;
|
||||
MethodReference _constructorInfoConstructor;
|
||||
MethodReference _getTypeFromHandleMethod;
|
||||
MethodReference _funcConstructor;
|
||||
MethodReference _funcPostInject;
|
||||
MethodReference _funcMemberSetter;
|
||||
MethodReference _preserveConstructor;
|
||||
|
||||
TypeReference _injectMethodInfoType;
|
||||
TypeReference _injectMemberInfoType;
|
||||
TypeReference _injectableInfoType;
|
||||
TypeReference _objectArrayType;
|
||||
TypeReference _zenjectTypeInfoType;
|
||||
|
||||
ReflectionBakingModuleEditor(
|
||||
ModuleDefinition module, Assembly assembly, List<string> namespacePatterns)
|
||||
{
|
||||
_module = module;
|
||||
_assembly = assembly;
|
||||
_namespaceRegexes = namespacePatterns.Select(CreateRegex).ToList();
|
||||
_namespaceRegexes.Add(CreateRegex("^Zenject"));
|
||||
}
|
||||
|
||||
public static int WeaveAssembly(
|
||||
ModuleDefinition module, Assembly assembly)
|
||||
{
|
||||
return WeaveAssembly(module, assembly, new List<string>());
|
||||
}
|
||||
|
||||
public static int WeaveAssembly(
|
||||
ModuleDefinition module, Assembly assembly, List<string> namespacePatterns)
|
||||
{
|
||||
return new ReflectionBakingModuleEditor(module, assembly, namespacePatterns).Run();
|
||||
}
|
||||
|
||||
int Run()
|
||||
{
|
||||
SaveImports();
|
||||
|
||||
int numTypesEditted = 0;
|
||||
|
||||
var allTypes = _module.LookupAllTypes();
|
||||
|
||||
foreach (var typeDef in allTypes)
|
||||
{
|
||||
if (_namespaceRegexes.Any() && !_namespaceRegexes.Any(x => x.IsMatch(typeDef.FullName)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var actualType = typeDef.TryGetActualType(_assembly);
|
||||
|
||||
if (actualType == null)
|
||||
{
|
||||
Log.Warn("Could not find actual type for type '{0}', skipping", typeDef.FullName);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (TryEditType(typeDef, actualType))
|
||||
{
|
||||
numTypesEditted++;
|
||||
}
|
||||
}
|
||||
|
||||
return numTypesEditted;
|
||||
}
|
||||
|
||||
Regex CreateRegex(string regexStr)
|
||||
{
|
||||
return new Regex(regexStr, RegexOptions.Compiled);
|
||||
}
|
||||
|
||||
void SaveImports()
|
||||
{
|
||||
_zenjectTypeInfoType = _module.ImportType<InjectTypeInfo>();
|
||||
_zenjectTypeInfoConstructor = _module.ImportMethod<InjectTypeInfo>(".ctor");
|
||||
|
||||
_injectableInfoConstructor = _module.ImportMethod<InjectableInfo>(".ctor");
|
||||
|
||||
_getTypeFromHandleMethod = _module.ImportMethod<Type>("GetTypeFromHandle", 1);
|
||||
|
||||
_injectMethodInfoType = _module.ImportType<InjectTypeInfo.InjectMethodInfo>();
|
||||
_injectMethodInfoConstructor = _module.ImportMethod<InjectTypeInfo.InjectMethodInfo>(".ctor");
|
||||
|
||||
_injectMemberInfoType = _module.ImportType<InjectTypeInfo.InjectMemberInfo>();
|
||||
_injectMemberInfoConstructor = _module.ImportMethod<InjectTypeInfo.InjectMemberInfo>(".ctor");
|
||||
|
||||
_preserveConstructor = _module.ImportMethod<Zenject.Internal.PreserveAttribute>(".ctor");
|
||||
_constructorInfoConstructor = _module.ImportMethod<InjectTypeInfo.InjectConstructorInfo>(".ctor");
|
||||
|
||||
_injectableInfoType = _module.ImportType<InjectableInfo>();
|
||||
|
||||
_objectArrayType = _module.Import(typeof(object[]));
|
||||
|
||||
_funcConstructor = _module.ImportMethod<ZenFactoryMethod>(".ctor", 2);
|
||||
|
||||
_funcPostInject = _module.ImportMethod<ZenInjectMethod>(".ctor", 2);
|
||||
|
||||
_funcMemberSetter = _module.ImportMethod<ZenMemberSetterMethod>(".ctor", 2);
|
||||
}
|
||||
|
||||
public bool TryEditType(TypeDefinition typeDef, Type actualType)
|
||||
{
|
||||
if (actualType.IsEnum || actualType.IsValueType || actualType.IsInterface
|
||||
|| actualType.HasAttribute<NoReflectionBakingAttribute>()
|
||||
|| IsStaticClass(actualType) || actualType.DerivesFromOrEqual<Delegate>() || actualType.DerivesFromOrEqual<Attribute>())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allow running on the same dll multiple times without causing problems
|
||||
if (IsTypeProcessed(typeDef))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var typeInfo = ReflectionTypeAnalyzer.GetReflectionInfo(actualType);
|
||||
|
||||
var factoryMethod = TryAddFactoryMethod(typeDef, typeInfo);
|
||||
var genericTypeDef = CreateGenericInstanceWithParameters(typeDef);
|
||||
var fieldSetMethods = AddFieldSetters(typeDef, genericTypeDef, typeInfo);
|
||||
var propertySetMethods = AddPropertySetters(typeDef, genericTypeDef, typeInfo);
|
||||
var postInjectMethods = AddPostInjectMethods(typeDef, genericTypeDef, typeInfo);
|
||||
|
||||
CreateGetInfoMethod(
|
||||
typeDef, genericTypeDef, typeInfo,
|
||||
factoryMethod, fieldSetMethods, propertySetMethods, postInjectMethods);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.ErrorException("Error when modifying type '{0}'".Fmt(actualType), e);
|
||||
throw;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool IsStaticClass(Type type)
|
||||
{
|
||||
// Apparently this is unique to static classes
|
||||
return type.IsAbstract && type.IsSealed;
|
||||
}
|
||||
|
||||
// We are already processed if our static constructor calls TypeAnalyzer
|
||||
bool IsTypeProcessed(TypeDefinition typeDef)
|
||||
{
|
||||
return typeDef.GetMethod(TypeAnalyzer.ReflectionBakingGetInjectInfoMethodName) != null;
|
||||
}
|
||||
|
||||
void EmitCastOperation(ILProcessor processor, Type type, Collection<GenericParameter> genericParams)
|
||||
{
|
||||
if (type.IsGenericParameter)
|
||||
{
|
||||
processor.Emit(OpCodes.Unbox_Any, genericParams[type.GenericParameterPosition]);
|
||||
}
|
||||
else if (type.IsEnum)
|
||||
{
|
||||
processor.Emit(OpCodes.Unbox_Any, _module.TypeSystem.Int32);
|
||||
}
|
||||
else if (type.IsValueType)
|
||||
{
|
||||
processor.Emit(OpCodes.Unbox_Any, _module.ImportType(type));
|
||||
}
|
||||
else
|
||||
{
|
||||
processor.Emit(OpCodes.Castclass, CreateGenericInstanceIfNecessary(type, genericParams));
|
||||
}
|
||||
}
|
||||
|
||||
TypeReference CreateGenericInstanceWithParameters(TypeDefinition typeDef)
|
||||
{
|
||||
if (typeDef.GenericParameters.Any())
|
||||
{
|
||||
var genericInstance = new GenericInstanceType(typeDef);
|
||||
|
||||
foreach (var parameter in typeDef.GenericParameters)
|
||||
{
|
||||
genericInstance.GenericArguments.Add(parameter);
|
||||
}
|
||||
|
||||
return genericInstance;
|
||||
}
|
||||
|
||||
return typeDef;
|
||||
}
|
||||
|
||||
MethodDefinition TryAddFactoryMethod(
|
||||
TypeDefinition typeDef, ReflectionTypeInfo typeInfo)
|
||||
{
|
||||
if (typeInfo.Type.GetParentTypes().Any(x => x.FullName == "UnityEngine.Component"))
|
||||
{
|
||||
Assert.That(typeInfo.InjectConstructor.Parameters.IsEmpty());
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeInfo.InjectConstructor.ConstructorInfo == null)
|
||||
{
|
||||
// static classes, abstract types
|
||||
return null;
|
||||
}
|
||||
|
||||
var factoryMethod = new MethodDefinition(
|
||||
TypeAnalyzer.ReflectionBakingFactoryMethodName,
|
||||
MethodAttributes.Private | MethodAttributes.HideBySig |
|
||||
MethodAttributes.Static,
|
||||
_module.TypeSystem.Object);
|
||||
|
||||
var p1 = new ParameterDefinition(_objectArrayType);
|
||||
p1.Name = "P_0";
|
||||
factoryMethod.Parameters.Add(p1);
|
||||
|
||||
var body = factoryMethod.Body;
|
||||
body.InitLocals = true;
|
||||
|
||||
var processor = body.GetILProcessor();
|
||||
|
||||
var returnValueVar = new VariableDefinition(_module.TypeSystem.Object);
|
||||
body.Variables.Add(returnValueVar);
|
||||
|
||||
processor.Emit(OpCodes.Nop);
|
||||
|
||||
Assert.IsNotNull(typeInfo.InjectConstructor);
|
||||
|
||||
var args = typeInfo.InjectConstructor.Parameters;
|
||||
|
||||
for (int i = 0; i < args.Count; i++)
|
||||
{
|
||||
var arg = args[i];
|
||||
|
||||
processor.Emit(OpCodes.Ldarg_0);
|
||||
processor.Emit(OpCodes.Ldc_I4, i);
|
||||
processor.Emit(OpCodes.Ldelem_Ref);
|
||||
|
||||
EmitCastOperation(
|
||||
processor, arg.ParameterInfo.ParameterType, typeDef.GenericParameters);
|
||||
}
|
||||
|
||||
processor.Emit(OpCodes.Newobj, _module.Import(typeInfo.InjectConstructor.ConstructorInfo));
|
||||
|
||||
processor.Emit(OpCodes.Stloc_0);
|
||||
processor.Emit(OpCodes.Ldloc_S, returnValueVar);
|
||||
processor.Emit(OpCodes.Ret);
|
||||
|
||||
typeDef.Methods.Add(factoryMethod);
|
||||
|
||||
return factoryMethod;
|
||||
}
|
||||
|
||||
void AddPostInjectMethodBody(
|
||||
ILProcessor processor, ReflectionTypeInfo.InjectMethodInfo postInjectInfo, TypeDefinition typeDef, TypeReference genericTypeDef)
|
||||
{
|
||||
processor.Emit(OpCodes.Nop);
|
||||
|
||||
TypeReference declaringTypeDef;
|
||||
MethodReference actualMethodDef;
|
||||
|
||||
if (!TryFindLocalMethod(
|
||||
genericTypeDef, postInjectInfo.MethodInfo.Name, out declaringTypeDef, out actualMethodDef))
|
||||
{
|
||||
throw Assert.CreateException();
|
||||
}
|
||||
|
||||
processor.Emit(OpCodes.Ldarg_0);
|
||||
processor.Emit(OpCodes.Castclass, declaringTypeDef);
|
||||
|
||||
for (int k = 0; k < postInjectInfo.Parameters.Count; k++)
|
||||
{
|
||||
var injectInfo = postInjectInfo.Parameters[k];
|
||||
|
||||
processor.Emit(OpCodes.Ldarg_1);
|
||||
processor.Emit(OpCodes.Ldc_I4, k);
|
||||
processor.Emit(OpCodes.Ldelem_Ref);
|
||||
|
||||
EmitCastOperation(processor, injectInfo.ParameterInfo.ParameterType, typeDef.GenericParameters);
|
||||
}
|
||||
|
||||
processor.Emit(OpCodes.Callvirt, actualMethodDef);
|
||||
processor.Emit(OpCodes.Ret);
|
||||
}
|
||||
|
||||
MethodDefinition AddPostInjectMethod(
|
||||
string name, ReflectionTypeInfo.InjectMethodInfo postInjectInfo, TypeDefinition typeDef, TypeReference genericTypeDef)
|
||||
{
|
||||
var methodDef = new MethodDefinition(
|
||||
name,
|
||||
MethodAttributes.Private | MethodAttributes.HideBySig |
|
||||
MethodAttributes.Static,
|
||||
_module.TypeSystem.Void);
|
||||
|
||||
var p1 = new ParameterDefinition(_module.TypeSystem.Object);
|
||||
p1.Name = "P_0";
|
||||
methodDef.Parameters.Add(p1);
|
||||
|
||||
var p2 = new ParameterDefinition(_objectArrayType);
|
||||
p2.Name = "P_1";
|
||||
methodDef.Parameters.Add(p2);
|
||||
|
||||
var body = methodDef.Body;
|
||||
var processor = body.GetILProcessor();
|
||||
|
||||
AddPostInjectMethodBody(processor, postInjectInfo, typeDef, genericTypeDef);
|
||||
|
||||
typeDef.Methods.Add(methodDef);
|
||||
|
||||
return methodDef;
|
||||
}
|
||||
|
||||
List<MethodDefinition> AddPostInjectMethods(
|
||||
TypeDefinition typeDef, TypeReference genericTypeDef, ReflectionTypeInfo typeInfo)
|
||||
{
|
||||
var postInjectMethods = new List<MethodDefinition>();
|
||||
|
||||
for (int i = 0; i < typeInfo.InjectMethods.Count; i++)
|
||||
{
|
||||
postInjectMethods.Add(
|
||||
AddPostInjectMethod(
|
||||
TypeAnalyzer.ReflectionBakingInjectMethodPrefix + i, typeInfo.InjectMethods[i], typeDef, genericTypeDef));
|
||||
}
|
||||
|
||||
return postInjectMethods;
|
||||
}
|
||||
|
||||
void EmitSetterMethod(
|
||||
ILProcessor processor, MemberInfo memberInfo, TypeDefinition typeDef, TypeReference genericTypeDef)
|
||||
{
|
||||
processor.Emit(OpCodes.Nop);
|
||||
|
||||
processor.Emit(OpCodes.Ldarg_0);
|
||||
processor.Emit(OpCodes.Castclass, genericTypeDef);
|
||||
|
||||
processor.Emit(OpCodes.Ldarg_1);
|
||||
|
||||
if (memberInfo is FieldInfo)
|
||||
{
|
||||
var fieldInfo = (FieldInfo)memberInfo;
|
||||
|
||||
EmitCastOperation(processor, fieldInfo.FieldType, typeDef.GenericParameters);
|
||||
|
||||
processor.Emit(OpCodes.Stfld, FindLocalField(genericTypeDef, fieldInfo.Name));
|
||||
}
|
||||
else
|
||||
{
|
||||
var propertyInfo = (PropertyInfo)memberInfo;
|
||||
|
||||
EmitCastOperation(processor, propertyInfo.PropertyType, typeDef.GenericParameters);
|
||||
|
||||
processor.Emit(OpCodes.Callvirt, FindLocalPropertySetMethod(genericTypeDef, propertyInfo.Name));
|
||||
}
|
||||
|
||||
processor.Emit(OpCodes.Ret);
|
||||
}
|
||||
|
||||
MethodDefinition AddSetterMethod(
|
||||
string name, MemberInfo memberInfo, TypeDefinition typeDef, TypeReference genericTypeDef)
|
||||
{
|
||||
var methodDef = new MethodDefinition(
|
||||
name,
|
||||
MethodAttributes.Private | MethodAttributes.HideBySig |
|
||||
MethodAttributes.Static,
|
||||
_module.TypeSystem.Void);
|
||||
|
||||
var p1 = new ParameterDefinition(_module.TypeSystem.Object);
|
||||
p1.Name = "P_0";
|
||||
methodDef.Parameters.Add(p1);
|
||||
|
||||
var p2 = new ParameterDefinition(_module.TypeSystem.Object);
|
||||
p2.Name = "P_1";
|
||||
methodDef.Parameters.Add(p2);
|
||||
|
||||
methodDef.Body.InitLocals = true;
|
||||
|
||||
EmitSetterMethod(
|
||||
methodDef.Body.GetILProcessor(), memberInfo, typeDef, genericTypeDef);
|
||||
|
||||
typeDef.Methods.Add(methodDef);
|
||||
|
||||
return methodDef;
|
||||
}
|
||||
|
||||
List<MethodDefinition> AddPropertySetters(
|
||||
TypeDefinition typeDef, TypeReference genericTypeDef, ReflectionTypeInfo typeInfo)
|
||||
{
|
||||
var methodDefs = new List<MethodDefinition>();
|
||||
|
||||
for (int i = 0; i < typeInfo.InjectProperties.Count; i++)
|
||||
{
|
||||
methodDefs.Add(
|
||||
AddSetterMethod(
|
||||
TypeAnalyzer.ReflectionBakingPropertySetterPrefix + i,
|
||||
typeInfo.InjectProperties[i].PropertyInfo, typeDef, genericTypeDef));
|
||||
}
|
||||
|
||||
return methodDefs;
|
||||
}
|
||||
|
||||
List<MethodDefinition> AddFieldSetters(
|
||||
TypeDefinition typeDef, TypeReference genericTypeDef, ReflectionTypeInfo typeInfo)
|
||||
{
|
||||
var methodDefs = new List<MethodDefinition>();
|
||||
|
||||
for (int i = 0; i < typeInfo.InjectFields.Count; i++)
|
||||
{
|
||||
methodDefs.Add(
|
||||
AddSetterMethod(
|
||||
TypeAnalyzer.ReflectionBakingFieldSetterPrefix + i,
|
||||
typeInfo.InjectFields[i].FieldInfo, typeDef, genericTypeDef));
|
||||
}
|
||||
|
||||
return methodDefs;
|
||||
}
|
||||
|
||||
void CreateGetInfoMethod(
|
||||
TypeDefinition typeDef, TypeReference genericTypeDef, ReflectionTypeInfo typeInfo,
|
||||
MethodDefinition factoryMethod, List<MethodDefinition> fieldSetMethods,
|
||||
List<MethodDefinition> propertySetMethods, List<MethodDefinition> postInjectMethods)
|
||||
{
|
||||
var getInfoMethodDef = new MethodDefinition(
|
||||
TypeAnalyzer.ReflectionBakingGetInjectInfoMethodName,
|
||||
MethodAttributes.Private | MethodAttributes.HideBySig |
|
||||
MethodAttributes.Static,
|
||||
_zenjectTypeInfoType);
|
||||
|
||||
typeDef.Methods.Add(getInfoMethodDef);
|
||||
|
||||
getInfoMethodDef.CustomAttributes.Add(
|
||||
new CustomAttribute(_preserveConstructor));
|
||||
|
||||
var returnValueVar = new VariableDefinition(_module.TypeSystem.Object);
|
||||
|
||||
var body = getInfoMethodDef.Body;
|
||||
|
||||
body.Variables.Add(returnValueVar);
|
||||
body.InitLocals = true;
|
||||
|
||||
var instructions = new List<Instruction>();
|
||||
|
||||
instructions.Add(Instruction.Create(OpCodes.Ldtoken, genericTypeDef));
|
||||
instructions.Add(Instruction.Create(OpCodes.Call, _getTypeFromHandleMethod));
|
||||
|
||||
if (factoryMethod == null)
|
||||
{
|
||||
instructions.Add(Instruction.Create(OpCodes.Ldnull));
|
||||
}
|
||||
else
|
||||
{
|
||||
instructions.Add(Instruction.Create(OpCodes.Ldnull));
|
||||
instructions.Add(Instruction.Create(OpCodes.Ldftn, factoryMethod.ChangeDeclaringType(genericTypeDef)));
|
||||
instructions.Add(Instruction.Create(OpCodes.Newobj, _funcConstructor));
|
||||
}
|
||||
|
||||
instructions.Add(Instruction.Create(OpCodes.Ldc_I4, typeInfo.InjectConstructor.Parameters.Count));
|
||||
instructions.Add(Instruction.Create(OpCodes.Newarr, _injectableInfoType));
|
||||
|
||||
for (int i = 0; i < typeInfo.InjectConstructor.Parameters.Count; i++)
|
||||
{
|
||||
var injectableInfo = typeInfo.InjectConstructor.Parameters[i].InjectableInfo;
|
||||
|
||||
instructions.Add(Instruction.Create(OpCodes.Dup));
|
||||
instructions.Add(Instruction.Create(OpCodes.Ldc_I4, i));
|
||||
|
||||
EmitNewInjectableInfoInstructions(
|
||||
instructions, injectableInfo, typeDef);
|
||||
|
||||
instructions.Add(Instruction.Create(OpCodes.Stelem_Ref));
|
||||
}
|
||||
|
||||
instructions.Add(Instruction.Create(OpCodes.Newobj, _constructorInfoConstructor));
|
||||
|
||||
instructions.Add(Instruction.Create(OpCodes.Ldc_I4, typeInfo.InjectMethods.Count));
|
||||
instructions.Add(Instruction.Create(OpCodes.Newarr, _injectMethodInfoType));
|
||||
|
||||
Assert.IsEqual(postInjectMethods.Count, typeInfo.InjectMethods.Count);
|
||||
|
||||
for (int i = 0; i < typeInfo.InjectMethods.Count; i++)
|
||||
{
|
||||
var injectMethodInfo = typeInfo.InjectMethods[i];
|
||||
|
||||
instructions.Add(Instruction.Create(OpCodes.Dup));
|
||||
instructions.Add(Instruction.Create(OpCodes.Ldc_I4, i));
|
||||
|
||||
AddInjectableMethodInstructions(
|
||||
instructions, injectMethodInfo, typeDef, genericTypeDef, postInjectMethods[i]);
|
||||
|
||||
instructions.Add(Instruction.Create(OpCodes.Stelem_Ref));
|
||||
}
|
||||
|
||||
instructions.Add(Instruction.Create(OpCodes.Ldc_I4, fieldSetMethods.Count + propertySetMethods.Count));
|
||||
instructions.Add(Instruction.Create(OpCodes.Newarr, _injectMemberInfoType));
|
||||
|
||||
for (int i = 0; i < fieldSetMethods.Count; i++)
|
||||
{
|
||||
var injectField = typeInfo.InjectFields[i];
|
||||
|
||||
instructions.Add(Instruction.Create(OpCodes.Dup));
|
||||
instructions.Add(Instruction.Create(OpCodes.Ldc_I4, i));
|
||||
|
||||
AddInjectableMemberInstructions(
|
||||
instructions,
|
||||
injectField.InjectableInfo, injectField.FieldInfo.Name,
|
||||
typeDef, genericTypeDef, fieldSetMethods[i]);
|
||||
|
||||
instructions.Add(Instruction.Create(OpCodes.Stelem_Ref));
|
||||
}
|
||||
|
||||
for (int i = 0; i < propertySetMethods.Count; i++)
|
||||
{
|
||||
var injectProperty = typeInfo.InjectProperties[i];
|
||||
|
||||
instructions.Add(Instruction.Create(OpCodes.Dup));
|
||||
instructions.Add(Instruction.Create(OpCodes.Ldc_I4, fieldSetMethods.Count + i));
|
||||
|
||||
AddInjectableMemberInstructions(
|
||||
instructions,
|
||||
injectProperty.InjectableInfo,
|
||||
injectProperty.PropertyInfo.Name, typeDef, genericTypeDef,
|
||||
propertySetMethods[i]);
|
||||
|
||||
instructions.Add(Instruction.Create(OpCodes.Stelem_Ref));
|
||||
}
|
||||
|
||||
instructions.Add(Instruction.Create(OpCodes.Newobj, _zenjectTypeInfoConstructor));
|
||||
|
||||
instructions.Add(Instruction.Create(OpCodes.Stloc_0));
|
||||
instructions.Add(Instruction.Create(OpCodes.Ldloc_S, returnValueVar));
|
||||
instructions.Add(Instruction.Create(OpCodes.Ret));
|
||||
|
||||
var processor = body.GetILProcessor();
|
||||
|
||||
foreach (var instruction in instructions)
|
||||
{
|
||||
processor.Append(instruction);
|
||||
}
|
||||
}
|
||||
|
||||
MethodReference FindLocalPropertySetMethod(
|
||||
TypeReference specificTypeRef, string memberName)
|
||||
{
|
||||
foreach (var typeRef in specificTypeRef.GetSpecificBaseTypesAndSelf())
|
||||
{
|
||||
var candidatePropertyDef = typeRef.Resolve().Properties
|
||||
.Where(x => x.Name == memberName).SingleOrDefault();
|
||||
|
||||
if (candidatePropertyDef != null)
|
||||
{
|
||||
return candidatePropertyDef.SetMethod.ChangeDeclaringType(typeRef);
|
||||
}
|
||||
}
|
||||
|
||||
throw Assert.CreateException();
|
||||
}
|
||||
|
||||
FieldReference FindLocalField(
|
||||
TypeReference specificTypeRef, string fieldName)
|
||||
{
|
||||
foreach (var typeRef in specificTypeRef.GetSpecificBaseTypesAndSelf())
|
||||
{
|
||||
var candidateFieldDef = typeRef.Resolve().Fields
|
||||
.Where(x => x.Name == fieldName).SingleOrDefault();
|
||||
|
||||
if (candidateFieldDef != null)
|
||||
{
|
||||
return candidateFieldDef.ChangeDeclaringType(typeRef);
|
||||
}
|
||||
}
|
||||
|
||||
throw Assert.CreateException();
|
||||
}
|
||||
|
||||
bool TryFindLocalMethod(
|
||||
TypeReference specificTypeRef, string methodName, out TypeReference declaringTypeRef, out MethodReference methodRef)
|
||||
{
|
||||
foreach (var typeRef in specificTypeRef.GetSpecificBaseTypesAndSelf())
|
||||
{
|
||||
var candidateMethodDef = typeRef.Resolve().Methods
|
||||
.Where(x => x.Name == methodName).SingleOrDefault();
|
||||
|
||||
if (candidateMethodDef != null)
|
||||
{
|
||||
declaringTypeRef = typeRef;
|
||||
methodRef = candidateMethodDef.ChangeDeclaringType(typeRef);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
declaringTypeRef = null;
|
||||
methodRef = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
void AddObjectInstructions(
|
||||
List<Instruction> instructions,
|
||||
object identifier)
|
||||
{
|
||||
if (identifier == null)
|
||||
{
|
||||
instructions.Add(Instruction.Create(OpCodes.Ldnull));
|
||||
}
|
||||
else if (identifier is string)
|
||||
{
|
||||
instructions.Add(Instruction.Create(OpCodes.Ldstr, (string)identifier));
|
||||
}
|
||||
else if (identifier is int)
|
||||
{
|
||||
instructions.Add(Instruction.Create(OpCodes.Ldc_I4, (int)identifier));
|
||||
instructions.Add(Instruction.Create(OpCodes.Box, _module.Import(typeof(int))));
|
||||
}
|
||||
else if (identifier.GetType().IsEnum)
|
||||
{
|
||||
instructions.Add(Instruction.Create(OpCodes.Ldc_I4, (int)identifier));
|
||||
instructions.Add(Instruction.Create(OpCodes.Box, _module.Import(identifier.GetType())));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw Assert.CreateException(
|
||||
"Cannot process values with type '{0}' currently. Feel free to add support for this and submit a pull request to github.", identifier.GetType());
|
||||
}
|
||||
}
|
||||
|
||||
TypeReference CreateGenericInstanceIfNecessary(
|
||||
Type memberType, Collection<GenericParameter> genericParams)
|
||||
{
|
||||
if (!memberType.ContainsGenericParameters)
|
||||
{
|
||||
return _module.Import(memberType);
|
||||
}
|
||||
|
||||
if (memberType.IsGenericParameter)
|
||||
{
|
||||
return genericParams[memberType.GenericParameterPosition];
|
||||
}
|
||||
|
||||
if (memberType.IsArray)
|
||||
{
|
||||
return new ArrayType(
|
||||
CreateGenericInstanceIfNecessary(memberType.GetElementType(), genericParams), memberType.GetArrayRank());
|
||||
}
|
||||
|
||||
var genericMemberType = memberType.GetGenericTypeDefinition();
|
||||
|
||||
var genericInstance = new GenericInstanceType(_module.Import(genericMemberType));
|
||||
|
||||
foreach (var arg in memberType.GenericArguments())
|
||||
{
|
||||
genericInstance.GenericArguments.Add(
|
||||
CreateGenericInstanceIfNecessary(arg, genericParams));
|
||||
}
|
||||
|
||||
return genericInstance;
|
||||
}
|
||||
|
||||
void AddInjectableMemberInstructions(
|
||||
List<Instruction> instructions,
|
||||
InjectableInfo injectableInfo, string name,
|
||||
TypeDefinition typeDef, TypeReference genericTypeDef,
|
||||
MethodDefinition methodDef)
|
||||
{
|
||||
instructions.Add(Instruction.Create(OpCodes.Ldnull));
|
||||
instructions.Add(Instruction.Create(OpCodes.Ldftn, methodDef.ChangeDeclaringType(genericTypeDef)));
|
||||
instructions.Add(Instruction.Create(OpCodes.Newobj, _funcMemberSetter));
|
||||
|
||||
EmitNewInjectableInfoInstructions(
|
||||
instructions, injectableInfo, typeDef);
|
||||
|
||||
instructions.Add(Instruction.Create(OpCodes.Newobj, _injectMemberInfoConstructor));
|
||||
}
|
||||
|
||||
void AddInjectableMethodInstructions(
|
||||
List<Instruction> instructions,
|
||||
ReflectionTypeInfo.InjectMethodInfo injectMethod,
|
||||
TypeDefinition typeDef, TypeReference genericTypeDef,
|
||||
MethodDefinition methodDef)
|
||||
{
|
||||
instructions.Add(Instruction.Create(OpCodes.Ldnull));
|
||||
instructions.Add(Instruction.Create(OpCodes.Ldftn, methodDef.ChangeDeclaringType(genericTypeDef)));
|
||||
instructions.Add(Instruction.Create(OpCodes.Newobj, _funcPostInject));
|
||||
|
||||
instructions.Add(Instruction.Create(OpCodes.Ldc_I4, injectMethod.Parameters.Count));
|
||||
instructions.Add(Instruction.Create(OpCodes.Newarr, _injectableInfoType));
|
||||
|
||||
for (int i = 0; i < injectMethod.Parameters.Count; i++)
|
||||
{
|
||||
var injectableInfo = injectMethod.Parameters[i].InjectableInfo;
|
||||
|
||||
instructions.Add(Instruction.Create(OpCodes.Dup));
|
||||
instructions.Add(Instruction.Create(OpCodes.Ldc_I4, i));
|
||||
|
||||
EmitNewInjectableInfoInstructions(
|
||||
instructions, injectableInfo, typeDef);
|
||||
|
||||
instructions.Add(Instruction.Create(OpCodes.Stelem_Ref));
|
||||
}
|
||||
|
||||
instructions.Add(Instruction.Create(OpCodes.Ldstr, injectMethod.MethodInfo.Name));
|
||||
|
||||
instructions.Add(Instruction.Create(OpCodes.Newobj, _injectMethodInfoConstructor));
|
||||
}
|
||||
|
||||
void EmitNewInjectableInfoInstructions(
|
||||
List<Instruction> instructions,
|
||||
InjectableInfo injectableInfo,
|
||||
TypeDefinition typeDef)
|
||||
{
|
||||
if (injectableInfo.Optional)
|
||||
{
|
||||
instructions.Add(Instruction.Create(OpCodes.Ldc_I4_1));
|
||||
}
|
||||
else
|
||||
{
|
||||
instructions.Add(Instruction.Create(OpCodes.Ldc_I4_0));
|
||||
}
|
||||
|
||||
AddObjectInstructions(instructions, injectableInfo.Identifier);
|
||||
|
||||
instructions.Add(Instruction.Create(OpCodes.Ldstr, injectableInfo.MemberName));
|
||||
|
||||
instructions.Add(Instruction.Create(OpCodes.Ldtoken, CreateGenericInstanceIfNecessary(injectableInfo.MemberType, typeDef.GenericParameters)));
|
||||
|
||||
instructions.Add(Instruction.Create(OpCodes.Call, _getTypeFromHandleMethod));
|
||||
|
||||
AddObjectInstructions(instructions, injectableInfo.DefaultValue);
|
||||
|
||||
instructions.Add(Instruction.Create(OpCodes.Ldc_I4, (int)injectableInfo.SourceType));
|
||||
|
||||
instructions.Add(Instruction.Create(OpCodes.Newobj, _injectableInfoConstructor));
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a664c0011937115449c81d8ca4a2f6c7
|
||||
timeCreated: 1538185954
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user