Commit f6ed3c6f authored by cann-alberto's avatar cann-alberto
Browse files

first project commit

parent 4487b14f
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);
}
}
}
fileFormatVersion: 2
guid: f52c39bddd95d42b88f9cd554dfd9198
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
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;
}
}
}
fileFormatVersion: 2
guid: fb3ce6c6f3f2942ae88178b86f5a8282
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
using System;
using System.Collections.Generic;
using Mono.CecilX;
using Mono.CecilX.Cil;
// to use Mono.CecilX.Rocks here, we need to 'override references' in the
// Unity.Mirror.CodeGen assembly definition file in the Editor, and add CecilX.Rocks.
// otherwise we get an unknown import exception.
using Mono.CecilX.Rocks;
namespace Mirror.Weaver
{
// not static, because ILPostProcessor is multithreaded
public class Readers
{
// Readers are only for this assembly.
// can't be used from another assembly, otherwise we will get:
// "System.ArgumentException: Member ... is declared in another module and needs to be imported"
AssemblyDefinition assembly;
WeaverTypes weaverTypes;
TypeDefinition GeneratedCodeClass;
Logger Log;
Dictionary<TypeReference, MethodReference> readFuncs =
new Dictionary<TypeReference, MethodReference>(new TypeReferenceComparer());
public Readers(AssemblyDefinition assembly, WeaverTypes weaverTypes, TypeDefinition GeneratedCodeClass, Logger Log)
{
this.assembly = assembly;
this.weaverTypes = weaverTypes;
this.GeneratedCodeClass = GeneratedCodeClass;
this.Log = Log;
}
internal void Register(TypeReference dataType, MethodReference methodReference)
{
if (readFuncs.ContainsKey(dataType))
{
// TODO enable this again later.
// Reader has some obsolete functions that were renamed.
// Don't want weaver warnings for all of them.
//Log.Warning($"Registering a Read method for {dataType.FullName} when one already exists", methodReference);
}
// we need to import type when we Initialize Readers so import here in case it is used anywhere else
TypeReference imported = assembly.MainModule.ImportReference(dataType);
readFuncs[imported] = methodReference;
}
void RegisterReadFunc(TypeReference typeReference, MethodDefinition newReaderFunc)
{
Register(typeReference, newReaderFunc);
GeneratedCodeClass.Methods.Add(newReaderFunc);
}
// Finds existing reader for type, if non exists trys to create one
public MethodReference GetReadFunc(TypeReference variable, ref bool WeavingFailed)
{
if (readFuncs.TryGetValue(variable, out MethodReference foundFunc))
return foundFunc;
TypeReference importedVariable = assembly.MainModule.ImportReference(variable);
return GenerateReader(importedVariable, ref WeavingFailed);
}
MethodReference GenerateReader(TypeReference variableReference, ref bool WeavingFailed)
{
// Arrays are special, if we resolve them, we get the element type,
// so the following ifs might choke on it for scriptable objects
// or other objects that require a custom serializer
// thus check if it is an array and skip all the checks.
if (variableReference.IsArray)
{
if (variableReference.IsMultidimensionalArray())
{
Log.Error($"{variableReference.Name} is an unsupported type. Multidimensional arrays are not supported", variableReference);
WeavingFailed = true;
return null;
}
return GenerateReadCollection(variableReference, variableReference.GetElementType(), nameof(NetworkReaderExtensions.ReadArray), ref WeavingFailed);
}
TypeDefinition variableDefinition = variableReference.Resolve();
// check if the type is completely invalid
if (variableDefinition == null)
{
Log.Error($"{variableReference.Name} is not a supported type", variableReference);
WeavingFailed = true;
return null;
}
else if (variableReference.IsByReference)
{
// error??
Log.Error($"Cannot pass type {variableReference.Name} by reference", variableReference);
WeavingFailed = true;
return null;
}
// use existing func for known types
if (variableDefinition.IsEnum)
{
return GenerateEnumReadFunc(variableReference, ref WeavingFailed);
}
else if (variableDefinition.Is(typeof(ArraySegment<>)))
{
return GenerateArraySegmentReadFunc(variableReference, ref WeavingFailed);
}
else if (variableDefinition.Is(typeof(List<>)))
{
GenericInstanceType genericInstance = (GenericInstanceType)variableReference;
TypeReference elementType = genericInstance.GenericArguments[0];
return GenerateReadCollection(variableReference, elementType, nameof(NetworkReaderExtensions.ReadList), ref WeavingFailed);
}
// handle both NetworkBehaviour and inheritors.
// fixes: https://github.com/MirrorNetworking/Mirror/issues/2939
else if (variableReference.IsDerivedFrom<NetworkBehaviour>() || variableReference.Is<NetworkBehaviour>())
{
return GetNetworkBehaviourReader(variableReference);
}
// check if reader generation is applicable on this type
if (variableDefinition.IsDerivedFrom<UnityEngine.Component>())
{
Log.Error($"Cannot generate reader for component type {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
WeavingFailed = true;
return null;
}
if (variableReference.Is<UnityEngine.Object>())
{
Log.Error($"Cannot generate reader for {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
WeavingFailed = true;
return null;
}
if (variableReference.Is<UnityEngine.ScriptableObject>())
{
Log.Error($"Cannot generate reader for {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
WeavingFailed = true;
return null;
}
if (variableDefinition.HasGenericParameters)
{
Log.Error($"Cannot generate reader for generic variable {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
WeavingFailed = true;
return null;
}
if (variableDefinition.IsInterface)
{
Log.Error($"Cannot generate reader for interface {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
WeavingFailed = true;
return null;
}
if (variableDefinition.IsAbstract)
{
Log.Error($"Cannot generate reader for abstract class {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
WeavingFailed = true;
return null;
}
return GenerateClassOrStructReadFunction(variableReference, ref WeavingFailed);
}
MethodReference GetNetworkBehaviourReader(TypeReference variableReference)
{
// uses generic ReadNetworkBehaviour rather than having weaver create one for each NB
MethodReference generic = weaverTypes.readNetworkBehaviourGeneric;
MethodReference readFunc = generic.MakeGeneric(assembly.MainModule, variableReference);
// register function so it is added to Reader<T>
// use Register instead of RegisterWriteFunc because this is not a generated function
Register(variableReference, readFunc);
return readFunc;
}
MethodDefinition GenerateEnumReadFunc(TypeReference variable, ref bool WeavingFailed)
{
MethodDefinition readerFunc = GenerateReaderFunction(variable);
ILProcessor worker = readerFunc.Body.GetILProcessor();
worker.Emit(OpCodes.Ldarg_0);
TypeReference underlyingType = variable.Resolve().GetEnumUnderlyingType();
MethodReference underlyingFunc = GetReadFunc(underlyingType, ref WeavingFailed);
worker.Emit(OpCodes.Call, underlyingFunc);
worker.Emit(OpCodes.Ret);
return readerFunc;
}
MethodDefinition GenerateArraySegmentReadFunc(TypeReference variable, ref bool WeavingFailed)
{
GenericInstanceType genericInstance = (GenericInstanceType)variable;
TypeReference elementType = genericInstance.GenericArguments[0];
MethodDefinition readerFunc = GenerateReaderFunction(variable);
ILProcessor worker = readerFunc.Body.GetILProcessor();
// $array = reader.Read<[T]>()
ArrayType arrayType = elementType.MakeArrayType();
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Call, GetReadFunc(arrayType, ref WeavingFailed));
// return new ArraySegment<T>($array);
worker.Emit(OpCodes.Newobj, weaverTypes.ArraySegmentConstructorReference.MakeHostInstanceGeneric(assembly.MainModule, genericInstance));
worker.Emit(OpCodes.Ret);
return readerFunc;
}
MethodDefinition GenerateReaderFunction(TypeReference variable)
{
string functionName = $"_Read_{variable.FullName}";
// create new reader for this type
MethodDefinition readerFunc = new MethodDefinition(functionName,
MethodAttributes.Public |
MethodAttributes.Static |
MethodAttributes.HideBySig,
variable);
readerFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, weaverTypes.Import<NetworkReader>()));
readerFunc.Body.InitLocals = true;
RegisterReadFunc(variable, readerFunc);
return readerFunc;
}
MethodDefinition GenerateReadCollection(TypeReference variable, TypeReference elementType, string readerFunction, ref bool WeavingFailed)
{
MethodDefinition readerFunc = GenerateReaderFunction(variable);
// generate readers for the element
GetReadFunc(elementType, ref WeavingFailed);
ModuleDefinition module = assembly.MainModule;
TypeReference readerExtensions = module.ImportReference(typeof(NetworkReaderExtensions));
MethodReference listReader = Resolvers.ResolveMethod(readerExtensions, assembly, Log, readerFunction, ref WeavingFailed);
GenericInstanceMethod methodRef = new GenericInstanceMethod(listReader);
methodRef.GenericArguments.Add(elementType);
// generates
// return reader.ReadList<T>();
ILProcessor worker = readerFunc.Body.GetILProcessor();
worker.Emit(OpCodes.Ldarg_0); // reader
worker.Emit(OpCodes.Call, methodRef); // Read
worker.Emit(OpCodes.Ret);
return readerFunc;
}
MethodDefinition GenerateClassOrStructReadFunction(TypeReference variable, ref bool WeavingFailed)
{
MethodDefinition readerFunc = GenerateReaderFunction(variable);
// create local for return value
readerFunc.Body.Variables.Add(new VariableDefinition(variable));
ILProcessor worker = readerFunc.Body.GetILProcessor();
TypeDefinition td = variable.Resolve();
if (!td.IsValueType)
GenerateNullCheck(worker, ref WeavingFailed);
CreateNew(variable, worker, td, ref WeavingFailed);
ReadAllFields(variable, worker, ref WeavingFailed);
worker.Emit(OpCodes.Ldloc_0);
worker.Emit(OpCodes.Ret);
return readerFunc;
}
void GenerateNullCheck(ILProcessor worker, ref bool WeavingFailed)
{
// if (!reader.ReadBoolean()) {
// return null;
// }
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Call, GetReadFunc(weaverTypes.Import<bool>(), ref WeavingFailed));
Instruction labelEmptyArray = worker.Create(OpCodes.Nop);
worker.Emit(OpCodes.Brtrue, labelEmptyArray);
// return null
worker.Emit(OpCodes.Ldnull);
worker.Emit(OpCodes.Ret);
worker.Append(labelEmptyArray);
}
// Initialize the local variable with a new instance
void CreateNew(TypeReference variable, ILProcessor worker, TypeDefinition td, ref bool WeavingFailed)
{
if (variable.IsValueType)
{
// structs are created with Initobj
worker.Emit(OpCodes.Ldloca, 0);
worker.Emit(OpCodes.Initobj, variable);
}
else if (td.IsDerivedFrom<UnityEngine.ScriptableObject>())
{
GenericInstanceMethod genericInstanceMethod = new GenericInstanceMethod(weaverTypes.ScriptableObjectCreateInstanceMethod);
genericInstanceMethod.GenericArguments.Add(variable);
worker.Emit(OpCodes.Call, genericInstanceMethod);
worker.Emit(OpCodes.Stloc_0);
}
else
{
// classes are created with their constructor
MethodDefinition ctor = Resolvers.ResolveDefaultPublicCtor(variable);
if (ctor == null)
{
Log.Error($"{variable.Name} can't be deserialized because it has no default constructor. Don't use {variable.Name} in [SyncVar]s, Rpcs, Cmds, etc.", variable);
WeavingFailed = true;
return;
}
MethodReference ctorRef = assembly.MainModule.ImportReference(ctor);
worker.Emit(OpCodes.Newobj, ctorRef);
worker.Emit(OpCodes.Stloc_0);
}
}
void ReadAllFields(TypeReference variable, ILProcessor worker, ref bool WeavingFailed)
{
foreach (FieldDefinition field in variable.FindAllPublicFields())
{
// mismatched ldloca/ldloc for struct/class combinations is invalid IL, which causes crash at runtime
OpCode opcode = variable.IsValueType ? OpCodes.Ldloca : OpCodes.Ldloc;
worker.Emit(opcode, 0);
MethodReference readFunc = GetReadFunc(field.FieldType, ref WeavingFailed);
if (readFunc != null)
{
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Call, readFunc);
}
else
{
Log.Error($"{field.Name} has an unsupported type", field);
WeavingFailed = true;
}
FieldReference fieldRef = assembly.MainModule.ImportReference(field);
worker.Emit(OpCodes.Stfld, fieldRef);
}
}
// Save a delegate for each one of the readers into Reader<T>.read
internal void InitializeReaders(ILProcessor worker)
{
ModuleDefinition module = assembly.MainModule;
TypeReference genericReaderClassRef = module.ImportReference(typeof(Reader<>));
System.Reflection.FieldInfo fieldInfo = typeof(Reader<>).GetField(nameof(Reader<object>.read));
FieldReference fieldRef = module.ImportReference(fieldInfo);
TypeReference networkReaderRef = module.ImportReference(typeof(NetworkReader));
TypeReference funcRef = module.ImportReference(typeof(Func<,>));
MethodReference funcConstructorRef = module.ImportReference(typeof(Func<,>).GetConstructors()[0]);
foreach (KeyValuePair<TypeReference, MethodReference> kvp in readFuncs)
{
TypeReference targetType = kvp.Key;
MethodReference readFunc = kvp.Value;
// create a Func<NetworkReader, T> delegate
worker.Emit(OpCodes.Ldnull);
worker.Emit(OpCodes.Ldftn, readFunc);
GenericInstanceType funcGenericInstance = funcRef.MakeGenericInstanceType(networkReaderRef, targetType);
MethodReference funcConstructorInstance = funcConstructorRef.MakeHostInstanceGeneric(assembly.MainModule, funcGenericInstance);
worker.Emit(OpCodes.Newobj, funcConstructorInstance);
// save it in Reader<T>.read
GenericInstanceType genericInstance = genericReaderClassRef.MakeGenericInstanceType(targetType);
FieldReference specializedField = fieldRef.SpecializeField(assembly.MainModule, genericInstance);
worker.Emit(OpCodes.Stsfld, specializedField);
}
}
}
}
fileFormatVersion: 2
guid: be40277098a024539bf63d0205cae824
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
// all the resolve functions for the weaver
// NOTE: these functions should be made extensions, but right now they still
// make heavy use of Weaver.fail and we'd have to check each one's return
// value for null otherwise.
// (original FieldType.Resolve returns null if not found too, so
// exceptions would be a bit inconsistent here)
using Mono.CecilX;
namespace Mirror.Weaver
{
public static class Resolvers
{
public static MethodReference ResolveMethod(TypeReference tr, AssemblyDefinition assembly, Logger Log, string name, ref bool WeavingFailed)
{
if (tr == null)
{
Log.Error($"Cannot resolve method {name} without a class");
WeavingFailed = true;
return null;
}
MethodReference method = ResolveMethod(tr, assembly, Log, m => m.Name == name, ref WeavingFailed);
if (method == null)
{
Log.Error($"Method not found with name {name} in type {tr.Name}", tr);
WeavingFailed = true;
}
return method;
}
public static MethodReference ResolveMethod(TypeReference t, AssemblyDefinition assembly, Logger Log, System.Func<MethodDefinition, bool> predicate, ref bool WeavingFailed)
{
foreach (MethodDefinition methodRef in t.Resolve().Methods)
{
if (predicate(methodRef))
{
return assembly.MainModule.ImportReference(methodRef);
}
}
Log.Error($"Method not found in type {t.Name}", t);
WeavingFailed = true;
return null;
}
public static FieldReference ResolveField(TypeReference tr, AssemblyDefinition assembly, Logger Log, string name, ref bool WeavingFailed)
{
if (tr == null)
{
Log.Error($"Cannot resolve Field {name} without a class");
WeavingFailed = true;
return null;
}
FieldReference field = ResolveField(tr, assembly, Log, m => m.Name == name, ref WeavingFailed);
if (field == null)
{
Log.Error($"Field not found with name {name} in type {tr.Name}", tr);
WeavingFailed = true;
}
return field;
}
public static FieldReference ResolveField(TypeReference t, AssemblyDefinition assembly, Logger Log, System.Func<FieldDefinition, bool> predicate, ref bool WeavingFailed)
{
foreach (FieldDefinition fieldRef in t.Resolve().Fields)
{
if (predicate(fieldRef))
{
return assembly.MainModule.ImportReference(fieldRef);
}
}
Log.Error($"Field not found in type {t.Name}", t);
WeavingFailed = true;
return null;
}
public static MethodReference TryResolveMethodInParents(TypeReference tr, AssemblyDefinition assembly, string name)
{
if (tr == null)
{
return null;
}
foreach (MethodDefinition methodDef in tr.Resolve().Methods)
{
if (methodDef.Name == name)
{
MethodReference methodRef = methodDef;
if (tr.IsGenericInstance)
{
methodRef = methodRef.MakeHostInstanceGeneric(tr.Module, (GenericInstanceType)tr);
}
return assembly.MainModule.ImportReference(methodRef);
}
}
// Could not find the method in this class, try the parent
return TryResolveMethodInParents(tr.Resolve().BaseType.ApplyGenericParameters(tr), assembly, name);
}
public static MethodDefinition ResolveDefaultPublicCtor(TypeReference variable)
{
foreach (MethodDefinition methodRef in variable.Resolve().Methods)
{
if (methodRef.Name == ".ctor" &&
methodRef.Resolve().IsPublic &&
methodRef.Parameters.Count == 0)
{
return methodRef;
}
}
return null;
}
public static MethodReference ResolveProperty(TypeReference tr, AssemblyDefinition assembly, string name)
{
foreach (PropertyDefinition pd in tr.Resolve().Properties)
{
if (pd.Name == name)
{
return assembly.MainModule.ImportReference(pd.GetMethod);
}
}
return null;
}
}
}
fileFormatVersion: 2
guid: 3039a59c76aec43c797ad66930430367
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
// tracks SyncVar read/write access when processing NetworkBehaviour,
// to later be replaced by SyncVarAccessReplacer.
using System.Collections.Generic;
using Mono.CecilX;
namespace Mirror.Weaver
{
// This data is flushed each time - if we are run multiple times in the same process/domain
public class SyncVarAccessLists
{
// setter functions that replace [SyncVar] member variable references. dict<field, replacement>
public Dictionary<FieldDefinition, MethodDefinition> replacementSetterProperties =
new Dictionary<FieldDefinition, MethodDefinition>();
// getter functions that replace [SyncVar] member variable references. dict<field, replacement>
public Dictionary<FieldDefinition, MethodDefinition> replacementGetterProperties =
new Dictionary<FieldDefinition, MethodDefinition>();
// amount of SyncVars per class. dict<className, amount>
// necessary for SyncVar dirty bits, where inheriting classes start
// their dirty bits at base class SyncVar amount.
public Dictionary<string, int> numSyncVars = new Dictionary<string, int>();
public int GetSyncVarStart(string className) =>
numSyncVars.TryGetValue(className, out int value) ? value : 0;
public void SetNumSyncVars(string className, int num)
{
numSyncVars[className] = num;
}
}
}
fileFormatVersion: 2
guid: 6905230c3c4c4e158760065a93380e83
timeCreated: 1629348618
\ No newline at end of file
using System.Collections.Generic;
using Mono.CecilX;
namespace Mirror.Weaver
{
// Compares TypeReference using FullName
public class TypeReferenceComparer : IEqualityComparer<TypeReference>
{
public bool Equals(TypeReference x, TypeReference y) =>
x.FullName == y.FullName;
public int GetHashCode(TypeReference obj) =>
obj.FullName.GetHashCode();
}
}
fileFormatVersion: 2
guid: 55eb9eb8794946f4da7ad39788c9920b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
{
"name": "Unity.Mirror.CodeGen",
"rootNamespace": "",
"references": [
"GUID:30817c1a0e6d646d99c048fc403f5979"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": true,
"overrideReferences": true,
"precompiledReferences": [
"Mono.CecilX.dll",
"Mono.CecilX.Rocks.dll"
],
"autoReferenced": false,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}
\ No newline at end of file
fileFormatVersion: 2
guid: 1d0b9d21c3ff546a4aa32399dfd33474
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Mono.CecilX;
using Mono.CecilX.Cil;
using Mono.CecilX.Rocks;
namespace Mirror.Weaver
{
// not static, because ILPostProcessor is multithreaded
internal class Weaver
{
// generated code class
public const string GeneratedCodeNamespace = "Mirror";
public const string GeneratedCodeClassName = "GeneratedNetworkCode";
TypeDefinition GeneratedCodeClass;
// for resolving Mirror.dll in ReaderWriterProcessor, we need to know
// Mirror.dll name
public const string MirrorAssemblyName = "Mirror";
WeaverTypes weaverTypes;
SyncVarAccessLists syncVarAccessLists;
AssemblyDefinition CurrentAssembly;
Writers writers;
Readers readers;
// in case of weaver errors, we don't stop immediately.
// we log all errors and then eventually return false if
// weaving has failed.
// this way the user can fix multiple errors at once, instead of having
// to fix -> recompile -> fix -> recompile for one error at a time.
bool WeavingFailed;
// logger functions can be set from the outside.
// for example, Debug.Log or ILPostProcessor Diagnostics log for
// multi threaded logging.
public Logger Log;
// remote actions now support overloads,
// -> but IL2CPP doesnt like it when two generated methods
// -> have the same signature,
// -> so, append the signature to the generated method name,
// -> to create a unique name
// Example:
// RpcTeleport(Vector3 position) -> InvokeUserCode_RpcTeleport__Vector3()
// RpcTeleport(Vector3 position, Quaternion rotation) -> InvokeUserCode_RpcTeleport__Vector3Quaternion()
// fixes https://github.com/vis2k/Mirror/issues/3060
public static string GenerateMethodName(string initialPrefix, MethodDefinition md)
{
initialPrefix += md.Name;
for (int i = 0; i < md.Parameters.Count; ++i)
{
// with __ so it's more obvious that this is the parameter suffix.
// otherwise RpcTest(int) => RpcTestInt(int) which is not obvious.
initialPrefix += $"__{md.Parameters[i].ParameterType.Name}";
}
return initialPrefix;
}
public Weaver(Logger Log)
{
this.Log = Log;
}
// returns 'true' if modified (=if we did anything)
bool WeaveNetworkBehavior(TypeDefinition td)
{
if (!td.IsClass)
return false;
if (!td.IsDerivedFrom<NetworkBehaviour>())
{
if (td.IsDerivedFrom<UnityEngine.MonoBehaviour>())
MonoBehaviourProcessor.Process(Log, td, ref WeavingFailed);
return false;
}
// process this and base classes from parent to child order
List<TypeDefinition> behaviourClasses = new List<TypeDefinition>();
TypeDefinition parent = td;
while (parent != null)
{
if (parent.Is<NetworkBehaviour>())
{
break;
}
try
{
behaviourClasses.Insert(0, parent);
parent = parent.BaseType.Resolve();
}
catch (AssemblyResolutionException)
{
// this can happen for plugins.
//Console.WriteLine("AssemblyResolutionException: "+ ex.ToString());
break;
}
}
bool modified = false;
foreach (TypeDefinition behaviour in behaviourClasses)
{
modified |= new NetworkBehaviourProcessor(CurrentAssembly, weaverTypes, syncVarAccessLists, writers, readers, Log, behaviour).Process(ref WeavingFailed);
}
return modified;
}
bool WeaveModule(ModuleDefinition moduleDefinition)
{
bool modified = false;
Stopwatch watch = Stopwatch.StartNew();
watch.Start();
// ModuleDefinition.Types only finds top level types.
// GetAllTypes recursively finds all nested types as well.
// fixes nested types not being weaved, for example:
// class Parent { // ModuleDefinition.Types finds this
// class Child { // .Types.NestedTypes finds this
// class GrandChild {} // only GetAllTypes finds this too
// }
// }
// note this is not about inheritance, only about type definitions.
// see test: NetworkBehaviourTests.DeeplyNested()
foreach (TypeDefinition td in moduleDefinition.GetAllTypes())
{
if (td.IsClass && td.BaseType.CanBeResolved())
{
modified |= WeaveNetworkBehavior(td);
modified |= ServerClientAttributeProcessor.Process(weaverTypes, Log, td, ref WeavingFailed);
}
}
watch.Stop();
Console.WriteLine($"Weave behaviours and messages took {watch.ElapsedMilliseconds} milliseconds");
return modified;
}
void CreateGeneratedCodeClass()
{
// create "Mirror.GeneratedNetworkCode" class which holds all
// Readers<T> and Writers<T>
GeneratedCodeClass = new TypeDefinition(GeneratedCodeNamespace, GeneratedCodeClassName,
TypeAttributes.BeforeFieldInit | TypeAttributes.Class | TypeAttributes.AnsiClass | TypeAttributes.Public | TypeAttributes.AutoClass | TypeAttributes.Abstract | TypeAttributes.Sealed,
weaverTypes.Import<object>());
}
void ToggleWeaverFuse()
{
// // find Weaved() function
MethodDefinition func = weaverTypes.weaverFuseMethod.Resolve();
// // change return 0 to return 1
ILProcessor worker = func.Body.GetILProcessor();
func.Body.Instructions[0] = worker.Create(OpCodes.Ldc_I4_1);
}
// Weave takes an AssemblyDefinition to be compatible with both old and
// new weavers:
// * old takes a filepath, new takes a in-memory byte[]
// * old uses DefaultAssemblyResolver with added dependencies paths,
// new uses ...?
//
// => assembly: the one we are currently weaving (MyGame.dll)
// => resolver: useful in case we need to resolve any of the assembly's
// assembly.MainModule.AssemblyReferences.
// -> we can resolve ANY of them given that the resolver
// works properly (need custom one for ILPostProcessor)
// -> IMPORTANT: .Resolve() takes an AssemblyNameReference.
// those from assembly.MainModule.AssemblyReferences are
// guaranteed to be resolve-able.
// Parsing from a string for Library/.../Mirror.dll
// would not be guaranteed to be resolve-able because
// for ILPostProcessor we can't assume where Mirror.dll
// is etc.
public bool Weave(AssemblyDefinition assembly, IAssemblyResolver resolver, out bool modified)
{
WeavingFailed = false;
modified = false;
try
{
CurrentAssembly = assembly;
// fix "No writer found for ..." error
// https://github.com/vis2k/Mirror/issues/2579
// -> when restarting Unity, weaver would try to weave a DLL
// again
// -> resulting in two GeneratedNetworkCode classes (see ILSpy)
// -> the second one wouldn't have all the writer types setup
if (CurrentAssembly.MainModule.ContainsClass(GeneratedCodeNamespace, GeneratedCodeClassName))
{
//Log.Warning($"Weaver: skipping {CurrentAssembly.Name} because already weaved");
return true;
}
weaverTypes = new WeaverTypes(CurrentAssembly, Log, ref WeavingFailed);
// weaverTypes are needed for CreateGeneratedCodeClass
CreateGeneratedCodeClass();
// WeaverList depends on WeaverTypes setup because it uses Import
syncVarAccessLists = new SyncVarAccessLists();
// initialize readers & writers with this assembly.
// we need to do this in every Process() call.
// otherwise we would get
// "System.ArgumentException: Member ... is declared in another module and needs to be imported"
// errors when still using the previous module's reader/writer funcs.
writers = new Writers(CurrentAssembly, weaverTypes, GeneratedCodeClass, Log);
readers = new Readers(CurrentAssembly, weaverTypes, GeneratedCodeClass, Log);
Stopwatch rwstopwatch = Stopwatch.StartNew();
// Need to track modified from ReaderWriterProcessor too because it could find custom read/write functions or create functions for NetworkMessages
modified = ReaderWriterProcessor.Process(CurrentAssembly, resolver, Log, writers, readers, ref WeavingFailed);
rwstopwatch.Stop();
Console.WriteLine($"Find all reader and writers took {rwstopwatch.ElapsedMilliseconds} milliseconds");
ModuleDefinition moduleDefinition = CurrentAssembly.MainModule;
Console.WriteLine($"Script Module: {moduleDefinition.Name}");
modified |= WeaveModule(moduleDefinition);
if (WeavingFailed)
{
return false;
}
if (modified)
{
SyncVarAttributeAccessReplacer.Process(Log, moduleDefinition, syncVarAccessLists);
// add class that holds read/write functions
moduleDefinition.Types.Add(GeneratedCodeClass);
ReaderWriterProcessor.InitializeReaderAndWriters(CurrentAssembly, weaverTypes, writers, readers, GeneratedCodeClass);
// DO NOT WRITE here.
// CompilationFinishedHook writes to the file.
// ILPostProcessor writes to in-memory assembly.
// it depends on the caller.
//CurrentAssembly.Write(new WriterParameters{ WriteSymbols = true });
}
// if weaving succeeded, switch on the Weaver Fuse in Mirror.dll
if (CurrentAssembly.Name.Name == MirrorAssemblyName)
{
ToggleWeaverFuse();
}
return true;
}
catch (Exception e)
{
Log.Error($"Exception :{e}");
WeavingFailed = true;
return false;
}
}
}
}
fileFormatVersion: 2
guid: de160f52931054064852f2afd7e7a86f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
using System;
using System.Runtime.Serialization;
using Mono.CecilX;
namespace Mirror.Weaver
{
[Serializable]
public abstract class WeaverException : Exception
{
public MemberReference MemberReference { get; }
protected WeaverException(string message, MemberReference member) : base(message)
{
MemberReference = member;
}
protected WeaverException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base(serializationInfo, streamingContext) {}
}
[Serializable]
public class GenerateWriterException : WeaverException
{
public GenerateWriterException(string message, MemberReference member) : base(message, member) {}
protected GenerateWriterException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base(serializationInfo, streamingContext) {}
}
}
fileFormatVersion: 2
guid: 8aaaf6193bad7424492677f8e81f1b30
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
using System;
using Mono.CecilX;
using UnityEditor;
using UnityEngine;
namespace Mirror.Weaver
{
// not static, because ILPostProcessor is multithreaded
public class WeaverTypes
{
public MethodReference ScriptableObjectCreateInstanceMethod;
public FieldReference NetworkBehaviourDirtyBitsReference;
public MethodReference GetWriterReference;
public MethodReference ReturnWriterReference;
public MethodReference NetworkClientConnectionReference;
public MethodReference RemoteCallDelegateConstructor;
public MethodReference NetworkServerGetActive;
public MethodReference NetworkClientGetActive;
// custom attribute types
public MethodReference InitSyncObjectReference;
// array segment
public MethodReference ArraySegmentConstructorReference;
// Action<T,T> for SyncVar Hooks
public MethodReference ActionT_T;
// syncvar
public MethodReference generatedSyncVarSetter;
public MethodReference generatedSyncVarSetter_GameObject;
public MethodReference generatedSyncVarSetter_NetworkIdentity;
public MethodReference generatedSyncVarSetter_NetworkBehaviour_T;
public MethodReference generatedSyncVarDeserialize;
public MethodReference generatedSyncVarDeserialize_GameObject;
public MethodReference generatedSyncVarDeserialize_NetworkIdentity;
public MethodReference generatedSyncVarDeserialize_NetworkBehaviour_T;
public MethodReference getSyncVarGameObjectReference;
public MethodReference getSyncVarNetworkIdentityReference;
public MethodReference getSyncVarNetworkBehaviourReference;
public MethodReference registerCommandReference;
public MethodReference registerRpcReference;
public MethodReference getTypeFromHandleReference;
public MethodReference logErrorReference;
public MethodReference logWarningReference;
public MethodReference sendCommandInternal;
public MethodReference sendRpcInternal;
public MethodReference sendTargetRpcInternal;
public MethodReference readNetworkBehaviourGeneric;
public TypeReference weaverFuseType;
public MethodReference weaverFuseMethod;
// attributes
public TypeDefinition initializeOnLoadMethodAttribute;
public TypeDefinition runtimeInitializeOnLoadMethodAttribute;
AssemblyDefinition assembly;
public TypeReference Import<T>() => Import(typeof(T));
public TypeReference Import(Type t) => assembly.MainModule.ImportReference(t);
// constructor resolves the types and stores them in fields
public WeaverTypes(AssemblyDefinition assembly, Logger Log, ref bool WeavingFailed)
{
// system types
this.assembly = assembly;
TypeReference ArraySegmentType = Import(typeof(ArraySegment<>));
ArraySegmentConstructorReference = Resolvers.ResolveMethod(ArraySegmentType, assembly, Log, ".ctor", ref WeavingFailed);
TypeReference ActionType = Import(typeof(Action<,>));
ActionT_T = Resolvers.ResolveMethod(ActionType, assembly, Log, ".ctor", ref WeavingFailed);
weaverFuseType = Import(typeof(WeaverFuse));
weaverFuseMethod = Resolvers.ResolveMethod(weaverFuseType, assembly, Log, "Weaved", ref WeavingFailed);
TypeReference NetworkServerType = Import(typeof(NetworkServer));
NetworkServerGetActive = Resolvers.ResolveMethod(NetworkServerType, assembly, Log, "get_active", ref WeavingFailed);
TypeReference NetworkClientType = Import(typeof(NetworkClient));
NetworkClientGetActive = Resolvers.ResolveMethod(NetworkClientType, assembly, Log, "get_active", ref WeavingFailed);
NetworkClientConnectionReference = Resolvers.ResolveMethod(NetworkClientType, assembly, Log, "get_connection", ref WeavingFailed);
TypeReference NetworkBehaviourType = Import<NetworkBehaviour>();
NetworkBehaviourDirtyBitsReference = Resolvers.ResolveField(NetworkBehaviourType, assembly, Log, "syncVarDirtyBits", ref WeavingFailed);
generatedSyncVarSetter = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GeneratedSyncVarSetter", ref WeavingFailed);
generatedSyncVarSetter_GameObject = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GeneratedSyncVarSetter_GameObject", ref WeavingFailed);
generatedSyncVarSetter_NetworkIdentity = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GeneratedSyncVarSetter_NetworkIdentity", ref WeavingFailed);
generatedSyncVarSetter_NetworkBehaviour_T = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GeneratedSyncVarSetter_NetworkBehaviour", ref WeavingFailed);
generatedSyncVarDeserialize_GameObject = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GeneratedSyncVarDeserialize_GameObject", ref WeavingFailed);
generatedSyncVarDeserialize = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GeneratedSyncVarDeserialize", ref WeavingFailed);
generatedSyncVarDeserialize_NetworkIdentity = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GeneratedSyncVarDeserialize_NetworkIdentity", ref WeavingFailed);
generatedSyncVarDeserialize_NetworkBehaviour_T = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GeneratedSyncVarDeserialize_NetworkBehaviour", ref WeavingFailed);
getSyncVarGameObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GetSyncVarGameObject", ref WeavingFailed);
getSyncVarNetworkIdentityReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GetSyncVarNetworkIdentity", ref WeavingFailed);
getSyncVarNetworkBehaviourReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GetSyncVarNetworkBehaviour", ref WeavingFailed);
sendCommandInternal = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SendCommandInternal", ref WeavingFailed);
sendRpcInternal = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SendRPCInternal", ref WeavingFailed);
sendTargetRpcInternal = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SendTargetRPCInternal", ref WeavingFailed);
InitSyncObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "InitSyncObject", ref WeavingFailed);
TypeReference RemoteProcedureCallsType = Import(typeof(RemoteCalls.RemoteProcedureCalls));
registerCommandReference = Resolvers.ResolveMethod(RemoteProcedureCallsType, assembly, Log, "RegisterCommand", ref WeavingFailed);
registerRpcReference = Resolvers.ResolveMethod(RemoteProcedureCallsType, assembly, Log, "RegisterRpc", ref WeavingFailed);
TypeReference RemoteCallDelegateType = Import<RemoteCalls.RemoteCallDelegate>();
RemoteCallDelegateConstructor = Resolvers.ResolveMethod(RemoteCallDelegateType, assembly, Log, ".ctor", ref WeavingFailed);
TypeReference ScriptableObjectType = Import<ScriptableObject>();
ScriptableObjectCreateInstanceMethod = Resolvers.ResolveMethod(
ScriptableObjectType, assembly, Log,
md => md.Name == "CreateInstance" && md.HasGenericParameters,
ref WeavingFailed);
TypeReference unityDebug = Import(typeof(UnityEngine.Debug));
// these have multiple methods with same name, so need to check parameters too
logErrorReference = Resolvers.ResolveMethod(unityDebug, assembly, Log, md =>
md.Name == "LogError" &&
md.Parameters.Count == 1 &&
md.Parameters[0].ParameterType.FullName == typeof(object).FullName,
ref WeavingFailed);
logWarningReference = Resolvers.ResolveMethod(unityDebug, assembly, Log, md =>
md.Name == "LogWarning" &&
md.Parameters.Count == 1 &&
md.Parameters[0].ParameterType.FullName == typeof(object).FullName,
ref WeavingFailed);
TypeReference typeType = Import(typeof(Type));
getTypeFromHandleReference = Resolvers.ResolveMethod(typeType, assembly, Log, "GetTypeFromHandle", ref WeavingFailed);
TypeReference NetworkWriterPoolType = Import(typeof(NetworkWriterPool));
GetWriterReference = Resolvers.ResolveMethod(NetworkWriterPoolType, assembly, Log, "Get", ref WeavingFailed);
ReturnWriterReference = Resolvers.ResolveMethod(NetworkWriterPoolType, assembly, Log, "Return", ref WeavingFailed);
TypeReference readerExtensions = Import(typeof(NetworkReaderExtensions));
readNetworkBehaviourGeneric = Resolvers.ResolveMethod(readerExtensions, assembly, Log, (md =>
{
return md.Name == nameof(NetworkReaderExtensions.ReadNetworkBehaviour) &&
md.HasGenericParameters;
}),
ref WeavingFailed);
// [InitializeOnLoadMethod]
// 'UnityEditor' is not available in builds.
// we can only import this attribute if we are in an Editor assembly.
if (Helpers.IsEditorAssembly(assembly))
{
TypeReference initializeOnLoadMethodAttributeRef = Import(typeof(InitializeOnLoadMethodAttribute));
initializeOnLoadMethodAttribute = initializeOnLoadMethodAttributeRef.Resolve();
}
// [RuntimeInitializeOnLoadMethod]
TypeReference runtimeInitializeOnLoadMethodAttributeRef = Import(typeof(RuntimeInitializeOnLoadMethodAttribute));
runtimeInitializeOnLoadMethodAttribute = runtimeInitializeOnLoadMethodAttributeRef.Resolve();
}
}
}
fileFormatVersion: 2
guid: 2585961bf7fe4c10a9143f4087efdf6f
timeCreated: 1596486854
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment