Revert "cleanup mirrorweaver"

This reverts commit 7c5e57db544da18cf7b8eb4b9dca28c6316af333.
This commit is contained in:
Mister_Nebula 2022-01-31 14:31:29 +00:00
parent 1991c681eb
commit 0a5970ebdc
25 changed files with 3611 additions and 3718 deletions

View File

@ -12,10 +12,7 @@ namespace MirrorWeaver
public void Warning(string message, MemberReference mr) public void Warning(string message, MemberReference mr)
{ {
if (mr != null) if (mr != null) message = $"{message} (at {mr})";
{
message = $"{message} (at {mr})";
}
Console.WriteLine(message); Console.WriteLine(message);
} }
@ -24,10 +21,7 @@ namespace MirrorWeaver
public void Error(string message, MemberReference mr) public void Error(string message, MemberReference mr)
{ {
if (mr != null) if (mr != null) message = $"{message} (at {mr})";
{
message = $"{message} (at {mr})";
}
Console.Error.WriteLine(message); Console.Error.WriteLine(message);
} }

View File

@ -19,27 +19,17 @@ namespace MirrorWeaver
foreach (var type in assembly.MainModule.GetTypes()) foreach (var type in assembly.MainModule.GetTypes())
{ {
if (type.HasGenericParameters) if (type.HasGenericParameters) continue;
{
continue;
}
TypeReference currentType = type; TypeReference currentType = type;
while (currentType != null) while (currentType != null)
{ {
foreach (var method in currentType.Resolve().Methods) foreach (var method in currentType.Resolve().Methods)
{ {
if (!method.HasBody) if (!method.HasBody) continue;
{
continue;
}
foreach (var instruction in method.Body.Instructions) foreach (var instruction in method.Body.Instructions)
{ {
if (instruction.Operand is not GenericInstanceMethod calledMethod) if (instruction.Operand is not GenericInstanceMethod calledMethod) continue;
{
continue;
}
if (calledMethod.DeclaringType.Name == NetworkWriter_Write.DeclaringType.Name && if (calledMethod.DeclaringType.Name == NetworkWriter_Write.DeclaringType.Name &&
calledMethod.Name == NetworkWriter_Write.Name) calledMethod.Name == NetworkWriter_Write.Name)
@ -47,9 +37,7 @@ namespace MirrorWeaver
var argType = calledMethod.GenericArguments[0]; var argType = calledMethod.GenericArguments[0];
if (argType is GenericParameter genericParameter && genericParameter.Owner == currentType.Resolve()) if (argType is GenericParameter genericParameter && genericParameter.Owner == currentType.Resolve())
{
argType = ((GenericInstanceType)currentType).GenericArguments[genericParameter.Position]; argType = ((GenericInstanceType)currentType).GenericArguments[genericParameter.Position];
}
writers.GetWriteFunc(argType, ref weavingFailed); writers.GetWriteFunc(argType, ref weavingFailed);
} }
@ -59,9 +47,7 @@ namespace MirrorWeaver
var argType = calledMethod.GenericArguments[0]; var argType = calledMethod.GenericArguments[0];
if (argType is GenericParameter genericParameter && genericParameter.Owner == currentType.Resolve()) if (argType is GenericParameter genericParameter && genericParameter.Owner == currentType.Resolve())
{
argType = ((GenericInstanceType)currentType).GenericArguments[genericParameter.Position]; argType = ((GenericInstanceType)currentType).GenericArguments[genericParameter.Position];
}
readers.GetReadFunc(argType, ref weavingFailed); readers.GetReadFunc(argType, ref weavingFailed);
} }

View File

@ -1,283 +1,263 @@
using Mono.Cecil;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Mono.Cecil;
namespace Mirror.Weaver namespace Mirror.Weaver
{ {
public static class Extensions public static class Extensions
{ {
public static bool Is(this TypeReference td, Type t) public static bool Is(this TypeReference td, Type t)
{ {
if (t.IsGenericType) if (t.IsGenericType)
{ {
return td.GetElementType().FullName == t.FullName; return td.GetElementType().FullName == t.FullName;
} }
return td.FullName == t.FullName; return td.FullName == t.FullName;
} }
public static bool Is<T>(this TypeReference td) => Is(td, typeof(T)); public static bool Is<T>(this TypeReference td) => Is(td, typeof(T));
public static bool IsDerivedFrom<T>(this TypeReference tr) => IsDerivedFrom(tr, typeof(T)); public static bool IsDerivedFrom<T>(this TypeReference tr) => IsDerivedFrom(tr, typeof(T));
public static bool IsDerivedFrom(this TypeReference tr, Type baseClass) public static bool IsDerivedFrom(this TypeReference tr, Type baseClass)
{ {
if (tr == null) if (tr == null)
{ return false;
return false; TypeDefinition td = tr.Resolve();
} if (td == null)
return false;
if (!td.IsClass)
return false;
var td = tr.Resolve(); // are ANY parent classes of baseClass?
if (td == null) TypeReference parent = td.BaseType;
{
return false;
}
if (!td.IsClass) if (parent == null)
{ return false;
return false;
}
// are ANY parent classes of baseClass? if (parent.Is(baseClass))
var parent = td.BaseType; return true;
if (parent == null) if (parent.CanBeResolved())
{ return IsDerivedFrom(parent.Resolve(), baseClass);
return false;
}
if (parent.Is(baseClass)) return false;
{ }
return true;
}
if (parent.CanBeResolved()) public static TypeReference GetEnumUnderlyingType(this TypeDefinition td)
{ {
return IsDerivedFrom(parent.Resolve(), baseClass); foreach (FieldDefinition field in td.Fields)
} {
if (!field.IsStatic)
return field.FieldType;
}
throw new ArgumentException($"Invalid enum {td.FullName}");
}
return false; public static bool ImplementsInterface<TInterface>(this TypeDefinition td)
} {
TypeDefinition typedef = td;
public static TypeReference GetEnumUnderlyingType(this TypeDefinition td) while (typedef != null)
{ {
foreach (var field in td.Fields) if (typedef.Interfaces.Any(iface => iface.InterfaceType.Is<TInterface>()))
{ return true;
if (!field.IsStatic)
{
return field.FieldType;
}
}
throw new ArgumentException($"Invalid enum {td.FullName}");
}
public static bool ImplementsInterface<TInterface>(this TypeDefinition td) try
{ {
var typedef = td; TypeReference parent = typedef.BaseType;
typedef = parent?.Resolve();
}
catch (AssemblyResolutionException)
{
// this can happen for plugins.
//Console.WriteLine("AssemblyResolutionException: "+ ex.ToString());
break;
}
}
while (typedef != null) return false;
{ }
if (typedef.Interfaces.Any(iface => iface.InterfaceType.Is<TInterface>()))
{
return true;
}
try public static bool IsMultidimensionalArray(this TypeReference tr) =>
{ tr is ArrayType arrayType && arrayType.Rank > 1;
var parent = typedef.BaseType;
typedef = parent?.Resolve();
}
catch (AssemblyResolutionException)
{
// this can happen for plugins.
//Console.WriteLine("AssemblyResolutionException: "+ ex.ToString());
break;
}
}
return false; // Does type use netId as backing field
} public static bool IsNetworkIdentityField(this TypeReference tr) =>
tr.Is<UnityEngine.GameObject>() ||
tr.Is<NetworkIdentity>() ||
tr.IsDerivedFrom<NetworkBehaviour>();
public static bool IsMultidimensionalArray(this TypeReference tr) => public static bool CanBeResolved(this TypeReference parent)
tr is ArrayType arrayType && arrayType.Rank > 1; {
while (parent != null)
{
if (parent.Scope.Name == "Windows")
{
return false;
}
// Does type use netId as backing field if (parent.Scope.Name == "mscorlib")
public static bool IsNetworkIdentityField(this TypeReference tr) => {
tr.Is<UnityEngine.GameObject>() || TypeDefinition resolved = parent.Resolve();
tr.Is<NetworkIdentity>() || return resolved != null;
tr.IsDerivedFrom<NetworkBehaviour>(); }
public static bool CanBeResolved(this TypeReference parent) try
{ {
while (parent != null) parent = parent.Resolve().BaseType;
{ }
if (parent.Scope.Name == "Windows") catch
{ {
return false; return false;
} }
}
return true;
}
if (parent.Scope.Name == "mscorlib") // Makes T => Variable and imports function
{ public static MethodReference MakeGeneric(this MethodReference generic, ModuleDefinition module, TypeReference variableReference)
var resolved = parent.Resolve(); {
return resolved != null; GenericInstanceMethod instance = new GenericInstanceMethod(generic);
} instance.GenericArguments.Add(variableReference);
try MethodReference readFunc = module.ImportReference(instance);
{ return readFunc;
parent = parent.Resolve().BaseType; }
}
catch
{
return false;
}
}
return true;
}
// Makes T => Variable and imports function // Given a method of a generic class such as ArraySegment`T.get_Count,
public static MethodReference MakeGeneric(this MethodReference generic, ModuleDefinition module, TypeReference variableReference) // and a generic instance such as ArraySegment`int
{ // Creates a reference to the specialized method ArraySegment`int`.get_Count
var instance = new GenericInstanceMethod(generic); // Note that calling ArraySegment`T.get_Count directly gives an invalid IL error
instance.GenericArguments.Add(variableReference); public static MethodReference MakeHostInstanceGeneric(this MethodReference self, ModuleDefinition module, GenericInstanceType instanceType)
{
MethodReference reference = new MethodReference(self.Name, self.ReturnType, instanceType)
{
CallingConvention = self.CallingConvention,
HasThis = self.HasThis,
ExplicitThis = self.ExplicitThis
};
var readFunc = module.ImportReference(instance); foreach (ParameterDefinition parameter in self.Parameters)
return readFunc; reference.Parameters.Add(new ParameterDefinition(parameter.ParameterType));
}
// Given a method of a generic class such as ArraySegment`T.get_Count, foreach (GenericParameter generic_parameter in self.GenericParameters)
// and a generic instance such as ArraySegment`int reference.GenericParameters.Add(new GenericParameter(generic_parameter.Name, reference));
// Creates a reference to the specialized method ArraySegment`int`.get_Count
// Note that calling ArraySegment`T.get_Count directly gives an invalid IL error
public static MethodReference MakeHostInstanceGeneric(this MethodReference self, ModuleDefinition module, GenericInstanceType instanceType)
{
var reference = new MethodReference(self.Name, self.ReturnType, instanceType)
{
CallingConvention = self.CallingConvention,
HasThis = self.HasThis,
ExplicitThis = self.ExplicitThis
};
foreach (var parameter in self.Parameters) return module.ImportReference(reference);
{ }
reference.Parameters.Add(new ParameterDefinition(parameter.ParameterType));
}
foreach (var generic_parameter in self.GenericParameters) // Given a field of a generic class such as Writer<T>.write,
{ // and a generic instance such as ArraySegment`int
reference.GenericParameters.Add(new GenericParameter(generic_parameter.Name, reference)); // Creates a reference to the specialized method ArraySegment`int`.get_Count
} // Note that calling ArraySegment`T.get_Count directly gives an invalid IL error
public static FieldReference SpecializeField(this FieldReference self, ModuleDefinition module, GenericInstanceType instanceType)
{
FieldReference reference = new FieldReference(self.Name, self.FieldType, instanceType);
return module.ImportReference(reference);
}
return module.ImportReference(reference); public static CustomAttribute GetCustomAttribute<TAttribute>(this ICustomAttributeProvider method)
} {
return method.CustomAttributes.FirstOrDefault(ca => ca.AttributeType.Is<TAttribute>());
}
// Given a field of a generic class such as Writer<T>.write, public static bool HasCustomAttribute<TAttribute>(this ICustomAttributeProvider attributeProvider)
// and a generic instance such as ArraySegment`int {
// Creates a reference to the specialized method ArraySegment`int`.get_Count return attributeProvider.CustomAttributes.Any(attr => attr.AttributeType.Is<TAttribute>());
// Note that calling ArraySegment`T.get_Count directly gives an invalid IL error }
public static FieldReference SpecializeField(this FieldReference self, ModuleDefinition module, GenericInstanceType instanceType)
{
var reference = new FieldReference(self.Name, self.FieldType, instanceType);
return module.ImportReference(reference);
}
public static CustomAttribute GetCustomAttribute<TAttribute>(this ICustomAttributeProvider method) => method.CustomAttributes.FirstOrDefault(ca => ca.AttributeType.Is<TAttribute>()); public static T GetField<T>(this CustomAttribute ca, string field, T defaultValue)
{
foreach (CustomAttributeNamedArgument customField in ca.Fields)
if (customField.Name == field)
return (T)customField.Argument.Value;
return defaultValue;
}
public static bool HasCustomAttribute<TAttribute>(this ICustomAttributeProvider attributeProvider) => attributeProvider.CustomAttributes.Any(attr => attr.AttributeType.Is<TAttribute>()); public static MethodDefinition GetMethod(this TypeDefinition td, string methodName)
{
return td.Methods.FirstOrDefault(method => method.Name == methodName);
}
public static T GetField<T>(this CustomAttribute ca, string field, T defaultValue) public static List<MethodDefinition> GetMethods(this TypeDefinition td, string methodName)
{ {
foreach (var customField in ca.Fields) return td.Methods.Where(method => method.Name == methodName).ToList();
{ }
if (customField.Name == field)
{
return (T)customField.Argument.Value;
}
}
return defaultValue; public static MethodDefinition GetMethodInBaseType(this TypeDefinition td, string methodName)
} {
TypeDefinition typedef = td;
while (typedef != null)
{
foreach (MethodDefinition md in typedef.Methods)
{
if (md.Name == methodName)
return md;
}
public static MethodDefinition GetMethod(this TypeDefinition td, string methodName) => td.Methods.FirstOrDefault(method => method.Name == methodName); try
{
TypeReference parent = typedef.BaseType;
typedef = parent?.Resolve();
}
catch (AssemblyResolutionException)
{
// this can happen for plugins.
break;
}
}
public static List<MethodDefinition> GetMethods(this TypeDefinition td, string methodName) => td.Methods.Where(method => method.Name == methodName).ToList(); return null;
}
public static MethodDefinition GetMethodInBaseType(this TypeDefinition td, string methodName) // Finds public fields in type and base type
{ public static IEnumerable<FieldDefinition> FindAllPublicFields(this TypeReference variable)
var typedef = td; {
while (typedef != null) return FindAllPublicFields(variable.Resolve());
{ }
foreach (var md in typedef.Methods)
{
if (md.Name == methodName)
{
return md;
}
}
try // Finds public fields in type and base type
{ public static IEnumerable<FieldDefinition> FindAllPublicFields(this TypeDefinition typeDefinition)
var parent = typedef.BaseType; {
typedef = parent?.Resolve(); while (typeDefinition != null)
} {
catch (AssemblyResolutionException) foreach (FieldDefinition field in typeDefinition.Fields)
{ {
// this can happen for plugins. if (field.IsStatic || field.IsPrivate)
break; continue;
}
}
return null; if (field.IsNotSerialized)
} continue;
// Finds public fields in type and base type yield return field;
public static IEnumerable<FieldDefinition> FindAllPublicFields(this TypeReference variable) => FindAllPublicFields(variable.Resolve()); }
// Finds public fields in type and base type try
public static IEnumerable<FieldDefinition> FindAllPublicFields(this TypeDefinition typeDefinition) {
{ typeDefinition = typeDefinition.BaseType?.Resolve();
while (typeDefinition != null) }
{ catch (AssemblyResolutionException)
foreach (var field in typeDefinition.Fields) {
{ break;
if (field.IsStatic || field.IsPrivate) }
{ }
continue; }
}
if (field.IsNotSerialized) public static bool ContainsClass(this ModuleDefinition module, string nameSpace, string className) =>
{ module.GetTypes().Any(td => td.Namespace == nameSpace &&
continue; td.Name == className);
}
yield return field;
}
try
{
typeDefinition = typeDefinition.BaseType?.Resolve();
}
catch (AssemblyResolutionException)
{
break;
}
}
}
public static bool ContainsClass(this ModuleDefinition module, string nameSpace, string className) =>
module.GetTypes().Any(td => td.Namespace == nameSpace &&
td.Name == className);
public static AssemblyNameReference FindReference(this ModuleDefinition module, string referenceName) public static AssemblyNameReference FindReference(this ModuleDefinition module, string referenceName)
{ {
foreach (var reference in module.AssemblyReferences) foreach (AssemblyNameReference reference in module.AssemblyReferences)
{ {
if (reference.Name == referenceName) if (reference.Name == referenceName)
{ return reference;
return reference; }
} return null;
} }
return null; }
}
}
} }

View File

@ -1,24 +1,26 @@
using Mono.Cecil;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Mono.Cecil;
namespace Mirror.Weaver namespace Mirror.Weaver
{ {
internal static class Helpers static class Helpers
{ {
// This code is taken from SerializationWeaver // This code is taken from SerializationWeaver
public static string UnityEngineDllDirectoryName() public static string UnityEngineDllDirectoryName()
{ {
var directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().CodeBase); string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().CodeBase);
return directoryName?.Replace(@"file:\", ""); return directoryName?.Replace(@"file:\", "");
} }
public static bool IsEditorAssembly(AssemblyDefinition currentAssembly) => public static bool IsEditorAssembly(AssemblyDefinition currentAssembly)
// we want to add the [InitializeOnLoad] attribute if it's available {
// -> usually either 'UnityEditor' or 'UnityEditor.CoreModule' // we want to add the [InitializeOnLoad] attribute if it's available
currentAssembly.MainModule.AssemblyReferences.Any(assemblyReference => // -> usually either 'UnityEditor' or 'UnityEditor.CoreModule'
assemblyReference.Name.StartsWith(nameof(UnityEditor)) return currentAssembly.MainModule.AssemblyReferences.Any(assemblyReference =>
); assemblyReference.Name.StartsWith(nameof(UnityEditor))
} );
}
}
} }

View File

@ -2,12 +2,12 @@ using Mono.Cecil;
namespace Mirror.Weaver namespace Mirror.Weaver
{ {
// not static, because ILPostProcessor is multithreaded // not static, because ILPostProcessor is multithreaded
public interface Logger public interface Logger
{ {
void Warning(string message); void Warning(string message);
void Warning(string message, MemberReference mr); void Warning(string message, MemberReference mr);
void Error(string message); void Error(string message);
void Error(string message, MemberReference mr); void Error(string message, MemberReference mr);
} }
} }

View File

@ -3,10 +3,10 @@ using Mono.Cecil.Cil;
namespace Mirror.Weaver namespace Mirror.Weaver
{ {
// Processes [Command] methods in NetworkBehaviour // Processes [Command] methods in NetworkBehaviour
public static class CommandProcessor public static class CommandProcessor
{ {
/* /*
// generates code like: // generates code like:
public void CmdThrust(float thrusting, int spin) public void CmdThrust(float thrusting, int spin)
{ {
@ -29,45 +29,43 @@ namespace Mirror.Weaver
This way we do not need to modify the code anywhere else, and this works This way we do not need to modify the code anywhere else, and this works
correctly in dependent assemblies correctly in dependent assemblies
*/ */
public static MethodDefinition ProcessCommandCall(WeaverTypes weaverTypes, Writers writers, Logger Log, TypeDefinition td, MethodDefinition md, CustomAttribute commandAttr, ref bool WeavingFailed) public static MethodDefinition ProcessCommandCall(WeaverTypes weaverTypes, Writers writers, Logger Log, TypeDefinition td, MethodDefinition md, CustomAttribute commandAttr, ref bool WeavingFailed)
{ {
var cmd = MethodProcessor.SubstituteMethod(Log, td, md, ref WeavingFailed); MethodDefinition cmd = MethodProcessor.SubstituteMethod(Log, td, md, ref WeavingFailed);
var worker = md.Body.GetILProcessor(); ILProcessor worker = md.Body.GetILProcessor();
NetworkBehaviourProcessor.WriteSetupLocals(worker, weaverTypes); NetworkBehaviourProcessor.WriteSetupLocals(worker, weaverTypes);
// NetworkWriter writer = new NetworkWriter(); // NetworkWriter writer = new NetworkWriter();
NetworkBehaviourProcessor.WriteCreateWriter(worker, weaverTypes); NetworkBehaviourProcessor.WriteCreateWriter(worker, weaverTypes);
// write all the arguments that the user passed to the Cmd call // write all the arguments that the user passed to the Cmd call
if (!NetworkBehaviourProcessor.WriteArguments(worker, writers, Log, md, RemoteCallType.Command, ref WeavingFailed)) if (!NetworkBehaviourProcessor.WriteArguments(worker, writers, Log, md, RemoteCallType.Command, ref WeavingFailed))
{ return null;
return null;
}
var channel = commandAttr.GetField("channel", 0); int channel = commandAttr.GetField("channel", 0);
var requiresAuthority = commandAttr.GetField("requiresAuthority", true); bool requiresAuthority = commandAttr.GetField("requiresAuthority", true);
// invoke internal send and return // invoke internal send and return
// load 'base.' to call the SendCommand function with // load 'base.' to call the SendCommand function with
worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldarg_0);
// pass full function name to avoid ClassA.Func <-> ClassB.Func collisions // pass full function name to avoid ClassA.Func <-> ClassB.Func collisions
worker.Emit(OpCodes.Ldstr, md.FullName); worker.Emit(OpCodes.Ldstr, md.FullName);
// writer // writer
worker.Emit(OpCodes.Ldloc_0); worker.Emit(OpCodes.Ldloc_0);
worker.Emit(OpCodes.Ldc_I4, channel); worker.Emit(OpCodes.Ldc_I4, channel);
// requiresAuthority ? 1 : 0 // requiresAuthority ? 1 : 0
worker.Emit(requiresAuthority ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); worker.Emit(requiresAuthority ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
worker.Emit(OpCodes.Call, weaverTypes.sendCommandInternal); worker.Emit(OpCodes.Call, weaverTypes.sendCommandInternal);
NetworkBehaviourProcessor.WriteRecycleWriter(worker, weaverTypes); NetworkBehaviourProcessor.WriteRecycleWriter(worker, weaverTypes);
worker.Emit(OpCodes.Ret); worker.Emit(OpCodes.Ret);
return cmd; return cmd;
} }
/* /*
// generates code like: // generates code like:
protected static void InvokeCmdCmdThrust(NetworkBehaviour obj, NetworkReader reader, NetworkConnection senderConnection) protected static void InvokeCmdCmdThrust(NetworkBehaviour obj, NetworkReader reader, NetworkConnection senderConnection)
{ {
@ -78,49 +76,47 @@ namespace Mirror.Weaver
((ShipControl)obj).CmdThrust(reader.ReadSingle(), (int)reader.ReadPackedUInt32()); ((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) public static MethodDefinition ProcessCommandInvoke(WeaverTypes weaverTypes, Readers readers, Logger Log, TypeDefinition td, MethodDefinition method, MethodDefinition cmdCallFunc, ref bool WeavingFailed)
{ {
var cmd = new MethodDefinition(Weaver.InvokeRpcPrefix + method.Name, MethodDefinition cmd = new MethodDefinition(Weaver.InvokeRpcPrefix + method.Name,
MethodAttributes.Family | MethodAttributes.Static | MethodAttributes.HideBySig, MethodAttributes.Family | MethodAttributes.Static | MethodAttributes.HideBySig,
weaverTypes.Import(typeof(void))); weaverTypes.Import(typeof(void)));
var worker = cmd.Body.GetILProcessor(); ILProcessor worker = cmd.Body.GetILProcessor();
var label = worker.Create(OpCodes.Nop); Instruction label = worker.Create(OpCodes.Nop);
NetworkBehaviourProcessor.WriteServerActiveCheck(worker, weaverTypes, method.Name, label, "Command"); NetworkBehaviourProcessor.WriteServerActiveCheck(worker, weaverTypes, method.Name, label, "Command");
// setup for reader // setup for reader
worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Castclass, td); worker.Emit(OpCodes.Castclass, td);
if (!NetworkBehaviourProcessor.ReadArguments(method, readers, Log, worker, RemoteCallType.Command, ref WeavingFailed)) if (!NetworkBehaviourProcessor.ReadArguments(method, readers, Log, worker, RemoteCallType.Command, ref WeavingFailed))
{ return null;
return null;
}
AddSenderConnection(method, worker); AddSenderConnection(method, worker);
// invoke actual command function // invoke actual command function
worker.Emit(OpCodes.Callvirt, cmdCallFunc); worker.Emit(OpCodes.Callvirt, cmdCallFunc);
worker.Emit(OpCodes.Ret); worker.Emit(OpCodes.Ret);
NetworkBehaviourProcessor.AddInvokeParameters(weaverTypes, cmd.Parameters); NetworkBehaviourProcessor.AddInvokeParameters(weaverTypes, cmd.Parameters);
td.Methods.Add(cmd); td.Methods.Add(cmd);
return cmd; return cmd;
} }
private static void AddSenderConnection(MethodDefinition method, ILProcessor worker) static void AddSenderConnection(MethodDefinition method, ILProcessor worker)
{ {
foreach (var param in method.Parameters) foreach (ParameterDefinition param in method.Parameters)
{ {
if (NetworkBehaviourProcessor.IsSenderConnection(param, RemoteCallType.Command)) if (NetworkBehaviourProcessor.IsSenderConnection(param, RemoteCallType.Command))
{ {
// NetworkConnection is 3nd arg (arg0 is "obj" not "this" because method is static) // NetworkConnection is 3nd arg (arg0 is "obj" not "this" because method is static)
// example: static void InvokeCmdCmdSendCommand(NetworkBehaviour obj, NetworkReader reader, NetworkConnection connection) // example: static void InvokeCmdCmdSendCommand(NetworkBehaviour obj, NetworkReader reader, NetworkConnection connection)
worker.Emit(OpCodes.Ldarg_2); worker.Emit(OpCodes.Ldarg_2);
} }
} }
} }
} }
} }

View File

@ -3,138 +3,128 @@ using Mono.Cecil.Cil;
namespace Mirror.Weaver namespace Mirror.Weaver
{ {
public static class MethodProcessor public static class MethodProcessor
{ {
private const string RpcPrefix = "UserCode_"; const string RpcPrefix = "UserCode_";
// creates a method substitute // creates a method substitute
// For example, if we have this: // For example, if we have this:
// public void CmdThrust(float thrusting, int spin) // public void CmdThrust(float thrusting, int spin)
// { // {
// xxxxx // xxxxx
// } // }
// //
// it will substitute the method and move the code to a new method with a provided name // it will substitute the method and move the code to a new method with a provided name
// for example: // for example:
// //
// public void CmdTrust(float thrusting, int spin) // public void CmdTrust(float thrusting, int spin)
// { // {
// } // }
// //
// public void <newName>(float thrusting, int spin) // public void <newName>(float thrusting, int spin)
// { // {
// xxxxx // xxxxx
// } // }
// //
// Note that all the calls to the method remain untouched // Note that all the calls to the method remain untouched
// //
// the original method definition loses all code // the original method definition loses all code
// this returns the newly created method with all the user provided code // this returns the newly created method with all the user provided code
public static MethodDefinition SubstituteMethod(Logger Log, TypeDefinition td, MethodDefinition md, ref bool WeavingFailed) public static MethodDefinition SubstituteMethod(Logger Log, TypeDefinition td, MethodDefinition md, ref bool WeavingFailed)
{ {
var newName = RpcPrefix + md.Name; string newName = RpcPrefix + md.Name;
var cmd = new MethodDefinition(newName, md.Attributes, md.ReturnType) MethodDefinition cmd = new MethodDefinition(newName, md.Attributes, md.ReturnType);
{
// force the substitute method to be protected. // force the substitute method to be protected.
// -> public would show in the Inspector for UnityEvents as // -> public would show in the Inspector for UnityEvents as
// User_CmdUsePotion() etc. but the user shouldn't use those. // User_CmdUsePotion() etc. but the user shouldn't use those.
// -> private would not allow inheriting classes to call it, see // -> private would not allow inheriting classes to call it, see
// OverrideVirtualWithBaseCallsBothVirtualAndBase test. // OverrideVirtualWithBaseCallsBothVirtualAndBase test.
// -> IL has no concept of 'protected', it's called IsFamily there. // -> IL has no concept of 'protected', it's called IsFamily there.
IsPublic = false, cmd.IsPublic = false;
IsFamily = true cmd.IsFamily = true;
};
// add parameters // add parameters
foreach (var pd in md.Parameters) foreach (ParameterDefinition pd in md.Parameters)
{ {
cmd.Parameters.Add(new ParameterDefinition(pd.Name, ParameterAttributes.None, pd.ParameterType)); cmd.Parameters.Add(new ParameterDefinition(pd.Name, ParameterAttributes.None, pd.ParameterType));
} }
// swap bodies // swap bodies
(cmd.Body, md.Body) = (md.Body, cmd.Body); (cmd.Body, md.Body) = (md.Body, cmd.Body);
// Move over all the debugging information // Move over all the debugging information
foreach (var sequencePoint in md.DebugInformation.SequencePoints) foreach (SequencePoint sequencePoint in md.DebugInformation.SequencePoints)
{ cmd.DebugInformation.SequencePoints.Add(sequencePoint);
cmd.DebugInformation.SequencePoints.Add(sequencePoint); md.DebugInformation.SequencePoints.Clear();
}
md.DebugInformation.SequencePoints.Clear(); foreach (CustomDebugInformation customInfo in md.CustomDebugInformations)
cmd.CustomDebugInformations.Add(customInfo);
md.CustomDebugInformations.Clear();
foreach (var customInfo in md.CustomDebugInformations) (md.DebugInformation.Scope, cmd.DebugInformation.Scope) = (cmd.DebugInformation.Scope, md.DebugInformation.Scope);
{
cmd.CustomDebugInformations.Add(customInfo);
}
md.CustomDebugInformations.Clear(); td.Methods.Add(cmd);
(md.DebugInformation.Scope, cmd.DebugInformation.Scope) = (cmd.DebugInformation.Scope, md.DebugInformation.Scope); FixRemoteCallToBaseMethod(Log, td, cmd, ref WeavingFailed);
return cmd;
}
td.Methods.Add(cmd); // Finds and fixes call to base methods within remote calls
//For example, changes `base.CmdDoSomething` to `base.CallCmdDoSomething` within `this.CallCmdDoSomething`
public static void FixRemoteCallToBaseMethod(Logger Log, TypeDefinition type, MethodDefinition method, ref bool WeavingFailed)
{
string callName = method.Name;
FixRemoteCallToBaseMethod(Log, td, cmd, ref WeavingFailed); // Cmd/rpc start with Weaver.RpcPrefix
return cmd; // e.g. CallCmdDoSomething
} if (!callName.StartsWith(RpcPrefix))
return;
// Finds and fixes call to base methods within remote calls // e.g. CmdDoSomething
//For example, changes `base.CmdDoSomething` to `base.CallCmdDoSomething` within `this.CallCmdDoSomething` string baseRemoteCallName = method.Name.Substring(RpcPrefix.Length);
public static void FixRemoteCallToBaseMethod(Logger Log, TypeDefinition type, MethodDefinition method, ref bool WeavingFailed)
{
var callName = method.Name;
// Cmd/rpc start with Weaver.RpcPrefix foreach (Instruction instruction in method.Body.Instructions)
// e.g. CallCmdDoSomething {
if (!callName.StartsWith(RpcPrefix)) // if call to base.CmdDoSomething within this.CallCmdDoSomething
{ if (IsCallToMethod(instruction, out MethodDefinition calledMethod) &&
return; calledMethod.Name == baseRemoteCallName)
} {
TypeDefinition baseType = type.BaseType.Resolve();
MethodDefinition baseMethod = baseType.GetMethodInBaseType(callName);
// e.g. CmdDoSomething if (baseMethod == null)
var baseRemoteCallName = method.Name.Substring(RpcPrefix.Length); {
Log.Error($"Could not find base method for {callName}", method);
WeavingFailed = true;
return;
}
foreach (var instruction in method.Body.Instructions) if (!baseMethod.IsVirtual)
{ {
// if call to base.CmdDoSomething within this.CallCmdDoSomething Log.Error($"Could not find base method that was virtual {callName}", method);
if (IsCallToMethod(instruction, out var calledMethod) && WeavingFailed = true;
calledMethod.Name == baseRemoteCallName) return;
{ }
var baseType = type.BaseType.Resolve();
var baseMethod = baseType.GetMethodInBaseType(callName);
if (baseMethod == null) instruction.Operand = baseMethod;
{ }
Log.Error($"Could not find base method for {callName}", method); }
WeavingFailed = true; }
return;
}
if (!baseMethod.IsVirtual) static bool IsCallToMethod(Instruction instruction, out MethodDefinition calledMethod)
{ {
Log.Error($"Could not find base method that was virtual {callName}", method); if (instruction.OpCode == OpCodes.Call &&
WeavingFailed = true; instruction.Operand is MethodDefinition method)
return; {
} calledMethod = method;
return true;
instruction.Operand = baseMethod; }
} else
} {
} calledMethod = null;
return false;
private 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

@ -2,55 +2,55 @@ using Mono.Cecil;
namespace Mirror.Weaver namespace Mirror.Weaver
{ {
// only shows warnings in case we use SyncVars etc. for MonoBehaviour. // only shows warnings in case we use SyncVars etc. for MonoBehaviour.
internal static class MonoBehaviourProcessor static class MonoBehaviourProcessor
{ {
public static void Process(Logger Log, TypeDefinition td, ref bool WeavingFailed) public static void Process(Logger Log, TypeDefinition td, ref bool WeavingFailed)
{ {
ProcessSyncVars(Log, td, ref WeavingFailed); ProcessSyncVars(Log, td, ref WeavingFailed);
ProcessMethods(Log, td, ref WeavingFailed); ProcessMethods(Log, td, ref WeavingFailed);
} }
private static void ProcessSyncVars(Logger Log, TypeDefinition td, ref bool WeavingFailed) static void ProcessSyncVars(Logger Log, TypeDefinition td, ref bool WeavingFailed)
{ {
// find syncvars // find syncvars
foreach (var fd in td.Fields) foreach (FieldDefinition fd in td.Fields)
{ {
if (fd.HasCustomAttribute<SyncVarAttribute>()) if (fd.HasCustomAttribute<SyncVarAttribute>())
{ {
Log.Error($"SyncVar {fd.Name} must be inside a NetworkBehaviour. {td.Name} is not a NetworkBehaviour", fd); Log.Error($"SyncVar {fd.Name} must be inside a NetworkBehaviour. {td.Name} is not a NetworkBehaviour", fd);
WeavingFailed = true; WeavingFailed = true;
} }
if (SyncObjectInitializer.ImplementsSyncObject(fd.FieldType)) 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); Log.Error($"{fd.Name} is a SyncObject and must be inside a NetworkBehaviour. {td.Name} is not a NetworkBehaviour", fd);
WeavingFailed = true; WeavingFailed = true;
} }
} }
} }
private static void ProcessMethods(Logger Log, TypeDefinition td, ref bool WeavingFailed) static void ProcessMethods(Logger Log, TypeDefinition td, ref bool WeavingFailed)
{ {
// find command and RPC functions // find command and RPC functions
foreach (var md in td.Methods) foreach (MethodDefinition md in td.Methods)
{ {
if (md.HasCustomAttribute<CommandAttribute>()) if (md.HasCustomAttribute<CommandAttribute>())
{ {
Log.Error($"Command {md.Name} must be declared inside a NetworkBehaviour", md); Log.Error($"Command {md.Name} must be declared inside a NetworkBehaviour", md);
WeavingFailed = true; WeavingFailed = true;
} }
if (md.HasCustomAttribute<ClientRpcAttribute>()) if (md.HasCustomAttribute<ClientRpcAttribute>())
{ {
Log.Error($"ClientRpc {md.Name} must be declared inside a NetworkBehaviour", md); Log.Error($"ClientRpc {md.Name} must be declared inside a NetworkBehaviour", md);
WeavingFailed = true; WeavingFailed = true;
} }
if (md.HasCustomAttribute<TargetRpcAttribute>()) if (md.HasCustomAttribute<TargetRpcAttribute>())
{ {
Log.Error($"TargetRpc {md.Name} must be declared inside a NetworkBehaviour", md); Log.Error($"TargetRpc {md.Name} must be declared inside a NetworkBehaviour", md);
WeavingFailed = true; WeavingFailed = true;
} }
} }
} }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,242 +1,216 @@
// finds all readers and writers and register them // finds all readers and writers and register them
using System.Linq;
using Mono.Cecil; using Mono.Cecil;
using Mono.Cecil.Cil; using Mono.Cecil.Cil;
using Mono.Cecil.Rocks; using Mono.Cecil.Rocks;
using System.Linq;
using UnityEngine; using UnityEngine;
namespace Mirror.Weaver namespace Mirror.Weaver
{ {
public static class ReaderWriterProcessor public static class ReaderWriterProcessor
{ {
public static bool Process(AssemblyDefinition CurrentAssembly, IAssemblyResolver resolver, Logger Log, Writers writers, Readers readers, ref bool WeavingFailed) 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. // find NetworkReader/Writer extensions from Mirror.dll first.
// and NetworkMessage custom writer/reader extensions. // and NetworkMessage custom writer/reader extensions.
// NOTE: do not include this result in our 'modified' return value, // NOTE: do not include this result in our 'modified' return value,
// otherwise Unity crashes when running tests // otherwise Unity crashes when running tests
ProcessMirrorAssemblyClasses(CurrentAssembly, resolver, Log, writers, readers, ref WeavingFailed); ProcessMirrorAssemblyClasses(CurrentAssembly, resolver, Log, writers, readers, ref WeavingFailed);
// find readers/writers in the assembly we are in right now. // find readers/writers in the assembly we are in right now.
return ProcessAssemblyClasses(CurrentAssembly, CurrentAssembly, writers, readers, ref WeavingFailed); return ProcessAssemblyClasses(CurrentAssembly, CurrentAssembly, writers, readers, ref WeavingFailed);
} }
private static void ProcessMirrorAssemblyClasses(AssemblyDefinition CurrentAssembly, IAssemblyResolver resolver, Logger Log, Writers writers, Readers readers, ref bool WeavingFailed) static void ProcessMirrorAssemblyClasses(AssemblyDefinition CurrentAssembly, IAssemblyResolver resolver, Logger Log, Writers writers, Readers readers, ref bool WeavingFailed)
{ {
// find Mirror.dll in assembly's references. // find Mirror.dll in assembly's references.
// those are guaranteed to be resolvable and correct. // those are guaranteed to be resolvable and correct.
// after all, it references them :) // after all, it references them :)
var mirrorAssemblyReference = CurrentAssembly.MainModule.FindReference(Weaver.MirrorAssemblyName); AssemblyNameReference mirrorAssemblyReference = CurrentAssembly.MainModule.FindReference(Weaver.MirrorAssemblyName);
if (mirrorAssemblyReference != null) if (mirrorAssemblyReference != null)
{ {
// resolve the assembly to load the AssemblyDefinition. // resolve the assembly to load the AssemblyDefinition.
// we need to search all types in it. // we need to search all types in it.
// if we only were to resolve one known type like in WeaverTypes, // if we only were to resolve one known type like in WeaverTypes,
// then we wouldn't need it. // then we wouldn't need it.
var mirrorAssembly = resolver.Resolve(mirrorAssemblyReference); AssemblyDefinition mirrorAssembly = resolver.Resolve(mirrorAssemblyReference);
if (mirrorAssembly != null) if (mirrorAssembly != null)
{ {
ProcessAssemblyClasses(CurrentAssembly, mirrorAssembly, writers, readers, ref WeavingFailed); ProcessAssemblyClasses(CurrentAssembly, mirrorAssembly, writers, readers, ref WeavingFailed);
} }
else else Log.Error($"Failed to resolve {mirrorAssemblyReference}");
{ }
Log.Error($"Failed to resolve {mirrorAssemblyReference}"); else Log.Error("Failed to find Mirror AssemblyNameReference. Can't register Mirror.dll readers/writers.");
} }
}
else
{
Log.Error("Failed to find Mirror AssemblyNameReference. Can't register Mirror.dll readers/writers.");
}
}
private static bool ProcessAssemblyClasses(AssemblyDefinition CurrentAssembly, AssemblyDefinition assembly, Writers writers, Readers readers, ref bool WeavingFailed) static bool ProcessAssemblyClasses(AssemblyDefinition CurrentAssembly, AssemblyDefinition assembly, Writers writers, Readers readers, ref bool WeavingFailed)
{ {
var modified = false; bool modified = false;
foreach (var klass in assembly.MainModule.Types) foreach (TypeDefinition klass in assembly.MainModule.Types)
{ {
// extension methods only live in static classes // extension methods only live in static classes
// static classes are represented as sealed and abstract // static classes are represented as sealed and abstract
if (klass.IsAbstract && klass.IsSealed) if (klass.IsAbstract && klass.IsSealed)
{ {
// if assembly has any declared writers then it is "modified" // if assembly has any declared writers then it is "modified"
modified |= LoadDeclaredWriters(CurrentAssembly, klass, writers); modified |= LoadDeclaredWriters(CurrentAssembly, klass, writers);
modified |= LoadDeclaredReaders(CurrentAssembly, klass, readers); modified |= LoadDeclaredReaders(CurrentAssembly, klass, readers);
} }
} }
foreach (var klass in assembly.MainModule.Types) foreach (TypeDefinition klass in assembly.MainModule.Types)
{ {
// if assembly has any network message then it is modified // if assembly has any network message then it is modified
modified |= LoadMessageReadWriter(CurrentAssembly.MainModule, writers, readers, klass, ref WeavingFailed); modified |= LoadMessageReadWriter(CurrentAssembly.MainModule, writers, readers, klass, ref WeavingFailed);
} }
return modified; return modified;
} }
private static bool LoadMessageReadWriter(ModuleDefinition module, Writers writers, Readers readers, TypeDefinition klass, ref bool WeavingFailed) static bool LoadMessageReadWriter(ModuleDefinition module, Writers writers, Readers readers, TypeDefinition klass, ref bool WeavingFailed)
{ {
var modified = false; bool modified = false;
if (!klass.IsAbstract && !klass.IsInterface && klass.ImplementsInterface<NetworkMessage>()) if (!klass.IsAbstract && !klass.IsInterface && klass.ImplementsInterface<NetworkMessage>())
{ {
readers.GetReadFunc(module.ImportReference(klass), ref WeavingFailed); readers.GetReadFunc(module.ImportReference(klass), ref WeavingFailed);
writers.GetWriteFunc(module.ImportReference(klass), ref WeavingFailed); writers.GetWriteFunc(module.ImportReference(klass), ref WeavingFailed);
modified = true; modified = true;
} }
foreach (var td in klass.NestedTypes) foreach (TypeDefinition td in klass.NestedTypes)
{ {
modified |= LoadMessageReadWriter(module, writers, readers, td, ref WeavingFailed); modified |= LoadMessageReadWriter(module, writers, readers, td, ref WeavingFailed);
} }
return modified; return modified;
} }
private static bool LoadDeclaredWriters(AssemblyDefinition currentAssembly, TypeDefinition klass, Writers writers) static bool LoadDeclaredWriters(AssemblyDefinition currentAssembly, TypeDefinition klass, Writers writers)
{ {
// register all the writers in this class. Skip the ones with wrong signature // register all the writers in this class. Skip the ones with wrong signature
var modified = false; bool modified = false;
foreach (var method in klass.Methods) foreach (MethodDefinition method in klass.Methods)
{ {
if (method.Parameters.Count != 2) if (method.Parameters.Count != 2)
{ continue;
continue;
}
if (!method.Parameters[0].ParameterType.Is<NetworkWriter>()) if (!method.Parameters[0].ParameterType.Is<NetworkWriter>())
{ continue;
continue;
}
if (!method.ReturnType.Is(typeof(void))) if (!method.ReturnType.Is(typeof(void)))
{ continue;
continue;
}
if (!method.HasCustomAttribute<System.Runtime.CompilerServices.ExtensionAttribute>()) if (!method.HasCustomAttribute<System.Runtime.CompilerServices.ExtensionAttribute>())
{ continue;
continue;
}
if (method.HasGenericParameters) if (method.HasGenericParameters)
{ continue;
continue;
}
var dataType = method.Parameters[1].ParameterType; TypeReference dataType = method.Parameters[1].ParameterType;
writers.Register(dataType, currentAssembly.MainModule.ImportReference(method)); writers.Register(dataType, currentAssembly.MainModule.ImportReference(method));
modified = true; modified = true;
} }
return modified; return modified;
} }
private static bool LoadDeclaredReaders(AssemblyDefinition currentAssembly, TypeDefinition klass, Readers readers) static bool LoadDeclaredReaders(AssemblyDefinition currentAssembly, TypeDefinition klass, Readers readers)
{ {
// register all the reader in this class. Skip the ones with wrong signature // register all the reader in this class. Skip the ones with wrong signature
var modified = false; bool modified = false;
foreach (var method in klass.Methods) foreach (MethodDefinition method in klass.Methods)
{ {
if (method.Parameters.Count != 1) if (method.Parameters.Count != 1)
{ continue;
continue;
}
if (!method.Parameters[0].ParameterType.Is<NetworkReader>()) if (!method.Parameters[0].ParameterType.Is<NetworkReader>())
{ continue;
continue;
}
if (method.ReturnType.Is(typeof(void))) if (method.ReturnType.Is(typeof(void)))
{ continue;
continue;
}
if (!method.HasCustomAttribute<System.Runtime.CompilerServices.ExtensionAttribute>()) if (!method.HasCustomAttribute<System.Runtime.CompilerServices.ExtensionAttribute>())
{ continue;
continue;
}
if (method.HasGenericParameters) if (method.HasGenericParameters)
{ continue;
continue;
}
readers.Register(method.ReturnType, currentAssembly.MainModule.ImportReference(method)); readers.Register(method.ReturnType, currentAssembly.MainModule.ImportReference(method));
modified = true; modified = true;
} }
return modified; return modified;
} }
// helper function to add [RuntimeInitializeOnLoad] attribute to method // helper function to add [RuntimeInitializeOnLoad] attribute to method
private static void AddRuntimeInitializeOnLoadAttribute(AssemblyDefinition assembly, WeaverTypes weaverTypes, MethodDefinition method) static void AddRuntimeInitializeOnLoadAttribute(AssemblyDefinition assembly, WeaverTypes weaverTypes, MethodDefinition method)
{ {
// NOTE: previously we used reflection because according paul, // NOTE: previously we used reflection because according paul,
// 'weaving Mirror.dll caused unity to rebuild all dlls but in wrong // 'weaving Mirror.dll caused unity to rebuild all dlls but in wrong
// order, which breaks rewired' // order, which breaks rewired'
// it's not obvious why importing an attribute via reflection instead // it's not obvious why importing an attribute via reflection instead
// of cecil would break anything. let's use cecil. // of cecil would break anything. let's use cecil.
// to add a CustomAttribute, we need the attribute's constructor. // to add a CustomAttribute, we need the attribute's constructor.
// in this case, there are two: empty, and RuntimeInitializeOnLoadType. // in this case, there are two: empty, and RuntimeInitializeOnLoadType.
// we want the last one, with the type parameter. // we want the last one, with the type parameter.
var ctor = weaverTypes.runtimeInitializeOnLoadMethodAttribute.GetConstructors().Last(); MethodDefinition ctor = weaverTypes.runtimeInitializeOnLoadMethodAttribute.GetConstructors().Last();
//MethodDefinition ctor = weaverTypes.runtimeInitializeOnLoadMethodAttribute.GetConstructors().First(); //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 // 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. // we need to import it first.
var attribute = new CustomAttribute(assembly.MainModule.ImportReference(ctor)); CustomAttribute attribute = new CustomAttribute(assembly.MainModule.ImportReference(ctor));
// add the RuntimeInitializeLoadType.BeforeSceneLoad argument to ctor // add the RuntimeInitializeLoadType.BeforeSceneLoad argument to ctor
attribute.ConstructorArguments.Add(new CustomAttributeArgument(weaverTypes.Import<RuntimeInitializeLoadType>(), RuntimeInitializeLoadType.BeforeSceneLoad)); attribute.ConstructorArguments.Add(new CustomAttributeArgument(weaverTypes.Import<RuntimeInitializeLoadType>(), RuntimeInitializeLoadType.BeforeSceneLoad));
method.CustomAttributes.Add(attribute); method.CustomAttributes.Add(attribute);
} }
// helper function to add [InitializeOnLoad] attribute to method // helper function to add [InitializeOnLoad] attribute to method
// (only works in Editor assemblies. check IsEditorAssembly first.) // (only works in Editor assemblies. check IsEditorAssembly first.)
private static void AddInitializeOnLoadAttribute(AssemblyDefinition assembly, WeaverTypes weaverTypes, MethodDefinition method) static void AddInitializeOnLoadAttribute(AssemblyDefinition assembly, WeaverTypes weaverTypes, MethodDefinition method)
{ {
// NOTE: previously we used reflection because according paul, // NOTE: previously we used reflection because according paul,
// 'weaving Mirror.dll caused unity to rebuild all dlls but in wrong // 'weaving Mirror.dll caused unity to rebuild all dlls but in wrong
// order, which breaks rewired' // order, which breaks rewired'
// it's not obvious why importing an attribute via reflection instead // it's not obvious why importing an attribute via reflection instead
// of cecil would break anything. let's use cecil. // of cecil would break anything. let's use cecil.
// to add a CustomAttribute, we need the attribute's constructor. // to add a CustomAttribute, we need the attribute's constructor.
// in this case, there's only one - and it's an empty constructor. // in this case, there's only one - and it's an empty constructor.
var ctor = weaverTypes.initializeOnLoadMethodAttribute.GetConstructors().First(); 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 // 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. // we need to import it first.
var attribute = new CustomAttribute(assembly.MainModule.ImportReference(ctor)); CustomAttribute attribute = new CustomAttribute(assembly.MainModule.ImportReference(ctor));
method.CustomAttributes.Add(attribute); method.CustomAttributes.Add(attribute);
} }
// adds Mirror.GeneratedNetworkCode.InitReadWriters() method that // adds Mirror.GeneratedNetworkCode.InitReadWriters() method that
// registers all generated writers into Mirror.Writer<T> static class. // registers all generated writers into Mirror.Writer<T> static class.
// -> uses [RuntimeInitializeOnLoad] attribute so it's invoke at runtime // -> uses [RuntimeInitializeOnLoad] attribute so it's invoke at runtime
// -> uses [InitializeOnLoad] if UnityEditor is referenced so it works // -> uses [InitializeOnLoad] if UnityEditor is referenced so it works
// in Editor and in tests too // in Editor and in tests too
// //
// use ILSpy to see the result (it's in the DLL's 'Mirror' namespace) // 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) public static void InitializeReaderAndWriters(AssemblyDefinition currentAssembly, WeaverTypes weaverTypes, Writers writers, Readers readers, TypeDefinition GeneratedCodeClass)
{ {
var initReadWriters = new MethodDefinition("InitReadWriters", MethodAttributes.Public | MethodDefinition initReadWriters = new MethodDefinition("InitReadWriters", MethodAttributes.Public |
MethodAttributes.Static, MethodAttributes.Static,
weaverTypes.Import(typeof(void))); weaverTypes.Import(typeof(void)));
// add [RuntimeInitializeOnLoad] in any case // add [RuntimeInitializeOnLoad] in any case
AddRuntimeInitializeOnLoadAttribute(currentAssembly, weaverTypes, initReadWriters); AddRuntimeInitializeOnLoadAttribute(currentAssembly, weaverTypes, initReadWriters);
// add [InitializeOnLoad] if UnityEditor is referenced // add [InitializeOnLoad] if UnityEditor is referenced
if (Helpers.IsEditorAssembly(currentAssembly)) if (Helpers.IsEditorAssembly(currentAssembly))
{ {
AddInitializeOnLoadAttribute(currentAssembly, weaverTypes, initReadWriters); AddInitializeOnLoadAttribute(currentAssembly, weaverTypes, initReadWriters);
} }
// fill function body with reader/writer initializers // fill function body with reader/writer initializers
var worker = initReadWriters.Body.GetILProcessor(); ILProcessor worker = initReadWriters.Body.GetILProcessor();
// for debugging: add a log to see if initialized on load // for debugging: add a log to see if initialized on load
//worker.Emit(OpCodes.Ldstr, $"[InitReadWriters] called!"); //worker.Emit(OpCodes.Ldstr, $"[InitReadWriters] called!");
//worker.Emit(OpCodes.Call, Weaver.weaverTypes.logWarningReference); //worker.Emit(OpCodes.Call, Weaver.weaverTypes.logWarningReference);
writers.InitializeWriters(worker); writers.InitializeWriters(worker);
readers.InitializeReaders(worker); readers.InitializeReaders(worker);
worker.Emit(OpCodes.Ret); worker.Emit(OpCodes.Ret);
GeneratedCodeClass.Methods.Add(initReadWriters); GeneratedCodeClass.Methods.Add(initReadWriters);
} }
} }
} }

View File

@ -3,40 +3,38 @@ using Mono.Cecil.Cil;
namespace Mirror.Weaver namespace Mirror.Weaver
{ {
// Processes [Rpc] methods in NetworkBehaviour // Processes [Rpc] methods in NetworkBehaviour
public static class RpcProcessor 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) public static MethodDefinition ProcessRpcInvoke(WeaverTypes weaverTypes, Writers writers, Readers readers, Logger Log, TypeDefinition td, MethodDefinition md, MethodDefinition rpcCallFunc, ref bool WeavingFailed)
{ {
var rpc = new MethodDefinition( MethodDefinition rpc = new MethodDefinition(
Weaver.InvokeRpcPrefix + md.Name, Weaver.InvokeRpcPrefix + md.Name,
MethodAttributes.Family | MethodAttributes.Static | MethodAttributes.HideBySig, MethodAttributes.Family | MethodAttributes.Static | MethodAttributes.HideBySig,
weaverTypes.Import(typeof(void))); weaverTypes.Import(typeof(void)));
var worker = rpc.Body.GetILProcessor(); ILProcessor worker = rpc.Body.GetILProcessor();
var label = worker.Create(OpCodes.Nop); Instruction label = worker.Create(OpCodes.Nop);
NetworkBehaviourProcessor.WriteClientActiveCheck(worker, weaverTypes, md.Name, label, "RPC"); NetworkBehaviourProcessor.WriteClientActiveCheck(worker, weaverTypes, md.Name, label, "RPC");
// setup for reader // setup for reader
worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Castclass, td); worker.Emit(OpCodes.Castclass, td);
if (!NetworkBehaviourProcessor.ReadArguments(md, readers, Log, worker, RemoteCallType.ClientRpc, ref WeavingFailed)) if (!NetworkBehaviourProcessor.ReadArguments(md, readers, Log, worker, RemoteCallType.ClientRpc, ref WeavingFailed))
{ return null;
return null;
}
// invoke actual command function // invoke actual command function
worker.Emit(OpCodes.Callvirt, rpcCallFunc); worker.Emit(OpCodes.Callvirt, rpcCallFunc);
worker.Emit(OpCodes.Ret); worker.Emit(OpCodes.Ret);
NetworkBehaviourProcessor.AddInvokeParameters(weaverTypes, rpc.Parameters); NetworkBehaviourProcessor.AddInvokeParameters(weaverTypes, rpc.Parameters);
td.Methods.Add(rpc); td.Methods.Add(rpc);
return rpc; return rpc;
} }
/* /*
* generates code like: * generates code like:
public void RpcTest (int param) public void RpcTest (int param)
@ -58,46 +56,44 @@ namespace Mirror.Weaver
This way we do not need to modify the code anywhere else, and this works This way we do not need to modify the code anywhere else, and this works
correctly in dependent assemblies correctly in dependent assemblies
*/ */
public static MethodDefinition ProcessRpcCall(WeaverTypes weaverTypes, Writers writers, Logger Log, TypeDefinition td, MethodDefinition md, CustomAttribute clientRpcAttr, ref bool WeavingFailed) public static MethodDefinition ProcessRpcCall(WeaverTypes weaverTypes, Writers writers, Logger Log, TypeDefinition td, MethodDefinition md, CustomAttribute clientRpcAttr, ref bool WeavingFailed)
{ {
var rpc = MethodProcessor.SubstituteMethod(Log, td, md, ref WeavingFailed); MethodDefinition rpc = MethodProcessor.SubstituteMethod(Log, td, md, ref WeavingFailed);
var worker = md.Body.GetILProcessor(); ILProcessor worker = md.Body.GetILProcessor();
NetworkBehaviourProcessor.WriteSetupLocals(worker, weaverTypes); NetworkBehaviourProcessor.WriteSetupLocals(worker, weaverTypes);
// add a log message if needed for debugging // add a log message if needed for debugging
//worker.Emit(OpCodes.Ldstr, $"Call ClientRpc function {md.Name}"); //worker.Emit(OpCodes.Ldstr, $"Call ClientRpc function {md.Name}");
//worker.Emit(OpCodes.Call, WeaverTypes.logErrorReference); //worker.Emit(OpCodes.Call, WeaverTypes.logErrorReference);
NetworkBehaviourProcessor.WriteCreateWriter(worker, weaverTypes); NetworkBehaviourProcessor.WriteCreateWriter(worker, weaverTypes);
// write all the arguments that the user passed to the Rpc call // write all the arguments that the user passed to the Rpc call
if (!NetworkBehaviourProcessor.WriteArguments(worker, writers, Log, md, RemoteCallType.ClientRpc, ref WeavingFailed)) if (!NetworkBehaviourProcessor.WriteArguments(worker, writers, Log, md, RemoteCallType.ClientRpc, ref WeavingFailed))
{ return null;
return null;
}
var channel = clientRpcAttr.GetField("channel", 0); int channel = clientRpcAttr.GetField("channel", 0);
var includeOwner = clientRpcAttr.GetField("includeOwner", true); bool includeOwner = clientRpcAttr.GetField("includeOwner", true);
// invoke SendInternal and return // invoke SendInternal and return
// this // this
worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldarg_0);
// pass full function name to avoid ClassA.Func <-> ClassB.Func collisions // pass full function name to avoid ClassA.Func <-> ClassB.Func collisions
worker.Emit(OpCodes.Ldstr, md.FullName); worker.Emit(OpCodes.Ldstr, md.FullName);
// writer // writer
worker.Emit(OpCodes.Ldloc_0); worker.Emit(OpCodes.Ldloc_0);
worker.Emit(OpCodes.Ldc_I4, channel); worker.Emit(OpCodes.Ldc_I4, channel);
// includeOwner ? 1 : 0 // includeOwner ? 1 : 0
worker.Emit(includeOwner ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); worker.Emit(includeOwner ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
worker.Emit(OpCodes.Callvirt, weaverTypes.sendRpcInternal); worker.Emit(OpCodes.Callvirt, weaverTypes.sendRpcInternal);
NetworkBehaviourProcessor.WriteRecycleWriter(worker, weaverTypes); NetworkBehaviourProcessor.WriteRecycleWriter(worker, weaverTypes);
worker.Emit(OpCodes.Ret); worker.Emit(OpCodes.Ret);
return rpc; return rpc;
} }
} }
} }

View File

@ -4,163 +4,151 @@ using Mono.Cecil.Cil;
namespace Mirror.Weaver namespace Mirror.Weaver
{ {
internal static class ServerClientAttributeProcessor static class ServerClientAttributeProcessor
{ {
public static bool Process(WeaverTypes weaverTypes, Logger Log, TypeDefinition td, ref bool WeavingFailed) public static bool Process(WeaverTypes weaverTypes, Logger Log, TypeDefinition td, ref bool WeavingFailed)
{ {
var modified = false; bool modified = false;
foreach (var md in td.Methods) foreach (MethodDefinition md in td.Methods)
{ {
modified |= ProcessSiteMethod(weaverTypes, Log, md, ref WeavingFailed); modified |= ProcessSiteMethod(weaverTypes, Log, md, ref WeavingFailed);
} }
foreach (var nested in td.NestedTypes) foreach (TypeDefinition nested in td.NestedTypes)
{ {
modified |= Process(weaverTypes, Log, nested, ref WeavingFailed); modified |= Process(weaverTypes, Log, nested, ref WeavingFailed);
} }
return modified; return modified;
} }
private static bool ProcessSiteMethod(WeaverTypes weaverTypes, Logger Log, MethodDefinition md, ref bool WeavingFailed) static bool ProcessSiteMethod(WeaverTypes weaverTypes, Logger Log, MethodDefinition md, ref bool WeavingFailed)
{ {
if (md.Name == ".cctor" || if (md.Name == ".cctor" ||
md.Name == NetworkBehaviourProcessor.ProcessedFunctionName || md.Name == NetworkBehaviourProcessor.ProcessedFunctionName ||
md.Name.StartsWith(Weaver.InvokeRpcPrefix)) md.Name.StartsWith(Weaver.InvokeRpcPrefix))
{ return false;
return false;
}
if (md.IsAbstract) if (md.IsAbstract)
{ {
if (HasServerClientAttribute(md)) 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); 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; WeavingFailed = true;
} }
return false; return false;
} }
if (md.Body != null && md.Body.Instructions != null) if (md.Body != null && md.Body.Instructions != null)
{ {
return ProcessMethodAttributes(weaverTypes, md); return ProcessMethodAttributes(weaverTypes, md);
} }
return false; return false;
} }
public static bool HasServerClientAttribute(MethodDefinition md) public static bool HasServerClientAttribute(MethodDefinition md)
{ {
foreach (var attr in md.CustomAttributes) foreach (CustomAttribute attr in md.CustomAttributes)
{ {
switch (attr.Constructor.DeclaringType.ToString()) switch (attr.Constructor.DeclaringType.ToString())
{ {
case "Mirror.ServerAttribute": case "Mirror.ServerAttribute":
case "Mirror.ServerCallbackAttribute": case "Mirror.ServerCallbackAttribute":
case "Mirror.ClientAttribute": case "Mirror.ClientAttribute":
case "Mirror.ClientCallbackAttribute": case "Mirror.ClientCallbackAttribute":
return true; return true;
default: default:
break; break;
} }
} }
return false; return false;
} }
public static bool ProcessMethodAttributes(WeaverTypes weaverTypes, MethodDefinition md) public static bool ProcessMethodAttributes(WeaverTypes weaverTypes, MethodDefinition md)
{ {
if (md.HasCustomAttribute<ServerAttribute>()) if (md.HasCustomAttribute<ServerAttribute>())
{ InjectServerGuard(weaverTypes, md, true);
InjectServerGuard(weaverTypes, md, true); else if (md.HasCustomAttribute<ServerCallbackAttribute>())
} InjectServerGuard(weaverTypes, md, false);
else if (md.HasCustomAttribute<ServerCallbackAttribute>()) else if (md.HasCustomAttribute<ClientAttribute>())
{ InjectClientGuard(weaverTypes, md, true);
InjectServerGuard(weaverTypes, md, false); else if (md.HasCustomAttribute<ClientCallbackAttribute>())
} InjectClientGuard(weaverTypes, md, false);
else if (md.HasCustomAttribute<ClientAttribute>()) else
{ return false;
InjectClientGuard(weaverTypes, md, true);
}
else if (md.HasCustomAttribute<ClientCallbackAttribute>())
{
InjectClientGuard(weaverTypes, md, false);
}
else
{
return false;
}
return true; return true;
} }
private static void InjectServerGuard(WeaverTypes weaverTypes, MethodDefinition md, bool logWarning) static void InjectServerGuard(WeaverTypes weaverTypes, MethodDefinition md, bool logWarning)
{ {
var worker = md.Body.GetILProcessor(); ILProcessor worker = md.Body.GetILProcessor();
var top = md.Body.Instructions[0]; Instruction top = md.Body.Instructions[0];
worker.InsertBefore(top, worker.Create(OpCodes.Call, weaverTypes.NetworkServerGetActive)); worker.InsertBefore(top, worker.Create(OpCodes.Call, weaverTypes.NetworkServerGetActive));
worker.InsertBefore(top, worker.Create(OpCodes.Brtrue, top)); worker.InsertBefore(top, worker.Create(OpCodes.Brtrue, top));
if (logWarning) 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.Ldstr, $"[Server] function '{md.FullName}' called when server was not active"));
worker.InsertBefore(top, worker.Create(OpCodes.Call, weaverTypes.logWarningReference)); worker.InsertBefore(top, worker.Create(OpCodes.Call, weaverTypes.logWarningReference));
} }
InjectGuardParameters(md, worker, top); InjectGuardParameters(md, worker, top);
InjectGuardReturnValue(md, worker, top); InjectGuardReturnValue(md, worker, top);
worker.InsertBefore(top, worker.Create(OpCodes.Ret)); worker.InsertBefore(top, worker.Create(OpCodes.Ret));
} }
private static void InjectClientGuard(WeaverTypes weaverTypes, MethodDefinition md, bool logWarning) static void InjectClientGuard(WeaverTypes weaverTypes, MethodDefinition md, bool logWarning)
{ {
var worker = md.Body.GetILProcessor(); ILProcessor worker = md.Body.GetILProcessor();
var top = md.Body.Instructions[0]; Instruction top = md.Body.Instructions[0];
worker.InsertBefore(top, worker.Create(OpCodes.Call, weaverTypes.NetworkClientGetActive)); worker.InsertBefore(top, worker.Create(OpCodes.Call, weaverTypes.NetworkClientGetActive));
worker.InsertBefore(top, worker.Create(OpCodes.Brtrue, top)); worker.InsertBefore(top, worker.Create(OpCodes.Brtrue, top));
if (logWarning) 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.Ldstr, $"[Client] function '{md.FullName}' called when client was not active"));
worker.InsertBefore(top, worker.Create(OpCodes.Call, weaverTypes.logWarningReference)); worker.InsertBefore(top, worker.Create(OpCodes.Call, weaverTypes.logWarningReference));
} }
InjectGuardParameters(md, worker, top); InjectGuardParameters(md, worker, top);
InjectGuardReturnValue(md, worker, top); InjectGuardReturnValue(md, worker, top);
worker.InsertBefore(top, worker.Create(OpCodes.Ret)); worker.InsertBefore(top, worker.Create(OpCodes.Ret));
} }
// this is required to early-out from a function with "ref" or "out" parameters // this is required to early-out from a function with "ref" or "out" parameters
private static void InjectGuardParameters(MethodDefinition md, ILProcessor worker, Instruction top) static void InjectGuardParameters(MethodDefinition md, ILProcessor worker, Instruction top)
{ {
var offset = md.Resolve().IsStatic ? 0 : 1; int offset = md.Resolve().IsStatic ? 0 : 1;
for (var index = 0; index < md.Parameters.Count; index++) for (int index = 0; index < md.Parameters.Count; index++)
{ {
var param = md.Parameters[index]; ParameterDefinition param = md.Parameters[index];
if (param.IsOut) if (param.IsOut)
{ {
var elementType = param.ParameterType.GetElementType(); TypeReference elementType = param.ParameterType.GetElementType();
md.Body.Variables.Add(new VariableDefinition(elementType)); md.Body.Variables.Add(new VariableDefinition(elementType));
md.Body.InitLocals = true; md.Body.InitLocals = true;
worker.InsertBefore(top, worker.Create(OpCodes.Ldarg, index + offset)); 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.Ldloca_S, (byte)(md.Body.Variables.Count - 1)));
worker.InsertBefore(top, worker.Create(OpCodes.Initobj, elementType)); 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.Ldloc, md.Body.Variables.Count - 1));
worker.InsertBefore(top, worker.Create(OpCodes.Stobj, elementType)); worker.InsertBefore(top, worker.Create(OpCodes.Stobj, elementType));
} }
} }
} }
// this is required to early-out from a function with a return value. // this is required to early-out from a function with a return value.
private static void InjectGuardReturnValue(MethodDefinition md, ILProcessor worker, Instruction top) static void InjectGuardReturnValue(MethodDefinition md, ILProcessor worker, Instruction top)
{ {
if (!md.ReturnType.Is(typeof(void))) if (!md.ReturnType.Is(typeof(void)))
{ {
md.Body.Variables.Add(new VariableDefinition(md.ReturnType)); md.Body.Variables.Add(new VariableDefinition(md.ReturnType));
md.Body.InitLocals = true; md.Body.InitLocals = true;
worker.InsertBefore(top, worker.Create(OpCodes.Ldloca_S, (byte)(md.Body.Variables.Count - 1))); 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.Initobj, md.ReturnType));
worker.InsertBefore(top, worker.Create(OpCodes.Ldloc, md.Body.Variables.Count - 1)); worker.InsertBefore(top, worker.Create(OpCodes.Ldloc, md.Body.Variables.Count - 1));
} }
} }
} }
} }

