diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs index 5396087381..6dfaf90168 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); @@ -1435,12 +1436,35 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] ass callMethod = callMethod.MakeGeneric(genericTypes.ToArray()); } - // __registerRpc(RpcMethodId, HandleFunc, methodName); + var isClientRpc = rpcAttribute.AttributeType.FullName == CodeGenHelpers.ClientRpcAttribute_FullName; + + 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; + } + } + + 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)); 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)); } @@ -1517,6 +1541,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; @@ -1600,6 +1625,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; @@ -2346,6 +2395,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 @@ -2845,19 +2895,28 @@ 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 requireOwnership = true; // default value MUST be == `ServerRpcAttribute.RequireOwnership` + var invokePermission = RpcInvokePermission.Anyone; 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; + case k_RpcAttribute_InvokePermission: + invokePermission = (RpcInvokePermission)attrField.Argument.Value; break; } } + // 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)); @@ -2883,7 +2942,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); @@ -2917,6 +2976,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..8a3cb7d2b2 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,10 +327,14 @@ 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) + 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."); } @@ -950,10 +955,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 +1006,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..d4b354f32f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ProxyMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ProxyMessage.cs @@ -41,9 +41,32 @@ public unsafe void Handle(ref NetworkContext context) } return; } - var observers = networkObject.Observers; + // Validate message if server + if (networkManager.IsServer) + { + 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; + } + + 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 70e4c2aadf..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) @@ -196,6 +213,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 diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcAttributes.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcAttributes.cs index 6d276ab580..fffb8a831b 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 @@ -48,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 @@ -56,9 +77,18 @@ public struct RpcAttributeParams /// public RpcDelivery Delivery = RpcDelivery.Reliable; + /// + /// Who has network permission to invoke this RPC + /// + public RpcInvokePermission InvokePermission; + /// /// When true, only the owner of the object can execute this RPC /// + /// + /// Deprecated in favor of . + /// + [Obsolete] public bool RequireOwnership; /// @@ -120,7 +150,6 @@ public class ClientRpcAttribute : RpcAttribute /// public ClientRpcAttribute() : base(SendTo.NotServer) { - } } }