This commit is contained in:
2025-06-16 15:14:23 +02:00
commit 074e590073
3174 changed files with 428263 additions and 0 deletions

View File

@ -0,0 +1,130 @@
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)
{
NetworkWriterPooled writer = NetworkWriterPool.Get();
writer.Write(thrusting);
writer.WritePackedUInt32((uint)spin);
base.SendCommandInternal(cmdName, cmdHash, writer, channel);
NetworkWriterPool.Return(writer);
}
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.WriteGetWriter(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;
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);
// pass full function name to avoid ClassA.Func <-> ClassB.Func collisions
worker.Emit(OpCodes.Ldstr, md.FullName);
// pass the function hash so we don't have to compute it at runtime
// otherwise each GetStableHash call requires O(N) complexity.
// noticeable for long function names:
// https://github.com/MirrorNetworking/Mirror/issues/3375
worker.Emit(OpCodes.Ldc_I4, md.FullName.GetStableHashCode());
// 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.WriteReturnWriter(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)
{
string cmdName = Weaver.GenerateMethodName(RemoteCalls.RemoteProcedureCalls.InvokeRpcPrefix, method);
MethodDefinition cmd = new MethodDefinition(cmdName,
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);
}
}
}
}
}

View File

@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 73f6c9cdbb9e54f65b3a0a35cc8e55c2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Editor/Weaver/Processors/CommandProcessor.cs
uploadId: 736421

View File

@ -0,0 +1,139 @@
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
public static class MethodProcessor
{
const string RpcPrefix = "UserCode_";
// For a function like
// [ClientRpc] void RpcTest(int value),
// Weaver substitutes the method and moves the code to a new method:
// UserCode_RpcTest(int value) <- contains original code
// RpcTest(int value) <- serializes parameters, sends the message
//
// Note that all the calls to the method remain untouched.
// FixRemoteCallToBaseMethod replaces them afterwards.
public static MethodDefinition SubstituteMethod(Logger Log, TypeDefinition td, MethodDefinition md, ref bool WeavingFailed)
{
string newName = Weaver.GenerateMethodName(RpcPrefix, md);
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;
}
// For a function like
// [ClientRpc] void RpcTest(int value),
// Weaver substitutes the method and moves the code to a new method:
// UserCode_RpcTest(int value) <- contains original code
// RpcTest(int value) <- serializes parameters, sends the message
//
// FixRemoteCallToBaseMethod replaces all calls to
// RpcTest(value)
// with
// UserCode_RpcTest(value)
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)
{
// is this instruction a Call to a method?
// if yes, output the method so we can check it.
if (IsCallToMethod(instruction, out MethodDefinition calledMethod))
{
// when considering if 'calledMethod' is a call to 'method',
// we originally compared .Name.
//
// to fix IL2CPP build bugs with overloaded Rpcs, we need to
// generated rpc names like
// RpcTest(string value) => RpcTestString(strig value)
// RpcTest(int value) => RpcTestInt(int value)
// to make them unique.
//
// calledMethod.Name is still "RpcTest", so we need to
// convert this to the generated name as well before comparing.
string calledMethodName_Generated = Weaver.GenerateMethodName("", calledMethod);
if (calledMethodName_Generated == 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;
}
}
}
}

View File

@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 661e1af528e3441f79e1552fb5ec4e0e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Editor/Weaver/Processors/MethodProcessor.cs
uploadId: 736421

View File

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

View File

@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 35c16722912b64af894e4f6668f2e54c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Editor/Weaver/Processors/MonoBehaviourProcessor.cs
uploadId: 736421

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 8118d606be3214e5d99943ec39530dd8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs
uploadId: 736421

View File

