using FishNet.CodeGenerating.Extension; 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; using MethodAttributes = MonoFN.Cecil.MethodAttributes; namespace FishNet.CodeGenerating.Helping { internal class NetworkBehaviourHelper : CodegenBase { #region Reflection references. //Names. internal string FullName; //Prediction. public MethodReference Replicate_Reader_MethodRef; public MethodReference Reconcile_Server_MethodRef; //public FieldReference UsesPrediction_FieldRef; public MethodReference EmptyReplicatesQueueIntoHistory_Start_MethodRef; public MethodReference EmptyReplicatesQueueIntoHistory_MethodRef; public MethodReference Reconcile_Client_Start_MethodRef; public MethodReference Replicate_Replay_Start_MethodRef; public MethodReference Replicate_Current_MethodRef; public MethodReference Reconcile_Client_MethodRef; public MethodReference Reconcile_Current_MethodRef; public MethodReference ClearReplicateCache_Internal_MethodRef; public MethodReference Replicate_Replay_MethodRef; public MethodReference Reconcile_Reader_MethodRef; public MethodReference RegisterReplicateRpc_MethodRef; public MethodReference RegisterReconcileRpc_MethodRef; public MethodReference ReplicateRpcDelegate_Ctor_MethodRef; public MethodReference ReconcileRpcDelegate_Ctor_MethodRef; //public MethodReference Replicate_Server_SendToSpectators_MethodRef; //RPCs. public MethodReference SendServerRpc_MethodRef; public MethodReference SendObserversRpc_MethodRef; public MethodReference SendTargetRpc_MethodRef; public MethodReference RegisterServerRpc_MethodRef; public MethodReference RegisterObserversRpc_MethodRef; public MethodReference RegisterTargetRpc_MethodRef; public MethodReference ServerRpcDelegate_Ctor_MethodRef; public MethodReference ClientRpcDelegate_Ctor_MethodRef; //Is checks. public MethodReference IsClientInitialized_MethodRef; public MethodReference IsOwner_MethodRef; public MethodReference IsServerInitialized_MethodRef; public MethodReference IsHost_MethodRef; public MethodReference IsNetworked_MethodRef; //Misc. public TypeReference TypeRef; public MethodReference OwnerMatches_MethodRef; public MethodReference LocalConnection_MethodRef; public MethodReference Owner_MethodRef; public MethodReference NetworkInitializeIfDisabled_MethodRef; //TimeManager. public MethodReference TimeManager_MethodRef; #endregion #region Const. internal const uint MAX_SYNCTYPE_ALLOWANCE = byte.MaxValue; internal const uint MAX_RPC_ALLOWANCE = ushort.MaxValue; internal const uint MAX_PREDICTION_ALLOWANCE = byte.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. ServerRpcDelegate_Ctor_MethodRef = base.ImportReference(typeof(ServerRpcDelegate).GetConstructors().First()); ClientRpcDelegate_Ctor_MethodRef = base.ImportReference(typeof(ClientRpcDelegate).GetConstructors().First()); //Prediction Rpc delegate constructors. ReplicateRpcDelegate_Ctor_MethodRef = base.ImportReference(typeof(ReplicateRpcDelegate).GetConstructors().First()); ReconcileRpcDelegate_Ctor_MethodRef = base.ImportReference(typeof(ReconcileRpcDelegate).GetConstructors().First()); foreach (MethodInfo mi in networkBehaviourType.GetMethods((BindingFlags.Static | BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic))) { if (mi.Name == nameof(NetworkBehaviour.GetIsNetworked)) IsNetworked_MethodRef = base.ImportReference(mi); //CreateDelegates. else if (mi.Name == nameof(NetworkBehaviour.RegisterServerRpc)) RegisterServerRpc_MethodRef = base.ImportReference(mi); else if (mi.Name == nameof(NetworkBehaviour.RegisterObserversRpc)) RegisterObserversRpc_MethodRef = base.ImportReference(mi); else if (mi.Name == nameof(NetworkBehaviour.RegisterTargetRpc)) RegisterTargetRpc_MethodRef = base.ImportReference(mi); //Prediction delegates. else if (mi.Name == nameof(NetworkBehaviour.RegisterReplicateRpc)) RegisterReplicateRpc_MethodRef = base.ImportReference(mi); else if (mi.Name == nameof(NetworkBehaviour.RegisterReconcileRpc)) RegisterReconcileRpc_MethodRef = base.ImportReference(mi); //SendRpcs. else if (mi.Name == nameof(NetworkBehaviour.SendServerRpc)) SendServerRpc_MethodRef = base.ImportReference(mi); else if (mi.Name == nameof(NetworkBehaviour.SendObserversRpc)) SendObserversRpc_MethodRef = base.ImportReference(mi); else if (mi.Name == nameof(NetworkBehaviour.SendTargetRpc)) SendTargetRpc_MethodRef = base.ImportReference(mi); //Misc. else if (mi.Name == nameof(NetworkBehaviour.OwnerMatches)) OwnerMatches_MethodRef = base.ImportReference(mi); else if (mi.Name == nameof(NetworkBehaviour.NetworkInitializeIfDisabled)) NetworkInitializeIfDisabled_MethodRef = base.ImportReference(mi); //Prediction else if (mi.Name == nameof(NetworkBehaviour.Replicate_Current)) Replicate_Current_MethodRef = base.ImportReference(mi); else if (mi.Name == nameof(NetworkBehaviour.Replicate_Replay_Start)) Replicate_Replay_Start_MethodRef = base.ImportReference(mi); else if (mi.Name == nameof(NetworkBehaviour.EmptyReplicatesQueueIntoHistory)) EmptyReplicatesQueueIntoHistory_MethodRef = base.ImportReference(mi); else if (mi.Name == nameof(NetworkBehaviour.EmptyReplicatesQueueIntoHistory_Start)) EmptyReplicatesQueueIntoHistory_Start_MethodRef = base.ImportReference(mi); else if (mi.Name == nameof(NetworkBehaviour.Reconcile_Client_Start)) Reconcile_Client_Start_MethodRef = base.ImportReference(mi); else if (mi.Name == nameof(NetworkBehaviour.Replicate_Replay)) Replicate_Replay_MethodRef = base.ImportReference(mi); else if (mi.Name == nameof(NetworkBehaviour.Replicate_Reader)) Replicate_Reader_MethodRef = base.ImportReference(mi); else if (mi.Name == nameof(NetworkBehaviour.Reconcile_Reader)) Reconcile_Reader_MethodRef = base.ImportReference(mi); else if (mi.Name == nameof(NetworkBehaviour.Reconcile_Server)) Reconcile_Server_MethodRef = base.ImportReference(mi); else if (mi.Name == nameof(NetworkBehaviour.Reconcile_Client)) Reconcile_Client_MethodRef = base.ImportReference(mi); else if (mi.Name == nameof(NetworkBehaviour.Reconcile_Current)) Reconcile_Current_MethodRef = base.ImportReference(mi); else if (mi.Name == nameof(NetworkBehaviour.ClearReplicateCache_Internal)) ClearReplicateCache_Internal_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.IsClientInitialized)) IsClientInitialized_MethodRef = base.ImportReference(pi.GetMethod); else if (pi.Name == nameof(NetworkBehaviour.IsServerInitialized)) IsServerInitialized_MethodRef = base.ImportReference(pi.GetMethod); else if (pi.Name == nameof(NetworkBehaviour.IsHostStarted)) 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(); 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(); 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, ServerRpcDelegate_Ctor_MethodRef)); insts.Add(processor.Create(OpCodes.Call, RegisterServerRpc_MethodRef)); } //Observers. else if (rpcType == RpcType.Observers) { insts.Add(processor.Create(OpCodes.Newobj, ClientRpcDelegate_Ctor_MethodRef)); insts.Add(processor.Create(OpCodes.Call, RegisterObserversRpc_MethodRef)); } //Target else if (rpcType == RpcType.Target) { insts.Add(processor.Create(OpCodes.Newobj, ClientRpcDelegate_Ctor_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(); /* 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. /// /// When true InstanceFinder.IsClient is used, when false base.IsClientInitialized is used. internal void CreateIsClientCheck(MethodDefinition methodDef, LoggingType loggingType, bool useStatic, bool insertFirst, bool checkIsNetworked) { /* 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(); if (checkIsNetworked) instructions.AddRange(CreateIsNetworkedCheck(methodDef, endIf)); //Checking against the NetworkObject. if (!useStatic) { instructions.Add(processor.Create(OpCodes.Ldarg_0)); //argument: this //If (!base.IsClient) instructions.Add(processor.Create(OpCodes.Call, IsClientInitialized_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, has deinitialized, or if it does not contain a NetworkObject component."; 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. /// /// When true InstanceFinder.IsServer is used, when false base.IsServerInitialized is used. internal void CreateIsServerCheck(MethodDefinition methodDef, LoggingType loggingType, bool useStatic, bool insertFirst, bool checkIsNetworked) { /* 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(); if (checkIsNetworked) instructions.AddRange(CreateIsNetworkedCheck(methodDef, endIf)); if (!useStatic) { instructions.Add(processor.Create(OpCodes.Ldarg_0)); //argument: this //If (!base.IsServer) instructions.Add(processor.Create(OpCodes.Call, IsServerInitialized_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, has deinitialized, or if it does not contain a NetworkObject component."; 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 call to base.IsNetworked and returns instructions. /// private List CreateIsNetworkedCheck(MethodDefinition methodDef, Instruction endIfInst) { List insts = new(); ILProcessor processor = methodDef.Body.GetILProcessor(); insts.Add(processor.Create(OpCodes.Ldarg_0)); insts.Add(processor.Create(OpCodes.Call, base.GetClass().IsNetworked_MethodRef)); insts.Add(processor.Create(OpCodes.Brfalse, endIfInst)); return insts; } /// /// Creates a return using the ReturnType for methodDef. /// /// /// /// public List CreateRetDefault(MethodDefinition methodDef, ModuleDefinition importReturnModule = null) { ILProcessor processor = methodDef.Body.GetILProcessor(); List instructions = new(); //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; } } }