View File

@ -3,37 +3,37 @@ using Mono.Cecil.Cil;
namespace Mirror.Weaver namespace Mirror.Weaver
{ {
public static class SyncObjectInitializer public static class SyncObjectInitializer
{ {
// generates code like: // generates code like:
// this.InitSyncObject(m_sizes); // this.InitSyncObject(m_sizes);
public static void GenerateSyncObjectInitializer(ILProcessor worker, WeaverTypes weaverTypes, FieldDefinition fd) public static void GenerateSyncObjectInitializer(ILProcessor worker, WeaverTypes weaverTypes, FieldDefinition fd)
{ {
// register syncobject in network behaviour // register syncobject in network behaviour
worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldfld, fd); worker.Emit(OpCodes.Ldfld, fd);
worker.Emit(OpCodes.Call, weaverTypes.InitSyncObjectReference); worker.Emit(OpCodes.Call, weaverTypes.InitSyncObjectReference);
} }
public static bool ImplementsSyncObject(TypeReference typeRef) public static bool ImplementsSyncObject(TypeReference typeRef)
{ {
try try
{ {
// value types cant inherit from SyncObject // value types cant inherit from SyncObject
if (typeRef.IsValueType) if (typeRef.IsValueType)
{ {
return false; return false;
} }
return typeRef.Resolve().IsDerivedFrom<SyncObject>(); return typeRef.Resolve().IsDerivedFrom<SyncObject>();
} }
catch 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 // 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; return false;
} }
} }
} }

