public static bool ProcessNetworkReaderParameters(MethodDefinition md, ILProcessor worker, bool skipFirst) { int count = 0; // read cmd args from NetworkReader foreach (ParameterDefinition arg in md.Parameters) { if (count++ == 0 && skipFirst) { continue; } //? MethodReference readFunc = Readers.GetReadFunc(arg.ParameterType); if (readFunc != null) { worker.Append(worker.Create(OpCodes.Ldarg_1)); worker.Append(worker.Create(OpCodes.Call, readFunc)); // conversion.. is this needed? if (arg.ParameterType.FullName == Weaver.singleType.FullName) { worker.Append(worker.Create(OpCodes.Conv_R4)); } else if (arg.ParameterType.FullName == Weaver.doubleType.FullName) { worker.Append(worker.Create(OpCodes.Conv_R8)); } } else { Weaver.Error($"{md.Name} has invalid parameter {arg}. Unsupported type {arg.ParameterType}, use a supported Mirror type instead", md); return(false); } } return(true); }
static bool LoadDeclaredReaders(AssemblyDefinition currentAssembly, TypeDefinition klass) { // register all the reader in this class. Skip the ones with wrong signature bool modified = false; foreach (MethodDefinition method in klass.Methods) { if (method.Parameters.Count != 1) { continue; } if (!method.Parameters[0].ParameterType.Is <NetworkReader>()) { continue; } if (method.ReturnType.Is(typeof(void))) { continue; } if (!method.HasCustomAttribute <System.Runtime.CompilerServices.ExtensionAttribute>()) { continue; } if (method.HasGenericParameters) { continue; } Readers.Register(method.ReturnType, currentAssembly.MainModule.ImportReference(method)); modified = true; } return(modified); }
static MethodReference GenerateDeserialization(string methodName, TypeDefinition td, TypeReference itemType) { Weaver.DLog(td, " GenerateDeserialization"); MethodDefinition existing = td.GetMethod(methodName); if (existing != null) { return(existing); } MethodDefinition deserializeFunction = new MethodDefinition(methodName, MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Public | MethodAttributes.HideBySig, itemType); deserializeFunction.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkReaderType))); ILProcessor serWorker = deserializeFunction.Body.GetILProcessor(); MethodReference readerFunc = Readers.GetReadFunc(itemType); if (readerFunc != null) { serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); serWorker.Append(serWorker.Create(OpCodes.Call, readerFunc)); serWorker.Append(serWorker.Create(OpCodes.Ret)); } else { Weaver.Error($"{td} cannot have item of type {itemType}. Use a type supported by mirror instead"); return(null); } td.Methods.Add(deserializeFunction); return(deserializeFunction); }
private static void GenerateReadersWriters(ModuleDefinition module, TypeReference parameterType) { if (!parameterType.IsGenericParameter && parameterType.CanBeResolved()) { TypeDefinition typeDefinition = parameterType.Resolve(); if (typeDefinition.IsClass && !typeDefinition.IsValueType) { MethodDefinition constructor = typeDefinition.GetMethod(".ctor"); bool hasAccess = constructor.IsPublic || constructor.IsAssembly && typeDefinition.Module == module; if (!hasAccess) { return; } } parameterType = module.ImportReference(parameterType); Writers.GetWriteFunc(parameterType); Readers.GetReadFunc(parameterType); } }
// Finds SyncObjects fields in a type // Type should be a NetworkBehaviour public static List <FieldDefinition> FindSyncObjectsFields(Writers writers, Readers readers, Logger Log, TypeDefinition td, ref bool WeavingFailed) { List <FieldDefinition> syncObjects = new List <FieldDefinition>(); foreach (FieldDefinition fd in td.Fields) { if (fd.FieldType.Resolve().ImplementsInterface <SyncObject>()) { if (fd.IsStatic) { Log.Error($"{fd.Name} cannot be static", fd); WeavingFailed = true; continue; } GenerateReadersAndWriters(writers, readers, fd.FieldType, ref WeavingFailed); syncObjects.Add(fd); } } return(syncObjects); }
public static bool ProcessNetworkReaderParameters(TypeDefinition td, MethodDefinition md, ILProcessor worker, bool skipFirst) { int count = 0; // read cmd args from NetworkReader foreach (ParameterDefinition arg in md.Parameters) { if (count++ == 0 && skipFirst) { continue; } MethodReference readFunc = Readers.GetReadFunc(arg.ParameterType); //? if (readFunc != null) { worker.Append(worker.Create(OpCodes.Ldarg_1)); worker.Append(worker.Create(OpCodes.Call, readFunc)); // conversion.. is this needed? if (arg.ParameterType.FullName == Weaver.singleType.FullName) { worker.Append(worker.Create(OpCodes.Conv_R4)); } else if (arg.ParameterType.FullName == Weaver.doubleType.FullName) { worker.Append(worker.Create(OpCodes.Conv_R8)); } } else { Weaver.Error("ProcessNetworkReaderParameters for " + td.Name + ":" + md.Name + " type " + arg.ParameterType + " not supported"); return(false); } } return(true); }
public AssemblyDefinition Weave(ICompiledAssembly compiledAssembly) { try { CurrentAssembly = AssemblyDefinitionFor(compiledAssembly); ModuleDefinition module = CurrentAssembly.MainModule; readers = new Readers(module, logger); writers = new Writers(module, logger); var rwstopwatch = System.Diagnostics.Stopwatch.StartNew(); propertySiteProcessor = new PropertySiteProcessor(); var rwProcessor = new ReaderWriterProcessor(module, readers, writers); bool modified = rwProcessor.Process(); rwstopwatch.Stop(); Console.WriteLine($"Find all reader and writers took {rwstopwatch.ElapsedMilliseconds} milliseconds"); Console.WriteLine($"Script Module: {module.Name}"); modified |= WeaveModule(module); if (!modified) { return(CurrentAssembly); } rwProcessor.InitializeReaderAndWriters(); return(CurrentAssembly); } catch (Exception e) { logger.Error("Exception :" + e); return(null); } }
public ClientRpcProcessor(ModuleDefinition module, Readers readers, Writers writers, IWeaverLogger logger) : base(module, readers, writers, logger) { }
static void GenerateDeSerialization(TypeDefinition td) { Weaver.DLog(td, " GenerateDeserialization"); MethodDefinition existingMethod = td.Methods.FirstOrDefault(md => md.Name == "Deserialize"); if (existingMethod != null && !existingMethod.Body.IsEmptyDefault()) { return; } if (td.Fields.Count == 0) { return; } MethodDefinition serializeFunc = existingMethod ?? new MethodDefinition("Deserialize", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, Weaver.voidType); if (existingMethod == null) //only add to new method { serializeFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkReaderType))); } ILProcessor serWorker = serializeFunc.Body.GetILProcessor(); if (existingMethod != null) { serWorker.Body.Instructions.Clear(); //remove default nop&ret from existing empty interface method } if (!td.IsValueType) //if not struct(IMessageBase), likely same as using else {} here in all cases { // call base MethodReference baseDeserialize = Resolvers.ResolveMethodInParents(td.BaseType, Weaver.CurrentAssembly, "Deserialize"); if (baseDeserialize != null) { serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // base serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // writer serWorker.Append(serWorker.Create(OpCodes.Call, baseDeserialize)); } } foreach (FieldDefinition field in td.Fields) { if (field.IsStatic || field.IsPrivate || field.IsSpecialName) { continue; } MethodReference readerFunc = Readers.GetReadFunc(field.FieldType); if (readerFunc != null) { serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); serWorker.Append(serWorker.Create(OpCodes.Call, readerFunc)); serWorker.Append(serWorker.Create(OpCodes.Stfld, field)); } else { Weaver.Error($"{field} has unsupported type"); return; } } serWorker.Append(serWorker.Create(OpCodes.Ret)); if (existingMethod == null) //only add if not just replaced body { td.Methods.Add(serializeFunc); } }
void GenerateDeSerialization() { Weaver.DLog(netBehaviourSubclass, " GenerateDeSerialization"); foreach (MethodDefinition m in netBehaviourSubclass.Methods) { if (m.Name == "OnDeserialize") { return; } } if (syncVars.Count == 0) { // no synvars, no need for custom OnDeserialize return; } MethodDefinition serialize = new MethodDefinition("OnDeserialize", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, Weaver.voidType); serialize.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkReaderType))); serialize.Parameters.Add(new ParameterDefinition("initialState", ParameterAttributes.None, Weaver.boolType)); ILProcessor serWorker = serialize.Body.GetILProcessor(); // setup local for dirty bits serialize.Body.InitLocals = true; VariableDefinition dirtyBitsLocal = new VariableDefinition(Weaver.int64Type); serialize.Body.Variables.Add(dirtyBitsLocal); MethodReference baseDeserialize = Resolvers.ResolveMethodInParents(netBehaviourSubclass.BaseType, Weaver.CurrentAssembly, "OnDeserialize"); if (baseDeserialize != null) { serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // base serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // reader serWorker.Append(serWorker.Create(OpCodes.Ldarg_2)); // initialState 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(Weaver.uint64Type))); serWorker.Append(serWorker.Create(OpCodes.Stloc_0)); // conditionally read each syncvar int dirtyBit = Weaver.GetSyncVarStart(netBehaviourSubclass.BaseType.FullName); // start at number of syncvars in parent 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, Weaver.logErrorReference)); } serWorker.Append(serWorker.Create(OpCodes.Ret)); netBehaviourSubclass.Methods.Add(serialize); }
public static bool Process(AssemblyDefinition CurrentAssembly, IAssemblyResolver resolver, Logger Log, Writers writers, Readers readers, ref bool WeavingFailed) { // find NetworkReader/Writer extensions from Mirror.dll first. // and NetworkMessage custom writer/reader extensions. // NOTE: do not include this result in our 'modified' return value, // otherwise Unity crashes when running tests ProcessMirrorAssemblyClasses(CurrentAssembly, resolver, Log, writers, readers, ref WeavingFailed); // find readers/writers in the assembly we are in right now. return(ProcessAssemblyClasses(CurrentAssembly, CurrentAssembly, writers, readers, ref WeavingFailed)); }
static bool Weave(string assName, IEnumerable <string> dependencies, IAssemblyResolver assemblyResolver, string unityEngineDLLPath, string mirrorNetDLLPath, string outputDir) { ReaderParameters readParams = Helpers.ReaderParameters(assName, dependencies, assemblyResolver, unityEngineDLLPath, mirrorNetDLLPath); using (CurrentAssembly = AssemblyDefinition.ReadAssembly(assName, readParams)) { SetupTargetTypes(); Readers.Init(CurrentAssembly); Writers.Init(CurrentAssembly); ModuleDefinition moduleDefinition = CurrentAssembly.MainModule; Console.WriteLine("Script Module: {0}", moduleDefinition.Name); // Process each NetworkBehaviour bool didWork = false; // We need to do 2 passes, because SyncListStructs might be referenced from other modules, so we must make sure we generate them first. for (int pass = 0; pass < 2; pass++) { System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew(); foreach (TypeDefinition td in moduleDefinition.Types) { if (td.IsClass && td.BaseType.CanBeResolved()) { try { if (pass == 0) { didWork |= CheckSyncList(td); } else { didWork |= CheckNetworkBehaviour(td); didWork |= CheckMessageBase(td); } } catch (Exception ex) { if (CurrentAssembly.MainModule.SymbolReader != null) { CurrentAssembly.MainModule.SymbolReader.Dispose(); } Weaver.Error(ex.Message); throw ex; } } if (WeavingFailed) { if (CurrentAssembly.MainModule.SymbolReader != null) { CurrentAssembly.MainModule.SymbolReader.Dispose(); } return(false); } } watch.Stop(); Console.WriteLine("Pass: "******" took " + watch.ElapsedMilliseconds + " milliseconds"); } if (didWork) { // this must be done for ALL code, not just NetworkBehaviours try { PropertySiteProcessor.ProcessSitesModule(CurrentAssembly.MainModule); } catch (Exception e) { Log.Error("ProcessPropertySites exception: " + e); if (CurrentAssembly.MainModule.SymbolReader != null) { CurrentAssembly.MainModule.SymbolReader.Dispose(); } return(false); } if (WeavingFailed) { //Log.Error("Failed phase II."); if (CurrentAssembly.MainModule.SymbolReader != null) { CurrentAssembly.MainModule.SymbolReader.Dispose(); } return(false); } string dest = Helpers.DestinationFileFor(outputDir, assName); //Console.WriteLine ("Output:" + dest); WriterParameters writeParams = Helpers.GetWriterParameters(readParams); CurrentAssembly.Write(dest, writeParams); } if (CurrentAssembly.MainModule.SymbolReader != null) { CurrentAssembly.MainModule.SymbolReader.Dispose(); } } return(true); }
public static MethodDefinition ProcessRpcInvoke(WeaverTypes weaverTypes, Writers writers, Readers readers, Logger Log, TypeDefinition td, MethodDefinition md, MethodDefinition rpcCallFunc, ref bool WeavingFailed) { string rpcName = Weaver.GenerateMethodName(Weaver.InvokeRpcPrefix, md); MethodDefinition rpc = new MethodDefinition(rpcName, 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, "RPC"); // setup for reader worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Castclass, td); if (!NetworkBehaviourProcessor.ReadArguments(md, readers, Log, worker, RemoteCallType.ClientRpc, 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); }
public NetworkBehaviourProcessor(AssemblyDefinition assembly, WeaverTypes weaverTypes, SyncVarAccessLists syncVarAccessLists, Writers writers, Readers readers, Logger Log, TypeDefinition td) { this.assembly = assembly; this.weaverTypes = weaverTypes; this.syncVarAccessLists = syncVarAccessLists; this.writers = writers; this.readers = readers; this.Log = Log; syncVarAttributeProcessor = new SyncVarAttributeProcessor(assembly, weaverTypes, syncVarAccessLists, Log); netBehaviourSubclass = td; }
static bool ProcessAssemblyClasses(AssemblyDefinition CurrentAssembly, AssemblyDefinition assembly, Writers writers, Readers readers, ref bool WeavingFailed) { bool modified = false; foreach (TypeDefinition klass in assembly.MainModule.Types) { // extension methods only live in static classes // static classes are represented as sealed and abstract if (klass.IsAbstract && klass.IsSealed) { // if assembly has any declared writers then it is "modified" modified |= LoadDeclaredWriters(CurrentAssembly, klass, writers); modified |= LoadDeclaredReaders(CurrentAssembly, klass, readers); } } foreach (TypeDefinition klass in assembly.MainModule.Types) { // if assembly has any network message then it is modified modified |= LoadMessageReadWriter(CurrentAssembly.MainModule, writers, readers, klass, ref WeavingFailed); } return(modified); }
static void ProcessMirrorAssemblyClasses(AssemblyDefinition CurrentAssembly, IAssemblyResolver resolver, Logger Log, Writers writers, Readers readers, ref bool WeavingFailed) { // find Mirror.dll in assembly's references. // those are guaranteed to be resolvable and correct. // after all, it references them :) AssemblyNameReference mirrorAssemblyReference = CurrentAssembly.MainModule.FindReference(Weaver.MirrorAssemblyName); if (mirrorAssemblyReference != null) { // resolve the assembly to load the AssemblyDefinition. // we need to search all types in it. // if we only were to resolve one known type like in WeaverTypes, // then we wouldn't need it. AssemblyDefinition mirrorAssembly = resolver.Resolve(mirrorAssemblyReference); if (mirrorAssembly != null) { ProcessAssemblyClasses(CurrentAssembly, mirrorAssembly, writers, readers, ref WeavingFailed); } else { Log.Error($"Failed to resolve {mirrorAssemblyReference}"); } } else { Log.Error("Failed to find Mirror AssemblyNameReference. Can't register Mirror.dll readers/writers."); } }
// adds Mirror.GeneratedNetworkCode.InitReadWriters() method that // registers all generated writers into Mirror.Writer<T> static class. // -> uses [RuntimeInitializeOnLoad] attribute so it's invoke at runtime // -> uses [InitializeOnLoad] if UnityEditor is referenced so it works // in Editor and in tests too // // use ILSpy to see the result (it's in the DLL's 'Mirror' namespace) public static void InitializeReaderAndWriters(AssemblyDefinition currentAssembly, WeaverTypes weaverTypes, Writers writers, Readers readers, TypeDefinition GeneratedCodeClass) { MethodDefinition initReadWriters = new MethodDefinition("InitReadWriters", MethodAttributes.Public | MethodAttributes.Static, weaverTypes.Import(typeof(void))); // add [RuntimeInitializeOnLoad] in any case AddRuntimeInitializeOnLoadAttribute(currentAssembly, weaverTypes, initReadWriters); // add [InitializeOnLoad] if UnityEditor is referenced if (Helpers.IsEditorAssembly(currentAssembly)) { AddInitializeOnLoadAttribute(currentAssembly, weaverTypes, initReadWriters); } // fill function body with reader/writer initializers ILProcessor worker = initReadWriters.Body.GetILProcessor(); // for debugging: add a log to see if initialized on load //worker.Emit(OpCodes.Ldstr, $"[InitReadWriters] called!"); //worker.Emit(OpCodes.Call, Weaver.weaverTypes.logWarningReference); writers.InitializeWriters(worker); readers.InitializeReaders(worker); worker.Emit(OpCodes.Ret); GeneratedCodeClass.Methods.Add(initReadWriters); }
static bool Weave(string assName, IEnumerable <string> dependencies, IAssemblyResolver assemblyResolver, 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); } } SetupTargetTypes(); Readers.Init(CurrentAssembly); Writers.Init(CurrentAssembly); ModuleDefinition moduleDefinition = CurrentAssembly.MainModule; Console.WriteLine("Script Module: {0}", moduleDefinition.Name); // Process each NetworkBehaviour bool didWork = false; // We need to do 2 passes, because SyncListStructs might be referenced from other modules, so we must make sure we generate them first. for (int pass = 0; pass < 2; pass++) { System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew(); foreach (TypeDefinition td in moduleDefinition.Types) { if (td.IsClass && td.BaseType.CanBeResolved()) { try { if (pass == 0) { didWork |= CheckSyncList(td); } else { didWork |= CheckNetworkBehaviour(td); didWork |= CheckMessageBase(td); } } catch (Exception ex) { Weaver.Error(ex.Message); throw ex; } } if (WeavingFailed) { return(false); } } watch.Stop(); Console.WriteLine("Pass: "******" took " + watch.ElapsedMilliseconds + " milliseconds"); } if (didWork) { // this must be done for ALL code, not just NetworkBehaviours try { PropertySiteProcessor.ProcessSitesModule(CurrentAssembly.MainModule); } catch (Exception e) { Log.Error("ProcessPropertySites exception: " + e); return(false); } if (WeavingFailed) { //Log.Error("Failed phase II."); return(false); } // write to outputDir if specified, otherwise perform in-place write WriterParameters writeParams = new WriterParameters { WriteSymbols = true }; if (outputDir != null) { CurrentAssembly.Write(Helpers.DestinationFileFor(outputDir, assName), writeParams); } else { CurrentAssembly.Write(writeParams); } } } return(true); }
void DeserializeField(FieldDefinition syncVar, ILProcessor serWorker, MethodDefinition deserialize) { // check for Hook function if (!SyncVarProcessor.CheckForHookFunction(netBehaviourSubclass, syncVar, out MethodDefinition foundMethod)) { return; } if (syncVar.FieldType.FullName == Weaver.gameObjectType.FullName || syncVar.FieldType.FullName == Weaver.NetworkIdentityType.FullName) { // 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]; VariableDefinition tmpValue = new VariableDefinition(Weaver.uint32Type); deserialize.Body.Variables.Add(tmpValue); // read id and store in a local variable serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); serWorker.Append(serWorker.Create(OpCodes.Call, Weaver.NetworkReaderReadPackedUInt32)); serWorker.Append(serWorker.Create(OpCodes.Stloc, tmpValue)); if (foundMethod != null) { // call Hook(this.GetSyncVarGameObject/NetworkIdentity(reader.ReadPackedUInt32())) // because we send/receive the netID, not the GameObject/NetworkIdentity serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // this. serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldloc, tmpValue)); serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldflda, syncVar)); if (syncVar.FieldType.FullName == Weaver.gameObjectType.FullName) { serWorker.Append(serWorker.Create(OpCodes.Callvirt, Weaver.getSyncVarGameObjectReference)); } else if (syncVar.FieldType.FullName == Weaver.NetworkIdentityType.FullName) { serWorker.Append(serWorker.Create(OpCodes.Callvirt, Weaver.getSyncVarNetworkIdentityReference)); } serWorker.Append(serWorker.Create(OpCodes.Call, foundMethod)); } // set the netid field serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldloc, tmpValue)); serWorker.Append(serWorker.Create(OpCodes.Stfld, netIdField)); } else { MethodReference readFunc = Readers.GetReadFunc(syncVar.FieldType); if (readFunc == null) { Weaver.Error("GenerateDeSerialization for " + netBehaviourSubclass.Name + " unknown type [" + syncVar.FieldType + "]. Mirror [SyncVar] member variables must be basic types."); return; } VariableDefinition tmpValue = new VariableDefinition(syncVar.FieldType); deserialize.Body.Variables.Add(tmpValue); // read value and put it in a local variable serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); serWorker.Append(serWorker.Create(OpCodes.Call, readFunc)); serWorker.Append(serWorker.Create(OpCodes.Stloc, tmpValue)); if (foundMethod != null) { // call hook serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldloc, tmpValue)); serWorker.Append(serWorker.Create(OpCodes.Call, foundMethod)); } // set the property serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldloc, tmpValue)); serWorker.Append(serWorker.Create(OpCodes.Stfld, syncVar)); } }
// Weave takes an AssemblyDefinition to be compatible with both old and // new weavers: // * old takes a filepath, new takes a in-memory byte[] // * old uses DefaultAssemblyResolver with added dependencies paths, // new uses ...? // // => assembly: the one we are currently weaving (MyGame.dll) // => resolver: useful in case we need to resolve any of the assembly's // assembly.MainModule.AssemblyReferences. // -> we can resolve ANY of them given that the resolver // works properly (need custom one for ILPostProcessor) // -> IMPORTANT: .Resolve() takes an AssemblyNameReference. // those from assembly.MainModule.AssemblyReferences are // guaranteed to be resolve-able. // Parsing from a string for Library/.../Mirror.dll // would not be guaranteed to be resolve-able because // for ILPostProcessor we can't assume where Mirror.dll // is etc. public bool Weave(AssemblyDefinition assembly, IAssemblyResolver resolver, out bool modified) { WeavingFailed = false; modified = false; try { CurrentAssembly = assembly; // fix "No writer found for ..." error // https://github.com/vis2k/Mirror/issues/2579 // -> when restarting Unity, weaver would try to weave a DLL // again // -> resulting in two GeneratedNetworkCode classes (see ILSpy) // -> the second one wouldn't have all the writer types setup if (CurrentAssembly.MainModule.ContainsClass(GeneratedCodeNamespace, GeneratedCodeClassName)) { //Log.Warning($"Weaver: skipping {CurrentAssembly.Name} because already weaved"); return(true); } weaverTypes = new WeaverTypes(CurrentAssembly, Log, ref WeavingFailed); // weaverTypes are needed for CreateGeneratedCodeClass CreateGeneratedCodeClass(); // WeaverList depends on WeaverTypes setup because it uses Import syncVarAccessLists = new SyncVarAccessLists(); // initialize readers & writers with this assembly. // we need to do this in every Process() call. // otherwise we would get // "System.ArgumentException: Member ... is declared in another module and needs to be imported" // errors when still using the previous module's reader/writer funcs. writers = new Writers(CurrentAssembly, weaverTypes, GeneratedCodeClass, Log); readers = new Readers(CurrentAssembly, weaverTypes, GeneratedCodeClass, Log); Stopwatch rwstopwatch = Stopwatch.StartNew(); // Need to track modified from ReaderWriterProcessor too because it could find custom read/write functions or create functions for NetworkMessages modified = ReaderWriterProcessor.Process(CurrentAssembly, resolver, Log, writers, readers, ref WeavingFailed); rwstopwatch.Stop(); Console.WriteLine($"Find all reader and writers took {rwstopwatch.ElapsedMilliseconds} milliseconds"); ModuleDefinition moduleDefinition = CurrentAssembly.MainModule; Console.WriteLine($"Script Module: {moduleDefinition.Name}"); MirrorWeaver.QSBReaderWriterProcessor.Process(moduleDefinition, writers, readers, ref WeavingFailed); modified |= WeaveModule(moduleDefinition); if (WeavingFailed) { return(false); } if (modified) { SyncVarAttributeAccessReplacer.Process(moduleDefinition, syncVarAccessLists); // add class that holds read/write functions moduleDefinition.Types.Add(GeneratedCodeClass); ReaderWriterProcessor.InitializeReaderAndWriters(CurrentAssembly, weaverTypes, writers, readers, GeneratedCodeClass); // DO NOT WRITE here. // CompilationFinishedHook writes to the file. // ILPostProcessor writes to in-memory assembly. // it depends on the caller. //CurrentAssembly.Write(new WriterParameters{ WriteSymbols = true }); } return(true); } catch (Exception e) { Log.Error($"Exception :{e}"); WeavingFailed = true; return(false); } }
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; } var 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; var 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.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; } serWorker.Append(serWorker.Create(OpCodes.Ret)); netBehaviourSubclass.Methods.Add(serialize); }
/// <summary> /// [SyncVar] int/float/struct/etc.? /// </summary> /// <param name="syncVar"></param> /// <param name="serWorker"></param> /// <param name="deserialize"></param> /// <param name="initialState"></param> /// <param name="hookResult"></param> void DeserializeNormalField(FieldDefinition syncVar, ILProcessor serWorker, MethodDefinition deserialize, MethodDefinition hookMethod) { /* * Generates code like: * // for hook * int oldValue = a; * Networka = reader.ReadPackedInt32(); * if (!SyncVarEqual(oldValue, ref a)) * { * OnSetA(oldValue, Networka); * } */ MethodReference readFunc = Readers.GetReadFunc(syncVar.FieldType); if (readFunc == null) { Weaver.Error($"{syncVar.Name} has unsupported type. Use a supported Mirror type instead", syncVar); return; } // T oldValue = value; VariableDefinition oldValue = new VariableDefinition(syncVar.FieldType); deserialize.Body.Variables.Add(oldValue); serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldfld, syncVar)); serWorker.Append(serWorker.Create(OpCodes.Stloc, oldValue)); // read value and store in syncvar 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.syncvar' below serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // reader. for 'reader.Read()' below serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // reader.Read() serWorker.Append(serWorker.Create(OpCodes.Call, readFunc)); // syncvar serWorker.Append(serWorker.Create(OpCodes.Stfld, syncVar)); if (hookMethod != null) { // call hook // 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 // Generates: if (!SyncVarEqual); Instruction syncVarEqualLabel = serWorker.Create(OpCodes.Nop); // 'this.' for 'this.SyncVarEqual' serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // 'oldValue' serWorker.Append(serWorker.Create(OpCodes.Ldloc, oldValue)); // 'ref this.syncVar' serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldflda, syncVar)); // call the function GenericInstanceMethod syncVarEqualGm = new GenericInstanceMethod(WeaverTypes.syncVarEqualReference); syncVarEqualGm.GenericArguments.Add(syncVar.FieldType); serWorker.Append(serWorker.Create(OpCodes.Call, syncVarEqualGm)); serWorker.Append(serWorker.Create(OpCodes.Brtrue, syncVarEqualLabel)); // call the hook // Generates: OnValueChanged(oldValue, this.syncVar); SyncVarProcessor.WriteCallHookMethodUsingField(serWorker, hookMethod, oldValue, syncVar); // Generates: end if (!SyncVarEqual); serWorker.Append(syncVarEqualLabel); } }
/// <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); } }
public ReaderWriterProcessor(ModuleDefinition module, Readers readers, Writers writers) { this.module = module; this.readers = readers; this.writers = writers; }
void DeserializeField(FieldDefinition syncVar, ILProcessor serWorker, MethodDefinition deserialize) { // check for Hook function if (!SyncVarProcessor.CheckForHookFunction(netBehaviourSubclass, syncVar, out MethodDefinition foundMethod)) { return; } // [SyncVar] GameObject/NetworkIdentity? /* * Generates code like: * uint oldNetId = ___qNetId; * GameObject oldSyncVar = syncvar.getter; // returns GetSyncVarGameObject(___qNetId) * ___qNetId = reader.ReadPackedUInt32(); * if (!SyncVarEqual(oldNetId, ref ___goNetId)) * { * OnSetQ(oldSyncVar, syncvar.getter); // getter returns GetSyncVarGameObject(___qNetId) * } */ if (syncVar.FieldType.FullName == Weaver.gameObjectType.FullName || syncVar.FieldType.FullName == Weaver.NetworkIdentityType.FullName) { // 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(Weaver.uint32Type); deserialize.Body.Variables.Add(oldNetId); serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldfld, netIdField)); serWorker.Append(serWorker.Create(OpCodes.Stloc, oldNetId)); // GameObject/NetworkIdentity oldSyncVar = syncvar.getter; VariableDefinition oldSyncVar = new VariableDefinition(syncVar.FieldType); deserialize.Body.Variables.Add(oldSyncVar); serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldfld, syncVar)); serWorker.Append(serWorker.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. serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // put 'this.' onto stack for 'this.netId' below serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // reader. for 'reader.Read()' below serWorker.Append(serWorker.Create(OpCodes.Call, Readers.GetReadFunc(Weaver.uint32Type))); // Read() serWorker.Append(serWorker.Create(OpCodes.Stfld, netIdField)); // netId if (foundMethod != 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 = serWorker.Create(OpCodes.Nop); // 'this.' for 'this.SyncVarEqual' serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // 'oldNetId' serWorker.Append(serWorker.Create(OpCodes.Ldloc, oldNetId)); // 'ref this.__netId' serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldflda, netIdField)); // call the function GenericInstanceMethod syncVarEqualGm = new GenericInstanceMethod(Weaver.syncVarEqualReference); syncVarEqualGm.GenericArguments.Add(netIdField.FieldType); serWorker.Append(serWorker.Create(OpCodes.Call, syncVarEqualGm)); serWorker.Append(serWorker.Create(OpCodes.Brtrue, syncVarEqualLabel)); // call the hook serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // this. serWorker.Append(serWorker.Create(OpCodes.Ldloc, oldSyncVar)); // oldSyncVar GO/NI serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // this. serWorker.Append(serWorker.Create(OpCodes.Ldfld, syncVar)); // syncvar.get (finds current GO/NI from netId) serWorker.Append(serWorker.Create(OpCodes.Call, foundMethod)); // Generates: end if (!SyncVarEqual); serWorker.Append(syncVarEqualLabel); } } // [SyncVar] int/float/struct/etc.? /* * Generates code like: * int oldValue = a; // for hook * Networka = reader.ReadPackedInt32(); * if (!SyncVarEqual(oldValue, ref a)) * { * OnSetA(oldValue, Networka); * } */ else { MethodReference readFunc = Readers.GetReadFunc(syncVar.FieldType); if (readFunc == null) { Weaver.Error($"{syncVar} has unsupported type. Use a supported Mirror type instead"); return; } // T oldValue = value; VariableDefinition oldValue = new VariableDefinition(syncVar.FieldType); deserialize.Body.Variables.Add(oldValue); serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldfld, syncVar)); serWorker.Append(serWorker.Create(OpCodes.Stloc, oldValue)); // read value and store in syncvar 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. serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // put 'this.' onto stack for 'this.syncvar' below serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // reader. for 'reader.Read()' below serWorker.Append(serWorker.Create(OpCodes.Call, readFunc)); // reader.Read() serWorker.Append(serWorker.Create(OpCodes.Stfld, syncVar)); // syncvar if (foundMethod != null) { // call hook // 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 // Generates: if (!SyncVarEqual); Instruction syncVarEqualLabel = serWorker.Create(OpCodes.Nop); // 'this.' for 'this.SyncVarEqual' serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // 'oldValue' serWorker.Append(serWorker.Create(OpCodes.Ldloc, oldValue)); // 'ref this.syncVar' serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldflda, syncVar)); // call the function GenericInstanceMethod syncVarEqualGm = new GenericInstanceMethod(Weaver.syncVarEqualReference); syncVarEqualGm.GenericArguments.Add(syncVar.FieldType); serWorker.Append(serWorker.Create(OpCodes.Call, syncVarEqualGm)); serWorker.Append(serWorker.Create(OpCodes.Brtrue, syncVarEqualLabel)); // call the hook serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // this. serWorker.Append(serWorker.Create(OpCodes.Ldloc, oldValue)); // oldvalue serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // this. serWorker.Append(serWorker.Create(OpCodes.Ldfld, syncVar)); // syncvar.get serWorker.Append(serWorker.Create(OpCodes.Call, foundMethod)); // Generates: end if (!SyncVarEqual); serWorker.Append(syncVarEqualLabel); } } }
public SyncObjectProcessor(Readers readers, Writers writers, IWeaverLogger logger) { this.readers = readers; this.writers = writers; this.logger = logger; }
public static MethodDefinition ProcessTargetRpcInvoke(WeaverTypes weaverTypes, Readers readers, Logger Log, TypeDefinition td, MethodDefinition md, MethodDefinition rpcCallFunc, ref bool WeavingFailed) { string trgName = Weaver.GenerateMethodName(Weaver.InvokeRpcPrefix, md); MethodDefinition rpc = new MethodDefinition(trgName, 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); }
void DeserializeField(FieldDefinition syncVar, ILProcessor serWorker, MethodDefinition deserialize) { // check for Hook function if (!SyncVarProcessor.CheckForHookFunction(netBehaviourSubclass, syncVar, out MethodDefinition foundMethod)) { return; } // [SyncVar] GameObject/NetworkIdentity? /* * Generates code like: * uint num = reader.ReadPackedUInt32(); * if (!SyncVarEqual(num, ref q)) * { * OnSetQ(GetSyncVarGameObject(num, ref q)); * } * ___qNetId = num; */ if (syncVar.FieldType.FullName == Weaver.gameObjectType.FullName || syncVar.FieldType.FullName == Weaver.NetworkIdentityType.FullName) { // 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]; VariableDefinition tmpValue = new VariableDefinition(Weaver.uint32Type); deserialize.Body.Variables.Add(tmpValue); // read id and store in a local variable serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); serWorker.Append(serWorker.Create(OpCodes.Call, Readers.GetReadFunc(Weaver.uint32Type))); serWorker.Append(serWorker.Create(OpCodes.Stloc, tmpValue)); if (foundMethod != 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 = serWorker.Create(OpCodes.Nop); // 'this.' for 'this.SyncVarEqual' serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // 'tmpValue' serWorker.Append(serWorker.Create(OpCodes.Ldloc, tmpValue)); // 'ref this.__netId' serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldflda, netIdField)); // call the function GenericInstanceMethod syncVarEqualGm = new GenericInstanceMethod(Weaver.syncVarEqualReference); syncVarEqualGm.GenericArguments.Add(netIdField.FieldType); serWorker.Append(serWorker.Create(OpCodes.Call, syncVarEqualGm)); serWorker.Append(serWorker.Create(OpCodes.Brtrue, syncVarEqualLabel)); // call the hook serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // this. serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldloc, tmpValue)); serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldflda, syncVar)); if (syncVar.FieldType.FullName == Weaver.gameObjectType.FullName) { serWorker.Append(serWorker.Create(OpCodes.Callvirt, Weaver.getSyncVarGameObjectReference)); } else if (syncVar.FieldType.FullName == Weaver.NetworkIdentityType.FullName) { serWorker.Append(serWorker.Create(OpCodes.Callvirt, Weaver.getSyncVarNetworkIdentityReference)); } serWorker.Append(serWorker.Create(OpCodes.Call, foundMethod)); // Generates: end if (!SyncVarEqual); serWorker.Append(syncVarEqualLabel); } // set the netid field serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldloc, tmpValue)); serWorker.Append(serWorker.Create(OpCodes.Stfld, netIdField)); } // [SyncVar] int/float/struct/etc.? /* * Generates code like: * int num = reader.ReadPackedInt32(); * if (!SyncVarEqual(num, ref a)) * { * OnSetA(num); * } * Networka = num; */ else { MethodReference readFunc = Readers.GetReadFunc(syncVar.FieldType); if (readFunc == null) { Weaver.Error($"{syncVar} has unsupported type. Use a supported Mirror type instead"); return; } VariableDefinition tmpValue = new VariableDefinition(syncVar.FieldType); deserialize.Body.Variables.Add(tmpValue); // read value and put it in a local variable serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); serWorker.Append(serWorker.Create(OpCodes.Call, readFunc)); serWorker.Append(serWorker.Create(OpCodes.Stloc, tmpValue)); if (foundMethod != null) { // call hook // 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 // Generates: if (!SyncVarEqual); Instruction syncVarEqualLabel = serWorker.Create(OpCodes.Nop); // 'this.' for 'this.SyncVarEqual' serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // 'tmpValue' serWorker.Append(serWorker.Create(OpCodes.Ldloc, tmpValue)); // 'ref this.syncVar' serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldflda, syncVar)); // call the function GenericInstanceMethod syncVarEqualGm = new GenericInstanceMethod(Weaver.syncVarEqualReference); syncVarEqualGm.GenericArguments.Add(syncVar.FieldType); serWorker.Append(serWorker.Create(OpCodes.Call, syncVarEqualGm)); serWorker.Append(serWorker.Create(OpCodes.Brtrue, syncVarEqualLabel)); // call the hook serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldloc, tmpValue)); serWorker.Append(serWorker.Create(OpCodes.Call, foundMethod)); // Generates: end if (!SyncVarEqual); serWorker.Append(syncVarEqualLabel); } // set the property serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); serWorker.Append(serWorker.Create(OpCodes.Ldloc, tmpValue)); serWorker.Append(serWorker.Create(OpCodes.Stfld, syncVar)); } }