Added multiplayer plugin

This commit is contained in:
Anthony Berg
2020-11-30 08:12:07 +00:00
parent 9cb342dd42
commit f64cf54803
450 changed files with 33131 additions and 10 deletions

View File

@@ -0,0 +1,127 @@
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 ignoreAuthority = commandAttr.GetField("ignoreAuthority", false);
// invoke internal send and return
// load 'base.' to call the SendCommand function with
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldtoken, td));
// invokerClass
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.getTypeFromHandleReference));
worker.Append(worker.Create(OpCodes.Ldstr, cmdName));
// writer
worker.Append(worker.Create(OpCodes.Ldloc_0));
worker.Append(worker.Create(OpCodes.Ldc_I4, channel));
worker.Append(worker.Create(ignoreAuthority ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0));
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.sendCommandInternal));
NetworkBehaviourProcessor.WriteRecycleWriter(worker);
worker.Append(worker.Create(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.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Castclass, td));
if (!NetworkBehaviourProcessor.ReadArguments(method, worker, RemoteCallType.Command))
return null;
AddSenderConnection(method, worker);
// invoke actual command function
worker.Append(worker.Create(OpCodes.Callvirt, cmdCallFunc));
worker.Append(worker.Create(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)
// exmaple: static void InvokeCmdCmdSendCommand(NetworkBehaviour obj, NetworkReader reader, NetworkConnection connection)
worker.Append(worker.Create(OpCodes.Ldarg_2));
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 73f6c9cdbb9e54f65b3a0a35cc8e55c2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// Removed Oct 1 2020

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fd67b3f7c2d66074a9bc7a23787e2ffb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// removed Oct 5 2020

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e25c00c88fc134f6ea7ab00ae4db8083
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,125 @@
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
public static class MethodProcessor
{
private 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);
// 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
// eg CallCmdDoSomething
if (!callName.StartsWith(RpcPrefix))
return;
// eg 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;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 661e1af528e3441f79e1552fb5ec4e0e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,45 @@
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);
}
}
}
}

View File