@ -0,0 +1,260 @@
// finds all readers and writers and register them
using System.Collections.Generic;
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);
// process dependencies first, this way weaver can process types of other assemblies properly.
// fixes: https://github.com/MirrorNetworking/Mirror/issues/2503
//
// find NetworkReader/Writer extensions in referenced assemblies
IEnumerable<AssemblyDefinition> assemblyReferences = FindProcessTargetAssemblies(CurrentAssembly, resolver)
.Where(assembly => assembly != null && assembly != CurrentAssembly);
foreach (AssemblyDefinition referencedAssembly in assemblyReferences)
ProcessAssemblyClasses(CurrentAssembly, referencedAssembly, writers, readers, ref WeavingFailed);
return ProcessAssemblyClasses(CurrentAssembly, CurrentAssembly, writers, readers, ref WeavingFailed);
}
// look for assembly instead of relying on CurrentAssembly.MainModule.
// fixes: https://github.com/MirrorNetworking/Mirror/issues/3816
static List<AssemblyDefinition> FindProcessTargetAssemblies(AssemblyDefinition assembly, IAssemblyResolver resolver)
{
HashSet<string> processedAssemblies = new HashSet<string>();
List<AssemblyDefinition> assemblies = new List<AssemblyDefinition>();
ProcessAssembly(assembly);
return assemblies;
void ProcessAssembly(AssemblyDefinition current)
{
// If the assembly has already been processed, we skip it
if (current.FullName == Weaver.MirrorAssemblyName || !processedAssemblies.Add(current.FullName))
return;
IEnumerable<AssemblyNameReference> references = current.MainModule.AssemblyReferences;
// If there is no Mirror reference, there will be no ReaderWriter or NetworkMessage, so skip
if (references.All(reference => reference.Name != Weaver.MirrorAssemblyName))
return;
// Add the assembly to the processed set and list
assemblies.Add(current);
// Process the references of the current assembly
foreach (AssemblyNameReference reference in references)
{
AssemblyDefinition referencedAssembly = resolver.Resolve(reference);
if (referencedAssembly != null)
ProcessAssembly(referencedAssembly);
}
}
}
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);
}
}
}

View File

@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: f3263602f0a374ecd8d08588b1fc2f76
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Editor/Weaver/Processors/ReaderWriterProcessor.cs
uploadId: 736421

View File

@ -0,0 +1,104 @@
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)
{
string rpcName = Weaver.GenerateMethodName(RemoteCalls.RemoteProcedureCalls.InvokeRpcPrefix, md);
MethodDefinition rpc = new MethodDefinition(rpcName, 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.WriteGetWriter(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;
int channel = clientRpcAttr.GetField("channel", 0);
bool includeOwner = clientRpcAttr.GetField("includeOwner", true);
// invoke SendInternal and return
// this
worker.Emit(OpCodes.Ldarg_0);
// pass full function name to avoid ClassA.Func <-> ClassB.Func collisions
worker.Emit(OpCodes.Ldstr, md.FullName);
// pass the function hash so we don't have to compute it at runtime
// otherwise each GetStableHash call requires O(N) complexity.
// noticeable for long function names:
// https://github.com/MirrorNetworking/Mirror/issues/3375
worker.Emit(OpCodes.Ldc_I4, md.FullName.GetStableHashCode());
// 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.WriteReturnWriter(worker, weaverTypes);
worker.Emit(OpCodes.Ret);
return rpc;
}
}
}

View File

@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: a3cb7051ff41947e59bba58bdd2b73fc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Editor/Weaver/Processors/RpcProcessor.cs
uploadId: 736421

View File

