RemoteCallHelper.cs 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. using System;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. namespace Mirror.RemoteCalls
  5. {
  6. // command function delegate
  7. public delegate void CmdDelegate(NetworkBehaviour obj, NetworkReader reader, NetworkConnectionToClient senderConnection);
  8. class Invoker
  9. {
  10. public Type invokeClass;
  11. public MirrorInvokeType invokeType;
  12. public CmdDelegate invokeFunction;
  13. public bool cmdRequiresAuthority;
  14. public bool AreEqual(Type invokeClass, MirrorInvokeType invokeType, CmdDelegate invokeFunction)
  15. {
  16. return (this.invokeClass == invokeClass &&
  17. this.invokeType == invokeType &&
  18. this.invokeFunction == invokeFunction);
  19. }
  20. }
  21. public struct CommandInfo
  22. {
  23. public bool requiresAuthority;
  24. }
  25. /// <summary>Used to help manage remote calls for NetworkBehaviours</summary>
  26. public static class RemoteCallHelper
  27. {
  28. static readonly Dictionary<int, Invoker> cmdHandlerDelegates = new Dictionary<int, Invoker>();
  29. internal static int GetMethodHash(Type invokeClass, string methodName)
  30. {
  31. // (invokeClass + ":" + cmdName).GetStableHashCode() would cause allocations.
  32. // so hash1 + hash2 is better.
  33. unchecked
  34. {
  35. int hash = invokeClass.FullName.GetStableHashCode();
  36. return hash * 503 + methodName.GetStableHashCode();
  37. }
  38. }
  39. internal static int RegisterDelegate(Type invokeClass, string cmdName, MirrorInvokeType invokerType, CmdDelegate func, bool cmdRequiresAuthority = true)
  40. {
  41. // type+func so Inventory.RpcUse != Equipment.RpcUse
  42. int cmdHash = GetMethodHash(invokeClass, cmdName);
  43. if (CheckIfDeligateExists(invokeClass, invokerType, func, cmdHash))
  44. return cmdHash;
  45. Invoker invoker = new Invoker
  46. {
  47. invokeType = invokerType,
  48. invokeClass = invokeClass,
  49. invokeFunction = func,
  50. cmdRequiresAuthority = cmdRequiresAuthority,
  51. };
  52. cmdHandlerDelegates[cmdHash] = invoker;
  53. //string ingoreAuthorityMessage = invokerType == MirrorInvokeType.Command ? $" requiresAuthority:{cmdRequiresAuthority}" : "";
  54. //Debug.Log($"RegisterDelegate hash: {cmdHash} invokerType: {invokerType} method: {func.GetMethodName()}{ingoreAuthorityMessage}");
  55. return cmdHash;
  56. }
  57. static bool CheckIfDeligateExists(Type invokeClass, MirrorInvokeType invokerType, CmdDelegate func, int cmdHash)
  58. {
  59. if (cmdHandlerDelegates.ContainsKey(cmdHash))
  60. {
  61. // something already registered this hash
  62. Invoker oldInvoker = cmdHandlerDelegates[cmdHash];
  63. if (oldInvoker.AreEqual(invokeClass, invokerType, func))
  64. {
  65. // it's all right, it was the same function
  66. return true;
  67. }
  68. Debug.LogError($"Function {oldInvoker.invokeClass}.{oldInvoker.invokeFunction.GetMethodName()} and {invokeClass}.{func.GetMethodName()} have the same hash. Please rename one of them");
  69. }
  70. return false;
  71. }
  72. public static void RegisterCommandDelegate(Type invokeClass, string cmdName, CmdDelegate func, bool requiresAuthority)
  73. {
  74. RegisterDelegate(invokeClass, cmdName, MirrorInvokeType.Command, func, requiresAuthority);
  75. }
  76. public static void RegisterRpcDelegate(Type invokeClass, string rpcName, CmdDelegate func)
  77. {
  78. RegisterDelegate(invokeClass, rpcName, MirrorInvokeType.ClientRpc, func);
  79. }
  80. // We need this in order to clean up tests
  81. internal static void RemoveDelegate(int hash)
  82. {
  83. cmdHandlerDelegates.Remove(hash);
  84. }
  85. static bool GetInvokerForHash(int cmdHash, MirrorInvokeType invokeType, out Invoker invoker)
  86. {
  87. if (cmdHandlerDelegates.TryGetValue(cmdHash, out invoker) && invoker != null && invoker.invokeType == invokeType)
  88. {
  89. return true;
  90. }
  91. // debug message if not found, or null, or mismatched type
  92. // (no need to throw an error, an attacker might just be trying to
  93. // call an cmd with an rpc's hash)
  94. // Debug.Log("GetInvokerForHash hash:" + cmdHash + " not found");
  95. return false;
  96. }
  97. // InvokeCmd/Rpc Delegate can all use the same function here
  98. internal static bool InvokeHandlerDelegate(int cmdHash, MirrorInvokeType invokeType, NetworkReader reader, NetworkBehaviour invokingType, NetworkConnectionToClient senderConnection = null)
  99. {
  100. if (GetInvokerForHash(cmdHash, invokeType, out Invoker invoker) && invoker.invokeClass.IsInstanceOfType(invokingType))
  101. {
  102. invoker.invokeFunction(invokingType, reader, senderConnection);
  103. return true;
  104. }
  105. return false;
  106. }
  107. internal static CommandInfo GetCommandInfo(int cmdHash, NetworkBehaviour invokingType)
  108. {
  109. if (GetInvokerForHash(cmdHash, MirrorInvokeType.Command, out Invoker invoker) && invoker.invokeClass.IsInstanceOfType(invokingType))
  110. {
  111. return new CommandInfo
  112. {
  113. requiresAuthority = invoker.cmdRequiresAuthority
  114. };
  115. }
  116. return default;
  117. }
  118. /// <summary>Gets the handler function by hash. Useful for profilers and debuggers.</summary>
  119. public static CmdDelegate GetDelegate(int cmdHash)
  120. {
  121. if (cmdHandlerDelegates.TryGetValue(cmdHash, out Invoker invoker))
  122. {
  123. return invoker.invokeFunction;
  124. }
  125. return null;
  126. }
  127. }
  128. }