Basic Networking done

This commit is contained in:
2022-01-26 19:10:12 +05:30
parent d852b2bb2c
commit 891318680d
1111 changed files with 105855 additions and 780 deletions

View File

@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Mirror.Tests")]

View File

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

View File

@@ -0,0 +1,171 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEditor.Compilation;
using UnityEngine;
using UnityAssembly = UnityEditor.Compilation.Assembly;
namespace Mirror.Weaver
{
public static class CompilationFinishedHook
{
const string MirrorRuntimeAssemblyName = "Mirror";
const string MirrorWeaverAssemblyName = "Mirror.Weaver";
// delegate for subscription to Weaver warning messages
public static Action<string> OnWeaverWarning;
// delete for subscription to Weaver error messages
public static Action<string> OnWeaverError;
// controls weather Weaver errors are reported direct to the Unity console (tests enable this)
public static bool UnityLogEnabled = true;
// warning message handler that also calls OnWarningMethod delegate
static void HandleWarning(string msg)
{
if (UnityLogEnabled) Debug.LogWarning(msg);
OnWeaverWarning?.Invoke(msg);
}
// error message handler that also calls OnErrorMethod delegate
static void HandleError(string msg)
{
if (UnityLogEnabled) Debug.LogError(msg);
OnWeaverError?.Invoke(msg);
}
[InitializeOnLoadMethod]
public static void OnInitializeOnLoad()
{
CompilationPipeline.assemblyCompilationFinished += OnCompilationFinished;
// We only need to run this once per session
// after that, all assemblies will be weaved by the event
if (!SessionState.GetBool("MIRROR_WEAVED", false))
{
// reset session flag
SessionState.SetBool("MIRROR_WEAVED", true);
SessionState.SetBool("MIRROR_WEAVE_SUCCESS", true);
WeaveExistingAssemblies();
}
}
public static void WeaveExistingAssemblies()
{
foreach (UnityAssembly assembly in CompilationPipeline.GetAssemblies())
{
if (File.Exists(assembly.outputPath))
{
OnCompilationFinished(assembly.outputPath, new CompilerMessage[0]);
}
}
#if UNITY_2019_3_OR_NEWER
EditorUtility.RequestScriptReload();
#else
UnityEditorInternal.InternalEditorUtility.RequestScriptReload();
#endif
}
static string FindMirrorRuntime()
{
foreach (UnityAssembly assembly in CompilationPipeline.GetAssemblies())
{
if (assembly.name == MirrorRuntimeAssemblyName)
{
return assembly.outputPath;
}
}
return "";
}
static bool CompilerMessagesContainError(CompilerMessage[] messages)
{
return messages.Any(msg => msg.type == CompilerMessageType.Error);
}
static void OnCompilationFinished(string assemblyPath, CompilerMessage[] messages)
{
// Do nothing if there were compile errors on the target
if (CompilerMessagesContainError(messages))
{
Debug.Log("Weaver: stop because compile errors on target");
return;
}
// Should not run on the editor only assemblies
if (assemblyPath.Contains("-Editor") || assemblyPath.Contains(".Editor"))
{
return;
}
// don't weave mirror files
string assemblyName = Path.GetFileNameWithoutExtension(assemblyPath);
if (assemblyName == MirrorRuntimeAssemblyName || assemblyName == MirrorWeaverAssemblyName)
{
return;
}
// find Mirror.dll
string mirrorRuntimeDll = FindMirrorRuntime();
if (string.IsNullOrEmpty(mirrorRuntimeDll))
{
Debug.LogError("Failed to find Mirror runtime assembly");
return;
}
if (!File.Exists(mirrorRuntimeDll))
{
// this is normal, it happens with any assembly that is built before mirror
// such as unity packages or your own assemblies
// those don't need to be weaved
// if any assembly depends on mirror, then it will be built after
return;
}
// find UnityEngine.CoreModule.dll
string unityEngineCoreModuleDLL = UnityEditorInternal.InternalEditorUtility.GetEngineCoreModuleAssemblyPath();
if (string.IsNullOrEmpty(unityEngineCoreModuleDLL))
{
Debug.LogError("Failed to find UnityEngine assembly");
return;
}
HashSet<string> dependencyPaths = GetDependecyPaths(assemblyPath);
dependencyPaths.Add(Path.GetDirectoryName(mirrorRuntimeDll));
dependencyPaths.Add(Path.GetDirectoryName(unityEngineCoreModuleDLL));
Log.Warning = HandleWarning;
Log.Error = HandleError;
if (!Weaver.WeaveAssembly(assemblyPath, dependencyPaths.ToArray()))
{
// Set false...will be checked in \Editor\EnterPlayModeSettingsCheck.CheckSuccessfulWeave()
SessionState.SetBool("MIRROR_WEAVE_SUCCESS", false);
if (UnityLogEnabled) Debug.LogError("Weaving failed for: " + assemblyPath);
}
}
static HashSet<string> GetDependecyPaths(string assemblyPath)
{
// build directory list for later asm/symbol resolving using CompilationPipeline refs
HashSet<string> dependencyPaths = new HashSet<string>
{
Path.GetDirectoryName(assemblyPath)
};
foreach (UnityAssembly unityAsm in CompilationPipeline.GetAssemblies())
{
if (unityAsm.outputPath == assemblyPath)
{
foreach (string unityAsmRef in unityAsm.compiledAssemblyReferences)
{
dependencyPaths.Add(Path.GetDirectoryName(unityAsmRef));
}
}
}
return dependencyPaths;
}
}
}

View File

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

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 30fc290f2ff9c29498f54f63de12ca6f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1 @@
// Removed 05/09/20

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,16 @@
using System.IO;
using System.Reflection;
namespace Mirror.Weaver
{
static class Helpers
{
// This code is taken from SerializationWeaver
public static string UnityEngineDllDirectoryName()
{
string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().CodeBase);
return directoryName?.Replace(@"file:\", "");
}
}
}

View File

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

View File

@@ -0,0 +1,10 @@
using System;
namespace Mirror.Weaver
{
public static class Log
{
public static Action<string> Warning;
public static Action<string> Error;
}
}

View File

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

View File