@ -0,0 +1,163 @@
// 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(RemoteCalls.RemoteProcedureCalls.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)
{
// this causes IL2CPP build issues with generic out parameters:
// https://github.com/MirrorNetworking/Mirror/issues/3482
// TypeReference elementType = param.ParameterType.GetElementType();
//
// instead we need to use ElementType not GetElementType()
// GetElementType() will get the element type of the inner elementType
// which will return wrong type for arrays and generic
// credit: JamesFrowen
ByReferenceType byRefType = (ByReferenceType)param.ParameterType;
TypeReference elementType = byRefType.ElementType;
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,18 @@
fileFormatVersion: 2
guid: 024f251bf693bb345b90b9177892d534
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Editor/Weaver/Processors/ServerClientAttributeProcessor.cs
uploadId: 736421

View File

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

View File

@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: d02219b00b3674e59a2151f41e791688
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Editor/Weaver/Processors/SyncObjectInitializer.cs
uploadId: 736421

View File

@ -0,0 +1,90 @@
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.IsGenericParameter || fd.ContainsGenericParameter)
{
// can't call .Resolve on generic ones
continue;
}
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);
}
}
}
}

View File

@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 78f71efc83cde4917b7d21efa90bcc9a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Editor/Weaver/Processors/SyncObjectProcessor.cs
uploadId: 736421

View File

