using MirrorWeaver; using System; using System.Collections.Generic; using Mono.Cecil; using Mono.Cecil.Cil; // to use Mono.Cecil.Rocks here, we need to 'override references' in the // Unity.Mirror.CodeGen assembly definition file in the Editor, and add CecilX.Rocks. // otherwise we get an unknown import exception. using Mono.Cecil.Rocks; namespace Mirror.Weaver { // not static, because ILPostProcessor is multithreaded public class Writers { // Writers are only for this assembly. // can't be used from another assembly, otherwise we will get: // "System.ArgumentException: Member ... is declared in another module and needs to be imported" AssemblyDefinition assembly; WeaverTypes weaverTypes; TypeDefinition GeneratedCodeClass; Logger Log; Dictionary writeFuncs = new Dictionary(new TypeReferenceComparer()); public Writers(AssemblyDefinition assembly, WeaverTypes weaverTypes, TypeDefinition GeneratedCodeClass, Logger Log) { this.assembly = assembly; this.weaverTypes = weaverTypes; this.GeneratedCodeClass = GeneratedCodeClass; this.Log = Log; } public 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. //Log.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 = assembly.MainModule.ImportReference(dataType); writeFuncs[imported] = methodReference; } void RegisterWriteFunc(TypeReference typeReference, MethodDefinition newWriterFunc) { Register(typeReference, newWriterFunc); GeneratedCodeClass.Methods.Add(newWriterFunc); } // Finds existing writer for type, if non exists trys to create one public MethodReference GetWriteFunc(TypeReference variable, ref bool WeavingFailed) { if (writeFuncs.TryGetValue(variable, out MethodReference foundFunc)) return foundFunc; // this try/catch will be removed in future PR and make `GetWriteFunc` throw instead try { TypeReference importedVariable = assembly.MainModule.ImportReference(variable); return GenerateWriter(importedVariable, ref WeavingFailed); } catch (GenerateWriterException e) { Log.Error(e.Message, e.MemberReference); WeavingFailed = true; return null; } } //Throws GenerateWriterException when writer could not be generated for type MethodReference GenerateWriter(TypeReference variableReference, ref bool WeavingFailed) { 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), ref WeavingFailed); } if (variableReference.Resolve()?.IsEnum ?? false) { // serialize enum as their base type return GenerateEnumWriteFunc(variableReference, ref WeavingFailed); } // check for collections if (variableReference.Is(typeof(ArraySegment<>))) { GenericInstanceType genericInstance = (GenericInstanceType)variableReference; TypeReference elementType = genericInstance.GenericArguments[0]; return GenerateCollectionWriter(variableReference, elementType, nameof(NetworkWriterExtensions.WriteArraySegment), ref WeavingFailed); } if (variableReference.Is(typeof(List<>))) { GenericInstanceType genericInstance = (GenericInstanceType)variableReference; TypeReference elementType = genericInstance.GenericArguments[0]; return GenerateCollectionWriter(variableReference, elementType, nameof(NetworkWriterExtensions.WriteList), ref WeavingFailed); } 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, ref WeavingFailed); } 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"); } } MethodDefinition GenerateEnumWriteFunc(TypeReference variable, ref bool WeavingFailed) { MethodDefinition writerFunc = GenerateWriterFunc(variable); ILProcessor worker = writerFunc.Body.GetILProcessor(); MethodReference underlyingWriter = GetWriteFunc(variable.Resolve().GetEnumUnderlyingType(), ref WeavingFailed); worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldarg_1); worker.Emit(OpCodes.Call, underlyingWriter); worker.Emit(OpCodes.Ret); return writerFunc; } 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; } MethodDefinition GenerateClassOrStructWriterFunction(TypeReference variable, ref bool WeavingFailed) { MethodDefinition writerFunc = GenerateWriterFunc(variable); ILProcessor worker = writerFunc.Body.GetILProcessor(); if (!variable.Resolve().IsValueType) WriteNullCheck(worker, ref WeavingFailed); if (!WriteAllFields(variable, worker, ref WeavingFailed)) return null; worker.Emit(OpCodes.Ret); return writerFunc; } void WriteNullCheck(ILProcessor worker, ref bool WeavingFailed) { // 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(), ref WeavingFailed)); 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(), ref WeavingFailed)); } // Find all fields in type and write them bool WriteAllFields(TypeReference variable, ILProcessor worker, ref bool WeavingFailed) { foreach (FieldDefinition field in variable.FindAllPublicFields_Improved()) { MethodReference writeFunc = GetWriteFunc(field.FieldType, ref WeavingFailed); // need this null check till later PR when GetWriteFunc throws exception instead if (writeFunc == null) { return false; } FieldReference fieldRef = assembly.MainModule.ImportReference(field); worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldarg_1); worker.Emit(OpCodes.Ldfld, fieldRef); worker.Emit(OpCodes.Call, writeFunc); } return true; } MethodDefinition GenerateCollectionWriter(TypeReference variable, TypeReference elementType, string writerFunction, ref bool WeavingFailed) { MethodDefinition writerFunc = GenerateWriterFunc(variable); MethodReference elementWriteFunc = GetWriteFunc(elementType, ref WeavingFailed); MethodReference intWriterFunc = GetWriteFunc(weaverTypes.Import(), ref WeavingFailed); // need this null check till later PR when GetWriteFunc throws exception instead if (elementWriteFunc == null) { Log.Error($"Cannot generate writer for {variable}. Use a supported type or provide a custom writer", variable); WeavingFailed = true; return writerFunc; } ModuleDefinition module = assembly.MainModule; TypeReference readerExtensions = module.ImportReference(typeof(NetworkWriterExtensions)); MethodReference collectionWriter = Resolvers.ResolveMethod(readerExtensions, assembly, Log, writerFunction, ref WeavingFailed); 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 Writer{T}.write internal void InitializeWriters(ILProcessor worker) { ModuleDefinition module = assembly.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(assembly.MainModule, actionGenericInstance); worker.Emit(OpCodes.Newobj, actionRefInstance); // save it in Writer.write GenericInstanceType genericInstance = genericWriterClassRef.MakeGenericInstanceType(targetType); FieldReference specializedField = fieldRef.SpecializeField(assembly.MainModule, genericInstance); worker.Emit(OpCodes.Stsfld, specializedField); } } } }