@@ -0,0 +1,16 @@
{
"name": "Mirror.Weaver",
"references": [
"Mirror"
],
"optionalUnityReferences": [],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": true,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": []
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 1d0b9d21c3ff546a4aa32399dfd33474
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e538d627280d2471b8c72fdea822ca49
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,127 @@
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
/// <summary>
/// Processes [Command] methods in NetworkBehaviour
/// </summary>
public static class CommandProcessor
{
/*
// generates code like:
public void CmdThrust(float thrusting, int spin)
{
NetworkWriter networkWriter = new NetworkWriter();
networkWriter.Write(thrusting);
networkWriter.WritePackedUInt32((uint)spin);
base.SendCommandInternal(cmdName, networkWriter, cmdName);
}
public void CallCmdThrust(float thrusting, int spin)
{
// whatever the user was doing before
}
Originally HLAPI put the send message code inside the Call function
and then proceeded to replace every call to CmdTrust with CallCmdTrust
This method moves all the user's code into the "CallCmd" method
and replaces the body of the original method with the send message code.
This way we do not need to modify the code anywhere else, and this works
correctly in dependent assemblies
*/
public static MethodDefinition ProcessCommandCall(TypeDefinition td, MethodDefinition md, CustomAttribute commandAttr)
{
MethodDefinition cmd = MethodProcessor.SubstituteMethod(td, md);
ILProcessor worker = md.Body.GetILProcessor();
NetworkBehaviourProcessor.WriteSetupLocals(worker);
// NetworkWriter writer = new NetworkWriter();
NetworkBehaviourProcessor.WriteCreateWriter(worker);
// write all the arguments that the user passed to the Cmd call
if (!NetworkBehaviourProcessor.WriteArguments(worker, md, RemoteCallType.Command))
return null;
string cmdName = md.Name;
int channel = commandAttr.GetField("channel", 0);
bool requiresAuthority = commandAttr.GetField("requiresAuthority", true);
// invoke internal send and return
// load 'base.' to call the SendCommand function with
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldtoken, td);
// invokerClass
worker.Emit(OpCodes.Call, WeaverTypes.getTypeFromHandleReference);
worker.Emit(OpCodes.Ldstr, cmdName);
// writer
worker.Emit(OpCodes.Ldloc_0);
worker.Emit(OpCodes.Ldc_I4, channel);
// requiresAuthority ? 1 : 0
worker.Emit(requiresAuthority ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
worker.Emit(OpCodes.Call, WeaverTypes.sendCommandInternal);
NetworkBehaviourProcessor.WriteRecycleWriter(worker);
worker.Emit(OpCodes.Ret);
return cmd;
}
/*
// generates code like:
protected static void InvokeCmdCmdThrust(NetworkBehaviour obj, NetworkReader reader, NetworkConnection senderConnection)
{
if (!NetworkServer.active)
{
return;
}
((ShipControl)obj).CmdThrust(reader.ReadSingle(), (int)reader.ReadPackedUInt32());
}
*/
public static MethodDefinition ProcessCommandInvoke(TypeDefinition td, MethodDefinition method, MethodDefinition cmdCallFunc)
{
MethodDefinition cmd = new MethodDefinition(Weaver.InvokeRpcPrefix + method.Name,
MethodAttributes.Family | MethodAttributes.Static | MethodAttributes.HideBySig,
WeaverTypes.Import(typeof(void)));
ILProcessor worker = cmd.Body.GetILProcessor();
Instruction label = worker.Create(OpCodes.Nop);
NetworkBehaviourProcessor.WriteServerActiveCheck(worker, method.Name, label, "Command");
// setup for reader
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Castclass, td);
if (!NetworkBehaviourProcessor.ReadArguments(method, worker, RemoteCallType.Command))
return null;
AddSenderConnection(method, worker);
// invoke actual command function
worker.Emit(OpCodes.Callvirt, cmdCallFunc);
worker.Emit(OpCodes.Ret);
NetworkBehaviourProcessor.AddInvokeParameters(cmd.Parameters);
td.Methods.Add(cmd);
return cmd;
}
static void AddSenderConnection(MethodDefinition method, ILProcessor worker)
{
foreach (ParameterDefinition param in method.Parameters)
{
if (NetworkBehaviourProcessor.IsSenderConnection(param, RemoteCallType.Command))
{
// NetworkConnection is 3nd arg (arg0 is "obj" not "this" because method is static)
// example: static void InvokeCmdCmdSendCommand(NetworkBehaviour obj, NetworkReader reader, NetworkConnection connection)
worker.Emit(OpCodes.Ldarg_2);
}
}
}
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,45 @@
using Mono.CecilX;
namespace Mirror.Weaver
{
/// <summary>
/// only shows warnings in case we use SyncVars etc. for MonoBehaviour.
/// </summary>
static class MonoBehaviourProcessor
{
public static void Process(TypeDefinition td)
{
ProcessSyncVars(td);
ProcessMethods(td);
}
static void ProcessSyncVars(TypeDefinition td)
{
// find syncvars
foreach (FieldDefinition fd in td.Fields)
{
if (fd.HasCustomAttribute<SyncVarAttribute>())
Weaver.Error($"SyncVar {fd.Name} must be inside a NetworkBehaviour. {td.Name} is not a NetworkBehaviour", fd);
if (SyncObjectInitializer.ImplementsSyncObject(fd.FieldType))
{
Weaver.Error($"{fd.Name} is a SyncObject and must be inside a NetworkBehaviour. {td.Name} is not a NetworkBehaviour", fd);
}
}
}
static void ProcessMethods(TypeDefinition td)
{
// find command and RPC functions
foreach (MethodDefinition md in td.Methods)
{
if (md.HasCustomAttribute<CommandAttribute>())
Weaver.Error($"Command {md.Name} must be declared inside a NetworkBehaviour", md);
if (md.HasCustomAttribute<ClientRpcAttribute>())
Weaver.Error($"ClientRpc {md.Name} must be declared inside a NetworkBehaviour", md);
if (md.HasCustomAttribute<TargetRpcAttribute>())
Weaver.Error($"TargetRpc {md.Name} must be declared inside a NetworkBehaviour", md);
}
}
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -0,0 +1,160 @@
using System;
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
public static class PropertySiteProcessor
{
public static void Process(ModuleDefinition moduleDef)
{
DateTime startTime = DateTime.Now;
//Search through the types
foreach (TypeDefinition td in moduleDef.Types)
{
if (td.IsClass)
{
ProcessSiteClass(td);
}
}
Console.WriteLine(" ProcessSitesModule " + moduleDef.Name + " elapsed time:" + (DateTime.Now - startTime));
}
static void ProcessSiteClass(TypeDefinition td)
{
//Console.WriteLine(" ProcessSiteClass " + td);
foreach (MethodDefinition md in td.Methods)
{
ProcessSiteMethod(md);
}
foreach (TypeDefinition nested in td.NestedTypes)
{
ProcessSiteClass(nested);
}
}
static void ProcessSiteMethod(MethodDefinition md)
{
// process all references to replaced members with properties
//Weaver.DLog(td, " ProcessSiteMethod " + md);
if (md.Name == ".cctor" ||
md.Name == NetworkBehaviourProcessor.ProcessedFunctionName ||
md.Name.StartsWith(Weaver.InvokeRpcPrefix))
return;
if (md.IsAbstract)
{
return;
}
if (md.Body != null && md.Body.Instructions != null)
{
for (int iCount = 0; iCount < md.Body.Instructions.Count;)
{
Instruction instr = md.Body.Instructions[iCount];
iCount += ProcessInstruction(md, instr, iCount);
}
}
}
// replaces syncvar write access with the NetworkXYZ.get property calls
static void ProcessInstructionSetterField(MethodDefinition md, Instruction i, FieldDefinition opField)
{
// don't replace property call sites in constructors
if (md.Name == ".ctor")
return;
// does it set a field that we replaced?
if (Weaver.WeaveLists.replacementSetterProperties.TryGetValue(opField, out MethodDefinition replacement))
{
//replace with property
//DLog(td, " replacing " + md.Name + ":" + i);
i.OpCode = OpCodes.Call;
i.Operand = replacement;
//DLog(td, " replaced " + md.Name + ":" + i);
}
}
// replaces syncvar read access with the NetworkXYZ.get property calls
static void ProcessInstructionGetterField(MethodDefinition md, Instruction i, FieldDefinition opField)
{
// don't replace property call sites in constructors
if (md.Name == ".ctor")
return;
// does it set a field that we replaced?
if (Weaver.WeaveLists.replacementGetterProperties.TryGetValue(opField, out MethodDefinition replacement))
{
//replace with property
//DLog(td, " replacing " + md.Name + ":" + i);
i.OpCode = OpCodes.Call;
i.Operand = replacement;
//DLog(td, " replaced " + md.Name + ":" + i);
}
}
static int ProcessInstruction(MethodDefinition md, Instruction instr, int iCount)
{
if (instr.OpCode == OpCodes.Stfld && instr.Operand is FieldDefinition opFieldst)
{
// this instruction sets the value of a field. cache the field reference.
ProcessInstructionSetterField(md, instr, opFieldst);
}
if (instr.OpCode == OpCodes.Ldfld && instr.Operand is FieldDefinition opFieldld)
{
// this instruction gets the value of a field. cache the field reference.
ProcessInstructionGetterField(md, instr, opFieldld);
}
if (instr.OpCode == OpCodes.Ldflda && instr.Operand is FieldDefinition opFieldlda)
{
// loading a field by reference, watch out for initobj instruction
// see https://github.com/vis2k/Mirror/issues/696
return ProcessInstructionLoadAddress(md, instr, opFieldlda, iCount);
}
return 1;
}
static int ProcessInstructionLoadAddress(MethodDefinition md, Instruction instr, FieldDefinition opField, int iCount)
{
// don't replace property call sites in constructors
if (md.Name == ".ctor")
return 1;
// does it set a field that we replaced?
if (Weaver.WeaveLists.replacementSetterProperties.TryGetValue(opField, out MethodDefinition replacement))
{
// we have a replacement for this property
// is the next instruction a initobj?
Instruction nextInstr = md.Body.Instructions[iCount + 1];
if (nextInstr.OpCode == OpCodes.Initobj)
{
// we need to replace this code with:
// var tmp = new MyStruct();
// this.set_Networkxxxx(tmp);
ILProcessor worker = md.Body.GetILProcessor();
VariableDefinition tmpVariable = new VariableDefinition(opField.FieldType);
md.Body.Variables.Add(tmpVariable);
worker.InsertBefore(instr, worker.Create(OpCodes.Ldloca, tmpVariable));
worker.InsertBefore(instr, worker.Create(OpCodes.Initobj, opField.FieldType));
worker.InsertBefore(instr, worker.Create(OpCodes.Ldloc, tmpVariable));
worker.InsertBefore(instr, worker.Create(OpCodes.Call, replacement));
worker.Remove(instr);
worker.Remove(nextInstr);
return 4;
}
}
return 1;
}
}
}

View File

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

View File

@@ -0,0 +1,179 @@
// finds all readers and writers and register them
using System;
using System.Linq;
using Mono.CecilX;
using Mono.CecilX.Cil;
using UnityEditor;
using UnityEditor.Compilation;
using UnityEngine;
namespace Mirror.Weaver
{
public static class ReaderWriterProcessor
{
public static bool Process(AssemblyDefinition CurrentAssembly)
{
Readers.Init();
Writers.Init();
foreach (Assembly unityAsm in CompilationPipeline.GetAssemblies())
{
if (unityAsm.name == "Mirror")
{
using (DefaultAssemblyResolver asmResolver = new DefaultAssemblyResolver())
using (AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(unityAsm.outputPath, new ReaderParameters { ReadWrite = false, ReadSymbols = false, AssemblyResolver = asmResolver }))
{
ProcessAssemblyClasses(CurrentAssembly, assembly);
}
}
}
return ProcessAssemblyClasses(CurrentAssembly, CurrentAssembly);
}
static bool ProcessAssemblyClasses(AssemblyDefinition CurrentAssembly, AssemblyDefinition assembly)
{
bool modified = false;
foreach (TypeDefinition klass in assembly.MainModule.Types)
{
// extension methods only live in static classes
// static classes are represented as sealed and abstract
if (klass.IsAbstract && klass.IsSealed)
{
// if assembly has any declared writers then it is "modified"
modified |= LoadDeclaredWriters(CurrentAssembly, klass);
modified |= LoadDeclaredReaders(CurrentAssembly, klass);
}
}
foreach (TypeDefinition klass in assembly.MainModule.Types)
{
// if assembly has any network message then it is modified
modified |= LoadMessageReadWriter(CurrentAssembly.MainModule, klass);
}
return modified;
}
static bool LoadMessageReadWriter(ModuleDefinition module, TypeDefinition klass)
{
bool modified = false;
if (!klass.IsAbstract && !klass.IsInterface && klass.ImplementsInterface<NetworkMessage>())
{
Readers.GetReadFunc(module.ImportReference(klass));
Writers.GetWriteFunc(module.ImportReference(klass));
modified = true;
}
foreach (TypeDefinition td in klass.NestedTypes)
{
modified |= LoadMessageReadWriter(module, td);
}
return modified;
}
static bool LoadDeclaredWriters(AssemblyDefinition currentAssembly, TypeDefinition klass)
{
// register all the writers in this class. Skip the ones with wrong signature
bool modified = false;
foreach (MethodDefinition method in klass.Methods)
{
if (method.Parameters.Count != 2)
continue;
if (!method.Parameters[0].ParameterType.Is<NetworkWriter>())
continue;
if (!method.ReturnType.Is(typeof(void)))
continue;
if (!method.HasCustomAttribute<System.Runtime.CompilerServices.ExtensionAttribute>())
continue;
if (method.HasGenericParameters)
continue;
TypeReference dataType = method.Parameters[1].ParameterType;
Writers.Register(dataType, currentAssembly.MainModule.ImportReference(method));
modified = true;
}
return modified;
}
static bool LoadDeclaredReaders(AssemblyDefinition currentAssembly, TypeDefinition klass)
{
// register all the reader in this class. Skip the ones with wrong signature
bool modified = false;
foreach (MethodDefinition method in klass.Methods)
{
if (method.Parameters.Count != 1)
continue;
if (!method.Parameters[0].ParameterType.Is<NetworkReader>())
continue;
if (method.ReturnType.Is(typeof(void)))
continue;
if (!method.HasCustomAttribute<System.Runtime.CompilerServices.ExtensionAttribute>())
continue;
if (method.HasGenericParameters)
continue;
Readers.Register(method.ReturnType, currentAssembly.MainModule.ImportReference(method));
modified = true;
}
return modified;
}
static bool IsEditorAssembly(AssemblyDefinition currentAssembly)
{
// we want to add the [InitializeOnLoad] attribute if it's available
// -> usually either 'UnityEditor' or 'UnityEditor.CoreModule'
return currentAssembly.MainModule.AssemblyReferences.Any(assemblyReference =>
assemblyReference.Name.StartsWith(nameof(UnityEditor))
);
}
// adds Mirror.GeneratedNetworkCode.InitReadWriters() method that
// registers all generated writers into Mirror.Writer<T> static class.
// -> uses [RuntimeInitializeOnLoad] attribute so it's invoke at runtime
// -> uses [InitializeOnLoad] if UnityEditor is referenced so it works
// in Editor and in tests too
//
// use ILSpy to see the result (it's in the DLL's 'Mirror' namespace)
public static void InitializeReaderAndWriters(AssemblyDefinition currentAssembly)
{
MethodDefinition rwInitializer = new MethodDefinition("InitReadWriters", MethodAttributes.Public |
MethodAttributes.Static,
WeaverTypes.Import(typeof(void)));
// add [RuntimeInitializeOnLoad] in any case
System.Reflection.ConstructorInfo attributeconstructor = typeof(RuntimeInitializeOnLoadMethodAttribute).GetConstructor(new[] { typeof(RuntimeInitializeLoadType) });
CustomAttribute customAttributeRef = new CustomAttribute(currentAssembly.MainModule.ImportReference(attributeconstructor));
customAttributeRef.ConstructorArguments.Add(new CustomAttributeArgument(WeaverTypes.Import<RuntimeInitializeLoadType>(), RuntimeInitializeLoadType.BeforeSceneLoad));
rwInitializer.CustomAttributes.Add(customAttributeRef);
// add [InitializeOnLoad] if UnityEditor is referenced
if (IsEditorAssembly(currentAssembly))
{
System.Reflection.ConstructorInfo initializeOnLoadConstructor = typeof(InitializeOnLoadMethodAttribute).GetConstructor(new Type[0]);
CustomAttribute initializeCustomConstructorRef = new CustomAttribute(currentAssembly.MainModule.ImportReference(initializeOnLoadConstructor));
rwInitializer.CustomAttributes.Add(initializeCustomConstructorRef);
}
// fill function body with reader/writer initializers
ILProcessor worker = rwInitializer.Body.GetILProcessor();
// for debugging: add a log to see if initialized on load
//worker.Emit(OpCodes.Ldstr, $"[InitReadWriters] called!");
//worker.Emit(OpCodes.Call, WeaverTypes.logWarningReference);
Writers.InitializeWriters(worker);
Readers.InitializeReaders(worker);
worker.Emit(OpCodes.Ret);
Weaver.GeneratedCodeClass.Methods.Add(rwInitializer);
}
}
}

View File

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

View File

@@ -0,0 +1,106 @@
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
/// <summary>
/// Processes [Rpc] methods in NetworkBehaviour
/// </summary>
public static class RpcProcessor
{
public static MethodDefinition ProcessRpcInvoke(TypeDefinition td, MethodDefinition md, MethodDefinition rpcCallFunc)
{
MethodDefinition rpc = new MethodDefinition(
Weaver.InvokeRpcPrefix + md.Name,
MethodAttributes.Family | MethodAttributes.Static | MethodAttributes.HideBySig,
WeaverTypes.Import(typeof(void)));
ILProcessor worker = rpc.Body.GetILProcessor();
Instruction label = worker.Create(OpCodes.Nop);
NetworkBehaviourProcessor.WriteClientActiveCheck(worker, md.Name, label, "RPC");
// setup for reader
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Castclass, td);
if (!NetworkBehaviourProcessor.ReadArguments(md, worker, RemoteCallType.ClientRpc))
return null;
// invoke actual command function
worker.Emit(OpCodes.Callvirt, rpcCallFunc);
worker.Emit(OpCodes.Ret);
NetworkBehaviourProcessor.AddInvokeParameters(rpc.Parameters);
td.Methods.Add(rpc);
return rpc;
}
/*
* generates code like:
public void RpcTest (int param)
{
NetworkWriter writer = new NetworkWriter ();
writer.WritePackedUInt32((uint)param);
base.SendRPCInternal(typeof(class),"RpcTest", writer, 0);
}
public void CallRpcTest (int param)
{
// whatever the user did before
}
Originally HLAPI put the send message code inside the Call function
and then proceeded to replace every call to RpcTest with CallRpcTest
This method moves all the user's code into the "CallRpc" method
and replaces the body of the original method with the send message code.
This way we do not need to modify the code anywhere else, and this works
correctly in dependent assemblies
*/
public static MethodDefinition ProcessRpcCall(TypeDefinition td, MethodDefinition md, CustomAttribute clientRpcAttr)
{
MethodDefinition rpc = MethodProcessor.SubstituteMethod(td, md);
ILProcessor worker = md.Body.GetILProcessor();
NetworkBehaviourProcessor.WriteSetupLocals(worker);
if (Weaver.GenerateLogErrors)
{
worker.Emit(OpCodes.Ldstr, "Call ClientRpc function " + md.Name);
worker.Emit(OpCodes.Call, WeaverTypes.logErrorReference);
}
NetworkBehaviourProcessor.WriteCreateWriter(worker);
// write all the arguments that the user passed to the Rpc call
if (!NetworkBehaviourProcessor.WriteArguments(worker, md, RemoteCallType.ClientRpc))
return null;
string rpcName = md.Name;
int channel = clientRpcAttr.GetField("channel", 0);
bool includeOwner = clientRpcAttr.GetField("includeOwner", true);
// invoke SendInternal and return
// this
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldtoken, td);
// invokerClass
worker.Emit(OpCodes.Call, WeaverTypes.getTypeFromHandleReference);
worker.Emit(OpCodes.Ldstr, rpcName);
// writer
worker.Emit(OpCodes.Ldloc_0);
worker.Emit(OpCodes.Ldc_I4, channel);
// includeOwner ? 1 : 0
worker.Emit(includeOwner ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
worker.Emit(OpCodes.Callvirt, WeaverTypes.sendRpcInternal);
NetworkBehaviourProcessor.WriteRecycleWriter(worker);
worker.Emit(OpCodes.Ret);
return rpc;
}
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,47 @@
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
public static class SyncObjectInitializer
{
public static void GenerateSyncObjectInitializer(ILProcessor worker, FieldDefinition fd)
{
// register syncobject in network behaviour
GenerateSyncObjectRegistration(worker, fd);
}
public static bool ImplementsSyncObject(TypeReference typeRef)
{
try
{
// value types cant inherit from SyncObject
if (typeRef.IsValueType)
{
return false;
}
return typeRef.Resolve().ImplementsInterface<SyncObject>();
}
catch
{
// sometimes this will fail if we reference a weird library that can't be resolved, so we just swallow that exception and return false
}
return false;
}
/*
// generates code like:
this.InitSyncObject(m_sizes);
*/
static void GenerateSyncObjectRegistration(ILProcessor worker, FieldDefinition fd)
{
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldfld, fd);
worker.Emit(OpCodes.Call, WeaverTypes.InitSyncObjectReference);
}
}
}

