RemoteCalls.cs 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Runtime.CompilerServices;
  4. using UnityEngine;
  5. namespace Mirror.RemoteCalls
  6. {
  7. // invoke type for Cmd/Rpc
  8. public enum RemoteCallType { Command, ClientRpc }
  9. // remote call function delegate
  10. public delegate void RemoteCallDelegate(NetworkBehaviour obj, NetworkReader reader, NetworkConnectionToClient senderConnection);
  11. class Invoker
  12. {
  13. // GameObjects might have multiple components of TypeA.CommandA().
  14. // when invoking, we check if 'TypeA' is an instance of the type.
  15. // the hash itself isn't enough because we wouldn't know which component
  16. // to invoke it on if there are multiple of the same type.
  17. public Type componentType;
  18. public RemoteCallType callType;
  19. public RemoteCallDelegate function;
  20. public bool cmdRequiresAuthority;
  21. public bool AreEqual(Type componentType, RemoteCallType remoteCallType, RemoteCallDelegate invokeFunction) =>
  22. this.componentType == componentType &&
  23. this.callType == remoteCallType &&
  24. this.function == invokeFunction;
  25. }
  26. /// <summary>Used to help manage remote calls for NetworkBehaviours</summary>
  27. public static class RemoteProcedureCalls
  28. {
  29. // one lookup for all remote calls.
  30. // allows us to easily add more remote call types without duplicating code.
  31. // note: do not clear those with [RuntimeInitializeOnLoad]
  32. //
  33. // IMPORTANT: cmd/rpc functions are identified via **HASHES**.
  34. // an index would requires half the bandwidth, but introduces issues
  35. // where static constructors are lazily called, so index order isn't
  36. // guaranteed:
  37. // https://github.com/vis2k/Mirror/pull/3135
  38. // https://github.com/vis2k/Mirror/issues/3138
  39. // keep the 4 byte hash for stability!
  40. static readonly Dictionary<int, Invoker> remoteCallDelegates = new Dictionary<int, Invoker>();
  41. static bool CheckIfDelegateExists(Type componentType, RemoteCallType remoteCallType, RemoteCallDelegate func, int functionHash)
  42. {
  43. if (remoteCallDelegates.ContainsKey(functionHash))
  44. {
  45. // something already registered this hash.
  46. // it's okay if it was the same function.
  47. Invoker oldInvoker = remoteCallDelegates[functionHash];
  48. if (oldInvoker.AreEqual(componentType, remoteCallType, func))
  49. {
  50. return true;
  51. }
  52. // otherwise notify user. there is a rare chance of string
  53. // hash collisions.
  54. Debug.LogError($"Function {oldInvoker.componentType}.{oldInvoker.function.GetMethodName()} and {componentType}.{func.GetMethodName()} have the same hash. Please rename one of them");
  55. }
  56. return false;
  57. }
  58. // pass full function name to avoid ClassA.Func & ClassB.Func collisions
  59. internal static int RegisterDelegate(Type componentType, string functionFullName, RemoteCallType remoteCallType, RemoteCallDelegate func, bool cmdRequiresAuthority = true)
  60. {
  61. // type+func so Inventory.RpcUse != Equipment.RpcUse
  62. int hash = functionFullName.GetStableHashCode();
  63. if (CheckIfDelegateExists(componentType, remoteCallType, func, hash))
  64. return hash;
  65. remoteCallDelegates[hash] = new Invoker
  66. {
  67. callType = remoteCallType,
  68. componentType = componentType,
  69. function = func,
  70. cmdRequiresAuthority = cmdRequiresAuthority
  71. };
  72. return hash;
  73. }
  74. // pass full function name to avoid ClassA.Func <-> ClassB.Func collisions
  75. // need to pass componentType to support invoking on GameObjects with
  76. // multiple components of same type with same remote call.
  77. public static void RegisterCommand(Type componentType, string functionFullName, RemoteCallDelegate func, bool requiresAuthority) =>
  78. RegisterDelegate(componentType, functionFullName, RemoteCallType.Command, func, requiresAuthority);
  79. // pass full function name to avoid ClassA.Func <-> ClassB.Func collisions
  80. // need to pass componentType to support invoking on GameObjects with
  81. // multiple components of same type with same remote call.
  82. public static void RegisterRpc(Type componentType, string functionFullName, RemoteCallDelegate func) =>
  83. RegisterDelegate(componentType, functionFullName, RemoteCallType.ClientRpc, func);
  84. // to clean up tests
  85. internal static void RemoveDelegate(int hash) =>
  86. remoteCallDelegates.Remove(hash);
  87. // note: no need to throw an error if not found.
  88. // an attacker might just try to call a cmd with an rpc's hash etc.
  89. // returning false is enough.
  90. static bool GetInvokerForHash(int functionHash, RemoteCallType remoteCallType, out Invoker invoker) =>
  91. remoteCallDelegates.TryGetValue(functionHash, out invoker) &&
  92. invoker != null &&
  93. invoker.callType == remoteCallType;
  94. // InvokeCmd/Rpc Delegate can all use the same function here
  95. internal static bool Invoke(int functionHash, RemoteCallType remoteCallType, NetworkReader reader, NetworkBehaviour component, NetworkConnectionToClient senderConnection = null)
  96. {
  97. // IMPORTANT: we check if the message's componentIndex component is
  98. // actually of the right type. prevents attackers trying
  99. // to invoke remote calls on wrong components.
  100. if (GetInvokerForHash(functionHash, remoteCallType, out Invoker invoker) &&
  101. invoker.componentType.IsInstanceOfType(component))
  102. {
  103. // invoke function on this component
  104. invoker.function(component, reader, senderConnection);
  105. return true;
  106. }
  107. return false;
  108. }
  109. // check if the command 'requiresAuthority' which is set in the attribute
  110. internal static bool CommandRequiresAuthority(int cmdHash) =>
  111. GetInvokerForHash(cmdHash, RemoteCallType.Command, out Invoker invoker) &&
  112. invoker.cmdRequiresAuthority;
  113. /// <summary>Gets the handler function by hash. Useful for profilers and debuggers.</summary>
  114. public static RemoteCallDelegate GetDelegate(int functionHash) =>
  115. remoteCallDelegates.TryGetValue(functionHash, out Invoker invoker)
  116. ? invoker.function
  117. : null;
  118. }
  119. }