443 lines
21 KiB
C#
443 lines
21 KiB
C#
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returnsthe child most Awake by iterating up childMostTypeDef.
|
|
/// </summary>
|
|
/// <param name="childMostTypeDef"></param>
|
|
/// <param name="created"></param>
|
|
/// <returns></returns>
|
|
internal MethodDefinition GetAwakeMethodDefinition(TypeDefinition typeDef)
|
|
{
|
|
return typeDef.GetMethod(AWAKE_METHOD_NAME);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Creates a replicate delegate.
|
|
/// </summary>
|
|
/// <param name="processor"></param>
|
|
/// <param name="originalMethodDef"></param>
|
|
/// <param name="readerMethodDef"></param>
|
|
/// <param name="rpcType"></param>
|
|
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<Instruction> insts = new List<Instruction>();
|
|
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);
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Creates a RPC delegate for rpcType.
|
|
/// </summary>
|
|
/// <param name="processor"></param>
|
|
/// <param name="originalMethodDef"></param>
|
|
/// <param name="readerMethodDef"></param>
|
|
/// <param name="rpcType"></param>
|
|
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<Instruction> insts = new List<Instruction>();
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates exit method condition if local client is not owner.
|
|
/// </summary>
|
|
/// <param name="retIfOwner">True if to ret when owner, false to ret when not owner.</param>
|
|
/// <returns>Returns Ret instruction.</returns>
|
|
internal Instruction CreateLocalClientIsOwnerCheck(MethodDefinition methodDef, LoggingType loggingType, bool notifyMessageCanBeDisabled, bool retIfOwner, bool insertFirst)
|
|
{
|
|
List<Instruction> instructions = new List<Instruction>();
|
|
/* 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<GeneralHelper>().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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates exit method condition if remote client is not owner.
|
|
/// </summary>
|
|
/// <param name="processor"></param>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates exit method condition if not client.
|
|
/// </summary>
|
|
/// <param name="processor"></param>
|
|
/// <param name="retInstruction"></param>
|
|
/// <param name="warn"></param>
|
|
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<Instruction> instructions = new List<Instruction>();
|
|
//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<ObjectHelper>().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<GeneralHelper>().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);
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Creates exit method condition if not server.
|
|
/// </summary>
|
|
/// <param name="processor"></param>
|
|
/// <param name="warn"></param>
|
|
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<Instruction> instructions = new List<Instruction>();
|
|
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<ObjectHelper>().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<GeneralHelper>().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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a return using the ReturnType for methodDef.
|
|
/// </summary>
|
|
/// <param name="processor"></param>
|
|
/// <param name="methodDef"></param>
|
|
/// <returns></returns>
|
|
public List<Instruction> CreateRetDefault(MethodDefinition methodDef, ModuleDefinition importReturnModule = null)
|
|
{
|
|
ILProcessor processor = methodDef.Body.GetILProcessor();
|
|
List<Instruction> instructions = new List<Instruction>();
|
|
//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<GeneralHelper>().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;
|
|
}
|
|
}
|
|
} |