View File

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

View File

@@ -0,0 +1,63 @@
using System.Collections.Generic;
using Mono.CecilX;
namespace Mirror.Weaver
{
public static class SyncObjectProcessor
{
/// <summary>
/// Finds SyncObjects fields in a type
/// <para>Type should be a NetworkBehaviour</para>
/// </summary>
/// <param name="td"></param>
/// <returns></returns>
public static List<FieldDefinition> FindSyncObjectsFields(TypeDefinition td)
{
List<FieldDefinition> syncObjects = new List<FieldDefinition>();
foreach (FieldDefinition fd in td.Fields)
{
if (fd.FieldType.Resolve().ImplementsInterface<SyncObject>())
{
if (fd.IsStatic)
{
Weaver.Error($"{fd.Name} cannot be static", fd);
continue;
}
GenerateReadersAndWriters(fd.FieldType);
syncObjects.Add(fd);
}
}
return syncObjects;
}
/// <summary>
/// Generates serialization methods for synclists
/// </summary>
/// <param name="td">The synclist class</param>
/// <param name="mirrorBaseType">the base SyncObject td inherits from</param>
static void GenerateReadersAndWriters(TypeReference tr)
{
if (tr is GenericInstanceType genericInstance)
{
foreach (TypeReference argument in genericInstance.GenericArguments)
{
if (!argument.IsGenericParameter)
{
Readers.GetReadFunc(argument);
Writers.GetWriteFunc(argument);
}
}
}
if (tr != null)
{
GenerateReadersAndWriters(tr.Resolve().BaseType);
}
}
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,141 @@
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
/// <summary>
/// Processes [TargetRpc] methods in NetworkBehaviour
/// </summary>
public static class TargetRpcProcessor
{
// helper functions to check if the method has a NetworkConnection parameter
public static bool HasNetworkConnectionParameter(MethodDefinition md)
{
return md.Parameters.Count > 0 &&
md.Parameters[0].ParameterType.Is<NetworkConnection>();
}
public static MethodDefinition ProcessTargetRpcInvoke(TypeDefinition td, MethodDefinition md, MethodDefinition rpcCallFunc)
{
MethodDefinition rpc = new MethodDefinition(Weaver.InvokeRpcPrefix + md.Name, MethodAttributes.Family |
MethodAttributes.Static |
MethodAttributes.HideBySig,
WeaverTypes.Import(typeof(void)));
ILProcessor worker = rpc.Body.GetILProcessor();
Instruction label = worker.Create(OpCodes.Nop);
NetworkBehaviourProcessor.WriteClientActiveCheck(worker, md.Name, label, "TargetRPC");
// setup for reader
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Castclass, td);
// NetworkConnection parameter is optional
if (HasNetworkConnectionParameter(md))
{
// on server, the NetworkConnection parameter is a connection to client.
// when the rpc is invoked on the client, it still has the same
// function signature. we pass in the connection to server,
// which is cleaner than just passing null)
//NetworkClient.readyconnection
//
// TODO
// a) .connectionToServer = best solution. no doubt.
// b) NetworkClient.connection for now. add TODO to not use static later.
worker.Emit(OpCodes.Call, WeaverTypes.ReadyConnectionReference);
}
// process reader parameters and skip first one if first one is NetworkConnection
if (!NetworkBehaviourProcessor.ReadArguments(md, worker, RemoteCallType.TargetRpc))
return null;
// invoke actual command function
worker.Emit(OpCodes.Callvirt, rpcCallFunc);
worker.Emit(OpCodes.Ret);
NetworkBehaviourProcessor.AddInvokeParameters(rpc.Parameters);
td.Methods.Add(rpc);
return rpc;
}
/* generates code like:
public void TargetTest (NetworkConnection conn, int param)
{
NetworkWriter writer = new NetworkWriter ();
writer.WritePackedUInt32 ((uint)param);
base.SendTargetRPCInternal (conn, typeof(class), "TargetTest", val);
}
public void CallTargetTest (NetworkConnection conn, int param)
{
// whatever the user did before
}
or if optional:
public void TargetTest (int param)
{
NetworkWriter writer = new NetworkWriter ();
writer.WritePackedUInt32 ((uint)param);
base.SendTargetRPCInternal (null, typeof(class), "TargetTest", val);
}
public void CallTargetTest (int param)
{
// whatever the user did before
}
Originally HLAPI put the send message code inside the Call function
and then proceeded to replace every call to TargetTest with CallTargetTest
This method moves all the user's code into the "CallTargetRpc" method
and replaces the body of the original method with the send message code.
This way we do not need to modify the code anywhere else, and this works
correctly in dependent assemblies
*/
public static MethodDefinition ProcessTargetRpcCall(TypeDefinition td, MethodDefinition md, CustomAttribute targetRpcAttr)
{
MethodDefinition rpc = MethodProcessor.SubstituteMethod(td, md);
ILProcessor worker = md.Body.GetILProcessor();
NetworkBehaviourProcessor.WriteSetupLocals(worker);
NetworkBehaviourProcessor.WriteCreateWriter(worker);
// write all the arguments that the user passed to the TargetRpc call
// (skip first one if first one is NetworkConnection)
if (!NetworkBehaviourProcessor.WriteArguments(worker, md, RemoteCallType.TargetRpc))
return null;
string rpcName = md.Name;
// invoke SendInternal and return
// this
worker.Emit(OpCodes.Ldarg_0);
if (HasNetworkConnectionParameter(md))
{
// connection
worker.Emit(OpCodes.Ldarg_1);
}
else
{
// null
worker.Emit(OpCodes.Ldnull);
}
worker.Emit(OpCodes.Ldtoken, td);
// invokerClass
worker.Emit(OpCodes.Call, WeaverTypes.getTypeFromHandleReference);
worker.Emit(OpCodes.Ldstr, rpcName);
// writer
worker.Emit(OpCodes.Ldloc_0);
worker.Emit(OpCodes.Ldc_I4, targetRpcAttr.GetField("channel", 0));
worker.Emit(OpCodes.Callvirt, WeaverTypes.sendTargetRpcInternal);
NetworkBehaviourProcessor.WriteRecycleWriter(worker);
worker.Emit(OpCodes.Ret);
return rpc;
}
}
}

