using System; using System.Collections.Generic; using Mono.CecilX; using Mono.CecilX.Cil; using Mono.CecilX.Rocks; namespace Mirror.Weaver { public static class Writers { static Dictionary writeFuncs; public static void Init() { writeFuncs = new Dictionary(new TypeReferenceComparer()); } public static void Register(TypeReference dataType, MethodReference methodReference) { if (writeFuncs.ContainsKey(dataType)) { // TODO enable this again later. // Writer has some obsolete functions that were renamed. // Don't want weaver warnings for all of them. //Weaver.Warning($"Registering a Write method for {dataType.FullName} when one already exists", methodReference); } // we need to import type when we Initialize Writers so import here in case it is used anywhere else TypeReference imported = Weaver.CurrentAssembly.MainModule.ImportReference(dataType); writeFuncs[imported] = methodReference; } static void RegisterWriteFunc(TypeReference typeReference, MethodDefinition newWriterFunc) { Register(typeReference, newWriterFunc); Weaver.GeneratedCodeClass.Methods.Add(newWriterFunc); } /// /// Finds existing writer for type, if non exists trys to create one /// This method is recursive /// /// /// Returns or null public static MethodReference GetWriteFunc(TypeReference variable) { if (writeFuncs.TryGetValue(variable, out MethodReference foundFunc)) { return foundFunc; } else { // this try/catch will be removed in future PR and make `GetWriteFunc` throw instead try { TypeReference importedVariable = Weaver.CurrentAssembly.MainModule.ImportReference(variable); return GenerateWriter(importedVariable); } catch (GenerateWriterException e) { Weaver.Error(e.Message, e.MemberReference); return null; } } } /// Throws when writer could not be generated for type static MethodReference GenerateWriter(TypeReference variableReference) { if (variableReference.IsByReference) { throw new GenerateWriterException($"Cannot pass {variableReference.Name} by reference", variableReference); } // Arrays are special, if we resolve them, we get the element type, // e.g. int[] resolves to int // therefore process this before checks below if (variableReference.IsArray) { if (variableReference.IsMultidimensionalArray()) { throw new GenerateWriterException($"{variableReference.Name} is an unsupported type. Multidimensional arrays are not supported", variableReference); } TypeReference elementType = variableReference.GetElementType(); return GenerateCollectionWriter(variableReference, elementType, nameof(NetworkWriterExtensions.WriteArray)); } if (variableReference.Resolve()?.IsEnum ?? false) { // serialize enum as their base type return GenerateEnumWriteFunc(variableReference); } // check for collections if (variableReference.Is(typeof(ArraySegment<>))) { GenericInstanceType genericInstance = (GenericInstanceType)variableReference; TypeReference elementType = genericInstance.GenericArguments[0]; return GenerateCollectionWriter(variableReference, elementType, nameof(NetworkWriterExtensions.WriteArraySegment)); } if (variableReference.Is(typeof(List<>))) { GenericInstanceType genericInstance = (GenericInstanceType)variableReference; TypeReference elementType = genericInstance.GenericArguments[0]; return GenerateCollectionWriter(variableReference, elementType, nameof(NetworkWriterExtensions.WriteList)); } if (variableReference.IsDerivedFrom()) { return GetNetworkBehaviourWriter(variableReference); } // check for invalid types TypeDefinition variableDefinition = variableReference.Resolve(); if (variableDefinition == null) { throw new GenerateWriterException($"{variableReference.Name} is not a supported type. Use a supported type or provide a custom writer", variableReference); } if (variableDefinition.IsDerivedFrom()) { throw new GenerateWriterException($"Cannot generate writer for component type {variableReference.Name}. Use a supported type or provide a custom writer", variableReference); } if (variableReference.Is()) { throw new GenerateWriterException($"Cannot generate writer for {variableReference.Name}. Use a supported type or provide a custom writer", variableReference); } if (variableReference.Is()) { throw new GenerateWriterException($"Cannot generate writer for {variableReference.Name}. Use a supported type or provide a custom writer", variableReference); } if (variableDefinition.HasGenericParameters) { throw new GenerateWriterException($"Cannot generate writer for generic type {variableReference.Name}. Use a supported type or provide a custom writer", variableReference); } if (variableDefinition.IsInterface) { throw new GenerateWriterException($"Cannot generate writer for interface {variableReference.Name}. Use a supported type or provide a custom writer", variableReference); } if (variableDefinition.IsAbstract) { throw new GenerateWriterException($"Cannot generate writer for abstract class {variableReference.Name}. Use a supported type or provide a custom writer", variableReference); } // generate writer for class/struct return GenerateClassOrStructWriterFunction(variableReference); } static MethodReference GetNetworkBehaviourWriter(TypeReference variableReference) { // all NetworkBehaviours can use the same write function if (writeFuncs.TryGetValue(WeaverTypes.Import(), out MethodReference func)) { // register function so it is added to writer // use Register instead of RegisterWriteFunc because this is not a generated function Register(variableReference, func); return func; } else { // this exception only happens if mirror is missing the WriteNetworkBehaviour method throw new MissingMethodException($"Could not find writer for NetworkBehaviour"); } } static MethodDefinition GenerateEnumWriteFunc(TypeReference variable) { MethodDefinition writerFunc = GenerateWriterFunc(variable); ILProcessor worker = writerFunc.Body.GetILProcessor(); MethodReference underlyingWriter = GetWriteFunc(variable.Resolve().GetEnumUnderlyingType()); worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldarg_1); worker.Emit(OpCodes.Call, underlyingWriter); worker.Emit(OpCodes.Ret); return writerFunc; } static MethodDefinition GenerateWriterFunc(TypeReference variable) { string functionName = "_Write_" + variable.FullName; // create new writer for this type MethodDefinition writerFunc = new MethodDefinition(functionName, MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, WeaverTypes.Import(typeof(void))); writerFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, WeaverTypes.Import())); writerFunc.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.None, variable)); writerFunc.Body.InitLocals = true; RegisterWriteFunc(variable, writerFunc); return writerFunc; } static MethodDefinition GenerateClassOrStructWriterFunction(TypeReference variable) { MethodDefinition writerFunc = GenerateWriterFunc(variable); ILProcessor worker = writerFunc.Body.GetILProcessor(); if (!variable.Resolve().IsValueType) WriteNullCheck(worker); if (!WriteAllFields(variable, worker)) return null; worker.Emit(OpCodes.Ret); return writerFunc; } static void WriteNullCheck(ILProcessor worker) { // if (value == null) // { // writer.WriteBoolean(false); // return; // } // Instruction labelNotNull = worker.Create(OpCodes.Nop); worker.Emit(OpCodes.Ldarg_1); worker.Emit(OpCodes.Brtrue, labelNotNull); worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldc_I4_0); worker.Emit(OpCodes.Call, GetWriteFunc(WeaverTypes.Import())); worker.Emit(OpCodes.Ret); worker.Append(labelNotNull); // write.WriteBoolean(true); worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldc_I4_1); worker.Emit(OpCodes.Call, GetWriteFunc(WeaverTypes.Import())); } /// /// Find all fields in type and write them /// /// /// /// false if fail static bool WriteAllFields(TypeReference variable, ILProcessor worker) { uint fields = 0; foreach (FieldDefinition field in variable.FindAllPublicFields()) { MethodReference writeFunc = GetWriteFunc(field.FieldType); // need this null check till later PR when GetWriteFunc throws exception instead if (writeFunc == null) { return false; } FieldReference fieldRef = Weaver.CurrentAssembly.MainModule.ImportReference(field); fields++; worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldarg_1); worker.Emit(OpCodes.Ldfld, fieldRef); worker.Emit(OpCodes.Call, writeFunc); } return true; } static MethodDefinition GenerateCollectionWriter(TypeReference variable, TypeReference elementType, string writerFunction) { MethodDefinition writerFunc = GenerateWriterFunc(variable); MethodReference elementWriteFunc = GetWriteFunc(elementType); MethodReference intWriterFunc = GetWriteFunc(WeaverTypes.Import()); // need this null check till later PR when GetWriteFunc throws exception instead if (elementWriteFunc == null) { Weaver.Error($"Cannot generate writer for {variable}. Use a supported type or provide a custom writer", variable); return writerFunc; } ModuleDefinition module = Weaver.CurrentAssembly.MainModule; TypeReference readerExtensions = module.ImportReference(typeof(NetworkWriterExtensions)); MethodReference collectionWriter = Resolvers.ResolveMethod(readerExtensions, Weaver.CurrentAssembly, writerFunction); GenericInstanceMethod methodRef = new GenericInstanceMethod(collectionWriter); methodRef.GenericArguments.Add(elementType); // generates // reader.WriteArray(array); ILProcessor worker = writerFunc.Body.GetILProcessor(); worker.Emit(OpCodes.Ldarg_0); // writer worker.Emit(OpCodes.Ldarg_1); // collection worker.Emit(OpCodes.Call, methodRef); // WriteArray worker.Emit(OpCodes.Ret); return writerFunc; } /// /// Save a delegate for each one of the writers into /// /// internal static void InitializeWriters(ILProcessor worker) { ModuleDefinition module = Weaver.CurrentAssembly.MainModule; TypeReference genericWriterClassRef = module.ImportReference(typeof(Writer<>)); System.Reflection.FieldInfo fieldInfo = typeof(Writer<>).GetField(nameof(Writer.write)); FieldReference fieldRef = module.ImportReference(fieldInfo); TypeReference networkWriterRef = module.ImportReference(typeof(NetworkWriter)); TypeReference actionRef = module.ImportReference(typeof(Action<,>)); MethodReference actionConstructorRef = module.ImportReference(typeof(Action<,>).GetConstructors()[0]); foreach (KeyValuePair kvp in writeFuncs) { TypeReference targetType = kvp.Key; MethodReference writeFunc = kvp.Value; // create a Action delegate worker.Emit(OpCodes.Ldnull); worker.Emit(OpCodes.Ldftn, writeFunc); GenericInstanceType actionGenericInstance = actionRef.MakeGenericInstanceType(networkWriterRef, targetType); MethodReference actionRefInstance = actionConstructorRef.MakeHostInstanceGeneric(actionGenericInstance); worker.Emit(OpCodes.Newobj, actionRefInstance); // save it in Writer.write GenericInstanceType genericInstance = genericWriterClassRef.MakeGenericInstanceType(targetType); FieldReference specializedField = fieldRef.SpecializeField(genericInstance); worker.Emit(OpCodes.Stsfld, specializedField); } } } }