Beispiel #1
0
        // 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;
                    }
                }
            }
        }
Beispiel #2
0
        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);
        }
Beispiel #3
0
        // 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);
        }