View File

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

View File

@@ -0,0 +1,370 @@
using System;
using System.Collections.Generic;
using Mono.CecilX;
using Mono.CecilX.Cil;
using Mono.CecilX.Rocks;
namespace Mirror.Weaver
{
public static class Readers
{
static Dictionary<TypeReference, MethodReference> readFuncs;
public static void Init()
{
readFuncs = new Dictionary<TypeReference, MethodReference>(new TypeReferenceComparer());
}
internal static void Register(TypeReference dataType, MethodReference methodReference)
{
if (readFuncs.ContainsKey(dataType))
{
// TODO enable this again later.
// Reader has some obsolete functions that were renamed.
// Don't want weaver warnings for all of them.
//Weaver.Warning($"Registering a Read method for {dataType.FullName} when one already exists", methodReference);
}
// we need to import type when we Initialize Readers so import here in case it is used anywhere else
TypeReference imported = Weaver.CurrentAssembly.MainModule.ImportReference(dataType);
readFuncs[imported] = methodReference;
}
static void RegisterReadFunc(TypeReference typeReference, MethodDefinition newReaderFunc)
{
Register(typeReference, newReaderFunc);
Weaver.GeneratedCodeClass.Methods.Add(newReaderFunc);
}
/// <summary>
/// Finds existing reader for type, if non exists trys to create one
/// <para>This method is recursive</para>
/// </summary>
/// <param name="variable"></param>
/// <returns>Returns <see cref="MethodReference"/> or null</returns>
public static MethodReference GetReadFunc(TypeReference variable)
{
if (readFuncs.TryGetValue(variable, out MethodReference foundFunc))
{
return foundFunc;
}
else
{
TypeReference importedVariable = Weaver.CurrentAssembly.MainModule.ImportReference(variable);
return GenerateReader(importedVariable);
}
}
static MethodReference GenerateReader(TypeReference variableReference)
{
// 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())
{
Weaver.Error($"{variableReference.Name} is an unsupported type. Multidimensional arrays are not supported", variableReference);
return null;
}
return GenerateReadCollection(variableReference, variableReference.GetElementType(), nameof(NetworkReaderExtensions.ReadArray));
}
TypeDefinition variableDefinition = variableReference.Resolve();
// check if the type is completely invalid
if (variableDefinition == null)
{
Weaver.Error($"{variableReference.Name} is not a supported type", variableReference);
return null;
}
else if (variableReference.IsByReference)
{
// error??
Weaver.Error($"Cannot pass type {variableReference.Name} by reference", variableReference);
return null;
}
// use existing func for known types
if (variableDefinition.IsEnum)
{
return GenerateEnumReadFunc(variableReference);
}
else if (variableDefinition.Is(typeof(ArraySegment<>)))
{
return GenerateArraySegmentReadFunc(variableReference);
}
else if (variableDefinition.Is(typeof(List<>)))
{
GenericInstanceType genericInstance = (GenericInstanceType)variableReference;
TypeReference elementType = genericInstance.GenericArguments[0];
return GenerateReadCollection(variableReference, elementType, nameof(NetworkReaderExtensions.ReadList));
}
else if (variableReference.IsDerivedFrom<NetworkBehaviour>())
{
return GetNetworkBehaviourReader(variableReference);
}
// check if reader generation is applicable on this type
if (variableDefinition.IsDerivedFrom<UnityEngine.Component>())
{
Weaver.Error($"Cannot generate reader for component type {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
return null;
}
if (variableReference.Is<UnityEngine.Object>())
{
Weaver.Error($"Cannot generate reader for {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
return null;
}
if (variableReference.Is<UnityEngine.ScriptableObject>())
{
Weaver.Error($"Cannot generate reader for {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
return null;
}
if (variableDefinition.HasGenericParameters)
{
Weaver.Error($"Cannot generate reader for generic variable {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
return null;
}
if (variableDefinition.IsInterface)
{
Weaver.Error($"Cannot generate reader for interface {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
return null;
}
if (variableDefinition.IsAbstract)
{
Weaver.Error($"Cannot generate reader for abstract class {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
return null;
}
return GenerateClassOrStructReadFunction(variableReference);
}
static MethodReference GetNetworkBehaviourReader(TypeReference variableReference)
{
// uses generic ReadNetworkBehaviour rather than having weaver create one for each NB
MethodReference generic = WeaverTypes.readNetworkBehaviourGeneric;
MethodReference readFunc = generic.MakeGeneric(variableReference);
// register function so it is added to Reader<T>
// use Register instead of RegisterWriteFunc because this is not a generated function
Register(variableReference, readFunc);
return readFunc;
}
static MethodDefinition GenerateEnumReadFunc(TypeReference variable)
{
MethodDefinition readerFunc = GenerateReaderFunction(variable);
ILProcessor worker = readerFunc.Body.GetILProcessor();
worker.Emit(OpCodes.Ldarg_0);
TypeReference underlyingType = variable.Resolve().GetEnumUnderlyingType();
MethodReference underlyingFunc = GetReadFunc(underlyingType);
worker.Emit(OpCodes.Call, underlyingFunc);
worker.Emit(OpCodes.Ret);
return readerFunc;
}
static MethodDefinition GenerateArraySegmentReadFunc(TypeReference variable)
{
GenericInstanceType genericInstance = (GenericInstanceType)variable;
TypeReference elementType = genericInstance.GenericArguments[0];
MethodDefinition readerFunc = GenerateReaderFunction(variable);
ILProcessor worker = readerFunc.Body.GetILProcessor();
// $array = reader.Read<[T]>()
ArrayType arrayType = elementType.MakeArrayType();
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Call, GetReadFunc(arrayType));
// return new ArraySegment<T>($array);
worker.Emit(OpCodes.Newobj, WeaverTypes.ArraySegmentConstructorReference.MakeHostInstanceGeneric(genericInstance));
worker.Emit(OpCodes.Ret);
return readerFunc;
}
static MethodDefinition GenerateReaderFunction(TypeReference variable)
{
string functionName = "_Read_" + variable.FullName;
// create new reader for this type
MethodDefinition readerFunc = new MethodDefinition(functionName,
MethodAttributes.Public |
MethodAttributes.Static |
MethodAttributes.HideBySig,
variable);
readerFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, WeaverTypes.Import<NetworkReader>()));
readerFunc.Body.InitLocals = true;
RegisterReadFunc(variable, readerFunc);
return readerFunc;
}
static MethodDefinition GenerateReadCollection(TypeReference variable, TypeReference elementType, string readerFunction)
{
MethodDefinition readerFunc = GenerateReaderFunction(variable);
// generate readers for the element
GetReadFunc(elementType);
ModuleDefinition module = Weaver.CurrentAssembly.MainModule;
TypeReference readerExtensions = module.ImportReference(typeof(NetworkReaderExtensions));
MethodReference listReader = Resolvers.ResolveMethod(readerExtensions, Weaver.CurrentAssembly, readerFunction);
GenericInstanceMethod methodRef = new GenericInstanceMethod(listReader);
methodRef.GenericArguments.Add(elementType);
// generates
// return reader.ReadList<T>();
ILProcessor worker = readerFunc.Body.GetILProcessor();
worker.Emit(OpCodes.Ldarg_0); // reader
worker.Emit(OpCodes.Call, methodRef); // Read
worker.Emit(OpCodes.Ret);
return readerFunc;
}
static MethodDefinition GenerateClassOrStructReadFunction(TypeReference variable)
{
MethodDefinition readerFunc = GenerateReaderFunction(variable);
// create local for return value
readerFunc.Body.Variables.Add(new VariableDefinition(variable));
ILProcessor worker = readerFunc.Body.GetILProcessor();
TypeDefinition td = variable.Resolve();
if (!td.IsValueType)
GenerateNullCheck(worker);
CreateNew(variable, worker, td);
ReadAllFields(variable, worker);
worker.Emit(OpCodes.Ldloc_0);
worker.Emit(OpCodes.Ret);
return readerFunc;
}
static void GenerateNullCheck(ILProcessor worker)
{
// if (!reader.ReadBoolean()) {
// return null;
// }
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Call, GetReadFunc(WeaverTypes.Import<bool>()));
Instruction labelEmptyArray = worker.Create(OpCodes.Nop);
worker.Emit(OpCodes.Brtrue, labelEmptyArray);
// return null
worker.Emit(OpCodes.Ldnull);
worker.Emit(OpCodes.Ret);
worker.Append(labelEmptyArray);
}
// Initialize the local variable with a new instance
static void CreateNew(TypeReference variable, ILProcessor worker, TypeDefinition td)
{
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)
{
Weaver.Error($"{variable.Name} can't be deserialized because it has no default constructor", variable);
return;
}
MethodReference ctorRef = Weaver.CurrentAssembly.MainModule.ImportReference(ctor);
worker.Emit(OpCodes.Newobj, ctorRef);
worker.Emit(OpCodes.Stloc_0);
}
}
static void ReadAllFields(TypeReference variable, ILProcessor worker)
{
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);
if (readFunc != null)
{
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Call, readFunc);
}
else
{
Weaver.Error($"{field.Name} has an unsupported type", field);
}
FieldReference fieldRef = Weaver.CurrentAssembly.MainModule.ImportReference(field);
worker.Emit(OpCodes.Stfld, fieldRef);
}
}
/// <summary>
/// Save a delegate for each one of the readers into <see cref="Reader{T}.read"/>
/// </summary>
/// <param name="worker"></param>
internal static void InitializeReaders(ILProcessor worker)
{
ModuleDefinition module = Weaver.CurrentAssembly.MainModule;
TypeReference genericReaderClassRef = module.ImportReference(typeof(Reader<>));
System.Reflection.FieldInfo fieldInfo = typeof(Reader<>).GetField(nameof(Reader<object>.read));
FieldReference fieldRef = module.ImportReference(fieldInfo);
TypeReference networkReaderRef = module.ImportReference(typeof(NetworkReader));
TypeReference funcRef = module.ImportReference(typeof(Func<,>));
MethodReference funcConstructorRef = module.ImportReference(typeof(Func<,>).GetConstructors()[0]);
foreach (KeyValuePair<TypeReference, MethodReference> kvp in readFuncs)
{
TypeReference targetType = kvp.Key;
MethodReference readFunc = kvp.Value;
// create a Func<NetworkReader, T> delegate
worker.Emit(OpCodes.Ldnull);
worker.Emit(OpCodes.Ldftn, readFunc);
GenericInstanceType funcGenericInstance = funcRef.MakeGenericInstanceType(networkReaderRef, targetType);
MethodReference funcConstructorInstance = funcConstructorRef.MakeHostInstanceGeneric(funcGenericInstance);
worker.Emit(OpCodes.Newobj, funcConstructorInstance);
// save it in Reader<T>.read
GenericInstanceType genericInstance = genericReaderClassRef.MakeGenericInstanceType(targetType);
FieldReference specializedField = fieldRef.SpecializeField(genericInstance);
worker.Emit(OpCodes.Stsfld, specializedField);
}
}
}
}