View File

@ -1,84 +1,84 @@
using Mono.Cecil;
using System.Collections.Generic; using System.Collections.Generic;
using Mono.Cecil;
namespace Mirror.Weaver namespace Mirror.Weaver
{ {
public static class SyncObjectProcessor public static class SyncObjectProcessor
{ {
// ulong = 64 bytes // ulong = 64 bytes
private const int SyncObjectsLimit = 64; const int SyncObjectsLimit = 64;
// Finds SyncObjects fields in a type // Finds SyncObjects fields in a type
// Type should be a NetworkBehaviour // Type should be a NetworkBehaviour
public static List<FieldDefinition> FindSyncObjectsFields(Writers writers, Readers readers, Logger Log, TypeDefinition td, ref bool WeavingFailed) public static List<FieldDefinition> FindSyncObjectsFields(Writers writers, Readers readers, Logger Log, TypeDefinition td, ref bool WeavingFailed)
{ {
var syncObjects = new List<FieldDefinition>(); List<FieldDefinition> syncObjects = new List<FieldDefinition>();
foreach (var fd in td.Fields) foreach (FieldDefinition fd in td.Fields)
{ {
if (fd.FieldType.Resolve().IsDerivedFrom<SyncObject>()) if (fd.FieldType.Resolve().IsDerivedFrom<SyncObject>())
{ {
if (fd.IsStatic) if (fd.IsStatic)
{ {
Log.Error($"{fd.Name} cannot be static", fd); Log.Error($"{fd.Name} cannot be static", fd);
WeavingFailed = true; WeavingFailed = true;
continue; continue;
} }
// SyncObjects always needs to be readonly to guarantee. // SyncObjects always needs to be readonly to guarantee.
// Weaver calls InitSyncObject on them for dirty bits etc. // Weaver calls InitSyncObject on them for dirty bits etc.
// Reassigning at runtime would cause undefined behaviour. // Reassigning at runtime would cause undefined behaviour.
// (C# 'readonly' is called 'initonly' in IL code.) // (C# 'readonly' is called 'initonly' in IL code.)
// //
// NOTE: instead of forcing readonly, we could also scan all // NOTE: instead of forcing readonly, we could also scan all
// instructions for SyncObject assignments. this would // instructions for SyncObject assignments. this would
// make unit tests very difficult though. // make unit tests very difficult though.
if (!fd.IsInitOnly) if (!fd.IsInitOnly)
{ {
// just a warning for now. // just a warning for now.
// many people might still use non-readonly SyncObjects. // 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); 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. // only log, but keep weaving. no need to break projects.
//WeavingFailed = true; //WeavingFailed = true;
} }
GenerateReadersAndWriters(writers, readers, fd.FieldType, ref WeavingFailed); GenerateReadersAndWriters(writers, readers, fd.FieldType, ref WeavingFailed);
syncObjects.Add(fd); syncObjects.Add(fd);
} }
} }
// SyncObjects dirty mask is 64 bit. can't sync more than 64. // SyncObjects dirty mask is 64 bit. can't sync more than 64.
if (syncObjects.Count > 64) if (syncObjects.Count > 64)
{ {
Log.Error($"{td.Name} has > {SyncObjectsLimit} SyncObjects (SyncLists etc). Consider refactoring your class into multiple components", td); Log.Error($"{td.Name} has > {SyncObjectsLimit} SyncObjects (SyncLists etc). Consider refactoring your class into multiple components", td);
WeavingFailed = true; WeavingFailed = true;
} }
return syncObjects; return syncObjects;
} }
// Generates serialization methods for synclists // Generates serialization methods for synclists
private static void GenerateReadersAndWriters(Writers writers, Readers readers, TypeReference tr, ref bool WeavingFailed) static void GenerateReadersAndWriters(Writers writers, Readers readers, TypeReference tr, ref bool WeavingFailed)
{ {
if (tr is GenericInstanceType genericInstance) if (tr is GenericInstanceType genericInstance)
{ {
foreach (var argument in genericInstance.GenericArguments) foreach (TypeReference argument in genericInstance.GenericArguments)
{ {
if (!argument.IsGenericParameter) if (!argument.IsGenericParameter)
{ {
readers.GetReadFunc(argument, ref WeavingFailed); readers.GetReadFunc(argument, ref WeavingFailed);
writers.GetWriteFunc(argument, ref WeavingFailed); writers.GetWriteFunc(argument, ref WeavingFailed);
} }
} }
} }
if (tr != null) if (tr != null)
{ {
GenerateReadersAndWriters(writers, readers, tr.Resolve().BaseType, ref WeavingFailed); GenerateReadersAndWriters(writers, readers, tr.Resolve().BaseType, ref WeavingFailed);
} }
} }
} }
} }

View File