@ -0,0 +1,206 @@
// [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(Logger Log, ModuleDefinition moduleDef, SyncVarAccessLists syncVarAccessLists)
{
DateTime startTime = DateTime.Now;
// process all classes in this module
foreach (TypeDefinition td in moduleDef.Types)
{
if (td.IsClass)
{
ProcessClass(Log, syncVarAccessLists, td);
}
}
Console.WriteLine($" ProcessSitesModule {moduleDef.Name} elapsed time:{(DateTime.Now - startTime)}");
}
static void ProcessClass(Logger Log, SyncVarAccessLists syncVarAccessLists, TypeDefinition td)
{
//Console.WriteLine($" ProcessClass {td}");
// process all methods in this class
foreach (MethodDefinition md in td.Methods)
{
ProcessMethod(Log, syncVarAccessLists, md);
}
// processes all nested classes in this class recursively
foreach (TypeDefinition nested in td.NestedTypes)
{
ProcessClass(Log, syncVarAccessLists, nested);
}
}
static void ProcessMethod(Logger Log, 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(RemoteCalls.RemoteProcedureCalls.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(Log, syncVarAccessLists, md, instr, i);
}
}
}
static int ProcessInstruction(Logger Log, SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction instr, int iCount)
{
// stfld (sets value of a field)?
if (instr.OpCode == OpCodes.Stfld)
{
// operand is a FieldDefinition in the same assembly?
if (instr.Operand is FieldDefinition opFieldst)
{
ProcessSetInstruction(syncVarAccessLists, md, instr, opFieldst);
}
// operand is a FieldReference in another assembly?
// this is not supported just yet.
// compilation error is better than silently failing SyncVar serialization at runtime.
// https://github.com/MirrorNetworking/Mirror/issues/3525
else if (instr.Operand is FieldReference opFieldstRef)
{
// resolve it from the other assembly
FieldDefinition field = opFieldstRef.Resolve();
// [SyncVar]?
if (field.HasCustomAttribute<SyncVarAttribute>())
{
// ILPostProcessor would need to Process() the assembly's
// references before processing this one.
// we can not control the order.
// instead, Log an error to suggest adding a SetSyncVar(value) function.
// this is a very easy solution for a very rare edge case.
Log.Error($"'[SyncVar] {opFieldstRef.DeclaringType.Name}.{opFieldstRef.Name}' in '{field.Module.Name}' is modified by '{md.FullName}' in '{md.Module.Name}'. Modifying a [SyncVar] from another assembly is not supported. Please add a: 'public void Set{opFieldstRef.Name}(value) {{ this.{opFieldstRef.Name} = value; }}' method in '{opFieldstRef.DeclaringType.Name}' and call this function from '{md.FullName}' instead.");
}
}
}
// ldfld (load value of a field)?
if (instr.OpCode == OpCodes.Ldfld)
{
// operand is a FieldDefinition in the same assembly?
if (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)
{
// operand is a FieldDefinition in the same assembly?
if (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;
}
}
}

View File

@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: d48f1ab125e9940a995603796bccc59e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Editor/Weaver/Processors/SyncVarAttributeAccessReplacer.cs
uploadId: 736421

View File

@ -0,0 +1,515 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Mono.CecilX;
using Mono.CecilX.Cil;
using Mono.CecilX.Rocks;
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);
}
// Create a field definition for a field that will store the Action<T, T> delegate instance for the syncvar hook method (only instantiate delegate once)
public FieldDefinition CreateNewActionFieldDefinitionFromHookMethod(FieldDefinition syncVarField)
{
TypeReference actionRef = assembly.MainModule.ImportReference(typeof(Action<,>));
GenericInstanceType syncVarHookActionDelegateType = actionRef.MakeGenericInstanceType(syncVarField.FieldType, syncVarField.FieldType);
string syncVarHookDelegateFieldName = $"_Mirror_SyncVarHookDelegate_{syncVarField.Name}";
return new FieldDefinition(syncVarHookDelegateFieldName, FieldAttributes.Public, syncVarHookActionDelegateType);
}
// push hook from GetHookMethod() onto the stack as a new Action<T,T>.
// allows for reuse without handling static/virtual cases every time.
// perf warning: it is recommended to use this method only when generating IL to create a new Action<T, T>() in order to store it into a field
// avoid using this to emit IL to instantiate a new action instance every single time one is needed for the same method
public void GenerateNewActionFromHookMethod(FieldDefinition syncVar, ILProcessor worker, MethodDefinition hookMethod)
{
// IL_000a: ldarg.0
// IL_000b: ldftn instance void Mirror.Examples.Tanks.Tank::ExampleHook(int32, int32)
// IL_0011: newobj instance void class [netstandard]System.Action`2<int32, int32>::.ctor(object, native int)
// we support static hooks and instance hooks.
if (hookMethod.IsStatic)
{
// for static hooks, we need to push 'null' first.
// we can't just push nothing.
// stack would get out of balance because we already pushed
// other stuff above.
worker.Emit(OpCodes.Ldnull);
}
else
{
// for instance hooks, we need to push 'this.' first.
worker.Emit(OpCodes.Ldarg_0);
}
MethodReference hookMethodReference;
// if the network behaviour class is generic, we need to make the method reference generic for correct IL
if (hookMethod.DeclaringType.HasGenericParameters)
{
hookMethodReference = hookMethod.MakeHostInstanceGeneric(hookMethod.Module, hookMethod.DeclaringType.MakeGenericInstanceType(hookMethod.DeclaringType.GenericParameters.ToArray()));
}
else
{
hookMethodReference = hookMethod;
}
// we support regular and virtual hook functions.
if (hookMethod.IsVirtual)
{
// for virtual / overwritten hooks, we need different IL.
// this is from simply testing Action = VirtualHook; in C#.
worker.Emit(OpCodes.Dup);
worker.Emit(OpCodes.Ldvirtftn, hookMethodReference);
}
else
{
worker.Emit(OpCodes.Ldftn, hookMethodReference);
}
// call 'new Action<T,T>()' constructor to convert the function to an action
// we need to make an instance of the generic Action<T,T>.
TypeReference actionRef = assembly.MainModule.ImportReference(typeof(Action<,>));
GenericInstanceType genericInstance = actionRef.MakeGenericInstanceType(syncVar.FieldType, syncVar.FieldType);
worker.Emit(OpCodes.Newobj, weaverTypes.ActionT_T.MakeHostInstanceGeneric(assembly.MainModule, genericInstance));
}
// generates CIL to set an Action<T,T> instance field to a new Action<T,T>(hookMethod)
// this.hookDelegate = new Action<T, T>(HookMethod);
public void GenerateSyncVarHookDelegateInitializer(ILProcessor worker, FieldDefinition syncVar, FieldDefinition hookDelegate, MethodDefinition hookMethod)
{
// push this
worker.Emit(OpCodes.Ldarg_0);
// push new Action<T, T>(hookMethod)
GenerateNewActionFromHookMethod(syncVar, worker, hookMethod);
// set field
worker.Emit(OpCodes.Stfld, hookDelegate);
}
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();
FieldReference fr;
if (fd.DeclaringType.HasGenericParameters)
{
fr = fd.MakeHostInstanceGeneric();
}
else
{
fr = fd;
}
FieldReference netIdFieldReference = null;
if (netFieldId != null)
{
if (netFieldId.DeclaringType.HasGenericParameters)
{
netIdFieldReference = netFieldId.MakeHostInstanceGeneric();
}
else
{
netIdFieldReference = netFieldId;
}
}
// [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, netIdFieldReference);
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldflda, fr);
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, netIdFieldReference);
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldflda, fr);
worker.Emit(OpCodes.Call, weaverTypes.getSyncVarNetworkIdentityReference);
worker.Emit(OpCodes.Ret);
}
// handle both NetworkBehaviour and inheritors.
// fixes: https://github.com/MirrorNetworking/Mirror/issues/2939
else if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>() || fd.FieldType.Is<NetworkBehaviour>())
{
// return this.GetSyncVarNetworkBehaviour<T>(ref field, uint netId);
// this.
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldfld, netIdFieldReference);
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldflda, fr);
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, fr);
worker.Emit(OpCodes.Ret);
}
get.Body.Variables.Add(new VariableDefinition(fd.FieldType));
get.Body.InitLocals = true;
get.SemanticsAttributes = MethodSemanticsAttributes.Getter;
return get;
}
// for [SyncVar] health, weaver generates
//
// NetworkHealth
// {
// get => health;
// set => GeneratedSyncVarSetter(...)
// }
//
// the setter used to be manually IL generated, but we moved it to C# :)
public MethodDefinition GenerateSyncVarSetter(TypeDefinition td, FieldDefinition fd, string originalName, long dirtyBit, FieldDefinition netFieldId, Dictionary<FieldDefinition, (FieldDefinition hookDelegateField, MethodDefinition hookMethod)> syncVarHookDelegates, 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();
FieldReference fr;
if (fd.DeclaringType.HasGenericParameters)
{
fr = fd.MakeHostInstanceGeneric();
}
else
{
fr = fd;
}
FieldReference netIdFieldReference = null;
if (netFieldId != null)
{
if (netFieldId.DeclaringType.HasGenericParameters)
{
netIdFieldReference = netFieldId.MakeHostInstanceGeneric();
}
else
{
netIdFieldReference = netFieldId;
}
}
// if (!SyncVarEqual(value, ref playerData))
Instruction endOfMethod = worker.Create(OpCodes.Nop);
// NOTE: SyncVar...Equal functions are static.
// don't Emit Ldarg_0 aka 'this'.
// call WeaverSyncVarSetter<T>(T value, ref T field, ulong dirtyBit, Action<T, T> OnChanged = null)
// IL_0000: ldarg.0
// IL_0001: ldarg.1
// IL_0002: ldarg.0
// IL_0003: ldflda int32 Mirror.Examples.Tanks.Tank::health
// IL_0008: ldc.i4.1
// IL_0009: conv.i8
// IL_000a: ldnull
// IL_000b: call instance void [Mirror]Mirror.NetworkBehaviour::GeneratedSyncVarSetter<int32>(!!0, !!0&, uint64, class [netstandard]System.Action`2<!!0, !!0>)
// IL_0010: ret
// 'this.' for the call
worker.Emit(OpCodes.Ldarg_0);
// first push 'value'
worker.Emit(OpCodes.Ldarg_1);
// push 'ref T this.field'
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldflda, fr);
// push the dirty bit for this SyncVar
worker.Emit(OpCodes.Ldc_I8, dirtyBit);
// hook? then push 'this.HookDelegate' onto stack
MethodDefinition hookMethod = GetHookMethod(td, fd, ref WeavingFailed);
if (hookMethod != null)
{
// Create the field that will store a single instance of the hook as a delegate (field will be set in constructor)
FieldDefinition hookActionDelegateField = CreateNewActionFieldDefinitionFromHookMethod(fd);
syncVarHookDelegates[fd] = (hookActionDelegateField, hookMethod);
// push this.hookActionDelegateField
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldfld, hookActionDelegateField);
}
// otherwise push 'null' as hook
else
{
worker.Emit(OpCodes.Ldnull);
}
// call GeneratedSyncVarSetter<T>.
// special cases for GameObject/NetworkIdentity/NetworkBehaviour
// passing netId too for persistence.
if (fd.FieldType.Is<UnityEngine.GameObject>())
{
// GameObject setter needs one more parameter: netId field ref
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldflda, netIdFieldReference);
worker.Emit(OpCodes.Call, weaverTypes.generatedSyncVarSetter_GameObject);
}
else if (fd.FieldType.Is<NetworkIdentity>())
{
// NetworkIdentity setter needs one more parameter: netId field ref
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldflda, netIdFieldReference);
worker.Emit(OpCodes.Call, weaverTypes.generatedSyncVarSetter_NetworkIdentity);
}
// handle both NetworkBehaviour and inheritors.
// fixes: https://github.com/MirrorNetworking/Mirror/issues/2939
else if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>() || fd.FieldType.Is<NetworkBehaviour>())
{
// NetworkIdentity setter needs one more parameter: netId field ref
// (actually its a NetworkBehaviourSyncVar type)
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldflda, netIdFieldReference);
// make generic version of GeneratedSyncVarSetter_NetworkBehaviour<T>
MethodReference getFunc = weaverTypes.generatedSyncVarSetter_NetworkBehaviour_T.MakeGeneric(assembly.MainModule, fd.FieldType);
worker.Emit(OpCodes.Call, getFunc);
}
else
{
// make generic version of GeneratedSyncVarSetter<T>
MethodReference generic = weaverTypes.generatedSyncVarSetter.MakeGeneric(assembly.MainModule, fd.FieldType);
worker.Emit(OpCodes.Call, generic);
}
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, Dictionary<FieldDefinition, (FieldDefinition hookDelegateField, MethodDefinition hookMethod)> syncVarHookDelegates, 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
// handle both NetworkBehaviour and inheritors.
// fixes: https://github.com/MirrorNetworking/Mirror/issues/2939
if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>() || fd.FieldType.Is<NetworkBehaviour>())
{
netIdField = new FieldDefinition($"___{fd.Name}NetId",
FieldAttributes.Family, // needs to be protected for generic classes, otherwise access isn't allowed
weaverTypes.Import<NetworkBehaviourSyncVar>());
netIdField.DeclaringType = td;
syncVarNetIds[fd] = netIdField;
}
else if (fd.FieldType.IsNetworkIdentityField())
{
netIdField = new FieldDefinition($"___{fd.Name}NetId",
FieldAttributes.Family, // needs to be protected for generic classes, otherwise access isn't allowed
weaverTypes.Import<uint>());
netIdField.DeclaringType = td;
syncVarNetIds[fd] = netIdField;
}
MethodDefinition get = GenerateSyncVarGetter(fd, originalName, netIdField);
MethodDefinition set = GenerateSyncVarSetter(td, fd, originalName, dirtyBit, netIdField, syncVarHookDelegates, 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, Dictionary<FieldDefinition, (FieldDefinition hookDelegateField, MethodDefinition hookMethod)> syncVarHookDelegates) ProcessSyncVars(TypeDefinition td, ref bool WeavingFailed)
{
List<FieldDefinition> syncVars = new List<FieldDefinition>();
Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds = new Dictionary<FieldDefinition, FieldDefinition>();
Dictionary<FieldDefinition, (FieldDefinition hookDelegateField, MethodDefinition hookMethod)> syncVarHookDelegates = new Dictionary<FieldDefinition, (FieldDefinition hookDelegateField, MethodDefinition hookMethod)>();
// 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.IsGenericParameter)
{
Log.Error($"{fd.Name} has generic type. Generic SyncVars are not supported", 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, syncVarHookDelegates, 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);
}
// add all of the new SyncVar Action<T,T> fields
foreach((FieldDefinition hookDelegateInstanceField, MethodDefinition) entry in syncVarHookDelegates.Values)
{
td.Fields.Add(entry.hookDelegateInstanceField);
}
// include parent class syncvars
// fixes: https://github.com/MirrorNetworking/Mirror/issues/3457
int parentSyncVarCount = syncVarAccessLists.GetSyncVarStart(td.BaseType.FullName);
syncVarAccessLists.SetNumSyncVars(td.FullName, parentSyncVarCount + syncVars.Count);
return (syncVars, syncVarNetIds, syncVarHookDelegates);
}
}
}

