// check if a Command/TargetRpc/Rpc function & parameters are valid for weaving public bool ValidateRemoteCallAndParameters(MethodDefinition method, RemoteCallType callType) { if (method.IsAbstract) { logger.Error("Abstract Rpcs are currently not supported, use virtual method instead", method); return(false); } if (method.IsStatic) { logger.Error($"{method.Name} must not be static", method); return(false); } if (method.ReturnType.Is <System.Collections.IEnumerator>()) { logger.Error($"{method.Name} cannot be a coroutine", method); return(false); } if (method.HasGenericParameters) { logger.Error($"{method.Name} cannot have generic parameters", method); return(false); } return(ValidateParameters(method, callType)); }
/// <summary> /// checks if return type if valid for rpc /// </summary> /// <exception cref="RpcException">Throws when parameter are invalid</exception> protected void ValidateReturnType(MethodDefinition md, RemoteCallType callType) { var returnType = md.ReturnType; if (returnType.Is(typeof(void))) { return; } // only ServerRpc allow UniTask if (callType == RemoteCallType.ServerRpc) { var unitaskType = typeof(UniTask <int>).GetGenericTypeDefinition(); if (returnType.Is(unitaskType)) { return; } } if (callType == RemoteCallType.ServerRpc) { throw new RpcException($"Use UniTask<{md.ReturnType}> to return values from [ServerRpc]", md); } else { throw new RpcException($"[ClientRpc] must return void", md); } }
/// <summary> /// checks if method parameters are valid for rpc /// </summary> /// <exception cref="RpcException">Throws when parameter are invalid</exception> protected void ValidateParameters(MethodReference method, RemoteCallType callType) { for (var i = 0; i < method.Parameters.Count; i++) { var param = method.Parameters[i]; ValidateParameter(method, param, callType, i == 0); } }
// check if a Command/TargetRpc/Rpc function & parameters are valid for weaving public static bool ValidateRemoteCallAndParameters(MethodDefinition method, RemoteCallType callType) { if (method.IsStatic) { Weaver.Error($"{method.Name} must not be static", method); return(false); } return(ValidateFunction(method) && ValidateParameters(method, callType)); }
// check if a Command/TargetRpc/Rpc function & parameters are valid for weaving public bool ValidateRemoteCallAndParameters(MethodDefinition method, RemoteCallType callType, ref bool WeavingFailed) { if (method.IsStatic) { Log.Error($"{method.Name} must not be static", method); WeavingFailed = true; return(false); } return(ValidateFunction(method, ref WeavingFailed) && ValidateParameters(method, callType, ref WeavingFailed)); }
// check if all Command/TargetRpc/Rpc function's parameters are valid for weaving static bool ValidateParameters(MethodReference method, RemoteCallType callType) { for (int i = 0; i < method.Parameters.Count; ++i) { ParameterDefinition param = method.Parameters[i]; if (!ValidateParameter(method, param, callType, i == 0)) { return(false); } } return(true); }
public static bool IsSenderConnection(ParameterDefinition param, RemoteCallType callType) { if (callType != RemoteCallType.Command) { return(false); } TypeReference type = param.ParameterType; return(type.Is <NetworkConnectionToClient>() || type.Resolve().IsDerivedFrom <NetworkConnectionToClient>()); }
// InvokeCmd/Rpc Delegate can all use the same function here // => invoke by index to save bandwidth (2 bytes instead of 4 bytes) internal static bool Invoke(ushort functionIndex, RemoteCallType remoteCallType, NetworkReader reader, NetworkBehaviour component, NetworkConnectionToClient senderConnection = null) { // IMPORTANT: we check if the message's componentIndex component is // actually of the right type. prevents attackers trying // to invoke remote calls on wrong components. if (GetInvoker(functionIndex, remoteCallType, out Invoker invoker) && invoker.componentType.IsInstanceOfType(component)) { // invoke function on this component invoker.function(component, reader, senderConnection); return(true); } return(false); }
public static bool IsSenderConnection(ParameterDefinition param, RemoteCallType callType) { if (callType != RemoteCallType.Command) { return(false); } TypeReference type = param.ParameterType; const string ConnectionToClient = "Mirror.NetworkConnectionToClient"; bool isConnectionToClient = type.FullName == ConnectionToClient || type.Resolve().IsDerivedFrom(ConnectionToClient); return(isConnectionToClient); }
// note: no need to throw an error if not found. // an attacker might just try to call a cmd with an rpc's hash etc. // returning false is enough. static bool GetInvoker(ushort functionIndex, RemoteCallType remoteCallType, out Invoker invoker) { // valid index? if (functionIndex <= remoteCallDelegates.Count) { // get key by index int functionHash = remoteCallDelegates.Keys[functionIndex]; invoker = remoteCallDelegates[functionHash]; // check rpc type. don't allow calling cmds from rpcs, etc. return(invoker != null && invoker.callType == remoteCallType); } invoker = null; return(false); }
public static bool ProcessMethodsValidateParameters(MethodReference method, RemoteCallType callType) { for (int i = 0; i < method.Parameters.Count; ++i) { ParameterDefinition param = method.Parameters[i]; bool valid = ValidateParameter(method, param, callType, i == 0); if (!valid) { return(false); } } return(true); }
// validate parameters for a remote function call like Rpc/Cmd bool ValidateParameter(MethodReference method, ParameterDefinition param, RemoteCallType callType, bool firstParam, ref bool WeavingFailed) { // need to check this before any type lookups since those will fail since generic types don't resolve if (param.ParameterType.IsGenericParameter) { Log.Error($"{method.Name} cannot have generic parameters", method); WeavingFailed = true; return(false); } bool isNetworkConnection = param.ParameterType.Is <NetworkConnection>(); bool isSenderConnection = IsSenderConnection(param, callType); if (param.IsOut) { Log.Error($"{method.Name} cannot have out parameters", method); WeavingFailed = true; return(false); } // if not SenderConnection And not TargetRpc NetworkConnection first param if (!isSenderConnection && isNetworkConnection && !(callType == RemoteCallType.TargetRpc && firstParam)) { if (callType == RemoteCallType.Command) { Log.Error($"{method.Name} has invalid parameter {param}, Cannot pass NetworkConnections. Instead use 'NetworkConnectionToClient conn = null' to get the sender's connection on the server", method); } else { Log.Error($"{method.Name} has invalid parameter {param}. Cannot pass NetworkConnections", method); } WeavingFailed = true; return(false); } // sender connection can be optional if (param.IsOptional && !isSenderConnection) { Log.Error($"{method.Name} cannot have optional parameters", method); WeavingFailed = true; return(false); } return(true); }
static bool CheckIfDelegateExists(Type componentType, RemoteCallType remoteCallType, RemoteCallDelegate func, int functionHash) { if (remoteCallDelegates.ContainsKey(functionHash)) { // something already registered this hash. // it's okay if it was the same function. Invoker oldInvoker = remoteCallDelegates[functionHash]; if (oldInvoker.AreEqual(componentType, remoteCallType, func)) { return(true); } // otherwise notify user. there is a rare chance of string // hash collisions. Debug.LogError($"Function {oldInvoker.componentType}.{oldInvoker.function.GetMethodName()} and {componentType}.{func.GetMethodName()} have the same hash. Please rename one of them"); } return(false); }
/// <summary> /// check if a method is valid for rpc /// </summary> /// <exception cref="RpcException">Throws when method is invalid</exception> protected void ValidateMethod(MethodDefinition method, RemoteCallType callType) { if (method.IsAbstract) { throw new RpcException("Abstract Rpcs are currently not supported, use virtual method instead", method); } if (method.IsStatic) { throw new RpcException($"{method.Name} must not be static", method); } if (method.ReturnType.Is <System.Collections.IEnumerator>()) { throw new RpcException($"{method.Name} cannot be a coroutine", method); } if (method.HasGenericParameters) { throw new RpcException($"{method.Name} cannot have generic parameters", method); } }
// validate parameters for a remote function call like Rpc/Cmd bool ValidateParameter(MethodReference method, ParameterDefinition param, RemoteCallType callType, bool firstParam) { if (param.IsOut) { logger.Error($"{method.Name} cannot have out parameters", method); return(false); } if (param.ParameterType.IsGenericParameter) { logger.Error($"{method.Name} cannot have generic parameters", method); return(false); } if (IsNetworkConnection(param.ParameterType)) { if (callType == RemoteCallType.ClientRpc && firstParam) { // perfectly fine, target rpc can receive a network connection as first parameter return(true); } if (callType == RemoteCallType.ServerRpc) { return(true); } logger.Error($"{method.Name} has invalid parameter {param}, Cannot pass NetworkConnections", method); return(false); } if (param.IsOptional) { logger.Error($"{method.Name} cannot have optional parameters", method); return(false); } return(true); }
public void WriteArguments(ILProcessor worker, MethodDefinition method, VariableDefinition writer, ValueSerializer[] paramSerializers, RemoteCallType callType) { // write each argument // example result /* * writer.WritePackedInt32(someNumber) * writer.WriteNetworkIdentity(someTarget) */ // NetworkConnection is not sent via the NetworkWriter so skip it here // skip first for NetworkConnection in TargetRpc var skipFirst = ClientRpcWithTarget(method, callType); var startingArg = skipFirst ? 1 : 0; for (var i = startingArg; i < method.Parameters.Count; i++) { // try/catch for each arg so that it will give error for each var param = method.Parameters[i]; var serializer = paramSerializers[i]; WriteArgument(worker, writer, param, serializer); } }
// validate parameters for a remote function call like Rpc/Cmd static bool ValidateParameter(MethodReference method, ParameterDefinition param, RemoteCallType callType, bool firstParam) { bool isNetworkConnection = param.ParameterType.Is <NetworkConnection>(); bool isSenderConnection = IsSenderConnection(param, callType); if (param.IsOut) { Weaver.Error($"{method.Name} cannot have out parameters", method); return(false); } // if not SenderConnection And not TargetRpc NetworkConnection first param if (!isSenderConnection && isNetworkConnection && !(callType == RemoteCallType.TargetRpc && firstParam)) { if (callType == RemoteCallType.Command) { Weaver.Error($"{method.Name} has invalid parameter {param}, Cannot pass NetworkConnections. Instead use 'NetworkConnectionToClient conn = null' to get the sender's connection on the server", method); } else { Weaver.Error($"{method.Name} has invalid parameter {param}. Cannot pass NetworkConnections", method); } return(false); } // sender connection can be optional if (param.IsOptional && !isSenderConnection) { Weaver.Error($"{method.Name} cannot have optional parameters", method); return(false); } return(true); }
/// <summary> /// checks if a parameter is valid for rpc /// </summary> /// <exception cref="RpcException">Throws when parameter are invalid</exception> private void ValidateParameter(MethodReference method, ParameterDefinition param, RemoteCallType callType, bool firstParam) { if (param.IsOut) { throw new RpcException($"{method.Name} cannot have out parameters", method); } if (IsNetworkPlayer(param.ParameterType)) { if (callType == RemoteCallType.ClientRpc && firstParam) { return; } if (callType == RemoteCallType.ServerRpc) { return; } throw new RpcException($"{method.Name} has invalid parameter {param}, Cannot pass NetworkConnections", method); } // check networkplayer before optional, because networkplayer can be optional if (param.IsOptional) { throw new RpcException($"{method.Name} cannot have optional parameters", method); } }
public static bool ReadArguments(MethodDefinition method, ILProcessor worker, RemoteCallType callType) { // read each argument // example result /* * CallCmdDoSomething(reader.ReadPackedInt32(), reader.ReadNetworkIdentity()); */ bool skipFirst = callType == RemoteCallType.TargetRpc && TargetRpcProcessor.HasNetworkConnectionParameter(method); // arg of calling function, arg 0 is "this" so start counting at 1 int argNum = 1; foreach (ParameterDefinition param in method.Parameters) { // NetworkConnection is not sent via the NetworkWriter so skip it here // skip first for NetworkConnection in TargetRpc if (argNum == 1 && skipFirst) { argNum += 1; continue; } // skip SenderConnection in Command if (IsSenderConnection(param, callType)) { argNum += 1; continue; } MethodReference readFunc = Readers.GetReadFunc(param.ParameterType); if (readFunc == null) { Weaver.Error($"{method.Name} has invalid parameter {param}. Unsupported type {param.ParameterType}, use a supported Mirror type instead", method); return(false); } worker.Append(worker.Create(OpCodes.Ldarg_1)); worker.Append(worker.Create(OpCodes.Call, readFunc)); // conversion.. is this needed? if (param.ParameterType.Is <float>()) { worker.Append(worker.Create(OpCodes.Conv_R4)); } else if (param.ParameterType.Is <double>()) { worker.Append(worker.Create(OpCodes.Conv_R8)); } } return(true); }
public static bool WriteArguments(ILProcessor worker, MethodDefinition method, RemoteCallType callType) { // write each argument // example result /* * writer.WritePackedInt32(someNumber); * writer.WriteNetworkIdentity(someTarget); */ bool skipFirst = callType == RemoteCallType.TargetRpc && TargetRpcProcessor.HasNetworkConnectionParameter(method); // arg of calling function, arg 0 is "this" so start counting at 1 int argNum = 1; foreach (ParameterDefinition param in method.Parameters) { // NetworkConnection is not sent via the NetworkWriter so skip it here // skip first for NetworkConnection in TargetRpc if (argNum == 1 && skipFirst) { argNum += 1; continue; } // skip SenderConnection in Command if (IsSenderConnection(param, callType)) { argNum += 1; continue; } MethodReference writeFunc = Writers.GetWriteFunc(param.ParameterType); if (writeFunc == null) { Weaver.Error($"{method.Name} has invalid parameter {param}", method); return(false); } // use built-in writer func on writer object // NetworkWriter object worker.Append(worker.Create(OpCodes.Ldloc_0)); // add argument to call worker.Append(worker.Create(OpCodes.Ldarg, argNum)); // call writer extension method worker.Append(worker.Create(OpCodes.Call, writeFunc)); argNum += 1; } return(true); }
static bool ValidateParameter(MethodReference method, ParameterDefinition param, RemoteCallType callType, bool firstParam) { bool isNetworkConnection = param.ParameterType.FullName == Weaver.NetworkConnectionType.FullName; if (param.IsOut) { Weaver.Error($"{method.Name} cannot have out parameters", method); return(false); } // TargetRPC is an exception to this rule and can have a NetworkConnection as first parameter if (isNetworkConnection && !(callType == RemoteCallType.TargetRpc && firstParam)) { Weaver.Error($"{method.Name} has invalid parameter {param}. Cannot pass NeworkConnections", method); return(false); } // sender connection can be optional if (param.IsOptional) { Weaver.Error($"{method.Name} cannot have optional parameters", method); return(false); } return(true); }
// note: no need to throw an error if not found. // an attacker might just try to call a cmd with an rpc's hash etc. // returning false is enough. static bool GetInvokerForHash(int functionHash, RemoteCallType remoteCallType, out Invoker invoker) => remoteCallDelegates.TryGetValue(functionHash, out invoker) && invoker != null && invoker.callType == remoteCallType;
public bool AreEqual(Type componentType, RemoteCallType remoteCallType, RemoteCallDelegate invokeFunction) => this.componentType == componentType && this.callType == remoteCallType && this.function == invokeFunction;
private static bool ClientRpcWithTarget(MethodDefinition method, RemoteCallType callType) { return((callType == RemoteCallType.ClientRpc) && HasNetworkPlayerParameter(method)); }
// pass full function name to avoid ClassA.Func & ClassB.Func collisions internal static int RegisterDelegate(Type componentType, string functionFullName, RemoteCallType remoteCallType, RemoteCallDelegate func, bool cmdRequiresAuthority = true) { // type+func so Inventory.RpcUse != Equipment.RpcUse int hash = functionFullName.GetStableHashCode(); if (CheckIfDelegateExists(componentType, remoteCallType, func, hash)) { return(hash); } // register invoker by hash remoteCallDelegates[hash] = new Invoker { callType = remoteCallType, componentType = componentType, function = func, cmdRequiresAuthority = cmdRequiresAuthority }; return(hash); }