@ -2,181 +2,173 @@
// is replaced with: // is replaced with:
// public int Networkhealth { get; set; } properties. // public int Networkhealth { get; set; } properties.
// this class processes all access to 'health' and replaces it with 'Networkhealth' // this class processes all access to 'health' and replaces it with 'Networkhealth'
using System;
using Mono.Cecil; using Mono.Cecil;
using Mono.Cecil.Cil; using Mono.Cecil.Cil;
using System;
namespace Mirror.Weaver namespace Mirror.Weaver
{ {
public static class SyncVarAttributeAccessReplacer public static class SyncVarAttributeAccessReplacer
{ {
// process the module // process the module
public static void Process(ModuleDefinition moduleDef, SyncVarAccessLists syncVarAccessLists) public static void Process(ModuleDefinition moduleDef, SyncVarAccessLists syncVarAccessLists)
{ {
var startTime = DateTime.Now; DateTime startTime = DateTime.Now;
// process all classes in this module // process all classes in this module
foreach (var td in moduleDef.Types) foreach (TypeDefinition td in moduleDef.Types)
{ {
if (td.IsClass) if (td.IsClass)
{ {
ProcessClass(syncVarAccessLists, td); ProcessClass(syncVarAccessLists, td);
} }
} }
Console.WriteLine($" ProcessSitesModule {moduleDef.Name} elapsed time:{(DateTime.Now - startTime)}"); Console.WriteLine($" ProcessSitesModule {moduleDef.Name} elapsed time:{(DateTime.Now - startTime)}");
} }
private static void ProcessClass(SyncVarAccessLists syncVarAccessLists, TypeDefinition td) static void ProcessClass(SyncVarAccessLists syncVarAccessLists, TypeDefinition td)
{ {
//Console.WriteLine($" ProcessClass {td}"); //Console.WriteLine($" ProcessClass {td}");
// process all methods in this class // process all methods in this class
foreach (var md in td.Methods) foreach (MethodDefinition md in td.Methods)
{ {
ProcessMethod(syncVarAccessLists, md); ProcessMethod(syncVarAccessLists, md);
} }
// processes all nested classes in this class recursively // processes all nested classes in this class recursively
foreach (var nested in td.NestedTypes) foreach (TypeDefinition nested in td.NestedTypes)
{ {
ProcessClass(syncVarAccessLists, nested); ProcessClass(syncVarAccessLists, nested);
} }
} }
private static void ProcessMethod(SyncVarAccessLists syncVarAccessLists, MethodDefinition md) static void ProcessMethod(SyncVarAccessLists syncVarAccessLists, MethodDefinition md)
{ {
// process all references to replaced members with properties // process all references to replaced members with properties
//Log.Warning($" ProcessSiteMethod {md}"); //Log.Warning($" ProcessSiteMethod {md}");
// skip static constructor, "MirrorProcessed", "InvokeUserCode_" // skip static constructor, "MirrorProcessed", "InvokeUserCode_"
if (md.Name == ".cctor" || if (md.Name == ".cctor" ||
md.Name == NetworkBehaviourProcessor.ProcessedFunctionName || md.Name == NetworkBehaviourProcessor.ProcessedFunctionName ||
md.Name.StartsWith(Weaver.InvokeRpcPrefix)) md.Name.StartsWith(Weaver.InvokeRpcPrefix))
{ return;
return;
}
// skip abstract // skip abstract
if (md.IsAbstract) if (md.IsAbstract)
{ {
return; return;
} }
// go through all instructions of this method // go through all instructions of this method
if (md.Body != null && md.Body.Instructions != null) if (md.Body != null && md.Body.Instructions != null)
{ {
for (var i = 0; i < md.Body.Instructions.Count;) for (int i = 0; i < md.Body.Instructions.Count;)
{ {
var instr = md.Body.Instructions[i]; Instruction instr = md.Body.Instructions[i];
i += ProcessInstruction(syncVarAccessLists, md, instr, i); i += ProcessInstruction(syncVarAccessLists, md, instr, i);
} }
} }
} }
private static int ProcessInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction instr, int iCount) static int ProcessInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction instr, int iCount)
{ {
// stfld (sets value of a field)? // stfld (sets value of a field)?
if (instr.OpCode == OpCodes.Stfld && instr.Operand is FieldDefinition opFieldst) if (instr.OpCode == OpCodes.Stfld && instr.Operand is FieldDefinition opFieldst)
{ {
ProcessSetInstruction(syncVarAccessLists, md, instr, opFieldst); ProcessSetInstruction(syncVarAccessLists, md, instr, opFieldst);
} }
// ldfld (load value of a field)? // ldfld (load value of a field)?
if (instr.OpCode == OpCodes.Ldfld && instr.Operand is FieldDefinition opFieldld) if (instr.OpCode == OpCodes.Ldfld && instr.Operand is FieldDefinition opFieldld)
{ {
// this instruction gets the value of a field. cache the field reference. // this instruction gets the value of a field. cache the field reference.
ProcessGetInstruction(syncVarAccessLists, md, instr, opFieldld); ProcessGetInstruction(syncVarAccessLists, md, instr, opFieldld);
} }
// ldflda (load field address aka reference) // ldflda (load field address aka reference)
if (instr.OpCode == OpCodes.Ldflda && instr.Operand is FieldDefinition opFieldlda) if (instr.OpCode == OpCodes.Ldflda && instr.Operand is FieldDefinition opFieldlda)
{ {
// watch out for initobj instruction // watch out for initobj instruction
// see https://github.com/vis2k/Mirror/issues/696 // see https://github.com/vis2k/Mirror/issues/696
return ProcessLoadAddressInstruction(syncVarAccessLists, md, instr, opFieldlda, iCount); return ProcessLoadAddressInstruction(syncVarAccessLists, md, instr, opFieldlda, iCount);
} }
// we processed one instruction (instr) // we processed one instruction (instr)
return 1; return 1;
} }
// replaces syncvar write access with the NetworkXYZ.set property calls // replaces syncvar write access with the NetworkXYZ.set property calls
private static void ProcessSetInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction i, FieldDefinition opField) static void ProcessSetInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction i, FieldDefinition opField)
{ {
// don't replace property call sites in constructors // don't replace property call sites in constructors
if (md.Name == ".ctor") if (md.Name == ".ctor")
{ return;
return;
}
// does it set a field that we replaced? // does it set a field that we replaced?
if (syncVarAccessLists.replacementSetterProperties.TryGetValue(opField, out var replacement)) if (syncVarAccessLists.replacementSetterProperties.TryGetValue(opField, out MethodDefinition replacement))
{ {
//replace with property //replace with property
//Log.Warning($" replacing {md.Name}:{i}", opField); //Log.Warning($" replacing {md.Name}:{i}", opField);
i.OpCode = OpCodes.Call; i.OpCode = OpCodes.Call;
i.Operand = replacement; i.Operand = replacement;
//Log.Warning($" replaced {md.Name}:{i}", opField); //Log.Warning($" replaced {md.Name}:{i}", opField);
} }
} }
// replaces syncvar read access with the NetworkXYZ.get property calls // replaces syncvar read access with the NetworkXYZ.get property calls
private static void ProcessGetInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction i, FieldDefinition opField) static void ProcessGetInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction i, FieldDefinition opField)
{ {
// don't replace property call sites in constructors // don't replace property call sites in constructors
if (md.Name == ".ctor") if (md.Name == ".ctor")
{ return;
return;
}
// does it set a field that we replaced? // does it set a field that we replaced?
if (syncVarAccessLists.replacementGetterProperties.TryGetValue(opField, out var replacement)) if (syncVarAccessLists.replacementGetterProperties.TryGetValue(opField, out MethodDefinition replacement))
{ {
//replace with property //replace with property
//Log.Warning($" replacing {md.Name}:{i}"); //Log.Warning($" replacing {md.Name}:{i}");
i.OpCode = OpCodes.Call; i.OpCode = OpCodes.Call;
i.Operand = replacement; i.Operand = replacement;
//Log.Warning($" replaced {md.Name}:{i}"); //Log.Warning($" replaced {md.Name}:{i}");
} }
} }
private static int ProcessLoadAddressInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction instr, FieldDefinition opField, int iCount) static int ProcessLoadAddressInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction instr, FieldDefinition opField, int iCount)
{ {
// don't replace property call sites in constructors // don't replace property call sites in constructors
if (md.Name == ".ctor") if (md.Name == ".ctor")
{ return 1;
return 1;
}
// does it set a field that we replaced? // does it set a field that we replaced?
if (syncVarAccessLists.replacementSetterProperties.TryGetValue(opField, out var replacement)) if (syncVarAccessLists.replacementSetterProperties.TryGetValue(opField, out MethodDefinition replacement))
{ {
// we have a replacement for this property // we have a replacement for this property
// is the next instruction a initobj? // is the next instruction a initobj?
var nextInstr = md.Body.Instructions[iCount + 1]; Instruction nextInstr = md.Body.Instructions[iCount + 1];
if (nextInstr.OpCode == OpCodes.Initobj) if (nextInstr.OpCode == OpCodes.Initobj)
{ {
// we need to replace this code with: // we need to replace this code with:
// var tmp = new MyStruct(); // var tmp = new MyStruct();
// this.set_Networkxxxx(tmp); // this.set_Networkxxxx(tmp);
var worker = md.Body.GetILProcessor(); ILProcessor worker = md.Body.GetILProcessor();
var tmpVariable = new VariableDefinition(opField.FieldType); VariableDefinition tmpVariable = new VariableDefinition(opField.FieldType);
md.Body.Variables.Add(tmpVariable); md.Body.Variables.Add(tmpVariable);
worker.InsertBefore(instr, worker.Create(OpCodes.Ldloca, tmpVariable)); worker.InsertBefore(instr, worker.Create(OpCodes.Ldloca, tmpVariable));
worker.InsertBefore(instr, worker.Create(OpCodes.Initobj, opField.FieldType)); worker.InsertBefore(instr, worker.Create(OpCodes.Initobj, opField.FieldType));
worker.InsertBefore(instr, worker.Create(OpCodes.Ldloc, tmpVariable)); worker.InsertBefore(instr, worker.Create(OpCodes.Ldloc, tmpVariable));
worker.InsertBefore(instr, worker.Create(OpCodes.Call, replacement)); worker.InsertBefore(instr, worker.Create(OpCodes.Call, replacement));
worker.Remove(instr); worker.Remove(instr);
worker.Remove(nextInstr); worker.Remove(nextInstr);
return 4; return 4;
} }
} }
return 1; return 1;
} }
} }
} }

View File

@ -1,485 +1,490 @@
using Mono.Cecil;
using Mono.Cecil.Cil;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;
namespace Mirror.Weaver namespace Mirror.Weaver
{ {
// Processes [SyncVar] attribute fields in NetworkBehaviour // Processes [SyncVar] attribute fields in NetworkBehaviour
// not static, because ILPostProcessor is multithreaded // not static, because ILPostProcessor is multithreaded
public class SyncVarAttributeProcessor public class SyncVarAttributeProcessor
{ {
// ulong = 64 bytes // ulong = 64 bytes
private const int SyncVarLimit = 64; const int SyncVarLimit = 64;
private readonly AssemblyDefinition assembly;
private readonly WeaverTypes weaverTypes; AssemblyDefinition assembly;
private readonly SyncVarAccessLists syncVarAccessLists; WeaverTypes weaverTypes;
private readonly Logger Log; SyncVarAccessLists syncVarAccessLists;
Logger Log;
private string HookParameterMessage(string hookName, TypeReference ValueType) =>
$"void {hookName}({ValueType} oldValue, {ValueType} newValue)"; string HookParameterMessage(string hookName, TypeReference ValueType) =>
$"void {hookName}({ValueType} oldValue, {ValueType} newValue)";
public SyncVarAttributeProcessor(AssemblyDefinition assembly, WeaverTypes weaverTypes, SyncVarAccessLists syncVarAccessLists, Logger Log)
{ public SyncVarAttributeProcessor(AssemblyDefinition assembly, WeaverTypes weaverTypes, SyncVarAccessLists syncVarAccessLists, Logger Log)
this.assembly = assembly; {
this.weaverTypes = weaverTypes; this.assembly = assembly;
this.syncVarAccessLists = syncVarAccessLists; this.weaverTypes = weaverTypes;
this.Log = Log; this.syncVarAccessLists = syncVarAccessLists;
} this.Log = Log;
}
// Get hook method if any
public MethodDefinition GetHookMethod(TypeDefinition td, FieldDefinition syncVar, ref bool WeavingFailed) // Get hook method if any
{ public MethodDefinition GetHookMethod(TypeDefinition td, FieldDefinition syncVar, ref bool WeavingFailed)
var syncVarAttr = syncVar.GetCustomAttribute<SyncVarAttribute>(); {
CustomAttribute syncVarAttr = syncVar.GetCustomAttribute<SyncVarAttribute>();
if (syncVarAttr == null)
{ if (syncVarAttr == null)
return null; return null;
}
string hookFunctionName = syncVarAttr.GetField<string>("hook", null);
var hookFunctionName = syncVarAttr.GetField<string>("hook", null);
if (hookFunctionName == null)
if (hookFunctionName == null) return null;
{
return null; return FindHookMethod(td, syncVar, hookFunctionName, ref WeavingFailed);
} }
return FindHookMethod(td, syncVar, hookFunctionName, ref WeavingFailed); MethodDefinition FindHookMethod(TypeDefinition td, FieldDefinition syncVar, string hookFunctionName, ref bool WeavingFailed)
} {
List<MethodDefinition> methods = td.GetMethods(hookFunctionName);
private MethodDefinition FindHookMethod(TypeDefinition td, FieldDefinition syncVar, string hookFunctionName, ref bool WeavingFailed)
{ List<MethodDefinition> methodsWith2Param = new List<MethodDefinition>(methods.Where(m => m.Parameters.Count == 2));
var methods = td.GetMethods(hookFunctionName);
if (methodsWith2Param.Count == 0)
var methodsWith2Param = new List<MethodDefinition>(methods.Where(m => m.Parameters.Count == 2)); {
Log.Error($"Could not find hook for '{syncVar.Name}', hook name '{hookFunctionName}'. " +
if (methodsWith2Param.Count == 0) $"Method signature should be {HookParameterMessage(hookFunctionName, syncVar.FieldType)}",
{ syncVar);
Log.Error($"Could not find hook for '{syncVar.Name}', hook name '{hookFunctionName}'. " + WeavingFailed = true;
$"Method signature should be {HookParameterMessage(hookFunctionName, syncVar.FieldType)}",
syncVar); return null;
WeavingFailed = true; }
return null; foreach (MethodDefinition method in methodsWith2Param)
} {
if (MatchesParameters(syncVar, method))
foreach (var method in methodsWith2Param) {
{ return method;
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);
Log.Error($"Wrong type for Parameter in hook for '{syncVar.Name}', hook name '{hookFunctionName}'. " + WeavingFailed = true;
$"Method signature should be {HookParameterMessage(hookFunctionName, syncVar.FieldType)}",
syncVar); return null;
WeavingFailed = true; }
return null; bool MatchesParameters(FieldDefinition syncVar, MethodDefinition method)
} {
// matches void onValueChange(T oldValue, T newValue)
private bool MatchesParameters(FieldDefinition syncVar, MethodDefinition method) => return method.Parameters[0].ParameterType.FullName == syncVar.FieldType.FullName &&
// matches void onValueChange(T oldValue, T newValue) method.Parameters[1].ParameterType.FullName == syncVar.FieldType.FullName;
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)
public MethodDefinition GenerateSyncVarGetter(FieldDefinition fd, string originalName, FieldDefinition netFieldId) {
{ //Create the get method
//Create the get method MethodDefinition get = new MethodDefinition(
var get = new MethodDefinition( $"get_Network{originalName}", MethodAttributes.Public |
$"get_Network{originalName}", MethodAttributes.Public | MethodAttributes.SpecialName |
MethodAttributes.SpecialName | MethodAttributes.HideBySig,
MethodAttributes.HideBySig, fd.FieldType);
fd.FieldType);
ILProcessor worker = get.Body.GetILProcessor();
var worker = get.Body.GetILProcessor();
// [SyncVar] GameObject?
// [SyncVar] GameObject? if (fd.FieldType.Is<UnityEngine.GameObject>())
if (fd.FieldType.Is<UnityEngine.GameObject>()) {
{ // return this.GetSyncVarGameObject(ref field, uint netId);
// return this.GetSyncVarGameObject(ref field, uint netId); // this.
// this. worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldfld, netFieldId);
worker.Emit(OpCodes.Ldfld, netFieldId); worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldflda, fd);
worker.Emit(OpCodes.Ldflda, fd); worker.Emit(OpCodes.Call, weaverTypes.getSyncVarGameObjectReference);
worker.Emit(OpCodes.Call, weaverTypes.getSyncVarGameObjectReference); worker.Emit(OpCodes.Ret);
worker.Emit(OpCodes.Ret); }
} // [SyncVar] NetworkIdentity?
// [SyncVar] NetworkIdentity? else if (fd.FieldType.Is<NetworkIdentity>())
else if (fd.FieldType.Is<NetworkIdentity>()) {
{ // return this.GetSyncVarNetworkIdentity(ref field, uint netId);
// return this.GetSyncVarNetworkIdentity(ref field, uint netId); // this.
// this. worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldfld, netFieldId);
worker.Emit(OpCodes.Ldfld, netFieldId); worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldflda, fd);
worker.Emit(OpCodes.Ldflda, fd); worker.Emit(OpCodes.Call, weaverTypes.getSyncVarNetworkIdentityReference);
worker.Emit(OpCodes.Call, weaverTypes.getSyncVarNetworkIdentityReference); worker.Emit(OpCodes.Ret);
worker.Emit(OpCodes.Ret); }
} else if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>())
else if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>()) {
{ // return this.GetSyncVarNetworkBehaviour<T>(ref field, uint netId);
// return this.GetSyncVarNetworkBehaviour<T>(ref field, uint netId); // this.
// this. worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldfld, netFieldId);
worker.Emit(OpCodes.Ldfld, netFieldId); worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldflda, fd);
worker.Emit(OpCodes.Ldflda, fd); MethodReference getFunc = weaverTypes.getSyncVarNetworkBehaviourReference.MakeGeneric(assembly.MainModule, fd.FieldType);
var getFunc = weaverTypes.getSyncVarNetworkBehaviourReference.MakeGeneric(assembly.MainModule, fd.FieldType); worker.Emit(OpCodes.Call, getFunc);
worker.Emit(OpCodes.Call, getFunc); worker.Emit(OpCodes.Ret);
worker.Emit(OpCodes.Ret); }
} // [SyncVar] int, string, etc.
// [SyncVar] int, string, etc. else
else {
{ worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldfld, fd);
worker.Emit(OpCodes.Ldfld, fd); worker.Emit(OpCodes.Ret);
worker.Emit(OpCodes.Ret); }
}
get.Body.Variables.Add(new VariableDefinition(fd.FieldType));
get.Body.Variables.Add(new VariableDefinition(fd.FieldType)); get.Body.InitLocals = true;
get.Body.InitLocals = true; get.SemanticsAttributes = MethodSemanticsAttributes.Getter;
get.SemanticsAttributes = MethodSemanticsAttributes.Getter;
return get;
return get; }
}
public MethodDefinition GenerateSyncVarSetter(TypeDefinition td, FieldDefinition fd, string originalName, long dirtyBit, FieldDefinition netFieldId, ref bool WeavingFailed)
public MethodDefinition GenerateSyncVarSetter(TypeDefinition td, FieldDefinition fd, string originalName, long dirtyBit, FieldDefinition netFieldId, ref bool WeavingFailed) {
{ //Create the set method
//Create the set method MethodDefinition set = new MethodDefinition($"set_Network{originalName}", MethodAttributes.Public |
var set = new MethodDefinition($"set_Network{originalName}", MethodAttributes.Public | MethodAttributes.SpecialName |
MethodAttributes.SpecialName | MethodAttributes.HideBySig,
MethodAttributes.HideBySig, weaverTypes.Import(typeof(void)));
weaverTypes.Import(typeof(void)));
ILProcessor worker = set.Body.GetILProcessor();
var worker = set.Body.GetILProcessor();
// if (!SyncVarEqual(value, ref playerData))
// if (!SyncVarEqual(value, ref playerData)) Instruction endOfMethod = worker.Create(OpCodes.Nop);
var endOfMethod = worker.Create(OpCodes.Nop);
// NOTE: SyncVar...Equal functions are static.
// NOTE: SyncVar...Equal functions are static. // don't Emit Ldarg_0 aka 'this'.
// don't Emit Ldarg_0 aka 'this'.
// new value to set
// new value to set worker.Emit(OpCodes.Ldarg_1);
worker.Emit(OpCodes.Ldarg_1);
// reference to field to set
// reference to field to set // make generic version of SetSyncVar with field type
// make generic version of SetSyncVar with field type if (fd.FieldType.Is<UnityEngine.GameObject>())
if (fd.FieldType.Is<UnityEngine.GameObject>()) {
{ // reference to netId Field to set
// reference to netId Field to set worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldfld, netFieldId);
worker.Emit(OpCodes.Ldfld, netFieldId);
worker.Emit(OpCodes.Call, weaverTypes.syncVarGameObjectEqualReference);
worker.Emit(OpCodes.Call, weaverTypes.syncVarGameObjectEqualReference); }
} else if (fd.FieldType.Is<NetworkIdentity>())
else if (fd.FieldType.Is<NetworkIdentity>()) {
{ // reference to netId Field to set
// reference to netId Field to set worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldfld, netFieldId);
worker.Emit(OpCodes.Ldfld, netFieldId);
worker.Emit(OpCodes.Call, weaverTypes.syncVarNetworkIdentityEqualReference);
worker.Emit(OpCodes.Call, weaverTypes.syncVarNetworkIdentityEqualReference); }
} else if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>())
else if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>()) {
{ // reference to netId Field to set
// reference to netId Field to set worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldfld, netFieldId);
worker.Emit(OpCodes.Ldfld, netFieldId);
MethodReference getFunc = weaverTypes.syncVarNetworkBehaviourEqualReference.MakeGeneric(assembly.MainModule, fd.FieldType);
var getFunc = weaverTypes.syncVarNetworkBehaviourEqualReference.MakeGeneric(assembly.MainModule, fd.FieldType); worker.Emit(OpCodes.Call, getFunc);
worker.Emit(OpCodes.Call, getFunc); }
} else
else {
{ worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldflda, fd);
worker.Emit(OpCodes.Ldflda, fd);
GenericInstanceMethod syncVarEqualGm = new GenericInstanceMethod(weaverTypes.syncVarEqualReference);
var syncVarEqualGm = new GenericInstanceMethod(weaverTypes.syncVarEqualReference); syncVarEqualGm.GenericArguments.Add(fd.FieldType);
syncVarEqualGm.GenericArguments.Add(fd.FieldType); worker.Emit(OpCodes.Call, syncVarEqualGm);
worker.Emit(OpCodes.Call, syncVarEqualGm); }
}
worker.Emit(OpCodes.Brtrue, endOfMethod);
worker.Emit(OpCodes.Brtrue, endOfMethod);
// T oldValue = value;
// T oldValue = value; // TODO for GO/NI we need to backup the netId don't we?
// TODO for GO/NI we need to backup the netId don't we? VariableDefinition oldValue = new VariableDefinition(fd.FieldType);
var oldValue = new VariableDefinition(fd.FieldType); set.Body.Variables.Add(oldValue);
set.Body.Variables.Add(oldValue); worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldfld, fd);
worker.Emit(OpCodes.Ldfld, fd); worker.Emit(OpCodes.Stloc, oldValue);
worker.Emit(OpCodes.Stloc, oldValue);
// this
// this worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldarg_0);
// new value to set
// new value to set worker.Emit(OpCodes.Ldarg_1);
worker.Emit(OpCodes.Ldarg_1);
// reference to field to set
// reference to field to set worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldflda, fd);
worker.Emit(OpCodes.Ldflda, fd);
// dirty bit
// dirty bit // 8 byte integer aka long
// 8 byte integer aka long worker.Emit(OpCodes.Ldc_I8, dirtyBit);
worker.Emit(OpCodes.Ldc_I8, dirtyBit);
if (fd.FieldType.Is<UnityEngine.GameObject>())
if (fd.FieldType.Is<UnityEngine.GameObject>()) {
{ // reference to netId Field to set
// reference to netId Field to set worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldflda, netFieldId);
worker.Emit(OpCodes.Ldflda, netFieldId);
worker.Emit(OpCodes.Call, weaverTypes.setSyncVarGameObjectReference);
worker.Emit(OpCodes.Call, weaverTypes.setSyncVarGameObjectReference); }
} else if (fd.FieldType.Is<NetworkIdentity>())
else if (fd.FieldType.Is<NetworkIdentity>()) {
{ // reference to netId Field to set
// reference to netId Field to set worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldflda, netFieldId);
worker.Emit(OpCodes.Ldflda, netFieldId);
worker.Emit(OpCodes.Call, weaverTypes.setSyncVarNetworkIdentityReference);
worker.Emit(OpCodes.Call, weaverTypes.setSyncVarNetworkIdentityReference); }
} else if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>())
else if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>()) {
{ // reference to netId Field to set
// reference to netId Field to set worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldflda, netFieldId);
worker.Emit(OpCodes.Ldflda, netFieldId);
MethodReference getFunc = weaverTypes.setSyncVarNetworkBehaviourReference.MakeGeneric(assembly.MainModule, fd.FieldType);
var getFunc = weaverTypes.setSyncVarNetworkBehaviourReference.MakeGeneric(assembly.MainModule, fd.FieldType); worker.Emit(OpCodes.Call, getFunc);
worker.Emit(OpCodes.Call, getFunc); }
} else
else {
{ // make generic version of SetSyncVar with field type
// make generic version of SetSyncVar with field type GenericInstanceMethod gm = new GenericInstanceMethod(weaverTypes.setSyncVarReference);
var gm = new GenericInstanceMethod(weaverTypes.setSyncVarReference); gm.GenericArguments.Add(fd.FieldType);
gm.GenericArguments.Add(fd.FieldType);
// invoke SetSyncVar
// invoke SetSyncVar worker.Emit(OpCodes.Call, gm);
worker.Emit(OpCodes.Call, gm); }
}
MethodDefinition hookMethod = GetHookMethod(td, fd, ref WeavingFailed);
var hookMethod = GetHookMethod(td, fd, ref WeavingFailed);
if (hookMethod != null)
if (hookMethod != null) {
{ //if (NetworkServer.localClientActive && !getSyncVarHookGuard(dirtyBit))
//if (NetworkServer.localClientActive && !getSyncVarHookGuard(dirtyBit)) Instruction label = worker.Create(OpCodes.Nop);
var label = worker.Create(OpCodes.Nop); worker.Emit(OpCodes.Call, weaverTypes.NetworkServerGetLocalClientActive);
worker.Emit(OpCodes.Call, weaverTypes.NetworkServerGetLocalClientActive); worker.Emit(OpCodes.Brfalse, label);
worker.Emit(OpCodes.Brfalse, label); worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldc_I8, dirtyBit);
worker.Emit(OpCodes.Ldc_I8, dirtyBit); worker.Emit(OpCodes.Call, weaverTypes.getSyncVarHookGuard);
worker.Emit(OpCodes.Call, weaverTypes.getSyncVarHookGuard); worker.Emit(OpCodes.Brtrue, label);
worker.Emit(OpCodes.Brtrue, label);
// setSyncVarHookGuard(dirtyBit, true);
// setSyncVarHookGuard(dirtyBit, true); worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldc_I8, dirtyBit);
worker.Emit(OpCodes.Ldc_I8, dirtyBit); worker.Emit(OpCodes.Ldc_I4_1);
worker.Emit(OpCodes.Ldc_I4_1); worker.Emit(OpCodes.Call, weaverTypes.setSyncVarHookGuard);
worker.Emit(OpCodes.Call, weaverTypes.setSyncVarHookGuard);
// call hook (oldValue, newValue)
// call hook (oldValue, newValue) // Generates: OnValueChanged(oldValue, value);
// Generates: OnValueChanged(oldValue, value); WriteCallHookMethodUsingArgument(worker, hookMethod, oldValue);
WriteCallHookMethodUsingArgument(worker, hookMethod, oldValue);
// setSyncVarHookGuard(dirtyBit, false);
// setSyncVarHookGuard(dirtyBit, false); worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldc_I8, dirtyBit);
worker.Emit(OpCodes.Ldc_I8, dirtyBit); worker.Emit(OpCodes.Ldc_I4_0);
worker.Emit(OpCodes.Ldc_I4_0); worker.Emit(OpCodes.Call, weaverTypes.setSyncVarHookGuard);
worker.Emit(OpCodes.Call, weaverTypes.setSyncVarHookGuard);
worker.Append(label);
worker.Append(label); }
}
worker.Append(endOfMethod);
worker.Append(endOfMethod);
worker.Emit(OpCodes.Ret);
worker.Emit(OpCodes.Ret);
set.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.In, fd.FieldType));
set.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.In, fd.FieldType)); set.SemanticsAttributes = MethodSemanticsAttributes.Setter;
set.SemanticsAttributes = MethodSemanticsAttributes.Setter;
return set;
return set; }
}
public void ProcessSyncVar(TypeDefinition td, FieldDefinition fd, Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds, long dirtyBit, ref bool WeavingFailed)
public void ProcessSyncVar(TypeDefinition td, FieldDefinition fd, Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds, long dirtyBit, ref bool WeavingFailed) {
{ string originalName = fd.Name;
var originalName = fd.Name;
// GameObject/NetworkIdentity SyncVars have a new field for netId
// GameObject/NetworkIdentity SyncVars have a new field for netId FieldDefinition netIdField = null;
FieldDefinition netIdField = null; // NetworkBehaviour has different field type than other NetworkIdentityFields
// NetworkBehaviour has different field type than other NetworkIdentityFields if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>())
if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>()) {
{ netIdField = new FieldDefinition($"___{fd.Name}NetId",
netIdField = new FieldDefinition($"___{fd.Name}NetId", FieldAttributes.Private,
FieldAttributes.Private, weaverTypes.Import<NetworkBehaviour.NetworkBehaviourSyncVar>());
weaverTypes.Import<NetworkBehaviour.NetworkBehaviourSyncVar>());
syncVarNetIds[fd] = netIdField;
syncVarNetIds[fd] = netIdField; }
} else if (fd.FieldType.IsNetworkIdentityField())
else if (fd.FieldType.IsNetworkIdentityField()) {
{ netIdField = new FieldDefinition($"___{fd.Name}NetId",
netIdField = new FieldDefinition($"___{fd.Name}NetId", FieldAttributes.Private,
FieldAttributes.Private, weaverTypes.Import<uint>());
weaverTypes.Import<uint>());
syncVarNetIds[fd] = netIdField;
syncVarNetIds[fd] = netIdField; }
}
MethodDefinition get = GenerateSyncVarGetter(fd, originalName, netIdField);
var get = GenerateSyncVarGetter(fd, originalName, netIdField); MethodDefinition set = GenerateSyncVarSetter(td, fd, originalName, dirtyBit, netIdField, ref WeavingFailed);
var set = GenerateSyncVarSetter(td, fd, originalName, dirtyBit, netIdField, ref WeavingFailed);
//NOTE: is property even needed? Could just use a setter function?
//NOTE: is property even needed? Could just use a setter function? //create the property
//create the property PropertyDefinition propertyDefinition = new PropertyDefinition($"Network{originalName}", PropertyAttributes.None, fd.FieldType)
var propertyDefinition = new PropertyDefinition($"Network{originalName}", PropertyAttributes.None, fd.FieldType) {
{ GetMethod = get,
GetMethod = get, SetMethod = set
SetMethod = set };
};
//add the methods and property to the type.
//add the methods and property to the type. td.Methods.Add(get);
td.Methods.Add(get); td.Methods.Add(set);
td.Methods.Add(set); td.Properties.Add(propertyDefinition);
td.Properties.Add(propertyDefinition); syncVarAccessLists.replacementSetterProperties[fd] = set;
syncVarAccessLists.replacementSetterProperties[fd] = set;
// replace getter field if GameObject/NetworkIdentity so it uses
// replace getter field if GameObject/NetworkIdentity so it uses // netId instead
// netId instead // -> only for GameObjects, otherwise an int syncvar's getter would
// -> only for GameObjects, otherwise an int syncvar's getter would // end up in recursion.
// end up in recursion. if (fd.FieldType.IsNetworkIdentityField())
if (fd.FieldType.IsNetworkIdentityField()) {
{ syncVarAccessLists.replacementGetterProperties[fd] = get;
syncVarAccessLists.replacementGetterProperties[fd] = get; }
} }
}
public (List<FieldDefinition> syncVars, Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds) ProcessSyncVars(TypeDefinition td, ref bool WeavingFailed)
public (List<FieldDefinition> syncVars, Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds) ProcessSyncVars(TypeDefinition td, ref bool WeavingFailed) {
{ List<FieldDefinition> syncVars = new List<FieldDefinition>();
var syncVars = new List<FieldDefinition>(); Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds = new Dictionary<FieldDefinition, FieldDefinition>();
var syncVarNetIds = new Dictionary<FieldDefinition, FieldDefinition>();
// the mapping of dirtybits to sync-vars is implicit in the order of the fields here. this order is recorded in m_replacementProperties.
// 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
// start assigning syncvars at the place the base class stopped, if any int dirtyBitCounter = syncVarAccessLists.GetSyncVarStart(td.BaseType.FullName);
var dirtyBitCounter = syncVarAccessLists.GetSyncVarStart(td.BaseType.FullName);
// find syncvars
// find syncvars foreach (FieldDefinition fd in td.Fields)
foreach (var fd in td.Fields) {
{ if (fd.HasCustomAttribute<SyncVarAttribute>())
if (fd.HasCustomAttribute<SyncVarAttribute>()) {
{ if ((fd.Attributes & FieldAttributes.Static) != 0)
if ((fd.Attributes & FieldAttributes.Static) != 0) {
{ Log.Error($"{fd.Name} cannot be static", fd);
Log.Error($"{fd.Name} cannot be static", fd); WeavingFailed = true;
WeavingFailed = true; continue;
continue; }
}
if (fd.FieldType.IsArray)
if (fd.FieldType.IsArray) {
{ Log.Error($"{fd.Name} has invalid type. Use SyncLists instead of arrays", fd);
Log.Error($"{fd.Name} has invalid type. Use SyncLists instead of arrays", fd); WeavingFailed = true;
WeavingFailed = true; continue;
continue; }
}
if (SyncObjectInitializer.ImplementsSyncObject(fd.FieldType))
if (SyncObjectInitializer.ImplementsSyncObject(fd.FieldType)) {
{ Log.Warning($"{fd.Name} has [SyncVar] attribute. SyncLists should not be marked with SyncVar", fd);
Log.Warning($"{fd.Name} has [SyncVar] attribute. SyncLists should not be marked with SyncVar", fd); }
} else
else {
{ syncVars.Add(fd);
syncVars.Add(fd);
ProcessSyncVar(td, fd, syncVarNetIds, 1L << dirtyBitCounter, ref WeavingFailed);
ProcessSyncVar(td, fd, syncVarNetIds, 1L << dirtyBitCounter, ref WeavingFailed); dirtyBitCounter += 1;
dirtyBitCounter += 1;
if (dirtyBitCounter > SyncVarLimit)
if (dirtyBitCounter > SyncVarLimit) {
{ Log.Error($"{td.Name} has > {SyncVarLimit} SyncVars. Consider refactoring your class into multiple components", td);
Log.Error($"{td.Name} has > {SyncVarLimit} SyncVars. Consider refactoring your class into multiple components", td); WeavingFailed = true;
WeavingFailed = true; continue;
continue; }
} }
} }
} }
}
// add all the new SyncVar __netId fields
// add all the new SyncVar __netId fields foreach (FieldDefinition fd in syncVarNetIds.Values)
foreach (var fd in syncVarNetIds.Values) {
{ td.Fields.Add(fd);
td.Fields.Add(fd); }
} syncVarAccessLists.SetNumSyncVars(td.FullName, syncVars.Count);
syncVarAccessLists.SetNumSyncVars(td.FullName, syncVars.Count);
return (syncVars, syncVarNetIds);
return (syncVars, syncVarNetIds); }
}
public void WriteCallHookMethodUsingArgument(ILProcessor worker, MethodDefinition hookMethod, VariableDefinition oldValue)
public void WriteCallHookMethodUsingArgument(ILProcessor worker, MethodDefinition hookMethod, VariableDefinition oldValue) => WriteCallHookMethod(worker, hookMethod, oldValue, null); {
WriteCallHookMethod(worker, hookMethod, oldValue, null);
public void WriteCallHookMethodUsingField(ILProcessor worker, MethodDefinition hookMethod, VariableDefinition oldValue, FieldDefinition newValue, ref bool WeavingFailed) }
{
if (newValue == null) public void WriteCallHookMethodUsingField(ILProcessor worker, MethodDefinition hookMethod, VariableDefinition oldValue, FieldDefinition newValue, ref bool WeavingFailed)
{ {
Log.Error("NewValue field was null when writing SyncVar hook"); if (newValue == null)
WeavingFailed = true; {
} Log.Error("NewValue field was null when writing SyncVar hook");
WeavingFailed = true;
WriteCallHookMethod(worker, hookMethod, oldValue, newValue); }
}
WriteCallHookMethod(worker, hookMethod, oldValue, newValue);
private void WriteCallHookMethod(ILProcessor worker, MethodDefinition hookMethod, VariableDefinition oldValue, FieldDefinition newValue) }
{
WriteStartFunctionCall(); void WriteCallHookMethod(ILProcessor worker, MethodDefinition hookMethod, VariableDefinition oldValue, FieldDefinition newValue)
{
// write args WriteStartFunctionCall();
WriteOldValue();
WriteNewValue(); // write args
WriteOldValue();
WriteEndFunctionCall(); WriteNewValue();
WriteEndFunctionCall();
// *** Local functions used to write OpCodes ***
// Local functions have access to function variables, no need to pass in args
// *** Local functions used to write OpCodes ***
void WriteOldValue() => worker.Emit(OpCodes.Ldloc, oldValue); // Local functions have access to function variables, no need to pass in args
void WriteNewValue() void WriteOldValue()
{ {
// write arg1 or this.field worker.Emit(OpCodes.Ldloc, oldValue);
if (newValue == null) }
{
worker.Emit(OpCodes.Ldarg_1); void WriteNewValue()
} {
else // write arg1 or this.field
{ if (newValue == null)
// this. {
worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldarg_1);
// syncvar.get }
worker.Emit(OpCodes.Ldfld, newValue); else
} {
} // this.
worker.Emit(OpCodes.Ldarg_0);
// Writes this before method if it is not static // syncvar.get
void WriteStartFunctionCall() worker.Emit(OpCodes.Ldfld, newValue);
{ }
// don't add this (Ldarg_0) if method is static }
if (!hookMethod.IsStatic)
{ // Writes this before method if it is not static
// this before method call void WriteStartFunctionCall()
// e.g. this.onValueChanged {
worker.Emit(OpCodes.Ldarg_0); // don't add this (Ldarg_0) if method is static
} if (!hookMethod.IsStatic)
} {
// this before method call
// Calls method // e.g. this.onValueChanged
void WriteEndFunctionCall() worker.Emit(OpCodes.Ldarg_0);
{ }
// only use Callvirt when not static }
var opcode = hookMethod.IsStatic ? OpCodes.Call : OpCodes.Callvirt;
worker.Emit(opcode, hookMethod); // Calls method
} void WriteEndFunctionCall()
} {
} // only use Callvirt when not static
OpCode opcode = hookMethod.IsStatic ? OpCodes.Call : OpCodes.Callvirt;
worker.Emit(opcode, hookMethod);
}
}
}
} }

