From 698fd2de5b2cd650ddfc7ca9a027964c62ee226e Mon Sep 17 00:00:00 2001 From: xmanning <166966460+xmanning@users.noreply.github.com> Date: Sat, 27 Sep 2025 16:26:57 -0400 Subject: [PATCH 1/8] Secure RpcMessage against SenderClientId spoofing When a server receives an RpcMessage, it should update the SenderClientId to match the SenderId provided by the messaging manager. This is to prevent modified clients from spoofing their SenderClientId as other clients. --- .../Runtime/Messaging/Messages/RpcMessages.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs index 70e4c2aadf..7cff3611c3 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs @@ -196,6 +196,12 @@ public unsafe bool Deserialize(FastBufferReader reader, ref NetworkContext conte public void Handle(ref NetworkContext context) { + var networkManager = (NetworkManager)context.SystemOwner; + if (networkManager.IsServer) + { + SenderClientId = context.SenderId; + } + var rpcParams = new __RpcParams { Ext = new RpcParams From 72f0e82ee14775181cf3cd0d95a84fb1e0bef573 Mon Sep 17 00:00:00 2001 From: xmanning Date: Wed, 1 Oct 2025 23:03:50 -0400 Subject: [PATCH 2/8] Replaced RequireOwnership with a new RpcInvokePermission that is respected from the server for both direct and proxy RPCs. --- .../Editor/CodeGen/NetworkBehaviourILPP.cs | 62 +++++++++++++++++-- .../Runtime/Core/NetworkBehaviour.cs | 12 +++- .../Messaging/Messages/ProxyMessage.cs | 24 ++++++- .../Runtime/Messaging/Messages/RpcMessages.cs | 19 +++++- .../Runtime/Messaging/RpcAttributes.cs | 29 +++++++-- 5 files changed, 130 insertions(+), 16 deletions(-) diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs index 894aaf76d5..83c40ab7de 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs @@ -609,6 +609,7 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly, private const string k_NetworkVariableBase_Initialize = nameof(NetworkVariableBase.Initialize); private const string k_RpcAttribute_Delivery = nameof(RpcAttribute.Delivery); + private const string k_RpcAttribute_InvokePermission = nameof(RpcAttribute.InvokePermission); private const string k_ServerRpcAttribute_RequireOwnership = nameof(ServerRpcAttribute.RequireOwnership); private const string k_RpcParams_Server = nameof(__RpcParams.Server); private const string k_RpcParams_Client = nameof(__RpcParams.Client); @@ -1311,7 +1312,7 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] ass return; } } - var rpcHandlers = new List<(uint RpcMethodId, MethodDefinition RpcHandler, string RpcMethodName)>(); + var rpcHandlers = new List<(uint RpcMethodId, MethodDefinition RpcHandler, string RpcMethodName, CustomAttribute rpcAttribute)>(); bool isEditorOrDevelopment = assemblyDefines.Contains("UNITY_EDITOR") || assemblyDefines.Contains("DEVELOPMENT_BUILD"); @@ -1342,7 +1343,7 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] ass InjectWriteAndCallBlocks(methodDefinition, rpcAttribute, rpcMethodId); - rpcHandlers.Add((rpcMethodId, GenerateStaticHandler(methodDefinition, rpcAttribute, rpcMethodId), methodDefinition.Name)); + rpcHandlers.Add((rpcMethodId, GenerateStaticHandler(methodDefinition, rpcAttribute, rpcMethodId), methodDefinition.Name, rpcAttribute)); } GenerateVariableInitialization(typeDefinition); @@ -1424,7 +1425,7 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] ass var instructions = new List(); var processor = initializeRpcsMethodDef.Body.GetILProcessor(); - foreach (var (rpcMethodId, rpcHandler, rpcMethodName) in rpcHandlers) + foreach (var (rpcMethodId, rpcHandler, rpcMethodName, rpcAttribute) in rpcHandlers) { typeDefinition.Methods.Add(rpcHandler); @@ -1439,12 +1440,25 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] ass callMethod = callMethod.MakeGeneric(genericTypes.ToArray()); } + RpcInvokePermission invokePermission = RpcInvokePermission.Anyone; + + foreach (var attrField in rpcAttribute.Fields) + { + switch (attrField.Name) + { + case k_RpcAttribute_InvokePermission: + invokePermission = (RpcInvokePermission)attrField.Argument.Value; + break; + } + } + // __registerRpc(RpcMethodId, HandleFunc, methodName); instructions.Add(processor.Create(OpCodes.Ldarg_0)); instructions.Add(processor.Create(OpCodes.Ldc_I4, unchecked((int)rpcMethodId))); instructions.Add(processor.Create(OpCodes.Ldnull)); instructions.Add(processor.Create(OpCodes.Ldftn, callMethod)); instructions.Add(processor.Create(OpCodes.Newobj, m_NetworkHandlerDelegateCtor_MethodRef)); + instructions.Add(processor.Create(OpCodes.Ldc_I4, (int)invokePermission)); instructions.Add(processor.Create(OpCodes.Ldstr, rpcMethodName)); instructions.Add(processor.Create(OpCodes.Call, m_NetworkBehaviour___registerRpc_MethodRef)); } @@ -2851,13 +2865,13 @@ private MethodDefinition GenerateStaticHandler(MethodDefinition methodDefinition var isServerRpc = rpcAttribute.AttributeType.FullName == CodeGenHelpers.ServerRpcAttribute_FullName; var isCientRpc = rpcAttribute.AttributeType.FullName == CodeGenHelpers.ClientRpcAttribute_FullName; var isGenericRpc = rpcAttribute.AttributeType.FullName == CodeGenHelpers.RpcAttribute_FullName; - var requireOwnership = true; // default value MUST be == `ServerRpcAttribute.RequireOwnership` + var invokePermission = RpcInvokePermission.Anyone; // default value MUST be == `ServerRpcAttribute.RequireOwnership` foreach (var attrField in rpcAttribute.Fields) { switch (attrField.Name) { case k_ServerRpcAttribute_RequireOwnership: - requireOwnership = attrField.Argument.Type == typeSystem.Boolean && (bool)attrField.Argument.Value; + invokePermission = (attrField.Argument.Type == typeSystem.Boolean && (bool)attrField.Argument.Value) ? RpcInvokePermission.Owner : RpcInvokePermission.Anyone; break; } } @@ -2887,7 +2901,7 @@ private MethodDefinition GenerateStaticHandler(MethodDefinition methodDefinition processor.Append(lastInstr); } - if (isServerRpc && requireOwnership) + if (isServerRpc && invokePermission == RpcInvokePermission.Owner) { var roReturnInstr = processor.Create(OpCodes.Ret); var roLastInstr = processor.Create(OpCodes.Nop); @@ -2921,6 +2935,42 @@ private MethodDefinition GenerateStaticHandler(MethodDefinition methodDefinition processor.Append(logNextInstr); + processor.Append(roReturnInstr); + processor.Append(roLastInstr); + } else if (invokePermission == RpcInvokePermission.Server) + { + var roReturnInstr = processor.Create(OpCodes.Ret); + var roLastInstr = processor.Create(OpCodes.Nop); + + // if (rpcParams.Server.Receive.SenderClientId != NetworkManager.IsServer) { ... } return; + processor.Emit(OpCodes.Ldarg_2); + processor.Emit(OpCodes.Ldfld, m_RpcParams_Server_FieldRef); + processor.Emit(OpCodes.Ldfld, m_ServerRpcParams_Receive_FieldRef); + processor.Emit(OpCodes.Ldfld, m_ServerRpcParams_Receive_SenderClientId_FieldRef); + processor.Emit(OpCodes.Ldarg_0); + processor.Emit(OpCodes.Call, m_NetworkManager_getIsServer_MethodRef); + processor.Emit(OpCodes.Ceq); + processor.Emit(OpCodes.Ldc_I4, 0); + processor.Emit(OpCodes.Ceq); + processor.Emit(OpCodes.Brfalse, roLastInstr); + + var logNextInstr = processor.Create(OpCodes.Nop); + + // if (LogLevel.Normal > networkManager.LogLevel) + processor.Emit(OpCodes.Ldloc, netManLocIdx); + processor.Emit(OpCodes.Ldfld, m_NetworkManager_LogLevel_FieldRef); + processor.Emit(OpCodes.Ldc_I4, (int)LogLevel.Normal); + processor.Emit(OpCodes.Cgt); + processor.Emit(OpCodes.Ldc_I4, 0); + processor.Emit(OpCodes.Ceq); + processor.Emit(OpCodes.Brfalse, logNextInstr); + + // Debug.LogError(...); + processor.Emit(OpCodes.Ldstr, "Only the server can invoke an Rpc with RpcInvokePermission.Server!"); + processor.Emit(OpCodes.Call, m_Debug_LogError_MethodRef); + + processor.Append(logNextInstr); + processor.Append(roReturnInstr); processor.Append(roLastInstr); } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 332694af7d..c3e599d541 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -39,6 +39,7 @@ public abstract class NetworkBehaviour : MonoBehaviour // RuntimeAccessModifiersILPP will make this `public` internal static readonly Dictionary> __rpc_func_table = new Dictionary>(); + internal static readonly Dictionary> __rpc_permission_table = new Dictionary>(); #if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE // RuntimeAccessModifiersILPP will make this `public` @@ -326,12 +327,15 @@ internal FastBufferWriter __beginSendRpc(uint rpcMethodId, RpcParams rpcParams, #pragma warning restore IDE1006 // restore naming rule violation check { if (m_NetworkObject == null && !IsSpawned) - { + { throw new RpcException("The NetworkBehaviour must be spawned before calling this method."); } - if (attributeParams.RequireOwnership && !IsOwner) + if (attributeParams.InvokePermission == RpcInvokePermission.Owner && !IsOwner) { throw new RpcException("This RPC can only be sent by its owner."); + } else if (attributeParams.InvokePermission == RpcInvokePermission.Server && !IsServer) + { + throw new RpcException("This RPC can only be sent by the server."); } return new FastBufferWriter(k_RpcMessageDefaultSize, Allocator.Temp, k_RpcMessageMaximumSize); } @@ -950,10 +954,11 @@ internal virtual void __initializeRpcs() #pragma warning disable IDE1006 // disable naming rule violation check // RuntimeAccessModifiersILPP will make this `protected` - internal void __registerRpc(uint hash, RpcReceiveHandler handler, string rpcMethodName) + internal void __registerRpc(uint hash, RpcReceiveHandler handler, RpcInvokePermission permission, string rpcMethodName) #pragma warning restore IDE1006 // restore naming rule violation check { __rpc_func_table[GetType()][hash] = handler; + __rpc_permission_table[GetType()][hash] = permission; #if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE __rpc_name_table[GetType()][hash] = rpcMethodName; #endif @@ -1000,6 +1005,7 @@ internal void InitializeVariables() if (!__rpc_func_table.ContainsKey(GetType())) { __rpc_func_table[GetType()] = new Dictionary(); + __rpc_permission_table[GetType()] = new Dictionary(); #if UNITY_EDITOR || DEVELOPMENT_BUILD || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE __rpc_name_table[GetType()] = new Dictionary(); #endif diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ProxyMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ProxyMessage.cs index 57c8345175..2b7346c062 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ProxyMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ProxyMessage.cs @@ -41,9 +41,31 @@ public unsafe void Handle(ref NetworkContext context) } return; } - var observers = networkObject.Observers; + var networkBehaviour = networkObject.GetNetworkBehaviourAtOrderIndex(WrappedMessage.Metadata.NetworkBehaviourId); + + RpcInvokePermission permission = NetworkBehaviour.__rpc_permission_table[networkBehaviour.GetType()][WrappedMessage.Metadata.NetworkRpcMethodId]; + bool hasPermission = permission switch + { + RpcInvokePermission.Anyone => true, + RpcInvokePermission.Server => context.SenderId == networkManager.LocalClientId, + RpcInvokePermission.Owner => context.SenderId == networkBehaviour.OwnerClientId, + _ => false, + }; + + // Do not handle the message if the sender does not have permission to do so. + if (!hasPermission) + { + return; + } + + if (networkManager.IsServer) + { + WrappedMessage.SenderClientId = context.SenderId; + } + + var nonServerIds = new NativeList(Allocator.Temp); for (var i = 0; i < TargetClientIds.Length; ++i) { diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs index 7cff3611c3..2b74f13186 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs @@ -66,12 +66,29 @@ public static void Handle(ref NetworkContext context, ref RpcMetadata metadata, { NetworkLog.LogWarning($"[{metadata.NetworkObjectId}, {metadata.NetworkBehaviourId}, {metadata.NetworkRpcMethodId}] An RPC called on a {nameof(NetworkObject)} that is not in the spawned objects list. Please make sure the {nameof(NetworkObject)} is spawned before calling RPCs."); } - return; } var networkBehaviour = networkObject.GetNetworkBehaviourAtOrderIndex(metadata.NetworkBehaviourId); try { + Type type = networkBehaviour.GetType(); + if (networkManager.IsServer) + { + RpcInvokePermission permission = NetworkBehaviour.__rpc_permission_table[networkBehaviour.GetType()][metadata.NetworkRpcMethodId]; + bool hasPermission = permission switch + { + RpcInvokePermission.Anyone => true, + RpcInvokePermission.Server => context.SenderId == networkManager.LocalClientId, + RpcInvokePermission.Owner => context.SenderId == networkBehaviour.OwnerClientId, + _ => false, + }; + + // Do not handle the message if the sender does not have permission to do so. + if (!hasPermission) + { + return; + } + } NetworkBehaviour.__rpc_func_table[networkBehaviour.GetType()][metadata.NetworkRpcMethodId](networkBehaviour, payload, rpcParams); } catch (Exception ex) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcAttributes.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcAttributes.cs index 6d276ab580..1e9b8c634e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcAttributes.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcAttributes.cs @@ -18,6 +18,25 @@ public enum RpcDelivery Unreliable } + /// + /// RPC invoke permissions + /// + public enum RpcInvokePermission + { + /// + /// Anyone can invoke the Rpc. + /// + Anyone = 0, + /// + /// Rpc can only be invoked by the server. + /// + Server, + /// + /// Rpc can only be invoked by the owner of the NetworkBehaviour. + /// + Owner, + } + /// /// Represents the common base class for Rpc attributes. /// @@ -35,9 +54,9 @@ public struct RpcAttributeParams public RpcDelivery Delivery; /// - /// When true, only the owner of the object can execute this RPC + /// Who has network permission to invoke this RPC /// - public bool RequireOwnership; + public RpcInvokePermission InvokePermission; /// /// When true, local execution of the RPC is deferred until the next network tick @@ -57,9 +76,9 @@ public struct RpcAttributeParams public RpcDelivery Delivery = RpcDelivery.Reliable; /// - /// When true, only the owner of the object can execute this RPC + /// Who has network permission to invoke this RPC /// - public bool RequireOwnership; + public RpcInvokePermission InvokePermission; /// /// When true, local execution of the RPC is deferred until the next network tick @@ -97,7 +116,7 @@ public class ServerRpcAttribute : RpcAttribute /// When true, only the owner of the NetworkObject can invoke this ServerRpc. /// This property overrides the base RpcAttribute.RequireOwnership. /// - public new bool RequireOwnership; + public bool RequireOwnership; /// /// Initializes a new instance of ServerRpcAttribute that targets the server From a0ade5b055d2535d866930d35e9bcd8f1cbe8c27 Mon Sep 17 00:00:00 2001 From: xmanning Date: Wed, 1 Oct 2025 23:03:50 -0400 Subject: [PATCH 3/8] Replaced RequireOwnership with a new RpcInvokePermission that is respected from the server for both direct and proxy RPCs. --- .../Editor/CodeGen/NetworkBehaviourILPP.cs | 62 +++++++++++++++++-- .../Runtime/Core/NetworkBehaviour.cs | 12 +++- .../Messaging/Messages/ProxyMessage.cs | 24 ++++++- .../Runtime/Messaging/Messages/RpcMessages.cs | 19 +++++- .../Runtime/Messaging/RpcAttributes.cs | 29 +++++++-- 5 files changed, 130 insertions(+), 16 deletions(-) diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs index 894aaf76d5..83c40ab7de 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs @@ -609,6 +609,7 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly, private const string k_NetworkVariableBase_Initialize = nameof(NetworkVariableBase.Initialize); private const string k_RpcAttribute_Delivery = nameof(RpcAttribute.Delivery); + private const string k_RpcAttribute_InvokePermission = nameof(RpcAttribute.InvokePermission); private const string k_ServerRpcAttribute_RequireOwnership = nameof(ServerRpcAttribute.RequireOwnership); private const string k_RpcParams_Server = nameof(__RpcParams.Server); private const string k_RpcParams_Client = nameof(__RpcParams.Client); @@ -1311,7 +1312,7 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] ass return; } } - var rpcHandlers = new List<(uint RpcMethodId, MethodDefinition RpcHandler, string RpcMethodName)>(); + var rpcHandlers = new List<(uint RpcMethodId, MethodDefinition RpcHandler, string RpcMethodName, CustomAttribute rpcAttribute)>(); bool isEditorOrDevelopment = assemblyDefines.Contains("UNITY_EDITOR") || assemblyDefines.Contains("DEVELOPMENT_BUILD"); @@ -1342,7 +1343,7 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] ass InjectWriteAndCallBlocks(methodDefinition, rpcAttribute, rpcMethodId); - rpcHandlers.Add((rpcMethodId, GenerateStaticHandler(methodDefinition, rpcAttribute, rpcMethodId), methodDefinition.Name)); + rpcHandlers.Add((rpcMethodId, GenerateStaticHandler(methodDefinition, rpcAttribute, rpcMethodId), methodDefinition.Name, rpcAttribute)); } GenerateVariableInitialization(typeDefinition); @@ -1424,7 +1425,7 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] ass var instructions = new List(); var processor = initializeRpcsMethodDef.Body.GetILProcessor(); - foreach (var (rpcMethodId, rpcHandler, rpcMethodName) in rpcHandlers) + foreach (var (rpcMethodId, rpcHandler, rpcMethodName, rpcAttribute) in rpcHandlers) { typeDefinition.Methods.Add(rpcHandler); @@ -1439,12 +1440,25 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] ass callMethod = callMethod.MakeGeneric(genericTypes.ToArray()); } + RpcInvokePermission invokePermission = RpcInvokePermission.Anyone; + + foreach (var attrField in rpcAttribute.Fields) + { + switch (attrField.Name) + { + case k_RpcAttribute_InvokePermission: + invokePermission = (RpcInvokePermission)attrField.Argument.Value; + break; + } + } + // __registerRpc(RpcMethodId, HandleFunc, methodName); instructions.Add(processor.Create(OpCodes.Ldarg_0)); instructions.Add(processor.Create(OpCodes.Ldc_I4, unchecked((int)rpcMethodId))); instructions.Add(processor.Create(OpCodes.Ldnull)); instructions.Add(processor.Create(OpCodes.Ldftn, callMethod)); instructions.Add(processor.Create(OpCodes.Newobj, m_NetworkHandlerDelegateCtor_MethodRef)); + instructions.Add(processor.Create(OpCodes.Ldc_I4, (int)invokePermission)); instructions.Add(processor.Create(OpCodes.Ldstr, rpcMethodName)); instructions.Add(processor.Create(OpCodes.Call, m_NetworkBehaviour___registerRpc_MethodRef)); } @@ -2851,13 +2865,13 @@ private MethodDefinition GenerateStaticHandler(MethodDefinition methodDefinition var isServerRpc = rpcAttribute.AttributeType.FullName == CodeGenHelpers.ServerRpcAttribute_FullName; var isCientRpc = rpcAttribute.AttributeType.FullName == CodeGenHelpers.ClientRpcAttribute_FullName; var isGenericRpc = rpcAttribute.AttributeType.FullName == CodeGenHelpers.RpcAttribute_FullName; - var requireOwnership = true; // default value MUST be == `ServerRpcAttribute.RequireOwnership` + var invokePermission = RpcInvokePermission.Anyone; // default value MUST be == `ServerRpcAttribute.RequireOwnership` foreach (var attrField in rpcAttribute.Fields) { switch (attrField.Name) { case k_ServerRpcAttribute_RequireOwnership: - requireOwnership = attrField.Argument.Type == typeSystem.Boolean && (bool)attrField.Argument.Value; + invokePermission = (attrField.Argument.Type == typeSystem.Boolean && (bool)attrField.Argument.Value) ? RpcInvokePermission.Owner : RpcInvokePermission.Anyone; break; } } @@ -2887,7 +2901,7 @@ private MethodDefinition GenerateStaticHandler(MethodDefinition methodDefinition processor.Append(lastInstr); } - if (isServerRpc && requireOwnership) + if (isServerRpc && invokePermission == RpcInvokePermission.Owner) { var roReturnInstr = processor.Create(OpCodes.Ret); var roLastInstr = processor.Create(OpCodes.Nop); @@ -2921,6 +2935,42 @@ private MethodDefinition GenerateStaticHandler(MethodDefinition methodDefinition processor.Append(logNextInstr); + processor.Append(roReturnInstr); + processor.Append(roLastInstr); + } else if (invokePermission == RpcInvokePermission.Server) + { + var roReturnInstr = processor.Create(OpCodes.Ret); + var roLastInstr = processor.Create(OpCodes.Nop); + + // if (rpcParams.Server.Receive.SenderClientId != NetworkManager.IsServer) { ... } return; + processor.Emit(OpCodes.Ldarg_2); + processor.Emit(OpCodes.Ldfld, m_RpcParams_Server_FieldRef); + processor.Emit(OpCodes.Ldfld, m_ServerRpcParams_Receive_FieldRef); + processor.Emit(OpCodes.Ldfld, m_ServerRpcParams_Receive_SenderClientId_FieldRef); + processor.Emit(OpCodes.Ldarg_0); + processor.Emit(OpCodes.Call, m_NetworkManager_getIsServer_MethodRef); + processor.Emit(OpCodes.Ceq); + processor.Emit(OpCodes.Ldc_I4, 0); + processor.Emit(OpCodes.Ceq); + processor.Emit(OpCodes.Brfalse, roLastInstr); + + var logNextInstr = processor.Create(OpCodes.Nop); + + // if (LogLevel.Normal > networkManager.LogLevel) + processor.Emit(OpCodes.Ldloc, netManLocIdx); + processor.Emit(OpCodes.Ldfld, m_NetworkManager_LogLevel_FieldRef); + processor.Emit(OpCodes.Ldc_I4, (int)LogLevel.Normal); + processor.Emit(OpCodes.Cgt); + processor.Emit(OpCodes.Ldc_I4, 0); + processor.Emit(OpCodes.Ceq); + processor.Emit(OpCodes.Brfalse, logNextInstr); + + // Debug.LogError(...); + processor.Emit(OpCodes.Ldstr, "Only the server can invoke an Rpc with RpcInvokePermission.Server!"); + processor.Emit(OpCodes.Call, m_Debug_LogError_MethodRef); + + processor.Append(logNextInstr); + processor.Append(roReturnInstr); processor.Append(roLastInstr); } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 332694af7d..c3e599d541 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -39,6 +39,7 @@ public abstract class NetworkBehaviour : MonoBehaviour // RuntimeAccessModifiersILPP will make this `public` internal static readonly Dictionary> __rpc_func_table = new Dictionary>(); + internal static readonly Dictionary> __rpc_permission_table = new Dictionary>(); #if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE // RuntimeAccessModifiersILPP will make this `public` @@ -326,12 +327,15 @@ internal FastBufferWriter __beginSendRpc(uint rpcMethodId, RpcParams rpcParams, #pragma warning restore IDE1006 // restore naming rule violation check { if (m_NetworkObject == null && !IsSpawned) - { + { throw new RpcException("The NetworkBehaviour must be spawned before calling this method."); } - if (attributeParams.RequireOwnership && !IsOwner) + if (attributeParams.InvokePermission == RpcInvokePermission.Owner && !IsOwner) { throw new RpcException("This RPC can only be sent by its owner."); + } else if (attributeParams.InvokePermission == RpcInvokePermission.Server && !IsServer) + { + throw new RpcException("This RPC can only be sent by the server."); } return new FastBufferWriter(k_RpcMessageDefaultSize, Allocator.Temp, k_RpcMessageMaximumSize); } @@ -950,10 +954,11 @@ internal virtual void __initializeRpcs() #pragma warning disable IDE1006 // disable naming rule violation check // RuntimeAccessModifiersILPP will make this `protected` - internal void __registerRpc(uint hash, RpcReceiveHandler handler, string rpcMethodName) + internal void __registerRpc(uint hash, RpcReceiveHandler handler, RpcInvokePermission permission, string rpcMethodName) #pragma warning restore IDE1006 // restore naming rule violation check { __rpc_func_table[GetType()][hash] = handler; + __rpc_permission_table[GetType()][hash] = permission; #if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE __rpc_name_table[GetType()][hash] = rpcMethodName; #endif @@ -1000,6 +1005,7 @@ internal void InitializeVariables() if (!__rpc_func_table.ContainsKey(GetType())) { __rpc_func_table[GetType()] = new Dictionary(); + __rpc_permission_table[GetType()] = new Dictionary(); #if UNITY_EDITOR || DEVELOPMENT_BUILD || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE __rpc_name_table[GetType()] = new Dictionary(); #endif diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ProxyMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ProxyMessage.cs index 57c8345175..2b7346c062 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ProxyMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ProxyMessage.cs @@ -41,9 +41,31 @@ public unsafe void Handle(ref NetworkContext context) } return; } - var observers = networkObject.Observers; + var networkBehaviour = networkObject.GetNetworkBehaviourAtOrderIndex(WrappedMessage.Metadata.NetworkBehaviourId); + + RpcInvokePermission permission = NetworkBehaviour.__rpc_permission_table[networkBehaviour.GetType()][WrappedMessage.Metadata.NetworkRpcMethodId]; + bool hasPermission = permission switch + { + RpcInvokePermission.Anyone => true, + RpcInvokePermission.Server => context.SenderId == networkManager.LocalClientId, + RpcInvokePermission.Owner => context.SenderId == networkBehaviour.OwnerClientId, + _ => false, + }; + + // Do not handle the message if the sender does not have permission to do so. + if (!hasPermission) + { + return; + } + + if (networkManager.IsServer) + { + WrappedMessage.SenderClientId = context.SenderId; + } + + var nonServerIds = new NativeList(Allocator.Temp); for (var i = 0; i < TargetClientIds.Length; ++i) { diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs index 7cff3611c3..2b74f13186 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs @@ -66,12 +66,29 @@ public static void Handle(ref NetworkContext context, ref RpcMetadata metadata, { NetworkLog.LogWarning($"[{metadata.NetworkObjectId}, {metadata.NetworkBehaviourId}, {metadata.NetworkRpcMethodId}] An RPC called on a {nameof(NetworkObject)} that is not in the spawned objects list. Please make sure the {nameof(NetworkObject)} is spawned before calling RPCs."); } - return; } var networkBehaviour = networkObject.GetNetworkBehaviourAtOrderIndex(metadata.NetworkBehaviourId); try { + Type type = networkBehaviour.GetType(); + if (networkManager.IsServer) + { + RpcInvokePermission permission = NetworkBehaviour.__rpc_permission_table[networkBehaviour.GetType()][metadata.NetworkRpcMethodId]; + bool hasPermission = permission switch + { + RpcInvokePermission.Anyone => true, + RpcInvokePermission.Server => context.SenderId == networkManager.LocalClientId, + RpcInvokePermission.Owner => context.SenderId == networkBehaviour.OwnerClientId, + _ => false, + }; + + // Do not handle the message if the sender does not have permission to do so. + if (!hasPermission) + { + return; + } + } NetworkBehaviour.__rpc_func_table[networkBehaviour.GetType()][metadata.NetworkRpcMethodId](networkBehaviour, payload, rpcParams); } catch (Exception ex) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcAttributes.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcAttributes.cs index 6d276ab580..1e9b8c634e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcAttributes.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcAttributes.cs @@ -18,6 +18,25 @@ public enum RpcDelivery Unreliable } + /// + /// RPC invoke permissions + /// + public enum RpcInvokePermission + { + /// + /// Anyone can invoke the Rpc. + /// + Anyone = 0, + /// + /// Rpc can only be invoked by the server. + /// + Server, + /// + /// Rpc can only be invoked by the owner of the NetworkBehaviour. + /// + Owner, + } + /// /// Represents the common base class for Rpc attributes. /// @@ -35,9 +54,9 @@ public struct RpcAttributeParams public RpcDelivery Delivery; /// - /// When true, only the owner of the object can execute this RPC + /// Who has network permission to invoke this RPC /// - public bool RequireOwnership; + public RpcInvokePermission InvokePermission; /// /// When true, local execution of the RPC is deferred until the next network tick @@ -57,9 +76,9 @@ public struct RpcAttributeParams public RpcDelivery Delivery = RpcDelivery.Reliable; /// - /// When true, only the owner of the object can execute this RPC + /// Who has network permission to invoke this RPC /// - public bool RequireOwnership; + public RpcInvokePermission InvokePermission; /// /// When true, local execution of the RPC is deferred until the next network tick @@ -97,7 +116,7 @@ public class ServerRpcAttribute : RpcAttribute /// When true, only the owner of the NetworkObject can invoke this ServerRpc. /// This property overrides the base RpcAttribute.RequireOwnership. /// - public new bool RequireOwnership; + public bool RequireOwnership; /// /// Initializes a new instance of ServerRpcAttribute that targets the server From fcfc662209e8cf0fb411451f7585cc6b995d868e Mon Sep 17 00:00:00 2001 From: xmanning Date: Thu, 2 Oct 2025 01:15:44 -0400 Subject: [PATCH 4/8] Add missing switch case --- .../Editor/CodeGen/NetworkBehaviourILPP.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs index 83c40ab7de..a5449cdf40 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs @@ -2865,7 +2865,7 @@ private MethodDefinition GenerateStaticHandler(MethodDefinition methodDefinition var isServerRpc = rpcAttribute.AttributeType.FullName == CodeGenHelpers.ServerRpcAttribute_FullName; var isCientRpc = rpcAttribute.AttributeType.FullName == CodeGenHelpers.ClientRpcAttribute_FullName; var isGenericRpc = rpcAttribute.AttributeType.FullName == CodeGenHelpers.RpcAttribute_FullName; - var invokePermission = RpcInvokePermission.Anyone; // default value MUST be == `ServerRpcAttribute.RequireOwnership` + var invokePermission = RpcInvokePermission.Anyone; foreach (var attrField in rpcAttribute.Fields) { switch (attrField.Name) @@ -2873,6 +2873,9 @@ private MethodDefinition GenerateStaticHandler(MethodDefinition methodDefinition case k_ServerRpcAttribute_RequireOwnership: invokePermission = (attrField.Argument.Type == typeSystem.Boolean && (bool)attrField.Argument.Value) ? RpcInvokePermission.Owner : RpcInvokePermission.Anyone; break; + case k_RpcAttribute_InvokePermission: + invokePermission = (RpcInvokePermission)attrField.Argument.Value; + break; } } From 553f8055080b2373517e7f5763ef72f7872fca00 Mon Sep 17 00:00:00 2001 From: xmanning Date: Thu, 2 Oct 2025 01:28:12 -0400 Subject: [PATCH 5/8] Another missing switch case --- .../Editor/CodeGen/NetworkBehaviourILPP.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs index a5449cdf40..2cd6e220a0 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs @@ -1440,12 +1440,14 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] ass callMethod = callMethod.MakeGeneric(genericTypes.ToArray()); } - RpcInvokePermission invokePermission = RpcInvokePermission.Anyone; - + var invokePermission = RpcInvokePermission.Anyone; foreach (var attrField in rpcAttribute.Fields) { switch (attrField.Name) { + case k_ServerRpcAttribute_RequireOwnership: + invokePermission = (attrField.Argument.Type == rpcHandler.Module.TypeSystem.Boolean && (bool)attrField.Argument.Value) ? RpcInvokePermission.Owner : RpcInvokePermission.Anyone; + break; case k_RpcAttribute_InvokePermission: invokePermission = (RpcInvokePermission)attrField.Argument.Value; break; From 82a80dbc5f725f32b144f9a846c7aea9714636eb Mon Sep 17 00:00:00 2001 From: xmanning Date: Thu, 2 Oct 2025 15:58:32 -0400 Subject: [PATCH 6/8] Restore and deprecate RequireOwnership in favor of RpcInvokePermission --- .../Editor/CodeGen/NetworkBehaviourILPP.cs | 27 +++++++++++++++++++ .../Runtime/Core/NetworkBehaviour.cs | 9 ++++--- .../Runtime/Messaging/RpcAttributes.cs | 15 +++++++++-- 3 files changed, 45 insertions(+), 6 deletions(-) diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs index 2cd6e220a0..1f019e4264 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs @@ -1441,6 +1441,7 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] ass } var invokePermission = RpcInvokePermission.Anyone; + foreach (var attrField in rpcAttribute.Fields) { switch (attrField.Name) @@ -1537,6 +1538,7 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] ass private CustomAttribute CheckAndGetRpcAttribute(MethodDefinition methodDefinition) { CustomAttribute rpcAttribute = null; + foreach (var customAttribute in methodDefinition.CustomAttributes) { var customAttributeType_FullName = customAttribute.AttributeType.FullName; @@ -1620,6 +1622,30 @@ private CustomAttribute CheckAndGetRpcAttribute(MethodDefinition methodDefinitio return null; } + + bool hasRequireOwnership = false, hasInvokePermission = false; + + foreach (var argument in rpcAttribute.Fields) + { + switch (argument.Name) + { + case k_ServerRpcAttribute_RequireOwnership: + hasRequireOwnership = true; + break; + case k_RpcAttribute_InvokePermission: + hasInvokePermission = true; + break; + default: + break; + } + } + + if (hasRequireOwnership && hasInvokePermission) + { + m_Diagnostics.AddError("Rpc attribute cannot declare both RequireOwnership and InvokePermission!"); + return null; + } + // Checks for IsSerializable are moved to later as the check is now done by dynamically seeing if any valid // serializer OR extension method exists for it. return rpcAttribute; @@ -2366,6 +2392,7 @@ private void InjectWriteAndCallBlocks(MethodDefinition methodDefinition, CustomA m_Diagnostics.AddError($"{nameof(RpcAttribute)} contains field {field} which is not present in {nameof(RpcAttribute.RpcAttributeParams)}."); } } + instructions.Add(processor.Create(OpCodes.Ldloc, rpcAttributeParamsIdx)); // defaultTarget diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index c3e599d541..8a3cb7d2b2 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -330,13 +330,14 @@ internal FastBufferWriter __beginSendRpc(uint rpcMethodId, RpcParams rpcParams, { throw new RpcException("The NetworkBehaviour must be spawned before calling this method."); } - if (attributeParams.InvokePermission == RpcInvokePermission.Owner && !IsOwner) - { - throw new RpcException("This RPC can only be sent by its owner."); - } else if (attributeParams.InvokePermission == RpcInvokePermission.Server && !IsServer) + else if (attributeParams.InvokePermission == RpcInvokePermission.Server && !IsServer) { throw new RpcException("This RPC can only be sent by the server."); } + else if ((attributeParams.RequireOwnership || attributeParams.InvokePermission == RpcInvokePermission.Owner) && !IsOwner) + { + throw new RpcException("This RPC can only be sent by its owner."); + } return new FastBufferWriter(k_RpcMessageDefaultSize, Allocator.Temp, k_RpcMessageMaximumSize); } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcAttributes.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcAttributes.cs index 1e9b8c634e..bd4af49b89 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcAttributes.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcAttributes.cs @@ -67,6 +67,8 @@ public struct RpcAttributeParams /// When true, allows the RPC target to be overridden at runtime /// public bool AllowTargetOverride; + + public bool RequireOwnership; } // Must match the fields in RemoteAttributeParams @@ -80,6 +82,15 @@ public struct RpcAttributeParams /// public RpcInvokePermission InvokePermission; + /// + /// When true, only the owner of the object can execute this RPC + /// + /// + /// Deprecated in favor of . + /// + [Obsolete] + public bool RequireOwnership; + /// /// When true, local execution of the RPC is deferred until the next network tick /// @@ -116,7 +127,7 @@ public class ServerRpcAttribute : RpcAttribute /// When true, only the owner of the NetworkObject can invoke this ServerRpc. /// This property overrides the base RpcAttribute.RequireOwnership. /// - public bool RequireOwnership; + public new bool RequireOwnership; /// /// Initializes a new instance of ServerRpcAttribute that targets the server @@ -139,7 +150,7 @@ public class ClientRpcAttribute : RpcAttribute /// public ClientRpcAttribute() : base(SendTo.NotServer) { - + InvokePermission = RpcInvokePermission.Server; } } } From d4f3fec22c9a5fc6fda77bab992a2cf542388bf8 Mon Sep 17 00:00:00 2001 From: xmanning Date: Thu, 2 Oct 2025 18:05:36 -0400 Subject: [PATCH 7/8] Only do permission validations on server context.SenderId will be the server for a client receiving the proxied message, so the checks will not be accurate. The checks are also unnecessary considering the checks have already been done by the server. --- .../Messaging/Messages/ProxyMessage.cs | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ProxyMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ProxyMessage.cs index 2b7346c062..d4b354f32f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ProxyMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ProxyMessage.cs @@ -43,25 +43,26 @@ public unsafe void Handle(ref NetworkContext context) } var observers = networkObject.Observers; - var networkBehaviour = networkObject.GetNetworkBehaviourAtOrderIndex(WrappedMessage.Metadata.NetworkBehaviourId); - - RpcInvokePermission permission = NetworkBehaviour.__rpc_permission_table[networkBehaviour.GetType()][WrappedMessage.Metadata.NetworkRpcMethodId]; - bool hasPermission = permission switch + // Validate message if server + if (networkManager.IsServer) { - RpcInvokePermission.Anyone => true, - RpcInvokePermission.Server => context.SenderId == networkManager.LocalClientId, - RpcInvokePermission.Owner => context.SenderId == networkBehaviour.OwnerClientId, - _ => false, - }; + var networkBehaviour = networkObject.GetNetworkBehaviourAtOrderIndex(WrappedMessage.Metadata.NetworkBehaviourId); - // Do not handle the message if the sender does not have permission to do so. - if (!hasPermission) - { - return; - } + RpcInvokePermission permission = NetworkBehaviour.__rpc_permission_table[networkBehaviour.GetType()][WrappedMessage.Metadata.NetworkRpcMethodId]; + bool hasPermission = permission switch + { + RpcInvokePermission.Anyone => true, + RpcInvokePermission.Server => context.SenderId == networkManager.LocalClientId, + RpcInvokePermission.Owner => context.SenderId == networkBehaviour.OwnerClientId, + _ => false, + }; + + // Do not handle the message if the sender does not have permission to do so. + if (!hasPermission) + { + return; + } - if (networkManager.IsServer) - { WrappedMessage.SenderClientId = context.SenderId; } From 087a2f7415d1d570ddd824166a78d51ddd9a5ff9 Mon Sep 17 00:00:00 2001 From: xmanning Date: Tue, 7 Oct 2025 14:46:01 -0400 Subject: [PATCH 8/8] ClientRpc properly assigned InvokePermission Assigning ClientRpc.InvokePermission in the constructor was incorrect. Since we can check if it is a ClientRpc easily, I just do that in ILPP --- .../Editor/CodeGen/NetworkBehaviourILPP.cs | 17 +++++++++++++++-- .../Runtime/Messaging/RpcAttributes.cs | 1 - 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs index 1f019e4264..dae04c5ab3 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs @@ -1440,6 +1440,8 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] ass callMethod = callMethod.MakeGeneric(genericTypes.ToArray()); } + var isClientRpc = rpcAttribute.AttributeType.FullName == CodeGenHelpers.ClientRpcAttribute_FullName; + var invokePermission = RpcInvokePermission.Anyone; foreach (var attrField in rpcAttribute.Fields) @@ -1455,7 +1457,12 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] ass } } - // __registerRpc(RpcMethodId, HandleFunc, methodName); + if (isClientRpc) + { + invokePermission = RpcInvokePermission.Server; + } + + // __registerRpc(RpcMethodId, HandleFunc, invokePermission, methodName); instructions.Add(processor.Create(OpCodes.Ldarg_0)); instructions.Add(processor.Create(OpCodes.Ldc_I4, unchecked((int)rpcMethodId))); instructions.Add(processor.Create(OpCodes.Ldnull)); @@ -2892,7 +2899,7 @@ private MethodDefinition GenerateStaticHandler(MethodDefinition methodDefinition var processor = rpcHandler.Body.GetILProcessor(); var isServerRpc = rpcAttribute.AttributeType.FullName == CodeGenHelpers.ServerRpcAttribute_FullName; - var isCientRpc = rpcAttribute.AttributeType.FullName == CodeGenHelpers.ClientRpcAttribute_FullName; + var isClientRpc = rpcAttribute.AttributeType.FullName == CodeGenHelpers.ClientRpcAttribute_FullName; var isGenericRpc = rpcAttribute.AttributeType.FullName == CodeGenHelpers.RpcAttribute_FullName; var invokePermission = RpcInvokePermission.Anyone; foreach (var attrField in rpcAttribute.Fields) @@ -2908,6 +2915,12 @@ private MethodDefinition GenerateStaticHandler(MethodDefinition methodDefinition } } + // legacy ClientRpc should always be RpcInvokePermission.Server + if (isClientRpc) + { + invokePermission = RpcInvokePermission.Server; + } + rpcHandler.Body.InitLocals = true; // NetworkManager networkManager; rpcHandler.Body.Variables.Add(new VariableDefinition(m_NetworkManager_TypeRef)); diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcAttributes.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcAttributes.cs index bd4af49b89..fffb8a831b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcAttributes.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcAttributes.cs @@ -150,7 +150,6 @@ public class ClientRpcAttribute : RpcAttribute /// public ClientRpcAttribute() : base(SendTo.NotServer) { - InvokePermission = RpcInvokePermission.Server; } } }