// helper function to add [InitializeOnLoad] attribute to method // (only works in Editor assemblies. check IsEditorAssembly first.) static void AddInitializeOnLoadAttribute(AssemblyDefinition assembly, WeaverTypes weaverTypes, MethodDefinition method) { // NOTE: previously we used reflection because according paul, // 'weaving Mirror.dll caused unity to rebuild all dlls but in wrong // order, which breaks rewired' // it's not obvious why importing an attribute via reflection instead // of cecil would break anything. let's use cecil. // to add a CustomAttribute, we need the attribute's constructor. // in this case, there's only one - and it's an empty constructor. MethodDefinition ctor = weaverTypes.initializeOnLoadMethodAttribute.GetConstructors().First(); // using ctor directly throws: ArgumentException: Member 'System.Void UnityEditor.InitializeOnLoadMethodAttribute::.ctor()' is declared in another module and needs to be imported // we need to import it first. CustomAttribute attribute = new CustomAttribute(assembly.MainModule.ImportReference(ctor)); method.CustomAttributes.Add(attribute); }
private static MethodReference GetNetworkBehaviourWriter(TypeReference variableReference) { // all NetworkBehaviours can use the same write function if (writeFuncs.TryGetValue(WeaverTypes.Import <NetworkBehaviour>(), out MethodReference func)) { // register function so it is added to writer<T> // 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"); } }
private 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 <NetworkWriter>())); writerFunc.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.None, variable)); writerFunc.Body.InitLocals = true; RegisterWriteFunc(variable, writerFunc); return(writerFunc); }
internal static void ConfirmGeneratedCodeClass() { if (WeaveLists.generateContainerClass == null) { WeaveLists.generateContainerClass = new TypeDefinition("Mirror", "GeneratedNetworkCode", TypeAttributes.BeforeFieldInit | TypeAttributes.Class | TypeAttributes.AnsiClass | TypeAttributes.Public | TypeAttributes.AutoClass, WeaverTypes.Import <object>()); const MethodAttributes methodAttributes = MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName; MethodDefinition method = new MethodDefinition(".ctor", methodAttributes, WeaverTypes.Import(typeof(void))); method.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0)); method.Body.Instructions.Add(Instruction.Create(OpCodes.Call, Resolvers.ResolveMethod(WeaverTypes.Import <object>(), CurrentAssembly, ".ctor"))); method.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); WeaveLists.generateContainerClass.Methods.Add(method); } }
public static void ProcessSyncVar(TypeDefinition td, FieldDefinition fd, Dictionary <FieldDefinition, FieldDefinition> syncVarNetIds, long dirtyBit) { string originalName = fd.Name; Weaver.DLog(td, "Sync Var " + fd.Name + " " + fd.FieldType); // GameObject/NetworkIdentity SyncVars have a new field for netId FieldDefinition netIdField = null; if (fd.FieldType.Is <UnityEngine.GameObject>() || fd.FieldType.Is <NetworkIdentity>()) { netIdField = new FieldDefinition("___" + fd.Name + "NetId", FieldAttributes.Private, WeaverTypes.Import <uint>()); syncVarNetIds[fd] = netIdField; } MethodDefinition get = GenerateSyncVarGetter(fd, originalName, netIdField); MethodDefinition set = GenerateSyncVarSetter(td, fd, originalName, dirtyBit, netIdField); //NOTE: is property even needed? Could just use a setter function? //create the property PropertyDefinition propertyDefinition = new PropertyDefinition("Network" + originalName, PropertyAttributes.None, fd.FieldType) { GetMethod = get, SetMethod = set }; //add the methods and property to the type. td.Methods.Add(get); td.Methods.Add(set); td.Properties.Add(propertyDefinition); Weaver.WeaveLists.replacementSetterProperties[fd] = set; // replace getter field if GameObject/NetworkIdentity so it uses // netId instead // -> only for GameObjects, otherwise an int syncvar's getter would // end up in recursion. if (fd.FieldType.Is <UnityEngine.GameObject>() || fd.FieldType.Is <NetworkIdentity>()) { Weaver.WeaveLists.replacementGetterProperties[fd] = get; } }
static void InjectClientGuard(WeaverTypes weaverTypes, MethodDefinition md, bool logWarning) { ILProcessor worker = md.Body.GetILProcessor(); Instruction top = md.Body.Instructions[0]; worker.InsertBefore(top, worker.Create(OpCodes.Call, weaverTypes.NetworkClientGetActive)); worker.InsertBefore(top, worker.Create(OpCodes.Brtrue, top)); if (logWarning) { worker.InsertBefore(top, worker.Create(OpCodes.Ldstr, $"[Client] function '{md.FullName}' called when client was not active")); worker.InsertBefore(top, worker.Create(OpCodes.Call, weaverTypes.logWarningReference)); } InjectGuardParameters(md, worker, top); InjectGuardReturnValue(md, worker, top); worker.InsertBefore(top, worker.Create(OpCodes.Ret)); }
public static MethodDefinition ProcessTargetRpcInvoke(WeaverTypes weaverTypes, Readers readers, Logger Log, TypeDefinition td, MethodDefinition md, MethodDefinition rpcCallFunc, ref bool WeavingFailed) { MethodDefinition rpc = new MethodDefinition(Weaver.InvokeRpcPrefix + md.Name, MethodAttributes.Family | MethodAttributes.Static | MethodAttributes.HideBySig, weaverTypes.Import(typeof(void))); ILProcessor worker = rpc.Body.GetILProcessor(); Instruction label = worker.Create(OpCodes.Nop); NetworkBehaviourProcessor.WriteClientActiveCheck(worker, weaverTypes, md.Name, label, "TargetRPC"); // setup for reader worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Castclass, td); // NetworkConnection parameter is optional if (HasNetworkConnectionParameter(md)) { // on server, the NetworkConnection parameter is a connection to client. // when the rpc is invoked on the client, it still has the same // function signature. we pass in the connection to server, // which is cleaner than just passing null) //NetworkClient.readyconnection // // TODO // a) .connectionToServer = best solution. no doubt. // b) NetworkClient.connection for now. add TODO to not use static later. worker.Emit(OpCodes.Call, weaverTypes.NetworkClientConnectionReference); } // process reader parameters and skip first one if first one is NetworkConnection if (!NetworkBehaviourProcessor.ReadArguments(md, readers, Log, worker, RemoteCallType.TargetRpc, ref WeavingFailed)) { return(null); } // invoke actual command function worker.Emit(OpCodes.Callvirt, rpcCallFunc); worker.Emit(OpCodes.Ret); NetworkBehaviourProcessor.AddInvokeParameters(weaverTypes, rpc.Parameters); td.Methods.Add(rpc); return(rpc); }
static bool WeaveSyncObject(TypeDefinition td) { bool modified = false; // ignore generic classes // we can not process generic classes // we give error if a generic syncObject is used in NetworkBehaviour if (td.HasGenericParameters) { return(false); } // ignore abstract classes // we dont need to process abstract classes because classes that // inherit from them will be processed instead // We cant early return with non classes or Abstract classes // because we still need to check for embeded types if (td.IsClass || !td.IsAbstract) { if (td.IsDerivedFrom(typeof(SyncList <>))) { SyncListProcessor.Process(td, WeaverTypes.Import(typeof(SyncList <>))); modified = true; } else if (td.IsDerivedFrom(typeof(SyncSet <>))) { SyncListProcessor.Process(td, WeaverTypes.Import(typeof(SyncSet <>))); modified = true; } else if (td.IsDerivedFrom(typeof(SyncDictionary <,>))) { SyncDictionaryProcessor.Process(td); modified = true; } } // check for embedded types foreach (TypeDefinition embedded in td.NestedTypes) { modified |= WeaveSyncObject(embedded); } return(modified); }
static bool Weave(string assName, IEnumerable <string> dependencies) { using (DefaultAssemblyResolver asmResolver = new DefaultAssemblyResolver()) using (CurrentAssembly = AssemblyDefinition.ReadAssembly(assName, new ReaderParameters { ReadWrite = true, ReadSymbols = true, AssemblyResolver = asmResolver })) { asmResolver.AddSearchDirectory(Path.GetDirectoryName(assName)); asmResolver.AddSearchDirectory(Helpers.UnityEngineDllDirectoryName()); if (dependencies != null) { foreach (string path in dependencies) { asmResolver.AddSearchDirectory(path); } } WeaverTypes.SetupTargetTypes(CurrentAssembly); System.Diagnostics.Stopwatch rwstopwatch = System.Diagnostics.Stopwatch.StartNew(); ReaderWriterProcessor.Process(CurrentAssembly); rwstopwatch.Stop(); Console.WriteLine($"Find all reader and writers took {rwstopwatch.ElapsedMilliseconds} milliseconds"); ModuleDefinition moduleDefinition = CurrentAssembly.MainModule; Console.WriteLine($"Script Module: {moduleDefinition.Name}"); bool modified = WeaveModule(moduleDefinition); if (WeavingFailed) { return(false); } if (modified) { // write to outputDir if specified, otherwise perform in-place write WriterParameters writeParams = new WriterParameters { WriteSymbols = true }; CurrentAssembly.Write(writeParams); } } return(true); }
/* * generates code like: * * public void RpcTest (int param) * { * NetworkWriter writer = new NetworkWriter (); * writer.WritePackedUInt32((uint)param); * base.SendRPCInternal(typeof(class),"RpcTest", writer, 0); * } * public void CallRpcTest (int param) * { * // whatever the user did before * } * * Originally HLAPI put the send message code inside the Call function * and then proceeded to replace every call to RpcTest with CallRpcTest * * This method moves all the user's code into the "CallRpc" method * and replaces the body of the original method with the send message code. * This way we do not need to modify the code anywhere else, and this works * correctly in dependent assemblies */ public static MethodDefinition ProcessRpcCall(WeaverTypes weaverTypes, Writers writers, Logger Log, TypeDefinition td, MethodDefinition md, CustomAttribute clientRpcAttr, ref bool WeavingFailed) { MethodDefinition rpc = MethodProcessor.SubstituteMethod(Log, td, md, ref WeavingFailed); ILProcessor worker = md.Body.GetILProcessor(); NetworkBehaviourProcessor.WriteSetupLocals(worker, weaverTypes); // add a log message if needed for debugging //worker.Emit(OpCodes.Ldstr, "Call ClientRpc function " + md.Name); //worker.Emit(OpCodes.Call, WeaverTypes.logErrorReference); NetworkBehaviourProcessor.WriteCreateWriter(worker, weaverTypes); // write all the arguments that the user passed to the Rpc call if (!NetworkBehaviourProcessor.WriteArguments(worker, writers, Log, md, RemoteCallType.ClientRpc, ref WeavingFailed)) { return(null); } string rpcName = md.Name; int channel = clientRpcAttr.GetField("channel", 0); bool includeOwner = clientRpcAttr.GetField("includeOwner", true); // invoke SendInternal and return // this worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldtoken, td); // invokerClass worker.Emit(OpCodes.Call, weaverTypes.getTypeFromHandleReference); worker.Emit(OpCodes.Ldstr, rpcName); // writer worker.Emit(OpCodes.Ldloc_0); worker.Emit(OpCodes.Ldc_I4, channel); // includeOwner ? 1 : 0 worker.Emit(includeOwner ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); worker.Emit(OpCodes.Callvirt, weaverTypes.sendRpcInternal); NetworkBehaviourProcessor.WriteRecycleWriter(worker, weaverTypes); worker.Emit(OpCodes.Ret); return(rpc); }
/* generates code like: * public void TargetTest (NetworkConnection conn, int param) * { * NetworkWriter writer = new NetworkWriter (); * writer.WritePackedUInt32 ((uint)param); * base.SendTargetRPCInternal (conn, typeof(class), "TargetTest", val); * } * public void CallTargetTest (NetworkConnection conn, int param) * { * // whatever the user did before * } * * or if optional: * public void TargetTest (int param) * { * NetworkWriter writer = new NetworkWriter (); * writer.WritePackedUInt32 ((uint)param); * base.SendTargetRPCInternal (null, typeof(class), "TargetTest", val); * } * public void CallTargetTest (int param) * { * // whatever the user did before * } * * Originally HLAPI put the send message code inside the Call function * and then proceeded to replace every call to TargetTest with CallTargetTest * * This method moves all the user's code into the "CallTargetRpc" method * and replaces the body of the original method with the send message code. * This way we do not need to modify the code anywhere else, and this works * correctly in dependent assemblies * */ public static MethodDefinition ProcessTargetRpcCall(WeaverTypes weaverTypes, Writers writers, Logger Log, TypeDefinition td, MethodDefinition md, CustomAttribute targetRpcAttr, ref bool WeavingFailed) { MethodDefinition rpc = MethodProcessor.SubstituteMethod(Log, td, md, ref WeavingFailed); ILProcessor worker = md.Body.GetILProcessor(); NetworkBehaviourProcessor.WriteSetupLocals(worker, weaverTypes); NetworkBehaviourProcessor.WriteGetWriter(worker, weaverTypes); // write all the arguments that the user passed to the TargetRpc call // (skip first one if first one is NetworkConnection) if (!NetworkBehaviourProcessor.WriteArguments(worker, writers, Log, md, RemoteCallType.TargetRpc, ref WeavingFailed)) { return(null); } // invoke SendInternal and return // this worker.Emit(OpCodes.Ldarg_0); if (HasNetworkConnectionParameter(md)) { // connection worker.Emit(OpCodes.Ldarg_1); } else { // null worker.Emit(OpCodes.Ldnull); } // pass full function name to avoid ClassA.Func <-> ClassB.Func collisions worker.Emit(OpCodes.Ldstr, md.FullName); // writer worker.Emit(OpCodes.Ldloc_0); worker.Emit(OpCodes.Ldc_I4, targetRpcAttr.GetField("channel", 0)); worker.Emit(OpCodes.Callvirt, weaverTypes.sendTargetRpcInternal); NetworkBehaviourProcessor.WriteReturnWriter(worker, weaverTypes); worker.Emit(OpCodes.Ret); return(rpc); }
private static void GenerateContainerNullCheck(ILProcessor worker) { // if (value == null) // { // writer.WritePackedInt32(-1); // return; // } Instruction labelNull = worker.Create(OpCodes.Nop); worker.Append(worker.Create(OpCodes.Ldarg_1)); worker.Append(worker.Create(OpCodes.Brtrue, labelNull)); worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Ldc_I4_M1)); worker.Append(worker.Create(OpCodes.Call, GetWriteFunc(WeaverTypes.Import <int>()))); worker.Append(worker.Create(OpCodes.Ret)); // else not null worker.Append(labelNull); }
private static void GenerateFor(ILProcessor worker, Action body) { MethodReference intWriterFunc = GetWriteFunc(WeaverTypes.Import <int>()); // writer.WritePackedInt32(count); worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Ldloc_0)); worker.Append(worker.Create(OpCodes.Call, intWriterFunc)); // Loop through the List<T> and call the writer for each element. // generates this: // for (int i=0; i < length; i++) // { // writer.WriteT(value[i]); // } worker.Append(worker.Create(OpCodes.Ldc_I4_0)); worker.Append(worker.Create(OpCodes.Stloc_1)); Instruction labelHead = worker.Create(OpCodes.Nop); worker.Append(worker.Create(OpCodes.Br, labelHead)); // loop body Instruction labelBody = worker.Create(OpCodes.Nop); worker.Append(labelBody); body(); // end for loop // for loop i++ worker.Append(worker.Create(OpCodes.Ldloc_1)); worker.Append(worker.Create(OpCodes.Ldc_I4_1)); worker.Append(worker.Create(OpCodes.Add)); worker.Append(worker.Create(OpCodes.Stloc_1)); worker.Append(labelHead); // for loop i < length worker.Append(worker.Create(OpCodes.Ldloc_1)); worker.Append(worker.Create(OpCodes.Ldloc_0)); worker.Append(worker.Create(OpCodes.Blt, labelBody)); }
// helper function to add [RuntimeInitializeOnLoad] attribute to method static void AddRuntimeInitializeOnLoadAttribute(AssemblyDefinition assembly, WeaverTypes weaverTypes, MethodDefinition method) { // NOTE: previously we used reflection because according paul, // 'weaving Mirror.dll caused unity to rebuild all dlls but in wrong // order, which breaks rewired' // it's not obvious why importing an attribute via reflection instead // of cecil would break anything. let's use cecil. // to add a CustomAttribute, we need the attribute's constructor. // in this case, there are two: empty, and RuntimeInitializeOnLoadType. // we want the last one, with the type parameter. MethodDefinition ctor = weaverTypes.runtimeInitializeOnLoadMethodAttribute.GetConstructors().Last(); //MethodDefinition ctor = weaverTypes.runtimeInitializeOnLoadMethodAttribute.GetConstructors().First(); // using ctor directly throws: ArgumentException: Member 'System.Void UnityEditor.InitializeOnLoadMethodAttribute::.ctor()' is declared in another module and needs to be imported // we need to import it first. CustomAttribute attribute = new CustomAttribute(assembly.MainModule.ImportReference(ctor)); // add the RuntimeInitializeLoadType.BeforeSceneLoad argument to ctor attribute.ConstructorArguments.Add(new CustomAttributeArgument(weaverTypes.Import <RuntimeInitializeLoadType>(), RuntimeInitializeLoadType.BeforeSceneLoad)); method.CustomAttributes.Add(attribute); }
/* * // generates code like: * public void CmdThrust(float thrusting, int spin) * { * NetworkWriter networkWriter = new NetworkWriter(); * networkWriter.Write(thrusting); * networkWriter.WritePackedUInt32((uint)spin); * base.SendCommandInternal(cmdName, networkWriter, cmdName); * } * * public void CallCmdThrust(float thrusting, int spin) * { * // whatever the user was doing before * } * * Originally HLAPI put the send message code inside the Call function * and then proceeded to replace every call to CmdTrust with CallCmdTrust * * This method moves all the user's code into the "CallCmd" method * and replaces the body of the original method with the send message code. * This way we do not need to modify the code anywhere else, and this works * correctly in dependent assemblies */ public static MethodDefinition ProcessCommandCall(WeaverTypes weaverTypes, Writers writers, Logger Log, TypeDefinition td, MethodDefinition md, CustomAttribute commandAttr, ref bool WeavingFailed) { MethodDefinition cmd = MethodProcessor.SubstituteMethod(Log, td, md, ref WeavingFailed); ILProcessor worker = md.Body.GetILProcessor(); NetworkBehaviourProcessor.WriteSetupLocals(worker, weaverTypes); // NetworkWriter writer = new NetworkWriter(); NetworkBehaviourProcessor.WriteCreateWriter(worker, weaverTypes); // write all the arguments that the user passed to the Cmd call if (!NetworkBehaviourProcessor.WriteArguments(worker, writers, Log, md, RemoteCallType.Command, ref WeavingFailed)) { return(null); } string cmdName = md.Name; int channel = commandAttr.GetField("channel", 0); bool requiresAuthority = commandAttr.GetField("requiresAuthority", true); // invoke internal send and return // load 'base.' to call the SendCommand function with worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldtoken, td); // invokerClass worker.Emit(OpCodes.Call, weaverTypes.getTypeFromHandleReference); worker.Emit(OpCodes.Ldstr, cmdName); // writer worker.Emit(OpCodes.Ldloc_0); worker.Emit(OpCodes.Ldc_I4, channel); // requiresAuthority ? 1 : 0 worker.Emit(requiresAuthority ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); worker.Emit(OpCodes.Call, weaverTypes.sendCommandInternal); NetworkBehaviourProcessor.WriteRecycleWriter(worker, weaverTypes); worker.Emit(OpCodes.Ret); return(cmd); }
/// <summary> /// Generates a skeleton for an RPC /// </summary> /// <param name="td"></param> /// <param name="method"></param> /// <param name="cmdCallFunc"></param> /// <returns>The newly created skeleton method</returns> /// <remarks> /// Generates code like this: /// <code> /// protected static void Skeleton_Test(NetworkBehaviour obj, NetworkReader reader, NetworkConnection senderConnection) /// { /// if (!obj.netIdentity.server.active) /// { /// return; /// } /// ((ShipControl) obj).UserCode_Test(reader.ReadSingle(), (int) reader.ReadPackedUInt32()); /// } /// </code> /// </remarks> public static MethodDefinition GenerateSkeleton(MethodDefinition md, MethodDefinition userCodeFunc, CustomAttribute clientRpcAttr) { var rpc = new MethodDefinition( MethodProcessor.SkeletonPrefix + md.Name, MethodAttributes.Family | MethodAttributes.Static | MethodAttributes.HideBySig, WeaverTypes.Import(typeof(void))); ILProcessor worker = rpc.Body.GetILProcessor(); Instruction label = worker.Create(OpCodes.Nop); NetworkBehaviourProcessor.WriteClientActiveCheck(worker, md.Name, label, "RPC"); // setup for reader worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Castclass, md.DeclaringType)); // NetworkConnection parameter is only required for Client.Connection Client target = clientRpcAttr.GetField("target", Client.Observers); bool hasNetworkConnection = target == Client.Connection && HasNetworkConnectionParameter(md); if (hasNetworkConnection) { //client.connection worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Call, WeaverTypes.BehaviorConnectionToServerReference)); } if (!NetworkBehaviourProcessor.ReadArguments(md, worker, hasNetworkConnection)) { return(null); } // invoke actual ServerRpc function worker.Append(worker.Create(OpCodes.Callvirt, userCodeFunc)); worker.Append(worker.Create(OpCodes.Ret)); NetworkBehaviourProcessor.AddInvokeParameters(rpc.Parameters); md.DeclaringType.Methods.Add(rpc); return(rpc); }
static bool Weave(Assembly unityAssembly) { using (var asmResolver = new DefaultAssemblyResolver()) using (CurrentAssembly = AssemblyDefinition.ReadAssembly(unityAssembly.outputPath, new ReaderParameters { ReadWrite = true, ReadSymbols = true, AssemblyResolver = asmResolver })) { AddPaths(asmResolver, unityAssembly); WeaverTypes.SetupTargetTypes(CurrentAssembly); var rwstopwatch = System.Diagnostics.Stopwatch.StartNew(); ReaderWriterProcessor.Process(CurrentAssembly, unityAssembly); rwstopwatch.Stop(); Console.WriteLine($"Find all reader and writers took {rwstopwatch.ElapsedMilliseconds} milliseconds"); ModuleDefinition moduleDefinition = CurrentAssembly.MainModule; Console.WriteLine($"Script Module: {moduleDefinition.Name}"); bool modified = WeaveModule(moduleDefinition); if (WeavingFailed) { return(false); } if (modified) { ReaderWriterProcessor.InitializeReaderAndWriters(CurrentAssembly); // write to outputDir if specified, otherwise perform in-place write var writeParams = new WriterParameters { WriteSymbols = true }; CurrentAssembly.Write(writeParams); } } return(true); }
static MethodDefinition GenerateClassOrStructWriterFunction(TypeReference variable, int recursionCount) { if (recursionCount > MaxRecursionCount) { Weaver.Error($"{variable.Name} can't be serialized because it references itself", variable); return(null); } string functionName = "_Write" + variable.Name + "_"; if (variable.DeclaringType != null) { functionName += variable.DeclaringType.Name; } else { functionName += "None"; } // create new writer for this type var writerFunc = new MethodDefinition(functionName, MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, WeaverTypes.Import(typeof(void))); writerFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, WeaverTypes.Import <Mirror.NetworkWriter>())); writerFunc.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(variable))); ILProcessor worker = writerFunc.Body.GetILProcessor(); if (!WriteAllFields(variable, recursionCount, worker)) { return(null); } worker.Append(worker.Create(OpCodes.Ret)); return(writerFunc); }
public static MethodDefinition ProcessTargetRpcInvoke(TypeDefinition td, MethodDefinition md, MethodDefinition rpcCallFunc) { MethodDefinition rpc = new MethodDefinition(Weaver.InvokeRpcPrefix + md.Name, MethodAttributes.Family | MethodAttributes.Static | MethodAttributes.HideBySig, WeaverTypes.Import(typeof(void))); ILProcessor worker = rpc.Body.GetILProcessor(); Instruction label = worker.Create(OpCodes.Nop); NetworkBehaviourProcessor.WriteClientActiveCheck(worker, md.Name, label, "TargetRPC"); // setup for reader worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Castclass, td); // NetworkConnection parameter is optional if (HasNetworkConnectionParameter(md)) { // if call has NetworkConnection write clients connection as first arg //ClientScene.readyconnection worker.Emit(OpCodes.Call, WeaverTypes.ReadyConnectionReference); } // process reader parameters and skip first one if first one is NetworkConnection if (!NetworkBehaviourProcessor.ReadArguments(md, worker, RemoteCallType.TargetRpc)) { return(null); } // invoke actual command function worker.Emit(OpCodes.Callvirt, rpcCallFunc); worker.Emit(OpCodes.Ret); NetworkBehaviourProcessor.AddInvokeParameters(rpc.Parameters); td.Methods.Add(rpc); return(rpc); }
/// <summary> /// Generates serialization methods for synclists /// </summary> /// <param name="td">The synclist class</param> public static void Process(TypeDefinition td) { GenericArgumentResolver resolver = new GenericArgumentResolver(2); TypeReference syncDictionaryType = WeaverTypes.Import(typeof(SyncDictionary <,>)); if (resolver.GetGenericFromBaseClass(td, 0, syncDictionaryType, out TypeReference keyType)) { SyncObjectProcessor.GenerateSerialization(td, keyType, syncDictionaryType, "SerializeKey", "DeserializeKey"); } else { Weaver.Error($"Could not find generic arguments for SyncDictionary in {td.Name}", td); return; } if (resolver.GetGenericFromBaseClass(td, 1, syncDictionaryType, out TypeReference itemType)) { SyncObjectProcessor.GenerateSerialization(td, itemType, syncDictionaryType, "SerializeItem", "DeserializeItem"); } else { Weaver.Error($"Could not find generic arguments for SyncDictionary in {td.Name}", td); } }
static MethodDefinition GenerateCollectionWriter(TypeReference variable, TypeReference elementType, string writerFunction) { MethodDefinition writerFunc = GenerateWriterFunc(variable); MethodReference elementWriteFunc = GetWriteFunc(elementType); MethodReference intWriterFunc = GetWriteFunc(WeaverTypes.Import <int>()); // 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); var methodRef = new GenericInstanceMethod(collectionWriter); methodRef.GenericArguments.Add(elementType); // generates // reader.WriteArray<T>(array); ILProcessor worker = writerFunc.Body.GetILProcessor(); worker.Append(worker.Create(OpCodes.Ldarg_0)); // writer worker.Append(worker.Create(OpCodes.Ldarg_1)); // collection worker.Append(worker.Create(OpCodes.Call, methodRef)); // WriteArray worker.Append(worker.Create(OpCodes.Ret)); return(writerFunc); }
private static void WriteNullCheck(ILProcessor worker) { // if (value == null) // { // writer.WriteBoolean(false); // return; // } // Instruction labelNotNull = worker.Create(OpCodes.Nop); worker.Append(worker.Create(OpCodes.Ldarg_1)); worker.Append(worker.Create(OpCodes.Brtrue, labelNotNull)); worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Ldc_I4_0)); worker.Append(worker.Create(OpCodes.Call, GetWriteFunc(WeaverTypes.Import <bool>()))); worker.Append(worker.Create(OpCodes.Ret)); worker.Append(labelNotNull); // write.WriteBoolean(true); worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Ldc_I4_1)); worker.Append(worker.Create(OpCodes.Call, GetWriteFunc(WeaverTypes.Import <bool>()))); }
static bool Weave(string assName, AssemblyDefinition unityAssembly, AssemblyDefinition mirrorAssembly, IEnumerable <string> dependencies, string unityEngineDLLPath, string mirrorNetDLLPath, string outputDir) { using (DefaultAssemblyResolver asmResolver = new DefaultAssemblyResolver()) using (CurrentAssembly = AssemblyDefinition.ReadAssembly(assName, new ReaderParameters { ReadWrite = true, ReadSymbols = true, AssemblyResolver = asmResolver })) { asmResolver.AddSearchDirectory(Path.GetDirectoryName(assName)); asmResolver.AddSearchDirectory(Helpers.UnityEngineDllDirectoryName()); asmResolver.AddSearchDirectory(Path.GetDirectoryName(unityEngineDLLPath)); asmResolver.AddSearchDirectory(Path.GetDirectoryName(mirrorNetDLLPath)); if (dependencies != null) { foreach (string path in dependencies) { asmResolver.AddSearchDirectory(path); } } WeaverTypes.SetupTargetTypes(unityAssembly, mirrorAssembly, CurrentAssembly); System.Diagnostics.Stopwatch rwstopwatch = System.Diagnostics.Stopwatch.StartNew(); ReaderWriterProcessor.Process(CurrentAssembly); rwstopwatch.Stop(); Console.WriteLine("Find all reader and writers took " + rwstopwatch.ElapsedMilliseconds + " milliseconds"); ModuleDefinition moduleDefinition = CurrentAssembly.MainModule; Console.WriteLine("Script Module: {0}", moduleDefinition.Name); bool modified = WeaveModule(moduleDefinition); if (WeavingFailed) { return(false); } if (modified) { // this must be done for ALL code, not just NetworkBehaviours try { PropertySiteProcessor.Process(moduleDefinition); } catch (Exception e) { Log.Error("ProcessPropertySites exception: " + e); return(false); } if (WeavingFailed) { return(false); } // write to outputDir if specified, otherwise perform in-place write WriterParameters writeParams = new WriterParameters { WriteSymbols = true }; if (!string.IsNullOrEmpty(outputDir)) { CurrentAssembly.Write(Helpers.DestinationFileFor(outputDir, assName), writeParams); } else { CurrentAssembly.Write(writeParams); } } } return(true); }
void GenerateConstants() { if (commands.Count == 0 && clientRpcs.Count == 0 && targetRpcs.Count == 0 && syncObjects.Count == 0) { return; } Weaver.DLog(netBehaviourSubclass, " GenerateConstants "); // find static constructor MethodDefinition cctor = netBehaviourSubclass.GetMethod(".cctor"); bool cctorFound = cctor != null; if (cctor != null) { // remove the return opcode from end of function. will add our own later. if (cctor.Body.Instructions.Count != 0) { Instruction retInstr = cctor.Body.Instructions[cctor.Body.Instructions.Count - 1]; if (retInstr.OpCode == OpCodes.Ret) { cctor.Body.Instructions.RemoveAt(cctor.Body.Instructions.Count - 1); } else { Weaver.Error($"{netBehaviourSubclass.Name} has invalid class constructor", cctor); return; } } } else { // make one! cctor = new MethodDefinition(".cctor", MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName | MethodAttributes.Static, WeaverTypes.Import(typeof(void))); } // find instance constructor MethodDefinition ctor = netBehaviourSubclass.GetMethod(".ctor"); if (ctor == null) { Weaver.Error($"{netBehaviourSubclass.Name} has invalid constructor", netBehaviourSubclass); return; } Instruction ret = ctor.Body.Instructions[ctor.Body.Instructions.Count - 1]; if (ret.OpCode == OpCodes.Ret) { ctor.Body.Instructions.RemoveAt(ctor.Body.Instructions.Count - 1); } else { Weaver.Error($"{netBehaviourSubclass.Name} has invalid constructor", ctor); return; } // TODO: find out if the order below matters. If it doesn't split code below into 2 functions ILProcessor ctorWorker = ctor.Body.GetILProcessor(); ILProcessor cctorWorker = cctor.Body.GetILProcessor(); for (int i = 0; i < commands.Count; ++i) { CmdResult cmdResult = commands[i]; GenerateRegisterCommandDelegate(cctorWorker, WeaverTypes.registerCommandDelegateReference, commandInvocationFuncs[i], cmdResult); } for (int i = 0; i < clientRpcs.Count; ++i) { ClientRpcResult clientRpcResult = clientRpcs[i]; GenerateRegisterRemoteDelegate(cctorWorker, WeaverTypes.registerRpcDelegateReference, clientRpcInvocationFuncs[i], clientRpcResult.method.Name); } for (int i = 0; i < targetRpcs.Count; ++i) { GenerateRegisterRemoteDelegate(cctorWorker, WeaverTypes.registerRpcDelegateReference, targetRpcInvocationFuncs[i], targetRpcs[i].Name); } foreach (FieldDefinition fd in syncObjects) { SyncObjectInitializer.GenerateSyncObjectInitializer(ctorWorker, fd); } cctorWorker.Append(cctorWorker.Create(OpCodes.Ret)); if (!cctorFound) { netBehaviourSubclass.Methods.Add(cctor); } // finish ctor ctorWorker.Append(ctorWorker.Create(OpCodes.Ret)); // in case class had no cctor, it might have BeforeFieldInit, so injected cctor would be called too late netBehaviourSubclass.Attributes &= ~TypeAttributes.BeforeFieldInit; }
void GenerateSerialization() { Weaver.DLog(netBehaviourSubclass, " GenerateSerialization"); const string SerializeMethodName = "SerializeSyncVars"; if (netBehaviourSubclass.GetMethod(SerializeMethodName) != null) { return; } if (syncVars.Count == 0) { // no synvars, no need for custom OnSerialize return; } MethodDefinition serialize = new MethodDefinition(SerializeMethodName, MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, WeaverTypes.Import <bool>()); serialize.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, WeaverTypes.Import <NetworkWriter>())); serialize.Parameters.Add(new ParameterDefinition("forceAll", ParameterAttributes.None, WeaverTypes.Import <bool>())); ILProcessor worker = serialize.Body.GetILProcessor(); serialize.Body.InitLocals = true; // loc_0, this local variable is to determine if any variable was dirty VariableDefinition dirtyLocal = new VariableDefinition(WeaverTypes.Import <bool>()); serialize.Body.Variables.Add(dirtyLocal); MethodReference baseSerialize = Resolvers.TryResolveMethodInParents(netBehaviourSubclass.BaseType, Weaver.CurrentAssembly, SerializeMethodName); if (baseSerialize != null) { // base worker.Append(worker.Create(OpCodes.Ldarg_0)); // writer worker.Append(worker.Create(OpCodes.Ldarg_1)); // forceAll worker.Append(worker.Create(OpCodes.Ldarg_2)); worker.Append(worker.Create(OpCodes.Call, baseSerialize)); // set dirtyLocal to result of base.OnSerialize() worker.Append(worker.Create(OpCodes.Stloc_0)); } // Generates: if (forceAll); Instruction initialStateLabel = worker.Create(OpCodes.Nop); // forceAll worker.Append(worker.Create(OpCodes.Ldarg_2)); worker.Append(worker.Create(OpCodes.Brfalse, initialStateLabel)); foreach (FieldDefinition syncVar in syncVars) { // Generates a writer call for each sync variable // writer worker.Append(worker.Create(OpCodes.Ldarg_1)); // this worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Ldfld, syncVar)); MethodReference writeFunc = Writers.GetWriteFunc(syncVar.FieldType); if (writeFunc != null) { worker.Append(worker.Create(OpCodes.Call, writeFunc)); } else { Weaver.Error($"{syncVar.Name} has unsupported type. Use a supported Mirror type instead", syncVar); return; } } // always return true if forceAll // Generates: return true worker.Append(worker.Create(OpCodes.Ldc_I4_1)); worker.Append(worker.Create(OpCodes.Ret)); // Generates: end if (forceAll); worker.Append(initialStateLabel); // write dirty bits before the data fields // Generates: writer.WritePackedUInt64 (base.get_syncVarDirtyBits ()); // writer worker.Append(worker.Create(OpCodes.Ldarg_1)); // base worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Call, WeaverTypes.NetworkBehaviourDirtyBitsReference)); MethodReference writeUint64Func = Writers.GetWriteFunc(WeaverTypes.Import <ulong>()); worker.Append(worker.Create(OpCodes.Call, writeUint64Func)); // generate a writer call for any dirty variable in this class // start at number of syncvars in parent int dirtyBit = Weaver.WeaveLists.GetSyncVarStart(netBehaviourSubclass.BaseType.FullName); foreach (FieldDefinition syncVar in syncVars) { Instruction varLabel = worker.Create(OpCodes.Nop); // Generates: if ((base.get_syncVarDirtyBits() & 1uL) != 0uL) // base worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Call, WeaverTypes.NetworkBehaviourDirtyBitsReference)); // 8 bytes = long worker.Append(worker.Create(OpCodes.Ldc_I8, 1L << dirtyBit)); worker.Append(worker.Create(OpCodes.And)); worker.Append(worker.Create(OpCodes.Brfalse, varLabel)); // Generates a call to the writer for that field // writer worker.Append(worker.Create(OpCodes.Ldarg_1)); // base worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Ldfld, syncVar)); MethodReference writeFunc = Writers.GetWriteFunc(syncVar.FieldType); if (writeFunc != null) { worker.Append(worker.Create(OpCodes.Call, writeFunc)); } else { Weaver.Error($"{syncVar.Name} has unsupported type. Use a supported Mirror type instead", syncVar); return; } // something was dirty worker.Append(worker.Create(OpCodes.Ldc_I4_1)); // set dirtyLocal to true worker.Append(worker.Create(OpCodes.Stloc_0)); worker.Append(varLabel); dirtyBit += 1; } if (Weaver.GenerateLogErrors) { worker.Append(worker.Create(OpCodes.Ldstr, "Injected Serialize " + netBehaviourSubclass.Name)); worker.Append(worker.Create(OpCodes.Call, WeaverTypes.logErrorReference)); } // generate: return dirtyLocal worker.Append(worker.Create(OpCodes.Ldloc_0)); worker.Append(worker.Create(OpCodes.Ret)); netBehaviourSubclass.Methods.Add(serialize); }
/// <summary> /// [SyncVar] GameObject/NetworkIdentity? /// </summary> /// <param name="syncVar"></param> /// <param name="worker"></param> /// <param name="deserialize"></param> /// <param name="initialState"></param> /// <param name="hookResult"></param> void DeserializeNetworkIdentityField(FieldDefinition syncVar, ILProcessor worker, MethodDefinition deserialize, MethodDefinition hookMethod) { /* * Generates code like: * uint oldNetId = ___qNetId; * // returns GetSyncVarGameObject(___qNetId) * GameObject oldSyncVar = syncvar.getter; * ___qNetId = reader.ReadPackedUInt32(); * if (!SyncVarEqual(oldNetId, ref ___goNetId)) * { * // getter returns GetSyncVarGameObject(___qNetId) * OnSetQ(oldSyncVar, syncvar.getter); * } */ // GameObject/NetworkIdentity SyncVar: // OnSerialize sends writer.Write(go); // OnDeserialize reads to __netId manually so we can use // lookups in the getter (so it still works if objects // move in and out of range repeatedly) FieldDefinition netIdField = syncVarNetIds[syncVar]; // uint oldNetId = ___qNetId; VariableDefinition oldNetId = new VariableDefinition(WeaverTypes.Import <uint>()); deserialize.Body.Variables.Add(oldNetId); worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Ldfld, netIdField)); worker.Append(worker.Create(OpCodes.Stloc, oldNetId)); // GameObject/NetworkIdentity oldSyncVar = syncvar.getter; VariableDefinition oldSyncVar = new VariableDefinition(syncVar.FieldType); deserialize.Body.Variables.Add(oldSyncVar); worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Ldfld, syncVar)); worker.Append(worker.Create(OpCodes.Stloc, oldSyncVar)); // read id and store in netId field BEFORE calling the hook // -> this makes way more sense. by definition, the hook is // supposed to be called after it was changed. not before. // -> setting it BEFORE calling the hook fixes the following bug: // https://github.com/vis2k/Mirror/issues/1151 in host mode // where the value during the Hook call would call Cmds on // the host server, and they would all happen and compare // values BEFORE the hook even returned and hence BEFORE the // actual value was even set. // put 'this.' onto stack for 'this.netId' below worker.Append(worker.Create(OpCodes.Ldarg_0)); // reader. for 'reader.Read()' below worker.Append(worker.Create(OpCodes.Ldarg_1)); // Read() worker.Append(worker.Create(OpCodes.Call, Readers.GetReadFunc(WeaverTypes.Import <uint>()))); // netId worker.Append(worker.Create(OpCodes.Stfld, netIdField)); if (hookMethod != null) { // call Hook(this.GetSyncVarGameObject/NetworkIdentity(reader.ReadPackedUInt32())) // because we send/receive the netID, not the GameObject/NetworkIdentity // but only if SyncVar changed. otherwise a client would // get hook calls for all initial values, even if they // didn't change from the default values on the client. // see also: https://github.com/vis2k/Mirror/issues/1278 // IMPORTANT: for GameObjects/NetworkIdentities we usually // use SyncVarGameObjectEqual to compare equality. // in this case however, we can just use // SyncVarEqual with the two uint netIds. // => this is easier weaver code because we don't // have to get the GameObject/NetworkIdentity // from the uint netId // => this is faster because we void one // GetComponent call for GameObjects to get // their NetworkIdentity when comparing. // Generates: if (!SyncVarEqual); Instruction syncVarEqualLabel = worker.Create(OpCodes.Nop); // 'this.' for 'this.SyncVarEqual' worker.Append(worker.Create(OpCodes.Ldarg_0)); // 'oldNetId' worker.Append(worker.Create(OpCodes.Ldloc, oldNetId)); // 'ref this.__netId' worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Ldflda, netIdField)); // call the function GenericInstanceMethod syncVarEqualGm = new GenericInstanceMethod(WeaverTypes.syncVarEqualReference); syncVarEqualGm.GenericArguments.Add(netIdField.FieldType); worker.Append(worker.Create(OpCodes.Call, syncVarEqualGm)); worker.Append(worker.Create(OpCodes.Brtrue, syncVarEqualLabel)); // call the hook // Generates: OnValueChanged(oldValue, this.syncVar); SyncVarProcessor.WriteCallHookMethodUsingField(worker, hookMethod, oldSyncVar, syncVar); // Generates: end if (!SyncVarEqual); worker.Append(syncVarEqualLabel); } }
// serialization of individual element static bool GenerateSerialization(string methodName, TypeDefinition td, TypeReference itemType, TypeReference mirrorBaseType) { Weaver.DLog(td, " GenerateSerialization"); bool existing = td.HasMethodInBaseType(methodName, mirrorBaseType); if (existing) { return(true); } // this check needs to happen inside GenerateSerialization because // we need to check if user has made custom function above if (itemType.IsGenericInstance) { Weaver.Error($"Can not create Serialize or Deserialize for generic element in {td.Name}. Override virtual methods with custom Serialize and Deserialize to use {itemType} in SyncList", td); return(false); } var serializeFunc = new MethodDefinition(methodName, MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Public | MethodAttributes.HideBySig, WeaverTypes.Import(typeof(void))); serializeFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, WeaverTypes.Import <NetworkWriter>())); serializeFunc.Parameters.Add(new ParameterDefinition("item", ParameterAttributes.None, itemType)); ILProcessor worker = serializeFunc.Body.GetILProcessor(); MethodReference writeFunc = Writers.GetWriteFunc(itemType); if (writeFunc != null) { worker.Append(worker.Create(OpCodes.Ldarg_1)); worker.Append(worker.Create(OpCodes.Ldarg_2)); worker.Append(worker.Create(OpCodes.Call, writeFunc)); } else { Weaver.Error($"{td.Name} has sync object generic type {itemType.Name}. Use a type supported by mirror instead", td); return(false); } worker.Append(worker.Create(OpCodes.Ret)); td.Methods.Add(serializeFunc); return(true); }
public static MethodDefinition GenerateSyncVarSetter(TypeDefinition td, FieldDefinition fd, string originalName, long dirtyBit, FieldDefinition netFieldId) { //Create the set method MethodDefinition set = new MethodDefinition("set_Network" + originalName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, WeaverTypes.Import(typeof(void))); ILProcessor worker = set.Body.GetILProcessor(); // if (!SyncVarEqual(value, ref playerData)) Instruction endOfMethod = worker.Create(OpCodes.Nop); // this worker.Append(worker.Create(OpCodes.Ldarg_0)); // new value to set worker.Append(worker.Create(OpCodes.Ldarg_1)); // reference to field to set // make generic version of SetSyncVar with field type if (fd.FieldType.Is <FlaxEngine.Actor>()) { // reference to netId Field to set worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Ldfld, netFieldId)); worker.Append(worker.Create(OpCodes.Call, WeaverTypes.syncVarGameObjectEqualReference)); } else if (fd.FieldType.Is <NetworkIdentity>()) { // reference to netId Field to set worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Ldfld, netFieldId)); worker.Append(worker.Create(OpCodes.Call, WeaverTypes.syncVarNetworkIdentityEqualReference)); } else if (fd.FieldType.IsDerivedFrom <NetworkBehaviour>()) { // reference to netId Field to set worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Ldfld, netFieldId)); MethodReference getFunc = WeaverTypes.syncVarNetworkBehaviourEqualReference.MakeGeneric(fd.FieldType); worker.Append(worker.Create(OpCodes.Call, getFunc)); } else { worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Ldflda, fd)); GenericInstanceMethod syncVarEqualGm = new GenericInstanceMethod(WeaverTypes.syncVarEqualReference); syncVarEqualGm.GenericArguments.Add(fd.FieldType); worker.Append(worker.Create(OpCodes.Call, syncVarEqualGm)); } worker.Append(worker.Create(OpCodes.Brtrue, endOfMethod)); // T oldValue = value; // TODO for GO/NI we need to backup the netId don't we? VariableDefinition oldValue = new VariableDefinition(fd.FieldType); set.Body.Variables.Add(oldValue); worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Ldfld, fd)); worker.Append(worker.Create(OpCodes.Stloc, oldValue)); // this worker.Append(worker.Create(OpCodes.Ldarg_0)); // new value to set worker.Append(worker.Create(OpCodes.Ldarg_1)); // reference to field to set worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Ldflda, fd)); // dirty bit // 8 byte integer aka long worker.Append(worker.Create(OpCodes.Ldc_I8, dirtyBit)); if (fd.FieldType.Is <FlaxEngine.Actor>()) { // reference to netId Field to set worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Ldflda, netFieldId)); worker.Append(worker.Create(OpCodes.Call, WeaverTypes.setSyncVarGameObjectReference)); } else if (fd.FieldType.Is <NetworkIdentity>()) { // reference to netId Field to set worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Ldflda, netFieldId)); worker.Append(worker.Create(OpCodes.Call, WeaverTypes.setSyncVarNetworkIdentityReference)); } else if (fd.FieldType.IsDerivedFrom <NetworkBehaviour>()) { // reference to netId Field to set worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Ldflda, netFieldId)); MethodReference getFunc = WeaverTypes.setSyncVarNetworkBehaviourReference.MakeGeneric(fd.FieldType); worker.Append(worker.Create(OpCodes.Call, getFunc)); } else { // make generic version of SetSyncVar with field type GenericInstanceMethod gm = new GenericInstanceMethod(WeaverTypes.setSyncVarReference); gm.GenericArguments.Add(fd.FieldType); // invoke SetSyncVar worker.Append(worker.Create(OpCodes.Call, gm)); } MethodDefinition hookMethod = GetHookMethod(td, fd); if (hookMethod != null) { //if (NetworkServer.localClientActive && !getSyncVarHookGuard(dirtyBit)) Instruction label = worker.Create(OpCodes.Nop); worker.Append(worker.Create(OpCodes.Call, WeaverTypes.NetworkServerGetLocalClientActive)); worker.Append(worker.Create(OpCodes.Brfalse, label)); worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Ldc_I8, dirtyBit)); worker.Append(worker.Create(OpCodes.Call, WeaverTypes.getSyncVarHookGuard)); worker.Append(worker.Create(OpCodes.Brtrue, label)); // setSyncVarHookGuard(dirtyBit, true); worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Ldc_I8, dirtyBit)); worker.Append(worker.Create(OpCodes.Ldc_I4_1)); worker.Append(worker.Create(OpCodes.Call, WeaverTypes.setSyncVarHookGuard)); // call hook (oldValue, newValue) // Generates: OnValueChanged(oldValue, value); WriteCallHookMethodUsingArgument(worker, hookMethod, oldValue); // setSyncVarHookGuard(dirtyBit, false); worker.Append(worker.Create(OpCodes.Ldarg_0)); worker.Append(worker.Create(OpCodes.Ldc_I8, dirtyBit)); worker.Append(worker.Create(OpCodes.Ldc_I4_0)); worker.Append(worker.Create(OpCodes.Call, WeaverTypes.setSyncVarHookGuard)); worker.Append(label); } worker.Append(endOfMethod); worker.Append(worker.Create(OpCodes.Ret)); set.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.In, fd.FieldType)); set.SemanticsAttributes = MethodSemanticsAttributes.Setter; return(set); }
public static void AddInvokeParameters(ICollection <ParameterDefinition> collection) { collection.Add(new ParameterDefinition("obj", ParameterAttributes.None, WeaverTypes.Import <NetworkBehaviour>())); collection.Add(new ParameterDefinition("reader", ParameterAttributes.None, WeaverTypes.Import <NetworkReader>())); // senderConnection is only used for commands but NetworkBehaviour.CmdDelegate is used for all remote calls collection.Add(new ParameterDefinition("senderConnection", ParameterAttributes.None, WeaverTypes.Import <NetworkConnectionToClient>())); }
void GenerateDeSerialization() { Weaver.DLog(netBehaviourSubclass, " GenerateDeSerialization"); const string DeserializeMethodName = "DeserializeSyncVars"; if (netBehaviourSubclass.GetMethod(DeserializeMethodName) != null) { return; } if (syncVars.Count == 0) { // no synvars, no need for custom OnDeserialize return; } MethodDefinition serialize = new MethodDefinition(DeserializeMethodName, MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, WeaverTypes.Import(typeof(void))); serialize.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, WeaverTypes.Import <NetworkReader>())); serialize.Parameters.Add(new ParameterDefinition("initialState", ParameterAttributes.None, WeaverTypes.Import <bool>())); ILProcessor serWorker = serialize.Body.GetILProcessor(); // setup local for dirty bits serialize.Body.InitLocals = true; VariableDefinition dirtyBitsLocal = new VariableDefinition(WeaverTypes.Import <long>()); serialize.Body.Variables.Add(dirtyBitsLocal); MethodReference baseDeserialize = Resolvers.TryResolveMethodInParents(netBehaviourSubclass.BaseType, Weaver.CurrentAssembly, DeserializeMethodName); if (baseDeserialize != null) { // base serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // reader serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // initialState serWorker.Append(serWorker.Create(OpCodes.Ldarg_2)); serWorker.Append(serWorker.Create(OpCodes.Call, baseDeserialize)); } // Generates: if (initialState); Instruction initialStateLabel = serWorker.Create(OpCodes.Nop); serWorker.Append(serWorker.Create(OpCodes.Ldarg_2)); serWorker.Append(serWorker.Create(OpCodes.Brfalse, initialStateLabel)); foreach (FieldDefinition syncVar in syncVars) { DeserializeField(syncVar, serWorker, serialize); } serWorker.Append(serWorker.Create(OpCodes.Ret)); // Generates: end if (initialState); serWorker.Append(initialStateLabel); // get dirty bits serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); serWorker.Append(serWorker.Create(OpCodes.Call, Readers.GetReadFunc(WeaverTypes.Import <ulong>()))); serWorker.Append(serWorker.Create(OpCodes.Stloc_0)); // conditionally read each syncvar // start at number of syncvars in parent int dirtyBit = Weaver.WeaveLists.GetSyncVarStart(netBehaviourSubclass.BaseType.FullName); foreach (FieldDefinition syncVar in syncVars) { Instruction varLabel = serWorker.Create(OpCodes.Nop); // check if dirty bit is set serWorker.Append(serWorker.Create(OpCodes.Ldloc_0)); serWorker.Append(serWorker.Create(OpCodes.Ldc_I8, 1L << dirtyBit)); serWorker.Append(serWorker.Create(OpCodes.And)); serWorker.Append(serWorker.Create(OpCodes.Brfalse, varLabel)); DeserializeField(syncVar, serWorker, serialize); serWorker.Append(varLabel); dirtyBit += 1; } if (Weaver.GenerateLogErrors) { serWorker.Append(serWorker.Create(OpCodes.Ldstr, "Injected Deserialize " + netBehaviourSubclass.Name)); serWorker.Append(serWorker.Create(OpCodes.Call, WeaverTypes.logErrorReference)); } serWorker.Append(serWorker.Create(OpCodes.Ret)); netBehaviourSubclass.Methods.Add(serialize); }