using FishNet.CodeGenerating.Helping.Extension; using FishNet.CodeGenerating.ILCore; using FishNet.Connection; using FishNet.Serializing; using MonoFN.Cecil; using MonoFN.Cecil.Cil; using System; using System.Collections.Generic; using System.Reflection; using UnityEngine; namespace FishNet.CodeGenerating.Helping { internal class ReaderHelper { #region Reflection references. internal TypeReference PooledReader_TypeRef; internal TypeReference Reader_TypeRef; internal TypeReference NetworkConnection_TypeRef; internal MethodReference PooledReader_ReadNetworkBehaviour_MethodRef; private readonly Dictionary _instancedReaderMethods = new Dictionary(new TypeReferenceComparer()); private readonly Dictionary _staticReaderMethods = new Dictionary(new TypeReferenceComparer()); private HashSet _autoPackedMethods = new HashSet(new TypeReferenceComparer()); private MethodReference Reader_ReadPackedWhole_MethodRef; internal MethodReference Reader_ReadDictionary_MethodRef; internal MethodReference Reader_ReadToCollection_MethodRef; #endregion #region Const. internal const string READ_PREFIX = "Read"; /// /// Types to exclude from being scanned for auto serialization. /// public static System.Type[] EXCLUDED_AUTO_SERIALIZER_TYPES => WriterHelper.EXCLUDED_AUTO_SERIALIZER_TYPES; /// /// Types to exclude from being scanned for auto serialization. /// public static string[] EXCLUDED_ASSEMBLY_PREFIXES => WriterHelper.EXCLUDED_ASSEMBLY_PREFIXES; #endregion /// /// Imports references needed by this helper. /// /// /// internal bool ImportReferences() { PooledReader_TypeRef = CodegenSession.ImportReference(typeof(PooledReader)); Reader_TypeRef = CodegenSession.ImportReference(typeof(Reader)); NetworkConnection_TypeRef = CodegenSession.ImportReference(typeof(NetworkConnection)); Type pooledReaderType = typeof(PooledReader); foreach (MethodInfo methodInfo in pooledReaderType.GetMethods()) { /* Special methods. */ //ReadPackedWhole. if (methodInfo.Name == nameof(PooledReader.ReadPackedWhole)) { Reader_ReadPackedWhole_MethodRef = CodegenSession.ImportReference(methodInfo); continue; } //ReadToCollection. else if (methodInfo.Name == nameof(PooledReader.ReadArray)) { Reader_ReadToCollection_MethodRef = CodegenSession.ImportReference(methodInfo); continue; } //ReadDictionary. else if (methodInfo.Name == nameof(PooledReader.ReadDictionary)) { Reader_ReadDictionary_MethodRef = CodegenSession.ImportReference(methodInfo); continue; } else if (CodegenSession.GeneralHelper.CodegenExclude(methodInfo)) continue; //Generic methods are not supported. else if (methodInfo.IsGenericMethod) continue; //Not long enough to be a write method. else if (methodInfo.Name.Length < READ_PREFIX.Length) continue; //Method name doesn't start with writePrefix. else if (methodInfo.Name.Substring(0, READ_PREFIX.Length) != READ_PREFIX) continue; ParameterInfo[] parameterInfos = methodInfo.GetParameters(); //Can have at most one parameter for packing. if (parameterInfos.Length > 1) continue; //If has one parameter make sure it's a packing type. bool autoPackMethod = false; if (parameterInfos.Length == 1) { autoPackMethod = (parameterInfos[0].ParameterType == typeof(AutoPackType)); if (!autoPackMethod) continue; } /* TypeReference for the return type * of the read method. */ TypeReference typeRef = CodegenSession.ImportReference(methodInfo.ReturnType); MethodReference methodRef = CodegenSession.ImportReference(methodInfo); /* If here all checks pass. */ AddReaderMethod(typeRef, methodRef, true, true); if (autoPackMethod) _autoPackedMethods.Add(typeRef); } Type readerExtensionsType = typeof(ReaderExtensions); foreach (MethodInfo methodInfo in readerExtensionsType.GetMethods()) { if (CodegenSession.GeneralHelper.CodegenExclude(methodInfo)) continue; //Generic methods are not supported. if (methodInfo.IsGenericMethod) continue; //Not static. if (!methodInfo.IsStatic) continue; //Not long enough to be a write method. if (methodInfo.Name.Length < READ_PREFIX.Length) continue; //Method name doesn't start with writePrefix. if (methodInfo.Name.Substring(0, READ_PREFIX.Length) != READ_PREFIX) continue; ParameterInfo[] parameterInfos = methodInfo.GetParameters(); //Can have at most one parameter for packing. if (parameterInfos.Length > 2) continue; //If has 2 parameters make sure it's a packing type. bool autoPackMethod = false; if (parameterInfos.Length == 2) { autoPackMethod = (parameterInfos[1].ParameterType == typeof(AutoPackType)); if (!autoPackMethod) continue; } /* TypeReference for the return type * of the read method. */ TypeReference typeRef = CodegenSession.ImportReference(methodInfo.ReturnType); MethodReference methodRef = CodegenSession.ImportReference(methodInfo); /* If here all checks pass. */ AddReaderMethod(typeRef, methodRef, false, true); } return true; } /// /// Creates generic write delegates for all currently known write types. /// internal bool CreateGenericDelegates() { bool modified = false; /* Only write statics. This will include extensions and generated. */ foreach (KeyValuePair item in _staticReaderMethods) { if (FishNetILPP.CODEGEN_THIS_NAMESPACE.Length == 0 || item.Key.FullName.Contains(FishNetILPP.CODEGEN_THIS_NAMESPACE)) { CodegenSession.GenericReaderHelper.CreateReadDelegate(item.Value); modified = true; } } return modified; } /// /// 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 (!CodegenSession.GeneralHelper.HasNonSerializableAttribute(typeRef.CachedResolve())) { MethodReference methodRef = CodegenSession.ReaderGenerator.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 = GetFavoredReadMethodReference(CodegenSession.GeneralHelper.GetTypeReference(typeof(bool)), true); processor.Emit(OpCodes.Ldarg, readerParameterDef); processor.Emit(OpCodes.Callvirt, 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(). processor.Emit(OpCodes.Callvirt, Reader_ReadPackedWhole_MethodRef); processor.Emit(OpCodes.Conv_I4); processor.Emit(OpCodes.Stloc, resultVariableDef); } #region GetReaderMethodReference. /// /// Returns the MethodReference for typeRef. /// /// /// internal MethodReference GetInstancedReadMethodReference(TypeReference typeRef) { _instancedReaderMethods.TryGetValue(typeRef, out MethodReference methodRef); return methodRef; } /// /// Returns the MethodReference for typeRef. /// /// /// internal MethodReference GetStaticReadMethodReference(TypeReference typeRef) { _staticReaderMethods.TryGetValue(typeRef, out MethodReference methodRef); return methodRef; } /// /// Returns the MethodReference for typeRef favoring instanced or static. Returns null if not found. /// /// /// /// internal MethodReference GetFavoredReadMethodReference(TypeReference typeRef, bool favorInstanced) { MethodReference result; 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 GetOrCreateFavoredReadMethodReference(TypeReference typeRef, bool favorInstanced) { //Try to get existing writer, if not present make one. MethodReference readMethodRef = GetFavoredReadMethodReference(typeRef, favorInstanced); if (readMethodRef == null) readMethodRef = CodegenSession.ReaderGenerator.CreateReader(typeRef); if (readMethodRef == null) CodegenSession.LogError($"Could not create deserializer for {typeRef.FullName}."); return readMethodRef; } #endregion /// /// Adds typeRef, methodDef to instanced or readerMethods. /// /// /// /// internal void AddReaderMethod(TypeReference typeRef, MethodReference methodRef, bool instanced, bool useAdd) { Dictionary dict = (instanced) ? _instancedReaderMethods : _staticReaderMethods; if (useAdd) dict.Add(typeRef, methodRef); else dict[typeRef] = methodRef; } /// /// Removes typeRef from static/instanced reader methods. /// internal void RemoveReaderMethod(TypeReference typeRef, bool instanced) { Dictionary dict = (instanced) ? _instancedReaderMethods : _staticReaderMethods; dict.Remove(typeRef); } /// /// 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 readerMethodRef = GetFavoredReadMethodReference(readTypeRef, true); if (readerMethodRef != null) { //Make a local variable. createdVariableDef = CodegenSession.GeneralHelper.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 = CodegenSession.GeneralHelper.GetDefaultAutoPackType(readTypeRef); insts.Add(processor.Create(OpCodes.Ldc_I4, (int)packType)); } insts.Add(processor.Create(OpCodes.Call, readerMethodRef)); //Store into local variable. insts.Add(processor.Create(OpCodes.Stloc, createdVariableDef)); return insts; } else { CodegenSession.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; processor.Emit(loadOpCode, objectVd); //reader. processor.Emit(OpCodes.Ldarg, readerPd); if (IsAutoPackedType(valueFr.FieldType)) { AutoPackType packType = CodegenSession.GeneralHelper.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 { CodegenSession.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; processor.Emit(loadOpCode, objectVariableDef); //reader. processor.Emit(OpCodes.Ldarg, readerPd); if (IsAutoPackedType(readTr)) { AutoPackType packType = CodegenSession.GeneralHelper.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 { CodegenSession.LogError($"Reader not found for {readTr.FullName}."); return false; } } } }