mirror of
				https://github.com/DerTyp7/defrain-shooter-unity.git
				synced 2025-10-30 21:17:09 +01:00 
			
		
		
		
	CHANGED TO MIRROR
This commit is contained in:
		
							
								
								
									
										125
									
								
								Assets/Mirror/Editor/Weaver/Processors/CommandProcessor.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								Assets/Mirror/Editor/Weaver/Processors/CommandProcessor.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,125 @@ | ||||
| using Mono.CecilX; | ||||
| using Mono.CecilX.Cil; | ||||
|  | ||||
| namespace Mirror.Weaver | ||||
| { | ||||
|     // Processes [Command] methods in NetworkBehaviour | ||||
|     public static class CommandProcessor | ||||
|     { | ||||
|         /* | ||||
|             // 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; | ||||
|         } | ||||
|  | ||||
|         /* | ||||
|             // generates code like: | ||||
|             protected static void InvokeCmdCmdThrust(NetworkBehaviour obj, NetworkReader reader, NetworkConnection senderConnection) | ||||
|             { | ||||
|                 if (!NetworkServer.active) | ||||
|                 { | ||||
|                     return; | ||||
|                 } | ||||
|                 ((ShipControl)obj).CmdThrust(reader.ReadSingle(), (int)reader.ReadPackedUInt32()); | ||||
|             } | ||||
|         */ | ||||
|         public static MethodDefinition ProcessCommandInvoke(WeaverTypes weaverTypes, Readers readers, Logger Log, TypeDefinition td, MethodDefinition method, MethodDefinition cmdCallFunc, ref bool WeavingFailed) | ||||
|         { | ||||
|             MethodDefinition cmd = new MethodDefinition(Weaver.InvokeRpcPrefix + method.Name, | ||||
|                 MethodAttributes.Family | MethodAttributes.Static | MethodAttributes.HideBySig, | ||||
|                 weaverTypes.Import(typeof(void))); | ||||
|  | ||||
|             ILProcessor worker = cmd.Body.GetILProcessor(); | ||||
|             Instruction label = worker.Create(OpCodes.Nop); | ||||
|  | ||||
|             NetworkBehaviourProcessor.WriteServerActiveCheck(worker, weaverTypes, method.Name, label, "Command"); | ||||
|  | ||||
|             // setup for reader | ||||
|             worker.Emit(OpCodes.Ldarg_0); | ||||
|             worker.Emit(OpCodes.Castclass, td); | ||||
|  | ||||
|             if (!NetworkBehaviourProcessor.ReadArguments(method, readers, Log, worker, RemoteCallType.Command, ref WeavingFailed)) | ||||
|                 return null; | ||||
|  | ||||
|             AddSenderConnection(method, worker); | ||||
|  | ||||
|             // invoke actual command function | ||||
|             worker.Emit(OpCodes.Callvirt, cmdCallFunc); | ||||
|             worker.Emit(OpCodes.Ret); | ||||
|  | ||||
|             NetworkBehaviourProcessor.AddInvokeParameters(weaverTypes, cmd.Parameters); | ||||
|  | ||||
|             td.Methods.Add(cmd); | ||||
|             return cmd; | ||||
|         } | ||||
|  | ||||
|         static void AddSenderConnection(MethodDefinition method, ILProcessor worker) | ||||
|         { | ||||
|             foreach (ParameterDefinition param in method.Parameters) | ||||
|             { | ||||
|                 if (NetworkBehaviourProcessor.IsSenderConnection(param, RemoteCallType.Command)) | ||||
|                 { | ||||
|                     // NetworkConnection is 3nd arg (arg0 is "obj" not "this" because method is static) | ||||
|                     // example: static void InvokeCmdCmdSendCommand(NetworkBehaviour obj, NetworkReader reader, NetworkConnection connection) | ||||
|                     worker.Emit(OpCodes.Ldarg_2); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,11 @@ | ||||
| fileFormatVersion: 2 | ||||
| guid: 73f6c9cdbb9e54f65b3a0a35cc8e55c2 | ||||
| MonoImporter: | ||||
|   externalObjects: {} | ||||
|   serializedVersion: 2 | ||||
|   defaultReferences: [] | ||||
|   executionOrder: 0 | ||||
|   icon: {instanceID: 0} | ||||
|   userData:  | ||||
|   assetBundleName:  | ||||
|   assetBundleVariant:  | ||||
							
								
								
									
										130
									
								
								Assets/Mirror/Editor/Weaver/Processors/MethodProcessor.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								Assets/Mirror/Editor/Weaver/Processors/MethodProcessor.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,130 @@ | ||||
| using Mono.CecilX; | ||||
| using Mono.CecilX.Cil; | ||||
|  | ||||
| namespace Mirror.Weaver | ||||
| { | ||||
|     public static class MethodProcessor | ||||
|     { | ||||
|         const string RpcPrefix = "UserCode_"; | ||||
|  | ||||
|         // creates a method substitute | ||||
|         // For example, if we have this: | ||||
|         //  public void CmdThrust(float thrusting, int spin) | ||||
|         //  { | ||||
|         //      xxxxx | ||||
|         //  } | ||||
|         // | ||||
|         //  it will substitute the method and move the code to a new method with a provided name | ||||
|         //  for example: | ||||
|         // | ||||
|         //  public void CmdTrust(float thrusting, int spin) | ||||
|         //  { | ||||
|         //  } | ||||
|         // | ||||
|         //  public void <newName>(float thrusting, int spin) | ||||
|         //  { | ||||
|         //      xxxxx | ||||
|         //  } | ||||
|         // | ||||
|         //  Note that all the calls to the method remain untouched | ||||
|         // | ||||
|         //  the original method definition loses all code | ||||
|         //  this returns the newly created method with all the user provided code | ||||
|         public static MethodDefinition SubstituteMethod(Logger Log, TypeDefinition td, MethodDefinition md, ref bool WeavingFailed) | ||||
|         { | ||||
|             string newName = RpcPrefix + md.Name; | ||||
|             MethodDefinition cmd = new MethodDefinition(newName, md.Attributes, md.ReturnType); | ||||
|  | ||||
|             // force the substitute method to be protected. | ||||
|             // -> public would show in the Inspector for UnityEvents as | ||||
|             //    User_CmdUsePotion() etc. but the user shouldn't use those. | ||||
|             // -> private would not allow inheriting classes to call it, see | ||||
|             //    OverrideVirtualWithBaseCallsBothVirtualAndBase test. | ||||
|             // -> IL has no concept of 'protected', it's called IsFamily there. | ||||
|             cmd.IsPublic = false; | ||||
|             cmd.IsFamily = true; | ||||
|  | ||||
|             // add parameters | ||||
|             foreach (ParameterDefinition pd in md.Parameters) | ||||
|             { | ||||
|                 cmd.Parameters.Add(new ParameterDefinition(pd.Name, ParameterAttributes.None, pd.ParameterType)); | ||||
|             } | ||||
|  | ||||
|             // swap bodies | ||||
|             (cmd.Body, md.Body) = (md.Body, cmd.Body); | ||||
|  | ||||
|             // Move over all the debugging information | ||||
|             foreach (SequencePoint sequencePoint in md.DebugInformation.SequencePoints) | ||||
|                 cmd.DebugInformation.SequencePoints.Add(sequencePoint); | ||||
|             md.DebugInformation.SequencePoints.Clear(); | ||||
|  | ||||
|             foreach (CustomDebugInformation customInfo in md.CustomDebugInformations) | ||||
|                 cmd.CustomDebugInformations.Add(customInfo); | ||||
|             md.CustomDebugInformations.Clear(); | ||||
|  | ||||
|             (md.DebugInformation.Scope, cmd.DebugInformation.Scope) = (cmd.DebugInformation.Scope, md.DebugInformation.Scope); | ||||
|  | ||||
|             td.Methods.Add(cmd); | ||||
|  | ||||
|             FixRemoteCallToBaseMethod(Log, td, cmd, ref WeavingFailed); | ||||
|             return cmd; | ||||
|         } | ||||
|  | ||||
|         // Finds and fixes call to base methods within remote calls | ||||
|         //For example, changes `base.CmdDoSomething` to `base.CallCmdDoSomething` within `this.CallCmdDoSomething` | ||||
|         public static void FixRemoteCallToBaseMethod(Logger Log, TypeDefinition type, MethodDefinition method, ref bool WeavingFailed) | ||||
|         { | ||||
|             string callName = method.Name; | ||||
|  | ||||
|             // Cmd/rpc start with Weaver.RpcPrefix | ||||
|             // e.g. CallCmdDoSomething | ||||
|             if (!callName.StartsWith(RpcPrefix)) | ||||
|                 return; | ||||
|  | ||||
|             // e.g. CmdDoSomething | ||||
|             string baseRemoteCallName = method.Name.Substring(RpcPrefix.Length); | ||||
|  | ||||
|             foreach (Instruction instruction in method.Body.Instructions) | ||||
|             { | ||||
|                 // if call to base.CmdDoSomething within this.CallCmdDoSomething | ||||
|                 if (IsCallToMethod(instruction, out MethodDefinition calledMethod) && | ||||
|                     calledMethod.Name == baseRemoteCallName) | ||||
|                 { | ||||
|                     TypeDefinition baseType = type.BaseType.Resolve(); | ||||
|                     MethodDefinition baseMethod = baseType.GetMethodInBaseType(callName); | ||||
|  | ||||
|                     if (baseMethod == null) | ||||
|                     { | ||||
|                         Log.Error($"Could not find base method for {callName}", method); | ||||
|                         WeavingFailed = true; | ||||
|                         return; | ||||
|                     } | ||||
|  | ||||
|                     if (!baseMethod.IsVirtual) | ||||
|                     { | ||||
|                         Log.Error($"Could not find base method that was virtual {callName}", method); | ||||
|                         WeavingFailed = true; | ||||
|                         return; | ||||
|                     } | ||||
|  | ||||
|                     instruction.Operand = baseMethod; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         static bool IsCallToMethod(Instruction instruction, out MethodDefinition calledMethod) | ||||
|         { | ||||
|             if (instruction.OpCode == OpCodes.Call && | ||||
|                 instruction.Operand is MethodDefinition method) | ||||
|             { | ||||
|                 calledMethod = method; | ||||
|                 return true; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 calledMethod = null; | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,11 @@ | ||||
| fileFormatVersion: 2 | ||||
| guid: 661e1af528e3441f79e1552fb5ec4e0e | ||||
| MonoImporter: | ||||
|   externalObjects: {} | ||||
|   serializedVersion: 2 | ||||
|   defaultReferences: [] | ||||
|   executionOrder: 0 | ||||
|   icon: {instanceID: 0} | ||||
|   userData:  | ||||
|   assetBundleName:  | ||||
|   assetBundleVariant:  | ||||
| @@ -0,0 +1,56 @@ | ||||
| using Mono.CecilX; | ||||
|  | ||||
| namespace Mirror.Weaver | ||||
| { | ||||
|     // only shows warnings in case we use SyncVars etc. for MonoBehaviour. | ||||
|     static class MonoBehaviourProcessor | ||||
|     { | ||||
|         public static void Process(Logger Log, TypeDefinition td, ref bool WeavingFailed) | ||||
|         { | ||||
|             ProcessSyncVars(Log, td, ref WeavingFailed); | ||||
|             ProcessMethods(Log, td, ref WeavingFailed); | ||||
|         } | ||||
|  | ||||
|         static void ProcessSyncVars(Logger Log, TypeDefinition td, ref bool WeavingFailed) | ||||
|         { | ||||
|             // find syncvars | ||||
|             foreach (FieldDefinition fd in td.Fields) | ||||
|             { | ||||
|                 if (fd.HasCustomAttribute<SyncVarAttribute>()) | ||||
|                 { | ||||
|                     Log.Error($"SyncVar {fd.Name} must be inside a NetworkBehaviour.  {td.Name} is not a NetworkBehaviour", fd); | ||||
|                     WeavingFailed = true; | ||||
|                 } | ||||
|  | ||||
|                 if (SyncObjectInitializer.ImplementsSyncObject(fd.FieldType)) | ||||
|                 { | ||||
|                     Log.Error($"{fd.Name} is a SyncObject and must be inside a NetworkBehaviour.  {td.Name} is not a NetworkBehaviour", fd); | ||||
|                     WeavingFailed = true; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         static void ProcessMethods(Logger Log, TypeDefinition td, ref bool WeavingFailed) | ||||
|         { | ||||
|             // find command and RPC functions | ||||
|             foreach (MethodDefinition md in td.Methods) | ||||
|             { | ||||
|                 if (md.HasCustomAttribute<CommandAttribute>()) | ||||
|                 { | ||||
|                     Log.Error($"Command {md.Name} must be declared inside a NetworkBehaviour", md); | ||||
|                     WeavingFailed = true; | ||||
|                 } | ||||
|                 if (md.HasCustomAttribute<ClientRpcAttribute>()) | ||||
|                 { | ||||
|                     Log.Error($"ClientRpc {md.Name} must be declared inside a NetworkBehaviour", md); | ||||
|                     WeavingFailed = true; | ||||
|                 } | ||||
|                 if (md.HasCustomAttribute<TargetRpcAttribute>()) | ||||
|                 { | ||||
|                     Log.Error($"TargetRpc {md.Name} must be declared inside a NetworkBehaviour", md); | ||||
|                     WeavingFailed = true; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,11 @@ | ||||
| fileFormatVersion: 2 | ||||
| guid: 35c16722912b64af894e4f6668f2e54c | ||||
| MonoImporter: | ||||
|   externalObjects: {} | ||||
|   serializedVersion: 2 | ||||
|   defaultReferences: [] | ||||
|   executionOrder: 0 | ||||
|   icon: {instanceID: 0} | ||||
|   userData:  | ||||
|   assetBundleName:  | ||||
|   assetBundleVariant:  | ||||
							
								
								
									
										1224
									
								
								Assets/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1224
									
								
								Assets/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -0,0 +1,11 @@ | ||||
| fileFormatVersion: 2 | ||||
| guid: 8118d606be3214e5d99943ec39530dd8 | ||||
| MonoImporter: | ||||
|   externalObjects: {} | ||||
|   serializedVersion: 2 | ||||
|   defaultReferences: [] | ||||
|   executionOrder: 0 | ||||
|   icon: {instanceID: 0} | ||||
|   userData:  | ||||
|   assetBundleName:  | ||||
|   assetBundleVariant:  | ||||
							
								
								
									
										216
									
								
								Assets/Mirror/Editor/Weaver/Processors/ReaderWriterProcessor.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										216
									
								
								Assets/Mirror/Editor/Weaver/Processors/ReaderWriterProcessor.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,216 @@ | ||||
| // finds all readers and writers and register them | ||||
| using System.Linq; | ||||
| using Mono.CecilX; | ||||
| using Mono.CecilX.Cil; | ||||
| using Mono.CecilX.Rocks; | ||||
| using UnityEngine; | ||||
|  | ||||
| namespace Mirror.Weaver | ||||
| { | ||||
|     public static class ReaderWriterProcessor | ||||
|     { | ||||
|         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 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."); | ||||
|         } | ||||
|  | ||||
|         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 bool LoadMessageReadWriter(ModuleDefinition module, Writers writers, Readers readers, TypeDefinition klass, ref bool WeavingFailed) | ||||
|         { | ||||
|             bool modified = false; | ||||
|             if (!klass.IsAbstract && !klass.IsInterface && klass.ImplementsInterface<NetworkMessage>()) | ||||
|             { | ||||
|                 readers.GetReadFunc(module.ImportReference(klass), ref WeavingFailed); | ||||
|                 writers.GetWriteFunc(module.ImportReference(klass), ref WeavingFailed); | ||||
|                 modified = true; | ||||
|             } | ||||
|  | ||||
|             foreach (TypeDefinition td in klass.NestedTypes) | ||||
|             { | ||||
|                 modified |= LoadMessageReadWriter(module, writers, readers, td, ref WeavingFailed); | ||||
|             } | ||||
|             return modified; | ||||
|         } | ||||
|  | ||||
|         static bool LoadDeclaredWriters(AssemblyDefinition currentAssembly, TypeDefinition klass, Writers writers) | ||||
|         { | ||||
|             // register all the writers in this class.  Skip the ones with wrong signature | ||||
|             bool modified = false; | ||||
|             foreach (MethodDefinition method in klass.Methods) | ||||
|             { | ||||
|                 if (method.Parameters.Count != 2) | ||||
|                     continue; | ||||
|  | ||||
|                 if (!method.Parameters[0].ParameterType.Is<NetworkWriter>()) | ||||
|                     continue; | ||||
|  | ||||
|                 if (!method.ReturnType.Is(typeof(void))) | ||||
|                     continue; | ||||
|  | ||||
|                 if (!method.HasCustomAttribute<System.Runtime.CompilerServices.ExtensionAttribute>()) | ||||
|                     continue; | ||||
|  | ||||
|                 if (method.HasGenericParameters) | ||||
|                     continue; | ||||
|  | ||||
|                 TypeReference dataType = method.Parameters[1].ParameterType; | ||||
|                 writers.Register(dataType, currentAssembly.MainModule.ImportReference(method)); | ||||
|                 modified = true; | ||||
|             } | ||||
|             return modified; | ||||
|         } | ||||
|  | ||||
|         static bool LoadDeclaredReaders(AssemblyDefinition currentAssembly, TypeDefinition klass, Readers readers) | ||||
|         { | ||||
|             // 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; | ||||
|         } | ||||
|  | ||||
|         // 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); | ||||
|         } | ||||
|  | ||||
|         // 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); | ||||
|         } | ||||
|  | ||||
|         // 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); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,11 @@ | ||||
| fileFormatVersion: 2 | ||||
| guid: f3263602f0a374ecd8d08588b1fc2f76 | ||||
| MonoImporter: | ||||
|   externalObjects: {} | ||||
|   serializedVersion: 2 | ||||
|   defaultReferences: [] | ||||
|   executionOrder: 0 | ||||
|   icon: {instanceID: 0} | ||||
|   userData:  | ||||
|   assetBundleName:  | ||||
|   assetBundleVariant:  | ||||
							
								
								
									
										102
									
								
								Assets/Mirror/Editor/Weaver/Processors/RpcProcessor.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								Assets/Mirror/Editor/Weaver/Processors/RpcProcessor.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,102 @@ | ||||
| using Mono.CecilX; | ||||
| using Mono.CecilX.Cil; | ||||
|  | ||||
| namespace Mirror.Weaver | ||||
| { | ||||
|     // Processes [Rpc] methods in NetworkBehaviour | ||||
|     public static class RpcProcessor | ||||
|     { | ||||
|         public static MethodDefinition ProcessRpcInvoke(WeaverTypes weaverTypes, Writers writers, 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, "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; | ||||
|         } | ||||
|  | ||||
|         /* | ||||
|          * 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; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										11
									
								
								Assets/Mirror/Editor/Weaver/Processors/RpcProcessor.cs.meta
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								Assets/Mirror/Editor/Weaver/Processors/RpcProcessor.cs.meta
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| fileFormatVersion: 2 | ||||
| guid: a3cb7051ff41947e59bba58bdd2b73fc | ||||
| MonoImporter: | ||||
|   externalObjects: {} | ||||
|   serializedVersion: 2 | ||||
|   defaultReferences: [] | ||||
|   executionOrder: 0 | ||||
|   icon: {instanceID: 0} | ||||
|   userData:  | ||||
|   assetBundleName:  | ||||
|   assetBundleVariant:  | ||||
| @@ -0,0 +1,154 @@ | ||||
| // Injects server/client active checks for [Server/Client] attributes | ||||
| using Mono.CecilX; | ||||
| using Mono.CecilX.Cil; | ||||
|  | ||||
| namespace Mirror.Weaver | ||||
| { | ||||
|     static class ServerClientAttributeProcessor | ||||
|     { | ||||
|         public static bool Process(WeaverTypes weaverTypes, Logger Log, TypeDefinition td, ref bool WeavingFailed) | ||||
|         { | ||||
|             bool modified = false; | ||||
|             foreach (MethodDefinition md in td.Methods) | ||||
|             { | ||||
|                 modified |= ProcessSiteMethod(weaverTypes, Log, md, ref WeavingFailed); | ||||
|             } | ||||
|  | ||||
|             foreach (TypeDefinition nested in td.NestedTypes) | ||||
|             { | ||||
|                 modified |= Process(weaverTypes, Log, nested, ref WeavingFailed); | ||||
|             } | ||||
|             return modified; | ||||
|         } | ||||
|  | ||||
|         static bool ProcessSiteMethod(WeaverTypes weaverTypes, Logger Log, MethodDefinition md, ref bool WeavingFailed) | ||||
|         { | ||||
|             if (md.Name == ".cctor" || | ||||
|                 md.Name == NetworkBehaviourProcessor.ProcessedFunctionName || | ||||
|                 md.Name.StartsWith(Weaver.InvokeRpcPrefix)) | ||||
|                 return false; | ||||
|  | ||||
|             if (md.IsAbstract) | ||||
|             { | ||||
|                 if (HasServerClientAttribute(md)) | ||||
|                 { | ||||
|                     Log.Error("Server or Client Attributes can't be added to abstract method. Server and Client Attributes are not inherited so they need to be applied to the override methods instead.", md); | ||||
|                     WeavingFailed = true; | ||||
|                 } | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             if (md.Body != null && md.Body.Instructions != null) | ||||
|             { | ||||
|                 return ProcessMethodAttributes(weaverTypes, md); | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         public static bool HasServerClientAttribute(MethodDefinition md) | ||||
|         { | ||||
|             foreach (CustomAttribute attr in md.CustomAttributes) | ||||
|             { | ||||
|                 switch (attr.Constructor.DeclaringType.ToString()) | ||||
|                 { | ||||
|                     case "Mirror.ServerAttribute": | ||||
|                     case "Mirror.ServerCallbackAttribute": | ||||
|                     case "Mirror.ClientAttribute": | ||||
|                     case "Mirror.ClientCallbackAttribute": | ||||
|                         return true; | ||||
|                     default: | ||||
|                         break; | ||||
|                 } | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         public static bool ProcessMethodAttributes(WeaverTypes weaverTypes, MethodDefinition md) | ||||
|         { | ||||
|             if (md.HasCustomAttribute<ServerAttribute>()) | ||||
|                 InjectServerGuard(weaverTypes, md, true); | ||||
|             else if (md.HasCustomAttribute<ServerCallbackAttribute>()) | ||||
|                 InjectServerGuard(weaverTypes, md, false); | ||||
|             else if (md.HasCustomAttribute<ClientAttribute>()) | ||||
|                 InjectClientGuard(weaverTypes, md, true); | ||||
|             else if (md.HasCustomAttribute<ClientCallbackAttribute>()) | ||||
|                 InjectClientGuard(weaverTypes, md, false); | ||||
|             else | ||||
|                 return false; | ||||
|  | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         static void InjectServerGuard(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.NetworkServerGetActive)); | ||||
|             worker.InsertBefore(top, worker.Create(OpCodes.Brtrue, top)); | ||||
|             if (logWarning) | ||||
|             { | ||||
|                 worker.InsertBefore(top, worker.Create(OpCodes.Ldstr, $"[Server] function '{md.FullName}' called when server 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)); | ||||
|         } | ||||
|  | ||||
|         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)); | ||||
|         } | ||||
|  | ||||
|         // this is required to early-out from a function with "ref" or "out" parameters | ||||
|         static void InjectGuardParameters(MethodDefinition md, ILProcessor worker, Instruction top) | ||||
|         { | ||||
|             int offset = md.Resolve().IsStatic ? 0 : 1; | ||||
|             for (int index = 0; index < md.Parameters.Count; index++) | ||||
|             { | ||||
|                 ParameterDefinition param = md.Parameters[index]; | ||||
|                 if (param.IsOut) | ||||
|                 { | ||||
|                     TypeReference elementType = param.ParameterType.GetElementType(); | ||||
|  | ||||
|                     md.Body.Variables.Add(new VariableDefinition(elementType)); | ||||
|                     md.Body.InitLocals = true; | ||||
|  | ||||
|                     worker.InsertBefore(top, worker.Create(OpCodes.Ldarg, index + offset)); | ||||
|                     worker.InsertBefore(top, worker.Create(OpCodes.Ldloca_S, (byte)(md.Body.Variables.Count - 1))); | ||||
|                     worker.InsertBefore(top, worker.Create(OpCodes.Initobj, elementType)); | ||||
|                     worker.InsertBefore(top, worker.Create(OpCodes.Ldloc, md.Body.Variables.Count - 1)); | ||||
|                     worker.InsertBefore(top, worker.Create(OpCodes.Stobj, elementType)); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // this is required to early-out from a function with a return value. | ||||
|         static void InjectGuardReturnValue(MethodDefinition md, ILProcessor worker, Instruction top) | ||||
|         { | ||||
|             if (!md.ReturnType.Is(typeof(void))) | ||||
|             { | ||||
|                 md.Body.Variables.Add(new VariableDefinition(md.ReturnType)); | ||||
|                 md.Body.InitLocals = true; | ||||
|  | ||||
|                 worker.InsertBefore(top, worker.Create(OpCodes.Ldloca_S, (byte)(md.Body.Variables.Count - 1))); | ||||
|                 worker.InsertBefore(top, worker.Create(OpCodes.Initobj, md.ReturnType)); | ||||
|                 worker.InsertBefore(top, worker.Create(OpCodes.Ldloc, md.Body.Variables.Count - 1)); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,11 @@ | ||||
| fileFormatVersion: 2 | ||||
| guid: 024f251bf693bb345b90b9177892d534 | ||||
| MonoImporter: | ||||
|   externalObjects: {} | ||||
|   serializedVersion: 2 | ||||
|   defaultReferences: [] | ||||
|   executionOrder: 0 | ||||
|   icon: {instanceID: 0} | ||||
|   userData:  | ||||
|   assetBundleName:  | ||||
|   assetBundleVariant:  | ||||
| @@ -0,0 +1,39 @@ | ||||
| using Mono.CecilX; | ||||
| using Mono.CecilX.Cil; | ||||
|  | ||||
| namespace Mirror.Weaver | ||||
| { | ||||
|     public static class SyncObjectInitializer | ||||
|     { | ||||
|         // generates code like: | ||||
|         // this.InitSyncObject(m_sizes); | ||||
|         public static void GenerateSyncObjectInitializer(ILProcessor worker, WeaverTypes weaverTypes, FieldDefinition fd) | ||||
|         { | ||||
|             // register syncobject in network behaviour | ||||
|             worker.Emit(OpCodes.Ldarg_0); | ||||
|             worker.Emit(OpCodes.Ldarg_0); | ||||
|             worker.Emit(OpCodes.Ldfld, fd); | ||||
|             worker.Emit(OpCodes.Call, weaverTypes.InitSyncObjectReference); | ||||
|         } | ||||
|  | ||||
|         public static bool ImplementsSyncObject(TypeReference typeRef) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 // value types cant inherit from SyncObject | ||||
|                 if (typeRef.IsValueType) | ||||
|                 { | ||||
|                     return false; | ||||
|                 } | ||||
|  | ||||
|                 return typeRef.Resolve().IsDerivedFrom<SyncObject>(); | ||||
|             } | ||||
|             catch | ||||
|             { | ||||
|                 // sometimes this will fail if we reference a weird library that can't be resolved, so we just swallow that exception and return false | ||||
|             } | ||||
|  | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,11 @@ | ||||
| fileFormatVersion: 2 | ||||
| guid: d02219b00b3674e59a2151f41e791688 | ||||
| MonoImporter: | ||||
|   externalObjects: {} | ||||
|   serializedVersion: 2 | ||||
|   defaultReferences: [] | ||||
|   executionOrder: 0 | ||||
|   icon: {instanceID: 0} | ||||
|   userData:  | ||||
|   assetBundleName:  | ||||
|   assetBundleVariant:  | ||||
| @@ -0,0 +1,84 @@ | ||||
| using System.Collections.Generic; | ||||
| using Mono.CecilX; | ||||
|  | ||||
| namespace Mirror.Weaver | ||||
| { | ||||
|     public static class SyncObjectProcessor | ||||
|     { | ||||
|         // ulong = 64 bytes | ||||
|         const int SyncObjectsLimit = 64; | ||||
|  | ||||
|         // 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().IsDerivedFrom<SyncObject>()) | ||||
|                 { | ||||
|                     if (fd.IsStatic) | ||||
|                     { | ||||
|                         Log.Error($"{fd.Name} cannot be static", fd); | ||||
|                         WeavingFailed = true; | ||||
|                         continue; | ||||
|                     } | ||||
|  | ||||
|                     // SyncObjects always needs to be readonly to guarantee. | ||||
|                     // Weaver calls InitSyncObject on them for dirty bits etc. | ||||
|                     // Reassigning at runtime would cause undefined behaviour. | ||||
|                     // (C# 'readonly' is called 'initonly' in IL code.) | ||||
|                     // | ||||
|                     // NOTE: instead of forcing readonly, we could also scan all | ||||
|                     //       instructions for SyncObject assignments. this would | ||||
|                     //       make unit tests very difficult though. | ||||
|                     if (!fd.IsInitOnly) | ||||
|                     { | ||||
|                         // just a warning for now. | ||||
|                         // many people might still use non-readonly SyncObjects. | ||||
|                         Log.Warning($"{fd.Name} should have a 'readonly' keyword in front of the variable because {typeof(SyncObject)}s always need to be initialized by the Weaver.", fd); | ||||
|  | ||||
|                         // only log, but keep weaving. no need to break projects. | ||||
|                         //WeavingFailed = true; | ||||
|                     } | ||||
|  | ||||
|                     GenerateReadersAndWriters(writers, readers, fd.FieldType, ref WeavingFailed); | ||||
|  | ||||
|                     syncObjects.Add(fd); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // SyncObjects dirty mask is 64 bit. can't sync more than 64. | ||||
|             if (syncObjects.Count > 64) | ||||
|             { | ||||
|                 Log.Error($"{td.Name} has > {SyncObjectsLimit} SyncObjects (SyncLists etc). Consider refactoring your class into multiple components", td); | ||||
|                 WeavingFailed = true; | ||||
|             } | ||||
|  | ||||
|  | ||||
|             return syncObjects; | ||||
|         } | ||||
|  | ||||
|         // Generates serialization methods for synclists | ||||
|         static void GenerateReadersAndWriters(Writers writers, Readers readers, TypeReference tr, ref bool WeavingFailed) | ||||
|         { | ||||
|             if (tr is GenericInstanceType genericInstance) | ||||
|             { | ||||
|                 foreach (TypeReference argument in genericInstance.GenericArguments) | ||||
|                 { | ||||
|                     if (!argument.IsGenericParameter) | ||||
|                     { | ||||
|                         readers.GetReadFunc(argument, ref WeavingFailed); | ||||
|                         writers.GetWriteFunc(argument, ref WeavingFailed); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (tr != null) | ||||
|             { | ||||
|                 GenerateReadersAndWriters(writers, readers, tr.Resolve().BaseType, ref WeavingFailed); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,11 @@ | ||||
| fileFormatVersion: 2 | ||||
| guid: 78f71efc83cde4917b7d21efa90bcc9a | ||||
| MonoImporter: | ||||
|   externalObjects: {} | ||||
|   serializedVersion: 2 | ||||
|   defaultReferences: [] | ||||
|   executionOrder: 0 | ||||
|   icon: {instanceID: 0} | ||||
|   userData:  | ||||
|   assetBundleName:  | ||||
|   assetBundleVariant:  | ||||
| @@ -0,0 +1,174 @@ | ||||
| // [SyncVar] int health; | ||||
| // is replaced with: | ||||
| // public int Networkhealth { get; set; } properties. | ||||
| // this class processes all access to 'health' and replaces it with 'Networkhealth' | ||||
| using System; | ||||
| using Mono.CecilX; | ||||
| using Mono.CecilX.Cil; | ||||
|  | ||||
| namespace Mirror.Weaver | ||||
| { | ||||
|     public static class SyncVarAttributeAccessReplacer | ||||
|     { | ||||
|         // process the module | ||||
|         public static void Process(ModuleDefinition moduleDef, SyncVarAccessLists syncVarAccessLists) | ||||
|         { | ||||
|             DateTime startTime = DateTime.Now; | ||||
|  | ||||
|             // process all classes in this module | ||||
|             foreach (TypeDefinition td in moduleDef.Types) | ||||
|             { | ||||
|                 if (td.IsClass) | ||||
|                 { | ||||
|                     ProcessClass(syncVarAccessLists, td); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             Console.WriteLine($"  ProcessSitesModule {moduleDef.Name} elapsed time:{(DateTime.Now - startTime)}"); | ||||
|         } | ||||
|  | ||||
|         static void ProcessClass(SyncVarAccessLists syncVarAccessLists, TypeDefinition td) | ||||
|         { | ||||
|             //Console.WriteLine($"    ProcessClass {td}"); | ||||
|  | ||||
|             // process all methods in this class | ||||
|             foreach (MethodDefinition md in td.Methods) | ||||
|             { | ||||
|                 ProcessMethod(syncVarAccessLists, md); | ||||
|             } | ||||
|  | ||||
|             // processes all nested classes in this class recursively | ||||
|             foreach (TypeDefinition nested in td.NestedTypes) | ||||
|             { | ||||
|                 ProcessClass(syncVarAccessLists, nested); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         static void ProcessMethod(SyncVarAccessLists syncVarAccessLists, MethodDefinition md) | ||||
|         { | ||||
|             // process all references to replaced members with properties | ||||
|             //Log.Warning($"      ProcessSiteMethod {md}"); | ||||
|  | ||||
|             // skip static constructor, "MirrorProcessed", "InvokeUserCode_" | ||||
|             if (md.Name == ".cctor" || | ||||
|                 md.Name == NetworkBehaviourProcessor.ProcessedFunctionName || | ||||
|                 md.Name.StartsWith(Weaver.InvokeRpcPrefix)) | ||||
|                 return; | ||||
|  | ||||
|             // skip abstract | ||||
|             if (md.IsAbstract) | ||||
|             { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             // go through all instructions of this method | ||||
|             if (md.Body != null && md.Body.Instructions != null) | ||||
|             { | ||||
|                 for (int i = 0; i < md.Body.Instructions.Count;) | ||||
|                 { | ||||
|                     Instruction instr = md.Body.Instructions[i]; | ||||
|                     i += ProcessInstruction(syncVarAccessLists, md, instr, i); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         static int ProcessInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction instr, int iCount) | ||||
|         { | ||||
|             // stfld (sets value of a field)? | ||||
|             if (instr.OpCode == OpCodes.Stfld && instr.Operand is FieldDefinition opFieldst) | ||||
|             { | ||||
|                 ProcessSetInstruction(syncVarAccessLists, md, instr, opFieldst); | ||||
|             } | ||||
|  | ||||
|             // ldfld (load value of a field)? | ||||
|             if (instr.OpCode == OpCodes.Ldfld && instr.Operand is FieldDefinition opFieldld) | ||||
|             { | ||||
|                 // this instruction gets the value of a field. cache the field reference. | ||||
|                 ProcessGetInstruction(syncVarAccessLists, md, instr, opFieldld); | ||||
|             } | ||||
|  | ||||
|             // ldflda (load field address aka reference) | ||||
|             if (instr.OpCode == OpCodes.Ldflda && instr.Operand is FieldDefinition opFieldlda) | ||||
|             { | ||||
|                 // watch out for initobj instruction | ||||
|                 // see https://github.com/vis2k/Mirror/issues/696 | ||||
|                 return ProcessLoadAddressInstruction(syncVarAccessLists, md, instr, opFieldlda, iCount); | ||||
|             } | ||||
|  | ||||
|             // we processed one instruction (instr) | ||||
|             return 1; | ||||
|         } | ||||
|  | ||||
|         // replaces syncvar write access with the NetworkXYZ.set property calls | ||||
|         static void ProcessSetInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction i, FieldDefinition opField) | ||||
|         { | ||||
|             // don't replace property call sites in constructors | ||||
|             if (md.Name == ".ctor") | ||||
|                 return; | ||||
|  | ||||
|             // does it set a field that we replaced? | ||||
|             if (syncVarAccessLists.replacementSetterProperties.TryGetValue(opField, out MethodDefinition replacement)) | ||||
|             { | ||||
|                 //replace with property | ||||
|                 //Log.Warning($"    replacing {md.Name}:{i}", opField); | ||||
|                 i.OpCode = OpCodes.Call; | ||||
|                 i.Operand = replacement; | ||||
|                 //Log.Warning($"    replaced {md.Name}:{i}", opField); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // replaces syncvar read access with the NetworkXYZ.get property calls | ||||
|         static void ProcessGetInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction i, FieldDefinition opField) | ||||
|         { | ||||
|             // don't replace property call sites in constructors | ||||
|             if (md.Name == ".ctor") | ||||
|                 return; | ||||
|  | ||||
|             // does it set a field that we replaced? | ||||
|             if (syncVarAccessLists.replacementGetterProperties.TryGetValue(opField, out MethodDefinition replacement)) | ||||
|             { | ||||
|                 //replace with property | ||||
|                 //Log.Warning($"    replacing {md.Name}:{i}"); | ||||
|                 i.OpCode = OpCodes.Call; | ||||
|                 i.Operand = replacement; | ||||
|                 //Log.Warning($"    replaced {md.Name}:{i}"); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         static int ProcessLoadAddressInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction instr, FieldDefinition opField, int iCount) | ||||
|         { | ||||
|             // don't replace property call sites in constructors | ||||
|             if (md.Name == ".ctor") | ||||
|                 return 1; | ||||
|  | ||||
|             // does it set a field that we replaced? | ||||
|             if (syncVarAccessLists.replacementSetterProperties.TryGetValue(opField, out MethodDefinition replacement)) | ||||
|             { | ||||
|                 // we have a replacement for this property | ||||
|                 // is the next instruction a initobj? | ||||
|                 Instruction nextInstr = md.Body.Instructions[iCount + 1]; | ||||
|  | ||||
|                 if (nextInstr.OpCode == OpCodes.Initobj) | ||||
|                 { | ||||
|                     // we need to replace this code with: | ||||
|                     //     var tmp = new MyStruct(); | ||||
|                     //     this.set_Networkxxxx(tmp); | ||||
|                     ILProcessor worker = md.Body.GetILProcessor(); | ||||
|                     VariableDefinition tmpVariable = new VariableDefinition(opField.FieldType); | ||||
|                     md.Body.Variables.Add(tmpVariable); | ||||
|  | ||||
|                     worker.InsertBefore(instr, worker.Create(OpCodes.Ldloca, tmpVariable)); | ||||
|                     worker.InsertBefore(instr, worker.Create(OpCodes.Initobj, opField.FieldType)); | ||||
|                     worker.InsertBefore(instr, worker.Create(OpCodes.Ldloc, tmpVariable)); | ||||
|                     worker.InsertBefore(instr, worker.Create(OpCodes.Call, replacement)); | ||||
|  | ||||
|                     worker.Remove(instr); | ||||
|                     worker.Remove(nextInstr); | ||||
|                     return 4; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return 1; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,11 @@ | ||||
| fileFormatVersion: 2 | ||||
| guid: d48f1ab125e9940a995603796bccc59e | ||||
| MonoImporter: | ||||
|   externalObjects: {} | ||||
|   serializedVersion: 2 | ||||
|   defaultReferences: [] | ||||
|   executionOrder: 0 | ||||
|   icon: {instanceID: 0} | ||||
|   userData:  | ||||
|   assetBundleName:  | ||||
|   assetBundleVariant:  | ||||
| @@ -0,0 +1,490 @@ | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using Mono.CecilX; | ||||
| using Mono.CecilX.Cil; | ||||
|  | ||||
| namespace Mirror.Weaver | ||||
| { | ||||
|     // Processes [SyncVar] attribute fields in NetworkBehaviour | ||||
|     // not static, because ILPostProcessor is multithreaded | ||||
|     public class SyncVarAttributeProcessor | ||||
|     { | ||||
|         // ulong = 64 bytes | ||||
|         const int SyncVarLimit = 64; | ||||
|  | ||||
|         AssemblyDefinition assembly; | ||||
|         WeaverTypes weaverTypes; | ||||
|         SyncVarAccessLists syncVarAccessLists; | ||||
|         Logger Log; | ||||
|  | ||||
|         string HookParameterMessage(string hookName, TypeReference ValueType) => | ||||
|             $"void {hookName}({ValueType} oldValue, {ValueType} newValue)"; | ||||
|  | ||||
|         public SyncVarAttributeProcessor(AssemblyDefinition assembly, WeaverTypes weaverTypes, SyncVarAccessLists syncVarAccessLists, Logger Log) | ||||
|         { | ||||
|             this.assembly = assembly; | ||||
|             this.weaverTypes = weaverTypes; | ||||
|             this.syncVarAccessLists = syncVarAccessLists; | ||||
|             this.Log = Log; | ||||
|         } | ||||
|  | ||||
|         // Get hook method if any | ||||
|         public MethodDefinition GetHookMethod(TypeDefinition td, FieldDefinition syncVar, ref bool WeavingFailed) | ||||
|         { | ||||
|             CustomAttribute syncVarAttr = syncVar.GetCustomAttribute<SyncVarAttribute>(); | ||||
|  | ||||
|             if (syncVarAttr == null) | ||||
|                 return null; | ||||
|  | ||||
|             string hookFunctionName = syncVarAttr.GetField<string>("hook", null); | ||||
|  | ||||
|             if (hookFunctionName == null) | ||||
|                 return null; | ||||
|  | ||||
|             return FindHookMethod(td, syncVar, hookFunctionName, ref WeavingFailed); | ||||
|         } | ||||
|  | ||||
|         MethodDefinition FindHookMethod(TypeDefinition td, FieldDefinition syncVar, string hookFunctionName, ref bool WeavingFailed) | ||||
|         { | ||||
|             List<MethodDefinition> methods = td.GetMethods(hookFunctionName); | ||||
|  | ||||
|             List<MethodDefinition> methodsWith2Param = new List<MethodDefinition>(methods.Where(m => m.Parameters.Count == 2)); | ||||
|  | ||||
|             if (methodsWith2Param.Count == 0) | ||||
|             { | ||||
|                 Log.Error($"Could not find hook for '{syncVar.Name}', hook name '{hookFunctionName}'. " + | ||||
|                     $"Method signature should be {HookParameterMessage(hookFunctionName, syncVar.FieldType)}", | ||||
|                     syncVar); | ||||
|                 WeavingFailed = true; | ||||
|  | ||||
|                 return null; | ||||
|             } | ||||
|  | ||||
|             foreach (MethodDefinition method in methodsWith2Param) | ||||
|             { | ||||
|                 if (MatchesParameters(syncVar, method)) | ||||
|                 { | ||||
|                     return method; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             Log.Error($"Wrong type for Parameter in hook for '{syncVar.Name}', hook name '{hookFunctionName}'. " + | ||||
|                      $"Method signature should be {HookParameterMessage(hookFunctionName, syncVar.FieldType)}", | ||||
|                    syncVar); | ||||
|             WeavingFailed = true; | ||||
|  | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
|         bool MatchesParameters(FieldDefinition syncVar, MethodDefinition method) | ||||
|         { | ||||
|             // matches void onValueChange(T oldValue, T newValue) | ||||
|             return method.Parameters[0].ParameterType.FullName == syncVar.FieldType.FullName && | ||||
|                    method.Parameters[1].ParameterType.FullName == syncVar.FieldType.FullName; | ||||
|         } | ||||
|  | ||||
|         public MethodDefinition GenerateSyncVarGetter(FieldDefinition fd, string originalName, FieldDefinition netFieldId) | ||||
|         { | ||||
|             //Create the get method | ||||
|             MethodDefinition get = new MethodDefinition( | ||||
|                 $"get_Network{originalName}", MethodAttributes.Public | | ||||
|                                               MethodAttributes.SpecialName | | ||||
|                                               MethodAttributes.HideBySig, | ||||
|                     fd.FieldType); | ||||
|  | ||||
|             ILProcessor worker = get.Body.GetILProcessor(); | ||||
|  | ||||
|             // [SyncVar] GameObject? | ||||
|             if (fd.FieldType.Is<UnityEngine.GameObject>()) | ||||
|             { | ||||
|                 // return this.GetSyncVarGameObject(ref field, uint netId); | ||||
|                 // this. | ||||
|                 worker.Emit(OpCodes.Ldarg_0); | ||||
|                 worker.Emit(OpCodes.Ldarg_0); | ||||
|                 worker.Emit(OpCodes.Ldfld, netFieldId); | ||||
|                 worker.Emit(OpCodes.Ldarg_0); | ||||
|                 worker.Emit(OpCodes.Ldflda, fd); | ||||
|                 worker.Emit(OpCodes.Call, weaverTypes.getSyncVarGameObjectReference); | ||||
|                 worker.Emit(OpCodes.Ret); | ||||
|             } | ||||
|             // [SyncVar] NetworkIdentity? | ||||
|             else if (fd.FieldType.Is<NetworkIdentity>()) | ||||
|             { | ||||
|                 // return this.GetSyncVarNetworkIdentity(ref field, uint netId); | ||||
|                 // this. | ||||
|                 worker.Emit(OpCodes.Ldarg_0); | ||||
|                 worker.Emit(OpCodes.Ldarg_0); | ||||
|                 worker.Emit(OpCodes.Ldfld, netFieldId); | ||||
|                 worker.Emit(OpCodes.Ldarg_0); | ||||
|                 worker.Emit(OpCodes.Ldflda, fd); | ||||
|                 worker.Emit(OpCodes.Call, weaverTypes.getSyncVarNetworkIdentityReference); | ||||
|                 worker.Emit(OpCodes.Ret); | ||||
|             } | ||||
|             else if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>()) | ||||
|             { | ||||
|                 // return this.GetSyncVarNetworkBehaviour<T>(ref field, uint netId); | ||||
|                 // this. | ||||
|                 worker.Emit(OpCodes.Ldarg_0); | ||||
|                 worker.Emit(OpCodes.Ldarg_0); | ||||
|                 worker.Emit(OpCodes.Ldfld, netFieldId); | ||||
|                 worker.Emit(OpCodes.Ldarg_0); | ||||
|                 worker.Emit(OpCodes.Ldflda, fd); | ||||
|                 MethodReference getFunc = weaverTypes.getSyncVarNetworkBehaviourReference.MakeGeneric(assembly.MainModule, fd.FieldType); | ||||
|                 worker.Emit(OpCodes.Call, getFunc); | ||||
|                 worker.Emit(OpCodes.Ret); | ||||
|             } | ||||
|             // [SyncVar] int, string, etc. | ||||
|             else | ||||
|             { | ||||
|                 worker.Emit(OpCodes.Ldarg_0); | ||||
|                 worker.Emit(OpCodes.Ldfld, fd); | ||||
|                 worker.Emit(OpCodes.Ret); | ||||
|             } | ||||
|  | ||||
|             get.Body.Variables.Add(new VariableDefinition(fd.FieldType)); | ||||
|             get.Body.InitLocals = true; | ||||
|             get.SemanticsAttributes = MethodSemanticsAttributes.Getter; | ||||
|  | ||||
|             return get; | ||||
|         } | ||||
|  | ||||
|         public MethodDefinition GenerateSyncVarSetter(TypeDefinition td, FieldDefinition fd, string originalName, long dirtyBit, FieldDefinition netFieldId, ref bool WeavingFailed) | ||||
|         { | ||||
|             //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); | ||||
|  | ||||
|             // NOTE: SyncVar...Equal functions are static. | ||||
|             // don't Emit Ldarg_0 aka 'this'. | ||||
|  | ||||
|             // new value to set | ||||
|             worker.Emit(OpCodes.Ldarg_1); | ||||
|  | ||||
|             // reference to field to set | ||||
|             // make generic version of SetSyncVar with field type | ||||
|             if (fd.FieldType.Is<UnityEngine.GameObject>()) | ||||
|             { | ||||
|                 // reference to netId Field to set | ||||
|                 worker.Emit(OpCodes.Ldarg_0); | ||||
|                 worker.Emit(OpCodes.Ldfld, netFieldId); | ||||
|  | ||||
|                 worker.Emit(OpCodes.Call, weaverTypes.syncVarGameObjectEqualReference); | ||||
|             } | ||||
|             else if (fd.FieldType.Is<NetworkIdentity>()) | ||||
|             { | ||||
|                 // reference to netId Field to set | ||||
|                 worker.Emit(OpCodes.Ldarg_0); | ||||
|                 worker.Emit(OpCodes.Ldfld, netFieldId); | ||||
|  | ||||
|                 worker.Emit(OpCodes.Call, weaverTypes.syncVarNetworkIdentityEqualReference); | ||||
|             } | ||||
|             else if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>()) | ||||
|             { | ||||
|                 // reference to netId Field to set | ||||
|                 worker.Emit(OpCodes.Ldarg_0); | ||||
|                 worker.Emit(OpCodes.Ldfld, netFieldId); | ||||
|  | ||||
|                 MethodReference getFunc = weaverTypes.syncVarNetworkBehaviourEqualReference.MakeGeneric(assembly.MainModule, fd.FieldType); | ||||
|                 worker.Emit(OpCodes.Call, getFunc); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 worker.Emit(OpCodes.Ldarg_0); | ||||
|                 worker.Emit(OpCodes.Ldflda, fd); | ||||
|  | ||||
|                 GenericInstanceMethod syncVarEqualGm = new GenericInstanceMethod(weaverTypes.syncVarEqualReference); | ||||
|                 syncVarEqualGm.GenericArguments.Add(fd.FieldType); | ||||
|                 worker.Emit(OpCodes.Call, syncVarEqualGm); | ||||
|             } | ||||
|  | ||||
|             worker.Emit(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.Emit(OpCodes.Ldarg_0); | ||||
|             worker.Emit(OpCodes.Ldfld, fd); | ||||
|             worker.Emit(OpCodes.Stloc, oldValue); | ||||
|  | ||||
|             // this | ||||
|             worker.Emit(OpCodes.Ldarg_0); | ||||
|  | ||||
|             // new value to set | ||||
|             worker.Emit(OpCodes.Ldarg_1); | ||||
|  | ||||
|             // reference to field to set | ||||
|             worker.Emit(OpCodes.Ldarg_0); | ||||
|             worker.Emit(OpCodes.Ldflda, fd); | ||||
|  | ||||
|             // dirty bit | ||||
|             // 8 byte integer aka long | ||||
|             worker.Emit(OpCodes.Ldc_I8, dirtyBit); | ||||
|  | ||||
|             if (fd.FieldType.Is<UnityEngine.GameObject>()) | ||||
|             { | ||||
|                 // reference to netId Field to set | ||||
|                 worker.Emit(OpCodes.Ldarg_0); | ||||
|                 worker.Emit(OpCodes.Ldflda, netFieldId); | ||||
|  | ||||
|                 worker.Emit(OpCodes.Call, weaverTypes.setSyncVarGameObjectReference); | ||||
|             } | ||||
|             else if (fd.FieldType.Is<NetworkIdentity>()) | ||||
|             { | ||||
|                 // reference to netId Field to set | ||||
|                 worker.Emit(OpCodes.Ldarg_0); | ||||
|                 worker.Emit(OpCodes.Ldflda, netFieldId); | ||||
|  | ||||
|                 worker.Emit(OpCodes.Call, weaverTypes.setSyncVarNetworkIdentityReference); | ||||
|             } | ||||
|             else if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>()) | ||||
|             { | ||||
|                 // reference to netId Field to set | ||||
|                 worker.Emit(OpCodes.Ldarg_0); | ||||
|                 worker.Emit(OpCodes.Ldflda, netFieldId); | ||||
|  | ||||
|                 MethodReference getFunc = weaverTypes.setSyncVarNetworkBehaviourReference.MakeGeneric(assembly.MainModule, fd.FieldType); | ||||
|                 worker.Emit(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.Emit(OpCodes.Call, gm); | ||||
|             } | ||||
|  | ||||
|             MethodDefinition hookMethod = GetHookMethod(td, fd, ref WeavingFailed); | ||||
|  | ||||
|             if (hookMethod != null) | ||||
|             { | ||||
|                 //if (NetworkServer.localClientActive && !getSyncVarHookGuard(dirtyBit)) | ||||
|                 Instruction label = worker.Create(OpCodes.Nop); | ||||
|                 worker.Emit(OpCodes.Call, weaverTypes.NetworkServerGetLocalClientActive); | ||||
|                 worker.Emit(OpCodes.Brfalse, label); | ||||
|                 worker.Emit(OpCodes.Ldarg_0); | ||||
|                 worker.Emit(OpCodes.Ldc_I8, dirtyBit); | ||||
|                 worker.Emit(OpCodes.Call, weaverTypes.getSyncVarHookGuard); | ||||
|                 worker.Emit(OpCodes.Brtrue, label); | ||||
|  | ||||
|                 // setSyncVarHookGuard(dirtyBit, true); | ||||
|                 worker.Emit(OpCodes.Ldarg_0); | ||||
|                 worker.Emit(OpCodes.Ldc_I8, dirtyBit); | ||||
|                 worker.Emit(OpCodes.Ldc_I4_1); | ||||
|                 worker.Emit(OpCodes.Call, weaverTypes.setSyncVarHookGuard); | ||||
|  | ||||
|                 // call hook (oldValue, newValue) | ||||
|                 // Generates: OnValueChanged(oldValue, value); | ||||
|                 WriteCallHookMethodUsingArgument(worker, hookMethod, oldValue); | ||||
|  | ||||
|                 // setSyncVarHookGuard(dirtyBit, false); | ||||
|                 worker.Emit(OpCodes.Ldarg_0); | ||||
|                 worker.Emit(OpCodes.Ldc_I8, dirtyBit); | ||||
|                 worker.Emit(OpCodes.Ldc_I4_0); | ||||
|                 worker.Emit(OpCodes.Call, weaverTypes.setSyncVarHookGuard); | ||||
|  | ||||
|                 worker.Append(label); | ||||
|             } | ||||
|  | ||||
|             worker.Append(endOfMethod); | ||||
|  | ||||
|             worker.Emit(OpCodes.Ret); | ||||
|  | ||||
|             set.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.In, fd.FieldType)); | ||||
|             set.SemanticsAttributes = MethodSemanticsAttributes.Setter; | ||||
|  | ||||
|             return set; | ||||
|         } | ||||
|  | ||||
|         public void ProcessSyncVar(TypeDefinition td, FieldDefinition fd, Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds, long dirtyBit, ref bool WeavingFailed) | ||||
|         { | ||||
|             string originalName = fd.Name; | ||||
|  | ||||
|             // GameObject/NetworkIdentity SyncVars have a new field for netId | ||||
|             FieldDefinition netIdField = null; | ||||
|             // NetworkBehaviour has different field type than other NetworkIdentityFields | ||||
|             if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>()) | ||||
|             { | ||||
|                 netIdField = new FieldDefinition($"___{fd.Name}NetId", | ||||
|                    FieldAttributes.Private, | ||||
|                    weaverTypes.Import<NetworkBehaviour.NetworkBehaviourSyncVar>()); | ||||
|  | ||||
|                 syncVarNetIds[fd] = netIdField; | ||||
|             } | ||||
|             else if (fd.FieldType.IsNetworkIdentityField()) | ||||
|             { | ||||
|                 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, ref WeavingFailed); | ||||
|  | ||||
|             //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); | ||||
|             syncVarAccessLists.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.IsNetworkIdentityField()) | ||||
|             { | ||||
|                 syncVarAccessLists.replacementGetterProperties[fd] = get; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public (List<FieldDefinition> syncVars, Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds) ProcessSyncVars(TypeDefinition td, ref bool WeavingFailed) | ||||
|         { | ||||
|             List<FieldDefinition> syncVars = new List<FieldDefinition>(); | ||||
|             Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds = new Dictionary<FieldDefinition, FieldDefinition>(); | ||||
|  | ||||
|             // the mapping of dirtybits to sync-vars is implicit in the order of the fields here. this order is recorded in m_replacementProperties. | ||||
|             // start assigning syncvars at the place the base class stopped, if any | ||||
|             int dirtyBitCounter = syncVarAccessLists.GetSyncVarStart(td.BaseType.FullName); | ||||
|  | ||||
|             // find syncvars | ||||
|             foreach (FieldDefinition fd in td.Fields) | ||||
|             { | ||||
|                 if (fd.HasCustomAttribute<SyncVarAttribute>()) | ||||
|                 { | ||||
|                     if ((fd.Attributes & FieldAttributes.Static) != 0) | ||||
|                     { | ||||
|                         Log.Error($"{fd.Name} cannot be static", fd); | ||||
|                         WeavingFailed = true; | ||||
|                         continue; | ||||
|                     } | ||||
|  | ||||
|                     if (fd.FieldType.IsArray) | ||||
|                     { | ||||
|                         Log.Error($"{fd.Name} has invalid type. Use SyncLists instead of arrays", fd); | ||||
|                         WeavingFailed = true; | ||||
|                         continue; | ||||
|                     } | ||||
|  | ||||
|                     if (SyncObjectInitializer.ImplementsSyncObject(fd.FieldType)) | ||||
|                     { | ||||
|                         Log.Warning($"{fd.Name} has [SyncVar] attribute. SyncLists should not be marked with SyncVar", fd); | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         syncVars.Add(fd); | ||||
|  | ||||
|                         ProcessSyncVar(td, fd, syncVarNetIds, 1L << dirtyBitCounter, ref WeavingFailed); | ||||
|                         dirtyBitCounter += 1; | ||||
|  | ||||
|                         if (dirtyBitCounter > SyncVarLimit) | ||||
|                         { | ||||
|                             Log.Error($"{td.Name} has > {SyncVarLimit} SyncVars. Consider refactoring your class into multiple components", td); | ||||
|                             WeavingFailed = true; | ||||
|                             continue; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // add all the new SyncVar __netId fields | ||||
|             foreach (FieldDefinition fd in syncVarNetIds.Values) | ||||
|             { | ||||
|                 td.Fields.Add(fd); | ||||
|             } | ||||
|             syncVarAccessLists.SetNumSyncVars(td.FullName, syncVars.Count); | ||||
|  | ||||
|             return (syncVars, syncVarNetIds); | ||||
|         } | ||||
|  | ||||
|         public void WriteCallHookMethodUsingArgument(ILProcessor worker, MethodDefinition hookMethod, VariableDefinition oldValue) | ||||
|         { | ||||
|             WriteCallHookMethod(worker, hookMethod, oldValue, null); | ||||
|         } | ||||
|  | ||||
|         public void WriteCallHookMethodUsingField(ILProcessor worker, MethodDefinition hookMethod, VariableDefinition oldValue, FieldDefinition newValue, ref bool WeavingFailed) | ||||
|         { | ||||
|             if (newValue == null) | ||||
|             { | ||||
|                 Log.Error("NewValue field was null when writing SyncVar hook"); | ||||
|                 WeavingFailed = true; | ||||
|             } | ||||
|  | ||||
|             WriteCallHookMethod(worker, hookMethod, oldValue, newValue); | ||||
|         } | ||||
|  | ||||
|         void WriteCallHookMethod(ILProcessor worker, MethodDefinition hookMethod, VariableDefinition oldValue, FieldDefinition newValue) | ||||
|         { | ||||
|             WriteStartFunctionCall(); | ||||
|  | ||||
|             // write args | ||||
|             WriteOldValue(); | ||||
|             WriteNewValue(); | ||||
|  | ||||
|             WriteEndFunctionCall(); | ||||
|  | ||||
|  | ||||
|             // *** Local functions used to write OpCodes *** | ||||
|             // Local functions have access to function variables, no need to pass in args | ||||
|  | ||||
|             void WriteOldValue() | ||||
|             { | ||||
|                 worker.Emit(OpCodes.Ldloc, oldValue); | ||||
|             } | ||||
|  | ||||
|             void WriteNewValue() | ||||
|             { | ||||
|                 // write arg1 or this.field | ||||
|                 if (newValue == null) | ||||
|                 { | ||||
|                     worker.Emit(OpCodes.Ldarg_1); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     // this. | ||||
|                     worker.Emit(OpCodes.Ldarg_0); | ||||
|                     // syncvar.get | ||||
|                     worker.Emit(OpCodes.Ldfld, newValue); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // Writes this before method if it is not static | ||||
|             void WriteStartFunctionCall() | ||||
|             { | ||||
|                 // don't add this (Ldarg_0) if method is static | ||||
|                 if (!hookMethod.IsStatic) | ||||
|                 { | ||||
|                     // this before method call | ||||
|                     // e.g. this.onValueChanged | ||||
|                     worker.Emit(OpCodes.Ldarg_0); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // Calls method | ||||
|             void WriteEndFunctionCall() | ||||
|             { | ||||
|                 // only use Callvirt when not static | ||||
|                 OpCode opcode = hookMethod.IsStatic ? OpCodes.Call : OpCodes.Callvirt; | ||||
|                 worker.Emit(opcode, hookMethod); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,11 @@ | ||||
| fileFormatVersion: 2 | ||||
| guid: f52c39bddd95d42b88f9cd554dfd9198 | ||||
| MonoImporter: | ||||
|   externalObjects: {} | ||||
|   serializedVersion: 2 | ||||
|   defaultReferences: [] | ||||
|   executionOrder: 0 | ||||
|   icon: {instanceID: 0} | ||||
|   userData:  | ||||
|   assetBundleName:  | ||||
|   assetBundleVariant:  | ||||
							
								
								
									
										139
									
								
								Assets/Mirror/Editor/Weaver/Processors/TargetRpcProcessor.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								Assets/Mirror/Editor/Weaver/Processors/TargetRpcProcessor.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,139 @@ | ||||
| using Mono.CecilX; | ||||
| using Mono.CecilX.Cil; | ||||
|  | ||||
| namespace Mirror.Weaver | ||||
| { | ||||
|     // Processes [TargetRpc] methods in NetworkBehaviour | ||||
|     public static class TargetRpcProcessor | ||||
|     { | ||||
|         // helper functions to check if the method has a NetworkConnection parameter | ||||
|         public static bool HasNetworkConnectionParameter(MethodDefinition md) | ||||
|         { | ||||
|             return md.Parameters.Count > 0 && | ||||
|                    md.Parameters[0].ParameterType.Is<NetworkConnection>(); | ||||
|         } | ||||
|  | ||||
|         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; | ||||
|         } | ||||
|  | ||||
|         /* 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.WriteCreateWriter(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; | ||||
|  | ||||
|             string rpcName = md.Name; | ||||
|  | ||||
|             // 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); | ||||
|             } | ||||
|             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, targetRpcAttr.GetField("channel", 0)); | ||||
|             worker.Emit(OpCodes.Callvirt, weaverTypes.sendTargetRpcInternal); | ||||
|  | ||||
|             NetworkBehaviourProcessor.WriteRecycleWriter(worker, weaverTypes); | ||||
|  | ||||
|             worker.Emit(OpCodes.Ret); | ||||
|  | ||||
|             return rpc; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,11 @@ | ||||
| fileFormatVersion: 2 | ||||
| guid: fb3ce6c6f3f2942ae88178b86f5a8282 | ||||
| MonoImporter: | ||||
|   externalObjects: {} | ||||
|   serializedVersion: 2 | ||||
|   defaultReferences: [] | ||||
|   executionOrder: 0 | ||||
|   icon: {instanceID: 0} | ||||
|   userData:  | ||||
|   assetBundleName:  | ||||
|   assetBundleVariant:  | ||||
		Reference in New Issue
	
	Block a user
	 DerTyp187
					DerTyp187