View File

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

View File

@@ -0,0 +1,86 @@
// all the resolve functions for the weaver
// NOTE: these functions should be made extensions, but right now they still
// make heavy use of Weaver.fail and we'd have to check each one's return
// value for null otherwise.
// (original FieldType.Resolve returns null if not found too, so
// exceptions would be a bit inconsistent here)
using Mono.CecilX;
namespace Mirror.Weaver
{
public static class Resolvers
{
public static MethodReference ResolveMethod(TypeReference tr, AssemblyDefinition scriptDef, string name)
{
if (tr == null)
{
Weaver.Error($"Cannot resolve method {name} without a class");
return null;
}
MethodReference method = ResolveMethod(tr, scriptDef, m => m.Name == name);
if (method == null)
{
Weaver.Error($"Method not found with name {name} in type {tr.Name}", tr);
}
return method;
}
public static MethodReference ResolveMethod(TypeReference t, AssemblyDefinition scriptDef, System.Func<MethodDefinition, bool> predicate)
{
foreach (MethodDefinition methodRef in t.Resolve().Methods)
{
if (predicate(methodRef))
{
return scriptDef.MainModule.ImportReference(methodRef);
}
}
Weaver.Error($"Method not found in type {t.Name}", t);
return null;
}
public static MethodReference TryResolveMethodInParents(TypeReference tr, AssemblyDefinition scriptDef, string name)
{
if (tr == null)
{
return null;
}
foreach (MethodDefinition methodRef in tr.Resolve().Methods)
{
if (methodRef.Name == name)
{
return scriptDef.MainModule.ImportReference(methodRef);
}
}
// Could not find the method in this class, try the parent
return TryResolveMethodInParents(tr.Resolve().BaseType, scriptDef, name);
}
public static MethodDefinition ResolveDefaultPublicCtor(TypeReference variable)
{
foreach (MethodDefinition methodRef in variable.Resolve().Methods)
{
if (methodRef.Name == ".ctor" &&
methodRef.Resolve().IsPublic &&
methodRef.Parameters.Count == 0)
{
return methodRef;
}
}
return null;
}
public static MethodReference ResolveProperty(TypeReference tr, AssemblyDefinition scriptDef, string name)
{
foreach (PropertyDefinition pd in tr.Resolve().Properties)
{
if (pd.Name == name)
{
return scriptDef.MainModule.ImportReference(pd.GetMethod);
}
}
return null;
}
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,257 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Mono.CecilX;
namespace Mirror.Weaver
{
// This data is flushed each time - if we are run multiple times in the same process/domain
class WeaverLists
{
// setter functions that replace [SyncVar] member variable references. dict<field, replacement>
public Dictionary<FieldDefinition, MethodDefinition> replacementSetterProperties = new Dictionary<FieldDefinition, MethodDefinition>();
// getter functions that replace [SyncVar] member variable references. dict<field, replacement>
public Dictionary<FieldDefinition, MethodDefinition> replacementGetterProperties = new Dictionary<FieldDefinition, MethodDefinition>();
// amount of SyncVars per class. dict<className, amount>
public Dictionary<string, int> numSyncVars = new Dictionary<string, int>();
public int GetSyncVarStart(string className)
{
return numSyncVars.ContainsKey(className)
? numSyncVars[className]
: 0;
}
public void SetNumSyncVars(string className, int num)
{
numSyncVars[className] = num;
}
}
internal static class Weaver
{
public static string InvokeRpcPrefix => "InvokeUserCode_";
// generated code class
public const string GeneratedCodeNamespace = "Mirror";
public const string GeneratedCodeClassName = "GeneratedNetworkCode";
public static TypeDefinition GeneratedCodeClass;
public static WeaverLists WeaveLists { get; private set; }
public static AssemblyDefinition CurrentAssembly { get; private set; }
public static bool WeavingFailed { get; private set; }
public static bool GenerateLogErrors;
// private properties
static readonly bool DebugLogEnabled = true;
public static void DLog(TypeDefinition td, string fmt, params object[] args)
{
if (!DebugLogEnabled)
return;
Console.WriteLine("[" + td.Name + "] " + string.Format(fmt, args));
}
// display weaver error
// and mark process as failed
public static void Error(string message)
{
Log.Error(message);
WeavingFailed = true;
}
public static void Error(string message, MemberReference mr)
{
Log.Error($"{message} (at {mr})");
WeavingFailed = true;
}
public static void Warning(string message, MemberReference mr)
{
Log.Warning($"{message} (at {mr})");
}
static void CheckMonoBehaviour(TypeDefinition td)
{
if (td.IsDerivedFrom<UnityEngine.MonoBehaviour>())
{
MonoBehaviourProcessor.Process(td);
}
}
static bool WeaveNetworkBehavior(TypeDefinition td)
{
if (!td.IsClass)
return false;
if (!td.IsDerivedFrom<NetworkBehaviour>())
{
CheckMonoBehaviour(td);
return false;
}
// process this and base classes from parent to child order
List<TypeDefinition> behaviourClasses = new List<TypeDefinition>();
TypeDefinition parent = td;
while (parent != null)
{
if (parent.Is<NetworkBehaviour>())
{
break;
}
try
{
behaviourClasses.Insert(0, parent);
parent = parent.BaseType.Resolve();
}
catch (AssemblyResolutionException)
{
// this can happen for plugins.
//Console.WriteLine("AssemblyResolutionException: "+ ex.ToString());
break;
}
}
bool modified = false;
foreach (TypeDefinition behaviour in behaviourClasses)
{
modified |= new NetworkBehaviourProcessor(behaviour).Process();
}
return modified;
}
static bool WeaveModule(ModuleDefinition moduleDefinition)
{
try
{
bool modified = false;
System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();
watch.Start();
foreach (TypeDefinition td in moduleDefinition.Types)
{
if (td.IsClass && td.BaseType.CanBeResolved())
{
modified |= WeaveNetworkBehavior(td);
modified |= ServerClientAttributeProcessor.Process(td);
}
}
watch.Stop();
Console.WriteLine("Weave behaviours and messages took " + watch.ElapsedMilliseconds + " milliseconds");
return modified;
}
catch (Exception ex)
{
Error(ex.ToString());
throw new Exception(ex.Message, ex);
}
}
static void CreateGeneratedCodeClass()
{
// create "Mirror.GeneratedNetworkCode" class
GeneratedCodeClass = new TypeDefinition(GeneratedCodeNamespace, GeneratedCodeClassName,
TypeAttributes.BeforeFieldInit | TypeAttributes.Class | TypeAttributes.AnsiClass | TypeAttributes.Public | TypeAttributes.AutoClass | TypeAttributes.Abstract | TypeAttributes.Sealed,
WeaverTypes.Import<object>());
}
static bool ContainsGeneratedCodeClass(ModuleDefinition module)
{
return module.GetTypes().Any(td => td.Namespace == GeneratedCodeNamespace &&
td.Name == GeneratedCodeClassName);
}
static bool Weave(string assName, IEnumerable<string> dependencies)
{
using (DefaultAssemblyResolver asmResolver = new DefaultAssemblyResolver())
using (CurrentAssembly = AssemblyDefinition.ReadAssembly(assName, new ReaderParameters { ReadWrite = true, ReadSymbols = true, AssemblyResolver = asmResolver }))
{
asmResolver.AddSearchDirectory(Path.GetDirectoryName(assName));
asmResolver.AddSearchDirectory(Helpers.UnityEngineDllDirectoryName());
if (dependencies != null)
{
foreach (string path in dependencies)
{
asmResolver.AddSearchDirectory(path);
}
}
// fix "No writer found for ..." error
// https://github.com/vis2k/Mirror/issues/2579
// -> when restarting Unity, weaver would try to weave a DLL
// again
// -> resulting in two GeneratedNetworkCode classes (see ILSpy)
// -> the second one wouldn't have all the writer types setup
if (ContainsGeneratedCodeClass(CurrentAssembly.MainModule))
{
//Log.Warning($"Weaver: skipping {CurrentAssembly.Name} because already weaved");
return true;
}
WeaverTypes.SetupTargetTypes(CurrentAssembly);
CreateGeneratedCodeClass();
// WeaverList depends on WeaverTypes setup because it uses Import
WeaveLists = new WeaverLists();
System.Diagnostics.Stopwatch rwstopwatch = System.Diagnostics.Stopwatch.StartNew();
// Need to track modified from ReaderWriterProcessor too because it could find custom read/write functions or create functions for NetworkMessages
bool modified = ReaderWriterProcessor.Process(CurrentAssembly);
rwstopwatch.Stop();
Console.WriteLine($"Find all reader and writers took {rwstopwatch.ElapsedMilliseconds} milliseconds");
ModuleDefinition moduleDefinition = CurrentAssembly.MainModule;
Console.WriteLine($"Script Module: {moduleDefinition.Name}");
modified |= WeaveModule(moduleDefinition);
if (WeavingFailed)
{
return false;
}
if (modified)
{
PropertySiteProcessor.Process(moduleDefinition);
// add class that holds read/write functions
moduleDefinition.Types.Add(GeneratedCodeClass);
ReaderWriterProcessor.InitializeReaderAndWriters(CurrentAssembly);
// write to outputDir if specified, otherwise perform in-place write
WriterParameters writeParams = new WriterParameters { WriteSymbols = true };
CurrentAssembly.Write(writeParams);
}
}
return true;
}
public static bool WeaveAssembly(string assembly, IEnumerable<string> dependencies)
{
WeavingFailed = false;
try
{
return Weave(assembly, dependencies);
}
catch (Exception e)
{
Log.Error("Exception :" + e);
return false;
}
}
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,144 @@
using System;
using Mono.CecilX;
namespace Mirror.Weaver
{
public static class WeaverTypes
{
public static MethodReference ScriptableObjectCreateInstanceMethod;
public static MethodReference NetworkBehaviourDirtyBitsReference;
public static MethodReference GetPooledWriterReference;
public static MethodReference RecycleWriterReference;
public static MethodReference ReadyConnectionReference;
public static MethodReference CmdDelegateConstructor;
public static MethodReference NetworkServerGetActive;
public static MethodReference NetworkServerGetLocalClientActive;
public static MethodReference NetworkClientGetActive;
// custom attribute types
public static MethodReference InitSyncObjectReference;
// array segment
public static MethodReference ArraySegmentConstructorReference;
// syncvar
public static MethodReference syncVarEqualReference;
public static MethodReference syncVarNetworkIdentityEqualReference;
public static MethodReference syncVarGameObjectEqualReference;
public static MethodReference setSyncVarReference;
public static MethodReference setSyncVarHookGuard;
public static MethodReference getSyncVarHookGuard;
public static MethodReference setSyncVarGameObjectReference;
public static MethodReference getSyncVarGameObjectReference;
public static MethodReference setSyncVarNetworkIdentityReference;
public static MethodReference getSyncVarNetworkIdentityReference;
public static MethodReference syncVarNetworkBehaviourEqualReference;
public static MethodReference setSyncVarNetworkBehaviourReference;
public static MethodReference getSyncVarNetworkBehaviourReference;
public static MethodReference registerCommandDelegateReference;
public static MethodReference registerRpcDelegateReference;
public static MethodReference getTypeFromHandleReference;
public static MethodReference logErrorReference;
public static MethodReference logWarningReference;
public static MethodReference sendCommandInternal;
public static MethodReference sendRpcInternal;
public static MethodReference sendTargetRpcInternal;
public static MethodReference readNetworkBehaviourGeneric;
static AssemblyDefinition currentAssembly;
public static TypeReference Import<T>() => Import(typeof(T));
public static TypeReference Import(Type t) => currentAssembly.MainModule.ImportReference(t);
public static void SetupTargetTypes(AssemblyDefinition currentAssembly)
{
// system types
WeaverTypes.currentAssembly = currentAssembly;
TypeReference ArraySegmentType = Import(typeof(ArraySegment<>));
ArraySegmentConstructorReference = Resolvers.ResolveMethod(ArraySegmentType, currentAssembly, ".ctor");
TypeReference ListType = Import(typeof(System.Collections.Generic.List<>));
TypeReference NetworkServerType = Import(typeof(NetworkServer));
NetworkServerGetActive = Resolvers.ResolveMethod(NetworkServerType, currentAssembly, "get_active");
NetworkServerGetLocalClientActive = Resolvers.ResolveMethod(NetworkServerType, currentAssembly, "get_localClientActive");
TypeReference NetworkClientType = Import(typeof(NetworkClient));
NetworkClientGetActive = Resolvers.ResolveMethod(NetworkClientType, currentAssembly, "get_active");
TypeReference cmdDelegateReference = Import<RemoteCalls.CmdDelegate>();
CmdDelegateConstructor = Resolvers.ResolveMethod(cmdDelegateReference, currentAssembly, ".ctor");
TypeReference NetworkBehaviourType = Import<NetworkBehaviour>();
TypeReference RemoteCallHelperType = Import(typeof(RemoteCalls.RemoteCallHelper));
TypeReference ScriptableObjectType = Import<UnityEngine.ScriptableObject>();
ScriptableObjectCreateInstanceMethod = Resolvers.ResolveMethod(
ScriptableObjectType, currentAssembly,
md => md.Name == "CreateInstance" && md.HasGenericParameters);
NetworkBehaviourDirtyBitsReference = Resolvers.ResolveProperty(NetworkBehaviourType, currentAssembly, "syncVarDirtyBits");
TypeReference NetworkWriterPoolType = Import(typeof(NetworkWriterPool));
GetPooledWriterReference = Resolvers.ResolveMethod(NetworkWriterPoolType, currentAssembly, "GetWriter");
RecycleWriterReference = Resolvers.ResolveMethod(NetworkWriterPoolType, currentAssembly, "Recycle");
ReadyConnectionReference = Resolvers.ResolveMethod(NetworkClientType, currentAssembly, "get_readyConnection");
syncVarEqualReference = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "SyncVarEqual");
syncVarNetworkIdentityEqualReference = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "SyncVarNetworkIdentityEqual");
syncVarGameObjectEqualReference = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "SyncVarGameObjectEqual");
setSyncVarReference = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "SetSyncVar");
setSyncVarHookGuard = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "setSyncVarHookGuard");
getSyncVarHookGuard = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "getSyncVarHookGuard");
setSyncVarGameObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "SetSyncVarGameObject");
getSyncVarGameObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "GetSyncVarGameObject");
setSyncVarNetworkIdentityReference = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "SetSyncVarNetworkIdentity");
getSyncVarNetworkIdentityReference = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "GetSyncVarNetworkIdentity");
syncVarNetworkBehaviourEqualReference = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "SyncVarNetworkBehaviourEqual");
setSyncVarNetworkBehaviourReference = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "SetSyncVarNetworkBehaviour");
getSyncVarNetworkBehaviourReference = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "GetSyncVarNetworkBehaviour");
registerCommandDelegateReference = Resolvers.ResolveMethod(RemoteCallHelperType, currentAssembly, "RegisterCommandDelegate");
registerRpcDelegateReference = Resolvers.ResolveMethod(RemoteCallHelperType, currentAssembly, "RegisterRpcDelegate");
TypeReference unityDebug = Import(typeof(UnityEngine.Debug));
// these have multiple methods with same name, so need to check parameters too
logErrorReference = Resolvers.ResolveMethod(unityDebug, currentAssembly, (md) =>
{
return md.Name == "LogError" &&
md.Parameters.Count == 1 &&
md.Parameters[0].ParameterType.FullName == typeof(object).FullName;
});
logWarningReference = Resolvers.ResolveMethod(unityDebug, currentAssembly, (md) =>
{
return md.Name == "LogWarning" &&
md.Parameters.Count == 1 &&
md.Parameters[0].ParameterType.FullName == typeof(object).FullName;
});
TypeReference typeType = Import(typeof(Type));
getTypeFromHandleReference = Resolvers.ResolveMethod(typeType, currentAssembly, "GetTypeFromHandle");
sendCommandInternal = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "SendCommandInternal");
sendRpcInternal = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "SendRPCInternal");
sendTargetRpcInternal = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "SendTargetRPCInternal");
InitSyncObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "InitSyncObject");
TypeReference readerExtensions = Import(typeof(NetworkReaderExtensions));
readNetworkBehaviourGeneric = Resolvers.ResolveMethod(readerExtensions, currentAssembly, (md =>
{
return md.Name == nameof(NetworkReaderExtensions.ReadNetworkBehaviour) &&
md.HasGenericParameters;
}));
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2585961bf7fe4c10a9143f4087efdf6f
timeCreated: 1596486854