View File

@ -3,60 +3,61 @@ using Mono.Cecil.Cil;
namespace Mirror.Weaver namespace Mirror.Weaver
{ {
// Processes [TargetRpc] methods in NetworkBehaviour // Processes [TargetRpc] methods in NetworkBehaviour
public static class TargetRpcProcessor public static class TargetRpcProcessor
{ {
// helper functions to check if the method has a NetworkConnection parameter // helper functions to check if the method has a NetworkConnection parameter
public static bool HasNetworkConnectionParameter(MethodDefinition md) => md.Parameters.Count > 0 && public static bool HasNetworkConnectionParameter(MethodDefinition md)
md.Parameters[0].ParameterType.Is<NetworkConnection>(); {
return md.Parameters.Count > 0 &&
md.Parameters[0].ParameterType.Is<NetworkConnection>();
}
public static MethodDefinition ProcessTargetRpcInvoke(WeaverTypes weaverTypes, Readers readers, Logger Log, TypeDefinition td, MethodDefinition md, MethodDefinition rpcCallFunc, ref bool WeavingFailed) public static MethodDefinition ProcessTargetRpcInvoke(WeaverTypes weaverTypes, Readers readers, Logger Log, TypeDefinition td, MethodDefinition md, MethodDefinition rpcCallFunc, ref bool WeavingFailed)
{ {
var rpc = new MethodDefinition(Weaver.InvokeRpcPrefix + md.Name, MethodAttributes.Family | MethodDefinition rpc = new MethodDefinition(Weaver.InvokeRpcPrefix + md.Name, MethodAttributes.Family |
MethodAttributes.Static | MethodAttributes.Static |
MethodAttributes.HideBySig, MethodAttributes.HideBySig,
weaverTypes.Import(typeof(void))); weaverTypes.Import(typeof(void)));
var worker = rpc.Body.GetILProcessor(); ILProcessor worker = rpc.Body.GetILProcessor();
var label = worker.Create(OpCodes.Nop); Instruction label = worker.Create(OpCodes.Nop);
NetworkBehaviourProcessor.WriteClientActiveCheck(worker, weaverTypes, md.Name, label, "TargetRPC"); NetworkBehaviourProcessor.WriteClientActiveCheck(worker, weaverTypes, md.Name, label, "TargetRPC");
// setup for reader // setup for reader
worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Castclass, td); worker.Emit(OpCodes.Castclass, td);
// NetworkConnection parameter is optional // NetworkConnection parameter is optional
if (HasNetworkConnectionParameter(md)) if (HasNetworkConnectionParameter(md))
{ {
// on server, the NetworkConnection parameter is a connection to client. // on server, the NetworkConnection parameter is a connection to client.
// when the rpc is invoked on the client, it still has the same // when the rpc is invoked on the client, it still has the same
// function signature. we pass in the connection to server, // function signature. we pass in the connection to server,
// which is cleaner than just passing null) // which is cleaner than just passing null)
//NetworkClient.readyconnection //NetworkClient.readyconnection
// //
// TODO // TODO
// a) .connectionToServer = best solution. no doubt. // a) .connectionToServer = best solution. no doubt.
// b) NetworkClient.connection for now. add TODO to not use static later. // b) NetworkClient.connection for now. add TODO to not use static later.
worker.Emit(OpCodes.Call, weaverTypes.NetworkClientConnectionReference); worker.Emit(OpCodes.Call, weaverTypes.NetworkClientConnectionReference);
} }
// process reader parameters and skip first one if first one is NetworkConnection // process reader parameters and skip first one if first one is NetworkConnection
if (!NetworkBehaviourProcessor.ReadArguments(md, readers, Log, worker, RemoteCallType.TargetRpc, ref WeavingFailed)) if (!NetworkBehaviourProcessor.ReadArguments(md, readers, Log, worker, RemoteCallType.TargetRpc, ref WeavingFailed))
{ return null;
return null;
}
// invoke actual command function // invoke actual command function
worker.Emit(OpCodes.Callvirt, rpcCallFunc); worker.Emit(OpCodes.Callvirt, rpcCallFunc);
worker.Emit(OpCodes.Ret); worker.Emit(OpCodes.Ret);
NetworkBehaviourProcessor.AddInvokeParameters(weaverTypes, rpc.Parameters); NetworkBehaviourProcessor.AddInvokeParameters(weaverTypes, rpc.Parameters);
td.Methods.Add(rpc); td.Methods.Add(rpc);
return rpc; return rpc;
} }
/* generates code like: /* generates code like:
public void TargetTest (NetworkConnection conn, int param) public void TargetTest (NetworkConnection conn, int param)
{ {
NetworkWriter writer = new NetworkWriter (); NetworkWriter writer = new NetworkWriter ();
@ -89,48 +90,46 @@ namespace Mirror.Weaver
correctly in dependent assemblies correctly in dependent assemblies
*/ */
public static MethodDefinition ProcessTargetRpcCall(WeaverTypes weaverTypes, Writers writers, Logger Log, TypeDefinition td, MethodDefinition md, CustomAttribute targetRpcAttr, ref bool WeavingFailed) public static MethodDefinition ProcessTargetRpcCall(WeaverTypes weaverTypes, Writers writers, Logger Log, TypeDefinition td, MethodDefinition md, CustomAttribute targetRpcAttr, ref bool WeavingFailed)
{ {
var rpc = MethodProcessor.SubstituteMethod(Log, td, md, ref WeavingFailed); MethodDefinition rpc = MethodProcessor.SubstituteMethod(Log, td, md, ref WeavingFailed);
var worker = md.Body.GetILProcessor(); ILProcessor worker = md.Body.GetILProcessor();
NetworkBehaviourProcessor.WriteSetupLocals(worker, weaverTypes); NetworkBehaviourProcessor.WriteSetupLocals(worker, weaverTypes);
NetworkBehaviourProcessor.WriteCreateWriter(worker, weaverTypes); NetworkBehaviourProcessor.WriteCreateWriter(worker, weaverTypes);
// write all the arguments that the user passed to the TargetRpc call // write all the arguments that the user passed to the TargetRpc call
// (skip first one if first one is NetworkConnection) // (skip first one if first one is NetworkConnection)
if (!NetworkBehaviourProcessor.WriteArguments(worker, writers, Log, md, RemoteCallType.TargetRpc, ref WeavingFailed)) if (!NetworkBehaviourProcessor.WriteArguments(worker, writers, Log, md, RemoteCallType.TargetRpc, ref WeavingFailed))
{ return null;
return null;
}
// invoke SendInternal and return // invoke SendInternal and return
// this // this
worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldarg_0);
if (HasNetworkConnectionParameter(md)) if (HasNetworkConnectionParameter(md))
{ {
// connection // connection
worker.Emit(OpCodes.Ldarg_1); worker.Emit(OpCodes.Ldarg_1);
} }
else else
{ {
// null // null
worker.Emit(OpCodes.Ldnull); worker.Emit(OpCodes.Ldnull);
} }
// pass full function name to avoid ClassA.Func <-> ClassB.Func collisions // pass full function name to avoid ClassA.Func <-> ClassB.Func collisions
worker.Emit(OpCodes.Ldstr, md.FullName); worker.Emit(OpCodes.Ldstr, md.FullName);
// writer // writer
worker.Emit(OpCodes.Ldloc_0); worker.Emit(OpCodes.Ldloc_0);
worker.Emit(OpCodes.Ldc_I4, targetRpcAttr.GetField("channel", 0)); worker.Emit(OpCodes.Ldc_I4, targetRpcAttr.GetField("channel", 0));
worker.Emit(OpCodes.Callvirt, weaverTypes.sendTargetRpcInternal); worker.Emit(OpCodes.Callvirt, weaverTypes.sendTargetRpcInternal);
NetworkBehaviourProcessor.WriteRecycleWriter(worker, weaverTypes); NetworkBehaviourProcessor.WriteRecycleWriter(worker, weaverTypes);
worker.Emit(OpCodes.Ret); worker.Emit(OpCodes.Ret);
return rpc; return rpc;
} }
} }
} }

View File

