// For a function like // [ClientRpc] void RpcTest(int value), // Weaver substitutes the method and moves the code to a new method: // UserCode_RpcTest(int value) <- contains original code // RpcTest(int value) <- serializes parameters, sends the message // // FixRemoteCallToBaseMethod replaces all calls to // RpcTest(value) // with // UserCode_RpcTest(value) 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) { // is this instruction a Call to a method? // if yes, output the method so we can check it. if (IsCallToMethod(instruction, out MethodDefinition calledMethod)) { // when considering if 'calledMethod' is a call to 'method', // we originally compared .Name. // // to fix IL2CPP build bugs with overloaded Rpcs, we need to // generated rpc names like // RpcTest(string value) => RpcTestString(strig value) // RpcTest(int value) => RpcTestInt(int value) // to make them unique. // // calledMethod.Name is still "RpcTest", so we need to // convert this to the generated name as well before comparing. string calledMethodName_Generated = Weaver.GenerateMethodName("", calledMethod); if (calledMethodName_Generated == 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; } } } }
public static MethodDefinition ProcessTargetRpcInvoke(WeaverTypes weaverTypes, Readers readers, Logger Log, TypeDefinition td, MethodDefinition md, MethodDefinition rpcCallFunc, ref bool WeavingFailed) { string trgName = Weaver.GenerateMethodName(Weaver.InvokeRpcPrefix, md); MethodDefinition rpc = new MethodDefinition(trgName, MethodAttributes.Family | MethodAttributes.Static | MethodAttributes.HideBySig, weaverTypes.Import(typeof(void))); ILProcessor worker = rpc.Body.GetILProcessor(); Instruction label = worker.Create(OpCodes.Nop); NetworkBehaviourProcessor.WriteClientActiveCheck(worker, weaverTypes, md.Name, label, "TargetRPC"); // setup for reader worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Castclass, td); // NetworkConnection parameter is optional if (HasNetworkConnectionParameter(md)) { // on server, the NetworkConnection parameter is a connection to client. // when the rpc is invoked on the client, it still has the same // function signature. we pass in the connection to server, // which is cleaner than just passing null) //NetworkClient.readyconnection // // TODO // a) .connectionToServer = best solution. no doubt. // b) NetworkClient.connection for now. add TODO to not use static later. worker.Emit(OpCodes.Call, weaverTypes.NetworkClientConnectionReference); } // process reader parameters and skip first one if first one is NetworkConnection if (!NetworkBehaviourProcessor.ReadArguments(md, readers, Log, worker, RemoteCallType.TargetRpc, ref WeavingFailed)) { return(null); } // invoke actual command function worker.Emit(OpCodes.Callvirt, rpcCallFunc); worker.Emit(OpCodes.Ret); NetworkBehaviourProcessor.AddInvokeParameters(weaverTypes, rpc.Parameters); td.Methods.Add(rpc); return(rpc); }
// For a function like // [ClientRpc] void RpcTest(int value), // Weaver substitutes the method and moves the code to a new method: // UserCode_RpcTest(int value) <- contains original code // RpcTest(int value) <- serializes parameters, sends the message // // Note that all the calls to the method remain untouched. // FixRemoteCallToBaseMethod replaces them afterwards. public static MethodDefinition SubstituteMethod(Logger Log, TypeDefinition td, MethodDefinition md, ref bool WeavingFailed) { string newName = Weaver.GenerateMethodName(RpcPrefix, md); 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); }