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 Readers { // Readers 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 readFuncs = new Dictionary(new TypeReferenceComparer()); public Readers(AssemblyDefinition assembly, WeaverTypes weaverTypes, TypeDefinition GeneratedCodeClass, Logger Log) { this.assembly = assembly; this.weaverTypes = weaverTypes; this.GeneratedCodeClass = GeneratedCodeClass; this.Log = Log; } internal void Register(TypeReference dataType, MethodReference methodReference) { if (readFuncs.ContainsKey(dataType)) { // TODO enable this again later. // Reader has some obsolete functions that were renamed. // Don't want weaver warnings for all of them. //Log.Warning($"Registering a Read method for {dataType.FullName} when one already exists", methodReference); } // we need to import type when we Initialize Readers so import here in case it is used anywhere else TypeReference imported = assembly.MainModule.ImportReference(dataType); readFuncs[imported] = methodReference; } void RegisterReadFunc(TypeReference typeReference, MethodDefinition newReaderFunc) { Register(typeReference, newReaderFunc); GeneratedCodeClass.Methods.Add(newReaderFunc); } // Finds existing reader for type, if non exists trys to create one public MethodReference GetReadFunc(TypeReference variable, ref bool WeavingFailed) { if (readFuncs.TryGetValue(variable, out MethodReference foundFunc)) return foundFunc; TypeReference importedVariable = assembly.MainModule.ImportReference(variable); return GenerateReader(importedVariable, ref WeavingFailed); } MethodReference GenerateReader(TypeReference variableReference, ref bool WeavingFailed) { // Arrays are special, if we resolve them, we get the element type, // so the following ifs might choke on it for scriptable objects // or other objects that require a custom serializer // thus check if it is an array and skip all the checks. if (variableReference.IsArray) { if (variableReference.IsMultidimensionalArray()) { Log.Error($"{variableReference.Name} is an unsupported type. Multidimensional arrays are not supported", variableReference); WeavingFailed = true; return null; } return GenerateReadCollection(variableReference, variableReference.GetElementType(), nameof(NetworkReaderExtensions.ReadArray), ref WeavingFailed); } TypeDefinition variableDefinition = variableReference.Resolve(); // check if the type is completely invalid if (variableDefinition == null) { Log.Error($"{variableReference.Name} is not a supported type", variableReference); WeavingFailed = true; return null; } else if (variableReference.IsByReference) { // error?? Log.Error($"Cannot pass type {variableReference.Name} by reference", variableReference); WeavingFailed = true; return null; } // use existing func for known types if (variableDefinition.IsEnum) { return GenerateEnumReadFunc(variableReference, ref WeavingFailed); } else if (variableDefinition.Is(typeof(ArraySegment<>))) { return GenerateArraySegmentReadFunc(variableReference, ref WeavingFailed); } else if (variableDefinition.Is(typeof(List<>))) { GenericInstanceType genericInstance = (GenericInstanceType)variableReference; TypeReference elementType = genericInstance.GenericArguments[0]; return GenerateReadCollection(variableReference, elementType, nameof(NetworkReaderExtensions.ReadList), ref WeavingFailed); } else if (variableReference.IsDerivedFrom()) { return GetNetworkBehaviourReader(variableReference); } // check if reader generation is applicable on this type if (variableDefinition.IsDerivedFrom()) { Log.Error($"Cannot generate reader for component type {variableReference.Name}. Use a supported type or provide a custom reader", variableReference); WeavingFailed = true; return null; } if (variableReference.Is()) { Log.Error($"Cannot generate reader for {variableReference.Name}. Use a supported type or provide a custom reader", variableReference); WeavingFailed = true; return null; } if (variableReference.Is()) { Log.Error($"Cannot generate reader for {variableReference.Name}. Use a supported type or provide a custom reader", variableReference); WeavingFailed = true; return null; } /* if (variableDefinition.HasGenericParameters) { Log.Error($"Cannot generate reader for generic variable {variableReference.Name}. Use a supported type or provide a custom reader", variableReference); WeavingFailed = true; return null; } */ if (variableDefinition.IsInterface) { Log.Error($"Cannot generate reader for interface {variableReference.Name}. Use a supported type or provide a custom reader", variableReference); WeavingFailed = true; return null; } if (variableDefinition.IsAbstract) { Log.Error($"Cannot generate reader for abstract class {variableReference.Name}. Use a supported type or provide a custom reader", variableReference); WeavingFailed = true; return null; } return GenerateClassOrStructReadFunction(variableReference, ref WeavingFailed); } MethodReference GetNetworkBehaviourReader(TypeReference variableReference) { // uses generic ReadNetworkBehaviour rather than having weaver create one for each NB MethodReference generic = weaverTypes.readNetworkBehaviourGeneric; MethodReference readFunc = generic.MakeGeneric(assembly.MainModule, variableReference); // register function so it is added to Reader // use Register instead of RegisterWriteFunc because this is not a generated function Register(variableReference, readFunc); return readFunc; } MethodDefinition GenerateEnumReadFunc(TypeReference variable, ref bool WeavingFailed) { MethodDefinition readerFunc = GenerateReaderFunction(variable); ILProcessor worker = readerFunc.Body.GetILProcessor(); worker.Emit(OpCodes.Ldarg_0); TypeReference underlyingType = variable.Resolve().GetEnumUnderlyingType(); MethodReference underlyingFunc = GetReadFunc(underlyingType, ref WeavingFailed); worker.Emit(OpCodes.Call, underlyingFunc); worker.Emit(OpCodes.Ret); return readerFunc; } MethodDefinition GenerateArraySegmentReadFunc(TypeReference variable, ref bool WeavingFailed) { GenericInstanceType genericInstance = (GenericInstanceType)variable; TypeReference elementType = genericInstance.GenericArguments[0]; MethodDefinition readerFunc = GenerateReaderFunction(variable); ILProcessor worker = readerFunc.Body.GetILProcessor(); // $array = reader.Read<[T]>() ArrayType arrayType = elementType.MakeArrayType(); worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Call, GetReadFunc(arrayType, ref WeavingFailed)); // return new ArraySegment($array); worker.Emit(OpCodes.Newobj, weaverTypes.ArraySegmentConstructorReference.MakeHostInstanceGeneric(assembly.MainModule, genericInstance)); worker.Emit(OpCodes.Ret); return readerFunc; } MethodDefinition GenerateReaderFunction(TypeReference variable) { string functionName = $"_Read_{variable.FullName}"; // create new reader for this type MethodDefinition readerFunc = new MethodDefinition(functionName, MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, variable); readerFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, weaverTypes.Import())); readerFunc.Body.InitLocals = true; RegisterReadFunc(variable, readerFunc); return readerFunc; } MethodDefinition GenerateReadCollection(TypeReference variable, TypeReference elementType, string readerFunction, ref bool WeavingFailed) { MethodDefinition readerFunc = GenerateReaderFunction(variable); // generate readers for the element GetReadFunc(elementType, ref WeavingFailed); ModuleDefinition module = assembly.MainModule; TypeReference readerExtensions = module.ImportReference(typeof(NetworkReaderExtensions)); MethodReference listReader = Resolvers.ResolveMethod(readerExtensions, assembly, Log, readerFunction, ref WeavingFailed); GenericInstanceMethod methodRef = new GenericInstanceMethod(listReader); methodRef.GenericArguments.Add(elementType); // generates // return reader.ReadList(); ILProcessor worker = readerFunc.Body.GetILProcessor(); worker.Emit(OpCodes.Ldarg_0); // reader worker.Emit(OpCodes.Call, methodRef); // Read worker.Emit(OpCodes.Ret); return readerFunc; } MethodDefinition GenerateClassOrStructReadFunction(TypeReference variable, ref bool WeavingFailed) { MethodDefinition readerFunc = GenerateReaderFunction(variable); // create local for return value readerFunc.Body.Variables.Add(new VariableDefinition(variable)); ILProcessor worker = readerFunc.Body.GetILProcessor(); TypeDefinition td = variable.Resolve(); if (!td.IsValueType) GenerateNullCheck(worker, ref WeavingFailed); CreateNew(variable, worker, td, ref WeavingFailed); ReadAllFields(variable, worker, ref WeavingFailed); worker.Emit(OpCodes.Ldloc_0); worker.Emit(OpCodes.Ret); return readerFunc; } void GenerateNullCheck(ILProcessor worker, ref bool WeavingFailed) { // if (!reader.ReadBoolean()) { // return null; // } worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Call, GetReadFunc(weaverTypes.Import(), ref WeavingFailed)); Instruction labelEmptyArray = worker.Create(OpCodes.Nop); worker.Emit(OpCodes.Brtrue, labelEmptyArray); // return null worker.Emit(OpCodes.Ldnull); worker.Emit(OpCodes.Ret); worker.Append(labelEmptyArray); } // Initialize the local variable with a new instance void CreateNew(TypeReference variable, ILProcessor worker, TypeDefinition td, ref bool WeavingFailed) { if (variable.IsValueType) { // structs are created with Initobj worker.Emit(OpCodes.Ldloca, 0); worker.Emit(OpCodes.Initobj, variable); } else if (td.IsDerivedFrom()) { GenericInstanceMethod genericInstanceMethod = new GenericInstanceMethod(weaverTypes.ScriptableObjectCreateInstanceMethod); genericInstanceMethod.GenericArguments.Add(variable); worker.Emit(OpCodes.Call, genericInstanceMethod); worker.Emit(OpCodes.Stloc_0); } else { // classes are created with their constructor MethodDefinition ctor = Resolvers.ResolveDefaultPublicCtor(variable); if (ctor == null) { Log.Error($"{variable.Name} can't be deserialized because it has no default constructor. Don't use {variable.Name} in [SyncVar]s, Rpcs, Cmds, etc.", variable); WeavingFailed = true; return; } MethodReference ctorRef = assembly.MainModule.ImportReference(ctor); worker.Emit(OpCodes.Newobj, ctorRef); worker.Emit(OpCodes.Stloc_0); } } void ReadAllFields(TypeReference variable, ILProcessor worker, ref bool WeavingFailed) { foreach (FieldDefinition field in variable.FindAllPublicFields_Improved()) { // mismatched ldloca/ldloc for struct/class combinations is invalid IL, which causes crash at runtime OpCode opcode = variable.IsValueType ? OpCodes.Ldloca : OpCodes.Ldloc; worker.Emit(opcode, 0); MethodReference readFunc = GetReadFunc(field.FieldType, ref WeavingFailed); if (readFunc != null) { worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Call, readFunc); } else { Log.Error($"{field.Name} has an unsupported type", field); WeavingFailed = true; } FieldReference fieldRef = assembly.MainModule.ImportReference(field); worker.Emit(OpCodes.Stfld, fieldRef); } } // Save a delegate for each one of the readers into Reader.read internal void InitializeReaders(ILProcessor worker) { ModuleDefinition module = assembly.MainModule; TypeReference genericReaderClassRef = module.ImportReference(typeof(Reader<>)); System.Reflection.FieldInfo fieldInfo = typeof(Reader<>).GetField(nameof(Reader.read)); FieldReference fieldRef = module.ImportReference(fieldInfo); TypeReference networkReaderRef = module.ImportReference(typeof(NetworkReader)); TypeReference funcRef = module.ImportReference(typeof(Func<,>)); MethodReference funcConstructorRef = module.ImportReference(typeof(Func<,>).GetConstructors()[0]); foreach (KeyValuePair kvp in readFuncs) { TypeReference targetType = kvp.Key; MethodReference readFunc = kvp.Value; // create a Func delegate worker.Emit(OpCodes.Ldnull); worker.Emit(OpCodes.Ldftn, readFunc); GenericInstanceType funcGenericInstance = funcRef.MakeGenericInstanceType(networkReaderRef, targetType); MethodReference funcConstructorInstance = funcConstructorRef.MakeHostInstanceGeneric(assembly.MainModule, funcGenericInstance); worker.Emit(OpCodes.Newobj, funcConstructorInstance); // save it in Reader.read GenericInstanceType genericInstance = genericReaderClassRef.MakeGenericInstanceType(targetType); FieldReference specializedField = fieldRef.SpecializeField(assembly.MainModule, genericInstance); worker.Emit(OpCodes.Stsfld, specializedField); } } } }