using FishNet.CodeGenerating.Extension; using FishNet.CodeGenerating.Helping.Extension; using FishNet.CodeGenerating.ILCore; using FishNet.Object; using FishNet.Serializing; using FishNet.Utility; using FishNet.Utility.Performance; using MonoFN.Cecil; using MonoFN.Cecil.Cil; using MonoFN.Cecil.Rocks; using System; using System.Collections.Generic; using GameKit.Dependencies.Utilities; using SR = System.Reflection; using UnityDebug = UnityEngine.Debug; namespace FishNet.CodeGenerating.Helping { internal class WriterProcessor : CodegenBase { #region Reflection references. public readonly Dictionary InstancedWriterMethods = new(); public readonly Dictionary StaticWriterMethods = new(); public TypeDefinition GeneratedWriterClassTypeDef; public MethodDefinition GeneratedWriterOnLoadMethodDef; #endregion #region Misc. /// /// TypeReferences which have already had delegates made for. /// private HashSet _delegatedTypes = new(); #endregion #region Const. /// /// Namespace to use for generated serializers and delegates. /// public const string GENERATED_WRITER_NAMESPACE = "FishNet.Serializing.Generated"; /// /// Name to use for generated serializers class. /// public const string GENERATED_WRITERS_CLASS_NAME = "GeneratedWriters___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 = "InitializeOnce"; /// /// Attributes to use for InitializeOnce method within generated serializer classes. /// public const MethodAttributes INITIALIZEONCE_METHOD_ATTRIBUTES = MethodAttributes.Static | MethodAttributes.Private | MethodAttributes.HideBySig; /// /// Attritbutes to use for generated serializers. /// public const MethodAttributes GENERATED_METHOD_ATTRIBUTES = MethodAttributes.Static | MethodAttributes.Public | MethodAttributes.HideBySig; /// /// Attributes required for custom serializer classes. /// public const TypeAttributes CUSTOM_SERIALIZER_TYPEDEF_ATTRIBUTES = TypeAttributes.Sealed | TypeAttributes.Abstract; /// /// Prefix all built-in and user created write methods should begin with. /// internal const string CUSTOM_WRITER_PREFIX = "Write"; /// /// Attribute fullname which indicates a default writer. /// public string DEFAULT_WRITER_ATTRIBUTE_FULLNAME => typeof(DefaultWriterAttribute).FullName; /// /// Types to exclude from being scanned for auto serialization. /// public static readonly Type[] EXCLUDED_AUTO_SERIALIZER_TYPES = new Type[] { typeof(NetworkBehaviour) }; /// /// Types within assemblies which begin with these prefixes will not have serializers created for them. /// public static readonly string[] EXCLUDED_ASSEMBLY_PREFIXES = new string[] { "UnityEngine.", "Unity.Mathmatics" }; #endregion public override bool ImportReferences() => true; /// /// Processes data. To be used after everything else has called ImportReferences. /// /// public bool Process() { GeneralHelper gh = GetClass(); CreateGeneratedWritersClass(); FindInstancedWriters(); CreateInstancedWriterExtensions(); // Creates class for generated writers, and init on load method. void CreateGeneratedWritersClass() { GeneratedWriterClassTypeDef = gh.GetOrCreateClass(out _, GENERATED_TYPE_ATTRIBUTES, GENERATED_WRITERS_CLASS_NAME, null, GENERATED_WRITER_NAMESPACE); /* If constructor isn't set then try to get or create it * and also add it to methods if were created. */ GeneratedWriterOnLoadMethodDef = gh.GetOrCreateMethod(GeneratedWriterClassTypeDef, out _, INITIALIZEONCE_METHOD_ATTRIBUTES, INITIALIZEONCE_METHOD_NAME, Module.TypeSystem.Void); ILProcessor pp = GeneratedWriterOnLoadMethodDef.Body.GetILProcessor(); pp.Emit(OpCodes.Ret); gh.CreateRuntimeInitializeOnLoadMethodAttribute(GeneratedWriterOnLoadMethodDef); } // Finds all instanced writers and autopack types. void FindInstancedWriters() { Type pooledWriterType = typeof(PooledWriter); foreach (SR.MethodInfo methodInfo in pooledWriterType.GetMethods()) { if (!HasDefaultSerializerAttribute()) continue; MethodReference methodRef = ImportReference(methodInfo); /* TypeReference for the first parameter in the write method. * The first parameter will always be the type written. */ TypeReference typeRef = ImportReference(methodRef.Parameters[0].ParameterType); /* If here all checks pass. */ AddWriterMethod(typeRef, methodRef, true, true); bool HasDefaultSerializerAttribute() { foreach (SR.CustomAttributeData item in methodInfo.CustomAttributes) { if (item.AttributeType.FullName == DEFAULT_WRITER_ATTRIBUTE_FULLNAME) return true; } return false; } } } return true; } /// /// Creates writer extension methods for built-in writers. /// private void CreateInstancedWriterExtensions() { if (!FishNetILPP.IsFishNetAssembly(Session)) return; GeneralHelper gh = GetClass(); WriterProcessor gwh = GetClass(); // List staticReaders = new List(); foreach (KeyValuePair item in InstancedWriterMethods) { MethodReference instancedWriteMr = item.Value; if (instancedWriteMr.HasGenericParameters) continue; TypeReference valueTr = instancedWriteMr.Parameters[0].ParameterType; MethodDefinition md = new($"InstancedExtension___{instancedWriteMr.Name}", GENERATED_METHOD_ATTRIBUTES, Module.TypeSystem.Void); // Add extension parameter. ParameterDefinition writerPd = gh.CreateParameter(md, typeof(Writer), "writer"); // Add parameters needed by instanced writer. List otherPds = md.CreateParameters(Session, instancedWriteMr.CachedResolve(Session)); gh.MakeExtensionMethod(md); // gwh.GeneratedWriterClassTypeDef.Methods.Add(md); ILProcessor processor = md.Body.GetILProcessor(); // Load writer. processor.Emit(OpCodes.Ldarg, writerPd); // Load args. foreach (ParameterDefinition pd in otherPds) processor.Emit(OpCodes.Ldarg, pd); // Call instanced. processor.Emit(instancedWriteMr.GetCallOpCode(Session), instancedWriteMr); processor.Emit(OpCodes.Ret); AddWriterMethod(valueTr, md, false, true); } } /// /// Adds typeRef, methodDef to Instanced or Static write methods. /// public void AddWriterMethod(TypeReference typeRef, MethodReference methodRef, bool instanced, bool useAdd) { Dictionary dict = instanced ? InstancedWriterMethods : StaticWriterMethods; string fullName = typeRef.GetFullnameWithoutBrackets(); if (useAdd) { if (dict.ContainsKey(fullName)) LogError($"Key {fullName} already exists. First method is {dict[fullName].Name}, new method is {methodRef.Name}."); else dict.Add(fullName, methodRef); } else { dict[fullName] = methodRef; } } /// /// Removes typeRef from Instanced or Static write methods. /// internal void RemoveWriterMethod(TypeReference typeRef, bool instanced) { Dictionary dict = instanced ? InstancedWriterMethods : StaticWriterMethods; string fullName = typeRef.GetFullnameWithoutBrackets(); dict.Remove(fullName); } /// /// Creates Write delegates for known static methods. /// public void CreateInitializeDelegates() { foreach (KeyValuePair item in StaticWriterMethods) GetClass().CreateInitializeDelegate(item.Value); } /// /// Creates a Write delegate for writeMethodRef and places it within the generated reader/writer constructor. /// /// private void CreateInitializeDelegate(MethodReference writeMr) { GeneralHelper gh = GetClass(); WriterImports wi = GetClass(); /* If a global serializer is declared for the type * and the method is not the declared serializer then * exit early. */ if (DeclaresUseGlobalCustomSerializer(writeMr.Parameters[1].ParameterType) && writeMr.Name.StartsWith(UtilityConstants.GeneratedWriterPrefix)) return; // Check if ret already exist, if so remove it; ret will be added on again in this method. if (GeneratedWriterOnLoadMethodDef.Body.Instructions.Count != 0) { int lastIndex = GeneratedWriterOnLoadMethodDef.Body.Instructions.Count - 1; if (GeneratedWriterOnLoadMethodDef.Body.Instructions[lastIndex].OpCode == OpCodes.Ret) GeneratedWriterOnLoadMethodDef.Body.Instructions.RemoveAt(lastIndex); } ILProcessor processor = GeneratedWriterOnLoadMethodDef.Body.GetILProcessor(); TypeReference dataTypeRef; dataTypeRef = writeMr.Parameters[1].ParameterType; // Check if writer already exist. if (_delegatedTypes.Contains(dataTypeRef)) { LogError($"Generic write already created for {dataTypeRef.FullName}."); return; } else { _delegatedTypes.Add(dataTypeRef); } /* Create a Action delegate. * May also be Action delegate * for packed types. */ processor.Emit(OpCodes.Ldnull); processor.Emit(OpCodes.Ldftn, writeMr); GenericInstanceType actionGenericInstance; MethodReference actionConstructorInstanceMethodRef; actionGenericInstance = gh.ActionT2_TypeRef.MakeGenericInstanceType(wi.Writer_TypeRef, dataTypeRef); actionConstructorInstanceMethodRef = gh.ActionT2Constructor_MethodRef.MakeHostInstanceGeneric(Session, actionGenericInstance); processor.Emit(OpCodes.Newobj, actionConstructorInstanceMethodRef); // Call delegate to GenericWriter.Write GenericInstanceType genericInstance = wi.GenericWriter_TypeRef.MakeGenericInstanceType(dataTypeRef); MethodReference genericWriteMethodRef = wi.GenericWriter_Write_MethodRef.MakeHostInstanceGeneric(Session, genericInstance); processor.Emit(OpCodes.Call, genericWriteMethodRef); processor.Emit(OpCodes.Ret); } /// /// Returns if typeRef has a serializer. /// /// /// internal bool HasSerializer(TypeReference typeRef, bool createMissing) { bool result = GetInstancedWriteMethodReference(typeRef) != null || GetStaticWriteMethodReference(typeRef) != null || DeclaresUseGlobalCustomSerializer(typeRef); if (!result && createMissing) { if (!GetClass().HasNonSerializableAttribute(typeRef.CachedResolve(Session))) { string traceText = TypeReferenceTraceText(typeRef); MethodReference methodRef = CreateWriter(typeRef, traceText); result = methodRef != null; } } return result; } #region GetWriterMethodReference. /// /// Returns the MethodReference for typeRef. /// /// /// internal MethodReference GetInstancedWriteMethodReference(TypeReference typeRef) { string fullName = typeRef.GetFullnameWithoutBrackets(); InstancedWriterMethods.TryGetValue(fullName, out MethodReference methodRef); return methodRef; } /// /// Returns the MethodReference for typeRef. /// /// /// internal MethodReference GetStaticWriteMethodReference(TypeReference typeRef) { string fullName = typeRef.GetFullnameWithoutBrackets(); StaticWriterMethods.TryGetValue(fullName, out MethodReference methodRef); return methodRef; } /// /// Returns the MethodReference for typeRef favoring instanced or static. /// /// /// /// internal MethodReference GetWriteMethodReference(TypeReference typeRef) { bool favorInstanced = false; MethodReference result; if (favorInstanced) { result = GetInstancedWriteMethodReference(typeRef); if (result == null) result = GetStaticWriteMethodReference(typeRef); } else { result = GetStaticWriteMethodReference(typeRef); if (result == null) result = GetInstancedWriteMethodReference(typeRef); } return result; } /// /// Gets the write MethodRef for typeRef, or tries to create it if not present. /// /// /// internal MethodReference GetOrCreateWriteMethodReference(TypeReference typeRef, string traceText) { // Try to get existing writer, if not present make one. MethodReference writeMethodRef = GetWriteMethodReference(typeRef); if (writeMethodRef == null) writeMethodRef = CreateWriter(typeRef, traceText); // If still null then return could not be generated. if (writeMethodRef == null) { LogError($"Could not create serializer 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 = GetOrCreateWriteMethodReference(item, traceText); if (result == null) { LogError($"Could not create serializer for {item.FullName}."); return null; } } } return writeMethodRef; } #endregion /// /// Creates a PooledWriter within the body/ and returns its variable index. /// EG: PooledWriter writer = WriterPool.RetrieveWriter(); /// internal VariableDefinition CreatePooledWriter(MethodDefinition methodDef, int length) { VariableDefinition resultVd; List insts = CreatePooledWriter(methodDef, length, out resultVd); ILProcessor processor = methodDef.Body.GetILProcessor(); processor.Add(insts); return resultVd; } /// /// Creates a PooledWriter within the body/ and returns its variable index. /// EG: PooledWriter writer = WriterPool.RetrieveWriter(); /// /// /// /// internal List CreatePooledWriter(MethodDefinition methodDef, int length, out VariableDefinition resultVd) { WriterImports wi = GetClass(); List insts = new(); ILProcessor processor = methodDef.Body.GetILProcessor(); resultVd = GetClass().CreateVariable(methodDef, wi.PooledWriter_TypeRef); // If length is specified then pass in length. if (length > 0) { insts.Add(processor.Create(OpCodes.Ldc_I4, length)); insts.Add(processor.Create(OpCodes.Call, wi.WriterPool_GetWriterLength_MethodRef)); } // Use parameter-less method if no length. else { insts.Add(processor.Create(OpCodes.Call, wi.WriterPool_GetWriter_MethodRef)); } // Set value to variable definition. insts.Add(processor.Create(OpCodes.Stloc, resultVd)); return insts; } /// /// Calls Dispose on a PooledWriter. /// EG: writer.Dispose(); /// /// /// internal List DisposePooledWriter(MethodDefinition methodDef, VariableDefinition writerDefinition) { WriterImports wi = GetClass(); List insts = new(); ILProcessor processor = methodDef.Body.GetILProcessor(); insts.Add(processor.Create(OpCodes.Ldloc, writerDefinition)); insts.Add(processor.Create(wi.PooledWriter_Dispose_MethodRef.GetCallOpCode(Session), wi.PooledWriter_Dispose_MethodRef)); return insts; } /// /// Creates a null check on the second argument using a boolean. /// internal void CreateRetOnNull(ILProcessor processor, ParameterDefinition writerParameterDef, ParameterDefinition checkedParameterDef) { Instruction endIf = processor.Create(OpCodes.Nop); // If (value) jmp to endIf. processor.Emit(OpCodes.Ldarg, checkedParameterDef); processor.Emit(OpCodes.Brtrue, endIf); // writer.WriteBool CreateWriteBool(processor, writerParameterDef, true); // Exit method. processor.Emit(OpCodes.Ret); // End of if check. processor.Append(endIf); } /// /// Creates a call to WriteBoolean with value. /// /// /// /// internal void CreateWriteBool(ILProcessor processor, ParameterDefinition writerParameterDef, bool value) { MethodReference writeBoolMethodRef = GetWriteMethodReference(GetClass().GetTypeReference(typeof(bool))); processor.Emit(OpCodes.Ldarg, writerParameterDef); int intValue = value ? 1 : 0; processor.Emit(OpCodes.Ldc_I4, intValue); processor.Emit(writeBoolMethodRef.GetCallOpCode(Session), writeBoolMethodRef); } /// /// Returns if a type should use a declared/custom serializer globally. /// public bool DeclaresUseGlobalCustomSerializer(TypeReference dataTypeRef) { return dataTypeRef.CachedResolve(Session).HasCustomAttribute(); } /// /// Creates a Write call on a PooledWriter variable for parameterDef. /// EG: writer.WriteBool(xxxxx); /// internal List CreateWriteInstructions(MethodDefinition methodDef, object pooledWriterDef, ParameterDefinition valueParameterDef, MethodReference writeMr) { List insts = new(); ILProcessor processor = methodDef.Body.GetILProcessor(); if (writeMr != null) { bool declaresUseGlobalSerializer = DeclaresUseGlobalCustomSerializer(valueParameterDef.ParameterType); if (pooledWriterDef is VariableDefinition) { insts.Add(processor.Create(OpCodes.Ldloc, (VariableDefinition)pooledWriterDef)); } else if (pooledWriterDef is ParameterDefinition) { insts.Add(processor.Create(OpCodes.Ldarg, (ParameterDefinition)pooledWriterDef)); } else { LogError($"{pooledWriterDef.GetType().FullName} is not a valid writerDef. Type must be VariableDefinition or ParameterDefinition."); return new(); } insts.Add(processor.Create(OpCodes.Ldarg, valueParameterDef)); TypeReference valueTr = valueParameterDef.ParameterType; /* 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]; writeMr = writeMr.GetMethodReference(Session, genericTr); } if (declaresUseGlobalSerializer) { // Switch out to use WriteUnpacked instead. writeMr = GetClass().Writer_Write_MethodRef.GetMethodReference(Session, valueTr); } insts.Add(processor.Create(OpCodes.Call, writeMr)); return insts; } else { LogError($"Writer not found for {valueParameterDef.ParameterType.FullName}."); return new(); } } /// /// Creates a Write call on a PooledWriter variable for parameterDef. /// EG: writer.WriteBool(xxxxx); /// internal void CreateWrite(MethodDefinition methodDef, object writerDef, ParameterDefinition valuePd, MethodReference writeMr) { List insts = CreateWriteInstructions(methodDef, writerDef, valuePd, writeMr); ILProcessor processor = methodDef.Body.GetILProcessor(); processor.Add(insts); } /// /// Creates a Write call to a writer. /// EG: StaticClass.WriteBool(xxxxx); /// /// /// internal void CreateWrite(MethodDefinition writerMd, ParameterDefinition encasingValuePd, FieldDefinition memberValueFd, MethodReference writeMr) { if (writeMr != null) { bool declaresUseGlobalSerializer = DeclaresUseGlobalCustomSerializer(memberValueFd.FieldType) || DeclaresUseGlobalCustomSerializer(encasingValuePd.ParameterType); ILProcessor processor = writerMd.Body.GetILProcessor(); ParameterDefinition writerPd = writerMd.Parameters[0]; /* If generic then find write class for * data type. Currently we only support one generic * for this. */ if (memberValueFd.FieldType.IsGenericInstance) { GenericInstanceType git = (GenericInstanceType)memberValueFd.FieldType; TypeReference genericTr = git.GenericArguments[0]; writeMr = writeMr.GetMethodReference(Session, genericTr); } FieldReference fieldRef = GetClass().GetFieldReference(memberValueFd); processor.Emit(OpCodes.Ldarg, writerPd); processor.Emit(OpCodes.Ldarg, encasingValuePd); processor.Emit(OpCodes.Ldfld, fieldRef); /* If a generated write then instead of calling the * generated write directly call writer.Write of * the type. * * This will reroute to the generic writer, which does add an * extra step, but this also allows us to decide what writer * to use. In GenericWriter we can check if a method being set * as the writer is generated, and if so while another method * had already been set then favor the other method. * * This will favor built-in and user created serializers. This has to be * done because we cannot check if a user created serializer exist * across assemblies, but at runtime we can make sure to favor the * created one as described above. */ // True if has Write prefix for generated writers. if (declaresUseGlobalSerializer) { // Switch out to use WriteUnpacked instead. TypeReference genericTr = ImportReference(memberValueFd.FieldType); writeMr = GetClass().Writer_Write_MethodRef.GetMethodReference(Session, genericTr); } processor.Emit(OpCodes.Call, writeMr); } else { LogError($"Writer not found for {memberValueFd.FieldType.FullName}."); } } /// /// Creates a Write call to a writer. /// EG: StaticClass.WriteBool(xxxxx); /// /// /// internal void CreateWrite(MethodDefinition writerMd, ParameterDefinition valuePd, MethodReference getMr, MethodReference writeMr) { TypeReference returnTr = ImportReference(getMr.ReturnType); if (writeMr != null) { ILProcessor processor = writerMd.Body.GetILProcessor(); ParameterDefinition writerPd = writerMd.Parameters[0]; /* If generic then find write class for * data type. Currently we only support one generic * for this. */ if (returnTr.IsGenericInstance) { GenericInstanceType git = (GenericInstanceType)returnTr; TypeReference genericTr = git.GenericArguments[0]; writeMr = writeMr.GetMethodReference(Session, genericTr); } processor.Emit(OpCodes.Ldarg, writerPd); OpCode ldArgOC0 = valuePd.ParameterType.IsValueType ? OpCodes.Ldarga : OpCodes.Ldarg; processor.Emit(ldArgOC0, valuePd); processor.Emit(OpCodes.Call, getMr); processor.Emit(OpCodes.Call, writeMr); } else { LogError($"Writer not found for {returnTr.FullName}."); } } #region TypeReference writer generators. /// /// Generates a writer for objectTypeReference if one does not already exist. /// /// /// internal MethodReference CreateWriter(TypeReference objectTr, string typeTrace) { MethodReference methodRefResult = null; TypeDefinition objectTd; SerializerType serializerType = GetClass().GetSerializerType(objectTr, true, out objectTd, typeTrace); if (serializerType != SerializerType.Invalid) { // Array. if (serializerType == SerializerType.Array) methodRefResult = CreateArrayWriterMethodReference(objectTr); // Enum. else if (serializerType == SerializerType.Enum) methodRefResult = CreateEnumWriterMethodDefinition(objectTr); // Dictionary, List, ListCache else if (serializerType == SerializerType.Dictionary || serializerType == SerializerType.List || serializerType == SerializerType.HashSet) methodRefResult = CreateGenericCollectionWriterMethodReference(objectTr, serializerType); // NetworkBehaviour. else if (serializerType == SerializerType.NetworkBehaviour) methodRefResult = CreateNetworkBehaviourWriterMethodReference(objectTd); // Nullable type. else if (serializerType == SerializerType.Nullable) methodRefResult = CreateNullableWriterMethodReference(objectTr, objectTd); // Class or struct. else if (serializerType == SerializerType.ClassOrStruct) methodRefResult = CreateClassOrStructWriterMethodDefinition(objectTr); } // If was not created. if (methodRefResult == null) RemoveFromStaticWriters(objectTr); return methodRefResult; } /// /// Removes from static writers. /// private void RemoveFromStaticWriters(TypeReference tr) { GetClass().RemoveWriterMethod(tr, false); } /// /// Adds to static writers. /// private void AddToStaticWriters(TypeReference tr, MethodReference mr) { GetClass().AddWriterMethod(tr, mr.CachedResolve(Session), false, true); } /// /// Adds a writer for a NetworkBehaviour class type to WriterMethods. /// /// private MethodReference CreateNetworkBehaviourWriterMethodReference(TypeReference objectTr) { ObjectHelper oh = GetClass(); objectTr = ImportReference(objectTr.Resolve()); // All NetworkBehaviour types will simply WriteNetworkBehaviour/ReadNetworkBehaviour. // Create generated reader/writer class. This class holds all generated reader/writers. GetClass().GetOrCreateClass(out _, GENERATED_TYPE_ATTRIBUTES, GENERATED_WRITERS_CLASS_NAME, null); MethodDefinition createdWriterMd = CreateStaticWriterStubMethodDefinition(objectTr); AddToStaticWriters(objectTr, createdWriterMd); ILProcessor processor = createdWriterMd.Body.GetILProcessor(); MethodReference writeMethodRef = GetClass().GetOrCreateWriteMethodReference(oh.NetworkBehaviour_TypeRef, string.Empty); // Get parameters for method. ParameterDefinition writerParameterDef = createdWriterMd.Parameters[0]; ParameterDefinition classParameterDef = createdWriterMd.Parameters[1]; // Load parameters as arguments. processor.Emit(OpCodes.Ldarg, writerParameterDef); processor.Emit(OpCodes.Ldarg, classParameterDef); // writer.WriteNetworkBehaviour(arg1); processor.Emit(OpCodes.Call, writeMethodRef); processor.Emit(OpCodes.Ret); return ImportReference(createdWriterMd); } /// /// Gets the length of a collection and writes the value to a variable. /// private void CreateCollectionLength(ILProcessor processor, ParameterDefinition collectionParameterDef, VariableDefinition storeVariableDef) { processor.Emit(OpCodes.Ldarg, collectionParameterDef); processor.Emit(OpCodes.Ldlen); processor.Emit(OpCodes.Conv_I4); processor.Emit(OpCodes.Stloc, storeVariableDef); } /// /// Creates a writer for a class or struct of objectTypeRef. /// /// /// private MethodReference CreateNullableWriterMethodReference(TypeReference objectTr, TypeDefinition objectTd) { WriterProcessor wh = GetClass(); GenericInstanceType objectGit = objectTr as GenericInstanceType; TypeReference valueTr = objectGit.GenericArguments[0]; // Get the writer for the value. MethodReference valueWriterMr = wh.GetOrCreateWriteMethodReference(valueTr, TypeReferenceTraceText(objectTr)); if (valueWriterMr == null) return null; MethodDefinition tmpMd; tmpMd = objectTd.GetMethod("get_Value"); MethodReference genericGetValueMr = tmpMd.MakeHostInstanceGeneric(Session, objectGit); tmpMd = objectTd.GetMethod("get_HasValue"); MethodReference genericHasValueMr = tmpMd.MakeHostInstanceGeneric(Session, objectGit); /* Stubs generate Method(Writer writer, T value). */ MethodDefinition createdWriterMd = CreateStaticWriterStubMethodDefinition(objectTr); AddToStaticWriters(objectTr, createdWriterMd); ILProcessor processor = createdWriterMd.Body.GetILProcessor(); // Value parameter. ParameterDefinition valuePd = createdWriterMd.Parameters[1]; ParameterDefinition writerPd = createdWriterMd.Parameters[0]; // Have to write a new ret on null because nullables use hasValue for null checks. Instruction afterNullRetInst = processor.Create(OpCodes.Nop); processor.Emit(OpCodes.Ldarga, valuePd); processor.Emit(OpCodes.Call, genericHasValueMr); processor.Emit(OpCodes.Brtrue_S, afterNullRetInst); wh.CreateWriteBool(processor, writerPd, true); processor.Emit(OpCodes.Ret); processor.Append(afterNullRetInst); // Code will only execute here and below if not null. wh.CreateWriteBool(processor, writerPd, false); processor.Emit(OpCodes.Ldarg, writerPd); processor.Emit(OpCodes.Ldarga, valuePd); processor.Emit(OpCodes.Call, genericGetValueMr); processor.Emit(OpCodes.Call, valueWriterMr); processor.Emit(OpCodes.Ret); return ImportReference(createdWriterMd); } /// /// Creates a writer for a class or struct of objectTypeRef. /// /// /// private MethodReference CreateClassOrStructWriterMethodDefinition(TypeReference objectTr) { WriterProcessor wh = GetClass(); /*Stubs generate Method(Writer writer, T value). */ MethodDefinition createdWriterMd = CreateStaticWriterStubMethodDefinition(objectTr); AddToStaticWriters(objectTr, createdWriterMd); ILProcessor processor = createdWriterMd.Body.GetILProcessor(); // If not a value type then add a null check. if (!objectTr.CachedResolve(Session).IsValueType) { ParameterDefinition writerPd = createdWriterMd.Parameters[0]; wh.CreateRetOnNull(processor, writerPd, createdWriterMd.Parameters[1]); // Code will only execute here and below if not null. wh.CreateWriteBool(processor, writerPd, false); } // Write all fields for the class or struct. ParameterDefinition valueParameterDef = createdWriterMd.Parameters[1]; if (!WriteFieldsAndProperties(createdWriterMd, valueParameterDef, objectTr)) return null; processor.Emit(OpCodes.Ret); return ImportReference(createdWriterMd); } /// /// Find all fields in type and write them /// /// /// /// false if fail private bool WriteFieldsAndProperties(MethodDefinition generatedWriteMd, ParameterDefinition encasingValuePd, TypeReference objectTr) { WriterProcessor wh = GetClass(); // This probably isn't needed but I'm too afraid to remove it. if (objectTr.Module != Module) objectTr = ImportReference(objectTr.CachedResolve(Session)); // Fields foreach (FieldDefinition fieldDef in objectTr.FindAllSerializableFields(Session)) // , WriterHelper.EXCLUDED_AUTO_SERIALIZER_TYPES)) { TypeReference tr; if (fieldDef.FieldType.IsGenericInstance) { GenericInstanceType genericTr = (GenericInstanceType)fieldDef.FieldType; tr = genericTr.GenericArguments[0]; } else { tr = fieldDef.FieldType; } if (GetWriteMethod(fieldDef.FieldType, out MethodReference writeMr)) wh.CreateWrite(generatedWriteMd, encasingValuePd, fieldDef, writeMr); } // Properties. foreach (PropertyDefinition propertyDef in objectTr.FindAllSerializableProperties(Session, EXCLUDED_AUTO_SERIALIZER_TYPES, EXCLUDED_ASSEMBLY_PREFIXES)) { if (GetWriteMethod(propertyDef.PropertyType, out MethodReference writerMr)) { MethodReference getMr = Module.ImportReference(propertyDef.GetMethod); wh.CreateWrite(generatedWriteMd, encasingValuePd, getMr, writerMr); } } // Gets or creates writer method and outputs it. Returns true if method is found or created. bool GetWriteMethod(TypeReference tr, out MethodReference writeMr) { tr = ImportReference(tr); writeMr = wh.GetOrCreateWriteMethodReference(tr, TypeReferenceTraceText(objectTr)); return writeMr != null; } return true; } /// /// Creates a writer for an enum. /// /// /// private MethodReference CreateEnumWriterMethodDefinition(TypeReference objectTr) { WriterProcessor wh = GetClass(); MethodDefinition createdWriterMd = CreateStaticWriterStubMethodDefinition(objectTr); AddToStaticWriters(objectTr, createdWriterMd); ILProcessor processor = createdWriterMd.Body.GetILProcessor(); // Element type for enum. EG: byte int ect TypeReference underlyingTypeRef = objectTr.CachedResolve(Session).GetEnumUnderlyingTypeReference(); // Method to write that type. MethodReference underlyingWriterMethodRef = wh.GetOrCreateWriteMethodReference(underlyingTypeRef, TypeReferenceTraceText(objectTr)); if (underlyingWriterMethodRef == null) return null; ParameterDefinition writerParameterDef = createdWriterMd.Parameters[0]; ParameterDefinition valueParameterDef = createdWriterMd.Parameters[1]; // Push writer and value into call. processor.Emit(OpCodes.Ldarg, writerParameterDef); processor.Emit(OpCodes.Ldarg, valueParameterDef); // writer.WriteXXX(value) processor.Emit(OpCodes.Call, underlyingWriterMethodRef); processor.Emit(OpCodes.Ret); return ImportReference(createdWriterMd); } /// /// Calls an instanced writer from a static writer. /// private void CallInstancedWriter(MethodDefinition staticWriterMd, MethodReference instancedWriterMr) { ParameterDefinition writerPd = staticWriterMd.Parameters[0]; ParameterDefinition valuePd = staticWriterMd.Parameters[1]; ILProcessor processor = staticWriterMd.Body.GetILProcessor(); processor.Emit(OpCodes.Ldarg, writerPd); processor.Emit(OpCodes.Ldarg, valuePd); processor.Emit(instancedWriterMr.GetCallOpCode(Session), instancedWriterMr); processor.Emit(OpCodes.Ret); } /// /// Creates a writer for an array. /// private MethodReference CreateArrayWriterMethodReference(TypeReference objectTr) { WriterImports wi = GetClass(); TypeReference valueTr = objectTr.GetElementType(); //Write not found. if (GetOrCreateWriteMethodReference(valueTr, TypeReferenceTraceText(objectTr)) == null) return null; MethodDefinition createdMd = CreateStaticWriterStubMethodDefinition(objectTr); AddToStaticWriters(objectTr, createdMd); //Find instanced writer to use. MethodReference instancedWriteMr = wi.Writer_WriteArray_MethodRef; //Make generic. GenericInstanceMethod writeGim = instancedWriteMr.MakeGenericMethod(new TypeReference[] { valueTr }); CallInstancedWriter(createdMd, writeGim); return ImportReference(createdMd); } /// /// Creates a writer for a variety of generic collections. /// private MethodReference CreateGenericCollectionWriterMethodReference(TypeReference objectTr, SerializerType st) { WriterImports wi = GetClass(); //Make value field generic. GenericInstanceType genericInstance = (GenericInstanceType)objectTr; ImportReference(genericInstance); TypeReference valueTr = genericInstance.GenericArguments[0]; List genericArguments = new(); //Make sure all arguments have writers. foreach (TypeReference gaTr in genericInstance.GenericArguments) { MethodReference mr = GetOrCreateWriteMethodReference(gaTr, TypeReferenceTraceText(objectTr)); //Writer not found. if (mr == null) { LogError($"Writer could not be found or created for type {gaTr.FullName}."); return null; } genericArguments.Add(gaTr); } MethodReference valueWriteMr = GetOrCreateWriteMethodReference(valueTr, TypeReferenceTraceText(objectTr)); if (valueWriteMr == null) return null; MethodDefinition createdMd = CreateStaticWriterStubMethodDefinition(objectTr); AddToStaticWriters(objectTr, createdMd); //Find instanced writer to use. MethodReference instancedWriteMr; if (st == SerializerType.Dictionary) instancedWriteMr = wi.Writer_WriteDictionary_MethodRef; else if (st == SerializerType.List) instancedWriteMr = wi.Writer_WriteList_MethodRef; else if (st == SerializerType.HashSet) instancedWriteMr = wi.Writer_WriteHashSet_MethodRef; else instancedWriteMr = null; //Not found. if (instancedWriteMr == null) { LogError($"Instanced writer not found for SerializerType {st} on object {objectTr.Name}."); return null; } //Make generic. GenericInstanceMethod writeGim = instancedWriteMr.MakeGenericMethod(genericArguments.ToArray()); CallInstancedWriter(createdMd, writeGim); return ImportReference(createdMd); } /// /// Creates a method definition stub for objectTypeRef. /// /// /// public MethodDefinition CreateStaticWriterStubMethodDefinition(TypeReference objectTypeRef, string nameExtension = GENERATED_WRITER_NAMESPACE) { string methodName = $"{UtilityConstants.GeneratedWriterPrefix}{objectTypeRef.FullName}{nameExtension}"; // create new writer for this type TypeDefinition writerTypeDef = GetClass().GetOrCreateClass(out _, GENERATED_TYPE_ATTRIBUTES, GENERATED_WRITERS_CLASS_NAME, null); MethodDefinition writerMethodDef = writerTypeDef.AddMethod(methodName, MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig); GetClass().CreateParameter(writerMethodDef, GetClass().Writer_TypeRef, "writer"); GetClass().CreateParameter(writerMethodDef, objectTypeRef, "value"); GetClass().MakeExtensionMethod(writerMethodDef); writerMethodDef.Body.InitLocals = true; return writerMethodDef; } #endregion } }