using FishNet.CodeGenerating.Helping.Extension; using FishNet.CodeGenerating.Processing; using FishNet.Configuring; using FishNet.Managing.Logging; using FishNet.Object; using FishNet.Object.Delegating; using FishNet.Object.Helping; using FishNet.Object.Prediction.Delegating; using MonoFN.Cecil; using MonoFN.Cecil.Cil; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; namespace FishNet.CodeGenerating.Helping { internal class NetworkBehaviourHelper : CodegenBase { #region Reflection references. //Names. internal string FullName; //Prediction. public string ClearReplicateCache_Method_Name = nameof(NetworkBehaviour.ClearReplicateCache_Internal); public MethodReference Replicate_Server_MethodRef; public MethodReference Replicate_Client_MethodRef; public MethodReference Replicate_Reader_MethodRef; public MethodReference Replicate_ExitEarly_A_MethodRef; public MethodReference Reconcile_ExitEarly_A_MethodRef; public MethodReference Reconcile_Server_MethodRef; public MethodReference Reconcile_Client_MethodRef; public MethodReference Reconcile_Reader_MethodRef; public MethodReference RegisterReplicateRpc_MethodRef; public MethodReference RegisterReconcileRpc_MethodRef; public MethodReference ReplicateRpcDelegateConstructor_MethodRef; public MethodReference ReconcileRpcDelegateConstructor_MethodRef; //RPCs. public MethodReference SendServerRpc_MethodRef; public MethodReference SendObserversRpc_MethodRef; public MethodReference SendTargetRpc_MethodRef; public MethodReference DirtySyncType_MethodRef; public MethodReference RegisterServerRpc_MethodRef; public MethodReference RegisterObserversRpc_MethodRef; public MethodReference RegisterTargetRpc_MethodRef; public MethodReference ServerRpcDelegateConstructor_MethodRef; public MethodReference ClientRpcDelegateConstructor_MethodRef; //Is checks. public MethodReference IsClient_MethodRef; public MethodReference IsOwner_MethodRef; public MethodReference IsServer_MethodRef; public MethodReference IsHost_MethodRef; //Misc. public TypeReference TypeRef; public MethodReference OwnerMatches_MethodRef; public MethodReference LocalConnection_MethodRef; public MethodReference Owner_MethodRef; public MethodReference ReadSyncVar_MethodRef; public MethodReference NetworkInitializeIfDisabled_MethodRef; //TimeManager. public MethodReference TimeManager_MethodRef; #endregion #region Const. internal const uint MAX_RPC_ALLOWANCE = ushort.MaxValue; internal const string AWAKE_METHOD_NAME = "Awake"; internal const string DISABLE_LOGGING_TEXT = "This message may be disabled by setting the Logging field in your attribute to LoggingType.Off"; #endregion public override bool ImportReferences() { Type networkBehaviourType = typeof(NetworkBehaviour); TypeRef = base.ImportReference(networkBehaviourType); FullName = networkBehaviourType.FullName; base.ImportReference(networkBehaviourType); //ServerRpcDelegate and ClientRpcDelegate constructors. ServerRpcDelegateConstructor_MethodRef = base.ImportReference(typeof(ServerRpcDelegate).GetConstructors().First()); ClientRpcDelegateConstructor_MethodRef = base.ImportReference(typeof(ClientRpcDelegate).GetConstructors().First()); //Prediction Rpc delegate constructors. ReplicateRpcDelegateConstructor_MethodRef = base.ImportReference(typeof(ReplicateRpcDelegate).GetConstructors().First()); ReconcileRpcDelegateConstructor_MethodRef = base.ImportReference(typeof(ReconcileRpcDelegate).GetConstructors().First()); foreach (MethodInfo mi in networkBehaviourType.GetMethods((BindingFlags.Static | BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic))) { //CreateDelegates. if (mi.Name == nameof(NetworkBehaviour.RegisterServerRpc_Internal)) RegisterServerRpc_MethodRef = base.ImportReference(mi); else if (mi.Name == nameof(NetworkBehaviour.RegisterObserversRpc_Internal)) RegisterObserversRpc_MethodRef = base.ImportReference(mi); else if (mi.Name == nameof(NetworkBehaviour.RegisterTargetRpc_Internal)) RegisterTargetRpc_MethodRef = base.ImportReference(mi); //SendPredictions. else if (mi.Name == nameof(NetworkBehaviour.RegisterReplicateRpc_Internal)) RegisterReplicateRpc_MethodRef = base.ImportReference(mi); else if (mi.Name == nameof(NetworkBehaviour.RegisterReconcileRpc_Internal)) RegisterReconcileRpc_MethodRef = base.ImportReference(mi); //SendRpcs. else if (mi.Name == nameof(NetworkBehaviour.SendServerRpc_Internal)) SendServerRpc_MethodRef = base.ImportReference(mi); else if (mi.Name == nameof(NetworkBehaviour.SendObserversRpc_Internal)) SendObserversRpc_MethodRef = base.ImportReference(mi); else if (mi.Name == nameof(NetworkBehaviour.SendTargetRpc_Internal)) SendTargetRpc_MethodRef = base.ImportReference(mi); //Prediction. else if (mi.Name == nameof(NetworkBehaviour.Replicate_ExitEarly_A_Internal)) Replicate_ExitEarly_A_MethodRef = base.ImportReference(mi); else if (mi.Name == nameof(NetworkBehaviour.Replicate_Server_Internal)) Replicate_Server_MethodRef = base.ImportReference(mi); else if (mi.Name == nameof(NetworkBehaviour.Replicate_Reader_Internal)) Replicate_Reader_MethodRef = base.ImportReference(mi); else if (mi.Name == nameof(NetworkBehaviour.Reconcile_Reader_Internal)) Reconcile_Reader_MethodRef = base.ImportReference(mi); else if (mi.Name == nameof(NetworkBehaviour.Reconcile_ExitEarly_A_Internal)) Reconcile_ExitEarly_A_MethodRef = base.ImportReference(mi); else if (mi.Name == nameof(NetworkBehaviour.Reconcile_Server_Internal)) Reconcile_Server_MethodRef = base.ImportReference(mi); else if (mi.Name == nameof(NetworkBehaviour.Replicate_Client_Internal)) Replicate_Client_MethodRef = base.ImportReference(mi); else if (mi.Name == nameof(NetworkBehaviour.Reconcile_Client_Internal)) Reconcile_Client_MethodRef = base.ImportReference(mi); //Misc. else if (mi.Name == nameof(NetworkBehaviour.OwnerMatches)) OwnerMatches_MethodRef = base.ImportReference(mi); else if (mi.Name == nameof(NetworkBehaviour.ReadSyncVar)) ReadSyncVar_MethodRef = base.ImportReference(mi); else if (mi.Name == nameof(NetworkBehaviour.DirtySyncType)) DirtySyncType_MethodRef = base.ImportReference(mi); else if (mi.Name == nameof(NetworkBehaviour.NetworkInitializeIfDisabled)) NetworkInitializeIfDisabled_MethodRef = base.ImportReference(mi); } foreach (PropertyInfo pi in networkBehaviourType.GetProperties((BindingFlags.Static | BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic))) { //Server/Client states. if (pi.Name == nameof(NetworkBehaviour.IsClient)) IsClient_MethodRef = base.ImportReference(pi.GetMethod); else if (pi.Name == nameof(NetworkBehaviour.IsServer)) IsServer_MethodRef = base.ImportReference(pi.GetMethod); else if (pi.Name == nameof(NetworkBehaviour.IsHost)) IsHost_MethodRef = base.ImportReference(pi.GetMethod); else if (pi.Name == nameof(NetworkBehaviour.IsOwner)) IsOwner_MethodRef = base.ImportReference(pi.GetMethod); //Owner. else if (pi.Name == nameof(NetworkBehaviour.Owner)) Owner_MethodRef = base.ImportReference(pi.GetMethod); else if (pi.Name == nameof(NetworkBehaviour.LocalConnection)) LocalConnection_MethodRef = base.ImportReference(pi.GetMethod); //Misc. else if (pi.Name == nameof(NetworkBehaviour.TimeManager)) TimeManager_MethodRef = base.ImportReference(pi.GetMethod); } return true; } /// /// Returnsthe child most Awake by iterating up childMostTypeDef. /// /// /// /// internal MethodDefinition GetAwakeMethodDefinition(TypeDefinition typeDef) { return typeDef.GetMethod(AWAKE_METHOD_NAME); } /// /// Creates a replicate delegate. /// /// /// /// /// internal void CreateReplicateDelegate(MethodDefinition originalMethodDef, MethodDefinition readerMethodDef, uint methodHash) { MethodDefinition methodDef = originalMethodDef.DeclaringType.GetMethod(NetworkBehaviourProcessor.NETWORKINITIALIZE_EARLY_INTERNAL_NAME); ILProcessor processor = methodDef.Body.GetILProcessor(); List insts = new List(); insts.Add(processor.Create(OpCodes.Ldarg_0)); insts.Add(processor.Create(OpCodes.Ldc_I4, (int)methodHash)); /* Create delegate and call NetworkBehaviour method. */ insts.Add(processor.Create(OpCodes.Ldnull)); insts.Add(processor.Create(OpCodes.Ldftn, readerMethodDef)); /* Has to be done last. This allows the NetworkBehaviour to * initialize it's fields first. */ processor.InsertLast(insts); } /// /// Creates a RPC delegate for rpcType. /// /// /// /// /// internal void CreateRpcDelegate(bool runLocally, TypeDefinition typeDef, MethodDefinition readerMethodDef, RpcType rpcType, uint methodHash, CustomAttribute rpcAttribute) { MethodDefinition methodDef = typeDef.GetMethod(NetworkBehaviourProcessor.NETWORKINITIALIZE_EARLY_INTERNAL_NAME); ILProcessor processor = methodDef.Body.GetILProcessor(); List insts = new List(); insts.Add(processor.Create(OpCodes.Ldarg_0)); insts.Add(processor.Create(OpCodes.Ldc_I4, (int)methodHash)); /* Create delegate and call NetworkBehaviour method. */ insts.Add(processor.Create(OpCodes.Ldarg_0)); insts.Add(processor.Create(OpCodes.Ldftn, readerMethodDef)); //Server. if (rpcType == RpcType.Server) { insts.Add(processor.Create(OpCodes.Newobj, ServerRpcDelegateConstructor_MethodRef)); insts.Add(processor.Create(OpCodes.Call, RegisterServerRpc_MethodRef)); } //Observers. else if (rpcType == RpcType.Observers) { insts.Add(processor.Create(OpCodes.Newobj, ClientRpcDelegateConstructor_MethodRef)); insts.Add(processor.Create(OpCodes.Call, RegisterObserversRpc_MethodRef)); } //Target else if (rpcType == RpcType.Target) { insts.Add(processor.Create(OpCodes.Newobj, ClientRpcDelegateConstructor_MethodRef)); insts.Add(processor.Create(OpCodes.Call, RegisterTargetRpc_MethodRef)); } /* Has to be done last. This allows the NetworkBehaviour to * initialize it's fields first. */ processor.InsertLast(insts); } /// /// Creates exit method condition if local client is not owner. /// /// True if to ret when owner, false to ret when not owner. /// Returns Ret instruction. internal Instruction CreateLocalClientIsOwnerCheck(MethodDefinition methodDef, LoggingType loggingType, bool notifyMessageCanBeDisabled, bool retIfOwner, bool insertFirst) { List instructions = new List(); /* This is placed after the if check. * Should the if check pass then code * jumps to this instruction. */ ILProcessor processor = methodDef.Body.GetILProcessor(); Instruction endIf = processor.Create(OpCodes.Nop); instructions.Add(processor.Create(OpCodes.Ldarg_0)); //argument: this //If !base.IsOwner endIf. instructions.Add(processor.Create(OpCodes.Call, IsOwner_MethodRef)); if (retIfOwner) instructions.Add(processor.Create(OpCodes.Brfalse, endIf)); else instructions.Add(processor.Create(OpCodes.Brtrue, endIf)); //If logging is not disabled. if (loggingType != LoggingType.Off) { string disableLoggingText = (notifyMessageCanBeDisabled) ? DISABLE_LOGGING_TEXT : string.Empty; string msg = (retIfOwner) ? $"Cannot complete action because you are the owner of this object. {disableLoggingText}." : $"Cannot complete action because you are not the owner of this object. {disableLoggingText}."; instructions.AddRange(base.GetClass().LogMessage(methodDef, msg, loggingType)); } //Return block. Instruction retInst = processor.Create(OpCodes.Ret); instructions.Add(retInst); //After if statement, jumped to when successful check. instructions.Add(endIf); if (insertFirst) { processor.InsertFirst(instructions); } else { foreach (Instruction inst in instructions) processor.Append(inst); } return retInst; } /// /// Creates exit method condition if remote client is not owner. /// /// internal Instruction CreateRemoteClientIsOwnerCheck(ILProcessor processor, ParameterDefinition connectionParameterDef) { /* This is placed after the if check. * Should the if check pass then code * jumps to this instruction. */ Instruction endIf = processor.Create(OpCodes.Nop); processor.Emit(OpCodes.Ldarg_0); //argument: this //If !base.IsOwner endIf. processor.Emit(OpCodes.Ldarg, connectionParameterDef); processor.Emit(OpCodes.Call, OwnerMatches_MethodRef); processor.Emit(OpCodes.Brtrue, endIf); //Return block. Instruction retInst = processor.Create(OpCodes.Ret); processor.Append(retInst); //After if statement, jumped to when successful check. processor.Append(endIf); return retInst; } /// /// Creates exit method condition if not client. /// /// /// /// internal void CreateIsClientCheck(MethodDefinition methodDef, LoggingType loggingType, bool useStatic, bool insertFirst) { /* This is placed after the if check. * Should the if check pass then code * jumps to this instruction. */ ILProcessor processor = methodDef.Body.GetILProcessor(); Instruction endIf = processor.Create(OpCodes.Nop); List instructions = new List(); //Checking against the NetworkObject. if (!useStatic) { instructions.Add(processor.Create(OpCodes.Ldarg_0)); //argument: this //If (!base.IsClient) instructions.Add(processor.Create(OpCodes.Call, IsClient_MethodRef)); } //Checking instanceFinder. else { instructions.Add(processor.Create(OpCodes.Call, base.GetClass().InstanceFinder_IsClient_MethodRef)); } instructions.Add(processor.Create(OpCodes.Brtrue, endIf)); //If warning then also append warning text. if (loggingType != LoggingType.Off) { string msg = $"Cannot complete action because client is not active. This may also occur if the object is not yet initialized or if it does not contain a NetworkObject component. {DISABLE_LOGGING_TEXT}."; instructions.AddRange(base.GetClass().LogMessage(methodDef, msg, loggingType)); } //Add return. instructions.AddRange(CreateRetDefault(methodDef)); //After if statement, jumped to when successful check. instructions.Add(endIf); if (insertFirst) { processor.InsertFirst(instructions); } else { foreach (Instruction inst in instructions) processor.Append(inst); } } /// /// Creates exit method condition if not server. /// /// /// internal void CreateIsServerCheck(MethodDefinition methodDef, LoggingType loggingType, bool useStatic, bool insertFirst) { /* This is placed after the if check. * Should the if check pass then code * jumps to this instruction. */ ILProcessor processor = methodDef.Body.GetILProcessor(); Instruction endIf = processor.Create(OpCodes.Nop); List instructions = new List(); if (!useStatic) { instructions.Add(processor.Create(OpCodes.Ldarg_0)); //argument: this //If (!base.IsServer) instructions.Add(processor.Create(OpCodes.Call, IsServer_MethodRef)); } //Checking instanceFinder. else { instructions.Add(processor.Create(OpCodes.Call, base.GetClass().InstanceFinder_IsServer_MethodRef)); } instructions.Add(processor.Create(OpCodes.Brtrue, endIf)); //If warning then also append warning text. if (loggingType != LoggingType.Off) { string msg = $"Cannot complete action because server is not active. This may also occur if the object is not yet initialized or if it does not contain a NetworkObject component. {DISABLE_LOGGING_TEXT}"; instructions.AddRange(base.GetClass().LogMessage(methodDef, msg, loggingType)); } //Add return. instructions.AddRange(CreateRetDefault(methodDef)); //After if statement, jumped to when successful check. instructions.Add(endIf); if (insertFirst) { processor.InsertFirst(instructions); } else { foreach (Instruction inst in instructions) processor.Append(inst); } } /// /// Creates a return using the ReturnType for methodDef. /// /// /// /// public List CreateRetDefault(MethodDefinition methodDef, ModuleDefinition importReturnModule = null) { ILProcessor processor = methodDef.Body.GetILProcessor(); List instructions = new List(); //If requires a value return. if (methodDef.ReturnType != methodDef.Module.TypeSystem.Void) { //Import type first. methodDef.Module.ImportReference(methodDef.ReturnType); if (importReturnModule != null) importReturnModule.ImportReference(methodDef.ReturnType); VariableDefinition vd = base.GetClass().CreateVariable(methodDef, methodDef.ReturnType); instructions.Add(processor.Create(OpCodes.Ldloca_S, vd)); instructions.Add(processor.Create(OpCodes.Initobj, vd.VariableType)); instructions.Add(processor.Create(OpCodes.Ldloc, vd)); } instructions.Add(processor.Create(OpCodes.Ret)); return instructions; } } }