Gonna move to server authority
This commit is contained in:
@@ -1,127 +1,125 @@
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
/// <summary>
|
||||
/// Processes [Command] methods in NetworkBehaviour
|
||||
/// </summary>
|
||||
public static class CommandProcessor
|
||||
{
|
||||
/*
|
||||
// generates code like:
|
||||
public void CmdThrust(float thrusting, int spin)
|
||||
{
|
||||
NetworkWriter networkWriter = new NetworkWriter();
|
||||
networkWriter.Write(thrusting);
|
||||
networkWriter.WritePackedUInt32((uint)spin);
|
||||
base.SendCommandInternal(cmdName, networkWriter, cmdName);
|
||||
}
|
||||
|
||||
public void CallCmdThrust(float thrusting, int spin)
|
||||
{
|
||||
// whatever the user was doing before
|
||||
}
|
||||
|
||||
Originally HLAPI put the send message code inside the Call function
|
||||
and then proceeded to replace every call to CmdTrust with CallCmdTrust
|
||||
|
||||
This method moves all the user's code into the "CallCmd" method
|
||||
and replaces the body of the original method with the send message code.
|
||||
This way we do not need to modify the code anywhere else, and this works
|
||||
correctly in dependent assemblies
|
||||
*/
|
||||
public static MethodDefinition ProcessCommandCall(TypeDefinition td, MethodDefinition md, CustomAttribute commandAttr)
|
||||
{
|
||||
MethodDefinition cmd = MethodProcessor.SubstituteMethod(td, md);
|
||||
|
||||
ILProcessor worker = md.Body.GetILProcessor();
|
||||
|
||||
NetworkBehaviourProcessor.WriteSetupLocals(worker);
|
||||
|
||||
// NetworkWriter writer = new NetworkWriter();
|
||||
NetworkBehaviourProcessor.WriteCreateWriter(worker);
|
||||
|
||||
// write all the arguments that the user passed to the Cmd call
|
||||
if (!NetworkBehaviourProcessor.WriteArguments(worker, md, RemoteCallType.Command))
|
||||
return null;
|
||||
|
||||
string cmdName = md.Name;
|
||||
int channel = commandAttr.GetField("channel", 0);
|
||||
bool requiresAuthority = commandAttr.GetField("requiresAuthority", true);
|
||||
|
||||
// invoke internal send and return
|
||||
// load 'base.' to call the SendCommand function with
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldtoken, td);
|
||||
// invokerClass
|
||||
worker.Emit(OpCodes.Call, WeaverTypes.getTypeFromHandleReference);
|
||||
worker.Emit(OpCodes.Ldstr, cmdName);
|
||||
// writer
|
||||
worker.Emit(OpCodes.Ldloc_0);
|
||||
worker.Emit(OpCodes.Ldc_I4, channel);
|
||||
// requiresAuthority ? 1 : 0
|
||||
worker.Emit(requiresAuthority ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
|
||||
worker.Emit(OpCodes.Call, WeaverTypes.sendCommandInternal);
|
||||
|
||||
NetworkBehaviourProcessor.WriteRecycleWriter(worker);
|
||||
|
||||
worker.Emit(OpCodes.Ret);
|
||||
return cmd;
|
||||
}
|
||||
|
||||
/*
|
||||
// generates code like:
|
||||
protected static void InvokeCmdCmdThrust(NetworkBehaviour obj, NetworkReader reader, NetworkConnection senderConnection)
|
||||
{
|
||||
if (!NetworkServer.active)
|
||||
{
|
||||
return;
|
||||
}
|
||||
((ShipControl)obj).CmdThrust(reader.ReadSingle(), (int)reader.ReadPackedUInt32());
|
||||
}
|
||||
*/
|
||||
public static MethodDefinition ProcessCommandInvoke(TypeDefinition td, MethodDefinition method, MethodDefinition cmdCallFunc)
|
||||
{
|
||||
MethodDefinition cmd = new MethodDefinition(Weaver.InvokeRpcPrefix + method.Name,
|
||||
MethodAttributes.Family | MethodAttributes.Static | MethodAttributes.HideBySig,
|
||||
WeaverTypes.Import(typeof(void)));
|
||||
|
||||
ILProcessor worker = cmd.Body.GetILProcessor();
|
||||
Instruction label = worker.Create(OpCodes.Nop);
|
||||
|
||||
NetworkBehaviourProcessor.WriteServerActiveCheck(worker, method.Name, label, "Command");
|
||||
|
||||
// setup for reader
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Castclass, td);
|
||||
|
||||
if (!NetworkBehaviourProcessor.ReadArguments(method, worker, RemoteCallType.Command))
|
||||
return null;
|
||||
|
||||
AddSenderConnection(method, worker);
|
||||
|
||||
// invoke actual command function
|
||||
worker.Emit(OpCodes.Callvirt, cmdCallFunc);
|
||||
worker.Emit(OpCodes.Ret);
|
||||
|
||||
NetworkBehaviourProcessor.AddInvokeParameters(cmd.Parameters);
|
||||
|
||||
td.Methods.Add(cmd);
|
||||
return cmd;
|
||||
}
|
||||
|
||||
static void AddSenderConnection(MethodDefinition method, ILProcessor worker)
|
||||
{
|
||||
foreach (ParameterDefinition param in method.Parameters)
|
||||
{
|
||||
if (NetworkBehaviourProcessor.IsSenderConnection(param, RemoteCallType.Command))
|
||||
{
|
||||
// NetworkConnection is 3nd arg (arg0 is "obj" not "this" because method is static)
|
||||
// example: static void InvokeCmdCmdSendCommand(NetworkBehaviour obj, NetworkReader reader, NetworkConnection connection)
|
||||
worker.Emit(OpCodes.Ldarg_2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
// Processes [Command] methods in NetworkBehaviour
|
||||
public static class CommandProcessor
|
||||
{
|
||||
/*
|
||||
// generates code like:
|
||||
public void CmdThrust(float thrusting, int spin)
|
||||
{
|
||||
NetworkWriter networkWriter = new NetworkWriter();
|
||||
networkWriter.Write(thrusting);
|
||||
networkWriter.WritePackedUInt32((uint)spin);
|
||||
base.SendCommandInternal(cmdName, networkWriter, cmdName);
|
||||
}
|
||||
|
||||
public void CallCmdThrust(float thrusting, int spin)
|
||||
{
|
||||
// whatever the user was doing before
|
||||
}
|
||||
|
||||
Originally HLAPI put the send message code inside the Call function
|
||||
and then proceeded to replace every call to CmdTrust with CallCmdTrust
|
||||
|
||||
This method moves all the user's code into the "CallCmd" method
|
||||
and replaces the body of the original method with the send message code.
|
||||
This way we do not need to modify the code anywhere else, and this works
|
||||
correctly in dependent assemblies
|
||||
*/
|
||||
public static MethodDefinition ProcessCommandCall(WeaverTypes weaverTypes, Writers writers, Logger Log, TypeDefinition td, MethodDefinition md, CustomAttribute commandAttr, ref bool WeavingFailed)
|
||||
{
|
||||
MethodDefinition cmd = MethodProcessor.SubstituteMethod(Log, td, md, ref WeavingFailed);
|
||||
|
||||
ILProcessor worker = md.Body.GetILProcessor();
|
||||
|
||||
NetworkBehaviourProcessor.WriteSetupLocals(worker, weaverTypes);
|
||||
|
||||
// NetworkWriter writer = new NetworkWriter();
|
||||
NetworkBehaviourProcessor.WriteCreateWriter(worker, weaverTypes);
|
||||
|
||||
// write all the arguments that the user passed to the Cmd call
|
||||
if (!NetworkBehaviourProcessor.WriteArguments(worker, writers, Log, md, RemoteCallType.Command, ref WeavingFailed))
|
||||
return null;
|
||||
|
||||
string cmdName = md.Name;
|
||||
int channel = commandAttr.GetField("channel", 0);
|
||||
bool requiresAuthority = commandAttr.GetField("requiresAuthority", true);
|
||||
|
||||
// invoke internal send and return
|
||||
// load 'base.' to call the SendCommand function with
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldtoken, td);
|
||||
// invokerClass
|
||||
worker.Emit(OpCodes.Call, weaverTypes.getTypeFromHandleReference);
|
||||
worker.Emit(OpCodes.Ldstr, cmdName);
|
||||
// writer
|
||||
worker.Emit(OpCodes.Ldloc_0);
|
||||
worker.Emit(OpCodes.Ldc_I4, channel);
|
||||
// requiresAuthority ? 1 : 0
|
||||
worker.Emit(requiresAuthority ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
|
||||
worker.Emit(OpCodes.Call, weaverTypes.sendCommandInternal);
|
||||
|
||||
NetworkBehaviourProcessor.WriteRecycleWriter(worker, weaverTypes);
|
||||
|
||||
worker.Emit(OpCodes.Ret);
|
||||
return cmd;
|
||||
}
|
||||
|
||||
/*
|
||||
// generates code like:
|
||||
protected static void InvokeCmdCmdThrust(NetworkBehaviour obj, NetworkReader reader, NetworkConnection senderConnection)
|
||||
{
|
||||
if (!NetworkServer.active)
|
||||
{
|
||||
return;
|
||||
}
|
||||
((ShipControl)obj).CmdThrust(reader.ReadSingle(), (int)reader.ReadPackedUInt32());
|
||||
}
|
||||
*/
|
||||
public static MethodDefinition ProcessCommandInvoke(WeaverTypes weaverTypes, Readers readers, Logger Log, TypeDefinition td, MethodDefinition method, MethodDefinition cmdCallFunc, ref bool WeavingFailed)
|
||||
{
|
||||
MethodDefinition cmd = new MethodDefinition(Weaver.InvokeRpcPrefix + method.Name,
|
||||
MethodAttributes.Family | MethodAttributes.Static | MethodAttributes.HideBySig,
|
||||
weaverTypes.Import(typeof(void)));
|
||||
|
||||
ILProcessor worker = cmd.Body.GetILProcessor();
|
||||
Instruction label = worker.Create(OpCodes.Nop);
|
||||
|
||||
NetworkBehaviourProcessor.WriteServerActiveCheck(worker, weaverTypes, method.Name, label, "Command");
|
||||
|
||||
// setup for reader
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Castclass, td);
|
||||
|
||||
if (!NetworkBehaviourProcessor.ReadArguments(method, readers, Log, worker, RemoteCallType.Command, ref WeavingFailed))
|
||||
return null;
|
||||
|
||||
AddSenderConnection(method, worker);
|
||||
|
||||
// invoke actual command function
|
||||
worker.Emit(OpCodes.Callvirt, cmdCallFunc);
|
||||
worker.Emit(OpCodes.Ret);
|
||||
|
||||
NetworkBehaviourProcessor.AddInvokeParameters(weaverTypes, cmd.Parameters);
|
||||
|
||||
td.Methods.Add(cmd);
|
||||
return cmd;
|
||||
}
|
||||
|
||||
static void AddSenderConnection(MethodDefinition method, ILProcessor worker)
|
||||
{
|
||||
foreach (ParameterDefinition param in method.Parameters)
|
||||
{
|
||||
if (NetworkBehaviourProcessor.IsSenderConnection(param, RemoteCallType.Command))
|
||||
{
|
||||
// NetworkConnection is 3nd arg (arg0 is "obj" not "this" because method is static)
|
||||
// example: static void InvokeCmdCmdSendCommand(NetworkBehaviour obj, NetworkReader reader, NetworkConnection connection)
|
||||
worker.Emit(OpCodes.Ldarg_2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 73f6c9cdbb9e54f65b3a0a35cc8e55c2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
fileFormatVersion: 2
|
||||
guid: 73f6c9cdbb9e54f65b3a0a35cc8e55c2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData: ''
|
||||
assetBundleName: ''
|
||||
assetBundleVariant: ''
|
||||
|
||||
@@ -1,134 +1,130 @@
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
public static class MethodProcessor
|
||||
{
|
||||
const string RpcPrefix = "UserCode_";
|
||||
|
||||
// creates a method substitute
|
||||
// For example, if we have this:
|
||||
// public void CmdThrust(float thrusting, int spin)
|
||||
// {
|
||||
// xxxxx
|
||||
// }
|
||||
//
|
||||
// it will substitute the method and move the code to a new method with a provided name
|
||||
// for example:
|
||||
//
|
||||
// public void CmdTrust(float thrusting, int spin)
|
||||
// {
|
||||
// }
|
||||
//
|
||||
// public void <newName>(float thrusting, int spin)
|
||||
// {
|
||||
// xxxxx
|
||||
// }
|
||||
//
|
||||
// Note that all the calls to the method remain untouched
|
||||
//
|
||||
// the original method definition loses all code
|
||||
// this returns the newly created method with all the user provided code
|
||||
public static MethodDefinition SubstituteMethod(TypeDefinition td, MethodDefinition md)
|
||||
{
|
||||
string newName = RpcPrefix + md.Name;
|
||||
MethodDefinition cmd = new MethodDefinition(newName, md.Attributes, md.ReturnType);
|
||||
|
||||
// force the substitute method to be protected.
|
||||
// -> public would show in the Inspector for UnityEvents as
|
||||
// User_CmdUsePotion() etc. but the user shouldn't use those.
|
||||
// -> private would not allow inheriting classes to call it, see
|
||||
// OverrideVirtualWithBaseCallsBothVirtualAndBase test.
|
||||
// -> IL has no concept of 'protected', it's called IsFamily there.
|
||||
cmd.IsPublic = false;
|
||||
cmd.IsFamily = true;
|
||||
|
||||
// add parameters
|
||||
foreach (ParameterDefinition pd in md.Parameters)
|
||||
{
|
||||
cmd.Parameters.Add(new ParameterDefinition(pd.Name, ParameterAttributes.None, pd.ParameterType));
|
||||
}
|
||||
|
||||
// swap bodies
|
||||
(cmd.Body, md.Body) = (md.Body, cmd.Body);
|
||||
|
||||
// Move over all the debugging information
|
||||
foreach (SequencePoint sequencePoint in md.DebugInformation.SequencePoints)
|
||||
cmd.DebugInformation.SequencePoints.Add(sequencePoint);
|
||||
md.DebugInformation.SequencePoints.Clear();
|
||||
|
||||
foreach (CustomDebugInformation customInfo in md.CustomDebugInformations)
|
||||
cmd.CustomDebugInformations.Add(customInfo);
|
||||
md.CustomDebugInformations.Clear();
|
||||
|
||||
(md.DebugInformation.Scope, cmd.DebugInformation.Scope) = (cmd.DebugInformation.Scope, md.DebugInformation.Scope);
|
||||
|
||||
td.Methods.Add(cmd);
|
||||
|
||||
FixRemoteCallToBaseMethod(td, cmd);
|
||||
return cmd;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds and fixes call to base methods within remote calls
|
||||
/// <para>For example, changes `base.CmdDoSomething` to `base.CallCmdDoSomething` within `this.CallCmdDoSomething`</para>
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="method"></param>
|
||||
public static void FixRemoteCallToBaseMethod(TypeDefinition type, MethodDefinition method)
|
||||
{
|
||||
string callName = method.Name;
|
||||
|
||||
// Cmd/rpc start with Weaver.RpcPrefix
|
||||
// e.g. CallCmdDoSomething
|
||||
if (!callName.StartsWith(RpcPrefix))
|
||||
return;
|
||||
|
||||
// e.g. CmdDoSomething
|
||||
string baseRemoteCallName = method.Name.Substring(RpcPrefix.Length);
|
||||
|
||||
foreach (Instruction instruction in method.Body.Instructions)
|
||||
{
|
||||
// if call to base.CmdDoSomething within this.CallCmdDoSomething
|
||||
if (IsCallToMethod(instruction, out MethodDefinition calledMethod) &&
|
||||
calledMethod.Name == baseRemoteCallName)
|
||||
{
|
||||
TypeDefinition baseType = type.BaseType.Resolve();
|
||||
MethodDefinition baseMethod = baseType.GetMethodInBaseType(callName);
|
||||
|
||||
if (baseMethod == null)
|
||||
{
|
||||
Weaver.Error($"Could not find base method for {callName}", method);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!baseMethod.IsVirtual)
|
||||
{
|
||||
Weaver.Error($"Could not find base method that was virutal {callName}", method);
|
||||
return;
|
||||
}
|
||||
|
||||
instruction.Operand = baseMethod;
|
||||
|
||||
Weaver.DLog(type, "Replacing call to '{0}' with '{1}' inside '{2}'", calledMethod.FullName, baseMethod.FullName, method.FullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool IsCallToMethod(Instruction instruction, out MethodDefinition calledMethod)
|
||||
{
|
||||
if (instruction.OpCode == OpCodes.Call &&
|
||||
instruction.Operand is MethodDefinition method)
|
||||
{
|
||||
calledMethod = method;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
calledMethod = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
public static class MethodProcessor
|
||||
{
|
||||
const string RpcPrefix = "UserCode_";
|
||||
|
||||
// creates a method substitute
|
||||
// For example, if we have this:
|
||||
// public void CmdThrust(float thrusting, int spin)
|
||||
// {
|
||||
// xxxxx
|
||||
// }
|
||||
//
|
||||
// it will substitute the method and move the code to a new method with a provided name
|
||||
// for example:
|
||||
//
|
||||
// public void CmdTrust(float thrusting, int spin)
|
||||
// {
|
||||
// }
|
||||
//
|
||||
// public void <newName>(float thrusting, int spin)
|
||||
// {
|
||||
// xxxxx
|
||||
// }
|
||||
//
|
||||
// Note that all the calls to the method remain untouched
|
||||
//
|
||||
// the original method definition loses all code
|
||||
// this returns the newly created method with all the user provided code
|
||||
public static MethodDefinition SubstituteMethod(Logger Log, TypeDefinition td, MethodDefinition md, ref bool WeavingFailed)
|
||||
{
|
||||
string newName = RpcPrefix + md.Name;
|
||||
MethodDefinition cmd = new MethodDefinition(newName, md.Attributes, md.ReturnType);
|
||||
|
||||
// force the substitute method to be protected.
|
||||
// -> public would show in the Inspector for UnityEvents as
|
||||
// User_CmdUsePotion() etc. but the user shouldn't use those.
|
||||
// -> private would not allow inheriting classes to call it, see
|
||||
// OverrideVirtualWithBaseCallsBothVirtualAndBase test.
|
||||
// -> IL has no concept of 'protected', it's called IsFamily there.
|
||||
cmd.IsPublic = false;
|
||||
cmd.IsFamily = true;
|
||||
|
||||
// add parameters
|
||||
foreach (ParameterDefinition pd in md.Parameters)
|
||||
{
|
||||
cmd.Parameters.Add(new ParameterDefinition(pd.Name, ParameterAttributes.None, pd.ParameterType));
|
||||
}
|
||||
|
||||
// swap bodies
|
||||
(cmd.Body, md.Body) = (md.Body, cmd.Body);
|
||||
|
||||
// Move over all the debugging information
|
||||
foreach (SequencePoint sequencePoint in md.DebugInformation.SequencePoints)
|
||||
cmd.DebugInformation.SequencePoints.Add(sequencePoint);
|
||||
md.DebugInformation.SequencePoints.Clear();
|
||||
|
||||
foreach (CustomDebugInformation customInfo in md.CustomDebugInformations)
|
||||
cmd.CustomDebugInformations.Add(customInfo);
|
||||
md.CustomDebugInformations.Clear();
|
||||
|
||||
(md.DebugInformation.Scope, cmd.DebugInformation.Scope) = (cmd.DebugInformation.Scope, md.DebugInformation.Scope);
|
||||
|
||||
td.Methods.Add(cmd);
|
||||
|
||||
FixRemoteCallToBaseMethod(Log, td, cmd, ref WeavingFailed);
|
||||
return cmd;
|
||||
}
|
||||
|
||||
// Finds and fixes call to base methods within remote calls
|
||||
//For example, changes `base.CmdDoSomething` to `base.CallCmdDoSomething` within `this.CallCmdDoSomething`
|
||||
public static void FixRemoteCallToBaseMethod(Logger Log, TypeDefinition type, MethodDefinition method, ref bool WeavingFailed)
|
||||
{
|
||||
string callName = method.Name;
|
||||
|
||||
// Cmd/rpc start with Weaver.RpcPrefix
|
||||
// e.g. CallCmdDoSomething
|
||||
if (!callName.StartsWith(RpcPrefix))
|
||||
return;
|
||||
|
||||
// e.g. CmdDoSomething
|
||||
string baseRemoteCallName = method.Name.Substring(RpcPrefix.Length);
|
||||
|
||||
foreach (Instruction instruction in method.Body.Instructions)
|
||||
{
|
||||
// if call to base.CmdDoSomething within this.CallCmdDoSomething
|
||||
if (IsCallToMethod(instruction, out MethodDefinition calledMethod) &&
|
||||
calledMethod.Name == baseRemoteCallName)
|
||||
{
|
||||
TypeDefinition baseType = type.BaseType.Resolve();
|
||||
MethodDefinition baseMethod = baseType.GetMethodInBaseType(callName);
|
||||
|
||||
if (baseMethod == null)
|
||||
{
|
||||
Log.Error($"Could not find base method for {callName}", method);
|
||||
WeavingFailed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!baseMethod.IsVirtual)
|
||||
{
|
||||
Log.Error($"Could not find base method that was virtual {callName}", method);
|
||||
WeavingFailed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
instruction.Operand = baseMethod;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool IsCallToMethod(Instruction instruction, out MethodDefinition calledMethod)
|
||||
{
|
||||
if (instruction.OpCode == OpCodes.Call &&
|
||||
instruction.Operand is MethodDefinition method)
|
||||
{
|
||||
calledMethod = method;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
calledMethod = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 661e1af528e3441f79e1552fb5ec4e0e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
fileFormatVersion: 2
|
||||
guid: 661e1af528e3441f79e1552fb5ec4e0e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData: ''
|
||||
assetBundleName: ''
|
||||
assetBundleVariant: ''
|
||||
|
||||
@@ -1,45 +1,56 @@
|
||||
using Mono.CecilX;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
/// <summary>
|
||||
/// only shows warnings in case we use SyncVars etc. for MonoBehaviour.
|
||||
/// </summary>
|
||||
static class MonoBehaviourProcessor
|
||||
{
|
||||
public static void Process(TypeDefinition td)
|
||||
{
|
||||
ProcessSyncVars(td);
|
||||
ProcessMethods(td);
|
||||
}
|
||||
|
||||
static void ProcessSyncVars(TypeDefinition td)
|
||||
{
|
||||
// find syncvars
|
||||
foreach (FieldDefinition fd in td.Fields)
|
||||
{
|
||||
if (fd.HasCustomAttribute<SyncVarAttribute>())
|
||||
Weaver.Error($"SyncVar {fd.Name} must be inside a NetworkBehaviour. {td.Name} is not a NetworkBehaviour", fd);
|
||||
|
||||
if (SyncObjectInitializer.ImplementsSyncObject(fd.FieldType))
|
||||
{
|
||||
Weaver.Error($"{fd.Name} is a SyncObject and must be inside a NetworkBehaviour. {td.Name} is not a NetworkBehaviour", fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ProcessMethods(TypeDefinition td)
|
||||
{
|
||||
// find command and RPC functions
|
||||
foreach (MethodDefinition md in td.Methods)
|
||||
{
|
||||
if (md.HasCustomAttribute<CommandAttribute>())
|
||||
Weaver.Error($"Command {md.Name} must be declared inside a NetworkBehaviour", md);
|
||||
if (md.HasCustomAttribute<ClientRpcAttribute>())
|
||||
Weaver.Error($"ClientRpc {md.Name} must be declared inside a NetworkBehaviour", md);
|
||||
if (md.HasCustomAttribute<TargetRpcAttribute>())
|
||||
Weaver.Error($"TargetRpc {md.Name} must be declared inside a NetworkBehaviour", md);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
using Mono.CecilX;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
// only shows warnings in case we use SyncVars etc. for MonoBehaviour.
|
||||
static class MonoBehaviourProcessor
|
||||
{
|
||||
public static void Process(Logger Log, TypeDefinition td, ref bool WeavingFailed)
|
||||
{
|
||||
ProcessSyncVars(Log, td, ref WeavingFailed);
|
||||
ProcessMethods(Log, td, ref WeavingFailed);
|
||||
}
|
||||
|
||||
static void ProcessSyncVars(Logger Log, TypeDefinition td, ref bool WeavingFailed)
|
||||
{
|
||||
// find syncvars
|
||||
foreach (FieldDefinition fd in td.Fields)
|
||||
{
|
||||
if (fd.HasCustomAttribute<SyncVarAttribute>())
|
||||
{
|
||||
Log.Error($"SyncVar {fd.Name} must be inside a NetworkBehaviour. {td.Name} is not a NetworkBehaviour", fd);
|
||||
WeavingFailed = true;
|
||||
}
|
||||
|
||||
if (SyncObjectInitializer.ImplementsSyncObject(fd.FieldType))
|
||||
{
|
||||
Log.Error($"{fd.Name} is a SyncObject and must be inside a NetworkBehaviour. {td.Name} is not a NetworkBehaviour", fd);
|
||||
WeavingFailed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ProcessMethods(Logger Log, TypeDefinition td, ref bool WeavingFailed)
|
||||
{
|
||||
// find command and RPC functions
|
||||
foreach (MethodDefinition md in td.Methods)
|
||||
{
|
||||
if (md.HasCustomAttribute<CommandAttribute>())
|
||||
{
|
||||
Log.Error($"Command {md.Name} must be declared inside a NetworkBehaviour", md);
|
||||
WeavingFailed = true;
|
||||
}
|
||||
if (md.HasCustomAttribute<ClientRpcAttribute>())
|
||||
{
|
||||
Log.Error($"ClientRpc {md.Name} must be declared inside a NetworkBehaviour", md);
|
||||
WeavingFailed = true;
|
||||
}
|
||||
if (md.HasCustomAttribute<TargetRpcAttribute>())
|
||||
{
|
||||
Log.Error($"TargetRpc {md.Name} must be declared inside a NetworkBehaviour", md);
|
||||
WeavingFailed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 35c16722912b64af894e4f6668f2e54c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
fileFormatVersion: 2
|
||||
guid: 35c16722912b64af894e4f6668f2e54c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData: ''
|
||||
assetBundleName: ''
|
||||
assetBundleVariant: ''
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8118d606be3214e5d99943ec39530dd8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
fileFormatVersion: 2
|
||||
guid: 8118d606be3214e5d99943ec39530dd8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData: ''
|
||||
assetBundleName: ''
|
||||
assetBundleVariant: ''
|
||||
|
||||
@@ -1,179 +1,216 @@
|
||||
// finds all readers and writers and register them
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Compilation;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
public static class ReaderWriterProcessor
|
||||
{
|
||||
public static bool Process(AssemblyDefinition CurrentAssembly)
|
||||
{
|
||||
Readers.Init();
|
||||
Writers.Init();
|
||||
foreach (Assembly unityAsm in CompilationPipeline.GetAssemblies())
|
||||
{
|
||||
if (unityAsm.name == "Mirror")
|
||||
{
|
||||
using (DefaultAssemblyResolver asmResolver = new DefaultAssemblyResolver())
|
||||
using (AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(unityAsm.outputPath, new ReaderParameters { ReadWrite = false, ReadSymbols = false, AssemblyResolver = asmResolver }))
|
||||
{
|
||||
ProcessAssemblyClasses(CurrentAssembly, assembly);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ProcessAssemblyClasses(CurrentAssembly, CurrentAssembly);
|
||||
}
|
||||
|
||||
static bool ProcessAssemblyClasses(AssemblyDefinition CurrentAssembly, AssemblyDefinition assembly)
|
||||
{
|
||||
bool modified = false;
|
||||
foreach (TypeDefinition klass in assembly.MainModule.Types)
|
||||
{
|
||||
// extension methods only live in static classes
|
||||
// static classes are represented as sealed and abstract
|
||||
if (klass.IsAbstract && klass.IsSealed)
|
||||
{
|
||||
// if assembly has any declared writers then it is "modified"
|
||||
modified |= LoadDeclaredWriters(CurrentAssembly, klass);
|
||||
modified |= LoadDeclaredReaders(CurrentAssembly, klass);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (TypeDefinition klass in assembly.MainModule.Types)
|
||||
{
|
||||
// if assembly has any network message then it is modified
|
||||
modified |= LoadMessageReadWriter(CurrentAssembly.MainModule, klass);
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
static bool LoadMessageReadWriter(ModuleDefinition module, TypeDefinition klass)
|
||||
{
|
||||
bool modified = false;
|
||||
if (!klass.IsAbstract && !klass.IsInterface && klass.ImplementsInterface<NetworkMessage>())
|
||||
{
|
||||
Readers.GetReadFunc(module.ImportReference(klass));
|
||||
Writers.GetWriteFunc(module.ImportReference(klass));
|
||||
modified = true;
|
||||
}
|
||||
|
||||
foreach (TypeDefinition td in klass.NestedTypes)
|
||||
{
|
||||
modified |= LoadMessageReadWriter(module, td);
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
static bool LoadDeclaredWriters(AssemblyDefinition currentAssembly, TypeDefinition klass)
|
||||
{
|
||||
// register all the writers in this class. Skip the ones with wrong signature
|
||||
bool modified = false;
|
||||
foreach (MethodDefinition method in klass.Methods)
|
||||
{
|
||||
if (method.Parameters.Count != 2)
|
||||
continue;
|
||||
|
||||
if (!method.Parameters[0].ParameterType.Is<NetworkWriter>())
|
||||
continue;
|
||||
|
||||
if (!method.ReturnType.Is(typeof(void)))
|
||||
continue;
|
||||
|
||||
if (!method.HasCustomAttribute<System.Runtime.CompilerServices.ExtensionAttribute>())
|
||||
continue;
|
||||
|
||||
if (method.HasGenericParameters)
|
||||
continue;
|
||||
|
||||
TypeReference dataType = method.Parameters[1].ParameterType;
|
||||
Writers.Register(dataType, currentAssembly.MainModule.ImportReference(method));
|
||||
modified = true;
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
static bool LoadDeclaredReaders(AssemblyDefinition currentAssembly, TypeDefinition klass)
|
||||
{
|
||||
// register all the reader in this class. Skip the ones with wrong signature
|
||||
bool modified = false;
|
||||
foreach (MethodDefinition method in klass.Methods)
|
||||
{
|
||||
if (method.Parameters.Count != 1)
|
||||
continue;
|
||||
|
||||
if (!method.Parameters[0].ParameterType.Is<NetworkReader>())
|
||||
continue;
|
||||
|
||||
if (method.ReturnType.Is(typeof(void)))
|
||||
continue;
|
||||
|
||||
if (!method.HasCustomAttribute<System.Runtime.CompilerServices.ExtensionAttribute>())
|
||||
continue;
|
||||
|
||||
if (method.HasGenericParameters)
|
||||
continue;
|
||||
|
||||
Readers.Register(method.ReturnType, currentAssembly.MainModule.ImportReference(method));
|
||||
modified = true;
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
static bool IsEditorAssembly(AssemblyDefinition currentAssembly)
|
||||
{
|
||||
// we want to add the [InitializeOnLoad] attribute if it's available
|
||||
// -> usually either 'UnityEditor' or 'UnityEditor.CoreModule'
|
||||
return currentAssembly.MainModule.AssemblyReferences.Any(assemblyReference =>
|
||||
assemblyReference.Name.StartsWith(nameof(UnityEditor))
|
||||
);
|
||||
}
|
||||
|
||||
// adds Mirror.GeneratedNetworkCode.InitReadWriters() method that
|
||||
// registers all generated writers into Mirror.Writer<T> static class.
|
||||
// -> uses [RuntimeInitializeOnLoad] attribute so it's invoke at runtime
|
||||
// -> uses [InitializeOnLoad] if UnityEditor is referenced so it works
|
||||
// in Editor and in tests too
|
||||
//
|
||||
// use ILSpy to see the result (it's in the DLL's 'Mirror' namespace)
|
||||
public static void InitializeReaderAndWriters(AssemblyDefinition currentAssembly)
|
||||
{
|
||||
MethodDefinition rwInitializer = new MethodDefinition("InitReadWriters", MethodAttributes.Public |
|
||||
MethodAttributes.Static,
|
||||
WeaverTypes.Import(typeof(void)));
|
||||
|
||||
// add [RuntimeInitializeOnLoad] in any case
|
||||
System.Reflection.ConstructorInfo attributeconstructor = typeof(RuntimeInitializeOnLoadMethodAttribute).GetConstructor(new[] { typeof(RuntimeInitializeLoadType) });
|
||||
CustomAttribute customAttributeRef = new CustomAttribute(currentAssembly.MainModule.ImportReference(attributeconstructor));
|
||||
customAttributeRef.ConstructorArguments.Add(new CustomAttributeArgument(WeaverTypes.Import<RuntimeInitializeLoadType>(), RuntimeInitializeLoadType.BeforeSceneLoad));
|
||||
rwInitializer.CustomAttributes.Add(customAttributeRef);
|
||||
|
||||
// add [InitializeOnLoad] if UnityEditor is referenced
|
||||
if (IsEditorAssembly(currentAssembly))
|
||||
{
|
||||
System.Reflection.ConstructorInfo initializeOnLoadConstructor = typeof(InitializeOnLoadMethodAttribute).GetConstructor(new Type[0]);
|
||||
CustomAttribute initializeCustomConstructorRef = new CustomAttribute(currentAssembly.MainModule.ImportReference(initializeOnLoadConstructor));
|
||||
rwInitializer.CustomAttributes.Add(initializeCustomConstructorRef);
|
||||
}
|
||||
|
||||
// fill function body with reader/writer initializers
|
||||
ILProcessor worker = rwInitializer.Body.GetILProcessor();
|
||||
|
||||
// for debugging: add a log to see if initialized on load
|
||||
//worker.Emit(OpCodes.Ldstr, $"[InitReadWriters] called!");
|
||||
//worker.Emit(OpCodes.Call, WeaverTypes.logWarningReference);
|
||||
|
||||
Writers.InitializeWriters(worker);
|
||||
Readers.InitializeReaders(worker);
|
||||
|
||||
worker.Emit(OpCodes.Ret);
|
||||
|
||||
Weaver.GeneratedCodeClass.Methods.Add(rwInitializer);
|
||||
}
|
||||
}
|
||||
}
|
||||
// finds all readers and writers and register them
|
||||
using System.Linq;
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
using Mono.CecilX.Rocks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
public static class ReaderWriterProcessor
|
||||
{
|
||||
public static bool Process(AssemblyDefinition CurrentAssembly, IAssemblyResolver resolver, Logger Log, Writers writers, Readers readers, ref bool WeavingFailed)
|
||||
{
|
||||
// find NetworkReader/Writer extensions from Mirror.dll first.
|
||||
// and NetworkMessage custom writer/reader extensions.
|
||||
// NOTE: do not include this result in our 'modified' return value,
|
||||
// otherwise Unity crashes when running tests
|
||||
ProcessMirrorAssemblyClasses(CurrentAssembly, resolver, Log, writers, readers, ref WeavingFailed);
|
||||
|
||||
// find readers/writers in the assembly we are in right now.
|
||||
return ProcessAssemblyClasses(CurrentAssembly, CurrentAssembly, writers, readers, ref WeavingFailed);
|
||||
}
|
||||
|
||||
static void ProcessMirrorAssemblyClasses(AssemblyDefinition CurrentAssembly, IAssemblyResolver resolver, Logger Log, Writers writers, Readers readers, ref bool WeavingFailed)
|
||||
{
|
||||
// find Mirror.dll in assembly's references.
|
||||
// those are guaranteed to be resolvable and correct.
|
||||
// after all, it references them :)
|
||||
AssemblyNameReference mirrorAssemblyReference = CurrentAssembly.MainModule.FindReference(Weaver.MirrorAssemblyName);
|
||||
if (mirrorAssemblyReference != null)
|
||||
{
|
||||
// resolve the assembly to load the AssemblyDefinition.
|
||||
// we need to search all types in it.
|
||||
// if we only were to resolve one known type like in WeaverTypes,
|
||||
// then we wouldn't need it.
|
||||
AssemblyDefinition mirrorAssembly = resolver.Resolve(mirrorAssemblyReference);
|
||||
if (mirrorAssembly != null)
|
||||
{
|
||||
ProcessAssemblyClasses(CurrentAssembly, mirrorAssembly, writers, readers, ref WeavingFailed);
|
||||
}
|
||||
else Log.Error($"Failed to resolve {mirrorAssemblyReference}");
|
||||
}
|
||||
else Log.Error("Failed to find Mirror AssemblyNameReference. Can't register Mirror.dll readers/writers.");
|
||||
}
|
||||
|
||||
static bool ProcessAssemblyClasses(AssemblyDefinition CurrentAssembly, AssemblyDefinition assembly, Writers writers, Readers readers, ref bool WeavingFailed)
|
||||
{
|
||||
bool modified = false;
|
||||
foreach (TypeDefinition klass in assembly.MainModule.Types)
|
||||
{
|
||||
// extension methods only live in static classes
|
||||
// static classes are represented as sealed and abstract
|
||||
if (klass.IsAbstract && klass.IsSealed)
|
||||
{
|
||||
// if assembly has any declared writers then it is "modified"
|
||||
modified |= LoadDeclaredWriters(CurrentAssembly, klass, writers);
|
||||
modified |= LoadDeclaredReaders(CurrentAssembly, klass, readers);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (TypeDefinition klass in assembly.MainModule.Types)
|
||||
{
|
||||
// if assembly has any network message then it is modified
|
||||
modified |= LoadMessageReadWriter(CurrentAssembly.MainModule, writers, readers, klass, ref WeavingFailed);
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
static bool LoadMessageReadWriter(ModuleDefinition module, Writers writers, Readers readers, TypeDefinition klass, ref bool WeavingFailed)
|
||||
{
|
||||
bool modified = false;
|
||||
if (!klass.IsAbstract && !klass.IsInterface && klass.ImplementsInterface<NetworkMessage>())
|
||||
{
|
||||
readers.GetReadFunc(module.ImportReference(klass), ref WeavingFailed);
|
||||
writers.GetWriteFunc(module.ImportReference(klass), ref WeavingFailed);
|
||||
modified = true;
|
||||
}
|
||||
|
||||
foreach (TypeDefinition td in klass.NestedTypes)
|
||||
{
|
||||
modified |= LoadMessageReadWriter(module, writers, readers, td, ref WeavingFailed);
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
static bool LoadDeclaredWriters(AssemblyDefinition currentAssembly, TypeDefinition klass, Writers writers)
|
||||
{
|
||||
// register all the writers in this class. Skip the ones with wrong signature
|
||||
bool modified = false;
|
||||
foreach (MethodDefinition method in klass.Methods)
|
||||
{
|
||||
if (method.Parameters.Count != 2)
|
||||
continue;
|
||||
|
||||
if (!method.Parameters[0].ParameterType.Is<NetworkWriter>())
|
||||
continue;
|
||||
|
||||
if (!method.ReturnType.Is(typeof(void)))
|
||||
continue;
|
||||
|
||||
if (!method.HasCustomAttribute<System.Runtime.CompilerServices.ExtensionAttribute>())
|
||||
continue;
|
||||
|
||||
if (method.HasGenericParameters)
|
||||
continue;
|
||||
|
||||
TypeReference dataType = method.Parameters[1].ParameterType;
|
||||
writers.Register(dataType, currentAssembly.MainModule.ImportReference(method));
|
||||
modified = true;
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
static bool LoadDeclaredReaders(AssemblyDefinition currentAssembly, TypeDefinition klass, Readers readers)
|
||||
{
|
||||
// register all the reader in this class. Skip the ones with wrong signature
|
||||
bool modified = false;
|
||||
foreach (MethodDefinition method in klass.Methods)
|
||||
{
|
||||
if (method.Parameters.Count != 1)
|
||||
continue;
|
||||
|
||||
if (!method.Parameters[0].ParameterType.Is<NetworkReader>())
|
||||
continue;
|
||||
|
||||
if (method.ReturnType.Is(typeof(void)))
|
||||
continue;
|
||||
|
||||
if (!method.HasCustomAttribute<System.Runtime.CompilerServices.ExtensionAttribute>())
|
||||
continue;
|
||||
|
||||
if (method.HasGenericParameters)
|
||||
continue;
|
||||
|
||||
readers.Register(method.ReturnType, currentAssembly.MainModule.ImportReference(method));
|
||||
modified = true;
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
// helper function to add [RuntimeInitializeOnLoad] attribute to method
|
||||
static void AddRuntimeInitializeOnLoadAttribute(AssemblyDefinition assembly, WeaverTypes weaverTypes, MethodDefinition method)
|
||||
{
|
||||
// NOTE: previously we used reflection because according paul,
|
||||
// 'weaving Mirror.dll caused unity to rebuild all dlls but in wrong
|
||||
// order, which breaks rewired'
|
||||
// it's not obvious why importing an attribute via reflection instead
|
||||
// of cecil would break anything. let's use cecil.
|
||||
|
||||
// to add a CustomAttribute, we need the attribute's constructor.
|
||||
// in this case, there are two: empty, and RuntimeInitializeOnLoadType.
|
||||
// we want the last one, with the type parameter.
|
||||
MethodDefinition ctor = weaverTypes.runtimeInitializeOnLoadMethodAttribute.GetConstructors().Last();
|
||||
//MethodDefinition ctor = weaverTypes.runtimeInitializeOnLoadMethodAttribute.GetConstructors().First();
|
||||
// using ctor directly throws: ArgumentException: Member 'System.Void UnityEditor.InitializeOnLoadMethodAttribute::.ctor()' is declared in another module and needs to be imported
|
||||
// we need to import it first.
|
||||
CustomAttribute attribute = new CustomAttribute(assembly.MainModule.ImportReference(ctor));
|
||||
// add the RuntimeInitializeLoadType.BeforeSceneLoad argument to ctor
|
||||
attribute.ConstructorArguments.Add(new CustomAttributeArgument(weaverTypes.Import<RuntimeInitializeLoadType>(), RuntimeInitializeLoadType.BeforeSceneLoad));
|
||||
method.CustomAttributes.Add(attribute);
|
||||
}
|
||||
|
||||
// helper function to add [InitializeOnLoad] attribute to method
|
||||
// (only works in Editor assemblies. check IsEditorAssembly first.)
|
||||
static void AddInitializeOnLoadAttribute(AssemblyDefinition assembly, WeaverTypes weaverTypes, MethodDefinition method)
|
||||
{
|
||||
// NOTE: previously we used reflection because according paul,
|
||||
// 'weaving Mirror.dll caused unity to rebuild all dlls but in wrong
|
||||
// order, which breaks rewired'
|
||||
// it's not obvious why importing an attribute via reflection instead
|
||||
// of cecil would break anything. let's use cecil.
|
||||
|
||||
// to add a CustomAttribute, we need the attribute's constructor.
|
||||
// in this case, there's only one - and it's an empty constructor.
|
||||
MethodDefinition ctor = weaverTypes.initializeOnLoadMethodAttribute.GetConstructors().First();
|
||||
// using ctor directly throws: ArgumentException: Member 'System.Void UnityEditor.InitializeOnLoadMethodAttribute::.ctor()' is declared in another module and needs to be imported
|
||||
// we need to import it first.
|
||||
CustomAttribute attribute = new CustomAttribute(assembly.MainModule.ImportReference(ctor));
|
||||
method.CustomAttributes.Add(attribute);
|
||||
}
|
||||
|
||||
// adds Mirror.GeneratedNetworkCode.InitReadWriters() method that
|
||||
// registers all generated writers into Mirror.Writer<T> static class.
|
||||
// -> uses [RuntimeInitializeOnLoad] attribute so it's invoke at runtime
|
||||
// -> uses [InitializeOnLoad] if UnityEditor is referenced so it works
|
||||
// in Editor and in tests too
|
||||
//
|
||||
// use ILSpy to see the result (it's in the DLL's 'Mirror' namespace)
|
||||
public static void InitializeReaderAndWriters(AssemblyDefinition currentAssembly, WeaverTypes weaverTypes, Writers writers, Readers readers, TypeDefinition GeneratedCodeClass)
|
||||
{
|
||||
MethodDefinition initReadWriters = new MethodDefinition("InitReadWriters", MethodAttributes.Public |
|
||||
MethodAttributes.Static,
|
||||
weaverTypes.Import(typeof(void)));
|
||||
|
||||
// add [RuntimeInitializeOnLoad] in any case
|
||||
AddRuntimeInitializeOnLoadAttribute(currentAssembly, weaverTypes, initReadWriters);
|
||||
|
||||
// add [InitializeOnLoad] if UnityEditor is referenced
|
||||
if (Helpers.IsEditorAssembly(currentAssembly))
|
||||
{
|
||||
AddInitializeOnLoadAttribute(currentAssembly, weaverTypes, initReadWriters);
|
||||
}
|
||||
|
||||
// fill function body with reader/writer initializers
|
||||
ILProcessor worker = initReadWriters.Body.GetILProcessor();
|
||||
// for debugging: add a log to see if initialized on load
|
||||
//worker.Emit(OpCodes.Ldstr, $"[InitReadWriters] called!");
|
||||
//worker.Emit(OpCodes.Call, Weaver.weaverTypes.logWarningReference);
|
||||
writers.InitializeWriters(worker);
|
||||
readers.InitializeReaders(worker);
|
||||
worker.Emit(OpCodes.Ret);
|
||||
|
||||
GeneratedCodeClass.Methods.Add(initReadWriters);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f3263602f0a374ecd8d08588b1fc2f76
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
fileFormatVersion: 2
|
||||
guid: f3263602f0a374ecd8d08588b1fc2f76
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData: ''
|
||||
assetBundleName: ''
|
||||
assetBundleVariant: ''
|
||||
|
||||
@@ -1,106 +1,102 @@
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
/// <summary>
|
||||
/// Processes [Rpc] methods in NetworkBehaviour
|
||||
/// </summary>
|
||||
public static class RpcProcessor
|
||||
{
|
||||
public static MethodDefinition ProcessRpcInvoke(TypeDefinition td, MethodDefinition md, MethodDefinition rpcCallFunc)
|
||||
{
|
||||
MethodDefinition rpc = new MethodDefinition(
|
||||
Weaver.InvokeRpcPrefix + md.Name,
|
||||
MethodAttributes.Family | MethodAttributes.Static | MethodAttributes.HideBySig,
|
||||
WeaverTypes.Import(typeof(void)));
|
||||
|
||||
ILProcessor worker = rpc.Body.GetILProcessor();
|
||||
Instruction label = worker.Create(OpCodes.Nop);
|
||||
|
||||
NetworkBehaviourProcessor.WriteClientActiveCheck(worker, md.Name, label, "RPC");
|
||||
|
||||
// setup for reader
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Castclass, td);
|
||||
|
||||
if (!NetworkBehaviourProcessor.ReadArguments(md, worker, RemoteCallType.ClientRpc))
|
||||
return null;
|
||||
|
||||
// invoke actual command function
|
||||
worker.Emit(OpCodes.Callvirt, rpcCallFunc);
|
||||
worker.Emit(OpCodes.Ret);
|
||||
|
||||
NetworkBehaviourProcessor.AddInvokeParameters(rpc.Parameters);
|
||||
td.Methods.Add(rpc);
|
||||
return rpc;
|
||||
}
|
||||
|
||||
/*
|
||||
* generates code like:
|
||||
|
||||
public void RpcTest (int param)
|
||||
{
|
||||
NetworkWriter writer = new NetworkWriter ();
|
||||
writer.WritePackedUInt32((uint)param);
|
||||
base.SendRPCInternal(typeof(class),"RpcTest", writer, 0);
|
||||
}
|
||||
public void CallRpcTest (int param)
|
||||
{
|
||||
// whatever the user did before
|
||||
}
|
||||
|
||||
Originally HLAPI put the send message code inside the Call function
|
||||
and then proceeded to replace every call to RpcTest with CallRpcTest
|
||||
|
||||
This method moves all the user's code into the "CallRpc" method
|
||||
and replaces the body of the original method with the send message code.
|
||||
This way we do not need to modify the code anywhere else, and this works
|
||||
correctly in dependent assemblies
|
||||
*/
|
||||
public static MethodDefinition ProcessRpcCall(TypeDefinition td, MethodDefinition md, CustomAttribute clientRpcAttr)
|
||||
{
|
||||
MethodDefinition rpc = MethodProcessor.SubstituteMethod(td, md);
|
||||
|
||||
ILProcessor worker = md.Body.GetILProcessor();
|
||||
|
||||
NetworkBehaviourProcessor.WriteSetupLocals(worker);
|
||||
|
||||
if (Weaver.GenerateLogErrors)
|
||||
{
|
||||
worker.Emit(OpCodes.Ldstr, "Call ClientRpc function " + md.Name);
|
||||
worker.Emit(OpCodes.Call, WeaverTypes.logErrorReference);
|
||||
}
|
||||
|
||||
NetworkBehaviourProcessor.WriteCreateWriter(worker);
|
||||
|
||||
// write all the arguments that the user passed to the Rpc call
|
||||
if (!NetworkBehaviourProcessor.WriteArguments(worker, md, RemoteCallType.ClientRpc))
|
||||
return null;
|
||||
|
||||
string rpcName = md.Name;
|
||||
int channel = clientRpcAttr.GetField("channel", 0);
|
||||
bool includeOwner = clientRpcAttr.GetField("includeOwner", true);
|
||||
|
||||
// invoke SendInternal and return
|
||||
// this
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldtoken, td);
|
||||
// invokerClass
|
||||
worker.Emit(OpCodes.Call, WeaverTypes.getTypeFromHandleReference);
|
||||
worker.Emit(OpCodes.Ldstr, rpcName);
|
||||
// writer
|
||||
worker.Emit(OpCodes.Ldloc_0);
|
||||
worker.Emit(OpCodes.Ldc_I4, channel);
|
||||
// includeOwner ? 1 : 0
|
||||
worker.Emit(includeOwner ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
|
||||
worker.Emit(OpCodes.Callvirt, WeaverTypes.sendRpcInternal);
|
||||
|
||||
NetworkBehaviourProcessor.WriteRecycleWriter(worker);
|
||||
|
||||
worker.Emit(OpCodes.Ret);
|
||||
|
||||
return rpc;
|
||||
}
|
||||
}
|
||||
}
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
// Processes [Rpc] methods in NetworkBehaviour
|
||||
public static class RpcProcessor
|
||||
{
|
||||
public static MethodDefinition ProcessRpcInvoke(WeaverTypes weaverTypes, Writers writers, Readers readers, Logger Log, TypeDefinition td, MethodDefinition md, MethodDefinition rpcCallFunc, ref bool WeavingFailed)
|
||||
{
|
||||
MethodDefinition rpc = new MethodDefinition(
|
||||
Weaver.InvokeRpcPrefix + md.Name,
|
||||
MethodAttributes.Family | MethodAttributes.Static | MethodAttributes.HideBySig,
|
||||
weaverTypes.Import(typeof(void)));
|
||||
|
||||
ILProcessor worker = rpc.Body.GetILProcessor();
|
||||
Instruction label = worker.Create(OpCodes.Nop);
|
||||
|
||||
NetworkBehaviourProcessor.WriteClientActiveCheck(worker, weaverTypes, md.Name, label, "RPC");
|
||||
|
||||
// setup for reader
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Castclass, td);
|
||||
|
||||
if (!NetworkBehaviourProcessor.ReadArguments(md, readers, Log, worker, RemoteCallType.ClientRpc, ref WeavingFailed))
|
||||
return null;
|
||||
|
||||
// invoke actual command function
|
||||
worker.Emit(OpCodes.Callvirt, rpcCallFunc);
|
||||
worker.Emit(OpCodes.Ret);
|
||||
|
||||
NetworkBehaviourProcessor.AddInvokeParameters(weaverTypes, rpc.Parameters);
|
||||
td.Methods.Add(rpc);
|
||||
return rpc;
|
||||
}
|
||||
|
||||
/*
|
||||
* generates code like:
|
||||
|
||||
public void RpcTest (int param)
|
||||
{
|
||||
NetworkWriter writer = new NetworkWriter ();
|
||||
writer.WritePackedUInt32((uint)param);
|
||||
base.SendRPCInternal(typeof(class),"RpcTest", writer, 0);
|
||||
}
|
||||
public void CallRpcTest (int param)
|
||||
{
|
||||
// whatever the user did before
|
||||
}
|
||||
|
||||
Originally HLAPI put the send message code inside the Call function
|
||||
and then proceeded to replace every call to RpcTest with CallRpcTest
|
||||
|
||||
This method moves all the user's code into the "CallRpc" method
|
||||
and replaces the body of the original method with the send message code.
|
||||
This way we do not need to modify the code anywhere else, and this works
|
||||
correctly in dependent assemblies
|
||||
*/
|
||||
public static MethodDefinition ProcessRpcCall(WeaverTypes weaverTypes, Writers writers, Logger Log, TypeDefinition td, MethodDefinition md, CustomAttribute clientRpcAttr, ref bool WeavingFailed)
|
||||
{
|
||||
MethodDefinition rpc = MethodProcessor.SubstituteMethod(Log, td, md, ref WeavingFailed);
|
||||
|
||||
ILProcessor worker = md.Body.GetILProcessor();
|
||||
|
||||
NetworkBehaviourProcessor.WriteSetupLocals(worker, weaverTypes);
|
||||
|
||||
// add a log message if needed for debugging
|
||||
//worker.Emit(OpCodes.Ldstr, $"Call ClientRpc function {md.Name}");
|
||||
//worker.Emit(OpCodes.Call, WeaverTypes.logErrorReference);
|
||||
|
||||
NetworkBehaviourProcessor.WriteCreateWriter(worker, weaverTypes);
|
||||
|
||||
// write all the arguments that the user passed to the Rpc call
|
||||
if (!NetworkBehaviourProcessor.WriteArguments(worker, writers, Log, md, RemoteCallType.ClientRpc, ref WeavingFailed))
|
||||
return null;
|
||||
|
||||
string rpcName = md.Name;
|
||||
int channel = clientRpcAttr.GetField("channel", 0);
|
||||
bool includeOwner = clientRpcAttr.GetField("includeOwner", true);
|
||||
|
||||
// invoke SendInternal and return
|
||||
// this
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldtoken, td);
|
||||
// invokerClass
|
||||
worker.Emit(OpCodes.Call, weaverTypes.getTypeFromHandleReference);
|
||||
worker.Emit(OpCodes.Ldstr, rpcName);
|
||||
// writer
|
||||
worker.Emit(OpCodes.Ldloc_0);
|
||||
worker.Emit(OpCodes.Ldc_I4, channel);
|
||||
// includeOwner ? 1 : 0
|
||||
worker.Emit(includeOwner ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
|
||||
worker.Emit(OpCodes.Callvirt, weaverTypes.sendRpcInternal);
|
||||
|
||||
NetworkBehaviourProcessor.WriteRecycleWriter(worker, weaverTypes);
|
||||
|
||||
worker.Emit(OpCodes.Ret);
|
||||
|
||||
return rpc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a3cb7051ff41947e59bba58bdd2b73fc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
fileFormatVersion: 2
|
||||
guid: a3cb7051ff41947e59bba58bdd2b73fc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData: ''
|
||||
assetBundleName: ''
|
||||
assetBundleVariant: ''
|
||||
|
||||
@@ -1,153 +1,154 @@
|
||||
// Injects server/client active checks for [Server/Client] attributes
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
static class ServerClientAttributeProcessor
|
||||
{
|
||||
public static bool Process(TypeDefinition td)
|
||||
{
|
||||
bool modified = false;
|
||||
foreach (MethodDefinition md in td.Methods)
|
||||
{
|
||||
modified |= ProcessSiteMethod(md);
|
||||
}
|
||||
|
||||
foreach (TypeDefinition nested in td.NestedTypes)
|
||||
{
|
||||
modified |= Process(nested);
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
static bool ProcessSiteMethod(MethodDefinition md)
|
||||
{
|
||||
if (md.Name == ".cctor" ||
|
||||
md.Name == NetworkBehaviourProcessor.ProcessedFunctionName ||
|
||||
md.Name.StartsWith(Weaver.InvokeRpcPrefix))
|
||||
return false;
|
||||
|
||||
if (md.IsAbstract)
|
||||
{
|
||||
if (HasServerClientAttribute(md))
|
||||
{
|
||||
Weaver.Error("Server or Client Attributes can't be added to abstract method. Server and Client Attributes are not inherited so they need to be applied to the override methods instead.", md);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (md.Body != null && md.Body.Instructions != null)
|
||||
{
|
||||
return ProcessMethodAttributes(md);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool HasServerClientAttribute(MethodDefinition md)
|
||||
{
|
||||
foreach (CustomAttribute attr in md.CustomAttributes)
|
||||
{
|
||||
switch (attr.Constructor.DeclaringType.ToString())
|
||||
{
|
||||
case "Mirror.ServerAttribute":
|
||||
case "Mirror.ServerCallbackAttribute":
|
||||
case "Mirror.ClientAttribute":
|
||||
case "Mirror.ClientCallbackAttribute":
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool ProcessMethodAttributes(MethodDefinition md)
|
||||
{
|
||||
if (md.HasCustomAttribute<ServerAttribute>())
|
||||
InjectServerGuard(md, true);
|
||||
else if (md.HasCustomAttribute<ServerCallbackAttribute>())
|
||||
InjectServerGuard(md, false);
|
||||
else if (md.HasCustomAttribute<ClientAttribute>())
|
||||
InjectClientGuard(md, true);
|
||||
else if (md.HasCustomAttribute<ClientCallbackAttribute>())
|
||||
InjectClientGuard(md, false);
|
||||
else
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void InjectServerGuard(MethodDefinition md, bool logWarning)
|
||||
{
|
||||
ILProcessor worker = md.Body.GetILProcessor();
|
||||
Instruction top = md.Body.Instructions[0];
|
||||
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Call, WeaverTypes.NetworkServerGetActive));
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Brtrue, top));
|
||||
if (logWarning)
|
||||
{
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Ldstr, $"[Server] function '{md.FullName}' called when server was not active"));
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Call, WeaverTypes.logWarningReference));
|
||||
}
|
||||
InjectGuardParameters(md, worker, top);
|
||||
InjectGuardReturnValue(md, worker, top);
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Ret));
|
||||
}
|
||||
|
||||
static void InjectClientGuard(MethodDefinition md, bool logWarning)
|
||||
{
|
||||
ILProcessor worker = md.Body.GetILProcessor();
|
||||
Instruction top = md.Body.Instructions[0];
|
||||
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Call, WeaverTypes.NetworkClientGetActive));
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Brtrue, top));
|
||||
if (logWarning)
|
||||
{
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Ldstr, $"[Client] function '{md.FullName}' called when client was not active"));
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Call, WeaverTypes.logWarningReference));
|
||||
}
|
||||
|
||||
InjectGuardParameters(md, worker, top);
|
||||
InjectGuardReturnValue(md, worker, top);
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Ret));
|
||||
}
|
||||
|
||||
// this is required to early-out from a function with "ref" or "out" parameters
|
||||
static void InjectGuardParameters(MethodDefinition md, ILProcessor worker, Instruction top)
|
||||
{
|
||||
int offset = md.Resolve().IsStatic ? 0 : 1;
|
||||
for (int index = 0; index < md.Parameters.Count; index++)
|
||||
{
|
||||
ParameterDefinition param = md.Parameters[index];
|
||||
if (param.IsOut)
|
||||
{
|
||||
TypeReference elementType = param.ParameterType.GetElementType();
|
||||
|
||||
md.Body.Variables.Add(new VariableDefinition(elementType));
|
||||
md.Body.InitLocals = true;
|
||||
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Ldarg, index + offset));
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Ldloca_S, (byte)(md.Body.Variables.Count - 1)));
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Initobj, elementType));
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Ldloc, md.Body.Variables.Count - 1));
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Stobj, elementType));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this is required to early-out from a function with a return value.
|
||||
static void InjectGuardReturnValue(MethodDefinition md, ILProcessor worker, Instruction top)
|
||||
{
|
||||
if (!md.ReturnType.Is(typeof(void)))
|
||||
{
|
||||
md.Body.Variables.Add(new VariableDefinition(md.ReturnType));
|
||||
md.Body.InitLocals = true;
|
||||
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Ldloca_S, (byte)(md.Body.Variables.Count - 1)));
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Initobj, md.ReturnType));
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Ldloc, md.Body.Variables.Count - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Injects server/client active checks for [Server/Client] attributes
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
static class ServerClientAttributeProcessor
|
||||
{
|
||||
public static bool Process(WeaverTypes weaverTypes, Logger Log, TypeDefinition td, ref bool WeavingFailed)
|
||||
{
|
||||
bool modified = false;
|
||||
foreach (MethodDefinition md in td.Methods)
|
||||
{
|
||||
modified |= ProcessSiteMethod(weaverTypes, Log, md, ref WeavingFailed);
|
||||
}
|
||||
|
||||
foreach (TypeDefinition nested in td.NestedTypes)
|
||||
{
|
||||
modified |= Process(weaverTypes, Log, nested, ref WeavingFailed);
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
static bool ProcessSiteMethod(WeaverTypes weaverTypes, Logger Log, MethodDefinition md, ref bool WeavingFailed)
|
||||
{
|
||||
if (md.Name == ".cctor" ||
|
||||
md.Name == NetworkBehaviourProcessor.ProcessedFunctionName ||
|
||||
md.Name.StartsWith(Weaver.InvokeRpcPrefix))
|
||||
return false;
|
||||
|
||||
if (md.IsAbstract)
|
||||
{
|
||||
if (HasServerClientAttribute(md))
|
||||
{
|
||||
Log.Error("Server or Client Attributes can't be added to abstract method. Server and Client Attributes are not inherited so they need to be applied to the override methods instead.", md);
|
||||
WeavingFailed = true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (md.Body != null && md.Body.Instructions != null)
|
||||
{
|
||||
return ProcessMethodAttributes(weaverTypes, md);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool HasServerClientAttribute(MethodDefinition md)
|
||||
{
|
||||
foreach (CustomAttribute attr in md.CustomAttributes)
|
||||
{
|
||||
switch (attr.Constructor.DeclaringType.ToString())
|
||||
{
|
||||
case "Mirror.ServerAttribute":
|
||||
case "Mirror.ServerCallbackAttribute":
|
||||
case "Mirror.ClientAttribute":
|
||||
case "Mirror.ClientCallbackAttribute":
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool ProcessMethodAttributes(WeaverTypes weaverTypes, MethodDefinition md)
|
||||
{
|
||||
if (md.HasCustomAttribute<ServerAttribute>())
|
||||
InjectServerGuard(weaverTypes, md, true);
|
||||
else if (md.HasCustomAttribute<ServerCallbackAttribute>())
|
||||
InjectServerGuard(weaverTypes, md, false);
|
||||
else if (md.HasCustomAttribute<ClientAttribute>())
|
||||
InjectClientGuard(weaverTypes, md, true);
|
||||
else if (md.HasCustomAttribute<ClientCallbackAttribute>())
|
||||
InjectClientGuard(weaverTypes, md, false);
|
||||
else
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void InjectServerGuard(WeaverTypes weaverTypes, MethodDefinition md, bool logWarning)
|
||||
{
|
||||
ILProcessor worker = md.Body.GetILProcessor();
|
||||
Instruction top = md.Body.Instructions[0];
|
||||
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Call, weaverTypes.NetworkServerGetActive));
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Brtrue, top));
|
||||
if (logWarning)
|
||||
{
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Ldstr, $"[Server] function '{md.FullName}' called when server was not active"));
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Call, weaverTypes.logWarningReference));
|
||||
}
|
||||
InjectGuardParameters(md, worker, top);
|
||||
InjectGuardReturnValue(md, worker, top);
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Ret));
|
||||
}
|
||||
|
||||
static void InjectClientGuard(WeaverTypes weaverTypes, MethodDefinition md, bool logWarning)
|
||||
{
|
||||
ILProcessor worker = md.Body.GetILProcessor();
|
||||
Instruction top = md.Body.Instructions[0];
|
||||
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Call, weaverTypes.NetworkClientGetActive));
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Brtrue, top));
|
||||
if (logWarning)
|
||||
{
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Ldstr, $"[Client] function '{md.FullName}' called when client was not active"));
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Call, weaverTypes.logWarningReference));
|
||||
}
|
||||
|
||||
InjectGuardParameters(md, worker, top);
|
||||
InjectGuardReturnValue(md, worker, top);
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Ret));
|
||||
}
|
||||
|
||||
// this is required to early-out from a function with "ref" or "out" parameters
|
||||
static void InjectGuardParameters(MethodDefinition md, ILProcessor worker, Instruction top)
|
||||
{
|
||||
int offset = md.Resolve().IsStatic ? 0 : 1;
|
||||
for (int index = 0; index < md.Parameters.Count; index++)
|
||||
{
|
||||
ParameterDefinition param = md.Parameters[index];
|
||||
if (param.IsOut)
|
||||
{
|
||||
TypeReference elementType = param.ParameterType.GetElementType();
|
||||
|
||||
md.Body.Variables.Add(new VariableDefinition(elementType));
|
||||
md.Body.InitLocals = true;
|
||||
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Ldarg, index + offset));
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Ldloca_S, (byte)(md.Body.Variables.Count - 1)));
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Initobj, elementType));
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Ldloc, md.Body.Variables.Count - 1));
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Stobj, elementType));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this is required to early-out from a function with a return value.
|
||||
static void InjectGuardReturnValue(MethodDefinition md, ILProcessor worker, Instruction top)
|
||||
{
|
||||
if (!md.ReturnType.Is(typeof(void)))
|
||||
{
|
||||
md.Body.Variables.Add(new VariableDefinition(md.ReturnType));
|
||||
md.Body.InitLocals = true;
|
||||
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Ldloca_S, (byte)(md.Body.Variables.Count - 1)));
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Initobj, md.ReturnType));
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Ldloc, md.Body.Variables.Count - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 024f251bf693bb345b90b9177892d534
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
fileFormatVersion: 2
|
||||
guid: 024f251bf693bb345b90b9177892d534
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData: ''
|
||||
assetBundleName: ''
|
||||
assetBundleVariant: ''
|
||||
|
||||
@@ -1,47 +1,39 @@
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
public static class SyncObjectInitializer
|
||||
{
|
||||
public static void GenerateSyncObjectInitializer(ILProcessor worker, FieldDefinition fd)
|
||||
{
|
||||
// register syncobject in network behaviour
|
||||
GenerateSyncObjectRegistration(worker, fd);
|
||||
}
|
||||
|
||||
public static bool ImplementsSyncObject(TypeReference typeRef)
|
||||
{
|
||||
try
|
||||
{
|
||||
// value types cant inherit from SyncObject
|
||||
if (typeRef.IsValueType)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return typeRef.Resolve().ImplementsInterface<SyncObject>();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// sometimes this will fail if we reference a weird library that can't be resolved, so we just swallow that exception and return false
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
// generates code like:
|
||||
this.InitSyncObject(m_sizes);
|
||||
*/
|
||||
static void GenerateSyncObjectRegistration(ILProcessor worker, FieldDefinition fd)
|
||||
{
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldfld, fd);
|
||||
|
||||
worker.Emit(OpCodes.Call, WeaverTypes.InitSyncObjectReference);
|
||||
}
|
||||
}
|
||||
}
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
public static class SyncObjectInitializer
|
||||
{
|
||||
// generates code like:
|
||||
// this.InitSyncObject(m_sizes);
|
||||
public static void GenerateSyncObjectInitializer(ILProcessor worker, WeaverTypes weaverTypes, FieldDefinition fd)
|
||||
{
|
||||
// register syncobject in network behaviour
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldfld, fd);
|
||||
worker.Emit(OpCodes.Call, weaverTypes.InitSyncObjectReference);
|
||||
}
|
||||
|
||||
public static bool ImplementsSyncObject(TypeReference typeRef)
|
||||
{
|
||||
try
|
||||
{
|
||||
// value types cant inherit from SyncObject
|
||||
if (typeRef.IsValueType)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return typeRef.Resolve().IsDerivedFrom<SyncObject>();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// sometimes this will fail if we reference a weird library that can't be resolved, so we just swallow that exception and return false
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d02219b00b3674e59a2151f41e791688
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
fileFormatVersion: 2
|
||||
guid: d02219b00b3674e59a2151f41e791688
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData: ''
|
||||
assetBundleName: ''
|
||||
assetBundleVariant: ''
|
||||
|
||||
@@ -1,63 +1,84 @@
|
||||
using System.Collections.Generic;
|
||||
using Mono.CecilX;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
public static class SyncObjectProcessor
|
||||
{
|
||||
/// <summary>
|
||||
/// Finds SyncObjects fields in a type
|
||||
/// <para>Type should be a NetworkBehaviour</para>
|
||||
/// </summary>
|
||||
/// <param name="td"></param>
|
||||
/// <returns></returns>
|
||||
public static List<FieldDefinition> FindSyncObjectsFields(TypeDefinition td)
|
||||
{
|
||||
List<FieldDefinition> syncObjects = new List<FieldDefinition>();
|
||||
|
||||
foreach (FieldDefinition fd in td.Fields)
|
||||
{
|
||||
if (fd.FieldType.Resolve().ImplementsInterface<SyncObject>())
|
||||
{
|
||||
if (fd.IsStatic)
|
||||
{
|
||||
Weaver.Error($"{fd.Name} cannot be static", fd);
|
||||
continue;
|
||||
}
|
||||
|
||||
GenerateReadersAndWriters(fd.FieldType);
|
||||
|
||||
syncObjects.Add(fd);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return syncObjects;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates serialization methods for synclists
|
||||
/// </summary>
|
||||
/// <param name="td">The synclist class</param>
|
||||
/// <param name="mirrorBaseType">the base SyncObject td inherits from</param>
|
||||
static void GenerateReadersAndWriters(TypeReference tr)
|
||||
{
|
||||
if (tr is GenericInstanceType genericInstance)
|
||||
{
|
||||
foreach (TypeReference argument in genericInstance.GenericArguments)
|
||||
{
|
||||
if (!argument.IsGenericParameter)
|
||||
{
|
||||
Readers.GetReadFunc(argument);
|
||||
Writers.GetWriteFunc(argument);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tr != null)
|
||||
{
|
||||
GenerateReadersAndWriters(tr.Resolve().BaseType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
using System.Collections.Generic;
|
||||
using Mono.CecilX;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
public static class SyncObjectProcessor
|
||||
{
|
||||
// ulong = 64 bytes
|
||||
const int SyncObjectsLimit = 64;
|
||||
|
||||
// Finds SyncObjects fields in a type
|
||||
// Type should be a NetworkBehaviour
|
||||
public static List<FieldDefinition> FindSyncObjectsFields(Writers writers, Readers readers, Logger Log, TypeDefinition td, ref bool WeavingFailed)
|
||||
{
|
||||
List<FieldDefinition> syncObjects = new List<FieldDefinition>();
|
||||
|
||||
foreach (FieldDefinition fd in td.Fields)
|
||||
{
|
||||
if (fd.FieldType.Resolve().IsDerivedFrom<SyncObject>())
|
||||
{
|
||||
if (fd.IsStatic)
|
||||
{
|
||||
Log.Error($"{fd.Name} cannot be static", fd);
|
||||
WeavingFailed = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// SyncObjects always needs to be readonly to guarantee.
|
||||
// Weaver calls InitSyncObject on them for dirty bits etc.
|
||||
// Reassigning at runtime would cause undefined behaviour.
|
||||
// (C# 'readonly' is called 'initonly' in IL code.)
|
||||
//
|
||||
// NOTE: instead of forcing readonly, we could also scan all
|
||||
// instructions for SyncObject assignments. this would
|
||||
// make unit tests very difficult though.
|
||||
if (!fd.IsInitOnly)
|
||||
{
|
||||
// just a warning for now.
|
||||
// many people might still use non-readonly SyncObjects.
|
||||
Log.Warning($"{fd.Name} should have a 'readonly' keyword in front of the variable because {typeof(SyncObject)}s always need to be initialized by the Weaver.", fd);
|
||||
|
||||
// only log, but keep weaving. no need to break projects.
|
||||
//WeavingFailed = true;
|
||||
}
|
||||
|
||||
GenerateReadersAndWriters(writers, readers, fd.FieldType, ref WeavingFailed);
|
||||
|
||||
syncObjects.Add(fd);
|
||||
}
|
||||
}
|
||||
|
||||
// SyncObjects dirty mask is 64 bit. can't sync more than 64.
|
||||
if (syncObjects.Count > 64)
|
||||
{
|
||||
Log.Error($"{td.Name} has > {SyncObjectsLimit} SyncObjects (SyncLists etc). Consider refactoring your class into multiple components", td);
|
||||
WeavingFailed = true;
|
||||
}
|
||||
|
||||
|
||||
return syncObjects;
|
||||
}
|
||||
|
||||
// Generates serialization methods for synclists
|
||||
static void GenerateReadersAndWriters(Writers writers, Readers readers, TypeReference tr, ref bool WeavingFailed)
|
||||
{
|
||||
if (tr is GenericInstanceType genericInstance)
|
||||
{
|
||||
foreach (TypeReference argument in genericInstance.GenericArguments)
|
||||
{
|
||||
if (!argument.IsGenericParameter)
|
||||
{
|
||||
readers.GetReadFunc(argument, ref WeavingFailed);
|
||||
writers.GetWriteFunc(argument, ref WeavingFailed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tr != null)
|
||||
{
|
||||
GenerateReadersAndWriters(writers, readers, tr.Resolve().BaseType, ref WeavingFailed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 78f71efc83cde4917b7d21efa90bcc9a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
fileFormatVersion: 2
|
||||
guid: 78f71efc83cde4917b7d21efa90bcc9a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData: ''
|
||||
assetBundleName: ''
|
||||
assetBundleVariant: ''
|
||||
|
||||
@@ -1,160 +1,174 @@
|
||||
using System;
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
public static class PropertySiteProcessor
|
||||
{
|
||||
public static void Process(ModuleDefinition moduleDef)
|
||||
{
|
||||
DateTime startTime = DateTime.Now;
|
||||
|
||||
//Search through the types
|
||||
foreach (TypeDefinition td in moduleDef.Types)
|
||||
{
|
||||
if (td.IsClass)
|
||||
{
|
||||
ProcessSiteClass(td);
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine(" ProcessSitesModule " + moduleDef.Name + " elapsed time:" + (DateTime.Now - startTime));
|
||||
}
|
||||
|
||||
static void ProcessSiteClass(TypeDefinition td)
|
||||
{
|
||||
//Console.WriteLine(" ProcessSiteClass " + td);
|
||||
foreach (MethodDefinition md in td.Methods)
|
||||
{
|
||||
ProcessSiteMethod(md);
|
||||
}
|
||||
|
||||
foreach (TypeDefinition nested in td.NestedTypes)
|
||||
{
|
||||
ProcessSiteClass(nested);
|
||||
}
|
||||
}
|
||||
|
||||
static void ProcessSiteMethod(MethodDefinition md)
|
||||
{
|
||||
// process all references to replaced members with properties
|
||||
//Weaver.DLog(td, " ProcessSiteMethod " + md);
|
||||
|
||||
if (md.Name == ".cctor" ||
|
||||
md.Name == NetworkBehaviourProcessor.ProcessedFunctionName ||
|
||||
md.Name.StartsWith(Weaver.InvokeRpcPrefix))
|
||||
return;
|
||||
|
||||
if (md.IsAbstract)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (md.Body != null && md.Body.Instructions != null)
|
||||
{
|
||||
for (int iCount = 0; iCount < md.Body.Instructions.Count;)
|
||||
{
|
||||
Instruction instr = md.Body.Instructions[iCount];
|
||||
iCount += ProcessInstruction(md, instr, iCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// replaces syncvar write access with the NetworkXYZ.get property calls
|
||||
static void ProcessInstructionSetterField(MethodDefinition md, Instruction i, FieldDefinition opField)
|
||||
{
|
||||
// don't replace property call sites in constructors
|
||||
if (md.Name == ".ctor")
|
||||
return;
|
||||
|
||||
// does it set a field that we replaced?
|
||||
if (Weaver.WeaveLists.replacementSetterProperties.TryGetValue(opField, out MethodDefinition replacement))
|
||||
{
|
||||
//replace with property
|
||||
//DLog(td, " replacing " + md.Name + ":" + i);
|
||||
i.OpCode = OpCodes.Call;
|
||||
i.Operand = replacement;
|
||||
//DLog(td, " replaced " + md.Name + ":" + i);
|
||||
}
|
||||
}
|
||||
|
||||
// replaces syncvar read access with the NetworkXYZ.get property calls
|
||||
static void ProcessInstructionGetterField(MethodDefinition md, Instruction i, FieldDefinition opField)
|
||||
{
|
||||
// don't replace property call sites in constructors
|
||||
if (md.Name == ".ctor")
|
||||
return;
|
||||
|
||||
// does it set a field that we replaced?
|
||||
if (Weaver.WeaveLists.replacementGetterProperties.TryGetValue(opField, out MethodDefinition replacement))
|
||||
{
|
||||
//replace with property
|
||||
//DLog(td, " replacing " + md.Name + ":" + i);
|
||||
i.OpCode = OpCodes.Call;
|
||||
i.Operand = replacement;
|
||||
//DLog(td, " replaced " + md.Name + ":" + i);
|
||||
}
|
||||
}
|
||||
|
||||
static int ProcessInstruction(MethodDefinition md, Instruction instr, int iCount)
|
||||
{
|
||||
if (instr.OpCode == OpCodes.Stfld && instr.Operand is FieldDefinition opFieldst)
|
||||
{
|
||||
// this instruction sets the value of a field. cache the field reference.
|
||||
ProcessInstructionSetterField(md, instr, opFieldst);
|
||||
}
|
||||
|
||||
if (instr.OpCode == OpCodes.Ldfld && instr.Operand is FieldDefinition opFieldld)
|
||||
{
|
||||
// this instruction gets the value of a field. cache the field reference.
|
||||
ProcessInstructionGetterField(md, instr, opFieldld);
|
||||
}
|
||||
|
||||
if (instr.OpCode == OpCodes.Ldflda && instr.Operand is FieldDefinition opFieldlda)
|
||||
{
|
||||
// loading a field by reference, watch out for initobj instruction
|
||||
// see https://github.com/vis2k/Mirror/issues/696
|
||||
return ProcessInstructionLoadAddress(md, instr, opFieldlda, iCount);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int ProcessInstructionLoadAddress(MethodDefinition md, Instruction instr, FieldDefinition opField, int iCount)
|
||||
{
|
||||
// don't replace property call sites in constructors
|
||||
if (md.Name == ".ctor")
|
||||
return 1;
|
||||
|
||||
// does it set a field that we replaced?
|
||||
if (Weaver.WeaveLists.replacementSetterProperties.TryGetValue(opField, out MethodDefinition replacement))
|
||||
{
|
||||
// we have a replacement for this property
|
||||
// is the next instruction a initobj?
|
||||
Instruction nextInstr = md.Body.Instructions[iCount + 1];
|
||||
|
||||
if (nextInstr.OpCode == OpCodes.Initobj)
|
||||
{
|
||||
// we need to replace this code with:
|
||||
// var tmp = new MyStruct();
|
||||
// this.set_Networkxxxx(tmp);
|
||||
ILProcessor worker = md.Body.GetILProcessor();
|
||||
VariableDefinition tmpVariable = new VariableDefinition(opField.FieldType);
|
||||
md.Body.Variables.Add(tmpVariable);
|
||||
|
||||
worker.InsertBefore(instr, worker.Create(OpCodes.Ldloca, tmpVariable));
|
||||
worker.InsertBefore(instr, worker.Create(OpCodes.Initobj, opField.FieldType));
|
||||
worker.InsertBefore(instr, worker.Create(OpCodes.Ldloc, tmpVariable));
|
||||
worker.InsertBefore(instr, worker.Create(OpCodes.Call, replacement));
|
||||
|
||||
worker.Remove(instr);
|
||||
worker.Remove(nextInstr);
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
// [SyncVar] int health;
|
||||
// is replaced with:
|
||||
// public int Networkhealth { get; set; } properties.
|
||||
// this class processes all access to 'health' and replaces it with 'Networkhealth'
|
||||
using System;
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
public static class SyncVarAttributeAccessReplacer
|
||||
{
|
||||
// process the module
|
||||
public static void Process(ModuleDefinition moduleDef, SyncVarAccessLists syncVarAccessLists)
|
||||
{
|
||||
DateTime startTime = DateTime.Now;
|
||||
|
||||
// process all classes in this module
|
||||
foreach (TypeDefinition td in moduleDef.Types)
|
||||
{
|
||||
if (td.IsClass)
|
||||
{
|
||||
ProcessClass(syncVarAccessLists, td);
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine($" ProcessSitesModule {moduleDef.Name} elapsed time:{(DateTime.Now - startTime)}");
|
||||
}
|
||||
|
||||
static void ProcessClass(SyncVarAccessLists syncVarAccessLists, TypeDefinition td)
|
||||
{
|
||||
//Console.WriteLine($" ProcessClass {td}");
|
||||
|
||||
// process all methods in this class
|
||||
foreach (MethodDefinition md in td.Methods)
|
||||
{
|
||||
ProcessMethod(syncVarAccessLists, md);
|
||||
}
|
||||
|
||||
// processes all nested classes in this class recursively
|
||||
foreach (TypeDefinition nested in td.NestedTypes)
|
||||
{
|
||||
ProcessClass(syncVarAccessLists, nested);
|
||||
}
|
||||
}
|
||||
|
||||
static void ProcessMethod(SyncVarAccessLists syncVarAccessLists, MethodDefinition md)
|
||||
{
|
||||
// process all references to replaced members with properties
|
||||
//Log.Warning($" ProcessSiteMethod {md}");
|
||||
|
||||
// skip static constructor, "MirrorProcessed", "InvokeUserCode_"
|
||||
if (md.Name == ".cctor" ||
|
||||
md.Name == NetworkBehaviourProcessor.ProcessedFunctionName ||
|
||||
md.Name.StartsWith(Weaver.InvokeRpcPrefix))
|
||||
return;
|
||||
|
||||
// skip abstract
|
||||
if (md.IsAbstract)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// go through all instructions of this method
|
||||
if (md.Body != null && md.Body.Instructions != null)
|
||||
{
|
||||
for (int i = 0; i < md.Body.Instructions.Count;)
|
||||
{
|
||||
Instruction instr = md.Body.Instructions[i];
|
||||
i += ProcessInstruction(syncVarAccessLists, md, instr, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int ProcessInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction instr, int iCount)
|
||||
{
|
||||
// stfld (sets value of a field)?
|
||||
if (instr.OpCode == OpCodes.Stfld && instr.Operand is FieldDefinition opFieldst)
|
||||
{
|
||||
ProcessSetInstruction(syncVarAccessLists, md, instr, opFieldst);
|
||||
}
|
||||
|
||||
// ldfld (load value of a field)?
|
||||
if (instr.OpCode == OpCodes.Ldfld && instr.Operand is FieldDefinition opFieldld)
|
||||
{
|
||||
// this instruction gets the value of a field. cache the field reference.
|
||||
ProcessGetInstruction(syncVarAccessLists, md, instr, opFieldld);
|
||||
}
|
||||
|
||||
// ldflda (load field address aka reference)
|
||||
if (instr.OpCode == OpCodes.Ldflda && instr.Operand is FieldDefinition opFieldlda)
|
||||
{
|
||||
// watch out for initobj instruction
|
||||
// see https://github.com/vis2k/Mirror/issues/696
|
||||
return ProcessLoadAddressInstruction(syncVarAccessLists, md, instr, opFieldlda, iCount);
|
||||
}
|
||||
|
||||
// we processed one instruction (instr)
|
||||
return 1;
|
||||
}
|
||||
|
||||
// replaces syncvar write access with the NetworkXYZ.set property calls
|
||||
static void ProcessSetInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction i, FieldDefinition opField)
|
||||
{
|
||||
// don't replace property call sites in constructors
|
||||
if (md.Name == ".ctor")
|
||||
return;
|
||||
|
||||
// does it set a field that we replaced?
|
||||
if (syncVarAccessLists.replacementSetterProperties.TryGetValue(opField, out MethodDefinition replacement))
|
||||
{
|
||||
//replace with property
|
||||
//Log.Warning($" replacing {md.Name}:{i}", opField);
|
||||
i.OpCode = OpCodes.Call;
|
||||
i.Operand = replacement;
|
||||
//Log.Warning($" replaced {md.Name}:{i}", opField);
|
||||
}
|
||||
}
|
||||
|
||||
// replaces syncvar read access with the NetworkXYZ.get property calls
|
||||
static void ProcessGetInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction i, FieldDefinition opField)
|
||||
{
|
||||
// don't replace property call sites in constructors
|
||||
if (md.Name == ".ctor")
|
||||
return;
|
||||
|
||||
// does it set a field that we replaced?
|
||||
if (syncVarAccessLists.replacementGetterProperties.TryGetValue(opField, out MethodDefinition replacement))
|
||||
{
|
||||
//replace with property
|
||||
//Log.Warning($" replacing {md.Name}:{i}");
|
||||
i.OpCode = OpCodes.Call;
|
||||
i.Operand = replacement;
|
||||
//Log.Warning($" replaced {md.Name}:{i}");
|
||||
}
|
||||
}
|
||||
|
||||
static int ProcessLoadAddressInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction instr, FieldDefinition opField, int iCount)
|
||||
{
|
||||
// don't replace property call sites in constructors
|
||||
if (md.Name == ".ctor")
|
||||
return 1;
|
||||
|
||||
// does it set a field that we replaced?
|
||||
if (syncVarAccessLists.replacementSetterProperties.TryGetValue(opField, out MethodDefinition replacement))
|
||||
{
|
||||
// we have a replacement for this property
|
||||
// is the next instruction a initobj?
|
||||
Instruction nextInstr = md.Body.Instructions[iCount + 1];
|
||||
|
||||
if (nextInstr.OpCode == OpCodes.Initobj)
|
||||
{
|
||||
// we need to replace this code with:
|
||||
// var tmp = new MyStruct();
|
||||
// this.set_Networkxxxx(tmp);
|
||||
ILProcessor worker = md.Body.GetILProcessor();
|
||||
VariableDefinition tmpVariable = new VariableDefinition(opField.FieldType);
|
||||
md.Body.Variables.Add(tmpVariable);
|
||||
|
||||
worker.InsertBefore(instr, worker.Create(OpCodes.Ldloca, tmpVariable));
|
||||
worker.InsertBefore(instr, worker.Create(OpCodes.Initobj, opField.FieldType));
|
||||
worker.InsertBefore(instr, worker.Create(OpCodes.Ldloc, tmpVariable));
|
||||
worker.InsertBefore(instr, worker.Create(OpCodes.Call, replacement));
|
||||
|
||||
worker.Remove(instr);
|
||||
worker.Remove(nextInstr);
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d48f1ab125e9940a995603796bccc59e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
fileFormatVersion: 2
|
||||
guid: d48f1ab125e9940a995603796bccc59e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData: ''
|
||||
assetBundleName: ''
|
||||
assetBundleVariant: ''
|
||||
@@ -1,472 +1,490 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
/// <summary>
|
||||
/// Processes [SyncVar] in NetworkBehaviour
|
||||
/// </summary>
|
||||
public static class SyncVarProcessor
|
||||
{
|
||||
// ulong = 64 bytes
|
||||
const int SyncVarLimit = 64;
|
||||
|
||||
|
||||
static string HookParameterMessage(string hookName, TypeReference ValueType)
|
||||
=> string.Format("void {0}({1} oldValue, {1} newValue)", hookName, ValueType);
|
||||
|
||||
// Get hook method if any
|
||||
public static MethodDefinition GetHookMethod(TypeDefinition td, FieldDefinition syncVar)
|
||||
{
|
||||
CustomAttribute syncVarAttr = syncVar.GetCustomAttribute<SyncVarAttribute>();
|
||||
|
||||
if (syncVarAttr == null)
|
||||
return null;
|
||||
|
||||
string hookFunctionName = syncVarAttr.GetField<string>("hook", null);
|
||||
|
||||
if (hookFunctionName == null)
|
||||
return null;
|
||||
|
||||
return FindHookMethod(td, syncVar, hookFunctionName);
|
||||
}
|
||||
|
||||
static MethodDefinition FindHookMethod(TypeDefinition td, FieldDefinition syncVar, string hookFunctionName)
|
||||
{
|
||||
List<MethodDefinition> methods = td.GetMethods(hookFunctionName);
|
||||
|
||||
List<MethodDefinition> methodsWith2Param = new List<MethodDefinition>(methods.Where(m => m.Parameters.Count == 2));
|
||||
|
||||
if (methodsWith2Param.Count == 0)
|
||||
{
|
||||
Weaver.Error($"Could not find hook for '{syncVar.Name}', hook name '{hookFunctionName}'. " +
|
||||
$"Method signature should be {HookParameterMessage(hookFunctionName, syncVar.FieldType)}",
|
||||
syncVar);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (MethodDefinition method in methodsWith2Param)
|
||||
{
|
||||
if (MatchesParameters(syncVar, method))
|
||||
{
|
||||
return method;
|
||||
}
|
||||
}
|
||||
|
||||
Weaver.Error($"Wrong type for Parameter in hook for '{syncVar.Name}', hook name '{hookFunctionName}'. " +
|
||||
$"Method signature should be {HookParameterMessage(hookFunctionName, syncVar.FieldType)}",
|
||||
syncVar);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static bool MatchesParameters(FieldDefinition syncVar, MethodDefinition method)
|
||||
{
|
||||
// matches void onValueChange(T oldValue, T newValue)
|
||||
return method.Parameters[0].ParameterType.FullName == syncVar.FieldType.FullName &&
|
||||
method.Parameters[1].ParameterType.FullName == syncVar.FieldType.FullName;
|
||||
}
|
||||
|
||||
public static MethodDefinition GenerateSyncVarGetter(FieldDefinition fd, string originalName, FieldDefinition netFieldId)
|
||||
{
|
||||
//Create the get method
|
||||
MethodDefinition get = new MethodDefinition(
|
||||
"get_Network" + originalName, MethodAttributes.Public |
|
||||
MethodAttributes.SpecialName |
|
||||
MethodAttributes.HideBySig,
|
||||
fd.FieldType);
|
||||
|
||||
ILProcessor worker = get.Body.GetILProcessor();
|
||||
|
||||
// [SyncVar] GameObject?
|
||||
if (fd.FieldType.Is<UnityEngine.GameObject>())
|
||||
{
|
||||
// return this.GetSyncVarGameObject(ref field, uint netId);
|
||||
// this.
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldfld, netFieldId);
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldflda, fd);
|
||||
worker.Emit(OpCodes.Call, WeaverTypes.getSyncVarGameObjectReference);
|
||||
worker.Emit(OpCodes.Ret);
|
||||
}
|
||||
// [SyncVar] NetworkIdentity?
|
||||
else if (fd.FieldType.Is<NetworkIdentity>())
|
||||
{
|
||||
// return this.GetSyncVarNetworkIdentity(ref field, uint netId);
|
||||
// this.
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldfld, netFieldId);
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldflda, fd);
|
||||
worker.Emit(OpCodes.Call, WeaverTypes.getSyncVarNetworkIdentityReference);
|
||||
worker.Emit(OpCodes.Ret);
|
||||
}
|
||||
else if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>())
|
||||
{
|
||||
// return this.GetSyncVarNetworkBehaviour<T>(ref field, uint netId);
|
||||
// this.
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldfld, netFieldId);
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldflda, fd);
|
||||
MethodReference getFunc = WeaverTypes.getSyncVarNetworkBehaviourReference.MakeGeneric(fd.FieldType);
|
||||
worker.Emit(OpCodes.Call, getFunc);
|
||||
worker.Emit(OpCodes.Ret);
|
||||
}
|
||||
// [SyncVar] int, string, etc.
|
||||
else
|
||||
{
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldfld, fd);
|
||||
worker.Emit(OpCodes.Ret);
|
||||
}
|
||||
|
||||
get.Body.Variables.Add(new VariableDefinition(fd.FieldType));
|
||||
get.Body.InitLocals = true;
|
||||
get.SemanticsAttributes = MethodSemanticsAttributes.Getter;
|
||||
|
||||
return get;
|
||||
}
|
||||
|
||||
public static MethodDefinition GenerateSyncVarSetter(TypeDefinition td, FieldDefinition fd, string originalName, long dirtyBit, FieldDefinition netFieldId)
|
||||
{
|
||||
//Create the set method
|
||||
MethodDefinition set = new MethodDefinition("set_Network" + originalName, MethodAttributes.Public |
|
||||
MethodAttributes.SpecialName |
|
||||
MethodAttributes.HideBySig,
|
||||
WeaverTypes.Import(typeof(void)));
|
||||
|
||||
ILProcessor worker = set.Body.GetILProcessor();
|
||||
|
||||
// if (!SyncVarEqual(value, ref playerData))
|
||||
Instruction endOfMethod = worker.Create(OpCodes.Nop);
|
||||
|
||||
// this
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
// new value to set
|
||||
worker.Emit(OpCodes.Ldarg_1);
|
||||
// reference to field to set
|
||||
// make generic version of SetSyncVar with field type
|
||||
if (fd.FieldType.Is<UnityEngine.GameObject>())
|
||||
{
|
||||
// reference to netId Field to set
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldfld, netFieldId);
|
||||
|
||||
worker.Emit(OpCodes.Call, WeaverTypes.syncVarGameObjectEqualReference);
|
||||
}
|
||||
else if (fd.FieldType.Is<NetworkIdentity>())
|
||||
{
|
||||
// reference to netId Field to set
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldfld, netFieldId);
|
||||
|
||||
worker.Emit(OpCodes.Call, WeaverTypes.syncVarNetworkIdentityEqualReference);
|
||||
}
|
||||
else if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>())
|
||||
{
|
||||
// reference to netId Field to set
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldfld, netFieldId);
|
||||
|
||||
MethodReference getFunc = WeaverTypes.syncVarNetworkBehaviourEqualReference.MakeGeneric(fd.FieldType);
|
||||
worker.Emit(OpCodes.Call, getFunc);
|
||||
}
|
||||
else
|
||||
{
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldflda, fd);
|
||||
|
||||
GenericInstanceMethod syncVarEqualGm = new GenericInstanceMethod(WeaverTypes.syncVarEqualReference);
|
||||
syncVarEqualGm.GenericArguments.Add(fd.FieldType);
|
||||
worker.Emit(OpCodes.Call, syncVarEqualGm);
|
||||
}
|
||||
|
||||
worker.Emit(OpCodes.Brtrue, endOfMethod);
|
||||
|
||||
// T oldValue = value;
|
||||
// TODO for GO/NI we need to backup the netId don't we?
|
||||
VariableDefinition oldValue = new VariableDefinition(fd.FieldType);
|
||||
set.Body.Variables.Add(oldValue);
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldfld, fd);
|
||||
worker.Emit(OpCodes.Stloc, oldValue);
|
||||
|
||||
// this
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
|
||||
// new value to set
|
||||
worker.Emit(OpCodes.Ldarg_1);
|
||||
|
||||
// reference to field to set
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldflda, fd);
|
||||
|
||||
// dirty bit
|
||||
// 8 byte integer aka long
|
||||
worker.Emit(OpCodes.Ldc_I8, dirtyBit);
|
||||
|
||||
if (fd.FieldType.Is<UnityEngine.GameObject>())
|
||||
{
|
||||
// reference to netId Field to set
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldflda, netFieldId);
|
||||
|
||||
worker.Emit(OpCodes.Call, WeaverTypes.setSyncVarGameObjectReference);
|
||||
}
|
||||
else if (fd.FieldType.Is<NetworkIdentity>())
|
||||
{
|
||||
// reference to netId Field to set
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldflda, netFieldId);
|
||||
|
||||
worker.Emit(OpCodes.Call, WeaverTypes.setSyncVarNetworkIdentityReference);
|
||||
}
|
||||
else if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>())
|
||||
{
|
||||
// reference to netId Field to set
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldflda, netFieldId);
|
||||
|
||||
MethodReference getFunc = WeaverTypes.setSyncVarNetworkBehaviourReference.MakeGeneric(fd.FieldType);
|
||||
worker.Emit(OpCodes.Call, getFunc);
|
||||
}
|
||||
else
|
||||
{
|
||||
// make generic version of SetSyncVar with field type
|
||||
GenericInstanceMethod gm = new GenericInstanceMethod(WeaverTypes.setSyncVarReference);
|
||||
gm.GenericArguments.Add(fd.FieldType);
|
||||
|
||||
// invoke SetSyncVar
|
||||
worker.Emit(OpCodes.Call, gm);
|
||||
}
|
||||
|
||||
MethodDefinition hookMethod = GetHookMethod(td, fd);
|
||||
|
||||
if (hookMethod != null)
|
||||
{
|
||||
//if (NetworkServer.localClientActive && !getSyncVarHookGuard(dirtyBit))
|
||||
Instruction label = worker.Create(OpCodes.Nop);
|
||||
worker.Emit(OpCodes.Call, WeaverTypes.NetworkServerGetLocalClientActive);
|
||||
worker.Emit(OpCodes.Brfalse, label);
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldc_I8, dirtyBit);
|
||||
worker.Emit(OpCodes.Call, WeaverTypes.getSyncVarHookGuard);
|
||||
worker.Emit(OpCodes.Brtrue, label);
|
||||
|
||||
// setSyncVarHookGuard(dirtyBit, true);
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldc_I8, dirtyBit);
|
||||
worker.Emit(OpCodes.Ldc_I4_1);
|
||||
worker.Emit(OpCodes.Call, WeaverTypes.setSyncVarHookGuard);
|
||||
|
||||
// call hook (oldValue, newValue)
|
||||
// Generates: OnValueChanged(oldValue, value);
|
||||
WriteCallHookMethodUsingArgument(worker, hookMethod, oldValue);
|
||||
|
||||
// setSyncVarHookGuard(dirtyBit, false);
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldc_I8, dirtyBit);
|
||||
worker.Emit(OpCodes.Ldc_I4_0);
|
||||
worker.Emit(OpCodes.Call, WeaverTypes.setSyncVarHookGuard);
|
||||
|
||||
worker.Append(label);
|
||||
}
|
||||
|
||||
worker.Append(endOfMethod);
|
||||
|
||||
worker.Emit(OpCodes.Ret);
|
||||
|
||||
set.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.In, fd.FieldType));
|
||||
set.SemanticsAttributes = MethodSemanticsAttributes.Setter;
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
public static void ProcessSyncVar(TypeDefinition td, FieldDefinition fd, Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds, long dirtyBit)
|
||||
{
|
||||
string originalName = fd.Name;
|
||||
Weaver.DLog(td, "Sync Var " + fd.Name + " " + fd.FieldType);
|
||||
|
||||
// GameObject/NetworkIdentity SyncVars have a new field for netId
|
||||
FieldDefinition netIdField = null;
|
||||
// NetworkBehaviour has different field type than other NetworkIdentityFields
|
||||
if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>())
|
||||
{
|
||||
netIdField = new FieldDefinition("___" + fd.Name + "NetId",
|
||||
FieldAttributes.Private,
|
||||
WeaverTypes.Import<NetworkBehaviour.NetworkBehaviourSyncVar>());
|
||||
|
||||
syncVarNetIds[fd] = netIdField;
|
||||
}
|
||||
else if (fd.FieldType.IsNetworkIdentityField())
|
||||
{
|
||||
netIdField = new FieldDefinition("___" + fd.Name + "NetId",
|
||||
FieldAttributes.Private,
|
||||
WeaverTypes.Import<uint>());
|
||||
|
||||
syncVarNetIds[fd] = netIdField;
|
||||
}
|
||||
|
||||
MethodDefinition get = GenerateSyncVarGetter(fd, originalName, netIdField);
|
||||
MethodDefinition set = GenerateSyncVarSetter(td, fd, originalName, dirtyBit, netIdField);
|
||||
|
||||
//NOTE: is property even needed? Could just use a setter function?
|
||||
//create the property
|
||||
PropertyDefinition propertyDefinition = new PropertyDefinition("Network" + originalName, PropertyAttributes.None, fd.FieldType)
|
||||
{
|
||||
GetMethod = get,
|
||||
SetMethod = set
|
||||
};
|
||||
|
||||
//add the methods and property to the type.
|
||||
td.Methods.Add(get);
|
||||
td.Methods.Add(set);
|
||||
td.Properties.Add(propertyDefinition);
|
||||
Weaver.WeaveLists.replacementSetterProperties[fd] = set;
|
||||
|
||||
// replace getter field if GameObject/NetworkIdentity so it uses
|
||||
// netId instead
|
||||
// -> only for GameObjects, otherwise an int syncvar's getter would
|
||||
// end up in recursion.
|
||||
if (fd.FieldType.IsNetworkIdentityField())
|
||||
{
|
||||
Weaver.WeaveLists.replacementGetterProperties[fd] = get;
|
||||
}
|
||||
}
|
||||
|
||||
public static (List<FieldDefinition> syncVars, Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds) ProcessSyncVars(TypeDefinition td)
|
||||
{
|
||||
List<FieldDefinition> syncVars = new List<FieldDefinition>();
|
||||
Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds = new Dictionary<FieldDefinition, FieldDefinition>();
|
||||
|
||||
// the mapping of dirtybits to sync-vars is implicit in the order of the fields here. this order is recorded in m_replacementProperties.
|
||||
// start assigning syncvars at the place the base class stopped, if any
|
||||
int dirtyBitCounter = Weaver.WeaveLists.GetSyncVarStart(td.BaseType.FullName);
|
||||
|
||||
// find syncvars
|
||||
foreach (FieldDefinition fd in td.Fields)
|
||||
{
|
||||
if (fd.HasCustomAttribute<SyncVarAttribute>())
|
||||
{
|
||||
if ((fd.Attributes & FieldAttributes.Static) != 0)
|
||||
{
|
||||
Weaver.Error($"{fd.Name} cannot be static", fd);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fd.FieldType.IsArray)
|
||||
{
|
||||
Weaver.Error($"{fd.Name} has invalid type. Use SyncLists instead of arrays", fd);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (SyncObjectInitializer.ImplementsSyncObject(fd.FieldType))
|
||||
{
|
||||
Weaver.Warning($"{fd.Name} has [SyncVar] attribute. SyncLists should not be marked with SyncVar", fd);
|
||||
}
|
||||
else
|
||||
{
|
||||
syncVars.Add(fd);
|
||||
|
||||
ProcessSyncVar(td, fd, syncVarNetIds, 1L << dirtyBitCounter);
|
||||
dirtyBitCounter += 1;
|
||||
|
||||
if (dirtyBitCounter == SyncVarLimit)
|
||||
{
|
||||
Weaver.Error($"{td.Name} has too many SyncVars. Consider refactoring your class into multiple components", td);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add all the new SyncVar __netId fields
|
||||
foreach (FieldDefinition fd in syncVarNetIds.Values)
|
||||
{
|
||||
td.Fields.Add(fd);
|
||||
}
|
||||
Weaver.WeaveLists.SetNumSyncVars(td.FullName, syncVars.Count);
|
||||
|
||||
return (syncVars, syncVarNetIds);
|
||||
}
|
||||
|
||||
public static void WriteCallHookMethodUsingArgument(ILProcessor worker, MethodDefinition hookMethod, VariableDefinition oldValue)
|
||||
{
|
||||
WriteCallHookMethod(worker, hookMethod, oldValue, null);
|
||||
}
|
||||
|
||||
public static void WriteCallHookMethodUsingField(ILProcessor worker, MethodDefinition hookMethod, VariableDefinition oldValue, FieldDefinition newValue)
|
||||
{
|
||||
if (newValue == null)
|
||||
{
|
||||
Weaver.Error("NewValue field was null when writing SyncVar hook");
|
||||
}
|
||||
|
||||
WriteCallHookMethod(worker, hookMethod, oldValue, newValue);
|
||||
}
|
||||
|
||||
static void WriteCallHookMethod(ILProcessor worker, MethodDefinition hookMethod, VariableDefinition oldValue, FieldDefinition newValue)
|
||||
{
|
||||
WriteStartFunctionCall();
|
||||
|
||||
// write args
|
||||
WriteOldValue();
|
||||
WriteNewValue();
|
||||
|
||||
WriteEndFunctionCall();
|
||||
|
||||
|
||||
// *** Local functions used to write OpCodes ***
|
||||
// Local functions have access to function variables, no need to pass in args
|
||||
|
||||
void WriteOldValue()
|
||||
{
|
||||
worker.Emit(OpCodes.Ldloc, oldValue);
|
||||
}
|
||||
|
||||
void WriteNewValue()
|
||||
{
|
||||
// write arg1 or this.field
|
||||
if (newValue == null)
|
||||
{
|
||||
worker.Emit(OpCodes.Ldarg_1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// this.
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
// syncvar.get
|
||||
worker.Emit(OpCodes.Ldfld, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
// Writes this before method if it is not static
|
||||
void WriteStartFunctionCall()
|
||||
{
|
||||
// don't add this (Ldarg_0) if method is static
|
||||
if (!hookMethod.IsStatic)
|
||||
{
|
||||
// this before method call
|
||||
// e.g. this.onValueChanged
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
}
|
||||
}
|
||||
|
||||
// Calls method
|
||||
void WriteEndFunctionCall()
|
||||
{
|
||||
// only use Callvirt when not static
|
||||
OpCode opcode = hookMethod.IsStatic ? OpCodes.Call : OpCodes.Callvirt;
|
||||
worker.Emit(opcode, hookMethod);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
// Processes [SyncVar] attribute fields in NetworkBehaviour
|
||||
// not static, because ILPostProcessor is multithreaded
|
||||
public class SyncVarAttributeProcessor
|
||||
{
|
||||
// ulong = 64 bytes
|
||||
const int SyncVarLimit = 64;
|
||||
|
||||
AssemblyDefinition assembly;
|
||||
WeaverTypes weaverTypes;
|
||||
SyncVarAccessLists syncVarAccessLists;
|
||||
Logger Log;
|
||||
|
||||
string HookParameterMessage(string hookName, TypeReference ValueType) =>
|
||||
$"void {hookName}({ValueType} oldValue, {ValueType} newValue)";
|
||||
|
||||
public SyncVarAttributeProcessor(AssemblyDefinition assembly, WeaverTypes weaverTypes, SyncVarAccessLists syncVarAccessLists, Logger Log)
|
||||
{
|
||||
this.assembly = assembly;
|
||||
this.weaverTypes = weaverTypes;
|
||||
this.syncVarAccessLists = syncVarAccessLists;
|
||||
this.Log = Log;
|
||||
}
|
||||
|
||||
// Get hook method if any
|
||||
public MethodDefinition GetHookMethod(TypeDefinition td, FieldDefinition syncVar, ref bool WeavingFailed)
|
||||
{
|
||||
CustomAttribute syncVarAttr = syncVar.GetCustomAttribute<SyncVarAttribute>();
|
||||
|
||||
if (syncVarAttr == null)
|
||||
return null;
|
||||
|
||||
string hookFunctionName = syncVarAttr.GetField<string>("hook", null);
|
||||
|
||||
if (hookFunctionName == null)
|
||||
return null;
|
||||
|
||||
return FindHookMethod(td, syncVar, hookFunctionName, ref WeavingFailed);
|
||||
}
|
||||
|
||||
MethodDefinition FindHookMethod(TypeDefinition td, FieldDefinition syncVar, string hookFunctionName, ref bool WeavingFailed)
|
||||
{
|
||||
List<MethodDefinition> methods = td.GetMethods(hookFunctionName);
|
||||
|
||||
List<MethodDefinition> methodsWith2Param = new List<MethodDefinition>(methods.Where(m => m.Parameters.Count == 2));
|
||||
|
||||
if (methodsWith2Param.Count == 0)
|
||||
{
|
||||
Log.Error($"Could not find hook for '{syncVar.Name}', hook name '{hookFunctionName}'. " +
|
||||
$"Method signature should be {HookParameterMessage(hookFunctionName, syncVar.FieldType)}",
|
||||
syncVar);
|
||||
WeavingFailed = true;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (MethodDefinition method in methodsWith2Param)
|
||||
{
|
||||
if (MatchesParameters(syncVar, method))
|
||||
{
|
||||
return method;
|
||||
}
|
||||
}
|
||||
|
||||
Log.Error($"Wrong type for Parameter in hook for '{syncVar.Name}', hook name '{hookFunctionName}'. " +
|
||||
$"Method signature should be {HookParameterMessage(hookFunctionName, syncVar.FieldType)}",
|
||||
syncVar);
|
||||
WeavingFailed = true;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
bool MatchesParameters(FieldDefinition syncVar, MethodDefinition method)
|
||||
{
|
||||
// matches void onValueChange(T oldValue, T newValue)
|
||||
return method.Parameters[0].ParameterType.FullName == syncVar.FieldType.FullName &&
|
||||
method.Parameters[1].ParameterType.FullName == syncVar.FieldType.FullName;
|
||||
}
|
||||
|
||||
public MethodDefinition GenerateSyncVarGetter(FieldDefinition fd, string originalName, FieldDefinition netFieldId)
|
||||
{
|
||||
//Create the get method
|
||||
MethodDefinition get = new MethodDefinition(
|
||||
$"get_Network{originalName}", MethodAttributes.Public |
|
||||
MethodAttributes.SpecialName |
|
||||
MethodAttributes.HideBySig,
|
||||
fd.FieldType);
|
||||
|
||||
ILProcessor worker = get.Body.GetILProcessor();
|
||||
|
||||
// [SyncVar] GameObject?
|
||||
if (fd.FieldType.Is<UnityEngine.GameObject>())
|
||||
{
|
||||
// return this.GetSyncVarGameObject(ref field, uint netId);
|
||||
// this.
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldfld, netFieldId);
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldflda, fd);
|
||||
worker.Emit(OpCodes.Call, weaverTypes.getSyncVarGameObjectReference);
|
||||
worker.Emit(OpCodes.Ret);
|
||||
}
|
||||
// [SyncVar] NetworkIdentity?
|
||||
else if (fd.FieldType.Is<NetworkIdentity>())
|
||||
{
|
||||
// return this.GetSyncVarNetworkIdentity(ref field, uint netId);
|
||||
// this.
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldfld, netFieldId);
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldflda, fd);
|
||||
worker.Emit(OpCodes.Call, weaverTypes.getSyncVarNetworkIdentityReference);
|
||||
worker.Emit(OpCodes.Ret);
|
||||
}
|
||||
else if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>())
|
||||
{
|
||||
// return this.GetSyncVarNetworkBehaviour<T>(ref field, uint netId);
|
||||
// this.
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldfld, netFieldId);
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldflda, fd);
|
||||
MethodReference getFunc = weaverTypes.getSyncVarNetworkBehaviourReference.MakeGeneric(assembly.MainModule, fd.FieldType);
|
||||
worker.Emit(OpCodes.Call, getFunc);
|
||||
worker.Emit(OpCodes.Ret);
|
||||
}
|
||||
// [SyncVar] int, string, etc.
|
||||
else
|
||||
{
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldfld, fd);
|
||||
worker.Emit(OpCodes.Ret);
|
||||
}
|
||||
|
||||
get.Body.Variables.Add(new VariableDefinition(fd.FieldType));
|
||||
get.Body.InitLocals = true;
|
||||
get.SemanticsAttributes = MethodSemanticsAttributes.Getter;
|
||||
|
||||
return get;
|
||||
}
|
||||
|
||||
public MethodDefinition GenerateSyncVarSetter(TypeDefinition td, FieldDefinition fd, string originalName, long dirtyBit, FieldDefinition netFieldId, ref bool WeavingFailed)
|
||||
{
|
||||
//Create the set method
|
||||
MethodDefinition set = new MethodDefinition($"set_Network{originalName}", MethodAttributes.Public |
|
||||
MethodAttributes.SpecialName |
|
||||
MethodAttributes.HideBySig,
|
||||
weaverTypes.Import(typeof(void)));
|
||||
|
||||
ILProcessor worker = set.Body.GetILProcessor();
|
||||
|
||||
// if (!SyncVarEqual(value, ref playerData))
|
||||
Instruction endOfMethod = worker.Create(OpCodes.Nop);
|
||||
|
||||
// NOTE: SyncVar...Equal functions are static.
|
||||
// don't Emit Ldarg_0 aka 'this'.
|
||||
|
||||
// new value to set
|
||||
worker.Emit(OpCodes.Ldarg_1);
|
||||
|
||||
// reference to field to set
|
||||
// make generic version of SetSyncVar with field type
|
||||
if (fd.FieldType.Is<UnityEngine.GameObject>())
|
||||
{
|
||||
// reference to netId Field to set
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldfld, netFieldId);
|
||||
|
||||
worker.Emit(OpCodes.Call, weaverTypes.syncVarGameObjectEqualReference);
|
||||
}
|
||||
else if (fd.FieldType.Is<NetworkIdentity>())
|
||||
{
|
||||
// reference to netId Field to set
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldfld, netFieldId);
|
||||
|
||||
worker.Emit(OpCodes.Call, weaverTypes.syncVarNetworkIdentityEqualReference);
|
||||
}
|
||||
else if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>())
|
||||
{
|
||||
// reference to netId Field to set
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldfld, netFieldId);
|
||||
|
||||
MethodReference getFunc = weaverTypes.syncVarNetworkBehaviourEqualReference.MakeGeneric(assembly.MainModule, fd.FieldType);
|
||||
worker.Emit(OpCodes.Call, getFunc);
|
||||
}
|
||||
else
|
||||
{
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldflda, fd);
|
||||
|
||||
GenericInstanceMethod syncVarEqualGm = new GenericInstanceMethod(weaverTypes.syncVarEqualReference);
|
||||
syncVarEqualGm.GenericArguments.Add(fd.FieldType);
|
||||
worker.Emit(OpCodes.Call, syncVarEqualGm);
|
||||
}
|
||||
|
||||
worker.Emit(OpCodes.Brtrue, endOfMethod);
|
||||
|
||||
// T oldValue = value;
|
||||
// TODO for GO/NI we need to backup the netId don't we?
|
||||
VariableDefinition oldValue = new VariableDefinition(fd.FieldType);
|
||||
set.Body.Variables.Add(oldValue);
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldfld, fd);
|
||||
worker.Emit(OpCodes.Stloc, oldValue);
|
||||
|
||||
// this
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
|
||||
// new value to set
|
||||
worker.Emit(OpCodes.Ldarg_1);
|
||||
|
||||
// reference to field to set
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldflda, fd);
|
||||
|
||||
// dirty bit
|
||||
// 8 byte integer aka long
|
||||
worker.Emit(OpCodes.Ldc_I8, dirtyBit);
|
||||
|
||||
if (fd.FieldType.Is<UnityEngine.GameObject>())
|
||||
{
|
||||
// reference to netId Field to set
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldflda, netFieldId);
|
||||
|
||||
worker.Emit(OpCodes.Call, weaverTypes.setSyncVarGameObjectReference);
|
||||
}
|
||||
else if (fd.FieldType.Is<NetworkIdentity>())
|
||||
{
|
||||
// reference to netId Field to set
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldflda, netFieldId);
|
||||
|
||||
worker.Emit(OpCodes.Call, weaverTypes.setSyncVarNetworkIdentityReference);
|
||||
}
|
||||
else if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>())
|
||||
{
|
||||
// reference to netId Field to set
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldflda, netFieldId);
|
||||
|
||||
MethodReference getFunc = weaverTypes.setSyncVarNetworkBehaviourReference.MakeGeneric(assembly.MainModule, fd.FieldType);
|
||||
worker.Emit(OpCodes.Call, getFunc);
|
||||
}
|
||||
else
|
||||
{
|
||||
// make generic version of SetSyncVar with field type
|
||||
GenericInstanceMethod gm = new GenericInstanceMethod(weaverTypes.setSyncVarReference);
|
||||
gm.GenericArguments.Add(fd.FieldType);
|
||||
|
||||
// invoke SetSyncVar
|
||||
worker.Emit(OpCodes.Call, gm);
|
||||
}
|
||||
|
||||
MethodDefinition hookMethod = GetHookMethod(td, fd, ref WeavingFailed);
|
||||
|
||||
if (hookMethod != null)
|
||||
{
|
||||
//if (NetworkServer.localClientActive && !getSyncVarHookGuard(dirtyBit))
|
||||
Instruction label = worker.Create(OpCodes.Nop);
|
||||
worker.Emit(OpCodes.Call, weaverTypes.NetworkServerGetLocalClientActive);
|
||||
worker.Emit(OpCodes.Brfalse, label);
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldc_I8, dirtyBit);
|
||||
worker.Emit(OpCodes.Call, weaverTypes.getSyncVarHookGuard);
|
||||
worker.Emit(OpCodes.Brtrue, label);
|
||||
|
||||
// setSyncVarHookGuard(dirtyBit, true);
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldc_I8, dirtyBit);
|
||||
worker.Emit(OpCodes.Ldc_I4_1);
|
||||
worker.Emit(OpCodes.Call, weaverTypes.setSyncVarHookGuard);
|
||||
|
||||
// call hook (oldValue, newValue)
|
||||
// Generates: OnValueChanged(oldValue, value);
|
||||
WriteCallHookMethodUsingArgument(worker, hookMethod, oldValue);
|
||||
|
||||
// setSyncVarHookGuard(dirtyBit, false);
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldc_I8, dirtyBit);
|
||||
worker.Emit(OpCodes.Ldc_I4_0);
|
||||
worker.Emit(OpCodes.Call, weaverTypes.setSyncVarHookGuard);
|
||||
|
||||
worker.Append(label);
|
||||
}
|
||||
|
||||
worker.Append(endOfMethod);
|
||||
|
||||
worker.Emit(OpCodes.Ret);
|
||||
|
||||
set.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.In, fd.FieldType));
|
||||
set.SemanticsAttributes = MethodSemanticsAttributes.Setter;
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
public void ProcessSyncVar(TypeDefinition td, FieldDefinition fd, Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds, long dirtyBit, ref bool WeavingFailed)
|
||||
{
|
||||
string originalName = fd.Name;
|
||||
|
||||
// GameObject/NetworkIdentity SyncVars have a new field for netId
|
||||
FieldDefinition netIdField = null;
|
||||
// NetworkBehaviour has different field type than other NetworkIdentityFields
|
||||
if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>())
|
||||
{
|
||||
netIdField = new FieldDefinition($"___{fd.Name}NetId",
|
||||
FieldAttributes.Private,
|
||||
weaverTypes.Import<NetworkBehaviour.NetworkBehaviourSyncVar>());
|
||||
|
||||
syncVarNetIds[fd] = netIdField;
|
||||
}
|
||||
else if (fd.FieldType.IsNetworkIdentityField())
|
||||
{
|
||||
netIdField = new FieldDefinition($"___{fd.Name}NetId",
|
||||
FieldAttributes.Private,
|
||||
weaverTypes.Import<uint>());
|
||||
|
||||
syncVarNetIds[fd] = netIdField;
|
||||
}
|
||||
|
||||
MethodDefinition get = GenerateSyncVarGetter(fd, originalName, netIdField);
|
||||
MethodDefinition set = GenerateSyncVarSetter(td, fd, originalName, dirtyBit, netIdField, ref WeavingFailed);
|
||||
|
||||
//NOTE: is property even needed? Could just use a setter function?
|
||||
//create the property
|
||||
PropertyDefinition propertyDefinition = new PropertyDefinition($"Network{originalName}", PropertyAttributes.None, fd.FieldType)
|
||||
{
|
||||
GetMethod = get,
|
||||
SetMethod = set
|
||||
};
|
||||
|
||||
//add the methods and property to the type.
|
||||
td.Methods.Add(get);
|
||||
td.Methods.Add(set);
|
||||
td.Properties.Add(propertyDefinition);
|
||||
syncVarAccessLists.replacementSetterProperties[fd] = set;
|
||||
|
||||
// replace getter field if GameObject/NetworkIdentity so it uses
|
||||
// netId instead
|
||||
// -> only for GameObjects, otherwise an int syncvar's getter would
|
||||
// end up in recursion.
|
||||
if (fd.FieldType.IsNetworkIdentityField())
|
||||
{
|
||||
syncVarAccessLists.replacementGetterProperties[fd] = get;
|
||||
}
|
||||
}
|
||||
|
||||
public (List<FieldDefinition> syncVars, Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds) ProcessSyncVars(TypeDefinition td, ref bool WeavingFailed)
|
||||
{
|
||||
List<FieldDefinition> syncVars = new List<FieldDefinition>();
|
||||
Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds = new Dictionary<FieldDefinition, FieldDefinition>();
|
||||
|
||||
// the mapping of dirtybits to sync-vars is implicit in the order of the fields here. this order is recorded in m_replacementProperties.
|
||||
// start assigning syncvars at the place the base class stopped, if any
|
||||
int dirtyBitCounter = syncVarAccessLists.GetSyncVarStart(td.BaseType.FullName);
|
||||
|
||||
// find syncvars
|
||||
foreach (FieldDefinition fd in td.Fields)
|
||||
{
|
||||
if (fd.HasCustomAttribute<SyncVarAttribute>())
|
||||
{
|
||||
if ((fd.Attributes & FieldAttributes.Static) != 0)
|
||||
{
|
||||
Log.Error($"{fd.Name} cannot be static", fd);
|
||||
WeavingFailed = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fd.FieldType.IsArray)
|
||||
{
|
||||
Log.Error($"{fd.Name} has invalid type. Use SyncLists instead of arrays", fd);
|
||||
WeavingFailed = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (SyncObjectInitializer.ImplementsSyncObject(fd.FieldType))
|
||||
{
|
||||
Log.Warning($"{fd.Name} has [SyncVar] attribute. SyncLists should not be marked with SyncVar", fd);
|
||||
}
|
||||
else
|
||||
{
|
||||
syncVars.Add(fd);
|
||||
|
||||
ProcessSyncVar(td, fd, syncVarNetIds, 1L << dirtyBitCounter, ref WeavingFailed);
|
||||
dirtyBitCounter += 1;
|
||||
|
||||
if (dirtyBitCounter > SyncVarLimit)
|
||||
{
|
||||
Log.Error($"{td.Name} has > {SyncVarLimit} SyncVars. Consider refactoring your class into multiple components", td);
|
||||
WeavingFailed = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add all the new SyncVar __netId fields
|
||||
foreach (FieldDefinition fd in syncVarNetIds.Values)
|
||||
{
|
||||
td.Fields.Add(fd);
|
||||
}
|
||||
syncVarAccessLists.SetNumSyncVars(td.FullName, syncVars.Count);
|
||||
|
||||
return (syncVars, syncVarNetIds);
|
||||
}
|
||||
|
||||
public void WriteCallHookMethodUsingArgument(ILProcessor worker, MethodDefinition hookMethod, VariableDefinition oldValue)
|
||||
{
|
||||
WriteCallHookMethod(worker, hookMethod, oldValue, null);
|
||||
}
|
||||
|
||||
public void WriteCallHookMethodUsingField(ILProcessor worker, MethodDefinition hookMethod, VariableDefinition oldValue, FieldDefinition newValue, ref bool WeavingFailed)
|
||||
{
|
||||
if (newValue == null)
|
||||
{
|
||||
Log.Error("NewValue field was null when writing SyncVar hook");
|
||||
WeavingFailed = true;
|
||||
}
|
||||
|
||||
WriteCallHookMethod(worker, hookMethod, oldValue, newValue);
|
||||
}
|
||||
|
||||
void WriteCallHookMethod(ILProcessor worker, MethodDefinition hookMethod, VariableDefinition oldValue, FieldDefinition newValue)
|
||||
{
|
||||
WriteStartFunctionCall();
|
||||
|
||||
// write args
|
||||
WriteOldValue();
|
||||
WriteNewValue();
|
||||
|
||||
WriteEndFunctionCall();
|
||||
|
||||
|
||||
// *** Local functions used to write OpCodes ***
|
||||
// Local functions have access to function variables, no need to pass in args
|
||||
|
||||
void WriteOldValue()
|
||||
{
|
||||
worker.Emit(OpCodes.Ldloc, oldValue);
|
||||
}
|
||||
|
||||
void WriteNewValue()
|
||||
{
|
||||
// write arg1 or this.field
|
||||
if (newValue == null)
|
||||
{
|
||||
worker.Emit(OpCodes.Ldarg_1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// this.
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
// syncvar.get
|
||||
worker.Emit(OpCodes.Ldfld, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
// Writes this before method if it is not static
|
||||
void WriteStartFunctionCall()
|
||||
{
|
||||
// don't add this (Ldarg_0) if method is static
|
||||
if (!hookMethod.IsStatic)
|
||||
{
|
||||
// this before method call
|
||||
// e.g. this.onValueChanged
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
}
|
||||
}
|
||||
|
||||
// Calls method
|
||||
void WriteEndFunctionCall()
|
||||
{
|
||||
// only use Callvirt when not static
|
||||
OpCode opcode = hookMethod.IsStatic ? OpCodes.Call : OpCodes.Callvirt;
|
||||
worker.Emit(opcode, hookMethod);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f52c39bddd95d42b88f9cd554dfd9198
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
fileFormatVersion: 2
|
||||
guid: f52c39bddd95d42b88f9cd554dfd9198
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData: ''
|
||||
assetBundleName: ''
|
||||
assetBundleVariant: ''
|
||||
@@ -1,141 +1,139 @@
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
/// <summary>
|
||||
/// Processes [TargetRpc] methods in NetworkBehaviour
|
||||
/// </summary>
|
||||
public static class TargetRpcProcessor
|
||||
{
|
||||
// helper functions to check if the method has a NetworkConnection parameter
|
||||
public static bool HasNetworkConnectionParameter(MethodDefinition md)
|
||||
{
|
||||
return md.Parameters.Count > 0 &&
|
||||
md.Parameters[0].ParameterType.Is<NetworkConnection>();
|
||||
}
|
||||
|
||||
public static MethodDefinition ProcessTargetRpcInvoke(TypeDefinition td, MethodDefinition md, MethodDefinition rpcCallFunc)
|
||||
{
|
||||
MethodDefinition rpc = new MethodDefinition(Weaver.InvokeRpcPrefix + md.Name, MethodAttributes.Family |
|
||||
MethodAttributes.Static |
|
||||
MethodAttributes.HideBySig,
|
||||
WeaverTypes.Import(typeof(void)));
|
||||
|
||||
ILProcessor worker = rpc.Body.GetILProcessor();
|
||||
Instruction label = worker.Create(OpCodes.Nop);
|
||||
|
||||
NetworkBehaviourProcessor.WriteClientActiveCheck(worker, md.Name, label, "TargetRPC");
|
||||
|
||||
// setup for reader
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Castclass, td);
|
||||
|
||||
// NetworkConnection parameter is optional
|
||||
if (HasNetworkConnectionParameter(md))
|
||||
{
|
||||
// on server, the NetworkConnection parameter is a connection to client.
|
||||
// when the rpc is invoked on the client, it still has the same
|
||||
// function signature. we pass in the connection to server,
|
||||
// which is cleaner than just passing null)
|
||||
//NetworkClient.readyconnection
|
||||
//
|
||||
// TODO
|
||||
// a) .connectionToServer = best solution. no doubt.
|
||||
// b) NetworkClient.connection for now. add TODO to not use static later.
|
||||
worker.Emit(OpCodes.Call, WeaverTypes.ReadyConnectionReference);
|
||||
}
|
||||
|
||||
// process reader parameters and skip first one if first one is NetworkConnection
|
||||
if (!NetworkBehaviourProcessor.ReadArguments(md, worker, RemoteCallType.TargetRpc))
|
||||
return null;
|
||||
|
||||
// invoke actual command function
|
||||
worker.Emit(OpCodes.Callvirt, rpcCallFunc);
|
||||
worker.Emit(OpCodes.Ret);
|
||||
|
||||
NetworkBehaviourProcessor.AddInvokeParameters(rpc.Parameters);
|
||||
td.Methods.Add(rpc);
|
||||
return rpc;
|
||||
}
|
||||
|
||||
/* generates code like:
|
||||
public void TargetTest (NetworkConnection conn, int param)
|
||||
{
|
||||
NetworkWriter writer = new NetworkWriter ();
|
||||
writer.WritePackedUInt32 ((uint)param);
|
||||
base.SendTargetRPCInternal (conn, typeof(class), "TargetTest", val);
|
||||
}
|
||||
public void CallTargetTest (NetworkConnection conn, int param)
|
||||
{
|
||||
// whatever the user did before
|
||||
}
|
||||
|
||||
or if optional:
|
||||
public void TargetTest (int param)
|
||||
{
|
||||
NetworkWriter writer = new NetworkWriter ();
|
||||
writer.WritePackedUInt32 ((uint)param);
|
||||
base.SendTargetRPCInternal (null, typeof(class), "TargetTest", val);
|
||||
}
|
||||
public void CallTargetTest (int param)
|
||||
{
|
||||
// whatever the user did before
|
||||
}
|
||||
|
||||
Originally HLAPI put the send message code inside the Call function
|
||||
and then proceeded to replace every call to TargetTest with CallTargetTest
|
||||
|
||||
This method moves all the user's code into the "CallTargetRpc" method
|
||||
and replaces the body of the original method with the send message code.
|
||||
This way we do not need to modify the code anywhere else, and this works
|
||||
correctly in dependent assemblies
|
||||
|
||||
*/
|
||||
public static MethodDefinition ProcessTargetRpcCall(TypeDefinition td, MethodDefinition md, CustomAttribute targetRpcAttr)
|
||||
{
|
||||
MethodDefinition rpc = MethodProcessor.SubstituteMethod(td, md);
|
||||
|
||||
ILProcessor worker = md.Body.GetILProcessor();
|
||||
|
||||
NetworkBehaviourProcessor.WriteSetupLocals(worker);
|
||||
|
||||
NetworkBehaviourProcessor.WriteCreateWriter(worker);
|
||||
|
||||
// write all the arguments that the user passed to the TargetRpc call
|
||||
// (skip first one if first one is NetworkConnection)
|
||||
if (!NetworkBehaviourProcessor.WriteArguments(worker, md, RemoteCallType.TargetRpc))
|
||||
return null;
|
||||
|
||||
string rpcName = md.Name;
|
||||
|
||||
// invoke SendInternal and return
|
||||
// this
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
if (HasNetworkConnectionParameter(md))
|
||||
{
|
||||
// connection
|
||||
worker.Emit(OpCodes.Ldarg_1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// null
|
||||
worker.Emit(OpCodes.Ldnull);
|
||||
}
|
||||
worker.Emit(OpCodes.Ldtoken, td);
|
||||
// invokerClass
|
||||
worker.Emit(OpCodes.Call, WeaverTypes.getTypeFromHandleReference);
|
||||
worker.Emit(OpCodes.Ldstr, rpcName);
|
||||
// writer
|
||||
worker.Emit(OpCodes.Ldloc_0);
|
||||
worker.Emit(OpCodes.Ldc_I4, targetRpcAttr.GetField("channel", 0));
|
||||
worker.Emit(OpCodes.Callvirt, WeaverTypes.sendTargetRpcInternal);
|
||||
|
||||
NetworkBehaviourProcessor.WriteRecycleWriter(worker);
|
||||
|
||||
worker.Emit(OpCodes.Ret);
|
||||
|
||||
return rpc;
|
||||
}
|
||||
}
|
||||
}
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
// Processes [TargetRpc] methods in NetworkBehaviour
|
||||
public static class TargetRpcProcessor
|
||||
{
|
||||
// helper functions to check if the method has a NetworkConnection parameter
|
||||
public static bool HasNetworkConnectionParameter(MethodDefinition md)
|
||||
{
|
||||
return md.Parameters.Count > 0 &&
|
||||
md.Parameters[0].ParameterType.Is<NetworkConnection>();
|
||||
}
|
||||
|
||||
public static MethodDefinition ProcessTargetRpcInvoke(WeaverTypes weaverTypes, Readers readers, Logger Log, TypeDefinition td, MethodDefinition md, MethodDefinition rpcCallFunc, ref bool WeavingFailed)
|
||||
{
|
||||
MethodDefinition rpc = new MethodDefinition(Weaver.InvokeRpcPrefix + md.Name, MethodAttributes.Family |
|
||||
MethodAttributes.Static |
|
||||
MethodAttributes.HideBySig,
|
||||
weaverTypes.Import(typeof(void)));
|
||||
|
||||
ILProcessor worker = rpc.Body.GetILProcessor();
|
||||
Instruction label = worker.Create(OpCodes.Nop);
|
||||
|
||||
NetworkBehaviourProcessor.WriteClientActiveCheck(worker, weaverTypes, md.Name, label, "TargetRPC");
|
||||
|
||||
// setup for reader
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Castclass, td);
|
||||
|
||||
// NetworkConnection parameter is optional
|
||||
if (HasNetworkConnectionParameter(md))
|
||||
{
|
||||
// on server, the NetworkConnection parameter is a connection to client.
|
||||
// when the rpc is invoked on the client, it still has the same
|
||||
// function signature. we pass in the connection to server,
|
||||
// which is cleaner than just passing null)
|
||||
//NetworkClient.readyconnection
|
||||
//
|
||||
// TODO
|
||||
// a) .connectionToServer = best solution. no doubt.
|
||||
// b) NetworkClient.connection for now. add TODO to not use static later.
|
||||
worker.Emit(OpCodes.Call, weaverTypes.NetworkClientConnectionReference);
|
||||
}
|
||||
|
||||
// process reader parameters and skip first one if first one is NetworkConnection
|
||||
if (!NetworkBehaviourProcessor.ReadArguments(md, readers, Log, worker, RemoteCallType.TargetRpc, ref WeavingFailed))
|
||||
return null;
|
||||
|
||||
// invoke actual command function
|
||||
worker.Emit(OpCodes.Callvirt, rpcCallFunc);
|
||||
worker.Emit(OpCodes.Ret);
|
||||
|
||||
NetworkBehaviourProcessor.AddInvokeParameters(weaverTypes, rpc.Parameters);
|
||||
td.Methods.Add(rpc);
|
||||
return rpc;
|
||||
}
|
||||
|
||||
/* generates code like:
|
||||
public void TargetTest (NetworkConnection conn, int param)
|
||||
{
|
||||
NetworkWriter writer = new NetworkWriter ();
|
||||
writer.WritePackedUInt32 ((uint)param);
|
||||
base.SendTargetRPCInternal (conn, typeof(class), "TargetTest", val);
|
||||
}
|
||||
public void CallTargetTest (NetworkConnection conn, int param)
|
||||
{
|
||||
// whatever the user did before
|
||||
}
|
||||
|
||||
or if optional:
|
||||
public void TargetTest (int param)
|
||||
{
|
||||
NetworkWriter writer = new NetworkWriter ();
|
||||
writer.WritePackedUInt32 ((uint)param);
|
||||
base.SendTargetRPCInternal (null, typeof(class), "TargetTest", val);
|
||||
}
|
||||
public void CallTargetTest (int param)
|
||||
{
|
||||
// whatever the user did before
|
||||
}
|
||||
|
||||
Originally HLAPI put the send message code inside the Call function
|
||||
and then proceeded to replace every call to TargetTest with CallTargetTest
|
||||
|
||||
This method moves all the user's code into the "CallTargetRpc" method
|
||||
and replaces the body of the original method with the send message code.
|
||||
This way we do not need to modify the code anywhere else, and this works
|
||||
correctly in dependent assemblies
|
||||
|
||||
*/
|
||||
public static MethodDefinition ProcessTargetRpcCall(WeaverTypes weaverTypes, Writers writers, Logger Log, TypeDefinition td, MethodDefinition md, CustomAttribute targetRpcAttr, ref bool WeavingFailed)
|
||||
{
|
||||
MethodDefinition rpc = MethodProcessor.SubstituteMethod(Log, td, md, ref WeavingFailed);
|
||||
|
||||
ILProcessor worker = md.Body.GetILProcessor();
|
||||
|
||||
NetworkBehaviourProcessor.WriteSetupLocals(worker, weaverTypes);
|
||||
|
||||
NetworkBehaviourProcessor.WriteCreateWriter(worker, weaverTypes);
|
||||
|
||||
// write all the arguments that the user passed to the TargetRpc call
|
||||
// (skip first one if first one is NetworkConnection)
|
||||
if (!NetworkBehaviourProcessor.WriteArguments(worker, writers, Log, md, RemoteCallType.TargetRpc, ref WeavingFailed))
|
||||
return null;
|
||||
|
||||
string rpcName = md.Name;
|
||||
|
||||
// invoke SendInternal and return
|
||||
// this
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
if (HasNetworkConnectionParameter(md))
|
||||
{
|
||||
// connection
|
||||
worker.Emit(OpCodes.Ldarg_1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// null
|
||||
worker.Emit(OpCodes.Ldnull);
|
||||
}
|
||||
worker.Emit(OpCodes.Ldtoken, td);
|
||||
// invokerClass
|
||||
worker.Emit(OpCodes.Call, weaverTypes.getTypeFromHandleReference);
|
||||
worker.Emit(OpCodes.Ldstr, rpcName);
|
||||
// writer
|
||||
worker.Emit(OpCodes.Ldloc_0);
|
||||
worker.Emit(OpCodes.Ldc_I4, targetRpcAttr.GetField("channel", 0));
|
||||
worker.Emit(OpCodes.Callvirt, weaverTypes.sendTargetRpcInternal);
|
||||
|
||||
NetworkBehaviourProcessor.WriteRecycleWriter(worker, weaverTypes);
|
||||
|
||||
worker.Emit(OpCodes.Ret);
|
||||
|
||||
return rpc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fb3ce6c6f3f2942ae88178b86f5a8282
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
fileFormatVersion: 2
|
||||
guid: fb3ce6c6f3f2942ae88178b86f5a8282
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData: ''
|
||||
assetBundleName: ''
|
||||
assetBundleVariant: ''
|
||||
|
||||
Reference in New Issue
Block a user