@ -1,386 +1,383 @@
using System;
using System.Collections.Generic;
using Mono.Cecil; using Mono.Cecil;
using Mono.Cecil.Cil; using Mono.Cecil.Cil;
// to use Mono.Cecil.Rocks here, we need to 'override references' in the // to use Mono.Cecil.Rocks here, we need to 'override references' in the
// Unity.Mirror.CodeGen assembly definition file in the Editor, and add CecilX.Rocks. // Unity.Mirror.CodeGen assembly definition file in the Editor, and add CecilX.Rocks.
// otherwise we get an unknown import exception. // otherwise we get an unknown import exception.
using Mono.Cecil.Rocks; using Mono.Cecil.Rocks;
using System;
using System.Collections.Generic;
namespace Mirror.Weaver namespace Mirror.Weaver
{ {
// not static, because ILPostProcessor is multithreaded // not static, because ILPostProcessor is multithreaded
public class Readers public class Readers
{ {
// Readers are only for this assembly. // Readers are only for this assembly.
// can't be used from another assembly, otherwise we will get: // can't be used from another assembly, otherwise we will get:
// "System.ArgumentException: Member ... is declared in another module and needs to be imported" // "System.ArgumentException: Member ... is declared in another module and needs to be imported"
private readonly AssemblyDefinition assembly; AssemblyDefinition assembly;
private readonly WeaverTypes weaverTypes; WeaverTypes weaverTypes;
private readonly TypeDefinition GeneratedCodeClass; TypeDefinition GeneratedCodeClass;
private readonly Logger Log; Logger Log;
private readonly Dictionary<TypeReference, MethodReference> readFuncs =
new Dictionary<TypeReference, MethodReference>(new TypeReferenceComparer());
public Readers(AssemblyDefinition assembly, WeaverTypes weaverTypes, TypeDefinition GeneratedCodeClass, Logger Log) Dictionary<TypeReference, MethodReference> readFuncs =
{ new Dictionary<TypeReference, MethodReference>(new TypeReferenceComparer());
this.assembly = assembly;
this.weaverTypes = weaverTypes;
this.GeneratedCodeClass = GeneratedCodeClass;
this.Log = Log;
}
internal void Register(TypeReference dataType, MethodReference methodReference) public Readers(AssemblyDefinition assembly, WeaverTypes weaverTypes, TypeDefinition GeneratedCodeClass, Logger Log)
{ {
if (readFuncs.ContainsKey(dataType)) this.assembly = assembly;
{ this.weaverTypes = weaverTypes;
// TODO enable this again later. this.GeneratedCodeClass = GeneratedCodeClass;
// Reader has some obsolete functions that were renamed. this.Log = Log;
// 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 internal void Register(TypeReference dataType, MethodReference methodReference)
var imported = assembly.MainModule.ImportReference(dataType); {
readFuncs[imported] = 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);
}
private void RegisterReadFunc(TypeReference typeReference, MethodDefinition newReaderFunc) // 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);
Register(typeReference, newReaderFunc); readFuncs[imported] = methodReference;
GeneratedCodeClass.Methods.Add(newReaderFunc); }
}
// Finds existing reader for type, if non exists trys to create one void RegisterReadFunc(TypeReference typeReference, MethodDefinition newReaderFunc)
public MethodReference GetReadFunc(TypeReference variable, ref bool WeavingFailed) {
{ Register(typeReference, newReaderFunc);
if (readFuncs.TryGetValue(variable, out var foundFunc)) GeneratedCodeClass.Methods.Add(newReaderFunc);
{ }
return foundFunc;
}
var importedVariable = assembly.MainModule.ImportReference(variable); // Finds existing reader for type, if non exists trys to create one
return GenerateReader(importedVariable, ref WeavingFailed); public MethodReference GetReadFunc(TypeReference variable, ref bool WeavingFailed)
} {
if (readFuncs.TryGetValue(variable, out MethodReference foundFunc))
return foundFunc;
private MethodReference GenerateReader(TypeReference variableReference, ref bool WeavingFailed) TypeReference importedVariable = assembly.MainModule.ImportReference(variable);
{ return GenerateReader(importedVariable, ref 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); 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;
}
var variableDefinition = variableReference.Resolve(); return GenerateReadCollection(variableReference, variableReference.GetElementType(), nameof(NetworkReaderExtensions.ReadArray), ref WeavingFailed);
}
// check if the type is completely invalid TypeDefinition variableDefinition = variableReference.Resolve();
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 // check if the type is completely invalid
if (variableDefinition.IsEnum) if (variableDefinition == null)
{ {
return GenerateEnumReadFunc(variableReference, ref WeavingFailed); Log.Error($"{variableReference.Name} is not a supported type", variableReference);
} WeavingFailed = true;
else if (variableDefinition.Is(typeof(ArraySegment<>))) return null;
{ }
return GenerateArraySegmentReadFunc(variableReference, ref WeavingFailed); else if (variableReference.IsByReference)
} {
else if (variableDefinition.Is(typeof(List<>))) // error??
{ Log.Error($"Cannot pass type {variableReference.Name} by reference", variableReference);
var genericInstance = (GenericInstanceType)variableReference; WeavingFailed = true;
var elementType = genericInstance.GenericArguments[0]; return null;
}
return GenerateReadCollection(variableReference, elementType, nameof(NetworkReaderExtensions.ReadList), ref WeavingFailed); // use existing func for known types
} if (variableDefinition.IsEnum)
else if (variableReference.IsDerivedFrom<NetworkBehaviour>()) {
{ return GenerateEnumReadFunc(variableReference, ref WeavingFailed);
return GetNetworkBehaviourReader(variableReference); }
} 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];
// check if reader generation is applicable on this type return GenerateReadCollection(variableReference, elementType, nameof(NetworkReaderExtensions.ReadList), ref WeavingFailed);
if (variableDefinition.IsDerivedFrom<UnityEngine.Component>()) }
{ else if (variableReference.IsDerivedFrom<NetworkBehaviour>())
Log.Error($"Cannot generate reader for component type {variableReference.Name}. Use a supported type or provide a custom reader", variableReference); {
WeavingFailed = true; return GetNetworkBehaviourReader(variableReference);
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); // 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;
}
private MethodReference GetNetworkBehaviourReader(TypeReference variableReference) return GenerateClassOrStructReadFunction(variableReference, ref WeavingFailed);
{ }
// uses generic ReadNetworkBehaviour rather than having weaver create one for each NB
var generic = weaverTypes.readNetworkBehaviourGeneric;
var readFunc = generic.MakeGeneric(assembly.MainModule, variableReference); MethodReference GetNetworkBehaviourReader(TypeReference variableReference)
{
// uses generic ReadNetworkBehaviour rather than having weaver create one for each NB
MethodReference generic = weaverTypes.readNetworkBehaviourGeneric;
// register function so it is added to Reader<T> MethodReference readFunc = generic.MakeGeneric(assembly.MainModule, variableReference);
// use Register instead of RegisterWriteFunc because this is not a generated function
Register(variableReference, readFunc);
return readFunc; // register function so it is added to Reader<T>
} // use Register instead of RegisterWriteFunc because this is not a generated function
Register(variableReference, readFunc);
private MethodDefinition GenerateEnumReadFunc(TypeReference variable, ref bool WeavingFailed) return readFunc;
{ }
var readerFunc = GenerateReaderFunction(variable);
var worker = readerFunc.Body.GetILProcessor(); MethodDefinition GenerateEnumReadFunc(TypeReference variable, ref bool WeavingFailed)
{
MethodDefinition readerFunc = GenerateReaderFunction(variable);
worker.Emit(OpCodes.Ldarg_0); ILProcessor worker = readerFunc.Body.GetILProcessor();
var underlyingType = variable.Resolve().GetEnumUnderlyingType(); worker.Emit(OpCodes.Ldarg_0);
var underlyingFunc = GetReadFunc(underlyingType, ref WeavingFailed);
worker.Emit(OpCodes.Call, underlyingFunc); TypeReference underlyingType = variable.Resolve().GetEnumUnderlyingType();
worker.Emit(OpCodes.Ret); MethodReference underlyingFunc = GetReadFunc(underlyingType, ref WeavingFailed);
return readerFunc;
}
private MethodDefinition GenerateArraySegmentReadFunc(TypeReference variable, ref bool WeavingFailed) worker.Emit(OpCodes.Call, underlyingFunc);
{ worker.Emit(OpCodes.Ret);
var genericInstance = (GenericInstanceType)variable; return readerFunc;
var elementType = genericInstance.GenericArguments[0]; }
var readerFunc = GenerateReaderFunction(variable); MethodDefinition GenerateArraySegmentReadFunc(TypeReference variable, ref bool WeavingFailed)
{
GenericInstanceType genericInstance = (GenericInstanceType)variable;
TypeReference elementType = genericInstance.GenericArguments[0];
var worker = readerFunc.Body.GetILProcessor(); MethodDefinition readerFunc = GenerateReaderFunction(variable);
// $array = reader.Read<[T]>() ILProcessor worker = readerFunc.Body.GetILProcessor();
var arrayType = elementType.MakeArrayType();
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Call, GetReadFunc(arrayType, ref WeavingFailed));
// return new ArraySegment<T>($array); // $array = reader.Read<[T]>()
worker.Emit(OpCodes.Newobj, weaverTypes.ArraySegmentConstructorReference.MakeHostInstanceGeneric(assembly.MainModule, genericInstance)); ArrayType arrayType = elementType.MakeArrayType();
worker.Emit(OpCodes.Ret); worker.Emit(OpCodes.Ldarg_0);
return readerFunc; worker.Emit(OpCodes.Call, GetReadFunc(arrayType, ref WeavingFailed));
}
private MethodDefinition GenerateReaderFunction(TypeReference variable) // return new ArraySegment<T>($array);
{ worker.Emit(OpCodes.Newobj, weaverTypes.ArraySegmentConstructorReference.MakeHostInstanceGeneric(assembly.MainModule, genericInstance));
var functionName = $"_Read_{variable.FullName}"; worker.Emit(OpCodes.Ret);
return readerFunc;
}
// create new reader for this type MethodDefinition GenerateReaderFunction(TypeReference variable)
var readerFunc = new MethodDefinition(functionName, {
MethodAttributes.Public | string functionName = $"_Read_{variable.FullName}";
MethodAttributes.Static |
MethodAttributes.HideBySig,
variable);
readerFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, weaverTypes.Import<NetworkReader>())); // create new reader for this type
readerFunc.Body.InitLocals = true; MethodDefinition readerFunc = new MethodDefinition(functionName,
RegisterReadFunc(variable, readerFunc); MethodAttributes.Public |
MethodAttributes.Static |
MethodAttributes.HideBySig,
variable);
return readerFunc; readerFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, weaverTypes.Import<NetworkReader>()));
} readerFunc.Body.InitLocals = true;
RegisterReadFunc(variable, readerFunc);
private MethodDefinition GenerateReadCollection(TypeReference variable, TypeReference elementType, string readerFunction, ref bool WeavingFailed) return readerFunc;
{ }
var readerFunc = GenerateReaderFunction(variable);
// generate readers for the element
GetReadFunc(elementType, ref WeavingFailed);
var module = assembly.MainModule; MethodDefinition GenerateReadCollection(TypeReference variable, TypeReference elementType, string readerFunction, ref bool WeavingFailed)
var readerExtensions = module.ImportReference(typeof(NetworkReaderExtensions)); {
var listReader = Resolvers.ResolveMethod(readerExtensions, assembly, Log, readerFunction, ref WeavingFailed); MethodDefinition readerFunc = GenerateReaderFunction(variable);
// generate readers for the element
GetReadFunc(elementType, ref WeavingFailed);
var methodRef = new GenericInstanceMethod(listReader); ModuleDefinition module = assembly.MainModule;
methodRef.GenericArguments.Add(elementType); TypeReference readerExtensions = module.ImportReference(typeof(NetworkReaderExtensions));
MethodReference listReader = Resolvers.ResolveMethod(readerExtensions, assembly, Log, readerFunction, ref WeavingFailed);
// generates GenericInstanceMethod methodRef = new GenericInstanceMethod(listReader);
// return reader.ReadList<T>(); methodRef.GenericArguments.Add(elementType);
var worker = readerFunc.Body.GetILProcessor(); // generates
worker.Emit(OpCodes.Ldarg_0); // reader // return reader.ReadList<T>();
worker.Emit(OpCodes.Call, methodRef); // Read
worker.Emit(OpCodes.Ret); ILProcessor worker = readerFunc.Body.GetILProcessor();
worker.Emit(OpCodes.Ldarg_0); // reader
worker.Emit(OpCodes.Call, methodRef); // Read
return readerFunc; worker.Emit(OpCodes.Ret);
}
private MethodDefinition GenerateClassOrStructReadFunction(TypeReference variable, ref bool WeavingFailed) return readerFunc;
{ }
var readerFunc = GenerateReaderFunction(variable);
// create local for return value MethodDefinition GenerateClassOrStructReadFunction(TypeReference variable, ref bool WeavingFailed)
readerFunc.Body.Variables.Add(new VariableDefinition(variable)); {
MethodDefinition readerFunc = GenerateReaderFunction(variable);
var worker = readerFunc.Body.GetILProcessor(); // create local for return value
readerFunc.Body.Variables.Add(new VariableDefinition(variable));
var td = variable.Resolve(); ILProcessor worker = readerFunc.Body.GetILProcessor();
if (!td.IsValueType) TypeDefinition td = variable.Resolve();
{
GenerateNullCheck(worker, ref WeavingFailed);
}
CreateNew(variable, worker, td, ref WeavingFailed); if (!td.IsValueType)
ReadAllFields(variable, worker, ref WeavingFailed); GenerateNullCheck(worker, ref WeavingFailed);
worker.Emit(OpCodes.Ldloc_0); CreateNew(variable, worker, td, ref WeavingFailed);
worker.Emit(OpCodes.Ret); ReadAllFields(variable, worker, ref WeavingFailed);
return readerFunc;
}
private void GenerateNullCheck(ILProcessor worker, ref bool WeavingFailed) worker.Emit(OpCodes.Ldloc_0);
{ worker.Emit(OpCodes.Ret);
// if (!reader.ReadBoolean()) { return readerFunc;
// return null; }
// }
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Call, GetReadFunc(weaverTypes.Import<bool>(), ref WeavingFailed));
var labelEmptyArray = worker.Create(OpCodes.Nop); void GenerateNullCheck(ILProcessor worker, ref bool WeavingFailed)
worker.Emit(OpCodes.Brtrue, labelEmptyArray); {
// return null // if (!reader.ReadBoolean()) {
worker.Emit(OpCodes.Ldnull); // return null;
worker.Emit(OpCodes.Ret); // }
worker.Append(labelEmptyArray); worker.Emit(OpCodes.Ldarg_0);
} worker.Emit(OpCodes.Call, GetReadFunc(weaverTypes.Import<bool>(), ref WeavingFailed));
// Initialize the local variable with a new instance Instruction labelEmptyArray = worker.Create(OpCodes.Nop);
private void CreateNew(TypeReference variable, ILProcessor worker, TypeDefinition td, ref bool WeavingFailed) worker.Emit(OpCodes.Brtrue, labelEmptyArray);
{ // return null
if (variable.IsValueType) worker.Emit(OpCodes.Ldnull);
{ worker.Emit(OpCodes.Ret);
// structs are created with Initobj worker.Append(labelEmptyArray);
worker.Emit(OpCodes.Ldloca, 0); }
worker.Emit(OpCodes.Initobj, variable);
}
else if (td.IsDerivedFrom<UnityEngine.ScriptableObject>())
{
var 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
var 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;
}
var ctorRef = assembly.MainModule.ImportReference(ctor); // 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;
}
worker.Emit(OpCodes.Newobj, ctorRef); MethodReference ctorRef = assembly.MainModule.ImportReference(ctor);
worker.Emit(OpCodes.Stloc_0);
}
}
private void ReadAllFields(TypeReference variable, ILProcessor worker, ref bool WeavingFailed) worker.Emit(OpCodes.Newobj, ctorRef);
{ worker.Emit(OpCodes.Stloc_0);
foreach (var field in variable.FindAllPublicFields()) }
{ }
// mismatched ldloca/ldloc for struct/class combinations is invalid IL, which causes crash at runtime
var opcode = variable.IsValueType ? OpCodes.Ldloca : OpCodes.Ldloc;
worker.Emit(opcode, 0);
var 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;
}
var fieldRef = assembly.MainModule.ImportReference(field);
worker.Emit(OpCodes.Stfld, fieldRef); 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);
// Save a delegate for each one of the readers into Reader<T>.read worker.Emit(OpCodes.Stfld, fieldRef);
internal void InitializeReaders(ILProcessor worker) }
{ }
var module = assembly.MainModule;
var genericReaderClassRef = module.ImportReference(typeof(Reader<>)); // Save a delegate for each one of the readers into Reader<T>.read
internal void InitializeReaders(ILProcessor worker)
{
ModuleDefinition module = assembly.MainModule;
var fieldInfo = typeof(Reader<>).GetField(nameof(Reader<object>.read)); TypeReference genericReaderClassRef = module.ImportReference(typeof(Reader<>));
var fieldRef = module.ImportReference(fieldInfo);
var networkReaderRef = module.ImportReference(typeof(NetworkReader));
var funcRef = module.ImportReference(typeof(Func<,>));
var funcConstructorRef = module.ImportReference(typeof(Func<,>).GetConstructors()[0]);
foreach (var kvp in readFuncs) System.Reflection.FieldInfo fieldInfo = typeof(Reader<>).GetField(nameof(Reader<object>.read));
{ FieldReference fieldRef = module.ImportReference(fieldInfo);
var targetType = kvp.Key; TypeReference networkReaderRef = module.ImportReference(typeof(NetworkReader));
var readFunc = kvp.Value; TypeReference funcRef = module.ImportReference(typeof(Func<,>));
MethodReference funcConstructorRef = module.ImportReference(typeof(Func<,>).GetConstructors()[0]);
// create a Func<NetworkReader, T> delegate foreach (KeyValuePair<TypeReference, MethodReference> kvp in readFuncs)
worker.Emit(OpCodes.Ldnull); {
worker.Emit(OpCodes.Ldftn, readFunc); TypeReference targetType = kvp.Key;
var funcGenericInstance = funcRef.MakeGenericInstanceType(networkReaderRef, targetType); MethodReference readFunc = kvp.Value;
var funcConstructorInstance = funcConstructorRef.MakeHostInstanceGeneric(assembly.MainModule, funcGenericInstance);
worker.Emit(OpCodes.Newobj, funcConstructorInstance);
// save it in Reader<T>.read // create a Func<NetworkReader, T> delegate
var genericInstance = genericReaderClassRef.MakeGenericInstanceType(targetType); worker.Emit(OpCodes.Ldnull);
var specializedField = fieldRef.SpecializeField(assembly.MainModule, genericInstance); worker.Emit(OpCodes.Ldftn, readFunc);
worker.Emit(OpCodes.Stsfld, specializedField); 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);
}
}
}
} }

View File

@ -8,82 +8,82 @@ using Mono.Cecil;
namespace Mirror.Weaver namespace Mirror.Weaver
{ {
public static class Resolvers public static class Resolvers
{ {
public static MethodReference ResolveMethod(TypeReference tr, AssemblyDefinition assembly, Logger Log, string name, ref bool WeavingFailed) public static MethodReference ResolveMethod(TypeReference tr, AssemblyDefinition assembly, Logger Log, string name, ref bool WeavingFailed)
{ {
if (tr == null) if (tr == null)
{ {
Log.Error($"Cannot resolve method {name} without a class"); Log.Error($"Cannot resolve method {name} without a class");
WeavingFailed = true; WeavingFailed = true;
return null; return null;
} }
var method = ResolveMethod(tr, assembly, Log, m => m.Name == name, ref WeavingFailed); MethodReference method = ResolveMethod(tr, assembly, Log, m => m.Name == name, ref WeavingFailed);
if (method == null) if (method == null)
{ {
Log.Error($"Method not found with name {name} in type {tr.Name}", tr); Log.Error($"Method not found with name {name} in type {tr.Name}", tr);
WeavingFailed = true; WeavingFailed = true;
} }
return method; return method;
} }
public static MethodReference ResolveMethod(TypeReference t, AssemblyDefinition assembly, Logger Log, System.Func<MethodDefinition, bool> predicate, ref bool WeavingFailed) public static MethodReference ResolveMethod(TypeReference t, AssemblyDefinition assembly, Logger Log, System.Func<MethodDefinition, bool> predicate, ref bool WeavingFailed)
{ {
foreach (var methodRef in t.Resolve().Methods) foreach (MethodDefinition methodRef in t.Resolve().Methods)
{ {
if (predicate(methodRef)) if (predicate(methodRef))
{ {
return assembly.MainModule.ImportReference(methodRef); return assembly.MainModule.ImportReference(methodRef);
} }
} }
Log.Error($"Method not found in type {t.Name}", t); Log.Error($"Method not found in type {t.Name}", t);
WeavingFailed = true; WeavingFailed = true;
return null; return null;
} }
public static MethodReference TryResolveMethodInParents(TypeReference tr, AssemblyDefinition assembly, string name) public static MethodReference TryResolveMethodInParents(TypeReference tr, AssemblyDefinition assembly, string name)
{ {
if (tr == null) if (tr == null)
{ {
return null; return null;
} }
foreach (var methodRef in tr.Resolve().Methods) foreach (MethodDefinition methodRef in tr.Resolve().Methods)
{ {
if (methodRef.Name == name) if (methodRef.Name == name)
{ {
return assembly.MainModule.ImportReference(methodRef); return assembly.MainModule.ImportReference(methodRef);
} }
} }
// Could not find the method in this class, try the parent // Could not find the method in this class, try the parent
return TryResolveMethodInParents(tr.Resolve().BaseType, assembly, name); return TryResolveMethodInParents(tr.Resolve().BaseType, assembly, name);
} }
public static MethodDefinition ResolveDefaultPublicCtor(TypeReference variable) public static MethodDefinition ResolveDefaultPublicCtor(TypeReference variable)
{ {
foreach (var methodRef in variable.Resolve().Methods) foreach (MethodDefinition methodRef in variable.Resolve().Methods)
{ {
if (methodRef.Name == ".ctor" && if (methodRef.Name == ".ctor" &&
methodRef.Resolve().IsPublic && methodRef.Resolve().IsPublic &&
methodRef.Parameters.Count == 0) methodRef.Parameters.Count == 0)
{ {
return methodRef; return methodRef;
} }
} }
return null; return null;
} }
public static MethodReference ResolveProperty(TypeReference tr, AssemblyDefinition assembly, string name) public static MethodReference ResolveProperty(TypeReference tr, AssemblyDefinition assembly, string name)
{ {
foreach (var pd in tr.Resolve().Properties) foreach (PropertyDefinition pd in tr.Resolve().Properties)
{ {
if (pd.Name == name) if (pd.Name == name)
{ {
return assembly.MainModule.ImportReference(pd.GetMethod); return assembly.MainModule.ImportReference(pd.GetMethod);
} }
} }
return null; return null;
} }
} }
} }

View File

@ -1,29 +1,32 @@
// tracks SyncVar read/write access when processing NetworkBehaviour, // tracks SyncVar read/write access when processing NetworkBehaviour,
// to later be replaced by SyncVarAccessReplacer. // to later be replaced by SyncVarAccessReplacer.
using Mono.Cecil;
using System.Collections.Generic; using System.Collections.Generic;
using Mono.Cecil;
namespace Mirror.Weaver namespace Mirror.Weaver
{ {
// This data is flushed each time - if we are run multiple times in the same process/domain // This data is flushed each time - if we are run multiple times in the same process/domain
public class SyncVarAccessLists public class SyncVarAccessLists
{ {
// setter functions that replace [SyncVar] member variable references. dict<field, replacement> // setter functions that replace [SyncVar] member variable references. dict<field, replacement>
public Dictionary<FieldDefinition, MethodDefinition> replacementSetterProperties = public Dictionary<FieldDefinition, MethodDefinition> replacementSetterProperties =
new Dictionary<FieldDefinition, MethodDefinition>(); new Dictionary<FieldDefinition, MethodDefinition>();
// getter functions that replace [SyncVar] member variable references. dict<field, replacement> // getter functions that replace [SyncVar] member variable references. dict<field, replacement>
public Dictionary<FieldDefinition, MethodDefinition> replacementGetterProperties = public Dictionary<FieldDefinition, MethodDefinition> replacementGetterProperties =
new Dictionary<FieldDefinition, MethodDefinition>(); new Dictionary<FieldDefinition, MethodDefinition>();
// amount of SyncVars per class. dict<className, amount> // amount of SyncVars per class. dict<className, amount>
// necessary for SyncVar dirty bits, where inheriting classes start // necessary for SyncVar dirty bits, where inheriting classes start
// their dirty bits at base class SyncVar amount. // their dirty bits at base class SyncVar amount.
public Dictionary<string, int> numSyncVars = new Dictionary<string, int>(); public Dictionary<string, int> numSyncVars = new Dictionary<string, int>();
public int GetSyncVarStart(string className) => public int GetSyncVarStart(string className) =>
numSyncVars.TryGetValue(className, out var value) ? value : 0; numSyncVars.TryGetValue(className, out int value) ? value : 0;
public void SetNumSyncVars(string className, int num) => numSyncVars[className] = num; public void SetNumSyncVars(string className, int num)
} {
numSyncVars[className] = num;
}
}
} }

View File

@ -1,15 +1,15 @@
using Mono.Cecil;
using System.Collections.Generic; using System.Collections.Generic;
using Mono.Cecil;
namespace Mirror.Weaver namespace Mirror.Weaver
{ {
// Compares TypeReference using FullName // Compares TypeReference using FullName
public class TypeReferenceComparer : IEqualityComparer<TypeReference> public class TypeReferenceComparer : IEqualityComparer<TypeReference>
{ {
public bool Equals(TypeReference x, TypeReference y) => public bool Equals(TypeReference x, TypeReference y) =>
x.FullName == y.FullName; x.FullName == y.FullName;
public int GetHashCode(TypeReference obj) => public int GetHashCode(TypeReference obj) =>
obj.FullName.GetHashCode(); obj.FullName.GetHashCode();
} }
} }

View File

