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 System.Type[] EXCLUDED_AUTO_SERIALIZER_TYPES = new System.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 = base.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, 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. */ GeneratedWriterOnLoadMethodDef = gh.GetOrCreateMethod(GeneratedWriterClassTypeDef, out _, INITIALIZEONCE_METHOD_ATTRIBUTES, INITIALIZEONCE_METHOD_NAME, base.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 = base.ImportReference(methodInfo); /* TypeReference for the first parameter in the write method. * The first parameter will always be the type written. */ TypeReference typeRef = base.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(base.Session)) return; GeneralHelper gh = base.GetClass(); WriterProcessor gwh = base.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}", WriterProcessor.GENERATED_METHOD_ATTRIBUTES, base.Module.TypeSystem.Void); //Add extension parameter. ParameterDefinition writerPd = gh.CreateParameter(md, typeof(Writer), "writer"); //Add parameters needed by instanced writer. List otherPds = md.CreateParameters(base.Session, instancedWriteMr.CachedResolve(base.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(base.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)) base.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) base.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 = base.GetClass(); WriterImports wi = base.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)) { base.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(base.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(base.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 (!base.GetClass().HasNonSerializableAttribute(typeRef.CachedResolve(base.Session))) { MethodReference methodRef = CreateWriter(typeRef); 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) { //Try to get existing writer, if not present make one. MethodReference writeMethodRef = GetWriteMethodReference(typeRef); if (writeMethodRef == null) writeMethodRef = CreateWriter(typeRef); //If still null then return could not be generated. if (writeMethodRef == null) { base.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); if (result == null) { base.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 = base.GetClass(); List insts = new(); ILProcessor processor = methodDef.Body.GetILProcessor(); resultVd = base.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 = base.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(base.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(base.GetClass().GetTypeReference(typeof(bool))); processor.Emit(OpCodes.Ldarg, writerParameterDef); int intValue = (value) ? 1 : 0; processor.Emit(OpCodes.Ldc_I4, intValue); processor.Emit(writeBoolMethodRef.GetCallOpCode(base.Session), writeBoolMethodRef); } /// /// Returns if a type should use a declared/custom serializer globally. /// public bool DeclaresUseGlobalCustomSerializer(TypeReference dataTypeRef) { return dataTypeRef.CachedResolve(base.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 { base.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(base.Session, genericTr); } if (declaresUseGlobalSerializer) { //Switch out to use WriteUnpacked instead. writeMr = base.GetClass().Writer_Write_MethodRef.GetMethodReference(base.Session, valueTr); } insts.Add(processor.Create(OpCodes.Call, writeMr)); return insts; } else { base.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(base.Session, genericTr); } FieldReference fieldRef = base.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 = base.ImportReference(memberValueFd.FieldType); writeMr = base.GetClass().Writer_Write_MethodRef.GetMethodReference(base.Session, genericTr); } processor.Emit(OpCodes.Call, writeMr); } else { base.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 = base.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(base.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 { base.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) { MethodReference methodRefResult = null; TypeDefinition objectTd; SerializerType serializerType = base.GetClass().GetSerializerType(objectTr, true, out objectTd); 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) 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) { base.GetClass().RemoveWriterMethod(tr, false); } /// /// Adds to static writers. /// private void AddToStaticWriters(TypeReference tr, MethodReference mr) { base.GetClass().AddWriterMethod(tr, mr.CachedResolve(base.Session), false, true); } /// /// Adds a write for a NetworkBehaviour class type to WriterMethods. /// /// private MethodReference CreateNetworkBehaviourWriterMethodReference(TypeReference objectTr) { ObjectHelper oh = base.GetClass(); objectTr = base.ImportReference(objectTr.Resolve()); //All NetworkBehaviour types will simply WriteNetworkBehaviour/ReadNetworkBehaviour. //Create generated reader/writer class. This class holds all generated reader/writers. base.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 = base.GetClass().GetOrCreateWriteMethodReference(oh.NetworkBehaviour_TypeRef); //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 base.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 = base.GetClass(); GenericInstanceType objectGit = objectTr as GenericInstanceType; TypeReference valueTr = objectGit.GenericArguments[0]; //Get the writer for the value. MethodReference valueWriterMr = wh.GetOrCreateWriteMethodReference(valueTr); if (valueWriterMr == null) return null; MethodDefinition tmpMd; tmpMd = objectTd.GetMethod("get_Value"); MethodReference genericGetValueMr = tmpMd.MakeHostInstanceGeneric(base.Session, objectGit); tmpMd = objectTd.GetMethod("get_HasValue"); MethodReference genericHasValueMr = tmpMd.MakeHostInstanceGeneric(base.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 base.ImportReference(createdWriterMd); } /// /// Creates a writer for a class or struct of objectTypeRef. /// /// /// private MethodReference CreateClassOrStructWriterMethodDefinition(TypeReference objectTr) { WriterProcessor wh = base.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(base.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 base.ImportReference(createdWriterMd); } /// /// Find all fields in type and write them /// /// /// /// false if fail private bool WriteFieldsAndProperties(MethodDefinition generatedWriteMd, ParameterDefinition encasingValuePd, TypeReference objectTr) { WriterProcessor wh = 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)) //, 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(base.Session , WriterProcessor.EXCLUDED_AUTO_SERIALIZER_TYPES, WriterProcessor.EXCLUDED_ASSEMBLY_PREFIXES)) { if (GetWriteMethod(propertyDef.PropertyType, out MethodReference writerMr)) { MethodReference getMr = base.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 = base.ImportReference(tr); writeMr = wh.GetOrCreateWriteMethodReference(tr); return (writeMr != null); } return true; } /// /// Creates a writer for an enum. /// /// /// private MethodReference CreateEnumWriterMethodDefinition(TypeReference enumTr) { WriterProcessor wh = base.GetClass(); MethodDefinition createdWriterMd = CreateStaticWriterStubMethodDefinition(enumTr); AddToStaticWriters(enumTr, createdWriterMd); ILProcessor processor = createdWriterMd.Body.GetILProcessor(); //Element type for enum. EG: byte int ect TypeReference underlyingTypeRef = enumTr.CachedResolve(base.Session).GetEnumUnderlyingTypeReference(); //Method to write that type. MethodReference underlyingWriterMethodRef = wh.GetOrCreateWriteMethodReference(underlyingTypeRef); 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 base.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(base.Session), instancedWriterMr); processor.Emit(OpCodes.Ret); } /// /// Creates a writer for an array. /// private MethodReference CreateArrayWriterMethodReference(TypeReference objectTr) { WriterImports wi = base.GetClass(); TypeReference valueTr = objectTr.GetElementType(); //Write not found. if (GetOrCreateWriteMethodReference(valueTr) == 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 base.ImportReference(createdMd); } /// /// Creates a writer for a variety of generic collections. /// private MethodReference CreateGenericCollectionWriterMethodReference(TypeReference objectTr, SerializerType st) { WriterImports wi = base.GetClass(); //Make value field generic. GenericInstanceType genericInstance = (GenericInstanceType)objectTr; base.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); //Writer not found. if (mr == null) { base.LogError($"Writer could not be found or created for type {gaTr.FullName}."); return null; } genericArguments.Add(gaTr); } MethodReference valueWriteMr = GetOrCreateWriteMethodReference(valueTr); 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 instancedWriteMr = null; //Not found. if (instancedWriteMr == null) { base.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 base.ImportReference(createdMd); } /// /// Creates a method definition stub for objectTypeRef. /// /// /// public MethodDefinition CreateStaticWriterStubMethodDefinition(TypeReference objectTypeRef, string nameExtension = WriterProcessor.GENERATED_WRITER_NAMESPACE) { string methodName = $"{UtilityConstants.GeneratedWriterPrefix}{objectTypeRef.FullName}{nameExtension}"; // create new writer for this type TypeDefinition writerTypeDef = base.GetClass().GetOrCreateClass(out _, GENERATED_TYPE_ATTRIBUTES, GENERATED_WRITERS_CLASS_NAME, null); MethodDefinition writerMethodDef = writerTypeDef.AddMethod(methodName, MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig); base.GetClass().CreateParameter(writerMethodDef, base.GetClass().Writer_TypeRef, "writer"); base.GetClass().CreateParameter(writerMethodDef, objectTypeRef, "value"); base.GetClass().MakeExtensionMethod(writerMethodDef); writerMethodDef.Body.InitLocals = true; return writerMethodDef; } #endregion } }