View File

@@ -0,0 +1,342 @@
using System;
using System.Collections.Generic;
using Mono.CecilX;
using Mono.CecilX.Cil;
using Mono.CecilX.Rocks;
namespace Mirror.Weaver
{
public static class Writers
{
static Dictionary<TypeReference, MethodReference> writeFuncs;
public static void Init()
{
writeFuncs = new Dictionary<TypeReference, MethodReference>(new TypeReferenceComparer());
}
public static void Register(TypeReference dataType, MethodReference 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.
//Weaver.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
TypeReference imported = Weaver.CurrentAssembly.MainModule.ImportReference(dataType);
writeFuncs[imported] = methodReference;
}
static void RegisterWriteFunc(TypeReference typeReference, MethodDefinition newWriterFunc)
{
Register(typeReference, newWriterFunc);
Weaver.GeneratedCodeClass.Methods.Add(newWriterFunc);
}
/// <summary>
/// Finds existing writer for type, if non exists trys to create one
/// <para>This method is recursive</para>
/// </summary>
/// <param name="variable"></param>
/// <returns>Returns <see cref="MethodReference"/> or null</returns>
public static MethodReference GetWriteFunc(TypeReference variable)
{
if (writeFuncs.TryGetValue(variable, out MethodReference foundFunc))
{
return foundFunc;
}
else
{
// this try/catch will be removed in future PR and make `GetWriteFunc` throw instead
try
{
TypeReference importedVariable = Weaver.CurrentAssembly.MainModule.ImportReference(variable);
return GenerateWriter(importedVariable);
}
catch (GenerateWriterException e)
{
Weaver.Error(e.Message, e.MemberReference);
return null;
}
}
}
/// <exception cref="GenerateWriterException">Throws when writer could not be generated for type</exception>
static MethodReference GenerateWriter(TypeReference variableReference)
{
if (variableReference.IsByReference)
{
throw new GenerateWriterException($"Cannot pass {variableReference.Name} by reference", variableReference);
}
// Arrays are special, if we resolve them, we get the element type,
// e.g. int[] resolves to int
// therefore process this before checks below
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));
}
if (variableReference.Resolve()?.IsEnum ?? false)
{
// serialize enum as their base type
return GenerateEnumWriteFunc(variableReference);
}
// check for collections
if (variableReference.Is(typeof(ArraySegment<>)))
{
GenericInstanceType genericInstance = (GenericInstanceType)variableReference;
TypeReference elementType = genericInstance.GenericArguments[0];
return GenerateCollectionWriter(variableReference, elementType, nameof(NetworkWriterExtensions.WriteArraySegment));
}
if (variableReference.Is(typeof(List<>)))
{
GenericInstanceType genericInstance = (GenericInstanceType)variableReference;
TypeReference elementType = genericInstance.GenericArguments[0];
return GenerateCollectionWriter(variableReference, elementType, nameof(NetworkWriterExtensions.WriteList));
}
if (variableReference.IsDerivedFrom<NetworkBehaviour>())
{
return GetNetworkBehaviourWriter(variableReference);
}
// check for invalid types
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);
}
// generate writer for class/struct
return GenerateClassOrStructWriterFunction(variableReference);
}
static MethodReference GetNetworkBehaviourWriter(TypeReference variableReference)
{
// all NetworkBehaviours can use the same write function
if (writeFuncs.TryGetValue(WeaverTypes.Import<NetworkBehaviour>(), out MethodReference 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;
}
else
{
// this exception only happens if mirror is missing the WriteNetworkBehaviour method
throw new MissingMethodException($"Could not find writer for NetworkBehaviour");
}
}
static MethodDefinition GenerateEnumWriteFunc(TypeReference variable)
{
MethodDefinition writerFunc = GenerateWriterFunc(variable);
ILProcessor worker = writerFunc.Body.GetILProcessor();
MethodReference underlyingWriter = GetWriteFunc(variable.Resolve().GetEnumUnderlyingType());
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldarg_1);
worker.Emit(OpCodes.Call, underlyingWriter);
worker.Emit(OpCodes.Ret);
return writerFunc;
}
static MethodDefinition GenerateWriterFunc(TypeReference variable)
{
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)));
writerFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, WeaverTypes.Import<NetworkWriter>()));
writerFunc.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.None, variable));
writerFunc.Body.InitLocals = true;
RegisterWriteFunc(variable, writerFunc);
return writerFunc;
}
static MethodDefinition GenerateClassOrStructWriterFunction(TypeReference variable)
{
MethodDefinition writerFunc = GenerateWriterFunc(variable);
ILProcessor worker = writerFunc.Body.GetILProcessor();
if (!variable.Resolve().IsValueType)
WriteNullCheck(worker);
if (!WriteAllFields(variable, worker))
return null;
worker.Emit(OpCodes.Ret);
return writerFunc;
}
static void WriteNullCheck(ILProcessor worker)
{
// if (value == null)
// {
// writer.WriteBoolean(false);
// return;
// }
//
Instruction labelNotNull = worker.Create(OpCodes.Nop);
worker.Emit(OpCodes.Ldarg_1);
worker.Emit(OpCodes.Brtrue, labelNotNull);
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldc_I4_0);
worker.Emit(OpCodes.Call, GetWriteFunc(WeaverTypes.Import<bool>()));
worker.Emit(OpCodes.Ret);
worker.Append(labelNotNull);
// write.WriteBoolean(true);
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldc_I4_1);
worker.Emit(OpCodes.Call, GetWriteFunc(WeaverTypes.Import<bool>()));
}
/// <summary>
/// Find all fields in type and write them
/// </summary>
/// <param name="variable"></param>
/// <param name="worker"></param>
/// <returns>false if fail</returns>
static bool WriteAllFields(TypeReference variable, ILProcessor worker)
{
uint fields = 0;
foreach (FieldDefinition field in variable.FindAllPublicFields())
{
MethodReference writeFunc = GetWriteFunc(field.FieldType);
// need this null check till later PR when GetWriteFunc throws exception instead
if (writeFunc == null) { return false; }
FieldReference fieldRef = Weaver.CurrentAssembly.MainModule.ImportReference(field);
fields++;
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldarg_1);
worker.Emit(OpCodes.Ldfld, fieldRef);
worker.Emit(OpCodes.Call, writeFunc);
}
return true;
}
static MethodDefinition GenerateCollectionWriter(TypeReference variable, TypeReference elementType, string writerFunction)
{
MethodDefinition writerFunc = GenerateWriterFunc(variable);
MethodReference elementWriteFunc = GetWriteFunc(elementType);
MethodReference intWriterFunc = GetWriteFunc(WeaverTypes.Import<int>());
// need this null check till later PR when GetWriteFunc throws exception instead
if (elementWriteFunc == null)
{
Weaver.Error($"Cannot generate writer for {variable}. Use a supported type or provide a custom writer", variable);
return writerFunc;
}
ModuleDefinition module = Weaver.CurrentAssembly.MainModule;
TypeReference readerExtensions = module.ImportReference(typeof(NetworkWriterExtensions));
MethodReference collectionWriter = Resolvers.ResolveMethod(readerExtensions, Weaver.CurrentAssembly, writerFunction);
GenericInstanceMethod methodRef = new GenericInstanceMethod(collectionWriter);
methodRef.GenericArguments.Add(elementType);
// generates
// reader.WriteArray<T>(array);
ILProcessor worker = writerFunc.Body.GetILProcessor();
worker.Emit(OpCodes.Ldarg_0); // writer
worker.Emit(OpCodes.Ldarg_1); // collection
worker.Emit(OpCodes.Call, methodRef); // WriteArray
worker.Emit(OpCodes.Ret);
return writerFunc;
}
/// <summary>
/// Save a delegate for each one of the writers into <see cref="Writer{T}.write"/>
/// </summary>
/// <param name="worker"></param>
internal static void InitializeWriters(ILProcessor worker)
{
ModuleDefinition module = Weaver.CurrentAssembly.MainModule;
TypeReference genericWriterClassRef = module.ImportReference(typeof(Writer<>));
System.Reflection.FieldInfo fieldInfo = typeof(Writer<>).GetField(nameof(Writer<object>.write));
FieldReference fieldRef = module.ImportReference(fieldInfo);
TypeReference networkWriterRef = module.ImportReference(typeof(NetworkWriter));
TypeReference actionRef = module.ImportReference(typeof(Action<,>));
MethodReference actionConstructorRef = module.ImportReference(typeof(Action<,>).GetConstructors()[0]);
foreach (KeyValuePair<TypeReference, MethodReference> kvp in writeFuncs)
{
TypeReference targetType = kvp.Key;
MethodReference writeFunc = kvp.Value;
// create a Action<NetworkWriter, T> delegate
worker.Emit(OpCodes.Ldnull);
worker.Emit(OpCodes.Ldftn, writeFunc);
GenericInstanceType actionGenericInstance = actionRef.MakeGenericInstanceType(networkWriterRef, targetType);
MethodReference actionRefInstance = actionConstructorRef.MakeHostInstanceGeneric(actionGenericInstance);
worker.Emit(OpCodes.Newobj, actionRefInstance);
// save it in Writer<T>.write
GenericInstanceType genericInstance = genericWriterClassRef.MakeGenericInstanceType(targetType);
FieldReference specializedField = fieldRef.SpecializeField(genericInstance);
worker.Emit(OpCodes.Stsfld, specializedField);
}
}
}
}

View File

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