@ -1,213 +1,214 @@
using MirrorWeaver; using MirrorWeaver;
using Mono.Cecil;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using Mono.Cecil;
namespace Mirror.Weaver namespace Mirror.Weaver
{ {
// not static, because ILPostProcessor is multithreaded // not static, because ILPostProcessor is multithreaded
internal class Weaver internal class Weaver
{ {
public const string InvokeRpcPrefix = "InvokeUserCode_"; public const string InvokeRpcPrefix = "InvokeUserCode_";
// generated code class // generated code class
public const string GeneratedCodeNamespace = "Mirror"; public const string GeneratedCodeNamespace = "Mirror";
public const string GeneratedCodeClassName = "GeneratedNetworkCode"; public const string GeneratedCodeClassName = "GeneratedNetworkCode";
private TypeDefinition GeneratedCodeClass; TypeDefinition GeneratedCodeClass;
// for resolving Mirror.dll in ReaderWriterProcessor, we need to know // for resolving Mirror.dll in ReaderWriterProcessor, we need to know
// Mirror.dll name // Mirror.dll name
public const string MirrorAssemblyName = "Mirror"; public const string MirrorAssemblyName = "Mirror";
private WeaverTypes weaverTypes;
private SyncVarAccessLists syncVarAccessLists;
private AssemblyDefinition CurrentAssembly;
private Writers writers;
private Readers readers;
private bool WeavingFailed;
// logger functions can be set from the outside. WeaverTypes weaverTypes;
// for example, Debug.Log or ILPostProcessor Diagnostics log for SyncVarAccessLists syncVarAccessLists;
// multi threaded logging. AssemblyDefinition CurrentAssembly;
public Logger Log; Writers writers;
Readers readers;
bool WeavingFailed;
public Weaver(Logger Log) => this.Log = Log; // logger functions can be set from the outside.
// for example, Debug.Log or ILPostProcessor Diagnostics log for
// multi threaded logging.
public Logger Log;
// returns 'true' if modified (=if we did anything) public Weaver(Logger Log)
private bool WeaveNetworkBehavior(TypeDefinition td) {
{ this.Log = Log;
if (!td.IsClass) }
{
return false;
}
if (!td.IsDerivedFrom<NetworkBehaviour>()) // returns 'true' if modified (=if we did anything)
{ bool WeaveNetworkBehavior(TypeDefinition td)
if (td.IsDerivedFrom<UnityEngine.MonoBehaviour>()) {
{ if (!td.IsClass)
MonoBehaviourProcessor.Process(Log, td, ref WeavingFailed); return false;
}
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 // process this and base classes from parent to child order
var behaviourClasses = new List<TypeDefinition>(); List<TypeDefinition> behaviourClasses = new List<TypeDefinition>();
var parent = td; TypeDefinition parent = td;
while (parent != null) while (parent != null)
{ {
if (parent.Is<NetworkBehaviour>()) if (parent.Is<NetworkBehaviour>())
{ {
break; break;
} }
try try
{ {
behaviourClasses.Insert(0, parent); behaviourClasses.Insert(0, parent);
parent = parent.BaseType.Resolve(); parent = parent.BaseType.Resolve();
} }
catch (AssemblyResolutionException) catch (AssemblyResolutionException)
{ {
// this can happen for plugins. // this can happen for plugins.
//Console.WriteLine("AssemblyResolutionException: "+ ex.ToString()); //Console.WriteLine("AssemblyResolutionException: "+ ex.ToString());
break; break;
} }
} }
var modified = false; bool modified = false;
foreach (var behaviour in behaviourClasses) foreach (TypeDefinition behaviour in behaviourClasses)
{ {
modified |= new NetworkBehaviourProcessor(CurrentAssembly, weaverTypes, syncVarAccessLists, writers, readers, Log, behaviour).Process(ref WeavingFailed); modified |= new NetworkBehaviourProcessor(CurrentAssembly, weaverTypes, syncVarAccessLists, writers, readers, Log, behaviour).Process(ref WeavingFailed);
} }
return modified; return modified;
} }
private bool WeaveModule(ModuleDefinition moduleDefinition) bool WeaveModule(ModuleDefinition moduleDefinition)
{ {
var modified = false; bool modified = false;
var watch = Stopwatch.StartNew(); Stopwatch watch = Stopwatch.StartNew();
watch.Start(); watch.Start();
foreach (var td in moduleDefinition.Types) foreach (TypeDefinition td in moduleDefinition.Types)
{ {
if (td.IsClass && td.BaseType.CanBeResolved()) if (td.IsClass && td.BaseType.CanBeResolved())
{ {
modified |= WeaveNetworkBehavior(td); modified |= WeaveNetworkBehavior(td);
modified |= ServerClientAttributeProcessor.Process(weaverTypes, Log, td, ref WeavingFailed); modified |= ServerClientAttributeProcessor.Process(weaverTypes, Log, td, ref WeavingFailed);
} }
} }
watch.Stop(); watch.Stop();
Console.WriteLine($"Weave behaviours and messages took {watch.ElapsedMilliseconds} milliseconds"); Console.WriteLine($"Weave behaviours and messages took {watch.ElapsedMilliseconds} milliseconds");
return modified; return modified;
} }
private void CreateGeneratedCodeClass() => void CreateGeneratedCodeClass()
// create "Mirror.GeneratedNetworkCode" class which holds all {
// Readers<T> and Writers<T> // create "Mirror.GeneratedNetworkCode" class which holds all
GeneratedCodeClass = new TypeDefinition(GeneratedCodeNamespace, GeneratedCodeClassName, // Readers<T> and Writers<T>
TypeAttributes.BeforeFieldInit | TypeAttributes.Class | TypeAttributes.AnsiClass | TypeAttributes.Public | TypeAttributes.AutoClass | TypeAttributes.Abstract | TypeAttributes.Sealed, GeneratedCodeClass = new TypeDefinition(GeneratedCodeNamespace, GeneratedCodeClassName,
weaverTypes.Import<object>()); TypeAttributes.BeforeFieldInit | TypeAttributes.Class | TypeAttributes.AnsiClass | TypeAttributes.Public | TypeAttributes.AutoClass | TypeAttributes.Abstract | TypeAttributes.Sealed,
weaverTypes.Import<object>());
}
// Weave takes an AssemblyDefinition to be compatible with both old and // Weave takes an AssemblyDefinition to be compatible with both old and
// new weavers: // new weavers:
// * old takes a filepath, new takes a in-memory byte[] // * old takes a filepath, new takes a in-memory byte[]
// * old uses DefaultAssemblyResolver with added dependencies paths, // * old uses DefaultAssemblyResolver with added dependencies paths,
// new uses ...? // new uses ...?
// //
// => assembly: the one we are currently weaving (MyGame.dll) // => assembly: the one we are currently weaving (MyGame.dll)
// => resolver: useful in case we need to resolve any of the assembly's // => resolver: useful in case we need to resolve any of the assembly's
// assembly.MainModule.AssemblyReferences. // assembly.MainModule.AssemblyReferences.
// -> we can resolve ANY of them given that the resolver // -> we can resolve ANY of them given that the resolver
// works properly (need custom one for ILPostProcessor) // works properly (need custom one for ILPostProcessor)
// -> IMPORTANT: .Resolve() takes an AssemblyNameReference. // -> IMPORTANT: .Resolve() takes an AssemblyNameReference.
// those from assembly.MainModule.AssemblyReferences are // those from assembly.MainModule.AssemblyReferences are
// guaranteed to be resolve-able. // guaranteed to be resolve-able.
// Parsing from a string for Library/.../Mirror.dll // Parsing from a string for Library/.../Mirror.dll
// would not be guaranteed to be resolve-able because // would not be guaranteed to be resolve-able because
// for ILPostProcessor we can't assume where Mirror.dll // for ILPostProcessor we can't assume where Mirror.dll
// is etc. // is etc.
public bool Weave(AssemblyDefinition assembly, IAssemblyResolver resolver, out bool modified) public bool Weave(AssemblyDefinition assembly, IAssemblyResolver resolver, out bool modified)
{ {
WeavingFailed = false; WeavingFailed = false;
modified = false; modified = false;
try try
{ {
CurrentAssembly = assembly; CurrentAssembly = assembly;
// fix "No writer found for ..." error // fix "No writer found for ..." error
// https://github.com/vis2k/Mirror/issues/2579 // https://github.com/vis2k/Mirror/issues/2579
// -> when restarting Unity, weaver would try to weave a DLL // -> when restarting Unity, weaver would try to weave a DLL
// again // again
// -> resulting in two GeneratedNetworkCode classes (see ILSpy) // -> resulting in two GeneratedNetworkCode classes (see ILSpy)
// -> the second one wouldn't have all the writer types setup // -> the second one wouldn't have all the writer types setup
if (CurrentAssembly.MainModule.ContainsClass(GeneratedCodeNamespace, GeneratedCodeClassName)) if (CurrentAssembly.MainModule.ContainsClass(GeneratedCodeNamespace, GeneratedCodeClassName))
{ {
//Log.Warning($"Weaver: skipping {CurrentAssembly.Name} because already weaved"); //Log.Warning($"Weaver: skipping {CurrentAssembly.Name} because already weaved");
return true; return true;
} }
weaverTypes = new WeaverTypes(CurrentAssembly, Log, ref WeavingFailed); weaverTypes = new WeaverTypes(CurrentAssembly, Log, ref WeavingFailed);
// weaverTypes are needed for CreateGeneratedCodeClass // weaverTypes are needed for CreateGeneratedCodeClass
CreateGeneratedCodeClass(); CreateGeneratedCodeClass();
// WeaverList depends on WeaverTypes setup because it uses Import // WeaverList depends on WeaverTypes setup because it uses Import
syncVarAccessLists = new SyncVarAccessLists(); syncVarAccessLists = new SyncVarAccessLists();
// initialize readers & writers with this assembly. // initialize readers & writers with this assembly.
// we need to do this in every Process() call. // we need to do this in every Process() call.
// otherwise we would get // otherwise we would get
// "System.ArgumentException: Member ... is declared in another module and needs to be imported" // "System.ArgumentException: Member ... is declared in another module and needs to be imported"
// errors when still using the previous module's reader/writer funcs. // errors when still using the previous module's reader/writer funcs.
writers = new Writers(CurrentAssembly, weaverTypes, GeneratedCodeClass, Log); writers = new Writers(CurrentAssembly, weaverTypes, GeneratedCodeClass, Log);
readers = new Readers(CurrentAssembly, weaverTypes, GeneratedCodeClass, Log); readers = new Readers(CurrentAssembly, weaverTypes, GeneratedCodeClass, Log);
var rwstopwatch = Stopwatch.StartNew(); Stopwatch rwstopwatch = Stopwatch.StartNew();
// Need to track modified from ReaderWriterProcessor too because it could find custom read/write functions or create functions for NetworkMessages // 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); modified = ReaderWriterProcessor.Process(CurrentAssembly, resolver, Log, writers, readers, ref WeavingFailed);
QSBReaderWriterProcessor.Process(CurrentAssembly, writers, readers, ref WeavingFailed); QSBReaderWriterProcessor.Process(CurrentAssembly, writers, readers, ref WeavingFailed);
rwstopwatch.Stop(); rwstopwatch.Stop();
Console.WriteLine($"Find all reader and writers took {rwstopwatch.ElapsedMilliseconds} milliseconds"); Console.WriteLine($"Find all reader and writers took {rwstopwatch.ElapsedMilliseconds} milliseconds");
var moduleDefinition = CurrentAssembly.MainModule; ModuleDefinition moduleDefinition = CurrentAssembly.MainModule;
Console.WriteLine($"Script Module: {moduleDefinition.Name}"); Console.WriteLine($"Script Module: {moduleDefinition.Name}");
modified |= WeaveModule(moduleDefinition); modified |= WeaveModule(moduleDefinition);
if (WeavingFailed) if (WeavingFailed)
{ {
return false; return false;
} }
if (modified) if (modified)
{ {
SyncVarAttributeAccessReplacer.Process(moduleDefinition, syncVarAccessLists); SyncVarAttributeAccessReplacer.Process(moduleDefinition, syncVarAccessLists);
// add class that holds read/write functions // add class that holds read/write functions
moduleDefinition.Types.Add(GeneratedCodeClass); moduleDefinition.Types.Add(GeneratedCodeClass);
ReaderWriterProcessor.InitializeReaderAndWriters(CurrentAssembly, weaverTypes, writers, readers, GeneratedCodeClass); ReaderWriterProcessor.InitializeReaderAndWriters(CurrentAssembly, weaverTypes, writers, readers, GeneratedCodeClass);
// DO NOT WRITE here. // DO NOT WRITE here.
// CompilationFinishedHook writes to the file. // CompilationFinishedHook writes to the file.
// ILPostProcessor writes to in-memory assembly. // ILPostProcessor writes to in-memory assembly.
// it depends on the caller. // it depends on the caller.
//CurrentAssembly.Write(new WriterParameters{ WriteSymbols = true }); //CurrentAssembly.Write(new WriterParameters{ WriteSymbols = true });
} }
return true; return true;
} }
catch (Exception e) catch (Exception e)
{ {
Log.Error($"Exception :{e}"); Log.Error($"Exception :{e}");
WeavingFailed = true; WeavingFailed = true;
return false; return false;
} }
} }
} }
} }

View File

@ -1,23 +1,26 @@
using Mono.Cecil;
using System; using System;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using Mono.Cecil;
namespace Mirror.Weaver namespace Mirror.Weaver
{ {
[Serializable] [Serializable]
public abstract class WeaverException : Exception public abstract class WeaverException : Exception
{ {
public MemberReference MemberReference { get; } public MemberReference MemberReference { get; }
protected WeaverException(string message, MemberReference member) : base(message) => MemberReference = member; protected WeaverException(string message, MemberReference member) : base(message)
{
MemberReference = member;
}
protected WeaverException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base(serializationInfo, streamingContext) { } protected WeaverException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base(serializationInfo, streamingContext) {}
} }
[Serializable] [Serializable]
public class GenerateWriterException : WeaverException public class GenerateWriterException : WeaverException
{ {
public GenerateWriterException(string message, MemberReference member) : base(message, member) { } public GenerateWriterException(string message, MemberReference member) : base(message, member) {}
protected GenerateWriterException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base(serializationInfo, streamingContext) { } protected GenerateWriterException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base(serializationInfo, streamingContext) {}
} }
} }

View File

@ -1,150 +1,152 @@
using Mono.Cecil;
using System; using System;
using Mono.Cecil;
using UnityEditor;
using UnityEngine; using UnityEngine;
namespace Mirror.Weaver namespace Mirror.Weaver
{ {
// not static, because ILPostProcessor is multithreaded // not static, because ILPostProcessor is multithreaded
public class WeaverTypes public class WeaverTypes
{ {
public MethodReference ScriptableObjectCreateInstanceMethod; public MethodReference ScriptableObjectCreateInstanceMethod;
public MethodReference NetworkBehaviourDirtyBitsReference; public MethodReference NetworkBehaviourDirtyBitsReference;
public MethodReference GetPooledWriterReference; public MethodReference GetPooledWriterReference;
public MethodReference RecycleWriterReference; public MethodReference RecycleWriterReference;
public MethodReference NetworkClientConnectionReference; public MethodReference NetworkClientConnectionReference;
public MethodReference RemoteCallDelegateConstructor; public MethodReference RemoteCallDelegateConstructor;
public MethodReference NetworkServerGetActive; public MethodReference NetworkServerGetActive;
public MethodReference NetworkServerGetLocalClientActive; public MethodReference NetworkServerGetLocalClientActive;
public MethodReference NetworkClientGetActive; public MethodReference NetworkClientGetActive;
// custom attribute types // custom attribute types
public MethodReference InitSyncObjectReference; public MethodReference InitSyncObjectReference;
// array segment // array segment
public MethodReference ArraySegmentConstructorReference; public MethodReference ArraySegmentConstructorReference;
// syncvar // syncvar
public MethodReference syncVarEqualReference; public MethodReference syncVarEqualReference;
public MethodReference syncVarNetworkIdentityEqualReference; public MethodReference syncVarNetworkIdentityEqualReference;
public MethodReference syncVarGameObjectEqualReference; public MethodReference syncVarGameObjectEqualReference;
public MethodReference setSyncVarReference; public MethodReference setSyncVarReference;
public MethodReference setSyncVarHookGuard; public MethodReference setSyncVarHookGuard;
public MethodReference getSyncVarHookGuard; public MethodReference getSyncVarHookGuard;
public MethodReference setSyncVarGameObjectReference; public MethodReference setSyncVarGameObjectReference;
public MethodReference getSyncVarGameObjectReference; public MethodReference getSyncVarGameObjectReference;
public MethodReference setSyncVarNetworkIdentityReference; public MethodReference setSyncVarNetworkIdentityReference;
public MethodReference getSyncVarNetworkIdentityReference; public MethodReference getSyncVarNetworkIdentityReference;
public MethodReference syncVarNetworkBehaviourEqualReference; public MethodReference syncVarNetworkBehaviourEqualReference;
public MethodReference setSyncVarNetworkBehaviourReference; public MethodReference setSyncVarNetworkBehaviourReference;
public MethodReference getSyncVarNetworkBehaviourReference; public MethodReference getSyncVarNetworkBehaviourReference;
public MethodReference registerCommandReference; public MethodReference registerCommandReference;
public MethodReference registerRpcReference; public MethodReference registerRpcReference;
public MethodReference getTypeFromHandleReference; public MethodReference getTypeFromHandleReference;
public MethodReference logErrorReference; public MethodReference logErrorReference;
public MethodReference logWarningReference; public MethodReference logWarningReference;
public MethodReference sendCommandInternal; public MethodReference sendCommandInternal;
public MethodReference sendRpcInternal; public MethodReference sendRpcInternal;
public MethodReference sendTargetRpcInternal; public MethodReference sendTargetRpcInternal;
public MethodReference readNetworkBehaviourGeneric; public MethodReference readNetworkBehaviourGeneric;
// attributes // attributes
public TypeDefinition initializeOnLoadMethodAttribute; public TypeDefinition initializeOnLoadMethodAttribute;
public TypeDefinition runtimeInitializeOnLoadMethodAttribute; public TypeDefinition runtimeInitializeOnLoadMethodAttribute;
private readonly AssemblyDefinition assembly;
public TypeReference Import<T>() => Import(typeof(T)); AssemblyDefinition assembly;
public TypeReference Import(Type t) => assembly.MainModule.ImportReference(t); public TypeReference Import<T>() => Import(typeof(T));
// constructor resolves the types and stores them in fields public TypeReference Import(Type t) => assembly.MainModule.ImportReference(t);
public WeaverTypes(AssemblyDefinition assembly, Logger Log, ref bool WeavingFailed)
{
// system types
this.assembly = assembly;
var ArraySegmentType = Import(typeof(ArraySegment<>)); // constructor resolves the types and stores them in fields
ArraySegmentConstructorReference = Resolvers.ResolveMethod(ArraySegmentType, assembly, Log, ".ctor", ref WeavingFailed); public WeaverTypes(AssemblyDefinition assembly, Logger Log, ref bool WeavingFailed)
{
// system types
this.assembly = assembly;
var NetworkServerType = Import(typeof(NetworkServer)); TypeReference ArraySegmentType = Import(typeof(ArraySegment<>));
NetworkServerGetActive = Resolvers.ResolveMethod(NetworkServerType, assembly, Log, "get_active", ref WeavingFailed); ArraySegmentConstructorReference = Resolvers.ResolveMethod(ArraySegmentType, assembly, Log, ".ctor", ref WeavingFailed);
NetworkServerGetLocalClientActive = Resolvers.ResolveMethod(NetworkServerType, assembly, Log, "get_localClientActive", ref WeavingFailed);
var NetworkClientType = Import(typeof(NetworkClient));
NetworkClientGetActive = Resolvers.ResolveMethod(NetworkClientType, assembly, Log, "get_active", ref WeavingFailed);
var RemoteCallDelegateType = Import<RemoteCalls.RemoteCallDelegate>(); TypeReference NetworkServerType = Import(typeof(NetworkServer));
RemoteCallDelegateConstructor = Resolvers.ResolveMethod(RemoteCallDelegateType, assembly, Log, ".ctor", ref WeavingFailed); NetworkServerGetActive = Resolvers.ResolveMethod(NetworkServerType, assembly, Log, "get_active", ref WeavingFailed);
NetworkServerGetLocalClientActive = Resolvers.ResolveMethod(NetworkServerType, assembly, Log, "get_localClientActive", ref WeavingFailed);
TypeReference NetworkClientType = Import(typeof(NetworkClient));
NetworkClientGetActive = Resolvers.ResolveMethod(NetworkClientType, assembly, Log, "get_active", ref WeavingFailed);
var NetworkBehaviourType = Import<NetworkBehaviour>(); TypeReference RemoteCallDelegateType = Import<RemoteCalls.RemoteCallDelegate>();
var RemoteProcedureCallsType = Import(typeof(RemoteCalls.RemoteProcedureCalls)); RemoteCallDelegateConstructor = Resolvers.ResolveMethod(RemoteCallDelegateType, assembly, Log, ".ctor", ref WeavingFailed);
var ScriptableObjectType = Import<UnityEngine.ScriptableObject>(); TypeReference NetworkBehaviourType = Import<NetworkBehaviour>();
TypeReference RemoteProcedureCallsType = Import(typeof(RemoteCalls.RemoteProcedureCalls));
ScriptableObjectCreateInstanceMethod = Resolvers.ResolveMethod( TypeReference ScriptableObjectType = Import<UnityEngine.ScriptableObject>();
ScriptableObjectType, assembly, Log,
md => md.Name == "CreateInstance" && md.HasGenericParameters,
ref WeavingFailed);
NetworkBehaviourDirtyBitsReference = Resolvers.ResolveProperty(NetworkBehaviourType, assembly, "syncVarDirtyBits"); ScriptableObjectCreateInstanceMethod = Resolvers.ResolveMethod(
var NetworkWriterPoolType = Import(typeof(NetworkWriterPool)); ScriptableObjectType, assembly, Log,
GetPooledWriterReference = Resolvers.ResolveMethod(NetworkWriterPoolType, assembly, Log, "GetWriter", ref WeavingFailed); md => md.Name == "CreateInstance" && md.HasGenericParameters,
RecycleWriterReference = Resolvers.ResolveMethod(NetworkWriterPoolType, assembly, Log, "Recycle", ref WeavingFailed); ref WeavingFailed);
NetworkClientConnectionReference = Resolvers.ResolveMethod(NetworkClientType, assembly, Log, "get_connection", ref WeavingFailed); NetworkBehaviourDirtyBitsReference = Resolvers.ResolveProperty(NetworkBehaviourType, assembly, "syncVarDirtyBits");
TypeReference NetworkWriterPoolType = Import(typeof(NetworkWriterPool));
GetPooledWriterReference = Resolvers.ResolveMethod(NetworkWriterPoolType, assembly, Log, "GetWriter", ref WeavingFailed);
RecycleWriterReference = Resolvers.ResolveMethod(NetworkWriterPoolType, assembly, Log, "Recycle", ref WeavingFailed);
syncVarEqualReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SyncVarEqual", ref WeavingFailed); NetworkClientConnectionReference = Resolvers.ResolveMethod(NetworkClientType, assembly, Log, "get_connection", ref WeavingFailed);
syncVarNetworkIdentityEqualReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SyncVarNetworkIdentityEqual", ref WeavingFailed);
syncVarGameObjectEqualReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SyncVarGameObjectEqual", ref WeavingFailed);
setSyncVarReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SetSyncVar", ref WeavingFailed);
setSyncVarHookGuard = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SetSyncVarHookGuard", ref WeavingFailed);
getSyncVarHookGuard = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GetSyncVarHookGuard", ref WeavingFailed);
setSyncVarGameObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SetSyncVarGameObject", ref WeavingFailed); syncVarEqualReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SyncVarEqual", ref WeavingFailed);
getSyncVarGameObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GetSyncVarGameObject", ref WeavingFailed); syncVarNetworkIdentityEqualReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SyncVarNetworkIdentityEqual", ref WeavingFailed);
setSyncVarNetworkIdentityReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SetSyncVarNetworkIdentity", ref WeavingFailed); syncVarGameObjectEqualReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SyncVarGameObjectEqual", ref WeavingFailed);
getSyncVarNetworkIdentityReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GetSyncVarNetworkIdentity", ref WeavingFailed); setSyncVarReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SetSyncVar", ref WeavingFailed);
syncVarNetworkBehaviourEqualReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SyncVarNetworkBehaviourEqual", ref WeavingFailed); setSyncVarHookGuard = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SetSyncVarHookGuard", ref WeavingFailed);
setSyncVarNetworkBehaviourReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SetSyncVarNetworkBehaviour", ref WeavingFailed); getSyncVarHookGuard = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GetSyncVarHookGuard", ref WeavingFailed);
getSyncVarNetworkBehaviourReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GetSyncVarNetworkBehaviour", ref WeavingFailed);
registerCommandReference = Resolvers.ResolveMethod(RemoteProcedureCallsType, assembly, Log, "RegisterCommand", ref WeavingFailed); setSyncVarGameObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SetSyncVarGameObject", ref WeavingFailed);
registerRpcReference = Resolvers.ResolveMethod(RemoteProcedureCallsType, assembly, Log, "RegisterRpc", ref WeavingFailed); getSyncVarGameObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GetSyncVarGameObject", ref WeavingFailed);
setSyncVarNetworkIdentityReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SetSyncVarNetworkIdentity", ref WeavingFailed);
getSyncVarNetworkIdentityReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GetSyncVarNetworkIdentity", ref WeavingFailed);
syncVarNetworkBehaviourEqualReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SyncVarNetworkBehaviourEqual", ref WeavingFailed);
setSyncVarNetworkBehaviourReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SetSyncVarNetworkBehaviour", ref WeavingFailed);
getSyncVarNetworkBehaviourReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GetSyncVarNetworkBehaviour", ref WeavingFailed);
var unityDebug = Import(typeof(UnityEngine.Debug)); registerCommandReference = Resolvers.ResolveMethod(RemoteProcedureCallsType, assembly, Log, "RegisterCommand", ref WeavingFailed);
// these have multiple methods with same name, so need to check parameters too registerRpcReference = Resolvers.ResolveMethod(RemoteProcedureCallsType, assembly, Log, "RegisterRpc", ref WeavingFailed);
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 => TypeReference unityDebug = Import(typeof(UnityEngine.Debug));
md.Name == "LogWarning" && // these have multiple methods with same name, so need to check parameters too
md.Parameters.Count == 1 && logErrorReference = Resolvers.ResolveMethod(unityDebug, assembly, Log, md =>
md.Parameters[0].ParameterType.FullName == typeof(object).FullName, md.Name == "LogError" &&
ref WeavingFailed); md.Parameters.Count == 1 &&
md.Parameters[0].ParameterType.FullName == typeof(object).FullName,
ref WeavingFailed);
var typeType = Import(typeof(Type)); logWarningReference = Resolvers.ResolveMethod(unityDebug, assembly, Log, md =>
getTypeFromHandleReference = Resolvers.ResolveMethod(typeType, assembly, Log, "GetTypeFromHandle", ref WeavingFailed); md.Name == "LogWarning" &&
sendCommandInternal = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SendCommandInternal", ref WeavingFailed); md.Parameters.Count == 1 &&
sendRpcInternal = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SendRPCInternal", ref WeavingFailed); md.Parameters[0].ParameterType.FullName == typeof(object).FullName,
sendTargetRpcInternal = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SendTargetRPCInternal", ref WeavingFailed); ref WeavingFailed);
InitSyncObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "InitSyncObject", ref WeavingFailed); TypeReference typeType = Import(typeof(Type));
getTypeFromHandleReference = Resolvers.ResolveMethod(typeType, assembly, Log, "GetTypeFromHandle", 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);
var readerExtensions = Import(typeof(NetworkReaderExtensions)); InitSyncObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "InitSyncObject", ref WeavingFailed);
readNetworkBehaviourGeneric = Resolvers.ResolveMethod(readerExtensions, assembly, Log, (md =>
{
return md.Name == nameof(NetworkReaderExtensions.ReadNetworkBehaviour) &&
md.HasGenericParameters;
}),
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] // [InitializeOnLoadMethod]
// 'UnityEditor' is not available in builds. // 'UnityEditor' is not available in builds.
// we can only import this attribute if we are in an Editor assembly. // we can only import this attribute if we are in an Editor assembly.
@ -155,9 +157,9 @@ namespace Mirror.Weaver
} }
*/ */
// [RuntimeInitializeOnLoadMethod] // [RuntimeInitializeOnLoadMethod]
var runtimeInitializeOnLoadMethodAttributeRef = Import(typeof(RuntimeInitializeOnLoadMethodAttribute)); TypeReference runtimeInitializeOnLoadMethodAttributeRef = Import(typeof(RuntimeInitializeOnLoadMethodAttribute));
runtimeInitializeOnLoadMethodAttribute = runtimeInitializeOnLoadMethodAttributeRef.Resolve(); runtimeInitializeOnLoadMethodAttribute = runtimeInitializeOnLoadMethodAttributeRef.Resolve();
} }
} }
} }

