using FishNet.CodeGenerating.Helping.Extension; using FishNet.Serializing; using MonoFN.Cecil; using MonoFN.Cecil.Cil; using MonoFN.Cecil.Rocks; using SR = System.Reflection; using System.Collections.Generic; using System; using FishNet.CodeGenerating.ILCore; using FishNet.CodeGenerating.Extension; using FishNet.Utility.Performance; using FishNet.Object; namespace FishNet.CodeGenerating.Helping { internal class ReaderProcessor : CodegenBase { #region Reflection references. public TypeDefinition GeneratedReaderClassTypeDef; public MethodDefinition GeneratedReaderOnLoadMethodDef; public readonly Dictionary InstancedReaderMethods = new Dictionary(); public readonly Dictionary StaticReaderMethods = new Dictionary(); public HashSet AutoPackedMethods = new HashSet(new TypeReferenceComparer()); #endregion #region Misc. /// /// TypeReferences which have already had delegates made for. /// private HashSet _delegatedTypes = new HashSet(); #endregion #region Const. /// /// Namespace to use for generated serializers and delegates. /// public const string GENERATED_READER_NAMESPACE = WriterProcessor.GENERATED_WRITER_NAMESPACE; /// /// Name to use for generated serializers class. /// public const string GENERATED_WRITERS_CLASS_NAME = "GeneratedReaders___Internal"; /// /// Attributes to use for generated serializers class. /// public const TypeAttributes GENERATED_TYPE_ATTRIBUTES = (TypeAttributes.BeforeFieldInit | TypeAttributes.Class | TypeAttributes.AnsiClass | TypeAttributes.Public | TypeAttributes.AutoClass | TypeAttributes.Abstract | TypeAttributes.Sealed); /// /// Name to use for InitializeOnce method. /// public const string INITIALIZEONCE_METHOD_NAME = WriterProcessor.INITIALIZEONCE_METHOD_NAME; /// /// Attributes to use for InitializeOnce method within generated serializer classes. /// public const MethodAttributes INITIALIZEONCE_METHOD_ATTRIBUTES = WriterProcessor.INITIALIZEONCE_METHOD_ATTRIBUTES; /// /// Attritbutes to use for generated serializers. /// public const MethodAttributes GENERATED_METHOD_ATTRIBUTES = WriterProcessor.GENERATED_METHOD_ATTRIBUTES; /// /// Prefix used which all instanced and user created serializers should start with. /// internal const string READ_PREFIX = "Read"; /// /// Class name to use for generated readers. /// internal const string GENERATED_READERS_CLASS_NAME = "GeneratedReaders___Internal"; /// /// Prefix to use for generated readers. /// private const string GENERATED_READ_PREFIX = "Read___"; /// /// Types to exclude from being scanned for auto serialization. /// public static System.Type[] EXCLUDED_AUTO_SERIALIZER_TYPES => WriterProcessor.EXCLUDED_AUTO_SERIALIZER_TYPES; /// /// Types to exclude from being scanned for auto serialization. /// public static string[] EXCLUDED_ASSEMBLY_PREFIXES => WriterProcessor.EXCLUDED_ASSEMBLY_PREFIXES; #endregion public override bool ImportReferences() => true; public bool Process() { GeneralHelper gh = base.GetClass(); CreateGeneratedClassData(); FindInstancedReaders(); CreateInstancedReaderExtensions(); void CreateGeneratedClassData() { GeneratedReaderClassTypeDef = gh.GetOrCreateClass(out _, ReaderProcessor.GENERATED_TYPE_ATTRIBUTES, ReaderProcessor.GENERATED_READERS_CLASS_NAME, null, WriterProcessor.GENERATED_WRITER_NAMESPACE); /* If constructor isn't set then try to get or create it * and also add it to methods if were created. */ GeneratedReaderOnLoadMethodDef = gh.GetOrCreateMethod(GeneratedReaderClassTypeDef, out _, INITIALIZEONCE_METHOD_ATTRIBUTES, INITIALIZEONCE_METHOD_NAME, base.Module.TypeSystem.Void); gh.CreateRuntimeInitializeOnLoadMethodAttribute(GeneratedReaderOnLoadMethodDef); ILProcessor ppp = GeneratedReaderOnLoadMethodDef.Body.GetILProcessor(); ppp.Emit(OpCodes.Ret); //GeneratedReaderOnLoadMethodDef.DeclaringType.Methods.Remove(GeneratedReaderOnLoadMethodDef); } void FindInstancedReaders() { Type pooledWriterType = typeof(PooledReader); foreach (SR.MethodInfo methodInfo in pooledWriterType.GetMethods()) { if (IsSpecialReadMethod(methodInfo)) continue; bool autoPackMethod; if (IsIgnoredReadMethod(methodInfo, out autoPackMethod)) continue; MethodReference methodRef = base.ImportReference(methodInfo); /* TypeReference for the return type * of the read method. */ TypeReference typeRef = base.ImportReference(methodRef.ReturnType); /* If here all checks pass. */ AddReaderMethod(typeRef, methodRef, true, true); if (autoPackMethod) AutoPackedMethods.Add(typeRef); } } return true; } /// /// Returns if a MethodInfo is considered a special write method. /// Special read methods have declared references within this class, and will not have extensions made for them. /// public bool IsSpecialReadMethod(SR.MethodInfo methodInfo) { /* Special methods. */ if (methodInfo.Name == nameof(PooledReader.ReadPackedWhole)) return true; else if (methodInfo.Name == nameof(PooledReader.ReadArray)) return true; else if (methodInfo.Name == nameof(PooledReader.ReadDictionary)) return true; return false; } /// /// Returns if a read method should be ignored. /// public bool IsIgnoredReadMethod(SR.MethodInfo methodInfo, out bool autoPackMethod) { autoPackMethod = false; if (base.GetClass().CodegenExclude(methodInfo)) return true; //Not long enough to be a write method. else if (methodInfo.Name.Length < READ_PREFIX.Length) return true; //Method name doesn't start with writePrefix. else if (methodInfo.Name.Substring(0, READ_PREFIX.Length) != READ_PREFIX) return true; SR.ParameterInfo[] parameterInfos = methodInfo.GetParameters(); //Can have at most one parameter for packing. if (parameterInfos.Length > 1) return true; //If has one parameter make sure it's a packing type. if (parameterInfos.Length == 1) { autoPackMethod = (parameterInfos[0].ParameterType == typeof(AutoPackType)); if (!autoPackMethod) return true; } return false; } /// /// Adds typeRef, methodDef to instanced or readerMethods. /// /// /// /// internal void AddReaderMethod(TypeReference typeRef, MethodReference methodRef, bool instanced, bool useAdd) { string fullName = typeRef.GetFullnameWithoutBrackets(); Dictionary dict = (instanced) ? InstancedReaderMethods : StaticReaderMethods; if (useAdd) dict.Add(fullName, methodRef); else dict[fullName] = methodRef; } /// /// Creates a Read delegate for readMethodRef and places it within the generated reader/writer constructor. /// /// /// internal void CreateReadDelegate(MethodReference readMr, bool isStatic) { GeneralHelper gh = base.GetClass(); ReaderImports ri = base.GetClass(); if (!isStatic) { //Supporting Write with types containing generics is more trouble than it's worth. if (readMr.IsGenericInstance || readMr.HasGenericParameters) return; } //Check if ret already exist, if so remove it; ret will be added on again in this method. if (GeneratedReaderOnLoadMethodDef.Body.Instructions.Count != 0) { int lastIndex = (GeneratedReaderOnLoadMethodDef.Body.Instructions.Count - 1); if (GeneratedReaderOnLoadMethodDef.Body.Instructions[lastIndex].OpCode == OpCodes.Ret) GeneratedReaderOnLoadMethodDef.Body.Instructions.RemoveAt(lastIndex); } //Check if already exist. ILProcessor processor = GeneratedReaderOnLoadMethodDef.Body.GetILProcessor(); TypeReference dataTypeRef = readMr.ReturnType; if (_delegatedTypes.Contains(dataTypeRef)) { base.LogError($"Generic read already created for {dataTypeRef.FullName}."); return; } else { _delegatedTypes.Add(dataTypeRef); } //Create a Func delegate processor.Emit(OpCodes.Ldnull); processor.Emit(OpCodes.Ldftn, readMr); GenericInstanceType functionGenericInstance; MethodReference functionConstructorInstanceMethodRef; bool isAutoPacked = IsAutoPackedType(dataTypeRef); //Generate for autopacktype. if (isAutoPacked) { functionGenericInstance = gh.FunctionT3TypeRef.MakeGenericInstanceType(ri.ReaderTypeRef, base.GetClass().AutoPackTypeRef, dataTypeRef); functionConstructorInstanceMethodRef = gh.FunctionT3ConstructorMethodRef.MakeHostInstanceGeneric(base.Session, functionGenericInstance); } //Not autopacked. else { functionGenericInstance = gh.FunctionT2TypeRef.MakeGenericInstanceType(ri.ReaderTypeRef, dataTypeRef); functionConstructorInstanceMethodRef = gh.FunctionT2ConstructorMethodRef.MakeHostInstanceGeneric(base.Session, functionGenericInstance); } processor.Emit(OpCodes.Newobj, functionConstructorInstanceMethodRef); //Call delegate to GeneratedReader.Read GenericInstanceType genericInstance = ri.GenericReaderTypeRef.MakeGenericInstanceType(dataTypeRef); MethodReference genericReadMethodRef = (isAutoPacked) ? ri.ReadAutoPackSetMethodRef.MakeHostInstanceGeneric(base.Session, genericInstance) : ri.ReadSetMethodRef.MakeHostInstanceGeneric(base.Session, genericInstance); processor.Emit(OpCodes.Call, genericReadMethodRef); processor.Emit(OpCodes.Ret); } /// /// Creates reader extension methods for built-in readers. /// private void CreateInstancedReaderExtensions() { if (!FishNetILPP.IsFishNetAssembly(base.Session)) return; GeneralHelper gh = base.GetClass(); ReaderProcessor gwh = base.GetClass(); //List staticReaders = new List(); foreach (KeyValuePair item in InstancedReaderMethods) { MethodReference instancedReadMr = item.Value; if (instancedReadMr.ContainsGenericParameter) continue; TypeReference returnTr = base.ImportReference(instancedReadMr.ReturnType); MethodDefinition md = new MethodDefinition($"InstancedExtension___{instancedReadMr.Name}", WriterProcessor.GENERATED_METHOD_ATTRIBUTES, returnTr); //Add extension parameter. ParameterDefinition readerPd = gh.CreateParameter(md, typeof(Reader), "reader"); //Add parameters needed by instanced writer. List otherPds = md.CreateParameters(base.Session, instancedReadMr); gh.MakeExtensionMethod(md); // gwh.GeneratedReaderClassTypeDef.Methods.Add(md); ILProcessor processor = md.Body.GetILProcessor(); //Load writer. processor.Emit(OpCodes.Ldarg, readerPd); //Load args. foreach (ParameterDefinition pd in otherPds) processor.Emit(OpCodes.Ldarg, pd); //Call instanced. processor.Emit(instancedReadMr.GetCallOpCode(base.Session), instancedReadMr); processor.Emit(OpCodes.Ret); AddReaderMethod(returnTr, md, false, true); } } /// /// Removes typeRef from static/instanced reader methods. /// internal void RemoveReaderMethod(TypeReference typeRef, bool instanced) { string fullName = typeRef.GetFullnameWithoutBrackets(); Dictionary dict = (instanced) ? InstancedReaderMethods : StaticReaderMethods; dict.Remove(fullName); } /// /// Creates read instructions returning instructions and outputing variable of read result. /// internal List CreateRead(MethodDefinition methodDef, ParameterDefinition readerParameterDef, TypeReference readTypeRef, out VariableDefinition createdVariableDef) { ILProcessor processor = methodDef.Body.GetILProcessor(); List insts = new List(); MethodReference readMr = GetOrCreateReadMethodReference(readTypeRef); if (readMr != null) { //Make a local variable. createdVariableDef = base.GetClass().CreateVariable(methodDef, readTypeRef); //pooledReader.ReadBool(); insts.Add(processor.Create(OpCodes.Ldarg, readerParameterDef)); //If an auto pack method then insert default value. if (AutoPackedMethods.Contains(readTypeRef)) { AutoPackType packType = base.GetClass().GetDefaultAutoPackType(readTypeRef); insts.Add(processor.Create(OpCodes.Ldc_I4, (int)packType)); } TypeReference valueTr = readTypeRef; /* If generic then find write class for * data type. Currently we only support one generic * for this. */ if (valueTr.IsGenericInstance) { GenericInstanceType git = (GenericInstanceType)valueTr; TypeReference genericTr = git.GenericArguments[0]; readMr = readMr.GetMethodReference(base.Session, genericTr); } insts.Add(processor.Create(OpCodes.Call, readMr)); //Store into local variable. insts.Add(processor.Create(OpCodes.Stloc, createdVariableDef)); return insts; } else { base.LogError("Reader not found for " + readTypeRef.ToString()); createdVariableDef = null; return null; } } /// /// Creates a read for fieldRef and populates it into a created variable of class or struct type. /// internal bool CreateReadIntoClassOrStruct(MethodDefinition readerMd, ParameterDefinition readerPd, MethodReference readMr, VariableDefinition objectVd, FieldReference valueFr) { if (readMr != null) { ILProcessor processor = readerMd.Body.GetILProcessor(); /* How to load object instance. If it's a structure * then it must be loaded by address. Otherwise if * class Ldloc can be used. */ OpCode loadOpCode = (objectVd.VariableType.IsValueType) ? OpCodes.Ldloca : OpCodes.Ldloc; /* If generic then find write class for * data type. Currently we only support one generic * for this. */ if (valueFr.FieldType.IsGenericInstance) { GenericInstanceType git = (GenericInstanceType)valueFr.FieldType; TypeReference genericTr = git.GenericArguments[0]; readMr = readMr.GetMethodReference(base.Session, genericTr); } processor.Emit(loadOpCode, objectVd); //reader. processor.Emit(OpCodes.Ldarg, readerPd); if (IsAutoPackedType(valueFr.FieldType)) { AutoPackType packType = base.GetClass().GetDefaultAutoPackType(valueFr.FieldType); processor.Emit(OpCodes.Ldc_I4, (int)packType); } //reader.ReadXXXX(). processor.Emit(OpCodes.Call, readMr); //obj.Field = result / reader.ReadXXXX(). processor.Emit(OpCodes.Stfld, valueFr); return true; } else { base.LogError($"Reader not found for {valueFr.FullName}."); return false; } } /// /// Creates a read for fieldRef and populates it into a created variable of class or struct type. /// internal bool CreateReadIntoClassOrStruct(MethodDefinition methodDef, ParameterDefinition readerPd, MethodReference readMr, VariableDefinition objectVariableDef, MethodReference setMr, TypeReference readTr) { if (readMr != null) { ILProcessor processor = methodDef.Body.GetILProcessor(); /* How to load object instance. If it's a structure * then it must be loaded by address. Otherwise if * class Ldloc can be used. */ OpCode loadOpCode = (objectVariableDef.VariableType.IsValueType) ? OpCodes.Ldloca : OpCodes.Ldloc; /* If generic then find write class for * data type. Currently we only support one generic * for this. */ if (readTr.IsGenericInstance) { GenericInstanceType git = (GenericInstanceType)readTr; TypeReference genericTr = git.GenericArguments[0]; readMr = readMr.GetMethodReference(base.Session, genericTr); } processor.Emit(loadOpCode, objectVariableDef); //reader. processor.Emit(OpCodes.Ldarg, readerPd); if (IsAutoPackedType(readTr)) { AutoPackType packType = base.GetClass().GetDefaultAutoPackType(readTr); processor.Emit(OpCodes.Ldc_I4, (int)packType); } //reader.ReadXXXX(). processor.Emit(OpCodes.Call, readMr); //obj.Property = result / reader.ReadXXXX(). processor.Emit(OpCodes.Call, setMr); return true; } else { base.LogError($"Reader not found for {readTr.FullName}."); return false; } } /// /// Creates generic write delegates for all currently known write types. /// internal void CreateStaticMethodDelegates() { foreach (KeyValuePair item in StaticReaderMethods) base.GetClass().CreateReadDelegate(item.Value, true); } /// /// Returns if typeRef has a deserializer. /// /// /// /// internal bool HasDeserializer(TypeReference typeRef, bool createMissing) { bool result = (GetInstancedReadMethodReference(typeRef) != null) || (GetStaticReadMethodReference(typeRef) != null); if (!result && createMissing) { if (!base.GetClass().HasNonSerializableAttribute(typeRef.CachedResolve(base.Session))) { MethodReference methodRef = CreateReader(typeRef); result = (methodRef != null); } } return result; } /// /// Returns if typeRef supports auto packing. /// /// /// internal bool IsAutoPackedType(TypeReference typeRef) { return AutoPackedMethods.Contains(typeRef); } /// /// Creates a null check on the first argument and returns a null object if result indicates to do so. /// internal void CreateRetOnNull(ILProcessor processor, ParameterDefinition readerParameterDef, VariableDefinition resultVariableDef, bool useBool) { Instruction endIf = processor.Create(OpCodes.Nop); if (useBool) CreateReadBool(processor, readerParameterDef, resultVariableDef); else CreateReadPackedWhole(processor, readerParameterDef, resultVariableDef); //If (true or == -1) jmp to endIf. True is null. processor.Emit(OpCodes.Ldloc, resultVariableDef); if (useBool) { processor.Emit(OpCodes.Brfalse, endIf); } else { //-1 processor.Emit(OpCodes.Ldc_I4_M1); processor.Emit(OpCodes.Bne_Un_S, endIf); } //Insert null. processor.Emit(OpCodes.Ldnull); //Exit method. processor.Emit(OpCodes.Ret); //End of if check. processor.Append(endIf); } /// /// Creates a call to WriteBoolean with value. /// /// /// /// internal void CreateReadBool(ILProcessor processor, ParameterDefinition readerParameterDef, VariableDefinition localBoolVariableDef) { MethodReference readBoolMethodRef = GetReadMethodReference(base.GetClass().GetTypeReference(typeof(bool))); processor.Emit(OpCodes.Ldarg, readerParameterDef); processor.Emit(readBoolMethodRef.GetCallOpCode(base.Session), readBoolMethodRef); processor.Emit(OpCodes.Stloc, localBoolVariableDef); } /// /// Creates a call to WritePackWhole with value. /// /// /// internal void CreateReadPackedWhole(ILProcessor processor, ParameterDefinition readerParameterDef, VariableDefinition resultVariableDef) { //Reader. processor.Emit(OpCodes.Ldarg, readerParameterDef); //Reader.ReadPackedWhole(). MethodReference readPwMr = base.GetClass().Reader_ReadPackedWhole_MethodRef; processor.Emit(readPwMr.GetCallOpCode(base.Session), readPwMr); processor.Emit(OpCodes.Conv_I4); processor.Emit(OpCodes.Stloc, resultVariableDef); } #region GetReaderMethodReference. /// /// Returns the MethodReference for typeRef. /// /// /// internal MethodReference GetInstancedReadMethodReference(TypeReference typeRef) { string fullName = typeRef.GetFullnameWithoutBrackets(); InstancedReaderMethods.TryGetValue(fullName, out MethodReference methodRef); return methodRef; } /// /// Returns the MethodReference for typeRef. /// /// /// internal MethodReference GetStaticReadMethodReference(TypeReference typeRef) { string fullName = typeRef.GetFullnameWithoutBrackets(); StaticReaderMethods.TryGetValue(fullName, out MethodReference methodRef); return methodRef; } /// /// Returns the MethodReference for typeRef favoring instanced or static. Returns null if not found. /// /// /// /// internal MethodReference GetReadMethodReference(TypeReference typeRef) { MethodReference result; bool favorInstanced = false; if (favorInstanced) { result = GetInstancedReadMethodReference(typeRef); if (result == null) result = GetStaticReadMethodReference(typeRef); } else { result = GetStaticReadMethodReference(typeRef); if (result == null) result = GetInstancedReadMethodReference(typeRef); } return result; } /// /// Returns the MethodReference for typeRef favoring instanced or static. /// /// /// /// internal MethodReference GetOrCreateReadMethodReference(TypeReference typeRef) { #pragma warning disable CS0219 bool favorInstanced = false; #pragma warning restore CS0219 //Try to get existing writer, if not present make one. MethodReference readMethodRef = GetReadMethodReference(typeRef); if (readMethodRef == null) readMethodRef = CreateReader(typeRef); //If still null then return could not be generated. if (readMethodRef == null) { base.LogError($"Could not create deserializer for {typeRef.FullName}."); } //Otherwise, check if generic and create writes for generic pararameters. else if (typeRef.IsGenericInstance) { GenericInstanceType git = (GenericInstanceType)typeRef; foreach (TypeReference item in git.GenericArguments) { MethodReference result = GetOrCreateReadMethodReference(item); if (result == null) { base.LogError($"Could not create deserializer for {item.FullName}."); return null; } } } return readMethodRef; } #endregion /// /// Generates a reader for objectTypeReference if one does not already exist. /// /// /// internal MethodReference CreateReader(TypeReference objectTr) { MethodReference resultMr = null; TypeDefinition objectTypeDef; SerializerType serializerType = base.GetClass().GetSerializerType(objectTr, false, out objectTypeDef); if (serializerType != SerializerType.Invalid) { //Array. if (serializerType == SerializerType.Array) resultMr = CreateArrayReaderMethodReference(objectTr); //Enum. else if (serializerType == SerializerType.Enum) resultMr = CreateEnumReaderMethodDefinition(objectTr); else if (serializerType == SerializerType.Dictionary || serializerType == SerializerType.List || serializerType == SerializerType.ListCache) resultMr = CreateGenericCollectionReaderMethodReference(objectTr, serializerType); //NetworkBehaviour. else if (serializerType == SerializerType.NetworkBehaviour) resultMr = GetNetworkBehaviourReaderMethodReference(objectTr); //Nullable. else if (serializerType == SerializerType.Nullable) resultMr = CreateNullableReaderMethodReference(objectTr); //Class or struct. else if (serializerType == SerializerType.ClassOrStruct) resultMr = CreateClassOrStructReaderMethodReference(objectTr); } //If was not created. if (resultMr == null) RemoveFromStaticReaders(objectTr); return resultMr; } /// /// Removes from static writers. /// private void RemoveFromStaticReaders(TypeReference tr) { base.GetClass().RemoveReaderMethod(tr, false); } /// /// Adds to static writers. /// private void AddToStaticReaders(TypeReference tr, MethodReference mr) { base.GetClass().AddReaderMethod(tr, mr.CachedResolve(base.Session), false, true); } /// /// Generates a reader for objectTypeReference if one does not already exist. /// /// /// private MethodReference CreateEnumReaderMethodDefinition(TypeReference objectTr) { MethodDefinition createdReaderMd = CreateStaticReaderStubMethodDefinition(objectTr); AddToStaticReaders(objectTr, createdReaderMd); ILProcessor processor = createdReaderMd.Body.GetILProcessor(); //Get type reference for enum type. eg byte int TypeReference underlyingTypeRef = objectTr.CachedResolve(base.Session).GetEnumUnderlyingTypeReference(); //Get read method for underlying type. MethodReference readMethodRef = base.GetClass().GetOrCreateReadMethodReference(underlyingTypeRef); if (readMethodRef == null) return null; ParameterDefinition readerParameterDef = createdReaderMd.Parameters[0]; //reader.ReadXXX(). processor.Emit(OpCodes.Ldarg, readerParameterDef); if (base.GetClass().IsAutoPackedType(underlyingTypeRef)) processor.Emit(OpCodes.Ldc_I4, (int)AutoPackType.Packed); processor.Emit(OpCodes.Call, readMethodRef); processor.Emit(OpCodes.Ret); return base.ImportReference(createdReaderMd); } /// /// Creates a read for a class type which inherits NetworkBehaviour. /// /// /// private MethodReference GetNetworkBehaviourReaderMethodReference(TypeReference objectTr) { MethodDefinition createdReaderMd = CreateStaticReaderStubMethodDefinition(objectTr); AddToStaticReaders(objectTr, createdReaderMd); ILProcessor processor = createdReaderMd.Body.GetILProcessor(); TypeReference networkBehaviourTypeRef = base.GetClass().GetTypeReference(typeof(NetworkBehaviour)); processor.Emit(OpCodes.Ldarg_0); processor.Emit(OpCodes.Call, base.GetClass().GetReadMethodReference(networkBehaviourTypeRef)); processor.Emit(OpCodes.Castclass, objectTr); processor.Emit(OpCodes.Ret); return base.ImportReference(createdReaderMd); } /// /// Creates a writer for an array. /// private MethodReference CreateArrayReaderMethodReference(TypeReference objectTr) { ReaderImports ri = base.GetClass(); TypeReference valueTr = objectTr.GetElementType(); //Write not found. if (GetOrCreateReadMethodReference(valueTr) == null) return null; MethodDefinition createdMd = CreateStaticReaderStubMethodDefinition(objectTr); AddToStaticReaders(objectTr, createdMd); //Find instanced writer to use. MethodReference instancedWriteMr = ri.Reader_ReadArray_MethodRef; //Make generic. GenericInstanceMethod writeGim = instancedWriteMr.MakeGenericMethod(new TypeReference[] { valueTr }); CallInstancedReader(createdMd, writeGim); return base.ImportReference(createdMd); } /// /// Creates a reader for a dictionary. /// private MethodReference CreateDictionaryReaderMethodReference(TypeReference objectTr) { ReaderProcessor rp = base.GetClass(); GenericInstanceType genericInstance = (GenericInstanceType)objectTr; base.ImportReference(genericInstance); TypeReference keyTr = genericInstance.GenericArguments[0]; TypeReference valueTr = genericInstance.GenericArguments[1]; /* Try to get instanced first for collection element type, if it doesn't exist then try to * get/or make a one. */ MethodReference keyWriteMr = rp.GetOrCreateReadMethodReference(keyTr); MethodReference valueWriteMr = rp.GetOrCreateReadMethodReference(valueTr); if (keyWriteMr == null || valueWriteMr == null) return null; MethodDefinition createdReaderMd = CreateStaticReaderStubMethodDefinition(objectTr); AddToStaticReaders(objectTr, createdReaderMd); GenericInstanceMethod readDictGim = base.GetClass().Reader_ReadDictionary_MethodRef.MakeGenericMethod(new TypeReference[] { keyTr, valueTr }); CallInstancedReader(createdReaderMd, readDictGim); return base.ImportReference(createdReaderMd); } /// /// Creates a writer for a variety of generic collections. /// private MethodReference CreateGenericCollectionReaderMethodReference(TypeReference objectTr, SerializerType st) { ReaderImports ri = base.GetClass(); //Make value field generic. GenericInstanceType genericInstance = (GenericInstanceType)objectTr; base.ImportReference(genericInstance); TypeReference valueTr = genericInstance.GenericArguments[0]; List genericArguments = new List(); //Make sure all arguments have writers. foreach (TypeReference gaTr in genericInstance.GenericArguments) { //Writer not found. if (GetOrCreateReadMethodReference(gaTr) == null) { base.LogError($"Reader could not be found or created for type {gaTr.FullName}."); return null; } genericArguments.Add(gaTr); } MethodReference valueWriteMr = GetOrCreateReadMethodReference(valueTr); if (valueWriteMr == null) return null; MethodDefinition createdMd = CreateStaticReaderStubMethodDefinition(objectTr); AddToStaticReaders(objectTr, createdMd); //Find instanced writer to use. MethodReference instancedReadMr; if (st == SerializerType.Dictionary) instancedReadMr = ri.Reader_ReadDictionary_MethodRef; else if (st == SerializerType.List) instancedReadMr = ri.Reader_ReadList_MethodRef; else if (st == SerializerType.ListCache) instancedReadMr = ri.Reader_ReadListCache_MethodRef; else instancedReadMr = null; //Not found. if (instancedReadMr == null) { base.LogError($"Instanced reader not found for SerializerType {st} on object {objectTr.Name}."); return null; } //Make generic. GenericInstanceMethod writeGim = instancedReadMr.MakeGenericMethod(genericArguments.ToArray()); CallInstancedReader(createdMd, writeGim); return base.ImportReference(createdMd); } /// /// Calls an instanced writer from a static writer. /// private void CallInstancedReader(MethodDefinition staticReaderMd, MethodReference instancedReaderMr) { ParameterDefinition readerPd = staticReaderMd.Parameters[0]; ILProcessor processor = staticReaderMd.Body.GetILProcessor(); processor.Emit(OpCodes.Ldarg, readerPd); processor.Emit(instancedReaderMr.GetCallOpCode(base.Session), instancedReaderMr); processor.Emit(OpCodes.Ret); } ///// ///// Create a reader for a list. ///// //private MethodReference CreateGenericTypeReader(TypeReference objectTr, SerializerType st) //{ // ReaderProcessor rp = base.GetClass(); // if (st != SerializerType.List && st != SerializerType.ListCache) // { // base.LogError($"Reader SerializerType {st} is not implemented"); // return null; // } // GenericInstanceType genericInstance = (GenericInstanceType)objectTr; // base.ImportReference(genericInstance); // TypeReference elementTr = genericInstance.GenericArguments[0]; // /* Try to get instanced first for collection element type, if it doesn't exist then try to // * get/or make a one. */ // MethodReference elementReadMr = rp.GetOrCreateReadMethodReference(elementTr); // if (elementReadMr == null) // return null; // TypeReference readerMethodTr = null; // if (st == SerializerType.List) // readerMethodTr = base.GetClass().GetTypeReference(typeof(List<>)); // else if (st == SerializerType.ListCache) // readerMethodTr = base.GetClass().GetTypeReference(typeof(ListCache<>)); // MethodReference readerMd = rp.GetReadMethodReference(readerMethodTr); // MethodDefinition typedReaderMd = CreateStaticReaderStubMethodDefinition(objectTr); // AddToStaticReaders(objectTr, typedReaderMd); // ParameterDefinition readerPd = typedReaderMd.Parameters[0]; // //Find add method for list. // MethodReference readerGim = readerMd.GetMethodReference(base.Session, elementTr); // ILProcessor processor = readerMd.CachedResolve(base.Session).Body.GetILProcessor(); // processor.Emit(OpCodes.Ldarg, readerPd); // processor.Emit(OpCodes.Call, readerGim); // return elementReadMr; //} /// /// Creates a reader method for a struct or class objectTypeRef. /// /// /// private MethodReference CreateNullableReaderMethodReference(TypeReference objectTr) { ReaderProcessor rp = base.GetClass(); GenericInstanceType objectGit = objectTr as GenericInstanceType; TypeReference valueTr = objectGit.GenericArguments[0]; //Make sure object has a ctor. MethodDefinition objectCtorMd = objectTr.GetConstructor(base.Session, 1); if (objectCtorMd == null) { base.LogError($"{objectTr.Name} can't be deserialized because the nullable type does not have a constructor."); return null; } //Get the reader for the value. MethodReference valueReaderMr = rp.GetOrCreateReadMethodReference(valueTr); if (valueReaderMr == null) return null; TypeDefinition objectTd = objectTr.CachedResolve(base.Session); MethodDefinition createdReaderMd = CreateStaticReaderStubMethodDefinition(objectTr); AddToStaticReaders(objectTr, createdReaderMd); ILProcessor processor = createdReaderMd.Body.GetILProcessor(); ParameterDefinition readerPd = createdReaderMd.Parameters[0]; // create local for return value VariableDefinition resultVd = base.GetClass().CreateVariable(createdReaderMd, objectTr); //Read if null into boolean. VariableDefinition nullBoolVd = createdReaderMd.CreateVariable(base.Session, typeof(bool)); rp.CreateReadBool(processor, readerPd, nullBoolVd); Instruction afterReturnNullInst = processor.Create(OpCodes.Nop); processor.Emit(OpCodes.Ldloc, nullBoolVd); processor.Emit(OpCodes.Brfalse, afterReturnNullInst); //Return a null result. base.GetClass().SetVariableDefinitionFromObject(processor, resultVd, objectTd); processor.Emit(OpCodes.Ldloc, resultVd); processor.Emit(OpCodes.Ret); processor.Append(afterReturnNullInst); MethodReference initMr = objectCtorMd.MakeHostInstanceGeneric(base.Session, objectGit); processor.Emit(OpCodes.Ldarg, readerPd); //If an auto pack method then insert default value. if (rp.IsAutoPackedType(valueTr)) { AutoPackType packType = base.GetClass().GetDefaultAutoPackType(valueTr); processor.Emit(OpCodes.Ldc_I4, (int)packType); } processor.Emit(OpCodes.Call, valueReaderMr); processor.Emit(OpCodes.Newobj, initMr); processor.Emit(OpCodes.Ret); return base.ImportReference(createdReaderMd); } /// /// Creates a reader method for a struct or class objectTypeRef. /// /// /// private MethodReference CreateClassOrStructReaderMethodReference(TypeReference objectTr) { MethodDefinition createdReaderMd = CreateStaticReaderStubMethodDefinition(objectTr); AddToStaticReaders(objectTr, createdReaderMd); TypeDefinition objectTypeDef = objectTr.CachedResolve(base.Session); ILProcessor processor = createdReaderMd.Body.GetILProcessor(); ParameterDefinition readerParameterDef = createdReaderMd.Parameters[0]; // create local for return value VariableDefinition objectVariableDef = base.GetClass().CreateVariable(createdReaderMd, objectTr); //If not a value type create a return null check. if (!objectTypeDef.IsValueType) { VariableDefinition nullVariableDef = base.GetClass().CreateVariable(createdReaderMd, typeof(bool)); //Load packed whole value into sizeVariableDef, exit if null indicator. base.GetClass().CreateRetOnNull(processor, readerParameterDef, nullVariableDef, true); } /* If here then not null. */ //Make a new instance of object type and set to objectVariableDef. base.GetClass().SetVariableDefinitionFromObject(processor, objectVariableDef, objectTypeDef); if (!ReadFieldsAndProperties(createdReaderMd, readerParameterDef, objectVariableDef, objectTr)) return null; /* //codegen scriptableobjects seem to climb too high up to UnityEngine.Object when * creating serializers/deserialized. Make sure this is not possible. */ //Load result and return it. processor.Emit(OpCodes.Ldloc, objectVariableDef); processor.Emit(OpCodes.Ret); return base.ImportReference(createdReaderMd); } /// /// Reads all fields of objectTypeRef. /// private bool ReadFieldsAndProperties(MethodDefinition readerMd, ParameterDefinition readerPd, VariableDefinition objectVd, TypeReference objectTr) { ReaderProcessor rp = base.GetClass(); //This probably isn't needed but I'm too afraid to remove it. if (objectTr.Module != base.Module) objectTr = base.ImportReference(objectTr.CachedResolve(base.Session)); //Fields. foreach (FieldDefinition fieldDef in objectTr.FindAllSerializableFields(base.Session , ReaderProcessor.EXCLUDED_AUTO_SERIALIZER_TYPES, ReaderProcessor.EXCLUDED_ASSEMBLY_PREFIXES)) { FieldReference importedFr = base.ImportReference(fieldDef); if (GetReadMethod(fieldDef.FieldType, out MethodReference readMr)) rp.CreateReadIntoClassOrStruct(readerMd, readerPd, readMr, objectVd, importedFr); } //Properties. foreach (PropertyDefinition propertyDef in objectTr.FindAllSerializableProperties(base.Session , ReaderProcessor.EXCLUDED_AUTO_SERIALIZER_TYPES, ReaderProcessor.EXCLUDED_ASSEMBLY_PREFIXES)) { if (GetReadMethod(propertyDef.PropertyType, out MethodReference readMr)) { MethodReference setMr = base.Module.ImportReference(propertyDef.SetMethod); rp.CreateReadIntoClassOrStruct(readerMd, readerPd, readMr, objectVd, setMr, propertyDef.PropertyType); } } //Gets or creates writer method and outputs it. Returns true if method is found or created. bool GetReadMethod(TypeReference tr, out MethodReference readMr) { tr = base.ImportReference(tr); readMr = rp.GetOrCreateReadMethodReference(tr); return (readMr != null); } return true; } /// /// Creates the stub for a new reader method. /// /// /// public MethodDefinition CreateStaticReaderStubMethodDefinition(TypeReference objectTypeRef, string nameExtension = WriterProcessor.GENERATED_WRITER_NAMESPACE) { string methodName = $"{GENERATED_READ_PREFIX}{objectTypeRef.FullName}{nameExtension}s"; // create new reader for this type TypeDefinition readerTypeDef = base.GetClass().GetOrCreateClass(out _, GENERATED_TYPE_ATTRIBUTES, GENERATED_READERS_CLASS_NAME, null); MethodDefinition readerMethodDef = readerTypeDef.AddMethod(methodName, ReaderProcessor.GENERATED_METHOD_ATTRIBUTES, objectTypeRef); base.GetClass().CreateParameter(readerMethodDef, base.GetClass().Reader_TypeRef, "reader"); readerMethodDef.Body.InitLocals = true; return readerMethodDef; } } }