View File

@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: f52c39bddd95d42b88f9cd554dfd9198
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Editor/Weaver/Processors/SyncVarAttributeProcessor.cs
uploadId: 736421

View File

@ -0,0 +1,159 @@
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)
{
if (md.Parameters.Count > 0)
{
// we need to allow both NetworkConnection, and inheriting types.
// NetworkBehaviour.SendTargetRpc takes a NetworkConnection parameter.
// fixes https://github.com/vis2k/Mirror/issues/3290
TypeReference type = md.Parameters[0].ParameterType;
return type.Is<NetworkConnection>() ||
type.IsDerivedFrom<NetworkConnection>();
}
return false;
}
public static MethodDefinition ProcessTargetRpcInvoke(WeaverTypes weaverTypes, Readers readers, Logger Log, TypeDefinition td, MethodDefinition md, MethodDefinition rpcCallFunc, ref bool WeavingFailed)
{
string trgName = Weaver.GenerateMethodName(RemoteCalls.RemoteProcedureCalls.InvokeRpcPrefix, md);
MethodDefinition rpc = new MethodDefinition(trgName, 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))
{
// TargetRpcs are sent from server to client.
// on server, we currently support two types:
// TargetRpc(NetworkConnection)
// TargetRpc(NetworkConnectionToClient)
// however, it's always a connection to client.
// in the future, only NetworkConnectionToClient will be supported.
// explicit typing helps catch issues at compile time.
//
// on client, InvokeTargetRpc calls the original code.
// we need to fill in the NetworkConnection parameter.
// NetworkClient.connection is always a connection to server.
//
// we used to pass NetworkClient.connection as the TargetRpc parameter.
// which caused: https://github.com/MirrorNetworking/Mirror/issues/3455
// when the parameter is defined as a NetworkConnectionToClient.
//
// a client's connection never fits into a NetworkConnectionToClient.
// we need to always pass null here.
worker.Emit(OpCodes.Ldnull);
}
// 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.WriteGetWriter(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;
// 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);
}
// pass full function name to avoid ClassA.Func <-> ClassB.Func collisions
worker.Emit(OpCodes.Ldstr, md.FullName);
// pass the function hash so we don't have to compute it at runtime
// otherwise each GetStableHash call requires O(N) complexity.
// noticeable for long function names:
// https://github.com/MirrorNetworking/Mirror/issues/3375
worker.Emit(OpCodes.Ldc_I4, md.FullName.GetStableHashCode());
// writer
worker.Emit(OpCodes.Ldloc_0);
worker.Emit(OpCodes.Ldc_I4, targetRpcAttr.GetField("channel", 0));
worker.Emit(OpCodes.Callvirt, weaverTypes.sendTargetRpcInternal);
NetworkBehaviourProcessor.WriteReturnWriter(worker, weaverTypes);
worker.Emit(OpCodes.Ret);
return rpc;
}
}
}

View File

@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: fb3ce6c6f3f2942ae88178b86f5a8282
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Editor/Weaver/Processors/TargetRpcProcessor.cs
uploadId: 736421