View File

@ -1,345 +1,339 @@
using System;
using System.Collections.Generic;
using Mono.Cecil; using Mono.Cecil;
using Mono.Cecil.Cil; using Mono.Cecil.Cil;
// to use Mono.Cecil.Rocks here, we need to 'override references' in the // to use Mono.Cecil.Rocks here, we need to 'override references' in the
// Unity.Mirror.CodeGen assembly definition file in the Editor, and add CecilX.Rocks. // Unity.Mirror.CodeGen assembly definition file in the Editor, and add CecilX.Rocks.
// otherwise we get an unknown import exception. // otherwise we get an unknown import exception.
using Mono.Cecil.Rocks; using Mono.Cecil.Rocks;
using System;
using System.Collections.Generic;
namespace Mirror.Weaver namespace Mirror.Weaver
{ {
// not static, because ILPostProcessor is multithreaded // not static, because ILPostProcessor is multithreaded
public class Writers public class Writers
{ {
// Writers are only for this assembly. // Writers are only for this assembly.
// can't be used from another assembly, otherwise we will get: // can't be used from another assembly, otherwise we will get:
// "System.ArgumentException: Member ... is declared in another module and needs to be imported" // "System.ArgumentException: Member ... is declared in another module and needs to be imported"
private readonly AssemblyDefinition assembly; AssemblyDefinition assembly;
private readonly WeaverTypes weaverTypes; WeaverTypes weaverTypes;
private readonly TypeDefinition GeneratedCodeClass; TypeDefinition GeneratedCodeClass;
private readonly Logger Log; Logger Log;
private readonly Dictionary<TypeReference, MethodReference> writeFuncs =
new Dictionary<TypeReference, MethodReference>(new TypeReferenceComparer());
public Writers(AssemblyDefinition assembly, WeaverTypes weaverTypes, TypeDefinition GeneratedCodeClass, Logger Log) Dictionary<TypeReference, MethodReference> writeFuncs =
{ new Dictionary<TypeReference, MethodReference>(new TypeReferenceComparer());
this.assembly = assembly;
this.weaverTypes = weaverTypes;
this.GeneratedCodeClass = GeneratedCodeClass;
this.Log = Log;
}
public void Register(TypeReference dataType, MethodReference methodReference) public Writers(AssemblyDefinition assembly, WeaverTypes weaverTypes, TypeDefinition GeneratedCodeClass, Logger Log)
{ {
if (writeFuncs.ContainsKey(dataType)) this.assembly = assembly;
{ this.weaverTypes = weaverTypes;
// TODO enable this again later. this.GeneratedCodeClass = GeneratedCodeClass;
// Writer has some obsolete functions that were renamed. this.Log = Log;
// Don't want weaver warnings for all of them. }
//Log.Warning($"Registering a Write method for {dataType.FullName} when one already exists", methodReference);
}
// we need to import type when we Initialize Writers so import here in case it is used anywhere else public void Register(TypeReference dataType, MethodReference methodReference)
var imported = assembly.MainModule.ImportReference(dataType); {
writeFuncs[imported] = methodReference; if (writeFuncs.ContainsKey(dataType))
} {
// TODO enable this again later.
// Writer has some obsolete functions that were renamed.
// Don't want weaver warnings for all of them.
//Log.Warning($"Registering a Write method for {dataType.FullName} when one already exists", methodReference);
}
private void RegisterWriteFunc(TypeReference typeReference, MethodDefinition newWriterFunc) // we need to import type when we Initialize Writers so import here in case it is used anywhere else
{ TypeReference imported = assembly.MainModule.ImportReference(dataType);
Register(typeReference, newWriterFunc); writeFuncs[imported] = methodReference;
GeneratedCodeClass.Methods.Add(newWriterFunc); }
}
// Finds existing writer for type, if non exists trys to create one void RegisterWriteFunc(TypeReference typeReference, MethodDefinition newWriterFunc)
public MethodReference GetWriteFunc(TypeReference variable, ref bool WeavingFailed) {
{ Register(typeReference, newWriterFunc);
if (writeFuncs.TryGetValue(variable, out var foundFunc)) GeneratedCodeClass.Methods.Add(newWriterFunc);
{ }
return foundFunc;
}
// this try/catch will be removed in future PR and make `GetWriteFunc` throw instead // Finds existing writer for type, if non exists trys to create one
try public MethodReference GetWriteFunc(TypeReference variable, ref bool WeavingFailed)
{ {
var importedVariable = assembly.MainModule.ImportReference(variable); if (writeFuncs.TryGetValue(variable, out MethodReference foundFunc))
return GenerateWriter(importedVariable, ref WeavingFailed); return foundFunc;
}
catch (GenerateWriterException e)
{
Log.Error(e.Message, e.MemberReference);
WeavingFailed = true;
return null;
}
}
//Throws GenerateWriterException when writer could not be generated for type // this try/catch will be removed in future PR and make `GetWriteFunc` throw instead
private MethodReference GenerateWriter(TypeReference variableReference, ref bool WeavingFailed) try
{ {
if (variableReference.IsByReference) TypeReference importedVariable = assembly.MainModule.ImportReference(variable);
{ return GenerateWriter(importedVariable, ref WeavingFailed);
throw new GenerateWriterException($"Cannot pass {variableReference.Name} by reference", variableReference); }
} catch (GenerateWriterException e)
{
Log.Error(e.Message, e.MemberReference);
WeavingFailed = true;
return null;
}
}
// Arrays are special, if we resolve them, we get the element type, //Throws GenerateWriterException when writer could not be generated for type
// e.g. int[] resolves to int MethodReference GenerateWriter(TypeReference variableReference, ref bool WeavingFailed)
// therefore process this before checks below {
if (variableReference.IsArray) if (variableReference.IsByReference)
{ {
if (variableReference.IsMultidimensionalArray()) throw new GenerateWriterException($"Cannot pass {variableReference.Name} by reference", variableReference);
{ }
throw new GenerateWriterException($"{variableReference.Name} is an unsupported type. Multidimensional arrays are not supported", variableReference);
}
var elementType = variableReference.GetElementType();
return GenerateCollectionWriter(variableReference, elementType, nameof(NetworkWriterExtensions.WriteArray), ref WeavingFailed);
}
if (variableReference.Resolve()?.IsEnum ?? false) // Arrays are special, if we resolve them, we get the element type,
{ // e.g. int[] resolves to int
// serialize enum as their base type // therefore process this before checks below
return GenerateEnumWriteFunc(variableReference, ref WeavingFailed); if (variableReference.IsArray)
} {
if (variableReference.IsMultidimensionalArray())
{
throw new GenerateWriterException($"{variableReference.Name} is an unsupported type. Multidimensional arrays are not supported", variableReference);
}
TypeReference elementType = variableReference.GetElementType();
return GenerateCollectionWriter(variableReference, elementType, nameof(NetworkWriterExtensions.WriteArray), ref WeavingFailed);
}
// check for collections if (variableReference.Resolve()?.IsEnum ?? false)
if (variableReference.Is(typeof(ArraySegment<>))) {
{ // serialize enum as their base type
var genericInstance = (GenericInstanceType)variableReference; return GenerateEnumWriteFunc(variableReference, ref WeavingFailed);
var elementType = genericInstance.GenericArguments[0]; }
return GenerateCollectionWriter(variableReference, elementType, nameof(NetworkWriterExtensions.WriteArraySegment), ref WeavingFailed); // check for collections
} if (variableReference.Is(typeof(ArraySegment<>)))
if (variableReference.Is(typeof(List<>))) {
{ GenericInstanceType genericInstance = (GenericInstanceType)variableReference;
var genericInstance = (GenericInstanceType)variableReference; TypeReference elementType = genericInstance.GenericArguments[0];
var elementType = genericInstance.GenericArguments[0];
return GenerateCollectionWriter(variableReference, elementType, nameof(NetworkWriterExtensions.WriteList), ref WeavingFailed); return GenerateCollectionWriter(variableReference, elementType, nameof(NetworkWriterExtensions.WriteArraySegment), ref WeavingFailed);
} }
if (variableReference.Is(typeof(List<>)))
{
GenericInstanceType genericInstance = (GenericInstanceType)variableReference;
TypeReference elementType = genericInstance.GenericArguments[0];
if (variableReference.IsDerivedFrom<NetworkBehaviour>()) return GenerateCollectionWriter(variableReference, elementType, nameof(NetworkWriterExtensions.WriteList), ref WeavingFailed);
{ }
return GetNetworkBehaviourWriter(variableReference);
}
// check for invalid types if (variableReference.IsDerivedFrom<NetworkBehaviour>())
var variableDefinition = variableReference.Resolve(); {
if (variableDefinition == null) return GetNetworkBehaviourWriter(variableReference);
{ }
throw new GenerateWriterException($"{variableReference.Name} is not a supported type. Use a supported type or provide a custom writer", variableReference);
}
if (variableDefinition.IsDerivedFrom<UnityEngine.Component>())
{
throw new GenerateWriterException($"Cannot generate writer for component type {variableReference.Name}. Use a supported type or provide a custom writer", variableReference);
}
if (variableReference.Is<UnityEngine.Object>())
{
throw new GenerateWriterException($"Cannot generate writer for {variableReference.Name}. Use a supported type or provide a custom writer", variableReference);
}
if (variableReference.Is<UnityEngine.ScriptableObject>())
{
throw new GenerateWriterException($"Cannot generate writer for {variableReference.Name}. Use a supported type or provide a custom writer", variableReference);
}
if (variableDefinition.HasGenericParameters)
{
throw new GenerateWriterException($"Cannot generate writer for generic type {variableReference.Name}. Use a supported type or provide a custom writer", variableReference);
}
if (variableDefinition.IsInterface)
{
throw new GenerateWriterException($"Cannot generate writer for interface {variableReference.Name}. Use a supported type or provide a custom writer", variableReference);
}
if (variableDefinition.IsAbstract)
{
throw new GenerateWriterException($"Cannot generate writer for abstract class {variableReference.Name}. Use a supported type or provide a custom writer", variableReference);
}
// generate writer for class/struct // check for invalid types
return GenerateClassOrStructWriterFunction(variableReference, ref WeavingFailed); TypeDefinition variableDefinition = variableReference.Resolve();
} if (variableDefinition == null)
{
throw new GenerateWriterException($"{variableReference.Name} is not a supported type. Use a supported type or provide a custom writer", variableReference);
}
if (variableDefinition.IsDerivedFrom<UnityEngine.Component>())
{
throw new GenerateWriterException($"Cannot generate writer for component type {variableReference.Name}. Use a supported type or provide a custom writer", variableReference);
}
if (variableReference.Is<UnityEngine.Object>())
{
throw new GenerateWriterException($"Cannot generate writer for {variableReference.Name}. Use a supported type or provide a custom writer", variableReference);
}
if (variableReference.Is<UnityEngine.ScriptableObject>())
{
throw new GenerateWriterException($"Cannot generate writer for {variableReference.Name}. Use a supported type or provide a custom writer", variableReference);
}
if (variableDefinition.HasGenericParameters)
{
throw new GenerateWriterException($"Cannot generate writer for generic type {variableReference.Name}. Use a supported type or provide a custom writer", variableReference);
}
if (variableDefinition.IsInterface)
{
throw new GenerateWriterException($"Cannot generate writer for interface {variableReference.Name}. Use a supported type or provide a custom writer", variableReference);
}
if (variableDefinition.IsAbstract)
{
throw new GenerateWriterException($"Cannot generate writer for abstract class {variableReference.Name}. Use a supported type or provide a custom writer", variableReference);
}
private MethodReference GetNetworkBehaviourWriter(TypeReference variableReference) // generate writer for class/struct
{ return GenerateClassOrStructWriterFunction(variableReference, ref WeavingFailed);
// all NetworkBehaviours can use the same write function }
if (writeFuncs.TryGetValue(weaverTypes.Import<NetworkBehaviour>(), out var func))
{
// register function so it is added to writer<T>
// use Register instead of RegisterWriteFunc because this is not a generated function
Register(variableReference, func);
return func; MethodReference GetNetworkBehaviourWriter(TypeReference variableReference)
} {
else // all NetworkBehaviours can use the same write function
{ if (writeFuncs.TryGetValue(weaverTypes.Import<NetworkBehaviour>(), out MethodReference func))
// this exception only happens if mirror is missing the WriteNetworkBehaviour method {
throw new MissingMethodException($"Could not find writer for NetworkBehaviour"); // register function so it is added to writer<T>
} // use Register instead of RegisterWriteFunc because this is not a generated function
} Register(variableReference, func);
private MethodDefinition GenerateEnumWriteFunc(TypeReference variable, ref bool WeavingFailed) return func;
{ }
var writerFunc = GenerateWriterFunc(variable); else
{
// this exception only happens if mirror is missing the WriteNetworkBehaviour method
throw new MissingMethodException($"Could not find writer for NetworkBehaviour");
}
}
var worker = writerFunc.Body.GetILProcessor(); MethodDefinition GenerateEnumWriteFunc(TypeReference variable, ref bool WeavingFailed)
{
MethodDefinition writerFunc = GenerateWriterFunc(variable);
var underlyingWriter = GetWriteFunc(variable.Resolve().GetEnumUnderlyingType(), ref WeavingFailed); ILProcessor worker = writerFunc.Body.GetILProcessor();
worker.Emit(OpCodes.Ldarg_0); MethodReference underlyingWriter = GetWriteFunc(variable.Resolve().GetEnumUnderlyingType(), ref WeavingFailed);
worker.Emit(OpCodes.Ldarg_1);
worker.Emit(OpCodes.Call, underlyingWriter);
worker.Emit(OpCodes.Ret); worker.Emit(OpCodes.Ldarg_0);
return writerFunc; worker.Emit(OpCodes.Ldarg_1);
} worker.Emit(OpCodes.Call, underlyingWriter);
private MethodDefinition GenerateWriterFunc(TypeReference variable) worker.Emit(OpCodes.Ret);
{ return writerFunc;
var functionName = $"_Write_{variable.FullName}"; }
// create new writer for this type
var writerFunc = new MethodDefinition(functionName,
MethodAttributes.Public |
MethodAttributes.Static |
MethodAttributes.HideBySig,
weaverTypes.Import(typeof(void)));
writerFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, weaverTypes.Import<NetworkWriter>())); MethodDefinition GenerateWriterFunc(TypeReference variable)
writerFunc.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.None, variable)); {
writerFunc.Body.InitLocals = true; string functionName = $"_Write_{variable.FullName}";
// create new writer for this type
MethodDefinition writerFunc = new MethodDefinition(functionName,
MethodAttributes.Public |
MethodAttributes.Static |
MethodAttributes.HideBySig,
weaverTypes.Import(typeof(void)));
RegisterWriteFunc(variable, writerFunc); writerFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, weaverTypes.Import<NetworkWriter>()));
return writerFunc; writerFunc.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.None, variable));
} writerFunc.Body.InitLocals = true;
private MethodDefinition GenerateClassOrStructWriterFunction(TypeReference variable, ref bool WeavingFailed) RegisterWriteFunc(variable, writerFunc);
{ return writerFunc;
var writerFunc = GenerateWriterFunc(variable); }
var worker = writerFunc.Body.GetILProcessor(); MethodDefinition GenerateClassOrStructWriterFunction(TypeReference variable, ref bool WeavingFailed)
{
MethodDefinition writerFunc = GenerateWriterFunc(variable);
if (!variable.Resolve().IsValueType) ILProcessor worker = writerFunc.Body.GetILProcessor();
{
WriteNullCheck(worker, ref WeavingFailed);
}
if (!WriteAllFields(variable, worker, ref WeavingFailed)) if (!variable.Resolve().IsValueType)
{ WriteNullCheck(worker, ref WeavingFailed);
return null;
}
worker.Emit(OpCodes.Ret); if (!WriteAllFields(variable, worker, ref WeavingFailed))
return writerFunc; return null;
}
private void WriteNullCheck(ILProcessor worker, ref bool WeavingFailed) worker.Emit(OpCodes.Ret);
{ return writerFunc;
// if (value == null) }
// {
// writer.WriteBoolean(false);
// return;
// }
//
var labelNotNull = worker.Create(OpCodes.Nop); void WriteNullCheck(ILProcessor worker, ref bool WeavingFailed)
worker.Emit(OpCodes.Ldarg_1); {
worker.Emit(OpCodes.Brtrue, labelNotNull); // if (value == null)
worker.Emit(OpCodes.Ldarg_0); // {
worker.Emit(OpCodes.Ldc_I4_0); // writer.WriteBoolean(false);
worker.Emit(OpCodes.Call, GetWriteFunc(weaverTypes.Import<bool>(), ref WeavingFailed)); // return;
worker.Emit(OpCodes.Ret); // }
worker.Append(labelNotNull); //
// write.WriteBoolean(true); Instruction labelNotNull = worker.Create(OpCodes.Nop);
worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldarg_1);
worker.Emit(OpCodes.Ldc_I4_1); worker.Emit(OpCodes.Brtrue, labelNotNull);
worker.Emit(OpCodes.Call, GetWriteFunc(weaverTypes.Import<bool>(), ref WeavingFailed)); worker.Emit(OpCodes.Ldarg_0);
} worker.Emit(OpCodes.Ldc_I4_0);
worker.Emit(OpCodes.Call, GetWriteFunc(weaverTypes.Import<bool>(), ref WeavingFailed));
worker.Emit(OpCodes.Ret);
worker.Append(labelNotNull);
// Find all fields in type and write them // write.WriteBoolean(true);
private bool WriteAllFields(TypeReference variable, ILProcessor worker, ref bool WeavingFailed) worker.Emit(OpCodes.Ldarg_0);
{ worker.Emit(OpCodes.Ldc_I4_1);
foreach (var field in variable.FindAllPublicFields()) worker.Emit(OpCodes.Call, GetWriteFunc(weaverTypes.Import<bool>(), ref WeavingFailed));
{ }
var writeFunc = GetWriteFunc(field.FieldType, ref WeavingFailed);
// need this null check till later PR when GetWriteFunc throws exception instead
if (writeFunc == null)
{ return false; }
var fieldRef = assembly.MainModule.ImportReference(field); // Find all fields in type and write them
bool WriteAllFields(TypeReference variable, ILProcessor worker, ref bool WeavingFailed)
{
foreach (FieldDefinition field in variable.FindAllPublicFields())
{
MethodReference writeFunc = GetWriteFunc(field.FieldType, ref WeavingFailed);
// need this null check till later PR when GetWriteFunc throws exception instead
if (writeFunc == null) { return false; }
worker.Emit(OpCodes.Ldarg_0); FieldReference fieldRef = assembly.MainModule.ImportReference(field);
worker.Emit(OpCodes.Ldarg_1);
worker.Emit(OpCodes.Ldfld, fieldRef);
worker.Emit(OpCodes.Call, writeFunc);
}
return true; worker.Emit(OpCodes.Ldarg_0);
} worker.Emit(OpCodes.Ldarg_1);
worker.Emit(OpCodes.Ldfld, fieldRef);
worker.Emit(OpCodes.Call, writeFunc);
}
private MethodDefinition GenerateCollectionWriter(TypeReference variable, TypeReference elementType, string writerFunction, ref bool WeavingFailed) return true;
{ }
var writerFunc = GenerateWriterFunc(variable); MethodDefinition GenerateCollectionWriter(TypeReference variable, TypeReference elementType, string writerFunction, ref bool WeavingFailed)
{
var elementWriteFunc = GetWriteFunc(elementType, ref WeavingFailed); MethodDefinition writerFunc = GenerateWriterFunc(variable);
var intWriterFunc = GetWriteFunc(weaverTypes.Import<int>(), ref WeavingFailed);
// need this null check till later PR when GetWriteFunc throws exception instead MethodReference elementWriteFunc = GetWriteFunc(elementType, ref WeavingFailed);
if (elementWriteFunc == null) MethodReference intWriterFunc = GetWriteFunc(weaverTypes.Import<int>(), ref WeavingFailed);
{
Log.Error($"Cannot generate writer for {variable}. Use a supported type or provide a custom writer", variable);
WeavingFailed = true;
return writerFunc;
}
var module = assembly.MainModule; // need this null check till later PR when GetWriteFunc throws exception instead
var readerExtensions = module.ImportReference(typeof(NetworkWriterExtensions)); if (elementWriteFunc == null)
var collectionWriter = Resolvers.ResolveMethod(readerExtensions, assembly, Log, writerFunction, ref WeavingFailed); {
Log.Error($"Cannot generate writer for {variable}. Use a supported type or provide a custom writer", variable);
WeavingFailed = true;
return writerFunc;
}
var methodRef = new GenericInstanceMethod(collectionWriter); ModuleDefinition module = assembly.MainModule;
methodRef.GenericArguments.Add(elementType); TypeReference readerExtensions = module.ImportReference(typeof(NetworkWriterExtensions));
MethodReference collectionWriter = Resolvers.ResolveMethod(readerExtensions, assembly, Log, writerFunction, ref WeavingFailed);
// generates GenericInstanceMethod methodRef = new GenericInstanceMethod(collectionWriter);
// reader.WriteArray<T>(array); methodRef.GenericArguments.Add(elementType);
var worker = writerFunc.Body.GetILProcessor(); // generates
worker.Emit(OpCodes.Ldarg_0); // writer // reader.WriteArray<T>(array);
worker.Emit(OpCodes.Ldarg_1); // collection
worker.Emit(OpCodes.Call, methodRef); // WriteArray ILProcessor worker = writerFunc.Body.GetILProcessor();
worker.Emit(OpCodes.Ldarg_0); // writer
worker.Emit(OpCodes.Ldarg_1); // collection
worker.Emit(OpCodes.Ret); worker.Emit(OpCodes.Call, methodRef); // WriteArray
return writerFunc; worker.Emit(OpCodes.Ret);
}
// Save a delegate for each one of the writers into Writer{T}.write return writerFunc;
internal void InitializeWriters(ILProcessor worker) }
{
var module = assembly.MainModule;
var genericWriterClassRef = module.ImportReference(typeof(Writer<>)); // Save a delegate for each one of the writers into Writer{T}.write
internal void InitializeWriters(ILProcessor worker)
{
ModuleDefinition module = assembly.MainModule;
var fieldInfo = typeof(Writer<>).GetField(nameof(Writer<object>.write)); TypeReference genericWriterClassRef = module.ImportReference(typeof(Writer<>));
var fieldRef = module.ImportReference(fieldInfo);
var networkWriterRef = module.ImportReference(typeof(NetworkWriter));
var actionRef = module.ImportReference(typeof(Action<,>));
var actionConstructorRef = module.ImportReference(typeof(Action<,>).GetConstructors()[0]);
foreach (var kvp in writeFuncs) System.Reflection.FieldInfo fieldInfo = typeof(Writer<>).GetField(nameof(Writer<object>.write));
{ FieldReference fieldRef = module.ImportReference(fieldInfo);
var targetType = kvp.Key; TypeReference networkWriterRef = module.ImportReference(typeof(NetworkWriter));
var writeFunc = kvp.Value; TypeReference actionRef = module.ImportReference(typeof(Action<,>));
MethodReference actionConstructorRef = module.ImportReference(typeof(Action<,>).GetConstructors()[0]);
// create a Action<NetworkWriter, T> delegate foreach (KeyValuePair<TypeReference, MethodReference> kvp in writeFuncs)
worker.Emit(OpCodes.Ldnull); {
worker.Emit(OpCodes.Ldftn, writeFunc); TypeReference targetType = kvp.Key;
var actionGenericInstance = actionRef.MakeGenericInstanceType(networkWriterRef, targetType); MethodReference writeFunc = kvp.Value;
var actionRefInstance = actionConstructorRef.MakeHostInstanceGeneric(assembly.MainModule, actionGenericInstance);
worker.Emit(OpCodes.Newobj, actionRefInstance);
// save it in Writer<T>.write // create a Action<NetworkWriter, T> delegate
var genericInstance = genericWriterClassRef.MakeGenericInstanceType(targetType); worker.Emit(OpCodes.Ldnull);
var specializedField = fieldRef.SpecializeField(assembly.MainModule, genericInstance); worker.Emit(OpCodes.Ldftn, writeFunc);
worker.Emit(OpCodes.Stsfld, specializedField); GenericInstanceType actionGenericInstance = actionRef.MakeGenericInstanceType(networkWriterRef, targetType);
} MethodReference actionRefInstance = actionConstructorRef.MakeHostInstanceGeneric(assembly.MainModule, actionGenericInstance);
} worker.Emit(OpCodes.Newobj, actionRefInstance);
}
// save it in Writer<T>.write
GenericInstanceType genericInstance = genericWriterClassRef.MakeGenericInstanceType(targetType);
FieldReference specializedField = fieldRef.SpecializeField(assembly.MainModule, genericInstance);
worker.Emit(OpCodes.Stsfld, specializedField);
}
}
}
} }