@@ -0,0 +1,11 @@
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

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8118d606be3214e5d99943ec39530dd8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,160 @@
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)
{
// dont 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)
{
// dont 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)
{
// dont 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;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d48f1ab125e9940a995603796bccc59e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,175 @@
// 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 asmebly has any declared writers then it is "modified"
modified |= LoadDeclaredWriters(CurrentAssembly, klass);
modified |= LoadDeclaredReaders(CurrentAssembly, klass);
}
}
foreach (TypeDefinition klass in assembly.MainModule.Types)
{
// if asmebly 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)
{
return currentAssembly.MainModule.AssemblyReferences.Any(assemblyReference =>
assemblyReference.Name == nameof(UnityEditor)
);
}
/// <summary>
/// Creates a method that will store all the readers and writers into
/// <see cref="Writer{T}.write"/> and <see cref="Reader{T}.read"/>
///
/// The method will be marked InitializeOnLoadMethodAttribute so it gets
/// executed before mirror runtime code
/// </summary>
/// <param name="currentAssembly"></param>
public static void InitializeReaderAndWriters(AssemblyDefinition currentAssembly)
{
MethodDefinition rwInitializer = new MethodDefinition("InitReadWriters", MethodAttributes.Public |
MethodAttributes.Static,
WeaverTypes.Import(typeof(void)));
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);
if (IsEditorAssembly(currentAssembly))
{
// editor assembly, add InitializeOnLoadMethod too. Useful for the editor tests
System.Reflection.ConstructorInfo initializeOnLoadConstructor = typeof(InitializeOnLoadMethodAttribute).GetConstructor(new Type[0]);
CustomAttribute initializeCustomConstructorRef = new CustomAttribute(currentAssembly.MainModule.ImportReference(initializeOnLoadConstructor));
rwInitializer.CustomAttributes.Add(initializeCustomConstructorRef);
}
ILProcessor worker = rwInitializer.Body.GetILProcessor();
Writers.InitializeWriters(worker);
Readers.InitializeReaders(worker);
worker.Append(worker.Create(OpCodes.Ret));
TypeDefinition generateClass = Weaver.WeaveLists.generateContainerClass;
generateClass.Methods.Add(rwInitializer);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f3263602f0a374ecd8d08588b1fc2f76
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,105 @@
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.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Castclass, td));
if (!NetworkBehaviourProcessor.ReadArguments(md, worker, RemoteCallType.ClientRpc))
return null;
// invoke actual command function
worker.Append(worker.Create(OpCodes.Callvirt, rpcCallFunc));
worker.Append(worker.Create(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.Append(worker.Create(OpCodes.Ldstr, "Call ClientRpc function " + md.Name));
worker.Append(worker.Create(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 excludeOwner = clientRpcAttr.GetField("excludeOwner", false);
// invoke SendInternal and return
// this
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldtoken, td));
// invokerClass
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.getTypeFromHandleReference));
worker.Append(worker.Create(OpCodes.Ldstr, rpcName));
// writer
worker.Append(worker.Create(OpCodes.Ldloc_0));
worker.Append(worker.Create(OpCodes.Ldc_I4, channel));
worker.Append(worker.Create(excludeOwner ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0));
worker.Append(worker.Create(OpCodes.Callvirt, WeaverTypes.sendRpcInternal));
NetworkBehaviourProcessor.WriteRecycleWriter(worker);
worker.Append(worker.Create(OpCodes.Ret));
return rpc;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a3cb7051ff41947e59bba58bdd2b73fc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,153 @@
// 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));
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 024f251bf693bb345b90b9177892d534
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// Removed Oct 1 2020

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 29e4a45f69822462ab0b15adda962a29
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// removed 2020-09

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a5d8b25543a624384944b599e5a832a8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// Removed Oct 1 2020

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4f3445268e45d437fac325837aff3246
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,47 @@
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.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldfld, fd));
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.InitSyncObjectReference));
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d02219b00b3674e59a2151f41e791688
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,63 @@
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);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 78f71efc83cde4917b7d21efa90bcc9a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,434 @@
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.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldfld, netFieldId));
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldflda, fd));
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.getSyncVarGameObjectReference));
worker.Append(worker.Create(OpCodes.Ret));
}
// [SyncVar] NetworkIdentity?
else if (fd.FieldType.Is<NetworkIdentity>())
{
// return this.GetSyncVarNetworkIdentity(ref field, uint netId);
// this.
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldfld, netFieldId));
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldflda, fd));
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.getSyncVarNetworkIdentityReference));
worker.Append(worker.Create(OpCodes.Ret));
}
// [SyncVar] int, string, etc.
else
{
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldfld, fd));
worker.Append(worker.Create(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.Append(worker.Create(OpCodes.Ldarg_0));
// new value to set
worker.Append(worker.Create(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.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldfld, netFieldId));
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.syncVarGameObjectEqualReference));
}
else if (fd.FieldType.Is<NetworkIdentity>())
{
// reference to netId Field to set
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldfld, netFieldId));
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.syncVarNetworkIdentityEqualReference));
}
else
{
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldflda, fd));
GenericInstanceMethod syncVarEqualGm = new GenericInstanceMethod(WeaverTypes.syncVarEqualReference);
syncVarEqualGm.GenericArguments.Add(fd.FieldType);
worker.Append(worker.Create(OpCodes.Call, syncVarEqualGm));
}
worker.Append(worker.Create(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.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldfld, fd));
worker.Append(worker.Create(OpCodes.Stloc, oldValue));
// this
worker.Append(worker.Create(OpCodes.Ldarg_0));
// new value to set
worker.Append(worker.Create(OpCodes.Ldarg_1));
// reference to field to set
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldflda, fd));
// dirty bit
// 8 byte integer aka long
worker.Append(worker.Create(OpCodes.Ldc_I8, dirtyBit));
if (fd.FieldType.Is<UnityEngine.GameObject>())
{
// reference to netId Field to set
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldflda, netFieldId));
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.setSyncVarGameObjectReference));
}
else if (fd.FieldType.Is<NetworkIdentity>())
{
// reference to netId Field to set
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldflda, netFieldId));
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.setSyncVarNetworkIdentityReference));
}
else
{
// make generic version of SetSyncVar with field type
GenericInstanceMethod gm = new GenericInstanceMethod(WeaverTypes.setSyncVarReference);
gm.GenericArguments.Add(fd.FieldType);
// invoke SetSyncVar
worker.Append(worker.Create(OpCodes.Call, gm));
}
MethodDefinition hookMethod = GetHookMethod(td, fd);
if (hookMethod != null)
{
//if (NetworkServer.localClientActive && !getSyncVarHookGuard(dirtyBit))
Instruction label = worker.Create(OpCodes.Nop);
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.NetworkServerGetLocalClientActive));
worker.Append(worker.Create(OpCodes.Brfalse, label));
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldc_I8, dirtyBit));
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.getSyncVarHookGuard));
worker.Append(worker.Create(OpCodes.Brtrue, label));
// setSyncVarHookGuard(dirtyBit, true);
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldc_I8, dirtyBit));
worker.Append(worker.Create(OpCodes.Ldc_I4_1));
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.setSyncVarHookGuard));
// call hook (oldValue, newValue)
// Generates: OnValueChanged(oldValue, value);
WriteCallHookMethodUsingArgument(worker, hookMethod, oldValue);
// setSyncVarHookGuard(dirtyBit, false);
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldc_I8, dirtyBit));
worker.Append(worker.Create(OpCodes.Ldc_I4_0));
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.setSyncVarHookGuard));
worker.Append(label);
}
worker.Append(endOfMethod);
worker.Append(worker.Create(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;
if (fd.FieldType.Is<UnityEngine.GameObject>() ||
fd.FieldType.Is<NetworkIdentity>())
{
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.Is<UnityEngine.GameObject>() ||
fd.FieldType.Is<NetworkIdentity>())
{
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.Append(worker.Create(OpCodes.Ldloc, oldValue));
}
void WriteNewValue()
{
// write arg1 or this.field
if (newValue == null)
{
worker.Append(worker.Create(OpCodes.Ldarg_1));
}
else
{
// this.
worker.Append(worker.Create(OpCodes.Ldarg_0));
// syncvar.get
worker.Append(worker.Create(OpCodes.Ldfld, newValue));
}
}
// Writes this before method if it is not static
void WriteStartFunctionCall()
{
// dont add this (Ldarg_0) if method is static
if (!hookMethod.IsStatic)
{
// this before method call
// eg this.onValueChanged
worker.Append(worker.Create(OpCodes.Ldarg_0));
}
}
// Calls method
void WriteEndFunctionCall()
{
// only use Callvirt when not static
OpCode opcode = hookMethod.IsStatic ? OpCodes.Call : OpCodes.Callvirt;
worker.Append(worker.Create(opcode, hookMethod));
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f52c39bddd95d42b88f9cd554dfd9198
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,134 @@
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.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Castclass, td));
// NetworkConnection parameter is optional
if (HasNetworkConnectionParameter(md))
{
// if call has NetworkConnection write clients connection as first arg
//ClientScene.readyconnection
worker.Append(worker.Create(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.Append(worker.Create(OpCodes.Callvirt, rpcCallFunc));
worker.Append(worker.Create(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.Append(worker.Create(OpCodes.Ldarg_0));
if (HasNetworkConnectionParameter(md))
{
// connection
worker.Append(worker.Create(OpCodes.Ldarg_1));
}
else
{
// null
worker.Append(worker.Create(OpCodes.Ldnull));
}
worker.Append(worker.Create(OpCodes.Ldtoken, td));
// invokerClass
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.getTypeFromHandleReference));
worker.Append(worker.Create(OpCodes.Ldstr, rpcName));
// writer
worker.Append(worker.Create(OpCodes.Ldloc_0));
worker.Append(worker.Create(OpCodes.Ldc_I4, targetRpcAttr.GetField("channel", 0)));
worker.Append(worker.Create(OpCodes.Callvirt, WeaverTypes.sendTargetRpcInternal));
NetworkBehaviourProcessor.WriteRecycleWriter(worker);
worker.Append(worker.Create(OpCodes.Ret));
return rpc;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fb3ce6c6f3f2942ae88178b86f5a8282
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: